@toon-protocol/rig 2.1.0 → 2.2.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/README.md +92 -13
- package/dist/{chunk-PLKZAUTG.js → chunk-JBB7HBQC.js} +119 -6
- package/dist/chunk-JBB7HBQC.js.map +1 -0
- package/dist/{chunk-3EKP7PMM.js → chunk-OIJNRACA.js} +5 -144
- package/dist/chunk-OIJNRACA.js.map +1 -0
- package/dist/{chunk-LFGDLD6J.js → chunk-PS5QOT62.js} +464 -4
- package/dist/chunk-PS5QOT62.js.map +1 -0
- package/dist/{chunk-O6TXHKWG.js → chunk-SW7ZHMGS.js} +143 -2
- package/dist/chunk-SW7ZHMGS.js.map +1 -0
- package/dist/cli/rig.js +1515 -149
- package/dist/cli/rig.js.map +1 -1
- package/dist/index.d.ts +271 -3
- package/dist/index.js +44 -8
- package/dist/{publisher-Cdr1H1hg.d.ts → publisher-CrwaNQrK.d.ts} +11 -2
- package/dist/standalone/index.d.ts +7 -1
- package/dist/standalone/index.js +9 -9
- package/dist/{standalone-mode-FCKTQ33Y.js → standalone-mode-64CKUVU2.js} +367 -97
- package/dist/standalone-mode-64CKUVU2.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-3EKP7PMM.js.map +0 -1
- package/dist/chunk-HPSOQP7Q.js +0 -109
- package/dist/chunk-HPSOQP7Q.js.map +0 -1
- package/dist/chunk-LFGDLD6J.js.map +0 -1
- package/dist/chunk-O6TXHKWG.js.map +0 -1
- package/dist/chunk-PLKZAUTG.js.map +0 -1
- package/dist/standalone-mode-FCKTQ33Y.js.map +0 -1
package/dist/cli/rig.js
CHANGED
|
@@ -4,27 +4,39 @@ import {
|
|
|
4
4
|
GitRepoReader,
|
|
5
5
|
NonFastForwardError,
|
|
6
6
|
OversizeObjectsError,
|
|
7
|
+
collectRepoObjects,
|
|
7
8
|
executePush,
|
|
9
|
+
isSafeRefname,
|
|
10
|
+
missingObjectsMessage,
|
|
11
|
+
ownerToHex,
|
|
8
12
|
planPush,
|
|
13
|
+
runGit,
|
|
9
14
|
serializeEventReceipt,
|
|
10
15
|
serializePushPlan,
|
|
11
|
-
serializePushResult
|
|
12
|
-
|
|
16
|
+
serializePushResult,
|
|
17
|
+
setHeadSymref,
|
|
18
|
+
updateRef,
|
|
19
|
+
writeGitObjects
|
|
20
|
+
} from "../chunk-PS5QOT62.js";
|
|
13
21
|
import {
|
|
14
22
|
MissingIdentityError,
|
|
15
23
|
resolveIdentity
|
|
16
24
|
} from "../chunk-CW4HJNMU.js";
|
|
17
25
|
import {
|
|
26
|
+
COMMENT_KIND,
|
|
18
27
|
buildComment,
|
|
19
28
|
buildIssue,
|
|
20
29
|
buildPatch,
|
|
21
|
-
buildStatus
|
|
22
|
-
|
|
30
|
+
buildStatus,
|
|
31
|
+
fetchRemoteState,
|
|
32
|
+
queryRelay
|
|
33
|
+
} from "../chunk-JBB7HBQC.js";
|
|
23
34
|
import {
|
|
24
35
|
ChannelMapStore,
|
|
25
36
|
channelStatus,
|
|
37
|
+
defaultDaemonPort,
|
|
26
38
|
resolveChannelPaths
|
|
27
|
-
} from "../chunk-
|
|
39
|
+
} from "../chunk-SW7ZHMGS.js";
|
|
28
40
|
import {
|
|
29
41
|
MAX_OBJECT_SIZE
|
|
30
42
|
} from "../chunk-X2CZPPDM.js";
|
|
@@ -38,6 +50,155 @@ import { createRequire } from "module";
|
|
|
38
50
|
// src/cli/balance.ts
|
|
39
51
|
import { parseArgs as parseArgs3 } from "util";
|
|
40
52
|
|
|
53
|
+
// src/cli/daemon-session.ts
|
|
54
|
+
function daemonBaseUrl(env) {
|
|
55
|
+
const raw = env["TOON_CLIENT_HTTP_PORT"];
|
|
56
|
+
const parsed = raw ? Number(raw) : NaN;
|
|
57
|
+
const port = Number.isFinite(parsed) && parsed > 0 ? parsed : defaultDaemonPort();
|
|
58
|
+
return `http://127.0.0.1:${port}`;
|
|
59
|
+
}
|
|
60
|
+
async function probeDaemon(env, fetchImpl, timeoutMs = 1500) {
|
|
61
|
+
const baseUrl = daemonBaseUrl(env);
|
|
62
|
+
try {
|
|
63
|
+
const res = await fetchImpl(`${baseUrl}/status`, {
|
|
64
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
65
|
+
});
|
|
66
|
+
if (!res.ok) return { baseUrl, reachable: false };
|
|
67
|
+
const body = await res.json();
|
|
68
|
+
const probe = { baseUrl, reachable: true };
|
|
69
|
+
const pubkey = body?.identity?.nostrPubkey;
|
|
70
|
+
if (typeof pubkey === "string" && pubkey !== "") probe.identity = pubkey;
|
|
71
|
+
if (typeof body?.ready === "boolean") probe.ready = body.ready;
|
|
72
|
+
if (typeof body?.relay?.url === "string" && body.relay.url !== "") {
|
|
73
|
+
probe.relayUrl = body.relay.url;
|
|
74
|
+
}
|
|
75
|
+
if (typeof body?.feePerEvent === "string" && body.feePerEvent !== "") {
|
|
76
|
+
probe.feePerEvent = body.feePerEvent;
|
|
77
|
+
}
|
|
78
|
+
return probe;
|
|
79
|
+
} catch {
|
|
80
|
+
return { baseUrl, reachable: false };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
var DaemonRouteError = class extends Error {
|
|
84
|
+
constructor(status, envelope) {
|
|
85
|
+
super(envelope.detail ?? envelope.error);
|
|
86
|
+
this.status = status;
|
|
87
|
+
this.envelope = envelope;
|
|
88
|
+
this.name = "DaemonRouteError";
|
|
89
|
+
}
|
|
90
|
+
status;
|
|
91
|
+
envelope;
|
|
92
|
+
};
|
|
93
|
+
var DaemonUnreachableError = class extends Error {
|
|
94
|
+
constructor(baseUrl, cause) {
|
|
95
|
+
super(
|
|
96
|
+
`toon-clientd stopped answering at ${baseUrl} after the identity probe \u2014 nothing was paid. Re-run: rig falls back to standalone automatically when no daemon responds` + (cause instanceof Error ? ` (${cause.message})` : "")
|
|
97
|
+
);
|
|
98
|
+
this.baseUrl = baseUrl;
|
|
99
|
+
this.name = "DaemonUnreachableError";
|
|
100
|
+
}
|
|
101
|
+
baseUrl;
|
|
102
|
+
};
|
|
103
|
+
var DaemonGitClient = class {
|
|
104
|
+
constructor(baseUrl, fetchImpl) {
|
|
105
|
+
this.baseUrl = baseUrl;
|
|
106
|
+
this.fetchImpl = fetchImpl;
|
|
107
|
+
}
|
|
108
|
+
baseUrl;
|
|
109
|
+
fetchImpl;
|
|
110
|
+
gitEstimate(req) {
|
|
111
|
+
return this.post("/git/estimate", req);
|
|
112
|
+
}
|
|
113
|
+
gitPush(req) {
|
|
114
|
+
return this.post("/git/push", req);
|
|
115
|
+
}
|
|
116
|
+
gitIssue(req) {
|
|
117
|
+
return this.post("/git/issue", req);
|
|
118
|
+
}
|
|
119
|
+
gitComment(req) {
|
|
120
|
+
return this.post("/git/comment", req);
|
|
121
|
+
}
|
|
122
|
+
gitPatch(req) {
|
|
123
|
+
return this.post("/git/patch", req);
|
|
124
|
+
}
|
|
125
|
+
gitStatus(req) {
|
|
126
|
+
return this.post("/git/status", req);
|
|
127
|
+
}
|
|
128
|
+
async post(path, body) {
|
|
129
|
+
let res;
|
|
130
|
+
try {
|
|
131
|
+
res = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
132
|
+
method: "POST",
|
|
133
|
+
headers: { "content-type": "application/json" },
|
|
134
|
+
body: JSON.stringify(body)
|
|
135
|
+
});
|
|
136
|
+
} catch (err) {
|
|
137
|
+
throw new DaemonUnreachableError(this.baseUrl, err);
|
|
138
|
+
}
|
|
139
|
+
const text = await res.text();
|
|
140
|
+
let parsed;
|
|
141
|
+
try {
|
|
142
|
+
parsed = text === "" ? {} : JSON.parse(text);
|
|
143
|
+
} catch {
|
|
144
|
+
throw new DaemonRouteError(res.status, {
|
|
145
|
+
error: "invalid_response",
|
|
146
|
+
detail: `daemon returned non-JSON (HTTP ${res.status}): ${text.slice(0, 200)}`
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
if (!res.ok) {
|
|
150
|
+
const envelope = parsed && typeof parsed === "object" && "error" in parsed ? parsed : { error: "http_error", detail: `HTTP ${res.status}` };
|
|
151
|
+
throw new DaemonRouteError(res.status, envelope);
|
|
152
|
+
}
|
|
153
|
+
return parsed;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
async function resolvePaidSession(options) {
|
|
157
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
158
|
+
const probe = await (options.probeDaemon ?? probeDaemon)(
|
|
159
|
+
options.env,
|
|
160
|
+
fetchImpl
|
|
161
|
+
);
|
|
162
|
+
if (probe.reachable && probe.identity !== void 0) {
|
|
163
|
+
const identity = await resolveIdentity({
|
|
164
|
+
env: options.env,
|
|
165
|
+
cwd: options.cwd,
|
|
166
|
+
warn: options.warn
|
|
167
|
+
});
|
|
168
|
+
if (identity.pubkey === probe.identity) {
|
|
169
|
+
options.warn(
|
|
170
|
+
`rig: paid path: daemon \u2014 toon-clientd at ${probe.baseUrl} holds this identity (${identity.pubkey.slice(0, 8)}\u2026), delegating`
|
|
171
|
+
);
|
|
172
|
+
return {
|
|
173
|
+
path: "daemon",
|
|
174
|
+
client: new DaemonGitClient(probe.baseUrl, fetchImpl),
|
|
175
|
+
baseUrl: probe.baseUrl,
|
|
176
|
+
identity: {
|
|
177
|
+
pubkey: identity.pubkey,
|
|
178
|
+
source: identity.source,
|
|
179
|
+
sourceLabel: identity.sourceLabel
|
|
180
|
+
},
|
|
181
|
+
...probe.feePerEvent !== void 0 ? { feePerEvent: probe.feePerEvent } : {},
|
|
182
|
+
...probe.relayUrl !== void 0 ? { daemonRelayUrl: probe.relayUrl } : {}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
options.warn(
|
|
186
|
+
`rig: paid path: standalone \u2014 the toon-clientd at ${probe.baseUrl} runs a different identity (${probe.identity.slice(0, 8)}\u2026), no shared channel state`
|
|
187
|
+
);
|
|
188
|
+
} else {
|
|
189
|
+
options.warn(
|
|
190
|
+
`rig: paid path: standalone (no toon-clientd at ${probe.baseUrl})`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
const ctx = await options.loadStandalone({
|
|
194
|
+
env: options.env,
|
|
195
|
+
cwd: options.cwd,
|
|
196
|
+
warn: options.warn,
|
|
197
|
+
...options.relayUrl !== void 0 ? { relayUrl: options.relayUrl } : {}
|
|
198
|
+
});
|
|
199
|
+
return { path: "standalone", ctx };
|
|
200
|
+
}
|
|
201
|
+
|
|
41
202
|
// src/cli/errors.ts
|
|
42
203
|
var UnconfiguredRepoAddressError = class extends Error {
|
|
43
204
|
constructor(missing) {
|
|
@@ -199,6 +360,39 @@ function describeError(err, command = "push") {
|
|
|
199
360
|
json: { error: "git_error", detail: err.message }
|
|
200
361
|
};
|
|
201
362
|
}
|
|
363
|
+
if (err instanceof DaemonRouteError) {
|
|
364
|
+
const envelope = err.envelope;
|
|
365
|
+
if (envelope.error === "non_fast_forward" && Array.isArray(envelope["refs"])) {
|
|
366
|
+
return {
|
|
367
|
+
code: "non_fast_forward",
|
|
368
|
+
lines: nonFastForwardLines(envelope["refs"]),
|
|
369
|
+
json: { ...envelope }
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
if (envelope.error === "oversize_objects" && Array.isArray(envelope["objects"])) {
|
|
373
|
+
return {
|
|
374
|
+
code: "oversize_objects",
|
|
375
|
+
lines: oversizeLines(envelope["objects"]),
|
|
376
|
+
json: { ...envelope }
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
const retryHint = envelope.retryable === true ? ["The daemon reports this as retryable \u2014 re-run shortly."] : [];
|
|
380
|
+
return {
|
|
381
|
+
code: envelope.error,
|
|
382
|
+
lines: [
|
|
383
|
+
`daemon rejected the operation (HTTP ${err.status}): ` + (envelope.detail ?? envelope.error),
|
|
384
|
+
...retryHint
|
|
385
|
+
],
|
|
386
|
+
json: { ...envelope }
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
if (err instanceof DaemonUnreachableError) {
|
|
390
|
+
return {
|
|
391
|
+
code: "daemon_unreachable",
|
|
392
|
+
lines: err.message.split("\n"),
|
|
393
|
+
json: { error: "daemon_unreachable", detail: err.message }
|
|
394
|
+
};
|
|
395
|
+
}
|
|
202
396
|
const name = err instanceof Error ? err.name : "";
|
|
203
397
|
const message = err instanceof Error ? err.message : String(err);
|
|
204
398
|
if (name === "DaemonIdentityConflictError") {
|
|
@@ -206,7 +400,7 @@ function describeError(err, command = "push") {
|
|
|
206
400
|
code: "daemon_identity_conflict",
|
|
207
401
|
lines: [
|
|
208
402
|
message,
|
|
209
|
-
"
|
|
403
|
+
"Paid writes delegate to a same-identity daemon automatically; seeing this means the daemon appeared mid-run or this operation has no daemon route \u2014 stop the daemon and re-run."
|
|
210
404
|
],
|
|
211
405
|
json: { error: "daemon_identity_conflict", detail: message }
|
|
212
406
|
};
|
|
@@ -240,6 +434,32 @@ function describeError(err, command = "push") {
|
|
|
240
434
|
}
|
|
241
435
|
};
|
|
242
436
|
}
|
|
437
|
+
if (name === "RepoNotFoundError") {
|
|
438
|
+
return {
|
|
439
|
+
code: "repo_not_found",
|
|
440
|
+
lines: message.split("\n"),
|
|
441
|
+
json: { error: "repo_not_found", detail: message }
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
if (name === "MissingRemoteObjectsError") {
|
|
445
|
+
const missing = err.missing;
|
|
446
|
+
return {
|
|
447
|
+
code: "missing_remote_objects",
|
|
448
|
+
lines: message.split("\n"),
|
|
449
|
+
json: {
|
|
450
|
+
error: "missing_remote_objects",
|
|
451
|
+
detail: message,
|
|
452
|
+
...Array.isArray(missing) ? { missing } : {}
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
if (name === "ObjectIntegrityError" || name === "ObjectWriteMismatchError") {
|
|
457
|
+
return {
|
|
458
|
+
code: "object_integrity",
|
|
459
|
+
lines: message.split("\n"),
|
|
460
|
+
json: { error: "object_integrity", detail: message }
|
|
461
|
+
};
|
|
462
|
+
}
|
|
243
463
|
return {
|
|
244
464
|
code: "error",
|
|
245
465
|
lines: [`rig ${command} failed: ${message}`],
|
|
@@ -665,8 +885,19 @@ async function runRemote(args, deps) {
|
|
|
665
885
|
}
|
|
666
886
|
|
|
667
887
|
// src/cli/push.ts
|
|
888
|
+
function loadPaidSession(deps, relayUrl) {
|
|
889
|
+
return resolvePaidSession({
|
|
890
|
+
env: deps.env,
|
|
891
|
+
cwd: deps.cwd,
|
|
892
|
+
warn: (line) => deps.io.err(line),
|
|
893
|
+
loadStandalone: deps.loadStandalone ?? defaultLoadStandalone,
|
|
894
|
+
...relayUrl !== void 0 ? { relayUrl } : {},
|
|
895
|
+
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {},
|
|
896
|
+
...deps.probeDaemon ? { probeDaemon: deps.probeDaemon } : {}
|
|
897
|
+
});
|
|
898
|
+
}
|
|
668
899
|
var defaultLoadStandalone = async (options) => {
|
|
669
|
-
const mod = await import("../standalone-mode-
|
|
900
|
+
const mod = await import("../standalone-mode-64CKUVU2.js");
|
|
670
901
|
return mod.createStandaloneContext(options);
|
|
671
902
|
};
|
|
672
903
|
function identityReport(ctx) {
|
|
@@ -777,7 +1008,7 @@ function parsePushArgs(args) {
|
|
|
777
1008
|
return flags;
|
|
778
1009
|
}
|
|
779
1010
|
async function runPush(args, deps) {
|
|
780
|
-
const { io: io2
|
|
1011
|
+
const { io: io2 } = deps;
|
|
781
1012
|
let flags;
|
|
782
1013
|
try {
|
|
783
1014
|
flags = parsePushArgs(args);
|
|
@@ -835,40 +1066,74 @@ async function runPush(args, deps) {
|
|
|
835
1066
|
io2.err(singleRelayRefusal(resolved, "Nothing was uploaded or paid."));
|
|
836
1067
|
return 1;
|
|
837
1068
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
}
|
|
845
|
-
|
|
1069
|
+
const session = await loadPaidSession(deps, relaysUsed[0]);
|
|
1070
|
+
const path = session.path;
|
|
1071
|
+
let identity;
|
|
1072
|
+
if (session.path === "standalone") {
|
|
1073
|
+
standaloneCtx = session.ctx;
|
|
1074
|
+
identity = identityReport(standaloneCtx);
|
|
1075
|
+
} else {
|
|
1076
|
+
identity = session.identity;
|
|
1077
|
+
}
|
|
846
1078
|
if (toonConfig.owner && toonConfig.owner !== identity.pubkey) {
|
|
847
1079
|
io2.err(
|
|
848
1080
|
`warning: git config toon.owner (${toonConfig.owner.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.`
|
|
849
1081
|
);
|
|
850
1082
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
feeRates
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
1083
|
+
let plan;
|
|
1084
|
+
let execute;
|
|
1085
|
+
if (session.path === "standalone") {
|
|
1086
|
+
const ctx = session.ctx;
|
|
1087
|
+
const remoteState = await ctx.fetchRemote({
|
|
1088
|
+
ownerPubkey: ctx.ownerPubkey,
|
|
1089
|
+
repoId,
|
|
1090
|
+
relayUrls: relaysUsed
|
|
1091
|
+
});
|
|
1092
|
+
const feeRates = await ctx.publisher.getFeeRates();
|
|
1093
|
+
const pushPlan = await planPush({
|
|
1094
|
+
repoReader: reader,
|
|
1095
|
+
remoteState,
|
|
1096
|
+
feeRates,
|
|
1097
|
+
repoId,
|
|
1098
|
+
refs: refspecs,
|
|
1099
|
+
force: flags.force
|
|
1100
|
+
});
|
|
1101
|
+
plan = serializePushPlan(pushPlan);
|
|
1102
|
+
execute = async () => serializePushResult(
|
|
1103
|
+
pushPlan,
|
|
1104
|
+
await executePush({
|
|
1105
|
+
plan: pushPlan,
|
|
1106
|
+
publisher: ctx.publisher,
|
|
1107
|
+
remoteState,
|
|
1108
|
+
repoReader: reader,
|
|
1109
|
+
relayUrls: relaysUsed
|
|
1110
|
+
})
|
|
1111
|
+
);
|
|
1112
|
+
} else {
|
|
1113
|
+
const client = session.client;
|
|
1114
|
+
plan = await client.gitEstimate({
|
|
1115
|
+
repoPath: repoRoot,
|
|
1116
|
+
repoId,
|
|
1117
|
+
refspecs,
|
|
1118
|
+
force: flags.force,
|
|
1119
|
+
relayUrls: relaysUsed
|
|
1120
|
+
});
|
|
1121
|
+
execute = () => client.gitPush({
|
|
1122
|
+
repoPath: repoRoot,
|
|
1123
|
+
repoId,
|
|
1124
|
+
refspecs,
|
|
1125
|
+
force: flags.force,
|
|
1126
|
+
relayUrls: relaysUsed,
|
|
1127
|
+
confirm: true
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
866
1130
|
const upToDate = plan.refUpdates.every((u) => u.kind === "up-to-date");
|
|
867
1131
|
if (upToDate) {
|
|
868
1132
|
if (flags.json) {
|
|
869
1133
|
io2.emitJson({
|
|
870
1134
|
command: "push",
|
|
871
1135
|
repoId,
|
|
1136
|
+
path,
|
|
872
1137
|
identity,
|
|
873
1138
|
executed: false,
|
|
874
1139
|
upToDate: true,
|
|
@@ -888,6 +1153,7 @@ async function runPush(args, deps) {
|
|
|
888
1153
|
io2.emitJson({
|
|
889
1154
|
command: "push",
|
|
890
1155
|
repoId,
|
|
1156
|
+
path,
|
|
891
1157
|
identity,
|
|
892
1158
|
executed: false,
|
|
893
1159
|
upToDate: false,
|
|
@@ -910,18 +1176,12 @@ async function runPush(args, deps) {
|
|
|
910
1176
|
return 1;
|
|
911
1177
|
}
|
|
912
1178
|
}
|
|
913
|
-
const
|
|
914
|
-
plan: pushPlan,
|
|
915
|
-
publisher: standaloneCtx.publisher,
|
|
916
|
-
remoteState,
|
|
917
|
-
repoReader: reader,
|
|
918
|
-
relayUrls: relaysUsed
|
|
919
|
-
});
|
|
920
|
-
const result = serializePushResult(pushPlan, pushResult);
|
|
1179
|
+
const result = await execute();
|
|
921
1180
|
if (flags.json) {
|
|
922
1181
|
io2.emitJson({
|
|
923
1182
|
command: "push",
|
|
924
1183
|
repoId,
|
|
1184
|
+
path,
|
|
925
1185
|
identity,
|
|
926
1186
|
executed: true,
|
|
927
1187
|
upToDate: false,
|
|
@@ -1210,7 +1470,7 @@ async function loadMoneyContext(deps, options) {
|
|
|
1210
1470
|
if (!money) {
|
|
1211
1471
|
await ctx.stop().catch(() => void 0);
|
|
1212
1472
|
throw new Error(
|
|
1213
|
-
"this standalone loader does not expose money operations \u2014 channel open/close/settle need the
|
|
1473
|
+
"this standalone loader does not expose money operations \u2014 channel open/close/settle need a loader with the money lifecycle"
|
|
1214
1474
|
);
|
|
1215
1475
|
}
|
|
1216
1476
|
return { ctx, money };
|
|
@@ -1519,84 +1779,794 @@ async function stopQuietly(ctx) {
|
|
|
1519
1779
|
}
|
|
1520
1780
|
}
|
|
1521
1781
|
|
|
1782
|
+
// src/cli/clone.ts
|
|
1783
|
+
import { mkdtemp, mkdir, readdir, rename, rm, rmdir } from "fs/promises";
|
|
1784
|
+
import { basename, dirname, join, resolve } from "path";
|
|
1785
|
+
import { parseArgs as parseArgs5 } from "util";
|
|
1786
|
+
var CLONE_USAGE = `Usage: rig clone <relay-url> <owner>/<repo-id> [dir] [options]
|
|
1787
|
+
|
|
1788
|
+
Clone a TOON repository \u2014 FREE (relay reads + Arweave gateway downloads; no
|
|
1789
|
+
payments, no channel, no identity needed). <relay-url> is a ws:// or wss://
|
|
1790
|
+
NIP-01 relay; <owner> is the repo owner's pubkey as npub1\u2026 or 64-char hex;
|
|
1791
|
+
[dir] defaults to <repo-id>.
|
|
1792
|
+
|
|
1793
|
+
Fetches the kind:30617/30618 repository state, downloads every git object
|
|
1794
|
+
the refs need from Arweave gateways (SHA-1 verified \u2014 corrupt content is
|
|
1795
|
+
rejected), and materializes a real git repository: objects via git plumbing,
|
|
1796
|
+
refs, HEAD, checked-out worktree, toon.* config, and the relay preconfigured
|
|
1797
|
+
as the "origin" remote \u2014 so \`rig fetch\`, \`rig push\`, and the issue/pr
|
|
1798
|
+
commands work immediately.
|
|
1799
|
+
|
|
1800
|
+
Note: recently pushed objects can take 10-20 minutes to become fetchable
|
|
1801
|
+
from Arweave gateways. A clone right after a push may report missing
|
|
1802
|
+
objects \u2014 retry after propagation.
|
|
1803
|
+
|
|
1804
|
+
Options:
|
|
1805
|
+
--concurrency <n> parallel gateway downloads (default 8)
|
|
1806
|
+
--json machine-readable result envelope
|
|
1807
|
+
-h, --help show this help`;
|
|
1808
|
+
var RepoNotFoundError = class extends Error {
|
|
1809
|
+
constructor(relay, owner, repoId) {
|
|
1810
|
+
super(
|
|
1811
|
+
`repository 30617:${owner}:${repoId} not found on ${relay} \u2014 no kind:30617 announcement or kind:30618 state event exists there. Check the relay URL, the owner pubkey (npub or hex), and the repo id.`
|
|
1812
|
+
);
|
|
1813
|
+
this.name = "RepoNotFoundError";
|
|
1814
|
+
}
|
|
1815
|
+
};
|
|
1816
|
+
var MissingRemoteObjectsError = class extends Error {
|
|
1817
|
+
constructor(missing, context) {
|
|
1818
|
+
super(missingObjectsMessage(missing, context));
|
|
1819
|
+
this.missing = missing;
|
|
1820
|
+
this.name = "MissingRemoteObjectsError";
|
|
1821
|
+
}
|
|
1822
|
+
missing;
|
|
1823
|
+
};
|
|
1824
|
+
var WS_URL_RE = /^wss?:\/\//i;
|
|
1825
|
+
function parseCloneArgs(args) {
|
|
1826
|
+
const { values, positionals } = parseArgs5({
|
|
1827
|
+
args,
|
|
1828
|
+
options: {
|
|
1829
|
+
json: { type: "boolean", default: false },
|
|
1830
|
+
concurrency: { type: "string" },
|
|
1831
|
+
help: { type: "boolean", short: "h", default: false }
|
|
1832
|
+
},
|
|
1833
|
+
allowPositionals: true
|
|
1834
|
+
});
|
|
1835
|
+
if (values.help) return { help: true };
|
|
1836
|
+
if (positionals.length < 2 || positionals.length > 3) {
|
|
1837
|
+
throw new Error("expected: rig clone <relay-url> <owner>/<repo-id> [dir]");
|
|
1838
|
+
}
|
|
1839
|
+
const [relayUrl, addr, dir] = positionals;
|
|
1840
|
+
if (!WS_URL_RE.test(relayUrl)) {
|
|
1841
|
+
throw new Error(
|
|
1842
|
+
`<relay-url> must be ws:// or wss:// (got ${JSON.stringify(relayUrl)})`
|
|
1843
|
+
);
|
|
1844
|
+
}
|
|
1845
|
+
const slash = addr.indexOf("/");
|
|
1846
|
+
if (slash <= 0 || slash === addr.length - 1) {
|
|
1847
|
+
throw new Error(
|
|
1848
|
+
`expected <owner>/<repo-id> (npub or 64-char hex owner), got ${JSON.stringify(addr)}`
|
|
1849
|
+
);
|
|
1850
|
+
}
|
|
1851
|
+
const owner = ownerToHex(addr.slice(0, slash));
|
|
1852
|
+
const repoId = addr.slice(slash + 1);
|
|
1853
|
+
let concurrency;
|
|
1854
|
+
if (values.concurrency !== void 0) {
|
|
1855
|
+
concurrency = Number.parseInt(values.concurrency, 10);
|
|
1856
|
+
if (!Number.isSafeInteger(concurrency) || concurrency < 1) {
|
|
1857
|
+
throw new Error(`--concurrency must be a positive integer`);
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
return {
|
|
1861
|
+
relayUrl,
|
|
1862
|
+
owner,
|
|
1863
|
+
repoId,
|
|
1864
|
+
dir,
|
|
1865
|
+
concurrency,
|
|
1866
|
+
json: values.json === true
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
async function isUsableDestination(dir) {
|
|
1870
|
+
try {
|
|
1871
|
+
const entries = await readdir(dir);
|
|
1872
|
+
return entries.length === 0;
|
|
1873
|
+
} catch (err) {
|
|
1874
|
+
return err.code === "ENOENT";
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
function initialBranch(headSymref) {
|
|
1878
|
+
if (headSymref?.startsWith("refs/heads/")) {
|
|
1879
|
+
const name = headSymref.slice("refs/heads/".length);
|
|
1880
|
+
if (name && isSafeRefname(headSymref)) return name;
|
|
1881
|
+
}
|
|
1882
|
+
return "main";
|
|
1883
|
+
}
|
|
1884
|
+
async function runClone(args, deps) {
|
|
1885
|
+
const { io: io2 } = deps;
|
|
1886
|
+
let parsed;
|
|
1887
|
+
try {
|
|
1888
|
+
const result = parseCloneArgs(args);
|
|
1889
|
+
if ("help" in result) {
|
|
1890
|
+
io2.out(CLONE_USAGE);
|
|
1891
|
+
return 0;
|
|
1892
|
+
}
|
|
1893
|
+
parsed = result;
|
|
1894
|
+
} catch (err) {
|
|
1895
|
+
io2.err(err instanceof Error ? err.message : String(err));
|
|
1896
|
+
io2.err(CLONE_USAGE);
|
|
1897
|
+
return 2;
|
|
1898
|
+
}
|
|
1899
|
+
const { relayUrl, owner, repoId, json } = parsed;
|
|
1900
|
+
const dest = resolve(deps.cwd, parsed.dir ?? repoId);
|
|
1901
|
+
let tempDir;
|
|
1902
|
+
try {
|
|
1903
|
+
if (!await isUsableDestination(dest)) {
|
|
1904
|
+
throw new Error(
|
|
1905
|
+
`destination path ${JSON.stringify(dest)} already exists and is not an empty directory`
|
|
1906
|
+
);
|
|
1907
|
+
}
|
|
1908
|
+
if (!json) io2.out(`Cloning into '${basename(dest)}'...`);
|
|
1909
|
+
const remoteState = await fetchRemoteState({
|
|
1910
|
+
relayUrls: [relayUrl],
|
|
1911
|
+
ownerPubkey: owner,
|
|
1912
|
+
repoId,
|
|
1913
|
+
...deps.webSocketFactory ? { webSocketFactory: deps.webSocketFactory } : {},
|
|
1914
|
+
...deps.resolveSha ? { resolveSha: deps.resolveSha } : {}
|
|
1915
|
+
});
|
|
1916
|
+
if (!remoteState.announced && remoteState.refsEvent === null) {
|
|
1917
|
+
throw new RepoNotFoundError(relayUrl, owner, repoId);
|
|
1918
|
+
}
|
|
1919
|
+
const refs = /* @__PURE__ */ new Map();
|
|
1920
|
+
for (const [refname, sha] of remoteState.refs) {
|
|
1921
|
+
if (!isSafeRefname(refname)) {
|
|
1922
|
+
io2.err(
|
|
1923
|
+
`warning: skipping unsafe ref name from relay: ${JSON.stringify(refname)}`
|
|
1924
|
+
);
|
|
1925
|
+
continue;
|
|
1926
|
+
}
|
|
1927
|
+
refs.set(refname, sha);
|
|
1928
|
+
}
|
|
1929
|
+
if (refs.size === 0) {
|
|
1930
|
+
io2.err(
|
|
1931
|
+
"warning: the repository has no refs yet (announced but never pushed) \u2014 cloning an empty repository"
|
|
1932
|
+
);
|
|
1933
|
+
}
|
|
1934
|
+
const tips = [...new Set(refs.values())];
|
|
1935
|
+
const collected = await collectRepoObjects({
|
|
1936
|
+
tips,
|
|
1937
|
+
shaToTxId: remoteState.shaToTxId,
|
|
1938
|
+
resolveMissing: (shas) => remoteState.resolveMissing(shas),
|
|
1939
|
+
...deps.fetchFn ? { fetchFn: deps.fetchFn } : {},
|
|
1940
|
+
...parsed.concurrency !== void 0 ? { concurrency: parsed.concurrency } : {}
|
|
1941
|
+
});
|
|
1942
|
+
if (collected.missing.length > 0) {
|
|
1943
|
+
throw new MissingRemoteObjectsError(
|
|
1944
|
+
collected.missing,
|
|
1945
|
+
`cannot clone ${owner.slice(0, 8)}\u2026/${repoId}`
|
|
1946
|
+
);
|
|
1947
|
+
}
|
|
1948
|
+
for (const { sha, txId } of collected.skippedUnavailable) {
|
|
1949
|
+
io2.err(
|
|
1950
|
+
`warning: unreachable object ${sha} (tx ${txId}) could not be downloaded \u2014 not needed by any ref, continuing`
|
|
1951
|
+
);
|
|
1952
|
+
}
|
|
1953
|
+
const parent = dirname(dest);
|
|
1954
|
+
await mkdir(parent, { recursive: true });
|
|
1955
|
+
tempDir = await mkdtemp(join(parent, `.${basename(dest)}.rig-clone-`));
|
|
1956
|
+
await runGit(tempDir, [
|
|
1957
|
+
"init",
|
|
1958
|
+
"--quiet",
|
|
1959
|
+
`--initial-branch=${initialBranch(remoteState.headSymref)}`
|
|
1960
|
+
]);
|
|
1961
|
+
const written = await writeGitObjects(tempDir, collected.objects.values());
|
|
1962
|
+
for (const [refname, sha] of refs) {
|
|
1963
|
+
await updateRef(tempDir, refname, sha);
|
|
1964
|
+
if (refname.startsWith("refs/heads/")) {
|
|
1965
|
+
const branch = refname.slice("refs/heads/".length);
|
|
1966
|
+
await updateRef(tempDir, `refs/remotes/origin/${branch}`, sha);
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
const head = remoteState.headSymref !== null && isSafeRefname(remoteState.headSymref) && refs.has(remoteState.headSymref) ? remoteState.headSymref : [...refs.keys()].find((r) => r.startsWith("refs/heads/")) ?? null;
|
|
1970
|
+
if (head !== null) {
|
|
1971
|
+
await setHeadSymref(tempDir, head);
|
|
1972
|
+
await runGit(tempDir, ["reset", "--hard", "--quiet"]);
|
|
1973
|
+
}
|
|
1974
|
+
await runGit(tempDir, ["config", "toon.repoid", repoId]);
|
|
1975
|
+
await runGit(tempDir, ["config", "toon.owner", owner]);
|
|
1976
|
+
await runGit(tempDir, ["remote", "add", "origin", relayUrl]);
|
|
1977
|
+
if (head !== null) {
|
|
1978
|
+
const branch = head.slice("refs/heads/".length);
|
|
1979
|
+
await runGit(tempDir, ["config", `branch.${branch}.remote`, "origin"]);
|
|
1980
|
+
await runGit(tempDir, ["config", `branch.${branch}.merge`, head]);
|
|
1981
|
+
}
|
|
1982
|
+
try {
|
|
1983
|
+
await rmdir(dest);
|
|
1984
|
+
} catch {
|
|
1985
|
+
}
|
|
1986
|
+
await rename(tempDir, dest);
|
|
1987
|
+
tempDir = void 0;
|
|
1988
|
+
if (json) {
|
|
1989
|
+
io2.emitJson({
|
|
1990
|
+
command: "clone",
|
|
1991
|
+
repoAddr: { ownerPubkey: owner, repoId },
|
|
1992
|
+
relay: relayUrl,
|
|
1993
|
+
directory: dest,
|
|
1994
|
+
head,
|
|
1995
|
+
refs: Object.fromEntries(refs),
|
|
1996
|
+
name: remoteState.name,
|
|
1997
|
+
objectsDownloaded: written,
|
|
1998
|
+
executed: true
|
|
1999
|
+
});
|
|
2000
|
+
} else {
|
|
2001
|
+
io2.out(`Downloaded ${written} object(s) from Arweave (SHA-1 verified).`);
|
|
2002
|
+
for (const [refname, sha] of refs) {
|
|
2003
|
+
io2.out(` ${sha.slice(0, 7)} ${refname}`);
|
|
2004
|
+
}
|
|
2005
|
+
if (head !== null) io2.out(`HEAD is now at ${head}`);
|
|
2006
|
+
io2.out(
|
|
2007
|
+
`Configured toon.repoid=${repoId}, toon.owner=${owner.slice(0, 8)}\u2026, origin \u2192 ${relayUrl}`
|
|
2008
|
+
);
|
|
2009
|
+
io2.out("Done.");
|
|
2010
|
+
}
|
|
2011
|
+
return 0;
|
|
2012
|
+
} catch (err) {
|
|
2013
|
+
return emitCliError(io2, json, "clone", err);
|
|
2014
|
+
} finally {
|
|
2015
|
+
if (tempDir !== void 0) {
|
|
2016
|
+
await rm(tempDir, { recursive: true, force: true }).catch(
|
|
2017
|
+
() => void 0
|
|
2018
|
+
);
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
|
|
1522
2023
|
// src/cli/events.ts
|
|
1523
2024
|
import { readFile } from "fs/promises";
|
|
1524
|
-
import { parseArgs as
|
|
2025
|
+
import { parseArgs as parseArgs7 } from "util";
|
|
2026
|
+
import {
|
|
2027
|
+
REPOSITORY_ANNOUNCEMENT_KIND as REPOSITORY_ANNOUNCEMENT_KIND2,
|
|
2028
|
+
STATUS_APPLIED_KIND as STATUS_APPLIED_KIND2,
|
|
2029
|
+
STATUS_CLOSED_KIND as STATUS_CLOSED_KIND2,
|
|
2030
|
+
STATUS_DRAFT_KIND as STATUS_DRAFT_KIND2,
|
|
2031
|
+
STATUS_OPEN_KIND as STATUS_OPEN_KIND2
|
|
2032
|
+
} from "@toon-protocol/core/nip34";
|
|
2033
|
+
|
|
2034
|
+
// src/cli/tracker.ts
|
|
2035
|
+
import { parseArgs as parseArgs6 } from "util";
|
|
1525
2036
|
import {
|
|
2037
|
+
ISSUE_KIND,
|
|
2038
|
+
PATCH_KIND,
|
|
1526
2039
|
REPOSITORY_ANNOUNCEMENT_KIND,
|
|
1527
2040
|
STATUS_APPLIED_KIND,
|
|
1528
2041
|
STATUS_CLOSED_KIND,
|
|
1529
2042
|
STATUS_DRAFT_KIND,
|
|
1530
2043
|
STATUS_OPEN_KIND
|
|
1531
2044
|
} from "@toon-protocol/core/nip34";
|
|
1532
|
-
var
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
};
|
|
1538
|
-
var COMMON_FLAGS_USAGE = ` --repo-id <id> repository id / NIP-34 d-tag (default: git config
|
|
1539
|
-
toon.repoid \u2014 run \`rig init\` to set it)
|
|
1540
|
-
--owner <pubkey> repository owner pubkey, 64-char hex (default: git config
|
|
1541
|
-
toon.owner, then the active identity)
|
|
1542
|
-
--remote <name> publish via this configured git remote (default: the
|
|
1543
|
-
"origin" remote \u2014 \`rig remote add origin <relay-url>\`)
|
|
1544
|
-
--relay <url> ad-hoc relay override (exactly one) \u2014 bypasses the
|
|
1545
|
-
configured remotes. The deprecated v0.1 \`git config
|
|
1546
|
-
toon.relay\` still works as a fallback, with a nudge
|
|
1547
|
-
--yes skip the fee confirmation (required when not a TTY)
|
|
1548
|
-
--json machine-readable receipt; without --yes it is a pure
|
|
1549
|
-
estimate (nothing published, exit 0)
|
|
2045
|
+
var READ_COMMON_FLAGS = ` --repo-id <id> repository id / NIP-34 d-tag (default: git config)
|
|
2046
|
+
--owner <pubkey> repository owner (npub or 64-char hex; default: git config)
|
|
2047
|
+
--remote <name> read via this configured git remote (default: origin)
|
|
2048
|
+
--relay <url> ad-hoc relay override; repeatable \u2014 reads are merged
|
|
2049
|
+
--json machine-readable envelope
|
|
1550
2050
|
-h, --help show this help`;
|
|
1551
|
-
var
|
|
1552
|
-
|
|
1553
|
-
File an issue (kind:1621) against a TOON repo \u2014 a paid publish; writes are
|
|
1554
|
-
permanent and non-refundable. The repo address (30617:<owner>:<repoId>) comes
|
|
1555
|
-
from the toon.* git config keys \`rig init\` writes.
|
|
2051
|
+
var ISSUE_LIST_USAGE = `Usage: rig issue list [--state open|closed|all] [options]
|
|
1556
2052
|
|
|
1557
|
-
|
|
2053
|
+
List the repo's issues (kind:1621) with their derived state \u2014 FREE (relay
|
|
2054
|
+
reads only). State comes from kind:1630-1633 status events: the latest status
|
|
2055
|
+
wins; an issue with no status events is open.
|
|
1558
2056
|
|
|
1559
2057
|
Options:
|
|
1560
|
-
--
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
--label <label> label (t tag); repeatable
|
|
1564
|
-
${COMMON_FLAGS_USAGE}`;
|
|
1565
|
-
var COMMENT_USAGE = `Usage: rig comment <root-event-id> --body <text> [options]
|
|
2058
|
+
--state <state> open | closed | all (default: all)
|
|
2059
|
+
${READ_COMMON_FLAGS}`;
|
|
2060
|
+
var ISSUE_SHOW_USAGE = `Usage: rig issue show <event-id> [options]
|
|
1566
2061
|
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
2062
|
+
Show one issue (kind:1621): metadata, derived state, body, and its
|
|
2063
|
+
kind:1622 comments \u2014 FREE (relay reads only). <event-id> is the 64-char hex
|
|
2064
|
+
id \`rig issue create\` printed (also visible in \`rig issue list\`).
|
|
1570
2065
|
|
|
1571
2066
|
Options:
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
tag; default: the repo owner)
|
|
1575
|
-
--marker <root|reply> e-tag marker (default: root \u2014 commenting directly
|
|
1576
|
-
on the issue/patch)
|
|
1577
|
-
${COMMON_FLAGS_USAGE}`;
|
|
1578
|
-
var PR_CREATE_USAGE = `Usage: rig pr create --title <title> (--range <range> | --patch-file <path>) [options]
|
|
2067
|
+
${READ_COMMON_FLAGS}`;
|
|
2068
|
+
var PR_LIST_USAGE = `Usage: rig pr list [--state open|applied|closed|draft|all] [options]
|
|
1579
2069
|
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
commit/parent-commit tags; --patch-file publishes a pre-generated patch
|
|
1584
|
-
verbatim. A multi-commit range publishes ONE kind:1617 event carrying the
|
|
1585
|
-
full series text \u2014 cover-letter threading (one event per commit) is out of
|
|
1586
|
-
scope in v1.
|
|
2070
|
+
List the repo's patches/PRs (kind:1617) with their derived state \u2014 FREE
|
|
2071
|
+
(relay reads only). State comes from kind:1630-1633 status events: the
|
|
2072
|
+
latest status wins; a patch with no status events is open.
|
|
1587
2073
|
|
|
1588
2074
|
Options:
|
|
1589
|
-
--
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
--patch-file <path> literal patch text to publish
|
|
1593
|
-
--branch <name> branch name (t tag)
|
|
1594
|
-
${COMMON_FLAGS_USAGE}`;
|
|
1595
|
-
var PR_STATUS_USAGE = `Usage: rig pr status <target-event-id> <open|applied|closed|draft> [options]
|
|
2075
|
+
--state <state> open | applied | closed | draft | all (default: all)
|
|
2076
|
+
${READ_COMMON_FLAGS}`;
|
|
2077
|
+
var PR_SHOW_USAGE = `Usage: rig pr show <event-id> [options]
|
|
1596
2078
|
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
2079
|
+
Show one patch/PR (kind:1617): metadata, derived state, the FULL patch text
|
|
2080
|
+
(real \`git format-patch\` output \u2014 pipe it to \`git am\` to apply), and its
|
|
2081
|
+
kind:1622 comments \u2014 FREE (relay reads only).
|
|
2082
|
+
|
|
2083
|
+
Options:
|
|
2084
|
+
${READ_COMMON_FLAGS}`;
|
|
2085
|
+
var STATUS_BY_KIND = {
|
|
2086
|
+
[STATUS_OPEN_KIND]: "open",
|
|
2087
|
+
[STATUS_APPLIED_KIND]: "applied",
|
|
2088
|
+
[STATUS_CLOSED_KIND]: "closed",
|
|
2089
|
+
[STATUS_DRAFT_KIND]: "draft"
|
|
2090
|
+
};
|
|
2091
|
+
var STATUS_KINDS = [
|
|
2092
|
+
STATUS_OPEN_KIND,
|
|
2093
|
+
STATUS_APPLIED_KIND,
|
|
2094
|
+
STATUS_CLOSED_KIND,
|
|
2095
|
+
STATUS_DRAFT_KIND
|
|
2096
|
+
];
|
|
2097
|
+
function tagValue(tags, name) {
|
|
2098
|
+
return tags.find((t) => t[0] === name)?.[1];
|
|
2099
|
+
}
|
|
2100
|
+
function tagValues(tags, name) {
|
|
2101
|
+
return tags.filter((t) => t[0] === name && t[1]).map((t) => t[1]);
|
|
2102
|
+
}
|
|
2103
|
+
function deriveStatus(targetEventId, statusEvents) {
|
|
2104
|
+
let winner = null;
|
|
2105
|
+
for (const event of statusEvents) {
|
|
2106
|
+
if (STATUS_BY_KIND[event.kind] === void 0) continue;
|
|
2107
|
+
if (!event.tags.some((t) => t[0] === "e" && t[1] === targetEventId))
|
|
2108
|
+
continue;
|
|
2109
|
+
if (winner === null || event.created_at > winner.created_at || event.created_at === winner.created_at && event.id < winner.id) {
|
|
2110
|
+
winner = event;
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
return winner === null ? "open" : STATUS_BY_KIND[winner.kind];
|
|
2114
|
+
}
|
|
2115
|
+
var RELAY_TIMEOUT_MS = 1e4;
|
|
2116
|
+
var HEX64_RE = /^[0-9a-f]{64}$/;
|
|
2117
|
+
var WS_URL_RE2 = /^wss?:\/\//i;
|
|
2118
|
+
function defaultWebSocketFactory(url) {
|
|
2119
|
+
const ctor = globalThis.WebSocket;
|
|
2120
|
+
if (!ctor) {
|
|
2121
|
+
throw new Error(
|
|
2122
|
+
"No global WebSocket constructor (Node >= 22 required) \u2014 pass webSocketFactory"
|
|
2123
|
+
);
|
|
2124
|
+
}
|
|
2125
|
+
return new ctor(url);
|
|
2126
|
+
}
|
|
2127
|
+
async function queryAll(relays, filters, webSocketFactory) {
|
|
2128
|
+
const jobs = relays.flatMap(
|
|
2129
|
+
(relay) => filters.map(
|
|
2130
|
+
(filter) => queryRelay(relay, filter, RELAY_TIMEOUT_MS, webSocketFactory)
|
|
2131
|
+
)
|
|
2132
|
+
);
|
|
2133
|
+
const results = await Promise.allSettled(jobs);
|
|
2134
|
+
const byId = /* @__PURE__ */ new Map();
|
|
2135
|
+
let failures = 0;
|
|
2136
|
+
let firstError;
|
|
2137
|
+
for (const result of results) {
|
|
2138
|
+
if (result.status === "rejected") {
|
|
2139
|
+
failures += 1;
|
|
2140
|
+
firstError ??= result.reason;
|
|
2141
|
+
continue;
|
|
2142
|
+
}
|
|
2143
|
+
for (const event of result.value) {
|
|
2144
|
+
if (typeof event.id === "string" && !byId.has(event.id)) {
|
|
2145
|
+
byId.set(event.id, event);
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
if (failures === results.length && results.length > 0) {
|
|
2150
|
+
throw firstError instanceof Error ? firstError : new Error(String(firstError));
|
|
2151
|
+
}
|
|
2152
|
+
return byId;
|
|
2153
|
+
}
|
|
2154
|
+
var TRACKER_OPTIONS = {
|
|
2155
|
+
json: { type: "boolean", default: false },
|
|
2156
|
+
state: { type: "string" },
|
|
2157
|
+
relay: { type: "string", multiple: true },
|
|
2158
|
+
remote: { type: "string" },
|
|
2159
|
+
"repo-id": { type: "string" },
|
|
2160
|
+
owner: { type: "string" },
|
|
2161
|
+
help: { type: "boolean", short: "h", default: false }
|
|
2162
|
+
};
|
|
2163
|
+
function pickTrackerFlags(values) {
|
|
2164
|
+
const flags = {
|
|
2165
|
+
json: values["json"] === true,
|
|
2166
|
+
help: values["help"] === true,
|
|
2167
|
+
relay: Array.isArray(values["relay"]) ? values["relay"] : []
|
|
2168
|
+
};
|
|
2169
|
+
if (typeof values["state"] === "string") flags.state = values["state"];
|
|
2170
|
+
if (typeof values["remote"] === "string") flags.remote = values["remote"];
|
|
2171
|
+
if (typeof values["repo-id"] === "string") flags.repoId = values["repo-id"];
|
|
2172
|
+
if (typeof values["owner"] === "string")
|
|
2173
|
+
flags.owner = ownerToHex(values["owner"]);
|
|
2174
|
+
return flags;
|
|
2175
|
+
}
|
|
2176
|
+
async function resolveTrackerContext(flags, deps, needRepoAddr) {
|
|
2177
|
+
let repoRoot;
|
|
2178
|
+
let toonConfig = {
|
|
2179
|
+
relays: []
|
|
2180
|
+
};
|
|
2181
|
+
try {
|
|
2182
|
+
repoRoot = await resolveRepoRoot(deps.cwd);
|
|
2183
|
+
toonConfig = await readToonConfig(repoRoot);
|
|
2184
|
+
} catch {
|
|
2185
|
+
}
|
|
2186
|
+
let repoAddr = null;
|
|
2187
|
+
if (needRepoAddr) {
|
|
2188
|
+
const repoId = flags.repoId ?? toonConfig.repoId;
|
|
2189
|
+
if (!repoId) throw new UnconfiguredRepoAddressError("repository id");
|
|
2190
|
+
const owner = flags.owner ?? toonConfig.owner;
|
|
2191
|
+
if (!owner) throw new UnconfiguredRepoAddressError("repository owner");
|
|
2192
|
+
repoAddr = { ownerPubkey: owner, repoId };
|
|
2193
|
+
}
|
|
2194
|
+
const resolved = await resolveRelays({
|
|
2195
|
+
relayFlags: flags.relay,
|
|
2196
|
+
remoteName: flags.remote,
|
|
2197
|
+
repoRoot,
|
|
2198
|
+
toonRelays: toonConfig.relays
|
|
2199
|
+
});
|
|
2200
|
+
const wsRelays = resolved.relays.filter((url) => WS_URL_RE2.test(url));
|
|
2201
|
+
if (wsRelays.length === 0) {
|
|
2202
|
+
throw new InvalidRelayUrlError(
|
|
2203
|
+
resolved.relays[0] ?? "",
|
|
2204
|
+
"reads need a ws:// or wss:// relay"
|
|
2205
|
+
);
|
|
2206
|
+
}
|
|
2207
|
+
return {
|
|
2208
|
+
relays: wsRelays,
|
|
2209
|
+
repoAddr,
|
|
2210
|
+
webSocketFactory: deps.webSocketFactory ?? defaultWebSocketFactory
|
|
2211
|
+
};
|
|
2212
|
+
}
|
|
2213
|
+
function repoATag(addr) {
|
|
2214
|
+
return `${REPOSITORY_ANNOUNCEMENT_KIND}:${addr.ownerPubkey}:${addr.repoId}`;
|
|
2215
|
+
}
|
|
2216
|
+
function parseTrackerItem(event, status) {
|
|
2217
|
+
const item = {
|
|
2218
|
+
eventId: event.id,
|
|
2219
|
+
kind: event.kind,
|
|
2220
|
+
title: tagValue(event.tags, "subject") ?? (event.content.split("\n")[0] ?? "").slice(0, 120),
|
|
2221
|
+
status,
|
|
2222
|
+
authorPubkey: event.pubkey,
|
|
2223
|
+
createdAt: event.created_at,
|
|
2224
|
+
labels: tagValues(event.tags, "t"),
|
|
2225
|
+
content: event.content
|
|
2226
|
+
};
|
|
2227
|
+
if (event.kind === PATCH_KIND) {
|
|
2228
|
+
item.commitShas = tagValues(event.tags, "commit");
|
|
2229
|
+
const branch = tagValue(event.tags, "branch");
|
|
2230
|
+
if (branch !== void 0) item.branch = branch;
|
|
2231
|
+
const description = tagValue(event.tags, "description");
|
|
2232
|
+
if (description !== void 0) item.description = description;
|
|
2233
|
+
}
|
|
2234
|
+
return item;
|
|
2235
|
+
}
|
|
2236
|
+
function isoDate(createdAt) {
|
|
2237
|
+
return new Date(createdAt * 1e3).toISOString().slice(0, 10);
|
|
2238
|
+
}
|
|
2239
|
+
async function fetchItems(ctx, kind) {
|
|
2240
|
+
const aTag = repoATag(
|
|
2241
|
+
ctx.repoAddr
|
|
2242
|
+
);
|
|
2243
|
+
const [itemEvents, aStatusEvents] = await Promise.all([
|
|
2244
|
+
queryAll(
|
|
2245
|
+
ctx.relays,
|
|
2246
|
+
[{ kinds: [kind], "#a": [aTag] }],
|
|
2247
|
+
ctx.webSocketFactory
|
|
2248
|
+
),
|
|
2249
|
+
queryAll(
|
|
2250
|
+
ctx.relays,
|
|
2251
|
+
[{ kinds: STATUS_KINDS, "#a": [aTag] }],
|
|
2252
|
+
ctx.webSocketFactory
|
|
2253
|
+
)
|
|
2254
|
+
]);
|
|
2255
|
+
const items = [...itemEvents.values()].filter(
|
|
2256
|
+
(e) => e.kind === kind && e.tags.some((t) => t[0] === "a" && t[1] === aTag)
|
|
2257
|
+
);
|
|
2258
|
+
const statuses = new Map(aStatusEvents);
|
|
2259
|
+
if (items.length > 0) {
|
|
2260
|
+
const byE = await queryAll(
|
|
2261
|
+
ctx.relays,
|
|
2262
|
+
[{ kinds: STATUS_KINDS, "#e": items.map((i) => i.id) }],
|
|
2263
|
+
ctx.webSocketFactory
|
|
2264
|
+
);
|
|
2265
|
+
for (const [id, event] of byE)
|
|
2266
|
+
if (!statuses.has(id)) statuses.set(id, event);
|
|
2267
|
+
}
|
|
2268
|
+
return items.map(
|
|
2269
|
+
(event) => parseTrackerItem(event, deriveStatus(event.id, statuses.values()))
|
|
2270
|
+
).sort((a, b) => b.createdAt - a.createdAt);
|
|
2271
|
+
}
|
|
2272
|
+
async function fetchItem(ctx, eventId, expectedKind) {
|
|
2273
|
+
const found = await queryAll(
|
|
2274
|
+
ctx.relays,
|
|
2275
|
+
[{ ids: [eventId] }],
|
|
2276
|
+
ctx.webSocketFactory
|
|
2277
|
+
);
|
|
2278
|
+
const event = found.get(eventId);
|
|
2279
|
+
if (!event) {
|
|
2280
|
+
throw new Error(`event ${eventId} not found on ${ctx.relays.join(", ")}`);
|
|
2281
|
+
}
|
|
2282
|
+
if (event.kind !== expectedKind) {
|
|
2283
|
+
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`)" : "";
|
|
2284
|
+
throw new Error(
|
|
2285
|
+
`event ${eventId} has kind ${event.kind}, expected ${expectedKind}${hint}`
|
|
2286
|
+
);
|
|
2287
|
+
}
|
|
2288
|
+
const [statusEvents, commentEvents] = await Promise.all([
|
|
2289
|
+
queryAll(
|
|
2290
|
+
ctx.relays,
|
|
2291
|
+
[{ kinds: STATUS_KINDS, "#e": [eventId] }],
|
|
2292
|
+
ctx.webSocketFactory
|
|
2293
|
+
),
|
|
2294
|
+
queryAll(
|
|
2295
|
+
ctx.relays,
|
|
2296
|
+
[{ kinds: [COMMENT_KIND], "#e": [eventId] }],
|
|
2297
|
+
ctx.webSocketFactory
|
|
2298
|
+
)
|
|
2299
|
+
]);
|
|
2300
|
+
const comments = [...commentEvents.values()].filter(
|
|
2301
|
+
(e) => e.kind === COMMENT_KIND && e.tags.some((t) => t[0] === "e" && t[1] === eventId)
|
|
2302
|
+
).sort((a, b) => a.created_at - b.created_at).map((e) => ({
|
|
2303
|
+
eventId: e.id,
|
|
2304
|
+
authorPubkey: e.pubkey,
|
|
2305
|
+
createdAt: e.created_at,
|
|
2306
|
+
content: e.content
|
|
2307
|
+
}));
|
|
2308
|
+
return {
|
|
2309
|
+
item: parseTrackerItem(event, deriveStatus(eventId, statusEvents.values())),
|
|
2310
|
+
comments,
|
|
2311
|
+
repoATag: tagValue(event.tags, "a") ?? null
|
|
2312
|
+
};
|
|
2313
|
+
}
|
|
2314
|
+
async function runList(args, deps, spec) {
|
|
2315
|
+
const { io: io2 } = deps;
|
|
2316
|
+
let flags;
|
|
2317
|
+
try {
|
|
2318
|
+
const { values, positionals } = parseArgs6({
|
|
2319
|
+
args,
|
|
2320
|
+
options: TRACKER_OPTIONS,
|
|
2321
|
+
allowPositionals: true
|
|
2322
|
+
});
|
|
2323
|
+
if (positionals.length > 0) {
|
|
2324
|
+
throw new Error(`rig ${spec.command} takes no positional arguments`);
|
|
2325
|
+
}
|
|
2326
|
+
flags = pickTrackerFlags(values);
|
|
2327
|
+
if (flags.state !== void 0 && flags.state !== "all" && !spec.states.includes(flags.state)) {
|
|
2328
|
+
throw new Error(
|
|
2329
|
+
`--state must be one of ${[...spec.states, "all"].join(" | ")} (got ${JSON.stringify(flags.state)})`
|
|
2330
|
+
);
|
|
2331
|
+
}
|
|
2332
|
+
} catch (err) {
|
|
2333
|
+
io2.err(err instanceof Error ? err.message : String(err));
|
|
2334
|
+
io2.err(spec.usage);
|
|
2335
|
+
return 2;
|
|
2336
|
+
}
|
|
2337
|
+
if (flags.help) {
|
|
2338
|
+
io2.out(spec.usage);
|
|
2339
|
+
return 0;
|
|
2340
|
+
}
|
|
2341
|
+
try {
|
|
2342
|
+
const ctx = await resolveTrackerContext(flags, deps, true);
|
|
2343
|
+
const all = await fetchItems(ctx, spec.kind);
|
|
2344
|
+
const state = flags.state ?? "all";
|
|
2345
|
+
const items = state === "all" ? all : all.filter((i) => i.status === state);
|
|
2346
|
+
if (flags.json) {
|
|
2347
|
+
io2.emitJson({
|
|
2348
|
+
command: spec.command,
|
|
2349
|
+
repoAddr: ctx.repoAddr,
|
|
2350
|
+
relays: ctx.relays,
|
|
2351
|
+
state,
|
|
2352
|
+
count: items.length,
|
|
2353
|
+
[spec.jsonKey]: items
|
|
2354
|
+
});
|
|
2355
|
+
return 0;
|
|
2356
|
+
}
|
|
2357
|
+
if (items.length === 0) {
|
|
2358
|
+
io2.out(
|
|
2359
|
+
`no ${state === "all" ? "" : `${state} `}${spec.jsonKey} found for ${repoATag(ctx.repoAddr)}`
|
|
2360
|
+
);
|
|
2361
|
+
return 0;
|
|
2362
|
+
}
|
|
2363
|
+
for (const item of items) {
|
|
2364
|
+
const labels = item.labels.length > 0 ? ` [${item.labels.join(", ")}]` : "";
|
|
2365
|
+
io2.out(
|
|
2366
|
+
`${item.status.padEnd(7)} ${item.eventId.slice(0, 8)} ${item.title} (${item.authorPubkey.slice(0, 8)}, ${isoDate(item.createdAt)})${labels}`
|
|
2367
|
+
);
|
|
2368
|
+
}
|
|
2369
|
+
io2.out(
|
|
2370
|
+
`${items.length} ${spec.jsonKey}${state === "all" ? "" : ` (${state})`}`
|
|
2371
|
+
);
|
|
2372
|
+
return 0;
|
|
2373
|
+
} catch (err) {
|
|
2374
|
+
return emitCliError(io2, flags.json, spec.command, err);
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
async function runShow(args, deps, spec) {
|
|
2378
|
+
const { io: io2 } = deps;
|
|
2379
|
+
let flags;
|
|
2380
|
+
let eventId;
|
|
2381
|
+
try {
|
|
2382
|
+
const { values, positionals } = parseArgs6({
|
|
2383
|
+
args,
|
|
2384
|
+
options: TRACKER_OPTIONS,
|
|
2385
|
+
allowPositionals: true
|
|
2386
|
+
});
|
|
2387
|
+
flags = pickTrackerFlags(values);
|
|
2388
|
+
if (flags.help) {
|
|
2389
|
+
io2.out(spec.usage);
|
|
2390
|
+
return 0;
|
|
2391
|
+
}
|
|
2392
|
+
if (positionals.length !== 1) {
|
|
2393
|
+
throw new Error("expected exactly one <event-id>");
|
|
2394
|
+
}
|
|
2395
|
+
eventId = positionals[0];
|
|
2396
|
+
if (!HEX64_RE.test(eventId)) {
|
|
2397
|
+
throw new Error(
|
|
2398
|
+
`<event-id> must be a 64-char lowercase hex id (got ${JSON.stringify(eventId)})`
|
|
2399
|
+
);
|
|
2400
|
+
}
|
|
2401
|
+
} catch (err) {
|
|
2402
|
+
io2.err(err instanceof Error ? err.message : String(err));
|
|
2403
|
+
io2.err(spec.usage);
|
|
2404
|
+
return 2;
|
|
2405
|
+
}
|
|
2406
|
+
try {
|
|
2407
|
+
const ctx = await resolveTrackerContext(flags, deps, false);
|
|
2408
|
+
const shown = await fetchItem(ctx, eventId, spec.kind);
|
|
2409
|
+
if (flags.json) {
|
|
2410
|
+
io2.emitJson({
|
|
2411
|
+
command: spec.command,
|
|
2412
|
+
relays: ctx.relays,
|
|
2413
|
+
repoATag: shown.repoATag,
|
|
2414
|
+
[spec.jsonKey]: shown.item,
|
|
2415
|
+
comments: shown.comments
|
|
2416
|
+
});
|
|
2417
|
+
return 0;
|
|
2418
|
+
}
|
|
2419
|
+
const { item } = shown;
|
|
2420
|
+
io2.out(`${spec.jsonKey} ${item.eventId}`);
|
|
2421
|
+
io2.out(`Title: ${item.title}`);
|
|
2422
|
+
io2.out(`Status: ${item.status}`);
|
|
2423
|
+
io2.out(`Author: ${item.authorPubkey}`);
|
|
2424
|
+
io2.out(`Date: ${isoDate(item.createdAt)}`);
|
|
2425
|
+
if (item.labels.length > 0) io2.out(`Labels: ${item.labels.join(", ")}`);
|
|
2426
|
+
if (item.branch !== void 0) io2.out(`Branch: ${item.branch}`);
|
|
2427
|
+
if (item.commitShas && item.commitShas.length > 0) {
|
|
2428
|
+
io2.out(`Commits: ${item.commitShas.join(", ")}`);
|
|
2429
|
+
}
|
|
2430
|
+
if (shown.repoATag !== null) io2.out(`Repo: ${shown.repoATag}`);
|
|
2431
|
+
if (item.description !== void 0) {
|
|
2432
|
+
io2.out("");
|
|
2433
|
+
io2.out("Body:");
|
|
2434
|
+
for (const line of item.description.split("\n")) io2.out(line);
|
|
2435
|
+
}
|
|
2436
|
+
io2.out("");
|
|
2437
|
+
io2.out(`${spec.bodyLabel}:`);
|
|
2438
|
+
for (const line of item.content.split("\n")) io2.out(line);
|
|
2439
|
+
io2.out("");
|
|
2440
|
+
io2.out(`Comments (${shown.comments.length}):`);
|
|
2441
|
+
for (const comment of shown.comments) {
|
|
2442
|
+
io2.out(
|
|
2443
|
+
`--- ${comment.eventId.slice(0, 8)} by ${comment.authorPubkey.slice(0, 8)} on ${isoDate(comment.createdAt)}`
|
|
2444
|
+
);
|
|
2445
|
+
for (const line of comment.content.split("\n")) io2.out(line);
|
|
2446
|
+
}
|
|
2447
|
+
return 0;
|
|
2448
|
+
} catch (err) {
|
|
2449
|
+
return emitCliError(io2, flags.json, spec.command, err);
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
var ISSUE_STATES = ["open", "closed", "applied", "draft"];
|
|
2453
|
+
var PR_STATES = ["open", "applied", "closed", "draft"];
|
|
2454
|
+
function runIssueList(args, deps) {
|
|
2455
|
+
return runList(args, deps, {
|
|
2456
|
+
command: "issue list",
|
|
2457
|
+
kind: ISSUE_KIND,
|
|
2458
|
+
usage: ISSUE_LIST_USAGE,
|
|
2459
|
+
states: ISSUE_STATES,
|
|
2460
|
+
jsonKey: "issues"
|
|
2461
|
+
});
|
|
2462
|
+
}
|
|
2463
|
+
function runIssueShow(args, deps) {
|
|
2464
|
+
return runShow(args, deps, {
|
|
2465
|
+
command: "issue show",
|
|
2466
|
+
kind: ISSUE_KIND,
|
|
2467
|
+
usage: ISSUE_SHOW_USAGE,
|
|
2468
|
+
jsonKey: "issue",
|
|
2469
|
+
bodyLabel: "Body"
|
|
2470
|
+
});
|
|
2471
|
+
}
|
|
2472
|
+
function runPrList(args, deps) {
|
|
2473
|
+
return runList(args, deps, {
|
|
2474
|
+
command: "pr list",
|
|
2475
|
+
kind: PATCH_KIND,
|
|
2476
|
+
usage: PR_LIST_USAGE,
|
|
2477
|
+
states: PR_STATES,
|
|
2478
|
+
jsonKey: "prs"
|
|
2479
|
+
});
|
|
2480
|
+
}
|
|
2481
|
+
function runPrShow(args, deps) {
|
|
2482
|
+
return runShow(args, deps, {
|
|
2483
|
+
command: "pr show",
|
|
2484
|
+
kind: PATCH_KIND,
|
|
2485
|
+
usage: PR_SHOW_USAGE,
|
|
2486
|
+
jsonKey: "pr",
|
|
2487
|
+
bodyLabel: "Patch"
|
|
2488
|
+
});
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
// src/cli/events.ts
|
|
2492
|
+
var defaultReadStdin = async () => {
|
|
2493
|
+
if (process.stdin.isTTY) return "";
|
|
2494
|
+
const chunks = [];
|
|
2495
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
2496
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
2497
|
+
};
|
|
2498
|
+
var COMMON_FLAGS_USAGE = ` --repo-id <id> repository id / NIP-34 d-tag (default: git config
|
|
2499
|
+
toon.repoid \u2014 run \`rig init\` to set it)
|
|
2500
|
+
--owner <pubkey> repository owner pubkey, 64-char hex (default: git config
|
|
2501
|
+
toon.owner, then the active identity)
|
|
2502
|
+
--remote <name> publish via this configured git remote (default: the
|
|
2503
|
+
"origin" remote \u2014 \`rig remote add origin <relay-url>\`)
|
|
2504
|
+
--relay <url> ad-hoc relay override (exactly one) \u2014 bypasses the
|
|
2505
|
+
configured remotes. The deprecated v0.1 \`git config
|
|
2506
|
+
toon.relay\` still works as a fallback, with a nudge
|
|
2507
|
+
--yes skip the fee confirmation (required when not a TTY)
|
|
2508
|
+
--json machine-readable receipt; without --yes it is a pure
|
|
2509
|
+
estimate (nothing published, exit 0)
|
|
2510
|
+
-h, --help show this help`;
|
|
2511
|
+
var ISSUE_USAGE = `Usage: rig issue create --title <title> [options]
|
|
2512
|
+
|
|
2513
|
+
File an issue (kind:1621) against a TOON repo \u2014 a paid publish; writes are
|
|
2514
|
+
permanent and non-refundable. The repo address (30617:<owner>:<repoId>) comes
|
|
2515
|
+
from the toon.* git config keys \`rig init\` writes.
|
|
2516
|
+
|
|
2517
|
+
Body source (exactly one): --body, --body-file, or piped stdin.
|
|
2518
|
+
|
|
2519
|
+
Options:
|
|
2520
|
+
--title <title> issue title (subject tag) [required]
|
|
2521
|
+
--body <text> issue body (Markdown)
|
|
2522
|
+
--body-file <path> read the issue body from a file
|
|
2523
|
+
--label <label> label (t tag); repeatable
|
|
2524
|
+
${COMMON_FLAGS_USAGE}
|
|
2525
|
+
|
|
2526
|
+
Related (FREE reads \u2014 no payment):
|
|
2527
|
+
rig issue list [--state open|closed|all] list the repo's issues
|
|
2528
|
+
rig issue show <event-id> one issue + its comments`;
|
|
2529
|
+
var COMMENT_USAGE = `Usage: rig comment <root-event-id> --body <text> [options]
|
|
2530
|
+
|
|
2531
|
+
Comment (kind:1622) on an issue or patch \u2014 a paid publish; writes are
|
|
2532
|
+
permanent and non-refundable. <root-event-id> is the 64-char hex id of the
|
|
2533
|
+
kind:1621 issue / kind:1617 patch being commented on.
|
|
2534
|
+
|
|
2535
|
+
Options:
|
|
2536
|
+
--body <text> comment body (Markdown) [required]
|
|
2537
|
+
--parent-author <pubkey> pubkey of the TARGET event's author (p threading
|
|
2538
|
+
tag; default: the repo owner)
|
|
2539
|
+
--marker <root|reply> e-tag marker (default: root \u2014 commenting directly
|
|
2540
|
+
on the issue/patch)
|
|
2541
|
+
${COMMON_FLAGS_USAGE}`;
|
|
2542
|
+
var PR_CREATE_USAGE = `Usage: rig pr create --title <title> (--range <range> | --patch-file <path>) [options]
|
|
2543
|
+
|
|
2544
|
+
Publish a patch (kind:1617) whose content is REAL \`git format-patch\` output \u2014
|
|
2545
|
+
a paid publish; writes are permanent and non-refundable. --range runs
|
|
2546
|
+
\`git format-patch --stdout <range>\` in the local repository and derives the
|
|
2547
|
+
commit/parent-commit tags; --patch-file publishes a pre-generated patch
|
|
2548
|
+
verbatim. A multi-commit range publishes ONE kind:1617 event carrying the
|
|
2549
|
+
full series text \u2014 cover-letter threading (one event per commit) is out of
|
|
2550
|
+
scope in v1.
|
|
2551
|
+
|
|
2552
|
+
The PR body (--body/--body-file) is carried in a dedicated \`description\`
|
|
2553
|
+
tag, not in the event content \u2014 the content stays pure format-patch output so
|
|
2554
|
+
\`rig pr show \u2026 | git am\` keeps working.
|
|
2555
|
+
|
|
2556
|
+
Options:
|
|
2557
|
+
--title <title> patch/PR title (subject tag) [required]
|
|
2558
|
+
--range <range> revision range for format-patch: <rev>, <rev>..<rev>,
|
|
2559
|
+
or <rev>...<rev> (mutually exclusive with --patch-file)
|
|
2560
|
+
--patch-file <path> literal patch text to publish
|
|
2561
|
+
--body <text> PR description (Markdown; description tag)
|
|
2562
|
+
--body-file <path> read the PR description from a file
|
|
2563
|
+
--branch <name> branch name (t tag)
|
|
2564
|
+
${COMMON_FLAGS_USAGE}`;
|
|
2565
|
+
var PR_STATUS_USAGE = `Usage: rig pr status <target-event-id> <open|applied|closed|draft> [options]
|
|
2566
|
+
|
|
2567
|
+
Set the status of an issue or patch \u2014 a paid publish; writes are permanent
|
|
2568
|
+
and non-refundable. Publishes kind:1630 (open), 1631 (applied), 1632
|
|
2569
|
+
(closed), or 1633 (draft) against the 64-char hex id of the target event,
|
|
1600
2570
|
with the repo a-tag attached so readers can scope a status stream to the
|
|
1601
2571
|
repository. (This command was \`rig status\` before v2 \u2014 bare \`rig status\`
|
|
1602
2572
|
now passes through to \`git status\`.)
|
|
@@ -1605,10 +2575,14 @@ Options:
|
|
|
1605
2575
|
${COMMON_FLAGS_USAGE}`;
|
|
1606
2576
|
var PR_USAGE = `${PR_CREATE_USAGE}
|
|
1607
2577
|
|
|
1608
|
-
${PR_STATUS_USAGE}
|
|
1609
|
-
|
|
2578
|
+
${PR_STATUS_USAGE}
|
|
2579
|
+
|
|
2580
|
+
${PR_LIST_USAGE}
|
|
2581
|
+
|
|
2582
|
+
${PR_SHOW_USAGE}`;
|
|
2583
|
+
var HEX64_RE2 = /^[0-9a-f]{64}$/;
|
|
1610
2584
|
function assertHex64(value, what) {
|
|
1611
|
-
if (!
|
|
2585
|
+
if (!HEX64_RE2.test(value)) {
|
|
1612
2586
|
throw new Error(
|
|
1613
2587
|
`${what} must be a 64-char lowercase hex id (got ${JSON.stringify(value)})`
|
|
1614
2588
|
);
|
|
@@ -1643,7 +2617,7 @@ function pickCommon(values) {
|
|
|
1643
2617
|
}
|
|
1644
2618
|
async function runEvent(opts) {
|
|
1645
2619
|
const { command, flags, deps, actionLabel } = opts;
|
|
1646
|
-
const { io: io2
|
|
2620
|
+
const { io: io2 } = deps;
|
|
1647
2621
|
let standaloneCtx;
|
|
1648
2622
|
try {
|
|
1649
2623
|
let repoRoot;
|
|
@@ -1667,21 +2641,35 @@ async function runEvent(opts) {
|
|
|
1667
2641
|
io2.err(singleRelayRefusal(resolved, "Nothing was published or paid."));
|
|
1668
2642
|
return 1;
|
|
1669
2643
|
}
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
2644
|
+
const session = await loadPaidSession(deps, relaysUsed[0]);
|
|
2645
|
+
const path = session.path;
|
|
2646
|
+
let identity;
|
|
2647
|
+
let fee;
|
|
2648
|
+
if (session.path === "standalone") {
|
|
2649
|
+
standaloneCtx = session.ctx;
|
|
2650
|
+
identity = identityReport(standaloneCtx);
|
|
2651
|
+
fee = (await standaloneCtx.publisher.getFeeRates()).eventFee.toString();
|
|
2652
|
+
} else {
|
|
2653
|
+
identity = session.identity;
|
|
2654
|
+
fee = session.feePerEvent;
|
|
2655
|
+
const resolvedRelay = relaysUsed[0];
|
|
2656
|
+
if (resolvedRelay !== void 0 && session.daemonRelayUrl !== void 0 && session.daemonRelayUrl !== resolvedRelay) {
|
|
2657
|
+
io2.err(
|
|
2658
|
+
`rig: the daemon publishes via its configured relay (${session.daemonRelayUrl}), not the resolved relay (${resolvedRelay}) \u2014 stop the daemon to force the standalone path if that matters`
|
|
2659
|
+
);
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
1679
2662
|
const owner = flags.owner ?? toonConfig.owner ?? identity.pubkey;
|
|
1680
2663
|
const addr = { ownerPubkey: owner, repoId };
|
|
1681
2664
|
const event = await opts.buildEvent(addr);
|
|
1682
2665
|
const action = `kind:${event.kind} ${actionLabel}`;
|
|
1683
2666
|
if (!flags.json) {
|
|
1684
|
-
for (const line of renderEventPlan({
|
|
2667
|
+
for (const line of renderEventPlan({
|
|
2668
|
+
action,
|
|
2669
|
+
addr,
|
|
2670
|
+
identity,
|
|
2671
|
+
...fee !== void 0 ? { fee } : {}
|
|
2672
|
+
})) {
|
|
1685
2673
|
io2.out(line);
|
|
1686
2674
|
}
|
|
1687
2675
|
}
|
|
@@ -1690,10 +2678,11 @@ async function runEvent(opts) {
|
|
|
1690
2678
|
io2.emitJson({
|
|
1691
2679
|
command,
|
|
1692
2680
|
repoAddr: addr,
|
|
2681
|
+
path,
|
|
1693
2682
|
identity,
|
|
1694
2683
|
kind: event.kind,
|
|
1695
2684
|
executed: false,
|
|
1696
|
-
feeEstimate: fee,
|
|
2685
|
+
feeEstimate: fee ?? null,
|
|
1697
2686
|
hint: "estimate only \u2014 re-run with --yes to publish (permanent, non-refundable)"
|
|
1698
2687
|
});
|
|
1699
2688
|
return 0;
|
|
@@ -1712,16 +2701,25 @@ async function runEvent(opts) {
|
|
|
1712
2701
|
return 1;
|
|
1713
2702
|
}
|
|
1714
2703
|
}
|
|
1715
|
-
|
|
1716
|
-
|
|
2704
|
+
let result;
|
|
2705
|
+
if (session.path === "standalone") {
|
|
2706
|
+
const receipt = await session.ctx.publisher.publishEvent(
|
|
2707
|
+
event,
|
|
2708
|
+
relaysUsed
|
|
2709
|
+
);
|
|
2710
|
+
result = serializeEventReceipt(event.kind, receipt);
|
|
2711
|
+
} else {
|
|
2712
|
+
result = await opts.sendDaemon(session.client, addr);
|
|
2713
|
+
}
|
|
1717
2714
|
if (flags.json) {
|
|
1718
2715
|
io2.emitJson({
|
|
1719
2716
|
command,
|
|
1720
2717
|
repoAddr: addr,
|
|
2718
|
+
path,
|
|
1721
2719
|
identity,
|
|
1722
2720
|
kind: result.kind,
|
|
1723
2721
|
executed: true,
|
|
1724
|
-
feeEstimate: fee,
|
|
2722
|
+
feeEstimate: fee ?? null,
|
|
1725
2723
|
result
|
|
1726
2724
|
});
|
|
1727
2725
|
} else {
|
|
@@ -1744,11 +2742,17 @@ async function runIssue(args, deps) {
|
|
|
1744
2742
|
const [sub, ...rest] = args;
|
|
1745
2743
|
if (sub === "--help" || sub === "-h" || sub === "help") {
|
|
1746
2744
|
io2.out(ISSUE_USAGE);
|
|
2745
|
+
io2.out("");
|
|
2746
|
+
io2.out(ISSUE_LIST_USAGE);
|
|
2747
|
+
io2.out("");
|
|
2748
|
+
io2.out(ISSUE_SHOW_USAGE);
|
|
1747
2749
|
return 0;
|
|
1748
2750
|
}
|
|
2751
|
+
if (sub === "list") return runIssueList(rest, deps);
|
|
2752
|
+
if (sub === "show") return runIssueShow(rest, deps);
|
|
1749
2753
|
if (sub !== "create") {
|
|
1750
2754
|
io2.err(
|
|
1751
|
-
sub === void 0 ? "missing subcommand: rig issue create" : `unknown rig issue subcommand: ${sub}`
|
|
2755
|
+
sub === void 0 ? "missing subcommand: rig issue <create|list|show>" : `unknown rig issue subcommand: ${sub}`
|
|
1752
2756
|
);
|
|
1753
2757
|
io2.err(ISSUE_USAGE);
|
|
1754
2758
|
return 2;
|
|
@@ -1759,7 +2763,7 @@ async function runIssue(args, deps) {
|
|
|
1759
2763
|
let bodyFile;
|
|
1760
2764
|
let labels;
|
|
1761
2765
|
try {
|
|
1762
|
-
const { values } =
|
|
2766
|
+
const { values } = parseArgs7({
|
|
1763
2767
|
args: rest,
|
|
1764
2768
|
options: {
|
|
1765
2769
|
...COMMON_OPTIONS,
|
|
@@ -1818,7 +2822,13 @@ async function runIssue(args, deps) {
|
|
|
1818
2822
|
flags,
|
|
1819
2823
|
deps,
|
|
1820
2824
|
actionLabel: `issue ${JSON.stringify(title)}`,
|
|
1821
|
-
buildEvent: async (addr) => buildIssue(addr.ownerPubkey, addr.repoId, title, body, labels)
|
|
2825
|
+
buildEvent: async (addr) => buildIssue(addr.ownerPubkey, addr.repoId, title, body, labels),
|
|
2826
|
+
sendDaemon: (client, addr) => client.gitIssue({
|
|
2827
|
+
repoAddr: addr,
|
|
2828
|
+
title,
|
|
2829
|
+
body,
|
|
2830
|
+
...labels.length > 0 ? { labels } : {}
|
|
2831
|
+
})
|
|
1822
2832
|
});
|
|
1823
2833
|
}
|
|
1824
2834
|
async function runComment(args, deps) {
|
|
@@ -1829,7 +2839,7 @@ async function runComment(args, deps) {
|
|
|
1829
2839
|
let parentAuthor;
|
|
1830
2840
|
let marker;
|
|
1831
2841
|
try {
|
|
1832
|
-
const { values, positionals } =
|
|
2842
|
+
const { values, positionals } = parseArgs7({
|
|
1833
2843
|
args,
|
|
1834
2844
|
options: {
|
|
1835
2845
|
...COMMON_OPTIONS,
|
|
@@ -1879,7 +2889,14 @@ async function runComment(args, deps) {
|
|
|
1879
2889
|
parentAuthor ?? addr.ownerPubkey,
|
|
1880
2890
|
body,
|
|
1881
2891
|
marker
|
|
1882
|
-
)
|
|
2892
|
+
),
|
|
2893
|
+
sendDaemon: (client, addr) => client.gitComment({
|
|
2894
|
+
repoAddr: addr,
|
|
2895
|
+
rootEventId,
|
|
2896
|
+
body,
|
|
2897
|
+
...parentAuthor !== void 0 ? { parentAuthorPubkey: parentAuthor } : {},
|
|
2898
|
+
marker
|
|
2899
|
+
})
|
|
1883
2900
|
});
|
|
1884
2901
|
}
|
|
1885
2902
|
var PATCH_FROM_RE = /^From ([0-9a-f]{40}) /gm;
|
|
@@ -1894,6 +2911,11 @@ async function runPr(args, deps) {
|
|
|
1894
2911
|
return runPrCreate(rest, deps);
|
|
1895
2912
|
case "status":
|
|
1896
2913
|
return runPrStatus(rest, deps);
|
|
2914
|
+
// FREE read subcommands (#278): relay queries only, no payment path.
|
|
2915
|
+
case "list":
|
|
2916
|
+
return runPrList(rest, deps);
|
|
2917
|
+
case "show":
|
|
2918
|
+
return runPrShow(rest, deps);
|
|
1897
2919
|
case "--help":
|
|
1898
2920
|
case "-h":
|
|
1899
2921
|
case "help":
|
|
@@ -1901,7 +2923,7 @@ async function runPr(args, deps) {
|
|
|
1901
2923
|
return 0;
|
|
1902
2924
|
default:
|
|
1903
2925
|
io2.err(
|
|
1904
|
-
sub === void 0 ? "missing subcommand: rig pr <create|status>" : `unknown rig pr subcommand: ${sub}`
|
|
2926
|
+
sub === void 0 ? "missing subcommand: rig pr <create|status|list|show>" : `unknown rig pr subcommand: ${sub}`
|
|
1905
2927
|
);
|
|
1906
2928
|
io2.err(PR_USAGE);
|
|
1907
2929
|
return 2;
|
|
@@ -1914,14 +2936,18 @@ async function runPrCreate(rest, deps) {
|
|
|
1914
2936
|
let range;
|
|
1915
2937
|
let patchFile;
|
|
1916
2938
|
let branch;
|
|
2939
|
+
let bodyFlag;
|
|
2940
|
+
let bodyFile;
|
|
1917
2941
|
try {
|
|
1918
|
-
const { values } =
|
|
2942
|
+
const { values } = parseArgs7({
|
|
1919
2943
|
args: rest,
|
|
1920
2944
|
options: {
|
|
1921
2945
|
...COMMON_OPTIONS,
|
|
1922
2946
|
title: { type: "string" },
|
|
1923
2947
|
range: { type: "string" },
|
|
1924
2948
|
"patch-file": { type: "string" },
|
|
2949
|
+
body: { type: "string" },
|
|
2950
|
+
"body-file": { type: "string" },
|
|
1925
2951
|
branch: { type: "string" }
|
|
1926
2952
|
},
|
|
1927
2953
|
allowPositionals: false
|
|
@@ -1937,15 +2963,39 @@ async function runPrCreate(rest, deps) {
|
|
|
1937
2963
|
if (values.range === void 0 === (values["patch-file"] === void 0)) {
|
|
1938
2964
|
throw new Error("exactly one of --range or --patch-file is required");
|
|
1939
2965
|
}
|
|
2966
|
+
if (values.body !== void 0 && values["body-file"] !== void 0) {
|
|
2967
|
+
throw new Error("--body and --body-file are mutually exclusive");
|
|
2968
|
+
}
|
|
1940
2969
|
title = values.title;
|
|
1941
2970
|
range = values.range;
|
|
1942
2971
|
patchFile = values["patch-file"];
|
|
1943
2972
|
branch = values.branch;
|
|
2973
|
+
bodyFlag = values.body;
|
|
2974
|
+
bodyFile = values["body-file"];
|
|
1944
2975
|
} catch (err) {
|
|
1945
2976
|
io2.err(err instanceof Error ? err.message : String(err));
|
|
1946
2977
|
io2.err(PR_CREATE_USAGE);
|
|
1947
2978
|
return 2;
|
|
1948
2979
|
}
|
|
2980
|
+
let body;
|
|
2981
|
+
if (bodyFlag !== void 0) {
|
|
2982
|
+
body = bodyFlag;
|
|
2983
|
+
} else if (bodyFile !== void 0) {
|
|
2984
|
+
try {
|
|
2985
|
+
body = await readFile(bodyFile, "utf-8");
|
|
2986
|
+
} catch (err) {
|
|
2987
|
+
io2.err(
|
|
2988
|
+
`cannot read --body-file ${bodyFile}: ${err instanceof Error ? err.message : String(err)}`
|
|
2989
|
+
);
|
|
2990
|
+
return 2;
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
if (body !== void 0 && body.trim() === "") {
|
|
2994
|
+
io2.err("the PR body is empty \u2014 drop --body/--body-file or pass text");
|
|
2995
|
+
return 2;
|
|
2996
|
+
}
|
|
2997
|
+
let builtPatchText;
|
|
2998
|
+
let builtCommits = [];
|
|
1949
2999
|
return runEvent({
|
|
1950
3000
|
command: "pr",
|
|
1951
3001
|
flags,
|
|
@@ -1975,22 +3025,38 @@ async function runPrCreate(rest, deps) {
|
|
|
1975
3025
|
}
|
|
1976
3026
|
commits = [];
|
|
1977
3027
|
}
|
|
3028
|
+
builtPatchText = patchText;
|
|
3029
|
+
builtCommits = commits;
|
|
1978
3030
|
return buildPatch(
|
|
1979
3031
|
addr.ownerPubkey,
|
|
1980
3032
|
addr.repoId,
|
|
1981
3033
|
title,
|
|
1982
3034
|
commits,
|
|
1983
3035
|
branch,
|
|
1984
|
-
patchText
|
|
3036
|
+
patchText,
|
|
3037
|
+
body
|
|
1985
3038
|
);
|
|
3039
|
+
},
|
|
3040
|
+
sendDaemon: (client, addr) => {
|
|
3041
|
+
if (builtPatchText === void 0) {
|
|
3042
|
+
throw new Error("internal: patch text not built before delegation");
|
|
3043
|
+
}
|
|
3044
|
+
return client.gitPatch({
|
|
3045
|
+
repoAddr: addr,
|
|
3046
|
+
title,
|
|
3047
|
+
patchText: builtPatchText,
|
|
3048
|
+
...body !== void 0 ? { description: body } : {},
|
|
3049
|
+
...builtCommits.length > 0 ? { commits: builtCommits } : {},
|
|
3050
|
+
...branch !== void 0 ? { branch } : {}
|
|
3051
|
+
});
|
|
1986
3052
|
}
|
|
1987
3053
|
});
|
|
1988
3054
|
}
|
|
1989
3055
|
var STATUS_KIND_BY_VALUE = {
|
|
1990
|
-
open:
|
|
1991
|
-
applied:
|
|
1992
|
-
closed:
|
|
1993
|
-
draft:
|
|
3056
|
+
open: STATUS_OPEN_KIND2,
|
|
3057
|
+
applied: STATUS_APPLIED_KIND2,
|
|
3058
|
+
closed: STATUS_CLOSED_KIND2,
|
|
3059
|
+
draft: STATUS_DRAFT_KIND2
|
|
1994
3060
|
};
|
|
1995
3061
|
function isStatusValue(value) {
|
|
1996
3062
|
return Object.hasOwn(STATUS_KIND_BY_VALUE, value);
|
|
@@ -2001,7 +3067,7 @@ async function runPrStatus(args, deps) {
|
|
|
2001
3067
|
let targetEventId;
|
|
2002
3068
|
let status;
|
|
2003
3069
|
try {
|
|
2004
|
-
const { values, positionals } =
|
|
3070
|
+
const { values, positionals } = parseArgs7({
|
|
2005
3071
|
args,
|
|
2006
3072
|
options: COMMON_OPTIONS,
|
|
2007
3073
|
allowPositionals: true
|
|
@@ -2039,18 +3105,238 @@ async function runPrStatus(args, deps) {
|
|
|
2039
3105
|
const event = buildStatus(targetEventId, STATUS_KIND_BY_VALUE[status]);
|
|
2040
3106
|
event.tags.push([
|
|
2041
3107
|
"a",
|
|
2042
|
-
`${
|
|
3108
|
+
`${REPOSITORY_ANNOUNCEMENT_KIND2}:${addr.ownerPubkey}:${addr.repoId}`
|
|
2043
3109
|
]);
|
|
2044
3110
|
return event;
|
|
2045
|
-
}
|
|
3111
|
+
},
|
|
3112
|
+
sendDaemon: (client, addr) => client.gitStatus({ repoAddr: addr, targetEventId, status })
|
|
2046
3113
|
});
|
|
2047
3114
|
}
|
|
2048
3115
|
|
|
3116
|
+
// src/cli/fetch.ts
|
|
3117
|
+
import { parseArgs as parseArgs8 } from "util";
|
|
3118
|
+
var FETCH_USAGE = `Usage: rig fetch [remote] [options]
|
|
3119
|
+
|
|
3120
|
+
Fetch from a TOON remote \u2014 FREE (relay reads + Arweave gateway downloads; no
|
|
3121
|
+
payments, no identity needed). Reads the kind:30618 repository state from the
|
|
3122
|
+
remote's relay, downloads only the git objects this repository is missing
|
|
3123
|
+
(SHA-1 verified), and updates the remote-tracking refs
|
|
3124
|
+
(refs/remotes/<remote>/*; tags land at refs/tags/*), reporting what moved.
|
|
3125
|
+
|
|
3126
|
+
No merge happens: integrate with the git passthrough, e.g.
|
|
3127
|
+
\`rig merge origin/main\`. \`rig fetch\` is the TOON transport and shadows
|
|
3128
|
+
\`git fetch\` \u2014 plain-git fetches remain available via \`git fetch\` directly.
|
|
3129
|
+
|
|
3130
|
+
The repo address (toon.repoid/toon.owner) comes from the config \`rig init\`
|
|
3131
|
+
or \`rig clone\` writes; --repo-id/--owner override.
|
|
3132
|
+
|
|
3133
|
+
Options:
|
|
3134
|
+
--repo-id <id> repository id / NIP-34 d-tag (default: git config)
|
|
3135
|
+
--owner <pubkey> repository owner (npub or 64-char hex; default: git config)
|
|
3136
|
+
--relay <url> ad-hoc relay override \u2014 bypasses the remote's URL
|
|
3137
|
+
--concurrency <n> parallel gateway downloads (default 8)
|
|
3138
|
+
--json machine-readable result envelope
|
|
3139
|
+
-h, --help show this help`;
|
|
3140
|
+
var WS_URL_RE3 = /^wss?:\/\//i;
|
|
3141
|
+
function localRefFor(refname, remote) {
|
|
3142
|
+
if (refname.startsWith("refs/heads/")) {
|
|
3143
|
+
return `refs/remotes/${remote}/${refname.slice("refs/heads/".length)}`;
|
|
3144
|
+
}
|
|
3145
|
+
if (refname.startsWith("refs/tags/")) return refname;
|
|
3146
|
+
return null;
|
|
3147
|
+
}
|
|
3148
|
+
async function resolveLocalRef(repoRoot, refname) {
|
|
3149
|
+
try {
|
|
3150
|
+
const out = await runGit(repoRoot, [
|
|
3151
|
+
"rev-parse",
|
|
3152
|
+
"--verify",
|
|
3153
|
+
"--quiet",
|
|
3154
|
+
refname
|
|
3155
|
+
]);
|
|
3156
|
+
const sha = out.trim();
|
|
3157
|
+
return /^[0-9a-f]{40}$/.test(sha) ? sha : null;
|
|
3158
|
+
} catch {
|
|
3159
|
+
return null;
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
async function runFetch(args, deps) {
|
|
3163
|
+
const { io: io2 } = deps;
|
|
3164
|
+
let json = false;
|
|
3165
|
+
let remote = "origin";
|
|
3166
|
+
let relayFlag;
|
|
3167
|
+
let repoIdFlag;
|
|
3168
|
+
let ownerFlag;
|
|
3169
|
+
let concurrency;
|
|
3170
|
+
try {
|
|
3171
|
+
const { values, positionals } = parseArgs8({
|
|
3172
|
+
args,
|
|
3173
|
+
options: {
|
|
3174
|
+
json: { type: "boolean", default: false },
|
|
3175
|
+
relay: { type: "string" },
|
|
3176
|
+
"repo-id": { type: "string" },
|
|
3177
|
+
owner: { type: "string" },
|
|
3178
|
+
concurrency: { type: "string" },
|
|
3179
|
+
help: { type: "boolean", short: "h", default: false }
|
|
3180
|
+
},
|
|
3181
|
+
allowPositionals: true
|
|
3182
|
+
});
|
|
3183
|
+
if (values.help) {
|
|
3184
|
+
io2.out(FETCH_USAGE);
|
|
3185
|
+
return 0;
|
|
3186
|
+
}
|
|
3187
|
+
json = values.json === true;
|
|
3188
|
+
if (positionals.length > 1) {
|
|
3189
|
+
throw new Error(
|
|
3190
|
+
`expected at most one [remote] argument, got ${positionals.length}`
|
|
3191
|
+
);
|
|
3192
|
+
}
|
|
3193
|
+
if (positionals[0] !== void 0) remote = positionals[0];
|
|
3194
|
+
relayFlag = values.relay;
|
|
3195
|
+
repoIdFlag = values["repo-id"];
|
|
3196
|
+
ownerFlag = values.owner !== void 0 ? ownerToHex(values.owner) : void 0;
|
|
3197
|
+
if (values.concurrency !== void 0) {
|
|
3198
|
+
concurrency = Number.parseInt(values.concurrency, 10);
|
|
3199
|
+
if (!Number.isSafeInteger(concurrency) || concurrency < 1) {
|
|
3200
|
+
throw new Error("--concurrency must be a positive integer");
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
} catch (err) {
|
|
3204
|
+
io2.err(err instanceof Error ? err.message : String(err));
|
|
3205
|
+
io2.err(FETCH_USAGE);
|
|
3206
|
+
return 2;
|
|
3207
|
+
}
|
|
3208
|
+
try {
|
|
3209
|
+
const repoRoot = await resolveRepoRoot(deps.cwd);
|
|
3210
|
+
const toonConfig = await readToonConfig(repoRoot);
|
|
3211
|
+
const repoId = repoIdFlag ?? toonConfig.repoId;
|
|
3212
|
+
if (!repoId) throw new UnconfiguredRepoAddressError("repository id");
|
|
3213
|
+
const owner = ownerFlag ?? toonConfig.owner;
|
|
3214
|
+
if (!owner) throw new UnconfiguredRepoAddressError("repository owner");
|
|
3215
|
+
let relayUrl;
|
|
3216
|
+
if (relayFlag !== void 0) {
|
|
3217
|
+
relayUrl = relayFlag;
|
|
3218
|
+
} else {
|
|
3219
|
+
const urls = await getGitRemoteUrls(repoRoot, remote);
|
|
3220
|
+
if (urls.length === 0) throw new UnknownRemoteError(remote);
|
|
3221
|
+
if (urls.length > 1) throw new MultiUrlRemoteError(remote, urls);
|
|
3222
|
+
relayUrl = urls[0];
|
|
3223
|
+
}
|
|
3224
|
+
if (!WS_URL_RE3.test(relayUrl)) {
|
|
3225
|
+
throw new InvalidRelayUrlError(
|
|
3226
|
+
relayUrl,
|
|
3227
|
+
`remote ${JSON.stringify(remote)}`
|
|
3228
|
+
);
|
|
3229
|
+
}
|
|
3230
|
+
const remoteState = await fetchRemoteState({
|
|
3231
|
+
relayUrls: [relayUrl],
|
|
3232
|
+
ownerPubkey: owner,
|
|
3233
|
+
repoId,
|
|
3234
|
+
...deps.webSocketFactory ? { webSocketFactory: deps.webSocketFactory } : {},
|
|
3235
|
+
...deps.resolveSha ? { resolveSha: deps.resolveSha } : {}
|
|
3236
|
+
});
|
|
3237
|
+
const planned = [];
|
|
3238
|
+
for (const [refname, sha] of remoteState.refs) {
|
|
3239
|
+
if (!isSafeRefname(refname)) {
|
|
3240
|
+
io2.err(
|
|
3241
|
+
`warning: skipping unsafe ref name from relay: ${JSON.stringify(refname)}`
|
|
3242
|
+
);
|
|
3243
|
+
continue;
|
|
3244
|
+
}
|
|
3245
|
+
const localRef = localRefFor(refname, remote);
|
|
3246
|
+
if (localRef === null) {
|
|
3247
|
+
io2.err(
|
|
3248
|
+
`warning: skipping ref outside refs/heads and refs/tags: ${refname}`
|
|
3249
|
+
);
|
|
3250
|
+
continue;
|
|
3251
|
+
}
|
|
3252
|
+
planned.push({ refname, localRef, newSha: sha });
|
|
3253
|
+
}
|
|
3254
|
+
const reader = new GitRepoReader(repoRoot);
|
|
3255
|
+
const tips = [...new Set(planned.map((p) => p.newSha))];
|
|
3256
|
+
const candidates = [.../* @__PURE__ */ new Set([...remoteState.shaToTxId.keys(), ...tips])];
|
|
3257
|
+
const { missing: absent } = await reader.statObjects(candidates);
|
|
3258
|
+
const absentSet = new Set(absent);
|
|
3259
|
+
const presentLocally = new Set(
|
|
3260
|
+
candidates.filter((sha) => !absentSet.has(sha))
|
|
3261
|
+
);
|
|
3262
|
+
const collected = await collectRepoObjects({
|
|
3263
|
+
tips,
|
|
3264
|
+
shaToTxId: remoteState.shaToTxId,
|
|
3265
|
+
resolveMissing: (shas) => remoteState.resolveMissing(shas),
|
|
3266
|
+
presentLocally,
|
|
3267
|
+
...deps.fetchFn ? { fetchFn: deps.fetchFn } : {},
|
|
3268
|
+
...concurrency !== void 0 ? { concurrency } : {}
|
|
3269
|
+
});
|
|
3270
|
+
if (collected.missing.length > 0) {
|
|
3271
|
+
throw new MissingRemoteObjectsError(
|
|
3272
|
+
collected.missing,
|
|
3273
|
+
`cannot fetch from ${relayUrl}`
|
|
3274
|
+
);
|
|
3275
|
+
}
|
|
3276
|
+
const written = await writeGitObjects(repoRoot, collected.objects.values());
|
|
3277
|
+
const updates = [];
|
|
3278
|
+
for (const { refname, localRef, newSha } of planned) {
|
|
3279
|
+
const oldSha = await resolveLocalRef(repoRoot, localRef);
|
|
3280
|
+
if (oldSha === newSha) {
|
|
3281
|
+
updates.push({ refname, localRef, oldSha, newSha, kind: "up-to-date" });
|
|
3282
|
+
continue;
|
|
3283
|
+
}
|
|
3284
|
+
let kind;
|
|
3285
|
+
if (oldSha === null) {
|
|
3286
|
+
kind = "new";
|
|
3287
|
+
} else {
|
|
3288
|
+
kind = await reader.isAncestor(oldSha, newSha) ? "fast-forward" : "forced";
|
|
3289
|
+
}
|
|
3290
|
+
await updateRef(repoRoot, localRef, newSha);
|
|
3291
|
+
updates.push({ refname, localRef, oldSha, newSha, kind });
|
|
3292
|
+
}
|
|
3293
|
+
const moved = updates.filter((u) => u.kind !== "up-to-date");
|
|
3294
|
+
if (json) {
|
|
3295
|
+
io2.emitJson({
|
|
3296
|
+
command: "fetch",
|
|
3297
|
+
repoAddr: { ownerPubkey: owner, repoId },
|
|
3298
|
+
remote,
|
|
3299
|
+
relay: relayUrl,
|
|
3300
|
+
objectsDownloaded: written,
|
|
3301
|
+
updates,
|
|
3302
|
+
executed: true
|
|
3303
|
+
});
|
|
3304
|
+
return 0;
|
|
3305
|
+
}
|
|
3306
|
+
if (moved.length === 0) {
|
|
3307
|
+
io2.out("Already up to date.");
|
|
3308
|
+
return 0;
|
|
3309
|
+
}
|
|
3310
|
+
io2.out(`From ${relayUrl}`);
|
|
3311
|
+
for (const u of moved) {
|
|
3312
|
+
const short = (refname) => refname.replace(/^refs\/heads\//, "").replace(/^refs\/tags\//, "").replace(/^refs\/remotes\//, "");
|
|
3313
|
+
const src = short(u.refname);
|
|
3314
|
+
const dst = short(u.localRef);
|
|
3315
|
+
if (u.kind === "new") {
|
|
3316
|
+
const label = u.refname.startsWith("refs/tags/") ? "[new tag]" : "[new branch]";
|
|
3317
|
+
io2.out(` * ${label.padEnd(17)} ${src.padEnd(14)} -> ${dst}`);
|
|
3318
|
+
} else if (u.kind === "forced") {
|
|
3319
|
+
io2.out(
|
|
3320
|
+
` + ${u.oldSha.slice(0, 7)}...${u.newSha.slice(0, 7)} ${src.padEnd(14)} -> ${dst} (forced update)`
|
|
3321
|
+
);
|
|
3322
|
+
} else {
|
|
3323
|
+
io2.out(
|
|
3324
|
+
` ${u.oldSha.slice(0, 7)}..${u.newSha.slice(0, 7)} ${src.padEnd(14)} -> ${dst}`
|
|
3325
|
+
);
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
io2.out(`Downloaded ${written} object(s) (SHA-1 verified).`);
|
|
3329
|
+
return 0;
|
|
3330
|
+
} catch (err) {
|
|
3331
|
+
return emitCliError(io2, json, "fetch", err);
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
|
|
2049
3335
|
// src/cli/fund.ts
|
|
2050
3336
|
import { readFileSync } from "fs";
|
|
2051
3337
|
import { homedir } from "os";
|
|
2052
|
-
import { join } from "path";
|
|
2053
|
-
import { parseArgs as
|
|
3338
|
+
import { join as join2 } from "path";
|
|
3339
|
+
import { parseArgs as parseArgs9 } from "util";
|
|
2054
3340
|
var DEVNET_FAUCET_URL = "https://faucet.devnet.toonprotocol.dev";
|
|
2055
3341
|
var CHAINS = ["evm", "solana", "mina"];
|
|
2056
3342
|
var FUND_USAGE = `Usage: rig fund [options]
|
|
@@ -2069,9 +3355,36 @@ Options:
|
|
|
2069
3355
|
--address <address> fund this address instead of the identity's own
|
|
2070
3356
|
--json machine-readable envelope
|
|
2071
3357
|
-h, --help show this help`;
|
|
3358
|
+
var SHARED_DEVNET_SUFFIX = ".devnet.toonprotocol.dev";
|
|
3359
|
+
function sharedDevnetOrigin(env, file) {
|
|
3360
|
+
const candidates = [
|
|
3361
|
+
env["TOON_CLIENT_RELAY_URL"] ?? file.relayUrl,
|
|
3362
|
+
env["TOON_CLIENT_PROXY_URL"] ?? file.proxyUrl,
|
|
3363
|
+
env["TOON_CLIENT_BTP_URL"] ?? file.btpUrl
|
|
3364
|
+
];
|
|
3365
|
+
for (const url of candidates) {
|
|
3366
|
+
if (!url) continue;
|
|
3367
|
+
try {
|
|
3368
|
+
const { hostname } = new URL(url);
|
|
3369
|
+
if (hostname.endsWith(SHARED_DEVNET_SUFFIX) || hostname === SHARED_DEVNET_SUFFIX.slice(1)) {
|
|
3370
|
+
return url;
|
|
3371
|
+
}
|
|
3372
|
+
} catch {
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
return void 0;
|
|
3376
|
+
}
|
|
3377
|
+
function noFaucetGuidance(network, devnetOrigin) {
|
|
3378
|
+
const head = `no faucet is configured for network ${JSON.stringify(network ?? "custom")}`;
|
|
3379
|
+
const external = "To fund the wallet externally instead, send the settlement token plus native gas to the address below for the chain your channels settle on.";
|
|
3380
|
+
if (devnetOrigin !== void 0) {
|
|
3381
|
+
return `${head} \u2014 but your configured origin (${devnetOrigin}) looks like the shared devnet. Set TOON_CLIENT_NETWORK=devnet (or "network": "devnet" in the client config) and re-run \`rig fund\` \u2014 no faucet URL is needed there. ${external}`;
|
|
3382
|
+
}
|
|
3383
|
+
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}`;
|
|
3384
|
+
}
|
|
2072
3385
|
function readFundConfig(env) {
|
|
2073
|
-
const dir = env["TOON_CLIENT_HOME"] ??
|
|
2074
|
-
const configPath =
|
|
3386
|
+
const dir = env["TOON_CLIENT_HOME"] ?? join2(homedir(), ".toon-client");
|
|
3387
|
+
const configPath = join2(dir, "config.json");
|
|
2075
3388
|
try {
|
|
2076
3389
|
return {
|
|
2077
3390
|
file: JSON.parse(readFileSync(configPath, "utf8")),
|
|
@@ -2092,7 +3405,7 @@ async function runFund(args, deps) {
|
|
|
2092
3405
|
let addressFlag;
|
|
2093
3406
|
let json = false;
|
|
2094
3407
|
try {
|
|
2095
|
-
const { values } =
|
|
3408
|
+
const { values } = parseArgs9({
|
|
2096
3409
|
args,
|
|
2097
3410
|
options: {
|
|
2098
3411
|
chain: { type: "string" },
|
|
@@ -2149,7 +3462,7 @@ async function runFund(args, deps) {
|
|
|
2149
3462
|
mina: derived.mina.publicKey || null
|
|
2150
3463
|
};
|
|
2151
3464
|
if (!faucetUrl) {
|
|
2152
|
-
const guidance =
|
|
3465
|
+
const guidance = noFaucetGuidance(network, sharedDevnetOrigin(env, file));
|
|
2153
3466
|
if (json) {
|
|
2154
3467
|
io2.emitJson({
|
|
2155
3468
|
command: "fund",
|
|
@@ -2221,7 +3534,7 @@ function signalExitCode(signal) {
|
|
|
2221
3534
|
var runGitPassthrough = (argv2, options = {}) => {
|
|
2222
3535
|
const err = options.err ?? ((line) => process.stderr.write(`${line}
|
|
2223
3536
|
`));
|
|
2224
|
-
return new Promise((
|
|
3537
|
+
return new Promise((resolve2) => {
|
|
2225
3538
|
const child = spawn("git", argv2, {
|
|
2226
3539
|
stdio: "inherit",
|
|
2227
3540
|
...options.cwd !== void 0 ? { cwd: options.cwd } : {},
|
|
@@ -2244,22 +3557,22 @@ var runGitPassthrough = (argv2, options = {}) => {
|
|
|
2244
3557
|
err(
|
|
2245
3558
|
`rig: git not found \u2014 \`rig ${argv2[0] ?? ""}\` is not a rig command, so it is passed through to the system \`git\`, which is not on your PATH. Install git (https://git-scm.com) or fix your PATH.`
|
|
2246
3559
|
);
|
|
2247
|
-
|
|
3560
|
+
resolve2(GIT_NOT_FOUND_EXIT);
|
|
2248
3561
|
return;
|
|
2249
3562
|
}
|
|
2250
3563
|
err(`rig: failed to run git: ${spawnErr.message}`);
|
|
2251
|
-
|
|
3564
|
+
resolve2(1);
|
|
2252
3565
|
});
|
|
2253
3566
|
child.on("close", (code, signal) => {
|
|
2254
3567
|
restoreSignals();
|
|
2255
|
-
|
|
3568
|
+
resolve2(signal !== null ? signalExitCode(signal) : code ?? 1);
|
|
2256
3569
|
});
|
|
2257
3570
|
});
|
|
2258
3571
|
};
|
|
2259
3572
|
|
|
2260
3573
|
// src/cli/init.ts
|
|
2261
|
-
import { basename } from "path";
|
|
2262
|
-
import { parseArgs as
|
|
3574
|
+
import { basename as basename2 } from "path";
|
|
3575
|
+
import { parseArgs as parseArgs10 } from "util";
|
|
2263
3576
|
var INIT_USAGE = `Usage: rig init [options]
|
|
2264
3577
|
|
|
2265
3578
|
Set up the current git repository for rig (one-shot, idempotent, free):
|
|
@@ -2279,7 +3592,7 @@ Options:
|
|
|
2279
3592
|
--json machine-readable report
|
|
2280
3593
|
-h, --help show this help`;
|
|
2281
3594
|
function parseInitArgs(args) {
|
|
2282
|
-
const { values, positionals } =
|
|
3595
|
+
const { values, positionals } = parseArgs10({
|
|
2283
3596
|
args,
|
|
2284
3597
|
options: {
|
|
2285
3598
|
"repo-id": { type: "string" },
|
|
@@ -2329,7 +3642,7 @@ async function runInit(args, deps) {
|
|
|
2329
3642
|
warn: (line) => io2.err(line)
|
|
2330
3643
|
});
|
|
2331
3644
|
const existing = await readToonConfig(repoRoot);
|
|
2332
|
-
const repoId = flags.repoId ?? existing.repoId ??
|
|
3645
|
+
const repoId = flags.repoId ?? existing.repoId ?? basename2(repoRoot);
|
|
2333
3646
|
const changed = {
|
|
2334
3647
|
repoId: existing.repoId !== repoId,
|
|
2335
3648
|
owner: existing.owner !== identity.pubkey
|
|
@@ -2415,12 +3728,22 @@ Commands rig owns:
|
|
|
2415
3728
|
remote add <name> <url> add a relay as a REAL git remote ("origin" is
|
|
2416
3729
|
remote remove <name> the default publish target); remove/list manage
|
|
2417
3730
|
remote list them \u2014 \`git remote -v\` shows the same data
|
|
3731
|
+
clone <relay-url> <owner>/<repo-id> [dir]
|
|
3732
|
+
clone a TOON repo (free): relay state + Arweave
|
|
3733
|
+
objects (SHA-1 verified) \u2192 a real git repository,
|
|
3734
|
+
push/pull-capable out of the box
|
|
3735
|
+
fetch [remote] fetch remote refs + missing objects (free) and
|
|
3736
|
+
update refs/remotes/<remote>/*; no merge. Shadows
|
|
3737
|
+
git fetch (plain \`git fetch\` stays available)
|
|
2418
3738
|
push [remote] [refspecs...] plan, price, confirm, and execute a paid push
|
|
2419
3739
|
to TOON (defaults to the "origin" remote). rig
|
|
2420
3740
|
push is the TOON transport and shadows git push;
|
|
2421
3741
|
plain-git pushes remain available by running
|
|
2422
3742
|
\`git push\` directly
|
|
2423
3743
|
issue create file an issue (kind:1621) against a repo
|
|
3744
|
+
issue list | show <id> read the repo's issues + comments (free)
|
|
3745
|
+
pr list | show <id> read the repo's patches; show prints the full
|
|
3746
|
+
patch text (free)
|
|
2424
3747
|
comment <root-event-id> comment (kind:1622) on an issue or patch
|
|
2425
3748
|
pr create publish a patch (kind:1617) with real
|
|
2426
3749
|
\`git format-patch\` content
|
|
@@ -2443,8 +3766,10 @@ Any other command is passed through to git verbatim: \`rig status\` runs
|
|
|
2443
3766
|
\`rig rebase -i\`, \u2026 behave exactly like git (same output, same exit code).
|
|
2444
3767
|
|
|
2445
3768
|
Run \`rig <command> --help\` for a rig command's flags. \`rig init\`,
|
|
2446
|
-
\`rig remote\`, \`rig
|
|
2447
|
-
|
|
3769
|
+
\`rig remote\`, \`rig clone\`, \`rig fetch\`, \`rig issue list/show\`,
|
|
3770
|
+
\`rig pr list/show\`, \`rig fund\`, \`rig balance\`, and \`rig channel list\`
|
|
3771
|
+
are free; push/issue create/comment/pr create/pr status are paid writes \u2014
|
|
3772
|
+
permanent and non-refundable \u2014
|
|
2448
3773
|
and channel open/close/settle are on-chain wallet transactions; each states
|
|
2449
3774
|
what it will spend and asks for confirmation before doing so (--yes skips,
|
|
2450
3775
|
--json emits machine output).
|
|
@@ -2474,6 +3799,11 @@ async function dispatch(argv2, deps) {
|
|
|
2474
3799
|
return runInit(rest, deps);
|
|
2475
3800
|
case "remote":
|
|
2476
3801
|
return runRemote(rest, deps);
|
|
3802
|
+
// The #278 read path — FREE (relay + Arweave gateway reads, no payment).
|
|
3803
|
+
case "clone":
|
|
3804
|
+
return runClone(rest, deps);
|
|
3805
|
+
case "fetch":
|
|
3806
|
+
return runFetch(rest, deps);
|
|
2477
3807
|
case "push":
|
|
2478
3808
|
return runPush(rest, deps);
|
|
2479
3809
|
case "issue":
|
|
@@ -2553,6 +3883,8 @@ function makeCliIo(options) {
|
|
|
2553
3883
|
var RIG_OWNED_VERBS = /* @__PURE__ */ new Set([
|
|
2554
3884
|
"init",
|
|
2555
3885
|
"remote",
|
|
3886
|
+
"clone",
|
|
3887
|
+
"fetch",
|
|
2556
3888
|
"push",
|
|
2557
3889
|
"issue",
|
|
2558
3890
|
"comment",
|
|
@@ -2570,6 +3902,29 @@ function isJsonInvocation(argv2) {
|
|
|
2570
3902
|
}
|
|
2571
3903
|
return false;
|
|
2572
3904
|
}
|
|
3905
|
+
var ANNOUNCE_FAILED_RE = /\[Bootstrap\] Announce failed/;
|
|
3906
|
+
var ANNOUNCE_402_RE = /402|payment required/i;
|
|
3907
|
+
var ANNOUNCE_402_INFO = "rig: skipped the optional identity self-announce \u2014 the payment peer charges for announces and this identity has not paid for one (expected on a fresh or unfunded identity); harmless, the command continues.\n";
|
|
3908
|
+
function calmBootstrapNoise() {
|
|
3909
|
+
const original = process.stderr.write;
|
|
3910
|
+
let reframed = false;
|
|
3911
|
+
const patched = ((chunk, encodingOrCb, cb) => {
|
|
3912
|
+
let text = chunk;
|
|
3913
|
+
if (typeof chunk === "string" && ANNOUNCE_FAILED_RE.test(chunk)) {
|
|
3914
|
+
if (ANNOUNCE_402_RE.test(chunk)) {
|
|
3915
|
+
text = reframed ? "" : ANNOUNCE_402_INFO;
|
|
3916
|
+
reframed = true;
|
|
3917
|
+
}
|
|
3918
|
+
}
|
|
3919
|
+
return typeof encodingOrCb === "function" ? original.call(process.stderr, text, encodingOrCb) : original.call(process.stderr, text, encodingOrCb, cb);
|
|
3920
|
+
});
|
|
3921
|
+
process.stderr.write = patched;
|
|
3922
|
+
return {
|
|
3923
|
+
restore: () => {
|
|
3924
|
+
if (process.stderr.write === patched) process.stderr.write = original;
|
|
3925
|
+
}
|
|
3926
|
+
};
|
|
3927
|
+
}
|
|
2573
3928
|
function redirectStdoutToStderr(realWrite) {
|
|
2574
3929
|
const original = process.stdout.write;
|
|
2575
3930
|
const real = realWrite ?? original.bind(process.stdout);
|
|
@@ -2591,6 +3946,7 @@ function redirectStdoutToStderr(realWrite) {
|
|
|
2591
3946
|
|
|
2592
3947
|
// src/cli/rig.ts
|
|
2593
3948
|
function makeIo(jsonMode) {
|
|
3949
|
+
calmBootstrapNoise();
|
|
2594
3950
|
const guard = jsonMode ? redirectStdoutToStderr() : void 0;
|
|
2595
3951
|
return makeCliIo({
|
|
2596
3952
|
jsonMode,
|
|
@@ -2615,6 +3971,16 @@ function makeIo(jsonMode) {
|
|
|
2615
3971
|
}
|
|
2616
3972
|
});
|
|
2617
3973
|
}
|
|
3974
|
+
function exitWhenFlushed(code) {
|
|
3975
|
+
process.exitCode = code;
|
|
3976
|
+
let pending = 2;
|
|
3977
|
+
const done = () => {
|
|
3978
|
+
pending -= 1;
|
|
3979
|
+
if (pending === 0) process.exit(code);
|
|
3980
|
+
};
|
|
3981
|
+
process.stdout.write("", done);
|
|
3982
|
+
process.stderr.write("", done);
|
|
3983
|
+
}
|
|
2618
3984
|
var argv = process.argv.slice(2);
|
|
2619
3985
|
var io = makeIo(isJsonInvocation(argv));
|
|
2620
3986
|
dispatch(argv, {
|
|
@@ -2624,7 +3990,7 @@ dispatch(argv, {
|
|
|
2624
3990
|
}).then(
|
|
2625
3991
|
(code) => {
|
|
2626
3992
|
io.ensureSingleJsonDoc(code);
|
|
2627
|
-
|
|
3993
|
+
exitWhenFlushed(code);
|
|
2628
3994
|
},
|
|
2629
3995
|
(err) => {
|
|
2630
3996
|
process.stderr.write(
|
|
@@ -2632,7 +3998,7 @@ dispatch(argv, {
|
|
|
2632
3998
|
`
|
|
2633
3999
|
);
|
|
2634
4000
|
io.ensureSingleJsonDoc(1);
|
|
2635
|
-
|
|
4001
|
+
exitWhenFlushed(1);
|
|
2636
4002
|
}
|
|
2637
4003
|
);
|
|
2638
4004
|
//# sourceMappingURL=rig.js.map
|