@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,35 @@
1
+ export interface UpdateOptions {
2
+ /** Pinned ref (`vX.Y.Z` tag or `main`). Default: the latest release tag. */
3
+ ref?: string | undefined;
4
+ /** Auto-accept prompts (none today; wired for surface parity with `up`). */
5
+ yes?: boolean | undefined;
6
+ /** Deploy dir override. Default: `~/.librarian/server`. */
7
+ dir?: string | undefined;
8
+ /** Override home (tests). */
9
+ home?: string | undefined;
10
+ /** Platform for preflight's daemon hint. Default `process.platform`. */
11
+ platform?: NodeJS.Platform | undefined;
12
+ /** Health-wait bound: how many polls before declaring failure (small in tests). */
13
+ healthAttempts?: number | undefined;
14
+ /** Milliseconds between health polls (0 in tests). */
15
+ healthIntervalMs?: number | undefined;
16
+ /** Lines of `docker logs` to surface on a failed health-wait. */
17
+ logTailLines?: number | undefined;
18
+ }
19
+ /** A teaching error from `update`; the runtime renders `.message` as one stderr line. */
20
+ export declare class UpdateError extends Error {
21
+ constructor(message: string);
22
+ }
23
+ export interface UpdateResult {
24
+ /** Human-readable report for stdout (carries a FRESH agent token at most once). */
25
+ output: string;
26
+ }
27
+ /**
28
+ * Run `server update`. Throws `UpdateError` (teaching message) on a failure
29
+ * before/around the recreate; a failed health-wait throws the (already
30
+ * secret-redacted) `UpError` from `waitForHealthy` after rolling the new
31
+ * container back. deploy-state is advanced ONLY after a confirmed-healthy
32
+ * recreate, so a failed update never records the new ref.
33
+ */
34
+ export declare function runUpdate(options?: UpdateOptions): Promise<UpdateResult>;
35
+ //# sourceMappingURL=update.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/server/update.ts"],"names":[],"mappings":"AA6EA,MAAM,WAAW,aAAa;IAC5B,4EAA4E;IAC5E,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,4EAA4E;IAC5E,GAAG,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1B,2DAA2D;IAC3D,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,6BAA6B;IAC7B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,GAAG,SAAS,CAAC;IACvC,mFAAmF;IACnF,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,sDAAsD;IACtD,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,iEAAiE;IACjE,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AAED,yFAAyF;AACzF,qBAAa,WAAY,SAAQ,KAAK;gBACxB,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,YAAY;IAC3B,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAsB,SAAS,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CA8FlF"}
@@ -0,0 +1,308 @@
1
+ // `librarian server update` — re-pin forward, rebuild, recreate, migrate.
2
+ //
3
+ // The tag-pinned, deploy-dir-owning successor to `pull-and-restart.sh` (the
4
+ // stash/branch dance is gone — the CLI owns its deploy dir, so it just fetches
5
+ // tags and checks out the resolved ref). The flow (spec §8, success criterion 5):
6
+ //
7
+ // 1. Resolve the target ref: `--ref <tag|main>` pins it; default = the latest
8
+ // release tag (`fetchLatestVersion`).
9
+ // 2. IDEMPOTENCY FIRST: read deploy-state (current ref) + the container's
10
+ // health. Already at the resolved ref AND healthy → a CLEAN no-op (no
11
+ // build, no run, no recreate). When `--ref` is given, compare against it.
12
+ // 3. Otherwise update, IN THE DEPLOY DIR:
13
+ // git fetch --tags origin → git checkout <ref>
14
+ // → docker build -f docker/all-in-one.Dockerfile -t the-librarian:<ref> .
15
+ // → read back the EXISTING agent token + master key from the running
16
+ // container's env (so clients keep working AND secrets stay
17
+ // decryptable) BEFORE removing it
18
+ // → docker stop → docker rm <container> (NEVER `-v`, NEVER `volume rm`)
19
+ // → write the 0600 deploy env-file (preserved/fresh token + preserved-or-
20
+ // omitted master key + loopback ALLOW_NO_AUTH) then docker run …
21
+ // (buildRunArgs with the SAME host + dataVolume from deploy-state, via
22
+ // `--env-file`)
23
+ // → waitForHealthy (rolls back with `docker rm -f` on failure — reusing
24
+ // up's pattern, so a failed recreate leaves no half-up container and
25
+ // does NOT advance deploy-state)
26
+ // → docker exec the-librarian the-librarian migrate-data-dir
27
+ // (server boot only WARNS about pending data-dir migrations; the admin
28
+ // CLI APPLIES them — see packages/cli/src/commands/migrate-data-dir.ts.
29
+ // The `the-librarian` runtime binary is bundled into the image in S7;
30
+ // this slice asserts the argv, the runtime target arrives with S7.)
31
+ // 4. writeDeployState with the new ref/imageTag (host/dataVolume unchanged).
32
+ //
33
+ // SECRETS ON UPDATE (decision): recreating the container needs the agent token
34
+ // AND — ADR 0008 P4 — the master key (`LIBRARIAN_SECRET_KEY`) again. Both are
35
+ // correctly NOT persisted host-side except in the 0600 deploy env-file. We read
36
+ // BOTH back from the running container's env via `docker inspect` BEFORE we
37
+ // remove it (P4 put the master key there via `--env-file`, so it's in
38
+ // `.Config.Env`), then recreate with them — clients keep working AND every
39
+ // settings.json secret stays decryptable.
40
+ //
41
+ // - AGENT TOKEN: prefer the read-back token; only if the old container is
42
+ // gone/unreadable do we mint a FRESH token and surface it ONCE with a
43
+ // "clients must update their token" note.
44
+ // - MASTER KEY: prefer the read-back key. If it can't be read back we DO NOT
45
+ // mint a fresh one — re-minting would orphan every secret encrypted under
46
+ // the old key. Instead we OMIT `LIBRARIAN_SECRET_KEY` from the new env-file
47
+ // so the server resolves it from `/data/secret.key` (env → file → generate),
48
+ // which preserves a pre-P4 on-disk key (and only generates when the data dir
49
+ // is genuinely fresh — nothing to orphan).
50
+ //
51
+ // Both secrets ride ONLY in the 0600 deploy env-file fed to `docker run
52
+ // --env-file` — never inline on argv, never in any other host file or log (the
53
+ // spec's no-leak boundary; tests scan for them).
54
+ //
55
+ // The DATA VOLUME is sacred: recreate removes the CONTAINER only (`docker rm`),
56
+ // never the named volume (the `-v` flag / `docker volume rm` never appear), so
57
+ // `up`/`update`/`down` all recall the same memories from the untouched volume.
58
+ //
59
+ // Everything goes through the injectable `docker.ts` runner + the injectable
60
+ // latest-release fetcher, so tests assert the exact argv (and its order) WITHOUT
61
+ // a real daemon, network, or git.
62
+ import path from "node:path";
63
+ import { librarianDir } from "../paths.js";
64
+ import { fetchLatestVersion } from "../status.js";
65
+ import { readDeployState, writeDeployState } from "./deploy-state.js";
66
+ import { run } from "./docker.js";
67
+ import { preflight } from "./preflight.js";
68
+ import { redactSecrets } from "./redact.js";
69
+ import { buildRunArgs, CONTAINER_NAME, mintAgentToken, waitForHealthy, writeDeployEnvFile, } from "./up.js";
70
+ /** A teaching error from `update`; the runtime renders `.message` as one stderr line. */
71
+ export class UpdateError extends Error {
72
+ constructor(message) {
73
+ super(message);
74
+ this.name = "UpdateError";
75
+ }
76
+ }
77
+ /**
78
+ * Run `server update`. Throws `UpdateError` (teaching message) on a failure
79
+ * before/around the recreate; a failed health-wait throws the (already
80
+ * secret-redacted) `UpError` from `waitForHealthy` after rolling the new
81
+ * container back. deploy-state is advanced ONLY after a confirmed-healthy
82
+ * recreate, so a failed update never records the new ref.
83
+ */
84
+ export async function runUpdate(options = {}) {
85
+ // 1) Preflight: docker (daemon reachable) + git, or a teaching error.
86
+ await preflight(options.platform ? { platform: options.platform } : {});
87
+ const deployDir = options.dir ?? path.join(librarianDir(options.home), "server");
88
+ // The deploy-state is authoritative for the bind host + data volume to reuse.
89
+ // Without it we cannot recreate the container with the same config — teach.
90
+ const state = readDeployState(deployDir);
91
+ if (!state) {
92
+ throw new UpdateError(`No deploy-state found at ${deployDir} — this host has not run \`librarian server up\` ` +
93
+ "(or the deploy dir is wrong). Run `librarian server up` first, " +
94
+ "or pass `--dir <path>` to point at an existing deploy dir.");
95
+ }
96
+ // 2) Resolve the target ref (explicit `--ref` wins; else the latest tag).
97
+ const targetRef = await resolveRef(options.ref);
98
+ // 3) IDEMPOTENCY FIRST: already at the resolved ref AND healthy → clean no-op.
99
+ if (state.ref === targetRef && (await isHealthy())) {
100
+ return {
101
+ output: [
102
+ `Already up to date (${targetRef}) — the container is healthy.`,
103
+ "Nothing to do. Pin a different ref with `--ref <tag|main>` to change it.",
104
+ ].join("\n"),
105
+ };
106
+ }
107
+ // 4) Update, in the deploy dir: fetch tags → checkout the resolved ref.
108
+ await git(["-C", deployDir, "fetch", "--tags", "origin"]);
109
+ await checkoutRef(deployDir, targetRef);
110
+ // 5) Build the new image from the deploy dir.
111
+ await dockerInDir(["build", "-f", "docker/all-in-one.Dockerfile", "-t", `${CONTAINER_NAME}:${targetRef}`, "."], deployDir);
112
+ // 6) Read the EXISTING secrets back from the running container's env BEFORE
113
+ // removal (one `docker inspect`; ADR 0008 P4 put the master key there via
114
+ // `--env-file`). PREFER reusing both:
115
+ // - agent token: reuse, else mint fresh (clients re-paste).
116
+ // - master key: reuse, else OMIT (server resolves /data/secret.key) —
117
+ // NEVER mint a fresh key, which would orphan encrypted secrets.
118
+ const existingEnv = await readExistingContainerEnv();
119
+ const existingToken = existingEnv.get("LIBRARIAN_AGENT_TOKEN") ?? null;
120
+ const existingKey = existingEnv.get("LIBRARIAN_SECRET_KEY") ?? null;
121
+ const agentToken = existingToken ?? mintAgentToken();
122
+ const tokenIsFresh = existingToken === null;
123
+ // 7) Recreate — CONTAINER ONLY. `docker stop` then `docker rm <name>`:
124
+ // NEVER `-v`, NEVER `docker volume rm`. The named data volume persists.
125
+ await dockerStop();
126
+ await dockerRm();
127
+ // 8) Write the 0600 deploy env-file, then run the new container with the SAME
128
+ // host + data volume from deploy-state, secrets via `--env-file` (ADR 0008
129
+ // P4 — never inline on argv). The master key is the preserved one, or
130
+ // omitted (then the server resolves it from /data/secret.key).
131
+ const envFile = writeDeployEnvFile(deployDir, {
132
+ agentToken,
133
+ secretKey: existingKey ?? undefined,
134
+ host: state.host,
135
+ });
136
+ await dockerInDir(buildRunArgs({
137
+ host: state.host,
138
+ dataVolume: state.dataVolume,
139
+ tag: targetRef,
140
+ envFile,
141
+ }), deployDir);
142
+ // 9) Wait for health (rolls back with `docker rm -f` on failure — up's
143
+ // pattern). If this throws, deploy-state is NOT advanced below.
144
+ await waitForHealthy(options);
145
+ // 10) Apply pending data-dir migrations via the bundled admin CLI (S7 bundles
146
+ // the `the-librarian` binary into the image; this slice asserts the argv).
147
+ await dockerExecMigrate();
148
+ // 11) Persist the new ref/imageTag — host/dataVolume/containerName unchanged.
149
+ writeDeployState(deployDir, {
150
+ containerName: state.containerName,
151
+ host: state.host,
152
+ dataVolume: state.dataVolume,
153
+ ref: targetRef,
154
+ imageTag: `${CONTAINER_NAME}:${targetRef}`,
155
+ });
156
+ return { output: renderSuccess(targetRef, tokenIsFresh ? agentToken : null) };
157
+ }
158
+ // --- ref + health probes -------------------------------------------------
159
+ /** Resolve the target ref: an explicit `--ref` wins; else the latest tag. */
160
+ async function resolveRef(ref) {
161
+ if (ref && ref.trim().length > 0)
162
+ return ref.trim();
163
+ const latest = await fetchLatestVersion();
164
+ if (!latest) {
165
+ throw new UpdateError("Could not resolve the latest release tag from GitHub. " +
166
+ "Check your network, or pin a ref with `--ref <tag|main>`.");
167
+ }
168
+ // `fetchLatestVersion` strips the leading `v`; the tag we check out keeps it.
169
+ return `v${latest}`;
170
+ }
171
+ /**
172
+ * True iff the container exists AND reports `healthy`. Used by the idempotency
173
+ * check — already at the ref but unhealthy means we still recreate. A failing
174
+ * `docker inspect` (no such container) → not healthy.
175
+ */
176
+ async function isHealthy() {
177
+ const status = await run("docker", ["inspect", "--format", "{{.State.Status}}", CONTAINER_NAME]);
178
+ if (status.code !== 0 || status.stdout.trim() !== "running")
179
+ return false;
180
+ const health = await run("docker", [
181
+ "inspect",
182
+ "--format",
183
+ "{{.State.Health.Status}}",
184
+ CONTAINER_NAME,
185
+ ]);
186
+ return health.code === 0 && health.stdout.trim() === "healthy";
187
+ }
188
+ // --- secret read-back (clients keep working; secrets stay decryptable) ----
189
+ /**
190
+ * Read the running container's env (`docker inspect .Config.Env`) into a map,
191
+ * BEFORE the container is removed, so an `update` reuses the existing secrets
192
+ * rather than silently breaking clients or orphaning encrypted settings. Returns
193
+ * an EMPTY map when the container is gone/unreadable (a non-zero inspect) — the
194
+ * caller then mints a fresh agent token and omits the master key. Empty values
195
+ * are dropped (treated as "not present"). The values are returned for reuse on
196
+ * recreate — never logged.
197
+ */
198
+ async function readExistingContainerEnv() {
199
+ const env = new Map();
200
+ const result = await run("docker", [
201
+ "inspect",
202
+ "--format",
203
+ "{{range .Config.Env}}{{println .}}{{end}}",
204
+ CONTAINER_NAME,
205
+ ]);
206
+ if (result.code !== 0)
207
+ return env;
208
+ for (const line of result.stdout.split("\n")) {
209
+ const trimmed = line.trim();
210
+ const eq = trimmed.indexOf("=");
211
+ if (eq <= 0)
212
+ continue; // no `=`, or a leading `=` (no name) → skip
213
+ const name = trimmed.slice(0, eq);
214
+ const value = trimmed.slice(eq + 1);
215
+ if (value.length > 0)
216
+ env.set(name, value);
217
+ }
218
+ return env;
219
+ }
220
+ // --- thin runner wrappers (teaching errors on a non-zero exit) ----------
221
+ /** Run a `git …` command; a non-zero exit is a teaching error. */
222
+ async function git(args) {
223
+ const result = await run("git", args);
224
+ failIfNonZero("git", args, result);
225
+ }
226
+ /**
227
+ * Check out `ref` in `dir` without letting a `--…`-shaped ref inject a git
228
+ * option (S-1). `git checkout` does NOT honor `--end-of-options` — it reads the
229
+ * marker itself as a pathspec (verified on git 2.43) — so we resolve the ref to
230
+ * a commit SHA with `git rev-parse` (which DOES honor it, the injection guard
231
+ * that works), then check out that SHA — a hex object id can't be an option.
232
+ */
233
+ async function checkoutRef(dir, ref) {
234
+ const resolved = await run("git", [
235
+ "-C",
236
+ dir,
237
+ "rev-parse",
238
+ "--verify",
239
+ "--end-of-options",
240
+ `${ref}^{commit}`,
241
+ ]);
242
+ failIfNonZero("git", ["-C", dir, "rev-parse", ref], resolved);
243
+ await git(["-C", dir, "checkout", resolved.stdout.trim()]);
244
+ }
245
+ /** Run a `docker …` command from the deploy dir; non-zero exit → teaching error. */
246
+ async function dockerInDir(args, cwd) {
247
+ const result = await run("docker", args, { cwd });
248
+ failIfNonZero("docker", args, result);
249
+ }
250
+ /** Stop the old container. A not-running/not-found container is fine (we recreate). */
251
+ async function dockerStop() {
252
+ const result = await run("docker", ["stop", CONTAINER_NAME]);
253
+ if (result.code === 0)
254
+ return;
255
+ if (isNotFound(result.stderr))
256
+ return; // nothing to stop — proceed to rm/run
257
+ failIfNonZero("docker", ["stop", CONTAINER_NAME], result);
258
+ }
259
+ /**
260
+ * Remove the old CONTAINER (NOT the volume). `docker rm <name>` — never `-v`,
261
+ * never `docker volume rm`. A not-found container is fine (we recreate).
262
+ */
263
+ async function dockerRm() {
264
+ const result = await run("docker", ["rm", CONTAINER_NAME]);
265
+ if (result.code === 0)
266
+ return;
267
+ if (isNotFound(result.stderr))
268
+ return; // already gone — proceed to run
269
+ failIfNonZero("docker", ["rm", CONTAINER_NAME], result);
270
+ }
271
+ /** Apply pending data-dir migrations inside the (now-healthy) container. */
272
+ async function dockerExecMigrate() {
273
+ const args = ["exec", CONTAINER_NAME, CONTAINER_NAME, "migrate-data-dir"];
274
+ const result = await run("docker", args);
275
+ failIfNonZero("docker", args, result);
276
+ }
277
+ /** True when a docker error means the container simply isn't there. */
278
+ function isNotFound(stderr) {
279
+ return /no such container|no such object|is not running/i.test(stderr);
280
+ }
281
+ function failIfNonZero(cmd, args, result) {
282
+ if (result.code === 0)
283
+ return;
284
+ // Redact in case a non-zero docker step echoed a secret-shaped line.
285
+ const detail = redactSecrets(result.stderr.trim() || result.stdout.trim());
286
+ throw new UpdateError(`\`${cmd} ${args[0]}\` failed (exit ${result.code ?? "signal"})` +
287
+ (detail ? `:\n${detail}` : ".") +
288
+ "\n\nResolve the error above, then re-run `librarian server update`.");
289
+ }
290
+ // --- success report ------------------------------------------------------
291
+ /**
292
+ * The success report. `freshToken` is non-null ONLY when we had to mint a new
293
+ * agent token (the old container's token was unreadable) — then we surface it
294
+ * ONCE with a clients-must-update note. When the existing token was reused,
295
+ * nothing about the token is printed (clients keep working untouched).
296
+ */
297
+ function renderSuccess(ref, freshToken) {
298
+ const lines = [
299
+ `Updated The Librarian server to ${ref} — the container is healthy.`,
300
+ "The data volume was preserved; pending data-dir migrations were applied.",
301
+ ];
302
+ if (freshToken) {
303
+ lines.push("", "NOTE: the previous container's agent token could not be read back, so a " +
304
+ "FRESH agent token was minted. Existing clients must update their token:", ` Agent token: ${freshToken}`, "Paste it into `librarian install` / `librarian config --token <token>` on your clients.");
305
+ }
306
+ return lines.join("\n");
307
+ }
308
+ //# sourceMappingURL=update.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update.js","sourceRoot":"","sources":["../../src/server/update.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Resolve the monorepo's latest release version (the tag, `v`-stripped), or
3
+ * `null` on ANY failure (offline, timeout, non-2xx, unparseable). Injectable
4
+ * so tests never hit the network.
5
+ */
6
+ export type LatestFetcher = () => Promise<string | null>;
7
+ /** Override the latest-version fetcher (tests inject a canned/failing one). */
8
+ export declare function setLatestFetcher(next: LatestFetcher): void;
9
+ /** Restore the default (network) latest-version fetcher (tests). */
10
+ export declare function resetLatestFetcher(): void;
11
+ /** The default fetcher, exported for direct use / testing of the GET itself. */
12
+ export declare const fetchLatestVersion: LatestFetcher;
13
+ /**
14
+ * Probe every harness in parallel and the latest version once, returning the
15
+ * rendered table. `home` is injectable for tests.
16
+ */
17
+ export declare function status(home?: string): Promise<string>;
18
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAyBA;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AA0BzD,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CAE1D;AAED,oEAAoE;AACpE,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED,gFAAgF;AAChF,eAAO,MAAM,kBAAkB,EAAE,aAAqC,CAAC;AAWvE;;;GAGG;AACH,wBAAsB,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAY3D"}
package/dist/status.js ADDED
@@ -0,0 +1,113 @@
1
+ // `librarian status` — live-probe every harness and render a plain table.
2
+ //
3
+ // Columns: harness · installed · version · latest · update? · url.
4
+ // - installed/version come from each harness's live `detect()` (never a
5
+ // cached file — spec §9);
6
+ // - `latest` is the monorepo's latest GitHub release tag, fetched once and
7
+ // shared across rows;
8
+ // - `update?` is `yes`/`no`/`?` — `?` whenever the installed version or the
9
+ // latest version is unknown, so an offline run never lies;
10
+ // - `url` is the configured MCP URL (from `readConfig`), shown only when a
11
+ // harness is installed.
12
+ //
13
+ // The "latest" fetch is INJECTABLE (`setLatestFetcher`) and OFFLINE-TOLERANT:
14
+ // any failure resolves to `null`, latest renders `unknown`, and `update?`
15
+ // renders `?`. The CLI never crashes because GitHub was unreachable.
16
+ import { readConfig } from "./config.js";
17
+ import { allHarnesses } from "./harnesses/index.js";
18
+ import { isBehind } from "./semver.js";
19
+ /** GitHub releases API for the monorepo (latest published release). */
20
+ const LATEST_RELEASE_URL = "https://api.github.com/repos/JimJafar/the-librarian/releases/latest";
21
+ /** A short timeout so an unreachable network never hangs `status`. */
22
+ const FETCH_TIMEOUT_MS = 3000;
23
+ const defaultLatestFetcher = async () => {
24
+ const controller = new AbortController();
25
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
26
+ try {
27
+ const res = await fetch(LATEST_RELEASE_URL, {
28
+ redirect: "error",
29
+ signal: controller.signal,
30
+ headers: { Accept: "application/vnd.github+json" },
31
+ });
32
+ if (!res.ok)
33
+ return null;
34
+ const body = (await res.json());
35
+ const tag = typeof body.tag_name === "string" ? body.tag_name.trim() : "";
36
+ if (!tag)
37
+ return null;
38
+ return tag.replace(/^v/i, "");
39
+ }
40
+ catch {
41
+ // Offline, DNS failure, timeout, redirect, bad JSON — all → unknown.
42
+ return null;
43
+ }
44
+ finally {
45
+ clearTimeout(timer);
46
+ }
47
+ };
48
+ let latestFetcher = defaultLatestFetcher;
49
+ /** Override the latest-version fetcher (tests inject a canned/failing one). */
50
+ export function setLatestFetcher(next) {
51
+ latestFetcher = next;
52
+ }
53
+ /** Restore the default (network) latest-version fetcher (tests). */
54
+ export function resetLatestFetcher() {
55
+ latestFetcher = defaultLatestFetcher;
56
+ }
57
+ /** The default fetcher, exported for direct use / testing of the GET itself. */
58
+ export const fetchLatestVersion = () => latestFetcher();
59
+ /**
60
+ * Probe every harness in parallel and the latest version once, returning the
61
+ * rendered table. `home` is injectable for tests.
62
+ */
63
+ export async function status(home) {
64
+ const [detections, latest] = await Promise.all([
65
+ Promise.all(allHarnesses.map((h) => h.detect())),
66
+ fetchLatestVersion(),
67
+ ]);
68
+ const cfg = readConfig(home);
69
+ const mcpUrl = cfg?.mcpUrl ?? "";
70
+ const rows = allHarnesses.map((h, i) => toRow(h, detections[i] ?? { installed: false }, latest, mcpUrl));
71
+ return renderTable(rows, latest);
72
+ }
73
+ function toRow(harness, detect, latest, mcpUrl) {
74
+ const version = detect.version ?? "";
75
+ const update = !detect.installed
76
+ ? "no"
77
+ : !version || !latest
78
+ ? "?"
79
+ : isBehind(version, latest)
80
+ ? "yes"
81
+ : "no";
82
+ return {
83
+ harness: harness.displayName,
84
+ installed: detect.installed,
85
+ version: detect.installed ? version || "(unknown)" : "—",
86
+ latest: latest ?? "unknown",
87
+ update,
88
+ url: detect.installed ? mcpUrl || "—" : "—",
89
+ };
90
+ }
91
+ // --- plain-text table rendering (aligned, no table lib) ------------------
92
+ const HEADERS = ["HARNESS", "INSTALLED", "VERSION", "LATEST", "UPDATE?", "URL"];
93
+ function renderTable(rows, latest) {
94
+ const cells = rows.map((r) => [
95
+ r.harness,
96
+ r.installed ? "yes" : "no",
97
+ r.version,
98
+ r.latest,
99
+ r.update,
100
+ r.url,
101
+ ]);
102
+ const widths = HEADERS.map((h, col) => Math.max(h.length, ...cells.map((row) => (row[col] ?? "").length)));
103
+ const line = (parts) => parts
104
+ .map((part, col) => part.padEnd(widths[col] ?? part.length))
105
+ .join(" ")
106
+ .trimEnd();
107
+ const out = [line(HEADERS), ...cells.map(line)];
108
+ if (latest === null) {
109
+ out.push("", "latest version unknown (could not reach GitHub) — update column shows ?");
110
+ }
111
+ return out.join("\n");
112
+ }
113
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function cliVersion(): string;
2
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAUA,wBAAgB,UAAU,IAAI,MAAM,CASnC"}
@@ -0,0 +1,20 @@
1
+ // Resolve the CLI's own version from its package.json.
2
+ //
3
+ // At runtime the built bin lives at `dist/bin.js`, so package.json is one
4
+ // directory up. Reading it (rather than baking the version in) keeps the
5
+ // reported version in lockstep with the published package — the phase gate
6
+ // owns the actual version bump.
7
+ import fs from "node:fs";
8
+ import { fileURLToPath } from "node:url";
9
+ export function cliVersion() {
10
+ try {
11
+ const pkgUrl = new URL("../package.json", import.meta.url);
12
+ const raw = fs.readFileSync(fileURLToPath(pkgUrl), "utf8");
13
+ const parsed = JSON.parse(raw);
14
+ return typeof parsed.version === "string" ? parsed.version : "0.0.0";
15
+ }
16
+ catch {
17
+ return "0.0.0";
18
+ }
19
+ }
20
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,EAAE;AACF,0EAA0E;AAC1E,yEAAyE;AACzE,2EAA2E;AAC3E,gCAAgC;AAEhC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,UAAU,UAAU;IACxB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC3D,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;QAC3D,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA0B,CAAC;QACxD,OAAO,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;IACvE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@the-librarian/cli",
3
+ "version": "1.0.0-rc.21",
4
+ "private": false,
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "type": "module",
9
+ "description": "Cross-harness installer CLI for The Librarian. Bin: librarian.",
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "bin": {
13
+ "librarian": "dist/bin.js"
14
+ },
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js"
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "devDependencies": {
25
+ "@types/node": "^22.10.5",
26
+ "typescript": "^5.7.2",
27
+ "vitest": "^2.1.8"
28
+ },
29
+ "license": "Apache-2.0",
30
+ "scripts": {
31
+ "build": "tsc -p tsconfig.json",
32
+ "test:vitest": "vitest run",
33
+ "test": "pnpm run build && pnpm run test:vitest",
34
+ "typecheck": "tsc --noEmit"
35
+ }
36
+ }