@toon-protocol/rig 2.8.0 → 2.10.0

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/dist/cli/rig.js CHANGED
@@ -26,8 +26,9 @@ import {
26
26
  clientKeystorePath,
27
27
  linkKeystoreInClientConfig,
28
28
  readClientConfigFile,
29
+ resolveAccountIndex,
29
30
  resolveIdentity
30
- } from "../chunk-XGFBDUQX.js";
31
+ } from "../chunk-GZUDRNOK.js";
31
32
  import {
32
33
  ChannelMapStore,
33
34
  channelStatus,
@@ -312,9 +313,7 @@ function nonFastForwardLines(refs) {
312
313
  function oversizeLines(objects) {
313
314
  return [
314
315
  `Push rejected: ${objects.length} object(s) exceed the ${MAX_OBJECT_SIZE}-byte (95KB) upload limit:`,
315
- ...objects.map(
316
- (o) => ` ${o.path ?? o.sha} ${o.type}, ${o.size} bytes`
317
- ),
316
+ ...objects.map((o) => ` ${o.path ?? o.sha} ${o.type}, ${o.size} bytes`),
318
317
  "Objects over 95KB are a hard error in v1 \u2014 split or remove the file(s) from history to push.",
319
318
  "Large-object support is tracked in toon-client#235."
320
319
  ];
@@ -345,7 +344,11 @@ function describeError(err, command = "push") {
345
344
  return {
346
345
  code: "unknown_remote",
347
346
  lines: err.message.split("\n"),
348
- json: { error: "unknown_remote", detail: err.message, remote: err.remote }
347
+ json: {
348
+ error: "unknown_remote",
349
+ detail: err.message,
350
+ remote: err.remote
351
+ }
349
352
  };
350
353
  }
351
354
  if (err instanceof MultiUrlRemoteError) {
@@ -406,6 +409,13 @@ function describeError(err, command = "push") {
406
409
  json: { error: "arns_sdk_unavailable", detail: err.message }
407
410
  };
408
411
  }
412
+ if (err instanceof Error && err.name === "ArnsSdkIncompatibleError") {
413
+ return {
414
+ code: "arns_sdk_incompatible",
415
+ lines: err.message.split("\n"),
416
+ json: { error: "arns_sdk_incompatible", detail: err.message }
417
+ };
418
+ }
409
419
  if (err instanceof DaemonRouteError) {
410
420
  const envelope = err.envelope;
411
421
  if (envelope.error === "non_fast_forward" && Array.isArray(envelope["refs"])) {
@@ -532,8 +542,97 @@ function emitCliError(io2, json, command, err) {
532
542
  }
533
543
 
534
544
  // src/cli/push.ts
545
+ import { createHash } from "crypto";
535
546
  import { parseArgs as parseArgs2 } from "util";
536
547
 
548
+ // src/rig-pointer.ts
549
+ var DEFAULT_RIG_WEB_BUNDLE = {
550
+ manifestTx: "Iy8sHYVnoevSpwDC2lqUoIg8BWdes9NmMbgmPd81UpU",
551
+ entryJs: "assets/index-BuKzYubR.js",
552
+ entryCss: "assets/index-H5D6cmfa.css"
553
+ };
554
+ var RIG_WEB_TX_ENV = "RIG_WEB_TX";
555
+ var RIG_WEB_ENTRY_JS_ENV = "RIG_WEB_ENTRY_JS";
556
+ var RIG_WEB_ENTRY_CSS_ENV = "RIG_WEB_ENTRY_CSS";
557
+ var DEFAULT_RIG_WEB_URL = "https://toon-protocol.github.io/toon-client";
558
+ var RIG_WEB_URL_ENV = "RIG_WEB_URL";
559
+ function repoHashRoute(options) {
560
+ const { relay, ownerNpub, repoId } = options;
561
+ return `#/${encodeURIComponent(ownerNpub)}/${encodeURIComponent(repoId)}?relay=${encodeURIComponent(relay)}`;
562
+ }
563
+ function rigWebRoute(options) {
564
+ return `${options.rigWebUrl.replace(/\/+$/, "")}/${repoHashRoute(options)}`;
565
+ }
566
+ function escapeHtml(value) {
567
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
568
+ }
569
+ function generateRigPointerHtml(options) {
570
+ const gateway = options.gateway.replace(/\/+$/, "");
571
+ const base = `${gateway}/${options.bundle.manifestTx}`;
572
+ const cssHref = `${base}/${options.bundle.entryCss}`;
573
+ const jsSrc = `${base}/${options.bundle.entryJs}`;
574
+ const fallback = rigWebRoute(options);
575
+ const safeFallback = escapeHtml(fallback);
576
+ const title = escapeHtml(`${options.repoId} \u2014 Rig`);
577
+ const config = JSON.stringify({
578
+ relay: options.relay,
579
+ owner: options.ownerNpub,
580
+ repo: options.repoId
581
+ }).replace(/</g, "\\u003c");
582
+ const hashRoute = JSON.stringify(repoHashRoute(options)).replace(
583
+ /</g,
584
+ "\\u003c"
585
+ );
586
+ return `<!DOCTYPE html>
587
+ <html lang="en"><head>
588
+ <meta charset="utf-8">
589
+ <meta name="viewport" content="width=device-width,initial-scale=1">
590
+ <meta name="description" content="Decentralized Git on Nostr &amp; TOON Protocol">
591
+ <title>${title}</title>
592
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>&#x2692;</text></svg>">
593
+ <script>window.__RIG_CONFIG__=${config};if(!location.hash||location.hash==="#")location.replace(${hashRoute})</script>
594
+ <link rel="stylesheet" href="${escapeHtml(cssHref)}">
595
+ <script type="module" crossorigin src="${escapeHtml(jsSrc)}"></script>
596
+ </head><body>
597
+ <div id="app"></div>
598
+ <noscript><p>JavaScript is off \u2014 open <a href="${safeFallback}">${title}</a> in the hosted Rig.</p></noscript>
599
+ <p hidden data-fallback>If nothing loads, open <a href="${safeFallback}">${title}</a> in the hosted Rig.</p>
600
+ <script>setTimeout(function(){var p=document.querySelector("[data-fallback]");var app=document.getElementById("app");if(p&&(!app||!app.childElementCount))p.hidden=false},8000)</script>
601
+ </body></html>
602
+ `;
603
+ }
604
+
605
+ // src/cli/rig-pointer-record.ts
606
+ import { mkdirSync, readFileSync, writeFileSync } from "fs";
607
+ import { homedir } from "os";
608
+ import { join } from "path";
609
+ var RIG_POINTER_FILENAME = "rig-pointers.json";
610
+ function storeDir(env) {
611
+ return env["TOON_CLIENT_HOME"] ?? join(homedir(), ".toon-client");
612
+ }
613
+ function storePath(env) {
614
+ return join(storeDir(env), RIG_POINTER_FILENAME);
615
+ }
616
+ function readAll(env) {
617
+ try {
618
+ const parsed = JSON.parse(readFileSync(storePath(env), "utf8"));
619
+ if (parsed && typeof parsed === "object") return parsed;
620
+ return {};
621
+ } catch {
622
+ return {};
623
+ }
624
+ }
625
+ function readRigPointerRecord(env, repoId) {
626
+ return readAll(env)[repoId];
627
+ }
628
+ function writeRigPointerRecord(env, record) {
629
+ const all = readAll(env);
630
+ all[record.repoId] = record;
631
+ mkdirSync(storeDir(env), { recursive: true });
632
+ writeFileSync(storePath(env), `${JSON.stringify(all, null, 2)}
633
+ `);
634
+ }
635
+
537
636
  // src/cli/render.ts
538
637
  function formatNumber(value) {
539
638
  return String(value).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
@@ -974,7 +1073,7 @@ function loadPaidSession(deps, relayUrl) {
974
1073
  });
975
1074
  }
976
1075
  var defaultLoadStandalone = async (options) => {
977
- const mod = await import("../standalone-mode-FPFEEL72.js");
1076
+ const mod = await import("../standalone-mode-6KIBWGLP.js");
978
1077
  return mod.createStandaloneContext(options);
979
1078
  };
980
1079
  function identityReport(ctx) {
@@ -1001,10 +1100,18 @@ remote name it is the remote; otherwise it is a refspec and the remote
1001
1100
  defaults to origin. Refspecs are branch/tag names or full refnames; default
1002
1101
  is the current branch.
1003
1102
 
1103
+ Every push also keeps the repo's RIG PAGE current: a permanent Arweave
1104
+ page that RENDERS this repo in place \u2014 it boots the FULL Rig (rig-web)
1105
+ from its Arweave deployment with this repo pinned, everything served from
1106
+ Arweave + the relay. The pointer is content-addressed, so it is paid for
1107
+ once and reused for free until the relay or Rig deployment changes.
1108
+ --no-rig-page skips it.
1109
+
1004
1110
  Options:
1005
1111
  --force allow non-fast-forward ref updates (overwrites remote history)
1006
1112
  --all push all local branches
1007
1113
  --tags push all local tags
1114
+ --no-rig-page skip the Rig-page pointer upload for this push
1008
1115
  --yes skip the fee confirmation (required when not a TTY)
1009
1116
  --json machine-readable plan/receipts; without --yes it is a pure
1010
1117
  estimate (nothing executed)
@@ -1077,6 +1184,7 @@ function parsePushArgs(args) {
1077
1184
  force: { type: "boolean", default: false },
1078
1185
  all: { type: "boolean", default: false },
1079
1186
  tags: { type: "boolean", default: false },
1187
+ "no-rig-page": { type: "boolean", default: false },
1080
1188
  yes: { type: "boolean", default: false },
1081
1189
  json: { type: "boolean", default: false },
1082
1190
  relay: { type: "string", multiple: true },
@@ -1089,6 +1197,7 @@ function parsePushArgs(args) {
1089
1197
  force: values.force ?? false,
1090
1198
  all: values.all ?? false,
1091
1199
  tags: values.tags ?? false,
1200
+ rigPage: !(values["no-rig-page"] ?? false),
1092
1201
  yes: values.yes ?? false,
1093
1202
  json: values.json ?? false,
1094
1203
  relay: values.relay ?? [],
@@ -1099,6 +1208,46 @@ function parsePushArgs(args) {
1099
1208
  if (repoId !== void 0) flags.repoId = repoId;
1100
1209
  return flags;
1101
1210
  }
1211
+ function pointerGateway(env) {
1212
+ return (env["RIG_ARWEAVE_GATEWAY"] ?? "https://arweave.net").replace(
1213
+ /\/+$/,
1214
+ ""
1215
+ );
1216
+ }
1217
+ function planRigPointer(args) {
1218
+ const html = generateRigPointerHtml({
1219
+ bundle: {
1220
+ manifestTx: args.env[RIG_WEB_TX_ENV] ?? DEFAULT_RIG_WEB_BUNDLE.manifestTx,
1221
+ entryJs: args.env[RIG_WEB_ENTRY_JS_ENV] ?? DEFAULT_RIG_WEB_BUNDLE.entryJs,
1222
+ entryCss: args.env[RIG_WEB_ENTRY_CSS_ENV] ?? DEFAULT_RIG_WEB_BUNDLE.entryCss
1223
+ },
1224
+ gateway: pointerGateway(args.env),
1225
+ rigWebUrl: args.env[RIG_WEB_URL_ENV] ?? DEFAULT_RIG_WEB_URL,
1226
+ relay: args.relay,
1227
+ ownerNpub: hexToNpub(args.ownerPubkey),
1228
+ repoId: args.repoId
1229
+ });
1230
+ const contentHash = createHash("sha256").update(html).digest("hex");
1231
+ const bytes = Buffer.byteLength(html);
1232
+ const record = readRigPointerRecord(args.env, args.repoId);
1233
+ if (record && record.contentHash === contentHash && record.owner === args.ownerPubkey) {
1234
+ return {
1235
+ action: "unchanged",
1236
+ html,
1237
+ contentHash,
1238
+ bytes,
1239
+ fee: 0n,
1240
+ recordedTxId: record.pointerTxId
1241
+ };
1242
+ }
1243
+ return {
1244
+ action: "upload",
1245
+ html,
1246
+ contentHash,
1247
+ bytes,
1248
+ fee: BigInt(bytes) * args.uploadFeePerByte
1249
+ };
1250
+ }
1102
1251
  async function runPush(args, deps) {
1103
1252
  const { io: io2 } = deps;
1104
1253
  let flags;
@@ -1175,6 +1324,8 @@ async function runPush(args, deps) {
1175
1324
  }
1176
1325
  let plan;
1177
1326
  let execute;
1327
+ let pointerPlan;
1328
+ let rigPageSkipDetail;
1178
1329
  if (session.path === "standalone") {
1179
1330
  const ctx = session.ctx;
1180
1331
  const remoteState = await ctx.fetchRemote({
@@ -1202,6 +1353,21 @@ async function runPush(args, deps) {
1202
1353
  relayUrls: relaysUsed
1203
1354
  })
1204
1355
  );
1356
+ if (!flags.rigPage) {
1357
+ rigPageSkipDetail = "skipped (--no-rig-page)";
1358
+ } else if (relaysUsed[0] === void 0) {
1359
+ rigPageSkipDetail = "skipped (no relay resolved for the pointer)";
1360
+ } else if (!ctx.publisher.uploadBlob) {
1361
+ rigPageSkipDetail = "skipped (the active publisher cannot upload raw blobs)";
1362
+ } else {
1363
+ pointerPlan = planRigPointer({
1364
+ env: deps.env,
1365
+ ownerPubkey: ctx.ownerPubkey,
1366
+ repoId,
1367
+ relay: relaysUsed[0],
1368
+ uploadFeePerByte: feeRates.uploadFeePerByte
1369
+ });
1370
+ }
1205
1371
  } else {
1206
1372
  const client = session.client;
1207
1373
  plan = await client.gitEstimate({
@@ -1219,9 +1385,100 @@ async function runPush(args, deps) {
1219
1385
  relayUrls: relaysUsed,
1220
1386
  confirm: true
1221
1387
  });
1388
+ rigPageSkipDetail = "skipped on the daemon path (refreshes on the next standalone push)";
1222
1389
  }
1390
+ const gateway = pointerGateway(deps.env);
1391
+ const publishPointer = async (pointer) => {
1392
+ try {
1393
+ const ctx = standaloneCtx;
1394
+ const receipt = await ctx.publisher.uploadBlob({
1395
+ body: Buffer.from(pointer.html, "utf8"),
1396
+ contentType: "text/html",
1397
+ repoId
1398
+ });
1399
+ writeRigPointerRecord(deps.env, {
1400
+ repoId,
1401
+ owner: identity.pubkey,
1402
+ pointerTxId: receipt.txId,
1403
+ contentHash: pointer.contentHash,
1404
+ updatedAt: Date.now()
1405
+ });
1406
+ return {
1407
+ status: "published",
1408
+ txId: receipt.txId,
1409
+ url: `${gateway}/${receipt.txId}`,
1410
+ feePaid: receipt.feePaid.toString()
1411
+ };
1412
+ } catch (err) {
1413
+ return {
1414
+ status: "skipped",
1415
+ detail: `pointer upload failed (push itself succeeded; the next push retries): ${err instanceof Error ? err.message : String(err)}`
1416
+ };
1417
+ }
1418
+ };
1419
+ const printRigPage = (rigPage2) => {
1420
+ if (rigPage2.url) {
1421
+ io2.out(
1422
+ `Rig page: ${rigPage2.url}` + (rigPage2.status === "published" ? ` paid ${rigPage2.feePaid}` : "") + " (renders this repo from Arweave)"
1423
+ );
1424
+ } else if (rigPage2.detail) {
1425
+ io2.err(`rig page: ${rigPage2.detail}`);
1426
+ }
1427
+ };
1223
1428
  const upToDate = plan.refUpdates.every((u) => u.kind === "up-to-date");
1224
1429
  if (upToDate) {
1430
+ if (pointerPlan?.action !== "upload") {
1431
+ if (flags.json) {
1432
+ io2.emitJson({
1433
+ command: "push",
1434
+ repoId,
1435
+ path,
1436
+ identity,
1437
+ executed: false,
1438
+ upToDate: true,
1439
+ plan
1440
+ });
1441
+ } else {
1442
+ io2.out("Everything up-to-date \u2014 nothing to push (and nothing paid).");
1443
+ }
1444
+ return 0;
1445
+ }
1446
+ if (!flags.json) {
1447
+ io2.out(
1448
+ "Everything up-to-date \u2014 refs unchanged, but the Rig page is stale (new Rig build, relay, or gateway):"
1449
+ );
1450
+ io2.out(` rig page ${pointerPlan.bytes} bytes ${pointerPlan.fee}`);
1451
+ io2.out(renderIdentityLine(identity));
1452
+ }
1453
+ if (!flags.yes) {
1454
+ if (flags.json) {
1455
+ io2.emitJson({
1456
+ command: "push",
1457
+ repoId,
1458
+ path,
1459
+ identity,
1460
+ executed: false,
1461
+ upToDate: true,
1462
+ plan,
1463
+ hint: "refs up-to-date; the Rig page is stale \u2014 re-run with --yes to refresh it (or --no-rig-page to skip)"
1464
+ });
1465
+ return 0;
1466
+ }
1467
+ if (!io2.isInteractive) {
1468
+ io2.err(
1469
+ "refusing to spend channel funds without confirmation in a non-interactive session \u2014 re-run with --yes (or --no-rig-page to skip the refresh)"
1470
+ );
1471
+ return 1;
1472
+ }
1473
+ const proceed = await io2.confirm(
1474
+ `Refresh the Rig page (total ${pointerPlan.fee} base units)? [y/N] `
1475
+ );
1476
+ if (!proceed) {
1477
+ io2.err("aborted \u2014 nothing was uploaded or published.");
1478
+ return 1;
1479
+ }
1480
+ }
1481
+ const rigPage2 = await publishPointer(pointerPlan);
1225
1482
  if (flags.json) {
1226
1483
  io2.emitJson({
1227
1484
  command: "push",
@@ -1230,15 +1487,23 @@ async function runPush(args, deps) {
1230
1487
  identity,
1231
1488
  executed: false,
1232
1489
  upToDate: true,
1233
- plan
1490
+ plan,
1491
+ rigPage: rigPage2
1234
1492
  });
1235
1493
  } else {
1236
- io2.out("Everything up-to-date \u2014 nothing to push (and nothing paid).");
1494
+ printRigPage(rigPage2);
1237
1495
  }
1238
1496
  return 0;
1239
1497
  }
1498
+ const confirmTotal = BigInt(plan.estimate.totalFee) + (pointerPlan?.fee ?? 0n);
1240
1499
  if (!flags.json) {
1241
1500
  for (const line of renderPlan(plan)) io2.out(line);
1501
+ if (pointerPlan?.action === "upload") {
1502
+ io2.out(
1503
+ ` rig page ${pointerPlan.bytes} bytes ${pointerPlan.fee} (repo page \u2014 paid once, reused until relay/rig-web changes)`
1504
+ );
1505
+ io2.out(` total with rig page ${confirmTotal}`);
1506
+ }
1242
1507
  io2.out(renderIdentityLine(identity));
1243
1508
  }
1244
1509
  if (!flags.yes) {
@@ -1262,7 +1527,7 @@ async function runPush(args, deps) {
1262
1527
  return 1;
1263
1528
  }
1264
1529
  const proceed = await io2.confirm(
1265
- `Proceed with paid push (total ${plan.estimate.totalFee} base units)? [y/N] `
1530
+ `Proceed with paid push (total ${confirmTotal} base units)? [y/N] `
1266
1531
  );
1267
1532
  if (!proceed) {
1268
1533
  io2.err("aborted \u2014 nothing was uploaded or published.");
@@ -1270,6 +1535,18 @@ async function runPush(args, deps) {
1270
1535
  }
1271
1536
  }
1272
1537
  const result = await execute();
1538
+ let rigPage;
1539
+ if (!pointerPlan) {
1540
+ rigPage = { status: "skipped", ...rigPageSkipDetail ? { detail: rigPageSkipDetail } : {} };
1541
+ } else if (pointerPlan.action === "unchanged") {
1542
+ rigPage = {
1543
+ status: "unchanged",
1544
+ txId: pointerPlan.recordedTxId,
1545
+ url: `${gateway}/${pointerPlan.recordedTxId}`
1546
+ };
1547
+ } else {
1548
+ rigPage = await publishPointer(pointerPlan);
1549
+ }
1273
1550
  if (flags.json) {
1274
1551
  io2.emitJson({
1275
1552
  command: "push",
@@ -1279,10 +1556,12 @@ async function runPush(args, deps) {
1279
1556
  executed: true,
1280
1557
  upToDate: false,
1281
1558
  plan,
1282
- result
1559
+ result,
1560
+ rigPage
1283
1561
  });
1284
1562
  } else {
1285
1563
  for (const line of renderResult(result)) io2.out(line);
1564
+ printRigPage(rigPage);
1286
1565
  }
1287
1566
  return 0;
1288
1567
  } catch (err) {
@@ -1896,7 +2175,7 @@ async function stopQuietly(ctx) {
1896
2175
 
1897
2176
  // src/cli/clone.ts
1898
2177
  import { mkdtemp, mkdir, readdir, rename, rm, rmdir } from "fs/promises";
1899
- import { basename, dirname, join, resolve } from "path";
2178
+ import { basename, dirname, join as join2, resolve } from "path";
1900
2179
  import { parseArgs as parseArgs5 } from "util";
1901
2180
  var CLONE_USAGE = `Usage: rig clone <relay-url> <owner>/<repo-id> [dir] [options]
1902
2181
 
@@ -2067,7 +2346,7 @@ async function runClone(args, deps) {
2067
2346
  }
2068
2347
  const parent = dirname(dest);
2069
2348
  await mkdir(parent, { recursive: true });
2070
- tempDir = await mkdtemp(join(parent, `.${basename(dest)}.rig-clone-`));
2349
+ tempDir = await mkdtemp(join2(parent, `.${basename(dest)}.rig-clone-`));
2071
2350
  await runGit(tempDir, [
2072
2351
  "init",
2073
2352
  "--quiet",
@@ -3533,9 +3812,9 @@ async function runFetch(args, deps) {
3533
3812
  }
3534
3813
 
3535
3814
  // src/cli/fund.ts
3536
- import { readFileSync } from "fs";
3537
- import { homedir } from "os";
3538
- import { join as join2 } from "path";
3815
+ import { readFileSync as readFileSync2 } from "fs";
3816
+ import { homedir as homedir2 } from "os";
3817
+ import { join as join3 } from "path";
3539
3818
  import { parseArgs as parseArgs9 } from "util";
3540
3819
  var DEVNET_FAUCET_URL = "https://faucet.devnet.toonprotocol.dev";
3541
3820
  var CHAINS = ["evm", "solana", "mina"];
@@ -3625,11 +3904,11 @@ function noFaucetGuidance(network, devnetOrigin) {
3625
3904
  return `${head}. If you meant the shared devnet, set TOON_CLIENT_NETWORK=devnet and re-run \`rig fund\` \u2014 no faucet URL is needed there. If this is a self-hosted network with its own faucet, set TOON_CLIENT_FAUCET_URL (or the faucetUrl config field). ${external}`;
3626
3905
  }
3627
3906
  function readFundConfig(env) {
3628
- const dir = env["TOON_CLIENT_HOME"] ?? join2(homedir(), ".toon-client");
3629
- const configPath = join2(dir, "config.json");
3907
+ const dir = env["TOON_CLIENT_HOME"] ?? join3(homedir2(), ".toon-client");
3908
+ const configPath = join3(dir, "config.json");
3630
3909
  try {
3631
3910
  return {
3632
- file: JSON.parse(readFileSync(configPath, "utf8")),
3911
+ file: JSON.parse(readFileSync2(configPath, "utf8")),
3633
3912
  configPath
3634
3913
  };
3635
3914
  } catch (err) {
@@ -3862,7 +4141,7 @@ var runGitPassthrough = (argv2, options = {}) => {
3862
4141
  };
3863
4142
 
3864
4143
  // src/cli/identity-cmd.ts
3865
- import { existsSync, mkdirSync } from "fs";
4144
+ import { existsSync, mkdirSync as mkdirSync2 } from "fs";
3866
4145
  import { dirname as dirname2 } from "path";
3867
4146
  import { createInterface } from "readline/promises";
3868
4147
  import { parseArgs as parseArgs10 } from "util";
@@ -3914,12 +4193,12 @@ async function prepareKeystoreTarget(input) {
3914
4193
  const envPassword = input.env["TOON_CLIENT_KEYSTORE_PASSWORD"]?.trim() || void 0;
3915
4194
  const autoPassword = envPassword === void 0;
3916
4195
  const password = envPassword ?? DEFAULT_KEYSTORE_PASSWORD;
3917
- mkdirSync(dirname2(keystorePath), { recursive: true });
4196
+ mkdirSync2(dirname2(keystorePath), { recursive: true });
3918
4197
  return { keystorePath, password, autoPassword, existing, keyOps };
3919
4198
  }
3920
4199
  function readAccountIndex(env) {
3921
4200
  const file = readClientConfigFile(clientConfigPath(env));
3922
- return typeof file.mnemonicAccountIndex === "number" ? file.mnemonicAccountIndex : 0;
4201
+ return resolveAccountIndex(env, "keystore", file);
3923
4202
  }
3924
4203
  function finishMint(input, target, mnemonic) {
3925
4204
  const configPath = linkKeystoreInClientConfig(
@@ -4412,7 +4691,7 @@ async function resolveProfileRelay(flagRelay, originRelay, toonRelays) {
4412
4691
  ].filter((u) => u !== void 0 && isWebSocketRelay2(u));
4413
4692
  if (candidates[0] !== void 0) return candidates[0];
4414
4693
  try {
4415
- const { loadGenesisSeed } = await import("../network-bootstrap-HTNM7BBX.js");
4694
+ const { loadGenesisSeed } = await import("../network-bootstrap-GRBEPZMI.js");
4416
4695
  const seed = loadGenesisSeed();
4417
4696
  if (seed?.relayUrl && isWebSocketRelay2(seed.relayUrl)) return seed.relayUrl;
4418
4697
  } catch {
@@ -4921,67 +5200,411 @@ async function runMutate(op, args, deps) {
4921
5200
  }
4922
5201
 
4923
5202
  // src/cli/name.ts
5203
+ import { randomUUID } from "crypto";
4924
5204
  import { parseArgs as parseArgs13 } from "util";
4925
5205
  var ARIO_DECIMALS = 6;
4926
5206
  function renderMario(mario) {
4927
5207
  return `${mario.toString()} mARIO (~${formatUnits(mario.toString(), ARIO_DECIMALS)} ARIO)`;
4928
5208
  }
4929
5209
  var MARIO_PAYMENT_NOTE = "Payment is mARIO from this identity's Solana wallet, charged on-chain by the ar.io registry program \u2014 NOT through TOON ILP payment channels. Fund this wallet with `rig fund --chain solana` (or send $ARIO to the Solana address above).";
5210
+ var DVM_PAYMENT_NOTE = "Brokered buy (--via): the DVM pays the mARIO name price from ITS Solana wallet via the kind:5095 job; this wallet needs only dust SOL to spawn the ANT it owns. On the paid path the job fee is enforced by the connector payment proxy in front of the DVM; when --via targets the DVM backend directly the job payment is STUBBED (dev/e2e only). The ArNS purchase itself is real either way, and the name is owned by YOUR ANT from inception.";
5211
+ var MIN_ARIO_SDK_VERSION = "4.0.3";
4930
5212
  var ArnsSdkUnavailableError = class extends Error {
4931
- constructor(cause) {
5213
+ constructor(cause, module = "@ar.io/sdk") {
4932
5214
  super(
4933
- "`rig name` needs the optional `@ar.io/sdk` dependency, which is not installed. It is intentionally NOT a base `rig` dependency (keeps the install lean). Add it to use ArNS naming:\n npm i @ar.io/sdk # or: pnpm add @ar.io/sdk\nthen re-run your `rig name` command." + (cause instanceof Error ? `
5215
+ `\`rig name\` needs the optional \`${module}\` dependency, which is not installed. It is intentionally NOT a base \`rig\` dependency (keeps the install lean). Add it to use ArNS naming:
5216
+ npm i @ar.io/sdk@^${MIN_ARIO_SDK_VERSION} # or: pnpm add @ar.io/sdk@^${MIN_ARIO_SDK_VERSION}
5217
+ (\`@solana/kit\` installs alongside it) then re-run your \`rig name\` command.` + (cause instanceof Error ? `
4934
5218
  (underlying error: ${cause.message})` : "")
4935
5219
  );
4936
5220
  this.name = "ArnsSdkUnavailableError";
4937
5221
  }
4938
5222
  };
4939
- var ARIO_MAINNET_PROCESS_ID = "qNvAoz0TgcH7DMg8BCVn8jF32QH5L6T29VjHxhHqqGE";
5223
+ var ArnsSdkIncompatibleError = class extends Error {
5224
+ constructor(detail) {
5225
+ super(
5226
+ `the installed @ar.io/sdk is incompatible with \`rig name\`: ${detail}.
5227
+ \`rig name\` requires @ar.io/sdk >= ${MIN_ARIO_SDK_VERSION} (the Solana-native release driven via a \`@solana/kit\` rpc + signer). Upgrade with:
5228
+ npm i @ar.io/sdk@latest # or: pnpm add @ar.io/sdk@latest`
5229
+ );
5230
+ this.name = "ArnsSdkIncompatibleError";
5231
+ }
5232
+ };
5233
+ var SOLANA_MAINNET_RPC_URL = "https://api.mainnet-beta.solana.com";
5234
+ var SOLANA_DEVNET_RPC_URL = "https://api.devnet.solana.com";
4940
5235
  var defaultLoadArns = async (options) => {
4941
- const specifier = "@ar.io/sdk";
5236
+ const sdkSpecifier = "@ar.io/sdk";
4942
5237
  let mod;
4943
5238
  try {
4944
- mod = await import(specifier);
5239
+ mod = await import(sdkSpecifier);
4945
5240
  } catch (err) {
4946
5241
  throw new ArnsSdkUnavailableError(err);
4947
5242
  }
4948
- if (!mod.ARIO?.init || !mod.ANT?.init) {
4949
- throw new ArnsSdkUnavailableError(
4950
- new Error("the installed @ar.io/sdk does not expose ARIO.init / ANT.init")
4951
- );
4952
- }
4953
- const processId = options.processId ?? (options.network === "devnet" ? mod.ARIO_DEVNET_PROCESS_ID ?? "" : options.network === "testnet" ? mod.ARIO_TESTNET_PROCESS_ID ?? "" : mod.ARIO_MAINNET_PROCESS_ID ?? ARIO_MAINNET_PROCESS_ID);
4954
- if (!processId) {
4955
- throw new ArnsSdkUnavailableError(
4956
- new Error(
4957
- `the installed @ar.io/sdk exposes no ${options.network} registry process id \u2014 pass --process-id explicitly`
4958
- )
5243
+ const kitSpecifier = "@solana/kit";
5244
+ let kit;
5245
+ try {
5246
+ kit = await import(kitSpecifier);
5247
+ } catch (err) {
5248
+ throw new ArnsSdkUnavailableError(err, "@solana/kit");
5249
+ }
5250
+ const arioInit = mod.ARIO?.init;
5251
+ const antInit = mod.ANT?.init;
5252
+ if (!arioInit || !antInit) {
5253
+ throw new ArnsSdkIncompatibleError("it exposes no ARIO.init / ANT.init");
5254
+ }
5255
+ const {
5256
+ createSolanaRpc,
5257
+ createSolanaRpcSubscriptions,
5258
+ createKeyPairSignerFromBytes
5259
+ } = kit;
5260
+ if (!createSolanaRpc || !createSolanaRpcSubscriptions || !createKeyPairSignerFromBytes) {
5261
+ throw new ArnsSdkIncompatibleError(
5262
+ "the resolved @solana/kit exposes no createSolanaRpc / createSolanaRpcSubscriptions / createKeyPairSignerFromBytes"
4959
5263
  );
4960
5264
  }
4961
- if (!mod.SolanaSigner) {
4962
- throw new ArnsSdkUnavailableError(
4963
- new Error(
4964
- "the installed @ar.io/sdk exposes no SolanaSigner \u2014 this build predates ar.io's Solana migration; upgrade @ar.io/sdk"
4965
- )
5265
+ const rpcUrl = options.network === "devnet" ? mod.DEVNET_RPC_URL ?? SOLANA_DEVNET_RPC_URL : mod.DEFAULT_SOLANA_RPC_URL ?? mod.MAINNET_RPC_URL ?? SOLANA_MAINNET_RPC_URL;
5266
+ const devnetIds = options.network === "devnet" ? mod.DEVNET_PROGRAM_IDS : void 0;
5267
+ if (options.network === "devnet" && devnetIds === void 0) {
5268
+ throw new ArnsSdkIncompatibleError(
5269
+ "it exposes no DEVNET_PROGRAM_IDS \u2014 devnet targeting needs the SDK's staging program ids"
4966
5270
  );
4967
5271
  }
4968
- const signer = new mod.SolanaSigner(options.solanaSecretKey);
4969
- const ario = mod.ARIO.init({ process: processId, signer });
4970
- const antInit = mod.ANT.init;
5272
+ const antProgramId = devnetIds?.ant;
5273
+ const programOverrides = {
5274
+ ...devnetIds?.core !== void 0 ? { coreProgramId: devnetIds.core } : {},
5275
+ ...devnetIds?.gar !== void 0 ? { garProgramId: devnetIds.gar } : {},
5276
+ ...devnetIds?.arns !== void 0 ? { arnsProgramId: devnetIds.arns } : {},
5277
+ ...antProgramId !== void 0 ? { antProgramId } : {},
5278
+ // --process-id / RIG_ARIO_PROCESS_ID: explicit ArNS registry program
5279
+ // override (wins over the cluster default).
5280
+ ...options.processId !== void 0 ? { arnsProgramId: options.processId } : {}
5281
+ };
5282
+ const rpc = createSolanaRpc(rpcUrl);
5283
+ const writeExtras = options.mode === "write" ? {
5284
+ signer: await createKeyPairSignerFromBytes(options.solanaSecretKey),
5285
+ rpcSubscriptions: createSolanaRpcSubscriptions(
5286
+ rpcUrl.replace(/^http/, "ws")
5287
+ )
5288
+ } : {};
5289
+ const ario = arioInit({ rpc, ...writeExtras, ...programOverrides });
4971
5290
  return {
4972
5291
  getTokenCost: async (args) => BigInt(String(await ario.getTokenCost(args))),
4973
- buyRecord: async (args) => await ario.buyRecord(args),
4974
- getArNSRecord: async (args) => await ario.getArNSRecord(args),
5292
+ buyRecord: async (args) => {
5293
+ if (typeof ario.buyRecord !== "function") {
5294
+ throw new ArnsSdkIncompatibleError(
5295
+ "the signed ARIO client exposes no buyRecord"
5296
+ );
5297
+ }
5298
+ return await ario.buyRecord(args);
5299
+ },
5300
+ spawnAnt: async (args) => {
5301
+ const spawn2 = mod.ANT?.spawn;
5302
+ if (typeof spawn2 !== "function") {
5303
+ throw new ArnsSdkIncompatibleError("it exposes no ANT.spawn");
5304
+ }
5305
+ if (options.mode !== "write") {
5306
+ throw new ArnsSdkIncompatibleError(
5307
+ "ANT.spawn requires the write-mode SDK (a signer) \u2014 this client was built signerless"
5308
+ );
5309
+ }
5310
+ const result = await spawn2({
5311
+ rpc,
5312
+ ...writeExtras,
5313
+ state: { name: args.name },
5314
+ ...antProgramId !== void 0 ? { antProgramId } : {}
5315
+ });
5316
+ return { processId: result.processId, signature: result.signature };
5317
+ },
5318
+ buildSetRecordTransaction: async (args) => {
5319
+ if (options.mode !== "write") {
5320
+ throw new ArnsSdkIncompatibleError(
5321
+ "buildSetRecordTransaction requires the write-mode SDK (a client partial-signature) \u2014 this client was built signerless"
5322
+ );
5323
+ }
5324
+ const contractsSpecifier = "@ar.io/solana-contracts/ant";
5325
+ let contracts;
5326
+ try {
5327
+ contracts = await import(contractsSpecifier);
5328
+ } catch (err) {
5329
+ throw new ArnsSdkUnavailableError(err, "@ar.io/solana-contracts");
5330
+ }
5331
+ const getSetRecordInstructionAsync = contracts.getSetRecordInstructionAsync;
5332
+ const getAntRecordPDA = mod.getAntRecordPDA;
5333
+ const {
5334
+ createKeyPairFromBytes,
5335
+ createNoopSigner,
5336
+ address: toAddr,
5337
+ blockhash: toBlockhash,
5338
+ createTransactionMessage,
5339
+ setTransactionMessageFeePayer,
5340
+ setTransactionMessageLifetimeUsingBlockhash,
5341
+ appendTransactionMessageInstructions,
5342
+ compileTransaction,
5343
+ partiallySignTransaction,
5344
+ getBase64EncodedWireTransaction
5345
+ } = kit;
5346
+ if (!getSetRecordInstructionAsync || !getAntRecordPDA || !createKeyPairFromBytes || !createNoopSigner || !toAddr || !toBlockhash || !createTransactionMessage || !setTransactionMessageFeePayer || !setTransactionMessageLifetimeUsingBlockhash || !appendTransactionMessageInstructions || !compileTransaction || !partiallySignTransaction || !getBase64EncodedWireTransaction) {
5347
+ throw new ArnsSdkIncompatibleError(
5348
+ "the installed @ar.io/sdk / @ar.io/solana-contracts / @solana/kit do not expose the record-set transaction builder surface"
5349
+ );
5350
+ }
5351
+ const asset = toAddr(args.antProcessId);
5352
+ const antProgram = antProgramId !== void 0 ? toAddr(antProgramId) : void 0;
5353
+ const [recordPda] = await getAntRecordPDA(
5354
+ asset,
5355
+ args.undername,
5356
+ antProgram
5357
+ );
5358
+ const ix = await getSetRecordInstructionAsync(
5359
+ {
5360
+ asset,
5361
+ record: recordPda,
5362
+ caller: createNoopSigner(toAddr(options.solanaPublicKey)),
5363
+ undername: args.undername,
5364
+ target: args.transactionId,
5365
+ targetProtocol: 0,
5366
+ ttlSeconds: args.ttlSeconds,
5367
+ priority: null,
5368
+ recordOwner: null
5369
+ },
5370
+ antProgram !== void 0 ? { programAddress: antProgram } : void 0
5371
+ );
5372
+ let message = createTransactionMessage({ version: 0 });
5373
+ message = setTransactionMessageFeePayer(toAddr(args.feePayer), message);
5374
+ message = setTransactionMessageLifetimeUsingBlockhash(
5375
+ {
5376
+ blockhash: toBlockhash(args.recentBlockhash),
5377
+ lastValidBlockHeight: 0n
5378
+ },
5379
+ message
5380
+ );
5381
+ message = appendTransactionMessageInstructions([ix], message);
5382
+ const keyPair = await createKeyPairFromBytes(options.solanaSecretKey);
5383
+ const signed = await partiallySignTransaction(
5384
+ [keyPair],
5385
+ compileTransaction(message)
5386
+ );
5387
+ return getBase64EncodedWireTransaction(signed);
5388
+ },
5389
+ getArNSRecord: async (args) => {
5390
+ let raw;
5391
+ try {
5392
+ raw = await ario.getArNSRecord(args);
5393
+ } catch (err) {
5394
+ if (err instanceof Error && /not found/i.test(err.message)) return null;
5395
+ throw err;
5396
+ }
5397
+ return {
5398
+ processId: raw.processId ?? null,
5399
+ type: raw.type ?? null,
5400
+ ...raw.startTimestamp !== void 0 ? { startTimestamp: raw.startTimestamp * 1e3 } : {},
5401
+ ...raw.endTimestamp !== void 0 ? { endTimestamp: raw.endTimestamp * 1e3 } : {},
5402
+ ...raw.undernameLimit !== void 0 ? { undernameLimit: raw.undernameLimit } : {}
5403
+ };
5404
+ },
4975
5405
  ant: async (pid) => {
4976
- const ant = antInit({ processId: pid, signer });
5406
+ const ant = await antInit({
5407
+ processId: pid,
5408
+ rpc,
5409
+ ...writeExtras,
5410
+ ...antProgramId !== void 0 ? { antProgramId } : {}
5411
+ });
4977
5412
  return {
4978
- getRecords: () => ant.getRecords(),
4979
- setBaseNameRecord: (a) => ant.setBaseNameRecord(a),
4980
- setUndernameRecord: (a) => ant.setUndernameRecord(a)
5413
+ getRecords: async () => {
5414
+ const records = await ant.getRecords();
5415
+ const targets = {};
5416
+ for (const [undername, record] of Object.entries(records)) {
5417
+ targets[undername] = {
5418
+ transactionId: record.transactionId,
5419
+ ttlSeconds: record.ttlSeconds
5420
+ };
5421
+ }
5422
+ return targets;
5423
+ },
5424
+ setBaseNameRecord: async (a) => {
5425
+ if (typeof ant.setBaseNameRecord !== "function") {
5426
+ throw new ArnsSdkIncompatibleError(
5427
+ "the signed ANT client exposes no setBaseNameRecord"
5428
+ );
5429
+ }
5430
+ return ant.setBaseNameRecord(a);
5431
+ },
5432
+ setUndernameRecord: async (a) => {
5433
+ if (typeof ant.setUndernameRecord !== "function") {
5434
+ throw new ArnsSdkIncompatibleError(
5435
+ "the signed ANT client exposes no setUndernameRecord"
5436
+ );
5437
+ }
5438
+ return ant.setUndernameRecord(a);
5439
+ }
4981
5440
  };
4982
5441
  }
4983
5442
  };
4984
5443
  };
5444
+ var ARNS_BUY_JOB_KIND = 5095;
5445
+ var DvmBuyJobError = class extends Error {
5446
+ constructor(message) {
5447
+ super(message);
5448
+ this.name = "DvmBuyJobError";
5449
+ }
5450
+ };
5451
+ var defaultSubmitDvmBuyJob = async (request) => {
5452
+ const { finalizeEvent } = await import("nostr-tools/pure");
5453
+ const event = finalizeEvent(
5454
+ {
5455
+ kind: ARNS_BUY_JOB_KIND,
5456
+ created_at: Math.floor(Date.now() / 1e3),
5457
+ content: "",
5458
+ tags: [
5459
+ ["param", "name", request.name],
5460
+ ["param", "type", request.type],
5461
+ ...request.years !== null ? [["param", "years", String(request.years)]] : [],
5462
+ ["param", "processId", request.processId]
5463
+ ]
5464
+ },
5465
+ request.nostrSecretKey
5466
+ );
5467
+ const url = `${request.viaUrl.replace(/\/+$/, "")}/store`;
5468
+ let response;
5469
+ try {
5470
+ response = await fetch(url, {
5471
+ method: "POST",
5472
+ headers: { "content-type": "application/json" },
5473
+ body: JSON.stringify({ event })
5474
+ });
5475
+ } catch (err) {
5476
+ throw new DvmBuyJobError(
5477
+ `could not reach the DVM at ${url}: ${err instanceof Error ? err.message : String(err)}`
5478
+ );
5479
+ }
5480
+ let body;
5481
+ try {
5482
+ body = await response.json();
5483
+ } catch {
5484
+ throw new DvmBuyJobError(
5485
+ `the DVM at ${url} returned a non-JSON response (HTTP ${response.status})`
5486
+ );
5487
+ }
5488
+ if (!response.ok || body.accept !== true) {
5489
+ throw new DvmBuyJobError(
5490
+ `the DVM rejected the kind:${ARNS_BUY_JOB_KIND} buy job (HTTP ${response.status}${body.code ? `, ${body.code}` : ""})${body.message ? `: ${body.message}` : ""}`
5491
+ );
5492
+ }
5493
+ const registryTxId = body.result?.registryTxId;
5494
+ if (typeof registryTxId !== "string" || registryTxId.length === 0) {
5495
+ throw new DvmBuyJobError(
5496
+ `the DVM accepted the job but returned no registryTxId \u2014 response: ` + JSON.stringify(body.result ?? null)
5497
+ );
5498
+ }
5499
+ return {
5500
+ registryTxId,
5501
+ quotedMario: typeof body.result?.quotedMario === "string" ? body.result.quotedMario : null,
5502
+ syncAttributesTxId: typeof body.result?.syncAttributesTxId === "string" ? body.result.syncAttributesTxId : null
5503
+ };
5504
+ };
5505
+ var GAS_STATION_JOB_KIND = 5096;
5506
+ var GasStationJobError = class extends Error {
5507
+ constructor(reason, message) {
5508
+ super(message);
5509
+ this.reason = reason;
5510
+ this.name = "GasStationJobError";
5511
+ }
5512
+ reason;
5513
+ };
5514
+ async function postGasStationJob(viaUrl, params, nostrSecretKey) {
5515
+ const { finalizeEvent } = await import("nostr-tools/pure");
5516
+ const event = finalizeEvent(
5517
+ {
5518
+ kind: GAS_STATION_JOB_KIND,
5519
+ created_at: Math.floor(Date.now() / 1e3),
5520
+ content: "",
5521
+ tags: params.map(([k, v]) => ["param", k, v])
5522
+ },
5523
+ nostrSecretKey
5524
+ );
5525
+ const url = `${viaUrl.replace(/\/+$/, "")}/store`;
5526
+ let response;
5527
+ try {
5528
+ response = await fetch(url, {
5529
+ method: "POST",
5530
+ headers: { "content-type": "application/json" },
5531
+ body: JSON.stringify({ event })
5532
+ });
5533
+ } catch (err) {
5534
+ throw new GasStationJobError(
5535
+ "unreachable",
5536
+ `could not reach the DVM at ${url}: ${err instanceof Error ? err.message : String(err)}`
5537
+ );
5538
+ }
5539
+ let body;
5540
+ try {
5541
+ body = await response.json();
5542
+ } catch {
5543
+ throw new GasStationJobError(
5544
+ "bad_response",
5545
+ `the DVM at ${url} returned a non-JSON response (HTTP ${response.status})`
5546
+ );
5547
+ }
5548
+ if (!response.ok || body.accept !== true || !body.result) {
5549
+ throw new GasStationJobError(
5550
+ String(body.code ?? "rejected"),
5551
+ `the DVM rejected the kind:${GAS_STATION_JOB_KIND} job (HTTP ${response.status}${body.code ? `, ${body.code}` : ""})${body.message ? `: ${body.message}` : ""}`
5552
+ );
5553
+ }
5554
+ const result = body.result;
5555
+ if (result["status"] === "failed") {
5556
+ throw new GasStationJobError(
5557
+ String(result["reason"] ?? "failed"),
5558
+ `gas-station job failed: ${String(result["reason"] ?? "unknown")} \u2014 ${String(result["detail"] ?? "")}`
5559
+ );
5560
+ }
5561
+ return result;
5562
+ }
5563
+ var defaultGasStationClient = {
5564
+ quote: async (args) => {
5565
+ const result = await postGasStationJob(
5566
+ args.viaUrl,
5567
+ [
5568
+ ["phase", "quote"],
5569
+ ...args.transaction !== void 0 ? [["transaction", args.transaction]] : []
5570
+ ],
5571
+ args.nostrSecretKey
5572
+ );
5573
+ const { quoteId, feePayer, maxLamports, recentBlockhash, expiresAt } = result;
5574
+ if (typeof quoteId !== "string" || typeof feePayer !== "string" || typeof maxLamports !== "string" || typeof recentBlockhash !== "string" || typeof expiresAt !== "number") {
5575
+ throw new GasStationJobError(
5576
+ "bad_response",
5577
+ `the DVM quote is missing fields: ${JSON.stringify(result)}`
5578
+ );
5579
+ }
5580
+ return { quoteId, feePayer, maxLamports, recentBlockhash, expiresAt };
5581
+ },
5582
+ execute: async (args) => {
5583
+ const result = await postGasStationJob(
5584
+ args.viaUrl,
5585
+ [
5586
+ ["phase", "execute"],
5587
+ ["transaction", args.transaction],
5588
+ ["quoteId", args.quoteId],
5589
+ ["idempotencyKey", args.idempotencyKey]
5590
+ ],
5591
+ args.nostrSecretKey
5592
+ );
5593
+ const signature = result["signature"];
5594
+ if (typeof signature !== "string" || signature.length === 0) {
5595
+ throw new GasStationJobError(
5596
+ "bad_response",
5597
+ `the DVM execute result carries no signature: ${JSON.stringify(result)}`
5598
+ );
5599
+ }
5600
+ return {
5601
+ signature,
5602
+ slot: typeof result["slot"] === "string" ? result["slot"] : null,
5603
+ feeLamportsActual: typeof result["feeLamportsActual"] === "string" ? result["feeLamportsActual"] : null,
5604
+ ...result["replayed"] === true ? { replayed: true } : {}
5605
+ };
5606
+ }
5607
+ };
4985
5608
  var NAME_USAGE = `Usage: rig name <buy|set|status> <name> [options]
4986
5609
 
4987
5610
  ArNS naming for Arweave-served artifacts. A name registered on the ar.io
@@ -4994,14 +5617,21 @@ mnemonic that pays for pushes (derived at m/44'/501'/0'/0'). Fund it with
4994
5617
  registry program \u2014 NOT through TOON ILP payment channels.
4995
5618
 
4996
5619
  Commands:
4997
- name buy <name> [--years n | --permabuy]
5620
+ name buy <name> [--years n | --permabuy] [--via <dvm-url>]
4998
5621
  quote (mARIO) \u2192 confirm \u2192 register. The spawned ANT is
4999
5622
  owned by this identity's Solana key. Default: 1-year
5000
5623
  lease. PAID \u2014 spends mARIO from the Solana wallet.
5001
- name set <name> <txId> [--undername <sub>] [--ttl <seconds>]
5624
+ With --via: BROKERED \u2014 this identity spawns and owns
5625
+ the ANT (dust SOL only), then a store DVM executes the
5626
+ kind:5095 buy job and pays the mARIO from ITS wallet;
5627
+ the name is owned by your ANT from inception.
5628
+ name set <name> <txId> [--undername <sub>] [--ttl <seconds>] [--via <dvm-url>]
5002
5629
  point the name (or an undername) at an Arweave txId
5003
5630
  (typically a deployed path manifest). Signs an ANT
5004
- record update with the Solana key.
5631
+ record update with the Solana key. With --via: the
5632
+ gas-station path (kind:5096) \u2014 you author and
5633
+ partial-sign, the DVM co-signs as fee payer and pays
5634
+ the lamports; needs ZERO SOL in this wallet.
5005
5635
  name status <name> FREE: registry record (lease/permabuy, expiry), ANT
5006
5636
  process id, current target txId(s), TTL, undernames.
5007
5637
 
@@ -5012,11 +5642,18 @@ Options:
5012
5642
  --undername <sub> target the undername <sub>_<name> instead of the base
5013
5643
  name (set)
5014
5644
  --ttl <seconds> record TTL in seconds (set; default 3600)
5015
- --network <net> registry: mainnet | devnet | testnet (default mainnet;
5016
- or RIG_ARIO_NETWORK). Non-mainnet gateway resolution is
5645
+ --network <net> cluster: mainnet | devnet (default mainnet; or
5646
+ RIG_ARIO_NETWORK). ar.io has no Solana-testnet
5647
+ deployment. Non-mainnet gateway resolution is
5017
5648
  unverified.
5018
- --process-id <id> explicit registry process id (overrides --network; or
5019
- RIG_ARIO_PROCESS_ID)
5649
+ --process-id <id> explicit ArNS registry program id \u2014 a Solana program
5650
+ address (overrides --network; or RIG_ARIO_PROCESS_ID)
5651
+ --via <dvm-url> buy only: broker the purchase through a store DVM's
5652
+ kind:5095 job endpoint (or RIG_ARNS_DVM_URL). The DVM
5653
+ pays the mARIO; you own the name via your spawned ANT.
5654
+ Direct backend targeting stubs the job payment
5655
+ (dev/e2e) \u2014 the paid path runs the same job through
5656
+ the connector payment proxy.
5020
5657
  --yes skip the confirmation (required when not a TTY) for
5021
5658
  buy/set
5022
5659
  --json machine-readable envelope; for buy/set without --yes it
@@ -5024,8 +5661,8 @@ Options:
5024
5661
  -h, --help show this help`;
5025
5662
  var DEFAULT_TTL_SECONDS = 3600;
5026
5663
  var DEFAULT_LEASE_YEARS = 1;
5027
- var NETWORKS = ["mainnet", "devnet", "testnet"];
5028
- async function resolveNameContext(deps, network, processId) {
5664
+ var NETWORKS = ["mainnet", "devnet"];
5665
+ async function resolveNameContext(deps, network, processId, mode) {
5029
5666
  const { io: io2, env } = deps;
5030
5667
  const resolved = await resolveIdentity({
5031
5668
  env,
@@ -5049,12 +5686,17 @@ async function resolveNameContext(deps, network, processId) {
5049
5686
  );
5050
5687
  }
5051
5688
  const sdk = await (deps.loadArns ?? defaultLoadArns)({
5689
+ mode,
5052
5690
  solanaSecretKey: derived.solana.secretKey,
5053
5691
  solanaPublicKey: solanaAddress,
5054
5692
  network,
5055
5693
  ...processId !== void 0 ? { processId } : {}
5056
5694
  });
5057
- return { identity, solanaAddress, network, processId, sdk };
5695
+ const nostrSecretKey = client.deriveNostrKeyFromMnemonic(
5696
+ resolved.mnemonic,
5697
+ resolved.accountIndex
5698
+ ).secretKey;
5699
+ return { identity, solanaAddress, network, processId, sdk, nostrSecretKey };
5058
5700
  }
5059
5701
  function parseNameFlags(args, env) {
5060
5702
  const { values, positionals } = parseArgs13({
@@ -5067,19 +5709,31 @@ function parseNameFlags(args, env) {
5067
5709
  ttl: { type: "string" },
5068
5710
  network: { type: "string" },
5069
5711
  "process-id": { type: "string" },
5712
+ via: { type: "string" },
5070
5713
  yes: { type: "boolean", default: false },
5071
5714
  json: { type: "boolean", default: false },
5072
5715
  help: { type: "boolean", short: "h", default: false }
5073
5716
  }
5074
5717
  });
5075
5718
  if (values.help) {
5076
- return { positionals: ["--help"], flags: { permabuy: false, ttl: 0, network: "mainnet", yes: false, json: false } };
5719
+ return {
5720
+ positionals: ["--help"],
5721
+ flags: {
5722
+ permabuy: false,
5723
+ ttl: 0,
5724
+ network: "mainnet",
5725
+ yes: false,
5726
+ json: false
5727
+ }
5728
+ };
5077
5729
  }
5078
5730
  let years;
5079
5731
  if (values.years !== void 0) {
5080
5732
  years = Number(values.years);
5081
5733
  if (!Number.isInteger(years) || years <= 0) {
5082
- throw new Error(`--years must be a positive integer, got ${JSON.stringify(values.years)}`);
5734
+ throw new Error(
5735
+ `--years must be a positive integer, got ${JSON.stringify(values.years)}`
5736
+ );
5083
5737
  }
5084
5738
  }
5085
5739
  if (years !== void 0 && values.permabuy) {
@@ -5089,14 +5743,28 @@ function parseNameFlags(args, env) {
5089
5743
  if (values.ttl !== void 0) {
5090
5744
  ttl = Number(values.ttl);
5091
5745
  if (!Number.isInteger(ttl) || ttl <= 0) {
5092
- throw new Error(`--ttl must be a positive integer number of seconds, got ${JSON.stringify(values.ttl)}`);
5746
+ throw new Error(
5747
+ `--ttl must be a positive integer number of seconds, got ${JSON.stringify(values.ttl)}`
5748
+ );
5093
5749
  }
5094
5750
  }
5095
5751
  const networkRaw = values.network ?? env["RIG_ARIO_NETWORK"] ?? "mainnet";
5096
5752
  if (!NETWORKS.includes(networkRaw)) {
5097
- throw new Error(`--network must be one of ${NETWORKS.join(" | ")}, got ${JSON.stringify(networkRaw)}`);
5753
+ throw new Error(
5754
+ `--network must be one of ${NETWORKS.join(" | ")}, got ${JSON.stringify(networkRaw)}`
5755
+ );
5098
5756
  }
5099
5757
  const processId = values["process-id"] ?? env["RIG_ARIO_PROCESS_ID"];
5758
+ const viaRaw = values.via ?? env["RIG_ARNS_DVM_URL"];
5759
+ let via;
5760
+ if (viaRaw !== void 0) {
5761
+ if (!/^https?:\/\/.+/.test(viaRaw)) {
5762
+ throw new Error(
5763
+ `--via must be an http(s) DVM endpoint URL, got ${JSON.stringify(viaRaw)}`
5764
+ );
5765
+ }
5766
+ via = viaRaw;
5767
+ }
5100
5768
  return {
5101
5769
  positionals,
5102
5770
  flags: {
@@ -5106,6 +5774,7 @@ function parseNameFlags(args, env) {
5106
5774
  ttl,
5107
5775
  network: networkRaw,
5108
5776
  ...processId !== void 0 ? { processId } : {},
5777
+ ...via !== void 0 ? { via } : {},
5109
5778
  yes: values.yes ?? false,
5110
5779
  json: values.json ?? false
5111
5780
  }
@@ -5115,7 +5784,12 @@ async function runBuy(name, flags, deps) {
5115
5784
  const { io: io2 } = deps;
5116
5785
  const type = flags.permabuy ? "permabuy" : "lease";
5117
5786
  const years = type === "lease" ? flags.years ?? DEFAULT_LEASE_YEARS : null;
5118
- const ctx = await resolveNameContext(deps, flags.network, flags.processId);
5787
+ const ctx = await resolveNameContext(
5788
+ deps,
5789
+ flags.network,
5790
+ flags.processId,
5791
+ "read"
5792
+ );
5119
5793
  const mario = await ctx.sdk.getTokenCost({
5120
5794
  intent: "Buy-Name",
5121
5795
  name,
@@ -5126,6 +5800,8 @@ async function runBuy(name, flags, deps) {
5126
5800
  mARIO: mario.toString(),
5127
5801
  ARIO: formatUnits(mario.toString(), ARIO_DECIMALS)
5128
5802
  };
5803
+ const via = flags.via ?? null;
5804
+ const paymentNote = via === null ? MARIO_PAYMENT_NOTE : DVM_PAYMENT_NOTE;
5129
5805
  const buildJson = (executed, extra = {}) => ({
5130
5806
  command: "name",
5131
5807
  action: "buy",
@@ -5134,46 +5810,100 @@ async function runBuy(name, flags, deps) {
5134
5810
  years,
5135
5811
  network: ctx.network,
5136
5812
  processId: ctx.processId ?? null,
5813
+ via,
5137
5814
  identity: ctx.identity,
5138
5815
  solanaAddress: ctx.solanaAddress,
5139
5816
  quote,
5140
- payment: MARIO_PAYMENT_NOTE,
5817
+ payment: paymentNote,
5141
5818
  executed,
5142
5819
  ...extra
5143
5820
  });
5144
5821
  if (!flags.json) {
5145
5822
  io2.out(
5146
- `Buy ArNS name "${name}" \u2014 ${type}` + (years !== null ? ` (${years} year${years === 1 ? "" : "s"})` : "") + ` on ${ctx.network}`
5823
+ `Buy ArNS name "${name}" \u2014 ${type}` + (years !== null ? ` (${years} year${years === 1 ? "" : "s"})` : "") + ` on ${ctx.network}` + (via !== null ? ` \u2014 brokered via DVM ${via}` : "")
5824
+ );
5825
+ io2.out(
5826
+ via === null ? ` Cost: ${renderMario(mario)}` : ` Name cost (paid by the DVM): ${renderMario(mario)}`
5147
5827
  );
5148
- io2.out(` Cost: ${renderMario(mario)}`);
5149
5828
  io2.out(` Solana wallet: ${ctx.solanaAddress}`);
5150
- io2.out(` ${MARIO_PAYMENT_NOTE}`);
5829
+ io2.out(` ${paymentNote}`);
5151
5830
  io2.out(renderIdentityLine(ctx.identity));
5152
5831
  }
5153
5832
  if (!flags.yes) {
5154
5833
  if (flags.json) {
5155
5834
  io2.emitJson(
5156
5835
  buildJson(false, {
5157
- hint: "estimate only \u2014 re-run with --yes to buy (spends mARIO on Solana, non-refundable)"
5836
+ hint: via === null ? "estimate only \u2014 re-run with --yes to buy (spends mARIO on Solana, non-refundable)" : "estimate only \u2014 re-run with --yes to spawn your ANT and submit the kind:5095 buy job to the DVM"
5158
5837
  })
5159
5838
  );
5160
5839
  return 0;
5161
5840
  }
5162
5841
  if (!io2.isInteractive) {
5163
5842
  io2.err(
5164
- "refusing to spend mARIO without confirmation in a non-interactive session \u2014 re-run with --yes (or use --json for a pure estimate)"
5843
+ via === null ? "refusing to spend mARIO without confirmation in a non-interactive session \u2014 re-run with --yes (or use --json for a pure estimate)" : "refusing to spawn an ANT and submit a paid DVM job without confirmation in a non-interactive session \u2014 re-run with --yes (or use --json for a pure estimate)"
5165
5844
  );
5166
5845
  return 1;
5167
5846
  }
5168
5847
  const proceed = await io2.confirm(
5169
- `Proceed \u2014 buy "${name}" for ${renderMario(mario)} from your Solana wallet? [y/N] `
5848
+ via === null ? `Proceed \u2014 buy "${name}" for ${renderMario(mario)} from your Solana wallet? [y/N] ` : `Proceed \u2014 spawn your ANT and have the DVM at ${via} buy "${name}" for ${renderMario(mario)} from ITS wallet? [y/N] `
5170
5849
  );
5171
5850
  if (!proceed) {
5172
5851
  io2.err("aborted \u2014 nothing was bought or paid.");
5173
5852
  return 1;
5174
5853
  }
5175
5854
  }
5176
- const receipt = await ctx.sdk.buyRecord({
5855
+ const signed = await resolveNameContext(
5856
+ deps,
5857
+ flags.network,
5858
+ flags.processId,
5859
+ "write"
5860
+ );
5861
+ if (via !== null) {
5862
+ const spawn2 = await signed.sdk.spawnAnt({ name });
5863
+ if (!flags.json) {
5864
+ io2.out(`Spawned ANT ${spawn2.processId} (tx ${spawn2.signature})`);
5865
+ }
5866
+ const submit = deps.submitDvmBuyJob ?? defaultSubmitDvmBuyJob;
5867
+ const receipt2 = await submit({
5868
+ viaUrl: via,
5869
+ name,
5870
+ type,
5871
+ years,
5872
+ processId: spawn2.processId,
5873
+ nostrSecretKey: signed.nostrSecretKey
5874
+ });
5875
+ const result2 = {
5876
+ registryTxId: receipt2.registryTxId,
5877
+ antProcessId: spawn2.processId,
5878
+ syncAttributesTxId: receipt2.syncAttributesTxId,
5879
+ dvmQuotedMario: receipt2.quotedMario
5880
+ };
5881
+ if (flags.json) {
5882
+ io2.emitJson(
5883
+ buildJson(true, {
5884
+ spawn: { processId: spawn2.processId, signature: spawn2.signature },
5885
+ result: result2
5886
+ })
5887
+ );
5888
+ } else {
5889
+ io2.out(
5890
+ `Registered "${name}" via the DVM \u2014 registry tx ${result2.registryTxId}`
5891
+ );
5892
+ io2.out(` ANT process (owned by YOU): ${spawn2.processId}`);
5893
+ if (receipt2.syncAttributesTxId) {
5894
+ io2.out(` Trait sync: ${receipt2.syncAttributesTxId}`);
5895
+ } else {
5896
+ io2.out(
5897
+ " Trait sync: not run by the DVM (holder-gated on this deployment) \u2014 as the ANT holder you can reconcile with syncAttributes later; resolution works regardless."
5898
+ );
5899
+ }
5900
+ io2.out(
5901
+ `Point it at content with \`rig name set ${name} <txId>\`, then it resolves at https://${name}.<gateway>/`
5902
+ );
5903
+ }
5904
+ return 0;
5905
+ }
5906
+ const receipt = await signed.sdk.buyRecord({
5177
5907
  name,
5178
5908
  type,
5179
5909
  ...years !== null ? { years } : {}
@@ -5195,7 +5925,12 @@ async function runBuy(name, flags, deps) {
5195
5925
  }
5196
5926
  async function runSet(name, txId, flags, deps) {
5197
5927
  const { io: io2 } = deps;
5198
- const ctx = await resolveNameContext(deps, flags.network, flags.processId);
5928
+ const ctx = await resolveNameContext(
5929
+ deps,
5930
+ flags.network,
5931
+ flags.processId,
5932
+ "read"
5933
+ );
5199
5934
  const record = await ctx.sdk.getArNSRecord({ name });
5200
5935
  if (!record || !record.processId) {
5201
5936
  throw new Error(
@@ -5204,6 +5939,7 @@ async function runSet(name, txId, flags, deps) {
5204
5939
  }
5205
5940
  const antProcessId = record.processId;
5206
5941
  const undername = flags.undername ?? null;
5942
+ const via = flags.via ?? null;
5207
5943
  const buildJson = (executed, extra = {}) => ({
5208
5944
  command: "name",
5209
5945
  action: "set",
@@ -5213,6 +5949,7 @@ async function runSet(name, txId, flags, deps) {
5213
5949
  ttl: flags.ttl,
5214
5950
  network: ctx.network,
5215
5951
  processId: ctx.processId ?? null,
5952
+ via,
5216
5953
  identity: ctx.identity,
5217
5954
  solanaAddress: ctx.solanaAddress,
5218
5955
  antProcessId,
@@ -5221,16 +5958,23 @@ async function runSet(name, txId, flags, deps) {
5221
5958
  });
5222
5959
  const targetLabel = undername ? `undername "${undername}_${name}"` : `base name "${name}"`;
5223
5960
  if (!flags.json) {
5224
- io2.out(`Set ${targetLabel} \u2192 txId ${txId} (ttl ${flags.ttl}s) on ${ctx.network}`);
5961
+ io2.out(
5962
+ `Set ${targetLabel} \u2192 txId ${txId} (ttl ${flags.ttl}s) on ${ctx.network}` + (via !== null ? ` \u2014 gas paid by the DVM at ${via} (kind:5096)` : "")
5963
+ );
5225
5964
  io2.out(` ANT process: ${antProcessId}`);
5226
5965
  io2.out(` Signed by the identity's Solana key (${ctx.solanaAddress}).`);
5966
+ if (via !== null) {
5967
+ io2.out(
5968
+ " Gas station: you author + partial-sign the record update; the DVM co-signs as fee payer and pays the lamports (toon-meta#163). Direct backend targeting stubs the job payment (dev/e2e)."
5969
+ );
5970
+ }
5227
5971
  io2.out(renderIdentityLine(ctx.identity));
5228
5972
  }
5229
5973
  if (!flags.yes) {
5230
5974
  if (flags.json) {
5231
5975
  io2.emitJson(
5232
5976
  buildJson(false, {
5233
- hint: "estimate only \u2014 re-run with --yes to write the ANT record"
5977
+ hint: via === null ? "estimate only \u2014 re-run with --yes to write the ANT record" : "estimate only \u2014 re-run with --yes to quote the gas-station and write the ANT record (DVM pays the gas)"
5234
5978
  })
5235
5979
  );
5236
5980
  return 0;
@@ -5242,28 +5986,90 @@ async function runSet(name, txId, flags, deps) {
5242
5986
  return 1;
5243
5987
  }
5244
5988
  const proceed = await io2.confirm(
5245
- `Proceed \u2014 point ${targetLabel} at ${txId}? [y/N] `
5989
+ `Proceed \u2014 point ${targetLabel} at ${txId}?` + (via !== null ? " (the DVM pays the gas)" : "") + " [y/N] "
5246
5990
  );
5247
5991
  if (!proceed) {
5248
5992
  io2.err("aborted \u2014 the ANT record was not changed.");
5249
5993
  return 1;
5250
5994
  }
5251
5995
  }
5252
- const ant = await ctx.sdk.ant(antProcessId);
5253
- const receipt = undername ? await ant.setUndernameRecord({ undername, transactionId: txId, ttlSeconds: flags.ttl }) : await ant.setBaseNameRecord({ transactionId: txId, ttlSeconds: flags.ttl });
5996
+ const signed = await resolveNameContext(
5997
+ deps,
5998
+ flags.network,
5999
+ flags.processId,
6000
+ "write"
6001
+ );
6002
+ if (via !== null) {
6003
+ const gas = deps.gasStation ?? defaultGasStationClient;
6004
+ const quote = await gas.quote({
6005
+ viaUrl: via,
6006
+ nostrSecretKey: signed.nostrSecretKey
6007
+ });
6008
+ const wireTx = await signed.sdk.buildSetRecordTransaction({
6009
+ antProcessId,
6010
+ undername: undername ?? "@",
6011
+ transactionId: txId,
6012
+ ttlSeconds: flags.ttl,
6013
+ feePayer: quote.feePayer,
6014
+ recentBlockhash: quote.recentBlockhash
6015
+ });
6016
+ const executed = await gas.execute({
6017
+ viaUrl: via,
6018
+ transaction: wireTx,
6019
+ quoteId: quote.quoteId,
6020
+ idempotencyKey: randomUUID(),
6021
+ nostrSecretKey: signed.nostrSecretKey
6022
+ });
6023
+ const gasStation = {
6024
+ quoteId: quote.quoteId,
6025
+ feePayer: quote.feePayer,
6026
+ maxLamports: quote.maxLamports,
6027
+ expiresAt: quote.expiresAt,
6028
+ feeLamportsActual: executed.feeLamportsActual
6029
+ };
6030
+ const result2 = { messageId: executed.signature };
6031
+ if (flags.json) {
6032
+ io2.emitJson(buildJson(true, { gasStation, result: result2 }));
6033
+ } else {
6034
+ io2.out(
6035
+ `Updated \u2014 record-set tx ${executed.signature} (gas paid by ${quote.feePayer})`
6036
+ );
6037
+ const host = undername ? `${undername}_${name}` : name;
6038
+ io2.out(
6039
+ ` Resolves at https://${host}.<gateway>/ (allow for gateway cache/TTL).`
6040
+ );
6041
+ }
6042
+ return 0;
6043
+ }
6044
+ const ant = await signed.sdk.ant(antProcessId);
6045
+ const receipt = undername ? await ant.setUndernameRecord({
6046
+ undername,
6047
+ transactionId: txId,
6048
+ ttlSeconds: flags.ttl
6049
+ }) : await ant.setBaseNameRecord({
6050
+ transactionId: txId,
6051
+ ttlSeconds: flags.ttl
6052
+ });
5254
6053
  const result = { messageId: receipt.id };
5255
6054
  if (flags.json) {
5256
6055
  io2.emitJson(buildJson(true, { result }));
5257
6056
  } else {
5258
6057
  io2.out(`Updated \u2014 ANT message ${result.messageId}`);
5259
6058
  const host = undername ? `${undername}_${name}` : name;
5260
- io2.out(` Resolves at https://${host}.<gateway>/ (allow for gateway cache/TTL).`);
6059
+ io2.out(
6060
+ ` Resolves at https://${host}.<gateway>/ (allow for gateway cache/TTL).`
6061
+ );
5261
6062
  }
5262
6063
  return 0;
5263
6064
  }
5264
6065
  async function runStatus(name, flags, deps) {
5265
6066
  const { io: io2 } = deps;
5266
- const ctx = await resolveNameContext(deps, flags.network, flags.processId);
6067
+ const ctx = await resolveNameContext(
6068
+ deps,
6069
+ flags.network,
6070
+ flags.processId,
6071
+ "read"
6072
+ );
5267
6073
  const record = await ctx.sdk.getArNSRecord({ name });
5268
6074
  let targets = null;
5269
6075
  if (record?.processId) {
@@ -5311,7 +6117,9 @@ async function runStatus(name, flags, deps) {
5311
6117
  io2.out(" Records:");
5312
6118
  for (const [under, target] of Object.entries(targets)) {
5313
6119
  const label = under === "@" ? "(base)" : `${under}_${name}`;
5314
- io2.out(` ${label} \u2192 ${target.transactionId} (ttl ${target.ttlSeconds}s)`);
6120
+ io2.out(
6121
+ ` ${label} \u2192 ${target.transactionId} (ttl ${target.ttlSeconds}s)`
6122
+ );
5315
6123
  }
5316
6124
  } else {
5317
6125
  io2.out(" Records: none set \u2014 point one with `rig name set`.");
@@ -5361,7 +6169,9 @@ async function runName(args, deps) {
5361
6169
  if (sub === "status") return await runStatus(name, flags, deps);
5362
6170
  const txId = positionals[1];
5363
6171
  if (txId === void 0) {
5364
- io2.err("`rig name set` needs a <txId> (the Arweave transaction to point at)");
6172
+ io2.err(
6173
+ "`rig name set` needs a <txId> (the Arweave transaction to point at)"
6174
+ );
5365
6175
  io2.err(NAME_USAGE);
5366
6176
  return 2;
5367
6177
  }
@@ -5391,22 +6201,22 @@ function buildArweaveManifest(entries, indexPath = DEFAULT_INDEX, fallbackTxId)
5391
6201
  }
5392
6202
 
5393
6203
  // src/cli/site-record.ts
5394
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
5395
- import { homedir as homedir2 } from "os";
5396
- import { join as join3 } from "path";
6204
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
6205
+ import { homedir as homedir3 } from "os";
6206
+ import { join as join4 } from "path";
5397
6207
  var SITE_MANIFEST_FILENAME = "site-manifests.json";
5398
- function storeDir(env) {
5399
- return env["TOON_CLIENT_HOME"] ?? join3(homedir2(), ".toon-client");
6208
+ function storeDir2(env) {
6209
+ return env["TOON_CLIENT_HOME"] ?? join4(homedir3(), ".toon-client");
5400
6210
  }
5401
- function storePath(env) {
5402
- return join3(storeDir(env), SITE_MANIFEST_FILENAME);
6211
+ function storePath2(env) {
6212
+ return join4(storeDir2(env), SITE_MANIFEST_FILENAME);
5403
6213
  }
5404
6214
  function keyOf(repoId, ref) {
5405
6215
  return `${repoId}\0${ref}`;
5406
6216
  }
5407
6217
  function readFile2(env) {
5408
6218
  try {
5409
- const parsed = JSON.parse(readFileSync2(storePath(env), "utf8"));
6219
+ const parsed = JSON.parse(readFileSync3(storePath2(env), "utf8"));
5410
6220
  if (parsed && typeof parsed === "object") return parsed;
5411
6221
  return {};
5412
6222
  } catch {
@@ -5417,11 +6227,11 @@ function readSiteRecord(env, repoId, ref) {
5417
6227
  return readFile2(env)[keyOf(repoId, ref)];
5418
6228
  }
5419
6229
  function writeSiteRecord(env, record) {
5420
- const dir = storeDir(env);
5421
- mkdirSync2(dir, { recursive: true });
6230
+ const dir = storeDir2(env);
6231
+ mkdirSync3(dir, { recursive: true });
5422
6232
  const file = readFile2(env);
5423
6233
  file[keyOf(record.repoId, record.ref)] = record;
5424
- writeFileSync(storePath(env), `${JSON.stringify(file, null, 2)}
6234
+ writeFileSync2(storePath2(env), `${JSON.stringify(file, null, 2)}
5425
6235
  `, "utf8");
5426
6236
  }
5427
6237