@agentage/cli 0.32.0 → 0.34.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.
- package/README.md +8 -0
- package/dist/cli.js +8 -47
- package/dist/daemon/entry.d.ts +8 -0
- package/dist/daemon/entry.js +113 -0
- package/dist/daemon-entry.d.ts +1 -7
- package/dist/daemon-entry.js +9 -118
- package/dist/lib/daemon/daemon-client.js +3 -0
- package/dist/observability.d.ts +4 -0
- package/dist/observability.js +35 -0
- package/dist/program.d.ts +1 -0
- package/dist/program.js +50 -0
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -94,6 +94,14 @@ too. Account-channel provisioning still needs an interactive `agentage setup` se
|
|
|
94
94
|
- [`docs/reference.md`](docs/reference.md) - every command and flag, the full sync
|
|
95
95
|
options, MCP details, and environment variables.
|
|
96
96
|
|
|
97
|
+
## Telemetry (off by default)
|
|
98
|
+
|
|
99
|
+
Nothing leaves your machine unless you ask for it. The CLI and its local helper ship with
|
|
100
|
+
`@agentage/observability`, which stays inert until `OTEL_EXPORTER_OTLP_ENDPOINT` points at a
|
|
101
|
+
collector you choose - then the CLI reports as `agentage-cli` and the helper as `agentage-daemon`
|
|
102
|
+
(override with `OTEL_SERVICE_NAME`). Every line it writes goes to stderr, so `--json` output and the
|
|
103
|
+
helper's MCP wire stay clean.
|
|
104
|
+
|
|
97
105
|
## Development
|
|
98
106
|
|
|
99
107
|
```bash
|
package/dist/cli.js
CHANGED
|
@@ -1,54 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import { registerMemory } from './commands/memory/memory.js';
|
|
6
|
-
import { registerSetup } from './commands/auth/setup.js';
|
|
7
|
-
import { registerStatus } from './commands/status/status.js';
|
|
8
|
-
import { registerUpdate } from './commands/update/update.js';
|
|
9
|
-
import { registerVault } from './commands/vault/vault.js';
|
|
10
|
-
import { disableDaemon } from './lib/daemon/daemon-pref.js';
|
|
11
|
-
import { refreshUpdateCache, updateHint } from './lib/update/update-cache.js';
|
|
12
|
-
import { VERSION } from './utils/version.js';
|
|
2
|
+
// The bin is a loader, nothing else. @agentage/observability instruments node:http, fetch and MCP
|
|
3
|
+
// tool registration through module hooks, and a hook only sees a module imported after it is
|
|
4
|
+
// registered - so the bootstrap runs first and the program is imported dynamically behind it.
|
|
13
5
|
// Dependency-free guard so the message survives even if deps fail to parse on old Node.
|
|
14
6
|
const nodeMajor = Number(process.versions.node.split('.')[0]);
|
|
15
7
|
if (nodeMajor < 22) {
|
|
16
8
|
process.stderr.write(`agentage requires Node.js >= 22 (you have v${process.versions.node})\n`);
|
|
17
9
|
process.exit(1);
|
|
18
10
|
}
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
.option('--no-daemon', 'run memory verbs in-process instead of via the daemon');
|
|
25
|
-
program.addHelpText('after', '\nNew here? Run: agentage vault add <name> --local, or agentage setup to connect an account.');
|
|
26
|
-
program.hook('preAction', () => {
|
|
27
|
-
if (program.opts().daemon === false)
|
|
28
|
-
disableDaemon();
|
|
29
|
-
});
|
|
30
|
-
// After any command (except the update-aware ones, which report richer state themselves), print a
|
|
31
|
-
// one-line hint from the cache, then kick a fire-and-forget refresh. Never awaited, never throws -
|
|
32
|
-
// the command output is already done and the cache-only read keeps this instant. The hint is
|
|
33
|
-
// suppressed under --json so it never corrupts machine-readable output.
|
|
34
|
-
program.hook('postAction', (_thisCommand, actionCommand) => {
|
|
35
|
-
const name = actionCommand.name();
|
|
36
|
-
if (name === 'status' || name === 'update')
|
|
37
|
-
return;
|
|
38
|
-
if (!actionCommand.opts()['json']) {
|
|
39
|
-
const hint = updateHint();
|
|
40
|
-
if (hint)
|
|
41
|
-
console.error(chalk.dim(hint)); // stderr: never corrupt piped stdout
|
|
42
|
-
}
|
|
43
|
-
void refreshUpdateCache();
|
|
44
|
-
});
|
|
45
|
-
registerSetup(program);
|
|
46
|
-
registerStatus(program);
|
|
47
|
-
registerVault(program);
|
|
48
|
-
registerMemory(program);
|
|
49
|
-
registerDaemon(program);
|
|
50
|
-
registerUpdate(program);
|
|
51
|
-
program.parseAsync().catch((err) => {
|
|
52
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
53
|
-
process.exit(1);
|
|
54
|
-
});
|
|
11
|
+
const { CLI_SERVICE, startObservability } = await import('./observability.js');
|
|
12
|
+
await startObservability(CLI_SERVICE);
|
|
13
|
+
const { run } = await import('./program.js');
|
|
14
|
+
await run();
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const isEaddrinuse: (err: unknown) => boolean;
|
|
2
|
+
export declare const createStateCleanup: (remove: () => void) => {
|
|
3
|
+
markOwned: () => void;
|
|
4
|
+
cleanup: () => void;
|
|
5
|
+
};
|
|
6
|
+
export declare const safeReschedule: (steps: Array<() => void>, onError: (msg: string) => void) => void;
|
|
7
|
+
export declare const mcpEnabled: (env?: NodeJS.ProcessEnv) => boolean;
|
|
8
|
+
export declare const boot: () => void;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { unwatchFile, watchFile } from 'node:fs';
|
|
2
|
+
import { createClientProvider } from './client-provider.js';
|
|
3
|
+
import { EADDRINUSE_EXIT_CODE, generateDaemonToken, removePidFile, removePortFile, removeTokenFile, resolvePort, writePidFile, writePortFile, writeTokenFile, } from './lifecycle.js';
|
|
4
|
+
import { createDaemonServer } from './server.js';
|
|
5
|
+
import { loadLocalMemoryServer } from '../mcp/local-server.js';
|
|
6
|
+
import { vaultsJsonPath } from '../lib/vault/vaults.js';
|
|
7
|
+
import { createDiscoverWatcher } from '../sync/discover/watcher.js';
|
|
8
|
+
import { createSyncManager } from '../sync/git/manager.js';
|
|
9
|
+
import { VERSION } from '../utils/version.js';
|
|
10
|
+
export const isEaddrinuse = (err) => typeof err === 'object' && err !== null && err.code === 'EADDRINUSE';
|
|
11
|
+
// Gate state-file cleanup on ownership: a loser of an autostart race must never wipe the winner's
|
|
12
|
+
// pid/port/token files. Only the process that actually wrote them may remove them.
|
|
13
|
+
export const createStateCleanup = (remove) => {
|
|
14
|
+
let owned = false;
|
|
15
|
+
return {
|
|
16
|
+
markOwned: () => {
|
|
17
|
+
owned = true;
|
|
18
|
+
},
|
|
19
|
+
cleanup: () => {
|
|
20
|
+
if (owned)
|
|
21
|
+
remove();
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
// Run each reschedule independently: a transiently-invalid config edit must not crash the daemon or
|
|
26
|
+
// stop the others rescheduling; the throwing one keeps its last-good schedule.
|
|
27
|
+
export const safeReschedule = (steps, onError) => {
|
|
28
|
+
for (const step of steps) {
|
|
29
|
+
try {
|
|
30
|
+
step();
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
onError(err instanceof Error ? err.message : String(err));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
// MCP is served at /mcp by default; AGENTAGE_DAEMON_NO_MCP=1 leaves buildMcpServer unset so the
|
|
38
|
+
// endpoint 404s (the `daemon start --no-mcp` flag sets this on the spawned process).
|
|
39
|
+
export const mcpEnabled = (env = process.env) => env['AGENTAGE_DAEMON_NO_MCP'] !== '1';
|
|
40
|
+
// Unset/empty/invalid -> undefined (the watcher's defaults apply); the watcher floors low values.
|
|
41
|
+
const envInt = (name) => {
|
|
42
|
+
const raw = process.env[name];
|
|
43
|
+
if (!raw)
|
|
44
|
+
return undefined;
|
|
45
|
+
const v = Number(raw);
|
|
46
|
+
return Number.isFinite(v) && v >= 0 ? v : undefined;
|
|
47
|
+
};
|
|
48
|
+
const state = createStateCleanup(() => {
|
|
49
|
+
removePidFile();
|
|
50
|
+
removePortFile();
|
|
51
|
+
removeTokenFile();
|
|
52
|
+
});
|
|
53
|
+
// The detached, long-lived engine host: one loopback HTTP server that owns a single in-process
|
|
54
|
+
// engine and serialises every vault mutation, avoiding concurrent git index.lock collisions. It
|
|
55
|
+
// runs the git sync loop and reschedules on config change.
|
|
56
|
+
const main = async () => {
|
|
57
|
+
const port = resolvePort();
|
|
58
|
+
const authToken = generateDaemonToken();
|
|
59
|
+
const git = createSyncManager();
|
|
60
|
+
const discover = createDiscoverWatcher({
|
|
61
|
+
log: (msg) => console.log(`[discover] ${msg}`),
|
|
62
|
+
debounceMs: envInt('AGENTAGE_DISCOVER_DEBOUNCE_MS'),
|
|
63
|
+
pollMs: envInt('AGENTAGE_DISCOVER_POLL_MS'),
|
|
64
|
+
});
|
|
65
|
+
const server = createDaemonServer({
|
|
66
|
+
getClient: createClientProvider(),
|
|
67
|
+
buildMcpServer: mcpEnabled() ? loadLocalMemoryServer : undefined,
|
|
68
|
+
sync: {
|
|
69
|
+
status: () => ({ ...git.status(), discover: discover.status() }),
|
|
70
|
+
runNow: (vault) => git.runNow(vault),
|
|
71
|
+
},
|
|
72
|
+
authToken,
|
|
73
|
+
version: VERSION,
|
|
74
|
+
});
|
|
75
|
+
await server.start(port);
|
|
76
|
+
writePidFile(process.pid);
|
|
77
|
+
writePortFile(port);
|
|
78
|
+
writeTokenFile(authToken);
|
|
79
|
+
state.markOwned();
|
|
80
|
+
const reschedule = () => safeReschedule([() => git.reschedule(), () => discover.reschedule()], (msg) => console.error(`[daemon] reschedule failed: ${msg}`));
|
|
81
|
+
reschedule();
|
|
82
|
+
const configPath = vaultsJsonPath();
|
|
83
|
+
watchFile(configPath, { interval: 2000 }, reschedule);
|
|
84
|
+
const shutdown = () => {
|
|
85
|
+
unwatchFile(configPath);
|
|
86
|
+
git.stop();
|
|
87
|
+
discover.stop();
|
|
88
|
+
server.stop().finally(() => {
|
|
89
|
+
state.cleanup();
|
|
90
|
+
process.exit(0);
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
process.on('SIGTERM', shutdown);
|
|
94
|
+
process.on('SIGINT', shutdown);
|
|
95
|
+
};
|
|
96
|
+
// Boot the real daemon. Invoked by the bin (src/daemon-entry.ts) once the observability bootstrap
|
|
97
|
+
// has run; importing this module for tests must never start a server.
|
|
98
|
+
export const boot = () => {
|
|
99
|
+
process.on('uncaughtException', (err) => {
|
|
100
|
+
console.error(`[daemon] uncaught: ${err instanceof Error ? err.message : String(err)}`);
|
|
101
|
+
state.cleanup();
|
|
102
|
+
process.exit(1);
|
|
103
|
+
});
|
|
104
|
+
main().catch((err) => {
|
|
105
|
+
if (isEaddrinuse(err)) {
|
|
106
|
+
// Another daemon owns the port + our state files: exit distinctly, touch nothing.
|
|
107
|
+
process.exit(EADDRINUSE_EXIT_CODE);
|
|
108
|
+
}
|
|
109
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
110
|
+
state.cleanup();
|
|
111
|
+
process.exit(1);
|
|
112
|
+
});
|
|
113
|
+
};
|
package/dist/daemon-entry.d.ts
CHANGED
|
@@ -1,7 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
export declare const createStateCleanup: (remove: () => void) => {
|
|
3
|
-
markOwned: () => void;
|
|
4
|
-
cleanup: () => void;
|
|
5
|
-
};
|
|
6
|
-
export declare const safeReschedule: (steps: Array<() => void>, onError: (msg: string) => void) => void;
|
|
7
|
-
export declare const mcpEnabled: (env?: NodeJS.ProcessEnv) => boolean;
|
|
1
|
+
export {};
|
package/dist/daemon-entry.js
CHANGED
|
@@ -1,118 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import { VERSION } from './utils/version.js';
|
|
11
|
-
export const isEaddrinuse = (err) => typeof err === 'object' && err !== null && err.code === 'EADDRINUSE';
|
|
12
|
-
// Gate state-file cleanup on ownership: a loser of an autostart race must never wipe the winner's
|
|
13
|
-
// pid/port/token files. Only the process that actually wrote them may remove them.
|
|
14
|
-
export const createStateCleanup = (remove) => {
|
|
15
|
-
let owned = false;
|
|
16
|
-
return {
|
|
17
|
-
markOwned: () => {
|
|
18
|
-
owned = true;
|
|
19
|
-
},
|
|
20
|
-
cleanup: () => {
|
|
21
|
-
if (owned)
|
|
22
|
-
remove();
|
|
23
|
-
},
|
|
24
|
-
};
|
|
25
|
-
};
|
|
26
|
-
// Run each reschedule independently: a transiently-invalid config edit must not crash the daemon or
|
|
27
|
-
// stop the others rescheduling; the throwing one keeps its last-good schedule.
|
|
28
|
-
export const safeReschedule = (steps, onError) => {
|
|
29
|
-
for (const step of steps) {
|
|
30
|
-
try {
|
|
31
|
-
step();
|
|
32
|
-
}
|
|
33
|
-
catch (err) {
|
|
34
|
-
onError(err instanceof Error ? err.message : String(err));
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
// MCP is served at /mcp by default; AGENTAGE_DAEMON_NO_MCP=1 leaves buildMcpServer unset so the
|
|
39
|
-
// endpoint 404s (the `daemon start --no-mcp` flag sets this on the spawned process).
|
|
40
|
-
export const mcpEnabled = (env = process.env) => env['AGENTAGE_DAEMON_NO_MCP'] !== '1';
|
|
41
|
-
// Unset/empty/invalid -> undefined (the watcher's defaults apply); the watcher floors low values.
|
|
42
|
-
const envInt = (name) => {
|
|
43
|
-
const raw = process.env[name];
|
|
44
|
-
if (!raw)
|
|
45
|
-
return undefined;
|
|
46
|
-
const v = Number(raw);
|
|
47
|
-
return Number.isFinite(v) && v >= 0 ? v : undefined;
|
|
48
|
-
};
|
|
49
|
-
const state = createStateCleanup(() => {
|
|
50
|
-
removePidFile();
|
|
51
|
-
removePortFile();
|
|
52
|
-
removeTokenFile();
|
|
53
|
-
});
|
|
54
|
-
// The detached, long-lived engine host: one loopback HTTP server that owns a single in-process
|
|
55
|
-
// engine and serialises every vault mutation, avoiding concurrent git index.lock collisions. It
|
|
56
|
-
// runs the git sync loop and reschedules on config change.
|
|
57
|
-
const main = async () => {
|
|
58
|
-
const port = resolvePort();
|
|
59
|
-
const authToken = generateDaemonToken();
|
|
60
|
-
const git = createSyncManager();
|
|
61
|
-
const discover = createDiscoverWatcher({
|
|
62
|
-
log: (msg) => console.log(`[discover] ${msg}`),
|
|
63
|
-
debounceMs: envInt('AGENTAGE_DISCOVER_DEBOUNCE_MS'),
|
|
64
|
-
pollMs: envInt('AGENTAGE_DISCOVER_POLL_MS'),
|
|
65
|
-
});
|
|
66
|
-
const server = createDaemonServer({
|
|
67
|
-
getClient: createClientProvider(),
|
|
68
|
-
buildMcpServer: mcpEnabled() ? loadLocalMemoryServer : undefined,
|
|
69
|
-
sync: {
|
|
70
|
-
status: () => ({ ...git.status(), discover: discover.status() }),
|
|
71
|
-
runNow: (vault) => git.runNow(vault),
|
|
72
|
-
},
|
|
73
|
-
authToken,
|
|
74
|
-
version: VERSION,
|
|
75
|
-
});
|
|
76
|
-
await server.start(port);
|
|
77
|
-
writePidFile(process.pid);
|
|
78
|
-
writePortFile(port);
|
|
79
|
-
writeTokenFile(authToken);
|
|
80
|
-
state.markOwned();
|
|
81
|
-
const reschedule = () => safeReschedule([() => git.reschedule(), () => discover.reschedule()], (msg) => console.error(`[daemon] reschedule failed: ${msg}`));
|
|
82
|
-
reschedule();
|
|
83
|
-
const configPath = vaultsJsonPath();
|
|
84
|
-
watchFile(configPath, { interval: 2000 }, reschedule);
|
|
85
|
-
const shutdown = () => {
|
|
86
|
-
unwatchFile(configPath);
|
|
87
|
-
git.stop();
|
|
88
|
-
discover.stop();
|
|
89
|
-
server.stop().finally(() => {
|
|
90
|
-
state.cleanup();
|
|
91
|
-
process.exit(0);
|
|
92
|
-
});
|
|
93
|
-
};
|
|
94
|
-
process.on('SIGTERM', shutdown);
|
|
95
|
-
process.on('SIGINT', shutdown);
|
|
96
|
-
};
|
|
97
|
-
// Only self-invoke when run directly (spawnDaemon's `node daemon-entry.js`); importing for tests
|
|
98
|
-
// must not boot a daemon.
|
|
99
|
-
const invokedDirectly = () => {
|
|
100
|
-
const entry = process.argv[1];
|
|
101
|
-
return !!entry && fileURLToPath(import.meta.url) === entry;
|
|
102
|
-
};
|
|
103
|
-
if (invokedDirectly()) {
|
|
104
|
-
process.on('uncaughtException', (err) => {
|
|
105
|
-
console.error(`[daemon] uncaught: ${err instanceof Error ? err.message : String(err)}`);
|
|
106
|
-
state.cleanup();
|
|
107
|
-
process.exit(1);
|
|
108
|
-
});
|
|
109
|
-
main().catch((err) => {
|
|
110
|
-
if (isEaddrinuse(err)) {
|
|
111
|
-
// Another daemon owns the port + our state files: exit distinctly, touch nothing.
|
|
112
|
-
process.exit(EADDRINUSE_EXIT_CODE);
|
|
113
|
-
}
|
|
114
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
115
|
-
state.cleanup();
|
|
116
|
-
process.exit(1);
|
|
117
|
-
});
|
|
118
|
-
}
|
|
1
|
+
// The daemon's bin, a loader and nothing else: @agentage/observability instruments node:http and
|
|
2
|
+
// MCP tool registration through module hooks that only see modules imported after they register, so
|
|
3
|
+
// the bootstrap runs before the daemon itself is loaded. The path stays src/daemon-entry.ts because
|
|
4
|
+
// spawnDaemon resolves dist/daemon-entry.js.
|
|
5
|
+
const { DAEMON_SERVICE, startObservability } = await import('./observability.js');
|
|
6
|
+
await startObservability(DAEMON_SERVICE);
|
|
7
|
+
const { boot } = await import('./daemon/entry.js');
|
|
8
|
+
boot();
|
|
9
|
+
export {};
|
|
@@ -2,6 +2,7 @@ import { spawn as spawnChild } from 'node:child_process';
|
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { EADDRINUSE_EXIT_CODE, readDaemonToken, resolvePort } from '../../daemon/lifecycle.js';
|
|
5
|
+
import { daemonServiceName } from '../../observability.js';
|
|
5
6
|
import { VERSION } from '../../utils/version.js';
|
|
6
7
|
const base = (port) => `http://127.0.0.1:${port}`;
|
|
7
8
|
// The 0600 token file is the daemon's auth: read it fresh per call so a restart's new token is
|
|
@@ -97,6 +98,8 @@ export const spawnDaemon = async (port, opts = {}) => {
|
|
|
97
98
|
env: {
|
|
98
99
|
...process.env,
|
|
99
100
|
AGENTAGE_DAEMON_PORT: String(port),
|
|
101
|
+
// Its own service in telemetry: never inherit the name we defaulted into the CLI's env.
|
|
102
|
+
OTEL_SERVICE_NAME: daemonServiceName(),
|
|
100
103
|
...(opts.noMcp ? { AGENTAGE_DAEMON_NO_MCP: '1' } : {}),
|
|
101
104
|
},
|
|
102
105
|
});
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const run: () => Promise<void>;
|
package/dist/program.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
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';
|
|
10
|
+
import { refreshUpdateCache, updateHint } from './lib/update/update-cache.js';
|
|
11
|
+
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')
|
|
30
|
+
return;
|
|
31
|
+
if (!actionCommand.opts()['json']) {
|
|
32
|
+
const hint = updateHint();
|
|
33
|
+
if (hint)
|
|
34
|
+
console.error(chalk.dim(hint)); // stderr: never corrupt piped stdout
|
|
35
|
+
}
|
|
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
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentage/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.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",
|
|
@@ -41,14 +41,16 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@agentage/memory-core": "^0.5.0",
|
|
44
|
-
"@agentage/
|
|
44
|
+
"@agentage/observability": "^1.3.0",
|
|
45
|
+
"@agentage/server-memory": "^0.10.0",
|
|
45
46
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
47
|
+
"@opentelemetry/api": "^1.9.0",
|
|
46
48
|
"chalk": "^6.0.0",
|
|
47
49
|
"commander": "^15.0.0",
|
|
48
50
|
"open": "^11.0.0"
|
|
49
51
|
},
|
|
50
52
|
"devDependencies": {
|
|
51
|
-
"@anthropic-ai/sdk": "0.
|
|
53
|
+
"@anthropic-ai/sdk": "0.129.0",
|
|
52
54
|
"@playwright/test": "latest",
|
|
53
55
|
"@types/node": "latest",
|
|
54
56
|
"@typescript-eslint/eslint-plugin": "latest",
|