@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.
- package/README.md +94 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +15 -0
- package/dist/bin.js.map +1 -0
- package/dist/commands/install.d.ts +22 -0
- package/dist/commands/install.d.ts.map +1 -0
- package/dist/commands/install.js +152 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/shared.d.ts +16 -0
- package/dist/commands/shared.d.ts.map +1 -0
- package/dist/commands/shared.js +30 -0
- package/dist/commands/shared.js.map +1 -0
- package/dist/commands/uninstall.d.ts +18 -0
- package/dist/commands/uninstall.d.ts.map +1 -0
- package/dist/commands/uninstall.js +67 -0
- package/dist/commands/uninstall.js.map +1 -0
- package/dist/commands/update.d.ts +21 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +75 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/config.d.ts +40 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +65 -0
- package/dist/config.js.map +1 -0
- package/dist/doctor.d.ts +23 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +81 -0
- package/dist/doctor.js.map +1 -0
- package/dist/env.d.ts +39 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +208 -0
- package/dist/env.js.map +1 -0
- package/dist/exec.d.ts +37 -0
- package/dist/exec.d.ts.map +1 -0
- package/dist/exec.js +81 -0
- package/dist/exec.js.map +1 -0
- package/dist/harnesses/claude.d.ts +3 -0
- package/dist/harnesses/claude.d.ts.map +1 -0
- package/dist/harnesses/claude.js +88 -0
- package/dist/harnesses/claude.js.map +1 -0
- package/dist/harnesses/cli.d.ts +4 -0
- package/dist/harnesses/cli.d.ts.map +1 -0
- package/dist/harnesses/cli.js +16 -0
- package/dist/harnesses/cli.js.map +1 -0
- package/dist/harnesses/codex.d.ts +3 -0
- package/dist/harnesses/codex.d.ts.map +1 -0
- package/dist/harnesses/codex.js +183 -0
- package/dist/harnesses/codex.js.map +1 -0
- package/dist/harnesses/hermes.d.ts +16 -0
- package/dist/harnesses/hermes.d.ts.map +1 -0
- package/dist/harnesses/hermes.js +154 -0
- package/dist/harnesses/hermes.js.map +1 -0
- package/dist/harnesses/index.d.ts +12 -0
- package/dist/harnesses/index.d.ts.map +1 -0
- package/dist/harnesses/index.js +29 -0
- package/dist/harnesses/index.js.map +1 -0
- package/dist/harnesses/opencode.d.ts +3 -0
- package/dist/harnesses/opencode.d.ts.map +1 -0
- package/dist/harnesses/opencode.js +141 -0
- package/dist/harnesses/opencode.js.map +1 -0
- package/dist/harnesses/pi.d.ts +3 -0
- package/dist/harnesses/pi.d.ts.map +1 -0
- package/dist/harnesses/pi.js +72 -0
- package/dist/harnesses/pi.js.map +1 -0
- package/dist/harnesses/types.d.ts +48 -0
- package/dist/harnesses/types.d.ts.map +1 -0
- package/dist/harnesses/types.js +22 -0
- package/dist/harnesses/types.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/machine.d.ts +10 -0
- package/dist/machine.d.ts.map +1 -0
- package/dist/machine.js +39 -0
- package/dist/machine.js.map +1 -0
- package/dist/parse-args.d.ts +12 -0
- package/dist/parse-args.d.ts.map +1 -0
- package/dist/parse-args.js +54 -0
- package/dist/parse-args.js.map +1 -0
- package/dist/paths.d.ts +32 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +75 -0
- package/dist/paths.js.map +1 -0
- package/dist/prompt.d.ts +46 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/prompt.js +115 -0
- package/dist/prompt.js.map +1 -0
- package/dist/runtime.d.ts +25 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +169 -0
- package/dist/runtime.js.map +1 -0
- package/dist/semver.d.ts +13 -0
- package/dist/semver.d.ts.map +1 -0
- package/dist/semver.js +98 -0
- package/dist/semver.js.map +1 -0
- package/dist/status.d.ts +18 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +113 -0
- package/dist/status.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +20 -0
- package/dist/version.js.map +1 -0
- package/package.json +37 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Claude Code harness.
|
|
2
|
+
//
|
|
3
|
+
// Drives the `claude` CLI's native plugin/marketplace commands — we never
|
|
4
|
+
// hand-edit Claude config. The marketplace is `JimJafar/the-librarian`;
|
|
5
|
+
// the plugin is `the-librarian@the-librarian` (name@marketplace).
|
|
6
|
+
//
|
|
7
|
+
// detect `claude` on PATH AND `claude plugin list` shows our plugin
|
|
8
|
+
// install `claude plugin marketplace add JimJafar/the-librarian`
|
|
9
|
+
// then `claude plugin install the-librarian@the-librarian`
|
|
10
|
+
// uninstall `claude plugin remove the-librarian@the-librarian`
|
|
11
|
+
// then `claude plugin marketplace remove the-librarian`
|
|
12
|
+
// update re-run install (idempotent; pulls the current version)
|
|
13
|
+
//
|
|
14
|
+
// The token plays no part here — Claude's MCP auth is configured by the
|
|
15
|
+
// plugin itself — so nothing token-shaped is ever passed or logged.
|
|
16
|
+
import { run, which } from "../exec.js";
|
|
17
|
+
const CLI = "claude";
|
|
18
|
+
const MARKETPLACE = "JimJafar/the-librarian";
|
|
19
|
+
const MARKETPLACE_ID = "the-librarian";
|
|
20
|
+
const PLUGIN = "the-librarian@the-librarian";
|
|
21
|
+
const PLUGIN_NAME = "the-librarian";
|
|
22
|
+
/** Friendly error when the harness CLI isn't installed. */
|
|
23
|
+
function notFound() {
|
|
24
|
+
return new Error("Claude Code CLI not found on PATH (expected `claude`).");
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Pull our plugin's version out of `claude plugin list` output, which
|
|
28
|
+
* formats loosely across versions. We look for a line mentioning the
|
|
29
|
+
* plugin and a trailing semver-ish token; absence is `undefined`.
|
|
30
|
+
*/
|
|
31
|
+
function parsePluginVersion(listOutput) {
|
|
32
|
+
for (const line of listOutput.split("\n")) {
|
|
33
|
+
if (!line.includes(PLUGIN_NAME))
|
|
34
|
+
continue;
|
|
35
|
+
const m = /\bv?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)\b/.exec(line);
|
|
36
|
+
if (m?.[1])
|
|
37
|
+
return m[1];
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
/** True iff `claude plugin list` output shows our plugin as present. */
|
|
42
|
+
function pluginListed(listOutput) {
|
|
43
|
+
return listOutput.split("\n").some((line) => line.includes(PLUGIN_NAME));
|
|
44
|
+
}
|
|
45
|
+
export const claude = {
|
|
46
|
+
id: "claude",
|
|
47
|
+
displayName: "Claude Code",
|
|
48
|
+
async detect() {
|
|
49
|
+
if (!(await which(CLI)))
|
|
50
|
+
return { installed: false };
|
|
51
|
+
const { stdout, code } = await run(CLI, ["plugin", "list"]);
|
|
52
|
+
if (code !== 0 || !pluginListed(stdout))
|
|
53
|
+
return { installed: false };
|
|
54
|
+
const version = parsePluginVersion(stdout);
|
|
55
|
+
return version === undefined ? { installed: true } : { installed: true, version };
|
|
56
|
+
},
|
|
57
|
+
async install(_cfg) {
|
|
58
|
+
if (!(await which(CLI)))
|
|
59
|
+
throw notFound();
|
|
60
|
+
// marketplace add is idempotent in the CLI; installing again is a no-op
|
|
61
|
+
// re-resolve, so a second `install` neither errors nor duplicates.
|
|
62
|
+
const add = await run(CLI, ["plugin", "marketplace", "add", MARKETPLACE]);
|
|
63
|
+
if (add.code !== 0) {
|
|
64
|
+
throw new Error(`claude plugin marketplace add failed: ${oneLine(add.stderr || add.stdout)}`);
|
|
65
|
+
}
|
|
66
|
+
const install = await run(CLI, ["plugin", "install", PLUGIN]);
|
|
67
|
+
if (install.code !== 0) {
|
|
68
|
+
throw new Error(`claude plugin install failed: ${oneLine(install.stderr || install.stdout)}`);
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
async uninstall() {
|
|
72
|
+
if (!(await which(CLI)))
|
|
73
|
+
return; // nothing to remove if the CLI is gone
|
|
74
|
+
// Removing an absent plugin / marketplace is a no-op; we don't fail the
|
|
75
|
+
// uninstall on a "not installed" exit, only surface genuine errors.
|
|
76
|
+
await run(CLI, ["plugin", "remove", PLUGIN]);
|
|
77
|
+
await run(CLI, ["plugin", "marketplace", "remove", MARKETPLACE_ID]);
|
|
78
|
+
},
|
|
79
|
+
async update(cfg) {
|
|
80
|
+
// Re-running install pulls the marketplace's current version.
|
|
81
|
+
await this.install(cfg);
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
/** Collapse multi-line CLI error text to a single tidy line. */
|
|
85
|
+
function oneLine(text) {
|
|
86
|
+
return text.replace(/\s+/g, " ").trim();
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=claude.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/harnesses/claude.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/harnesses/cli.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,6EAA6E;AAC7E,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAMxD,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Which native CLI binary (if any) each harness drives.
|
|
2
|
+
//
|
|
3
|
+
// The `HarnessModule` interface deliberately doesn't expose this — the
|
|
4
|
+
// modules are thin and uniform. But the orchestration layer needs it for two
|
|
5
|
+
// things: choosing the default install set (harnesses whose CLI is on PATH)
|
|
6
|
+
// and the `doctor` report ("which harness CLIs are present?"). File-based
|
|
7
|
+
// harnesses (opencode, hermes) have no CLI gate, so they map to `null`.
|
|
8
|
+
/** Harness id → the native CLI binary it drives, or `null` if file-based. */
|
|
9
|
+
export const HARNESS_CLI = {
|
|
10
|
+
claude: "claude",
|
|
11
|
+
codex: "codex",
|
|
12
|
+
opencode: null,
|
|
13
|
+
hermes: null,
|
|
14
|
+
pi: "pi",
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/harnesses/cli.ts"],"names":[],"mappings":"AAAA,wDAAwD;AACxD,EAAE;AACF,uEAAuE;AACvE,6EAA6E;AAC7E,4EAA4E;AAC5E,0EAA0E;AAC1E,wEAAwE;AAIxE,6EAA6E;AAC7E,MAAM,CAAC,MAAM,WAAW,GAAqC;IAC3D,MAAM,EAAE,QAAQ;IAChB,KAAK,EAAE,OAAO;IACd,QAAQ,EAAE,IAAI;IACd,MAAM,EAAE,IAAI;IACZ,EAAE,EAAE,IAAI;CACT,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../src/harnesses/codex.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAiB,aAAa,EAAE,MAAM,YAAY,CAAC;AA6F/D,eAAO,MAAM,KAAK,EAAE,aAuDnB,CAAC"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// Codex harness.
|
|
2
|
+
//
|
|
3
|
+
// Prefers the native `codex` CLI; falls back to editing the config file
|
|
4
|
+
// directly only when `codex` isn't on PATH but the config is still
|
|
5
|
+
// writable (so a partial Codex setup can still be wired up).
|
|
6
|
+
//
|
|
7
|
+
// detect `~/.codex/config.toml` has a `[mcp_servers.librarian]` table
|
|
8
|
+
// install `codex mcp add librarian --url <U>
|
|
9
|
+
// --bearer-token-env-var LIBRARIAN_AGENT_TOKEN`
|
|
10
|
+
// (fallback: write the table into config.toml)
|
|
11
|
+
// uninstall `codex mcp remove librarian` (fallback: strip the table)
|
|
12
|
+
// update re-run install (idempotent)
|
|
13
|
+
//
|
|
14
|
+
// Token handling (spec §9): the token's *value* never enters config or the
|
|
15
|
+
// command line. Codex stores only the env-var NAME `LIBRARIAN_AGENT_TOKEN`;
|
|
16
|
+
// it resolves the value at request time. We pass `cfg.mcpUrl` (not the
|
|
17
|
+
// token) on the CLI, so nothing secret is ever logged.
|
|
18
|
+
import fs from "node:fs";
|
|
19
|
+
import path from "node:path";
|
|
20
|
+
import { run, which } from "../exec.js";
|
|
21
|
+
import { codexConfigPath } from "../paths.js";
|
|
22
|
+
const CLI = "codex";
|
|
23
|
+
const SERVER_ID = "librarian";
|
|
24
|
+
const TOKEN_ENV_VAR = "LIBRARIAN_AGENT_TOKEN";
|
|
25
|
+
const TABLE_HEADER = "[mcp_servers.librarian]";
|
|
26
|
+
// The config-shape version we stamp, so detect can report something even
|
|
27
|
+
// though TOML carries no native version for an MCP entry.
|
|
28
|
+
const CONFIG_VERSION = "1";
|
|
29
|
+
const VERSION_MARKER = "# librarian-config-version =";
|
|
30
|
+
/** Read config.toml, or "" if it doesn't exist yet. */
|
|
31
|
+
function readConfig() {
|
|
32
|
+
try {
|
|
33
|
+
return fs.readFileSync(codexConfigPath(), "utf8");
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return "";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** True iff the config text already declares our MCP server table. */
|
|
40
|
+
function hasTable(config) {
|
|
41
|
+
return config.split("\n").some((line) => line.trim() === TABLE_HEADER);
|
|
42
|
+
}
|
|
43
|
+
/** Parse the stamped config-shape version from a managed comment, if any. */
|
|
44
|
+
function parseConfigVersion(config) {
|
|
45
|
+
for (const line of config.split("\n")) {
|
|
46
|
+
const trimmed = line.trim();
|
|
47
|
+
if (trimmed.startsWith(VERSION_MARKER)) {
|
|
48
|
+
const v = trimmed
|
|
49
|
+
.slice(VERSION_MARKER.length)
|
|
50
|
+
.trim()
|
|
51
|
+
.replace(/^["']|["']$/g, "");
|
|
52
|
+
if (v)
|
|
53
|
+
return v;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
/** Append our managed table to existing config text (idempotent caller). */
|
|
59
|
+
function withTable(config, cfg) {
|
|
60
|
+
const block = [
|
|
61
|
+
`${VERSION_MARKER} "${CONFIG_VERSION}"`,
|
|
62
|
+
TABLE_HEADER,
|
|
63
|
+
`url = ${tomlString(cfg.mcpUrl)}`,
|
|
64
|
+
`bearer_token_env_var = ${tomlString(TOKEN_ENV_VAR)}`,
|
|
65
|
+
].join("\n");
|
|
66
|
+
const base = config.length === 0 || config.endsWith("\n") ? config : `${config}\n`;
|
|
67
|
+
const sep = base.length === 0 ? "" : "\n";
|
|
68
|
+
return `${base}${sep}${block}\n`;
|
|
69
|
+
}
|
|
70
|
+
/** Strip our managed table (and its version marker) from config text. */
|
|
71
|
+
function withoutTable(config) {
|
|
72
|
+
const lines = config.split("\n");
|
|
73
|
+
const out = [];
|
|
74
|
+
let skipping = false;
|
|
75
|
+
for (const line of lines) {
|
|
76
|
+
const trimmed = line.trim();
|
|
77
|
+
if (trimmed === VERSION_MARKER || trimmed.startsWith(VERSION_MARKER)) {
|
|
78
|
+
// Drop a marker line immediately preceding our table.
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (trimmed === TABLE_HEADER) {
|
|
82
|
+
skipping = true;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (skipping) {
|
|
86
|
+
// A new table header (or end of our keys) ends the skip region.
|
|
87
|
+
if (/^\[.+\]$/.test(trimmed)) {
|
|
88
|
+
skipping = false;
|
|
89
|
+
out.push(line);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (trimmed === "" || /^[A-Za-z0-9_]+\s*=/.test(trimmed)) {
|
|
93
|
+
continue; // a key/blank inside our table — drop it
|
|
94
|
+
}
|
|
95
|
+
skipping = false;
|
|
96
|
+
}
|
|
97
|
+
out.push(line);
|
|
98
|
+
}
|
|
99
|
+
// Tidy trailing blank lines.
|
|
100
|
+
while (out.length > 0 && out[out.length - 1] === "")
|
|
101
|
+
out.pop();
|
|
102
|
+
return out.length === 0 ? "" : `${out.join("\n")}\n`;
|
|
103
|
+
}
|
|
104
|
+
function writeConfig(content) {
|
|
105
|
+
const file = codexConfigPath();
|
|
106
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
107
|
+
fs.writeFileSync(file, content, "utf8");
|
|
108
|
+
}
|
|
109
|
+
export const codex = {
|
|
110
|
+
id: "codex",
|
|
111
|
+
displayName: "Codex",
|
|
112
|
+
async detect() {
|
|
113
|
+
const config = readConfig();
|
|
114
|
+
if (!hasTable(config))
|
|
115
|
+
return { installed: false };
|
|
116
|
+
const version = parseConfigVersion(config) ?? CONFIG_VERSION;
|
|
117
|
+
return { installed: true, version };
|
|
118
|
+
},
|
|
119
|
+
async install(cfg) {
|
|
120
|
+
if (await which(CLI)) {
|
|
121
|
+
// Idempotent: if our table is already present, do nothing.
|
|
122
|
+
if (hasTable(readConfig()))
|
|
123
|
+
return;
|
|
124
|
+
const result = await run(CLI, [
|
|
125
|
+
"mcp",
|
|
126
|
+
"add",
|
|
127
|
+
SERVER_ID,
|
|
128
|
+
"--url",
|
|
129
|
+
cfg.mcpUrl,
|
|
130
|
+
"--bearer-token-env-var",
|
|
131
|
+
TOKEN_ENV_VAR,
|
|
132
|
+
]);
|
|
133
|
+
if (result.code !== 0) {
|
|
134
|
+
throw new Error(`codex mcp add failed: ${oneLine(result.stderr || result.stdout)}`);
|
|
135
|
+
}
|
|
136
|
+
// Stamp our config-shape version alongside the CLI-written table so
|
|
137
|
+
// detect can report it (the CLI doesn't write the marker comment).
|
|
138
|
+
stampVersionIfMissing();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
// Fallback: no `codex` on PATH. We can still write the config file.
|
|
142
|
+
const config = readConfig();
|
|
143
|
+
if (hasTable(config))
|
|
144
|
+
return; // idempotent
|
|
145
|
+
writeConfig(withTable(config, cfg));
|
|
146
|
+
},
|
|
147
|
+
async uninstall() {
|
|
148
|
+
if (await which(CLI)) {
|
|
149
|
+
// `codex mcp remove` is a no-op when the server is absent.
|
|
150
|
+
await run(CLI, ["mcp", "remove", SERVER_ID]);
|
|
151
|
+
}
|
|
152
|
+
// Always clean the config text too (covers the CLI-absent path and the
|
|
153
|
+
// version marker the CLI doesn't manage).
|
|
154
|
+
const config = readConfig();
|
|
155
|
+
if (hasTable(config) || parseConfigVersion(config) !== undefined) {
|
|
156
|
+
writeConfig(withoutTable(config));
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
async update(cfg) {
|
|
160
|
+
// Re-applying is idempotent; install short-circuits when present.
|
|
161
|
+
await this.install(cfg);
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
/** If the table exists but our version marker doesn't, add the marker. */
|
|
165
|
+
function stampVersionIfMissing() {
|
|
166
|
+
const config = readConfig();
|
|
167
|
+
if (!hasTable(config) || parseConfigVersion(config) !== undefined)
|
|
168
|
+
return;
|
|
169
|
+
const lines = config.split("\n");
|
|
170
|
+
const idx = lines.findIndex((line) => line.trim() === TABLE_HEADER);
|
|
171
|
+
if (idx === -1)
|
|
172
|
+
return;
|
|
173
|
+
lines.splice(idx, 0, `${VERSION_MARKER} "${CONFIG_VERSION}"`);
|
|
174
|
+
writeConfig(lines.join("\n"));
|
|
175
|
+
}
|
|
176
|
+
/** Double-quote a TOML string value with minimal escaping. */
|
|
177
|
+
function tomlString(value) {
|
|
178
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
179
|
+
}
|
|
180
|
+
function oneLine(text) {
|
|
181
|
+
return text.replace(/\s+/g, " ").trim();
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=codex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex.js","sourceRoot":"","sources":["../../src/harnesses/codex.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { HarnessModule } from "./types.js";
|
|
2
|
+
export declare const PINNED_REF = "v1.0.0-rc.3";
|
|
3
|
+
/**
|
|
4
|
+
* Fetches the Hermes adapter for a pinned ref and returns the absolute
|
|
5
|
+
* path to a directory whose contents ARE the adapter (the equivalent of
|
|
6
|
+
* `integrations/hermes/librarian/`). The caller copies from there.
|
|
7
|
+
*
|
|
8
|
+
* Injectable so tests substitute a local fixture instead of the network.
|
|
9
|
+
*/
|
|
10
|
+
export type AdapterFetcher = (ref: string) => Promise<string>;
|
|
11
|
+
/** Override the adapter fetcher (tests inject a local-fixture fetcher). */
|
|
12
|
+
export declare function setAdapterFetcher(next: AdapterFetcher): void;
|
|
13
|
+
/** Restore the default (network) adapter fetcher (tests). */
|
|
14
|
+
export declare function resetAdapterFetcher(): void;
|
|
15
|
+
export declare const hermes: HarnessModule;
|
|
16
|
+
//# sourceMappingURL=hermes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hermes.d.ts","sourceRoot":"","sources":["../../src/harnesses/hermes.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAiB,aAAa,EAAE,MAAM,YAAY,CAAC;AAK/D,eAAO,MAAM,UAAU,gBAAgB,CAAC;AAExC;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAqC9D,2EAA2E;AAC3E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,CAE5D;AAED,6DAA6D;AAC7D,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AAoDD,eAAO,MAAM,MAAM,EAAE,aA+CpB,CAAC"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// Hermes harness.
|
|
2
|
+
//
|
|
3
|
+
// Hermes has no native install command for a memory provider, so we:
|
|
4
|
+
// - copy the adapter into `~/.hermes/plugins/librarian`, and
|
|
5
|
+
// - set `memory.provider = librarian` in `~/.hermes/config.json`.
|
|
6
|
+
//
|
|
7
|
+
// detect plugin dir present AND provider set to "librarian"
|
|
8
|
+
// version comes from the adapter's `plugin.yaml`
|
|
9
|
+
// uninstall remove the plugin dir + unset the provider key
|
|
10
|
+
// update re-fetch + re-copy the adapter (idempotent)
|
|
11
|
+
//
|
|
12
|
+
// ARTIFACT SOURCING. On a user's machine the adapter does NOT live in the
|
|
13
|
+
// repo — the CLI must fetch it from a pinned release. We download a tarball
|
|
14
|
+
// of the pinned ref from GitHub's codeload endpoint
|
|
15
|
+
// https://codeload.github.com/JimJafar/the-librarian/tar.gz/refs/tags/<ref>
|
|
16
|
+
// and extract `integrations/hermes/librarian/**` out of it with `tar`. The
|
|
17
|
+
// FETCH is injectable (`setAdapterFetcher`): tests inject a fetcher that
|
|
18
|
+
// returns a local fixture dir, so nothing touches the network. The pinned
|
|
19
|
+
// ref defaults to the repo's current release tag (PINNED_REF).
|
|
20
|
+
import fs from "node:fs";
|
|
21
|
+
import os from "node:os";
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
import { run } from "../exec.js";
|
|
24
|
+
import { hermesConfigPath, hermesHomeDir, hermesPluginDir } from "../paths.js";
|
|
25
|
+
const PROVIDER_ID = "librarian";
|
|
26
|
+
// The pinned monorepo release we fetch the adapter from. Default to the
|
|
27
|
+
// repo's current release tag; the phase gate owns version bumps.
|
|
28
|
+
export const PINNED_REF = "v1.0.0-rc.3";
|
|
29
|
+
/** The default fetcher: codeload tarball → `tar` extract → adapter dir. */
|
|
30
|
+
const defaultFetcher = async (ref) => {
|
|
31
|
+
const url = `https://codeload.github.com/JimJafar/the-librarian/tar.gz/refs/tags/${ref}`;
|
|
32
|
+
const work = fs.mkdtempSync(path.join(os.tmpdir(), "librarian-hermes-fetch-"));
|
|
33
|
+
const tarball = path.join(work, "src.tar.gz");
|
|
34
|
+
const res = await fetch(url, { redirect: "error" });
|
|
35
|
+
if (!res.ok) {
|
|
36
|
+
throw new Error(`Failed to fetch Hermes adapter (${ref}): HTTP ${res.status}`);
|
|
37
|
+
}
|
|
38
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
39
|
+
fs.writeFileSync(tarball, buf);
|
|
40
|
+
// codeload nests the adapter at
|
|
41
|
+
// the-librarian-<ref>/integrations/hermes/librarian/**
|
|
42
|
+
// — four leading path components. Strip all four so the adapter's own
|
|
43
|
+
// files (plugin.yaml, …) land at the extraction root, which is what the
|
|
44
|
+
// caller copies into `~/.hermes/plugins/librarian/`.
|
|
45
|
+
const out = path.join(work, "adapter");
|
|
46
|
+
fs.mkdirSync(out, { recursive: true });
|
|
47
|
+
const extract = await run("tar", [
|
|
48
|
+
"-xzf",
|
|
49
|
+
tarball,
|
|
50
|
+
"-C",
|
|
51
|
+
out,
|
|
52
|
+
"--strip-components=4",
|
|
53
|
+
"--wildcards",
|
|
54
|
+
"*/integrations/hermes/librarian/*",
|
|
55
|
+
]);
|
|
56
|
+
if (extract.code !== 0) {
|
|
57
|
+
throw new Error(`Failed to extract Hermes adapter: ${(extract.stderr || "").trim()}`);
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
};
|
|
61
|
+
let fetcher = defaultFetcher;
|
|
62
|
+
/** Override the adapter fetcher (tests inject a local-fixture fetcher). */
|
|
63
|
+
export function setAdapterFetcher(next) {
|
|
64
|
+
fetcher = next;
|
|
65
|
+
}
|
|
66
|
+
/** Restore the default (network) adapter fetcher (tests). */
|
|
67
|
+
export function resetAdapterFetcher() {
|
|
68
|
+
fetcher = defaultFetcher;
|
|
69
|
+
}
|
|
70
|
+
function readHermesConfig() {
|
|
71
|
+
try {
|
|
72
|
+
const raw = fs.readFileSync(hermesConfigPath(), "utf8");
|
|
73
|
+
const parsed = JSON.parse(raw);
|
|
74
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
75
|
+
return parsed;
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function writeHermesConfig(config) {
|
|
84
|
+
fs.mkdirSync(hermesHomeDir(), { recursive: true });
|
|
85
|
+
fs.writeFileSync(hermesConfigPath(), `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
86
|
+
}
|
|
87
|
+
/** True iff `memory.provider` is set to our provider id. */
|
|
88
|
+
function providerSet() {
|
|
89
|
+
const config = readHermesConfig();
|
|
90
|
+
return config?.memory?.provider === PROVIDER_ID;
|
|
91
|
+
}
|
|
92
|
+
/** Parse `version:` from the installed adapter's plugin.yaml, if present. */
|
|
93
|
+
function readAdapterVersion() {
|
|
94
|
+
try {
|
|
95
|
+
const raw = fs.readFileSync(path.join(hermesPluginDir(), "plugin.yaml"), "utf8");
|
|
96
|
+
for (const line of raw.split("\n")) {
|
|
97
|
+
const m = /^\s*version\s*:\s*["']?([^"'#\s]+)/.exec(line);
|
|
98
|
+
if (m?.[1])
|
|
99
|
+
return m[1];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// dir/file gone — no version
|
|
104
|
+
}
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
/** Recursively copy a directory tree (Node 22 `fs.cpSync`). */
|
|
108
|
+
function copyDir(from, to) {
|
|
109
|
+
fs.rmSync(to, { recursive: true, force: true });
|
|
110
|
+
fs.mkdirSync(path.dirname(to), { recursive: true });
|
|
111
|
+
fs.cpSync(from, to, { recursive: true });
|
|
112
|
+
}
|
|
113
|
+
export const hermes = {
|
|
114
|
+
id: "hermes",
|
|
115
|
+
displayName: "Hermes",
|
|
116
|
+
async detect() {
|
|
117
|
+
const dirPresent = fs.existsSync(hermesPluginDir());
|
|
118
|
+
if (!dirPresent || !providerSet())
|
|
119
|
+
return { installed: false };
|
|
120
|
+
const version = readAdapterVersion();
|
|
121
|
+
return version === undefined ? { installed: true } : { installed: true, version };
|
|
122
|
+
},
|
|
123
|
+
async install(_cfg) {
|
|
124
|
+
// Fetch the adapter for the pinned ref (network by default; a local
|
|
125
|
+
// fixture in tests) and copy it into place — overwriting any prior copy
|
|
126
|
+
// so re-running is idempotent.
|
|
127
|
+
const adapterDir = await fetcher(PINNED_REF);
|
|
128
|
+
copyDir(adapterDir, hermesPluginDir());
|
|
129
|
+
// Set memory.provider = librarian, preserving any other config keys.
|
|
130
|
+
const config = readHermesConfig() ?? {};
|
|
131
|
+
const memory = (config.memory && typeof config.memory === "object" ? config.memory : {});
|
|
132
|
+
memory.provider = PROVIDER_ID;
|
|
133
|
+
config.memory = memory;
|
|
134
|
+
writeHermesConfig(config);
|
|
135
|
+
},
|
|
136
|
+
async uninstall() {
|
|
137
|
+
// Remove the plugin dir (no-op if already gone).
|
|
138
|
+
fs.rmSync(hermesPluginDir(), { recursive: true, force: true });
|
|
139
|
+
// Unset memory.provider only if it's ours, preserving other keys.
|
|
140
|
+
const config = readHermesConfig();
|
|
141
|
+
if (config?.memory && typeof config.memory === "object") {
|
|
142
|
+
if (config.memory.provider === PROVIDER_ID) {
|
|
143
|
+
delete config.memory.provider;
|
|
144
|
+
if (Object.keys(config.memory).length === 0)
|
|
145
|
+
delete config.memory;
|
|
146
|
+
writeHermesConfig(config);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
async update(cfg) {
|
|
151
|
+
await this.install(cfg);
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
//# sourceMappingURL=hermes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hermes.js","sourceRoot":"","sources":["../../src/harnesses/hermes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { HarnessId, HarnessModule } from "./types.js";
|
|
2
|
+
/** Every harness module, keyed by id. */
|
|
3
|
+
export declare const registry: Record<HarnessId, HarnessModule>;
|
|
4
|
+
/** The harness ids in canonical display order. */
|
|
5
|
+
export declare const HARNESS_IDS: readonly HarnessId[];
|
|
6
|
+
/** All harness modules in canonical display order. */
|
|
7
|
+
export declare const allHarnesses: readonly HarnessModule[];
|
|
8
|
+
/** True iff `id` names a known harness. */
|
|
9
|
+
export declare function isHarnessId(id: string): id is HarnessId;
|
|
10
|
+
export type { HarnessConfig, HarnessModule, DetectResult, HarnessId } from "./types.js";
|
|
11
|
+
export { NotImplemented } from "./types.js";
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/harnesses/index.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3D,yCAAyC;AACzC,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,SAAS,EAAE,aAAa,CAMrD,CAAC;AAEF,kDAAkD;AAClD,eAAO,MAAM,WAAW,EAAE,SAAS,SAAS,EAAoD,CAAC;AAEjG,sDAAsD;AACtD,eAAO,MAAM,YAAY,EAAE,SAAS,aAAa,EAA0C,CAAC;AAE5F,2CAA2C;AAC3C,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,EAAE,IAAI,SAAS,CAEvD;AAED,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACxF,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// The harness registry: id → module.
|
|
2
|
+
//
|
|
3
|
+
// The single source of truth for "which harnesses exist". Commands iterate
|
|
4
|
+
// this in declaration order (the order the dashboard and prompts show them).
|
|
5
|
+
// The next wave swaps the stub modules for real implementations without
|
|
6
|
+
// touching this file.
|
|
7
|
+
import { claude } from "./claude.js";
|
|
8
|
+
import { codex } from "./codex.js";
|
|
9
|
+
import { hermes } from "./hermes.js";
|
|
10
|
+
import { opencode } from "./opencode.js";
|
|
11
|
+
import { pi } from "./pi.js";
|
|
12
|
+
/** Every harness module, keyed by id. */
|
|
13
|
+
export const registry = {
|
|
14
|
+
claude,
|
|
15
|
+
codex,
|
|
16
|
+
opencode,
|
|
17
|
+
hermes,
|
|
18
|
+
pi,
|
|
19
|
+
};
|
|
20
|
+
/** The harness ids in canonical display order. */
|
|
21
|
+
export const HARNESS_IDS = ["claude", "codex", "opencode", "hermes", "pi"];
|
|
22
|
+
/** All harness modules in canonical display order. */
|
|
23
|
+
export const allHarnesses = HARNESS_IDS.map((id) => registry[id]);
|
|
24
|
+
/** True iff `id` names a known harness. */
|
|
25
|
+
export function isHarnessId(id) {
|
|
26
|
+
return Object.prototype.hasOwnProperty.call(registry, id);
|
|
27
|
+
}
|
|
28
|
+
export { NotImplemented } from "./types.js";
|
|
29
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/harnesses/index.ts"],"names":[],"mappings":"AAAA,qCAAqC;AACrC,EAAE;AACF,2EAA2E;AAC3E,6EAA6E;AAC7E,wEAAwE;AACxE,sBAAsB;AAEtB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAG7B,yCAAyC;AACzC,MAAM,CAAC,MAAM,QAAQ,GAAqC;IACxD,MAAM;IACN,KAAK;IACL,QAAQ;IACR,MAAM;IACN,EAAE;CACH,CAAC;AAEF,kDAAkD;AAClD,MAAM,CAAC,MAAM,WAAW,GAAyB,CAAC,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;AAEjG,sDAAsD;AACtD,MAAM,CAAC,MAAM,YAAY,GAA6B,WAAW,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;AAE5F,2CAA2C;AAC3C,MAAM,UAAU,WAAW,CAAC,EAAU;IACpC,OAAO,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;AAC5D,CAAC;AAGD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opencode.d.ts","sourceRoot":"","sources":["../../src/harnesses/opencode.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAiB,aAAa,EAAE,MAAM,YAAY,CAAC;AA4E/D,eAAO,MAAM,QAAQ,EAAE,aAuEtB,CAAC"}
|