@the-librarian/cli 1.0.0-rc.3

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 (106) hide show
  1. package/README.md +94 -0
  2. package/dist/bin.d.ts +3 -0
  3. package/dist/bin.d.ts.map +1 -0
  4. package/dist/bin.js +15 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/commands/install.d.ts +22 -0
  7. package/dist/commands/install.d.ts.map +1 -0
  8. package/dist/commands/install.js +152 -0
  9. package/dist/commands/install.js.map +1 -0
  10. package/dist/commands/shared.d.ts +16 -0
  11. package/dist/commands/shared.d.ts.map +1 -0
  12. package/dist/commands/shared.js +30 -0
  13. package/dist/commands/shared.js.map +1 -0
  14. package/dist/commands/uninstall.d.ts +18 -0
  15. package/dist/commands/uninstall.d.ts.map +1 -0
  16. package/dist/commands/uninstall.js +67 -0
  17. package/dist/commands/uninstall.js.map +1 -0
  18. package/dist/commands/update.d.ts +21 -0
  19. package/dist/commands/update.d.ts.map +1 -0
  20. package/dist/commands/update.js +75 -0
  21. package/dist/commands/update.js.map +1 -0
  22. package/dist/config.d.ts +40 -0
  23. package/dist/config.d.ts.map +1 -0
  24. package/dist/config.js +65 -0
  25. package/dist/config.js.map +1 -0
  26. package/dist/doctor.d.ts +23 -0
  27. package/dist/doctor.d.ts.map +1 -0
  28. package/dist/doctor.js +81 -0
  29. package/dist/doctor.js.map +1 -0
  30. package/dist/env.d.ts +39 -0
  31. package/dist/env.d.ts.map +1 -0
  32. package/dist/env.js +208 -0
  33. package/dist/env.js.map +1 -0
  34. package/dist/exec.d.ts +37 -0
  35. package/dist/exec.d.ts.map +1 -0
  36. package/dist/exec.js +81 -0
  37. package/dist/exec.js.map +1 -0
  38. package/dist/harnesses/claude.d.ts +3 -0
  39. package/dist/harnesses/claude.d.ts.map +1 -0
  40. package/dist/harnesses/claude.js +88 -0
  41. package/dist/harnesses/claude.js.map +1 -0
  42. package/dist/harnesses/cli.d.ts +4 -0
  43. package/dist/harnesses/cli.d.ts.map +1 -0
  44. package/dist/harnesses/cli.js +16 -0
  45. package/dist/harnesses/cli.js.map +1 -0
  46. package/dist/harnesses/codex.d.ts +3 -0
  47. package/dist/harnesses/codex.d.ts.map +1 -0
  48. package/dist/harnesses/codex.js +183 -0
  49. package/dist/harnesses/codex.js.map +1 -0
  50. package/dist/harnesses/hermes.d.ts +16 -0
  51. package/dist/harnesses/hermes.d.ts.map +1 -0
  52. package/dist/harnesses/hermes.js +154 -0
  53. package/dist/harnesses/hermes.js.map +1 -0
  54. package/dist/harnesses/index.d.ts +12 -0
  55. package/dist/harnesses/index.d.ts.map +1 -0
  56. package/dist/harnesses/index.js +29 -0
  57. package/dist/harnesses/index.js.map +1 -0
  58. package/dist/harnesses/opencode.d.ts +3 -0
  59. package/dist/harnesses/opencode.d.ts.map +1 -0
  60. package/dist/harnesses/opencode.js +141 -0
  61. package/dist/harnesses/opencode.js.map +1 -0
  62. package/dist/harnesses/pi.d.ts +3 -0
  63. package/dist/harnesses/pi.d.ts.map +1 -0
  64. package/dist/harnesses/pi.js +72 -0
  65. package/dist/harnesses/pi.js.map +1 -0
  66. package/dist/harnesses/types.d.ts +48 -0
  67. package/dist/harnesses/types.d.ts.map +1 -0
  68. package/dist/harnesses/types.js +22 -0
  69. package/dist/harnesses/types.js.map +1 -0
  70. package/dist/index.d.ts +11 -0
  71. package/dist/index.d.ts.map +1 -0
  72. package/dist/index.js +15 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/machine.d.ts +10 -0
  75. package/dist/machine.d.ts.map +1 -0
  76. package/dist/machine.js +39 -0
  77. package/dist/machine.js.map +1 -0
  78. package/dist/parse-args.d.ts +12 -0
  79. package/dist/parse-args.d.ts.map +1 -0
  80. package/dist/parse-args.js +54 -0
  81. package/dist/parse-args.js.map +1 -0
  82. package/dist/paths.d.ts +32 -0
  83. package/dist/paths.d.ts.map +1 -0
  84. package/dist/paths.js +75 -0
  85. package/dist/paths.js.map +1 -0
  86. package/dist/prompt.d.ts +46 -0
  87. package/dist/prompt.d.ts.map +1 -0
  88. package/dist/prompt.js +115 -0
  89. package/dist/prompt.js.map +1 -0
  90. package/dist/runtime.d.ts +25 -0
  91. package/dist/runtime.d.ts.map +1 -0
  92. package/dist/runtime.js +169 -0
  93. package/dist/runtime.js.map +1 -0
  94. package/dist/semver.d.ts +13 -0
  95. package/dist/semver.d.ts.map +1 -0
  96. package/dist/semver.js +98 -0
  97. package/dist/semver.js.map +1 -0
  98. package/dist/status.d.ts +18 -0
  99. package/dist/status.d.ts.map +1 -0
  100. package/dist/status.js +113 -0
  101. package/dist/status.js.map +1 -0
  102. package/dist/version.d.ts +2 -0
  103. package/dist/version.d.ts.map +1 -0
  104. package/dist/version.js +20 -0
  105. package/dist/version.js.map +1 -0
  106. package/package.json +37 -0
@@ -0,0 +1,46 @@
1
+ import type { Readable, Writable } from "node:stream";
2
+ export interface PromptTextOptions {
3
+ /** Value used when the user just hits enter (or in non-interactive mode). */
4
+ default?: string;
5
+ /** Don't echo the typed characters (used for the token). */
6
+ secret?: boolean;
7
+ }
8
+ /**
9
+ * The injectable prompt function. Given a fully-rendered question (the caller
10
+ * has already appended any `[default]` hint), it resolves the user's raw
11
+ * answer. `secret` lets a fake distinguish a token prompt if it wants to.
12
+ */
13
+ export type PromptFn = (question: string, opts: {
14
+ secret: boolean;
15
+ }) => Promise<string>;
16
+ export interface Prompter {
17
+ selectHarnesses(available: HarnessChoice[]): Promise<string[]>;
18
+ promptText(question: string, opts?: PromptTextOptions): Promise<string>;
19
+ }
20
+ /** A pickable harness in the multi-select. */
21
+ export interface HarnessChoice {
22
+ id: string;
23
+ label: string;
24
+ }
25
+ export interface PrompterOptions {
26
+ /** Inject a deterministic answer function (tests). Wins over streams. */
27
+ prompt?: PromptFn;
28
+ /** The stream questions are read from. Defaults to `process.stdin`. */
29
+ input?: Readable;
30
+ /** The stream prompts/labels are written to. Defaults to `process.stdout`. */
31
+ output?: Writable;
32
+ /**
33
+ * Force interactive vs non-interactive. Defaults to whether `input` is a
34
+ * TTY. Non-interactive never blocks on a read.
35
+ */
36
+ interactive?: boolean;
37
+ }
38
+ /** A clear, typed error thrown when a required value can't be obtained. */
39
+ export declare class MissingValueError extends Error {
40
+ constructor(what: string);
41
+ }
42
+ /** Build a Prompter over the given options (defaults to the real stdio). */
43
+ export declare function createPrompter(options?: PrompterOptions): Prompter;
44
+ /** Map a multi-select answer string to the chosen harness ids. */
45
+ export declare function resolveSelection(answer: string, available: HarnessChoice[]): string[];
46
+ //# sourceMappingURL=prompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEtD,MAAM,WAAW,iBAAiB;IAChC,6EAA6E;IAC7E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAExF,MAAM,WAAW,QAAQ;IACvB,eAAe,CAAC,SAAS,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC/D,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACzE;AAED,8CAA8C;AAC9C,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,yEAAyE;IACzE,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,uEAAuE;IACvE,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,2EAA2E;AAC3E,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,IAAI,EAAE,MAAM;CAIzB;AAED,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,OAAO,GAAE,eAAoB,GAAG,QAAQ,CAqCtE;AAED,kEAAkE;AAClE,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,GAAG,MAAM,EAAE,CAarF"}
package/dist/prompt.js ADDED
@@ -0,0 +1,115 @@
1
+ // A tiny `node:readline`-based prompt layer, injectable for tests.
2
+ //
3
+ // Two surfaces:
4
+ // - `selectHarnesses(available)` — a numbered multi-select; the user types
5
+ // numbers (`1 3`), `all`, or `none`.
6
+ // - `promptText(question, opts)` — a single line; `secret` suppresses the
7
+ // echo (for the token).
8
+ //
9
+ // EVERYTHING is injectable so tests never touch a real TTY or stdin:
10
+ // - pass a `prompt` fn to answer questions deterministically, OR
11
+ // - pass `input`/`output` streams the readline reads from / writes to.
12
+ // Non-interactive (no TTY, no injected prompt) NEVER hangs: `selectHarnesses`
13
+ // falls back to "everything available", and `promptText` returns the default
14
+ // (or throws a clear error when a required value has no default).
15
+ import readline from "node:readline";
16
+ /** A clear, typed error thrown when a required value can't be obtained. */
17
+ export class MissingValueError extends Error {
18
+ constructor(what) {
19
+ super(`${what} is required but no value was provided (non-interactive run).`);
20
+ this.name = "MissingValueError";
21
+ }
22
+ }
23
+ /** Build a Prompter over the given options (defaults to the real stdio). */
24
+ export function createPrompter(options = {}) {
25
+ const input = options.input ?? process.stdin;
26
+ const output = options.output ?? process.stdout;
27
+ const interactive = options.interactive ??
28
+ (options.prompt !== undefined ? true : Boolean(input.isTTY));
29
+ const ask = options.prompt ?? ((question, opts) => readLine(input, output, question, opts.secret));
30
+ return {
31
+ async selectHarnesses(available) {
32
+ if (available.length === 0)
33
+ return [];
34
+ if (!interactive && options.prompt === undefined) {
35
+ // No way to ask — default to every available harness.
36
+ return available.map((h) => h.id);
37
+ }
38
+ write(output, "Select harnesses to install:\n");
39
+ available.forEach((h, i) => write(output, ` ${i + 1}) ${h.label}\n`));
40
+ write(output, "Enter numbers (e.g. 1 3), 'all', or 'none' [all]: ");
41
+ const answer = (await ask("", { secret: false })).trim();
42
+ return resolveSelection(answer, available);
43
+ },
44
+ async promptText(question, opts = {}) {
45
+ const hasDefault = opts.default !== undefined;
46
+ if (!interactive && options.prompt === undefined) {
47
+ if (hasDefault)
48
+ return opts.default;
49
+ throw new MissingValueError(question);
50
+ }
51
+ const hint = hasDefault && !opts.secret ? ` [${opts.default}]` : "";
52
+ const raw = (await ask(`${question}${hint}: `, { secret: Boolean(opts.secret) })).trim();
53
+ if (raw.length === 0 && hasDefault)
54
+ return opts.default;
55
+ if (raw.length === 0 && !hasDefault)
56
+ throw new MissingValueError(question);
57
+ return raw;
58
+ },
59
+ };
60
+ }
61
+ /** Map a multi-select answer string to the chosen harness ids. */
62
+ export function resolveSelection(answer, available) {
63
+ const normalized = answer.trim().toLowerCase();
64
+ if (normalized === "" || normalized === "all")
65
+ return available.map((h) => h.id);
66
+ if (normalized === "none")
67
+ return [];
68
+ const picked = new Set();
69
+ for (const token of normalized.split(/[\s,]+/).filter(Boolean)) {
70
+ const n = Number.parseInt(token, 10);
71
+ if (Number.isInteger(n) && n >= 1 && n <= available.length) {
72
+ const choice = available[n - 1];
73
+ if (choice)
74
+ picked.add(choice.id);
75
+ }
76
+ }
77
+ return available.filter((h) => picked.has(h.id)).map((h) => h.id);
78
+ }
79
+ // --- readline plumbing ---------------------------------------------------
80
+ function write(output, text) {
81
+ output.write(text);
82
+ }
83
+ /** Read one line, optionally with the echo muted (secret entry). */
84
+ function readLine(input, output, question, secret) {
85
+ return new Promise((resolve) => {
86
+ const rl = readline.createInterface({
87
+ input,
88
+ output,
89
+ terminal: true,
90
+ });
91
+ if (secret) {
92
+ // Mute the echo: overwrite each keystroke so the token never renders.
93
+ const muteWrite = rl._writeToOutput;
94
+ rl._writeToOutput = (s) => {
95
+ if (s.includes("\n") || s.includes("\r")) {
96
+ muteWrite?.call(rl, s);
97
+ }
98
+ // else: swallow the character so it isn't echoed.
99
+ };
100
+ if (question)
101
+ write(output, question);
102
+ rl.question("", (answer) => {
103
+ write(output, "\n");
104
+ rl.close();
105
+ resolve(answer);
106
+ });
107
+ return;
108
+ }
109
+ rl.question(question, (answer) => {
110
+ rl.close();
111
+ resolve(answer);
112
+ });
113
+ });
114
+ }
115
+ //# sourceMappingURL=prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.js","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { type Shell } from "./env.js";
2
+ import { type Prompter } from "./prompt.js";
3
+ export interface CliResult {
4
+ stdout: string;
5
+ stderr: string;
6
+ exitCode: number;
7
+ }
8
+ export interface RuntimeOptions {
9
+ /** Override the home dir (tests). Defaults to the real `os.homedir()`. */
10
+ home?: string;
11
+ /** Override the detected shell (tests / `--shell`). */
12
+ shell?: Shell;
13
+ /** Inject a prompter (tests). Defaults to a real stdio-backed prompter. */
14
+ prompter?: Prompter;
15
+ }
16
+ /**
17
+ * The CLI entrypoint. Dispatches to the command handlers and — crucially —
18
+ * turns any escaped error into one clean stderr line + exit 1, so a failure
19
+ * never leaks a stack trace into the user's terminal (house rule: "never
20
+ * leak a stack trace"). `MissingValueError` (a required prompt with no value
21
+ * in a non-interactive run) gets a specific, actionable message.
22
+ */
23
+ export declare function runCli(argv: string[], options?: RuntimeOptions): Promise<CliResult>;
24
+ export declare function usage(): string;
25
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAe,KAAK,KAAK,EAAE,MAAM,UAAU,CAAC;AAGnD,OAAO,EAAqC,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAI/E,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAID;;;;;;GAMG;AACH,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,SAAS,CAAC,CAY7F;AAiHD,wBAAgB,KAAK,IAAI,MAAM,CAwB9B"}
@@ -0,0 +1,169 @@
1
+ // The `librarian` CLI runtime — an async function over argv.
2
+ //
3
+ // `runCli(argv, options)` resolves to `{ stdout, stderr, exitCode }` so the
4
+ // bin entry shapes it into a real process exit and tests assert against
5
+ // captured output without spawning a subprocess. Everything that touches the
6
+ // system is injectable through `options`: `home` (temp dir), `shell`, a
7
+ // `prompter` (prompt answers), and the per-module seams the commands pull in
8
+ // (`setRunner` / `setHomeOverride` / `setAdapterFetcher` / `setLatestFetcher`
9
+ // / `setServerProbe`). NOTHING here touches the real system, network, or
10
+ // stdin in tests.
11
+ import { runInstall } from "./commands/install.js";
12
+ import { runUninstall } from "./commands/uninstall.js";
13
+ import { runUpdate } from "./commands/update.js";
14
+ import { formatConfig, readConfig, redact, setConfig } from "./config.js";
15
+ import { doctor } from "./doctor.js";
16
+ import { detectShell } from "./env.js";
17
+ import { allHarnesses } from "./harnesses/index.js";
18
+ import { flagString, parseArgs } from "./parse-args.js";
19
+ import { createPrompter, MissingValueError } from "./prompt.js";
20
+ import { status } from "./status.js";
21
+ import { cliVersion } from "./version.js";
22
+ const PHASE_2_STUBS = new Set(["report", "self-update"]);
23
+ /**
24
+ * The CLI entrypoint. Dispatches to the command handlers and — crucially —
25
+ * turns any escaped error into one clean stderr line + exit 1, so a failure
26
+ * never leaks a stack trace into the user's terminal (house rule: "never
27
+ * leak a stack trace"). `MissingValueError` (a required prompt with no value
28
+ * in a non-interactive run) gets a specific, actionable message.
29
+ */
30
+ export async function runCli(argv, options = {}) {
31
+ try {
32
+ return await dispatch(argv, options);
33
+ }
34
+ catch (error) {
35
+ if (error instanceof MissingValueError) {
36
+ return err("MCP URL and token are required — re-run interactively, or pass --mcp-url/--token.");
37
+ }
38
+ const message = error instanceof Error ? error.message : String(error);
39
+ return err(`librarian: ${message}`);
40
+ }
41
+ }
42
+ async function dispatch(argv, options) {
43
+ const [command, ...rest] = argv;
44
+ if (!command || command === "--help" || command === "-h" || command === "help") {
45
+ return ok(usage());
46
+ }
47
+ if (command === "--version" || command === "-v" || command === "version") {
48
+ return ok(cliVersion());
49
+ }
50
+ if (command === "config") {
51
+ return runConfig(rest, options);
52
+ }
53
+ if (command === "install") {
54
+ return runInstallCommand(rest, options);
55
+ }
56
+ if (command === "uninstall") {
57
+ return runUninstallCommand(rest, options);
58
+ }
59
+ if (command === "update") {
60
+ return runUpdateCommand(rest, options);
61
+ }
62
+ if (command === "status") {
63
+ return ok(await status(options.home));
64
+ }
65
+ if (command === "doctor") {
66
+ // Diagnostic: exits 0 even when it flags problems.
67
+ return ok(await doctor(options.home));
68
+ }
69
+ if (PHASE_2_STUBS.has(command)) {
70
+ return runPhase2Stub(command);
71
+ }
72
+ return err(`Unknown command: ${command}\n\n${usage()}`);
73
+ }
74
+ // --- config (fully implemented) ------------------------------------------
75
+ function runConfig(rest, options) {
76
+ const { flags } = parseArgs(rest);
77
+ const mcpUrl = flagString(flags["mcp-url"]) ?? flagString(flags.url);
78
+ const token = flagString(flags.token);
79
+ const shell = options.shell ?? resolveShellFlag(flags);
80
+ const wantsSet = mcpUrl !== undefined || token !== undefined;
81
+ if (wantsSet) {
82
+ const updated = setConfig({ mcpUrl, token }, { home: options.home, shell });
83
+ // Confirm what changed WITHOUT echoing the token.
84
+ return ok(["Updated config.", "", formatConfig(redact(updated))].join("\n"));
85
+ }
86
+ const current = readConfig(options.home);
87
+ if (!current) {
88
+ return ok([
89
+ "No config set yet.",
90
+ "",
91
+ "Set it with:",
92
+ " librarian config --mcp-url <url> --token <token>",
93
+ ].join("\n"));
94
+ }
95
+ return ok(formatConfig(redact(current)));
96
+ }
97
+ function resolveShellFlag(flags) {
98
+ const raw = flagString(flags.shell);
99
+ if (!raw)
100
+ return undefined;
101
+ return detectShell(raw);
102
+ }
103
+ // --- harness-touching commands -------------------------------------------
104
+ async function runInstallCommand(rest, options) {
105
+ const { positionals, flags } = parseArgs(rest);
106
+ const shell = options.shell ?? resolveShellFlag(flags);
107
+ const prompter = options.prompter ?? createPrompter();
108
+ const outcome = await runInstall(positionals, { home: options.home, shell, prompter });
109
+ // A mid-install failure makes the command non-zero so scripts notice; a
110
+ // pure skip (CLI absent) is a success.
111
+ return outcome.failed.length > 0 ? errOut(outcome.output) : ok(outcome.output);
112
+ }
113
+ async function runUninstallCommand(rest, options) {
114
+ const { positionals, flags } = parseArgs(rest);
115
+ const shell = options.shell ?? resolveShellFlag(flags);
116
+ const prompter = options.prompter ?? createPrompter();
117
+ const outcome = await runUninstall(positionals, { home: options.home, shell, prompter });
118
+ return outcome.failed.length > 0 ? errOut(outcome.output) : ok(outcome.output);
119
+ }
120
+ async function runUpdateCommand(rest, options) {
121
+ const { positionals } = parseArgs(rest);
122
+ const outcome = await runUpdate(positionals, { home: options.home });
123
+ return outcome.failed.length > 0 ? errOut(outcome.output) : ok(outcome.output);
124
+ }
125
+ // --- Phase 2 stubs (friendly "coming later") -----------------------------
126
+ function runPhase2Stub(command) {
127
+ const detail = command === "report"
128
+ ? "Server reporting (the dashboard's Installs view) arrives in a later release."
129
+ : "Self-update of the CLI arrives in a later release. For now: `npm i -g @the-librarian/cli`.";
130
+ return ok(`librarian ${command}: coming in a later release.\n\n${detail}`);
131
+ }
132
+ // --- usage ---------------------------------------------------------------
133
+ export function usage() {
134
+ const harnesses = allHarnesses.map((h) => h.id).join(", ");
135
+ return [
136
+ "Usage: librarian <command> [harness…] [flags]",
137
+ "",
138
+ "Commands:",
139
+ " install [harness…] Install The Librarian into one or more harnesses",
140
+ " uninstall [harness…] Remove The Librarian from one or more harnesses",
141
+ " update [harness…] Update the integration to the current version",
142
+ " status Live table of harness / installed / version",
143
+ " doctor Diagnose token, server reachability, harness CLIs",
144
+ " config Show or set MCP URL, token, server URL",
145
+ " self-update Update the librarian CLI itself",
146
+ " report Push this machine's state to the server",
147
+ "",
148
+ "Flags:",
149
+ " --mcp-url <url> config: set the MCP endpoint URL",
150
+ " --token <token> config: set the bearer token (never printed)",
151
+ " --shell <bash|zsh|fish> override shell detection for the rc block",
152
+ " -h, --help Show this help",
153
+ " -v, --version Show the CLI version",
154
+ "",
155
+ `Harnesses: ${harnesses}`,
156
+ ].join("\n");
157
+ }
158
+ // --- result helpers ------------------------------------------------------
159
+ function ok(stdout) {
160
+ return { stdout, stderr: "", exitCode: 0 };
161
+ }
162
+ function err(stderr) {
163
+ return { stdout: "", stderr, exitCode: 1 };
164
+ }
165
+ /** A non-zero result that still carries its (already-formatted) report on stdout. */
166
+ function errOut(stdout) {
167
+ return { stdout, stderr: "", exitCode: 1 };
168
+ }
169
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Compare two version strings. Returns <0 if `a` precedes `b`, >0 if `a`
3
+ * follows `b`, 0 if equal or either is unparseable (so an unknown version
4
+ * never reads as "behind").
5
+ */
6
+ export declare function compareVersions(a: string, b: string): number;
7
+ /**
8
+ * True iff `installed` is strictly behind `latest`. Either side being unknown
9
+ * (empty / unparseable) yields `false` — the caller renders `?`, never a
10
+ * false "update available".
11
+ */
12
+ export declare function isBehind(installed: string, latest: string): boolean;
13
+ //# sourceMappingURL=semver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semver.d.ts","sourceRoot":"","sources":["../src/semver.ts"],"names":[],"mappings":"AAoCA;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAqB5D;AAyBD;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAGnE"}
package/dist/semver.js ADDED
@@ -0,0 +1,98 @@
1
+ // A tiny, dependency-light semver-ish comparison.
2
+ //
3
+ // We only need "is the installed version behind the latest release?" — not a
4
+ // full spec-compliant semver implementation. This tolerates the prerelease
5
+ // shapes the monorepo actually ships (`1.0.0`, `1.0.0-rc.2`, `v1.2.3`) and
6
+ // degrades gracefully on anything it can't parse (returns 0 → "equal", so an
7
+ // unparseable version is never reported as an update).
8
+ //
9
+ // Precedence rules (subset of semver §11):
10
+ // - numeric release fields compare numerically (1.2.0 < 1.10.0);
11
+ // - a version WITH a prerelease has LOWER precedence than the same version
12
+ // WITHOUT one (1.0.0-rc.1 < 1.0.0);
13
+ // - prerelease identifiers compare left-to-right; numeric identifiers
14
+ // compare numerically, others lexically, numeric < non-numeric.
15
+ /** Strip a leading `v` and any build-metadata (`+…`) we don't compare on. */
16
+ function parse(version) {
17
+ const trimmed = version.trim().replace(/^v/i, "");
18
+ if (!trimmed)
19
+ return null;
20
+ const [core, ...preParts] = trimmed.split("-");
21
+ // Drop build metadata (`+sha`) from the prerelease tail and the core.
22
+ const prerelease = preParts.join("-").split("+")[0] ?? "";
23
+ const coreNoBuild = (core ?? "").split("+")[0] ?? "";
24
+ const release = coreNoBuild.split(".").map((n) => Number.parseInt(n, 10));
25
+ if (release.length === 0 || release.some((n) => Number.isNaN(n)))
26
+ return null;
27
+ return {
28
+ release,
29
+ prerelease: prerelease.length > 0 ? prerelease.split(".") : [],
30
+ };
31
+ }
32
+ /**
33
+ * Compare two version strings. Returns <0 if `a` precedes `b`, >0 if `a`
34
+ * follows `b`, 0 if equal or either is unparseable (so an unknown version
35
+ * never reads as "behind").
36
+ */
37
+ export function compareVersions(a, b) {
38
+ const pa = parse(a);
39
+ const pb = parse(b);
40
+ if (!pa || !pb)
41
+ return 0;
42
+ const len = Math.max(pa.release.length, pb.release.length);
43
+ for (let i = 0; i < len; i += 1) {
44
+ const da = pa.release[i] ?? 0;
45
+ const db = pb.release[i] ?? 0;
46
+ if (da !== db)
47
+ return da < db ? -1 : 1;
48
+ }
49
+ // Equal release fields → a version with a prerelease is LOWER than one
50
+ // without (1.0.0-rc.1 < 1.0.0).
51
+ const aPre = pa.prerelease.length > 0;
52
+ const bPre = pb.prerelease.length > 0;
53
+ if (aPre && !bPre)
54
+ return -1;
55
+ if (!aPre && bPre)
56
+ return 1;
57
+ if (!aPre && !bPre)
58
+ return 0;
59
+ return comparePrerelease(pa.prerelease, pb.prerelease);
60
+ }
61
+ function comparePrerelease(a, b) {
62
+ const len = Math.max(a.length, b.length);
63
+ for (let i = 0; i < len; i += 1) {
64
+ // A longer prerelease list (with all-else-equal prefixes) has higher
65
+ // precedence (rc.1 < rc.1.1).
66
+ if (i >= a.length)
67
+ return -1;
68
+ if (i >= b.length)
69
+ return 1;
70
+ const ai = a[i] ?? "";
71
+ const bi = b[i] ?? "";
72
+ const an = /^\d+$/.test(ai);
73
+ const bn = /^\d+$/.test(bi);
74
+ if (an && bn) {
75
+ const na = Number.parseInt(ai, 10);
76
+ const nb = Number.parseInt(bi, 10);
77
+ if (na !== nb)
78
+ return na < nb ? -1 : 1;
79
+ continue;
80
+ }
81
+ if (an !== bn)
82
+ return an ? -1 : 1; // numeric identifiers rank below alphanumeric
83
+ if (ai !== bi)
84
+ return ai < bi ? -1 : 1;
85
+ }
86
+ return 0;
87
+ }
88
+ /**
89
+ * True iff `installed` is strictly behind `latest`. Either side being unknown
90
+ * (empty / unparseable) yields `false` — the caller renders `?`, never a
91
+ * false "update available".
92
+ */
93
+ export function isBehind(installed, latest) {
94
+ if (!installed || !latest)
95
+ return false;
96
+ return compareVersions(installed, latest) < 0;
97
+ }
98
+ //# sourceMappingURL=semver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semver.js","sourceRoot":"","sources":["../src/semver.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