@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
@@ -7,11 +7,18 @@
7
7
  * runs. `resolveServers` reports the winner; this module writes there and warns
8
8
  * loudly when a lower scope also defines the name.
9
9
  *
10
- * Byte-for-byte reversibility: the *text* of every file touched is stashed before
11
- * the first edit, together with a hash of what we wrote. On uninstall, a file
12
- * still matching that hash is restored verbatim; a file edited since is repaired
13
- * entry-by-entry instead, with a warning. That is the difference between "we put
14
- * the entries back" and "the file is as you left it".
10
+ * Byte-for-byte reversibility: the *text* of every project file touched is
11
+ * stashed before the first edit, together with a hash of what we wrote. On
12
+ * uninstall, a file still matching that hash — and not changed by anybody else
13
+ * between two of our writes — is restored verbatim; a file edited since is
14
+ * repaired entry-by-entry instead, with a warning. `~/.claude.json` is always
15
+ * repaired entry by entry: it is Claude Code's own, rewritten constantly, and
16
+ * holds every project's settings.
17
+ *
18
+ * Where the wrapped entries go (bir.ts, install): the LOCAL scope, as an
19
+ * override of the same name — never the team's `.mcp.json`, which gets
20
+ * committed and would carry this machine's node and runner paths to every
21
+ * clone, and never the user scope, which applies to every project.
15
22
  */
16
23
  import { type McpServerConfig, type Scope } from "../resolve.js";
17
24
  export interface ClaudeCodePaths {
@@ -42,12 +49,21 @@ export declare const LOCAL_SETTINGS_EXCLUDE = ".claude/settings.local.json";
42
49
  * `.git/info/exclude`, never `.gitignore`: the first is the person's own,
43
50
  * untracked list; the second is a file the team owns and would see in a diff.
44
51
  * Walks up from `dir` to find the repository, follows a worktree's `gitdir:`
45
- * pointer, and appends the line once. Returns the exclude file's path when the
52
+ * pointer to the repository's common git directory (the only exclude file git
53
+ * reads for a linked worktree), and appends the line once. Returns the exclude file's path when the
46
54
  * line is there (added or already present), undefined outside a repository or
47
55
  * when the file could not be written — an exclude that fails is a note, not a
48
56
  * failed install.
49
57
  */
50
58
  export declare function ensureGitExclude(dir: string, line?: string): string | undefined;
59
+ /**
60
+ * {@link ensureGitExclude}, also saying whether the line was ADDED now (false:
61
+ * it was already there — somebody's own, which uninstall must leave alone).
62
+ */
63
+ export declare function ensureGitExcludeLine(dir: string, line?: string): {
64
+ path: string;
65
+ added: boolean;
66
+ } | undefined;
51
67
  /** Undo {@link ensureGitExclude}: drop the line, leave everything else. */
52
68
  export declare function removeGitExclude(dir: string, line?: string): string | undefined;
53
69
  /**
@@ -68,6 +84,28 @@ export declare function fileForScope(scope: Scope, paths: ClaudeCodePaths): stri
68
84
  export declare function sha256(text: string): string;
69
85
  /** Read a file's text, or null when it does not exist. */
70
86
  export declare function readTextOrNull(path: string): string | null;
87
+ /**
88
+ * Read-modify-write one JSON file, safely:
89
+ *
90
+ * - a file that is not plain JSON (comments, trailing commas, a torn write)
91
+ * is REFUSED with an error naming it, never rewritten from a guess;
92
+ * - the file is re-read just before the write, and the edit is applied again
93
+ * to the fresh text when somebody else changed it in between (Claude Code
94
+ * rewrites `~/.claude.json` constantly, and a lost update there loses a
95
+ * server the user just added, or ours);
96
+ * - the write itself is atomic (util/atomic-write.ts), so Claude Code never
97
+ * reads a half-written config, and a symlinked file stays a symlink.
98
+ *
99
+ * `mutate` gets a fresh parse each attempt and must not keep state between
100
+ * calls. Returns the text written, for the sidecar's hash.
101
+ */
102
+ export declare function editJson(path: string, mutate: (parsed: Record<string, unknown>) => void): string;
103
+ /**
104
+ * Throw (naming the file) when `path` exists and is not a plain JSON object —
105
+ * so an install can refuse up front, before it has written anything, instead
106
+ * of stopping halfway through with some files changed and others not.
107
+ */
108
+ export declare function assertEditableJson(path: string): void;
71
109
  /**
72
110
  * Set (or delete, with `entry === null`) one MCP server entry in `scope`.
73
111
  * Returns the file's new text so the caller can hash it for the sidecar.
@@ -76,6 +114,17 @@ export declare function setServerEntry(scope: Scope, cwd: string, name: string,
76
114
  file: string;
77
115
  text: string;
78
116
  };
117
+ /**
118
+ * Set (or delete) one entry of the local scope under each of `keys` in
119
+ * `~/.claude.json`, in ONE atomic edit. `shouldTouch` sees the entry currently
120
+ * under a key (or undefined) and may veto that key — so a user's own entry
121
+ * under another spelling of the project is never overwritten or deleted.
122
+ * Returns the text written and the keys actually changed (none: no write).
123
+ */
124
+ export declare function setLocalEntries(claudeJson: string, keys: string[], name: string, entry: McpServerConfig | null, shouldTouch?: (current: McpServerConfig | undefined) => boolean): {
125
+ text?: string;
126
+ keys: string[];
127
+ };
79
128
  /**
80
129
  * The hook events `bir-hooks` registers, and the control route each drives.
81
130
  *
@@ -84,6 +133,17 @@ export declare function setServerEntry(scope: Scope, cwd: string, name: string,
84
133
  * only listens to the success one records a step that never closes.
85
134
  * `SubagentStart` / `SubagentStop` carry `agent_id`, which is what lets a
86
135
  * subagent's steps be attributed rather than silently interleaved.
136
+ *
137
+ * THE TIMEOUTS ARE THE USER'S WORST CASE. Claude Code waits for a hung hook up
138
+ * to its `timeout`, so each number here is how long a slow or blackholed
139
+ * service could make a person wait — at every prompt, every tool call, every
140
+ * `/exit`. The control server keeps its own per-route deadlines well inside
141
+ * them (prompt ≤ 7 s, tool/pre ≤ 12 s, tool/post and stop ≤ 2 s, end ≤ 1 s,
142
+ * start ≤ 2 s) and answers `{}` on overrun, so these are the second line of
143
+ * defence, not a budget to spend. SessionEnd matters twice: Claude Code's own
144
+ * default for it is 1.5 s, and whatever is written here RAISES that — the 30 it
145
+ * used to be turned every `/exit` during a service incident into a 30 s wait.
146
+ * A re-run of `bir setup` rewrites existing installs with these.
87
147
  */
88
148
  export declare const HOOK_ROUTES: Array<{
89
149
  event: string;
@@ -121,8 +181,12 @@ export type HooksBlock = Record<string, Array<{
121
181
  * Exported so the binary and the adapter cannot drift apart on the spelling.
122
182
  */
123
183
  export declare const ENSURE_ARG = "ensure";
124
- /** How long SessionStart may wait for a background recorder to come up. */
125
- export declare const ENSURE_TIMEOUT_S = 30;
184
+ /**
185
+ * How long SessionStart may wait for a background recorder to come up. The
186
+ * ensure hook keeps its own budget at 12 s or less and always exits 0; this is
187
+ * the host's ceiling on top of it.
188
+ */
189
+ export declare const ENSURE_TIMEOUT_S = 15;
126
190
  export interface HooksBlockOptions {
127
191
  /**
128
192
  * Make SessionStart start the recorder itself when none is running.
@@ -134,27 +198,41 @@ export interface HooksBlockOptions {
134
198
  * **replaced**, not added to. Hooks for one event run in parallel, and an
135
199
  * HTTP hook racing the daemon it depends on would fail every first session.
136
200
  *
137
- * Omitted for an npx invocation: resolving a package inside a hook is too
138
- * slow to start a recorder from, so those installs keep the HTTP hook and a
139
- * recorder someone started.
201
+ * The `{ command, args }` form is an already-built exec-form invocation —
202
+ * what an npx install uses (`npx --prefer-offline -y -p <spec> bir-hooks
203
+ * ensure`, in `cmd /c` on Windows). It pays npx's start-up once per session,
204
+ * which is affordable there and not per tool call; without it nothing ever
205
+ * starts the recorder of an npx install, and every hook of every session
206
+ * fails against a port nobody listens on.
140
207
  */
141
208
  ensure?: {
142
209
  node: string;
143
210
  hooksScript: string;
144
211
  nodeFlags?: string[];
212
+ } | {
213
+ command: string;
214
+ args: string[];
145
215
  };
146
216
  }
147
217
  /** Build the 'hooks' block pointing at a control server. */
148
218
  export declare function buildHooksBlock(controlUrl: string, token: string, opts?: HooksBlockOptions): HooksBlock;
149
219
  /**
150
- * True when a hook entry is one of ours: an HTTP hook aimed at a control-server
151
- * route, or the exec-form command hook that starts the recorder.
220
+ * True when a hook entry is one of ours, and only then — install and uninstall
221
+ * delete what this says is ours, so a false positive deletes somebody's hook.
222
+ *
223
+ * HTTP: a loopback URL (127.0.0.1 / localhost / [::1]) whose path is exactly
224
+ * one of our routes, AND either our kind of bearer token or a port in
225
+ * the range `bir` allocates (the token-less 0.2.8 shape). A hook to
226
+ * `https://hooks.example.com/session/end` is somebody else's.
227
+ * exec: our self-starting SessionStart hook — `bir-hooks.js ensure`, or the
228
+ * npx form `… -p @basein/runner@<v> bir-hooks ensure`.
152
229
  */
153
230
  export declare function isBirHook(entry: unknown): boolean;
154
231
  /**
155
232
  * Merge our hooks into a settings file, leaving every other hook alone. Returns
156
233
  * the new text. Our own entries are replaced rather than appended, so repeated
157
- * installs stay idempotent.
234
+ * installs stay idempotent — hook by hook: a user's own hook that happens to
235
+ * share a matcher group with one of ours stays exactly where it is.
158
236
  */
159
237
  export declare function installHooks(settingsPath: string, block: HooksBlock): string;
160
238
  /** Remove only our hook entries, and drop events left empty. */