toon-memory 1.1.2 β†’ 1.3.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,21 @@ 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
+ - **Auto gitignore** β€” automatically adds `.opencode/memory/` to `.gitignore`
31
+ - **Date filtering** β€” search memory by date range
32
+
33
+ ---
34
+
35
+ ## Quick Start
29
36
 
30
- ### 1. Install (one command)
37
+ ### 1. Install
31
38
 
32
39
  ```bash
33
40
  # macOS / Linux
@@ -60,16 +67,16 @@ memory_remember # Save important decisions
60
67
 
61
68
  ## Supported Agents
62
69
 
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 |
70
+ | Agent | Config Location | Auto-Setup |
71
+ |-------|-----------------|------------|
72
+ | **OpenCode** | `.opencode/opencode.json` | βœ… |
73
+ | **VS Code / Copilot** | `.vscode/mcp.json` | βœ… |
74
+ | **Claude Code** | `.claude/settings.json` | βœ… |
75
+ | **Cursor** | `.cursor/mcp.json` | βœ… |
76
+ | **Windsurf** | `.windsurfrules` | βœ… |
77
+ | **Cline** | `.cline/mcp.json` | βœ… |
78
+ | **Continue** | `.continue/config.json` | βœ… |
79
+ | **Aider** | `.aider.conf.yml` | ⚠️ Manual |
73
80
 
74
81
  ---
75
82
 
@@ -83,6 +90,19 @@ memory_remember # Save important decisions
83
90
  | `memory_stats` | View memory state |
84
91
  | `memory_summary` | Save/retrieve file summaries |
85
92
 
93
+ ### Date Filtering
94
+
95
+ Search memory by date range:
96
+
97
+ ```typescript
98
+ // Search with date filter
99
+ memory_recall({
100
+ query: "redis",
101
+ from_date: "2026-07-01",
102
+ to_date: "2026-07-31"
103
+ })
104
+ ```
105
+
86
106
  ---
87
107
 
88
108
  ## How It Works
@@ -109,24 +129,55 @@ summaries:
109
129
  ## CLI Commands
110
130
 
111
131
  ```bash
112
- npx toon-memory # Interactive installer
113
- npx toon-memory init # Quick setup (no prompts)
114
- npx toon-memory mcp # Run MCP server directly
115
- npx toon-memory status # Check installation status
116
- npx toon-memory upgrade # Update to latest version
117
- npx toon-memory uninstall # Remove from all agents
132
+ npx toon-memory # Interactive installer
133
+ npx toon-memory init # Quick setup (no prompts)
134
+ npx toon-memory mcp # Run MCP server directly
135
+ npx toon-memory status # Check installation status
136
+ npx toon-memory stats # View memory statistics
137
+ npx toon-memory export # Export memory to JSON
138
+ npx toon-memory import <file> # Import memory from JSON
139
+ npx toon-memory upgrade # Update to latest version
140
+ npx toon-memory uninstall # Remove from all agents
118
141
  ```
119
142
 
120
- ---
143
+ ### Stats
121
144
 
122
- ## Uninstall
145
+ ```bash
146
+ $ npx toon-memory stats
147
+
148
+ 🧠 toon-memory stats
149
+
150
+ πŸ“Š Memory Stats
151
+ ━━━━━━━━━━━━━━━━━━
152
+ Total entries: 45
153
+ β”œβ”€β”€ decision: 12
154
+ β”œβ”€β”€ pattern: 18
155
+ β”œβ”€β”€ bug: 8
156
+ └── knowledge: 7
157
+ Last updated: 2026-07-10
158
+ File size: 12.4 KB
159
+ ```
160
+
161
+ ### Export
123
162
 
124
163
  ```bash
125
- # Remove from all agents and uninstall CLI
126
- npx toon-memory uninstall
164
+ $ npx toon-memory export
165
+
166
+ 🧠 toon-memory export
127
167
 
128
- # Or with the global install
129
- toon-memory uninstall
168
+ Exported 45 entries to:
169
+ /path/to/project/toon-memory-export.json
170
+ ```
171
+
172
+ ### Import
173
+
174
+ ```bash
175
+ $ npx toon-memory import backup.json
176
+
177
+ 🧠 toon-memory import
178
+
179
+ Imported 3 new entries
180
+ Skipped 2 duplicates
130
181
  ```
131
182
 
132
183
  ---
@@ -205,10 +256,23 @@ git clone https://github.com/LuiggiVal08/toon-memory.git
205
256
  cd toon-memory
206
257
  npm install
207
258
  npm run build
259
+ npm test
208
260
  ```
209
261
 
210
262
  ---
211
263
 
264
+ ## Contributing
265
+
266
+ Contributions are welcome! Please read our [Code of Conduct](CODE_OF_CONDUCT.md) first.
267
+
268
+ 1. Fork the repository
269
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
270
+ 3. Commit your changes (`git commit -m 'feat: add amazing feature'`)
271
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
272
+ 5. Open a Pull Request
273
+
274
+ ---
275
+
212
276
  ## License
213
277
 
214
278
  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";
@@ -77,6 +77,23 @@ function installOpenCodeTools() {
77
77
  console.log(" Created .opencode/memory/data.toon");
78
78
  }
79
79
  }
80
+ function ensureGitignore() {
81
+ const gitignorePath = join(projectRoot, ".gitignore");
82
+ const entry = ".opencode/memory/";
83
+ if (!existsSync(gitignorePath)) {
84
+ writeFileSync(gitignorePath, `${entry}
85
+ `);
86
+ console.log(" Created .gitignore with memory exclusion");
87
+ return;
88
+ }
89
+ const content = readFileSync(gitignorePath, "utf-8");
90
+ if (!content.includes(entry)) {
91
+ writeFileSync(gitignorePath, `${content.trim()}
92
+ ${entry}
93
+ `);
94
+ console.log(" Added .opencode/memory/ to .gitignore");
95
+ }
96
+ }
80
97
  function installMCPConfig(agent, scope) {
81
98
  const configPath = scope === "global" ? agent.global : agent.local;
82
99
  if (!configPath) {
@@ -139,6 +156,7 @@ function init(scope = "local") {
139
156
  installMCPConfig(agent, scope);
140
157
  console.log("");
141
158
  }
159
+ ensureGitignore();
142
160
  console.log("Done! Restart your agent to use memory tools.\n");
143
161
  }
144
162
  function status() {
@@ -195,6 +213,128 @@ function upgrade() {
195
213
  console.error("Upgrade failed:", error.message);
196
214
  }
197
215
  }
216
+ function stats() {
217
+ console.log("\n\u{1F9E0} toon-memory stats\n");
218
+ const memoryFile = join(projectRoot, ".opencode", "memory", "data.toon");
219
+ if (!existsSync(memoryFile)) {
220
+ console.log("Memory not initialized. Run 'npx toon-memory init' first.\n");
221
+ return;
222
+ }
223
+ const data = readFileSync(memoryFile, "utf-8");
224
+ const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|"));
225
+ const categories = {};
226
+ let latestDate = "";
227
+ for (const line of lines) {
228
+ const parts = line.trim().split("|");
229
+ if (parts.length >= 7) {
230
+ const category = parts[1];
231
+ const date = parts[6];
232
+ categories[category] = (categories[category] || 0) + 1;
233
+ if (date > latestDate) latestDate = date;
234
+ }
235
+ }
236
+ console.log("\u{1F4CA} Memory Stats");
237
+ console.log("\u2501".repeat(20));
238
+ console.log(`Total entries: ${lines.length}`);
239
+ for (const [cat, count] of Object.entries(categories)) {
240
+ console.log(`\u251C\u2500\u2500 ${cat}: ${count}`);
241
+ }
242
+ console.log(`Last updated: ${latestDate || "never"}`);
243
+ const fileSize = Buffer.byteLength(data, "utf-8");
244
+ console.log(`File size: ${(fileSize / 1024).toFixed(1)} KB`);
245
+ console.log("");
246
+ }
247
+ function exportMemory() {
248
+ console.log("\n\u{1F9E0} toon-memory export\n");
249
+ const memoryFile = join(projectRoot, ".opencode", "memory", "data.toon");
250
+ if (!existsSync(memoryFile)) {
251
+ console.log("Memory not initialized. Run 'npx toon-memory init' first.\n");
252
+ return;
253
+ }
254
+ const data = readFileSync(memoryFile, "utf-8");
255
+ const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|"));
256
+ const entries = lines.map((line) => {
257
+ const parts = line.trim().split("|");
258
+ return {
259
+ id: parts[0],
260
+ category: parts[1],
261
+ key: parts[2],
262
+ content: parts[3],
263
+ file: parts[4],
264
+ tags: parts[5] ? parts[5].split(";") : [],
265
+ date: parts[6]
266
+ };
267
+ });
268
+ const exportData = {
269
+ project: basename(projectRoot),
270
+ exported_at: (/* @__PURE__ */ new Date()).toISOString(),
271
+ entries,
272
+ summaries: {}
273
+ };
274
+ const outputPath = join(projectRoot, "toon-memory-export.json");
275
+ writeFileSync(outputPath, JSON.stringify(exportData, null, 2));
276
+ console.log(`Exported ${entries.length} entries to:`);
277
+ console.log(` ${outputPath}
278
+ `);
279
+ }
280
+ function importMemory() {
281
+ console.log("\n\u{1F9E0} toon-memory import\n");
282
+ const importFile = process.argv[3];
283
+ if (!importFile) {
284
+ console.log("Usage: npx toon-memory import <file.json>\n");
285
+ return;
286
+ }
287
+ const importPath = importFile.startsWith("/") ? importFile : join(projectRoot, importFile);
288
+ if (!existsSync(importPath)) {
289
+ console.log(`File not found: ${importPath}
290
+ `);
291
+ return;
292
+ }
293
+ let importData;
294
+ try {
295
+ importData = JSON.parse(readFileSync(importPath, "utf-8"));
296
+ } catch {
297
+ console.log("Invalid JSON file\n");
298
+ return;
299
+ }
300
+ if (!importData.entries || !Array.isArray(importData.entries)) {
301
+ console.log("Invalid format: missing 'entries' array\n");
302
+ return;
303
+ }
304
+ const memoryDir = join(projectRoot, ".opencode", "memory");
305
+ const memoryFile = join(memoryDir, "data.toon");
306
+ if (!existsSync(memoryDir)) mkdirSync(memoryDir, { recursive: true });
307
+ let existingKeys = [];
308
+ if (existsSync(memoryFile)) {
309
+ const existing = readFileSync(memoryFile, "utf-8");
310
+ existingKeys = existing.split("\n").filter((l) => l.startsWith(" ") && l.includes("|")).map((l) => l.trim().split("|")[2]);
311
+ }
312
+ const newEntries = importData.entries.filter((e) => !existingKeys.includes(e.key));
313
+ if (newEntries.length === 0) {
314
+ console.log("No new entries to import (all keys already exist)\n");
315
+ return;
316
+ }
317
+ const newLines = newEntries.map((e) => {
318
+ const tags = Array.isArray(e.tags) ? e.tags.join(";") : e.tags || "";
319
+ return ` ${e.id}|${e.category}|${e.key}|${e.content}|${e.file}|${tags}|${e.date}`;
320
+ }).join("\n");
321
+ if (existsSync(memoryFile)) {
322
+ const existing = readFileSync(memoryFile, "utf-8");
323
+ const updated = existing.replace(
324
+ /entries\[\d+\|]/,
325
+ `entries[${newEntries.length}|]`
326
+ ) + "\n" + newLines;
327
+ writeFileSync(memoryFile, updated);
328
+ } else {
329
+ writeFileSync(memoryFile, `version: 1
330
+ entries[${newEntries.length}|]{id|category|key|content|file|tags|date}:
331
+ ${newLines}
332
+ `);
333
+ }
334
+ console.log(`Imported ${newEntries.length} new entries`);
335
+ console.log(`Skipped ${importData.entries.length - newEntries.length} duplicates
336
+ `);
337
+ }
198
338
  const args = process.argv.slice(2);
199
339
  if (args[0] === "uninstall") {
200
340
  uninstall();
@@ -212,6 +352,18 @@ if (args[0] === "upgrade") {
212
352
  upgrade();
213
353
  process.exit(0);
214
354
  }
355
+ if (args[0] === "stats") {
356
+ stats();
357
+ process.exit(0);
358
+ }
359
+ if (args[0] === "export") {
360
+ exportMemory();
361
+ process.exit(0);
362
+ }
363
+ if (args[0] === "import") {
364
+ importMemory();
365
+ process.exit(0);
366
+ }
215
367
  const agents = detectAgents();
216
368
  console.log("\n\u{1F9E0} toon-memory installer\n");
217
369
  console.log("Supported agents:");
@@ -231,6 +383,7 @@ Installing ${scope}ly...
231
383
  installMCPConfig(agent, scope);
232
384
  console.log("");
233
385
  }
386
+ ensureGitignore();
234
387
  console.log("Done! Restart your agent to use memory tools.");
235
388
  console.log("Run 'npx toon-memory uninstall' to remove.\n");
236
389
  rl.close();
package/mcp/server.js CHANGED
@@ -27709,10 +27709,12 @@ server.registerTool(
27709
27709
  description: "Busca en la memoria persistente del proyecto. Devuelve entradas relevantes. Usar ANTES de leer archivos.",
27710
27710
  inputSchema: {
27711
27711
  query: external_exports.string().describe("Texto a buscar"),
27712
- category: external_exports.string().optional().default("").describe("Filtrar por categor\xEDa (vac\xEDo = todos)")
27712
+ category: external_exports.string().optional().default("").describe("Filtrar por categor\xEDa (vac\xEDo = todos)"),
27713
+ from_date: external_exports.string().optional().default("").describe("Fecha inicio filtro (YYYY-MM-DD)"),
27714
+ to_date: external_exports.string().optional().default("").describe("Fecha fin filtro (YYYY-MM-DD)")
27713
27715
  }
27714
27716
  },
27715
- async ({ query, category }) => {
27717
+ async ({ query, category, from_date, to_date }) => {
27716
27718
  const data = readMemory();
27717
27719
  const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|"));
27718
27720
  const queryLower = query.toLowerCase();
@@ -27722,6 +27724,8 @@ server.registerTool(
27722
27724
  if (parts.length < 7) return null;
27723
27725
  const [id, cat, key, content, file2, tags, date5] = parts;
27724
27726
  if (category && cat !== category) return null;
27727
+ if (from_date && date5 < from_date) return null;
27728
+ if (to_date && date5 > to_date) return null;
27725
27729
  const searchStr = `${id} ${cat} ${key} ${content} ${file2} ${tags}`.toLowerCase();
27726
27730
  if (!searchStr.includes(queryLower)) return null;
27727
27731
  return { id, cat, key, content, file: file2, tags, date: date5 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toon-memory",
3
- "version": "1.1.2",
3
+ "version": "1.3.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"
@@ -112,6 +112,24 @@ function installOpenCodeTools(): void {
112
112
  }
113
113
  }
114
114
 
115
+ // Add .opencode/memory/ to .gitignore if not present
116
+ function ensureGitignore(): void {
117
+ const gitignorePath = join(projectRoot, ".gitignore")
118
+ const entry = ".opencode/memory/"
119
+
120
+ if (!existsSync(gitignorePath)) {
121
+ writeFileSync(gitignorePath, `${entry}\n`)
122
+ console.log(" Created .gitignore with memory exclusion")
123
+ return
124
+ }
125
+
126
+ const content = readFileSync(gitignorePath, "utf-8")
127
+ if (!content.includes(entry)) {
128
+ writeFileSync(gitignorePath, `${content.trim()}\n${entry}\n`)
129
+ console.log(" Added .opencode/memory/ to .gitignore")
130
+ }
131
+ }
132
+
115
133
  // Install MCP server config for different agents
116
134
  function installMCPConfig(agent: Agent, scope: string): void {
117
135
  const configPath = scope === "global" ? agent.global : agent.local
@@ -197,6 +215,8 @@ function init(scope: string = "local"): void {
197
215
  console.log("")
198
216
  }
199
217
 
218
+ ensureGitignore()
219
+
200
220
  console.log("Done! Restart your agent to use memory tools.\n")
201
221
  }
202
222
 
@@ -271,6 +291,161 @@ function upgrade(): void {
271
291
  }
272
292
  }
273
293
 
294
+ // Show memory statistics
295
+ function stats(): void {
296
+ console.log("\n🧠 toon-memory stats\n")
297
+
298
+ const memoryFile = join(projectRoot, ".opencode", "memory", "data.toon")
299
+
300
+ if (!existsSync(memoryFile)) {
301
+ console.log("Memory not initialized. Run 'npx toon-memory init' first.\n")
302
+ return
303
+ }
304
+
305
+ const data = readFileSync(memoryFile, "utf-8")
306
+ const lines = data.split("\n").filter((l: string) => l.startsWith(" ") && l.includes("|"))
307
+
308
+ const categories: Record<string, number> = {}
309
+ let latestDate = ""
310
+
311
+ for (const line of lines) {
312
+ const parts = line.trim().split("|")
313
+ if (parts.length >= 7) {
314
+ const category = parts[1]
315
+ const date = parts[6]
316
+ categories[category] = (categories[category] || 0) + 1
317
+ if (date > latestDate) latestDate = date
318
+ }
319
+ }
320
+
321
+ console.log("πŸ“Š Memory Stats")
322
+ console.log("━".repeat(20))
323
+ console.log(`Total entries: ${lines.length}`)
324
+
325
+ for (const [cat, count] of Object.entries(categories)) {
326
+ console.log(`β”œβ”€β”€ ${cat}: ${count}`)
327
+ }
328
+
329
+ console.log(`Last updated: ${latestDate || "never"}`)
330
+
331
+ const fileSize = Buffer.byteLength(data, "utf-8")
332
+ console.log(`File size: ${(fileSize / 1024).toFixed(1)} KB`)
333
+ console.log("")
334
+ }
335
+
336
+ // Export memory to JSON
337
+ function exportMemory(): void {
338
+ console.log("\n🧠 toon-memory export\n")
339
+
340
+ const memoryFile = join(projectRoot, ".opencode", "memory", "data.toon")
341
+
342
+ if (!existsSync(memoryFile)) {
343
+ console.log("Memory not initialized. Run 'npx toon-memory init' first.\n")
344
+ return
345
+ }
346
+
347
+ const data = readFileSync(memoryFile, "utf-8")
348
+ const lines = data.split("\n").filter((l: string) => l.startsWith(" ") && l.includes("|"))
349
+
350
+ const entries = lines.map((line: string) => {
351
+ const parts = line.trim().split("|")
352
+ return {
353
+ id: parts[0],
354
+ category: parts[1],
355
+ key: parts[2],
356
+ content: parts[3],
357
+ file: parts[4],
358
+ tags: parts[5] ? parts[5].split(";") : [],
359
+ date: parts[6]
360
+ }
361
+ })
362
+
363
+ const exportData = {
364
+ project: basename(projectRoot),
365
+ exported_at: new Date().toISOString(),
366
+ entries,
367
+ summaries: {}
368
+ }
369
+
370
+ const outputPath = join(projectRoot, "toon-memory-export.json")
371
+ writeFileSync(outputPath, JSON.stringify(exportData, null, 2))
372
+
373
+ console.log(`Exported ${entries.length} entries to:`)
374
+ console.log(` ${outputPath}\n`)
375
+ }
376
+
377
+ // Import memory from JSON
378
+ function importMemory(): void {
379
+ console.log("\n🧠 toon-memory import\n")
380
+
381
+ const importFile = process.argv[3]
382
+
383
+ if (!importFile) {
384
+ console.log("Usage: npx toon-memory import <file.json>\n")
385
+ return
386
+ }
387
+
388
+ // Use absolute path if provided, otherwise resolve relative to project root
389
+ const importPath = importFile.startsWith("/") ? importFile : join(projectRoot, importFile)
390
+
391
+ if (!existsSync(importPath)) {
392
+ console.log(`File not found: ${importPath}\n`)
393
+ return
394
+ }
395
+
396
+ let importData: any
397
+ try {
398
+ importData = JSON.parse(readFileSync(importPath, "utf-8"))
399
+ } catch {
400
+ console.log("Invalid JSON file\n")
401
+ return
402
+ }
403
+
404
+ if (!importData.entries || !Array.isArray(importData.entries)) {
405
+ console.log("Invalid format: missing 'entries' array\n")
406
+ return
407
+ }
408
+
409
+ const memoryDir = join(projectRoot, ".opencode", "memory")
410
+ const memoryFile = join(memoryDir, "data.toon")
411
+
412
+ if (!existsSync(memoryDir)) mkdirSync(memoryDir, { recursive: true })
413
+
414
+ let existingKeys: string[] = []
415
+ if (existsSync(memoryFile)) {
416
+ const existing = readFileSync(memoryFile, "utf-8")
417
+ existingKeys = existing.split("\n")
418
+ .filter((l: string) => l.startsWith(" ") && l.includes("|"))
419
+ .map((l: string) => l.trim().split("|")[2])
420
+ }
421
+
422
+ const newEntries = importData.entries.filter((e: any) => !existingKeys.includes(e.key))
423
+
424
+ if (newEntries.length === 0) {
425
+ console.log("No new entries to import (all keys already exist)\n")
426
+ return
427
+ }
428
+
429
+ const newLines = newEntries.map((e: any) => {
430
+ const tags = Array.isArray(e.tags) ? e.tags.join(";") : (e.tags || "")
431
+ return ` ${e.id}|${e.category}|${e.key}|${e.content}|${e.file}|${tags}|${e.date}`
432
+ }).join("\n")
433
+
434
+ if (existsSync(memoryFile)) {
435
+ const existing = readFileSync(memoryFile, "utf-8")
436
+ const updated = existing.replace(
437
+ /entries\[\d+\|]/,
438
+ `entries[${newEntries.length}|]`
439
+ ) + "\n" + newLines
440
+ writeFileSync(memoryFile, updated)
441
+ } else {
442
+ writeFileSync(memoryFile, `version: 1\nentries[${newEntries.length}|]{id|category|key|content|file|tags|date}:\n${newLines}\n`)
443
+ }
444
+
445
+ console.log(`Imported ${newEntries.length} new entries`)
446
+ console.log(`Skipped ${importData.entries.length - newEntries.length} duplicates\n`)
447
+ }
448
+
274
449
  // Main
275
450
  const args = process.argv.slice(2)
276
451
 
@@ -294,6 +469,21 @@ if (args[0] === "upgrade") {
294
469
  process.exit(0)
295
470
  }
296
471
 
472
+ if (args[0] === "stats") {
473
+ stats()
474
+ process.exit(0)
475
+ }
476
+
477
+ if (args[0] === "export") {
478
+ exportMemory()
479
+ process.exit(0)
480
+ }
481
+
482
+ if (args[0] === "import") {
483
+ importMemory()
484
+ process.exit(0)
485
+ }
486
+
297
487
  const agents = detectAgents()
298
488
  console.log("\n🧠 toon-memory installer\n")
299
489
 
@@ -317,6 +507,8 @@ rl.question("Install (1) Local or (2) Global? [1/2]: ", (answer: string) => {
317
507
  console.log("")
318
508
  }
319
509
 
510
+ ensureGitignore()
511
+
320
512
  console.log("Done! Restart your agent to use memory tools.")
321
513
  console.log("Run 'npx toon-memory uninstall' to remove.\n")
322
514
  rl.close()
package/src/mcp/server.ts CHANGED
@@ -83,9 +83,11 @@ server.registerTool(
83
83
  inputSchema: {
84
84
  query: z.string().describe("Texto a buscar"),
85
85
  category: z.string().optional().default("").describe("Filtrar por categorΓ­a (vacΓ­o = todos)"),
86
+ from_date: z.string().optional().default("").describe("Fecha inicio filtro (YYYY-MM-DD)"),
87
+ to_date: z.string().optional().default("").describe("Fecha fin filtro (YYYY-MM-DD)"),
86
88
  },
87
89
  },
88
- async ({ query, category }) => {
90
+ async ({ query, category, from_date, to_date }) => {
89
91
  const data = readMemory()
90
92
  const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|"))
91
93
  const queryLower = query.toLowerCase()
@@ -97,6 +99,8 @@ server.registerTool(
97
99
  if (parts.length < 7) return null
98
100
  const [id, cat, key, content, file, tags, date] = parts
99
101
  if (category && cat !== category) return null
102
+ if (from_date && date < from_date) return null
103
+ if (to_date && date > to_date) return null
100
104
  const searchStr = `${id} ${cat} ${key} ${content} ${file} ${tags}`.toLowerCase()
101
105
  if (!searchStr.includes(queryLower)) return null
102
106
  return { id, cat, key, content, file, tags, date }