@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.
Files changed (144) hide show
  1. package/README.md +540 -350
  2. package/dist/auth/client.d.ts +65 -3
  3. package/dist/auth/client.js +305 -53
  4. package/dist/bin/bir-hooks.d.ts +72 -31
  5. package/dist/bin/bir-hooks.js +176 -284
  6. package/dist/bin/bir-proxy.d.ts +17 -2
  7. package/dist/bin/bir-proxy.js +222 -28
  8. package/dist/bin/bir-scenario.d.ts +61 -10
  9. package/dist/bin/bir-scenario.js +704 -135
  10. package/dist/bin/bir.d.ts +14 -8
  11. package/dist/bin/bir.js +2937 -488
  12. package/dist/bin/investigate.js +41 -16
  13. package/dist/bin/scenario-edit.d.ts +43 -10
  14. package/dist/bin/scenario-edit.js +281 -80
  15. package/dist/bin/scenario-liveness.d.ts +194 -0
  16. package/dist/bin/scenario-liveness.js +459 -0
  17. package/dist/bin/scenario-stderr.d.ts +43 -0
  18. package/dist/bin/scenario-stderr.js +76 -0
  19. package/dist/bin/service-read.d.ts +88 -0
  20. package/dist/bin/service-read.js +128 -0
  21. package/dist/bin/setup.d.ts +19 -0
  22. package/dist/bin/setup.js +146 -20
  23. package/dist/config/adapters/claude-code.d.ts +92 -14
  24. package/dist/config/adapters/claude-code.js +291 -101
  25. package/dist/config/adapters/generic.d.ts +4 -1
  26. package/dist/config/adapters/generic.js +13 -14
  27. package/dist/config/generate.d.ts +392 -14
  28. package/dist/config/generate.js +666 -19
  29. package/dist/config/mcp-approval.d.ts +76 -0
  30. package/dist/config/mcp-approval.js +210 -0
  31. package/dist/config/node-path.d.ts +42 -0
  32. package/dist/config/node-path.js +102 -0
  33. package/dist/config/overrides.d.ts +62 -0
  34. package/dist/config/overrides.js +136 -0
  35. package/dist/config/resolve.d.ts +70 -8
  36. package/dist/config/resolve.js +186 -15
  37. package/dist/control/client.d.ts +22 -0
  38. package/dist/control/client.js +26 -7
  39. package/dist/control/correlation.d.ts +23 -5
  40. package/dist/control/correlation.js +0 -0
  41. package/dist/control/daemon.d.ts +400 -18
  42. package/dist/control/daemon.js +1105 -102
  43. package/dist/control/discovery.d.ts +77 -5
  44. package/dist/control/discovery.js +186 -11
  45. package/dist/control/ensure-hook.d.ts +61 -0
  46. package/dist/control/ensure-hook.js +160 -8
  47. package/dist/control/paths.d.ts +26 -0
  48. package/dist/control/paths.js +34 -0
  49. package/dist/control/pin-guard.d.ts +83 -0
  50. package/dist/control/pin-guard.js +534 -0
  51. package/dist/control/recorder-keeper.d.ts +229 -0
  52. package/dist/control/recorder-keeper.js +721 -0
  53. package/dist/control/recorder-main.d.ts +73 -0
  54. package/dist/control/recorder-main.js +778 -0
  55. package/dist/control/server.d.ts +707 -31
  56. package/dist/control/server.js +2803 -306
  57. package/dist/control/transcript.d.ts +21 -1
  58. package/dist/control/transcript.js +378 -112
  59. package/dist/index.d.ts +5 -1
  60. package/dist/index.js +5 -1
  61. package/dist/jsonrpc/framing.d.ts +76 -12
  62. package/dist/jsonrpc/framing.js +216 -59
  63. package/dist/proxy/intercept.d.ts +49 -5
  64. package/dist/proxy/intercept.js +188 -59
  65. package/dist/proxy/relay.d.ts +16 -0
  66. package/dist/proxy/relay.js +77 -5
  67. package/dist/proxy/revive.d.ts +122 -0
  68. package/dist/proxy/revive.js +251 -0
  69. package/dist/proxy/session.d.ts +222 -14
  70. package/dist/proxy/session.js +708 -99
  71. package/dist/record/housekeeping.d.ts +71 -0
  72. package/dist/record/housekeeping.js +417 -0
  73. package/dist/record/recorder.d.ts +51 -3
  74. package/dist/record/recorder.js +3 -0
  75. package/dist/record/redact.d.ts +103 -9
  76. package/dist/record/redact.js +659 -30
  77. package/dist/record/remote-recorder.d.ts +327 -32
  78. package/dist/record/remote-recorder.js +1517 -187
  79. package/dist/record/switchable-recorder.d.ts +126 -0
  80. package/dist/record/switchable-recorder.js +351 -0
  81. package/dist/record/truncate.d.ts +40 -1
  82. package/dist/record/truncate.js +111 -4
  83. package/dist/replay/bash-channel.d.ts +35 -0
  84. package/dist/replay/bash-channel.js +133 -0
  85. package/dist/replay/bundle.d.ts +10 -0
  86. package/dist/replay/bundle.js +10 -0
  87. package/dist/replay/controller.d.ts +371 -20
  88. package/dist/replay/controller.js +1185 -111
  89. package/dist/replay/derive.d.ts +1 -0
  90. package/dist/replay/derive.js +81 -24
  91. package/dist/replay/executor.d.ts +51 -16
  92. package/dist/replay/executor.js +114 -41
  93. package/dist/replay/handover.d.ts +54 -1
  94. package/dist/replay/handover.js +55 -5
  95. package/dist/replay/logic.d.ts +62 -10
  96. package/dist/replay/logic.js +140 -22
  97. package/dist/replay/plan.d.ts +201 -27
  98. package/dist/replay/plan.js +581 -141
  99. package/dist/replay/safety.d.ts +72 -0
  100. package/dist/replay/safety.js +303 -0
  101. package/dist/replay/source-run.d.ts +10 -0
  102. package/dist/replay/source-run.js +80 -35
  103. package/dist/replay/targets.d.ts +15 -1
  104. package/dist/replay/targets.js +62 -4
  105. package/dist/replay/tool-error.d.ts +15 -1
  106. package/dist/replay/tool-error.js +33 -1
  107. package/dist/replay/types.d.ts +7 -4
  108. package/dist/upstream/client.d.ts +56 -3
  109. package/dist/upstream/client.js +61 -4
  110. package/dist/upstream/headers.d.ts +43 -0
  111. package/dist/upstream/headers.js +90 -0
  112. package/dist/upstream/http-client.d.ts +213 -22
  113. package/dist/upstream/http-client.js +1034 -168
  114. package/dist/upstream/http-transport.d.ts +143 -0
  115. package/dist/upstream/http-transport.js +391 -0
  116. package/dist/upstream/lazy-client.d.ts +8 -0
  117. package/dist/upstream/lazy-client.js +29 -1
  118. package/dist/upstream/sse.d.ts +78 -0
  119. package/dist/upstream/sse.js +189 -0
  120. package/dist/upstream/stdio-client.d.ts +61 -1
  121. package/dist/upstream/stdio-client.js +356 -33
  122. package/dist/util/atomic-write.d.ts +29 -0
  123. package/dist/util/atomic-write.js +96 -0
  124. package/dist/util/journal.d.ts +15 -0
  125. package/dist/util/journal.js +48 -3
  126. package/dist/util/log.d.ts +10 -0
  127. package/dist/util/log.js +28 -1
  128. package/dist/util/off-switch.d.ts +46 -0
  129. package/dist/util/off-switch.js +91 -0
  130. package/dist/util/service-health.d.ts +133 -0
  131. package/dist/util/service-health.js +217 -0
  132. package/docs/BaseInstRunner.md +694 -621
  133. package/docs/calculatedReplay.md +1625 -1244
  134. package/docs/calculatedReplayGuide.md +1212 -879
  135. package/docs/installRun.md +1013 -772
  136. package/docs/loginWeb.md +627 -607
  137. package/docs/mcpmark.md +49 -16
  138. package/docs/my-first-sample.md +569 -545
  139. package/docs/quickstart.md +384 -271
  140. package/docs/t-bench.md +406 -394
  141. package/package.json +58 -57
  142. package/production.md +1371 -0
  143. package/scripts/install.ps1 +97 -6
  144. package/scripts/install.sh +104 -7
@@ -45,6 +45,12 @@
45
45
  BIR_NODE_ARCHIVE a local node-v<V>-win-<arch>.zip instead of the download
46
46
  BIR_TARBALL a local @basein/runner .tgz instead of the registry
47
47
  BIR_NO_CLAUDE=1 do not install Claude Code
48
+ BIR_UNINSTALL=1 the other direction: `bir uninstall --all` - every
49
+ project on this machine put back as it was, every
50
+ recorder stopped - so ~\.baseinstrunner can be deleted
51
+ without breaking anything. Downloads nothing, needs no
52
+ folder and no token:
53
+ $env:BIR_UNINSTALL="1"; irm https://api.<domain>/install.ps1 | iex
48
54
  BIR_DRY_RUN=1 print every action and perform none: no download, no
49
55
  install, no registry write, no `bir setup`. For support:
50
56
  it shows what the line would do on a given machine.
@@ -167,13 +173,44 @@ function Invoke-BirInstall {
167
173
  return $null
168
174
  }
169
175
 
170
- # --use-system-ca exists from Node 22.15; with it a corporate root in the
171
- # Windows store is trusted without NODE_EXTRA_CA_CERTS.
176
+ # --use-system-ca exists from Node 22.15, 23.8 and 24 (not 23.0-23.7, which
177
+ # exit on it as a bad option); with it a corporate root in the Windows store
178
+ # is trusted without NODE_EXTRA_CA_CERTS. The version table is for a Node
179
+ # not on disk yet (the private one, in a dry run); a node that exists is
180
+ # asked instead (Get-SystemCaFlags).
172
181
  function Get-NodeFlags([int]$Major, [int]$Minor) {
173
- if (($Major -gt 22) -or ($Major -eq 22 -and $Minor -ge 15)) { return @('--use-system-ca') }
182
+ if (($Major -ge 24) -or ($Major -eq 23 -and $Minor -ge 8) -or ($Major -eq 22 -and $Minor -ge 15)) { return @('--use-system-ca') }
174
183
  return @()
175
184
  }
176
185
 
186
+ # True when this node binary accepts a flag: it is asked, not guessed from a
187
+ # version. Node's fetch ignores HTTPS_PROXY unless started with
188
+ # --use-env-proxy (recent Node 22 and 24 have it), so behind a proxy-only
189
+ # network every call the runner makes would wait out a connect timeout.
190
+ function Test-NodeFlag([string]$NodeExe, [string]$Flag) {
191
+ if (-not $NodeExe -or -not (Test-Path -LiteralPath $NodeExe -PathType Leaf)) { return $false }
192
+ $r = Invoke-Tool $NodeExe @($Flag, '-e', '0') -Quiet
193
+ return ($r.Code -eq 0)
194
+ }
195
+
196
+ # True when NO_PROXY sends 127.0.0.1 past the proxy. With --use-env-proxy
197
+ # Node sends the runner's own loopback calls to the proxy too - the hooks'
198
+ # recorder, a proxy registering, `bir setup` waiting for the recorder -
199
+ # unless NO_PROXY names 127.0.0.1, '*', or a suffix of it such as .0.0.1
200
+ # ('localhost' alone is not enough). Windows has one variable for both
201
+ # spellings.
202
+ function Test-LoopbackBypassesProxy {
203
+ $value = $env:NO_PROXY
204
+ if (-not $value -or -not $value.Trim()) { return $false }
205
+ foreach ($entry in ($value -split '[\s,]+')) {
206
+ $e = $entry.Trim().ToLowerInvariant()
207
+ if (-not $e) { continue }
208
+ if ($e -eq '*' -or $e -eq '127.0.0.1') { return $true }
209
+ if ($e.StartsWith('.') -and '127.0.0.1'.EndsWith($e)) { return $true }
210
+ }
211
+ return $false
212
+ }
213
+
177
214
  # A per-shell or per-version directory: fnm's multishell links vanish with
178
215
  # the shell, and nvm, Volta, Scoop, asdf and mise switch or remove versions
179
216
  # under the same path. The SessionStart hook pins an absolute node path, so
@@ -394,6 +431,40 @@ public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, UIntPtr wP
394
431
  try {
395
432
  if ($dryRun) { Write-Warn 'BIR_DRY_RUN=1: printing what would happen; nothing is downloaded, installed or written.' }
396
433
 
434
+ # -- uninstall mode ---------------------------------------------------------
435
+ #
436
+ # BIR_UNINSTALL=1: undo `bir setup` in every project on this machine
437
+ # before its files go. Every wrapped MCP server and every hook names a
438
+ # file under ~\.baseinstrunner; deleting that first would break them
439
+ # all, in every project, with no `bir` left to repair it.
440
+ if ($env:BIR_UNINSTALL -eq '1') {
441
+ Write-Step 'Removing the runner from every project on this machine'
442
+ $birCmd = Join-Path $birHome 'bin\bir.cmd'
443
+ if (-not (Test-Path -LiteralPath $birCmd -PathType Leaf)) {
444
+ # A global npm install: its .cmd shim, never the .ps1 one, which
445
+ # the execution policy may refuse.
446
+ $found = @(Get-Command 'bir.cmd' -CommandType Application -ErrorAction SilentlyContinue)
447
+ if ($found.Count -ge 1) { $birCmd = $found[0].Source } else { $birCmd = '' }
448
+ }
449
+ if (-not $birCmd) {
450
+ Write-Note "bir is not installed here ($birHome\bin\bir.cmd is missing), so nothing on this machine is wired to this install."
451
+ Write-Note "If $birHome is still there, it can be deleted."
452
+ return
453
+ }
454
+ if ($dryRun) {
455
+ Write-Would "run: $birCmd uninstall --all"
456
+ return
457
+ }
458
+ $r = Invoke-Tool $birCmd @('uninstall', '--all')
459
+ if ($r.Code -ne 0) {
460
+ throw "bir uninstall --all could not undo everything (the lines above say what). Keep $birHome until it can: those entries still name files in it."
461
+ }
462
+ Write-Step 'Done: nothing on this machine runs through the runner now'
463
+ Write-Note "To delete its files too: Remove-Item -Recurse -Force `"$birHome`""
464
+ Write-Note "and take $birHome\bin off your user Path (Settings, Environment Variables)."
465
+ return
466
+ }
467
+
397
468
  # -- 00. the folder, before anything is downloaded ----------------------
398
469
  #
399
470
  # A terminal opened from Start begins in the home folder, and that is
@@ -486,9 +557,13 @@ public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, UIntPtr wP
486
557
  else {
487
558
  $nodeExe = $realPath
488
559
  $npmCli = $candidateCli
489
- # @(...) around the call: a one-element array unrolls to a
490
- # string on assignment, and string + array is one string.
491
- $nodeFlags = @(Get-NodeFlags $parts.Major $parts.Minor)
560
+ # This node exists, so it is asked whether it knows the
561
+ # flag rather than trusted to by its version: a 23.0-23.7
562
+ # exits on it, and the npm install below would fail.
563
+ # @(...) around the value: a one-element array unrolls to
564
+ # a string on assignment, and string + array is one string.
565
+ $nodeFlags = @()
566
+ if (Test-NodeFlag $realPath '--use-system-ca') { $nodeFlags = @('--use-system-ca') }
492
567
  Write-Note "node v$($parts.Text) at $realPath"
493
568
  }
494
569
  }
@@ -558,6 +633,22 @@ public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, UIntPtr wP
558
633
  Write-Step 'Putting bir on your PATH'
559
634
  $binDir = Join-Path $birHome 'bin'
560
635
  if (-not $dryRun) { New-Item -ItemType Directory -Path $binDir -Force | Out-Null }
636
+ # Behind a proxy, `bir` and everything it wires (proxies, hooks, the
637
+ # recorder) carry --use-env-proxy too, when this Node knows it AND
638
+ # NO_PROXY exempts 127.0.0.1: without that the flag would send the
639
+ # runner's own loopback calls to the proxy and break every hook, where
640
+ # leaving it out only costs reaching the service. npm needs nothing, it
641
+ # reads HTTPS_PROXY itself.
642
+ if ($proxy -and -not (Test-LoopbackBypassesProxy)) {
643
+ Write-Note 'HTTPS_PROXY is set but NO_PROXY does not name 127.0.0.1, so the runner will not use the proxy (it would swallow its own loopback calls). To use it: set NO_PROXY=127.0.0.1,localhost,::1 (plus your own entries) and run this again'
644
+ }
645
+ elseif ($proxy -and (Test-NodeFlag $nodeExe '--use-env-proxy')) {
646
+ $nodeFlags = @($nodeFlags) + @('--use-env-proxy')
647
+ Write-Note 'node runs with --use-env-proxy, so the runner reaches the service through the proxy'
648
+ }
649
+ elseif ($proxy) {
650
+ Write-Note 'this node has no --use-env-proxy; the runner itself will not use the proxy (a newer Node 22 or 24 has the flag)'
651
+ }
561
652
  # The launchers carry the same node flags this run used, so a `bir setup`
562
653
  # typed later - and everything it wires - trusts the same certificates.
563
654
  Write-Launcher (Join-Path $binDir 'bir.cmd') $nodeExe $nodeFlags $birJs
@@ -28,6 +28,9 @@
28
28
  #
29
29
  # Safe to re-run; re-running is also the upgrade path. To record another project
30
30
  # later: cd there and run `bir setup` (no token needed, the sign-in is kept).
31
+ # To take it out of every project before deleting it (BIR_UNINSTALL below):
32
+ #
33
+ # curl -fsSL https://api.bi2202.com/install.sh | BIR_UNINSTALL=1 sh
31
34
  #
32
35
  # Environment:
33
36
  # BIR_SETUP_TOKEN one-time setup token from the console. Copied and removed
@@ -36,6 +39,8 @@
36
39
  # BIR_AUTH_URL the API address. Needed only when this is the raw file from
37
40
  # the repository — the copy the API serves has it filled in.
38
41
  # BIR_VERSION runner version to install (default: the served one, else latest).
42
+ # Going back to an OLDER runner: run `bir uninstall --all` with
43
+ # the one installed now first, then this line, then `bir setup`.
39
44
  # BIR_PROJECT the project directory (default: the current one).
40
45
  # BIR_NODE_VERSION private Node version (default 24.21.0). Any other version
41
46
  # is verified against nodejs.org's SHASUMS256.txt instead of
@@ -44,6 +49,10 @@
44
49
  # of the download (air-gapped machines). Still digest-checked.
45
50
  # BIR_TARBALL a local @basein/runner .tgz instead of the registry (air-gapped).
46
51
  # BIR_NO_CLAUDE=1 do not install Claude Code.
52
+ # BIR_UNINSTALL=1 the other direction: `bir uninstall --all` — every project
53
+ # on this machine put back as it was, every recorder stopped —
54
+ # so ~/.baseinstrunner can be deleted without breaking anything.
55
+ # Downloads nothing; needs no folder and no token.
47
56
  # BIR_DRY_RUN=1 print every action; download, install and write nothing.
48
57
  # HTTPS_PROXY, HTTP_PROXY, NO_PROXY, NODE_EXTRA_CA_CERTS pass through to node and npm.
49
58
  #
@@ -79,6 +88,31 @@ ver_minor() {
79
88
  printf '%s\n' "$_v"
80
89
  }
81
90
 
91
+ # True when NO_PROXY (either spelling) sends 127.0.0.1 past the proxy. With
92
+ # --use-env-proxy Node sends the runner's own loopback calls to the proxy too -
93
+ # the hooks' recorder, a proxy registering, `bir setup` waiting for the
94
+ # recorder - unless NO_PROXY names 127.0.0.1, '*', or a suffix of it such as
95
+ # .0.0.1 ('localhost' alone is not enough). Every spelling that is set must say
96
+ # so: node and its fetch disagree on which one wins. A subshell, for set -f.
97
+ loopback_bypasses_proxy() (
98
+ set -f
99
+ _seen=''
100
+ for _np in "${NO_PROXY:-}" "${no_proxy:-}"; do
101
+ [ -n "$(printf '%s' "$_np" | tr -d ' ,')" ] || continue
102
+ _seen=1
103
+ _ok=''
104
+ for _e in $(printf '%s' "$_np" | tr ',' ' '); do
105
+ _e=$(printf '%s' "$_e" | tr '[:upper:]' '[:lower:]')
106
+ case "$_e" in
107
+ '*'|127.0.0.1) _ok=1 ;;
108
+ .*) case 127.0.0.1 in *"$_e") _ok=1 ;; esac ;;
109
+ esac
110
+ done
111
+ [ -n "$_ok" ] || exit 1
112
+ done
113
+ [ -n "$_seen" ]
114
+ )
115
+
82
116
  # True when a Node of this version understands --use-system-ca: 22.15+, 23.8+, 24+.
83
117
  node_has_system_ca() {
84
118
  _maj=$(ver_major "$1"); _min=$(ver_minor "$1")
@@ -313,12 +347,44 @@ write_launcher() {
313
347
  mkdir -p "$bin_dir"
314
348
  # The launcher carries the node flag this run used (--use-system-ca when the
315
349
  # Node knows it), so a `bir setup` typed later trusts the same certificates.
316
- printf '#!/bin/sh\nexec "%s"%s "%s/dist/bin/%s" "$@"\n' "$node_bin" "${system_ca:+ --use-system-ca}" "$runner_dir" "$2" > "$_launcher.tmp"
350
+ printf '#!/bin/sh\nexec "%s"%s%s "%s/dist/bin/%s" "$@"\n' "$node_bin" "${system_ca:+ --use-system-ca}" "${env_proxy:+ --use-env-proxy}" "$runner_dir" "$2" > "$_launcher.tmp"
317
351
  chmod +x "$_launcher.tmp"
318
352
  mv -f "$_launcher.tmp" "$_launcher"
319
353
  note "$_launcher"
320
354
  }
321
355
 
356
+ # BIR_UNINSTALL=1: undo `bir setup` in every project on this machine before its
357
+ # files go. Every wrapped MCP server and every hook names a file under
358
+ # ~/.baseinstrunner; deleting that first would break them all, in every project,
359
+ # with no `bir` left to repair it. Exits; nothing is downloaded.
360
+ uninstall_everywhere() {
361
+ step "Removing the runner from every project on this machine"
362
+ if [ -x "$bin_dir/bir" ]; then
363
+ bir_cmd="$bin_dir/bir"
364
+ elif command -v bir >/dev/null 2>&1; then
365
+ bir_cmd=$(command -v bir)
366
+ else
367
+ note "bir is not installed here ($bin_dir/bir is missing), so nothing on this machine is wired to this install."
368
+ note "If $bir_home is still there, it can be deleted."
369
+ exit 0
370
+ fi
371
+ if [ "$DRY_RUN" = 1 ]; then
372
+ dry "\"$bir_cmd\" uninstall --all"
373
+ exit 0
374
+ fi
375
+ un_rc=0
376
+ "$bir_cmd" uninstall --all || un_rc=$?
377
+ printf '\n'
378
+ if [ "$un_rc" -ne 0 ]; then
379
+ printf '%serror: %s%s\n' "$C_ERR" "bir uninstall --all could not undo everything (the lines above say what). Keep $bir_home until it can: those entries still name files in it." "$C_OFF" >&2
380
+ exit "$un_rc"
381
+ fi
382
+ step "Done: nothing on this machine runs through the runner now"
383
+ note "To delete its files too: rm -rf \"$bir_home\""
384
+ note "and remove this line from your shell profile: $path_line"
385
+ exit 0
386
+ }
387
+
322
388
  # find_claude → the path of `claude`, from PATH or the native installer's
323
389
  # ~/.local/bin. Fails when there is none.
324
390
  find_claude() {
@@ -373,6 +439,11 @@ main() {
373
439
  # shellcheck disable=SC2016
374
440
  path_line='export PATH="$HOME/.baseinstrunner/bin:$PATH"'
375
441
 
442
+ if [ "${BIR_UNINSTALL:-}" = 1 ]; then
443
+ if [ "$DRY_RUN" = 1 ]; then note "dry run (BIR_DRY_RUN=1): nothing is changed"; fi
444
+ uninstall_everywhere
445
+ fi
446
+
376
447
  # 00. The folder — before anything is downloaded. A terminal opened from the
377
448
  # Dock or the applications menu starts in the home directory, and that is
378
449
  # where a first paste tends to land.
@@ -454,6 +525,25 @@ main() {
454
525
  # where a company CA usually is. Older Nodes need NODE_EXTRA_CA_CERTS.
455
526
  system_ca=''
456
527
  if node_has_system_ca "$node_ver"; then system_ca=1; fi
528
+ # Node's fetch ignores HTTPS_PROXY unless node runs with --use-env-proxy, so
529
+ # behind a proxy-only network every call the runner makes would wait out a
530
+ # connect timeout. The node is asked whether it knows the flag, not guessed
531
+ # from a version; the launchers and `bir setup` (and so everything it wires)
532
+ # carry it. Only when NO_PROXY exempts 127.0.0.1, though: otherwise the flag
533
+ # sends the runner's own loopback calls to the proxy and breaks every hook,
534
+ # where leaving it out only costs reaching the service. npm needs nothing: it
535
+ # reads HTTPS_PROXY itself.
536
+ env_proxy=''
537
+ if [ -n "${HTTPS_PROXY:-}${https_proxy:-}${HTTP_PROXY:-}${http_proxy:-}" ] && ! loopback_bypasses_proxy; then
538
+ note "HTTPS_PROXY is set but NO_PROXY does not name 127.0.0.1, so the runner will not use the proxy (it would swallow its own loopback calls). To use it: set NO_PROXY=127.0.0.1,localhost,::1 (plus your own entries) and run this again"
539
+ elif [ -n "${HTTPS_PROXY:-}${https_proxy:-}${HTTP_PROXY:-}${http_proxy:-}" ] && [ -x "$node_bin" ]; then
540
+ if "$node_bin" --use-env-proxy -e 0 >/dev/null 2>&1; then
541
+ env_proxy=1
542
+ note "node runs with --use-env-proxy, so the runner reaches the service through the proxy"
543
+ else
544
+ note "this node has no --use-env-proxy; the runner itself will not use the proxy (a newer Node 22 or 24 has the flag)"
545
+ fi
546
+ fi
457
547
 
458
548
  # 2. The runner — one code path for both Nodes: always the private prefix,
459
549
  # never the machine's (root-owned after a .pkg or apt install; per-version
@@ -471,6 +561,10 @@ main() {
471
561
  note "into $prefix (the machine's npm prefix is left alone; no sudo)"
472
562
  runner_dir="$prefix/lib/node_modules/$PACKAGE"
473
563
  runner_js="$runner_dir/dist/bin/bir.js"
564
+ if [ -n "${BIR_VERSION:-}" ] && [ -f "$runner_dir/package.json" ]; then
565
+ # A rollback: an older runner cannot read everything a newer one wrote.
566
+ note "BIR_VERSION=$BIR_VERSION replaces the runner installed now. Going back to an OLDER one? Run: bir uninstall --all (with the current one) first"
567
+ fi
474
568
  if [ "$DRY_RUN" = 1 ]; then
475
569
  dry "\"$node_bin\"${system_ca:+ --use-system-ca} \"$npm_cli\" install -g --prefix \"$prefix\" --no-fund --no-audit $spec"
476
570
  else
@@ -548,19 +642,22 @@ main() {
548
642
  fi
549
643
 
550
644
  # 4. Setup: sign in, wire the project, start the recorder. The token goes into
551
- # this child's environment and nowhere else.
645
+ # this child's environment and nowhere else. The PHYSICAL project path
646
+ # (pwd -P): setup, the recorder and Claude Code all key the project by the
647
+ # resolved path, and a symlinked one (macOS's /tmp, a linked ~/code) made
648
+ # setup wait for a recorder it was looking for under the other spelling.
552
649
  step "Setting up the runner in $project"
553
650
  setup_rc=0
554
651
  if [ "$DRY_RUN" = 1 ]; then
555
652
  token_note=''
556
653
  if [ -n "$setup_token" ]; then token_note=' (with BIR_SETUP_TOKEN in its environment)'; fi
557
- dry "\"$node_bin\"${system_ca:+ --use-system-ca} \"$runner_js\" setup --auth-url $auth_url --project \"$project\"$token_note"
654
+ dry "\"$node_bin\"${system_ca:+ --use-system-ca}${env_proxy:+ --use-env-proxy} \"$runner_js\" setup --auth-url $auth_url --project \"$project_phys\"$token_note"
558
655
  elif [ -n "$setup_token" ]; then
559
- BIR_SETUP_TOKEN=$setup_token "$node_bin" ${system_ca:+--use-system-ca} "$runner_js" setup \
560
- --auth-url "$auth_url" --project "$project" || setup_rc=$?
656
+ BIR_SETUP_TOKEN=$setup_token "$node_bin" ${system_ca:+--use-system-ca} ${env_proxy:+--use-env-proxy} "$runner_js" setup \
657
+ --auth-url "$auth_url" --project "$project_phys" || setup_rc=$?
561
658
  else
562
- "$node_bin" ${system_ca:+--use-system-ca} "$runner_js" setup \
563
- --auth-url "$auth_url" --project "$project" || setup_rc=$?
659
+ "$node_bin" ${system_ca:+--use-system-ca} ${env_proxy:+--use-env-proxy} "$runner_js" setup \
660
+ --auth-url "$auth_url" --project "$project_phys" || setup_rc=$?
564
661
  fi
565
662
 
566
663
  printf '\n'