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 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
  [![npm version](https://img.shields.io/npm/v/toon-memory.svg)](https://www.npmjs.com/package/toon-memory)
12
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![CI](https://github.com/LuiggiVal08/toon-memory/actions/workflows/ci.yml/badge.svg)](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
- ## Get Started
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 (one command)
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 | Status |
64
- |-------|-----------------|--------|
65
- | **OpenCode** | `.opencode/opencode.json` | โœ… Full support |
66
- | **VS Code / Copilot** | `.vscode/mcp.json` | โœ… Full support |
67
- | **Claude Code** | `.claude/settings.json` | โœ… Full support |
68
- | **Cursor** | `.cursor/mcp.json` | โœ… Full support |
69
- | **Windsurf** | `.windsurfrules` | โœ… Full support |
70
- | **Cline** | `.cline/mcp.json` | โœ… Full support |
71
- | **Continue** | `.continue/config.json` | โœ… Full support |
72
- | **Aider** | `.aider.conf.yml` | โš ๏ธ Manual config |
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
@@ -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 __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))
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.1.2",
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