agent-working-memory 0.4.2 → 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 +19 -3
  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 +69 -67
  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 +2 -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 -383
  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 +2 -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,383 +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
-
182
- ### Lifecycle (always do these)
183
- - Session start: call memory_restore to recover previous context
184
- - Starting a task: call memory_task_begin (checkpoints + recalls relevant memories)
185
- - Finishing a task: call memory_task_end with a summary
186
- - Auto-checkpoint: hooks handle compaction, session end, and 15-min timer (no action needed)
187
-
188
- ### Write memory when:
189
- - A project decision is made or changed
190
- - A root cause is discovered after debugging
191
- - A reusable implementation pattern is established
192
- - A user preference, constraint, or requirement is clarified
193
- - A prior assumption is found to be wrong
194
- - A significant piece of work is completed
195
-
196
- ### Recall memory when:
197
- - Starting work on a new task or subsystem
198
- - Re-entering code you haven't touched recently
199
- - After a failed attempt check if there's prior knowledge
200
- - Before refactoring or making architectural changes
201
- - When a topic comes up that you might have prior context on
202
-
203
- ### Also:
204
- - After using a recalled memory: call memory_feedback (useful/not-useful)
205
- - To correct wrong info: call memory_retract
206
- - To track work items: memory_task_add, memory_task_update, memory_task_list, memory_task_next
207
- `;
208
-
209
- // For global: write to ~/.claude/CLAUDE.md (loaded by Claude Code in every session)
210
- // For project: write to ./CLAUDE.md in the current directory
211
- const claudeMdPath = isGlobal
212
- ? join(osHomedir(), '.claude', 'CLAUDE.md')
213
- : join(cwd, 'CLAUDE.md');
214
-
215
- // Ensure parent directory exists (for ~/.claude/CLAUDE.md)
216
- const claudeMdDir = dirname(claudeMdPath);
217
- if (!existsSync(claudeMdDir)) {
218
- mkdirSync(claudeMdDir, { recursive: true });
219
- }
220
-
221
- if (skipClaudeMd) {
222
- claudeMdAction = ' CLAUDE.md: skipped (--no-claude-md)';
223
- } else if (existsSync(claudeMdPath)) {
224
- const content = readFileSync(claudeMdPath, 'utf-8');
225
- if (content.includes('## Memory (AWM)')) {
226
- claudeMdAction = ' CLAUDE.md: already has AWM section (skipped)';
227
- } else {
228
- writeFileSync(claudeMdPath, content.trimEnd() + '\n' + claudeMdSnippet);
229
- claudeMdAction = ' CLAUDE.md: appended AWM workflow section';
230
- }
231
- } else {
232
- const title = isGlobal ? '# Global Instructions' : `# ${basename(cwd)}`;
233
- writeFileSync(claudeMdPath, `${title}\n${claudeMdSnippet}`);
234
- claudeMdAction = ' CLAUDE.md: created with AWM workflow section';
235
- }
236
-
237
- // --- Hook configuration ---
238
- let hookAction = '';
239
- if (skipHooks) {
240
- hookAction = ' Hooks: skipped (--no-hooks)';
241
- } else {
242
- // Write hooks to Claude Code settings (~/.claude/settings.json)
243
- const settingsPath = join(osHomedir(), '.claude', 'settings.json');
244
- let settings: any = {};
245
- if (existsSync(settingsPath)) {
246
- try {
247
- settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
248
- } catch {
249
- settings = {};
250
- }
251
- }
252
-
253
- if (!settings.hooks) settings.hooks = {};
254
-
255
- const hookUrl = `http://127.0.0.1:${hookPort}/hooks/checkpoint`;
256
-
257
- // Stop remind Claude to write/recall/switch tasks after each response
258
- settings.hooks.Stop = [{
259
- matcher: '',
260
- hooks: [{
261
- type: 'command',
262
- 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."',
263
- timeout: 5,
264
- async: true,
265
- }],
266
- }];
267
-
268
- // PreCompact — auto-checkpoint before context compaction
269
- settings.hooks.PreCompact = [{
270
- matcher: '',
271
- hooks: [{
272
- type: 'command',
273
- command: `curl -sf -X POST ${hookUrl} -H "Content-Type: application/json" -H "Authorization: Bearer ${hookSecret}" -d "{\\"hook_event_name\\":\\"PreCompact\\"}" --max-time 2`,
274
- timeout: 3,
275
- }],
276
- }];
277
-
278
- // SessionEnd — auto-checkpoint on session close (fast timeout to avoid cancellation)
279
- settings.hooks.SessionEnd = [{
280
- matcher: '',
281
- hooks: [{
282
- type: 'command',
283
- command: `curl -sf -X POST ${hookUrl} -H "Content-Type: application/json" -H "Authorization: Bearer ${hookSecret}" -d "{\\"hook_event_name\\":\\"SessionEnd\\"}" --max-time 2`,
284
- timeout: 3,
285
- }],
286
- }];
287
-
288
- // Ensure settings directory exists
289
- const settingsDir = dirname(settingsPath);
290
- if (!existsSync(settingsDir)) {
291
- mkdirSync(settingsDir, { recursive: true });
292
- }
293
-
294
- writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
295
- hookAction = ` Hooks: Stop (memory reminder) + PreCompact + SessionEnd → auto-checkpoint (port ${hookPort})`;
296
- }
297
-
298
- const scope = isGlobal ? 'globally (all projects)' : cwd;
299
- console.log(`
300
- AWM configured ${isGlobal ? 'globally' : 'for: ' + cwd}
301
-
302
- Agent ID: ${agentId}
303
- DB path: ${dbPath}
304
- MCP config: ${mcpJsonPath}
305
- Hook port: ${hookPort}
306
- Hook secret: ${hookSecret.slice(0, 8)}...
307
- ${claudeMdAction}
308
- ${hookAction}
309
-
310
- Next steps:
311
- 1. Restart Claude Code to pick up the MCP server
312
- 2. The memory tools will appear automatically
313
- 3. Hooks auto-checkpoint on context compaction and session end${isGlobal ? '\n 4. One brain across all your projects — no per-project setup needed' : ''}
314
- `.trim());
315
- }
316
-
317
- // ─── MCP ──────────────────────────────────────
318
-
319
- async function mcp() {
320
- // Dynamic import to avoid loading heavy deps for setup/health commands
321
- await import('./mcp.js');
322
- }
323
-
324
- // ─── SERVE ──────────────────────────────────────
325
-
326
- async function serve() {
327
- // Parse --port flag
328
- for (let i = 1; i < args.length; i++) {
329
- if (args[i] === '--port' && args[i + 1]) {
330
- process.env.AWM_PORT = args[++i];
331
- }
332
- }
333
- await import('./index.js');
334
- }
335
-
336
- // ─── HEALTH ──────────────────────────────────────
337
-
338
- function health() {
339
- let port = '8400';
340
- for (let i = 1; i < args.length; i++) {
341
- if (args[i] === '--port' && args[i + 1]) {
342
- port = args[++i];
343
- }
344
- }
345
-
346
- try {
347
- const result = execSync(`curl -sf http://localhost:${port}/health`, {
348
- encoding: 'utf8',
349
- timeout: 5000,
350
- });
351
- const data = JSON.parse(result);
352
- console.log(`OK — v${data.version} (${data.timestamp})`);
353
- } catch {
354
- console.error(`Cannot reach AWM server on port ${port}`);
355
- process.exit(1);
356
- }
357
- }
358
-
359
- // ─── Dispatch ──────────────────────────────────────
360
-
361
- switch (command) {
362
- case 'setup':
363
- setup();
364
- break;
365
- case 'mcp':
366
- mcp();
367
- break;
368
- case 'serve':
369
- serve();
370
- break;
371
- case 'health':
372
- health();
373
- break;
374
- case '--help':
375
- case '-h':
376
- case undefined:
377
- printUsage();
378
- break;
379
- default:
380
- console.error(`Unknown command: ${command}`);
381
- printUsage();
382
- process.exit(1);
383
- }
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
+ }