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.
Files changed (47) hide show
  1. package/README.md +53 -32
  2. package/dist/agent.d.ts +5 -0
  3. package/dist/agent.js +13 -1
  4. package/dist/cli.d.ts +16 -0
  5. package/dist/cli.js +181 -24
  6. package/dist/client.d.ts +24 -2
  7. package/dist/client.js +34 -3
  8. package/dist/commands/affected.js +8 -7
  9. package/dist/commands/check-seals.js +4 -4
  10. package/dist/commands/discover.js +16 -7
  11. package/dist/commands/explain.d.ts +1 -1
  12. package/dist/commands/explain.js +1 -1
  13. package/dist/commands/invite.js +2 -1
  14. package/dist/commands/prepare.d.ts +74 -0
  15. package/dist/commands/prepare.js +218 -0
  16. package/dist/commands/propose.d.ts +10 -0
  17. package/dist/commands/propose.js +29 -6
  18. package/dist/commands/repositories.js +1 -0
  19. package/dist/commands/session.d.ts +35 -0
  20. package/dist/commands/session.js +131 -0
  21. package/dist/commands/setup.d.ts +29 -0
  22. package/dist/commands/setup.js +302 -92
  23. package/dist/commands/status.d.ts +16 -0
  24. package/dist/commands/status.js +106 -23
  25. package/dist/commands/touch-map.js +2 -2
  26. package/dist/commands/whoami.js +2 -1
  27. package/dist/conventions.d.ts +9 -1
  28. package/dist/conventions.js +9 -1
  29. package/dist/copy.d.ts +83 -7
  30. package/dist/copy.js +226 -29
  31. package/dist/desktop-config.d.ts +85 -0
  32. package/dist/desktop-config.js +217 -0
  33. package/dist/git.d.ts +15 -0
  34. package/dist/git.js +23 -0
  35. package/dist/legacy.d.ts +41 -0
  36. package/dist/legacy.js +143 -0
  37. package/dist/local-time.d.ts +66 -0
  38. package/dist/local-time.js +84 -0
  39. package/dist/mcp-config.d.ts +10 -0
  40. package/dist/mcp-config.js +8 -4
  41. package/dist/session.d.ts +84 -0
  42. package/dist/session.js +135 -0
  43. package/dist/store.d.ts +11 -1
  44. package/dist/store.js +18 -6
  45. package/dist/wire.d.ts +95 -4
  46. package/dist/wire.js +2 -1
  47. 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, "credentials.json");
129
- const temporary = join(directory, `.credentials.${randomBytes(8).toString("hex")}.tmp`);
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, `${JSON.stringify(credentials, null, 2)}\n`);
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.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.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.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";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "balladeer",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Set up Balladeer from your terminal, or from a coding agent's.",
5
5
  "license": "Apache-2.0",
6
6
  "private": false,