agent-working-memory 0.14.6 → 0.15.4

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.
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env node
2
+ // Copyright 2026 Robert Winter / Complete Ideas
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ //
5
+ // Entry point for the Claude Code plugin's MCP server.
6
+ //
7
+ // This is a REAL source file, not a string inside the generator, because it is real code:
8
+ // it gets syntax-checked, linted and tested like everything else. `npm run build:plugin`
9
+ // copies it to plugin/bin/ and substitutes __AWM_VERSION__.
10
+ //
11
+ // Two jobs a bare `mcpServers` entry in plugin.json cannot do:
12
+ //
13
+ // 1. Default the store to ~/.awm/memory.db — the SAME file `awm setup --global` writes.
14
+ // Without this the plugin opens its own empty database and "your memory follows you
15
+ // between Claude Code, Desktop and Cowork" is simply false.
16
+ //
17
+ // 2. Find the server. A plugin does not know whether the package is installed globally,
18
+ // sits in a local node_modules, or is a checkout on disk.
19
+ //
20
+ // Fails loudly on stderr. A silent exit here looks like "the memory tools just aren't there",
21
+ // which is the least debuggable failure this could have.
22
+ 'use strict';
23
+
24
+ const { join, resolve, sep } = require('node:path');
25
+ const { homedir } = require('node:os');
26
+ const { existsSync } = require('node:fs');
27
+ const { pathToFileURL } = require('node:url');
28
+ const { spawn, execFileSync } = require('node:child_process');
29
+
30
+ const NL = '\n';
31
+
32
+ // Claude Desktop substitutes user_config values into env, and a field the user cleared
33
+ // arrives as an empty string rather than being absent. An empty AWM_AGENT_ID would override
34
+ // the server's directory-derived default with nothing, so drop empties before anything reads
35
+ // them and let the normal defaults apply.
36
+ for (const k of ['AWM_DB_PATH', 'AWM_AGENT_ID', 'AWM_HOOK_PORT', 'AWM_PACKAGE_ROOT']) {
37
+ if (process.env[k] !== undefined && String(process.env[k]).trim() === '') delete process.env[k];
38
+ }
39
+
40
+ // An explicit AWM_DB_PATH always wins, so per-project pools still work.
41
+ if (!process.env.AWM_DB_PATH) {
42
+ process.env.AWM_DB_PATH = join(homedir(), '.awm', 'memory.db').split(sep).join('/');
43
+ }
44
+
45
+ const tried = [];
46
+ function candidate(p) {
47
+ if (!p) return null;
48
+ tried.push(p);
49
+ return existsSync(p) ? p : null;
50
+ }
51
+
52
+ // In priority order. npx is last: on a cold cache it rebuilds better-sqlite3, which takes
53
+ // minutes and is indistinguishable from a hang.
54
+ let found =
55
+ // 1. An explicit override.
56
+ candidate(process.env.AWM_PACKAGE_ROOT && join(process.env.AWM_PACKAGE_ROOT, 'dist', 'mcp.js')) ||
57
+ // 2. The plugin sitting inside an AWM checkout (plugin/bin -> ../../dist).
58
+ candidate(resolve(__dirname, '..', '..', 'dist', 'mcp.js')) ||
59
+ // 3. A local dependency of the project being worked on.
60
+ candidate(join(process.cwd(), 'node_modules', 'agent-working-memory', 'dist', 'mcp.js'));
61
+
62
+ // 4. A global install. require.resolve does not search global roots, so ask npm once.
63
+ if (!found) {
64
+ try {
65
+ const isWin = process.platform === 'win32';
66
+ const root = execFileSync(isWin ? 'npm.cmd' : 'npm', ['root', '-g'], {
67
+ encoding: 'utf-8',
68
+ shell: isWin,
69
+ stdio: ['ignore', 'pipe', 'ignore'],
70
+ }).trim();
71
+ found = candidate(root && join(root, 'agent-working-memory', 'dist', 'mcp.js'));
72
+ } catch {
73
+ /* npm not on PATH — fall through */
74
+ }
75
+ }
76
+
77
+ // 5. Anywhere node can resolve it from here.
78
+ if (!found) {
79
+ try {
80
+ found = require.resolve('agent-working-memory/dist/mcp.js');
81
+ } catch {
82
+ /* not installed where this script can see it */
83
+ }
84
+ }
85
+
86
+ if (found) {
87
+ // dist/ is ESM ("type": "module"), so import() — require() throws ERR_REQUIRE_ESM.
88
+ import(pathToFileURL(found).href).catch((e) => {
89
+ process.stderr.write('[awm] failed to load ' + found + ': ' + e.message + NL);
90
+ process.exit(1);
91
+ });
92
+ } else {
93
+ // Last resort, pinned so the tools match the hook scripts shipped beside them.
94
+ // shell:true on Windows: spawning npx.cmd directly throws EINVAL since Node 18.20/20.12.
95
+ const isWin = process.platform === 'win32';
96
+ const child = spawn(
97
+ isWin ? 'npx.cmd' : 'npx',
98
+ ['-y', '-p', 'agent-working-memory@__AWM_VERSION__', 'awm', 'mcp'],
99
+ { stdio: 'inherit', env: process.env, shell: isWin }
100
+ );
101
+ child.on('error', (e) => {
102
+ process.stderr.write('[awm] could not start the MCP server: ' + e.message + NL);
103
+ process.stderr.write('[awm] looked in:' + NL);
104
+ for (const t of tried) process.stderr.write(' ' + t + NL);
105
+ process.stderr.write('[awm] fix: npm install -g agent-working-memory' + NL);
106
+ process.exit(1);
107
+ });
108
+ child.on('exit', (code) => process.exit(code == null ? 0 : code));
109
+ }
@@ -49,13 +49,25 @@ export type StoreBackend = 'sqlite' | 'pglite' | 'postgres';
49
49
  function detectBackendFromDisk(): StoreBackend | null {
50
50
  // If AWM_DB_PATH is set, infer from its shape.
51
51
  const explicitPath = process.env.AWM_DB_PATH;
52
- if (explicitPath && existsSync(explicitPath)) {
53
- try {
54
- const stat = statSync(explicitPath);
55
- if (stat.isDirectory()) return 'pglite'; // PGlite uses a directory
56
- if (stat.isFile()) return 'sqlite'; // SQLite is a single file
57
- } catch { /* fall through */ }
58
- }
52
+ if (explicitPath) {
53
+ if (existsSync(explicitPath)) {
54
+ try {
55
+ const stat = statSync(explicitPath);
56
+ if (stat.isDirectory()) return 'pglite'; // PGlite uses a directory
57
+ if (stat.isFile()) return 'sqlite'; // SQLite is a single file
58
+ } catch { /* fall through to the default below */ }
59
+ }
60
+ // The caller named a path that does not exist yet — a brand-new store. Default it to
61
+ // SQLite and STOP: falling through to the cwd conventions below would pick the backend
62
+ // from files that have nothing to do with the path that was asked for.
63
+ //
64
+ // Not hypothetical. Running from a checkout containing a `memory-pglite/` directory
65
+ // made a fresh AWM_DB_PATH=/tmp/new-store.db resolve to PGlite, which is
66
+ // single-process — so two surfaces pointed at 'the same store' would have had the
67
+ // second refuse to start. Found by the cross-surface end-to-end test.
68
+ // PGlite at a new path is still reachable via AWM_STORE_BACKEND=pglite.
69
+ return 'sqlite';
70
+ }
59
71
 
60
72
  // Otherwise look for the conventional defaults in cwd.
61
73
  // PGlite directory wins over SQLite file when both exist — assume the