@toon-protocol/rig 2.6.3 → 2.7.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
@@ -18,7 +18,7 @@ import {
18
18
  setHeadSymref,
19
19
  updateRef,
20
20
  writeGitObjects
21
- } from "../chunk-NIRC5LFS.js";
21
+ } from "../chunk-PG5PUKDR.js";
22
22
  import {
23
23
  DEFAULT_KEYSTORE_PASSWORD,
24
24
  MissingIdentityError,
@@ -32,8 +32,9 @@ import {
32
32
  ChannelMapStore,
33
33
  channelStatus,
34
34
  defaultDaemonPort,
35
- resolveChannelPaths
36
- } from "../chunk-SW7ZHMGS.js";
35
+ resolveChannelPaths,
36
+ resolveConflictingPath
37
+ } from "../chunk-FQXNLPUR.js";
37
38
  import {
38
39
  MAX_OBJECT_SIZE
39
40
  } from "../chunk-B5ISARMU.js";
@@ -398,6 +399,13 @@ function describeError(err, command = "push") {
398
399
  json: { error: "git_error", detail: err.message }
399
400
  };
400
401
  }
402
+ if (err instanceof Error && err.name === "ArnsSdkUnavailableError") {
403
+ return {
404
+ code: "arns_sdk_unavailable",
405
+ lines: err.message.split("\n"),
406
+ json: { error: "arns_sdk_unavailable", detail: err.message }
407
+ };
408
+ }
401
409
  if (err instanceof DaemonRouteError) {
402
410
  const envelope = err.envelope;
403
411
  if (envelope.error === "non_fast_forward" && Array.isArray(envelope["refs"])) {
@@ -966,7 +974,7 @@ function loadPaidSession(deps, relayUrl) {
966
974
  });
967
975
  }
968
976
  var defaultLoadStandalone = async (options) => {
969
- const mod = await import("../standalone-mode-WEMJBHME.js");
977
+ const mod = await import("../standalone-mode-F7L5GUTK.js");
970
978
  return mod.createStandaloneContext(options);
971
979
  };
972
980
  function identityReport(ctx) {
@@ -4912,6 +4920,896 @@ async function runMutate(op, args, deps) {
4912
4920
  }
4913
4921
  }
4914
4922
 
4923
+ // src/cli/name.ts
4924
+ import { parseArgs as parseArgs13 } from "util";
4925
+ var ARIO_DECIMALS = 6;
4926
+ function renderMario(mario) {
4927
+ return `${mario.toString()} mARIO (~${formatUnits(mario.toString(), ARIO_DECIMALS)} ARIO)`;
4928
+ }
4929
+ 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).";
4930
+ var ArnsSdkUnavailableError = class extends Error {
4931
+ constructor(cause) {
4932
+ 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 ? `
4934
+ (underlying error: ${cause.message})` : "")
4935
+ );
4936
+ this.name = "ArnsSdkUnavailableError";
4937
+ }
4938
+ };
4939
+ var ARIO_MAINNET_PROCESS_ID = "qNvAoz0TgcH7DMg8BCVn8jF32QH5L6T29VjHxhHqqGE";
4940
+ var defaultLoadArns = async (options) => {
4941
+ const specifier = "@ar.io/sdk";
4942
+ let mod;
4943
+ try {
4944
+ mod = await import(specifier);
4945
+ } catch (err) {
4946
+ throw new ArnsSdkUnavailableError(err);
4947
+ }
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
+ )
4959
+ );
4960
+ }
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
+ )
4966
+ );
4967
+ }
4968
+ const signer = new mod.SolanaSigner(options.solanaSecretKey);
4969
+ const ario = mod.ARIO.init({ process: processId, signer });
4970
+ const antInit = mod.ANT.init;
4971
+ return {
4972
+ 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),
4975
+ ant: async (pid) => {
4976
+ const ant = antInit({ processId: pid, signer });
4977
+ return {
4978
+ getRecords: () => ant.getRecords(),
4979
+ setBaseNameRecord: (a) => ant.setBaseNameRecord(a),
4980
+ setUndernameRecord: (a) => ant.setUndernameRecord(a)
4981
+ };
4982
+ }
4983
+ };
4984
+ };
4985
+ var NAME_USAGE = `Usage: rig name <buy|set|status> <name> [options]
4986
+
4987
+ ArNS naming for Arweave-served artifacts. A name registered on the ar.io
4988
+ registry resolves at every ar.io gateway (https://<name>.<gateway>/), pointing
4989
+ at whatever Arweave txId its ANT record targets (10 undernames included).
4990
+
4991
+ Names are OWNED and PAID FOR by this rig identity's Solana key \u2014 the same
4992
+ mnemonic that pays for pushes (derived at m/44'/501'/0'/0'). Fund it with
4993
+ \`rig fund --chain solana\`. Purchases spend mARIO on Solana via the ar.io
4994
+ registry program \u2014 NOT through TOON ILP payment channels.
4995
+
4996
+ Commands:
4997
+ name buy <name> [--years n | --permabuy]
4998
+ quote (mARIO) \u2192 confirm \u2192 register. The spawned ANT is
4999
+ owned by this identity's Solana key. Default: 1-year
5000
+ lease. PAID \u2014 spends mARIO from the Solana wallet.
5001
+ name set <name> <txId> [--undername <sub>] [--ttl <seconds>]
5002
+ point the name (or an undername) at an Arweave txId
5003
+ (typically a deployed path manifest). Signs an ANT
5004
+ record update with the Solana key.
5005
+ name status <name> FREE: registry record (lease/permabuy, expiry), ANT
5006
+ process id, current target txId(s), TTL, undernames.
5007
+
5008
+ Options:
5009
+ --years <n> lease length in years (buy; default 1; mutually
5010
+ exclusive with --permabuy)
5011
+ --permabuy permanent registration instead of a lease (buy)
5012
+ --undername <sub> target the undername <sub>_<name> instead of the base
5013
+ name (set)
5014
+ --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
5017
+ unverified.
5018
+ --process-id <id> explicit registry process id (overrides --network; or
5019
+ RIG_ARIO_PROCESS_ID)
5020
+ --yes skip the confirmation (required when not a TTY) for
5021
+ buy/set
5022
+ --json machine-readable envelope; for buy/set without --yes it
5023
+ is a pure estimate (nothing is bought or written)
5024
+ -h, --help show this help`;
5025
+ var DEFAULT_TTL_SECONDS = 3600;
5026
+ var DEFAULT_LEASE_YEARS = 1;
5027
+ var NETWORKS = ["mainnet", "devnet", "testnet"];
5028
+ async function resolveNameContext(deps, network, processId) {
5029
+ const { io: io2, env } = deps;
5030
+ const resolved = await resolveIdentity({
5031
+ env,
5032
+ cwd: deps.cwd,
5033
+ warn: (line) => io2.err(line)
5034
+ });
5035
+ const identity = {
5036
+ pubkey: resolved.pubkey,
5037
+ source: resolved.source,
5038
+ sourceLabel: resolved.sourceLabel
5039
+ };
5040
+ const client = await import("@toon-protocol/client");
5041
+ const derived = await client.deriveFullIdentity(
5042
+ resolved.mnemonic,
5043
+ resolved.accountIndex
5044
+ );
5045
+ const solanaAddress = derived.solana.publicKey;
5046
+ if (!solanaAddress) {
5047
+ throw new Error(
5048
+ "no Solana key could be derived for this identity \u2014 ArNS names are owned by the Solana key (m/44'/501'/0'/0'). Ensure the optional Solana derivation deps are installed."
5049
+ );
5050
+ }
5051
+ const sdk = await (deps.loadArns ?? defaultLoadArns)({
5052
+ solanaSecretKey: derived.solana.secretKey,
5053
+ solanaPublicKey: solanaAddress,
5054
+ network,
5055
+ ...processId !== void 0 ? { processId } : {}
5056
+ });
5057
+ return { identity, solanaAddress, network, processId, sdk };
5058
+ }
5059
+ function parseNameFlags(args, env) {
5060
+ const { values, positionals } = parseArgs13({
5061
+ args,
5062
+ allowPositionals: true,
5063
+ options: {
5064
+ years: { type: "string" },
5065
+ permabuy: { type: "boolean", default: false },
5066
+ undername: { type: "string" },
5067
+ ttl: { type: "string" },
5068
+ network: { type: "string" },
5069
+ "process-id": { type: "string" },
5070
+ yes: { type: "boolean", default: false },
5071
+ json: { type: "boolean", default: false },
5072
+ help: { type: "boolean", short: "h", default: false }
5073
+ }
5074
+ });
5075
+ if (values.help) {
5076
+ return { positionals: ["--help"], flags: { permabuy: false, ttl: 0, network: "mainnet", yes: false, json: false } };
5077
+ }
5078
+ let years;
5079
+ if (values.years !== void 0) {
5080
+ years = Number(values.years);
5081
+ if (!Number.isInteger(years) || years <= 0) {
5082
+ throw new Error(`--years must be a positive integer, got ${JSON.stringify(values.years)}`);
5083
+ }
5084
+ }
5085
+ if (years !== void 0 && values.permabuy) {
5086
+ throw new Error("--years and --permabuy are mutually exclusive");
5087
+ }
5088
+ let ttl = DEFAULT_TTL_SECONDS;
5089
+ if (values.ttl !== void 0) {
5090
+ ttl = Number(values.ttl);
5091
+ if (!Number.isInteger(ttl) || ttl <= 0) {
5092
+ throw new Error(`--ttl must be a positive integer number of seconds, got ${JSON.stringify(values.ttl)}`);
5093
+ }
5094
+ }
5095
+ const networkRaw = values.network ?? env["RIG_ARIO_NETWORK"] ?? "mainnet";
5096
+ if (!NETWORKS.includes(networkRaw)) {
5097
+ throw new Error(`--network must be one of ${NETWORKS.join(" | ")}, got ${JSON.stringify(networkRaw)}`);
5098
+ }
5099
+ const processId = values["process-id"] ?? env["RIG_ARIO_PROCESS_ID"];
5100
+ return {
5101
+ positionals,
5102
+ flags: {
5103
+ ...years !== void 0 ? { years } : {},
5104
+ permabuy: values.permabuy ?? false,
5105
+ ...values.undername !== void 0 ? { undername: values.undername } : {},
5106
+ ttl,
5107
+ network: networkRaw,
5108
+ ...processId !== void 0 ? { processId } : {},
5109
+ yes: values.yes ?? false,
5110
+ json: values.json ?? false
5111
+ }
5112
+ };
5113
+ }
5114
+ async function runBuy(name, flags, deps) {
5115
+ const { io: io2 } = deps;
5116
+ const type = flags.permabuy ? "permabuy" : "lease";
5117
+ const years = type === "lease" ? flags.years ?? DEFAULT_LEASE_YEARS : null;
5118
+ const ctx = await resolveNameContext(deps, flags.network, flags.processId);
5119
+ const mario = await ctx.sdk.getTokenCost({
5120
+ intent: "Buy-Name",
5121
+ name,
5122
+ type,
5123
+ ...years !== null ? { years } : {}
5124
+ });
5125
+ const quote = {
5126
+ mARIO: mario.toString(),
5127
+ ARIO: formatUnits(mario.toString(), ARIO_DECIMALS)
5128
+ };
5129
+ const buildJson = (executed, extra = {}) => ({
5130
+ command: "name",
5131
+ action: "buy",
5132
+ name,
5133
+ type,
5134
+ years,
5135
+ network: ctx.network,
5136
+ processId: ctx.processId ?? null,
5137
+ identity: ctx.identity,
5138
+ solanaAddress: ctx.solanaAddress,
5139
+ quote,
5140
+ payment: MARIO_PAYMENT_NOTE,
5141
+ executed,
5142
+ ...extra
5143
+ });
5144
+ if (!flags.json) {
5145
+ io2.out(
5146
+ `Buy ArNS name "${name}" \u2014 ${type}` + (years !== null ? ` (${years} year${years === 1 ? "" : "s"})` : "") + ` on ${ctx.network}`
5147
+ );
5148
+ io2.out(` Cost: ${renderMario(mario)}`);
5149
+ io2.out(` Solana wallet: ${ctx.solanaAddress}`);
5150
+ io2.out(` ${MARIO_PAYMENT_NOTE}`);
5151
+ io2.out(renderIdentityLine(ctx.identity));
5152
+ }
5153
+ if (!flags.yes) {
5154
+ if (flags.json) {
5155
+ io2.emitJson(
5156
+ buildJson(false, {
5157
+ hint: "estimate only \u2014 re-run with --yes to buy (spends mARIO on Solana, non-refundable)"
5158
+ })
5159
+ );
5160
+ return 0;
5161
+ }
5162
+ if (!io2.isInteractive) {
5163
+ 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)"
5165
+ );
5166
+ return 1;
5167
+ }
5168
+ const proceed = await io2.confirm(
5169
+ `Proceed \u2014 buy "${name}" for ${renderMario(mario)} from your Solana wallet? [y/N] `
5170
+ );
5171
+ if (!proceed) {
5172
+ io2.err("aborted \u2014 nothing was bought or paid.");
5173
+ return 1;
5174
+ }
5175
+ }
5176
+ const receipt = await ctx.sdk.buyRecord({
5177
+ name,
5178
+ type,
5179
+ ...years !== null ? { years } : {}
5180
+ });
5181
+ const result = {
5182
+ registryTxId: receipt.id,
5183
+ antProcessId: receipt.processId ?? null
5184
+ };
5185
+ if (flags.json) {
5186
+ io2.emitJson(buildJson(true, { result }));
5187
+ } else {
5188
+ io2.out(`Registered "${name}" \u2014 registry tx ${result.registryTxId}`);
5189
+ if (result.antProcessId) io2.out(` ANT process: ${result.antProcessId}`);
5190
+ io2.out(
5191
+ `Point it at content with \`rig name set ${name} <txId>\`, then it resolves at https://${name}.<gateway>/`
5192
+ );
5193
+ }
5194
+ return 0;
5195
+ }
5196
+ async function runSet(name, txId, flags, deps) {
5197
+ const { io: io2 } = deps;
5198
+ const ctx = await resolveNameContext(deps, flags.network, flags.processId);
5199
+ const record = await ctx.sdk.getArNSRecord({ name });
5200
+ if (!record || !record.processId) {
5201
+ throw new Error(
5202
+ `no ArNS record for "${name}" on ${ctx.network} \u2014 buy it first with \`rig name buy ${name}\` (or check \`rig name status ${name}\`)`
5203
+ );
5204
+ }
5205
+ const antProcessId = record.processId;
5206
+ const undername = flags.undername ?? null;
5207
+ const buildJson = (executed, extra = {}) => ({
5208
+ command: "name",
5209
+ action: "set",
5210
+ name,
5211
+ undername,
5212
+ txId,
5213
+ ttl: flags.ttl,
5214
+ network: ctx.network,
5215
+ processId: ctx.processId ?? null,
5216
+ identity: ctx.identity,
5217
+ solanaAddress: ctx.solanaAddress,
5218
+ antProcessId,
5219
+ executed,
5220
+ ...extra
5221
+ });
5222
+ const targetLabel = undername ? `undername "${undername}_${name}"` : `base name "${name}"`;
5223
+ if (!flags.json) {
5224
+ io2.out(`Set ${targetLabel} \u2192 txId ${txId} (ttl ${flags.ttl}s) on ${ctx.network}`);
5225
+ io2.out(` ANT process: ${antProcessId}`);
5226
+ io2.out(` Signed by the identity's Solana key (${ctx.solanaAddress}).`);
5227
+ io2.out(renderIdentityLine(ctx.identity));
5228
+ }
5229
+ if (!flags.yes) {
5230
+ if (flags.json) {
5231
+ io2.emitJson(
5232
+ buildJson(false, {
5233
+ hint: "estimate only \u2014 re-run with --yes to write the ANT record"
5234
+ })
5235
+ );
5236
+ return 0;
5237
+ }
5238
+ if (!io2.isInteractive) {
5239
+ io2.err(
5240
+ "refusing to write an ANT record without confirmation in a non-interactive session \u2014 re-run with --yes (or --json to preview)"
5241
+ );
5242
+ return 1;
5243
+ }
5244
+ const proceed = await io2.confirm(
5245
+ `Proceed \u2014 point ${targetLabel} at ${txId}? [y/N] `
5246
+ );
5247
+ if (!proceed) {
5248
+ io2.err("aborted \u2014 the ANT record was not changed.");
5249
+ return 1;
5250
+ }
5251
+ }
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 });
5254
+ const result = { messageId: receipt.id };
5255
+ if (flags.json) {
5256
+ io2.emitJson(buildJson(true, { result }));
5257
+ } else {
5258
+ io2.out(`Updated \u2014 ANT message ${result.messageId}`);
5259
+ const host = undername ? `${undername}_${name}` : name;
5260
+ io2.out(` Resolves at https://${host}.<gateway>/ (allow for gateway cache/TTL).`);
5261
+ }
5262
+ return 0;
5263
+ }
5264
+ async function runStatus(name, flags, deps) {
5265
+ const { io: io2 } = deps;
5266
+ const ctx = await resolveNameContext(deps, flags.network, flags.processId);
5267
+ const record = await ctx.sdk.getArNSRecord({ name });
5268
+ let targets = null;
5269
+ if (record?.processId) {
5270
+ const ant = await ctx.sdk.ant(record.processId);
5271
+ targets = await ant.getRecords();
5272
+ }
5273
+ if (flags.json) {
5274
+ io2.emitJson({
5275
+ command: "name",
5276
+ action: "status",
5277
+ name,
5278
+ network: ctx.network,
5279
+ processId: ctx.processId ?? null,
5280
+ registered: record !== null,
5281
+ identity: ctx.identity,
5282
+ solanaAddress: ctx.solanaAddress,
5283
+ record: record ? {
5284
+ antProcessId: record.processId,
5285
+ type: record.type,
5286
+ startTimestamp: record.startTimestamp ?? null,
5287
+ endTimestamp: record.endTimestamp ?? null,
5288
+ undernameLimit: record.undernameLimit ?? null
5289
+ } : null,
5290
+ targets
5291
+ });
5292
+ return 0;
5293
+ }
5294
+ io2.out(`ArNS name "${name}" on ${ctx.network}:`);
5295
+ if (!record) {
5296
+ io2.out(" not registered \u2014 available to `rig name buy`.");
5297
+ io2.out(renderIdentityLine(ctx.identity));
5298
+ return 0;
5299
+ }
5300
+ io2.out(` ANT process: ${record.processId ?? "(unknown)"}`);
5301
+ io2.out(` Type: ${record.type ?? "(unknown)"}`);
5302
+ if (record.type === "permabuy") {
5303
+ io2.out(" Expiry: never (permabuy)");
5304
+ } else if (record.endTimestamp !== void 0) {
5305
+ io2.out(` Expiry: ${new Date(record.endTimestamp).toISOString()}`);
5306
+ }
5307
+ if (record.undernameLimit !== void 0) {
5308
+ io2.out(` Undernames included: ${record.undernameLimit}`);
5309
+ }
5310
+ if (targets && Object.keys(targets).length > 0) {
5311
+ io2.out(" Records:");
5312
+ for (const [under, target] of Object.entries(targets)) {
5313
+ const label = under === "@" ? "(base)" : `${under}_${name}`;
5314
+ io2.out(` ${label} \u2192 ${target.transactionId} (ttl ${target.ttlSeconds}s)`);
5315
+ }
5316
+ } else {
5317
+ io2.out(" Records: none set \u2014 point one with `rig name set`.");
5318
+ }
5319
+ io2.out(renderIdentityLine(ctx.identity));
5320
+ return 0;
5321
+ }
5322
+ async function runName(args, deps) {
5323
+ const { io: io2 } = deps;
5324
+ const sub = args[0];
5325
+ if (sub === void 0) {
5326
+ io2.err(NAME_USAGE);
5327
+ return 2;
5328
+ }
5329
+ if (sub === "--help" || sub === "-h" || sub === "help") {
5330
+ io2.out(NAME_USAGE);
5331
+ return 0;
5332
+ }
5333
+ if (!["buy", "set", "status"].includes(sub)) {
5334
+ io2.err(`unknown \`rig name\` subcommand: ${JSON.stringify(sub)}`);
5335
+ io2.err(NAME_USAGE);
5336
+ return 2;
5337
+ }
5338
+ let positionals;
5339
+ let flags;
5340
+ try {
5341
+ const parsed = parseNameFlags(args.slice(1), deps.env);
5342
+ positionals = parsed.positionals;
5343
+ flags = parsed.flags;
5344
+ } catch (err) {
5345
+ io2.err(err instanceof Error ? err.message : String(err));
5346
+ io2.err(NAME_USAGE);
5347
+ return 2;
5348
+ }
5349
+ if (positionals[0] === "--help") {
5350
+ io2.out(NAME_USAGE);
5351
+ return 0;
5352
+ }
5353
+ const name = positionals[0];
5354
+ if (name === void 0) {
5355
+ io2.err(`\`rig name ${sub}\` needs a <name>`);
5356
+ io2.err(NAME_USAGE);
5357
+ return 2;
5358
+ }
5359
+ try {
5360
+ if (sub === "buy") return await runBuy(name, flags, deps);
5361
+ if (sub === "status") return await runStatus(name, flags, deps);
5362
+ const txId = positionals[1];
5363
+ if (txId === void 0) {
5364
+ io2.err("`rig name set` needs a <txId> (the Arweave transaction to point at)");
5365
+ io2.err(NAME_USAGE);
5366
+ return 2;
5367
+ }
5368
+ return await runSet(name, txId, flags, deps);
5369
+ } catch (err) {
5370
+ return emitCliError(io2, flags.json, "name", err);
5371
+ }
5372
+ }
5373
+
5374
+ // src/cli/site.ts
5375
+ import { parseArgs as parseArgs14 } from "util";
5376
+
5377
+ // src/arweave-manifest.ts
5378
+ var DEFAULT_INDEX = "index.html";
5379
+ function buildArweaveManifest(entries, indexPath = DEFAULT_INDEX, fallbackTxId) {
5380
+ const paths = {};
5381
+ for (const { path, txId } of entries) {
5382
+ paths[path] = { id: txId };
5383
+ }
5384
+ return {
5385
+ manifest: "arweave/paths",
5386
+ version: "0.2.0",
5387
+ index: { path: indexPath },
5388
+ ...fallbackTxId ? { fallback: { id: fallbackTxId } } : {},
5389
+ paths
5390
+ };
5391
+ }
5392
+
5393
+ // 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";
5397
+ var SITE_MANIFEST_FILENAME = "site-manifests.json";
5398
+ function storeDir(env) {
5399
+ return env["TOON_CLIENT_HOME"] ?? join3(homedir2(), ".toon-client");
5400
+ }
5401
+ function storePath(env) {
5402
+ return join3(storeDir(env), SITE_MANIFEST_FILENAME);
5403
+ }
5404
+ function keyOf(repoId, ref) {
5405
+ return `${repoId}\0${ref}`;
5406
+ }
5407
+ function readFile2(env) {
5408
+ try {
5409
+ const parsed = JSON.parse(readFileSync2(storePath(env), "utf8"));
5410
+ if (parsed && typeof parsed === "object") return parsed;
5411
+ return {};
5412
+ } catch {
5413
+ return {};
5414
+ }
5415
+ }
5416
+ function readSiteRecord(env, repoId, ref) {
5417
+ return readFile2(env)[keyOf(repoId, ref)];
5418
+ }
5419
+ function writeSiteRecord(env, record) {
5420
+ const dir = storeDir(env);
5421
+ mkdirSync2(dir, { recursive: true });
5422
+ const file = readFile2(env);
5423
+ file[keyOf(record.repoId, record.ref)] = record;
5424
+ writeFileSync(storePath(env), `${JSON.stringify(file, null, 2)}
5425
+ `, "utf8");
5426
+ }
5427
+
5428
+ // src/cli/site.ts
5429
+ var MANIFEST_CONTENT_TYPE = "application/x.arweave-manifest+json";
5430
+ var DEFAULT_GATEWAY = "https://arweave.net";
5431
+ var SITE_USAGE = `Usage: rig site <publish|url> [ref] [options]
5432
+
5433
+ Serve a pushed repo as a permanent, path-routed website. \`rig push\` already
5434
+ stored the file bytes on Arweave; \`rig site publish\` builds the ar.io path
5435
+ manifest (paths \u2192 Arweave txids, index \u2192 index.html) and uploads it as ONE
5436
+ paid store write, printing the site URL. \`rig site url\` re-prints the URL of
5437
+ the last publish for free.
5438
+
5439
+ The repo must be set up with \`rig init\`; the relay is the "origin" remote
5440
+ (\`rig push\` reads the same). \`ref\` defaults to the current branch.
5441
+
5442
+ Commands:
5443
+ publish [ref] build + upload the path manifest (paid); prints the URL and
5444
+ a \`rig name set\` suggestion (#367)
5445
+ url [ref] print the last-published site URL for a ref (free)
5446
+
5447
+ Options (publish):
5448
+ --index <path> path served at / (default: index.html)
5449
+ --spa single-page app: serve index for any unknown path
5450
+ (sets the manifest \`fallback\` to the index txid)
5451
+ --fallback <path> explicit fallback path for unknown routes (overrides
5452
+ --spa); must be one of the site's files
5453
+ --force-reupload re-pay to re-upload the ref's blobs with #368 per-file
5454
+ Content-Type before building the manifest \u2014 for repos
5455
+ pushed before mime-typing existed (else they serve as
5456
+ application/octet-stream)
5457
+ --gateway <url> gateway base for the printed URL (default: ${DEFAULT_GATEWAY};
5458
+ env RIG_ARWEAVE_GATEWAY also sets it)
5459
+ --relay <url> ad-hoc relay override (bypasses configured remotes)
5460
+ --repo-id <id> repository id (default: git config toon.repoid)
5461
+ --yes skip the fee confirmation (required when not a TTY)
5462
+ --json machine-readable envelope; without --yes it is a pure
5463
+ estimate (nothing uploaded)
5464
+ -h, --help show this help`;
5465
+ function parseSiteArgs(args) {
5466
+ const { values, positionals } = parseArgs14({
5467
+ args,
5468
+ options: {
5469
+ index: { type: "string" },
5470
+ spa: { type: "boolean", default: false },
5471
+ fallback: { type: "string" },
5472
+ "force-reupload": { type: "boolean", default: false },
5473
+ gateway: { type: "string" },
5474
+ relay: { type: "string", multiple: true },
5475
+ "repo-id": { type: "string" },
5476
+ yes: { type: "boolean", default: false },
5477
+ json: { type: "boolean", default: false },
5478
+ help: { type: "boolean", short: "h", default: false }
5479
+ },
5480
+ allowPositionals: true
5481
+ });
5482
+ const flags = {
5483
+ index: values.index ?? "index.html",
5484
+ spa: values.spa ?? false,
5485
+ forceReupload: values["force-reupload"] ?? false,
5486
+ relay: values.relay ?? [],
5487
+ yes: values.yes ?? false,
5488
+ json: values.json ?? false,
5489
+ help: values.help ?? false,
5490
+ positionals
5491
+ };
5492
+ if (values.fallback !== void 0) flags.fallback = values.fallback;
5493
+ if (values.gateway !== void 0) flags.gateway = values.gateway;
5494
+ if (values["repo-id"] !== void 0) flags.repoId = values["repo-id"];
5495
+ return flags;
5496
+ }
5497
+ function normalizeGateway(gateway) {
5498
+ return gateway.replace(/\/+$/, "");
5499
+ }
5500
+ function siteUrl(gateway, manifestTxId) {
5501
+ return `${normalizeGateway(gateway)}/${manifestTxId}/`;
5502
+ }
5503
+ var PLACEHOLDER_TX = "A".repeat(43);
5504
+ async function runSite(args, deps) {
5505
+ const { io: io2 } = deps;
5506
+ const [sub, ...rest] = args;
5507
+ if (sub === "publish") return runSitePublish(rest, deps);
5508
+ if (sub === "url") return runSiteUrl(rest, deps);
5509
+ if (sub === void 0 || sub === "-h" || sub === "--help" || sub === "help") {
5510
+ io2.out(SITE_USAGE);
5511
+ return sub === void 0 ? 2 : 0;
5512
+ }
5513
+ io2.err(`unknown site subcommand ${JSON.stringify(sub)}`);
5514
+ io2.err(SITE_USAGE);
5515
+ return 2;
5516
+ }
5517
+ async function runSitePublish(args, deps) {
5518
+ const { io: io2 } = deps;
5519
+ let flags;
5520
+ try {
5521
+ flags = parseSiteArgs(args);
5522
+ } catch (err) {
5523
+ io2.err(err instanceof Error ? err.message : String(err));
5524
+ io2.err(SITE_USAGE);
5525
+ return 2;
5526
+ }
5527
+ if (flags.help) {
5528
+ io2.out(SITE_USAGE);
5529
+ return 0;
5530
+ }
5531
+ let ctx;
5532
+ try {
5533
+ const repoRoot = await resolveRepoRoot(deps.cwd);
5534
+ const toonConfig = await readToonConfig(repoRoot);
5535
+ const repoId = flags.repoId ?? toonConfig.repoId;
5536
+ if (!repoId) throw new UnconfiguredRepoAddressError("repository id");
5537
+ const reader = new GitRepoReader(repoRoot);
5538
+ const selected = await selectRefspecs(reader, flags.positionals, false, false);
5539
+ if (selected.length !== 1) {
5540
+ throw new Error(
5541
+ `rig site publish builds ONE ref into a site \u2014 got ${selected.length} (${selected.join(", ")}); pass a single branch or tag`
5542
+ );
5543
+ }
5544
+ const ref = selected[0];
5545
+ const resolved = await resolveRelays({
5546
+ relayFlags: flags.relay,
5547
+ remoteName: void 0,
5548
+ repoRoot,
5549
+ toonRelays: toonConfig.relays
5550
+ });
5551
+ if (resolved.nudge !== void 0) io2.err(resolved.nudge);
5552
+ const relaysUsed = resolved.relays;
5553
+ if (relaysUsed.length > 1) {
5554
+ io2.err(singleRelayRefusal(resolved, "Nothing was uploaded or paid."));
5555
+ return 1;
5556
+ }
5557
+ ctx = await (deps.loadStandalone ?? defaultLoadStandalone)({
5558
+ env: deps.env,
5559
+ cwd: deps.cwd,
5560
+ warn: (line) => io2.err(line),
5561
+ ...relaysUsed[0] !== void 0 ? { relayUrl: relaysUsed[0] } : {}
5562
+ });
5563
+ const identity = identityReport(ctx);
5564
+ if (!ctx.publisher.uploadBlob) {
5565
+ throw new Error(
5566
+ "the active publisher cannot upload a raw manifest blob (uploadBlob unavailable) \u2014 `rig site` requires the standalone publisher"
5567
+ );
5568
+ }
5569
+ const blobs = await reader.listBlobs(ref);
5570
+ if (blobs.length === 0) {
5571
+ throw new Error(`ref ${JSON.stringify(ref)} has no files to serve`);
5572
+ }
5573
+ const shaToPaths = /* @__PURE__ */ new Map();
5574
+ for (const { path, sha } of blobs) {
5575
+ const paths = shaToPaths.get(sha) ?? [];
5576
+ paths.push(path);
5577
+ shaToPaths.set(sha, paths);
5578
+ }
5579
+ const uniqueShas = [...shaToPaths.keys()];
5580
+ const remoteState = await ctx.fetchRemote({
5581
+ ownerPubkey: ctx.ownerPubkey,
5582
+ repoId,
5583
+ relayUrls: relaysUsed
5584
+ });
5585
+ const known = new Map(remoteState.shaToTxId);
5586
+ const gaps = uniqueShas.filter((sha) => !known.has(sha));
5587
+ if (gaps.length > 0) {
5588
+ const found = await remoteState.resolveMissing(gaps);
5589
+ for (const [sha, txId] of found) known.set(sha, txId);
5590
+ }
5591
+ const fallbackPath = flags.fallback ?? (flags.spa ? flags.index : void 0);
5592
+ const pathSet = new Set(blobs.map((b) => b.path));
5593
+ if (fallbackPath !== void 0 && !pathSet.has(fallbackPath)) {
5594
+ throw new Error(
5595
+ `fallback path ${JSON.stringify(fallbackPath)} is not one of the site's files \u2014 pick an existing path (or the --index file for --spa)`
5596
+ );
5597
+ }
5598
+ if (!pathSet.has(flags.index)) {
5599
+ io2.err(
5600
+ `warning: no ${JSON.stringify(flags.index)} in this ref \u2014 the site will 404 at / until you add one (or pass --index <path>)`
5601
+ );
5602
+ }
5603
+ if (!flags.forceReupload) {
5604
+ const unpublished = uniqueShas.filter((sha) => !known.has(sha));
5605
+ if (unpublished.length > 0) {
5606
+ const paths = unpublished.flatMap((sha) => shaToPaths.get(sha) ?? []).slice(0, 10);
5607
+ throw new Error(
5608
+ `${unpublished.length} file(s) in ${ref} are not on Arweave yet (e.g. ${paths.join(", ")}) \u2014 run \`rig push\` first, or \`rig site publish --force-reupload\` to upload them now (paid).`
5609
+ );
5610
+ }
5611
+ }
5612
+ const { uploadFeePerByte } = await ctx.publisher.getFeeRates();
5613
+ const previewEntries = blobs.map((b) => ({
5614
+ path: b.path,
5615
+ txId: known.get(b.sha) ?? PLACEHOLDER_TX
5616
+ }));
5617
+ const previewManifest = buildArweaveManifest(
5618
+ previewEntries,
5619
+ flags.index,
5620
+ fallbackPath ? PLACEHOLDER_TX : void 0
5621
+ );
5622
+ const manifestBytes = Buffer.byteLength(JSON.stringify(previewManifest));
5623
+ let reuploadBytes = 0;
5624
+ if (flags.forceReupload) {
5625
+ const { objects } = await reader.statObjects(uniqueShas);
5626
+ reuploadBytes = objects.reduce((sum, o) => sum + o.size, 0);
5627
+ }
5628
+ const manifestFee = BigInt(manifestBytes) * uploadFeePerByte;
5629
+ const reuploadFee = BigInt(reuploadBytes) * uploadFeePerByte;
5630
+ const totalFee = manifestFee + reuploadFee;
5631
+ const gateway = flags.gateway ?? deps.env["RIG_ARWEAVE_GATEWAY"] ?? DEFAULT_GATEWAY;
5632
+ const baseJson = () => ({
5633
+ command: "site publish",
5634
+ repoId,
5635
+ ref,
5636
+ identity,
5637
+ forceReupload: flags.forceReupload,
5638
+ fileCount: blobs.length,
5639
+ index: flags.index,
5640
+ ...fallbackPath ? { fallback: fallbackPath } : {},
5641
+ estimate: {
5642
+ manifestBytes,
5643
+ reuploadBytes,
5644
+ totalFee: totalFee.toString()
5645
+ }
5646
+ });
5647
+ if (!flags.json) {
5648
+ io2.out(`Site publish plan for ${ref} (repo ${repoId}):`);
5649
+ io2.out(` files: ${blobs.length}`);
5650
+ io2.out(` index: ${flags.index}${pathSet.has(flags.index) ? "" : " (missing!)"}`);
5651
+ if (fallbackPath) io2.out(` fallback: ${fallbackPath}`);
5652
+ if (flags.forceReupload) {
5653
+ io2.out(` re-upload ${uniqueShas.length} blob(s), ${reuploadBytes} bytes (paid)`);
5654
+ }
5655
+ io2.out(` manifest: ${manifestBytes} bytes`);
5656
+ io2.out(` total fee: ${totalFee} base units`);
5657
+ io2.out(renderIdentityLine(identity));
5658
+ }
5659
+ if (!flags.yes) {
5660
+ if (flags.json) {
5661
+ io2.emitJson({
5662
+ ...baseJson(),
5663
+ executed: false,
5664
+ hint: "estimate only \u2014 re-run with --yes to upload the manifest (permanent, non-refundable)"
5665
+ });
5666
+ return 0;
5667
+ }
5668
+ if (!io2.isInteractive) {
5669
+ io2.err(
5670
+ "refusing to spend channel funds without confirmation in a non-interactive session \u2014 re-run with --yes (or use --json for an estimate)"
5671
+ );
5672
+ return 1;
5673
+ }
5674
+ const proceed = await io2.confirm(
5675
+ `Proceed with paid site publish (total ${totalFee} base units)? [y/N] `
5676
+ );
5677
+ if (!proceed) {
5678
+ io2.err("aborted \u2014 nothing was uploaded.");
5679
+ return 1;
5680
+ }
5681
+ }
5682
+ if (flags.forceReupload) {
5683
+ const { objects, missing } = await reader.readObjects(uniqueShas);
5684
+ if (missing.length > 0) {
5685
+ throw new Error(
5686
+ `objects vanished from the local repository: ${missing.join(", ")}`
5687
+ );
5688
+ }
5689
+ const bodyBySha = new Map(objects.map((o) => [o.sha, o.body]));
5690
+ for (const sha of uniqueShas) {
5691
+ const body = bodyBySha.get(sha);
5692
+ if (!body) throw new Error(`internal: no body for ${sha}`);
5693
+ const uploadPath = resolveConflictingPath(shaToPaths.get(sha) ?? []);
5694
+ const receipt2 = await ctx.publisher.uploadGitObject({
5695
+ sha,
5696
+ type: "blob",
5697
+ body,
5698
+ repoId,
5699
+ ...uploadPath ? { path: uploadPath } : {}
5700
+ });
5701
+ known.set(sha, receipt2.txId);
5702
+ }
5703
+ }
5704
+ const entries = blobs.map((b) => {
5705
+ const txId = known.get(b.sha);
5706
+ if (!txId) {
5707
+ throw new Error(`internal: no Arweave txid for ${b.path} (${b.sha})`);
5708
+ }
5709
+ return { path: b.path, txId };
5710
+ });
5711
+ const fallbackTxId = fallbackPath !== void 0 ? entries.find((e) => e.path === fallbackPath)?.txId : void 0;
5712
+ const manifest = buildArweaveManifest(entries, flags.index, fallbackTxId);
5713
+ const manifestBody = Buffer.from(JSON.stringify(manifest), "utf8");
5714
+ const receipt = await ctx.publisher.uploadBlob({
5715
+ body: manifestBody,
5716
+ contentType: MANIFEST_CONTENT_TYPE,
5717
+ repoId
5718
+ });
5719
+ const url = siteUrl(gateway, receipt.txId);
5720
+ writeSiteRecord(deps.env, {
5721
+ repoId,
5722
+ ref,
5723
+ owner: ctx.ownerPubkey,
5724
+ manifestTxId: receipt.txId,
5725
+ gateway: normalizeGateway(gateway),
5726
+ updatedAt: Date.now()
5727
+ });
5728
+ const nameHint = `Point a name at it (see #367): rig name set <name> ${receipt.txId}`;
5729
+ if (flags.json) {
5730
+ io2.emitJson({
5731
+ ...baseJson(),
5732
+ executed: true,
5733
+ manifest: { txId: receipt.txId, url },
5734
+ nameHint
5735
+ });
5736
+ } else {
5737
+ io2.out(`Site published: ${url}`);
5738
+ io2.out(nameHint);
5739
+ }
5740
+ return 0;
5741
+ } catch (err) {
5742
+ return emitCliError(io2, flags.json, "site publish", err);
5743
+ } finally {
5744
+ if (ctx) {
5745
+ try {
5746
+ await ctx.stop();
5747
+ } catch {
5748
+ }
5749
+ }
5750
+ }
5751
+ }
5752
+ async function runSiteUrl(args, deps) {
5753
+ const { io: io2 } = deps;
5754
+ let flags;
5755
+ try {
5756
+ flags = parseSiteArgs(args);
5757
+ } catch (err) {
5758
+ io2.err(err instanceof Error ? err.message : String(err));
5759
+ io2.err(SITE_USAGE);
5760
+ return 2;
5761
+ }
5762
+ if (flags.help) {
5763
+ io2.out(SITE_USAGE);
5764
+ return 0;
5765
+ }
5766
+ try {
5767
+ const repoRoot = await resolveRepoRoot(deps.cwd);
5768
+ const toonConfig = await readToonConfig(repoRoot);
5769
+ const repoId = flags.repoId ?? toonConfig.repoId;
5770
+ if (!repoId) throw new UnconfiguredRepoAddressError("repository id");
5771
+ const reader = new GitRepoReader(repoRoot);
5772
+ const selected = await selectRefspecs(reader, flags.positionals, false, false);
5773
+ if (selected.length !== 1) {
5774
+ throw new Error(
5775
+ `rig site url takes ONE ref \u2014 got ${selected.length} (${selected.join(", ")})`
5776
+ );
5777
+ }
5778
+ const ref = selected[0];
5779
+ const record = readSiteRecord(deps.env, repoId, ref);
5780
+ if (!record) {
5781
+ const detail = `no site published for ${ref} (repo ${repoId}) yet \u2014 run \`rig site publish\` first (the manifest txid is only known after a paid publish)`;
5782
+ if (flags.json) {
5783
+ io2.emitJson({
5784
+ command: "site url",
5785
+ repoId,
5786
+ ref,
5787
+ found: false
5788
+ });
5789
+ }
5790
+ io2.err(detail);
5791
+ return 1;
5792
+ }
5793
+ const url = siteUrl(record.gateway, record.manifestTxId);
5794
+ if (flags.json) {
5795
+ io2.emitJson({
5796
+ command: "site url",
5797
+ repoId,
5798
+ ref,
5799
+ found: true,
5800
+ manifestTxId: record.manifestTxId,
5801
+ url,
5802
+ updatedAt: record.updatedAt
5803
+ });
5804
+ } else {
5805
+ io2.out(url);
5806
+ }
5807
+ return 0;
5808
+ } catch (err) {
5809
+ return emitCliError(io2, flags.json, "site url", err);
5810
+ }
5811
+ }
5812
+
4915
5813
  // src/cli/dispatch.ts
4916
5814
  var USAGE = `rig \u2014 git with a TOON remote (pay-to-write Nostr + Arweave)
4917
5815
 
@@ -4942,6 +5840,10 @@ Commands rig owns:
4942
5840
  push is the TOON transport and shadows git push;
4943
5841
  plain-git pushes remain available by running
4944
5842
  \`git push\` directly
5843
+ site publish [ref] deploy a pushed repo as a permaweb site: build the
5844
+ ar.io path manifest (paths \u2192 Arweave txids) and
5845
+ upload it as one paid store write; prints the URL
5846
+ site url [ref] print the last-published site URL for a ref (free)
4945
5847
  issue create file an issue (kind:1621) against a repo
4946
5848
  issue list | show <id> read the repo's issues + comments (free)
4947
5849
  pr list | show <id> read the repo's patches; show prints the full
@@ -4960,6 +5862,11 @@ Commands rig owns:
4960
5862
  address(es) to fund externally
4961
5863
  balance wallet balances + payment-channel holdings
4962
5864
  (free \u2014 chain reads and local state only)
5865
+ name buy <name> ArNS naming (#367): buy points a human name at an
5866
+ name set <name> <txId> Arweave txId, owned + paid by this identity's
5867
+ name status <name> Solana key. buy spends mARIO on Solana via the
5868
+ ar.io registry (NOT ILP); status is free. Needs
5869
+ the optional \`@ar.io/sdk\` dependency
4963
5870
  channel list show the payment channels paid commands hold
4964
5871
  (free \u2014 reads local state)
4965
5872
  channel open explicitly open (or resume) the channel for a
@@ -5014,6 +5921,8 @@ async function dispatch(argv2, deps) {
5014
5921
  return runFetch(rest, deps);
5015
5922
  case "push":
5016
5923
  return runPush(rest, deps);
5924
+ case "site":
5925
+ return runSite(rest, deps);
5017
5926
  case "issue":
5018
5927
  return runIssue(rest, deps);
5019
5928
  case "comment":
@@ -5028,6 +5937,8 @@ async function dispatch(argv2, deps) {
5028
5937
  return runFund(rest, deps);
5029
5938
  case "balance":
5030
5939
  return runBalance(rest, deps);
5940
+ case "name":
5941
+ return runName(rest, deps);
5031
5942
  case "help":
5032
5943
  case "--help":
5033
5944
  case "-h":
@@ -5097,13 +6008,15 @@ var RIG_OWNED_VERBS = /* @__PURE__ */ new Set([
5097
6008
  "clone",
5098
6009
  "fetch",
5099
6010
  "push",
6011
+ "site",
5100
6012
  "issue",
5101
6013
  "comment",
5102
6014
  "pr",
5103
6015
  "maintainers",
5104
6016
  "channel",
5105
6017
  "fund",
5106
- "balance"
6018
+ "balance",
6019
+ "name"
5107
6020
  ]);
5108
6021
  function isJsonInvocation(argv2) {
5109
6022
  const [verb, ...rest] = argv2;