@cogmem/engram 0.2.1 → 0.3.1
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 +45 -25
- package/SKILL.md +120 -0
- package/drizzle/meta/0000_snapshot.json +17 -50
- package/drizzle/meta/_journal.json +1 -1
- package/package.json +30 -28
- package/src/cli/commands/encode.ts +15 -6
- package/src/cli/commands/focus.ts +38 -11
- package/src/cli/commands/health.ts +47 -13
- package/src/cli/commands/inspect.ts +7 -2
- package/src/cli/commands/install.ts +122 -0
- package/src/cli/commands/list.ts +3 -1
- package/src/cli/commands/recall.ts +4 -2
- package/src/cli/commands/sleep.ts +31 -11
- package/src/cli/commands/stats.ts +38 -15
- package/src/cli/format.ts +3 -2
- package/src/cli/index.ts +6 -3
- package/src/cli/providers/claude.ts +100 -0
- package/src/cli/providers/index.ts +12 -0
- package/src/cli/providers/types.ts +20 -0
- package/src/config/defaults.ts +5 -2
- package/src/core/activation.ts +43 -11
- package/src/core/associations.ts +22 -19
- package/src/core/chunking.ts +14 -4
- package/src/core/consolidation.ts +21 -6
- package/src/core/encoder.ts +3 -3
- package/src/core/engine.ts +3 -2
- package/src/core/forgetting.ts +11 -4
- package/src/core/memory.ts +2 -1
- package/src/core/procedural-store.ts +1 -1
- package/src/core/recall.ts +32 -19
- package/src/core/reconsolidation.ts +5 -2
- package/src/core/working-memory.ts +8 -4
- package/src/mcp/server.ts +53 -15
- package/src/mcp/tools.ts +108 -45
- package/src/storage/schema.ts +1 -0
- package/src/storage/sqlite.ts +22 -7
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { defineCommand } from "citty";
|
|
2
|
+
|
|
2
3
|
import { EngramEngine } from "../../core/engine.ts";
|
|
3
|
-
import { focusUtilization } from "../../core/working-memory.ts";
|
|
4
4
|
import { refreshActivations } from "../../core/forgetting.ts";
|
|
5
|
+
import { focusUtilization } from "../../core/working-memory.ts";
|
|
5
6
|
import { bold, dim, green, yellow, red, isInteractive } from "../format.ts";
|
|
6
7
|
|
|
7
8
|
export const healthCommand = defineCommand({
|
|
@@ -13,7 +14,10 @@ export const healthCommand = defineCommand({
|
|
|
13
14
|
const engine = EngramEngine.create();
|
|
14
15
|
try {
|
|
15
16
|
const { atRisk } = refreshActivations(engine.storage, engine.config);
|
|
16
|
-
const { used, capacity } = focusUtilization(
|
|
17
|
+
const { used, capacity } = focusUtilization(
|
|
18
|
+
engine.storage,
|
|
19
|
+
engine.config
|
|
20
|
+
);
|
|
17
21
|
const lastConsolidation = engine.storage.getLastConsolidation();
|
|
18
22
|
const totalMemories = engine.storage.getMemoryCount();
|
|
19
23
|
const associationCount = engine.storage.getAssociationCount();
|
|
@@ -29,7 +33,7 @@ export const healthCommand = defineCommand({
|
|
|
29
33
|
totalMemories,
|
|
30
34
|
associations: associationCount,
|
|
31
35
|
lastConsolidationHoursAgo: hoursAgo ? Math.round(hoursAgo) : null,
|
|
32
|
-
})
|
|
36
|
+
})
|
|
33
37
|
);
|
|
34
38
|
return;
|
|
35
39
|
}
|
|
@@ -37,42 +41,72 @@ export const healthCommand = defineCommand({
|
|
|
37
41
|
console.log(bold(" engram — health check\n"));
|
|
38
42
|
|
|
39
43
|
if (atRisk > 0) {
|
|
40
|
-
console.log(
|
|
44
|
+
console.log(
|
|
45
|
+
yellow(` ! ${atRisk} memories at risk of being forgotten`)
|
|
46
|
+
);
|
|
41
47
|
} else {
|
|
42
48
|
console.log(green(" + All memories above retrieval threshold"));
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
if (used <= capacity) {
|
|
46
|
-
console.log(
|
|
52
|
+
console.log(
|
|
53
|
+
green(` + Working memory within capacity (${used}/${capacity})`)
|
|
54
|
+
);
|
|
47
55
|
} else {
|
|
48
|
-
console.log(
|
|
56
|
+
console.log(
|
|
57
|
+
red(` ! Working memory over capacity (${used}/${capacity})`)
|
|
58
|
+
);
|
|
49
59
|
}
|
|
50
60
|
|
|
51
61
|
if (lastConsolidation) {
|
|
52
62
|
const hoursAgo = (Date.now() - lastConsolidation.ranAt) / 3600000;
|
|
53
63
|
if (hoursAgo > 18) {
|
|
54
|
-
console.log(
|
|
64
|
+
console.log(
|
|
65
|
+
red(
|
|
66
|
+
` ! Consolidation overdue (last: ${Math.round(hoursAgo)}h ago)`
|
|
67
|
+
)
|
|
68
|
+
);
|
|
55
69
|
} else if (hoursAgo > 8) {
|
|
56
70
|
console.log(
|
|
57
|
-
yellow(
|
|
71
|
+
yellow(
|
|
72
|
+
` ~ Consolidation recommended soon (last: ${Math.round(
|
|
73
|
+
hoursAgo
|
|
74
|
+
)}h ago)`
|
|
75
|
+
)
|
|
58
76
|
);
|
|
59
77
|
} else {
|
|
60
|
-
console.log(
|
|
78
|
+
console.log(
|
|
79
|
+
green(
|
|
80
|
+
` + Consolidation recent (last: ${Math.round(hoursAgo)}h ago)`
|
|
81
|
+
)
|
|
82
|
+
);
|
|
61
83
|
}
|
|
62
84
|
} else if (totalMemories > 0) {
|
|
63
|
-
console.log(
|
|
85
|
+
console.log(
|
|
86
|
+
yellow(" ! No consolidation has ever run — run `engram sleep`")
|
|
87
|
+
);
|
|
64
88
|
} else {
|
|
65
89
|
console.log(dim(" ~ No memories encoded yet"));
|
|
66
90
|
}
|
|
67
91
|
|
|
68
92
|
if (totalMemories > 5 && associationCount === 0) {
|
|
69
|
-
console.log(
|
|
93
|
+
console.log(
|
|
94
|
+
yellow(
|
|
95
|
+
" ! No associations formed — run `engram sleep` to discover links"
|
|
96
|
+
)
|
|
97
|
+
);
|
|
70
98
|
} else if (associationCount > 0) {
|
|
71
99
|
const ratio = associationCount / Math.max(1, totalMemories);
|
|
72
100
|
if (ratio > 1) {
|
|
73
|
-
console.log(
|
|
101
|
+
console.log(
|
|
102
|
+
green(` + Association network is rich (${associationCount} links)`)
|
|
103
|
+
);
|
|
74
104
|
} else {
|
|
75
|
-
console.log(
|
|
105
|
+
console.log(
|
|
106
|
+
green(
|
|
107
|
+
` + Association network is healthy (${associationCount} links)`
|
|
108
|
+
)
|
|
109
|
+
);
|
|
76
110
|
}
|
|
77
111
|
}
|
|
78
112
|
} finally {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defineCommand } from "citty";
|
|
2
|
+
|
|
2
3
|
import { EngramEngine } from "../../core/engine.ts";
|
|
3
4
|
import { formatMemoryInspection, dim } from "../format.ts";
|
|
4
5
|
|
|
@@ -18,7 +19,9 @@ export const inspectCommand = defineCommand({
|
|
|
18
19
|
const engine = EngramEngine.create();
|
|
19
20
|
try {
|
|
20
21
|
const allMemories = engine.storage.getAllMemories();
|
|
21
|
-
const match = allMemories.find(
|
|
22
|
+
const match = allMemories.find(
|
|
23
|
+
(m) => m.id === args.id || m.id.startsWith(args.id)
|
|
24
|
+
);
|
|
22
25
|
|
|
23
26
|
if (!match) {
|
|
24
27
|
console.log(dim(` No memory found matching "${args.id}"`));
|
|
@@ -28,7 +31,9 @@ export const inspectCommand = defineCommand({
|
|
|
28
31
|
const accessLog = engine.storage.getAccessLog(match.id);
|
|
29
32
|
const associations = engine.storage.getAssociations(match.id);
|
|
30
33
|
|
|
31
|
-
console.log(
|
|
34
|
+
console.log(
|
|
35
|
+
formatMemoryInspection(match, accessLog.length, associations.length)
|
|
36
|
+
);
|
|
32
37
|
} finally {
|
|
33
38
|
engine.close();
|
|
34
39
|
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
import { defineCommand } from "citty";
|
|
6
|
+
import { consola } from "consola";
|
|
7
|
+
|
|
8
|
+
import { green, dim, yellow, bold } from "../format.ts";
|
|
9
|
+
import { getProvider, availableProviders } from "../providers/index.ts";
|
|
10
|
+
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const SKILL_PATH = join(__dirname, "..", "..", "..", "SKILL.md");
|
|
13
|
+
|
|
14
|
+
function loadSkillContent(): string {
|
|
15
|
+
return readFileSync(SKILL_PATH, "utf-8");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const installCommand = defineCommand({
|
|
19
|
+
meta: {
|
|
20
|
+
name: "install",
|
|
21
|
+
description: "Install engram skill + MCP config for your AI editor",
|
|
22
|
+
},
|
|
23
|
+
args: {
|
|
24
|
+
provider: {
|
|
25
|
+
type: "string",
|
|
26
|
+
description: "Provider to install for (claude)",
|
|
27
|
+
alias: "p",
|
|
28
|
+
},
|
|
29
|
+
global: {
|
|
30
|
+
type: "boolean",
|
|
31
|
+
description: "Install globally (~/.claude/)",
|
|
32
|
+
alias: "g",
|
|
33
|
+
},
|
|
34
|
+
project: {
|
|
35
|
+
type: "boolean",
|
|
36
|
+
description: "Install to current project directory",
|
|
37
|
+
},
|
|
38
|
+
dryRun: {
|
|
39
|
+
type: "boolean",
|
|
40
|
+
description: "Show what would be installed without writing files",
|
|
41
|
+
alias: "n",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
async run({ args }) {
|
|
45
|
+
const dryRun = args.dryRun ?? false;
|
|
46
|
+
|
|
47
|
+
let providerName = args.provider;
|
|
48
|
+
if (!providerName) {
|
|
49
|
+
const choices = availableProviders.map((p) => ({
|
|
50
|
+
label: p.available ? p.displayName : `${p.displayName} (coming soon)`,
|
|
51
|
+
value: p.name,
|
|
52
|
+
disabled: !p.available,
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
providerName = (await consola.prompt("Select a provider", {
|
|
56
|
+
type: "select",
|
|
57
|
+
options: choices,
|
|
58
|
+
})) as unknown as string;
|
|
59
|
+
|
|
60
|
+
if (typeof providerName === "symbol") process.exit(0);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const provider = getProvider(providerName);
|
|
64
|
+
if (!provider) {
|
|
65
|
+
consola.error(`Unknown provider: ${providerName}`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
if (!provider.available) {
|
|
69
|
+
consola.error(`${provider.displayName} is not yet supported`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let scope: "global" | "project" | undefined;
|
|
74
|
+
if (args.global) scope = "global";
|
|
75
|
+
else if (args.project) scope = "project";
|
|
76
|
+
|
|
77
|
+
if (!scope) {
|
|
78
|
+
scope = (await consola.prompt("Install scope", {
|
|
79
|
+
type: "select",
|
|
80
|
+
options: [
|
|
81
|
+
{ label: `Global (~/.claude/)`, value: "global" },
|
|
82
|
+
{ label: `Project (./.claude/)`, value: "project" },
|
|
83
|
+
],
|
|
84
|
+
})) as unknown as "global" | "project";
|
|
85
|
+
|
|
86
|
+
if (typeof scope === "symbol") process.exit(0);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const skillContent = loadSkillContent();
|
|
90
|
+
|
|
91
|
+
if (dryRun) console.log(yellow("\n dry run — no files will be written\n"));
|
|
92
|
+
|
|
93
|
+
const result =
|
|
94
|
+
scope === "global"
|
|
95
|
+
? await provider.installGlobal(skillContent, dryRun)
|
|
96
|
+
: await provider.installProject(skillContent, process.cwd(), dryRun);
|
|
97
|
+
|
|
98
|
+
if (result.status === "already_installed") {
|
|
99
|
+
console.log(dim(" already installed — nothing to do"));
|
|
100
|
+
console.log(dim(` skill: ${result.skillPath}`));
|
|
101
|
+
if (result.mcpConfigPath)
|
|
102
|
+
console.log(dim(` mcp: ${result.mcpConfigPath}`));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const prefix = dryRun ? "would install" : "installed";
|
|
107
|
+
const check = dryRun ? yellow("~") : green("\u2713");
|
|
108
|
+
|
|
109
|
+
console.log(
|
|
110
|
+
` ${check} Skill ${prefix} ${dim("\u2192")} ${bold(result.skillPath)}`
|
|
111
|
+
);
|
|
112
|
+
if (result.mcpConfigured && result.mcpConfigPath) {
|
|
113
|
+
console.log(
|
|
114
|
+
` ${check} MCP ${prefix} ${dim("\u2192")} ${bold(
|
|
115
|
+
result.mcpConfigPath
|
|
116
|
+
)}`
|
|
117
|
+
);
|
|
118
|
+
} else if (result.mcpConfigPath) {
|
|
119
|
+
console.log(dim(` - MCP already configured → ${result.mcpConfigPath}`));
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
});
|
package/src/cli/commands/list.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defineCommand } from "citty";
|
|
2
|
+
|
|
2
3
|
import { EngramEngine } from "../../core/engine.ts";
|
|
3
4
|
import { isValidMemoryType } from "../../core/memory.ts";
|
|
4
5
|
import { formatMemoryList } from "../format.ts";
|
|
@@ -32,7 +33,8 @@ export const listCommand = defineCommand({
|
|
|
32
33
|
run({ args }) {
|
|
33
34
|
const engine = EngramEngine.create();
|
|
34
35
|
try {
|
|
35
|
-
const type =
|
|
36
|
+
const type =
|
|
37
|
+
args.type && isValidMemoryType(args.type) ? args.type : undefined;
|
|
36
38
|
const limit = Number(args.limit);
|
|
37
39
|
|
|
38
40
|
let memories;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { defineCommand } from "citty";
|
|
2
|
+
|
|
2
3
|
import { EngramEngine } from "../../core/engine.ts";
|
|
4
|
+
import { isValidMemoryType } from "../../core/memory.ts";
|
|
3
5
|
import { recall } from "../../core/recall.ts";
|
|
4
6
|
import { formatRecallResults } from "../format.ts";
|
|
5
|
-
import { isValidMemoryType } from "../../core/memory.ts";
|
|
6
7
|
|
|
7
8
|
export const recallCommand = defineCommand({
|
|
8
9
|
meta: {
|
|
@@ -38,7 +39,8 @@ export const recallCommand = defineCommand({
|
|
|
38
39
|
run({ args }) {
|
|
39
40
|
const engine = EngramEngine.create();
|
|
40
41
|
try {
|
|
41
|
-
const typeFilter =
|
|
42
|
+
const typeFilter =
|
|
43
|
+
args.type && isValidMemoryType(args.type) ? args.type : undefined;
|
|
42
44
|
|
|
43
45
|
const results = recall(engine.storage, args.cue, engine.config, {
|
|
44
46
|
type: typeFilter,
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { defineCommand } from "citty";
|
|
2
|
-
|
|
3
|
-
import { consolidate } from "../../core/consolidation.ts";
|
|
2
|
+
|
|
4
3
|
import { discoverChunks } from "../../core/chunking.ts";
|
|
4
|
+
import { consolidate } from "../../core/consolidation.ts";
|
|
5
|
+
import { EngramEngine } from "../../core/engine.ts";
|
|
5
6
|
import { bold, dim, green, cyan, isInteractive } from "../format.ts";
|
|
6
7
|
|
|
7
8
|
export const sleepCommand = defineCommand({
|
|
8
9
|
meta: {
|
|
9
10
|
name: "sleep",
|
|
10
|
-
description:
|
|
11
|
+
description:
|
|
12
|
+
"Run consolidation cycle (replay, strengthen, prune, extract, link)",
|
|
11
13
|
},
|
|
12
14
|
args: {
|
|
13
15
|
report: {
|
|
@@ -41,7 +43,7 @@ export const sleepCommand = defineCommand({
|
|
|
41
43
|
})),
|
|
42
44
|
}
|
|
43
45
|
: {}),
|
|
44
|
-
})
|
|
46
|
+
})
|
|
45
47
|
);
|
|
46
48
|
return;
|
|
47
49
|
}
|
|
@@ -49,17 +51,29 @@ export const sleepCommand = defineCommand({
|
|
|
49
51
|
console.log(dim(" Running consolidation cycle...\n"));
|
|
50
52
|
console.log(green(" Consolidation complete:\n"));
|
|
51
53
|
console.log(
|
|
52
|
-
` ${cyan("Strengthened")} ${
|
|
54
|
+
` ${cyan("Strengthened")} ${
|
|
55
|
+
result.memoriesStrengthened
|
|
56
|
+
} frequently-accessed memories`
|
|
53
57
|
);
|
|
54
58
|
console.log(
|
|
55
|
-
` ${cyan("Pruned")} ${
|
|
59
|
+
` ${cyan("Pruned")} ${
|
|
60
|
+
result.memoriesPruned
|
|
61
|
+
} memories below activation threshold`
|
|
56
62
|
);
|
|
57
63
|
console.log(
|
|
58
|
-
` ${cyan("Extracted")} ${
|
|
64
|
+
` ${cyan("Extracted")} ${
|
|
65
|
+
result.factsExtracted
|
|
66
|
+
} semantic facts from episodic patterns`
|
|
67
|
+
);
|
|
68
|
+
console.log(
|
|
69
|
+
` ${cyan("Discovered")} ${
|
|
70
|
+
result.associationsDiscovered
|
|
71
|
+
} new associations`
|
|
59
72
|
);
|
|
60
|
-
console.log(` ${cyan("Discovered")} ${result.associationsDiscovered} new associations`);
|
|
61
73
|
if (chunks.length > 0) {
|
|
62
|
-
console.log(
|
|
74
|
+
console.log(
|
|
75
|
+
` ${cyan("Chunked")} ${chunks.length} new memory groups`
|
|
76
|
+
);
|
|
63
77
|
}
|
|
64
78
|
|
|
65
79
|
if (args.report) {
|
|
@@ -84,14 +98,20 @@ export const sleepCommand = defineCommand({
|
|
|
84
98
|
if (chunks.length > 0) {
|
|
85
99
|
console.log(bold(" New Chunks:"));
|
|
86
100
|
for (const chunk of chunks) {
|
|
87
|
-
console.log(
|
|
101
|
+
console.log(
|
|
102
|
+
` ${dim(">")} ${chunk.label} (${
|
|
103
|
+
chunk.memberIds.length
|
|
104
|
+
} memories)`
|
|
105
|
+
);
|
|
88
106
|
}
|
|
89
107
|
console.log("");
|
|
90
108
|
}
|
|
91
109
|
|
|
92
110
|
if (result.discoveredAssociationPairs.length > 0) {
|
|
93
111
|
console.log(
|
|
94
|
-
bold(
|
|
112
|
+
bold(
|
|
113
|
+
` Associations Discovered: ${result.discoveredAssociationPairs.length}`
|
|
114
|
+
)
|
|
95
115
|
);
|
|
96
116
|
}
|
|
97
117
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { defineCommand } from "citty";
|
|
2
|
+
|
|
2
3
|
import { EngramEngine } from "../../core/engine.ts";
|
|
4
|
+
import { refreshActivations } from "../../core/forgetting.ts";
|
|
3
5
|
import { focusUtilization } from "../../core/working-memory.ts";
|
|
4
6
|
import { bold, dim, green, yellow, red, isInteractive } from "../format.ts";
|
|
5
|
-
import { refreshActivations } from "../../core/forgetting.ts";
|
|
6
7
|
|
|
7
8
|
export const statsCommand = defineCommand({
|
|
8
9
|
meta: {
|
|
@@ -18,16 +19,28 @@ export const statsCommand = defineCommand({
|
|
|
18
19
|
const semanticCount = engine.storage.getMemoryCount("semantic");
|
|
19
20
|
const proceduralCount = engine.storage.getMemoryCount("procedural");
|
|
20
21
|
const associationCount = engine.storage.getAssociationCount();
|
|
21
|
-
const { used, capacity } = focusUtilization(
|
|
22
|
+
const { used, capacity } = focusUtilization(
|
|
23
|
+
engine.storage,
|
|
24
|
+
engine.config
|
|
25
|
+
);
|
|
22
26
|
|
|
23
27
|
const lastConsolidation = engine.storage.getLastConsolidation();
|
|
24
28
|
|
|
25
29
|
const projectContext = engine.projectContext;
|
|
26
30
|
const projectCounts = projectContext
|
|
27
31
|
? {
|
|
28
|
-
episodic: engine.storage.getMemoryCountByContext(
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
episodic: engine.storage.getMemoryCountByContext(
|
|
33
|
+
projectContext,
|
|
34
|
+
"episodic"
|
|
35
|
+
),
|
|
36
|
+
semantic: engine.storage.getMemoryCountByContext(
|
|
37
|
+
projectContext,
|
|
38
|
+
"semantic"
|
|
39
|
+
),
|
|
40
|
+
procedural: engine.storage.getMemoryCountByContext(
|
|
41
|
+
projectContext,
|
|
42
|
+
"procedural"
|
|
43
|
+
),
|
|
31
44
|
total: engine.storage.getMemoryCountByContext(projectContext),
|
|
32
45
|
}
|
|
33
46
|
: null;
|
|
@@ -41,9 +54,13 @@ export const statsCommand = defineCommand({
|
|
|
41
54
|
procedural: proceduralCount,
|
|
42
55
|
associations: associationCount,
|
|
43
56
|
atRisk,
|
|
44
|
-
lastConsolidation: lastConsolidation
|
|
45
|
-
|
|
46
|
-
|
|
57
|
+
lastConsolidation: lastConsolidation
|
|
58
|
+
? { ranAt: lastConsolidation.ranAt }
|
|
59
|
+
: null,
|
|
60
|
+
...(projectCounts
|
|
61
|
+
? { project: { context: projectContext, ...projectCounts } }
|
|
62
|
+
: {}),
|
|
63
|
+
})
|
|
47
64
|
);
|
|
48
65
|
return;
|
|
49
66
|
}
|
|
@@ -54,27 +71,33 @@ export const statsCommand = defineCommand({
|
|
|
54
71
|
used >= capacity
|
|
55
72
|
? red(`${used}/${capacity} slots used (FULL)`)
|
|
56
73
|
: used > capacity * 0.7
|
|
57
|
-
|
|
58
|
-
|
|
74
|
+
? yellow(`${used}/${capacity} slots used`)
|
|
75
|
+
: green(`${used}/${capacity} slots used`);
|
|
59
76
|
console.log(` Working Memory: ${wmStatus}`);
|
|
60
77
|
|
|
61
78
|
console.log(
|
|
62
79
|
` Episodic: ${episodicCount} memories` +
|
|
63
|
-
(atRisk > 0 ? ` ${yellow(`(${atRisk} at risk of forgetting)`)}` : "")
|
|
80
|
+
(atRisk > 0 ? ` ${yellow(`(${atRisk} at risk of forgetting)`)}` : "")
|
|
64
81
|
);
|
|
65
82
|
console.log(` Semantic: ${semanticCount} facts`);
|
|
66
|
-
console.log(
|
|
83
|
+
console.log(
|
|
84
|
+
` Procedural: ${proceduralCount} skills ${dim(
|
|
85
|
+
"(immune to decay)"
|
|
86
|
+
)}`
|
|
87
|
+
);
|
|
67
88
|
|
|
68
89
|
console.log(` Associations: ${associationCount} links`);
|
|
69
90
|
|
|
70
91
|
if (lastConsolidation) {
|
|
71
|
-
const hoursAgo = Math.round(
|
|
92
|
+
const hoursAgo = Math.round(
|
|
93
|
+
(Date.now() - lastConsolidation.ranAt) / 3600000
|
|
94
|
+
);
|
|
72
95
|
const consolidationStatus =
|
|
73
96
|
hoursAgo > 12
|
|
74
97
|
? red(`${hoursAgo}h ago (overdue)`)
|
|
75
98
|
: hoursAgo > 6
|
|
76
|
-
|
|
77
|
-
|
|
99
|
+
? yellow(`${hoursAgo}h ago`)
|
|
100
|
+
: green(`${hoursAgo}h ago`);
|
|
78
101
|
console.log(` Last sleep: ${consolidationStatus}`);
|
|
79
102
|
} else {
|
|
80
103
|
console.log(` Last sleep: ${dim("never")}`);
|
package/src/cli/format.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { Memory, RecallResult } from "../core/memory.ts";
|
|
2
|
-
import kleur from "kleur";
|
|
3
1
|
import Table from "cli-table3";
|
|
4
2
|
import dayjs from "dayjs";
|
|
5
3
|
import relativeTime from "dayjs/plugin/relativeTime.js";
|
|
4
|
+
import kleur from "kleur";
|
|
5
|
+
|
|
6
|
+
import type { Memory, RecallResult } from "../core/memory.ts";
|
|
6
7
|
|
|
7
8
|
dayjs.extend(relativeTime);
|
|
8
9
|
|
package/src/cli/index.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { defineCommand, runMain } from "citty";
|
|
3
|
+
|
|
3
4
|
import pkg from "../../package.json";
|
|
4
5
|
import { encodeCommand } from "./commands/encode.ts";
|
|
5
|
-
import { recallCommand } from "./commands/recall.ts";
|
|
6
6
|
import { focusCommand } from "./commands/focus.ts";
|
|
7
|
+
import { healthCommand } from "./commands/health.ts";
|
|
7
8
|
import { inspectCommand } from "./commands/inspect.ts";
|
|
8
|
-
import {
|
|
9
|
+
import { installCommand } from "./commands/install.ts";
|
|
9
10
|
import { listCommand } from "./commands/list.ts";
|
|
11
|
+
import { recallCommand } from "./commands/recall.ts";
|
|
10
12
|
import { sleepCommand } from "./commands/sleep.ts";
|
|
11
|
-
import {
|
|
13
|
+
import { statsCommand } from "./commands/stats.ts";
|
|
12
14
|
|
|
13
15
|
const main = defineCommand({
|
|
14
16
|
meta: {
|
|
@@ -25,6 +27,7 @@ const main = defineCommand({
|
|
|
25
27
|
stats: statsCommand,
|
|
26
28
|
sleep: sleepCommand,
|
|
27
29
|
health: healthCommand,
|
|
30
|
+
install: installCommand,
|
|
28
31
|
},
|
|
29
32
|
});
|
|
30
33
|
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
import type { ProviderInstaller } from "./types.ts";
|
|
6
|
+
|
|
7
|
+
const MCP_SERVER_CONFIG = {
|
|
8
|
+
command: "bunx",
|
|
9
|
+
args: ["-p", "@cogmem/engram", "engram-mcp"],
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function ensureDir(dir: string) {
|
|
13
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function readJsonFile(path: string): Record<string, unknown> {
|
|
17
|
+
if (!existsSync(path)) return {};
|
|
18
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function writeJsonFile(path: string, data: Record<string, unknown>) {
|
|
22
|
+
writeFileSync(path, JSON.stringify(data, null, 2) + "\n");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function installSkill(skillDir: string, skillContent: string, dryRun: boolean): boolean {
|
|
26
|
+
const skillPath = join(skillDir, "SKILL.md");
|
|
27
|
+
if (existsSync(skillPath)) {
|
|
28
|
+
const existing = readFileSync(skillPath, "utf-8");
|
|
29
|
+
if (existing === skillContent) return false;
|
|
30
|
+
}
|
|
31
|
+
if (!dryRun) {
|
|
32
|
+
ensureDir(skillDir);
|
|
33
|
+
writeFileSync(skillPath, skillContent);
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function configureMcp(configPath: string, dryRun: boolean): boolean {
|
|
39
|
+
const config = readJsonFile(configPath);
|
|
40
|
+
const servers = (config.mcpServers ?? {}) as Record<string, unknown>;
|
|
41
|
+
if (servers.engram) return false;
|
|
42
|
+
if (!dryRun) {
|
|
43
|
+
servers.engram = MCP_SERVER_CONFIG;
|
|
44
|
+
config.mcpServers = servers;
|
|
45
|
+
ensureDir(join(configPath, ".."));
|
|
46
|
+
writeJsonFile(configPath, config);
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const claudeProvider: ProviderInstaller = {
|
|
52
|
+
name: "claude",
|
|
53
|
+
displayName: "Claude Code",
|
|
54
|
+
available: true,
|
|
55
|
+
|
|
56
|
+
async installGlobal(skillContent, dryRun) {
|
|
57
|
+
const home = homedir();
|
|
58
|
+
const skillDir = join(home, ".claude", "skills", "engram");
|
|
59
|
+
const configPath = join(home, ".claude.json");
|
|
60
|
+
|
|
61
|
+
const skillInstalled = installSkill(skillDir, skillContent, dryRun);
|
|
62
|
+
const mcpInstalled = configureMcp(configPath, dryRun);
|
|
63
|
+
|
|
64
|
+
const status =
|
|
65
|
+
!skillInstalled && !mcpInstalled
|
|
66
|
+
? "already_installed"
|
|
67
|
+
: skillInstalled && mcpInstalled
|
|
68
|
+
? "installed"
|
|
69
|
+
: "updated";
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
status,
|
|
73
|
+
skillPath: join(skillDir, "SKILL.md"),
|
|
74
|
+
mcpConfigured: mcpInstalled,
|
|
75
|
+
mcpConfigPath: configPath,
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
async installProject(skillContent, projectDir, dryRun) {
|
|
80
|
+
const skillDir = join(projectDir, ".claude", "skills", "engram");
|
|
81
|
+
const configPath = join(projectDir, ".mcp.json");
|
|
82
|
+
|
|
83
|
+
const skillInstalled = installSkill(skillDir, skillContent, dryRun);
|
|
84
|
+
const mcpInstalled = configureMcp(configPath, dryRun);
|
|
85
|
+
|
|
86
|
+
const status =
|
|
87
|
+
!skillInstalled && !mcpInstalled
|
|
88
|
+
? "already_installed"
|
|
89
|
+
: skillInstalled && mcpInstalled
|
|
90
|
+
? "installed"
|
|
91
|
+
: "updated";
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
status,
|
|
95
|
+
skillPath: join(skillDir, "SKILL.md"),
|
|
96
|
+
mcpConfigured: mcpInstalled,
|
|
97
|
+
mcpConfigPath: configPath,
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { claudeProvider } from "./claude.ts";
|
|
2
|
+
import type { ProviderInstaller } from "./types.ts";
|
|
3
|
+
|
|
4
|
+
const providers: Record<string, ProviderInstaller> = {
|
|
5
|
+
claude: claudeProvider,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function getProvider(name: string): ProviderInstaller | undefined {
|
|
9
|
+
return providers[name];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const availableProviders = Object.values(providers);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type InstallStatus = "installed" | "already_installed" | "updated";
|
|
2
|
+
|
|
3
|
+
export interface InstallResult {
|
|
4
|
+
status: InstallStatus;
|
|
5
|
+
skillPath: string;
|
|
6
|
+
mcpConfigured: boolean;
|
|
7
|
+
mcpConfigPath: string | null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ProviderInstaller {
|
|
11
|
+
name: string;
|
|
12
|
+
displayName: string;
|
|
13
|
+
available: boolean;
|
|
14
|
+
installGlobal(skillContent: string, dryRun: boolean): Promise<InstallResult>;
|
|
15
|
+
installProject(
|
|
16
|
+
skillContent: string,
|
|
17
|
+
projectDir: string,
|
|
18
|
+
dryRun: boolean
|
|
19
|
+
): Promise<InstallResult>;
|
|
20
|
+
}
|
package/src/config/defaults.ts
CHANGED
|
@@ -50,11 +50,14 @@ export function resolveDbPath(dbPath: string): string {
|
|
|
50
50
|
return dbPath;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
export function loadConfig(
|
|
53
|
+
export function loadConfig(
|
|
54
|
+
overrides?: Partial<CognitiveConfig>
|
|
55
|
+
): CognitiveConfig {
|
|
54
56
|
const config = { ...DEFAULT_CONFIG, ...overrides };
|
|
55
57
|
|
|
56
58
|
if (process.env.ENGRAM_DB_PATH) config.dbPath = process.env.ENGRAM_DB_PATH;
|
|
57
|
-
if (process.env.ENGRAM_DECAY_RATE)
|
|
59
|
+
if (process.env.ENGRAM_DECAY_RATE)
|
|
60
|
+
config.decayRate = Number(process.env.ENGRAM_DECAY_RATE);
|
|
58
61
|
if (process.env.ENGRAM_WM_CAPACITY)
|
|
59
62
|
config.workingMemoryCapacity = Number(process.env.ENGRAM_WM_CAPACITY);
|
|
60
63
|
if (process.env.ENGRAM_RETRIEVAL_THRESHOLD)
|