@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.
- package/README.md +22 -8
- package/dist/EngramClient.d.ts +10 -14
- package/dist/EngramClient.js +14 -64
- package/dist/EngramClient.js.map +1 -1
- package/dist/ProfileManager.d.ts +3 -19
- package/dist/ProfileManager.js +1 -58
- package/dist/ProfileManager.js.map +1 -1
- package/dist/SessionManager.d.ts +1 -1
- package/dist/SessionManager.js.map +1 -1
- package/dist/claude-setup.d.ts +15 -6
- package/dist/claude-setup.js +30 -17
- package/dist/claude-setup.js.map +1 -1
- package/dist/cli.d.ts +7 -1
- package/dist/cli.js +134 -184
- package/dist/cli.js.map +1 -1
- package/dist/connect-config.d.ts +109 -0
- package/dist/connect-config.js +251 -0
- package/dist/connect-config.js.map +1 -0
- package/dist/encryption.js +7 -1
- package/dist/encryption.js.map +1 -1
- package/dist/idl.d.ts +16 -1
- package/dist/idl.js +9 -1
- package/dist/idl.js.map +1 -1
- package/dist/index.d.ts +4 -5
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/dist/ledgerfsHash.d.ts +0 -2
- package/dist/ledgerfsHash.js +0 -2
- package/dist/ledgerfsHash.js.map +1 -1
- package/dist/lineage.d.ts +7 -1
- package/dist/lineage.js +7 -1
- package/dist/lineage.js.map +1 -1
- package/dist/memoryArchive.d.ts +12 -77
- package/dist/memoryArchive.js.map +1 -1
- package/dist/migrate.d.ts +18 -30
- package/dist/migrate.js +42 -136
- package/dist/migrate.js.map +1 -1
- package/dist/openclaw-local.d.ts +86 -0
- package/dist/openclaw-local.js +332 -0
- package/dist/openclaw-local.js.map +1 -0
- package/dist/openclaw-setup.d.ts +82 -3
- package/dist/openclaw-setup.js +162 -50
- package/dist/openclaw-setup.js.map +1 -1
- package/dist/path-utils.d.ts +0 -16
- package/dist/path-utils.js +4 -27
- package/dist/path-utils.js.map +1 -1
- package/dist/schema.d.ts +6 -17
- package/dist/schema.js +6 -28
- package/dist/schema.js.map +1 -1
- package/dist/types.d.ts +46 -453
- package/dist/verify.d.ts +2 -0
- package/dist/verify.js +11 -0
- package/dist/verify.js.map +1 -0
- package/dist/version.d.ts +22 -0
- package/dist/version.js +22 -0
- package/dist/version.js.map +1 -1
- package/package.json +17 -5
- package/dist/offline.d.ts +0 -21
- package/dist/offline.js +0 -104
- package/dist/offline.js.map +0 -1
package/dist/claude-setup.js
CHANGED
|
@@ -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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
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
|
/**
|
package/dist/claude-setup.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-setup.js","sourceRoot":"","sources":["../src/claude-setup.ts"],"names":[],"mappings":"AAAA
|
|
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
|
-
|
|
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(
|
|
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-
|
|
275
|
-
${NPX_CLIENT}
|
|
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-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
--
|
|
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
|
-
|
|
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
|
-
//
|
|
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(
|
|
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
|
-
|
|
389
|
-
|
|
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(
|
|
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(
|
|
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.
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
739
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
1036
|
+
info(`Tip: Add git versioning with ${NPX_CLIENT} git-setup <url>`);
|
|
1108
1037
|
}
|
|
1109
1038
|
}
|
|
1110
1039
|
else {
|
|
1111
|
-
info(`Run '
|
|
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
|
-
|
|
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
|
});
|