@basein/runner 0.2.11 → 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 -350
- 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 -10
- package/dist/bin/bir-scenario.js +704 -135
- package/dist/bin/bir.d.ts +14 -8
- package/dist/bin/bir.js +2937 -488
- package/dist/bin/investigate.js +41 -16
- package/dist/bin/scenario-edit.d.ts +43 -10
- package/dist/bin/scenario-edit.js +281 -80
- 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 +2803 -306
- 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/housekeeping.d.ts +71 -0
- package/dist/record/housekeeping.js +417 -0
- 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 +371 -20
- package/dist/replay/controller.js +1185 -111
- 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 -1244
- package/docs/calculatedReplayGuide.md +1212 -879
- 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 -271
- 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
package/docs/installRun.md
CHANGED
|
@@ -1,772 +1,1013 @@
|
|
|
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
|
|
5
|
-
invocation of its own package name into every wrapped MCP entry
|
|
6
|
-
was less a new distribution channel than
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
to
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
|
25
|
-
|
|
|
26
|
-
|
|
|
27
|
-
|
|
|
28
|
-
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
| npm registry (
|
|
54
|
-
|
|
|
55
|
-
|
|
|
56
|
-
| `npm i -g
|
|
57
|
-
|
|
|
58
|
-
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
`
|
|
72
|
-
`
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
`
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
|
100
|
-
|
|
|
101
|
-
|
|
|
102
|
-
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
| **global** | `--global` | `<node> <prefix>/lib/node_modules/@basein/runner/dist/bin/bir-proxy.js …` (Windows: `<prefix>/node_modules/@basein/runner/…`) |
|
|
159
|
-
| **
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
and
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
`
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
the
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
setup
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
`
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
`
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
`
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
`bir
|
|
663
|
-
|
|
664
|
-
```
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
`bir
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
---
|
|
705
|
-
|
|
706
|
-
##
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
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 wrote an `npx`
|
|
5
|
+
invocation of its own package name into every wrapped MCP entry (it still does
|
|
6
|
+
with `--npx`, §4) — so publishing was less a new distribution channel than
|
|
7
|
+
closing a loop that was already there.
|
|
8
|
+
|
|
9
|
+
This is the fleet-facing reference: what ships, how a runner machine goes from
|
|
10
|
+
bare to recording, how it gets updated, and what to do when there is no registry
|
|
11
|
+
to reach. For one person setting up one machine, [quickstart.md](quickstart.md)
|
|
12
|
+
is shorter and assumes nothing.
|
|
13
|
+
|
|
14
|
+
**Status.** Everything here is implemented except the last item of §7: a
|
|
15
|
+
non-interactive grant for machines with no person attached needs a service-side
|
|
16
|
+
decision rather than a change here.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 1. Why this package is an easy case
|
|
21
|
+
|
|
22
|
+
| Property | Value | Why it matters for packaging |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| Runtime dependencies | **none** | The tarball is self-contained. No lockfile resolution, no transitive CVEs, no native builds, and offline install just works. |
|
|
25
|
+
| Module format | pure ESM (`"type": "module"`) | No dual-build gymnastics. |
|
|
26
|
+
| Node floor | `>=20` | The one prerequisite — and the served line installs a private copy when the machine has none, or only one the hook must not pin: a version-managed, Homebrew or snap Node, or one with no npm beside it (§5.1). |
|
|
27
|
+
| Build | `tsc` → `dist/`, `prepublishOnly` wired | `npm publish` cannot ship a stale `dist/`. |
|
|
28
|
+
| Bins | `bir`, `bir-proxy`, `bir-hooks`, `bir-scenario`, all with `#!/usr/bin/env node` | npm creates the shims, including `.cmd`/`.ps1` on Windows. |
|
|
29
|
+
| Tarball | 161 files, ~910 kB | `dist/` + types + docs + `production.md` + `scripts/` (the install scripts, so a tarball can bootstrap an offline machine). Sourcemaps and `docs/next.md` are excluded. |
|
|
30
|
+
|
|
31
|
+
A zero-dependency, pure-JS, four-bin package is the best-behaved thing npm
|
|
32
|
+
distributes. The delivery does not need to be clever.
|
|
33
|
+
|
|
34
|
+
### What is excluded, and why
|
|
35
|
+
|
|
36
|
+
`files` carries two negations:
|
|
37
|
+
|
|
38
|
+
- `!dist/**/*.map` — 146 files, ~1.2 MB. The maps carry no `sourcesContent`, and
|
|
39
|
+
`src/` is not published, so they cannot resolve a single stack frame. They were
|
|
40
|
+
bytes that did nothing on every install and every `npx` cold start.
|
|
41
|
+
- `!docs/next.md` — a working scratchpad, not documentation.
|
|
42
|
+
|
|
43
|
+
The rest of `docs/` **does** ship, and deliberately: the GitHub repository is
|
|
44
|
+
private, so for anyone on a runner machine the tarball is the only place this
|
|
45
|
+
documentation exists.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 2. The options, and why this one
|
|
50
|
+
|
|
51
|
+
| Approach | Runner install | Good for | Cost |
|
|
52
|
+
|---|---|---|---|
|
|
53
|
+
| **npm registry (public)** ← chosen | `npm i -g @basein/runner@0.1.0` | the default; makes the generated entries resolvable | the code is public |
|
|
54
|
+
| npm registry (private, scoped) | same, plus an `.npmrc` token | code you cannot publish | every runner needs a read token, and so does `npx` |
|
|
55
|
+
| GitHub Packages | same, `registry=https://npm.pkg.github.com` | no second account | GH tokens are clumsier to rotate across a fleet |
|
|
56
|
+
| Tarball from a Release | `npm i -g ./basein-runner-0.1.0.tgz` | airgapped runners | no registry resolution — see §6 |
|
|
57
|
+
| `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 |
|
|
58
|
+
| Node SEA / bundled binary | copy one file | machines where Node cannot be installed | you own a three-OS build matrix |
|
|
59
|
+
| **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. |
|
|
60
|
+
|
|
61
|
+
The package is public; the repository is private. Those are separate decisions
|
|
62
|
+
and this project makes them differently. Nothing operational leaks either way —
|
|
63
|
+
the service is gated by its address plus credentials.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 3. Publishing
|
|
68
|
+
|
|
69
|
+
### 3.1 Manifest — done
|
|
70
|
+
|
|
71
|
+
`package.json` carries `repository` (npm requires it for provenance) and
|
|
72
|
+
`publishConfig.access: public` (a scoped package is private by default, and
|
|
73
|
+
`npm publish` on a scope you have no paid plan for otherwise fails).
|
|
74
|
+
|
|
75
|
+
There is deliberately **no** `provenance`, and no `--provenance` in the workflow:
|
|
76
|
+
npm's attestation requires a *public* repository. Turn both on the day that
|
|
77
|
+
changes; nothing else has to move.
|
|
78
|
+
|
|
79
|
+
`homepage` and `bugs` are also omitted, for the same reason — they would be dead
|
|
80
|
+
links on a public package page.
|
|
81
|
+
|
|
82
|
+
### 3.2 The name is load-bearing
|
|
83
|
+
|
|
84
|
+
`PACKAGE_NAME` in [generate.ts](../src/config/generate.ts) is written into every
|
|
85
|
+
config file the installer produces. Renaming after runners are deployed points
|
|
86
|
+
every previously-installed project at a package that no longer resolves. It is
|
|
87
|
+
settled now; treat it as fixed.
|
|
88
|
+
|
|
89
|
+
### 3.3 The publishing credential
|
|
90
|
+
|
|
91
|
+
The `basein` org exists on npmjs.com (free tier, public packages), and
|
|
92
|
+
`NPM_TOKEN` is set as a repository secret in GitHub Actions.
|
|
93
|
+
|
|
94
|
+
It is a **granular** token, not a classic automation one, because npm now steers
|
|
95
|
+
you there:
|
|
96
|
+
|
|
97
|
+
| Property | Value |
|
|
98
|
+
|---|---|
|
|
99
|
+
| Name | `github-actions-release` |
|
|
100
|
+
| Scope | `basein`, read and write |
|
|
101
|
+
| Organizations | no access — publishing needs the scope, not org administration |
|
|
102
|
+
| 2FA | bypassed, which is what lets CI publish unattended |
|
|
103
|
+
| **Expires** | **2026-10-05** |
|
|
104
|
+
|
|
105
|
+
**Two dates to keep.**
|
|
106
|
+
|
|
107
|
+
*2026-10-05 — the token dies.* Granular tokens must carry an expiry; npm offers
|
|
108
|
+
no perpetual option. When a release starts failing with `ENEEDAUTH` around then,
|
|
109
|
+
this is why. Regenerate with the same settings and overwrite the secret.
|
|
110
|
+
|
|
111
|
+
*January 2027 — direct token publishing ends.* The token summary says so
|
|
112
|
+
outright: "Publish new versions directly (deprecated — ends January 2027)". The
|
|
113
|
+
replacement is **trusted publishing**, where the workflow authenticates to npm
|
|
114
|
+
over OIDC and there is no stored credential at all — no secret to leak, and no
|
|
115
|
+
expiry to forget. Migrating also removes the first date above, so doing it before
|
|
116
|
+
2026-10-05 turns two chores into one.
|
|
117
|
+
|
|
118
|
+
Releasing is then a tag:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
git tag v0.1.0 && git push origin v0.1.0
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 3.4 The workflows — done
|
|
125
|
+
|
|
126
|
+
- [`.github/workflows/ci.yml`](../.github/workflows/ci.yml) — every push and PR
|
|
127
|
+
to main, on Ubuntu, Windows and macOS.
|
|
128
|
+
- [`.github/workflows/release.yml`](../.github/workflows/release.yml) — on a
|
|
129
|
+
`v*` tag: the same three-platform suite, then publish, then attach the tarball
|
|
130
|
+
to a GitHub Release.
|
|
131
|
+
|
|
132
|
+
Three platforms is not decoration. `resolveCommand` shells a `.cmd` upstream
|
|
133
|
+
through `cmd.exe` with its own quoting, and `normalizePath` reconciles Claude
|
|
134
|
+
Code's forward slashes with `process.cwd()`'s backslashes — the failure the code
|
|
135
|
+
itself calls "the number one cause of *installed but never runs*". Neither is
|
|
136
|
+
exercised on Linux.
|
|
137
|
+
|
|
138
|
+
Both workflows run `npm run build` before the tests, because `test:smoke` spawns
|
|
139
|
+
the real binaries out of `dist/` while `npm test` only builds `.test-build/`.
|
|
140
|
+
|
|
141
|
+
The release job refuses to publish when the tag disagrees with `package.json` —
|
|
142
|
+
npm has no undo past 72 hours, and a mismatched tag publishes a version nobody
|
|
143
|
+
asked for.
|
|
144
|
+
|
|
145
|
+
**One known flake.** `run_scenario finds the session whose plan is armed` loses
|
|
146
|
+
an arming race roughly one run in three. It is unrelated to packaging, and the
|
|
147
|
+
test steps retry once so it cannot redden a release at random. Remove the retry
|
|
148
|
+
the day that test is made deterministic.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 4. What `bir install` writes
|
|
153
|
+
|
|
154
|
+
Three invocation modes, one flag each:
|
|
155
|
+
|
|
156
|
+
| Mode | Flag | Entry |
|
|
157
|
+
|---|---|---|
|
|
158
|
+
| **global** | `--global`; the flag-less default when `bir` runs from an installed package | `<node> [flags] <prefix>/lib/node_modules/@basein/runner/dist/bin/bir-proxy.js …` (Windows: `<prefix>/node_modules/@basein/runner/…`) |
|
|
159
|
+
| **npx** | `--npx`; the flag-less default only from a checkout or from npx's cache | `npx --prefer-offline -y -p @basein/runner@0.2.9 bir-proxy …` — plain `npx` on Windows too; only the npx SessionStart hook runs through `cmd /c` there |
|
|
160
|
+
| **local** | `--local` | `<node> [flags] <this checkout>/dist/bin/bir-proxy.js …` |
|
|
161
|
+
|
|
162
|
+
Where the entries are written — this project's local scope in `~/.claude.json`,
|
|
163
|
+
not `.mcp.json` — is §4.4.
|
|
164
|
+
|
|
165
|
+
`<node>` is the Node that ran the install, by absolute path: on macOS and Linux
|
|
166
|
+
with symlinks resolved, except that a Homebrew keg is named by Homebrew's stable
|
|
167
|
+
`opt/<formula>/bin/node` link, which a `brew upgrade` keeps; on Windows the path
|
|
168
|
+
it was launched through (nvm-windows' and Scoop's stable links), except fnm's
|
|
169
|
+
per-shell directory. A Node that a version manager will move gets a one-line
|
|
170
|
+
warning at install. `[flags]` are `--use-system-ca` when the install ran with
|
|
171
|
+
it, and `--use-env-proxy` when it did or a proxy is set — either way only while
|
|
172
|
+
`NO_PROXY` exempts `127.0.0.1` (§5.1). Under the served line that Node is the
|
|
173
|
+
machine's own when it qualifies (20 or newer, not under a version manager, npm
|
|
174
|
+
beside it), else the private Node in `~/.baseinstrunner/node/v<V>/`; `<prefix>`
|
|
175
|
+
is `~/.baseinstrunner/npm` either way. By hand it is whatever `npm i -g` used.
|
|
176
|
+
npm's global layout differs by platform:
|
|
177
|
+
the package is at `<prefix>/lib/node_modules/@basein/runner` on macOS and Linux
|
|
178
|
+
and at `<prefix>/node_modules/@basein/runner` on Windows.
|
|
179
|
+
|
|
180
|
+
**The npx spec is pinned.** It was not, and that was the one real defect in the
|
|
181
|
+
original packaging story: two runners provisioned a week apart could run
|
|
182
|
+
different proxy versions against the same service, with nothing in a recording to
|
|
183
|
+
say which. Pinned is also what makes `--prefer-offline` safe: a cached copy of an
|
|
184
|
+
exact version starts without asking the registry.
|
|
185
|
+
|
|
186
|
+
**Absolute paths are what a fleet wants**, and they are the default: a flag-less
|
|
187
|
+
`bir install` from an installed package writes `global` entries, and `bir setup`
|
|
188
|
+
runs `bir install --global --replay` in-process (from a checkout `--local`, since
|
|
189
|
+
`--global` refuses one; run as `npx @basein/runner setup`, pinned npx, never a
|
|
190
|
+
path into npx's cache). It removes the registry from the hot
|
|
191
|
+
path entirely: no resolution before the MCP server can start, no dependence on
|
|
192
|
+
the MCP host seeing the same `PATH` as the shell that installed things — which
|
|
193
|
+
GUI-launched hosts on Windows do not — and it works with no network at all.
|
|
194
|
+
|
|
195
|
+
It also survives upgrades. The path
|
|
196
|
+
`<prefix>/lib/node_modules/@basein/runner/dist/bin/bir-proxy.js` contains no
|
|
197
|
+
version, so `npm i -g @basein/runner@<version>` changes what that path points at
|
|
198
|
+
without changing the path. **A pinned-npx install, by contrast, must re-run
|
|
199
|
+
`bir install` on every upgrade** to move the pin.
|
|
200
|
+
|
|
201
|
+
A flag-less `bir install` keeps the invocation the project was installed with
|
|
202
|
+
(`invocation` in its record, §4.3): a `bir install --replay` after `bir setup`
|
|
203
|
+
keeps the absolute paths instead of quietly turning every entry into a
|
|
204
|
+
pinned-npx one. The one exception is a remembered npx that nobody asked for with
|
|
205
|
+
`--npx` — the flag-less default of older versions — which an installed `bir`
|
|
206
|
+
turns into absolute paths.
|
|
207
|
+
|
|
208
|
+
**An entry whose runner moved is re-wrapped.** `bir install` regenerates each
|
|
209
|
+
wrapped entry and compares it with what is there — its Node path and flags, its
|
|
210
|
+
proxy path or pinned npx version, its control port, the upstream entry it
|
|
211
|
+
wraps — and rewrites any that differ from its source:
|
|
212
|
+
`~ fleet — re-wrapping: its runner moved (was …)`. The
|
|
213
|
+
scenario entry gets the same check. That is what lets `bir setup` be the upgrade
|
|
214
|
+
path even when the package changed prefix (from the machine's npm prefix to the
|
|
215
|
+
private one) or the Node moved (a version manager, a reinstall), instead of
|
|
216
|
+
printing "already wrapped" about an entry the host would keep spawning from a
|
|
217
|
+
path that is gone.
|
|
218
|
+
|
|
219
|
+
`--global` refuses to run from a checkout: it asserts a path that outlives the
|
|
220
|
+
shell, and a checkout's `dist/` moves with the checkout. Use `--local` when you
|
|
221
|
+
mean the checkout on purpose.
|
|
222
|
+
|
|
223
|
+
### 4.1 Hooks go to `.claude/settings.local.json`
|
|
224
|
+
|
|
225
|
+
Claude Code's hooks are written to `<project>/.claude/settings.local.json`, not
|
|
226
|
+
to the `.claude/settings.json` a team commits. They carry a bearer token and an
|
|
227
|
+
absolute path to this machine's Node; neither belongs in a teammate's clone.
|
|
228
|
+
Inside a git repository `bir` adds the line `.claude/settings.local.json` to
|
|
229
|
+
`.git/info/exclude` — the person's own, untracked list, never a `.gitignore` the
|
|
230
|
+
team would see in a diff — and says so. A linked worktree's `gitdir:` pointer
|
|
231
|
+
is followed to the repository's common exclude file, the only one git reads for
|
|
232
|
+
it; nothing is created inside the worktree. Outside a repository, or when the
|
|
233
|
+
exclude file cannot be written, that is a note, not a failed install.
|
|
234
|
+
|
|
235
|
+
An install that finds `bir` hooks in a legacy `.claude/settings.json` removes
|
|
236
|
+
them from there on the way (`- hooks removed from … (they live in
|
|
237
|
+
settings.local.json now)`). `bir uninstall` cleans both files and takes the
|
|
238
|
+
`.claude/settings.local.json` line out of `.git/info/exclude` again — unless the
|
|
239
|
+
line was there before the install. It restores
|
|
240
|
+
a file byte-for-byte only when nothing else edited it since the install; a file
|
|
241
|
+
it created — usually `settings.local.json` itself — stays behind, empty of our
|
|
242
|
+
entries.
|
|
243
|
+
|
|
244
|
+
With `--replay`, `bir install` also adds the `bir` scenario server, beside the
|
|
245
|
+
wrapped entries in this project's local scope (§4.4). Claude Code never asks
|
|
246
|
+
before it starts a local-scope server, so there is no `enabledMcpjsonServers`
|
|
247
|
+
pre-approval any more; an install finds an older one in `settings.local.json`
|
|
248
|
+
and takes it out. Without `--replay`, in a project with no scenario server,
|
|
249
|
+
replay is stored *off* (§4.3). `bir uninstall --replay` removes only the
|
|
250
|
+
scenario server and switches replay off, restarting an idle recorder so that
|
|
251
|
+
applies at once; the proxies and the hooks stay as they are, and
|
|
252
|
+
`bir install --replay` turns both back on.
|
|
253
|
+
|
|
254
|
+
### 4.2 SessionStart is a command hook
|
|
255
|
+
|
|
256
|
+
Every install writes the SessionStart hook as a command hook in
|
|
257
|
+
Claude Code's exec form — `command` spawned directly with `args` as its argument
|
|
258
|
+
vector, no shell in between, so a path with a space needs no quoting and it runs
|
|
259
|
+
the same under Git Bash, PowerShell and cmd.exe:
|
|
260
|
+
|
|
261
|
+
```json
|
|
262
|
+
{ "type": "command", "command": "<node>", "args": ["<dist>/bin/bir-hooks.js", "ensure"], "timeout": 15 }
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
(`args` begins with the node flags of §4 when there are any.) A pinned-npx
|
|
266
|
+
install writes the same hook through npx —
|
|
267
|
+
`npx --prefer-offline -y -p @basein/runner@<v> bir-hooks ensure --budget-ms 8000`,
|
|
268
|
+
inside `cmd /c` on Windows — so npx's start-up is paid once per session and still
|
|
269
|
+
fits the 15 s. The hook's own work is capped at 11 s (8 s for the npx form) and
|
|
270
|
+
never waits on the BaseIn service.
|
|
271
|
+
|
|
272
|
+
`bir-hooks ensure` reads the SessionStart payload from stdin, starts the
|
|
273
|
+
recorder for this directory in the background when none answers `/health`, and
|
|
274
|
+
relays the payload to it. It *replaces* the HTTP SessionStart hook rather than
|
|
275
|
+
sitting beside it: Claude Code runs every hook of an event in parallel, and an
|
|
276
|
+
HTTP hook racing the daemon it depends on would fail on every first session of
|
|
277
|
+
the day. It prints nothing on stdout — which Claude Code adds to the model's
|
|
278
|
+
context — unless the control server answered with something, and it always exits
|
|
279
|
+
0: a recorder that would not start is one line on stderr and a session that
|
|
280
|
+
records Tier 2, exactly what happened before the hook existed.
|
|
281
|
+
|
|
282
|
+
Every other event (`UserPromptSubmit`, `PreToolUse`, `PostToolUse`, `Stop`,
|
|
283
|
+
`SessionEnd`, …) stays an HTTP hook aimed at the control server, with the
|
|
284
|
+
project's bearer token in its `Authorization` header. Their timeouts are the
|
|
285
|
+
longest a hung service could make a person wait, so they are short:
|
|
286
|
+
`UserPromptSubmit` 10 s, `PreToolUse` 15 s, `PostToolUse`/`PostToolUseFailure`,
|
|
287
|
+
`Stop`/`StopFailure` 5 s, `SubagentStart`/`SubagentStop` and `SessionEnd` 3 s.
|
|
288
|
+
The control server answers each well inside its timeout, `{}` on an overrun
|
|
289
|
+
(see [production.md](../production.md)). Re-running `bir setup` rewrites
|
|
290
|
+
the timeouts of an older install.
|
|
291
|
+
|
|
292
|
+
`bir doctor` checks that the hook's `command` and script still exist and, when
|
|
293
|
+
the Node does not, reports:
|
|
294
|
+
|
|
295
|
+
```
|
|
296
|
+
the SessionStart hook in <file> points at a Node that is gone (<command>) — run `bir setup` here again
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
That is the failure a version manager's per-shell directory produces, and it is
|
|
300
|
+
why the bootstrap script installs a private Node rather than pin one that lives
|
|
301
|
+
under `fnm_multishells` or `.nvm` (§5.1).
|
|
302
|
+
|
|
303
|
+
### 4.3 One port and one token per project
|
|
304
|
+
|
|
305
|
+
`~/.baseinstrunner/installed.json` keeps a record per project directory:
|
|
306
|
+
|
|
307
|
+
```
|
|
308
|
+
projects[<normalised cwd>] = { port, token, replay, invocation, … }
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
The rest is bookkeeping for `bir uninstall`: whether the exclude line was
|
|
312
|
+
added by the install, and every `.claude.json` the install wrote to
|
|
313
|
+
(`CLAUDE_CONFIG_DIR` moves it, §4.4).
|
|
314
|
+
|
|
315
|
+
**Port.** The first project on a machine gets 53411; the next gets the lowest
|
|
316
|
+
port above it that no other project holds *and* that is free right now — it is
|
|
317
|
+
bound once to check, because 53411 sits inside the ranges Hyper-V, WSL and
|
|
318
|
+
Docker reserve on Windows, and a recorder told to use an excluded port exits.
|
|
319
|
+
Once recorded the port is kept; the hooks name it.
|
|
320
|
+
`bir setup --port <n>` (or `bir install --port <n>`) overrides. A *background*
|
|
321
|
+
recorder — the one the hook, `bir up` or `bir setup` starts — for a project with
|
|
322
|
+
a recorded port (or with `BIR_CONTROL_PORT` set) does not fall back to an
|
|
323
|
+
ephemeral one, rather than let the project's hooks post to whatever else is
|
|
324
|
+
listening there. It finds out who holds the port — the system's reservation,
|
|
325
|
+
another project's recorder, another program — and exits 1 with
|
|
326
|
+
`control.port_busy` on its log; its `fix` reads "run `bir setup --port <another
|
|
327
|
+
port>` in <project> (below 49152 avoids the ranges the system reserves); the
|
|
328
|
+
next Claude Code session then uses it". It also writes
|
|
329
|
+
`~/.baseinstrunner/control/<key>.port.json`, which `bir doctor` reports as a
|
|
330
|
+
problem ("the recorder could not take port N: …") and `bir status` as a `Port`
|
|
331
|
+
line, and the hook adds one stderr line. When the holder is this project's own
|
|
332
|
+
recorder, the new process steps aside and exits 0 (`control.already_running`).
|
|
333
|
+
`bir setup` without `--port` repairs this itself: a recorded port this machine
|
|
334
|
+
no longer lets it bind — reservations move across reboots — is replaced by the
|
|
335
|
+
lowest free one ("port N is not available on this machine; this project moves
|
|
336
|
+
to M"), and the hooks and entries are rewritten with it. `bir-hooks` run by hand
|
|
337
|
+
in a terminal still falls back to an ephemeral port. Two projects used to share
|
|
338
|
+
53411 and one project's steps landed in the other's run; this is the fix.
|
|
339
|
+
|
|
340
|
+
**A port change with a window open.** Claude Code reads a session's hooks when
|
|
341
|
+
the session starts, so a window opened before the port moved still posts to the
|
|
342
|
+
old one. That recorder keeps serving it, as a *superseded* recorder — `bir
|
|
343
|
+
status` lists it, it logs to `logs/<key>.superseded-<port>.log` — while a second
|
|
344
|
+
one serves the new port. It exits by itself once it has had nothing to serve for
|
|
345
|
+
a day (`BIR_SUPERSEDED_EXIT_MS`); `bir down` stops both.
|
|
346
|
+
|
|
347
|
+
**Invocation.** `global`, `local` or `npx` — what the entries were generated
|
|
348
|
+
with, so a later flag-less `bir install` keeps the same shape (§4).
|
|
349
|
+
|
|
350
|
+
**Token.** Generated once per project, so a token in one project's settings
|
|
351
|
+
file opens that project's recorder and no other. A record written by 0.2.8 has
|
|
352
|
+
none and falls back to the machine-wide `token` in the same file. A running
|
|
353
|
+
recorder re-reads the project's current token from this file (at most every
|
|
354
|
+
3 s), so a reinstall that changes it needs no restart.
|
|
355
|
+
|
|
356
|
+
**Replay.** The switches `bir replay on|off|allow …` and
|
|
357
|
+
`bir setup --allow-servers` store, read by the recorder at start (§5.1).
|
|
358
|
+
`bir setup --no-replay` and `bir uninstall --replay` store replay off, and so
|
|
359
|
+
does a `bir install` without `--replay` where no scenario server is installed;
|
|
360
|
+
an install with `--replay` turns such an off back on, but never a
|
|
361
|
+
`bir replay off` of your own.
|
|
362
|
+
|
|
363
|
+
### 4.4 Wrapped entries go to the local scope
|
|
364
|
+
|
|
365
|
+
`bir install` does not rewrite `.mcp.json` or the user scope. It writes a
|
|
366
|
+
wrapped copy of each server, under the same key, into this project's *local
|
|
367
|
+
scope*: `projects[<key>].mcpServers` in `~/.claude.json` (in
|
|
368
|
+
`$CLAUDE_CONFIG_DIR/.claude.json` when that is set, as Claude Code does), under
|
|
369
|
+
the key Claude Code itself uses — the canonical git root, forward slashes on
|
|
370
|
+
Windows. The local scope outranks the project and user scopes, so the copy is
|
|
371
|
+
what Claude Code starts here, other projects are untouched, and the team's
|
|
372
|
+
`.mcp.json` never carries this machine's paths.
|
|
373
|
+
|
|
374
|
+
What is copied, and what is left as Claude Code runs it (its calls are still
|
|
375
|
+
recorded by the hooks):
|
|
376
|
+
|
|
377
|
+
- **A `.mcp.json` server only once Claude Code has approved it** — "Use this MCP
|
|
378
|
+
server", `enabledMcpjsonServers` or `enableAllProjectMcpServers` in a settings
|
|
379
|
+
file Claude Code honours for that. Claude Code asks before it starts a
|
|
380
|
+
`.mcp.json` server and never asks about a local-scope one, so a copy would
|
|
381
|
+
skip the question: a rejected server is never copied, and one never asked
|
|
382
|
+
about (a fresh clone) is left unwrapped with a line saying so. On a fresh
|
|
383
|
+
clone that means `bir setup`, then `claude` and approve the servers, then
|
|
384
|
+
`bir setup` again. `bir install --server <name>` wraps a pending one anyway,
|
|
385
|
+
and later installs remember it. A fleet that runs `claude -p`, where nobody
|
|
386
|
+
answers the question, sets `enableAllProjectMcpServers` in user or managed
|
|
387
|
+
settings.
|
|
388
|
+
- **A remote (`http`/`sse`) server only when its entry carries a literal
|
|
389
|
+
credential header.** One Claude Code signs in itself — OAuth, a
|
|
390
|
+
`headersHelper`, `${VAR}` headers — would reach the proxy with no credentials
|
|
391
|
+
and answer 401, so it is left unwrapped; `--server <name>` wraps it anyway. A
|
|
392
|
+
wrapped remote entry carries its headers in its env (`BIR_UPSTREAM_HEADERS`),
|
|
393
|
+
not on the command line.
|
|
394
|
+
- **Nothing** when managed settings set `allowManagedMcpServersOnly`, or match
|
|
395
|
+
servers by `serverCommand` or `serverUrl` — wrapping changes the command.
|
|
396
|
+
- **Nothing in a subdirectory or a linked worktree** of a repository: Claude Code
|
|
397
|
+
keeps the local scope per repository, so a copy would apply to the root and
|
|
398
|
+
every worktree. Hooks only; `bir setup` in the root wraps the servers. From the
|
|
399
|
+
root, the install names the linked worktrees that now run the wrapped servers
|
|
400
|
+
too, and leaves a server alone where a worktree's own `.mcp.json` defines it
|
|
401
|
+
differently.
|
|
402
|
+
|
|
403
|
+
A copy remembers what it copied. When the `.mcp.json` or user-scope entry
|
|
404
|
+
changes, or its approval is withdrawn, `bir doctor` reports the copy (a changed
|
|
405
|
+
or removed source exits 1), `bir status` marks it `STALE`, the SessionStart hook
|
|
406
|
+
names it in the recorder log (`overrides.stale`), and `bir setup` copies it
|
|
407
|
+
again or removes it. `bir install` refuses, changing nothing, when
|
|
408
|
+
`~/.claude.json`, `.mcp.json`, `settings.local.json` or a `--config` file is not
|
|
409
|
+
plain JSON (comments, a trailing comma). `bir uninstall` repairs
|
|
410
|
+
`~/.claude.json` entry by entry; it never restores that file whole.
|
|
411
|
+
|
|
412
|
+
`--mcp-json` (or `BIR_WRAP_MCP_JSON=1`) keeps the old shape — `.mcp.json`
|
|
413
|
+
servers wrapped in place, the scenario server added there and pre-approved in
|
|
414
|
+
`settings.local.json` — for a `.mcp.json` nobody commits. An install over an
|
|
415
|
+
older one that used that shape puts `.mcp.json` back as it was, byte for byte
|
|
416
|
+
when nothing else changed it, and moves the entries to the local scope.
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
## 5. Provisioning a runner
|
|
421
|
+
|
|
422
|
+
Four scopes, and conflating them is the usual source of "installed but never
|
|
423
|
+
runs":
|
|
424
|
+
|
|
425
|
+
| Scope | How often | What it does |
|
|
426
|
+
|---|---|---|
|
|
427
|
+
| **machine** | once | Node ≥ 20 and the package. The served line uses the machine's Node when it qualifies and otherwise puts a private one under `~/.baseinstrunner/node/`; the package goes under `~/.baseinstrunner/npm/`, and `bir` / `bir-hooks` launchers in `~/.baseinstrunner/bin/`, first on the user PATH |
|
|
428
|
+
| **account** | once per user | the sign-in → `~/.baseinstrunner/credentials.json` (0600); the service address → `~/.baseinstrunner/config.json`. `bir setup` stores both; so does `bir login` |
|
|
429
|
+
| **project** | once per repo | `bir setup` in that directory: `bir install --global --replay` (`--local` from a checkout) writes wrapped copies of the MCP servers into the project's local scope (§4.4), wires the hooks into `.claude/settings.local.json`, records the port, token and invocation |
|
|
430
|
+
| **session** | while recording | the recorder — `bir-hooks` as a background process — started by the SessionStart hook, discovered by `sha256(normalised cwd)` |
|
|
431
|
+
|
|
432
|
+
That cwd keying is why the recorder must be started for the *same directory*
|
|
433
|
+
the agent session runs in. A control server started for another one is
|
|
434
|
+
invisible, and the run silently degrades to Tier 2. It is also why the hook is
|
|
435
|
+
what starts it: the hook already runs in the right directory, with the right
|
|
436
|
+
environment, at exactly the right moment. (The background process itself runs
|
|
437
|
+
in `~/.baseinstrunner` and is told the project in `BIR_DAEMON_CWD`, so the
|
|
438
|
+
project folder can be renamed, deleted or `git worktree remove`d while it
|
|
439
|
+
runs.)
|
|
440
|
+
|
|
441
|
+
### 5.1 One command
|
|
442
|
+
|
|
443
|
+
The console's *Set up the runner* page shows one line, with a fresh one-time
|
|
444
|
+
setup token filled in, to paste into a terminal opened in the project folder:
|
|
445
|
+
|
|
446
|
+
```powershell
|
|
447
|
+
$env:BIR_SETUP_TOKEN="<token>"; irm https://api.bi2202.com/install.ps1 | iex
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
```cmd
|
|
451
|
+
powershell -NoProfile -Command "$env:BIR_SETUP_TOKEN='<token>'; irm https://api.bi2202.com/install.ps1 | iex"
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
```bash
|
|
455
|
+
curl -fsSL https://api.bi2202.com/install.sh | BIR_SETUP_TOKEN="<token>" sh
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
The first is Windows PowerShell, the second the same line from cmd.exe (a
|
|
459
|
+
terminal opened from Start is often cmd), the third macOS and Linux. Without the
|
|
460
|
+
variable the line still installs everything; `bir setup` then opens the browser
|
|
461
|
+
for the device flow — even under the scripts, where its output is a pipe — and
|
|
462
|
+
over SSH prints the link instead (§7).
|
|
463
|
+
|
|
464
|
+
The scripts are [`scripts/install.ps1`](../scripts/install.ps1) and
|
|
465
|
+
[`scripts/install.sh`](../scripts/install.sh); they replace
|
|
466
|
+
`install-runner.ps1` and `install-runner.sh`, and they ship in the tarball. The
|
|
467
|
+
API serves copies at `/install.ps1` and `/install.sh` with two placeholders
|
|
468
|
+
filled in: `__BIR_AUTH_URL__` (the service's `PUBLIC_API_URL`, else the origin
|
|
469
|
+
the request arrived on) and `__BIR_VERSION__` (`RUNNER_VERSION`, default
|
|
470
|
+
`latest`). Run straight from the repository or the tarball the placeholders are
|
|
471
|
+
unreplaced, which counts as unset: `BIR_AUTH_URL` must then be in the
|
|
472
|
+
environment, and the version is `latest`.
|
|
473
|
+
|
|
474
|
+
What they do, in order, the same on every platform:
|
|
475
|
+
|
|
476
|
+
0. **Stop before any download** if the current directory is the home folder or
|
|
477
|
+
a drive root: "This is your home folder. Open a terminal in the project you
|
|
478
|
+
start Claude Code in (cd there) and paste the line again." Nothing is
|
|
479
|
+
installed, nobody is signed in and the token is unused, so the fix is
|
|
480
|
+
exactly that — `cd` to the project and paste the *same* line
|
|
481
|
+
(`BIR_PROJECT=<dir>` names the project instead; the script `cd`s there
|
|
482
|
+
first). Move `BIR_SETUP_TOKEN` out of the environment into a local variable,
|
|
483
|
+
so npm, the Claude Code installer and the `claude` the person starts next
|
|
484
|
+
never see it.
|
|
485
|
+
1. **Node.** A Node ≥ 20 already on PATH is used by its real binary — unless
|
|
486
|
+
that binary is one the hook must not pin: under a version manager's
|
|
487
|
+
per-shell or per-version directory (nvm, fnm, Volta, asdf, mise and nodenv
|
|
488
|
+
on every platform; Scoop on Windows) or, on macOS and Linux, a Homebrew Node
|
|
489
|
+
(`*/Cellar/*` — a `brew upgrade` moves the path the hook pinned) or a snap
|
|
490
|
+
one. Both scripts also insist on npm's `npm-cli.js` beside the binary
|
|
491
|
+
(`lib/node_modules/npm/bin/` on macOS and Linux, `node_modules\npm\bin\` on
|
|
492
|
+
Windows). Otherwise a private Node is installed instead: pinned version,
|
|
493
|
+
digest embedded in the script and checked, unpacked to
|
|
494
|
+
`~/.baseinstrunner/node/v<V>/`, never put on PATH. The SessionStart hook
|
|
495
|
+
pins an absolute node path, and a path that changes with the shell is a
|
|
496
|
+
hook that fails every session. `BIR_NODE_ARCHIVE=<file>` uses a downloaded
|
|
497
|
+
archive instead of nodejs.org.
|
|
498
|
+
2. **The package.** `@basein/runner@<version>` into the private prefix
|
|
499
|
+
`~/.baseinstrunner/npm`, always — never the machine's npm prefix, which is
|
|
500
|
+
root-owned under a `.pkg` or apt Node and per-version under nvm, Volta, Scoop
|
|
501
|
+
or asdf, where a global install vanishes on the next switch.
|
|
502
|
+
`BIR_TARBALL=<file.tgz>` instead of the registry. Launchers go into
|
|
503
|
+
`~/.baseinstrunner/bin`, which is put first on the user PATH; in PowerShell
|
|
504
|
+
`bir` then resolves to `bir.cmd`, so there is no execution-policy change,
|
|
505
|
+
ever. The PowerShell window the line was pasted into gets `bir` in place —
|
|
506
|
+
the script's last two lines are "bir works in every new terminal (and in
|
|
507
|
+
this window when it is PowerShell); `bir doctor` checks the setup" and the
|
|
508
|
+
cmd hint below — but the cmd variant
|
|
509
|
+
runs the script in a child `powershell.exe`, so a cmd window sees `bir` only
|
|
510
|
+
once it is a new window, or after the `set PATH=…` line the script prints
|
|
511
|
+
for it. On macOS and Linux the script prints the
|
|
512
|
+
`export PATH="$HOME/.baseinstrunner/bin:$PATH"` line for the terminal it ran
|
|
513
|
+
in (with `~/.local/bin` added when it just installed Claude Code); every new
|
|
514
|
+
terminal has it from the profile.
|
|
515
|
+
3. **Claude Code**, through its official installer, when `claude` is not on
|
|
516
|
+
PATH. `BIR_NO_CLAUDE=1` skips it. It needs a Claude subscription and asks to
|
|
517
|
+
sign in the first time it starts.
|
|
518
|
+
4. **`bir setup --auth-url <url> --project <cwd>`**, with the token in that
|
|
519
|
+
child's environment only. It signs in, stores the service address, runs
|
|
520
|
+
`bir install --global --replay`, starts the recorder in the background, and
|
|
521
|
+
prints a summary that names the account, the wrapped servers (`(no MCP
|
|
522
|
+
servers here)` means none in *any* scope — `.mcp.json`, and the user and
|
|
523
|
+
local scopes of `~/.claude.json`; `(none — x, y left as they are, see
|
|
524
|
+
above)` means servers exist but none could be wrapped, for the reasons of
|
|
525
|
+
§4.4; replay is "steer mode only" in both cases), the recorder's pid and log
|
|
526
|
+
file, and the one thing left to do: `claude`.
|
|
527
|
+
|
|
528
|
+
`BIR_UNINSTALL=1` on the same line runs the other direction and nothing else:
|
|
529
|
+
`bir uninstall --all` with the runner already installed (§8). It downloads
|
|
530
|
+
nothing and needs no project folder and no token; `BIR_DRY_RUN=1` prints it.
|
|
531
|
+
|
|
532
|
+
Both scripts are idempotent and neither prompts (under `curl | sh` stdin is the
|
|
533
|
+
script itself). `bir setup` exits 1 when the recorder reports it has no session
|
|
534
|
+
to record with, so a provisioning script can gate on the exit code.
|
|
535
|
+
|
|
536
|
+
Behind a corporate proxy:
|
|
537
|
+
|
|
538
|
+
- The scripts' own downloads and npm use `HTTPS_PROXY`, `HTTP_PROXY` and
|
|
539
|
+
`NO_PROXY` as they are. The one fetch they cannot reach is the `irm` that
|
|
540
|
+
fetches the script itself: Windows PowerShell 5.1's `irm` uses the system
|
|
541
|
+
(WinINET) proxy, not `HTTPS_PROXY`.
|
|
542
|
+
- The runner's own calls to the service — the sign-in, the recorder's uploads —
|
|
543
|
+
go through Node's `fetch`, which ignores those variables unless node runs with
|
|
544
|
+
`--use-env-proxy`. The scripts add that flag to the launchers and to
|
|
545
|
+
`bir setup`, which carries it into every entry, hook and recorder it writes,
|
|
546
|
+
only when the Node accepts it (the node is asked, not judged by its version;
|
|
547
|
+
recent 22.x and 24.x have it) **and** `NO_PROXY` exempts `127.0.0.1` — as
|
|
548
|
+
`127.0.0.1`, `*` or a suffix such as `.0.0.1`; `localhost` alone is not
|
|
549
|
+
enough. Without that exemption the flag would send the runner's own loopback
|
|
550
|
+
calls — the SessionStart hook reaching the recorder, proxies registering,
|
|
551
|
+
`bir setup` waiting for it — to the proxy, so it is
|
|
552
|
+
left out, the runner reaches the service directly or not at all, and the
|
|
553
|
+
script says so. Set `NO_PROXY=127.0.0.1,localhost,::1` (plus your own
|
|
554
|
+
entries) before the line; `bir install` prints the same hint.
|
|
555
|
+
- A remote (`http`/`sse`) MCP server behind `bir-proxy` is reached through
|
|
556
|
+
`HTTPS_PROXY`/`NO_PROXY` by Node's `http.Agent` `proxyEnv`, flag or not, on
|
|
557
|
+
Node 24.5+ and 22.21+. An older Node connects directly and logs
|
|
558
|
+
`upstream.proxy_env_ignored`; loopback upstreams always connect directly, and
|
|
559
|
+
`BIR_UPSTREAM_PROXY_ENV=0` turns this off.
|
|
560
|
+
- `NODE_EXTRA_CA_CERTS` passes through to every node and npm the scripts run. A
|
|
561
|
+
Node that knows `--use-system-ca` also trusts the system certificate store,
|
|
562
|
+
and the launchers carry that flag too.
|
|
563
|
+
|
|
564
|
+
The scripts never reach `bir setup` from the home folder (step 0). `bir setup`
|
|
565
|
+
run *by hand* there, with the runner already installed, is different: it keeps
|
|
566
|
+
the sign-in, wires nothing, prints the two commands left — `cd <project>` and
|
|
567
|
+
`bir setup` — and exits 0. The second needs no token; the session is cached.
|
|
568
|
+
That is also how a second project on the same machine is added.
|
|
569
|
+
|
|
570
|
+
Fleet switches on `bir setup`: `--allow-servers a,b` stores the servers replay
|
|
571
|
+
may call unattended for this project; `bir replay allow|on|off|status` changes
|
|
572
|
+
it later. Both live in the project record (§4.3), so a recorder the hook starts
|
|
573
|
+
— with Claude Code's environment, not the operator's terminal — keeps them;
|
|
574
|
+
`BIR_REPLAY`, `BIR_REPLAY_ALLOW_SERVERS` and `BIR_MIN_STEER_SIMILARITY` in the
|
|
575
|
+
recorder's environment still override, and `bir doctor` says which source is in
|
|
576
|
+
effect. `--no-replay` adds no scenario server (one an earlier setup added stays
|
|
577
|
+
until `bir uninstall --replay`) and stores replay off (`bir replay on` turns it
|
|
578
|
+
on); `--no-daemon` wires everything and starts
|
|
579
|
+
nothing; `--no-browser` prints the sign-in link without opening one.
|
|
580
|
+
|
|
581
|
+
**Permissions on a fleet.** The runner does not approve wrapped MCP tool calls:
|
|
582
|
+
Claude Code asks about them as it would without the runner. (0.2.12 answered
|
|
583
|
+
every one "allow"; §8.) Headless `claude -p` runs must allow those tools
|
|
584
|
+
themselves — `permissions.allow` in a trusted workspace, or `--allowedTools` —
|
|
585
|
+
or set `BIR_CORRELATION_DECISION=allow` in the environment the recorder starts
|
|
586
|
+
in — Claude Code's when the SessionStart hook starts it, the shell's for
|
|
587
|
+
`bir up --restart` — which brings the old auto-approval back. A recorder already
|
|
588
|
+
running keeps what it started with. `bir doctor` says which applies. A
|
|
589
|
+
matched calculated scenario's own steps are still approved by the runner
|
|
590
|
+
([calculatedReplayGuide.md §5.1](calculatedReplayGuide.md)).
|
|
591
|
+
|
|
592
|
+
Two things the scripts deliberately do not do: supervise the recorder (the hook
|
|
593
|
+
does, §5.2) and mint the token (the console does, §7).
|
|
594
|
+
|
|
595
|
+
The PowerShell script is **pure ASCII** on purpose. Windows PowerShell 5.1
|
|
596
|
+
decodes a BOM-less `.ps1` using the machine's ANSI codepage, and PowerShell
|
|
597
|
+
accepts curly quotes as string delimiters — so a UTF-8 em-dash in a comment can
|
|
598
|
+
decode into a quote and swallow the rest of the file. It parsed fine on one
|
|
599
|
+
machine and failed on another before this was fixed. Keep it ASCII. It also
|
|
600
|
+
never calls `exit` — under `iex` that closes the person's window — and it sets
|
|
601
|
+
strict mode and `$ErrorActionPreference` inside a function so neither leaks into
|
|
602
|
+
the caller's session.
|
|
603
|
+
|
|
604
|
+
### 5.2 Keeping the recorder alive
|
|
605
|
+
|
|
606
|
+
Not needed. The SessionStart hook is `bir-hooks ensure` (§4.2): every Claude
|
|
607
|
+
Code session in the project starts the recorder when there is none, in the
|
|
608
|
+
background, so a recorder that died or was never started is running by the first
|
|
609
|
+
prompt. Nothing has to stay open. `bir up` starts it by hand, `bir up --restart`
|
|
610
|
+
replaces it, `bir down` stops it — it asks first (`POST /control/stop`, which
|
|
611
|
+
finishes the run being written), signals only a pid that answered `/health` with
|
|
612
|
+
the token, and says "forced" when it had to — and `bir status` and `bir doctor`
|
|
613
|
+
show its pid and log. Two Claude Code windows opened together take a lock, so
|
|
614
|
+
only one spawns.
|
|
615
|
+
|
|
616
|
+
`bir down` stops the recorder and nothing else: the hooks stay installed, so a
|
|
617
|
+
Claude Code session open in the project shows a "hook error" on each prompt and
|
|
618
|
+
tool call (the session keeps working) until it ends, and the next session starts
|
|
619
|
+
the recorder again. It also stops superseded recorders (§4.3), and it stays down
|
|
620
|
+
against the automatic revival below until the next session start, `bir up` or
|
|
621
|
+
`bir setup`. To take the runner out of the way without hook errors, use the
|
|
622
|
+
kill switch instead: `bir off --here` for one project, `bir off` for every
|
|
623
|
+
project (`bir on` undoes it). Hooks then answer with nothing within about a
|
|
624
|
+
second, in open sessions too; wrapped servers run as plain pipes from their next
|
|
625
|
+
start; nothing is uninstalled.
|
|
626
|
+
|
|
627
|
+
On POSIX the daemon is a detached spawn with its stdio ignored. On Windows it is
|
|
628
|
+
launched through `Start-Process -WindowStyle Hidden` in a short-lived
|
|
629
|
+
`powershell.exe`, because a node started under a PowerShell layer carries an
|
|
630
|
+
extra copy of its stdout pipe and a daemon that inherited it would hold that pipe
|
|
631
|
+
open for days. That costs about a second, once per project per boot.
|
|
632
|
+
`BIR_WINDOWS_LAUNCH=spawn` opts into a direct hidden spawn instead — faster, and
|
|
633
|
+
unsafe only when something above Claude Code captures its output
|
|
634
|
+
(`$r = claude -p …`).
|
|
635
|
+
|
|
636
|
+
Its audit lines go to `~/.baseinstrunner/logs/<project-key>.log`: the daemon
|
|
637
|
+
opens the file itself, appends, and rolls it to `.1` once it is past 8 MB — at
|
|
638
|
+
start and while it runs. `bir status` names the file. The old shape still works —
|
|
639
|
+
`bir-hooks` in a terminal writes them to that terminal — and is what the hook
|
|
640
|
+
does for you.
|
|
641
|
+
|
|
642
|
+
```bash
|
|
643
|
+
grep version.skew ~/.baseinstrunner/logs/*.log
|
|
644
|
+
grep run.lossy ~/.baseinstrunner/logs/*.log
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
Restart-on-crash mid-session is the session's own part: a bound `bir-proxy`, or
|
|
648
|
+
the `bir` scenario server, that finds the project's recorder gone — its pid dead
|
|
649
|
+
and its port refusing — starts it again with `bir-hooks revive`
|
|
650
|
+
(`proxy.recorder_revive` / `scenario.recorder_revive` on stderr), rate-limited,
|
|
651
|
+
and never after `bir down`. A session with neither is back on the next session
|
|
652
|
+
start or `/clear`. A service manager is
|
|
653
|
+
optional. If you want one — a machine that records SDK sessions with nobody
|
|
654
|
+
starting Claude Code, or a recorder up before the first session — have it run
|
|
655
|
+
`bir up` in the project directory at logon, and nothing else: `bir up` returns
|
|
656
|
+
once the recorder answers and leaves it running, so a supervisor must not expect
|
|
657
|
+
a long-lived process. The three recipes below are recipes — nothing the package
|
|
658
|
+
ships or its tests cover.
|
|
659
|
+
|
|
660
|
+
**Windows** — a Scheduled Task at logon, with *Start in* set to the project
|
|
661
|
+
directory (`bir.cmd` is the launcher the served line writes; a by-hand install
|
|
662
|
+
has `bir` wherever npm put it):
|
|
663
|
+
|
|
664
|
+
```powershell
|
|
665
|
+
$action = New-ScheduledTaskAction -Execute "$env:USERPROFILE\.baseinstrunner\bin\bir.cmd" -Argument 'up' -WorkingDirectory 'C:\work\repo'
|
|
666
|
+
$trigger = New-ScheduledTaskTrigger -AtLogOn
|
|
667
|
+
Register-ScheduledTask -TaskName 'bir up (repo)' -Action $action -Trigger $trigger
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
**macOS** — a launchd agent at `~/Library/LaunchAgents/com.basein.up.plist`
|
|
671
|
+
running `$HOME/.baseinstrunner/bin/bir up` with `WorkingDirectory` set to the
|
|
672
|
+
project and `RunAtLoad` (not `KeepAlive`), then `launchctl load` it.
|
|
673
|
+
|
|
674
|
+
**Linux** — a systemd user unit, `Type=oneshot` with `RemainAfterExit=yes` (so
|
|
675
|
+
the unit does not stop and take the recorder down with it), `ExecStart=%h/.baseinstrunner/bin/bir up`,
|
|
676
|
+
`WorkingDirectory=` the project, plus `loginctl enable-linger "$USER"` so it
|
|
677
|
+
runs without a login session.
|
|
678
|
+
|
|
679
|
+
### 5.3 Verify
|
|
680
|
+
|
|
681
|
+
```bash
|
|
682
|
+
bir doctor --json # exits 1 if something here breaks a Claude Code session
|
|
683
|
+
bir doctor --json --strict # exits 1 on warnings too: not recording, a proxy not registered, …
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
`bir doctor` asks the control server which proxies actually registered; it never
|
|
687
|
+
reads the config file back. That makes it the right gate for a provisioning
|
|
688
|
+
script. `bir status` only describes intent. It sorts what it finds three ways.
|
|
689
|
+
*Problems* break a session and are the only thing that exits 1: a wrapped
|
|
690
|
+
server, the scenario server or the SessionStart hook naming a Node or script
|
|
691
|
+
that is gone (§4.2), a local copy out of date with its source (§4.4), a remote
|
|
692
|
+
server wrapped without credentials, hooks posting to a port the recorder is not
|
|
693
|
+
on or could not take (§4.3), hooks with nothing to start the recorder, a
|
|
694
|
+
recorder that does not answer. *Warnings* exit 0 unless `--strict`: the
|
|
695
|
+
recorder has nowhere to record to ("the recorder is running but has nowhere to
|
|
696
|
+
send steps — run `bir setup` …"), a proxy has not registered while sessions
|
|
697
|
+
run, the service is not answering the recorder, a recorder of another version.
|
|
698
|
+
*Notes* are facts, such as "the recorder is not running; the SessionStart hook
|
|
699
|
+
starts it with the next session (`bir up` starts it now)", who approves wrapped
|
|
700
|
+
MCP calls, and the other projects set up on the machine. `--quick` skips the
|
|
701
|
+
network call; `--json` carries `problems`, `warnings`, `notes`, `off`,
|
|
702
|
+
`service`, `otherProjects` and `portProblem`.
|
|
703
|
+
|
|
704
|
+
---
|
|
705
|
+
|
|
706
|
+
## 6. Machines with no registry
|
|
707
|
+
|
|
708
|
+
Zero dependencies makes offline install trivial — no `npm ci --offline`, no
|
|
709
|
+
vendored `node_modules`. The install script ships in the tarball, takes a local
|
|
710
|
+
tarball instead of the registry and a local Node archive instead of nodejs.org,
|
|
711
|
+
and can skip the Claude Code installer:
|
|
712
|
+
|
|
713
|
+
```bash
|
|
714
|
+
# on a connected machine
|
|
715
|
+
npm pack @basein/runner@<version> # or download the Release asset
|
|
716
|
+
tar -xzf basein-runner-<version>.tgz package/scripts
|
|
717
|
+
# plus the Node archive for the runner's platform from nodejs.org/dist, if the
|
|
718
|
+
# runner has no Node ≥ 20 (the script names the pinned version, 24.21.0)
|
|
719
|
+
|
|
720
|
+
# on the runner
|
|
721
|
+
BIR_AUTH_URL=https://api.example.com \
|
|
722
|
+
BIR_TARBALL=./basein-runner-<version>.tgz \
|
|
723
|
+
BIR_NODE_ARCHIVE=./node-v24.21.0-linux-x64.tar.gz \
|
|
724
|
+
BIR_NO_CLAUDE=1 sh ./package/scripts/install.sh
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
```powershell
|
|
728
|
+
$env:BIR_AUTH_URL='https://api.example.com'; $env:BIR_TARBALL='.\basein-runner-<version>.tgz'; $env:BIR_NODE_ARCHIVE='.\node-v24.21.0-win-x64.zip'; $env:BIR_NO_CLAUDE=1; Get-Content -Raw .\package\scripts\install.ps1 | iex
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
`<version>` is whatever the registry holds (`npm view @basein/runner version`).
|
|
732
|
+
The PowerShell line pipes the file through `iex`, as the served line does: a
|
|
733
|
+
`.ps1` invoked by path is subject to the execution policy, and a default client
|
|
734
|
+
(Restricted) refuses it with "running scripts is disabled". The script is
|
|
735
|
+
written to be run that way — everything inside one function, no `exit`.
|
|
736
|
+
|
|
737
|
+
`BIR_AUTH_URL` is required here because the raw script's placeholder is
|
|
738
|
+
unreplaced (§5.1). Claude Code has to come from somewhere too; `BIR_NO_CLAUDE=1`
|
|
739
|
+
only stops the script from fetching it. The service itself must be reachable —
|
|
740
|
+
"no registry" is not "no network" — or the sign-in has nothing to talk to.
|
|
741
|
+
|
|
742
|
+
`bir setup` passes `--global`, so the resulting configs contain absolute paths
|
|
743
|
+
and never reach for a registry. Nothing further is needed.
|
|
744
|
+
|
|
745
|
+
Note that a private repository's Release assets require a GitHub token to fetch,
|
|
746
|
+
so the tarball has to be carried to the runner by whatever means you already use.
|
|
747
|
+
|
|
748
|
+
---
|
|
749
|
+
|
|
750
|
+
## 7. Headless authentication
|
|
751
|
+
|
|
752
|
+
`bir login` signs in through the browser — the OAuth 2.0 device grant (RFC 8628,
|
|
753
|
+
[loginWeb.md](loginWeb.md)) — and caches
|
|
754
|
+
`{accessToken, refreshToken, accessExpiresAt, user, authUrl}` at
|
|
755
|
+
`~/.baseinstrunner/credentials.json`, mode 0600. `bir setup` does the same
|
|
756
|
+
sign-in and stores the service address beside it in
|
|
757
|
+
`~/.baseinstrunner/config.json`, so no process afterwards needs `BIR_AUTH_URL`
|
|
758
|
+
(it still overrides when set).
|
|
759
|
+
|
|
760
|
+
**The setup token.** The served line carries `BIR_SETUP_TOKEN`: a one-time token
|
|
761
|
+
minted by the console's *Set up the runner* page — ten-minute TTL, single use,
|
|
762
|
+
hashed at rest, and minting a new one retires the person's earlier unused ones.
|
|
763
|
+
It travels as an environment-variable assignment rather than an argument so that
|
|
764
|
+
it is never on a command line, where a process listing would show it; the
|
|
765
|
+
variable's *name* is what keeps the pasted line out of PSReadLine's on-disk
|
|
766
|
+
history, which drops lines containing "token" (a `--token` argument would be
|
|
767
|
+
dropped just the same). The script moves it out of the environment as its first
|
|
768
|
+
statement and hands it to `bir setup` through that child's environment only;
|
|
769
|
+
`bir setup` deletes it again the moment it has read it, so the recorder it
|
|
770
|
+
starts never holds it. By hand: `bir setup --token <t>` or
|
|
771
|
+
`bir login --token <t>`. It signs in one person on one machine with no browser
|
|
772
|
+
step; it is not a fleet credential.
|
|
773
|
+
|
|
774
|
+
What `bir setup` says about a token that is not fresh:
|
|
775
|
+
|
|
776
|
+
- pasted a second time after a successful paste — "the setup token was already
|
|
777
|
+
used; keeping the sign-in from before (<email>)", and it goes on; that is the
|
|
778
|
+
upgrade path (§8) and the retry after a failure past the sign-in;
|
|
779
|
+
- used or expired, with no cached sign-in to keep — "this setup token has
|
|
780
|
+
expired or was already used — make a new one on the console's Set up the
|
|
781
|
+
runner page and paste it within ten minutes";
|
|
782
|
+
- not a token at all — "that is not a valid setup token — copy the whole token
|
|
783
|
+
from the console's Set up the runner page".
|
|
784
|
+
|
|
785
|
+
"sign-in failed: …" is reserved for the unexpected, and "not signed in — run
|
|
786
|
+
`bir setup` again (or `bir login`)." for a sign-in that ended with no session.
|
|
787
|
+
|
|
788
|
+
**Credentials are bound to the service that issued them.** `credentials.json`
|
|
789
|
+
carries `authUrl`, and `authenticate()` refuses to use or refresh a session
|
|
790
|
+
against another origin — `auth.url_mismatch` on the audit log, and the runner
|
|
791
|
+
records nothing rather than send a session where it was not minted. A file from
|
|
792
|
+
before the binding is trusted as before. So a copied `credentials.json` works
|
|
793
|
+
only where `config.json` (or `BIR_AUTH_URL`) names the same service, and a stale
|
|
794
|
+
`BIR_AUTH_URL` in a profile file now means "not recording" — `bir setup` warns
|
|
795
|
+
when the environment names a different address than the one being set up, or
|
|
796
|
+
than the stored one (a bare `bir setup` in a second project included), and says
|
|
797
|
+
to remove the variable.
|
|
798
|
+
|
|
799
|
+
**For a machine with a person attached, headless is no longer a problem.** The
|
|
800
|
+
runner prints a link and a short code and waits; the link opens on *any* device,
|
|
801
|
+
so a box with no browser, no display, or only an SSH session is signed in from a
|
|
802
|
+
phone. Nothing is typed into the terminal, so nothing lands in a shell history or
|
|
803
|
+
a CI transcript. `bir login --no-browser` (or `bir setup --no-browser`) skips
|
|
804
|
+
even trying to open one, which is the right flag in a provisioning script (over
|
|
805
|
+
SSH it is the default; a pipe on stdout is not — under the install scripts
|
|
806
|
+
`bir setup` still opens the browser, because somebody just pasted the line). The
|
|
807
|
+
setup token removes even the waiting.
|
|
808
|
+
|
|
809
|
+
For machines with **no person attached**, in order of preference:
|
|
810
|
+
|
|
811
|
+
1. **A service account, signed in once.** Approve the device code for a dedicated
|
|
812
|
+
account, then copy `credentials.json` to each machine at 0600, together with
|
|
813
|
+
a `config.json` naming the same service. The rotating refresh token keeps it
|
|
814
|
+
alive and nothing ever prompts. It is still a shared long-lived secret, so
|
|
815
|
+
never a person's account.
|
|
816
|
+
2. **No service address at all** for runners that should record nothing: no
|
|
817
|
+
address in `~/.baseinstrunner/config.json` and no `BIR_AUTH_URL`. The
|
|
818
|
+
recorder then starts with a null recorder (`recorder.disabled` on its log)
|
|
819
|
+
and everything else still works; `bir up` exits 1 to say so, and
|
|
820
|
+
`bir doctor` warns (exit 1 only with `--strict`).
|
|
821
|
+
`BIR_AUTH_DISABLE=1` is *not* that: it fakes a session and records with a
|
|
822
|
+
bogus token — a local-dev and CI switch, not a fleet one.
|
|
823
|
+
3. **Ask the service for a non-interactive grant** — a long-lived runner token,
|
|
824
|
+
created in the console and read from an env var. This remains the right answer
|
|
825
|
+
for a large fleet and is still a service-side change; the device grant and the
|
|
826
|
+
setup token make it less urgent, not unnecessary.
|
|
827
|
+
|
|
828
|
+
`bir logout` clears the file first — a hung service or a Ctrl-C cannot leave the
|
|
829
|
+
machine signed in — and then revokes the session at the service that issued it
|
|
830
|
+
(the one `credentials.json` names, not whatever `BIR_AUTH_URL` says), best
|
|
831
|
+
effort and within 8 s, so decommissioning a runner ends its access rather than
|
|
832
|
+
merely tidying the disk. Running recorders stop sending within seconds.
|
|
833
|
+
Decommissioning also means `bir uninstall --all` first (§8).
|
|
834
|
+
|
|
835
|
+
`BIR_HOME` relocates all of this if state belongs somewhere other than
|
|
836
|
+
`~/.baseinstrunner`.
|
|
837
|
+
|
|
838
|
+
---
|
|
839
|
+
|
|
840
|
+
## 8. Updating a runner
|
|
841
|
+
|
|
842
|
+
Paste the line again — the same line, used token and all. It installs the
|
|
843
|
+
version the service serves (`RUNNER_VERSION`, default `latest`), keeps the
|
|
844
|
+
sign-in it already has ("the setup token was already used; keeping the sign-in
|
|
845
|
+
from before (<email>)"), and runs `bir setup`, which re-wraps whatever moved and
|
|
846
|
+
restarts an older recorder. For a machine installed by hand: the new package,
|
|
847
|
+
then `bir setup` in each recorded project.
|
|
848
|
+
|
|
849
|
+
```bash
|
|
850
|
+
npm i -g @basein/runner@<version> && cd ~/work/repo && bir setup
|
|
851
|
+
```
|
|
852
|
+
|
|
853
|
+
Two rules used to follow, and neither is a step now:
|
|
854
|
+
|
|
855
|
+
- **Restarting the recorder.** A running process is still the old code, and
|
|
856
|
+
this was the step people forgot. `bir setup`, `bir up` and the SessionStart
|
|
857
|
+
hook all replace a running recorder that is older than the package — provided
|
|
858
|
+
no session is mid-turn. A newer one (another runner copy on the machine) is
|
|
859
|
+
kept. A recorder still serving a turn is left alone, and `bir setup` says so:
|
|
860
|
+
"it is still 0.2.12, serving a Claude Code session that is mid-turn; it keeps
|
|
861
|
+
running as 0.2.12 until `bir up --restart` (run it once that session ends)
|
|
862
|
+
replaces it with <version>". The next session's hook replaces it when no turn
|
|
863
|
+
is open, but a 0.2.12 recorder can report an interrupted turn as open for good;
|
|
864
|
+
`bir setup` and `bir up` look twice, a few seconds apart, and replace one when
|
|
865
|
+
nothing moved in between and no session's proxy is alive. `bir up --restart`
|
|
866
|
+
forces it now.
|
|
867
|
+
- **Re-running `bir install`.** `bir setup` does. A `--global` entry whose path
|
|
868
|
+
did not change needs nothing (§4); one whose runner moved — a new prefix, a
|
|
869
|
+
new Node — is re-wrapped from its source; a pinned-npx entry always
|
|
870
|
+
has its pin moved.
|
|
871
|
+
|
|
872
|
+
**Upgrading from 0.2.12.** The first `bir setup` after it changes more than
|
|
873
|
+
paths:
|
|
874
|
+
|
|
875
|
+
- Wrapped entries move to the local scope (§4.4). `.mcp.json` is put back as it
|
|
876
|
+
was; a user-scope server 0.2.12 wrapped in place is put back too, and every
|
|
877
|
+
other project that recorded it that way needs `bir setup` there again (the
|
|
878
|
+
install names them). A `.mcp.json` server Claude Code has not approved is no
|
|
879
|
+
longer wrapped.
|
|
880
|
+
- The runner no longer approves wrapped MCP tool calls; 0.2.12 answered every
|
|
881
|
+
one "allow". `bir install` and `bir setup` say so once per project. Headless
|
|
882
|
+
`claude -p` fleets must allow those tools themselves, or set
|
|
883
|
+
`BIR_CORRELATION_DECISION=allow` (§5.1).
|
|
884
|
+
- In a project with no scenario server (a plain `bir install`), the next
|
|
885
|
+
install without `--replay` switches replay off (§4.3); `bir replay on` turns
|
|
886
|
+
it back on.
|
|
887
|
+
- Until it is replaced, a 0.2.12 recorder still running keeps approving those
|
|
888
|
+
calls and ignores `bir off`; `bir doctor` warns about a recorder of another
|
|
889
|
+
version, and `bir up --restart` replaces it once its session ends.
|
|
890
|
+
|
|
891
|
+
**Rolling back.** First run `bir uninstall --all` with the version installed
|
|
892
|
+
now: an older runner cannot read everything this one wrote, and would find no
|
|
893
|
+
record for some wrapped entries and leave them — credentials included — in
|
|
894
|
+
place. Then install the older version (`BIR_VERSION=<version>` on the macOS and
|
|
895
|
+
Linux line, or by hand) and run `bir setup` in each project.
|
|
896
|
+
|
|
897
|
+
### The half-upgraded machine
|
|
898
|
+
|
|
899
|
+
This is the update failure that looks like success: the package is new, but a
|
|
900
|
+
proxy is still the old code because a config points elsewhere, or the recorder
|
|
901
|
+
is a process that outlived the upgrade. Everything keeps working, and quietly
|
|
902
|
+
runs code you did not ship.
|
|
903
|
+
|
|
904
|
+
The restart-on-skew above closes the common case. For the rest, a proxy reports
|
|
905
|
+
its version when it registers, the control server compares it with its own, and
|
|
906
|
+
a mismatch is one line on the audit log:
|
|
907
|
+
|
|
908
|
+
```
|
|
909
|
+
[bir] … version.skew server=fleet proxy=0.0.9 hooks=0.1.0 fix="restart bir-hooks after upgrading, …"
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
`bir doctor --json` carries the same facts structurally — under `health`,
|
|
913
|
+
`hooksVersion` and a `version` on each entry of `registeredProxies` — so a
|
|
914
|
+
provisioning script can assert agreement rather than grep a log. A proxy too old
|
|
915
|
+
to report a version is not treated as skewed: silence, not a false alarm.
|
|
916
|
+
|
|
917
|
+
### Removing a runner
|
|
918
|
+
|
|
919
|
+
`bir uninstall` covers one project. Before `npm rm -g @basein/runner` or
|
|
920
|
+
deleting `~/.baseinstrunner`, run `bir uninstall --all` — or paste the install
|
|
921
|
+
line with `BIR_UNINSTALL=1`, which runs just that:
|
|
922
|
+
|
|
923
|
+
```bash
|
|
924
|
+
curl -fsSL https://api.bi2202.com/install.sh | BIR_UNINSTALL=1 sh
|
|
925
|
+
```
|
|
926
|
+
|
|
927
|
+
```powershell
|
|
928
|
+
$env:BIR_UNINSTALL="1"; irm https://api.bi2202.com/install.ps1 | iex
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
Every wrapped server and every hook names a file under the runner's install,
|
|
932
|
+
so removing it first breaks every project still set up, with no `bir` left to
|
|
933
|
+
repair them. `--all` undoes every installed project as `bir uninstall` would
|
|
934
|
+
there, sweeps every `.claude.json` an install wrote to (user scope included),
|
|
935
|
+
stops every recorder, superseded ones too, and clears the off switches. When
|
|
936
|
+
something could not be undone it lists it, keeps the off switch in place, and
|
|
937
|
+
exits 1 ("Not everything could be undone"); fix that and run it again before
|
|
938
|
+
removing anything. Then `bir logout` (§7), then remove the package.
|
|
939
|
+
|
|
940
|
+
---
|
|
941
|
+
|
|
942
|
+
## 9. Per-release verification
|
|
943
|
+
|
|
944
|
+
CI does this on three platforms. To do it by hand on a clean machine:
|
|
945
|
+
|
|
946
|
+
```bash
|
|
947
|
+
npm run build && npm test # ~1,000 tests (npm runs pretest first)
|
|
948
|
+
npm run test:smoke # real processes: hooks + proxy + scenario over stdio;
|
|
949
|
+
# needs the build and the pretest above
|
|
950
|
+
npm pack && npm i -g ./basein-runner-*.tgz
|
|
951
|
+
cd /tmp/scratch && bir setup --auth-url <api> --token <t> # or BIR_AUTH_URL=<api> bir login, then bir install --global && bir up
|
|
952
|
+
bir doctor --json # must exit 0 (--strict: no warnings either)
|
|
953
|
+
bir uninstall # must end with "Uninstalled." and exit 0
|
|
954
|
+
```
|
|
955
|
+
|
|
956
|
+
`test:smoke` is the one that matters for packaging: it is the only test that
|
|
957
|
+
crosses process boundaries, which is exactly where a bad `files` list, a missing
|
|
958
|
+
shebang, or a broken bin mapping shows up.
|
|
959
|
+
|
|
960
|
+
Sandbox a manual run with `BIR_HOME` and `BIR_CLAUDE_JSON` so it cannot touch
|
|
961
|
+
your real Claude Code configuration. A fresh `BIR_HOME` has no sign-in, which is
|
|
962
|
+
why the recipe signs in: a bare `bir install --global && bir up` there starts a
|
|
963
|
+
recorder with nothing to record to, `bir up` ends with "it has no session to
|
|
964
|
+
record with" and exit 1, and `bir doctor --json` warns about the same thing
|
|
965
|
+
(exit 1 with `--strict`). `bir uninstall` restores byte-for-byte only a project
|
|
966
|
+
file nothing else edited since the install, and repairs `~/.claude.json` entry
|
|
967
|
+
by entry; a file it created stays behind, empty of our entries. It exits 1 with
|
|
968
|
+
"Not everything could be undone" while anything here still runs through the
|
|
969
|
+
runner.
|
|
970
|
+
|
|
971
|
+
---
|
|
972
|
+
|
|
973
|
+
## 10. Where things stand
|
|
974
|
+
|
|
975
|
+
| # | Item | State |
|
|
976
|
+
|---|---|---|
|
|
977
|
+
| 1 | Name settled; `PACKAGE_NAME` aligned | done |
|
|
978
|
+
| 2 | `repository` + `publishConfig`; provenance deferred (private repo) | done |
|
|
979
|
+
| 3 | Tarball trimmed — no dead sourcemaps, no scratchpad; `scripts/` ships | done |
|
|
980
|
+
| 4 | npx spec pinned; `--global` added; moved runners re-wrapped | done |
|
|
981
|
+
| 5 | CI and release workflows, three platforms | done |
|
|
982
|
+
| 6 | `scripts/install.{ps1,sh}`, served by the API at `/install.ps1` and `/install.sh` (replace `install-runner.*`) | done |
|
|
983
|
+
| 7 | Version-skew detection on registration; recorder restarts itself between sessions | done |
|
|
984
|
+
| 8 | UTF-8 BOM tolerated in MCP configs | done |
|
|
985
|
+
| 9 | npm org `basein`, `NPM_TOKEN` secret | done |
|
|
986
|
+
| 10 | `bir setup`: sign-in, wrap, replay, recorder in one command | done |
|
|
987
|
+
| 11 | Background recorder started by the SessionStart hook (`bir-hooks ensure`); `bir up` / `bir down`; log per project | done |
|
|
988
|
+
| 12 | Hooks in `.claude/settings.local.json` + `.git/info/exclude`; one port and token per project | done |
|
|
989
|
+
| 13 | Setup token (`BIR_SETUP_TOKEN`) for the first sign-in; credentials bound to their service; address in `config.json` | done |
|
|
990
|
+
| 14 | Tag `v0.2.9` to publish | ready |
|
|
991
|
+
| 15 | Non-interactive auth grant for fleets | **needs the service** |
|
|
992
|
+
| 16 | Migrate to trusted publishing before Jan 2027 (§3.3) | scheduled |
|
|
993
|
+
| 17 | Production readiness: local-scope wrapping (§4.4), no auto-approval of wrapped calls, `bir off` / `bir on`, `bir uninstall --all` and `BIR_UNINSTALL=1` (§8) — contract and measurements in [production.md](../production.md) | done, unreleased |
|
|
994
|
+
|
|
995
|
+
Item 15 is the only one that needs somebody else.
|
|
996
|
+
|
|
997
|
+
---
|
|
998
|
+
|
|
999
|
+
## Appendix: the BOM
|
|
1000
|
+
|
|
1001
|
+
`bir` used to reject any MCP config saved with a UTF-8 byte order mark. Notepad
|
|
1002
|
+
writes one, so does VS Code's "UTF-8 with BOM", and so does PowerShell's
|
|
1003
|
+
`Out-File -Encoding utf8`. `readFileSync(path, "utf8")` decodes the BOM to U+FEFF
|
|
1004
|
+
rather than consuming it, and `JSON.parse` then rejects the file.
|
|
1005
|
+
|
|
1006
|
+
The failure mode was the bad part: `bir install` logged the parse error and
|
|
1007
|
+
**still reported success**, so an operator saw a green run and no wrapping. On
|
|
1008
|
+
Windows runners this was a matter of time.
|
|
1009
|
+
|
|
1010
|
+
`parseJsonFile` now strips it, which covers every user-authored config read.
|
|
1011
|
+
Restoring is unaffected — the uninstall backup keeps the file's own text (its
|
|
1012
|
+
env and header values replaced by placeholders) on a separate path, so a BOM'd
|
|
1013
|
+
file comes back with its BOM intact.
|