balladeer 1.0.1 → 1.0.3
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 +75 -41
- package/dist/agent.d.ts +5 -0
- package/dist/agent.js +13 -1
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +78 -24
- package/dist/client.d.ts +2 -2
- package/dist/client.js +7 -4
- package/dist/codex-config.d.ts +7 -0
- package/dist/codex-config.js +159 -0
- package/dist/commands/affected.js +6 -6
- package/dist/commands/check-seals.js +4 -4
- package/dist/commands/discover.js +5 -4
- package/dist/commands/prepare.js +2 -1
- package/dist/commands/propose.js +5 -4
- package/dist/commands/repositories.js +1 -0
- package/dist/commands/session.js +17 -4
- package/dist/commands/setup.d.ts +8 -0
- package/dist/commands/setup.js +215 -88
- package/dist/commands/status.js +41 -18
- package/dist/commands/touch-map.js +2 -2
- package/dist/conventions.d.ts +2 -1
- package/dist/conventions.js +2 -2
- package/dist/copy.d.ts +8 -8
- package/dist/copy.js +60 -39
- package/dist/store.d.ts +3 -1
- package/dist/store.js +3 -3
- package/dist/wire.d.ts +9 -4
- package/dist/wire.js +2 -1
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -23,8 +23,9 @@ npx -y balladeer@latest setup
|
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
Use `@latest` so a new session picks up the current command. Balladeer tells an older command when
|
|
26
|
-
an update is available. Run `balladeer setup --refresh` to update an existing
|
|
27
|
-
and its instructions without replacing other tools' settings.
|
|
26
|
+
an update is available. Run `npx -y balladeer@latest setup --refresh` to update an existing
|
|
27
|
+
repository connection and its instructions without replacing other tools' settings. For Codex, keep
|
|
28
|
+
`--client codex` on the refresh command: `npx -y balladeer@latest setup --client codex --refresh`.
|
|
28
29
|
|
|
29
30
|
Run it inside the repository you want to protect. It prints the boundary explanation below, then one
|
|
30
31
|
step at a time, and it exits within seconds rather than blocking on anything.
|
|
@@ -34,10 +35,16 @@ connects this coding agent, records the CI identity and opens the pull request w
|
|
|
34
35
|
and hands you the playbook for discovering this repository's own promises. The two things it never
|
|
35
36
|
does are sign you in and agree to a promise; both of those are yours, in a browser.
|
|
36
37
|
|
|
38
|
+
If you are joining an existing workspace, use `npx -y balladeer@latest setup --existing`. It
|
|
39
|
+
completes browser onboarding without adding a repository or changing CI. When this checkout is
|
|
40
|
+
already enrolled it connects this machine and writes its MCP configuration; when it is absent, setup
|
|
41
|
+
leaves the checkout unchanged and Balladeer shows that enforcement tracking is unavailable until an
|
|
42
|
+
administrator connects a repository.
|
|
43
|
+
|
|
37
44
|
## Requirements
|
|
38
45
|
|
|
39
|
-
- Node 22 or newer. The command
|
|
40
|
-
|
|
46
|
+
- Node 22 or newer. The command uses Node builtins and the pinned `@iarna/toml` parser to preserve
|
|
47
|
+
Codex project configuration safely.
|
|
41
48
|
- `git`, and the GitHub CLI (`gh`) signed in to an account with push access to the repository. Your
|
|
42
49
|
`gh` is how the command reads the repository's ids and opens the pull request that carries the
|
|
43
50
|
workflow. Balladeer itself never holds a GitHub credential.
|
|
@@ -58,19 +65,20 @@ test output.
|
|
|
58
65
|
The verify job in your CI runs your tests with your checkout and has no Balladeer credential. A
|
|
59
66
|
separate publish job, with no checkout, sends only those outcomes and hashes.
|
|
60
67
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
Browser approval creates a temporary setup session limited by that person's workspace role. A
|
|
69
|
+
contributor can connect this machine's coding agent to an already-enrolled repository and propose
|
|
70
|
+
promises; an administrator can also add repositories, connect CI, and invite teammates. The session
|
|
71
|
+
cannot approve, activate, rotate, revoke, or remove anything. It lasts a day, each use buys it
|
|
72
|
+
another day, and it is gone seven days after approval however much it was used. When setup connects
|
|
73
|
+
an enrolled repository it also issues that machine a coding-agent credential. That connection has no
|
|
74
|
+
expiry: it lets the agent read the promises your team has approved, propose new ones, and propose
|
|
75
|
+
replacing, retiring, excepting, or reassigning one you already have; every one of those waits for a
|
|
76
|
+
named person to decide it. It can also carry out one of those acts for you, and only this way: you
|
|
77
|
+
open a Balladeer page in your own browser, read what the act is, sign it off there, and read back
|
|
78
|
+
the one-time code that page gives you. That code covers that one act on that one thing, once, and
|
|
79
|
+
Balladeer records you as the person who took it and the connection as the messenger. Without a code
|
|
80
|
+
you signed, the connection cannot approve, activate, grant, transfer, retire, or delete anything.
|
|
81
|
+
You can revoke either one at any time in Balladeer, and the setup session also ends on its own.
|
|
74
82
|
|
|
75
83
|
Adding the workflow puts a check on pull requests into your default branch. That check is advisory
|
|
76
84
|
on Balladeer's side; whether it blocks a merge is your own branch protection.
|
|
@@ -93,10 +101,10 @@ with your checkout and no Balladeer credential.
|
|
|
93
101
|
It writes, locally and visibly: your credentials to `$XDG_CONFIG_HOME/balladeer/credentials.json`,
|
|
94
102
|
or `~/.config/balladeer/credentials.json`, with mode 600 inside a directory with mode 700, and it
|
|
95
103
|
refuses to write them anywhere a commit could pick them up; a `balladeer` entry in the repository's
|
|
96
|
-
`.mcp.json
|
|
97
|
-
and a marker-fenced block in the
|
|
98
|
-
markers on each run and never outside them. A file whose markers are
|
|
99
|
-
reported rather than appended to.
|
|
104
|
+
`.mcp.json` for Claude, or `.codex/config.toml` with `--client codex`, preserving unrelated entries;
|
|
105
|
+
and a marker-fenced block in `AGENTS.md` for Codex (the existing `CLAUDE.md` or `AGENTS.md` for
|
|
106
|
+
Claude), replaced between the markers on each run and never outside them. A file whose markers are
|
|
107
|
+
damaged is left alone and reported rather than appended to.
|
|
100
108
|
|
|
101
109
|
It changes, on your GitHub repository and through your own `gh`: the repository variables the
|
|
102
110
|
workflow reads, so the workflow file itself names no host by hand.
|
|
@@ -108,22 +116,22 @@ your own branch protection.
|
|
|
108
116
|
|
|
109
117
|
## Commands
|
|
110
118
|
|
|
111
|
-
| Command
|
|
112
|
-
|
|
|
113
|
-
| `balladeer setup` | Run all five steps, then report what a person still has to do |
|
|
114
|
-
| `balladeer repositories` | List what this machine could add, marking the one you are in and the ones in |
|
|
115
|
-
| `balladeer invite` | Invite teammates by email, and say per address whether the email went |
|
|
116
|
-
| `balladeer status` | Report this repository over its agent connection, and the workspace |
|
|
117
|
-
| `balladeer status <id>` | Report one promise: whether it is holding, and if not, what the run reported |
|
|
118
|
-
| `balladeer prepare <id>` | Prepare a promise's one-time qualification setup, and write it where CI reads it |
|
|
119
|
-
| `balladeer propose` | Propose one promise from a proposal file, over that connection |
|
|
120
|
-
| `balladeer discover` | File a whole catalog, up to ten promises from one file, behind one review link |
|
|
121
|
-
| `balladeer touch-map` | Record which files each promise's verifier runs, on this machine only |
|
|
122
|
-
| `balladeer affected` | Say which promises the files you name touch, out of that record |
|
|
123
|
-
| `balladeer session` | Print the id for this piece of work, and the line to write into the commit |
|
|
124
|
-
| `balladeer mcp` | Forward one MCP session over stdio using this repository's connection |
|
|
125
|
-
| `balladeer explain` | What Balladeer can and cannot see, where to watch it, and what leaving costs |
|
|
126
|
-
| `balladeer whoami` | Report the stored session's workspace, role, scopes, and expiry |
|
|
119
|
+
| Command | What it does |
|
|
120
|
+
| -------------------------------------- | -------------------------------------------------------------------------------- |
|
|
121
|
+
| `npx -y balladeer@latest setup` | Run all five steps, then report what a person still has to do |
|
|
122
|
+
| `npx -y balladeer@latest repositories` | List what this machine could add, marking the one you are in and the ones in |
|
|
123
|
+
| `npx -y balladeer@latest invite` | Invite teammates by email, and say per address whether the email went |
|
|
124
|
+
| `npx -y balladeer@latest status` | Report this repository over its agent connection, and the workspace |
|
|
125
|
+
| `npx -y balladeer@latest status <id>` | Report one promise: whether it is holding, and if not, what the run reported |
|
|
126
|
+
| `npx -y balladeer@latest prepare <id>` | Prepare a promise's one-time qualification setup, and write it where CI reads it |
|
|
127
|
+
| `npx -y balladeer@latest propose` | Propose one promise from a proposal file, over that connection |
|
|
128
|
+
| `npx -y balladeer@latest discover` | File a whole catalog, up to ten promises from one file, behind one review link |
|
|
129
|
+
| `npx -y balladeer@latest touch-map` | Record which files each promise's verifier runs, on this machine only |
|
|
130
|
+
| `npx -y balladeer@latest affected` | Say which promises the files you name touch, out of that record |
|
|
131
|
+
| `npx -y balladeer@latest session` | Print the id for this piece of work, and the line to write into the commit |
|
|
132
|
+
| `npx -y balladeer@latest mcp` | Forward one MCP session over stdio using this repository's connection |
|
|
133
|
+
| `npx -y balladeer@latest explain` | What Balladeer can and cannot see, where to watch it, and what leaving costs |
|
|
134
|
+
| `npx -y balladeer@latest whoami` | Report the stored session's workspace, role, scopes, and expiry |
|
|
127
135
|
|
|
128
136
|
`--json` emits one object per step on stdout and nothing else. `--wait` makes `setup` poll for the
|
|
129
137
|
approval instead of exiting; without it the command exits and a later run finishes the pairing.
|
|
@@ -132,7 +140,7 @@ approval instead of exiting; without it the command exits and a later run finish
|
|
|
132
140
|
to be the one you are standing in, because connecting a coding agent writes files into a working
|
|
133
141
|
tree and connecting CI pushes a branch to a remote. The rest are added to the workspace and nothing
|
|
134
142
|
more, and the run says so for each of them; somebody runs setup in a checkout of each one to finish
|
|
135
|
-
it. `balladeer repositories` is what you read first to decide which ones to name.
|
|
143
|
+
it. `npx -y balladeer@latest repositories` is what you read first to decide which ones to name.
|
|
136
144
|
|
|
137
145
|
`invite` takes one or more addresses and `--role contributor|viewer|administrator`, defaulting to
|
|
138
146
|
contributor. It goes over the setup session's `workspace:invite` grant, so it works only for a
|
|
@@ -161,16 +169,42 @@ A machine that ran the earlier Balladeer still carries it: that client registers
|
|
|
161
169
|
this one goes on. Setup finds it before it writes anything and stops with one instruction. If you
|
|
162
170
|
used the earlier Balladeer on this machine: run `balladeer uninstall`, then the removal command it
|
|
163
171
|
prints, then continue. That uninstall is reversible, it backs up `~/.balladeer`, and it leaves the
|
|
164
|
-
old binary in place. `balladeer setup --force` runs anyway, for whoever has decided to
|
|
172
|
+
old binary in place. `npx -y balladeer@latest setup --force` runs anyway, for whoever has decided to
|
|
173
|
+
keep both.
|
|
165
174
|
|
|
166
175
|
## Revoking
|
|
167
176
|
|
|
168
177
|
Both credentials are yours to end. Revoke the setup session or the coding agent's connection at any
|
|
169
178
|
time in Balladeer; the setup session also ends on its own. Removing the `balladeer` entry from
|
|
170
|
-
`.mcp.json`
|
|
171
|
-
|
|
179
|
+
`.mcp.json` (Claude) or the managed table in `.codex/config.toml` (Codex) disconnects that project
|
|
180
|
+
host locally after its next reload. Archiving the credential store removes this machine's saved
|
|
181
|
+
Balladeer access without changing the server's records.
|
|
172
182
|
|
|
173
183
|
## Licence
|
|
174
184
|
|
|
175
185
|
Apache License 2.0. The full text ships in this package as `LICENSE`, so you can read the rights you
|
|
176
186
|
have from the copy on your own machine rather than taking a field in a manifest on trust.
|
|
187
|
+
|
|
188
|
+
## Choose Codex or reconnect a workspace
|
|
189
|
+
|
|
190
|
+
Run `npx -y balladeer@latest setup --client codex` in the repository to write its
|
|
191
|
+
`.codex/config.toml` and the managed Balladeer block in `AGENTS.md`. This leaves Claude files,
|
|
192
|
+
Claude Desktop, global Codex settings and unrelated MCP entries alone. The default client remains
|
|
193
|
+
Claude; choose it explicitly with `--client claude`.
|
|
194
|
+
|
|
195
|
+
Open and trust the project in Codex if appropriate, then start a fresh session. Project settings are
|
|
196
|
+
ignored in untrusted projects; the setup connection probe does not prove the host loaded its tools.
|
|
197
|
+
Check the actual Balladeer tools in that new session. Refresh only local Codex configuration with
|
|
198
|
+
`setup --client codex --refresh`; no pairing or network request is made.
|
|
199
|
+
|
|
200
|
+
To reconnect a different existing workspace, run:
|
|
201
|
+
|
|
202
|
+
```sh
|
|
203
|
+
npx -y balladeer@latest setup --client codex --existing --choose-workspace --wait
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Choose the workspace and approve pairing in the browser. The current control plane's setup session
|
|
207
|
+
is replaced only after a new pairing starts; all stored agent connections and other control planes
|
|
208
|
+
remain. `--create-workspace "Team name"` likewise opens fresh pairing instead of silently reusing
|
|
209
|
+
the previous workspace. It pre-fills the name; a human still creates the workspace. An interrupted
|
|
210
|
+
command can resume the same pending choice by running it again with the same flags.
|
package/dist/agent.d.ts
CHANGED
|
@@ -124,3 +124,8 @@ export declare function safeJson(response: Response): Promise<unknown>;
|
|
|
124
124
|
export declare function updateLine(payload: unknown): string;
|
|
125
125
|
/** One field of a tool result, when it is a string. Never a guess. */
|
|
126
126
|
export declare function structuredString(structured: unknown, field: string): string | undefined;
|
|
127
|
+
/** Render the server's live enforcement limitation once per CLI operation. */
|
|
128
|
+
export declare function reportAgentEnforcementWarning(call: AgentToolCall, output: Readonly<{
|
|
129
|
+
json: boolean;
|
|
130
|
+
write: (text: string) => void;
|
|
131
|
+
}>): void;
|
package/dist/agent.js
CHANGED
|
@@ -21,7 +21,8 @@ const REPOSITORY_NAME = /^[A-Za-z0-9._-]{1,39}\/[A-Za-z0-9._-]{1,100}$/;
|
|
|
21
21
|
export function selectAgent(agents, controlPlane, repositoryId, currentRepository) {
|
|
22
22
|
const here = agents.filter((agent) => agent.controlPlane === controlPlane);
|
|
23
23
|
if (repositoryId !== undefined) {
|
|
24
|
-
const named = here.find((agent) => agent.repositoryId === repositoryId
|
|
24
|
+
const named = here.find((agent) => agent.repositoryId === repositoryId ||
|
|
25
|
+
agent.repository?.toLowerCase() === repositoryId.toLowerCase());
|
|
25
26
|
if (named === undefined) {
|
|
26
27
|
return {
|
|
27
28
|
kind: "refused",
|
|
@@ -207,3 +208,14 @@ export function structuredString(structured, field) {
|
|
|
207
208
|
const value = structured[field];
|
|
208
209
|
return typeof value === "string" ? value : undefined;
|
|
209
210
|
}
|
|
211
|
+
/** Render the server's live enforcement limitation once per CLI operation. */
|
|
212
|
+
export function reportAgentEnforcementWarning(call, output) {
|
|
213
|
+
if (call.kind !== "result" && call.kind !== "tool_refusal")
|
|
214
|
+
return;
|
|
215
|
+
const message = structuredString(call.structured, "enforcementWarning");
|
|
216
|
+
if (message === undefined)
|
|
217
|
+
return;
|
|
218
|
+
output.write(output.json
|
|
219
|
+
? `${JSON.stringify({ step: "warning", code: "enforcement_unavailable", message })}\n`
|
|
220
|
+
: `WARNING ${message}\n`);
|
|
221
|
+
}
|
package/dist/cli.d.ts
CHANGED
|
@@ -6,8 +6,12 @@ type Parsed = Readonly<{
|
|
|
6
6
|
wait: boolean;
|
|
7
7
|
/** Repair the files a previous setup wrote, and do nothing else. */
|
|
8
8
|
refresh: boolean;
|
|
9
|
+
client: "codex" | "claude" | undefined;
|
|
10
|
+
chooseWorkspace: boolean;
|
|
9
11
|
/** `setup --force`: set up even though an earlier Balladeer is still installed. */
|
|
10
12
|
force: boolean;
|
|
13
|
+
/** `setup --existing`: connect only when this checkout is already enrolled. */
|
|
14
|
+
existingOnly: boolean;
|
|
11
15
|
/**
|
|
12
16
|
* `setup --claude-desktop` / `--no-claude-desktop`. Undefined is neither
|
|
13
17
|
* asked for nor refused, which is the ordinary run: connect the chat client
|
package/dist/cli.js
CHANGED
|
@@ -18,22 +18,37 @@ import { runTouchMap } from "./commands/touch-map.js";
|
|
|
18
18
|
import { runWhoami } from "./commands/whoami.js";
|
|
19
19
|
import { updateNotice } from "./currency.js";
|
|
20
20
|
import { StoreError, normalizeControlPlane } from "./store.js";
|
|
21
|
-
import { CLI_VERSION, DEFAULT_CONTROL_PLANE } from "./wire.js";
|
|
21
|
+
import { CLI_INVOCATION, CLI_VERSION, DEFAULT_CONTROL_PLANE } from "./wire.js";
|
|
22
22
|
const USAGE = `balladeer ${CLI_VERSION}
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
[--create-workspace <name>] [--refresh] [--force]
|
|
24
|
+
${CLI_INVOCATION} setup [--repository owner/name]... [--json] [--wait] [--control-plane <url>]
|
|
25
|
+
[--create-workspace <name>] [--choose-workspace] [--refresh] [--force]
|
|
26
|
+
[--client codex|claude]
|
|
27
|
+
[--existing]
|
|
26
28
|
[--claude-desktop | --no-claude-desktop]
|
|
27
29
|
Pair this session, add this repository, connect this coding agent and CI,
|
|
28
30
|
and report what a person still has to do. Name --repository more than once
|
|
29
31
|
to add other repositories to the same workspace; each of those is added and
|
|
30
32
|
nothing more, because connecting an agent and CI happens in a checkout.
|
|
33
|
+
--client codex writes project .codex/config.toml and AGENTS.md.
|
|
34
|
+
Trust the project in Codex and start a fresh session to load its tools.
|
|
35
|
+
--client claude (the default) preserves the Claude setup behavior.
|
|
36
|
+
For --refresh only, a lone managed Codex entry selects Codex automatically.
|
|
37
|
+
If both clients have Balladeer entries, choose --client explicitly.
|
|
38
|
+
--choose-workspace starts fresh browser pairing for this control plane,
|
|
39
|
+
preserving other saved agent connections. Use it with --existing to
|
|
40
|
+
reconnect an existing workspace. --create-workspace also starts fresh
|
|
41
|
+
pairing instead of reusing the previously selected workspace.
|
|
42
|
+
|
|
31
43
|
--create-workspace carries a name to the approval page, where a person
|
|
32
44
|
signs in and creates the workspace themselves; this command never creates
|
|
33
45
|
one.
|
|
46
|
+
--existing is for an invited teammate and never enrolls repositories or changes CI.
|
|
47
|
+
If this repository is already connected, connect this machine; otherwise
|
|
48
|
+
continue in the browser while an administrator connects it.
|
|
34
49
|
--refresh does one thing and talks to nobody: it rewrites this
|
|
35
|
-
|
|
36
|
-
|
|
50
|
+
selected client's project MCP entry and Balladeer instructions block
|
|
51
|
+
(and Claude desktop entry for Claude) to the current form, leaves every
|
|
37
52
|
other entry in those files alone, and prints what it changed. Run it when
|
|
38
53
|
Balladeer says a newer version is available.
|
|
39
54
|
--force sets up even though an earlier Balladeer is still installed on
|
|
@@ -45,15 +60,15 @@ const USAGE = `balladeer ${CLI_VERSION}
|
|
|
45
60
|
skipping quietly; --no-claude-desktop leaves that file alone entirely.
|
|
46
61
|
Quit and reopen the app afterwards: it reads its configuration at startup.
|
|
47
62
|
|
|
48
|
-
|
|
63
|
+
${CLI_INVOCATION} repositories [--json] [--control-plane <url>]
|
|
49
64
|
List the repositories this machine's GitHub account can see, marking the
|
|
50
65
|
one you are standing in and the ones Balladeer already has.
|
|
51
66
|
|
|
52
|
-
|
|
67
|
+
${CLI_INVOCATION} invite <email>... [--role contributor|viewer|administrator] [--json]
|
|
53
68
|
Invite teammates into this workspace by email, and say per address whether
|
|
54
69
|
the invitation went. Inviting is an administrator's act.
|
|
55
70
|
|
|
56
|
-
|
|
71
|
+
${CLI_INVOCATION} status [<promise id>] [--repo owner/name] [--repository <uuid>] [--json]
|
|
57
72
|
[--control-plane <url>]
|
|
58
73
|
Report this repository over its agent connection, and every repository in
|
|
59
74
|
the workspace when a setup session is still live. Named with a promise id,
|
|
@@ -62,11 +77,11 @@ const USAGE = `balladeer ${CLI_VERSION}
|
|
|
62
77
|
the agreed meaning that run was checking. That is the form the line on a
|
|
63
78
|
broken promise tells a person to run first.
|
|
64
79
|
|
|
65
|
-
|
|
80
|
+
${CLI_INVOCATION} propose --file <path> [--repo owner/name] [--repository <uuid>] [--json]
|
|
66
81
|
Propose one promise from a proposal file, over this repository's agent
|
|
67
82
|
connection. A named person still agrees to it.
|
|
68
83
|
|
|
69
|
-
|
|
84
|
+
${CLI_INVOCATION} prepare <promise id> [--again] [--repo owner/name] [--repository <uuid>]
|
|
70
85
|
[--json] [--control-plane <url>]
|
|
71
86
|
Prepare the one-time qualification setup for a promise whose meaning is
|
|
72
87
|
agreed and which nothing is checking yet, and write it to
|
|
@@ -79,13 +94,13 @@ const USAGE = `balladeer ${CLI_VERSION}
|
|
|
79
94
|
prepared it and when. --again prepares a replacement and invalidates that
|
|
80
95
|
earlier packet: a run publishing its identities afterwards is refused.
|
|
81
96
|
|
|
82
|
-
|
|
97
|
+
${CLI_INVOCATION} discover --file <path> [--repo owner/name] [--repository <uuid>]
|
|
83
98
|
[--owner <membership id>] [--json]
|
|
84
99
|
Propose a whole discovered catalog, up to ten promises from one file, each
|
|
85
100
|
owned by whoever paired this machine, and print one link that opens all of
|
|
86
101
|
them. A named person still agrees to every one.
|
|
87
102
|
|
|
88
|
-
|
|
103
|
+
${CLI_INVOCATION} check-seals [--json] [--runner <path>] [--install-hook]
|
|
89
104
|
Say whether what you are about to push would break a promise's seal. It
|
|
90
105
|
prints nothing and exits 0 when it would not, and names the promise, its
|
|
91
106
|
owner, its page and the line that seals it again when it would. It runs
|
|
@@ -93,18 +108,18 @@ const USAGE = `balladeer ${CLI_VERSION}
|
|
|
93
108
|
writes .git/hooks/pre-push so every push asks first; nothing installs it
|
|
94
109
|
for you, and deleting that file removes it.
|
|
95
110
|
|
|
96
|
-
|
|
111
|
+
${CLI_INVOCATION} touch-map [--json]
|
|
97
112
|
Run this repository's promise verifiers under coverage and write down
|
|
98
113
|
which files each one actually executed, to .continuity/touch-map.json.
|
|
99
114
|
It runs offline and the map stays on this machine: Balladeer is never
|
|
100
115
|
sent it. Node verifiers only in this release.
|
|
101
116
|
|
|
102
|
-
|
|
117
|
+
${CLI_INVOCATION} affected <paths...> [--json]
|
|
103
118
|
Say which promises the named files touch, out of that map, marking any
|
|
104
119
|
answer whose verifier has changed since the map was built. It reads one
|
|
105
120
|
local file and contacts nothing.
|
|
106
121
|
|
|
107
|
-
|
|
122
|
+
${CLI_INVOCATION} session [--new] [--record] [--repo owner/name] [--repository <uuid>] [--json]
|
|
108
123
|
[--control-plane <url>]
|
|
109
124
|
Print the id for this piece of work, and the one line to write into the
|
|
110
125
|
commit it produces. Pass the id to every promise read you make, so a check
|
|
@@ -114,18 +129,18 @@ const USAGE = `balladeer ${CLI_VERSION}
|
|
|
114
129
|
commit this session wrote. Your commit message never leaves this machine:
|
|
115
130
|
only the session id and the commit SHA are sent.
|
|
116
131
|
|
|
117
|
-
|
|
132
|
+
${CLI_INVOCATION} explain [--control-plane <url>]
|
|
118
133
|
Print, word for word, what Balladeer can and cannot see, where to watch
|
|
119
134
|
your promises, and what leaving costs.
|
|
120
135
|
|
|
121
|
-
|
|
136
|
+
${CLI_INVOCATION} whoami [--json] [--control-plane <url>]
|
|
122
137
|
Report the stored setup session's workspace, role, scopes, and expiry.
|
|
123
138
|
|
|
124
|
-
|
|
139
|
+
${CLI_INVOCATION} agent rotate [--control-plane <url>]
|
|
125
140
|
Replace this repository's agent credential. A setup session cannot do this,
|
|
126
141
|
because rotating revokes the connections the repository already has.
|
|
127
142
|
|
|
128
|
-
|
|
143
|
+
${CLI_INVOCATION} mcp [--repository <uuid>] [--control-plane <url>]
|
|
129
144
|
Forward one MCP session over stdio using this repository's agent connection.
|
|
130
145
|
|
|
131
146
|
Exit codes: 0 progress reported truthfully, 2 pairing expired or denied or already
|
|
@@ -147,7 +162,10 @@ export function parseArguments(argv) {
|
|
|
147
162
|
let json = false;
|
|
148
163
|
let wait = false;
|
|
149
164
|
let refresh = false;
|
|
165
|
+
let client;
|
|
166
|
+
let chooseWorkspace = false;
|
|
150
167
|
let force = false;
|
|
168
|
+
let existingOnly = false;
|
|
151
169
|
let claudeDesktop;
|
|
152
170
|
let installHook = false;
|
|
153
171
|
let fresh = false;
|
|
@@ -170,6 +188,7 @@ export function parseArguments(argv) {
|
|
|
170
188
|
"--owner": "a membership id",
|
|
171
189
|
"--create-workspace": "a workspace name",
|
|
172
190
|
"--role": "contributor, viewer, or administrator",
|
|
191
|
+
"--client": "codex or claude",
|
|
173
192
|
"--runner": "a path to the pinned runner's cli.js",
|
|
174
193
|
};
|
|
175
194
|
const value = (flag, inline) => {
|
|
@@ -202,8 +221,18 @@ export function parseArguments(argv) {
|
|
|
202
221
|
wait = true;
|
|
203
222
|
else if (name === "--refresh")
|
|
204
223
|
refresh = true;
|
|
224
|
+
else if (name === "--choose-workspace")
|
|
225
|
+
chooseWorkspace = true;
|
|
226
|
+
else if (name === "--client") {
|
|
227
|
+
const requested = value("--client", inline);
|
|
228
|
+
if (requested !== "codex" && requested !== "claude")
|
|
229
|
+
throw new StoreError("usage", "--client needs codex or claude.");
|
|
230
|
+
client = requested;
|
|
231
|
+
}
|
|
205
232
|
else if (name === "--force")
|
|
206
233
|
force = true;
|
|
234
|
+
else if (name === "--existing")
|
|
235
|
+
existingOnly = true;
|
|
207
236
|
else if (name === "--claude-desktop")
|
|
208
237
|
claudeDesktop = true;
|
|
209
238
|
else if (name === "--no-claude-desktop")
|
|
@@ -241,7 +270,7 @@ export function parseArguments(argv) {
|
|
|
241
270
|
// belongs to the one command that mints one. Accepted silently elsewhere, it
|
|
242
271
|
// would read as a general "do it anyway" flag.
|
|
243
272
|
if (again && command !== "prepare") {
|
|
244
|
-
throw new StoreError("usage",
|
|
273
|
+
throw new StoreError("usage", `--again belongs to prepare: run \`${CLI_INVOCATION} prepare <promise id> --again\`.`);
|
|
245
274
|
}
|
|
246
275
|
if (positional.length > 0 &&
|
|
247
276
|
command !== "invite" &&
|
|
@@ -257,20 +286,39 @@ export function parseArguments(argv) {
|
|
|
257
286
|
// `--refresh` repairs the files setup writes, so it belongs to setup and to
|
|
258
287
|
// nothing else. Accepting it silently elsewhere would let somebody run
|
|
259
288
|
// `status --refresh`, see no error, and believe their install was repaired.
|
|
289
|
+
if ((client !== undefined || chooseWorkspace || createWorkspace !== undefined) &&
|
|
290
|
+
command !== "setup") {
|
|
291
|
+
throw new StoreError("usage", "--client, --choose-workspace and --create-workspace belong to setup.");
|
|
292
|
+
}
|
|
293
|
+
if (refresh && (chooseWorkspace || createWorkspace !== undefined)) {
|
|
294
|
+
throw new StoreError("usage", "--refresh only repairs local files; omit it to choose or create a workspace.");
|
|
295
|
+
}
|
|
296
|
+
if (client === "codex" && claudeDesktop === true) {
|
|
297
|
+
throw new StoreError("usage", "--client codex leaves Claude configuration alone; run a separate --client claude setup to connect Claude Desktop.");
|
|
298
|
+
}
|
|
260
299
|
if (refresh && command !== "setup") {
|
|
261
|
-
throw new StoreError("usage",
|
|
300
|
+
throw new StoreError("usage", `--refresh belongs to setup: run \`${CLI_INVOCATION} setup --refresh\`.`);
|
|
262
301
|
}
|
|
263
302
|
// `--force` says one thing only: set up alongside an earlier Balladeer. Taken
|
|
264
303
|
// silently by another command it would read as a general "do it anyway" flag,
|
|
265
304
|
// which is exactly what nothing else here offers.
|
|
266
305
|
if (force && command !== "setup") {
|
|
267
|
-
throw new StoreError("usage",
|
|
306
|
+
throw new StoreError("usage", `--force belongs to setup: run \`${CLI_INVOCATION} setup --force\`.`);
|
|
307
|
+
}
|
|
308
|
+
if (existingOnly && command !== "setup") {
|
|
309
|
+
throw new StoreError("usage", `--existing belongs to setup: run \`${CLI_INVOCATION} setup --existing\`.`);
|
|
310
|
+
}
|
|
311
|
+
if (existingOnly && createWorkspace !== undefined) {
|
|
312
|
+
throw new StoreError("usage", "--existing cannot be combined with --create-workspace: one refuses repository enrollment and the other starts a new workspace.");
|
|
313
|
+
}
|
|
314
|
+
if (existingOnly && repositories.length > 1) {
|
|
315
|
+
throw new StoreError("usage", "--existing connects one checkout and never adds repositories. Leave off --repository, or name only this checkout.");
|
|
268
316
|
}
|
|
269
317
|
// Connecting a chat client is something setup does, so the flag belongs to
|
|
270
318
|
// setup. Accepted silently on `status`, it would let somebody believe they had
|
|
271
319
|
// connected Claude desktop when nothing had written a line.
|
|
272
320
|
if (claudeDesktop !== undefined && command !== "setup") {
|
|
273
|
-
throw new StoreError("usage",
|
|
321
|
+
throw new StoreError("usage", `--claude-desktop belongs to setup: run \`${CLI_INVOCATION} setup --claude-desktop\`.`);
|
|
274
322
|
}
|
|
275
323
|
if (command !== "setup" && repositories.length > 1) {
|
|
276
324
|
throw new StoreError("usage", `${command} acts on one repository. Name --repository once, or run it again for the other.`);
|
|
@@ -282,7 +330,10 @@ export function parseArguments(argv) {
|
|
|
282
330
|
json,
|
|
283
331
|
wait,
|
|
284
332
|
refresh,
|
|
333
|
+
client,
|
|
334
|
+
chooseWorkspace,
|
|
285
335
|
force,
|
|
336
|
+
existingOnly,
|
|
286
337
|
claudeDesktop,
|
|
287
338
|
repo,
|
|
288
339
|
file,
|
|
@@ -330,7 +381,10 @@ async function dispatch(parsed, write) {
|
|
|
330
381
|
json: parsed.json,
|
|
331
382
|
wait: parsed.wait,
|
|
332
383
|
refresh: parsed.refresh,
|
|
384
|
+
chooseWorkspace: parsed.chooseWorkspace,
|
|
385
|
+
...(parsed.client === undefined ? {} : { client: parsed.client }),
|
|
333
386
|
force: parsed.force,
|
|
387
|
+
existingOnly: parsed.existingOnly,
|
|
334
388
|
...(parsed.claudeDesktop === undefined ? {} : { claudeDesktop: parsed.claudeDesktop }),
|
|
335
389
|
...(parsed.repo === undefined ? {} : { repo: parsed.repo }),
|
|
336
390
|
repositories: parsed.repositories,
|
|
@@ -423,7 +477,7 @@ async function dispatch(parsed, write) {
|
|
|
423
477
|
// two one-time identities and write two files, and reporting the first
|
|
424
478
|
// silently would leave the second promise unprepared with nobody told.
|
|
425
479
|
if (parsed.positional.length !== 1) {
|
|
426
|
-
process.stderr.write(
|
|
480
|
+
process.stderr.write(`prepare reads one promise id: ${CLI_INVOCATION} prepare <promise id> [--again].\n`);
|
|
427
481
|
return 4;
|
|
428
482
|
}
|
|
429
483
|
return runPrepare({
|
package/dist/client.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export declare class RefusalError extends Error {
|
|
|
26
26
|
* identifier and this builds the address, which is why there is no parameter
|
|
27
27
|
* here for a server's own link to arrive through.
|
|
28
28
|
*/
|
|
29
|
-
export declare function reviewLink(controlPlane: string, path: string): string;
|
|
29
|
+
export declare function reviewLink(controlPlane: string, path: string, workspaceId?: string): string;
|
|
30
30
|
/**
|
|
31
31
|
* The one place this command decides where a person reads a proposal.
|
|
32
32
|
*
|
|
@@ -37,7 +37,7 @@ export declare function reviewLink(controlPlane: string, path: string): string;
|
|
|
37
37
|
* the retired address redirects to it permanently, so a link an older copy of
|
|
38
38
|
* this command already printed still lands on it.
|
|
39
39
|
*/
|
|
40
|
-
export declare function proposalReviewLink(controlPlane: string, proposalId: string): string;
|
|
40
|
+
export declare function proposalReviewLink(controlPlane: string, proposalId: string, workspaceId?: string): string;
|
|
41
41
|
/** The address the whole waiting inbox is read on. */
|
|
42
42
|
export declare function proposalInboxLink(controlPlane: string): string;
|
|
43
43
|
/**
|
package/dist/client.js
CHANGED
|
@@ -42,8 +42,11 @@ export class RefusalError extends Error {
|
|
|
42
42
|
* identifier and this builds the address, which is why there is no parameter
|
|
43
43
|
* here for a server's own link to arrive through.
|
|
44
44
|
*/
|
|
45
|
-
export function reviewLink(controlPlane, path) {
|
|
46
|
-
|
|
45
|
+
export function reviewLink(controlPlane, path, workspaceId) {
|
|
46
|
+
const url = new URL(path, ensureTrailing(controlPlane));
|
|
47
|
+
if (workspaceId !== undefined)
|
|
48
|
+
url.searchParams.set("workspaceId", workspaceId);
|
|
49
|
+
return url.toString();
|
|
47
50
|
}
|
|
48
51
|
/**
|
|
49
52
|
* The one place this command decides where a person reads a proposal.
|
|
@@ -55,8 +58,8 @@ export function reviewLink(controlPlane, path) {
|
|
|
55
58
|
* the retired address redirects to it permanently, so a link an older copy of
|
|
56
59
|
* this command already printed still lands on it.
|
|
57
60
|
*/
|
|
58
|
-
export function proposalReviewLink(controlPlane, proposalId) {
|
|
59
|
-
return reviewLink(controlPlane, `proposals/${encodeURIComponent(proposalId)}
|
|
61
|
+
export function proposalReviewLink(controlPlane, proposalId, workspaceId) {
|
|
62
|
+
return reviewLink(controlPlane, `proposals/${encodeURIComponent(proposalId)}`, workspaceId);
|
|
60
63
|
}
|
|
61
64
|
/** The address the whole waiting inbox is read on. */
|
|
62
65
|
export function proposalInboxLink(controlPlane) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type MergeResult, type StdioMcpEntry } from "./mcp-config.js";
|
|
2
|
+
export declare const CODEX_CONFIG_FILE = ".codex/config.toml";
|
|
3
|
+
export declare function readCodexEntry(root: string): unknown;
|
|
4
|
+
/** A client hint only when the complete file and our exact fenced table agree. */
|
|
5
|
+
export declare function hasManagedCodexEntry(root: string, controlPlane: string): boolean;
|
|
6
|
+
/** Only our fenced table is replaced; every unrelated byte and parsed setting survives. */
|
|
7
|
+
export declare function mergeCodexConfig(root: string, entry: StdioMcpEntry, controlPlane: string): MergeResult;
|