mnemos-capture 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,28 +1,47 @@
1
1
  /**
2
- * Mnemos CLI config — stores which AI assistant kos should drive, and an
3
- * optional API key, in ~/.mnemos/config.json. Provider/agent-agnostic: the
4
- * user tells Mnemos the exact command to launch their assistant.
2
+ * Xmu CLI config — stores which AI assistant kos should drive, and an
3
+ * optional API key, in ~/.xmu/config.json. Falls back to ~/.mnemos/config.json.
5
4
  */
6
5
  import { homedir } from "os";
7
6
  import { join } from "path";
8
7
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
9
- const CONFIG_DIR = join(homedir(), ".mnemos");
10
- const CONFIG_PATH = join(CONFIG_DIR, "config.json");
8
+ function xmuDir() {
9
+ return join(homedir(), ".xmu");
10
+ }
11
+ function legacyDir() {
12
+ return join(homedir(), ".mnemos");
13
+ }
14
+ function xmuPath() {
15
+ return join(xmuDir(), "config.json");
16
+ }
17
+ function legacyPath() {
18
+ return join(legacyDir(), "config.json");
19
+ }
20
+ /** Path we currently read from: new dir if present, else the mnemos fallback. */
21
+ function readPath() {
22
+ if (existsSync(xmuPath()))
23
+ return xmuPath();
24
+ if (existsSync(legacyPath()))
25
+ return legacyPath();
26
+ return xmuPath();
27
+ }
11
28
  const ALLOWED_FIELDS = ["agent", "key"];
12
29
  export function readConfig() {
13
- if (!existsSync(CONFIG_PATH))
30
+ const path = readPath();
31
+ if (!existsSync(path))
14
32
  return {};
15
33
  try {
16
- return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
34
+ return JSON.parse(readFileSync(path, "utf-8"));
17
35
  }
18
36
  catch {
19
37
  return {};
20
38
  }
21
39
  }
22
40
  export function writeConfig(cfg) {
23
- if (!existsSync(CONFIG_DIR))
24
- mkdirSync(CONFIG_DIR, { recursive: true });
25
- writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2) + "\n");
41
+ const dir = xmuDir();
42
+ if (!existsSync(dir))
43
+ mkdirSync(dir, { recursive: true });
44
+ writeFileSync(xmuPath(), JSON.stringify(cfg, null, 2) + "\n");
26
45
  }
27
46
  export function setConfigField(field, value) {
28
47
  if (!ALLOWED_FIELDS.includes(field)) {
@@ -32,21 +51,21 @@ export function setConfigField(field, value) {
32
51
  cfg[field] = value;
33
52
  writeConfig(cfg);
34
53
  }
35
- /** Route `mnemos config <set|get> ...`. */
54
+ /** Route `xmu config <set|get> ...`. */
36
55
  export function runConfigCommand(args) {
37
56
  const action = args[0];
38
57
  if (action === "set") {
39
58
  const field = args[1];
40
59
  const value = args.slice(2).join(" ");
41
60
  if (!field || !value) {
42
- console.error('Usage: mnemos config set <field> <value>');
43
- console.error(' e.g. mnemos config set agent "claude -p"');
61
+ console.error("Usage: xmu config set <field> <value>");
62
+ console.error(' e.g. xmu config set agent "claude -p"');
44
63
  process.exit(1);
45
64
  }
46
65
  try {
47
66
  setConfigField(field, value);
48
67
  console.log(`Set ${field} = ${value}`);
49
- console.log(`Saved to ${CONFIG_PATH}`);
68
+ console.log(`Saved to ${xmuPath()}`);
50
69
  }
51
70
  catch (err) {
52
71
  console.error(err instanceof Error ? err.message : String(err));
@@ -65,6 +84,6 @@ export function runConfigCommand(args) {
65
84
  }
66
85
  return;
67
86
  }
68
- console.error("Usage: mnemos config set|get <field> [value]");
87
+ console.error("Usage: xmu config set|get <field> [value]");
69
88
  process.exit(1);
70
89
  }
package/dist/cli/hooks.js CHANGED
@@ -108,7 +108,7 @@ export function setupHooks(apiKey, opts = {}) {
108
108
  settings.hooks = { ...hooks, SessionStart: filtered };
109
109
  writeSettings(settings);
110
110
  if (!opts.silent) {
111
- console.log("Mnemos inbox hook installed.");
111
+ console.log("Xmu inbox hook installed.");
112
112
  console.log(`Location: ${CLAUDE_SETTINGS_PATH}`);
113
113
  if (opts.briefing) {
114
114
  console.log("At the start of each Claude Code session you'll see a full project briefing.");
@@ -151,7 +151,7 @@ export function setupPreToolHook(apiKey, opts = {}) {
151
151
  settings.hooks = { ...hooks, PreToolCall: filtered };
152
152
  writeSettings(settings);
153
153
  if (!opts.silent) {
154
- console.log("Mnemos vault hook installed (PreToolCall).");
154
+ console.log("Xmu vault hook installed (PreToolCall).");
155
155
  console.log("Your knowledge vault will surface relevant captures as you edit files.");
156
156
  }
157
157
  }
@@ -294,7 +294,7 @@ export async function inboxCheck(apiKey, opts = {}) {
294
294
  return;
295
295
  const count = parseInt(match[1], 10);
296
296
  if (count > 0) {
297
- process.stdout.write(`Mnemos: ${count} capture${count === 1 ? "" : "s"} in inbox — run list_inbox to review\n`);
297
+ process.stdout.write(`Xmu: ${count} capture${count === 1 ? "" : "s"} in inbox — run list_inbox to review\n`);
298
298
  }
299
299
  }
300
300
  catch {
package/dist/cli/index.js CHANGED
@@ -52,7 +52,7 @@ async function main() {
52
52
  const keyIdx = args.indexOf("--key");
53
53
  const apiKey = keyIdx !== -1 ? args[keyIdx + 1] : readConfig().key;
54
54
  if (!apiKey) {
55
- console.error("No API key. Pass --key YOUR_KEY or store it: mnemos config set key YOUR_KEY");
55
+ console.error("No API key. Pass --key YOUR_KEY or store it: xmu config set key YOUR_KEY");
56
56
  process.exit(1);
57
57
  }
58
58
  const planIdx = args.indexOf("--plan");
@@ -62,13 +62,13 @@ async function main() {
62
62
  }
63
63
  // Default: open the hosted app
64
64
  console.log("");
65
- console.log(" Mnemos — Knowledge capture for agentic workflows\n");
65
+ console.log(" Xmu — A knowledge graph for your AI workers\n");
66
66
  console.log(` Open ${HOSTED_URL} to start capturing.`);
67
67
  console.log("");
68
68
  console.log(" First time? Sign in with GitHub — setup takes 30 seconds.");
69
69
  console.log("");
70
- console.log(" Connect to Claude Code:");
71
- console.log(" claude mcp add mnemos -- npx -y mnemos-capture@latest serve-mcp --key YOUR_API_KEY");
70
+ console.log(" Connect an AI worker:");
71
+ console.log(" claude mcp add xmu -- npx -y mnemos-capture@latest serve-mcp --key YOUR_API_KEY");
72
72
  console.log("");
73
73
  // Try to open the URL in the default browser
74
74
  const { exec } = await import("child_process");
@@ -77,31 +77,33 @@ async function main() {
77
77
  }
78
78
  function printHelp() {
79
79
  console.log(`
80
- Mnemos — Knowledge capture for agentic workflows
80
+ Xmu — A knowledge graph for your AI workers
81
81
 
82
82
  Usage:
83
- npx mnemos-capture Open Mnemos in your browser
84
- npx mnemos-capture serve-mcp --key KEY Start the MCP server for Claude Code
85
- npx mnemos-capture setup-hooks --key KEY Install inbox count hook (fast)
86
- npx mnemos-capture setup-hooks --key KEY --briefing Install full briefing hook (uses LLM)
87
- npx mnemos-capture setup-hooks --key KEY --vault Install vault hook (PreToolCall)
88
- npx mnemos-capture setup-hooks --key KEY --briefing --vault Install both hooks
89
- npx mnemos-capture config set agent "claude -p" Set which AI assistant kos drives
90
- npx mnemos-capture kos --key KEY [--plan FILE] Implement a plan in an isolated worktree
91
- npx mnemos-capture help Show this help
83
+ xmu Open Xmu in your browser
84
+ xmu serve-mcp --key KEY Start the MCP server for your AI worker
85
+ xmu setup-hooks --key KEY Install inbox count hook (fast)
86
+ xmu setup-hooks --key KEY --briefing Install full briefing hook (uses LLM)
87
+ xmu setup-hooks --key KEY --vault Install vault hook (PreToolCall)
88
+ xmu setup-hooks --key KEY --briefing --vault Install both hooks
89
+ xmu config set agent "claude -p" Set which AI worker kos drives
90
+ xmu kos --key KEY [--plan FILE] Implement a plan in an isolated worktree
91
+ xmu help Show this help
92
92
 
93
- MCP tools (via your AI assistant):
93
+ npx -y mnemos-capture@latest … Same CLI, via the published package
94
+
95
+ MCP tools (via your AI worker):
94
96
  briefing — Session-start briefing with ranked insights to apply
95
97
  generate_plan — Turn selected captures into a full implementation plan
96
98
  list_plans — List or read saved implementation plans
97
99
  vault_scan — Scan all captures for relevance to current activity
98
100
  curate — Validate URLs and flag stale captures
99
101
 
100
- kos — implement plans with your own AI assistant (model-agnostic):
101
- 1. Tell Mnemos which assistant to drive (once):
102
- mnemos config set agent "claude -p" # or "codex exec", "aider --yes --message"
102
+ kos — implement plans with your own AI worker (model-agnostic):
103
+ 1. Tell Xmu which assistant to drive (once):
104
+ xmu config set agent "claude -p" # or "codex exec", "aider --yes --message"
103
105
  2. Generate a plan via the generate_plan MCP tool.
104
- 3. Run: mnemos kos --key YOUR_KEY
106
+ 3. Run: xmu kos --key YOUR_KEY
105
107
  kos creates an isolated git worktree, hands the plan to your assistant,
106
108
  and reports the branch + the plan's Verification Checklist when done.
107
109
 
@@ -112,7 +114,7 @@ function printHelp() {
112
114
  4. Start capturing!
113
115
 
114
116
  Connect to Claude Code:
115
- claude mcp add mnemos -- npx -y mnemos-capture@latest serve-mcp --key YOUR_API_KEY
117
+ claude mcp add xmu -- npx -y mnemos-capture@latest serve-mcp --key YOUR_API_KEY
116
118
 
117
119
  The @latest tag ensures every Claude Code session uses the most recent
118
120
  published version. serve-mcp also self-upgrades on startup as a safety net
package/dist/cli/kos.js CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
- * kos — Mnemos' Knowledge Operations orchestrator.
2
+ * kos — mnemos' Knowledge Operations orchestrator.
3
3
  *
4
- * Drives whatever AI assistant the user configured (`mnemos config set agent ...`)
5
- * to implement a generated plan in an isolated git worktree. Mnemos owns the
4
+ * Drives whatever AI assistant the user configured (`xmu config set agent ...`)
5
+ * to implement a generated plan in an isolated git worktree. mnemos owns the
6
6
  * harness-independent parts: fetching the plan, the worktree, and reporting the
7
7
  * Verification Checklist. The assistant is a black box — model-agnostic by design.
8
8
  */
@@ -54,10 +54,10 @@ function run(cmd, cwd) {
54
54
  export async function kos(apiKey, opts = {}) {
55
55
  const cfg = readConfig();
56
56
  if (!cfg.agent) {
57
- console.error("No agent configured. Tell Mnemos which AI assistant to drive:\n");
58
- console.error(' mnemos config set agent "claude -p" # Claude Code');
59
- console.error(' mnemos config set agent "codex exec" # Codex');
60
- console.error(' mnemos config set agent "aider --yes --message" # Aider');
57
+ console.error("No agent configured. Tell Xmu which AI worker to drive:\n");
58
+ console.error(' xmu config set agent "claude -p" # Claude Code');
59
+ console.error(' xmu config set agent "codex exec" # Codex');
60
+ console.error(' xmu config set agent "aider --yes --message" # Aider');
61
61
  process.exit(1);
62
62
  }
63
63
  // 1. Resolve the plan — explicit filename, or the newest saved plan
@@ -77,7 +77,7 @@ export async function kos(apiKey, opts = {}) {
77
77
  repoRoot = run("git rev-parse --show-toplevel");
78
78
  }
79
79
  catch {
80
- console.error("mnemos kos must be run inside a git repository.");
80
+ console.error("xmu kos must be run inside a git repository.");
81
81
  process.exit(1);
82
82
  return;
83
83
  }
@@ -1,13 +1,20 @@
1
- // MCP server that runs locally via stdio and proxies to the hosted Mnemos API.
2
- // Used by Claude Code: claude mcp add mnemos -- npx mnemos-capture serve-mcp --key <api-key>
3
- const HOSTED_URL = "https://mnemos-capture.vercel.app/api/mcp";
1
+ // MCP server that runs locally via stdio and proxies to the hosted mnemos API.
2
+ // Used by Claude Code: claude mcp add xmu -- npx mnemos-capture serve-mcp --key <api-key>
3
+ const DEFAULT_HOSTED_URL = "https://mnemos-capture.vercel.app/api/mcp";
4
+ /** Override for pointing the proxy at a local instance during development. */
5
+ const HOSTED_URL = process.env.XMU_API_URL ?? process.env.MNEMOS_API_URL ?? DEFAULT_HOSTED_URL;
4
6
  function sendMessage(msg) {
5
- const json = JSON.stringify(msg);
6
- process.stdout.write(`Content-Length: ${Buffer.byteLength(json)}\r\n\r\n${json}`);
7
+ process.stdout.write(`${JSON.stringify(msg)}\n`);
8
+ }
9
+ /** In-flight proxied requests, so shutdown can wait for them. */
10
+ const pending = new Set();
11
+ function rpcError(id, message) {
12
+ sendMessage({ jsonrpc: "2.0", id, error: { code: -32603, message } });
7
13
  }
8
14
  async function proxyToHosted(apiKey, msg) {
15
+ let res;
9
16
  try {
10
- const res = await fetch(HOSTED_URL, {
17
+ res = await fetch(HOSTED_URL, {
11
18
  method: "POST",
12
19
  headers: {
13
20
  "Content-Type": "application/json",
@@ -15,17 +22,50 @@ async function proxyToHosted(apiKey, msg) {
15
22
  },
16
23
  body: JSON.stringify(msg),
17
24
  });
18
- const data = (await res.json());
19
- sendMessage(data);
20
25
  }
21
26
  catch (err) {
22
- sendMessage({
23
- jsonrpc: "2.0",
24
- id: msg.id,
25
- error: { code: -32603, message: err instanceof Error ? err.message : "Proxy error" },
26
- });
27
+ const detail = err instanceof Error ? err.message : "unknown error";
28
+ rpcError(msg.id, `Could not reach Xmu at ${HOSTED_URL}: ${detail}`);
29
+ return;
30
+ }
31
+ const body = await res.text().catch(() => "");
32
+ const result = interpretResponse(res.status, res.headers.get("content-type"), body);
33
+ if ("error" in result) {
34
+ rpcError(msg.id, result.error);
35
+ return;
36
+ }
37
+ sendMessage(result.data);
38
+ }
39
+ /**
40
+ * Turn a raw HTTP reply into either parsed JSON-RPC data or a useful message.
41
+ *
42
+ * Parsing straight to JSON turns any non-JSON reply — an HTML error page from a
43
+ * 500, a proxy block page, a login redirect — into "Unexpected token '<'",
44
+ * which says nothing about what actually happened. Reporting the status and a
45
+ * snippet makes the real cause visible in the MCP client.
46
+ *
47
+ * Exported for testing; this is the logic worth covering, not the stdio plumbing.
48
+ */
49
+ export function interpretResponse(status, contentType, body) {
50
+ const type = contentType ?? "";
51
+ if (!type.includes("application/json")) {
52
+ const snippet = body.trim().slice(0, 200).replace(/\s+/g, " ");
53
+ return {
54
+ error: `Xmu returned a non-JSON response (HTTP ${status}${type ? `, ${type}` : ""})${snippet ? `: ${snippet}` : ""}`,
55
+ };
56
+ }
57
+ try {
58
+ return { data: JSON.parse(body) };
59
+ }
60
+ catch {
61
+ return { error: `Xmu returned malformed JSON (HTTP ${status}).` };
27
62
  }
28
63
  }
64
+ /** Track a proxied request so shutdown can await it. */
65
+ function track(p) {
66
+ pending.add(p);
67
+ void p.finally(() => pending.delete(p));
68
+ }
29
69
  export async function serveMcp() {
30
70
  // Parse --key flag
31
71
  const args = process.argv.slice(2);
@@ -57,38 +97,33 @@ export async function serveMcp() {
57
97
  catch {
58
98
  // Non-fatal — hook setup should never block the MCP server from starting
59
99
  }
60
- process.stderr.write("Mnemos MCP server starting (proxying to hosted instance)...\n");
100
+ process.stderr.write("Xmu MCP server starting (proxying to hosted instance)...\n");
61
101
  let buffer = "";
62
102
  process.stdin.setEncoding("utf-8");
63
103
  process.stdin.on("data", (chunk) => {
64
104
  buffer += chunk;
65
- while (true) {
66
- const headerEnd = buffer.indexOf("\r\n\r\n");
67
- if (headerEnd === -1)
68
- break;
69
- const header = buffer.slice(0, headerEnd);
70
- const match = header.match(/Content-Length:\s*(\d+)/i);
71
- if (!match) {
72
- buffer = buffer.slice(headerEnd + 4);
105
+ let newlineIdx;
106
+ while ((newlineIdx = buffer.indexOf("\n")) !== -1) {
107
+ const line = buffer.slice(0, newlineIdx).trim();
108
+ buffer = buffer.slice(newlineIdx + 1);
109
+ if (!line)
73
110
  continue;
74
- }
75
- const contentLength = parseInt(match[1], 10);
76
- const bodyStart = headerEnd + 4;
77
- if (buffer.length < bodyStart + contentLength)
78
- break;
79
- const body = buffer.slice(bodyStart, bodyStart + contentLength);
80
- buffer = buffer.slice(bodyStart + contentLength);
81
111
  try {
82
- const msg = JSON.parse(body);
112
+ const msg = JSON.parse(line);
83
113
  // Handle notifications/initialized locally (no response needed)
84
114
  if (msg.method === "notifications/initialized")
85
115
  continue;
86
- void proxyToHosted(apiKey, msg);
116
+ track(proxyToHosted(apiKey, msg));
87
117
  }
88
118
  catch {
89
- process.stderr.write(`Failed to parse: ${body}\n`);
119
+ process.stderr.write(`Failed to parse: ${line}\n`);
90
120
  }
91
121
  }
92
122
  });
93
- process.stdin.on("end", () => process.exit(0));
123
+ // Wait for in-flight requests before exiting: exiting immediately on stdin
124
+ // close discards responses that were still being fetched, so the client sees
125
+ // silence rather than an answer or an error.
126
+ process.stdin.on("end", () => {
127
+ void Promise.allSettled([...pending]).then(() => process.exit(0));
128
+ });
94
129
  }
@@ -59,7 +59,7 @@ export async function maybeSelfUpgrade() {
59
59
  const latest = await getLatestVersion();
60
60
  if (!latest || !isNewer(latest, current))
61
61
  return false;
62
- process.stderr.write(`Mnemos: upgrading to v${latest} (was v${current})...\n`);
62
+ process.stderr.write(`Xmu: upgrading to v${latest} (was v${current})...\n`);
63
63
  const child = spawn("npx", ["-y", `mnemos-capture@${latest}`, ...process.argv.slice(2)], {
64
64
  stdio: "inherit",
65
65
  env: { ...process.env, MNEMOS_UPGRADED: "1" },
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "mnemos-capture",
3
- "version": "1.2.0",
4
- "description": "Capture anything. Your agents apply it. A knowledge pipeline that ingests the outside world and makes it directly actionable by any AI agent over MCP.",
3
+ "version": "1.3.0",
4
+ "description": "Xmu — Turn what you discover into context your AI workers can use. Capture once, then brief, plan, and execute with any MCP-compatible tool.",
5
5
  "type": "module",
6
6
  "bin": {
7
- "mnemos-capture": "./dist/cli/index.js"
7
+ "mnemos-capture": "./dist/cli/index.js",
8
+ "xmu": "./dist/cli/index.js"
8
9
  },
9
10
  "scripts": {
10
11
  "dev": "next dev",
@@ -13,10 +14,14 @@
13
14
  "build:cli": "tsc -p cli/tsconfig.json",
14
15
  "prepublishOnly": "npm run build:cli",
15
16
  "test": "vitest run",
16
- "test:watch": "vitest"
17
+ "test:watch": "vitest",
18
+ "test:e2e": "playwright test",
19
+ "lint": "eslint .",
20
+ "typecheck": "tsc --noEmit"
17
21
  },
18
22
  "keywords": [
19
23
  "ai",
24
+ "xmu",
20
25
  "knowledge",
21
26
  "capture",
22
27
  "claude",
@@ -40,23 +45,26 @@
40
45
  "README.md"
41
46
  ],
42
47
  "dependencies": {
43
- "@anthropic-ai/sdk": "^0.36.3",
48
+ "@anthropic-ai/sdk": "^0.122.0",
44
49
  "@vercel/postgres": "^0.10.0",
45
50
  "next": "^16.1.6",
46
51
  "react": "^19.0.0",
47
52
  "react-dom": "^19.0.0"
48
53
  },
49
54
  "devDependencies": {
50
- "@types/node": "^20",
51
- "@types/react": "^19",
52
- "@types/react-dom": "^19",
55
+ "@playwright/test": "^1.62.1",
56
+ "@types/node": "^26",
57
+ "@types/react": "^19.2.18",
58
+ "@types/react-dom": "^19.2.5",
53
59
  "autoprefixer": "^10.4.21",
60
+ "eslint": "^9.39.5",
61
+ "eslint-config-next": "^16.3.3",
54
62
  "postcss": "^8.5.3",
55
63
  "tailwindcss": "^3.4.17",
56
- "typescript": "^5",
64
+ "typescript": "^6.0.3",
57
65
  "vitest": "^4.1.8"
58
66
  },
59
67
  "engines": {
60
- "node": ">=18"
68
+ "node": ">=20.9"
61
69
  }
62
70
  }
@@ -1,49 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
- import { mkdtempSync, rmSync, readFileSync, existsSync } from "fs";
3
- import { tmpdir } from "os";
4
- import { join } from "path";
5
- describe("config", () => {
6
- let tmpDir;
7
- let configPath;
8
- let origHome;
9
- beforeEach(() => {
10
- tmpDir = mkdtempSync(join(tmpdir(), "mnemos-config-test-"));
11
- configPath = join(tmpDir, ".mnemos", "config.json");
12
- origHome = process.env["HOME"];
13
- process.env["HOME"] = tmpDir;
14
- // Module-level CONFIG_DIR is computed from homedir() at import — reset cache
15
- vi.resetModules();
16
- });
17
- afterEach(() => {
18
- rmSync(tmpDir, { recursive: true, force: true });
19
- process.env["HOME"] = origHome;
20
- });
21
- it("returns an empty object when no config exists", async () => {
22
- const { readConfig } = await import("../config.js");
23
- expect(readConfig()).toEqual({});
24
- });
25
- it("writes and reads back the agent field", async () => {
26
- const { setConfigField, readConfig } = await import("../config.js");
27
- setConfigField("agent", "claude -p");
28
- expect(readConfig().agent).toBe("claude -p");
29
- expect(existsSync(configPath)).toBe(true);
30
- });
31
- it("persists the agent command verbatim, including flags", async () => {
32
- const { setConfigField } = await import("../config.js");
33
- setConfigField("agent", "aider --yes --message");
34
- const saved = JSON.parse(readFileSync(configPath, "utf-8"));
35
- expect(saved.agent).toBe("aider --yes --message");
36
- });
37
- it("preserves other fields when updating one", async () => {
38
- const { setConfigField, readConfig } = await import("../config.js");
39
- setConfigField("key", "mnemos_abc123");
40
- setConfigField("agent", "codex exec");
41
- const cfg = readConfig();
42
- expect(cfg.key).toBe("mnemos_abc123");
43
- expect(cfg.agent).toBe("codex exec");
44
- });
45
- it("throws on an unknown config field", async () => {
46
- const { setConfigField } = await import("../config.js");
47
- expect(() => setConfigField("provider", "openai")).toThrow(/Unknown config field/);
48
- });
49
- });