@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.
@@ -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(engine.storage, engine.config);
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(yellow(` ! ${atRisk} memories at risk of being forgotten`));
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(green(` + Working memory within capacity (${used}/${capacity})`));
52
+ console.log(
53
+ green(` + Working memory within capacity (${used}/${capacity})`)
54
+ );
47
55
  } else {
48
- console.log(red(` ! Working memory over capacity (${used}/${capacity})`));
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(red(` ! Consolidation overdue (last: ${Math.round(hoursAgo)}h ago)`));
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(` ~ Consolidation recommended soon (last: ${Math.round(hoursAgo)}h ago)`),
71
+ yellow(
72
+ ` ~ Consolidation recommended soon (last: ${Math.round(
73
+ hoursAgo
74
+ )}h ago)`
75
+ )
58
76
  );
59
77
  } else {
60
- console.log(green(` + Consolidation recent (last: ${Math.round(hoursAgo)}h ago)`));
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(yellow(" ! No consolidation has ever run — run `engram sleep`"));
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(yellow(" ! No associations formed — run `engram sleep` to discover links"));
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(green(` + Association network is rich (${associationCount} links)`));
101
+ console.log(
102
+ green(` + Association network is rich (${associationCount} links)`)
103
+ );
74
104
  } else {
75
- console.log(green(` + Association network is healthy (${associationCount} links)`));
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((m) => m.id === args.id || m.id.startsWith(args.id));
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(formatMemoryInspection(match, accessLog.length, associations.length));
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
+ });
@@ -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 = args.type && isValidMemoryType(args.type) ? args.type : undefined;
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 = args.type && isValidMemoryType(args.type) ? args.type : undefined;
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
- import { EngramEngine } from "../../core/engine.ts";
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: "Run consolidation cycle (replay, strengthen, prune, extract, link)",
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")} ${result.memoriesStrengthened} frequently-accessed memories`,
54
+ ` ${cyan("Strengthened")} ${
55
+ result.memoriesStrengthened
56
+ } frequently-accessed memories`
53
57
  );
54
58
  console.log(
55
- ` ${cyan("Pruned")} ${result.memoriesPruned} memories below activation threshold`,
59
+ ` ${cyan("Pruned")} ${
60
+ result.memoriesPruned
61
+ } memories below activation threshold`
56
62
  );
57
63
  console.log(
58
- ` ${cyan("Extracted")} ${result.factsExtracted} semantic facts from episodic patterns`,
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(` ${cyan("Chunked")} ${chunks.length} new memory groups`);
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(` ${dim(">")} ${chunk.label} (${chunk.memberIds.length} memories)`);
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(` Associations Discovered: ${result.discoveredAssociationPairs.length}`),
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(engine.storage, engine.config);
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(projectContext, "episodic"),
29
- semantic: engine.storage.getMemoryCountByContext(projectContext, "semantic"),
30
- procedural: engine.storage.getMemoryCountByContext(projectContext, "procedural"),
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 ? { ranAt: lastConsolidation.ranAt } : null,
45
- ...(projectCounts ? { project: { context: projectContext, ...projectCounts } } : {}),
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
- ? yellow(`${used}/${capacity} slots used`)
58
- : green(`${used}/${capacity} slots used`);
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(` Procedural: ${proceduralCount} skills ${dim("(immune to decay)")}`);
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((Date.now() - lastConsolidation.ranAt) / 3600000);
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
- ? yellow(`${hoursAgo}h ago`)
77
- : green(`${hoursAgo}h ago`);
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 { statsCommand } from "./commands/stats.ts";
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 { healthCommand } from "./commands/health.ts";
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
+ }
@@ -50,11 +50,14 @@ export function resolveDbPath(dbPath: string): string {
50
50
  return dbPath;
51
51
  }
52
52
 
53
- export function loadConfig(overrides?: Partial<CognitiveConfig>): CognitiveConfig {
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) config.decayRate = Number(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)