@toon-protocol/rig 5.2.0 → 5.4.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.
@@ -4,12 +4,14 @@ import {
4
4
  CI_SERVICE_REQUEST_KIND,
5
5
  CI_SERVICE_STOP_KIND,
6
6
  CI_STATUS_USAGE,
7
+ CI_TRUST_ORDER,
7
8
  CI_WORKFLOW_PROGRESS_KIND,
8
9
  CI_WORKFLOW_RESULT_KIND,
9
10
  GitRepoReader,
10
11
  InvalidRelayUrlError,
11
12
  MultiUrlRemoteError,
12
13
  NotAGitRepositoryError,
14
+ PrMergeError,
13
15
  RESERVED_SECRET_NAMES,
14
16
  SECRET_NAME_RE,
15
17
  UnconfiguredRepoAddressError,
@@ -26,11 +28,13 @@ import {
26
28
  earliestUniqueCommit,
27
29
  emitCliError,
28
30
  encryptSecretUpdate,
31
+ evaluateCiGate,
29
32
  executePush,
30
33
  generateSecretsKey,
31
34
  getGitRemoteUrls,
32
35
  hexToNpub,
33
36
  initGitRepository,
37
+ isCiTrustLevel,
34
38
  isRelayUrl,
35
39
  isSafeRefname,
36
40
  listGitRemotes,
@@ -57,13 +61,13 @@ import {
57
61
  updateRef,
58
62
  writeGitObjects,
59
63
  writeToonConfig
60
- } from "./chunk-S5R44MMB.js";
64
+ } from "./chunk-MIK7SRWE.js";
61
65
  import {
62
66
  PREFERRED_GATEWAY,
63
67
  mirrorGatewaysFor,
64
68
  readGateways,
65
69
  shaResolverFor
66
- } from "./chunk-FX5GPZYU.js";
70
+ } from "./chunk-QSUO7J4H.js";
67
71
  import {
68
72
  COMMENT_KIND,
69
73
  DEFAULT_KEYSTORE_PASSWORD,
@@ -92,7 +96,7 @@ import {
92
96
  resolveIdentity,
93
97
  revisionRootId,
94
98
  selectRevision
95
- } from "./chunk-JX5P2KAA.js";
99
+ } from "./chunk-3KS6S4S5.js";
96
100
  import {
97
101
  ChannelMapStore,
98
102
  channelStatus,
@@ -321,7 +325,7 @@ function loadPaidSession(deps, relayUrl) {
321
325
  });
322
326
  }
323
327
  var defaultLoadStandalone = async (options) => {
324
- const mod = await import("./standalone-mode-NWCCRFCL.js");
328
+ const mod = await import("./standalone-mode-LUWNCDPL.js");
325
329
  return mod.createStandaloneContext(options);
326
330
  };
327
331
  function identityReport(ctx) {
@@ -2306,9 +2310,9 @@ async function runCiTrigger(args, deps) {
2306
2310
  });
2307
2311
  }
2308
2312
  async function gitShowBytes(repoRoot, commit, path) {
2309
- const { execFile } = await import("child_process");
2313
+ const { execFile: execFile2 } = await import("child_process");
2310
2314
  return new Promise((resolve2, reject) => {
2311
- execFile(
2315
+ execFile2(
2312
2316
  "git",
2313
2317
  ["show", `${commit}:${path}`],
2314
2318
  { cwd: repoRoot, encoding: "buffer", maxBuffer: 16 * 1024 * 1024 },
@@ -2519,7 +2523,7 @@ async function runCi(args, deps) {
2519
2523
  case "status":
2520
2524
  return runCiStatus(rest, deps);
2521
2525
  case "serve":
2522
- return (await import("./ci-serve-CA37KQVC.js")).runCiServe(rest, deps);
2526
+ return (await import("./ci-serve-4RJCKEPV.js")).runCiServe(rest, deps);
2523
2527
  case "--help":
2524
2528
  case "-h":
2525
2529
  case "help":
@@ -2790,18 +2794,258 @@ async function runClone(args, deps) {
2790
2794
 
2791
2795
  // src/cli/events.ts
2792
2796
  import { readFile } from "fs/promises";
2793
- import { parseArgs as parseArgs8 } from "util";
2797
+ import { parseArgs as parseArgs10 } from "util";
2798
+ import {
2799
+ PATCH_KIND as PATCH_KIND3,
2800
+ REPOSITORY_ANNOUNCEMENT_KIND as REPOSITORY_ANNOUNCEMENT_KIND3,
2801
+ STATUS_APPLIED_KIND as STATUS_APPLIED_KIND3,
2802
+ STATUS_CLOSED_KIND as STATUS_CLOSED_KIND3,
2803
+ STATUS_DRAFT_KIND as STATUS_DRAFT_KIND2,
2804
+ STATUS_OPEN_KIND as STATUS_OPEN_KIND2
2805
+ } from "@toon-protocol/core/nip34";
2806
+
2807
+ // src/cli/merge.ts
2808
+ import { execFile } from "child_process";
2809
+ import { mkdtemp as mkdtemp2, rm as rm2, writeFile } from "fs/promises";
2810
+ import { tmpdir } from "os";
2811
+ import { join as join4 } from "path";
2812
+ import { parseArgs as parseArgs9 } from "util";
2813
+ import { promisify } from "util";
2794
2814
  import {
2795
2815
  PATCH_KIND as PATCH_KIND2,
2796
2816
  REPOSITORY_ANNOUNCEMENT_KIND as REPOSITORY_ANNOUNCEMENT_KIND2,
2797
2817
  STATUS_APPLIED_KIND as STATUS_APPLIED_KIND2,
2798
- STATUS_CLOSED_KIND as STATUS_CLOSED_KIND2,
2799
- STATUS_DRAFT_KIND as STATUS_DRAFT_KIND2,
2800
- STATUS_OPEN_KIND as STATUS_OPEN_KIND2
2818
+ STATUS_CLOSED_KIND as STATUS_CLOSED_KIND2
2801
2819
  } from "@toon-protocol/core/nip34";
2802
2820
 
2803
- // src/cli/tracker.ts
2821
+ // src/cli/fetch.ts
2804
2822
  import { parseArgs as parseArgs7 } from "util";
2823
+ var FETCH_USAGE = `Usage: rig fetch [remote] [options]
2824
+
2825
+ Fetch from a TOON remote \u2014 FREE (relay reads + Arweave gateway downloads; no
2826
+ payments, no identity needed). Reads the kind:30618 repository state from the
2827
+ remote's relay, downloads only the git objects this repository is missing
2828
+ (SHA-1 verified), and updates the remote-tracking refs
2829
+ (refs/remotes/<remote>/*; tags land at refs/tags/*), reporting what moved.
2830
+
2831
+ No merge happens: integrate with the git passthrough, e.g.
2832
+ \`rig merge origin/main\`. \`rig fetch\` is the TOON transport and shadows
2833
+ \`git fetch\` \u2014 plain-git fetches remain available via \`git fetch\` directly.
2834
+
2835
+ The repo address (toon.repoid/toon.owner) comes from the config \`rig init\`
2836
+ or \`rig clone\` writes; --repo-id/--owner override.
2837
+
2838
+ Options:
2839
+ --repo-id <id> repository id / NIP-34 d-tag (default: git config)
2840
+ --owner <pubkey> repository owner (npub or 64-char hex; default: git config)
2841
+ --relay <url> ad-hoc relay override \u2014 bypasses the remote's URL
2842
+ --gateway <url> gateway to read objects through FIRST, on its raw-bytes
2843
+ route <url>/raw/<txId>, ahead of the public gateway
2844
+ list \u2014 name a private, local or air-gapped gateway to
2845
+ fetch objects the public ones do not carry
2846
+ (env RIG_ARWEAVE_GATEWAY also sets it)
2847
+ --concurrency <n> parallel gateway downloads (default 8)
2848
+ --json machine-readable result envelope
2849
+ -h, --help show this help`;
2850
+ var WS_URL_RE3 = /^wss?:\/\//i;
2851
+ function localRefFor(refname, remote) {
2852
+ if (refname.startsWith("refs/heads/")) {
2853
+ return `refs/remotes/${remote}/${refname.slice("refs/heads/".length)}`;
2854
+ }
2855
+ if (refname.startsWith("refs/tags/")) return refname;
2856
+ return null;
2857
+ }
2858
+ async function resolveLocalRef(repoRoot, refname) {
2859
+ try {
2860
+ const out = await runGit(repoRoot, [
2861
+ "rev-parse",
2862
+ "--verify",
2863
+ "--quiet",
2864
+ refname
2865
+ ]);
2866
+ const sha = out.trim();
2867
+ return /^[0-9a-f]{40}$/.test(sha) ? sha : null;
2868
+ } catch {
2869
+ return null;
2870
+ }
2871
+ }
2872
+ async function runFetch(args, deps) {
2873
+ const { io } = deps;
2874
+ let json = false;
2875
+ let remote = "origin";
2876
+ let relayFlag;
2877
+ let repoIdFlag;
2878
+ let ownerFlag;
2879
+ let gatewayFlag;
2880
+ let concurrency;
2881
+ try {
2882
+ const { values, positionals } = parseArgs7({
2883
+ args,
2884
+ options: {
2885
+ json: { type: "boolean", default: false },
2886
+ relay: { type: "string" },
2887
+ "repo-id": { type: "string" },
2888
+ owner: { type: "string" },
2889
+ gateway: { type: "string" },
2890
+ concurrency: { type: "string" },
2891
+ help: { type: "boolean", short: "h", default: false }
2892
+ },
2893
+ allowPositionals: true
2894
+ });
2895
+ if (values.help) {
2896
+ io.out(FETCH_USAGE);
2897
+ return 0;
2898
+ }
2899
+ json = values.json === true;
2900
+ if (positionals.length > 1) {
2901
+ throw new Error(
2902
+ `expected at most one [remote] argument, got ${positionals.length}`
2903
+ );
2904
+ }
2905
+ if (positionals[0] !== void 0) remote = positionals[0];
2906
+ relayFlag = values.relay;
2907
+ repoIdFlag = values["repo-id"];
2908
+ ownerFlag = values.owner !== void 0 ? ownerToHex(values.owner) : void 0;
2909
+ gatewayFlag = values.gateway;
2910
+ if (values.concurrency !== void 0) {
2911
+ concurrency = Number.parseInt(values.concurrency, 10);
2912
+ if (!Number.isSafeInteger(concurrency) || concurrency < 1) {
2913
+ throw new Error("--concurrency must be a positive integer");
2914
+ }
2915
+ }
2916
+ } catch (err) {
2917
+ io.err(err instanceof Error ? err.message : String(err));
2918
+ io.err(FETCH_USAGE);
2919
+ return 2;
2920
+ }
2921
+ try {
2922
+ const repoRoot = await resolveRepoRoot(deps.cwd);
2923
+ const toonConfig = await readToonConfig(repoRoot);
2924
+ const repoId = repoIdFlag ?? toonConfig.repoId;
2925
+ if (!repoId) throw new UnconfiguredRepoAddressError("repository id");
2926
+ const owner = ownerFlag ?? toonConfig.owner;
2927
+ if (!owner) throw new UnconfiguredRepoAddressError("repository owner");
2928
+ let relayUrl;
2929
+ if (relayFlag !== void 0) {
2930
+ relayUrl = relayFlag;
2931
+ } else {
2932
+ const urls = await getGitRemoteUrls(repoRoot, remote);
2933
+ if (urls.length === 0) throw new UnknownRemoteError(remote);
2934
+ if (urls.length > 1) throw new MultiUrlRemoteError(remote, urls);
2935
+ relayUrl = urls[0];
2936
+ }
2937
+ if (!WS_URL_RE3.test(relayUrl)) {
2938
+ throw new InvalidRelayUrlError(
2939
+ relayUrl,
2940
+ `remote ${JSON.stringify(remote)}`
2941
+ );
2942
+ }
2943
+ const remoteState = await fetchRemoteState({
2944
+ relayUrls: [relayUrl],
2945
+ ownerPubkey: owner,
2946
+ repoId,
2947
+ ...deps.webSocketFactory ? { webSocketFactory: deps.webSocketFactory } : {},
2948
+ // The GraphQL fallback for SHAs the map does not cover asks the SAME
2949
+ // permaweb the objects are read from (#183).
2950
+ resolveSha: deps.resolveSha ?? shaResolverFor(gatewayFlag, deps.env)
2951
+ });
2952
+ const planned = [];
2953
+ for (const [refname, sha] of remoteState.refs) {
2954
+ if (!isSafeRefname(refname)) {
2955
+ io.err(
2956
+ `warning: skipping unsafe ref name from relay: ${JSON.stringify(refname)}`
2957
+ );
2958
+ continue;
2959
+ }
2960
+ const localRef = localRefFor(refname, remote);
2961
+ if (localRef === null) {
2962
+ io.err(
2963
+ `warning: skipping ref outside refs/heads and refs/tags: ${refname}`
2964
+ );
2965
+ continue;
2966
+ }
2967
+ planned.push({ refname, localRef, newSha: sha });
2968
+ }
2969
+ const reader = new GitRepoReader(repoRoot);
2970
+ const tips = [...new Set(planned.map((p) => p.newSha))];
2971
+ const presentLocally = new Set(await reader.listAllObjectShas());
2972
+ const collected = await collectRepoObjects({
2973
+ tips,
2974
+ shaToTxId: remoteState.shaToTxId,
2975
+ resolveMissing: (shas) => remoteState.resolveMissing(shas),
2976
+ presentLocally,
2977
+ // `--gateway` / RIG_ARWEAVE_GATEWAY first, public list behind it (#176).
2978
+ gateways: readGateways(gatewayFlag, deps.env),
2979
+ ...deps.fetchFn ? { fetchFn: deps.fetchFn } : {},
2980
+ ...concurrency !== void 0 ? { concurrency } : {}
2981
+ });
2982
+ if (collected.missing.length > 0) {
2983
+ throw new MissingRemoteObjectsError(
2984
+ collected.missing,
2985
+ `cannot fetch from ${relayUrl}`
2986
+ );
2987
+ }
2988
+ const written = await writeGitObjects(repoRoot, collected.objects.values());
2989
+ const updates = [];
2990
+ for (const { refname, localRef, newSha } of planned) {
2991
+ const oldSha = await resolveLocalRef(repoRoot, localRef);
2992
+ if (oldSha === newSha) {
2993
+ updates.push({ refname, localRef, oldSha, newSha, kind: "up-to-date" });
2994
+ continue;
2995
+ }
2996
+ let kind;
2997
+ if (oldSha === null) {
2998
+ kind = "new";
2999
+ } else {
3000
+ kind = await reader.isAncestor(oldSha, newSha) ? "fast-forward" : "forced";
3001
+ }
3002
+ await updateRef(repoRoot, localRef, newSha);
3003
+ updates.push({ refname, localRef, oldSha, newSha, kind });
3004
+ }
3005
+ const moved = updates.filter((u) => u.kind !== "up-to-date");
3006
+ if (json) {
3007
+ io.emitJson({
3008
+ command: "fetch",
3009
+ repoAddr: { ownerPubkey: owner, repoId },
3010
+ remote,
3011
+ relay: relayUrl,
3012
+ objectsDownloaded: written,
3013
+ updates,
3014
+ executed: true
3015
+ });
3016
+ return 0;
3017
+ }
3018
+ if (moved.length === 0) {
3019
+ io.out("Already up to date.");
3020
+ return 0;
3021
+ }
3022
+ io.out(`From ${relayUrl}`);
3023
+ for (const u of moved) {
3024
+ const short = (refname) => refname.replace(/^refs\/heads\//, "").replace(/^refs\/tags\//, "").replace(/^refs\/remotes\//, "");
3025
+ const src = short(u.refname);
3026
+ const dst = short(u.localRef);
3027
+ if (u.kind === "new") {
3028
+ const label = u.refname.startsWith("refs/tags/") ? "[new tag]" : "[new branch]";
3029
+ io.out(` * ${label.padEnd(17)} ${src.padEnd(14)} -> ${dst}`);
3030
+ } else if (u.kind === "forced") {
3031
+ io.out(
3032
+ ` + ${u.oldSha.slice(0, 7)}...${u.newSha.slice(0, 7)} ${src.padEnd(14)} -> ${dst} (forced update)`
3033
+ );
3034
+ } else {
3035
+ io.out(
3036
+ ` ${u.oldSha.slice(0, 7)}..${u.newSha.slice(0, 7)} ${src.padEnd(14)} -> ${dst}`
3037
+ );
3038
+ }
3039
+ }
3040
+ io.out(`Downloaded ${written} object(s) (SHA-1 verified).`);
3041
+ return 0;
3042
+ } catch (err) {
3043
+ return emitCliError(io, json, "fetch", err);
3044
+ }
3045
+ }
3046
+
3047
+ // src/cli/tracker.ts
3048
+ import { parseArgs as parseArgs8 } from "util";
2805
3049
  import {
2806
3050
  ISSUE_KIND,
2807
3051
  PATCH_KIND,
@@ -2887,7 +3131,7 @@ function withTargetAuthor(authorized, targetAuthorPubkey) {
2887
3131
  }
2888
3132
  var RELAY_TIMEOUT_MS = 1e4;
2889
3133
  var HEX64_RE2 = /^[0-9a-f]{64}$/;
2890
- var WS_URL_RE3 = /^wss?:\/\//i;
3134
+ var WS_URL_RE4 = /^wss?:\/\//i;
2891
3135
  function defaultWebSocketFactory2(url) {
2892
3136
  const ctor = globalThis.WebSocket;
2893
3137
  if (!ctor) {
@@ -2971,7 +3215,7 @@ async function resolveTrackerContext(flags, deps, needRepoAddr) {
2971
3215
  repoRoot,
2972
3216
  toonRelays: toonConfig.relays
2973
3217
  });
2974
- const wsRelays = resolved.relays.filter((url) => WS_URL_RE3.test(url));
3218
+ const wsRelays = resolved.relays.filter((url) => WS_URL_RE4.test(url));
2975
3219
  if (wsRelays.length === 0) {
2976
3220
  throw new InvalidRelayUrlError(
2977
3221
  resolved.relays[0] ?? "",
@@ -3198,7 +3442,7 @@ async function runList(args, deps, spec) {
3198
3442
  const { io } = deps;
3199
3443
  let flags;
3200
3444
  try {
3201
- const { values, positionals } = parseArgs7({
3445
+ const { values, positionals } = parseArgs8({
3202
3446
  args,
3203
3447
  options: TRACKER_OPTIONS,
3204
3448
  allowPositionals: true
@@ -3263,7 +3507,7 @@ async function runShow(args, deps, spec) {
3263
3507
  let flags;
3264
3508
  let eventId;
3265
3509
  try {
3266
- const { values, positionals } = parseArgs7({
3510
+ const { values, positionals } = parseArgs8({
3267
3511
  args,
3268
3512
  options: TRACKER_OPTIONS,
3269
3513
  allowPositionals: true
@@ -3381,14 +3625,524 @@ function runPrList(args, deps) {
3381
3625
  jsonKey: "prs"
3382
3626
  });
3383
3627
  }
3384
- function runPrShow(args, deps) {
3385
- return runShow(args, deps, {
3386
- command: "pr show",
3387
- kind: PATCH_KIND,
3388
- usage: PR_SHOW_USAGE,
3389
- jsonKey: "pr",
3390
- bodyLabel: "Patch"
3391
- });
3628
+ function runPrShow(args, deps) {
3629
+ return runShow(args, deps, {
3630
+ command: "pr show",
3631
+ kind: PATCH_KIND,
3632
+ usage: PR_SHOW_USAGE,
3633
+ jsonKey: "pr",
3634
+ bodyLabel: "Patch"
3635
+ });
3636
+ }
3637
+
3638
+ // src/cli/merge.ts
3639
+ var execFileAsync = promisify(execFile);
3640
+ var HEX64_RE3 = /^[0-9a-f]{64}$/;
3641
+ var PR_MERGE_USAGE = `Usage: rig pr merge <patch-id> [--target <branch>] [options]
3642
+
3643
+ Apply the latest authorized revision of a patch with \`git am\` onto the
3644
+ honored tip of the target branch, push the result, and publish an applied
3645
+ status (kind:1631) naming the new commit SHAs \u2014 in that order, so the status
3646
+ never claims a merge that did not happen. A paid command; writes are permanent
3647
+ and non-refundable. Only the owner or a declared maintainer may merge.
3648
+
3649
+ If the patch does not apply, nothing is spent and your working tree is left as
3650
+ it was. If the push succeeds but the status publish fails, the merge is
3651
+ reported as a partial success; retry with \`rig pr status <patch-id> applied\`.
3652
+
3653
+ Options:
3654
+ --target <branch> branch to merge onto (default: the patch's branch-name
3655
+ tag, else the repo's default branch)
3656
+ --require-ci-trust <level>
3657
+ refuse the merge, before paying, unless the patch
3658
+ series' head commit has a concluded green CI run at or
3659
+ above this trust level (same gate as \`rig ci status\`)
3660
+ --closes <issue-id> after a successful merge, publish closed (kind:1632)
3661
+ for this issue; repeatable
3662
+ --yes skip the confirmation prompt
3663
+ --json machine-readable output (without --yes: estimate only)
3664
+ --relay <url> ad-hoc relay override (bypasses git remotes)
3665
+ --remote <name> git remote to use (default: origin)
3666
+ --repo-id <id> repository id (default: git config toon.repoid)
3667
+ --owner <pubkey> repository owner (default: git config toon.owner)`;
3668
+ function parseMergeArgs(args) {
3669
+ const { values, positionals } = parseArgs9({
3670
+ args,
3671
+ options: {
3672
+ target: { type: "string" },
3673
+ yes: { type: "boolean", default: false },
3674
+ json: { type: "boolean", default: false },
3675
+ relay: { type: "string", multiple: true },
3676
+ remote: { type: "string" },
3677
+ "repo-id": { type: "string" },
3678
+ owner: { type: "string" },
3679
+ "require-ci-trust": { type: "string" },
3680
+ closes: { type: "string", multiple: true },
3681
+ standalone: { type: "boolean", default: false },
3682
+ "no-daemon": { type: "boolean", default: false },
3683
+ help: { type: "boolean", short: "h", default: false }
3684
+ },
3685
+ allowPositionals: true
3686
+ });
3687
+ if (values.help) return "help";
3688
+ if (positionals.length !== 1) {
3689
+ throw new Error("expected exactly one argument: <patch-id>");
3690
+ }
3691
+ const patchId = positionals[0];
3692
+ if (!HEX64_RE3.test(patchId)) {
3693
+ throw new Error("<patch-id> must be a 64-character lowercase hex event id");
3694
+ }
3695
+ if (values.owner !== void 0 && !HEX64_RE3.test(values.owner)) {
3696
+ throw new Error("--owner must be a 64-character lowercase hex pubkey");
3697
+ }
3698
+ const flags = {
3699
+ patchId,
3700
+ yes: values.yes === true,
3701
+ json: values.json === true,
3702
+ relay: values.relay ?? [],
3703
+ closes: [...new Set(values.closes ?? [])]
3704
+ };
3705
+ for (const id of flags.closes) {
3706
+ if (!HEX64_RE3.test(id)) {
3707
+ throw new Error("--closes must be a 64-character lowercase hex issue id");
3708
+ }
3709
+ }
3710
+ const level = values["require-ci-trust"];
3711
+ if (level !== void 0) {
3712
+ if (!isCiTrustLevel(level)) {
3713
+ throw new Error(
3714
+ `--require-ci-trust must be one of ${CI_TRUST_ORDER.join(" | ")} (got ${JSON.stringify(level)})`
3715
+ );
3716
+ }
3717
+ flags.requireTrust = level;
3718
+ }
3719
+ if (values.target !== void 0) flags.target = values.target;
3720
+ if (values.remote !== void 0) flags.remote = values.remote;
3721
+ if (values["repo-id"] !== void 0) flags.repoId = values["repo-id"];
3722
+ if (values.owner !== void 0) flags.owner = values.owner;
3723
+ return flags;
3724
+ }
3725
+ async function git(cwd, args, env) {
3726
+ const { stdout } = await execFileAsync("git", args, {
3727
+ cwd,
3728
+ encoding: "utf-8",
3729
+ maxBuffer: 64 * 1024 * 1024,
3730
+ ...env ? { env } : {}
3731
+ });
3732
+ return stdout.trim();
3733
+ }
3734
+ async function gitOk(cwd, args) {
3735
+ try {
3736
+ await git(cwd, args);
3737
+ return true;
3738
+ } catch {
3739
+ return false;
3740
+ }
3741
+ }
3742
+ function gitFailure(err) {
3743
+ const e = err;
3744
+ return (e.stderr || e.stdout || e.message || String(err)).trim();
3745
+ }
3746
+ var OverlayReader = class extends GitRepoReader {
3747
+ constructor(repoPath, overlay) {
3748
+ super(repoPath);
3749
+ this.overlay = overlay;
3750
+ }
3751
+ overlay;
3752
+ async listRefs() {
3753
+ const base = await super.listRefs();
3754
+ const refs = base.refs.filter(
3755
+ (r) => r.refname !== this.overlay.refname
3756
+ );
3757
+ refs.push({
3758
+ refname: this.overlay.refname,
3759
+ sha: this.overlay.sha,
3760
+ type: "commit"
3761
+ });
3762
+ return { ...base, refs };
3763
+ }
3764
+ };
3765
+ async function applyInIsolation(repoRoot, tip, patchText, committer) {
3766
+ const scratch = await mkdtemp2(join4(tmpdir(), "rig-merge-"));
3767
+ const worktree = join4(scratch, "wt");
3768
+ try {
3769
+ await git(repoRoot, ["worktree", "add", "--detach", worktree, tip]);
3770
+ const patchFile = join4(scratch, "patch.mbox");
3771
+ await writeFile(patchFile, patchText, "utf-8");
3772
+ const env = { ...process.env };
3773
+ if (committer) {
3774
+ env["GIT_COMMITTER_NAME"] = committer.name;
3775
+ env["GIT_COMMITTER_EMAIL"] = committer.email;
3776
+ }
3777
+ try {
3778
+ await git(worktree, ["am", "--quiet", patchFile], env);
3779
+ } catch (err) {
3780
+ await git(worktree, ["am", "--abort"]).catch(() => void 0);
3781
+ throw new PrMergeError(
3782
+ "apply_conflict",
3783
+ `the patch does not apply cleanly onto ${tip.slice(0, 7)} \u2014 ask the contributor for a revision (\`rig pr create --revises\`). Nothing was pushed, published, or paid, and your working tree is unchanged.
3784
+ ` + gitFailure(err)
3785
+ );
3786
+ }
3787
+ const newHead = await git(worktree, ["rev-parse", "HEAD"]);
3788
+ const listed = await git(worktree, [
3789
+ "rev-list",
3790
+ "--reverse",
3791
+ `${tip}..${newHead}`
3792
+ ]);
3793
+ const commits = listed === "" ? [] : listed.split("\n");
3794
+ if (commits.length === 0) {
3795
+ throw new PrMergeError(
3796
+ "nothing_to_apply",
3797
+ "the patch applied no commits (already merged?) \u2014 nothing was pushed, published, or paid."
3798
+ );
3799
+ }
3800
+ return { newHead, commits };
3801
+ } finally {
3802
+ await git(repoRoot, ["worktree", "remove", "--force", worktree]).catch(
3803
+ () => void 0
3804
+ );
3805
+ await git(repoRoot, ["worktree", "prune"]).catch(() => void 0);
3806
+ await rm2(scratch, { recursive: true, force: true });
3807
+ }
3808
+ }
3809
+ var BRANCH_PREFIX = "refs/heads/";
3810
+ async function runPrMerge(args, deps) {
3811
+ const { io } = deps;
3812
+ let flags;
3813
+ try {
3814
+ const parsed = parseMergeArgs(args);
3815
+ if (parsed === "help") {
3816
+ io.out(PR_MERGE_USAGE);
3817
+ return 0;
3818
+ }
3819
+ flags = parsed;
3820
+ } catch (err) {
3821
+ io.err(err instanceof Error ? err.message : String(err));
3822
+ io.err(PR_MERGE_USAGE);
3823
+ return 2;
3824
+ }
3825
+ deps = withForcedStandalone(deps);
3826
+ const webSocketFactory = deps.webSocketFactory ?? defaultWebSocketFactory;
3827
+ let stop;
3828
+ try {
3829
+ const repoRoot = await resolveRepoRoot(deps.cwd);
3830
+ const toonConfig = await readToonConfig(repoRoot);
3831
+ const repoId = flags.repoId ?? toonConfig.repoId;
3832
+ if (!repoId) throw new UnconfiguredRepoAddressError("repository id");
3833
+ const resolved = await resolveRelays({
3834
+ relayFlags: flags.relay,
3835
+ remoteName: flags.remote,
3836
+ repoRoot,
3837
+ toonRelays: toonConfig.relays
3838
+ });
3839
+ if (resolved.nudge !== void 0) io.err(resolved.nudge);
3840
+ const relays = resolved.relays;
3841
+ if (relays.length > 1) {
3842
+ io.err(singleRelayRefusal(resolved, "Nothing was pushed or paid."));
3843
+ return 1;
3844
+ }
3845
+ const relay = relays[0];
3846
+ const session = await loadPaidSession(deps, relay);
3847
+ if (session.path !== "standalone") {
3848
+ throw new Error("internal: rig pr merge requires the standalone session");
3849
+ }
3850
+ const ctx = session.ctx;
3851
+ stop = () => ctx.stop();
3852
+ const identity = identityReport(ctx);
3853
+ const owner = flags.owner ?? toonConfig.owner ?? identity.pubkey;
3854
+ const addr = { ownerPubkey: owner, repoId };
3855
+ const aTag = `${REPOSITORY_ANNOUNCEMENT_KIND2}:${owner}:${repoId}`;
3856
+ const lookup = async (id) => (await queryAll(relays, [{ ids: [id] }], webSocketFactory)).get(id);
3857
+ let root = await lookup(flags.patchId);
3858
+ if (root && root.kind === PATCH_KIND2 && isPatchRevision(root)) {
3859
+ const rootId = revisionRootId(root);
3860
+ if (rootId !== void 0) root = await lookup(rootId);
3861
+ }
3862
+ if (!root || root.kind !== PATCH_KIND2) {
3863
+ throw new PrMergeError(
3864
+ "patch_not_found",
3865
+ `patch ${flags.patchId} not found as a kind:1617 patch on ${relay} \u2014 nothing was pushed or paid`
3866
+ );
3867
+ }
3868
+ if (!root.tags.some((t) => t[0] === "a" && t[1] === aTag)) {
3869
+ throw new PrMergeError(
3870
+ "patch_not_found",
3871
+ `patch ${root.id} does not belong to ${aTag} \u2014 nothing was pushed or paid`
3872
+ );
3873
+ }
3874
+ const remote = await ctx.fetchRemote({
3875
+ ownerPubkey: owner,
3876
+ repoId,
3877
+ relayUrls: relays
3878
+ });
3879
+ const authorized = authorizedAuthors(
3880
+ owner,
3881
+ remote.announceEvent?.tags ?? []
3882
+ );
3883
+ const revisionEvents = await queryAll(
3884
+ relays,
3885
+ [{ kinds: [PATCH_KIND2], "#e": [root.id] }],
3886
+ webSocketFactory
3887
+ );
3888
+ const { latest } = selectRevision(
3889
+ root,
3890
+ [...revisionEvents.values()].filter((e) => e.kind === PATCH_KIND2),
3891
+ authorized
3892
+ );
3893
+ if (!authorized.has(identity.pubkey.toLowerCase())) {
3894
+ throw new PrMergeError(
3895
+ "not_authorized",
3896
+ `only the owner or a declared maintainer can merge \u2014 ${identity.pubkey.slice(0, 8)}\u2026 is neither for ${aTag}. Readers would ignore the pushed state. Nothing was pushed or paid.`
3897
+ );
3898
+ }
3899
+ if (!remote.announced) {
3900
+ throw new PrMergeError(
3901
+ "target_not_found",
3902
+ `${aTag} is not announced on ${relay} \u2014 nothing to merge onto`
3903
+ );
3904
+ }
3905
+ const branch = (flags.target ?? firstTagValue(latest.tags, "branch-name") ?? remote.headSymref ?? "main").replace(/^refs\/heads\//, "");
3906
+ const refname = `${BRANCH_PREFIX}${branch}`;
3907
+ const tip = remote.refs.get(refname);
3908
+ if (tip === void 0) {
3909
+ throw new PrMergeError(
3910
+ "target_not_found",
3911
+ `branch ${JSON.stringify(branch)} is not in the honored repo state (${[...remote.refs.keys()].join(", ") || "no refs"}) \u2014 pass --target. Nothing was pushed or paid.`
3912
+ );
3913
+ }
3914
+ if (!await gitOk(repoRoot, ["cat-file", "-e", `${tip}^{commit}`])) {
3915
+ await runFetch(
3916
+ ["--relay", relay, "--owner", owner, "--repo-id", repoId],
3917
+ { ...deps, io: { ...io, out: () => void 0 } }
3918
+ );
3919
+ if (!await gitOk(repoRoot, ["cat-file", "-e", `${tip}^{commit}`])) {
3920
+ throw new PrMergeError(
3921
+ "tip_unavailable",
3922
+ `the honored tip ${tip.slice(0, 7)} of ${branch} could not be fetched \u2014 run \`rig fetch\` and retry. Nothing was pushed or paid.`
3923
+ );
3924
+ }
3925
+ }
3926
+ const reader = new GitRepoReader(repoRoot);
3927
+ const localRefs = (await reader.listRefs()).refs;
3928
+ const localSha = localRefs.find((r) => r.refname === refname)?.sha;
3929
+ if (localSha !== void 0 && localSha !== tip && !await reader.isAncestor(localSha, tip)) {
3930
+ throw new PrMergeError(
3931
+ "local_branch_diverged",
3932
+ `local ${branch} (${localSha.slice(0, 7)}) is not behind the honored tip (${tip.slice(0, 7)}) \u2014 push or reset it first. Nothing was pushed or paid.`
3933
+ );
3934
+ }
3935
+ const hasCommitter = await gitOk(repoRoot, ["config", "user.email"]);
3936
+ const applied = await applyInIsolation(
3937
+ repoRoot,
3938
+ tip,
3939
+ latest.content,
3940
+ hasCommitter ? void 0 : {
3941
+ name: hexToNpub(identity.pubkey),
3942
+ email: `${hexToNpub(identity.pubkey)}@nostr`
3943
+ }
3944
+ );
3945
+ if (flags.requireTrust !== void 0) {
3946
+ const headCommit = (latest.tags.filter((t) => t[0] === "commit").at(-1)?.[1] ?? applied.newHead).toLowerCase();
3947
+ const gate = await evaluateCiGate({
3948
+ relays,
3949
+ owner,
3950
+ repoId,
3951
+ commit: headCommit,
3952
+ webSocketFactory,
3953
+ requireTrust: flags.requireTrust
3954
+ });
3955
+ if (!gate.ok) {
3956
+ throw new PrMergeError(
3957
+ "ci_gate_failed",
3958
+ `no qualifying green CI run (trust >= ${flags.requireTrust}) for ${headCommit.slice(0, 12)}: ${gate.reason ?? "not green"}. Nothing was pushed, published, or paid.`
3959
+ );
3960
+ }
3961
+ }
3962
+ const overlay = new OverlayReader(repoRoot, {
3963
+ refname,
3964
+ sha: applied.newHead
3965
+ });
3966
+ const feeRates = await ctx.publisher.getFeeRates();
3967
+ const pushPlan = await planPush({
3968
+ repoReader: overlay,
3969
+ remoteState: remote,
3970
+ feeRates,
3971
+ repoId,
3972
+ refs: [refname],
3973
+ force: false
3974
+ });
3975
+ const plan = serializePushPlan(pushPlan);
3976
+ const total = BigInt(plan.estimate.totalFee) + feeRates.eventFee * BigInt(1 + flags.closes.length);
3977
+ if (!flags.json) {
3978
+ for (const line of renderPlan(plan)) io.out(line);
3979
+ io.out(
3980
+ ` status kind:1631 applied (${applied.commits.length} commit${applied.commits.length === 1 ? "" : "s"}) ${feeRates.eventFee}`
3981
+ );
3982
+ if (flags.closes.length > 0) {
3983
+ io.out(
3984
+ ` closes kind:1632 closed (${flags.closes.length} issue${flags.closes.length === 1 ? "" : "s"}) ${feeRates.eventFee * BigInt(flags.closes.length)}`
3985
+ );
3986
+ }
3987
+ io.out(` total with status ${total}`);
3988
+ io.out(renderIdentityLine(identity));
3989
+ }
3990
+ if (!flags.yes) {
3991
+ if (flags.json) {
3992
+ io.emitJson({
3993
+ command: "pr merge",
3994
+ repoAddr: addr,
3995
+ identity,
3996
+ executed: false,
3997
+ patchId: root.id,
3998
+ revisionId: latest.id,
3999
+ target: refname,
4000
+ mergedShas: applied.commits,
4001
+ plan,
4002
+ totalFee: total.toString(),
4003
+ hint: "estimate only \u2014 re-run with --yes to push and publish (permanent, non-refundable)"
4004
+ });
4005
+ return 0;
4006
+ }
4007
+ if (!io.isInteractive) {
4008
+ io.err(
4009
+ "refusing to spend channel funds without confirmation in a non-interactive session \u2014 re-run with --yes (or use --json for an estimate)"
4010
+ );
4011
+ return 1;
4012
+ }
4013
+ const proceed = await io.confirm(
4014
+ `Proceed with paid merge (total ${total} base units)? [y/N] `
4015
+ );
4016
+ if (!proceed) {
4017
+ io.err("aborted \u2014 nothing was pushed or published.");
4018
+ return 1;
4019
+ }
4020
+ }
4021
+ const pushResult = await executePush({
4022
+ plan: pushPlan,
4023
+ publisher: ctx.publisher,
4024
+ remoteState: remote,
4025
+ repoReader: overlay,
4026
+ relayUrls: relays
4027
+ });
4028
+ const result = serializePushResult(pushPlan, pushResult);
4029
+ const localNote = await advanceLocalBranch(
4030
+ repoRoot,
4031
+ reader,
4032
+ refname,
4033
+ applied.newHead,
4034
+ localSha
4035
+ );
4036
+ if (localNote !== void 0) io.err(localNote);
4037
+ let statusEventId;
4038
+ let statusError;
4039
+ try {
4040
+ const receipt = await ctx.publisher.publishEvent(
4041
+ buildStatus(owner, repoId, root.id, STATUS_APPLIED_KIND2, root.pubkey, {
4042
+ commits: applied.commits,
4043
+ revisionEventId: latest.id
4044
+ }),
4045
+ relays
4046
+ );
4047
+ statusEventId = receipt.eventId;
4048
+ } catch (err) {
4049
+ statusError = err instanceof Error ? err.message : String(err);
4050
+ }
4051
+ const closed = [];
4052
+ const closeErrors = [];
4053
+ for (const issueId of flags.closes) {
4054
+ try {
4055
+ await ctx.publisher.publishEvent(
4056
+ buildStatus(owner, repoId, issueId, STATUS_CLOSED_KIND2),
4057
+ relays
4058
+ );
4059
+ closed.push(issueId);
4060
+ } catch (err) {
4061
+ closeErrors.push({
4062
+ issueId,
4063
+ error: err instanceof Error ? err.message : String(err)
4064
+ });
4065
+ }
4066
+ }
4067
+ const closeRetryHints = closeErrors.map(
4068
+ (e) => `rig issue close ${e.issueId}`
4069
+ );
4070
+ const retryHint = `rig pr status ${root.id} applied`;
4071
+ if (flags.json) {
4072
+ io.emitJson({
4073
+ command: "pr merge",
4074
+ repoAddr: addr,
4075
+ identity,
4076
+ executed: true,
4077
+ patchId: root.id,
4078
+ revisionId: latest.id,
4079
+ target: refname,
4080
+ mergedShas: applied.commits,
4081
+ closed,
4082
+ ...closeErrors.length > 0 ? { closeErrors } : {},
4083
+ result,
4084
+ ...statusEventId !== void 0 ? { statusEventId } : {},
4085
+ ...statusError !== void 0 ? {
4086
+ partial: true,
4087
+ error: "status_publish_failed",
4088
+ detail: statusError,
4089
+ hint: `the push succeeded but the applied status was not published \u2014 retry with \`${retryHint}\``
4090
+ } : closeErrors.length > 0 ? {
4091
+ partial: true,
4092
+ error: "close_publish_failed",
4093
+ detail: closeErrors.map((e) => `${e.issueId}: ${e.error}`).join("; "),
4094
+ hint: `the push succeeded but some issues were not closed \u2014 retry with ${closeRetryHints.map((h) => `\`${h}\``).join(", ")}`
4095
+ } : {}
4096
+ });
4097
+ } else {
4098
+ for (const line of renderResult(result)) io.out(line);
4099
+ if (statusEventId !== void 0) {
4100
+ io.out(
4101
+ `Merged ${applied.commits.length} commit(s) onto ${branch}; applied status ${statusEventId}`
4102
+ );
4103
+ }
4104
+ }
4105
+ if (!flags.json && closed.length > 0) {
4106
+ io.out(`Closed ${closed.length} issue(s): ${closed.join(", ")}`);
4107
+ }
4108
+ if (statusError !== void 0) {
4109
+ io.err(
4110
+ `partial success: the push succeeded but publishing the applied status failed: ${statusError}`
4111
+ );
4112
+ io.err(`retry the status with: ${retryHint}`);
4113
+ }
4114
+ if (closeErrors.length > 0) {
4115
+ for (const e of closeErrors) {
4116
+ io.err(
4117
+ `partial success: the push succeeded but closing issue ${e.issueId} failed: ${e.error}`
4118
+ );
4119
+ }
4120
+ for (const h of closeRetryHints) io.err(`retry the close with: ${h}`);
4121
+ }
4122
+ return statusError !== void 0 || closeErrors.length > 0 ? 1 : 0;
4123
+ } catch (err) {
4124
+ return emitCliError(io, flags.json, "pr merge", err);
4125
+ } finally {
4126
+ if (stop) await stop().catch(() => void 0);
4127
+ }
4128
+ }
4129
+ async function advanceLocalBranch(repoRoot, reader, refname, newHead, oldSha) {
4130
+ try {
4131
+ const { head } = await reader.listRefs();
4132
+ if (head === refname) {
4133
+ await git(repoRoot, ["merge", "--ff-only", newHead]);
4134
+ } else {
4135
+ await git(repoRoot, [
4136
+ "update-ref",
4137
+ refname,
4138
+ newHead,
4139
+ oldSha ?? "0".repeat(40)
4140
+ ]);
4141
+ }
4142
+ return void 0;
4143
+ } catch (err) {
4144
+ return `warning: the push succeeded but local ${refname} could not be moved to ${newHead.slice(0, 7)}: ${gitFailure(err)} \u2014 run \`rig fetch\``;
4145
+ }
3392
4146
  }
3393
4147
 
3394
4148
  // src/cli/events.ts
@@ -3512,14 +4266,16 @@ Options:
3512
4266
  ${COMMON_FLAGS_USAGE2}`;
3513
4267
  var PR_USAGE = `${PR_CREATE_USAGE}
3514
4268
 
4269
+ ${PR_MERGE_USAGE}
4270
+
3515
4271
  ${PR_STATUS_USAGE}
3516
4272
 
3517
4273
  ${PR_LIST_USAGE}
3518
4274
 
3519
4275
  ${PR_SHOW_USAGE}`;
3520
- var HEX64_RE3 = /^[0-9a-f]{64}$/;
4276
+ var HEX64_RE4 = /^[0-9a-f]{64}$/;
3521
4277
  function assertHex64(value, what) {
3522
- if (!HEX64_RE3.test(value)) {
4278
+ if (!HEX64_RE4.test(value)) {
3523
4279
  throw new Error(
3524
4280
  `${what} must be a 64-char lowercase hex id (got ${JSON.stringify(value)})`
3525
4281
  );
@@ -3716,7 +4472,7 @@ async function runIssue(args, deps) {
3716
4472
  let bodyFile;
3717
4473
  let labels;
3718
4474
  try {
3719
- const { values } = parseArgs8({
4475
+ const { values } = parseArgs10({
3720
4476
  args: rest,
3721
4477
  options: {
3722
4478
  ...COMMON_OPTIONS2,
@@ -3812,7 +4568,7 @@ async function resolveCommentTarget(deps, targetEventId, relayUrl) {
3812
4568
  const rootEventId = firstTagValue(target.tags, "E");
3813
4569
  const rootKind = Number(firstTagValue(target.tags, "K"));
3814
4570
  const rootAuthor = firstTagValue(target.tags, "P");
3815
- if (rootEventId === void 0 || !HEX64_RE3.test(rootEventId) || !Number.isSafeInteger(rootKind) || rootAuthor === void 0 || !HEX64_RE3.test(rootAuthor)) {
4571
+ if (rootEventId === void 0 || !HEX64_RE4.test(rootEventId) || !Number.isSafeInteger(rootKind) || rootAuthor === void 0 || !HEX64_RE4.test(rootAuthor)) {
3816
4572
  throw new Error(
3817
4573
  `kind:1111 comment ${targetEventId} carries no usable NIP-22 root scope (E/K/P tags) \u2014 cannot derive the thread root to reply into`
3818
4574
  );
@@ -3836,7 +4592,7 @@ async function runComment(args, deps) {
3836
4592
  let targetEventId;
3837
4593
  let body;
3838
4594
  try {
3839
- const { values, positionals } = parseArgs8({
4595
+ const { values, positionals } = parseArgs10({
3840
4596
  args,
3841
4597
  options: {
3842
4598
  ...COMMON_OPTIONS2,
@@ -3917,6 +4673,8 @@ async function runPr(args, deps) {
3917
4673
  switch (sub) {
3918
4674
  case "create":
3919
4675
  return runPrCreate(rest, deps);
4676
+ case "merge":
4677
+ return runPrMerge(rest, deps);
3920
4678
  case "status":
3921
4679
  return runStatusCommand(rest, deps, PR_STATUS_SPEC);
3922
4680
  // FREE read subcommands (#278): relay queries only, no payment path.
@@ -3931,7 +4689,7 @@ async function runPr(args, deps) {
3931
4689
  return 0;
3932
4690
  default:
3933
4691
  io.err(
3934
- sub === void 0 ? "missing subcommand: rig pr <create|status|list|show>" : `unknown rig pr subcommand: ${sub}`
4692
+ sub === void 0 ? "missing subcommand: rig pr <create|merge|status|list|show>" : `unknown rig pr subcommand: ${sub}`
3935
4693
  );
3936
4694
  io.err(PR_USAGE);
3937
4695
  return 2;
@@ -3955,12 +4713,12 @@ async function resolveRevisedRoot(deps, rootId, relayUrl, addr) {
3955
4713
  `patch ${rootId} not found on ${relayUrl} \u2014 nothing was published or paid`
3956
4714
  );
3957
4715
  }
3958
- if (root.kind !== PATCH_KIND2) {
4716
+ if (root.kind !== PATCH_KIND3) {
3959
4717
  throw new Error(
3960
4718
  `event ${rootId} has kind ${root.kind}, not a kind:1617 patch \u2014 nothing was published or paid`
3961
4719
  );
3962
4720
  }
3963
- const aTag = `${REPOSITORY_ANNOUNCEMENT_KIND2}:${addr.ownerPubkey}:${addr.repoId}`;
4721
+ const aTag = `${REPOSITORY_ANNOUNCEMENT_KIND3}:${addr.ownerPubkey}:${addr.repoId}`;
3964
4722
  if (!root.tags.some((t) => t[0] === "a" && t[1] === aTag)) {
3965
4723
  throw new Error(
3966
4724
  `patch ${rootId} belongs to a different repository than ${aTag} \u2014 nothing was published or paid`
@@ -4011,7 +4769,7 @@ async function runPrCreate(rest, deps) {
4011
4769
  let bodyFile;
4012
4770
  let revises;
4013
4771
  try {
4014
- const { values } = parseArgs8({
4772
+ const { values } = parseArgs10({
4015
4773
  args: rest,
4016
4774
  options: {
4017
4775
  ...COMMON_OPTIONS2,
@@ -4145,8 +4903,8 @@ async function runPrCreate(rest, deps) {
4145
4903
  }
4146
4904
  var STATUS_KIND_BY_VALUE = {
4147
4905
  open: STATUS_OPEN_KIND2,
4148
- applied: STATUS_APPLIED_KIND2,
4149
- closed: STATUS_CLOSED_KIND2,
4906
+ applied: STATUS_APPLIED_KIND3,
4907
+ closed: STATUS_CLOSED_KIND3,
4150
4908
  draft: STATUS_DRAFT_KIND2
4151
4909
  };
4152
4910
  function isStatusValue(value) {
@@ -4172,7 +4930,7 @@ async function runStatusCommand(args, deps, spec, fixedStatus) {
4172
4930
  let targetEventId;
4173
4931
  let status;
4174
4932
  try {
4175
- const { values, positionals } = parseArgs8({
4933
+ const { values, positionals } = parseArgs10({
4176
4934
  args,
4177
4935
  options: COMMON_OPTIONS2,
4178
4936
  allowPositionals: true
@@ -4258,8 +5016,8 @@ async function warnIfNotMaintainer(deps, args) {
4258
5016
  // src/cli/entry.ts
4259
5017
  import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
4260
5018
  import { homedir as homedir3 } from "os";
4261
- import { dirname as dirname4, join as join4 } from "path";
4262
- import { parseArgs as parseArgs9 } from "util";
5019
+ import { dirname as dirname4, join as join5 } from "path";
5020
+ import { parseArgs as parseArgs11 } from "util";
4263
5021
  var ENTRY_USAGE = `Usage: rig entry [<connector-url> | clear] [options]
4264
5022
 
4265
5023
  Name the TOON connector rig pays. A connector describes itself on GET /ilp
@@ -4283,8 +5041,8 @@ Options:
4283
5041
  --json machine-readable envelope
4284
5042
  -h, --help show this help`;
4285
5043
  function configPathFor2(env) {
4286
- const dir = env["TOON_CLIENT_HOME"] ?? join4(homedir3(), ".toon-client");
4287
- return join4(dir, "config.json");
5044
+ const dir = env["TOON_CLIENT_HOME"] ?? join5(homedir3(), ".toon-client");
5045
+ return join5(dir, "config.json");
4288
5046
  }
4289
5047
  function readEntryConfig(configPath) {
4290
5048
  try {
@@ -4329,381 +5087,155 @@ async function runEntry(args, deps) {
4329
5087
  let relayFlag;
4330
5088
  let json = false;
4331
5089
  try {
4332
- const parsed = parseArgs9({
5090
+ const parsed = parseArgs11({
4333
5091
  args,
4334
5092
  allowPositionals: true,
4335
5093
  options: {
4336
- relay: { type: "string" },
4337
- json: { type: "boolean", default: false },
4338
- help: { type: "boolean", short: "h", default: false }
4339
- }
4340
- });
4341
- if (parsed.values.help) {
4342
- io.out(ENTRY_USAGE);
4343
- return 0;
4344
- }
4345
- positionals = parsed.positionals;
4346
- relayFlag = parsed.values.relay;
4347
- json = parsed.values.json ?? false;
4348
- } catch (err) {
4349
- io.err(err instanceof Error ? err.message : String(err));
4350
- io.err(ENTRY_USAGE);
4351
- return 2;
4352
- }
4353
- if (positionals.length > 1) {
4354
- io.err(
4355
- `rig entry takes at most one argument, got ${String(positionals.length)}: ${positionals.join(" ")}`
4356
- );
4357
- io.err(ENTRY_USAGE);
4358
- return 2;
4359
- }
4360
- const target = positionals[0];
4361
- let mutation;
4362
- if (target !== void 0) {
4363
- if (target === "clear") {
4364
- if (relayFlag !== void 0) {
4365
- io.err("--relay only applies to a <connector-url> entry");
4366
- io.err(ENTRY_USAGE);
4367
- return 2;
4368
- }
4369
- mutation = { kind: "clear" };
4370
- } else if (HTTP_URL.test(target)) {
4371
- if (relayFlag !== void 0 && !WS_URL.test(relayFlag)) {
4372
- io.err(`--relay must be a ws(s) URL, got ${JSON.stringify(relayFlag)}`);
4373
- io.err(ENTRY_USAGE);
4374
- return 2;
4375
- }
4376
- mutation = {
4377
- kind: "url",
4378
- connectorUrl: target.replace(/\/+$/, ""),
4379
- ...relayFlag !== void 0 ? { relayUrl: relayFlag } : {}
4380
- };
4381
- } else if (WS_URL.test(target)) {
4382
- io.err(
4383
- `${JSON.stringify(target)} is a WebSocket URL \u2014 the entry is the connector's http(s) URL (the one whose GET /ilp describes it); pass the relay with --relay`
4384
- );
4385
- io.err(ENTRY_USAGE);
4386
- return 2;
4387
- } else {
4388
- io.err(
4389
- `unknown entry ${JSON.stringify(target)} \u2014 expected a connector http(s):// URL or \`clear\``
4390
- );
4391
- io.err(ENTRY_USAGE);
4392
- return 2;
4393
- }
4394
- }
4395
- try {
4396
- const configPath = configPathFor2(env);
4397
- const file = readEntryConfig(configPath);
4398
- const label = (source) => source === "env" ? "env" : source === "legacy-env" ? "env (TOON_CLIENT_PROXY_URL, pre-4.0 spelling \u2014 set TOON_CONNECTOR)" : source === "config" ? `config (${configPath})` : source === "legacy-config" ? `config proxyUrl (${configPath}, pre-4.0 spelling)` : "not configured";
4399
- if (mutation === void 0) {
4400
- const state2 = effectiveEntry(env, file);
4401
- if (json) {
4402
- io.emitJson({
4403
- command: "entry",
4404
- ...state2,
4405
- configPath
4406
- });
4407
- return 0;
4408
- }
4409
- io.out(
4410
- `Connector ${state2.connectorUrl ?? "(none)"} [${label(state2.connectorSource)}]`
4411
- );
4412
- io.out(
4413
- `Relay ${state2.relayUrl ?? "(none)"} [${label(state2.relaySource)}]`
4414
- );
4415
- if (!state2.connectorUrl) {
4416
- io.out(
4417
- "Set one with `rig entry <connector-url>` (or export TOON_CONNECTOR)."
4418
- );
4419
- }
4420
- return 0;
4421
- }
4422
- const warnings = [];
4423
- const next = { ...file };
4424
- if (file.proxyUrl !== void 0 || file.btpUrl !== void 0) {
4425
- delete next.proxyUrl;
4426
- delete next.btpUrl;
4427
- warnings.push(
4428
- "removed the pre-4.0 proxyUrl/btpUrl fields (the connector URL replaces both)"
4429
- );
4430
- }
4431
- if (mutation.kind === "clear") {
4432
- delete next.connectorUrl;
4433
- delete next.relayUrl;
4434
- } else {
4435
- next.connectorUrl = mutation.connectorUrl;
4436
- if (mutation.relayUrl !== void 0) next.relayUrl = mutation.relayUrl;
4437
- }
4438
- if (env["TOON_CONNECTOR"] || env["TOON_CLIENT_PROXY_URL"]) {
4439
- warnings.push(
4440
- "TOON_CONNECTOR / TOON_CLIENT_PROXY_URL is set in this shell and outranks the config file"
4441
- );
4442
- }
4443
- writeEntryConfig(configPath, next);
4444
- const wrote = {
4445
- connectorUrl: next.connectorUrl ?? null,
4446
- relayUrl: next.relayUrl ?? null
4447
- };
4448
- const state = effectiveEntry(env, next);
4449
- if (json) {
4450
- io.emitJson({
4451
- command: "entry",
4452
- ...state,
4453
- wrote,
4454
- configPath,
4455
- ...warnings.length > 0 ? { warnings } : {}
4456
- });
4457
- return 0;
4458
- }
4459
- if (mutation.kind === "clear") {
4460
- io.out(`Cleared the recorded connector in ${configPath}.`);
4461
- } else {
4462
- io.out(`Recorded connector ${mutation.connectorUrl} in ${configPath}.`);
4463
- if (mutation.relayUrl !== void 0)
4464
- io.out(`Recorded relay ${mutation.relayUrl}.`);
4465
- }
4466
- for (const w of warnings) io.out(`warning: ${w}`);
4467
- io.out(
4468
- `Effective connector: ${state.connectorUrl ?? "(none)"} [${label(state.connectorSource)}]`
4469
- );
4470
- return 0;
4471
- } catch (err) {
4472
- return emitCliError(io, json, "entry", err);
4473
- }
4474
- }
4475
-
4476
- // src/cli/fetch.ts
4477
- import { parseArgs as parseArgs10 } from "util";
4478
- var FETCH_USAGE = `Usage: rig fetch [remote] [options]
4479
-
4480
- Fetch from a TOON remote \u2014 FREE (relay reads + Arweave gateway downloads; no
4481
- payments, no identity needed). Reads the kind:30618 repository state from the
4482
- remote's relay, downloads only the git objects this repository is missing
4483
- (SHA-1 verified), and updates the remote-tracking refs
4484
- (refs/remotes/<remote>/*; tags land at refs/tags/*), reporting what moved.
4485
-
4486
- No merge happens: integrate with the git passthrough, e.g.
4487
- \`rig merge origin/main\`. \`rig fetch\` is the TOON transport and shadows
4488
- \`git fetch\` \u2014 plain-git fetches remain available via \`git fetch\` directly.
4489
-
4490
- The repo address (toon.repoid/toon.owner) comes from the config \`rig init\`
4491
- or \`rig clone\` writes; --repo-id/--owner override.
4492
-
4493
- Options:
4494
- --repo-id <id> repository id / NIP-34 d-tag (default: git config)
4495
- --owner <pubkey> repository owner (npub or 64-char hex; default: git config)
4496
- --relay <url> ad-hoc relay override \u2014 bypasses the remote's URL
4497
- --gateway <url> gateway to read objects through FIRST, on its raw-bytes
4498
- route <url>/raw/<txId>, ahead of the public gateway
4499
- list \u2014 name a private, local or air-gapped gateway to
4500
- fetch objects the public ones do not carry
4501
- (env RIG_ARWEAVE_GATEWAY also sets it)
4502
- --concurrency <n> parallel gateway downloads (default 8)
4503
- --json machine-readable result envelope
4504
- -h, --help show this help`;
4505
- var WS_URL_RE4 = /^wss?:\/\//i;
4506
- function localRefFor(refname, remote) {
4507
- if (refname.startsWith("refs/heads/")) {
4508
- return `refs/remotes/${remote}/${refname.slice("refs/heads/".length)}`;
4509
- }
4510
- if (refname.startsWith("refs/tags/")) return refname;
4511
- return null;
4512
- }
4513
- async function resolveLocalRef(repoRoot, refname) {
4514
- try {
4515
- const out = await runGit(repoRoot, [
4516
- "rev-parse",
4517
- "--verify",
4518
- "--quiet",
4519
- refname
4520
- ]);
4521
- const sha = out.trim();
4522
- return /^[0-9a-f]{40}$/.test(sha) ? sha : null;
4523
- } catch {
4524
- return null;
4525
- }
4526
- }
4527
- async function runFetch(args, deps) {
4528
- const { io } = deps;
4529
- let json = false;
4530
- let remote = "origin";
4531
- let relayFlag;
4532
- let repoIdFlag;
4533
- let ownerFlag;
4534
- let gatewayFlag;
4535
- let concurrency;
4536
- try {
4537
- const { values, positionals } = parseArgs10({
4538
- args,
4539
- options: {
4540
- json: { type: "boolean", default: false },
4541
- relay: { type: "string" },
4542
- "repo-id": { type: "string" },
4543
- owner: { type: "string" },
4544
- gateway: { type: "string" },
4545
- concurrency: { type: "string" },
4546
- help: { type: "boolean", short: "h", default: false }
4547
- },
4548
- allowPositionals: true
4549
- });
4550
- if (values.help) {
4551
- io.out(FETCH_USAGE);
4552
- return 0;
4553
- }
4554
- json = values.json === true;
4555
- if (positionals.length > 1) {
4556
- throw new Error(
4557
- `expected at most one [remote] argument, got ${positionals.length}`
4558
- );
4559
- }
4560
- if (positionals[0] !== void 0) remote = positionals[0];
4561
- relayFlag = values.relay;
4562
- repoIdFlag = values["repo-id"];
4563
- ownerFlag = values.owner !== void 0 ? ownerToHex(values.owner) : void 0;
4564
- gatewayFlag = values.gateway;
4565
- if (values.concurrency !== void 0) {
4566
- concurrency = Number.parseInt(values.concurrency, 10);
4567
- if (!Number.isSafeInteger(concurrency) || concurrency < 1) {
4568
- throw new Error("--concurrency must be a positive integer");
5094
+ relay: { type: "string" },
5095
+ json: { type: "boolean", default: false },
5096
+ help: { type: "boolean", short: "h", default: false }
4569
5097
  }
5098
+ });
5099
+ if (parsed.values.help) {
5100
+ io.out(ENTRY_USAGE);
5101
+ return 0;
4570
5102
  }
5103
+ positionals = parsed.positionals;
5104
+ relayFlag = parsed.values.relay;
5105
+ json = parsed.values.json ?? false;
4571
5106
  } catch (err) {
4572
5107
  io.err(err instanceof Error ? err.message : String(err));
4573
- io.err(FETCH_USAGE);
5108
+ io.err(ENTRY_USAGE);
4574
5109
  return 2;
4575
5110
  }
4576
- try {
4577
- const repoRoot = await resolveRepoRoot(deps.cwd);
4578
- const toonConfig = await readToonConfig(repoRoot);
4579
- const repoId = repoIdFlag ?? toonConfig.repoId;
4580
- if (!repoId) throw new UnconfiguredRepoAddressError("repository id");
4581
- const owner = ownerFlag ?? toonConfig.owner;
4582
- if (!owner) throw new UnconfiguredRepoAddressError("repository owner");
4583
- let relayUrl;
4584
- if (relayFlag !== void 0) {
4585
- relayUrl = relayFlag;
5111
+ if (positionals.length > 1) {
5112
+ io.err(
5113
+ `rig entry takes at most one argument, got ${String(positionals.length)}: ${positionals.join(" ")}`
5114
+ );
5115
+ io.err(ENTRY_USAGE);
5116
+ return 2;
5117
+ }
5118
+ const target = positionals[0];
5119
+ let mutation;
5120
+ if (target !== void 0) {
5121
+ if (target === "clear") {
5122
+ if (relayFlag !== void 0) {
5123
+ io.err("--relay only applies to a <connector-url> entry");
5124
+ io.err(ENTRY_USAGE);
5125
+ return 2;
5126
+ }
5127
+ mutation = { kind: "clear" };
5128
+ } else if (HTTP_URL.test(target)) {
5129
+ if (relayFlag !== void 0 && !WS_URL.test(relayFlag)) {
5130
+ io.err(`--relay must be a ws(s) URL, got ${JSON.stringify(relayFlag)}`);
5131
+ io.err(ENTRY_USAGE);
5132
+ return 2;
5133
+ }
5134
+ mutation = {
5135
+ kind: "url",
5136
+ connectorUrl: target.replace(/\/+$/, ""),
5137
+ ...relayFlag !== void 0 ? { relayUrl: relayFlag } : {}
5138
+ };
5139
+ } else if (WS_URL.test(target)) {
5140
+ io.err(
5141
+ `${JSON.stringify(target)} is a WebSocket URL \u2014 the entry is the connector's http(s) URL (the one whose GET /ilp describes it); pass the relay with --relay`
5142
+ );
5143
+ io.err(ENTRY_USAGE);
5144
+ return 2;
4586
5145
  } else {
4587
- const urls = await getGitRemoteUrls(repoRoot, remote);
4588
- if (urls.length === 0) throw new UnknownRemoteError(remote);
4589
- if (urls.length > 1) throw new MultiUrlRemoteError(remote, urls);
4590
- relayUrl = urls[0];
4591
- }
4592
- if (!WS_URL_RE4.test(relayUrl)) {
4593
- throw new InvalidRelayUrlError(
4594
- relayUrl,
4595
- `remote ${JSON.stringify(remote)}`
5146
+ io.err(
5147
+ `unknown entry ${JSON.stringify(target)} \u2014 expected a connector http(s):// URL or \`clear\``
4596
5148
  );
5149
+ io.err(ENTRY_USAGE);
5150
+ return 2;
4597
5151
  }
4598
- const remoteState = await fetchRemoteState({
4599
- relayUrls: [relayUrl],
4600
- ownerPubkey: owner,
4601
- repoId,
4602
- ...deps.webSocketFactory ? { webSocketFactory: deps.webSocketFactory } : {},
4603
- // The GraphQL fallback for SHAs the map does not cover asks the SAME
4604
- // permaweb the objects are read from (#183).
4605
- resolveSha: deps.resolveSha ?? shaResolverFor(gatewayFlag, deps.env)
4606
- });
4607
- const planned = [];
4608
- for (const [refname, sha] of remoteState.refs) {
4609
- if (!isSafeRefname(refname)) {
4610
- io.err(
4611
- `warning: skipping unsafe ref name from relay: ${JSON.stringify(refname)}`
4612
- );
4613
- continue;
5152
+ }
5153
+ try {
5154
+ const configPath = configPathFor2(env);
5155
+ const file = readEntryConfig(configPath);
5156
+ const label = (source) => source === "env" ? "env" : source === "legacy-env" ? "env (TOON_CLIENT_PROXY_URL, pre-4.0 spelling \u2014 set TOON_CONNECTOR)" : source === "config" ? `config (${configPath})` : source === "legacy-config" ? `config proxyUrl (${configPath}, pre-4.0 spelling)` : "not configured";
5157
+ if (mutation === void 0) {
5158
+ const state2 = effectiveEntry(env, file);
5159
+ if (json) {
5160
+ io.emitJson({
5161
+ command: "entry",
5162
+ ...state2,
5163
+ configPath
5164
+ });
5165
+ return 0;
4614
5166
  }
4615
- const localRef = localRefFor(refname, remote);
4616
- if (localRef === null) {
4617
- io.err(
4618
- `warning: skipping ref outside refs/heads and refs/tags: ${refname}`
5167
+ io.out(
5168
+ `Connector ${state2.connectorUrl ?? "(none)"} [${label(state2.connectorSource)}]`
5169
+ );
5170
+ io.out(
5171
+ `Relay ${state2.relayUrl ?? "(none)"} [${label(state2.relaySource)}]`
5172
+ );
5173
+ if (!state2.connectorUrl) {
5174
+ io.out(
5175
+ "Set one with `rig entry <connector-url>` (or export TOON_CONNECTOR)."
4619
5176
  );
4620
- continue;
4621
5177
  }
4622
- planned.push({ refname, localRef, newSha: sha });
5178
+ return 0;
4623
5179
  }
4624
- const reader = new GitRepoReader(repoRoot);
4625
- const tips = [...new Set(planned.map((p) => p.newSha))];
4626
- const presentLocally = new Set(await reader.listAllObjectShas());
4627
- const collected = await collectRepoObjects({
4628
- tips,
4629
- shaToTxId: remoteState.shaToTxId,
4630
- resolveMissing: (shas) => remoteState.resolveMissing(shas),
4631
- presentLocally,
4632
- // `--gateway` / RIG_ARWEAVE_GATEWAY first, public list behind it (#176).
4633
- gateways: readGateways(gatewayFlag, deps.env),
4634
- ...deps.fetchFn ? { fetchFn: deps.fetchFn } : {},
4635
- ...concurrency !== void 0 ? { concurrency } : {}
4636
- });
4637
- if (collected.missing.length > 0) {
4638
- throw new MissingRemoteObjectsError(
4639
- collected.missing,
4640
- `cannot fetch from ${relayUrl}`
5180
+ const warnings = [];
5181
+ const next = { ...file };
5182
+ if (file.proxyUrl !== void 0 || file.btpUrl !== void 0) {
5183
+ delete next.proxyUrl;
5184
+ delete next.btpUrl;
5185
+ warnings.push(
5186
+ "removed the pre-4.0 proxyUrl/btpUrl fields (the connector URL replaces both)"
4641
5187
  );
4642
5188
  }
4643
- const written = await writeGitObjects(repoRoot, collected.objects.values());
4644
- const updates = [];
4645
- for (const { refname, localRef, newSha } of planned) {
4646
- const oldSha = await resolveLocalRef(repoRoot, localRef);
4647
- if (oldSha === newSha) {
4648
- updates.push({ refname, localRef, oldSha, newSha, kind: "up-to-date" });
4649
- continue;
4650
- }
4651
- let kind;
4652
- if (oldSha === null) {
4653
- kind = "new";
4654
- } else {
4655
- kind = await reader.isAncestor(oldSha, newSha) ? "fast-forward" : "forced";
4656
- }
4657
- await updateRef(repoRoot, localRef, newSha);
4658
- updates.push({ refname, localRef, oldSha, newSha, kind });
5189
+ if (mutation.kind === "clear") {
5190
+ delete next.connectorUrl;
5191
+ delete next.relayUrl;
5192
+ } else {
5193
+ next.connectorUrl = mutation.connectorUrl;
5194
+ if (mutation.relayUrl !== void 0) next.relayUrl = mutation.relayUrl;
4659
5195
  }
4660
- const moved = updates.filter((u) => u.kind !== "up-to-date");
5196
+ if (env["TOON_CONNECTOR"] || env["TOON_CLIENT_PROXY_URL"]) {
5197
+ warnings.push(
5198
+ "TOON_CONNECTOR / TOON_CLIENT_PROXY_URL is set in this shell and outranks the config file"
5199
+ );
5200
+ }
5201
+ writeEntryConfig(configPath, next);
5202
+ const wrote = {
5203
+ connectorUrl: next.connectorUrl ?? null,
5204
+ relayUrl: next.relayUrl ?? null
5205
+ };
5206
+ const state = effectiveEntry(env, next);
4661
5207
  if (json) {
4662
5208
  io.emitJson({
4663
- command: "fetch",
4664
- repoAddr: { ownerPubkey: owner, repoId },
4665
- remote,
4666
- relay: relayUrl,
4667
- objectsDownloaded: written,
4668
- updates,
4669
- executed: true
5209
+ command: "entry",
5210
+ ...state,
5211
+ wrote,
5212
+ configPath,
5213
+ ...warnings.length > 0 ? { warnings } : {}
4670
5214
  });
4671
5215
  return 0;
4672
5216
  }
4673
- if (moved.length === 0) {
4674
- io.out("Already up to date.");
4675
- return 0;
4676
- }
4677
- io.out(`From ${relayUrl}`);
4678
- for (const u of moved) {
4679
- const short = (refname) => refname.replace(/^refs\/heads\//, "").replace(/^refs\/tags\//, "").replace(/^refs\/remotes\//, "");
4680
- const src = short(u.refname);
4681
- const dst = short(u.localRef);
4682
- if (u.kind === "new") {
4683
- const label = u.refname.startsWith("refs/tags/") ? "[new tag]" : "[new branch]";
4684
- io.out(` * ${label.padEnd(17)} ${src.padEnd(14)} -> ${dst}`);
4685
- } else if (u.kind === "forced") {
4686
- io.out(
4687
- ` + ${u.oldSha.slice(0, 7)}...${u.newSha.slice(0, 7)} ${src.padEnd(14)} -> ${dst} (forced update)`
4688
- );
4689
- } else {
4690
- io.out(
4691
- ` ${u.oldSha.slice(0, 7)}..${u.newSha.slice(0, 7)} ${src.padEnd(14)} -> ${dst}`
4692
- );
4693
- }
5217
+ if (mutation.kind === "clear") {
5218
+ io.out(`Cleared the recorded connector in ${configPath}.`);
5219
+ } else {
5220
+ io.out(`Recorded connector ${mutation.connectorUrl} in ${configPath}.`);
5221
+ if (mutation.relayUrl !== void 0)
5222
+ io.out(`Recorded relay ${mutation.relayUrl}.`);
4694
5223
  }
4695
- io.out(`Downloaded ${written} object(s) (SHA-1 verified).`);
5224
+ for (const w of warnings) io.out(`warning: ${w}`);
5225
+ io.out(
5226
+ `Effective connector: ${state.connectorUrl ?? "(none)"} [${label(state.connectorSource)}]`
5227
+ );
4696
5228
  return 0;
4697
5229
  } catch (err) {
4698
- return emitCliError(io, json, "fetch", err);
5230
+ return emitCliError(io, json, "entry", err);
4699
5231
  }
4700
5232
  }
4701
5233
 
4702
5234
  // src/cli/fund.ts
4703
5235
  import { readFileSync as readFileSync4 } from "fs";
4704
5236
  import { homedir as homedir4 } from "os";
4705
- import { join as join5 } from "path";
4706
- import { parseArgs as parseArgs11 } from "util";
5237
+ import { join as join6 } from "path";
5238
+ import { parseArgs as parseArgs12 } from "util";
4707
5239
  var DEVNET_FAUCET_URL = "https://faucet.devnet.toonprotocol.dev";
4708
5240
  var CHAINS = ["evm", "solana"];
4709
5241
  var CHAIN_ALIASES = {
@@ -4755,11 +5287,15 @@ Options:
4755
5287
  --json machine-readable envelope (per-chain results array)
4756
5288
  -h, --help show this help`;
4757
5289
  var SHARED_DEVNET_SUFFIX = ".devnet.toonprotocol.dev";
5290
+ var SHARED_DEVNET_HOSTS = /* @__PURE__ */ new Set([
5291
+ "toonprotocol.dev",
5292
+ "relay.toonprotocol.dev"
5293
+ ]);
4758
5294
  function devnetHost(url) {
4759
5295
  if (!url) return void 0;
4760
5296
  try {
4761
5297
  const { hostname } = new URL(url);
4762
- if (hostname.endsWith(SHARED_DEVNET_SUFFIX) || hostname === SHARED_DEVNET_SUFFIX.slice(1)) {
5298
+ if (hostname.endsWith(SHARED_DEVNET_SUFFIX) || hostname === SHARED_DEVNET_SUFFIX.slice(1) || SHARED_DEVNET_HOSTS.has(hostname)) {
4763
5299
  return url;
4764
5300
  }
4765
5301
  } catch {
@@ -4841,8 +5377,8 @@ function noFaucetGuidance(network, devnetOrigin) {
4841
5377
  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}`;
4842
5378
  }
4843
5379
  function readFundConfig(env) {
4844
- const dir = env["TOON_CLIENT_HOME"] ?? join5(homedir4(), ".toon-client");
4845
- const configPath = join5(dir, "config.json");
5380
+ const dir = env["TOON_CLIENT_HOME"] ?? join6(homedir4(), ".toon-client");
5381
+ const configPath = join6(dir, "config.json");
4846
5382
  try {
4847
5383
  return {
4848
5384
  file: JSON.parse(readFileSync4(configPath, "utf8")),
@@ -4863,7 +5399,7 @@ async function runFund(args, deps) {
4863
5399
  let addressFlag;
4864
5400
  let json = false;
4865
5401
  try {
4866
- const { values, positionals } = parseArgs11({
5402
+ const { values, positionals } = parseArgs12({
4867
5403
  args,
4868
5404
  allowPositionals: true,
4869
5405
  options: {
@@ -4953,7 +5489,8 @@ async function runFund(args, deps) {
4953
5489
  }
4954
5490
  const timeoutEnv = env["TOON_CLIENT_FAUCET_TIMEOUT_MS"];
4955
5491
  const timeoutFor = () => {
4956
- if (timeoutEnv && Number.isFinite(Number(timeoutEnv))) return Number(timeoutEnv);
5492
+ if (timeoutEnv && Number.isFinite(Number(timeoutEnv)))
5493
+ return Number(timeoutEnv);
4957
5494
  if (file.faucetTimeoutMs !== void 0) return file.faucetTimeoutMs;
4958
5495
  return 9e4;
4959
5496
  };
@@ -4981,10 +5518,15 @@ async function runFund(args, deps) {
4981
5518
  }
4982
5519
  const started = Date.now();
4983
5520
  try {
4984
- const { response } = await client.fundWallet(faucetUrl, address, chain, {
4985
- timeout: timeoutFor(),
4986
- ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
4987
- });
5521
+ const { response } = await client.fundWallet(
5522
+ faucetUrl,
5523
+ address,
5524
+ chain,
5525
+ {
5526
+ timeout: timeoutFor(),
5527
+ ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
5528
+ }
5529
+ );
4988
5530
  return {
4989
5531
  chain,
4990
5532
  funded: true,
@@ -5030,7 +5572,9 @@ async function runFund(args, deps) {
5030
5572
  }
5031
5573
  }
5032
5574
  io.out(renderIdentityLine(identity));
5033
- io.out("Re-check with `rig balance` (a drip can take a few blocks to land).");
5575
+ io.out(
5576
+ "Re-check with `rig balance` (a drip can take a few blocks to land)."
5577
+ );
5034
5578
  return allFunded ? 0 : 1;
5035
5579
  } catch (err) {
5036
5580
  return emitCliError(io, json, "fund", err);
@@ -5089,7 +5633,7 @@ var runGitPassthrough = (argv, options = {}) => {
5089
5633
  import { existsSync, mkdirSync as mkdirSync4 } from "fs";
5090
5634
  import { dirname as dirname5 } from "path";
5091
5635
  import { createInterface } from "readline/promises";
5092
- import { parseArgs as parseArgs12 } from "util";
5636
+ import { parseArgs as parseArgs13 } from "util";
5093
5637
  async function loadKeyOps() {
5094
5638
  const { generateKeystore, importKeystore } = await import("@toon-protocol/client");
5095
5639
  return { generateKeystore, importKeystore, deriveNostrKeyFromMnemonic };
@@ -5261,7 +5805,7 @@ async function runIdentity(args, deps) {
5261
5805
  }
5262
5806
  }
5263
5807
  function parseMintArgs(args, verb) {
5264
- const { values, positionals } = parseArgs12({
5808
+ const { values, positionals } = parseArgs13({
5265
5809
  args,
5266
5810
  options: {
5267
5811
  force: { type: "boolean", default: false },
@@ -5366,7 +5910,7 @@ async function runIdentityShow(args, deps) {
5366
5910
  let json = false;
5367
5911
  let help = false;
5368
5912
  try {
5369
- const parsed = parseArgs12({
5913
+ const parsed = parseArgs13({
5370
5914
  args,
5371
5915
  options: {
5372
5916
  json: { type: "boolean", default: false },
@@ -5474,15 +6018,15 @@ async function defaultReadSecretLine(prompt, isInteractive) {
5474
6018
  }
5475
6019
 
5476
6020
  // src/cli/inbox.ts
5477
- import { parseArgs as parseArgs13 } from "util";
6021
+ import { parseArgs as parseArgs14 } from "util";
5478
6022
  import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
5479
- import { join as join6 } from "path";
6023
+ import { join as join7 } from "path";
5480
6024
  import {
5481
6025
  ISSUE_KIND as ISSUE_KIND2,
5482
- PATCH_KIND as PATCH_KIND3,
5483
- REPOSITORY_ANNOUNCEMENT_KIND as REPOSITORY_ANNOUNCEMENT_KIND3,
5484
- STATUS_APPLIED_KIND as STATUS_APPLIED_KIND3,
5485
- STATUS_CLOSED_KIND as STATUS_CLOSED_KIND3,
6026
+ PATCH_KIND as PATCH_KIND4,
6027
+ REPOSITORY_ANNOUNCEMENT_KIND as REPOSITORY_ANNOUNCEMENT_KIND4,
6028
+ STATUS_APPLIED_KIND as STATUS_APPLIED_KIND4,
6029
+ STATUS_CLOSED_KIND as STATUS_CLOSED_KIND4,
5486
6030
  STATUS_DRAFT_KIND as STATUS_DRAFT_KIND3,
5487
6031
  STATUS_OPEN_KIND as STATUS_OPEN_KIND3
5488
6032
  } from "@toon-protocol/core/nip34";
@@ -5599,8 +6143,8 @@ Options:
5599
6143
  -h, --help show this help`;
5600
6144
  var STATUS_KINDS2 = [
5601
6145
  STATUS_OPEN_KIND3,
5602
- STATUS_APPLIED_KIND3,
5603
- STATUS_CLOSED_KIND3,
6146
+ STATUS_APPLIED_KIND4,
6147
+ STATUS_CLOSED_KIND4,
5604
6148
  STATUS_DRAFT_KIND3
5605
6149
  ];
5606
6150
  var CI_KINDS = [
@@ -5611,17 +6155,17 @@ var CI_KINDS = [
5611
6155
  CI_JOB_RESULT_KIND
5612
6156
  ];
5613
6157
  var INBOX_KINDS = [
5614
- PATCH_KIND3,
6158
+ PATCH_KIND4,
5615
6159
  ISSUE_KIND2,
5616
6160
  COMMENT_KIND,
5617
6161
  LEGACY_COMMENT_KIND,
5618
6162
  ...STATUS_KINDS2,
5619
6163
  ...CI_KINDS
5620
6164
  ];
5621
- var HEX64_RE4 = /^[0-9a-f]{64}$/;
6165
+ var HEX64_RE5 = /^[0-9a-f]{64}$/;
5622
6166
  var INBOX_CURSOR_FILENAME = "inbox-cursors.json";
5623
6167
  function cursorPath(env) {
5624
- return join6(clientStateDir(env), INBOX_CURSOR_FILENAME);
6168
+ return join7(clientStateDir(env), INBOX_CURSOR_FILENAME);
5625
6169
  }
5626
6170
  function readCursors(env) {
5627
6171
  try {
@@ -5660,7 +6204,7 @@ function hasTopic(event, topic) {
5660
6204
  return event.tags.some((t) => t[0] === "t" && t[1] === topic);
5661
6205
  }
5662
6206
  function typeOf(event) {
5663
- if (event.kind === PATCH_KIND3) {
6207
+ if (event.kind === PATCH_KIND4) {
5664
6208
  if (hasTopic(event, "root-revision")) return "revision";
5665
6209
  if (hasTopic(event, "root") || tagValue2(event.tags, "e") === void 0) {
5666
6210
  return "patch";
@@ -5696,7 +6240,7 @@ function toInboxEntry(event) {
5696
6240
  const type = typeOf(event);
5697
6241
  if (type === null) return null;
5698
6242
  const repo = event.tags.find(
5699
- (t) => t[0] === "a" && t[1]?.startsWith(`${REPOSITORY_ANNOUNCEMENT_KIND3}:`)
6243
+ (t) => t[0] === "a" && t[1]?.startsWith(`${REPOSITORY_ANNOUNCEMENT_KIND4}:`)
5700
6244
  )?.[1];
5701
6245
  return {
5702
6246
  id: event.id,
@@ -5722,7 +6266,7 @@ async function ownerAnnouncement(relays, owner, repoId, ws) {
5722
6266
  relays,
5723
6267
  [
5724
6268
  {
5725
- kinds: [REPOSITORY_ANNOUNCEMENT_KIND3],
6269
+ kinds: [REPOSITORY_ANNOUNCEMENT_KIND4],
5726
6270
  authors: [owner],
5727
6271
  "#d": [repoId]
5728
6272
  }
@@ -5731,7 +6275,7 @@ async function ownerAnnouncement(relays, owner, repoId, ws) {
5731
6275
  ).catch(() => /* @__PURE__ */ new Map());
5732
6276
  return newest(
5733
6277
  [...found.values()].filter(
5734
- (e) => e.kind === REPOSITORY_ANNOUNCEMENT_KIND3 && e.pubkey.toLowerCase() === owner && tagValue2(e.tags, "d") === repoId
6278
+ (e) => e.kind === REPOSITORY_ANNOUNCEMENT_KIND4 && e.pubkey.toLowerCase() === owner && tagValue2(e.tags, "d") === repoId
5735
6279
  )
5736
6280
  );
5737
6281
  }
@@ -5763,7 +6307,7 @@ async function runInbox(args, deps) {
5763
6307
  let remote;
5764
6308
  let relayFlags = [];
5765
6309
  try {
5766
- const { values, positionals } = parseArgs13({
6310
+ const { values, positionals } = parseArgs14({
5767
6311
  args,
5768
6312
  options: INBOX_OPTIONS,
5769
6313
  allowPositionals: true
@@ -5822,14 +6366,14 @@ async function runInbox(args, deps) {
5822
6366
  const announcements = await queryAll(
5823
6367
  configured,
5824
6368
  [
5825
- { kinds: [REPOSITORY_ANNOUNCEMENT_KIND3], authors: [me] },
5826
- { kinds: [REPOSITORY_ANNOUNCEMENT_KIND3], "#maintainers": [me] }
6369
+ { kinds: [REPOSITORY_ANNOUNCEMENT_KIND4], authors: [me] },
6370
+ { kinds: [REPOSITORY_ANNOUNCEMENT_KIND4], "#maintainers": [me] }
5827
6371
  ],
5828
6372
  ws
5829
6373
  );
5830
6374
  const byRepo = /* @__PURE__ */ new Map();
5831
6375
  for (const e of announcements.values()) {
5832
- if (e.kind !== REPOSITORY_ANNOUNCEMENT_KIND3) continue;
6376
+ if (e.kind !== REPOSITORY_ANNOUNCEMENT_KIND4) continue;
5833
6377
  const d = tagValue2(e.tags, "d");
5834
6378
  if (!d) continue;
5835
6379
  const key = `${e.pubkey.toLowerCase()}:${d}`;
@@ -5843,7 +6387,7 @@ async function runInbox(args, deps) {
5843
6387
  if (latest === null) continue;
5844
6388
  if (owner !== me && !parseMaintainers(latest.tags).includes(me)) continue;
5845
6389
  watched.push({
5846
- address: `${REPOSITORY_ANNOUNCEMENT_KIND3}:${owner}:${repoId}`,
6390
+ address: `${REPOSITORY_ANNOUNCEMENT_KIND4}:${owner}:${repoId}`,
5847
6391
  ownerPubkey: owner,
5848
6392
  authorized: authorizedAuthors(owner, latest.tags),
5849
6393
  relays: relaysTag(latest.tags)
@@ -5901,7 +6445,7 @@ async function runInbox(args, deps) {
5901
6445
  const unknownTargets = /* @__PURE__ */ new Set();
5902
6446
  for (const e of statusEvents) {
5903
6447
  const target = tagValue2(e.tags, "e");
5904
- if (!target || !HEX64_RE4.test(target)) continue;
6448
+ if (!target || !HEX64_RE5.test(target)) continue;
5905
6449
  const known = found.get(target);
5906
6450
  if (known) targetAuthors.set(target, known.pubkey.toLowerCase());
5907
6451
  else unknownTargets.add(target);
@@ -6035,7 +6579,7 @@ async function runInbox(args, deps) {
6035
6579
 
6036
6580
  // src/cli/init.ts
6037
6581
  import { basename as basename2 } from "path";
6038
- import { parseArgs as parseArgs14 } from "util";
6582
+ import { parseArgs as parseArgs15 } from "util";
6039
6583
 
6040
6584
  // src/cli/git-author.ts
6041
6585
  var PROFILE_KIND = 0;
@@ -6153,7 +6697,7 @@ Options:
6153
6697
  --json machine-readable report
6154
6698
  -h, --help show this help`;
6155
6699
  function parseInitArgs(args) {
6156
- const { values, positionals } = parseArgs14({
6700
+ const { values, positionals } = parseArgs15({
6157
6701
  args,
6158
6702
  options: {
6159
6703
  "repo-id": { type: "string" },
@@ -6380,8 +6924,8 @@ async function runInit(args, deps) {
6380
6924
  }
6381
6925
 
6382
6926
  // src/cli/maintainers.ts
6383
- import { parseArgs as parseArgs15 } from "util";
6384
- var HEX64_RE5 = /^[0-9a-f]{64}$/;
6927
+ import { parseArgs as parseArgs16 } from "util";
6928
+ var HEX64_RE6 = /^[0-9a-f]{64}$/;
6385
6929
  var REF_AUTHORITY_NOTICE = "Note: maintainers can move refs (publish honored repo state), not only set status.";
6386
6930
  var MAINTAINERS_USAGE = `Usage: rig maintainers <list|add|remove> [<pubkey>] [options]
6387
6931
 
@@ -6434,7 +6978,7 @@ async function runList2(args, deps) {
6434
6978
  const { io } = deps;
6435
6979
  let flags;
6436
6980
  try {
6437
- const { values, positionals } = parseArgs15({
6981
+ const { values, positionals } = parseArgs16({
6438
6982
  args,
6439
6983
  options: REPO_COMMAND_OPTIONS,
6440
6984
  allowPositionals: true
@@ -6501,7 +7045,7 @@ async function runMutate(op, args, deps) {
6501
7045
  let flags;
6502
7046
  let pubkey;
6503
7047
  try {
6504
- const { values, positionals } = parseArgs15({
7048
+ const { values, positionals } = parseArgs16({
6505
7049
  args,
6506
7050
  options: REPO_COMMAND_OPTIONS,
6507
7051
  allowPositionals: true
@@ -6515,7 +7059,7 @@ async function runMutate(op, args, deps) {
6515
7059
  throw new Error(`expected exactly one <pubkey> to ${op}`);
6516
7060
  }
6517
7061
  pubkey = ownerToHex(positionals[0]).toLowerCase();
6518
- if (!HEX64_RE5.test(pubkey)) {
7062
+ if (!HEX64_RE6.test(pubkey)) {
6519
7063
  throw new Error(
6520
7064
  `<pubkey> must resolve to 64-char hex (got ${JSON.stringify(positionals[0])})`
6521
7065
  );
@@ -6636,7 +7180,7 @@ async function runMutate(op, args, deps) {
6636
7180
  }
6637
7181
 
6638
7182
  // src/cli/refresh.ts
6639
- import { parseArgs as parseArgs16 } from "util";
7183
+ import { parseArgs as parseArgs17 } from "util";
6640
7184
  var REFRESH_USAGE = `Usage: rig refresh [options]
6641
7185
 
6642
7186
  Republish this repo's kind:30617 announcement with no field edit, refreshing
@@ -6672,7 +7216,7 @@ async function runRefresh(args, deps) {
6672
7216
  const { io } = deps;
6673
7217
  let flags;
6674
7218
  try {
6675
- const { values, positionals } = parseArgs16({
7219
+ const { values, positionals } = parseArgs17({
6676
7220
  args,
6677
7221
  options: REPO_COMMAND_OPTIONS,
6678
7222
  allowPositionals: true
@@ -6795,7 +7339,7 @@ async function runRefresh(args, deps) {
6795
7339
 
6796
7340
  // src/cli/name.ts
6797
7341
  import { randomUUID } from "crypto";
6798
- import { parseArgs as parseArgs17 } from "util";
7342
+ import { parseArgs as parseArgs18 } from "util";
6799
7343
  var ARIO_DECIMALS = 6;
6800
7344
  function renderMario(mario) {
6801
7345
  return `${mario.toString()} mARIO (~${formatUnits(mario.toString(), ARIO_DECIMALS)} ARIO)`;
@@ -7304,7 +7848,7 @@ async function resolveNameContext(deps, network, processId, mode) {
7304
7848
  return { identity, solanaAddress, network, processId, sdk, nostrSecretKey };
7305
7849
  }
7306
7850
  function parseNameFlags(args, env) {
7307
- const { values, positionals } = parseArgs17({
7851
+ const { values, positionals } = parseArgs18({
7308
7852
  args,
7309
7853
  allowPositionals: true,
7310
7854
  options: {
@@ -7863,7 +8407,7 @@ async function runName(args, deps) {
7863
8407
  }
7864
8408
 
7865
8409
  // src/cli/payout.ts
7866
- import { parseArgs as parseArgs18 } from "util";
8410
+ import { parseArgs as parseArgs19 } from "util";
7867
8411
  import { getAddress } from "viem";
7868
8412
  var PAYOUT_USAGE = `Usage: rig payout <set|clear|show> [<address>] [options]
7869
8413
 
@@ -7919,7 +8463,7 @@ async function runShow2(args, deps) {
7919
8463
  const { io } = deps;
7920
8464
  let flags;
7921
8465
  try {
7922
- const { values, positionals } = parseArgs18({
8466
+ const { values, positionals } = parseArgs19({
7923
8467
  args,
7924
8468
  options: REPO_COMMAND_OPTIONS,
7925
8469
  allowPositionals: true
@@ -7981,7 +8525,7 @@ async function runMutate2(op, args, deps) {
7981
8525
  let flags;
7982
8526
  let nextPayout = null;
7983
8527
  try {
7984
- const { values, positionals } = parseArgs18({
8528
+ const { values, positionals } = parseArgs19({
7985
8529
  args,
7986
8530
  options: REPO_COMMAND_OPTIONS,
7987
8531
  allowPositionals: true
@@ -8115,7 +8659,7 @@ async function runMutate2(op, args, deps) {
8115
8659
  }
8116
8660
 
8117
8661
  // src/cli/site.ts
8118
- import { parseArgs as parseArgs19 } from "util";
8662
+ import { parseArgs as parseArgs20 } from "util";
8119
8663
 
8120
8664
  // src/arweave-manifest.ts
8121
8665
  var DEFAULT_INDEX = "index.html";
@@ -8136,13 +8680,13 @@ function buildArweaveManifest(entries, indexPath = DEFAULT_INDEX, fallbackTxId)
8136
8680
  // src/cli/site-record.ts
8137
8681
  import { mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
8138
8682
  import { homedir as homedir5 } from "os";
8139
- import { join as join7 } from "path";
8683
+ import { join as join8 } from "path";
8140
8684
  var SITE_MANIFEST_FILENAME = "site-manifests.json";
8141
8685
  function storeDir2(env) {
8142
- return env["TOON_CLIENT_HOME"] ?? join7(homedir5(), ".toon-client");
8686
+ return env["TOON_CLIENT_HOME"] ?? join8(homedir5(), ".toon-client");
8143
8687
  }
8144
8688
  function storePath2(env) {
8145
- return join7(storeDir2(env), SITE_MANIFEST_FILENAME);
8689
+ return join8(storeDir2(env), SITE_MANIFEST_FILENAME);
8146
8690
  }
8147
8691
  function keyOf(repoId, ref) {
8148
8692
  return `${repoId}\0${ref}`;
@@ -8206,7 +8750,7 @@ Options (publish):
8206
8750
  estimate (nothing uploaded)
8207
8751
  -h, --help show this help`;
8208
8752
  function parseSiteArgs(args) {
8209
- const { values, positionals } = parseArgs19({
8753
+ const { values, positionals } = parseArgs20({
8210
8754
  args,
8211
8755
  options: {
8212
8756
  index: { type: "string" },
@@ -8616,6 +9160,8 @@ Commands rig owns:
8616
9160
  pr create publish a patch (kind:1617) with real
8617
9161
  \`git format-patch\` content (--revises <patch-id>
8618
9162
  publishes a revision of an existing patch)
9163
+ pr merge <patch-id> apply the latest revision with git am, push, then
9164
+ publish the applied status (owner/maintainer)
8619
9165
  pr status <event-id> <state> set an issue/patch status (kind:1630-1633):
8620
9166
  open | applied | closed | draft
8621
9167
  inbox everything new since you last read it (free):
@@ -8804,4 +9350,4 @@ export {
8804
9350
  rigVersion,
8805
9351
  dispatch
8806
9352
  };
8807
- //# sourceMappingURL=chunk-Y7TUE7JT.js.map
9353
+ //# sourceMappingURL=chunk-Z3XRZ5E5.js.map