@phnx-labs/agents-cli 1.20.26 → 1.20.28
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 +29 -0
- package/dist/commands/doctor.d.ts +5 -2
- package/dist/commands/doctor.js +182 -30
- package/dist/commands/exec.d.ts +1 -1
- package/dist/commands/exec.js +177 -6
- package/dist/commands/hosts.d.ts +11 -0
- package/dist/commands/hosts.js +229 -0
- package/dist/commands/repo.d.ts +29 -0
- package/dist/commands/repo.js +174 -38
- package/dist/commands/secrets.d.ts +2 -7
- package/dist/commands/secrets.js +15 -23
- package/dist/commands/sessions.d.ts +2 -0
- package/dist/commands/sessions.js +27 -25
- package/dist/commands/sync.d.ts +2 -0
- package/dist/commands/sync.js +22 -5
- package/dist/commands/view.js +27 -11
- package/dist/index.js +4 -13
- package/dist/lib/agent-spec.d.ts +36 -0
- package/dist/lib/agent-spec.js +157 -0
- package/dist/lib/agents.d.ts +1 -0
- package/dist/lib/agents.js +45 -4
- package/dist/lib/browser/drivers/ssh.d.ts +47 -2
- package/dist/lib/browser/drivers/ssh.js +113 -24
- package/dist/lib/browser/profiles.js +28 -1
- package/dist/lib/browser/runtime-state.js +28 -8
- package/dist/lib/browser/types.d.ts +10 -1
- package/dist/lib/cli-resources.js +10 -1
- package/dist/lib/daemon.js +32 -0
- package/dist/lib/doctor-diff.d.ts +19 -0
- package/dist/lib/doctor-diff.js +107 -15
- package/dist/lib/exec.d.ts +27 -0
- package/dist/lib/exec.js +62 -19
- package/dist/lib/heal.d.ts +107 -0
- package/dist/lib/heal.js +279 -0
- package/dist/lib/hooks.d.ts +17 -0
- package/dist/lib/hooks.js +127 -3
- package/dist/lib/hosts/dispatch.d.ts +26 -0
- package/dist/lib/hosts/dispatch.js +71 -0
- package/dist/lib/hosts/progress.d.ts +21 -0
- package/dist/lib/hosts/progress.js +49 -0
- package/dist/lib/hosts/providers/local.d.ts +17 -0
- package/dist/lib/hosts/providers/local.js +81 -0
- package/dist/lib/hosts/ready.d.ts +37 -0
- package/dist/lib/hosts/ready.js +88 -0
- package/dist/lib/hosts/registry.d.ts +22 -0
- package/dist/lib/hosts/registry.js +65 -0
- package/dist/lib/hosts/ssh-config.d.ts +37 -0
- package/dist/lib/hosts/ssh-config.js +157 -0
- package/dist/lib/hosts/tasks.d.ts +32 -0
- package/dist/lib/hosts/tasks.js +58 -0
- package/dist/lib/hosts/types.d.ts +51 -0
- package/dist/lib/hosts/types.js +21 -0
- package/dist/lib/loop.d.ts +9 -0
- package/dist/lib/loop.js +13 -1
- package/dist/lib/mcp.js +12 -3
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/migrate.js +9 -5
- package/dist/lib/platform/exec.d.ts +10 -0
- package/dist/lib/platform/exec.js +17 -0
- package/dist/lib/platform/index.d.ts +1 -0
- package/dist/lib/platform/index.js +1 -0
- package/dist/lib/platform/links.d.ts +15 -0
- package/dist/lib/platform/links.js +42 -0
- package/dist/lib/platform/paths.d.ts +18 -0
- package/dist/lib/platform/paths.js +22 -0
- package/dist/lib/platform/posixpath.d.ts +28 -0
- package/dist/lib/platform/posixpath.js +153 -0
- package/dist/lib/plugin-marketplace.d.ts +18 -0
- package/dist/lib/plugin-marketplace.js +67 -1
- package/dist/lib/plugins.d.ts +33 -1
- package/dist/lib/plugins.js +56 -11
- package/dist/lib/project-launch.js +6 -3
- package/dist/lib/sandbox.js +5 -2
- package/dist/lib/self-update.js +7 -2
- package/dist/lib/session/db.d.ts +23 -0
- package/dist/lib/session/db.js +76 -1
- package/dist/lib/session/discover.d.ts +26 -0
- package/dist/lib/session/discover.js +75 -4
- package/dist/lib/session/relative-time.d.ts +7 -0
- package/dist/lib/session/relative-time.js +28 -0
- package/dist/lib/session/remote.d.ts +61 -0
- package/dist/lib/session/remote.js +221 -0
- package/dist/lib/ssh-exec.d.ts +45 -0
- package/dist/lib/ssh-exec.js +61 -0
- package/dist/lib/staleness/detectors/commands.js +7 -6
- package/dist/lib/staleness/writers/commands.js +7 -12
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +2 -0
- package/dist/lib/startup/dev-build.d.ts +22 -0
- package/dist/lib/startup/dev-build.js +41 -0
- package/dist/lib/types.d.ts +28 -0
- package/dist/lib/versions.d.ts +9 -3
- package/dist/lib/versions.js +43 -7
- package/package.json +3 -3
- package/scripts/postinstall.js +62 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
2
|
import type { SessionMeta } from '../lib/session/types.js';
|
|
3
3
|
import { type ActiveSession } from '../lib/session/active.js';
|
|
4
|
+
import { type PickedSession } from './sessions-picker.js';
|
|
4
5
|
/** Grouped + sorted view of active sessions for the --active renderer. */
|
|
5
6
|
export interface ActiveSessionsLayout {
|
|
6
7
|
workspaces: Array<{
|
|
@@ -28,6 +29,7 @@ export interface ActiveSessionsLayout {
|
|
|
28
29
|
* - sessions within a window/flat bucket: input order preserved
|
|
29
30
|
*/
|
|
30
31
|
export declare function groupActiveSessions(sessions: ActiveSession[]): ActiveSessionsLayout;
|
|
32
|
+
export declare function pickSessionInteractive(sessions: SessionMeta[], message?: string, initialSearch?: string, hiddenCount?: number): Promise<PickedSession | null>;
|
|
31
33
|
/**
|
|
32
34
|
* Build the shell command that resumes a picked session.
|
|
33
35
|
*
|
|
@@ -20,6 +20,8 @@ import { getActiveSessions } from '../lib/session/active.js';
|
|
|
20
20
|
import { discoverSessions, countSessionsInScope, resolveSessionById, searchContentIndex } from '../lib/session/discover.js';
|
|
21
21
|
import { filterTeamSessions } from '../lib/session/team-filter.js';
|
|
22
22
|
import { parseSession } from '../lib/session/parse.js';
|
|
23
|
+
import { runRemoteSessions } from '../lib/session/remote.js';
|
|
24
|
+
import { formatRelativeTime } from '../lib/session/relative-time.js';
|
|
23
25
|
import { renderConversationMarkdown, renderSummary, renderSummaryHeader, computeSummaryStats, renderJson, filterEvents, parseRoleList } from '../lib/session/render.js';
|
|
24
26
|
import { renderMarkdown } from '../lib/markdown.js';
|
|
25
27
|
import { colorAgent, resolveAgentName } from '../lib/agents.js';
|
|
@@ -313,6 +315,16 @@ async function renderActiveSessions(asJson) {
|
|
|
313
315
|
}
|
|
314
316
|
/** Main action handler for `agents sessions`. Routes to picker, table, or single-session render. */
|
|
315
317
|
async function sessionsAction(query, options) {
|
|
318
|
+
if (options.host && options.host.length > 0) {
|
|
319
|
+
try {
|
|
320
|
+
runRemoteSessions(options.host);
|
|
321
|
+
}
|
|
322
|
+
catch (err) {
|
|
323
|
+
console.error(chalk.red(err.message));
|
|
324
|
+
process.exit(1);
|
|
325
|
+
}
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
316
328
|
if (options.active) {
|
|
317
329
|
await renderActiveSessions(options.json === true);
|
|
318
330
|
return;
|
|
@@ -645,7 +657,7 @@ function formatPickerLabel(s, query) {
|
|
|
645
657
|
renderTopicCell(label, topic, query, 48, 50) +
|
|
646
658
|
chalk.gray(when));
|
|
647
659
|
}
|
|
648
|
-
async function pickSessionInteractive(sessions, message = 'Search sessions:', initialSearch, hiddenCount = 0) {
|
|
660
|
+
export async function pickSessionInteractive(sessions, message = 'Search sessions:', initialSearch, hiddenCount = 0) {
|
|
649
661
|
if (hiddenCount > 0) {
|
|
650
662
|
console.log(chalk.gray(formatTeamHiddenFooter(hiddenCount)));
|
|
651
663
|
}
|
|
@@ -1116,7 +1128,8 @@ export function registerSessionsCommands(program) {
|
|
|
1116
1128
|
.option('--artifacts', 'List all files written or edited during a session')
|
|
1117
1129
|
.option('--artifact <name>', 'Read a specific artifact by filename or path (outputs to stdout)')
|
|
1118
1130
|
.option('--active', 'Show only sessions running right now across terminals, teams, cloud, and headless agents')
|
|
1119
|
-
.option('--cloud', 'Source sessions from Rush Cloud (captured runs) instead of local disk')
|
|
1131
|
+
.option('--cloud', 'Source sessions from Rush Cloud (captured runs) instead of local disk')
|
|
1132
|
+
.option('-H, --host <target...>', 'Run this query on remote machine(s) over SSH (host alias or user@host; repeatable)');
|
|
1120
1133
|
setHelpSections(sessionsCmd, {
|
|
1121
1134
|
examples: `
|
|
1122
1135
|
# Search prior sessions in this project by topic, file path, or command
|
|
@@ -1136,8 +1149,15 @@ export function registerSessionsCommands(program) {
|
|
|
1136
1149
|
|
|
1137
1150
|
# Export for analysis
|
|
1138
1151
|
agents sessions --since 30d --limit 200 --json > sessions.json
|
|
1152
|
+
|
|
1153
|
+
# Search another machine's sessions live over SSH (no sync needed)
|
|
1154
|
+
agents sessions "auth bug" --last 3 --host yosemite-s1
|
|
1155
|
+
|
|
1156
|
+
# Fan the same query out across several machines
|
|
1157
|
+
agents sessions --all "deploy script" --host box-a --host box-b
|
|
1139
1158
|
`,
|
|
1140
1159
|
notes: `
|
|
1160
|
+
- --host runs the query on the remote's own index over SSH (host alias or user@host); repeat or pass several to fan out. SSH access is the only auth.
|
|
1141
1161
|
- --include and --exclude are mutually exclusive.
|
|
1142
1162
|
- --first and --last are mutually exclusive.
|
|
1143
1163
|
- A filter flag (--include/--exclude/--first/--last) without --markdown/--json defaults to --markdown output.
|
|
@@ -1313,7 +1333,11 @@ function findClaudeResumeTimestamp(filePath, targetTimestampMs) {
|
|
|
1313
1333
|
}
|
|
1314
1334
|
}
|
|
1315
1335
|
function isWithinProject(sessionCwd, projectRoot) {
|
|
1316
|
-
|
|
1336
|
+
// Compare separator- and case-normalized (Windows folds `\`→`/` and lowercases)
|
|
1337
|
+
// so a backslash session cwd matches a forward-slash project root and vice versa.
|
|
1338
|
+
const cwd = toComparablePath(sessionCwd);
|
|
1339
|
+
const root = toComparablePath(projectRoot);
|
|
1340
|
+
return cwd === root || cwd.startsWith(root + '/');
|
|
1317
1341
|
}
|
|
1318
1342
|
function sessionDistance(session, historyEntry) {
|
|
1319
1343
|
if (!historyEntry.timestampMs)
|
|
@@ -1341,25 +1365,3 @@ function padRight(s, width) {
|
|
|
1341
1365
|
function truncate(s, max) {
|
|
1342
1366
|
return s.length > max ? s.slice(0, max - 1) + '.' : s;
|
|
1343
1367
|
}
|
|
1344
|
-
function formatRelativeTime(isoTimestamp) {
|
|
1345
|
-
const now = Date.now();
|
|
1346
|
-
const then = new Date(isoTimestamp).getTime();
|
|
1347
|
-
if (isNaN(then))
|
|
1348
|
-
return isoTimestamp;
|
|
1349
|
-
const diffMs = now - then;
|
|
1350
|
-
const diffMin = Math.floor(diffMs / 60_000);
|
|
1351
|
-
const diffHrs = Math.floor(diffMs / 3_600_000);
|
|
1352
|
-
const diffDays = Math.floor(diffMs / 86_400_000);
|
|
1353
|
-
if (diffMin < 1)
|
|
1354
|
-
return 'just now';
|
|
1355
|
-
if (diffMin < 60)
|
|
1356
|
-
return `${diffMin} min ago`;
|
|
1357
|
-
if (diffHrs < 24)
|
|
1358
|
-
return `${diffHrs} hour${diffHrs === 1 ? '' : 's'} ago`;
|
|
1359
|
-
if (diffDays < 7)
|
|
1360
|
-
return `${diffDays} day${diffDays === 1 ? '' : 's'} ago`;
|
|
1361
|
-
// Older: show date
|
|
1362
|
-
const d = new Date(then);
|
|
1363
|
-
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
1364
|
-
return `${months[d.getMonth()]} ${d.getDate()}`;
|
|
1365
|
-
}
|
package/dist/commands/sync.d.ts
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* agents sync claude # one agent: uses default/sole installed version
|
|
10
10
|
* agents sync claude@2.1.142 # one agent: explicit version
|
|
11
11
|
* agents sync claude@latest # one agent: newest installed
|
|
12
|
+
* agents sync claude@oldest # one agent: oldest installed
|
|
13
|
+
* agents sync claude@pinned (= claude@default) # one agent: the pinned default version
|
|
12
14
|
* agents sync --agent claude --agent-version 2.1.142 # legacy form, still supported
|
|
13
15
|
*
|
|
14
16
|
* The umbrella stages live in lib/sync-umbrella.ts; this file dispatches to them
|
package/dist/commands/sync.js
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* agents sync claude # one agent: uses default/sole installed version
|
|
10
10
|
* agents sync claude@2.1.142 # one agent: explicit version
|
|
11
11
|
* agents sync claude@latest # one agent: newest installed
|
|
12
|
+
* agents sync claude@oldest # one agent: oldest installed
|
|
13
|
+
* agents sync claude@pinned (= claude@default) # one agent: the pinned default version
|
|
12
14
|
* agents sync --agent claude --agent-version 2.1.142 # legacy form, still supported
|
|
13
15
|
*
|
|
14
16
|
* The umbrella stages live in lib/sync-umbrella.ts; this file dispatches to them
|
|
@@ -28,7 +30,7 @@
|
|
|
28
30
|
import * as path from 'path';
|
|
29
31
|
import chalk from 'chalk';
|
|
30
32
|
import { agentLabel, resolveAgentName } from '../lib/agents.js';
|
|
31
|
-
import { isVersionInstalled, syncResourcesToVersion, parseAgentSpec, resolveVersion, listInstalledVersions, getAvailableResources, getActuallySyncedResources, getProjectOnlyResources, getNewResources, hasNewResources, promptResourceSelection, promptNewResourceSelection, } from '../lib/versions.js';
|
|
33
|
+
import { isVersionInstalled, syncResourcesToVersion, parseAgentSpec, resolveVersion, resolveVersionAlias, listInstalledVersions, getAvailableResources, getActuallySyncedResources, getProjectOnlyResources, getNewResources, hasNewResources, promptResourceSelection, promptNewResourceSelection, } from '../lib/versions.js';
|
|
32
34
|
import { compileRulesForProject } from '../lib/rules/compile.js';
|
|
33
35
|
import { runLaunchSync } from '../lib/project-launch.js';
|
|
34
36
|
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
@@ -38,7 +40,7 @@ export function registerSyncCommand(program) {
|
|
|
38
40
|
program
|
|
39
41
|
.command('sync [agentSpec]')
|
|
40
42
|
.summary('Make this machine current, or sync resources into one agent')
|
|
41
|
-
.description('With an [agentSpec], syncs resources (commands, skills, hooks, rules, MCPs, plugins, etc.) into that installed agent version — previews changes and lets you pick. e.g. "claude"
|
|
43
|
+
.description('With an [agentSpec], syncs resources (commands, skills, hooks, rules, MCPs, plugins, etc.) into that installed agent version — previews changes and lets you pick. e.g. "claude", "claude@2.1.142", or a selector: @latest / @oldest / @pinned (= @default).\n\nWith NO agent, runs the umbrella verb: fetch remote state (config repos + secrets + sessions) then reconcile it into every installed agent. Scope it with --repos / --secrets / --sessions, --cloud (fetch only), or --local (reconcile only).')
|
|
42
44
|
.option('--agent <agent>', 'Agent identifier (legacy form; prefer the positional spec)')
|
|
43
45
|
.option('--agent-version <version>', 'Version to sync into (legacy form; prefer "agent@version")')
|
|
44
46
|
.option('--project-dir <path>', 'Path to project-level .agents/ directory containing project-scoped resources')
|
|
@@ -116,17 +118,23 @@ async function runSync(agentSpec, opts) {
|
|
|
116
118
|
// ---------- 1. Resolve agent + version ----------
|
|
117
119
|
let agentId;
|
|
118
120
|
let version;
|
|
121
|
+
// A positional @selector typed by the user (latest/oldest/pinned/default/
|
|
122
|
+
// explicit). parseAgentSpec defaults a missing version to 'latest', so a bare
|
|
123
|
+
// `agents sync claude` and `agents sync claude@latest` are indistinguishable
|
|
124
|
+
// after parsing — we only treat the version as a selector when an '@' was
|
|
125
|
+
// actually typed, keeping bare `claude` on the default-version path.
|
|
126
|
+
let selector;
|
|
119
127
|
if (agentSpec) {
|
|
120
128
|
const parsed = parseAgentSpec(agentSpec);
|
|
121
129
|
if (!parsed) {
|
|
122
130
|
errLog(chalk.red(`Invalid agent spec '${agentSpec}'.`));
|
|
123
|
-
errLog(chalk.gray('Examples: claude, claude@2.1.142,
|
|
131
|
+
errLog(chalk.gray('Examples: claude, claude@2.1.142, claude@latest, claude@oldest, claude@pinned'));
|
|
124
132
|
process.exitCode = 1;
|
|
125
133
|
return;
|
|
126
134
|
}
|
|
127
135
|
agentId = parsed.agent;
|
|
128
|
-
if (
|
|
129
|
-
|
|
136
|
+
if (agentSpec.includes('@'))
|
|
137
|
+
selector = parsed.version;
|
|
130
138
|
}
|
|
131
139
|
if (opts.agent) {
|
|
132
140
|
const resolved = resolveAgentName(opts.agent);
|
|
@@ -138,6 +146,8 @@ async function runSync(agentSpec, opts) {
|
|
|
138
146
|
agentId = resolved;
|
|
139
147
|
}
|
|
140
148
|
if (opts.agentVersion) {
|
|
149
|
+
// Legacy flag and the launch-shim hot path (`--agent-version <concrete>`):
|
|
150
|
+
// pass through verbatim. Selector aliases are a positional-spec feature.
|
|
141
151
|
version = opts.agentVersion;
|
|
142
152
|
}
|
|
143
153
|
if (!agentId) {
|
|
@@ -147,6 +157,13 @@ async function runSync(agentSpec, opts) {
|
|
|
147
157
|
return;
|
|
148
158
|
}
|
|
149
159
|
// ---------- 2. Resolve version (project pin → global default → sole installed) ----------
|
|
160
|
+
// A positional @selector wins over the default-resolution below.
|
|
161
|
+
// @latest / @oldest → newest / oldest installed (process.exit if none)
|
|
162
|
+
// @pinned / @default → undefined → fall through to the default path
|
|
163
|
+
// @x.y.z → that version (process.exit if not installed)
|
|
164
|
+
if (selector !== undefined && !version) {
|
|
165
|
+
version = resolveVersionAlias(agentId, selector);
|
|
166
|
+
}
|
|
150
167
|
if (!version) {
|
|
151
168
|
version = resolveVersion(agentId, process.cwd()) || undefined;
|
|
152
169
|
if (!version) {
|
package/dist/commands/view.js
CHANGED
|
@@ -21,6 +21,9 @@ import { listProfiles, profileSummary } from '../lib/profiles.js';
|
|
|
21
21
|
import { loadManifest, isStale } from '../lib/staleness/index.js';
|
|
22
22
|
import { confirm } from '@inquirer/prompts';
|
|
23
23
|
import { formatPath, isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
24
|
+
// Shown in the email column for agents that are signed in but expose no email
|
|
25
|
+
// address locally (Antigravity, Kimi store an opaque OAuth/JWT credential).
|
|
26
|
+
const SIGNED_IN_LABEL = 'signed in';
|
|
24
27
|
/**
|
|
25
28
|
* Group profile summaries by their host harness, optionally filtered to a
|
|
26
29
|
* single agent. Profile YAMLs that fail validation are silently skipped by
|
|
@@ -350,6 +353,8 @@ async function showInstalledVersions(filterAgentId) {
|
|
|
350
353
|
const info = rawInfo ? mergeCanonical(rawInfo) : undefined;
|
|
351
354
|
if (info?.email)
|
|
352
355
|
maxEmail = Math.max(maxEmail, info.email.length);
|
|
356
|
+
else if (info?.signedIn)
|
|
357
|
+
maxEmail = Math.max(maxEmail, SIGNED_IN_LABEL.length);
|
|
353
358
|
if (info?.plan)
|
|
354
359
|
maxPlanWidth = Math.max(maxPlanWidth, info.plan.length);
|
|
355
360
|
}
|
|
@@ -406,6 +411,7 @@ async function showInstalledVersions(filterAgentId) {
|
|
|
406
411
|
// Build columns, trimming trailing whitespace when columns are empty
|
|
407
412
|
const parts = [` ${label}`];
|
|
408
413
|
const hasEmail = !!vInfo?.email;
|
|
414
|
+
const signedIn = !!vInfo?.signedIn;
|
|
409
415
|
const usageStr = formatUsageSummary(vInfo?.plan || null, usageInfo?.snapshot || null, maxPlanWidth);
|
|
410
416
|
const hasUsage = usageStr.length > 0;
|
|
411
417
|
// Only show lastActive for versions with an actual logged-in account.
|
|
@@ -418,14 +424,17 @@ async function showInstalledVersions(filterAgentId) {
|
|
|
418
424
|
runDefaultBits.push(`mode:${runDefaults.mode}`);
|
|
419
425
|
if (runDefaults.model)
|
|
420
426
|
runDefaultBits.push(`model:${runDefaults.model}`);
|
|
421
|
-
if (!hasEmail && !hasUsage) {
|
|
427
|
+
if (!hasEmail && !hasUsage && !signedIn) {
|
|
422
428
|
// Installed but never signed in
|
|
423
429
|
parts.push(chalk.gray('(not signed in — run ' + agent.cliCommand + ' to log in)'));
|
|
424
430
|
}
|
|
425
431
|
else {
|
|
426
|
-
if (hasEmail || hasUsage || hasActive) {
|
|
427
|
-
|
|
428
|
-
|
|
432
|
+
if (hasEmail || hasUsage || hasActive || signedIn) {
|
|
433
|
+
// Signed-in agents without a local email (Antigravity, Kimi) show a
|
|
434
|
+
// "signed in" placeholder so they read as logged in, not blank.
|
|
435
|
+
const display = vInfo?.email || (signedIn ? SIGNED_IN_LABEL : '');
|
|
436
|
+
const emailCol = display.padEnd(maxEmail);
|
|
437
|
+
parts.push(display ? chalk.cyan(emailCol) : ' '.repeat(maxEmail));
|
|
429
438
|
}
|
|
430
439
|
if (hasUsage || hasActive) {
|
|
431
440
|
const usagePad = ' '.repeat(Math.max(0, maxUsageWidth - visibleWidth(usageStr)));
|
|
@@ -503,8 +512,10 @@ async function showInstalledVersions(filterAgentId) {
|
|
|
503
512
|
const gUsage = gUsageKey ? usageByKey.get(gUsageKey) : undefined;
|
|
504
513
|
const gUsageStr = formatUsageSummary(gInfo?.plan || null, gUsage?.snapshot || null);
|
|
505
514
|
const gActiveStr = gInfo ? formatLastActive(gInfo.lastActive) : '';
|
|
506
|
-
if (gInfo?.email || gUsageStr || gActiveStr)
|
|
507
|
-
|
|
515
|
+
if (gInfo?.email || gUsageStr || gActiveStr || gInfo?.signedIn) {
|
|
516
|
+
const gDisplay = gInfo?.email || (gInfo?.signedIn ? SIGNED_IN_LABEL : '');
|
|
517
|
+
parts.push(gDisplay ? chalk.cyan(gDisplay) : '');
|
|
518
|
+
}
|
|
508
519
|
if (gUsageStr || gActiveStr)
|
|
509
520
|
parts.push(gUsageStr);
|
|
510
521
|
const gStatusStr = formatUsageStatusBadge(gInfo?.usageStatus);
|
|
@@ -814,7 +825,11 @@ async function showAgentResources(agentId, requestedVersion, filter) {
|
|
|
814
825
|
cliVersion: version,
|
|
815
826
|
info: accountInfo,
|
|
816
827
|
});
|
|
817
|
-
const emailStr = accountInfo.email
|
|
828
|
+
const emailStr = accountInfo.email
|
|
829
|
+
? chalk.cyan(` ${accountInfo.email}`)
|
|
830
|
+
: accountInfo.signedIn
|
|
831
|
+
? chalk.cyan(` ${SIGNED_IN_LABEL}`)
|
|
832
|
+
: '';
|
|
818
833
|
const status = chalk.green(version);
|
|
819
834
|
const usageStr = formatUsageSummary(accountInfo.plan, null);
|
|
820
835
|
const usagePart = usageStr ? ` ${usageStr}` : '';
|
|
@@ -1006,7 +1021,7 @@ async function collectAgentsJson(filterAgentId) {
|
|
|
1006
1021
|
const entry = {
|
|
1007
1022
|
version,
|
|
1008
1023
|
isDefault: version === globalDefault,
|
|
1009
|
-
signedIn:
|
|
1024
|
+
signedIn: info.signedIn,
|
|
1010
1025
|
email: info.email,
|
|
1011
1026
|
plan: info.plan,
|
|
1012
1027
|
usageStatus: info.usageStatus,
|
|
@@ -1273,9 +1288,10 @@ export async function viewAction(agentArg, options) {
|
|
|
1273
1288
|
console.log(chalk.red(formatAgentError(agentName)));
|
|
1274
1289
|
process.exit(1);
|
|
1275
1290
|
}
|
|
1276
|
-
// Keep 'default' as-is since showAgentResources handles
|
|
1277
|
-
// returns undefined for '
|
|
1278
|
-
|
|
1291
|
+
// Keep 'default'/'pinned' as-is since showAgentResources handles 'default';
|
|
1292
|
+
// resolveVersionAlias returns undefined for both (they're synonyms), which
|
|
1293
|
+
// would otherwise skip the detailed view.
|
|
1294
|
+
const requestedVersion = (parts[1] === 'default' || parts[1] === 'pinned')
|
|
1279
1295
|
? 'default'
|
|
1280
1296
|
: (resolveVersionAlias(agentId, parts[1]) ?? null);
|
|
1281
1297
|
if (prune) {
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import * as fs from 'fs';
|
|
|
11
11
|
import * as os from 'os';
|
|
12
12
|
import * as path from 'path';
|
|
13
13
|
import { fileURLToPath } from 'url';
|
|
14
|
+
import { detectDevBuild } from './lib/startup/dev-build.js';
|
|
14
15
|
// `ora`, `@inquirer/prompts`, `./commands/utils.js`, and the agents/versions/shims
|
|
15
16
|
// modules are imported dynamically at their use sites: they are needed only on
|
|
16
17
|
// interactive / update / shim-repair paths, never for fast commands like
|
|
@@ -37,18 +38,7 @@ import { NPM_PACKAGE_NAME, deriveGlobalPrefix, detectPackageManager, installPack
|
|
|
37
38
|
// must not scribble on the user's real ~/.agents/), and skip the update prompt
|
|
38
39
|
// (the "0.0.0-dev -> 1.x.y" message is misleading). Each individual env var
|
|
39
40
|
// can still be set explicitly to override (set to '0' to re-enable).
|
|
40
|
-
const IS_DEV_BUILD = (
|
|
41
|
-
if (VERSION.startsWith('0.0.0-dev'))
|
|
42
|
-
return true;
|
|
43
|
-
try {
|
|
44
|
-
const cliPath = process.argv[1] || '';
|
|
45
|
-
const repoRoot = path.dirname(path.dirname(cliPath));
|
|
46
|
-
return fs.existsSync(path.join(repoRoot, '.git'));
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
})();
|
|
41
|
+
const IS_DEV_BUILD = detectDevBuild(process.argv[1] || '', VERSION);
|
|
52
42
|
if (IS_DEV_BUILD) {
|
|
53
43
|
if (process.env.AGENTS_NO_AUTOPULL === undefined)
|
|
54
44
|
process.env.AGENTS_NO_AUTOPULL = '1';
|
|
@@ -61,7 +51,7 @@ if (IS_DEV_BUILD) {
|
|
|
61
51
|
// module on each invocation (which loaded the whole ~50-module tree before the
|
|
62
52
|
// first byte of output), the registry maps a command name to a thunk that
|
|
63
53
|
// imports only what that command needs. See src/lib/startup/command-registry.ts.
|
|
64
|
-
import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadPackages, loadDaemon, loadRoutines, loadRun, loadDefaults, loadModels, loadPrune, loadTrash, loadRestore, loadDoctor, loadProfiles, loadSecrets, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadRefreshRules, loadDrive, loadFactory, loadUsage, loadCost, loadBudget, loadAlias, loadPty, loadTmux, loadBrowser, loadComputer, loadPull, loadPush, loadRepo, loadSetup, } from './lib/startup/command-registry.js';
|
|
54
|
+
import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadPackages, loadDaemon, loadRoutines, loadRun, loadDefaults, loadModels, loadPrune, loadTrash, loadRestore, loadDoctor, loadProfiles, loadSecrets, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadRefreshRules, loadDrive, loadFactory, loadUsage, loadCost, loadBudget, loadAlias, loadPty, loadTmux, loadBrowser, loadComputer, loadHosts, loadPull, loadPush, loadRepo, loadSetup, } from './lib/startup/command-registry.js';
|
|
65
55
|
import { applyGlobalHelpConventions } from './lib/help.js';
|
|
66
56
|
import { IS_WINDOWS } from './lib/platform/index.js';
|
|
67
57
|
// Transparent shim delegate: the generated Windows `.cmd` shims invoke
|
|
@@ -793,6 +783,7 @@ async function registerAllEagerCommands() {
|
|
|
793
783
|
await reg(loadTmux);
|
|
794
784
|
await reg(loadBrowser);
|
|
795
785
|
await reg(loadComputer);
|
|
786
|
+
await reg(loadHosts);
|
|
796
787
|
registerJobsCronAliasCommand(program, 'jobs');
|
|
797
788
|
registerJobsCronAliasCommand(program, 'cron');
|
|
798
789
|
registerUpgradeCommand(program);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { AgentId } from './types.js';
|
|
2
|
+
export interface AgentTarget {
|
|
3
|
+
agent: AgentId;
|
|
4
|
+
/** Resolved exact version, or null when the agent has no installed versions yet. */
|
|
5
|
+
version: string | null;
|
|
6
|
+
}
|
|
7
|
+
/** Canonical qualifier set, in help/display order. `pinned` ≡ `default`. */
|
|
8
|
+
export declare const AGENT_QUALIFIERS: readonly ["latest", "oldest", "pinned", "default", "all"];
|
|
9
|
+
export type AgentQualifier = (typeof AGENT_QUALIFIERS)[number];
|
|
10
|
+
/** Shared `--help` epilog so every agent-spec command documents the same grammar. */
|
|
11
|
+
export declare const AGENT_SPEC_HELP: string;
|
|
12
|
+
export declare class AgentSpecError extends Error {
|
|
13
|
+
constructor(message: string);
|
|
14
|
+
}
|
|
15
|
+
export interface ResolveAgentTargetsOptions {
|
|
16
|
+
/** Project dir for resolving a bare spec's project pin. Defaults to process.cwd(). */
|
|
17
|
+
cwd?: string;
|
|
18
|
+
/** Restrict the agents a spec may name (e.g. only mcp-capable). Defaults to all. */
|
|
19
|
+
availableAgents?: readonly AgentId[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Resolve an agent spec (single or comma-list) into concrete installed targets.
|
|
23
|
+
* Domain = installed: `@latest`/`@oldest`/`@all` range over installed versions
|
|
24
|
+
* (`add`/`install` use a separate available-version path). Throws AgentSpecError
|
|
25
|
+
* on bad input — never calls process.exit, so it is safe on the hot path and in
|
|
26
|
+
* library contexts.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveAgentTargets(spec: string, opts?: ResolveAgentTargetsOptions): AgentTarget[];
|
|
29
|
+
/**
|
|
30
|
+
* Convenience for single-target commands (`use`, `run`): resolve a spec that
|
|
31
|
+
* must name exactly one installed version. Rejects `@all` / multi-target specs.
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveSingleAgentTarget(spec: string, opts?: ResolveAgentTargetsOptions): {
|
|
34
|
+
agent: AgentId;
|
|
35
|
+
version: string;
|
|
36
|
+
};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// Centralized agent-spec resolution — one vocabulary, one resolver, reused by
|
|
2
|
+
// every subcommand that accepts `<agent>[@<qualifier>]`.
|
|
3
|
+
//
|
|
4
|
+
// The qualifier vocabulary used to be split across three functions in
|
|
5
|
+
// versions.ts (parseAgentSpec, resolveVersionAlias, resolveInstalledAgentTargets)
|
|
6
|
+
// with diverging support — `@latest`/`@oldest` in one, `@all`/`@default` in
|
|
7
|
+
// another, `@pinned` nowhere. This module is the single source of truth.
|
|
8
|
+
//
|
|
9
|
+
// Built for the hot path (`--launch`, ~100ms budget): the common specs resolve
|
|
10
|
+
// with NO directory enumeration —
|
|
11
|
+
// exact `claude@2.1.181` → one isVersionInstalled() (existsSync)
|
|
12
|
+
// `claude@pinned|@default` → memoized getGlobalDefault() + existsSync
|
|
13
|
+
// bare `claude` → resolveVersion() (memoized meta), no readdir
|
|
14
|
+
// Only the relative qualifiers `@latest`/`@oldest`/`@all` enumerate, and even
|
|
15
|
+
// then via the mtime-cached listInstalledVersions().
|
|
16
|
+
import { AGENTS, ALL_AGENT_IDS, resolveAgentName, formatAgentError } from './agents.js';
|
|
17
|
+
import { listInstalledVersions, getGlobalDefault, isVersionInstalled, resolveVersion, } from './versions.js';
|
|
18
|
+
/** Canonical qualifier set, in help/display order. `pinned` ≡ `default`. */
|
|
19
|
+
export const AGENT_QUALIFIERS = ['latest', 'oldest', 'pinned', 'default', 'all'];
|
|
20
|
+
/** Shared `--help` epilog so every agent-spec command documents the same grammar. */
|
|
21
|
+
export const AGENT_SPEC_HELP = 'Agent spec: <agent>[@<qualifier>]. Qualifiers: ' +
|
|
22
|
+
'@latest (highest installed), @oldest (lowest installed), ' +
|
|
23
|
+
'@pinned / @default (your configured default — synonyms), ' +
|
|
24
|
+
'@all (every installed version), or an exact @x.y.z. ' +
|
|
25
|
+
'Bare <agent> uses the resolved default (project pin → global default). ' +
|
|
26
|
+
'Comma-separate to combine: claude@all,codex@latest.';
|
|
27
|
+
export class AgentSpecError extends Error {
|
|
28
|
+
constructor(message) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = 'AgentSpecError';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Resolve an agent spec (single or comma-list) into concrete installed targets.
|
|
35
|
+
* Domain = installed: `@latest`/`@oldest`/`@all` range over installed versions
|
|
36
|
+
* (`add`/`install` use a separate available-version path). Throws AgentSpecError
|
|
37
|
+
* on bad input — never calls process.exit, so it is safe on the hot path and in
|
|
38
|
+
* library contexts.
|
|
39
|
+
*/
|
|
40
|
+
export function resolveAgentTargets(spec, opts = {}) {
|
|
41
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
42
|
+
const available = opts.availableAgents ?? ALL_AGENT_IDS;
|
|
43
|
+
const rawEntries = spec
|
|
44
|
+
.split(',')
|
|
45
|
+
.map((s) => s.trim())
|
|
46
|
+
.filter(Boolean);
|
|
47
|
+
if (rawEntries.length === 0) {
|
|
48
|
+
throw new AgentSpecError('Empty agent spec.');
|
|
49
|
+
}
|
|
50
|
+
// Expand the bare literal `all` (or `all@all`) into every available agent that
|
|
51
|
+
// has at least one installed version. Lenient: agents with nothing installed
|
|
52
|
+
// are skipped rather than erroring.
|
|
53
|
+
const entries = [];
|
|
54
|
+
for (const e of rawEntries) {
|
|
55
|
+
if (e === 'all' || e === 'all@all') {
|
|
56
|
+
for (const a of available) {
|
|
57
|
+
if (listInstalledVersions(a).length > 0)
|
|
58
|
+
entries.push(`${a}@all`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
entries.push(e);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const out = [];
|
|
66
|
+
const seen = new Set();
|
|
67
|
+
const push = (agent, version) => {
|
|
68
|
+
const key = `${agent}@${version ?? ''}`;
|
|
69
|
+
if (!seen.has(key)) {
|
|
70
|
+
seen.add(key);
|
|
71
|
+
out.push({ agent, version });
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
const at = entry.indexOf('@');
|
|
76
|
+
const agentToken = (at === -1 ? entry : entry.slice(0, at)).trim();
|
|
77
|
+
const qualifier = at === -1 ? null : entry.slice(at + 1).trim();
|
|
78
|
+
if (!agentToken)
|
|
79
|
+
continue;
|
|
80
|
+
if (at !== -1 && !qualifier) {
|
|
81
|
+
throw new AgentSpecError(`Missing version in '${entry}'. Use ${agentToken}@x.y.z, @latest, @oldest, @pinned, @default, or @all.`);
|
|
82
|
+
}
|
|
83
|
+
const agent = resolveAgentName(agentToken);
|
|
84
|
+
if (!agent || !available.includes(agent)) {
|
|
85
|
+
throw new AgentSpecError(formatAgentError(agentToken, [...available]));
|
|
86
|
+
}
|
|
87
|
+
const name = AGENTS[agent].name;
|
|
88
|
+
// ----- bare: resolved default, NO enumeration in the common case -----
|
|
89
|
+
if (qualifier === null) {
|
|
90
|
+
const resolved = resolveVersion(agent, cwd); // project pin → global default (meta-only)
|
|
91
|
+
if (resolved) {
|
|
92
|
+
push(agent, resolved);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
const installed = listInstalledVersions(agent);
|
|
96
|
+
if (installed.length === 0)
|
|
97
|
+
push(agent, null);
|
|
98
|
+
else if (installed.length === 1)
|
|
99
|
+
push(agent, installed[0]);
|
|
100
|
+
else
|
|
101
|
+
throw new AgentSpecError(`No default version set for ${name}. Specify one (${agent}@<version>) or set it: agents use ${agent}@<version>.`);
|
|
102
|
+
}
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
// ----- @pinned / @default: synonyms, meta-only fast path -----
|
|
106
|
+
if (qualifier === 'pinned' || qualifier === 'default') {
|
|
107
|
+
const def = getGlobalDefault(agent);
|
|
108
|
+
if (!def) {
|
|
109
|
+
throw new AgentSpecError(`No default version set for ${name}. Run: agents use ${agent}@<version>`);
|
|
110
|
+
}
|
|
111
|
+
push(agent, def);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
// ----- @all: every installed version -----
|
|
115
|
+
if (qualifier === 'all') {
|
|
116
|
+
const installed = listInstalledVersions(agent);
|
|
117
|
+
if (installed.length === 0) {
|
|
118
|
+
throw new AgentSpecError(`No managed versions are installed for ${name}. Run: agents add ${agent}@latest`);
|
|
119
|
+
}
|
|
120
|
+
for (const v of installed)
|
|
121
|
+
push(agent, v);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
// ----- @latest / @oldest: enumerate (mtime-cached), pick an end -----
|
|
125
|
+
if (qualifier === 'latest' || qualifier === 'oldest') {
|
|
126
|
+
const installed = listInstalledVersions(agent); // already sorted ascending
|
|
127
|
+
if (installed.length === 0) {
|
|
128
|
+
throw new AgentSpecError(`No managed versions are installed for ${name}. Run: agents add ${agent}@latest`);
|
|
129
|
+
}
|
|
130
|
+
push(agent, qualifier === 'oldest' ? installed[0] : installed[installed.length - 1]);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
// ----- exact version: one existsSync, NO enumeration -----
|
|
134
|
+
if (!isVersionInstalled(agent, qualifier)) {
|
|
135
|
+
const installed = listInstalledVersions(agent);
|
|
136
|
+
const hint = installed.length ? ` Installed: ${installed.join(', ')}.` : '';
|
|
137
|
+
throw new AgentSpecError(`${name}@${qualifier} is not installed.${hint} Install it: agents add ${agent}@${qualifier}`);
|
|
138
|
+
}
|
|
139
|
+
push(agent, qualifier);
|
|
140
|
+
}
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Convenience for single-target commands (`use`, `run`): resolve a spec that
|
|
145
|
+
* must name exactly one installed version. Rejects `@all` / multi-target specs.
|
|
146
|
+
*/
|
|
147
|
+
export function resolveSingleAgentTarget(spec, opts = {}) {
|
|
148
|
+
const targets = resolveAgentTargets(spec, opts);
|
|
149
|
+
if (targets.length !== 1) {
|
|
150
|
+
throw new AgentSpecError(`'${spec}' resolves to ${targets.length} targets; this command needs exactly one.`);
|
|
151
|
+
}
|
|
152
|
+
const t = targets[0];
|
|
153
|
+
if (t.version === null) {
|
|
154
|
+
throw new AgentSpecError(`No installed version for ${AGENTS[t.agent].name}. Run: agents add ${t.agent}@latest`);
|
|
155
|
+
}
|
|
156
|
+
return { agent: t.agent, version: t.version };
|
|
157
|
+
}
|
package/dist/lib/agents.d.ts
CHANGED
|
@@ -107,6 +107,7 @@ export interface AccountInfo {
|
|
|
107
107
|
currency: string;
|
|
108
108
|
} | null;
|
|
109
109
|
lastActive: Date | null;
|
|
110
|
+
signedIn: boolean;
|
|
110
111
|
}
|
|
111
112
|
/** Return the email address associated with the agent's auth config, or null. */
|
|
112
113
|
export declare function getAccountEmail(agentId: AgentId, home?: string): Promise<string | null>;
|