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.
- package/LICENSE +200 -5
- package/README.md +154 -68
- package/dist/agent.d.ts +126 -0
- package/dist/agent.js +209 -0
- package/dist/cli.d.ts +34 -0
- package/dist/cli.js +392 -0
- package/dist/client.d.ts +44 -0
- package/dist/client.js +114 -0
- package/dist/commands/affected.d.ts +22 -0
- package/dist/commands/affected.js +122 -0
- package/dist/commands/check-seals.d.ts +37 -0
- package/dist/commands/check-seals.js +289 -0
- package/dist/commands/discover.d.ts +68 -0
- package/dist/commands/discover.js +395 -0
- package/dist/commands/explain.d.ts +35 -0
- package/dist/commands/explain.js +90 -0
- package/dist/commands/invite.d.ts +24 -0
- package/dist/commands/invite.js +197 -0
- package/dist/commands/mcp.d.ts +65 -0
- package/dist/commands/mcp.js +202 -0
- package/dist/commands/propose.d.ts +59 -0
- package/dist/commands/propose.js +262 -0
- package/dist/commands/repositories.d.ts +18 -0
- package/dist/commands/repositories.js +185 -0
- package/dist/commands/setup.d.ts +75 -0
- package/dist/commands/setup.js +1471 -0
- package/dist/commands/status.d.ts +35 -0
- package/dist/commands/status.js +482 -0
- package/dist/commands/touch-map.d.ts +42 -0
- package/dist/commands/touch-map.js +251 -0
- package/dist/commands/whoami.d.ts +8 -0
- package/dist/commands/whoami.js +79 -0
- package/dist/conventions.d.ts +69 -0
- package/dist/conventions.js +175 -0
- package/dist/copy.d.ts +148 -0
- package/dist/copy.js +459 -0
- package/dist/currency.d.ts +31 -0
- package/dist/currency.js +72 -0
- package/dist/gh.d.ts +80 -0
- package/dist/gh.js +188 -0
- package/dist/git.d.ts +76 -0
- package/dist/git.js +203 -0
- package/dist/markers.d.ts +76 -0
- package/dist/markers.js +125 -0
- package/dist/mcp-config.d.ts +99 -0
- package/dist/mcp-config.js +230 -0
- package/dist/release.d.ts +55 -0
- package/dist/release.js +67 -0
- package/dist/repository.d.ts +8 -0
- package/dist/repository.js +32 -0
- package/dist/seals.d.ts +48 -0
- package/dist/seals.js +112 -0
- package/dist/store.d.ts +98 -0
- package/dist/store.js +225 -0
- package/dist/touch-map.d.ts +241 -0
- package/dist/touch-map.js +487 -0
- package/dist/wire.d.ts +588 -0
- package/dist/wire.js +20 -0
- package/package.json +19 -10
- 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
|
-
"description": "
|
|
5
|
-
"license": "
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
|
|
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": "
|
|
13
|
+
"balladeer": "dist/cli.js"
|
|
12
14
|
},
|
|
13
15
|
"files": [
|
|
14
|
-
"
|
|
16
|
+
"dist",
|
|
15
17
|
"LICENSE",
|
|
16
18
|
"README.md"
|
|
17
19
|
],
|
|
18
|
-
"
|
|
19
|
-
"
|
|
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
|
}
|