@rel-time/mcp-server 1.2.4 → 1.2.6

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 (2) hide show
  1. package/index.js +216 -41
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -17,7 +17,7 @@ const os = require("os");
17
17
  const path = require("path");
18
18
  const readline = require("readline");
19
19
  const { exec } = require("child_process");
20
- const { PDFDocument } = require("pdf-lib");
20
+ const { PDFDocument, PDFName, PDFString } = require("pdf-lib");
21
21
  const QRCode = require("qrcode");
22
22
  const sharp = require("sharp");
23
23
 
@@ -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.4",
59
+ version: "1.2.6",
60
60
  };
61
61
 
62
62
  /**
@@ -118,33 +118,55 @@ function buildPackSelectionPrompt(contextMessage) {
118
118
  status: "pack_selection_required",
119
119
  api_endpoint: API_BASE,
120
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'`.",
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: (1) which of the 4 Rel-Time Tiers below they want to provision (`universal`, `launch`, `standard`, or `enterprise`), AND (2) whether they want to pay via **Bitcoin Lightning (`lightning`)** or **Stripe Card (`stripe`)** (or if they already have a `rel_key_...` key to paste). Then call `reltime_provision_agent_key` with `action: 'create_invoice'`, `pack`, and `payment_method`.",
122
122
  message:
123
123
  contextMessage ||
124
- "No Rel-Time API key is active yet. Ask the user which key pack they want to provision first:",
124
+ "No Rel-Time API key is active yet. Ask the user which tier and payment method (Lightning or Stripe) they want to provision:",
125
125
  available_packs: [
126
+ {
127
+ pack: "universal",
128
+ name: "1. Universal Anchor ($3 / anchor)",
129
+ price_usd: "$3.00 per credit",
130
+ credits: 1,
131
+ settlement_queue: "Priority 10-Minute Bitcoin Queue",
132
+ hashing_modes: "Standard Mode (Data Integrity + Biometric or Anonymous)",
133
+ payment_methods: ["lightning", "stripe"],
134
+ outputs: "64-Ray Sun-Stamp SVG + 1-Click Stamped Copy (.PDF/.PNG) + .JSON Doomsday Proof",
135
+ },
126
136
  {
127
137
  pack: "launch",
128
- name: "1. Agent Launch Pack ($5 / 50 Hashes)",
138
+ name: "2. Agent Launch Pack ($5 / 50 Hashes)",
129
139
  price_usd: "$5.00",
130
140
  credits: 50,
131
141
  settlement_queue: "Priority 10-Minute Bitcoin Queue",
132
142
  hashing_modes: "Standard Mode only (Data Integrity + Biometric or Anonymous)",
143
+ payment_methods: ["lightning", "stripe"],
133
144
  outputs: "64-Ray Sun-Stamp SVG + 1-Click Stamped Copy (.PDF/.PNG) + .JSON Doomsday Proof",
134
145
  },
135
146
  {
136
147
  pack: "standard",
137
- name: "2. Autonomous Agents ($50 / 500 Hashes)",
148
+ name: "3. Autonomous Agents ($50 / 500 Hashes)",
138
149
  price_usd: "$50.00",
139
150
  credits: 500,
140
151
  settlement_queue: "Dynamic 6-Hour Batch Queue",
141
152
  hashing_modes: "Temporal Mode (T_index sub-second VDF sequencing) + Standard Mode (Biometric or Anonymous)",
153
+ payment_methods: ["lightning", "stripe"],
142
154
  outputs:
143
155
  "64-Ray Sun-Stamp SVG + 1-Click Stamped Copy (.PDF/.PNG) + .JSON Doomsday Proof + Reloadable anytime (+500 credits / $50)",
144
156
  },
157
+ {
158
+ pack: "enterprise",
159
+ name: "4. Rel-Time Superhighway ($600 / 5,000 Hashes)",
160
+ price_usd: "$600.00",
161
+ credits: 5000,
162
+ settlement_queue: "Priority 10-Minute Bitcoin Queue",
163
+ hashing_modes: "Temporal Mode (T_index) + Standard Mode + Bulk Folder & Superhighway Dashboard",
164
+ payment_methods: ["lightning", "stripe"],
165
+ outputs: "64-Ray Sun-Stamp SVG + 1-Click Stamped Copy (.PDF/.PNG) + Certificate, HTML & .JSON Proof",
166
+ },
145
167
  ],
146
168
  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' })`.",
169
+ "Ask the user which tier (`universal` $3, `launch` $5/50, `standard` $50/500, or `enterprise` $600/5,000) and which payment method (`lightning` or `stripe`) they prefer, then call `reltime_provision_agent_key({ action: 'create_invoice', pack, payment_method })`.",
148
170
  };
149
171
  }
150
172
 
@@ -282,10 +304,48 @@ function launchBrowserPasskeyBridge(timeoutMs = 300000) {
282
304
  }
283
305
 
284
306
  /**
285
- * Stamps the Option A (pure crisp 64-ray Sun-Stamp, 0% glare, no pill) onto a local Image or PDF.
307
+ * Injects a standard PNG tEXt chunk ("RelTimeVerify\0<verifyUrl>") right before IEND
308
+ * so Stamped PNG copies can be instantaneously verified when dropped into /verify.
309
+ */
310
+ function injectPngVerifyBuffer(pngBuffer, verifyUrl) {
311
+ if (!verifyUrl || !Buffer.isBuffer(pngBuffer) || pngBuffer.length < 24) {
312
+ return pngBuffer;
313
+ }
314
+ const iendOffset = pngBuffer.length - 12;
315
+ if (pngBuffer.slice(iendOffset + 4, iendOffset + 8).toString("ascii") !== "IEND") {
316
+ return pngBuffer;
317
+ }
318
+ const payload = Buffer.from(`RelTimeVerify\0${verifyUrl}`, "utf8");
319
+ const typeBuf = Buffer.from("tEXt", "ascii");
320
+ const crcInput = Buffer.concat([typeBuf, payload]);
321
+
322
+ let crc = 0xffffffff;
323
+ for (let i = 0; i < crcInput.length; i++) {
324
+ crc ^= crcInput[i];
325
+ for (let b = 0; b < 8; b++) {
326
+ crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);
327
+ }
328
+ }
329
+ crc = (crc ^ 0xffffffff) >>> 0;
330
+
331
+ const lenBuf = Buffer.alloc(4);
332
+ lenBuf.writeUInt32BE(payload.length, 0);
333
+ const crcBuf = Buffer.alloc(4);
334
+ crcBuf.writeUInt32BE(crc, 0);
335
+
336
+ const chunk = Buffer.concat([lenBuf, crcInput, crcBuf]);
337
+ return Buffer.concat([
338
+ pngBuffer.slice(0, iendOffset),
339
+ chunk,
340
+ pngBuffer.slice(iendOffset),
341
+ ]);
342
+ }
343
+
344
+ /**
345
+ * Stamps the Option 1A (+14° Bitcoin Tilt, pure crisp 64-ray Sun-Stamp, 0% glare, no pill) onto a local Image or PDF.
286
346
  * Returns { stamped: true, output_path, format } or { stamped: false, reason }.
287
347
  */
288
- async function stampLocalFileOptionA(filePath, svgContent, customOutputPath = null) {
348
+ async function stampLocalFileOptionA(filePath, svgContent, customOutputPath = null, verifyHash = null) {
289
349
  const ext = path.extname(filePath).toLowerCase().replace(/^\./, "");
290
350
  const dir = path.dirname(filePath);
291
351
  const baseName = path.basename(filePath, path.extname(filePath));
@@ -300,18 +360,32 @@ async function stampLocalFileOptionA(filePath, svgContent, customOutputPath = nu
300
360
  };
301
361
  }
302
362
 
363
+ // Resolve verification URL from verifyHash or from <a href="..."> inside svgContent
364
+ let verifyUrl = verifyHash ? `https://rel-time.com/verify?t=${verifyHash.toLowerCase()}` : null;
365
+ if (!verifyUrl && typeof svgContent === "string") {
366
+ const match = svgContent.match(/https?:\/\/rel-time\.com\/verify\?[tThH]=([0-9a-fA-F]{64})/);
367
+ if (match) {
368
+ verifyUrl = `https://rel-time.com/verify?t=${match[1].toLowerCase()}`;
369
+ }
370
+ }
371
+
372
+ // Strip outer <a> wrapper before rasterizing with sharp/librsvg
373
+ const cleanSvg = String(svgContent || "")
374
+ .replace(/<a\b[^>]*>/gi, "")
375
+ .replace(/<\/a>/gi, "");
376
+
303
377
  if (isImage) {
304
378
  const outPath = customOutputPath ? path.resolve(customOutputPath) : path.join(dir, `reltime-stamped-${baseName}.png`);
305
379
  const img = sharp(filePath);
306
380
  const meta = await img.metadata();
307
- const w = meta.width || 1200;
308
- const h = meta.height || 800;
381
+ const w = (meta.autoOrient && meta.autoOrient.width) || meta.width || 1200;
382
+ const h = (meta.autoOrient && meta.autoOrient.height) || meta.height || 800;
309
383
 
310
384
  const minDim = Math.min(w, h);
311
- const badgeSize = Math.max(130, Math.min(420, Math.round(minDim * 0.22)));
312
- const margin = Math.max(12, Math.round(minDim * 0.025));
385
+ const badgeSize = Math.max(180, Math.round(minDim * 0.22));
386
+ const margin = Math.max(14, Math.round(minDim * 0.025));
313
387
 
314
- const stampPngBuffer = await sharp(Buffer.from(svgContent, "utf8"))
388
+ const stampPngBuffer = await sharp(Buffer.from(cleanSvg, "utf8"))
315
389
  .resize(badgeSize, badgeSize)
316
390
  .png()
317
391
  .toBuffer();
@@ -319,10 +393,14 @@ async function stampLocalFileOptionA(filePath, svgContent, customOutputPath = nu
319
393
  const left = Math.max(0, w - badgeSize - margin);
320
394
  const top = Math.max(0, h - badgeSize - margin);
321
395
 
322
- await img
396
+ const rawOutBuf = await sharp(filePath)
397
+ .rotate()
323
398
  .composite([{ input: stampPngBuffer, left, top }])
324
399
  .png()
325
- .toFile(outPath);
400
+ .toBuffer();
401
+
402
+ const finalPngBuf = verifyUrl ? injectPngVerifyBuffer(rawOutBuf, verifyUrl) : rawOutBuf;
403
+ fs.writeFileSync(outPath, finalPngBuf);
326
404
 
327
405
  return {
328
406
  stamped: true,
@@ -343,24 +421,52 @@ async function stampLocalFileOptionA(filePath, svgContent, customOutputPath = nu
343
421
  const firstPage = pages[0];
344
422
  const { width, height } = firstPage.getSize();
345
423
 
346
- // Render high-DPI 480x480 Option A Sun-Stamp PNG (pure crisp SVG, 0% glare, no pill)
347
- const stampPngBuffer = await sharp(Buffer.from(svgContent, "utf8"))
348
- .resize(480, 480)
424
+ // Render high-DPI 1024x1024 (300+ DPI) Option 1A Sun-Stamp PNG
425
+ const stampPngBuffer = await sharp(Buffer.from(cleanSvg, "utf8"))
426
+ .resize(1024, 1024)
349
427
  .png()
350
428
  .toBuffer();
351
429
 
352
430
  const pngImage = await pdfDoc.embedPng(stampPngBuffer);
353
- const targetSize = Math.min(135, Math.max(85, Math.min(width, height) * 0.2));
431
+ const targetSize = Math.min(148, Math.max(105, Math.min(width, height) * 0.22));
354
432
  const margin = 18;
433
+ const drawX = Math.max(margin, width - targetSize - margin);
434
+ const drawY = margin;
355
435
 
356
436
  firstPage.drawImage(pngImage, {
357
- x: Math.max(margin, width - targetSize - margin),
358
- y: margin,
437
+ x: drawX,
438
+ y: drawY,
359
439
  width: targetSize,
360
440
  height: targetSize,
361
441
  });
362
442
 
363
- const modifiedPdfBytes = await pdfDoc.save();
443
+ // Attach clickable PDF Link Annotation over the Sun-Stamp rectangle
444
+ if (verifyUrl) {
445
+ try {
446
+ const linkAnnotation = pdfDoc.context.obj({
447
+ Type: "Annot",
448
+ Subtype: "Link",
449
+ Rect: [drawX, drawY, drawX + targetSize, drawY + targetSize],
450
+ Border: [0, 0, 0],
451
+ F: 4,
452
+ A: {
453
+ Type: "Action",
454
+ S: "URI",
455
+ URI: PDFString.of(verifyUrl),
456
+ },
457
+ });
458
+ const linkRef = pdfDoc.context.register(linkAnnotation);
459
+ if (typeof firstPage.node.addAnnot === "function") {
460
+ firstPage.node.addAnnot(linkRef);
461
+ } else {
462
+ firstPage.node.set(PDFName.of("Annots"), pdfDoc.context.obj([linkRef]));
463
+ }
464
+ } catch (e) {
465
+ // Ignore annotation error on restricted PDFs
466
+ }
467
+ }
468
+
469
+ const modifiedPdfBytes = await pdfDoc.save({ useObjectStreams: false });
364
470
  fs.writeFileSync(outPath, Buffer.from(modifiedPdfBytes));
365
471
 
366
472
  return {
@@ -518,7 +624,7 @@ const TOOLS = [
518
624
  {
519
625
  name: "reltime_account_status",
520
626
  description:
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).",
627
+ "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 4 available Rel-Time Tiers (`universal` $3, `launch` $5/50, `standard` $50/500, `enterprise` $600/5,000) and payment options (`lightning` or `stripe`) so you can ask the user which tier they want to provision in chat.",
522
628
  inputSchema: {
523
629
  type: "object",
524
630
  properties: {
@@ -533,7 +639,7 @@ const TOOLS = [
533
639
  {
534
640
  name: "reltime_provision_agent_key",
535
641
  description:
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!",
642
+ "Self-onboarding & Top-Up for all 4 Rel-Time Tiers via EITHER Bitcoin Lightning OR Stripe Card Checkout (`POST /v1/agent/invoice` & `POST /v1/agent/claim`). IMPORTANT:\n1. Before calling `create_invoice` for a new key, ask the user which tier AND payment method (`lightning` or `stripe`) they want:\n - **Universal Anchor (`pack: 'universal'`, $3 / anchor):** Priority 10-Minute Bitcoin Queue · Standard Mode (Biometric or Anonymous) · Supports `quantity` (default 1).\n - **Agent Launch Pack (`pack: 'launch'`, $5 / 50 Hashes):** Priority 10-Minute Bitcoin Queue · Standard Mode · 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.\n - **Rel-Time Superhighway (`pack: 'enterprise'`, $600 / 5,000 Hashes):** Priority 10-Minute Bitcoin Queue · Supports Temporal Mode (`T_index`) & Standard Mode + Bulk Folder.\n2. When `create_invoice` returns:\n - For **Lightning**: ALWAYS present BOTH the scannable QR code image (by calling Read on `qr_code_png_path`) AND the full, un-truncated `ln_invoice` (`bolt11`) string in a copyable code block on the VERY FIRST try!\n - For **Stripe**: The Stripe Checkout page opens automatically in the user's browser; also present the clickable `checkout_url` link and QR code (`qr_code_png_path`). Once paid, call `claim_invoice` with `invoice_id` (`cs_...`) to activate and save the key.",
537
643
  inputSchema: {
538
644
  type: "object",
539
645
  properties: {
@@ -541,27 +647,39 @@ const TOOLS = [
541
647
  type: "string",
542
648
  enum: ["create_invoice", "claim_invoice"],
543
649
  description:
544
- "'create_invoice' generates a BOLT11 Lightning invoice; 'claim_invoice' exchanges a paid invoice_id for a new API key or reloads an existing API key.",
650
+ "'create_invoice' generates a Lightning BOLT11 invoice or Stripe Checkout Session; 'claim_invoice' exchanges a paid Lightning invoice_id or Stripe session_id ('cs_...') for a new API key or reloads an existing API key.",
545
651
  },
546
652
  pack: {
547
653
  type: "string",
548
- enum: ["launch", "standard"],
654
+ enum: ["universal", "launch", "standard", "enterprise"],
655
+ description:
656
+ "Which tier the user selected: 'universal' ($3/anchor), 'launch' ($5 / 50 Hashes), 'standard' ($50 / 500 Hashes), or 'enterprise' ($600 / 5,000 Hashes — Superhighway). If omitted on 'create_invoice', returns the 4 tier options so you can ask the user.",
657
+ },
658
+ payment_method: {
659
+ type: "string",
660
+ enum: ["lightning", "stripe"],
549
661
  description:
550
- "Which key pack the user selected: 'launch' (Agent Launch Pack — $5 for 50 Hash Credits, 10-Min Queue, Standard Mode only) or 'standard' (Autonomous Agents — $50 for 500 Hash Credits, 6-Hr Queue, Temporal & Standard Modes, reloadable). If omitted on 'create_invoice', the tool returns the two options so you can ask the user which pack they prefer.",
662
+ "Payment method: 'lightning' (Bitcoin Lightning BOLT11 invoice + QR code) or 'stripe' (Stripe Card Checkout URL + browser launch). Defaults to 'lightning'.",
663
+ },
664
+ quantity: {
665
+ type: "number",
666
+ description:
667
+ "Optional credit quantity when pack is 'universal' ($3.00 per credit, default 1).",
551
668
  },
552
669
  invoice_id: {
553
670
  type: "string",
554
- description: "Required when action is 'claim_invoice'.",
671
+ description:
672
+ "Required when action is 'claim_invoice'. Pass either the Strike Lightning `invoice_id` UUID or the Stripe Checkout `session_id` (`cs_...`).",
555
673
  },
556
674
  api_key: {
557
675
  type: "string",
558
676
  description:
559
- "Optional when action is 'claim_invoice': pass an existing `rel_key_agent_...` key (or omit to use RELTIME_API_KEY if reload_existing is true) to reload Hash Credits into that key without rotating it.",
677
+ "Optional: pass an existing `rel_key_...` key (or omit to use RELTIME_API_KEY if reload_existing is true) to reload Hash Credits into that key without rotating it.",
560
678
  },
561
679
  reload_existing: {
562
680
  type: "boolean",
563
681
  description:
564
- "If true during 'claim_invoice', reloads Hash Credits into the currently configured RELTIME_API_KEY (or passed `api_key`) instead of generating a new key.",
682
+ "If true, reloads Hash Credits into the currently configured RELTIME_API_KEY (or passed `api_key`) instead of generating a new key.",
565
683
  },
566
684
  },
567
685
  required: ["action"],
@@ -701,7 +819,7 @@ async function executeTool(name, args = {}) {
701
819
  // 3. Automatically generate Stamped Copy (.PDF or .PNG) for PDFs and Images
702
820
  let stampedCopyResult = null;
703
821
  if (args.save_stamped_copy !== false) {
704
- stampedCopyResult = await stampLocalFileOptionA(filePath, svgContent);
822
+ stampedCopyResult = await stampLocalFileOptionA(filePath, svgContent, null, clientHash);
705
823
  }
706
824
 
707
825
  return {
@@ -743,7 +861,7 @@ async function executeTool(name, args = {}) {
743
861
  throw new Error(`Failed to fetch Sun-Stamp (${stampRes.status}): ${await stampRes.text()}`);
744
862
  }
745
863
  const svgContent = await stampRes.text();
746
- const result = await stampLocalFileOptionA(filePath, svgContent, args.output_path || null);
864
+ const result = await stampLocalFileOptionA(filePath, svgContent, args.output_path || null, targetHash);
747
865
  return {
748
866
  file_path: filePath,
749
867
  hash: targetHash,
@@ -868,23 +986,80 @@ async function executeTool(name, args = {}) {
868
986
 
869
987
  if (name === "reltime_provision_agent_key") {
870
988
  if (args.action === "create_invoice") {
871
- if (args.pack !== "launch" && args.pack !== "standard") {
989
+ const validPacks = ["universal", "launch", "standard", "enterprise", "superhighway"];
990
+ const normalizedPack = (args.pack || "").trim().toLowerCase();
991
+ if (!validPacks.includes(normalizedPack)) {
872
992
  return buildPackSelectionPrompt(
873
- "Please ask the user which Rel-Time Agent API Key pack they would like to provision before generating the Lightning invoice:"
993
+ "Please ask the user which Rel-Time Tier (`universal` $3, `launch` $5/50, `standard` $50/500, or `enterprise` $600/5,000) and payment method (`lightning` or `stripe`) they would like to provision:"
874
994
  );
875
995
  }
876
996
 
877
- LAST_INVOICE_PACK = args.pack;
997
+ const finalPack = normalizedPack === "superhighway" ? "enterprise" : normalizedPack;
998
+ const paymentMethod = (args.payment_method || "lightning").trim().toLowerCase() === "stripe" ? "stripe" : "lightning";
999
+ const targetReloadKey = args.api_key || (args.reload_existing ? loadSavedApiKey() : "");
1000
+
1001
+ LAST_INVOICE_PACK = finalPack;
1002
+ const reqBody = {
1003
+ accept_terms: true,
1004
+ pack: finalPack,
1005
+ payment_method: paymentMethod,
1006
+ };
1007
+ if (args.quantity && Number(args.quantity) > 0) {
1008
+ reqBody.quantity = Math.max(1, Math.round(Number(args.quantity)));
1009
+ }
1010
+ if (targetReloadKey && targetReloadKey.trim()) {
1011
+ reqBody.api_key = targetReloadKey.trim();
1012
+ }
1013
+
878
1014
  const res = await fetch(`${API_BASE}/agent/invoice`, {
879
1015
  method: "POST",
880
1016
  headers: { "Content-Type": "application/json" },
881
- body: JSON.stringify({
882
- accept_terms: true,
883
- pack: args.pack,
884
- }),
1017
+ body: JSON.stringify(reqBody),
885
1018
  });
886
1019
  const data = await res.json();
1020
+ if (!res.ok) {
1021
+ throw new Error(`Rel-Time checkout error (${res.status}): ${JSON.stringify(data)}`);
1022
+ }
1023
+
1024
+ const checkoutUrl = data.checkout_url || data.url || "";
887
1025
  const bolt11 = data.ln_invoice || data.bolt11 || "";
1026
+
1027
+ if (paymentMethod === "stripe" && checkoutUrl) {
1028
+ try {
1029
+ openSystemBrowser(checkoutUrl);
1030
+ if (!fs.existsSync(PASSKEY_CONFIG_DIR)) {
1031
+ fs.mkdirSync(PASSKEY_CONFIG_DIR, { recursive: true, mode: 0o700 });
1032
+ }
1033
+ const qrPngBuffer = await QRCode.toBuffer(checkoutUrl, {
1034
+ type: "png",
1035
+ width: 560,
1036
+ margin: 2,
1037
+ errorCorrectionLevel: "M",
1038
+ });
1039
+ const homeQrPath = path.join(PASSKEY_CONFIG_DIR, "stripe-checkout-qr.png");
1040
+ fs.writeFileSync(homeQrPath, qrPngBuffer);
1041
+ data.qr_code_png_path = homeQrPath;
1042
+ data._mcp_image_base64 = qrPngBuffer.toString("base64");
1043
+ } catch (qrErr) {
1044
+ data.qr_error = qrErr.message;
1045
+ }
1046
+ if (data.api_key && isValidApiKeyCandidate(data.api_key)) {
1047
+ // Pre-save the pending Stripe key so as soon as payment finishes it is ready
1048
+ CURRENT_API_KEY = data.api_key.trim();
1049
+ saveApiKeyConfig(CURRENT_API_KEY, finalPack);
1050
+ data.saved_to_config = API_KEY_CONFIG_PATH;
1051
+ }
1052
+ data.mandatory_first_reply_instructions =
1053
+ "CRITICAL INSTRUCTION FOR AI AGENT: The Stripe Checkout URL has been opened in the user's browser AND the pending API key has been saved to ~/.reltime/api_key.json. Show the user the clickable `checkout_url` link (`" +
1054
+ checkoutUrl +
1055
+ "`), the `invoice_id` (`" +
1056
+ (data.invoice_id || data.session_id) +
1057
+ "`), and tell them that once they finish paying on Stripe, you will call `reltime_provision_agent_key({ action: 'claim_invoice', invoice_id: '" +
1058
+ (data.invoice_id || data.session_id) +
1059
+ "' })` to activate the credits immediately!";
1060
+ return data;
1061
+ }
1062
+
888
1063
  if (bolt11) {
889
1064
  try {
890
1065
  if (!fs.existsSync(PASSKEY_CONFIG_DIR)) {
@@ -922,7 +1097,7 @@ async function executeTool(name, args = {}) {
922
1097
  const resolvedPack = args.pack || LAST_INVOICE_PACK || undefined;
923
1098
  const claimPayload = {
924
1099
  invoice_id: args.invoice_id,
925
- pack: resolvedPack === "launch" ? "launch" : undefined,
1100
+ pack: resolvedPack,
926
1101
  };
927
1102
  const targetReloadKey = args.api_key || (args.reload_existing ? loadSavedApiKey() : "");
928
1103
  if (targetReloadKey && targetReloadKey.trim()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rel-time/mcp-server",
3
- "version": "1.2.4",
3
+ "version": "1.2.6",
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": {