toon-memory 1.1.2 โ 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 +135 -1
- package/package.json +3 -2
- package/src/bin/toon-memory.ts +15 -0
- package/src/cli/setup.ts +171 -1
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,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";
|
|
@@ -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,7 +17,8 @@
|
|
|
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",
|
|
@@ -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"
|
|
@@ -271,6 +271,161 @@ function upgrade(): void {
|
|
|
271
271
|
}
|
|
272
272
|
}
|
|
273
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
|
+
|
|
274
429
|
// Main
|
|
275
430
|
const args = process.argv.slice(2)
|
|
276
431
|
|
|
@@ -294,6 +449,21 @@ if (args[0] === "upgrade") {
|
|
|
294
449
|
process.exit(0)
|
|
295
450
|
}
|
|
296
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
|
+
|
|
297
467
|
const agents = detectAgents()
|
|
298
468
|
console.log("\n๐ง toon-memory installer\n")
|
|
299
469
|
|