agent-working-memory 0.14.6 → 0.15.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/cli.ts CHANGED
@@ -47,6 +47,7 @@ AgentWorkingMemory — Cognitive memory for AI agents
47
47
 
48
48
  Usage:
49
49
  awm setup [target] [options] Configure AWM for an AI CLI
50
+ awm plugin Install as a Claude Code plugin (local, no GitHub)
50
51
  awm doctor [target|--all] Validate AWM integrations
51
52
  awm mcp Start MCP server (stdio)
52
53
  awm serve [--port <port>] Start HTTP API server
@@ -145,7 +146,8 @@ async function setup() {
145
146
  // Build context. A previous install's values survive unless a flag overrides them
146
147
  // (0.14.6) — an upgrade must never repoint the database or rename the agent.
147
148
  const existingEnv = adapter.readExistingEnv?.(isGlobal, process.cwd()) ?? null;
148
- const ctx = buildSetupContext({ agentId, dbPath, isGlobal, hookPort, hookPortRange, installPrime, existingEnv });
149
+ // `awm setup claude-code` -> client=claude-code, cursor -> client=cursor, and so on.
150
+ const ctx = buildSetupContext({ agentId, dbPath, isGlobal, hookPort, hookPortRange, installPrime, existingEnv, client: target });
149
151
 
150
152
  // Run adapter
151
153
  const configAction = adapter.writeMcpConfig(ctx);
@@ -172,6 +174,58 @@ Next steps:
172
174
  `.trim());
173
175
  }
174
176
 
177
+ // ─── PLUGIN ──────────────────────────────────────
178
+
179
+ /**
180
+ * Print the two lines that install AWM as a Claude Code plugin FROM THIS INSTALL.
181
+ *
182
+ * Most users are on Windows and will never clone the repository, so
183
+ * `/plugin marketplace add CompleteIdeas/agent-working-memory` — which needs GitHub — is the
184
+ * wrong default for them. npm has already put a complete marketplace on their disk; they
185
+ * just have no way to know where it is. This tells them, with the real resolved path.
186
+ */
187
+ function pluginInfo(): void {
188
+ // dist/cli.js -> package root
189
+ const pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
190
+ const marketplace = join(pkgRoot, '.claude-plugin', 'marketplace.json');
191
+
192
+ if (!existsSync(marketplace)) {
193
+ console.log(`
194
+ AWM ${VERSION} — plugin not found in this install
195
+
196
+ Looked for: ${marketplace}
197
+
198
+ This happens when AWM is running from a source checkout that has not been built,
199
+ or from a package published before the plugin was bundled (< 0.15.3).
200
+
201
+ From a checkout: npm run build && npm run build:plugin
202
+ Otherwise: npm install -g agent-working-memory@latest
203
+ `.trimStart());
204
+ return;
205
+ }
206
+
207
+ console.log(`
208
+ AWM ${VERSION} — install as a Claude Code plugin
209
+
210
+ Paste these two lines into Claude Code:
211
+
212
+ /plugin marketplace add ${pkgRoot}
213
+ /plugin install awm@agent-working-memory
214
+
215
+ Then restart Claude Code. You get 19 memory tools, the session hooks, and the
216
+ usage guidance as a skill.
217
+
218
+ No GitHub access needed — the marketplace is this npm install, already on disk.
219
+
220
+ The store defaults to the same file \`awm setup --global\` uses, so a plugin
221
+ install and a CLI install share one memory rather than quietly keeping two.
222
+
223
+ Check it worked: awm doctor claude-code
224
+ More: https://github.com/CompleteIdeas/agent-working-memory/blob/master/docs/plugin.md
225
+ `.trimStart());
226
+ }
227
+
228
+
175
229
  // ─── DOCTOR ──────────────────────────────────────
176
230
 
177
231
  async function doctor() {
@@ -803,6 +857,9 @@ switch (command) {
803
857
  case 'setup':
804
858
  await setup();
805
859
  break;
860
+ case 'plugin':
861
+ pluginInfo();
862
+ break;
806
863
  case 'doctor':
807
864
  await doctor();
808
865
  break;
@@ -491,6 +491,24 @@ async function createNewEngram(
491
491
 
492
492
  const tags = [...(input.tags ?? [])];
493
493
  if (isLowSalience && !tags.includes('low-salience')) tags.push('low-salience');
494
+
495
+ // Which CLIENT wrote this: `client=claude-code`, `client=claude-desktop`, `client=cursor`.
496
+ //
497
+ // Deliberately not called `surface`: memory_whoami already reports a `surface` field
498
+ // meaning the TRANSPORT (mcp | http), and one word for two concepts is how you lose an
499
+ // hour six months from now. Transport is how the request arrived; client is which
500
+ // application it arrived from.
501
+ // AWM_CLIENT is set by the thing that launches the server (the Claude Code plugin, the
502
+ // Desktop extension, `awm setup`), never by the caller, so it records where a memory came
503
+ // from rather than what the writer claims.
504
+ //
505
+ // Captured by default rather than behind a flag, because provenance is one-way: a tag can
506
+ // be ignored or stripped later, but the origin of a memory written without one is gone for
507
+ // good. An explicit `client=` in the caller's tags always wins.
508
+ const client = (process.env.AWM_CLIENT ?? '').trim();
509
+ if (client && !tags.some(t => t.toLowerCase().startsWith('client='))) {
510
+ tags.push(`client=${client}`);
511
+ }
494
512
 
495
513
  // Auto-tag meta-tags (default-OFF, AWM_AUTOTAG=1). extractMetaTags emits
496
514
  // `entity:<ProperNoun>` and `cat:<category>` tags from concept+content. These
@@ -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