@agentage/cli 0.25.0 → 0.27.0

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 (46) hide show
  1. package/dist/commands/daemon/daemon-cmd.js +0 -17
  2. package/dist/commands/vault/vault-sync.d.ts +2 -4
  3. package/dist/commands/vault/vault-sync.js +32 -42
  4. package/dist/commands/vault/vault.js +3 -2
  5. package/dist/daemon/server.d.ts +1 -4
  6. package/dist/daemon/server.js +0 -1
  7. package/dist/daemon-entry.js +6 -16
  8. package/dist/lib/auth/api.d.ts +0 -1
  9. package/dist/lib/auth/api.js +0 -12
  10. package/dist/lib/auth/provision.d.ts +1 -1
  11. package/dist/lib/auth/provision.js +5 -27
  12. package/dist/lib/daemon/daemon-client.d.ts +1 -3
  13. package/dist/lib/daemon/daemon-client.js +1 -2
  14. package/dist/lib/status/status-info.js +11 -9
  15. package/dist/lib/status/vaults-format.js +7 -2
  16. package/dist/lib/status/vaults-status.d.ts +2 -2
  17. package/dist/lib/status/vaults-status.js +13 -12
  18. package/dist/lib/vault/vault-registry.js +2 -3
  19. package/dist/sync/discover/watcher.js +1 -1
  20. package/dist/sync/git/manager.d.ts +0 -2
  21. package/dist/sync/git/planner.js +3 -3
  22. package/package.json +7 -7
  23. package/dist/sync/couch/cycle.d.ts +0 -2
  24. package/dist/sync/couch/cycle.js +0 -58
  25. package/dist/sync/couch/discovery.d.ts +0 -22
  26. package/dist/sync/couch/discovery.js +0 -34
  27. package/dist/sync/couch/file-store.d.ts +0 -2
  28. package/dist/sync/couch/file-store.js +0 -47
  29. package/dist/sync/couch/local-commit.d.ts +0 -2
  30. package/dist/sync/couch/local-commit.js +0 -24
  31. package/dist/sync/couch/manager.d.ts +0 -4
  32. package/dist/sync/couch/manager.fixtures.d.ts +0 -20
  33. package/dist/sync/couch/manager.fixtures.js +0 -56
  34. package/dist/sync/couch/manager.js +0 -126
  35. package/dist/sync/couch/manager.types.d.ts +0 -81
  36. package/dist/sync/couch/manager.types.js +0 -1
  37. package/dist/sync/couch/mutation-target.d.ts +0 -5
  38. package/dist/sync/couch/mutation-target.js +0 -33
  39. package/dist/sync/couch/push-on-write.d.ts +0 -3
  40. package/dist/sync/couch/push-on-write.js +0 -33
  41. package/dist/sync/couch/state-store.d.ts +0 -3
  42. package/dist/sync/couch/state-store.js +0 -28
  43. package/dist/sync/couch/targets.d.ts +0 -9
  44. package/dist/sync/couch/targets.js +0 -23
  45. package/dist/sync/couch/wire.d.ts +0 -6
  46. package/dist/sync/couch/wire.js +0 -16
@@ -83,23 +83,6 @@ const statusAction = async () => {
83
83
  console.log(` ${v.vault.padEnd(16)} ${cadence.padEnd(12)} ${state}`);
84
84
  }
85
85
  }
86
- if (sync?.couch && sync.couch.length > 0) {
87
- console.log('couch sync');
88
- for (const v of sync.couch) {
89
- const cadence = v.intervalSeconds > 0 ? `every ${v.intervalSeconds}s` : 'manual';
90
- const state = v.paused
91
- ? `paused: ${v.paused}`
92
- : v.running
93
- ? 'running'
94
- : v.lastError
95
- ? `error: ${v.lastError}`
96
- : v.lastSync
97
- ? `ok ${v.lastSync}`
98
- : 'scheduled';
99
- const pending = v.pendingCount > 0 ? ` ${v.pendingCount} pending` : '';
100
- console.log(` ${v.vault.padEnd(16)} ${cadence.padEnd(12)} ${state}${pending}`);
101
- }
102
- }
103
86
  if (sync?.discover && sync.discover.roots.length > 0) {
104
87
  console.log(`discover roots (${sync.discover.roots.length})`);
105
88
  for (const root of sync.discover.roots)
@@ -1,15 +1,13 @@
1
1
  import { type VaultsConfig } from '@agentage/memory-core';
2
- import { type SyncRunResult } from '../../lib/daemon/daemon-client.js';
3
2
  import { type SyncResult } from '../../sync/git/cycle.js';
4
- import { type CouchSyncResult } from '../../sync/couch/manager.js';
5
3
  import { type SyncTarget } from '../../sync/git/planner.js';
6
4
  export interface VaultSyncDeps {
7
5
  loadConfig: () => VaultsConfig;
8
6
  daemonPort: () => Promise<number | null>;
9
- runViaDaemon: (port: number, vault: string) => Promise<SyncRunResult>;
7
+ runViaDaemon: (port: number, vault: string) => Promise<SyncResult>;
10
8
  runGitInProcess: (target: SyncTarget) => Promise<SyncResult>;
11
- runCouchInProcess: (vault: string) => Promise<CouchSyncResult>;
12
9
  log: (msg: string) => void;
13
10
  }
11
+ export declare const ACCOUNT_NO_CHANNEL = "not synced - account vaults have no sync channel";
14
12
  export declare const runVaultSync: (name: string | undefined, deps: VaultSyncDeps) => Promise<void>;
15
13
  export declare const defaultVaultSyncDeps: () => VaultSyncDeps;
@@ -1,14 +1,13 @@
1
1
  import chalk from 'chalk';
2
+ import { isAccountVault } from '@agentage/memory-core';
2
3
  import { health, syncRun } from '../../lib/daemon/daemon-client.js';
3
4
  import { daemonDisabled } from '../../lib/daemon/daemon-pref.js';
4
5
  import { loadVaultsConfig } from '../../lib/vault/vaults.js';
5
6
  import { resolvePort } from '../../daemon/lifecycle.js';
6
7
  import { runSyncCycle } from '../../sync/git/cycle.js';
7
- import { createCouchSyncManager } from '../../sync/couch/manager.js';
8
- import { couchTargets } from '../../sync/couch/targets.js';
9
8
  import { syncTargets } from '../../sync/git/planner.js';
10
9
  import { redactRemoteUrl } from '../../sync/git/remote-url.js';
11
- const isCouch = (r) => 'channel' in r && r.channel === 'couch';
10
+ export const ACCOUNT_NO_CHANNEL = 'not synced - account vaults have no sync channel';
12
11
  const describeGit = (r) => {
13
12
  if (!r.ok)
14
13
  return chalk.red(`failed (${r.reason ?? 'error'})${r.error ? `: ${r.error}` : ''}`);
@@ -23,49 +22,43 @@ const describeGit = (r) => {
23
22
  bits.push('pushed');
24
23
  return chalk.green(bits.length ? bits.join(', ') : 'up to date');
25
24
  };
26
- const describeCouch = (r) => {
27
- if (r.paused)
28
- return chalk.yellow(`paused (${r.paused})`);
29
- if (!r.ok)
30
- return chalk.red(`failed${r.error ? `: ${r.error}` : ''}`);
31
- const bits = [];
32
- if (r.committed)
33
- bits.push('committed');
34
- if (r.pulled)
35
- bits.push('pulled');
36
- if (r.pendingCount)
37
- bits.push(`${r.pendingCount} pending`);
38
- return chalk.green(bits.length ? bits.join(', ') : 'up to date');
39
- };
40
25
  const report = (log, r) => {
41
- if (isCouch(r)) {
42
- log(`${r.vault} (account): ${describeCouch(r)}`);
43
- return;
44
- }
45
26
  log(`${r.vault} -> ${redactRemoteUrl(r.remote)}: ${describeGit(r)}`);
46
27
  for (const c of r.conflicts)
47
28
  log(` kept remote copy: ${c}`);
48
29
  };
49
- // `agentage vault sync [name]`: sync one vault (or every syncable vault). Git-origin vaults
50
- // commit + push + pull-rebase; account (agentage) vaults sync the couch channel. Prefers a running
51
- // daemon (single writer), else runs the cycle in-process. Works for interval-0 (manual-only) vaults
52
- // and with the daemon down. Failures are surfaced, not thrown (V6: never a crash).
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).
53
46
  export const runVaultSync = async (name, deps) => {
54
47
  const config = deps.loadConfig();
55
48
  // A named vault that is not registered is an error (mirrors `vault remove`), not a silent no-op.
56
49
  if (name && !(config.vaults ?? {})[name])
57
50
  throw new Error(`vault '${name}' not found`);
58
51
  const gitTargets = syncTargets(config).filter((t) => !name || t.vault === name);
59
- const couchVaults = couchTargets(config)
60
- .filter((t) => !name || t.vault === name)
61
- .map((t) => t.vault);
62
- if (gitTargets.length === 0 && couchVaults.length === 0) {
63
- deps.log(name
64
- ? `No syncable origin configured for vault '${name}'.`
65
- : 'No syncable vaults. Add one with `agentage vault add <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>`.');
66
59
  return;
67
60
  }
68
- const vaults = [...new Set([...gitTargets.map((t) => t.vault), ...couchVaults])];
61
+ const vaults = [...new Set(gitTargets.map((t) => t.vault))];
69
62
  deps.log(`Syncing ${vaults.length} vault(s)...`);
70
63
  const port = await deps.daemonPort();
71
64
  if (port !== null) {
@@ -73,16 +66,14 @@ export const runVaultSync = async (name, deps) => {
73
66
  deps.log(`${vault}...`);
74
67
  report(deps.log, await deps.runViaDaemon(port, vault));
75
68
  }
76
- return;
77
- }
78
- for (const target of gitTargets) {
79
- deps.log(`${target.vault}...`);
80
- report(deps.log, await deps.runGitInProcess(target));
81
69
  }
82
- for (const vault of couchVaults) {
83
- deps.log(`${vault}...`);
84
- report(deps.log, await deps.runCouchInProcess(vault));
70
+ else {
71
+ for (const target of gitTargets) {
72
+ deps.log(`${target.vault}...`);
73
+ report(deps.log, await deps.runGitInProcess(target));
74
+ }
85
75
  }
76
+ reportAccounts(deps.log, accounts);
86
77
  };
87
78
  const resolveDaemonPort = async () => {
88
79
  if (daemonDisabled())
@@ -95,6 +86,5 @@ export const defaultVaultSyncDeps = () => ({
95
86
  daemonPort: resolveDaemonPort,
96
87
  runViaDaemon: syncRun,
97
88
  runGitInProcess: runSyncCycle,
98
- runCouchInProcess: (vault) => createCouchSyncManager().runNow(vault),
99
89
  log: (msg) => console.log(msg),
100
90
  });
@@ -28,7 +28,8 @@ const buildEntry = (name, opts) => {
28
28
  const path = typeof opts.local === 'string' ? opts.local : `~/vaults/${name}`;
29
29
  return { path, mcp: ['local'] };
30
30
  }
31
- // No --local/--git: an account vault - a local mirror synced to the account (agentage) channel.
31
+ // No --local/--git: an account vault - a local folder plus a memory in your account; this CLI
32
+ // has no channel that syncs the two.
32
33
  return { path: opts.path ?? `~/vaults/${name}`, origin: [{ remote: 'agentage' }] };
33
34
  };
34
35
  export const runVaultAdd = async (name, opts, deps = defaultDeps) => {
@@ -120,7 +121,7 @@ export const registerVault = (program) => {
120
121
  .action((name) => guardAsync(() => runVaultRemove(name)));
121
122
  vault
122
123
  .command('sync [name]')
123
- .description('Sync vaults now (git commit/push/pull, or the account channel)')
124
+ .description('Sync vaults now (git commit/push/pull)')
124
125
  .action((name) => runVaultSync(name, defaultVaultSyncDeps()).catch((err) => {
125
126
  console.error(chalk.red(err instanceof Error ? err.message : String(err)));
126
127
  process.exitCode = 1;
@@ -2,18 +2,15 @@ import { type Server } from 'node:http';
2
2
  import { type McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import { type MemoryClient } from '../lib/memory/memory-client.js';
4
4
  import { type SyncResult } from '../sync/git/cycle.js';
5
- import { type CouchSyncResult } from '../sync/couch/manager.js';
6
5
  import { type SyncStatus } from '../sync/git/manager.js';
7
- import { type MemoryVerb } from './actions.js';
8
6
  export interface DaemonSyncApi {
9
7
  status: () => SyncStatus;
10
- runNow: (vault: string) => Promise<SyncResult | CouchSyncResult>;
8
+ runNow: (vault: string) => Promise<SyncResult>;
11
9
  }
12
10
  export interface DaemonServerOptions {
13
11
  getClient: () => MemoryClient | Promise<MemoryClient>;
14
12
  buildMcpServer?: () => Promise<McpServer>;
15
13
  sync?: DaemonSyncApi;
16
- onMutation?: (verb: MemoryVerb, body: unknown) => void;
17
14
  authToken: string;
18
15
  version: string;
19
16
  startedAt?: number;
@@ -93,7 +93,6 @@ export const createDaemonServer = (opts) => {
93
93
  const body = await readBody(req);
94
94
  const result = await dispatchMemory(await opts.getClient(), verb, body);
95
95
  served += 1;
96
- opts.onMutation?.(verb, body); // fire-and-forget account push-on-save
97
96
  return send(res, 200, result);
98
97
  }
99
98
  catch (err) {
@@ -1,12 +1,10 @@
1
1
  import { unwatchFile, watchFile } from 'node:fs';
2
2
  import { fileURLToPath } from 'node:url';
3
- import { isAccountVault } from '@agentage/memory-core';
4
3
  import { createClientProvider } from './daemon/client-provider.js';
5
4
  import { EADDRINUSE_EXIT_CODE, generateDaemonToken, removePidFile, removePortFile, removeTokenFile, resolvePort, writePidFile, writePortFile, writeTokenFile, } from './daemon/lifecycle.js';
6
5
  import { createDaemonServer } from './daemon/server.js';
7
6
  import { loadLocalMemoryServer } from './mcp/local-server.js';
8
- import { loadVaultsConfig, vaultsJsonPath } from './lib/vault/vaults.js';
9
- import { createCouchSyncManager } from './sync/couch/manager.js';
7
+ import { vaultsJsonPath } from './lib/vault/vaults.js';
10
8
  import { createDiscoverWatcher } from './sync/discover/watcher.js';
11
9
  import { createSyncManager } from './sync/git/manager.js';
12
10
  import { VERSION } from './utils/version.js';
@@ -26,7 +24,7 @@ export const createStateCleanup = (remove) => {
26
24
  };
27
25
  };
28
26
  // Run each reschedule independently: a transiently-invalid config edit must not crash the daemon or
29
- // stop the other channels rescheduling; the throwing one keeps its last-good schedule.
27
+ // stop the others rescheduling; the throwing one keeps its last-good schedule.
30
28
  export const safeReschedule = (steps, onError) => {
31
29
  for (const step of steps) {
32
30
  try {
@@ -55,30 +53,23 @@ const state = createStateCleanup(() => {
55
53
  });
56
54
  // The detached, long-lived engine host: one loopback HTTP server that owns a single in-process
57
55
  // engine and serialises every vault mutation, avoiding concurrent git index.lock collisions. It
58
- // runs both sync loops (git origins + the account/couch channel) and reschedules on config change.
56
+ // runs the git sync loop and reschedules on config change.
59
57
  const main = async () => {
60
58
  const port = resolvePort();
61
59
  const authToken = generateDaemonToken();
62
60
  const git = createSyncManager();
63
- const couch = createCouchSyncManager();
64
61
  const discover = createDiscoverWatcher({
65
62
  log: (msg) => console.log(`[discover] ${msg}`),
66
63
  debounceMs: envInt('AGENTAGE_DISCOVER_DEBOUNCE_MS'),
67
64
  pollMs: envInt('AGENTAGE_DISCOVER_POLL_MS'),
68
65
  });
69
- // A vault is on exactly one channel: an account (agentage) vault syncs over couch, else git.
70
- const runNow = (vault) => {
71
- const entry = loadVaultsConfig().config.vaults?.[vault];
72
- return entry && isAccountVault(entry) ? couch.runNow(vault) : git.runNow(vault);
73
- };
74
66
  const server = createDaemonServer({
75
67
  getClient: createClientProvider(),
76
68
  buildMcpServer: mcpEnabled() ? loadLocalMemoryServer : undefined,
77
69
  sync: {
78
- status: () => ({ ...git.status(), couch: couch.status(), discover: discover.status() }),
79
- runNow,
70
+ status: () => ({ ...git.status(), discover: discover.status() }),
71
+ runNow: (vault) => git.runNow(vault),
80
72
  },
81
- onMutation: (verb, body) => couch.onWrite(verb, body),
82
73
  authToken,
83
74
  version: VERSION,
84
75
  });
@@ -87,14 +78,13 @@ const main = async () => {
87
78
  writePortFile(port);
88
79
  writeTokenFile(authToken);
89
80
  state.markOwned();
90
- const reschedule = () => safeReschedule([() => git.reschedule(), () => couch.reschedule(), () => discover.reschedule()], (msg) => console.error(`[daemon] reschedule failed: ${msg}`));
81
+ const reschedule = () => safeReschedule([() => git.reschedule(), () => discover.reschedule()], (msg) => console.error(`[daemon] reschedule failed: ${msg}`));
91
82
  reschedule();
92
83
  const configPath = vaultsJsonPath();
93
84
  watchFile(configPath, { interval: 2000 }, reschedule);
94
85
  const shutdown = () => {
95
86
  unwatchFile(configPath);
96
87
  git.stop();
97
- couch.stop();
98
88
  discover.stop();
99
89
  server.stop().finally(() => {
100
90
  state.cleanup();
@@ -2,7 +2,6 @@ import { type AuthState } from '../fs/config.js';
2
2
  import { type Links } from '../net/origins.js';
3
3
  export { AuthRequiredError, TransientAuthError } from './auth-errors.js';
4
4
  export declare const refreshOrThrow: (auth: AuthState, links: Links) => Promise<void>;
5
- export declare const currentBearer: (readAuth: () => AuthState | null, links: Links) => Promise<string | null>;
6
5
  export declare const authedGet: <T>(auth: AuthState, links: Links, url: string) => Promise<T>;
7
6
  export declare const authedPost: (auth: AuthState, links: Links, url: string, body: unknown) => Promise<Response>;
8
7
  export { introspectToken, type TokenSession } from './introspect.js';
@@ -47,18 +47,6 @@ const tryRefresh = async (auth, links) => {
47
47
  return false;
48
48
  }
49
49
  };
50
- // The current OAuth bearer for background (couch) sync. Reads auth.json fresh on every call - the
51
- // user may sign in or out between ticks - and refreshes once when the stored token is past its
52
- // stated expiry. Returns null when signed out so a caller pauses with zero network, never throws.
53
- export const currentBearer = async (readAuth, links) => {
54
- const auth = readAuth();
55
- if (!auth?.tokens.accessToken)
56
- return null;
57
- const expired = auth.tokens.expiresAt !== undefined && auth.tokens.expiresAt <= Date.now();
58
- if (expired && !(await tryRefresh(auth, links)))
59
- return null;
60
- return auth.tokens.accessToken;
61
- };
62
50
  // redirect: 'manual' so the bearer is never replayed to a redirect target; any 3xx is an error.
63
51
  const isRedirect = (res) => res.type === 'opaqueredirect' || (res.status >= 300 && res.status < 400);
64
52
  export const authedGet = async (auth, links, url) => {
@@ -1,6 +1,6 @@
1
1
  import { type AuthState } from '../fs/config.js';
2
2
  import { type Links } from '../net/origins.js';
3
- export type ProvisionStatus = 'provisioned' | 'exists' | 'disabled' | 'conflict' | 'unauthenticated' | 'offline';
3
+ export type ProvisionStatus = 'provisioned' | 'exists' | 'unauthenticated' | 'offline';
4
4
  export interface ProvisionResult {
5
5
  status: ProvisionStatus;
6
6
  message: string;
@@ -6,39 +6,28 @@ export const defaultProvisionDeps = () => ({
6
6
  links: () => buildLinks(siteFqdn()),
7
7
  post: authedPost,
8
8
  });
9
- // The API error envelope carries the code as `error.code` (or a top-level `code`); read it best
10
- // effort - a non-JSON body just yields undefined and the caller falls back to non-fatal.
11
- const errorCode = async (res) => {
12
- try {
13
- const body = (await res.json());
14
- return body.error?.code ?? body.code;
15
- }
16
- catch {
17
- return undefined;
18
- }
19
- };
20
9
  const registeredLocally = (name, tail) => `Vault '${name}' registered locally${tail}`;
21
10
  export const provisionAccountVault = async (name, deps = defaultProvisionDeps()) => {
22
11
  const auth = deps.readAuth();
23
12
  if (!auth) {
24
13
  return {
25
14
  status: 'unauthenticated',
26
- message: registeredLocally(name, ' - run `agentage setup` to sync.'),
15
+ message: registeredLocally(name, ' - run `agentage setup` to create it in your account.'),
27
16
  };
28
17
  }
29
18
  // A PAT is an MCP-surface credential; the backend REST provisioning endpoint rejects plain
30
- // bearers (only session cookies), so it cannot provision an account channel. Fail clearly.
19
+ // bearers (only session cookies), so it cannot create the account memory. Fail clearly.
31
20
  if (auth.kind === 'pat') {
32
21
  return {
33
22
  status: 'unauthenticated',
34
- message: registeredLocally(name, ' - account-channel provisioning needs an interactive session (run `agentage setup`); ' +
23
+ message: registeredLocally(name, ' - account provisioning needs an interactive session (run `agentage setup`); ' +
35
24
  'a personal access token only authorizes memory (MCP) calls.'),
36
25
  };
37
26
  }
38
27
  const links = deps.links();
39
28
  let res;
40
29
  try {
41
- res = await deps.post(auth, links, `${links.api}/memories`, { name, channel: 'couch' });
30
+ res = await deps.post(auth, links, `${links.api}/memories`, { name });
42
31
  }
43
32
  catch {
44
33
  return {
@@ -53,18 +42,7 @@ export const provisionAccountVault = async (name, deps = defaultProvisionDeps())
53
42
  if (res.status === 401)
54
43
  return {
55
44
  status: 'unauthenticated',
56
- message: registeredLocally(name, ' - run `agentage setup` to sync.'),
57
- };
58
- const code = await errorCode(res);
59
- if (res.status === 403 && code === 'CHANNEL_DISABLED')
60
- return {
61
- status: 'disabled',
62
- message: registeredLocally(name, '. Account sync is not enabled on this server.'),
63
- };
64
- if (res.status === 409 && code === 'CHANNEL_CONFLICT')
65
- return {
66
- status: 'conflict',
67
- message: registeredLocally(name, `. A memory named '${name}' already exists on another channel - not syncing.`),
45
+ message: registeredLocally(name, ' - run `agentage setup` to create it in your account.'),
68
46
  };
69
47
  // Any other status stays non-fatal: keep the local entry, let the daemon retry later.
70
48
  return { status: 'offline', message: registeredLocally(name, ' - will provision when online.') };
@@ -1,7 +1,5 @@
1
1
  import { type SyncResult } from '../../sync/git/cycle.js';
2
- import { type CouchSyncResult } from '../../sync/couch/manager.js';
3
2
  import { type SyncStatus } from '../../sync/git/manager.js';
4
- export type SyncRunResult = SyncResult | CouchSyncResult;
5
3
  import { type MemoryClient } from '../memory/memory-client.js';
6
4
  export interface Health {
7
5
  ok: boolean;
@@ -13,7 +11,7 @@ export interface Health {
13
11
  }
14
12
  export declare const health: (port: number, timeoutMs?: number) => Promise<Health | null>;
15
13
  export declare const syncStatus: (port: number, timeoutMs?: number) => Promise<SyncStatus | null>;
16
- export declare const syncRun: (port: number, vault: string) => Promise<SyncRunResult>;
14
+ export declare const syncRun: (port: number, vault: string) => Promise<SyncResult>;
17
15
  export declare const waitForHealth: (port: number, opts?: {
18
16
  timeoutMs?: number;
19
17
  intervalMs?: number;
@@ -47,8 +47,7 @@ export const syncStatus = async (port, timeoutMs = 1000) => {
47
47
  return null;
48
48
  }
49
49
  };
50
- // Ask the daemon to sync one vault now; the daemon runs the cycle in its own process. The result
51
- // shape depends on the vault's channel (git SyncResult vs account CouchSyncResult).
50
+ // Ask the daemon to sync one vault now; the daemon runs the git cycle in its own process.
52
51
  export const syncRun = async (port, vault) => {
53
52
  const res = await fetch(`${base(port)}/api/sync/run`, {
54
53
  method: 'POST',
@@ -20,18 +20,20 @@ const checkSite = async (siteUrl, headers) => {
20
20
  const res = await fetchJsonUnref(siteUrl, 3000, headers);
21
21
  return res !== null;
22
22
  };
23
- // Fold the git + couch per-vault states into one summary: any error wins, then any in-flight
24
- // run, else ok. lastRun is the freshest reported; lastError the first seen.
23
+ // Fold the per-vault git states into one summary: any error wins, then any in-flight run, else ok.
24
+ // lastRun is the freshest reported; lastError the first seen. Account vaults are absent by design -
25
+ // they have no sync channel, so they are never counted as synced here (the vaults block names them).
25
26
  const summarizeSync = (sync) => {
26
27
  const git = Array.isArray(sync.vaults) ? sync.vaults : [];
27
- const couch = Array.isArray(sync.couch) ? sync.couch : [];
28
- const vaults = git.length + couch.length;
29
- const error = git.find((v) => v.lastError)?.lastError ?? couch.find((v) => v.lastError)?.lastError;
30
- const running = git.some((v) => v.running) || couch.some((v) => v.running);
28
+ const error = git.find((v) => v.lastError)?.lastError;
29
+ const running = git.some((v) => v.running);
31
30
  const state = error ? 'error' : running ? 'syncing' : 'ok';
32
- const runs = [...git.map((v) => v.lastRun), ...couch.map((v) => v.lastSync)].filter((r) => Boolean(r));
33
- const lastRun = runs.sort().at(-1);
34
- return { vaults, state, lastRun, lastError: error };
31
+ const lastRun = git
32
+ .map((v) => v.lastRun)
33
+ .filter((r) => Boolean(r))
34
+ .sort()
35
+ .at(-1);
36
+ return { vaults: git.length, state, lastRun, lastError: error };
35
37
  };
36
38
  const probeDaemon = async () => {
37
39
  const port = resolvePort();
@@ -21,12 +21,16 @@ const statusCell = (v) => {
21
21
  return `${mark(false)} error (${shortError(v.lastError)})`;
22
22
  case 'unknown':
23
23
  return chalk.dim('- unknown (daemon stopped)');
24
+ case 'unsynced':
25
+ return `${mark(false)} not synced - account vaults have no sync channel`;
24
26
  case 'idle':
25
27
  return v.channel === 'local' ? chalk.dim('- local only') : chalk.dim('- idle');
26
28
  }
27
29
  };
30
+ // Only git vaults are "connected": an account vault has no sync channel, so counting it would
31
+ // re-tell the lie the per-vault row exists to correct.
28
32
  const countLabel = (vaults) => {
29
- const connected = vaults.filter((v) => v.channel !== 'local').length;
33
+ const connected = vaults.filter((v) => v.channel === 'git').length;
30
34
  if (connected > 0)
31
35
  return `${connected} connected`;
32
36
  const n = vaults.length;
@@ -38,7 +42,8 @@ export const vaultLines = (vaults) => {
38
42
  if (vaults.length === 0)
39
43
  return [`${'vaults'.padEnd(10)} none - run: agentage vault add <name> --local`];
40
44
  const nameW = Math.max(...vaults.map((v) => v.name.length));
45
+ const chanW = Math.max(...vaults.map((v) => v.channel.length));
41
46
  const header = `${'vaults'.padEnd(10)} ${countLabel(vaults)}`;
42
- const rows = vaults.map((v) => ` ${v.name.padEnd(nameW)} ${v.channel.padEnd(6)} ${statusCell(v)}`);
47
+ const rows = vaults.map((v) => ` ${v.name.padEnd(nameW)} ${v.channel.padEnd(chanW)} ${statusCell(v)}`);
43
48
  return [header, ...rows];
44
49
  };
@@ -1,7 +1,7 @@
1
1
  import { type VaultsConfig } from '@agentage/memory-core';
2
2
  import { type SyncStatus } from '../../sync/git/manager.js';
3
- export type VaultChannel = 'local' | 'git' | 'cloud';
4
- export type VaultSyncState = 'ok' | 'syncing' | 'error' | 'idle' | 'unknown';
3
+ export type VaultChannel = 'local' | 'git' | 'account';
4
+ export type VaultSyncState = 'ok' | 'syncing' | 'error' | 'idle' | 'unknown' | 'unsynced';
5
5
  export interface VaultStatus {
6
6
  name: string;
7
7
  channel: VaultChannel;
@@ -1,19 +1,22 @@
1
1
  import { isAccountVault } from '@agentage/memory-core';
2
2
  import { loadVaultsConfig } from '../vault/vaults.js';
3
- // Config alone decides the channel: an `agentage` origin is the cloud (couch) channel, any other
4
- // origin is an external git remote, and no origin at all is a local-only vault (nothing to sync).
3
+ // Config alone decides the channel: an external remote means git (it really syncs), else an
4
+ // `agentage` origin means account, else local-only. External wins so a hand-edited entry carrying
5
+ // both is reported by the channel that actually moves bytes.
5
6
  const channelOf = (entry) => {
6
- if (isAccountVault(entry))
7
- return 'cloud';
8
- return entry.origin?.some((o) => o.remote.trim() && o.remote.trim() !== 'agentage')
9
- ? 'git'
10
- : 'local';
7
+ const external = entry.origin?.some((o) => o.remote.trim() && o.remote.trim() !== 'agentage');
8
+ if (external)
9
+ return 'git';
10
+ return isAccountVault(entry) ? 'account' : 'local';
11
11
  };
12
- // Live state from the daemon wins; a local-only vault is `idle` (nothing to sync), and any synced
13
- // vault with no daemon report is `unknown` (daemon down or the vault not yet scheduled).
12
+ // Live state from the daemon wins; a local-only vault is `idle` (nothing to sync), an account vault
13
+ // is always `unsynced` (it has no sync channel, so no daemon report can make it healthy), and any
14
+ // git vault with no daemon report is `unknown` (daemon down or the vault not yet scheduled).
14
15
  const stateFrom = (channel, live, daemonUp) => {
15
16
  if (channel === 'local')
16
17
  return 'idle';
18
+ if (channel === 'account')
19
+ return 'unsynced';
17
20
  if (!daemonUp)
18
21
  return 'unknown';
19
22
  if (!live)
@@ -24,13 +27,11 @@ const stateFrom = (channel, live, daemonUp) => {
24
27
  return 'syncing';
25
28
  return live.lastRun ? 'ok' : 'idle';
26
29
  };
27
- // Index the daemon's per-vault reports by name across both channels into one lookup.
30
+ // Index the daemon's per-vault git reports by name.
28
31
  const indexLive = (sync) => {
29
32
  const map = new Map();
30
33
  for (const v of sync?.vaults ?? [])
31
34
  map.set(v.vault, { running: v.running, lastError: v.lastError, lastRun: v.lastRun });
32
- for (const c of sync?.couch ?? [])
33
- map.set(c.vault, { running: c.running, lastError: c.lastError, lastRun: c.lastSync });
34
35
  return map;
35
36
  };
36
37
  // The full per-vault picture: every configured vault (so local-only vaults are never hidden),
@@ -3,9 +3,8 @@ import { resolve } from 'node:path';
3
3
  import { expandPath, isAccountVault, validateConfig, } from '@agentage/memory-core';
4
4
  import { redactRemoteUrl } from '../../sync/git/remote-url.js';
5
5
  import { isValidVaultName } from './vaults.schema.js';
6
- // The human-facing type of an entry: an agentage origin is an account vault (local mirror +
7
- // cloud channel); otherwise a path with an external origin is git, a bare path is local, and an
8
- // origin without a path is remote.
6
+ // The human-facing type of an entry: an agentage origin is an account vault; otherwise a path with
7
+ // an external origin is git, a bare path is local, and an origin without a path is remote.
9
8
  export const vaultType = (entry) => {
10
9
  if (isAccountVault(entry))
11
10
  return 'account';
@@ -80,7 +80,7 @@ export const createDiscoverWatcher = (deps = {}) => {
80
80
  return [];
81
81
  for (const c of added) {
82
82
  log(`discovered account vault '${c.name}' -> ${c.entry.path}`);
83
- void provision(c.name).catch(() => { }); // never fatal: the couch loop re-provisions
83
+ void provision(c.name).catch(() => { }); // never fatal: the next scan re-provisions
84
84
  }
85
85
  return added;
86
86
  };
@@ -1,5 +1,4 @@
1
1
  import { type VaultsConfig } from '@agentage/memory-core';
2
- import { type CouchTargetStatus } from '../couch/manager.js';
3
2
  import { type DiscoverStatus } from '../discover/watcher.js';
4
3
  import { type SyncResult } from './cycle.js';
5
4
  import { type SyncTarget } from './planner.js';
@@ -14,7 +13,6 @@ export interface VaultSyncState {
14
13
  }
15
14
  export interface SyncStatus {
16
15
  vaults: VaultSyncState[];
17
- couch?: CouchTargetStatus[];
18
16
  discover?: DiscoverStatus;
19
17
  }
20
18
  export interface SyncManagerDeps {
@@ -7,15 +7,15 @@ export const DEFAULT_INTERVAL_SECONDS = 300;
7
7
  // When `ignore` is absent these editor/runtime files are excluded from sync; a set value REPLACES
8
8
  // the defaults, and an empty array syncs everything.
9
9
  export const DEFAULT_IGNORE = ['.obsidian/', 'data.json'];
10
- // The reserved cloud channel is never synced over external git (that path is out of scope here).
10
+ // The reserved account remote is a sentinel, not a URL: never synced over external git.
11
11
  const RESERVED_REMOTE = 'agentage';
12
12
  export const resolveIgnore = (ignore) => ignore === undefined ? [...DEFAULT_IGNORE] : ignore;
13
13
  export const intervalMs = (seconds) => Math.max(0, Math.floor(seconds)) * 1000;
14
14
  // One vault may carry several origins; each gets a distinct remote name within its own repo.
15
15
  const remoteNameFor = (index) => (index === 0 ? 'sync' : `sync-${index}`);
16
16
  // Flatten (vault, origin) pairs into sync targets. A target needs a local `path` (the working
17
- // copy to commit/push from) AND an external origin; origin-only entries (cloud remote backends)
18
- // and the reserved cloud channel are skipped.
17
+ // copy to commit/push from) AND an external origin; origin-only entries (remote backends) and the
18
+ // reserved account remote are skipped.
19
19
  export const syncTargets = (config) => {
20
20
  const out = [];
21
21
  for (const [vault, entry] of Object.entries(config.vaults ?? {})) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentage/cli",
3
- "version": "0.25.0",
3
+ "version": "0.27.0",
4
4
  "description": "The agentage CLI - connect this machine to agentage from the terminal",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -40,15 +40,15 @@
40
40
  "prepublishOnly": "npm run verify"
41
41
  },
42
42
  "dependencies": {
43
- "@agentage/memory-core": "^0.3.2",
44
- "@agentage/server-memory": "^0.1.0",
43
+ "@agentage/memory-core": "^0.5.0",
44
+ "@agentage/server-memory": "^0.3.0",
45
45
  "@modelcontextprotocol/sdk": "^1.29.0",
46
- "chalk": "^5.6.2",
47
- "commander": "^14.0.3",
46
+ "chalk": "^6.0.0",
47
+ "commander": "^15.0.0",
48
48
  "open": "^11.0.0"
49
49
  },
50
50
  "devDependencies": {
51
- "@anthropic-ai/sdk": "0.112.1",
51
+ "@anthropic-ai/sdk": "0.116.0",
52
52
  "@playwright/test": "latest",
53
53
  "@types/node": "latest",
54
54
  "@typescript-eslint/eslint-plugin": "latest",
@@ -58,7 +58,7 @@
58
58
  "eslint-config-prettier": "latest",
59
59
  "eslint-plugin-prettier": "latest",
60
60
  "prettier": "latest",
61
- "typescript": "latest",
61
+ "typescript": "6.0.3",
62
62
  "vitest": "latest"
63
63
  },
64
64
  "keywords": [