500 lines
20 KiB
HTML
500 lines
20 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Install Verae timestamping</title>
|
||
<meta name="description" content="Download a local hasher, connect to the Verae MCP timestamping server, and stamp digests without uploading files.">
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;9..144,600&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||
<link rel="icon" href="assets/VeraeFullLogo.png">
|
||
<style>
|
||
:root {
|
||
--ink: oklch(0.21 0.020 310);
|
||
--ink-soft: oklch(0.42 0.018 310);
|
||
--ink-mute: oklch(0.60 0.014 310);
|
||
--paper: oklch(0.992 0.004 90);
|
||
--soft: oklch(0.972 0.008 85);
|
||
--hair: oklch(0.90 0.010 80);
|
||
--purple: oklch(0.32 0.110 310);
|
||
--purple-mid: oklch(0.42 0.130 312);
|
||
--purple-wash: oklch(0.96 0.020 312);
|
||
--gold: oklch(0.80 0.090 85);
|
||
--dark: oklch(0.19 0.022 312);
|
||
--ease: cubic-bezier(0.16, 1, 0.3, 1);
|
||
--ok: oklch(0.55 0.12 145);
|
||
}
|
||
* { box-sizing: border-box; }
|
||
html { scroll-behavior: smooth; }
|
||
body {
|
||
margin: 0;
|
||
font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
|
||
color: var(--ink);
|
||
background: var(--paper);
|
||
line-height: 1.55;
|
||
}
|
||
a { color: var(--purple); }
|
||
header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
max-width: 1100px;
|
||
margin: 0 auto;
|
||
padding: 28px 28px 0;
|
||
}
|
||
.brand img { height: 46px; width: auto; display: block; }
|
||
nav { display: flex; gap: 22px; font-size: 14px; font-weight: 600; flex-wrap: wrap; }
|
||
nav a { color: var(--ink-soft); text-decoration: none; }
|
||
nav a:hover { color: var(--purple); }
|
||
.hero {
|
||
max-width: 1100px;
|
||
margin: 0 auto;
|
||
padding: 64px 28px 24px;
|
||
}
|
||
.eyebrow {
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.14em;
|
||
text-transform: uppercase;
|
||
color: var(--purple-mid);
|
||
}
|
||
h1 {
|
||
font-family: Fraunces, Georgia, serif;
|
||
font-weight: 560;
|
||
font-size: clamp(40px, 6vw, 68px);
|
||
line-height: 1.05;
|
||
letter-spacing: -0.03em;
|
||
margin: 12px 0 18px;
|
||
max-width: 18ch;
|
||
}
|
||
.lede { max-width: 42rem; font-size: 18px; color: var(--ink-soft); margin: 0; }
|
||
.wrap { max-width: 1100px; margin: 0 auto; padding: 12px 28px 80px; }
|
||
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; margin-top: 36px; }
|
||
.card {
|
||
background: white;
|
||
border: 1px solid var(--hair);
|
||
border-radius: 16px;
|
||
padding: 22px 22px 18px;
|
||
}
|
||
.card h2 { font-size: 18px; margin: 0 0 8px; }
|
||
.card p { margin: 0; color: var(--ink-soft); font-size: 15px; }
|
||
.step { font-size: 12px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--purple-mid); }
|
||
h2.section {
|
||
font-family: Fraunces, Georgia, serif;
|
||
font-size: 34px;
|
||
font-weight: 560;
|
||
margin: 56px 0 8px;
|
||
}
|
||
.section-note { color: var(--ink-soft); max-width: 44rem; margin-top: 0; }
|
||
pre {
|
||
background: var(--dark);
|
||
color: oklch(0.95 0.01 90);
|
||
border-radius: 14px;
|
||
padding: 18px 20px;
|
||
overflow: auto;
|
||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||
font-size: 13.5px;
|
||
line-height: 1.5;
|
||
}
|
||
code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.92em; }
|
||
.path { background: var(--purple-wash); padding: 2px 6px; border-radius: 6px; }
|
||
ol.steps { padding-left: 1.2rem; max-width: 44rem; }
|
||
ol.steps li { margin: 0.55rem 0; }
|
||
.gold { height: 3px; width: 72px; background: var(--gold); border: 0; margin: 28px 0; }
|
||
.detect {
|
||
background: white;
|
||
border: 1px solid var(--hair);
|
||
border-radius: 18px;
|
||
padding: 22px;
|
||
margin-top: 22px;
|
||
}
|
||
.detect .status { font-size: 15px; color: var(--ink-soft); margin: 0 0 14px; }
|
||
.detect .status strong { color: var(--ink); }
|
||
.btn-row { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
|
||
.btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
background: var(--purple);
|
||
color: white;
|
||
text-decoration: none;
|
||
border: 0;
|
||
border-radius: 999px;
|
||
padding: 12px 18px;
|
||
font: inherit;
|
||
font-weight: 600;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
}
|
||
.btn.secondary {
|
||
background: white;
|
||
color: var(--purple);
|
||
border: 1px solid var(--hair);
|
||
}
|
||
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||
.meta { font-size: 13px; color: var(--ink-mute); margin-top: 12px; }
|
||
.ok { color: var(--ok); font-weight: 600; }
|
||
table.artifacts {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
margin-top: 16px;
|
||
font-size: 14px;
|
||
}
|
||
table.artifacts th, table.artifacts td {
|
||
text-align: left;
|
||
padding: 10px 8px;
|
||
border-bottom: 1px solid var(--hair);
|
||
}
|
||
table.artifacts th { color: var(--ink-mute); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em; }
|
||
footer {
|
||
border-top: 1px solid var(--hair);
|
||
padding: 28px;
|
||
color: var(--ink-mute);
|
||
font-size: 14px;
|
||
}
|
||
footer .wrap { padding: 0; display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
|
||
@media (max-width: 800px) {
|
||
.grid { grid-template-columns: 1fr; }
|
||
header { padding-top: 18px; }
|
||
nav { gap: 14px; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<header>
|
||
<a class="brand" href="https://www.verae.com/"><img src="assets/VeraeFullLogo.png" alt="Verae"></a>
|
||
<nav>
|
||
<a href="#hasher">Hasher</a>
|
||
<a href="#connect">Connect</a>
|
||
<a href="#register">Register</a>
|
||
<a href="ways.html">Ways to use</a>
|
||
<a href="developers.html">Developers</a>
|
||
<a href="sample.html">Try a stamp</a>
|
||
</nav>
|
||
</header>
|
||
<main>
|
||
<section class="hero">
|
||
<p class="eyebrow">Model Context Protocol</p>
|
||
<h1>Hash locally. Stamp remotely.</h1>
|
||
<p class="lede">Download a small <code>verae-sha256</code> binary for your OS (or use WASM on mobile/browser). Digests go to the MCP server. File bytes stay on your machine.</p>
|
||
<hr class="gold">
|
||
</section>
|
||
<div class="wrap">
|
||
<section id="hasher">
|
||
<h2 class="section">Get the local hasher</h2>
|
||
<p class="section-note">No repo clone. This page detects your platform, fetches the matching published binary from <code>/hasher/</code>, and falls back to WASM/JS when a native build is not available.</p>
|
||
<div class="detect" id="hasher-panel">
|
||
<p class="status" id="hasher-status">Detecting platform…</p>
|
||
<div class="btn-row" id="hasher-actions"></div>
|
||
<p class="meta" id="hasher-meta"></p>
|
||
<pre id="hasher-snippet" hidden></pre>
|
||
</div>
|
||
<h3 style="margin-top:36px;font-size:18px;">All published binaries</h3>
|
||
<table class="artifacts" id="artifact-table">
|
||
<thead><tr><th>Target</th><th>File</th><th>Size</th><th>SHA-256</th></tr></thead>
|
||
<tbody></tbody>
|
||
</table>
|
||
<p class="meta">Manifest: <a id="manifest-link" href="/hasher/latest.json">/hasher/latest.json</a> · Source rebuild: <code>./scripts/publish-hasher.sh</code> in the client repo.</p>
|
||
</section>
|
||
|
||
<section id="connect">
|
||
<h2 class="section">Connect an MCP client</h2>
|
||
<p class="section-note">Point Claude, Cursor, or another MCP client at this host. You do not need to run a local server.</p>
|
||
<pre id="mcp-config">{
|
||
"mcpServers": {
|
||
"verae-timestamping": {
|
||
"url": "https://HOST/mcp"
|
||
}
|
||
}
|
||
}</pre>
|
||
<p class="section-note">Public endpoints: <code id="mcp-url-primary">https://HOST/mcp</code>
|
||
· also <a href="https://mcp.veraetime.net/mcp">mcp.veraetime.net/mcp</a>
|
||
· <a href="https://mcp.georgelambert.org/mcp">mcp.georgelambert.org/mcp</a></p>
|
||
|
||
<h2 class="section">Hash a file, then stamp</h2>
|
||
<pre id="hash-flow"># after installing the binary as verae-sha256 on your PATH:
|
||
DIGEST=$(verae-sha256 ./document.pdf)
|
||
echo "$DIGEST"
|
||
# then ask your MCP client to call tool "stamp" with { "sha256": "$DIGEST", "scope": "global" }
|
||
# or try the browser sample:
|
||
# open sample.html and paste the digest</pre>
|
||
</section>
|
||
|
||
<section id="register">
|
||
<h2 class="section">Optional: register on your machine</h2>
|
||
<p class="section-note">For a local <code>uvx</code> server (or global stamps that need a VeraeTime account), register once. You still do not clone a repository.</p>
|
||
<pre>uvx verae-mcp-timestamping-server setup</pre>
|
||
<ol class="steps">
|
||
<li>Choose <strong>new</strong> to create a VeraeTime account, or <strong>existing</strong> to log in.</li>
|
||
<li>It writes <span class="path">~/.verae/auth.properties</span> with mode <code>600</code>. It does not print the token.</li>
|
||
<li>Check later with <code>uvx verae-mcp-timestamping-server session</code>.</li>
|
||
</ol>
|
||
<div class="grid">
|
||
<article class="card">
|
||
<p class="step">Claude Desktop</p>
|
||
<h2>macOS and Windows</h2>
|
||
<p>macOS: <span class="path">~/Library/Application Support/Claude/claude_desktop_config.json</span>. Windows: <span class="path">%APPDATA%\Claude\claude_desktop_config.json</span>.</p>
|
||
</article>
|
||
<article class="card">
|
||
<p class="step">Cursor</p>
|
||
<h2>Project or user</h2>
|
||
<p>User file: <span class="path">~/.cursor/mcp.json</span>. Project file: <span class="path">.cursor/mcp.json</span>.</p>
|
||
</article>
|
||
<article class="card">
|
||
<p class="step">Local uvx</p>
|
||
<h2>Run on this computer</h2>
|
||
<p>Use <code>command: uvx</code> / <code>args: ["verae-mcp-timestamping-server"]</code> instead of a remote URL when you want the process on your machine.</p>
|
||
</article>
|
||
</div>
|
||
</section>
|
||
|
||
<section id="token">
|
||
<h2 class="section">Where credentials live</h2>
|
||
<div class="grid">
|
||
<article class="card">
|
||
<p class="step">Your machine</p>
|
||
<h2>~/.verae/auth.properties</h2>
|
||
<p>Mode 600. Holds the JWT for local/global stamps. Do not commit this file.</p>
|
||
</article>
|
||
<article class="card">
|
||
<p class="step">Your assistant</p>
|
||
<h2>OAuth store</h2>
|
||
<p>Browser login on the public server keeps tokens in the assistant’s OAuth store, not in mcp.json.</p>
|
||
</article>
|
||
<article class="card">
|
||
<p class="step">Never</p>
|
||
<h2>Chat or mcp.json</h2>
|
||
<p>Do not paste tokens into a conversation or client config.</p>
|
||
</article>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</main>
|
||
<footer>
|
||
<div class="wrap">
|
||
<span>Verae timestamping · <a href="https://www.verae.com/">verae.com</a></span>
|
||
<span><a href="sample.html">Try a stamp</a> · <a href="/hasher/latest.json">hasher latest</a> · <a href="https://api.veraetime.net/docs/swagger/">API docs</a></span>
|
||
</div>
|
||
</footer>
|
||
<script>
|
||
(function () {
|
||
const HASHER_BASE = (location.origin || "https://mcp.georgelambert.org") + "/hasher";
|
||
// On veraetime host, hasher artifacts still live on georgelambert CDN-style path.
|
||
const ARTIFACT_BASES = [
|
||
HASHER_BASE,
|
||
"https://mcp.georgelambert.org/hasher"
|
||
];
|
||
const host = location.hostname || "mcp.georgelambert.org";
|
||
const mcpUrl = "https://" + host + "/mcp";
|
||
document.getElementById("mcp-config").textContent =
|
||
`{
|
||
"mcpServers": {
|
||
"verae-timestamping": {
|
||
"url": "${mcpUrl}"
|
||
}
|
||
}
|
||
}`;
|
||
document.getElementById("mcp-url-primary").textContent = mcpUrl;
|
||
|
||
function detectPlatform() {
|
||
const uaData = navigator.userAgentData;
|
||
let os = null, arch = null;
|
||
if (uaData && uaData.platform) {
|
||
const p = uaData.platform.toLowerCase();
|
||
if (p.includes("mac")) os = "darwin";
|
||
else if (p.includes("win")) os = "windows";
|
||
else if (p.includes("linux") || p.includes("chrome os")) os = "linux";
|
||
else if (p.includes("android")) os = "android";
|
||
else if (p.includes("ios") || p.includes("iphone") || p.includes("ipad")) os = "ios";
|
||
}
|
||
const ua = navigator.userAgent || "";
|
||
const plat = (navigator.platform || "").toLowerCase();
|
||
if (!os) {
|
||
if (/android/i.test(ua)) os = "android";
|
||
else if (/iphone|ipad|ipod/i.test(ua)) os = "ios";
|
||
else if (/mac/i.test(plat) || /mac os/i.test(ua)) os = "darwin";
|
||
else if (/win/i.test(plat) || /windows/i.test(ua)) os = "windows";
|
||
else if (/linux/i.test(plat) || /linux/i.test(ua)) os = "linux";
|
||
}
|
||
// arch hints
|
||
if (uaData && Array.isArray(uaData.architecture)) {
|
||
// not standard; ignore
|
||
}
|
||
if (/aarch64|arm64|windows arm/i.test(ua) || /arm64/i.test(plat)) arch = "aarch64";
|
||
else if (/x86_64|win64|wow64|amd64/i.test(ua) || /x86_64|macintel/i.test(plat)) arch = "x86_64";
|
||
// Apple Silicon often reports MacIntel in platform historically; prefer arm on modern Macs when possible
|
||
if (os === "darwin" && !arch) {
|
||
// Safari on Apple Silicon still often says MacIntel; default arm64 for darwin when unknown in 2026
|
||
arch = "aarch64";
|
||
}
|
||
if (os === "windows" && !arch) arch = "x86_64";
|
||
if (os === "linux" && !arch) arch = "x86_64";
|
||
|
||
// Optional URL override: ?libc=musl|gnu on Linux
|
||
const params = new URLSearchParams(location.search);
|
||
const libcPref = (params.get("libc") || "").toLowerCase();
|
||
const wantMusl = libcPref === "musl";
|
||
|
||
let key = null;
|
||
let label = "unknown";
|
||
let kind = "wasm";
|
||
if (os === "darwin" && arch === "aarch64") { key = "aarch64-apple-darwin"; label = "macOS Apple Silicon"; kind = "native"; }
|
||
else if (os === "darwin" && arch === "x86_64") { key = "x86_64-apple-darwin"; label = "macOS Intel"; kind = "native"; }
|
||
else if (os === "linux" && arch === "x86_64") {
|
||
key = wantMusl ? "x86_64-unknown-linux-musl" : "x86_64-unknown-linux-gnu";
|
||
label = wantMusl ? "Linux x86_64 (musl/static)" : "Linux x86_64 (glibc)";
|
||
kind = "native";
|
||
}
|
||
else if (os === "linux" && arch === "aarch64") {
|
||
key = wantMusl ? "aarch64-unknown-linux-musl" : "aarch64-unknown-linux-gnu";
|
||
label = wantMusl ? "Linux aarch64 (musl/static)" : "Linux aarch64 (glibc)";
|
||
kind = "native";
|
||
}
|
||
else if (os === "windows" && arch === "x86_64") { key = "x86_64-pc-windows-gnu"; label = "Windows x86_64"; kind = "native"; }
|
||
else if (os === "windows" && arch === "aarch64") { key = "aarch64-pc-windows-gnullvm"; label = "Windows ARM64"; kind = "native"; }
|
||
else if (os === "android" || os === "ios") { label = os === "ios" ? "iOS / iPadOS" : "Android"; kind = "wasm"; }
|
||
else { label = (os || "browser") + (arch ? (" / " + arch) : ""); kind = "wasm"; }
|
||
|
||
return { os, arch, key, label, kind };
|
||
}
|
||
|
||
function fmtSize(n) {
|
||
if (n < 1024) return n + " B";
|
||
if (n < 1024 * 1024) return (n / 1024).toFixed(1) + " KB";
|
||
return (n / (1024 * 1024)).toFixed(2) + " MB";
|
||
}
|
||
|
||
async function fetchJson(urls) {
|
||
let lastErr;
|
||
for (const url of urls) {
|
||
try {
|
||
const res = await fetch(url, { cache: "no-cache" });
|
||
if (!res.ok) throw new Error(res.status + " " + url);
|
||
return { data: await res.json(), url };
|
||
} catch (e) { lastErr = e; }
|
||
}
|
||
throw lastErr || new Error("fetch failed");
|
||
}
|
||
|
||
function installSnippet(fileUrl, key, isWindows) {
|
||
if (isWindows) {
|
||
return `# PowerShell
|
||
Invoke-WebRequest -Uri "${fileUrl}" -OutFile verae-sha256.exe
|
||
Get-FileHash .\\verae-sha256.exe -Algorithm SHA256
|
||
.\\verae-sha256.exe .\\document.pdf`;
|
||
}
|
||
const name = "verae-sha256";
|
||
return `# curl
|
||
curl -fsSL -o ${name} "${fileUrl}"
|
||
chmod +x ${name}
|
||
shasum -a 256 ${name} # or: sha256sum ${name}
|
||
./${name} ./document.pdf
|
||
|
||
# wget
|
||
wget -O ${name} "${fileUrl}"
|
||
chmod +x ${name}
|
||
./${name} ./document.pdf`;
|
||
}
|
||
|
||
async function run() {
|
||
const status = document.getElementById("hasher-status");
|
||
const actions = document.getElementById("hasher-actions");
|
||
const meta = document.getElementById("hasher-meta");
|
||
const snippet = document.getElementById("hasher-snippet");
|
||
const det = detectPlatform();
|
||
|
||
let latest, manifest, baseUsed;
|
||
try {
|
||
const latestRes = await fetchJson(ARTIFACT_BASES.map(b => b + "/latest.json"));
|
||
latest = latestRes.data;
|
||
// Prefer manifest URL from latest, else construct
|
||
const manifestUrls = [];
|
||
if (latest.manifest) manifestUrls.push(latest.manifest);
|
||
for (const b of ARTIFACT_BASES) {
|
||
manifestUrls.push(b + "/" + latest.version + "/manifest.json");
|
||
}
|
||
const manRes = await fetchJson([...new Set(manifestUrls)]);
|
||
manifest = manRes.data;
|
||
baseUsed = (manifest.base_url || "").replace(/\/$/, "");
|
||
} catch (err) {
|
||
status.innerHTML = "Could not load hasher manifest: <code>" + String(err) + "</code>";
|
||
return;
|
||
}
|
||
|
||
document.getElementById("manifest-link").href = latest.manifest || (baseUsed + "/manifest.json");
|
||
document.getElementById("manifest-link").textContent = latest.manifest || (baseUsed + "/manifest.json");
|
||
|
||
// fill table
|
||
const tbody = document.querySelector("#artifact-table tbody");
|
||
tbody.innerHTML = "";
|
||
const arts = manifest.artifacts || {};
|
||
Object.keys(arts).sort().forEach(k => {
|
||
const a = arts[k];
|
||
const tr = document.createElement("tr");
|
||
const url = baseUsed + "/" + a.file;
|
||
tr.innerHTML = "<td><code>" + k + "</code></td><td><a href=\"" + url + "\">" + a.file + "</a></td><td>" + fmtSize(a.size) + "</td><td><code style=\"font-size:11px\">" + a.sha256.slice(0, 16) + "…</code></td>";
|
||
tbody.appendChild(tr);
|
||
});
|
||
if (manifest.wasm) {
|
||
const tr = document.createElement("tr");
|
||
tr.innerHTML = "<td><code>wasm32</code> (browser/mobile)</td><td><a href=\"" + baseUsed + "/" + manifest.wasm.module + "\">" + manifest.wasm.module + "</a> · <a href=\"" + baseUsed + "/" + manifest.wasm.js + "\">" + manifest.wasm.js + "</a></td><td>—</td><td><code style=\"font-size:11px\">" + (manifest.wasm.sha256 || "").slice(0, 16) + "…</code></td>";
|
||
tbody.appendChild(tr);
|
||
}
|
||
|
||
const art = det.key ? arts[det.key] : null;
|
||
if (det.kind === "native" && art) {
|
||
const fileUrl = baseUsed + "/" + art.file;
|
||
const isWin = det.key.indexOf("windows") !== -1;
|
||
status.innerHTML = "Detected <strong>" + det.label + "</strong> → <code>" + det.key + "</code> · hasher <strong>v" + manifest.version + "</strong>";
|
||
const a = document.createElement("a");
|
||
a.className = "btn";
|
||
a.href = fileUrl;
|
||
a.textContent = "Download " + art.file;
|
||
actions.appendChild(a);
|
||
const copy = document.createElement("button");
|
||
copy.className = "btn secondary";
|
||
copy.type = "button";
|
||
copy.textContent = "Show install commands";
|
||
copy.onclick = () => {
|
||
snippet.hidden = false;
|
||
snippet.textContent = installSnippet(fileUrl, det.key, isWin);
|
||
};
|
||
actions.appendChild(copy);
|
||
meta.innerHTML = "SHA-256 <code>" + art.sha256 + "</code> · " + fmtSize(art.size) + " · verify against <a href=\"" + baseUsed + "/SHA256SUMS\">SHA256SUMS</a>";
|
||
snippet.hidden = false;
|
||
snippet.textContent = installSnippet(fileUrl, det.key, isWin);
|
||
} else {
|
||
status.innerHTML = "Detected <strong>" + det.label + "</strong>. No native binary for this environment — use the <strong>WASM/JS</strong> hasher (browser/mobile).";
|
||
if (manifest.wasm) {
|
||
const wa = document.createElement("a");
|
||
wa.className = "btn";
|
||
wa.href = baseUsed + "/" + manifest.wasm.module;
|
||
wa.textContent = "Download " + manifest.wasm.module;
|
||
actions.appendChild(wa);
|
||
const ja = document.createElement("a");
|
||
ja.className = "btn secondary";
|
||
ja.href = baseUsed + "/" + manifest.wasm.js;
|
||
ja.textContent = manifest.wasm.js;
|
||
actions.appendChild(ja);
|
||
const tryBtn = document.createElement("a");
|
||
tryBtn.className = "btn secondary";
|
||
tryBtn.href = "sample.html";
|
||
tryBtn.textContent = "Open stamp sample";
|
||
actions.appendChild(tryBtn);
|
||
meta.innerHTML = "WASM SHA-256 <code>" + manifest.wasm.sha256 + "</code>. Import <code>" + manifest.wasm.js + "</code> in a page or worker; hash bytes locally, then call MCP <code>stamp</code> with the hex digest only.";
|
||
snippet.hidden = false;
|
||
snippet.textContent =
|
||
`import init, { sha256_hex_js, Sha256Stream } from "${baseUsed}/${manifest.wasm.js}";
|
||
await init();
|
||
const digest = sha256_hex_js(new TextEncoder().encode("hello"));
|
||
// or stream a File via Sha256Stream.update(chunk) / .finish()
|
||
// then POST stamp { sha256: digest } to ${mcpUrl}`;
|
||
}
|
||
}
|
||
}
|
||
run();
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|