@basein/runner 0.1.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/LICENSE +201 -0
- package/README.md +276 -0
- package/dist/auth/client.d.ts +85 -0
- package/dist/auth/client.js +284 -0
- package/dist/bin/bir-hooks.d.ts +48 -0
- package/dist/bin/bir-hooks.js +201 -0
- package/dist/bin/bir-proxy.d.ts +45 -0
- package/dist/bin/bir-proxy.js +207 -0
- package/dist/bin/bir-scenario.d.ts +24 -0
- package/dist/bin/bir-scenario.js +177 -0
- package/dist/bin/bir.d.ts +21 -0
- package/dist/bin/bir.js +876 -0
- package/dist/config/adapters/claude-code.d.ts +76 -0
- package/dist/config/adapters/claude-code.js +181 -0
- package/dist/config/adapters/generic.d.ts +17 -0
- package/dist/config/adapters/generic.js +36 -0
- package/dist/config/generate.d.ts +127 -0
- package/dist/config/generate.js +114 -0
- package/dist/config/resolve.d.ts +68 -0
- package/dist/config/resolve.js +132 -0
- package/dist/control/client.d.ts +56 -0
- package/dist/control/client.js +86 -0
- package/dist/control/correlation.d.ts +86 -0
- package/dist/control/correlation.js +0 -0
- package/dist/control/discovery.d.ts +50 -0
- package/dist/control/discovery.js +123 -0
- package/dist/control/ordering.d.ts +38 -0
- package/dist/control/ordering.js +44 -0
- package/dist/control/paths.d.ts +32 -0
- package/dist/control/paths.js +56 -0
- package/dist/control/server.d.ts +272 -0
- package/dist/control/server.js +1131 -0
- package/dist/control/transcript.d.ts +75 -0
- package/dist/control/transcript.js +241 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +32 -0
- package/dist/jsonrpc/framing.d.ts +49 -0
- package/dist/jsonrpc/framing.js +143 -0
- package/dist/jsonrpc/types.d.ts +52 -0
- package/dist/jsonrpc/types.js +46 -0
- package/dist/proxy/intercept.d.ts +55 -0
- package/dist/proxy/intercept.js +147 -0
- package/dist/proxy/relay.d.ts +97 -0
- package/dist/proxy/relay.js +166 -0
- package/dist/proxy/session.d.ts +116 -0
- package/dist/proxy/session.js +319 -0
- package/dist/record/housekeeping.d.ts +34 -0
- package/dist/record/housekeeping.js +39 -0
- package/dist/record/queue.d.ts +48 -0
- package/dist/record/queue.js +96 -0
- package/dist/record/recorder.d.ts +111 -0
- package/dist/record/recorder.js +39 -0
- package/dist/record/redact.d.ts +37 -0
- package/dist/record/redact.js +119 -0
- package/dist/record/remote-recorder.d.ts +110 -0
- package/dist/record/remote-recorder.js +301 -0
- package/dist/record/truncate.d.ts +36 -0
- package/dist/record/truncate.js +85 -0
- package/dist/replay/bundle.d.ts +36 -0
- package/dist/replay/bundle.js +89 -0
- package/dist/replay/controller.d.ts +300 -0
- package/dist/replay/controller.js +807 -0
- package/dist/replay/coverage.d.ts +41 -0
- package/dist/replay/coverage.js +56 -0
- package/dist/replay/derive.d.ts +58 -0
- package/dist/replay/derive.js +166 -0
- package/dist/replay/executor.d.ts +78 -0
- package/dist/replay/executor.js +233 -0
- package/dist/replay/logic.d.ts +31 -0
- package/dist/replay/logic.js +50 -0
- package/dist/replay/plan.d.ts +181 -0
- package/dist/replay/plan.js +397 -0
- package/dist/replay/pricing.d.ts +41 -0
- package/dist/replay/pricing.js +76 -0
- package/dist/replay/source-run.d.ts +50 -0
- package/dist/replay/source-run.js +98 -0
- package/dist/replay/tool-error.d.ts +22 -0
- package/dist/replay/tool-error.js +60 -0
- package/dist/replay/types.d.ts +116 -0
- package/dist/replay/types.js +35 -0
- package/dist/upstream/client.d.ts +78 -0
- package/dist/upstream/client.js +114 -0
- package/dist/upstream/http-client.d.ts +78 -0
- package/dist/upstream/http-client.js +261 -0
- package/dist/upstream/lazy-client.d.ts +31 -0
- package/dist/upstream/lazy-client.js +53 -0
- package/dist/upstream/stdio-client.d.ts +57 -0
- package/dist/upstream/stdio-client.js +203 -0
- package/dist/util/log.d.ts +27 -0
- package/dist/util/log.js +51 -0
- package/dist/util/version.d.ts +2 -0
- package/dist/util/version.js +40 -0
- package/docs/BaseInstRunner.md +621 -0
- package/docs/calculatedReplay.md +1185 -0
- package/docs/calculatedReplayGuide.md +448 -0
- package/docs/installRun.md +413 -0
- package/docs/mcpmark.md +752 -0
- package/docs/quickstart.md +201 -0
- package/docs/t-bench.md +394 -0
- package/package.json +56 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
# Packaging BaseInstRunner for runner machines
|
|
2
|
+
|
|
3
|
+
**The approach: publish to npm as `@basein/runner`, and install it globally at a
|
|
4
|
+
pinned version.** The code already assumed this — the installer writes an `npx`
|
|
5
|
+
invocation of its own package name into every wrapped MCP entry — so publishing
|
|
6
|
+
was less a new distribution channel than closing a loop that was already there.
|
|
7
|
+
|
|
8
|
+
This is the fleet-facing reference: what ships, how a runner machine goes from
|
|
9
|
+
bare to recording, how it gets updated, and what to do when there is no registry
|
|
10
|
+
to reach. For one person setting up one machine, [quickstart.md](quickstart.md)
|
|
11
|
+
is shorter and assumes nothing.
|
|
12
|
+
|
|
13
|
+
**Status.** Everything here is implemented and verified except §7: headless
|
|
14
|
+
authentication is still a stopgap, and it is the one open item that needs a
|
|
15
|
+
service-side decision rather than a change here.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 1. Why this package is an easy case
|
|
20
|
+
|
|
21
|
+
| Property | Value | Why it matters for packaging |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| Runtime dependencies | **none** | The tarball is self-contained. No lockfile resolution, no transitive CVEs, no native builds, and offline install just works. |
|
|
24
|
+
| Module format | pure ESM (`"type": "module"`) | No dual-build gymnastics. |
|
|
25
|
+
| Node floor | `>=20` | One prerequisite to check on a runner. |
|
|
26
|
+
| Build | `tsc` → `dist/`, `prepublishOnly` wired | `npm publish` cannot ship a stale `dist/`. |
|
|
27
|
+
| Bins | `bir`, `bir-proxy`, `bir-hooks`, `bir-scenario`, all with `#!/usr/bin/env node` | npm creates the shims, including `.cmd`/`.ps1` on Windows. |
|
|
28
|
+
| Tarball | 97 files, 230 kB | `dist/` + types + docs. Sourcemaps and `docs/next.md` are excluded. |
|
|
29
|
+
|
|
30
|
+
A zero-dependency, pure-JS, four-bin package is the best-behaved thing npm
|
|
31
|
+
distributes. The delivery does not need to be clever.
|
|
32
|
+
|
|
33
|
+
### What is excluded, and why
|
|
34
|
+
|
|
35
|
+
`files` carries two negations:
|
|
36
|
+
|
|
37
|
+
- `!dist/**/*.map` — 88 files, 287 kB. The maps carry no `sourcesContent`, and
|
|
38
|
+
`src/` is not published, so they cannot resolve a single stack frame. They were
|
|
39
|
+
bytes that did nothing on every install and every `npx` cold start.
|
|
40
|
+
- `!docs/next.md` — a working scratchpad, not documentation.
|
|
41
|
+
|
|
42
|
+
The rest of `docs/` **does** ship, and deliberately: the GitHub repository is
|
|
43
|
+
private, so for anyone on a runner machine the tarball is the only place this
|
|
44
|
+
documentation exists.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 2. The options, and why this one
|
|
49
|
+
|
|
50
|
+
| Approach | Runner install | Good for | Cost |
|
|
51
|
+
|---|---|---|---|
|
|
52
|
+
| **npm registry (public)** ← chosen | `npm i -g @basein/runner@0.1.0` | the default; makes the generated entries resolvable | the code is public |
|
|
53
|
+
| npm registry (private, scoped) | same, plus an `.npmrc` token | code you cannot publish | every runner needs a read token, and so does `npx` |
|
|
54
|
+
| GitHub Packages | same, `registry=https://npm.pkg.github.com` | no second account | GH tokens are clumsier to rotate across a fleet |
|
|
55
|
+
| Tarball from a Release | `npm i -g ./basein-runner-0.1.0.tgz` | airgapped runners | no registry resolution — see §6 |
|
|
56
|
+
| `npm i -g git+ssh://…#v0.1.0` | one line, no registry | a handful of machines | needs git and a toolchain on every runner; slowest and most fragile |
|
|
57
|
+
| Node SEA / bundled binary | copy one file | machines where Node cannot be installed | you own a three-OS build matrix |
|
|
58
|
+
| **Docker** | — | **not suitable** | The proxy spawns the host's own MCP servers as child processes, rewrites config in the user's home, and shares a cwd with the agent session. A container isolates exactly what this tool must reach. |
|
|
59
|
+
|
|
60
|
+
The package is public; the repository is private. Those are separate decisions
|
|
61
|
+
and this project makes them differently. Nothing operational leaks either way —
|
|
62
|
+
the service is gated by `BIR_AUTH_URL` plus credentials.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 3. Publishing
|
|
67
|
+
|
|
68
|
+
### 3.1 Manifest — done
|
|
69
|
+
|
|
70
|
+
`package.json` carries `repository` (npm requires it for provenance) and
|
|
71
|
+
`publishConfig.access: public` (a scoped package is private by default, and
|
|
72
|
+
`npm publish` on a scope you have no paid plan for otherwise fails).
|
|
73
|
+
|
|
74
|
+
There is deliberately **no** `provenance`, and no `--provenance` in the workflow:
|
|
75
|
+
npm's attestation requires a *public* repository. Turn both on the day that
|
|
76
|
+
changes; nothing else has to move.
|
|
77
|
+
|
|
78
|
+
`homepage` and `bugs` are also omitted, for the same reason — they would be dead
|
|
79
|
+
links on a public package page.
|
|
80
|
+
|
|
81
|
+
### 3.2 The name is load-bearing
|
|
82
|
+
|
|
83
|
+
`PACKAGE_NAME` in [generate.ts](../src/config/generate.ts) is written into every
|
|
84
|
+
config file the installer produces. Renaming after runners are deployed points
|
|
85
|
+
every previously-installed project at a package that no longer resolves. It is
|
|
86
|
+
settled now; treat it as fixed.
|
|
87
|
+
|
|
88
|
+
### 3.3 The publishing credential
|
|
89
|
+
|
|
90
|
+
The `basein` org exists on npmjs.com (free tier, public packages), and
|
|
91
|
+
`NPM_TOKEN` is set as a repository secret in GitHub Actions.
|
|
92
|
+
|
|
93
|
+
It is a **granular** token, not a classic automation one, because npm now steers
|
|
94
|
+
you there:
|
|
95
|
+
|
|
96
|
+
| Property | Value |
|
|
97
|
+
|---|---|
|
|
98
|
+
| Name | `github-actions-release` |
|
|
99
|
+
| Scope | `basein`, read and write |
|
|
100
|
+
| Organizations | no access — publishing needs the scope, not org administration |
|
|
101
|
+
| 2FA | bypassed, which is what lets CI publish unattended |
|
|
102
|
+
| **Expires** | **2026-10-05** |
|
|
103
|
+
|
|
104
|
+
**Two dates to keep.**
|
|
105
|
+
|
|
106
|
+
*2026-10-05 — the token dies.* Granular tokens must carry an expiry; npm offers
|
|
107
|
+
no perpetual option. When a release starts failing with `ENEEDAUTH` around then,
|
|
108
|
+
this is why. Regenerate with the same settings and overwrite the secret.
|
|
109
|
+
|
|
110
|
+
*January 2027 — direct token publishing ends.* The token summary says so
|
|
111
|
+
outright: "Publish new versions directly (deprecated — ends January 2027)". The
|
|
112
|
+
replacement is **trusted publishing**, where the workflow authenticates to npm
|
|
113
|
+
over OIDC and there is no stored credential at all — no secret to leak, and no
|
|
114
|
+
expiry to forget. Migrating also removes the first date above, so doing it before
|
|
115
|
+
2026-10-05 turns two chores into one.
|
|
116
|
+
|
|
117
|
+
Releasing is then a tag:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
git tag v0.1.0 && git push origin v0.1.0
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 3.4 The workflows — done
|
|
124
|
+
|
|
125
|
+
- [`.github/workflows/ci.yml`](../.github/workflows/ci.yml) — every push and PR
|
|
126
|
+
to main, on Ubuntu, Windows and macOS.
|
|
127
|
+
- [`.github/workflows/release.yml`](../.github/workflows/release.yml) — on a
|
|
128
|
+
`v*` tag: the same three-platform suite, then publish, then attach the tarball
|
|
129
|
+
to a GitHub Release.
|
|
130
|
+
|
|
131
|
+
Three platforms is not decoration. `resolveCommand` shells a `.cmd` upstream
|
|
132
|
+
through `cmd.exe` with its own quoting, and `normalizePath` reconciles Claude
|
|
133
|
+
Code's forward slashes with `process.cwd()`'s backslashes — the failure the code
|
|
134
|
+
itself calls "the number one cause of *installed but never runs*". Neither is
|
|
135
|
+
exercised on Linux.
|
|
136
|
+
|
|
137
|
+
Both workflows run `npm run build` before the tests, because `test:smoke` spawns
|
|
138
|
+
the real binaries out of `dist/` while `npm test` only builds `.test-build/`.
|
|
139
|
+
|
|
140
|
+
The release job refuses to publish when the tag disagrees with `package.json` —
|
|
141
|
+
npm has no undo past 72 hours, and a mismatched tag publishes a version nobody
|
|
142
|
+
asked for.
|
|
143
|
+
|
|
144
|
+
**One known flake.** `run_scenario finds the session whose plan is armed` loses
|
|
145
|
+
an arming race roughly one run in three. It is unrelated to packaging, and the
|
|
146
|
+
test steps retry once so it cannot redden a release at random. Remove the retry
|
|
147
|
+
the day that test is made deterministic.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 4. What `bir install` writes
|
|
152
|
+
|
|
153
|
+
Three invocation modes, one flag each:
|
|
154
|
+
|
|
155
|
+
| Mode | Flag | Entry |
|
|
156
|
+
|---|---|---|
|
|
157
|
+
| **npx** | *(default)* | `npx -y -p @basein/runner@0.1.0 bir-proxy …` |
|
|
158
|
+
| **global** | `--global` | `<node> <prefix>/node_modules/@basein/runner/dist/bin/bir-proxy.js …` |
|
|
159
|
+
| **local** | `--local` | `<node> <this checkout>/dist/bin/bir-proxy.js …` |
|
|
160
|
+
|
|
161
|
+
**The npx spec is pinned.** It was not, and that was the one real defect in the
|
|
162
|
+
original packaging story: two runners provisioned a week apart could run
|
|
163
|
+
different proxy versions against the same service, with nothing in a recording to
|
|
164
|
+
say which.
|
|
165
|
+
|
|
166
|
+
**`--global` is what a fleet wants**, and it is what the bootstrap scripts pass.
|
|
167
|
+
It removes the registry from the hot path entirely: no resolution before the MCP
|
|
168
|
+
server can start, no dependence on the MCP host seeing the same `PATH` as the
|
|
169
|
+
shell that installed things — which GUI-launched hosts on Windows do not — and
|
|
170
|
+
it works with no network at all.
|
|
171
|
+
|
|
172
|
+
It also survives upgrades. The path
|
|
173
|
+
`<prefix>/node_modules/@basein/runner/dist/bin/bir-proxy.js` contains no version,
|
|
174
|
+
so `npm i -g @basein/runner@0.2.0` changes what that path points at without
|
|
175
|
+
changing the path. **A pinned-npx install, by contrast, must re-run `bir install`
|
|
176
|
+
on every upgrade** to move the pin.
|
|
177
|
+
|
|
178
|
+
`--global` refuses to run from a checkout: it asserts a path that outlives the
|
|
179
|
+
shell, and a checkout's `dist/` moves with the checkout. Use `--local` when you
|
|
180
|
+
mean the checkout on purpose.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## 5. Provisioning a runner
|
|
185
|
+
|
|
186
|
+
Four scopes, and conflating them is the usual source of "installed but never
|
|
187
|
+
runs":
|
|
188
|
+
|
|
189
|
+
| Scope | How often | What it does |
|
|
190
|
+
|---|---|---|
|
|
191
|
+
| **machine** | once | Node ≥ 20, and the package installed globally |
|
|
192
|
+
| **account** | once per user | `BIR_AUTH_URL`, then `bir login` → `~/.baseinstrunner/credentials.json` (0600) |
|
|
193
|
+
| **project** | once per repo | `bir install --global` rewrites that project's MCP entries and wires the hooks |
|
|
194
|
+
| **session** | while recording | `bir-hooks` running, discovered by `sha256(normalised cwd)` |
|
|
195
|
+
|
|
196
|
+
That cwd keying is why `bir-hooks` must start in the *same directory* the agent
|
|
197
|
+
session runs in. A control server started elsewhere is invisible, and the run
|
|
198
|
+
silently degrades to Tier 2.
|
|
199
|
+
|
|
200
|
+
### 5.1 One command
|
|
201
|
+
|
|
202
|
+
[`scripts/install-runner.ps1`](../scripts/install-runner.ps1) and
|
|
203
|
+
[`scripts/install-runner.sh`](../scripts/install-runner.sh) do machine, account
|
|
204
|
+
and project scope together:
|
|
205
|
+
|
|
206
|
+
```powershell
|
|
207
|
+
.\scripts\install-runner.ps1 C:\work\repo -AuthUrl https://basein.example.com
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
BIR_AUTH_URL=https://basein.example.com ./scripts/install-runner.sh ~/work/repo
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Both are idempotent, both validate everything checkable **before** changing
|
|
215
|
+
anything (a failed run leaves no half-provisioned machine), both skip `bir login`
|
|
216
|
+
when a session is already cached, and both accept a local tarball
|
|
217
|
+
(`-Tarball` / `BIR_TARBALL`) instead of the registry.
|
|
218
|
+
|
|
219
|
+
Two things they deliberately do not do: supervise `bir-hooks`, and automate
|
|
220
|
+
authentication. See §5.2 and §7.
|
|
221
|
+
|
|
222
|
+
The PowerShell script is **pure ASCII** on purpose. Windows PowerShell 5.1
|
|
223
|
+
decodes a BOM-less `.ps1` using the machine's ANSI codepage, and PowerShell
|
|
224
|
+
accepts curly quotes as string delimiters — so a UTF-8 em-dash in a comment can
|
|
225
|
+
decode into a quote and swallow the rest of the file. It parsed fine on one
|
|
226
|
+
machine and failed on another before this was fixed. Keep it ASCII.
|
|
227
|
+
|
|
228
|
+
### 5.2 Keeping `bir-hooks` alive
|
|
229
|
+
|
|
230
|
+
One process per project cwd. On a headless runner, supervise it rather than
|
|
231
|
+
leaving it in a shell.
|
|
232
|
+
|
|
233
|
+
**Windows** — a Scheduled Task at logon, with *Start in* set to the project
|
|
234
|
+
directory:
|
|
235
|
+
|
|
236
|
+
```powershell
|
|
237
|
+
$action = New-ScheduledTaskAction -Execute 'bir-hooks' -WorkingDirectory 'C:\work\repo'
|
|
238
|
+
$trigger = New-ScheduledTaskTrigger -AtLogOn
|
|
239
|
+
Register-ScheduledTask -TaskName 'bir-hooks (repo)' -Action $action -Trigger $trigger
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**macOS** — a launchd agent at `~/Library/LaunchAgents/com.basein.hooks.plist`,
|
|
243
|
+
with `WorkingDirectory` set to the project and `StandardErrorPath` to a log file,
|
|
244
|
+
then `launchctl load` it.
|
|
245
|
+
|
|
246
|
+
**Linux** — a systemd user unit with `WorkingDirectory=`, plus
|
|
247
|
+
`loginctl enable-linger "$USER"` so it survives logout.
|
|
248
|
+
|
|
249
|
+
Whichever you use, redirect stderr to a file. Every decision is one `key=value`
|
|
250
|
+
line on stderr and there is no file sink to configure, so the supervisor's
|
|
251
|
+
redirection *is* the sink:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
grep version.skew ~/.baseinstrunner/audit.log
|
|
255
|
+
grep run.lossy ~/.baseinstrunner/audit.log
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### 5.3 Verify
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
bir doctor --json # exits non-zero if it is not working right now
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
`bir doctor` asks the control server which proxies actually registered; it never
|
|
265
|
+
reads the config file back. That makes it the right gate for a provisioning
|
|
266
|
+
script. `bir status` only describes intent.
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## 6. Machines with no registry
|
|
271
|
+
|
|
272
|
+
Zero dependencies makes offline install trivial — no `npm ci --offline`, no
|
|
273
|
+
vendored `node_modules`:
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
# on a connected machine
|
|
277
|
+
npm pack @basein/runner@0.1.0 # or download the Release asset
|
|
278
|
+
|
|
279
|
+
# on the runner
|
|
280
|
+
BIR_TARBALL=./basein-runner-0.1.0.tgz ./scripts/install-runner.sh ~/work/repo
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
The bootstrap scripts already pass `--global`, so the resulting configs contain
|
|
284
|
+
absolute paths and never reach for a registry. Nothing further is needed.
|
|
285
|
+
|
|
286
|
+
Note that a private repository's Release assets require a GitHub token to fetch,
|
|
287
|
+
so the tarball has to be carried to the runner by whatever means you already use.
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## 7. Headless authentication — still a stopgap
|
|
292
|
+
|
|
293
|
+
`bir login` prompts for email and password on a TTY and caches
|
|
294
|
+
`{accessToken, refreshToken, accessExpiresAt, user}` at
|
|
295
|
+
`~/.baseinstrunner/credentials.json`, mode 0600. For a fleet, in order of
|
|
296
|
+
preference:
|
|
297
|
+
|
|
298
|
+
1. **Pre-seed the credentials file.** Log in once as a dedicated service account,
|
|
299
|
+
copy `credentials.json` to each machine at 0600. The refresh token keeps it
|
|
300
|
+
alive and nothing prompts. Works today — but it is a shared long-lived secret,
|
|
301
|
+
so never a person's account.
|
|
302
|
+
2. **`BIR_NO_AUTH` / `BIR_AUTH_DISABLE=1`** for runners that should record
|
|
303
|
+
nothing. Everything else still works.
|
|
304
|
+
3. **Ask the service for a non-interactive grant** — client credentials, or a
|
|
305
|
+
long-lived runner token read from an env var. This is the right answer for a
|
|
306
|
+
fleet, and it is a service-side change. Worth raising before the fleet grows.
|
|
307
|
+
|
|
308
|
+
`BIR_HOME` relocates all of this if state belongs somewhere other than
|
|
309
|
+
`~/.baseinstrunner`.
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## 8. Updating a runner
|
|
314
|
+
|
|
315
|
+
Re-run the bootstrap script with the new version. It is idempotent, it will not
|
|
316
|
+
prompt for a login it already has, and `bir install` will not double-wrap:
|
|
317
|
+
|
|
318
|
+
```powershell
|
|
319
|
+
.\scripts\install-runner.ps1 C:\work\repo -Version 0.2.0 -AuthUrl https://basein.example.com
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
BIR_VERSION=0.2.0 BIR_AUTH_URL=https://basein.example.com ./scripts/install-runner.sh ~/work/repo
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Then two rules:
|
|
327
|
+
|
|
328
|
+
- **Restart `bir-hooks`.** A running process is still the old code. This is the
|
|
329
|
+
step people forget.
|
|
330
|
+
- **Re-run `bir install` only if the entry shape changed** — or always, since it
|
|
331
|
+
is idempotent. With `--global` entries an upgrade needs no re-install at all
|
|
332
|
+
(§4). With pinned-npx entries it always does.
|
|
333
|
+
|
|
334
|
+
Rolling back is the same command with the older version.
|
|
335
|
+
|
|
336
|
+
### The half-upgraded machine
|
|
337
|
+
|
|
338
|
+
This is the update failure that looks like success: the package is new, but a
|
|
339
|
+
proxy is still the old code because a config points elsewhere or `bir-hooks` was
|
|
340
|
+
never restarted. Everything keeps working, and quietly runs code you did not
|
|
341
|
+
ship.
|
|
342
|
+
|
|
343
|
+
A proxy now reports its version when it registers, the control server compares it
|
|
344
|
+
with its own, and a mismatch is one line on the audit log:
|
|
345
|
+
|
|
346
|
+
```
|
|
347
|
+
[bir] … version.skew server=fleet proxy=0.0.9 hooks=0.1.0 fix="restart bir-hooks after upgrading, …"
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
`bir doctor --json` carries the same facts structurally — `hooksVersion` at the
|
|
351
|
+
top and a `version` on each entry of `registeredProxies` — so a provisioning
|
|
352
|
+
script can assert agreement rather than grep a log. A proxy too old to report a
|
|
353
|
+
version is not treated as skewed: silence, not a false alarm.
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## 9. Per-release verification
|
|
358
|
+
|
|
359
|
+
CI does this on three platforms. To do it by hand on a clean machine:
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
npm run build && npm test # 233 tests
|
|
363
|
+
npm run test:smoke # real processes: hooks + proxy + scenario over stdio
|
|
364
|
+
npm pack && npm i -g ./basein-runner-*.tgz
|
|
365
|
+
cd /tmp/scratch && bir install --global && bir-hooks &
|
|
366
|
+
bir doctor --json # must exit 0
|
|
367
|
+
bir uninstall # must restore the config byte-for-byte
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
`test:smoke` is the one that matters for packaging: it is the only test that
|
|
371
|
+
crosses process boundaries, which is exactly where a bad `files` list, a missing
|
|
372
|
+
shebang, or a broken bin mapping shows up.
|
|
373
|
+
|
|
374
|
+
Sandbox a manual run with `BIR_HOME` and `BIR_CLAUDE_JSON` so it cannot touch
|
|
375
|
+
your real Claude Code configuration.
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## 10. Where things stand
|
|
380
|
+
|
|
381
|
+
| # | Item | State |
|
|
382
|
+
|---|---|---|
|
|
383
|
+
| 1 | Name settled; `PACKAGE_NAME` aligned | done |
|
|
384
|
+
| 2 | `repository` + `publishConfig`; provenance deferred (private repo) | done |
|
|
385
|
+
| 3 | Tarball trimmed — no dead sourcemaps, no scratchpad | done |
|
|
386
|
+
| 4 | npx spec pinned; `--global` added | done |
|
|
387
|
+
| 5 | CI and release workflows, three platforms | done |
|
|
388
|
+
| 6 | `scripts/install-runner.{ps1,sh}` | done |
|
|
389
|
+
| 7 | Version-skew detection on registration | done |
|
|
390
|
+
| 8 | UTF-8 BOM tolerated in MCP configs | done |
|
|
391
|
+
| 9 | npm org `basein`, `NPM_TOKEN` secret | done |
|
|
392
|
+
| 10 | Tag `v0.1.0` to publish | ready |
|
|
393
|
+
| 11 | Non-interactive auth grant for fleets | **needs the service** |
|
|
394
|
+
| 12 | Migrate to trusted publishing before Jan 2027 (§3.3) | scheduled |
|
|
395
|
+
|
|
396
|
+
Item 11 is the only one that needs somebody else.
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## Appendix: the BOM
|
|
401
|
+
|
|
402
|
+
`bir` used to reject any MCP config saved with a UTF-8 byte order mark. Notepad
|
|
403
|
+
writes one, so does VS Code's "UTF-8 with BOM", and so does PowerShell's
|
|
404
|
+
`Out-File -Encoding utf8`. `readFileSync(path, "utf8")` decodes the BOM to U+FEFF
|
|
405
|
+
rather than consuming it, and `JSON.parse` then rejects the file.
|
|
406
|
+
|
|
407
|
+
The failure mode was the bad part: `bir install` logged the parse error and
|
|
408
|
+
**still reported success**, so an operator saw a green run and no wrapping. On
|
|
409
|
+
Windows runners this was a matter of time.
|
|
410
|
+
|
|
411
|
+
`parseJsonFile` now strips it, which covers every user-authored config read.
|
|
412
|
+
Restoring is unaffected — the uninstall backup stashes raw text on a separate
|
|
413
|
+
path, so a BOM'd file comes back with its BOM intact.
|