balladeer 0.0.4 → 1.0.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.
Files changed (60) hide show
  1. package/LICENSE +200 -5
  2. package/README.md +154 -68
  3. package/dist/agent.d.ts +126 -0
  4. package/dist/agent.js +209 -0
  5. package/dist/cli.d.ts +34 -0
  6. package/dist/cli.js +392 -0
  7. package/dist/client.d.ts +44 -0
  8. package/dist/client.js +114 -0
  9. package/dist/commands/affected.d.ts +22 -0
  10. package/dist/commands/affected.js +122 -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 +395 -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 +197 -0
  19. package/dist/commands/mcp.d.ts +65 -0
  20. package/dist/commands/mcp.js +202 -0
  21. package/dist/commands/propose.d.ts +59 -0
  22. package/dist/commands/propose.js +262 -0
  23. package/dist/commands/repositories.d.ts +18 -0
  24. package/dist/commands/repositories.js +185 -0
  25. package/dist/commands/setup.d.ts +75 -0
  26. package/dist/commands/setup.js +1471 -0
  27. package/dist/commands/status.d.ts +35 -0
  28. package/dist/commands/status.js +482 -0
  29. package/dist/commands/touch-map.d.ts +42 -0
  30. package/dist/commands/touch-map.js +251 -0
  31. package/dist/commands/whoami.d.ts +8 -0
  32. package/dist/commands/whoami.js +79 -0
  33. package/dist/conventions.d.ts +69 -0
  34. package/dist/conventions.js +175 -0
  35. package/dist/copy.d.ts +148 -0
  36. package/dist/copy.js +459 -0
  37. package/dist/currency.d.ts +31 -0
  38. package/dist/currency.js +72 -0
  39. package/dist/gh.d.ts +80 -0
  40. package/dist/gh.js +188 -0
  41. package/dist/git.d.ts +76 -0
  42. package/dist/git.js +203 -0
  43. package/dist/markers.d.ts +76 -0
  44. package/dist/markers.js +125 -0
  45. package/dist/mcp-config.d.ts +99 -0
  46. package/dist/mcp-config.js +230 -0
  47. package/dist/release.d.ts +55 -0
  48. package/dist/release.js +67 -0
  49. package/dist/repository.d.ts +8 -0
  50. package/dist/repository.js +32 -0
  51. package/dist/seals.d.ts +48 -0
  52. package/dist/seals.js +112 -0
  53. package/dist/store.d.ts +98 -0
  54. package/dist/store.js +225 -0
  55. package/dist/touch-map.d.ts +241 -0
  56. package/dist/touch-map.js +487 -0
  57. package/dist/wire.d.ts +588 -0
  58. package/dist/wire.js +20 -0
  59. package/package.json +19 -10
  60. package/bin/balladeer.js +0 -136
package/dist/wire.d.ts ADDED
@@ -0,0 +1,588 @@
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.0";
9
+ export declare const CLIENT_HEADER = "x-balladeer-client";
10
+ export declare const CLIENT_HEADER_VALUE = "balladeer/1.0.0";
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
+ export type SetupRepositoryView = Readonly<{
112
+ id: string;
113
+ displayName: string;
114
+ defaultBranch: string;
115
+ status: "enrolling" | "active" | "revoked";
116
+ githubRepositoryId: string | null;
117
+ githubOwnerId: string | null;
118
+ agentConfigured: boolean;
119
+ agentConnectionId: string | null;
120
+ ciIdentityRecorded: boolean;
121
+ ciConfigured: boolean;
122
+ ciValidatedRunCount: number;
123
+ ciFirstValidatedAt: string | null;
124
+ ciLastValidatedAt: string | null;
125
+ eventClasses: readonly string[];
126
+ ciAttestorReleaseId: string | null;
127
+ /**
128
+ * The release this repository was moved off and whose runs are still
129
+ * accepted, and null when the enrollment names only its current release.
130
+ * Publication moves the enrollment; this is the pin the repository's own
131
+ * workflow file may still carry.
132
+ */
133
+ ciSupersededAttestorReleaseId: string | null;
134
+ /** That release's commit, which is the pin the workflow file is leaving. */
135
+ ciSupersededAttestorReleaseSha: string | null;
136
+ /**
137
+ * Candidates still waiting for a named person, and the newest of them. One a
138
+ * person has already accepted, merged, or rejected is in neither, so this
139
+ * command can never send someone back to the browser to agree to something
140
+ * that is already agreed or was refused.
141
+ */
142
+ candidateCount: number;
143
+ latestCandidateId: string | null;
144
+ promiseCount: number;
145
+ /** The promise this repository's first agreement produced, once one exists. */
146
+ firstPromiseId: string | null;
147
+ }>;
148
+ /**
149
+ * How the server says this command should be invoked.
150
+ *
151
+ * `publishedVersion` is null until the package is actually on the registry, and
152
+ * `command` is the invocation to print: the pinned registry form once it is,
153
+ * and the checkout form until then.
154
+ */
155
+ export type SetupClientRelease = Readonly<{
156
+ publishedVersion: string | null;
157
+ command: string;
158
+ }>;
159
+ export type SetupStateResponse = Readonly<{
160
+ workspace: Readonly<{
161
+ id: string;
162
+ name: string;
163
+ slug: string;
164
+ }>;
165
+ repositories: readonly SetupRepositoryView[];
166
+ client: SetupClientRelease;
167
+ }>;
168
+ export type CallerWorkflowResponse = Readonly<{
169
+ workspaceLocator: string;
170
+ defaultBranch: string;
171
+ workflowPath: string;
172
+ workflowYaml: string;
173
+ attestorWorkflowRef: string;
174
+ attestorSha: string;
175
+ attestationOrigin: string;
176
+ variables: readonly Readonly<{
177
+ name: string;
178
+ value: string;
179
+ }>[];
180
+ }>;
181
+ export type AgentPacketResponse = Readonly<{
182
+ connectionId: string;
183
+ repositoryId: string;
184
+ scopes: readonly string[];
185
+ token: string;
186
+ mcpUrl: string;
187
+ configuration: unknown;
188
+ instructions: readonly string[];
189
+ }>;
190
+ export type SetupCandidateResponse = Readonly<{
191
+ candidateId: string;
192
+ repositoryId: string;
193
+ reviewUrl: string;
194
+ }>;
195
+ /** The step objects `--json` emits, one per line, and nothing else on stdout. */
196
+ export type JsonStep = Readonly<{
197
+ step: "explain";
198
+ version: string;
199
+ }> | Readonly<{
200
+ step: "pair";
201
+ status: "pending" | "paired" | "expired" | "denied";
202
+ userCode?: string;
203
+ verificationUri?: string;
204
+ expiresAt?: string;
205
+ workspace?: Readonly<{
206
+ id: string;
207
+ name: string;
208
+ slug: string;
209
+ }>;
210
+ scopes?: readonly DelegatedScope[];
211
+ }> | Readonly<{
212
+ step: "repository";
213
+ status: "added" | "already" | "blocked";
214
+ repository?: string;
215
+ repositoryId?: string;
216
+ defaultBranch?: string;
217
+ /**
218
+ * Present only where this run filled in the GitHub owner id of a
219
+ * repository that was added without one. It is the id now recorded, so an
220
+ * agent reading these lines can see that step 4 became possible here.
221
+ */
222
+ githubOwnerIdRecorded?: string;
223
+ reason?: string;
224
+ }> | Readonly<{
225
+ step: "agent";
226
+ /**
227
+ * `connected` is reported only where Balladeer answered a real call over
228
+ * the credential this step issued. `unproven` is the connection existing
229
+ * and that call not having succeeded: the credential is stored and the
230
+ * files are written, and a caller must not report it as working.
231
+ * `already` is this machine holding a credential for this repository
232
+ * before the run started, which is the only reading of "already" that
233
+ * means the tools will work here.
234
+ */
235
+ status: "connected" | "unproven" | "already" | "blocked";
236
+ repositoryId?: string;
237
+ connectionId?: string;
238
+ /**
239
+ * True where this machine holds a credential for the repository once the
240
+ * step is over. A caller reads this rather than inferring a working
241
+ * machine from the repository being connected: the two are different
242
+ * facts, and only this one says the tools can run from here.
243
+ */
244
+ storedHere?: boolean;
245
+ /**
246
+ * True where the repository already had a live connection issued
247
+ * elsewhere and this run issued a further one for this machine. The
248
+ * existing connection is untouched; connections coexist and each is
249
+ * revoked on its own.
250
+ */
251
+ alreadyConnectedElsewhere?: boolean;
252
+ files?: readonly string[];
253
+ reason?: string;
254
+ /** The tool call that proved the connection, on a `connected` step. */
255
+ provedBy?: string;
256
+ /** What the person or agent does next, on an `unproven` step. */
257
+ nextAction?: string;
258
+ /** Present only where this command refused to change one of these files. */
259
+ mcpConfig?: string;
260
+ conventions?: string;
261
+ }> | Readonly<{
262
+ step: "ci";
263
+ /**
264
+ * `release_upgraded` is this repository having been moved onto a newer
265
+ * Balladeer release. It is deliberately not `connected`: the enrollment has
266
+ * moved, and the repository's own workflow file has not, so the proof is a
267
+ * run on the new pin that has not happened yet.
268
+ */
269
+ status: "identity_recorded" | "connected" | "release_upgraded" | "blocked";
270
+ repositoryId?: string;
271
+ pullRequestUrl?: string;
272
+ validatedRunCount?: number;
273
+ firstValidatedAt?: string;
274
+ /** The workflow is already on the default branch, so there was nothing to commit. */
275
+ workflowInstalled?: boolean;
276
+ /** The release this repository was moved onto, on a `release_upgraded` step. */
277
+ attestorReleaseSha?: string;
278
+ reason?: string;
279
+ }> | Readonly<{
280
+ step: "promise";
281
+ status: "proposed" | "agreed" | "none";
282
+ candidateId?: string;
283
+ promiseId?: string;
284
+ reviewUrl?: string;
285
+ promiseUrl?: string;
286
+ }>
287
+ /**
288
+ * A whole discovered catalog, filed in one run.
289
+ *
290
+ * `proposed` and `requested` are separate numbers on purpose: an agent that
291
+ * read only the count would report eight promises filed from a file of ten.
292
+ * `reviewUrl` is the one page that opens every promise this run filed, and it
293
+ * is absent when nothing was filed rather than pointing at an empty page.
294
+ */
295
+ | Readonly<{
296
+ step: "discovery";
297
+ status: "proposed" | "partial";
298
+ proposed: number;
299
+ requested: number;
300
+ /** The membership every promise in this run was proposed as owned by. */
301
+ ownerId: string;
302
+ candidateIds: readonly string[];
303
+ reviewUrl?: string;
304
+ /**
305
+ * Promises the run never tried, because a connection-level failure would
306
+ * have answered every one of them the same way. Present only when the run
307
+ * stopped early, so a caller can tell "refused" from "not attempted".
308
+ */
309
+ notAttempted?: number;
310
+ failures?: readonly Readonly<{
311
+ promise: number;
312
+ reason: string;
313
+ message: string;
314
+ }>[];
315
+ }>
316
+ /**
317
+ * This repository, read over its own agent connection rather than over a setup
318
+ * session. It is the step that answers "is Balladeer working here" long after
319
+ * the setup session that issued the connection has gone.
320
+ */
321
+ | Readonly<{
322
+ step: "connection";
323
+ status: "agent";
324
+ repositoryId: string;
325
+ repository?: string;
326
+ /** False when the repository has no current attestor release enrolled. */
327
+ enrolled: boolean;
328
+ /** How many active members may be named as a promise's proposed owner. */
329
+ memberCount: number;
330
+ }>
331
+ /**
332
+ * Something about this repository that a person should look at, observed on
333
+ * this machine rather than reported by Balladeer.
334
+ *
335
+ * Today there is one: a promise naming a path this checkout does not have,
336
+ * which is what a rename leaves behind. Balladeer cannot see it, because it
337
+ * holds no token for the repository and reads no file. `considered` and
338
+ * `total` are separate because the run is bounded: a caller has to be able to
339
+ * tell "nothing is missing" from "nothing is missing in the part I read".
340
+ */
341
+ | Readonly<{
342
+ step: "attention";
343
+ reason: "scope_marker_missing";
344
+ observed: number;
345
+ considered: number;
346
+ total: number;
347
+ stoppedEarly: boolean;
348
+ promises: readonly Readonly<{
349
+ promiseId: string;
350
+ missing: readonly string[];
351
+ }>[];
352
+ }>
353
+ /**
354
+ * One promise, read by id, and whether it is currently holding.
355
+ *
356
+ * This is what the copyable line on a broken promise sends an agent to, so it
357
+ * carries exactly the three facts a repair starts from: the commit the failing
358
+ * run checked, the bounded reason it could not report green, and the cases in
359
+ * the agreed meaning that run was checking. A promise that is holding says so
360
+ * and carries none of them.
361
+ */
362
+ | Readonly<{
363
+ step: "promise";
364
+ status: "holding" | "not_holding" | "unreadable";
365
+ promiseId: string;
366
+ title?: string;
367
+ protectionPosture?: string;
368
+ promiseUrl?: string;
369
+ ownerName?: string;
370
+ /** `refuted` is the caught regression; `unknown` is the check not deciding. */
371
+ kind?: "refuted" | "unknown";
372
+ sourceSha?: string;
373
+ reasonCode?: string;
374
+ refutedCaseIds?: readonly string[];
375
+ refutedCaseNote?: string;
376
+ observedAt?: string;
377
+ message?: string;
378
+ }>
379
+ /**
380
+ * The workspace-wide view, named as unavailable rather than reported as empty.
381
+ * Only a live setup session can read every repository, so a caller is told
382
+ * which view it did not get and why.
383
+ */
384
+ | Readonly<{
385
+ step: "workspace";
386
+ status: "unavailable";
387
+ reason: string;
388
+ message: string;
389
+ }>
390
+ /**
391
+ * The four receipts, each true on its own and each with an explicit not-yet
392
+ * form. Never one rolled-up "done": a person reading this has to be able to
393
+ * tell what was set up from what a named person still has to agree to.
394
+ */
395
+ | Readonly<{
396
+ step: "receipt";
397
+ setup: Readonly<{
398
+ complete: boolean;
399
+ sentence: string;
400
+ }>;
401
+ /**
402
+ * `reviewUrl` is a candidate a person still has to read, and `promiseUrl`
403
+ * is one they have agreed to. Never both, and never one under the other's
404
+ * name: a caller that followed `reviewUrl` to an already-agreed promise
405
+ * would send its person to agree to it a second time.
406
+ */
407
+ firstPromise: Readonly<{
408
+ complete: boolean;
409
+ sentence: string;
410
+ reviewUrl?: string;
411
+ promiseUrl?: string;
412
+ }>;
413
+ protection: Readonly<{
414
+ complete: boolean;
415
+ sentence: string;
416
+ }>;
417
+ /**
418
+ * Which credential keeps working once this run is over. Setting Balladeer
419
+ * up needs a session that ends; proposing and reporting from then on run
420
+ * over each repository's agent connection, which does not.
421
+ */
422
+ connection: Readonly<{
423
+ complete: boolean;
424
+ sentence: string;
425
+ }>;
426
+ repositories: readonly Readonly<{
427
+ repository: string;
428
+ agent: string;
429
+ ci: string;
430
+ }>[];
431
+ }>
432
+ /**
433
+ * Which promises this checkout seals, and which of them a pending change
434
+ * would break the seal of.
435
+ *
436
+ * `broken` is the count that matters and `promises` is the bounded list a
437
+ * person or an agent acts on. The reseal line travels with each row because
438
+ * the alternative is an agent assembling one from a path it half remembers.
439
+ */
440
+ | Readonly<{
441
+ step: "seals";
442
+ broken: number;
443
+ promises: readonly Readonly<{
444
+ promiseId: string;
445
+ sealedPath: string;
446
+ title?: string;
447
+ owner?: string;
448
+ promiseUrl?: string;
449
+ changedPaths: readonly string[];
450
+ resealWith: string;
451
+ }>[];
452
+ changed: boolean;
453
+ }>
454
+ /** The sealed directories this checkout carries, listed before anybody edits one. */
455
+ | Readonly<{
456
+ step: "sealed_paths";
457
+ total: number;
458
+ shown: readonly Readonly<{
459
+ promiseId: string;
460
+ sealedPath: string;
461
+ title?: string;
462
+ }>[];
463
+ }>
464
+ /** The optional pre-push hook, written only when somebody asked for it by name. */
465
+ | Readonly<{
466
+ step: "hook";
467
+ path: string;
468
+ changed: boolean;
469
+ }>
470
+ /**
471
+ * Every way this command can fail, in the shape a caller can branch on.
472
+ *
473
+ * `--json` is the mode an agent picks, and an agent that receives a bare exit
474
+ * code cannot tell a person what went wrong or whether the run left anything
475
+ * behind. `reason` is the bounded code, `message` is the same sentence a person
476
+ * would have read, and `changed` says whether this run altered anything.
477
+ */
478
+ | Readonly<{
479
+ step: "error";
480
+ reason: string;
481
+ message: string;
482
+ changed: boolean;
483
+ exitCode: number;
484
+ }>
485
+ /**
486
+ * One invited address, one line, whatever happened to it.
487
+ *
488
+ * A run that invites four people emits four of these, because a caller has to
489
+ * be able to tell a person which of the four emails went. Never one rolled-up
490
+ * status: three sent and one refused is not "partly done", it is three people
491
+ * who will get an email and one who will not.
492
+ */
493
+ | Readonly<{
494
+ step: "invitation";
495
+ status: "sent" | "refused";
496
+ email: string;
497
+ role: "admin" | "contributor" | "viewer";
498
+ workspace?: string;
499
+ expiresAt?: string | null;
500
+ reason?: string;
501
+ message?: string;
502
+ }>
503
+ /**
504
+ * What this machine can see and could add, so an agent has something to offer
505
+ * rather than asking a person to type repository names from memory.
506
+ *
507
+ * `enrolled` is read from the workspace, and absent entirely when no setup
508
+ * session could read it: a listing that quietly showed every repository as not
509
+ * enrolled would invite an agent to add the ones already there.
510
+ */
511
+ | Readonly<{
512
+ step: "repositories";
513
+ /** Where the names came from: the GitHub CLI, or this checkout's origin. */
514
+ source: "gh" | "origin" | "none";
515
+ workspaceKnown: boolean;
516
+ repositories: readonly Readonly<{
517
+ repository: string;
518
+ /** True for the repository this directory's origin remote names. */
519
+ here: boolean;
520
+ enrolled?: boolean;
521
+ }>[];
522
+ reason?: string;
523
+ }>
524
+ /**
525
+ * What the touch map now holds, after measuring this checkout's verifiers.
526
+ *
527
+ * `bytes` is here because the map is a file a person keeps, and `changed`
528
+ * because a second run over an unchanged repository rewrites nothing. Neither
529
+ * this step nor the file it describes ever leaves the machine: the map names
530
+ * a customer's own files, which Balladeer promises never to receive.
531
+ */
532
+ | Readonly<{
533
+ step: "touch_map";
534
+ promises: number;
535
+ files: number;
536
+ /** Promises whose verifier could not be measured, so nothing is mapped to them. */
537
+ unmapped: number;
538
+ bytes: number;
539
+ changed: boolean;
540
+ /** Present only when a size cap cut the map, so a short map is never read as complete. */
541
+ truncated?: true;
542
+ }>
543
+ /**
544
+ * Which promises the named paths touch, read out of the local map.
545
+ *
546
+ * `mapped` false is the answer when there is no map to read, and it is not an
547
+ * error: a repository nobody has measured yet has no answer to give, and a
548
+ * caller that treated that as a failure would break the hook it sits in.
549
+ * `stale` on a row says the verifier has changed since the map was built, so
550
+ * the answer is the last measured one rather than a current one.
551
+ */
552
+ | Readonly<{
553
+ step: "affected";
554
+ mapped: boolean;
555
+ generatedAt?: string;
556
+ truncated?: true;
557
+ paths: readonly Readonly<{
558
+ path: string;
559
+ promises: readonly Readonly<{
560
+ promiseId: string;
561
+ stale: boolean;
562
+ title?: string;
563
+ claim?: string;
564
+ }>[];
565
+ }>[];
566
+ changed: boolean;
567
+ }>
568
+ /**
569
+ * What `setup --refresh` rewrote, and what it left alone.
570
+ *
571
+ * `current` is the honest answer for a repository whose files already say the
572
+ * right thing, and it is not the same as `updated`: an agent that reported a
573
+ * repair on a run that changed nothing would be telling a person their stale
574
+ * install was fixed.
575
+ */
576
+ | Readonly<{
577
+ step: "refresh";
578
+ status: "updated" | "current" | "blocked";
579
+ repositoryId?: string;
580
+ files: readonly string[];
581
+ conventionsVersion?: number;
582
+ previousConventionsVersion?: number;
583
+ reason?: string;
584
+ message?: string;
585
+ }> | Readonly<{
586
+ step: "whoami";
587
+ session: PairSessionSummary;
588
+ }>;
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.0";
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:";
package/package.json CHANGED
@@ -1,21 +1,30 @@
1
1
  {
2
2
  "name": "balladeer",
3
- "version": "0.0.4",
4
- "description": "Installer for the Balladeer client (decision memory for engineering teams and their AI agents): downloads the sha256-verified client from get.balladeer.ai and installs it globally.",
5
- "license": "SEE LICENSE IN LICENSE",
6
- "homepage": "https://balladeer.ai",
7
- "engines": {
8
- "node": ">=20"
3
+ "version": "1.0.0",
4
+ "description": "Set up Balladeer from your terminal, or from a coding agent's.",
5
+ "license": "Apache-2.0",
6
+ "private": false,
7
+ "//": "repository and homepage are deliberately absent: the source repository is private, and a registry page linking somewhere a reader cannot open is worse than one that links nowhere. Add both when the repository is public, and delete this note when you do.",
8
+ "type": "module",
9
+ "publishConfig": {
10
+ "access": "public"
9
11
  },
10
12
  "bin": {
11
- "balladeer": "bin/balladeer.js"
13
+ "balladeer": "dist/cli.js"
12
14
  },
13
15
  "files": [
14
- "bin",
16
+ "dist",
15
17
  "LICENSE",
16
18
  "README.md"
17
19
  ],
18
- "publishConfig": {
19
- "access": "public"
20
+ "engines": {
21
+ "node": ">=22.0.0"
22
+ },
23
+ "scripts": {
24
+ "build": "tsc -p tsconfig.json",
25
+ "typecheck": "tsc -p tsconfig.json --noEmit"
26
+ },
27
+ "devDependencies": {
28
+ "typescript": "5.9.3"
20
29
  }
21
30
  }