@phnx-labs/agents-cli 1.20.51 → 1.20.52
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/CHANGELOG.md +16 -0
- package/dist/commands/browser.js +215 -7
- package/dist/commands/cloud.js +6 -0
- package/dist/commands/events.d.ts +1 -1
- package/dist/commands/events.js +2 -3
- package/dist/commands/exec.js +17 -2
- package/dist/commands/factory.js +8 -0
- package/dist/commands/feed.d.ts +9 -0
- package/dist/commands/feed.js +69 -0
- package/dist/commands/logs.d.ts +5 -1
- package/dist/commands/logs.js +248 -3
- package/dist/commands/mcp.js +7 -0
- package/dist/commands/secrets.d.ts +22 -0
- package/dist/commands/secrets.js +173 -42
- package/dist/commands/teams.js +4 -0
- package/dist/index.js +6 -2
- package/dist/lib/browser/login-detection.d.ts +94 -0
- package/dist/lib/browser/login-detection.js +274 -0
- package/dist/lib/browser/profiles.d.ts +17 -8
- package/dist/lib/browser/profiles.js +27 -8
- package/dist/lib/browser/secret-ref.d.ts +10 -0
- package/dist/lib/browser/secret-ref.js +14 -0
- package/dist/lib/browser/service.js +14 -12
- package/dist/lib/cloud/rush.d.ts +15 -0
- package/dist/lib/cloud/rush.js +7 -1
- package/dist/lib/crabbox/lease.d.ts +6 -0
- package/dist/lib/crabbox/lease.js +11 -9
- package/dist/lib/crabbox/runtimes.d.ts +38 -1
- package/dist/lib/crabbox/runtimes.js +98 -5
- package/dist/lib/daemon.d.ts +12 -9
- package/dist/lib/daemon.js +32 -17
- package/dist/lib/events.d.ts +31 -5
- package/dist/lib/events.js +288 -101
- package/dist/lib/exec.js +1 -0
- package/dist/lib/feed.d.ts +56 -0
- package/dist/lib/feed.js +251 -0
- package/dist/lib/hooks.js +7 -2
- package/dist/lib/hosts/passthrough.js +1 -0
- package/dist/lib/rotate.js +2 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/agent.d.ts +21 -0
- package/dist/lib/secrets/agent.js +63 -1
- package/dist/lib/secrets/bundles.d.ts +33 -1
- package/dist/lib/secrets/bundles.js +38 -8
- package/dist/lib/secrets/icloud-import.d.ts +70 -0
- package/dist/lib/secrets/icloud-import.js +173 -0
- package/dist/lib/secrets/index.d.ts +36 -0
- package/dist/lib/secrets/index.js +99 -9
- package/dist/lib/secrets/remote.js +1 -1
- package/dist/lib/secrets/sync.js +1 -1
- package/dist/lib/session/discover.js +1 -2
- package/dist/lib/session/state.js +13 -1
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +2 -0
- package/dist/lib/state.d.ts +2 -0
- package/dist/lib/state.js +25 -8
- package/dist/lib/teams/agents.js +6 -3
- package/dist/lib/types.d.ts +10 -0
- package/dist/lib/whats-new.d.ts +5 -3
- package/dist/lib/whats-new.js +25 -5
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* `--script-stdin` body so the token contents never touch argv.
|
|
8
8
|
*/
|
|
9
9
|
import { crabboxWarmup, crabboxWaitReady, crabboxRunScript, crabboxStop } from './cli.js';
|
|
10
|
-
import { buildCredentialScript } from './runtimes.js';
|
|
10
|
+
import { buildCredentialScript, CLAUDE_TOKEN_REMOTE } from './runtimes.js';
|
|
11
11
|
/** POSIX single-quote for safe embedding in the generated bootstrap script. */
|
|
12
12
|
function q(s) {
|
|
13
13
|
return "'" + s.replace(/'/g, "'\\''") + "'";
|
|
@@ -46,20 +46,22 @@ const ENSURE_AGENTS_CLI = [
|
|
|
46
46
|
* the credential files. Best-effort install steps never abort the run.
|
|
47
47
|
*/
|
|
48
48
|
export function buildBootstrapScript(opts) {
|
|
49
|
-
const credScript = buildCredentialScript(opts.runtimes, opts.detected
|
|
49
|
+
const credScript = buildCredentialScript(opts.runtimes, opts.detected, {
|
|
50
|
+
claudeCredentialsJson: opts.claudeCredentialsJson,
|
|
51
|
+
});
|
|
50
52
|
const runParts = ['agents', 'run', q(opts.agent), q(opts.prompt), '--quiet'];
|
|
51
53
|
if (opts.mode)
|
|
52
54
|
runParts.push('--mode', q(opts.mode));
|
|
53
55
|
if (opts.model)
|
|
54
56
|
runParts.push('--model', q(opts.model));
|
|
55
57
|
// Credential files to shred after the run (home-level paths written above).
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
// Runs regardless of --keep-box (it's in the box body, not teardown), so a kept
|
|
59
|
+
// box still loses the token after the run — minimizing the credential window.
|
|
60
|
+
const shredPaths = opts.runtimes.flatMap((id) => {
|
|
61
|
+
const paths = { claude: ['.claude.json', CLAUDE_TOKEN_REMOTE], codex: ['.codex/auth.json'], gemini: ['.gemini/google_accounts.json'], grok: ['.grok/auth.json'] }[id];
|
|
62
|
+
return paths ?? [];
|
|
63
|
+
});
|
|
64
|
+
const shred = shredPaths.map((p) => `rm -f "$HOME/${p}" 2>/dev/null || true`).join('\n');
|
|
63
65
|
const installRuntimes = opts.runtimes.map((id) => `agents add ${q(id)} >/dev/null 2>&1 || true`).join('\n');
|
|
64
66
|
return [
|
|
65
67
|
'set -uo pipefail',
|
|
@@ -47,11 +47,48 @@ export declare function pickRuntimes(detected: DetectedRuntime[], prompt?: (choi
|
|
|
47
47
|
checked: boolean;
|
|
48
48
|
disabled: boolean | string;
|
|
49
49
|
}[]) => Promise<AgentId[]>): Promise<AgentId[]>;
|
|
50
|
+
/**
|
|
51
|
+
* Where Claude Code reads its OAuth token on the box. `.claude.json` (the file
|
|
52
|
+
* LEASE_RUNTIMES copies) is config/account-metadata ONLY — the actual token
|
|
53
|
+
* lives here, so without it the box boots "Not logged in".
|
|
54
|
+
*/
|
|
55
|
+
export declare const CLAUDE_TOKEN_REMOTE = ".claude/.credentials.json";
|
|
56
|
+
/**
|
|
57
|
+
* The RAW wrapped Claude credential payload (`{"claudeAiOauth":{…}}`) to write to
|
|
58
|
+
* the box's `~/.claude/.credentials.json`, or null if no signed-in token is found.
|
|
59
|
+
*
|
|
60
|
+
* On macOS the token is in the login Keychain, read SILENTLY via
|
|
61
|
+
* `getKeychainToken` (the `/usr/bin/security … -w` path — Claude's item trusts it,
|
|
62
|
+
* no Touch ID). A default native install uses the bare `Claude Code-credentials`
|
|
63
|
+
* service; an agents-cli managed install (where `~/.claude` symlinks into a
|
|
64
|
+
* versioned home) uses a hash-suffixed service, so we try the bare service first,
|
|
65
|
+
* then enumerate installed version homes (preferring the account whose email
|
|
66
|
+
* matches `preferEmail`, so the token matches the `.claude.json` config we copy).
|
|
67
|
+
* Off macOS the local Claude CLI stores the token in `.credentials.json` already —
|
|
68
|
+
* reuse the rush.ts Linux branch verbatim.
|
|
69
|
+
*
|
|
70
|
+
* The reader/service/version helpers are injected so unit tests never touch the
|
|
71
|
+
* real Keychain.
|
|
72
|
+
*/
|
|
73
|
+
export declare function resolveClaudeCredentialsBlob(opts?: {
|
|
74
|
+
preferEmail?: string | null;
|
|
75
|
+
readItem?: (service: string) => string;
|
|
76
|
+
service?: (home?: string) => string;
|
|
77
|
+
listVersions?: () => string[];
|
|
78
|
+
versionHome?: (version: string) => string;
|
|
79
|
+
accountEmail?: (home: string) => Promise<string | null>;
|
|
80
|
+
}): Promise<string | null>;
|
|
50
81
|
/**
|
|
51
82
|
* Build a bash snippet that writes each picked runtime's token file to the box's
|
|
52
83
|
* home-level config path (0600), from the token contents read locally. Returns
|
|
53
84
|
* `''` when no runtimes were selected. The snippet is meant to be embedded in
|
|
54
85
|
* the `--script-stdin` body (never argv).
|
|
86
|
+
*
|
|
87
|
+
* `extras.claudeCredentialsJson` (the raw wrapped payload from
|
|
88
|
+
* `resolveClaudeCredentialsBlob`) is written to `~/.claude/.credentials.json` in
|
|
89
|
+
* ADDITION to claude's `.claude.json` config — without it the box is "Not logged in".
|
|
55
90
|
*/
|
|
56
|
-
export declare function buildCredentialScript(picked: AgentId[], detected: DetectedRuntime[]
|
|
91
|
+
export declare function buildCredentialScript(picked: AgentId[], detected: DetectedRuntime[], extras?: {
|
|
92
|
+
claudeCredentialsJson?: string | null;
|
|
93
|
+
}): string;
|
|
57
94
|
export {};
|
|
@@ -16,6 +16,10 @@ import * as os from 'os';
|
|
|
16
16
|
import * as path from 'path';
|
|
17
17
|
import * as fs from 'fs';
|
|
18
18
|
import { getAccountInfo } from '../agents.js';
|
|
19
|
+
import { getKeychainToken } from '../secrets/index.js';
|
|
20
|
+
import { getClaudeKeychainService } from '../usage.js';
|
|
21
|
+
import { listInstalledVersions, getVersionHomePath } from '../versions.js';
|
|
22
|
+
import { readClaudeCredentialsBlob } from '../cloud/rush.js';
|
|
19
23
|
export const LEASE_RUNTIMES = [
|
|
20
24
|
{ id: 'claude', label: 'Claude Code', localCandidates: ['.claude/.claude.json', '.claude.json'], remote: '.claude.json' },
|
|
21
25
|
{ id: 'codex', label: 'Codex CLI', localCandidates: ['.codex/auth.json'], remote: '.codex/auth.json' },
|
|
@@ -79,15 +83,102 @@ export async function pickRuntimes(detected, prompt) {
|
|
|
79
83
|
// token's contents effectively impossible, so the quoted heredoc can never be
|
|
80
84
|
// closed early by the credential body.
|
|
81
85
|
const CRED_EOF = 'AGENTS_LEASE_CRED_EOF_9f3c1a7b5e2d4068';
|
|
86
|
+
/**
|
|
87
|
+
* Where Claude Code reads its OAuth token on the box. `.claude.json` (the file
|
|
88
|
+
* LEASE_RUNTIMES copies) is config/account-metadata ONLY — the actual token
|
|
89
|
+
* lives here, so without it the box boots "Not logged in".
|
|
90
|
+
*/
|
|
91
|
+
export const CLAUDE_TOKEN_REMOTE = '.claude/.credentials.json';
|
|
92
|
+
/** True when `s` parses to a Claude keychain payload with an OAuth access token. */
|
|
93
|
+
function isClaudeCredentialsBlob(s) {
|
|
94
|
+
try {
|
|
95
|
+
const p = JSON.parse(s);
|
|
96
|
+
return typeof p?.claudeAiOauth?.accessToken === 'string';
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The RAW wrapped Claude credential payload (`{"claudeAiOauth":{…}}`) to write to
|
|
104
|
+
* the box's `~/.claude/.credentials.json`, or null if no signed-in token is found.
|
|
105
|
+
*
|
|
106
|
+
* On macOS the token is in the login Keychain, read SILENTLY via
|
|
107
|
+
* `getKeychainToken` (the `/usr/bin/security … -w` path — Claude's item trusts it,
|
|
108
|
+
* no Touch ID). A default native install uses the bare `Claude Code-credentials`
|
|
109
|
+
* service; an agents-cli managed install (where `~/.claude` symlinks into a
|
|
110
|
+
* versioned home) uses a hash-suffixed service, so we try the bare service first,
|
|
111
|
+
* then enumerate installed version homes (preferring the account whose email
|
|
112
|
+
* matches `preferEmail`, so the token matches the `.claude.json` config we copy).
|
|
113
|
+
* Off macOS the local Claude CLI stores the token in `.credentials.json` already —
|
|
114
|
+
* reuse the rush.ts Linux branch verbatim.
|
|
115
|
+
*
|
|
116
|
+
* The reader/service/version helpers are injected so unit tests never touch the
|
|
117
|
+
* real Keychain.
|
|
118
|
+
*/
|
|
119
|
+
export async function resolveClaudeCredentialsBlob(opts) {
|
|
120
|
+
const readItem = opts?.readItem ?? getKeychainToken;
|
|
121
|
+
const service = opts?.service ?? getClaudeKeychainService;
|
|
122
|
+
const listVersions = opts?.listVersions ?? (() => listInstalledVersions('claude'));
|
|
123
|
+
const versionHome = opts?.versionHome ?? ((v) => getVersionHomePath('claude', v));
|
|
124
|
+
const accountEmail = opts?.accountEmail ?? (async (home) => (await getAccountInfo('claude', home)).email);
|
|
125
|
+
const tryRead = (svc) => {
|
|
126
|
+
try {
|
|
127
|
+
const raw = readItem(svc).trim();
|
|
128
|
+
return isClaudeCredentialsBlob(raw) ? raw : null;
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
if (process.platform === 'darwin') {
|
|
135
|
+
// 1) Bare service — the default native (non-managed) install.
|
|
136
|
+
const bare = tryRead(service(undefined));
|
|
137
|
+
if (bare)
|
|
138
|
+
return bare;
|
|
139
|
+
// 2) Managed installs — hash-suffixed service keyed to each version home.
|
|
140
|
+
// Prefer the version whose account email matches the copied config.
|
|
141
|
+
let homes;
|
|
142
|
+
try {
|
|
143
|
+
homes = listVersions().map(versionHome);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
homes = [];
|
|
147
|
+
}
|
|
148
|
+
if (opts?.preferEmail) {
|
|
149
|
+
const scored = await Promise.all(homes.map(async (home) => ({ home, match: (await accountEmail(home).catch(() => null)) === opts.preferEmail })));
|
|
150
|
+
homes = [...scored.filter((s) => s.match), ...scored.filter((s) => !s.match)].map((s) => s.home);
|
|
151
|
+
}
|
|
152
|
+
for (const home of homes) {
|
|
153
|
+
const hit = tryRead(service(home));
|
|
154
|
+
if (hit)
|
|
155
|
+
return hit;
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
// Off darwin: the local Claude CLI already stores the wrapped blob on disk.
|
|
160
|
+
const home = process.env.AGENTS_REAL_HOME || os.homedir();
|
|
161
|
+
return readClaudeCredentialsBlob(home);
|
|
162
|
+
}
|
|
82
163
|
/**
|
|
83
164
|
* Build a bash snippet that writes each picked runtime's token file to the box's
|
|
84
165
|
* home-level config path (0600), from the token contents read locally. Returns
|
|
85
166
|
* `''` when no runtimes were selected. The snippet is meant to be embedded in
|
|
86
167
|
* the `--script-stdin` body (never argv).
|
|
168
|
+
*
|
|
169
|
+
* `extras.claudeCredentialsJson` (the raw wrapped payload from
|
|
170
|
+
* `resolveClaudeCredentialsBlob`) is written to `~/.claude/.credentials.json` in
|
|
171
|
+
* ADDITION to claude's `.claude.json` config — without it the box is "Not logged in".
|
|
87
172
|
*/
|
|
88
|
-
export function buildCredentialScript(picked, detected) {
|
|
173
|
+
export function buildCredentialScript(picked, detected, extras) {
|
|
89
174
|
const byId = new Map(detected.map((d) => [d.id, d]));
|
|
90
175
|
const parts = [];
|
|
176
|
+
const writeFile = (remote, contents) => {
|
|
177
|
+
const dir = path.posix.dirname(remote);
|
|
178
|
+
const mkdir = dir && dir !== '.' ? `mkdir -p "$HOME/${dir}"\n` : '';
|
|
179
|
+
return (`${mkdir}cat > "$HOME/${remote}" <<'${CRED_EOF}'\n${contents}${contents.endsWith('\n') ? '' : '\n'}${CRED_EOF}\n` +
|
|
180
|
+
`chmod 600 "$HOME/${remote}"`);
|
|
181
|
+
};
|
|
91
182
|
for (const id of picked) {
|
|
92
183
|
const d = byId.get(id);
|
|
93
184
|
const cred = LEASE_RUNTIMES.find((c) => c.id === id);
|
|
@@ -100,10 +191,12 @@ export function buildCredentialScript(picked, detected) {
|
|
|
100
191
|
catch {
|
|
101
192
|
continue;
|
|
102
193
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
194
|
+
parts.push(writeFile(cred.remote, contents));
|
|
195
|
+
// For claude the file above is config/state only; the OAuth token is a
|
|
196
|
+
// second artifact — without it the box comes up "Not logged in".
|
|
197
|
+
if (id === 'claude' && extras?.claudeCredentialsJson) {
|
|
198
|
+
parts.push(writeFile(CLAUDE_TOKEN_REMOTE, extras.claudeCredentialsJson));
|
|
199
|
+
}
|
|
107
200
|
}
|
|
108
201
|
return parts.join('\n');
|
|
109
202
|
}
|
package/dist/lib/daemon.d.ts
CHANGED
|
@@ -54,13 +54,15 @@ export declare function runDaemon(): Promise<void>;
|
|
|
54
54
|
/**
|
|
55
55
|
* Read the long-lived Claude OAuth token (from `claude setup-token`) that the
|
|
56
56
|
* user stored under the `claude` secrets bundle. Resolves the bundle the same
|
|
57
|
-
* way `agents run --secrets` does
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
57
|
+
* way `agents run --secrets` does. Interactive starts may prompt Keychain;
|
|
58
|
+
* headless auto-starts are broker-only and return null unless the user already
|
|
59
|
+
* unlocked the bundle in the secrets agent. That keeps a background browser
|
|
60
|
+
* command from hanging on an unseen biometric prompt. Never throws: an absent
|
|
61
|
+
* token leaves the daemon on its existing interactive OAuth session.
|
|
62
62
|
*/
|
|
63
|
-
export declare function readDaemonClaudeOAuthToken(
|
|
63
|
+
export declare function readDaemonClaudeOAuthToken(opts?: {
|
|
64
|
+
allowPrompt?: boolean;
|
|
65
|
+
}): string | null;
|
|
64
66
|
/**
|
|
65
67
|
* Write a launchd plist or systemd unit with owner-only permissions atomically.
|
|
66
68
|
*
|
|
@@ -72,9 +74,10 @@ export declare function readDaemonClaudeOAuthToken(): string | null;
|
|
|
72
74
|
*/
|
|
73
75
|
export declare function writeOwnerOnlyServiceManifest(filePath: string, content: string): void;
|
|
74
76
|
/** Generate a macOS launchd plist for auto-starting the daemon. */
|
|
75
|
-
export declare function generateLaunchdPlist(): string;
|
|
77
|
+
export declare function generateLaunchdPlist(oauthToken?: string | null): string;
|
|
76
78
|
/** Generate a Linux systemd user unit for auto-starting the daemon. */
|
|
77
|
-
export declare function generateSystemdUnit(): string;
|
|
79
|
+
export declare function generateSystemdUnit(oauthToken?: string | null): string;
|
|
80
|
+
export declare function getAgentsBinPath(): string;
|
|
78
81
|
/** Start the daemon via launchd, systemd, or as a detached process. */
|
|
79
82
|
export declare function startDaemon(): {
|
|
80
83
|
pid: number | null;
|
|
@@ -102,7 +105,7 @@ export declare function ensureDaemonStarted(): {
|
|
|
102
105
|
* the daemon then passes it to every routine run it spawns. An already-set
|
|
103
106
|
* value (e.g. inherited from launchd) is left untouched.
|
|
104
107
|
*/
|
|
105
|
-
export declare function buildDetachedDaemonEnv(baseEnv?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
108
|
+
export declare function buildDetachedDaemonEnv(baseEnv?: NodeJS.ProcessEnv, oauthToken?: string | null): NodeJS.ProcessEnv;
|
|
106
109
|
/**
|
|
107
110
|
* Resolve how to launch the daemon: `node <entry> daemon _run`, matching the
|
|
108
111
|
* exact form that works under a direct `daemon _run`.
|
package/dist/lib/daemon.js
CHANGED
|
@@ -558,15 +558,19 @@ export async function runDaemon() {
|
|
|
558
558
|
/**
|
|
559
559
|
* Read the long-lived Claude OAuth token (from `claude setup-token`) that the
|
|
560
560
|
* user stored under the `claude` secrets bundle. Resolves the bundle the same
|
|
561
|
-
* way `agents run --secrets` does
|
|
562
|
-
*
|
|
563
|
-
*
|
|
564
|
-
*
|
|
565
|
-
*
|
|
561
|
+
* way `agents run --secrets` does. Interactive starts may prompt Keychain;
|
|
562
|
+
* headless auto-starts are broker-only and return null unless the user already
|
|
563
|
+
* unlocked the bundle in the secrets agent. That keeps a background browser
|
|
564
|
+
* command from hanging on an unseen biometric prompt. Never throws: an absent
|
|
565
|
+
* token leaves the daemon on its existing interactive OAuth session.
|
|
566
566
|
*/
|
|
567
|
-
export function readDaemonClaudeOAuthToken() {
|
|
567
|
+
export function readDaemonClaudeOAuthToken(opts = {}) {
|
|
568
568
|
try {
|
|
569
|
-
const
|
|
569
|
+
const allowPrompt = opts.allowPrompt ?? Boolean(process.stdin.isTTY);
|
|
570
|
+
const { env } = readAndResolveBundleEnv(DAEMON_OAUTH_BUNDLE, {
|
|
571
|
+
caller: 'daemon',
|
|
572
|
+
agentOnly: !allowPrompt,
|
|
573
|
+
});
|
|
570
574
|
const token = (env[DAEMON_OAUTH_KEY] ?? '').trim();
|
|
571
575
|
return token.length > 0 ? token : null;
|
|
572
576
|
}
|
|
@@ -599,10 +603,9 @@ export function writeOwnerOnlyServiceManifest(filePath, content) {
|
|
|
599
603
|
fs.writeFileSync(filePath, content, { encoding: 'utf-8', mode: 0o600 });
|
|
600
604
|
}
|
|
601
605
|
/** Generate a macOS launchd plist for auto-starting the daemon. */
|
|
602
|
-
export function generateLaunchdPlist() {
|
|
606
|
+
export function generateLaunchdPlist(oauthToken = readDaemonClaudeOAuthToken()) {
|
|
603
607
|
const agentsBin = getAgentsBinPath();
|
|
604
608
|
const logPath = getLogPath();
|
|
605
|
-
const oauthToken = readDaemonClaudeOAuthToken();
|
|
606
609
|
const oauthEntry = oauthToken
|
|
607
610
|
? `
|
|
608
611
|
<key>${DAEMON_OAUTH_KEY}</key>
|
|
@@ -637,9 +640,8 @@ export function generateLaunchdPlist() {
|
|
|
637
640
|
</plist>`;
|
|
638
641
|
}
|
|
639
642
|
/** Generate a Linux systemd user unit for auto-starting the daemon. */
|
|
640
|
-
export function generateSystemdUnit() {
|
|
643
|
+
export function generateSystemdUnit(oauthToken = readDaemonClaudeOAuthToken()) {
|
|
641
644
|
const agentsBin = getAgentsBinPath();
|
|
642
|
-
const oauthToken = readDaemonClaudeOAuthToken();
|
|
643
645
|
const oauthLine = oauthToken
|
|
644
646
|
? `\nEnvironment=${DAEMON_OAUTH_KEY}=${oauthToken}`
|
|
645
647
|
: '';
|
|
@@ -657,15 +659,29 @@ Environment=PATH=/usr/local/bin:/usr/bin:/bin:${os.homedir()}/.nvm/versions/node
|
|
|
657
659
|
[Install]
|
|
658
660
|
WantedBy=default.target`;
|
|
659
661
|
}
|
|
660
|
-
function getAgentsBinPath() {
|
|
662
|
+
export function getAgentsBinPath() {
|
|
661
663
|
// Prefer the binary actively executing this code. `which agents` returns
|
|
662
664
|
// whatever happens to be first on PATH, which means a side-by-side dev
|
|
663
665
|
// build at ~/.local/bin would silently spawn the registry-installed
|
|
664
666
|
// daemon and run stale code. process.argv[1] is the absolute path of
|
|
665
667
|
// the JS entrypoint the user actually invoked.
|
|
666
668
|
const argv1 = process.argv[1];
|
|
667
|
-
if (argv1 && fs.existsSync(argv1))
|
|
669
|
+
if (argv1 && fs.existsSync(argv1)) {
|
|
670
|
+
// The package's browser/computer entrypoints are sibling shims without a
|
|
671
|
+
// `daemon` command. A daemon started as their IPC side effect must launch
|
|
672
|
+
// through the main agents entrypoint instead of replaying the shim path.
|
|
673
|
+
const entryName = path.basename(argv1);
|
|
674
|
+
const compiledShim = /^(browser|computer)\.(c|m)?js$/.test(entryName);
|
|
675
|
+
const installedShim = /^(browser|computer)$/.test(entryName);
|
|
676
|
+
if (compiledShim || installedShim) {
|
|
677
|
+
const agentsEntry = path.join(path.dirname(argv1), compiledShim ? 'index.js' : 'agents');
|
|
678
|
+
if (!fs.existsSync(agentsEntry)) {
|
|
679
|
+
throw new Error(`Cannot start agents daemon: main CLI entry not found at ${agentsEntry}`);
|
|
680
|
+
}
|
|
681
|
+
return agentsEntry;
|
|
682
|
+
}
|
|
668
683
|
return argv1;
|
|
684
|
+
}
|
|
669
685
|
try {
|
|
670
686
|
return execFileSync('which', ['agents'], { encoding: 'utf-8' }).trim();
|
|
671
687
|
}
|
|
@@ -799,12 +815,11 @@ function startDaemonLocked() {
|
|
|
799
815
|
* the daemon then passes it to every routine run it spawns. An already-set
|
|
800
816
|
* value (e.g. inherited from launchd) is left untouched.
|
|
801
817
|
*/
|
|
802
|
-
export function buildDetachedDaemonEnv(baseEnv = process.env) {
|
|
818
|
+
export function buildDetachedDaemonEnv(baseEnv = process.env, oauthToken = readDaemonClaudeOAuthToken()) {
|
|
803
819
|
const env = { ...baseEnv };
|
|
804
820
|
if (!env.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
env.CLAUDE_CODE_OAUTH_TOKEN = token;
|
|
821
|
+
if (oauthToken)
|
|
822
|
+
env.CLAUDE_CODE_OAUTH_TOKEN = oauthToken;
|
|
808
823
|
}
|
|
809
824
|
return env;
|
|
810
825
|
}
|
package/dist/lib/events.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Centralized event logging for agents-cli.
|
|
3
3
|
*
|
|
4
|
-
* Structured JSONL
|
|
5
|
-
*
|
|
4
|
+
* Structured JSONL audit log at ~/.agents/events.jsonl with lossless numbered
|
|
5
|
+
* gzip rotation at 10 MB and rich metadata for debugging/auditing.
|
|
6
6
|
*
|
|
7
7
|
* Features:
|
|
8
8
|
* - Rich metadata: hostname, platform, arch, pid, timezone
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
* - Permissions: logs dir is 0700, files are 0600 (owner-only)
|
|
12
12
|
* - Performance tracking: withTiming() wrapper for any async function
|
|
13
13
|
*/
|
|
14
|
-
export type
|
|
14
|
+
export type EventLevel = 'audit' | 'warn' | 'info' | 'debug';
|
|
15
|
+
export type EventType = 'agent.run.start' | 'agent.run.end' | 'agent.spawn.start' | 'agent.spawn.end' | 'version.install' | 'version.switch' | 'version.remove' | 'skill.install' | 'skill.remove' | 'browser.launch' | 'browser.close' | 'browser.navigate' | 'browser.screenshot' | 'secrets.get' | 'secrets.set' | 'secrets.delete' | 'secrets.rename' | 'cloud.dispatch' | 'cloud.complete' | 'cloud.cancel' | 'cloud.message' | 'teams.create' | 'teams.add' | 'teams.start' | 'teams.complete' | 'teams.disband' | 'hook.fire' | 'hook.complete' | 'hook.error' | 'mcp.add' | 'mcp.remove' | 'mcp.register' | 'resource.sync' | 'rotation.resolved' | 'command.start' | 'command.end' | 'perf.timing' | 'session.start' | 'session.end' | 'error' | 'warn' | 'info' | 'debug';
|
|
16
|
+
export declare function levelFor(event: EventType): EventLevel;
|
|
15
17
|
export interface EventMeta {
|
|
16
18
|
ts: string;
|
|
17
19
|
tz: string;
|
|
@@ -22,6 +24,9 @@ export interface EventMeta {
|
|
|
22
24
|
pid: number;
|
|
23
25
|
ppid: number;
|
|
24
26
|
event: EventType;
|
|
27
|
+
level: EventLevel;
|
|
28
|
+
caller: string;
|
|
29
|
+
session?: string;
|
|
25
30
|
osUser: string;
|
|
26
31
|
transport: 'local' | 'ssh';
|
|
27
32
|
sshClientIp?: string;
|
|
@@ -67,8 +72,14 @@ export declare function redactArgs(args: string[] | undefined): string[] | undef
|
|
|
67
72
|
* Returns undefined for null/undefined input.
|
|
68
73
|
*/
|
|
69
74
|
export declare function truncate(str: string | null | undefined, maxLength?: number): string | undefined;
|
|
75
|
+
export interface CallerIdentity {
|
|
76
|
+
kind: string;
|
|
77
|
+
session?: string;
|
|
78
|
+
}
|
|
79
|
+
/** Identify the environment that invoked agents-cli, not the source callsite. */
|
|
80
|
+
export declare function detectCaller(env?: NodeJS.ProcessEnv, stdoutIsTTY?: boolean): CallerIdentity;
|
|
70
81
|
/**
|
|
71
|
-
* Emit a structured event to the
|
|
82
|
+
* Emit a structured event to the append-only audit log.
|
|
72
83
|
*
|
|
73
84
|
* @param event - The event type
|
|
74
85
|
* @param payload - Event-specific data (agent, version, cwd, etc.)
|
|
@@ -142,7 +153,7 @@ export declare function emitCommand(command: string, args?: string[], payload?:
|
|
|
142
153
|
export declare function emitError(err: Error | string, payload?: EventPayload): void;
|
|
143
154
|
/**
|
|
144
155
|
* Remove log files older than the retention period.
|
|
145
|
-
*
|
|
156
|
+
* Removes numbered gzip archives whose filesystem mtime exceeds retention.
|
|
146
157
|
*
|
|
147
158
|
* @param retentionDays - Number of days to keep (default 7, from DEFAULT_RETENTION_DAYS)
|
|
148
159
|
* @returns Number of files removed
|
|
@@ -159,7 +170,9 @@ export declare function query(options: {
|
|
|
159
170
|
startDate?: Date;
|
|
160
171
|
endDate?: Date;
|
|
161
172
|
eventTypes?: EventType[];
|
|
173
|
+
level?: EventLevel;
|
|
162
174
|
agent?: string;
|
|
175
|
+
caller?: string;
|
|
163
176
|
command?: string;
|
|
164
177
|
module?: string;
|
|
165
178
|
limit?: number;
|
|
@@ -177,4 +190,17 @@ export declare function getTimingStats(label: string, options?: {
|
|
|
177
190
|
p50Ms: number;
|
|
178
191
|
p95Ms: number;
|
|
179
192
|
} | null;
|
|
193
|
+
export interface EventStats {
|
|
194
|
+
totalEvents: number;
|
|
195
|
+
byLevel: Record<string, number>;
|
|
196
|
+
byEvent: Record<string, number>;
|
|
197
|
+
byModule: Record<string, number>;
|
|
198
|
+
byUser: Record<string, number>;
|
|
199
|
+
fileCount: number;
|
|
200
|
+
totalBytes: number;
|
|
201
|
+
}
|
|
202
|
+
export declare function stats(options?: {
|
|
203
|
+
days?: number;
|
|
204
|
+
}): EventStats;
|
|
180
205
|
export declare function getLogsPath(): string;
|
|
206
|
+
export declare function _resetForTest(overrideEventsPath?: string): void;
|