@phnx-labs/agents-cli 1.20.86 → 1.20.87
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 +112 -0
- package/dist/bin/agents +0 -0
- package/dist/commands/sessions-browser.d.ts +18 -0
- package/dist/commands/sessions-browser.js +126 -24
- package/dist/commands/sessions-picker.d.ts +21 -8
- package/dist/commands/sessions-picker.js +83 -7
- package/dist/commands/sessions.d.ts +19 -0
- package/dist/commands/sessions.js +147 -18
- package/dist/commands/ssh.js +59 -1
- package/dist/commands/teams-picker.d.ts +2 -0
- package/dist/commands/teams-picker.js +2 -1
- package/dist/commands/teams.d.ts +4 -1
- package/dist/commands/teams.js +106 -70
- package/dist/commands/view.js +14 -3
- package/dist/index.js +31 -1
- package/dist/lib/claude-account-token.d.ts +12 -0
- package/dist/lib/claude-account-token.js +63 -0
- package/dist/lib/devices/registry.d.ts +25 -0
- package/dist/lib/devices/registry.js +82 -1
- package/dist/lib/events.d.ts +8 -1
- package/dist/lib/events.js +13 -0
- package/dist/lib/exec.js +10 -1
- package/dist/lib/format.d.ts +7 -0
- package/dist/lib/format.js +11 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -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/session/db.d.ts +21 -0
- package/dist/lib/session/db.js +45 -4
- package/dist/lib/session/remote-list.d.ts +7 -0
- package/dist/lib/session/remote-list.js +8 -4
- package/dist/lib/session/state.js +69 -2
- package/dist/lib/session/team-filter.d.ts +22 -3
- package/dist/lib/session/team-filter.js +106 -17
- package/dist/lib/session/types.d.ts +8 -0
- package/dist/lib/signin-badge.d.ts +17 -0
- package/dist/lib/signin-badge.js +19 -0
- package/dist/lib/state.d.ts +2 -0
- package/dist/lib/state.js +2 -0
- package/dist/lib/usage.js +1 -60
- package/package.json +1 -1
|
@@ -12,14 +12,33 @@ import type { SessionMeta, TeamOrigin } from './types.js';
|
|
|
12
12
|
*
|
|
13
13
|
* Primary signal is `session.isTeamOrigin`, captured at scan time from the
|
|
14
14
|
* JSONL `entrypoint` field ('sdk-cli' for team spawns, 'cli' for real CLI).
|
|
15
|
-
* When a team meta.json exists we additionally enrich with handle/mode
|
|
16
|
-
*
|
|
17
|
-
* was cleaned up still get recognized
|
|
15
|
+
* When a team meta.json exists we additionally enrich with handle/mode/team and
|
|
16
|
+
* the orchestrator that spawned it — but its absence no longer demotes a
|
|
17
|
+
* session: older team runs whose meta dir was cleaned up still get recognized
|
|
18
|
+
* via the entrypoint flag.
|
|
18
19
|
*
|
|
19
20
|
* Returns the TeamOrigin metadata when the session is team-origin, or null
|
|
20
21
|
* when it is a normal interactive session.
|
|
21
22
|
*/
|
|
22
23
|
export declare function classifyTeamSession(session: SessionMeta): TeamOrigin | null;
|
|
24
|
+
/**
|
|
25
|
+
* A team name / handle as it is safe to render: a real string, terminal escapes
|
|
26
|
+
* stripped, or undefined.
|
|
27
|
+
*
|
|
28
|
+
* These values reach the row and the preview pane, and for a peer's row they are
|
|
29
|
+
* whatever JSON that machine sent — `parseRemoteList` copies the object through
|
|
30
|
+
* without inspecting its fields, so neither the type nor the content is ours to
|
|
31
|
+
* assume. A non-string `spawnedTeam` used to throw out of `teamBadge`, which runs
|
|
32
|
+
* on every row, taking down the whole listing rather than one entry.
|
|
33
|
+
*/
|
|
34
|
+
export declare function safeTeamText(value: unknown): string | undefined;
|
|
35
|
+
/** Drop the cached teammate index — for tests that rewrite AGENTS_TEAMS_DIR. */
|
|
36
|
+
export declare function _resetTeamOriginIndex(): void;
|
|
37
|
+
/**
|
|
38
|
+
* Attach `teamOrigin` to every team-spawned row in `sessions`, from the shared
|
|
39
|
+
* teammate index rather than a stat per row.
|
|
40
|
+
*/
|
|
41
|
+
export declare function enrichTeamOrigins(sessions: SessionMeta[]): SessionMeta[];
|
|
23
42
|
/** Result of splitting sessions into visible and hidden (team-origin) groups. */
|
|
24
43
|
export interface FilterResult {
|
|
25
44
|
visible: SessionMeta[];
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import * as fs from 'fs';
|
|
10
10
|
import * as os from 'os';
|
|
11
11
|
import * as path from 'path';
|
|
12
|
+
import { sanitizeForTerminal } from './parse.js';
|
|
12
13
|
import { getTeamsAgentsDir } from '../state.js';
|
|
13
14
|
const HOME = os.homedir();
|
|
14
15
|
// Default path; tests can override via AGENTS_TEAMS_DIR env var.
|
|
@@ -21,33 +22,121 @@ function teamsAgentsDir() {
|
|
|
21
22
|
*
|
|
22
23
|
* Primary signal is `session.isTeamOrigin`, captured at scan time from the
|
|
23
24
|
* JSONL `entrypoint` field ('sdk-cli' for team spawns, 'cli' for real CLI).
|
|
24
|
-
* When a team meta.json exists we additionally enrich with handle/mode
|
|
25
|
-
*
|
|
26
|
-
* was cleaned up still get recognized
|
|
25
|
+
* When a team meta.json exists we additionally enrich with handle/mode/team and
|
|
26
|
+
* the orchestrator that spawned it — but its absence no longer demotes a
|
|
27
|
+
* session: older team runs whose meta dir was cleaned up still get recognized
|
|
28
|
+
* via the entrypoint flag.
|
|
27
29
|
*
|
|
28
30
|
* Returns the TeamOrigin metadata when the session is team-origin, or null
|
|
29
31
|
* when it is a normal interactive session.
|
|
30
32
|
*/
|
|
31
33
|
export function classifyTeamSession(session) {
|
|
32
|
-
const
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
const raw = fs.readFileSync(metaPath, 'utf-8');
|
|
36
|
-
const meta = JSON.parse(raw);
|
|
37
|
-
const name = typeof meta.name === 'string' && meta.name ? meta.name : undefined;
|
|
38
|
-
const handle = name ?? session.id.slice(0, 8);
|
|
39
|
-
const mode = typeof meta.mode === 'string' ? meta.mode : undefined;
|
|
40
|
-
return { handle, mode };
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
return { handle: session.id.slice(0, 8) };
|
|
44
|
-
}
|
|
45
|
-
}
|
|
34
|
+
const origin = teamOriginIndex().get(session.id);
|
|
35
|
+
if (origin)
|
|
36
|
+
return origin;
|
|
46
37
|
if (session.isTeamOrigin) {
|
|
47
38
|
return { handle: session.id.slice(0, 8) };
|
|
48
39
|
}
|
|
49
40
|
return null;
|
|
50
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Parse one teammate `meta.json` into a {@link TeamOrigin}. Degrades to a bare
|
|
44
|
+
* handle when the file is unreadable or malformed — a teammate whose record we
|
|
45
|
+
* can't parse is still a teammate.
|
|
46
|
+
*/
|
|
47
|
+
function readTeamOrigin(metaPath, agentId) {
|
|
48
|
+
try {
|
|
49
|
+
const meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
|
|
50
|
+
const str = (v) => (typeof v === 'string' && v ? v : undefined);
|
|
51
|
+
return {
|
|
52
|
+
origin: {
|
|
53
|
+
handle: str(meta.name) ?? agentId.slice(0, 8),
|
|
54
|
+
mode: str(meta.mode),
|
|
55
|
+
team: str(meta.task_name),
|
|
56
|
+
parentSessionId: str(meta.parent_session_id),
|
|
57
|
+
},
|
|
58
|
+
sessionId: str(meta.remote_session_id),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return { origin: { handle: agentId.slice(0, 8) } };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* A team name / handle as it is safe to render: a real string, terminal escapes
|
|
67
|
+
* stripped, or undefined.
|
|
68
|
+
*
|
|
69
|
+
* These values reach the row and the preview pane, and for a peer's row they are
|
|
70
|
+
* whatever JSON that machine sent — `parseRemoteList` copies the object through
|
|
71
|
+
* without inspecting its fields, so neither the type nor the content is ours to
|
|
72
|
+
* assume. A non-string `spawnedTeam` used to throw out of `teamBadge`, which runs
|
|
73
|
+
* on every row, taking down the whole listing rather than one entry.
|
|
74
|
+
*/
|
|
75
|
+
export function safeTeamText(value) {
|
|
76
|
+
if (typeof value !== 'string' || value === '')
|
|
77
|
+
return undefined;
|
|
78
|
+
return sanitizeForTerminal(value);
|
|
79
|
+
}
|
|
80
|
+
/** Cached teammate index; the directory is small (teams GC it after 7 days). */
|
|
81
|
+
let originIndexCache = null;
|
|
82
|
+
/** Drop the cached teammate index — for tests that rewrite AGENTS_TEAMS_DIR. */
|
|
83
|
+
export function _resetTeamOriginIndex() {
|
|
84
|
+
originIndexCache = null;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Every teammate record, keyed by the session ids it can be reached under.
|
|
88
|
+
*
|
|
89
|
+
* A teammate's directory name is its **agent id**, which is only sometimes the
|
|
90
|
+
* id of the transcript it produced: the harness mints its own session id, and
|
|
91
|
+
* the spawn records it separately as `remote_session_id`. Keying the lookup on
|
|
92
|
+
* the directory name alone therefore missed most teammates — on a live box, 14
|
|
93
|
+
* of 16 records were reachable only by `remote_session_id` — so a teammate row
|
|
94
|
+
* could not name its team however good the record was. Both keys are registered.
|
|
95
|
+
*
|
|
96
|
+
* Read once per process rather than per row: the old per-session `existsSync` +
|
|
97
|
+
* `readFileSync` cost a pair of syscalls for every row in the pool, which the
|
|
98
|
+
* interactive browser re-pays on each hotkey.
|
|
99
|
+
*/
|
|
100
|
+
function teamOriginIndex() {
|
|
101
|
+
if (originIndexCache)
|
|
102
|
+
return originIndexCache;
|
|
103
|
+
const dir = teamsAgentsDir();
|
|
104
|
+
const index = new Map();
|
|
105
|
+
let entries;
|
|
106
|
+
try {
|
|
107
|
+
entries = fs.readdirSync(dir);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
entries = [];
|
|
111
|
+
}
|
|
112
|
+
for (const agentId of entries) {
|
|
113
|
+
const metaPath = path.join(dir, agentId, 'meta.json');
|
|
114
|
+
if (!fs.existsSync(metaPath))
|
|
115
|
+
continue;
|
|
116
|
+
const { origin, sessionId } = readTeamOrigin(metaPath, agentId);
|
|
117
|
+
index.set(agentId, origin);
|
|
118
|
+
if (sessionId)
|
|
119
|
+
index.set(sessionId, origin);
|
|
120
|
+
}
|
|
121
|
+
originIndexCache = index;
|
|
122
|
+
return index;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Attach `teamOrigin` to every team-spawned row in `sessions`, from the shared
|
|
126
|
+
* teammate index rather than a stat per row.
|
|
127
|
+
*/
|
|
128
|
+
export function enrichTeamOrigins(sessions) {
|
|
129
|
+
const index = teamOriginIndex();
|
|
130
|
+
return sessions.map((session) => {
|
|
131
|
+
// A peer's rows are classified on the peer (its meta.json is on its disk, not
|
|
132
|
+
// ours) and ride across in the --json fan-out already populated. Re-deriving
|
|
133
|
+
// here would find no local record and downgrade a named teammate to a bare id.
|
|
134
|
+
if (session.teamOrigin)
|
|
135
|
+
return session;
|
|
136
|
+
const origin = index.get(session.id) ?? (session.isTeamOrigin ? { handle: session.id.slice(0, 8) } : null);
|
|
137
|
+
return origin ? { ...session, teamOrigin: origin } : session;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
51
140
|
/**
|
|
52
141
|
* Split `sessions` into visible and hidden (team-origin) groups.
|
|
53
142
|
* When `showTeams` is true every session is visible and `teamOrigin` is
|
|
@@ -88,6 +88,14 @@ export interface TeamOrigin {
|
|
|
88
88
|
handle?: string;
|
|
89
89
|
/** Agent mode: 'plan', 'edit', 'auto', or 'skip' ('full' accepted as legacy alias for 'skip'). */
|
|
90
90
|
mode?: string;
|
|
91
|
+
/** The team this teammate belongs to (`task_name` in its meta.json). */
|
|
92
|
+
team?: string;
|
|
93
|
+
/**
|
|
94
|
+
* The orchestrator session that spawned this teammate (`parent_session_id`).
|
|
95
|
+
* Absent for a team started outside any agent session, and for teammates whose
|
|
96
|
+
* meta dir has aged past the teams cleanup window.
|
|
97
|
+
*/
|
|
98
|
+
parentSessionId?: string;
|
|
91
99
|
}
|
|
92
100
|
/** Lightweight metadata for a discovered session, used in listings and pickers. */
|
|
93
101
|
export interface SessionMeta {
|
|
@@ -23,6 +23,23 @@ export declare function loginHint(agentId: AgentId): string;
|
|
|
23
23
|
* agents (the ones the feature is for), so the resume's `forceInteractive` flag is
|
|
24
24
|
* consulted directly.
|
|
25
25
|
*/
|
|
26
|
+
/**
|
|
27
|
+
* Is a Claude run on this box going to authenticate from an ambient
|
|
28
|
+
* `CLAUDE_CODE_OAUTH_TOKEN` rather than a per-version login?
|
|
29
|
+
*
|
|
30
|
+
* `AccountInfo.signedIn` is `!!email` read from a version home's `.claude.json`
|
|
31
|
+
* (agents.ts), so a version with no account written there reports signed-out —
|
|
32
|
+
* even though Claude Code authenticates fine from the env token and the run
|
|
33
|
+
* succeeds. Rendering that as "logged out" sends people hunting a login that is
|
|
34
|
+
* not missing (a real fleet incident: every version on a box read as locked out
|
|
35
|
+
* while all of them answered a live prompt).
|
|
36
|
+
*
|
|
37
|
+
* It is also the more useful warning: an ambient token is ONE account, so every
|
|
38
|
+
* version on the box resolves to it and balanced rotation across them rotates
|
|
39
|
+
* nothing. `env` is a parameter so the branch is testable without mutating the
|
|
40
|
+
* process environment.
|
|
41
|
+
*/
|
|
42
|
+
export declare function ambientClaudeToken(agentId: AgentId | string, env?: NodeJS.ProcessEnv): boolean;
|
|
26
43
|
export declare function shouldCheckLoginBeforeLaunch(o: {
|
|
27
44
|
interactive?: boolean;
|
|
28
45
|
forceInteractive?: boolean;
|
package/dist/lib/signin-badge.js
CHANGED
|
@@ -46,6 +46,25 @@ export function loginHint(agentId) {
|
|
|
46
46
|
* agents (the ones the feature is for), so the resume's `forceInteractive` flag is
|
|
47
47
|
* consulted directly.
|
|
48
48
|
*/
|
|
49
|
+
/**
|
|
50
|
+
* Is a Claude run on this box going to authenticate from an ambient
|
|
51
|
+
* `CLAUDE_CODE_OAUTH_TOKEN` rather than a per-version login?
|
|
52
|
+
*
|
|
53
|
+
* `AccountInfo.signedIn` is `!!email` read from a version home's `.claude.json`
|
|
54
|
+
* (agents.ts), so a version with no account written there reports signed-out —
|
|
55
|
+
* even though Claude Code authenticates fine from the env token and the run
|
|
56
|
+
* succeeds. Rendering that as "logged out" sends people hunting a login that is
|
|
57
|
+
* not missing (a real fleet incident: every version on a box read as locked out
|
|
58
|
+
* while all of them answered a live prompt).
|
|
59
|
+
*
|
|
60
|
+
* It is also the more useful warning: an ambient token is ONE account, so every
|
|
61
|
+
* version on the box resolves to it and balanced rotation across them rotates
|
|
62
|
+
* nothing. `env` is a parameter so the branch is testable without mutating the
|
|
63
|
+
* process environment.
|
|
64
|
+
*/
|
|
65
|
+
export function ambientClaudeToken(agentId, env = process.env) {
|
|
66
|
+
return agentId === 'claude' && (env.CLAUDE_CODE_OAUTH_TOKEN ?? '').trim().length > 0;
|
|
67
|
+
}
|
|
49
68
|
export function shouldCheckLoginBeforeLaunch(o) {
|
|
50
69
|
if (o.json || o.quiet || o.authCheckDisabled || o.rotated)
|
|
51
70
|
return false;
|
package/dist/lib/state.d.ts
CHANGED
|
@@ -181,6 +181,8 @@ export declare function getTeamsRegistryPath(): string;
|
|
|
181
181
|
export declare function getDevicesRegistryPath(): string;
|
|
182
182
|
/** Path to the device ignore-list — tailscale node names the user dismissed, so auto-discovery never re-suggests them. Per-machine, same dir as the registry. */
|
|
183
183
|
export declare function getDevicesIgnoredPath(): string;
|
|
184
|
+
/** Path to the device auto-launch preference file — which registered devices are eligible/preferred for Factory's auto-host selection. Per-machine, same dir as the registry. */
|
|
185
|
+
export declare function getDevicesAutoLaunchPath(): string;
|
|
184
186
|
/** Dir of "pending device" sentinels (~/.agents/.cache/state/devices-pending/) — one empty-ish file per newly-discovered, not-yet-approved tailnet node. Written by the daemon probe, read by the menu-bar helper (mirrors the attention sentinel dir). */
|
|
185
187
|
export declare function getDevicesPendingDir(): string;
|
|
186
188
|
/** Path to cloud dispatch cache (~/.agents/.cache/cloud/). */
|
package/dist/lib/state.js
CHANGED
|
@@ -402,6 +402,8 @@ function getDevicesDir() {
|
|
|
402
402
|
export function getDevicesRegistryPath() { return path.join(getDevicesDir(), 'registry.json'); }
|
|
403
403
|
/** Path to the device ignore-list — tailscale node names the user dismissed, so auto-discovery never re-suggests them. Per-machine, same dir as the registry. */
|
|
404
404
|
export function getDevicesIgnoredPath() { return path.join(getDevicesDir(), 'ignored.json'); }
|
|
405
|
+
/** Path to the device auto-launch preference file — which registered devices are eligible/preferred for Factory's auto-host selection. Per-machine, same dir as the registry. */
|
|
406
|
+
export function getDevicesAutoLaunchPath() { return path.join(getDevicesDir(), 'auto-launch.json'); }
|
|
405
407
|
/** Dir of "pending device" sentinels (~/.agents/.cache/state/devices-pending/) — one empty-ish file per newly-discovered, not-yet-approved tailnet node. Written by the daemon probe, read by the menu-bar helper (mirrors the attention sentinel dir). */
|
|
406
408
|
export function getDevicesPendingDir() { return path.join(RUNTIME_STATE_DIR, 'devices-pending'); }
|
|
407
409
|
/** Path to cloud dispatch cache (~/.agents/.cache/cloud/). */
|
package/dist/lib/usage.js
CHANGED
|
@@ -18,7 +18,7 @@ import chalk from 'chalk';
|
|
|
18
18
|
import { decodeJwtPayload, decryptDroidAuthPayload } from './agents.js';
|
|
19
19
|
import { walkForFiles } from './fs-walk.js';
|
|
20
20
|
import { getKeychainToken, setKeychainToken, deleteKeychainToken, isKeychainBackendOverridden, } from './secrets/index.js';
|
|
21
|
-
import {
|
|
21
|
+
import { resolveClaudeSetupToken } from './claude-account-token.js';
|
|
22
22
|
import { getCacheDir } from './state.js';
|
|
23
23
|
const execFileAsync = promisify(execFile);
|
|
24
24
|
const CLAUDE_USAGE_URL = 'https://api.anthropic.com/api/oauth/usage';
|
|
@@ -999,65 +999,6 @@ function deleteCachedClaudeOauth(service) {
|
|
|
999
999
|
/* best-effort — cache is an optimization */
|
|
1000
1000
|
}
|
|
1001
1001
|
}
|
|
1002
|
-
/**
|
|
1003
|
-
* Reserved FILE-BASED secrets bundle holding long-lived, non-rotating Claude
|
|
1004
|
-
* setup-tokens. Usage/probe reads authenticate with these instead of Claude
|
|
1005
|
-
* Code's ACL-bound login item, so they never pop Touch ID. Keyed strictly
|
|
1006
|
-
* per-account (`CLAUDE_CODE_OAUTH_TOKEN_<slug>` from the account email) — never a
|
|
1007
|
-
* bare key, so one account's token can't be misapplied to another in a
|
|
1008
|
-
* multi-account fleet.
|
|
1009
|
-
*/
|
|
1010
|
-
const AUTH_BUNDLE = 'auth';
|
|
1011
|
-
/** The per-account key an email maps to inside the `auth` bundle. */
|
|
1012
|
-
function claudeAccountTokenKey(account) {
|
|
1013
|
-
const slug = account
|
|
1014
|
-
.trim()
|
|
1015
|
-
.toUpperCase()
|
|
1016
|
-
.replace(/@/g, '_AT_')
|
|
1017
|
-
.replace(/\./g, '_DOT_')
|
|
1018
|
-
.replace(/[^A-Z0-9_]/g, '_');
|
|
1019
|
-
return `CLAUDE_CODE_OAUTH_TOKEN_${slug}`;
|
|
1020
|
-
}
|
|
1021
|
-
/** Signed-in account email for a version home, from `.claude.json` (no keychain). */
|
|
1022
|
-
function readClaudeAccountEmail(home) {
|
|
1023
|
-
const base = home ?? os.homedir();
|
|
1024
|
-
for (const p of [path.join(base, '.claude', '.claude.json'), path.join(base, '.claude.json')]) {
|
|
1025
|
-
try {
|
|
1026
|
-
const email = JSON.parse(fs.readFileSync(p, 'utf-8')).oauthAccount?.emailAddress;
|
|
1027
|
-
if (typeof email === 'string' && email.trim().length > 0)
|
|
1028
|
-
return email.trim();
|
|
1029
|
-
}
|
|
1030
|
-
catch {
|
|
1031
|
-
// Missing/unreadable at this location — try the next.
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
return null;
|
|
1035
|
-
}
|
|
1036
|
-
/**
|
|
1037
|
-
* Resolve a long-lived `claude setup-token` for the account signed into `home`
|
|
1038
|
-
* from the reserved FILE-BASED `auth` bundle. Returns the token or null. Reads
|
|
1039
|
-
* ONLY when the bundle is file-backed (never keychain), so this path itself can
|
|
1040
|
-
* never trigger a Touch ID prompt — that is the entire point: usage/probe reads
|
|
1041
|
-
* authenticate with the shareable setup-token, not the ACL-bound login item.
|
|
1042
|
-
*/
|
|
1043
|
-
function resolveClaudeSetupToken(home) {
|
|
1044
|
-
try {
|
|
1045
|
-
// Require a known account (email) up front: without it we cannot key a
|
|
1046
|
-
// per-account token, and we must NOT fall back to a bare shared key that
|
|
1047
|
-
// would misapply one account's setup-token to another.
|
|
1048
|
-
const email = readClaudeAccountEmail(home);
|
|
1049
|
-
if (!email)
|
|
1050
|
-
return null;
|
|
1051
|
-
if (!bundleExists(AUTH_BUNDLE) || bundleBackend(AUTH_BUNDLE) !== 'file')
|
|
1052
|
-
return null;
|
|
1053
|
-
const { env } = readAndResolveBundleEnv(AUTH_BUNDLE, { caller: 'usage', agentOnly: true });
|
|
1054
|
-
const v = (env[claudeAccountTokenKey(email)] ?? '').trim();
|
|
1055
|
-
return v.length > 0 ? v : null;
|
|
1056
|
-
}
|
|
1057
|
-
catch {
|
|
1058
|
-
return null;
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
1002
|
/**
|
|
1062
1003
|
* Load a version home's Claude OAuth credential from the two stores Claude Code
|
|
1063
1004
|
* uses, tried in order:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phnx-labs/agents-cli",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.87",
|
|
4
4
|
"description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|