@cogmem/engram 0.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 +236 -0
- package/drizzle/0000_jittery_ender_wiggin.sql +59 -0
- package/drizzle/meta/0000_snapshot.json +417 -0
- package/drizzle/meta/_journal.json +13 -0
- package/package.json +72 -0
- package/src/cli/commands/encode.ts +71 -0
- package/src/cli/commands/focus.ts +106 -0
- package/src/cli/commands/health.ts +82 -0
- package/src/cli/commands/inspect.ts +36 -0
- package/src/cli/commands/list.ts +58 -0
- package/src/cli/commands/recall.ts +55 -0
- package/src/cli/commands/sleep.ts +102 -0
- package/src/cli/commands/stats.ts +95 -0
- package/src/cli/format.ts +231 -0
- package/src/cli/index.ts +31 -0
- package/src/config/defaults.ts +58 -0
- package/src/core/activation.ts +75 -0
- package/src/core/associations.ts +186 -0
- package/src/core/chunking.ts +108 -0
- package/src/core/consolidation.ts +150 -0
- package/src/core/emotional-tag.ts +19 -0
- package/src/core/encoder.ts +47 -0
- package/src/core/engine.ts +50 -0
- package/src/core/forgetting.ts +58 -0
- package/src/core/memory.ts +94 -0
- package/src/core/procedural-store.ts +36 -0
- package/src/core/recall.ts +102 -0
- package/src/core/reconsolidation.ts +42 -0
- package/src/core/search.ts +24 -0
- package/src/core/working-memory.ts +67 -0
- package/src/index.ts +57 -0
- package/src/mcp/server.ts +122 -0
- package/src/mcp/tools.ts +334 -0
- package/src/storage/schema.ts +97 -0
- package/src/storage/sqlite.ts +402 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
import { EngramEngine } from "../../core/engine.ts";
|
|
3
|
+
import { focusUtilization } from "../../core/working-memory.ts";
|
|
4
|
+
import { refreshActivations } from "../../core/forgetting.ts";
|
|
5
|
+
import { bold, dim, green, yellow, red, isInteractive } from "../format.ts";
|
|
6
|
+
|
|
7
|
+
export const healthCommand = defineCommand({
|
|
8
|
+
meta: {
|
|
9
|
+
name: "health",
|
|
10
|
+
description: "Brain health check — diagnose memory system issues",
|
|
11
|
+
},
|
|
12
|
+
run() {
|
|
13
|
+
const engine = EngramEngine.create();
|
|
14
|
+
try {
|
|
15
|
+
const { atRisk } = refreshActivations(engine.storage, engine.config);
|
|
16
|
+
const { used, capacity } = focusUtilization(engine.storage, engine.config);
|
|
17
|
+
const lastConsolidation = engine.storage.getLastConsolidation();
|
|
18
|
+
const totalMemories = engine.storage.getMemoryCount();
|
|
19
|
+
const associationCount = engine.storage.getAssociationCount();
|
|
20
|
+
|
|
21
|
+
if (!isInteractive()) {
|
|
22
|
+
const hoursAgo = lastConsolidation
|
|
23
|
+
? (Date.now() - lastConsolidation.ranAt) / 3600000
|
|
24
|
+
: null;
|
|
25
|
+
console.log(
|
|
26
|
+
JSON.stringify({
|
|
27
|
+
atRisk,
|
|
28
|
+
workingMemory: { used, capacity },
|
|
29
|
+
totalMemories,
|
|
30
|
+
associations: associationCount,
|
|
31
|
+
lastConsolidationHoursAgo: hoursAgo ? Math.round(hoursAgo) : null,
|
|
32
|
+
}),
|
|
33
|
+
);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log(bold(" engram — health check\n"));
|
|
38
|
+
|
|
39
|
+
if (atRisk > 0) {
|
|
40
|
+
console.log(yellow(` ! ${atRisk} memories at risk of being forgotten`));
|
|
41
|
+
} else {
|
|
42
|
+
console.log(green(" + All memories above retrieval threshold"));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (used <= capacity) {
|
|
46
|
+
console.log(green(` + Working memory within capacity (${used}/${capacity})`));
|
|
47
|
+
} else {
|
|
48
|
+
console.log(red(` ! Working memory over capacity (${used}/${capacity})`));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (lastConsolidation) {
|
|
52
|
+
const hoursAgo = (Date.now() - lastConsolidation.ranAt) / 3600000;
|
|
53
|
+
if (hoursAgo > 18) {
|
|
54
|
+
console.log(red(` ! Consolidation overdue (last: ${Math.round(hoursAgo)}h ago)`));
|
|
55
|
+
} else if (hoursAgo > 8) {
|
|
56
|
+
console.log(
|
|
57
|
+
yellow(` ~ Consolidation recommended soon (last: ${Math.round(hoursAgo)}h ago)`),
|
|
58
|
+
);
|
|
59
|
+
} else {
|
|
60
|
+
console.log(green(` + Consolidation recent (last: ${Math.round(hoursAgo)}h ago)`));
|
|
61
|
+
}
|
|
62
|
+
} else if (totalMemories > 0) {
|
|
63
|
+
console.log(yellow(" ! No consolidation has ever run — run `engram sleep`"));
|
|
64
|
+
} else {
|
|
65
|
+
console.log(dim(" ~ No memories encoded yet"));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (totalMemories > 5 && associationCount === 0) {
|
|
69
|
+
console.log(yellow(" ! No associations formed — run `engram sleep` to discover links"));
|
|
70
|
+
} else if (associationCount > 0) {
|
|
71
|
+
const ratio = associationCount / Math.max(1, totalMemories);
|
|
72
|
+
if (ratio > 1) {
|
|
73
|
+
console.log(green(` + Association network is rich (${associationCount} links)`));
|
|
74
|
+
} else {
|
|
75
|
+
console.log(green(` + Association network is healthy (${associationCount} links)`));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} finally {
|
|
79
|
+
engine.close();
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
import { EngramEngine } from "../../core/engine.ts";
|
|
3
|
+
import { formatMemoryInspection, dim } from "../format.ts";
|
|
4
|
+
|
|
5
|
+
export const inspectCommand = defineCommand({
|
|
6
|
+
meta: {
|
|
7
|
+
name: "inspect",
|
|
8
|
+
description: "Examine a memory's full lifecycle",
|
|
9
|
+
},
|
|
10
|
+
args: {
|
|
11
|
+
id: {
|
|
12
|
+
type: "positional",
|
|
13
|
+
description: "Memory ID (or prefix) to inspect",
|
|
14
|
+
required: true,
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
run({ args }) {
|
|
18
|
+
const engine = EngramEngine.create();
|
|
19
|
+
try {
|
|
20
|
+
const allMemories = engine.storage.getAllMemories();
|
|
21
|
+
const match = allMemories.find((m) => m.id === args.id || m.id.startsWith(args.id));
|
|
22
|
+
|
|
23
|
+
if (!match) {
|
|
24
|
+
console.log(dim(` No memory found matching "${args.id}"`));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const accessLog = engine.storage.getAccessLog(match.id);
|
|
29
|
+
const associations = engine.storage.getAssociations(match.id);
|
|
30
|
+
|
|
31
|
+
console.log(formatMemoryInspection(match, accessLog.length, associations.length));
|
|
32
|
+
} finally {
|
|
33
|
+
engine.close();
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
import { EngramEngine } from "../../core/engine.ts";
|
|
3
|
+
import { isValidMemoryType } from "../../core/memory.ts";
|
|
4
|
+
import { formatMemoryList } from "../format.ts";
|
|
5
|
+
|
|
6
|
+
export const listCommand = defineCommand({
|
|
7
|
+
meta: {
|
|
8
|
+
name: "list",
|
|
9
|
+
description: "Browse stored memories (read-only, no activation effects)",
|
|
10
|
+
},
|
|
11
|
+
args: {
|
|
12
|
+
type: {
|
|
13
|
+
type: "string",
|
|
14
|
+
description: "Filter by memory type (episodic, semantic, procedural)",
|
|
15
|
+
},
|
|
16
|
+
context: {
|
|
17
|
+
type: "string",
|
|
18
|
+
description: "Filter by context prefix (e.g. project:acme)",
|
|
19
|
+
},
|
|
20
|
+
all: {
|
|
21
|
+
type: "boolean",
|
|
22
|
+
description: "Show all memories regardless of context",
|
|
23
|
+
default: false,
|
|
24
|
+
},
|
|
25
|
+
limit: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "Maximum number of results",
|
|
28
|
+
alias: "n",
|
|
29
|
+
default: "20",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
run({ args }) {
|
|
33
|
+
const engine = EngramEngine.create();
|
|
34
|
+
try {
|
|
35
|
+
const type = args.type && isValidMemoryType(args.type) ? args.type : undefined;
|
|
36
|
+
const limit = Number(args.limit);
|
|
37
|
+
|
|
38
|
+
let memories;
|
|
39
|
+
let displayContext: string | null = null;
|
|
40
|
+
|
|
41
|
+
if (args.all) {
|
|
42
|
+
memories = engine.storage.getAllMemories(type).slice(0, limit);
|
|
43
|
+
} else {
|
|
44
|
+
const context = args.context ?? engine.projectContext;
|
|
45
|
+
if (context) {
|
|
46
|
+
displayContext = context;
|
|
47
|
+
memories = engine.storage.getMemoriesByContext(context, type, limit);
|
|
48
|
+
} else {
|
|
49
|
+
memories = engine.storage.getAllMemories(type).slice(0, limit);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
console.log(formatMemoryList(memories, displayContext));
|
|
54
|
+
} finally {
|
|
55
|
+
engine.close();
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
import { EngramEngine } from "../../core/engine.ts";
|
|
3
|
+
import { recall } from "../../core/recall.ts";
|
|
4
|
+
import { formatRecallResults } from "../format.ts";
|
|
5
|
+
import { isValidMemoryType } from "../../core/memory.ts";
|
|
6
|
+
|
|
7
|
+
export const recallCommand = defineCommand({
|
|
8
|
+
meta: {
|
|
9
|
+
name: "recall",
|
|
10
|
+
description: "Retrieve memories by cue (associative recall)",
|
|
11
|
+
},
|
|
12
|
+
args: {
|
|
13
|
+
cue: {
|
|
14
|
+
type: "positional",
|
|
15
|
+
description: "The recall cue — what you're trying to remember",
|
|
16
|
+
required: true,
|
|
17
|
+
},
|
|
18
|
+
type: {
|
|
19
|
+
type: "string",
|
|
20
|
+
description: "Filter by memory type (episodic, semantic, procedural)",
|
|
21
|
+
},
|
|
22
|
+
context: {
|
|
23
|
+
type: "string",
|
|
24
|
+
description: "Filter by context tag",
|
|
25
|
+
},
|
|
26
|
+
limit: {
|
|
27
|
+
type: "string",
|
|
28
|
+
description: "Maximum number of results",
|
|
29
|
+
alias: "n",
|
|
30
|
+
default: "10",
|
|
31
|
+
},
|
|
32
|
+
noAssociative: {
|
|
33
|
+
type: "boolean",
|
|
34
|
+
description: "Disable spreading activation",
|
|
35
|
+
default: false,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
run({ args }) {
|
|
39
|
+
const engine = EngramEngine.create();
|
|
40
|
+
try {
|
|
41
|
+
const typeFilter = args.type && isValidMemoryType(args.type) ? args.type : undefined;
|
|
42
|
+
|
|
43
|
+
const results = recall(engine.storage, args.cue, engine.config, {
|
|
44
|
+
type: typeFilter,
|
|
45
|
+
context: args.context ?? engine.projectContext ?? undefined,
|
|
46
|
+
limit: Number(args.limit),
|
|
47
|
+
associative: !args.noAssociative,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
console.log(formatRecallResults(results));
|
|
51
|
+
} finally {
|
|
52
|
+
engine.close();
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
import { EngramEngine } from "../../core/engine.ts";
|
|
3
|
+
import { consolidate } from "../../core/consolidation.ts";
|
|
4
|
+
import { discoverChunks } from "../../core/chunking.ts";
|
|
5
|
+
import { bold, dim, green, cyan, isInteractive } from "../format.ts";
|
|
6
|
+
|
|
7
|
+
export const sleepCommand = defineCommand({
|
|
8
|
+
meta: {
|
|
9
|
+
name: "sleep",
|
|
10
|
+
description: "Run consolidation cycle (replay, strengthen, prune, extract, link)",
|
|
11
|
+
},
|
|
12
|
+
args: {
|
|
13
|
+
report: {
|
|
14
|
+
type: "boolean",
|
|
15
|
+
description: "Show detailed consolidation report",
|
|
16
|
+
default: false,
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
run({ args }) {
|
|
20
|
+
const engine = EngramEngine.create();
|
|
21
|
+
try {
|
|
22
|
+
const result = consolidate(engine.storage, engine.config);
|
|
23
|
+
const chunks = discoverChunks(engine.storage, engine.config);
|
|
24
|
+
|
|
25
|
+
if (!isInteractive()) {
|
|
26
|
+
console.log(
|
|
27
|
+
JSON.stringify({
|
|
28
|
+
memoriesStrengthened: result.memoriesStrengthened,
|
|
29
|
+
memoriesPruned: result.memoriesPruned,
|
|
30
|
+
factsExtracted: result.factsExtracted,
|
|
31
|
+
associationsDiscovered: result.associationsDiscovered,
|
|
32
|
+
chunksFormed: chunks.length,
|
|
33
|
+
...(args.report
|
|
34
|
+
? {
|
|
35
|
+
extractedFacts: result.extractedFacts,
|
|
36
|
+
prunedIds: result.prunedIds,
|
|
37
|
+
chunks: chunks.map((c) => ({
|
|
38
|
+
id: c.id,
|
|
39
|
+
label: c.label,
|
|
40
|
+
members: c.memberIds.length,
|
|
41
|
+
})),
|
|
42
|
+
}
|
|
43
|
+
: {}),
|
|
44
|
+
}),
|
|
45
|
+
);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log(dim(" Running consolidation cycle...\n"));
|
|
50
|
+
console.log(green(" Consolidation complete:\n"));
|
|
51
|
+
console.log(
|
|
52
|
+
` ${cyan("Strengthened")} ${result.memoriesStrengthened} frequently-accessed memories`,
|
|
53
|
+
);
|
|
54
|
+
console.log(
|
|
55
|
+
` ${cyan("Pruned")} ${result.memoriesPruned} memories below activation threshold`,
|
|
56
|
+
);
|
|
57
|
+
console.log(
|
|
58
|
+
` ${cyan("Extracted")} ${result.factsExtracted} semantic facts from episodic patterns`,
|
|
59
|
+
);
|
|
60
|
+
console.log(` ${cyan("Discovered")} ${result.associationsDiscovered} new associations`);
|
|
61
|
+
if (chunks.length > 0) {
|
|
62
|
+
console.log(` ${cyan("Chunked")} ${chunks.length} new memory groups`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (args.report) {
|
|
66
|
+
console.log(dim("\n ─── Detailed Report ───────────────────────\n"));
|
|
67
|
+
|
|
68
|
+
if (result.extractedFacts.length > 0) {
|
|
69
|
+
console.log(bold(" Extracted Facts:"));
|
|
70
|
+
for (const fact of result.extractedFacts) {
|
|
71
|
+
console.log(` ${dim(">")} ${fact}`);
|
|
72
|
+
}
|
|
73
|
+
console.log("");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (result.prunedIds.length > 0) {
|
|
77
|
+
console.log(bold(" Pruned Memories:"));
|
|
78
|
+
for (const id of result.prunedIds) {
|
|
79
|
+
console.log(` ${dim(">")} ${id.slice(0, 8)}`);
|
|
80
|
+
}
|
|
81
|
+
console.log("");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (chunks.length > 0) {
|
|
85
|
+
console.log(bold(" New Chunks:"));
|
|
86
|
+
for (const chunk of chunks) {
|
|
87
|
+
console.log(` ${dim(">")} ${chunk.label} (${chunk.memberIds.length} memories)`);
|
|
88
|
+
}
|
|
89
|
+
console.log("");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (result.discoveredAssociationPairs.length > 0) {
|
|
93
|
+
console.log(
|
|
94
|
+
bold(` Associations Discovered: ${result.discoveredAssociationPairs.length}`),
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} finally {
|
|
99
|
+
engine.close();
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
import { EngramEngine } from "../../core/engine.ts";
|
|
3
|
+
import { focusUtilization } from "../../core/working-memory.ts";
|
|
4
|
+
import { bold, dim, green, yellow, red, isInteractive } from "../format.ts";
|
|
5
|
+
import { refreshActivations } from "../../core/forgetting.ts";
|
|
6
|
+
|
|
7
|
+
export const statsCommand = defineCommand({
|
|
8
|
+
meta: {
|
|
9
|
+
name: "stats",
|
|
10
|
+
description: "Memory system health overview",
|
|
11
|
+
},
|
|
12
|
+
run() {
|
|
13
|
+
const engine = EngramEngine.create();
|
|
14
|
+
try {
|
|
15
|
+
const { atRisk } = refreshActivations(engine.storage, engine.config);
|
|
16
|
+
|
|
17
|
+
const episodicCount = engine.storage.getMemoryCount("episodic");
|
|
18
|
+
const semanticCount = engine.storage.getMemoryCount("semantic");
|
|
19
|
+
const proceduralCount = engine.storage.getMemoryCount("procedural");
|
|
20
|
+
const associationCount = engine.storage.getAssociationCount();
|
|
21
|
+
const { used, capacity } = focusUtilization(engine.storage, engine.config);
|
|
22
|
+
|
|
23
|
+
const lastConsolidation = engine.storage.getLastConsolidation();
|
|
24
|
+
|
|
25
|
+
const projectContext = engine.projectContext;
|
|
26
|
+
const projectCounts = projectContext
|
|
27
|
+
? {
|
|
28
|
+
episodic: engine.storage.getMemoryCountByContext(projectContext, "episodic"),
|
|
29
|
+
semantic: engine.storage.getMemoryCountByContext(projectContext, "semantic"),
|
|
30
|
+
procedural: engine.storage.getMemoryCountByContext(projectContext, "procedural"),
|
|
31
|
+
total: engine.storage.getMemoryCountByContext(projectContext),
|
|
32
|
+
}
|
|
33
|
+
: null;
|
|
34
|
+
|
|
35
|
+
if (!isInteractive()) {
|
|
36
|
+
console.log(
|
|
37
|
+
JSON.stringify({
|
|
38
|
+
workingMemory: { used, capacity },
|
|
39
|
+
episodic: episodicCount,
|
|
40
|
+
semantic: semanticCount,
|
|
41
|
+
procedural: proceduralCount,
|
|
42
|
+
associations: associationCount,
|
|
43
|
+
atRisk,
|
|
44
|
+
lastConsolidation: lastConsolidation ? { ranAt: lastConsolidation.ranAt } : null,
|
|
45
|
+
...(projectCounts ? { project: { context: projectContext, ...projectCounts } } : {}),
|
|
46
|
+
}),
|
|
47
|
+
);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log(bold(" engram — memory system stats\n"));
|
|
52
|
+
|
|
53
|
+
const wmStatus =
|
|
54
|
+
used >= capacity
|
|
55
|
+
? red(`${used}/${capacity} slots used (FULL)`)
|
|
56
|
+
: used > capacity * 0.7
|
|
57
|
+
? yellow(`${used}/${capacity} slots used`)
|
|
58
|
+
: green(`${used}/${capacity} slots used`);
|
|
59
|
+
console.log(` Working Memory: ${wmStatus}`);
|
|
60
|
+
|
|
61
|
+
console.log(
|
|
62
|
+
` Episodic: ${episodicCount} memories` +
|
|
63
|
+
(atRisk > 0 ? ` ${yellow(`(${atRisk} at risk of forgetting)`)}` : ""),
|
|
64
|
+
);
|
|
65
|
+
console.log(` Semantic: ${semanticCount} facts`);
|
|
66
|
+
console.log(` Procedural: ${proceduralCount} skills ${dim("(immune to decay)")}`);
|
|
67
|
+
|
|
68
|
+
console.log(` Associations: ${associationCount} links`);
|
|
69
|
+
|
|
70
|
+
if (lastConsolidation) {
|
|
71
|
+
const hoursAgo = Math.round((Date.now() - lastConsolidation.ranAt) / 3600000);
|
|
72
|
+
const consolidationStatus =
|
|
73
|
+
hoursAgo > 12
|
|
74
|
+
? red(`${hoursAgo}h ago (overdue)`)
|
|
75
|
+
: hoursAgo > 6
|
|
76
|
+
? yellow(`${hoursAgo}h ago`)
|
|
77
|
+
: green(`${hoursAgo}h ago`);
|
|
78
|
+
console.log(` Last sleep: ${consolidationStatus}`);
|
|
79
|
+
} else {
|
|
80
|
+
console.log(` Last sleep: ${dim("never")}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (projectCounts) {
|
|
84
|
+
console.log("");
|
|
85
|
+
console.log(bold(` project: ${projectContext}\n`));
|
|
86
|
+
console.log(` Episodic: ${projectCounts.episodic} memories`);
|
|
87
|
+
console.log(` Semantic: ${projectCounts.semantic} facts`);
|
|
88
|
+
console.log(` Procedural: ${projectCounts.procedural} skills`);
|
|
89
|
+
console.log(dim(` Total: ${projectCounts.total} memories`));
|
|
90
|
+
}
|
|
91
|
+
} finally {
|
|
92
|
+
engine.close();
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
});
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import type { Memory, RecallResult } from "../core/memory.ts";
|
|
2
|
+
import kleur from "kleur";
|
|
3
|
+
import Table from "cli-table3";
|
|
4
|
+
import dayjs from "dayjs";
|
|
5
|
+
import relativeTime from "dayjs/plugin/relativeTime.js";
|
|
6
|
+
|
|
7
|
+
dayjs.extend(relativeTime);
|
|
8
|
+
|
|
9
|
+
export function isInteractive(): boolean {
|
|
10
|
+
return !!process.stdout.isTTY;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const dim = (s: string) => kleur.dim(s);
|
|
14
|
+
const bold = (s: string) => kleur.bold(s);
|
|
15
|
+
const green = (s: string) => kleur.green(s);
|
|
16
|
+
const yellow = (s: string) => kleur.yellow(s);
|
|
17
|
+
const red = (s: string) => kleur.red(s);
|
|
18
|
+
const cyan = (s: string) => kleur.cyan(s);
|
|
19
|
+
const magenta = (s: string) => kleur.magenta(s);
|
|
20
|
+
|
|
21
|
+
export function formatMemoryEncoded(memory: Memory): string {
|
|
22
|
+
if (!isInteractive()) {
|
|
23
|
+
return JSON.stringify({
|
|
24
|
+
id: memory.id,
|
|
25
|
+
type: memory.type,
|
|
26
|
+
content: memory.content,
|
|
27
|
+
activation: memory.activation,
|
|
28
|
+
emotion: memory.emotion,
|
|
29
|
+
context: memory.context,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const lines: string[] = [];
|
|
34
|
+
lines.push(green(" Encoded memory") + dim(` [${memory.id.slice(0, 8)}]`));
|
|
35
|
+
lines.push(` ${bold(memory.content)}`);
|
|
36
|
+
lines.push(
|
|
37
|
+
dim(` type: ${memory.type}`) +
|
|
38
|
+
(memory.emotion !== "neutral"
|
|
39
|
+
? ` ${dim("emotion:")} ${formatEmotion(memory.emotion, memory.emotionWeight)}`
|
|
40
|
+
: "") +
|
|
41
|
+
(memory.context ? ` ${dim("context:")} ${memory.context}` : ""),
|
|
42
|
+
);
|
|
43
|
+
lines.push(dim(` activation: ${memory.activation.toFixed(3)}`));
|
|
44
|
+
return lines.join("\n");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function formatRecallResults(results: RecallResult[]): string {
|
|
48
|
+
if (!isInteractive()) {
|
|
49
|
+
return JSON.stringify(
|
|
50
|
+
results.map((r) => ({
|
|
51
|
+
id: r.memory.id,
|
|
52
|
+
content: r.memory.content,
|
|
53
|
+
context: r.memory.context,
|
|
54
|
+
activation: r.activation,
|
|
55
|
+
})),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (results.length === 0) {
|
|
60
|
+
return dim(" No memories found above retrieval threshold.");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const lines: string[] = [];
|
|
64
|
+
for (const result of results) {
|
|
65
|
+
const m = result.memory;
|
|
66
|
+
const activationColor = result.activation > 0.5 ? green : result.activation > 0 ? yellow : red;
|
|
67
|
+
|
|
68
|
+
lines.push(` ${activationColor("●")} ${bold(m.content)} ` + dim(`[${m.id.slice(0, 8)}]`));
|
|
69
|
+
|
|
70
|
+
const meta: string[] = [];
|
|
71
|
+
meta.push(`type: ${m.type}`);
|
|
72
|
+
meta.push(`activation: ${result.activation.toFixed(3)}`);
|
|
73
|
+
if (m.recallCount > 0) meta.push(`recalled: ${m.recallCount}x`);
|
|
74
|
+
if (result.spreadingActivation > 0) {
|
|
75
|
+
meta.push(`spreading: +${result.spreadingActivation.toFixed(3)}`);
|
|
76
|
+
}
|
|
77
|
+
if (m.emotion !== "neutral") {
|
|
78
|
+
meta.push(`emotion: ${formatEmotion(m.emotion, m.emotionWeight)}`);
|
|
79
|
+
}
|
|
80
|
+
if (m.context) meta.push(`context: ${m.context}`);
|
|
81
|
+
|
|
82
|
+
lines.push(dim(` ${meta.join(" | ")}`));
|
|
83
|
+
lines.push(
|
|
84
|
+
dim(` latency: ${result.latency.toFixed(0)}ms`) +
|
|
85
|
+
dim(` | encoded: ${formatTimeAgo(m.encodedAt)}`),
|
|
86
|
+
);
|
|
87
|
+
lines.push("");
|
|
88
|
+
}
|
|
89
|
+
return lines.join("\n");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function formatEmotion(emotion: string, weight: number): string {
|
|
93
|
+
const w = weight.toFixed(1);
|
|
94
|
+
switch (emotion) {
|
|
95
|
+
case "anxiety":
|
|
96
|
+
return red(`${emotion}(${w})`);
|
|
97
|
+
case "frustration":
|
|
98
|
+
return red(`${emotion}(${w})`);
|
|
99
|
+
case "joy":
|
|
100
|
+
return green(`${emotion}(${w})`);
|
|
101
|
+
case "satisfaction":
|
|
102
|
+
return green(`${emotion}(${w})`);
|
|
103
|
+
case "surprise":
|
|
104
|
+
return yellow(`${emotion}(${w})`);
|
|
105
|
+
case "curiosity":
|
|
106
|
+
return cyan(`${emotion}(${w})`);
|
|
107
|
+
default:
|
|
108
|
+
return dim(`${emotion}(${w})`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function formatTimeAgo(timestamp: number): string {
|
|
113
|
+
return dayjs(timestamp).fromNow();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function formatMemoryInspection(
|
|
117
|
+
memory: Memory,
|
|
118
|
+
accessCount: number,
|
|
119
|
+
associationCount: number,
|
|
120
|
+
): string {
|
|
121
|
+
if (!isInteractive()) {
|
|
122
|
+
return JSON.stringify({
|
|
123
|
+
id: memory.id,
|
|
124
|
+
type: memory.type,
|
|
125
|
+
content: memory.content,
|
|
126
|
+
encodedAt: memory.encodedAt,
|
|
127
|
+
lastRecalledAt: memory.lastRecalledAt,
|
|
128
|
+
recallCount: memory.recallCount,
|
|
129
|
+
activation: memory.activation,
|
|
130
|
+
emotion: memory.emotion,
|
|
131
|
+
emotionWeight: memory.emotionWeight,
|
|
132
|
+
context: memory.context,
|
|
133
|
+
chunkId: memory.chunkId,
|
|
134
|
+
reconsolidationCount: memory.reconsolidationCount,
|
|
135
|
+
accessCount,
|
|
136
|
+
associationCount,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const lines: string[] = [];
|
|
141
|
+
lines.push(bold(`Memory [${memory.id.slice(0, 8)}]`));
|
|
142
|
+
lines.push(` ${bold(memory.content)}`);
|
|
143
|
+
lines.push("");
|
|
144
|
+
lines.push(dim(" Lifecycle:"));
|
|
145
|
+
lines.push(` Type: ${memory.type}`);
|
|
146
|
+
lines.push(` Encoded: ${new Date(memory.encodedAt).toISOString()}`);
|
|
147
|
+
lines.push(
|
|
148
|
+
` Last recalled: ${memory.lastRecalledAt ? new Date(memory.lastRecalledAt).toISOString() : "never"}`,
|
|
149
|
+
);
|
|
150
|
+
lines.push(` Recall count: ${memory.recallCount}`);
|
|
151
|
+
lines.push(` Reconsolidations: ${memory.reconsolidationCount}`);
|
|
152
|
+
lines.push("");
|
|
153
|
+
lines.push(dim(" Activation:"));
|
|
154
|
+
lines.push(` Current: ${memory.activation.toFixed(4)}`);
|
|
155
|
+
lines.push(` Emotion: ${formatEmotion(memory.emotion, memory.emotionWeight)}`);
|
|
156
|
+
lines.push(` Associations: ${associationCount} links`);
|
|
157
|
+
lines.push(` Total accesses: ${accessCount}`);
|
|
158
|
+
if (memory.context) {
|
|
159
|
+
lines.push(` Context: ${memory.context}`);
|
|
160
|
+
}
|
|
161
|
+
if (memory.chunkId) {
|
|
162
|
+
lines.push(` Chunk: ${memory.chunkId.slice(0, 8)}`);
|
|
163
|
+
}
|
|
164
|
+
return lines.join("\n");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function formatMemoryList(memories: Memory[], context?: string | null): string {
|
|
168
|
+
if (!isInteractive()) {
|
|
169
|
+
return JSON.stringify(
|
|
170
|
+
memories.map((m) => ({
|
|
171
|
+
id: m.id,
|
|
172
|
+
type: m.type,
|
|
173
|
+
content: m.content,
|
|
174
|
+
activation: m.activation,
|
|
175
|
+
recallCount: m.recallCount,
|
|
176
|
+
emotion: m.emotion,
|
|
177
|
+
context: m.context,
|
|
178
|
+
encodedAt: m.encodedAt,
|
|
179
|
+
})),
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (memories.length === 0) {
|
|
184
|
+
return context
|
|
185
|
+
? dim(` No memories found for context: ${context}`)
|
|
186
|
+
: dim(" No memories found.");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const table = new Table({
|
|
190
|
+
head: ["", "content", "id", "type", "act.", "recalls", "emotion", "encoded"],
|
|
191
|
+
style: { head: ["dim"], border: ["dim"] },
|
|
192
|
+
chars: {
|
|
193
|
+
top: "─",
|
|
194
|
+
"top-mid": "┬",
|
|
195
|
+
"top-left": "┌",
|
|
196
|
+
"top-right": "┐",
|
|
197
|
+
bottom: "─",
|
|
198
|
+
"bottom-mid": "┴",
|
|
199
|
+
"bottom-left": "└",
|
|
200
|
+
"bottom-right": "┘",
|
|
201
|
+
left: "│",
|
|
202
|
+
"left-mid": "├",
|
|
203
|
+
mid: "─",
|
|
204
|
+
"mid-mid": "┼",
|
|
205
|
+
right: "│",
|
|
206
|
+
"right-mid": "┤",
|
|
207
|
+
middle: "│",
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
for (const m of memories) {
|
|
212
|
+
const activationColor = m.activation > 0.5 ? green : m.activation > 0 ? yellow : red;
|
|
213
|
+
const emotionStr =
|
|
214
|
+
m.emotion !== "neutral" ? formatEmotion(m.emotion, m.emotionWeight) : dim("—");
|
|
215
|
+
|
|
216
|
+
table.push([
|
|
217
|
+
activationColor("●"),
|
|
218
|
+
m.content,
|
|
219
|
+
dim(m.id.slice(0, 8)),
|
|
220
|
+
m.type,
|
|
221
|
+
m.activation.toFixed(3),
|
|
222
|
+
m.recallCount > 0 ? `${m.recallCount}x` : dim("—"),
|
|
223
|
+
emotionStr,
|
|
224
|
+
formatTimeAgo(m.encodedAt),
|
|
225
|
+
]);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return table.toString();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export { dim, bold, green, yellow, red, cyan, magenta };
|
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { defineCommand, runMain } from "citty";
|
|
3
|
+
import pkg from "../../package.json";
|
|
4
|
+
import { encodeCommand } from "./commands/encode.ts";
|
|
5
|
+
import { recallCommand } from "./commands/recall.ts";
|
|
6
|
+
import { focusCommand } from "./commands/focus.ts";
|
|
7
|
+
import { inspectCommand } from "./commands/inspect.ts";
|
|
8
|
+
import { statsCommand } from "./commands/stats.ts";
|
|
9
|
+
import { listCommand } from "./commands/list.ts";
|
|
10
|
+
import { sleepCommand } from "./commands/sleep.ts";
|
|
11
|
+
import { healthCommand } from "./commands/health.ts";
|
|
12
|
+
|
|
13
|
+
const main = defineCommand({
|
|
14
|
+
meta: {
|
|
15
|
+
name: "engram",
|
|
16
|
+
version: pkg.version,
|
|
17
|
+
description: "Human memory for artificial minds",
|
|
18
|
+
},
|
|
19
|
+
subCommands: {
|
|
20
|
+
encode: encodeCommand,
|
|
21
|
+
recall: recallCommand,
|
|
22
|
+
focus: focusCommand,
|
|
23
|
+
inspect: inspectCommand,
|
|
24
|
+
list: listCommand,
|
|
25
|
+
stats: statsCommand,
|
|
26
|
+
sleep: sleepCommand,
|
|
27
|
+
health: healthCommand,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
runMain(main);
|