@rel-time/mcp-server 1.2.7 → 1.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -7
- package/index.js +1081 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,8 @@ Official Model Context Protocol (MCP) server for **[Rel-Time](https://rel-time.c
|
|
|
6
6
|
3. **Anchor SHA-256 hashes to the Bitcoin Timechain & Rel-Time VDF** (`POST /v1/anchor`).
|
|
7
7
|
4. **Generate & overlay 64-ray Sun-Stamps onto PDFs and Images** (`reltime-stamped-<name>.pdf` / `.png`).
|
|
8
8
|
5. **Verify Bitcoin block settlement & Merkle proofs** (`GET /v1/proof/{hash}`).
|
|
9
|
-
6. **Self-provision or Reload
|
|
9
|
+
6. **Self-provision or Reload all 4 Rel-Time Tiers via Bitcoin Lightning or Stripe Card** (`Universal Anchor $3`, `Agent Launch Pack $5 / 50`, `Autonomous Agents $50 / 500`, and `Rel-Time Superhighway $600 / 5,000`).
|
|
10
|
+
7. **Run a Superhighway Cryptographic Audit Trail & Local Folder Integrity Check** (`reltime_audit_trail`), generating an interactive HTML Audit Dashboard with Corporate Hardware Passkey (`signer_public_key`) attribution and Bitcoin block verification.
|
|
10
11
|
|
|
11
12
|
---
|
|
12
13
|
|
|
@@ -25,11 +26,13 @@ Add the following to your `.mcp.json`, `claude_desktop_config.json`, or `.cursor
|
|
|
25
26
|
}
|
|
26
27
|
```
|
|
27
28
|
|
|
28
|
-
When you start a fresh chat and ask to anchor a file or provision a key, `@rel-time/mcp-server` automatically prompts your agent to ask which
|
|
29
|
-
1. **
|
|
30
|
-
2. **
|
|
29
|
+
When you start a fresh chat and ask to anchor a file or provision a key, `@rel-time/mcp-server` automatically prompts your agent to ask which tier and payment method (**Bitcoin Lightning** or **Stripe Card**) you want first:
|
|
30
|
+
1. **Universal Anchor (`pack: "universal"`, `$3 / anchor`):** Priority 10-Minute Bitcoin Queue · Standard Mode (Data Integrity + Biometric or Anonymous).
|
|
31
|
+
2. **Agent Launch Pack (`pack: "launch"`, `$5 / 50 Hashes`):** Priority 10-Minute Bitcoin Queue · Standard Mode · Includes 64-Ray Sun-Stamp, 1-Click Stamped Copy (`.PDF`/`.PNG`), and `.JSON` Doomsday Proof.
|
|
32
|
+
3. **Autonomous Agents (`pack: "standard"`, `$50 / 500 Hashes`):** Dynamic 6-Hour Batch Queue · Supports both Temporal Mode (`T_index` sub-second VDF sequencing) and Standard Mode · Reloadable anytime (`+500` credits / `$50`).
|
|
33
|
+
4. **Rel-Time Superhighway (`pack: "enterprise"`, `$600 / 5,000 Hashes`):** Priority 10-Minute Bitcoin Queue · Temporal Mode (`T_index`) + Standard Mode + Bulk Folder & Superhighway Audit Dashboard.
|
|
31
34
|
|
|
32
|
-
Once paid via Lightning (or if you pass an existing `rel_key_...` key), the MCP server automatically saves your API key to `~/.reltime/api_key.json` (`0600` permissions) — no app restart or `.zshrc` edits required.
|
|
35
|
+
Once paid via Lightning or Stripe (or if you pass an existing `rel_key_...` key), the MCP server automatically saves your API key to `~/.reltime/api_key.json` (`0600` permissions) — no app restart or `.zshrc` edits required.
|
|
33
36
|
|
|
34
37
|
---
|
|
35
38
|
|
|
@@ -37,11 +40,12 @@ Once paid via Lightning (or if you pass an existing `rel_key_...` key), the MCP
|
|
|
37
40
|
|
|
38
41
|
| Tool Name | Requires Key? | What It Does |
|
|
39
42
|
| :--- | :---: | :--- |
|
|
40
|
-
| `reltime_anchor_file` | Yes | Computes the SHA-256 hash of a local file **on your machine** (0 bytes uploaded), supports `biometric: true` for **Biometric Anchors** (TouchID/FaceID/YubiKey), anchors via `POST /v1/anchor`, saves `reltime-stamp-<filename>.svg` and `reltime-doomsday-proof-<filename>.json`, and automatically outputs a **Stamped Copy** (`reltime-stamped-<name>.pdf` or `.png`). |
|
|
43
|
+
| `reltime_anchor_file` | Yes | Computes the SHA-256 hash of a local file **on your machine** (0 bytes uploaded), supports `biometric: true` for **Biometric Anchors** (TouchID/FaceID/YubiKey), anchors via `POST /v1/anchor`, saves `reltime-stamp-<filename>.svg` and `reltime-doomsday-proof-<filename>.json`, and automatically outputs a clickable **Stamped Copy** (`reltime-stamped-<name>.pdf` or `.png`). |
|
|
44
|
+
| `reltime_audit_trail` | Yes | Fetches the account's chronological anchor ledger (`GET /v1/account/history`), aggregates Bitcoin block settlements, VDF `T_index` ticks, and Corporate Hardware Passkey (`signer_public_key`) signatures, optionally verifies a local file or entire folder bit-for-bit (`verify_local_path`), and spins up a self-contained interactive HTML Audit Report (`reltime-audit-report-*.html`) in your browser. |
|
|
41
45
|
| `reltime_link_passkey` | **No** | Opens `https://rel-time.com/mcp-passkey` in the system browser so the user can touch TouchID / FaceID / YubiKey once, saving `{ public_key_hex, passkey_signature }` to `~/.reltime/passkey.json` for seamless Biometric Anchors. |
|
|
42
46
|
| `reltime_stamp_file` | **No** | Overlays the authentic 64-ray Sun-Stamp onto the bottom-right corner of any local PDF or Image (`.png`, `.jpg`, `.jpeg`, `.webp`, `.gif`) 100% locally. |
|
|
43
47
|
| `reltime_anchor_hash` | Yes | Anchors any 64-char hex SHA-256 hash in `standard` (10-min Express / Integrity) or `temporal` (VDF sequence ordering) mode. |
|
|
44
48
|
| `reltime_get_sun_stamp` | **No** | Fetches the deterministic 64-ray SVG Sun-Stamp (`GET /v1/stamp/{hash}`) and returns ready-to-paste Markdown/HTML badge snippets. |
|
|
45
49
|
| `reltime_check_proof` | **No** | Queries `GET /v1/proof/{hash}` for Bitcoin TXID, Block Height, UTC Timestamp, and Merkle inclusion path, and optionally saves/updates the `.json` Doomsday Proof receipt on disk. |
|
|
46
50
|
| `reltime_account_status` | Yes | Checks remaining Hash Credits and tier status (`GET /v1/account`). |
|
|
47
|
-
| `reltime_provision_agent_key` | **No** |
|
|
51
|
+
| `reltime_provision_agent_key` | **No** | Provisions or reloads any of the 4 Rel-Time Tiers (`universal` `$3`, `launch` `$5/50`, `standard` `$50/500`, `enterprise` `$600/5,000`) via **Bitcoin Lightning** or **Stripe Card**. |
|
package/index.js
CHANGED
|
@@ -56,7 +56,7 @@ let LAST_INVOICE_PACK = null;
|
|
|
56
56
|
|
|
57
57
|
const SERVER_INFO = {
|
|
58
58
|
name: "rel-time-mcp-server",
|
|
59
|
-
version: "1.2.
|
|
59
|
+
version: "1.2.8",
|
|
60
60
|
};
|
|
61
61
|
|
|
62
62
|
/**
|
|
@@ -477,6 +477,629 @@ async function stampLocalFileOptionA(filePath, svgContent, customOutputPath = nu
|
|
|
477
477
|
}
|
|
478
478
|
}
|
|
479
479
|
|
|
480
|
+
/**
|
|
481
|
+
* Extracts an embedded Rel-Time verification hash from a Stamped Copy (.png or .pdf) if present.
|
|
482
|
+
*/
|
|
483
|
+
function extractStampedCopyEmbeddedHash(filePath, fileBuffer) {
|
|
484
|
+
try {
|
|
485
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
486
|
+
if (ext === ".png" || ext === ".pdf" || ext === ".svg") {
|
|
487
|
+
const latin1 = fileBuffer.toString("latin1");
|
|
488
|
+
const m = latin1.match(/rel-time\.com\/verify\?t=([0-9a-fA-F]{64})/);
|
|
489
|
+
if (m && m[1]) {
|
|
490
|
+
return m[1].toLowerCase();
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
} catch (e) {
|
|
494
|
+
// Ignore extraction errors
|
|
495
|
+
}
|
|
496
|
+
return null;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function escapeHtml(str) {
|
|
500
|
+
return String(str == null ? "" : str)
|
|
501
|
+
.replace(/&/g, "&")
|
|
502
|
+
.replace(/</g, "<")
|
|
503
|
+
.replace(/>/g, ">")
|
|
504
|
+
.replace(/"/g, """)
|
|
505
|
+
.replace(/'/g, "'");
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Generates a self-contained, interactive Rel-Time Superhighway Cryptographic Audit Trail HTML report.
|
|
510
|
+
*/
|
|
511
|
+
function generateAuditReportHtml(audit) {
|
|
512
|
+
const generatedUtc = audit.generated_at_utc || new Date().toISOString();
|
|
513
|
+
const acct = audit.account_overview || {};
|
|
514
|
+
const sum = audit.audit_summary || {};
|
|
515
|
+
const modes = sum.mode_breakdown || {};
|
|
516
|
+
const btc = sum.bitcoin_settlement || {};
|
|
517
|
+
const signers = Array.isArray(sum.hardware_passkey_signers) ? sum.hardware_passkey_signers : [];
|
|
518
|
+
const localCheck = audit.local_path_verification || null;
|
|
519
|
+
const entries = Array.isArray(audit.audit_trail) ? audit.audit_trail : [];
|
|
520
|
+
|
|
521
|
+
const signerRowsHtml = signers.length
|
|
522
|
+
? signers
|
|
523
|
+
.map(
|
|
524
|
+
(s, idx) => `
|
|
525
|
+
<tr>
|
|
526
|
+
<td class="mono">#${idx + 1}</td>
|
|
527
|
+
<td>
|
|
528
|
+
<span class="badge badge-passkey">FIDO2 / WebAuthn Hardware Key</span>
|
|
529
|
+
<div class="mono hash-full" style="margin-top:4px">${escapeHtml(s.signer_public_key)}</div>
|
|
530
|
+
</td>
|
|
531
|
+
<td class="mono" style="font-weight:700;color:#F5B041">${s.anchor_count}</td>
|
|
532
|
+
<td class="mono" style="color:#2ECC71">${s.settled_count}</td>
|
|
533
|
+
<td class="mono">${escapeHtml(s.latest_timestamp_utc || "—")}</td>
|
|
534
|
+
</tr>`
|
|
535
|
+
)
|
|
536
|
+
.join("")
|
|
537
|
+
: `<tr><td colspan="5" style="text-align:center;color:#8892b0;padding:18px">No hardware Passkey signatures recorded yet in this window. Link a passkey with <code>reltime_link_passkey</code> and anchor with <code>biometric: true</code>.</td></tr>`;
|
|
538
|
+
|
|
539
|
+
const localVerificationHtml = localCheck
|
|
540
|
+
? `
|
|
541
|
+
<div class="section-card">
|
|
542
|
+
<div class="section-header">
|
|
543
|
+
<div>
|
|
544
|
+
<h2>Local File & Folder Integrity Audit</h2>
|
|
545
|
+
<p class="section-sub">Target Path: <span class="mono">${escapeHtml(localCheck.target_path)}</span> · Zero-Knowledge Local SHA-256 Cross-Reference</p>
|
|
546
|
+
</div>
|
|
547
|
+
<span class="badge ${
|
|
548
|
+
localCheck.summary.unanchored_or_modified_count === 0 ? "badge-settled" : "badge-warn"
|
|
549
|
+
}">
|
|
550
|
+
${
|
|
551
|
+
localCheck.summary.unanchored_or_modified_count === 0
|
|
552
|
+
? `100% INTACT (${localCheck.summary.total_files_scanned} Verified)`
|
|
553
|
+
: `${localCheck.summary.unanchored_or_modified_count} UNANCHORED / MODIFIED`
|
|
554
|
+
}
|
|
555
|
+
</span>
|
|
556
|
+
</div>
|
|
557
|
+
<div class="table-wrap">
|
|
558
|
+
<table>
|
|
559
|
+
<thead>
|
|
560
|
+
<tr>
|
|
561
|
+
<th>File</th>
|
|
562
|
+
<th>Size</th>
|
|
563
|
+
<th>Local SHA-256 / Embedded Hash</th>
|
|
564
|
+
<th>Integrity Status</th>
|
|
565
|
+
<th>Mode / Signer</th>
|
|
566
|
+
<th>Bitcoin Settlement</th>
|
|
567
|
+
<th>Proof</th>
|
|
568
|
+
</tr>
|
|
569
|
+
</thead>
|
|
570
|
+
<tbody>
|
|
571
|
+
${localCheck.files
|
|
572
|
+
.map((f) => {
|
|
573
|
+
const statusBadge =
|
|
574
|
+
f.integrity_status === "VERIFIED_SETTLED"
|
|
575
|
+
? `<span class="badge badge-settled">✓ VERIFIED (BITCOIN SETTLED)</span>`
|
|
576
|
+
: f.integrity_status === "VERIFIED_PENDING"
|
|
577
|
+
? `<span class="badge badge-pending">⏳ VERIFIED (PENDING BLOCK)</span>`
|
|
578
|
+
: `<span class="badge badge-warn">⚠ UNANCHORED OR MODIFIED</span>`;
|
|
579
|
+
const signerText = f.signer_public_key
|
|
580
|
+
? `<span class="badge badge-passkey" title="${escapeHtml(f.signer_public_key)}">Passkey ${escapeHtml(
|
|
581
|
+
f.signer_public_key.slice(0, 10)
|
|
582
|
+
)}…</span>`
|
|
583
|
+
: `<span class="mono" style="color:#94a3b8">${escapeHtml(f.anchor_mode || "—")}</span>`;
|
|
584
|
+
const btcText = f.block_height
|
|
585
|
+
? `<span class="mono" style="color:#2ECC71">Block #${escapeHtml(f.block_height)}</span>`
|
|
586
|
+
: f.integrity_status === "VERIFIED_PENDING"
|
|
587
|
+
? `<span class="mono" style="color:#F5B041">Awaiting Next Block</span>`
|
|
588
|
+
: `—`;
|
|
589
|
+
const verifyLink = f.verify_url
|
|
590
|
+
? `<a class="btn-link" href="${escapeHtml(f.verify_url)}" target="_blank" rel="noopener">Verify ↗</a>`
|
|
591
|
+
: `—`;
|
|
592
|
+
return `
|
|
593
|
+
<tr>
|
|
594
|
+
<td style="font-weight:600;color:#f8fafc">${escapeHtml(f.file_name)}${
|
|
595
|
+
f.is_stamped_copy ? ` <span class="badge badge-mode">Stamped Copy</span>` : ""
|
|
596
|
+
}</td>
|
|
597
|
+
<td class="mono">${escapeHtml(f.size_human)}</td>
|
|
598
|
+
<td class="mono hash-cell" title="${escapeHtml(f.local_sha256)}">${escapeHtml(
|
|
599
|
+
f.local_sha256.slice(0, 16)
|
|
600
|
+
)}…${escapeHtml(f.local_sha256.slice(-8))}</td>
|
|
601
|
+
<td>${statusBadge}</td>
|
|
602
|
+
<td>${signerText}</td>
|
|
603
|
+
<td>${btcText}</td>
|
|
604
|
+
<td>${verifyLink}</td>
|
|
605
|
+
</tr>`;
|
|
606
|
+
})
|
|
607
|
+
.join("")}
|
|
608
|
+
</tbody>
|
|
609
|
+
</table>
|
|
610
|
+
</div>
|
|
611
|
+
</div>`
|
|
612
|
+
: "";
|
|
613
|
+
|
|
614
|
+
const ledgerRowsHtml = entries.length
|
|
615
|
+
? entries
|
|
616
|
+
.map((e, idx) => {
|
|
617
|
+
const isSettled = e.status === "settled";
|
|
618
|
+
const isBiometric = Boolean(e.signer_public_key);
|
|
619
|
+
const modeType = e.anchor_mode || "standard";
|
|
620
|
+
const filterClasses = [
|
|
621
|
+
isSettled ? "status-settled" : "status-pending",
|
|
622
|
+
`mode-${modeType}`,
|
|
623
|
+
isBiometric ? "mode-biometric" : "",
|
|
624
|
+
]
|
|
625
|
+
.filter(Boolean)
|
|
626
|
+
.join(" ");
|
|
627
|
+
|
|
628
|
+
const statusHtml = isSettled
|
|
629
|
+
? `<span class="badge badge-settled">✓ SETTLED</span>`
|
|
630
|
+
: `<span class="badge badge-pending">⏳ PENDING FINALITY</span>`;
|
|
631
|
+
|
|
632
|
+
const modeHtml = `
|
|
633
|
+
<span class="badge badge-mode">${escapeHtml(modeType.toUpperCase())}</span>
|
|
634
|
+
${
|
|
635
|
+
isBiometric
|
|
636
|
+
? `<span class="badge badge-passkey" title="Signed by Passkey: ${escapeHtml(
|
|
637
|
+
e.signer_public_key
|
|
638
|
+
)}">🔐 Passkey ${escapeHtml(e.signer_public_key.slice(0, 8))}…</span>`
|
|
639
|
+
: ""
|
|
640
|
+
}
|
|
641
|
+
`;
|
|
642
|
+
|
|
643
|
+
const btcHtml = isSettled
|
|
644
|
+
? `
|
|
645
|
+
<div class="mono" style="color:#2ECC71;font-weight:600">Block #${escapeHtml(e.block_height || "—")}</div>
|
|
646
|
+
${
|
|
647
|
+
e.txid
|
|
648
|
+
? `<a class="mono tx-link" href="https://mempool.space/tx/${escapeHtml(
|
|
649
|
+
e.txid
|
|
650
|
+
)}" target="_blank" rel="noopener" title="${escapeHtml(e.txid)}">TX: ${escapeHtml(
|
|
651
|
+
e.txid.slice(0, 10)
|
|
652
|
+
)}…${escapeHtml(e.txid.slice(-6))} ↗</a>`
|
|
653
|
+
: ""
|
|
654
|
+
}
|
|
655
|
+
`
|
|
656
|
+
: `<span class="mono" style="color:#94a3b8">Queued in VDF Batch</span>`;
|
|
657
|
+
|
|
658
|
+
return `
|
|
659
|
+
<tr class="ledger-row ${filterClasses}" data-search="${escapeHtml(
|
|
660
|
+
`${e.client_hash} ${e.t_index} ${e.original_file_hash || ""} ${e.signer_public_key || ""} ${
|
|
661
|
+
e.txid || ""
|
|
662
|
+
} ${e.block_height || ""} ${modeType}`.toLowerCase()
|
|
663
|
+
)}">
|
|
664
|
+
<td class="mono" style="color:#64748b">#${idx + 1}</td>
|
|
665
|
+
<td class="mono">${escapeHtml(e.timestamp_utc || "—")}</td>
|
|
666
|
+
<td>${statusHtml}</td>
|
|
667
|
+
<td>${modeHtml}</td>
|
|
668
|
+
<td class="mono hash-cell" title="Client Hash: ${escapeHtml(e.client_hash)}${
|
|
669
|
+
e.original_file_hash ? ` | Original File SHA-256: ${escapeHtml(e.original_file_hash)}` : ""
|
|
670
|
+
}">
|
|
671
|
+
<div>${escapeHtml(e.client_hash.slice(0, 14))}…${escapeHtml(e.client_hash.slice(-8))}</div>
|
|
672
|
+
${
|
|
673
|
+
e.original_file_hash
|
|
674
|
+
? `<div style="font-size:11px;color:#94a3b8">File: ${escapeHtml(
|
|
675
|
+
e.original_file_hash.slice(0, 10)
|
|
676
|
+
)}…</div>`
|
|
677
|
+
: ""
|
|
678
|
+
}
|
|
679
|
+
</td>
|
|
680
|
+
<td class="mono hash-cell" title="T_index: ${escapeHtml(e.t_index)} | Solar Ticks: ${escapeHtml(
|
|
681
|
+
e.vdf_ticks
|
|
682
|
+
)}">
|
|
683
|
+
<div>${escapeHtml(e.t_index.slice(0, 12))}…${escapeHtml(e.t_index.slice(-6))}</div>
|
|
684
|
+
<div style="font-size:11px;color:#F5B041">Ticks: ${ Number(e.vdf_ticks || 0).toLocaleString() }</div>
|
|
685
|
+
</td>
|
|
686
|
+
<td>${btcHtml}</td>
|
|
687
|
+
<td>
|
|
688
|
+
<a class="btn-link" href="${escapeHtml(e.verify_url)}" target="_blank" rel="noopener">Verify ↗</a>
|
|
689
|
+
</td>
|
|
690
|
+
</tr>`;
|
|
691
|
+
})
|
|
692
|
+
.join("")
|
|
693
|
+
: `<tr><td colspan="8" style="text-align:center;color:#8892b0;padding:24px">No ledger entries matched the current filter.</td></tr>`;
|
|
694
|
+
|
|
695
|
+
return `<!DOCTYPE html>
|
|
696
|
+
<html lang="en">
|
|
697
|
+
<head>
|
|
698
|
+
<meta charset="UTF-8" />
|
|
699
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
700
|
+
<title>Rel-Time Superhighway — Cryptographic Audit Trail</title>
|
|
701
|
+
<style>
|
|
702
|
+
:root {
|
|
703
|
+
--bg: #07090e;
|
|
704
|
+
--card: #0f1420;
|
|
705
|
+
--card-border: #1e293b;
|
|
706
|
+
--gold: #f5b041;
|
|
707
|
+
--gold-dim: rgba(245, 176, 65, 0.14);
|
|
708
|
+
--green: #2ecc71;
|
|
709
|
+
--green-dim: rgba(46, 204, 113, 0.14);
|
|
710
|
+
--blue: #38bdf8;
|
|
711
|
+
--blue-dim: rgba(56, 189, 248, 0.14);
|
|
712
|
+
--purple: #c084fc;
|
|
713
|
+
--purple-dim: rgba(192, 132, 252, 0.15);
|
|
714
|
+
--red: #f87171;
|
|
715
|
+
--red-dim: rgba(248, 113, 113, 0.15);
|
|
716
|
+
--text: #f8fafc;
|
|
717
|
+
--muted: #94a3b8;
|
|
718
|
+
}
|
|
719
|
+
* { box-sizing: border-box; }
|
|
720
|
+
body {
|
|
721
|
+
margin: 0;
|
|
722
|
+
padding: 32px 24px 64px;
|
|
723
|
+
background: radial-gradient(circle at top right, #131c31 0%, var(--bg) 55%);
|
|
724
|
+
color: var(--text);
|
|
725
|
+
font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", Roboto, sans-serif;
|
|
726
|
+
line-height: 1.5;
|
|
727
|
+
}
|
|
728
|
+
.container { max-width: 1340px; margin: 0 auto; }
|
|
729
|
+
.topbar {
|
|
730
|
+
display: flex;
|
|
731
|
+
flex-wrap: wrap;
|
|
732
|
+
justify-content: space-between;
|
|
733
|
+
align-items: center;
|
|
734
|
+
gap: 16px;
|
|
735
|
+
padding: 24px 28px;
|
|
736
|
+
background: linear-gradient(135deg, rgba(15,20,32,0.96), rgba(20,28,46,0.92));
|
|
737
|
+
border: 1px solid rgba(245, 176, 65, 0.35);
|
|
738
|
+
border-radius: 16px;
|
|
739
|
+
box-shadow: 0 18px 40px rgba(0,0,0,0.45);
|
|
740
|
+
margin-bottom: 24px;
|
|
741
|
+
}
|
|
742
|
+
.brand-eyebrow {
|
|
743
|
+
font-size: 11px;
|
|
744
|
+
letter-spacing: 0.18em;
|
|
745
|
+
text-transform: uppercase;
|
|
746
|
+
color: var(--gold);
|
|
747
|
+
font-weight: 700;
|
|
748
|
+
margin-bottom: 4px;
|
|
749
|
+
}
|
|
750
|
+
h1 {
|
|
751
|
+
margin: 0;
|
|
752
|
+
font-size: 26px;
|
|
753
|
+
font-weight: 800;
|
|
754
|
+
letter-spacing: -0.02em;
|
|
755
|
+
}
|
|
756
|
+
.meta-line {
|
|
757
|
+
font-size: 13px;
|
|
758
|
+
color: var(--muted);
|
|
759
|
+
margin-top: 4px;
|
|
760
|
+
}
|
|
761
|
+
.actions-bar {
|
|
762
|
+
display: flex;
|
|
763
|
+
gap: 10px;
|
|
764
|
+
align-items: center;
|
|
765
|
+
}
|
|
766
|
+
.btn {
|
|
767
|
+
background: var(--gold);
|
|
768
|
+
color: #07090e;
|
|
769
|
+
border: none;
|
|
770
|
+
padding: 10px 16px;
|
|
771
|
+
border-radius: 8px;
|
|
772
|
+
font-weight: 700;
|
|
773
|
+
font-size: 13px;
|
|
774
|
+
cursor: pointer;
|
|
775
|
+
text-decoration: none;
|
|
776
|
+
display: inline-flex;
|
|
777
|
+
align-items: center;
|
|
778
|
+
gap: 6px;
|
|
779
|
+
}
|
|
780
|
+
.btn-outline {
|
|
781
|
+
background: transparent;
|
|
782
|
+
color: var(--text);
|
|
783
|
+
border: 1px solid var(--card-border);
|
|
784
|
+
padding: 10px 16px;
|
|
785
|
+
border-radius: 8px;
|
|
786
|
+
font-weight: 600;
|
|
787
|
+
font-size: 13px;
|
|
788
|
+
cursor: pointer;
|
|
789
|
+
text-decoration: none;
|
|
790
|
+
}
|
|
791
|
+
.kpi-grid {
|
|
792
|
+
display: grid;
|
|
793
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
794
|
+
gap: 16px;
|
|
795
|
+
margin-bottom: 24px;
|
|
796
|
+
}
|
|
797
|
+
.kpi-card {
|
|
798
|
+
background: var(--card);
|
|
799
|
+
border: 1px solid var(--card-border);
|
|
800
|
+
border-radius: 14px;
|
|
801
|
+
padding: 18px 20px;
|
|
802
|
+
}
|
|
803
|
+
.kpi-label {
|
|
804
|
+
font-size: 11px;
|
|
805
|
+
text-transform: uppercase;
|
|
806
|
+
letter-spacing: 0.1em;
|
|
807
|
+
color: var(--muted);
|
|
808
|
+
margin-bottom: 6px;
|
|
809
|
+
}
|
|
810
|
+
.kpi-value {
|
|
811
|
+
font-size: 26px;
|
|
812
|
+
font-weight: 800;
|
|
813
|
+
font-family: "JetBrains Mono", "SF Mono", Menlo, monospace;
|
|
814
|
+
}
|
|
815
|
+
.kpi-sub {
|
|
816
|
+
font-size: 12px;
|
|
817
|
+
color: var(--muted);
|
|
818
|
+
margin-top: 4px;
|
|
819
|
+
}
|
|
820
|
+
.section-card {
|
|
821
|
+
background: var(--card);
|
|
822
|
+
border: 1px solid var(--card-border);
|
|
823
|
+
border-radius: 16px;
|
|
824
|
+
padding: 22px 24px;
|
|
825
|
+
margin-bottom: 24px;
|
|
826
|
+
}
|
|
827
|
+
.section-header {
|
|
828
|
+
display: flex;
|
|
829
|
+
flex-wrap: wrap;
|
|
830
|
+
justify-content: space-between;
|
|
831
|
+
align-items: center;
|
|
832
|
+
gap: 12px;
|
|
833
|
+
margin-bottom: 16px;
|
|
834
|
+
}
|
|
835
|
+
h2 {
|
|
836
|
+
margin: 0;
|
|
837
|
+
font-size: 18px;
|
|
838
|
+
font-weight: 700;
|
|
839
|
+
}
|
|
840
|
+
.section-sub {
|
|
841
|
+
margin: 3px 0 0;
|
|
842
|
+
font-size: 13px;
|
|
843
|
+
color: var(--muted);
|
|
844
|
+
}
|
|
845
|
+
.filter-bar {
|
|
846
|
+
display: flex;
|
|
847
|
+
flex-wrap: wrap;
|
|
848
|
+
gap: 8px;
|
|
849
|
+
align-items: center;
|
|
850
|
+
margin-bottom: 16px;
|
|
851
|
+
}
|
|
852
|
+
.search-input {
|
|
853
|
+
background: #07090e;
|
|
854
|
+
border: 1px solid var(--card-border);
|
|
855
|
+
color: var(--text);
|
|
856
|
+
padding: 8px 12px;
|
|
857
|
+
border-radius: 8px;
|
|
858
|
+
font-size: 13px;
|
|
859
|
+
min-width: 260px;
|
|
860
|
+
}
|
|
861
|
+
.filter-pill {
|
|
862
|
+
background: #07090e;
|
|
863
|
+
border: 1px solid var(--card-border);
|
|
864
|
+
color: var(--muted);
|
|
865
|
+
padding: 6px 12px;
|
|
866
|
+
border-radius: 999px;
|
|
867
|
+
font-size: 12px;
|
|
868
|
+
font-weight: 600;
|
|
869
|
+
cursor: pointer;
|
|
870
|
+
}
|
|
871
|
+
.filter-pill.active {
|
|
872
|
+
background: var(--gold-dim);
|
|
873
|
+
border-color: var(--gold);
|
|
874
|
+
color: var(--gold);
|
|
875
|
+
}
|
|
876
|
+
.table-wrap {
|
|
877
|
+
overflow-x: auto;
|
|
878
|
+
}
|
|
879
|
+
table {
|
|
880
|
+
width: 100%;
|
|
881
|
+
border-collapse: collapse;
|
|
882
|
+
font-size: 13px;
|
|
883
|
+
}
|
|
884
|
+
th {
|
|
885
|
+
text-align: left;
|
|
886
|
+
padding: 11px 12px;
|
|
887
|
+
font-size: 11px;
|
|
888
|
+
text-transform: uppercase;
|
|
889
|
+
letter-spacing: 0.08em;
|
|
890
|
+
color: var(--muted);
|
|
891
|
+
border-bottom: 1px solid var(--card-border);
|
|
892
|
+
white-space: nowrap;
|
|
893
|
+
}
|
|
894
|
+
td {
|
|
895
|
+
padding: 12px;
|
|
896
|
+
border-bottom: 1px solid rgba(30, 41, 59, 0.65);
|
|
897
|
+
vertical-align: middle;
|
|
898
|
+
}
|
|
899
|
+
tr:hover td {
|
|
900
|
+
background: rgba(255, 255, 255, 0.02);
|
|
901
|
+
}
|
|
902
|
+
.mono {
|
|
903
|
+
font-family: "JetBrains Mono", "SF Mono", Menlo, monospace;
|
|
904
|
+
font-size: 12px;
|
|
905
|
+
}
|
|
906
|
+
.hash-full {
|
|
907
|
+
word-break: break-all;
|
|
908
|
+
color: #cbd5e1;
|
|
909
|
+
font-size: 11.5px;
|
|
910
|
+
}
|
|
911
|
+
.badge {
|
|
912
|
+
display: inline-block;
|
|
913
|
+
padding: 3px 9px;
|
|
914
|
+
border-radius: 999px;
|
|
915
|
+
font-size: 11px;
|
|
916
|
+
font-weight: 700;
|
|
917
|
+
letter-spacing: 0.03em;
|
|
918
|
+
white-space: nowrap;
|
|
919
|
+
}
|
|
920
|
+
.badge-settled { background: var(--green-dim); color: var(--green); border: 1px solid rgba(46,204,113,0.35); }
|
|
921
|
+
.badge-pending { background: var(--gold-dim); color: var(--gold); border: 1px solid rgba(245,176,65,0.35); }
|
|
922
|
+
.badge-mode { background: var(--blue-dim); color: var(--blue); border: 1px solid rgba(56,189,248,0.3); }
|
|
923
|
+
.badge-passkey { background: var(--purple-dim); color: var(--purple); border: 1px solid rgba(192,132,252,0.35); }
|
|
924
|
+
.badge-warn { background: var(--red-dim); color: var(--red); border: 1px solid rgba(248,113,113,0.35); }
|
|
925
|
+
.btn-link {
|
|
926
|
+
color: var(--gold);
|
|
927
|
+
text-decoration: none;
|
|
928
|
+
font-weight: 700;
|
|
929
|
+
font-size: 12px;
|
|
930
|
+
padding: 5px 10px;
|
|
931
|
+
border: 1px solid rgba(245,176,65,0.35);
|
|
932
|
+
border-radius: 6px;
|
|
933
|
+
background: var(--gold-dim);
|
|
934
|
+
white-space: nowrap;
|
|
935
|
+
}
|
|
936
|
+
.tx-link {
|
|
937
|
+
color: #94a3b8;
|
|
938
|
+
text-decoration: none;
|
|
939
|
+
font-size: 11px;
|
|
940
|
+
display: inline-block;
|
|
941
|
+
margin-top: 2px;
|
|
942
|
+
}
|
|
943
|
+
.tx-link:hover { color: var(--gold); }
|
|
944
|
+
@media print {
|
|
945
|
+
body { background: #07090e !important; -webkit-print-color-adjust: exact; print-color-adjust: exact; padding: 12px; }
|
|
946
|
+
.no-print { display: none !important; }
|
|
947
|
+
}
|
|
948
|
+
</style>
|
|
949
|
+
</head>
|
|
950
|
+
<body>
|
|
951
|
+
<div class="container">
|
|
952
|
+
<div class="topbar">
|
|
953
|
+
<div>
|
|
954
|
+
<div class="brand-eyebrow">Rel-Time Sovereign Provenance Protocol · Audit Trail</div>
|
|
955
|
+
<h1>Cryptographic Data Integrity & Timechain Audit Report</h1>
|
|
956
|
+
<div class="meta-line">
|
|
957
|
+
Generated: <span class="mono">${escapeHtml(generatedUtc)}</span> ·
|
|
958
|
+
Account Tier: <strong style="color:#F5B041">${escapeHtml(acct.tier || "Active")}</strong> ·
|
|
959
|
+
Key: <span class="mono">${escapeHtml(acct.masked_api_key || "rel_key_***")}</span>
|
|
960
|
+
</div>
|
|
961
|
+
</div>
|
|
962
|
+
<div class="actions-bar no-print">
|
|
963
|
+
<a class="btn-outline" href="https://rel-time.com/verify" target="_blank" rel="noopener">Open Verifier ↗</a>
|
|
964
|
+
<button class="btn" onclick="window.print()">Print / Save PDF Audit Certificate</button>
|
|
965
|
+
</div>
|
|
966
|
+
</div>
|
|
967
|
+
|
|
968
|
+
<div class="kpi-grid">
|
|
969
|
+
<div class="kpi-card">
|
|
970
|
+
<div class="kpi-label">Audited Anchors</div>
|
|
971
|
+
<div class="kpi-value" style="color:#f8fafc">${Number(sum.matched_entries_count || 0).toLocaleString()}</div>
|
|
972
|
+
<div class="kpi-sub">Lifetime Account Total: ${Number(acct.lifetime_anchors || 0).toLocaleString()}</div>
|
|
973
|
+
</div>
|
|
974
|
+
<div class="kpi-card">
|
|
975
|
+
<div class="kpi-label">Bitcoin Finality</div>
|
|
976
|
+
<div class="kpi-value" style="color:#2ECC71">${Number(sum.settled_on_bitcoin_count || 0).toLocaleString()}</div>
|
|
977
|
+
<div class="kpi-sub">${escapeHtml(sum.settlement_rate_pct || "0%")} Settled · ${Number(
|
|
978
|
+
sum.pending_finality_count || 0
|
|
979
|
+
)} Pending</div>
|
|
980
|
+
</div>
|
|
981
|
+
<div class="kpi-card">
|
|
982
|
+
<div class="kpi-label">Bitcoin Blocks & TXs</div>
|
|
983
|
+
<div class="kpi-value" style="color:#F5B041">${Number(btc.unique_blocks_count || 0)}</div>
|
|
984
|
+
<div class="kpi-sub">${Number(btc.unique_txids_count || 0)} Unique OP_RETURN TXIDs${
|
|
985
|
+
btc.latest_settled_block ? ` · Latest #${escapeHtml(btc.latest_settled_block)}` : ""
|
|
986
|
+
}</div>
|
|
987
|
+
</div>
|
|
988
|
+
<div class="kpi-card">
|
|
989
|
+
<div class="kpi-label">Hashing Modes</div>
|
|
990
|
+
<div class="kpi-value" style="color:#38bdf8">${Number(modes.standard_count || 0)} / ${Number(
|
|
991
|
+
modes.temporal_vdf_count || 0
|
|
992
|
+
)}</div>
|
|
993
|
+
<div class="kpi-sub">Standard Integrity / Temporal VDF</div>
|
|
994
|
+
</div>
|
|
995
|
+
<div class="kpi-card">
|
|
996
|
+
<div class="kpi-label">Hardware Passkey Signers</div>
|
|
997
|
+
<div class="kpi-value" style="color:#c084fc">${signers.length}</div>
|
|
998
|
+
<div class="kpi-sub">${Number(modes.biometric_passkey_count || 0)} Biometric-Attested Anchors</div>
|
|
999
|
+
</div>
|
|
1000
|
+
<div class="kpi-card">
|
|
1001
|
+
<div class="kpi-label">Available Fuel</div>
|
|
1002
|
+
<div class="kpi-value" style="color:#F5B041">${Number(acct.hash_credits_remaining || 0).toLocaleString()}</div>
|
|
1003
|
+
<div class="kpi-sub">Remaining Hash Credits</div>
|
|
1004
|
+
</div>
|
|
1005
|
+
</div>
|
|
1006
|
+
|
|
1007
|
+
${localVerificationHtml}
|
|
1008
|
+
|
|
1009
|
+
<div class="section-card">
|
|
1010
|
+
<div class="section-header">
|
|
1011
|
+
<div>
|
|
1012
|
+
<h2>Corporate Hardware Passkey Signers (WebAuthn / FIDO2 COSE Keys)</h2>
|
|
1013
|
+
<p class="section-sub">Hardware Secure Enclave & YubiKey public keys that have attested files under this corporate account</p>
|
|
1014
|
+
</div>
|
|
1015
|
+
</div>
|
|
1016
|
+
<div class="table-wrap">
|
|
1017
|
+
<table>
|
|
1018
|
+
<thead>
|
|
1019
|
+
<tr>
|
|
1020
|
+
<th>#</th>
|
|
1021
|
+
<th>Hardware Signer Public Key (COSE Hex)</th>
|
|
1022
|
+
<th>Signed Anchors</th>
|
|
1023
|
+
<th>Bitcoin Settled</th>
|
|
1024
|
+
<th>Latest Signature (UTC)</th>
|
|
1025
|
+
</tr>
|
|
1026
|
+
</thead>
|
|
1027
|
+
<tbody>
|
|
1028
|
+
${signerRowsHtml}
|
|
1029
|
+
</tbody>
|
|
1030
|
+
</table>
|
|
1031
|
+
</div>
|
|
1032
|
+
</div>
|
|
1033
|
+
|
|
1034
|
+
<div class="section-card">
|
|
1035
|
+
<div class="section-header">
|
|
1036
|
+
<div>
|
|
1037
|
+
<h2>Chronological Timechain Audit Ledger</h2>
|
|
1038
|
+
<p class="section-sub">Complete record of SHA-256 data integrity hashes, VDF thermodynamic proofs (T_index), and Bitcoin Merkle settlements</p>
|
|
1039
|
+
</div>
|
|
1040
|
+
</div>
|
|
1041
|
+
<div class="filter-bar no-print">
|
|
1042
|
+
<input id="searchInput" class="search-input" type="text" placeholder="Filter by SHA-256 hash, T_index, Passkey, TXID, or Block #..." oninput="filterLedger()" />
|
|
1043
|
+
<button class="filter-pill active" onclick="setPill(this, 'all')">All (${entries.length})</button>
|
|
1044
|
+
<button class="filter-pill" onclick="setPill(this, 'status-settled')">Settled (${Number(
|
|
1045
|
+
sum.settled_on_bitcoin_count || 0
|
|
1046
|
+
)})</button>
|
|
1047
|
+
<button class="filter-pill" onclick="setPill(this, 'status-pending')">Pending (${Number(
|
|
1048
|
+
sum.pending_finality_count || 0
|
|
1049
|
+
)})</button>
|
|
1050
|
+
<button class="filter-pill" onclick="setPill(this, 'mode-biometric')">Biometric Passkey (${Number(
|
|
1051
|
+
modes.biometric_passkey_count || 0
|
|
1052
|
+
)})</button>
|
|
1053
|
+
<button class="filter-pill" onclick="setPill(this, 'mode-temporal')">Temporal VDF (${Number(
|
|
1054
|
+
modes.temporal_vdf_count || 0
|
|
1055
|
+
)})</button>
|
|
1056
|
+
<button class="filter-pill" onclick="setPill(this, 'mode-standard')">Standard (${Number(
|
|
1057
|
+
modes.standard_count || 0
|
|
1058
|
+
)})</button>
|
|
1059
|
+
</div>
|
|
1060
|
+
<div class="table-wrap">
|
|
1061
|
+
<table>
|
|
1062
|
+
<thead>
|
|
1063
|
+
<tr>
|
|
1064
|
+
<th>#</th>
|
|
1065
|
+
<th>Timestamp (UTC)</th>
|
|
1066
|
+
<th>Status</th>
|
|
1067
|
+
<th>Mode & Signer</th>
|
|
1068
|
+
<th>Client / File SHA-256</th>
|
|
1069
|
+
<th>Thermodynamic Proof (T_index)</th>
|
|
1070
|
+
<th>Bitcoin Settlement</th>
|
|
1071
|
+
<th>Verify</th>
|
|
1072
|
+
</tr>
|
|
1073
|
+
</thead>
|
|
1074
|
+
<tbody id="ledgerBody">
|
|
1075
|
+
${ledgerRowsHtml}
|
|
1076
|
+
</tbody>
|
|
1077
|
+
</table>
|
|
1078
|
+
</div>
|
|
1079
|
+
</div>
|
|
1080
|
+
</div>
|
|
1081
|
+
|
|
1082
|
+
<script>
|
|
1083
|
+
let currentFilter = 'all';
|
|
1084
|
+
function setPill(btn, filter) {
|
|
1085
|
+
currentFilter = filter;
|
|
1086
|
+
document.querySelectorAll('.filter-pill').forEach(el => el.classList.remove('active'));
|
|
1087
|
+
btn.classList.add('active');
|
|
1088
|
+
filterLedger();
|
|
1089
|
+
}
|
|
1090
|
+
function filterLedger() {
|
|
1091
|
+
const q = (document.getElementById('searchInput').value || '').trim().toLowerCase();
|
|
1092
|
+
document.querySelectorAll('.ledger-row').forEach(row => {
|
|
1093
|
+
const matchesFilter = currentFilter === 'all' || row.classList.contains(currentFilter);
|
|
1094
|
+
const matchesQuery = !q || (row.getAttribute('data-search') || '').includes(q);
|
|
1095
|
+
row.style.display = (matchesFilter && matchesQuery) ? '' : 'none';
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
</script>
|
|
1099
|
+
</body>
|
|
1100
|
+
</html>`;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
480
1103
|
const TOOLS = [
|
|
481
1104
|
{
|
|
482
1105
|
name: "reltime_anchor_file",
|
|
@@ -685,6 +1308,64 @@ const TOOLS = [
|
|
|
685
1308
|
required: ["action"],
|
|
686
1309
|
},
|
|
687
1310
|
},
|
|
1311
|
+
{
|
|
1312
|
+
name: "reltime_audit_trail",
|
|
1313
|
+
description:
|
|
1314
|
+
"Superhighway Cryptographic Audit Trail & Local Folder Integrity Verifier (`GET /api/v1/account` + `GET /api/v1/account/history` + local SHA-256 cross-reference). Fetches the account's chronological anchor ledger, aggregates Bitcoin block settlements, VDF `T_index` ticks, and Corporate Hardware Passkey (`signer_public_key`) signatures, optionally verifies a local file or entire folder bit-for-bit against the ledger (`verify_local_path`), and spins up a self-contained interactive HTML Audit Report (`reltime-audit-report-*.html`) in the user's browser.",
|
|
1315
|
+
inputSchema: {
|
|
1316
|
+
type: "object",
|
|
1317
|
+
properties: {
|
|
1318
|
+
verify_local_path: {
|
|
1319
|
+
type: "string",
|
|
1320
|
+
description:
|
|
1321
|
+
"Optional local file path or folder/directory path to audit bit-for-bit (Zero-Knowledge local SHA-256 + Passkey attestation check + Stamped Copy check) against the Rel-Time Bitcoin ledger.",
|
|
1322
|
+
},
|
|
1323
|
+
limit: {
|
|
1324
|
+
type: "number",
|
|
1325
|
+
description: "Maximum number of audit ledger entries to retrieve (default 100, max 500).",
|
|
1326
|
+
},
|
|
1327
|
+
status_filter: {
|
|
1328
|
+
type: "string",
|
|
1329
|
+
enum: ["all", "settled", "pending"],
|
|
1330
|
+
description: "Filter ledger entries by settlement status: 'all' (default), 'settled', or 'pending'.",
|
|
1331
|
+
},
|
|
1332
|
+
mode_filter: {
|
|
1333
|
+
type: "string",
|
|
1334
|
+
enum: ["all", "standard", "temporal", "biometric"],
|
|
1335
|
+
description:
|
|
1336
|
+
"Filter ledger entries by mode: 'all' (default), 'standard', 'temporal' (VDF T_index), or 'biometric' (Hardware Passkey attested).",
|
|
1337
|
+
},
|
|
1338
|
+
signer_filter: {
|
|
1339
|
+
type: "string",
|
|
1340
|
+
description:
|
|
1341
|
+
"Optional full or partial Hardware Passkey `public_key_hex` to filter anchors signed by a specific employee or corporate YubiKey.",
|
|
1342
|
+
},
|
|
1343
|
+
save_html_report: {
|
|
1344
|
+
type: "boolean",
|
|
1345
|
+
description:
|
|
1346
|
+
"If true (default), saves an interactive dark-mode HTML Superhighway Audit Trail dashboard (`reltime-audit-report-<date>.html`).",
|
|
1347
|
+
},
|
|
1348
|
+
open_in_browser: {
|
|
1349
|
+
type: "boolean",
|
|
1350
|
+
description:
|
|
1351
|
+
"If true (default), automatically opens the generated HTML Audit Trail dashboard in the user's browser.",
|
|
1352
|
+
},
|
|
1353
|
+
export_csv: {
|
|
1354
|
+
type: "boolean",
|
|
1355
|
+
description: "If true, also exports the audit ledger to a `.csv` spreadsheet file.",
|
|
1356
|
+
},
|
|
1357
|
+
output_dir: {
|
|
1358
|
+
type: "string",
|
|
1359
|
+
description:
|
|
1360
|
+
"Optional directory where the generated `.html` / `.csv` audit reports should be saved (defaults to `verify_local_path` folder or `~/.reltime/audits`).",
|
|
1361
|
+
},
|
|
1362
|
+
api_key: {
|
|
1363
|
+
type: "string",
|
|
1364
|
+
description: "Optional override for RELTIME_API_KEY.",
|
|
1365
|
+
},
|
|
1366
|
+
},
|
|
1367
|
+
},
|
|
1368
|
+
},
|
|
688
1369
|
];
|
|
689
1370
|
|
|
690
1371
|
async function executeTool(name, args = {}) {
|
|
@@ -984,6 +1665,405 @@ async function executeTool(name, args = {}) {
|
|
|
984
1665
|
return data;
|
|
985
1666
|
}
|
|
986
1667
|
|
|
1668
|
+
if (name === "reltime_audit_trail") {
|
|
1669
|
+
if (!key) {
|
|
1670
|
+
return buildPackSelectionPrompt(
|
|
1671
|
+
"To run a Superhighway Audit Trail, a Rel-Time API key is required. Ask the user which tier and payment method they would like to provision:"
|
|
1672
|
+
);
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
const acctRes = await fetch(`${API_BASE}/account`, {
|
|
1676
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
1677
|
+
});
|
|
1678
|
+
if (!acctRes.ok) {
|
|
1679
|
+
if (acctRes.status === 401 || acctRes.status === 403) {
|
|
1680
|
+
return buildPackSelectionPrompt(
|
|
1681
|
+
"The configured API key was not recognized. Ask the user which tier and payment method they would like to provision:"
|
|
1682
|
+
);
|
|
1683
|
+
}
|
|
1684
|
+
throw new Error(`Rel-Time Account error (${acctRes.status}): ${await acctRes.text()}`);
|
|
1685
|
+
}
|
|
1686
|
+
const acctData = await acctRes.json();
|
|
1687
|
+
|
|
1688
|
+
const maxEntries = Math.min(500, Math.max(1, Number(args.limit) || 100));
|
|
1689
|
+
const pageSize = Math.min(100, maxEntries);
|
|
1690
|
+
const pagesToFetch = Math.ceil(maxEntries / pageSize);
|
|
1691
|
+
let rawHistory = [];
|
|
1692
|
+
let totalInLedger = 0;
|
|
1693
|
+
|
|
1694
|
+
for (let p = 1; p <= pagesToFetch; p++) {
|
|
1695
|
+
const histRes = await fetch(`${API_BASE}/account/history?page=${p}&limit=${pageSize}`, {
|
|
1696
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
1697
|
+
});
|
|
1698
|
+
if (!histRes.ok) break;
|
|
1699
|
+
const histJson = await histRes.json();
|
|
1700
|
+
totalInLedger = Number(histJson.total || 0);
|
|
1701
|
+
const batch = Array.isArray(histJson.history) ? histJson.history : [];
|
|
1702
|
+
rawHistory.push(...batch);
|
|
1703
|
+
if (batch.length < pageSize || rawHistory.length >= maxEntries || rawHistory.length >= totalInLedger) {
|
|
1704
|
+
break;
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
rawHistory = rawHistory.slice(0, maxEntries);
|
|
1708
|
+
|
|
1709
|
+
const statusFilter = (args.status_filter || "all").toLowerCase();
|
|
1710
|
+
const modeFilter = (args.mode_filter || "all").toLowerCase();
|
|
1711
|
+
const signerFilter = (args.signer_filter || "").trim().toLowerCase();
|
|
1712
|
+
|
|
1713
|
+
const normalizedEntries = rawHistory.map((item) => {
|
|
1714
|
+
const clientHash = (item.client_hash || "").toLowerCase();
|
|
1715
|
+
const tIndex = (item.t_index || "").toLowerCase();
|
|
1716
|
+
const origHash = item.original_file_hash ? item.original_file_hash.toLowerCase() : null;
|
|
1717
|
+
const mode = (item.anchor_mode || "standard").toLowerCase();
|
|
1718
|
+
const lookupHash = mode === "temporal" ? tIndex || clientHash : clientHash;
|
|
1719
|
+
const tsSec = Number(item.timestamp || 0);
|
|
1720
|
+
const tsUtc = tsSec > 0 ? new Date(tsSec * 1000).toISOString() : null;
|
|
1721
|
+
return {
|
|
1722
|
+
timestamp: tsSec,
|
|
1723
|
+
timestamp_utc: tsUtc,
|
|
1724
|
+
status: item.status || "pending_finality",
|
|
1725
|
+
anchor_mode: mode,
|
|
1726
|
+
identity_mode: item.signer_public_key ? "biometric_passkey" : "anonymous",
|
|
1727
|
+
client_hash: clientHash,
|
|
1728
|
+
original_file_hash: origHash,
|
|
1729
|
+
t_index: tIndex,
|
|
1730
|
+
vdf_ticks: Number(item.vdf_ticks || 0),
|
|
1731
|
+
signer_public_key: item.signer_public_key || null,
|
|
1732
|
+
block_height: item.block_height || null,
|
|
1733
|
+
block_time: item.block_time || null,
|
|
1734
|
+
block_time_utc: item.block_time ? new Date(Number(item.block_time) * 1000).toISOString() : null,
|
|
1735
|
+
txid: item.txid || null,
|
|
1736
|
+
merkle_root: item.merkle_root || null,
|
|
1737
|
+
verify_url: `https://rel-time.com/verify?t=${lookupHash}`,
|
|
1738
|
+
sun_stamp_svg_url: `${API_BASE}/stamp/${lookupHash}`,
|
|
1739
|
+
};
|
|
1740
|
+
});
|
|
1741
|
+
|
|
1742
|
+
const filteredEntries = normalizedEntries.filter((e) => {
|
|
1743
|
+
if (statusFilter === "settled" && e.status !== "settled") return false;
|
|
1744
|
+
if (statusFilter === "pending" && e.status === "settled") return false;
|
|
1745
|
+
if (modeFilter === "standard" && e.anchor_mode !== "standard") return false;
|
|
1746
|
+
if (modeFilter === "temporal" && e.anchor_mode !== "temporal") return false;
|
|
1747
|
+
if (modeFilter === "biometric" && !e.signer_public_key) return false;
|
|
1748
|
+
if (signerFilter && !(e.signer_public_key || "").toLowerCase().includes(signerFilter)) return false;
|
|
1749
|
+
return true;
|
|
1750
|
+
});
|
|
1751
|
+
|
|
1752
|
+
// Aggregate KPI & Hardware Passkey Signer statistics
|
|
1753
|
+
let settledCount = 0;
|
|
1754
|
+
let pendingCount = 0;
|
|
1755
|
+
let standardCount = 0;
|
|
1756
|
+
let temporalCount = 0;
|
|
1757
|
+
let biometricCount = 0;
|
|
1758
|
+
const uniqueBlocks = new Set();
|
|
1759
|
+
const uniqueTxids = new Set();
|
|
1760
|
+
let latestBlock = null;
|
|
1761
|
+
let latestTxid = null;
|
|
1762
|
+
const signerMap = new Map();
|
|
1763
|
+
|
|
1764
|
+
for (const e of filteredEntries) {
|
|
1765
|
+
if (e.status === "settled") {
|
|
1766
|
+
settledCount++;
|
|
1767
|
+
if (e.block_height) {
|
|
1768
|
+
uniqueBlocks.add(e.block_height);
|
|
1769
|
+
if (!latestBlock || Number(e.block_height) > Number(latestBlock)) {
|
|
1770
|
+
latestBlock = e.block_height;
|
|
1771
|
+
latestTxid = e.txid || latestTxid;
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
if (e.txid) uniqueTxids.add(e.txid);
|
|
1775
|
+
} else {
|
|
1776
|
+
pendingCount++;
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
if (e.anchor_mode === "temporal") temporalCount++;
|
|
1780
|
+
else standardCount++;
|
|
1781
|
+
|
|
1782
|
+
if (e.signer_public_key) {
|
|
1783
|
+
biometricCount++;
|
|
1784
|
+
const pk = e.signer_public_key;
|
|
1785
|
+
const cur = signerMap.get(pk) || {
|
|
1786
|
+
signer_public_key: pk,
|
|
1787
|
+
short_key: `${pk.slice(0, 12)}...${pk.slice(-8)}`,
|
|
1788
|
+
anchor_count: 0,
|
|
1789
|
+
settled_count: 0,
|
|
1790
|
+
latest_timestamp_utc: e.timestamp_utc,
|
|
1791
|
+
};
|
|
1792
|
+
cur.anchor_count++;
|
|
1793
|
+
if (e.status === "settled") cur.settled_count++;
|
|
1794
|
+
if (e.timestamp_utc && (!cur.latest_timestamp_utc || e.timestamp_utc > cur.latest_timestamp_utc)) {
|
|
1795
|
+
cur.latest_timestamp_utc = e.timestamp_utc;
|
|
1796
|
+
}
|
|
1797
|
+
signerMap.set(pk, cur);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
// Build fast lookup index of all known hashes in account history + known passkeys
|
|
1802
|
+
const ledgerByHash = new Map();
|
|
1803
|
+
const knownPasskeys = new Set();
|
|
1804
|
+
const localPasskey = loadSavedPasskey();
|
|
1805
|
+
if (localPasskey && localPasskey.public_key_hex) {
|
|
1806
|
+
knownPasskeys.add(localPasskey.public_key_hex);
|
|
1807
|
+
}
|
|
1808
|
+
for (const e of normalizedEntries) {
|
|
1809
|
+
if (e.client_hash) ledgerByHash.set(e.client_hash, e);
|
|
1810
|
+
if (e.t_index) ledgerByHash.set(e.t_index, e);
|
|
1811
|
+
if (e.original_file_hash) ledgerByHash.set(e.original_file_hash, e);
|
|
1812
|
+
if (e.signer_public_key) knownPasskeys.add(e.signer_public_key);
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
// Optional Zero-Knowledge Local File / Folder Integrity Verification
|
|
1816
|
+
let localPathVerification = null;
|
|
1817
|
+
if (args.verify_local_path) {
|
|
1818
|
+
const targetPath = resolveLocalFilePath(args.verify_local_path);
|
|
1819
|
+
if (!fs.existsSync(targetPath)) {
|
|
1820
|
+
throw new Error(`verify_local_path not found: ${targetPath}`);
|
|
1821
|
+
}
|
|
1822
|
+
const stat = fs.statSync(targetPath);
|
|
1823
|
+
let candidateFiles = [];
|
|
1824
|
+
if (stat.isFile()) {
|
|
1825
|
+
candidateFiles.push(targetPath);
|
|
1826
|
+
} else if (stat.isDirectory()) {
|
|
1827
|
+
const dirItems = fs.readdirSync(targetPath);
|
|
1828
|
+
for (const item of dirItems) {
|
|
1829
|
+
if (item.startsWith(".")) continue;
|
|
1830
|
+
if (
|
|
1831
|
+
item.startsWith("reltime-stamp-") ||
|
|
1832
|
+
item.startsWith("reltime-doomsday-proof-") ||
|
|
1833
|
+
item.startsWith("reltime-proof-") ||
|
|
1834
|
+
item.startsWith("reltime-audit-report-")
|
|
1835
|
+
) {
|
|
1836
|
+
continue;
|
|
1837
|
+
}
|
|
1838
|
+
const fullItemPath = path.join(targetPath, item);
|
|
1839
|
+
try {
|
|
1840
|
+
if (fs.statSync(fullItemPath).isFile()) {
|
|
1841
|
+
candidateFiles.push(fullItemPath);
|
|
1842
|
+
}
|
|
1843
|
+
} catch (e) {
|
|
1844
|
+
// Skip unreadable entry
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
const fileAuditResults = [];
|
|
1850
|
+
let vSettled = 0;
|
|
1851
|
+
let vPending = 0;
|
|
1852
|
+
let vUnanchored = 0;
|
|
1853
|
+
|
|
1854
|
+
for (const fPath of candidateFiles) {
|
|
1855
|
+
const buf = fs.readFileSync(fPath);
|
|
1856
|
+
const fName = path.basename(fPath);
|
|
1857
|
+
const rawSha256 = crypto.createHash("sha256").update(buf).digest("hex");
|
|
1858
|
+
const embeddedHash = extractStampedCopyEmbeddedHash(fPath, buf);
|
|
1859
|
+
const isStampedCopy = Boolean(embeddedHash) || fName.startsWith("reltime-stamped-");
|
|
1860
|
+
|
|
1861
|
+
let matchedEntry =
|
|
1862
|
+
ledgerByHash.get(rawSha256) || (embeddedHash ? ledgerByHash.get(embeddedHash) : null) || null;
|
|
1863
|
+
|
|
1864
|
+
// Also check biometric attested hashes SHA-256(rawSha256 + passkey_hex)
|
|
1865
|
+
if (!matchedEntry) {
|
|
1866
|
+
for (const pkHex of knownPasskeys) {
|
|
1867
|
+
const attestedCandidate = crypto
|
|
1868
|
+
.createHash("sha256")
|
|
1869
|
+
.update(`${rawSha256}${pkHex}`, "utf8")
|
|
1870
|
+
.digest("hex");
|
|
1871
|
+
if (ledgerByHash.has(attestedCandidate)) {
|
|
1872
|
+
matchedEntry = ledgerByHash.get(attestedCandidate);
|
|
1873
|
+
break;
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
// Fallback: query live GET /v1/proof/{hash} in case it was anchored outside the recent history window
|
|
1879
|
+
const probeHash = embeddedHash || rawSha256;
|
|
1880
|
+
if (!matchedEntry) {
|
|
1881
|
+
try {
|
|
1882
|
+
const proofRes = await fetch(`${API_BASE}/proof/${probeHash}`);
|
|
1883
|
+
if (proofRes.ok) {
|
|
1884
|
+
const proofData = await proofRes.json();
|
|
1885
|
+
const s = proofData.settlement || {};
|
|
1886
|
+
matchedEntry = {
|
|
1887
|
+
status: proofData.status || "settled",
|
|
1888
|
+
anchor_mode: proofData.anchor_mode || "standard",
|
|
1889
|
+
client_hash: proofData.client_hash || probeHash,
|
|
1890
|
+
t_index: proofData.t_index || probeHash,
|
|
1891
|
+
signer_public_key: proofData.signer_public_key || null,
|
|
1892
|
+
block_height: s.bitcoin_block_height || s.block_height || null,
|
|
1893
|
+
txid: s.bitcoin_txid || null,
|
|
1894
|
+
merkle_root: s.merkle_root || null,
|
|
1895
|
+
verify_url: `https://rel-time.com/verify?t=${probeHash}`,
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1898
|
+
} catch (e) {
|
|
1899
|
+
// Ignore network error during fallback lookup
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
let integrityStatus = "UNANCHORED_OR_MODIFIED";
|
|
1904
|
+
if (matchedEntry) {
|
|
1905
|
+
if (matchedEntry.status === "settled") {
|
|
1906
|
+
integrityStatus = "VERIFIED_SETTLED";
|
|
1907
|
+
vSettled++;
|
|
1908
|
+
} else {
|
|
1909
|
+
integrityStatus = "VERIFIED_PENDING";
|
|
1910
|
+
vPending++;
|
|
1911
|
+
}
|
|
1912
|
+
} else {
|
|
1913
|
+
vUnanchored++;
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
const bytes = buf.length;
|
|
1917
|
+
const sizeHuman =
|
|
1918
|
+
bytes >= 1048576
|
|
1919
|
+
? `${(bytes / 1048576).toFixed(2)} MB`
|
|
1920
|
+
: bytes >= 1024
|
|
1921
|
+
? `${(bytes / 1024).toFixed(1)} KB`
|
|
1922
|
+
: `${bytes} B`;
|
|
1923
|
+
|
|
1924
|
+
fileAuditResults.push({
|
|
1925
|
+
file_name: fName,
|
|
1926
|
+
file_path: fPath,
|
|
1927
|
+
size_bytes: bytes,
|
|
1928
|
+
size_human: sizeHuman,
|
|
1929
|
+
local_sha256: embeddedHash || rawSha256,
|
|
1930
|
+
raw_file_sha256: rawSha256,
|
|
1931
|
+
embedded_stamp_hash: embeddedHash,
|
|
1932
|
+
is_stamped_copy: isStampedCopy,
|
|
1933
|
+
integrity_status: integrityStatus,
|
|
1934
|
+
anchor_mode: matchedEntry ? matchedEntry.anchor_mode : null,
|
|
1935
|
+
signer_public_key: matchedEntry ? matchedEntry.signer_public_key : null,
|
|
1936
|
+
t_index: matchedEntry ? matchedEntry.t_index : null,
|
|
1937
|
+
block_height: matchedEntry ? matchedEntry.block_height : null,
|
|
1938
|
+
txid: matchedEntry ? matchedEntry.txid : null,
|
|
1939
|
+
verify_url: matchedEntry ? matchedEntry.verify_url : null,
|
|
1940
|
+
});
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
localPathVerification = {
|
|
1944
|
+
target_path: targetPath,
|
|
1945
|
+
summary: {
|
|
1946
|
+
total_files_scanned: fileAuditResults.length,
|
|
1947
|
+
verified_settled_count: vSettled,
|
|
1948
|
+
verified_pending_count: vPending,
|
|
1949
|
+
unanchored_or_modified_count: vUnanchored,
|
|
1950
|
+
overall_integrity: vUnanchored === 0 ? "ALL_FILES_VERIFIED" : "ATTENTION_UNANCHORED_OR_MODIFIED_FILES",
|
|
1951
|
+
},
|
|
1952
|
+
files: fileAuditResults,
|
|
1953
|
+
};
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
const maskedKey = key.length > 16 ? `${key.slice(0, 14)}…${key.slice(-4)}` : "rel_key_***";
|
|
1957
|
+
const settlementRatePct =
|
|
1958
|
+
filteredEntries.length > 0 ? `${Math.round((settledCount / filteredEntries.length) * 100)}%` : "0%";
|
|
1959
|
+
|
|
1960
|
+
const auditPayload = {
|
|
1961
|
+
status: "audit_complete",
|
|
1962
|
+
generated_at_utc: new Date().toISOString(),
|
|
1963
|
+
account_overview: {
|
|
1964
|
+
tier: acctData.tier || "Active",
|
|
1965
|
+
hash_credits_remaining: Number(acctData.hash_credits || 0),
|
|
1966
|
+
lifetime_anchors: Number(acctData.total_usage || totalInLedger || 0),
|
|
1967
|
+
masked_api_key: maskedKey,
|
|
1968
|
+
},
|
|
1969
|
+
audit_summary: {
|
|
1970
|
+
total_ledger_entries: totalInLedger,
|
|
1971
|
+
matched_entries_count: filteredEntries.length,
|
|
1972
|
+
settled_on_bitcoin_count: settledCount,
|
|
1973
|
+
pending_finality_count: pendingCount,
|
|
1974
|
+
settlement_rate_pct: settlementRatePct,
|
|
1975
|
+
mode_breakdown: {
|
|
1976
|
+
standard_count: standardCount,
|
|
1977
|
+
temporal_vdf_count: temporalCount,
|
|
1978
|
+
biometric_passkey_count: biometricCount,
|
|
1979
|
+
},
|
|
1980
|
+
bitcoin_settlement: {
|
|
1981
|
+
unique_blocks_count: uniqueBlocks.size,
|
|
1982
|
+
unique_txids_count: uniqueTxids.size,
|
|
1983
|
+
latest_settled_block: latestBlock,
|
|
1984
|
+
latest_txid: latestTxid,
|
|
1985
|
+
},
|
|
1986
|
+
hardware_passkey_signers: Array.from(signerMap.values()),
|
|
1987
|
+
},
|
|
1988
|
+
local_path_verification: localPathVerification,
|
|
1989
|
+
audit_trail: filteredEntries,
|
|
1990
|
+
};
|
|
1991
|
+
|
|
1992
|
+
// Determine output directory for reports
|
|
1993
|
+
let reportDir = path.join(PASSKEY_CONFIG_DIR, "audits");
|
|
1994
|
+
if (args.output_dir) {
|
|
1995
|
+
reportDir = resolveLocalFilePath(args.output_dir);
|
|
1996
|
+
} else if (localPathVerification) {
|
|
1997
|
+
const tp = localPathVerification.target_path;
|
|
1998
|
+
reportDir = fs.statSync(tp).isDirectory() ? tp : path.dirname(tp);
|
|
1999
|
+
}
|
|
2000
|
+
if (!fs.existsSync(reportDir)) {
|
|
2001
|
+
fs.mkdirSync(reportDir, { recursive: true });
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
const dateStamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
2005
|
+
const savedReports = {};
|
|
2006
|
+
|
|
2007
|
+
if (args.save_html_report !== false) {
|
|
2008
|
+
const htmlPath = path.join(reportDir, `reltime-audit-report-${dateStamp}.html`);
|
|
2009
|
+
const htmlContent = generateAuditReportHtml(auditPayload);
|
|
2010
|
+
fs.writeFileSync(htmlPath, htmlContent, "utf8");
|
|
2011
|
+
savedReports.html_report_path = htmlPath;
|
|
2012
|
+
|
|
2013
|
+
if (args.open_in_browser !== false) {
|
|
2014
|
+
try {
|
|
2015
|
+
openSystemBrowser(htmlPath);
|
|
2016
|
+
savedReports.opened_in_browser = true;
|
|
2017
|
+
} catch (e) {
|
|
2018
|
+
savedReports.opened_in_browser = false;
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
if (args.export_csv) {
|
|
2024
|
+
const csvPath = path.join(reportDir, `reltime-audit-trail-${dateStamp}.csv`);
|
|
2025
|
+
const csvHeader = [
|
|
2026
|
+
"timestamp_utc",
|
|
2027
|
+
"status",
|
|
2028
|
+
"anchor_mode",
|
|
2029
|
+
"identity_mode",
|
|
2030
|
+
"client_hash",
|
|
2031
|
+
"original_file_hash",
|
|
2032
|
+
"t_index",
|
|
2033
|
+
"vdf_ticks",
|
|
2034
|
+
"signer_public_key",
|
|
2035
|
+
"bitcoin_block_height",
|
|
2036
|
+
"bitcoin_txid",
|
|
2037
|
+
"merkle_root",
|
|
2038
|
+
"verify_url",
|
|
2039
|
+
].join(",");
|
|
2040
|
+
const csvRows = filteredEntries.map((e) =>
|
|
2041
|
+
[
|
|
2042
|
+
e.timestamp_utc || "",
|
|
2043
|
+
e.status || "",
|
|
2044
|
+
e.anchor_mode || "",
|
|
2045
|
+
e.identity_mode || "",
|
|
2046
|
+
e.client_hash || "",
|
|
2047
|
+
e.original_file_hash || "",
|
|
2048
|
+
e.t_index || "",
|
|
2049
|
+
e.vdf_ticks || 0,
|
|
2050
|
+
e.signer_public_key || "",
|
|
2051
|
+
e.block_height || "",
|
|
2052
|
+
e.txid || "",
|
|
2053
|
+
e.merkle_root || "",
|
|
2054
|
+
e.verify_url || "",
|
|
2055
|
+
]
|
|
2056
|
+
.map((v) => `"${String(v).replace(/"/g, '""')}"`)
|
|
2057
|
+
.join(",")
|
|
2058
|
+
);
|
|
2059
|
+
fs.writeFileSync(csvPath, [csvHeader, ...csvRows].join("\n"), "utf8");
|
|
2060
|
+
savedReports.csv_report_path = csvPath;
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
auditPayload.saved_reports = savedReports;
|
|
2064
|
+
return auditPayload;
|
|
2065
|
+
}
|
|
2066
|
+
|
|
987
2067
|
if (name === "reltime_provision_agent_key") {
|
|
988
2068
|
if (args.action === "create_invoice") {
|
|
989
2069
|
const validPacks = ["universal", "launch", "standard", "enterprise", "superhighway"];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rel-time/mcp-server",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.8",
|
|
4
4
|
"description": "Official Model Context Protocol (MCP) server for Rel-Time (rel-time.com) — Zero-Knowledge Bitcoin & VDF Provenance, Local File Hashing, and Sun-Stamp Generation for Claude, Cursor, Windsurf, and Antigravity.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|