@hasna/mementos 0.14.22 → 0.14.24

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 CHANGED
@@ -18,8 +18,9 @@ mementos auth signup
18
18
  mementos setup agents
19
19
  ```
20
20
 
21
- `mementos setup agents` writes remote MCP and hook snippets for Claude Code,
22
- Codex, OpenCode, and Antigravity under the user's config directory.
21
+ `mementos setup agents` registers the hosted `mementos-mcp` server in Claude
22
+ Code, Codex, OpenCode, and Antigravity config files. It also writes managed
23
+ diagnostic snippets under `~/.config/mementos/agent-integrations`.
23
24
 
24
25
  ## MCP
25
26
 
@@ -36,3 +37,9 @@ MEMENTOS_API_TOKEN=<token> mementos inject "project context"
36
37
  ```
37
38
 
38
39
  The command prints a prompt-ready memory block from the hosted API.
40
+
41
+ Claude Code also receives `UserPromptSubmit`, `SessionStart`, and `Stop` hook
42
+ entries that call `mementos hook ...` and return hosted memory as hook context.
43
+ Codex does not expose a native hook file in `~/.codex/config.toml`, so the
44
+ installer registers MCP there and keeps the injection command in the managed
45
+ integration snippets.
@@ -1 +1 @@
1
- {"version":3,"file":"agent-setup.d.ts","sourceRoot":"","sources":["../src/agent-setup.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,qBAAqB,8DAA+D,CAAC;AAElG,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;AAExE,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,WAAW,CAAC;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,kBAAkB,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,kBAAkB,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;CAChF;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,WAAW,EAAE,oBAAoB,EAAE,CAAC;IACpC,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,iBAAsB,GAAG,cAAc,CA0CnF;AAED,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,iBAAsB,GAAG,cAAc,CAmCjF;AAED,wBAAgB,wBAAwB,CAAC,MAAM,SAAwB;;;EAStE;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAW9D"}
1
+ {"version":3,"file":"agent-setup.d.ts","sourceRoot":"","sources":["../src/agent-setup.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,qBAAqB,8DAA+D,CAAC;AAElG,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;AAExE,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,WAAW,CAAC;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,kBAAkB,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,kBAAkB,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;CAChF;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,WAAW,EAAE,oBAAoB,EAAE,CAAC;IACpC,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,iBAAsB,GAAG,cAAc,CA0CnF;AAED,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,iBAAsB,GAAG,cAAc,CAqCjF;AAED,wBAAgB,wBAAwB,CAAC,MAAM,SAAwB;;;EAStE;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAW9D"}
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // @bun
3
3
 
4
4
  // src/agent-setup.ts
5
- import { existsSync, mkdirSync, rmSync, writeFileSync } from "fs";
5
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
6
6
  import { homedir } from "os";
7
7
  import { dirname, join } from "path";
8
8
  var SUPPORTED_AGENT_HOSTS = ["claude-code", "codex", "opencode", "antigravity"];
@@ -45,7 +45,7 @@ function buildAgentSetupPlan(options = {}) {
45
45
  return {
46
46
  baseDir,
47
47
  files,
48
- hostResults: hosts.map((host) => ({ host, path: hostManagedConfigPath(home, host), status: "planned" })),
48
+ hostResults: hosts.map((host) => ({ host, path: hostNativeConfigPath(home, host), status: "planned" })),
49
49
  hosts,
50
50
  mcpCommand
51
51
  };
@@ -57,7 +57,7 @@ function installAgentSetup(options = {}) {
57
57
  ...plan,
58
58
  hostResults: plan.hostResults.map((result) => ({
59
59
  ...result,
60
- status: existsSync(result.path) ? "present" : "missing"
60
+ status: hasNativeHostInstall(result.path, result.host) ? "present" : "missing"
61
61
  }))
62
62
  };
63
63
  }
@@ -69,7 +69,9 @@ function installAgentSetup(options = {}) {
69
69
  };
70
70
  }
71
71
  for (const result of plan.hostResults)
72
- rmSync(result.path, { force: true });
72
+ uninstallNativeHost(result.path, result.host);
73
+ for (const host of plan.hosts)
74
+ rmSync(hostManagedConfigPath(options.homeDir ?? homedir(), host), { force: true });
73
75
  return {
74
76
  ...plan,
75
77
  hostResults: plan.hostResults.map((result) => ({ ...result, status: "removed" }))
@@ -81,6 +83,8 @@ function installAgentSetup(options = {}) {
81
83
  mkdirSync(dirname(file.path), { recursive: true });
82
84
  writeFileSync(file.path, file.content, { mode: file.executable ? 493 : 420 });
83
85
  }
86
+ for (const result of plan.hostResults)
87
+ installNativeHost(result.path, result.host, plan);
84
88
  return {
85
89
  ...plan,
86
90
  hostResults: plan.hostResults.map((result) => ({ ...result, status: "installed" }))
@@ -155,7 +159,9 @@ function renderManagedHostConfig(host, commands) {
155
159
  return {
156
160
  ...base,
157
161
  hooks: {
158
- Stop: [{ hooks: [{ type: "command", command: commands.hookCommand }] }]
162
+ UserPromptSubmit: [{ matcher: "", hooks: [{ type: "command", command: "mementos hook user-prompt-submit" }] }],
163
+ SessionStart: [{ matcher: "", hooks: [{ type: "command", command: "mementos hook session-start" }] }],
164
+ Stop: [{ matcher: "", hooks: [{ type: "command", command: commands.hookCommand }] }]
159
165
  }
160
166
  };
161
167
  }
@@ -184,6 +190,235 @@ function hostManagedConfigPath(home, host) {
184
190
  return join(home, ".config", "antigravity", "mementos.managed.json");
185
191
  }
186
192
  }
193
+ function hostNativeConfigPath(home, host) {
194
+ switch (host) {
195
+ case "claude-code":
196
+ return join(home, ".claude", "settings.json");
197
+ case "codex":
198
+ return join(home, ".codex", "config.toml");
199
+ case "opencode":
200
+ return join(home, ".config", "opencode", "opencode.json");
201
+ case "antigravity":
202
+ return join(home, ".antigravity", "mcp.json");
203
+ }
204
+ }
205
+ function installNativeHost(path, host, plan) {
206
+ mkdirSync(dirname(path), { recursive: true });
207
+ if (host === "codex") {
208
+ writeFileSync(path, installCodexToml(readTextFile(path), plan), { mode: 420 });
209
+ return;
210
+ }
211
+ const config = readJsonObject(path);
212
+ if (host === "claude-code") {
213
+ writeJsonObject(path, installClaudeConfig(config, plan));
214
+ return;
215
+ }
216
+ if (host === "opencode") {
217
+ writeJsonObject(path, installOpenCodeConfig(config, plan));
218
+ return;
219
+ }
220
+ writeJsonObject(path, installAntigravityConfig(config, plan));
221
+ }
222
+ function uninstallNativeHost(path, host) {
223
+ if (!existsSync(path))
224
+ return;
225
+ if (host === "codex") {
226
+ writeFileSync(path, uninstallCodexToml(readTextFile(path)), { mode: 420 });
227
+ return;
228
+ }
229
+ const config = readJsonObject(path);
230
+ if (host === "claude-code")
231
+ writeJsonObject(path, uninstallClaudeConfig(config));
232
+ if (host === "opencode")
233
+ writeJsonObject(path, uninstallOpenCodeConfig(config));
234
+ if (host === "antigravity")
235
+ writeJsonObject(path, uninstallMcpServersConfig(config));
236
+ }
237
+ function hasNativeHostInstall(path, host) {
238
+ if (!existsSync(path))
239
+ return false;
240
+ if (host === "codex")
241
+ return /\[mcp_servers\.mementos\]/.test(readTextFile(path));
242
+ const config = readJsonObject(path);
243
+ if (host === "opencode")
244
+ return isRecord(config.mcp) && isRecord(config.mcp.mementos);
245
+ return isRecord(config.mcpServers) && isRecord(config.mcpServers.mementos);
246
+ }
247
+ function installClaudeConfig(config, plan) {
248
+ return {
249
+ ...config,
250
+ mcpServers: {
251
+ ...recordValue(config.mcpServers),
252
+ mementos: stdioMcpServer(plan, true)
253
+ },
254
+ hooks: mergeAgentHooks(recordValue(config.hooks), {
255
+ UserPromptSubmit: "mementos hook user-prompt-submit",
256
+ SessionStart: "mementos hook session-start",
257
+ Stop: "mementos hook stop"
258
+ })
259
+ };
260
+ }
261
+ function uninstallClaudeConfig(config) {
262
+ return {
263
+ ...config,
264
+ mcpServers: omitKey(recordValue(config.mcpServers), "mementos"),
265
+ hooks: removeAgentHooks(recordValue(config.hooks), [
266
+ "mementos hook user-prompt-submit",
267
+ "mementos hook session-start",
268
+ "mementos hook stop"
269
+ ])
270
+ };
271
+ }
272
+ function installOpenCodeConfig(config, plan) {
273
+ return {
274
+ ...config,
275
+ mcp: {
276
+ ...recordValue(config.mcp),
277
+ mementos: {
278
+ type: "local",
279
+ command: [plan.mcpCommand],
280
+ enabled: true,
281
+ environment: remoteEnv(baseApiUrlFromPlan(plan))
282
+ }
283
+ }
284
+ };
285
+ }
286
+ function uninstallOpenCodeConfig(config) {
287
+ return {
288
+ ...config,
289
+ mcp: omitKey(recordValue(config.mcp), "mementos")
290
+ };
291
+ }
292
+ function installAntigravityConfig(config, plan) {
293
+ return {
294
+ ...config,
295
+ mcpServers: {
296
+ ...recordValue(config.mcpServers),
297
+ mementos: stdioMcpServer(plan, false)
298
+ }
299
+ };
300
+ }
301
+ function uninstallMcpServersConfig(config) {
302
+ return {
303
+ ...config,
304
+ mcpServers: omitKey(recordValue(config.mcpServers), "mementos")
305
+ };
306
+ }
307
+ function installCodexToml(current, plan) {
308
+ const withoutExisting = removeTomlTable(removeManagedCodexBlock(current), "mcp_servers.mementos");
309
+ const block = [
310
+ "# BEGIN @hasna/mementos",
311
+ "[mcp_servers.mementos]",
312
+ `command = ${JSON.stringify(plan.mcpCommand)}`,
313
+ "[mcp_servers.mementos.env]",
314
+ `MEMENTOS_API_URL = ${JSON.stringify(baseApiUrlFromPlan(plan))}`,
315
+ 'MEMENTOS_API_TOKEN = "$MEMENTOS_API_TOKEN"',
316
+ "# END @hasna/mementos",
317
+ ""
318
+ ].join(`
319
+ `);
320
+ return `${withoutExisting.trimEnd()}
321
+
322
+ ${block}`;
323
+ }
324
+ function uninstallCodexToml(current) {
325
+ return `${removeTomlTable(removeManagedCodexBlock(current), "mcp_servers.mementos").trimEnd()}
326
+ `;
327
+ }
328
+ function mergeAgentHooks(hooks, commands) {
329
+ const next = { ...hooks };
330
+ for (const [event, command] of Object.entries(commands)) {
331
+ const current = next[event];
332
+ const entries = Array.isArray(current) ? [...current] : [];
333
+ if (!entries.some((entry) => JSON.stringify(entry).includes(command))) {
334
+ entries.push({ matcher: "", hooks: [{ type: "command", command }] });
335
+ }
336
+ next[event] = entries;
337
+ }
338
+ return next;
339
+ }
340
+ function removeAgentHooks(hooks, commands) {
341
+ const next = { ...hooks };
342
+ for (const [event, entries] of Object.entries(next)) {
343
+ if (!Array.isArray(entries))
344
+ continue;
345
+ next[event] = entries.filter((entry) => !commands.some((command) => JSON.stringify(entry).includes(command)));
346
+ }
347
+ return next;
348
+ }
349
+ function stdioMcpServer(plan, includeType) {
350
+ return {
351
+ ...includeType ? { type: "stdio" } : {},
352
+ command: plan.mcpCommand,
353
+ env: remoteEnv(baseApiUrlFromPlan(plan))
354
+ };
355
+ }
356
+ function remoteEnv(apiUrl) {
357
+ return {
358
+ MEMENTOS_API_URL: apiUrl,
359
+ MEMENTOS_API_TOKEN: "$MEMENTOS_API_TOKEN"
360
+ };
361
+ }
362
+ function baseApiUrlFromPlan(plan) {
363
+ const mcpFile = plan.files.find((file) => file.path.endsWith("mcp-server.json"));
364
+ if (!mcpFile)
365
+ return "https://mementos.md";
366
+ try {
367
+ const parsed = JSON.parse(mcpFile.content);
368
+ return parsed.mcpServers?.mementos?.env?.MEMENTOS_API_URL ?? "https://mementos.md";
369
+ } catch {
370
+ return "https://mementos.md";
371
+ }
372
+ }
373
+ function removeManagedCodexBlock(value) {
374
+ return value.replace(/\n?# BEGIN @hasna\/mementos\n[\s\S]*?# END @hasna\/mementos\n?/g, `
375
+ `);
376
+ }
377
+ function removeTomlTable(value, table) {
378
+ const tablePattern = table.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
379
+ const lines = value.split(`
380
+ `);
381
+ const output = [];
382
+ let skipping = false;
383
+ for (const line of lines) {
384
+ if (new RegExp(`^\\[${tablePattern}(?:\\.|\\])`).test(line.trim())) {
385
+ skipping = true;
386
+ continue;
387
+ }
388
+ if (skipping && /^\[[^\]]+\]/.test(line.trim()))
389
+ skipping = false;
390
+ if (!skipping)
391
+ output.push(line);
392
+ }
393
+ return output.join(`
394
+ `);
395
+ }
396
+ function readTextFile(path) {
397
+ return existsSync(path) ? readFileSync(path, "utf8") : "";
398
+ }
399
+ function readJsonObject(path) {
400
+ if (!existsSync(path))
401
+ return {};
402
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
403
+ if (!isRecord(parsed))
404
+ throw new Error(`${path} must contain a JSON object`);
405
+ return parsed;
406
+ }
407
+ function writeJsonObject(path, value) {
408
+ mkdirSync(dirname(path), { recursive: true });
409
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}
410
+ `, { mode: 420 });
411
+ }
412
+ function recordValue(value) {
413
+ return isRecord(value) ? value : {};
414
+ }
415
+ function isRecord(value) {
416
+ return typeof value === "object" && value !== null && !Array.isArray(value);
417
+ }
418
+ function omitKey(record, key) {
419
+ const { [key]: _removed, ...rest } = record;
420
+ return rest;
421
+ }
187
422
  function renderInjectionInstructions({ apiUrl, injectCommand }) {
188
423
  return `# mementos.md Memory Injection
189
424
 
@@ -453,12 +688,52 @@ async function injectMemory(args) {
453
688
  }
454
689
  async function runHook(args) {
455
690
  const subcommand = args[0] ?? "help";
691
+ const hookEvents = {
692
+ "user-prompt-submit": "UserPromptSubmit",
693
+ "session-start": "SessionStart",
694
+ "subagent-start": "SubagentStart",
695
+ setup: "Setup"
696
+ };
697
+ if (subcommand in hookEvents) {
698
+ const query2 = await readHookQuery();
699
+ const payload = await client.injectionContext({ query: query2, limit: 12 });
700
+ const block = readTextPayload(payload) ?? formatMemoryInjection(payload);
701
+ return {
702
+ suppressOutput: true,
703
+ hookSpecificOutput: {
704
+ hookEventName: hookEvents[subcommand],
705
+ additionalContext: block
706
+ }
707
+ };
708
+ }
456
709
  if (subcommand !== "stop") {
457
- throw new Error("usage: mementos hook stop");
710
+ throw new Error("usage: mementos hook <stop|user-prompt-submit|session-start|subagent-start|setup>");
458
711
  }
459
712
  const query = process.env.MEMENTOS_INJECTION_QUERY ?? process.cwd();
460
713
  return injectMemory([query]);
461
714
  }
715
+ async function readHookQuery() {
716
+ if (process.env.MEMENTOS_INJECTION_QUERY)
717
+ return process.env.MEMENTOS_INJECTION_QUERY;
718
+ const stdin = await readOptionalStdin();
719
+ if (!stdin)
720
+ return process.cwd();
721
+ try {
722
+ const parsed = JSON.parse(stdin);
723
+ const prompt = parsed.prompt ?? parsed.userPrompt ?? parsed.message ?? parsed.cwd;
724
+ if (typeof prompt === "string" && prompt.trim())
725
+ return prompt.trim();
726
+ } catch {
727
+ if (stdin.trim())
728
+ return stdin.trim();
729
+ }
730
+ return process.cwd();
731
+ }
732
+ async function readOptionalStdin() {
733
+ if (process.stdin.isTTY)
734
+ return "";
735
+ return new Response(Bun.stdin.stream()).text();
736
+ }
462
737
  function parseSetupOptions(args) {
463
738
  const options = {};
464
739
  for (let index = 0;index < args.length; index += 1) {
@@ -502,7 +777,7 @@ Usage:
502
777
  mementos auth signup
503
778
  mementos setup agents [--dry-run|--verify|--uninstall]
504
779
  mementos inject <query>
505
- mementos hook stop
780
+ mementos hook <stop|user-prompt-submit|session-start|subagent-start|setup>
506
781
  mementos capabilities
507
782
  mementos agents
508
783
  mementos usage
package/dist/index.js CHANGED
@@ -131,6 +131,7 @@ function readErrorMessage(payload, status) {
131
131
  }
132
132
  // src/mcp-runtime.ts
133
133
  import { createInterface } from "readline";
134
+ var MEMENTOS_MCP_VERSION = "0.14.24";
134
135
  var AUTHLESS_SCHEMA = {
135
136
  type: "object",
136
137
  properties: {},
@@ -245,7 +246,7 @@ async function handleMcpMessage(message, client = createRemoteClientFromEnv()) {
245
246
  if (message?.method === "initialize") {
246
247
  return jsonRpcResult(id, {
247
248
  protocolVersion: "2024-11-05",
248
- serverInfo: { name: "mementos", version: "0.14.22" },
249
+ serverInfo: { name: "mementos", version: MEMENTOS_MCP_VERSION },
249
250
  capabilities: { tools: {} }
250
251
  });
251
252
  }
@@ -291,7 +292,7 @@ function requiredText(input, message) {
291
292
  return input.trim();
292
293
  }
293
294
  // src/agent-setup.ts
294
- import { existsSync, mkdirSync, rmSync, writeFileSync } from "fs";
295
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
295
296
  import { homedir } from "os";
296
297
  import { dirname, join } from "path";
297
298
  var SUPPORTED_AGENT_HOSTS = ["claude-code", "codex", "opencode", "antigravity"];
@@ -334,7 +335,7 @@ function buildAgentSetupPlan(options = {}) {
334
335
  return {
335
336
  baseDir,
336
337
  files,
337
- hostResults: hosts.map((host) => ({ host, path: hostManagedConfigPath(home, host), status: "planned" })),
338
+ hostResults: hosts.map((host) => ({ host, path: hostNativeConfigPath(home, host), status: "planned" })),
338
339
  hosts,
339
340
  mcpCommand
340
341
  };
@@ -346,7 +347,7 @@ function installAgentSetup(options = {}) {
346
347
  ...plan,
347
348
  hostResults: plan.hostResults.map((result) => ({
348
349
  ...result,
349
- status: existsSync(result.path) ? "present" : "missing"
350
+ status: hasNativeHostInstall(result.path, result.host) ? "present" : "missing"
350
351
  }))
351
352
  };
352
353
  }
@@ -358,7 +359,9 @@ function installAgentSetup(options = {}) {
358
359
  };
359
360
  }
360
361
  for (const result of plan.hostResults)
361
- rmSync(result.path, { force: true });
362
+ uninstallNativeHost(result.path, result.host);
363
+ for (const host of plan.hosts)
364
+ rmSync(hostManagedConfigPath(options.homeDir ?? homedir(), host), { force: true });
362
365
  return {
363
366
  ...plan,
364
367
  hostResults: plan.hostResults.map((result) => ({ ...result, status: "removed" }))
@@ -370,6 +373,8 @@ function installAgentSetup(options = {}) {
370
373
  mkdirSync(dirname(file.path), { recursive: true });
371
374
  writeFileSync(file.path, file.content, { mode: file.executable ? 493 : 420 });
372
375
  }
376
+ for (const result of plan.hostResults)
377
+ installNativeHost(result.path, result.host, plan);
373
378
  return {
374
379
  ...plan,
375
380
  hostResults: plan.hostResults.map((result) => ({ ...result, status: "installed" }))
@@ -444,7 +449,9 @@ function renderManagedHostConfig(host, commands) {
444
449
  return {
445
450
  ...base,
446
451
  hooks: {
447
- Stop: [{ hooks: [{ type: "command", command: commands.hookCommand }] }]
452
+ UserPromptSubmit: [{ matcher: "", hooks: [{ type: "command", command: "mementos hook user-prompt-submit" }] }],
453
+ SessionStart: [{ matcher: "", hooks: [{ type: "command", command: "mementos hook session-start" }] }],
454
+ Stop: [{ matcher: "", hooks: [{ type: "command", command: commands.hookCommand }] }]
448
455
  }
449
456
  };
450
457
  }
@@ -473,6 +480,235 @@ function hostManagedConfigPath(home, host) {
473
480
  return join(home, ".config", "antigravity", "mementos.managed.json");
474
481
  }
475
482
  }
483
+ function hostNativeConfigPath(home, host) {
484
+ switch (host) {
485
+ case "claude-code":
486
+ return join(home, ".claude", "settings.json");
487
+ case "codex":
488
+ return join(home, ".codex", "config.toml");
489
+ case "opencode":
490
+ return join(home, ".config", "opencode", "opencode.json");
491
+ case "antigravity":
492
+ return join(home, ".antigravity", "mcp.json");
493
+ }
494
+ }
495
+ function installNativeHost(path, host, plan) {
496
+ mkdirSync(dirname(path), { recursive: true });
497
+ if (host === "codex") {
498
+ writeFileSync(path, installCodexToml(readTextFile(path), plan), { mode: 420 });
499
+ return;
500
+ }
501
+ const config = readJsonObject(path);
502
+ if (host === "claude-code") {
503
+ writeJsonObject(path, installClaudeConfig(config, plan));
504
+ return;
505
+ }
506
+ if (host === "opencode") {
507
+ writeJsonObject(path, installOpenCodeConfig(config, plan));
508
+ return;
509
+ }
510
+ writeJsonObject(path, installAntigravityConfig(config, plan));
511
+ }
512
+ function uninstallNativeHost(path, host) {
513
+ if (!existsSync(path))
514
+ return;
515
+ if (host === "codex") {
516
+ writeFileSync(path, uninstallCodexToml(readTextFile(path)), { mode: 420 });
517
+ return;
518
+ }
519
+ const config = readJsonObject(path);
520
+ if (host === "claude-code")
521
+ writeJsonObject(path, uninstallClaudeConfig(config));
522
+ if (host === "opencode")
523
+ writeJsonObject(path, uninstallOpenCodeConfig(config));
524
+ if (host === "antigravity")
525
+ writeJsonObject(path, uninstallMcpServersConfig(config));
526
+ }
527
+ function hasNativeHostInstall(path, host) {
528
+ if (!existsSync(path))
529
+ return false;
530
+ if (host === "codex")
531
+ return /\[mcp_servers\.mementos\]/.test(readTextFile(path));
532
+ const config = readJsonObject(path);
533
+ if (host === "opencode")
534
+ return isRecord(config.mcp) && isRecord(config.mcp.mementos);
535
+ return isRecord(config.mcpServers) && isRecord(config.mcpServers.mementos);
536
+ }
537
+ function installClaudeConfig(config, plan) {
538
+ return {
539
+ ...config,
540
+ mcpServers: {
541
+ ...recordValue(config.mcpServers),
542
+ mementos: stdioMcpServer(plan, true)
543
+ },
544
+ hooks: mergeAgentHooks(recordValue(config.hooks), {
545
+ UserPromptSubmit: "mementos hook user-prompt-submit",
546
+ SessionStart: "mementos hook session-start",
547
+ Stop: "mementos hook stop"
548
+ })
549
+ };
550
+ }
551
+ function uninstallClaudeConfig(config) {
552
+ return {
553
+ ...config,
554
+ mcpServers: omitKey(recordValue(config.mcpServers), "mementos"),
555
+ hooks: removeAgentHooks(recordValue(config.hooks), [
556
+ "mementos hook user-prompt-submit",
557
+ "mementos hook session-start",
558
+ "mementos hook stop"
559
+ ])
560
+ };
561
+ }
562
+ function installOpenCodeConfig(config, plan) {
563
+ return {
564
+ ...config,
565
+ mcp: {
566
+ ...recordValue(config.mcp),
567
+ mementos: {
568
+ type: "local",
569
+ command: [plan.mcpCommand],
570
+ enabled: true,
571
+ environment: remoteEnv(baseApiUrlFromPlan(plan))
572
+ }
573
+ }
574
+ };
575
+ }
576
+ function uninstallOpenCodeConfig(config) {
577
+ return {
578
+ ...config,
579
+ mcp: omitKey(recordValue(config.mcp), "mementos")
580
+ };
581
+ }
582
+ function installAntigravityConfig(config, plan) {
583
+ return {
584
+ ...config,
585
+ mcpServers: {
586
+ ...recordValue(config.mcpServers),
587
+ mementos: stdioMcpServer(plan, false)
588
+ }
589
+ };
590
+ }
591
+ function uninstallMcpServersConfig(config) {
592
+ return {
593
+ ...config,
594
+ mcpServers: omitKey(recordValue(config.mcpServers), "mementos")
595
+ };
596
+ }
597
+ function installCodexToml(current, plan) {
598
+ const withoutExisting = removeTomlTable(removeManagedCodexBlock(current), "mcp_servers.mementos");
599
+ const block = [
600
+ "# BEGIN @hasna/mementos",
601
+ "[mcp_servers.mementos]",
602
+ `command = ${JSON.stringify(plan.mcpCommand)}`,
603
+ "[mcp_servers.mementos.env]",
604
+ `MEMENTOS_API_URL = ${JSON.stringify(baseApiUrlFromPlan(plan))}`,
605
+ 'MEMENTOS_API_TOKEN = "$MEMENTOS_API_TOKEN"',
606
+ "# END @hasna/mementos",
607
+ ""
608
+ ].join(`
609
+ `);
610
+ return `${withoutExisting.trimEnd()}
611
+
612
+ ${block}`;
613
+ }
614
+ function uninstallCodexToml(current) {
615
+ return `${removeTomlTable(removeManagedCodexBlock(current), "mcp_servers.mementos").trimEnd()}
616
+ `;
617
+ }
618
+ function mergeAgentHooks(hooks, commands) {
619
+ const next = { ...hooks };
620
+ for (const [event, command] of Object.entries(commands)) {
621
+ const current = next[event];
622
+ const entries = Array.isArray(current) ? [...current] : [];
623
+ if (!entries.some((entry) => JSON.stringify(entry).includes(command))) {
624
+ entries.push({ matcher: "", hooks: [{ type: "command", command }] });
625
+ }
626
+ next[event] = entries;
627
+ }
628
+ return next;
629
+ }
630
+ function removeAgentHooks(hooks, commands) {
631
+ const next = { ...hooks };
632
+ for (const [event, entries] of Object.entries(next)) {
633
+ if (!Array.isArray(entries))
634
+ continue;
635
+ next[event] = entries.filter((entry) => !commands.some((command) => JSON.stringify(entry).includes(command)));
636
+ }
637
+ return next;
638
+ }
639
+ function stdioMcpServer(plan, includeType) {
640
+ return {
641
+ ...includeType ? { type: "stdio" } : {},
642
+ command: plan.mcpCommand,
643
+ env: remoteEnv(baseApiUrlFromPlan(plan))
644
+ };
645
+ }
646
+ function remoteEnv(apiUrl) {
647
+ return {
648
+ MEMENTOS_API_URL: apiUrl,
649
+ MEMENTOS_API_TOKEN: "$MEMENTOS_API_TOKEN"
650
+ };
651
+ }
652
+ function baseApiUrlFromPlan(plan) {
653
+ const mcpFile = plan.files.find((file) => file.path.endsWith("mcp-server.json"));
654
+ if (!mcpFile)
655
+ return "https://mementos.md";
656
+ try {
657
+ const parsed = JSON.parse(mcpFile.content);
658
+ return parsed.mcpServers?.mementos?.env?.MEMENTOS_API_URL ?? "https://mementos.md";
659
+ } catch {
660
+ return "https://mementos.md";
661
+ }
662
+ }
663
+ function removeManagedCodexBlock(value) {
664
+ return value.replace(/\n?# BEGIN @hasna\/mementos\n[\s\S]*?# END @hasna\/mementos\n?/g, `
665
+ `);
666
+ }
667
+ function removeTomlTable(value, table) {
668
+ const tablePattern = table.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
669
+ const lines = value.split(`
670
+ `);
671
+ const output = [];
672
+ let skipping = false;
673
+ for (const line of lines) {
674
+ if (new RegExp(`^\\[${tablePattern}(?:\\.|\\])`).test(line.trim())) {
675
+ skipping = true;
676
+ continue;
677
+ }
678
+ if (skipping && /^\[[^\]]+\]/.test(line.trim()))
679
+ skipping = false;
680
+ if (!skipping)
681
+ output.push(line);
682
+ }
683
+ return output.join(`
684
+ `);
685
+ }
686
+ function readTextFile(path) {
687
+ return existsSync(path) ? readFileSync(path, "utf8") : "";
688
+ }
689
+ function readJsonObject(path) {
690
+ if (!existsSync(path))
691
+ return {};
692
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
693
+ if (!isRecord(parsed))
694
+ throw new Error(`${path} must contain a JSON object`);
695
+ return parsed;
696
+ }
697
+ function writeJsonObject(path, value) {
698
+ mkdirSync(dirname(path), { recursive: true });
699
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}
700
+ `, { mode: 420 });
701
+ }
702
+ function recordValue(value) {
703
+ return isRecord(value) ? value : {};
704
+ }
705
+ function isRecord(value) {
706
+ return typeof value === "object" && value !== null && !Array.isArray(value);
707
+ }
708
+ function omitKey(record, key) {
709
+ const { [key]: _removed, ...rest } = record;
710
+ return rest;
711
+ }
476
712
  function renderInjectionInstructions({ apiUrl, injectCommand }) {
477
713
  return `# mementos.md Memory Injection
478
714
 
@@ -1,4 +1,5 @@
1
1
  import { MementosRemoteClient } from "./remote-client";
2
+ export declare const MEMENTOS_MCP_VERSION = "0.14.24";
2
3
  export interface RemoteMcpTool {
3
4
  name: string;
4
5
  description: string;
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-runtime.d.ts","sourceRoot":"","sources":["../src/mcp-runtime.ts"],"names":[],"mappings":"AACA,OAAO,EAA6B,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAElF,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,oBAAoB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACzF;AAaD,eAAO,MAAM,gBAAgB,EAAE,aAAa,EA4F3C,CAAC;AAEF,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACnC,MAAM,uBAA8B,oBAKrC;AAED,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,MAAM,uBAA8B;;;;;;;;;;;GA8BxF;AAED,wBAAgB,aAAa,CAAC,MAAM,uBAA8B,QAUjE"}
1
+ {"version":3,"file":"mcp-runtime.d.ts","sourceRoot":"","sources":["../src/mcp-runtime.ts"],"names":[],"mappings":"AACA,OAAO,EAA6B,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAElF,eAAO,MAAM,oBAAoB,YAAY,CAAC;AAE9C,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,oBAAoB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACzF;AAaD,eAAO,MAAM,gBAAgB,EAAE,aAAa,EA4F3C,CAAC;AAEF,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACnC,MAAM,uBAA8B,oBAKrC;AAED,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,MAAM,uBAA8B;;;;;;;;;;;GA8BxF;AAED,wBAAgB,aAAa,CAAC,MAAM,uBAA8B,QAUjE"}
package/dist/mcp.js CHANGED
@@ -136,6 +136,7 @@ function readErrorMessage(payload, status) {
136
136
  }
137
137
 
138
138
  // src/mcp-runtime.ts
139
+ var MEMENTOS_MCP_VERSION = "0.14.24";
139
140
  var AUTHLESS_SCHEMA = {
140
141
  type: "object",
141
142
  properties: {},
@@ -250,7 +251,7 @@ async function handleMcpMessage(message, client = createRemoteClientFromEnv()) {
250
251
  if (message?.method === "initialize") {
251
252
  return jsonRpcResult(id, {
252
253
  protocolVersion: "2024-11-05",
253
- serverInfo: { name: "mementos", version: "0.14.22" },
254
+ serverInfo: { name: "mementos", version: MEMENTOS_MCP_VERSION },
254
255
  capabilities: { tools: {} }
255
256
  });
256
257
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/mementos",
3
- "version": "0.14.22",
3
+ "version": "0.14.24",
4
4
  "description": "Remote-only mementos.md SaaS CLI and MCP client",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",