@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
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scenario-stderr — how the `bir` MCP server (bir-scenario) reads the stderr
|
|
3
|
+
* of a `bir` it spawned for a scenario tool.
|
|
4
|
+
*
|
|
5
|
+
* Two kinds of line arrive there, mixed: `bir`'s audit lines
|
|
6
|
+
* (`[bir] <iso time> <event> …`), which belong in the log where `bir doctor`
|
|
7
|
+
* and a person look, and the sentences `bir` wrote for a person
|
|
8
|
+
* ("[bir] not signed in — run `bir login`"), which the model needs when the
|
|
9
|
+
* command failed. They are sorted line by line AS THEY ARRIVE: audit lines go
|
|
10
|
+
* straight on, and of the rest only the LAST so many characters are kept.
|
|
11
|
+
*
|
|
12
|
+
* WHY AS THEY ARRIVE, AND WHY THE LAST. Sorting after the exit, with the first
|
|
13
|
+
* 8 000 characters kept, lost exactly the line that mattered: with
|
|
14
|
+
* `BIR_VERBOSE=1` the audit lines filled the cap, and `bir`'s closing reason —
|
|
15
|
+
* the last thing it prints — never reached the model, which got "exited with
|
|
16
|
+
* code 1" and nothing to act on.
|
|
17
|
+
*
|
|
18
|
+
* Its own module so it can be tested: bir-scenario starts serving on import.
|
|
19
|
+
*/
|
|
20
|
+
/** `bir`'s own audit lines — for the log, not for the model. */
|
|
21
|
+
export declare const AUDIT_LINE: RegExp;
|
|
22
|
+
export interface StderrSorterOptions {
|
|
23
|
+
/** How much of the non-audit text is kept: the last this many characters. */
|
|
24
|
+
keepChars: number;
|
|
25
|
+
/** How much audit text is passed on, at most; the rest is dropped. */
|
|
26
|
+
forwardChars: number;
|
|
27
|
+
/** Where an audit line goes (one line, without its newline). Must not throw. */
|
|
28
|
+
forward: (line: string) => void;
|
|
29
|
+
}
|
|
30
|
+
export declare class StderrSorter {
|
|
31
|
+
private readonly opts;
|
|
32
|
+
private partial;
|
|
33
|
+
/** Where the rest of a line cut for length goes, once its start was sorted. */
|
|
34
|
+
private continuing;
|
|
35
|
+
private kept;
|
|
36
|
+
private forwarded;
|
|
37
|
+
constructor(opts: StderrSorterOptions);
|
|
38
|
+
push(chunk: string): void;
|
|
39
|
+
/** Everything that was not an audit line (the last of it), once the stream ended. */
|
|
40
|
+
end(): string;
|
|
41
|
+
private line;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=scenario-stderr.d.ts.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scenario-stderr — how the `bir` MCP server (bir-scenario) reads the stderr
|
|
3
|
+
* of a `bir` it spawned for a scenario tool.
|
|
4
|
+
*
|
|
5
|
+
* Two kinds of line arrive there, mixed: `bir`'s audit lines
|
|
6
|
+
* (`[bir] <iso time> <event> …`), which belong in the log where `bir doctor`
|
|
7
|
+
* and a person look, and the sentences `bir` wrote for a person
|
|
8
|
+
* ("[bir] not signed in — run `bir login`"), which the model needs when the
|
|
9
|
+
* command failed. They are sorted line by line AS THEY ARRIVE: audit lines go
|
|
10
|
+
* straight on, and of the rest only the LAST so many characters are kept.
|
|
11
|
+
*
|
|
12
|
+
* WHY AS THEY ARRIVE, AND WHY THE LAST. Sorting after the exit, with the first
|
|
13
|
+
* 8 000 characters kept, lost exactly the line that mattered: with
|
|
14
|
+
* `BIR_VERBOSE=1` the audit lines filled the cap, and `bir`'s closing reason —
|
|
15
|
+
* the last thing it prints — never reached the model, which got "exited with
|
|
16
|
+
* code 1" and nothing to act on.
|
|
17
|
+
*
|
|
18
|
+
* Its own module so it can be tested: bir-scenario starts serving on import.
|
|
19
|
+
*/
|
|
20
|
+
/** `bir`'s own audit lines — for the log, not for the model. */
|
|
21
|
+
export const AUDIT_LINE = /^\[bir\] \d{4}-\d\d-\d\dT/;
|
|
22
|
+
export class StderrSorter {
|
|
23
|
+
opts;
|
|
24
|
+
partial = "";
|
|
25
|
+
/** Where the rest of a line cut for length goes, once its start was sorted. */
|
|
26
|
+
continuing;
|
|
27
|
+
kept = "";
|
|
28
|
+
forwarded = 0;
|
|
29
|
+
constructor(opts) {
|
|
30
|
+
this.opts = opts;
|
|
31
|
+
}
|
|
32
|
+
push(chunk) {
|
|
33
|
+
const lines = `${this.partial}${chunk}`.split("\n");
|
|
34
|
+
this.partial = lines.pop() ?? "";
|
|
35
|
+
for (const line of lines) {
|
|
36
|
+
this.line(line);
|
|
37
|
+
this.continuing = undefined;
|
|
38
|
+
}
|
|
39
|
+
// A "line" with no end in sight is sorted in pieces rather than held whole.
|
|
40
|
+
if (this.partial.length > this.opts.keepChars) {
|
|
41
|
+
this.line(this.partial);
|
|
42
|
+
this.partial = "";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Everything that was not an audit line (the last of it), once the stream ended. */
|
|
46
|
+
end() {
|
|
47
|
+
if (this.partial !== "")
|
|
48
|
+
this.line(this.partial);
|
|
49
|
+
this.partial = "";
|
|
50
|
+
this.continuing = undefined;
|
|
51
|
+
return this.kept;
|
|
52
|
+
}
|
|
53
|
+
line(raw) {
|
|
54
|
+
const line = raw.replace(/\r$/, "");
|
|
55
|
+
const kind = this.continuing ?? (AUDIT_LINE.test(line) ? "audit" : "kept");
|
|
56
|
+
this.continuing = kind;
|
|
57
|
+
if (line.trim() === "")
|
|
58
|
+
return;
|
|
59
|
+
if (kind === "audit") {
|
|
60
|
+
if (this.forwarded >= this.opts.forwardChars)
|
|
61
|
+
return;
|
|
62
|
+
this.forwarded += line.length + 1;
|
|
63
|
+
try {
|
|
64
|
+
this.opts.forward(line);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// The log is gone; nothing to do about that here.
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
this.kept += `${line}\n`;
|
|
72
|
+
if (this.kept.length > this.opts.keepChars)
|
|
73
|
+
this.kept = this.kept.slice(-this.opts.keepChars);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=scenario-stderr.js.map
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* service-read — what `bir scenario show|edits` and `bir investigate` share
|
|
3
|
+
* about asking the service and printing its answer, so a slow service, a
|
|
4
|
+
* signed-out machine or a large scenario never turns into a stall or a flood.
|
|
5
|
+
*
|
|
6
|
+
* WHY A BOUND OF THEIR OWN. The `service()` these commands are handed settles
|
|
7
|
+
* the service address with a `/health` probe, may refresh the session, and
|
|
8
|
+
* then makes its request; a service (or a corporate proxy in front of it) that
|
|
9
|
+
* accepts the connection and never answers held every one of those for as long
|
|
10
|
+
* as Node's fetch waits — measured at 120 s through the `bir` MCP server, which
|
|
11
|
+
* is 120 s of a turn stalled on a debugging tool. A read is safe to abandon:
|
|
12
|
+
* nothing was asked to change. So each one gets {@link readTimeoutMs} — a
|
|
13
|
+
* command that reads twice shares it between them ({@link ReadClock}) — and
|
|
14
|
+
* then an answer in words. Writes (`check`, `edit`, `undo`, `calc`) are not
|
|
15
|
+
* bounded here — abandoning one would leave "was it saved?" unanswerable — and
|
|
16
|
+
* the MCP server bounds the whole command instead.
|
|
17
|
+
*
|
|
18
|
+
* WHY COMPACT JSON UNDER THE MCP SERVER. The `bir` MCP server runs these
|
|
19
|
+
* commands with `BIR_MCP=1` and hands their `--json` output to the model.
|
|
20
|
+
* Indentation is for a person at a terminal; in a model's context it is a
|
|
21
|
+
* quarter of the tokens and none of the meaning.
|
|
22
|
+
*/
|
|
23
|
+
/** Default bound on one command's reads of the service. `BIR_READ_TIMEOUT_MS` overrides it. */
|
|
24
|
+
export declare const DEFAULT_READ_TIMEOUT_MS = 15000;
|
|
25
|
+
/**
|
|
26
|
+
* The most any timer here is set for from the environment. Node turns a delay
|
|
27
|
+
* past 2^31 - 1 ms into 1 ms, so a mistyped `BIR_READ_TIMEOUT_MS=3000000000`
|
|
28
|
+
* used to make every read give up at once; ten minutes is already far longer
|
|
29
|
+
* than anybody waits for a debugging tool.
|
|
30
|
+
*/
|
|
31
|
+
export declare const MAX_ENV_TIMEOUT_MS = 600000;
|
|
32
|
+
/**
|
|
33
|
+
* A number of milliseconds from the environment: positive and at most
|
|
34
|
+
* {@link MAX_ENV_TIMEOUT_MS}, or `fallback`. Out of range is a mistake, and
|
|
35
|
+
* the default is the value known to work.
|
|
36
|
+
*/
|
|
37
|
+
export declare function envTimeoutMs(name: string, fallback: number): number;
|
|
38
|
+
export declare function readTimeoutMs(): number;
|
|
39
|
+
/** True when the `bir` MCP server ran this command for a model (it sets `BIR_MCP=1`). */
|
|
40
|
+
export declare function underMcp(): boolean;
|
|
41
|
+
/** `--json` output: indented for a person, compact for a model. */
|
|
42
|
+
export declare function jsonText(value: unknown): string;
|
|
43
|
+
/**
|
|
44
|
+
* `pending`, or `{ error }` when it takes longer than `ms`. The request itself
|
|
45
|
+
* is not cancelled — `bir` exits as soon as the command returns, which is what
|
|
46
|
+
* ends it — and a late rejection is observed here, so it can never surface as
|
|
47
|
+
* an unhandled one. `statedMs` is the bound the answer names, when `ms` is
|
|
48
|
+
* only what was left of it ({@link ReadClock}).
|
|
49
|
+
*/
|
|
50
|
+
export declare function readWithin<T>(pending: Promise<T | {
|
|
51
|
+
error: string;
|
|
52
|
+
}>, ms?: number, statedMs?: number): Promise<T | {
|
|
53
|
+
error: string;
|
|
54
|
+
}>;
|
|
55
|
+
/**
|
|
56
|
+
* One command's reads of the service, sharing one bound. A command that reads
|
|
57
|
+
* twice — `scenario edits` on a `run_` id finds the scenario, then its
|
|
58
|
+
* history; `investigate` tries the matched run after a "no" — used to give
|
|
59
|
+
* each read the whole bound, so a service that answered the first slowly and
|
|
60
|
+
* held the second took twice as long as the `bir` MCP server allows a
|
|
61
|
+
* reading tool, and was killed there with none of the answer the command
|
|
62
|
+
* already had (the journal half of `investigate`, say). Sharing the bound
|
|
63
|
+
* keeps the whole command inside it, and the timeout still names the bound,
|
|
64
|
+
* not the scrap of it the last read had left.
|
|
65
|
+
*/
|
|
66
|
+
export declare class ReadClock {
|
|
67
|
+
readonly totalMs: number;
|
|
68
|
+
private readonly deadline;
|
|
69
|
+
constructor(totalMs?: number);
|
|
70
|
+
/** What is left of the bound, in ms (0 once it is spent). */
|
|
71
|
+
remaining(): number;
|
|
72
|
+
/** {@link readWithin}, with what is left of the command's bound. */
|
|
73
|
+
within<T>(pending: Promise<T | {
|
|
74
|
+
error: string;
|
|
75
|
+
}>): Promise<T | {
|
|
76
|
+
error: string;
|
|
77
|
+
}>;
|
|
78
|
+
}
|
|
79
|
+
/** Whether a `{ error }` means the service could not be reached at all, as opposed to answering. */
|
|
80
|
+
export declare function unreachable(error: string): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* A failure to reach the service, in words a person (or a model) can act on.
|
|
83
|
+
* The transport's own words — "fetch failed", "This operation was aborted" —
|
|
84
|
+
* say nothing about what to do, so they are replaced; the sentences `bir`
|
|
85
|
+
* itself wrote ("not signed in — run `bir login`") are already the answer.
|
|
86
|
+
*/
|
|
87
|
+
export declare function serviceProblem(error: string): string;
|
|
88
|
+
//# sourceMappingURL=service-read.d.ts.map
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* service-read — what `bir scenario show|edits` and `bir investigate` share
|
|
3
|
+
* about asking the service and printing its answer, so a slow service, a
|
|
4
|
+
* signed-out machine or a large scenario never turns into a stall or a flood.
|
|
5
|
+
*
|
|
6
|
+
* WHY A BOUND OF THEIR OWN. The `service()` these commands are handed settles
|
|
7
|
+
* the service address with a `/health` probe, may refresh the session, and
|
|
8
|
+
* then makes its request; a service (or a corporate proxy in front of it) that
|
|
9
|
+
* accepts the connection and never answers held every one of those for as long
|
|
10
|
+
* as Node's fetch waits — measured at 120 s through the `bir` MCP server, which
|
|
11
|
+
* is 120 s of a turn stalled on a debugging tool. A read is safe to abandon:
|
|
12
|
+
* nothing was asked to change. So each one gets {@link readTimeoutMs} — a
|
|
13
|
+
* command that reads twice shares it between them ({@link ReadClock}) — and
|
|
14
|
+
* then an answer in words. Writes (`check`, `edit`, `undo`, `calc`) are not
|
|
15
|
+
* bounded here — abandoning one would leave "was it saved?" unanswerable — and
|
|
16
|
+
* the MCP server bounds the whole command instead.
|
|
17
|
+
*
|
|
18
|
+
* WHY COMPACT JSON UNDER THE MCP SERVER. The `bir` MCP server runs these
|
|
19
|
+
* commands with `BIR_MCP=1` and hands their `--json` output to the model.
|
|
20
|
+
* Indentation is for a person at a terminal; in a model's context it is a
|
|
21
|
+
* quarter of the tokens and none of the meaning.
|
|
22
|
+
*/
|
|
23
|
+
/** Default bound on one command's reads of the service. `BIR_READ_TIMEOUT_MS` overrides it. */
|
|
24
|
+
export const DEFAULT_READ_TIMEOUT_MS = 15_000;
|
|
25
|
+
/**
|
|
26
|
+
* The most any timer here is set for from the environment. Node turns a delay
|
|
27
|
+
* past 2^31 - 1 ms into 1 ms, so a mistyped `BIR_READ_TIMEOUT_MS=3000000000`
|
|
28
|
+
* used to make every read give up at once; ten minutes is already far longer
|
|
29
|
+
* than anybody waits for a debugging tool.
|
|
30
|
+
*/
|
|
31
|
+
export const MAX_ENV_TIMEOUT_MS = 600_000;
|
|
32
|
+
/**
|
|
33
|
+
* A number of milliseconds from the environment: positive and at most
|
|
34
|
+
* {@link MAX_ENV_TIMEOUT_MS}, or `fallback`. Out of range is a mistake, and
|
|
35
|
+
* the default is the value known to work.
|
|
36
|
+
*/
|
|
37
|
+
export function envTimeoutMs(name, fallback) {
|
|
38
|
+
const n = Number(process.env[name]);
|
|
39
|
+
return Number.isFinite(n) && n > 0 && n <= MAX_ENV_TIMEOUT_MS ? n : fallback;
|
|
40
|
+
}
|
|
41
|
+
export function readTimeoutMs() {
|
|
42
|
+
return envTimeoutMs("BIR_READ_TIMEOUT_MS", DEFAULT_READ_TIMEOUT_MS);
|
|
43
|
+
}
|
|
44
|
+
/** True when the `bir` MCP server ran this command for a model (it sets `BIR_MCP=1`). */
|
|
45
|
+
export function underMcp() {
|
|
46
|
+
return process.env.BIR_MCP === "1";
|
|
47
|
+
}
|
|
48
|
+
/** `--json` output: indented for a person, compact for a model. */
|
|
49
|
+
export function jsonText(value) {
|
|
50
|
+
return underMcp() ? JSON.stringify(value) : JSON.stringify(value, null, 2);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* `pending`, or `{ error }` when it takes longer than `ms`. The request itself
|
|
54
|
+
* is not cancelled — `bir` exits as soon as the command returns, which is what
|
|
55
|
+
* ends it — and a late rejection is observed here, so it can never surface as
|
|
56
|
+
* an unhandled one. `statedMs` is the bound the answer names, when `ms` is
|
|
57
|
+
* only what was left of it ({@link ReadClock}).
|
|
58
|
+
*/
|
|
59
|
+
export async function readWithin(pending, ms = readTimeoutMs(), statedMs = ms) {
|
|
60
|
+
pending.catch(() => undefined);
|
|
61
|
+
let timer;
|
|
62
|
+
const late = new Promise((resolve) => {
|
|
63
|
+
timer = setTimeout(() => resolve({ error: `the BaseIn service did not answer within ${seconds(statedMs)} s` }), Math.max(0, ms));
|
|
64
|
+
timer.unref?.();
|
|
65
|
+
});
|
|
66
|
+
try {
|
|
67
|
+
return await Promise.race([pending, late]);
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
if (timer)
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* One command's reads of the service, sharing one bound. A command that reads
|
|
79
|
+
* twice — `scenario edits` on a `run_` id finds the scenario, then its
|
|
80
|
+
* history; `investigate` tries the matched run after a "no" — used to give
|
|
81
|
+
* each read the whole bound, so a service that answered the first slowly and
|
|
82
|
+
* held the second took twice as long as the `bir` MCP server allows a
|
|
83
|
+
* reading tool, and was killed there with none of the answer the command
|
|
84
|
+
* already had (the journal half of `investigate`, say). Sharing the bound
|
|
85
|
+
* keeps the whole command inside it, and the timeout still names the bound,
|
|
86
|
+
* not the scrap of it the last read had left.
|
|
87
|
+
*/
|
|
88
|
+
export class ReadClock {
|
|
89
|
+
totalMs;
|
|
90
|
+
deadline;
|
|
91
|
+
constructor(totalMs = readTimeoutMs()) {
|
|
92
|
+
this.totalMs = totalMs;
|
|
93
|
+
this.deadline = Date.now() + totalMs;
|
|
94
|
+
}
|
|
95
|
+
/** What is left of the bound, in ms (0 once it is spent). */
|
|
96
|
+
remaining() {
|
|
97
|
+
return Math.max(0, this.deadline - Date.now());
|
|
98
|
+
}
|
|
99
|
+
/** {@link readWithin}, with what is left of the command's bound. */
|
|
100
|
+
within(pending) {
|
|
101
|
+
return readWithin(pending, this.remaining(), this.totalMs);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/** Whether a `{ error }` means the service could not be reached at all, as opposed to answering. */
|
|
105
|
+
export function unreachable(error) {
|
|
106
|
+
return /did not answer within|fetch failed|ECONN|ENOTFOUND|EAI_AGAIN|ETIMEDOUT|EHOSTUNREACH|ENETUNREACH|socket|network|aborted|timed out|UND_ERR/i.test(error);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* A failure to reach the service, in words a person (or a model) can act on.
|
|
110
|
+
* The transport's own words — "fetch failed", "This operation was aborted" —
|
|
111
|
+
* say nothing about what to do, so they are replaced; the sentences `bir`
|
|
112
|
+
* itself wrote ("not signed in — run `bir login`") are already the answer.
|
|
113
|
+
*/
|
|
114
|
+
export function serviceProblem(error) {
|
|
115
|
+
if (/BIR_AUTH_URL is not set/.test(error)) {
|
|
116
|
+
return "no BaseIn service is configured on this machine — run `bir setup` or `bir login`";
|
|
117
|
+
}
|
|
118
|
+
if (/did not answer within/.test(error))
|
|
119
|
+
return error;
|
|
120
|
+
if (unreachable(error))
|
|
121
|
+
return "the BaseIn service could not be reached — check the network, or try again later";
|
|
122
|
+
return error;
|
|
123
|
+
}
|
|
124
|
+
function seconds(ms) {
|
|
125
|
+
const s = ms / 1000;
|
|
126
|
+
return Number.isInteger(s) ? String(s) : s.toFixed(1);
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=service-read.js.map
|
package/dist/bin/setup.d.ts
CHANGED
|
@@ -43,7 +43,26 @@ export interface SetupArgs {
|
|
|
43
43
|
daemon: boolean;
|
|
44
44
|
/** `--allow-servers a,b`: the servers replay may call unattended, stored for this project. */
|
|
45
45
|
allowServers?: string;
|
|
46
|
+
/**
|
|
47
|
+
* `--port` was given: the port is the person's, and is never moved. Without
|
|
48
|
+
* it a recorded port this machine no longer lets the recorder bind is
|
|
49
|
+
* replaced (see {@link runSetup}, step 4).
|
|
50
|
+
*/
|
|
51
|
+
portExplicit?: boolean;
|
|
46
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* The project directory every later step keys by: the PHYSICAL path on POSIX.
|
|
55
|
+
*
|
|
56
|
+
* After `chdir`, `process.cwd()` — which install keys the sidecar by and the
|
|
57
|
+
* recorder keys its discovery file by — is the resolved path. Keying setup's own
|
|
58
|
+
* steps by the logical one (a symlinked ~/code, macOS's /tmp → /private/tmp)
|
|
59
|
+
* made it wait 15 s for a discovery file that was written under the other
|
|
60
|
+
* spelling, report "the recorder did not start" while it ran, and store
|
|
61
|
+
* `--allow-servers` where the recorder never looks. Windows keeps the path as
|
|
62
|
+
* given: its cwd is not resolved through junctions, and resolving would only
|
|
63
|
+
* change the drive letter's case.
|
|
64
|
+
*/
|
|
65
|
+
export declare function canonicalProject(dir: string, platform?: NodeJS.Platform): string;
|
|
47
66
|
export interface SetupDeps {
|
|
48
67
|
out: (line?: string) => void;
|
|
49
68
|
err: (line: string) => void;
|
package/dist/bin/setup.js
CHANGED
|
@@ -28,16 +28,41 @@
|
|
|
28
28
|
* `cd <project>` and `bir setup`. The second needs no token; the session is
|
|
29
29
|
* cached.
|
|
30
30
|
*/
|
|
31
|
-
import { existsSync, statSync } from "node:fs";
|
|
31
|
+
import { existsSync, realpathSync, statSync } from "node:fs";
|
|
32
32
|
import { homedir } from "node:os";
|
|
33
33
|
import { delimiter, join, parse as parsePath, resolve as resolvePath } from "node:path";
|
|
34
|
+
import { pidAlive, readDiscovery } from "../control/discovery.js";
|
|
34
35
|
import { AUTH_URL_HINT, DeviceFlowAborted, DeviceFlowUnsupported, authenticate, describeAuthService, deviceLogin, loadStoredConfig, normalizeAuthUrl, rememberAuthUrl, tokenLogin, } from "../auth/client.js";
|
|
35
|
-
import { activeSessions, ensureDaemon, SETUP_TOKEN_ENV, stopDaemon, } from "../control/daemon.js";
|
|
36
|
+
import { activeSessions, compareVersions, ensureDaemon, SETUP_TOKEN_ENV, stopDaemon, } from "../control/daemon.js";
|
|
36
37
|
import { normalizePath } from "../control/paths.js";
|
|
37
|
-
import { isWrapped, projectRecord, readSidecar, SCENARIO_SERVER_KEY, setProjectRecord, writeSidecar, allocateFreeProjectPort, } from "../config/generate.js";
|
|
38
|
+
import { allocateReplacementPort, isScenarioServer, isWrapped, LOOPBACK_NO_PROXY, probeFreePort, proxyConfigured, projectRecord, readSidecar, SCENARIO_SERVER_KEY, setProjectRecord, writeSidecar, allocateFreeProjectPort, } from "../config/generate.js";
|
|
38
39
|
import { resolveServers } from "../config/resolve.js";
|
|
39
40
|
import { errText } from "../util/log.js";
|
|
41
|
+
import { offSource } from "../util/off-switch.js";
|
|
40
42
|
import { packageVersion } from "../util/version.js";
|
|
43
|
+
/**
|
|
44
|
+
* The project directory every later step keys by: the PHYSICAL path on POSIX.
|
|
45
|
+
*
|
|
46
|
+
* After `chdir`, `process.cwd()` — which install keys the sidecar by and the
|
|
47
|
+
* recorder keys its discovery file by — is the resolved path. Keying setup's own
|
|
48
|
+
* steps by the logical one (a symlinked ~/code, macOS's /tmp → /private/tmp)
|
|
49
|
+
* made it wait 15 s for a discovery file that was written under the other
|
|
50
|
+
* spelling, report "the recorder did not start" while it ran, and store
|
|
51
|
+
* `--allow-servers` where the recorder never looks. Windows keeps the path as
|
|
52
|
+
* given: its cwd is not resolved through junctions, and resolving would only
|
|
53
|
+
* change the drive letter's case.
|
|
54
|
+
*/
|
|
55
|
+
export function canonicalProject(dir, platform = process.platform) {
|
|
56
|
+
const logical = resolvePath(dir);
|
|
57
|
+
if (platform === "win32")
|
|
58
|
+
return logical;
|
|
59
|
+
try {
|
|
60
|
+
return realpathSync(logical);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return logical;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
41
66
|
/** True when `claude` (Claude Code) is on PATH or in its native install location. */
|
|
42
67
|
export function claudeOnPath(env = process.env) {
|
|
43
68
|
const names = process.platform === "win32" ? ["claude.exe", "claude.cmd", "claude"] : ["claude"];
|
|
@@ -93,7 +118,18 @@ export async function runSetup(args, deps) {
|
|
|
93
118
|
const problem = await describeAuthService(baseUrl);
|
|
94
119
|
if (problem) {
|
|
95
120
|
err(`${baseUrl} is not a BaseIn service: ${problem}.`);
|
|
96
|
-
|
|
121
|
+
if (/unreachable/.test(problem) && proxyConfigured(env) && !process.execArgv.includes("--use-env-proxy")) {
|
|
122
|
+
// Behind a proxy-only network the likeliest cause, and not the address:
|
|
123
|
+
// Node's fetch ignores HTTPS_PROXY unless it is started with this flag.
|
|
124
|
+
// NO_PROXY too: with the flag, the runner's own calls to 127.0.0.1 would
|
|
125
|
+
// go to the proxy as well, and install leaves the flag out without it.
|
|
126
|
+
err("HTTPS_PROXY/HTTP_PROXY is set, but this node does not use it without --use-env-proxy: set " +
|
|
127
|
+
`NO_PROXY=${LOOPBACK_NO_PROXY} (plus your own entries) and run ` +
|
|
128
|
+
"`node --use-env-proxy <path to bir.js> setup …` (the install line does this for you).");
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
err(AUTH_URL_HINT);
|
|
132
|
+
}
|
|
97
133
|
return 1;
|
|
98
134
|
}
|
|
99
135
|
const envUrl = (env.BIR_AUTH_URL ?? "").replace(/\/+$/, "");
|
|
@@ -107,7 +143,7 @@ export async function runSetup(args, deps) {
|
|
|
107
143
|
err(`BIR_AUTH_URL in this environment is ${envUrl}, but ${args.authUrl ? "this sets up" : "the stored address is"} ${args.authUrl ? baseUrl : storedUrl}.`);
|
|
108
144
|
err("Remove that variable (check your shell profile), or the recorder will not record.");
|
|
109
145
|
}
|
|
110
|
-
const project =
|
|
146
|
+
const project = canonicalProject(args.project ?? process.cwd());
|
|
111
147
|
if (!existsSync(project) || !statSync(project).isDirectory()) {
|
|
112
148
|
err(`Project directory not found: ${project}`);
|
|
113
149
|
return 1;
|
|
@@ -185,6 +221,33 @@ export async function runSetup(args, deps) {
|
|
|
185
221
|
}
|
|
186
222
|
// ── 4. the project ───────────────────────────────────────────────────────
|
|
187
223
|
chdir(project);
|
|
224
|
+
if (!args.portExplicit) {
|
|
225
|
+
// A port chosen at the first install is kept on later ones — the hooks name
|
|
226
|
+
// it — but this machine may no longer let it be bound: Hyper-V, WSL and
|
|
227
|
+
// Docker reserve blocks inside the dynamic range, and those move across
|
|
228
|
+
// reboots. A recorder told to use it exits, and every hook of every session
|
|
229
|
+
// then fails. Setup is the upgrade and repair path, so it moves the project
|
|
230
|
+
// to a free port and lets install rewrite the hooks and entries with it —
|
|
231
|
+
// unless the port is busy because THIS project's recorder is on it.
|
|
232
|
+
const sidecar = readSidecar();
|
|
233
|
+
const recorded = projectRecord(sidecar, project);
|
|
234
|
+
if (recorded) {
|
|
235
|
+
// Age does not matter here, only a live pid: a recorder whose file went
|
|
236
|
+
// stale over a long sleep (or an older runner's, whose heartbeat is
|
|
237
|
+
// slower) still holds the port, and moving away from it would leave
|
|
238
|
+
// two recorders and hooks pointing at the new one.
|
|
239
|
+
const ours = readDiscovery(project, { ignoreAge: true });
|
|
240
|
+
const heldByUs = Boolean(ours && pidAlive(ours.pid) && ours.url.endsWith(`:${recorded.port}`));
|
|
241
|
+
if (!heldByUs && !(await probeFreePort(recorded.port))) {
|
|
242
|
+
const next = await allocateReplacementPort(sidecar, project, recorded.port);
|
|
243
|
+
if (next !== undefined) {
|
|
244
|
+
setProjectRecord(sidecar, project, { port: next });
|
|
245
|
+
writeSidecar(sidecar);
|
|
246
|
+
out(` port ${recorded.port} is not available on this machine; this project moves to ${next}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
188
251
|
if (args.allowServers !== undefined) {
|
|
189
252
|
// Stored before install so the recorder started below reads it at start.
|
|
190
253
|
const sidecar = readSidecar();
|
|
@@ -202,11 +265,17 @@ export async function runSetup(args, deps) {
|
|
|
202
265
|
if (args.daemon) {
|
|
203
266
|
out("==> Starting the recorder in the background");
|
|
204
267
|
try {
|
|
205
|
-
|
|
268
|
+
// probeIdle: a recorder too old to say whether its "open" turn is real
|
|
269
|
+
// (0.2.12 never closes an interrupted one) is replaced when two /health
|
|
270
|
+
// probes a few seconds apart show it idle (control/daemon.ts). Setup has
|
|
271
|
+
// the time; the SessionStart hook does not ask for it.
|
|
272
|
+
const ensureOpts = {
|
|
206
273
|
hooksScript: deps.hooksScript,
|
|
207
274
|
env,
|
|
208
275
|
restartOnSkew: true,
|
|
209
|
-
|
|
276
|
+
probeIdle: true,
|
|
277
|
+
};
|
|
278
|
+
ensured = await ensureDaemon(project, ensureOpts);
|
|
210
279
|
if (!ensured.started &&
|
|
211
280
|
ensured.status.health.recording === false &&
|
|
212
281
|
activeSessions(ensured.status.health) === 0) {
|
|
@@ -219,6 +288,36 @@ export async function runSetup(args, deps) {
|
|
|
219
288
|
ensured = await ensureDaemon(project, { hooksScript: deps.hooksScript, env });
|
|
220
289
|
ensured = { ...ensured, restarted: true };
|
|
221
290
|
}
|
|
291
|
+
const port = projectRecord(readSidecar(), project)?.port;
|
|
292
|
+
if (port !== undefined &&
|
|
293
|
+
!ensured.status.info.url.endsWith(`:${port}`) &&
|
|
294
|
+
activeSessions(ensured.status.health) === 0) {
|
|
295
|
+
// A recorder still on the port this project had before (moved above,
|
|
296
|
+
// or by `--port`): the hooks now post to the new one, so it would
|
|
297
|
+
// receive nothing. Nothing is mid-run; replace it.
|
|
298
|
+
out(` the running recorder is on ${ensured.status.info.url}, not port ${port}; replacing it`);
|
|
299
|
+
await stopDaemon(project);
|
|
300
|
+
ensured = await ensureDaemon(project, { hooksScript: deps.hooksScript, env });
|
|
301
|
+
ensured = { ...ensured, restarted: true };
|
|
302
|
+
}
|
|
303
|
+
// The replay switch is read once, when a recorder starts: one started
|
|
304
|
+
// before this setup turned replay off (--no-replay) or on would go on
|
|
305
|
+
// doing what it did. `source: "env"` is BIR_REPLAY where it started,
|
|
306
|
+
// which outranks the switch — a restart from here would not carry it.
|
|
307
|
+
const wantReplay = projectRecord(readSidecar(), project)?.replay?.enabled !== false;
|
|
308
|
+
const running = ensured.status.health.replay;
|
|
309
|
+
if (typeof running?.enabled === "boolean" && running.enabled !== wantReplay && running.source !== "env") {
|
|
310
|
+
if (activeSessions(ensured.status.health) === 0) {
|
|
311
|
+
out(` the running recorder has replay ${running.enabled ? "on" : "off"}; replacing it`);
|
|
312
|
+
await stopDaemon(project);
|
|
313
|
+
ensured = await ensureDaemon(project, { hooksScript: deps.hooksScript, env });
|
|
314
|
+
ensured = { ...ensured, restarted: true };
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
out(` the running recorder keeps replay ${running.enabled ? "on" : "off"} for the session open now; ` +
|
|
318
|
+
"`bir up --restart` once it ends applies the change");
|
|
319
|
+
}
|
|
320
|
+
}
|
|
222
321
|
}
|
|
223
322
|
catch (e) {
|
|
224
323
|
err(`the recorder did not start: ${errText(e)}`);
|
|
@@ -227,40 +326,67 @@ export async function runSetup(args, deps) {
|
|
|
227
326
|
}
|
|
228
327
|
}
|
|
229
328
|
// ── 6. the summary ───────────────────────────────────────────────────────
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
329
|
+
const live = resolveServers(project).filter((s) => s.name !== SCENARIO_SERVER_KEY);
|
|
330
|
+
const wrapped = live.filter((s) => isWrapped(s.config)).map((s) => s.name);
|
|
331
|
+
// Servers install left as Claude Code runs them (not approved there yet, a
|
|
332
|
+
// remote it signs in itself, …) are still here: "no MCP servers" would lie.
|
|
333
|
+
const unwrapped = live.filter((s) => !isWrapped(s.config)).map((s) => s.name);
|
|
233
334
|
const health = ensured?.status.health;
|
|
234
335
|
const replay = health?.replay;
|
|
235
336
|
const recording = health ? health.recording !== false : undefined;
|
|
236
337
|
const policy = projectRecord(readSidecar(), project)?.replay;
|
|
237
338
|
const allow = replay?.allowServers ?? policy?.allowServers ?? null;
|
|
339
|
+
// The stored switch, which every recorder here reads at start — unless the
|
|
340
|
+
// one that answered was started with BIR_REPLAY, which outranks it.
|
|
341
|
+
const replayOn = replay?.source === "env" ? replay.enabled !== false : policy?.enabled !== false;
|
|
238
342
|
out();
|
|
239
|
-
out(`Done. Recording${args.replay ? " and replay are" : " is"} on for ${project}`);
|
|
343
|
+
out(`Done. Recording${args.replay && replayOn ? " and replay are" : " is"} on for ${project}`);
|
|
344
|
+
// The kill switch outranks all of it, and setup leaves it as the person set
|
|
345
|
+
// it: turning the runner back on is theirs to decide, not an upgrade's.
|
|
346
|
+
const off = offSource(project);
|
|
347
|
+
if (off) {
|
|
348
|
+
out(` !! but the runner is OFF (${off === "env" ? "BIR_OFF=1 in this environment" : off === "global" ? "`bir off`, every project" : "`bir off --here`"}): nothing is recorded or replayed until \`bir on${off === "project" ? " --here" : ""}\``);
|
|
349
|
+
}
|
|
240
350
|
out(` account ${session.user.email} (${baseUrl})`);
|
|
241
|
-
out(` wrapped ${wrapped.length ? wrapped.join(", ") : "(no MCP servers here)"}`);
|
|
351
|
+
out(` wrapped ${wrapped.length ? wrapped.join(", ") : unwrapped.length ? `(none — ${unwrapped.join(", ")} left as they are, see above)` : "(no MCP servers here)"}`);
|
|
242
352
|
if (!args.replay) {
|
|
243
|
-
|
|
353
|
+
const scenarioLeft = resolveServers(project).some((s) => s.name === SCENARIO_SERVER_KEY && isScenarioServer(s.config));
|
|
354
|
+
out(replayOn
|
|
355
|
+
? " replay ON despite --no-replay: BIR_REPLAY is set where the recorder started"
|
|
356
|
+
: ` replay off (--no-replay) — \`bir replay on\` turns it on${scenarioLeft ? "; the scenario server an earlier setup added stays (`bir uninstall --replay` removes it)" : ""}`);
|
|
357
|
+
}
|
|
358
|
+
else if (!replayOn) {
|
|
359
|
+
out(replay?.source === "env"
|
|
360
|
+
? " replay off — BIR_REPLAY=0 where the recorder started"
|
|
361
|
+
: " replay off here (`bir replay off`) — `bir replay on` turns it on");
|
|
244
362
|
}
|
|
245
363
|
else if (wrapped.length === 0) {
|
|
246
|
-
out(
|
|
364
|
+
out(unwrapped.length
|
|
365
|
+
? " replay steer mode only — none of this project's MCP servers is wrapped (why is above); `bir setup` again once one can be, for direct replay"
|
|
366
|
+
: " replay steer mode only — no MCP servers wrapped here; add a .mcp.json and run `bir setup` again for direct replay");
|
|
247
367
|
}
|
|
248
368
|
else {
|
|
249
|
-
|
|
250
|
-
out(` replay ${on ? "on" : "off"} — direct execution allowed for: ${allow?.length ? allow.join(", ") : `all wrapped (narrow it with \`bir replay allow ${wrapped[0]}\`)`}`);
|
|
369
|
+
out(` replay on — direct execution allowed for: ${allow?.length ? allow.join(", ") : `all wrapped (narrow it with \`bir replay allow ${wrapped[0]}\`)`}`);
|
|
251
370
|
}
|
|
252
371
|
if (ensured) {
|
|
253
372
|
const { info, health: h } = ensured.status;
|
|
254
373
|
const theirs = String(h.hooksVersion ?? info.version ?? "");
|
|
255
374
|
out(` recorder ${ensured.started ? (ensured.restarted ? "restarted" : "started") : "already running"} in the background (pid ${info.pid})` +
|
|
256
375
|
" — it starts itself with every Claude Code session");
|
|
257
|
-
|
|
258
|
-
|
|
376
|
+
// Only for an OLDER recorder: a newer one is kept on purpose
|
|
377
|
+
// (daemon.newer_kept) and nothing about it waits on a restart.
|
|
378
|
+
if (theirs && compareVersions(theirs, packageVersion()) === -1) {
|
|
379
|
+
// Said as it is: nothing replaces it by itself while that session runs —
|
|
380
|
+
// the next session's start keeps it too, as long as it reports a turn
|
|
381
|
+
// open — and until it is replaced it behaves as ${theirs} does (0.2.12:
|
|
382
|
+
// approves every wrapped MCP call, ignores `bir off`).
|
|
383
|
+
out(` it is still ${theirs}, serving a Claude Code session that is mid-turn; it keeps running as ${theirs} ` +
|
|
384
|
+
`until \`bir up --restart\` (run it once that session ends) replaces it with ${packageVersion()}`);
|
|
259
385
|
}
|
|
260
386
|
if (info.logFile)
|
|
261
387
|
out(` log ${info.logFile}`);
|
|
262
388
|
if (recording === false) {
|
|
263
|
-
out(" !! the recorder has no session to record with — run `bir login
|
|
389
|
+
out(" !! the recorder has no session to record with — run `bir login`; it starts recording by itself after that");
|
|
264
390
|
}
|
|
265
391
|
}
|
|
266
392
|
else {
|
|
@@ -280,7 +406,7 @@ export async function runSetup(args, deps) {
|
|
|
280
406
|
out(' first time in this folder: say Yes to "trust this folder"');
|
|
281
407
|
out(" just installed Claude Code? it asks you to sign in to Claude (it needs a Claude subscription)");
|
|
282
408
|
out(` give it a task that takes 4+ tool calls — it appears ${console_ ? `at ${console_}/recordings` : "in the console's Recordings"} within a minute of finishing`);
|
|
283
|
-
out("Any time: bir doctor bir investigate bir
|
|
409
|
+
out("Any time: bir doctor bir investigate bir off (out of the way, at once) bir uninstall (undo)");
|
|
284
410
|
return recording === false ? 1 : 0;
|
|
285
411
|
}
|
|
286
412
|
//# sourceMappingURL=setup.js.map
|