toon-memory 1.7.0 → 2.0.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.
package/dist/cli/setup.js CHANGED
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync, readdirSync, statSync } from "fs";
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync, readdirSync, statSync, chmodSync, rmSync } from "fs";
2
2
  import { basename, dirname, join } from "path";
3
3
  import { fileURLToPath } from "url";
4
4
  import { createInterface } from "readline";
@@ -7,68 +7,170 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
7
7
  const projectRoot = process.cwd();
8
8
  const sourceDir = join(__dirname, "..", "..", "src");
9
9
  const HOME = process.env.HOME || process.env.USERPROFILE || "~";
10
+ const MEMORY_DIR = join(projectRoot, ".toon-memory", "memory");
11
+ const HOOK_CONTENT = `#!/bin/bash
12
+ echo "toon-memory: Use memory_recall BEFORE reading files for project context."
13
+ exit 0
14
+ `;
15
+ const INSTRUCTION_CONTENT = `# toon-memory
16
+
17
+ Persistent memory for this project. Use it to avoid re-investigating things.
18
+
19
+ ## At the START of every session
20
+ 1. Run memory_stats to see what's in memory.
21
+ 2. If the user asks something that might be in memory, run memory_recall BEFORE reading files.
22
+
23
+ ## When making decisions
24
+ - Before implementing a non-trivial change: memory_remember(category='decision')
25
+ - When you resolve a complex bug: memory_remember(category='bug')
26
+ - When you observe a code pattern: memory_remember(category='pattern')
27
+
28
+ ## At the END of every session
29
+ - Save important decisions, bugs resolved, and patterns observed.
30
+ `;
10
31
  function detectAgents() {
11
32
  const agents2 = [];
12
- const opencodeGlobal = join(HOME, ".config", "opencode", "opencode.json");
13
- const opencodeLocal = join(projectRoot, ".opencode", "opencode.json");
14
33
  agents2.push({
15
34
  name: "opencode",
16
- global: opencodeGlobal,
17
- local: opencodeLocal,
18
- mcpKey: "mcp"
35
+ global: join(HOME, ".config", "opencode", "opencode.json"),
36
+ local: join(projectRoot, ".opencode", "opencode.json"),
37
+ mcpKey: "mcp",
38
+ format: "json",
39
+ needsHooks: false,
40
+ needsInstructions: true,
41
+ instructionFile: join(projectRoot, "AGENTS.md")
19
42
  });
20
- const vscodeLocal = join(projectRoot, ".vscode", "mcp.json");
21
43
  agents2.push({
22
44
  name: "vscode/copilot",
23
- local: vscodeLocal,
24
- mcpKey: "servers"
45
+ local: join(projectRoot, ".vscode", "mcp.json"),
46
+ mcpKey: "servers",
47
+ format: "json",
48
+ needsHooks: false,
49
+ needsInstructions: false
25
50
  });
26
- const claudeGlobal = join(HOME, ".claude", "settings.json");
27
- const claudeLocal = join(projectRoot, ".claude", "settings.json");
28
51
  agents2.push({
29
52
  name: "claude",
30
- global: claudeGlobal,
31
- local: claudeLocal,
32
- mcpKey: "mcpServers"
53
+ global: join(HOME, ".claude", "settings.json"),
54
+ local: join(projectRoot, ".claude", "settings.json"),
55
+ mcpKey: "mcpServers",
56
+ format: "json",
57
+ needsHooks: true,
58
+ needsInstructions: true,
59
+ instructionFile: join(projectRoot, ".claude", "AGENTS.md")
33
60
  });
34
- const cursorLocal = join(projectRoot, ".cursor", "mcp.json");
35
61
  agents2.push({
36
62
  name: "cursor",
37
- local: cursorLocal,
38
- mcpKey: "mcpServers"
63
+ local: join(projectRoot, ".cursor", "mcp.json"),
64
+ mcpKey: "mcpServers",
65
+ format: "json",
66
+ needsHooks: false,
67
+ needsInstructions: false
39
68
  });
40
- const windsurfGlobal = join(HOME, ".codeium", "windsurf", "mcp_config.json");
41
69
  agents2.push({
42
70
  name: "windsurf",
43
- global: windsurfGlobal,
44
- mcpKey: "mcpServers"
71
+ global: join(HOME, ".codeium", "windsurf", "mcp_config.json"),
72
+ mcpKey: "mcpServers",
73
+ format: "json",
74
+ needsHooks: false,
75
+ needsInstructions: false
45
76
  });
46
- const clineLocal = join(projectRoot, ".cline", "mcp.json");
47
77
  agents2.push({
48
78
  name: "cline",
49
- local: clineLocal,
50
- mcpKey: "mcpServers"
79
+ local: join(projectRoot, ".cline", "mcp.json"),
80
+ mcpKey: "mcpServers",
81
+ format: "json",
82
+ needsHooks: false,
83
+ needsInstructions: false
51
84
  });
52
- const continueLocal = join(projectRoot, ".continue", "config.json");
53
85
  agents2.push({
54
86
  name: "continue",
55
- local: continueLocal,
56
- mcpKey: "mcpServers"
87
+ local: join(projectRoot, ".continue", "config.json"),
88
+ mcpKey: "mcpServers",
89
+ format: "json",
90
+ needsHooks: false,
91
+ needsInstructions: false
92
+ });
93
+ agents2.push({
94
+ name: "codex",
95
+ local: join(projectRoot, ".codex", "config.toml"),
96
+ mcpKey: "mcpServers",
97
+ format: "toml",
98
+ needsHooks: true,
99
+ needsInstructions: true,
100
+ instructionFile: join(projectRoot, ".codex", "AGENTS.md")
101
+ });
102
+ agents2.push({
103
+ name: "gemini",
104
+ local: join(projectRoot, ".gemini", "settings.json"),
105
+ mcpKey: "mcpServers",
106
+ format: "json",
107
+ needsHooks: true,
108
+ needsInstructions: true,
109
+ instructionFile: join(projectRoot, ".gemini", "GEMINI.md")
110
+ });
111
+ agents2.push({
112
+ name: "zed",
113
+ global: join(HOME, ".config", "zed", "settings.json"),
114
+ mcpKey: "mcp_servers",
115
+ format: "jsonc",
116
+ needsHooks: false,
117
+ needsInstructions: false
118
+ });
119
+ agents2.push({
120
+ name: "antigravity",
121
+ local: join(projectRoot, ".gemini", "config", "mcp_config.json"),
122
+ mcpKey: "mcpServers",
123
+ format: "json",
124
+ needsHooks: true,
125
+ needsInstructions: true,
126
+ instructionFile: join(projectRoot, "antigravity-cli", "AGENTS.md")
127
+ });
128
+ agents2.push({
129
+ name: "aider",
130
+ mcpKey: "",
131
+ format: "none",
132
+ needsHooks: false,
133
+ needsInstructions: true,
134
+ instructionFile: join(projectRoot, "CONVENTIONS.md")
135
+ });
136
+ agents2.push({
137
+ name: "kilocode",
138
+ global: join(HOME, ".kilocode", "mcp_settings.json"),
139
+ mcpKey: "mcpServers",
140
+ format: "json",
141
+ needsHooks: false,
142
+ needsInstructions: true,
143
+ instructionFile: join(HOME, ".kilocode", "rules", "toon-memory.md")
144
+ });
145
+ agents2.push({
146
+ name: "openclaw",
147
+ local: join(projectRoot, ".openclaw.json"),
148
+ mcpKey: "mcpServers",
149
+ format: "json",
150
+ needsHooks: false,
151
+ needsInstructions: false
152
+ });
153
+ agents2.push({
154
+ name: "kiro",
155
+ local: join(projectRoot, ".kiro", "settings", "mcp.json"),
156
+ mcpKey: "mcpServers",
157
+ format: "json",
158
+ needsHooks: false,
159
+ needsInstructions: false
57
160
  });
58
161
  return agents2;
59
162
  }
60
- function installOpenCodeTools() {
61
- const memoryDir = join(projectRoot, ".opencode", "memory");
62
- const memoryFile = join(memoryDir, "data.toon");
63
- if (!existsSync(memoryDir)) mkdirSync(memoryDir, { recursive: true });
163
+ function installMemoryDir() {
164
+ const memoryFile = join(MEMORY_DIR, "data.toon");
165
+ if (!existsSync(MEMORY_DIR)) mkdirSync(MEMORY_DIR, { recursive: true });
64
166
  if (!existsSync(memoryFile)) {
65
167
  writeFileSync(memoryFile, "version: 1\n[0|]\n");
66
- console.log(" Created .opencode/memory/data.toon");
168
+ console.log(" Created .toon-memory/memory/data.toon");
67
169
  }
68
170
  }
69
171
  function ensureGitignore() {
70
172
  const gitignorePath = join(projectRoot, ".gitignore");
71
- const entry = ".opencode/memory/";
173
+ const entry = ".toon-memory/memory/";
72
174
  if (!existsSync(gitignorePath)) {
73
175
  writeFileSync(gitignorePath, `${entry}
74
176
  `);
@@ -80,10 +182,10 @@ function ensureGitignore() {
80
182
  writeFileSync(gitignorePath, `${content.trim()}
81
183
  ${entry}
82
184
  `);
83
- console.log(" Added .opencode/memory/ to .gitignore");
185
+ console.log(" Added .toon-memory/memory/ to .gitignore");
84
186
  }
85
187
  }
86
- function installMCPConfig(agent, scope) {
188
+ function installJSONConfig(agent, scope) {
87
189
  const configPath = scope === "global" ? agent.global : agent.local;
88
190
  if (!configPath) {
89
191
  console.log(` No ${scope} config path for ${agent.name}`);
@@ -116,46 +218,217 @@ function installMCPConfig(agent, scope) {
116
218
  writeFileSync(configPath, JSON.stringify(config, null, 2));
117
219
  console.log(` MCP server added to ${configPath}`);
118
220
  }
221
+ function installTOMLConfig(agent) {
222
+ const configPath = agent.local;
223
+ if (!configPath) return;
224
+ const configDir = dirname(configPath);
225
+ if (!existsSync(configDir)) mkdirSync(configDir, { recursive: true });
226
+ const toml = `[mcpServers.toon-memory]
227
+ command = "npx"
228
+ args = ["-y", "toon-memory", "mcp"]
229
+ `;
230
+ writeFileSync(configPath, toml);
231
+ console.log(` MCP server added to ${configPath}`);
232
+ }
233
+ function installZedConfig(agent) {
234
+ const configPath = agent.global;
235
+ if (!configPath) return;
236
+ const configDir = dirname(configPath);
237
+ if (!existsSync(configDir)) mkdirSync(configDir, { recursive: true });
238
+ let config = {};
239
+ if (existsSync(configPath)) {
240
+ try {
241
+ const raw = readFileSync(configPath, "utf-8");
242
+ const stripped = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
243
+ config = JSON.parse(stripped);
244
+ } catch {
245
+ config = {};
246
+ }
247
+ }
248
+ if (!config.mcp_servers) config.mcp_servers = {};
249
+ config.mcp_servers["toon-memory"] = {
250
+ command: "npx",
251
+ args: ["-y", "toon-memory", "mcp"]
252
+ };
253
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
254
+ console.log(` MCP server added to ${configPath}`);
255
+ }
256
+ function installMCPConfig(agent, scope) {
257
+ if (agent.format === "none") {
258
+ console.log(` ${agent.name}: instructions only (no MCP)`);
259
+ return;
260
+ }
261
+ if (agent.format === "toml") {
262
+ installTOMLConfig(agent);
263
+ } else if (agent.format === "jsonc") {
264
+ installZedConfig(agent);
265
+ } else {
266
+ installJSONConfig(agent, scope);
267
+ }
268
+ }
269
+ function installInstructions(agent) {
270
+ if (!agent.needsInstructions || !agent.instructionFile) return;
271
+ const filePath = agent.instructionFile;
272
+ const fileDir = dirname(filePath);
273
+ if (!existsSync(fileDir)) mkdirSync(fileDir, { recursive: true });
274
+ if (existsSync(filePath)) {
275
+ const existing = readFileSync(filePath, "utf-8");
276
+ if (existing.includes("toon-memory") || existing.includes("memory_recall")) {
277
+ console.log(` Instructions already present in ${filePath}`);
278
+ return;
279
+ }
280
+ writeFileSync(filePath, `${existing.trim()}
281
+
282
+ ${INSTRUCTION_CONTENT}`);
283
+ console.log(` Appended toon-memory instructions to ${filePath}`);
284
+ } else {
285
+ writeFileSync(filePath, INSTRUCTION_CONTENT);
286
+ console.log(` Created ${filePath}`);
287
+ }
288
+ }
289
+ function installHooks(agent) {
290
+ if (!agent.needsHooks) return;
291
+ const hookDir = join(projectRoot, ".toon-memory", "hooks");
292
+ if (!existsSync(hookDir)) mkdirSync(hookDir, { recursive: true });
293
+ const hookPath = join(hookDir, `session-start-${agent.name}.sh`);
294
+ writeFileSync(hookPath, HOOK_CONTENT);
295
+ chmodSync(hookPath, 493);
296
+ console.log(` Hook script created at ${hookPath}`);
297
+ if (agent.format === "toml" && agent.local) {
298
+ registerHookTOML(agent, hookPath);
299
+ } else if (agent.format === "json" || agent.format === "jsonc") {
300
+ registerHookJSON(agent, hookPath);
301
+ }
302
+ }
303
+ function registerHookTOML(agent, hookPath) {
304
+ const configPath = agent.local;
305
+ if (!configPath || !existsSync(configPath)) return;
306
+ let content = readFileSync(configPath, "utf-8");
307
+ if (content.includes("session_start")) {
308
+ console.log(` Hook already registered in ${configPath}`);
309
+ return;
310
+ }
311
+ content += `
312
+ [hooks.session_start]
313
+ command = "${hookPath}"
314
+ `;
315
+ writeFileSync(configPath, content);
316
+ console.log(` Hook registered in ${configPath}`);
317
+ }
318
+ function registerHookJSON(agent, hookPath) {
319
+ const configPath = agent.format === "jsonc" ? agent.global : agent.local || agent.global;
320
+ if (!configPath) return;
321
+ const configDir = dirname(configPath);
322
+ if (!existsSync(configDir)) mkdirSync(configDir, { recursive: true });
323
+ let config = {};
324
+ if (existsSync(configPath)) {
325
+ try {
326
+ if (agent.format === "jsonc") {
327
+ const raw = readFileSync(configPath, "utf-8");
328
+ const stripped = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
329
+ config = JSON.parse(stripped);
330
+ } else {
331
+ config = JSON.parse(readFileSync(configPath, "utf-8"));
332
+ }
333
+ } catch {
334
+ config = {};
335
+ }
336
+ }
337
+ if (agent.name === "claude") {
338
+ if (!config.hooks) config.hooks = {};
339
+ if (!config.hooks.SessionStart) config.hooks.SessionStart = [];
340
+ if (!config.hooks.SessionStart.some((h) => h.command === hookPath)) {
341
+ config.hooks.SessionStart.push({ command: hookPath });
342
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
343
+ console.log(` Hook registered in ${configPath}`);
344
+ }
345
+ return;
346
+ }
347
+ if (!config.session_start_hooks) config.session_start_hooks = [];
348
+ if (!config.session_start_hooks.includes(hookPath)) {
349
+ config.session_start_hooks.push(hookPath);
350
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
351
+ console.log(` Hook registered in ${configPath}`);
352
+ }
353
+ }
354
+ function installForAgent(agent, scope) {
355
+ console.log(`${agent.name}:`);
356
+ installMCPConfig(agent, scope);
357
+ installInstructions(agent);
358
+ installHooks(agent);
359
+ }
360
+ function init(scope = "local") {
361
+ console.log("\n\u{1F9E0} toon-memory init\n");
362
+ installMemoryDir();
363
+ const agents2 = detectAgents();
364
+ for (const agent of agents2) {
365
+ installForAgent(agent, scope);
366
+ console.log("");
367
+ }
368
+ ensureGitignore();
369
+ console.log("Done! Restart your agent to use memory tools.\n");
370
+ }
119
371
  function uninstall() {
120
372
  console.log("\n\u{1F9E0} toon-memory uninstaller\n");
121
373
  const agents2 = detectAgents();
122
374
  for (const agent of agents2) {
123
- const configs = [agent.global, agent.local].filter(Boolean);
124
- for (const configPath of configs) {
125
- if (!existsSync(configPath)) continue;
375
+ if (agent.format === "json" || agent.format === "jsonc") {
376
+ const configs = [agent.global, agent.local].filter(Boolean);
377
+ for (const configPath of configs) {
378
+ if (!existsSync(configPath)) continue;
379
+ try {
380
+ let config;
381
+ if (agent.format === "jsonc") {
382
+ const raw = readFileSync(configPath, "utf-8");
383
+ const stripped = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
384
+ config = JSON.parse(stripped);
385
+ } else {
386
+ config = JSON.parse(readFileSync(configPath, "utf-8"));
387
+ }
388
+ const mcpKey = agent.mcpKey || "mcpServers";
389
+ if (config[mcpKey]?.["toon-memory"]) {
390
+ delete config[mcpKey]["toon-memory"];
391
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
392
+ console.log(` Removed MCP from ${agent.name} (${configPath})`);
393
+ }
394
+ } catch {
395
+ }
396
+ }
397
+ }
398
+ if (agent.format === "toml" && agent.local && existsSync(agent.local)) {
399
+ try {
400
+ let content = readFileSync(agent.local, "utf-8");
401
+ content = content.replace(/\[mcpServers\.toon-memory\][\s\S]*?(?=\n\[|$)/, "").trim() + "\n";
402
+ writeFileSync(agent.local, content);
403
+ console.log(` Removed MCP from ${agent.name} (${agent.local})`);
404
+ } catch {
405
+ }
406
+ }
407
+ }
408
+ for (const agent of agents2) {
409
+ if (agent.instructionFile && existsSync(agent.instructionFile)) {
126
410
  try {
127
- const config = JSON.parse(readFileSync(configPath, "utf-8"));
128
- const mcpKey = agent.mcpKey || "mcpServers";
129
- if (config[mcpKey]?.["toon-memory"]) {
130
- delete config[mcpKey]["toon-memory"];
131
- writeFileSync(configPath, JSON.stringify(config, null, 2));
132
- console.log(` \u2705 Removed from ${agent.name} (${configPath})`);
411
+ const content = readFileSync(agent.instructionFile, "utf-8");
412
+ if (content.includes(INSTRUCTION_CONTENT.trim()) && content.length < INSTRUCTION_CONTENT.length + 100) {
413
+ unlinkSync(agent.instructionFile);
414
+ console.log(` Removed ${agent.instructionFile}`);
133
415
  }
134
416
  } catch {
135
417
  }
136
418
  }
137
419
  }
420
+ const hookDir = join(projectRoot, ".toon-memory", "hooks");
421
+ if (existsSync(hookDir)) {
422
+ rmSync(hookDir, { recursive: true, force: true });
423
+ console.log(" Removed .toon-memory/hooks/");
424
+ }
138
425
  const toolsFile = join(projectRoot, ".opencode", "tools", "memory.ts");
139
426
  if (existsSync(toolsFile)) {
140
427
  unlinkSync(toolsFile);
141
- console.log(" \u2705 Removed .opencode/tools/memory.ts");
428
+ console.log(" Removed .opencode/tools/memory.ts");
142
429
  }
143
430
  console.log("\n\u2705 toon-memory uninstalled from all agents\n");
144
431
  }
145
- function init(scope = "local") {
146
- console.log("\n\u{1F9E0} toon-memory init\n");
147
- const agents2 = detectAgents();
148
- for (const agent of agents2) {
149
- console.log(`${agent.name}:`);
150
- if (agent.name === "opencode") {
151
- installOpenCodeTools();
152
- }
153
- installMCPConfig(agent, scope);
154
- console.log("");
155
- }
156
- ensureGitignore();
157
- console.log("Done! Restart your agent to use memory tools.\n");
158
- }
159
432
  function status() {
160
433
  console.log("\n\u{1F9E0} toon-memory status\n");
161
434
  try {
@@ -164,7 +437,7 @@ function status() {
164
437
  } catch {
165
438
  console.log("Version: unknown");
166
439
  }
167
- const memoryFile = join(projectRoot, ".opencode", "memory", "data.toon");
440
+ const memoryFile = join(MEMORY_DIR, "data.toon");
168
441
  if (existsSync(memoryFile)) {
169
442
  const data = readFileSync(memoryFile, "utf-8");
170
443
  const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|"));
@@ -175,22 +448,41 @@ function status() {
175
448
  const agents2 = detectAgents();
176
449
  console.log("\nAgent configs:");
177
450
  for (const agent of agents2) {
178
- const configs = [agent.global, agent.local].filter(Boolean);
179
- let found = false;
180
- for (const configPath of configs) {
181
- if (!existsSync(configPath)) continue;
182
- try {
183
- const config = JSON.parse(readFileSync(configPath, "utf-8"));
184
- const mcpKey = agent.mcpKey || "mcpServers";
185
- if (config[mcpKey]?.["toon-memory"]) {
186
- console.log(` \u2705 ${agent.name} (${configPath})`);
187
- found = true;
451
+ let configured = false;
452
+ if (agent.format === "json" || agent.format === "jsonc") {
453
+ const configs = [agent.global, agent.local].filter(Boolean);
454
+ for (const configPath of configs) {
455
+ if (!existsSync(configPath)) continue;
456
+ try {
457
+ let config;
458
+ if (agent.format === "jsonc") {
459
+ const raw = readFileSync(configPath, "utf-8");
460
+ const stripped = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
461
+ config = JSON.parse(stripped);
462
+ } else {
463
+ config = JSON.parse(readFileSync(configPath, "utf-8"));
464
+ }
465
+ if (config[agent.mcpKey]?.["toon-memory"]) {
466
+ configured = true;
467
+ }
468
+ } catch {
188
469
  }
189
- } catch {
190
470
  }
191
471
  }
192
- if (!found) {
193
- console.log(` \u274C ${agent.name} (not configured)`);
472
+ if (agent.format === "toml" && agent.local && existsSync(agent.local)) {
473
+ const content = readFileSync(agent.local, "utf-8");
474
+ if (content.includes("toon-memory")) configured = true;
475
+ }
476
+ const hasInstructions = agent.instructionFile ? existsSync(agent.instructionFile) : false;
477
+ const hookPath = join(projectRoot, ".toon-memory", "hooks", `session-start-${agent.name}.sh`);
478
+ const hasHooks = existsSync(hookPath);
479
+ if (agent.format === "none") {
480
+ console.log(` ${hasInstructions ? "\u2705" : "\u274C"} ${agent.name} (instructions only)`);
481
+ } else {
482
+ const mcpStatus = configured ? "\u2705" : "\u274C";
483
+ const instrStatus = agent.needsInstructions ? hasInstructions ? " \u{1F4DD}" : "" : "";
484
+ const hookStatus = agent.needsHooks ? hasHooks ? " \u{1FA9D}" : "" : "";
485
+ console.log(` ${mcpStatus} ${agent.name}${instrStatus}${hookStatus}`);
194
486
  }
195
487
  }
196
488
  console.log("");
@@ -212,7 +504,7 @@ function upgrade() {
212
504
  }
213
505
  function stats() {
214
506
  console.log("\n\u{1F9E0} toon-memory stats\n");
215
- const memoryFile = join(projectRoot, ".opencode", "memory", "data.toon");
507
+ const memoryFile = join(MEMORY_DIR, "data.toon");
216
508
  if (!existsSync(memoryFile)) {
217
509
  console.log("Memory not initialized. Run 'npx toon-memory init' first.\n");
218
510
  return;
@@ -243,7 +535,7 @@ function stats() {
243
535
  }
244
536
  function exportMemory() {
245
537
  console.log("\n\u{1F9E0} toon-memory export\n");
246
- const memoryFile = join(projectRoot, ".opencode", "memory", "data.toon");
538
+ const memoryFile = join(MEMORY_DIR, "data.toon");
247
539
  if (!existsSync(memoryFile)) {
248
540
  console.log("Memory not initialized. Run 'npx toon-memory init' first.\n");
249
541
  return;
@@ -298,9 +590,8 @@ function importMemory() {
298
590
  console.log("Invalid format: missing 'entries' array\n");
299
591
  return;
300
592
  }
301
- const memoryDir = join(projectRoot, ".opencode", "memory");
302
- const memoryFile = join(memoryDir, "data.toon");
303
- if (!existsSync(memoryDir)) mkdirSync(memoryDir, { recursive: true });
593
+ const memoryFile = join(MEMORY_DIR, "data.toon");
594
+ if (!existsSync(MEMORY_DIR)) mkdirSync(MEMORY_DIR, { recursive: true });
304
595
  let existingKeys = [];
305
596
  if (existsSync(memoryFile)) {
306
597
  const existing = readFileSync(memoryFile, "utf-8");
@@ -317,9 +608,10 @@ function importMemory() {
317
608
  }).join("\n");
318
609
  if (existsSync(memoryFile)) {
319
610
  const existing = readFileSync(memoryFile, "utf-8");
611
+ const existingCount = existing.split("\n").filter((l) => l.startsWith(" ") && l.includes("|")).length;
320
612
  const updated = existing.replace(
321
613
  /entries\[\d+\|]/,
322
- `entries[${newEntries.length}|]`
614
+ `entries[${existingCount + newEntries.length}|]`
323
615
  ) + "\n" + newLines;
324
616
  writeFileSync(memoryFile, updated);
325
617
  } else {
@@ -346,7 +638,7 @@ function parseWatchOptions(args2) {
346
638
  opts.compress = true;
347
639
  } else if (arg === "--log" || arg === "-l") {
348
640
  opts.logFile = true;
349
- opts.logPath = args2[++i] || join(projectRoot, ".opencode", "memory", "watch.log");
641
+ opts.logPath = args2[++i] || join(MEMORY_DIR, "watch.log");
350
642
  } else if (arg === "--max-backups" || arg === "-m") {
351
643
  opts.maxBackups = parseInt(args2[++i]) || 10;
352
644
  } else if (!arg.startsWith("-")) {
@@ -384,8 +676,8 @@ function decompressData(data) {
384
676
  }
385
677
  function watch() {
386
678
  console.log("\n\u{1F9E0} toon-memory watch\n");
387
- const memoryFile = join(projectRoot, ".opencode", "memory", "data.toon");
388
- const backupDir = join(projectRoot, ".opencode", "memory", "backups");
679
+ const memoryFile = join(MEMORY_DIR, "data.toon");
680
+ const backupDir = join(MEMORY_DIR, "backups");
389
681
  if (!existsSync(memoryFile)) {
390
682
  console.log("Memory not initialized. Run 'npx toon-memory init' first.\n");
391
683
  return;
@@ -495,25 +787,46 @@ if (args[0] === "watch") {
495
787
  }
496
788
  const agents = detectAgents();
497
789
  console.log("\n\u{1F9E0} toon-memory installer\n");
498
- console.log("Supported agents:");
499
- agents.forEach((a, i) => console.log(` ${i + 1}. ${a.name}`));
790
+ console.log("Available agents:");
791
+ agents.forEach((a, i) => {
792
+ const hasConfig = a.format !== "none" && (a.local && existsSync(a.local) || a.global && existsSync(a.global));
793
+ const indicator = hasConfig ? "\u2713" : "\xB7";
794
+ console.log(` ${indicator} ${i + 1}. ${a.name}`);
795
+ });
500
796
  console.log("");
501
797
  const rl = createInterface({ input: process.stdin, output: process.stdout });
502
- rl.question("Install (1) Local or (2) Global? [1/2]: ", (answer) => {
503
- const scope = answer === "2" ? "global" : "local";
798
+ rl.question("Select agents (numbers separated by commas, 'all', or Enter for all): ", (answer) => {
799
+ let selected;
800
+ const trimmed = answer.trim().toLowerCase();
801
+ if (trimmed === "all" || trimmed === "") {
802
+ selected = agents;
803
+ } else if (trimmed === "none") {
804
+ selected = [];
805
+ } else {
806
+ const indices = trimmed.split(",").map((s) => parseInt(s.trim(), 10) - 1).filter((i) => i >= 0 && i < agents.length);
807
+ selected = indices.map((i) => agents[i]);
808
+ }
809
+ if (selected.length === 0) {
810
+ console.log("\nNo agents selected. Nothing installed.\n");
811
+ rl.close();
812
+ return;
813
+ }
504
814
  console.log(`
815
+ Selected: ${selected.map((a) => a.name).join(", ")}
816
+ `);
817
+ rl.question("Installation scope \u2014 (1) Local or (2) Global? [1/2]: ", (scopeAnswer) => {
818
+ const scope = scopeAnswer === "2" ? "global" : "local";
819
+ console.log(`
505
820
  Installing ${scope}ly...
506
821
  `);
507
- for (const agent of agents) {
508
- console.log(`${agent.name}:`);
509
- if (agent.name === "opencode") {
510
- installOpenCodeTools();
822
+ installMemoryDir();
823
+ for (const agent of selected) {
824
+ installForAgent(agent, scope);
825
+ console.log("");
511
826
  }
512
- installMCPConfig(agent, scope);
513
- console.log("");
514
- }
515
- ensureGitignore();
516
- console.log("Done! Restart your agent to use memory tools.");
517
- console.log("Run 'npx toon-memory uninstall' to remove.\n");
518
- rl.close();
827
+ ensureGitignore();
828
+ console.log("Done! Restart your agent to use memory tools.");
829
+ console.log("Run 'npx toon-memory uninstall' to remove.\n");
830
+ rl.close();
831
+ });
519
832
  });