@engramx/client 0.16.0-rc.11 → 0.16.0-rc.13

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 (60) hide show
  1. package/README.md +22 -8
  2. package/dist/EngramClient.d.ts +10 -14
  3. package/dist/EngramClient.js +14 -64
  4. package/dist/EngramClient.js.map +1 -1
  5. package/dist/ProfileManager.d.ts +3 -19
  6. package/dist/ProfileManager.js +1 -58
  7. package/dist/ProfileManager.js.map +1 -1
  8. package/dist/SessionManager.d.ts +1 -1
  9. package/dist/SessionManager.js.map +1 -1
  10. package/dist/claude-setup.d.ts +15 -6
  11. package/dist/claude-setup.js +30 -17
  12. package/dist/claude-setup.js.map +1 -1
  13. package/dist/cli.d.ts +7 -1
  14. package/dist/cli.js +134 -184
  15. package/dist/cli.js.map +1 -1
  16. package/dist/connect-config.d.ts +109 -0
  17. package/dist/connect-config.js +251 -0
  18. package/dist/connect-config.js.map +1 -0
  19. package/dist/encryption.js +7 -1
  20. package/dist/encryption.js.map +1 -1
  21. package/dist/idl.d.ts +16 -1
  22. package/dist/idl.js +9 -1
  23. package/dist/idl.js.map +1 -1
  24. package/dist/index.d.ts +4 -5
  25. package/dist/index.js +5 -4
  26. package/dist/index.js.map +1 -1
  27. package/dist/ledgerfsHash.d.ts +0 -2
  28. package/dist/ledgerfsHash.js +0 -2
  29. package/dist/ledgerfsHash.js.map +1 -1
  30. package/dist/lineage.d.ts +7 -1
  31. package/dist/lineage.js +7 -1
  32. package/dist/lineage.js.map +1 -1
  33. package/dist/memoryArchive.d.ts +12 -77
  34. package/dist/memoryArchive.js.map +1 -1
  35. package/dist/migrate.d.ts +18 -30
  36. package/dist/migrate.js +42 -136
  37. package/dist/migrate.js.map +1 -1
  38. package/dist/openclaw-local.d.ts +86 -0
  39. package/dist/openclaw-local.js +332 -0
  40. package/dist/openclaw-local.js.map +1 -0
  41. package/dist/openclaw-setup.d.ts +82 -3
  42. package/dist/openclaw-setup.js +162 -50
  43. package/dist/openclaw-setup.js.map +1 -1
  44. package/dist/path-utils.d.ts +0 -16
  45. package/dist/path-utils.js +4 -27
  46. package/dist/path-utils.js.map +1 -1
  47. package/dist/schema.d.ts +6 -17
  48. package/dist/schema.js +6 -28
  49. package/dist/schema.js.map +1 -1
  50. package/dist/types.d.ts +46 -453
  51. package/dist/verify.d.ts +2 -0
  52. package/dist/verify.js +11 -0
  53. package/dist/verify.js.map +1 -0
  54. package/dist/version.d.ts +22 -0
  55. package/dist/version.js +22 -0
  56. package/dist/version.js.map +1 -1
  57. package/package.json +17 -5
  58. package/dist/offline.d.ts +0 -21
  59. package/dist/offline.js +0 -104
  60. package/dist/offline.js.map +0 -1
@@ -1,37 +1,50 @@
1
1
  /**
2
2
  * Claude setup — generates MCP config for Claude Code and Claude Cowork.
3
+ *
4
+ * ⟨2026-10-13, the bloat audit⟩ ONE generator, ONE writer. This file built its own
5
+ * `mcpServers.engramx` entry while @engramx/mcp's `connect` built another (connect-config.ts), and
6
+ * the two had drifted: this path was macOS-only, the other per-OS; this one wrote atomically, the
7
+ * other did not; the mainnet spellings and the session-key variable differed. The entry now comes
8
+ * from connect-config.ts (`claudeDesktopConfig`), the Claude Desktop path is per-OS here, and
9
+ * `installMcpConfig` — atomic, refusing a config it cannot parse — is the one writer, which
10
+ * `@engramx/mcp connect --write` uses too.
3
11
  */
4
12
  import * as fs from 'fs';
13
+ import * as os from 'os';
5
14
  import * as path from 'path';
6
15
  import { atomicWriteFileSync } from './path-utils.js';
16
+ import { VERSION } from './version.js';
17
+ import { claudeDesktopConfig } from './connect-config.js';
7
18
  /** Generate the MCP server config block for Claude. */
8
19
  export function generateMcpConfig(options) {
9
- const env = {
10
- ENGRAM_CANISTER_ID: options.canisterId,
11
- ENGRAM_SESSION_KEY_PATH: options.sessionKeyPath,
12
- };
13
- if (options.host && options.host !== 'https://ic0.app') {
14
- env['ENGRAM_HOST'] = options.host;
20
+ return claudeDesktopConfig({
21
+ canisterId: options.canisterId,
22
+ host: options.host,
23
+ clientVersion: options.version ?? VERSION,
24
+ sessionKeyPath: options.sessionKeyPath,
25
+ });
26
+ }
27
+ /** The default claude_desktop_config.json path for this OS. */
28
+ function claudeDesktopConfigPath(home) {
29
+ switch (os.platform()) {
30
+ case 'darwin':
31
+ return path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
32
+ case 'win32':
33
+ return path.join(process.env['APPDATA'] || path.join(home, 'AppData', 'Roaming'), 'Claude', 'claude_desktop_config.json');
34
+ default:
35
+ return path.join(home, '.config', 'Claude', 'claude_desktop_config.json');
15
36
  }
16
- return {
17
- mcpServers: {
18
- engramx: {
19
- command: 'npx',
20
- args: ['-y', '@engramx/mcp'],
21
- env,
22
- },
23
- },
24
- };
25
37
  }
26
38
  /** Return the config paths for each Claude product. */
27
39
  export function claudeConfigPaths() {
28
- const home = process.env['HOME'] || '';
40
+ const home = process.env['HOME'] || os.homedir();
29
41
  return {
30
42
  // Claude Code reads user-scope MCP servers from ~/.claude.json (the file
31
43
  // `claude mcp add --scope user` writes). ~/.claude/settings.json does NOT
32
44
  // carry mcpServers — writing there is a silent no-op for the user.
33
45
  claudeCode: path.join(home, '.claude.json'),
34
- claudeDesktop: path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'),
46
+ // ⟨2026-10-13⟩ Per-OS; it was the macOS path everywhere until the two generators merged.
47
+ claudeDesktop: claudeDesktopConfigPath(home),
35
48
  };
36
49
  }
37
50
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"claude-setup.js","sourceRoot":"","sources":["../src/claude-setup.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"claude-setup.js","sourceRoot":"","sources":["../src/claude-setup.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts CHANGED
@@ -1,2 +1,8 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ /**
3
+ * The run this module starts when it is loaded — the command line's whole life.
4
+ * ⟨2026-10-13⟩ Exported so a test can load the CLI with stubbed network modules and wait for the
5
+ * command to END (test/pair-migrate.test.ts); before, a test could only wait one tick, which is
6
+ * enough for --help and not for pair.
7
+ */
8
+ export declare const finished: Promise<void>;
package/dist/cli.js CHANGED
@@ -6,11 +6,11 @@ import { pair, PairError } from './pair.js';
6
6
  import { NPX_CLIENT, VERSION, pairCommand } from './version.js';
7
7
  import { execFileSync } from 'node:child_process';
8
8
  import * as path from 'path';
9
- import { setupOpenClaw, setupGitVersioning, OpenClawNotFoundError, getOpenClawDir, getMemoryDir, isGitRepo, } from './openclaw-setup.js';
9
+ import { setupOpenClaw, setupGitVersioning, OpenClawNotFoundError, OpenClawPluginOutdatedError, OPENCLAW_PLUGIN, outdatedOpenClawPlugins, getOpenClawDir, getMemoryDir, isGitRepo, } from './openclaw-setup.js';
10
10
  import { generateMcpConfig, claudeConfigPaths, installMcpConfig } from './claude-setup.js';
11
- import { migrateOpenClawMemory, recoverOpenClaw, rollbackCheckpoint, listCheckpoints, hasValidPairing, findIncompleteCheckpoint, resumeMigration, } from './migrate.js';
11
+ import { migrateOpenClawMemory, findOpenClawMemoryFiles, recoverOpenClaw, rollbackCheckpoint, listCheckpoints, hasValidPairing, findIncompleteCheckpoint, resumeMigration, } from './migrate.js';
12
12
  import { EngramClient } from './EngramClient.js';
13
- import { openEngram } from './openEngram.js';
13
+ import { openEngram, shellWord } from './openEngram.js';
14
14
  import { Principal } from '@icp-sdk/core/principal';
15
15
  // Simple colored output helpers
16
16
  const green = (s) => `\x1b[32m${s}\x1b[0m`;
@@ -254,7 +254,7 @@ async function runResumeMigration(clientOptions) {
254
254
  fail(`Resume failed: ${err.message}`);
255
255
  process.exit(1);
256
256
  }
257
- console.log(`\nTo undo: ${bold('npx @engramx/client rollback')}`);
257
+ console.log(`\nTo undo: ${bold(`${NPX_CLIENT} rollback`)}`);
258
258
  }
259
259
  // ── Help text ───────────────────────────────────────────────────
260
260
  // ⟨2026-10-03⟩ Every usage line names the version this help was built with (version.ts): the
@@ -271,10 +271,8 @@ ${bold('Usage:')}
271
271
  ${NPX_CLIENT} migrate --engram <id> [options]
272
272
  ${NPX_CLIENT} rollback [checkpoint-id]
273
273
  ${NPX_CLIENT} git-setup <remote-url>
274
- ${NPX_CLIENT} git-log [file]
275
- ${NPX_CLIENT} git-diff [ref] [--file <path>]
276
- ${NPX_CLIENT} git-revert <ref> --engram <id> [--file <path>]
277
- ${NPX_CLIENT} read <path> --engram <id> [--raw]
274
+ ${NPX_CLIENT} git-revert <ref> [--file <path>]
275
+ ${NPX_CLIENT} read <path> --engram <id>
278
276
 
279
277
  ${bold('Commands:')}
280
278
  pair Pair this agent with an engram: a fresh key for it, enrolled under your lasting key
@@ -283,10 +281,9 @@ ${bold('Commands:')}
283
281
  migrate Migrate local memory to engram (or recover with --recover)
284
282
  rollback Restore files from a checkpoint
285
283
  git-setup Initialize git versioning for the memory directory
286
- git-log Show commit history for the memory repo
287
- git-diff Show changes since a commit (or working tree diff)
288
- git-revert Revert memory to a previous commit and sync to canister
289
- read Read a memory file (use --raw to see raw stored bytes)
284
+ git-revert Revert the local memory repo to a previous commit; prints the owner kit's command
285
+ that puts the reverted files back on the engram (an owner's act)
286
+ read Read one memory file and print its text
290
287
 
291
288
  ${bold('Options (pair):')}
292
289
  --engram <id> Engram canister ID (required)
@@ -299,7 +296,10 @@ ${bold('Options (pair):')}
299
296
  (default: ~/.engramx/root.key; created on first use, never overwritten)
300
297
  --git-remote <url> Enable git versioning and set remote
301
298
  --force Overwrite an existing key for this engram (never the lasting key)
302
- --yes, -y Auto-confirm all prompts (non-interactive mode)
299
+ --migrate Also move this machine's OpenClaw memory files into the engram: each
300
+ is uploaded, then deleted here (undo with rollback). Without it, pair
301
+ moves nothing and says what it found
302
+ --yes, -y Auto-confirm all prompts (non-interactive mode); never adds --migrate
303
303
 
304
304
  ${bold('Options (open-engram):')}
305
305
  --engram <id> The engram this agent works in, which opens the new one (required)
@@ -317,19 +317,13 @@ ${bold('Options (open-engram):')}
317
317
  ${bold('Options (migrate):')}
318
318
  --engram <id> Engram canister ID (required)
319
319
  --recover Recovery mode: download memory from engram to local
320
- --resume Resume an incomplete migration
321
320
  --dry-run Show what would be migrated without uploading
322
321
  --host <url> ICP host (default: https://ic0.app)
323
322
  --key-path <path> The key for this engram (default: ~/.engramx/session.key)
324
323
 
325
324
  ${bold('Options (git-revert):')}
326
- --engram <id> Engram canister ID (required)
327
325
  --file <path> Revert a single file instead of the entire memory dir
328
- --host <url> ICP host (default: https://ic0.app)
329
- --key-path <path> The key for this engram (default: ~/.engramx/session.key)
330
-
331
- ${bold('Options (read):')}
332
- --raw Show raw stored bytes as hex instead of decoded text
326
+ (An incomplete migration is offered for resumption by a plain \`migrate\`.)
333
327
 
334
328
  --help Show this help message
335
329
  `);
@@ -342,16 +336,15 @@ async function main() {
342
336
  target: { type: 'string' },
343
337
  install: { type: 'boolean', default: false },
344
338
  recover: { type: 'boolean', default: false },
345
- resume: { type: 'boolean', default: false },
346
339
  'dry-run': { type: 'boolean', default: false },
347
340
  host: { type: 'string' },
348
341
  'key-path': { type: 'string' },
349
342
  'root-key': { type: 'string' },
350
343
  'git-remote': { type: 'string' },
351
344
  force: { type: 'boolean', default: false },
345
+ migrate: { type: 'boolean', default: false },
352
346
  yes: { type: 'boolean', short: 'y', default: false },
353
347
  file: { type: 'string' },
354
- raw: { type: 'boolean', default: false },
355
348
  help: { type: 'boolean', default: false },
356
349
  },
357
350
  });
@@ -370,11 +363,23 @@ async function main() {
370
363
  process.exit(1);
371
364
  }
372
365
  }
373
- // ── read command (debug) ─────────────────────────────────────
366
+ // ⟨2026-10-13, the bloat audit⟩ `read <path> [--raw]`, `git-log` and `git-diff` were removed: no doc
367
+ // described them and no test ran them, in this repo or the platform. `read --raw` also printed
368
+ // "[object Object]" for every refusal (`fail(result.Err)` on the six-arm error variant) and a
369
+ // "stored as plaintext" line that was false on an encrypted engram. `git log` and `git diff` in
370
+ // the memory directory do what the other two did.
371
+ // ── read command ──────────────────────────────────────────────
372
+ // ⟨RESTORED 2026-10-14⟩ engramx f1907dc3 removed `read` with git-log and git-diff as "undocumented,
373
+ // untested" (the bloat audit, the owner's answers of 2026-10-13). The premise was wrong for `read`:
374
+ // printUsage listed it, downstream end-to-end checks of the packaged CLI read through it, and the
375
+ // staging test plan sent to a design partner tells their agents to read back with it — those checks
376
+ // failed on exactly the four read cases. `read` comes back; its `--raw` option does not (it printed the raw stored bytes through the
377
+ // private actor, was in no doc and no test — the dead part the audit meant). git-log and git-diff stay
378
+ // removed. WHAT WOULD MAKE THIS WRONG: a later ruling that agents read only through the SDK or MCP.
374
379
  if (command === 'read') {
375
380
  const filePath = positionals[1];
376
381
  if (!filePath) {
377
- fail('Usage: npx @engramx/client read <path> --engram <id> [--raw]');
382
+ fail(`Usage: ${NPX_CLIENT} read <path> --engram <id>`);
378
383
  process.exit(1);
379
384
  }
380
385
  if (!values.engram) {
@@ -385,25 +390,8 @@ async function main() {
385
390
  const host = values.host || 'https://ic0.app';
386
391
  const client = new EngramClient({ canisterId: values.engram, sessionKeyPath: keyPath, host });
387
392
  try {
388
- if (values.raw) {
389
- console.warn('WARNING: --raw shows the raw stored bytes (content is stored as plaintext).');
390
- // Show raw stored bytes as hex (no decode)
391
- const result = await client.actor.readMemory(filePath);
392
- if ('Err' in result) {
393
- fail(result.Err);
394
- process.exit(1);
395
- }
396
- const raw = result.Ok;
397
- const bytes = new Uint8Array(raw.content);
398
- console.log(`Path: ${raw.path}`);
399
- console.log(`Version: ${raw.version}`);
400
- console.log(`Size: ${bytes.length} bytes (raw stored, plaintext)`);
401
- console.log(`Hex: ${Buffer.from(bytes).toString('hex').slice(0, 200)}...`);
402
- }
403
- else {
404
- const file = await client.readMemory(filePath);
405
- console.log(file.content);
406
- }
393
+ const file = await client.readMemory(filePath);
394
+ console.log(file.content);
407
395
  }
408
396
  catch (err) {
409
397
  fail(`Read failed: ${err.message}`);
@@ -474,79 +462,19 @@ async function main() {
474
462
  }
475
463
  return;
476
464
  }
477
- // ── git-log command ──────────────────────────────────────────
478
- if (command === 'git-log') {
479
- const memoryDir = getMemoryDir();
480
- if (!isGitRepo(memoryDir)) {
481
- fail('Git versioning not set up. Run: npx @engramx/client git-setup <url>');
482
- process.exit(1);
483
- }
484
- const filePath = positionals[1] || values.file;
485
- const args = ['log', '--oneline', '--no-decorate', '-30'];
486
- if (filePath)
487
- args.push('--', filePath);
488
- try {
489
- const output = execFileSync('git', args, { cwd: memoryDir, stdio: 'pipe' }).toString().trim();
490
- if (output) {
491
- console.log(output);
492
- }
493
- else {
494
- info('No commits yet.');
495
- }
496
- }
497
- catch {
498
- info('No commits yet.');
499
- }
500
- return;
501
- }
502
- // ── git-diff command ────────────────────────────────────────
503
- if (command === 'git-diff') {
504
- const memoryDir = getMemoryDir();
505
- if (!isGitRepo(memoryDir)) {
506
- fail('Git versioning not set up. Run: npx @engramx/client git-setup <url>');
507
- process.exit(1);
508
- }
509
- const ref = positionals[1];
510
- const filePath = values.file;
511
- const args = ['diff'];
512
- if (ref)
513
- args.push(ref);
514
- if (filePath)
515
- args.push('--', filePath);
516
- try {
517
- const output = execFileSync('git', args, { cwd: memoryDir, stdio: 'pipe' }).toString().trim();
518
- if (output) {
519
- console.log(output);
520
- }
521
- else {
522
- info('No changes.');
523
- }
524
- }
525
- catch (err) {
526
- fail(`git diff failed: ${err.message}`);
527
- process.exit(1);
528
- }
529
- return;
530
- }
531
465
  // ── git-revert command ──────────────────────────────────────
532
466
  if (command === 'git-revert') {
533
467
  const ref = positionals[1];
534
468
  if (!ref) {
535
- fail('Usage: npx @engramx/client git-revert <commit-or-ref> --engram <id> [--file <path>]');
536
- process.exit(1);
537
- }
538
- if (!values.engram) {
539
- fail('Missing --engram <canister-id> (needed to sync reverted files to canister)');
469
+ fail(`Usage: ${NPX_CLIENT} git-revert <commit-or-ref> [--file <path>]`);
540
470
  process.exit(1);
541
471
  }
542
472
  const memoryDir = getMemoryDir();
543
473
  if (!isGitRepo(memoryDir)) {
544
- fail('Git versioning not set up. Run: npx @engramx/client git-setup <url>');
474
+ fail(`Git versioning not set up. Run: ${NPX_CLIENT} git-setup <url>`);
545
475
  process.exit(1);
546
476
  }
547
477
  const filePath = values.file;
548
- const keyPath = values['key-path'] || '~/.engramx/session.key';
549
- const host = values.host || 'https://ic0.app';
550
478
  // 1. Git checkout to revert files
551
479
  step(`Reverting to ${ref}${filePath ? ` (file: ${filePath})` : ''}...`);
552
480
  try {
@@ -589,62 +517,13 @@ async function main() {
589
517
  warn(`Git commit failed: ${msg}`);
590
518
  }
591
519
  }
592
- // 3. Restore reverted files on the canister
593
- step('Restoring reverted files on engram canister...');
594
- const client = new EngramClient({ canisterId: values.engram, sessionKeyPath: keyPath, host });
595
- let syncCount = 0;
596
- let syncErrors = 0;
597
- async function syncDir(dir) {
598
- if (!fs.existsSync(dir))
599
- return;
600
- const entries = fs.readdirSync(dir, { withFileTypes: true });
601
- for (const entry of entries) {
602
- const fullPath = `${dir}/${entry.name}`;
603
- if (entry.isDirectory()) {
604
- if (entry.name === '.git')
605
- continue;
606
- await syncDir(fullPath);
607
- }
608
- else if (entry.isFile() && entry.name.endsWith('.md')) {
609
- const relativePath = path.relative(memoryDir, fullPath);
610
- if (relativePath.startsWith('..'))
611
- continue; // skip files outside memoryDir
612
- const content = fs.readFileSync(fullPath, 'utf-8');
613
- try {
614
- // A revert must RESTORE the canister file to the reverted content —
615
- // appendMemory would stack the old content on top of the current, leaving
616
- // a doubled, self-contradictory memory. writeMemory overwrites; it is
617
- // OWNER-gated on the canister, so it respects the append-only invariant for
618
- // agents — a non-owner key is rejected here (a loud, honest failure)
619
- // rather than silently corrupting.
620
- // NOTE: writes plaintext bytes; a memory stored in framed-encrypted (Private)
621
- // mode would need an encryption-aware owner restore in the SDK core (the
622
- // deeper fix — out of scope for the CLI).
623
- const ciphertext = new TextEncoder().encode(content);
624
- const result = await client.actor.writeMemory(relativePath, ciphertext);
625
- if ('Err' in result)
626
- throw new Error(result.Err);
627
- ok(` ${relativePath}`);
628
- syncCount++;
629
- }
630
- catch (err) {
631
- fail(` ${relativePath}: ${err.message}`);
632
- syncErrors++;
633
- }
634
- }
635
- }
636
- }
637
- await syncDir(memoryDir);
638
- if (syncErrors > 0) {
639
- fail(`Restored ${syncCount} file(s) on engram, ${syncErrors} failed`);
640
- info("Restore requires owner credentials — an agent's key cannot overwrite canister memory.");
641
- }
642
- else if (syncCount > 0) {
643
- ok(`Restored ${syncCount} file${syncCount === 1 ? '' : 's'} on engram`);
644
- }
645
- else {
646
- info('No .md files to restore');
647
- }
520
+ // 3. ⟨2026-10-13, the owner's 5A⟩ The canister step MOVED to the owner kit. It wrote each file
521
+ // back with writeMemory, which REPLACES a file and is owner-only on the engram — so from an
522
+ // agent's key it always failed (and printed "[object Object]" for the refusal), and it wrote
523
+ // plaintext into an engram that encrypts memory. An owner's act belongs in the owner kit:
524
+ // `@engramx/owner --restore` (its owned.ts · restoreMemory says what changed on the way).
525
+ info('The files are reverted here. To put them back on the engram, its owner runs:');
526
+ console.log(` ${bold(`npx @engramx/owner@${VERSION} --restore ${shellWord(memoryDir)}${filePath ? ` --file ${shellWord(filePath)}` : ''} --key-file <the owner's key file>`)}`);
648
527
  // 4. Push to remote if configured
649
528
  try {
650
529
  execFileSync('git', ['push'], { cwd: memoryDir, stdio: 'pipe' });
@@ -660,7 +539,7 @@ async function main() {
660
539
  if (command === 'git-setup') {
661
540
  const remoteUrl = positionals[1];
662
541
  if (!remoteUrl) {
663
- fail('Missing remote URL. Usage: npx @engramx/client git-setup <remote-url>');
542
+ fail(`Missing remote URL. Usage: ${NPX_CLIENT} git-setup <remote-url>`);
664
543
  process.exit(1);
665
544
  }
666
545
  step('Setting up git versioning...');
@@ -700,11 +579,6 @@ async function main() {
700
579
  process.exit(1);
701
580
  }
702
581
  }
703
- // Flag validation
704
- if (values.resume && values['dry-run']) {
705
- fail('Cannot combine --resume with --dry-run');
706
- process.exit(1);
707
- }
708
582
  if (values.recover) {
709
583
  // ── Recovery mode ───────────────────────────────────────
710
584
  step('Recovering from engram...');
@@ -731,15 +605,12 @@ async function main() {
731
605
  fail(`Recovery failed: ${err.message}`);
732
606
  process.exit(1);
733
607
  }
734
- console.log(`\nTo undo: ${bold('npx @engramx/client rollback')}`);
608
+ console.log(`\nTo undo: ${bold(`${NPX_CLIENT} rollback`)}`);
735
609
  console.log(`Restart OpenClaw \u2014 memory files will sync automatically.`);
736
610
  return;
737
611
  }
738
- if (values.resume) {
739
- // ── Resume mode ─────────────────────────────────────────
740
- await runResumeMigration(clientOptions);
741
- return;
742
- }
612
+ // ⟨2026-10-13, the bloat audit⟩ The `--resume` flag went: it was a second door to what a plain
613
+ // `migrate` already does just below — find the incomplete checkpoint and offer to resume it.
743
614
  // ── Default: check for incomplete checkpoint ────────────
744
615
  const incomplete = findIncompleteCheckpoint();
745
616
  if (incomplete && !values['dry-run']) {
@@ -774,7 +645,7 @@ async function main() {
774
645
  process.exit(1);
775
646
  }
776
647
  if (!values['dry-run']) {
777
- console.log(`\nTo undo: ${bold('npx @engramx/client rollback')}`);
648
+ console.log(`\nTo undo: ${bold(`${NPX_CLIENT} rollback`)}`);
778
649
  }
779
650
  return;
780
651
  }
@@ -814,7 +685,7 @@ async function main() {
814
685
  }
815
686
  // Error if --recover is passed to pair (moved to migrate)
816
687
  if (values.recover) {
817
- fail('The --recover flag has moved to the migrate command. Use: npx @engramx/client migrate --engram <id> --recover');
688
+ fail(`The --recover flag has moved to the migrate command. Use: ${NPX_CLIENT} migrate --engram <id> --recover`);
818
689
  process.exit(1);
819
690
  }
820
691
  const inviteCode = positionals[1];
@@ -837,6 +708,23 @@ async function main() {
837
708
  info(`Key path: ${keyPath}`);
838
709
  info(`Lasting key: ${rootKeyPath}`);
839
710
  info(`Target: ${target}`);
711
+ // Before anything changes: an EngramX plugin for OpenClaw already on this machine must be this
712
+ // release's, or pair stops here.
713
+ // ⟨ADDED 2026-10-13, the second review of the owner's "fix the pairing migration"⟩ WHY. Step 2 left
714
+ // an older plugin in place and printed "Installed EngramX plugin for OpenClaw" over it, and one from
715
+ // 0.16.0-rc.11 or earlier deletes the local memory files the engram does not list when OpenClaw
716
+ // next starts — the files Step 4 now leaves where they are. Stopping HERE, before the invite is
717
+ // used, is what lets the same pair command run again once the plugin is upgraded; refused at Step
718
+ // 2 the pairing would already be done and the invite spent. openclaw-setup.ts ·
719
+ // outdatedOpenClawPlugins says the rest. Held by test/pair-migrate.test.ts.
720
+ if (target === 'openclaw') {
721
+ const outdated = outdatedOpenClawPlugins();
722
+ if (outdated.length > 0) {
723
+ fail(new OpenClawPluginOutdatedError(outdated).message);
724
+ info('Nothing was changed, and the invite is not used yet: upgrade the plugin, then run this pair command again.');
725
+ process.exit(1);
726
+ }
727
+ }
840
728
  // Step 1: Pair (keep the lasting key, generate a fresh key for this engram, enrol it, pairAgent)
841
729
  step('Generating a key for this engram, enrolling it under your lasting key, pairing...');
842
730
  info(`This will write: ${keyPath} (and ${rootKeyPath} if you have no lasting key yet)`);
@@ -897,14 +785,31 @@ async function main() {
897
785
  sessionKeyPath: keyPath,
898
786
  host: values.host,
899
787
  });
900
- ok(`Installed EngramX plugin for OpenClaw`);
788
+ // ⟨CHANGED 2026-10-13, the second review⟩ This printed "Installed EngramX plugin for OpenClaw"
789
+ // whatever setupOpenClaw reported — over a plugin already there at any version, and over no
790
+ // install at all when OpenClaw's gateway was not running. It now says what happened.
791
+ const where = setupResult.pluginNote ?? '';
792
+ switch (setupResult.pluginMethod) {
793
+ case 'cli':
794
+ ok(`Installed ${OPENCLAW_PLUGIN} through OpenClaw's plugin installer`);
795
+ break;
796
+ case 'manual-npm':
797
+ ok(`Installed ${OPENCLAW_PLUGIN} with npm ${dim(`\u2192 ${where}`)}`);
798
+ break;
799
+ case 'current':
800
+ ok(`The EngramX plugin is already ${OPENCLAW_PLUGIN} ${dim(`\u2192 ${where}`)}`);
801
+ break;
802
+ default:
803
+ warn(`Did not install the EngramX plugin for OpenClaw: ${where}.`);
804
+ info(`Install it with: openclaw plugins install ${OPENCLAW_PLUGIN}`);
805
+ }
901
806
  ok(`Updated ${dim(setupResult.configPath)}`);
902
807
  }
903
808
  catch (err) {
904
809
  if (err instanceof OpenClawNotFoundError) {
905
810
  fail(err.message);
906
811
  console.log(`\n${dim('Manual setup:')}`);
907
- console.log(dim(' 1. npm install @engramx/openclaw'));
812
+ console.log(dim(` 1. npm install @engramx/openclaw@${VERSION}`));
908
813
  console.log(dim(' 2. Add to openclaw.json: { "plugins": { "slots": { "memory": "memory-engramx" } } }'));
909
814
  }
910
815
  else {
@@ -1077,11 +982,35 @@ async function main() {
1077
982
  }
1078
983
  catch (err) {
1079
984
  fail(`Git setup failed: ${err.message}`);
1080
- info('You can set this up manually later. See: npx @engramx/client git-setup <url>');
985
+ info(`You can set this up manually later. See: ${NPX_CLIENT} git-setup <url>`);
1081
986
  }
1082
987
  }
1083
- // Step 4: Migration
1084
- {
988
+ // Step 4: Migration — only when asked (--migrate).
989
+ // ⟨CHANGED 2026-10-13, the owner's ruling of that day: "fix the pairing migration"⟩ WHY. This step
990
+ // ran on every pair, for every --target (claude too), and under --yes it confirmed itself: it moved
991
+ // every OpenClaw memory file under OPENCLAW_CONFIG_DIR or ~/.openclaw into the engram and deleted
992
+ // the local originals (migrate.ts · migrateFiles → fs.unlinkSync, after a checkpoint copy). Pairing
993
+ // an agent for Claude on a machine that had once run OpenClaw moved that machine's OpenClaw memory
994
+ // into an engram nobody asked to fill, and an environment variable pointed elsewhere was the only
995
+ // guard. Now pairing migrates only on the explicit --migrate; --yes answers the prompts of what was
996
+ // asked and never asks for the migration itself. Without --migrate, pairing reads the file list the
997
+ // migration would start from, moves nothing, and prints one line: what it found and the command
998
+ // that moves it. The `migrate` command is unchanged. Held by test/pair-migrate.test.ts.
999
+ // WHAT WOULD MAKE THIS WRONG: a caller that relies on pair to move the files. None in this
1000
+ // repository does: integrations/openclaw never runs pair, and its README gives `migrate` as the
1001
+ // second of two commands.
1002
+ // ⟨CORRECTED 2026-10-13, later, on review⟩ One did, without calling it: the OpenClaw plugin's sync
1003
+ // deleted every local memory file the engram did not list, before pushing it, so the files in its
1004
+ // memory folder that this step no longer moves were lost at OpenClaw's next start, unless more than
1005
+ // five, and more than half the folder, would go. The plugin now deletes only what the engram had
1006
+ // (integrations/openclaw/index.ts · knownPathsFile).
1007
+ // ⟨CORRECTED 2026-10-13, the second review⟩ "The plugin now deletes only what the engram had" held
1008
+ // for the plugin released with this kit, not for the one already installed: Step 2 left an older
1009
+ // plugin in place and said it had installed one, so on such a machine the files this step leaves
1010
+ // were still lost at OpenClaw's next start. pair now stops before pairing when the installed plugin
1011
+ // is a different version (the check above Step 1), and the plugin's delete step reads the engram's
1012
+ // whole listing (index.ts · readEngramListing).
1013
+ if (values.migrate) {
1085
1014
  const clientOptions = {
1086
1015
  canisterId: values.engram,
1087
1016
  sessionKeyPath: keyPath,
@@ -1099,20 +1028,41 @@ async function main() {
1099
1028
  fail(`Migration failed: ${err.message}`);
1100
1029
  process.exit(1);
1101
1030
  }
1102
- console.log(`\nTo undo: ${bold('npx @engramx/client rollback')}`);
1031
+ console.log(`\nTo undo: ${bold(`${NPX_CLIENT} rollback`)}`);
1103
1032
  if (gitRemoteUrl) {
1104
1033
  console.log(`Memory changes will be auto-committed and pushed to GitHub.`);
1105
1034
  }
1106
1035
  else {
1107
- info('Tip: Add git versioning with npx @engramx/client git-setup <url>');
1036
+ info(`Tip: Add git versioning with ${NPX_CLIENT} git-setup <url>`);
1108
1037
  }
1109
1038
  }
1110
1039
  else {
1111
- info(`Run 'npx @engramx/client migrate --engram ${values.engram}' to migrate later.`);
1040
+ info(`Run '${NPX_CLIENT} migrate --engram ${values.engram}' to migrate later.`);
1041
+ }
1042
+ }
1043
+ else {
1044
+ let found;
1045
+ try {
1046
+ found = findOpenClawMemoryFiles();
1047
+ }
1048
+ catch (err) {
1049
+ warn(`Could not look for OpenClaw memory files (none were moved): ${err.message}`);
1050
+ }
1051
+ const n = found?.files.length ?? 0;
1052
+ if (found && n > 0) {
1053
+ info(`Found ${n} OpenClaw memory file${n === 1 ? '' : 's'} in ${found.openclawDir} and left ${n === 1 ? 'it' : 'them'} there. ` +
1054
+ `To move ${n === 1 ? 'it' : 'them'} into this engram (each is uploaded, then deleted here): ` +
1055
+ `${NPX_CLIENT} migrate --engram ${values.engram}`);
1112
1056
  }
1113
1057
  }
1114
1058
  }
1115
- main().catch((err) => {
1059
+ /**
1060
+ * The run this module starts when it is loaded — the command line's whole life.
1061
+ * ⟨2026-10-13⟩ Exported so a test can load the CLI with stubbed network modules and wait for the
1062
+ * command to END (test/pair-migrate.test.ts); before, a test could only wait one tick, which is
1063
+ * enough for --help and not for pair.
1064
+ */
1065
+ export const finished = main().catch((err) => {
1116
1066
  fail(`Fatal: ${err.message}`);
1117
1067
  process.exit(1);
1118
1068
  });