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/README.md +270 -34
- package/dist/cli/setup.js +409 -96
- package/mcp/server.js +268 -19
- package/package.json +9 -1
- package/skills/toon-memory.md +25 -14
- package/src/cli/setup.ts +650 -335
- package/src/mcp/server.ts +327 -11
- package/uninstall.sh +33 -5
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:
|
|
17
|
-
local:
|
|
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:
|
|
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:
|
|
31
|
-
local:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
61
|
-
const
|
|
62
|
-
|
|
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 .
|
|
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 = ".
|
|
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 .
|
|
185
|
+
console.log(" Added .toon-memory/memory/ to .gitignore");
|
|
84
186
|
}
|
|
85
187
|
}
|
|
86
|
-
function
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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("
|
|
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(
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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 (
|
|
193
|
-
|
|
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(
|
|
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(
|
|
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
|
|
302
|
-
|
|
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(
|
|
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(
|
|
388
|
-
const backupDir = join(
|
|
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("
|
|
499
|
-
agents.forEach((a, i) =>
|
|
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("
|
|
503
|
-
|
|
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
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
822
|
+
installMemoryDir();
|
|
823
|
+
for (const agent of selected) {
|
|
824
|
+
installForAgent(agent, scope);
|
|
825
|
+
console.log("");
|
|
511
826
|
}
|
|
512
|
-
|
|
513
|
-
console.log("");
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
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
|
});
|