@toon-protocol/rig 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-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/cli/rig.js +447 -22
- 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-JLGAUMRF.js} +2 -2
- package/package.json +1 -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-JLGAUMRF.js.map} +0 -0
package/dist/cli/rig.js
CHANGED
|
@@ -17,20 +17,23 @@ import {
|
|
|
17
17
|
setHeadSymref,
|
|
18
18
|
updateRef,
|
|
19
19
|
writeGitObjects
|
|
20
|
-
} from "../chunk-
|
|
20
|
+
} from "../chunk-W2SDL2PE.js";
|
|
21
21
|
import {
|
|
22
22
|
MissingIdentityError,
|
|
23
23
|
resolveIdentity
|
|
24
24
|
} from "../chunk-CW4HJNMU.js";
|
|
25
25
|
import {
|
|
26
26
|
COMMENT_KIND,
|
|
27
|
+
authorizedStatusAuthors,
|
|
27
28
|
buildComment,
|
|
28
29
|
buildIssue,
|
|
29
30
|
buildPatch,
|
|
31
|
+
buildRepoAnnouncement,
|
|
30
32
|
buildStatus,
|
|
31
33
|
fetchRemoteState,
|
|
34
|
+
parseMaintainers,
|
|
32
35
|
queryRelay
|
|
33
|
-
} from "../chunk-
|
|
36
|
+
} from "../chunk-3HRFDH7H.js";
|
|
34
37
|
import {
|
|
35
38
|
ChannelMapStore,
|
|
36
39
|
channelStatus,
|
|
@@ -897,7 +900,7 @@ function loadPaidSession(deps, relayUrl) {
|
|
|
897
900
|
});
|
|
898
901
|
}
|
|
899
902
|
var defaultLoadStandalone = async (options) => {
|
|
900
|
-
const mod = await import("../standalone-mode-
|
|
903
|
+
const mod = await import("../standalone-mode-JLGAUMRF.js");
|
|
901
904
|
return mod.createStandaloneContext(options);
|
|
902
905
|
};
|
|
903
906
|
function identityReport(ctx) {
|
|
@@ -2100,10 +2103,11 @@ function tagValue(tags, name) {
|
|
|
2100
2103
|
function tagValues(tags, name) {
|
|
2101
2104
|
return tags.filter((t) => t[0] === name && t[1]).map((t) => t[1]);
|
|
2102
2105
|
}
|
|
2103
|
-
function deriveStatus(targetEventId, statusEvents) {
|
|
2106
|
+
function deriveStatus(targetEventId, statusEvents, authorized) {
|
|
2104
2107
|
let winner = null;
|
|
2105
2108
|
for (const event of statusEvents) {
|
|
2106
2109
|
if (STATUS_BY_KIND[event.kind] === void 0) continue;
|
|
2110
|
+
if (!authorized.has(event.pubkey.toLowerCase())) continue;
|
|
2107
2111
|
if (!event.tags.some((t) => t[0] === "e" && t[1] === targetEventId))
|
|
2108
2112
|
continue;
|
|
2109
2113
|
if (winner === null || event.created_at > winner.created_at || event.created_at === winner.created_at && event.id < winner.id) {
|
|
@@ -2184,12 +2188,13 @@ async function resolveTrackerContext(flags, deps, needRepoAddr) {
|
|
|
2184
2188
|
} catch {
|
|
2185
2189
|
}
|
|
2186
2190
|
let repoAddr = null;
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
const owner = flags.owner ?? toonConfig.owner;
|
|
2191
|
-
if (!owner) throw new UnconfiguredRepoAddressError("repository owner");
|
|
2191
|
+
const repoId = flags.repoId ?? toonConfig.repoId;
|
|
2192
|
+
const owner = flags.owner ?? toonConfig.owner;
|
|
2193
|
+
if (repoId && owner) {
|
|
2192
2194
|
repoAddr = { ownerPubkey: owner, repoId };
|
|
2195
|
+
} else if (needRepoAddr) {
|
|
2196
|
+
if (!repoId) throw new UnconfiguredRepoAddressError("repository id");
|
|
2197
|
+
throw new UnconfiguredRepoAddressError("repository owner");
|
|
2193
2198
|
}
|
|
2194
2199
|
const resolved = await resolveRelays({
|
|
2195
2200
|
relayFlags: flags.relay,
|
|
@@ -2213,6 +2218,34 @@ async function resolveTrackerContext(flags, deps, needRepoAddr) {
|
|
|
2213
2218
|
function repoATag(addr) {
|
|
2214
2219
|
return `${REPOSITORY_ANNOUNCEMENT_KIND}:${addr.ownerPubkey}:${addr.repoId}`;
|
|
2215
2220
|
}
|
|
2221
|
+
async function fetchAuthorizedAuthors(ctx, ownerPubkey, repoId) {
|
|
2222
|
+
let announceTags = [];
|
|
2223
|
+
try {
|
|
2224
|
+
const events = await queryAll(
|
|
2225
|
+
ctx.relays,
|
|
2226
|
+
[
|
|
2227
|
+
{
|
|
2228
|
+
kinds: [REPOSITORY_ANNOUNCEMENT_KIND],
|
|
2229
|
+
authors: [ownerPubkey],
|
|
2230
|
+
"#d": [repoId]
|
|
2231
|
+
}
|
|
2232
|
+
],
|
|
2233
|
+
ctx.webSocketFactory
|
|
2234
|
+
);
|
|
2235
|
+
let latest = null;
|
|
2236
|
+
for (const event of events.values()) {
|
|
2237
|
+
if (event.kind !== REPOSITORY_ANNOUNCEMENT_KIND) continue;
|
|
2238
|
+
if (event.pubkey !== ownerPubkey) continue;
|
|
2239
|
+
if (!event.tags.some((t) => t[0] === "d" && t[1] === repoId)) continue;
|
|
2240
|
+
if (latest === null || event.created_at > latest.created_at || event.created_at === latest.created_at && event.id < latest.id) {
|
|
2241
|
+
latest = event;
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
if (latest) announceTags = latest.tags;
|
|
2245
|
+
} catch {
|
|
2246
|
+
}
|
|
2247
|
+
return authorizedStatusAuthors(ownerPubkey, announceTags);
|
|
2248
|
+
}
|
|
2216
2249
|
function parseTrackerItem(event, status) {
|
|
2217
2250
|
const item = {
|
|
2218
2251
|
eventId: event.id,
|
|
@@ -2237,8 +2270,12 @@ function isoDate(createdAt) {
|
|
|
2237
2270
|
return new Date(createdAt * 1e3).toISOString().slice(0, 10);
|
|
2238
2271
|
}
|
|
2239
2272
|
async function fetchItems(ctx, kind) {
|
|
2240
|
-
const
|
|
2241
|
-
|
|
2273
|
+
const addr = ctx.repoAddr;
|
|
2274
|
+
const aTag = repoATag(addr);
|
|
2275
|
+
const authorizedPromise = fetchAuthorizedAuthors(
|
|
2276
|
+
ctx,
|
|
2277
|
+
addr.ownerPubkey,
|
|
2278
|
+
addr.repoId
|
|
2242
2279
|
);
|
|
2243
2280
|
const [itemEvents, aStatusEvents] = await Promise.all([
|
|
2244
2281
|
queryAll(
|
|
@@ -2265,8 +2302,9 @@ async function fetchItems(ctx, kind) {
|
|
|
2265
2302
|
for (const [id, event] of byE)
|
|
2266
2303
|
if (!statuses.has(id)) statuses.set(id, event);
|
|
2267
2304
|
}
|
|
2305
|
+
const authorized = await authorizedPromise;
|
|
2268
2306
|
return items.map(
|
|
2269
|
-
(event) => parseTrackerItem(event, deriveStatus(event.id, statuses.values()))
|
|
2307
|
+
(event) => parseTrackerItem(event, deriveStatus(event.id, statuses.values(), authorized))
|
|
2270
2308
|
).sort((a, b) => b.createdAt - a.createdAt);
|
|
2271
2309
|
}
|
|
2272
2310
|
async function fetchItem(ctx, eventId, expectedKind) {
|
|
@@ -2305,12 +2343,27 @@ async function fetchItem(ctx, eventId, expectedKind) {
|
|
|
2305
2343
|
createdAt: e.created_at,
|
|
2306
2344
|
content: e.content
|
|
2307
2345
|
}));
|
|
2346
|
+
const repoATag2 = tagValue(event.tags, "a") ?? null;
|
|
2347
|
+
const parsedAddr = parseRepoATag(repoATag2) ?? ctx.repoAddr;
|
|
2348
|
+
const authorized = parsedAddr ? await fetchAuthorizedAuthors(ctx, parsedAddr.ownerPubkey, parsedAddr.repoId) : /* @__PURE__ */ new Set();
|
|
2308
2349
|
return {
|
|
2309
|
-
item: parseTrackerItem(
|
|
2350
|
+
item: parseTrackerItem(
|
|
2351
|
+
event,
|
|
2352
|
+
deriveStatus(eventId, statusEvents.values(), authorized)
|
|
2353
|
+
),
|
|
2310
2354
|
comments,
|
|
2311
|
-
repoATag:
|
|
2355
|
+
repoATag: repoATag2
|
|
2312
2356
|
};
|
|
2313
2357
|
}
|
|
2358
|
+
function parseRepoATag(aTag) {
|
|
2359
|
+
if (!aTag) return null;
|
|
2360
|
+
const [kind, ownerPubkey, ...repoIdParts] = aTag.split(":");
|
|
2361
|
+
const repoId = repoIdParts.join(":");
|
|
2362
|
+
if (kind !== String(REPOSITORY_ANNOUNCEMENT_KIND) || !ownerPubkey || !repoId) {
|
|
2363
|
+
return null;
|
|
2364
|
+
}
|
|
2365
|
+
return { ownerPubkey, repoId };
|
|
2366
|
+
}
|
|
2314
2367
|
async function runList(args, deps, spec) {
|
|
2315
2368
|
const { io: io2 } = deps;
|
|
2316
2369
|
let flags;
|
|
@@ -2663,6 +2716,9 @@ async function runEvent(opts) {
|
|
|
2663
2716
|
const addr = { ownerPubkey: owner, repoId };
|
|
2664
2717
|
const event = await opts.buildEvent(addr);
|
|
2665
2718
|
const action = `kind:${event.kind} ${actionLabel}`;
|
|
2719
|
+
if (opts.preConfirm) {
|
|
2720
|
+
await opts.preConfirm({ identity, addr, relayUrl: relaysUsed[0] });
|
|
2721
|
+
}
|
|
2666
2722
|
if (!flags.json) {
|
|
2667
2723
|
for (const line of renderEventPlan({
|
|
2668
2724
|
action,
|
|
@@ -3109,9 +3165,41 @@ async function runPrStatus(args, deps) {
|
|
|
3109
3165
|
]);
|
|
3110
3166
|
return event;
|
|
3111
3167
|
},
|
|
3168
|
+
// Authority warning (#287): a status event only moves an issue/PR's
|
|
3169
|
+
// resolved state for repo-authority-honoring clients if its author is the
|
|
3170
|
+
// owner or a declared maintainer. Warn clearly when the active identity is
|
|
3171
|
+
// neither — the publish still happens (permissionless relay; the caller
|
|
3172
|
+
// may be acting on their own repo/fork), but the futility is made obvious.
|
|
3173
|
+
preConfirm: (args2) => warnIfNotMaintainer(deps, args2),
|
|
3112
3174
|
sendDaemon: (client, addr) => client.gitStatus({ repoAddr: addr, targetEventId, status })
|
|
3113
3175
|
});
|
|
3114
3176
|
}
|
|
3177
|
+
async function warnIfNotMaintainer(deps, args) {
|
|
3178
|
+
const { io: io2 } = deps;
|
|
3179
|
+
const { identity, addr, relayUrl } = args;
|
|
3180
|
+
if (identity.pubkey.toLowerCase() === addr.ownerPubkey.toLowerCase()) return;
|
|
3181
|
+
if (relayUrl === void 0) return;
|
|
3182
|
+
try {
|
|
3183
|
+
const remote = await fetchRemoteState({
|
|
3184
|
+
relayUrls: [relayUrl],
|
|
3185
|
+
ownerPubkey: addr.ownerPubkey,
|
|
3186
|
+
repoId: addr.repoId,
|
|
3187
|
+
...deps.webSocketFactory ? { webSocketFactory: deps.webSocketFactory } : {}
|
|
3188
|
+
});
|
|
3189
|
+
const authorized = authorizedStatusAuthors(
|
|
3190
|
+
addr.ownerPubkey,
|
|
3191
|
+
remote.announceEvent?.tags ?? []
|
|
3192
|
+
);
|
|
3193
|
+
if (authorized.has(identity.pubkey.toLowerCase())) return;
|
|
3194
|
+
io2.err(
|
|
3195
|
+
`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.`
|
|
3196
|
+
);
|
|
3197
|
+
} catch {
|
|
3198
|
+
io2.err(
|
|
3199
|
+
"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."
|
|
3200
|
+
);
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3115
3203
|
|
|
3116
3204
|
// src/cli/fetch.ts
|
|
3117
3205
|
import { parseArgs as parseArgs8 } from "util";
|
|
@@ -3344,10 +3432,11 @@ var FUND_USAGE = `Usage: rig fund [options]
|
|
|
3344
3432
|
Drip devnet test funds to the active identity's wallet \u2014 free (the faucet
|
|
3345
3433
|
pays). The identity comes from RIG_MNEMONIC (env or a project .env) or the
|
|
3346
3434
|
~/.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
|
|
3435
|
+
faucetUrl config field, or the deployed devnet faucet when the network is
|
|
3436
|
+
devnet \u2014 including when a configured *.devnet.toonprotocol.dev origin infers
|
|
3437
|
+
it. The faucet drips a FIXED amount per chain (there is no --amount). On a
|
|
3438
|
+
network without a faucet, prints the wallet address(es) to fund externally
|
|
3439
|
+
instead.
|
|
3351
3440
|
|
|
3352
3441
|
Options:
|
|
3353
3442
|
--chain <chain> evm | solana | mina (default: TOON_CLIENT_CHAIN, the
|
|
@@ -3440,7 +3529,10 @@ async function runFund(args, deps) {
|
|
|
3440
3529
|
);
|
|
3441
3530
|
}
|
|
3442
3531
|
const network = env["TOON_CLIENT_NETWORK"] ?? file.network;
|
|
3443
|
-
const
|
|
3532
|
+
const devnetOrigin = sharedDevnetOrigin(env, file);
|
|
3533
|
+
const inferredDevnet = devnetOrigin !== void 0 && (network === void 0 || network === "custom");
|
|
3534
|
+
const effectiveNetwork = inferredDevnet ? "devnet" : network;
|
|
3535
|
+
const faucetUrl = env["TOON_CLIENT_FAUCET_URL"] ?? file.faucetUrl ?? (effectiveNetwork === "devnet" ? DEVNET_FAUCET_URL : void 0);
|
|
3444
3536
|
const resolved = await resolveIdentity({
|
|
3445
3537
|
env,
|
|
3446
3538
|
cwd: deps.cwd,
|
|
@@ -3462,7 +3554,7 @@ async function runFund(args, deps) {
|
|
|
3462
3554
|
mina: derived.mina.publicKey || null
|
|
3463
3555
|
};
|
|
3464
3556
|
if (!faucetUrl) {
|
|
3465
|
-
const guidance = noFaucetGuidance(network,
|
|
3557
|
+
const guidance = noFaucetGuidance(network, devnetOrigin);
|
|
3466
3558
|
if (json) {
|
|
3467
3559
|
io2.emitJson({
|
|
3468
3560
|
command: "fund",
|
|
@@ -3491,6 +3583,11 @@ async function runFund(args, deps) {
|
|
|
3491
3583
|
}
|
|
3492
3584
|
const timeoutEnv = env["TOON_CLIENT_FAUCET_TIMEOUT_MS"];
|
|
3493
3585
|
const timeout = timeoutEnv && Number.isFinite(Number(timeoutEnv)) ? Number(timeoutEnv) : file.faucetTimeoutMs ?? (chain === "mina" ? 13e4 : 9e4);
|
|
3586
|
+
if (!json && inferredDevnet) {
|
|
3587
|
+
io2.out(
|
|
3588
|
+
`Inferred network 'devnet' from the configured origin ${devnetOrigin} (network was ${JSON.stringify(network ?? "custom")}). Set TOON_CLIENT_NETWORK explicitly to override.`
|
|
3589
|
+
);
|
|
3590
|
+
}
|
|
3494
3591
|
if (!json) {
|
|
3495
3592
|
io2.out(
|
|
3496
3593
|
`Requesting ${chain} drip from ${faucetUrl} for ${address} \u2026` + (chain === "mina" ? " (mina settles slowly; this can take ~2 minutes)" : "")
|
|
@@ -3505,11 +3602,12 @@ async function runFund(args, deps) {
|
|
|
3505
3602
|
command: "fund",
|
|
3506
3603
|
identity,
|
|
3507
3604
|
funded: true,
|
|
3508
|
-
network:
|
|
3605
|
+
network: effectiveNetwork ?? null,
|
|
3509
3606
|
chain,
|
|
3510
3607
|
address,
|
|
3511
3608
|
faucetUrl,
|
|
3512
|
-
response
|
|
3609
|
+
response,
|
|
3610
|
+
...inferredDevnet ? { inferredDevnetFrom: devnetOrigin } : {}
|
|
3513
3611
|
});
|
|
3514
3612
|
return 0;
|
|
3515
3613
|
}
|
|
@@ -3717,6 +3815,326 @@ async function runInit(args, deps) {
|
|
|
3717
3815
|
}
|
|
3718
3816
|
}
|
|
3719
3817
|
|
|
3818
|
+
// src/cli/maintainers.ts
|
|
3819
|
+
import { parseArgs as parseArgs11 } from "util";
|
|
3820
|
+
var HEX64_RE3 = /^[0-9a-f]{64}$/;
|
|
3821
|
+
var WS_URL_RE4 = /^wss?:\/\//i;
|
|
3822
|
+
var MAINTAINERS_USAGE = `Usage: rig maintainers <list|add|remove> [<pubkey>] [options]
|
|
3823
|
+
|
|
3824
|
+
Manage a repo's declared maintainers (#287). Status authority is consumer-side:
|
|
3825
|
+
rig and rig-web honor an issue/PR status (kind:1630-1633) ONLY when its author
|
|
3826
|
+
is the repo owner or a declared maintainer. The owner is always an implicit
|
|
3827
|
+
maintainer; this command edits the explicit set on the kind:30617 announcement.
|
|
3828
|
+
|
|
3829
|
+
Subcommands:
|
|
3830
|
+
list show the owner + declared maintainers \u2014 FREE (relay read)
|
|
3831
|
+
add <pubkey> add a maintainer (npub or 64-char hex) \u2014 PAID: republishes
|
|
3832
|
+
the kind:30617 (permanent, non-refundable)
|
|
3833
|
+
remove <pubkey> remove a maintainer \u2014 PAID: republishes the kind:30617
|
|
3834
|
+
|
|
3835
|
+
add/remove must run under the repo OWNER's identity (only the owner's
|
|
3836
|
+
announcement is authoritative).
|
|
3837
|
+
|
|
3838
|
+
Options:
|
|
3839
|
+
--repo-id <id> repository id / NIP-34 d-tag (default: git config)
|
|
3840
|
+
--owner <pubkey> repository owner (npub or hex; default: git config)
|
|
3841
|
+
--remote <name> publish/read via this configured git remote (default: origin)
|
|
3842
|
+
--relay <url> ad-hoc relay override (exactly one for add/remove)
|
|
3843
|
+
--yes skip the fee confirmation (required when not a TTY)
|
|
3844
|
+
--json machine-readable envelope
|
|
3845
|
+
-h, --help show this help`;
|
|
3846
|
+
var MAINT_OPTIONS = {
|
|
3847
|
+
json: { type: "boolean", default: false },
|
|
3848
|
+
yes: { type: "boolean", default: false },
|
|
3849
|
+
relay: { type: "string", multiple: true },
|
|
3850
|
+
remote: { type: "string" },
|
|
3851
|
+
"repo-id": { type: "string" },
|
|
3852
|
+
owner: { type: "string" },
|
|
3853
|
+
help: { type: "boolean", short: "h", default: false }
|
|
3854
|
+
};
|
|
3855
|
+
function pickFlags(values) {
|
|
3856
|
+
const flags = {
|
|
3857
|
+
json: values["json"] === true,
|
|
3858
|
+
yes: values["yes"] === true,
|
|
3859
|
+
help: values["help"] === true,
|
|
3860
|
+
relay: Array.isArray(values["relay"]) ? values["relay"] : []
|
|
3861
|
+
};
|
|
3862
|
+
if (typeof values["remote"] === "string") flags.remote = values["remote"];
|
|
3863
|
+
if (typeof values["repo-id"] === "string") flags.repoId = values["repo-id"];
|
|
3864
|
+
if (typeof values["owner"] === "string")
|
|
3865
|
+
flags.owner = ownerToHex(values["owner"]);
|
|
3866
|
+
return flags;
|
|
3867
|
+
}
|
|
3868
|
+
async function resolveContext(flags, deps) {
|
|
3869
|
+
let repoRoot;
|
|
3870
|
+
let toonConfig = {
|
|
3871
|
+
relays: []
|
|
3872
|
+
};
|
|
3873
|
+
try {
|
|
3874
|
+
repoRoot = await resolveRepoRoot(deps.cwd);
|
|
3875
|
+
toonConfig = await readToonConfig(repoRoot);
|
|
3876
|
+
} catch {
|
|
3877
|
+
}
|
|
3878
|
+
const repoId = flags.repoId ?? toonConfig.repoId;
|
|
3879
|
+
if (!repoId) throw new UnconfiguredRepoAddressError("repository id");
|
|
3880
|
+
const owner = flags.owner ?? toonConfig.owner;
|
|
3881
|
+
if (!owner) throw new UnconfiguredRepoAddressError("repository owner");
|
|
3882
|
+
const resolved = await resolveRelays({
|
|
3883
|
+
relayFlags: flags.relay,
|
|
3884
|
+
remoteName: flags.remote,
|
|
3885
|
+
repoRoot,
|
|
3886
|
+
toonRelays: toonConfig.relays
|
|
3887
|
+
});
|
|
3888
|
+
if (resolved.nudge !== void 0) deps.io.err(resolved.nudge);
|
|
3889
|
+
return {
|
|
3890
|
+
repoId,
|
|
3891
|
+
owner,
|
|
3892
|
+
relays: resolved.relays,
|
|
3893
|
+
resolved,
|
|
3894
|
+
...repoRoot !== void 0 ? { repoRoot } : {}
|
|
3895
|
+
};
|
|
3896
|
+
}
|
|
3897
|
+
async function runMaintainers(args, deps) {
|
|
3898
|
+
const { io: io2 } = deps;
|
|
3899
|
+
const [sub, ...rest] = args;
|
|
3900
|
+
switch (sub) {
|
|
3901
|
+
case "list":
|
|
3902
|
+
return runList2(rest, deps);
|
|
3903
|
+
case "add":
|
|
3904
|
+
return runMutate("add", rest, deps);
|
|
3905
|
+
case "remove":
|
|
3906
|
+
return runMutate("remove", rest, deps);
|
|
3907
|
+
case "--help":
|
|
3908
|
+
case "-h":
|
|
3909
|
+
case "help":
|
|
3910
|
+
io2.out(MAINTAINERS_USAGE);
|
|
3911
|
+
return 0;
|
|
3912
|
+
default:
|
|
3913
|
+
io2.err(
|
|
3914
|
+
sub === void 0 ? "missing subcommand: rig maintainers <list|add|remove>" : `unknown rig maintainers subcommand: ${sub}`
|
|
3915
|
+
);
|
|
3916
|
+
io2.err(MAINTAINERS_USAGE);
|
|
3917
|
+
return 2;
|
|
3918
|
+
}
|
|
3919
|
+
}
|
|
3920
|
+
async function runList2(args, deps) {
|
|
3921
|
+
const { io: io2 } = deps;
|
|
3922
|
+
let flags;
|
|
3923
|
+
try {
|
|
3924
|
+
const { values, positionals } = parseArgs11({
|
|
3925
|
+
args,
|
|
3926
|
+
options: MAINT_OPTIONS,
|
|
3927
|
+
allowPositionals: true
|
|
3928
|
+
});
|
|
3929
|
+
if (positionals.length > 0) {
|
|
3930
|
+
throw new Error("rig maintainers list takes no positional arguments");
|
|
3931
|
+
}
|
|
3932
|
+
flags = pickFlags(values);
|
|
3933
|
+
} catch (err) {
|
|
3934
|
+
io2.err(err instanceof Error ? err.message : String(err));
|
|
3935
|
+
io2.err(MAINTAINERS_USAGE);
|
|
3936
|
+
return 2;
|
|
3937
|
+
}
|
|
3938
|
+
if (flags.help) {
|
|
3939
|
+
io2.out(MAINTAINERS_USAGE);
|
|
3940
|
+
return 0;
|
|
3941
|
+
}
|
|
3942
|
+
try {
|
|
3943
|
+
const ctx = await resolveContext(flags, deps);
|
|
3944
|
+
const wsRelays = ctx.relays.filter((url) => WS_URL_RE4.test(url));
|
|
3945
|
+
if (wsRelays.length === 0) {
|
|
3946
|
+
throw new InvalidRelayUrlError(
|
|
3947
|
+
ctx.relays[0] ?? "",
|
|
3948
|
+
"reads need a ws:// or wss:// relay"
|
|
3949
|
+
);
|
|
3950
|
+
}
|
|
3951
|
+
const remote = await fetchRemoteState({
|
|
3952
|
+
relayUrls: wsRelays,
|
|
3953
|
+
ownerPubkey: ctx.owner,
|
|
3954
|
+
repoId: ctx.repoId,
|
|
3955
|
+
...deps.webSocketFactory ? { webSocketFactory: deps.webSocketFactory } : {}
|
|
3956
|
+
});
|
|
3957
|
+
const maintainers = remote.maintainers;
|
|
3958
|
+
if (flags.json) {
|
|
3959
|
+
io2.emitJson({
|
|
3960
|
+
command: "maintainers list",
|
|
3961
|
+
repoAddr: { ownerPubkey: ctx.owner, repoId: ctx.repoId },
|
|
3962
|
+
announced: remote.announced,
|
|
3963
|
+
owner: ctx.owner,
|
|
3964
|
+
maintainers
|
|
3965
|
+
});
|
|
3966
|
+
return 0;
|
|
3967
|
+
}
|
|
3968
|
+
io2.out(`Repo: 30617:${ctx.owner}:${ctx.repoId}`);
|
|
3969
|
+
io2.out(`Owner: ${ctx.owner} (implicit maintainer)`);
|
|
3970
|
+
if (!remote.announced) {
|
|
3971
|
+
io2.out("No kind:30617 announcement found \u2014 owner-only authority.");
|
|
3972
|
+
return 0;
|
|
3973
|
+
}
|
|
3974
|
+
if (maintainers.length === 0) {
|
|
3975
|
+
io2.out("Maintainers: (none declared \u2014 owner-only authority)");
|
|
3976
|
+
} else {
|
|
3977
|
+
io2.out(`Maintainers (${maintainers.length}):`);
|
|
3978
|
+
for (const m of maintainers) io2.out(` ${m}`);
|
|
3979
|
+
}
|
|
3980
|
+
return 0;
|
|
3981
|
+
} catch (err) {
|
|
3982
|
+
return emitCliError(io2, flags.json, "maintainers list", err);
|
|
3983
|
+
}
|
|
3984
|
+
}
|
|
3985
|
+
async function runMutate(op, args, deps) {
|
|
3986
|
+
const { io: io2 } = deps;
|
|
3987
|
+
const command = `maintainers ${op}`;
|
|
3988
|
+
let flags;
|
|
3989
|
+
let pubkey;
|
|
3990
|
+
try {
|
|
3991
|
+
const { values, positionals } = parseArgs11({
|
|
3992
|
+
args,
|
|
3993
|
+
options: MAINT_OPTIONS,
|
|
3994
|
+
allowPositionals: true
|
|
3995
|
+
});
|
|
3996
|
+
flags = pickFlags(values);
|
|
3997
|
+
if (flags.help) {
|
|
3998
|
+
io2.out(MAINTAINERS_USAGE);
|
|
3999
|
+
return 0;
|
|
4000
|
+
}
|
|
4001
|
+
if (positionals.length !== 1) {
|
|
4002
|
+
throw new Error(`expected exactly one <pubkey> to ${op}`);
|
|
4003
|
+
}
|
|
4004
|
+
pubkey = ownerToHex(positionals[0]).toLowerCase();
|
|
4005
|
+
if (!HEX64_RE3.test(pubkey)) {
|
|
4006
|
+
throw new Error(`<pubkey> must resolve to 64-char hex (got ${JSON.stringify(positionals[0])})`);
|
|
4007
|
+
}
|
|
4008
|
+
} catch (err) {
|
|
4009
|
+
io2.err(err instanceof Error ? err.message : String(err));
|
|
4010
|
+
io2.err(MAINTAINERS_USAGE);
|
|
4011
|
+
return 2;
|
|
4012
|
+
}
|
|
4013
|
+
let standaloneCtx;
|
|
4014
|
+
try {
|
|
4015
|
+
const ctx = await resolveContext(flags, deps);
|
|
4016
|
+
if (ctx.relays.length > 1) {
|
|
4017
|
+
io2.err(singleRelayRefusal(ctx.resolved, "Nothing was published or paid."));
|
|
4018
|
+
return 1;
|
|
4019
|
+
}
|
|
4020
|
+
const relayUrl = ctx.relays[0];
|
|
4021
|
+
if (relayUrl === void 0 || !WS_URL_RE4.test(relayUrl)) {
|
|
4022
|
+
throw new InvalidRelayUrlError(
|
|
4023
|
+
relayUrl ?? "",
|
|
4024
|
+
"a paid publish needs a ws:// or wss:// relay"
|
|
4025
|
+
);
|
|
4026
|
+
}
|
|
4027
|
+
const load = deps.loadStandalone ?? defaultLoadStandalone;
|
|
4028
|
+
standaloneCtx = await load({
|
|
4029
|
+
env: deps.env,
|
|
4030
|
+
cwd: deps.cwd,
|
|
4031
|
+
warn: (line) => io2.err(line),
|
|
4032
|
+
relayUrl
|
|
4033
|
+
});
|
|
4034
|
+
const identity = identityReport(standaloneCtx);
|
|
4035
|
+
if (identity.pubkey.toLowerCase() !== ctx.owner.toLowerCase()) {
|
|
4036
|
+
io2.err(
|
|
4037
|
+
`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.`
|
|
4038
|
+
);
|
|
4039
|
+
return 1;
|
|
4040
|
+
}
|
|
4041
|
+
const remote = await fetchRemoteState({
|
|
4042
|
+
relayUrls: [relayUrl],
|
|
4043
|
+
ownerPubkey: ctx.owner,
|
|
4044
|
+
repoId: ctx.repoId,
|
|
4045
|
+
...deps.webSocketFactory ? { webSocketFactory: deps.webSocketFactory } : {}
|
|
4046
|
+
});
|
|
4047
|
+
if (!remote.announced) {
|
|
4048
|
+
io2.err(
|
|
4049
|
+
`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.`
|
|
4050
|
+
);
|
|
4051
|
+
return 1;
|
|
4052
|
+
}
|
|
4053
|
+
const current = remote.announceEvent ? parseMaintainers(remote.announceEvent.tags) : [];
|
|
4054
|
+
const currentSet = new Set(current);
|
|
4055
|
+
if (op === "add" && currentSet.has(pubkey)) {
|
|
4056
|
+
io2.err(
|
|
4057
|
+
`rig: ${pubkey.slice(0, 8)}\u2026 is already a maintainer \u2014 nothing to do (not published).`
|
|
4058
|
+
);
|
|
4059
|
+
return 0;
|
|
4060
|
+
}
|
|
4061
|
+
if (op === "remove" && !currentSet.has(pubkey)) {
|
|
4062
|
+
io2.err(
|
|
4063
|
+
`rig: ${pubkey.slice(0, 8)}\u2026 is not a declared maintainer \u2014 nothing to do (not published).`
|
|
4064
|
+
);
|
|
4065
|
+
return 0;
|
|
4066
|
+
}
|
|
4067
|
+
const next = op === "add" ? [...current, pubkey] : current.filter((m) => m !== pubkey);
|
|
4068
|
+
const name = remote.name ?? ctx.repoId;
|
|
4069
|
+
const description = remote.description ?? "";
|
|
4070
|
+
const event = buildRepoAnnouncement(ctx.repoId, name, description, next);
|
|
4071
|
+
const fee = (await standaloneCtx.publisher.getFeeRates()).eventFee.toString();
|
|
4072
|
+
const action = `kind:30617 maintainers ${op} ${pubkey.slice(0, 8)}\u2026`;
|
|
4073
|
+
if (!flags.json) {
|
|
4074
|
+
io2.out(`Republish ${action}`);
|
|
4075
|
+
io2.out(`Repo: 30617:${ctx.owner}:${ctx.repoId}`);
|
|
4076
|
+
io2.out(`Maintainers after: ${next.length === 0 ? "(none)" : next.join(", ")}`);
|
|
4077
|
+
io2.out(`Fee: ${feeLabel(fee)}. Writes are permanent and non-refundable.`);
|
|
4078
|
+
}
|
|
4079
|
+
if (!flags.yes) {
|
|
4080
|
+
if (flags.json) {
|
|
4081
|
+
io2.emitJson({
|
|
4082
|
+
command,
|
|
4083
|
+
repoAddr: { ownerPubkey: ctx.owner, repoId: ctx.repoId },
|
|
4084
|
+
identity,
|
|
4085
|
+
executed: false,
|
|
4086
|
+
feeEstimate: fee,
|
|
4087
|
+
maintainers: next,
|
|
4088
|
+
hint: "estimate only \u2014 re-run with --yes to publish (permanent, non-refundable)"
|
|
4089
|
+
});
|
|
4090
|
+
return 0;
|
|
4091
|
+
}
|
|
4092
|
+
if (!io2.isInteractive) {
|
|
4093
|
+
io2.err(
|
|
4094
|
+
"refusing to spend channel funds without confirmation in a non-interactive session \u2014 re-run with --yes (or use --json for an estimate)"
|
|
4095
|
+
);
|
|
4096
|
+
return 1;
|
|
4097
|
+
}
|
|
4098
|
+
const proceed = await io2.confirm(
|
|
4099
|
+
`Proceed with paid republish (${feeLabel(fee)})? [y/N] `
|
|
4100
|
+
);
|
|
4101
|
+
if (!proceed) {
|
|
4102
|
+
io2.err("aborted \u2014 nothing was published.");
|
|
4103
|
+
return 1;
|
|
4104
|
+
}
|
|
4105
|
+
}
|
|
4106
|
+
const receipt = await standaloneCtx.publisher.publishEvent(event, [
|
|
4107
|
+
relayUrl
|
|
4108
|
+
]);
|
|
4109
|
+
const result = serializeEventReceipt(event.kind, receipt);
|
|
4110
|
+
if (flags.json) {
|
|
4111
|
+
io2.emitJson({
|
|
4112
|
+
command,
|
|
4113
|
+
repoAddr: { ownerPubkey: ctx.owner, repoId: ctx.repoId },
|
|
4114
|
+
identity,
|
|
4115
|
+
executed: true,
|
|
4116
|
+
feeEstimate: fee,
|
|
4117
|
+
maintainers: next,
|
|
4118
|
+
result
|
|
4119
|
+
});
|
|
4120
|
+
} else {
|
|
4121
|
+
io2.out(
|
|
4122
|
+
`Published ${action}: ${result.eventId} paid ${result.feePaid} base units`
|
|
4123
|
+
);
|
|
4124
|
+
}
|
|
4125
|
+
return 0;
|
|
4126
|
+
} catch (err) {
|
|
4127
|
+
return emitCliError(io2, flags.json, command, err);
|
|
4128
|
+
} finally {
|
|
4129
|
+
if (standaloneCtx) {
|
|
4130
|
+
try {
|
|
4131
|
+
await standaloneCtx.stop();
|
|
4132
|
+
} catch {
|
|
4133
|
+
}
|
|
4134
|
+
}
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
4137
|
+
|
|
3720
4138
|
// src/cli/dispatch.ts
|
|
3721
4139
|
var USAGE = `rig \u2014 git with a TOON remote (pay-to-write Nostr + Arweave)
|
|
3722
4140
|
|
|
@@ -3749,6 +4167,10 @@ Commands rig owns:
|
|
|
3749
4167
|
\`git format-patch\` content
|
|
3750
4168
|
pr status <event-id> <state> set an issue/patch status (kind:1630-1633):
|
|
3751
4169
|
open | applied | closed | draft
|
|
4170
|
+
maintainers list show the repo's declared maintainers (free); add/
|
|
4171
|
+
maintainers add <pubkey> remove republish the kind:30617 to change who may
|
|
4172
|
+
maintainers remove <pubkey> author authoritative issue/PR status (owner is
|
|
4173
|
+
always an implicit maintainer)
|
|
3752
4174
|
fund drip devnet faucet funds to this identity's
|
|
3753
4175
|
wallet (free); on other networks prints the
|
|
3754
4176
|
address(es) to fund externally
|
|
@@ -3812,6 +4234,8 @@ async function dispatch(argv2, deps) {
|
|
|
3812
4234
|
return runComment(rest, deps);
|
|
3813
4235
|
case "pr":
|
|
3814
4236
|
return runPr(rest, deps);
|
|
4237
|
+
case "maintainers":
|
|
4238
|
+
return runMaintainers(rest, deps);
|
|
3815
4239
|
case "channel":
|
|
3816
4240
|
return runChannel(rest, deps);
|
|
3817
4241
|
case "fund":
|
|
@@ -3889,6 +4313,7 @@ var RIG_OWNED_VERBS = /* @__PURE__ */ new Set([
|
|
|
3889
4313
|
"issue",
|
|
3890
4314
|
"comment",
|
|
3891
4315
|
"pr",
|
|
4316
|
+
"maintainers",
|
|
3892
4317
|
"channel",
|
|
3893
4318
|
"fund",
|
|
3894
4319
|
"balance"
|