balladeer 1.0.0 → 1.0.2
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/README.md +53 -32
- package/dist/agent.d.ts +5 -0
- package/dist/agent.js +13 -1
- package/dist/cli.d.ts +16 -0
- package/dist/cli.js +181 -24
- package/dist/client.d.ts +24 -2
- package/dist/client.js +34 -3
- package/dist/commands/affected.js +8 -7
- package/dist/commands/check-seals.js +4 -4
- package/dist/commands/discover.js +16 -7
- package/dist/commands/explain.d.ts +1 -1
- package/dist/commands/explain.js +1 -1
- package/dist/commands/invite.js +2 -1
- package/dist/commands/prepare.d.ts +74 -0
- package/dist/commands/prepare.js +218 -0
- package/dist/commands/propose.d.ts +10 -0
- package/dist/commands/propose.js +29 -6
- package/dist/commands/repositories.js +1 -0
- package/dist/commands/session.d.ts +35 -0
- package/dist/commands/session.js +131 -0
- package/dist/commands/setup.d.ts +29 -0
- package/dist/commands/setup.js +302 -92
- package/dist/commands/status.d.ts +16 -0
- package/dist/commands/status.js +106 -23
- package/dist/commands/touch-map.js +2 -2
- package/dist/commands/whoami.js +2 -1
- package/dist/conventions.d.ts +9 -1
- package/dist/conventions.js +9 -1
- package/dist/copy.d.ts +83 -7
- package/dist/copy.js +226 -29
- package/dist/desktop-config.d.ts +85 -0
- package/dist/desktop-config.js +217 -0
- package/dist/git.d.ts +15 -0
- package/dist/git.js +23 -0
- package/dist/legacy.d.ts +41 -0
- package/dist/legacy.js +143 -0
- package/dist/local-time.d.ts +66 -0
- package/dist/local-time.js +84 -0
- package/dist/mcp-config.d.ts +10 -0
- package/dist/mcp-config.js +8 -4
- package/dist/session.d.ts +84 -0
- package/dist/session.js +135 -0
- package/dist/store.d.ts +11 -1
- package/dist/store.js +18 -6
- package/dist/wire.d.ts +95 -4
- package/dist/wire.js +2 -1
- package/package.json +1 -1
package/dist/store.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { DelegatedScope } from "./wire.js";
|
|
2
2
|
export declare class StoreError extends Error {
|
|
3
3
|
readonly code: string;
|
|
4
|
-
constructor(code: string, message: string);
|
|
4
|
+
constructor(code: string, message: string, options?: ErrorOptions);
|
|
5
5
|
}
|
|
6
6
|
export type PendingPairing = Readonly<{
|
|
7
7
|
controlPlane: string;
|
|
@@ -81,6 +81,16 @@ export declare function readCredentials(environment?: NodeJS.ProcessEnv): Creden
|
|
|
81
81
|
* its mode is set on the open descriptor, and it is flushed before the rename.
|
|
82
82
|
*/
|
|
83
83
|
export declare function writeCredentials(credentials: Credentials, environment?: NodeJS.ProcessEnv): void;
|
|
84
|
+
/**
|
|
85
|
+
* One private file in the store directory, written the same careful way the
|
|
86
|
+
* credential is.
|
|
87
|
+
*
|
|
88
|
+
* Pulled out of `writeCredentials` rather than copied beside it: the session
|
|
89
|
+
* marker written next to the credential is not a secret, but it is written into
|
|
90
|
+
* the same directory by the same command, and a second, sloppier writer there
|
|
91
|
+
* would be the one an attacker pre-plants a symlink for.
|
|
92
|
+
*/
|
|
93
|
+
export declare function writeStoreFile(fileName: string, contents: string, environment?: NodeJS.ProcessEnv): void;
|
|
84
94
|
/** Origins, not URL strings, so `https://host/../` cannot alias a stored entry. */
|
|
85
95
|
export declare function normalizeControlPlane(value: string): string;
|
|
86
96
|
/** At most one pending pairing per control plane, newest wins. */
|
package/dist/store.js
CHANGED
|
@@ -5,8 +5,8 @@ import { homedir } from "node:os";
|
|
|
5
5
|
import { dirname, join, parse, resolve } from "node:path";
|
|
6
6
|
export class StoreError extends Error {
|
|
7
7
|
code;
|
|
8
|
-
constructor(code, message) {
|
|
9
|
-
super(message);
|
|
8
|
+
constructor(code, message, options) {
|
|
9
|
+
super(message, options);
|
|
10
10
|
this.name = "StoreError";
|
|
11
11
|
this.code = code;
|
|
12
12
|
}
|
|
@@ -124,14 +124,26 @@ export function readCredentials(environment = process.env) {
|
|
|
124
124
|
* its mode is set on the open descriptor, and it is flushed before the rename.
|
|
125
125
|
*/
|
|
126
126
|
export function writeCredentials(credentials, environment = process.env) {
|
|
127
|
+
writeStoreFile("credentials.json", `${JSON.stringify(credentials, null, 2)}\n`, environment);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* One private file in the store directory, written the same careful way the
|
|
131
|
+
* credential is.
|
|
132
|
+
*
|
|
133
|
+
* Pulled out of `writeCredentials` rather than copied beside it: the session
|
|
134
|
+
* marker written next to the credential is not a secret, but it is written into
|
|
135
|
+
* the same directory by the same command, and a second, sloppier writer there
|
|
136
|
+
* would be the one an attacker pre-plants a symlink for.
|
|
137
|
+
*/
|
|
138
|
+
export function writeStoreFile(fileName, contents, environment = process.env) {
|
|
127
139
|
const directory = ensureStoreDirectory(environment);
|
|
128
|
-
const path = join(directory,
|
|
129
|
-
const temporary = join(directory,
|
|
140
|
+
const path = join(directory, fileName);
|
|
141
|
+
const temporary = join(directory, `.${fileName}.${randomBytes(8).toString("hex")}.tmp`);
|
|
130
142
|
let descriptor;
|
|
131
143
|
try {
|
|
132
144
|
descriptor = openSync(temporary, "wx", 0o600);
|
|
133
145
|
fchmodSync(descriptor, 0o600);
|
|
134
|
-
writeSync(descriptor,
|
|
146
|
+
writeSync(descriptor, contents);
|
|
135
147
|
fsyncSync(descriptor);
|
|
136
148
|
closeSync(descriptor);
|
|
137
149
|
descriptor = undefined;
|
|
@@ -154,7 +166,7 @@ export function writeCredentials(credentials, environment = process.env) {
|
|
|
154
166
|
}
|
|
155
167
|
if (error instanceof StoreError)
|
|
156
168
|
throw error;
|
|
157
|
-
throw new StoreError("credential_store_unwritable", `Balladeer could not write ${path}: ${error instanceof Error ? error.message : "unknown reason"}
|
|
169
|
+
throw new StoreError("credential_store_unwritable", `Balladeer could not write ${path}: ${error instanceof Error ? error.message : "unknown reason"}`, { cause: error });
|
|
158
170
|
}
|
|
159
171
|
}
|
|
160
172
|
/** Origins, not URL strings, so `https://host/../` cannot alias a stored entry. */
|
package/dist/wire.d.ts
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
* runtime dependency at all: Node 22 builtins and global fetch, nothing else.
|
|
6
6
|
* A contract test compares the scope list below against the server's.
|
|
7
7
|
*/
|
|
8
|
-
export declare const CLI_VERSION = "1.0.
|
|
8
|
+
export declare const CLI_VERSION = "1.0.2";
|
|
9
|
+
export declare const CLI_INVOCATION = "npx -y balladeer@latest";
|
|
9
10
|
export declare const CLIENT_HEADER = "x-balladeer-client";
|
|
10
|
-
export declare const CLIENT_HEADER_VALUE = "balladeer/1.0.
|
|
11
|
+
export declare const CLIENT_HEADER_VALUE = "balladeer/1.0.2";
|
|
11
12
|
export declare const DEFAULT_CONTROL_PLANE = "https://envelopes.balladeer.ai";
|
|
12
13
|
export type DelegatedScope = "repository:enroll" | "agent:issue" | "ci:connect" | "workspace:invite" | "candidate:propose";
|
|
13
14
|
export declare const DELEGATED_SCOPES: readonly DelegatedScope[];
|
|
@@ -108,6 +109,21 @@ export type SetupInvitationResponse = Readonly<{
|
|
|
108
109
|
export type SetupCiRequest = Readonly<{
|
|
109
110
|
eventClasses: readonly ("push" | "pull_request")[];
|
|
110
111
|
}>;
|
|
112
|
+
/**
|
|
113
|
+
* The GitHub login this machine's `gh` is signed in as.
|
|
114
|
+
*
|
|
115
|
+
* Sent so a run started by this person can be shown under their name instead of
|
|
116
|
+
* a handle nobody has learned. No token is sent, here or anywhere: the login is
|
|
117
|
+
* read from the GitHub CLI they already signed in to, and Balladeer stores it as
|
|
118
|
+
* something they said rather than something anyone checked. It grants nothing.
|
|
119
|
+
* An empty string clears it.
|
|
120
|
+
*/
|
|
121
|
+
export type SetupGithubLoginRequest = Readonly<{
|
|
122
|
+
login: string;
|
|
123
|
+
}>;
|
|
124
|
+
export type SetupGithubLoginResponse = Readonly<{
|
|
125
|
+
claimedLogin: string | null;
|
|
126
|
+
}>;
|
|
111
127
|
export type SetupRepositoryView = Readonly<{
|
|
112
128
|
id: string;
|
|
113
129
|
displayName: string;
|
|
@@ -193,9 +209,32 @@ export type SetupCandidateResponse = Readonly<{
|
|
|
193
209
|
reviewUrl: string;
|
|
194
210
|
}>;
|
|
195
211
|
/** The step objects `--json` emits, one per line, and nothing else on stdout. */
|
|
212
|
+
/**
|
|
213
|
+
* What a run did about Claude desktop chat, on the two steps that can touch it.
|
|
214
|
+
*
|
|
215
|
+
* Its own type because both `agent` and `refresh` report it and they must report
|
|
216
|
+
* it identically: an agent reading either step reads the same three fields.
|
|
217
|
+
*/
|
|
218
|
+
export type ClaudeDesktopStep = Readonly<{
|
|
219
|
+
status: "connected" | "current" | "absent" | "unsupported" | "refused";
|
|
220
|
+
key?: string;
|
|
221
|
+
path?: string;
|
|
222
|
+
reason?: string;
|
|
223
|
+
}>;
|
|
196
224
|
export type JsonStep = Readonly<{
|
|
197
225
|
step: "explain";
|
|
198
226
|
version: string;
|
|
227
|
+
}> | Readonly<{
|
|
228
|
+
/**
|
|
229
|
+
* The GitHub login this machine was signed in as, handed over so a run
|
|
230
|
+
* this person starts can be shown under their name. `status` says what
|
|
231
|
+
* happened: `claimed` when it was recorded, `unavailable` when `gh` could
|
|
232
|
+
* not name a login, and `refused` when the control plane would not take
|
|
233
|
+
* it. None of the three stops setup.
|
|
234
|
+
*/
|
|
235
|
+
step: "github_login";
|
|
236
|
+
status: "claimed" | "unavailable" | "refused";
|
|
237
|
+
login?: string;
|
|
199
238
|
}> | Readonly<{
|
|
200
239
|
step: "pair";
|
|
201
240
|
status: "pending" | "paired" | "expired" | "denied";
|
|
@@ -210,7 +249,7 @@ export type JsonStep = Readonly<{
|
|
|
210
249
|
scopes?: readonly DelegatedScope[];
|
|
211
250
|
}> | Readonly<{
|
|
212
251
|
step: "repository";
|
|
213
|
-
status: "added" | "already" | "blocked";
|
|
252
|
+
status: "added" | "already" | "not_connected" | "blocked";
|
|
214
253
|
repository?: string;
|
|
215
254
|
repositoryId?: string;
|
|
216
255
|
defaultBranch?: string;
|
|
@@ -258,6 +297,15 @@ export type JsonStep = Readonly<{
|
|
|
258
297
|
/** Present only where this command refused to change one of these files. */
|
|
259
298
|
mcpConfig?: string;
|
|
260
299
|
conventions?: string;
|
|
300
|
+
/**
|
|
301
|
+
* What happened to Claude desktop chat, present only where this run had
|
|
302
|
+
* something to say about it. `connected` and `current` carry the key this
|
|
303
|
+
* repository's server is named under and the file it was written to;
|
|
304
|
+
* `absent`, `unsupported` and `refused` carry the sentence saying why
|
|
305
|
+
* nothing was written, and are only ever reported where the flag asked for
|
|
306
|
+
* it or the file itself refused the merge.
|
|
307
|
+
*/
|
|
308
|
+
claudeDesktop?: ClaudeDesktopStep;
|
|
261
309
|
}> | Readonly<{
|
|
262
310
|
step: "ci";
|
|
263
311
|
/**
|
|
@@ -266,7 +314,7 @@ export type JsonStep = Readonly<{
|
|
|
266
314
|
* moved, and the repository's own workflow file has not, so the proof is a
|
|
267
315
|
* run on the new pin that has not happened yet.
|
|
268
316
|
*/
|
|
269
|
-
status: "identity_recorded" | "connected" | "release_upgraded" | "blocked";
|
|
317
|
+
status: "identity_recorded" | "connected" | "release_upgraded" | "not_changed" | "blocked";
|
|
270
318
|
repositoryId?: string;
|
|
271
319
|
pullRequestUrl?: string;
|
|
272
320
|
validatedRunCount?: number;
|
|
@@ -276,6 +324,10 @@ export type JsonStep = Readonly<{
|
|
|
276
324
|
/** The release this repository was moved onto, on a `release_upgraded` step. */
|
|
277
325
|
attestorReleaseSha?: string;
|
|
278
326
|
reason?: string;
|
|
327
|
+
}> | Readonly<{
|
|
328
|
+
step: "warning";
|
|
329
|
+
code: "enforcement_unavailable";
|
|
330
|
+
message: string;
|
|
279
331
|
}> | Readonly<{
|
|
280
332
|
step: "promise";
|
|
281
333
|
status: "proposed" | "agreed" | "none";
|
|
@@ -284,6 +336,20 @@ export type JsonStep = Readonly<{
|
|
|
284
336
|
reviewUrl?: string;
|
|
285
337
|
promiseUrl?: string;
|
|
286
338
|
}>
|
|
339
|
+
/**
|
|
340
|
+
* The one-time qualification setup, minted and written to disk.
|
|
341
|
+
*
|
|
342
|
+
* `supersededPackets` is the number of earlier packets this run invalidated,
|
|
343
|
+
* so a caller can tell an ordinary first preparation from a deliberate
|
|
344
|
+
* replacement without reading prose. It is zero on the ordinary case.
|
|
345
|
+
*/
|
|
346
|
+
| Readonly<{
|
|
347
|
+
step: "qualification";
|
|
348
|
+
status: "prepared";
|
|
349
|
+
promiseId: string;
|
|
350
|
+
metadataPath: string;
|
|
351
|
+
supersededPackets: number;
|
|
352
|
+
}>
|
|
287
353
|
/**
|
|
288
354
|
* A whole discovered catalog, filed in one run.
|
|
289
355
|
*
|
|
@@ -482,6 +548,30 @@ export type JsonStep = Readonly<{
|
|
|
482
548
|
changed: boolean;
|
|
483
549
|
exitCode: number;
|
|
484
550
|
}>
|
|
551
|
+
/**
|
|
552
|
+
* The working session an agent reads and commits under.
|
|
553
|
+
*
|
|
554
|
+
* `minted` is the difference between "here is a new session" and "you already
|
|
555
|
+
* have one open": an agent that asked twice must reuse the second answer
|
|
556
|
+
* rather than treat it as a second session, or its reads and its commit end
|
|
557
|
+
* up under two ids and the join breaks by the act of asking for it.
|
|
558
|
+
*/
|
|
559
|
+
| Readonly<{
|
|
560
|
+
step: "session";
|
|
561
|
+
sessionId: string;
|
|
562
|
+
startedAt: string;
|
|
563
|
+
minted: boolean;
|
|
564
|
+
/** The exact line to write into the commit or the pull-request body. */
|
|
565
|
+
trailer: string;
|
|
566
|
+
changed: boolean;
|
|
567
|
+
}>
|
|
568
|
+
/** One commit recorded as written by one session. */
|
|
569
|
+
| Readonly<{
|
|
570
|
+
step: "session_commit";
|
|
571
|
+
sessionId: string;
|
|
572
|
+
commitSha: string;
|
|
573
|
+
changed: boolean;
|
|
574
|
+
}>
|
|
485
575
|
/**
|
|
486
576
|
* One invited address, one line, whatever happened to it.
|
|
487
577
|
*
|
|
@@ -582,6 +672,7 @@ export type JsonStep = Readonly<{
|
|
|
582
672
|
previousConventionsVersion?: number;
|
|
583
673
|
reason?: string;
|
|
584
674
|
message?: string;
|
|
675
|
+
claudeDesktop?: ClaudeDesktopStep;
|
|
585
676
|
}> | Readonly<{
|
|
586
677
|
step: "whoami";
|
|
587
678
|
session: PairSessionSummary;
|
package/dist/wire.js
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* runtime dependency at all: Node 22 builtins and global fetch, nothing else.
|
|
6
6
|
* A contract test compares the scope list below against the server's.
|
|
7
7
|
*/
|
|
8
|
-
export const CLI_VERSION = "1.0.
|
|
8
|
+
export const CLI_VERSION = "1.0.2";
|
|
9
|
+
export const CLI_INVOCATION = "npx -y balladeer@latest";
|
|
9
10
|
export const CLIENT_HEADER = "x-balladeer-client";
|
|
10
11
|
export const CLIENT_HEADER_VALUE = `balladeer/${CLI_VERSION}`;
|
|
11
12
|
export const DEFAULT_CONTROL_PLANE = "https://envelopes.balladeer.ai";
|