@the-librarian/cli 1.0.0-rc.21

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 (155) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +94 -0
  3. package/dist/bin.d.ts +3 -0
  4. package/dist/bin.d.ts.map +1 -0
  5. package/dist/bin.js +15 -0
  6. package/dist/bin.js.map +1 -0
  7. package/dist/commands/install.d.ts +28 -0
  8. package/dist/commands/install.d.ts.map +1 -0
  9. package/dist/commands/install.js +204 -0
  10. package/dist/commands/install.js.map +1 -0
  11. package/dist/commands/shared.d.ts +16 -0
  12. package/dist/commands/shared.d.ts.map +1 -0
  13. package/dist/commands/shared.js +30 -0
  14. package/dist/commands/shared.js.map +1 -0
  15. package/dist/commands/uninstall.d.ts +18 -0
  16. package/dist/commands/uninstall.d.ts.map +1 -0
  17. package/dist/commands/uninstall.js +67 -0
  18. package/dist/commands/uninstall.js.map +1 -0
  19. package/dist/commands/update.d.ts +21 -0
  20. package/dist/commands/update.d.ts.map +1 -0
  21. package/dist/commands/update.js +75 -0
  22. package/dist/commands/update.js.map +1 -0
  23. package/dist/config.d.ts +40 -0
  24. package/dist/config.d.ts.map +1 -0
  25. package/dist/config.js +65 -0
  26. package/dist/config.js.map +1 -0
  27. package/dist/doctor.d.ts +23 -0
  28. package/dist/doctor.d.ts.map +1 -0
  29. package/dist/doctor.js +81 -0
  30. package/dist/doctor.js.map +1 -0
  31. package/dist/env.d.ts +39 -0
  32. package/dist/env.d.ts.map +1 -0
  33. package/dist/env.js +208 -0
  34. package/dist/env.js.map +1 -0
  35. package/dist/exec.d.ts +37 -0
  36. package/dist/exec.d.ts.map +1 -0
  37. package/dist/exec.js +81 -0
  38. package/dist/exec.js.map +1 -0
  39. package/dist/harnesses/claude.d.ts +3 -0
  40. package/dist/harnesses/claude.d.ts.map +1 -0
  41. package/dist/harnesses/claude.js +88 -0
  42. package/dist/harnesses/claude.js.map +1 -0
  43. package/dist/harnesses/cli.d.ts +4 -0
  44. package/dist/harnesses/cli.d.ts.map +1 -0
  45. package/dist/harnesses/cli.js +16 -0
  46. package/dist/harnesses/cli.js.map +1 -0
  47. package/dist/harnesses/codex.d.ts +3 -0
  48. package/dist/harnesses/codex.d.ts.map +1 -0
  49. package/dist/harnesses/codex.js +183 -0
  50. package/dist/harnesses/codex.js.map +1 -0
  51. package/dist/harnesses/hermes.d.ts +16 -0
  52. package/dist/harnesses/hermes.d.ts.map +1 -0
  53. package/dist/harnesses/hermes.js +158 -0
  54. package/dist/harnesses/hermes.js.map +1 -0
  55. package/dist/harnesses/index.d.ts +12 -0
  56. package/dist/harnesses/index.d.ts.map +1 -0
  57. package/dist/harnesses/index.js +29 -0
  58. package/dist/harnesses/index.js.map +1 -0
  59. package/dist/harnesses/opencode.d.ts +3 -0
  60. package/dist/harnesses/opencode.d.ts.map +1 -0
  61. package/dist/harnesses/opencode.js +141 -0
  62. package/dist/harnesses/opencode.js.map +1 -0
  63. package/dist/harnesses/pi.d.ts +3 -0
  64. package/dist/harnesses/pi.d.ts.map +1 -0
  65. package/dist/harnesses/pi.js +72 -0
  66. package/dist/harnesses/pi.js.map +1 -0
  67. package/dist/harnesses/types.d.ts +48 -0
  68. package/dist/harnesses/types.d.ts.map +1 -0
  69. package/dist/harnesses/types.js +22 -0
  70. package/dist/harnesses/types.js.map +1 -0
  71. package/dist/index.d.ts +11 -0
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +15 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/machine.d.ts +10 -0
  76. package/dist/machine.d.ts.map +1 -0
  77. package/dist/machine.js +39 -0
  78. package/dist/machine.js.map +1 -0
  79. package/dist/parse-args.d.ts +12 -0
  80. package/dist/parse-args.d.ts.map +1 -0
  81. package/dist/parse-args.js +54 -0
  82. package/dist/parse-args.js.map +1 -0
  83. package/dist/paths.d.ts +32 -0
  84. package/dist/paths.d.ts.map +1 -0
  85. package/dist/paths.js +75 -0
  86. package/dist/paths.js.map +1 -0
  87. package/dist/prompt.d.ts +53 -0
  88. package/dist/prompt.d.ts.map +1 -0
  89. package/dist/prompt.js +165 -0
  90. package/dist/prompt.js.map +1 -0
  91. package/dist/runtime.d.ts +45 -0
  92. package/dist/runtime.d.ts.map +1 -0
  93. package/dist/runtime.js +370 -0
  94. package/dist/runtime.js.map +1 -0
  95. package/dist/semver.d.ts +13 -0
  96. package/dist/semver.d.ts.map +1 -0
  97. package/dist/semver.js +98 -0
  98. package/dist/semver.js.map +1 -0
  99. package/dist/server/admin.d.ts +31 -0
  100. package/dist/server/admin.d.ts.map +1 -0
  101. package/dist/server/admin.js +110 -0
  102. package/dist/server/admin.js.map +1 -0
  103. package/dist/server/boot.d.ts +51 -0
  104. package/dist/server/boot.d.ts.map +1 -0
  105. package/dist/server/boot.js +233 -0
  106. package/dist/server/boot.js.map +1 -0
  107. package/dist/server/deploy-state.d.ts +33 -0
  108. package/dist/server/deploy-state.d.ts.map +1 -0
  109. package/dist/server/deploy-state.js +79 -0
  110. package/dist/server/deploy-state.js.map +1 -0
  111. package/dist/server/docker.d.ts +37 -0
  112. package/dist/server/docker.d.ts.map +1 -0
  113. package/dist/server/docker.js +138 -0
  114. package/dist/server/docker.js.map +1 -0
  115. package/dist/server/down.d.ts +19 -0
  116. package/dist/server/down.d.ts.map +1 -0
  117. package/dist/server/down.js +62 -0
  118. package/dist/server/down.js.map +1 -0
  119. package/dist/server/index.d.ts +8 -0
  120. package/dist/server/index.d.ts.map +1 -0
  121. package/dist/server/index.js +48 -0
  122. package/dist/server/index.js.map +1 -0
  123. package/dist/server/logs.d.ts +50 -0
  124. package/dist/server/logs.d.ts.map +1 -0
  125. package/dist/server/logs.js +162 -0
  126. package/dist/server/logs.js.map +1 -0
  127. package/dist/server/preflight.d.ts +24 -0
  128. package/dist/server/preflight.d.ts.map +1 -0
  129. package/dist/server/preflight.js +58 -0
  130. package/dist/server/preflight.js.map +1 -0
  131. package/dist/server/redact.d.ts +2 -0
  132. package/dist/server/redact.d.ts.map +1 -0
  133. package/dist/server/redact.js +26 -0
  134. package/dist/server/redact.js.map +1 -0
  135. package/dist/server/status.d.ts +20 -0
  136. package/dist/server/status.d.ts.map +1 -0
  137. package/dist/server/status.js +103 -0
  138. package/dist/server/status.js.map +1 -0
  139. package/dist/server/up.d.ts +211 -0
  140. package/dist/server/up.d.ts.map +1 -0
  141. package/dist/server/up.js +625 -0
  142. package/dist/server/up.js.map +1 -0
  143. package/dist/server/update.d.ts +35 -0
  144. package/dist/server/update.d.ts.map +1 -0
  145. package/dist/server/update.js +308 -0
  146. package/dist/server/update.js.map +1 -0
  147. package/dist/status.d.ts +18 -0
  148. package/dist/status.d.ts.map +1 -0
  149. package/dist/status.js +113 -0
  150. package/dist/status.js.map +1 -0
  151. package/dist/version.d.ts +2 -0
  152. package/dist/version.d.ts.map +1 -0
  153. package/dist/version.js +20 -0
  154. package/dist/version.js.map +1 -0
  155. package/package.json +36 -0
@@ -0,0 +1,79 @@
1
+ // The NON-SECRET deploy-state file for the `server` command group.
2
+ //
3
+ // `up` runs the container with a chosen bind host, data volume, and ref, but
4
+ // none of that was persisted anywhere — so `update` couldn't recreate the
5
+ // container with the same config and `status` couldn't report the deployed ref
6
+ // reliably (it would have to guess via `git describe`). This file records that
7
+ // config in the deploy dir (default `~/.librarian/server/deploy-state.json`).
8
+ //
9
+ // SECURITY (AGENTS.md / spec §11): this file is NON-SECRET by construction. It
10
+ // carries ONLY the five fields below — a bind host, a volume name, a ref, an
11
+ // image tag, and the container name. It NEVER carries a bearer token, master
12
+ // key, or admin token. `writeDeployState` whitelists exactly those fields, so a
13
+ // caller that accidentally passes extra keys (a smuggled secret) cannot leak
14
+ // them into the file. The master key / admin token are surfaced to stdout once
15
+ // by `up` and persisted nowhere host-side — that contract is unchanged.
16
+ import fs from "node:fs";
17
+ import path from "node:path";
18
+ /** The keys we ever persist — the whitelist that keeps secrets out of the file. */
19
+ const STATE_KEYS = ["containerName", "host", "dataVolume", "ref", "imageTag"];
20
+ /** `<dir>/deploy-state.json` — the deploy-state file path within a deploy dir. */
21
+ export function deployStatePath(dir) {
22
+ return path.join(dir, "deploy-state.json");
23
+ }
24
+ /**
25
+ * Write the deploy-state to `<dir>/deploy-state.json`, creating `dir` if absent.
26
+ *
27
+ * Only the five declared fields are persisted — a `pick`, not a spread — so no
28
+ * extra key (e.g. a smuggled token) can ride along into the file. The file is
29
+ * non-secret, so it gets ordinary (not 0600) permissions.
30
+ */
31
+ export function writeDeployState(dir, state) {
32
+ fs.mkdirSync(dir, { recursive: true });
33
+ // Pick ONLY the whitelisted keys — never spread `state`, which could carry
34
+ // extra (secret-shaped) properties a caller smuggled in.
35
+ const safe = {
36
+ containerName: state.containerName,
37
+ host: state.host,
38
+ dataVolume: state.dataVolume,
39
+ ref: state.ref,
40
+ imageTag: state.imageTag,
41
+ };
42
+ fs.writeFileSync(deployStatePath(dir), `${JSON.stringify(safe, null, 2)}\n`, "utf8");
43
+ }
44
+ /**
45
+ * Read the deploy-state back, or `null` if the file is absent, unparseable, or
46
+ * missing any required field. Never throws — a missing/corrupt state file means
47
+ * the caller falls back (e.g. `status` uses `git describe`), never crashes.
48
+ */
49
+ export function readDeployState(dir) {
50
+ let raw;
51
+ try {
52
+ raw = fs.readFileSync(deployStatePath(dir), "utf8");
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ let parsed;
58
+ try {
59
+ parsed = JSON.parse(raw);
60
+ }
61
+ catch {
62
+ return null;
63
+ }
64
+ if (typeof parsed !== "object" || parsed === null)
65
+ return null;
66
+ const obj = parsed;
67
+ for (const key of STATE_KEYS) {
68
+ if (typeof obj[key] !== "string")
69
+ return null;
70
+ }
71
+ return {
72
+ containerName: obj.containerName,
73
+ host: obj.host,
74
+ dataVolume: obj.dataVolume,
75
+ ref: obj.ref,
76
+ imageTag: obj.imageTag,
77
+ };
78
+ }
79
+ //# sourceMappingURL=deploy-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deploy-state.js","sourceRoot":"","sources":["../../src/server/deploy-state.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,EAAE;AACF,6EAA6E;AAC7E,0EAA0E;AAC1E,+EAA+E;AAC/E,+EAA+E;AAC/E,8EAA8E;AAC9E,EAAE;AACF,+EAA+E;AAC/E,6EAA6E;AAC7E,6EAA6E;AAC7E,gFAAgF;AAChF,6EAA6E;AAC7E,+EAA+E;AAC/E,wEAAwE;AAExE,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAmB7B,mFAAmF;AACnF,MAAM,UAAU,GAAG,CAAC,eAAe,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,UAAU,CAAU,CAAC;AAEvF,kFAAkF;AAClF,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,mBAAmB,CAAC,CAAC;AAC7C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAW,EAAE,KAAkB;IAC9D,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvC,2EAA2E;IAC3E,yDAAyD;IACzD,MAAM,IAAI,GAAgB;QACxB,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,QAAQ,EAAE,KAAK,CAAC,QAAQ;KACzB,CAAC;IACF,EAAE,CAAC,aAAa,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AACvF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/D,MAAM,GAAG,GAAG,MAAiC,CAAC;IAC9C,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,IAAI,OAAO,GAAG,CAAC,GAAG,CAAC,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;IAChD,CAAC;IACD,OAAO;QACL,aAAa,EAAE,GAAG,CAAC,aAAuB;QAC1C,IAAI,EAAE,GAAG,CAAC,IAAc;QACxB,UAAU,EAAE,GAAG,CAAC,UAAoB;QACpC,GAAG,EAAE,GAAG,CAAC,GAAa;QACtB,QAAQ,EAAE,GAAG,CAAC,QAAkB;KACjC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,37 @@
1
+ import type { RunOptions, RunResult, Runner } from "../exec.js";
2
+ export type { RunOptions, RunResult, Runner } from "../exec.js";
3
+ /** Per-stream chunk handlers for a streaming spawn. Chunks arrive as they do. */
4
+ export interface StreamHandlers {
5
+ /** Called with each stdout chunk (decoded utf8) as it arrives. */
6
+ onStdout?: (chunk: string) => void;
7
+ /** Called with each stderr chunk (decoded utf8) as it arrives. */
8
+ onStderr?: (chunk: string) => void;
9
+ }
10
+ /**
11
+ * The streaming counterpart to {@link Runner}. Spawns `cmd args…`, forwards
12
+ * each stdout/stderr chunk to the handlers LIVE (never buffered to the end),
13
+ * and resolves with the exit code when the process exits — `null` if it was
14
+ * signalled (e.g. the user Ctrl-Cs a `-f` follow, or the container stops).
15
+ */
16
+ export interface Streamer {
17
+ stream(cmd: string, args: readonly string[], handlers: StreamHandlers, opts?: RunOptions): Promise<number | null>;
18
+ }
19
+ /** Run a command (`docker`/`git`) through the current server runner. */
20
+ export declare function run(cmd: string, args: readonly string[], opts?: RunOptions): Promise<RunResult>;
21
+ /** Resolve a command to its PATH location through the current server runner. */
22
+ export declare function which(cmd: string): Promise<string | null>;
23
+ /**
24
+ * Stream a command (e.g. `docker logs -f`) through the current server streamer,
25
+ * forwarding each stdout/stderr chunk to `handlers` LIVE. Resolves with the exit
26
+ * code when the process exits.
27
+ */
28
+ export declare function stream(cmd: string, args: readonly string[], handlers: StreamHandlers, opts?: RunOptions): Promise<number | null>;
29
+ /** Swap in a fake runner (tests). Returns the previous runner. */
30
+ export declare function setRunner(runner: Runner): Runner;
31
+ /** Restore the real, process-spawning runner (tests). */
32
+ export declare function resetRunner(): void;
33
+ /** Swap in a fake streamer (tests). Returns the previous streamer. */
34
+ export declare function setStreamer(streamer: Streamer): Streamer;
35
+ /** Restore the real, process-spawning streamer (tests). */
36
+ export declare function resetStreamer(): void;
37
+ //# sourceMappingURL=docker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docker.d.ts","sourceRoot":"","sources":["../../src/server/docker.ts"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEhE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAIhE,iFAAiF;AACjF,MAAM,WAAW,cAAc;IAC7B,kEAAkE;IAClE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,kEAAkE;IAClE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED;;;;;GAKG;AACH,MAAM,WAAW,QAAQ;IACvB,MAAM,CACJ,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,QAAQ,EAAE,cAAc,EACxB,IAAI,CAAC,EAAE,UAAU,GAChB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC3B;AA8ED,wEAAwE;AACxE,wBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAE/F;AAED,gFAAgF;AAChF,wBAAgB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAEzD;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CACpB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,QAAQ,EAAE,cAAc,EACxB,IAAI,CAAC,EAAE,UAAU,GAChB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAExB;AAED,kEAAkE;AAClE,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAIhD;AAED,yDAAyD;AACzD,wBAAgB,WAAW,IAAI,IAAI,CAElC;AAED,sEAAsE;AACtE,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,GAAG,QAAQ,CAIxD;AAED,2DAA2D;AAC3D,wBAAgB,aAAa,IAAI,IAAI,CAEpC"}
@@ -0,0 +1,138 @@
1
+ // Injectable `docker` / `git` runner for the `server` command group.
2
+ //
3
+ // Every `server` subcommand (up / update / down / status / logs / boot /
4
+ // admin) drives the host's `docker` and `git` binaries. They go through THIS
5
+ // seam rather than `node:child_process` directly, so tests stub the runner and
6
+ // assert the exact argv each command invokes — WITHOUT spawning a real process
7
+ // or touching a real Docker daemon.
8
+ //
9
+ // This mirrors `src/exec.ts` (the harness CLI runner) deliberately: same
10
+ // `Runner` shape, same `run`/`which`/`setRunner`/`resetRunner` surface. It is a
11
+ // SEPARATE module-level runner, though — the harness commands and the server
12
+ // commands inject independently, so a test that stubs one never disturbs the
13
+ // other. Reusing `exec.ts`'s `Runner`/`RunResult`/`RunOptions` types keeps a
14
+ // single fake (`FakeRunner`) usable for both.
15
+ //
16
+ // Security (AGENTS.md): a command may *carry* a bearer token via an env var its
17
+ // native mechanism reads, but this module never logs, echoes, or persists the
18
+ // streams it returns.
19
+ //
20
+ // Two surfaces, two seams:
21
+ // - `run` CAPTURES a process's streams and resolves on close. Right for a
22
+ // command that EXITS on its own (`docker logs` without `-f`, `docker stop`,
23
+ // `docker info`): you want the whole output, after the fact.
24
+ // - `stream` does NOT capture — it hands each stdout/stderr chunk to a callback
25
+ // as it arrives and resolves only when the process exits. Right for a FOLLOW
26
+ // (`docker logs -f`), which never closes on its own: capturing it would buffer
27
+ // forever and emit nothing until the user Ctrl-Cs. Each seam injects
28
+ // independently (`setRunner`/`setStreamer`) so a test stubs exactly what it
29
+ // exercises, and neither ever spawns a real `docker`.
30
+ import { spawn } from "node:child_process";
31
+ // --- the real, process-spawning runner -----------------------------------
32
+ const realRunner = {
33
+ run(cmd, args, opts = {}) {
34
+ return new Promise((resolve, reject) => {
35
+ const child = spawn(cmd, [...args], {
36
+ cwd: opts.cwd,
37
+ env: opts.env ? { ...process.env, ...opts.env } : process.env,
38
+ stdio: ["ignore", "pipe", "pipe"],
39
+ });
40
+ let stdout = "";
41
+ let stderr = "";
42
+ child.stdout?.on("data", (chunk) => {
43
+ stdout += chunk.toString("utf8");
44
+ });
45
+ child.stderr?.on("data", (chunk) => {
46
+ stderr += chunk.toString("utf8");
47
+ });
48
+ // ENOENT (binary not on PATH) surfaces here, not as a non-zero exit.
49
+ child.on("error", reject);
50
+ child.on("close", (code) => {
51
+ resolve({ stdout, stderr, code });
52
+ });
53
+ });
54
+ },
55
+ async which(cmd) {
56
+ const probe = process.platform === "win32" ? "where" : "which";
57
+ try {
58
+ const { stdout, code } = await realRunner.run(probe, [cmd]);
59
+ if (code !== 0)
60
+ return null;
61
+ const first = stdout
62
+ .split("\n")
63
+ .map((l) => l.trim())
64
+ .find((l) => l.length > 0);
65
+ return first ?? null;
66
+ }
67
+ catch {
68
+ return null;
69
+ }
70
+ },
71
+ };
72
+ // --- the real, process-spawning streamer ----------------------------------
73
+ const realStreamer = {
74
+ stream(cmd, args, handlers, opts = {}) {
75
+ return new Promise((resolve, reject) => {
76
+ const child = spawn(cmd, [...args], {
77
+ cwd: opts.cwd,
78
+ env: opts.env ? { ...process.env, ...opts.env } : process.env,
79
+ // pipe (not inherit) so the caller can filter/transform per chunk; the
80
+ // caller is responsible for writing kept output to the terminal.
81
+ stdio: ["ignore", "pipe", "pipe"],
82
+ });
83
+ child.stdout?.on("data", (chunk) => {
84
+ handlers.onStdout?.(chunk.toString("utf8"));
85
+ });
86
+ child.stderr?.on("data", (chunk) => {
87
+ handlers.onStderr?.(chunk.toString("utf8"));
88
+ });
89
+ // ENOENT (binary not on PATH) surfaces here, not as a non-zero exit.
90
+ child.on("error", reject);
91
+ // Resolves only when the process EXITS — for `docker logs -f` that means
92
+ // the container stopped or the user Ctrl-C'd; until then chunks stream.
93
+ child.on("close", (code) => {
94
+ resolve(code);
95
+ });
96
+ });
97
+ },
98
+ };
99
+ // --- the swappable module-level runner -----------------------------------
100
+ let current = realRunner;
101
+ let currentStreamer = realStreamer;
102
+ /** Run a command (`docker`/`git`) through the current server runner. */
103
+ export function run(cmd, args, opts) {
104
+ return current.run(cmd, args, opts);
105
+ }
106
+ /** Resolve a command to its PATH location through the current server runner. */
107
+ export function which(cmd) {
108
+ return current.which(cmd);
109
+ }
110
+ /**
111
+ * Stream a command (e.g. `docker logs -f`) through the current server streamer,
112
+ * forwarding each stdout/stderr chunk to `handlers` LIVE. Resolves with the exit
113
+ * code when the process exits.
114
+ */
115
+ export function stream(cmd, args, handlers, opts) {
116
+ return currentStreamer.stream(cmd, args, handlers, opts);
117
+ }
118
+ /** Swap in a fake runner (tests). Returns the previous runner. */
119
+ export function setRunner(runner) {
120
+ const prev = current;
121
+ current = runner;
122
+ return prev;
123
+ }
124
+ /** Restore the real, process-spawning runner (tests). */
125
+ export function resetRunner() {
126
+ current = realRunner;
127
+ }
128
+ /** Swap in a fake streamer (tests). Returns the previous streamer. */
129
+ export function setStreamer(streamer) {
130
+ const prev = currentStreamer;
131
+ currentStreamer = streamer;
132
+ return prev;
133
+ }
134
+ /** Restore the real, process-spawning streamer (tests). */
135
+ export function resetStreamer() {
136
+ currentStreamer = realStreamer;
137
+ }
138
+ //# sourceMappingURL=docker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docker.js","sourceRoot":"","sources":["../../src/server/docker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ export interface DownOptions {
2
+ /** Platform for preflight's daemon hint. Default `process.platform`. */
3
+ platform?: NodeJS.Platform | undefined;
4
+ }
5
+ /** A teaching error from `down`; the runtime renders `.message` as one stderr line. */
6
+ export declare class DownError extends Error {
7
+ constructor(message: string);
8
+ }
9
+ export interface DownResult {
10
+ /** Human-readable report for stdout. */
11
+ output: string;
12
+ }
13
+ /**
14
+ * Run `server down`. Preflights docker, then `docker stop the-librarian` — the
15
+ * ONLY command it ever issues. A not-found/not-running container is a friendly
16
+ * no-op; any other non-zero exit is a teaching `DownError`.
17
+ */
18
+ export declare function runDown(options?: DownOptions): Promise<DownResult>;
19
+ //# sourceMappingURL=down.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"down.d.ts","sourceRoot":"","sources":["../../src/server/down.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,WAAW;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,GAAG,SAAS,CAAC;CACxC;AAED,uFAAuF;AACvF,qBAAa,SAAU,SAAQ,KAAK;gBACtB,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,UAAU;IACzB,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;CAChB;AAOD;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CA+B5E"}
@@ -0,0 +1,62 @@
1
+ // `librarian server down` — stop the container. DATA IS SACRED.
2
+ //
3
+ // `down` maps to ONE command: `docker stop the-librarian`. It NEVER removes the
4
+ // container (`docker rm`), never passes `-v`, never touches the data volume
5
+ // (`docker volume rm`), and never force-removes (`rm -f`). A later `up`/`update`
6
+ // brings the SAME memories back from the untouched named volume — that is the
7
+ // whole point (spec §11, success criterion 6).
8
+ //
9
+ // Idempotent-ish: if the container isn't running (or doesn't exist), `docker
10
+ // stop` exits non-zero with a "No such container" message — `down` turns that
11
+ // into a friendly "nothing to stop" outcome rather than a crash or a stack
12
+ // trace. A genuine daemon error (anything that isn't the not-found case) still
13
+ // surfaces as a teaching error.
14
+ //
15
+ // Everything goes through the injectable `docker.ts` runner, so tests assert the
16
+ // exact argv without a real daemon.
17
+ import { run } from "./docker.js";
18
+ import { preflight } from "./preflight.js";
19
+ import { CONTAINER_NAME } from "./up.js";
20
+ /** A teaching error from `down`; the runtime renders `.message` as one stderr line. */
21
+ export class DownError extends Error {
22
+ constructor(message) {
23
+ super(message);
24
+ this.name = "DownError";
25
+ }
26
+ }
27
+ /** True when `docker stop`'s failure means the container simply isn't there. */
28
+ function isNotRunning(stderr) {
29
+ return /no such container|is not running/i.test(stderr);
30
+ }
31
+ /**
32
+ * Run `server down`. Preflights docker, then `docker stop the-librarian` — the
33
+ * ONLY command it ever issues. A not-found/not-running container is a friendly
34
+ * no-op; any other non-zero exit is a teaching `DownError`.
35
+ */
36
+ export async function runDown(options = {}) {
37
+ await preflight(options.platform ? { platform: options.platform } : {});
38
+ // The single, data-safe command. No `rm`, no `-v`, no `volume`, no `-f`.
39
+ const result = await run("docker", ["stop", CONTAINER_NAME]);
40
+ if (result.code === 0) {
41
+ return {
42
+ output: [
43
+ `Stopped ${CONTAINER_NAME}.`,
44
+ "The data volume is preserved — `librarian server up` brings the same memories back.",
45
+ ].join("\n"),
46
+ };
47
+ }
48
+ const detail = result.stderr.trim();
49
+ if (isNotRunning(detail)) {
50
+ return {
51
+ output: [
52
+ `${CONTAINER_NAME} is not running — nothing to stop.`,
53
+ "The data volume (if any) is untouched.",
54
+ ].join("\n"),
55
+ };
56
+ }
57
+ // A real failure (daemon error, permission, etc.) — teach, don't crash.
58
+ throw new DownError(`\`docker stop ${CONTAINER_NAME}\` failed (exit ${result.code ?? "signal"})` +
59
+ (detail ? `:\n${detail}` : ".") +
60
+ "\n\nResolve the error above, then re-run `librarian server down`.");
61
+ }
62
+ //# sourceMappingURL=down.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"down.js","sourceRoot":"","sources":["../../src/server/down.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,EAAE;AACF,gFAAgF;AAChF,4EAA4E;AAC5E,iFAAiF;AACjF,8EAA8E;AAC9E,+CAA+C;AAC/C,EAAE;AACF,6EAA6E;AAC7E,8EAA8E;AAC9E,2EAA2E;AAC3E,+EAA+E;AAC/E,gCAAgC;AAChC,EAAE;AACF,iFAAiF;AACjF,oCAAoC;AAEpC,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAOzC,uFAAuF;AACvF,MAAM,OAAO,SAAU,SAAQ,KAAK;IAClC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;IAC1B,CAAC;CACF;AAOD,gFAAgF;AAChF,SAAS,YAAY,CAAC,MAAc;IAClC,OAAO,mCAAmC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC1D,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,UAAuB,EAAE;IACrD,MAAM,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAExE,yEAAyE;IACzE,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;IAE7D,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO;YACL,MAAM,EAAE;gBACN,WAAW,cAAc,GAAG;gBAC5B,qFAAqF;aACtF,CAAC,IAAI,CAAC,IAAI,CAAC;SACb,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IACpC,IAAI,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC;QACzB,OAAO;YACL,MAAM,EAAE;gBACN,GAAG,cAAc,oCAAoC;gBACrD,wCAAwC;aACzC,CAAC,IAAI,CAAC,IAAI,CAAC;SACb,CAAC;IACJ,CAAC;IAED,wEAAwE;IACxE,MAAM,IAAI,SAAS,CACjB,iBAAiB,cAAc,mBAAmB,MAAM,CAAC,IAAI,IAAI,QAAQ,GAAG;QAC1E,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,MAAM,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;QAC/B,mEAAmE,CACtE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,8 @@
1
+ /** The `server` subcommands, in the order they appear in the surface (§4). */
2
+ export declare const SERVER_SUBCOMMANDS: readonly ["up", "update", "down", "status", "logs", "enable-boot", "disable-boot", "admin"];
3
+ export type ServerSubcommand = (typeof SERVER_SUBCOMMANDS)[number];
4
+ /** The `librarian server` command surface (spec §4). */
5
+ export declare function serverUsage(): string;
6
+ /** True iff `name` is one of the known `server` subcommands. */
7
+ export declare function isServerSubcommand(name: string): name is ServerSubcommand;
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAYA,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,6FASrB,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnE,wDAAwD;AACxD,wBAAgB,WAAW,IAAI,MAAM,CAmBpC;AAED,gEAAgE;AAChE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,IAAI,gBAAgB,CAEzE"}
@@ -0,0 +1,48 @@
1
+ // The `librarian server` command group — self-host the Librarian from the CLI.
2
+ //
3
+ // S1 lays the foundation the later slices build on: the command SURFACE (the
4
+ // help table from spec §4), the `docker.ts` seam, and `preflight()`. The
5
+ // individual subcommands (up / update / down / status / logs / enable-boot /
6
+ // disable-boot / admin) are implemented in their own slices; here they resolve
7
+ // to a clear "arrives in a later slice" notice so the surface is honest about
8
+ // what exists today.
9
+ //
10
+ // `server` with NO subcommand prints the surface; `librarian --help` reveals
11
+ // this group alongside the harness commands (wired in `runtime.ts`).
12
+ /** The `server` subcommands, in the order they appear in the surface (§4). */
13
+ export const SERVER_SUBCOMMANDS = [
14
+ "up",
15
+ "update",
16
+ "down",
17
+ "status",
18
+ "logs",
19
+ "enable-boot",
20
+ "disable-boot",
21
+ "admin",
22
+ ];
23
+ /** The `librarian server` command surface (spec §4). */
24
+ export function serverUsage() {
25
+ return [
26
+ "Usage: librarian server <subcommand> [flags]",
27
+ "",
28
+ "Self-host the Librarian server (build + run the all-in-one container),",
29
+ "then hand its MCP URL + agent token to `librarian install` on clients.",
30
+ "",
31
+ "Subcommands:",
32
+ " up Build + run the server; print the MCP URL + agent token",
33
+ " update Re-pin to a release, rebuild, recreate (data volume kept)",
34
+ " down Stop the container (the data volume is preserved)",
35
+ " status Running? healthy? deployed version vs latest release",
36
+ " logs Tail the container logs ([-f] [--service mcp|dashboard|all])",
37
+ " enable-boot Start the server on boot (Linux systemd; macOS deferred)",
38
+ " disable-boot Reverse enable-boot",
39
+ " admin Run an admin command in the container (backup|restore|auth|rebuild)",
40
+ "",
41
+ "Run `librarian server <subcommand> --help` for flags (per subcommand).",
42
+ ].join("\n");
43
+ }
44
+ /** True iff `name` is one of the known `server` subcommands. */
45
+ export function isServerSubcommand(name) {
46
+ return SERVER_SUBCOMMANDS.includes(name);
47
+ }
48
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,EAAE;AACF,6EAA6E;AAC7E,yEAAyE;AACzE,6EAA6E;AAC7E,+EAA+E;AAC/E,8EAA8E;AAC9E,qBAAqB;AACrB,EAAE;AACF,6EAA6E;AAC7E,qEAAqE;AAErE,8EAA8E;AAC9E,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,IAAI;IACJ,QAAQ;IACR,MAAM;IACN,QAAQ;IACR,MAAM;IACN,aAAa;IACb,cAAc;IACd,OAAO;CACC,CAAC;AAIX,wDAAwD;AACxD,MAAM,UAAU,WAAW;IACzB,OAAO;QACL,8CAA8C;QAC9C,EAAE;QACF,wEAAwE;QACxE,wEAAwE;QACxE,EAAE;QACF,cAAc;QACd,yEAAyE;QACzE,2EAA2E;QAC3E,mEAAmE;QACnE,sEAAsE;QACtE,8EAA8E;QAC9E,0EAA0E;QAC1E,qCAAqC;QACrC,qFAAqF;QACrF,EAAE;QACF,wEAAwE;KACzE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,OAAQ,kBAAwC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AAClE,CAAC"}
@@ -0,0 +1,50 @@
1
+ /** The services `--service` can target. `all` (default) is unfiltered. */
2
+ export type LogsService = "mcp" | "dashboard" | "all";
3
+ export interface LogsOptions {
4
+ /** Follow the log output (`-f` / `--follow`). */
5
+ follow?: boolean | undefined;
6
+ /** Which service to show. Default `all` (unfiltered). */
7
+ service?: string | undefined;
8
+ /** Platform for preflight's daemon hint. Default `process.platform`. */
9
+ platform?: NodeJS.Platform | undefined;
10
+ /**
11
+ * Where a FOLLOW (`-f`) writes its (service-filtered) lines, AS THEY ARRIVE.
12
+ * Defaults to `process.stdout` so a live tail reaches the terminal directly —
13
+ * never buffered to the end. Injectable so tests observe ordering without a
14
+ * real terminal. Unused by the one-shot path (it returns its output instead).
15
+ */
16
+ write?: ((chunk: string) => void) | undefined;
17
+ }
18
+ /** A teaching error from `logs`; the runtime renders `.message` as one stderr line. */
19
+ export declare class LogsError extends Error {
20
+ constructor(message: string);
21
+ }
22
+ export interface LogsResult {
23
+ /**
24
+ * The (service-filtered) log output for stdout. Populated by the one-shot
25
+ * (no-`-f`) path; EMPTY for a follow, which streams its lines live via
26
+ * `write` rather than returning them.
27
+ */
28
+ output: string;
29
+ /**
30
+ * The followed process's exit code (`-f` path), or `0` for the one-shot path.
31
+ * `null` when the follow was signalled (e.g. Ctrl-C / container stop).
32
+ */
33
+ exitCode: number | null;
34
+ }
35
+ /**
36
+ * Run `server logs`. Preflights docker, then either:
37
+ * - FOLLOW (`-f`): streams `docker logs -f the-librarian` through the streaming
38
+ * seam, writing each service-matching line to `write` (default
39
+ * `process.stdout`) AS IT ARRIVES, resolving with the process's exit code
40
+ * when the follow ends (Ctrl-C / container stop). Output is empty (already
41
+ * streamed live).
42
+ * - ONE-SHOT (no `-f`): captures `docker logs the-librarian` via the capturing
43
+ * runner (it exits on its own), service-filters the captured output, and
44
+ * returns it for stdout.
45
+ *
46
+ * An unknown `--service` is a teaching error; a non-zero one-shot `docker logs`
47
+ * exit surfaces its stderr as a teaching error (e.g. "No such container").
48
+ */
49
+ export declare function runLogs(options?: LogsOptions): Promise<LogsResult>;
50
+ //# sourceMappingURL=logs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.d.ts","sourceRoot":"","sources":["../../src/server/logs.ts"],"names":[],"mappings":"AAmCA,0EAA0E;AAC1E,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,WAAW,GAAG,KAAK,CAAC;AAItD,MAAM,WAAW,WAAW;IAC1B,iDAAiD;IACjD,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7B,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,GAAG,SAAS,CAAC;IACvC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;CAC/C;AAED,uFAAuF;AACvF,qBAAa,SAAU,SAAQ,KAAK;gBACtB,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,UAAU;IACzB;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAoFD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,OAAO,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CA8B5E"}
@@ -0,0 +1,162 @@
1
+ // `librarian server logs [-f] [--service mcp|dashboard|all]`.
2
+ //
3
+ // Maps to `docker logs [-f] the-librarian`. The all-in-one image runs BOTH
4
+ // services (mcp-server + dashboard) in ONE container under `docker/supervisor.mjs`,
5
+ // which spawns its children with `stdio: "inherit"` — so it emits NO per-service
6
+ // prefix. The two streams are interleaved with no supervisor-added label.
7
+ //
8
+ // How `--service` filtering works, given that constraint:
9
+ // - The mcp-server logs structured pino NDJSON — every line is a JSON object
10
+ // carrying `"service":"the-librarian"` (see packages/mcp-server/src/logging.ts).
11
+ // - The dashboard (Next.js standalone `server.js`) logs plain text.
12
+ // So we distinguish them by SHAPE: a line that parses as a JSON object is an
13
+ // mcp-server line; anything else is a dashboard line. `--service mcp` keeps the
14
+ // NDJSON lines; `--service dashboard` keeps the rest; `all` (default) is
15
+ // unfiltered. This is the most reliable signal available without changing the
16
+ // image to prefix each child's output.
17
+ //
18
+ // `-f` (follow) vs one-shot — two different `docker.ts` seams, because the two
19
+ // commands have opposite lifetimes:
20
+ // - WITHOUT `-f`, `docker logs the-librarian` prints what's there and EXITS.
21
+ // The capturing `run()` seam is right: it resolves on close with the whole
22
+ // output, which we then service-filter and return for stdout.
23
+ // - WITH `-f`, `docker logs -f the-librarian` NEVER closes on its own — it
24
+ // tails until the container stops or the user Ctrl-Cs. Capturing it would
25
+ // buffer forever and emit nothing until then (the old bug). So `-f` uses the
26
+ // STREAMING seam (`stream()`): each line is service-filtered and written to
27
+ // the terminal AS IT ARRIVES, and the call resolves with the process's exit
28
+ // code when the follow ends. Both paths apply the identical JSON-shape
29
+ // `--service` filter; only the delivery (buffered-then-returned vs.
30
+ // live-per-line) differs.
31
+ import { run, stream } from "./docker.js";
32
+ import { preflight } from "./preflight.js";
33
+ import { CONTAINER_NAME } from "./up.js";
34
+ const SERVICES = ["mcp", "dashboard", "all"];
35
+ /** A teaching error from `logs`; the runtime renders `.message` as one stderr line. */
36
+ export class LogsError extends Error {
37
+ constructor(message) {
38
+ super(message);
39
+ this.name = "LogsError";
40
+ }
41
+ }
42
+ /** Validate + default the `--service` value, or throw a teaching error. */
43
+ function resolveService(raw) {
44
+ if (raw === undefined)
45
+ return "all";
46
+ if (SERVICES.includes(raw))
47
+ return raw;
48
+ throw new LogsError(`Unknown --service '${raw}'. Valid values: mcp, dashboard, all (default). ` +
49
+ "`mcp` shows the MCP server's structured logs, `dashboard` shows the Next.js " +
50
+ "dashboard's, `all` shows both.");
51
+ }
52
+ /**
53
+ * True iff a log line is an mcp-server line — i.e. it parses as a JSON object
54
+ * (the pino NDJSON shape). The dashboard's plain-text lines do not parse as JSON
55
+ * objects, so they're treated as `dashboard`.
56
+ */
57
+ function isMcpLine(line) {
58
+ const trimmed = line.trim();
59
+ if (!trimmed.startsWith("{"))
60
+ return false;
61
+ try {
62
+ const parsed = JSON.parse(trimmed);
63
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed);
64
+ }
65
+ catch {
66
+ return false;
67
+ }
68
+ }
69
+ /**
70
+ * True iff a single line belongs to `service`. `all` keeps everything; `mcp`
71
+ * keeps the NDJSON lines; `dashboard` keeps the non-NDJSON, non-blank lines.
72
+ * The SAME predicate drives both the buffered and the streaming paths.
73
+ */
74
+ function keepLine(line, service) {
75
+ if (service === "all")
76
+ return true;
77
+ if (service === "mcp")
78
+ return isMcpLine(line);
79
+ // dashboard: everything that isn't an mcp NDJSON line, skipping blanks so the
80
+ // output isn't padded.
81
+ return line.trim().length > 0 && !isMcpLine(line);
82
+ }
83
+ /** Keep only the lines belonging to `service` (preserving order + blanks for `all`). */
84
+ function filterByService(text, service) {
85
+ if (service === "all")
86
+ return text;
87
+ return text
88
+ .split("\n")
89
+ .filter((l) => keepLine(l, service))
90
+ .join("\n");
91
+ }
92
+ /**
93
+ * A streaming line filter: feed it raw stdout chunks (which may split mid-line),
94
+ * and it emits each COMPLETE line that belongs to `service` to `write` as soon
95
+ * as the line is whole — never buffering to the end. Returns a `flush()` for any
96
+ * trailing partial line left when the stream closes.
97
+ */
98
+ function makeLineFilter(service, write) {
99
+ let buffer = "";
100
+ const emit = (line) => {
101
+ if (keepLine(line, service))
102
+ write(line + "\n");
103
+ };
104
+ return {
105
+ push(chunk) {
106
+ buffer += chunk;
107
+ let nl = buffer.indexOf("\n");
108
+ while (nl !== -1) {
109
+ emit(buffer.slice(0, nl));
110
+ buffer = buffer.slice(nl + 1);
111
+ nl = buffer.indexOf("\n");
112
+ }
113
+ },
114
+ flush() {
115
+ if (buffer.length > 0) {
116
+ emit(buffer);
117
+ buffer = "";
118
+ }
119
+ },
120
+ };
121
+ }
122
+ /**
123
+ * Run `server logs`. Preflights docker, then either:
124
+ * - FOLLOW (`-f`): streams `docker logs -f the-librarian` through the streaming
125
+ * seam, writing each service-matching line to `write` (default
126
+ * `process.stdout`) AS IT ARRIVES, resolving with the process's exit code
127
+ * when the follow ends (Ctrl-C / container stop). Output is empty (already
128
+ * streamed live).
129
+ * - ONE-SHOT (no `-f`): captures `docker logs the-librarian` via the capturing
130
+ * runner (it exits on its own), service-filters the captured output, and
131
+ * returns it for stdout.
132
+ *
133
+ * An unknown `--service` is a teaching error; a non-zero one-shot `docker logs`
134
+ * exit surfaces its stderr as a teaching error (e.g. "No such container").
135
+ */
136
+ export async function runLogs(options = {}) {
137
+ const service = resolveService(options.service);
138
+ await preflight(options.platform ? { platform: options.platform } : {});
139
+ if (options.follow) {
140
+ // Live tail: stream line-by-line, never buffer to close.
141
+ const write = options.write ?? ((chunk) => process.stdout.write(chunk));
142
+ const filter = makeLineFilter(service, write);
143
+ const exitCode = await stream("docker", ["logs", "-f", CONTAINER_NAME], {
144
+ onStdout: (chunk) => filter.push(chunk),
145
+ // `docker logs` writes the container's stderr stream here too; service
146
+ // filtering applies the same way so it isn't lost or unfiltered.
147
+ onStderr: (chunk) => filter.push(chunk),
148
+ });
149
+ filter.flush();
150
+ return { output: "", exitCode };
151
+ }
152
+ // One-shot: `docker logs` exits on its own, so capture + filter is correct.
153
+ const result = await run("docker", ["logs", CONTAINER_NAME]);
154
+ if (result.code !== 0) {
155
+ const detail = result.stderr.trim() || result.stdout.trim();
156
+ throw new LogsError(`\`docker logs ${CONTAINER_NAME}\` failed (exit ${result.code ?? "signal"})` +
157
+ (detail ? `:\n${detail}` : ".") +
158
+ "\n\nIs the server up? Run `librarian server status`.");
159
+ }
160
+ return { output: filterByService(result.stdout, service), exitCode: result.code };
161
+ }
162
+ //# sourceMappingURL=logs.js.map