@toon-protocol/rig 2.2.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -8
- package/dist/{chunk-JBB7HBQC.js → chunk-3HRFDH7H.js} +48 -7
- package/dist/chunk-3HRFDH7H.js.map +1 -0
- package/dist/{chunk-PS5QOT62.js → chunk-W2SDL2PE.js} +2 -2
- package/dist/{chunk-CW4HJNMU.js → chunk-XGFBDUQX.js} +37 -5
- package/dist/chunk-XGFBDUQX.js.map +1 -0
- package/dist/cli/rig.js +919 -40
- package/dist/cli/rig.js.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.js +8 -2
- package/dist/{publisher-CrwaNQrK.d.ts → publisher-BtVceNeI.d.ts} +29 -2
- package/dist/standalone/index.d.ts +1 -1
- package/dist/{standalone-mode-64CKUVU2.js → standalone-mode-VH2XPOPK.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-CW4HJNMU.js.map +0 -1
- package/dist/chunk-JBB7HBQC.js.map +0 -1
- /package/dist/{chunk-PS5QOT62.js.map → chunk-W2SDL2PE.js.map} +0 -0
- /package/dist/{standalone-mode-64CKUVU2.js.map → standalone-mode-VH2XPOPK.js.map} +0 -0
package/dist/cli/rig.js
CHANGED
|
@@ -17,20 +17,28 @@ import {
|
|
|
17
17
|
setHeadSymref,
|
|
18
18
|
updateRef,
|
|
19
19
|
writeGitObjects
|
|
20
|
-
} from "../chunk-
|
|
20
|
+
} from "../chunk-W2SDL2PE.js";
|
|
21
21
|
import {
|
|
22
|
+
DEFAULT_KEYSTORE_PASSWORD,
|
|
22
23
|
MissingIdentityError,
|
|
24
|
+
clientConfigPath,
|
|
25
|
+
clientKeystorePath,
|
|
26
|
+
linkKeystoreInClientConfig,
|
|
27
|
+
readClientConfigFile,
|
|
23
28
|
resolveIdentity
|
|
24
|
-
} from "../chunk-
|
|
29
|
+
} from "../chunk-XGFBDUQX.js";
|
|
25
30
|
import {
|
|
26
31
|
COMMENT_KIND,
|
|
32
|
+
authorizedStatusAuthors,
|
|
27
33
|
buildComment,
|
|
28
34
|
buildIssue,
|
|
29
35
|
buildPatch,
|
|
36
|
+
buildRepoAnnouncement,
|
|
30
37
|
buildStatus,
|
|
31
38
|
fetchRemoteState,
|
|
39
|
+
parseMaintainers,
|
|
32
40
|
queryRelay
|
|
33
|
-
} from "../chunk-
|
|
41
|
+
} from "../chunk-3HRFDH7H.js";
|
|
34
42
|
import {
|
|
35
43
|
ChannelMapStore,
|
|
36
44
|
channelStatus,
|
|
@@ -42,7 +50,7 @@ import {
|
|
|
42
50
|
} from "../chunk-X2CZPPDM.js";
|
|
43
51
|
|
|
44
52
|
// src/cli/rig.ts
|
|
45
|
-
import { createInterface } from "readline/promises";
|
|
53
|
+
import { createInterface as createInterface2 } from "readline/promises";
|
|
46
54
|
|
|
47
55
|
// src/cli/dispatch.ts
|
|
48
56
|
import { createRequire } from "module";
|
|
@@ -897,7 +905,7 @@ function loadPaidSession(deps, relayUrl) {
|
|
|
897
905
|
});
|
|
898
906
|
}
|
|
899
907
|
var defaultLoadStandalone = async (options) => {
|
|
900
|
-
const mod = await import("../standalone-mode-
|
|
908
|
+
const mod = await import("../standalone-mode-VH2XPOPK.js");
|
|
901
909
|
return mod.createStandaloneContext(options);
|
|
902
910
|
};
|
|
903
911
|
function identityReport(ctx) {
|
|
@@ -2100,10 +2108,11 @@ function tagValue(tags, name) {
|
|
|
2100
2108
|
function tagValues(tags, name) {
|
|
2101
2109
|
return tags.filter((t) => t[0] === name && t[1]).map((t) => t[1]);
|
|
2102
2110
|
}
|
|
2103
|
-
function deriveStatus(targetEventId, statusEvents) {
|
|
2111
|
+
function deriveStatus(targetEventId, statusEvents, authorized) {
|
|
2104
2112
|
let winner = null;
|
|
2105
2113
|
for (const event of statusEvents) {
|
|
2106
2114
|
if (STATUS_BY_KIND[event.kind] === void 0) continue;
|
|
2115
|
+
if (!authorized.has(event.pubkey.toLowerCase())) continue;
|
|
2107
2116
|
if (!event.tags.some((t) => t[0] === "e" && t[1] === targetEventId))
|
|
2108
2117
|
continue;
|
|
2109
2118
|
if (winner === null || event.created_at > winner.created_at || event.created_at === winner.created_at && event.id < winner.id) {
|
|
@@ -2184,12 +2193,13 @@ async function resolveTrackerContext(flags, deps, needRepoAddr) {
|
|
|
2184
2193
|
} catch {
|
|
2185
2194
|
}
|
|
2186
2195
|
let repoAddr = null;
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
const owner = flags.owner ?? toonConfig.owner;
|
|
2191
|
-
if (!owner) throw new UnconfiguredRepoAddressError("repository owner");
|
|
2196
|
+
const repoId = flags.repoId ?? toonConfig.repoId;
|
|
2197
|
+
const owner = flags.owner ?? toonConfig.owner;
|
|
2198
|
+
if (repoId && owner) {
|
|
2192
2199
|
repoAddr = { ownerPubkey: owner, repoId };
|
|
2200
|
+
} else if (needRepoAddr) {
|
|
2201
|
+
if (!repoId) throw new UnconfiguredRepoAddressError("repository id");
|
|
2202
|
+
throw new UnconfiguredRepoAddressError("repository owner");
|
|
2193
2203
|
}
|
|
2194
2204
|
const resolved = await resolveRelays({
|
|
2195
2205
|
relayFlags: flags.relay,
|
|
@@ -2213,6 +2223,34 @@ async function resolveTrackerContext(flags, deps, needRepoAddr) {
|
|
|
2213
2223
|
function repoATag(addr) {
|
|
2214
2224
|
return `${REPOSITORY_ANNOUNCEMENT_KIND}:${addr.ownerPubkey}:${addr.repoId}`;
|
|
2215
2225
|
}
|
|
2226
|
+
async function fetchAuthorizedAuthors(ctx, ownerPubkey, repoId) {
|
|
2227
|
+
let announceTags = [];
|
|
2228
|
+
try {
|
|
2229
|
+
const events = await queryAll(
|
|
2230
|
+
ctx.relays,
|
|
2231
|
+
[
|
|
2232
|
+
{
|
|
2233
|
+
kinds: [REPOSITORY_ANNOUNCEMENT_KIND],
|
|
2234
|
+
authors: [ownerPubkey],
|
|
2235
|
+
"#d": [repoId]
|
|
2236
|
+
}
|
|
2237
|
+
],
|
|
2238
|
+
ctx.webSocketFactory
|
|
2239
|
+
);
|
|
2240
|
+
let latest = null;
|
|
2241
|
+
for (const event of events.values()) {
|
|
2242
|
+
if (event.kind !== REPOSITORY_ANNOUNCEMENT_KIND) continue;
|
|
2243
|
+
if (event.pubkey !== ownerPubkey) continue;
|
|
2244
|
+
if (!event.tags.some((t) => t[0] === "d" && t[1] === repoId)) continue;
|
|
2245
|
+
if (latest === null || event.created_at > latest.created_at || event.created_at === latest.created_at && event.id < latest.id) {
|
|
2246
|
+
latest = event;
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
if (latest) announceTags = latest.tags;
|
|
2250
|
+
} catch {
|
|
2251
|
+
}
|
|
2252
|
+
return authorizedStatusAuthors(ownerPubkey, announceTags);
|
|
2253
|
+
}
|
|
2216
2254
|
function parseTrackerItem(event, status) {
|
|
2217
2255
|
const item = {
|
|
2218
2256
|
eventId: event.id,
|
|
@@ -2237,8 +2275,12 @@ function isoDate(createdAt) {
|
|
|
2237
2275
|
return new Date(createdAt * 1e3).toISOString().slice(0, 10);
|
|
2238
2276
|
}
|
|
2239
2277
|
async function fetchItems(ctx, kind) {
|
|
2240
|
-
const
|
|
2241
|
-
|
|
2278
|
+
const addr = ctx.repoAddr;
|
|
2279
|
+
const aTag = repoATag(addr);
|
|
2280
|
+
const authorizedPromise = fetchAuthorizedAuthors(
|
|
2281
|
+
ctx,
|
|
2282
|
+
addr.ownerPubkey,
|
|
2283
|
+
addr.repoId
|
|
2242
2284
|
);
|
|
2243
2285
|
const [itemEvents, aStatusEvents] = await Promise.all([
|
|
2244
2286
|
queryAll(
|
|
@@ -2265,8 +2307,9 @@ async function fetchItems(ctx, kind) {
|
|
|
2265
2307
|
for (const [id, event] of byE)
|
|
2266
2308
|
if (!statuses.has(id)) statuses.set(id, event);
|
|
2267
2309
|
}
|
|
2310
|
+
const authorized = await authorizedPromise;
|
|
2268
2311
|
return items.map(
|
|
2269
|
-
(event) => parseTrackerItem(event, deriveStatus(event.id, statuses.values()))
|
|
2312
|
+
(event) => parseTrackerItem(event, deriveStatus(event.id, statuses.values(), authorized))
|
|
2270
2313
|
).sort((a, b) => b.createdAt - a.createdAt);
|
|
2271
2314
|
}
|
|
2272
2315
|
async function fetchItem(ctx, eventId, expectedKind) {
|
|
@@ -2305,12 +2348,27 @@ async function fetchItem(ctx, eventId, expectedKind) {
|
|
|
2305
2348
|
createdAt: e.created_at,
|
|
2306
2349
|
content: e.content
|
|
2307
2350
|
}));
|
|
2351
|
+
const repoATag2 = tagValue(event.tags, "a") ?? null;
|
|
2352
|
+
const parsedAddr = parseRepoATag(repoATag2) ?? ctx.repoAddr;
|
|
2353
|
+
const authorized = parsedAddr ? await fetchAuthorizedAuthors(ctx, parsedAddr.ownerPubkey, parsedAddr.repoId) : /* @__PURE__ */ new Set();
|
|
2308
2354
|
return {
|
|
2309
|
-
item: parseTrackerItem(
|
|
2355
|
+
item: parseTrackerItem(
|
|
2356
|
+
event,
|
|
2357
|
+
deriveStatus(eventId, statusEvents.values(), authorized)
|
|
2358
|
+
),
|
|
2310
2359
|
comments,
|
|
2311
|
-
repoATag:
|
|
2360
|
+
repoATag: repoATag2
|
|
2312
2361
|
};
|
|
2313
2362
|
}
|
|
2363
|
+
function parseRepoATag(aTag) {
|
|
2364
|
+
if (!aTag) return null;
|
|
2365
|
+
const [kind, ownerPubkey, ...repoIdParts] = aTag.split(":");
|
|
2366
|
+
const repoId = repoIdParts.join(":");
|
|
2367
|
+
if (kind !== String(REPOSITORY_ANNOUNCEMENT_KIND) || !ownerPubkey || !repoId) {
|
|
2368
|
+
return null;
|
|
2369
|
+
}
|
|
2370
|
+
return { ownerPubkey, repoId };
|
|
2371
|
+
}
|
|
2314
2372
|
async function runList(args, deps, spec) {
|
|
2315
2373
|
const { io: io2 } = deps;
|
|
2316
2374
|
let flags;
|
|
@@ -2663,6 +2721,9 @@ async function runEvent(opts) {
|
|
|
2663
2721
|
const addr = { ownerPubkey: owner, repoId };
|
|
2664
2722
|
const event = await opts.buildEvent(addr);
|
|
2665
2723
|
const action = `kind:${event.kind} ${actionLabel}`;
|
|
2724
|
+
if (opts.preConfirm) {
|
|
2725
|
+
await opts.preConfirm({ identity, addr, relayUrl: relaysUsed[0] });
|
|
2726
|
+
}
|
|
2666
2727
|
if (!flags.json) {
|
|
2667
2728
|
for (const line of renderEventPlan({
|
|
2668
2729
|
action,
|
|
@@ -3109,9 +3170,41 @@ async function runPrStatus(args, deps) {
|
|
|
3109
3170
|
]);
|
|
3110
3171
|
return event;
|
|
3111
3172
|
},
|
|
3173
|
+
// Authority warning (#287): a status event only moves an issue/PR's
|
|
3174
|
+
// resolved state for repo-authority-honoring clients if its author is the
|
|
3175
|
+
// owner or a declared maintainer. Warn clearly when the active identity is
|
|
3176
|
+
// neither — the publish still happens (permissionless relay; the caller
|
|
3177
|
+
// may be acting on their own repo/fork), but the futility is made obvious.
|
|
3178
|
+
preConfirm: (args2) => warnIfNotMaintainer(deps, args2),
|
|
3112
3179
|
sendDaemon: (client, addr) => client.gitStatus({ repoAddr: addr, targetEventId, status })
|
|
3113
3180
|
});
|
|
3114
3181
|
}
|
|
3182
|
+
async function warnIfNotMaintainer(deps, args) {
|
|
3183
|
+
const { io: io2 } = deps;
|
|
3184
|
+
const { identity, addr, relayUrl } = args;
|
|
3185
|
+
if (identity.pubkey.toLowerCase() === addr.ownerPubkey.toLowerCase()) return;
|
|
3186
|
+
if (relayUrl === void 0) return;
|
|
3187
|
+
try {
|
|
3188
|
+
const remote = await fetchRemoteState({
|
|
3189
|
+
relayUrls: [relayUrl],
|
|
3190
|
+
ownerPubkey: addr.ownerPubkey,
|
|
3191
|
+
repoId: addr.repoId,
|
|
3192
|
+
...deps.webSocketFactory ? { webSocketFactory: deps.webSocketFactory } : {}
|
|
3193
|
+
});
|
|
3194
|
+
const authorized = authorizedStatusAuthors(
|
|
3195
|
+
addr.ownerPubkey,
|
|
3196
|
+
remote.announceEvent?.tags ?? []
|
|
3197
|
+
);
|
|
3198
|
+
if (authorized.has(identity.pubkey.toLowerCase())) return;
|
|
3199
|
+
io2.err(
|
|
3200
|
+
`warning: you (${identity.pubkey.slice(0, 8)}\u2026) are not a maintainer of 30617:${addr.ownerPubkey.slice(0, 8)}\u2026:${addr.repoId} \u2014 clients honoring repo authority (rig, rig-web) will IGNORE this status when resolving the issue/PR's state. Publishing anyway (the relay is permissionless); ask the owner to \`rig maintainers add\` you if this should stick.`
|
|
3201
|
+
);
|
|
3202
|
+
} catch {
|
|
3203
|
+
io2.err(
|
|
3204
|
+
"warning: could not read the repo announcement to verify your maintainer status \u2014 if you are not the owner or a declared maintainer, authority-honoring clients will ignore this status."
|
|
3205
|
+
);
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3115
3208
|
|
|
3116
3209
|
// src/cli/fetch.ts
|
|
3117
3210
|
import { parseArgs as parseArgs8 } from "util";
|
|
@@ -3344,10 +3437,11 @@ var FUND_USAGE = `Usage: rig fund [options]
|
|
|
3344
3437
|
Drip devnet test funds to the active identity's wallet \u2014 free (the faucet
|
|
3345
3438
|
pays). The identity comes from RIG_MNEMONIC (env or a project .env) or the
|
|
3346
3439
|
~/.toon-client keystore/config; the faucet from TOON_CLIENT_FAUCET_URL, the
|
|
3347
|
-
faucetUrl config field, or the deployed devnet faucet when the
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
fund externally
|
|
3440
|
+
faucetUrl config field, or the deployed devnet faucet when the network is
|
|
3441
|
+
devnet \u2014 including when a configured *.devnet.toonprotocol.dev origin infers
|
|
3442
|
+
it. The faucet drips a FIXED amount per chain (there is no --amount). On a
|
|
3443
|
+
network without a faucet, prints the wallet address(es) to fund externally
|
|
3444
|
+
instead.
|
|
3351
3445
|
|
|
3352
3446
|
Options:
|
|
3353
3447
|
--chain <chain> evm | solana | mina (default: TOON_CLIENT_CHAIN, the
|
|
@@ -3440,7 +3534,10 @@ async function runFund(args, deps) {
|
|
|
3440
3534
|
);
|
|
3441
3535
|
}
|
|
3442
3536
|
const network = env["TOON_CLIENT_NETWORK"] ?? file.network;
|
|
3443
|
-
const
|
|
3537
|
+
const devnetOrigin = sharedDevnetOrigin(env, file);
|
|
3538
|
+
const inferredDevnet = devnetOrigin !== void 0 && (network === void 0 || network === "custom");
|
|
3539
|
+
const effectiveNetwork = inferredDevnet ? "devnet" : network;
|
|
3540
|
+
const faucetUrl = env["TOON_CLIENT_FAUCET_URL"] ?? file.faucetUrl ?? (effectiveNetwork === "devnet" ? DEVNET_FAUCET_URL : void 0);
|
|
3444
3541
|
const resolved = await resolveIdentity({
|
|
3445
3542
|
env,
|
|
3446
3543
|
cwd: deps.cwd,
|
|
@@ -3462,7 +3559,7 @@ async function runFund(args, deps) {
|
|
|
3462
3559
|
mina: derived.mina.publicKey || null
|
|
3463
3560
|
};
|
|
3464
3561
|
if (!faucetUrl) {
|
|
3465
|
-
const guidance = noFaucetGuidance(network,
|
|
3562
|
+
const guidance = noFaucetGuidance(network, devnetOrigin);
|
|
3466
3563
|
if (json) {
|
|
3467
3564
|
io2.emitJson({
|
|
3468
3565
|
command: "fund",
|
|
@@ -3491,6 +3588,11 @@ async function runFund(args, deps) {
|
|
|
3491
3588
|
}
|
|
3492
3589
|
const timeoutEnv = env["TOON_CLIENT_FAUCET_TIMEOUT_MS"];
|
|
3493
3590
|
const timeout = timeoutEnv && Number.isFinite(Number(timeoutEnv)) ? Number(timeoutEnv) : file.faucetTimeoutMs ?? (chain === "mina" ? 13e4 : 9e4);
|
|
3591
|
+
if (!json && inferredDevnet) {
|
|
3592
|
+
io2.out(
|
|
3593
|
+
`Inferred network 'devnet' from the configured origin ${devnetOrigin} (network was ${JSON.stringify(network ?? "custom")}). Set TOON_CLIENT_NETWORK explicitly to override.`
|
|
3594
|
+
);
|
|
3595
|
+
}
|
|
3494
3596
|
if (!json) {
|
|
3495
3597
|
io2.out(
|
|
3496
3598
|
`Requesting ${chain} drip from ${faucetUrl} for ${address} \u2026` + (chain === "mina" ? " (mina settles slowly; this can take ~2 minutes)" : "")
|
|
@@ -3505,11 +3607,12 @@ async function runFund(args, deps) {
|
|
|
3505
3607
|
command: "fund",
|
|
3506
3608
|
identity,
|
|
3507
3609
|
funded: true,
|
|
3508
|
-
network:
|
|
3610
|
+
network: effectiveNetwork ?? null,
|
|
3509
3611
|
chain,
|
|
3510
3612
|
address,
|
|
3511
3613
|
faucetUrl,
|
|
3512
|
-
response
|
|
3614
|
+
response,
|
|
3615
|
+
...inferredDevnet ? { inferredDevnetFrom: devnetOrigin } : {}
|
|
3513
3616
|
});
|
|
3514
3617
|
return 0;
|
|
3515
3618
|
}
|
|
@@ -3570,9 +3673,396 @@ var runGitPassthrough = (argv2, options = {}) => {
|
|
|
3570
3673
|
});
|
|
3571
3674
|
};
|
|
3572
3675
|
|
|
3676
|
+
// src/cli/identity-cmd.ts
|
|
3677
|
+
import { existsSync, mkdirSync } from "fs";
|
|
3678
|
+
import { dirname as dirname2 } from "path";
|
|
3679
|
+
import { createInterface } from "readline/promises";
|
|
3680
|
+
import { parseArgs as parseArgs10 } from "util";
|
|
3681
|
+
async function loadKeyOps() {
|
|
3682
|
+
return await import("@toon-protocol/client");
|
|
3683
|
+
}
|
|
3684
|
+
var IdentityExistsError = class extends Error {
|
|
3685
|
+
constructor(pubkey, sourceLabel) {
|
|
3686
|
+
super(
|
|
3687
|
+
`an identity already resolves (${pubkey}, from ${sourceLabel}) \u2014 rig will not overwrite it. Inspect it with \`rig identity show\`, or pass \`--force\` to replace the keystore with a brand-new identity (the old phrase becomes unrecoverable unless you backed it up).`
|
|
3688
|
+
);
|
|
3689
|
+
this.pubkey = pubkey;
|
|
3690
|
+
this.sourceLabel = sourceLabel;
|
|
3691
|
+
this.name = "IdentityExistsError";
|
|
3692
|
+
}
|
|
3693
|
+
pubkey;
|
|
3694
|
+
sourceLabel;
|
|
3695
|
+
};
|
|
3696
|
+
var KeystoreFileExistsError = class extends Error {
|
|
3697
|
+
constructor(keystorePath) {
|
|
3698
|
+
super(
|
|
3699
|
+
`a keystore file already exists at ${keystorePath} \u2014 rig will not overwrite it. Link it via the client config, or pass \`--force\` to replace it with a new identity.`
|
|
3700
|
+
);
|
|
3701
|
+
this.keystorePath = keystorePath;
|
|
3702
|
+
this.name = "KeystoreFileExistsError";
|
|
3703
|
+
}
|
|
3704
|
+
keystorePath;
|
|
3705
|
+
};
|
|
3706
|
+
async function prepareKeystoreTarget(input) {
|
|
3707
|
+
const keyOps = await (input.loadKeyOps ?? loadKeyOps)();
|
|
3708
|
+
const resolveImpl = input.resolveIdentityImpl ?? resolveIdentity;
|
|
3709
|
+
let existing;
|
|
3710
|
+
try {
|
|
3711
|
+
existing = await resolveImpl({
|
|
3712
|
+
env: input.env,
|
|
3713
|
+
cwd: input.cwd,
|
|
3714
|
+
warn: input.warn
|
|
3715
|
+
});
|
|
3716
|
+
} catch (err) {
|
|
3717
|
+
if (!(err instanceof MissingIdentityError)) throw err;
|
|
3718
|
+
}
|
|
3719
|
+
if (existing && !input.force) {
|
|
3720
|
+
throw new IdentityExistsError(existing.pubkey, existing.sourceLabel);
|
|
3721
|
+
}
|
|
3722
|
+
const keystorePath = clientKeystorePath(input.env);
|
|
3723
|
+
if (existsSync(keystorePath) && !input.force) {
|
|
3724
|
+
throw new KeystoreFileExistsError(keystorePath);
|
|
3725
|
+
}
|
|
3726
|
+
const envPassword = input.env["TOON_CLIENT_KEYSTORE_PASSWORD"]?.trim() || void 0;
|
|
3727
|
+
const autoPassword = envPassword === void 0;
|
|
3728
|
+
const password = envPassword ?? DEFAULT_KEYSTORE_PASSWORD;
|
|
3729
|
+
mkdirSync(dirname2(keystorePath), { recursive: true });
|
|
3730
|
+
return { keystorePath, password, autoPassword, existing, keyOps };
|
|
3731
|
+
}
|
|
3732
|
+
function readAccountIndex(env) {
|
|
3733
|
+
const file = readClientConfigFile(clientConfigPath(env));
|
|
3734
|
+
return typeof file.mnemonicAccountIndex === "number" ? file.mnemonicAccountIndex : 0;
|
|
3735
|
+
}
|
|
3736
|
+
function finishMint(input, target, mnemonic) {
|
|
3737
|
+
const configPath = linkKeystoreInClientConfig(
|
|
3738
|
+
input.env,
|
|
3739
|
+
target.keystorePath,
|
|
3740
|
+
target.autoPassword
|
|
3741
|
+
);
|
|
3742
|
+
const accountIndex = readAccountIndex(input.env);
|
|
3743
|
+
const { pubkey } = target.keyOps.deriveNostrKeyFromMnemonic(
|
|
3744
|
+
mnemonic,
|
|
3745
|
+
accountIndex
|
|
3746
|
+
);
|
|
3747
|
+
const result = {
|
|
3748
|
+
mnemonic,
|
|
3749
|
+
pubkey,
|
|
3750
|
+
keystorePath: target.keystorePath,
|
|
3751
|
+
configPath,
|
|
3752
|
+
autoPassword: target.autoPassword,
|
|
3753
|
+
accountIndex
|
|
3754
|
+
};
|
|
3755
|
+
if (target.existing && target.existing.pubkey !== pubkey) {
|
|
3756
|
+
result.shadowedBy = {
|
|
3757
|
+
sourceLabel: target.existing.sourceLabel,
|
|
3758
|
+
pubkey: target.existing.pubkey
|
|
3759
|
+
};
|
|
3760
|
+
}
|
|
3761
|
+
return result;
|
|
3762
|
+
}
|
|
3763
|
+
async function createIdentity(input) {
|
|
3764
|
+
const target = await prepareKeystoreTarget(input);
|
|
3765
|
+
const { mnemonic } = target.keyOps.generateKeystore(
|
|
3766
|
+
target.keystorePath,
|
|
3767
|
+
target.password
|
|
3768
|
+
);
|
|
3769
|
+
return finishMint(input, target, mnemonic);
|
|
3770
|
+
}
|
|
3771
|
+
async function importIdentity(input) {
|
|
3772
|
+
const target = await prepareKeystoreTarget(input);
|
|
3773
|
+
target.keyOps.importKeystore(
|
|
3774
|
+
target.keystorePath,
|
|
3775
|
+
input.mnemonic,
|
|
3776
|
+
target.password
|
|
3777
|
+
);
|
|
3778
|
+
return finishMint(input, target, input.mnemonic);
|
|
3779
|
+
}
|
|
3780
|
+
function createdIdentityBanner(result) {
|
|
3781
|
+
return [
|
|
3782
|
+
"",
|
|
3783
|
+
"\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",
|
|
3784
|
+
" rig: generated a new identity",
|
|
3785
|
+
"\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",
|
|
3786
|
+
` Nostr pubkey : ${result.pubkey}`,
|
|
3787
|
+
"",
|
|
3788
|
+
" Seed phrase \u2014 this phrase CONTROLS YOUR FUNDS. Write it down and",
|
|
3789
|
+
" store it safely. It is shown ONCE and CANNOT be recovered:",
|
|
3790
|
+
"",
|
|
3791
|
+
` ${result.mnemonic}`,
|
|
3792
|
+
"",
|
|
3793
|
+
` Encrypted keystore: ${result.keystorePath}`,
|
|
3794
|
+
result.autoPassword ? " Encrypted with the default password, so the identity reloads with no\n env var. To use your own password: set TOON_CLIENT_KEYSTORE_PASSWORD\n and re-import with `rig identity import`." : " Encrypted with your TOON_CLIENT_KEYSTORE_PASSWORD.",
|
|
3795
|
+
"\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",
|
|
3796
|
+
""
|
|
3797
|
+
];
|
|
3798
|
+
}
|
|
3799
|
+
function shadowNote(shadowedBy) {
|
|
3800
|
+
return `note: a higher-precedence identity (${shadowedBy.pubkey}, from ${shadowedBy.sourceLabel}) still shadows the new keystore \u2014 rig keeps using it until you remove that source (unset the env var / edit .env).`;
|
|
3801
|
+
}
|
|
3802
|
+
var IDENTITY_USAGE = `Usage: rig identity <command> [options]
|
|
3803
|
+
|
|
3804
|
+
Manage the BIP-39 signing identity rig uses to sign and pay:
|
|
3805
|
+
|
|
3806
|
+
create generate a fresh identity into the encrypted keystore under
|
|
3807
|
+
TOON_CLIENT_HOME. The seed phrase is shown ONCE \u2014 back it up.
|
|
3808
|
+
Refuses to overwrite an existing identity without --force.
|
|
3809
|
+
show report the ACTIVE identity's source + derived pubkey (never the
|
|
3810
|
+
phrase); errors with the resolution remediation when none exists.
|
|
3811
|
+
import read an EXISTING phrase from stdin (never a CLI argument) and write
|
|
3812
|
+
it to the encrypted keystore.
|
|
3813
|
+
|
|
3814
|
+
Options:
|
|
3815
|
+
--force (create/import) replace an existing identity/keystore
|
|
3816
|
+
--json machine-readable output. NOTE: \`identity create --json\`
|
|
3817
|
+
DELIBERATELY emits the seed phrase in a \`mnemonic\`
|
|
3818
|
+
field \u2014 treat that output as SECRET. \`identity show\`
|
|
3819
|
+
and \`identity import\` never emit the phrase.
|
|
3820
|
+
-h, --help show this help
|
|
3821
|
+
|
|
3822
|
+
The keystore is encrypted with TOON_CLIENT_KEYSTORE_PASSWORD when that env var
|
|
3823
|
+
is set, else a default password so the identity reloads with no configuration.
|
|
3824
|
+
rig deliberately does NOT accept the keystore password as a CLI flag \u2014 that
|
|
3825
|
+
would leak the value that decrypts your keys to shell history and \`ps\`.`;
|
|
3826
|
+
async function runIdentity(args, deps) {
|
|
3827
|
+
const [sub, ...rest] = args;
|
|
3828
|
+
switch (sub) {
|
|
3829
|
+
case "create":
|
|
3830
|
+
return runIdentityCreate(rest, deps);
|
|
3831
|
+
case "show":
|
|
3832
|
+
return runIdentityShow(rest, deps);
|
|
3833
|
+
case "import":
|
|
3834
|
+
return runIdentityImport(rest, deps);
|
|
3835
|
+
case "help":
|
|
3836
|
+
case "--help":
|
|
3837
|
+
case "-h":
|
|
3838
|
+
deps.io.out(IDENTITY_USAGE);
|
|
3839
|
+
return 0;
|
|
3840
|
+
case void 0:
|
|
3841
|
+
deps.io.err("rig identity needs a subcommand: create | show | import");
|
|
3842
|
+
deps.io.err(IDENTITY_USAGE);
|
|
3843
|
+
return 2;
|
|
3844
|
+
default:
|
|
3845
|
+
deps.io.err(`unknown rig identity subcommand: ${sub}`);
|
|
3846
|
+
deps.io.err(IDENTITY_USAGE);
|
|
3847
|
+
return 2;
|
|
3848
|
+
}
|
|
3849
|
+
}
|
|
3850
|
+
function parseMintArgs(args, verb) {
|
|
3851
|
+
const { values, positionals } = parseArgs10({
|
|
3852
|
+
args,
|
|
3853
|
+
options: {
|
|
3854
|
+
force: { type: "boolean", default: false },
|
|
3855
|
+
json: { type: "boolean", default: false },
|
|
3856
|
+
help: { type: "boolean", short: "h", default: false }
|
|
3857
|
+
},
|
|
3858
|
+
allowPositionals: false
|
|
3859
|
+
});
|
|
3860
|
+
if (positionals.length > 0) {
|
|
3861
|
+
throw new Error(`rig identity ${verb} takes no positional arguments`);
|
|
3862
|
+
}
|
|
3863
|
+
return {
|
|
3864
|
+
force: values.force ?? false,
|
|
3865
|
+
json: values.json ?? false,
|
|
3866
|
+
help: values.help ?? false
|
|
3867
|
+
};
|
|
3868
|
+
}
|
|
3869
|
+
async function runIdentityCreate(args, deps) {
|
|
3870
|
+
const { io: io2 } = deps;
|
|
3871
|
+
let flags;
|
|
3872
|
+
try {
|
|
3873
|
+
flags = parseMintArgs(args, "create");
|
|
3874
|
+
} catch (err) {
|
|
3875
|
+
io2.err(err instanceof Error ? err.message : String(err));
|
|
3876
|
+
io2.err(IDENTITY_USAGE);
|
|
3877
|
+
return 2;
|
|
3878
|
+
}
|
|
3879
|
+
if (flags.help) {
|
|
3880
|
+
io2.out(IDENTITY_USAGE);
|
|
3881
|
+
return 0;
|
|
3882
|
+
}
|
|
3883
|
+
try {
|
|
3884
|
+
const result = await createIdentity({
|
|
3885
|
+
env: deps.env,
|
|
3886
|
+
cwd: deps.cwd,
|
|
3887
|
+
force: flags.force,
|
|
3888
|
+
warn: (line) => io2.err(line),
|
|
3889
|
+
...deps.resolveIdentityImpl ? { resolveIdentityImpl: deps.resolveIdentityImpl } : {},
|
|
3890
|
+
...deps.loadKeyOps ? { loadKeyOps: deps.loadKeyOps } : {}
|
|
3891
|
+
});
|
|
3892
|
+
emitCreated(io2, flags.json, "identity create", result);
|
|
3893
|
+
return 0;
|
|
3894
|
+
} catch (err) {
|
|
3895
|
+
return emitMintError(io2, flags.json, "identity create", err);
|
|
3896
|
+
}
|
|
3897
|
+
}
|
|
3898
|
+
async function runIdentityImport(args, deps) {
|
|
3899
|
+
const { io: io2 } = deps;
|
|
3900
|
+
let flags;
|
|
3901
|
+
try {
|
|
3902
|
+
flags = parseMintArgs(args, "import");
|
|
3903
|
+
} catch (err) {
|
|
3904
|
+
io2.err(err instanceof Error ? err.message : String(err));
|
|
3905
|
+
io2.err(IDENTITY_USAGE);
|
|
3906
|
+
return 2;
|
|
3907
|
+
}
|
|
3908
|
+
if (flags.help) {
|
|
3909
|
+
io2.out(IDENTITY_USAGE);
|
|
3910
|
+
return 0;
|
|
3911
|
+
}
|
|
3912
|
+
try {
|
|
3913
|
+
const read = deps.readSecretLine ?? defaultReadSecretLine;
|
|
3914
|
+
const phrase = (await read(
|
|
3915
|
+
"Paste your BIP-39 seed phrase (read from stdin, not the terminal history): ",
|
|
3916
|
+
io2.isInteractive
|
|
3917
|
+
)).trim();
|
|
3918
|
+
if (phrase === "") {
|
|
3919
|
+
throw new Error(
|
|
3920
|
+
"no seed phrase provided on stdin \u2014 pipe it in or type it at the prompt (never pass a phrase as a CLI argument; it leaks to shell history and `ps`)."
|
|
3921
|
+
);
|
|
3922
|
+
}
|
|
3923
|
+
const result = await importIdentity({
|
|
3924
|
+
env: deps.env,
|
|
3925
|
+
cwd: deps.cwd,
|
|
3926
|
+
force: flags.force,
|
|
3927
|
+
mnemonic: phrase,
|
|
3928
|
+
warn: (line) => io2.err(line),
|
|
3929
|
+
...deps.resolveIdentityImpl ? { resolveIdentityImpl: deps.resolveIdentityImpl } : {},
|
|
3930
|
+
...deps.loadKeyOps ? { loadKeyOps: deps.loadKeyOps } : {}
|
|
3931
|
+
});
|
|
3932
|
+
if (flags.json) {
|
|
3933
|
+
io2.emitJson({
|
|
3934
|
+
command: "identity import",
|
|
3935
|
+
imported: true,
|
|
3936
|
+
pubkey: result.pubkey,
|
|
3937
|
+
keystorePath: result.keystorePath,
|
|
3938
|
+
autoPassword: result.autoPassword,
|
|
3939
|
+
...result.shadowedBy ? { shadowedBy: result.shadowedBy } : {}
|
|
3940
|
+
});
|
|
3941
|
+
} else {
|
|
3942
|
+
io2.out(`Imported identity ${result.pubkey}`);
|
|
3943
|
+
io2.out(` Encrypted keystore: ${result.keystorePath}`);
|
|
3944
|
+
}
|
|
3945
|
+
if (result.shadowedBy) io2.err(shadowNote(result.shadowedBy));
|
|
3946
|
+
return 0;
|
|
3947
|
+
} catch (err) {
|
|
3948
|
+
return emitMintError(io2, flags.json, "identity import", err);
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
async function runIdentityShow(args, deps) {
|
|
3952
|
+
const { io: io2 } = deps;
|
|
3953
|
+
let json = false;
|
|
3954
|
+
let help = false;
|
|
3955
|
+
try {
|
|
3956
|
+
const parsed = parseArgs10({
|
|
3957
|
+
args,
|
|
3958
|
+
options: {
|
|
3959
|
+
json: { type: "boolean", default: false },
|
|
3960
|
+
help: { type: "boolean", short: "h", default: false }
|
|
3961
|
+
},
|
|
3962
|
+
allowPositionals: false
|
|
3963
|
+
});
|
|
3964
|
+
json = parsed.values.json ?? false;
|
|
3965
|
+
help = parsed.values.help ?? false;
|
|
3966
|
+
} catch (err) {
|
|
3967
|
+
io2.err(err instanceof Error ? err.message : String(err));
|
|
3968
|
+
io2.err(IDENTITY_USAGE);
|
|
3969
|
+
return 2;
|
|
3970
|
+
}
|
|
3971
|
+
if (help) {
|
|
3972
|
+
io2.out(IDENTITY_USAGE);
|
|
3973
|
+
return 0;
|
|
3974
|
+
}
|
|
3975
|
+
try {
|
|
3976
|
+
const identity = await (deps.resolveIdentityImpl ?? resolveIdentity)({
|
|
3977
|
+
env: deps.env,
|
|
3978
|
+
cwd: deps.cwd,
|
|
3979
|
+
warn: (line) => io2.err(line)
|
|
3980
|
+
});
|
|
3981
|
+
if (json) {
|
|
3982
|
+
io2.emitJson({
|
|
3983
|
+
command: "identity show",
|
|
3984
|
+
source: identity.source,
|
|
3985
|
+
sourceLabel: identity.sourceLabel,
|
|
3986
|
+
pubkey: identity.pubkey,
|
|
3987
|
+
accountIndex: identity.accountIndex
|
|
3988
|
+
});
|
|
3989
|
+
} else {
|
|
3990
|
+
io2.out(renderIdentityLine(identity));
|
|
3991
|
+
io2.out(` source : ${identity.source} (${identity.sourceLabel})`);
|
|
3992
|
+
io2.out(` pubkey : ${identity.pubkey}`);
|
|
3993
|
+
}
|
|
3994
|
+
return 0;
|
|
3995
|
+
} catch (err) {
|
|
3996
|
+
return emitCliError(io2, json, "identity show", err);
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3999
|
+
function emitCreated(io2, json, command, result) {
|
|
4000
|
+
if (json) {
|
|
4001
|
+
io2.emitJson({
|
|
4002
|
+
command,
|
|
4003
|
+
created: true,
|
|
4004
|
+
pubkey: result.pubkey,
|
|
4005
|
+
keystorePath: result.keystorePath,
|
|
4006
|
+
autoPassword: result.autoPassword,
|
|
4007
|
+
// DELIBERATE, documented exception to "never print the phrase": the
|
|
4008
|
+
// scripting/agent path needs the generated phrase to back it up.
|
|
4009
|
+
mnemonic: result.mnemonic,
|
|
4010
|
+
...result.shadowedBy ? { shadowedBy: result.shadowedBy } : {}
|
|
4011
|
+
});
|
|
4012
|
+
io2.err(
|
|
4013
|
+
"rig: `--json` emitted your seed phrase in the `mnemonic` field \u2014 this output is SECRET; store it safely and do not log or share it."
|
|
4014
|
+
);
|
|
4015
|
+
} else {
|
|
4016
|
+
for (const line of createdIdentityBanner(result)) io2.out(line);
|
|
4017
|
+
}
|
|
4018
|
+
if (result.shadowedBy) io2.err(shadowNote(result.shadowedBy));
|
|
4019
|
+
}
|
|
4020
|
+
function emitMintError(io2, json, command, err) {
|
|
4021
|
+
if (err instanceof IdentityExistsError) {
|
|
4022
|
+
if (json) {
|
|
4023
|
+
io2.emitJson({
|
|
4024
|
+
command,
|
|
4025
|
+
error: "identity_exists",
|
|
4026
|
+
pubkey: err.pubkey,
|
|
4027
|
+
detail: err.message
|
|
4028
|
+
});
|
|
4029
|
+
}
|
|
4030
|
+
for (const line of err.message.split("\n")) io2.err(line);
|
|
4031
|
+
return 1;
|
|
4032
|
+
}
|
|
4033
|
+
if (err instanceof KeystoreFileExistsError) {
|
|
4034
|
+
if (json) {
|
|
4035
|
+
io2.emitJson({
|
|
4036
|
+
command,
|
|
4037
|
+
error: "keystore_exists",
|
|
4038
|
+
keystorePath: err.keystorePath,
|
|
4039
|
+
detail: err.message
|
|
4040
|
+
});
|
|
4041
|
+
}
|
|
4042
|
+
for (const line of err.message.split("\n")) io2.err(line);
|
|
4043
|
+
return 1;
|
|
4044
|
+
}
|
|
4045
|
+
return emitCliError(io2, json, command, err);
|
|
4046
|
+
}
|
|
4047
|
+
async function defaultReadSecretLine(prompt, isInteractive) {
|
|
4048
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
4049
|
+
if (isInteractive) {
|
|
4050
|
+
process.stderr.write(prompt);
|
|
4051
|
+
rl._writeToOutput = () => {
|
|
4052
|
+
};
|
|
4053
|
+
}
|
|
4054
|
+
try {
|
|
4055
|
+
const answer = await rl.question("");
|
|
4056
|
+
if (isInteractive) process.stderr.write("\n");
|
|
4057
|
+
return answer;
|
|
4058
|
+
} finally {
|
|
4059
|
+
rl.close();
|
|
4060
|
+
}
|
|
4061
|
+
}
|
|
4062
|
+
|
|
3573
4063
|
// src/cli/init.ts
|
|
3574
4064
|
import { basename as basename2 } from "path";
|
|
3575
|
-
import { parseArgs as
|
|
4065
|
+
import { parseArgs as parseArgs11 } from "util";
|
|
3576
4066
|
var INIT_USAGE = `Usage: rig init [options]
|
|
3577
4067
|
|
|
3578
4068
|
Set up the current git repository for rig (one-shot, idempotent, free):
|
|
@@ -3586,16 +4076,23 @@ The follow-up step is adding a relay: \`rig remote add origin <relay-url>\`
|
|
|
3586
4076
|
\`git config toon.relay\` is migrated to the origin remote automatically
|
|
3587
4077
|
when no origin exists (the old key stays readable and is removed in v0.3).
|
|
3588
4078
|
|
|
4079
|
+
When NO identity resolves, init does not dead-end: in a terminal it offers to
|
|
4080
|
+
generate one (\`Create a new identity now? [y/N]\`), and \`--generate-identity\`
|
|
4081
|
+
does it non-interactively (equivalent to \`rig identity create\` \u2014 the phrase
|
|
4082
|
+
is shown once to back up). Nothing is ever auto-generated without your yes.
|
|
4083
|
+
|
|
3589
4084
|
Options:
|
|
3590
|
-
--repo-id <id>
|
|
3591
|
-
|
|
3592
|
-
--
|
|
3593
|
-
-
|
|
4085
|
+
--repo-id <id> repository id / NIP-34 d-tag (default: the existing
|
|
4086
|
+
toon.repoid, then the repo directory basename)
|
|
4087
|
+
--generate-identity when no identity resolves, mint a fresh one (no prompt)
|
|
4088
|
+
--json machine-readable report
|
|
4089
|
+
-h, --help show this help`;
|
|
3594
4090
|
function parseInitArgs(args) {
|
|
3595
|
-
const { values, positionals } =
|
|
4091
|
+
const { values, positionals } = parseArgs11({
|
|
3596
4092
|
args,
|
|
3597
4093
|
options: {
|
|
3598
4094
|
"repo-id": { type: "string" },
|
|
4095
|
+
"generate-identity": { type: "boolean", default: false },
|
|
3599
4096
|
json: { type: "boolean", default: false },
|
|
3600
4097
|
help: { type: "boolean", short: "h", default: false }
|
|
3601
4098
|
},
|
|
@@ -3605,6 +4102,7 @@ function parseInitArgs(args) {
|
|
|
3605
4102
|
throw new Error(`rig init takes no positional arguments`);
|
|
3606
4103
|
}
|
|
3607
4104
|
const flags = {
|
|
4105
|
+
generateIdentity: values["generate-identity"] ?? false,
|
|
3608
4106
|
json: values.json ?? false,
|
|
3609
4107
|
help: values.help ?? false
|
|
3610
4108
|
};
|
|
@@ -3615,8 +4113,38 @@ function parseInitArgs(args) {
|
|
|
3615
4113
|
}
|
|
3616
4114
|
return flags;
|
|
3617
4115
|
}
|
|
3618
|
-
async function
|
|
4116
|
+
async function resolveOrGenerateIdentity(deps, flags) {
|
|
3619
4117
|
const { io: io2, env } = deps;
|
|
4118
|
+
const resolve2 = deps.resolveIdentityImpl ?? resolveIdentity;
|
|
4119
|
+
try {
|
|
4120
|
+
return { identity: await resolve2({ env, cwd: deps.cwd, warn: io2.err }) };
|
|
4121
|
+
} catch (err) {
|
|
4122
|
+
if (!(err instanceof MissingIdentityError)) throw err;
|
|
4123
|
+
let generate = flags.generateIdentity;
|
|
4124
|
+
if (!generate && io2.isInteractive && !flags.json) {
|
|
4125
|
+
io2.err("No identity found \u2014 rig needs one to sign and pay.");
|
|
4126
|
+
generate = await io2.confirm("Create a new identity now? [y/N] ");
|
|
4127
|
+
}
|
|
4128
|
+
if (!generate) throw err;
|
|
4129
|
+
const created = await (deps.createIdentityImpl ?? createIdentity)({
|
|
4130
|
+
env,
|
|
4131
|
+
cwd: deps.cwd,
|
|
4132
|
+
force: false,
|
|
4133
|
+
warn: io2.err,
|
|
4134
|
+
...deps.resolveIdentityImpl ? { resolveIdentityImpl: deps.resolveIdentityImpl } : {}
|
|
4135
|
+
});
|
|
4136
|
+
const identity = {
|
|
4137
|
+
mnemonic: created.mnemonic,
|
|
4138
|
+
accountIndex: created.accountIndex,
|
|
4139
|
+
source: "keystore",
|
|
4140
|
+
sourceLabel: created.keystorePath,
|
|
4141
|
+
pubkey: created.pubkey
|
|
4142
|
+
};
|
|
4143
|
+
return { identity, generated: created };
|
|
4144
|
+
}
|
|
4145
|
+
}
|
|
4146
|
+
async function runInit(args, deps) {
|
|
4147
|
+
const { io: io2 } = deps;
|
|
3620
4148
|
let flags;
|
|
3621
4149
|
try {
|
|
3622
4150
|
flags = parseInitArgs(args);
|
|
@@ -3636,11 +4164,7 @@ async function runInit(args, deps) {
|
|
|
3636
4164
|
} catch {
|
|
3637
4165
|
throw new NotAGitRepositoryError(deps.cwd);
|
|
3638
4166
|
}
|
|
3639
|
-
const identity = await (deps
|
|
3640
|
-
env,
|
|
3641
|
-
cwd: deps.cwd,
|
|
3642
|
-
warn: (line) => io2.err(line)
|
|
3643
|
-
});
|
|
4167
|
+
const { identity, generated } = await resolveOrGenerateIdentity(deps, flags);
|
|
3644
4168
|
const existing = await readToonConfig(repoRoot);
|
|
3645
4169
|
const repoId = flags.repoId ?? existing.repoId ?? basename2(repoRoot);
|
|
3646
4170
|
const changed = {
|
|
@@ -3674,11 +4198,29 @@ async function runInit(args, deps) {
|
|
|
3674
4198
|
remotes,
|
|
3675
4199
|
origin: originRelay ?? null,
|
|
3676
4200
|
migratedToonRelay,
|
|
3677
|
-
changed
|
|
4201
|
+
changed,
|
|
4202
|
+
...generated ? {
|
|
4203
|
+
generatedIdentity: {
|
|
4204
|
+
mnemonic: generated.mnemonic,
|
|
4205
|
+
pubkey: generated.pubkey,
|
|
4206
|
+
keystorePath: generated.keystorePath,
|
|
4207
|
+
autoPassword: generated.autoPassword
|
|
4208
|
+
}
|
|
4209
|
+
} : {}
|
|
3678
4210
|
};
|
|
3679
4211
|
io2.emitJson(output);
|
|
4212
|
+
if (generated) {
|
|
4213
|
+
io2.err(
|
|
4214
|
+
"rig: `--json` emitted your new seed phrase in `generatedIdentity.mnemonic` \u2014 this output is SECRET; store it safely and do not log or share it."
|
|
4215
|
+
);
|
|
4216
|
+
if (generated.shadowedBy) io2.err(shadowNote(generated.shadowedBy));
|
|
4217
|
+
}
|
|
3680
4218
|
return 0;
|
|
3681
4219
|
}
|
|
4220
|
+
if (generated) {
|
|
4221
|
+
for (const line of createdIdentityBanner(generated)) io2.out(line);
|
|
4222
|
+
if (generated.shadowedBy) io2.err(shadowNote(generated.shadowedBy));
|
|
4223
|
+
}
|
|
3682
4224
|
io2.out(`Initialized rig for ${repoRoot}`);
|
|
3683
4225
|
io2.out(renderIdentityLine(identity));
|
|
3684
4226
|
io2.out(
|
|
@@ -3717,14 +4259,341 @@ async function runInit(args, deps) {
|
|
|
3717
4259
|
}
|
|
3718
4260
|
}
|
|
3719
4261
|
|
|
4262
|
+
// src/cli/maintainers.ts
|
|
4263
|
+
import { parseArgs as parseArgs12 } from "util";
|
|
4264
|
+
var HEX64_RE3 = /^[0-9a-f]{64}$/;
|
|
4265
|
+
var WS_URL_RE4 = /^wss?:\/\//i;
|
|
4266
|
+
var MAINTAINERS_USAGE = `Usage: rig maintainers <list|add|remove> [<pubkey>] [options]
|
|
4267
|
+
|
|
4268
|
+
Manage a repo's declared maintainers (#287). Status authority is consumer-side:
|
|
4269
|
+
rig and rig-web honor an issue/PR status (kind:1630-1633) ONLY when its author
|
|
4270
|
+
is the repo owner or a declared maintainer. The owner is always an implicit
|
|
4271
|
+
maintainer; this command edits the explicit set on the kind:30617 announcement.
|
|
4272
|
+
|
|
4273
|
+
Subcommands:
|
|
4274
|
+
list show the owner + declared maintainers \u2014 FREE (relay read)
|
|
4275
|
+
add <pubkey> add a maintainer (npub or 64-char hex) \u2014 PAID: republishes
|
|
4276
|
+
the kind:30617 (permanent, non-refundable)
|
|
4277
|
+
remove <pubkey> remove a maintainer \u2014 PAID: republishes the kind:30617
|
|
4278
|
+
|
|
4279
|
+
add/remove must run under the repo OWNER's identity (only the owner's
|
|
4280
|
+
announcement is authoritative).
|
|
4281
|
+
|
|
4282
|
+
Options:
|
|
4283
|
+
--repo-id <id> repository id / NIP-34 d-tag (default: git config)
|
|
4284
|
+
--owner <pubkey> repository owner (npub or hex; default: git config)
|
|
4285
|
+
--remote <name> publish/read via this configured git remote (default: origin)
|
|
4286
|
+
--relay <url> ad-hoc relay override (exactly one for add/remove)
|
|
4287
|
+
--yes skip the fee confirmation (required when not a TTY)
|
|
4288
|
+
--json machine-readable envelope
|
|
4289
|
+
-h, --help show this help`;
|
|
4290
|
+
var MAINT_OPTIONS = {
|
|
4291
|
+
json: { type: "boolean", default: false },
|
|
4292
|
+
yes: { type: "boolean", default: false },
|
|
4293
|
+
relay: { type: "string", multiple: true },
|
|
4294
|
+
remote: { type: "string" },
|
|
4295
|
+
"repo-id": { type: "string" },
|
|
4296
|
+
owner: { type: "string" },
|
|
4297
|
+
help: { type: "boolean", short: "h", default: false }
|
|
4298
|
+
};
|
|
4299
|
+
function pickFlags(values) {
|
|
4300
|
+
const flags = {
|
|
4301
|
+
json: values["json"] === true,
|
|
4302
|
+
yes: values["yes"] === true,
|
|
4303
|
+
help: values["help"] === true,
|
|
4304
|
+
relay: Array.isArray(values["relay"]) ? values["relay"] : []
|
|
4305
|
+
};
|
|
4306
|
+
if (typeof values["remote"] === "string") flags.remote = values["remote"];
|
|
4307
|
+
if (typeof values["repo-id"] === "string") flags.repoId = values["repo-id"];
|
|
4308
|
+
if (typeof values["owner"] === "string")
|
|
4309
|
+
flags.owner = ownerToHex(values["owner"]);
|
|
4310
|
+
return flags;
|
|
4311
|
+
}
|
|
4312
|
+
async function resolveContext(flags, deps) {
|
|
4313
|
+
let repoRoot;
|
|
4314
|
+
let toonConfig = {
|
|
4315
|
+
relays: []
|
|
4316
|
+
};
|
|
4317
|
+
try {
|
|
4318
|
+
repoRoot = await resolveRepoRoot(deps.cwd);
|
|
4319
|
+
toonConfig = await readToonConfig(repoRoot);
|
|
4320
|
+
} catch {
|
|
4321
|
+
}
|
|
4322
|
+
const repoId = flags.repoId ?? toonConfig.repoId;
|
|
4323
|
+
if (!repoId) throw new UnconfiguredRepoAddressError("repository id");
|
|
4324
|
+
const owner = flags.owner ?? toonConfig.owner;
|
|
4325
|
+
if (!owner) throw new UnconfiguredRepoAddressError("repository owner");
|
|
4326
|
+
const resolved = await resolveRelays({
|
|
4327
|
+
relayFlags: flags.relay,
|
|
4328
|
+
remoteName: flags.remote,
|
|
4329
|
+
repoRoot,
|
|
4330
|
+
toonRelays: toonConfig.relays
|
|
4331
|
+
});
|
|
4332
|
+
if (resolved.nudge !== void 0) deps.io.err(resolved.nudge);
|
|
4333
|
+
return {
|
|
4334
|
+
repoId,
|
|
4335
|
+
owner,
|
|
4336
|
+
relays: resolved.relays,
|
|
4337
|
+
resolved,
|
|
4338
|
+
...repoRoot !== void 0 ? { repoRoot } : {}
|
|
4339
|
+
};
|
|
4340
|
+
}
|
|
4341
|
+
async function runMaintainers(args, deps) {
|
|
4342
|
+
const { io: io2 } = deps;
|
|
4343
|
+
const [sub, ...rest] = args;
|
|
4344
|
+
switch (sub) {
|
|
4345
|
+
case "list":
|
|
4346
|
+
return runList2(rest, deps);
|
|
4347
|
+
case "add":
|
|
4348
|
+
return runMutate("add", rest, deps);
|
|
4349
|
+
case "remove":
|
|
4350
|
+
return runMutate("remove", rest, deps);
|
|
4351
|
+
case "--help":
|
|
4352
|
+
case "-h":
|
|
4353
|
+
case "help":
|
|
4354
|
+
io2.out(MAINTAINERS_USAGE);
|
|
4355
|
+
return 0;
|
|
4356
|
+
default:
|
|
4357
|
+
io2.err(
|
|
4358
|
+
sub === void 0 ? "missing subcommand: rig maintainers <list|add|remove>" : `unknown rig maintainers subcommand: ${sub}`
|
|
4359
|
+
);
|
|
4360
|
+
io2.err(MAINTAINERS_USAGE);
|
|
4361
|
+
return 2;
|
|
4362
|
+
}
|
|
4363
|
+
}
|
|
4364
|
+
async function runList2(args, deps) {
|
|
4365
|
+
const { io: io2 } = deps;
|
|
4366
|
+
let flags;
|
|
4367
|
+
try {
|
|
4368
|
+
const { values, positionals } = parseArgs12({
|
|
4369
|
+
args,
|
|
4370
|
+
options: MAINT_OPTIONS,
|
|
4371
|
+
allowPositionals: true
|
|
4372
|
+
});
|
|
4373
|
+
if (positionals.length > 0) {
|
|
4374
|
+
throw new Error("rig maintainers list takes no positional arguments");
|
|
4375
|
+
}
|
|
4376
|
+
flags = pickFlags(values);
|
|
4377
|
+
} catch (err) {
|
|
4378
|
+
io2.err(err instanceof Error ? err.message : String(err));
|
|
4379
|
+
io2.err(MAINTAINERS_USAGE);
|
|
4380
|
+
return 2;
|
|
4381
|
+
}
|
|
4382
|
+
if (flags.help) {
|
|
4383
|
+
io2.out(MAINTAINERS_USAGE);
|
|
4384
|
+
return 0;
|
|
4385
|
+
}
|
|
4386
|
+
try {
|
|
4387
|
+
const ctx = await resolveContext(flags, deps);
|
|
4388
|
+
const wsRelays = ctx.relays.filter((url) => WS_URL_RE4.test(url));
|
|
4389
|
+
if (wsRelays.length === 0) {
|
|
4390
|
+
throw new InvalidRelayUrlError(
|
|
4391
|
+
ctx.relays[0] ?? "",
|
|
4392
|
+
"reads need a ws:// or wss:// relay"
|
|
4393
|
+
);
|
|
4394
|
+
}
|
|
4395
|
+
const remote = await fetchRemoteState({
|
|
4396
|
+
relayUrls: wsRelays,
|
|
4397
|
+
ownerPubkey: ctx.owner,
|
|
4398
|
+
repoId: ctx.repoId,
|
|
4399
|
+
...deps.webSocketFactory ? { webSocketFactory: deps.webSocketFactory } : {}
|
|
4400
|
+
});
|
|
4401
|
+
const maintainers = remote.maintainers;
|
|
4402
|
+
if (flags.json) {
|
|
4403
|
+
io2.emitJson({
|
|
4404
|
+
command: "maintainers list",
|
|
4405
|
+
repoAddr: { ownerPubkey: ctx.owner, repoId: ctx.repoId },
|
|
4406
|
+
announced: remote.announced,
|
|
4407
|
+
owner: ctx.owner,
|
|
4408
|
+
maintainers
|
|
4409
|
+
});
|
|
4410
|
+
return 0;
|
|
4411
|
+
}
|
|
4412
|
+
io2.out(`Repo: 30617:${ctx.owner}:${ctx.repoId}`);
|
|
4413
|
+
io2.out(`Owner: ${ctx.owner} (implicit maintainer)`);
|
|
4414
|
+
if (!remote.announced) {
|
|
4415
|
+
io2.out("No kind:30617 announcement found \u2014 owner-only authority.");
|
|
4416
|
+
return 0;
|
|
4417
|
+
}
|
|
4418
|
+
if (maintainers.length === 0) {
|
|
4419
|
+
io2.out("Maintainers: (none declared \u2014 owner-only authority)");
|
|
4420
|
+
} else {
|
|
4421
|
+
io2.out(`Maintainers (${maintainers.length}):`);
|
|
4422
|
+
for (const m of maintainers) io2.out(` ${m}`);
|
|
4423
|
+
}
|
|
4424
|
+
return 0;
|
|
4425
|
+
} catch (err) {
|
|
4426
|
+
return emitCliError(io2, flags.json, "maintainers list", err);
|
|
4427
|
+
}
|
|
4428
|
+
}
|
|
4429
|
+
async function runMutate(op, args, deps) {
|
|
4430
|
+
const { io: io2 } = deps;
|
|
4431
|
+
const command = `maintainers ${op}`;
|
|
4432
|
+
let flags;
|
|
4433
|
+
let pubkey;
|
|
4434
|
+
try {
|
|
4435
|
+
const { values, positionals } = parseArgs12({
|
|
4436
|
+
args,
|
|
4437
|
+
options: MAINT_OPTIONS,
|
|
4438
|
+
allowPositionals: true
|
|
4439
|
+
});
|
|
4440
|
+
flags = pickFlags(values);
|
|
4441
|
+
if (flags.help) {
|
|
4442
|
+
io2.out(MAINTAINERS_USAGE);
|
|
4443
|
+
return 0;
|
|
4444
|
+
}
|
|
4445
|
+
if (positionals.length !== 1) {
|
|
4446
|
+
throw new Error(`expected exactly one <pubkey> to ${op}`);
|
|
4447
|
+
}
|
|
4448
|
+
pubkey = ownerToHex(positionals[0]).toLowerCase();
|
|
4449
|
+
if (!HEX64_RE3.test(pubkey)) {
|
|
4450
|
+
throw new Error(`<pubkey> must resolve to 64-char hex (got ${JSON.stringify(positionals[0])})`);
|
|
4451
|
+
}
|
|
4452
|
+
} catch (err) {
|
|
4453
|
+
io2.err(err instanceof Error ? err.message : String(err));
|
|
4454
|
+
io2.err(MAINTAINERS_USAGE);
|
|
4455
|
+
return 2;
|
|
4456
|
+
}
|
|
4457
|
+
let standaloneCtx;
|
|
4458
|
+
try {
|
|
4459
|
+
const ctx = await resolveContext(flags, deps);
|
|
4460
|
+
if (ctx.relays.length > 1) {
|
|
4461
|
+
io2.err(singleRelayRefusal(ctx.resolved, "Nothing was published or paid."));
|
|
4462
|
+
return 1;
|
|
4463
|
+
}
|
|
4464
|
+
const relayUrl = ctx.relays[0];
|
|
4465
|
+
if (relayUrl === void 0 || !WS_URL_RE4.test(relayUrl)) {
|
|
4466
|
+
throw new InvalidRelayUrlError(
|
|
4467
|
+
relayUrl ?? "",
|
|
4468
|
+
"a paid publish needs a ws:// or wss:// relay"
|
|
4469
|
+
);
|
|
4470
|
+
}
|
|
4471
|
+
const load = deps.loadStandalone ?? defaultLoadStandalone;
|
|
4472
|
+
standaloneCtx = await load({
|
|
4473
|
+
env: deps.env,
|
|
4474
|
+
cwd: deps.cwd,
|
|
4475
|
+
warn: (line) => io2.err(line),
|
|
4476
|
+
relayUrl
|
|
4477
|
+
});
|
|
4478
|
+
const identity = identityReport(standaloneCtx);
|
|
4479
|
+
if (identity.pubkey.toLowerCase() !== ctx.owner.toLowerCase()) {
|
|
4480
|
+
io2.err(
|
|
4481
|
+
`rig: only the repo owner (${ctx.owner.slice(0, 8)}\u2026) can change the maintainer set \u2014 the active identity is ${identity.pubkey.slice(0, 8)}\u2026. A non-owner republish would write your own (ignored) announcement. Nothing was published or paid.`
|
|
4482
|
+
);
|
|
4483
|
+
return 1;
|
|
4484
|
+
}
|
|
4485
|
+
const remote = await fetchRemoteState({
|
|
4486
|
+
relayUrls: [relayUrl],
|
|
4487
|
+
ownerPubkey: ctx.owner,
|
|
4488
|
+
repoId: ctx.repoId,
|
|
4489
|
+
...deps.webSocketFactory ? { webSocketFactory: deps.webSocketFactory } : {}
|
|
4490
|
+
});
|
|
4491
|
+
if (!remote.announced) {
|
|
4492
|
+
io2.err(
|
|
4493
|
+
`rig: 30617:${ctx.owner.slice(0, 8)}\u2026:${ctx.repoId} has no announcement yet \u2014 run \`rig push\` to publish the repo (with its real name/description) before managing maintainers. Nothing was published or paid.`
|
|
4494
|
+
);
|
|
4495
|
+
return 1;
|
|
4496
|
+
}
|
|
4497
|
+
const current = remote.announceEvent ? parseMaintainers(remote.announceEvent.tags) : [];
|
|
4498
|
+
const currentSet = new Set(current);
|
|
4499
|
+
if (op === "add" && currentSet.has(pubkey)) {
|
|
4500
|
+
io2.err(
|
|
4501
|
+
`rig: ${pubkey.slice(0, 8)}\u2026 is already a maintainer \u2014 nothing to do (not published).`
|
|
4502
|
+
);
|
|
4503
|
+
return 0;
|
|
4504
|
+
}
|
|
4505
|
+
if (op === "remove" && !currentSet.has(pubkey)) {
|
|
4506
|
+
io2.err(
|
|
4507
|
+
`rig: ${pubkey.slice(0, 8)}\u2026 is not a declared maintainer \u2014 nothing to do (not published).`
|
|
4508
|
+
);
|
|
4509
|
+
return 0;
|
|
4510
|
+
}
|
|
4511
|
+
const next = op === "add" ? [...current, pubkey] : current.filter((m) => m !== pubkey);
|
|
4512
|
+
const name = remote.name ?? ctx.repoId;
|
|
4513
|
+
const description = remote.description ?? "";
|
|
4514
|
+
const event = buildRepoAnnouncement(ctx.repoId, name, description, next);
|
|
4515
|
+
const fee = (await standaloneCtx.publisher.getFeeRates()).eventFee.toString();
|
|
4516
|
+
const action = `kind:30617 maintainers ${op} ${pubkey.slice(0, 8)}\u2026`;
|
|
4517
|
+
if (!flags.json) {
|
|
4518
|
+
io2.out(`Republish ${action}`);
|
|
4519
|
+
io2.out(`Repo: 30617:${ctx.owner}:${ctx.repoId}`);
|
|
4520
|
+
io2.out(`Maintainers after: ${next.length === 0 ? "(none)" : next.join(", ")}`);
|
|
4521
|
+
io2.out(`Fee: ${feeLabel(fee)}. Writes are permanent and non-refundable.`);
|
|
4522
|
+
}
|
|
4523
|
+
if (!flags.yes) {
|
|
4524
|
+
if (flags.json) {
|
|
4525
|
+
io2.emitJson({
|
|
4526
|
+
command,
|
|
4527
|
+
repoAddr: { ownerPubkey: ctx.owner, repoId: ctx.repoId },
|
|
4528
|
+
identity,
|
|
4529
|
+
executed: false,
|
|
4530
|
+
feeEstimate: fee,
|
|
4531
|
+
maintainers: next,
|
|
4532
|
+
hint: "estimate only \u2014 re-run with --yes to publish (permanent, non-refundable)"
|
|
4533
|
+
});
|
|
4534
|
+
return 0;
|
|
4535
|
+
}
|
|
4536
|
+
if (!io2.isInteractive) {
|
|
4537
|
+
io2.err(
|
|
4538
|
+
"refusing to spend channel funds without confirmation in a non-interactive session \u2014 re-run with --yes (or use --json for an estimate)"
|
|
4539
|
+
);
|
|
4540
|
+
return 1;
|
|
4541
|
+
}
|
|
4542
|
+
const proceed = await io2.confirm(
|
|
4543
|
+
`Proceed with paid republish (${feeLabel(fee)})? [y/N] `
|
|
4544
|
+
);
|
|
4545
|
+
if (!proceed) {
|
|
4546
|
+
io2.err("aborted \u2014 nothing was published.");
|
|
4547
|
+
return 1;
|
|
4548
|
+
}
|
|
4549
|
+
}
|
|
4550
|
+
const receipt = await standaloneCtx.publisher.publishEvent(event, [
|
|
4551
|
+
relayUrl
|
|
4552
|
+
]);
|
|
4553
|
+
const result = serializeEventReceipt(event.kind, receipt);
|
|
4554
|
+
if (flags.json) {
|
|
4555
|
+
io2.emitJson({
|
|
4556
|
+
command,
|
|
4557
|
+
repoAddr: { ownerPubkey: ctx.owner, repoId: ctx.repoId },
|
|
4558
|
+
identity,
|
|
4559
|
+
executed: true,
|
|
4560
|
+
feeEstimate: fee,
|
|
4561
|
+
maintainers: next,
|
|
4562
|
+
result
|
|
4563
|
+
});
|
|
4564
|
+
} else {
|
|
4565
|
+
io2.out(
|
|
4566
|
+
`Published ${action}: ${result.eventId} paid ${result.feePaid} base units`
|
|
4567
|
+
);
|
|
4568
|
+
}
|
|
4569
|
+
return 0;
|
|
4570
|
+
} catch (err) {
|
|
4571
|
+
return emitCliError(io2, flags.json, command, err);
|
|
4572
|
+
} finally {
|
|
4573
|
+
if (standaloneCtx) {
|
|
4574
|
+
try {
|
|
4575
|
+
await standaloneCtx.stop();
|
|
4576
|
+
} catch {
|
|
4577
|
+
}
|
|
4578
|
+
}
|
|
4579
|
+
}
|
|
4580
|
+
}
|
|
4581
|
+
|
|
3720
4582
|
// src/cli/dispatch.ts
|
|
3721
4583
|
var USAGE = `rig \u2014 git with a TOON remote (pay-to-write Nostr + Arweave)
|
|
3722
4584
|
|
|
3723
4585
|
Usage: rig <command> [options]
|
|
3724
4586
|
|
|
3725
4587
|
Commands rig owns:
|
|
3726
|
-
|
|
3727
|
-
|
|
4588
|
+
identity create generate a fresh identity (BIP-39) into the
|
|
4589
|
+
encrypted keystore \u2014 the phrase is shown ONCE;
|
|
4590
|
+
back it up. This is your first step on a new box
|
|
4591
|
+
identity show the active identity's source + derived pubkey
|
|
4592
|
+
(never the phrase)
|
|
4593
|
+
identity import write an EXISTING phrase (read from stdin) to the
|
|
4594
|
+
encrypted keystore
|
|
4595
|
+
init set up this repo: resolve your identity (or offer
|
|
4596
|
+
to generate one) and write the toon.* git config
|
|
3728
4597
|
remote add <name> <url> add a relay as a REAL git remote ("origin" is
|
|
3729
4598
|
remote remove <name> the default publish target); remove/list manage
|
|
3730
4599
|
remote list them \u2014 \`git remote -v\` shows the same data
|
|
@@ -3749,6 +4618,10 @@ Commands rig owns:
|
|
|
3749
4618
|
\`git format-patch\` content
|
|
3750
4619
|
pr status <event-id> <state> set an issue/patch status (kind:1630-1633):
|
|
3751
4620
|
open | applied | closed | draft
|
|
4621
|
+
maintainers list show the repo's declared maintainers (free); add/
|
|
4622
|
+
maintainers add <pubkey> remove republish the kind:30617 to change who may
|
|
4623
|
+
maintainers remove <pubkey> author authoritative issue/PR status (owner is
|
|
4624
|
+
always an implicit maintainer)
|
|
3752
4625
|
fund drip devnet faucet funds to this identity's
|
|
3753
4626
|
wallet (free); on other networks prints the
|
|
3754
4627
|
address(es) to fund externally
|
|
@@ -3797,6 +4670,8 @@ async function dispatch(argv2, deps) {
|
|
|
3797
4670
|
switch (command) {
|
|
3798
4671
|
case "init":
|
|
3799
4672
|
return runInit(rest, deps);
|
|
4673
|
+
case "identity":
|
|
4674
|
+
return runIdentity(rest, deps);
|
|
3800
4675
|
case "remote":
|
|
3801
4676
|
return runRemote(rest, deps);
|
|
3802
4677
|
// The #278 read path — FREE (relay + Arweave gateway reads, no payment).
|
|
@@ -3812,6 +4687,8 @@ async function dispatch(argv2, deps) {
|
|
|
3812
4687
|
return runComment(rest, deps);
|
|
3813
4688
|
case "pr":
|
|
3814
4689
|
return runPr(rest, deps);
|
|
4690
|
+
case "maintainers":
|
|
4691
|
+
return runMaintainers(rest, deps);
|
|
3815
4692
|
case "channel":
|
|
3816
4693
|
return runChannel(rest, deps);
|
|
3817
4694
|
case "fund":
|
|
@@ -3882,6 +4759,7 @@ function makeCliIo(options) {
|
|
|
3882
4759
|
}
|
|
3883
4760
|
var RIG_OWNED_VERBS = /* @__PURE__ */ new Set([
|
|
3884
4761
|
"init",
|
|
4762
|
+
"identity",
|
|
3885
4763
|
"remote",
|
|
3886
4764
|
"clone",
|
|
3887
4765
|
"fetch",
|
|
@@ -3889,6 +4767,7 @@ var RIG_OWNED_VERBS = /* @__PURE__ */ new Set([
|
|
|
3889
4767
|
"issue",
|
|
3890
4768
|
"comment",
|
|
3891
4769
|
"pr",
|
|
4770
|
+
"maintainers",
|
|
3892
4771
|
"channel",
|
|
3893
4772
|
"fund",
|
|
3894
4773
|
"balance"
|
|
@@ -3958,7 +4837,7 @@ function makeIo(jsonMode) {
|
|
|
3958
4837
|
},
|
|
3959
4838
|
isInteractive: Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
3960
4839
|
confirm: async (question) => {
|
|
3961
|
-
const rl =
|
|
4840
|
+
const rl = createInterface2({
|
|
3962
4841
|
input: process.stdin,
|
|
3963
4842
|
output: process.stderr
|
|
3964
4843
|
});
|