@toon-protocol/rig 2.0.0 → 2.1.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 +10 -3
- package/dist/chunk-3EKP7PMM.js +727 -0
- package/dist/chunk-3EKP7PMM.js.map +1 -0
- package/dist/{chunk-QD437XAW.js → chunk-CW4HJNMU.js} +2 -8
- package/dist/chunk-CW4HJNMU.js.map +1 -0
- package/dist/chunk-O6TXHKWG.js +217 -0
- package/dist/chunk-O6TXHKWG.js.map +1 -0
- package/dist/{chunk-G4W4MH6G.js → chunk-PLKZAUTG.js} +2 -1
- package/dist/chunk-PLKZAUTG.js.map +1 -0
- package/dist/cli/rig.js +1286 -398
- package/dist/cli/rig.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/{publisher-CClD9OIZ.d.ts → publisher-Cdr1H1hg.d.ts} +1 -1
- package/dist/standalone/index.d.ts +404 -3
- package/dist/standalone/index.js +16 -2
- package/dist/standalone-mode-FCKTQ33Y.js +595 -0
- package/dist/standalone-mode-FCKTQ33Y.js.map +1 -0
- package/package.json +3 -11
- package/dist/chunk-G4W4MH6G.js.map +0 -1
- package/dist/chunk-QD437XAW.js.map +0 -1
- package/dist/chunk-XRRU6YBQ.js +0 -402
- package/dist/chunk-XRRU6YBQ.js.map +0 -1
- package/dist/standalone-mode-CAYWOURK.js +0 -111
- package/dist/standalone-mode-CAYWOURK.js.map +0 -1
package/dist/cli/rig.js
CHANGED
|
@@ -13,13 +13,18 @@ import {
|
|
|
13
13
|
import {
|
|
14
14
|
MissingIdentityError,
|
|
15
15
|
resolveIdentity
|
|
16
|
-
} from "../chunk-
|
|
16
|
+
} from "../chunk-CW4HJNMU.js";
|
|
17
17
|
import {
|
|
18
18
|
buildComment,
|
|
19
19
|
buildIssue,
|
|
20
20
|
buildPatch,
|
|
21
21
|
buildStatus
|
|
22
22
|
} from "../chunk-HPSOQP7Q.js";
|
|
23
|
+
import {
|
|
24
|
+
ChannelMapStore,
|
|
25
|
+
channelStatus,
|
|
26
|
+
resolveChannelPaths
|
|
27
|
+
} from "../chunk-O6TXHKWG.js";
|
|
23
28
|
import {
|
|
24
29
|
MAX_OBJECT_SIZE
|
|
25
30
|
} from "../chunk-X2CZPPDM.js";
|
|
@@ -30,16 +35,8 @@ import { createInterface } from "readline/promises";
|
|
|
30
35
|
// src/cli/dispatch.ts
|
|
31
36
|
import { createRequire } from "module";
|
|
32
37
|
|
|
33
|
-
// src/cli/
|
|
34
|
-
import { readFile } from "fs/promises";
|
|
38
|
+
// src/cli/balance.ts
|
|
35
39
|
import { parseArgs as parseArgs3 } from "util";
|
|
36
|
-
import {
|
|
37
|
-
REPOSITORY_ANNOUNCEMENT_KIND,
|
|
38
|
-
STATUS_APPLIED_KIND,
|
|
39
|
-
STATUS_CLOSED_KIND,
|
|
40
|
-
STATUS_DRAFT_KIND,
|
|
41
|
-
STATUS_OPEN_KIND
|
|
42
|
-
} from "@toon-protocol/core/nip34";
|
|
43
40
|
|
|
44
41
|
// src/cli/errors.ts
|
|
45
42
|
var UnconfiguredRepoAddressError = class extends Error {
|
|
@@ -221,12 +218,127 @@ function describeError(err, command = "push") {
|
|
|
221
218
|
json: { error: "missing_uplink", detail: message }
|
|
222
219
|
};
|
|
223
220
|
}
|
|
221
|
+
if (name === "ChannelMapCorruptError") {
|
|
222
|
+
return {
|
|
223
|
+
code: "channel_map_corrupt",
|
|
224
|
+
lines: [message],
|
|
225
|
+
json: { error: "channel_map_corrupt", detail: message }
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
if (name === "SettleTooEarlyError") {
|
|
229
|
+
const settleableAt = err.settleableAt;
|
|
230
|
+
return {
|
|
231
|
+
code: "settle_too_early",
|
|
232
|
+
lines: [
|
|
233
|
+
message,
|
|
234
|
+
"The settlement challenge window is still open \u2014 nothing was spent. Re-run `rig channel settle` after the settleable time."
|
|
235
|
+
],
|
|
236
|
+
json: {
|
|
237
|
+
error: "settle_too_early",
|
|
238
|
+
detail: message,
|
|
239
|
+
...typeof settleableAt === "string" ? { settleableAt } : {}
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
}
|
|
224
243
|
return {
|
|
225
244
|
code: "error",
|
|
226
245
|
lines: [`rig ${command} failed: ${message}`],
|
|
227
246
|
json: { error: "error", detail: message }
|
|
228
247
|
};
|
|
229
248
|
}
|
|
249
|
+
function emitCliError(io2, json, command, err) {
|
|
250
|
+
const described = describeError(err, command);
|
|
251
|
+
if (json) io2.emitJson({ command, ...described.json });
|
|
252
|
+
for (const line of described.lines) io2.err(line);
|
|
253
|
+
return 1;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// src/cli/push.ts
|
|
257
|
+
import { parseArgs as parseArgs2 } from "util";
|
|
258
|
+
|
|
259
|
+
// src/cli/render.ts
|
|
260
|
+
function formatNumber(value) {
|
|
261
|
+
return String(value).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
262
|
+
}
|
|
263
|
+
function shortSha(sha) {
|
|
264
|
+
return sha ? sha.slice(0, 7) : "(none)";
|
|
265
|
+
}
|
|
266
|
+
function refLine(update) {
|
|
267
|
+
const arrow = `${shortSha(update.remoteSha)} \u2192 ${shortSha(update.localSha)}`;
|
|
268
|
+
return ` ${update.refname} ${arrow} (${update.kind})`;
|
|
269
|
+
}
|
|
270
|
+
function renderIdentityLine(identity) {
|
|
271
|
+
return `Identity: ${identity.pubkey} (from ${identity.sourceLabel})`;
|
|
272
|
+
}
|
|
273
|
+
function renderPlan(plan) {
|
|
274
|
+
const lines = [];
|
|
275
|
+
lines.push(
|
|
276
|
+
`Push plan for repo "${plan.repoId}"` + (plan.announceNeeded ? " \u2014 first push, will announce (kind:30617)" : "")
|
|
277
|
+
);
|
|
278
|
+
lines.push("Refs:");
|
|
279
|
+
for (const update of plan.refUpdates) lines.push(refLine(update));
|
|
280
|
+
const est = plan.estimate;
|
|
281
|
+
const skipped = Object.keys(plan.knownShaToTxId).length;
|
|
282
|
+
lines.push(
|
|
283
|
+
`Objects: ${est.objectCount} to upload (${formatNumber(est.totalObjectBytes)} bytes)` + (skipped > 0 ? `; ${skipped} already on Arweave (free)` : "")
|
|
284
|
+
);
|
|
285
|
+
lines.push("Fees (base units):");
|
|
286
|
+
lines.push(
|
|
287
|
+
` upload ${est.objectCount} object(s), ${formatNumber(est.totalObjectBytes)} bytes ${formatNumber(est.uploadFee)}`
|
|
288
|
+
);
|
|
289
|
+
lines.push(` events ${est.eventCount} event(s) ${formatNumber(est.eventFees)}`);
|
|
290
|
+
lines.push(` total ${formatNumber(est.totalFee)}`);
|
|
291
|
+
lines.push("Writes are permanent and non-refundable.");
|
|
292
|
+
return lines;
|
|
293
|
+
}
|
|
294
|
+
function feeLabel(fee) {
|
|
295
|
+
return fee !== void 0 ? `${formatNumber(fee)} base units` : "the publisher's configured per-event fee";
|
|
296
|
+
}
|
|
297
|
+
function renderEventPlan(opts) {
|
|
298
|
+
return [
|
|
299
|
+
`Publish ${opts.action}`,
|
|
300
|
+
`Repo: 30617:${opts.addr.ownerPubkey}:${opts.addr.repoId}`,
|
|
301
|
+
renderIdentityLine(opts.identity),
|
|
302
|
+
`Fee: ${feeLabel(opts.fee)}. Writes are permanent and non-refundable.`
|
|
303
|
+
];
|
|
304
|
+
}
|
|
305
|
+
function renderEventReceipt(action, result) {
|
|
306
|
+
const lines = [
|
|
307
|
+
`Published ${action}: ${result.eventId} paid ${formatNumber(result.feePaid)} base units`
|
|
308
|
+
];
|
|
309
|
+
if (result.channelBalanceAfter !== void 0) {
|
|
310
|
+
lines.push(
|
|
311
|
+
`Channel balance after: ${formatNumber(result.channelBalanceAfter)} base units`
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
return lines;
|
|
315
|
+
}
|
|
316
|
+
function renderResult(result) {
|
|
317
|
+
const lines = [];
|
|
318
|
+
lines.push(`Pushed "${result.repoId}":`);
|
|
319
|
+
const paid = result.uploads.filter((u) => !u.skipped);
|
|
320
|
+
const skipped = result.uploads.filter((u) => u.skipped);
|
|
321
|
+
for (const upload of result.uploads) {
|
|
322
|
+
lines.push(
|
|
323
|
+
` object ${upload.sha.slice(0, 12)} ${upload.skipped ? "skipped (already stored)" : `paid ${formatNumber(upload.feePaid)}`} ar:${upload.txId}`
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
lines.push(
|
|
327
|
+
`Uploads: ${paid.length} paid, ${skipped.length} skipped (content-addressed).`
|
|
328
|
+
);
|
|
329
|
+
if (result.announceReceipt) {
|
|
330
|
+
lines.push(
|
|
331
|
+
`Announcement (kind:30617): ${result.announceReceipt.eventId} paid ${formatNumber(result.announceReceipt.feePaid)}`
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
lines.push(
|
|
335
|
+
`Refs event (kind:30618): ${result.refsReceipt.eventId} paid ${formatNumber(result.refsReceipt.feePaid)}`
|
|
336
|
+
);
|
|
337
|
+
lines.push(
|
|
338
|
+
`Total paid: ${formatNumber(result.totalFeePaid)} base units (estimate was ${formatNumber(result.estimate.totalFee)})`
|
|
339
|
+
);
|
|
340
|
+
return lines;
|
|
341
|
+
}
|
|
230
342
|
|
|
231
343
|
// src/cli/git-config.ts
|
|
232
344
|
import { execFile } from "child_process";
|
|
@@ -320,93 +432,6 @@ async function writeToonConfig(repoPath, config) {
|
|
|
320
432
|
}
|
|
321
433
|
}
|
|
322
434
|
|
|
323
|
-
// src/cli/push.ts
|
|
324
|
-
import { parseArgs as parseArgs2 } from "util";
|
|
325
|
-
|
|
326
|
-
// src/cli/render.ts
|
|
327
|
-
function formatNumber(value) {
|
|
328
|
-
return String(value).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
329
|
-
}
|
|
330
|
-
function shortSha(sha) {
|
|
331
|
-
return sha ? sha.slice(0, 7) : "(none)";
|
|
332
|
-
}
|
|
333
|
-
function refLine(update) {
|
|
334
|
-
const arrow = `${shortSha(update.remoteSha)} \u2192 ${shortSha(update.localSha)}`;
|
|
335
|
-
return ` ${update.refname} ${arrow} (${update.kind})`;
|
|
336
|
-
}
|
|
337
|
-
function renderIdentityLine(identity) {
|
|
338
|
-
return `Identity: ${identity.pubkey} (from ${identity.sourceLabel})`;
|
|
339
|
-
}
|
|
340
|
-
function renderPlan(plan) {
|
|
341
|
-
const lines = [];
|
|
342
|
-
lines.push(
|
|
343
|
-
`Push plan for repo "${plan.repoId}"` + (plan.announceNeeded ? " \u2014 first push, will announce (kind:30617)" : "")
|
|
344
|
-
);
|
|
345
|
-
lines.push("Refs:");
|
|
346
|
-
for (const update of plan.refUpdates) lines.push(refLine(update));
|
|
347
|
-
const est = plan.estimate;
|
|
348
|
-
const skipped = Object.keys(plan.knownShaToTxId).length;
|
|
349
|
-
lines.push(
|
|
350
|
-
`Objects: ${est.objectCount} to upload (${formatNumber(est.totalObjectBytes)} bytes)` + (skipped > 0 ? `; ${skipped} already on Arweave (free)` : "")
|
|
351
|
-
);
|
|
352
|
-
lines.push("Fees (base units):");
|
|
353
|
-
lines.push(
|
|
354
|
-
` upload ${est.objectCount} object(s), ${formatNumber(est.totalObjectBytes)} bytes ${formatNumber(est.uploadFee)}`
|
|
355
|
-
);
|
|
356
|
-
lines.push(` events ${est.eventCount} event(s) ${formatNumber(est.eventFees)}`);
|
|
357
|
-
lines.push(` total ${formatNumber(est.totalFee)}`);
|
|
358
|
-
lines.push("Writes are permanent and non-refundable.");
|
|
359
|
-
return lines;
|
|
360
|
-
}
|
|
361
|
-
function feeLabel(fee) {
|
|
362
|
-
return fee !== void 0 ? `${formatNumber(fee)} base units` : "the publisher's configured per-event fee";
|
|
363
|
-
}
|
|
364
|
-
function renderEventPlan(opts) {
|
|
365
|
-
return [
|
|
366
|
-
`Publish ${opts.action}`,
|
|
367
|
-
`Repo: 30617:${opts.addr.ownerPubkey}:${opts.addr.repoId}`,
|
|
368
|
-
renderIdentityLine(opts.identity),
|
|
369
|
-
`Fee: ${feeLabel(opts.fee)}. Writes are permanent and non-refundable.`
|
|
370
|
-
];
|
|
371
|
-
}
|
|
372
|
-
function renderEventReceipt(action, result) {
|
|
373
|
-
const lines = [
|
|
374
|
-
`Published ${action}: ${result.eventId} paid ${formatNumber(result.feePaid)} base units`
|
|
375
|
-
];
|
|
376
|
-
if (result.channelBalanceAfter !== void 0) {
|
|
377
|
-
lines.push(
|
|
378
|
-
`Channel balance after: ${formatNumber(result.channelBalanceAfter)} base units`
|
|
379
|
-
);
|
|
380
|
-
}
|
|
381
|
-
return lines;
|
|
382
|
-
}
|
|
383
|
-
function renderResult(result) {
|
|
384
|
-
const lines = [];
|
|
385
|
-
lines.push(`Pushed "${result.repoId}":`);
|
|
386
|
-
const paid = result.uploads.filter((u) => !u.skipped);
|
|
387
|
-
const skipped = result.uploads.filter((u) => u.skipped);
|
|
388
|
-
for (const upload of result.uploads) {
|
|
389
|
-
lines.push(
|
|
390
|
-
` object ${upload.sha.slice(0, 12)} ${upload.skipped ? "skipped (already stored)" : `paid ${formatNumber(upload.feePaid)}`} ar:${upload.txId}`
|
|
391
|
-
);
|
|
392
|
-
}
|
|
393
|
-
lines.push(
|
|
394
|
-
`Uploads: ${paid.length} paid, ${skipped.length} skipped (content-addressed).`
|
|
395
|
-
);
|
|
396
|
-
if (result.announceReceipt) {
|
|
397
|
-
lines.push(
|
|
398
|
-
`Announcement (kind:30617): ${result.announceReceipt.eventId} paid ${formatNumber(result.announceReceipt.feePaid)}`
|
|
399
|
-
);
|
|
400
|
-
}
|
|
401
|
-
lines.push(
|
|
402
|
-
`Refs event (kind:30618): ${result.refsReceipt.eventId} paid ${formatNumber(result.refsReceipt.feePaid)}`
|
|
403
|
-
);
|
|
404
|
-
lines.push(
|
|
405
|
-
`Total paid: ${formatNumber(result.totalFeePaid)} base units (estimate was ${formatNumber(result.estimate.totalFee)})`
|
|
406
|
-
);
|
|
407
|
-
return lines;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
435
|
// src/cli/remote.ts
|
|
411
436
|
import { parseArgs } from "util";
|
|
412
437
|
var RELAY_PROTOCOLS = /* @__PURE__ */ new Set(["ws:", "wss:", "http:", "https:"]);
|
|
@@ -471,10 +496,10 @@ Commands:
|
|
|
471
496
|
list list remote names + URLs (the default subcommand)
|
|
472
497
|
|
|
473
498
|
Options:
|
|
474
|
-
--json machine-readable
|
|
499
|
+
--json machine-readable envelope (all subcommands)
|
|
475
500
|
-h, --help show this help`;
|
|
476
501
|
async function runRemote(args, deps) {
|
|
477
|
-
const { io } = deps;
|
|
502
|
+
const { io: io2 } = deps;
|
|
478
503
|
let sub;
|
|
479
504
|
let rest;
|
|
480
505
|
let json;
|
|
@@ -488,34 +513,34 @@ async function runRemote(args, deps) {
|
|
|
488
513
|
allowPositionals: true
|
|
489
514
|
});
|
|
490
515
|
if (values.help) {
|
|
491
|
-
|
|
516
|
+
io2.out(REMOTE_USAGE);
|
|
492
517
|
return 0;
|
|
493
518
|
}
|
|
494
519
|
json = values.json ?? false;
|
|
495
520
|
[sub, ...rest] = positionals;
|
|
496
521
|
} catch (err) {
|
|
497
|
-
|
|
498
|
-
|
|
522
|
+
io2.err(err instanceof Error ? err.message : String(err));
|
|
523
|
+
io2.err(REMOTE_USAGE);
|
|
499
524
|
return 2;
|
|
500
525
|
}
|
|
501
526
|
switch (sub) {
|
|
502
527
|
case void 0:
|
|
503
528
|
case "list":
|
|
504
529
|
if (rest.length > 0) {
|
|
505
|
-
|
|
506
|
-
|
|
530
|
+
io2.err(`rig remote list takes no arguments (got ${rest.join(" ")})`);
|
|
531
|
+
io2.err(REMOTE_USAGE);
|
|
507
532
|
return 2;
|
|
508
533
|
}
|
|
509
534
|
break;
|
|
510
535
|
case "add": {
|
|
511
536
|
if (rest.length !== 2) {
|
|
512
|
-
|
|
513
|
-
|
|
537
|
+
io2.err("usage: rig remote add <name> <relay-url>");
|
|
538
|
+
io2.err(REMOTE_USAGE);
|
|
514
539
|
return 2;
|
|
515
540
|
}
|
|
516
541
|
const url = rest[1];
|
|
517
542
|
if (!isRelayUrl(url)) {
|
|
518
|
-
|
|
543
|
+
io2.err(
|
|
519
544
|
`cannot add remote: ${JSON.stringify(url)} is not a relay URL \u2014 relays are ws://, wss://, http://, or https://`
|
|
520
545
|
);
|
|
521
546
|
return 2;
|
|
@@ -524,14 +549,14 @@ async function runRemote(args, deps) {
|
|
|
524
549
|
}
|
|
525
550
|
case "remove":
|
|
526
551
|
if (rest.length !== 1) {
|
|
527
|
-
|
|
528
|
-
|
|
552
|
+
io2.err("usage: rig remote remove <name>");
|
|
553
|
+
io2.err(REMOTE_USAGE);
|
|
529
554
|
return 2;
|
|
530
555
|
}
|
|
531
556
|
break;
|
|
532
557
|
default:
|
|
533
|
-
|
|
534
|
-
|
|
558
|
+
io2.err(`unknown rig remote subcommand: ${sub}`);
|
|
559
|
+
io2.err(REMOTE_USAGE);
|
|
535
560
|
return 2;
|
|
536
561
|
}
|
|
537
562
|
try {
|
|
@@ -546,23 +571,23 @@ async function runRemote(args, deps) {
|
|
|
546
571
|
case "list": {
|
|
547
572
|
const remotes = await listGitRemotes(repoRoot);
|
|
548
573
|
if (json) {
|
|
549
|
-
|
|
574
|
+
io2.emitJson({ command: "remote", remotes });
|
|
550
575
|
return 0;
|
|
551
576
|
}
|
|
552
577
|
if (remotes.length === 0) {
|
|
553
|
-
|
|
578
|
+
io2.out(
|
|
554
579
|
"no remotes configured \u2014 add one: rig remote add origin <relay-url>"
|
|
555
580
|
);
|
|
556
581
|
return 0;
|
|
557
582
|
}
|
|
558
583
|
for (const remote of remotes) {
|
|
559
584
|
for (const url of remote.urls) {
|
|
560
|
-
|
|
585
|
+
io2.out(
|
|
561
586
|
`${remote.name} ${url}` + (isRelayUrl(url) ? "" : " (not a relay URL \u2014 ignored by rig)")
|
|
562
587
|
);
|
|
563
588
|
}
|
|
564
589
|
if (remote.urls.length > 1) {
|
|
565
|
-
|
|
590
|
+
io2.err(
|
|
566
591
|
`warning: remote "${remote.name}" has ${remote.urls.length} URLs \u2014 rig supports one relay URL per remote (fix with \`git remote set-url ${remote.name} <relay-url>\`)`
|
|
567
592
|
);
|
|
568
593
|
}
|
|
@@ -573,37 +598,61 @@ async function runRemote(args, deps) {
|
|
|
573
598
|
const [name, url] = rest;
|
|
574
599
|
const existing = await getGitRemoteUrls(repoRoot, name);
|
|
575
600
|
if (existing.length > 0) {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
601
|
+
const detail = `remote ${JSON.stringify(name)} already exists (${existing.join(", ")}) \u2014 nothing changed. Point it somewhere else with \`git remote set-url ${name} <relay-url>\`, or \`rig remote remove ${name}\` first.`;
|
|
602
|
+
if (json) {
|
|
603
|
+
io2.emitJson({
|
|
604
|
+
command: "remote",
|
|
605
|
+
error: "remote_exists",
|
|
606
|
+
detail,
|
|
607
|
+
remote: name,
|
|
608
|
+
urls: existing
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
io2.err(detail);
|
|
579
612
|
return 1;
|
|
580
613
|
}
|
|
581
614
|
await addGitRemote(repoRoot, name, url);
|
|
582
|
-
|
|
615
|
+
if (!json) io2.out(`Added remote ${name} \u2192 ${url}`);
|
|
583
616
|
if (name === "origin") {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
617
|
+
if (!json) {
|
|
618
|
+
io2.out(
|
|
619
|
+
"`rig push` and the event commands now publish here by default."
|
|
620
|
+
);
|
|
621
|
+
}
|
|
587
622
|
const toonConfig = await readToonConfig(repoRoot);
|
|
588
623
|
if (toonConfig.relays.length > 0) {
|
|
589
|
-
|
|
624
|
+
io2.err(
|
|
590
625
|
`note: git config toon.relay (${toonConfig.relays.join(", ")}) is deprecated and now shadowed by the origin remote \u2014 drop it with \`git config --unset-all toon.relay\` (the key is removed in v0.3).`
|
|
591
626
|
);
|
|
592
627
|
}
|
|
593
628
|
}
|
|
629
|
+
if (json) {
|
|
630
|
+
io2.emitJson({ command: "remote", action: "add", name, url });
|
|
631
|
+
}
|
|
594
632
|
return 0;
|
|
595
633
|
}
|
|
596
634
|
case "remove": {
|
|
597
635
|
const name = rest[0];
|
|
598
636
|
const existing = await getGitRemoteUrls(repoRoot, name);
|
|
599
637
|
if (existing.length === 0) {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
638
|
+
const detail = `no remote named ${JSON.stringify(name)} \u2014 \`rig remote list\` shows configured remotes.`;
|
|
639
|
+
if (json) {
|
|
640
|
+
io2.emitJson({
|
|
641
|
+
command: "remote",
|
|
642
|
+
error: "unknown_remote",
|
|
643
|
+
detail,
|
|
644
|
+
remote: name
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
io2.err(detail);
|
|
603
648
|
return 1;
|
|
604
649
|
}
|
|
605
650
|
await removeGitRemote(repoRoot, name);
|
|
606
|
-
|
|
651
|
+
if (json) {
|
|
652
|
+
io2.emitJson({ command: "remote", action: "remove", name });
|
|
653
|
+
} else {
|
|
654
|
+
io2.out(`Removed remote ${name}`);
|
|
655
|
+
}
|
|
607
656
|
return 0;
|
|
608
657
|
}
|
|
609
658
|
/* v8 ignore next 2 -- unreachable: validated above */
|
|
@@ -611,19 +660,13 @@ async function runRemote(args, deps) {
|
|
|
611
660
|
return 2;
|
|
612
661
|
}
|
|
613
662
|
} catch (err) {
|
|
614
|
-
|
|
615
|
-
if (json) {
|
|
616
|
-
io.out(JSON.stringify({ command: "remote", ...described.json }, null, 2));
|
|
617
|
-
} else {
|
|
618
|
-
for (const line of described.lines) io.err(line);
|
|
619
|
-
}
|
|
620
|
-
return 1;
|
|
663
|
+
return emitCliError(io2, json, "remote", err);
|
|
621
664
|
}
|
|
622
665
|
}
|
|
623
666
|
|
|
624
667
|
// src/cli/push.ts
|
|
625
668
|
var defaultLoadStandalone = async (options) => {
|
|
626
|
-
const mod = await import("../standalone-mode-
|
|
669
|
+
const mod = await import("../standalone-mode-FCKTQ33Y.js");
|
|
627
670
|
return mod.createStandaloneContext(options);
|
|
628
671
|
};
|
|
629
672
|
function identityReport(ctx) {
|
|
@@ -734,172 +777,758 @@ function parsePushArgs(args) {
|
|
|
734
777
|
return flags;
|
|
735
778
|
}
|
|
736
779
|
async function runPush(args, deps) {
|
|
737
|
-
const { io, env } = deps;
|
|
780
|
+
const { io: io2, env } = deps;
|
|
738
781
|
let flags;
|
|
739
782
|
try {
|
|
740
783
|
flags = parsePushArgs(args);
|
|
741
784
|
} catch (err) {
|
|
742
|
-
|
|
743
|
-
|
|
785
|
+
io2.err(err instanceof Error ? err.message : String(err));
|
|
786
|
+
io2.err(PUSH_USAGE);
|
|
787
|
+
return 2;
|
|
788
|
+
}
|
|
789
|
+
if (flags.help) {
|
|
790
|
+
io2.out(PUSH_USAGE);
|
|
791
|
+
return 0;
|
|
792
|
+
}
|
|
793
|
+
let standaloneCtx;
|
|
794
|
+
try {
|
|
795
|
+
const repoRoot = await resolveRepoRoot(deps.cwd);
|
|
796
|
+
const toonConfig = await readToonConfig(repoRoot);
|
|
797
|
+
const repoId = flags.repoId ?? toonConfig.repoId;
|
|
798
|
+
if (!repoId) throw new UnconfiguredRepoAddressError("repository id");
|
|
799
|
+
const reader = new GitRepoReader(repoRoot);
|
|
800
|
+
let remoteName;
|
|
801
|
+
let refspecArgs = flags.positionals;
|
|
802
|
+
if (flags.relay.length === 0 && refspecArgs.length > 0) {
|
|
803
|
+
const first = refspecArgs[0];
|
|
804
|
+
const remoteNames = new Set(
|
|
805
|
+
(await listGitRemotes(repoRoot)).map((r) => r.name)
|
|
806
|
+
);
|
|
807
|
+
if (remoteNames.has(first)) {
|
|
808
|
+
remoteName = first;
|
|
809
|
+
refspecArgs = refspecArgs.slice(1);
|
|
810
|
+
} else {
|
|
811
|
+
const { refs } = await reader.listRefs();
|
|
812
|
+
const known = new Set(refs.map((r) => r.refname));
|
|
813
|
+
if (!known.has(first) && !known.has(`refs/heads/${first}`) && !known.has(`refs/tags/${first}`)) {
|
|
814
|
+
throw new Error(
|
|
815
|
+
`${JSON.stringify(first)} is neither a configured remote nor a local branch/tag \u2014 add the relay remote (\`rig remote add ${first} <relay-url>\`; \`rig remote list\` shows configured remotes) or fix the refspec`
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
const refspecs = await selectRefspecs(
|
|
821
|
+
reader,
|
|
822
|
+
refspecArgs,
|
|
823
|
+
flags.all,
|
|
824
|
+
flags.tags
|
|
825
|
+
);
|
|
826
|
+
const resolved = await resolveRelays({
|
|
827
|
+
relayFlags: flags.relay,
|
|
828
|
+
remoteName,
|
|
829
|
+
repoRoot,
|
|
830
|
+
toonRelays: toonConfig.relays
|
|
831
|
+
});
|
|
832
|
+
if (resolved.nudge !== void 0) io2.err(resolved.nudge);
|
|
833
|
+
const relaysUsed = resolved.relays;
|
|
834
|
+
if (relaysUsed.length > 1) {
|
|
835
|
+
io2.err(singleRelayRefusal(resolved, "Nothing was uploaded or paid."));
|
|
836
|
+
return 1;
|
|
837
|
+
}
|
|
838
|
+
standaloneCtx = await (deps.loadStandalone ?? defaultLoadStandalone)({
|
|
839
|
+
env,
|
|
840
|
+
cwd: deps.cwd,
|
|
841
|
+
warn: (line) => io2.err(line),
|
|
842
|
+
// Relay-origin for #264 network bootstrap (announce discovery).
|
|
843
|
+
...relaysUsed[0] !== void 0 ? { relayUrl: relaysUsed[0] } : {}
|
|
844
|
+
});
|
|
845
|
+
const identity = identityReport(standaloneCtx);
|
|
846
|
+
if (toonConfig.owner && toonConfig.owner !== identity.pubkey) {
|
|
847
|
+
io2.err(
|
|
848
|
+
`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
|
+
);
|
|
850
|
+
}
|
|
851
|
+
const remoteState = await standaloneCtx.fetchRemote({
|
|
852
|
+
ownerPubkey: standaloneCtx.ownerPubkey,
|
|
853
|
+
repoId,
|
|
854
|
+
relayUrls: relaysUsed
|
|
855
|
+
});
|
|
856
|
+
const feeRates = await standaloneCtx.publisher.getFeeRates();
|
|
857
|
+
const pushPlan = await planPush({
|
|
858
|
+
repoReader: reader,
|
|
859
|
+
remoteState,
|
|
860
|
+
feeRates,
|
|
861
|
+
repoId,
|
|
862
|
+
refs: refspecs,
|
|
863
|
+
force: flags.force
|
|
864
|
+
});
|
|
865
|
+
const plan = serializePushPlan(pushPlan);
|
|
866
|
+
const upToDate = plan.refUpdates.every((u) => u.kind === "up-to-date");
|
|
867
|
+
if (upToDate) {
|
|
868
|
+
if (flags.json) {
|
|
869
|
+
io2.emitJson({
|
|
870
|
+
command: "push",
|
|
871
|
+
repoId,
|
|
872
|
+
identity,
|
|
873
|
+
executed: false,
|
|
874
|
+
upToDate: true,
|
|
875
|
+
plan
|
|
876
|
+
});
|
|
877
|
+
} else {
|
|
878
|
+
io2.out("Everything up-to-date \u2014 nothing to push (and nothing paid).");
|
|
879
|
+
}
|
|
880
|
+
return 0;
|
|
881
|
+
}
|
|
882
|
+
if (!flags.json) {
|
|
883
|
+
for (const line of renderPlan(plan)) io2.out(line);
|
|
884
|
+
io2.out(renderIdentityLine(identity));
|
|
885
|
+
}
|
|
886
|
+
if (!flags.yes) {
|
|
887
|
+
if (flags.json) {
|
|
888
|
+
io2.emitJson({
|
|
889
|
+
command: "push",
|
|
890
|
+
repoId,
|
|
891
|
+
identity,
|
|
892
|
+
executed: false,
|
|
893
|
+
upToDate: false,
|
|
894
|
+
plan,
|
|
895
|
+
hint: "estimate only \u2014 re-run with --yes to upload and publish (permanent, non-refundable)"
|
|
896
|
+
});
|
|
897
|
+
return 0;
|
|
898
|
+
}
|
|
899
|
+
if (!io2.isInteractive) {
|
|
900
|
+
io2.err(
|
|
901
|
+
"refusing to spend channel funds without confirmation in a non-interactive session \u2014 re-run with --yes (or use --json for an estimate)"
|
|
902
|
+
);
|
|
903
|
+
return 1;
|
|
904
|
+
}
|
|
905
|
+
const proceed = await io2.confirm(
|
|
906
|
+
`Proceed with paid push (total ${plan.estimate.totalFee} base units)? [y/N] `
|
|
907
|
+
);
|
|
908
|
+
if (!proceed) {
|
|
909
|
+
io2.err("aborted \u2014 nothing was uploaded or published.");
|
|
910
|
+
return 1;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
const pushResult = await executePush({
|
|
914
|
+
plan: pushPlan,
|
|
915
|
+
publisher: standaloneCtx.publisher,
|
|
916
|
+
remoteState,
|
|
917
|
+
repoReader: reader,
|
|
918
|
+
relayUrls: relaysUsed
|
|
919
|
+
});
|
|
920
|
+
const result = serializePushResult(pushPlan, pushResult);
|
|
921
|
+
if (flags.json) {
|
|
922
|
+
io2.emitJson({
|
|
923
|
+
command: "push",
|
|
924
|
+
repoId,
|
|
925
|
+
identity,
|
|
926
|
+
executed: true,
|
|
927
|
+
upToDate: false,
|
|
928
|
+
plan,
|
|
929
|
+
result
|
|
930
|
+
});
|
|
931
|
+
} else {
|
|
932
|
+
for (const line of renderResult(result)) io2.out(line);
|
|
933
|
+
}
|
|
934
|
+
return 0;
|
|
935
|
+
} catch (err) {
|
|
936
|
+
return emitCliError(io2, flags.json, "push", err);
|
|
937
|
+
} finally {
|
|
938
|
+
if (standaloneCtx) {
|
|
939
|
+
try {
|
|
940
|
+
await standaloneCtx.stop();
|
|
941
|
+
} catch {
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// src/cli/balance.ts
|
|
948
|
+
var BALANCE_USAGE = `Usage: rig balance [--json]
|
|
949
|
+
|
|
950
|
+
Show the active identity's money: on-chain wallet balances (per configured
|
|
951
|
+
chain, read from the settlement chain the payment channels actually use) and
|
|
952
|
+
recorded payment-channel holdings (deposited / claimed / available). Free \u2014
|
|
953
|
+
reads chain RPCs and local state only; nothing is signed or paid.
|
|
954
|
+
|
|
955
|
+
Options:
|
|
956
|
+
--json machine-readable envelope (base units as strings)
|
|
957
|
+
-h, --help show this help`;
|
|
958
|
+
async function runBalance(args, deps) {
|
|
959
|
+
const { io: io2, env } = deps;
|
|
960
|
+
let json = false;
|
|
961
|
+
try {
|
|
962
|
+
const { values } = parseArgs3({
|
|
963
|
+
args,
|
|
964
|
+
options: {
|
|
965
|
+
json: { type: "boolean", default: false },
|
|
966
|
+
help: { type: "boolean", short: "h", default: false }
|
|
967
|
+
}
|
|
968
|
+
});
|
|
969
|
+
if (values.help) {
|
|
970
|
+
io2.out(BALANCE_USAGE);
|
|
971
|
+
return 0;
|
|
972
|
+
}
|
|
973
|
+
json = values.json ?? false;
|
|
974
|
+
} catch (err) {
|
|
975
|
+
io2.err(err instanceof Error ? err.message : String(err));
|
|
976
|
+
io2.err(BALANCE_USAGE);
|
|
977
|
+
return 2;
|
|
978
|
+
}
|
|
979
|
+
let ctx;
|
|
980
|
+
try {
|
|
981
|
+
ctx = await (deps.loadStandalone ?? defaultLoadStandalone)({
|
|
982
|
+
env,
|
|
983
|
+
cwd: deps.cwd,
|
|
984
|
+
warn: (line) => io2.err(line),
|
|
985
|
+
// Free read: works without a proxy/BTP write uplink.
|
|
986
|
+
requireUplink: false
|
|
987
|
+
});
|
|
988
|
+
const identity = identityReport(ctx);
|
|
989
|
+
const wallet = ctx.money ? await ctx.money.walletBalances() : [];
|
|
990
|
+
const store = new ChannelMapStore(resolveChannelPaths(env));
|
|
991
|
+
const channels = store.list().filter((record) => record.identity === identity.pubkey).map((record) => {
|
|
992
|
+
const watermark = store.readWatermark(record.channelId);
|
|
993
|
+
const deposited = record.depositTotal;
|
|
994
|
+
const claimed = watermark?.cumulativeAmount;
|
|
995
|
+
let available = null;
|
|
996
|
+
if (deposited !== void 0 && claimed !== void 0) {
|
|
997
|
+
const remaining = BigInt(deposited) - BigInt(claimed);
|
|
998
|
+
available = (remaining > 0n ? remaining : 0n).toString();
|
|
999
|
+
}
|
|
1000
|
+
return {
|
|
1001
|
+
channelId: record.channelId,
|
|
1002
|
+
destination: record.destination,
|
|
1003
|
+
peerId: record.peerId,
|
|
1004
|
+
chain: record.chain,
|
|
1005
|
+
status: channelStatus(watermark),
|
|
1006
|
+
depositTotal: deposited ?? null,
|
|
1007
|
+
cumulativeClaimed: claimed ?? null,
|
|
1008
|
+
nonce: watermark?.nonce ?? null,
|
|
1009
|
+
available
|
|
1010
|
+
};
|
|
1011
|
+
});
|
|
1012
|
+
if (json) {
|
|
1013
|
+
io2.emitJson({
|
|
1014
|
+
command: "balance",
|
|
1015
|
+
identity,
|
|
1016
|
+
wallet,
|
|
1017
|
+
channels
|
|
1018
|
+
});
|
|
1019
|
+
return 0;
|
|
1020
|
+
}
|
|
1021
|
+
io2.out(renderIdentityLine(identity));
|
|
1022
|
+
io2.out("");
|
|
1023
|
+
io2.out("Wallet (on-chain):");
|
|
1024
|
+
if (wallet.length === 0) {
|
|
1025
|
+
io2.out(
|
|
1026
|
+
" (no balance readable \u2014 no chain configured, the RPC is unreachable, or the chain's keys derive only during a client start)"
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
for (const balance of wallet) {
|
|
1030
|
+
io2.out(
|
|
1031
|
+
` ${balance.chain.padEnd(7)} ${balance.address} ${balance.amount}` + (balance.asset ? ` ${balance.asset}` : " base units") + (balance.assetScale !== void 0 ? ` (scale ${balance.assetScale})` : "")
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
1034
|
+
io2.out("");
|
|
1035
|
+
io2.out("Channels (recorded):");
|
|
1036
|
+
if (channels.length === 0) {
|
|
1037
|
+
io2.out(
|
|
1038
|
+
" none \u2014 paid commands record their channel on first use; `rig channel open` records one explicitly."
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
for (const c of channels) {
|
|
1042
|
+
io2.out(
|
|
1043
|
+
` ${c.channelId} [${c.status}] deposited ${c.depositTotal ?? "?"} claimed ${c.cumulativeClaimed ?? "?"} available ${c.available ?? "?"} (${c.chain} \u2192 ${c.destination})`
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
1046
|
+
return 0;
|
|
1047
|
+
} catch (err) {
|
|
1048
|
+
return emitCliError(io2, json, "balance", err);
|
|
1049
|
+
} finally {
|
|
1050
|
+
if (ctx) {
|
|
1051
|
+
try {
|
|
1052
|
+
await ctx.stop();
|
|
1053
|
+
} catch {
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// src/cli/channel.ts
|
|
1060
|
+
import { parseArgs as parseArgs4 } from "util";
|
|
1061
|
+
var CHANNEL_USAGE = `Usage: rig channel <subcommand>
|
|
1062
|
+
|
|
1063
|
+
Manage the payment channels paid rig commands hold with relay/store peers.
|
|
1064
|
+
Paid commands open a channel lazily on first use and record it under
|
|
1065
|
+
TOON_CLIENT_HOME (default ~/.toon-client, rig-channels.json), so later
|
|
1066
|
+
invocations resume the same channel instead of opening a new one per run.
|
|
1067
|
+
|
|
1068
|
+
Subcommands:
|
|
1069
|
+
list [--json] show recorded channels \u2014 peer, chain, channel id, deposit,
|
|
1070
|
+
cumulative claimed, status. Free: reads local state only.
|
|
1071
|
+
|
|
1072
|
+
open [--peer <ilp-destination>] [--deposit <base-units>]
|
|
1073
|
+
explicitly open the payment channel for a peer \u2014 the SAME
|
|
1074
|
+
path paid commands use lazily: resumes the recorded live
|
|
1075
|
+
channel if one exists (no on-chain spend), else opens and
|
|
1076
|
+
records a fresh one (locks the peer-negotiated initial
|
|
1077
|
+
deposit on-chain). --peer is the ILP destination to anchor
|
|
1078
|
+
to (default: the configured destination, e.g. the devnet
|
|
1079
|
+
apex); --deposit adds that much extra collateral after the
|
|
1080
|
+
open/resume. On-chain: asks for confirmation (--yes skips).
|
|
1081
|
+
|
|
1082
|
+
close <channelId>
|
|
1083
|
+
close a recorded channel \u2014 an on-chain tx that starts the
|
|
1084
|
+
settlement challenge window (the peer's settlementTimeout).
|
|
1085
|
+
The channel stops paying immediately; the remaining
|
|
1086
|
+
collateral stays locked until \`rig channel settle\` after
|
|
1087
|
+
the window elapses. Asks for confirmation (--yes skips).
|
|
1088
|
+
|
|
1089
|
+
settle <channelId>
|
|
1090
|
+
settle a closed channel once its challenge window elapsed \u2014
|
|
1091
|
+
an on-chain tx that releases the remaining collateral back
|
|
1092
|
+
to your wallet. Refused (without spending gas) while the
|
|
1093
|
+
window is still open. Asks for confirmation (--yes skips).
|
|
1094
|
+
|
|
1095
|
+
Common options: --json (machine-readable envelopes; without --yes lifecycle
|
|
1096
|
+
commands emit a pure plan and execute nothing), --yes, -h/--help.`;
|
|
1097
|
+
async function runChannel(args, deps) {
|
|
1098
|
+
const { io: io2 } = deps;
|
|
1099
|
+
const [sub, ...rest] = args;
|
|
1100
|
+
switch (sub) {
|
|
1101
|
+
case "list":
|
|
1102
|
+
return runChannelList(rest, deps);
|
|
1103
|
+
case "open":
|
|
1104
|
+
return runChannelOpen(rest, deps);
|
|
1105
|
+
case "close":
|
|
1106
|
+
return runChannelWithdrawStep(rest, deps, "close");
|
|
1107
|
+
case "settle":
|
|
1108
|
+
return runChannelWithdrawStep(rest, deps, "settle");
|
|
1109
|
+
case "help":
|
|
1110
|
+
case "--help":
|
|
1111
|
+
case "-h":
|
|
1112
|
+
io2.out(CHANNEL_USAGE);
|
|
1113
|
+
return 0;
|
|
1114
|
+
case void 0:
|
|
1115
|
+
io2.err(CHANNEL_USAGE);
|
|
1116
|
+
return 2;
|
|
1117
|
+
default:
|
|
1118
|
+
io2.err(`rig channel: unknown subcommand ${JSON.stringify(sub)}`);
|
|
1119
|
+
io2.err(CHANNEL_USAGE);
|
|
1120
|
+
return 2;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
function runChannelList(args, deps) {
|
|
1124
|
+
const { io: io2, env } = deps;
|
|
1125
|
+
let json = false;
|
|
1126
|
+
try {
|
|
1127
|
+
const { values } = parseArgs4({
|
|
1128
|
+
args,
|
|
1129
|
+
options: {
|
|
1130
|
+
json: { type: "boolean", default: false },
|
|
1131
|
+
help: { type: "boolean", short: "h", default: false }
|
|
1132
|
+
}
|
|
1133
|
+
});
|
|
1134
|
+
if (values.help) {
|
|
1135
|
+
io2.out(CHANNEL_USAGE);
|
|
1136
|
+
return 0;
|
|
1137
|
+
}
|
|
1138
|
+
json = values.json ?? false;
|
|
1139
|
+
} catch (err) {
|
|
1140
|
+
io2.err(err instanceof Error ? err.message : String(err));
|
|
1141
|
+
io2.err(CHANNEL_USAGE);
|
|
1142
|
+
return 2;
|
|
1143
|
+
}
|
|
1144
|
+
try {
|
|
1145
|
+
const paths = resolveChannelPaths(env);
|
|
1146
|
+
const store = new ChannelMapStore(paths);
|
|
1147
|
+
const records = store.list();
|
|
1148
|
+
const rows = records.map((record) => describeChannel(record, store));
|
|
1149
|
+
if (json) {
|
|
1150
|
+
io2.emitJson({ command: "channel list", channels: rows });
|
|
1151
|
+
return 0;
|
|
1152
|
+
}
|
|
1153
|
+
if (rows.length === 0) {
|
|
1154
|
+
io2.out(
|
|
1155
|
+
"No payment channels recorded \u2014 paid rig commands (push, issue, comment, pr) record the channel they open on first use, and `rig channel open` records an explicit one."
|
|
1156
|
+
);
|
|
1157
|
+
return 0;
|
|
1158
|
+
}
|
|
1159
|
+
io2.out(
|
|
1160
|
+
`${rows.length} payment channel${rows.length === 1 ? "" : "s"} recorded in ${paths.mapPath}:`
|
|
1161
|
+
);
|
|
1162
|
+
for (const row of rows) {
|
|
1163
|
+
io2.out("");
|
|
1164
|
+
for (const line of renderChannel(row)) io2.out(line);
|
|
1165
|
+
}
|
|
1166
|
+
return 0;
|
|
1167
|
+
} catch (err) {
|
|
1168
|
+
return emitCliError(io2, json, "channel list", err);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
function describeChannel(record, store) {
|
|
1172
|
+
const watermark = store.readWatermark(
|
|
1173
|
+
record.channelId
|
|
1174
|
+
);
|
|
1175
|
+
return {
|
|
1176
|
+
channelId: record.channelId,
|
|
1177
|
+
peerId: record.peerId,
|
|
1178
|
+
identity: record.identity,
|
|
1179
|
+
destination: record.destination,
|
|
1180
|
+
chain: record.chain,
|
|
1181
|
+
tokenNetwork: record.tokenNetwork,
|
|
1182
|
+
depositTotal: record.depositTotal ?? null,
|
|
1183
|
+
cumulativeClaimed: watermark?.cumulativeAmount ?? null,
|
|
1184
|
+
nonce: watermark?.nonce ?? null,
|
|
1185
|
+
status: channelStatus(watermark),
|
|
1186
|
+
openedAt: record.openedAt,
|
|
1187
|
+
lastUsedAt: record.lastUsedAt
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
function renderChannel(row) {
|
|
1191
|
+
const claimed = row.cumulativeClaimed === null ? "unknown (no local claim state)" : `${row.cumulativeClaimed} base units (nonce ${row.nonce})`;
|
|
1192
|
+
return [
|
|
1193
|
+
`channel ${row.channelId} [${row.status}]`,
|
|
1194
|
+
` peer ${row.destination} (${row.peerId})`,
|
|
1195
|
+
` identity ${row.identity.slice(0, 8)}\u2026`,
|
|
1196
|
+
` chain ${row.chain}` + (row.tokenNetwork ? ` token-network ${row.tokenNetwork}` : ""),
|
|
1197
|
+
` deposited ${row.depositTotal ?? "unrecorded"}` + (row.depositTotal ? " base units" : ""),
|
|
1198
|
+
` claimed ${claimed}`,
|
|
1199
|
+
` opened ${row.openedAt} (last used ${row.lastUsedAt})`
|
|
1200
|
+
];
|
|
1201
|
+
}
|
|
1202
|
+
async function loadMoneyContext(deps, options) {
|
|
1203
|
+
const ctx = await (deps.loadStandalone ?? defaultLoadStandalone)({
|
|
1204
|
+
env: deps.env,
|
|
1205
|
+
cwd: deps.cwd,
|
|
1206
|
+
warn: (line) => deps.io.err(line),
|
|
1207
|
+
...options?.channelDestination ? { channelDestination: options.channelDestination } : {}
|
|
1208
|
+
});
|
|
1209
|
+
const money = ctx.money;
|
|
1210
|
+
if (!money) {
|
|
1211
|
+
await ctx.stop().catch(() => void 0);
|
|
1212
|
+
throw new Error(
|
|
1213
|
+
"this standalone loader does not expose money operations \u2014 channel open/close/settle need the #263 loader"
|
|
1214
|
+
);
|
|
1215
|
+
}
|
|
1216
|
+
return { ctx, money };
|
|
1217
|
+
}
|
|
1218
|
+
async function confirmOnChain(io2, flags, question) {
|
|
1219
|
+
if (flags.yes) return "proceed";
|
|
1220
|
+
if (flags.json) return "json-plan";
|
|
1221
|
+
if (!io2.isInteractive) {
|
|
1222
|
+
io2.err(
|
|
1223
|
+
"refusing to move on-chain funds without confirmation in a non-interactive session \u2014 re-run with --yes (or use --json for a plan)"
|
|
1224
|
+
);
|
|
1225
|
+
return 1;
|
|
1226
|
+
}
|
|
1227
|
+
const proceed = await io2.confirm(question);
|
|
1228
|
+
if (proceed) return "proceed";
|
|
1229
|
+
io2.err("aborted \u2014 no on-chain transaction was sent.");
|
|
1230
|
+
return 1;
|
|
1231
|
+
}
|
|
1232
|
+
function formatUnixSeconds(value) {
|
|
1233
|
+
const ms = Number(value) * 1e3;
|
|
1234
|
+
return Number.isFinite(ms) ? new Date(ms).toISOString() : `t=${value}s`;
|
|
1235
|
+
}
|
|
1236
|
+
function secondsUntil(value, nowSec) {
|
|
1237
|
+
return Number(value) - nowSec;
|
|
1238
|
+
}
|
|
1239
|
+
async function runChannelOpen(args, deps) {
|
|
1240
|
+
const { io: io2 } = deps;
|
|
1241
|
+
let peer;
|
|
1242
|
+
let deposit;
|
|
1243
|
+
let yes = false;
|
|
1244
|
+
let json = false;
|
|
1245
|
+
try {
|
|
1246
|
+
const { values } = parseArgs4({
|
|
1247
|
+
args,
|
|
1248
|
+
options: {
|
|
1249
|
+
peer: { type: "string" },
|
|
1250
|
+
deposit: { type: "string" },
|
|
1251
|
+
yes: { type: "boolean", default: false },
|
|
1252
|
+
json: { type: "boolean", default: false },
|
|
1253
|
+
help: { type: "boolean", short: "h", default: false }
|
|
1254
|
+
}
|
|
1255
|
+
});
|
|
1256
|
+
if (values.help) {
|
|
1257
|
+
io2.out(CHANNEL_USAGE);
|
|
1258
|
+
return 0;
|
|
1259
|
+
}
|
|
1260
|
+
peer = values.peer;
|
|
1261
|
+
yes = values.yes ?? false;
|
|
1262
|
+
json = values.json ?? false;
|
|
1263
|
+
if (values.deposit !== void 0) {
|
|
1264
|
+
if (!/^\d+$/.test(values.deposit) || BigInt(values.deposit) <= 0n) {
|
|
1265
|
+
throw new Error(
|
|
1266
|
+
`--deposit must be a positive base-unit integer, got ${JSON.stringify(values.deposit)}`
|
|
1267
|
+
);
|
|
1268
|
+
}
|
|
1269
|
+
deposit = BigInt(values.deposit);
|
|
1270
|
+
}
|
|
1271
|
+
} catch (err) {
|
|
1272
|
+
io2.err(err instanceof Error ? err.message : String(err));
|
|
1273
|
+
io2.err(CHANNEL_USAGE);
|
|
1274
|
+
return 2;
|
|
1275
|
+
}
|
|
1276
|
+
let ctx;
|
|
1277
|
+
try {
|
|
1278
|
+
const loaded = await loadMoneyContext(deps, {
|
|
1279
|
+
...peer ? { channelDestination: peer } : {}
|
|
1280
|
+
});
|
|
1281
|
+
ctx = loaded.ctx;
|
|
1282
|
+
const identity = identityReport(ctx);
|
|
1283
|
+
const plan = {
|
|
1284
|
+
destination: peer ?? null,
|
|
1285
|
+
deposit: deposit?.toString() ?? null
|
|
1286
|
+
};
|
|
1287
|
+
if (!json) {
|
|
1288
|
+
io2.out("Channel open plan:");
|
|
1289
|
+
io2.out(` peer (ILP) ${peer ?? "(configured default destination)"}`);
|
|
1290
|
+
io2.out(
|
|
1291
|
+
` deposit ${deposit !== void 0 ? `+${deposit} base units of extra collateral after the open/resume` : "none beyond the peer-negotiated initial deposit"}`
|
|
1292
|
+
);
|
|
1293
|
+
io2.out(renderIdentityLine(identity));
|
|
1294
|
+
io2.out(
|
|
1295
|
+
"If a live channel is already recorded for this peer it is RESUMED (no on-chain spend); otherwise an on-chain channel open locks the peer-negotiated initial deposit from your wallet (plus gas)."
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
const gate = await confirmOnChain(
|
|
1299
|
+
io2,
|
|
1300
|
+
{ yes, json },
|
|
1301
|
+
"Proceed with on-chain channel open? [y/N] "
|
|
1302
|
+
);
|
|
1303
|
+
if (gate === "json-plan") {
|
|
1304
|
+
io2.emitJson({
|
|
1305
|
+
command: "channel open",
|
|
1306
|
+
identity,
|
|
1307
|
+
executed: false,
|
|
1308
|
+
plan,
|
|
1309
|
+
hint: "plan only \u2014 re-run with --yes to open (on-chain: locks collateral and spends gas)"
|
|
1310
|
+
});
|
|
1311
|
+
return 0;
|
|
1312
|
+
}
|
|
1313
|
+
if (gate !== "proceed") return gate;
|
|
1314
|
+
const outcome = await loaded.money.openChannel(
|
|
1315
|
+
deposit !== void 0 ? { deposit } : void 0
|
|
1316
|
+
);
|
|
1317
|
+
if (json) {
|
|
1318
|
+
io2.emitJson({
|
|
1319
|
+
command: "channel open",
|
|
1320
|
+
identity,
|
|
1321
|
+
executed: true,
|
|
1322
|
+
plan,
|
|
1323
|
+
result: {
|
|
1324
|
+
channelId: outcome.channelId,
|
|
1325
|
+
resumed: outcome.resumed,
|
|
1326
|
+
destination: outcome.destination,
|
|
1327
|
+
chain: outcome.chain ?? null,
|
|
1328
|
+
peerId: outcome.peerId ?? null,
|
|
1329
|
+
depositTotal: outcome.depositTotal ?? null,
|
|
1330
|
+
depositAdded: outcome.depositAdded ?? null,
|
|
1331
|
+
depositTxHash: outcome.depositTxHash ?? null
|
|
1332
|
+
}
|
|
1333
|
+
});
|
|
1334
|
+
return 0;
|
|
1335
|
+
}
|
|
1336
|
+
io2.out(
|
|
1337
|
+
outcome.resumed ? `Resumed recorded channel ${outcome.channelId} \u2014 no on-chain open was needed.` : `Opened channel ${outcome.channelId}${outcome.chain ? ` on ${outcome.chain}` : ""} and recorded it for reuse.`
|
|
1338
|
+
);
|
|
1339
|
+
io2.out(` peer ${outcome.destination}${outcome.peerId ? ` (${outcome.peerId})` : ""}`);
|
|
1340
|
+
if (outcome.depositAdded) {
|
|
1341
|
+
io2.out(
|
|
1342
|
+
` deposited +${outcome.depositAdded} base units` + (outcome.depositTxHash ? ` (tx ${outcome.depositTxHash})` : "")
|
|
1343
|
+
);
|
|
1344
|
+
}
|
|
1345
|
+
if (outcome.depositTotal) {
|
|
1346
|
+
io2.out(` collateral ${outcome.depositTotal} base units total on-chain`);
|
|
1347
|
+
}
|
|
1348
|
+
return 0;
|
|
1349
|
+
} catch (err) {
|
|
1350
|
+
return emitCliError(io2, json, "channel open", err);
|
|
1351
|
+
} finally {
|
|
1352
|
+
await stopQuietly(ctx);
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
function withdrawPrecheck(step, channelId, watermark, nowSec) {
|
|
1356
|
+
const status = channelStatus(watermark, nowSec);
|
|
1357
|
+
if (status === "settled") {
|
|
1358
|
+
return `channel ${channelId} is already settled \u2014 nothing left to ${step}.`;
|
|
1359
|
+
}
|
|
1360
|
+
if (step === "close") {
|
|
1361
|
+
if (status === "closing" || status === "settleable") {
|
|
1362
|
+
const at = watermark?.settleableAt;
|
|
1363
|
+
return `channel ${channelId} is already closing \u2014 ` + (status === "settleable" ? "its challenge window has elapsed; run `rig channel settle` to release the collateral." : `settleable at ${at ? formatUnixSeconds(at) : "the end of its challenge window"} (run \`rig channel settle\` then).`);
|
|
1364
|
+
}
|
|
1365
|
+
return void 0;
|
|
1366
|
+
}
|
|
1367
|
+
if (status === "open") {
|
|
1368
|
+
return `channel ${channelId} is not closed \u2014 run \`rig channel close ${channelId}\` first (settle only releases collateral after the challenge window of a closed channel).`;
|
|
1369
|
+
}
|
|
1370
|
+
if (status === "closing" && watermark?.settleableAt !== void 0) {
|
|
1371
|
+
const remain = secondsUntil(watermark.settleableAt, nowSec);
|
|
1372
|
+
return `channel ${channelId} is not settleable yet \u2014 the challenge window is still open (${remain}s remain, settleable at ${formatUnixSeconds(watermark.settleableAt)}). Nothing was spent; re-run after that time.`;
|
|
1373
|
+
}
|
|
1374
|
+
return void 0;
|
|
1375
|
+
}
|
|
1376
|
+
async function runChannelWithdrawStep(args, deps, step) {
|
|
1377
|
+
const { io: io2, env } = deps;
|
|
1378
|
+
const command = `channel ${step}`;
|
|
1379
|
+
let channelId;
|
|
1380
|
+
let yes = false;
|
|
1381
|
+
let json = false;
|
|
1382
|
+
try {
|
|
1383
|
+
const { values, positionals } = parseArgs4({
|
|
1384
|
+
args,
|
|
1385
|
+
options: {
|
|
1386
|
+
yes: { type: "boolean", default: false },
|
|
1387
|
+
json: { type: "boolean", default: false },
|
|
1388
|
+
help: { type: "boolean", short: "h", default: false }
|
|
1389
|
+
},
|
|
1390
|
+
allowPositionals: true
|
|
1391
|
+
});
|
|
1392
|
+
if (values.help) {
|
|
1393
|
+
io2.out(CHANNEL_USAGE);
|
|
1394
|
+
return 0;
|
|
1395
|
+
}
|
|
1396
|
+
yes = values.yes ?? false;
|
|
1397
|
+
json = values.json ?? false;
|
|
1398
|
+
if (positionals.length !== 1) {
|
|
1399
|
+
throw new Error(
|
|
1400
|
+
`rig channel ${step} takes exactly one <channelId> (got ${positionals.length}) \u2014 \`rig channel list\` shows recorded channels`
|
|
1401
|
+
);
|
|
1402
|
+
}
|
|
1403
|
+
channelId = positionals[0];
|
|
1404
|
+
} catch (err) {
|
|
1405
|
+
io2.err(err instanceof Error ? err.message : String(err));
|
|
1406
|
+
io2.err(CHANNEL_USAGE);
|
|
744
1407
|
return 2;
|
|
745
1408
|
}
|
|
746
|
-
|
|
747
|
-
io.out(PUSH_USAGE);
|
|
748
|
-
return 0;
|
|
749
|
-
}
|
|
750
|
-
let standaloneCtx;
|
|
1409
|
+
let ctx;
|
|
751
1410
|
try {
|
|
752
|
-
const
|
|
753
|
-
const
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
let remoteName;
|
|
758
|
-
let refspecArgs = flags.positionals;
|
|
759
|
-
if (flags.relay.length === 0 && refspecArgs.length > 0) {
|
|
760
|
-
const first = refspecArgs[0];
|
|
761
|
-
const remoteNames = new Set(
|
|
762
|
-
(await listGitRemotes(repoRoot)).map((r) => r.name)
|
|
1411
|
+
const store = new ChannelMapStore(resolveChannelPaths(env));
|
|
1412
|
+
const record = store.list().find((r) => r.channelId === channelId);
|
|
1413
|
+
if (!record) {
|
|
1414
|
+
throw new Error(
|
|
1415
|
+
`no recorded channel ${JSON.stringify(channelId)} \u2014 \`rig channel list\` shows the channels this identity holds`
|
|
763
1416
|
);
|
|
764
|
-
if (remoteNames.has(first)) {
|
|
765
|
-
remoteName = first;
|
|
766
|
-
refspecArgs = refspecArgs.slice(1);
|
|
767
|
-
} else {
|
|
768
|
-
const { refs } = await reader.listRefs();
|
|
769
|
-
const known = new Set(refs.map((r) => r.refname));
|
|
770
|
-
if (!known.has(first) && !known.has(`refs/heads/${first}`) && !known.has(`refs/tags/${first}`)) {
|
|
771
|
-
throw new Error(
|
|
772
|
-
`${JSON.stringify(first)} is neither a configured remote nor a local branch/tag \u2014 add the relay remote (\`rig remote add ${first} <relay-url>\`; \`rig remote list\` shows configured remotes) or fix the refspec`
|
|
773
|
-
);
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
const refspecs = await selectRefspecs(
|
|
778
|
-
reader,
|
|
779
|
-
refspecArgs,
|
|
780
|
-
flags.all,
|
|
781
|
-
flags.tags
|
|
782
|
-
);
|
|
783
|
-
const resolved = await resolveRelays({
|
|
784
|
-
relayFlags: flags.relay,
|
|
785
|
-
remoteName,
|
|
786
|
-
repoRoot,
|
|
787
|
-
toonRelays: toonConfig.relays
|
|
788
|
-
});
|
|
789
|
-
if (resolved.nudge !== void 0) io.err(resolved.nudge);
|
|
790
|
-
const relaysUsed = resolved.relays;
|
|
791
|
-
if (relaysUsed.length > 1) {
|
|
792
|
-
io.err(singleRelayRefusal(resolved, "Nothing was uploaded or paid."));
|
|
793
|
-
return 1;
|
|
794
1417
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
1418
|
+
const watermark = store.readWatermark(channelId);
|
|
1419
|
+
const nowSec = Math.floor(Date.now() / 1e3);
|
|
1420
|
+
const refusal = withdrawPrecheck(step, channelId, watermark, nowSec);
|
|
1421
|
+
if (refusal) throw new Error(refusal);
|
|
1422
|
+
const loaded = await loadMoneyContext(deps);
|
|
1423
|
+
ctx = loaded.ctx;
|
|
1424
|
+
const identity = identityReport(ctx);
|
|
1425
|
+
if (record.identity !== identity.pubkey) {
|
|
1426
|
+
throw new Error(
|
|
1427
|
+
`channel ${channelId} was opened by identity ${record.identity.slice(0, 8)}\u2026 but the active identity is ${identity.pubkey.slice(0, 8)}\u2026 (from ${identity.sourceLabel}) \u2014 on-chain ${step} must be signed by the opener's wallet key. Switch RIG_MNEMONIC (or the keystore) to that identity.`
|
|
804
1428
|
);
|
|
805
1429
|
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
});
|
|
820
|
-
const plan = serializePushPlan(pushPlan);
|
|
821
|
-
const upToDate = plan.refUpdates.every((u) => u.kind === "up-to-date");
|
|
822
|
-
if (upToDate) {
|
|
823
|
-
if (flags.json) {
|
|
824
|
-
io.out(
|
|
825
|
-
jsonOut({ command: "push", repoId, identity, executed: false, upToDate: true, plan })
|
|
1430
|
+
if (!json) {
|
|
1431
|
+
const claimed = watermark?.cumulativeAmount;
|
|
1432
|
+
io2.out(`Channel ${step} plan:`);
|
|
1433
|
+
io2.out(` channel ${channelId} [${channelStatus(watermark, nowSec)}]`);
|
|
1434
|
+
io2.out(` peer ${record.destination} (${record.peerId})`);
|
|
1435
|
+
io2.out(` chain ${record.chain}`);
|
|
1436
|
+
io2.out(
|
|
1437
|
+
` deposited ${record.depositTotal ?? "unrecorded"}` + (claimed !== void 0 ? ` claimed ${claimed} base units` : "")
|
|
1438
|
+
);
|
|
1439
|
+
io2.out(renderIdentityLine(identity));
|
|
1440
|
+
if (step === "close") {
|
|
1441
|
+
io2.out(
|
|
1442
|
+
"Closing is an ON-CHAIN transaction (gas) that starts the settlement challenge window: the channel stops paying immediately, and the remaining collateral stays locked until `rig channel settle` succeeds after the window elapses."
|
|
826
1443
|
);
|
|
827
1444
|
} else {
|
|
828
|
-
|
|
1445
|
+
io2.out(
|
|
1446
|
+
"Settling is an ON-CHAIN transaction (gas) that releases the remaining collateral back to your wallet. The chain's challenge window is authoritative \u2014 a too-early settle is refused before any gas is spent."
|
|
1447
|
+
);
|
|
829
1448
|
}
|
|
830
|
-
return 0;
|
|
831
1449
|
}
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
1450
|
+
const gate = await confirmOnChain(
|
|
1451
|
+
io2,
|
|
1452
|
+
{ yes, json },
|
|
1453
|
+
`Proceed with on-chain channel ${step}? [y/N] `
|
|
1454
|
+
);
|
|
1455
|
+
if (gate === "json-plan") {
|
|
1456
|
+
io2.emitJson({
|
|
1457
|
+
command,
|
|
1458
|
+
identity,
|
|
1459
|
+
executed: false,
|
|
1460
|
+
channelId,
|
|
1461
|
+
hint: `plan only \u2014 re-run with --yes to ${step} (on-chain transaction)`
|
|
1462
|
+
});
|
|
1463
|
+
return 0;
|
|
835
1464
|
}
|
|
836
|
-
if (
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
1465
|
+
if (gate !== "proceed") return gate;
|
|
1466
|
+
if (step === "close") {
|
|
1467
|
+
const result2 = await loaded.money.closeChannel(record);
|
|
1468
|
+
if (json) {
|
|
1469
|
+
io2.emitJson({
|
|
1470
|
+
command,
|
|
1471
|
+
identity,
|
|
1472
|
+
executed: true,
|
|
1473
|
+
channelId,
|
|
1474
|
+
result: {
|
|
1475
|
+
txHash: result2.txHash ?? null,
|
|
1476
|
+
closedAt: result2.closedAt,
|
|
1477
|
+
settleableAt: result2.settleableAt
|
|
1478
|
+
}
|
|
1479
|
+
});
|
|
849
1480
|
return 0;
|
|
850
1481
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
"refusing to spend channel funds without confirmation in a non-interactive session \u2014 re-run with --yes (or use --json for an estimate)"
|
|
854
|
-
);
|
|
855
|
-
return 1;
|
|
856
|
-
}
|
|
857
|
-
const proceed = await io.confirm(
|
|
858
|
-
`Proceed with paid push (total ${plan.estimate.totalFee} base units)? [y/N] `
|
|
1482
|
+
io2.out(
|
|
1483
|
+
`Channel ${channelId} is closing` + (result2.txHash ? ` (tx ${result2.txHash})` : "") + "."
|
|
859
1484
|
);
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
return 1;
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
const pushResult = await executePush({
|
|
866
|
-
plan: pushPlan,
|
|
867
|
-
publisher: standaloneCtx.publisher,
|
|
868
|
-
remoteState,
|
|
869
|
-
repoReader: reader,
|
|
870
|
-
relayUrls: relaysUsed
|
|
871
|
-
});
|
|
872
|
-
const result = serializePushResult(pushPlan, pushResult);
|
|
873
|
-
if (flags.json) {
|
|
874
|
-
io.out(
|
|
875
|
-
jsonOut({ command: "push", repoId, identity, executed: true, upToDate: false, plan, result })
|
|
1485
|
+
io2.out(
|
|
1486
|
+
` challenge window: settleable at ${formatUnixSeconds(result2.settleableAt)} (~${Math.max(0, secondsUntil(result2.settleableAt, nowSec))}s from now)`
|
|
876
1487
|
);
|
|
877
|
-
|
|
878
|
-
|
|
1488
|
+
io2.out(
|
|
1489
|
+
` run \`rig channel settle ${channelId}\` after that time to release the collateral.`
|
|
1490
|
+
);
|
|
1491
|
+
return 0;
|
|
1492
|
+
}
|
|
1493
|
+
const result = await loaded.money.settleChannel(record);
|
|
1494
|
+
if (json) {
|
|
1495
|
+
io2.emitJson({
|
|
1496
|
+
command,
|
|
1497
|
+
identity,
|
|
1498
|
+
executed: true,
|
|
1499
|
+
channelId,
|
|
1500
|
+
result: { txHash: result.txHash ?? null }
|
|
1501
|
+
});
|
|
1502
|
+
return 0;
|
|
879
1503
|
}
|
|
1504
|
+
io2.out(
|
|
1505
|
+
`Channel ${channelId} settled` + (result.txHash ? ` (tx ${result.txHash})` : "") + " \u2014 the remaining collateral was released to your wallet."
|
|
1506
|
+
);
|
|
880
1507
|
return 0;
|
|
881
1508
|
} catch (err) {
|
|
882
|
-
|
|
883
|
-
if (flags.json) {
|
|
884
|
-
io.out(JSON.stringify({ command: "push", ...described.json }, null, 2));
|
|
885
|
-
} else {
|
|
886
|
-
for (const line of described.lines) io.err(line);
|
|
887
|
-
}
|
|
888
|
-
return 1;
|
|
1509
|
+
return emitCliError(io2, json, command, err);
|
|
889
1510
|
} finally {
|
|
890
|
-
|
|
891
|
-
try {
|
|
892
|
-
await standaloneCtx.stop();
|
|
893
|
-
} catch {
|
|
894
|
-
}
|
|
895
|
-
}
|
|
1511
|
+
await stopQuietly(ctx);
|
|
896
1512
|
}
|
|
897
1513
|
}
|
|
898
|
-
function
|
|
899
|
-
|
|
1514
|
+
async function stopQuietly(ctx) {
|
|
1515
|
+
if (!ctx) return;
|
|
1516
|
+
try {
|
|
1517
|
+
await ctx.stop();
|
|
1518
|
+
} catch {
|
|
1519
|
+
}
|
|
900
1520
|
}
|
|
901
1521
|
|
|
902
1522
|
// src/cli/events.ts
|
|
1523
|
+
import { readFile } from "fs/promises";
|
|
1524
|
+
import { parseArgs as parseArgs5 } from "util";
|
|
1525
|
+
import {
|
|
1526
|
+
REPOSITORY_ANNOUNCEMENT_KIND,
|
|
1527
|
+
STATUS_APPLIED_KIND,
|
|
1528
|
+
STATUS_CLOSED_KIND,
|
|
1529
|
+
STATUS_DRAFT_KIND,
|
|
1530
|
+
STATUS_OPEN_KIND
|
|
1531
|
+
} from "@toon-protocol/core/nip34";
|
|
903
1532
|
var defaultReadStdin = async () => {
|
|
904
1533
|
if (process.stdin.isTTY) return "";
|
|
905
1534
|
const chunks = [];
|
|
@@ -1014,7 +1643,7 @@ function pickCommon(values) {
|
|
|
1014
1643
|
}
|
|
1015
1644
|
async function runEvent(opts) {
|
|
1016
1645
|
const { command, flags, deps, actionLabel } = opts;
|
|
1017
|
-
const { io, env } = deps;
|
|
1646
|
+
const { io: io2, env } = deps;
|
|
1018
1647
|
let standaloneCtx;
|
|
1019
1648
|
try {
|
|
1020
1649
|
let repoRoot;
|
|
@@ -1032,16 +1661,18 @@ async function runEvent(opts) {
|
|
|
1032
1661
|
repoRoot,
|
|
1033
1662
|
toonRelays: toonConfig.relays
|
|
1034
1663
|
});
|
|
1035
|
-
if (resolved.nudge !== void 0)
|
|
1664
|
+
if (resolved.nudge !== void 0) io2.err(resolved.nudge);
|
|
1036
1665
|
const relaysUsed = resolved.relays;
|
|
1037
1666
|
if (relaysUsed.length > 1) {
|
|
1038
|
-
|
|
1667
|
+
io2.err(singleRelayRefusal(resolved, "Nothing was published or paid."));
|
|
1039
1668
|
return 1;
|
|
1040
1669
|
}
|
|
1041
1670
|
standaloneCtx = await (deps.loadStandalone ?? defaultLoadStandalone)({
|
|
1042
1671
|
env,
|
|
1043
1672
|
cwd: deps.cwd,
|
|
1044
|
-
warn: (line) =>
|
|
1673
|
+
warn: (line) => io2.err(line),
|
|
1674
|
+
// Relay-origin for #264 network bootstrap (announce discovery).
|
|
1675
|
+
...relaysUsed[0] !== void 0 ? { relayUrl: relaysUsed[0] } : {}
|
|
1045
1676
|
});
|
|
1046
1677
|
const identity = identityReport(standaloneCtx);
|
|
1047
1678
|
const fee = (await standaloneCtx.publisher.getFeeRates()).eventFee.toString();
|
|
@@ -1051,64 +1682,54 @@ async function runEvent(opts) {
|
|
|
1051
1682
|
const action = `kind:${event.kind} ${actionLabel}`;
|
|
1052
1683
|
if (!flags.json) {
|
|
1053
1684
|
for (const line of renderEventPlan({ action, addr, identity, fee })) {
|
|
1054
|
-
|
|
1685
|
+
io2.out(line);
|
|
1055
1686
|
}
|
|
1056
1687
|
}
|
|
1057
1688
|
if (!flags.yes) {
|
|
1058
1689
|
if (flags.json) {
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
})
|
|
1069
|
-
);
|
|
1690
|
+
io2.emitJson({
|
|
1691
|
+
command,
|
|
1692
|
+
repoAddr: addr,
|
|
1693
|
+
identity,
|
|
1694
|
+
kind: event.kind,
|
|
1695
|
+
executed: false,
|
|
1696
|
+
feeEstimate: fee,
|
|
1697
|
+
hint: "estimate only \u2014 re-run with --yes to publish (permanent, non-refundable)"
|
|
1698
|
+
});
|
|
1070
1699
|
return 0;
|
|
1071
1700
|
}
|
|
1072
|
-
if (!
|
|
1073
|
-
|
|
1701
|
+
if (!io2.isInteractive) {
|
|
1702
|
+
io2.err(
|
|
1074
1703
|
"refusing to spend channel funds without confirmation in a non-interactive session \u2014 re-run with --yes (or use --json for an estimate)"
|
|
1075
1704
|
);
|
|
1076
1705
|
return 1;
|
|
1077
1706
|
}
|
|
1078
|
-
const proceed = await
|
|
1707
|
+
const proceed = await io2.confirm(
|
|
1079
1708
|
`Proceed with paid publish (${feeLabel(fee)})? [y/N] `
|
|
1080
1709
|
);
|
|
1081
1710
|
if (!proceed) {
|
|
1082
|
-
|
|
1711
|
+
io2.err("aborted \u2014 nothing was published.");
|
|
1083
1712
|
return 1;
|
|
1084
1713
|
}
|
|
1085
1714
|
}
|
|
1086
1715
|
const receipt = await standaloneCtx.publisher.publishEvent(event, relaysUsed);
|
|
1087
1716
|
const result = serializeEventReceipt(event.kind, receipt);
|
|
1088
1717
|
if (flags.json) {
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
})
|
|
1099
|
-
);
|
|
1718
|
+
io2.emitJson({
|
|
1719
|
+
command,
|
|
1720
|
+
repoAddr: addr,
|
|
1721
|
+
identity,
|
|
1722
|
+
kind: result.kind,
|
|
1723
|
+
executed: true,
|
|
1724
|
+
feeEstimate: fee,
|
|
1725
|
+
result
|
|
1726
|
+
});
|
|
1100
1727
|
} else {
|
|
1101
|
-
for (const line of renderEventReceipt(action, result))
|
|
1728
|
+
for (const line of renderEventReceipt(action, result)) io2.out(line);
|
|
1102
1729
|
}
|
|
1103
1730
|
return 0;
|
|
1104
1731
|
} catch (err) {
|
|
1105
|
-
|
|
1106
|
-
if (flags.json) {
|
|
1107
|
-
io.out(JSON.stringify({ command, ...described.json }, null, 2));
|
|
1108
|
-
} else {
|
|
1109
|
-
for (const line of described.lines) io.err(line);
|
|
1110
|
-
}
|
|
1111
|
-
return 1;
|
|
1732
|
+
return emitCliError(io2, flags.json, command, err);
|
|
1112
1733
|
} finally {
|
|
1113
1734
|
if (standaloneCtx) {
|
|
1114
1735
|
try {
|
|
@@ -1118,21 +1739,18 @@ async function runEvent(opts) {
|
|
|
1118
1739
|
}
|
|
1119
1740
|
}
|
|
1120
1741
|
}
|
|
1121
|
-
function jsonOut2(output) {
|
|
1122
|
-
return JSON.stringify(output, null, 2);
|
|
1123
|
-
}
|
|
1124
1742
|
async function runIssue(args, deps) {
|
|
1125
|
-
const { io } = deps;
|
|
1743
|
+
const { io: io2 } = deps;
|
|
1126
1744
|
const [sub, ...rest] = args;
|
|
1127
1745
|
if (sub === "--help" || sub === "-h" || sub === "help") {
|
|
1128
|
-
|
|
1746
|
+
io2.out(ISSUE_USAGE);
|
|
1129
1747
|
return 0;
|
|
1130
1748
|
}
|
|
1131
1749
|
if (sub !== "create") {
|
|
1132
|
-
|
|
1750
|
+
io2.err(
|
|
1133
1751
|
sub === void 0 ? "missing subcommand: rig issue create" : `unknown rig issue subcommand: ${sub}`
|
|
1134
1752
|
);
|
|
1135
|
-
|
|
1753
|
+
io2.err(ISSUE_USAGE);
|
|
1136
1754
|
return 2;
|
|
1137
1755
|
}
|
|
1138
1756
|
let flags;
|
|
@@ -1141,7 +1759,7 @@ async function runIssue(args, deps) {
|
|
|
1141
1759
|
let bodyFile;
|
|
1142
1760
|
let labels;
|
|
1143
1761
|
try {
|
|
1144
|
-
const { values } =
|
|
1762
|
+
const { values } = parseArgs5({
|
|
1145
1763
|
args: rest,
|
|
1146
1764
|
options: {
|
|
1147
1765
|
...COMMON_OPTIONS,
|
|
@@ -1164,12 +1782,12 @@ async function runIssue(args, deps) {
|
|
|
1164
1782
|
bodyFile = values["body-file"];
|
|
1165
1783
|
labels = values.label ?? [];
|
|
1166
1784
|
} catch (err) {
|
|
1167
|
-
|
|
1168
|
-
|
|
1785
|
+
io2.err(err instanceof Error ? err.message : String(err));
|
|
1786
|
+
io2.err(ISSUE_USAGE);
|
|
1169
1787
|
return 2;
|
|
1170
1788
|
}
|
|
1171
1789
|
if (flags.help) {
|
|
1172
|
-
|
|
1790
|
+
io2.out(ISSUE_USAGE);
|
|
1173
1791
|
return 0;
|
|
1174
1792
|
}
|
|
1175
1793
|
let body;
|
|
@@ -1179,20 +1797,20 @@ async function runIssue(args, deps) {
|
|
|
1179
1797
|
try {
|
|
1180
1798
|
body = await readFile(bodyFile, "utf-8");
|
|
1181
1799
|
} catch (err) {
|
|
1182
|
-
|
|
1800
|
+
io2.err(
|
|
1183
1801
|
`cannot read --body-file ${bodyFile}: ${err instanceof Error ? err.message : String(err)}`
|
|
1184
1802
|
);
|
|
1185
1803
|
return 2;
|
|
1186
1804
|
}
|
|
1187
|
-
} else if (!
|
|
1805
|
+
} else if (!io2.isInteractive) {
|
|
1188
1806
|
body = await (deps.readStdin ?? defaultReadStdin)();
|
|
1189
1807
|
} else {
|
|
1190
|
-
|
|
1191
|
-
|
|
1808
|
+
io2.err("an issue body is required: pass --body/--body-file or pipe stdin");
|
|
1809
|
+
io2.err(ISSUE_USAGE);
|
|
1192
1810
|
return 2;
|
|
1193
1811
|
}
|
|
1194
1812
|
if (body.trim() === "") {
|
|
1195
|
-
|
|
1813
|
+
io2.err("the issue body is empty \u2014 nothing to publish");
|
|
1196
1814
|
return 2;
|
|
1197
1815
|
}
|
|
1198
1816
|
return runEvent({
|
|
@@ -1204,14 +1822,14 @@ async function runIssue(args, deps) {
|
|
|
1204
1822
|
});
|
|
1205
1823
|
}
|
|
1206
1824
|
async function runComment(args, deps) {
|
|
1207
|
-
const { io } = deps;
|
|
1825
|
+
const { io: io2 } = deps;
|
|
1208
1826
|
let flags;
|
|
1209
1827
|
let rootEventId;
|
|
1210
1828
|
let body;
|
|
1211
1829
|
let parentAuthor;
|
|
1212
1830
|
let marker;
|
|
1213
1831
|
try {
|
|
1214
|
-
const { values, positionals } =
|
|
1832
|
+
const { values, positionals } = parseArgs5({
|
|
1215
1833
|
args,
|
|
1216
1834
|
options: {
|
|
1217
1835
|
...COMMON_OPTIONS,
|
|
@@ -1223,7 +1841,7 @@ async function runComment(args, deps) {
|
|
|
1223
1841
|
});
|
|
1224
1842
|
flags = pickCommon(values);
|
|
1225
1843
|
if (flags.help) {
|
|
1226
|
-
|
|
1844
|
+
io2.out(COMMENT_USAGE);
|
|
1227
1845
|
return 0;
|
|
1228
1846
|
}
|
|
1229
1847
|
if (positionals.length !== 1) {
|
|
@@ -1245,8 +1863,8 @@ async function runComment(args, deps) {
|
|
|
1245
1863
|
}
|
|
1246
1864
|
marker = rawMarker;
|
|
1247
1865
|
} catch (err) {
|
|
1248
|
-
|
|
1249
|
-
|
|
1866
|
+
io2.err(err instanceof Error ? err.message : String(err));
|
|
1867
|
+
io2.err(COMMENT_USAGE);
|
|
1250
1868
|
return 2;
|
|
1251
1869
|
}
|
|
1252
1870
|
return runEvent({
|
|
@@ -1269,7 +1887,7 @@ function extractPatchShas(patchText) {
|
|
|
1269
1887
|
return [...patchText.matchAll(PATCH_FROM_RE)].map((m) => m[1]);
|
|
1270
1888
|
}
|
|
1271
1889
|
async function runPr(args, deps) {
|
|
1272
|
-
const { io } = deps;
|
|
1890
|
+
const { io: io2 } = deps;
|
|
1273
1891
|
const [sub, ...rest] = args;
|
|
1274
1892
|
switch (sub) {
|
|
1275
1893
|
case "create":
|
|
@@ -1279,25 +1897,25 @@ async function runPr(args, deps) {
|
|
|
1279
1897
|
case "--help":
|
|
1280
1898
|
case "-h":
|
|
1281
1899
|
case "help":
|
|
1282
|
-
|
|
1900
|
+
io2.out(PR_USAGE);
|
|
1283
1901
|
return 0;
|
|
1284
1902
|
default:
|
|
1285
|
-
|
|
1903
|
+
io2.err(
|
|
1286
1904
|
sub === void 0 ? "missing subcommand: rig pr <create|status>" : `unknown rig pr subcommand: ${sub}`
|
|
1287
1905
|
);
|
|
1288
|
-
|
|
1906
|
+
io2.err(PR_USAGE);
|
|
1289
1907
|
return 2;
|
|
1290
1908
|
}
|
|
1291
1909
|
}
|
|
1292
1910
|
async function runPrCreate(rest, deps) {
|
|
1293
|
-
const { io } = deps;
|
|
1911
|
+
const { io: io2 } = deps;
|
|
1294
1912
|
let flags;
|
|
1295
1913
|
let title;
|
|
1296
1914
|
let range;
|
|
1297
1915
|
let patchFile;
|
|
1298
1916
|
let branch;
|
|
1299
1917
|
try {
|
|
1300
|
-
const { values } =
|
|
1918
|
+
const { values } = parseArgs5({
|
|
1301
1919
|
args: rest,
|
|
1302
1920
|
options: {
|
|
1303
1921
|
...COMMON_OPTIONS,
|
|
@@ -1310,7 +1928,7 @@ async function runPrCreate(rest, deps) {
|
|
|
1310
1928
|
});
|
|
1311
1929
|
flags = pickCommon(values);
|
|
1312
1930
|
if (flags.help) {
|
|
1313
|
-
|
|
1931
|
+
io2.out(PR_CREATE_USAGE);
|
|
1314
1932
|
return 0;
|
|
1315
1933
|
}
|
|
1316
1934
|
if (values.title === void 0 || values.title === "") {
|
|
@@ -1324,8 +1942,8 @@ async function runPrCreate(rest, deps) {
|
|
|
1324
1942
|
patchFile = values["patch-file"];
|
|
1325
1943
|
branch = values.branch;
|
|
1326
1944
|
} catch (err) {
|
|
1327
|
-
|
|
1328
|
-
|
|
1945
|
+
io2.err(err instanceof Error ? err.message : String(err));
|
|
1946
|
+
io2.err(PR_CREATE_USAGE);
|
|
1329
1947
|
return 2;
|
|
1330
1948
|
}
|
|
1331
1949
|
return runEvent({
|
|
@@ -1378,19 +1996,19 @@ function isStatusValue(value) {
|
|
|
1378
1996
|
return Object.hasOwn(STATUS_KIND_BY_VALUE, value);
|
|
1379
1997
|
}
|
|
1380
1998
|
async function runPrStatus(args, deps) {
|
|
1381
|
-
const { io } = deps;
|
|
1999
|
+
const { io: io2 } = deps;
|
|
1382
2000
|
let flags;
|
|
1383
2001
|
let targetEventId;
|
|
1384
2002
|
let status;
|
|
1385
2003
|
try {
|
|
1386
|
-
const { values, positionals } =
|
|
2004
|
+
const { values, positionals } = parseArgs5({
|
|
1387
2005
|
args,
|
|
1388
2006
|
options: COMMON_OPTIONS,
|
|
1389
2007
|
allowPositionals: true
|
|
1390
2008
|
});
|
|
1391
2009
|
flags = pickCommon(values);
|
|
1392
2010
|
if (flags.help) {
|
|
1393
|
-
|
|
2011
|
+
io2.out(PR_STATUS_USAGE);
|
|
1394
2012
|
return 0;
|
|
1395
2013
|
}
|
|
1396
2014
|
if (positionals.length !== 2) {
|
|
@@ -1408,8 +2026,8 @@ async function runPrStatus(args, deps) {
|
|
|
1408
2026
|
}
|
|
1409
2027
|
status = rawStatus;
|
|
1410
2028
|
} catch (err) {
|
|
1411
|
-
|
|
1412
|
-
|
|
2029
|
+
io2.err(err instanceof Error ? err.message : String(err));
|
|
2030
|
+
io2.err(PR_STATUS_USAGE);
|
|
1413
2031
|
return 2;
|
|
1414
2032
|
}
|
|
1415
2033
|
return runEvent({
|
|
@@ -1428,6 +2046,169 @@ async function runPrStatus(args, deps) {
|
|
|
1428
2046
|
});
|
|
1429
2047
|
}
|
|
1430
2048
|
|
|
2049
|
+
// src/cli/fund.ts
|
|
2050
|
+
import { readFileSync } from "fs";
|
|
2051
|
+
import { homedir } from "os";
|
|
2052
|
+
import { join } from "path";
|
|
2053
|
+
import { parseArgs as parseArgs6 } from "util";
|
|
2054
|
+
var DEVNET_FAUCET_URL = "https://faucet.devnet.toonprotocol.dev";
|
|
2055
|
+
var CHAINS = ["evm", "solana", "mina"];
|
|
2056
|
+
var FUND_USAGE = `Usage: rig fund [options]
|
|
2057
|
+
|
|
2058
|
+
Drip devnet test funds to the active identity's wallet \u2014 free (the faucet
|
|
2059
|
+
pays). The identity comes from RIG_MNEMONIC (env or a project .env) or the
|
|
2060
|
+
~/.toon-client keystore/config; the faucet from TOON_CLIENT_FAUCET_URL, the
|
|
2061
|
+
faucetUrl config field, or the deployed devnet faucet when the configured
|
|
2062
|
+
network is devnet. The faucet drips a FIXED amount per chain (there is no
|
|
2063
|
+
--amount). On a network without a faucet, prints the wallet address(es) to
|
|
2064
|
+
fund externally instead.
|
|
2065
|
+
|
|
2066
|
+
Options:
|
|
2067
|
+
--chain <chain> evm | solana | mina (default: TOON_CLIENT_CHAIN, the
|
|
2068
|
+
\`chain\` config field, else evm)
|
|
2069
|
+
--address <address> fund this address instead of the identity's own
|
|
2070
|
+
--json machine-readable envelope
|
|
2071
|
+
-h, --help show this help`;
|
|
2072
|
+
function readFundConfig(env) {
|
|
2073
|
+
const dir = env["TOON_CLIENT_HOME"] ?? join(homedir(), ".toon-client");
|
|
2074
|
+
const configPath = join(dir, "config.json");
|
|
2075
|
+
try {
|
|
2076
|
+
return {
|
|
2077
|
+
file: JSON.parse(readFileSync(configPath, "utf8")),
|
|
2078
|
+
configPath
|
|
2079
|
+
};
|
|
2080
|
+
} catch (err) {
|
|
2081
|
+
if (err.code === "ENOENT") {
|
|
2082
|
+
return { file: {}, configPath };
|
|
2083
|
+
}
|
|
2084
|
+
throw new Error(
|
|
2085
|
+
`failed to read client config at ${configPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
2086
|
+
);
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
async function runFund(args, deps) {
|
|
2090
|
+
const { io: io2, env } = deps;
|
|
2091
|
+
let chainFlag;
|
|
2092
|
+
let addressFlag;
|
|
2093
|
+
let json = false;
|
|
2094
|
+
try {
|
|
2095
|
+
const { values } = parseArgs6({
|
|
2096
|
+
args,
|
|
2097
|
+
options: {
|
|
2098
|
+
chain: { type: "string" },
|
|
2099
|
+
address: { type: "string" },
|
|
2100
|
+
json: { type: "boolean", default: false },
|
|
2101
|
+
help: { type: "boolean", short: "h", default: false }
|
|
2102
|
+
}
|
|
2103
|
+
});
|
|
2104
|
+
if (values.help) {
|
|
2105
|
+
io2.out(FUND_USAGE);
|
|
2106
|
+
return 0;
|
|
2107
|
+
}
|
|
2108
|
+
chainFlag = values.chain;
|
|
2109
|
+
addressFlag = values.address;
|
|
2110
|
+
json = values.json ?? false;
|
|
2111
|
+
if (chainFlag !== void 0 && !CHAINS.includes(chainFlag)) {
|
|
2112
|
+
throw new Error(
|
|
2113
|
+
`--chain must be one of ${CHAINS.join(" | ")}, got ${JSON.stringify(chainFlag)}`
|
|
2114
|
+
);
|
|
2115
|
+
}
|
|
2116
|
+
} catch (err) {
|
|
2117
|
+
io2.err(err instanceof Error ? err.message : String(err));
|
|
2118
|
+
io2.err(FUND_USAGE);
|
|
2119
|
+
return 2;
|
|
2120
|
+
}
|
|
2121
|
+
try {
|
|
2122
|
+
const { file } = readFundConfig(env);
|
|
2123
|
+
const chain = chainFlag ?? env["TOON_CLIENT_CHAIN"] ?? file.chain ?? "evm";
|
|
2124
|
+
if (!CHAINS.includes(chain)) {
|
|
2125
|
+
throw new Error(
|
|
2126
|
+
`configured settlement chain ${JSON.stringify(chain)} has no faucet \u2014 pass --chain ${CHAINS.join(" | ")}`
|
|
2127
|
+
);
|
|
2128
|
+
}
|
|
2129
|
+
const network = env["TOON_CLIENT_NETWORK"] ?? file.network;
|
|
2130
|
+
const faucetUrl = env["TOON_CLIENT_FAUCET_URL"] ?? file.faucetUrl ?? (network === "devnet" ? DEVNET_FAUCET_URL : void 0);
|
|
2131
|
+
const resolved = await resolveIdentity({
|
|
2132
|
+
env,
|
|
2133
|
+
cwd: deps.cwd,
|
|
2134
|
+
warn: (line) => io2.err(line)
|
|
2135
|
+
});
|
|
2136
|
+
const identity = {
|
|
2137
|
+
pubkey: resolved.pubkey,
|
|
2138
|
+
source: resolved.source,
|
|
2139
|
+
sourceLabel: resolved.sourceLabel
|
|
2140
|
+
};
|
|
2141
|
+
const client = await import("@toon-protocol/client");
|
|
2142
|
+
const derived = await client.deriveFullIdentity(
|
|
2143
|
+
resolved.mnemonic,
|
|
2144
|
+
resolved.accountIndex
|
|
2145
|
+
);
|
|
2146
|
+
const addresses = {
|
|
2147
|
+
evm: derived.evm.address || null,
|
|
2148
|
+
solana: derived.solana.publicKey || null,
|
|
2149
|
+
mina: derived.mina.publicKey || null
|
|
2150
|
+
};
|
|
2151
|
+
if (!faucetUrl) {
|
|
2152
|
+
const guidance = `no faucet on network ${JSON.stringify(network ?? "custom")} \u2014 fund the wallet externally (send the settlement token + native gas to the address for the chain your channels settle on), or set TOON_CLIENT_FAUCET_URL / the faucetUrl config field if this network has one.`;
|
|
2153
|
+
if (json) {
|
|
2154
|
+
io2.emitJson({
|
|
2155
|
+
command: "fund",
|
|
2156
|
+
identity,
|
|
2157
|
+
funded: false,
|
|
2158
|
+
network: network ?? null,
|
|
2159
|
+
chain,
|
|
2160
|
+
addresses,
|
|
2161
|
+
guidance
|
|
2162
|
+
});
|
|
2163
|
+
return 0;
|
|
2164
|
+
}
|
|
2165
|
+
io2.out(renderIdentityLine(identity));
|
|
2166
|
+
io2.out(guidance);
|
|
2167
|
+
io2.out("Wallet addresses:");
|
|
2168
|
+
io2.out(` evm ${addresses.evm ?? "(no key derived)"}`);
|
|
2169
|
+
io2.out(` solana ${addresses.solana ?? "(no key derived)"}`);
|
|
2170
|
+
io2.out(` mina ${addresses.mina ?? "(no key derived \u2014 optional mina-signer dependency missing)"}`);
|
|
2171
|
+
return 0;
|
|
2172
|
+
}
|
|
2173
|
+
const address = addressFlag ?? addresses[chain];
|
|
2174
|
+
if (!address) {
|
|
2175
|
+
throw new Error(
|
|
2176
|
+
`no ${chain} address could be derived for this identity \u2014 pass an explicit --address (for mina, install the optional mina-signer dependency)`
|
|
2177
|
+
);
|
|
2178
|
+
}
|
|
2179
|
+
const timeoutEnv = env["TOON_CLIENT_FAUCET_TIMEOUT_MS"];
|
|
2180
|
+
const timeout = timeoutEnv && Number.isFinite(Number(timeoutEnv)) ? Number(timeoutEnv) : file.faucetTimeoutMs ?? (chain === "mina" ? 13e4 : 9e4);
|
|
2181
|
+
if (!json) {
|
|
2182
|
+
io2.out(
|
|
2183
|
+
`Requesting ${chain} drip from ${faucetUrl} for ${address} \u2026` + (chain === "mina" ? " (mina settles slowly; this can take ~2 minutes)" : "")
|
|
2184
|
+
);
|
|
2185
|
+
}
|
|
2186
|
+
const { response } = await client.fundWallet(faucetUrl, address, chain, {
|
|
2187
|
+
timeout,
|
|
2188
|
+
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}
|
|
2189
|
+
});
|
|
2190
|
+
if (json) {
|
|
2191
|
+
io2.emitJson({
|
|
2192
|
+
command: "fund",
|
|
2193
|
+
identity,
|
|
2194
|
+
funded: true,
|
|
2195
|
+
network: network ?? null,
|
|
2196
|
+
chain,
|
|
2197
|
+
address,
|
|
2198
|
+
faucetUrl,
|
|
2199
|
+
response
|
|
2200
|
+
});
|
|
2201
|
+
return 0;
|
|
2202
|
+
}
|
|
2203
|
+
io2.out(`Faucet drip succeeded: ${chain} \u2192 ${address}`);
|
|
2204
|
+
io2.out(renderIdentityLine(identity));
|
|
2205
|
+
io2.out("Re-check with `rig balance` (a drip can take a few blocks to land).");
|
|
2206
|
+
return 0;
|
|
2207
|
+
} catch (err) {
|
|
2208
|
+
return emitCliError(io2, json, "fund", err);
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
|
|
1431
2212
|
// src/cli/git-passthrough.ts
|
|
1432
2213
|
import { spawn } from "child_process";
|
|
1433
2214
|
import { constants as osConstants } from "os";
|
|
@@ -1437,11 +2218,11 @@ function signalExitCode(signal) {
|
|
|
1437
2218
|
const num = osConstants.signals[signal];
|
|
1438
2219
|
return num === void 0 ? 1 : 128 + num;
|
|
1439
2220
|
}
|
|
1440
|
-
var runGitPassthrough = (
|
|
2221
|
+
var runGitPassthrough = (argv2, options = {}) => {
|
|
1441
2222
|
const err = options.err ?? ((line) => process.stderr.write(`${line}
|
|
1442
2223
|
`));
|
|
1443
2224
|
return new Promise((resolve) => {
|
|
1444
|
-
const child = spawn("git",
|
|
2225
|
+
const child = spawn("git", argv2, {
|
|
1445
2226
|
stdio: "inherit",
|
|
1446
2227
|
...options.cwd !== void 0 ? { cwd: options.cwd } : {},
|
|
1447
2228
|
env: options.env ?? process.env
|
|
@@ -1461,7 +2242,7 @@ var runGitPassthrough = (argv, options = {}) => {
|
|
|
1461
2242
|
restoreSignals();
|
|
1462
2243
|
if (spawnErr.code === "ENOENT") {
|
|
1463
2244
|
err(
|
|
1464
|
-
`rig: git not found \u2014 \`rig ${
|
|
2245
|
+
`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.`
|
|
1465
2246
|
);
|
|
1466
2247
|
resolve(GIT_NOT_FOUND_EXIT);
|
|
1467
2248
|
return;
|
|
@@ -1478,7 +2259,7 @@ var runGitPassthrough = (argv, options = {}) => {
|
|
|
1478
2259
|
|
|
1479
2260
|
// src/cli/init.ts
|
|
1480
2261
|
import { basename } from "path";
|
|
1481
|
-
import { parseArgs as
|
|
2262
|
+
import { parseArgs as parseArgs7 } from "util";
|
|
1482
2263
|
var INIT_USAGE = `Usage: rig init [options]
|
|
1483
2264
|
|
|
1484
2265
|
Set up the current git repository for rig (one-shot, idempotent, free):
|
|
@@ -1498,7 +2279,7 @@ Options:
|
|
|
1498
2279
|
--json machine-readable report
|
|
1499
2280
|
-h, --help show this help`;
|
|
1500
2281
|
function parseInitArgs(args) {
|
|
1501
|
-
const { values, positionals } =
|
|
2282
|
+
const { values, positionals } = parseArgs7({
|
|
1502
2283
|
args,
|
|
1503
2284
|
options: {
|
|
1504
2285
|
"repo-id": { type: "string" },
|
|
@@ -1522,17 +2303,17 @@ function parseInitArgs(args) {
|
|
|
1522
2303
|
return flags;
|
|
1523
2304
|
}
|
|
1524
2305
|
async function runInit(args, deps) {
|
|
1525
|
-
const { io, env } = deps;
|
|
2306
|
+
const { io: io2, env } = deps;
|
|
1526
2307
|
let flags;
|
|
1527
2308
|
try {
|
|
1528
2309
|
flags = parseInitArgs(args);
|
|
1529
2310
|
} catch (err) {
|
|
1530
|
-
|
|
1531
|
-
|
|
2311
|
+
io2.err(err instanceof Error ? err.message : String(err));
|
|
2312
|
+
io2.err(INIT_USAGE);
|
|
1532
2313
|
return 2;
|
|
1533
2314
|
}
|
|
1534
2315
|
if (flags.help) {
|
|
1535
|
-
|
|
2316
|
+
io2.out(INIT_USAGE);
|
|
1536
2317
|
return 0;
|
|
1537
2318
|
}
|
|
1538
2319
|
try {
|
|
@@ -1545,7 +2326,7 @@ async function runInit(args, deps) {
|
|
|
1545
2326
|
const identity = await (deps.resolveIdentityImpl ?? resolveIdentity)({
|
|
1546
2327
|
env,
|
|
1547
2328
|
cwd: deps.cwd,
|
|
1548
|
-
warn: (line) =>
|
|
2329
|
+
warn: (line) => io2.err(line)
|
|
1549
2330
|
});
|
|
1550
2331
|
const existing = await readToonConfig(repoRoot);
|
|
1551
2332
|
const repoId = flags.repoId ?? existing.repoId ?? basename(repoRoot);
|
|
@@ -1582,50 +2363,44 @@ async function runInit(args, deps) {
|
|
|
1582
2363
|
migratedToonRelay,
|
|
1583
2364
|
changed
|
|
1584
2365
|
};
|
|
1585
|
-
|
|
2366
|
+
io2.emitJson(output);
|
|
1586
2367
|
return 0;
|
|
1587
2368
|
}
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
2369
|
+
io2.out(`Initialized rig for ${repoRoot}`);
|
|
2370
|
+
io2.out(renderIdentityLine(identity));
|
|
2371
|
+
io2.out(
|
|
1591
2372
|
` toon.repoid = ${repoId}` + (changed.repoId ? "" : " (unchanged)") + (existing.repoId && changed.repoId ? ` (was ${existing.repoId})` : "")
|
|
1592
2373
|
);
|
|
1593
|
-
|
|
2374
|
+
io2.out(
|
|
1594
2375
|
` toon.owner = ${identity.pubkey}` + (changed.owner ? "" : " (unchanged)") + (existing.owner && changed.owner ? ` (was ${existing.owner})` : "")
|
|
1595
2376
|
);
|
|
1596
2377
|
if (originRelay !== void 0) {
|
|
1597
|
-
|
|
2378
|
+
io2.out(
|
|
1598
2379
|
` origin = ${originRelay}` + (migratedToonRelay ? " (migrated from git config toon.relay)" : "")
|
|
1599
2380
|
);
|
|
1600
2381
|
if (migratedToonRelay) {
|
|
1601
|
-
|
|
2382
|
+
io2.out(
|
|
1602
2383
|
"note: toon.relay is deprecated \u2014 it stays readable as a fallback and is removed in v0.3; drop it now with `git config --unset-all toon.relay`."
|
|
1603
2384
|
);
|
|
1604
2385
|
}
|
|
1605
|
-
|
|
2386
|
+
io2.out('Ready: `rig push` publishes this repo via remote "origin".');
|
|
1606
2387
|
} else if (existing.relays.length > 0) {
|
|
1607
|
-
|
|
1608
|
-
|
|
2388
|
+
io2.out(` toon.relay = ${existing.relays.join(", ")} (deprecated)`);
|
|
2389
|
+
io2.out(
|
|
1609
2390
|
origin !== void 0 ? `The "origin" remote (${origin.urls.join(", ")}) is not a single relay URL, so toon.relay stays the fallback \u2014 add the relay under another name (\`rig remote add toon <relay-url>\`) and push with \`rig push toon\`.` : existing.relays.length > 1 ? `toon.relay has ${existing.relays.length} values and rig publishes to one relay \u2014 migrate the right one: \`rig remote add origin <relay-url>\`.` : "This value is not a relay URL (ws://, wss://, http://, or https://) \u2014 set a real one: `rig remote add origin <relay-url>`."
|
|
1610
2391
|
);
|
|
1611
2392
|
} else if (origin !== void 0) {
|
|
1612
|
-
|
|
2393
|
+
io2.out(
|
|
1613
2394
|
`No relay configured yet \u2014 "origin" (${origin.urls.join(", ")}) is not a relay URL, so add the relay under another name: \`rig remote add toon <relay-url>\`, then \`rig push toon\`.`
|
|
1614
2395
|
);
|
|
1615
2396
|
} else {
|
|
1616
|
-
|
|
2397
|
+
io2.out(
|
|
1617
2398
|
"No relay configured yet \u2014 add one as the follow-up step: `rig remote add origin <relay-url>` (a real git remote; `git remote -v` shows it). One-off pushes can pass --relay <url>."
|
|
1618
2399
|
);
|
|
1619
2400
|
}
|
|
1620
2401
|
return 0;
|
|
1621
2402
|
} catch (err) {
|
|
1622
|
-
|
|
1623
|
-
if (flags.json) {
|
|
1624
|
-
io.out(JSON.stringify({ command: "init", ...described.json }, null, 2));
|
|
1625
|
-
} else {
|
|
1626
|
-
for (const line of described.lines) io.err(line);
|
|
1627
|
-
}
|
|
1628
|
-
return 1;
|
|
2403
|
+
return emitCliError(io2, flags.json, "init", err);
|
|
1629
2404
|
}
|
|
1630
2405
|
}
|
|
1631
2406
|
|
|
@@ -1651,15 +2426,35 @@ Commands rig owns:
|
|
|
1651
2426
|
\`git format-patch\` content
|
|
1652
2427
|
pr status <event-id> <state> set an issue/patch status (kind:1630-1633):
|
|
1653
2428
|
open | applied | closed | draft
|
|
2429
|
+
fund drip devnet faucet funds to this identity's
|
|
2430
|
+
wallet (free); on other networks prints the
|
|
2431
|
+
address(es) to fund externally
|
|
2432
|
+
balance wallet balances + payment-channel holdings
|
|
2433
|
+
(free \u2014 chain reads and local state only)
|
|
2434
|
+
channel list show the payment channels paid commands hold
|
|
2435
|
+
(free \u2014 reads local state)
|
|
2436
|
+
channel open explicitly open (or resume) the channel for a
|
|
2437
|
+
peer; --deposit adds collateral (on-chain)
|
|
2438
|
+
channel close <channelId> start the settlement challenge window (on-chain)
|
|
2439
|
+
channel settle <channelId> release collateral after the window (on-chain)
|
|
1654
2440
|
|
|
1655
2441
|
Any other command is passed through to git verbatim: \`rig status\` runs
|
|
1656
2442
|
\`git status\`, and \`rig add -p\`, \`rig commit\`, \`rig log --oneline\`,
|
|
1657
2443
|
\`rig rebase -i\`, \u2026 behave exactly like git (same output, same exit code).
|
|
1658
2444
|
|
|
1659
|
-
Run \`rig <command> --help\` for a rig command's flags. \`rig init
|
|
1660
|
-
\`rig remote\`
|
|
1661
|
-
|
|
1662
|
-
|
|
2445
|
+
Run \`rig <command> --help\` for a rig command's flags. \`rig init\`,
|
|
2446
|
+
\`rig remote\`, \`rig fund\`, \`rig balance\`, and \`rig channel list\` are
|
|
2447
|
+
free; push/issue/comment/pr are paid writes \u2014 permanent and non-refundable \u2014
|
|
2448
|
+
and channel open/close/settle are on-chain wallet transactions; each states
|
|
2449
|
+
what it will spend and asks for confirmation before doing so (--yes skips,
|
|
2450
|
+
--json emits machine output).
|
|
2451
|
+
|
|
2452
|
+
With --json, stdout carries exactly ONE JSON document (everything human-facing
|
|
2453
|
+
goes to stderr), so \`rig <command> --json | jq\` always parses. --json is a
|
|
2454
|
+
per-subcommand flag on the commands rig owns, NOT a global rig flag: it does
|
|
2455
|
+
not apply to the git passthrough (\`rig status --json\` runs
|
|
2456
|
+
\`git status --json\`), and flags placed before the subcommand
|
|
2457
|
+
(\`rig --json status\`) pass through to git untouched.`;
|
|
1663
2458
|
function rigVersion() {
|
|
1664
2459
|
const require2 = createRequire(import.meta.url);
|
|
1665
2460
|
for (const rel of ["../package.json", "../../package.json", "../../../package.json"]) {
|
|
@@ -1671,9 +2466,9 @@ function rigVersion() {
|
|
|
1671
2466
|
}
|
|
1672
2467
|
return "unknown";
|
|
1673
2468
|
}
|
|
1674
|
-
async function dispatch(
|
|
1675
|
-
const [command, ...rest] =
|
|
1676
|
-
const { io } = deps;
|
|
2469
|
+
async function dispatch(argv2, deps) {
|
|
2470
|
+
const [command, ...rest] = argv2;
|
|
2471
|
+
const { io: io2 } = deps;
|
|
1677
2472
|
switch (command) {
|
|
1678
2473
|
case "init":
|
|
1679
2474
|
return runInit(rest, deps);
|
|
@@ -1687,35 +2482,124 @@ async function dispatch(argv, deps) {
|
|
|
1687
2482
|
return runComment(rest, deps);
|
|
1688
2483
|
case "pr":
|
|
1689
2484
|
return runPr(rest, deps);
|
|
2485
|
+
case "channel":
|
|
2486
|
+
return runChannel(rest, deps);
|
|
2487
|
+
case "fund":
|
|
2488
|
+
return runFund(rest, deps);
|
|
2489
|
+
case "balance":
|
|
2490
|
+
return runBalance(rest, deps);
|
|
1690
2491
|
case "help":
|
|
1691
2492
|
case "--help":
|
|
1692
2493
|
case "-h":
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
2494
|
+
io2.out(USAGE);
|
|
2495
|
+
io2.out("");
|
|
2496
|
+
io2.out(PUSH_USAGE);
|
|
1696
2497
|
return 0;
|
|
1697
2498
|
case "--version":
|
|
1698
|
-
|
|
2499
|
+
io2.out(`rig ${rigVersion()}`);
|
|
1699
2500
|
return 0;
|
|
1700
2501
|
case void 0:
|
|
1701
|
-
|
|
2502
|
+
io2.err(USAGE);
|
|
1702
2503
|
return 2;
|
|
1703
2504
|
default:
|
|
1704
|
-
return (deps.runGit ?? runGitPassthrough)(
|
|
2505
|
+
return (deps.runGit ?? runGitPassthrough)(argv2, {
|
|
1705
2506
|
cwd: deps.cwd,
|
|
1706
2507
|
env: deps.env,
|
|
1707
|
-
err: (line) =>
|
|
2508
|
+
err: (line) => io2.err(line)
|
|
1708
2509
|
});
|
|
1709
2510
|
}
|
|
1710
2511
|
}
|
|
1711
2512
|
|
|
1712
|
-
// src/cli/
|
|
1713
|
-
function
|
|
2513
|
+
// src/cli/output.ts
|
|
2514
|
+
function makeCliIo(options) {
|
|
2515
|
+
const { jsonMode, writeStdout, writeStderr } = options;
|
|
2516
|
+
let emittedJson = false;
|
|
2517
|
+
const humanLines = [];
|
|
2518
|
+
const toStderr = (line) => {
|
|
2519
|
+
if (jsonMode) humanLines.push(line);
|
|
2520
|
+
writeStderr(`${line}
|
|
2521
|
+
`);
|
|
2522
|
+
};
|
|
2523
|
+
return {
|
|
2524
|
+
jsonMode,
|
|
2525
|
+
get emittedJson() {
|
|
2526
|
+
return emittedJson;
|
|
2527
|
+
},
|
|
2528
|
+
out: (line) => {
|
|
2529
|
+
if (jsonMode) {
|
|
2530
|
+
toStderr(line);
|
|
2531
|
+
} else {
|
|
2532
|
+
writeStdout(`${line}
|
|
2533
|
+
`);
|
|
2534
|
+
}
|
|
2535
|
+
},
|
|
2536
|
+
err: toStderr,
|
|
2537
|
+
emitJson: (payload) => {
|
|
2538
|
+
emittedJson = true;
|
|
2539
|
+
writeStdout(`${JSON.stringify(payload, null, 2)}
|
|
2540
|
+
`);
|
|
2541
|
+
},
|
|
2542
|
+
isInteractive: options.isInteractive,
|
|
2543
|
+
confirm: options.confirm,
|
|
2544
|
+
ensureSingleJsonDoc(exitCode) {
|
|
2545
|
+
if (!jsonMode || emittedJson) return;
|
|
2546
|
+
const detail = humanLines.join("\n") || (exitCode === 0 ? "the command produced no machine output" : "the command failed before emitting machine output \u2014 see stderr");
|
|
2547
|
+
this.emitJson(
|
|
2548
|
+
exitCode === 0 ? { error: null, exitCode, detail } : { error: "error", exitCode, detail }
|
|
2549
|
+
);
|
|
2550
|
+
}
|
|
2551
|
+
};
|
|
2552
|
+
}
|
|
2553
|
+
var RIG_OWNED_VERBS = /* @__PURE__ */ new Set([
|
|
2554
|
+
"init",
|
|
2555
|
+
"remote",
|
|
2556
|
+
"push",
|
|
2557
|
+
"issue",
|
|
2558
|
+
"comment",
|
|
2559
|
+
"pr",
|
|
2560
|
+
"channel",
|
|
2561
|
+
"fund",
|
|
2562
|
+
"balance"
|
|
2563
|
+
]);
|
|
2564
|
+
function isJsonInvocation(argv2) {
|
|
2565
|
+
const [verb, ...rest] = argv2;
|
|
2566
|
+
if (verb === void 0 || !RIG_OWNED_VERBS.has(verb)) return false;
|
|
2567
|
+
for (const arg of rest) {
|
|
2568
|
+
if (arg === "--") return false;
|
|
2569
|
+
if (arg === "--json") return true;
|
|
2570
|
+
}
|
|
2571
|
+
return false;
|
|
2572
|
+
}
|
|
2573
|
+
function redirectStdoutToStderr(realWrite) {
|
|
2574
|
+
const original = process.stdout.write;
|
|
2575
|
+
const real = realWrite ?? original.bind(process.stdout);
|
|
2576
|
+
const patched = ((chunk, encodingOrCb, cb) => typeof encodingOrCb === "function" ? process.stderr.write(chunk, encodingOrCb) : process.stderr.write(
|
|
2577
|
+
chunk,
|
|
2578
|
+
encodingOrCb,
|
|
2579
|
+
cb
|
|
2580
|
+
));
|
|
2581
|
+
process.stdout.write = patched;
|
|
1714
2582
|
return {
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
2583
|
+
write: (text) => {
|
|
2584
|
+
real(text);
|
|
2585
|
+
},
|
|
2586
|
+
restore: () => {
|
|
2587
|
+
if (process.stdout.write === patched) process.stdout.write = original;
|
|
2588
|
+
}
|
|
2589
|
+
};
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
// src/cli/rig.ts
|
|
2593
|
+
function makeIo(jsonMode) {
|
|
2594
|
+
const guard = jsonMode ? redirectStdoutToStderr() : void 0;
|
|
2595
|
+
return makeCliIo({
|
|
2596
|
+
jsonMode,
|
|
2597
|
+
writeStdout: guard ? guard.write : (text) => {
|
|
2598
|
+
process.stdout.write(text);
|
|
2599
|
+
},
|
|
2600
|
+
writeStderr: (text) => {
|
|
2601
|
+
process.stderr.write(text);
|
|
2602
|
+
},
|
|
1719
2603
|
isInteractive: Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
1720
2604
|
confirm: async (question) => {
|
|
1721
2605
|
const rl = createInterface({
|
|
@@ -1729,14 +2613,17 @@ function makeIo() {
|
|
|
1729
2613
|
rl.close();
|
|
1730
2614
|
}
|
|
1731
2615
|
}
|
|
1732
|
-
};
|
|
2616
|
+
});
|
|
1733
2617
|
}
|
|
1734
|
-
|
|
1735
|
-
|
|
2618
|
+
var argv = process.argv.slice(2);
|
|
2619
|
+
var io = makeIo(isJsonInvocation(argv));
|
|
2620
|
+
dispatch(argv, {
|
|
2621
|
+
io,
|
|
1736
2622
|
env: process.env,
|
|
1737
2623
|
cwd: process.cwd()
|
|
1738
2624
|
}).then(
|
|
1739
2625
|
(code) => {
|
|
2626
|
+
io.ensureSingleJsonDoc(code);
|
|
1740
2627
|
process.exitCode = code;
|
|
1741
2628
|
},
|
|
1742
2629
|
(err) => {
|
|
@@ -1744,6 +2631,7 @@ dispatch(process.argv.slice(2), {
|
|
|
1744
2631
|
`rig: unexpected error: ${err instanceof Error ? err.stack ?? err.message : String(err)}
|
|
1745
2632
|
`
|
|
1746
2633
|
);
|
|
2634
|
+
io.ensureSingleJsonDoc(1);
|
|
1747
2635
|
process.exitCode = 1;
|
|
1748
2636
|
}
|
|
1749
2637
|
);
|