@toon-protocol/rig 2.5.0 → 2.6.1

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-PTXKCR5R.js";
21
+ } from "../chunk-NIRC5LFS.js";
22
22
  import {
23
23
  DEFAULT_KEYSTORE_PASSWORD,
24
24
  MissingIdentityError,
@@ -36,7 +36,7 @@ import {
36
36
  } from "../chunk-SW7ZHMGS.js";
37
37
  import {
38
38
  MAX_OBJECT_SIZE
39
- } from "../chunk-X2CZPPDM.js";
39
+ } from "../chunk-B5ISARMU.js";
40
40
  import {
41
41
  COMMENT_KIND,
42
42
  authorizedStatusAuthors,
@@ -60,6 +60,9 @@ import { createRequire } from "module";
60
60
  import { parseArgs as parseArgs3 } from "util";
61
61
 
62
62
  // src/cli/daemon-session.ts
63
+ function daemonSupportsGit(probe) {
64
+ return Array.isArray(probe.capabilities) && probe.capabilities.includes("git");
65
+ }
63
66
  function daemonBaseUrl(env) {
64
67
  const raw = env["TOON_CLIENT_HTTP_PORT"];
65
68
  const parsed = raw ? Number(raw) : NaN;
@@ -84,6 +87,11 @@ async function probeDaemon(env, fetchImpl, timeoutMs = 1500) {
84
87
  if (typeof body?.feePerEvent === "string" && body.feePerEvent !== "") {
85
88
  probe.feePerEvent = body.feePerEvent;
86
89
  }
90
+ if (Array.isArray(body?.capabilities)) {
91
+ probe.capabilities = body.capabilities.filter(
92
+ (c) => typeof c === "string"
93
+ );
94
+ }
87
95
  return probe;
88
96
  } catch {
89
97
  return { baseUrl, reachable: false };
@@ -99,6 +107,19 @@ var DaemonRouteError = class extends Error {
99
107
  status;
100
108
  envelope;
101
109
  };
110
+ var DaemonTooOldForGitError = class extends Error {
111
+ constructor(baseUrl, pubkey) {
112
+ const holds = pubkey !== void 0 && pubkey !== "" ? ` holds this identity (${pubkey.slice(0, 8)}\u2026)` : " holds this identity";
113
+ super(
114
+ `toon-clientd at ${baseUrl}${holds} but is too old to handle git operations (missing /git routes). Upgrade the daemon (npm i -g @toon-protocol/client-mcp@latest, then restart it) \u2014 or stop it to let rig run standalone.`
115
+ );
116
+ this.baseUrl = baseUrl;
117
+ this.pubkey = pubkey;
118
+ this.name = "DaemonTooOldForGitError";
119
+ }
120
+ baseUrl;
121
+ pubkey;
122
+ };
102
123
  var DaemonUnreachableError = class extends Error {
103
124
  constructor(baseUrl, cause) {
104
125
  super(
@@ -110,12 +131,14 @@ var DaemonUnreachableError = class extends Error {
110
131
  baseUrl;
111
132
  };
112
133
  var DaemonGitClient = class {
113
- constructor(baseUrl, fetchImpl) {
134
+ constructor(baseUrl, fetchImpl, pubkey) {
114
135
  this.baseUrl = baseUrl;
115
136
  this.fetchImpl = fetchImpl;
137
+ this.pubkey = pubkey;
116
138
  }
117
139
  baseUrl;
118
140
  fetchImpl;
141
+ pubkey;
119
142
  gitEstimate(req) {
120
143
  return this.post("/git/estimate", req);
121
144
  }
@@ -145,6 +168,9 @@ var DaemonGitClient = class {
145
168
  } catch (err) {
146
169
  throw new DaemonUnreachableError(this.baseUrl, err);
147
170
  }
171
+ if (res.status === 404) {
172
+ throw new DaemonTooOldForGitError(this.baseUrl, this.pubkey);
173
+ }
148
174
  const text = await res.text();
149
175
  let parsed;
150
176
  try {
@@ -175,12 +201,15 @@ async function resolvePaidSession(options) {
175
201
  warn: options.warn
176
202
  });
177
203
  if (identity.pubkey === probe.identity) {
204
+ if (!daemonSupportsGit(probe)) {
205
+ throw new DaemonTooOldForGitError(probe.baseUrl, identity.pubkey);
206
+ }
178
207
  options.warn(
179
208
  `rig: paid path: daemon \u2014 toon-clientd at ${probe.baseUrl} holds this identity (${identity.pubkey.slice(0, 8)}\u2026), delegating`
180
209
  );
181
210
  return {
182
211
  path: "daemon",
183
- client: new DaemonGitClient(probe.baseUrl, fetchImpl),
212
+ client: new DaemonGitClient(probe.baseUrl, fetchImpl, identity.pubkey),
184
213
  baseUrl: probe.baseUrl,
185
214
  identity: {
186
215
  pubkey: identity.pubkey,
@@ -402,6 +431,18 @@ function describeError(err, command = "push") {
402
431
  json: { error: "daemon_unreachable", detail: err.message }
403
432
  };
404
433
  }
434
+ if (err instanceof DaemonTooOldForGitError) {
435
+ return {
436
+ code: "daemon_too_old_for_git",
437
+ lines: err.message.split("\n"),
438
+ json: {
439
+ error: "daemon_too_old_for_git",
440
+ detail: err.message,
441
+ baseUrl: err.baseUrl,
442
+ ...err.pubkey !== void 0 ? { pubkey: err.pubkey } : {}
443
+ }
444
+ };
445
+ }
405
446
  const name = err instanceof Error ? err.name : "";
406
447
  const message = err instanceof Error ? err.message : String(err);
407
448
  if (name === "DaemonIdentityConflictError") {
@@ -508,8 +549,9 @@ function renderPlan(plan) {
508
549
  for (const update of plan.refUpdates) lines.push(refLine(update));
509
550
  const est = plan.estimate;
510
551
  const skipped = Object.keys(plan.knownShaToTxId).length;
552
+ const emptySkipped = est.skippedEmptyCount ?? 0;
511
553
  lines.push(
512
- `Objects: ${est.objectCount} to upload (${formatNumber(est.totalObjectBytes)} bytes)` + (skipped > 0 ? `; ${skipped} already on Arweave (free)` : "")
554
+ `Objects: ${est.objectCount} to upload (${formatNumber(est.totalObjectBytes)} bytes)` + (skipped > 0 ? `; ${skipped} already on Arweave (free)` : "") + (emptySkipped > 0 ? `; ${emptySkipped} empty blob${emptySkipped === 1 ? "" : "s"} skipped (reconstructed on clone)` : "")
513
555
  );
514
556
  lines.push("Fees (base units):");
515
557
  lines.push(
@@ -555,6 +597,12 @@ function renderResult(result) {
555
597
  lines.push(
556
598
  `Uploads: ${paid.length} paid, ${skipped.length} skipped (content-addressed).`
557
599
  );
600
+ const emptySkipped = result.estimate.skippedEmptyCount ?? 0;
601
+ if (emptySkipped > 0) {
602
+ lines.push(
603
+ `Empty blobs: ${emptySkipped} skipped (zero-byte, not uploaded \u2014 reconstructed on clone).`
604
+ );
605
+ }
558
606
  if (result.announceReceipt) {
559
607
  lines.push(
560
608
  `Announcement (kind:30617): ${result.announceReceipt.eventId} paid ${formatNumber(result.announceReceipt.feePaid)}`
@@ -609,7 +657,12 @@ async function resolveRepoRoot(cwd) {
609
657
  }
610
658
  }
611
659
  async function initGitRepository(dir) {
612
- await git(dir, ["init"]);
660
+ try {
661
+ await git(dir, ["init", "--initial-branch=main"]);
662
+ } catch {
663
+ await git(dir, ["init"]);
664
+ await git(dir, ["symbolic-ref", "HEAD", "refs/heads/main"]);
665
+ }
613
666
  }
614
667
  async function readToonConfig(repoPath) {
615
668
  const [repoId, owner, relays] = await Promise.all([
@@ -913,7 +966,7 @@ function loadPaidSession(deps, relayUrl) {
913
966
  });
914
967
  }
915
968
  var defaultLoadStandalone = async (options) => {
916
- const mod = await import("../standalone-mode-BIFFNDH3.js");
969
+ const mod = await import("../standalone-mode-WEMJBHME.js");
917
970
  return mod.createStandaloneContext(options);
918
971
  };
919
972
  function identityReport(ctx) {
@@ -954,7 +1007,20 @@ Options:
954
1007
  --repo-id <id> repository id / NIP-34 d-tag (default: git config
955
1008
  toon.repoid \u2014 run \`rig init\` to set it)
956
1009
  -h, --help show this help`;
957
- async function selectRefspecs(reader, positionals, all, tags) {
1010
+ function noSuchRefMessage(spec, head, refs, remoteName) {
1011
+ const branches = refs.filter((r) => r.refname.startsWith("refs/heads/")).map((r) => r.refname.slice("refs/heads/".length));
1012
+ const current = head && head.startsWith("refs/heads/") ? head.slice("refs/heads/".length) : void 0;
1013
+ let msg = `no local branch or tag ${JSON.stringify(spec)}`;
1014
+ if (current) {
1015
+ msg += ` \u2014 your current branch is ${JSON.stringify(current)} (did you mean \`rig push ${remoteName} ${current}\`?)`;
1016
+ } else if (branches.length > 0) {
1017
+ msg += ` \u2014 local branches: ${branches.join(", ")}`;
1018
+ } else {
1019
+ msg += " \u2014 this repository has no branches yet (make a commit first)";
1020
+ }
1021
+ return msg;
1022
+ }
1023
+ async function selectRefspecs(reader, positionals, all, tags, remoteName = "origin") {
958
1024
  const { head, refs } = await reader.listRefs();
959
1025
  const byName = new Set(refs.map((r) => r.refname));
960
1026
  const selected = [];
@@ -968,10 +1034,12 @@ async function selectRefspecs(reader, positionals, all, tags) {
968
1034
  add(`refs/heads/${spec}`);
969
1035
  } else if (byName.has(`refs/tags/${spec}`)) {
970
1036
  add(`refs/tags/${spec}`);
971
- } else {
1037
+ } else if (spec.startsWith(":")) {
972
1038
  throw new Error(
973
- `refspec ${JSON.stringify(spec)} matches no local branch or tag (ref deletion is out of scope in v1)`
1039
+ `deleting remote refs (${JSON.stringify(spec)}) is out of scope in v1`
974
1040
  );
1041
+ } else {
1042
+ throw new Error(noSuchRefMessage(spec, head, refs, remoteName));
975
1043
  }
976
1044
  }
977
1045
  if (all) {
@@ -1068,7 +1136,8 @@ async function runPush(args, deps) {
1068
1136
  reader,
1069
1137
  refspecArgs,
1070
1138
  flags.all,
1071
- flags.tags
1139
+ flags.tags,
1140
+ remoteName ?? "origin"
1072
1141
  );
1073
1142
  const resolved = await resolveRelays({
1074
1143
  relayFlags: flags.relay,
@@ -3462,24 +3531,37 @@ import { join as join2 } from "path";
3462
3531
  import { parseArgs as parseArgs9 } from "util";
3463
3532
  var DEVNET_FAUCET_URL = "https://faucet.devnet.toonprotocol.dev";
3464
3533
  var CHAINS = ["evm", "solana", "mina"];
3534
+ var CHAIN_CHOICES = [...CHAINS, "all"];
3535
+ var NATIVE_COIN = {
3536
+ evm: "ETH",
3537
+ solana: "SOL",
3538
+ mina: "MINA"
3539
+ };
3465
3540
  var FUND_USAGE = `Usage: rig fund [options]
3466
3541
 
3467
3542
  Drip devnet test funds to the active identity's wallet \u2014 free (the faucet
3468
- pays). The identity comes from RIG_MNEMONIC (env or a project .env) or the
3543
+ pays). By default funds ALL supported chains (evm + solana + mina), each drip
3544
+ covering the native coin AND USDC, so the wallet matches the multi-chain
3545
+ \`rig balance\` view in one run. The per-chain drips run in parallel and are
3546
+ independent \u2014 one chain's faucet failing does not abort the others (exit 0
3547
+ only when every targeted chain funded; exit 1 if any failed, with the
3548
+ per-chain breakdown always shown).
3549
+
3550
+ The identity comes from RIG_MNEMONIC (env or a project .env) or the
3469
3551
  ~/.toon-client keystore/config; the faucet from TOON_CLIENT_FAUCET_URL, the
3470
3552
  faucetUrl config field, or the deployed devnet faucet when the network is
3471
3553
  devnet \u2014 including when a configured *.devnet.toonprotocol.dev origin infers
3472
3554
  it (a devnet relay/proxy/btp endpoint, or the git origin remote from
3473
3555
  \`rig remote add origin <devnet relay>\`). The faucet drips a FIXED amount per
3474
- chain (there is no --amount). On a
3475
- network without a faucet, prints the wallet address(es) to fund externally
3476
- instead.
3556
+ chain (there is no --amount). On a network without a faucet, prints the wallet
3557
+ address(es) to fund externally instead.
3477
3558
 
3478
3559
  Options:
3479
- --chain <chain> evm | solana | mina (default: TOON_CLIENT_CHAIN, the
3480
- \`chain\` config field, else evm)
3560
+ --chain <chain> evm | solana | mina | all \u2014 fund one chain, or all
3561
+ (default: all supported chains)
3481
3562
  --address <address> fund this address instead of the identity's own
3482
- --json machine-readable envelope
3563
+ (requires an explicit single --chain)
3564
+ --json machine-readable envelope (per-chain results array)
3483
3565
  -h, --help show this help`;
3484
3566
  var SHARED_DEVNET_SUFFIX = ".devnet.toonprotocol.dev";
3485
3567
  function devnetHost(url) {
@@ -3573,9 +3655,14 @@ async function runFund(args, deps) {
3573
3655
  chainFlag = values.chain;
3574
3656
  addressFlag = values.address;
3575
3657
  json = values.json ?? false;
3576
- if (chainFlag !== void 0 && !CHAINS.includes(chainFlag)) {
3658
+ if (chainFlag !== void 0 && !CHAIN_CHOICES.includes(chainFlag)) {
3577
3659
  throw new Error(
3578
- `--chain must be one of ${CHAINS.join(" | ")}, got ${JSON.stringify(chainFlag)}`
3660
+ `--chain must be one of ${CHAIN_CHOICES.join(" | ")}, got ${JSON.stringify(chainFlag)}`
3661
+ );
3662
+ }
3663
+ if (addressFlag !== void 0 && (chainFlag === void 0 || chainFlag === "all")) {
3664
+ throw new Error(
3665
+ "--address requires an explicit single --chain (evm | solana | mina) \u2014 a single address cannot fund every chain"
3579
3666
  );
3580
3667
  }
3581
3668
  } catch (err) {
@@ -3585,12 +3672,7 @@ async function runFund(args, deps) {
3585
3672
  }
3586
3673
  try {
3587
3674
  const { file } = readFundConfig(env);
3588
- const chain = chainFlag ?? env["TOON_CLIENT_CHAIN"] ?? file.chain ?? "evm";
3589
- if (!CHAINS.includes(chain)) {
3590
- throw new Error(
3591
- `configured settlement chain ${JSON.stringify(chain)} has no faucet \u2014 pass --chain ${CHAINS.join(" | ")}`
3592
- );
3593
- }
3675
+ const targetChains = chainFlag === void 0 || chainFlag === "all" ? [...CHAINS] : [chainFlag];
3594
3676
  const network = env["TOON_CLIENT_NETWORK"] ?? file.network;
3595
3677
  const canInfer = network === void 0 || network === "custom";
3596
3678
  const originRelays = canInfer ? await resolveOriginRelays(deps.cwd) : [];
@@ -3626,7 +3708,6 @@ async function runFund(args, deps) {
3626
3708
  identity,
3627
3709
  funded: false,
3628
3710
  network: network ?? null,
3629
- chain,
3630
3711
  addresses,
3631
3712
  guidance
3632
3713
  });
@@ -3640,46 +3721,85 @@ async function runFund(args, deps) {
3640
3721
  io2.out(` mina ${addresses.mina ?? "(no key derived \u2014 optional mina-signer dependency missing)"}`);
3641
3722
  return 0;
3642
3723
  }
3643
- const address = addressFlag ?? addresses[chain];
3644
- if (!address) {
3645
- throw new Error(
3646
- `no ${chain} address could be derived for this identity \u2014 pass an explicit --address (for mina, install the optional mina-signer dependency)`
3647
- );
3648
- }
3649
3724
  const timeoutEnv = env["TOON_CLIENT_FAUCET_TIMEOUT_MS"];
3650
- const timeout = timeoutEnv && Number.isFinite(Number(timeoutEnv)) ? Number(timeoutEnv) : file.faucetTimeoutMs ?? (chain === "mina" ? 13e4 : 9e4);
3725
+ const timeoutFor = (chain) => {
3726
+ if (timeoutEnv && Number.isFinite(Number(timeoutEnv))) return Number(timeoutEnv);
3727
+ if (file.faucetTimeoutMs !== void 0) return file.faucetTimeoutMs;
3728
+ return chain === "mina" ? 13e4 : 9e4;
3729
+ };
3651
3730
  if (!json && inferredDevnet) {
3652
3731
  io2.out(
3653
3732
  `Inferred network 'devnet' from the configured origin ${devnetOrigin} (network was ${JSON.stringify(network ?? "custom")}). Set TOON_CLIENT_NETWORK explicitly to override.`
3654
3733
  );
3655
3734
  }
3656
3735
  if (!json) {
3736
+ const list = targetChains.join(", ");
3657
3737
  io2.out(
3658
- `Requesting ${chain} drip from ${faucetUrl} for ${address} \u2026` + (chain === "mina" ? " (mina settles slowly; this can take ~2 minutes)" : "")
3738
+ `Requesting ${targetChains.length === 1 ? "" : "parallel "}drip${targetChains.length === 1 ? "" : "s"} from ${faucetUrl} for ${list} \u2026` + (targetChains.includes("mina") ? " (mina settles slowly; this can take ~2 minutes)" : "")
3659
3739
  );
3660
3740
  }
3661
- const { response } = await client.fundWallet(faucetUrl, address, chain, {
3662
- timeout,
3663
- ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
3741
+ const singleChain = targetChains.length === 1;
3742
+ const drips = targetChains.map(async (chain) => {
3743
+ const address = (singleChain ? addressFlag : void 0) ?? addresses[chain];
3744
+ if (!address) {
3745
+ return {
3746
+ chain,
3747
+ funded: false,
3748
+ address: null,
3749
+ error: `no ${chain} address could be derived for this identity` + (chain === "mina" ? " (install the optional mina-signer dependency)" : "")
3750
+ };
3751
+ }
3752
+ const started = Date.now();
3753
+ try {
3754
+ const { response } = await client.fundWallet(faucetUrl, address, chain, {
3755
+ timeout: timeoutFor(chain),
3756
+ ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
3757
+ });
3758
+ return {
3759
+ chain,
3760
+ funded: true,
3761
+ address,
3762
+ response,
3763
+ elapsedMs: Date.now() - started
3764
+ };
3765
+ } catch (err) {
3766
+ return {
3767
+ chain,
3768
+ funded: false,
3769
+ address,
3770
+ error: err instanceof Error ? err.message : String(err),
3771
+ elapsedMs: Date.now() - started
3772
+ };
3773
+ }
3664
3774
  });
3775
+ const results = await Promise.all(drips);
3776
+ const allFunded = results.every((r) => r.funded);
3665
3777
  if (json) {
3666
3778
  io2.emitJson({
3667
3779
  command: "fund",
3668
3780
  identity,
3669
- funded: true,
3781
+ funded: allFunded,
3670
3782
  network: effectiveNetwork ?? null,
3671
- chain,
3672
- address,
3673
3783
  faucetUrl,
3674
- response,
3784
+ results,
3675
3785
  ...inferredDevnet ? { inferredDevnetFrom: devnetOrigin } : {}
3676
3786
  });
3677
- return 0;
3787
+ return allFunded ? 0 : 1;
3788
+ }
3789
+ for (const r of results) {
3790
+ const label = r.chain.padEnd(6);
3791
+ const addr = r.address ? ` \u2192 ${r.address}` : "";
3792
+ if (r.funded) {
3793
+ const slow = r.elapsedMs !== void 0 && r.elapsedMs >= 5e3;
3794
+ const time = slow ? ` (${Math.round(r.elapsedMs / 1e3)}s)` : "";
3795
+ io2.out(` ${label} \u2713 funded (${NATIVE_COIN[r.chain]} + USDC)${addr}${time}`);
3796
+ } else {
3797
+ io2.out(` ${label} \u2717${addr} \u2014 ${r.error ?? "failed"}`);
3798
+ }
3678
3799
  }
3679
- io2.out(`Faucet drip succeeded: ${chain} \u2192 ${address}`);
3680
3800
  io2.out(renderIdentityLine(identity));
3681
3801
  io2.out("Re-check with `rig balance` (a drip can take a few blocks to land).");
3682
- return 0;
3802
+ return allFunded ? 0 : 1;
3683
3803
  } catch (err) {
3684
3804
  return emitCliError(io2, json, "fund", err);
3685
3805
  }