@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.
- package/index.js +216 -41
- 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.
|
|
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
|
|
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
|
|
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: "
|
|
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: "
|
|
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
|
|
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
|
-
*
|
|
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(
|
|
312
|
-
const margin = Math.max(
|
|
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(
|
|
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
|
|
396
|
+
const rawOutBuf = await sharp(filePath)
|
|
397
|
+
.rotate()
|
|
323
398
|
.composite([{ input: stampPngBuffer, left, top }])
|
|
324
399
|
.png()
|
|
325
|
-
.
|
|
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
|
|
347
|
-
const stampPngBuffer = await sharp(Buffer.from(
|
|
348
|
-
.resize(
|
|
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(
|
|
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:
|
|
358
|
-
y:
|
|
437
|
+
x: drawX,
|
|
438
|
+
y: drawY,
|
|
359
439
|
width: targetSize,
|
|
360
440
|
height: targetSize,
|
|
361
441
|
});
|
|
362
442
|
|
|
363
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
"
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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.
|
|
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": {
|