leak-cli 2026.2.15-beta.0 → 2026.2.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -105,6 +105,14 @@ Security note: use a dedicated buyer key with limited funds.
105
105
 
106
106
  Under the hood, the skill scripts try `leak` on PATH first and fall back to `npx -y leak-cli@2026.2.14` for one-off usage if needed.
107
107
 
108
+ Recommended first-time agent UX for unknown URLs:
109
+ - ask only for skill-install approval (`clawhub install leak`)
110
+ - ask for an existing buyer key file path (default mode)
111
+ - if user opts in and no key exists, create `./.leak/buyer.key` (hot wallet; fund minimally; user must back it up)
112
+ - if workspace is a git repo, add `./.leak/buyer.key` to `.gitignore` so it is not tracked
113
+ - run: `bash skills/leak/scripts/buy.sh "<promo_or_download_url>" --buyer-private-key-file <buyer_key_file_path>` (or `./.leak/buyer.key` for generated fallback)
114
+ - avoid protocol deep-dives unless the user explicitly asks for x402 internals
115
+
108
116
  ### Next: Mainnet checklist (optional)
109
117
 
110
118
  Warning: switching only `CHAIN_ID` to mainnet is not sufficient.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leak-cli",
3
- "version": "2026.2.15-beta.0",
3
+ "version": "2026.2.15",
4
4
  "description": "Self-hosted pop-up stores for creators -- with agent-friendly automation built in",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -28,7 +28,7 @@
28
28
  "check:version-sync": "node scripts/check_version_sync.js",
29
29
  "check:changelog-version": "node scripts/check_changelog_version.js",
30
30
  "check:no-local-paths": "node scripts/check_no_local_paths.js",
31
- "check:syntax": "node --check src/index.js && node --check scripts/cli.js && node --check scripts/leak.js && node --check scripts/buy.js && node --check scripts/config.js",
31
+ "check:syntax": "node --check src/index.js && node --check src/chain_meta.js && node --check scripts/cli.js && node --check scripts/leak.js && node --check scripts/buy.js && node --check scripts/config.js",
32
32
  "check:smoke": "node scripts/cli.js --help && node scripts/leak.js --help && node scripts/buy.js --help && node scripts/config.js --help",
33
33
  "check:release": "npm run check:version-sync && npm run check:syntax && npm run check:smoke && npm run check:no-local-paths && npm pack --dry-run --cache ./.npm-cache",
34
34
  "release:beta": "npm publish --tag beta --provenance",
package/scripts/config.js CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  readConfig,
15
15
  writeConfig,
16
16
  } from "./config_store.js";
17
+ import { resolveSupportedChain } from "../src/chain_meta.js";
17
18
 
18
19
  const ALLOWED_FACILITATOR_MODES = new Set(["testnet", "cdp_mainnet"]);
19
20
  const ALLOWED_CONFIRMATION_POLICIES = new Set(["confirmed", "optimistic"]);
@@ -217,11 +218,21 @@ async function runWizard({ writeEnv }) {
217
218
  sellerPayTo = await askWithDefault(rl, "SELLER_PAY_TO (seller payout address)", sellerPayTo);
218
219
  }
219
220
 
220
- const chainId = await askWithDefault(
221
+ let chainIdInput = await askWithDefault(
221
222
  rl,
222
223
  "CHAIN_ID",
223
224
  existing.chainId || "eip155:84532",
224
225
  );
226
+ let chainId;
227
+ while (true) {
228
+ try {
229
+ chainId = resolveSupportedChain(chainIdInput).caip2;
230
+ break;
231
+ } catch (err) {
232
+ console.error(err.message || String(err));
233
+ chainIdInput = await askWithDefault(rl, "CHAIN_ID", chainIdInput || "eip155:84532");
234
+ }
235
+ }
225
236
 
226
237
  let facilitatorMode = await askWithDefault(
227
238
  rl,
package/scripts/leak.js CHANGED
@@ -8,6 +8,7 @@ import { stdin as input, stdout as output } from "node:process";
8
8
  import { spawn, spawnSync } from "node:child_process";
9
9
  import { isAddress } from "viem";
10
10
  import { defaultFacilitatorUrlForMode, readConfig } from "./config_store.js";
11
+ import { resolveSupportedChain } from "../src/chain_meta.js";
11
12
 
12
13
  const __filename = fileURLToPath(import.meta.url);
13
14
  const __dirname = path.dirname(__filename);
@@ -330,7 +331,17 @@ async function main() {
330
331
  process.exit(1);
331
332
  }
332
333
 
333
- const network = args.network || process.env.CHAIN_ID || configDefaults.chainId || "eip155:84532";
334
+ const networkInput = args.network || process.env.CHAIN_ID || configDefaults.chainId || "eip155:84532";
335
+ let network;
336
+ let networkName;
337
+ try {
338
+ const networkMeta = resolveSupportedChain(networkInput);
339
+ network = networkMeta.caip2;
340
+ networkName = networkMeta.name;
341
+ } catch (err) {
342
+ console.error(err.message || String(err));
343
+ process.exit(1);
344
+ }
334
345
  const port = Number(args.port || process.env.PORT || configDefaults.port || 4021);
335
346
  const facilitatorMode = (
336
347
  process.env.FACILITATOR_MODE || configDefaults.facilitatorMode || "testnet"
@@ -419,7 +430,7 @@ async function main() {
419
430
  console.log(`- price: ${prompted.price} USDC`);
420
431
  console.log(`- window: ${prompted.windowSeconds}s`);
421
432
  console.log(`- to: ${payTo}`);
422
- console.log(`- net: ${network}`);
433
+ console.log(`- net: ${network} (${networkName})`);
423
434
  console.log(`- mode: ${confirmationPolicy}`);
424
435
  console.log(`- facilitator_mode: ${facilitatorMode}`);
425
436
  console.log(`- facilitator_url: ${facilitatorUrl}`);
@@ -0,0 +1,55 @@
1
+ import { base, baseSepolia } from "viem/chains";
2
+ import { extractChain } from "viem/chains/utils";
3
+
4
+ const SUPPORTED_CHAINS = [base, baseSepolia];
5
+
6
+ const SUPPORTED_CHAIN_SUMMARY = [
7
+ `${base.id} (${base.name})`,
8
+ `${baseSepolia.id} (${baseSepolia.name})`,
9
+ ].join(", ");
10
+
11
+ function invalidFormatMessage(raw) {
12
+ const value = String(raw ?? "").trim() || "<empty>";
13
+ return `Invalid chain identifier '${value}'. Expected eip155:<number>. Supported values: ${SUPPORTED_CHAIN_SUMMARY}.`;
14
+ }
15
+
16
+ function unsupportedChainMessage(caip2) {
17
+ return `Unsupported chain '${caip2}'. Supported values: ${SUPPORTED_CHAIN_SUMMARY}.`;
18
+ }
19
+
20
+ export function parseCaip2Eip155(chainIdString) {
21
+ const value = String(chainIdString ?? "").trim();
22
+ const match = value.match(/^eip155:(\d+)$/);
23
+ if (!match) {
24
+ throw new Error(invalidFormatMessage(chainIdString));
25
+ }
26
+
27
+ const id = Number(match[1]);
28
+ if (!Number.isSafeInteger(id) || id <= 0) {
29
+ throw new Error(invalidFormatMessage(chainIdString));
30
+ }
31
+
32
+ return {
33
+ id,
34
+ caip2: `eip155:${id}`,
35
+ };
36
+ }
37
+
38
+ export function resolveSupportedChain(chainIdString) {
39
+ const parsed = parseCaip2Eip155(chainIdString);
40
+ const chain = extractChain({ chains: SUPPORTED_CHAINS, id: parsed.id });
41
+ if (!chain) {
42
+ throw new Error(unsupportedChainMessage(parsed.caip2));
43
+ }
44
+
45
+ return {
46
+ caip2: parsed.caip2,
47
+ id: parsed.id,
48
+ name: chain.name,
49
+ testnet: Boolean(chain.testnet),
50
+ };
51
+ }
52
+
53
+ export function formatChainDisplayName(chainIdString) {
54
+ return resolveSupportedChain(chainIdString).name;
55
+ }
package/src/index.js CHANGED
@@ -9,6 +9,7 @@ import { x402ResourceServer } from "@x402/core/server";
9
9
  import { x402HTTPResourceServer, HTTPFacilitatorClient } from "@x402/core/http";
10
10
  import { ExactEvmScheme } from "@x402/evm/exact/server";
11
11
  import { isAddress } from "viem";
12
+ import { resolveSupportedChain } from "./chain_meta.js";
12
13
 
13
14
  dotenv.config();
14
15
 
@@ -80,7 +81,7 @@ const FACILITATOR_URL = (
80
81
  ).trim();
81
82
  const SELLER_PAY_TO = String(process.env.SELLER_PAY_TO || process.env.PAY_TO || "").trim();
82
83
  const PRICE_USD = process.env.PRICE_USD || "1.00";
83
- const CHAIN_ID = process.env.CHAIN_ID || process.env.NETWORK || "eip155:84532";
84
+ const RAW_CHAIN_ID = process.env.CHAIN_ID || process.env.NETWORK || "eip155:84532";
84
85
  const ARTIFACT_PATH = process.env.ARTIFACT_PATH || process.env.PROTECTED_FILE;
85
86
  const WINDOW_SECONDS = Number(process.env.WINDOW_SECONDS || 3600);
86
87
  const MAX_GRANTS = parsePositiveInt(process.env.MAX_GRANTS, 10000);
@@ -110,7 +111,19 @@ const LEGACY_DISCOVERY_DEPRECATION =
110
111
  const SALE_START_TS = parsePositiveInt(process.env.SALE_START_TS, now());
111
112
  const SALE_END_TS = parsePositiveInt(process.env.SALE_END_TS, SALE_START_TS + WINDOW_SECONDS);
112
113
  const ENDED_WINDOW_SECONDS = parseNonNegativeInt(process.env.ENDED_WINDOW_SECONDS, 0);
113
- const IS_BASE_MAINNET = CHAIN_ID === "eip155:8453";
114
+
115
+ let CHAIN_META;
116
+ try {
117
+ CHAIN_META = resolveSupportedChain(RAW_CHAIN_ID);
118
+ } catch (err) {
119
+ console.error(err?.message || String(err));
120
+ process.exit(1);
121
+ }
122
+
123
+ const CHAIN_ID = CHAIN_META.caip2;
124
+ const CHAIN_NAME = CHAIN_META.name;
125
+ const CHAIN_NUMERIC_ID = CHAIN_META.id;
126
+ const IS_BASE_MAINNET = CHAIN_NUMERIC_ID === 8453;
114
127
 
115
128
  if (!new Set(["testnet", "cdp_mainnet"]).has(FACILITATOR_MODE)) {
116
129
  console.error("Invalid FACILITATOR_MODE. Supported values: testnet, cdp_mainnet");
@@ -307,7 +320,7 @@ function promoModel(req) {
307
320
  const ogTitle = OG_TITLE || ARTIFACT_NAME;
308
321
  const ogDescription =
309
322
  OG_DESCRIPTION ||
310
- `Pay ${PRICE_USD} on ${CHAIN_ID} to unlock ${ARTIFACT_NAME}. Access is time-limited and agent-assisted via /download.`;
323
+ `$${PRICE_USD} to unlock ${ARTIFACT_NAME}`;
311
324
 
312
325
  return {
313
326
  baseUrl,
@@ -358,10 +371,17 @@ ${SKILL_DESCRIPTION}
358
371
  - Status: ${resource.status}
359
372
  - Sale ends: ${resource.sale_end}
360
373
 
361
- ## Agent Flow
374
+ ## Buyer Fast Path (for unknown agents)
375
+ 1. Ask the user for approval to install the skill once.
376
+ 2. Install: \`${SKILL_INSTALL_COMMAND}\`
377
+ 3. Load the skill and continue with Buyer Fast Path.
378
+
379
+ Do not explain x402 internals unless the user explicitly asks.
380
+
381
+ ## Discovery Fallback
362
382
  1. Read resource metadata from \`/.well-known/skills/${SKILL_NAME}/resource.json\`.
363
383
  2. Use \`download_url\` for x402 purchase and token mint.
364
- 3. Download the file from \`/download?token=...\` and save it locally.
384
+ 3. Download from \`/download?token=...\` and save locally.
365
385
 
366
386
  ## CLI
367
387
  - Install: \`${SKILL_INSTALL_COMMAND}\`
@@ -369,6 +389,84 @@ ${SKILL_DESCRIPTION}
369
389
  `;
370
390
  }
371
391
 
392
+ function buildAgentQuickPath(promoUrl, downloadUrl) {
393
+ const safePromoUrl = String(promoUrl || "").trim();
394
+ const safeDownloadUrl = String(downloadUrl || "").trim();
395
+ const buyTarget = safePromoUrl || safeDownloadUrl || "<promo_or_download_url>";
396
+ const buyScriptCommand = `bash skills/leak/scripts/buy.sh "${buyTarget}" --buyer-private-key-file <buyer_key_file_path>`;
397
+
398
+ return {
399
+ promoUrl: safePromoUrl,
400
+ downloadUrl: safeDownloadUrl,
401
+ installCommand: SKILL_INSTALL_COMMAND,
402
+ buyTarget,
403
+ buyScriptCommand,
404
+ };
405
+ }
406
+
407
+ function urlsForQuickPathFromRequestUrl(requestUrl) {
408
+ try {
409
+ const parsed = new URL(String(requestUrl || ""));
410
+ const origin = parsed.origin;
411
+ return {
412
+ promoUrl: `${origin}/`,
413
+ downloadUrl: `${origin}/download`,
414
+ };
415
+ } catch {
416
+ const fallbackBase = isAbsoluteHttpUrl(PUBLIC_BASE_URL)
417
+ ? PUBLIC_BASE_URL.replace(/\/+$/, "")
418
+ : `http://127.0.0.1:${PORT}`;
419
+ return {
420
+ promoUrl: `${fallbackBase}/`,
421
+ downloadUrl: `${fallbackBase}/download`,
422
+ };
423
+ }
424
+ }
425
+
426
+ function renderAgentQuickPathHtmlBlock(fastPath) {
427
+ return `<section class="agent-quick-path">
428
+ <h2>Agent Quick Path</h2>
429
+ <ol>
430
+ <li>Ask for approval to install the skill once.</li>
431
+ <li>Install: <code>${escapeHtml(fastPath.installCommand)}</code></li>
432
+ <li>Load the skill and continue with Buyer Fast Path.</li>
433
+ </ol>
434
+ <p><strong>Rule:</strong> do not explain x402 internals unless the user explicitly asks.</p>
435
+ </section>`;
436
+ }
437
+
438
+ function renderUnpaidDownloadGuidancePage(requestUrl) {
439
+ const urls = urlsForQuickPathFromRequestUrl(requestUrl);
440
+ const fastPath = buildAgentQuickPath(urls.promoUrl, urls.downloadUrl);
441
+ return `<!doctype html>
442
+ <html lang="en">
443
+ <head>
444
+ <meta charset="utf-8" />
445
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
446
+ <title>Payment Required - leak</title>
447
+ <style>
448
+ body { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; margin: 0; padding: 24px; background: #f7f7f5; color: #1f1f1f; }
449
+ .card { max-width: 760px; margin: 0 auto; border: 1px solid #d8d8d0; background: #fff; border-radius: 10px; padding: 20px; }
450
+ h1 { margin: 0 0 12px; font-size: 24px; }
451
+ h2 { margin: 0 0 10px; font-size: 18px; }
452
+ p { line-height: 1.5; }
453
+ code, pre { background: #f0f0eb; border-radius: 6px; padding: 2px 6px; }
454
+ pre { padding: 10px; overflow-x: auto; }
455
+ ol { margin: 10px 0 12px 20px; }
456
+ </style>
457
+ </head>
458
+ <body>
459
+ <main class="card">
460
+ <h1>402 Payment Required</h1>
461
+ <p>This URL is paywalled. Use the leak skill fast path below.</p>
462
+ <p><strong>Promo URL:</strong> <code>${escapeHtml(fastPath.promoUrl)}</code></p>
463
+ <p><strong>x402 URL:</strong> <code>${escapeHtml(fastPath.downloadUrl)}</code></p>
464
+ ${renderAgentQuickPathHtmlBlock(fastPath)}
465
+ </main>
466
+ </body>
467
+ </html>`;
468
+ }
469
+
372
470
  function sendSkillIndex(req, res) {
373
471
  const payload = {
374
472
  skills: [
@@ -408,6 +506,7 @@ function renderPromoPage(model, { ended }) {
408
506
  ? `This leak has ended. ${model.ogDescription}`
409
507
  : model.ogDescription;
410
508
  const expiresIso = new Date(model.saleEndTs * 1000).toISOString();
509
+ const fastPath = buildAgentQuickPath(model.promoUrl, model.downloadUrl);
411
510
  const jsonLd = {
412
511
  "@context": "https://schema.org",
413
512
  "@type": "Product",
@@ -433,7 +532,7 @@ function renderPromoPage(model, { ended }) {
433
532
  };
434
533
  const safeJsonLd = toSafeJsonForScript(jsonLd);
435
534
 
436
- const examplePrompt = `Buy this and save it: ${model.downloadUrl}`;
535
+ const humanActionText = "Just send the link to this page to your agent";
437
536
 
438
537
  return `<!doctype html>
439
538
  <html lang="en">
@@ -472,53 +571,68 @@ function renderPromoPage(model, { ended }) {
472
571
  .copy-status { font-size: 12px; color: #3f3f3f; min-height: 1em; }
473
572
  .install-note { margin-top: 16px; font-size: 13px; color: #2f2f2f; }
474
573
  .install-note a { color: #1f1f1f; }
574
+ .agent-quick-path { margin: 16px 0; padding: 14px; border: 1px solid #d8d8d0; border-radius: 8px; background: #fafaf6; }
575
+ .agent-quick-path h2 { margin: 0 0 8px; font-size: 18px; }
576
+ .agent-quick-path ol { margin: 8px 0 8px 20px; }
577
+ .agent-quick-path p { margin: 8px 0 0; }
475
578
  </style>
476
579
  </head>
477
580
  <body>
478
581
  <main class="card">
479
582
  <div class="state">${escapeHtml(stateLabel)}</div>
480
583
  <h1>${escapeHtml(pageTitle)}</h1>
481
- <p>${escapeHtml(description)}</p>
482
- <p><strong>Agent-assisted purchase:</strong> this release is designed to be bought through an agent using the x402 endpoint below.</p>
483
584
 
484
585
  <div class="kv"><strong>Price:</strong> ${escapeHtml(PRICE_USD)} USD equivalent</div>
485
- <div class="kv"><strong>Network:</strong> ${escapeHtml(CHAIN_ID)}</div>
486
- <div class="kv"><strong>Sale end:</strong> ${escapeHtml(expiresIso)}</div>
586
+ <div class="kv"><strong>Network:</strong> ${escapeHtml(CHAIN_NAME)} (${escapeHtml(CHAIN_ID)})</div>
587
+ <div class="kv"><strong>Sale end:</strong> <span id="sale-end-local" data-sale-end-iso="${escapeHtml(expiresIso)}">${escapeHtml(expiresIso)}</span></div>
588
+ ${renderAgentQuickPathHtmlBlock(fastPath)}
487
589
 
488
- <p><strong>x402 URL</strong><br /><code>${escapeHtml(model.downloadUrl)}</code></p>
489
590
  <div class="prompt-head">
490
- <p><strong>Example agent prompt</strong></p>
491
- <button class="copy-btn" id="copy-agent-prompt" type="button" aria-label="Copy example agent prompt">Copy prompt</button>
492
- <span class="copy-status" id="copy-prompt-status" aria-live="polite"></span>
591
+ <p><strong>Human action</strong></p>
592
+ <button class="copy-btn" id="copy-link-btn" type="button" aria-label="Copy page link">Copy link</button>
593
+ <span class="copy-status" id="copy-link-status" aria-live="polite"></span>
493
594
  </div>
494
- <pre id="example-agent-prompt">${escapeHtml(examplePrompt)}</pre>
595
+ <pre id="human-action-text">${escapeHtml(humanActionText)}</pre>
495
596
  <p class="install-note">
496
- Need help setting this up? Install leak at
597
+ Want to know more about <code>leak</code>? Visit
497
598
  <a href="https://github.com/eucalyptus-viminalis/leak">github.com/eucalyptus-viminalis/leak</a>
498
599
  or search for leak on clawhub.
499
600
  </p>
500
601
  </main>
501
602
  <script>
502
603
  (() => {
503
- const button = document.getElementById("copy-agent-prompt");
504
- const pre = document.getElementById("example-agent-prompt");
505
- const status = document.getElementById("copy-prompt-status");
506
- if (!button || !pre) return;
604
+ const button = document.getElementById("copy-link-btn");
605
+ const status = document.getElementById("copy-link-status");
606
+ const promoUrl = ${toSafeJsonForScript(model.promoUrl)};
607
+ const saleEndLocal = document.getElementById("sale-end-local");
608
+
609
+ if (saleEndLocal) {
610
+ const saleEndIso = saleEndLocal.getAttribute("data-sale-end-iso") || "";
611
+ const saleEndDate = new Date(saleEndIso);
612
+ if (!Number.isNaN(saleEndDate.getTime())) {
613
+ try {
614
+ const formatter = new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "medium" });
615
+ saleEndLocal.textContent = formatter.format(saleEndDate) + " (local time)";
616
+ } catch {
617
+ saleEndLocal.textContent = saleEndDate.toLocaleString() + " (local time)";
618
+ }
619
+ }
620
+ }
621
+
622
+ if (!button || !promoUrl) return;
507
623
 
508
624
  const setStatus = (text) => {
509
625
  if (status) status.textContent = text;
510
626
  };
511
627
 
512
628
  button.addEventListener("click", async () => {
513
- const text = pre.textContent || "";
514
- if (!text) return;
515
- const original = "Copy prompt";
629
+ const original = "Copy link";
516
630
  try {
517
631
  if (navigator.clipboard?.writeText) {
518
- await navigator.clipboard.writeText(text);
632
+ await navigator.clipboard.writeText(promoUrl);
519
633
  } else {
520
634
  const ta = document.createElement("textarea");
521
- ta.value = text;
635
+ ta.value = promoUrl;
522
636
  ta.setAttribute("readonly", "");
523
637
  ta.style.position = "absolute";
524
638
  ta.style.left = "-9999px";
@@ -546,7 +660,7 @@ function renderPromoPage(model, { ended }) {
546
660
  function renderOgSvg(req) {
547
661
  const model = promoModel(req);
548
662
  const title = model.ogTitle;
549
- const subtitle = `Pay ${PRICE_USD} on ${CHAIN_ID}`;
663
+ const subtitle = `$${PRICE_USD} on ${CHAIN_NAME}`;
550
664
  const status = saleEnded() ? "ENDED" : "LIVE";
551
665
 
552
666
  return `<?xml version="1.0" encoding="UTF-8"?>
@@ -559,10 +673,10 @@ function renderOgSvg(req) {
559
673
  </defs>
560
674
  <rect width="1200" height="630" fill="url(#bg)"/>
561
675
  <rect x="64" y="64" width="1072" height="502" rx="18" fill="#ffffff" stroke="#bdbdae"/>
562
- <text x="96" y="170" font-size="32" font-family="monospace" fill="#222">${escapeXml(status)} LEAK</text>
676
+ <text x="96" y="170" font-size="32" font-family="monospace" fill="#222">${escapeXml(status)}</text>
563
677
  <text x="96" y="250" font-size="52" font-family="monospace" fill="#111">${escapeXml(title)}</text>
564
678
  <text x="96" y="330" font-size="30" font-family="monospace" fill="#333">${escapeXml(subtitle)}</text>
565
- <text x="96" y="404" font-size="22" font-family="monospace" fill="#444">x402 via /download</text>
679
+ <text x="96" y="404" font-size="22" font-family="monospace" fill="#444">Share this link with your OpenClaw agent to download</text>
566
680
  </svg>`;
567
681
  }
568
682
 
@@ -637,6 +751,10 @@ const routes = {
637
751
  ],
638
752
  description: ARTIFACT_NAME,
639
753
  mimeType: MIME_TYPE,
754
+ unpaidResponseBody: async (context) => ({
755
+ contentType: "text/html; charset=utf-8",
756
+ body: renderUnpaidDownloadGuidancePage(context?.adapter?.getUrl?.()),
757
+ }),
640
758
  },
641
759
  };
642
760