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 +94 -30
- package/bin/toon-memory.js +9 -14
- package/dist/cli/setup.js +154 -1
- package/mcp/server.js +6 -2
- package/package.json +3 -2
- package/src/bin/toon-memory.ts +15 -0
- package/src/cli/setup.ts +193 -1
- package/src/mcp/server.ts +5 -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,21 @@ 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
|
+
- **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
|
|
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 |
|
|
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
|
|
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
|
|
113
|
-
npx toon-memory init
|
|
114
|
-
npx toon-memory mcp
|
|
115
|
-
npx toon-memory status
|
|
116
|
-
npx toon-memory
|
|
117
|
-
npx toon-memory
|
|
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
|
-
|
|
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
|
-
|
|
126
|
-
|
|
164
|
+
$ npx toon-memory export
|
|
165
|
+
|
|
166
|
+
π§ toon-memory export
|
|
127
167
|
|
|
128
|
-
|
|
129
|
-
toon-memory
|
|
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
|
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";
|
|
@@ -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.
|
|
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 }
|