@basein/runner 0.2.8 → 0.2.11
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 +86 -22
- package/dist/auth/client.d.ts +40 -1
- package/dist/auth/client.js +77 -9
- package/dist/bin/bir-hooks.d.ts +18 -3
- package/dist/bin/bir-hooks.js +124 -38
- package/dist/bin/bir-scenario.d.ts +18 -2
- package/dist/bin/bir-scenario.js +374 -4
- package/dist/bin/bir.d.ts +12 -0
- package/dist/bin/bir.js +501 -81
- package/dist/bin/investigate.js +1 -1
- package/dist/bin/scenario-edit.d.ts +173 -0
- package/dist/bin/scenario-edit.js +771 -0
- package/dist/bin/setup.d.ts +72 -0
- package/dist/bin/setup.js +286 -0
- package/dist/config/adapters/claude-code.d.ts +90 -4
- package/dist/config/adapters/claude-code.js +164 -16
- package/dist/config/generate.d.ts +114 -1
- package/dist/config/generate.js +106 -3
- package/dist/control/client.d.ts +5 -0
- package/dist/control/client.js +8 -0
- package/dist/control/daemon.d.ts +116 -0
- package/dist/control/daemon.js +339 -0
- package/dist/control/discovery.d.ts +26 -0
- package/dist/control/discovery.js +41 -9
- package/dist/control/ensure-hook.d.ts +39 -0
- package/dist/control/ensure-hook.js +98 -0
- package/dist/control/paths.d.ts +14 -0
- package/dist/control/paths.js +20 -0
- package/dist/control/server.d.ts +28 -0
- package/dist/control/server.js +15 -2
- package/dist/proxy/session.d.ts +8 -1
- package/dist/proxy/session.js +28 -6
- package/docs/calculatedReplay.md +51 -0
- package/docs/calculatedReplayGuide.md +471 -74
- package/docs/installRun.md +457 -111
- package/docs/loginWeb.md +1 -1
- package/docs/quickstart.md +195 -158
- package/package.json +2 -1
- package/scripts/install.ps1 +669 -0
- package/scripts/install.sh +586 -0
package/README.md
CHANGED
|
@@ -22,40 +22,70 @@ hooks ──built-ins, prompt, stop──▶ bir-hooks (control server) ──
|
|
|
22
22
|
|
|
23
23
|
## Install
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
One line, pasted into a terminal **opened in the project you start Claude Code
|
|
26
|
+
in**. The console's *Set up the runner* page shows it with a one-time setup
|
|
27
|
+
token filled in, so there is no sign-in step either:
|
|
26
28
|
|
|
27
29
|
```powershell
|
|
28
|
-
|
|
30
|
+
$env:BIR_SETUP_TOKEN="<token>"; irm https://api.bi2202.com/install.ps1 | iex # Windows (PowerShell)
|
|
29
31
|
```
|
|
30
32
|
|
|
31
33
|
```bash
|
|
32
|
-
|
|
34
|
+
curl -fsSL https://api.bi2202.com/install.sh | BIR_SETUP_TOKEN="<token>" sh # macOS / Linux
|
|
33
35
|
```
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
That installs Node if the machine has none (a private copy, never on PATH),
|
|
38
|
+
installs this package, installs Claude Code if it is missing, and runs
|
|
39
|
+
`bir setup`: it signs in, stores the service address, wraps the project's MCP
|
|
40
|
+
servers, wires Claude Code's hooks, adds the scenario server, and starts the
|
|
41
|
+
recorder in the background. It ends with the one thing left to do: `claude`.
|
|
42
|
+
Without a token the same line signs in through the browser. Re-running it is
|
|
43
|
+
safe, and is also how you upgrade. To record another project: `cd` there and
|
|
44
|
+
run `bir setup` (no token needed; the sign-in is kept).
|
|
45
|
+
|
|
46
|
+
The scripts are [scripts/install.ps1](scripts/install.ps1) and
|
|
47
|
+
[scripts/install.sh](scripts/install.sh); the API serves them with its own
|
|
48
|
+
address filled in. Step by step, and what each line does:
|
|
49
|
+
[docs/quickstart.md](docs/quickstart.md). For a fleet — packaging, releases,
|
|
50
|
+
offline installs, updates: [docs/installRun.md](docs/installRun.md).
|
|
38
51
|
|
|
39
52
|
By hand, or when developing BaseInstRunner itself:
|
|
40
53
|
|
|
41
54
|
```bash
|
|
42
55
|
npm i -g @basein/runner # or: npm install && npm run build, in a checkout
|
|
43
56
|
cd the-project-you-record-from
|
|
44
|
-
bir
|
|
57
|
+
bir setup --auth-url https://api.bi2202.com # everything below, in one go
|
|
45
58
|
```
|
|
46
59
|
|
|
47
|
-
`bir
|
|
48
|
-
`mcp__<server>__<tool>` names never change), and wires Claude Code's hooks. It
|
|
49
|
-
stashes the original text of every file first — `bir uninstall` puts it back
|
|
50
|
-
byte-for-byte.
|
|
60
|
+
or the pieces `bir setup` is made of:
|
|
51
61
|
|
|
52
62
|
```bash
|
|
53
|
-
|
|
54
|
-
bir
|
|
55
|
-
bir
|
|
56
|
-
claude
|
|
63
|
+
bir login # once; approve it in the browser (or --token <t> from the console)
|
|
64
|
+
bir install --global --replay # --local from a checkout; plain `bir install` writes a pinned npx entry
|
|
65
|
+
bir up # the recorder, in the background; the SessionStart hook does this too
|
|
66
|
+
claude # your session, now recorded
|
|
57
67
|
```
|
|
58
68
|
|
|
69
|
+
`bir install` rewrites your MCP entries in place, keeping every key (so
|
|
70
|
+
`mcp__<server>__<tool>` names never change), and wires Claude Code's hooks into
|
|
71
|
+
`.claude/settings.local.json` — your file, not the one a team commits; in a git
|
|
72
|
+
repository it is added to `.git/info/exclude`. It stashes the original text of
|
|
73
|
+
every file first — `bir uninstall` puts a file back byte-for-byte when nothing
|
|
74
|
+
else has edited it since, and repairs it entry by entry when something has. A
|
|
75
|
+
project remembers whether it was installed with `--global`, `--local` or the
|
|
76
|
+
pinned-npx default, so a later `bir install` with no flag keeps that shape.
|
|
77
|
+
|
|
78
|
+
**Nothing has to stay open.** The SessionStart hook is `bir-hooks ensure`: it
|
|
79
|
+
starts the recorder for the project when none is running, in the background,
|
|
80
|
+
with its audit log at `~/.baseinstrunner/logs/<project-key>.log`. `bir up`
|
|
81
|
+
starts it by hand, `bir down` stops it, `bir status` shows it. Running
|
|
82
|
+
`bir-hooks` in a terminal still works and is what the hook does for you.
|
|
83
|
+
|
|
84
|
+
The service address is stored by `bir setup` and `bir login` in
|
|
85
|
+
`~/.baseinstrunner/config.json`; `BIR_AUTH_URL` in the environment overrides
|
|
86
|
+
it. A cached session is bound to the service that issued it and is never sent
|
|
87
|
+
anywhere else.
|
|
88
|
+
|
|
59
89
|
`bir login` prints a link and a short code and waits for you to approve it in
|
|
60
90
|
the browser — the OAuth device grant, the same shape `gh auth login` uses. It
|
|
61
91
|
never handles a password, so it works for accounts that only sign in with
|
|
@@ -117,7 +147,9 @@ between the two is not guaranteed.
|
|
|
117
147
|
section.
|
|
118
148
|
- **Change what the model does.** Tool descriptions are never edited. The
|
|
119
149
|
model-visible changes are schema relaxation, below, and — only with
|
|
120
|
-
`--replay` —
|
|
150
|
+
`--replay` — the first-party `bir` server's tools: `run_scenario`, and three
|
|
151
|
+
that only read a scenario. The three that change one appear only after
|
|
152
|
+
`bir scenario editing on` in that project.
|
|
121
153
|
|
|
122
154
|
---
|
|
123
155
|
|
|
@@ -134,10 +166,18 @@ record, calculate, check, enable, read the audit lines — is
|
|
|
134
166
|
[docs/calculatedReplayGuide.md](docs/calculatedReplayGuide.md).
|
|
135
167
|
|
|
136
168
|
```bash
|
|
137
|
-
bir install --replay # adds the `bir` MCP server
|
|
138
|
-
bir
|
|
169
|
+
bir install --replay # adds the `bir` MCP server, pre-approved in .claude/settings.local.json
|
|
170
|
+
bir up # or let the SessionStart hook start the recorder; replay is on by default
|
|
171
|
+
bir replay allow fleet # the servers replay may call unattended — stored for this project
|
|
172
|
+
bir replay off # and back on with `bir replay on`; `bir replay status` shows it
|
|
139
173
|
```
|
|
140
174
|
|
|
175
|
+
`bir setup` does the first two. The switches are kept per project in
|
|
176
|
+
`~/.baseinstrunner/installed.json`, so a recorder the hook starts — with Claude
|
|
177
|
+
Code's environment, not your terminal's — keeps them; `BIR_REPLAY`,
|
|
178
|
+
`BIR_REPLAY_ALLOW_SERVERS` and `BIR_MIN_STEER_SIMILARITY` in the environment
|
|
179
|
+
still override them, and `bir doctor` says which source is in effect.
|
|
180
|
+
|
|
141
181
|
Two switches, not one, because of what replay is: **a scenario is a pre-approved
|
|
142
182
|
list of tool calls with computed arguments.** A steered step is answered
|
|
143
183
|
`permissionDecision: "allow"`, so a pinned `Bash` or `Edit` runs without the
|
|
@@ -154,6 +194,25 @@ sequence runs through connections the proxies already hold — **zero model toke
|
|
|
154
194
|
scenarios steer the live session instead, pinning each step's arguments. Either
|
|
155
195
|
way the turn reports what it cost, so the savings ledger measures something real.
|
|
156
196
|
|
|
197
|
+
**Fixing a step.** A calculated step is code, and code can be wrong.
|
|
198
|
+
`bir investigate` names such a step; `bir scenario edit` fixes it, and the
|
|
199
|
+
service checks the change against the recording before it saves it. **A change
|
|
200
|
+
is saved only if it reproduces the recording — or on purpose, with
|
|
201
|
+
`--force --note "why"`.** Only the owner can change a scenario. Claude can do it
|
|
202
|
+
too: in its terminal, or with the `bir` server's tools once
|
|
203
|
+
`bir scenario editing on` was run in the project. Built 2026-09-25, not yet
|
|
204
|
+
released: it needs the next `@basein/runner` version and a service deploy. Step
|
|
205
|
+
by step: [the guide's §9.2](docs/calculatedReplayGuide.md).
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
bir scenario show scn_… --step 3 # read one step
|
|
209
|
+
bir scenario check scn_… --step 3 --input-logic step3.js # try a change; nothing is saved
|
|
210
|
+
bir scenario edit scn_… --step 3 --input-logic step3.js --note "why" # the same check, then save
|
|
211
|
+
bir scenario edits scn_… # the history
|
|
212
|
+
bir scenario undo scn_… # undo the newest edit
|
|
213
|
+
bir scenario editing on # let Claude's `bir` tools change scenarios here
|
|
214
|
+
```
|
|
215
|
+
|
|
157
216
|
---
|
|
158
217
|
|
|
159
218
|
## Two things to know before you turn it on
|
|
@@ -184,7 +243,8 @@ instead.
|
|
|
184
243
|
|
|
185
244
|
| Variable | Effect |
|
|
186
245
|
|---|---|
|
|
187
|
-
| `BIR_AUTH_URL` | BaseIn auth-service — the **API** address (normally `https://api.<your-domain>`), not the docs or app website. **
|
|
246
|
+
| `BIR_AUTH_URL` | BaseIn auth-service — the **API** address (normally `https://api.<your-domain>`), not the docs or app website. Overrides the address `bir setup` / `bir login` stored in `~/.baseinstrunner/config.json`. **Neither set means nothing is recorded** — everything else still works. |
|
|
247
|
+
| `BIR_SETUP_TOKEN` | A one-time setup token from the console, read by `bir setup` when `--token` is not given; the install scripts pass it this way. Never inherited by the recorder. |
|
|
188
248
|
| `BIR_AUTH_DISABLE=1` | Skip authentication (local dev / CI). |
|
|
189
249
|
| `BIR_CONTROL_PORT` | Preferred control-server port (default 53411). |
|
|
190
250
|
| `BIR_CONTROL_URL` / `BIR_CONTROL_TOKEN` | Point a proxy at a control server directly, bypassing discovery. Injected into generated config; the only reliable channel for an SDK session. |
|
|
@@ -200,7 +260,7 @@ what to do when they register, so there is one switch and no second copy of it:
|
|
|
200
260
|
| Variable | Effect |
|
|
201
261
|
|---|---|
|
|
202
262
|
| `BIR_REPLAY=0` | Disable replay. It is on by default; nothing below matters while this is `0`. |
|
|
203
|
-
| `BIR_REPLAY_ALLOW_SERVERS` | Server keys eligible for **direct** execution. Unset means every wrapped server;
|
|
263
|
+
| `BIR_REPLAY_ALLOW_SERVERS` | Server keys eligible for **direct** execution. Unset means the list `bir replay allow` stored for the project, else every wrapped server; narrowing it is the recommendation. |
|
|
204
264
|
| `BIR_MIN_STEER_SIMILARITY` | Minimum match similarity to replay (default `0.92`, above the service's own `0.9` detection threshold). |
|
|
205
265
|
| `ANTHROPIC_API_KEY` | **Optional.** Working out what a new request is about — which fleet, which file, which date — is done for you by the service on its own key, as long as you are signed in. Set this only to keep that reading on your machine, on your key. Signed out *and* unset, a scenario whose values change between requests is declined rather than replayed on stale ones. |
|
|
206
266
|
| `BIR_DERIVE_MODEL` | Derivation model, when you set a key of your own (default `claude-haiku-4-5-20251001`). |
|
|
@@ -211,7 +271,10 @@ what to do when they register, so there is one switch and no second copy of it:
|
|
|
211
271
|
### The audit log
|
|
212
272
|
|
|
213
273
|
Every decision is one `key=value` line on **stderr** — never stdout, which is the
|
|
214
|
-
host's JSON-RPC stream.
|
|
274
|
+
host's JSON-RPC stream. A recorder started in the background (by `bir up`, `bir
|
|
275
|
+
setup` or the SessionStart hook) writes them to
|
|
276
|
+
`~/.baseinstrunner/logs/<project-key>.log` — `bir status` names the file. One
|
|
277
|
+
started in a terminal writes them to that terminal, and persistence is the
|
|
215
278
|
shell's job:
|
|
216
279
|
|
|
217
280
|
```bash
|
|
@@ -227,6 +290,7 @@ The governing rule: **a host session never fails because of BaseInstRunner.**
|
|
|
227
290
|
|
|
228
291
|
| Failure | Behaviour |
|
|
229
292
|
|---|---|
|
|
293
|
+
| No recorder running at session start | the SessionStart hook starts one in the background; a session is never blocked or failed by it |
|
|
230
294
|
| Control server unreachable at startup | buffer ≤ 5 s, then Tier 2; logged once |
|
|
231
295
|
| Control server dies mid-session | the proxy switches to Tier 2 for the rest; run flagged lossy |
|
|
232
296
|
| Recorder HTTP fails | send dropped and logged; never retried inline |
|
|
@@ -242,7 +306,7 @@ The governing rule: **a host session never fails because of BaseInstRunner.**
|
|
|
242
306
|
|
|
243
307
|
```bash
|
|
244
308
|
npm run build # src → dist
|
|
245
|
-
npm test #
|
|
309
|
+
npm test # ~400 tests: transcript identity, passthrough, correlation, config, replay, setup
|
|
246
310
|
npm run typecheck
|
|
247
311
|
npm run test:smoke # end-to-end replay with real processes (needs build + pretest first)
|
|
248
312
|
```
|
package/dist/auth/client.d.ts
CHANGED
|
@@ -32,7 +32,10 @@
|
|
|
32
32
|
* server the host is waiting on. Callers get `undefined` and decide.
|
|
33
33
|
*
|
|
34
34
|
* Config:
|
|
35
|
-
* BIR_AUTH_URL base URL of the BaseIn auth-service
|
|
35
|
+
* BIR_AUTH_URL base URL of the BaseIn auth-service. When unset, the
|
|
36
|
+
* address `bir setup` / `bir login` stored in
|
|
37
|
+
* ~/.baseinstrunner/config.json is used — see
|
|
38
|
+
* {@link resolveAuthUrl}.
|
|
36
39
|
* BIR_AUTH_DISABLE "1" to skip auth entirely (local dev / CI).
|
|
37
40
|
*/
|
|
38
41
|
export interface AuthUser {
|
|
@@ -46,6 +49,15 @@ export interface AuthSession {
|
|
|
46
49
|
/** Epoch milliseconds at which the access token expires. */
|
|
47
50
|
accessExpiresAt: number;
|
|
48
51
|
user: AuthUser;
|
|
52
|
+
/**
|
|
53
|
+
* The service that issued this session. `authenticate()` refuses to hand the
|
|
54
|
+
* tokens to any other origin: a stale `BIR_AUTH_URL` in some profile file, or
|
|
55
|
+
* one set by a repository's settings, must degrade to not-recording rather
|
|
56
|
+
* than send a live refresh token and every recording somewhere else. Absent
|
|
57
|
+
* on a file written before this field existed; those are trusted as before
|
|
58
|
+
* and re-bound by the next sign-in.
|
|
59
|
+
*/
|
|
60
|
+
authUrl?: string;
|
|
49
61
|
}
|
|
50
62
|
export interface AuthenticateOptions {
|
|
51
63
|
/** Base URL of the auth service. Defaults to BIR_AUTH_URL. */
|
|
@@ -73,6 +85,33 @@ export declare function loadCredentials(): AuthSession | undefined;
|
|
|
73
85
|
export declare function saveCredentials(session: AuthSession): void;
|
|
74
86
|
/** Remove the cached session (`bir logout`). No-op if absent. */
|
|
75
87
|
export declare function clearCredentials(): void;
|
|
88
|
+
/** The persisted settings in ~/.baseinstrunner/config.json. */
|
|
89
|
+
export interface StoredConfig {
|
|
90
|
+
/** The service address 'bir setup' or 'bir login' last signed in against. */
|
|
91
|
+
authUrl?: string;
|
|
92
|
+
}
|
|
93
|
+
/** Read the stored settings; an absent or unreadable file is an empty one. */
|
|
94
|
+
export declare function loadStoredConfig(): StoredConfig;
|
|
95
|
+
/** Merge 'patch' into the stored settings and write them back, mode 0600. */
|
|
96
|
+
export declare function saveStoredConfig(patch: StoredConfig): StoredConfig;
|
|
97
|
+
/**
|
|
98
|
+
* Remember the service address, so no later process needs BIR_AUTH_URL.
|
|
99
|
+
*
|
|
100
|
+
* Called by 'bir setup' and 'bir login' — the two moments a person has just
|
|
101
|
+
* proved the address is right by signing in to it. A wrong address is never
|
|
102
|
+
* stored, because a session was never issued for it.
|
|
103
|
+
*/
|
|
104
|
+
export declare function rememberAuthUrl(authUrl: string): void;
|
|
105
|
+
/**
|
|
106
|
+
* The service address this process should talk to, or "" when there is none.
|
|
107
|
+
*
|
|
108
|
+
* Three sources, in order: an explicit option, BIR_AUTH_URL, and the address
|
|
109
|
+
* stored by 'bir setup' / 'bir login'. The third is what makes the install a
|
|
110
|
+
* single step: a recorder started by a Claude Code hook inherits whatever
|
|
111
|
+
* environment the host had, which on a GUI-launched host is not the shell the
|
|
112
|
+
* person set things up in. The stored address is read the same way from every
|
|
113
|
+
* process, so they cannot disagree about where the recording goes.
|
|
114
|
+
*/
|
|
76
115
|
export declare function resolveAuthUrl(opts?: AuthenticateOptions): string;
|
|
77
116
|
/**
|
|
78
117
|
* Follow a redirect on the base URL *once*, so a bearer token is never sent to
|
package/dist/auth/client.js
CHANGED
|
@@ -32,14 +32,17 @@
|
|
|
32
32
|
* server the host is waiting on. Callers get `undefined` and decide.
|
|
33
33
|
*
|
|
34
34
|
* Config:
|
|
35
|
-
* BIR_AUTH_URL base URL of the BaseIn auth-service
|
|
35
|
+
* BIR_AUTH_URL base URL of the BaseIn auth-service. When unset, the
|
|
36
|
+
* address `bir setup` / `bir login` stored in
|
|
37
|
+
* ~/.baseinstrunner/config.json is used — see
|
|
38
|
+
* {@link resolveAuthUrl}.
|
|
36
39
|
* BIR_AUTH_DISABLE "1" to skip auth entirely (local dev / CI).
|
|
37
40
|
*/
|
|
38
41
|
import { spawn } from "node:child_process";
|
|
39
42
|
import { createInterface } from "node:readline";
|
|
40
43
|
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
41
44
|
import { hostname } from "node:os";
|
|
42
|
-
import { configDir } from "../control/paths.js";
|
|
45
|
+
import { configDir, configPath, ensureDir } from "../control/paths.js";
|
|
43
46
|
import { join } from "node:path";
|
|
44
47
|
import { logLine, errText } from "../util/log.js";
|
|
45
48
|
import { packageVersion } from "../util/version.js";
|
|
@@ -103,16 +106,70 @@ export function clearCredentials() {
|
|
|
103
106
|
function isAccessValid(session) {
|
|
104
107
|
return session.accessExpiresAt - EXPIRY_SKEW_MS > Date.now();
|
|
105
108
|
}
|
|
106
|
-
function toSession(res) {
|
|
109
|
+
function toSession(res, authUrl) {
|
|
107
110
|
return {
|
|
108
111
|
accessToken: res.accessToken,
|
|
109
112
|
refreshToken: res.refreshToken,
|
|
110
113
|
accessExpiresAt: Date.now() + res.expiresIn * 1000,
|
|
111
114
|
user: res.user,
|
|
115
|
+
...(authUrl ? { authUrl: authUrl.replace(/\/+$/, "") } : {}),
|
|
112
116
|
};
|
|
113
117
|
}
|
|
118
|
+
/** True when a cached session may be used against `authUrl`. */
|
|
119
|
+
function issuedFor(session, authUrl) {
|
|
120
|
+
if (!session.authUrl)
|
|
121
|
+
return true; // pre-binding file: trusted as before
|
|
122
|
+
return session.authUrl === authUrl.replace(/\/+$/, "");
|
|
123
|
+
}
|
|
124
|
+
/** Read the stored settings; an absent or unreadable file is an empty one. */
|
|
125
|
+
export function loadStoredConfig() {
|
|
126
|
+
try {
|
|
127
|
+
if (!existsSync(configPath()))
|
|
128
|
+
return {};
|
|
129
|
+
const parsed = JSON.parse(readFileSync(configPath(), "utf8"));
|
|
130
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return {};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/** Merge 'patch' into the stored settings and write them back, mode 0600. */
|
|
137
|
+
export function saveStoredConfig(patch) {
|
|
138
|
+
const next = { ...loadStoredConfig(), ...patch };
|
|
139
|
+
ensureDir(configDir());
|
|
140
|
+
writeFileSync(configPath(), JSON.stringify(next, null, 2) + "\n", { mode: 0o600 });
|
|
141
|
+
return next;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Remember the service address, so no later process needs BIR_AUTH_URL.
|
|
145
|
+
*
|
|
146
|
+
* Called by 'bir setup' and 'bir login' — the two moments a person has just
|
|
147
|
+
* proved the address is right by signing in to it. A wrong address is never
|
|
148
|
+
* stored, because a session was never issued for it.
|
|
149
|
+
*/
|
|
150
|
+
export function rememberAuthUrl(authUrl) {
|
|
151
|
+
const url = authUrl.replace(/\/+$/, "");
|
|
152
|
+
if (!url)
|
|
153
|
+
return;
|
|
154
|
+
if (loadStoredConfig().authUrl === url)
|
|
155
|
+
return;
|
|
156
|
+
saveStoredConfig({ authUrl: url });
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* The service address this process should talk to, or "" when there is none.
|
|
160
|
+
*
|
|
161
|
+
* Three sources, in order: an explicit option, BIR_AUTH_URL, and the address
|
|
162
|
+
* stored by 'bir setup' / 'bir login'. The third is what makes the install a
|
|
163
|
+
* single step: a recorder started by a Claude Code hook inherits whatever
|
|
164
|
+
* environment the host had, which on a GUI-launched host is not the shell the
|
|
165
|
+
* person set things up in. The stored address is read the same way from every
|
|
166
|
+
* process, so they cannot disagree about where the recording goes.
|
|
167
|
+
*/
|
|
114
168
|
export function resolveAuthUrl(opts) {
|
|
115
|
-
|
|
169
|
+
const explicit = opts?.authUrl ?? process.env.BIR_AUTH_URL;
|
|
170
|
+
if (explicit !== undefined && explicit !== "")
|
|
171
|
+
return explicit.replace(/\/+$/, "");
|
|
172
|
+
return (loadStoredConfig().authUrl ?? "").replace(/\/+$/, "");
|
|
116
173
|
}
|
|
117
174
|
/**
|
|
118
175
|
* Follow a redirect on the base URL *once*, so a bearer token is never sent to
|
|
@@ -304,7 +361,7 @@ async function postJson(url, body) {
|
|
|
304
361
|
/** Exchange a refresh token for a new session. Undefined on failure. */
|
|
305
362
|
async function tryRefresh(authUrl, refreshToken) {
|
|
306
363
|
try {
|
|
307
|
-
return toSession(await postJson(`${authUrl}/auth/refresh`, { refreshToken }));
|
|
364
|
+
return toSession(await postJson(`${authUrl}/auth/refresh`, { refreshToken }), authUrl);
|
|
308
365
|
}
|
|
309
366
|
catch {
|
|
310
367
|
return undefined;
|
|
@@ -326,11 +383,22 @@ export async function authenticate(opts) {
|
|
|
326
383
|
const authUrl = resolveAuthUrl(opts);
|
|
327
384
|
if (!authUrl) {
|
|
328
385
|
logLine("auth.unconfigured", {
|
|
329
|
-
why: "
|
|
386
|
+
why: "no service address — run `bir setup`, or set BIR_AUTH_URL; nothing will be recorded",
|
|
330
387
|
});
|
|
331
388
|
return undefined;
|
|
332
389
|
}
|
|
333
390
|
const cached = loadCredentials();
|
|
391
|
+
if (cached && !issuedFor(cached, authUrl)) {
|
|
392
|
+
// The tokens on disk belong to another service. Using them here would
|
|
393
|
+
// hand a live session to whatever this address is; refreshing them here
|
|
394
|
+
// would hand it the refresh token too. Neither is ever the right call.
|
|
395
|
+
logLine("auth.url_mismatch", {
|
|
396
|
+
cached: cached.authUrl,
|
|
397
|
+
requested: authUrl,
|
|
398
|
+
why: "the cached session was issued by another service — run `bir setup` (or `bir login`) for this one",
|
|
399
|
+
});
|
|
400
|
+
return undefined;
|
|
401
|
+
}
|
|
334
402
|
if (cached && isAccessValid(cached))
|
|
335
403
|
return cached;
|
|
336
404
|
if (cached?.refreshToken) {
|
|
@@ -513,7 +581,7 @@ export async function deviceLogin(authUrl, opts = {}) {
|
|
|
513
581
|
sleep: opts.sleep,
|
|
514
582
|
signal: opts.signal,
|
|
515
583
|
});
|
|
516
|
-
const session = toSession(res);
|
|
584
|
+
const session = toSession(res, authUrl);
|
|
517
585
|
saveCredentials(session);
|
|
518
586
|
return session;
|
|
519
587
|
}
|
|
@@ -536,7 +604,7 @@ export async function tokenLogin(authUrl, token, opts = {}) {
|
|
|
536
604
|
if (res.status === 404)
|
|
537
605
|
throw new DeviceFlowUnsupported();
|
|
538
606
|
if (res.ok) {
|
|
539
|
-
const session = toSession((await res.json()));
|
|
607
|
+
const session = toSession((await res.json()), authUrl);
|
|
540
608
|
saveCredentials(session);
|
|
541
609
|
logLine("auth.token_login", { url: authUrl });
|
|
542
610
|
return session;
|
|
@@ -604,7 +672,7 @@ export async function legacyPasswordLogin(authUrl) {
|
|
|
604
672
|
return undefined;
|
|
605
673
|
}
|
|
606
674
|
try {
|
|
607
|
-
const session = toSession(await postJson(`${authUrl}/auth/login`, { email, password }));
|
|
675
|
+
const session = toSession(await postJson(`${authUrl}/auth/login`, { email, password }), authUrl);
|
|
608
676
|
saveCredentials(session);
|
|
609
677
|
return session;
|
|
610
678
|
}
|
package/dist/bin/bir-hooks.d.ts
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* bir-hooks — the Claude Code hook receiver and the control server (Tier 1).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Normally nobody runs it: `bir install` makes it the SessionStart hook
|
|
6
|
+
* (`bir-hooks ensure`), which starts it in the background for the project when no
|
|
7
|
+
* recorder is running there, and `bir up` / `bir down` start and stop that
|
|
8
|
+
* background process by hand. Its audit lines then go to
|
|
9
|
+
* ~/.baseinstrunner/logs/<project-key>.log. It can still be run in a terminal:
|
|
6
10
|
*
|
|
7
11
|
* bir-hooks # start; Ctrl-C to stop
|
|
8
12
|
* bir-hooks 2>&1 | tee -a ~/.baseinstrunner/audit.log
|
|
@@ -13,7 +17,8 @@
|
|
|
13
17
|
* 3. owns run identity, the monotonic `stepIndex`, and the recorder chain (D5).
|
|
14
18
|
*
|
|
15
19
|
* Environment:
|
|
16
|
-
* BIR_AUTH_URL BaseIn auth-service
|
|
20
|
+
* BIR_AUTH_URL BaseIn auth-service. Unset, the address `bir setup`
|
|
21
|
+
* stored in ~/.baseinstrunner/config.json is used
|
|
17
22
|
* BIR_CONTROL_PORT preferred port (default 53411; falls back if busy)
|
|
18
23
|
* BIR_CORRELATION_DECISION `allow` (default) or `ask` — see below
|
|
19
24
|
* BIR_NO_CORRELATION=1 never inject a call id; join on fingerprints
|
|
@@ -60,5 +65,15 @@
|
|
|
60
65
|
* hosts that ignore `updatedInput` on a non-allow decision then fall back to
|
|
61
66
|
* fingerprint matching, which is lossy under identical concurrent calls.
|
|
62
67
|
*/
|
|
63
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Assemble the replay configuration (docs/calculatedReplay.md §5.3 of the guide).
|
|
70
|
+
*
|
|
71
|
+
* The steer threshold defaults **above** the service's own detection threshold
|
|
72
|
+
* (0.9). Detection decides "do not record this again", which is cheap — the worst
|
|
73
|
+
* case is a lost recording. Steering decides "do not think about this again",
|
|
74
|
+
* which is a much stronger claim, and a 0.90-similar prompt usually covers only
|
|
75
|
+
* part of the live task.
|
|
76
|
+
*/
|
|
77
|
+
/** Where the three replay switches came from, for `/health` and `bir doctor`. */
|
|
78
|
+
export type ReplaySource = "env" | "sidecar" | "default";
|
|
64
79
|
//# sourceMappingURL=bir-hooks.d.ts.map
|