agent-working-memory 0.4.1 → 0.4.3

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.
Files changed (123) hide show
  1. package/LICENSE +190 -21
  2. package/README.md +175 -191
  3. package/dist/api/index.d.ts.map +1 -1
  4. package/dist/api/index.js +2 -0
  5. package/dist/api/index.js.map +1 -1
  6. package/dist/api/routes.d.ts.map +1 -1
  7. package/dist/api/routes.js +7 -0
  8. package/dist/api/routes.js.map +1 -1
  9. package/dist/cli.d.ts +0 -9
  10. package/dist/cli.d.ts.map +1 -1
  11. package/dist/cli.js +85 -56
  12. package/dist/cli.js.map +1 -1
  13. package/dist/core/decay.d.ts.map +1 -1
  14. package/dist/core/decay.js +2 -0
  15. package/dist/core/decay.js.map +1 -1
  16. package/dist/core/embeddings.d.ts.map +1 -1
  17. package/dist/core/embeddings.js +2 -0
  18. package/dist/core/embeddings.js.map +1 -1
  19. package/dist/core/hebbian.d.ts.map +1 -1
  20. package/dist/core/hebbian.js +2 -0
  21. package/dist/core/hebbian.js.map +1 -1
  22. package/dist/core/index.d.ts.map +1 -1
  23. package/dist/core/index.js +2 -0
  24. package/dist/core/index.js.map +1 -1
  25. package/dist/core/logger.d.ts.map +1 -1
  26. package/dist/core/logger.js +2 -0
  27. package/dist/core/logger.js.map +1 -1
  28. package/dist/core/query-expander.d.ts.map +1 -1
  29. package/dist/core/query-expander.js +2 -0
  30. package/dist/core/query-expander.js.map +1 -1
  31. package/dist/core/reranker.d.ts.map +1 -1
  32. package/dist/core/reranker.js +2 -0
  33. package/dist/core/reranker.js.map +1 -1
  34. package/dist/core/salience.d.ts.map +1 -1
  35. package/dist/core/salience.js +12 -8
  36. package/dist/core/salience.js.map +1 -1
  37. package/dist/engine/activation.d.ts.map +1 -1
  38. package/dist/engine/activation.js +2 -0
  39. package/dist/engine/activation.js.map +1 -1
  40. package/dist/engine/connections.d.ts.map +1 -1
  41. package/dist/engine/connections.js +2 -0
  42. package/dist/engine/connections.js.map +1 -1
  43. package/dist/engine/consolidation-scheduler.d.ts.map +1 -1
  44. package/dist/engine/consolidation-scheduler.js +2 -0
  45. package/dist/engine/consolidation-scheduler.js.map +1 -1
  46. package/dist/engine/consolidation.d.ts.map +1 -1
  47. package/dist/engine/consolidation.js +12 -3
  48. package/dist/engine/consolidation.js.map +1 -1
  49. package/dist/engine/eval.d.ts.map +1 -1
  50. package/dist/engine/eval.js +2 -0
  51. package/dist/engine/eval.js.map +1 -1
  52. package/dist/engine/eviction.d.ts.map +1 -1
  53. package/dist/engine/eviction.js +2 -0
  54. package/dist/engine/eviction.js.map +1 -1
  55. package/dist/engine/index.d.ts.map +1 -1
  56. package/dist/engine/index.js +2 -0
  57. package/dist/engine/index.js.map +1 -1
  58. package/dist/engine/retraction.d.ts.map +1 -1
  59. package/dist/engine/retraction.js +2 -0
  60. package/dist/engine/retraction.js.map +1 -1
  61. package/dist/engine/staging.d.ts.map +1 -1
  62. package/dist/engine/staging.js +2 -0
  63. package/dist/engine/staging.js.map +1 -1
  64. package/dist/hooks/sidecar.d.ts.map +1 -1
  65. package/dist/hooks/sidecar.js +29 -0
  66. package/dist/hooks/sidecar.js.map +1 -1
  67. package/dist/index.js +2 -0
  68. package/dist/index.js.map +1 -1
  69. package/dist/mcp.d.ts.map +1 -1
  70. package/dist/mcp.js +2 -0
  71. package/dist/mcp.js.map +1 -1
  72. package/dist/storage/index.d.ts.map +1 -1
  73. package/dist/storage/index.js +2 -0
  74. package/dist/storage/index.js.map +1 -1
  75. package/dist/storage/sqlite.d.ts.map +1 -1
  76. package/dist/storage/sqlite.js +12 -2
  77. package/dist/storage/sqlite.js.map +1 -1
  78. package/dist/types/agent.d.ts.map +1 -1
  79. package/dist/types/agent.js +2 -0
  80. package/dist/types/agent.js.map +1 -1
  81. package/dist/types/checkpoint.d.ts.map +1 -1
  82. package/dist/types/checkpoint.js +2 -0
  83. package/dist/types/checkpoint.js.map +1 -1
  84. package/dist/types/engram.d.ts.map +1 -1
  85. package/dist/types/engram.js +2 -0
  86. package/dist/types/engram.js.map +1 -1
  87. package/dist/types/eval.d.ts.map +1 -1
  88. package/dist/types/eval.js +2 -0
  89. package/dist/types/eval.js.map +1 -1
  90. package/dist/types/index.d.ts.map +1 -1
  91. package/dist/types/index.js +2 -0
  92. package/dist/types/index.js.map +1 -1
  93. package/package.json +2 -2
  94. package/src/api/index.ts +2 -0
  95. package/src/api/routes.ts +8 -0
  96. package/src/cli.ts +385 -355
  97. package/src/core/decay.ts +2 -0
  98. package/src/core/embeddings.ts +2 -0
  99. package/src/core/hebbian.ts +2 -0
  100. package/src/core/index.ts +2 -0
  101. package/src/core/logger.ts +2 -0
  102. package/src/core/query-expander.ts +2 -0
  103. package/src/core/reranker.ts +2 -0
  104. package/src/core/salience.ts +14 -10
  105. package/src/engine/activation.ts +2 -0
  106. package/src/engine/connections.ts +2 -0
  107. package/src/engine/consolidation-scheduler.ts +125 -123
  108. package/src/engine/consolidation.ts +11 -3
  109. package/src/engine/eval.ts +2 -0
  110. package/src/engine/eviction.ts +2 -0
  111. package/src/engine/index.ts +2 -0
  112. package/src/engine/retraction.ts +2 -0
  113. package/src/engine/staging.ts +2 -0
  114. package/src/hooks/sidecar.ts +31 -0
  115. package/src/index.ts +2 -0
  116. package/src/mcp.ts +2 -0
  117. package/src/storage/index.ts +2 -0
  118. package/src/storage/sqlite.ts +12 -2
  119. package/src/types/agent.ts +2 -0
  120. package/src/types/checkpoint.ts +46 -44
  121. package/src/types/engram.ts +2 -0
  122. package/src/types/eval.ts +2 -0
  123. package/src/types/index.ts +2 -0
package/src/cli.ts CHANGED
@@ -1,355 +1,385 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * CLI entrypoint for AgentWorkingMemory.
5
- *
6
- * Commands:
7
- * awm setup — configure MCP for the current project
8
- * awm mcp — start the MCP server (called by Claude Code)
9
- * awm servestart the HTTP API server
10
- * awm health check if a running server is healthy
11
- */
12
-
13
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
14
- import { resolve, basename, join, dirname } from 'node:path';
15
- import { execSync } from 'node:child_process';
16
- import { randomBytes } from 'node:crypto';
17
- import { fileURLToPath } from 'node:url';
18
- import { homedir as osHomedir } from 'node:os';
19
-
20
- const __filename = fileURLToPath(import.meta.url);
21
- const __dirname = dirname(__filename);
22
-
23
- // Load .env if present
24
- try {
25
- const envPath = resolve(process.cwd(), '.env');
26
- const envContent = readFileSync(envPath, 'utf-8');
27
- for (const line of envContent.split('\n')) {
28
- const trimmed = line.trim();
29
- if (!trimmed || trimmed.startsWith('#')) continue;
30
- const eqIdx = trimmed.indexOf('=');
31
- if (eqIdx === -1) continue;
32
- const key = trimmed.slice(0, eqIdx).trim();
33
- const val = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, '');
34
- if (!process.env[key]) process.env[key] = val;
35
- }
36
- } catch { /* No .env file */ }
37
-
38
- const args = process.argv.slice(2);
39
- const command = args[0];
40
-
41
- function printUsage() {
42
- console.log(`
43
- AgentWorkingMemory Cognitive memory for AI agents
44
-
45
- Usage:
46
- awm setup [--global] [--agent-id <id>] [--db-path <path>] [--no-claude-md]
47
- [--no-hooks] [--hook-port <port>] Configure MCP for Claude Code
48
- awm mcp Start MCP server (used by Claude Code)
49
- awm serve [--port <port>] Start HTTP API server
50
- awm health [--port <port>] Check server health
51
-
52
- Setup:
53
- awm setup --global Recommended. Writes ~/.mcp.json so AWM is available
54
- in every project — one brain across all your work.
55
-
56
- awm setup Project-level. Writes .mcp.json in the current directory
57
- and appends workflow instructions to CLAUDE.md.
58
-
59
- --no-claude-md Skip CLAUDE.md modification
60
- --no-hooks Skip hook installation (no auto-checkpoint)
61
- --hook-port PORT Sidecar port for hooks (default: 8401)
62
-
63
- Restart Claude Code after setup to pick up the new MCP server.
64
- `.trim());
65
- }
66
-
67
- // ─── SETUP ──────────────────────────────────────
68
-
69
- function setup() {
70
- const cwd = process.cwd();
71
- const projectName = basename(cwd).toLowerCase().replace(/[^a-z0-9-]/g, '-');
72
-
73
- // Parse flags
74
- let agentId = projectName;
75
- let dbPath: string | null = null;
76
- let skipClaudeMd = false;
77
- let isGlobal = false;
78
- let skipHooks = false;
79
- let hookPort = '8401';
80
-
81
- for (let i = 1; i < args.length; i++) {
82
- if (args[i] === '--agent-id' && args[i + 1]) {
83
- agentId = args[++i];
84
- } else if (args[i] === '--db-path' && args[i + 1]) {
85
- dbPath = args[++i];
86
- } else if (args[i] === '--no-claude-md') {
87
- skipClaudeMd = true;
88
- } else if (args[i] === '--no-hooks') {
89
- skipHooks = true;
90
- } else if (args[i] === '--hook-port' && args[i + 1]) {
91
- hookPort = args[++i];
92
- } else if (args[i] === '--global') {
93
- isGlobal = true;
94
- agentId = 'claude'; // unified agent ID for global setup
95
- }
96
- }
97
-
98
- // Find the package root (where src/mcp.ts lives)
99
- const packageRoot = resolve(__dirname, '..');
100
- const mcpScript = join(packageRoot, 'src', 'mcp.ts');
101
- const mcpDist = join(packageRoot, 'dist', 'mcp.js');
102
-
103
- // Determine DB path default to <awm-root>/data/memory.db (shared across projects)
104
- if (!dbPath) {
105
- dbPath = join(packageRoot, 'data', 'memory.db');
106
- }
107
- const dbDir = dirname(dbPath);
108
-
109
- // Ensure data directory exists
110
- if (!existsSync(dbDir)) {
111
- mkdirSync(dbDir, { recursive: true });
112
- console.log(`Created data directory: ${dbDir}`);
113
- }
114
-
115
- // Generate hook secret (or reuse existing one)
116
- let hookSecret = '';
117
- const secretPath = join(dirname(dbPath!), '.awm-hook-secret');
118
- if (existsSync(secretPath)) {
119
- hookSecret = readFileSync(secretPath, 'utf-8').trim();
120
- }
121
- if (!hookSecret) {
122
- hookSecret = randomBytes(32).toString('hex');
123
- mkdirSync(dirname(secretPath), { recursive: true });
124
- writeFileSync(secretPath, hookSecret + '\n');
125
- }
126
-
127
- // Determine command based on platform and whether dist exists
128
- const isWindows = process.platform === 'win32';
129
- const hasDist = existsSync(mcpDist);
130
-
131
- const envVars: Record<string, string> = {
132
- AWM_DB_PATH: (isWindows ? dbPath!.replace(/\\/g, '/') : dbPath!),
133
- AWM_AGENT_ID: agentId,
134
- AWM_HOOK_PORT: hookPort,
135
- AWM_HOOK_SECRET: hookSecret,
136
- };
137
-
138
- let mcpConfig: { command: string; args: string[]; env: Record<string, string> };
139
-
140
- if (hasDist) {
141
- mcpConfig = {
142
- command: 'node',
143
- args: [mcpDist.replace(/\\/g, '/')],
144
- env: envVars,
145
- };
146
- } else if (isWindows) {
147
- mcpConfig = {
148
- command: 'cmd',
149
- args: ['/c', 'npx', 'tsx', mcpScript.replace(/\\/g, '/')],
150
- env: envVars,
151
- };
152
- } else {
153
- mcpConfig = {
154
- command: 'npx',
155
- args: ['tsx', mcpScript],
156
- env: envVars,
157
- };
158
- }
159
-
160
- // Read or create .mcp.json
161
- const mcpJsonPath = isGlobal ? join(osHomedir(), '.mcp.json') : join(cwd, '.mcp.json');
162
- let existing: any = { mcpServers: {} };
163
- if (existsSync(mcpJsonPath)) {
164
- try {
165
- existing = JSON.parse(readFileSync(mcpJsonPath, 'utf-8'));
166
- if (!existing.mcpServers) existing.mcpServers = {};
167
- } catch {
168
- existing = { mcpServers: {} };
169
- }
170
- }
171
-
172
- existing.mcpServers['agent-working-memory'] = mcpConfig;
173
- writeFileSync(mcpJsonPath, JSON.stringify(existing, null, 2) + '\n');
174
-
175
- // Auto-append CLAUDE.md snippet unless --no-claude-md
176
- let claudeMdAction = '';
177
- const claudeMdSnippet = `
178
-
179
- ## Memory (AWM)
180
- You have persistent memory via the agent-working-memory MCP server.
181
- - At conversation start: ALWAYS call memory_restore to recover previous context
182
- - When you learn something important: call memory_write PROACTIVELY — do not wait to be asked
183
- - When you need past context: call memory_recall (use the "query" parameter)
184
- - Before long operations: ALWAYS call memory_task_begin to checkpoint and recall context
185
- - When finishing a task: call memory_task_end with a summary of what was accomplished
186
- - After using a recalled memory: call memory_feedback (useful/not-useful)
187
- - To retract incorrect info: call memory_retract
188
- - To manage tasks: call memory_task_add, memory_task_update, memory_task_list, memory_task_next
189
- - Auto-checkpoint: hooks auto-save on context compaction and session end (no action needed)
190
- `;
191
-
192
- // For global: write to ~/.claude/CLAUDE.md (loaded by Claude Code in every session)
193
- // For project: write to ./CLAUDE.md in the current directory
194
- const claudeMdPath = isGlobal
195
- ? join(osHomedir(), '.claude', 'CLAUDE.md')
196
- : join(cwd, 'CLAUDE.md');
197
-
198
- // Ensure parent directory exists (for ~/.claude/CLAUDE.md)
199
- const claudeMdDir = dirname(claudeMdPath);
200
- if (!existsSync(claudeMdDir)) {
201
- mkdirSync(claudeMdDir, { recursive: true });
202
- }
203
-
204
- if (skipClaudeMd) {
205
- claudeMdAction = ' CLAUDE.md: skipped (--no-claude-md)';
206
- } else if (existsSync(claudeMdPath)) {
207
- const content = readFileSync(claudeMdPath, 'utf-8');
208
- if (content.includes('## Memory (AWM)')) {
209
- claudeMdAction = ' CLAUDE.md: already has AWM section (skipped)';
210
- } else {
211
- writeFileSync(claudeMdPath, content.trimEnd() + '\n' + claudeMdSnippet);
212
- claudeMdAction = ' CLAUDE.md: appended AWM workflow section';
213
- }
214
- } else {
215
- const title = isGlobal ? '# Global Instructions' : `# ${basename(cwd)}`;
216
- writeFileSync(claudeMdPath, `${title}\n${claudeMdSnippet}`);
217
- claudeMdAction = ' CLAUDE.md: created with AWM workflow section';
218
- }
219
-
220
- // --- Hook configuration ---
221
- let hookAction = '';
222
- if (skipHooks) {
223
- hookAction = ' Hooks: skipped (--no-hooks)';
224
- } else {
225
- // Write hooks to Claude Code settings (~/.claude/settings.json)
226
- const settingsPath = join(osHomedir(), '.claude', 'settings.json');
227
- let settings: any = {};
228
- if (existsSync(settingsPath)) {
229
- try {
230
- settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
231
- } catch {
232
- settings = {};
233
- }
234
- }
235
-
236
- if (!settings.hooks) settings.hooks = {};
237
-
238
- const hookUrl = `http://127.0.0.1:${hookPort}/hooks/checkpoint`;
239
-
240
- // PreCompact auto-checkpoint before context compaction
241
- settings.hooks.PreCompact = [{
242
- matcher: '',
243
- hooks: [{
244
- type: 'command',
245
- command: `curl -sf -X POST ${hookUrl} -H "Content-Type: application/json" -H "Authorization: Bearer ${hookSecret}" -d "{\\"hook_event_name\\":\\"PreCompact\\"}" --max-time 5`,
246
- timeout: 10,
247
- }],
248
- }];
249
-
250
- // SessionEnd — auto-checkpoint on session close
251
- settings.hooks.SessionEnd = [{
252
- matcher: '',
253
- hooks: [{
254
- type: 'command',
255
- command: `curl -sf -X POST ${hookUrl} -H "Content-Type: application/json" -H "Authorization: Bearer ${hookSecret}" -d "{\\"hook_event_name\\":\\"SessionEnd\\"}" --max-time 5`,
256
- timeout: 10,
257
- }],
258
- }];
259
-
260
- // Ensure settings directory exists
261
- const settingsDir = dirname(settingsPath);
262
- if (!existsSync(settingsDir)) {
263
- mkdirSync(settingsDir, { recursive: true });
264
- }
265
-
266
- writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
267
- hookAction = ` Hooks: PreCompact + SessionEnd → auto-checkpoint (port ${hookPort})`;
268
- }
269
-
270
- const scope = isGlobal ? 'globally (all projects)' : cwd;
271
- console.log(`
272
- AWM configured ${isGlobal ? 'globally' : 'for: ' + cwd}
273
-
274
- Agent ID: ${agentId}
275
- DB path: ${dbPath}
276
- MCP config: ${mcpJsonPath}
277
- Hook port: ${hookPort}
278
- Hook secret: ${hookSecret.slice(0, 8)}...
279
- ${claudeMdAction}
280
- ${hookAction}
281
-
282
- Next steps:
283
- 1. Restart Claude Code to pick up the MCP server
284
- 2. The memory tools will appear automatically
285
- 3. Hooks auto-checkpoint on context compaction and session end${isGlobal ? '\n 4. One brain across all your projects no per-project setup needed' : ''}
286
- `.trim());
287
- }
288
-
289
- // ─── MCP ──────────────────────────────────────
290
-
291
- async function mcp() {
292
- // Dynamic import to avoid loading heavy deps for setup/health commands
293
- await import('./mcp.js');
294
- }
295
-
296
- // ─── SERVE ──────────────────────────────────────
297
-
298
- async function serve() {
299
- // Parse --port flag
300
- for (let i = 1; i < args.length; i++) {
301
- if (args[i] === '--port' && args[i + 1]) {
302
- process.env.AWM_PORT = args[++i];
303
- }
304
- }
305
- await import('./index.js');
306
- }
307
-
308
- // ─── HEALTH ──────────────────────────────────────
309
-
310
- function health() {
311
- let port = '8400';
312
- for (let i = 1; i < args.length; i++) {
313
- if (args[i] === '--port' && args[i + 1]) {
314
- port = args[++i];
315
- }
316
- }
317
-
318
- try {
319
- const result = execSync(`curl -sf http://localhost:${port}/health`, {
320
- encoding: 'utf8',
321
- timeout: 5000,
322
- });
323
- const data = JSON.parse(result);
324
- console.log(`OK — v${data.version} (${data.timestamp})`);
325
- } catch {
326
- console.error(`Cannot reach AWM server on port ${port}`);
327
- process.exit(1);
328
- }
329
- }
330
-
331
- // ─── Dispatch ──────────────────────────────────────
332
-
333
- switch (command) {
334
- case 'setup':
335
- setup();
336
- break;
337
- case 'mcp':
338
- mcp();
339
- break;
340
- case 'serve':
341
- serve();
342
- break;
343
- case 'health':
344
- health();
345
- break;
346
- case '--help':
347
- case '-h':
348
- case undefined:
349
- printUsage();
350
- break;
351
- default:
352
- console.error(`Unknown command: ${command}`);
353
- printUsage();
354
- process.exit(1);
355
- }
1
+ #!/usr/bin/env node
2
+ // Copyright 2026 Robert Winter / Complete Ideas
3
+ // SPDX-License-Identifier: Apache-2.0
4
+
5
+ /**
6
+ * CLI entrypoint for AgentWorkingMemory.
7
+ *
8
+ * Commands:
9
+ * awm setupconfigure MCP for the current project
10
+ * awm mcp start the MCP server (called by Claude Code)
11
+ * awm serve — start the HTTP API server
12
+ * awm health — check if a running server is healthy
13
+ */
14
+
15
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
16
+ import { resolve, basename, join, dirname } from 'node:path';
17
+ import { execSync } from 'node:child_process';
18
+ import { randomBytes } from 'node:crypto';
19
+ import { fileURLToPath } from 'node:url';
20
+ import { homedir as osHomedir } from 'node:os';
21
+
22
+ const __filename = fileURLToPath(import.meta.url);
23
+ const __dirname = dirname(__filename);
24
+
25
+ // Load .env if present
26
+ try {
27
+ const envPath = resolve(process.cwd(), '.env');
28
+ const envContent = readFileSync(envPath, 'utf-8');
29
+ for (const line of envContent.split('\n')) {
30
+ const trimmed = line.trim();
31
+ if (!trimmed || trimmed.startsWith('#')) continue;
32
+ const eqIdx = trimmed.indexOf('=');
33
+ if (eqIdx === -1) continue;
34
+ const key = trimmed.slice(0, eqIdx).trim();
35
+ const val = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, '');
36
+ if (!process.env[key]) process.env[key] = val;
37
+ }
38
+ } catch { /* No .env file */ }
39
+
40
+ const args = process.argv.slice(2);
41
+ const command = args[0];
42
+
43
+ function printUsage() {
44
+ console.log(`
45
+ AgentWorkingMemory — Cognitive memory for AI agents
46
+
47
+ Usage:
48
+ awm setup [--global] [--agent-id <id>] [--db-path <path>] [--no-claude-md]
49
+ [--no-hooks] [--hook-port <port>] Configure MCP for Claude Code
50
+ awm mcp Start MCP server (used by Claude Code)
51
+ awm serve [--port <port>] Start HTTP API server
52
+ awm health [--port <port>] Check server health
53
+
54
+ Setup:
55
+ awm setup --global Recommended. Writes ~/.mcp.json so AWM is available
56
+ in every project one brain across all your work.
57
+
58
+ awm setup Project-level. Writes .mcp.json in the current directory
59
+ and appends workflow instructions to CLAUDE.md.
60
+
61
+ --no-claude-md Skip CLAUDE.md modification
62
+ --no-hooks Skip hook installation (no auto-checkpoint)
63
+ --hook-port PORT Sidecar port for hooks (default: 8401)
64
+
65
+ Restart Claude Code after setup to pick up the new MCP server.
66
+ `.trim());
67
+ }
68
+
69
+ // ─── SETUP ──────────────────────────────────────
70
+
71
+ function setup() {
72
+ const cwd = process.cwd();
73
+ const projectName = basename(cwd).toLowerCase().replace(/[^a-z0-9-]/g, '-');
74
+
75
+ // Parse flags
76
+ let agentId = projectName;
77
+ let dbPath: string | null = null;
78
+ let skipClaudeMd = false;
79
+ let isGlobal = false;
80
+ let skipHooks = false;
81
+ let hookPort = '8401';
82
+
83
+ for (let i = 1; i < args.length; i++) {
84
+ if (args[i] === '--agent-id' && args[i + 1]) {
85
+ agentId = args[++i];
86
+ } else if (args[i] === '--db-path' && args[i + 1]) {
87
+ dbPath = args[++i];
88
+ } else if (args[i] === '--no-claude-md') {
89
+ skipClaudeMd = true;
90
+ } else if (args[i] === '--no-hooks') {
91
+ skipHooks = true;
92
+ } else if (args[i] === '--hook-port' && args[i + 1]) {
93
+ hookPort = args[++i];
94
+ } else if (args[i] === '--global') {
95
+ isGlobal = true;
96
+ agentId = 'claude'; // unified agent ID for global setup
97
+ }
98
+ }
99
+
100
+ // Find the package root (where src/mcp.ts lives)
101
+ const packageRoot = resolve(__dirname, '..');
102
+ const mcpScript = join(packageRoot, 'src', 'mcp.ts');
103
+ const mcpDist = join(packageRoot, 'dist', 'mcp.js');
104
+
105
+ // Determine DB path — default to <awm-root>/data/memory.db (shared across projects)
106
+ if (!dbPath) {
107
+ dbPath = join(packageRoot, 'data', 'memory.db');
108
+ }
109
+ const dbDir = dirname(dbPath);
110
+
111
+ // Ensure data directory exists
112
+ if (!existsSync(dbDir)) {
113
+ mkdirSync(dbDir, { recursive: true });
114
+ console.log(`Created data directory: ${dbDir}`);
115
+ }
116
+
117
+ // Generate hook secret (or reuse existing one)
118
+ let hookSecret = '';
119
+ const secretPath = join(dirname(dbPath!), '.awm-hook-secret');
120
+ if (existsSync(secretPath)) {
121
+ hookSecret = readFileSync(secretPath, 'utf-8').trim();
122
+ }
123
+ if (!hookSecret) {
124
+ hookSecret = randomBytes(32).toString('hex');
125
+ mkdirSync(dirname(secretPath), { recursive: true });
126
+ writeFileSync(secretPath, hookSecret + '\n');
127
+ }
128
+
129
+ // Determine command based on platform and whether dist exists
130
+ const isWindows = process.platform === 'win32';
131
+ const hasDist = existsSync(mcpDist);
132
+
133
+ const envVars: Record<string, string> = {
134
+ AWM_DB_PATH: (isWindows ? dbPath!.replace(/\\/g, '/') : dbPath!),
135
+ AWM_AGENT_ID: agentId,
136
+ AWM_HOOK_PORT: hookPort,
137
+ AWM_HOOK_SECRET: hookSecret,
138
+ };
139
+
140
+ let mcpConfig: { command: string; args: string[]; env: Record<string, string> };
141
+
142
+ if (hasDist) {
143
+ mcpConfig = {
144
+ command: 'node',
145
+ args: [mcpDist.replace(/\\/g, '/')],
146
+ env: envVars,
147
+ };
148
+ } else if (isWindows) {
149
+ mcpConfig = {
150
+ command: 'cmd',
151
+ args: ['/c', 'npx', 'tsx', mcpScript.replace(/\\/g, '/')],
152
+ env: envVars,
153
+ };
154
+ } else {
155
+ mcpConfig = {
156
+ command: 'npx',
157
+ args: ['tsx', mcpScript],
158
+ env: envVars,
159
+ };
160
+ }
161
+
162
+ // Read or create .mcp.json
163
+ const mcpJsonPath = isGlobal ? join(osHomedir(), '.mcp.json') : join(cwd, '.mcp.json');
164
+ let existing: any = { mcpServers: {} };
165
+ if (existsSync(mcpJsonPath)) {
166
+ try {
167
+ existing = JSON.parse(readFileSync(mcpJsonPath, 'utf-8'));
168
+ if (!existing.mcpServers) existing.mcpServers = {};
169
+ } catch {
170
+ existing = { mcpServers: {} };
171
+ }
172
+ }
173
+
174
+ existing.mcpServers['agent-working-memory'] = mcpConfig;
175
+ writeFileSync(mcpJsonPath, JSON.stringify(existing, null, 2) + '\n');
176
+
177
+ // Auto-append CLAUDE.md snippet unless --no-claude-md
178
+ let claudeMdAction = '';
179
+ const claudeMdSnippet = `
180
+
181
+ ## Memory (AWM)
182
+ You have persistent memory via the agent-working-memory MCP server.
183
+
184
+ ### Lifecycle (always do these)
185
+ - Session start: call memory_restore to recover previous context
186
+ - Starting a task: call memory_task_begin (checkpoints + recalls relevant memories)
187
+ - Finishing a task: call memory_task_end with a summary
188
+ - Auto-checkpoint: hooks handle compaction, session end, and 15-min timer (no action needed)
189
+
190
+ ### Write memory when:
191
+ - A project decision is made or changed
192
+ - A root cause is discovered after debugging
193
+ - A reusable implementation pattern is established
194
+ - A user preference, constraint, or requirement is clarified
195
+ - A prior assumption is found to be wrong
196
+ - A significant piece of work is completed
197
+
198
+ ### Recall memory when:
199
+ - Starting work on a new task or subsystem
200
+ - Re-entering code you haven't touched recently
201
+ - After a failed attempt — check if there's prior knowledge
202
+ - Before refactoring or making architectural changes
203
+ - When a topic comes up that you might have prior context on
204
+
205
+ ### Also:
206
+ - After using a recalled memory: call memory_feedback (useful/not-useful)
207
+ - To correct wrong info: call memory_retract
208
+ - To track work items: memory_task_add, memory_task_update, memory_task_list, memory_task_next
209
+ `;
210
+
211
+ // For global: write to ~/.claude/CLAUDE.md (loaded by Claude Code in every session)
212
+ // For project: write to ./CLAUDE.md in the current directory
213
+ const claudeMdPath = isGlobal
214
+ ? join(osHomedir(), '.claude', 'CLAUDE.md')
215
+ : join(cwd, 'CLAUDE.md');
216
+
217
+ // Ensure parent directory exists (for ~/.claude/CLAUDE.md)
218
+ const claudeMdDir = dirname(claudeMdPath);
219
+ if (!existsSync(claudeMdDir)) {
220
+ mkdirSync(claudeMdDir, { recursive: true });
221
+ }
222
+
223
+ if (skipClaudeMd) {
224
+ claudeMdAction = ' CLAUDE.md: skipped (--no-claude-md)';
225
+ } else if (existsSync(claudeMdPath)) {
226
+ const content = readFileSync(claudeMdPath, 'utf-8');
227
+ if (content.includes('## Memory (AWM)')) {
228
+ claudeMdAction = ' CLAUDE.md: already has AWM section (skipped)';
229
+ } else {
230
+ writeFileSync(claudeMdPath, content.trimEnd() + '\n' + claudeMdSnippet);
231
+ claudeMdAction = ' CLAUDE.md: appended AWM workflow section';
232
+ }
233
+ } else {
234
+ const title = isGlobal ? '# Global Instructions' : `# ${basename(cwd)}`;
235
+ writeFileSync(claudeMdPath, `${title}\n${claudeMdSnippet}`);
236
+ claudeMdAction = ' CLAUDE.md: created with AWM workflow section';
237
+ }
238
+
239
+ // --- Hook configuration ---
240
+ let hookAction = '';
241
+ if (skipHooks) {
242
+ hookAction = ' Hooks: skipped (--no-hooks)';
243
+ } else {
244
+ // Write hooks to Claude Code settings (~/.claude/settings.json)
245
+ const settingsPath = join(osHomedir(), '.claude', 'settings.json');
246
+ let settings: any = {};
247
+ if (existsSync(settingsPath)) {
248
+ try {
249
+ settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
250
+ } catch {
251
+ settings = {};
252
+ }
253
+ }
254
+
255
+ if (!settings.hooks) settings.hooks = {};
256
+
257
+ const hookUrl = `http://127.0.0.1:${hookPort}/hooks/checkpoint`;
258
+
259
+ // Stop — remind Claude to write/recall/switch tasks after each response
260
+ settings.hooks.Stop = [{
261
+ matcher: '',
262
+ hooks: [{
263
+ type: 'command',
264
+ command: 'echo "MEMORY: (1) Did you learn anything new? Call memory_write. (2) Are you about to work on a topic you might have prior knowledge about? Call memory_recall. (3) Switching tasks? Call memory_task_begin."',
265
+ timeout: 5,
266
+ async: true,
267
+ }],
268
+ }];
269
+
270
+ // PreCompact auto-checkpoint before context compaction
271
+ settings.hooks.PreCompact = [{
272
+ matcher: '',
273
+ hooks: [{
274
+ type: 'command',
275
+ command: `curl -sf -X POST ${hookUrl} -H "Content-Type: application/json" -H "Authorization: Bearer ${hookSecret}" -d "{\\"hook_event_name\\":\\"PreCompact\\"}" --max-time 2`,
276
+ timeout: 3,
277
+ }],
278
+ }];
279
+
280
+ // SessionEnd — auto-checkpoint on session close (fast timeout to avoid cancellation)
281
+ settings.hooks.SessionEnd = [{
282
+ matcher: '',
283
+ hooks: [{
284
+ type: 'command',
285
+ command: `curl -sf -X POST ${hookUrl} -H "Content-Type: application/json" -H "Authorization: Bearer ${hookSecret}" -d "{\\"hook_event_name\\":\\"SessionEnd\\"}" --max-time 2`,
286
+ timeout: 3,
287
+ }],
288
+ }];
289
+
290
+ // Ensure settings directory exists
291
+ const settingsDir = dirname(settingsPath);
292
+ if (!existsSync(settingsDir)) {
293
+ mkdirSync(settingsDir, { recursive: true });
294
+ }
295
+
296
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
297
+ hookAction = ` Hooks: Stop (memory reminder) + PreCompact + SessionEnd → auto-checkpoint (port ${hookPort})`;
298
+ }
299
+
300
+ const scope = isGlobal ? 'globally (all projects)' : cwd;
301
+ console.log(`
302
+ AWM configured ${isGlobal ? 'globally' : 'for: ' + cwd}
303
+
304
+ Agent ID: ${agentId}
305
+ DB path: ${dbPath}
306
+ MCP config: ${mcpJsonPath}
307
+ Hook port: ${hookPort}
308
+ Hook secret: ${hookSecret.slice(0, 8)}...
309
+ ${claudeMdAction}
310
+ ${hookAction}
311
+
312
+ Next steps:
313
+ 1. Restart Claude Code to pick up the MCP server
314
+ 2. The memory tools will appear automatically
315
+ 3. Hooks auto-checkpoint on context compaction and session end${isGlobal ? '\n 4. One brain across all your projects — no per-project setup needed' : ''}
316
+ `.trim());
317
+ }
318
+
319
+ // ─── MCP ──────────────────────────────────────
320
+
321
+ async function mcp() {
322
+ // Dynamic import to avoid loading heavy deps for setup/health commands
323
+ await import('./mcp.js');
324
+ }
325
+
326
+ // ─── SERVE ──────────────────────────────────────
327
+
328
+ async function serve() {
329
+ // Parse --port flag
330
+ for (let i = 1; i < args.length; i++) {
331
+ if (args[i] === '--port' && args[i + 1]) {
332
+ process.env.AWM_PORT = args[++i];
333
+ }
334
+ }
335
+ await import('./index.js');
336
+ }
337
+
338
+ // ─── HEALTH ──────────────────────────────────────
339
+
340
+ function health() {
341
+ let port = '8400';
342
+ for (let i = 1; i < args.length; i++) {
343
+ if (args[i] === '--port' && args[i + 1]) {
344
+ port = args[++i];
345
+ }
346
+ }
347
+
348
+ try {
349
+ const result = execSync(`curl -sf http://localhost:${port}/health`, {
350
+ encoding: 'utf8',
351
+ timeout: 5000,
352
+ });
353
+ const data = JSON.parse(result);
354
+ console.log(`OK — v${data.version} (${data.timestamp})`);
355
+ } catch {
356
+ console.error(`Cannot reach AWM server on port ${port}`);
357
+ process.exit(1);
358
+ }
359
+ }
360
+
361
+ // ─── Dispatch ──────────────────────────────────────
362
+
363
+ switch (command) {
364
+ case 'setup':
365
+ setup();
366
+ break;
367
+ case 'mcp':
368
+ mcp();
369
+ break;
370
+ case 'serve':
371
+ serve();
372
+ break;
373
+ case 'health':
374
+ health();
375
+ break;
376
+ case '--help':
377
+ case '-h':
378
+ case undefined:
379
+ printUsage();
380
+ break;
381
+ default:
382
+ console.error(`Unknown command: ${command}`);
383
+ printUsage();
384
+ process.exit(1);
385
+ }