@agentage/cli 0.37.2 → 0.38.1

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 (109) hide show
  1. package/README.md +53 -83
  2. package/dist/cli.js +5 -8
  3. package/dist/commands/auth/setup.d.ts +6 -16
  4. package/dist/commands/auth/setup.js +9 -139
  5. package/dist/commands/status/status.d.ts +4 -3
  6. package/dist/commands/status/status.js +4 -27
  7. package/dist/commands/update/update.d.ts +3 -10
  8. package/dist/commands/update/update.js +24 -41
  9. package/dist/core/flag.d.ts +1 -0
  10. package/dist/core/flag.js +1 -0
  11. package/dist/lib/auth/api.d.ts +3 -2
  12. package/dist/lib/auth/api.js +50 -56
  13. package/dist/lib/auth/env-match.js +1 -0
  14. package/dist/lib/auth/introspect.js +10 -11
  15. package/dist/lib/auth/oauth.d.ts +2 -0
  16. package/dist/lib/auth/oauth.js +30 -28
  17. package/dist/lib/auth/sign-in.d.ts +20 -0
  18. package/dist/lib/auth/sign-in.js +95 -0
  19. package/dist/lib/fs/config.d.ts +1 -0
  20. package/dist/lib/fs/config.js +14 -21
  21. package/dist/lib/fs/file-lock.d.ts +7 -2
  22. package/dist/lib/fs/file-lock.js +64 -74
  23. package/dist/lib/fs/json-file.d.ts +3 -0
  24. package/dist/lib/fs/json-file.js +43 -0
  25. package/dist/lib/net/fetch.d.ts +10 -0
  26. package/dist/lib/net/fetch.js +13 -0
  27. package/dist/lib/net/http.js +51 -23
  28. package/dist/lib/net/origins.d.ts +0 -2
  29. package/dist/lib/net/origins.js +1 -5
  30. package/dist/lib/net/user-agent.d.ts +1 -6
  31. package/dist/lib/net/user-agent.js +3 -4
  32. package/dist/lib/npm/npm-cli.d.ts +17 -0
  33. package/dist/lib/npm/npm-cli.js +77 -0
  34. package/dist/lib/status/status-info.d.ts +0 -18
  35. package/dist/lib/status/status-info.js +2 -42
  36. package/dist/lib/update/update-cache.d.ts +1 -0
  37. package/dist/lib/update/update-cache.js +7 -11
  38. package/dist/lib/update/update-check.js +1 -4
  39. package/dist/plugin.d.ts +1 -0
  40. package/dist/plugin.js +2 -0
  41. package/dist/program.d.ts +18 -1
  42. package/dist/program.js +75 -44
  43. package/package.json +17 -12
  44. package/dist/commands/daemon/daemon-cmd.d.ts +0 -2
  45. package/dist/commands/daemon/daemon-cmd.js +0 -107
  46. package/dist/commands/memory/memory-verbs.d.ts +0 -21
  47. package/dist/commands/memory/memory-verbs.js +0 -98
  48. package/dist/commands/memory/memory.d.ts +0 -2
  49. package/dist/commands/memory/memory.js +0 -54
  50. package/dist/commands/vault/vault-sync.d.ts +0 -13
  51. package/dist/commands/vault/vault-sync.js +0 -90
  52. package/dist/commands/vault/vault.d.ts +0 -22
  53. package/dist/commands/vault/vault.js +0 -129
  54. package/dist/daemon/actions.d.ts +0 -5
  55. package/dist/daemon/actions.js +0 -22
  56. package/dist/daemon/client-provider.d.ts +0 -2
  57. package/dist/daemon/client-provider.js +0 -16
  58. package/dist/daemon/entry.d.ts +0 -8
  59. package/dist/daemon/entry.js +0 -113
  60. package/dist/daemon/guards.d.ts +0 -3
  61. package/dist/daemon/guards.js +0 -13
  62. package/dist/daemon/lifecycle.d.ts +0 -17
  63. package/dist/daemon/lifecycle.js +0 -151
  64. package/dist/daemon/mcp-http.d.ts +0 -3
  65. package/dist/daemon/mcp-http.js +0 -34
  66. package/dist/daemon/server.d.ts +0 -23
  67. package/dist/daemon/server.js +0 -135
  68. package/dist/daemon-entry.d.ts +0 -1
  69. package/dist/daemon-entry.js +0 -9
  70. package/dist/lib/auth/provision.d.ts +0 -14
  71. package/dist/lib/auth/provision.js +0 -49
  72. package/dist/lib/daemon/daemon-client.d.ts +0 -37
  73. package/dist/lib/daemon/daemon-client.js +0 -136
  74. package/dist/lib/daemon/daemon-pref.d.ts +0 -2
  75. package/dist/lib/daemon/daemon-pref.js +0 -10
  76. package/dist/lib/memory/memory-client.d.ts +0 -30
  77. package/dist/lib/memory/memory-client.js +0 -98
  78. package/dist/lib/status/format.d.ts +0 -1
  79. package/dist/lib/status/format.js +0 -12
  80. package/dist/lib/status/vaults-format.d.ts +0 -2
  81. package/dist/lib/status/vaults-format.js +0 -49
  82. package/dist/lib/status/vaults-status.d.ts +0 -12
  83. package/dist/lib/status/vaults-status.js +0 -54
  84. package/dist/lib/update/update-lock.d.ts +0 -2
  85. package/dist/lib/update/update-lock.js +0 -109
  86. package/dist/lib/vault/vault-registry.d.ts +0 -12
  87. package/dist/lib/vault/vault-registry.js +0 -81
  88. package/dist/lib/vault/vaults.d.ts +0 -10
  89. package/dist/lib/vault/vaults.js +0 -66
  90. package/dist/lib/vault/vaults.schema.d.ts +0 -2
  91. package/dist/lib/vault/vaults.schema.js +0 -10
  92. package/dist/mcp/local-server.d.ts +0 -4
  93. package/dist/mcp/local-server.js +0 -26
  94. package/dist/observability.d.ts +0 -4
  95. package/dist/observability.js +0 -35
  96. package/dist/sync/discover/watcher.d.ts +0 -26
  97. package/dist/sync/discover/watcher.js +0 -159
  98. package/dist/sync/git/conflict.d.ts +0 -1
  99. package/dist/sync/git/conflict.js +0 -16
  100. package/dist/sync/git/cycle.d.ts +0 -18
  101. package/dist/sync/git/cycle.js +0 -158
  102. package/dist/sync/git/git-exec.d.ts +0 -22
  103. package/dist/sync/git/git-exec.js +0 -56
  104. package/dist/sync/git/manager.d.ts +0 -28
  105. package/dist/sync/git/manager.js +0 -114
  106. package/dist/sync/git/planner.d.ts +0 -15
  107. package/dist/sync/git/planner.js +0 -46
  108. package/dist/sync/git/remote-url.d.ts +0 -3
  109. package/dist/sync/git/remote-url.js +0 -30
@@ -1,6 +1,6 @@
1
- import { readFileSync, writeFileSync } from 'node:fs';
2
1
  import { join } from 'node:path';
3
2
  import { ensureConfigDir, getConfigDir } from '../fs/config.js';
3
+ import { isRecord, readJson, writeJsonAtomic } from '../fs/json-file.js';
4
4
  import { fetchJsonUnref } from '../net/http.js';
5
5
  import { compareVersions, INSTALL_HINT, REGISTRY_URL } from './update-check.js';
6
6
  import { VERSION } from '../../utils/version.js';
@@ -8,18 +8,14 @@ const CACHE_FILE = 'update-check.json';
8
8
  const TTL_MS = 60 * 60 * 1000; // passive checks refresh at most once an hour
9
9
  const BG_TIMEOUT_MS = 3000; // hard cap on the whole background attempt, connect included
10
10
  const cachePath = () => join(getConfigDir(), CACHE_FILE);
11
- export const readUpdateCache = () => {
12
- try {
13
- const c = JSON.parse(readFileSync(cachePath(), 'utf-8'));
14
- return typeof c.checkedAt === 'number' ? c : null;
15
- }
16
- catch {
17
- return null;
18
- }
19
- };
11
+ // A wrong-typed field reads as no cache: the post-action hint must never throw.
12
+ export const isUpdateCache = (value) => isRecord(value) &&
13
+ typeof value['checkedAt'] === 'number' &&
14
+ (value['latest'] === null || typeof value['latest'] === 'string');
15
+ export const readUpdateCache = () => readJson(cachePath(), isUpdateCache);
20
16
  const writeUpdateCache = (latest, now) => {
21
17
  ensureConfigDir();
22
- writeFileSync(cachePath(), JSON.stringify({ checkedAt: now, latest }) + '\n', 'utf-8');
18
+ writeJsonAtomic(cachePath(), { checkedAt: now, latest });
23
19
  };
24
20
  // The one dim hint line printed after a command, read purely from the cache (never the network),
25
21
  // only when the cached latest is newer than us. null = print nothing.
@@ -7,10 +7,7 @@ export const REGISTRY_URL = 'https://registry.npmjs.org/@agentage/cli/latest';
7
7
  // Compare dotted numeric versions (major.minor.patch); prerelease/build suffixes are
8
8
  // ignored - good enough for an update hint. Returns -1 / 0 / 1.
9
9
  export const compareVersions = (a, b) => {
10
- const parts = (v) => v
11
- .split('-')[0]
12
- .split('.')
13
- .map((n) => Number.parseInt(n, 10) || 0);
10
+ const parts = (v) => (v.split('-')[0] ?? '').split('.').map((n) => Number.parseInt(n, 10) || 0);
14
11
  const pa = parts(a);
15
12
  const pb = parts(b);
16
13
  for (let i = 0; i < 3; i++) {
@@ -0,0 +1 @@
1
+ export declare const PLUGIN_API_VERSION = "1.0.0";
package/dist/plugin.js ADDED
@@ -0,0 +1,2 @@
1
+ // The plugin contract is versioned apart from the CLI, so plugins pin it, not a CLI release.
2
+ export const PLUGIN_API_VERSION = '1.0.0';
package/dist/program.d.ts CHANGED
@@ -1 +1,18 @@
1
- export declare const run: () => Promise<void>;
1
+ import { Command } from 'commander';
2
+ import { type SetupOptions } from './commands/auth/setup.js';
3
+ import { type StatusOptions } from './commands/status/status.js';
4
+ import { type UpdateOptions } from './commands/update/update.js';
5
+ export interface ProgramDeps {
6
+ setup: (opts: SetupOptions) => Promise<void>;
7
+ status: (opts: StatusOptions) => Promise<void>;
8
+ update: (opts: UpdateOptions) => Promise<void>;
9
+ updateHint: () => string | null;
10
+ refreshUpdateCache: () => Promise<void>;
11
+ writeOut: (text: string) => void;
12
+ writeErr: (text: string) => void;
13
+ }
14
+ export declare const EXIT_OK = 0;
15
+ export declare const EXIT_FAILED = 1;
16
+ export declare const EXIT_USAGE = 2;
17
+ export declare const createProgram: (deps?: ProgramDeps) => Command;
18
+ export declare const run: (argv?: string[], deps?: ProgramDeps) => Promise<number>;
package/dist/program.js CHANGED
@@ -1,50 +1,81 @@
1
1
  import chalk from 'chalk';
2
- import { Command } from 'commander';
3
- import { registerDaemon } from './commands/daemon/daemon-cmd.js';
4
- import { registerMemory } from './commands/memory/memory.js';
5
- import { registerSetup } from './commands/auth/setup.js';
6
- import { registerStatus } from './commands/status/status.js';
7
- import { registerUpdate } from './commands/update/update.js';
8
- import { registerVault } from './commands/vault/vault.js';
9
- import { disableDaemon } from './lib/daemon/daemon-pref.js';
2
+ import { Command, CommanderError } from 'commander';
3
+ import { registerSetup, runSetup } from './commands/auth/setup.js';
4
+ import { registerStatus, runStatus } from './commands/status/status.js';
5
+ import { registerUpdate, runUpdate } from './commands/update/update.js';
6
+ import { ensureSignedIn, signOut } from './lib/auth/sign-in.js';
10
7
  import { refreshUpdateCache, updateHint } from './lib/update/update-cache.js';
11
8
  import { VERSION } from './utils/version.js';
12
- const program = new Command();
13
- program
14
- .name('agentage')
15
- .description('The offline-first terminal client for agentage Memory')
16
- .version(VERSION)
17
- .option('--no-daemon', 'run memory verbs in-process instead of via the daemon');
18
- program.addHelpText('after', '\nNew here? Run: agentage vault add <name> --local, or agentage setup to connect an account.');
19
- program.hook('preAction', () => {
20
- if (program.opts().daemon === false)
21
- disableDaemon();
22
- });
23
- // After any command (except the update-aware ones, which report richer state themselves), print a
24
- // one-line hint from the cache, then kick a fire-and-forget refresh. Never awaited, never throws -
25
- // the command output is already done and the cache-only read keeps this instant. The hint is
26
- // suppressed under --json so it never corrupts machine-readable output.
27
- program.hook('postAction', (_thisCommand, actionCommand) => {
28
- const name = actionCommand.name();
29
- if (name === 'status' || name === 'update')
9
+ const defaultDeps = {
10
+ setup: (opts) => runSetup(opts, {
11
+ ensureSignedIn: (signInOpts) => ensureSignedIn(signInOpts),
12
+ signOut: () => signOut(),
13
+ printStatus: () => runStatus({}),
14
+ }),
15
+ status: (opts) => runStatus(opts),
16
+ update: (opts) => runUpdate(opts),
17
+ updateHint,
18
+ refreshUpdateCache: () => refreshUpdateCache(),
19
+ writeOut: (text) => void process.stdout.write(text),
20
+ writeErr: (text) => void process.stderr.write(text),
21
+ };
22
+ export const EXIT_OK = 0;
23
+ export const EXIT_FAILED = 1;
24
+ export const EXIT_USAGE = 2;
25
+ const ACTION_FAILED = 'agentage.actionFailed';
26
+ const messageOf = (err) => (err instanceof Error ? err.message : String(err));
27
+ // One failure path for every command: a red stderr line, then exit 1 through run().
28
+ const withAction = (deps, fn, prefix = '') => async (opts) => {
29
+ try {
30
+ await fn(opts);
31
+ }
32
+ catch (err) {
33
+ deps.writeErr(`${chalk.red(`${prefix}${messageOf(err)}`)}\n`);
34
+ throw new CommanderError(EXIT_FAILED, ACTION_FAILED, messageOf(err));
35
+ }
36
+ };
37
+ // status and update report update state themselves; the hint never touches --json stdout.
38
+ const hintAfter = (deps) => (_program, action) => {
39
+ if (action.name() === 'status' || action.name() === 'update')
30
40
  return;
31
- if (!actionCommand.opts()['json']) {
32
- const hint = updateHint();
33
- if (hint)
34
- console.error(chalk.dim(hint)); // stderr: never corrupt piped stdout
41
+ const hint = action.opts()['json'] ? null : deps.updateHint();
42
+ if (hint)
43
+ deps.writeErr(`${chalk.dim(hint)}\n`);
44
+ void deps.refreshUpdateCache();
45
+ };
46
+ export const createProgram = (deps = defaultDeps) => {
47
+ const program = new Command()
48
+ .name('agentage')
49
+ .description('Connect this machine to agentage from the terminal')
50
+ .version(VERSION)
51
+ .exitOverride()
52
+ .configureOutput({ writeOut: deps.writeOut, writeErr: deps.writeErr })
53
+ .addHelpText('after', '\nNew here? Run: agentage setup to connect this machine.')
54
+ .hook('postAction', hintAfter(deps));
55
+ registerSetup(program, withAction(deps, deps.setup, 'Setup failed: '));
56
+ registerStatus(program, withAction(deps, deps.status));
57
+ registerUpdate(program, withAction(deps, deps.update));
58
+ return program;
59
+ };
60
+ // Commander's own exit 0 (--help, --version) stays 0; any other commander error is usage.
61
+ const exitCodeOf = (err, deps) => {
62
+ if (!(err instanceof CommanderError)) {
63
+ deps.writeErr(`${chalk.red(messageOf(err))}\n`);
64
+ return EXIT_FAILED;
65
+ }
66
+ if (err.code === ACTION_FAILED)
67
+ return EXIT_FAILED;
68
+ return err.exitCode === 0 ? EXIT_OK : EXIT_USAGE;
69
+ };
70
+ // Returns the exit code instead of exiting, so piped stdout is never cut short.
71
+ export const run = async (argv = process.argv, deps = defaultDeps) => {
72
+ if (process.env['NO_COLOR'])
73
+ chalk.level = 0; // chalk ignores NO_COLOR on its own
74
+ try {
75
+ await createProgram(deps).parseAsync(argv);
76
+ return EXIT_OK;
77
+ }
78
+ catch (err) {
79
+ return exitCodeOf(err, deps);
35
80
  }
36
- void refreshUpdateCache();
37
- });
38
- registerSetup(program);
39
- registerStatus(program);
40
- registerVault(program);
41
- registerMemory(program);
42
- registerDaemon(program);
43
- registerUpdate(program);
44
- // Called by the bin (src/cli.ts) once the observability bootstrap has run.
45
- export const run = async () => {
46
- await program.parseAsync().catch((err) => {
47
- console.error(err instanceof Error ? err.message : String(err));
48
- process.exit(1);
49
- });
50
81
  };
package/package.json CHANGED
@@ -1,10 +1,21 @@
1
1
  {
2
2
  "name": "@agentage/cli",
3
- "version": "0.37.2",
3
+ "version": "0.38.1",
4
4
  "description": "The agentage CLI - connect this machine to agentage from the terminal",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "./plugin": {
14
+ "types": "./dist/plugin.d.ts",
15
+ "default": "./dist/plugin.js"
16
+ },
17
+ "./package.json": "./package.json"
18
+ },
8
19
  "bin": {
9
20
  "agentage": "./dist/cli.js"
10
21
  },
@@ -25,11 +36,11 @@
25
36
  },
26
37
  "scripts": {
27
38
  "build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json",
28
- "type-check": "tsc --noEmit",
29
- "lint": "eslint src",
30
- "lint:fix": "eslint src --fix",
31
- "format": "prettier --write \"{src,e2e}/**/*.ts\"",
32
- "format:check": "prettier --check \"{src,e2e}/**/*.ts\"",
39
+ "type-check": "tsc --noEmit && tsc -p fixtures",
40
+ "lint": "eslint src e2e fixtures",
41
+ "lint:fix": "eslint src e2e fixtures --fix",
42
+ "format": "prettier --write \"{src,e2e}/**/*.ts\" \"fixtures/**/*.{js,json}\"",
43
+ "format:check": "prettier --check \"{src,e2e}/**/*.ts\" \"fixtures/**/*.{js,json}\"",
33
44
  "test": "vitest run",
34
45
  "test:watch": "vitest",
35
46
  "test:coverage": "vitest run --coverage",
@@ -40,17 +51,11 @@
40
51
  "prepublishOnly": "npm run verify"
41
52
  },
42
53
  "dependencies": {
43
- "@agentage/memory-core": "^0.5.0",
44
- "@agentage/observability": "^1.4.0",
45
- "@agentage/server-memory": "^0.11.0",
46
- "@modelcontextprotocol/sdk": "^1.29.0",
47
- "@opentelemetry/api": "^1.9.0",
48
54
  "chalk": "^6.0.0",
49
55
  "commander": "^15.0.0",
50
56
  "open": "^11.0.0"
51
57
  },
52
58
  "devDependencies": {
53
- "@anthropic-ai/sdk": "0.135.0",
54
59
  "@playwright/test": "latest",
55
60
  "@types/node": "latest",
56
61
  "@typescript-eslint/eslint-plugin": "latest",
@@ -1,2 +0,0 @@
1
- import { type Command } from 'commander';
2
- export declare const registerDaemon: (program: Command) => void;
@@ -1,107 +0,0 @@
1
- import chalk from 'chalk';
2
- import { isDaemonRunning, resolvePort, signalPid, stopDaemonSafely, } from '../../daemon/lifecycle.js';
3
- import { health, mismatchNotice, spawnDaemon, syncStatus } from '../../lib/daemon/daemon-client.js';
4
- const startAction = async (opts = {}) => {
5
- const port = resolvePort();
6
- const existing = await health(port);
7
- if (existing) {
8
- console.log(chalk.gray(`Daemon already running (pid ${existing.pid}, port ${port}).`));
9
- return;
10
- }
11
- const noMcp = opts.mcp === false;
12
- const outcome = await spawnDaemon(port, { noMcp });
13
- if (!outcome.ok) {
14
- if (outcome.reason === 'port-in-use') {
15
- console.error(chalk.red(`Port ${port} is in use by another process - set AGENTAGE_DAEMON_PORT to use a different port.`));
16
- }
17
- else {
18
- console.error(chalk.red('Daemon failed to start.'));
19
- }
20
- process.exitCode = 1;
21
- return;
22
- }
23
- const h = await health(port);
24
- const mcp = noMcp ? 'off' : 'on';
25
- console.log(chalk.green(`Daemon started (pid ${h?.pid ?? '?'}, port ${port}, mcp ${mcp}).`));
26
- };
27
- // Detect the daemon the same way `start` does (tokenless /health), so a legacy daemon that never
28
- // wrote this config dir's pidfile is not misreported as "not running". With a pidfile we own, use
29
- // the safe pid-confirming stop; otherwise fall back to signalling the pid /health reports, or - if
30
- // none is available - report the truth rather than a false "not running".
31
- const stopAction = async () => {
32
- if (isDaemonRunning()) {
33
- if (await stopDaemonSafely())
34
- console.log(chalk.green('Daemon stopped.'));
35
- return;
36
- }
37
- const port = resolvePort();
38
- const h = await health(port);
39
- if (!h) {
40
- console.log(chalk.gray('Daemon is not running.'));
41
- return;
42
- }
43
- if (typeof h.pid === 'number' && signalPid(h.pid)) {
44
- console.log(chalk.green(`Daemon stopped (pid ${h.pid}).`));
45
- return;
46
- }
47
- const who = typeof h.pid === 'number' ? `pid ${h.pid}` : `port ${port}`;
48
- console.error(chalk.yellow(`Daemon is running (${who}) but has no pidfile to stop it safely; stop it with: kill ${h.pid ?? '<pid>'}`));
49
- process.exitCode = 1;
50
- };
51
- const statusAction = async () => {
52
- const port = resolvePort();
53
- const h = await health(port);
54
- if (!h) {
55
- console.log(isDaemonRunning()
56
- ? chalk.yellow(`Daemon pid file present but unreachable on port ${port}.`)
57
- : chalk.gray('Daemon is not running.'));
58
- return;
59
- }
60
- console.log(`pid ${h.pid ?? '?'}`);
61
- console.log(`port ${port}`);
62
- if (typeof h.uptime === 'number')
63
- console.log(`uptime ${h.uptime}s`);
64
- if (typeof h.served === 'number')
65
- console.log(`served ${h.served}`);
66
- console.log(`mcp ${h.mcp === false ? 'off' : 'on'}`);
67
- console.log(`version ${h.version}`);
68
- const notice = mismatchNotice(h.version);
69
- if (notice)
70
- console.log(chalk.yellow(notice));
71
- const sync = await syncStatus(port);
72
- if (sync && sync.vaults.length > 0) {
73
- console.log('sync');
74
- for (const v of sync.vaults) {
75
- const cadence = v.intervalSeconds > 0 ? `every ${v.intervalSeconds}s` : 'manual';
76
- const state = v.running
77
- ? 'running'
78
- : v.lastError
79
- ? `error: ${v.lastError}`
80
- : v.lastRun
81
- ? `ok ${v.lastRun}`
82
- : 'scheduled';
83
- console.log(` ${v.vault.padEnd(16)} ${cadence.padEnd(12)} ${state}`);
84
- }
85
- }
86
- if (sync?.discover && sync.discover.roots.length > 0) {
87
- console.log(`discover roots (${sync.discover.roots.length})`);
88
- for (const root of sync.discover.roots)
89
- console.log(` ${root}`);
90
- }
91
- };
92
- export const registerDaemon = (program) => {
93
- const daemon = program.command('daemon').description('Manage the local engine daemon');
94
- daemon
95
- .command('start')
96
- .description('Start the daemon (idempotent)')
97
- .option('--no-mcp', 'do not serve the local MCP endpoint at /mcp')
98
- .action((opts) => startAction(opts));
99
- daemon
100
- .command('stop')
101
- .description('Stop the daemon')
102
- .action(() => stopAction());
103
- daemon
104
- .command('status')
105
- .description('Show the daemon pid, uptime, and version')
106
- .action(() => statusAction());
107
- };
@@ -1,21 +0,0 @@
1
- import { type MemoryClient } from '../../lib/memory/memory-client.js';
2
- export interface CommonOpts {
3
- vault?: string;
4
- json?: boolean;
5
- }
6
- export declare const runSearch: (query: string, opts: CommonOpts & {
7
- limit?: string;
8
- }, client?: MemoryClient) => Promise<void>;
9
- export declare const runRead: (ref: string, opts: CommonOpts, client?: MemoryClient) => Promise<void>;
10
- export declare const runWrite: (ref: string, opts: CommonOpts & {
11
- body?: string;
12
- frontmatter?: string;
13
- }, client?: MemoryClient) => Promise<void>;
14
- export declare const runEdit: (ref: string, opts: CommonOpts & {
15
- old?: string;
16
- new?: string;
17
- body?: string;
18
- append?: boolean;
19
- }, client?: MemoryClient) => Promise<void>;
20
- export declare const runList: (folder: string | undefined, opts: CommonOpts, client?: MemoryClient) => Promise<void>;
21
- export declare const runDelete: (ref: string, opts: CommonOpts, client?: MemoryClient) => Promise<void>;
@@ -1,98 +0,0 @@
1
- import chalk from 'chalk';
2
- import { ensureDaemon } from '../../lib/daemon/daemon-client.js';
3
- import { daemonDisabled } from '../../lib/daemon/daemon-pref.js';
4
- import { createDirectClient } from '../../lib/memory/memory-client.js';
5
- import { loadVaultsConfig } from '../../lib/vault/vaults.js';
6
- // Default engine path (DO3/DO4): the daemon - single writer, autostarted - when reachable; the
7
- // in-process DirectClient as a seamless fallback (--no-daemon, sandbox/CI, or fork blocked).
8
- const resolveClient = async () => {
9
- const direct = () => createDirectClient(loadVaultsConfig().config);
10
- if (daemonDisabled())
11
- return direct();
12
- const daemon = await ensureDaemon();
13
- return daemon ?? direct();
14
- };
15
- const readStdin = async () => {
16
- const chunks = [];
17
- for await (const chunk of process.stdin)
18
- chunks.push(chunk);
19
- return Buffer.concat(chunks).toString('utf-8');
20
- };
21
- // --frontmatter must be a JSON object; a raw parser error or a non-object value is unfriendly.
22
- const parseFrontmatter = (raw) => {
23
- if (!raw)
24
- return undefined;
25
- let parsed;
26
- try {
27
- parsed = JSON.parse(raw);
28
- }
29
- catch (err) {
30
- const detail = err instanceof Error ? err.message : String(err);
31
- throw new Error(`--frontmatter must be a JSON object, e.g. '{"tags":["x"]}': ${detail}`);
32
- }
33
- if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
34
- throw new Error(`--frontmatter must be a JSON object, e.g. '{"tags":["x"]}'`);
35
- return parsed;
36
- };
37
- const emit = (json, data, human) => {
38
- if (json)
39
- console.log(JSON.stringify(data, null, 2));
40
- else
41
- human();
42
- };
43
- // Flatten a folder-tree to the file paths it contains, in tree order.
44
- const treeFiles = (entries) => entries.flatMap((e) => (e.type === 'file' ? [e.path] : e.entries ? treeFiles(e.entries) : []));
45
- export const runSearch = async (query, opts, client) => {
46
- const c = client ?? (await resolveClient());
47
- const out = await c.search(query, {
48
- vault: opts.vault,
49
- limit: opts.limit ? Number.parseInt(opts.limit, 10) : undefined,
50
- });
51
- emit(opts.json ?? false, out, () => {
52
- if (out.results.length === 0)
53
- console.log('No matches.');
54
- for (const h of out.results)
55
- console.log(`${h.path}\n ${chalk.gray(h.snippet)}`);
56
- });
57
- };
58
- export const runRead = async (ref, opts, client) => {
59
- const c = client ?? (await resolveClient());
60
- const doc = await c.read(ref, { vault: opts.vault });
61
- emit(opts.json ?? false, doc, () => console.log(doc.body));
62
- };
63
- export const runWrite = async (ref, opts, client) => {
64
- const c = client ?? (await resolveClient());
65
- const inlineBody = opts.body !== undefined && opts.body !== '-';
66
- if (!inlineBody && process.stdin.isTTY)
67
- throw new Error('provide --body, or pipe content on stdin');
68
- const body = inlineBody ? opts.body : await readStdin();
69
- const frontmatter = parseFrontmatter(opts.frontmatter);
70
- const out = await c.write(ref, body, { vault: opts.vault, frontmatter });
71
- emit(opts.json ?? false, out, () => console.log(chalk.green(`Wrote ${out.path}`)));
72
- };
73
- export const runEdit = async (ref, opts, client) => {
74
- const c = client ?? (await resolveClient());
75
- if (opts.old === undefined && opts.body === undefined)
76
- throw new Error('specify --old/--new for a replacement or --body to overwrite');
77
- const op = opts.old !== undefined
78
- ? { mode: 'str_replace', old_str: opts.old, new_str: opts.new ?? '' }
79
- : { mode: opts.append ? 'append' : 'replace', body: opts.body };
80
- const out = await c.edit(ref, op, { vault: opts.vault });
81
- emit(opts.json ?? false, out, () => console.log(chalk.green(`Edited ${out.path}`)));
82
- };
83
- export const runList = async (folder, opts, client) => {
84
- const c = client ?? (await resolveClient());
85
- const out = await c.list(folder, { vault: opts.vault });
86
- emit(opts.json ?? false, out, () => {
87
- const files = treeFiles(out.entries);
88
- if (files.length === 0)
89
- console.log('No documents.');
90
- for (const p of files)
91
- console.log(p);
92
- });
93
- };
94
- export const runDelete = async (ref, opts, client) => {
95
- const c = client ?? (await resolveClient());
96
- const out = await c.delete(ref, { vault: opts.vault });
97
- emit(opts.json ?? false, out, () => console.log(`Deleted ${out.path} (recoverable from git history)`));
98
- };
@@ -1,2 +0,0 @@
1
- import { type Command } from 'commander';
2
- export declare const registerMemory: (program: Command) => void;
@@ -1,54 +0,0 @@
1
- import chalk from 'chalk';
2
- import { translateEngineMessage } from '../../lib/memory/memory-client.js';
3
- import { runDelete, runEdit, runList, runRead, runSearch, runWrite, } from './memory-verbs.js';
4
- const guard = (fn) => fn().catch((err) => {
5
- const message = translateEngineMessage(err instanceof Error ? err.message : String(err));
6
- console.error(chalk.red(message));
7
- process.exitCode = 1;
8
- });
9
- export const registerMemory = (program) => {
10
- const memory = program.command('memory').description('Read + write memory, offline');
11
- memory
12
- .command('search <query...>')
13
- .description('Search a vault (git grep)')
14
- .option('--vault <name>', 'target vault')
15
- .option('--limit <n>', 'max hits (default 20)')
16
- .option('--json', 'machine-readable output')
17
- .action((query, opts) => guard(() => runSearch(query.join(' '), opts)));
18
- memory
19
- .command('read <ref>')
20
- .description('Print a document (@<vault>/<path> or <path> --vault)')
21
- .option('--vault <name>', 'target vault')
22
- .option('--json', 'machine-readable output')
23
- .action((ref, opts) => guard(() => runRead(ref, opts)));
24
- memory
25
- .command('write <ref>')
26
- .description('Create or overwrite a document (body from --body or stdin)')
27
- .option('--vault <name>', 'target vault')
28
- .option('--body <text>', 'document body (omit or "-" to read stdin)')
29
- .option('--frontmatter <json>', 'frontmatter as a JSON object')
30
- .option('--json', 'machine-readable output')
31
- .action((ref, opts) => guard(() => runWrite(ref, opts)));
32
- memory
33
- .command('edit <ref>')
34
- .description('Edit a document: str_replace (--old/--new) or --body (--append)')
35
- .option('--vault <name>', 'target vault')
36
- .option('--old <str>', 'exact, unique substring to replace')
37
- .option('--new <str>', 'replacement (omit to delete the match)')
38
- .option('--body <text>', 'replace the whole body')
39
- .option('--append', 'append --body instead of replacing')
40
- .option('--json', 'machine-readable output')
41
- .action((ref, opts) => guard(() => runEdit(ref, opts)));
42
- memory
43
- .command('list [folder]')
44
- .description('List documents in a vault (optionally under a folder)')
45
- .option('--vault <name>', 'target vault')
46
- .option('--json', 'machine-readable output')
47
- .action((folder, opts) => guard(() => runList(folder, opts)));
48
- memory
49
- .command('delete <ref>')
50
- .description('Delete a document (recoverable from git history)')
51
- .option('--vault <name>', 'target vault')
52
- .option('--json', 'machine-readable output')
53
- .action((ref, opts) => guard(() => runDelete(ref, opts)));
54
- };
@@ -1,13 +0,0 @@
1
- import { type VaultsConfig } from '@agentage/memory-core';
2
- import { type SyncResult } from '../../sync/git/cycle.js';
3
- import { type SyncTarget } from '../../sync/git/planner.js';
4
- export interface VaultSyncDeps {
5
- loadConfig: () => VaultsConfig;
6
- daemonPort: () => Promise<number | null>;
7
- runViaDaemon: (port: number, vault: string) => Promise<SyncResult>;
8
- runGitInProcess: (target: SyncTarget) => Promise<SyncResult>;
9
- log: (msg: string) => void;
10
- }
11
- export declare const ACCOUNT_NO_CHANNEL = "not synced - account vaults have no sync channel";
12
- export declare const runVaultSync: (name: string | undefined, deps: VaultSyncDeps) => Promise<void>;
13
- export declare const defaultVaultSyncDeps: () => VaultSyncDeps;
@@ -1,90 +0,0 @@
1
- import chalk from 'chalk';
2
- import { isAccountVault } from '@agentage/memory-core';
3
- import { health, syncRun } from '../../lib/daemon/daemon-client.js';
4
- import { daemonDisabled } from '../../lib/daemon/daemon-pref.js';
5
- import { loadVaultsConfig } from '../../lib/vault/vaults.js';
6
- import { resolvePort } from '../../daemon/lifecycle.js';
7
- import { runSyncCycle } from '../../sync/git/cycle.js';
8
- import { syncTargets } from '../../sync/git/planner.js';
9
- import { redactRemoteUrl } from '../../sync/git/remote-url.js';
10
- export const ACCOUNT_NO_CHANNEL = 'not synced - account vaults have no sync channel';
11
- const describeGit = (r) => {
12
- if (!r.ok)
13
- return chalk.red(`failed (${r.reason ?? 'error'})${r.error ? `: ${r.error}` : ''}`);
14
- if (r.skipped)
15
- return chalk.yellow(`skipped (${r.skipped})`);
16
- const bits = [];
17
- if (r.committed)
18
- bits.push('committed');
19
- if (r.conflicts.length)
20
- bits.push(`${r.conflicts.length} conflict copy(ies)`);
21
- if (r.pushed)
22
- bits.push('pushed');
23
- return chalk.green(bits.length ? bits.join(', ') : 'up to date');
24
- };
25
- const report = (log, r) => {
26
- log(`${r.vault} -> ${redactRemoteUrl(r.remote)}: ${describeGit(r)}`);
27
- for (const c of r.conflicts)
28
- log(` kept remote copy: ${c}`);
29
- };
30
- // Account vaults whose only origin is the reserved `agentage` remote: nothing syncs them. They are
31
- // named, never silently skipped, so `vault sync` cannot read as "everything is up to date".
32
- const unsyncableAccountVaults = (config, gitTargets) => {
33
- const git = new Set(gitTargets.map((t) => t.vault));
34
- return Object.entries(config.vaults ?? {})
35
- .filter(([vault, entry]) => isAccountVault(entry) && !git.has(vault))
36
- .map(([vault]) => vault);
37
- };
38
- const reportAccounts = (log, vaults) => {
39
- for (const vault of vaults)
40
- log(`${vault} (account): ${chalk.yellow(ACCOUNT_NO_CHANNEL)}`);
41
- };
42
- // `agentage vault sync [name]`: sync one vault (or every syncable vault). Git-origin vaults commit
43
- // + push + pull-rebase; account (agentage) vaults have no sync channel and are reported as such.
44
- // Prefers a running daemon (single writer), else runs the cycle in-process. Works for interval-0
45
- // (manual-only) vaults and with the daemon down. Failures are surfaced, not thrown (V6: never a crash).
46
- export const runVaultSync = async (name, deps) => {
47
- const config = deps.loadConfig();
48
- // A named vault that is not registered is an error (mirrors `vault remove`), not a silent no-op.
49
- if (name && !(config.vaults ?? {})[name])
50
- throw new Error(`vault '${name}' not found`);
51
- const gitTargets = syncTargets(config).filter((t) => !name || t.vault === name);
52
- const accounts = unsyncableAccountVaults(config, gitTargets).filter((v) => !name || v === name);
53
- if (gitTargets.length === 0) {
54
- reportAccounts(deps.log, accounts);
55
- if (accounts.length === 0)
56
- deps.log(name
57
- ? `No syncable origin configured for vault '${name}'.`
58
- : 'No syncable vaults. Add one with `agentage vault add <name>`.');
59
- return;
60
- }
61
- const vaults = [...new Set(gitTargets.map((t) => t.vault))];
62
- deps.log(`Syncing ${vaults.length} vault(s)...`);
63
- const port = await deps.daemonPort();
64
- if (port !== null) {
65
- for (const vault of vaults) {
66
- deps.log(`${vault}...`);
67
- report(deps.log, await deps.runViaDaemon(port, vault));
68
- }
69
- }
70
- else {
71
- for (const target of gitTargets) {
72
- deps.log(`${target.vault}...`);
73
- report(deps.log, await deps.runGitInProcess(target));
74
- }
75
- }
76
- reportAccounts(deps.log, accounts);
77
- };
78
- const resolveDaemonPort = async () => {
79
- if (daemonDisabled())
80
- return null;
81
- const port = resolvePort();
82
- return (await health(port)) ? port : null;
83
- };
84
- export const defaultVaultSyncDeps = () => ({
85
- loadConfig: () => loadVaultsConfig().config,
86
- daemonPort: resolveDaemonPort,
87
- runViaDaemon: syncRun,
88
- runGitInProcess: runSyncCycle,
89
- log: (msg) => console.log(msg),
90
- });