balladeer 0.0.5 → 1.0.1

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.
Files changed (72) hide show
  1. package/LICENSE +200 -5
  2. package/README.md +167 -68
  3. package/dist/agent.d.ts +126 -0
  4. package/dist/agent.js +209 -0
  5. package/dist/cli.d.ts +48 -0
  6. package/dist/cli.js +531 -0
  7. package/dist/client.d.ts +66 -0
  8. package/dist/client.js +142 -0
  9. package/dist/commands/affected.d.ts +22 -0
  10. package/dist/commands/affected.js +123 -0
  11. package/dist/commands/check-seals.d.ts +37 -0
  12. package/dist/commands/check-seals.js +289 -0
  13. package/dist/commands/discover.d.ts +68 -0
  14. package/dist/commands/discover.js +403 -0
  15. package/dist/commands/explain.d.ts +35 -0
  16. package/dist/commands/explain.js +90 -0
  17. package/dist/commands/invite.d.ts +24 -0
  18. package/dist/commands/invite.js +198 -0
  19. package/dist/commands/mcp.d.ts +65 -0
  20. package/dist/commands/mcp.js +202 -0
  21. package/dist/commands/prepare.d.ts +74 -0
  22. package/dist/commands/prepare.js +217 -0
  23. package/dist/commands/propose.d.ts +69 -0
  24. package/dist/commands/propose.js +284 -0
  25. package/dist/commands/repositories.d.ts +18 -0
  26. package/dist/commands/repositories.js +185 -0
  27. package/dist/commands/session.d.ts +35 -0
  28. package/dist/commands/session.js +118 -0
  29. package/dist/commands/setup.d.ts +98 -0
  30. package/dist/commands/setup.js +1600 -0
  31. package/dist/commands/status.d.ts +51 -0
  32. package/dist/commands/status.js +542 -0
  33. package/dist/commands/touch-map.d.ts +42 -0
  34. package/dist/commands/touch-map.js +251 -0
  35. package/dist/commands/whoami.d.ts +8 -0
  36. package/dist/commands/whoami.js +80 -0
  37. package/dist/conventions.d.ts +77 -0
  38. package/dist/conventions.js +183 -0
  39. package/dist/copy.d.ts +224 -0
  40. package/dist/copy.js +641 -0
  41. package/dist/currency.d.ts +31 -0
  42. package/dist/currency.js +72 -0
  43. package/dist/desktop-config.d.ts +85 -0
  44. package/dist/desktop-config.js +217 -0
  45. package/dist/gh.d.ts +80 -0
  46. package/dist/gh.js +188 -0
  47. package/dist/git.d.ts +91 -0
  48. package/dist/git.js +226 -0
  49. package/dist/legacy.d.ts +41 -0
  50. package/dist/legacy.js +143 -0
  51. package/dist/local-time.d.ts +66 -0
  52. package/dist/local-time.js +84 -0
  53. package/dist/markers.d.ts +76 -0
  54. package/dist/markers.js +125 -0
  55. package/dist/mcp-config.d.ts +109 -0
  56. package/dist/mcp-config.js +234 -0
  57. package/dist/release.d.ts +55 -0
  58. package/dist/release.js +67 -0
  59. package/dist/repository.d.ts +8 -0
  60. package/dist/repository.js +32 -0
  61. package/dist/seals.d.ts +48 -0
  62. package/dist/seals.js +112 -0
  63. package/dist/session.d.ts +84 -0
  64. package/dist/session.js +135 -0
  65. package/dist/store.d.ts +108 -0
  66. package/dist/store.js +237 -0
  67. package/dist/touch-map.d.ts +241 -0
  68. package/dist/touch-map.js +487 -0
  69. package/dist/wire.d.ts +674 -0
  70. package/dist/wire.js +20 -0
  71. package/package.json +19 -10
  72. package/bin/balladeer.js +0 -161
package/dist/wire.d.ts ADDED
@@ -0,0 +1,674 @@
1
+ /**
2
+ * The request and response shapes this command sends and receives, inlined.
3
+ *
4
+ * They are written out here rather than imported so the published package has no
5
+ * runtime dependency at all: Node 22 builtins and global fetch, nothing else.
6
+ * A contract test compares the scope list below against the server's.
7
+ */
8
+ export declare const CLI_VERSION = "1.0.1";
9
+ export declare const CLIENT_HEADER = "x-balladeer-client";
10
+ export declare const CLIENT_HEADER_VALUE = "balladeer/1.0.1";
11
+ export declare const DEFAULT_CONTROL_PLANE = "https://envelopes.balladeer.ai";
12
+ export type DelegatedScope = "repository:enroll" | "agent:issue" | "ci:connect" | "workspace:invite" | "candidate:propose";
13
+ export declare const DELEGATED_SCOPES: readonly DelegatedScope[];
14
+ /** The verifier digest the server accepts, and the form the store writes. */
15
+ export declare const VERIFIER_DIGEST_PREFIX = "sha256:";
16
+ export type PairStartRequest = Readonly<{
17
+ verifierDigest: string;
18
+ repositoryHint: string;
19
+ hostHint: string;
20
+ }>;
21
+ export type PairStartResponse = Readonly<{
22
+ pairingId: string;
23
+ userCode: string;
24
+ verificationUri: string;
25
+ verificationUriComplete: string;
26
+ expiresAt: string;
27
+ pollIntervalSeconds: number;
28
+ }>;
29
+ export type PairSessionSummary = Readonly<{
30
+ sessionId: string;
31
+ workspaceId: string;
32
+ workspaceName: string;
33
+ workspaceSlug: string;
34
+ /**
35
+ * The membership that approved this session. `discover` names it as the
36
+ * proposed owner of every promise it files, because the person who ran setup
37
+ * is the person the catalog is waiting on. Absent from a server too old to
38
+ * send it, which is a refusal to guess rather than a wrong owner.
39
+ */
40
+ membershipId?: string;
41
+ membershipDisplayName: string;
42
+ role: string;
43
+ scopes: readonly DelegatedScope[];
44
+ scopeMeanings: readonly string[];
45
+ expiresAt: string;
46
+ }>;
47
+ export type PairPollResponse = Readonly<{
48
+ status: "pending";
49
+ pollIntervalSeconds: number;
50
+ }> | Readonly<{
51
+ status: "claimed";
52
+ token: string;
53
+ session: PairSessionSummary;
54
+ }>;
55
+ export type PairRefusal = Readonly<{
56
+ error: string;
57
+ message?: string;
58
+ minimum?: string;
59
+ blessed?: string;
60
+ update?: string;
61
+ }>;
62
+ /**
63
+ * The setup API, inlined the same way and for the same reason.
64
+ *
65
+ * A contract test compares these field names against `@balladeer/contracts`, so
66
+ * the published package still carries no runtime dependency while a change to
67
+ * either side that the other did not follow fails the build.
68
+ */
69
+ export type SetupRepositoryRequest = Readonly<{
70
+ repository: string;
71
+ githubRepositoryId: string;
72
+ githubOwnerId: string;
73
+ defaultBranch: string;
74
+ }>;
75
+ export type SetupAgentRequest = Readonly<{
76
+ label?: string;
77
+ rotate?: boolean;
78
+ }>;
79
+ /**
80
+ * One teammate invited. The workspace is absent because the session decides it:
81
+ * a command cannot invite anybody into a workspace its approver is not in.
82
+ */
83
+ export type SetupInvitationRequest = Readonly<{
84
+ email: string;
85
+ requestedRole: "admin" | "contributor" | "viewer";
86
+ }>;
87
+ /**
88
+ * What the server answers once the email has actually gone.
89
+ *
90
+ * The address comes back as the server recorded it rather than as it was typed,
91
+ * so the confirmation a person reads names the address the invitation went to.
92
+ * `expiresAt` is null where the deadline could not be read back; the invitation
93
+ * was still sent, and reporting a guessed date would be worse than reporting
94
+ * none.
95
+ */
96
+ export type SetupInvitationResponse = Readonly<{
97
+ email: string;
98
+ requestedRole: "admin" | "contributor" | "viewer";
99
+ status: "sent";
100
+ workspaceName: string;
101
+ expiresAt: string | null;
102
+ }>;
103
+ /**
104
+ * Only the event classes. The GitHub owner id and the caller workflow reference
105
+ * decide whose signed runs count, so the server derives both from the
106
+ * enrollment and this command never names either.
107
+ */
108
+ export type SetupCiRequest = Readonly<{
109
+ eventClasses: readonly ("push" | "pull_request")[];
110
+ }>;
111
+ /**
112
+ * The GitHub login this machine's `gh` is signed in as.
113
+ *
114
+ * Sent so a run started by this person can be shown under their name instead of
115
+ * a handle nobody has learned. No token is sent, here or anywhere: the login is
116
+ * read from the GitHub CLI they already signed in to, and Balladeer stores it as
117
+ * something they said rather than something anyone checked. It grants nothing.
118
+ * An empty string clears it.
119
+ */
120
+ export type SetupGithubLoginRequest = Readonly<{
121
+ login: string;
122
+ }>;
123
+ export type SetupGithubLoginResponse = Readonly<{
124
+ claimedLogin: string | null;
125
+ }>;
126
+ export type SetupRepositoryView = Readonly<{
127
+ id: string;
128
+ displayName: string;
129
+ defaultBranch: string;
130
+ status: "enrolling" | "active" | "revoked";
131
+ githubRepositoryId: string | null;
132
+ githubOwnerId: string | null;
133
+ agentConfigured: boolean;
134
+ agentConnectionId: string | null;
135
+ ciIdentityRecorded: boolean;
136
+ ciConfigured: boolean;
137
+ ciValidatedRunCount: number;
138
+ ciFirstValidatedAt: string | null;
139
+ ciLastValidatedAt: string | null;
140
+ eventClasses: readonly string[];
141
+ ciAttestorReleaseId: string | null;
142
+ /**
143
+ * The release this repository was moved off and whose runs are still
144
+ * accepted, and null when the enrollment names only its current release.
145
+ * Publication moves the enrollment; this is the pin the repository's own
146
+ * workflow file may still carry.
147
+ */
148
+ ciSupersededAttestorReleaseId: string | null;
149
+ /** That release's commit, which is the pin the workflow file is leaving. */
150
+ ciSupersededAttestorReleaseSha: string | null;
151
+ /**
152
+ * Candidates still waiting for a named person, and the newest of them. One a
153
+ * person has already accepted, merged, or rejected is in neither, so this
154
+ * command can never send someone back to the browser to agree to something
155
+ * that is already agreed or was refused.
156
+ */
157
+ candidateCount: number;
158
+ latestCandidateId: string | null;
159
+ promiseCount: number;
160
+ /** The promise this repository's first agreement produced, once one exists. */
161
+ firstPromiseId: string | null;
162
+ }>;
163
+ /**
164
+ * How the server says this command should be invoked.
165
+ *
166
+ * `publishedVersion` is null until the package is actually on the registry, and
167
+ * `command` is the invocation to print: the pinned registry form once it is,
168
+ * and the checkout form until then.
169
+ */
170
+ export type SetupClientRelease = Readonly<{
171
+ publishedVersion: string | null;
172
+ command: string;
173
+ }>;
174
+ export type SetupStateResponse = Readonly<{
175
+ workspace: Readonly<{
176
+ id: string;
177
+ name: string;
178
+ slug: string;
179
+ }>;
180
+ repositories: readonly SetupRepositoryView[];
181
+ client: SetupClientRelease;
182
+ }>;
183
+ export type CallerWorkflowResponse = Readonly<{
184
+ workspaceLocator: string;
185
+ defaultBranch: string;
186
+ workflowPath: string;
187
+ workflowYaml: string;
188
+ attestorWorkflowRef: string;
189
+ attestorSha: string;
190
+ attestationOrigin: string;
191
+ variables: readonly Readonly<{
192
+ name: string;
193
+ value: string;
194
+ }>[];
195
+ }>;
196
+ export type AgentPacketResponse = Readonly<{
197
+ connectionId: string;
198
+ repositoryId: string;
199
+ scopes: readonly string[];
200
+ token: string;
201
+ mcpUrl: string;
202
+ configuration: unknown;
203
+ instructions: readonly string[];
204
+ }>;
205
+ export type SetupCandidateResponse = Readonly<{
206
+ candidateId: string;
207
+ repositoryId: string;
208
+ reviewUrl: string;
209
+ }>;
210
+ /** The step objects `--json` emits, one per line, and nothing else on stdout. */
211
+ /**
212
+ * What a run did about Claude desktop chat, on the two steps that can touch it.
213
+ *
214
+ * Its own type because both `agent` and `refresh` report it and they must report
215
+ * it identically: an agent reading either step reads the same three fields.
216
+ */
217
+ export type ClaudeDesktopStep = Readonly<{
218
+ status: "connected" | "current" | "absent" | "unsupported" | "refused";
219
+ key?: string;
220
+ path?: string;
221
+ reason?: string;
222
+ }>;
223
+ export type JsonStep = Readonly<{
224
+ step: "explain";
225
+ version: string;
226
+ }> | Readonly<{
227
+ /**
228
+ * The GitHub login this machine was signed in as, handed over so a run
229
+ * this person starts can be shown under their name. `status` says what
230
+ * happened: `claimed` when it was recorded, `unavailable` when `gh` could
231
+ * not name a login, and `refused` when the control plane would not take
232
+ * it. None of the three stops setup.
233
+ */
234
+ step: "github_login";
235
+ status: "claimed" | "unavailable" | "refused";
236
+ login?: string;
237
+ }> | Readonly<{
238
+ step: "pair";
239
+ status: "pending" | "paired" | "expired" | "denied";
240
+ userCode?: string;
241
+ verificationUri?: string;
242
+ expiresAt?: string;
243
+ workspace?: Readonly<{
244
+ id: string;
245
+ name: string;
246
+ slug: string;
247
+ }>;
248
+ scopes?: readonly DelegatedScope[];
249
+ }> | Readonly<{
250
+ step: "repository";
251
+ status: "added" | "already" | "blocked";
252
+ repository?: string;
253
+ repositoryId?: string;
254
+ defaultBranch?: string;
255
+ /**
256
+ * Present only where this run filled in the GitHub owner id of a
257
+ * repository that was added without one. It is the id now recorded, so an
258
+ * agent reading these lines can see that step 4 became possible here.
259
+ */
260
+ githubOwnerIdRecorded?: string;
261
+ reason?: string;
262
+ }> | Readonly<{
263
+ step: "agent";
264
+ /**
265
+ * `connected` is reported only where Balladeer answered a real call over
266
+ * the credential this step issued. `unproven` is the connection existing
267
+ * and that call not having succeeded: the credential is stored and the
268
+ * files are written, and a caller must not report it as working.
269
+ * `already` is this machine holding a credential for this repository
270
+ * before the run started, which is the only reading of "already" that
271
+ * means the tools will work here.
272
+ */
273
+ status: "connected" | "unproven" | "already" | "blocked";
274
+ repositoryId?: string;
275
+ connectionId?: string;
276
+ /**
277
+ * True where this machine holds a credential for the repository once the
278
+ * step is over. A caller reads this rather than inferring a working
279
+ * machine from the repository being connected: the two are different
280
+ * facts, and only this one says the tools can run from here.
281
+ */
282
+ storedHere?: boolean;
283
+ /**
284
+ * True where the repository already had a live connection issued
285
+ * elsewhere and this run issued a further one for this machine. The
286
+ * existing connection is untouched; connections coexist and each is
287
+ * revoked on its own.
288
+ */
289
+ alreadyConnectedElsewhere?: boolean;
290
+ files?: readonly string[];
291
+ reason?: string;
292
+ /** The tool call that proved the connection, on a `connected` step. */
293
+ provedBy?: string;
294
+ /** What the person or agent does next, on an `unproven` step. */
295
+ nextAction?: string;
296
+ /** Present only where this command refused to change one of these files. */
297
+ mcpConfig?: string;
298
+ conventions?: string;
299
+ /**
300
+ * What happened to Claude desktop chat, present only where this run had
301
+ * something to say about it. `connected` and `current` carry the key this
302
+ * repository's server is named under and the file it was written to;
303
+ * `absent`, `unsupported` and `refused` carry the sentence saying why
304
+ * nothing was written, and are only ever reported where the flag asked for
305
+ * it or the file itself refused the merge.
306
+ */
307
+ claudeDesktop?: ClaudeDesktopStep;
308
+ }> | Readonly<{
309
+ step: "ci";
310
+ /**
311
+ * `release_upgraded` is this repository having been moved onto a newer
312
+ * Balladeer release. It is deliberately not `connected`: the enrollment has
313
+ * moved, and the repository's own workflow file has not, so the proof is a
314
+ * run on the new pin that has not happened yet.
315
+ */
316
+ status: "identity_recorded" | "connected" | "release_upgraded" | "blocked";
317
+ repositoryId?: string;
318
+ pullRequestUrl?: string;
319
+ validatedRunCount?: number;
320
+ firstValidatedAt?: string;
321
+ /** The workflow is already on the default branch, so there was nothing to commit. */
322
+ workflowInstalled?: boolean;
323
+ /** The release this repository was moved onto, on a `release_upgraded` step. */
324
+ attestorReleaseSha?: string;
325
+ reason?: string;
326
+ }> | Readonly<{
327
+ step: "promise";
328
+ status: "proposed" | "agreed" | "none";
329
+ candidateId?: string;
330
+ promiseId?: string;
331
+ reviewUrl?: string;
332
+ promiseUrl?: string;
333
+ }>
334
+ /**
335
+ * The one-time qualification setup, minted and written to disk.
336
+ *
337
+ * `supersededPackets` is the number of earlier packets this run invalidated,
338
+ * so a caller can tell an ordinary first preparation from a deliberate
339
+ * replacement without reading prose. It is zero on the ordinary case.
340
+ */
341
+ | Readonly<{
342
+ step: "qualification";
343
+ status: "prepared";
344
+ promiseId: string;
345
+ metadataPath: string;
346
+ supersededPackets: number;
347
+ }>
348
+ /**
349
+ * A whole discovered catalog, filed in one run.
350
+ *
351
+ * `proposed` and `requested` are separate numbers on purpose: an agent that
352
+ * read only the count would report eight promises filed from a file of ten.
353
+ * `reviewUrl` is the one page that opens every promise this run filed, and it
354
+ * is absent when nothing was filed rather than pointing at an empty page.
355
+ */
356
+ | Readonly<{
357
+ step: "discovery";
358
+ status: "proposed" | "partial";
359
+ proposed: number;
360
+ requested: number;
361
+ /** The membership every promise in this run was proposed as owned by. */
362
+ ownerId: string;
363
+ candidateIds: readonly string[];
364
+ reviewUrl?: string;
365
+ /**
366
+ * Promises the run never tried, because a connection-level failure would
367
+ * have answered every one of them the same way. Present only when the run
368
+ * stopped early, so a caller can tell "refused" from "not attempted".
369
+ */
370
+ notAttempted?: number;
371
+ failures?: readonly Readonly<{
372
+ promise: number;
373
+ reason: string;
374
+ message: string;
375
+ }>[];
376
+ }>
377
+ /**
378
+ * This repository, read over its own agent connection rather than over a setup
379
+ * session. It is the step that answers "is Balladeer working here" long after
380
+ * the setup session that issued the connection has gone.
381
+ */
382
+ | Readonly<{
383
+ step: "connection";
384
+ status: "agent";
385
+ repositoryId: string;
386
+ repository?: string;
387
+ /** False when the repository has no current attestor release enrolled. */
388
+ enrolled: boolean;
389
+ /** How many active members may be named as a promise's proposed owner. */
390
+ memberCount: number;
391
+ }>
392
+ /**
393
+ * Something about this repository that a person should look at, observed on
394
+ * this machine rather than reported by Balladeer.
395
+ *
396
+ * Today there is one: a promise naming a path this checkout does not have,
397
+ * which is what a rename leaves behind. Balladeer cannot see it, because it
398
+ * holds no token for the repository and reads no file. `considered` and
399
+ * `total` are separate because the run is bounded: a caller has to be able to
400
+ * tell "nothing is missing" from "nothing is missing in the part I read".
401
+ */
402
+ | Readonly<{
403
+ step: "attention";
404
+ reason: "scope_marker_missing";
405
+ observed: number;
406
+ considered: number;
407
+ total: number;
408
+ stoppedEarly: boolean;
409
+ promises: readonly Readonly<{
410
+ promiseId: string;
411
+ missing: readonly string[];
412
+ }>[];
413
+ }>
414
+ /**
415
+ * One promise, read by id, and whether it is currently holding.
416
+ *
417
+ * This is what the copyable line on a broken promise sends an agent to, so it
418
+ * carries exactly the three facts a repair starts from: the commit the failing
419
+ * run checked, the bounded reason it could not report green, and the cases in
420
+ * the agreed meaning that run was checking. A promise that is holding says so
421
+ * and carries none of them.
422
+ */
423
+ | Readonly<{
424
+ step: "promise";
425
+ status: "holding" | "not_holding" | "unreadable";
426
+ promiseId: string;
427
+ title?: string;
428
+ protectionPosture?: string;
429
+ promiseUrl?: string;
430
+ ownerName?: string;
431
+ /** `refuted` is the caught regression; `unknown` is the check not deciding. */
432
+ kind?: "refuted" | "unknown";
433
+ sourceSha?: string;
434
+ reasonCode?: string;
435
+ refutedCaseIds?: readonly string[];
436
+ refutedCaseNote?: string;
437
+ observedAt?: string;
438
+ message?: string;
439
+ }>
440
+ /**
441
+ * The workspace-wide view, named as unavailable rather than reported as empty.
442
+ * Only a live setup session can read every repository, so a caller is told
443
+ * which view it did not get and why.
444
+ */
445
+ | Readonly<{
446
+ step: "workspace";
447
+ status: "unavailable";
448
+ reason: string;
449
+ message: string;
450
+ }>
451
+ /**
452
+ * The four receipts, each true on its own and each with an explicit not-yet
453
+ * form. Never one rolled-up "done": a person reading this has to be able to
454
+ * tell what was set up from what a named person still has to agree to.
455
+ */
456
+ | Readonly<{
457
+ step: "receipt";
458
+ setup: Readonly<{
459
+ complete: boolean;
460
+ sentence: string;
461
+ }>;
462
+ /**
463
+ * `reviewUrl` is a candidate a person still has to read, and `promiseUrl`
464
+ * is one they have agreed to. Never both, and never one under the other's
465
+ * name: a caller that followed `reviewUrl` to an already-agreed promise
466
+ * would send its person to agree to it a second time.
467
+ */
468
+ firstPromise: Readonly<{
469
+ complete: boolean;
470
+ sentence: string;
471
+ reviewUrl?: string;
472
+ promiseUrl?: string;
473
+ }>;
474
+ protection: Readonly<{
475
+ complete: boolean;
476
+ sentence: string;
477
+ }>;
478
+ /**
479
+ * Which credential keeps working once this run is over. Setting Balladeer
480
+ * up needs a session that ends; proposing and reporting from then on run
481
+ * over each repository's agent connection, which does not.
482
+ */
483
+ connection: Readonly<{
484
+ complete: boolean;
485
+ sentence: string;
486
+ }>;
487
+ repositories: readonly Readonly<{
488
+ repository: string;
489
+ agent: string;
490
+ ci: string;
491
+ }>[];
492
+ }>
493
+ /**
494
+ * Which promises this checkout seals, and which of them a pending change
495
+ * would break the seal of.
496
+ *
497
+ * `broken` is the count that matters and `promises` is the bounded list a
498
+ * person or an agent acts on. The reseal line travels with each row because
499
+ * the alternative is an agent assembling one from a path it half remembers.
500
+ */
501
+ | Readonly<{
502
+ step: "seals";
503
+ broken: number;
504
+ promises: readonly Readonly<{
505
+ promiseId: string;
506
+ sealedPath: string;
507
+ title?: string;
508
+ owner?: string;
509
+ promiseUrl?: string;
510
+ changedPaths: readonly string[];
511
+ resealWith: string;
512
+ }>[];
513
+ changed: boolean;
514
+ }>
515
+ /** The sealed directories this checkout carries, listed before anybody edits one. */
516
+ | Readonly<{
517
+ step: "sealed_paths";
518
+ total: number;
519
+ shown: readonly Readonly<{
520
+ promiseId: string;
521
+ sealedPath: string;
522
+ title?: string;
523
+ }>[];
524
+ }>
525
+ /** The optional pre-push hook, written only when somebody asked for it by name. */
526
+ | Readonly<{
527
+ step: "hook";
528
+ path: string;
529
+ changed: boolean;
530
+ }>
531
+ /**
532
+ * Every way this command can fail, in the shape a caller can branch on.
533
+ *
534
+ * `--json` is the mode an agent picks, and an agent that receives a bare exit
535
+ * code cannot tell a person what went wrong or whether the run left anything
536
+ * behind. `reason` is the bounded code, `message` is the same sentence a person
537
+ * would have read, and `changed` says whether this run altered anything.
538
+ */
539
+ | Readonly<{
540
+ step: "error";
541
+ reason: string;
542
+ message: string;
543
+ changed: boolean;
544
+ exitCode: number;
545
+ }>
546
+ /**
547
+ * The working session an agent reads and commits under.
548
+ *
549
+ * `minted` is the difference between "here is a new session" and "you already
550
+ * have one open": an agent that asked twice must reuse the second answer
551
+ * rather than treat it as a second session, or its reads and its commit end
552
+ * up under two ids and the join breaks by the act of asking for it.
553
+ */
554
+ | Readonly<{
555
+ step: "session";
556
+ sessionId: string;
557
+ startedAt: string;
558
+ minted: boolean;
559
+ /** The exact line to write into the commit or the pull-request body. */
560
+ trailer: string;
561
+ changed: boolean;
562
+ }>
563
+ /** One commit recorded as written by one session. */
564
+ | Readonly<{
565
+ step: "session_commit";
566
+ sessionId: string;
567
+ commitSha: string;
568
+ changed: boolean;
569
+ }>
570
+ /**
571
+ * One invited address, one line, whatever happened to it.
572
+ *
573
+ * A run that invites four people emits four of these, because a caller has to
574
+ * be able to tell a person which of the four emails went. Never one rolled-up
575
+ * status: three sent and one refused is not "partly done", it is three people
576
+ * who will get an email and one who will not.
577
+ */
578
+ | Readonly<{
579
+ step: "invitation";
580
+ status: "sent" | "refused";
581
+ email: string;
582
+ role: "admin" | "contributor" | "viewer";
583
+ workspace?: string;
584
+ expiresAt?: string | null;
585
+ reason?: string;
586
+ message?: string;
587
+ }>
588
+ /**
589
+ * What this machine can see and could add, so an agent has something to offer
590
+ * rather than asking a person to type repository names from memory.
591
+ *
592
+ * `enrolled` is read from the workspace, and absent entirely when no setup
593
+ * session could read it: a listing that quietly showed every repository as not
594
+ * enrolled would invite an agent to add the ones already there.
595
+ */
596
+ | Readonly<{
597
+ step: "repositories";
598
+ /** Where the names came from: the GitHub CLI, or this checkout's origin. */
599
+ source: "gh" | "origin" | "none";
600
+ workspaceKnown: boolean;
601
+ repositories: readonly Readonly<{
602
+ repository: string;
603
+ /** True for the repository this directory's origin remote names. */
604
+ here: boolean;
605
+ enrolled?: boolean;
606
+ }>[];
607
+ reason?: string;
608
+ }>
609
+ /**
610
+ * What the touch map now holds, after measuring this checkout's verifiers.
611
+ *
612
+ * `bytes` is here because the map is a file a person keeps, and `changed`
613
+ * because a second run over an unchanged repository rewrites nothing. Neither
614
+ * this step nor the file it describes ever leaves the machine: the map names
615
+ * a customer's own files, which Balladeer promises never to receive.
616
+ */
617
+ | Readonly<{
618
+ step: "touch_map";
619
+ promises: number;
620
+ files: number;
621
+ /** Promises whose verifier could not be measured, so nothing is mapped to them. */
622
+ unmapped: number;
623
+ bytes: number;
624
+ changed: boolean;
625
+ /** Present only when a size cap cut the map, so a short map is never read as complete. */
626
+ truncated?: true;
627
+ }>
628
+ /**
629
+ * Which promises the named paths touch, read out of the local map.
630
+ *
631
+ * `mapped` false is the answer when there is no map to read, and it is not an
632
+ * error: a repository nobody has measured yet has no answer to give, and a
633
+ * caller that treated that as a failure would break the hook it sits in.
634
+ * `stale` on a row says the verifier has changed since the map was built, so
635
+ * the answer is the last measured one rather than a current one.
636
+ */
637
+ | Readonly<{
638
+ step: "affected";
639
+ mapped: boolean;
640
+ generatedAt?: string;
641
+ truncated?: true;
642
+ paths: readonly Readonly<{
643
+ path: string;
644
+ promises: readonly Readonly<{
645
+ promiseId: string;
646
+ stale: boolean;
647
+ title?: string;
648
+ claim?: string;
649
+ }>[];
650
+ }>[];
651
+ changed: boolean;
652
+ }>
653
+ /**
654
+ * What `setup --refresh` rewrote, and what it left alone.
655
+ *
656
+ * `current` is the honest answer for a repository whose files already say the
657
+ * right thing, and it is not the same as `updated`: an agent that reported a
658
+ * repair on a run that changed nothing would be telling a person their stale
659
+ * install was fixed.
660
+ */
661
+ | Readonly<{
662
+ step: "refresh";
663
+ status: "updated" | "current" | "blocked";
664
+ repositoryId?: string;
665
+ files: readonly string[];
666
+ conventionsVersion?: number;
667
+ previousConventionsVersion?: number;
668
+ reason?: string;
669
+ message?: string;
670
+ claudeDesktop?: ClaudeDesktopStep;
671
+ }> | Readonly<{
672
+ step: "whoami";
673
+ session: PairSessionSummary;
674
+ }>;
package/dist/wire.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The request and response shapes this command sends and receives, inlined.
3
+ *
4
+ * They are written out here rather than imported so the published package has no
5
+ * runtime dependency at all: Node 22 builtins and global fetch, nothing else.
6
+ * A contract test compares the scope list below against the server's.
7
+ */
8
+ export const CLI_VERSION = "1.0.1";
9
+ export const CLIENT_HEADER = "x-balladeer-client";
10
+ export const CLIENT_HEADER_VALUE = `balladeer/${CLI_VERSION}`;
11
+ export const DEFAULT_CONTROL_PLANE = "https://envelopes.balladeer.ai";
12
+ export const DELEGATED_SCOPES = [
13
+ "repository:enroll",
14
+ "agent:issue",
15
+ "ci:connect",
16
+ "workspace:invite",
17
+ "candidate:propose",
18
+ ];
19
+ /** The verifier digest the server accepts, and the form the store writes. */
20
+ export const VERIFIER_DIGEST_PREFIX = "sha256:";