@hasna/mementos 0.14.22 → 0.14.23
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 +9 -2
- package/dist/agent-setup.d.ts.map +1 -1
- package/dist/cli.js +282 -7
- package/dist/index.js +240 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,8 +18,9 @@ mementos auth signup
|
|
|
18
18
|
mementos setup agents
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
`mementos setup agents`
|
|
22
|
-
Codex, OpenCode, and Antigravity
|
|
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,
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
@@ -291,7 +291,7 @@ function requiredText(input, message) {
|
|
|
291
291
|
return input.trim();
|
|
292
292
|
}
|
|
293
293
|
// src/agent-setup.ts
|
|
294
|
-
import { existsSync, mkdirSync, rmSync, writeFileSync } from "fs";
|
|
294
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
295
295
|
import { homedir } from "os";
|
|
296
296
|
import { dirname, join } from "path";
|
|
297
297
|
var SUPPORTED_AGENT_HOSTS = ["claude-code", "codex", "opencode", "antigravity"];
|
|
@@ -334,7 +334,7 @@ function buildAgentSetupPlan(options = {}) {
|
|
|
334
334
|
return {
|
|
335
335
|
baseDir,
|
|
336
336
|
files,
|
|
337
|
-
hostResults: hosts.map((host) => ({ host, path:
|
|
337
|
+
hostResults: hosts.map((host) => ({ host, path: hostNativeConfigPath(home, host), status: "planned" })),
|
|
338
338
|
hosts,
|
|
339
339
|
mcpCommand
|
|
340
340
|
};
|
|
@@ -346,7 +346,7 @@ function installAgentSetup(options = {}) {
|
|
|
346
346
|
...plan,
|
|
347
347
|
hostResults: plan.hostResults.map((result) => ({
|
|
348
348
|
...result,
|
|
349
|
-
status:
|
|
349
|
+
status: hasNativeHostInstall(result.path, result.host) ? "present" : "missing"
|
|
350
350
|
}))
|
|
351
351
|
};
|
|
352
352
|
}
|
|
@@ -358,7 +358,9 @@ function installAgentSetup(options = {}) {
|
|
|
358
358
|
};
|
|
359
359
|
}
|
|
360
360
|
for (const result of plan.hostResults)
|
|
361
|
-
|
|
361
|
+
uninstallNativeHost(result.path, result.host);
|
|
362
|
+
for (const host of plan.hosts)
|
|
363
|
+
rmSync(hostManagedConfigPath(options.homeDir ?? homedir(), host), { force: true });
|
|
362
364
|
return {
|
|
363
365
|
...plan,
|
|
364
366
|
hostResults: plan.hostResults.map((result) => ({ ...result, status: "removed" }))
|
|
@@ -370,6 +372,8 @@ function installAgentSetup(options = {}) {
|
|
|
370
372
|
mkdirSync(dirname(file.path), { recursive: true });
|
|
371
373
|
writeFileSync(file.path, file.content, { mode: file.executable ? 493 : 420 });
|
|
372
374
|
}
|
|
375
|
+
for (const result of plan.hostResults)
|
|
376
|
+
installNativeHost(result.path, result.host, plan);
|
|
373
377
|
return {
|
|
374
378
|
...plan,
|
|
375
379
|
hostResults: plan.hostResults.map((result) => ({ ...result, status: "installed" }))
|
|
@@ -444,7 +448,9 @@ function renderManagedHostConfig(host, commands) {
|
|
|
444
448
|
return {
|
|
445
449
|
...base,
|
|
446
450
|
hooks: {
|
|
447
|
-
|
|
451
|
+
UserPromptSubmit: [{ matcher: "", hooks: [{ type: "command", command: "mementos hook user-prompt-submit" }] }],
|
|
452
|
+
SessionStart: [{ matcher: "", hooks: [{ type: "command", command: "mementos hook session-start" }] }],
|
|
453
|
+
Stop: [{ matcher: "", hooks: [{ type: "command", command: commands.hookCommand }] }]
|
|
448
454
|
}
|
|
449
455
|
};
|
|
450
456
|
}
|
|
@@ -473,6 +479,235 @@ function hostManagedConfigPath(home, host) {
|
|
|
473
479
|
return join(home, ".config", "antigravity", "mementos.managed.json");
|
|
474
480
|
}
|
|
475
481
|
}
|
|
482
|
+
function hostNativeConfigPath(home, host) {
|
|
483
|
+
switch (host) {
|
|
484
|
+
case "claude-code":
|
|
485
|
+
return join(home, ".claude", "settings.json");
|
|
486
|
+
case "codex":
|
|
487
|
+
return join(home, ".codex", "config.toml");
|
|
488
|
+
case "opencode":
|
|
489
|
+
return join(home, ".config", "opencode", "opencode.json");
|
|
490
|
+
case "antigravity":
|
|
491
|
+
return join(home, ".antigravity", "mcp.json");
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
function installNativeHost(path, host, plan) {
|
|
495
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
496
|
+
if (host === "codex") {
|
|
497
|
+
writeFileSync(path, installCodexToml(readTextFile(path), plan), { mode: 420 });
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
const config = readJsonObject(path);
|
|
501
|
+
if (host === "claude-code") {
|
|
502
|
+
writeJsonObject(path, installClaudeConfig(config, plan));
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
if (host === "opencode") {
|
|
506
|
+
writeJsonObject(path, installOpenCodeConfig(config, plan));
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
writeJsonObject(path, installAntigravityConfig(config, plan));
|
|
510
|
+
}
|
|
511
|
+
function uninstallNativeHost(path, host) {
|
|
512
|
+
if (!existsSync(path))
|
|
513
|
+
return;
|
|
514
|
+
if (host === "codex") {
|
|
515
|
+
writeFileSync(path, uninstallCodexToml(readTextFile(path)), { mode: 420 });
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
const config = readJsonObject(path);
|
|
519
|
+
if (host === "claude-code")
|
|
520
|
+
writeJsonObject(path, uninstallClaudeConfig(config));
|
|
521
|
+
if (host === "opencode")
|
|
522
|
+
writeJsonObject(path, uninstallOpenCodeConfig(config));
|
|
523
|
+
if (host === "antigravity")
|
|
524
|
+
writeJsonObject(path, uninstallMcpServersConfig(config));
|
|
525
|
+
}
|
|
526
|
+
function hasNativeHostInstall(path, host) {
|
|
527
|
+
if (!existsSync(path))
|
|
528
|
+
return false;
|
|
529
|
+
if (host === "codex")
|
|
530
|
+
return /\[mcp_servers\.mementos\]/.test(readTextFile(path));
|
|
531
|
+
const config = readJsonObject(path);
|
|
532
|
+
if (host === "opencode")
|
|
533
|
+
return isRecord(config.mcp) && isRecord(config.mcp.mementos);
|
|
534
|
+
return isRecord(config.mcpServers) && isRecord(config.mcpServers.mementos);
|
|
535
|
+
}
|
|
536
|
+
function installClaudeConfig(config, plan) {
|
|
537
|
+
return {
|
|
538
|
+
...config,
|
|
539
|
+
mcpServers: {
|
|
540
|
+
...recordValue(config.mcpServers),
|
|
541
|
+
mementos: stdioMcpServer(plan, true)
|
|
542
|
+
},
|
|
543
|
+
hooks: mergeAgentHooks(recordValue(config.hooks), {
|
|
544
|
+
UserPromptSubmit: "mementos hook user-prompt-submit",
|
|
545
|
+
SessionStart: "mementos hook session-start",
|
|
546
|
+
Stop: "mementos hook stop"
|
|
547
|
+
})
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
function uninstallClaudeConfig(config) {
|
|
551
|
+
return {
|
|
552
|
+
...config,
|
|
553
|
+
mcpServers: omitKey(recordValue(config.mcpServers), "mementos"),
|
|
554
|
+
hooks: removeAgentHooks(recordValue(config.hooks), [
|
|
555
|
+
"mementos hook user-prompt-submit",
|
|
556
|
+
"mementos hook session-start",
|
|
557
|
+
"mementos hook stop"
|
|
558
|
+
])
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
function installOpenCodeConfig(config, plan) {
|
|
562
|
+
return {
|
|
563
|
+
...config,
|
|
564
|
+
mcp: {
|
|
565
|
+
...recordValue(config.mcp),
|
|
566
|
+
mementos: {
|
|
567
|
+
type: "local",
|
|
568
|
+
command: [plan.mcpCommand],
|
|
569
|
+
enabled: true,
|
|
570
|
+
environment: remoteEnv(baseApiUrlFromPlan(plan))
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
function uninstallOpenCodeConfig(config) {
|
|
576
|
+
return {
|
|
577
|
+
...config,
|
|
578
|
+
mcp: omitKey(recordValue(config.mcp), "mementos")
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
function installAntigravityConfig(config, plan) {
|
|
582
|
+
return {
|
|
583
|
+
...config,
|
|
584
|
+
mcpServers: {
|
|
585
|
+
...recordValue(config.mcpServers),
|
|
586
|
+
mementos: stdioMcpServer(plan, false)
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
function uninstallMcpServersConfig(config) {
|
|
591
|
+
return {
|
|
592
|
+
...config,
|
|
593
|
+
mcpServers: omitKey(recordValue(config.mcpServers), "mementos")
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
function installCodexToml(current, plan) {
|
|
597
|
+
const withoutExisting = removeTomlTable(removeManagedCodexBlock(current), "mcp_servers.mementos");
|
|
598
|
+
const block = [
|
|
599
|
+
"# BEGIN @hasna/mementos",
|
|
600
|
+
"[mcp_servers.mementos]",
|
|
601
|
+
`command = ${JSON.stringify(plan.mcpCommand)}`,
|
|
602
|
+
"[mcp_servers.mementos.env]",
|
|
603
|
+
`MEMENTOS_API_URL = ${JSON.stringify(baseApiUrlFromPlan(plan))}`,
|
|
604
|
+
'MEMENTOS_API_TOKEN = "$MEMENTOS_API_TOKEN"',
|
|
605
|
+
"# END @hasna/mementos",
|
|
606
|
+
""
|
|
607
|
+
].join(`
|
|
608
|
+
`);
|
|
609
|
+
return `${withoutExisting.trimEnd()}
|
|
610
|
+
|
|
611
|
+
${block}`;
|
|
612
|
+
}
|
|
613
|
+
function uninstallCodexToml(current) {
|
|
614
|
+
return `${removeTomlTable(removeManagedCodexBlock(current), "mcp_servers.mementos").trimEnd()}
|
|
615
|
+
`;
|
|
616
|
+
}
|
|
617
|
+
function mergeAgentHooks(hooks, commands) {
|
|
618
|
+
const next = { ...hooks };
|
|
619
|
+
for (const [event, command] of Object.entries(commands)) {
|
|
620
|
+
const current = next[event];
|
|
621
|
+
const entries = Array.isArray(current) ? [...current] : [];
|
|
622
|
+
if (!entries.some((entry) => JSON.stringify(entry).includes(command))) {
|
|
623
|
+
entries.push({ matcher: "", hooks: [{ type: "command", command }] });
|
|
624
|
+
}
|
|
625
|
+
next[event] = entries;
|
|
626
|
+
}
|
|
627
|
+
return next;
|
|
628
|
+
}
|
|
629
|
+
function removeAgentHooks(hooks, commands) {
|
|
630
|
+
const next = { ...hooks };
|
|
631
|
+
for (const [event, entries] of Object.entries(next)) {
|
|
632
|
+
if (!Array.isArray(entries))
|
|
633
|
+
continue;
|
|
634
|
+
next[event] = entries.filter((entry) => !commands.some((command) => JSON.stringify(entry).includes(command)));
|
|
635
|
+
}
|
|
636
|
+
return next;
|
|
637
|
+
}
|
|
638
|
+
function stdioMcpServer(plan, includeType) {
|
|
639
|
+
return {
|
|
640
|
+
...includeType ? { type: "stdio" } : {},
|
|
641
|
+
command: plan.mcpCommand,
|
|
642
|
+
env: remoteEnv(baseApiUrlFromPlan(plan))
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
function remoteEnv(apiUrl) {
|
|
646
|
+
return {
|
|
647
|
+
MEMENTOS_API_URL: apiUrl,
|
|
648
|
+
MEMENTOS_API_TOKEN: "$MEMENTOS_API_TOKEN"
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
function baseApiUrlFromPlan(plan) {
|
|
652
|
+
const mcpFile = plan.files.find((file) => file.path.endsWith("mcp-server.json"));
|
|
653
|
+
if (!mcpFile)
|
|
654
|
+
return "https://mementos.md";
|
|
655
|
+
try {
|
|
656
|
+
const parsed = JSON.parse(mcpFile.content);
|
|
657
|
+
return parsed.mcpServers?.mementos?.env?.MEMENTOS_API_URL ?? "https://mementos.md";
|
|
658
|
+
} catch {
|
|
659
|
+
return "https://mementos.md";
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
function removeManagedCodexBlock(value) {
|
|
663
|
+
return value.replace(/\n?# BEGIN @hasna\/mementos\n[\s\S]*?# END @hasna\/mementos\n?/g, `
|
|
664
|
+
`);
|
|
665
|
+
}
|
|
666
|
+
function removeTomlTable(value, table) {
|
|
667
|
+
const tablePattern = table.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
668
|
+
const lines = value.split(`
|
|
669
|
+
`);
|
|
670
|
+
const output = [];
|
|
671
|
+
let skipping = false;
|
|
672
|
+
for (const line of lines) {
|
|
673
|
+
if (new RegExp(`^\\[${tablePattern}(?:\\.|\\])`).test(line.trim())) {
|
|
674
|
+
skipping = true;
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
if (skipping && /^\[[^\]]+\]/.test(line.trim()))
|
|
678
|
+
skipping = false;
|
|
679
|
+
if (!skipping)
|
|
680
|
+
output.push(line);
|
|
681
|
+
}
|
|
682
|
+
return output.join(`
|
|
683
|
+
`);
|
|
684
|
+
}
|
|
685
|
+
function readTextFile(path) {
|
|
686
|
+
return existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
687
|
+
}
|
|
688
|
+
function readJsonObject(path) {
|
|
689
|
+
if (!existsSync(path))
|
|
690
|
+
return {};
|
|
691
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
692
|
+
if (!isRecord(parsed))
|
|
693
|
+
throw new Error(`${path} must contain a JSON object`);
|
|
694
|
+
return parsed;
|
|
695
|
+
}
|
|
696
|
+
function writeJsonObject(path, value) {
|
|
697
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
698
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}
|
|
699
|
+
`, { mode: 420 });
|
|
700
|
+
}
|
|
701
|
+
function recordValue(value) {
|
|
702
|
+
return isRecord(value) ? value : {};
|
|
703
|
+
}
|
|
704
|
+
function isRecord(value) {
|
|
705
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
706
|
+
}
|
|
707
|
+
function omitKey(record, key) {
|
|
708
|
+
const { [key]: _removed, ...rest } = record;
|
|
709
|
+
return rest;
|
|
710
|
+
}
|
|
476
711
|
function renderInjectionInstructions({ apiUrl, injectCommand }) {
|
|
477
712
|
return `# mementos.md Memory Injection
|
|
478
713
|
|