@toon-protocol/rig 5.1.0 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-J6GNB54J.js → chunk-DGF7WCRW.js} +22 -4
- package/dist/chunk-DGF7WCRW.js.map +1 -0
- package/dist/{chunk-ANVDRW5F.js → chunk-KQ4NQV7T.js} +1527 -336
- package/dist/chunk-KQ4NQV7T.js.map +1 -0
- package/dist/{chunk-ITC7ESTZ.js → chunk-OVZJ5P6E.js} +81 -38
- package/dist/chunk-OVZJ5P6E.js.map +1 -0
- package/dist/{chunk-P5ZKJN6B.js → chunk-UXRWMKXV.js} +0 -0
- package/dist/{ci-serve-MPJ2WPCC.js → ci-serve-R7ZQ7GJQ.js} +5 -5
- package/dist/cli/rig.js +10 -4
- package/dist/cli/rig.js.map +1 -1
- package/dist/index.d.ts +48 -3
- package/dist/index.js +11 -3
- package/dist/index.js.map +1 -1
- package/dist/{publisher-BGV0OOUk.d.ts → publisher-rblR_O9q.d.ts} +17 -19
- package/dist/standalone/index.d.ts +1 -1
- package/dist/{standalone-mode-IETLYVCE.js → standalone-mode-E7TNMWGO.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-ANVDRW5F.js.map +0 -1
- package/dist/chunk-ITC7ESTZ.js.map +0 -1
- package/dist/chunk-J6GNB54J.js.map +0 -1
- /package/dist/{chunk-P5ZKJN6B.js.map → chunk-UXRWMKXV.js.map} +0 -0
- /package/dist/{ci-serve-MPJ2WPCC.js.map → ci-serve-R7ZQ7GJQ.js.map} +0 -0
- /package/dist/{standalone-mode-IETLYVCE.js.map → standalone-mode-E7TNMWGO.js.map} +0 -0
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CI_ADVERTISEMENT_KIND,
|
|
3
|
+
CI_JOB_RESULT_KIND,
|
|
4
|
+
CI_SERVICE_REQUEST_KIND,
|
|
5
|
+
CI_SERVICE_STOP_KIND,
|
|
3
6
|
CI_STATUS_USAGE,
|
|
7
|
+
CI_WORKFLOW_PROGRESS_KIND,
|
|
8
|
+
CI_WORKFLOW_RESULT_KIND,
|
|
4
9
|
GitRepoReader,
|
|
5
10
|
InvalidRelayUrlError,
|
|
6
11
|
MultiUrlRemoteError,
|
|
7
12
|
NotAGitRepositoryError,
|
|
13
|
+
PrMergeError,
|
|
8
14
|
RESERVED_SECRET_NAMES,
|
|
9
15
|
SECRET_NAME_RE,
|
|
10
16
|
UnconfiguredRepoAddressError,
|
|
@@ -52,13 +58,13 @@ import {
|
|
|
52
58
|
updateRef,
|
|
53
59
|
writeGitObjects,
|
|
54
60
|
writeToonConfig
|
|
55
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-DGF7WCRW.js";
|
|
56
62
|
import {
|
|
57
63
|
PREFERRED_GATEWAY,
|
|
58
64
|
mirrorGatewaysFor,
|
|
59
65
|
readGateways,
|
|
60
66
|
shaResolverFor
|
|
61
|
-
} from "./chunk-
|
|
67
|
+
} from "./chunk-UXRWMKXV.js";
|
|
62
68
|
import {
|
|
63
69
|
COMMENT_KIND,
|
|
64
70
|
DEFAULT_KEYSTORE_PASSWORD,
|
|
@@ -71,18 +77,23 @@ import {
|
|
|
71
77
|
buildStatus,
|
|
72
78
|
clientConfigPath,
|
|
73
79
|
clientKeystorePath,
|
|
80
|
+
clientStateDir,
|
|
74
81
|
commentBelongsToThread,
|
|
82
|
+
decodeEventPayload,
|
|
75
83
|
defaultWebSocketFactory,
|
|
76
84
|
fetchRemoteState,
|
|
77
85
|
firstTagValue,
|
|
86
|
+
isPatchRevision,
|
|
78
87
|
isValidEvmPayoutAddress,
|
|
79
88
|
linkKeystoreInClientConfig,
|
|
80
89
|
parseMaintainers,
|
|
81
90
|
queryRelay,
|
|
82
91
|
readClientConfigFile,
|
|
83
92
|
resolveAccountIndex,
|
|
84
|
-
resolveIdentity
|
|
85
|
-
|
|
93
|
+
resolveIdentity,
|
|
94
|
+
revisionRootId,
|
|
95
|
+
selectRevision
|
|
96
|
+
} from "./chunk-OVZJ5P6E.js";
|
|
86
97
|
import {
|
|
87
98
|
ChannelMapStore,
|
|
88
99
|
channelStatus,
|
|
@@ -311,7 +322,7 @@ function loadPaidSession(deps, relayUrl) {
|
|
|
311
322
|
});
|
|
312
323
|
}
|
|
313
324
|
var defaultLoadStandalone = async (options) => {
|
|
314
|
-
const mod = await import("./standalone-mode-
|
|
325
|
+
const mod = await import("./standalone-mode-E7TNMWGO.js");
|
|
315
326
|
return mod.createStandaloneContext(options);
|
|
316
327
|
};
|
|
317
328
|
function identityReport(ctx) {
|
|
@@ -565,9 +576,23 @@ async function runPush(args, deps) {
|
|
|
565
576
|
} else {
|
|
566
577
|
identity = session.identity;
|
|
567
578
|
}
|
|
568
|
-
|
|
579
|
+
const foreignOwner = toonConfig.owner && toonConfig.owner.toLowerCase() !== identity.pubkey.toLowerCase() ? toonConfig.owner : void 0;
|
|
580
|
+
let maintainerRemote;
|
|
581
|
+
if (session.path === "standalone" && foreignOwner) {
|
|
582
|
+
const ownerState = await session.ctx.fetchRemote({
|
|
583
|
+
ownerPubkey: foreignOwner,
|
|
584
|
+
repoId,
|
|
585
|
+
relayUrls: relaysUsed
|
|
586
|
+
});
|
|
587
|
+
if (ownerState.announced && authorizedAuthors(foreignOwner, ownerState.announceEvent?.tags).has(
|
|
588
|
+
identity.pubkey.toLowerCase()
|
|
589
|
+
)) {
|
|
590
|
+
maintainerRemote = ownerState;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
if (!maintainerRemote && foreignOwner) {
|
|
569
594
|
io.err(
|
|
570
|
-
`warning: git config toon.owner (${
|
|
595
|
+
`warning: git config toon.owner (${foreignOwner.slice(0, 8)}\u2026) differs from the active identity (${identity.pubkey.slice(0, 8)}\u2026) \u2014 this push publishes under the ACTIVE identity's repo namespace, not the configured owner's. Re-run \`rig init\` to adopt the active identity.`
|
|
571
596
|
);
|
|
572
597
|
}
|
|
573
598
|
const announcement = relaysUsed[0] === void 0 ? void 0 : {
|
|
@@ -584,7 +609,7 @@ async function runPush(args, deps) {
|
|
|
584
609
|
let rigPageSkipDetail;
|
|
585
610
|
if (session.path === "standalone") {
|
|
586
611
|
const ctx = session.ctx;
|
|
587
|
-
const remoteState = await ctx.fetchRemote({
|
|
612
|
+
const remoteState = maintainerRemote ?? await ctx.fetchRemote({
|
|
588
613
|
ownerPubkey: ctx.ownerPubkey,
|
|
589
614
|
repoId,
|
|
590
615
|
relayUrls: relaysUsed
|
|
@@ -614,6 +639,8 @@ async function runPush(args, deps) {
|
|
|
614
639
|
rigPageSkipDetail = "skipped (--no-rig-page)";
|
|
615
640
|
} else if (relaysUsed[0] === void 0) {
|
|
616
641
|
rigPageSkipDetail = "skipped (no relay resolved for the pointer)";
|
|
642
|
+
} else if (maintainerRemote) {
|
|
643
|
+
rigPageSkipDetail = "skipped (the owner's rig page is the owner's to refresh)";
|
|
617
644
|
} else if (!ctx.publisher.uploadBlob) {
|
|
618
645
|
rigPageSkipDetail = "skipped (the active publisher cannot upload raw blobs)";
|
|
619
646
|
} else {
|
|
@@ -2280,9 +2307,9 @@ async function runCiTrigger(args, deps) {
|
|
|
2280
2307
|
});
|
|
2281
2308
|
}
|
|
2282
2309
|
async function gitShowBytes(repoRoot, commit, path) {
|
|
2283
|
-
const { execFile } = await import("child_process");
|
|
2310
|
+
const { execFile: execFile2 } = await import("child_process");
|
|
2284
2311
|
return new Promise((resolve2, reject) => {
|
|
2285
|
-
|
|
2312
|
+
execFile2(
|
|
2286
2313
|
"git",
|
|
2287
2314
|
["show", `${commit}:${path}`],
|
|
2288
2315
|
{ cwd: repoRoot, encoding: "buffer", maxBuffer: 16 * 1024 * 1024 },
|
|
@@ -2493,7 +2520,7 @@ async function runCi(args, deps) {
|
|
|
2493
2520
|
case "status":
|
|
2494
2521
|
return runCiStatus(rest, deps);
|
|
2495
2522
|
case "serve":
|
|
2496
|
-
return (await import("./ci-serve-
|
|
2523
|
+
return (await import("./ci-serve-R7ZQ7GJQ.js")).runCiServe(rest, deps);
|
|
2497
2524
|
case "--help":
|
|
2498
2525
|
case "-h":
|
|
2499
2526
|
case "help":
|
|
@@ -2764,16 +2791,257 @@ async function runClone(args, deps) {
|
|
|
2764
2791
|
|
|
2765
2792
|
// src/cli/events.ts
|
|
2766
2793
|
import { readFile } from "fs/promises";
|
|
2767
|
-
import { parseArgs as
|
|
2794
|
+
import { parseArgs as parseArgs10 } from "util";
|
|
2768
2795
|
import {
|
|
2769
|
-
|
|
2796
|
+
PATCH_KIND as PATCH_KIND3,
|
|
2797
|
+
REPOSITORY_ANNOUNCEMENT_KIND as REPOSITORY_ANNOUNCEMENT_KIND3,
|
|
2798
|
+
STATUS_APPLIED_KIND as STATUS_APPLIED_KIND3,
|
|
2770
2799
|
STATUS_CLOSED_KIND as STATUS_CLOSED_KIND2,
|
|
2771
2800
|
STATUS_DRAFT_KIND as STATUS_DRAFT_KIND2,
|
|
2772
2801
|
STATUS_OPEN_KIND as STATUS_OPEN_KIND2
|
|
2773
2802
|
} from "@toon-protocol/core/nip34";
|
|
2774
2803
|
|
|
2775
|
-
// src/cli/
|
|
2804
|
+
// src/cli/merge.ts
|
|
2805
|
+
import { execFile } from "child_process";
|
|
2806
|
+
import { mkdtemp as mkdtemp2, rm as rm2, writeFile } from "fs/promises";
|
|
2807
|
+
import { tmpdir } from "os";
|
|
2808
|
+
import { join as join4 } from "path";
|
|
2809
|
+
import { parseArgs as parseArgs9 } from "util";
|
|
2810
|
+
import { promisify } from "util";
|
|
2811
|
+
import {
|
|
2812
|
+
PATCH_KIND as PATCH_KIND2,
|
|
2813
|
+
REPOSITORY_ANNOUNCEMENT_KIND as REPOSITORY_ANNOUNCEMENT_KIND2,
|
|
2814
|
+
STATUS_APPLIED_KIND as STATUS_APPLIED_KIND2
|
|
2815
|
+
} from "@toon-protocol/core/nip34";
|
|
2816
|
+
|
|
2817
|
+
// src/cli/fetch.ts
|
|
2776
2818
|
import { parseArgs as parseArgs7 } from "util";
|
|
2819
|
+
var FETCH_USAGE = `Usage: rig fetch [remote] [options]
|
|
2820
|
+
|
|
2821
|
+
Fetch from a TOON remote \u2014 FREE (relay reads + Arweave gateway downloads; no
|
|
2822
|
+
payments, no identity needed). Reads the kind:30618 repository state from the
|
|
2823
|
+
remote's relay, downloads only the git objects this repository is missing
|
|
2824
|
+
(SHA-1 verified), and updates the remote-tracking refs
|
|
2825
|
+
(refs/remotes/<remote>/*; tags land at refs/tags/*), reporting what moved.
|
|
2826
|
+
|
|
2827
|
+
No merge happens: integrate with the git passthrough, e.g.
|
|
2828
|
+
\`rig merge origin/main\`. \`rig fetch\` is the TOON transport and shadows
|
|
2829
|
+
\`git fetch\` \u2014 plain-git fetches remain available via \`git fetch\` directly.
|
|
2830
|
+
|
|
2831
|
+
The repo address (toon.repoid/toon.owner) comes from the config \`rig init\`
|
|
2832
|
+
or \`rig clone\` writes; --repo-id/--owner override.
|
|
2833
|
+
|
|
2834
|
+
Options:
|
|
2835
|
+
--repo-id <id> repository id / NIP-34 d-tag (default: git config)
|
|
2836
|
+
--owner <pubkey> repository owner (npub or 64-char hex; default: git config)
|
|
2837
|
+
--relay <url> ad-hoc relay override \u2014 bypasses the remote's URL
|
|
2838
|
+
--gateway <url> gateway to read objects through FIRST, on its raw-bytes
|
|
2839
|
+
route <url>/raw/<txId>, ahead of the public gateway
|
|
2840
|
+
list \u2014 name a private, local or air-gapped gateway to
|
|
2841
|
+
fetch objects the public ones do not carry
|
|
2842
|
+
(env RIG_ARWEAVE_GATEWAY also sets it)
|
|
2843
|
+
--concurrency <n> parallel gateway downloads (default 8)
|
|
2844
|
+
--json machine-readable result envelope
|
|
2845
|
+
-h, --help show this help`;
|
|
2846
|
+
var WS_URL_RE3 = /^wss?:\/\//i;
|
|
2847
|
+
function localRefFor(refname, remote) {
|
|
2848
|
+
if (refname.startsWith("refs/heads/")) {
|
|
2849
|
+
return `refs/remotes/${remote}/${refname.slice("refs/heads/".length)}`;
|
|
2850
|
+
}
|
|
2851
|
+
if (refname.startsWith("refs/tags/")) return refname;
|
|
2852
|
+
return null;
|
|
2853
|
+
}
|
|
2854
|
+
async function resolveLocalRef(repoRoot, refname) {
|
|
2855
|
+
try {
|
|
2856
|
+
const out = await runGit(repoRoot, [
|
|
2857
|
+
"rev-parse",
|
|
2858
|
+
"--verify",
|
|
2859
|
+
"--quiet",
|
|
2860
|
+
refname
|
|
2861
|
+
]);
|
|
2862
|
+
const sha = out.trim();
|
|
2863
|
+
return /^[0-9a-f]{40}$/.test(sha) ? sha : null;
|
|
2864
|
+
} catch {
|
|
2865
|
+
return null;
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
async function runFetch(args, deps) {
|
|
2869
|
+
const { io } = deps;
|
|
2870
|
+
let json = false;
|
|
2871
|
+
let remote = "origin";
|
|
2872
|
+
let relayFlag;
|
|
2873
|
+
let repoIdFlag;
|
|
2874
|
+
let ownerFlag;
|
|
2875
|
+
let gatewayFlag;
|
|
2876
|
+
let concurrency;
|
|
2877
|
+
try {
|
|
2878
|
+
const { values, positionals } = parseArgs7({
|
|
2879
|
+
args,
|
|
2880
|
+
options: {
|
|
2881
|
+
json: { type: "boolean", default: false },
|
|
2882
|
+
relay: { type: "string" },
|
|
2883
|
+
"repo-id": { type: "string" },
|
|
2884
|
+
owner: { type: "string" },
|
|
2885
|
+
gateway: { type: "string" },
|
|
2886
|
+
concurrency: { type: "string" },
|
|
2887
|
+
help: { type: "boolean", short: "h", default: false }
|
|
2888
|
+
},
|
|
2889
|
+
allowPositionals: true
|
|
2890
|
+
});
|
|
2891
|
+
if (values.help) {
|
|
2892
|
+
io.out(FETCH_USAGE);
|
|
2893
|
+
return 0;
|
|
2894
|
+
}
|
|
2895
|
+
json = values.json === true;
|
|
2896
|
+
if (positionals.length > 1) {
|
|
2897
|
+
throw new Error(
|
|
2898
|
+
`expected at most one [remote] argument, got ${positionals.length}`
|
|
2899
|
+
);
|
|
2900
|
+
}
|
|
2901
|
+
if (positionals[0] !== void 0) remote = positionals[0];
|
|
2902
|
+
relayFlag = values.relay;
|
|
2903
|
+
repoIdFlag = values["repo-id"];
|
|
2904
|
+
ownerFlag = values.owner !== void 0 ? ownerToHex(values.owner) : void 0;
|
|
2905
|
+
gatewayFlag = values.gateway;
|
|
2906
|
+
if (values.concurrency !== void 0) {
|
|
2907
|
+
concurrency = Number.parseInt(values.concurrency, 10);
|
|
2908
|
+
if (!Number.isSafeInteger(concurrency) || concurrency < 1) {
|
|
2909
|
+
throw new Error("--concurrency must be a positive integer");
|
|
2910
|
+
}
|
|
2911
|
+
}
|
|
2912
|
+
} catch (err) {
|
|
2913
|
+
io.err(err instanceof Error ? err.message : String(err));
|
|
2914
|
+
io.err(FETCH_USAGE);
|
|
2915
|
+
return 2;
|
|
2916
|
+
}
|
|
2917
|
+
try {
|
|
2918
|
+
const repoRoot = await resolveRepoRoot(deps.cwd);
|
|
2919
|
+
const toonConfig = await readToonConfig(repoRoot);
|
|
2920
|
+
const repoId = repoIdFlag ?? toonConfig.repoId;
|
|
2921
|
+
if (!repoId) throw new UnconfiguredRepoAddressError("repository id");
|
|
2922
|
+
const owner = ownerFlag ?? toonConfig.owner;
|
|
2923
|
+
if (!owner) throw new UnconfiguredRepoAddressError("repository owner");
|
|
2924
|
+
let relayUrl;
|
|
2925
|
+
if (relayFlag !== void 0) {
|
|
2926
|
+
relayUrl = relayFlag;
|
|
2927
|
+
} else {
|
|
2928
|
+
const urls = await getGitRemoteUrls(repoRoot, remote);
|
|
2929
|
+
if (urls.length === 0) throw new UnknownRemoteError(remote);
|
|
2930
|
+
if (urls.length > 1) throw new MultiUrlRemoteError(remote, urls);
|
|
2931
|
+
relayUrl = urls[0];
|
|
2932
|
+
}
|
|
2933
|
+
if (!WS_URL_RE3.test(relayUrl)) {
|
|
2934
|
+
throw new InvalidRelayUrlError(
|
|
2935
|
+
relayUrl,
|
|
2936
|
+
`remote ${JSON.stringify(remote)}`
|
|
2937
|
+
);
|
|
2938
|
+
}
|
|
2939
|
+
const remoteState = await fetchRemoteState({
|
|
2940
|
+
relayUrls: [relayUrl],
|
|
2941
|
+
ownerPubkey: owner,
|
|
2942
|
+
repoId,
|
|
2943
|
+
...deps.webSocketFactory ? { webSocketFactory: deps.webSocketFactory } : {},
|
|
2944
|
+
// The GraphQL fallback for SHAs the map does not cover asks the SAME
|
|
2945
|
+
// permaweb the objects are read from (#183).
|
|
2946
|
+
resolveSha: deps.resolveSha ?? shaResolverFor(gatewayFlag, deps.env)
|
|
2947
|
+
});
|
|
2948
|
+
const planned = [];
|
|
2949
|
+
for (const [refname, sha] of remoteState.refs) {
|
|
2950
|
+
if (!isSafeRefname(refname)) {
|
|
2951
|
+
io.err(
|
|
2952
|
+
`warning: skipping unsafe ref name from relay: ${JSON.stringify(refname)}`
|
|
2953
|
+
);
|
|
2954
|
+
continue;
|
|
2955
|
+
}
|
|
2956
|
+
const localRef = localRefFor(refname, remote);
|
|
2957
|
+
if (localRef === null) {
|
|
2958
|
+
io.err(
|
|
2959
|
+
`warning: skipping ref outside refs/heads and refs/tags: ${refname}`
|
|
2960
|
+
);
|
|
2961
|
+
continue;
|
|
2962
|
+
}
|
|
2963
|
+
planned.push({ refname, localRef, newSha: sha });
|
|
2964
|
+
}
|
|
2965
|
+
const reader = new GitRepoReader(repoRoot);
|
|
2966
|
+
const tips = [...new Set(planned.map((p) => p.newSha))];
|
|
2967
|
+
const presentLocally = new Set(await reader.listAllObjectShas());
|
|
2968
|
+
const collected = await collectRepoObjects({
|
|
2969
|
+
tips,
|
|
2970
|
+
shaToTxId: remoteState.shaToTxId,
|
|
2971
|
+
resolveMissing: (shas) => remoteState.resolveMissing(shas),
|
|
2972
|
+
presentLocally,
|
|
2973
|
+
// `--gateway` / RIG_ARWEAVE_GATEWAY first, public list behind it (#176).
|
|
2974
|
+
gateways: readGateways(gatewayFlag, deps.env),
|
|
2975
|
+
...deps.fetchFn ? { fetchFn: deps.fetchFn } : {},
|
|
2976
|
+
...concurrency !== void 0 ? { concurrency } : {}
|
|
2977
|
+
});
|
|
2978
|
+
if (collected.missing.length > 0) {
|
|
2979
|
+
throw new MissingRemoteObjectsError(
|
|
2980
|
+
collected.missing,
|
|
2981
|
+
`cannot fetch from ${relayUrl}`
|
|
2982
|
+
);
|
|
2983
|
+
}
|
|
2984
|
+
const written = await writeGitObjects(repoRoot, collected.objects.values());
|
|
2985
|
+
const updates = [];
|
|
2986
|
+
for (const { refname, localRef, newSha } of planned) {
|
|
2987
|
+
const oldSha = await resolveLocalRef(repoRoot, localRef);
|
|
2988
|
+
if (oldSha === newSha) {
|
|
2989
|
+
updates.push({ refname, localRef, oldSha, newSha, kind: "up-to-date" });
|
|
2990
|
+
continue;
|
|
2991
|
+
}
|
|
2992
|
+
let kind;
|
|
2993
|
+
if (oldSha === null) {
|
|
2994
|
+
kind = "new";
|
|
2995
|
+
} else {
|
|
2996
|
+
kind = await reader.isAncestor(oldSha, newSha) ? "fast-forward" : "forced";
|
|
2997
|
+
}
|
|
2998
|
+
await updateRef(repoRoot, localRef, newSha);
|
|
2999
|
+
updates.push({ refname, localRef, oldSha, newSha, kind });
|
|
3000
|
+
}
|
|
3001
|
+
const moved = updates.filter((u) => u.kind !== "up-to-date");
|
|
3002
|
+
if (json) {
|
|
3003
|
+
io.emitJson({
|
|
3004
|
+
command: "fetch",
|
|
3005
|
+
repoAddr: { ownerPubkey: owner, repoId },
|
|
3006
|
+
remote,
|
|
3007
|
+
relay: relayUrl,
|
|
3008
|
+
objectsDownloaded: written,
|
|
3009
|
+
updates,
|
|
3010
|
+
executed: true
|
|
3011
|
+
});
|
|
3012
|
+
return 0;
|
|
3013
|
+
}
|
|
3014
|
+
if (moved.length === 0) {
|
|
3015
|
+
io.out("Already up to date.");
|
|
3016
|
+
return 0;
|
|
3017
|
+
}
|
|
3018
|
+
io.out(`From ${relayUrl}`);
|
|
3019
|
+
for (const u of moved) {
|
|
3020
|
+
const short = (refname) => refname.replace(/^refs\/heads\//, "").replace(/^refs\/tags\//, "").replace(/^refs\/remotes\//, "");
|
|
3021
|
+
const src = short(u.refname);
|
|
3022
|
+
const dst = short(u.localRef);
|
|
3023
|
+
if (u.kind === "new") {
|
|
3024
|
+
const label = u.refname.startsWith("refs/tags/") ? "[new tag]" : "[new branch]";
|
|
3025
|
+
io.out(` * ${label.padEnd(17)} ${src.padEnd(14)} -> ${dst}`);
|
|
3026
|
+
} else if (u.kind === "forced") {
|
|
3027
|
+
io.out(
|
|
3028
|
+
` + ${u.oldSha.slice(0, 7)}...${u.newSha.slice(0, 7)} ${src.padEnd(14)} -> ${dst} (forced update)`
|
|
3029
|
+
);
|
|
3030
|
+
} else {
|
|
3031
|
+
io.out(
|
|
3032
|
+
` ${u.oldSha.slice(0, 7)}..${u.newSha.slice(0, 7)} ${src.padEnd(14)} -> ${dst}`
|
|
3033
|
+
);
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
io.out(`Downloaded ${written} object(s) (SHA-1 verified).`);
|
|
3037
|
+
return 0;
|
|
3038
|
+
} catch (err) {
|
|
3039
|
+
return emitCliError(io, json, "fetch", err);
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
|
|
3043
|
+
// src/cli/tracker.ts
|
|
3044
|
+
import { parseArgs as parseArgs8 } from "util";
|
|
2777
3045
|
import {
|
|
2778
3046
|
ISSUE_KIND,
|
|
2779
3047
|
PATCH_KIND,
|
|
@@ -2859,7 +3127,7 @@ function withTargetAuthor(authorized, targetAuthorPubkey) {
|
|
|
2859
3127
|
}
|
|
2860
3128
|
var RELAY_TIMEOUT_MS = 1e4;
|
|
2861
3129
|
var HEX64_RE2 = /^[0-9a-f]{64}$/;
|
|
2862
|
-
var
|
|
3130
|
+
var WS_URL_RE4 = /^wss?:\/\//i;
|
|
2863
3131
|
function defaultWebSocketFactory2(url) {
|
|
2864
3132
|
const ctor = globalThis.WebSocket;
|
|
2865
3133
|
if (!ctor) {
|
|
@@ -2943,7 +3211,7 @@ async function resolveTrackerContext(flags, deps, needRepoAddr) {
|
|
|
2943
3211
|
repoRoot,
|
|
2944
3212
|
toonRelays: toonConfig.relays
|
|
2945
3213
|
});
|
|
2946
|
-
const wsRelays = resolved.relays.filter((url) =>
|
|
3214
|
+
const wsRelays = resolved.relays.filter((url) => WS_URL_RE4.test(url));
|
|
2947
3215
|
if (wsRelays.length === 0) {
|
|
2948
3216
|
throw new InvalidRelayUrlError(
|
|
2949
3217
|
resolved.relays[0] ?? "",
|
|
@@ -3031,7 +3299,7 @@ async function fetchItems(ctx, kind) {
|
|
|
3031
3299
|
)
|
|
3032
3300
|
]);
|
|
3033
3301
|
const items = [...itemEvents.values()].filter(
|
|
3034
|
-
(e) => e.kind === kind && e.tags.some((t) => t[0] === "a" && t[1] === aTag)
|
|
3302
|
+
(e) => e.kind === kind && e.tags.some((t) => t[0] === "a" && t[1] === aTag) && !(kind === PATCH_KIND && isPatchRevision(e))
|
|
3035
3303
|
);
|
|
3036
3304
|
const statuses = new Map(aStatusEvents);
|
|
3037
3305
|
if (items.length > 0) {
|
|
@@ -3065,6 +3333,12 @@ async function fetchItem(ctx, eventId, expectedKind) {
|
|
|
3065
3333
|
if (!event) {
|
|
3066
3334
|
throw new Error(`event ${eventId} not found on ${ctx.relays.join(", ")}`);
|
|
3067
3335
|
}
|
|
3336
|
+
if (event.kind === PATCH_KIND && isPatchRevision(event)) {
|
|
3337
|
+
const rootId = revisionRootId(event);
|
|
3338
|
+
if (rootId !== void 0 && rootId !== eventId) {
|
|
3339
|
+
return fetchItem(ctx, rootId, expectedKind);
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3068
3342
|
if (event.kind !== expectedKind) {
|
|
3069
3343
|
const hint = event.kind === PATCH_KIND ? " (it is a kind:1617 patch \u2014 use `rig pr show`)" : event.kind === ISSUE_KIND ? " (it is a kind:1621 issue \u2014 use `rig issue show`)" : "";
|
|
3070
3344
|
throw new Error(
|
|
@@ -3105,18 +3379,51 @@ async function fetchItem(ctx, eventId, expectedKind) {
|
|
|
3105
3379
|
parsedAddr.ownerPubkey,
|
|
3106
3380
|
parsedAddr.repoId
|
|
3107
3381
|
) : /* @__PURE__ */ new Set();
|
|
3108
|
-
|
|
3109
|
-
|
|
3382
|
+
const item = parseTrackerItem(
|
|
3383
|
+
event,
|
|
3384
|
+
deriveStatus(
|
|
3385
|
+
eventId,
|
|
3386
|
+
statusEvents.values(),
|
|
3387
|
+
withTargetAuthor(authorized, event.pubkey)
|
|
3388
|
+
)
|
|
3389
|
+
);
|
|
3390
|
+
let revisions;
|
|
3391
|
+
if (event.kind === PATCH_KIND) {
|
|
3392
|
+
const revisionEvents = await queryAll(
|
|
3393
|
+
ctx.relays,
|
|
3394
|
+
[{ kinds: [PATCH_KIND], "#e": [eventId] }],
|
|
3395
|
+
ctx.webSocketFactory
|
|
3396
|
+
);
|
|
3397
|
+
const { latest, earlier } = selectRevision(
|
|
3110
3398
|
event,
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3399
|
+
[...revisionEvents.values()].filter((e) => e.kind === PATCH_KIND),
|
|
3400
|
+
authorized
|
|
3401
|
+
);
|
|
3402
|
+
const describe = (e) => ({
|
|
3403
|
+
eventId: e.id,
|
|
3404
|
+
authorPubkey: e.pubkey,
|
|
3405
|
+
createdAt: e.created_at
|
|
3406
|
+
});
|
|
3407
|
+
revisions = { latest: describe(latest), earlier: earlier.map(describe) };
|
|
3408
|
+
if (latest.id !== event.id) {
|
|
3409
|
+
const shown = parseTrackerItem(latest, item.status);
|
|
3410
|
+
item.content = shown.content;
|
|
3411
|
+
item.title = shown.title;
|
|
3412
|
+
item.commitShas = shown.commitShas ?? [];
|
|
3413
|
+
delete item.branch;
|
|
3414
|
+
delete item.description;
|
|
3415
|
+
if (shown.branch !== void 0) item.branch = shown.branch;
|
|
3416
|
+
if (shown.description !== void 0) {
|
|
3417
|
+
item.description = shown.description;
|
|
3418
|
+
}
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
return {
|
|
3422
|
+
item,
|
|
3423
|
+
...revisions ? { revisions } : {},
|
|
3424
|
+
comments,
|
|
3425
|
+
repoATag: repoATag2
|
|
3426
|
+
};
|
|
3120
3427
|
}
|
|
3121
3428
|
function parseRepoATag(aTag) {
|
|
3122
3429
|
if (!aTag) return null;
|
|
@@ -3131,7 +3438,7 @@ async function runList(args, deps, spec) {
|
|
|
3131
3438
|
const { io } = deps;
|
|
3132
3439
|
let flags;
|
|
3133
3440
|
try {
|
|
3134
|
-
const { values, positionals } =
|
|
3441
|
+
const { values, positionals } = parseArgs8({
|
|
3135
3442
|
args,
|
|
3136
3443
|
options: TRACKER_OPTIONS,
|
|
3137
3444
|
allowPositionals: true
|
|
@@ -3196,7 +3503,7 @@ async function runShow(args, deps, spec) {
|
|
|
3196
3503
|
let flags;
|
|
3197
3504
|
let eventId;
|
|
3198
3505
|
try {
|
|
3199
|
-
const { values, positionals } =
|
|
3506
|
+
const { values, positionals } = parseArgs8({
|
|
3200
3507
|
args,
|
|
3201
3508
|
options: TRACKER_OPTIONS,
|
|
3202
3509
|
allowPositionals: true
|
|
@@ -3229,6 +3536,10 @@ async function runShow(args, deps, spec) {
|
|
|
3229
3536
|
relays: ctx.relays,
|
|
3230
3537
|
repoATag: shown.repoATag,
|
|
3231
3538
|
[spec.jsonKey]: shown.item,
|
|
3539
|
+
...shown.revisions ? {
|
|
3540
|
+
latestRevision: shown.revisions.latest,
|
|
3541
|
+
earlierRevisions: shown.revisions.earlier
|
|
3542
|
+
} : {},
|
|
3232
3543
|
comments: shown.comments
|
|
3233
3544
|
});
|
|
3234
3545
|
return 0;
|
|
@@ -3245,6 +3556,20 @@ async function runShow(args, deps, spec) {
|
|
|
3245
3556
|
io.out(`Commits: ${item.commitShas.join(", ")}`);
|
|
3246
3557
|
}
|
|
3247
3558
|
if (shown.repoATag !== null) io.out(`Repo: ${shown.repoATag}`);
|
|
3559
|
+
if (shown.revisions) {
|
|
3560
|
+
const { latest, earlier } = shown.revisions;
|
|
3561
|
+
if (earlier.length > 0) {
|
|
3562
|
+
io.out(
|
|
3563
|
+
`Showing: ${latest.eventId} (latest revision, by ${latest.authorPubkey.slice(0, 8)}, ${isoDate(latest.createdAt)})`
|
|
3564
|
+
);
|
|
3565
|
+
io.out("Earlier revisions (oldest first):");
|
|
3566
|
+
for (const r of shown.revisions.earlier) {
|
|
3567
|
+
io.out(
|
|
3568
|
+
` ${r.eventId} (${r.authorPubkey.slice(0, 8)}, ${isoDate(r.createdAt)})`
|
|
3569
|
+
);
|
|
3570
|
+
}
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3248
3573
|
if (item.description !== void 0) {
|
|
3249
3574
|
io.out("");
|
|
3250
3575
|
io.out("Body:");
|
|
@@ -3306,6 +3631,435 @@ function runPrShow(args, deps) {
|
|
|
3306
3631
|
});
|
|
3307
3632
|
}
|
|
3308
3633
|
|
|
3634
|
+
// src/cli/merge.ts
|
|
3635
|
+
var execFileAsync = promisify(execFile);
|
|
3636
|
+
var HEX64_RE3 = /^[0-9a-f]{64}$/;
|
|
3637
|
+
var PR_MERGE_USAGE = `Usage: rig pr merge <patch-id> [--target <branch>] [options]
|
|
3638
|
+
|
|
3639
|
+
Apply the latest authorized revision of a patch with \`git am\` onto the
|
|
3640
|
+
honored tip of the target branch, push the result, and publish an applied
|
|
3641
|
+
status (kind:1631) naming the new commit SHAs \u2014 in that order, so the status
|
|
3642
|
+
never claims a merge that did not happen. A paid command; writes are permanent
|
|
3643
|
+
and non-refundable. Only the owner or a declared maintainer may merge.
|
|
3644
|
+
|
|
3645
|
+
If the patch does not apply, nothing is spent and your working tree is left as
|
|
3646
|
+
it was. If the push succeeds but the status publish fails, the merge is
|
|
3647
|
+
reported as a partial success; retry with \`rig pr status <patch-id> applied\`.
|
|
3648
|
+
|
|
3649
|
+
Options:
|
|
3650
|
+
--target <branch> branch to merge onto (default: the patch's branch-name
|
|
3651
|
+
tag, else the repo's default branch)
|
|
3652
|
+
--yes skip the confirmation prompt
|
|
3653
|
+
--json machine-readable output (without --yes: estimate only)
|
|
3654
|
+
--relay <url> ad-hoc relay override (bypasses git remotes)
|
|
3655
|
+
--remote <name> git remote to use (default: origin)
|
|
3656
|
+
--repo-id <id> repository id (default: git config toon.repoid)
|
|
3657
|
+
--owner <pubkey> repository owner (default: git config toon.owner)`;
|
|
3658
|
+
function parseMergeArgs(args) {
|
|
3659
|
+
const { values, positionals } = parseArgs9({
|
|
3660
|
+
args,
|
|
3661
|
+
options: {
|
|
3662
|
+
target: { type: "string" },
|
|
3663
|
+
yes: { type: "boolean", default: false },
|
|
3664
|
+
json: { type: "boolean", default: false },
|
|
3665
|
+
relay: { type: "string", multiple: true },
|
|
3666
|
+
remote: { type: "string" },
|
|
3667
|
+
"repo-id": { type: "string" },
|
|
3668
|
+
owner: { type: "string" },
|
|
3669
|
+
standalone: { type: "boolean", default: false },
|
|
3670
|
+
"no-daemon": { type: "boolean", default: false },
|
|
3671
|
+
help: { type: "boolean", short: "h", default: false }
|
|
3672
|
+
},
|
|
3673
|
+
allowPositionals: true
|
|
3674
|
+
});
|
|
3675
|
+
if (values.help) return "help";
|
|
3676
|
+
if (positionals.length !== 1) {
|
|
3677
|
+
throw new Error("expected exactly one argument: <patch-id>");
|
|
3678
|
+
}
|
|
3679
|
+
const patchId = positionals[0];
|
|
3680
|
+
if (!HEX64_RE3.test(patchId)) {
|
|
3681
|
+
throw new Error("<patch-id> must be a 64-character lowercase hex event id");
|
|
3682
|
+
}
|
|
3683
|
+
if (values.owner !== void 0 && !HEX64_RE3.test(values.owner)) {
|
|
3684
|
+
throw new Error("--owner must be a 64-character lowercase hex pubkey");
|
|
3685
|
+
}
|
|
3686
|
+
const flags = {
|
|
3687
|
+
patchId,
|
|
3688
|
+
yes: values.yes === true,
|
|
3689
|
+
json: values.json === true,
|
|
3690
|
+
relay: values.relay ?? []
|
|
3691
|
+
};
|
|
3692
|
+
if (values.target !== void 0) flags.target = values.target;
|
|
3693
|
+
if (values.remote !== void 0) flags.remote = values.remote;
|
|
3694
|
+
if (values["repo-id"] !== void 0) flags.repoId = values["repo-id"];
|
|
3695
|
+
if (values.owner !== void 0) flags.owner = values.owner;
|
|
3696
|
+
return flags;
|
|
3697
|
+
}
|
|
3698
|
+
async function git(cwd, args, env) {
|
|
3699
|
+
const { stdout } = await execFileAsync("git", args, {
|
|
3700
|
+
cwd,
|
|
3701
|
+
encoding: "utf-8",
|
|
3702
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
3703
|
+
...env ? { env } : {}
|
|
3704
|
+
});
|
|
3705
|
+
return stdout.trim();
|
|
3706
|
+
}
|
|
3707
|
+
async function gitOk(cwd, args) {
|
|
3708
|
+
try {
|
|
3709
|
+
await git(cwd, args);
|
|
3710
|
+
return true;
|
|
3711
|
+
} catch {
|
|
3712
|
+
return false;
|
|
3713
|
+
}
|
|
3714
|
+
}
|
|
3715
|
+
function gitFailure(err) {
|
|
3716
|
+
const e = err;
|
|
3717
|
+
return (e.stderr || e.stdout || e.message || String(err)).trim();
|
|
3718
|
+
}
|
|
3719
|
+
var OverlayReader = class extends GitRepoReader {
|
|
3720
|
+
constructor(repoPath, overlay) {
|
|
3721
|
+
super(repoPath);
|
|
3722
|
+
this.overlay = overlay;
|
|
3723
|
+
}
|
|
3724
|
+
overlay;
|
|
3725
|
+
async listRefs() {
|
|
3726
|
+
const base = await super.listRefs();
|
|
3727
|
+
const refs = base.refs.filter(
|
|
3728
|
+
(r) => r.refname !== this.overlay.refname
|
|
3729
|
+
);
|
|
3730
|
+
refs.push({
|
|
3731
|
+
refname: this.overlay.refname,
|
|
3732
|
+
sha: this.overlay.sha,
|
|
3733
|
+
type: "commit"
|
|
3734
|
+
});
|
|
3735
|
+
return { ...base, refs };
|
|
3736
|
+
}
|
|
3737
|
+
};
|
|
3738
|
+
async function applyInIsolation(repoRoot, tip, patchText, committer) {
|
|
3739
|
+
const scratch = await mkdtemp2(join4(tmpdir(), "rig-merge-"));
|
|
3740
|
+
const worktree = join4(scratch, "wt");
|
|
3741
|
+
try {
|
|
3742
|
+
await git(repoRoot, ["worktree", "add", "--detach", worktree, tip]);
|
|
3743
|
+
const patchFile = join4(scratch, "patch.mbox");
|
|
3744
|
+
await writeFile(patchFile, patchText, "utf-8");
|
|
3745
|
+
const env = { ...process.env };
|
|
3746
|
+
if (committer) {
|
|
3747
|
+
env["GIT_COMMITTER_NAME"] = committer.name;
|
|
3748
|
+
env["GIT_COMMITTER_EMAIL"] = committer.email;
|
|
3749
|
+
}
|
|
3750
|
+
try {
|
|
3751
|
+
await git(worktree, ["am", "--quiet", patchFile], env);
|
|
3752
|
+
} catch (err) {
|
|
3753
|
+
await git(worktree, ["am", "--abort"]).catch(() => void 0);
|
|
3754
|
+
throw new PrMergeError(
|
|
3755
|
+
"apply_conflict",
|
|
3756
|
+
`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.
|
|
3757
|
+
` + gitFailure(err)
|
|
3758
|
+
);
|
|
3759
|
+
}
|
|
3760
|
+
const newHead = await git(worktree, ["rev-parse", "HEAD"]);
|
|
3761
|
+
const listed = await git(worktree, [
|
|
3762
|
+
"rev-list",
|
|
3763
|
+
"--reverse",
|
|
3764
|
+
`${tip}..${newHead}`
|
|
3765
|
+
]);
|
|
3766
|
+
const commits = listed === "" ? [] : listed.split("\n");
|
|
3767
|
+
if (commits.length === 0) {
|
|
3768
|
+
throw new PrMergeError(
|
|
3769
|
+
"nothing_to_apply",
|
|
3770
|
+
"the patch applied no commits (already merged?) \u2014 nothing was pushed, published, or paid."
|
|
3771
|
+
);
|
|
3772
|
+
}
|
|
3773
|
+
return { newHead, commits };
|
|
3774
|
+
} finally {
|
|
3775
|
+
await git(repoRoot, ["worktree", "remove", "--force", worktree]).catch(
|
|
3776
|
+
() => void 0
|
|
3777
|
+
);
|
|
3778
|
+
await git(repoRoot, ["worktree", "prune"]).catch(() => void 0);
|
|
3779
|
+
await rm2(scratch, { recursive: true, force: true });
|
|
3780
|
+
}
|
|
3781
|
+
}
|
|
3782
|
+
var BRANCH_PREFIX = "refs/heads/";
|
|
3783
|
+
async function runPrMerge(args, deps) {
|
|
3784
|
+
const { io } = deps;
|
|
3785
|
+
let flags;
|
|
3786
|
+
try {
|
|
3787
|
+
const parsed = parseMergeArgs(args);
|
|
3788
|
+
if (parsed === "help") {
|
|
3789
|
+
io.out(PR_MERGE_USAGE);
|
|
3790
|
+
return 0;
|
|
3791
|
+
}
|
|
3792
|
+
flags = parsed;
|
|
3793
|
+
} catch (err) {
|
|
3794
|
+
io.err(err instanceof Error ? err.message : String(err));
|
|
3795
|
+
io.err(PR_MERGE_USAGE);
|
|
3796
|
+
return 2;
|
|
3797
|
+
}
|
|
3798
|
+
deps = withForcedStandalone(deps);
|
|
3799
|
+
const webSocketFactory = deps.webSocketFactory ?? defaultWebSocketFactory;
|
|
3800
|
+
let stop;
|
|
3801
|
+
try {
|
|
3802
|
+
const repoRoot = await resolveRepoRoot(deps.cwd);
|
|
3803
|
+
const toonConfig = await readToonConfig(repoRoot);
|
|
3804
|
+
const repoId = flags.repoId ?? toonConfig.repoId;
|
|
3805
|
+
if (!repoId) throw new UnconfiguredRepoAddressError("repository id");
|
|
3806
|
+
const resolved = await resolveRelays({
|
|
3807
|
+
relayFlags: flags.relay,
|
|
3808
|
+
remoteName: flags.remote,
|
|
3809
|
+
repoRoot,
|
|
3810
|
+
toonRelays: toonConfig.relays
|
|
3811
|
+
});
|
|
3812
|
+
if (resolved.nudge !== void 0) io.err(resolved.nudge);
|
|
3813
|
+
const relays = resolved.relays;
|
|
3814
|
+
if (relays.length > 1) {
|
|
3815
|
+
io.err(singleRelayRefusal(resolved, "Nothing was pushed or paid."));
|
|
3816
|
+
return 1;
|
|
3817
|
+
}
|
|
3818
|
+
const relay = relays[0];
|
|
3819
|
+
const session = await loadPaidSession(deps, relay);
|
|
3820
|
+
if (session.path !== "standalone") {
|
|
3821
|
+
throw new Error("internal: rig pr merge requires the standalone session");
|
|
3822
|
+
}
|
|
3823
|
+
const ctx = session.ctx;
|
|
3824
|
+
stop = () => ctx.stop();
|
|
3825
|
+
const identity = identityReport(ctx);
|
|
3826
|
+
const owner = flags.owner ?? toonConfig.owner ?? identity.pubkey;
|
|
3827
|
+
const addr = { ownerPubkey: owner, repoId };
|
|
3828
|
+
const aTag = `${REPOSITORY_ANNOUNCEMENT_KIND2}:${owner}:${repoId}`;
|
|
3829
|
+
const lookup = async (id) => (await queryAll(relays, [{ ids: [id] }], webSocketFactory)).get(id);
|
|
3830
|
+
let root = await lookup(flags.patchId);
|
|
3831
|
+
if (root && root.kind === PATCH_KIND2 && isPatchRevision(root)) {
|
|
3832
|
+
const rootId = revisionRootId(root);
|
|
3833
|
+
if (rootId !== void 0) root = await lookup(rootId);
|
|
3834
|
+
}
|
|
3835
|
+
if (!root || root.kind !== PATCH_KIND2) {
|
|
3836
|
+
throw new PrMergeError(
|
|
3837
|
+
"patch_not_found",
|
|
3838
|
+
`patch ${flags.patchId} not found as a kind:1617 patch on ${relay} \u2014 nothing was pushed or paid`
|
|
3839
|
+
);
|
|
3840
|
+
}
|
|
3841
|
+
if (!root.tags.some((t) => t[0] === "a" && t[1] === aTag)) {
|
|
3842
|
+
throw new PrMergeError(
|
|
3843
|
+
"patch_not_found",
|
|
3844
|
+
`patch ${root.id} does not belong to ${aTag} \u2014 nothing was pushed or paid`
|
|
3845
|
+
);
|
|
3846
|
+
}
|
|
3847
|
+
const remote = await ctx.fetchRemote({
|
|
3848
|
+
ownerPubkey: owner,
|
|
3849
|
+
repoId,
|
|
3850
|
+
relayUrls: relays
|
|
3851
|
+
});
|
|
3852
|
+
const authorized = authorizedAuthors(
|
|
3853
|
+
owner,
|
|
3854
|
+
remote.announceEvent?.tags ?? []
|
|
3855
|
+
);
|
|
3856
|
+
const revisionEvents = await queryAll(
|
|
3857
|
+
relays,
|
|
3858
|
+
[{ kinds: [PATCH_KIND2], "#e": [root.id] }],
|
|
3859
|
+
webSocketFactory
|
|
3860
|
+
);
|
|
3861
|
+
const { latest } = selectRevision(
|
|
3862
|
+
root,
|
|
3863
|
+
[...revisionEvents.values()].filter((e) => e.kind === PATCH_KIND2),
|
|
3864
|
+
authorized
|
|
3865
|
+
);
|
|
3866
|
+
if (!authorized.has(identity.pubkey.toLowerCase())) {
|
|
3867
|
+
throw new PrMergeError(
|
|
3868
|
+
"not_authorized",
|
|
3869
|
+
`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.`
|
|
3870
|
+
);
|
|
3871
|
+
}
|
|
3872
|
+
if (!remote.announced) {
|
|
3873
|
+
throw new PrMergeError(
|
|
3874
|
+
"target_not_found",
|
|
3875
|
+
`${aTag} is not announced on ${relay} \u2014 nothing to merge onto`
|
|
3876
|
+
);
|
|
3877
|
+
}
|
|
3878
|
+
const branch = (flags.target ?? firstTagValue(latest.tags, "branch-name") ?? remote.headSymref ?? "main").replace(/^refs\/heads\//, "");
|
|
3879
|
+
const refname = `${BRANCH_PREFIX}${branch}`;
|
|
3880
|
+
const tip = remote.refs.get(refname);
|
|
3881
|
+
if (tip === void 0) {
|
|
3882
|
+
throw new PrMergeError(
|
|
3883
|
+
"target_not_found",
|
|
3884
|
+
`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.`
|
|
3885
|
+
);
|
|
3886
|
+
}
|
|
3887
|
+
if (!await gitOk(repoRoot, ["cat-file", "-e", `${tip}^{commit}`])) {
|
|
3888
|
+
await runFetch(
|
|
3889
|
+
["--relay", relay, "--owner", owner, "--repo-id", repoId],
|
|
3890
|
+
{ ...deps, io: { ...io, out: () => void 0 } }
|
|
3891
|
+
);
|
|
3892
|
+
if (!await gitOk(repoRoot, ["cat-file", "-e", `${tip}^{commit}`])) {
|
|
3893
|
+
throw new PrMergeError(
|
|
3894
|
+
"tip_unavailable",
|
|
3895
|
+
`the honored tip ${tip.slice(0, 7)} of ${branch} could not be fetched \u2014 run \`rig fetch\` and retry. Nothing was pushed or paid.`
|
|
3896
|
+
);
|
|
3897
|
+
}
|
|
3898
|
+
}
|
|
3899
|
+
const reader = new GitRepoReader(repoRoot);
|
|
3900
|
+
const localRefs = (await reader.listRefs()).refs;
|
|
3901
|
+
const localSha = localRefs.find((r) => r.refname === refname)?.sha;
|
|
3902
|
+
if (localSha !== void 0 && localSha !== tip && !await reader.isAncestor(localSha, tip)) {
|
|
3903
|
+
throw new PrMergeError(
|
|
3904
|
+
"local_branch_diverged",
|
|
3905
|
+
`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.`
|
|
3906
|
+
);
|
|
3907
|
+
}
|
|
3908
|
+
const hasCommitter = await gitOk(repoRoot, ["config", "user.email"]);
|
|
3909
|
+
const applied = await applyInIsolation(
|
|
3910
|
+
repoRoot,
|
|
3911
|
+
tip,
|
|
3912
|
+
latest.content,
|
|
3913
|
+
hasCommitter ? void 0 : {
|
|
3914
|
+
name: hexToNpub(identity.pubkey),
|
|
3915
|
+
email: `${hexToNpub(identity.pubkey)}@nostr`
|
|
3916
|
+
}
|
|
3917
|
+
);
|
|
3918
|
+
const overlay = new OverlayReader(repoRoot, {
|
|
3919
|
+
refname,
|
|
3920
|
+
sha: applied.newHead
|
|
3921
|
+
});
|
|
3922
|
+
const feeRates = await ctx.publisher.getFeeRates();
|
|
3923
|
+
const pushPlan = await planPush({
|
|
3924
|
+
repoReader: overlay,
|
|
3925
|
+
remoteState: remote,
|
|
3926
|
+
feeRates,
|
|
3927
|
+
repoId,
|
|
3928
|
+
refs: [refname],
|
|
3929
|
+
force: false
|
|
3930
|
+
});
|
|
3931
|
+
const plan = serializePushPlan(pushPlan);
|
|
3932
|
+
const total = BigInt(plan.estimate.totalFee) + feeRates.eventFee;
|
|
3933
|
+
if (!flags.json) {
|
|
3934
|
+
for (const line of renderPlan(plan)) io.out(line);
|
|
3935
|
+
io.out(
|
|
3936
|
+
` status kind:1631 applied (${applied.commits.length} commit${applied.commits.length === 1 ? "" : "s"}) ${feeRates.eventFee}`
|
|
3937
|
+
);
|
|
3938
|
+
io.out(` total with status ${total}`);
|
|
3939
|
+
io.out(renderIdentityLine(identity));
|
|
3940
|
+
}
|
|
3941
|
+
if (!flags.yes) {
|
|
3942
|
+
if (flags.json) {
|
|
3943
|
+
io.emitJson({
|
|
3944
|
+
command: "pr merge",
|
|
3945
|
+
repoAddr: addr,
|
|
3946
|
+
identity,
|
|
3947
|
+
executed: false,
|
|
3948
|
+
patchId: root.id,
|
|
3949
|
+
revisionId: latest.id,
|
|
3950
|
+
target: refname,
|
|
3951
|
+
mergedShas: applied.commits,
|
|
3952
|
+
plan,
|
|
3953
|
+
totalFee: total.toString(),
|
|
3954
|
+
hint: "estimate only \u2014 re-run with --yes to push and publish (permanent, non-refundable)"
|
|
3955
|
+
});
|
|
3956
|
+
return 0;
|
|
3957
|
+
}
|
|
3958
|
+
if (!io.isInteractive) {
|
|
3959
|
+
io.err(
|
|
3960
|
+
"refusing to spend channel funds without confirmation in a non-interactive session \u2014 re-run with --yes (or use --json for an estimate)"
|
|
3961
|
+
);
|
|
3962
|
+
return 1;
|
|
3963
|
+
}
|
|
3964
|
+
const proceed = await io.confirm(
|
|
3965
|
+
`Proceed with paid merge (total ${total} base units)? [y/N] `
|
|
3966
|
+
);
|
|
3967
|
+
if (!proceed) {
|
|
3968
|
+
io.err("aborted \u2014 nothing was pushed or published.");
|
|
3969
|
+
return 1;
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
const pushResult = await executePush({
|
|
3973
|
+
plan: pushPlan,
|
|
3974
|
+
publisher: ctx.publisher,
|
|
3975
|
+
remoteState: remote,
|
|
3976
|
+
repoReader: overlay,
|
|
3977
|
+
relayUrls: relays
|
|
3978
|
+
});
|
|
3979
|
+
const result = serializePushResult(pushPlan, pushResult);
|
|
3980
|
+
const localNote = await advanceLocalBranch(
|
|
3981
|
+
repoRoot,
|
|
3982
|
+
reader,
|
|
3983
|
+
refname,
|
|
3984
|
+
applied.newHead,
|
|
3985
|
+
localSha
|
|
3986
|
+
);
|
|
3987
|
+
if (localNote !== void 0) io.err(localNote);
|
|
3988
|
+
let statusEventId;
|
|
3989
|
+
let statusError;
|
|
3990
|
+
try {
|
|
3991
|
+
const receipt = await ctx.publisher.publishEvent(
|
|
3992
|
+
buildStatus(owner, repoId, root.id, STATUS_APPLIED_KIND2, root.pubkey, {
|
|
3993
|
+
commits: applied.commits,
|
|
3994
|
+
revisionEventId: latest.id
|
|
3995
|
+
}),
|
|
3996
|
+
relays
|
|
3997
|
+
);
|
|
3998
|
+
statusEventId = receipt.eventId;
|
|
3999
|
+
} catch (err) {
|
|
4000
|
+
statusError = err instanceof Error ? err.message : String(err);
|
|
4001
|
+
}
|
|
4002
|
+
const retryHint = `rig pr status ${root.id} applied`;
|
|
4003
|
+
if (flags.json) {
|
|
4004
|
+
io.emitJson({
|
|
4005
|
+
command: "pr merge",
|
|
4006
|
+
repoAddr: addr,
|
|
4007
|
+
identity,
|
|
4008
|
+
executed: true,
|
|
4009
|
+
patchId: root.id,
|
|
4010
|
+
revisionId: latest.id,
|
|
4011
|
+
target: refname,
|
|
4012
|
+
mergedShas: applied.commits,
|
|
4013
|
+
result,
|
|
4014
|
+
...statusEventId !== void 0 ? { statusEventId } : {},
|
|
4015
|
+
...statusError !== void 0 ? {
|
|
4016
|
+
partial: true,
|
|
4017
|
+
error: "status_publish_failed",
|
|
4018
|
+
detail: statusError,
|
|
4019
|
+
hint: `the push succeeded but the applied status was not published \u2014 retry with \`${retryHint}\``
|
|
4020
|
+
} : {}
|
|
4021
|
+
});
|
|
4022
|
+
} else {
|
|
4023
|
+
for (const line of renderResult(result)) io.out(line);
|
|
4024
|
+
if (statusEventId !== void 0) {
|
|
4025
|
+
io.out(
|
|
4026
|
+
`Merged ${applied.commits.length} commit(s) onto ${branch}; applied status ${statusEventId}`
|
|
4027
|
+
);
|
|
4028
|
+
}
|
|
4029
|
+
}
|
|
4030
|
+
if (statusError !== void 0) {
|
|
4031
|
+
io.err(
|
|
4032
|
+
`partial success: the push succeeded but publishing the applied status failed: ${statusError}`
|
|
4033
|
+
);
|
|
4034
|
+
io.err(`retry the status with: ${retryHint}`);
|
|
4035
|
+
return 1;
|
|
4036
|
+
}
|
|
4037
|
+
return 0;
|
|
4038
|
+
} catch (err) {
|
|
4039
|
+
return emitCliError(io, flags.json, "pr merge", err);
|
|
4040
|
+
} finally {
|
|
4041
|
+
if (stop) await stop().catch(() => void 0);
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
4044
|
+
async function advanceLocalBranch(repoRoot, reader, refname, newHead, oldSha) {
|
|
4045
|
+
try {
|
|
4046
|
+
const { head } = await reader.listRefs();
|
|
4047
|
+
if (head === refname) {
|
|
4048
|
+
await git(repoRoot, ["merge", "--ff-only", newHead]);
|
|
4049
|
+
} else {
|
|
4050
|
+
await git(repoRoot, [
|
|
4051
|
+
"update-ref",
|
|
4052
|
+
refname,
|
|
4053
|
+
newHead,
|
|
4054
|
+
oldSha ?? "0".repeat(40)
|
|
4055
|
+
]);
|
|
4056
|
+
}
|
|
4057
|
+
return void 0;
|
|
4058
|
+
} catch (err) {
|
|
4059
|
+
return `warning: the push succeeded but local ${refname} could not be moved to ${newHead.slice(0, 7)}: ${gitFailure(err)} \u2014 run \`rig fetch\``;
|
|
4060
|
+
}
|
|
4061
|
+
}
|
|
4062
|
+
|
|
3309
4063
|
// src/cli/events.ts
|
|
3310
4064
|
var defaultReadStdin2 = async () => {
|
|
3311
4065
|
if (process.stdin.isTTY) return "";
|
|
@@ -3372,7 +4126,7 @@ comments published by this version.
|
|
|
3372
4126
|
Options:
|
|
3373
4127
|
--body <text> comment body (Markdown) [required]
|
|
3374
4128
|
${COMMON_FLAGS_USAGE2}`;
|
|
3375
|
-
var PR_CREATE_USAGE = `Usage: rig pr create --title <title> (--range <range> | --patch-file <path>) [options]
|
|
4129
|
+
var PR_CREATE_USAGE = `Usage: rig pr create --title <title> (--range <range> | --patch-file <path>) [--revises <patch-id>] [options]
|
|
3376
4130
|
|
|
3377
4131
|
Publish a patch (kind:1617) whose content is REAL \`git format-patch\` output \u2014
|
|
3378
4132
|
a paid publish; writes are permanent and non-refundable. --range runs
|
|
@@ -3386,6 +4140,12 @@ The PR body (--body/--body-file) is carried in a dedicated \`description\`
|
|
|
3386
4140
|
tag, not in the event content \u2014 the content stays pure format-patch output so
|
|
3387
4141
|
\`rig pr show \u2026 | git am\` keeps working.
|
|
3388
4142
|
|
|
4143
|
+
--revises publishes the patch as a NIP-34 revision of an existing root patch
|
|
4144
|
+
(\`t root-revision\` plus a NIP-10 reply to it), keeping the original thread,
|
|
4145
|
+
comments and status. Readers honor a revision only from the root's author or a
|
|
4146
|
+
declared maintainer; otherwise rig warns and still publishes. A revision is
|
|
4147
|
+
always published through the embedded (standalone) path.
|
|
4148
|
+
|
|
3389
4149
|
Options:
|
|
3390
4150
|
--title <title> patch/PR title (subject tag) [required]
|
|
3391
4151
|
--range <range> revision range for format-patch: <rev>, <rev>..<rev>,
|
|
@@ -3394,6 +4154,7 @@ Options:
|
|
|
3394
4154
|
--body <text> PR description (Markdown; description tag)
|
|
3395
4155
|
--body-file <path> read the PR description from a file
|
|
3396
4156
|
--branch <name> branch name (branch-name tag)
|
|
4157
|
+
--revises <patch-id> publish as a revision of this root patch (64-hex id)
|
|
3397
4158
|
${COMMON_FLAGS_USAGE2}`;
|
|
3398
4159
|
var PR_STATUS_USAGE = `Usage: rig pr status <target-event-id> <open|applied|closed|draft> [options]
|
|
3399
4160
|
|
|
@@ -3420,14 +4181,16 @@ Options:
|
|
|
3420
4181
|
${COMMON_FLAGS_USAGE2}`;
|
|
3421
4182
|
var PR_USAGE = `${PR_CREATE_USAGE}
|
|
3422
4183
|
|
|
4184
|
+
${PR_MERGE_USAGE}
|
|
4185
|
+
|
|
3423
4186
|
${PR_STATUS_USAGE}
|
|
3424
4187
|
|
|
3425
4188
|
${PR_LIST_USAGE}
|
|
3426
4189
|
|
|
3427
4190
|
${PR_SHOW_USAGE}`;
|
|
3428
|
-
var
|
|
4191
|
+
var HEX64_RE4 = /^[0-9a-f]{64}$/;
|
|
3429
4192
|
function assertHex64(value, what) {
|
|
3430
|
-
if (!
|
|
4193
|
+
if (!HEX64_RE4.test(value)) {
|
|
3431
4194
|
throw new Error(
|
|
3432
4195
|
`${what} must be a 64-char lowercase hex id (got ${JSON.stringify(value)})`
|
|
3433
4196
|
);
|
|
@@ -3624,7 +4387,7 @@ async function runIssue(args, deps) {
|
|
|
3624
4387
|
let bodyFile;
|
|
3625
4388
|
let labels;
|
|
3626
4389
|
try {
|
|
3627
|
-
const { values } =
|
|
4390
|
+
const { values } = parseArgs10({
|
|
3628
4391
|
args: rest,
|
|
3629
4392
|
options: {
|
|
3630
4393
|
...COMMON_OPTIONS2,
|
|
@@ -3720,7 +4483,7 @@ async function resolveCommentTarget(deps, targetEventId, relayUrl) {
|
|
|
3720
4483
|
const rootEventId = firstTagValue(target.tags, "E");
|
|
3721
4484
|
const rootKind = Number(firstTagValue(target.tags, "K"));
|
|
3722
4485
|
const rootAuthor = firstTagValue(target.tags, "P");
|
|
3723
|
-
if (rootEventId === void 0 || !
|
|
4486
|
+
if (rootEventId === void 0 || !HEX64_RE4.test(rootEventId) || !Number.isSafeInteger(rootKind) || rootAuthor === void 0 || !HEX64_RE4.test(rootAuthor)) {
|
|
3724
4487
|
throw new Error(
|
|
3725
4488
|
`kind:1111 comment ${targetEventId} carries no usable NIP-22 root scope (E/K/P tags) \u2014 cannot derive the thread root to reply into`
|
|
3726
4489
|
);
|
|
@@ -3744,7 +4507,7 @@ async function runComment(args, deps) {
|
|
|
3744
4507
|
let targetEventId;
|
|
3745
4508
|
let body;
|
|
3746
4509
|
try {
|
|
3747
|
-
const { values, positionals } =
|
|
4510
|
+
const { values, positionals } = parseArgs10({
|
|
3748
4511
|
args,
|
|
3749
4512
|
options: {
|
|
3750
4513
|
...COMMON_OPTIONS2,
|
|
@@ -3825,6 +4588,8 @@ async function runPr(args, deps) {
|
|
|
3825
4588
|
switch (sub) {
|
|
3826
4589
|
case "create":
|
|
3827
4590
|
return runPrCreate(rest, deps);
|
|
4591
|
+
case "merge":
|
|
4592
|
+
return runPrMerge(rest, deps);
|
|
3828
4593
|
case "status":
|
|
3829
4594
|
return runStatusCommand(rest, deps, PR_STATUS_SPEC);
|
|
3830
4595
|
// FREE read subcommands (#278): relay queries only, no payment path.
|
|
@@ -3839,12 +4604,75 @@ async function runPr(args, deps) {
|
|
|
3839
4604
|
return 0;
|
|
3840
4605
|
default:
|
|
3841
4606
|
io.err(
|
|
3842
|
-
sub === void 0 ? "missing subcommand: rig pr <create|status|list|show>" : `unknown rig pr subcommand: ${sub}`
|
|
4607
|
+
sub === void 0 ? "missing subcommand: rig pr <create|merge|status|list|show>" : `unknown rig pr subcommand: ${sub}`
|
|
3843
4608
|
);
|
|
3844
4609
|
io.err(PR_USAGE);
|
|
3845
4610
|
return 2;
|
|
3846
4611
|
}
|
|
3847
4612
|
}
|
|
4613
|
+
async function resolveRevisedRoot(deps, rootId, relayUrl, addr) {
|
|
4614
|
+
if (relayUrl === void 0) {
|
|
4615
|
+
throw new Error(
|
|
4616
|
+
"--revises needs the root patch from a relay; pass --relay <url> or configure a remote"
|
|
4617
|
+
);
|
|
4618
|
+
}
|
|
4619
|
+
const found = await queryRelay(
|
|
4620
|
+
relayUrl,
|
|
4621
|
+
{ ids: [rootId] },
|
|
4622
|
+
TARGET_LOOKUP_TIMEOUT_MS,
|
|
4623
|
+
deps.webSocketFactory ?? defaultWebSocketFactory
|
|
4624
|
+
);
|
|
4625
|
+
const root = found.find((e) => e.id === rootId);
|
|
4626
|
+
if (!root) {
|
|
4627
|
+
throw new Error(
|
|
4628
|
+
`patch ${rootId} not found on ${relayUrl} \u2014 nothing was published or paid`
|
|
4629
|
+
);
|
|
4630
|
+
}
|
|
4631
|
+
if (root.kind !== PATCH_KIND3) {
|
|
4632
|
+
throw new Error(
|
|
4633
|
+
`event ${rootId} has kind ${root.kind}, not a kind:1617 patch \u2014 nothing was published or paid`
|
|
4634
|
+
);
|
|
4635
|
+
}
|
|
4636
|
+
const aTag = `${REPOSITORY_ANNOUNCEMENT_KIND3}:${addr.ownerPubkey}:${addr.repoId}`;
|
|
4637
|
+
if (!root.tags.some((t) => t[0] === "a" && t[1] === aTag)) {
|
|
4638
|
+
throw new Error(
|
|
4639
|
+
`patch ${rootId} belongs to a different repository than ${aTag} \u2014 nothing was published or paid`
|
|
4640
|
+
);
|
|
4641
|
+
}
|
|
4642
|
+
if (isPatchRevision(root)) {
|
|
4643
|
+
throw new Error(
|
|
4644
|
+
`patch ${rootId} is itself a revision \u2014 pass the ROOT patch id to --revises; nothing was published or paid`
|
|
4645
|
+
);
|
|
4646
|
+
}
|
|
4647
|
+
return { pubkey: root.pubkey };
|
|
4648
|
+
}
|
|
4649
|
+
async function warnIfRevisionIgnored(deps, args, rootAuthor) {
|
|
4650
|
+
const { io } = deps;
|
|
4651
|
+
const { identity, addr, relayUrl } = args;
|
|
4652
|
+
const me = identity.pubkey.toLowerCase();
|
|
4653
|
+
if (me === rootAuthor?.toLowerCase()) return;
|
|
4654
|
+
if (me === addr.ownerPubkey.toLowerCase()) return;
|
|
4655
|
+
let authorized;
|
|
4656
|
+
if (relayUrl !== void 0) {
|
|
4657
|
+
try {
|
|
4658
|
+
const remote = await fetchRemoteState({
|
|
4659
|
+
relayUrls: [relayUrl],
|
|
4660
|
+
ownerPubkey: addr.ownerPubkey,
|
|
4661
|
+
repoId: addr.repoId,
|
|
4662
|
+
...deps.webSocketFactory ? { webSocketFactory: deps.webSocketFactory } : {}
|
|
4663
|
+
});
|
|
4664
|
+
authorized = authorizedAuthors(
|
|
4665
|
+
addr.ownerPubkey,
|
|
4666
|
+
remote.announceEvent?.tags ?? []
|
|
4667
|
+
);
|
|
4668
|
+
} catch {
|
|
4669
|
+
}
|
|
4670
|
+
}
|
|
4671
|
+
if (authorized?.has(me)) return;
|
|
4672
|
+
io.err(
|
|
4673
|
+
authorized === void 0 ? "warning: could not read the repo announcement to verify your maintainer status \u2014 unless you are the root patch's author or a declared maintainer, readers will IGNORE this revision." : `warning: you (${identity.pubkey.slice(0, 8)}\u2026) are neither the root patch's author nor a maintainer of 30617:${addr.ownerPubkey.slice(0, 8)}\u2026:${addr.repoId} \u2014 readers will IGNORE this revision. Publishing anyway (the relay is permissionless).`
|
|
4674
|
+
);
|
|
4675
|
+
}
|
|
3848
4676
|
async function runPrCreate(rest, deps) {
|
|
3849
4677
|
const { io } = deps;
|
|
3850
4678
|
let flags;
|
|
@@ -3854,8 +4682,9 @@ async function runPrCreate(rest, deps) {
|
|
|
3854
4682
|
let branch;
|
|
3855
4683
|
let bodyFlag;
|
|
3856
4684
|
let bodyFile;
|
|
4685
|
+
let revises;
|
|
3857
4686
|
try {
|
|
3858
|
-
const { values } =
|
|
4687
|
+
const { values } = parseArgs10({
|
|
3859
4688
|
args: rest,
|
|
3860
4689
|
options: {
|
|
3861
4690
|
...COMMON_OPTIONS2,
|
|
@@ -3864,7 +4693,8 @@ async function runPrCreate(rest, deps) {
|
|
|
3864
4693
|
"patch-file": { type: "string" },
|
|
3865
4694
|
body: { type: "string" },
|
|
3866
4695
|
"body-file": { type: "string" },
|
|
3867
|
-
branch: { type: "string" }
|
|
4696
|
+
branch: { type: "string" },
|
|
4697
|
+
revises: { type: "string" }
|
|
3868
4698
|
},
|
|
3869
4699
|
allowPositionals: false
|
|
3870
4700
|
});
|
|
@@ -3888,6 +4718,11 @@ async function runPrCreate(rest, deps) {
|
|
|
3888
4718
|
branch = values.branch;
|
|
3889
4719
|
bodyFlag = values.body;
|
|
3890
4720
|
bodyFile = values["body-file"];
|
|
4721
|
+
if (values.revises !== void 0) {
|
|
4722
|
+
assertHex64(values.revises, "--revises");
|
|
4723
|
+
revises = values.revises;
|
|
4724
|
+
flags = { ...flags, standalone: true };
|
|
4725
|
+
}
|
|
3891
4726
|
} catch (err) {
|
|
3892
4727
|
io.err(err instanceof Error ? err.message : String(err));
|
|
3893
4728
|
io.err(PR_CREATE_USAGE);
|
|
@@ -3912,12 +4747,16 @@ async function runPrCreate(rest, deps) {
|
|
|
3912
4747
|
}
|
|
3913
4748
|
let builtPatchText;
|
|
3914
4749
|
let builtCommits = [];
|
|
4750
|
+
let rootAuthor;
|
|
3915
4751
|
return runEvent({
|
|
3916
4752
|
command: "pr",
|
|
3917
4753
|
flags,
|
|
3918
4754
|
deps,
|
|
3919
4755
|
actionLabel: `patch ${JSON.stringify(title)}`,
|
|
3920
|
-
buildEvent: async (addr) => {
|
|
4756
|
+
buildEvent: async (addr, relayUrl) => {
|
|
4757
|
+
if (revises !== void 0) {
|
|
4758
|
+
rootAuthor = (await resolveRevisedRoot(deps, revises, relayUrl, addr)).pubkey;
|
|
4759
|
+
}
|
|
3921
4760
|
let patchText;
|
|
3922
4761
|
let commits;
|
|
3923
4762
|
if (range !== void 0) {
|
|
@@ -3952,9 +4791,16 @@ async function runPrCreate(rest, deps) {
|
|
|
3952
4791
|
commits,
|
|
3953
4792
|
branch,
|
|
3954
4793
|
patchText,
|
|
3955
|
-
body
|
|
4794
|
+
body,
|
|
4795
|
+
revises !== void 0 ? {
|
|
4796
|
+
rootEventId: revises,
|
|
4797
|
+
...rootAuthor !== void 0 ? { rootAuthorPubkey: rootAuthor } : {}
|
|
4798
|
+
} : void 0
|
|
3956
4799
|
);
|
|
3957
4800
|
},
|
|
4801
|
+
// A revision from anyone but the root's author or a maintainer is ignored
|
|
4802
|
+
// by readers: warn, but publish (the relay is permissionless).
|
|
4803
|
+
preConfirm: revises === void 0 ? void 0 : (args) => warnIfRevisionIgnored(deps, args, rootAuthor),
|
|
3958
4804
|
sendDaemon: (client, addr) => {
|
|
3959
4805
|
if (builtPatchText === void 0) {
|
|
3960
4806
|
throw new Error("internal: patch text not built before delegation");
|
|
@@ -3972,7 +4818,7 @@ async function runPrCreate(rest, deps) {
|
|
|
3972
4818
|
}
|
|
3973
4819
|
var STATUS_KIND_BY_VALUE = {
|
|
3974
4820
|
open: STATUS_OPEN_KIND2,
|
|
3975
|
-
applied:
|
|
4821
|
+
applied: STATUS_APPLIED_KIND3,
|
|
3976
4822
|
closed: STATUS_CLOSED_KIND2,
|
|
3977
4823
|
draft: STATUS_DRAFT_KIND2
|
|
3978
4824
|
};
|
|
@@ -3999,7 +4845,7 @@ async function runStatusCommand(args, deps, spec, fixedStatus) {
|
|
|
3999
4845
|
let targetEventId;
|
|
4000
4846
|
let status;
|
|
4001
4847
|
try {
|
|
4002
|
-
const { values, positionals } =
|
|
4848
|
+
const { values, positionals } = parseArgs10({
|
|
4003
4849
|
args,
|
|
4004
4850
|
options: COMMON_OPTIONS2,
|
|
4005
4851
|
allowPositionals: true
|
|
@@ -4085,8 +4931,8 @@ async function warnIfNotMaintainer(deps, args) {
|
|
|
4085
4931
|
// src/cli/entry.ts
|
|
4086
4932
|
import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
4087
4933
|
import { homedir as homedir3 } from "os";
|
|
4088
|
-
import { dirname as dirname4, join as
|
|
4089
|
-
import { parseArgs as
|
|
4934
|
+
import { dirname as dirname4, join as join5 } from "path";
|
|
4935
|
+
import { parseArgs as parseArgs11 } from "util";
|
|
4090
4936
|
var ENTRY_USAGE = `Usage: rig entry [<connector-url> | clear] [options]
|
|
4091
4937
|
|
|
4092
4938
|
Name the TOON connector rig pays. A connector describes itself on GET /ilp
|
|
@@ -4110,8 +4956,8 @@ Options:
|
|
|
4110
4956
|
--json machine-readable envelope
|
|
4111
4957
|
-h, --help show this help`;
|
|
4112
4958
|
function configPathFor2(env) {
|
|
4113
|
-
const dir = env["TOON_CLIENT_HOME"] ??
|
|
4114
|
-
return
|
|
4959
|
+
const dir = env["TOON_CLIENT_HOME"] ?? join5(homedir3(), ".toon-client");
|
|
4960
|
+
return join5(dir, "config.json");
|
|
4115
4961
|
}
|
|
4116
4962
|
function readEntryConfig(configPath) {
|
|
4117
4963
|
try {
|
|
@@ -4156,7 +5002,7 @@ async function runEntry(args, deps) {
|
|
|
4156
5002
|
let relayFlag;
|
|
4157
5003
|
let json = false;
|
|
4158
5004
|
try {
|
|
4159
|
-
const parsed =
|
|
5005
|
+
const parsed = parseArgs11({
|
|
4160
5006
|
args,
|
|
4161
5007
|
allowPositionals: true,
|
|
4162
5008
|
options: {
|
|
@@ -4259,278 +5105,52 @@ async function runEntry(args, deps) {
|
|
|
4259
5105
|
delete next.connectorUrl;
|
|
4260
5106
|
delete next.relayUrl;
|
|
4261
5107
|
} else {
|
|
4262
|
-
next.connectorUrl = mutation.connectorUrl;
|
|
4263
|
-
if (mutation.relayUrl !== void 0) next.relayUrl = mutation.relayUrl;
|
|
4264
|
-
}
|
|
4265
|
-
if (env["TOON_CONNECTOR"] || env["TOON_CLIENT_PROXY_URL"]) {
|
|
4266
|
-
warnings.push(
|
|
4267
|
-
"TOON_CONNECTOR / TOON_CLIENT_PROXY_URL is set in this shell and outranks the config file"
|
|
4268
|
-
);
|
|
4269
|
-
}
|
|
4270
|
-
writeEntryConfig(configPath, next);
|
|
4271
|
-
const wrote = {
|
|
4272
|
-
connectorUrl: next.connectorUrl ?? null,
|
|
4273
|
-
relayUrl: next.relayUrl ?? null
|
|
4274
|
-
};
|
|
4275
|
-
const state = effectiveEntry(env, next);
|
|
4276
|
-
if (json) {
|
|
4277
|
-
io.emitJson({
|
|
4278
|
-
command: "entry",
|
|
4279
|
-
...state,
|
|
4280
|
-
wrote,
|
|
4281
|
-
configPath,
|
|
4282
|
-
...warnings.length > 0 ? { warnings } : {}
|
|
4283
|
-
});
|
|
4284
|
-
return 0;
|
|
4285
|
-
}
|
|
4286
|
-
if (mutation.kind === "clear") {
|
|
4287
|
-
io.out(`Cleared the recorded connector in ${configPath}.`);
|
|
4288
|
-
} else {
|
|
4289
|
-
io.out(`Recorded connector ${mutation.connectorUrl} in ${configPath}.`);
|
|
4290
|
-
if (mutation.relayUrl !== void 0)
|
|
4291
|
-
io.out(`Recorded relay ${mutation.relayUrl}.`);
|
|
4292
|
-
}
|
|
4293
|
-
for (const w of warnings) io.out(`warning: ${w}`);
|
|
4294
|
-
io.out(
|
|
4295
|
-
`Effective connector: ${state.connectorUrl ?? "(none)"} [${label(state.connectorSource)}]`
|
|
4296
|
-
);
|
|
4297
|
-
return 0;
|
|
4298
|
-
} catch (err) {
|
|
4299
|
-
return emitCliError(io, json, "entry", err);
|
|
4300
|
-
}
|
|
4301
|
-
}
|
|
4302
|
-
|
|
4303
|
-
// src/cli/fetch.ts
|
|
4304
|
-
import { parseArgs as parseArgs10 } from "util";
|
|
4305
|
-
var FETCH_USAGE = `Usage: rig fetch [remote] [options]
|
|
4306
|
-
|
|
4307
|
-
Fetch from a TOON remote \u2014 FREE (relay reads + Arweave gateway downloads; no
|
|
4308
|
-
payments, no identity needed). Reads the kind:30618 repository state from the
|
|
4309
|
-
remote's relay, downloads only the git objects this repository is missing
|
|
4310
|
-
(SHA-1 verified), and updates the remote-tracking refs
|
|
4311
|
-
(refs/remotes/<remote>/*; tags land at refs/tags/*), reporting what moved.
|
|
4312
|
-
|
|
4313
|
-
No merge happens: integrate with the git passthrough, e.g.
|
|
4314
|
-
\`rig merge origin/main\`. \`rig fetch\` is the TOON transport and shadows
|
|
4315
|
-
\`git fetch\` \u2014 plain-git fetches remain available via \`git fetch\` directly.
|
|
4316
|
-
|
|
4317
|
-
The repo address (toon.repoid/toon.owner) comes from the config \`rig init\`
|
|
4318
|
-
or \`rig clone\` writes; --repo-id/--owner override.
|
|
4319
|
-
|
|
4320
|
-
Options:
|
|
4321
|
-
--repo-id <id> repository id / NIP-34 d-tag (default: git config)
|
|
4322
|
-
--owner <pubkey> repository owner (npub or 64-char hex; default: git config)
|
|
4323
|
-
--relay <url> ad-hoc relay override \u2014 bypasses the remote's URL
|
|
4324
|
-
--gateway <url> gateway to read objects through FIRST, on its raw-bytes
|
|
4325
|
-
route <url>/raw/<txId>, ahead of the public gateway
|
|
4326
|
-
list \u2014 name a private, local or air-gapped gateway to
|
|
4327
|
-
fetch objects the public ones do not carry
|
|
4328
|
-
(env RIG_ARWEAVE_GATEWAY also sets it)
|
|
4329
|
-
--concurrency <n> parallel gateway downloads (default 8)
|
|
4330
|
-
--json machine-readable result envelope
|
|
4331
|
-
-h, --help show this help`;
|
|
4332
|
-
var WS_URL_RE4 = /^wss?:\/\//i;
|
|
4333
|
-
function localRefFor(refname, remote) {
|
|
4334
|
-
if (refname.startsWith("refs/heads/")) {
|
|
4335
|
-
return `refs/remotes/${remote}/${refname.slice("refs/heads/".length)}`;
|
|
4336
|
-
}
|
|
4337
|
-
if (refname.startsWith("refs/tags/")) return refname;
|
|
4338
|
-
return null;
|
|
4339
|
-
}
|
|
4340
|
-
async function resolveLocalRef(repoRoot, refname) {
|
|
4341
|
-
try {
|
|
4342
|
-
const out = await runGit(repoRoot, [
|
|
4343
|
-
"rev-parse",
|
|
4344
|
-
"--verify",
|
|
4345
|
-
"--quiet",
|
|
4346
|
-
refname
|
|
4347
|
-
]);
|
|
4348
|
-
const sha = out.trim();
|
|
4349
|
-
return /^[0-9a-f]{40}$/.test(sha) ? sha : null;
|
|
4350
|
-
} catch {
|
|
4351
|
-
return null;
|
|
4352
|
-
}
|
|
4353
|
-
}
|
|
4354
|
-
async function runFetch(args, deps) {
|
|
4355
|
-
const { io } = deps;
|
|
4356
|
-
let json = false;
|
|
4357
|
-
let remote = "origin";
|
|
4358
|
-
let relayFlag;
|
|
4359
|
-
let repoIdFlag;
|
|
4360
|
-
let ownerFlag;
|
|
4361
|
-
let gatewayFlag;
|
|
4362
|
-
let concurrency;
|
|
4363
|
-
try {
|
|
4364
|
-
const { values, positionals } = parseArgs10({
|
|
4365
|
-
args,
|
|
4366
|
-
options: {
|
|
4367
|
-
json: { type: "boolean", default: false },
|
|
4368
|
-
relay: { type: "string" },
|
|
4369
|
-
"repo-id": { type: "string" },
|
|
4370
|
-
owner: { type: "string" },
|
|
4371
|
-
gateway: { type: "string" },
|
|
4372
|
-
concurrency: { type: "string" },
|
|
4373
|
-
help: { type: "boolean", short: "h", default: false }
|
|
4374
|
-
},
|
|
4375
|
-
allowPositionals: true
|
|
4376
|
-
});
|
|
4377
|
-
if (values.help) {
|
|
4378
|
-
io.out(FETCH_USAGE);
|
|
4379
|
-
return 0;
|
|
4380
|
-
}
|
|
4381
|
-
json = values.json === true;
|
|
4382
|
-
if (positionals.length > 1) {
|
|
4383
|
-
throw new Error(
|
|
4384
|
-
`expected at most one [remote] argument, got ${positionals.length}`
|
|
4385
|
-
);
|
|
4386
|
-
}
|
|
4387
|
-
if (positionals[0] !== void 0) remote = positionals[0];
|
|
4388
|
-
relayFlag = values.relay;
|
|
4389
|
-
repoIdFlag = values["repo-id"];
|
|
4390
|
-
ownerFlag = values.owner !== void 0 ? ownerToHex(values.owner) : void 0;
|
|
4391
|
-
gatewayFlag = values.gateway;
|
|
4392
|
-
if (values.concurrency !== void 0) {
|
|
4393
|
-
concurrency = Number.parseInt(values.concurrency, 10);
|
|
4394
|
-
if (!Number.isSafeInteger(concurrency) || concurrency < 1) {
|
|
4395
|
-
throw new Error("--concurrency must be a positive integer");
|
|
4396
|
-
}
|
|
4397
|
-
}
|
|
4398
|
-
} catch (err) {
|
|
4399
|
-
io.err(err instanceof Error ? err.message : String(err));
|
|
4400
|
-
io.err(FETCH_USAGE);
|
|
4401
|
-
return 2;
|
|
4402
|
-
}
|
|
4403
|
-
try {
|
|
4404
|
-
const repoRoot = await resolveRepoRoot(deps.cwd);
|
|
4405
|
-
const toonConfig = await readToonConfig(repoRoot);
|
|
4406
|
-
const repoId = repoIdFlag ?? toonConfig.repoId;
|
|
4407
|
-
if (!repoId) throw new UnconfiguredRepoAddressError("repository id");
|
|
4408
|
-
const owner = ownerFlag ?? toonConfig.owner;
|
|
4409
|
-
if (!owner) throw new UnconfiguredRepoAddressError("repository owner");
|
|
4410
|
-
let relayUrl;
|
|
4411
|
-
if (relayFlag !== void 0) {
|
|
4412
|
-
relayUrl = relayFlag;
|
|
4413
|
-
} else {
|
|
4414
|
-
const urls = await getGitRemoteUrls(repoRoot, remote);
|
|
4415
|
-
if (urls.length === 0) throw new UnknownRemoteError(remote);
|
|
4416
|
-
if (urls.length > 1) throw new MultiUrlRemoteError(remote, urls);
|
|
4417
|
-
relayUrl = urls[0];
|
|
4418
|
-
}
|
|
4419
|
-
if (!WS_URL_RE4.test(relayUrl)) {
|
|
4420
|
-
throw new InvalidRelayUrlError(
|
|
4421
|
-
relayUrl,
|
|
4422
|
-
`remote ${JSON.stringify(remote)}`
|
|
4423
|
-
);
|
|
4424
|
-
}
|
|
4425
|
-
const remoteState = await fetchRemoteState({
|
|
4426
|
-
relayUrls: [relayUrl],
|
|
4427
|
-
ownerPubkey: owner,
|
|
4428
|
-
repoId,
|
|
4429
|
-
...deps.webSocketFactory ? { webSocketFactory: deps.webSocketFactory } : {},
|
|
4430
|
-
// The GraphQL fallback for SHAs the map does not cover asks the SAME
|
|
4431
|
-
// permaweb the objects are read from (#183).
|
|
4432
|
-
resolveSha: deps.resolveSha ?? shaResolverFor(gatewayFlag, deps.env)
|
|
4433
|
-
});
|
|
4434
|
-
const planned = [];
|
|
4435
|
-
for (const [refname, sha] of remoteState.refs) {
|
|
4436
|
-
if (!isSafeRefname(refname)) {
|
|
4437
|
-
io.err(
|
|
4438
|
-
`warning: skipping unsafe ref name from relay: ${JSON.stringify(refname)}`
|
|
4439
|
-
);
|
|
4440
|
-
continue;
|
|
4441
|
-
}
|
|
4442
|
-
const localRef = localRefFor(refname, remote);
|
|
4443
|
-
if (localRef === null) {
|
|
4444
|
-
io.err(
|
|
4445
|
-
`warning: skipping ref outside refs/heads and refs/tags: ${refname}`
|
|
4446
|
-
);
|
|
4447
|
-
continue;
|
|
4448
|
-
}
|
|
4449
|
-
planned.push({ refname, localRef, newSha: sha });
|
|
4450
|
-
}
|
|
4451
|
-
const reader = new GitRepoReader(repoRoot);
|
|
4452
|
-
const tips = [...new Set(planned.map((p) => p.newSha))];
|
|
4453
|
-
const presentLocally = new Set(await reader.listAllObjectShas());
|
|
4454
|
-
const collected = await collectRepoObjects({
|
|
4455
|
-
tips,
|
|
4456
|
-
shaToTxId: remoteState.shaToTxId,
|
|
4457
|
-
resolveMissing: (shas) => remoteState.resolveMissing(shas),
|
|
4458
|
-
presentLocally,
|
|
4459
|
-
// `--gateway` / RIG_ARWEAVE_GATEWAY first, public list behind it (#176).
|
|
4460
|
-
gateways: readGateways(gatewayFlag, deps.env),
|
|
4461
|
-
...deps.fetchFn ? { fetchFn: deps.fetchFn } : {},
|
|
4462
|
-
...concurrency !== void 0 ? { concurrency } : {}
|
|
4463
|
-
});
|
|
4464
|
-
if (collected.missing.length > 0) {
|
|
4465
|
-
throw new MissingRemoteObjectsError(
|
|
4466
|
-
collected.missing,
|
|
4467
|
-
`cannot fetch from ${relayUrl}`
|
|
4468
|
-
);
|
|
4469
|
-
}
|
|
4470
|
-
const written = await writeGitObjects(repoRoot, collected.objects.values());
|
|
4471
|
-
const updates = [];
|
|
4472
|
-
for (const { refname, localRef, newSha } of planned) {
|
|
4473
|
-
const oldSha = await resolveLocalRef(repoRoot, localRef);
|
|
4474
|
-
if (oldSha === newSha) {
|
|
4475
|
-
updates.push({ refname, localRef, oldSha, newSha, kind: "up-to-date" });
|
|
4476
|
-
continue;
|
|
4477
|
-
}
|
|
4478
|
-
let kind;
|
|
4479
|
-
if (oldSha === null) {
|
|
4480
|
-
kind = "new";
|
|
4481
|
-
} else {
|
|
4482
|
-
kind = await reader.isAncestor(oldSha, newSha) ? "fast-forward" : "forced";
|
|
4483
|
-
}
|
|
4484
|
-
await updateRef(repoRoot, localRef, newSha);
|
|
4485
|
-
updates.push({ refname, localRef, oldSha, newSha, kind });
|
|
5108
|
+
next.connectorUrl = mutation.connectorUrl;
|
|
5109
|
+
if (mutation.relayUrl !== void 0) next.relayUrl = mutation.relayUrl;
|
|
4486
5110
|
}
|
|
4487
|
-
|
|
5111
|
+
if (env["TOON_CONNECTOR"] || env["TOON_CLIENT_PROXY_URL"]) {
|
|
5112
|
+
warnings.push(
|
|
5113
|
+
"TOON_CONNECTOR / TOON_CLIENT_PROXY_URL is set in this shell and outranks the config file"
|
|
5114
|
+
);
|
|
5115
|
+
}
|
|
5116
|
+
writeEntryConfig(configPath, next);
|
|
5117
|
+
const wrote = {
|
|
5118
|
+
connectorUrl: next.connectorUrl ?? null,
|
|
5119
|
+
relayUrl: next.relayUrl ?? null
|
|
5120
|
+
};
|
|
5121
|
+
const state = effectiveEntry(env, next);
|
|
4488
5122
|
if (json) {
|
|
4489
5123
|
io.emitJson({
|
|
4490
|
-
command: "
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
updates,
|
|
4496
|
-
executed: true
|
|
5124
|
+
command: "entry",
|
|
5125
|
+
...state,
|
|
5126
|
+
wrote,
|
|
5127
|
+
configPath,
|
|
5128
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
4497
5129
|
});
|
|
4498
5130
|
return 0;
|
|
4499
5131
|
}
|
|
4500
|
-
if (
|
|
4501
|
-
io.out(
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
const short = (refname) => refname.replace(/^refs\/heads\//, "").replace(/^refs\/tags\//, "").replace(/^refs\/remotes\//, "");
|
|
4507
|
-
const src = short(u.refname);
|
|
4508
|
-
const dst = short(u.localRef);
|
|
4509
|
-
if (u.kind === "new") {
|
|
4510
|
-
const label = u.refname.startsWith("refs/tags/") ? "[new tag]" : "[new branch]";
|
|
4511
|
-
io.out(` * ${label.padEnd(17)} ${src.padEnd(14)} -> ${dst}`);
|
|
4512
|
-
} else if (u.kind === "forced") {
|
|
4513
|
-
io.out(
|
|
4514
|
-
` + ${u.oldSha.slice(0, 7)}...${u.newSha.slice(0, 7)} ${src.padEnd(14)} -> ${dst} (forced update)`
|
|
4515
|
-
);
|
|
4516
|
-
} else {
|
|
4517
|
-
io.out(
|
|
4518
|
-
` ${u.oldSha.slice(0, 7)}..${u.newSha.slice(0, 7)} ${src.padEnd(14)} -> ${dst}`
|
|
4519
|
-
);
|
|
4520
|
-
}
|
|
5132
|
+
if (mutation.kind === "clear") {
|
|
5133
|
+
io.out(`Cleared the recorded connector in ${configPath}.`);
|
|
5134
|
+
} else {
|
|
5135
|
+
io.out(`Recorded connector ${mutation.connectorUrl} in ${configPath}.`);
|
|
5136
|
+
if (mutation.relayUrl !== void 0)
|
|
5137
|
+
io.out(`Recorded relay ${mutation.relayUrl}.`);
|
|
4521
5138
|
}
|
|
4522
|
-
io.out(`
|
|
5139
|
+
for (const w of warnings) io.out(`warning: ${w}`);
|
|
5140
|
+
io.out(
|
|
5141
|
+
`Effective connector: ${state.connectorUrl ?? "(none)"} [${label(state.connectorSource)}]`
|
|
5142
|
+
);
|
|
4523
5143
|
return 0;
|
|
4524
5144
|
} catch (err) {
|
|
4525
|
-
return emitCliError(io, json, "
|
|
5145
|
+
return emitCliError(io, json, "entry", err);
|
|
4526
5146
|
}
|
|
4527
5147
|
}
|
|
4528
5148
|
|
|
4529
5149
|
// src/cli/fund.ts
|
|
4530
5150
|
import { readFileSync as readFileSync4 } from "fs";
|
|
4531
5151
|
import { homedir as homedir4 } from "os";
|
|
4532
|
-
import { join as
|
|
4533
|
-
import { parseArgs as
|
|
5152
|
+
import { join as join6 } from "path";
|
|
5153
|
+
import { parseArgs as parseArgs12 } from "util";
|
|
4534
5154
|
var DEVNET_FAUCET_URL = "https://faucet.devnet.toonprotocol.dev";
|
|
4535
5155
|
var CHAINS = ["evm", "solana"];
|
|
4536
5156
|
var CHAIN_ALIASES = {
|
|
@@ -4668,8 +5288,8 @@ function noFaucetGuidance(network, devnetOrigin) {
|
|
|
4668
5288
|
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}`;
|
|
4669
5289
|
}
|
|
4670
5290
|
function readFundConfig(env) {
|
|
4671
|
-
const dir = env["TOON_CLIENT_HOME"] ??
|
|
4672
|
-
const configPath =
|
|
5291
|
+
const dir = env["TOON_CLIENT_HOME"] ?? join6(homedir4(), ".toon-client");
|
|
5292
|
+
const configPath = join6(dir, "config.json");
|
|
4673
5293
|
try {
|
|
4674
5294
|
return {
|
|
4675
5295
|
file: JSON.parse(readFileSync4(configPath, "utf8")),
|
|
@@ -4690,7 +5310,7 @@ async function runFund(args, deps) {
|
|
|
4690
5310
|
let addressFlag;
|
|
4691
5311
|
let json = false;
|
|
4692
5312
|
try {
|
|
4693
|
-
const { values, positionals } =
|
|
5313
|
+
const { values, positionals } = parseArgs12({
|
|
4694
5314
|
args,
|
|
4695
5315
|
allowPositionals: true,
|
|
4696
5316
|
options: {
|
|
@@ -4916,7 +5536,7 @@ var runGitPassthrough = (argv, options = {}) => {
|
|
|
4916
5536
|
import { existsSync, mkdirSync as mkdirSync4 } from "fs";
|
|
4917
5537
|
import { dirname as dirname5 } from "path";
|
|
4918
5538
|
import { createInterface } from "readline/promises";
|
|
4919
|
-
import { parseArgs as
|
|
5539
|
+
import { parseArgs as parseArgs13 } from "util";
|
|
4920
5540
|
async function loadKeyOps() {
|
|
4921
5541
|
const { generateKeystore, importKeystore } = await import("@toon-protocol/client");
|
|
4922
5542
|
return { generateKeystore, importKeystore, deriveNostrKeyFromMnemonic };
|
|
@@ -5088,7 +5708,7 @@ async function runIdentity(args, deps) {
|
|
|
5088
5708
|
}
|
|
5089
5709
|
}
|
|
5090
5710
|
function parseMintArgs(args, verb) {
|
|
5091
|
-
const { values, positionals } =
|
|
5711
|
+
const { values, positionals } = parseArgs13({
|
|
5092
5712
|
args,
|
|
5093
5713
|
options: {
|
|
5094
5714
|
force: { type: "boolean", default: false },
|
|
@@ -5193,7 +5813,7 @@ async function runIdentityShow(args, deps) {
|
|
|
5193
5813
|
let json = false;
|
|
5194
5814
|
let help = false;
|
|
5195
5815
|
try {
|
|
5196
|
-
const parsed =
|
|
5816
|
+
const parsed = parseArgs13({
|
|
5197
5817
|
args,
|
|
5198
5818
|
options: {
|
|
5199
5819
|
json: { type: "boolean", default: false },
|
|
@@ -5300,9 +5920,569 @@ async function defaultReadSecretLine(prompt, isInteractive) {
|
|
|
5300
5920
|
}
|
|
5301
5921
|
}
|
|
5302
5922
|
|
|
5923
|
+
// src/cli/inbox.ts
|
|
5924
|
+
import { parseArgs as parseArgs14 } from "util";
|
|
5925
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
5926
|
+
import { join as join7 } from "path";
|
|
5927
|
+
import {
|
|
5928
|
+
ISSUE_KIND as ISSUE_KIND2,
|
|
5929
|
+
PATCH_KIND as PATCH_KIND4,
|
|
5930
|
+
REPOSITORY_ANNOUNCEMENT_KIND as REPOSITORY_ANNOUNCEMENT_KIND4,
|
|
5931
|
+
STATUS_APPLIED_KIND as STATUS_APPLIED_KIND4,
|
|
5932
|
+
STATUS_CLOSED_KIND as STATUS_CLOSED_KIND3,
|
|
5933
|
+
STATUS_DRAFT_KIND as STATUS_DRAFT_KIND3,
|
|
5934
|
+
STATUS_OPEN_KIND as STATUS_OPEN_KIND3
|
|
5935
|
+
} from "@toon-protocol/core/nip34";
|
|
5936
|
+
|
|
5937
|
+
// src/cli/relay-follow.ts
|
|
5938
|
+
var WS_OPEN = 1;
|
|
5939
|
+
var CONNECT_TIMEOUT_MS = 1e4;
|
|
5940
|
+
function defaultBackoffMs(attempt) {
|
|
5941
|
+
return Math.min(3e4, 1e3 * 2 ** Math.max(0, attempt - 1));
|
|
5942
|
+
}
|
|
5943
|
+
function connectOnce(opts, onSubscribed) {
|
|
5944
|
+
return new Promise((resolve2) => {
|
|
5945
|
+
let ws;
|
|
5946
|
+
let done = false;
|
|
5947
|
+
let subscribed = false;
|
|
5948
|
+
const subIds = [];
|
|
5949
|
+
let connectTimer;
|
|
5950
|
+
const finish = (reason) => {
|
|
5951
|
+
if (done) return;
|
|
5952
|
+
done = true;
|
|
5953
|
+
clearTimeout(connectTimer);
|
|
5954
|
+
opts.signal.removeEventListener("abort", onAbort);
|
|
5955
|
+
try {
|
|
5956
|
+
if (ws.readyState === WS_OPEN) {
|
|
5957
|
+
for (const id of subIds) ws.send(JSON.stringify(["CLOSE", id]));
|
|
5958
|
+
}
|
|
5959
|
+
ws.close();
|
|
5960
|
+
} catch {
|
|
5961
|
+
}
|
|
5962
|
+
resolve2(reason);
|
|
5963
|
+
};
|
|
5964
|
+
const onAbort = () => finish(null);
|
|
5965
|
+
try {
|
|
5966
|
+
ws = opts.webSocketFactory(opts.url);
|
|
5967
|
+
} catch (err) {
|
|
5968
|
+
resolve2(`failed to connect to ${opts.url}: ${String(err)}`);
|
|
5969
|
+
return;
|
|
5970
|
+
}
|
|
5971
|
+
opts.signal.addEventListener("abort", onAbort);
|
|
5972
|
+
connectTimer = setTimeout(
|
|
5973
|
+
() => finish(`timed out connecting to ${opts.url}`),
|
|
5974
|
+
CONNECT_TIMEOUT_MS
|
|
5975
|
+
);
|
|
5976
|
+
ws.addEventListener("open", () => {
|
|
5977
|
+
clearTimeout(connectTimer);
|
|
5978
|
+
opts.filters().forEach((filter, i) => {
|
|
5979
|
+
const subId = `follow-${Date.now()}-${i}-${Math.random().toString(36).slice(2, 8)}`;
|
|
5980
|
+
subIds.push(subId);
|
|
5981
|
+
ws.send(JSON.stringify(["REQ", subId, filter]));
|
|
5982
|
+
});
|
|
5983
|
+
});
|
|
5984
|
+
ws.addEventListener("message", (msgEvent) => {
|
|
5985
|
+
try {
|
|
5986
|
+
const msg = JSON.parse(String(msgEvent.data));
|
|
5987
|
+
if (msg[0] === "EVENT" && msg[2] !== void 0) {
|
|
5988
|
+
const event = decodeEventPayload(msg[2]);
|
|
5989
|
+
if (event) opts.onEvent(event, opts.url);
|
|
5990
|
+
} else if (msg[0] === "EOSE" && !subscribed && subIds.includes(msg[1])) {
|
|
5991
|
+
subscribed = true;
|
|
5992
|
+
onSubscribed();
|
|
5993
|
+
}
|
|
5994
|
+
} catch {
|
|
5995
|
+
}
|
|
5996
|
+
});
|
|
5997
|
+
ws.addEventListener("error", (event) => {
|
|
5998
|
+
const detail = typeof event === "object" && event !== null && "message" in event ? String(event.message) : "unknown";
|
|
5999
|
+
finish(`websocket error on ${opts.url}: ${detail}`);
|
|
6000
|
+
});
|
|
6001
|
+
ws.addEventListener("close", () => {
|
|
6002
|
+
finish(`connection to ${opts.url} closed`);
|
|
6003
|
+
});
|
|
6004
|
+
});
|
|
6005
|
+
}
|
|
6006
|
+
async function followRelay(opts) {
|
|
6007
|
+
let attempt = 0;
|
|
6008
|
+
while (!opts.signal.aborted) {
|
|
6009
|
+
const reason = await connectOnce(opts, () => {
|
|
6010
|
+
attempt = 0;
|
|
6011
|
+
});
|
|
6012
|
+
if (opts.signal.aborted) return;
|
|
6013
|
+
opts.onError(reason ?? `connection to ${opts.url} ended`);
|
|
6014
|
+
attempt += 1;
|
|
6015
|
+
await new Promise((resolve2) => {
|
|
6016
|
+
const done = () => {
|
|
6017
|
+
clearTimeout(timer);
|
|
6018
|
+
opts.signal.removeEventListener("abort", done);
|
|
6019
|
+
resolve2();
|
|
6020
|
+
};
|
|
6021
|
+
const timer = setTimeout(done, opts.backoffMs(attempt));
|
|
6022
|
+
opts.signal.addEventListener("abort", done);
|
|
6023
|
+
});
|
|
6024
|
+
}
|
|
6025
|
+
}
|
|
6026
|
+
|
|
6027
|
+
// src/cli/inbox.ts
|
|
6028
|
+
var INBOX_USAGE = `Usage: rig inbox [options]
|
|
6029
|
+
|
|
6030
|
+
Print everything new since you last checked (free \u2014 relay reads only):
|
|
6031
|
+
patches and revisions, issues, comments, status changes and CI results for
|
|
6032
|
+
repos you own or maintain, plus any event that tags your pubkey.
|
|
6033
|
+
|
|
6034
|
+
Options:
|
|
6035
|
+
--since <time> read from a unix time, an ISO date, or a relative
|
|
6036
|
+
age (30m, 2h, 7d); does not move the cursor
|
|
6037
|
+
--all read everything, ignoring the cursor; does not move it
|
|
6038
|
+
--pubkey <pubkey> act as this pubkey (npub or hex) instead of the
|
|
6039
|
+
resolved identity
|
|
6040
|
+
--remote <name> read via this configured git remote (default: origin)
|
|
6041
|
+
--relay <url> ad-hoc relay override; repeatable \u2014 reads are merged
|
|
6042
|
+
--json machine-readable array, one object per event
|
|
6043
|
+
--follow emit the backlog, then stay connected and stream new
|
|
6044
|
+
events as JSON lines (one object per line) until
|
|
6045
|
+
interrupted; reconnects when a relay drops
|
|
6046
|
+
-h, --help show this help`;
|
|
6047
|
+
var STATUS_KINDS2 = [
|
|
6048
|
+
STATUS_OPEN_KIND3,
|
|
6049
|
+
STATUS_APPLIED_KIND4,
|
|
6050
|
+
STATUS_CLOSED_KIND3,
|
|
6051
|
+
STATUS_DRAFT_KIND3
|
|
6052
|
+
];
|
|
6053
|
+
var CI_KINDS = [
|
|
6054
|
+
CI_SERVICE_REQUEST_KIND,
|
|
6055
|
+
CI_SERVICE_STOP_KIND,
|
|
6056
|
+
CI_WORKFLOW_RESULT_KIND,
|
|
6057
|
+
CI_WORKFLOW_PROGRESS_KIND,
|
|
6058
|
+
CI_JOB_RESULT_KIND
|
|
6059
|
+
];
|
|
6060
|
+
var INBOX_KINDS = [
|
|
6061
|
+
PATCH_KIND4,
|
|
6062
|
+
ISSUE_KIND2,
|
|
6063
|
+
COMMENT_KIND,
|
|
6064
|
+
LEGACY_COMMENT_KIND,
|
|
6065
|
+
...STATUS_KINDS2,
|
|
6066
|
+
...CI_KINDS
|
|
6067
|
+
];
|
|
6068
|
+
var HEX64_RE5 = /^[0-9a-f]{64}$/;
|
|
6069
|
+
var INBOX_CURSOR_FILENAME = "inbox-cursors.json";
|
|
6070
|
+
function cursorPath(env) {
|
|
6071
|
+
return join7(clientStateDir(env), INBOX_CURSOR_FILENAME);
|
|
6072
|
+
}
|
|
6073
|
+
function readCursors(env) {
|
|
6074
|
+
try {
|
|
6075
|
+
const parsed = JSON.parse(readFileSync5(cursorPath(env), "utf8"));
|
|
6076
|
+
if (parsed && typeof parsed === "object") {
|
|
6077
|
+
return parsed;
|
|
6078
|
+
}
|
|
6079
|
+
} catch {
|
|
6080
|
+
}
|
|
6081
|
+
return {};
|
|
6082
|
+
}
|
|
6083
|
+
function writeCursor(env, pubkey, cursor) {
|
|
6084
|
+
const cursors = readCursors(env);
|
|
6085
|
+
cursors[pubkey] = cursor;
|
|
6086
|
+
mkdirSync5(clientStateDir(env), { recursive: true });
|
|
6087
|
+
writeFileSync4(cursorPath(env), JSON.stringify(cursors, null, 2) + "\n");
|
|
6088
|
+
}
|
|
6089
|
+
function parseSince(value, nowSec) {
|
|
6090
|
+
const trimmed = value.trim();
|
|
6091
|
+
if (/^\d+$/.test(trimmed)) return Number(trimmed);
|
|
6092
|
+
const rel = /^(\d+)([smhdw])$/.exec(trimmed);
|
|
6093
|
+
if (rel) {
|
|
6094
|
+
const unit = { s: 1, m: 60, h: 3600, d: 86400, w: 604800 }[rel[2]];
|
|
6095
|
+
return nowSec - Number(rel[1]) * unit;
|
|
6096
|
+
}
|
|
6097
|
+
const ms = Date.parse(trimmed);
|
|
6098
|
+
if (Number.isNaN(ms)) {
|
|
6099
|
+
throw new Error(
|
|
6100
|
+
`--since must be a unix time, an ISO date, or an age like 2h (got ${JSON.stringify(value)})`
|
|
6101
|
+
);
|
|
6102
|
+
}
|
|
6103
|
+
return Math.floor(ms / 1e3);
|
|
6104
|
+
}
|
|
6105
|
+
var tagValue2 = firstTagValue;
|
|
6106
|
+
function hasTopic(event, topic) {
|
|
6107
|
+
return event.tags.some((t) => t[0] === "t" && t[1] === topic);
|
|
6108
|
+
}
|
|
6109
|
+
function typeOf(event) {
|
|
6110
|
+
if (event.kind === PATCH_KIND4) {
|
|
6111
|
+
if (hasTopic(event, "root-revision")) return "revision";
|
|
6112
|
+
if (hasTopic(event, "root") || tagValue2(event.tags, "e") === void 0) {
|
|
6113
|
+
return "patch";
|
|
6114
|
+
}
|
|
6115
|
+
return null;
|
|
6116
|
+
}
|
|
6117
|
+
if (event.kind === ISSUE_KIND2) return "issue";
|
|
6118
|
+
if (event.kind === COMMENT_KIND || event.kind === LEGACY_COMMENT_KIND) {
|
|
6119
|
+
return "comment";
|
|
6120
|
+
}
|
|
6121
|
+
if (STATUS_KINDS2.includes(event.kind)) return "status";
|
|
6122
|
+
if (CI_KINDS.includes(event.kind)) return "ci";
|
|
6123
|
+
return null;
|
|
6124
|
+
}
|
|
6125
|
+
function targetOf(event, type) {
|
|
6126
|
+
const root = tagValue2(event.tags, "E");
|
|
6127
|
+
const parent = tagValue2(event.tags, "e");
|
|
6128
|
+
switch (type) {
|
|
6129
|
+
case "patch":
|
|
6130
|
+
case "issue":
|
|
6131
|
+
return event.id;
|
|
6132
|
+
case "revision":
|
|
6133
|
+
return parent ?? event.id;
|
|
6134
|
+
case "comment":
|
|
6135
|
+
return root ?? parent ?? event.id;
|
|
6136
|
+
case "status":
|
|
6137
|
+
return parent ?? event.id;
|
|
6138
|
+
case "ci":
|
|
6139
|
+
return root ?? tagValue2(event.tags, "c") ?? event.id;
|
|
6140
|
+
}
|
|
6141
|
+
}
|
|
6142
|
+
function toInboxEntry(event) {
|
|
6143
|
+
const type = typeOf(event);
|
|
6144
|
+
if (type === null) return null;
|
|
6145
|
+
const repo = event.tags.find(
|
|
6146
|
+
(t) => t[0] === "a" && t[1]?.startsWith(`${REPOSITORY_ANNOUNCEMENT_KIND4}:`)
|
|
6147
|
+
)?.[1];
|
|
6148
|
+
return {
|
|
6149
|
+
id: event.id,
|
|
6150
|
+
kind: event.kind,
|
|
6151
|
+
type,
|
|
6152
|
+
repo: repo ?? null,
|
|
6153
|
+
target: targetOf(event, type),
|
|
6154
|
+
author: event.pubkey,
|
|
6155
|
+
created_at: event.created_at
|
|
6156
|
+
};
|
|
6157
|
+
}
|
|
6158
|
+
function newest(events) {
|
|
6159
|
+
let winner = null;
|
|
6160
|
+
for (const e of events) {
|
|
6161
|
+
if (winner === null || e.created_at > winner.created_at || e.created_at === winner.created_at && e.id < winner.id) {
|
|
6162
|
+
winner = e;
|
|
6163
|
+
}
|
|
6164
|
+
}
|
|
6165
|
+
return winner;
|
|
6166
|
+
}
|
|
6167
|
+
async function ownerAnnouncement(relays, owner, repoId, ws) {
|
|
6168
|
+
const found = await queryAll(
|
|
6169
|
+
relays,
|
|
6170
|
+
[
|
|
6171
|
+
{
|
|
6172
|
+
kinds: [REPOSITORY_ANNOUNCEMENT_KIND4],
|
|
6173
|
+
authors: [owner],
|
|
6174
|
+
"#d": [repoId]
|
|
6175
|
+
}
|
|
6176
|
+
],
|
|
6177
|
+
ws
|
|
6178
|
+
).catch(() => /* @__PURE__ */ new Map());
|
|
6179
|
+
return newest(
|
|
6180
|
+
[...found.values()].filter(
|
|
6181
|
+
(e) => e.kind === REPOSITORY_ANNOUNCEMENT_KIND4 && e.pubkey.toLowerCase() === owner && tagValue2(e.tags, "d") === repoId
|
|
6182
|
+
)
|
|
6183
|
+
);
|
|
6184
|
+
}
|
|
6185
|
+
function relaysTag(tags) {
|
|
6186
|
+
return tags.filter((t) => t[0] === "relays").flatMap((t) => t.slice(1)).filter((url) => WS_URL_RE.test(url));
|
|
6187
|
+
}
|
|
6188
|
+
var INBOX_OPTIONS = {
|
|
6189
|
+
json: { type: "boolean", default: false },
|
|
6190
|
+
follow: { type: "boolean", default: false },
|
|
6191
|
+
all: { type: "boolean", default: false },
|
|
6192
|
+
since: { type: "string" },
|
|
6193
|
+
pubkey: { type: "string" },
|
|
6194
|
+
remote: { type: "string" },
|
|
6195
|
+
relay: { type: "string", multiple: true },
|
|
6196
|
+
help: { type: "boolean", short: "h", default: false }
|
|
6197
|
+
};
|
|
6198
|
+
function renderLine(entry) {
|
|
6199
|
+
const when = new Date(entry.created_at * 1e3).toISOString().slice(0, 16).replace("T", " ");
|
|
6200
|
+
const repo = entry.repo ?? "(no repo)";
|
|
6201
|
+
return `${when} ${entry.type.padEnd(8)} ${entry.id.slice(0, 8)} \u2192 ${entry.target.slice(0, 8)} ${repo} (${entry.author.slice(0, 8)})`;
|
|
6202
|
+
}
|
|
6203
|
+
async function runInbox(args, deps) {
|
|
6204
|
+
const { io } = deps;
|
|
6205
|
+
let json = false;
|
|
6206
|
+
let all = false;
|
|
6207
|
+
let follow = false;
|
|
6208
|
+
let sinceArg;
|
|
6209
|
+
let pubkeyArg;
|
|
6210
|
+
let remote;
|
|
6211
|
+
let relayFlags = [];
|
|
6212
|
+
try {
|
|
6213
|
+
const { values, positionals } = parseArgs14({
|
|
6214
|
+
args,
|
|
6215
|
+
options: INBOX_OPTIONS,
|
|
6216
|
+
allowPositionals: true
|
|
6217
|
+
});
|
|
6218
|
+
if (values.help) {
|
|
6219
|
+
io.out(INBOX_USAGE);
|
|
6220
|
+
return 0;
|
|
6221
|
+
}
|
|
6222
|
+
if (positionals.length > 0) {
|
|
6223
|
+
throw new Error("rig inbox takes no positional arguments");
|
|
6224
|
+
}
|
|
6225
|
+
json = values.json === true;
|
|
6226
|
+
all = values.all === true;
|
|
6227
|
+
follow = values.follow === true;
|
|
6228
|
+
sinceArg = values.since;
|
|
6229
|
+
if (values.pubkey !== void 0) pubkeyArg = ownerToHex(values.pubkey);
|
|
6230
|
+
remote = values.remote;
|
|
6231
|
+
relayFlags = values.relay ?? [];
|
|
6232
|
+
if (all && sinceArg !== void 0) {
|
|
6233
|
+
throw new Error("--all and --since are mutually exclusive");
|
|
6234
|
+
}
|
|
6235
|
+
} catch (err) {
|
|
6236
|
+
io.err(err instanceof Error ? err.message : String(err));
|
|
6237
|
+
io.err(INBOX_USAGE);
|
|
6238
|
+
return 2;
|
|
6239
|
+
}
|
|
6240
|
+
try {
|
|
6241
|
+
const nowSec = Math.floor((deps.now ?? Date.now)() / 1e3);
|
|
6242
|
+
const override = sinceArg !== void 0 ? parseSince(sinceArg, nowSec) : null;
|
|
6243
|
+
const me = (pubkeyArg ?? (await (deps.resolveIdentityImpl ?? resolveIdentity)({
|
|
6244
|
+
env: deps.env,
|
|
6245
|
+
cwd: deps.cwd,
|
|
6246
|
+
warn: (line) => io.err(line)
|
|
6247
|
+
})).pubkey).toLowerCase();
|
|
6248
|
+
let repoRoot;
|
|
6249
|
+
let toonRelays = [];
|
|
6250
|
+
try {
|
|
6251
|
+
repoRoot = await resolveRepoRoot(deps.cwd);
|
|
6252
|
+
toonRelays = (await readToonConfig(repoRoot)).relays;
|
|
6253
|
+
} catch {
|
|
6254
|
+
}
|
|
6255
|
+
const resolved = await resolveRelays({
|
|
6256
|
+
relayFlags,
|
|
6257
|
+
remoteName: remote,
|
|
6258
|
+
repoRoot,
|
|
6259
|
+
toonRelays
|
|
6260
|
+
});
|
|
6261
|
+
const configured = resolved.relays.filter((url) => WS_URL_RE.test(url));
|
|
6262
|
+
if (configured.length === 0) {
|
|
6263
|
+
throw new InvalidRelayUrlError(
|
|
6264
|
+
resolved.relays[0] ?? "",
|
|
6265
|
+
"reads need a ws:// or wss:// relay"
|
|
6266
|
+
);
|
|
6267
|
+
}
|
|
6268
|
+
const ws = deps.webSocketFactory ?? defaultWebSocketFactory;
|
|
6269
|
+
const announcements = await queryAll(
|
|
6270
|
+
configured,
|
|
6271
|
+
[
|
|
6272
|
+
{ kinds: [REPOSITORY_ANNOUNCEMENT_KIND4], authors: [me] },
|
|
6273
|
+
{ kinds: [REPOSITORY_ANNOUNCEMENT_KIND4], "#maintainers": [me] }
|
|
6274
|
+
],
|
|
6275
|
+
ws
|
|
6276
|
+
);
|
|
6277
|
+
const byRepo = /* @__PURE__ */ new Map();
|
|
6278
|
+
for (const e of announcements.values()) {
|
|
6279
|
+
if (e.kind !== REPOSITORY_ANNOUNCEMENT_KIND4) continue;
|
|
6280
|
+
const d = tagValue2(e.tags, "d");
|
|
6281
|
+
if (!d) continue;
|
|
6282
|
+
const key = `${e.pubkey.toLowerCase()}:${d}`;
|
|
6283
|
+
byRepo.set(key, [...byRepo.get(key) ?? [], e]);
|
|
6284
|
+
}
|
|
6285
|
+
const watched = [];
|
|
6286
|
+
for (const [key, events2] of byRepo) {
|
|
6287
|
+
const owner = events2[0].pubkey.toLowerCase();
|
|
6288
|
+
const repoId = key.slice(owner.length + 1);
|
|
6289
|
+
const latest = owner === me ? newest(events2) : await ownerAnnouncement(configured, owner, repoId, ws);
|
|
6290
|
+
if (latest === null) continue;
|
|
6291
|
+
if (owner !== me && !parseMaintainers(latest.tags).includes(me)) continue;
|
|
6292
|
+
watched.push({
|
|
6293
|
+
address: `${REPOSITORY_ANNOUNCEMENT_KIND4}:${owner}:${repoId}`,
|
|
6294
|
+
ownerPubkey: owner,
|
|
6295
|
+
authorized: authorizedAuthors(owner, latest.tags),
|
|
6296
|
+
relays: relaysTag(latest.tags)
|
|
6297
|
+
});
|
|
6298
|
+
}
|
|
6299
|
+
const cursor = readCursors(deps.env)[me];
|
|
6300
|
+
const since = all ? void 0 : override !== null ? override : cursor?.at;
|
|
6301
|
+
const base = since !== void 0 ? { since } : {};
|
|
6302
|
+
const events = /* @__PURE__ */ new Map();
|
|
6303
|
+
const collect = (found) => {
|
|
6304
|
+
for (const [id, e] of found) if (!events.has(id)) events.set(id, e);
|
|
6305
|
+
};
|
|
6306
|
+
collect(
|
|
6307
|
+
await queryAll(
|
|
6308
|
+
configured,
|
|
6309
|
+
[{ kinds: INBOX_KINDS, "#p": [me], ...base }],
|
|
6310
|
+
ws
|
|
6311
|
+
)
|
|
6312
|
+
);
|
|
6313
|
+
for (const repo of watched) {
|
|
6314
|
+
const relays = [.../* @__PURE__ */ new Set([...configured, ...repo.relays])];
|
|
6315
|
+
collect(
|
|
6316
|
+
await queryAll(
|
|
6317
|
+
relays,
|
|
6318
|
+
[{ kinds: INBOX_KINDS, "#a": [repo.address], ...base }],
|
|
6319
|
+
ws
|
|
6320
|
+
)
|
|
6321
|
+
);
|
|
6322
|
+
}
|
|
6323
|
+
const authCache = /* @__PURE__ */ new Map();
|
|
6324
|
+
for (const repo of watched) authCache.set(repo.address, repo.authorized);
|
|
6325
|
+
const authorizedFor = async (address) => {
|
|
6326
|
+
const hit = authCache.get(address);
|
|
6327
|
+
if (hit) return hit;
|
|
6328
|
+
const m = /^30617:([0-9a-f]{64}):(.+)$/i.exec(address);
|
|
6329
|
+
let set = /* @__PURE__ */ new Set();
|
|
6330
|
+
if (m) {
|
|
6331
|
+
const owner = m[1].toLowerCase();
|
|
6332
|
+
const latest = await ownerAnnouncement(
|
|
6333
|
+
configured,
|
|
6334
|
+
owner,
|
|
6335
|
+
m[2],
|
|
6336
|
+
ws
|
|
6337
|
+
);
|
|
6338
|
+
set = authorizedAuthors(owner, latest?.tags ?? []);
|
|
6339
|
+
}
|
|
6340
|
+
authCache.set(address, set);
|
|
6341
|
+
return set;
|
|
6342
|
+
};
|
|
6343
|
+
const toEntries = async (found) => {
|
|
6344
|
+
const targetAuthors = /* @__PURE__ */ new Map();
|
|
6345
|
+
const statusEvents = [...found.values()].filter(
|
|
6346
|
+
(e) => STATUS_KINDS2.includes(e.kind)
|
|
6347
|
+
);
|
|
6348
|
+
const unknownTargets = /* @__PURE__ */ new Set();
|
|
6349
|
+
for (const e of statusEvents) {
|
|
6350
|
+
const target = tagValue2(e.tags, "e");
|
|
6351
|
+
if (!target || !HEX64_RE5.test(target)) continue;
|
|
6352
|
+
const known = found.get(target);
|
|
6353
|
+
if (known) targetAuthors.set(target, known.pubkey.toLowerCase());
|
|
6354
|
+
else unknownTargets.add(target);
|
|
6355
|
+
}
|
|
6356
|
+
if (unknownTargets.size > 0) {
|
|
6357
|
+
const lookedUp = await queryAll(
|
|
6358
|
+
configured,
|
|
6359
|
+
[{ ids: [...unknownTargets] }],
|
|
6360
|
+
ws
|
|
6361
|
+
).catch(() => /* @__PURE__ */ new Map());
|
|
6362
|
+
for (const [id, e] of lookedUp)
|
|
6363
|
+
targetAuthors.set(id, e.pubkey.toLowerCase());
|
|
6364
|
+
}
|
|
6365
|
+
const entries = [];
|
|
6366
|
+
for (const event of found.values()) {
|
|
6367
|
+
const entry = toInboxEntry(event);
|
|
6368
|
+
if (entry === null) continue;
|
|
6369
|
+
if (entry.type === "status") {
|
|
6370
|
+
if (entry.repo === null) continue;
|
|
6371
|
+
const repoWide = await authorizedFor(entry.repo);
|
|
6372
|
+
const targetAuthor = targetAuthors.get(entry.target);
|
|
6373
|
+
const authors = targetAuthor === void 0 ? repoWide : withTargetAuthor(repoWide, targetAuthor);
|
|
6374
|
+
if (!authors.has(event.pubkey.toLowerCase())) continue;
|
|
6375
|
+
}
|
|
6376
|
+
entries.push(entry);
|
|
6377
|
+
}
|
|
6378
|
+
return entries;
|
|
6379
|
+
};
|
|
6380
|
+
const entriesOf = await toEntries(events);
|
|
6381
|
+
const isFresh = (e) => all || override !== null || cursor === void 0 || e.created_at > cursor.at || e.created_at === cursor.at && !cursor.seen.includes(e.id);
|
|
6382
|
+
const byAge = (a, b) => a.created_at - b.created_at || (a.id < b.id ? -1 : 1);
|
|
6383
|
+
const fresh = entriesOf.filter(isFresh).sort(byAge);
|
|
6384
|
+
const moveCursor = !all && override === null;
|
|
6385
|
+
let position = cursor;
|
|
6386
|
+
const advance = (emitted) => {
|
|
6387
|
+
if (!moveCursor || emitted.length === 0) return;
|
|
6388
|
+
const at = Math.max(...emitted.map((e) => e.created_at));
|
|
6389
|
+
const seen = emitted.filter((e) => e.created_at === at).map((e) => e.id);
|
|
6390
|
+
if (position?.at === at) seen.push(...position.seen);
|
|
6391
|
+
else if (position !== void 0 && position.at > at) return;
|
|
6392
|
+
position = { at, seen: [...new Set(seen)] };
|
|
6393
|
+
writeCursor(deps.env, me, position);
|
|
6394
|
+
};
|
|
6395
|
+
if (follow) {
|
|
6396
|
+
const line = (entry) => io.emitJsonLine ? io.emitJsonLine(entry) : io.out(JSON.stringify(entry));
|
|
6397
|
+
const emittedIds = /* @__PURE__ */ new Set();
|
|
6398
|
+
let liveSince = position?.at;
|
|
6399
|
+
const emit = (entries) => {
|
|
6400
|
+
for (const entry of entries) {
|
|
6401
|
+
emittedIds.add(entry.id);
|
|
6402
|
+
liveSince = Math.max(liveSince ?? 0, entry.created_at);
|
|
6403
|
+
line(entry);
|
|
6404
|
+
}
|
|
6405
|
+
advance(entries);
|
|
6406
|
+
};
|
|
6407
|
+
emit(fresh);
|
|
6408
|
+
const controller = new AbortController();
|
|
6409
|
+
const stop = () => controller.abort();
|
|
6410
|
+
const external = deps.followSignal;
|
|
6411
|
+
if (external?.aborted) return 0;
|
|
6412
|
+
external?.addEventListener("abort", stop);
|
|
6413
|
+
if (!external) {
|
|
6414
|
+
process.once("SIGINT", stop);
|
|
6415
|
+
process.once("SIGTERM", stop);
|
|
6416
|
+
}
|
|
6417
|
+
let chain = Promise.resolve();
|
|
6418
|
+
const onEvent = (event) => {
|
|
6419
|
+
chain = chain.then(async () => {
|
|
6420
|
+
if (emittedIds.has(event.id)) return;
|
|
6421
|
+
try {
|
|
6422
|
+
const [entry] = await toEntries(/* @__PURE__ */ new Map([[event.id, event]]));
|
|
6423
|
+
if (entry === void 0 || emittedIds.has(entry.id) || !isFresh(entry)) {
|
|
6424
|
+
return;
|
|
6425
|
+
}
|
|
6426
|
+
emit([entry]);
|
|
6427
|
+
} catch (err) {
|
|
6428
|
+
io.err(
|
|
6429
|
+
`rig inbox: could not process live event ${event.id}: ${err instanceof Error ? err.message : String(err)}`
|
|
6430
|
+
);
|
|
6431
|
+
}
|
|
6432
|
+
});
|
|
6433
|
+
};
|
|
6434
|
+
const targets = [];
|
|
6435
|
+
for (const url of configured) {
|
|
6436
|
+
targets.push({ url, filter: { kinds: INBOX_KINDS, "#p": [me] } });
|
|
6437
|
+
}
|
|
6438
|
+
for (const repo of watched) {
|
|
6439
|
+
for (const url of /* @__PURE__ */ new Set([...configured, ...repo.relays])) {
|
|
6440
|
+
targets.push({
|
|
6441
|
+
url,
|
|
6442
|
+
filter: { kinds: INBOX_KINDS, "#a": [repo.address] }
|
|
6443
|
+
});
|
|
6444
|
+
}
|
|
6445
|
+
}
|
|
6446
|
+
const urls = [...new Set(targets.map((t) => t.url))];
|
|
6447
|
+
try {
|
|
6448
|
+
await Promise.all(
|
|
6449
|
+
urls.map(
|
|
6450
|
+
(url) => followRelay({
|
|
6451
|
+
url,
|
|
6452
|
+
filters: () => targets.filter((t) => t.url === url).map((t) => ({ ...t.filter, since: liveSince ?? nowSec })),
|
|
6453
|
+
webSocketFactory: ws,
|
|
6454
|
+
onEvent,
|
|
6455
|
+
onError: (message) => io.err(`rig inbox: ${message}`),
|
|
6456
|
+
signal: controller.signal,
|
|
6457
|
+
backoffMs: deps.followBackoffMs ?? defaultBackoffMs
|
|
6458
|
+
})
|
|
6459
|
+
)
|
|
6460
|
+
);
|
|
6461
|
+
await chain;
|
|
6462
|
+
} finally {
|
|
6463
|
+
external?.removeEventListener("abort", stop);
|
|
6464
|
+
process.off("SIGINT", stop);
|
|
6465
|
+
process.off("SIGTERM", stop);
|
|
6466
|
+
}
|
|
6467
|
+
return 0;
|
|
6468
|
+
}
|
|
6469
|
+
if (json) {
|
|
6470
|
+
io.emitJson(fresh);
|
|
6471
|
+
} else if (fresh.length === 0) {
|
|
6472
|
+
io.out("inbox is empty");
|
|
6473
|
+
} else {
|
|
6474
|
+
for (const entry of fresh) io.out(renderLine(entry));
|
|
6475
|
+
}
|
|
6476
|
+
advance(fresh);
|
|
6477
|
+
return 0;
|
|
6478
|
+
} catch (err) {
|
|
6479
|
+
return emitCliError(io, json, "inbox", err);
|
|
6480
|
+
}
|
|
6481
|
+
}
|
|
6482
|
+
|
|
5303
6483
|
// src/cli/init.ts
|
|
5304
6484
|
import { basename as basename2 } from "path";
|
|
5305
|
-
import { parseArgs as
|
|
6485
|
+
import { parseArgs as parseArgs15 } from "util";
|
|
5306
6486
|
|
|
5307
6487
|
// src/cli/git-author.ts
|
|
5308
6488
|
var PROFILE_KIND = 0;
|
|
@@ -5420,7 +6600,7 @@ Options:
|
|
|
5420
6600
|
--json machine-readable report
|
|
5421
6601
|
-h, --help show this help`;
|
|
5422
6602
|
function parseInitArgs(args) {
|
|
5423
|
-
const { values, positionals } =
|
|
6603
|
+
const { values, positionals } = parseArgs15({
|
|
5424
6604
|
args,
|
|
5425
6605
|
options: {
|
|
5426
6606
|
"repo-id": { type: "string" },
|
|
@@ -5647,8 +6827,8 @@ async function runInit(args, deps) {
|
|
|
5647
6827
|
}
|
|
5648
6828
|
|
|
5649
6829
|
// src/cli/maintainers.ts
|
|
5650
|
-
import { parseArgs as
|
|
5651
|
-
var
|
|
6830
|
+
import { parseArgs as parseArgs16 } from "util";
|
|
6831
|
+
var HEX64_RE6 = /^[0-9a-f]{64}$/;
|
|
5652
6832
|
var REF_AUTHORITY_NOTICE = "Note: maintainers can move refs (publish honored repo state), not only set status.";
|
|
5653
6833
|
var MAINTAINERS_USAGE = `Usage: rig maintainers <list|add|remove> [<pubkey>] [options]
|
|
5654
6834
|
|
|
@@ -5701,7 +6881,7 @@ async function runList2(args, deps) {
|
|
|
5701
6881
|
const { io } = deps;
|
|
5702
6882
|
let flags;
|
|
5703
6883
|
try {
|
|
5704
|
-
const { values, positionals } =
|
|
6884
|
+
const { values, positionals } = parseArgs16({
|
|
5705
6885
|
args,
|
|
5706
6886
|
options: REPO_COMMAND_OPTIONS,
|
|
5707
6887
|
allowPositionals: true
|
|
@@ -5768,7 +6948,7 @@ async function runMutate(op, args, deps) {
|
|
|
5768
6948
|
let flags;
|
|
5769
6949
|
let pubkey;
|
|
5770
6950
|
try {
|
|
5771
|
-
const { values, positionals } =
|
|
6951
|
+
const { values, positionals } = parseArgs16({
|
|
5772
6952
|
args,
|
|
5773
6953
|
options: REPO_COMMAND_OPTIONS,
|
|
5774
6954
|
allowPositionals: true
|
|
@@ -5782,7 +6962,7 @@ async function runMutate(op, args, deps) {
|
|
|
5782
6962
|
throw new Error(`expected exactly one <pubkey> to ${op}`);
|
|
5783
6963
|
}
|
|
5784
6964
|
pubkey = ownerToHex(positionals[0]).toLowerCase();
|
|
5785
|
-
if (!
|
|
6965
|
+
if (!HEX64_RE6.test(pubkey)) {
|
|
5786
6966
|
throw new Error(
|
|
5787
6967
|
`<pubkey> must resolve to 64-char hex (got ${JSON.stringify(positionals[0])})`
|
|
5788
6968
|
);
|
|
@@ -5903,7 +7083,7 @@ async function runMutate(op, args, deps) {
|
|
|
5903
7083
|
}
|
|
5904
7084
|
|
|
5905
7085
|
// src/cli/refresh.ts
|
|
5906
|
-
import { parseArgs as
|
|
7086
|
+
import { parseArgs as parseArgs17 } from "util";
|
|
5907
7087
|
var REFRESH_USAGE = `Usage: rig refresh [options]
|
|
5908
7088
|
|
|
5909
7089
|
Republish this repo's kind:30617 announcement with no field edit, refreshing
|
|
@@ -5939,7 +7119,7 @@ async function runRefresh(args, deps) {
|
|
|
5939
7119
|
const { io } = deps;
|
|
5940
7120
|
let flags;
|
|
5941
7121
|
try {
|
|
5942
|
-
const { values, positionals } =
|
|
7122
|
+
const { values, positionals } = parseArgs17({
|
|
5943
7123
|
args,
|
|
5944
7124
|
options: REPO_COMMAND_OPTIONS,
|
|
5945
7125
|
allowPositionals: true
|
|
@@ -6062,7 +7242,7 @@ async function runRefresh(args, deps) {
|
|
|
6062
7242
|
|
|
6063
7243
|
// src/cli/name.ts
|
|
6064
7244
|
import { randomUUID } from "crypto";
|
|
6065
|
-
import { parseArgs as
|
|
7245
|
+
import { parseArgs as parseArgs18 } from "util";
|
|
6066
7246
|
var ARIO_DECIMALS = 6;
|
|
6067
7247
|
function renderMario(mario) {
|
|
6068
7248
|
return `${mario.toString()} mARIO (~${formatUnits(mario.toString(), ARIO_DECIMALS)} ARIO)`;
|
|
@@ -6571,7 +7751,7 @@ async function resolveNameContext(deps, network, processId, mode) {
|
|
|
6571
7751
|
return { identity, solanaAddress, network, processId, sdk, nostrSecretKey };
|
|
6572
7752
|
}
|
|
6573
7753
|
function parseNameFlags(args, env) {
|
|
6574
|
-
const { values, positionals } =
|
|
7754
|
+
const { values, positionals } = parseArgs18({
|
|
6575
7755
|
args,
|
|
6576
7756
|
allowPositionals: true,
|
|
6577
7757
|
options: {
|
|
@@ -7130,7 +8310,7 @@ async function runName(args, deps) {
|
|
|
7130
8310
|
}
|
|
7131
8311
|
|
|
7132
8312
|
// src/cli/payout.ts
|
|
7133
|
-
import { parseArgs as
|
|
8313
|
+
import { parseArgs as parseArgs19 } from "util";
|
|
7134
8314
|
import { getAddress } from "viem";
|
|
7135
8315
|
var PAYOUT_USAGE = `Usage: rig payout <set|clear|show> [<address>] [options]
|
|
7136
8316
|
|
|
@@ -7186,7 +8366,7 @@ async function runShow2(args, deps) {
|
|
|
7186
8366
|
const { io } = deps;
|
|
7187
8367
|
let flags;
|
|
7188
8368
|
try {
|
|
7189
|
-
const { values, positionals } =
|
|
8369
|
+
const { values, positionals } = parseArgs19({
|
|
7190
8370
|
args,
|
|
7191
8371
|
options: REPO_COMMAND_OPTIONS,
|
|
7192
8372
|
allowPositionals: true
|
|
@@ -7248,7 +8428,7 @@ async function runMutate2(op, args, deps) {
|
|
|
7248
8428
|
let flags;
|
|
7249
8429
|
let nextPayout = null;
|
|
7250
8430
|
try {
|
|
7251
|
-
const { values, positionals } =
|
|
8431
|
+
const { values, positionals } = parseArgs19({
|
|
7252
8432
|
args,
|
|
7253
8433
|
options: REPO_COMMAND_OPTIONS,
|
|
7254
8434
|
allowPositionals: true
|
|
@@ -7382,7 +8562,7 @@ async function runMutate2(op, args, deps) {
|
|
|
7382
8562
|
}
|
|
7383
8563
|
|
|
7384
8564
|
// src/cli/site.ts
|
|
7385
|
-
import { parseArgs as
|
|
8565
|
+
import { parseArgs as parseArgs20 } from "util";
|
|
7386
8566
|
|
|
7387
8567
|
// src/arweave-manifest.ts
|
|
7388
8568
|
var DEFAULT_INDEX = "index.html";
|
|
@@ -7401,22 +8581,22 @@ function buildArweaveManifest(entries, indexPath = DEFAULT_INDEX, fallbackTxId)
|
|
|
7401
8581
|
}
|
|
7402
8582
|
|
|
7403
8583
|
// src/cli/site-record.ts
|
|
7404
|
-
import { mkdirSync as
|
|
8584
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
7405
8585
|
import { homedir as homedir5 } from "os";
|
|
7406
|
-
import { join as
|
|
8586
|
+
import { join as join8 } from "path";
|
|
7407
8587
|
var SITE_MANIFEST_FILENAME = "site-manifests.json";
|
|
7408
8588
|
function storeDir2(env) {
|
|
7409
|
-
return env["TOON_CLIENT_HOME"] ??
|
|
8589
|
+
return env["TOON_CLIENT_HOME"] ?? join8(homedir5(), ".toon-client");
|
|
7410
8590
|
}
|
|
7411
8591
|
function storePath2(env) {
|
|
7412
|
-
return
|
|
8592
|
+
return join8(storeDir2(env), SITE_MANIFEST_FILENAME);
|
|
7413
8593
|
}
|
|
7414
8594
|
function keyOf(repoId, ref) {
|
|
7415
8595
|
return `${repoId}\0${ref}`;
|
|
7416
8596
|
}
|
|
7417
8597
|
function readFile2(env) {
|
|
7418
8598
|
try {
|
|
7419
|
-
const parsed = JSON.parse(
|
|
8599
|
+
const parsed = JSON.parse(readFileSync6(storePath2(env), "utf8"));
|
|
7420
8600
|
if (parsed && typeof parsed === "object") return parsed;
|
|
7421
8601
|
return {};
|
|
7422
8602
|
} catch {
|
|
@@ -7428,10 +8608,10 @@ function readSiteRecord(env, repoId, ref) {
|
|
|
7428
8608
|
}
|
|
7429
8609
|
function writeSiteRecord(env, record) {
|
|
7430
8610
|
const dir = storeDir2(env);
|
|
7431
|
-
|
|
8611
|
+
mkdirSync6(dir, { recursive: true });
|
|
7432
8612
|
const file = readFile2(env);
|
|
7433
8613
|
file[keyOf(record.repoId, record.ref)] = record;
|
|
7434
|
-
|
|
8614
|
+
writeFileSync5(storePath2(env), `${JSON.stringify(file, null, 2)}
|
|
7435
8615
|
`, "utf8");
|
|
7436
8616
|
}
|
|
7437
8617
|
|
|
@@ -7473,7 +8653,7 @@ Options (publish):
|
|
|
7473
8653
|
estimate (nothing uploaded)
|
|
7474
8654
|
-h, --help show this help`;
|
|
7475
8655
|
function parseSiteArgs(args) {
|
|
7476
|
-
const { values, positionals } =
|
|
8656
|
+
const { values, positionals } = parseArgs20({
|
|
7477
8657
|
args,
|
|
7478
8658
|
options: {
|
|
7479
8659
|
index: { type: "string" },
|
|
@@ -7881,9 +9061,18 @@ Commands rig owns:
|
|
|
7881
9061
|
comment <target-event-id> comment (NIP-22 kind:1111) on an issue or patch,
|
|
7882
9062
|
or reply to an existing kind:1111 comment
|
|
7883
9063
|
pr create publish a patch (kind:1617) with real
|
|
7884
|
-
\`git format-patch\` content
|
|
9064
|
+
\`git format-patch\` content (--revises <patch-id>
|
|
9065
|
+
publishes a revision of an existing patch)
|
|
9066
|
+
pr merge <patch-id> apply the latest revision with git am, push, then
|
|
9067
|
+
publish the applied status (owner/maintainer)
|
|
7885
9068
|
pr status <event-id> <state> set an issue/patch status (kind:1630-1633):
|
|
7886
9069
|
open | applied | closed | draft
|
|
9070
|
+
inbox everything new since you last read it (free):
|
|
9071
|
+
patches, revisions, issues, comments, statuses
|
|
9072
|
+
and CI results for repos you own or maintain,
|
|
9073
|
+
plus events that tag you; --since/--all look
|
|
9074
|
+
back without moving the cursor; --follow keeps
|
|
9075
|
+
streaming new events as JSON lines
|
|
7887
9076
|
maintainers list show the repo's declared maintainers (free); add/
|
|
7888
9077
|
maintainers add <pubkey> remove republish the kind:30617 to change who may
|
|
7889
9078
|
maintainers remove <pubkey> author authoritative issue/PR status (owner is
|
|
@@ -8006,6 +9195,8 @@ async function dispatch(argv, deps) {
|
|
|
8006
9195
|
return runComment(rest, deps);
|
|
8007
9196
|
case "pr":
|
|
8008
9197
|
return runPr(rest, deps);
|
|
9198
|
+
case "inbox":
|
|
9199
|
+
return runInbox(rest, deps);
|
|
8009
9200
|
case "maintainers":
|
|
8010
9201
|
return runMaintainers(rest, deps);
|
|
8011
9202
|
case "payout":
|
|
@@ -8062,4 +9253,4 @@ export {
|
|
|
8062
9253
|
rigVersion,
|
|
8063
9254
|
dispatch
|
|
8064
9255
|
};
|
|
8065
|
-
//# sourceMappingURL=chunk-
|
|
9256
|
+
//# sourceMappingURL=chunk-KQ4NQV7T.js.map
|