@basein/runner 0.3.4 → 0.3.6
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 +19 -11
- package/dist/bin/bir-scenario.d.ts +9 -8
- package/dist/bin/bir-scenario.js +9 -8
- package/dist/bin/bir.js +71 -5
- package/dist/bin/setup.d.ts +5 -1
- package/dist/bin/setup.js +16 -2
- package/dist/config/adapters/claude-code.d.ts +27 -0
- package/dist/config/adapters/claude-code.js +69 -1
- package/dist/config/generate.d.ts +21 -4
- package/dist/config/generate.js +12 -0
- package/dist/control/server.js +20 -0
- package/docs/calculatedReplay.md +11 -9
- package/docs/calculatedReplayGuide.md +25 -18
- package/docs/quickstart.md +20 -10
- package/package.json +1 -1
- package/production.md +2 -2
package/README.md
CHANGED
|
@@ -79,6 +79,8 @@ written; wraps an older install made there are put back. `--mcp-json` (or
|
|
|
79
79
|
`BIR_WRAP_MCP_JSON=1`) is that old in-place shape, for a `.mcp.json` nobody
|
|
80
80
|
commits. The hooks go into `.claude/settings.local.json` — your file, not the
|
|
81
81
|
one a team commits; in a git repository it is added to `.git/info/exclude`.
|
|
82
|
+
`bir setup` also puts `mcp__bir` in that file's `permissions.allow`, so Claude
|
|
83
|
+
Code runs the `bir` server's tools without asking (a forced change still asks).
|
|
82
84
|
Uninstall repairs `~/.claude.json` (and a `--config` file) entry by entry;
|
|
83
85
|
`settings.local.json` it puts back byte-for-byte when nothing else has edited
|
|
84
86
|
it since, and repairs entry by entry when something has. A project remembers
|
|
@@ -248,12 +250,18 @@ between the two is not guaranteed.
|
|
|
248
250
|
`bir` server's `run_scenario` (only with `--replay`), and replay's own
|
|
249
251
|
`[BaseInstRunner calculated replay]` notes when a prompt matches a scenario.
|
|
250
252
|
The `bir` server's three tools that only read a scenario (`scenario_show`,
|
|
251
|
-
`scenario_edits`, `investigate`)
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
`
|
|
255
|
-
|
|
256
|
-
|
|
253
|
+
`scenario_edits`, `investigate`) and the three for changing one
|
|
254
|
+
(`scenario_check`, `scenario_edit`, `scenario_undo`) come with scenario
|
|
255
|
+
editing, which `bir setup` turns on. Setup also lets Claude Code run every
|
|
256
|
+
`bir` tool without asking: it adds `mcp__bir` to `permissions.allow` in
|
|
257
|
+
`.claude/settings.local.json`, and uninstall takes it out again. A change
|
|
258
|
+
forced past the scenario check (`force`) still asks the person first. Their
|
|
259
|
+
definitions ride along with every request, so `bir scenario editing off`
|
|
260
|
+
takes the six away, and a later setup leaves that off. A bare
|
|
261
|
+
`bir install --replay` lists `run_scenario` alone until
|
|
262
|
+
`bir scenario editing on` (or `BIR_SCENARIO_TOOLS=read` in the server's
|
|
263
|
+
environment, for the reading three). With the runner off it lists no tools
|
|
264
|
+
at all.
|
|
257
265
|
- **Record its own `bir` calls** (next version, not yet released). A session
|
|
258
266
|
that fixes a plan is not a task to replay. The `bir` server's tools other
|
|
259
267
|
than `run_scenario`, and a `Bash` or `PowerShell` command made only of `bir`
|
|
@@ -331,9 +339,9 @@ copy — or on purpose, with `--force --note "why"`.** Only the owner can change
|
|
|
331
339
|
a scenario. `bir scenario undo` takes changes back one at a time: run it again
|
|
332
340
|
to go one further back. An undo is checked too, and is refused if it would
|
|
333
341
|
break a later step or the final answer, unless you add `--force --note "why"`.
|
|
334
|
-
Claude can do all of this: in its terminal, or with the `bir` server's tools
|
|
335
|
-
|
|
336
|
-
output:
|
|
342
|
+
Claude can do all of this: in its terminal, or with the `bir` server's tools,
|
|
343
|
+
which `bir setup` turns on (`bir scenario editing on` in a project wired by
|
|
344
|
+
`bir install`). Step by step, with real output:
|
|
337
345
|
[the guide's §9.2](docs/calculatedReplayGuide.md).
|
|
338
346
|
|
|
339
347
|
```bash
|
|
@@ -344,7 +352,7 @@ bir scenario edits scn_… # the history, newe
|
|
|
344
352
|
bir scenario undo scn_… # take back the newest edit; again to go further back
|
|
345
353
|
bir scenario undo scn_… --step 3 # take back step 3's newest edit only
|
|
346
354
|
bir scenario calc scn_… --force --discard-edits # recalculate this plan, dropping its hand edits
|
|
347
|
-
bir scenario editing on # let Claude's `bir` tools change scenarios here
|
|
355
|
+
bir scenario editing on # let Claude's `bir` tools change scenarios here (setup did)
|
|
348
356
|
```
|
|
349
357
|
|
|
350
358
|
**A value no prompt names.** Built 2026-09-28, not yet released. Every
|
|
@@ -439,7 +447,7 @@ what to do when they register, so there is one switch and no second copy of it:
|
|
|
439
447
|
| `BIR_REPLAY_RECORDED_OUTPUTS=1` | Let a step's recorded output stand in when the step could not be sent. Unset, the plan stops at that step and hands the task back. |
|
|
440
448
|
|
|
441
449
|
The `bir` server reads its own: `BIR_SCENARIO_TOOLS=read` lists its three reading
|
|
442
|
-
tools
|
|
450
|
+
tools where scenario editing is off, and `BIR_SCENARIO_RUN_TIMEOUT_MS` is how
|
|
443
451
|
long `run_scenario` waits for a run (default `290000`). Everything else — remote
|
|
444
452
|
upstreams, the recorder's backlog, service timeouts, the `bir` server's output
|
|
445
453
|
caps — is in [production.md](production.md).
|
|
@@ -61,14 +61,15 @@
|
|
|
61
61
|
* WHICH TOOLS ARE OFFERED. `run_scenario`, always — unless the runner is
|
|
62
62
|
* switched off (`bir off`), when nothing is: an inert runner adds nothing to
|
|
63
63
|
* the model's context, and a call from a list taken before the switch starts
|
|
64
|
-
* nothing and asks nobody. The
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* this server runs in every session
|
|
70
|
-
*
|
|
71
|
-
*
|
|
64
|
+
* nothing and asks nobody. The rest come with scenario editing, which
|
|
65
|
+
* `bir setup` turns on (a person's `bir scenario editing off` is kept): the
|
|
66
|
+
* reading tools — also with `BIR_SCENARIO_TOOLS=read` in this server's
|
|
67
|
+
* environment — and the tools that CHANGE a scenario (D2). A project wired by
|
|
68
|
+
* a bare `bir install` (a fleet, say) offers `run_scenario` alone until a
|
|
69
|
+
* person runs `bir scenario editing on`: this server runs in every session
|
|
70
|
+
* there, each listed tool's definition rides along with every request, and a
|
|
71
|
+
* plan must not change because some agent there decided it should. The
|
|
72
|
+
* switch is read when the host lists the tools, and again on every
|
|
72
73
|
* call, so a call to a tool that was never offered — or was offered before the
|
|
73
74
|
* switch went off — is refused rather than run. Turning the runner off or on
|
|
74
75
|
* mid-session tells the host its list changed (`notifications/tools/list_changed`).
|
package/dist/bin/bir-scenario.js
CHANGED
|
@@ -61,14 +61,15 @@
|
|
|
61
61
|
* WHICH TOOLS ARE OFFERED. `run_scenario`, always — unless the runner is
|
|
62
62
|
* switched off (`bir off`), when nothing is: an inert runner adds nothing to
|
|
63
63
|
* the model's context, and a call from a list taken before the switch starts
|
|
64
|
-
* nothing and asks nobody. The
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* this server runs in every session
|
|
70
|
-
*
|
|
71
|
-
*
|
|
64
|
+
* nothing and asks nobody. The rest come with scenario editing, which
|
|
65
|
+
* `bir setup` turns on (a person's `bir scenario editing off` is kept): the
|
|
66
|
+
* reading tools — also with `BIR_SCENARIO_TOOLS=read` in this server's
|
|
67
|
+
* environment — and the tools that CHANGE a scenario (D2). A project wired by
|
|
68
|
+
* a bare `bir install` (a fleet, say) offers `run_scenario` alone until a
|
|
69
|
+
* person runs `bir scenario editing on`: this server runs in every session
|
|
70
|
+
* there, each listed tool's definition rides along with every request, and a
|
|
71
|
+
* plan must not change because some agent there decided it should. The
|
|
72
|
+
* switch is read when the host lists the tools, and again on every
|
|
72
73
|
* call, so a call to a tool that was never offered — or was offered before the
|
|
73
74
|
* switch went off — is refused rather than run. Turning the runner off or on
|
|
74
75
|
* mid-session tells the host its list changed (`notifications/tools/list_changed`).
|
package/dist/bin/bir.js
CHANGED
|
@@ -46,14 +46,14 @@ import * as daemonApi from "../control/daemon.js";
|
|
|
46
46
|
import { activeSessions, daemonStatus, ensureDaemon, probeHealth, readPortProblem, stopDaemon, } from "../control/daemon.js";
|
|
47
47
|
import { configDir, controlDir, controlKey, discoveryPath, installedPath, normalizePath } from "../control/paths.js";
|
|
48
48
|
import { offFlagPath, offSource, projectOffFlagPath, setRunnerOff } from "../util/off-switch.js";
|
|
49
|
-
import { allocateFreeProjectPort, backupIsCreation, backupOriginalText, configSha, envProxyHint, exemptLoopbackFromProxy, isNpxEntry, isScenarioServer, isWrapped, nodeFlagsToCarry, normaliseRecordKeys, npxHookInvocation, protectBackupText, recordKey, sameServerConfig, PACKAGE_NAME, projectRecord, readSidecar, redactSecrets, restoreSecrets, remoteWrapBlocker, scenarioEntry, setProjectRecord, settleReplaySwitch, sidecarKey, unwrapEntry, UPSTREAM_HEADERS_ENV, wrapEntry, writeSidecar, SCENARIO_SERVER_KEY, } from "../config/generate.js";
|
|
49
|
+
import { allocateFreeProjectPort, backupIsCreation, backupOriginalText, configSha, envProxyHint, exemptLoopbackFromProxy, isNpxEntry, isScenarioServer, isWrapped, nodeFlagsToCarry, normaliseRecordKeys, npxHookInvocation, protectBackupText, recordKey, sameServerConfig, PACKAGE_NAME, projectRecord, readSidecar, redactSecrets, restoreSecrets, remoteWrapBlocker, scenarioEntry, editingEnabled, setProjectRecord, settleEditing, settleReplaySwitch, sidecarKey, unwrapEntry, UPSTREAM_HEADERS_ENV, wrapEntry, writeSidecar, SCENARIO_SERVER_KEY, } from "../config/generate.js";
|
|
50
50
|
import { chooseNodePath } from "../config/node-path.js";
|
|
51
|
-
import { managedMcpPolicyBlocker, mcpjsonApprovals } from "../config/mcp-approval.js";
|
|
51
|
+
import { claudeUserSettingsPath, managedMcpPolicyBlocker, mcpjsonApprovals } from "../config/mcp-approval.js";
|
|
52
52
|
import { checkOverrides, recordServerName } from "../config/overrides.js";
|
|
53
53
|
import { awaitWithin, isUnhealthyStatus, serviceHealth, serviceTimeoutMs, timeoutSignal } from "../util/service-health.js";
|
|
54
54
|
import { runSetup } from "./setup.js";
|
|
55
55
|
import { claudeConfigHome, claudeProjectKey, claudeProjectRoot, isRemote, linkedWorktrees, parseJsonFile, projectKeyFor, projectKeysFor, projectScope, resolveServers, } from "../config/resolve.js";
|
|
56
|
-
import { assertEditableJson, buildHooksBlock, claudeCodePaths, disableMcpjsonServers, enableMcpjsonServers, ENSURE_ARG, ensureGitExcludeLine, hasBirHooks, installHooks, isBirHook, LOCAL_SETTINGS_EXCLUDE, readTextOrNull, removeGitExclude, setLocalEntries, setServerEntry, sha256, uninstallHooks, } from "../config/adapters/claude-code.js";
|
|
56
|
+
import { addAllowRules, allowRules, assertEditableJson, BIR_TOOLS_RULE, buildHooksBlock, claudeCodePaths, disableMcpjsonServers, enableMcpjsonServers, ENSURE_ARG, ensureGitExcludeLine, hasBirHooks, installHooks, isBirHook, LOCAL_SETTINGS_EXCLUDE, readTextOrNull, removeAllowRules, removeGitExclude, setLocalEntries, setServerEntry, sha256, uninstallHooks, } from "../config/adapters/claude-code.js";
|
|
57
57
|
import { writeFileAtomic } from "../util/atomic-write.js";
|
|
58
58
|
import { readGenericServers, setGenericServerEntry } from "../config/adapters/generic.js";
|
|
59
59
|
import { AUTH_URL_HINT, DeviceFlowAborted, DeviceFlowUnsupported, authenticate, clearCredentials, deviceLogin, describeAuthService, legacyPasswordLogin, tokenLogin, normalizeAuthUrl, rememberAuthUrl, resolveAuthUrl, } from "../auth/client.js";
|
|
@@ -80,7 +80,8 @@ function usage(code = 0) {
|
|
|
80
80
|
out(`bir ${VERSION} — a recording MCP proxy
|
|
81
81
|
|
|
82
82
|
Commands:
|
|
83
|
-
setup the one command: sign in, wire this project, start the recorder
|
|
83
|
+
setup the one command: sign in, wire this project, start the recorder, and give
|
|
84
|
+
Claude Code every bir tool (run_scenario, investigate, scenario_*) with no prompt
|
|
84
85
|
up start the recorder for this project in the background (--restart: replace it)
|
|
85
86
|
down stop it (open sessions here then show a hook error until they restart;
|
|
86
87
|
\`bir off --here\` is the quiet way to take it out of the way)
|
|
@@ -119,7 +120,8 @@ Commands:
|
|
|
119
120
|
take back the newest edit (of step n, or parameter name);
|
|
120
121
|
run it again to go one further back; undo an undo's id to redo
|
|
121
122
|
scenario editing on|off|status whether this project's bir MCP server offers the tools
|
|
122
|
-
that change a scenario (restart Claude Code after)
|
|
123
|
+
that read and change a scenario (restart Claude Code after);
|
|
124
|
+
\`bir setup\` turns it on, and an off stays off
|
|
123
125
|
scenario replay <scnId> --prompt "…" [--dry]
|
|
124
126
|
replay --scenario <scnId> --prompt "…" [--dry]
|
|
125
127
|
replay status | on | off | allow <a,b> | allow all
|
|
@@ -770,6 +772,15 @@ async function installWriting(args, cwd, sidecar, plan) {
|
|
|
770
772
|
? " - calculated replay is off here (--no-replay); `bir replay on` turns it on"
|
|
771
773
|
: " - calculated replay is off here: it was not asked for (`bir install --replay` adds it)");
|
|
772
774
|
}
|
|
775
|
+
// EVERY `bir` TOOL, for `bir setup`: investigate and the scenario tools are
|
|
776
|
+
// offered as well as run_scenario (scenario editing on — an `off` a person
|
|
777
|
+
// stored stays off), and Claude Code runs them without asking (below, with
|
|
778
|
+
// the hooks). Only where `bir` is the scenario server: a `bir` server of the
|
|
779
|
+
// person's own gets neither.
|
|
780
|
+
const birToolsHere = args.birTools === true && scenarioServerHere(args, cwd);
|
|
781
|
+
if (birToolsHere && settleEditing(sidecar, cwd)) {
|
|
782
|
+
out(" + scenario editing on: the bir server offers investigate and the scenario tools too (`bir scenario editing off` takes them away)");
|
|
783
|
+
}
|
|
773
784
|
// SessionStart starts the recorder itself when this directory has none
|
|
774
785
|
// (control/ensure-hook.ts). Every invocation gets it — an npx one through
|
|
775
786
|
// npx, once per session — because without it nothing starts the recorder,
|
|
@@ -814,9 +825,35 @@ async function installWriting(args, cwd, sidecar, plan) {
|
|
|
814
825
|
// never asks about; an older install's pre-approval has nothing to approve.
|
|
815
826
|
text = disableMcpjsonServers(paths.settingsLocal, [SCENARIO_SERVER_KEY]) ?? text;
|
|
816
827
|
}
|
|
828
|
+
let allowLine;
|
|
829
|
+
if (args.birTools) {
|
|
830
|
+
// A prompt for a tool the person never named reads as the runner getting
|
|
831
|
+
// in the way, and under `claude -p` it is a refusal. `run_scenario` runs
|
|
832
|
+
// only a plan the recorder armed, the reading tools only read, and a
|
|
833
|
+
// change must reproduce the recording or be saved on purpose with a note
|
|
834
|
+
// (editSteps.md D3) — and `scenario_undo` takes it back.
|
|
835
|
+
const granted = projectRecord(sidecar, cwd)?.allowRules ?? [];
|
|
836
|
+
if (birToolsHere) {
|
|
837
|
+
const allowed = addAllowRules(paths.settingsLocal, [BIR_TOOLS_RULE]);
|
|
838
|
+
text = allowed.text;
|
|
839
|
+
if (allowed.added.length > 0) {
|
|
840
|
+
setProjectRecord(sidecar, cwd, { port, allowRules: [...new Set([...granted, ...allowed.added])] });
|
|
841
|
+
allowLine = ` + ${BIR_TOOLS_RULE} → permissions.allow in the same file (Claude Code runs the bir tools without asking)`;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
else if (granted.length > 0) {
|
|
845
|
+
// `bir` here is not the scenario server any more: the rule an earlier
|
|
846
|
+
// setup added must not approve another server's tools by its name.
|
|
847
|
+
text = removeAllowRules(paths.settingsLocal, granted) ?? text;
|
|
848
|
+
setProjectRecord(sidecar, cwd, { port, allowRules: undefined });
|
|
849
|
+
allowLine = ` - ${granted.join(", ")} — taken out of permissions.allow there: \`${SCENARIO_SERVER_KEY}\` here is not the scenario server`;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
817
852
|
noteWritten(sidecar, paths.settingsLocal, text, claudeJson);
|
|
818
853
|
sidecar.hookFiles = [...new Set([...(sidecar.hookFiles ?? []), paths.settingsLocal])];
|
|
819
854
|
out(` + hooks → ${paths.settingsLocal} (control server on ${controlUrl}; the recorder starts itself with each session${invocation === "npx" ? ", through npx" : ""})`);
|
|
855
|
+
if (allowLine)
|
|
856
|
+
out(allowLine);
|
|
820
857
|
const exclude = ensureGitExcludeLine(cwd);
|
|
821
858
|
if (exclude) {
|
|
822
859
|
out(` + ${LOCAL_SETTINGS_EXCLUDE} is listed in ${exclude.path}`);
|
|
@@ -1847,6 +1884,22 @@ function uninstallProject(cwd, sidecar, opts) {
|
|
|
1847
1884
|
}
|
|
1848
1885
|
}
|
|
1849
1886
|
}
|
|
1887
|
+
// 4b. The rule `bir setup` added so the `bir` server's tools run without a
|
|
1888
|
+
// prompt: only the rules it added, and once that server is gone — so a `bir`
|
|
1889
|
+
// server of the person's own never inherits it. `--replay` takes it too.
|
|
1890
|
+
const projectRec = projectRecord(sidecar, cwd);
|
|
1891
|
+
const granted = projectRec?.allowRules ?? [];
|
|
1892
|
+
if (projectRec && granted.length > 0 && wanted(SCENARIO_SERVER_KEY) && !remaining.has(SCENARIO_SERVER_KEY)) {
|
|
1893
|
+
try {
|
|
1894
|
+
if (removeAllowRules(paths.settingsLocal, granted) !== undefined) {
|
|
1895
|
+
out(` ← ${granted.join(", ")} — taken out of ${paths.settingsLocal} (permissions.allow)`);
|
|
1896
|
+
}
|
|
1897
|
+
setProjectRecord(sidecar, cwd, { port: projectRec.port, allowRules: undefined });
|
|
1898
|
+
}
|
|
1899
|
+
catch (err) {
|
|
1900
|
+
remaining.set(`allow:${normalizePath(paths.settingsLocal)}`, `${granted.join(", ")} in ${paths.settingsLocal} — ${errText(err)}`);
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1850
1903
|
// 5. Read it back: what would a session here spawn now — in this
|
|
1851
1904
|
// environment, and in any other profile the install wrote to?
|
|
1852
1905
|
const after = runnerLeftovers(cwd, sidecar, opts.configPath, otherClaude);
|
|
@@ -3163,6 +3216,18 @@ async function doctor(args) {
|
|
|
3163
3216
|
notes.push("the bir scenario server starts via npx, which can take longer than the first tool call " +
|
|
3164
3217
|
"of a turn — run `bir setup` (or `bir install --replay --global`) to pin it to the installed copy");
|
|
3165
3218
|
}
|
|
3219
|
+
// WHICH `bir` TOOLS A SESSION HERE IS GIVEN. `bir setup` offers every one and
|
|
3220
|
+
// lets Claude Code run them without asking; said only when that is not so.
|
|
3221
|
+
if (scenarioEntry && isScenarioServer(scenarioEntry.config)) {
|
|
3222
|
+
if (!editingEnabled(sidecar, cwd)) {
|
|
3223
|
+
notes.push("the bir MCP server offers run_scenario only: scenario editing is off here — `bir scenario editing on` " +
|
|
3224
|
+
"adds investigate and the scenario tools (restart Claude Code after)");
|
|
3225
|
+
}
|
|
3226
|
+
else if (![claudeUserSettingsPath(), paths.settings, paths.settingsLocal].some((file) => allowRules(file).some((rule) => rule === BIR_TOOLS_RULE || rule === `${BIR_TOOLS_RULE}__*`))) {
|
|
3227
|
+
notes.push("Claude Code asks before investigate and the scenario tools run here — `bir setup` allows every bir tool " +
|
|
3228
|
+
`(${BIR_TOOLS_RULE} in ${paths.settingsLocal})`);
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3166
3231
|
// THE PORT. The hooks' own URLs are the ground truth — they are what every
|
|
3167
3232
|
// hook posts to — checked against the recorder actually answering here. Not
|
|
3168
3233
|
// the machine-wide "last port any install wrote", which named another
|
|
@@ -3761,6 +3826,7 @@ async function main() {
|
|
|
3761
3826
|
npx: false,
|
|
3762
3827
|
forceInvocation: invocation,
|
|
3763
3828
|
replay,
|
|
3829
|
+
birTools: true,
|
|
3764
3830
|
hooks: true,
|
|
3765
3831
|
servers: [],
|
|
3766
3832
|
}),
|
package/dist/bin/setup.d.ts
CHANGED
|
@@ -10,7 +10,11 @@
|
|
|
10
10
|
* 2. the sign-in — a setup token from the console, a cached session, or the
|
|
11
11
|
* browser flow — stored the way `bir login` stores it;
|
|
12
12
|
* 3. the project: MCP servers wrapped, hooks wired, the scenario server added
|
|
13
|
-
* and pre-approved (`bir install --global --replay`)
|
|
13
|
+
* and pre-approved (`bir install --global --replay`) — and every one of
|
|
14
|
+
* its tools with it: investigate and the scenario tools are offered as
|
|
15
|
+
* well as run_scenario, and Claude Code runs them without asking (Eran,
|
|
16
|
+
* 2026-09-28: after setup, every `bir` tool works). A person's
|
|
17
|
+
* `bir scenario editing off` is kept;
|
|
14
18
|
* 4. the recorder, started in the background and from now on started by the
|
|
15
19
|
* SessionStart hook whenever it is missing;
|
|
16
20
|
* 5. a summary that names the account, the servers, the recorder's log, and
|
package/dist/bin/setup.js
CHANGED
|
@@ -10,7 +10,11 @@
|
|
|
10
10
|
* 2. the sign-in — a setup token from the console, a cached session, or the
|
|
11
11
|
* browser flow — stored the way `bir login` stores it;
|
|
12
12
|
* 3. the project: MCP servers wrapped, hooks wired, the scenario server added
|
|
13
|
-
* and pre-approved (`bir install --global --replay`)
|
|
13
|
+
* and pre-approved (`bir install --global --replay`) — and every one of
|
|
14
|
+
* its tools with it: investigate and the scenario tools are offered as
|
|
15
|
+
* well as run_scenario, and Claude Code runs them without asking (Eran,
|
|
16
|
+
* 2026-09-28: after setup, every `bir` tool works). A person's
|
|
17
|
+
* `bir scenario editing off` is kept;
|
|
14
18
|
* 4. the recorder, started in the background and from now on started by the
|
|
15
19
|
* SessionStart hook whenever it is missing;
|
|
16
20
|
* 5. a summary that names the account, the servers, the recorder's log, and
|
|
@@ -35,8 +39,10 @@ import { pidAlive, readDiscovery } from "../control/discovery.js";
|
|
|
35
39
|
import { AUTH_URL_HINT, DeviceFlowAborted, DeviceFlowUnsupported, authenticate, describeAuthService, deviceLogin, loadStoredConfig, normalizeAuthUrl, rememberAuthUrl, tokenLogin, } from "../auth/client.js";
|
|
36
40
|
import { activeSessions, compareVersions, ensureDaemon, SETUP_TOKEN_ENV, stopDaemon, } from "../control/daemon.js";
|
|
37
41
|
import { normalizePath } from "../control/paths.js";
|
|
38
|
-
import {
|
|
42
|
+
import { allowRules, BIR_TOOLS_RULE, claudeCodePaths } from "../config/adapters/claude-code.js";
|
|
43
|
+
import { allocateReplacementPort, editingEnabled, isScenarioServer, isWrapped, LOOPBACK_NO_PROXY, probeFreePort, proxyConfigured, projectRecord, readSidecar, SCENARIO_SERVER_KEY, setProjectRecord, writeSidecar, allocateFreeProjectPort, } from "../config/generate.js";
|
|
39
44
|
import { resolveServers } from "../config/resolve.js";
|
|
45
|
+
import { EDIT_TOOLS, READ_TOOLS } from "./scenario-edit.js";
|
|
40
46
|
import { errText } from "../util/log.js";
|
|
41
47
|
import { offSource } from "../util/off-switch.js";
|
|
42
48
|
import { packageVersion } from "../util/version.js";
|
|
@@ -368,6 +374,14 @@ export async function runSetup(args, deps) {
|
|
|
368
374
|
else {
|
|
369
375
|
out(` replay on — direct execution allowed for: ${allow?.length ? allow.join(", ") : `all wrapped (narrow it with \`bir replay allow ${wrapped[0]}\`)`}`);
|
|
370
376
|
}
|
|
377
|
+
// What the `bir` server gives a session here, and whether Claude Code asks
|
|
378
|
+
// before each call — both set by install for setup (bir.ts, installWriting).
|
|
379
|
+
if (resolveServers(project).some((s) => s.name === SCENARIO_SERVER_KEY && isScenarioServer(s.config))) {
|
|
380
|
+
const asks = !allowRules(claudeCodePaths(project).settingsLocal).includes(BIR_TOOLS_RULE);
|
|
381
|
+
out(editingEnabled(readSidecar(), project)
|
|
382
|
+
? ` tools ${["run_scenario", ...READ_TOOLS, ...EDIT_TOOLS].join(", ")} — ${asks ? "Claude Code asks before each" : "Claude Code runs them without asking, except a forced change"}`
|
|
383
|
+
: " tools run_scenario only — scenario editing is off here (`bir scenario editing on` adds investigate and the scenario tools)");
|
|
384
|
+
}
|
|
371
385
|
if (ensured) {
|
|
372
386
|
const { info, health: h } = ensured.status;
|
|
373
387
|
const theirs = String(h.hooksVersion ?? info.version ?? "");
|
|
@@ -77,6 +77,33 @@ export declare function removeGitExclude(dir: string, line?: string): string | u
|
|
|
77
77
|
export declare function enableMcpjsonServers(settingsPath: string, names: string[]): string;
|
|
78
78
|
/** Undo {@link enableMcpjsonServers} for `names`; drops the key when empty. */
|
|
79
79
|
export declare function disableMcpjsonServers(settingsPath: string, names: string[]): string | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* The rule that lets Claude Code run the runner's own `bir` server's tools
|
|
82
|
+
* without asking. In Claude Code's permission syntax `mcp__<server>` matches
|
|
83
|
+
* every tool that server offers, so it also covers the tools `bir scenario
|
|
84
|
+
* editing on` brings, and any a later runner adds.
|
|
85
|
+
*
|
|
86
|
+
* `bir setup` writes it (bir.ts, installWriting), into the person's own
|
|
87
|
+
* `.claude/settings.local.json` only, and only where `bir` IS the runner's
|
|
88
|
+
* scenario server: the rule matches a server by name, and a server of the
|
|
89
|
+
* person's own that is called `bir` must keep its prompts.
|
|
90
|
+
*/
|
|
91
|
+
export declare const BIR_TOOLS_RULE = "mcp__bir";
|
|
92
|
+
/** The `permissions.allow` rules in a settings file; none when it is missing or unreadable. */
|
|
93
|
+
export declare function allowRules(settingsPath: string): string[];
|
|
94
|
+
/**
|
|
95
|
+
* Add `rules` to `permissions.allow`, leaving every other rule and key alone.
|
|
96
|
+
* Returns the file's new text and the rules that were not there before — the
|
|
97
|
+
* only ones {@link removeAllowRules} may take out again. A `permissions` or
|
|
98
|
+
* `allow` of the wrong shape is left as it is (nothing added): it is the
|
|
99
|
+
* person's to fix, and Claude Code reads neither.
|
|
100
|
+
*/
|
|
101
|
+
export declare function addAllowRules(settingsPath: string, rules: string[]): {
|
|
102
|
+
text: string;
|
|
103
|
+
added: string[];
|
|
104
|
+
};
|
|
105
|
+
/** Undo {@link addAllowRules} for `rules`; drops `allow`, then `permissions`, when left empty. */
|
|
106
|
+
export declare function removeAllowRules(settingsPath: string, rules: string[]): string | undefined;
|
|
80
107
|
/** True when `settingsPath` holds any hook of ours. */
|
|
81
108
|
export declare function hasBirHooks(settingsPath: string): boolean;
|
|
82
109
|
/** Which file holds a given scope's `mcpServers` for this cwd. */
|
|
@@ -24,7 +24,7 @@ import { createHash } from "node:crypto";
|
|
|
24
24
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
|
|
25
25
|
import { dirname, join, parse as parsePath, resolve as resolvePath } from "node:path";
|
|
26
26
|
import { writeFileAtomic } from "../../util/atomic-write.js";
|
|
27
|
-
import { FIRST_PROJECT_PORT, PACKAGE_NAME } from "../generate.js";
|
|
27
|
+
import { FIRST_PROJECT_PORT, PACKAGE_NAME, SCENARIO_SERVER_KEY } from "../generate.js";
|
|
28
28
|
import { claudeJsonPath, parseJsonFile, parseJsonText, projectKeyFor, } from "../resolve.js";
|
|
29
29
|
export function claudeCodePaths(cwd, claudeJsonOverride) {
|
|
30
30
|
return {
|
|
@@ -176,6 +176,74 @@ export function disableMcpjsonServers(settingsPath, names) {
|
|
|
176
176
|
fresh.enabledMcpjsonServers = kept;
|
|
177
177
|
});
|
|
178
178
|
}
|
|
179
|
+
// ── permission rules ───────────────────────────────────────────────────────
|
|
180
|
+
/**
|
|
181
|
+
* The rule that lets Claude Code run the runner's own `bir` server's tools
|
|
182
|
+
* without asking. In Claude Code's permission syntax `mcp__<server>` matches
|
|
183
|
+
* every tool that server offers, so it also covers the tools `bir scenario
|
|
184
|
+
* editing on` brings, and any a later runner adds.
|
|
185
|
+
*
|
|
186
|
+
* `bir setup` writes it (bir.ts, installWriting), into the person's own
|
|
187
|
+
* `.claude/settings.local.json` only, and only where `bir` IS the runner's
|
|
188
|
+
* scenario server: the rule matches a server by name, and a server of the
|
|
189
|
+
* person's own that is called `bir` must keep its prompts.
|
|
190
|
+
*/
|
|
191
|
+
export const BIR_TOOLS_RULE = `mcp__${SCENARIO_SERVER_KEY}`;
|
|
192
|
+
/** The `permissions.allow` rules in a settings file; none when it is missing or unreadable. */
|
|
193
|
+
export function allowRules(settingsPath) {
|
|
194
|
+
let parsed;
|
|
195
|
+
try {
|
|
196
|
+
parsed = parseJsonFile(settingsPath);
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
const allow = parsed?.permissions?.allow;
|
|
202
|
+
return Array.isArray(allow) ? allow.filter((r) => typeof r === "string") : [];
|
|
203
|
+
}
|
|
204
|
+
const plainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
205
|
+
/**
|
|
206
|
+
* Add `rules` to `permissions.allow`, leaving every other rule and key alone.
|
|
207
|
+
* Returns the file's new text and the rules that were not there before — the
|
|
208
|
+
* only ones {@link removeAllowRules} may take out again. A `permissions` or
|
|
209
|
+
* `allow` of the wrong shape is left as it is (nothing added): it is the
|
|
210
|
+
* person's to fix, and Claude Code reads neither.
|
|
211
|
+
*/
|
|
212
|
+
export function addAllowRules(settingsPath, rules) {
|
|
213
|
+
let added = [];
|
|
214
|
+
const text = editJson(settingsPath, (parsed) => {
|
|
215
|
+
added = [];
|
|
216
|
+
if (parsed.permissions !== undefined && !plainObject(parsed.permissions))
|
|
217
|
+
return;
|
|
218
|
+
const permissions = { ...(parsed.permissions ?? {}) };
|
|
219
|
+
if (permissions.allow !== undefined && !Array.isArray(permissions.allow))
|
|
220
|
+
return;
|
|
221
|
+
const allow = [...(permissions.allow ?? [])];
|
|
222
|
+
added = rules.filter((rule) => !allow.includes(rule));
|
|
223
|
+
if (added.length === 0)
|
|
224
|
+
return;
|
|
225
|
+
permissions.allow = [...allow, ...added];
|
|
226
|
+
parsed.permissions = permissions;
|
|
227
|
+
});
|
|
228
|
+
return { text, added };
|
|
229
|
+
}
|
|
230
|
+
/** Undo {@link addAllowRules} for `rules`; drops `allow`, then `permissions`, when left empty. */
|
|
231
|
+
export function removeAllowRules(settingsPath, rules) {
|
|
232
|
+
if (!allowRules(settingsPath).some((rule) => rules.includes(rule)))
|
|
233
|
+
return undefined;
|
|
234
|
+
return editJson(settingsPath, (parsed) => {
|
|
235
|
+
const permissions = parsed.permissions;
|
|
236
|
+
if (!plainObject(permissions) || !Array.isArray(permissions.allow))
|
|
237
|
+
return;
|
|
238
|
+
const kept = permissions.allow.filter((rule) => !(typeof rule === "string" && rules.includes(rule)));
|
|
239
|
+
if (kept.length > 0)
|
|
240
|
+
permissions.allow = kept;
|
|
241
|
+
else
|
|
242
|
+
delete permissions.allow;
|
|
243
|
+
if (Object.keys(permissions).length === 0)
|
|
244
|
+
delete parsed.permissions;
|
|
245
|
+
});
|
|
246
|
+
}
|
|
179
247
|
/** True when `settingsPath` holds any hook of ours. */
|
|
180
248
|
export function hasBirHooks(settingsPath) {
|
|
181
249
|
let parsed;
|
|
@@ -455,12 +455,23 @@ export interface ProjectRecord {
|
|
|
455
455
|
* environment: their definitions ride along with every request of every
|
|
456
456
|
* session, and only somebody working on a scenario calls them.
|
|
457
457
|
*
|
|
458
|
-
* Per project
|
|
459
|
-
*
|
|
460
|
-
*
|
|
461
|
-
*
|
|
458
|
+
* Per project. `bir setup` turns it on (Eran, 2026-09-28: setup leaves every
|
|
459
|
+
* `bir` tool offered and working), except where a person turned it off —
|
|
460
|
+
* that `false` is kept by every later setup (see {@link settleEditing}). A
|
|
461
|
+
* bare `bir install` leaves it as it is, so a fleet wired by hand keeps the
|
|
462
|
+
* old default: the `bir` server runs in every session of every installed
|
|
463
|
+
* project, and there a plan must not change because some agent decided it
|
|
464
|
+
* should. The `bir scenario` commands ignore it: a person in a terminal is
|
|
465
|
+
* asked before each one.
|
|
462
466
|
*/
|
|
463
467
|
editing?: boolean;
|
|
468
|
+
/**
|
|
469
|
+
* The `permissions.allow` rules `bir setup` added to this project's
|
|
470
|
+
* `.claude/settings.local.json` so Claude Code runs the `bir` server's tools
|
|
471
|
+
* without asking (adapters/claude-code.ts, BIR_TOOLS_RULE). Uninstall takes
|
|
472
|
+
* out only these: a rule that was there before is the person's own.
|
|
473
|
+
*/
|
|
474
|
+
allowRules?: string[];
|
|
464
475
|
/**
|
|
465
476
|
* Every `.claude.json` an install for this project wrote local-scope entries
|
|
466
477
|
* to. Claude Code reads `$CLAUDE_CONFIG_DIR/.claude.json` when that is set,
|
|
@@ -588,6 +599,12 @@ export declare function editingEnabled(sidecar: InstalledSidecar, cwd: string):
|
|
|
588
599
|
* inventing a record would invent a port the hooks do not use.
|
|
589
600
|
*/
|
|
590
601
|
export declare function setEditing(sidecar: InstalledSidecar, cwd: string, on: boolean): ProjectRecord | undefined;
|
|
602
|
+
/**
|
|
603
|
+
* `bir setup`: scenario editing on for `cwd`, unless it was ever set — an
|
|
604
|
+
* `off` a person stored is theirs, and no setup turns it back on. Returns true
|
|
605
|
+
* when it was switched on now.
|
|
606
|
+
*/
|
|
607
|
+
export declare function settleEditing(sidecar: InstalledSidecar, cwd: string): boolean;
|
|
591
608
|
/**
|
|
592
609
|
* What an install or uninstall said about calculated replay:
|
|
593
610
|
*
|
package/dist/config/generate.js
CHANGED
|
@@ -823,6 +823,18 @@ export function setEditing(sidecar, cwd, on) {
|
|
|
823
823
|
return undefined;
|
|
824
824
|
return setProjectRecord(sidecar, cwd, { port: record.port, editing: on });
|
|
825
825
|
}
|
|
826
|
+
/**
|
|
827
|
+
* `bir setup`: scenario editing on for `cwd`, unless it was ever set — an
|
|
828
|
+
* `off` a person stored is theirs, and no setup turns it back on. Returns true
|
|
829
|
+
* when it was switched on now.
|
|
830
|
+
*/
|
|
831
|
+
export function settleEditing(sidecar, cwd) {
|
|
832
|
+
const record = projectRecord(sidecar, cwd);
|
|
833
|
+
if (!record || record.editing !== undefined)
|
|
834
|
+
return false;
|
|
835
|
+
setProjectRecord(sidecar, cwd, { port: record.port, editing: true });
|
|
836
|
+
return true;
|
|
837
|
+
}
|
|
826
838
|
/**
|
|
827
839
|
* Keep this project's stored replay switch ({@link ReplayPolicy.enabled}) in
|
|
828
840
|
* line with what the person asked for, so the recorder never steers — or runs
|
package/dist/control/server.js
CHANGED
|
@@ -225,6 +225,12 @@ function isHookRoute(route) {
|
|
|
225
225
|
function isPlanMode(payload) {
|
|
226
226
|
return payload.permission_mode === "plan";
|
|
227
227
|
}
|
|
228
|
+
/** The `bir` tools whose `force` saves (or undoes) what the scenario check refused. */
|
|
229
|
+
const FORCEABLE_BIR_TOOLS = new Set(["scenario_edit", "scenario_undo"].map((tool) => `mcp__${SCENARIO_SERVER_KEY}__${tool}`));
|
|
230
|
+
/** A `bir` call that forces a change past the check (see `onToolPre`). */
|
|
231
|
+
function forcedBirChange(toolName, input) {
|
|
232
|
+
return FORCEABLE_BIR_TOOLS.has(toolName) && input?.force === true;
|
|
233
|
+
}
|
|
228
234
|
/**
|
|
229
235
|
* Tool arguments as an older proxy reports them — redacted, long strings
|
|
230
236
|
* clipped — for joining a report that carries no `argsFingerprint` of its own.
|
|
@@ -2266,6 +2272,20 @@ export class ControlServer {
|
|
|
2266
2272
|
if (toolUseId)
|
|
2267
2273
|
(run.housekeepingUses ??= new Set()).add(toolUseId);
|
|
2268
2274
|
logDetail("tool.pre.skipped", { run: run.runId, tool: toolName, why: "host housekeeping" });
|
|
2275
|
+
// `bir setup` lets Claude Code run every `bir` tool without asking — but a
|
|
2276
|
+
// change saved or taken back with `force`, past the check that refused
|
|
2277
|
+
// it, is the person's decision (editSteps.md D3), so Claude Code asks
|
|
2278
|
+
// whatever the allow rule says. Not where the person chose to be asked
|
|
2279
|
+
// nothing at all.
|
|
2280
|
+
if (forcedBirChange(toolName, modelArgs) && payload.permission_mode !== "bypassPermissions") {
|
|
2281
|
+
return {
|
|
2282
|
+
hookSpecificOutput: {
|
|
2283
|
+
hookEventName: "PreToolUse",
|
|
2284
|
+
permissionDecision: "ask",
|
|
2285
|
+
permissionDecisionReason: "force saves what the scenario check refused — only for a recording that was itself wrong; you decide, not the model",
|
|
2286
|
+
},
|
|
2287
|
+
};
|
|
2288
|
+
}
|
|
2269
2289
|
return {};
|
|
2270
2290
|
}
|
|
2271
2291
|
// What a cancelled direct run left this session to tell (see
|
package/docs/calculatedReplay.md
CHANGED
|
@@ -1456,15 +1456,17 @@ are where `bir` changes a scenario by hand, so the rules are strict:
|
|
|
1456
1456
|
- Exit codes: `0` checked OK, saved or undone; `1` refused, not found, or a service error; `2`
|
|
1457
1457
|
usage. `--json` prints the service's body unchanged.
|
|
1458
1458
|
|
|
1459
|
-
The same commands are tools on the `bir` MCP server
|
|
1460
|
-
|
|
1461
|
-
`
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
`
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1459
|
+
The same commands are tools on the `bir` MCP server. `scenario_show` (`target`, `step`),
|
|
1460
|
+
`scenario_edits` (`target`) and `investigate` (`id`) only read; `scenario_check` (`target`, `step`,
|
|
1461
|
+
`inputLogic`, `outputLogic`, `unfreeze`), `scenario_edit` (the same, plus `freeze`, `force`, `note`,
|
|
1462
|
+
`revision`) and `scenario_undo` (`target`, `edit` or `step`, `force`, `note`) change a scenario. All
|
|
1463
|
+
six are listed where scenario editing is on — `bir setup` turns it on, and adds `mcp__bir` to
|
|
1464
|
+
`permissions.allow` in `.claude/settings.local.json`, so Claude Code runs every `bir` tool without
|
|
1465
|
+
asking. Elsewhere — `bir scenario editing off`, which a later setup keeps, or a project wired by a
|
|
1466
|
+
bare `bir install` — the server lists `run_scenario` alone: it runs in every session of every
|
|
1467
|
+
installed project, and every listed tool's definition rides along with every request.
|
|
1468
|
+
`BIR_SCENARIO_TOOLS=read` in its environment lists the reading three there, and they answer a host
|
|
1469
|
+
that calls them unlisted all the same. Each tool runs this package's own `bir … --json`, with logic bodies
|
|
1468
1470
|
passed through temporary files; a refusal comes back as an MCP error result carrying the report. A
|
|
1469
1471
|
reading tool's answer is capped at 24 000 characters (`BIR_SCENARIO_MAX_OUTPUT_CHARS`) and gives up
|
|
1470
1472
|
after `BIR_READ_TIMEOUT_MS` (15 s) plus 10 s; a changing tool gets 120 s. A call the host cancels, or
|
|
@@ -271,12 +271,14 @@ undoes both, leaving everything else as it is:
|
|
|
271
271
|
- registers a first-party MCP server under the key **`bir`** in the local scope — `~/.claude.json`,
|
|
272
272
|
under Claude Code's key for this project, never the `.mcp.json` a team commits (`--mcp-json` puts
|
|
273
273
|
it there instead, pre-approved in `.claude/settings.local.json`, the older shape). It exposes
|
|
274
|
-
`mcp__bir__run_scenario` — the channel a fully-wrapped scenario's results come back through
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
`
|
|
278
|
-
|
|
279
|
-
|
|
274
|
+
`mcp__bir__run_scenario` — the channel a fully-wrapped scenario's results come back through. Three
|
|
275
|
+
tools that only read a scenario (`scenario_show`, `scenario_edits`, `investigate`) and three for
|
|
276
|
+
changing one (`scenario_check` tries a change, `scenario_edit` saves it, `scenario_undo` takes it
|
|
277
|
+
back) come with scenario editing: `bir setup` turns it on, and adds `mcp__bir` to
|
|
278
|
+
`permissions.allow` in `.claude/settings.local.json` so Claude Code runs every `bir` tool without
|
|
279
|
+
asking. A bare `bir install --replay` does neither: its server lists `run_scenario` alone until
|
|
280
|
+
`bir scenario editing on` (or `BIR_SCENARIO_TOOLS=read` in the server's environment, for the
|
|
281
|
+
reading three). §9.2 lists them with their arguments; the editing it describes
|
|
280
282
|
is built but not yet released. From the next version, a call to any of them except
|
|
281
283
|
`run_scenario` is never recorded. In a subdirectory or linked worktree of a git repository the
|
|
282
284
|
server is not added — in the local scope it would apply to the whole repository — and replay
|
|
@@ -1026,13 +1028,17 @@ check.
|
|
|
1026
1028
|
1. **In its terminal.** Claude runs `bir scenario …` like any other command. You see each command,
|
|
1027
1029
|
and Claude Code asks you before it runs one, unless you allowed it. This works in every project.
|
|
1028
1030
|
2. **As tools on the `bir` MCP server** — the server `bir setup` or `bir install --replay` adds
|
|
1029
|
-
(§5.2). This is smoother for the model.
|
|
1030
|
-
every tool
|
|
1031
|
-
|
|
1031
|
+
(§5.2). This is smoother for the model. **`bir setup` turns scenario editing on, so the server
|
|
1032
|
+
lists every tool below, and Claude Code runs them without asking (`mcp__bir` in
|
|
1033
|
+
`permissions.allow`, `.claude/settings.local.json`) — except `scenario_edit` or `scenario_undo`
|
|
1034
|
+
with `force`: the recorder's `PreToolUse` answers `ask` for those, so the person decides whether
|
|
1035
|
+
a change goes past the check that refused it.** Every listed tool's definition rides along
|
|
1036
|
+
with every request of every session, so the switch is per project, and an `off` is kept by
|
|
1037
|
+
every later setup. A bare `bir install --replay` leaves it off: `run_scenario` alone.
|
|
1032
1038
|
|
|
1033
1039
|
```bash
|
|
1034
1040
|
bir scenario editing on # this project's `bir` server also offers the tools that read and change a scenario
|
|
1035
|
-
bir scenario editing off # back to run_scenario alone
|
|
1041
|
+
bir scenario editing off # back to run_scenario alone; a later `bir setup` keeps it off
|
|
1036
1042
|
bir scenario editing status # what is stored for this project
|
|
1037
1043
|
```
|
|
1038
1044
|
|
|
@@ -1045,12 +1051,12 @@ The tools, as the `bir` server defines them. **Bold** arguments are required.
|
|
|
1045
1051
|
| Tool (`mcp__bir__…`) | Offered | Arguments | Runs |
|
|
1046
1052
|
|---|---|---|---|
|
|
1047
1053
|
| `run_scenario` | always, unless the runner is off | none | the plan armed for this turn (§6.1) |
|
|
1048
|
-
| `scenario_show` |
|
|
1049
|
-
| `scenario_edits` |
|
|
1050
|
-
| `investigate` |
|
|
1051
|
-
| `scenario_check` |
|
|
1052
|
-
| `scenario_edit` |
|
|
1053
|
-
| `scenario_undo` |
|
|
1054
|
+
| `scenario_show` | with scenario editing on (`bir setup` turns it on), or with `BIR_SCENARIO_TOOLS=read` | **`target`**, `step` | `bir scenario show` |
|
|
1055
|
+
| `scenario_edits` | with scenario editing on, or with `BIR_SCENARIO_TOOLS=read` | **`target`** | `bir scenario edits` |
|
|
1056
|
+
| `investigate` | with scenario editing on, or with `BIR_SCENARIO_TOOLS=read` | `id` — a `run_`, `scn_` or `sexec_` id; without it, the newest turn here | `bir investigate` |
|
|
1057
|
+
| `scenario_check` | with scenario editing on | **`target`**, **`step`**, `inputLogic`, `outputLogic`, `unfreeze` | `bir scenario check` |
|
|
1058
|
+
| `scenario_edit` | with scenario editing on | **`target`**, and **`step`** with `inputLogic`, `outputLogic`, `freeze`, `unfreeze`, `force` — or **`param`** with **`kind`**; `note`, `revision` | `bir scenario edit` |
|
|
1059
|
+
| `scenario_undo` | with scenario editing on | **`target`**, one of `edit`, `step` or `param`, `force`, `note` | `bir scenario undo` |
|
|
1054
1060
|
|
|
1055
1061
|
- `target` is a `scn_` id, or the `run_` id of a whole-run scenario. `step` is the `stepIndex`.
|
|
1056
1062
|
- `inputLogic` and `outputLogic` are the code itself, as text. The server writes them to temporary
|
|
@@ -1174,8 +1180,9 @@ Parameter database: target → setting (revision 4). Undo: bir scenario undo scn
|
|
|
1174
1180
|
| `payload_too_large` | The whole request is larger than the service accepts (HTTP 413, over 256 kB) | A logic body may be at most 20 000 characters, and a note 500 |
|
|
1175
1181
|
| `rate_limited` | More checks, edits and undos in a minute than the service allows (`SCENARIO_EDITS_PER_MINUTE`, 30 by default) | Wait a minute |
|
|
1176
1182
|
| `not found` for a scenario you can see | It is not yours. **Only the owner can change a scenario** — an admin gets `not found` too | Ask the owner |
|
|
1177
|
-
| The `scenario_check`, `scenario_edit` and `scenario_undo` tools are missing | `bir scenario editing
|
|
1178
|
-
| Only `run_scenario` is listed; `scenario_show`, `scenario_edits` and `investigate` are missing |
|
|
1183
|
+
| The `scenario_check`, `scenario_edit` and `scenario_undo` tools are missing | Scenario editing is off here: `bir scenario editing off` was run (a later setup keeps it off), the project was wired by `bir install` rather than `bir setup`, or the session started before the switch | `bir scenario editing on` here (or `bir setup`), then restart the session |
|
|
1184
|
+
| Only `run_scenario` is listed; `scenario_show`, `scenario_edits` and `investigate` are missing | The same: the reading tools come with scenario editing, or with `BIR_SCENARIO_TOOLS=read` in the server's environment | Either one, then restart the session. Or run the `bir scenario …` commands in the terminal |
|
|
1185
|
+
| Claude Code asks before each `bir` tool | No `mcp__bir` in `permissions.allow`: the project was wired by `bir install`, or the rule was taken out | `bir setup` here adds it (`bir doctor` says when it is missing) |
|
|
1179
1186
|
| The `bir` server lists no tools at all | The runner is off here (`bir off`, `bir off --here`, or `BIR_OFF=1` where Claude Code started) | `bir on` (or `bir on --here`), and the server tells the open session its list changed. For `BIR_OFF=1`, start Claude Code without it |
|
|
1180
1187
|
|
|
1181
1188
|
---
|
package/docs/quickstart.md
CHANGED
|
@@ -75,7 +75,9 @@ if it is, it stops and asks you to `cd` to the project and paste again. Then:
|
|
|
75
75
|
==> Wiring the project
|
|
76
76
|
+ chrome-devtools → bir-proxy (local scope, over the .mcp.json entry; upstream: npx)
|
|
77
77
|
+ bir → bir-scenario (calculated replay; local scope)
|
|
78
|
+
+ scenario editing on: the bir server offers investigate and the scenario tools too (`bir scenario editing off` takes them away)
|
|
78
79
|
+ hooks → C:\work\repo\.claude\settings.local.json (control server on http://127.0.0.1:53411; the recorder starts itself with each session)
|
|
80
|
+
+ mcp__bir → permissions.allow in the same file (Claude Code runs the bir tools without asking)
|
|
79
81
|
+ .claude/settings.local.json is listed in C:\work\repo\.git\info\exclude
|
|
80
82
|
|
|
81
83
|
Wrapped 1 server.
|
|
@@ -86,6 +88,7 @@ Done. Recording and replay are on for C:\work\repo
|
|
|
86
88
|
account you@example.com (https://api.bi2202.com)
|
|
87
89
|
wrapped chrome-devtools
|
|
88
90
|
replay on — direct execution allowed for: all wrapped (narrow it with `bir replay allow chrome-devtools`)
|
|
91
|
+
tools run_scenario, scenario_show, scenario_edits, investigate, scenario_check, scenario_edit, scenario_undo — Claude Code runs them without asking, except a forced change
|
|
89
92
|
recorder started in the background (pid 1234) — it starts itself with every Claude Code session
|
|
90
93
|
log C:\Users\you\.baseinstrunner\logs\3f9c1a2b7d4e5f60.log
|
|
91
94
|
|
|
@@ -104,8 +107,11 @@ is not changed;
|
|
|
104
107
|
*replay* says which of them a calculated scenario may call unattended — narrow
|
|
105
108
|
it with `bir replay allow <names>`, turn it off with `bir replay off`, and read
|
|
106
109
|
[calculatedReplayGuide.md §5.1](calculatedReplayGuide.md) before leaving it wide
|
|
107
|
-
anywhere that matters; *
|
|
108
|
-
|
|
110
|
+
anywhere that matters; *tools* is what the `bir` MCP server gives Claude here —
|
|
111
|
+
every tool, and Claude Code runs them without asking, except a change forced
|
|
112
|
+
past the scenario check (`bir scenario editing off` leaves `run_scenario` alone,
|
|
113
|
+
and a later setup keeps it that way); *recorder* is
|
|
114
|
+
the background process, and *log* is where its audit lines go.
|
|
109
115
|
|
|
110
116
|
A project with no MCP servers in any scope — none in `.mcp.json`, and none for
|
|
111
117
|
this project or your user in `~/.claude.json` — shows `wrapped (no MCP servers
|
|
@@ -190,12 +196,12 @@ its prompt, its tool calls and its final answer.
|
|
|
190
196
|
|---|---|
|
|
191
197
|
| `~/.baseinstrunner/config.json` | the service address; `BIR_AUTH_URL` in the environment overrides it, and `bir setup` warns when the two differ |
|
|
192
198
|
| `~/.baseinstrunner/credentials.json` | the cached session, bound to the service that issued it |
|
|
193
|
-
| `~/.baseinstrunner/installed.json` | what `bir uninstall` needs to undo each change — the original text of the project files it rewrote, with their credential values left out — and each project's port, token and replay switches,
|
|
199
|
+
| `~/.baseinstrunner/installed.json` | what `bir uninstall` needs to undo each change — the original text of the project files it rewrote, with their credential values left out — and each project's port, token and replay switches, whether Claude's `bir` tools may change scenarios there (`bir setup` turns it on, `bir scenario editing off` turns it off), and the permission rule setup added |
|
|
194
200
|
| `~/.baseinstrunner/logs/<key>.log` | the recorder's audit log, one file per project |
|
|
195
201
|
| `~/.baseinstrunner/node/` | a private Node, only if the machine had no usable one; not on PATH |
|
|
196
202
|
| `~/.baseinstrunner/npm/` | the runner package |
|
|
197
203
|
| `~/.baseinstrunner/bin/` | the `bir` and `bir-hooks` launchers, first on your user PATH |
|
|
198
|
-
| `<project>/.claude/settings.local.json` | Claude Code's hooks for this project. Yours, not the team's: it carries this project's token and an absolute Node path |
|
|
204
|
+
| `<project>/.claude/settings.local.json` | Claude Code's hooks for this project, and `mcp__bir` in `permissions.allow` so the `bir` server's tools run without asking. Yours, not the team's: it carries this project's token and an absolute Node path |
|
|
199
205
|
| `~/.claude.json` (`$CLAUDE_CONFIG_DIR/.claude.json` when that is set), this project's local scope | a copy of each MCP server it wraps, under the same name, running through `bir-proxy`; plus the `bir` scenario server. The copy takes precedence in this project only; `.mcp.json` and your user-scope servers are not changed |
|
|
200
206
|
| `<project>/.git/info/exclude` | one line, `.claude/settings.local.json`, so it never shows in `git status` |
|
|
201
207
|
|
|
@@ -363,12 +369,16 @@ The saved code runs on your machine without asking you, like the code the
|
|
|
363
369
|
calculation wrote. The check only tests what it sends for the recording's
|
|
364
370
|
values. So read the code before it is saved, yours or Claude's.
|
|
365
371
|
|
|
366
|
-
Claude can do this for you. Ask it in the session
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
+
Claude can do this for you. Ask it in the session: `bir setup` gave the `bir`
|
|
373
|
+
server the tools that read and change a scenario, and Claude Code runs them
|
|
374
|
+
without asking you. The service still refuses a change that breaks the
|
|
375
|
+
recording, and a change Claude wants to force past that check (`force`) is
|
|
376
|
+
still yours to approve: Claude Code asks you first. `bir scenario edits` shows
|
|
377
|
+
what Claude changed; `bir scenario undo` takes it back. `bir scenario editing off` in the project
|
|
378
|
+
folder takes those tools away (restart Claude Code after), and a later
|
|
379
|
+
`bir setup` leaves them off; Claude can still run the same commands in its
|
|
380
|
+
terminal, and Claude Code then asks you before each one. These `bir` calls are
|
|
381
|
+
never recorded as part of your task.
|
|
372
382
|
|
|
373
383
|
---
|
|
374
384
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@basein/runner",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "A recording MCP proxy: sits between any MCP client and its MCP servers, executes each call on the client's behalf, and records the run as a reusable BaseIn scenario.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/production.md
CHANGED
|
@@ -635,7 +635,7 @@ Truncation is always marked with the original size.
|
|
|
635
635
|
|
|
636
636
|
### 6.8 The `bir` MCP server (`src/bin/bir-scenario.ts`, `scenario-liveness.ts`, `service-read.ts`)
|
|
637
637
|
|
|
638
|
-
**Tools listed.** `tools/list` returns
|
|
638
|
+
**Tools listed.** `tools/list` returns every tool where scenario editing is on, which `bir setup` turns on (it also adds `mcp__bir` to `permissions.allow` in `.claude/settings.local.json`, so Claude Code runs them without asking — except `scenario_edit` or `scenario_undo` with `force`, which the recorder's `PreToolUse` answers with `ask` unless the session is in `bypassPermissions` mode). Where it is off — `bir scenario editing off`, which a later setup keeps, or a project wired by a bare `bir install` — it returns only `run_scenario`, and the read tools come with `BIR_SCENARIO_TOOLS=read`, because every listed tool's definition rides along with every request of every session.
|
|
639
639
|
|
|
640
640
|
**`run_scenario`.**
|
|
641
641
|
|
|
@@ -678,7 +678,7 @@ Truncation is always marked with the original size.
|
|
|
678
678
|
| The direct plan budget is 100 s (was 120 s), and direct runs are clamped to 270 s. | Keeps a direct run under Claude Code's 2-minute auto-background of an MCP call, and inside `run_scenario`'s client timeout. | `BIR_REPLAY_PLAN_BUDGET_MS=120000` (or `BIR_REPLAY_BUDGET_MS`). |
|
|
679
679
|
| The background recorder ignores an inherited `ANTHROPIC_API_KEY` for derivation. | That key is Claude Code's, so derivation billed the user's own account. | `BIR_DERIVE_API_KEY=<key>`, or `BIR_USE_ANTHROPIC_API_KEY=1`. A `bir-hooks` run by hand still uses the terminal's key. |
|
|
680
680
|
| `run_scenario` never returns `isError`. | An error result makes the model retry or report a failure the user cannot act on. The reason is in `scenario.answered`. | `BIR_SCENARIO_DEBUG=1`. |
|
|
681
|
-
|
|
|
681
|
+
| A bare `bir install` leaves the `bir` server listing only `run_scenario`, and adds no permission rule. | Tool definitions ride along with every request, and a fleet wired by hand must not have its plans changed by an agent. `bir setup` turns both on. | `bir scenario editing on` (or `BIR_SCENARIO_TOOLS=read` for the read tools), and `mcp__bir` in `permissions.allow`. |
|
|
682
682
|
| The `run_scenario` client timeout is 290 s (was 180 s). | It stays under Claude Code's 5-minute MCP idle timeout, above the direct-run worst case. | `BIR_SCENARIO_RUN_TIMEOUT_MS=180000`. |
|
|
683
683
|
| `bir-proxy` exits when its upstream dies. | Claude Code then shows the server as failed and offers a reconnect, as unwrapped. Staying up left tools listed that only answered errors. | `BIR_PROXY_STAY_UP=1`. |
|
|
684
684
|
| Proxy stderr is quiet: routine lines only with verbose logging. | Claude Code files every stderr line as a "Server stderr" error. | `BIR_VERBOSE=1`. |
|