@toon-protocol/rig 5.0.2 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,11 @@
1
1
  import {
2
2
  CI_ADVERTISEMENT_KIND,
3
+ CI_JOB_RESULT_KIND,
4
+ CI_SERVICE_REQUEST_KIND,
5
+ CI_SERVICE_STOP_KIND,
3
6
  CI_STATUS_USAGE,
7
+ CI_WORKFLOW_PROGRESS_KIND,
8
+ CI_WORKFLOW_RESULT_KIND,
4
9
  GitRepoReader,
5
10
  InvalidRelayUrlError,
6
11
  MultiUrlRemoteError,
@@ -52,37 +57,42 @@ import {
52
57
  updateRef,
53
58
  writeGitObjects,
54
59
  writeToonConfig
55
- } from "./chunk-4WVH2ITA.js";
60
+ } from "./chunk-S5R44MMB.js";
56
61
  import {
57
62
  PREFERRED_GATEWAY,
58
63
  mirrorGatewaysFor,
59
64
  readGateways,
60
65
  shaResolverFor
61
- } from "./chunk-CHVOXCDV.js";
66
+ } from "./chunk-FX5GPZYU.js";
62
67
  import {
63
68
  COMMENT_KIND,
64
69
  DEFAULT_KEYSTORE_PASSWORD,
65
70
  LEGACY_COMMENT_KIND,
66
71
  MissingIdentityError,
67
- authorizedStatusAuthors,
72
+ authorizedAuthors,
68
73
  buildComment,
69
74
  buildIssue,
70
75
  buildPatch,
71
76
  buildStatus,
72
77
  clientConfigPath,
73
78
  clientKeystorePath,
79
+ clientStateDir,
74
80
  commentBelongsToThread,
81
+ decodeEventPayload,
75
82
  defaultWebSocketFactory,
76
83
  fetchRemoteState,
77
84
  firstTagValue,
85
+ isPatchRevision,
78
86
  isValidEvmPayoutAddress,
79
87
  linkKeystoreInClientConfig,
80
88
  parseMaintainers,
81
89
  queryRelay,
82
90
  readClientConfigFile,
83
91
  resolveAccountIndex,
84
- resolveIdentity
85
- } from "./chunk-QKL53YZS.js";
92
+ resolveIdentity,
93
+ revisionRootId,
94
+ selectRevision
95
+ } from "./chunk-JX5P2KAA.js";
86
96
  import {
87
97
  ChannelMapStore,
88
98
  channelStatus,
@@ -311,7 +321,7 @@ function loadPaidSession(deps, relayUrl) {
311
321
  });
312
322
  }
313
323
  var defaultLoadStandalone = async (options) => {
314
- const mod = await import("./standalone-mode-AT4S5EZG.js");
324
+ const mod = await import("./standalone-mode-NWCCRFCL.js");
315
325
  return mod.createStandaloneContext(options);
316
326
  };
317
327
  function identityReport(ctx) {
@@ -565,9 +575,23 @@ async function runPush(args, deps) {
565
575
  } else {
566
576
  identity = session.identity;
567
577
  }
568
- if (toonConfig.owner && toonConfig.owner !== identity.pubkey) {
578
+ const foreignOwner = toonConfig.owner && toonConfig.owner.toLowerCase() !== identity.pubkey.toLowerCase() ? toonConfig.owner : void 0;
579
+ let maintainerRemote;
580
+ if (session.path === "standalone" && foreignOwner) {
581
+ const ownerState = await session.ctx.fetchRemote({
582
+ ownerPubkey: foreignOwner,
583
+ repoId,
584
+ relayUrls: relaysUsed
585
+ });
586
+ if (ownerState.announced && authorizedAuthors(foreignOwner, ownerState.announceEvent?.tags).has(
587
+ identity.pubkey.toLowerCase()
588
+ )) {
589
+ maintainerRemote = ownerState;
590
+ }
591
+ }
592
+ if (!maintainerRemote && foreignOwner) {
569
593
  io.err(
570
- `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.`
594
+ `warning: git config toon.owner (${foreignOwner.slice(0, 8)}\u2026) differs from the active identity (${identity.pubkey.slice(0, 8)}\u2026) \u2014 this push publishes under the ACTIVE identity's repo namespace, not the configured owner's. Re-run \`rig init\` to adopt the active identity.`
571
595
  );
572
596
  }
573
597
  const announcement = relaysUsed[0] === void 0 ? void 0 : {
@@ -584,7 +608,7 @@ async function runPush(args, deps) {
584
608
  let rigPageSkipDetail;
585
609
  if (session.path === "standalone") {
586
610
  const ctx = session.ctx;
587
- const remoteState = await ctx.fetchRemote({
611
+ const remoteState = maintainerRemote ?? await ctx.fetchRemote({
588
612
  ownerPubkey: ctx.ownerPubkey,
589
613
  repoId,
590
614
  relayUrls: relaysUsed
@@ -614,6 +638,8 @@ async function runPush(args, deps) {
614
638
  rigPageSkipDetail = "skipped (--no-rig-page)";
615
639
  } else if (relaysUsed[0] === void 0) {
616
640
  rigPageSkipDetail = "skipped (no relay resolved for the pointer)";
641
+ } else if (maintainerRemote) {
642
+ rigPageSkipDetail = "skipped (the owner's rig page is the owner's to refresh)";
617
643
  } else if (!ctx.publisher.uploadBlob) {
618
644
  rigPageSkipDetail = "skipped (the active publisher cannot upload raw blobs)";
619
645
  } else {
@@ -2493,7 +2519,7 @@ async function runCi(args, deps) {
2493
2519
  case "status":
2494
2520
  return runCiStatus(rest, deps);
2495
2521
  case "serve":
2496
- return (await import("./ci-serve-BU7NDT7O.js")).runCiServe(rest, deps);
2522
+ return (await import("./ci-serve-CA37KQVC.js")).runCiServe(rest, deps);
2497
2523
  case "--help":
2498
2524
  case "-h":
2499
2525
  case "help":
@@ -2766,6 +2792,8 @@ async function runClone(args, deps) {
2766
2792
  import { readFile } from "fs/promises";
2767
2793
  import { parseArgs as parseArgs8 } from "util";
2768
2794
  import {
2795
+ PATCH_KIND as PATCH_KIND2,
2796
+ REPOSITORY_ANNOUNCEMENT_KIND as REPOSITORY_ANNOUNCEMENT_KIND2,
2769
2797
  STATUS_APPLIED_KIND as STATUS_APPLIED_KIND2,
2770
2798
  STATUS_CLOSED_KIND as STATUS_CLOSED_KIND2,
2771
2799
  STATUS_DRAFT_KIND as STATUS_DRAFT_KIND2,
@@ -2985,7 +3013,7 @@ async function fetchAuthorizedAuthors(ctx, ownerPubkey, repoId) {
2985
3013
  if (latest) announceTags = latest.tags;
2986
3014
  } catch {
2987
3015
  }
2988
- return authorizedStatusAuthors(ownerPubkey, announceTags);
3016
+ return authorizedAuthors(ownerPubkey, announceTags);
2989
3017
  }
2990
3018
  function parseTrackerItem(event, status) {
2991
3019
  const item = {
@@ -3031,7 +3059,7 @@ async function fetchItems(ctx, kind) {
3031
3059
  )
3032
3060
  ]);
3033
3061
  const items = [...itemEvents.values()].filter(
3034
- (e) => e.kind === kind && e.tags.some((t) => t[0] === "a" && t[1] === aTag)
3062
+ (e) => e.kind === kind && e.tags.some((t) => t[0] === "a" && t[1] === aTag) && !(kind === PATCH_KIND && isPatchRevision(e))
3035
3063
  );
3036
3064
  const statuses = new Map(aStatusEvents);
3037
3065
  if (items.length > 0) {
@@ -3065,6 +3093,12 @@ async function fetchItem(ctx, eventId, expectedKind) {
3065
3093
  if (!event) {
3066
3094
  throw new Error(`event ${eventId} not found on ${ctx.relays.join(", ")}`);
3067
3095
  }
3096
+ if (event.kind === PATCH_KIND && isPatchRevision(event)) {
3097
+ const rootId = revisionRootId(event);
3098
+ if (rootId !== void 0 && rootId !== eventId) {
3099
+ return fetchItem(ctx, rootId, expectedKind);
3100
+ }
3101
+ }
3068
3102
  if (event.kind !== expectedKind) {
3069
3103
  const hint = event.kind === PATCH_KIND ? " (it is a kind:1617 patch \u2014 use `rig pr show`)" : event.kind === ISSUE_KIND ? " (it is a kind:1621 issue \u2014 use `rig issue show`)" : "";
3070
3104
  throw new Error(
@@ -3100,16 +3134,53 @@ async function fetchItem(ctx, eventId, expectedKind) {
3100
3134
  }));
3101
3135
  const repoATag2 = tagValue(event.tags, "a") ?? null;
3102
3136
  const parsedAddr = parseRepoATag(repoATag2) ?? ctx.repoAddr;
3103
- const authorized = parsedAddr ? await fetchAuthorizedAuthors(ctx, parsedAddr.ownerPubkey, parsedAddr.repoId) : /* @__PURE__ */ new Set();
3104
- return {
3105
- item: parseTrackerItem(
3137
+ const authorized = parsedAddr ? await fetchAuthorizedAuthors(
3138
+ ctx,
3139
+ parsedAddr.ownerPubkey,
3140
+ parsedAddr.repoId
3141
+ ) : /* @__PURE__ */ new Set();
3142
+ const item = parseTrackerItem(
3143
+ event,
3144
+ deriveStatus(
3145
+ eventId,
3146
+ statusEvents.values(),
3147
+ withTargetAuthor(authorized, event.pubkey)
3148
+ )
3149
+ );
3150
+ let revisions;
3151
+ if (event.kind === PATCH_KIND) {
3152
+ const revisionEvents = await queryAll(
3153
+ ctx.relays,
3154
+ [{ kinds: [PATCH_KIND], "#e": [eventId] }],
3155
+ ctx.webSocketFactory
3156
+ );
3157
+ const { latest, earlier } = selectRevision(
3106
3158
  event,
3107
- deriveStatus(
3108
- eventId,
3109
- statusEvents.values(),
3110
- withTargetAuthor(authorized, event.pubkey)
3111
- )
3112
- ),
3159
+ [...revisionEvents.values()].filter((e) => e.kind === PATCH_KIND),
3160
+ authorized
3161
+ );
3162
+ const describe = (e) => ({
3163
+ eventId: e.id,
3164
+ authorPubkey: e.pubkey,
3165
+ createdAt: e.created_at
3166
+ });
3167
+ revisions = { latest: describe(latest), earlier: earlier.map(describe) };
3168
+ if (latest.id !== event.id) {
3169
+ const shown = parseTrackerItem(latest, item.status);
3170
+ item.content = shown.content;
3171
+ item.title = shown.title;
3172
+ item.commitShas = shown.commitShas ?? [];
3173
+ delete item.branch;
3174
+ delete item.description;
3175
+ if (shown.branch !== void 0) item.branch = shown.branch;
3176
+ if (shown.description !== void 0) {
3177
+ item.description = shown.description;
3178
+ }
3179
+ }
3180
+ }
3181
+ return {
3182
+ item,
3183
+ ...revisions ? { revisions } : {},
3113
3184
  comments,
3114
3185
  repoATag: repoATag2
3115
3186
  };
@@ -3225,6 +3296,10 @@ async function runShow(args, deps, spec) {
3225
3296
  relays: ctx.relays,
3226
3297
  repoATag: shown.repoATag,
3227
3298
  [spec.jsonKey]: shown.item,
3299
+ ...shown.revisions ? {
3300
+ latestRevision: shown.revisions.latest,
3301
+ earlierRevisions: shown.revisions.earlier
3302
+ } : {},
3228
3303
  comments: shown.comments
3229
3304
  });
3230
3305
  return 0;
@@ -3241,6 +3316,20 @@ async function runShow(args, deps, spec) {
3241
3316
  io.out(`Commits: ${item.commitShas.join(", ")}`);
3242
3317
  }
3243
3318
  if (shown.repoATag !== null) io.out(`Repo: ${shown.repoATag}`);
3319
+ if (shown.revisions) {
3320
+ const { latest, earlier } = shown.revisions;
3321
+ if (earlier.length > 0) {
3322
+ io.out(
3323
+ `Showing: ${latest.eventId} (latest revision, by ${latest.authorPubkey.slice(0, 8)}, ${isoDate(latest.createdAt)})`
3324
+ );
3325
+ io.out("Earlier revisions (oldest first):");
3326
+ for (const r of shown.revisions.earlier) {
3327
+ io.out(
3328
+ ` ${r.eventId} (${r.authorPubkey.slice(0, 8)}, ${isoDate(r.createdAt)})`
3329
+ );
3330
+ }
3331
+ }
3332
+ }
3244
3333
  if (item.description !== void 0) {
3245
3334
  io.out("");
3246
3335
  io.out("Body:");
@@ -3341,7 +3430,11 @@ ${COMMON_FLAGS_USAGE2}
3341
3430
 
3342
3431
  Related (FREE reads \u2014 no payment):
3343
3432
  rig issue list [--state open|closed|all] list the repo's issues
3344
- rig issue show <event-id> one issue + its comments`;
3433
+ rig issue show <event-id> one issue + its comments
3434
+
3435
+ Related (paid status \u2014 see \`rig issue status --help\`):
3436
+ rig issue status <event-id> <open|closed> set an issue's status
3437
+ rig issue close | reopen <event-id> shortcuts for closed / open`;
3345
3438
  var COMMENT_USAGE = `Usage: rig comment <target-event-id> --body <text> [options]
3346
3439
 
3347
3440
  Comment (NIP-22 kind:1111) on an issue or patch \u2014 a paid publish; writes are
@@ -3364,7 +3457,7 @@ comments published by this version.
3364
3457
  Options:
3365
3458
  --body <text> comment body (Markdown) [required]
3366
3459
  ${COMMON_FLAGS_USAGE2}`;
3367
- var PR_CREATE_USAGE = `Usage: rig pr create --title <title> (--range <range> | --patch-file <path>) [options]
3460
+ var PR_CREATE_USAGE = `Usage: rig pr create --title <title> (--range <range> | --patch-file <path>) [--revises <patch-id>] [options]
3368
3461
 
3369
3462
  Publish a patch (kind:1617) whose content is REAL \`git format-patch\` output \u2014
3370
3463
  a paid publish; writes are permanent and non-refundable. --range runs
@@ -3378,6 +3471,12 @@ The PR body (--body/--body-file) is carried in a dedicated \`description\`
3378
3471
  tag, not in the event content \u2014 the content stays pure format-patch output so
3379
3472
  \`rig pr show \u2026 | git am\` keeps working.
3380
3473
 
3474
+ --revises publishes the patch as a NIP-34 revision of an existing root patch
3475
+ (\`t root-revision\` plus a NIP-10 reply to it), keeping the original thread,
3476
+ comments and status. Readers honor a revision only from the root's author or a
3477
+ declared maintainer; otherwise rig warns and still publishes. A revision is
3478
+ always published through the embedded (standalone) path.
3479
+
3381
3480
  Options:
3382
3481
  --title <title> patch/PR title (subject tag) [required]
3383
3482
  --range <range> revision range for format-patch: <rev>, <rev>..<rev>,
@@ -3386,6 +3485,7 @@ Options:
3386
3485
  --body <text> PR description (Markdown; description tag)
3387
3486
  --body-file <path> read the PR description from a file
3388
3487
  --branch <name> branch name (branch-name tag)
3488
+ --revises <patch-id> publish as a revision of this root patch (64-hex id)
3389
3489
  ${COMMON_FLAGS_USAGE2}`;
3390
3490
  var PR_STATUS_USAGE = `Usage: rig pr status <target-event-id> <open|applied|closed|draft> [options]
3391
3491
 
@@ -3396,6 +3496,18 @@ with the repo a-tag attached so readers can scope a status stream to the
3396
3496
  repository. (This command was \`rig status\` before v2 \u2014 bare \`rig status\`
3397
3497
  now passes through to \`git status\`.)
3398
3498
 
3499
+ Options:
3500
+ ${COMMON_FLAGS_USAGE2}`;
3501
+ var ISSUE_STATUS_USAGE = `Usage: rig issue status <issue-event-id> <open|closed> [options]
3502
+ rig issue close <issue-event-id> [options]
3503
+ rig issue reopen <issue-event-id> [options]
3504
+
3505
+ Set the status of an issue \u2014 a paid publish; writes are permanent and
3506
+ non-refundable. Aliases over \`rig pr status\`: \`closed\` / \`close\` publish
3507
+ kind:1632, \`open\` / \`reopen\` publish kind:1630, against the 64-char hex id
3508
+ of the issue, with the repo a-tag attached. \`applied\` and \`draft\` apply to
3509
+ patches only and are rejected for issues (use \`rig pr status\`).
3510
+
3399
3511
  Options:
3400
3512
  ${COMMON_FLAGS_USAGE2}`;
3401
3513
  var PR_USAGE = `${PR_CREATE_USAGE}
@@ -3578,13 +3690,22 @@ async function runIssue(args, deps) {
3578
3690
  io.out(ISSUE_LIST_USAGE);
3579
3691
  io.out("");
3580
3692
  io.out(ISSUE_SHOW_USAGE);
3693
+ io.out("");
3694
+ io.out(ISSUE_STATUS_USAGE);
3581
3695
  return 0;
3582
3696
  }
3697
+ if (sub === "status") return runStatusCommand(rest, deps, ISSUE_STATUS_SPEC);
3698
+ if (sub === "close") {
3699
+ return runStatusCommand(rest, deps, ISSUE_STATUS_SPEC, "closed");
3700
+ }
3701
+ if (sub === "reopen") {
3702
+ return runStatusCommand(rest, deps, ISSUE_STATUS_SPEC, "open");
3703
+ }
3583
3704
  if (sub === "list") return runIssueList(rest, deps);
3584
3705
  if (sub === "show") return runIssueShow(rest, deps);
3585
3706
  if (sub !== "create") {
3586
3707
  io.err(
3587
- sub === void 0 ? "missing subcommand: rig issue <create|list|show>" : `unknown rig issue subcommand: ${sub}`
3708
+ sub === void 0 ? "missing subcommand: rig issue <create|list|show|status|close|reopen>" : `unknown rig issue subcommand: ${sub}`
3588
3709
  );
3589
3710
  io.err(ISSUE_USAGE);
3590
3711
  return 2;
@@ -3797,7 +3918,7 @@ async function runPr(args, deps) {
3797
3918
  case "create":
3798
3919
  return runPrCreate(rest, deps);
3799
3920
  case "status":
3800
- return runPrStatus(rest, deps);
3921
+ return runStatusCommand(rest, deps, PR_STATUS_SPEC);
3801
3922
  // FREE read subcommands (#278): relay queries only, no payment path.
3802
3923
  case "list":
3803
3924
  return runPrList(rest, deps);
@@ -3816,6 +3937,69 @@ async function runPr(args, deps) {
3816
3937
  return 2;
3817
3938
  }
3818
3939
  }
3940
+ async function resolveRevisedRoot(deps, rootId, relayUrl, addr) {
3941
+ if (relayUrl === void 0) {
3942
+ throw new Error(
3943
+ "--revises needs the root patch from a relay; pass --relay <url> or configure a remote"
3944
+ );
3945
+ }
3946
+ const found = await queryRelay(
3947
+ relayUrl,
3948
+ { ids: [rootId] },
3949
+ TARGET_LOOKUP_TIMEOUT_MS,
3950
+ deps.webSocketFactory ?? defaultWebSocketFactory
3951
+ );
3952
+ const root = found.find((e) => e.id === rootId);
3953
+ if (!root) {
3954
+ throw new Error(
3955
+ `patch ${rootId} not found on ${relayUrl} \u2014 nothing was published or paid`
3956
+ );
3957
+ }
3958
+ if (root.kind !== PATCH_KIND2) {
3959
+ throw new Error(
3960
+ `event ${rootId} has kind ${root.kind}, not a kind:1617 patch \u2014 nothing was published or paid`
3961
+ );
3962
+ }
3963
+ const aTag = `${REPOSITORY_ANNOUNCEMENT_KIND2}:${addr.ownerPubkey}:${addr.repoId}`;
3964
+ if (!root.tags.some((t) => t[0] === "a" && t[1] === aTag)) {
3965
+ throw new Error(
3966
+ `patch ${rootId} belongs to a different repository than ${aTag} \u2014 nothing was published or paid`
3967
+ );
3968
+ }
3969
+ if (isPatchRevision(root)) {
3970
+ throw new Error(
3971
+ `patch ${rootId} is itself a revision \u2014 pass the ROOT patch id to --revises; nothing was published or paid`
3972
+ );
3973
+ }
3974
+ return { pubkey: root.pubkey };
3975
+ }
3976
+ async function warnIfRevisionIgnored(deps, args, rootAuthor) {
3977
+ const { io } = deps;
3978
+ const { identity, addr, relayUrl } = args;
3979
+ const me = identity.pubkey.toLowerCase();
3980
+ if (me === rootAuthor?.toLowerCase()) return;
3981
+ if (me === addr.ownerPubkey.toLowerCase()) return;
3982
+ let authorized;
3983
+ if (relayUrl !== void 0) {
3984
+ try {
3985
+ const remote = await fetchRemoteState({
3986
+ relayUrls: [relayUrl],
3987
+ ownerPubkey: addr.ownerPubkey,
3988
+ repoId: addr.repoId,
3989
+ ...deps.webSocketFactory ? { webSocketFactory: deps.webSocketFactory } : {}
3990
+ });
3991
+ authorized = authorizedAuthors(
3992
+ addr.ownerPubkey,
3993
+ remote.announceEvent?.tags ?? []
3994
+ );
3995
+ } catch {
3996
+ }
3997
+ }
3998
+ if (authorized?.has(me)) return;
3999
+ io.err(
4000
+ authorized === void 0 ? "warning: could not read the repo announcement to verify your maintainer status \u2014 unless you are the root patch's author or a declared maintainer, readers will IGNORE this revision." : `warning: you (${identity.pubkey.slice(0, 8)}\u2026) are neither the root patch's author nor a maintainer of 30617:${addr.ownerPubkey.slice(0, 8)}\u2026:${addr.repoId} \u2014 readers will IGNORE this revision. Publishing anyway (the relay is permissionless).`
4001
+ );
4002
+ }
3819
4003
  async function runPrCreate(rest, deps) {
3820
4004
  const { io } = deps;
3821
4005
  let flags;
@@ -3825,6 +4009,7 @@ async function runPrCreate(rest, deps) {
3825
4009
  let branch;
3826
4010
  let bodyFlag;
3827
4011
  let bodyFile;
4012
+ let revises;
3828
4013
  try {
3829
4014
  const { values } = parseArgs8({
3830
4015
  args: rest,
@@ -3835,7 +4020,8 @@ async function runPrCreate(rest, deps) {
3835
4020
  "patch-file": { type: "string" },
3836
4021
  body: { type: "string" },
3837
4022
  "body-file": { type: "string" },
3838
- branch: { type: "string" }
4023
+ branch: { type: "string" },
4024
+ revises: { type: "string" }
3839
4025
  },
3840
4026
  allowPositionals: false
3841
4027
  });
@@ -3859,6 +4045,11 @@ async function runPrCreate(rest, deps) {
3859
4045
  branch = values.branch;
3860
4046
  bodyFlag = values.body;
3861
4047
  bodyFile = values["body-file"];
4048
+ if (values.revises !== void 0) {
4049
+ assertHex64(values.revises, "--revises");
4050
+ revises = values.revises;
4051
+ flags = { ...flags, standalone: true };
4052
+ }
3862
4053
  } catch (err) {
3863
4054
  io.err(err instanceof Error ? err.message : String(err));
3864
4055
  io.err(PR_CREATE_USAGE);
@@ -3883,12 +4074,16 @@ async function runPrCreate(rest, deps) {
3883
4074
  }
3884
4075
  let builtPatchText;
3885
4076
  let builtCommits = [];
4077
+ let rootAuthor;
3886
4078
  return runEvent({
3887
4079
  command: "pr",
3888
4080
  flags,
3889
4081
  deps,
3890
4082
  actionLabel: `patch ${JSON.stringify(title)}`,
3891
- buildEvent: async (addr) => {
4083
+ buildEvent: async (addr, relayUrl) => {
4084
+ if (revises !== void 0) {
4085
+ rootAuthor = (await resolveRevisedRoot(deps, revises, relayUrl, addr)).pubkey;
4086
+ }
3892
4087
  let patchText;
3893
4088
  let commits;
3894
4089
  if (range !== void 0) {
@@ -3908,7 +4103,9 @@ async function runPrCreate(rest, deps) {
3908
4103
  } else {
3909
4104
  patchText = await readFile(patchFile, "utf-8");
3910
4105
  if (patchText.trim() === "") {
3911
- throw new Error(`--patch-file ${patchFile} is empty \u2014 nothing to publish`);
4106
+ throw new Error(
4107
+ `--patch-file ${patchFile} is empty \u2014 nothing to publish`
4108
+ );
3912
4109
  }
3913
4110
  commits = [];
3914
4111
  }
@@ -3921,9 +4118,16 @@ async function runPrCreate(rest, deps) {
3921
4118
  commits,
3922
4119
  branch,
3923
4120
  patchText,
3924
- body
4121
+ body,
4122
+ revises !== void 0 ? {
4123
+ rootEventId: revises,
4124
+ ...rootAuthor !== void 0 ? { rootAuthorPubkey: rootAuthor } : {}
4125
+ } : void 0
3925
4126
  );
3926
4127
  },
4128
+ // A revision from anyone but the root's author or a maintainer is ignored
4129
+ // by readers: warn, but publish (the relay is permissionless).
4130
+ preConfirm: revises === void 0 ? void 0 : (args) => warnIfRevisionIgnored(deps, args, rootAuthor),
3927
4131
  sendDaemon: (client, addr) => {
3928
4132
  if (builtPatchText === void 0) {
3929
4133
  throw new Error("internal: patch text not built before delegation");
@@ -3948,7 +4152,21 @@ var STATUS_KIND_BY_VALUE = {
3948
4152
  function isStatusValue(value) {
3949
4153
  return Object.hasOwn(STATUS_KIND_BY_VALUE, value);
3950
4154
  }
3951
- async function runPrStatus(args, deps) {
4155
+ var PR_STATUS_SPEC = {
4156
+ command: "pr status",
4157
+ usage: PR_STATUS_USAGE,
4158
+ allowed: ["open", "applied", "closed", "draft"],
4159
+ shape: "<target-event-id> <open|applied|closed|draft>",
4160
+ targetLabel: "<target-event-id>"
4161
+ };
4162
+ var ISSUE_STATUS_SPEC = {
4163
+ command: "issue status",
4164
+ usage: ISSUE_STATUS_USAGE,
4165
+ allowed: ["open", "closed"],
4166
+ shape: "<issue-event-id> <open|closed>",
4167
+ targetLabel: "<issue-event-id>"
4168
+ };
4169
+ async function runStatusCommand(args, deps, spec, fixedStatus) {
3952
4170
  const { io } = deps;
3953
4171
  let flags;
3954
4172
  let targetEventId;
@@ -3961,30 +4179,31 @@ async function runPrStatus(args, deps) {
3961
4179
  });
3962
4180
  flags = pickCommon2(values);
3963
4181
  if (flags.help) {
3964
- io.out(PR_STATUS_USAGE);
4182
+ io.out(spec.usage);
3965
4183
  return 0;
3966
4184
  }
3967
- if (positionals.length !== 2) {
4185
+ const want = fixedStatus === void 0 ? 2 : 1;
4186
+ if (positionals.length !== want) {
3968
4187
  throw new Error(
3969
- "expected exactly two arguments: <target-event-id> <open|applied|closed|draft>"
4188
+ fixedStatus === void 0 ? `expected exactly two arguments: ${spec.shape}` : `expected exactly one argument: ${spec.targetLabel}`
3970
4189
  );
3971
4190
  }
3972
4191
  targetEventId = positionals[0];
3973
- assertHex64(targetEventId, "<target-event-id>");
3974
- const rawStatus = positionals[1];
3975
- if (!isStatusValue(rawStatus)) {
4192
+ assertHex64(targetEventId, spec.targetLabel);
4193
+ const rawStatus = fixedStatus ?? positionals[1];
4194
+ if (!isStatusValue(rawStatus) || !spec.allowed.includes(rawStatus)) {
3976
4195
  throw new Error(
3977
- `status must be one of open | applied | closed | draft (got ${JSON.stringify(rawStatus)})`
4196
+ `status must be one of ${spec.allowed.join(" | ")} (got ${JSON.stringify(rawStatus)})`
3978
4197
  );
3979
4198
  }
3980
4199
  status = rawStatus;
3981
4200
  } catch (err) {
3982
4201
  io.err(err instanceof Error ? err.message : String(err));
3983
- io.err(PR_STATUS_USAGE);
4202
+ io.err(spec.usage);
3984
4203
  return 2;
3985
4204
  }
3986
4205
  return runEvent({
3987
- command: "pr status",
4206
+ command: spec.command,
3988
4207
  flags,
3989
4208
  deps,
3990
4209
  actionLabel: `status ${status} on ${targetEventId.slice(0, 8)}\u2026`,
@@ -4021,7 +4240,7 @@ async function warnIfNotMaintainer(deps, args) {
4021
4240
  repoId: addr.repoId,
4022
4241
  ...deps.webSocketFactory ? { webSocketFactory: deps.webSocketFactory } : {}
4023
4242
  });
4024
- const authorized = authorizedStatusAuthors(
4243
+ const authorized = authorizedAuthors(
4025
4244
  addr.ownerPubkey,
4026
4245
  remote.announceEvent?.tags ?? []
4027
4246
  );
@@ -5254,9 +5473,569 @@ async function defaultReadSecretLine(prompt, isInteractive) {
5254
5473
  }
5255
5474
  }
5256
5475
 
5476
+ // src/cli/inbox.ts
5477
+ import { parseArgs as parseArgs13 } from "util";
5478
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
5479
+ import { join as join6 } from "path";
5480
+ import {
5481
+ ISSUE_KIND as ISSUE_KIND2,
5482
+ PATCH_KIND as PATCH_KIND3,
5483
+ REPOSITORY_ANNOUNCEMENT_KIND as REPOSITORY_ANNOUNCEMENT_KIND3,
5484
+ STATUS_APPLIED_KIND as STATUS_APPLIED_KIND3,
5485
+ STATUS_CLOSED_KIND as STATUS_CLOSED_KIND3,
5486
+ STATUS_DRAFT_KIND as STATUS_DRAFT_KIND3,
5487
+ STATUS_OPEN_KIND as STATUS_OPEN_KIND3
5488
+ } from "@toon-protocol/core/nip34";
5489
+
5490
+ // src/cli/relay-follow.ts
5491
+ var WS_OPEN = 1;
5492
+ var CONNECT_TIMEOUT_MS = 1e4;
5493
+ function defaultBackoffMs(attempt) {
5494
+ return Math.min(3e4, 1e3 * 2 ** Math.max(0, attempt - 1));
5495
+ }
5496
+ function connectOnce(opts, onSubscribed) {
5497
+ return new Promise((resolve2) => {
5498
+ let ws;
5499
+ let done = false;
5500
+ let subscribed = false;
5501
+ const subIds = [];
5502
+ let connectTimer;
5503
+ const finish = (reason) => {
5504
+ if (done) return;
5505
+ done = true;
5506
+ clearTimeout(connectTimer);
5507
+ opts.signal.removeEventListener("abort", onAbort);
5508
+ try {
5509
+ if (ws.readyState === WS_OPEN) {
5510
+ for (const id of subIds) ws.send(JSON.stringify(["CLOSE", id]));
5511
+ }
5512
+ ws.close();
5513
+ } catch {
5514
+ }
5515
+ resolve2(reason);
5516
+ };
5517
+ const onAbort = () => finish(null);
5518
+ try {
5519
+ ws = opts.webSocketFactory(opts.url);
5520
+ } catch (err) {
5521
+ resolve2(`failed to connect to ${opts.url}: ${String(err)}`);
5522
+ return;
5523
+ }
5524
+ opts.signal.addEventListener("abort", onAbort);
5525
+ connectTimer = setTimeout(
5526
+ () => finish(`timed out connecting to ${opts.url}`),
5527
+ CONNECT_TIMEOUT_MS
5528
+ );
5529
+ ws.addEventListener("open", () => {
5530
+ clearTimeout(connectTimer);
5531
+ opts.filters().forEach((filter, i) => {
5532
+ const subId = `follow-${Date.now()}-${i}-${Math.random().toString(36).slice(2, 8)}`;
5533
+ subIds.push(subId);
5534
+ ws.send(JSON.stringify(["REQ", subId, filter]));
5535
+ });
5536
+ });
5537
+ ws.addEventListener("message", (msgEvent) => {
5538
+ try {
5539
+ const msg = JSON.parse(String(msgEvent.data));
5540
+ if (msg[0] === "EVENT" && msg[2] !== void 0) {
5541
+ const event = decodeEventPayload(msg[2]);
5542
+ if (event) opts.onEvent(event, opts.url);
5543
+ } else if (msg[0] === "EOSE" && !subscribed && subIds.includes(msg[1])) {
5544
+ subscribed = true;
5545
+ onSubscribed();
5546
+ }
5547
+ } catch {
5548
+ }
5549
+ });
5550
+ ws.addEventListener("error", (event) => {
5551
+ const detail = typeof event === "object" && event !== null && "message" in event ? String(event.message) : "unknown";
5552
+ finish(`websocket error on ${opts.url}: ${detail}`);
5553
+ });
5554
+ ws.addEventListener("close", () => {
5555
+ finish(`connection to ${opts.url} closed`);
5556
+ });
5557
+ });
5558
+ }
5559
+ async function followRelay(opts) {
5560
+ let attempt = 0;
5561
+ while (!opts.signal.aborted) {
5562
+ const reason = await connectOnce(opts, () => {
5563
+ attempt = 0;
5564
+ });
5565
+ if (opts.signal.aborted) return;
5566
+ opts.onError(reason ?? `connection to ${opts.url} ended`);
5567
+ attempt += 1;
5568
+ await new Promise((resolve2) => {
5569
+ const done = () => {
5570
+ clearTimeout(timer);
5571
+ opts.signal.removeEventListener("abort", done);
5572
+ resolve2();
5573
+ };
5574
+ const timer = setTimeout(done, opts.backoffMs(attempt));
5575
+ opts.signal.addEventListener("abort", done);
5576
+ });
5577
+ }
5578
+ }
5579
+
5580
+ // src/cli/inbox.ts
5581
+ var INBOX_USAGE = `Usage: rig inbox [options]
5582
+
5583
+ Print everything new since you last checked (free \u2014 relay reads only):
5584
+ patches and revisions, issues, comments, status changes and CI results for
5585
+ repos you own or maintain, plus any event that tags your pubkey.
5586
+
5587
+ Options:
5588
+ --since <time> read from a unix time, an ISO date, or a relative
5589
+ age (30m, 2h, 7d); does not move the cursor
5590
+ --all read everything, ignoring the cursor; does not move it
5591
+ --pubkey <pubkey> act as this pubkey (npub or hex) instead of the
5592
+ resolved identity
5593
+ --remote <name> read via this configured git remote (default: origin)
5594
+ --relay <url> ad-hoc relay override; repeatable \u2014 reads are merged
5595
+ --json machine-readable array, one object per event
5596
+ --follow emit the backlog, then stay connected and stream new
5597
+ events as JSON lines (one object per line) until
5598
+ interrupted; reconnects when a relay drops
5599
+ -h, --help show this help`;
5600
+ var STATUS_KINDS2 = [
5601
+ STATUS_OPEN_KIND3,
5602
+ STATUS_APPLIED_KIND3,
5603
+ STATUS_CLOSED_KIND3,
5604
+ STATUS_DRAFT_KIND3
5605
+ ];
5606
+ var CI_KINDS = [
5607
+ CI_SERVICE_REQUEST_KIND,
5608
+ CI_SERVICE_STOP_KIND,
5609
+ CI_WORKFLOW_RESULT_KIND,
5610
+ CI_WORKFLOW_PROGRESS_KIND,
5611
+ CI_JOB_RESULT_KIND
5612
+ ];
5613
+ var INBOX_KINDS = [
5614
+ PATCH_KIND3,
5615
+ ISSUE_KIND2,
5616
+ COMMENT_KIND,
5617
+ LEGACY_COMMENT_KIND,
5618
+ ...STATUS_KINDS2,
5619
+ ...CI_KINDS
5620
+ ];
5621
+ var HEX64_RE4 = /^[0-9a-f]{64}$/;
5622
+ var INBOX_CURSOR_FILENAME = "inbox-cursors.json";
5623
+ function cursorPath(env) {
5624
+ return join6(clientStateDir(env), INBOX_CURSOR_FILENAME);
5625
+ }
5626
+ function readCursors(env) {
5627
+ try {
5628
+ const parsed = JSON.parse(readFileSync5(cursorPath(env), "utf8"));
5629
+ if (parsed && typeof parsed === "object") {
5630
+ return parsed;
5631
+ }
5632
+ } catch {
5633
+ }
5634
+ return {};
5635
+ }
5636
+ function writeCursor(env, pubkey, cursor) {
5637
+ const cursors = readCursors(env);
5638
+ cursors[pubkey] = cursor;
5639
+ mkdirSync5(clientStateDir(env), { recursive: true });
5640
+ writeFileSync4(cursorPath(env), JSON.stringify(cursors, null, 2) + "\n");
5641
+ }
5642
+ function parseSince(value, nowSec) {
5643
+ const trimmed = value.trim();
5644
+ if (/^\d+$/.test(trimmed)) return Number(trimmed);
5645
+ const rel = /^(\d+)([smhdw])$/.exec(trimmed);
5646
+ if (rel) {
5647
+ const unit = { s: 1, m: 60, h: 3600, d: 86400, w: 604800 }[rel[2]];
5648
+ return nowSec - Number(rel[1]) * unit;
5649
+ }
5650
+ const ms = Date.parse(trimmed);
5651
+ if (Number.isNaN(ms)) {
5652
+ throw new Error(
5653
+ `--since must be a unix time, an ISO date, or an age like 2h (got ${JSON.stringify(value)})`
5654
+ );
5655
+ }
5656
+ return Math.floor(ms / 1e3);
5657
+ }
5658
+ var tagValue2 = firstTagValue;
5659
+ function hasTopic(event, topic) {
5660
+ return event.tags.some((t) => t[0] === "t" && t[1] === topic);
5661
+ }
5662
+ function typeOf(event) {
5663
+ if (event.kind === PATCH_KIND3) {
5664
+ if (hasTopic(event, "root-revision")) return "revision";
5665
+ if (hasTopic(event, "root") || tagValue2(event.tags, "e") === void 0) {
5666
+ return "patch";
5667
+ }
5668
+ return null;
5669
+ }
5670
+ if (event.kind === ISSUE_KIND2) return "issue";
5671
+ if (event.kind === COMMENT_KIND || event.kind === LEGACY_COMMENT_KIND) {
5672
+ return "comment";
5673
+ }
5674
+ if (STATUS_KINDS2.includes(event.kind)) return "status";
5675
+ if (CI_KINDS.includes(event.kind)) return "ci";
5676
+ return null;
5677
+ }
5678
+ function targetOf(event, type) {
5679
+ const root = tagValue2(event.tags, "E");
5680
+ const parent = tagValue2(event.tags, "e");
5681
+ switch (type) {
5682
+ case "patch":
5683
+ case "issue":
5684
+ return event.id;
5685
+ case "revision":
5686
+ return parent ?? event.id;
5687
+ case "comment":
5688
+ return root ?? parent ?? event.id;
5689
+ case "status":
5690
+ return parent ?? event.id;
5691
+ case "ci":
5692
+ return root ?? tagValue2(event.tags, "c") ?? event.id;
5693
+ }
5694
+ }
5695
+ function toInboxEntry(event) {
5696
+ const type = typeOf(event);
5697
+ if (type === null) return null;
5698
+ const repo = event.tags.find(
5699
+ (t) => t[0] === "a" && t[1]?.startsWith(`${REPOSITORY_ANNOUNCEMENT_KIND3}:`)
5700
+ )?.[1];
5701
+ return {
5702
+ id: event.id,
5703
+ kind: event.kind,
5704
+ type,
5705
+ repo: repo ?? null,
5706
+ target: targetOf(event, type),
5707
+ author: event.pubkey,
5708
+ created_at: event.created_at
5709
+ };
5710
+ }
5711
+ function newest(events) {
5712
+ let winner = null;
5713
+ for (const e of events) {
5714
+ if (winner === null || e.created_at > winner.created_at || e.created_at === winner.created_at && e.id < winner.id) {
5715
+ winner = e;
5716
+ }
5717
+ }
5718
+ return winner;
5719
+ }
5720
+ async function ownerAnnouncement(relays, owner, repoId, ws) {
5721
+ const found = await queryAll(
5722
+ relays,
5723
+ [
5724
+ {
5725
+ kinds: [REPOSITORY_ANNOUNCEMENT_KIND3],
5726
+ authors: [owner],
5727
+ "#d": [repoId]
5728
+ }
5729
+ ],
5730
+ ws
5731
+ ).catch(() => /* @__PURE__ */ new Map());
5732
+ return newest(
5733
+ [...found.values()].filter(
5734
+ (e) => e.kind === REPOSITORY_ANNOUNCEMENT_KIND3 && e.pubkey.toLowerCase() === owner && tagValue2(e.tags, "d") === repoId
5735
+ )
5736
+ );
5737
+ }
5738
+ function relaysTag(tags) {
5739
+ return tags.filter((t) => t[0] === "relays").flatMap((t) => t.slice(1)).filter((url) => WS_URL_RE.test(url));
5740
+ }
5741
+ var INBOX_OPTIONS = {
5742
+ json: { type: "boolean", default: false },
5743
+ follow: { type: "boolean", default: false },
5744
+ all: { type: "boolean", default: false },
5745
+ since: { type: "string" },
5746
+ pubkey: { type: "string" },
5747
+ remote: { type: "string" },
5748
+ relay: { type: "string", multiple: true },
5749
+ help: { type: "boolean", short: "h", default: false }
5750
+ };
5751
+ function renderLine(entry) {
5752
+ const when = new Date(entry.created_at * 1e3).toISOString().slice(0, 16).replace("T", " ");
5753
+ const repo = entry.repo ?? "(no repo)";
5754
+ return `${when} ${entry.type.padEnd(8)} ${entry.id.slice(0, 8)} \u2192 ${entry.target.slice(0, 8)} ${repo} (${entry.author.slice(0, 8)})`;
5755
+ }
5756
+ async function runInbox(args, deps) {
5757
+ const { io } = deps;
5758
+ let json = false;
5759
+ let all = false;
5760
+ let follow = false;
5761
+ let sinceArg;
5762
+ let pubkeyArg;
5763
+ let remote;
5764
+ let relayFlags = [];
5765
+ try {
5766
+ const { values, positionals } = parseArgs13({
5767
+ args,
5768
+ options: INBOX_OPTIONS,
5769
+ allowPositionals: true
5770
+ });
5771
+ if (values.help) {
5772
+ io.out(INBOX_USAGE);
5773
+ return 0;
5774
+ }
5775
+ if (positionals.length > 0) {
5776
+ throw new Error("rig inbox takes no positional arguments");
5777
+ }
5778
+ json = values.json === true;
5779
+ all = values.all === true;
5780
+ follow = values.follow === true;
5781
+ sinceArg = values.since;
5782
+ if (values.pubkey !== void 0) pubkeyArg = ownerToHex(values.pubkey);
5783
+ remote = values.remote;
5784
+ relayFlags = values.relay ?? [];
5785
+ if (all && sinceArg !== void 0) {
5786
+ throw new Error("--all and --since are mutually exclusive");
5787
+ }
5788
+ } catch (err) {
5789
+ io.err(err instanceof Error ? err.message : String(err));
5790
+ io.err(INBOX_USAGE);
5791
+ return 2;
5792
+ }
5793
+ try {
5794
+ const nowSec = Math.floor((deps.now ?? Date.now)() / 1e3);
5795
+ const override = sinceArg !== void 0 ? parseSince(sinceArg, nowSec) : null;
5796
+ const me = (pubkeyArg ?? (await (deps.resolveIdentityImpl ?? resolveIdentity)({
5797
+ env: deps.env,
5798
+ cwd: deps.cwd,
5799
+ warn: (line) => io.err(line)
5800
+ })).pubkey).toLowerCase();
5801
+ let repoRoot;
5802
+ let toonRelays = [];
5803
+ try {
5804
+ repoRoot = await resolveRepoRoot(deps.cwd);
5805
+ toonRelays = (await readToonConfig(repoRoot)).relays;
5806
+ } catch {
5807
+ }
5808
+ const resolved = await resolveRelays({
5809
+ relayFlags,
5810
+ remoteName: remote,
5811
+ repoRoot,
5812
+ toonRelays
5813
+ });
5814
+ const configured = resolved.relays.filter((url) => WS_URL_RE.test(url));
5815
+ if (configured.length === 0) {
5816
+ throw new InvalidRelayUrlError(
5817
+ resolved.relays[0] ?? "",
5818
+ "reads need a ws:// or wss:// relay"
5819
+ );
5820
+ }
5821
+ const ws = deps.webSocketFactory ?? defaultWebSocketFactory;
5822
+ const announcements = await queryAll(
5823
+ configured,
5824
+ [
5825
+ { kinds: [REPOSITORY_ANNOUNCEMENT_KIND3], authors: [me] },
5826
+ { kinds: [REPOSITORY_ANNOUNCEMENT_KIND3], "#maintainers": [me] }
5827
+ ],
5828
+ ws
5829
+ );
5830
+ const byRepo = /* @__PURE__ */ new Map();
5831
+ for (const e of announcements.values()) {
5832
+ if (e.kind !== REPOSITORY_ANNOUNCEMENT_KIND3) continue;
5833
+ const d = tagValue2(e.tags, "d");
5834
+ if (!d) continue;
5835
+ const key = `${e.pubkey.toLowerCase()}:${d}`;
5836
+ byRepo.set(key, [...byRepo.get(key) ?? [], e]);
5837
+ }
5838
+ const watched = [];
5839
+ for (const [key, events2] of byRepo) {
5840
+ const owner = events2[0].pubkey.toLowerCase();
5841
+ const repoId = key.slice(owner.length + 1);
5842
+ const latest = owner === me ? newest(events2) : await ownerAnnouncement(configured, owner, repoId, ws);
5843
+ if (latest === null) continue;
5844
+ if (owner !== me && !parseMaintainers(latest.tags).includes(me)) continue;
5845
+ watched.push({
5846
+ address: `${REPOSITORY_ANNOUNCEMENT_KIND3}:${owner}:${repoId}`,
5847
+ ownerPubkey: owner,
5848
+ authorized: authorizedAuthors(owner, latest.tags),
5849
+ relays: relaysTag(latest.tags)
5850
+ });
5851
+ }
5852
+ const cursor = readCursors(deps.env)[me];
5853
+ const since = all ? void 0 : override !== null ? override : cursor?.at;
5854
+ const base = since !== void 0 ? { since } : {};
5855
+ const events = /* @__PURE__ */ new Map();
5856
+ const collect = (found) => {
5857
+ for (const [id, e] of found) if (!events.has(id)) events.set(id, e);
5858
+ };
5859
+ collect(
5860
+ await queryAll(
5861
+ configured,
5862
+ [{ kinds: INBOX_KINDS, "#p": [me], ...base }],
5863
+ ws
5864
+ )
5865
+ );
5866
+ for (const repo of watched) {
5867
+ const relays = [.../* @__PURE__ */ new Set([...configured, ...repo.relays])];
5868
+ collect(
5869
+ await queryAll(
5870
+ relays,
5871
+ [{ kinds: INBOX_KINDS, "#a": [repo.address], ...base }],
5872
+ ws
5873
+ )
5874
+ );
5875
+ }
5876
+ const authCache = /* @__PURE__ */ new Map();
5877
+ for (const repo of watched) authCache.set(repo.address, repo.authorized);
5878
+ const authorizedFor = async (address) => {
5879
+ const hit = authCache.get(address);
5880
+ if (hit) return hit;
5881
+ const m = /^30617:([0-9a-f]{64}):(.+)$/i.exec(address);
5882
+ let set = /* @__PURE__ */ new Set();
5883
+ if (m) {
5884
+ const owner = m[1].toLowerCase();
5885
+ const latest = await ownerAnnouncement(
5886
+ configured,
5887
+ owner,
5888
+ m[2],
5889
+ ws
5890
+ );
5891
+ set = authorizedAuthors(owner, latest?.tags ?? []);
5892
+ }
5893
+ authCache.set(address, set);
5894
+ return set;
5895
+ };
5896
+ const toEntries = async (found) => {
5897
+ const targetAuthors = /* @__PURE__ */ new Map();
5898
+ const statusEvents = [...found.values()].filter(
5899
+ (e) => STATUS_KINDS2.includes(e.kind)
5900
+ );
5901
+ const unknownTargets = /* @__PURE__ */ new Set();
5902
+ for (const e of statusEvents) {
5903
+ const target = tagValue2(e.tags, "e");
5904
+ if (!target || !HEX64_RE4.test(target)) continue;
5905
+ const known = found.get(target);
5906
+ if (known) targetAuthors.set(target, known.pubkey.toLowerCase());
5907
+ else unknownTargets.add(target);
5908
+ }
5909
+ if (unknownTargets.size > 0) {
5910
+ const lookedUp = await queryAll(
5911
+ configured,
5912
+ [{ ids: [...unknownTargets] }],
5913
+ ws
5914
+ ).catch(() => /* @__PURE__ */ new Map());
5915
+ for (const [id, e] of lookedUp)
5916
+ targetAuthors.set(id, e.pubkey.toLowerCase());
5917
+ }
5918
+ const entries = [];
5919
+ for (const event of found.values()) {
5920
+ const entry = toInboxEntry(event);
5921
+ if (entry === null) continue;
5922
+ if (entry.type === "status") {
5923
+ if (entry.repo === null) continue;
5924
+ const repoWide = await authorizedFor(entry.repo);
5925
+ const targetAuthor = targetAuthors.get(entry.target);
5926
+ const authors = targetAuthor === void 0 ? repoWide : withTargetAuthor(repoWide, targetAuthor);
5927
+ if (!authors.has(event.pubkey.toLowerCase())) continue;
5928
+ }
5929
+ entries.push(entry);
5930
+ }
5931
+ return entries;
5932
+ };
5933
+ const entriesOf = await toEntries(events);
5934
+ const isFresh = (e) => all || override !== null || cursor === void 0 || e.created_at > cursor.at || e.created_at === cursor.at && !cursor.seen.includes(e.id);
5935
+ const byAge = (a, b) => a.created_at - b.created_at || (a.id < b.id ? -1 : 1);
5936
+ const fresh = entriesOf.filter(isFresh).sort(byAge);
5937
+ const moveCursor = !all && override === null;
5938
+ let position = cursor;
5939
+ const advance = (emitted) => {
5940
+ if (!moveCursor || emitted.length === 0) return;
5941
+ const at = Math.max(...emitted.map((e) => e.created_at));
5942
+ const seen = emitted.filter((e) => e.created_at === at).map((e) => e.id);
5943
+ if (position?.at === at) seen.push(...position.seen);
5944
+ else if (position !== void 0 && position.at > at) return;
5945
+ position = { at, seen: [...new Set(seen)] };
5946
+ writeCursor(deps.env, me, position);
5947
+ };
5948
+ if (follow) {
5949
+ const line = (entry) => io.emitJsonLine ? io.emitJsonLine(entry) : io.out(JSON.stringify(entry));
5950
+ const emittedIds = /* @__PURE__ */ new Set();
5951
+ let liveSince = position?.at;
5952
+ const emit = (entries) => {
5953
+ for (const entry of entries) {
5954
+ emittedIds.add(entry.id);
5955
+ liveSince = Math.max(liveSince ?? 0, entry.created_at);
5956
+ line(entry);
5957
+ }
5958
+ advance(entries);
5959
+ };
5960
+ emit(fresh);
5961
+ const controller = new AbortController();
5962
+ const stop = () => controller.abort();
5963
+ const external = deps.followSignal;
5964
+ if (external?.aborted) return 0;
5965
+ external?.addEventListener("abort", stop);
5966
+ if (!external) {
5967
+ process.once("SIGINT", stop);
5968
+ process.once("SIGTERM", stop);
5969
+ }
5970
+ let chain = Promise.resolve();
5971
+ const onEvent = (event) => {
5972
+ chain = chain.then(async () => {
5973
+ if (emittedIds.has(event.id)) return;
5974
+ try {
5975
+ const [entry] = await toEntries(/* @__PURE__ */ new Map([[event.id, event]]));
5976
+ if (entry === void 0 || emittedIds.has(entry.id) || !isFresh(entry)) {
5977
+ return;
5978
+ }
5979
+ emit([entry]);
5980
+ } catch (err) {
5981
+ io.err(
5982
+ `rig inbox: could not process live event ${event.id}: ${err instanceof Error ? err.message : String(err)}`
5983
+ );
5984
+ }
5985
+ });
5986
+ };
5987
+ const targets = [];
5988
+ for (const url of configured) {
5989
+ targets.push({ url, filter: { kinds: INBOX_KINDS, "#p": [me] } });
5990
+ }
5991
+ for (const repo of watched) {
5992
+ for (const url of /* @__PURE__ */ new Set([...configured, ...repo.relays])) {
5993
+ targets.push({
5994
+ url,
5995
+ filter: { kinds: INBOX_KINDS, "#a": [repo.address] }
5996
+ });
5997
+ }
5998
+ }
5999
+ const urls = [...new Set(targets.map((t) => t.url))];
6000
+ try {
6001
+ await Promise.all(
6002
+ urls.map(
6003
+ (url) => followRelay({
6004
+ url,
6005
+ filters: () => targets.filter((t) => t.url === url).map((t) => ({ ...t.filter, since: liveSince ?? nowSec })),
6006
+ webSocketFactory: ws,
6007
+ onEvent,
6008
+ onError: (message) => io.err(`rig inbox: ${message}`),
6009
+ signal: controller.signal,
6010
+ backoffMs: deps.followBackoffMs ?? defaultBackoffMs
6011
+ })
6012
+ )
6013
+ );
6014
+ await chain;
6015
+ } finally {
6016
+ external?.removeEventListener("abort", stop);
6017
+ process.off("SIGINT", stop);
6018
+ process.off("SIGTERM", stop);
6019
+ }
6020
+ return 0;
6021
+ }
6022
+ if (json) {
6023
+ io.emitJson(fresh);
6024
+ } else if (fresh.length === 0) {
6025
+ io.out("inbox is empty");
6026
+ } else {
6027
+ for (const entry of fresh) io.out(renderLine(entry));
6028
+ }
6029
+ advance(fresh);
6030
+ return 0;
6031
+ } catch (err) {
6032
+ return emitCliError(io, json, "inbox", err);
6033
+ }
6034
+ }
6035
+
5257
6036
  // src/cli/init.ts
5258
6037
  import { basename as basename2 } from "path";
5259
- import { parseArgs as parseArgs13 } from "util";
6038
+ import { parseArgs as parseArgs14 } from "util";
5260
6039
 
5261
6040
  // src/cli/git-author.ts
5262
6041
  var PROFILE_KIND = 0;
@@ -5374,7 +6153,7 @@ Options:
5374
6153
  --json machine-readable report
5375
6154
  -h, --help show this help`;
5376
6155
  function parseInitArgs(args) {
5377
- const { values, positionals } = parseArgs13({
6156
+ const { values, positionals } = parseArgs14({
5378
6157
  args,
5379
6158
  options: {
5380
6159
  "repo-id": { type: "string" },
@@ -5601,8 +6380,9 @@ async function runInit(args, deps) {
5601
6380
  }
5602
6381
 
5603
6382
  // src/cli/maintainers.ts
5604
- import { parseArgs as parseArgs14 } from "util";
5605
- var HEX64_RE4 = /^[0-9a-f]{64}$/;
6383
+ import { parseArgs as parseArgs15 } from "util";
6384
+ var HEX64_RE5 = /^[0-9a-f]{64}$/;
6385
+ var REF_AUTHORITY_NOTICE = "Note: maintainers can move refs (publish honored repo state), not only set status.";
5606
6386
  var MAINTAINERS_USAGE = `Usage: rig maintainers <list|add|remove> [<pubkey>] [options]
5607
6387
 
5608
6388
  Manage a repo's declared maintainers (#287). Status authority is consumer-side:
@@ -5654,7 +6434,7 @@ async function runList2(args, deps) {
5654
6434
  const { io } = deps;
5655
6435
  let flags;
5656
6436
  try {
5657
- const { values, positionals } = parseArgs14({
6437
+ const { values, positionals } = parseArgs15({
5658
6438
  args,
5659
6439
  options: REPO_COMMAND_OPTIONS,
5660
6440
  allowPositionals: true
@@ -5721,7 +6501,7 @@ async function runMutate(op, args, deps) {
5721
6501
  let flags;
5722
6502
  let pubkey;
5723
6503
  try {
5724
- const { values, positionals } = parseArgs14({
6504
+ const { values, positionals } = parseArgs15({
5725
6505
  args,
5726
6506
  options: REPO_COMMAND_OPTIONS,
5727
6507
  allowPositionals: true
@@ -5735,8 +6515,10 @@ async function runMutate(op, args, deps) {
5735
6515
  throw new Error(`expected exactly one <pubkey> to ${op}`);
5736
6516
  }
5737
6517
  pubkey = ownerToHex(positionals[0]).toLowerCase();
5738
- if (!HEX64_RE4.test(pubkey)) {
5739
- throw new Error(`<pubkey> must resolve to 64-char hex (got ${JSON.stringify(positionals[0])})`);
6518
+ if (!HEX64_RE5.test(pubkey)) {
6519
+ throw new Error(
6520
+ `<pubkey> must resolve to 64-char hex (got ${JSON.stringify(positionals[0])})`
6521
+ );
5740
6522
  }
5741
6523
  } catch (err) {
5742
6524
  io.err(err instanceof Error ? err.message : String(err));
@@ -5781,11 +6563,14 @@ async function runMutate(op, args, deps) {
5781
6563
  if (!flags.json) {
5782
6564
  io.out(`Republish ${action}`);
5783
6565
  io.out(`Repo: 30617:${ctx.owner}:${ctx.repoId}`);
5784
- io.out(`Maintainers after: ${next.length === 0 ? "(none)" : next.join(", ")}`);
6566
+ io.out(
6567
+ `Maintainers after: ${next.length === 0 ? "(none)" : next.join(", ")}`
6568
+ );
5785
6569
  if (!diff.unchanged) {
5786
6570
  io.out("Tags changed:");
5787
6571
  for (const line of describeAnnouncementDiff(diff)) io.out(line);
5788
6572
  }
6573
+ io.out(REF_AUTHORITY_NOTICE);
5789
6574
  io.out(`Fee: ${feeLabel(fee)}. Writes are permanent and non-refundable.`);
5790
6575
  }
5791
6576
  if (!flags.yes) {
@@ -5835,6 +6620,7 @@ async function runMutate(op, args, deps) {
5835
6620
  io.out(
5836
6621
  `Published ${action}: ${result.eventId} paid ${result.feePaid} base units`
5837
6622
  );
6623
+ io.out(REF_AUTHORITY_NOTICE);
5838
6624
  }
5839
6625
  return 0;
5840
6626
  } catch (err) {
@@ -5850,7 +6636,7 @@ async function runMutate(op, args, deps) {
5850
6636
  }
5851
6637
 
5852
6638
  // src/cli/refresh.ts
5853
- import { parseArgs as parseArgs15 } from "util";
6639
+ import { parseArgs as parseArgs16 } from "util";
5854
6640
  var REFRESH_USAGE = `Usage: rig refresh [options]
5855
6641
 
5856
6642
  Republish this repo's kind:30617 announcement with no field edit, refreshing
@@ -5886,7 +6672,7 @@ async function runRefresh(args, deps) {
5886
6672
  const { io } = deps;
5887
6673
  let flags;
5888
6674
  try {
5889
- const { values, positionals } = parseArgs15({
6675
+ const { values, positionals } = parseArgs16({
5890
6676
  args,
5891
6677
  options: REPO_COMMAND_OPTIONS,
5892
6678
  allowPositionals: true
@@ -6009,7 +6795,7 @@ async function runRefresh(args, deps) {
6009
6795
 
6010
6796
  // src/cli/name.ts
6011
6797
  import { randomUUID } from "crypto";
6012
- import { parseArgs as parseArgs16 } from "util";
6798
+ import { parseArgs as parseArgs17 } from "util";
6013
6799
  var ARIO_DECIMALS = 6;
6014
6800
  function renderMario(mario) {
6015
6801
  return `${mario.toString()} mARIO (~${formatUnits(mario.toString(), ARIO_DECIMALS)} ARIO)`;
@@ -6518,7 +7304,7 @@ async function resolveNameContext(deps, network, processId, mode) {
6518
7304
  return { identity, solanaAddress, network, processId, sdk, nostrSecretKey };
6519
7305
  }
6520
7306
  function parseNameFlags(args, env) {
6521
- const { values, positionals } = parseArgs16({
7307
+ const { values, positionals } = parseArgs17({
6522
7308
  args,
6523
7309
  allowPositionals: true,
6524
7310
  options: {
@@ -7077,7 +7863,7 @@ async function runName(args, deps) {
7077
7863
  }
7078
7864
 
7079
7865
  // src/cli/payout.ts
7080
- import { parseArgs as parseArgs17 } from "util";
7866
+ import { parseArgs as parseArgs18 } from "util";
7081
7867
  import { getAddress } from "viem";
7082
7868
  var PAYOUT_USAGE = `Usage: rig payout <set|clear|show> [<address>] [options]
7083
7869
 
@@ -7133,7 +7919,7 @@ async function runShow2(args, deps) {
7133
7919
  const { io } = deps;
7134
7920
  let flags;
7135
7921
  try {
7136
- const { values, positionals } = parseArgs17({
7922
+ const { values, positionals } = parseArgs18({
7137
7923
  args,
7138
7924
  options: REPO_COMMAND_OPTIONS,
7139
7925
  allowPositionals: true
@@ -7195,7 +7981,7 @@ async function runMutate2(op, args, deps) {
7195
7981
  let flags;
7196
7982
  let nextPayout = null;
7197
7983
  try {
7198
- const { values, positionals } = parseArgs17({
7984
+ const { values, positionals } = parseArgs18({
7199
7985
  args,
7200
7986
  options: REPO_COMMAND_OPTIONS,
7201
7987
  allowPositionals: true
@@ -7329,7 +8115,7 @@ async function runMutate2(op, args, deps) {
7329
8115
  }
7330
8116
 
7331
8117
  // src/cli/site.ts
7332
- import { parseArgs as parseArgs18 } from "util";
8118
+ import { parseArgs as parseArgs19 } from "util";
7333
8119
 
7334
8120
  // src/arweave-manifest.ts
7335
8121
  var DEFAULT_INDEX = "index.html";
@@ -7348,22 +8134,22 @@ function buildArweaveManifest(entries, indexPath = DEFAULT_INDEX, fallbackTxId)
7348
8134
  }
7349
8135
 
7350
8136
  // src/cli/site-record.ts
7351
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
8137
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
7352
8138
  import { homedir as homedir5 } from "os";
7353
- import { join as join6 } from "path";
8139
+ import { join as join7 } from "path";
7354
8140
  var SITE_MANIFEST_FILENAME = "site-manifests.json";
7355
8141
  function storeDir2(env) {
7356
- return env["TOON_CLIENT_HOME"] ?? join6(homedir5(), ".toon-client");
8142
+ return env["TOON_CLIENT_HOME"] ?? join7(homedir5(), ".toon-client");
7357
8143
  }
7358
8144
  function storePath2(env) {
7359
- return join6(storeDir2(env), SITE_MANIFEST_FILENAME);
8145
+ return join7(storeDir2(env), SITE_MANIFEST_FILENAME);
7360
8146
  }
7361
8147
  function keyOf(repoId, ref) {
7362
8148
  return `${repoId}\0${ref}`;
7363
8149
  }
7364
8150
  function readFile2(env) {
7365
8151
  try {
7366
- const parsed = JSON.parse(readFileSync5(storePath2(env), "utf8"));
8152
+ const parsed = JSON.parse(readFileSync6(storePath2(env), "utf8"));
7367
8153
  if (parsed && typeof parsed === "object") return parsed;
7368
8154
  return {};
7369
8155
  } catch {
@@ -7375,10 +8161,10 @@ function readSiteRecord(env, repoId, ref) {
7375
8161
  }
7376
8162
  function writeSiteRecord(env, record) {
7377
8163
  const dir = storeDir2(env);
7378
- mkdirSync5(dir, { recursive: true });
8164
+ mkdirSync6(dir, { recursive: true });
7379
8165
  const file = readFile2(env);
7380
8166
  file[keyOf(record.repoId, record.ref)] = record;
7381
- writeFileSync4(storePath2(env), `${JSON.stringify(file, null, 2)}
8167
+ writeFileSync5(storePath2(env), `${JSON.stringify(file, null, 2)}
7382
8168
  `, "utf8");
7383
8169
  }
7384
8170
 
@@ -7420,7 +8206,7 @@ Options (publish):
7420
8206
  estimate (nothing uploaded)
7421
8207
  -h, --help show this help`;
7422
8208
  function parseSiteArgs(args) {
7423
- const { values, positionals } = parseArgs18({
8209
+ const { values, positionals } = parseArgs19({
7424
8210
  args,
7425
8211
  options: {
7426
8212
  index: { type: "string" },
@@ -7821,14 +8607,23 @@ Commands rig owns:
7821
8607
  site url [ref] print the last-published site URL for a ref (free)
7822
8608
  issue create file an issue (kind:1621) against a repo
7823
8609
  issue list | show <id> read the repo's issues + comments (free)
8610
+ issue status <id> <open|closed> set an issue's status (kind:1630/1632);
8611
+ close | reopen <id> are shortcuts
7824
8612
  pr list | show <id> read the repo's patches; show prints the full
7825
8613
  patch text (free)
7826
8614
  comment <target-event-id> comment (NIP-22 kind:1111) on an issue or patch,
7827
8615
  or reply to an existing kind:1111 comment
7828
8616
  pr create publish a patch (kind:1617) with real
7829
- \`git format-patch\` content
8617
+ \`git format-patch\` content (--revises <patch-id>
8618
+ publishes a revision of an existing patch)
7830
8619
  pr status <event-id> <state> set an issue/patch status (kind:1630-1633):
7831
8620
  open | applied | closed | draft
8621
+ inbox everything new since you last read it (free):
8622
+ patches, revisions, issues, comments, statuses
8623
+ and CI results for repos you own or maintain,
8624
+ plus events that tag you; --since/--all look
8625
+ back without moving the cursor; --follow keeps
8626
+ streaming new events as JSON lines
7832
8627
  maintainers list show the repo's declared maintainers (free); add/
7833
8628
  maintainers add <pubkey> remove republish the kind:30617 to change who may
7834
8629
  maintainers remove <pubkey> author authoritative issue/PR status (owner is
@@ -7889,7 +8684,8 @@ Any other command is passed through to git verbatim: \`rig status\` runs
7889
8684
  Run \`rig <command> --help\` for a rig command's flags. \`rig init\`,
7890
8685
  \`rig remote\`, \`rig clone\`, \`rig fetch\`, \`rig issue list/show\`,
7891
8686
  \`rig pr list/show\`, \`rig fund\`, \`rig balance\`, and \`rig channel list\`
7892
- are free; push/issue create/comment/pr create/pr status are paid writes \u2014
8687
+ are free; push/issue create/issue status/close/reopen/comment/pr create/pr
8688
+ status are paid writes \u2014
7893
8689
  permanent and non-refundable \u2014
7894
8690
  and channel open/close/settle are on-chain wallet transactions; each states
7895
8691
  what it will spend and asks for confirmation before doing so (--yes skips,
@@ -7950,6 +8746,8 @@ async function dispatch(argv, deps) {
7950
8746
  return runComment(rest, deps);
7951
8747
  case "pr":
7952
8748
  return runPr(rest, deps);
8749
+ case "inbox":
8750
+ return runInbox(rest, deps);
7953
8751
  case "maintainers":
7954
8752
  return runMaintainers(rest, deps);
7955
8753
  case "payout":
@@ -8006,4 +8804,4 @@ export {
8006
8804
  rigVersion,
8007
8805
  dispatch
8008
8806
  };
8009
- //# sourceMappingURL=chunk-2TG4YHEC.js.map
8807
+ //# sourceMappingURL=chunk-Y7TUE7JT.js.map