toon-memory 1.7.0 → 2.0.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 +270 -34
- package/dist/cli/setup.js +409 -96
- package/mcp/server.js +268 -19
- package/package.json +9 -1
- package/skills/toon-memory.md +25 -14
- package/src/cli/setup.ts +650 -335
- package/src/mcp/server.ts +327 -11
- package/uninstall.sh +33 -5
package/mcp/server.js
CHANGED
|
@@ -16506,8 +16506,8 @@ var require_resolve = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
16506
16506
|
}
|
|
16507
16507
|
return count;
|
|
16508
16508
|
}
|
|
16509
|
-
function getFullPath(resolver, id = "",
|
|
16510
|
-
if (
|
|
16509
|
+
function getFullPath(resolver, id = "", normalize2) {
|
|
16510
|
+
if (normalize2 !== false) id = normalizeId(id);
|
|
16511
16511
|
return _getFullPath(resolver, resolver.parse(id));
|
|
16512
16512
|
}
|
|
16513
16513
|
exports.getFullPath = getFullPath;
|
|
@@ -17602,7 +17602,7 @@ var require_schemes = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
17602
17602
|
var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
17603
17603
|
const { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizeComponentEncoding, isIPv4, nonSimpleDomain } = require_utils();
|
|
17604
17604
|
const { SCHEMES, getSchemeHandler } = require_schemes();
|
|
17605
|
-
function
|
|
17605
|
+
function normalize2(uri, options) {
|
|
17606
17606
|
if (typeof uri === "string") uri = serialize(parse3(uri, options), options);
|
|
17607
17607
|
else if (typeof uri === "object") uri = parse3(serialize(uri, options), options);
|
|
17608
17608
|
return uri;
|
|
@@ -17780,7 +17780,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
17780
17780
|
}
|
|
17781
17781
|
const fastUri = {
|
|
17782
17782
|
SCHEMES,
|
|
17783
|
-
normalize,
|
|
17783
|
+
normalize: normalize2,
|
|
17784
17784
|
resolve,
|
|
17785
17785
|
resolveComponent,
|
|
17786
17786
|
equal,
|
|
@@ -27644,10 +27644,11 @@ import { dirname, join } from "path";
|
|
|
27644
27644
|
import { fileURLToPath } from "url";
|
|
27645
27645
|
import { randomBytes, createCipheriv, createDecipheriv } from "crypto";
|
|
27646
27646
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
27647
|
-
var MEMORY_DIR = join(process.cwd(), ".
|
|
27647
|
+
var MEMORY_DIR = join(process.cwd(), ".toon-memory", "memory");
|
|
27648
27648
|
var MEMORY_FILE = join(MEMORY_DIR, "data.toon");
|
|
27649
27649
|
var ARCHIVE_FILE = join(MEMORY_DIR, "archive.toon");
|
|
27650
27650
|
var CONFIG_FILE = join(MEMORY_DIR, "config.json");
|
|
27651
|
+
var MAX_ENTRIES = 100;
|
|
27651
27652
|
var ARCHIVE_DAYS = 30;
|
|
27652
27653
|
var ALGORITHM = "aes-256-gcm";
|
|
27653
27654
|
function loadConfig() {
|
|
@@ -27673,7 +27674,7 @@ function ensureMemoryDir() {
|
|
|
27673
27674
|
function ensureMemoryFile() {
|
|
27674
27675
|
ensureMemoryDir();
|
|
27675
27676
|
if (!existsSync(MEMORY_FILE)) {
|
|
27676
|
-
writeFileSync(MEMORY_FILE, "version: 1\nentries[0|]{id|category|key|content|file|tags|date}:\n");
|
|
27677
|
+
writeFileSync(MEMORY_FILE, "version: 1\nentries[0|]{id|category|key|content|file|tags|date|ttl}:\n");
|
|
27677
27678
|
}
|
|
27678
27679
|
}
|
|
27679
27680
|
function encrypt(text, key) {
|
|
@@ -27726,6 +27727,96 @@ function writeMemory(content) {
|
|
|
27726
27727
|
function generateId() {
|
|
27727
27728
|
return randomBytes(4).toString("hex");
|
|
27728
27729
|
}
|
|
27730
|
+
function parseTTL(ttl) {
|
|
27731
|
+
if (!ttl || !ttl.trim()) return "";
|
|
27732
|
+
const trimmed = ttl.trim();
|
|
27733
|
+
const dayMatch = trimmed.match(/^(\d+)d$/);
|
|
27734
|
+
if (dayMatch) {
|
|
27735
|
+
const days = parseInt(dayMatch[1]);
|
|
27736
|
+
const d = /* @__PURE__ */ new Date();
|
|
27737
|
+
d.setDate(d.getDate() + days);
|
|
27738
|
+
return d.toISOString().split("T")[0];
|
|
27739
|
+
}
|
|
27740
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(trimmed)) return trimmed;
|
|
27741
|
+
return "";
|
|
27742
|
+
}
|
|
27743
|
+
function isExpired(ttl) {
|
|
27744
|
+
if (!ttl) return false;
|
|
27745
|
+
const ttlDate = parseTTL(ttl) || ttl;
|
|
27746
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
27747
|
+
return ttlDate <= today;
|
|
27748
|
+
}
|
|
27749
|
+
var TAG_VOCABULARY = {
|
|
27750
|
+
"redis": ["redis", "cache", "caching", "memcached"],
|
|
27751
|
+
"auth": ["auth", "authentication", "authorization", "login", "token", "jwt", "session", "oauth"],
|
|
27752
|
+
"api": ["api", "endpoint", "rest", "graphql", "route", "router", "controller"],
|
|
27753
|
+
"db": ["database", "db", "sql", "postgres", "mysql", "mongo", "query", "migration", "schema"],
|
|
27754
|
+
"security": ["security", "encrypt", "decrypt", "vulnerability", "xss", "csrf", "cors", "sanitiz"],
|
|
27755
|
+
"test": ["test", "testing", "vitest", "jest", "spec", "mock", "assert", "coverage"],
|
|
27756
|
+
"deploy": ["deploy", "docker", "ci/cd", "github actions", "pipeline", "kubernetes", "k8s"],
|
|
27757
|
+
"config": ["config", "configuration", "settings", "env", "environment", "dotenv"],
|
|
27758
|
+
"performance": ["performance", "optimize", "benchmark", "latency", "throughput", "cache"],
|
|
27759
|
+
"refactor": ["refactor", "cleanup", "restructure", "reorganize", "rework"],
|
|
27760
|
+
"error": ["error", "exception", "throw", "catch", "handling", "retry", "fallback"],
|
|
27761
|
+
"logging": ["log", "logging", "logger", "debug", "trace", "monitor", "observability"],
|
|
27762
|
+
"types": ["typescript", "types", "type", "interface", "generic", "enum", "zod", "schema"],
|
|
27763
|
+
"async": ["async", "await", "promise", "concurrent", "parallel", "worker", "queue"],
|
|
27764
|
+
"state": ["state", "store", "redux", "context", "reducer", "action", "observable"],
|
|
27765
|
+
"ui": ["ui", "component", "render", "dom", "css", "style", "layout", "responsive"],
|
|
27766
|
+
"storage": ["storage", "file", "filesystem", "s3", "blob", "upload", "download"],
|
|
27767
|
+
"email": ["email", "mail", "smtp", "sendgrid", "newsletter", "notification"],
|
|
27768
|
+
"payment": ["payment", "stripe", "billing", "invoice", "checkout", "subscription"],
|
|
27769
|
+
"webhook": ["webhook", "callback", "event", "listener", "hook"]
|
|
27770
|
+
};
|
|
27771
|
+
function inferTags(content, key) {
|
|
27772
|
+
const text = `${key} ${content}`.toLowerCase();
|
|
27773
|
+
const matched = [];
|
|
27774
|
+
for (const [tag, keywords] of Object.entries(TAG_VOCABULARY)) {
|
|
27775
|
+
if (keywords.some((kw) => text.includes(kw))) {
|
|
27776
|
+
matched.push(tag);
|
|
27777
|
+
}
|
|
27778
|
+
}
|
|
27779
|
+
return matched.join(";");
|
|
27780
|
+
}
|
|
27781
|
+
function parseRelativeDate(since) {
|
|
27782
|
+
const trimmed = since.trim();
|
|
27783
|
+
const hourMatch = trimmed.match(/^(\d+)h$/);
|
|
27784
|
+
if (hourMatch) {
|
|
27785
|
+
const d = /* @__PURE__ */ new Date();
|
|
27786
|
+
d.setHours(d.getHours() - parseInt(hourMatch[1]));
|
|
27787
|
+
return d.toISOString().split("T")[0];
|
|
27788
|
+
}
|
|
27789
|
+
const dayMatch = trimmed.match(/^(\d+)d$/);
|
|
27790
|
+
if (dayMatch) {
|
|
27791
|
+
const d = /* @__PURE__ */ new Date();
|
|
27792
|
+
d.setDate(d.getDate() - parseInt(dayMatch[1]));
|
|
27793
|
+
return d.toISOString().split("T")[0];
|
|
27794
|
+
}
|
|
27795
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(trimmed)) return trimmed;
|
|
27796
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
27797
|
+
return today;
|
|
27798
|
+
}
|
|
27799
|
+
var normalize = (s) => s.toLowerCase().replace(/[-_]/g, " ").replace(/\s+/g, " ").trim();
|
|
27800
|
+
function findRelatedEntries(text, excludeKey = "", limit = 3) {
|
|
27801
|
+
const data = readMemory();
|
|
27802
|
+
const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|") && !l.startsWith(" summaries:"));
|
|
27803
|
+
const queryTokens = normalize(text).split(" ").filter(Boolean);
|
|
27804
|
+
const scored = lines.map((line) => {
|
|
27805
|
+
const trimmed = line.trim();
|
|
27806
|
+
const parts = trimmed.split("|");
|
|
27807
|
+
if (parts.length < 7) return null;
|
|
27808
|
+
const [id, cat, key, content, file2, tags, date5] = parts;
|
|
27809
|
+
if (key === excludeKey) return null;
|
|
27810
|
+
const searchStr = normalize(`${id} ${cat} ${key} ${content} ${file2} ${tags}`);
|
|
27811
|
+
let score = 0;
|
|
27812
|
+
for (const token of queryTokens) {
|
|
27813
|
+
if (searchStr.includes(token)) score++;
|
|
27814
|
+
}
|
|
27815
|
+
if (score === 0) return null;
|
|
27816
|
+
return { id, cat, key, content, file: file2, tags, date: date5, score };
|
|
27817
|
+
}).filter(Boolean).sort((a, b) => b.score - a.score).slice(0, limit);
|
|
27818
|
+
return scored;
|
|
27819
|
+
}
|
|
27729
27820
|
function archiveOldEntries() {
|
|
27730
27821
|
const data = readMemory();
|
|
27731
27822
|
const lines = data.split("\n");
|
|
@@ -27742,7 +27833,10 @@ function archiveOldEntries() {
|
|
|
27742
27833
|
const parts = line.trim().split("|");
|
|
27743
27834
|
if (parts.length >= 7) {
|
|
27744
27835
|
const date5 = parts[6];
|
|
27745
|
-
|
|
27836
|
+
const ttl = parts[7] || "";
|
|
27837
|
+
const isOld = date5 < cutoffStr;
|
|
27838
|
+
const isTtlExpired = ttl && isExpired(ttl);
|
|
27839
|
+
if (isOld || isTtlExpired) {
|
|
27746
27840
|
toArchive.push(line.trim());
|
|
27747
27841
|
} else {
|
|
27748
27842
|
toKeep.push(line.trim());
|
|
@@ -27779,8 +27873,58 @@ archived:
|
|
|
27779
27873
|
return { archived: toArchive.length, kept: toKeep.length };
|
|
27780
27874
|
}
|
|
27781
27875
|
var server = new McpServer(
|
|
27782
|
-
{ name: "toon-memory", version: "
|
|
27783
|
-
{ capabilities: { tools: { listChanged: true } } }
|
|
27876
|
+
{ name: "toon-memory", version: "2.0.0" },
|
|
27877
|
+
{ capabilities: { tools: { listChanged: true }, resources: { listChanged: true } } }
|
|
27878
|
+
);
|
|
27879
|
+
server.registerResource(
|
|
27880
|
+
"memory-entries",
|
|
27881
|
+
"toon://memory/entries",
|
|
27882
|
+
{ title: "Memory Entries", mimeType: "text/plain" },
|
|
27883
|
+
async (uri) => {
|
|
27884
|
+
const data = readMemory();
|
|
27885
|
+
return { contents: [{ uri: uri.href, text: data }] };
|
|
27886
|
+
}
|
|
27887
|
+
);
|
|
27888
|
+
server.registerResource(
|
|
27889
|
+
"memory-stats",
|
|
27890
|
+
"toon://memory/stats",
|
|
27891
|
+
{ title: "Memory Stats", mimeType: "text/plain" },
|
|
27892
|
+
async (uri) => {
|
|
27893
|
+
const data = readMemory();
|
|
27894
|
+
const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|") && !l.startsWith(" summaries:"));
|
|
27895
|
+
const entries = lines.map((l) => {
|
|
27896
|
+
const parts = l.trim().split("|");
|
|
27897
|
+
return { category: parts[1] || "unknown", ttl: parts[7] || "" };
|
|
27898
|
+
});
|
|
27899
|
+
const byCategory = {};
|
|
27900
|
+
for (const e of entries) {
|
|
27901
|
+
byCategory[e.category] = (byCategory[e.category] || 0) + 1;
|
|
27902
|
+
}
|
|
27903
|
+
const withTtl = entries.filter((e) => e.ttl).length;
|
|
27904
|
+
const expired = entries.filter((e) => e.ttl && isExpired(e.ttl)).length;
|
|
27905
|
+
const text = [
|
|
27906
|
+
`Entradas totales: ${entries.length}`,
|
|
27907
|
+
`TTL: ${withTtl} con expiraci\xF3n, ${expired} expiradas`,
|
|
27908
|
+
"Por categor\xEDa:",
|
|
27909
|
+
...Object.entries(byCategory).map(([k, v]) => ` ${k}: ${v}`)
|
|
27910
|
+
].join("\n");
|
|
27911
|
+
return { contents: [{ uri: uri.href, text }] };
|
|
27912
|
+
}
|
|
27913
|
+
);
|
|
27914
|
+
server.registerResource(
|
|
27915
|
+
"memory-summaries",
|
|
27916
|
+
"toon://memory/summaries",
|
|
27917
|
+
{ title: "Memory Summaries", mimeType: "text/plain" },
|
|
27918
|
+
async (uri) => {
|
|
27919
|
+
const data = readMemory();
|
|
27920
|
+
const lines = data.split("\n");
|
|
27921
|
+
const summaryIdx = lines.findIndex((l) => l.trim().startsWith("summaries:"));
|
|
27922
|
+
if (summaryIdx === -1) {
|
|
27923
|
+
return { contents: [{ uri: uri.href, text: "No hay res\xFAmenes guardados" }] };
|
|
27924
|
+
}
|
|
27925
|
+
const summaryText = lines.slice(summaryIdx + 1).filter((l) => l.includes(":")).join("\n");
|
|
27926
|
+
return { contents: [{ uri: uri.href, text: summaryText || "No hay res\xFAmenes guardados" }] };
|
|
27927
|
+
}
|
|
27784
27928
|
);
|
|
27785
27929
|
server.registerTool(
|
|
27786
27930
|
"memory_remember",
|
|
@@ -27792,10 +27936,11 @@ server.registerTool(
|
|
|
27792
27936
|
key: external_exports.string().describe("T\xEDtulo corto en kebab-case (ej: risk-engine-prioridad)"),
|
|
27793
27937
|
content: external_exports.string().describe("Contenido detallado del hecho"),
|
|
27794
27938
|
file: external_exports.string().optional().default("").describe("Archivo o l\xEDnea relacionada (ej: spec.md:145)"),
|
|
27795
|
-
tags: external_exports.string().optional().default("").describe("Tags separados por punto y coma (ej: risk;spec)")
|
|
27939
|
+
tags: external_exports.string().optional().default("").describe("Tags separados por punto y coma (ej: risk;spec)"),
|
|
27940
|
+
ttl: external_exports.string().optional().default("").describe("Time to live (ej: 7d, 2026-07-17). Vac\xEDo = sin expiraci\xF3n")
|
|
27796
27941
|
}
|
|
27797
27942
|
},
|
|
27798
|
-
async ({ category, key, content, file: file2, tags }) => {
|
|
27943
|
+
async ({ category, key, content, file: file2, tags, ttl }) => {
|
|
27799
27944
|
const data = readMemory();
|
|
27800
27945
|
const newId = generateId();
|
|
27801
27946
|
const date5 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -27819,8 +27964,11 @@ server.registerTool(
|
|
|
27819
27964
|
}
|
|
27820
27965
|
}
|
|
27821
27966
|
const entryId = existingIdx !== -1 ? existingId : newId;
|
|
27822
|
-
const
|
|
27967
|
+
const resolvedTtl = parseTTL(ttl);
|
|
27968
|
+
const resolvedTags = tags ? tags : inferTags(content, key);
|
|
27969
|
+
const newEntry = `${entryId}|${category}|${key}|${content}|${file2 || ""}|${resolvedTags}|${date5}|${resolvedTtl}`;
|
|
27823
27970
|
let action = "Guardado";
|
|
27971
|
+
const tagsInferred = !tags && resolvedTags ? true : false;
|
|
27824
27972
|
if (existingIdx !== -1) {
|
|
27825
27973
|
lines[existingIdx] = ` ${newEntry}`;
|
|
27826
27974
|
action = "Actualizado";
|
|
@@ -27831,9 +27979,32 @@ server.registerTool(
|
|
|
27831
27979
|
lines[headerIdx] = lines[headerIdx].replace(/\[\d+\|/, `[${count + 1}|`);
|
|
27832
27980
|
}
|
|
27833
27981
|
writeMemory(lines.join("\n"));
|
|
27982
|
+
const headerMatch = lines[headerIdx].match(/\[(\d+)\|/);
|
|
27983
|
+
const entryCount = headerMatch ? parseInt(headerMatch[1]) : 0;
|
|
27984
|
+
let archiveMsg = "";
|
|
27985
|
+
if (entryCount > MAX_ENTRIES) {
|
|
27986
|
+
const result = archiveOldEntries();
|
|
27987
|
+
if (result.archived > 0) {
|
|
27988
|
+
archiveMsg = `
|
|
27989
|
+
\u{1F4E6} Auto-archived ${result.archived} old entries (${result.kept} kept)`;
|
|
27990
|
+
}
|
|
27991
|
+
}
|
|
27992
|
+
const ttlMsg = resolvedTtl ? `
|
|
27993
|
+
\u23F0 TTL: ${resolvedTtl}` : "";
|
|
27994
|
+
const inferredMsg = tagsInferred ? `
|
|
27995
|
+
\u{1F3F7}\uFE0F Tags inferidos: ${resolvedTags}` : "";
|
|
27996
|
+
const related = findRelatedEntries(`${key} ${content} ${resolvedTags}`, key, 3);
|
|
27997
|
+
let relatedMsg = "";
|
|
27998
|
+
if (related.length > 0) {
|
|
27999
|
+
const items = related.map((r) => ` [${r.cat}] ${r.key} \u2014 ${r.content.slice(0, 80)}`).join("\n");
|
|
28000
|
+
relatedMsg = `
|
|
28001
|
+
|
|
28002
|
+
\u{1F517} Entradas relacionadas:
|
|
28003
|
+
${items}`;
|
|
28004
|
+
}
|
|
27834
28005
|
return {
|
|
27835
28006
|
content: [{ type: "text", text: `\u{1F9E0} ${action}: ${category}/${key} (${entryId})
|
|
27836
|
-
${content}` }]
|
|
28007
|
+
${content}${ttlMsg}${inferredMsg}${archiveMsg}${relatedMsg}` }]
|
|
27837
28008
|
};
|
|
27838
28009
|
}
|
|
27839
28010
|
);
|
|
@@ -27852,17 +28023,18 @@ server.registerTool(
|
|
|
27852
28023
|
async ({ query, category, from_date, to_date }) => {
|
|
27853
28024
|
const data = readMemory();
|
|
27854
28025
|
const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|") && !l.startsWith("summaries:"));
|
|
27855
|
-
const
|
|
27856
|
-
const queryTokens =
|
|
28026
|
+
const normalize2 = (s) => s.toLowerCase().replace(/[-_]/g, " ").replace(/\s+/g, " ").trim();
|
|
28027
|
+
const queryTokens = normalize2(query).split(" ").filter(Boolean);
|
|
27857
28028
|
const results = lines.map((line) => {
|
|
27858
28029
|
const trimmed = line.trim();
|
|
27859
28030
|
const parts = trimmed.split("|");
|
|
27860
28031
|
if (parts.length < 7) return null;
|
|
27861
|
-
const [id, cat, key, content, file2, tags, date5] = parts;
|
|
28032
|
+
const [id, cat, key, content, file2, tags, date5, ttl] = parts;
|
|
27862
28033
|
if (category && cat !== category) return null;
|
|
27863
28034
|
if (from_date && date5 < from_date) return null;
|
|
27864
28035
|
if (to_date && date5 > to_date) return null;
|
|
27865
|
-
|
|
28036
|
+
if (ttl && isExpired(ttl)) return null;
|
|
28037
|
+
const searchStr = normalize2(`${id} ${cat} ${key} ${content} ${file2} ${tags}`);
|
|
27866
28038
|
if (!queryTokens.every((token) => searchStr.includes(token))) return null;
|
|
27867
28039
|
return { id, cat, key, content, file: file2, tags, date: date5 };
|
|
27868
28040
|
}).filter(Boolean);
|
|
@@ -27919,12 +28091,14 @@ server.registerTool(
|
|
|
27919
28091
|
const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|") && !l.startsWith(" summaries:"));
|
|
27920
28092
|
const entries = lines.map((l) => {
|
|
27921
28093
|
const parts = l.trim().split("|");
|
|
27922
|
-
return { category: parts[1] || "unknown" };
|
|
28094
|
+
return { category: parts[1] || "unknown", ttl: parts[7] || "" };
|
|
27923
28095
|
});
|
|
27924
28096
|
const byCategory = {};
|
|
27925
28097
|
for (const e of entries) {
|
|
27926
28098
|
byCategory[e.category] = (byCategory[e.category] || 0) + 1;
|
|
27927
28099
|
}
|
|
28100
|
+
const withTtl = entries.filter((e) => e.ttl).length;
|
|
28101
|
+
const expired = entries.filter((e) => e.ttl && isExpired(e.ttl)).length;
|
|
27928
28102
|
const summaryLines = data.split("\n").filter((l) => l.includes(":") && !l.startsWith(" ") && !l.startsWith("version") && !l.startsWith("entries") && !/^\[\d+\|]/.test(l));
|
|
27929
28103
|
const stats = [
|
|
27930
28104
|
`Entradas totales: ${entries.length}`,
|
|
@@ -27933,15 +28107,90 @@ server.registerTool(
|
|
|
27933
28107
|
"Por categor\xEDa:",
|
|
27934
28108
|
...Object.entries(byCategory).map(([k, v]) => ` ${k}: ${v}`),
|
|
27935
28109
|
"",
|
|
28110
|
+
`TTL: ${withTtl} con expiraci\xF3n, ${expired} expiradas`,
|
|
28111
|
+
"",
|
|
27936
28112
|
`\xDAltimas 5 entradas:`,
|
|
27937
28113
|
...lines.slice(-5).map((l) => {
|
|
27938
28114
|
const parts = l.trim().split("|");
|
|
27939
|
-
|
|
28115
|
+
const ttlInfo = parts[7] ? ` | TTL: ${parts[7]}` : "";
|
|
28116
|
+
return ` [${parts[1]}] ${parts[2]} (${parts[0]})${ttlInfo}`;
|
|
27940
28117
|
})
|
|
27941
28118
|
];
|
|
27942
28119
|
return { content: [{ type: "text", text: stats.join("\n") }] };
|
|
27943
28120
|
}
|
|
27944
28121
|
);
|
|
28122
|
+
server.registerTool(
|
|
28123
|
+
"memory_diff",
|
|
28124
|
+
{
|
|
28125
|
+
title: "Memory Diff",
|
|
28126
|
+
description: "Muestra qu\xE9 cambi\xF3 en la memoria desde una fecha. \xDAtil para saber qu\xE9 se aprendi\xF3 desde la \xFAltima sesi\xF3n.",
|
|
28127
|
+
inputSchema: {
|
|
28128
|
+
since: external_exports.string().describe("Desde cu\xE1ndo mostrar cambios (ej: 24h, 7d, 2026-07-10)"),
|
|
28129
|
+
type: external_exports.enum(["all", "created", "updated"]).optional().default("all").describe("Filtrar por tipo de cambio")
|
|
28130
|
+
}
|
|
28131
|
+
},
|
|
28132
|
+
async ({ since, type }) => {
|
|
28133
|
+
const sinceDate = parseRelativeDate(since);
|
|
28134
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
28135
|
+
const data = readMemory();
|
|
28136
|
+
const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|") && !l.startsWith(" summaries:"));
|
|
28137
|
+
const results = lines.map((line) => {
|
|
28138
|
+
const trimmed = line.trim();
|
|
28139
|
+
const parts = trimmed.split("|");
|
|
28140
|
+
if (parts.length < 7) return null;
|
|
28141
|
+
const [id, cat, key, content, file2, tags, date5] = parts;
|
|
28142
|
+
if (date5 < sinceDate) return null;
|
|
28143
|
+
const changeType = date5 === today ? "created" : "updated";
|
|
28144
|
+
if (type !== "all" && changeType !== type) return null;
|
|
28145
|
+
return { id, cat, key, content, file: file2, tags, date: date5, changeType };
|
|
28146
|
+
}).filter(Boolean);
|
|
28147
|
+
if (results.length === 0) {
|
|
28148
|
+
return { content: [{ type: "text", text: `No hay cambios desde ${sinceDate}` }] };
|
|
28149
|
+
}
|
|
28150
|
+
const created = results.filter((r) => r.changeType === "created");
|
|
28151
|
+
const updated = results.filter((r) => r.changeType === "updated");
|
|
28152
|
+
const sections = [`\u{1F4CB} Cambios desde ${sinceDate}:`, ""];
|
|
28153
|
+
if (created.length > 0 && (type === "all" || type === "created")) {
|
|
28154
|
+
sections.push(`\u2795 Nuevas (${created.length}):`);
|
|
28155
|
+
for (const r of created) {
|
|
28156
|
+
sections.push(` [${r.cat}] ${r.key} (${r.id})
|
|
28157
|
+
${r.content}`);
|
|
28158
|
+
}
|
|
28159
|
+
sections.push("");
|
|
28160
|
+
}
|
|
28161
|
+
if (updated.length > 0 && (type === "all" || type === "updated")) {
|
|
28162
|
+
sections.push(`\u270F\uFE0F Actualizadas (${updated.length}):`);
|
|
28163
|
+
for (const r of updated) {
|
|
28164
|
+
sections.push(` [${r.cat}] ${r.key} (${r.id}) \u2014 ${r.date}`);
|
|
28165
|
+
}
|
|
28166
|
+
sections.push("");
|
|
28167
|
+
}
|
|
28168
|
+
return { content: [{ type: "text", text: sections.join("\n") }] };
|
|
28169
|
+
}
|
|
28170
|
+
);
|
|
28171
|
+
server.registerTool(
|
|
28172
|
+
"memory_suggest",
|
|
28173
|
+
{
|
|
28174
|
+
title: "Suggest Related Memories",
|
|
28175
|
+
description: "Sugiere entradas de memoria relacionadas con un contexto dado. \xDAtil para obtener contexto antes de una tarea.",
|
|
28176
|
+
inputSchema: {
|
|
28177
|
+
context: external_exports.string().describe("Texto o contexto para buscar sugerencias"),
|
|
28178
|
+
limit: external_exports.number().optional().default(5).describe("M\xE1ximo de sugerencias")
|
|
28179
|
+
}
|
|
28180
|
+
},
|
|
28181
|
+
async ({ context, limit }) => {
|
|
28182
|
+
const related = findRelatedEntries(context, "", limit);
|
|
28183
|
+
if (related.length === 0) {
|
|
28184
|
+
return { content: [{ type: "text", text: `No se encontraron entradas relacionadas con "${context}"` }] };
|
|
28185
|
+
}
|
|
28186
|
+
const formatted = related.map((r) => `[${r.cat}] ${r.key} (${r.id})
|
|
28187
|
+
${r.content}
|
|
28188
|
+
File: ${r.file} | Tags: ${r.tags} | Date: ${r.date}`).join("\n\n");
|
|
28189
|
+
return { content: [{ type: "text", text: `\u{1F50D} Sugerencias para "${context}":
|
|
28190
|
+
|
|
28191
|
+
${formatted}` }] };
|
|
28192
|
+
}
|
|
28193
|
+
);
|
|
27945
28194
|
server.registerTool(
|
|
27946
28195
|
"memory_summary",
|
|
27947
28196
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toon-memory",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.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": {
|
|
@@ -45,6 +45,14 @@
|
|
|
45
45
|
"windsurf",
|
|
46
46
|
"cline",
|
|
47
47
|
"continue",
|
|
48
|
+
"codex",
|
|
49
|
+
"gemini",
|
|
50
|
+
"zed",
|
|
51
|
+
"antigravity",
|
|
52
|
+
"aider",
|
|
53
|
+
"kilocode",
|
|
54
|
+
"openclaw",
|
|
55
|
+
"kiro",
|
|
48
56
|
"ai-agent",
|
|
49
57
|
"memory",
|
|
50
58
|
"toon",
|
package/skills/toon-memory.md
CHANGED
|
@@ -6,16 +6,23 @@ A persistent memory system for AI coding agents. It remembers decisions, pattern
|
|
|
6
6
|
|
|
7
7
|
## Supported Agents
|
|
8
8
|
|
|
9
|
-
| Agent | Config File | Format |
|
|
10
|
-
|
|
11
|
-
| OpenCode | `.opencode/opencode.json` | MCP server |
|
|
12
|
-
| VS Code / Copilot | `.vscode/mcp.json` | MCP server |
|
|
13
|
-
| Claude | `.claude/settings.json` | MCP server |
|
|
14
|
-
| Cursor | `.cursor/mcp.json` | MCP server |
|
|
15
|
-
| Windsurf |
|
|
16
|
-
| Cline | `.cline/mcp.json` | MCP server |
|
|
17
|
-
| Continue | `.continue/config.json` | MCP server |
|
|
18
|
-
|
|
|
9
|
+
| Agent | Config File | Format | Hooks |
|
|
10
|
+
|-------|-------------|--------|-------|
|
|
11
|
+
| OpenCode | `.opencode/opencode.json` | MCP server | — |
|
|
12
|
+
| VS Code / Copilot | `.vscode/mcp.json` | MCP server | — |
|
|
13
|
+
| Claude Code | `.claude/settings.json` | MCP server | SessionStart |
|
|
14
|
+
| Cursor | `.cursor/mcp.json` | MCP server | — |
|
|
15
|
+
| Windsurf | `~/.codeium/windsurf/mcp_config.json` | MCP server | — |
|
|
16
|
+
| Cline | `.cline/mcp.json` | MCP server | — |
|
|
17
|
+
| Continue | `.continue/config.json` | MCP server | — |
|
|
18
|
+
| Codex CLI | `.codex/config.toml` | TOML | SessionStart |
|
|
19
|
+
| Gemini CLI | `.gemini/settings.json` | MCP server | SessionStart |
|
|
20
|
+
| Zed | `~/.config/zed/settings.json` | JSONC | — |
|
|
21
|
+
| Antigravity | `.gemini/config/mcp_config.json` | MCP server | SessionStart |
|
|
22
|
+
| Aider | — | Instructions only | — |
|
|
23
|
+
| KiloCode | `~/.kilocode/mcp_settings.json` | MCP server | — |
|
|
24
|
+
| OpenClaw | `.openclaw.json` | MCP server | — |
|
|
25
|
+
| Kiro | `.kiro/settings/mcp.json` | MCP server | — |
|
|
19
26
|
|
|
20
27
|
## Tools
|
|
21
28
|
|
|
@@ -26,6 +33,9 @@ A persistent memory system for AI coding agents. It remembers decisions, pattern
|
|
|
26
33
|
| `memory_forget` | Remove an entry by key or id |
|
|
27
34
|
| `memory_stats` | View memory state |
|
|
28
35
|
| `memory_summary` | Save/retrieve file summaries |
|
|
36
|
+
| `memory_archive` | Archive old entries |
|
|
37
|
+
| `memory_encrypt` | Enable encryption |
|
|
38
|
+
| `memory_decrypt` | Disable encryption |
|
|
29
39
|
|
|
30
40
|
## Installation
|
|
31
41
|
|
|
@@ -37,8 +47,9 @@ npx toon-memory
|
|
|
37
47
|
|
|
38
48
|
This will:
|
|
39
49
|
1. Detect installed agents
|
|
40
|
-
2.
|
|
41
|
-
3.
|
|
50
|
+
2. Let you select which agents to configure
|
|
51
|
+
3. Ask for local or global installation
|
|
52
|
+
4. Configure MCP server, instructions, and hooks automatically
|
|
42
53
|
|
|
43
54
|
### Manual installation
|
|
44
55
|
|
|
@@ -71,7 +82,7 @@ Add to your agent's MCP config:
|
|
|
71
82
|
### When exploring the project
|
|
72
83
|
|
|
73
84
|
1. First: `memory_recall(query="what you need to know")`
|
|
74
|
-
2. If no result: use
|
|
85
|
+
2. If no result: use grep or search tools
|
|
75
86
|
3. Only if both fail: read files directly
|
|
76
87
|
|
|
77
88
|
### At the END of every session
|
|
@@ -91,7 +102,7 @@ Add to your agent's MCP config:
|
|
|
91
102
|
|
|
92
103
|
## File format
|
|
93
104
|
|
|
94
|
-
Data is stored in `.
|
|
105
|
+
Data is stored in `.toon-memory/memory/data.toon` using TOON (Token-Oriented Object Notation):
|
|
95
106
|
|
|
96
107
|
```
|
|
97
108
|
version: 1
|