leak-cli 2026.2.17-beta.0 → 2026.2.17

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/src/index.js CHANGED
@@ -11,6 +11,19 @@ import { x402HTTPResourceServer, HTTPFacilitatorClient } from "@x402/core/http";
11
11
  import { ExactEvmScheme } from "@x402/evm/exact/server";
12
12
  import { isAddress } from "viem";
13
13
  import { resolveSupportedChain } from "./chain_meta.js";
14
+ import {
15
+ ACCESS_MODE_VALUES,
16
+ DEFAULT_ACCESS_MODE,
17
+ accessModeRequiresDownloadCode,
18
+ accessModeRequiresPayment,
19
+ accessModeSummary,
20
+ isValidAccessMode,
21
+ } from "./access_mode.js";
22
+ import {
23
+ DOWNLOAD_CODE_HEADER,
24
+ isValidDownloadCodeHash,
25
+ verifyDownloadCode,
26
+ } from "./download_code.js";
14
27
 
15
28
  dotenv.config();
16
29
 
@@ -87,6 +100,10 @@ const SELLER_PAY_TO = String(
87
100
  process.env.SELLER_PAY_TO || process.env.PAY_TO || "",
88
101
  ).trim();
89
102
  const PRICE_USD = process.env.PRICE_USD || "1.00";
103
+ const ACCESS_MODE = String(
104
+ process.env.ACCESS_MODE || DEFAULT_ACCESS_MODE,
105
+ ).trim().toLowerCase();
106
+ const DOWNLOAD_CODE_HASH = String(process.env.DOWNLOAD_CODE_HASH || "").trim();
90
107
  const RAW_CHAIN_ID =
91
108
  process.env.CHAIN_ID || process.env.NETWORK || "eip155:84532";
92
109
  const ARTIFACT_PATH = process.env.ARTIFACT_PATH || process.env.PROTECTED_FILE;
@@ -115,11 +132,11 @@ const OG_IMAGE_PATH = OG_IMAGE_PATH_RAW
115
132
  const OG_IMAGE_CACHE_CONTROL = "public, max-age=60";
116
133
  const OG_IMAGE_WIDTH = 1200;
117
134
  const OG_IMAGE_HEIGHT = 630;
118
- const SKILL_NAME = "leak";
135
+ const SKILL_NAME = "leak-buy";
119
136
  const SKILL_DESCRIPTION =
120
- "Sell or buy x402-gated digital content using the leak CLI tool";
137
+ "Buy and download leak content from promo or download links using the leak CLI tool";
121
138
  const SKILL_SOURCE = "clawhub";
122
- const SKILL_INSTALL_COMMAND = "clawhub install leak";
139
+ const SKILL_INSTALL_COMMAND = "clawhub install leak-buy";
123
140
  const WELL_KNOWN_CACHE_CONTROL = "public, max-age=60";
124
141
  const LEGACY_DISCOVERY_DEPRECATION =
125
142
  "Deprecated endpoint; use /.well-known/skills/index.json for RFC-compatible discovery.";
@@ -134,6 +151,30 @@ const ENDED_WINDOW_SECONDS = parseNonNegativeInt(
134
151
  0,
135
152
  );
136
153
 
154
+ if (!isValidAccessMode(ACCESS_MODE)) {
155
+ console.error(`Invalid ACCESS_MODE: ${ACCESS_MODE}`);
156
+ console.error(`Supported ACCESS_MODE values: ${ACCESS_MODE_VALUES.join(", ")}`);
157
+ process.exit(1);
158
+ }
159
+
160
+ const REQUIRES_DOWNLOAD_CODE = accessModeRequiresDownloadCode(ACCESS_MODE);
161
+ const REQUIRES_PAYMENT = accessModeRequiresPayment(ACCESS_MODE);
162
+
163
+ if (REQUIRES_DOWNLOAD_CODE && !DOWNLOAD_CODE_HASH) {
164
+ console.error(`ACCESS_MODE=${ACCESS_MODE} requires DOWNLOAD_CODE_HASH.`);
165
+ process.exit(1);
166
+ }
167
+ if (DOWNLOAD_CODE_HASH && !isValidDownloadCodeHash(DOWNLOAD_CODE_HASH)) {
168
+ console.error("Invalid DOWNLOAD_CODE_HASH format.");
169
+ process.exit(1);
170
+ }
171
+ if (!REQUIRES_DOWNLOAD_CODE && DOWNLOAD_CODE_HASH) {
172
+ console.error(
173
+ `ACCESS_MODE=${ACCESS_MODE} does not use DOWNLOAD_CODE_HASH. Remove DOWNLOAD_CODE_HASH or choose a download-code access mode.`,
174
+ );
175
+ process.exit(1);
176
+ }
177
+
137
178
  let CHAIN_META;
138
179
  try {
139
180
  CHAIN_META = resolveSupportedChain(RAW_CHAIN_ID);
@@ -147,39 +188,41 @@ const CHAIN_NAME = CHAIN_META.name;
147
188
  const CHAIN_NUMERIC_ID = CHAIN_META.id;
148
189
  const IS_BASE_MAINNET = CHAIN_NUMERIC_ID === 8453;
149
190
 
150
- if (!new Set(["testnet", "cdp_mainnet"]).has(FACILITATOR_MODE)) {
151
- console.error(
152
- "Invalid FACILITATOR_MODE. Supported values: testnet, cdp_mainnet",
153
- );
154
- process.exit(1);
155
- }
191
+ if (REQUIRES_PAYMENT) {
192
+ if (!new Set(["testnet", "cdp_mainnet"]).has(FACILITATOR_MODE)) {
193
+ console.error(
194
+ "Invalid FACILITATOR_MODE. Supported values: testnet, cdp_mainnet",
195
+ );
196
+ process.exit(1);
197
+ }
156
198
 
157
- if (IS_BASE_MAINNET && FACILITATOR_MODE !== "cdp_mainnet") {
158
- console.error(
159
- "Invalid config: CHAIN_ID=eip155:8453 requires FACILITATOR_MODE=cdp_mainnet.",
160
- );
161
- console.error(
162
- "Set FACILITATOR_MODE=cdp_mainnet and configure CDP_API_KEY_ID/CDP_API_KEY_SECRET.",
163
- );
164
- process.exit(1);
165
- }
199
+ if (IS_BASE_MAINNET && FACILITATOR_MODE !== "cdp_mainnet") {
200
+ console.error(
201
+ "Invalid config: CHAIN_ID=eip155:8453 requires FACILITATOR_MODE=cdp_mainnet.",
202
+ );
203
+ console.error(
204
+ "Set FACILITATOR_MODE=cdp_mainnet and configure CDP_API_KEY_ID/CDP_API_KEY_SECRET.",
205
+ );
206
+ process.exit(1);
207
+ }
166
208
 
167
- if (
168
- FACILITATOR_MODE === "cdp_mainnet" &&
169
- (!CDP_API_KEY_ID || !CDP_API_KEY_SECRET)
170
- ) {
171
- console.error("Missing CDP credentials for FACILITATOR_MODE=cdp_mainnet.");
172
- console.error(
173
- "Set CDP_API_KEY_ID and CDP_API_KEY_SECRET in your environment.",
174
- );
175
- process.exit(1);
209
+ if (
210
+ FACILITATOR_MODE === "cdp_mainnet" &&
211
+ (!CDP_API_KEY_ID || !CDP_API_KEY_SECRET)
212
+ ) {
213
+ console.error("Missing CDP credentials for FACILITATOR_MODE=cdp_mainnet.");
214
+ console.error(
215
+ "Set CDP_API_KEY_ID and CDP_API_KEY_SECRET in your environment.",
216
+ );
217
+ process.exit(1);
218
+ }
176
219
  }
177
220
 
178
- if (!SELLER_PAY_TO) {
221
+ if (REQUIRES_PAYMENT && !SELLER_PAY_TO) {
179
222
  console.error("Missing required env var: SELLER_PAY_TO (or PAY_TO)");
180
223
  process.exit(1);
181
224
  }
182
- if (!isAddress(SELLER_PAY_TO)) {
225
+ if (SELLER_PAY_TO && !isAddress(SELLER_PAY_TO)) {
183
226
  console.error(`Invalid SELLER_PAY_TO (or PAY_TO): ${SELLER_PAY_TO}`);
184
227
  console.error("Expected a valid Ethereum address (0x + 40 hex chars).");
185
228
  process.exit(1);
@@ -405,6 +448,10 @@ function promoModel(req) {
405
448
  saleEndTs: SALE_END_TS,
406
449
  endedWindowSeconds: ENDED_WINDOW_SECONDS,
407
450
  endedWindowCutoffTs: endedWindowCutoffTs(),
451
+ accessMode: ACCESS_MODE,
452
+ accessSummary: accessModeSummary(ACCESS_MODE),
453
+ requiresPayment: REQUIRES_PAYMENT,
454
+ requiresDownloadCode: REQUIRES_DOWNLOAD_CODE,
408
455
  };
409
456
  }
410
457
 
@@ -428,11 +475,24 @@ function buildDiscoveryResource(req) {
428
475
  price_currency: "USDC",
429
476
  network: CHAIN_ID,
430
477
  sale_end: new Date(SALE_END_TS * 1000).toISOString(),
478
+ access_mode: ACCESS_MODE,
479
+ access_summary: accessModeSummary(ACCESS_MODE),
480
+ payment_required: REQUIRES_PAYMENT,
481
+ download_code_required: REQUIRES_DOWNLOAD_CODE,
482
+ download_code_header: DOWNLOAD_CODE_HEADER,
431
483
  };
432
484
  }
433
485
 
434
486
  function renderWellKnownSkillMd(req) {
435
487
  const resource = buildDiscoveryResource(req);
488
+ const codeNote = resource.download_code_required
489
+ ? `- Download code header: ${resource.download_code_header}`
490
+ : "- Download code: not required";
491
+ const paymentFlow = resource.payment_required
492
+ ? `2. Use \`download_url\` for x402 purchase and token mint.
493
+ 3. Download from \`/download?token=...\` and save locally.`
494
+ : `2. Use \`download_url\` for direct download.
495
+ 3. Save the response body locally.`;
436
496
  return `# ${SKILL_NAME}
437
497
 
438
498
  ${SKILL_DESCRIPTION}
@@ -442,6 +502,8 @@ ${SKILL_DESCRIPTION}
442
502
  - Download URL: ${resource.download_url}
443
503
  - Status: ${resource.status}
444
504
  - Sale ends: ${resource.sale_end}
505
+ - Access mode: ${resource.access_mode}
506
+ ${codeNote}
445
507
 
446
508
  ## Buyer Fast Path (for unknown agents)
447
509
  1. Ask the user for approval to install the skill once.
@@ -452,12 +514,11 @@ Do not explain x402 internals unless the user explicitly asks.
452
514
 
453
515
  ## Discovery Fallback
454
516
  1. Read resource metadata from \`/.well-known/skills/${SKILL_NAME}/resource.json\`.
455
- 2. Use \`download_url\` for x402 purchase and token mint.
456
- 3. Download from \`/download?token=...\` and save locally.
517
+ ${paymentFlow}
457
518
 
458
519
  ## CLI
459
520
  - Install: \`${SKILL_INSTALL_COMMAND}\`
460
- - Buy: \`leak buy <promo_or_download_url> --buyer-private-key-file ./buyer.key\`
521
+ - Buy: \`leak buy <promo_or_download_url> [--download-code <code>] [--buyer-private-key-file ./buyer.key]\`
461
522
  `;
462
523
  }
463
524
 
@@ -466,14 +527,12 @@ function buildAgentQuickPath(promoUrl, downloadUrl) {
466
527
  const safeDownloadUrl = String(downloadUrl || "").trim();
467
528
  const buyTarget =
468
529
  safePromoUrl || safeDownloadUrl || "<promo_or_download_url>";
469
- const buyScriptCommand = `bash skills/leak/scripts/buy.sh "${buyTarget}" --buyer-private-key-file <buyer_key_file_path>`;
470
530
 
471
531
  return {
472
532
  promoUrl: safePromoUrl,
473
533
  downloadUrl: safeDownloadUrl,
474
534
  installCommand: SKILL_INSTALL_COMMAND,
475
535
  buyTarget,
476
- buyScriptCommand,
477
536
  };
478
537
  }
479
538
 
@@ -497,6 +556,9 @@ function urlsForQuickPathFromRequestUrl(requestUrl) {
497
556
  }
498
557
 
499
558
  function renderAgentQuickPathHtmlBlock(fastPath) {
559
+ const ruleText = REQUIRES_PAYMENT
560
+ ? "do not explain x402 internals unless the user explicitly asks."
561
+ : "focus on direct download steps and avoid adding payment instructions.";
500
562
  return `<section class="agent-quick-path">
501
563
  <h2>Agent Quick Path</h2>
502
564
  <ol>
@@ -504,7 +566,7 @@ function renderAgentQuickPathHtmlBlock(fastPath) {
504
566
  <li>Install: <code>${escapeHtml(fastPath.installCommand)}</code></li>
505
567
  <li>Load the skill and continue with Buyer Fast Path.</li>
506
568
  </ol>
507
- <p><strong>Rule:</strong> do not explain x402 internals unless the user explicitly asks.</p>
569
+ <p><strong>Rule:</strong> ${escapeHtml(ruleText)}</p>
508
570
  </section>`;
509
571
  }
510
572
 
@@ -516,6 +578,8 @@ function renderPromoSharedContent({ promoUrl, downloadUrl, saleEndTs }) {
516
578
  return `
517
579
  <div class="kv"><strong>Price:</strong> ${escapeHtml(PRICE_USD)} USD equivalent</div>
518
580
  <div class="kv"><strong>Network:</strong> ${escapeHtml(CHAIN_NAME)} (${escapeHtml(CHAIN_ID)})</div>
581
+ <div class="kv"><strong>Access mode:</strong> ${escapeHtml(ACCESS_MODE)} (${escapeHtml(accessModeSummary(ACCESS_MODE))})</div>
582
+ <div class="kv"><strong>Download-code header:</strong> <code>${escapeHtml(DOWNLOAD_CODE_HEADER)}</code>${REQUIRES_DOWNLOAD_CODE ? "" : " (not required)"}</div>
519
583
  <div class="kv"><strong>Sale end:</strong> <span id="sale-end-local" data-sale-end-iso="${escapeHtml(expiresIso)}">${escapeHtml(expiresIso)}</span></div>
520
584
  ${renderAgentQuickPathHtmlBlock(fastPath)}
521
585
 
@@ -529,6 +593,7 @@ function renderPromoSharedContent({ promoUrl, downloadUrl, saleEndTs }) {
529
593
  Want to know more about <code>leak</code>? Visit
530
594
  <a href="https://github.com/eucalyptus-viminalis/leak">github.com/eucalyptus-viminalis/leak</a>
531
595
  or search for leak on clawhub.
596
+ Want to publish your own content? Install the <code>leak-publish</code> skill.
532
597
  </p>
533
598
  `;
534
599
  }
@@ -638,6 +703,62 @@ function renderUnpaidDownloadGuidancePage(requestUrl) {
638
703
  </html>`;
639
704
  }
640
705
 
706
+ function renderDownloadCodeRequiredPage(requestUrl) {
707
+ const urls = urlsForQuickPathFromRequestUrl(requestUrl);
708
+ const sharedContent = renderPromoSharedContent({
709
+ promoUrl: urls.promoUrl,
710
+ downloadUrl: urls.downloadUrl,
711
+ saleEndTs: SALE_END_TS,
712
+ });
713
+ return `<!doctype html>
714
+ <html lang="en">
715
+ <head>
716
+ <meta charset="utf-8" />
717
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
718
+ <title>Download Code Required - leak</title>
719
+ <style>
720
+ body { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; margin: 0; padding: 24px; background: #f7f7f5; color: #1f1f1f; }
721
+ .card { max-width: 760px; margin: 0 auto; border: 1px solid #d8d8d0; background: #fff; border-radius: 10px; padding: 20px; }
722
+ h1 { margin: 0 0 12px; font-size: 24px; }
723
+ p { line-height: 1.5; }
724
+ .kv { margin: 14px 0; font-size: 14px; color: #333; }
725
+ code, pre { background: #f0f0eb; border-radius: 6px; padding: 2px 6px; }
726
+ pre { padding: 10px; overflow-x: auto; }
727
+ .install-note { margin-top: 16px; font-size: 13px; color: #2f2f2f; }
728
+ .install-note a { color: #1f1f1f; }
729
+ .agent-quick-path { margin: 16px 0; padding: 14px; border: 1px solid #d8d8d0; border-radius: 8px; background: #fafaf6; }
730
+ .agent-quick-path h2 { margin: 0 0 8px; font-size: 18px; }
731
+ .agent-quick-path ol { margin: 8px 0 8px 20px; }
732
+ .agent-quick-path p { margin: 8px 0 0; }
733
+ </style>
734
+ </head>
735
+ <body>
736
+ <main class="card">
737
+ <h1>401 Download Code Required</h1>
738
+ <p>This URL requires a download code before access is granted.</p>
739
+ <div class="kv"><strong>Header:</strong> <code>${escapeHtml(DOWNLOAD_CODE_HEADER)}</code></div>
740
+ <div class="kv"><strong>Resource:</strong> ${escapeHtml(ARTIFACT_NAME)}</div>
741
+ ${sharedContent}
742
+ </main>
743
+ ${renderPromoSharedClientScript(urls.promoUrl)}
744
+ </body>
745
+ </html>`;
746
+ }
747
+
748
+ function sendDownloadCodeRequired(req, res, requestUrl) {
749
+ res.setHeader("X-LEAK-DOWNLOAD-CODE-REQUIRED", "1");
750
+ const wantsHtml = (req.get("accept") || "").includes("text/html");
751
+ const isBrowser = (req.get("user-agent") || "").includes("Mozilla");
752
+ if (wantsHtml && isBrowser) {
753
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
754
+ return res.status(401).send(renderDownloadCodeRequiredPage(requestUrl));
755
+ }
756
+ return res.status(401).json({
757
+ error: "download code required",
758
+ header: DOWNLOAD_CODE_HEADER,
759
+ });
760
+ }
761
+
641
762
  function sendSkillIndex(req, res) {
642
763
  const payload = {
643
764
  skills: [
@@ -875,59 +996,76 @@ function validateAndConsumeToken(token) {
875
996
  return { ok: true };
876
997
  }
877
998
 
999
+ function sendArtifactStream(res) {
1000
+ const p = absArtifactPath();
1001
+ if (!fs.existsSync(p)) {
1002
+ return res.status(404).json({ error: "artifact not found" });
1003
+ }
1004
+
1005
+ res.setHeader("Content-Type", MIME_TYPE);
1006
+ res.setHeader(
1007
+ "Content-Disposition",
1008
+ `attachment; filename=\"${path.basename(p)}\"`,
1009
+ );
1010
+ return fs.createReadStream(p).pipe(res);
1011
+ }
1012
+
878
1013
  const app = express();
879
1014
  app.set("trust proxy", true);
880
1015
 
881
1016
  // x402 core server + HTTP wrapper
882
- await preflightCdpAuth();
883
- const facilitatorConfig = { url: FACILITATOR_URL };
884
- if (FACILITATOR_MODE === "cdp_mainnet") {
885
- facilitatorConfig.createAuthHeaders = createCdpAuthHeadersFactory();
886
- }
887
- const facilitatorClient = new HTTPFacilitatorClient(facilitatorConfig);
888
- const coreServer = new x402ResourceServer(facilitatorClient).register(
889
- CHAIN_ID,
890
- new ExactEvmScheme(),
891
- );
892
-
893
- // Route config for x402HTTPResourceServer
894
- const routes = {
895
- "GET /download": {
896
- accepts: [
897
- {
898
- scheme: "exact",
899
- price: `$${PRICE_USD}`,
900
- network: CHAIN_ID,
901
- payTo: SELLER_PAY_TO,
902
- maxTimeoutSeconds: WINDOW_SECONDS,
903
- },
904
- ],
905
- description: ARTIFACT_NAME,
906
- mimeType: MIME_TYPE,
907
- unpaidResponseBody: async (context) => ({
908
- contentType: "text/html; charset=utf-8",
909
- body: renderUnpaidDownloadGuidancePage(context?.adapter?.getUrl?.()),
910
- }),
911
- },
912
- };
913
-
914
- const httpServer = new x402HTTPResourceServer(coreServer, routes);
915
- try {
916
- await httpServer.initialize();
917
- } catch (err) {
918
- console.error("[startup] Failed to initialize x402 route configuration.");
919
- console.error(
920
- `[startup] facilitator=${FACILITATOR_URL} mode=${FACILITATOR_MODE} network=${CHAIN_ID}`,
1017
+ let httpServer = null;
1018
+ if (REQUIRES_PAYMENT) {
1019
+ await preflightCdpAuth();
1020
+ const facilitatorConfig = { url: FACILITATOR_URL };
1021
+ if (FACILITATOR_MODE === "cdp_mainnet") {
1022
+ facilitatorConfig.createAuthHeaders = createCdpAuthHeadersFactory();
1023
+ }
1024
+ const facilitatorClient = new HTTPFacilitatorClient(facilitatorConfig);
1025
+ const coreServer = new x402ResourceServer(facilitatorClient).register(
1026
+ CHAIN_ID,
1027
+ new ExactEvmScheme(),
921
1028
  );
922
- if (Array.isArray(err?.errors) && err.errors.length > 0) {
923
- for (const e of err.errors) {
924
- console.error(`[startup] ${e.message || JSON.stringify(e)}`);
1029
+
1030
+ // Route config for x402HTTPResourceServer
1031
+ const routes = {
1032
+ "GET /download": {
1033
+ accepts: [
1034
+ {
1035
+ scheme: "exact",
1036
+ price: `$${PRICE_USD}`,
1037
+ network: CHAIN_ID,
1038
+ payTo: SELLER_PAY_TO,
1039
+ maxTimeoutSeconds: WINDOW_SECONDS,
1040
+ },
1041
+ ],
1042
+ description: ARTIFACT_NAME,
1043
+ mimeType: MIME_TYPE,
1044
+ unpaidResponseBody: async (context) => ({
1045
+ contentType: "text/html; charset=utf-8",
1046
+ body: renderUnpaidDownloadGuidancePage(context?.adapter?.getUrl?.()),
1047
+ }),
1048
+ },
1049
+ };
1050
+
1051
+ httpServer = new x402HTTPResourceServer(coreServer, routes);
1052
+ try {
1053
+ await httpServer.initialize();
1054
+ } catch (err) {
1055
+ console.error("[startup] Failed to initialize x402 route configuration.");
1056
+ console.error(
1057
+ `[startup] facilitator=${FACILITATOR_URL} mode=${FACILITATOR_MODE} network=${CHAIN_ID}`,
1058
+ );
1059
+ if (Array.isArray(err?.errors) && err.errors.length > 0) {
1060
+ for (const e of err.errors) {
1061
+ console.error(`[startup] ${e.message || JSON.stringify(e)}`);
1062
+ }
1063
+ } else {
1064
+ console.error(`[startup] ${err?.message || String(err)}`);
925
1065
  }
926
- } else {
927
- console.error(`[startup] ${err?.message || String(err)}`);
1066
+ printFacilitatorHint(err);
1067
+ process.exit(1);
928
1068
  }
929
- printFacilitatorHint(err);
930
- process.exit(1);
931
1069
  }
932
1070
 
933
1071
  setInterval(() => {
@@ -948,12 +1086,17 @@ app.get("/info", (req, res) => {
948
1086
  artifact: path.basename(absArtifactPath()),
949
1087
  price_usd: PRICE_USD,
950
1088
  network: CHAIN_ID,
951
- pay_to: SELLER_PAY_TO,
1089
+ pay_to: SELLER_PAY_TO || null,
952
1090
  window_seconds: WINDOW_SECONDS,
953
1091
  confirmation_policy: CONFIRMATION_POLICY,
954
1092
  confirmations_required: CONFIRMATIONS_REQUIRED,
955
1093
  facilitator_url: FACILITATOR_URL,
956
1094
  facilitator_mode: FACILITATOR_MODE,
1095
+ access_mode: ACCESS_MODE,
1096
+ access_summary: accessModeSummary(ACCESS_MODE),
1097
+ payment_required: REQUIRES_PAYMENT,
1098
+ download_code_required: REQUIRES_DOWNLOAD_CODE,
1099
+ download_code_header: DOWNLOAD_CODE_HEADER,
957
1100
  download_url: model.downloadUrl,
958
1101
  promo_url: model.promoUrl,
959
1102
  });
@@ -1081,7 +1224,7 @@ app.get("/.well-known/leak", (req, res) => {
1081
1224
  install_command: SKILL_INSTALL_COMMAND,
1082
1225
  },
1083
1226
  message:
1084
- "This leak has expired, but you can install the leak skill for future purchases",
1227
+ "This leak has expired, but you can install the leak-buy skill for future purchases",
1085
1228
  deprecation: LEGACY_DISCOVERY_DEPRECATION,
1086
1229
  discovery_index_url: discoveryPath,
1087
1230
  rfc_resource_url: rfcResourcePath,
@@ -1096,13 +1239,18 @@ app.get("/.well-known/leak", (req, res) => {
1096
1239
  install_command: SKILL_INSTALL_COMMAND,
1097
1240
  },
1098
1241
  resource: {
1099
- type: "x402-gated-download",
1242
+ type: REQUIRES_PAYMENT ? "x402-gated-download" : "direct-download",
1100
1243
  download_url: model.downloadUrl,
1101
1244
  promo_url: model.promoUrl,
1102
1245
  artifact_name: ARTIFACT_NAME,
1103
1246
  price_usd: PRICE_USD,
1104
1247
  price_currency: "USDC",
1105
1248
  network: CHAIN_ID,
1249
+ access_mode: ACCESS_MODE,
1250
+ access_summary: accessModeSummary(ACCESS_MODE),
1251
+ payment_required: REQUIRES_PAYMENT,
1252
+ download_code_required: REQUIRES_DOWNLOAD_CODE,
1253
+ download_code_header: DOWNLOAD_CODE_HEADER,
1106
1254
  sale_end: new Date(SALE_END_TS * 1000).toISOString(),
1107
1255
  },
1108
1256
  deprecation: LEGACY_DISCOVERY_DEPRECATION,
@@ -1111,7 +1259,7 @@ app.get("/.well-known/leak", (req, res) => {
1111
1259
  });
1112
1260
  });
1113
1261
 
1114
- // x402 gate for GET /download (supports PAYMENT-SIGNATURE and legacy X-PAYMENT by aliasing)
1262
+ // Access gate for GET /download (download-code check, then optional x402 payment).
1115
1263
  app.use("/download", async (req, res, next) => {
1116
1264
  if (saleEnded()) {
1117
1265
  return res.status(410).json({ error: "leak ended" });
@@ -1123,10 +1271,30 @@ app.use("/download", async (req, res, next) => {
1123
1271
  return next();
1124
1272
  }
1125
1273
 
1274
+ const requestUrl = `${req.protocol}://${req.get("host")}${req.originalUrl}`;
1275
+
1276
+ if (REQUIRES_DOWNLOAD_CODE) {
1277
+ const submittedCode = String(req.get(DOWNLOAD_CODE_HEADER) || "").trim();
1278
+ if (!submittedCode) return sendDownloadCodeRequired(req, res, requestUrl);
1279
+
1280
+ let valid = false;
1281
+ try {
1282
+ valid = await verifyDownloadCode(submittedCode, DOWNLOAD_CODE_HASH);
1283
+ } catch (err) {
1284
+ console.error(
1285
+ `[download-code] verification failed: ${err?.message || String(err)}`,
1286
+ );
1287
+ return res.status(500).json({ error: "download code validation failed" });
1288
+ }
1289
+
1290
+ if (!valid) return sendDownloadCodeRequired(req, res, requestUrl);
1291
+ }
1292
+
1293
+ if (!REQUIRES_PAYMENT) return next();
1294
+
1126
1295
  // NOTE: because this middleware is mounted at "/download", Express strips the mount
1127
1296
  // path and `req.path` becomes "/". x402 route matching needs the *full* path.
1128
1297
  const fullPath = `${req.baseUrl || ""}${req.path || ""}`;
1129
- const requestUrl = `${req.protocol}://${req.get("host")}${req.originalUrl}`;
1130
1298
 
1131
1299
  const adapter = {
1132
1300
  getHeader(name) {
@@ -1217,20 +1385,15 @@ app.get("/download", async (req, res) => {
1217
1385
  if (token) {
1218
1386
  const check = validateAndConsumeToken(token);
1219
1387
  if (!check.ok) return res.status(403).json({ error: check.reason });
1388
+ return sendArtifactStream(res);
1389
+ }
1220
1390
 
1221
- const p = absArtifactPath();
1222
- if (!fs.existsSync(p))
1223
- return res.status(404).json({ error: "artifact not found" });
1224
-
1225
- res.setHeader("Content-Type", MIME_TYPE);
1226
- res.setHeader(
1227
- "Content-Disposition",
1228
- `attachment; filename=\"${path.basename(p)}\"`,
1229
- );
1230
- return fs.createReadStream(p).pipe(res);
1391
+ // 2) No token.
1392
+ if (!REQUIRES_PAYMENT) {
1393
+ return sendArtifactStream(res);
1231
1394
  }
1232
1395
 
1233
- // 2) No token: if we got here, payment has been verified by the middleware.
1396
+ // If we got here with payment enabled, payment has been verified by middleware.
1234
1397
  // If you want immediate UX, just mint token. If you want stronger guarantees, settle.
1235
1398
  if (CONFIRMATION_POLICY === "confirmed") {
1236
1399
  let settle;
@@ -1279,13 +1442,25 @@ app.get("/download", async (req, res) => {
1279
1442
 
1280
1443
  app.listen(PORT, () => {
1281
1444
  console.log(`x402-node listening on http://localhost:${PORT}`);
1282
- console.log(`facilitator mode: ${FACILITATOR_MODE}`);
1283
- console.log(`facilitator url: ${FACILITATOR_URL}`);
1445
+ console.log(`access mode: ${ACCESS_MODE} (${accessModeSummary(ACCESS_MODE)})`);
1446
+ if (REQUIRES_PAYMENT) {
1447
+ console.log(`facilitator mode: ${FACILITATOR_MODE}`);
1448
+ console.log(`facilitator url: ${FACILITATOR_URL}`);
1449
+ }
1284
1450
  console.log(`network: ${CHAIN_ID}`);
1451
+ if (REQUIRES_DOWNLOAD_CODE) {
1452
+ console.log(`download-code: required via header ${DOWNLOAD_CODE_HEADER}`);
1453
+ }
1285
1454
  console.log(`promo: http://localhost:${PORT}/ (share this)`);
1286
1455
  console.log(`info: http://localhost:${PORT}/info`);
1287
1456
  console.log(`health: http://localhost:${PORT}/health`);
1288
- console.log(`download http://localhost:${PORT}/download (x402 protected)`);
1457
+ const protection = [
1458
+ REQUIRES_DOWNLOAD_CODE ? "download-code" : null,
1459
+ REQUIRES_PAYMENT ? "x402 payment" : null,
1460
+ ].filter(Boolean);
1461
+ console.log(
1462
+ `download http://localhost:${PORT}/download (${protection.length > 0 ? protection.join(" + ") : "direct"})`,
1463
+ );
1289
1464
  if (endedWindowActive()) {
1290
1465
  console.log(
1291
1466
  `ended-window active until ${new Date(endedWindowCutoffTs() * 1000).toISOString()} (download endpoints HTTP 410 mode)`,