@basein/runner 0.2.12 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +540 -369
- package/dist/auth/client.d.ts +65 -3
- package/dist/auth/client.js +305 -53
- package/dist/bin/bir-hooks.d.ts +72 -31
- package/dist/bin/bir-hooks.js +176 -284
- package/dist/bin/bir-proxy.d.ts +17 -2
- package/dist/bin/bir-proxy.js +222 -28
- package/dist/bin/bir-scenario.d.ts +61 -21
- package/dist/bin/bir-scenario.js +593 -122
- package/dist/bin/bir.d.ts +12 -6
- package/dist/bin/bir.js +2935 -489
- package/dist/bin/investigate.js +40 -15
- package/dist/bin/scenario-edit.d.ts +7 -1
- package/dist/bin/scenario-edit.js +43 -21
- package/dist/bin/scenario-liveness.d.ts +194 -0
- package/dist/bin/scenario-liveness.js +459 -0
- package/dist/bin/scenario-stderr.d.ts +43 -0
- package/dist/bin/scenario-stderr.js +76 -0
- package/dist/bin/service-read.d.ts +88 -0
- package/dist/bin/service-read.js +128 -0
- package/dist/bin/setup.d.ts +19 -0
- package/dist/bin/setup.js +146 -20
- package/dist/config/adapters/claude-code.d.ts +92 -14
- package/dist/config/adapters/claude-code.js +291 -101
- package/dist/config/adapters/generic.d.ts +4 -1
- package/dist/config/adapters/generic.js +13 -14
- package/dist/config/generate.d.ts +392 -14
- package/dist/config/generate.js +666 -19
- package/dist/config/mcp-approval.d.ts +76 -0
- package/dist/config/mcp-approval.js +210 -0
- package/dist/config/node-path.d.ts +42 -0
- package/dist/config/node-path.js +102 -0
- package/dist/config/overrides.d.ts +62 -0
- package/dist/config/overrides.js +136 -0
- package/dist/config/resolve.d.ts +70 -8
- package/dist/config/resolve.js +186 -15
- package/dist/control/client.d.ts +22 -0
- package/dist/control/client.js +26 -7
- package/dist/control/correlation.d.ts +23 -5
- package/dist/control/correlation.js +0 -0
- package/dist/control/daemon.d.ts +400 -18
- package/dist/control/daemon.js +1105 -102
- package/dist/control/discovery.d.ts +77 -5
- package/dist/control/discovery.js +186 -11
- package/dist/control/ensure-hook.d.ts +61 -0
- package/dist/control/ensure-hook.js +160 -8
- package/dist/control/paths.d.ts +26 -0
- package/dist/control/paths.js +34 -0
- package/dist/control/pin-guard.d.ts +83 -0
- package/dist/control/pin-guard.js +534 -0
- package/dist/control/recorder-keeper.d.ts +229 -0
- package/dist/control/recorder-keeper.js +721 -0
- package/dist/control/recorder-main.d.ts +73 -0
- package/dist/control/recorder-main.js +778 -0
- package/dist/control/server.d.ts +707 -31
- package/dist/control/server.js +2786 -302
- package/dist/control/transcript.d.ts +21 -1
- package/dist/control/transcript.js +378 -112
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -1
- package/dist/jsonrpc/framing.d.ts +76 -12
- package/dist/jsonrpc/framing.js +216 -59
- package/dist/proxy/intercept.d.ts +49 -5
- package/dist/proxy/intercept.js +188 -59
- package/dist/proxy/relay.d.ts +16 -0
- package/dist/proxy/relay.js +77 -5
- package/dist/proxy/revive.d.ts +122 -0
- package/dist/proxy/revive.js +251 -0
- package/dist/proxy/session.d.ts +222 -14
- package/dist/proxy/session.js +708 -99
- package/dist/record/recorder.d.ts +51 -3
- package/dist/record/recorder.js +3 -0
- package/dist/record/redact.d.ts +103 -9
- package/dist/record/redact.js +659 -30
- package/dist/record/remote-recorder.d.ts +327 -32
- package/dist/record/remote-recorder.js +1517 -187
- package/dist/record/switchable-recorder.d.ts +126 -0
- package/dist/record/switchable-recorder.js +351 -0
- package/dist/record/truncate.d.ts +40 -1
- package/dist/record/truncate.js +111 -4
- package/dist/replay/bash-channel.d.ts +35 -0
- package/dist/replay/bash-channel.js +133 -0
- package/dist/replay/bundle.d.ts +10 -0
- package/dist/replay/bundle.js +10 -0
- package/dist/replay/controller.d.ts +369 -20
- package/dist/replay/controller.js +1176 -107
- package/dist/replay/derive.d.ts +1 -0
- package/dist/replay/derive.js +81 -24
- package/dist/replay/executor.d.ts +51 -16
- package/dist/replay/executor.js +114 -41
- package/dist/replay/handover.d.ts +54 -1
- package/dist/replay/handover.js +55 -5
- package/dist/replay/logic.d.ts +62 -10
- package/dist/replay/logic.js +140 -22
- package/dist/replay/plan.d.ts +201 -27
- package/dist/replay/plan.js +581 -141
- package/dist/replay/safety.d.ts +72 -0
- package/dist/replay/safety.js +303 -0
- package/dist/replay/source-run.d.ts +10 -0
- package/dist/replay/source-run.js +80 -35
- package/dist/replay/targets.d.ts +15 -1
- package/dist/replay/targets.js +62 -4
- package/dist/replay/tool-error.d.ts +15 -1
- package/dist/replay/tool-error.js +33 -1
- package/dist/replay/types.d.ts +7 -4
- package/dist/upstream/client.d.ts +56 -3
- package/dist/upstream/client.js +61 -4
- package/dist/upstream/headers.d.ts +43 -0
- package/dist/upstream/headers.js +90 -0
- package/dist/upstream/http-client.d.ts +213 -22
- package/dist/upstream/http-client.js +1034 -168
- package/dist/upstream/http-transport.d.ts +143 -0
- package/dist/upstream/http-transport.js +391 -0
- package/dist/upstream/lazy-client.d.ts +8 -0
- package/dist/upstream/lazy-client.js +29 -1
- package/dist/upstream/sse.d.ts +78 -0
- package/dist/upstream/sse.js +189 -0
- package/dist/upstream/stdio-client.d.ts +61 -1
- package/dist/upstream/stdio-client.js +356 -33
- package/dist/util/atomic-write.d.ts +29 -0
- package/dist/util/atomic-write.js +96 -0
- package/dist/util/journal.d.ts +15 -0
- package/dist/util/journal.js +48 -3
- package/dist/util/log.d.ts +10 -0
- package/dist/util/log.js +28 -1
- package/dist/util/off-switch.d.ts +46 -0
- package/dist/util/off-switch.js +91 -0
- package/dist/util/service-health.d.ts +133 -0
- package/dist/util/service-health.js +217 -0
- package/docs/BaseInstRunner.md +694 -621
- package/docs/calculatedReplay.md +1625 -1304
- package/docs/calculatedReplayGuide.md +1212 -1087
- package/docs/installRun.md +1013 -772
- package/docs/loginWeb.md +627 -607
- package/docs/mcpmark.md +49 -16
- package/docs/my-first-sample.md +569 -545
- package/docs/quickstart.md +384 -311
- package/docs/t-bench.md +406 -394
- package/package.json +58 -57
- package/production.md +1371 -0
- package/scripts/install.ps1 +97 -6
- package/scripts/install.sh +104 -7
|
@@ -7,21 +7,34 @@
|
|
|
7
7
|
* model-visible edit (a non-goal) as well as a broken scenario corpus.
|
|
8
8
|
*
|
|
9
9
|
* Reversibility is not an afterthought (Phase 7.4): the original entry is stashed
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* in `~/.baseinstrunner/installed.json` before anything is rewritten — verbatim
|
|
11
|
+
* but for its env and header VALUES, which uninstall reads back from the wrapped
|
|
12
|
+
* entry — so `bir uninstall` restores exactly what was there, including fields
|
|
12
13
|
* BaseInstRunner never understood.
|
|
13
14
|
*/
|
|
14
|
-
import { type McpServerConfig, type Scope } from "./resolve.js";
|
|
15
|
+
import { type McpServerConfig, type RemoteServerConfig, type Scope } from "./resolve.js";
|
|
15
16
|
/** npm package name, as it appears in a generated `npx -p` invocation. */
|
|
16
17
|
export declare const PACKAGE_NAME = "@basein/runner";
|
|
17
18
|
/**
|
|
18
19
|
* How the generated entry reaches a `bir` binary.
|
|
19
20
|
*
|
|
20
|
-
* npx `npx -y -p @basein/runner@<version> bir-proxy` —
|
|
21
|
-
* registry, so it survives a missing global
|
|
22
|
-
* an unpinned spec lets two runners
|
|
23
|
-
* different proxy versions against the
|
|
24
|
-
* the recording to say which.
|
|
21
|
+
* npx `npx --prefer-offline -y -p @basein/runner@<version> bir-proxy` —
|
|
22
|
+
* resolves through the registry, so it survives a missing global
|
|
23
|
+
* install. Always pinned: an unpinned spec lets two runners
|
|
24
|
+
* provisioned a week apart run different proxy versions against the
|
|
25
|
+
* same service, with nothing in the recording to say which. Pinned is
|
|
26
|
+
* also what makes `--prefer-offline` safe: an exact version in the
|
|
27
|
+
* cache cannot be stale, and without it a cached package still waits
|
|
28
|
+
* on the registry — measured at 70 s per server start offline, past
|
|
29
|
+
* the host's MCP connect timeout. An MCP entry is plain `npx` on
|
|
30
|
+
* every platform: Claude Code spawns MCP servers through cross-spawn,
|
|
31
|
+
* which finds `npx.cmd` on Windows and escapes every argument for
|
|
32
|
+
* cmd.exe itself. `cmd /c npx …` would hand the user's own arguments
|
|
33
|
+
* (a URL with `&`, a `^1.2` range, a `%VAR%`) to cmd.exe unescaped.
|
|
34
|
+
* Only the SessionStart hook, whose arguments are all ours, keeps
|
|
35
|
+
* `cmd /c` there ({@link npxHookInvocation}).
|
|
36
|
+
* Only on request (`--npx`), or when `bir` itself runs from the npx
|
|
37
|
+
* cache: every spawn pays ~1 s of npx start-up.
|
|
25
38
|
* local an absolute path into this checkout — developing BaseInstRunner.
|
|
26
39
|
* global an absolute path into the globally installed package. No registry
|
|
27
40
|
* round trip, no PATH lookup, works offline. What a fleet wants.
|
|
@@ -66,21 +79,127 @@ export interface WrapOptions {
|
|
|
66
79
|
* trust what the install did (see {@link nodeFlagsToCarry}).
|
|
67
80
|
*/
|
|
68
81
|
nodeFlags?: string[];
|
|
82
|
+
/**
|
|
83
|
+
* The interpreter for `local` and `global` entries. Default
|
|
84
|
+
* `process.execPath`; `bir install` passes the stable path it chose
|
|
85
|
+
* (node-path.ts), the same one the SessionStart hook names.
|
|
86
|
+
*/
|
|
87
|
+
node?: string;
|
|
88
|
+
/**
|
|
89
|
+
* Default `process.platform`. Kept for callers that pass one; an npx MCP
|
|
90
|
+
* entry is the same plain `npx` shape on every platform now (see
|
|
91
|
+
* {@link npxInvocation}).
|
|
92
|
+
*/
|
|
93
|
+
platform?: NodeJS.Platform;
|
|
69
94
|
}
|
|
95
|
+
/** True when an HTTP(S) proxy is configured in `env` (either spelling). */
|
|
96
|
+
export declare function proxyConfigured(env?: NodeJS.ProcessEnv): boolean;
|
|
97
|
+
/**
|
|
98
|
+
* True when NO_PROXY (either spelling) sends 127.0.0.1 straight to the socket.
|
|
99
|
+
*
|
|
100
|
+
* `--use-env-proxy` sends EVERY request through HTTP(S)_PROXY — the runner's own
|
|
101
|
+
* loopback traffic too: the hooks' recorder, a proxy registering, `bir setup`
|
|
102
|
+
* waiting for the recorder — unless NO_PROXY exempts the address. Measured on
|
|
103
|
+
* Node 24: `127.0.0.1`, `*` and a suffix such as `.0.0.1` do; `localhost`,
|
|
104
|
+
* `127.0.0.0/8` and `127.*` do not. Node and undici disagree on which spelling
|
|
105
|
+
* wins when both are set, so every one that is set must exempt it.
|
|
106
|
+
*/
|
|
107
|
+
export declare function loopbackBypassesProxy(env?: NodeJS.ProcessEnv): boolean;
|
|
108
|
+
/** The addresses to add to NO_PROXY so the runner's loopback traffic skips the proxy. */
|
|
109
|
+
export declare const LOOPBACK_NO_PROXY = "127.0.0.1,localhost,::1";
|
|
110
|
+
/**
|
|
111
|
+
* What to tell a person whose proxy the runner will NOT use, or undefined. A
|
|
112
|
+
* proxy with a NO_PROXY that does not name 127.0.0.1 would swallow the runner's
|
|
113
|
+
* own loopback calls, so `--use-env-proxy` is left out rather than break every
|
|
114
|
+
* hook; the service is then reached directly, if at all.
|
|
115
|
+
*/
|
|
116
|
+
export declare function envProxyHint(env?: NodeJS.ProcessEnv): string | undefined;
|
|
117
|
+
/**
|
|
118
|
+
* Make THIS process's loopback fetches skip the proxy when it runs with
|
|
119
|
+
* `--use-env-proxy` and NO_PROXY does not already exempt them: a launcher
|
|
120
|
+
* written behind a proxy keeps the flag after the shell's NO_PROXY changes.
|
|
121
|
+
* undici re-reads NO_PROXY per request, so fetch honours the change; so does
|
|
122
|
+
* every process started from here (a recorder `bir up` launches). Returns true
|
|
123
|
+
* when it changed anything. The flags carried into the entries are decided
|
|
124
|
+
* from the environment as it was ({@link nodeFlagsToCarry}).
|
|
125
|
+
*/
|
|
126
|
+
export declare function exemptLoopbackFromProxy(env?: NodeJS.ProcessEnv, execArgv?: string[]): boolean;
|
|
70
127
|
/**
|
|
71
128
|
* The node flags this process was started with that everything it sets up
|
|
72
129
|
* should share. The install scripts run `bir setup` with `--use-system-ca`
|
|
73
130
|
* when the Node supports it; a hook, a proxy or a recorder started without it
|
|
74
131
|
* would then fail the first TLS-inspected upload while the install itself
|
|
75
132
|
* succeeded.
|
|
133
|
+
*
|
|
134
|
+
* `--use-env-proxy` is carried the same way, and added when HTTPS_PROXY or
|
|
135
|
+
* HTTP_PROXY is set and this Node knows the flag: Node's `fetch` ignores those
|
|
136
|
+
* variables otherwise, so behind a proxy-only network every service call waited
|
|
137
|
+
* out a connect timeout. A FLAG rather than `NODE_USE_ENV_PROXY=1` in the
|
|
138
|
+
* entries' env on purpose: `bir-proxy` hands its environment to the user's own
|
|
139
|
+
* MCP server, and that server's networking is not ours to change.
|
|
140
|
+
*
|
|
141
|
+
* ONLY WHEN NO_PROXY EXEMPTS 127.0.0.1 ({@link loopbackBypassesProxy}). With the
|
|
142
|
+
* flag, Node sends the runner's loopback calls to the proxy too, which refuses
|
|
143
|
+
* or swallows them: the SessionStart hook could not see its own recorder, the
|
|
144
|
+
* proxies could not register, and `bir setup` timed out — on a network where
|
|
145
|
+
* all of that worked without the flag. So without that exemption the flag is
|
|
146
|
+
* neither added nor carried, and {@link envProxyHint} says what to set.
|
|
147
|
+
*/
|
|
148
|
+
export declare function nodeFlagsToCarry(execArgv?: string[], env?: NodeJS.ProcessEnv): string[];
|
|
149
|
+
/**
|
|
150
|
+
* A wrapped entry is always a stdio entry: the proxy is the process the host
|
|
151
|
+
* spawns. Fields the client understands and the proxy does not care about
|
|
152
|
+
* (`disabled`, `autoApprove`, `timeout`, …) are carried over from the original.
|
|
76
153
|
*/
|
|
77
|
-
export declare function nodeFlagsToCarry(execArgv?: string[]): string[];
|
|
78
|
-
/** A wrapped entry is always a stdio entry: the proxy is the process the host spawns. */
|
|
79
154
|
export interface WrappedEntry {
|
|
80
155
|
command: string;
|
|
81
156
|
args: string[];
|
|
82
157
|
env?: Record<string, string>;
|
|
158
|
+
[field: string]: unknown;
|
|
83
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* The env variable a wrapped REMOTE entry carries its upstream's static
|
|
162
|
+
* headers in: a JSON object, `{"Header-Name": "value", …}`. bir-proxy reads it
|
|
163
|
+
* (merged with any `--header K:V` arguments, which an entry an older runner
|
|
164
|
+
* wrote still has), and never hands it to a stdio upstream. In the env rather
|
|
165
|
+
* than on the command line because argv is visible to every local user
|
|
166
|
+
* (`ps`, /proc), and a header is usually a credential.
|
|
167
|
+
*/
|
|
168
|
+
export declare const UPSTREAM_HEADERS_ENV = "BIR_UPSTREAM_HEADERS";
|
|
169
|
+
/**
|
|
170
|
+
* The headers in a wrapped entry's {@link UPSTREAM_HEADERS_ENV}, or none. Only
|
|
171
|
+
* string values count; anything unreadable is no headers rather than an error.
|
|
172
|
+
*/
|
|
173
|
+
export declare function upstreamHeadersFromEnv(env: Record<string, string> | undefined): Record<string, string>;
|
|
174
|
+
/**
|
|
175
|
+
* `npx … <bin>`, pinned and offline-first, for an MCP server entry.
|
|
176
|
+
*
|
|
177
|
+
* Plain `npx` on every platform, Windows included. Claude Code spawns MCP
|
|
178
|
+
* servers through cross-spawn, which resolves `npx` to `npx.cmd` and escapes
|
|
179
|
+
* each argument for cmd.exe the way cmd.exe reads it. Wrapping the entry in
|
|
180
|
+
* `cmd /c` instead hands cmd.exe a line it parses itself — and everything after
|
|
181
|
+
* `--` is the user's own server invocation: `?a=1&b=2` in a URL ran `b=2` as a
|
|
182
|
+
* command, `|` and `>` piped or redirected the proxy's stdout, `^1.2.0` lost its
|
|
183
|
+
* caret and `%VAR%` was expanded. `platform` is accepted and ignored.
|
|
184
|
+
*/
|
|
185
|
+
export declare function npxInvocation(bin: string, version: string | undefined, _platform?: NodeJS.Platform): {
|
|
186
|
+
command: string;
|
|
187
|
+
args: string[];
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* `npx … <bin>` for a COMMAND HOOK (the SessionStart `bir-hooks ensure`).
|
|
191
|
+
*
|
|
192
|
+
* Claude Code spawns a hook's exec form directly, without cross-spawn's
|
|
193
|
+
* `.cmd` resolution, so on native Windows it keeps `cmd /c` — safe there
|
|
194
|
+
* because every argument is ours: a pinned package spec, a bin name and
|
|
195
|
+
* fixed flags, never a user's text.
|
|
196
|
+
*/
|
|
197
|
+
export declare function npxHookInvocation(bin: string, version: string | undefined, platform?: NodeJS.Platform): {
|
|
198
|
+
command: string;
|
|
199
|
+
args: string[];
|
|
200
|
+
};
|
|
201
|
+
/** True when an entry is spawned through npx (with or without `cmd /c`). */
|
|
202
|
+
export declare function isNpxEntry(config: McpServerConfig): boolean;
|
|
84
203
|
/** True when this entry already points at `bir-proxy` — makes install idempotent. */
|
|
85
204
|
export declare function isWrapped(config: McpServerConfig): boolean;
|
|
86
205
|
/**
|
|
@@ -89,6 +208,60 @@ export declare function isWrapped(config: McpServerConfig): boolean;
|
|
|
89
208
|
* host would have spawned it.
|
|
90
209
|
*/
|
|
91
210
|
export declare function wrapEntry(config: McpServerConfig, opts: WrapOptions): WrappedEntry;
|
|
211
|
+
/**
|
|
212
|
+
* Rebuild the original entry from a wrapped one — for an entry wrapped by an
|
|
213
|
+
* older runner or on another machine (nothing stashed here), and for remote
|
|
214
|
+
* header values, which the sidecar does not keep. `undefined` when `wrapped`
|
|
215
|
+
* is not an entry `wrapEntry` could have produced.
|
|
216
|
+
*/
|
|
217
|
+
export declare function unwrapEntry(wrapped: McpServerConfig): McpServerConfig | undefined;
|
|
218
|
+
/**
|
|
219
|
+
* The options `wrapEntry` was given for `wrapped`, read back from the entry:
|
|
220
|
+
* the interpreter, its flags and the proxy script (or the npx spec), the
|
|
221
|
+
* control URL and `--no-correlation`. What lets a local copy be re-generated
|
|
222
|
+
* over a changed source without knowing how `bir install` was run.
|
|
223
|
+
*/
|
|
224
|
+
export declare function wrapOptionsOf(wrapped: McpServerConfig): Omit<WrapOptions, "serverName"> | undefined;
|
|
225
|
+
/** Two entries that say the same thing, whatever order or optional spelling they were written in. */
|
|
226
|
+
export declare function sameServerConfig(a: unknown, b: unknown): boolean;
|
|
227
|
+
/**
|
|
228
|
+
* A fingerprint of an entry (see {@link canonicalServer}), kept in an
|
|
229
|
+
* override's record so a later change to the entry it copied is noticed —
|
|
230
|
+
* without keeping the entry, whose env and headers are credentials.
|
|
231
|
+
*/
|
|
232
|
+
export declare function configSha(config: unknown): string;
|
|
233
|
+
/**
|
|
234
|
+
* Whether a remote entry can be wrapped without breaking it.
|
|
235
|
+
*
|
|
236
|
+
* The proxy can only send what the entry spells out. A server Claude Code signs
|
|
237
|
+
* in itself — OAuth through `/mcp`, a `headersHelper` script, `${VAR}` headers
|
|
238
|
+
* Claude Code expands — would reach the upstream with no credentials, answer
|
|
239
|
+
* 401 on every call, and lose the "Authenticate" option in `/mcp` because the
|
|
240
|
+
* entry is now a stdio one. Those are left unwrapped: their calls are still
|
|
241
|
+
* recorded by the PreToolUse/PostToolUse hooks. Only an entry carrying a
|
|
242
|
+
* literal credential header is wrapped by default; `--server <name>` wraps
|
|
243
|
+
* any other one anyway.
|
|
244
|
+
*/
|
|
245
|
+
export declare function remoteWrapBlocker(config: RemoteServerConfig): string | undefined;
|
|
246
|
+
/**
|
|
247
|
+
* `config` as the sidecar keeps it: a remote entry's header VALUES and a stdio
|
|
248
|
+
* entry's env VALUES are dropped — they are credentials more often than not
|
|
249
|
+
* (API keys, database URLs, bearer tokens), and a second long-lived copy of
|
|
250
|
+
* them in installed.json is exposure for nothing. The names stay; uninstall
|
|
251
|
+
* reads the values back from the wrapped entry, which carries them verbatim
|
|
252
|
+
* (and carries a key the user rotated since, which a stash would not).
|
|
253
|
+
*/
|
|
254
|
+
export declare function redactSecrets(config: McpServerConfig): {
|
|
255
|
+
config: McpServerConfig;
|
|
256
|
+
redacted: boolean;
|
|
257
|
+
};
|
|
258
|
+
/**
|
|
259
|
+
* Undo {@link redactSecrets}: the stash, with every redacted value read back
|
|
260
|
+
* from `wrapped`. Undefined when a value cannot be found there (the wrapped
|
|
261
|
+
* entry was changed or removed) — restoring an entry without its credentials
|
|
262
|
+
* would break it, so the caller leaves the entry alone instead.
|
|
263
|
+
*/
|
|
264
|
+
export declare function restoreSecrets(stash: McpServerConfig, wrapped: McpServerConfig | undefined): McpServerConfig | undefined;
|
|
92
265
|
/**
|
|
93
266
|
* The entry for the scenario server itself. Not a proxy: it wraps nothing and
|
|
94
267
|
* spawns nothing, it just forwards one call to the control server.
|
|
@@ -99,17 +272,55 @@ export declare function scenarioEntry(opts: {
|
|
|
99
272
|
controlUrl?: string;
|
|
100
273
|
version?: string;
|
|
101
274
|
nodeFlags?: string[];
|
|
275
|
+
node?: string;
|
|
276
|
+
platform?: NodeJS.Platform;
|
|
102
277
|
}): WrappedEntry;
|
|
103
278
|
/** True when this entry is the scenario server — makes `install --replay` idempotent. */
|
|
104
279
|
export declare function isScenarioServer(config: McpServerConfig): boolean;
|
|
105
280
|
export interface InstalledRecord {
|
|
106
|
-
/**
|
|
281
|
+
/**
|
|
282
|
+
* Original entry, verbatim — except header and env values (see
|
|
283
|
+
* {@link InstalledRecord.secretsRedacted}). `null` means the entry did not
|
|
284
|
+
* exist in `scope` before: uninstall removes it rather than restoring it.
|
|
285
|
+
*/
|
|
107
286
|
original: McpServerConfig | null;
|
|
108
287
|
scope: Scope;
|
|
109
288
|
/** File the entry was written to, so uninstall edits the same one. */
|
|
110
289
|
file: string;
|
|
111
290
|
cwd: string;
|
|
112
291
|
installedAt: number;
|
|
292
|
+
/**
|
|
293
|
+
* Set when the wrapped entry was ADDED to the local scope as an override of
|
|
294
|
+
* the same name in `source` (the project's `.mcp.json`, or the user scope).
|
|
295
|
+
* The source entry itself is never touched: `.mcp.json` is a file a team
|
|
296
|
+
* commits, and the user scope applies to every project on the machine.
|
|
297
|
+
*/
|
|
298
|
+
override?: {
|
|
299
|
+
source: Scope;
|
|
300
|
+
};
|
|
301
|
+
/**
|
|
302
|
+
* An override's {@link configSha} of the entry it copied, as it was then. The
|
|
303
|
+
* copy is frozen and outranks that entry, so a teammate's change to
|
|
304
|
+
* `.mcp.json` (or a token rotated in the user scope) would otherwise never
|
|
305
|
+
* reach this project; `bir doctor` compares it, and `bir setup` re-copies.
|
|
306
|
+
*/
|
|
307
|
+
sourceSha?: string;
|
|
308
|
+
/** Local scope: the `~/.claude.json` project keys the entry was written under. */
|
|
309
|
+
keys?: string[];
|
|
310
|
+
/**
|
|
311
|
+
* `original.headers` / `original.env` hold names only; the values are read
|
|
312
|
+
* back from the wrapped entry (see {@link redactSecrets}). Such a record is
|
|
313
|
+
* kept under a {@link REDACTED_KEY_PREFIX} key, out of an older runner's sight.
|
|
314
|
+
*/
|
|
315
|
+
secretsRedacted?: boolean;
|
|
316
|
+
/**
|
|
317
|
+
* Wrapped although {@link remoteWrapBlocker} advised against it, or although
|
|
318
|
+
* Claude Code had not yet been told to use the `.mcp.json` server it copies
|
|
319
|
+
* (config/mcp-approval.ts), because the person named it with `--server`.
|
|
320
|
+
* Remembered, so the next plain `bir setup` (the upgrade path) keeps that
|
|
321
|
+
* choice instead of un-wrapping it. A server they REJECTED is never kept.
|
|
322
|
+
*/
|
|
323
|
+
optedIn?: boolean;
|
|
113
324
|
}
|
|
114
325
|
/**
|
|
115
326
|
* A file we edited, captured before the first edit.
|
|
@@ -118,11 +329,64 @@ export interface InstalledRecord {
|
|
|
118
329
|
* still hashes to it is restored from `originalText` **byte-for-byte**; a file
|
|
119
330
|
* edited since is repaired entry-by-entry instead, because clobbering somebody's
|
|
120
331
|
* later edit is worse than an imperfect restore.
|
|
332
|
+
*
|
|
333
|
+
* `stale` is set once somebody else changed the file between two of our
|
|
334
|
+
* writes: from then on `originalText` is older than a change we never saw, and
|
|
335
|
+
* restoring it would delete that change even though the file hashes to our
|
|
336
|
+
* last write. Never kept for `~/.claude.json` at all — Claude Code rewrites it
|
|
337
|
+
* constantly and it holds every project's settings; it is repaired entry by
|
|
338
|
+
* entry, always.
|
|
121
339
|
*/
|
|
122
340
|
export interface FileBackup {
|
|
123
341
|
originalText: string | null;
|
|
124
342
|
writtenSha: string;
|
|
343
|
+
stale?: boolean;
|
|
344
|
+
/**
|
|
345
|
+
* A `.mcp.json` whose servers carry env or header VALUES (API keys, bearer
|
|
346
|
+
* tokens) is not kept verbatim — that would be a second long-lived copy of
|
|
347
|
+
* them under ~/.baseinstrunner. Its text is kept with each value's literal
|
|
348
|
+
* replaced by a placeholder, and {@link FileBackup.originalText} stays null;
|
|
349
|
+
* uninstall reads the values back from the entries in the file (wrapped or
|
|
350
|
+
* not) and restores the file byte-for-byte only when the result hashes to
|
|
351
|
+
* `sha`, the original's. See {@link protectBackupText}. A runner older than
|
|
352
|
+
* this field (0.2.12) reads the backup as a file install created, and never
|
|
353
|
+
* writes it back whole.
|
|
354
|
+
*/
|
|
355
|
+
redacted?: RedactedText;
|
|
125
356
|
}
|
|
357
|
+
/** A file's text with its env and header values replaced by placeholders. */
|
|
358
|
+
export interface RedactedText {
|
|
359
|
+
/** The text, each secret's JSON string literal replaced by a placeholder literal. */
|
|
360
|
+
text: string;
|
|
361
|
+
/** sha256 of the original text: a restore is used only when it matches. */
|
|
362
|
+
sha: string;
|
|
363
|
+
/** Placeholder `i` is the value of `mcpServers[server][field][key]`. */
|
|
364
|
+
secrets: Array<{
|
|
365
|
+
server: string;
|
|
366
|
+
field: "env" | "headers";
|
|
367
|
+
key: string;
|
|
368
|
+
}>;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* The backup of a project file's pre-install text, as installed.json keeps it
|
|
372
|
+
* (SEC-8): verbatim when it holds no env or header value; otherwise with every
|
|
373
|
+
* such value's literal replaced by a placeholder ({@link FileBackup.redacted}).
|
|
374
|
+
* When that cannot be done exactly — a value spelled with escapes, a text that
|
|
375
|
+
* does not parse, a placeholder already in the text — no text is kept at all
|
|
376
|
+
* and the file is repaired entry by entry on uninstall (`stale`), which loses
|
|
377
|
+
* the byte-for-byte restore and nothing else.
|
|
378
|
+
*/
|
|
379
|
+
export declare function protectBackupText(text: string): FileBackup;
|
|
380
|
+
/**
|
|
381
|
+
* The text a backup restores: its verbatim original, or the redacted one with
|
|
382
|
+
* every value read back from `currentText` — the file as it is now, whose
|
|
383
|
+
* entries (wrapped by us, or left as they were) carry those values. Null for a
|
|
384
|
+
* file install created; undefined when the original cannot be rebuilt exactly
|
|
385
|
+
* (a value is gone, or the result does not hash to the original's).
|
|
386
|
+
*/
|
|
387
|
+
export declare function backupOriginalText(backup: FileBackup, currentText: string | null): string | null | undefined;
|
|
388
|
+
/** True when a backup says the file did not exist before install (nothing to restore). */
|
|
389
|
+
export declare function backupIsCreation(backup: FileBackup): boolean;
|
|
126
390
|
/**
|
|
127
391
|
* The replay switches an operator set for one project, kept where a recorder
|
|
128
392
|
* started by a hook can read them. They used to be environment variables read
|
|
@@ -131,8 +395,21 @@ export interface FileBackup {
|
|
|
131
395
|
* the SessionStart hook started it with Claude Code's environment instead.
|
|
132
396
|
*/
|
|
133
397
|
export interface ReplayPolicy {
|
|
134
|
-
/**
|
|
398
|
+
/**
|
|
399
|
+
* `bir replay off` → false, `bir replay on` → true. Absent means on — the
|
|
400
|
+
* recorder reads `enabled !== false` at start (control/recorder-main.ts), and
|
|
401
|
+
* `BIR_REPLAY` in its environment outranks it.
|
|
402
|
+
*/
|
|
135
403
|
enabled?: boolean;
|
|
404
|
+
/**
|
|
405
|
+
* `"install"` when `enabled: false` was written by an install rather than by
|
|
406
|
+
* the person: `bir setup --no-replay`, `bir uninstall --replay`, or a `bir
|
|
407
|
+
* install` without `--replay` in a project with no scenario server. Replay
|
|
408
|
+
* was not asked for there, and a direct replay would have no delivery tool.
|
|
409
|
+
* An install that asks for replay again turns such an off back on; one the
|
|
410
|
+
* person set with `bir replay off` it never does. See {@link settleReplaySwitch}.
|
|
411
|
+
*/
|
|
412
|
+
disabledBy?: "install";
|
|
136
413
|
/** Server keys eligible for direct execution. Absent means every wrapped one. */
|
|
137
414
|
allowServers?: string[];
|
|
138
415
|
/** `BIR_MIN_STEER_SIMILARITY`'s stored twin. */
|
|
@@ -157,11 +434,26 @@ export interface ProjectRecord {
|
|
|
157
434
|
* a fleet's absolute paths into pinned-npx entries.
|
|
158
435
|
*/
|
|
159
436
|
invocation?: Invocation;
|
|
437
|
+
/**
|
|
438
|
+
* True when {@link ProjectRecord.invocation} was asked for with a flag. A
|
|
439
|
+
* remembered `npx` without it is the old flag-less default, which an
|
|
440
|
+
* installed `bir` replaces with its absolute path.
|
|
441
|
+
*/
|
|
442
|
+
invocationChosen?: boolean;
|
|
443
|
+
/**
|
|
444
|
+
* Whether an install added `.claude/settings.local.json` to the repository's
|
|
445
|
+
* exclude file (false: the line was already there). Uninstall removes only a
|
|
446
|
+
* line it added. Absent (older installs): removed, as before.
|
|
447
|
+
*/
|
|
448
|
+
excludeAdded?: boolean;
|
|
160
449
|
/**
|
|
161
450
|
* `bir scenario editing on|off` (editSteps.md D2, in the BaseIn repository):
|
|
162
451
|
* whether this project's `bir` MCP server also offers the tools that CHANGE
|
|
163
452
|
* a calculated scenario — `scenario_check`, `scenario_edit`, `scenario_undo`.
|
|
164
|
-
* Absent means off. The
|
|
453
|
+
* Absent means off. The tools that only READ a scenario come with it too,
|
|
454
|
+
* and otherwise only with `BIR_SCENARIO_TOOLS=read` in that server's
|
|
455
|
+
* environment: their definitions ride along with every request of every
|
|
456
|
+
* session, and only somebody working on a scenario calls them.
|
|
165
457
|
*
|
|
166
458
|
* Per project, and off by default, because the `bir` server runs in every
|
|
167
459
|
* session of every installed project — a fleet included — and a plan must
|
|
@@ -169,6 +461,20 @@ export interface ProjectRecord {
|
|
|
169
461
|
* commands ignore it: a person in a terminal is asked before each one.
|
|
170
462
|
*/
|
|
171
463
|
editing?: boolean;
|
|
464
|
+
/**
|
|
465
|
+
* Every `.claude.json` an install for this project wrote local-scope entries
|
|
466
|
+
* to. Claude Code reads `$CLAUDE_CONFIG_DIR/.claude.json` when that is set,
|
|
467
|
+
* so a project installed from two environments (two profiles, an IDE
|
|
468
|
+
* terminal) has entries in two files — and an uninstall run from either must
|
|
469
|
+
* find both, or the other profile's servers keep naming the runner's files
|
|
470
|
+
* after the runner is gone.
|
|
471
|
+
*/
|
|
472
|
+
claudeJsonFiles?: string[];
|
|
473
|
+
/**
|
|
474
|
+
* The upgrade note about auto-approval (wrapped MCP calls are no longer
|
|
475
|
+
* approved by the runner) was shown for this project; it is said once.
|
|
476
|
+
*/
|
|
477
|
+
approvalNoteShown?: boolean;
|
|
172
478
|
}
|
|
173
479
|
export interface InstalledSidecar {
|
|
174
480
|
version: 1;
|
|
@@ -187,7 +493,10 @@ export interface InstalledSidecar {
|
|
|
187
493
|
* gets its own port, chosen once and reused.
|
|
188
494
|
*/
|
|
189
495
|
projects?: Record<string, ProjectRecord>;
|
|
190
|
-
/**
|
|
496
|
+
/**
|
|
497
|
+
* `<cwd>::<serverName>` → record; `v2|<cwd>::<serverName>` for a record
|
|
498
|
+
* whose stash has its secrets blanked ({@link REDACTED_KEY_PREFIX}).
|
|
499
|
+
*/
|
|
191
500
|
servers: Record<string, InstalledRecord>;
|
|
192
501
|
/** Absolute path → the text that was there before we touched it. */
|
|
193
502
|
files?: Record<string, FileBackup>;
|
|
@@ -195,6 +504,40 @@ export interface InstalledSidecar {
|
|
|
195
504
|
hookFiles?: string[];
|
|
196
505
|
}
|
|
197
506
|
export declare function readSidecar(): InstalledSidecar;
|
|
507
|
+
/**
|
|
508
|
+
* The prefix of the key a record is kept under when its stash has its
|
|
509
|
+
* secrets blanked ({@link InstalledRecord.secretsRedacted}).
|
|
510
|
+
*
|
|
511
|
+
* A runner older than redaction (0.2.12) finds its records by the exact key
|
|
512
|
+
* `<cwd>::<name>`, or by that prefix, and writes `original` back VERBATIM: on
|
|
513
|
+
* a rollback (`BIR_VERSION=0.2.12`, a second `bir` earlier on PATH, `npx
|
|
514
|
+
* @basein/runner@0.2.12`) its `bir uninstall` would put a server back with its
|
|
515
|
+
* API key blanked, the key's only copy — the wrapped entry — overwritten. Under
|
|
516
|
+
* this prefix it finds no record, says nothing to undo for that server, and
|
|
517
|
+
* leaves the wrapped entry, credentials and all, alone. Records it can restore
|
|
518
|
+
* correctly (no env, no headers, or an `original: null`) keep the old key.
|
|
519
|
+
*/
|
|
520
|
+
export declare const REDACTED_KEY_PREFIX = "v2|";
|
|
521
|
+
/** The key a record for `serverName` in `cwd` belongs under, redacted or not. */
|
|
522
|
+
export declare function recordKey(cwd: string, serverName: string, redacted?: boolean): string;
|
|
523
|
+
/**
|
|
524
|
+
* Put every record under the key its stash calls for ({@link REDACTED_KEY_PREFIX}):
|
|
525
|
+
* a redacted record written under the plain key (by an earlier build, or by
|
|
526
|
+
* code that wrote the plain key) moves out of an older runner's sight, and
|
|
527
|
+
* two records for one server — one per key shape, left by installs of both
|
|
528
|
+
* versions — become the newer one. Returns true when anything moved.
|
|
529
|
+
*/
|
|
530
|
+
export declare function normaliseRecordKeys(servers: Record<string, InstalledRecord>): boolean;
|
|
531
|
+
/**
|
|
532
|
+
* Write the sidecar so no reader ever sees half of it (a recorder that read a
|
|
533
|
+
* torn file used to start with a random token and refuse every hook).
|
|
534
|
+
*
|
|
535
|
+
* `readSidecar` answers an empty sidecar for a file it cannot parse, so the
|
|
536
|
+
* next write would silently replace every project's originals, tokens and
|
|
537
|
+
* ports with nothing. Before that happens the unreadable file is copied aside
|
|
538
|
+
* as `installed.json.corrupt-<time>`, where a person (or support) can get the
|
|
539
|
+
* originals back.
|
|
540
|
+
*/
|
|
198
541
|
export declare function writeSidecar(sidecar: InstalledSidecar): void;
|
|
199
542
|
export declare function sidecarKey(cwd: string, serverName: string): string;
|
|
200
543
|
/** The lowest port a project is given when none was asked for. */
|
|
@@ -224,6 +567,14 @@ export declare function probeFreePort(port: number): Promise<boolean>;
|
|
|
224
567
|
* kept as it is — the hooks name it — and the recorder says so if it is busy.
|
|
225
568
|
*/
|
|
226
569
|
export declare function allocateFreeProjectPort(sidecar: InstalledSidecar, cwd: string): Promise<number>;
|
|
570
|
+
/**
|
|
571
|
+
* A new port for a project whose recorded one this machine will no longer let
|
|
572
|
+
* it bind (a Hyper-V/WSL/Docker exclusion that moved after a reboot, or another
|
|
573
|
+
* program). The lowest free one no other project holds, never `avoid`;
|
|
574
|
+
* `undefined` when none of the 200 is free. The caller rewrites the hooks and
|
|
575
|
+
* every wrapped entry's control URL — `bir setup` does, by re-running install.
|
|
576
|
+
*/
|
|
577
|
+
export declare function allocateReplacementPort(sidecar: InstalledSidecar, cwd: string, avoid: number): Promise<number | undefined>;
|
|
227
578
|
/** Merge `patch` into the record for `cwd`, creating it when absent. */
|
|
228
579
|
export declare function setProjectRecord(sidecar: InstalledSidecar, cwd: string, patch: Partial<ProjectRecord> & Pick<ProjectRecord, "port">): ProjectRecord;
|
|
229
580
|
/** The bearer token this project's hooks and control server share. */
|
|
@@ -237,4 +588,31 @@ export declare function editingEnabled(sidecar: InstalledSidecar, cwd: string):
|
|
|
237
588
|
* inventing a record would invent a port the hooks do not use.
|
|
238
589
|
*/
|
|
239
590
|
export declare function setEditing(sidecar: InstalledSidecar, cwd: string, on: boolean): ProjectRecord | undefined;
|
|
591
|
+
/**
|
|
592
|
+
* What an install or uninstall said about calculated replay:
|
|
593
|
+
*
|
|
594
|
+
* wanted `--replay` (and `bir setup`, which adds it by default)
|
|
595
|
+
* refused `bir setup --no-replay`, `bir uninstall --replay`
|
|
596
|
+
* unasked a `bir install` without `--replay`
|
|
597
|
+
*/
|
|
598
|
+
export type ReplayIntent = "wanted" | "refused" | "unasked";
|
|
599
|
+
/**
|
|
600
|
+
* Keep this project's stored replay switch ({@link ReplayPolicy.enabled}) in
|
|
601
|
+
* line with what the person asked for, so the recorder never steers — or runs
|
|
602
|
+
* steps unattended — in a project that did not opt in. It used to default to
|
|
603
|
+
* on everywhere: `bir setup --no-replay` printed "replay not installed" while
|
|
604
|
+
* the recorder replayed matched prompts anyway, and a direct plan named a
|
|
605
|
+
* delivery tool the session did not have.
|
|
606
|
+
*
|
|
607
|
+
* refused → off, unless it is off already.
|
|
608
|
+
* unasked → off, but only while the switch was never set and no scenario
|
|
609
|
+
* server is here (a project that has one keeps it — a plain
|
|
610
|
+
* `bir install --server x` must not take replay away — and
|
|
611
|
+
* `bir replay on|off` is the person's to keep).
|
|
612
|
+
* wanted → on again, but only when an install turned it off; a
|
|
613
|
+
* `bir replay off` stays off.
|
|
614
|
+
*
|
|
615
|
+
* Returns what changed, or undefined. The caller writes the sidecar.
|
|
616
|
+
*/
|
|
617
|
+
export declare function settleReplaySwitch(sidecar: InstalledSidecar, cwd: string, intent: ReplayIntent, scenarioServerHere: boolean): "on" | "off" | undefined;
|
|
240
618
|
//# sourceMappingURL=generate.d.ts.map
|