@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,54 +0,0 @@
1
- import { isAccountVault } from '@agentage/memory-core';
2
- import { loadVaultsConfig } from '../vault/vaults.js';
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.
6
- const channelOf = (entry) => {
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
- };
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).
15
- const stateFrom = (channel, live, daemonUp) => {
16
- if (channel === 'local')
17
- return 'idle';
18
- if (channel === 'account')
19
- return 'unsynced';
20
- if (!daemonUp)
21
- return 'unknown';
22
- if (!live)
23
- return 'unknown';
24
- if (live.lastError)
25
- return 'error';
26
- if (live.running)
27
- return 'syncing';
28
- return live.lastRun ? 'ok' : 'idle';
29
- };
30
- // Index the daemon's per-vault git reports by name.
31
- const indexLive = (sync) => {
32
- const map = new Map();
33
- for (const v of sync?.vaults ?? [])
34
- map.set(v.vault, { running: v.running, lastError: v.lastError, lastRun: v.lastRun });
35
- return map;
36
- };
37
- // The full per-vault picture: every configured vault (so local-only vaults are never hidden),
38
- // classified by channel and annotated with the daemon's live sync state when available.
39
- export const buildVaultStatuses = (sync, daemonUp, config = loadVaultsConfig().config) => {
40
- const live = indexLive(sync);
41
- return Object.entries(config.vaults ?? {})
42
- .map(([name, entry]) => {
43
- const channel = channelOf(entry);
44
- const l = live.get(name);
45
- return {
46
- name,
47
- channel,
48
- status: stateFrom(channel, l, daemonUp),
49
- lastRun: l?.lastRun,
50
- lastError: l?.lastError,
51
- };
52
- })
53
- .sort((a, b) => a.name.localeCompare(b.name));
54
- };
@@ -1,2 +0,0 @@
1
- export declare const acquireUpdateLock: (now?: number) => boolean;
2
- export declare const releaseUpdateLock: () => void;
@@ -1,109 +0,0 @@
1
- import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { ensureConfigDir, getConfigDir } from '../fs/config.js';
4
- const LOCK_FILE = 'update.lock';
5
- const LOCK_TTL_MS = 10 * 60 * 1000; // a lock older than this whose holder is gone is a crashed run
6
- // Past this age a holder is taken over even if its pid still resolves (pid reuse after a crash); far
7
- // beyond any real run or scheduler pause, so a live-but-slow holder is never mistaken for crashed.
8
- const STALE_HARD_MS = 30 * 60 * 1000;
9
- const lockPath = () => join(getConfigDir(), LOCK_FILE);
10
- // The lock file holds "<pid> <timestamp>": pid gates crash-takeover, the trailing token is the
11
- // acquisition time. A legacy single-token file parses as pid 0 (unknown holder), aging out on TTL.
12
- const holderOf = (path) => {
13
- try {
14
- const parts = readFileSync(path, 'utf-8').trim().split(/\s+/);
15
- const at = Number.parseInt(parts[parts.length - 1] ?? '', 10);
16
- if (Number.isNaN(at))
17
- return null;
18
- const pid = parts.length >= 2 ? Number.parseInt(parts[0] ?? '', 10) : 0;
19
- return { pid: Number.isNaN(pid) ? 0 : pid, at };
20
- }
21
- catch {
22
- return null;
23
- }
24
- };
25
- // Same-host liveness probe. ESRCH = the holder is gone (crashed); EPERM = alive but owned by another
26
- // user. pid 0/unknown counts as gone so a legacy or malformed lock still ages out on the TTL.
27
- const holderAlive = (pid) => {
28
- if (pid <= 0)
29
- return false;
30
- try {
31
- process.kill(pid, 0);
32
- return true;
33
- }
34
- catch (err) {
35
- return err.code === 'EPERM';
36
- }
37
- };
38
- // A holder is safe to take over only once it is past the TTL AND provably gone: a dead pid, our own
39
- // leftover pid, or so old that pid reuse is the only explanation. A live holder merely starved of
40
- // CPU is never stolen - stealing it would let two writers run concurrently.
41
- const isCrashed = (holder, now) => {
42
- const age = now - holder.at;
43
- if (age < LOCK_TTL_MS)
44
- return false;
45
- if (age >= STALE_HARD_MS)
46
- return true;
47
- return holder.pid === process.pid || !holderAlive(holder.pid);
48
- };
49
- const unlinkQuiet = (path) => {
50
- try {
51
- unlinkSync(path);
52
- }
53
- catch {
54
- // already gone
55
- }
56
- };
57
- // Serialize stale-lock takeover behind its own exclusive create, so no taker can ever delete a
58
- // lock that turned FRESH mid-takeover (the ABA race: reader sees the stale timestamp, a winner
59
- // re-creates the lock, the reader deletes the winner's file). Under the guard the staleness is
60
- // re-checked and only a genuinely stale lock is cleared; losers simply refuse. A guard left by a
61
- // crashed taker expires like the lock itself. Returns whether to re-race the create.
62
- const takeOverStale = (now) => {
63
- const guard = `${lockPath()}.takeover`;
64
- const guardHeld = holderOf(guard);
65
- if (guardHeld !== null && isCrashed(guardHeld, now))
66
- unlinkQuiet(guard);
67
- try {
68
- writeFileSync(guard, `${process.pid} ${now}`, { flag: 'wx' });
69
- }
70
- catch {
71
- return false; // another taker is mid-takeover; let it win
72
- }
73
- try {
74
- const holder = holderOf(lockPath());
75
- if (holder !== null && !isCrashed(holder, now))
76
- return false; // fresh or a live-but-slow holder
77
- unlinkQuiet(lockPath());
78
- return true;
79
- }
80
- finally {
81
- unlinkQuiet(guard);
82
- }
83
- };
84
- // Acquire the single-writer update lock. The O_EXCL ('wx') create IS the mutex - concurrent runs
85
- // cannot both win it, unlike a check-then-write. A fresh holder (younger than the TTL) refuses;
86
- // a stale one (crashed run) is cleared under the takeover guard and re-raced once. `now` is
87
- // injectable.
88
- export const acquireUpdateLock = (now = Date.now()) => {
89
- ensureConfigDir();
90
- for (let attempt = 0; attempt < 2; attempt++) {
91
- try {
92
- writeFileSync(lockPath(), `${process.pid} ${now}`, { flag: 'wx' });
93
- return true;
94
- }
95
- catch {
96
- const holder = holderOf(lockPath());
97
- if (holder !== null && !isCrashed(holder, now))
98
- return false;
99
- if (!takeOverStale(now))
100
- return false;
101
- }
102
- }
103
- return false;
104
- };
105
- export const releaseUpdateLock = () => {
106
- const path = lockPath();
107
- if (existsSync(path))
108
- unlinkSync(path);
109
- };
@@ -1,12 +0,0 @@
1
- import { type VaultEntry, type VaultsConfig } from '@agentage/memory-core';
2
- export type VaultType = 'account' | 'git' | 'local' | 'remote';
3
- export declare const vaultType: (entry: VaultEntry) => VaultType;
4
- export declare const ensureVaultDir: (path: string) => void;
5
- export declare const formatVaultLine: (name: string, entry: VaultEntry) => string;
6
- export declare const redactEntry: (entry: VaultEntry) => VaultEntry;
7
- export declare const addVault: (config: VaultsConfig, name: string, entry: VaultEntry) => VaultsConfig;
8
- export declare const removeVault: (config: VaultsConfig, name: string) => VaultsConfig;
9
- export declare const appendDiscoverIgnore: (config: VaultsConfig, name: string, vaultPath: string) => {
10
- config: VaultsConfig;
11
- root: string;
12
- } | null;
@@ -1,81 +0,0 @@
1
- import { existsSync, mkdirSync } from 'node:fs';
2
- import { resolve } from 'node:path';
3
- import { expandPath, isAccountVault, validateConfig, } from '@agentage/memory-core';
4
- import { redactRemoteUrl } from '../../sync/git/remote-url.js';
5
- import { isValidVaultName } from './vaults.schema.js';
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.
8
- export const vaultType = (entry) => {
9
- if (isAccountVault(entry))
10
- return 'account';
11
- if (entry.path)
12
- return entry.origin?.length ? 'git' : 'local';
13
- return 'remote';
14
- };
15
- // A local vault's markdown directory is created on `vault add` (~ is expanded).
16
- export const ensureVaultDir = (path) => {
17
- const dir = expandPath(path);
18
- if (!existsSync(dir))
19
- mkdirSync(dir, { recursive: true });
20
- };
21
- export const formatVaultLine = (name, entry) => {
22
- const kind = vaultType(entry);
23
- const rawWhere = entry.path ? expandPath(entry.path) : (entry.origin?.[0]?.remote ?? '');
24
- const where = entry.path ? rawWhere : redactRemoteUrl(rawWhere);
25
- // Only an external git remote is worth echoing; the account channel is implied by the type.
26
- const remote = kind === 'git' && entry.origin?.length
27
- ? ` <- ${redactRemoteUrl(entry.origin[0].remote)}`
28
- : '';
29
- return `${name.padEnd(16)} ${kind.padEnd(8)} ${where}${remote}`;
30
- };
31
- // A copy of `entry` with every origin remote redacted (credentials stripped) for display/JSON;
32
- // the on-disk value is never mutated.
33
- export const redactEntry = (entry) => entry.origin?.length
34
- ? { ...entry, origin: entry.origin.map((o) => ({ ...o, remote: redactRemoteUrl(o.remote) })) }
35
- : entry;
36
- // Add an entry under `name`; the first vault added also becomes the `default`.
37
- export const addVault = (config, name, entry) => {
38
- if (!isValidVaultName(name))
39
- throw new Error(`invalid vault name: ${JSON.stringify(name)}`);
40
- const vaults = config.vaults ?? {};
41
- if (vaults[name])
42
- throw new Error(`vault '${name}' already exists`);
43
- return validateConfig({
44
- ...config,
45
- default: config.default ?? name,
46
- vaults: { ...vaults, [name]: entry },
47
- });
48
- };
49
- // Remove an entry; if it was the `default`, reassign to a remaining vault or drop the field.
50
- export const removeVault = (config, name) => {
51
- const vaults = config.vaults ?? {};
52
- if (!vaults[name])
53
- throw new Error(`vault '${name}' not found`);
54
- const rest = { ...vaults };
55
- delete rest[name];
56
- const next = { ...config, vaults: rest };
57
- if (config.default === name) {
58
- const fallback = Object.keys(rest)[0];
59
- if (fallback)
60
- next.default = fallback;
61
- else
62
- delete next.default;
63
- }
64
- return validateConfig(next);
65
- };
66
- // V8: when a removed vault sat directly under a discover root, append its name to that root's
67
- // `ignore` so the next scan does not re-add it. Returns the updated config plus the matched root
68
- // path (for the message), or null when the path is not directly under any discover root.
69
- export const appendDiscoverIgnore = (config, name, vaultPath) => {
70
- const parent = resolve(expandPath(vaultPath), '..');
71
- const roots = config.discover ?? [];
72
- const idx = roots.findIndex((r) => resolve(expandPath(r.path)) === parent);
73
- if (idx === -1)
74
- return null;
75
- const root = roots[idx];
76
- if (root.ignore?.includes(name))
77
- return { config, root: root.path };
78
- const next = [...roots];
79
- next[idx] = { ...root, ignore: [...(root.ignore ?? []), name] };
80
- return { config: validateConfig({ ...config, discover: next }), root: root.path };
81
- };
@@ -1,10 +0,0 @@
1
- import { type VaultsConfig } from '@agentage/memory-core';
2
- export declare const vaultsJsonPath: () => string;
3
- export interface LoadedVaults {
4
- config: VaultsConfig;
5
- source: string | null;
6
- }
7
- export declare const loadVaultsConfig: () => LoadedVaults;
8
- export declare const ensureVaultsConfig: () => void;
9
- export declare const saveVaultsConfig: (config: VaultsConfig) => string;
10
- export declare const mutateVaultsConfig: (fn: (config: VaultsConfig) => VaultsConfig | null | void) => Promise<VaultsConfig>;
@@ -1,66 +0,0 @@
1
- import { randomBytes } from 'node:crypto';
2
- import { chmodSync, existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
3
- import { join } from 'node:path';
4
- import { validateConfig } from '@agentage/memory-core';
5
- import { ensureConfigDir, getConfigDir } from '../fs/config.js';
6
- import { withFileLock } from '../fs/file-lock.js';
7
- import { VAULTS_SCHEMA_URL } from './vaults.schema.js';
8
- export const vaultsJsonPath = () => join(getConfigDir(), 'vaults.json');
9
- const EMPTY = { version: 1, vaults: {} };
10
- // JSON only (one format everywhere; the standalone @agentage/server-memory reads the same file
11
- // and parses only JSON). A missing file yields an empty config; a bad shape throws ConfigError.
12
- export const loadVaultsConfig = () => {
13
- const path = vaultsJsonPath();
14
- if (!existsSync(path))
15
- return { config: EMPTY, source: null };
16
- const raw = JSON.parse(readFileSync(path, 'utf-8'));
17
- return { config: validateConfig(raw), source: path };
18
- };
19
- // Scaffold an empty, $schema-linked vaults.json when none exists yet, so a fresh machine always
20
- // has an editable file with editor autocomplete. Leaves any existing file untouched.
21
- export const ensureVaultsConfig = () => {
22
- if (existsSync(vaultsJsonPath()))
23
- return;
24
- saveVaultsConfig(EMPTY);
25
- };
26
- // Atomic 0600 write. Re-injects the canonical $schema first and preserves every other field.
27
- export const saveVaultsConfig = (config) => {
28
- ensureConfigDir();
29
- const path = vaultsJsonPath();
30
- const out = { $schema: VAULTS_SCHEMA_URL, version: config.version };
31
- if (config.vaultsDir !== undefined)
32
- out.vaultsDir = config.vaultsDir;
33
- if (config.autodiscover !== undefined)
34
- out.autodiscover = config.autodiscover;
35
- if (config.autoInit !== undefined)
36
- out.autoInit = config.autoInit;
37
- if (config.default !== undefined)
38
- out.default = config.default;
39
- if (config.discover !== undefined)
40
- out.discover = config.discover;
41
- out.vaults = config.vaults ?? {};
42
- // A per-save tmp name: two concurrent savers can never clobber each other's tmp file.
43
- const tmp = `${path}.${process.pid}.${randomBytes(4).toString('hex')}.tmp`;
44
- writeFileSync(tmp, JSON.stringify(out, null, 2) + '\n', { encoding: 'utf-8', mode: 0o600 });
45
- renameSync(tmp, path);
46
- chmodSync(path, 0o600);
47
- return path;
48
- };
49
- // Cross-process-safe read-modify-write on vaults.json (issue #231). Under the advisory lock, re-read
50
- // FRESH from disk, apply `fn`, then atomic-save - so a concurrent `vault add` or the daemon's
51
- // discovery write-back can never clobber each other's update. `fn` may return a new config, mutate
52
- // the fresh one in place and return void, or return null to skip the write entirely (a no-op scan
53
- // must not re-touch the file, else the daemon's config watcher would loop). Returns the config on
54
- // disk after the call. A plain read stays lock-free via loadVaultsConfig.
55
- export const mutateVaultsConfig = async (fn) => {
56
- ensureConfigDir();
57
- return withFileLock(vaultsJsonPath(), () => {
58
- const fresh = loadVaultsConfig().config;
59
- const next = fn(fresh);
60
- if (next === null)
61
- return fresh; // explicit no-op: leave the file untouched
62
- const result = next ?? fresh;
63
- saveVaultsConfig(result);
64
- return result;
65
- });
66
- };
@@ -1,2 +0,0 @@
1
- export declare const VAULTS_SCHEMA_URL = "https://agentage.io/schemas/vaults.schema.json";
2
- export { isValidVaultName, VAULT_NAME_PATTERN } from '@agentage/memory-core';
@@ -1,10 +0,0 @@
1
- // The vaults.json format is owned by @agentage/memory-core (validateConfig / VaultsConfig).
2
- // This module keeps the one CLI-surface piece memory-core does not: the editor-tooling $schema
3
- // pointer written on create. Name validation re-exports memory-core's single source of truth.
4
- // The public JSON Schema location (served by the landing). Written into the file on create so
5
- // editors get autocomplete + inline validation; accepted-and-ignored on load. A fixed
6
- // production URL by design: editor tooling fetches it regardless of the CLI's target FQDN.
7
- export const VAULTS_SCHEMA_URL = 'https://agentage.io/schemas/vaults.schema.json';
8
- // ADR-013 AA-4: every name stays a valid cloud path segment (`<sub>/<name>.git`). memory-core's
9
- // schema accepts any string key, so the CLI guards the name at the add surface with its allowlist.
10
- export { isValidVaultName, VAULT_NAME_PATTERN } from '@agentage/memory-core';
@@ -1,4 +0,0 @@
1
- import { type VaultRegistry } from '@agentage/memory-core';
2
- import { type McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
- export declare const createLocalMemoryServer: (registry: VaultRegistry) => McpServer;
4
- export declare const loadLocalMemoryServer: () => Promise<McpServer>;
@@ -1,26 +0,0 @@
1
- import { createRegistry } from '@agentage/memory-core';
2
- import { createMemoryServer } from '@agentage/server-memory';
3
- import { loadVaultsConfig } from '../lib/vault/vaults.js';
4
- import { VERSION } from '../utils/version.js';
5
- // Restrict a registry to its offline (git working-copy) vaults, so the local MCP surface exposes
6
- // exactly what the DirectClient does - never a network-backed vault over the no-auth loopback.
7
- const localOnly = (registry) => {
8
- const local = registry.list().filter((h) => h.backend.capabilities().kind === 'local');
9
- const byId = new Map(local.map((h) => [h.id, h]));
10
- const def = registry.default();
11
- const defaultHandle = def && byId.has(def.id) ? def : undefined;
12
- return {
13
- list: () => local,
14
- get: (id) => byId.get(id),
15
- default: () => defaultHandle,
16
- surfaced: () => local,
17
- watch: () => () => { },
18
- close: async () => { },
19
- };
20
- };
21
- // Build the frozen 6-tool MCP server over one registry, reusing @agentage/server-memory's contract
22
- // layer verbatim (descriptions, schemas, annotations, per-connection instructions) - zero drift.
23
- export const createLocalMemoryServer = (registry) => createMemoryServer(localOnly(registry), { scope: 'local', version: VERSION });
24
- // The whole local stack: read this machine's vaults.json (the same config the daemon + CLI verbs
25
- // load) -> registry -> an MCP server over the surfaced local vaults.
26
- export const loadLocalMemoryServer = async () => createLocalMemoryServer(await createRegistry(loadVaultsConfig().config));
@@ -1,4 +0,0 @@
1
- export declare const CLI_SERVICE = "agentage-cli";
2
- export declare const DAEMON_SERVICE = "agentage-daemon";
3
- export declare const daemonServiceName: () => string;
4
- export declare const startObservability: (service: string) => Promise<void>;
@@ -1,35 +0,0 @@
1
- // Telemetry for the CLI and its daemon, through @agentage/observability. A real dependency here
2
- // (unlike @agentage/server-memory, which must stay npx-light): `agentage` is installed once,
3
- // globally. It is inert until OTEL_EXPORTER_OTLP_ENDPOINT names a collector, and every line it
4
- // writes goes to stderr - stdout stays the machine-readable channel (`--json`, the daemon's
5
- // JSON-RPC wire).
6
- // Captured before startObservability() can overwrite it, so the daemon we spawn can tell a name the
7
- // user chose from the default we injected.
8
- const userServiceName = process.env['OTEL_SERVICE_NAME'];
9
- export const CLI_SERVICE = 'agentage-cli';
10
- export const DAEMON_SERVICE = 'agentage-daemon';
11
- // What OTEL_SERVICE_NAME the spawned daemon should run under: the user's, else our own default.
12
- export const daemonServiceName = () => userServiceName || DAEMON_SERVICE;
13
- // Guarded even though the kit is a dependency: a half-installed global must degrade to a working
14
- // CLI, never to a stack trace on every command.
15
- export const startObservability = async (service) => {
16
- // The kit (1.0.0) announces an enabled tracer with one console.log, which would land in a
17
- // `--json` payload or on the daemon's JSON-RPC wire. Divert stdout to stderr while it boots; drop
18
- // this once the kit writes that line to stderr itself.
19
- const stdoutWrite = process.stdout.write;
20
- process.stdout.write = process.stderr.write.bind(process.stderr);
21
- try {
22
- if (!process.env['OTEL_SERVICE_NAME'])
23
- process.env['OTEL_SERVICE_NAME'] = service;
24
- await import('@agentage/observability/bootstrap');
25
- }
26
- catch (err) {
27
- if (process.env['AGENTAGE_DEBUG']) {
28
- const reason = err instanceof Error ? err.message : String(err);
29
- console.error(`[agentage] observability off: ${reason}`); // stderr: never corrupt stdout
30
- }
31
- }
32
- finally {
33
- process.stdout.write = stdoutWrite;
34
- }
35
- };
@@ -1,26 +0,0 @@
1
- import { type DiscoverCandidate, type VaultsConfig } from '@agentage/memory-core';
2
- export interface DiscoverStatus {
3
- roots: string[];
4
- }
5
- interface Watcher {
6
- close(): void;
7
- }
8
- export interface DiscoverWatcherDeps {
9
- getConfig?: () => VaultsConfig;
10
- mutate?: (fn: (config: VaultsConfig) => VaultsConfig | null | void) => Promise<VaultsConfig>;
11
- scan?: (config: VaultsConfig) => DiscoverCandidate[];
12
- provision?: (name: string) => Promise<unknown>;
13
- isDirectory?: (path: string) => boolean;
14
- watch?: (dir: string, onChange: () => void) => Watcher;
15
- debounceMs?: number;
16
- pollMs?: number;
17
- log?: (msg: string) => void;
18
- }
19
- export interface DiscoverWatcher {
20
- reschedule(): void;
21
- scanNow(): Promise<DiscoverCandidate[]>;
22
- status(): DiscoverStatus;
23
- stop(): void;
24
- }
25
- export declare const createDiscoverWatcher: (deps?: DiscoverWatcherDeps) => DiscoverWatcher;
26
- export {};
@@ -1,159 +0,0 @@
1
- import { statSync, watch as fsWatch } from 'node:fs';
2
- import { expandPath, scanDiscoverRoots, } from '@agentage/memory-core';
3
- import { defaultProvisionDeps, provisionAccountVault } from '../../lib/auth/provision.js';
4
- import { addVault } from '../../lib/vault/vault-registry.js';
5
- import { loadVaultsConfig, mutateVaultsConfig } from '../../lib/vault/vaults.js';
6
- const msgOf = (err) => (err instanceof Error ? err.message : String(err));
7
- // Floors: a 0/near-0 poll interval would busy-loop the daemon; a 0 debounce defeats coalescing.
8
- const POLL_FLOOR_MS = 1000;
9
- const DEBOUNCE_FLOOR_MS = 50;
10
- const isDir = (path) => {
11
- try {
12
- return statSync(path).isDirectory();
13
- }
14
- catch {
15
- return false;
16
- }
17
- };
18
- const defaultWatch = (dir, onChange) => {
19
- const w = fsWatch(dir, { persistent: false }, () => onChange());
20
- w.on('error', () => { }); // an unreliable fs: the poll fallback still covers the root
21
- return w;
22
- };
23
- export const createDiscoverWatcher = (deps = {}) => {
24
- const getConfig = deps.getConfig ?? (() => loadVaultsConfig().config);
25
- const mutate = deps.mutate ?? mutateVaultsConfig;
26
- const scan = deps.scan ?? scanDiscoverRoots;
27
- const provision = deps.provision ?? ((name) => provisionAccountVault(name, defaultProvisionDeps()));
28
- const isDirectory = deps.isDirectory ?? isDir;
29
- const watch = deps.watch ?? defaultWatch;
30
- const debounceMs = Math.max(DEBOUNCE_FLOOR_MS, deps.debounceMs ?? 500);
31
- const pollMs = Math.max(POLL_FLOOR_MS, deps.pollMs ?? 60_000);
32
- const log = deps.log ?? (() => { });
33
- const watchers = [];
34
- let debounceTimer;
35
- let pollTimer;
36
- let scanning = false;
37
- let queued = false;
38
- const roots = (config) => (config.discover ?? []).map((r) => expandPath(r.path));
39
- const runScan = async () => {
40
- let candidates;
41
- try {
42
- candidates = scan(getConfig());
43
- }
44
- catch (err) {
45
- log(`scan failed: ${msgOf(err)}`);
46
- return [];
47
- }
48
- if (candidates.length === 0)
49
- return [];
50
- // Fold the candidates onto the FRESH on-disk config inside the cross-process lock (issue #231):
51
- // re-read, register any still-absent-and-present folder, save - so a concurrent `vault add` (or
52
- // a second daemon) is never clobbered. Return null when nothing changed so the file is left
53
- // untouched (a redundant write would loop the daemon's own config watcher).
54
- let added = [];
55
- try {
56
- await mutate((fresh) => {
57
- added = [];
58
- let next = fresh;
59
- for (const c of candidates) {
60
- if (next.vaults?.[c.name])
61
- continue; // registered by a concurrent writer
62
- if (!c.entry.path || !isDirectory(c.entry.path))
63
- continue; // vanished before we wrote it
64
- try {
65
- next = addVault(next, c.name, c.entry);
66
- added.push(c);
67
- }
68
- catch (err) {
69
- log(`register '${c.name}' failed: ${msgOf(err)}`);
70
- }
71
- }
72
- return added.length > 0 ? next : null;
73
- });
74
- }
75
- catch (err) {
76
- log(`save failed: ${msgOf(err)}`);
77
- return [];
78
- }
79
- if (added.length === 0)
80
- return [];
81
- for (const c of added) {
82
- log(`discovered account vault '${c.name}' -> ${c.entry.path}`);
83
- void provision(c.name).catch(() => { }); // never fatal: the next scan re-provisions
84
- }
85
- return added;
86
- };
87
- // Serialise scans against themselves; a change during an in-flight scan queues one more pass.
88
- const scanNow = async () => {
89
- if (scanning) {
90
- queued = true;
91
- return [];
92
- }
93
- scanning = true;
94
- try {
95
- return await runScan();
96
- }
97
- finally {
98
- scanning = false;
99
- if (queued) {
100
- queued = false;
101
- void scanNow();
102
- }
103
- }
104
- };
105
- const debounced = () => {
106
- if (debounceTimer)
107
- clearTimeout(debounceTimer);
108
- debounceTimer = setTimeout(() => {
109
- debounceTimer = undefined;
110
- void scanNow();
111
- }, debounceMs);
112
- debounceTimer.unref?.();
113
- };
114
- const closeWatchers = () => {
115
- for (const w of watchers.splice(0)) {
116
- try {
117
- w.close();
118
- }
119
- catch {
120
- // already closed
121
- }
122
- }
123
- };
124
- const reschedule = () => {
125
- closeWatchers();
126
- const config = getConfig();
127
- for (const dir of roots(config).filter(isDirectory)) {
128
- try {
129
- watchers.push(watch(dir, debounced));
130
- }
131
- catch (err) {
132
- log(`watch ${dir} failed: ${msgOf(err)}`);
133
- }
134
- }
135
- // The poll fallback runs while any root is configured (even one not yet a directory), so a root
136
- // created after boot is still picked up where fs.watch is unreliable.
137
- if (roots(config).length > 0 && !pollTimer) {
138
- pollTimer = setInterval(() => void scanNow(), pollMs);
139
- pollTimer.unref?.();
140
- }
141
- else if (roots(config).length === 0 && pollTimer) {
142
- clearInterval(pollTimer);
143
- pollTimer = undefined;
144
- }
145
- void scanNow(); // initial scan on boot, and a re-scan whenever the config changes
146
- };
147
- const stop = () => {
148
- closeWatchers();
149
- if (debounceTimer) {
150
- clearTimeout(debounceTimer);
151
- debounceTimer = undefined;
152
- }
153
- if (pollTimer) {
154
- clearInterval(pollTimer);
155
- pollTimer = undefined;
156
- }
157
- };
158
- return { reschedule, scanNow, status: () => ({ roots: roots(getConfig()) }), stop };
159
- };
@@ -1 +0,0 @@
1
- export declare const conflictName: (path: string, exists: (candidate: string) => boolean) => string;
@@ -1,16 +0,0 @@
1
- // The name for the file that preserves the remote side of a conflict: `x.md` -> `x.conflict.md`.
2
- // An existing name is never clobbered - a numeric suffix is added instead (`x.conflict-1.md`).
3
- export const conflictName = (path, exists) => {
4
- const slash = path.lastIndexOf('/');
5
- const dot = path.lastIndexOf('.');
6
- const hasExt = dot > slash + 1; // a dot inside the basename, not a leading dot
7
- const stem = hasExt ? path.slice(0, dot) : path;
8
- const ext = hasExt ? path.slice(dot) : '';
9
- const base = `${stem}.conflict${ext}`;
10
- if (!exists(base))
11
- return base;
12
- let i = 1;
13
- while (exists(`${stem}.conflict-${i}${ext}`))
14
- i += 1;
15
- return `${stem}.conflict-${i}${ext}`;
16
- };
@@ -1,18 +0,0 @@
1
- import { type GitErrorKind, type SyncGit } from './git-exec.js';
2
- import { type SyncTarget } from './planner.js';
3
- export interface SyncResult {
4
- vault: string;
5
- remote: string;
6
- ok: boolean;
7
- committed: boolean;
8
- pushed: boolean;
9
- conflicts: string[];
10
- skipped?: 'lock' | 'busy' | 'invalid-remote';
11
- reason?: GitErrorKind;
12
- error?: string;
13
- }
14
- export interface CycleDeps {
15
- git?: SyncGit;
16
- now?: () => string;
17
- }
18
- export declare const runSyncCycle: (target: SyncTarget, deps?: CycleDeps) => Promise<SyncResult>;