formative-memory 0.1.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/LICENSE +21 -0
- package/README.md +246 -0
- package/dist/cli.js +520 -0
- package/dist/db-D2pzT6fw.js +629 -0
- package/dist/index.js +11181 -0
- package/package.json +74 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { n as MemorySourceGuard, r as TemporalStateGuard, t as MemoryDatabase } from "./db-D2pzT6fw.js";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
+
//#region src/cli.ts
|
|
6
|
+
/**
|
|
7
|
+
* Associative Memory CLI
|
|
8
|
+
*
|
|
9
|
+
* Diagnostic tool for inspecting and managing the memory database.
|
|
10
|
+
* Operates directly on SQLite — does not require the OpenClaw runtime.
|
|
11
|
+
*
|
|
12
|
+
* Usage: memory <command> <memory-dir> [options]
|
|
13
|
+
*
|
|
14
|
+
* Commands:
|
|
15
|
+
* stats — Overview of memory database
|
|
16
|
+
* list — List memories (filterable)
|
|
17
|
+
* inspect — Detailed view of a single memory
|
|
18
|
+
* search — Search memories by content
|
|
19
|
+
* export — Export database to JSON
|
|
20
|
+
*/
|
|
21
|
+
function fail(message) {
|
|
22
|
+
throw new CliError(message);
|
|
23
|
+
}
|
|
24
|
+
var CliError = class extends Error {
|
|
25
|
+
constructor(message) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = "CliError";
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
function parsePositiveInt(value, name) {
|
|
31
|
+
const n = Number(value);
|
|
32
|
+
if (!Number.isInteger(n) || n < 1) fail(`Invalid value for ${name}: '${value}'. Expected a positive integer.`);
|
|
33
|
+
return n;
|
|
34
|
+
}
|
|
35
|
+
function parseNonNegativeNumber(value, name) {
|
|
36
|
+
const n = Number(value);
|
|
37
|
+
if (!Number.isFinite(n) || n < 0) fail(`Invalid value for ${name}: '${value}'. Expected a non-negative number.`);
|
|
38
|
+
return n;
|
|
39
|
+
}
|
|
40
|
+
function validateFormat(value) {
|
|
41
|
+
if (value === "json" || value === "text") return value;
|
|
42
|
+
fail(`Invalid format: '${value}'. Expected 'json' or 'text'.`);
|
|
43
|
+
}
|
|
44
|
+
function main() {
|
|
45
|
+
const args = process.argv.slice(2);
|
|
46
|
+
let format = "json";
|
|
47
|
+
const filteredArgs = [];
|
|
48
|
+
for (let i = 0; i < args.length; i++) if (args[i] === "--format" && args[i + 1]) format = validateFormat(args[++i]);
|
|
49
|
+
else if (args[i] === "--text") format = "text";
|
|
50
|
+
else filteredArgs.push(args[i]);
|
|
51
|
+
const [command, memoryDir, ...rest] = filteredArgs;
|
|
52
|
+
if (!command || command === "--help" || command === "-h") {
|
|
53
|
+
printUsage();
|
|
54
|
+
process.exit(0);
|
|
55
|
+
}
|
|
56
|
+
if (!memoryDir) fail("Memory directory path required. Usage: memory <command> <memory-dir> [options]");
|
|
57
|
+
const dbPath = join(memoryDir, "associations.db");
|
|
58
|
+
if (command !== "import" && !existsSync(dbPath)) fail(`Database not found at ${dbPath}`);
|
|
59
|
+
const db = new MemoryDatabase(dbPath);
|
|
60
|
+
try {
|
|
61
|
+
const ctx = {
|
|
62
|
+
db,
|
|
63
|
+
format
|
|
64
|
+
};
|
|
65
|
+
switch (command) {
|
|
66
|
+
case "stats":
|
|
67
|
+
cmdStats(ctx);
|
|
68
|
+
break;
|
|
69
|
+
case "list":
|
|
70
|
+
cmdList(ctx, rest);
|
|
71
|
+
break;
|
|
72
|
+
case "inspect":
|
|
73
|
+
cmdInspect(ctx, rest);
|
|
74
|
+
break;
|
|
75
|
+
case "search":
|
|
76
|
+
cmdSearch(ctx, rest);
|
|
77
|
+
break;
|
|
78
|
+
case "export":
|
|
79
|
+
cmdExport(ctx);
|
|
80
|
+
break;
|
|
81
|
+
case "history":
|
|
82
|
+
cmdHistory(ctx, rest);
|
|
83
|
+
break;
|
|
84
|
+
case "graph":
|
|
85
|
+
cmdGraph(ctx);
|
|
86
|
+
break;
|
|
87
|
+
case "import":
|
|
88
|
+
cmdImport(ctx, rest);
|
|
89
|
+
break;
|
|
90
|
+
default: fail(`Unknown command: ${command}`);
|
|
91
|
+
}
|
|
92
|
+
} finally {
|
|
93
|
+
db.close();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
main();
|
|
98
|
+
} catch (err) {
|
|
99
|
+
if (err instanceof CliError) console.error(`Error: ${err.message}`);
|
|
100
|
+
else console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
function cmdStats(ctx) {
|
|
104
|
+
const { db } = ctx;
|
|
105
|
+
const stats = db.stats();
|
|
106
|
+
const lastConsolidation = db.getState("last_consolidation_at");
|
|
107
|
+
output(ctx, {
|
|
108
|
+
...stats,
|
|
109
|
+
lastConsolidation
|
|
110
|
+
}, () => {
|
|
111
|
+
console.log(`Memories: ${stats.total} (${stats.working} working, ${stats.consolidated} consolidated)`);
|
|
112
|
+
console.log(`Associations: ${stats.associations}`);
|
|
113
|
+
console.log(`Last consolidation: ${lastConsolidation ?? "never"}`);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
function cmdList(ctx, args) {
|
|
117
|
+
const { db } = ctx;
|
|
118
|
+
let type = null;
|
|
119
|
+
let state = null;
|
|
120
|
+
let minStrength = 0;
|
|
121
|
+
let limit = 50;
|
|
122
|
+
for (let i = 0; i < args.length; i++) {
|
|
123
|
+
const arg = args[i];
|
|
124
|
+
if (arg === "--type") type = args[++i] ?? fail("Missing value for --type");
|
|
125
|
+
else if (arg === "--state") state = args[++i] ?? fail("Missing value for --state");
|
|
126
|
+
else if (arg === "--min-strength") minStrength = parseNonNegativeNumber(args[++i] ?? "", "--min-strength");
|
|
127
|
+
else if (arg === "--limit") limit = parsePositiveInt(args[++i] ?? "", "--limit");
|
|
128
|
+
else if (arg.startsWith("--")) fail(`Unknown option for list: ${arg}`);
|
|
129
|
+
}
|
|
130
|
+
let memories = db.getAllMemories();
|
|
131
|
+
if (type) memories = memories.filter((m) => m.type === type);
|
|
132
|
+
if (state) memories = memories.filter((m) => m.temporal_state === state);
|
|
133
|
+
if (minStrength > 0) memories = memories.filter((m) => m.strength >= minStrength);
|
|
134
|
+
memories = memories.slice(0, limit);
|
|
135
|
+
const result = memories.map((m) => ({
|
|
136
|
+
id: m.id,
|
|
137
|
+
id_short: m.id.slice(0, 8),
|
|
138
|
+
type: m.type,
|
|
139
|
+
content: m.content.length > 100 ? m.content.slice(0, 97) + "..." : m.content,
|
|
140
|
+
strength: Math.round(m.strength * 1e3) / 1e3,
|
|
141
|
+
temporal_state: m.temporal_state,
|
|
142
|
+
consolidated: m.consolidated === 1,
|
|
143
|
+
source: m.source,
|
|
144
|
+
created_at: m.created_at
|
|
145
|
+
}));
|
|
146
|
+
output(ctx, result, () => {
|
|
147
|
+
if (result.length === 0) {
|
|
148
|
+
console.log("No memories found.");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
for (const m of result) {
|
|
152
|
+
const flag = m.consolidated ? "C" : "W";
|
|
153
|
+
console.log(`[${m.id_short}] [${flag}] str=${m.strength} type=${m.type} ${m.content}`);
|
|
154
|
+
}
|
|
155
|
+
console.log(`\n${result.length} memories shown.`);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
/** Resolve a memory by exact ID or unique prefix. Fails on ambiguous or missing. */
|
|
159
|
+
function resolveMemory(db, idOrPrefix) {
|
|
160
|
+
const exact = db.getMemory(idOrPrefix);
|
|
161
|
+
if (exact) return exact;
|
|
162
|
+
const matches = db.getAllMemories().filter((m) => m.id.startsWith(idOrPrefix));
|
|
163
|
+
if (matches.length === 0) fail(`Memory not found: ${idOrPrefix}`);
|
|
164
|
+
if (matches.length > 1) fail(`Ambiguous prefix '${idOrPrefix}'. Matches: ${matches.slice(0, 5).map((m) => m.id.slice(0, 12)).join(", ")}${matches.length > 5 ? `, ... (${matches.length} total)` : ""}`);
|
|
165
|
+
return matches[0];
|
|
166
|
+
}
|
|
167
|
+
function cmdInspect(ctx, args) {
|
|
168
|
+
const { db } = ctx;
|
|
169
|
+
const id = args[0];
|
|
170
|
+
if (!id) fail("Memory ID required for inspect");
|
|
171
|
+
const row = resolveMemory(db, id);
|
|
172
|
+
const associations = db.getAssociations(row.id).map((a) => ({
|
|
173
|
+
neighbor: a.memory_a === row.id ? a.memory_b : a.memory_a,
|
|
174
|
+
weight: Math.round(a.weight * 1e3) / 1e3
|
|
175
|
+
}));
|
|
176
|
+
const attributions = db.getAttributionsByMemory(row.id).map((a) => ({
|
|
177
|
+
message_id: a.message_id,
|
|
178
|
+
evidence: a.evidence,
|
|
179
|
+
confidence: a.confidence,
|
|
180
|
+
turn_id: a.turn_id,
|
|
181
|
+
reinforcement_applied: a.reinforcement_applied === 1
|
|
182
|
+
}));
|
|
183
|
+
const exposures = db.getExposuresByMemory(row.id).map((e) => ({
|
|
184
|
+
turn_id: e.turn_id,
|
|
185
|
+
mode: e.mode,
|
|
186
|
+
score: e.score,
|
|
187
|
+
retrieval_mode: e.retrieval_mode
|
|
188
|
+
}));
|
|
189
|
+
const alias = db.getAlias(row.id);
|
|
190
|
+
const canonical = alias ? db.resolveAlias(row.id) : null;
|
|
191
|
+
const result = {
|
|
192
|
+
id: row.id,
|
|
193
|
+
id_short: row.id.slice(0, 8),
|
|
194
|
+
type: row.type,
|
|
195
|
+
content: row.content,
|
|
196
|
+
strength: row.strength,
|
|
197
|
+
temporal_state: row.temporal_state,
|
|
198
|
+
temporal_anchor: row.temporal_anchor,
|
|
199
|
+
consolidated: row.consolidated === 1,
|
|
200
|
+
source: row.source,
|
|
201
|
+
created_at: row.created_at,
|
|
202
|
+
alias: alias ? {
|
|
203
|
+
points_to: alias,
|
|
204
|
+
canonical
|
|
205
|
+
} : null,
|
|
206
|
+
associations,
|
|
207
|
+
attributions,
|
|
208
|
+
exposures
|
|
209
|
+
};
|
|
210
|
+
output(ctx, result, () => {
|
|
211
|
+
console.log(`ID: ${result.id}`);
|
|
212
|
+
console.log(`Type: ${result.type}`);
|
|
213
|
+
console.log(`Strength: ${result.strength}`);
|
|
214
|
+
console.log(`State: ${result.temporal_state}${result.temporal_anchor ? ` (anchor: ${result.temporal_anchor})` : ""}`);
|
|
215
|
+
console.log(`Source: ${result.source} | ${result.consolidated ? "Consolidated" : "Working"}`);
|
|
216
|
+
console.log(`Created: ${result.created_at}`);
|
|
217
|
+
if (result.alias) console.log(`Alias: → ${result.alias.canonical}`);
|
|
218
|
+
console.log(`\nContent:\n${result.content}`);
|
|
219
|
+
if (associations.length > 0) {
|
|
220
|
+
console.log(`\nAssociations (${associations.length}):`);
|
|
221
|
+
for (const a of associations) console.log(` ${a.neighbor.slice(0, 8)} weight=${a.weight}`);
|
|
222
|
+
}
|
|
223
|
+
if (attributions.length > 0) {
|
|
224
|
+
console.log(`\nAttributions (${attributions.length}):`);
|
|
225
|
+
for (const a of attributions) console.log(` ${a.evidence} conf=${a.confidence} turn=${a.turn_id.slice(0, 20)} reinforced=${a.reinforcement_applied}`);
|
|
226
|
+
}
|
|
227
|
+
if (exposures.length > 0) {
|
|
228
|
+
console.log(`\nExposures (${exposures.length}):`);
|
|
229
|
+
for (const e of exposures) console.log(` ${e.mode} score=${e.score} mode=${e.retrieval_mode}`);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
function cmdSearch(ctx, args) {
|
|
234
|
+
const { db } = ctx;
|
|
235
|
+
const query = args.join(" ");
|
|
236
|
+
if (!query) {
|
|
237
|
+
console.error("Error: search query required");
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
const result = db.searchFts(query, 20).map((r) => {
|
|
241
|
+
const mem = db.getMemory(r.id);
|
|
242
|
+
return {
|
|
243
|
+
id: r.id,
|
|
244
|
+
id_short: r.id.slice(0, 8),
|
|
245
|
+
type: mem?.type ?? "unknown",
|
|
246
|
+
content: mem?.content ?? "(not found)",
|
|
247
|
+
strength: mem?.strength ?? 0,
|
|
248
|
+
fts_rank: r.rank
|
|
249
|
+
};
|
|
250
|
+
});
|
|
251
|
+
output(ctx, result, () => {
|
|
252
|
+
if (result.length === 0) {
|
|
253
|
+
console.log("No results found.");
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
for (const r of result) {
|
|
257
|
+
const content = r.content.length > 80 ? r.content.slice(0, 77) + "..." : r.content;
|
|
258
|
+
console.log(`[${r.id_short}] str=${r.strength} rank=${Math.round(r.fts_rank * 100) / 100} ${content}`);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
function cmdExport(ctx) {
|
|
263
|
+
const { db } = ctx;
|
|
264
|
+
const memories = db.getAllMemories().map((m) => ({
|
|
265
|
+
id: m.id,
|
|
266
|
+
type: m.type,
|
|
267
|
+
content: m.content,
|
|
268
|
+
strength: m.strength,
|
|
269
|
+
temporal_state: m.temporal_state,
|
|
270
|
+
temporal_anchor: m.temporal_anchor,
|
|
271
|
+
consolidated: m.consolidated === 1,
|
|
272
|
+
source: m.source,
|
|
273
|
+
created_at: m.created_at
|
|
274
|
+
}));
|
|
275
|
+
const associations = db.getAllAssociations().map((a) => ({
|
|
276
|
+
memory_a: a.memory_a,
|
|
277
|
+
memory_b: a.memory_b,
|
|
278
|
+
weight: a.weight,
|
|
279
|
+
created_at: a.created_at,
|
|
280
|
+
last_updated_at: a.last_updated_at
|
|
281
|
+
}));
|
|
282
|
+
const attributions = db.getAllAttributions();
|
|
283
|
+
const exposures = db.getAllExposures();
|
|
284
|
+
const aliases = db.getAllAliases();
|
|
285
|
+
const state = db.getAllState();
|
|
286
|
+
const exportData = {
|
|
287
|
+
version: 2,
|
|
288
|
+
exported_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
289
|
+
memories,
|
|
290
|
+
associations,
|
|
291
|
+
attributions,
|
|
292
|
+
exposures,
|
|
293
|
+
aliases,
|
|
294
|
+
state
|
|
295
|
+
};
|
|
296
|
+
console.log(JSON.stringify(exportData, null, 2));
|
|
297
|
+
}
|
|
298
|
+
function cmdHistory(ctx, args) {
|
|
299
|
+
const { db } = ctx;
|
|
300
|
+
const id = args[0];
|
|
301
|
+
if (!id) fail("Memory ID required for history");
|
|
302
|
+
const row = resolveMemory(db, id);
|
|
303
|
+
const canonical = db.resolveAlias(row.id);
|
|
304
|
+
const isAliased = canonical !== row.id;
|
|
305
|
+
const predecessors = db.getAliasedIdsPointingTo(row.id);
|
|
306
|
+
const attributions = db.getAttributionsByMemory(row.id);
|
|
307
|
+
const exposures = db.getExposuresByMemory(row.id);
|
|
308
|
+
const result = {
|
|
309
|
+
id: row.id,
|
|
310
|
+
id_short: row.id.slice(0, 8),
|
|
311
|
+
source: row.source,
|
|
312
|
+
created_at: row.created_at,
|
|
313
|
+
current_strength: row.strength,
|
|
314
|
+
consolidated: row.consolidated === 1,
|
|
315
|
+
alias: isAliased ? {
|
|
316
|
+
canonical,
|
|
317
|
+
note: "This memory was merged into another"
|
|
318
|
+
} : null,
|
|
319
|
+
predecessors: predecessors.length > 0 ? predecessors : null,
|
|
320
|
+
attribution_count: attributions.length,
|
|
321
|
+
exposure_count: exposures.length,
|
|
322
|
+
timeline: [
|
|
323
|
+
{
|
|
324
|
+
event: "created",
|
|
325
|
+
at: row.created_at,
|
|
326
|
+
detail: `source=${row.source}, type=${row.type}`
|
|
327
|
+
},
|
|
328
|
+
...attributions.map((a) => ({
|
|
329
|
+
event: "attributed",
|
|
330
|
+
at: a.created_at,
|
|
331
|
+
detail: `${a.evidence} conf=${a.confidence} msg=${a.message_id.slice(0, 20)}`
|
|
332
|
+
})),
|
|
333
|
+
...exposures.map((e) => ({
|
|
334
|
+
event: "exposed",
|
|
335
|
+
at: e.created_at,
|
|
336
|
+
detail: `${e.mode} turn=${e.turn_id.slice(0, 20)}`
|
|
337
|
+
}))
|
|
338
|
+
].sort((a, b) => a.at.localeCompare(b.at))
|
|
339
|
+
};
|
|
340
|
+
output(ctx, result, () => {
|
|
341
|
+
console.log(`History for ${result.id_short} (${row.type})`);
|
|
342
|
+
console.log(`Source: ${result.source} | Created: ${result.created_at}`);
|
|
343
|
+
console.log(`Current strength: ${result.current_strength}`);
|
|
344
|
+
if (result.alias) console.log(`Merged into: ${result.alias.canonical.slice(0, 8)}`);
|
|
345
|
+
if (result.predecessors) console.log(`Predecessors: ${result.predecessors.map((p) => p.slice(0, 8)).join(", ")}`);
|
|
346
|
+
console.log(`\nTimeline (${result.timeline.length} events):`);
|
|
347
|
+
for (const ev of result.timeline) console.log(` ${ev.at} ${ev.event}: ${ev.detail}`);
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
function cmdGraph(ctx) {
|
|
351
|
+
const { db } = ctx;
|
|
352
|
+
const memories = db.getAllMemories();
|
|
353
|
+
const edges = [];
|
|
354
|
+
const seen = /* @__PURE__ */ new Set();
|
|
355
|
+
for (const mem of memories) for (const assoc of db.getAssociations(mem.id)) {
|
|
356
|
+
const key = `${assoc.memory_a}:${assoc.memory_b}`;
|
|
357
|
+
if (!seen.has(key)) {
|
|
358
|
+
seen.add(key);
|
|
359
|
+
edges.push({
|
|
360
|
+
a: assoc.memory_a,
|
|
361
|
+
b: assoc.memory_b,
|
|
362
|
+
weight: assoc.weight
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (ctx.format === "text") {
|
|
367
|
+
console.log("graph associations {");
|
|
368
|
+
console.log(" node [shape=box, style=rounded];");
|
|
369
|
+
for (const mem of memories) {
|
|
370
|
+
const label = mem.content.length > 30 ? mem.content.slice(0, 27) + "..." : mem.content;
|
|
371
|
+
const flag = mem.consolidated === 1 ? "C" : "W";
|
|
372
|
+
console.log(` "${mem.id.slice(0, 8)}" [label="${mem.id.slice(0, 8)}\\n${flag} str=${mem.strength.toFixed(2)}\\n${escapeDot(label)}"];`);
|
|
373
|
+
}
|
|
374
|
+
for (const e of edges) console.log(` "${e.a.slice(0, 8)}" -- "${e.b.slice(0, 8)}" [label="${e.weight.toFixed(2)}"];`);
|
|
375
|
+
console.log("}");
|
|
376
|
+
} else {
|
|
377
|
+
const result = {
|
|
378
|
+
nodes: memories.map((m) => ({
|
|
379
|
+
id: m.id,
|
|
380
|
+
id_short: m.id.slice(0, 8),
|
|
381
|
+
type: m.type,
|
|
382
|
+
strength: m.strength,
|
|
383
|
+
consolidated: m.consolidated === 1
|
|
384
|
+
})),
|
|
385
|
+
edges: edges.map((e) => ({
|
|
386
|
+
a: e.a.slice(0, 8),
|
|
387
|
+
b: e.b.slice(0, 8),
|
|
388
|
+
weight: Math.round(e.weight * 1e3) / 1e3
|
|
389
|
+
}))
|
|
390
|
+
};
|
|
391
|
+
console.log(JSON.stringify(result, null, 2));
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
function cmdImport(ctx, args) {
|
|
395
|
+
const { db } = ctx;
|
|
396
|
+
const filePath = args[0];
|
|
397
|
+
if (!filePath) fail("Import file path required");
|
|
398
|
+
if (!existsSync(filePath)) fail(`File not found: ${filePath}`);
|
|
399
|
+
let data;
|
|
400
|
+
try {
|
|
401
|
+
data = JSON.parse(readFileSync(filePath, "utf8"));
|
|
402
|
+
} catch (err) {
|
|
403
|
+
fail(`Failed to parse import file: ${err instanceof Error ? err.message : String(err)}`);
|
|
404
|
+
}
|
|
405
|
+
if (typeof data !== "object" || data === null) fail("Import file must contain a JSON object");
|
|
406
|
+
const version = data.version;
|
|
407
|
+
if (version !== 1 && version !== 2) fail(`Unsupported export version: ${version}. Expected 1 or 2.`);
|
|
408
|
+
const counts = {
|
|
409
|
+
memories: 0,
|
|
410
|
+
memoriesSkipped: 0,
|
|
411
|
+
associations: 0,
|
|
412
|
+
attributions: 0,
|
|
413
|
+
exposures: 0,
|
|
414
|
+
aliases: 0,
|
|
415
|
+
state: 0
|
|
416
|
+
};
|
|
417
|
+
db.transaction(() => {
|
|
418
|
+
const memories = data.memories;
|
|
419
|
+
if (memories) for (const m of memories) {
|
|
420
|
+
if (db.getMemory(m.id)) {
|
|
421
|
+
counts.memoriesSkipped++;
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
db.insertMemory({
|
|
425
|
+
id: m.id,
|
|
426
|
+
type: m.type,
|
|
427
|
+
content: m.content,
|
|
428
|
+
strength: m.strength,
|
|
429
|
+
temporal_state: TemporalStateGuard.is(m.temporal_state) ? m.temporal_state : "none",
|
|
430
|
+
temporal_anchor: m.temporal_anchor ?? null,
|
|
431
|
+
consolidated: m.consolidated ?? false,
|
|
432
|
+
source: MemorySourceGuard.is(m.source) ? m.source : "agent_tool",
|
|
433
|
+
created_at: m.created_at
|
|
434
|
+
});
|
|
435
|
+
db.insertFts(m.id, m.content, m.type);
|
|
436
|
+
counts.memories++;
|
|
437
|
+
}
|
|
438
|
+
const associations = data.associations;
|
|
439
|
+
if (associations) for (const a of associations) {
|
|
440
|
+
const memA = a.memory_a ?? a.a;
|
|
441
|
+
const memB = a.memory_b ?? a.b;
|
|
442
|
+
const createdAt = a.created_at ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
443
|
+
db.upsertAssociation(memA, memB, a.weight, createdAt);
|
|
444
|
+
counts.associations++;
|
|
445
|
+
}
|
|
446
|
+
const attributions = data.attributions;
|
|
447
|
+
if (attributions) for (const a of attributions) {
|
|
448
|
+
db.insertAttributionRaw({
|
|
449
|
+
message_id: a.message_id,
|
|
450
|
+
memory_id: a.memory_id,
|
|
451
|
+
evidence: a.evidence,
|
|
452
|
+
confidence: a.confidence,
|
|
453
|
+
turn_id: a.turn_id,
|
|
454
|
+
created_at: a.created_at,
|
|
455
|
+
updated_at: a.updated_at ?? null,
|
|
456
|
+
reinforcement_applied: a.reinforcement_applied ?? 0
|
|
457
|
+
});
|
|
458
|
+
counts.attributions++;
|
|
459
|
+
}
|
|
460
|
+
const exposures = data.exposures;
|
|
461
|
+
if (exposures) for (const e of exposures) {
|
|
462
|
+
db.insertExposureRaw(e);
|
|
463
|
+
counts.exposures++;
|
|
464
|
+
}
|
|
465
|
+
const aliases = data.aliases;
|
|
466
|
+
if (aliases) for (const a of aliases) {
|
|
467
|
+
db.insertAlias(a.old_id, a.new_id, a.reason, a.created_at);
|
|
468
|
+
counts.aliases++;
|
|
469
|
+
}
|
|
470
|
+
const stateEntries = data.state;
|
|
471
|
+
if (stateEntries) for (const s of stateEntries) {
|
|
472
|
+
db.setState(s.key, s.value);
|
|
473
|
+
counts.state++;
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
output(ctx, counts, () => {
|
|
477
|
+
console.log(`Imported: ${counts.memories} memories, ${counts.associations} associations`);
|
|
478
|
+
if (counts.attributions > 0) console.log(` ${counts.attributions} attributions`);
|
|
479
|
+
if (counts.exposures > 0) console.log(` ${counts.exposures} exposures`);
|
|
480
|
+
if (counts.aliases > 0) console.log(` ${counts.aliases} aliases`);
|
|
481
|
+
if (counts.state > 0) console.log(` ${counts.state} state entries`);
|
|
482
|
+
if (counts.memoriesSkipped > 0) console.log(`Skipped: ${counts.memoriesSkipped} memories (already exist)`);
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
function escapeDot(s) {
|
|
486
|
+
return s.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/\r/g, "").replace(/"/g, "\\\"");
|
|
487
|
+
}
|
|
488
|
+
function output(ctx, data, textFn) {
|
|
489
|
+
if (ctx.format === "text") textFn();
|
|
490
|
+
else console.log(JSON.stringify(data, null, 2));
|
|
491
|
+
}
|
|
492
|
+
function printUsage() {
|
|
493
|
+
console.log(`
|
|
494
|
+
Associative Memory CLI
|
|
495
|
+
|
|
496
|
+
Usage: memory <command> <memory-dir> [options]
|
|
497
|
+
|
|
498
|
+
Commands:
|
|
499
|
+
stats Overview of memory database
|
|
500
|
+
list List memories
|
|
501
|
+
inspect <id> Detailed view of a single memory
|
|
502
|
+
search <query> Search memories by content
|
|
503
|
+
history <id> Timeline of a memory's lifecycle
|
|
504
|
+
graph Association graph (DOT format in text mode)
|
|
505
|
+
export Export database to JSON
|
|
506
|
+
import <file> Import memories from JSON export file
|
|
507
|
+
|
|
508
|
+
Options:
|
|
509
|
+
--format json|text Output format (default: json)
|
|
510
|
+
--text Shorthand for --format text
|
|
511
|
+
|
|
512
|
+
List filters:
|
|
513
|
+
--type <type> Filter by memory type
|
|
514
|
+
--state <state> Filter by temporal state
|
|
515
|
+
--min-strength <n> Minimum strength threshold
|
|
516
|
+
--limit <n> Max results (default: 50)
|
|
517
|
+
`.trim());
|
|
518
|
+
}
|
|
519
|
+
//#endregion
|
|
520
|
+
export {};
|