@rel-time/mcp-server 1.2.2 → 1.2.4

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.
Files changed (3) hide show
  1. package/README.md +8 -7
  2. package/index.js +183 -57
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -10,25 +10,26 @@ Official Model Context Protocol (MCP) server for **[Rel-Time](https://rel-time.c
10
10
 
11
11
  ---
12
12
 
13
- ## Quick Setup (Claude Desktop / Cursor / Windsurf / Antigravity)
13
+ ## Quick Setup (Claude Desktop / Claude Code / Cursor / Windsurf / Antigravity)
14
14
 
15
- Add the following to your `claude_desktop_config.json` or `.cursor/mcp.json`:
15
+ Add the following to your `.mcp.json`, `claude_desktop_config.json`, or `.cursor/mcp.json` (no API key or `.zshrc` environment variables needed to start!):
16
16
 
17
17
  ```json
18
18
  {
19
19
  "mcpServers": {
20
20
  "rel-time": {
21
21
  "command": "npx",
22
- "args": ["-y", "@rel-time/mcp-server"],
23
- "env": {
24
- "RELTIME_API_KEY": "YOUR_RELTIME_API_KEY"
25
- }
22
+ "args": ["-y", "@rel-time/mcp-server@latest"]
26
23
  }
27
24
  }
28
25
  }
29
26
  ```
30
27
 
31
- *(Note: `reltime_link_passkey`, `reltime_get_sun_stamp`, `reltime_check_proof`, and `reltime_provision_agent_key` work immediately even without an API key configured!)*
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 pack you want first:
29
+ 1. **Agent Launch Pack (`pack: "launch"`, `$5 / 50 Hashes`):** Priority 10-Minute Bitcoin Queue · Standard Mode only (Data Integrity + Biometric or Anonymous) · Includes 64-Ray Sun-Stamp, 1-Click Stamped Copy (`.PDF`/`.PNG`), and `.JSON` Doomsday Proof.
30
+ 2. **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`).
31
+
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.
32
33
 
33
34
  ---
34
35
 
package/index.js CHANGED
@@ -18,11 +18,36 @@ const path = require("path");
18
18
  const readline = require("readline");
19
19
  const { exec } = require("child_process");
20
20
  const { PDFDocument } = require("pdf-lib");
21
+ const QRCode = require("qrcode");
21
22
  const sharp = require("sharp");
22
23
 
23
- const API_BASE = (process.env.RELTIME_API_URL || "https://rel-time.com/api/v1").replace(/\/+$/, "");
24
+ function normalizeApiBase(rawUrl) {
25
+ const cleaned = (rawUrl || "https://rel-time.com/api/v1").trim().replace(/\/+$/, "");
26
+ if (
27
+ cleaned === "https://rel-time.com/v1" ||
28
+ cleaned === "https://rel-time.com" ||
29
+ cleaned.includes("api.rel-time.com")
30
+ ) {
31
+ return "https://rel-time.com/api/v1";
32
+ }
33
+ return cleaned;
34
+ }
35
+
36
+ function isValidApiKeyCandidate(k) {
37
+ if (!k || typeof k !== "string") return false;
38
+ const trimmed = k.trim();
39
+ if (!trimmed || trimmed.length < 16) return false;
40
+ if (trimmed.includes("${") || trimmed.includes("...") || trimmed.includes("YOUR_") || trimmed.includes("<")) {
41
+ return false;
42
+ }
43
+ return trimmed.startsWith("rel_");
44
+ }
45
+
46
+ const API_BASE = normalizeApiBase(process.env.RELTIME_API_URL);
24
47
  const WEB_BASE = (process.env.RELTIME_WEB_URL || "https://rel-time.com").replace(/\/+$/, "");
25
- let CURRENT_API_KEY = process.env.RELTIME_API_KEY || "";
48
+ let CURRENT_API_KEY = isValidApiKeyCandidate(process.env.RELTIME_API_KEY)
49
+ ? process.env.RELTIME_API_KEY.trim()
50
+ : "";
26
51
 
27
52
  const PASSKEY_CONFIG_DIR = path.join(os.homedir(), ".reltime");
28
53
  const PASSKEY_CONFIG_PATH = path.join(PASSKEY_CONFIG_DIR, "passkey.json");
@@ -31,23 +56,53 @@ let LAST_INVOICE_PACK = null;
31
56
 
32
57
  const SERVER_INFO = {
33
58
  name: "rel-time-mcp-server",
34
- version: "1.2.2",
59
+ version: "1.2.4",
35
60
  };
36
61
 
62
+ /**
63
+ * Resolves a local file path, automatically matching macOS screenshot filenames
64
+ * that contain Narrow No-Break Spaces (U+202F) or Non-Breaking Spaces (U+00A0)
65
+ * when the caller passes normal ASCII spaces.
66
+ */
67
+ function resolveLocalFilePath(rawPath) {
68
+ const resolved = path.resolve(rawPath);
69
+ if (fs.existsSync(resolved)) {
70
+ return resolved;
71
+ }
72
+ const dir = path.dirname(resolved);
73
+ const targetBase = path.basename(resolved);
74
+ const normalizeSpaces = (s) => s.normalize("NFC").replace(/[\u00A0\u202F\u2007\u2009\s]+/g, " ");
75
+ const normalizedTarget = normalizeSpaces(targetBase);
76
+ try {
77
+ if (fs.existsSync(dir)) {
78
+ const entries = fs.readdirSync(dir);
79
+ for (const entry of entries) {
80
+ if (normalizeSpaces(entry) === normalizedTarget) {
81
+ return path.join(dir, entry);
82
+ }
83
+ }
84
+ }
85
+ } catch (e) {
86
+ // Ignore directory read error
87
+ }
88
+ return resolved;
89
+ }
90
+
37
91
  /**
38
92
  * Loads saved API key from ~/.reltime/api_key.json if not provided via env/args.
39
93
  */
40
94
  function loadSavedApiKey() {
41
- if (CURRENT_API_KEY && CURRENT_API_KEY.trim()) {
95
+ if (isValidApiKeyCandidate(CURRENT_API_KEY)) {
42
96
  return CURRENT_API_KEY.trim();
43
97
  }
44
- if (process.env.RELTIME_API_KEY && process.env.RELTIME_API_KEY.trim()) {
45
- return process.env.RELTIME_API_KEY.trim();
98
+ if (isValidApiKeyCandidate(process.env.RELTIME_API_KEY)) {
99
+ CURRENT_API_KEY = process.env.RELTIME_API_KEY.trim();
100
+ return CURRENT_API_KEY;
46
101
  }
47
102
  try {
48
103
  if (fs.existsSync(API_KEY_CONFIG_PATH)) {
49
104
  const parsed = JSON.parse(fs.readFileSync(API_KEY_CONFIG_PATH, "utf8"));
50
- if (parsed.api_key && parsed.api_key.trim()) {
105
+ if (isValidApiKeyCandidate(parsed.api_key)) {
51
106
  CURRENT_API_KEY = parsed.api_key.trim();
52
107
  return CURRENT_API_KEY;
53
108
  }
@@ -58,6 +113,41 @@ function loadSavedApiKey() {
58
113
  return "";
59
114
  }
60
115
 
116
+ function buildPackSelectionPrompt(contextMessage) {
117
+ return {
118
+ status: "pack_selection_required",
119
+ api_endpoint: API_BASE,
120
+ important_instruction_for_ai_agent:
121
+ "DO NOT tell the user to edit ~/.zshrc, .mcp.json, or restart Claude/Cursor! The MCP server manages and persists API keys automatically in ~/.reltime/api_key.json. Simply ask the user right here in chat which of the two Agent API Key packs below they want to provision (or if they already have a `rel_key_...` key to paste), then call `reltime_provision_agent_key` with `action: 'create_invoice'` and `pack: 'launch'` or `pack: 'standard'`.",
122
+ message:
123
+ contextMessage ||
124
+ "No Rel-Time API key is active yet. Ask the user which key pack they want to provision first:",
125
+ available_packs: [
126
+ {
127
+ pack: "launch",
128
+ name: "1. Agent Launch Pack ($5 / 50 Hashes)",
129
+ price_usd: "$5.00",
130
+ credits: 50,
131
+ settlement_queue: "Priority 10-Minute Bitcoin Queue",
132
+ hashing_modes: "Standard Mode only (Data Integrity + Biometric or Anonymous)",
133
+ outputs: "64-Ray Sun-Stamp SVG + 1-Click Stamped Copy (.PDF/.PNG) + .JSON Doomsday Proof",
134
+ },
135
+ {
136
+ pack: "standard",
137
+ name: "2. Autonomous Agents ($50 / 500 Hashes)",
138
+ price_usd: "$50.00",
139
+ credits: 500,
140
+ settlement_queue: "Dynamic 6-Hour Batch Queue",
141
+ hashing_modes: "Temporal Mode (T_index sub-second VDF sequencing) + Standard Mode (Biometric or Anonymous)",
142
+ outputs:
143
+ "64-Ray Sun-Stamp SVG + 1-Click Stamped Copy (.PDF/.PNG) + .JSON Doomsday Proof + Reloadable anytime (+500 credits / $50)",
144
+ },
145
+ ],
146
+ next_step:
147
+ "Ask the user: 'Would you like to start with the **Agent Launch Pack ($5 / 50 Hashes · 10-Min Queue · Standard Mode)** or the **Autonomous Agents Pack ($50 / 500 Hashes · 6-Hr Queue · Standard + Temporal Mode)**?' Once they answer, call `reltime_provision_agent_key({ action: 'create_invoice', pack: 'launch' | 'standard' })`.",
148
+ };
149
+ }
150
+
61
151
  /**
62
152
  * Saves provisioned API key to ~/.reltime/api_key.json with 0600 permissions.
63
153
  */
@@ -143,7 +233,7 @@ function openSystemBrowser(url) {
143
233
  * Rel-Time WebAuthn Passkey Bridge in the user's browser so they can touch
144
234
  * TouchID / FaceID / YubiKey.
145
235
  */
146
- function launchBrowserPasskeyBridge(timeoutMs = 120000) {
236
+ function launchBrowserPasskeyBridge(timeoutMs = 300000) {
147
237
  return new Promise((resolve, reject) => {
148
238
  const server = http.createServer((req, res) => {
149
239
  res.setHeader("Access-Control-Allow-Origin", "*");
@@ -428,13 +518,14 @@ const TOOLS = [
428
518
  {
429
519
  name: "reltime_account_status",
430
520
  description:
431
- "Checks the remaining Hash Credits, tier, and lifetime usage of the configured Rel-Time API key (`GET /v1/account`).",
521
+ "Checks the remaining Hash Credits, tier, and lifetime usage of the configured Rel-Time API key (`GET /api/v1/account`). If no API key is configured yet, returns the two available Agent API Key packs (`launch` $5/50 hashes vs `standard` $50/500 hashes) so you can ask the user which pack they want to provision in chat (without needing any .zshrc or config edits).",
432
522
  inputSchema: {
433
523
  type: "object",
434
524
  properties: {
435
525
  api_key: {
436
526
  type: "string",
437
- description: "Optional override for RELTIME_API_KEY.",
527
+ description:
528
+ "Optional: pass a `rel_key_...` API key to check its balance AND automatically save it to ~/.reltime/api_key.json for all future calls.",
438
529
  },
439
530
  },
440
531
  },
@@ -442,7 +533,7 @@ const TOOLS = [
442
533
  {
443
534
  name: "reltime_provision_agent_key",
444
535
  description:
445
- "Self-onboarding & Top-Up for Autonomous AI Agents via Bitcoin Lightning (`POST /v1/agent/invoice` & `POST /v1/agent/claim`). IMPORTANT: Before calling `create_invoice` for a new key, ALWAYS ask the user which pack they want first:\n1. **Agent Launch Pack (`pack: 'launch'`, $5 / 50 Hashes):** Priority 10-Minute Bitcoin Queue · Standard Mode only (Data Integrity + Biometric or Anonymous) · Includes Sun-Stamp, 1-Click Stamped Copy (.PDF/.PNG), and .JSON Doomsday Proof.\n2. **Autonomous Agents (`pack: 'standard'`, $50 / 500 Hashes):** Dynamic 6-Hour Batch Queue · Supports both Temporal Mode (`T_index` sub-second sequencing) and Standard Mode · Reloadable anytime (+500 credits / $50) with zero key rotation.",
536
+ "Self-onboarding & Top-Up for Autonomous AI Agents via Bitcoin Lightning (`POST /v1/agent/invoice` & `POST /v1/agent/claim`). IMPORTANT:\n1. Before calling `create_invoice` for a new key, ALWAYS ask the user which pack they want first:\n - **Agent Launch Pack (`pack: 'launch'`, $5 / 50 Hashes):** Priority 10-Minute Bitcoin Queue · Standard Mode only (Data Integrity + Biometric or Anonymous) · Includes Sun-Stamp, 1-Click Stamped Copy (.PDF/.PNG), and .JSON Doomsday Proof.\n - **Autonomous Agents (`pack: 'standard'`, $50 / 500 Hashes):** Dynamic 6-Hour Batch Queue · Supports both Temporal Mode (`T_index` sub-second sequencing) and Standard Mode · Reloadable anytime (+500 credits / $50) with zero key rotation.\n2. When `create_invoice` returns, ALWAYS present BOTH the scannable QR code image (by calling Read on `qr_code_png_path` so the image renders inline in chat!) AND the full, un-truncated `ln_invoice` (`bolt11`) string in a copyable code block on the VERY FIRST try!",
446
537
  inputSchema: {
447
538
  type: "object",
448
539
  properties: {
@@ -479,7 +570,11 @@ const TOOLS = [
479
570
  ];
480
571
 
481
572
  async function executeTool(name, args = {}) {
482
- const key = (args.api_key || loadSavedApiKey() || "").trim();
573
+ if (args.api_key && isValidApiKeyCandidate(args.api_key)) {
574
+ CURRENT_API_KEY = args.api_key.trim();
575
+ saveApiKeyConfig(CURRENT_API_KEY, "custom");
576
+ }
577
+ const key = (isValidApiKeyCandidate(args.api_key) ? args.api_key.trim() : loadSavedApiKey()) || "";
483
578
 
484
579
  if (name === "reltime_link_passkey") {
485
580
  if (args.public_key_hex && args.passkey_signature) {
@@ -506,15 +601,15 @@ async function executeTool(name, args = {}) {
506
601
  }
507
602
 
508
603
  if (name === "reltime_anchor_file") {
509
- const filePath = path.resolve(args.file_path);
510
- if (!fs.existsSync(filePath)) {
511
- throw new Error(`File not found at path: ${filePath}`);
512
- }
513
604
  if (!key) {
514
- throw new Error(
515
- "Missing RELTIME_API_KEY. Please ask the user which API key pack they would like to provision first before calling `reltime_provision_agent_key`:\n1. **Agent Launch Pack (`pack: \"launch\"`, $5 / 50 Hashes):** Priority 10-Min Bitcoin Queue · Standard Mode only (Data Integrity + Biometric or Anonymous)\n2. **Autonomous Agents (`pack: \"standard\"`, $50 / 500 Hashes):** Dynamic 6-Hr Batch Queue · Temporal Mode (T_index sequencing) + Standard Mode · Reloadable anytime"
605
+ return buildPackSelectionPrompt(
606
+ "Before anchoring files, a Rel-Time API key is required. Ask the user which pack they want to provision first:"
516
607
  );
517
608
  }
609
+ const filePath = resolveLocalFilePath(args.file_path);
610
+ if (!fs.existsSync(filePath)) {
611
+ throw new Error(`File not found at path: ${filePath}`);
612
+ }
518
613
 
519
614
  // 1. Compute SHA-256 locally (Zero-Knowledge: raw file never leaves the device)
520
615
  const fileBuffer = fs.readFileSync(filePath);
@@ -631,7 +726,7 @@ async function executeTool(name, args = {}) {
631
726
  }
632
727
 
633
728
  if (name === "reltime_stamp_file") {
634
- const filePath = path.resolve(args.file_path);
729
+ const filePath = resolveLocalFilePath(args.file_path);
635
730
  if (!fs.existsSync(filePath)) {
636
731
  throw new Error(`File not found at path: ${filePath}`);
637
732
  }
@@ -659,8 +754,8 @@ async function executeTool(name, args = {}) {
659
754
 
660
755
  if (name === "reltime_anchor_hash") {
661
756
  if (!key) {
662
- throw new Error(
663
- "Missing RELTIME_API_KEY. Please ask the user which API key pack they would like to provision first before calling `reltime_provision_agent_key`: 1) Agent Launch Pack (`pack: \"launch\"`, $5 / 50 Hashes · 10-Min Queue · Standard Mode only) or 2) Autonomous Agents (`pack: \"standard\"`, $50 / 500 Hashes · 6-Hr Queue · Temporal & Standard Modes)."
757
+ return buildPackSelectionPrompt(
758
+ "Before anchoring hashes, a Rel-Time API key is required. Ask the user which pack they want to provision first:"
664
759
  );
665
760
  }
666
761
  const clientHash = (args.client_hash || "").trim().toLowerCase();
@@ -746,13 +841,26 @@ async function executeTool(name, args = {}) {
746
841
 
747
842
  if (name === "reltime_account_status") {
748
843
  if (!key) {
749
- throw new Error("Missing RELTIME_API_KEY.");
844
+ return buildPackSelectionPrompt(
845
+ "Rel-Time MCP is connected! No API key is saved yet. Ask the user which pack they would like to provision first:"
846
+ );
750
847
  }
751
848
  const res = await fetch(`${API_BASE}/account`, {
752
849
  headers: { Authorization: `Bearer ${key}` },
753
850
  });
754
- const data = await res.json();
851
+ const text = await res.text();
852
+ let data;
853
+ try {
854
+ data = JSON.parse(text);
855
+ } catch (e) {
856
+ throw new Error(`Rel-Time Account error (${res.status}) from ${API_BASE}/account: ${text.slice(0, 200)}`);
857
+ }
755
858
  if (!res.ok) {
859
+ if (res.status === 401 || res.status === 403) {
860
+ return buildPackSelectionPrompt(
861
+ "The configured API key was not recognized. Ask the user which pack they would like to provision:"
862
+ );
863
+ }
756
864
  throw new Error(`Rel-Time Account error (${res.status}): ${JSON.stringify(data)}`);
757
865
  }
758
866
  return data;
@@ -761,33 +869,9 @@ async function executeTool(name, args = {}) {
761
869
  if (name === "reltime_provision_agent_key") {
762
870
  if (args.action === "create_invoice") {
763
871
  if (args.pack !== "launch" && args.pack !== "standard") {
764
- return {
765
- status: "pack_selection_required",
766
- message:
767
- "Please ask the user which Rel-Time Agent API Key pack they would like to provision before generating the Lightning invoice:",
768
- available_packs: [
769
- {
770
- pack: "launch",
771
- name: "Agent Launch Pack",
772
- price_usd: "$5.00",
773
- credits: 50,
774
- settlement_queue: "Priority 10-Minute Bitcoin Queue",
775
- hashing_modes: "Standard Mode only (Data Integrity + Biometric or Anonymous)",
776
- outputs: "64-Ray Sun-Stamp SVG + 1-Click Stamped Copy (.PDF/.PNG) + .JSON Doomsday Proof",
777
- },
778
- {
779
- pack: "standard",
780
- name: "Autonomous Agents",
781
- price_usd: "$50.00",
782
- credits: 500,
783
- settlement_queue: "Dynamic 6-Hour Batch Queue",
784
- hashing_modes: "Temporal Mode (T_index sub-second VDF sequencing) + Standard Mode (Biometric or Anonymous)",
785
- outputs: "64-Ray Sun-Stamp SVG + 1-Click Stamped Copy (.PDF/.PNG) + .JSON Doomsday Proof + Reloadable anytime (+500 credits / $50)",
786
- },
787
- ],
788
- next_step:
789
- "Once the user chooses, call `reltime_provision_agent_key` with `action: 'create_invoice'` and `pack: 'launch'` or `pack: 'standard'`.",
790
- };
872
+ return buildPackSelectionPrompt(
873
+ "Please ask the user which Rel-Time Agent API Key pack they would like to provision before generating the Lightning invoice:"
874
+ );
791
875
  }
792
876
 
793
877
  LAST_INVOICE_PACK = args.pack;
@@ -799,7 +883,40 @@ async function executeTool(name, args = {}) {
799
883
  pack: args.pack,
800
884
  }),
801
885
  });
802
- return await res.json();
886
+ const data = await res.json();
887
+ const bolt11 = data.ln_invoice || data.bolt11 || "";
888
+ if (bolt11) {
889
+ try {
890
+ if (!fs.existsSync(PASSKEY_CONFIG_DIR)) {
891
+ fs.mkdirSync(PASSKEY_CONFIG_DIR, { recursive: true, mode: 0o700 });
892
+ }
893
+ const qrPngBuffer = await QRCode.toBuffer(bolt11.toUpperCase(), {
894
+ type: "png",
895
+ width: 560,
896
+ margin: 2,
897
+ errorCorrectionLevel: "M",
898
+ });
899
+ const homeQrPath = path.join(PASSKEY_CONFIG_DIR, "lightning-invoice-qr.png");
900
+ const tmpQrPath = "/tmp/reltime-lightning-qr.png";
901
+ fs.writeFileSync(homeQrPath, qrPngBuffer);
902
+ try {
903
+ fs.writeFileSync(tmpQrPath, qrPngBuffer);
904
+ } catch (e) {
905
+ // Ignore /tmp write error
906
+ }
907
+ data.bolt11 = bolt11;
908
+ data.ln_invoice = bolt11;
909
+ data.qr_code_png_path = homeQrPath;
910
+ data.qr_code_tmp_png_path = tmpQrPath;
911
+ data.invoice_expiration = "60 minutes (3,600 seconds)";
912
+ data.mandatory_first_reply_instructions =
913
+ "CRITICAL INSTRUCTION FOR AI AGENT: On this very first reply, you MUST show the user BOTH: (1) the scannable QR code image (immediately call your Read tool on `qr_code_png_path` BEFORE sending your message so the QR code PNG renders inline in the chat window right now!) AND (2) the full, un-truncated `bolt11` (`lnbc...`) Lightning invoice string inside a copyable code block so the user can either scan with their phone or copy-paste on the first try without asking!";
914
+ data._mcp_image_base64 = qrPngBuffer.toString("base64");
915
+ } catch (qrErr) {
916
+ data.qr_error = qrErr.message;
917
+ }
918
+ }
919
+ return data;
803
920
  } else if (args.action === "claim_invoice") {
804
921
  if (!args.invoice_id) throw new Error("invoice_id is required when action is 'claim_invoice'.");
805
922
  const resolvedPack = args.pack || LAST_INVOICE_PACK || undefined;
@@ -879,16 +996,25 @@ rl.on("line", async (line) => {
879
996
  const toolArgs = params?.arguments || {};
880
997
  try {
881
998
  const resultObj = await executeTool(toolName, toolArgs);
999
+ const contentItems = [];
1000
+ if (resultObj && resultObj._mcp_image_base64) {
1001
+ const b64 = resultObj._mcp_image_base64;
1002
+ delete resultObj._mcp_image_base64;
1003
+ contentItems.push({
1004
+ type: "image",
1005
+ data: b64,
1006
+ mimeType: "image/png",
1007
+ });
1008
+ }
1009
+ contentItems.push({
1010
+ type: "text",
1011
+ text: JSON.stringify(resultObj, null, 2),
1012
+ });
882
1013
  sendJsonRpc({
883
1014
  jsonrpc: "2.0",
884
1015
  id,
885
1016
  result: {
886
- content: [
887
- {
888
- type: "text",
889
- text: JSON.stringify(resultObj, null, 2),
890
- },
891
- ],
1017
+ content: contentItems,
892
1018
  },
893
1019
  });
894
1020
  } catch (toolErr) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rel-time/mcp-server",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
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": {
@@ -32,6 +32,7 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "pdf-lib": "^1.17.1",
35
+ "qrcode": "^1.5.4",
35
36
  "sharp": "^0.35.5"
36
37
  }
37
38
  }