toon-memory 1.1.1 โ 1.2.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 +36 -30
- package/bin/toon-memory.js +9 -14
- package/dist/cli/setup.js +136 -2
- package/package.json +10 -4
- package/src/bin/toon-memory.ts +15 -0
- package/src/cli/setup.ts +174 -2
package/README.md
CHANGED
|
@@ -2,14 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Persistent memory for AI coding agents โ remember decisions, patterns, and bugs between sessions.
|
|
4
4
|
|
|
5
|
-
๐ **1.0 Released!**
|
|
6
|
-
|
|
7
|
-
Supercharge **OpenCode, VS Code Copilot, Claude Code, Cursor, Windsurf, Cline, Continue** and more with Persistent Memory
|
|
8
|
-
|
|
9
|
-
Saves context in **TOON format** (40% fewer tokens than JSON) ยท 5 MCP tools ยท Works everywhere
|
|
10
|
-
|
|
11
5
|
[](https://www.npmjs.com/package/toon-memory)
|
|
12
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://github.com/LuiggiVal08/toon-memory/actions/workflows/ci.yml)
|
|
13
8
|
|
|
14
9
|
---
|
|
15
10
|
|
|
@@ -25,9 +20,19 @@ AI agents forget everything between sessions. toon-memory fixes this by providin
|
|
|
25
20
|
|
|
26
21
|
---
|
|
27
22
|
|
|
28
|
-
##
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
- **5 MCP tools** โ `memory_remember`, `memory_recall`, `memory_forget`, `memory_stats`, `memory_summary`
|
|
26
|
+
- **7 agents supported** โ OpenCode, VS Code/Copilot, Claude Code, Cursor, Windsurf, Cline, Continue
|
|
27
|
+
- **TOON format** โ 40% fewer tokens than JSON, better LLM comprehension
|
|
28
|
+
- **Per-project memory** โ each project gets its own memory file
|
|
29
|
+
- **Zero config** โ just install and use
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
29
34
|
|
|
30
|
-
### 1. Install
|
|
35
|
+
### 1. Install
|
|
31
36
|
|
|
32
37
|
```bash
|
|
33
38
|
# macOS / Linux
|
|
@@ -60,16 +65,16 @@ memory_remember # Save important decisions
|
|
|
60
65
|
|
|
61
66
|
## Supported Agents
|
|
62
67
|
|
|
63
|
-
| Agent | Config Location |
|
|
64
|
-
|
|
65
|
-
| **OpenCode** | `.opencode/opencode.json` | โ
|
|
66
|
-
| **VS Code / Copilot** | `.vscode/mcp.json` | โ
|
|
67
|
-
| **Claude Code** | `.claude/settings.json` | โ
|
|
68
|
-
| **Cursor** | `.cursor/mcp.json` | โ
|
|
69
|
-
| **Windsurf** | `.windsurfrules` | โ
|
|
70
|
-
| **Cline** | `.cline/mcp.json` | โ
|
|
71
|
-
| **Continue** | `.continue/config.json` | โ
|
|
72
|
-
| **Aider** | `.aider.conf.yml` | โ ๏ธ Manual
|
|
68
|
+
| Agent | Config Location | Auto-Setup |
|
|
69
|
+
|-------|-----------------|------------|
|
|
70
|
+
| **OpenCode** | `.opencode/opencode.json` | โ
|
|
|
71
|
+
| **VS Code / Copilot** | `.vscode/mcp.json` | โ
|
|
|
72
|
+
| **Claude Code** | `.claude/settings.json` | โ
|
|
|
73
|
+
| **Cursor** | `.cursor/mcp.json` | โ
|
|
|
74
|
+
| **Windsurf** | `.windsurfrules` | โ
|
|
|
75
|
+
| **Cline** | `.cline/mcp.json` | โ
|
|
|
76
|
+
| **Continue** | `.continue/config.json` | โ
|
|
|
77
|
+
| **Aider** | `.aider.conf.yml` | โ ๏ธ Manual |
|
|
73
78
|
|
|
74
79
|
---
|
|
75
80
|
|
|
@@ -119,18 +124,6 @@ npx toon-memory uninstall # Remove from all agents
|
|
|
119
124
|
|
|
120
125
|
---
|
|
121
126
|
|
|
122
|
-
## Uninstall
|
|
123
|
-
|
|
124
|
-
```bash
|
|
125
|
-
# Remove from all agents and uninstall CLI
|
|
126
|
-
npx toon-memory uninstall
|
|
127
|
-
|
|
128
|
-
# Or with the global install
|
|
129
|
-
toon-memory uninstall
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
---
|
|
133
|
-
|
|
134
127
|
## Configuration
|
|
135
128
|
|
|
136
129
|
### Global (all projects)
|
|
@@ -205,10 +198,23 @@ git clone https://github.com/LuiggiVal08/toon-memory.git
|
|
|
205
198
|
cd toon-memory
|
|
206
199
|
npm install
|
|
207
200
|
npm run build
|
|
201
|
+
npm test
|
|
208
202
|
```
|
|
209
203
|
|
|
210
204
|
---
|
|
211
205
|
|
|
206
|
+
## Contributing
|
|
207
|
+
|
|
208
|
+
Contributions are welcome! Please read our [Code of Conduct](CODE_OF_CONDUCT.md) first.
|
|
209
|
+
|
|
210
|
+
1. Fork the repository
|
|
211
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
212
|
+
3. Commit your changes (`git commit -m 'feat: add amazing feature'`)
|
|
213
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
214
|
+
5. Open a Pull Request
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
212
218
|
## License
|
|
213
219
|
|
|
214
220
|
MIT
|
package/bin/toon-memory.js
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { dirname, join } from "path"
|
|
3
|
-
import { fileURLToPath } from "url"
|
|
4
|
-
import { spawn } from "child_process"
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
: join(__dirname, "..", "dist", "cli", "setup.js")
|
|
12
|
-
|
|
13
|
-
const extraArgs = args[0] === "mcp" ? [] : args
|
|
14
|
-
const child = spawn("node", [target, ...extraArgs], { stdio: "inherit" })
|
|
15
|
-
child.on("exit", (code) => process.exit(code ?? 0))
|
|
2
|
+
import { dirname, join } from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import { spawn } from "child_process";
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const target = args[0] === "mcp" ? join(__dirname, "..", "mcp", "server.js") : join(__dirname, "..", "dist", "cli", "setup.js");
|
|
8
|
+
const extraArgs = args[0] === "mcp" ? [] : args;
|
|
9
|
+
const child = spawn("node", [target, ...extraArgs], { stdio: "inherit" });
|
|
10
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
package/dist/cli/setup.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, cpSync, unlinkSync } from "fs";
|
|
2
|
-
import { dirname, join } from "path";
|
|
2
|
+
import { basename, dirname, join } from "path";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
4
|
import { execSync } from "child_process";
|
|
5
5
|
import { createInterface } from "readline";
|
|
6
6
|
import { createRequire } from "module";
|
|
7
7
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
8
|
const projectRoot = process.cwd();
|
|
9
|
-
const sourceDir = join(__dirname, "..", "src");
|
|
9
|
+
const sourceDir = join(__dirname, "..", "..", "src");
|
|
10
10
|
const HOME = process.env.HOME || process.env.USERPROFILE || "~";
|
|
11
11
|
try {
|
|
12
12
|
createRequire(import.meta.url).resolve("@toon-format/toon");
|
|
@@ -195,6 +195,128 @@ function upgrade() {
|
|
|
195
195
|
console.error("Upgrade failed:", error.message);
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
|
+
function stats() {
|
|
199
|
+
console.log("\n\u{1F9E0} toon-memory stats\n");
|
|
200
|
+
const memoryFile = join(projectRoot, ".opencode", "memory", "data.toon");
|
|
201
|
+
if (!existsSync(memoryFile)) {
|
|
202
|
+
console.log("Memory not initialized. Run 'npx toon-memory init' first.\n");
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const data = readFileSync(memoryFile, "utf-8");
|
|
206
|
+
const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|"));
|
|
207
|
+
const categories = {};
|
|
208
|
+
let latestDate = "";
|
|
209
|
+
for (const line of lines) {
|
|
210
|
+
const parts = line.trim().split("|");
|
|
211
|
+
if (parts.length >= 7) {
|
|
212
|
+
const category = parts[1];
|
|
213
|
+
const date = parts[6];
|
|
214
|
+
categories[category] = (categories[category] || 0) + 1;
|
|
215
|
+
if (date > latestDate) latestDate = date;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
console.log("\u{1F4CA} Memory Stats");
|
|
219
|
+
console.log("\u2501".repeat(20));
|
|
220
|
+
console.log(`Total entries: ${lines.length}`);
|
|
221
|
+
for (const [cat, count] of Object.entries(categories)) {
|
|
222
|
+
console.log(`\u251C\u2500\u2500 ${cat}: ${count}`);
|
|
223
|
+
}
|
|
224
|
+
console.log(`Last updated: ${latestDate || "never"}`);
|
|
225
|
+
const fileSize = Buffer.byteLength(data, "utf-8");
|
|
226
|
+
console.log(`File size: ${(fileSize / 1024).toFixed(1)} KB`);
|
|
227
|
+
console.log("");
|
|
228
|
+
}
|
|
229
|
+
function exportMemory() {
|
|
230
|
+
console.log("\n\u{1F9E0} toon-memory export\n");
|
|
231
|
+
const memoryFile = join(projectRoot, ".opencode", "memory", "data.toon");
|
|
232
|
+
if (!existsSync(memoryFile)) {
|
|
233
|
+
console.log("Memory not initialized. Run 'npx toon-memory init' first.\n");
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const data = readFileSync(memoryFile, "utf-8");
|
|
237
|
+
const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|"));
|
|
238
|
+
const entries = lines.map((line) => {
|
|
239
|
+
const parts = line.trim().split("|");
|
|
240
|
+
return {
|
|
241
|
+
id: parts[0],
|
|
242
|
+
category: parts[1],
|
|
243
|
+
key: parts[2],
|
|
244
|
+
content: parts[3],
|
|
245
|
+
file: parts[4],
|
|
246
|
+
tags: parts[5] ? parts[5].split(";") : [],
|
|
247
|
+
date: parts[6]
|
|
248
|
+
};
|
|
249
|
+
});
|
|
250
|
+
const exportData = {
|
|
251
|
+
project: basename(projectRoot),
|
|
252
|
+
exported_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
253
|
+
entries,
|
|
254
|
+
summaries: {}
|
|
255
|
+
};
|
|
256
|
+
const outputPath = join(projectRoot, "toon-memory-export.json");
|
|
257
|
+
writeFileSync(outputPath, JSON.stringify(exportData, null, 2));
|
|
258
|
+
console.log(`Exported ${entries.length} entries to:`);
|
|
259
|
+
console.log(` ${outputPath}
|
|
260
|
+
`);
|
|
261
|
+
}
|
|
262
|
+
function importMemory() {
|
|
263
|
+
console.log("\n\u{1F9E0} toon-memory import\n");
|
|
264
|
+
const importFile = process.argv[3];
|
|
265
|
+
if (!importFile) {
|
|
266
|
+
console.log("Usage: npx toon-memory import <file.json>\n");
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const importPath = importFile.startsWith("/") ? importFile : join(projectRoot, importFile);
|
|
270
|
+
if (!existsSync(importPath)) {
|
|
271
|
+
console.log(`File not found: ${importPath}
|
|
272
|
+
`);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
let importData;
|
|
276
|
+
try {
|
|
277
|
+
importData = JSON.parse(readFileSync(importPath, "utf-8"));
|
|
278
|
+
} catch {
|
|
279
|
+
console.log("Invalid JSON file\n");
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (!importData.entries || !Array.isArray(importData.entries)) {
|
|
283
|
+
console.log("Invalid format: missing 'entries' array\n");
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const memoryDir = join(projectRoot, ".opencode", "memory");
|
|
287
|
+
const memoryFile = join(memoryDir, "data.toon");
|
|
288
|
+
if (!existsSync(memoryDir)) mkdirSync(memoryDir, { recursive: true });
|
|
289
|
+
let existingKeys = [];
|
|
290
|
+
if (existsSync(memoryFile)) {
|
|
291
|
+
const existing = readFileSync(memoryFile, "utf-8");
|
|
292
|
+
existingKeys = existing.split("\n").filter((l) => l.startsWith(" ") && l.includes("|")).map((l) => l.trim().split("|")[2]);
|
|
293
|
+
}
|
|
294
|
+
const newEntries = importData.entries.filter((e) => !existingKeys.includes(e.key));
|
|
295
|
+
if (newEntries.length === 0) {
|
|
296
|
+
console.log("No new entries to import (all keys already exist)\n");
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const newLines = newEntries.map((e) => {
|
|
300
|
+
const tags = Array.isArray(e.tags) ? e.tags.join(";") : e.tags || "";
|
|
301
|
+
return ` ${e.id}|${e.category}|${e.key}|${e.content}|${e.file}|${tags}|${e.date}`;
|
|
302
|
+
}).join("\n");
|
|
303
|
+
if (existsSync(memoryFile)) {
|
|
304
|
+
const existing = readFileSync(memoryFile, "utf-8");
|
|
305
|
+
const updated = existing.replace(
|
|
306
|
+
/entries\[\d+\|]/,
|
|
307
|
+
`entries[${newEntries.length}|]`
|
|
308
|
+
) + "\n" + newLines;
|
|
309
|
+
writeFileSync(memoryFile, updated);
|
|
310
|
+
} else {
|
|
311
|
+
writeFileSync(memoryFile, `version: 1
|
|
312
|
+
entries[${newEntries.length}|]{id|category|key|content|file|tags|date}:
|
|
313
|
+
${newLines}
|
|
314
|
+
`);
|
|
315
|
+
}
|
|
316
|
+
console.log(`Imported ${newEntries.length} new entries`);
|
|
317
|
+
console.log(`Skipped ${importData.entries.length - newEntries.length} duplicates
|
|
318
|
+
`);
|
|
319
|
+
}
|
|
198
320
|
const args = process.argv.slice(2);
|
|
199
321
|
if (args[0] === "uninstall") {
|
|
200
322
|
uninstall();
|
|
@@ -212,6 +334,18 @@ if (args[0] === "upgrade") {
|
|
|
212
334
|
upgrade();
|
|
213
335
|
process.exit(0);
|
|
214
336
|
}
|
|
337
|
+
if (args[0] === "stats") {
|
|
338
|
+
stats();
|
|
339
|
+
process.exit(0);
|
|
340
|
+
}
|
|
341
|
+
if (args[0] === "export") {
|
|
342
|
+
exportMemory();
|
|
343
|
+
process.exit(0);
|
|
344
|
+
}
|
|
345
|
+
if (args[0] === "import") {
|
|
346
|
+
importMemory();
|
|
347
|
+
process.exit(0);
|
|
348
|
+
}
|
|
215
349
|
const agents = detectAgents();
|
|
216
350
|
console.log("\n\u{1F9E0} toon-memory installer\n");
|
|
217
351
|
console.log("Supported agents:");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toon-memory",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Persistent memory system for AI coding agents using TOON format (40% fewer tokens than JSON)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -17,10 +17,13 @@
|
|
|
17
17
|
"uninstall.sh"
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
|
-
"build": "npm run build:mcp && npm run build:cli",
|
|
20
|
+
"build": "npm run build:bin && npm run build:mcp && npm run build:cli",
|
|
21
|
+
"build:bin": "esbuild src/bin/toon-memory.ts --platform=node --format=esm --outfile=bin/toon-memory.js",
|
|
21
22
|
"build:mcp": "esbuild src/mcp/server.ts --bundle --platform=node --format=esm --outfile=mcp/server.js",
|
|
22
23
|
"build:cli": "esbuild src/cli/setup.ts --platform=node --format=esm --outfile=dist/cli/setup.js && esbuild src/cli/toon-memory.ts --platform=node --format=esm --outfile=dist/cli/toon-memory.js",
|
|
23
|
-
"
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:watch": "vitest",
|
|
26
|
+
"prepublishOnly": "npm run build && npm test"
|
|
24
27
|
},
|
|
25
28
|
"dependencies": {
|
|
26
29
|
"@modelcontextprotocol/server": "^2.0.0-beta.3",
|
|
@@ -28,7 +31,10 @@
|
|
|
28
31
|
"zod": "^4.4.3"
|
|
29
32
|
},
|
|
30
33
|
"devDependencies": {
|
|
31
|
-
"
|
|
34
|
+
"@types/node": "^26.1.1",
|
|
35
|
+
"esbuild": "^0.25.0",
|
|
36
|
+
"typescript": "^7.0.2",
|
|
37
|
+
"vitest": "^4.1.10"
|
|
32
38
|
},
|
|
33
39
|
"keywords": [
|
|
34
40
|
"opencode",
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { dirname, join } from "path"
|
|
3
|
+
import { fileURLToPath } from "url"
|
|
4
|
+
import { spawn } from "child_process"
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
7
|
+
const args = process.argv.slice(2)
|
|
8
|
+
|
|
9
|
+
const target = args[0] === "mcp"
|
|
10
|
+
? join(__dirname, "..", "mcp", "server.js")
|
|
11
|
+
: join(__dirname, "..", "dist", "cli", "setup.js")
|
|
12
|
+
|
|
13
|
+
const extraArgs = args[0] === "mcp" ? [] : args
|
|
14
|
+
const child = spawn("node", [target, ...extraArgs], { stdio: "inherit" })
|
|
15
|
+
child.on("exit", (code) => process.exit(code ?? 0))
|
package/src/cli/setup.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, cpSync, unlinkSync } from "fs"
|
|
2
|
-
import { dirname, join } from "path"
|
|
2
|
+
import { basename, dirname, join } from "path"
|
|
3
3
|
import { fileURLToPath } from "url"
|
|
4
4
|
import { execSync } from "child_process"
|
|
5
5
|
import { createInterface } from "readline"
|
|
@@ -7,7 +7,9 @@ import { createRequire } from "module"
|
|
|
7
7
|
|
|
8
8
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
9
9
|
const projectRoot = process.cwd()
|
|
10
|
-
|
|
10
|
+
// When compiled to dist/cli/, src is at ../../src
|
|
11
|
+
// When running directly from src/cli/, src is at ../src
|
|
12
|
+
const sourceDir = join(__dirname, "..", "..", "src")
|
|
11
13
|
const HOME = process.env.HOME || process.env.USERPROFILE || "~"
|
|
12
14
|
|
|
13
15
|
interface Agent {
|
|
@@ -269,6 +271,161 @@ function upgrade(): void {
|
|
|
269
271
|
}
|
|
270
272
|
}
|
|
271
273
|
|
|
274
|
+
// Show memory statistics
|
|
275
|
+
function stats(): void {
|
|
276
|
+
console.log("\n๐ง toon-memory stats\n")
|
|
277
|
+
|
|
278
|
+
const memoryFile = join(projectRoot, ".opencode", "memory", "data.toon")
|
|
279
|
+
|
|
280
|
+
if (!existsSync(memoryFile)) {
|
|
281
|
+
console.log("Memory not initialized. Run 'npx toon-memory init' first.\n")
|
|
282
|
+
return
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const data = readFileSync(memoryFile, "utf-8")
|
|
286
|
+
const lines = data.split("\n").filter((l: string) => l.startsWith(" ") && l.includes("|"))
|
|
287
|
+
|
|
288
|
+
const categories: Record<string, number> = {}
|
|
289
|
+
let latestDate = ""
|
|
290
|
+
|
|
291
|
+
for (const line of lines) {
|
|
292
|
+
const parts = line.trim().split("|")
|
|
293
|
+
if (parts.length >= 7) {
|
|
294
|
+
const category = parts[1]
|
|
295
|
+
const date = parts[6]
|
|
296
|
+
categories[category] = (categories[category] || 0) + 1
|
|
297
|
+
if (date > latestDate) latestDate = date
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
console.log("๐ Memory Stats")
|
|
302
|
+
console.log("โ".repeat(20))
|
|
303
|
+
console.log(`Total entries: ${lines.length}`)
|
|
304
|
+
|
|
305
|
+
for (const [cat, count] of Object.entries(categories)) {
|
|
306
|
+
console.log(`โโโ ${cat}: ${count}`)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
console.log(`Last updated: ${latestDate || "never"}`)
|
|
310
|
+
|
|
311
|
+
const fileSize = Buffer.byteLength(data, "utf-8")
|
|
312
|
+
console.log(`File size: ${(fileSize / 1024).toFixed(1)} KB`)
|
|
313
|
+
console.log("")
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Export memory to JSON
|
|
317
|
+
function exportMemory(): void {
|
|
318
|
+
console.log("\n๐ง toon-memory export\n")
|
|
319
|
+
|
|
320
|
+
const memoryFile = join(projectRoot, ".opencode", "memory", "data.toon")
|
|
321
|
+
|
|
322
|
+
if (!existsSync(memoryFile)) {
|
|
323
|
+
console.log("Memory not initialized. Run 'npx toon-memory init' first.\n")
|
|
324
|
+
return
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const data = readFileSync(memoryFile, "utf-8")
|
|
328
|
+
const lines = data.split("\n").filter((l: string) => l.startsWith(" ") && l.includes("|"))
|
|
329
|
+
|
|
330
|
+
const entries = lines.map((line: string) => {
|
|
331
|
+
const parts = line.trim().split("|")
|
|
332
|
+
return {
|
|
333
|
+
id: parts[0],
|
|
334
|
+
category: parts[1],
|
|
335
|
+
key: parts[2],
|
|
336
|
+
content: parts[3],
|
|
337
|
+
file: parts[4],
|
|
338
|
+
tags: parts[5] ? parts[5].split(";") : [],
|
|
339
|
+
date: parts[6]
|
|
340
|
+
}
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
const exportData = {
|
|
344
|
+
project: basename(projectRoot),
|
|
345
|
+
exported_at: new Date().toISOString(),
|
|
346
|
+
entries,
|
|
347
|
+
summaries: {}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const outputPath = join(projectRoot, "toon-memory-export.json")
|
|
351
|
+
writeFileSync(outputPath, JSON.stringify(exportData, null, 2))
|
|
352
|
+
|
|
353
|
+
console.log(`Exported ${entries.length} entries to:`)
|
|
354
|
+
console.log(` ${outputPath}\n`)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Import memory from JSON
|
|
358
|
+
function importMemory(): void {
|
|
359
|
+
console.log("\n๐ง toon-memory import\n")
|
|
360
|
+
|
|
361
|
+
const importFile = process.argv[3]
|
|
362
|
+
|
|
363
|
+
if (!importFile) {
|
|
364
|
+
console.log("Usage: npx toon-memory import <file.json>\n")
|
|
365
|
+
return
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Use absolute path if provided, otherwise resolve relative to project root
|
|
369
|
+
const importPath = importFile.startsWith("/") ? importFile : join(projectRoot, importFile)
|
|
370
|
+
|
|
371
|
+
if (!existsSync(importPath)) {
|
|
372
|
+
console.log(`File not found: ${importPath}\n`)
|
|
373
|
+
return
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
let importData: any
|
|
377
|
+
try {
|
|
378
|
+
importData = JSON.parse(readFileSync(importPath, "utf-8"))
|
|
379
|
+
} catch {
|
|
380
|
+
console.log("Invalid JSON file\n")
|
|
381
|
+
return
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (!importData.entries || !Array.isArray(importData.entries)) {
|
|
385
|
+
console.log("Invalid format: missing 'entries' array\n")
|
|
386
|
+
return
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const memoryDir = join(projectRoot, ".opencode", "memory")
|
|
390
|
+
const memoryFile = join(memoryDir, "data.toon")
|
|
391
|
+
|
|
392
|
+
if (!existsSync(memoryDir)) mkdirSync(memoryDir, { recursive: true })
|
|
393
|
+
|
|
394
|
+
let existingKeys: string[] = []
|
|
395
|
+
if (existsSync(memoryFile)) {
|
|
396
|
+
const existing = readFileSync(memoryFile, "utf-8")
|
|
397
|
+
existingKeys = existing.split("\n")
|
|
398
|
+
.filter((l: string) => l.startsWith(" ") && l.includes("|"))
|
|
399
|
+
.map((l: string) => l.trim().split("|")[2])
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const newEntries = importData.entries.filter((e: any) => !existingKeys.includes(e.key))
|
|
403
|
+
|
|
404
|
+
if (newEntries.length === 0) {
|
|
405
|
+
console.log("No new entries to import (all keys already exist)\n")
|
|
406
|
+
return
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const newLines = newEntries.map((e: any) => {
|
|
410
|
+
const tags = Array.isArray(e.tags) ? e.tags.join(";") : (e.tags || "")
|
|
411
|
+
return ` ${e.id}|${e.category}|${e.key}|${e.content}|${e.file}|${tags}|${e.date}`
|
|
412
|
+
}).join("\n")
|
|
413
|
+
|
|
414
|
+
if (existsSync(memoryFile)) {
|
|
415
|
+
const existing = readFileSync(memoryFile, "utf-8")
|
|
416
|
+
const updated = existing.replace(
|
|
417
|
+
/entries\[\d+\|]/,
|
|
418
|
+
`entries[${newEntries.length}|]`
|
|
419
|
+
) + "\n" + newLines
|
|
420
|
+
writeFileSync(memoryFile, updated)
|
|
421
|
+
} else {
|
|
422
|
+
writeFileSync(memoryFile, `version: 1\nentries[${newEntries.length}|]{id|category|key|content|file|tags|date}:\n${newLines}\n`)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
console.log(`Imported ${newEntries.length} new entries`)
|
|
426
|
+
console.log(`Skipped ${importData.entries.length - newEntries.length} duplicates\n`)
|
|
427
|
+
}
|
|
428
|
+
|
|
272
429
|
// Main
|
|
273
430
|
const args = process.argv.slice(2)
|
|
274
431
|
|
|
@@ -292,6 +449,21 @@ if (args[0] === "upgrade") {
|
|
|
292
449
|
process.exit(0)
|
|
293
450
|
}
|
|
294
451
|
|
|
452
|
+
if (args[0] === "stats") {
|
|
453
|
+
stats()
|
|
454
|
+
process.exit(0)
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (args[0] === "export") {
|
|
458
|
+
exportMemory()
|
|
459
|
+
process.exit(0)
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (args[0] === "import") {
|
|
463
|
+
importMemory()
|
|
464
|
+
process.exit(0)
|
|
465
|
+
}
|
|
466
|
+
|
|
295
467
|
const agents = detectAgents()
|
|
296
468
|
console.log("\n๐ง toon-memory installer\n")
|
|
297
469
|
|