@phnx-labs/agents-cli 1.20.5 → 1.20.7
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 +13 -0
- package/README.md +1 -1
- package/dist/commands/browser.js +31 -4
- package/dist/commands/computer-actions.d.ts +36 -0
- package/dist/commands/computer-actions.js +328 -0
- package/dist/commands/computer.js +74 -55
- package/dist/commands/defaults.d.ts +7 -0
- package/dist/commands/defaults.js +89 -0
- package/dist/commands/exec.js +24 -6
- package/dist/commands/inspect.d.ts +38 -7
- package/dist/commands/inspect.js +194 -24
- package/dist/commands/rules.js +3 -3
- package/dist/commands/secrets.js +46 -9
- package/dist/commands/sessions.js +9 -12
- package/dist/commands/setup.js +2 -2
- package/dist/commands/teams.js +108 -11
- package/dist/commands/view.d.ts +12 -1
- package/dist/commands/view.js +121 -38
- package/dist/index.js +61 -22
- package/dist/lib/agents.d.ts +10 -6
- package/dist/lib/agents.js +23 -14
- package/dist/lib/browser/chrome.d.ts +10 -0
- package/dist/lib/browser/chrome.js +84 -3
- package/dist/lib/daemon.js +4 -7
- package/dist/lib/exec.d.ts +9 -0
- package/dist/lib/exec.js +85 -9
- package/dist/lib/migrate.js +6 -4
- package/dist/lib/permissions.d.ts +23 -0
- package/dist/lib/permissions.js +89 -7
- package/dist/lib/platform/exec.d.ts +9 -0
- package/dist/lib/platform/exec.js +24 -0
- package/dist/lib/platform/index.d.ts +20 -0
- package/dist/lib/platform/index.js +20 -0
- package/dist/lib/platform/paths.d.ts +22 -0
- package/dist/lib/platform/paths.js +49 -0
- package/dist/lib/platform/process.d.ts +12 -0
- package/dist/lib/platform/process.js +22 -0
- package/dist/lib/plugin-marketplace.js +1 -1
- package/dist/lib/project-launch.d.ts +5 -0
- package/dist/lib/project-launch.js +37 -0
- package/dist/lib/pty-client.js +13 -5
- package/dist/lib/pty-server.d.ts +24 -1
- package/dist/lib/pty-server.js +109 -29
- package/dist/lib/resources/rules.js +1 -1
- package/dist/lib/resources/skills.js +1 -1
- package/dist/lib/resources.d.ts +2 -0
- package/dist/lib/resources.js +2 -1
- package/dist/lib/rotate.js +6 -18
- package/dist/lib/run-config.d.ts +9 -0
- package/dist/lib/run-config.js +35 -0
- package/dist/lib/run-defaults.d.ts +42 -0
- package/dist/lib/run-defaults.js +180 -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/install-helper.d.ts +11 -3
- package/dist/lib/secrets/install-helper.js +48 -6
- package/dist/lib/secrets/linux.d.ts +12 -0
- package/dist/lib/secrets/linux.js +30 -16
- package/dist/lib/session/artifacts.js +8 -2
- package/dist/lib/shims.d.ts +9 -1
- package/dist/lib/shims.js +80 -3
- package/dist/lib/staleness/detectors/hooks.js +1 -1
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/teams/agents.js +5 -7
- package/dist/lib/teams/api.d.ts +67 -0
- package/dist/lib/teams/api.js +78 -0
- package/dist/lib/types.d.ts +15 -6
- package/dist/lib/versions.js +4 -4
- package/package.json +5 -2
- package/scripts/postinstall.js +18 -1
|
@@ -15,6 +15,7 @@ import chalk from 'chalk';
|
|
|
15
15
|
import ora from 'ora';
|
|
16
16
|
import { SESSION_AGENTS } from '../lib/session/types.js';
|
|
17
17
|
import { discoverArtifacts, readArtifact, resolveArtifact } from '../lib/session/artifacts.js';
|
|
18
|
+
import { looksLikePath, toComparablePath, homeDir } from '../lib/platform/index.js';
|
|
18
19
|
import { getActiveSessions } from '../lib/session/active.js';
|
|
19
20
|
import { discoverSessions, countSessionsInScope, resolveSessionById, searchContentIndex } from '../lib/session/discover.js';
|
|
20
21
|
import { filterTeamSessions } from '../lib/session/team-filter.js';
|
|
@@ -69,15 +70,6 @@ function createScanProgressTracker(verbs, suffix, spinner) {
|
|
|
69
70
|
}
|
|
70
71
|
const PICKER_RECENT_COUNT = 15;
|
|
71
72
|
const PICKER_POOL_LIMIT = 200;
|
|
72
|
-
/**
|
|
73
|
-
* Detect whether a positional argument looks like a filesystem path.
|
|
74
|
-
* Naked paths (., ./, ../, /, ~) filter sessions by project directory.
|
|
75
|
-
* Everything else is treated as a search query string.
|
|
76
|
-
*/
|
|
77
|
-
function isPathLike(query) {
|
|
78
|
-
return query === '.' || query.startsWith('./') || query.startsWith('../')
|
|
79
|
-
|| query.startsWith('/') || query.startsWith('~');
|
|
80
|
-
}
|
|
81
73
|
/**
|
|
82
74
|
* Resolve a path-like query to an absolute directory path.
|
|
83
75
|
*/
|
|
@@ -153,8 +145,13 @@ function contextColor(context) {
|
|
|
153
145
|
function shortCwd(cwd) {
|
|
154
146
|
if (!cwd)
|
|
155
147
|
return '-';
|
|
156
|
-
const home =
|
|
157
|
-
|
|
148
|
+
const home = homeDir();
|
|
149
|
+
// Compare in normalized form so the `~` shorthand also lands on Windows
|
|
150
|
+
// (case-insensitive, backslash paths); on POSIX this is byte-identical to the
|
|
151
|
+
// previous `cwd.startsWith(home)`. The displayed tail keeps original casing.
|
|
152
|
+
return toComparablePath(cwd).startsWith(toComparablePath(home))
|
|
153
|
+
? '~' + cwd.slice(home.length)
|
|
154
|
+
: cwd;
|
|
158
155
|
}
|
|
159
156
|
function formatStartedAt(startedAtMs) {
|
|
160
157
|
if (!startedAtMs)
|
|
@@ -334,7 +331,7 @@ async function sessionsAction(query, options) {
|
|
|
334
331
|
// Path-like queries filter by project directory instead of text search.
|
|
335
332
|
let pathFilter;
|
|
336
333
|
let searchQuery;
|
|
337
|
-
if (query &&
|
|
334
|
+
if (query && looksLikePath(query)) {
|
|
338
335
|
const resolved = resolvePathFilter(query);
|
|
339
336
|
if (!fs.existsSync(resolved)) {
|
|
340
337
|
console.log(chalk.yellow(`Path not found: ${resolved}`));
|
package/dist/commands/setup.js
CHANGED
|
@@ -14,7 +14,7 @@ import { DEFAULT_SYSTEM_REPO, systemRepoSlug } from '../lib/types.js';
|
|
|
14
14
|
import { getAgentsDir, getVersionsDir, ensureAgentsDir } from '../lib/state.js';
|
|
15
15
|
import { isGitRepo, cloneIntoExisting, pullRepo } from '../lib/git.js';
|
|
16
16
|
import { isPromptCancelled, isInteractiveTerminal } from './utils.js';
|
|
17
|
-
import { AGENTS, getUnmanagedAgentInstalls, countSessionFiles, agentLabel } from '../lib/agents.js';
|
|
17
|
+
import { AGENTS, agentConfigDirName, getUnmanagedAgentInstalls, countSessionFiles, agentLabel } from '../lib/agents.js';
|
|
18
18
|
import { setGlobalDefault } from '../lib/versions.js';
|
|
19
19
|
import { ensureShimCurrent, switchHomeFileSymlinks, isShimsInPath, addShimsToPath, getPathSetupInstructions } from '../lib/shims.js';
|
|
20
20
|
import { setHelpSections } from '../lib/help.js';
|
|
@@ -28,7 +28,7 @@ async function importAgent(agentId, version) {
|
|
|
28
28
|
const configDir = agent.configDir;
|
|
29
29
|
const versionsDir = getVersionsDir();
|
|
30
30
|
const versionHome = path.join(versionsDir, agentId, version, 'home');
|
|
31
|
-
const versionConfigDir = path.join(versionHome,
|
|
31
|
+
const versionConfigDir = path.join(versionHome, agentConfigDirName(agentId));
|
|
32
32
|
// Skip if version dir already exists (collision)
|
|
33
33
|
if (fs.existsSync(versionConfigDir)) {
|
|
34
34
|
return { success: false, skipped: true, error: `${version} already installed` };
|
package/dist/commands/teams.js
CHANGED
|
@@ -4,7 +4,7 @@ import * as path from 'path';
|
|
|
4
4
|
import { AgentManager, checkAllClis, getAgentsDir, VALID_TASK_TYPES, } from '../lib/teams/agents.js';
|
|
5
5
|
import { resolveProvider } from '../lib/cloud/registry.js';
|
|
6
6
|
import { runSupervisor } from '../lib/teams/supervisor.js';
|
|
7
|
-
import { handleSpawn, handleStatus, handleStop, handleTasks, } from '../lib/teams/api.js';
|
|
7
|
+
import { handleSpawn, handleStatus, handleStop, handleTasks, toTaskStatusSummary, } from '../lib/teams/api.js';
|
|
8
8
|
import { createTeam, ensureTeam, getTeam, loadTeams, removeTeam, teamExists, } from '../lib/teams/registry.js';
|
|
9
9
|
import { setHelpSections } from '../lib/help.js';
|
|
10
10
|
import { createWorktree, isGitRepo, hasUncommittedChanges, removeWorktree, } from '../lib/teams/worktree.js';
|
|
@@ -382,6 +382,55 @@ async function resolveTeammateSessions(agents) {
|
|
|
382
382
|
}
|
|
383
383
|
return map;
|
|
384
384
|
}
|
|
385
|
+
// Default compact renderer — one block per teammate, optimized for the
|
|
386
|
+
// orchestrator scanning "what state, what did you touch, what did you say
|
|
387
|
+
// last." Caller passes the projected AgentStatusSummary; for the full
|
|
388
|
+
// verbose layout use printAgentDetail above.
|
|
389
|
+
function printAgentSummary(s) {
|
|
390
|
+
const label = statusColor(s.status)(s.status.toUpperCase());
|
|
391
|
+
const handle = s.name ?? shortId(s.agent_id);
|
|
392
|
+
const ident = s.name ? chalk.gray(`(${shortId(s.agent_id)})`) : '';
|
|
393
|
+
const duration = s.duration ? `${chalk.gray(' · ')}${chalk.white(s.duration)}` : '';
|
|
394
|
+
const errBadge = s.has_errors ? chalk.red(' !') : '';
|
|
395
|
+
const tools = chalk.gray(` · ${s.tool_count} tools`);
|
|
396
|
+
console.log(` ${chalk.cyan(handle.padEnd(14))} ${ident.padEnd(11)} ${label}${duration}${tools}${errBadge}`);
|
|
397
|
+
// Files: counts + basenames. Read is count only.
|
|
398
|
+
const fileLines = [];
|
|
399
|
+
const renderCat = (label, cat) => {
|
|
400
|
+
if (cat.count === 0)
|
|
401
|
+
return;
|
|
402
|
+
const more = cat.count > cat.names.length ? ` +${cat.count - cat.names.length}` : '';
|
|
403
|
+
const names = cat.names.length ? ` ${cat.names.join(', ')}${more}` : '';
|
|
404
|
+
fileLines.push(`${label} ${cat.count}${names}`);
|
|
405
|
+
};
|
|
406
|
+
renderCat('modified', s.files.modified);
|
|
407
|
+
renderCat('created', s.files.created);
|
|
408
|
+
renderCat('deleted', s.files.deleted);
|
|
409
|
+
if (s.files.read.count > 0)
|
|
410
|
+
fileLines.push(`read ${s.files.read.count}`);
|
|
411
|
+
if (fileLines.length) {
|
|
412
|
+
console.log(` ${chalk.gray('files ')} ${fileLines.join(chalk.gray(' · '))}`);
|
|
413
|
+
}
|
|
414
|
+
// Last 3 bash commands.
|
|
415
|
+
const recentBash = s.bash_commands.slice(-3);
|
|
416
|
+
if (recentBash.length) {
|
|
417
|
+
console.log(` ${chalk.gray('bash ')}`);
|
|
418
|
+
for (const cmd of recentBash) {
|
|
419
|
+
console.log(` ${chalk.gray('$')} ${truncate(cmd, 96)}`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
// Last messages — first non-empty line of each, truncated.
|
|
423
|
+
if (s.last_messages.length) {
|
|
424
|
+
console.log(` ${chalk.gray('messages')}`);
|
|
425
|
+
for (const msg of s.last_messages) {
|
|
426
|
+
const firstLine = msg.split(/\r?\n/).find((l) => l.trim()) || '';
|
|
427
|
+
if (firstLine)
|
|
428
|
+
console.log(` ${chalk.gray('>')} ${truncate(firstLine, 96)}`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
if (s.pr_url)
|
|
432
|
+
console.log(` ${chalk.gray('PR ')} ${chalk.cyan(s.pr_url)}`);
|
|
433
|
+
}
|
|
385
434
|
// Render a team's status in the same format the `status` subcommand uses, so
|
|
386
435
|
// the interactive picker's Enter action drops the user into a familiar view.
|
|
387
436
|
async function printTeamStatus(team, result) {
|
|
@@ -409,6 +458,36 @@ async function printTeamStatus(team, result) {
|
|
|
409
458
|
console.log();
|
|
410
459
|
console.log(chalk.gray(`cursor: ${result.cursor}`));
|
|
411
460
|
}
|
|
461
|
+
// Compact default renderer — no session-file dive, no per-teammate
|
|
462
|
+
// 15-line preview. One block per teammate, suitable for the orchestrator
|
|
463
|
+
// scanning what each agent did. Use `printTeamStatus` (above) for the
|
|
464
|
+
// verbose/legacy layout.
|
|
465
|
+
function printTeamSummary(team, result) {
|
|
466
|
+
const { summary, agents } = result;
|
|
467
|
+
console.log(chalk.bold(`Team ${chalk.cyan(team)} `) +
|
|
468
|
+
chalk.gray(summary.pending > 0
|
|
469
|
+
? `(${summary.pending} pending, ${summary.running} working, ${summary.completed} done, ${summary.failed} failed, ${summary.stopped} stopped)`
|
|
470
|
+
: `(${summary.running} working, ${summary.completed} done, ${summary.failed} failed, ${summary.stopped} stopped)`));
|
|
471
|
+
if (agents.length === 0) {
|
|
472
|
+
console.log(chalk.gray(' (no teammates yet — add one with `agents teams add`)'));
|
|
473
|
+
}
|
|
474
|
+
else {
|
|
475
|
+
const width = Math.min(process.stdout.columns || 80, 80);
|
|
476
|
+
const divider = chalk.gray('┈'.repeat(width));
|
|
477
|
+
for (let i = 0; i < agents.length; i++) {
|
|
478
|
+
console.log();
|
|
479
|
+
if (i > 0) {
|
|
480
|
+
console.log(divider);
|
|
481
|
+
console.log();
|
|
482
|
+
}
|
|
483
|
+
printAgentSummary(agents[i]);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
console.log();
|
|
487
|
+
console.log(chalk.gray(`cursor: ${result.cursor}`));
|
|
488
|
+
console.log(chalk.gray('Full detail: agents teams status ' + team + ' --verbose'));
|
|
489
|
+
console.log(chalk.gray('Raw log: agents teams logs --team ' + team + ' --teammate <name>'));
|
|
490
|
+
}
|
|
412
491
|
// Classify a team into a single bucket for --status filtering.
|
|
413
492
|
// - empty: no teammates (created but nobody added yet)
|
|
414
493
|
// - waiting: only staged teammates — call `teams start` to kick them off
|
|
@@ -964,10 +1043,11 @@ export function registerTeamsCommands(program) {
|
|
|
964
1043
|
teams
|
|
965
1044
|
.command('status [team]')
|
|
966
1045
|
.aliases(['s', 'st', 'check'])
|
|
967
|
-
.description("Check in on a team:
|
|
1046
|
+
.description("Check in on a team: status, files touched, recent commands, last messages. Pass --verbose for the full per-teammate dump; --since for delta polling.")
|
|
968
1047
|
.option('-f, --filter <state>', 'Show only teammates in this state: running, completed, failed, stopped, or all (default: all)', 'all')
|
|
969
1048
|
.option('-s, --since <iso>', 'Cursor from a previous status call; only show updates after this timestamp (enables efficient polling)')
|
|
970
1049
|
.option('--agent-id <id>', 'Show only this one teammate (by UUID or UUID prefix)')
|
|
1050
|
+
.option('-v, --verbose', 'Emit the full per-teammate detail (prompt, all file paths, all messages). Default is a compact summary.')
|
|
971
1051
|
.option('--json', 'Output machine-readable JSON')
|
|
972
1052
|
.action(async (team, opts) => {
|
|
973
1053
|
const filter = opts.filter;
|
|
@@ -984,8 +1064,15 @@ export function registerTeamsCommands(program) {
|
|
|
984
1064
|
const agents = opts.agentId
|
|
985
1065
|
? result.agents.filter((a) => a.agent_id.startsWith(opts.agentId))
|
|
986
1066
|
: result.agents;
|
|
1067
|
+
const filtered = { ...result, agents };
|
|
987
1068
|
if (isJsonMode(opts)) {
|
|
988
|
-
|
|
1069
|
+
// JSON output also respects --verbose. Default = compact summary
|
|
1070
|
+
// shape; --verbose = full AgentStatusDetail. Same toggle covers
|
|
1071
|
+
// both text and JSON so MCP-style consumers can opt into detail.
|
|
1072
|
+
const payload = opts.verbose
|
|
1073
|
+
? filtered
|
|
1074
|
+
: toTaskStatusSummary(filtered);
|
|
1075
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
989
1076
|
return;
|
|
990
1077
|
}
|
|
991
1078
|
const exists = await teamExists(team);
|
|
@@ -993,7 +1080,12 @@ export function registerTeamsCommands(program) {
|
|
|
993
1080
|
console.log(chalk.yellow(`No team called '${team}'. Create it with: agents teams create ${team}`));
|
|
994
1081
|
return;
|
|
995
1082
|
}
|
|
996
|
-
|
|
1083
|
+
if (opts.verbose) {
|
|
1084
|
+
await printTeamStatus(team, filtered);
|
|
1085
|
+
}
|
|
1086
|
+
else {
|
|
1087
|
+
printTeamSummary(team, toTaskStatusSummary(filtered));
|
|
1088
|
+
}
|
|
997
1089
|
}
|
|
998
1090
|
catch (err) {
|
|
999
1091
|
die(`Could not check on team ${team}: ${err.message}`);
|
|
@@ -1308,14 +1400,19 @@ export function registerTeamsCommands(program) {
|
|
|
1308
1400
|
teams
|
|
1309
1401
|
.command('logs [teammate]')
|
|
1310
1402
|
.alias('log')
|
|
1311
|
-
.description("Read a teammate's raw log output. Accepts name, UUID, or UUID prefix.")
|
|
1403
|
+
.description("Read a teammate's raw log output. Accepts positional name, --teammate <name>, UUID, or UUID prefix.")
|
|
1312
1404
|
.option('-n, --tail <n>', 'Show only the last N lines instead of the full log')
|
|
1313
1405
|
.option('--team <team>', 'Disambiguate when the same name appears in multiple teams')
|
|
1406
|
+
.option('--teammate <name>', 'Teammate name (alias for the positional arg; useful for scripts)')
|
|
1314
1407
|
.action(async (ref, opts) => {
|
|
1315
1408
|
const base = await getAgentsDir();
|
|
1316
|
-
//
|
|
1409
|
+
// Resolve teammate identity. Precedence:
|
|
1410
|
+
// 1. positional `[teammate]` arg (back-compat, most common)
|
|
1411
|
+
// 2. --teammate <name> flag (script-friendly alias)
|
|
1412
|
+
// 3. interactive picker (TTY only)
|
|
1413
|
+
const teammateRef = ref ?? opts.teammate;
|
|
1317
1414
|
let agentId;
|
|
1318
|
-
if (!
|
|
1415
|
+
if (!teammateRef) {
|
|
1319
1416
|
const mgr = mkManager();
|
|
1320
1417
|
const picked = await pickTeammateOr(mgr, 'agents teams logs');
|
|
1321
1418
|
if (!picked)
|
|
@@ -1323,13 +1420,13 @@ export function registerTeamsCommands(program) {
|
|
|
1323
1420
|
agentId = picked.agentId;
|
|
1324
1421
|
}
|
|
1325
1422
|
else {
|
|
1326
|
-
const resolved = await resolveTeammateAcrossTeams(base,
|
|
1423
|
+
const resolved = await resolveTeammateAcrossTeams(base, teammateRef, opts.team);
|
|
1327
1424
|
if (resolved.kind === 'none') {
|
|
1328
|
-
die(`No notes on record for teammate '${
|
|
1425
|
+
die(`No notes on record for teammate '${teammateRef}'`, 2);
|
|
1329
1426
|
}
|
|
1330
1427
|
if (resolved.kind === 'ambiguous') {
|
|
1331
1428
|
const hints = resolved.candidates.map((c) => `${c.team}/${c.display}`).join(', ');
|
|
1332
|
-
die(`'${
|
|
1429
|
+
die(`'${teammateRef}' matches multiple teammates: ${hints}.\n` +
|
|
1333
1430
|
` Narrow it with --team <team>, or pass a UUID prefix.`, 2);
|
|
1334
1431
|
}
|
|
1335
1432
|
agentId = resolved.agentId;
|
|
@@ -1346,7 +1443,7 @@ export function registerTeamsCommands(program) {
|
|
|
1346
1443
|
process.stdout.write(lines.slice(-n).join('\n'));
|
|
1347
1444
|
}
|
|
1348
1445
|
catch {
|
|
1349
|
-
die(`No notes on record for teammate '${
|
|
1446
|
+
die(`No notes on record for teammate '${teammateRef ?? agentId}' (looked in ${logPath})`, 2);
|
|
1350
1447
|
}
|
|
1351
1448
|
});
|
|
1352
1449
|
// doctor
|
package/dist/commands/view.d.ts
CHANGED
|
@@ -9,6 +9,17 @@
|
|
|
9
9
|
import type { Command } from 'commander';
|
|
10
10
|
import type { AgentId } from '../lib/types.js';
|
|
11
11
|
import { type ProfileSummary } from '../lib/profiles.js';
|
|
12
|
+
/** Per-section filter flags. When any are true, only those sections render. */
|
|
13
|
+
export interface ViewSectionFilter {
|
|
14
|
+
commands?: boolean;
|
|
15
|
+
skills?: boolean;
|
|
16
|
+
mcp?: boolean;
|
|
17
|
+
workflows?: boolean;
|
|
18
|
+
plugins?: boolean;
|
|
19
|
+
rules?: boolean;
|
|
20
|
+
hooks?: boolean;
|
|
21
|
+
promptcuts?: boolean;
|
|
22
|
+
}
|
|
12
23
|
/** Machine-readable entry for a single installed version. */
|
|
13
24
|
export interface ViewJsonVersion {
|
|
14
25
|
version: string;
|
|
@@ -54,6 +65,6 @@ export declare function viewAction(agentArg?: string, options?: {
|
|
|
54
65
|
prune?: boolean;
|
|
55
66
|
yes?: boolean;
|
|
56
67
|
dryRun?: boolean;
|
|
57
|
-
}): Promise<void>;
|
|
68
|
+
} & ViewSectionFilter): Promise<void>;
|
|
58
69
|
/** Register the `agents view` command. */
|
|
59
70
|
export declare function registerViewCommand(program: Command): void;
|
package/dist/commands/view.js
CHANGED
|
@@ -15,6 +15,7 @@ import { isGitRepo, getGitSyncStatus } from '../lib/git.js';
|
|
|
15
15
|
import { getCentralRulesFileName } from '../lib/rules/rules.js';
|
|
16
16
|
import { composeRulesFromState } from '../lib/rules/compose.js';
|
|
17
17
|
import { getConfiguredRunStrategy } from '../lib/rotate.js';
|
|
18
|
+
import { resolveRunDefaults } from '../lib/run-defaults.js';
|
|
18
19
|
import { listProfiles, profileSummary } from '../lib/profiles.js';
|
|
19
20
|
import { loadManifest, isStale } from '../lib/staleness/index.js';
|
|
20
21
|
import { confirm } from '@inquirer/prompts';
|
|
@@ -113,6 +114,29 @@ function getProjectVersionFromCwd(agent) {
|
|
|
113
114
|
return null;
|
|
114
115
|
}
|
|
115
116
|
}
|
|
117
|
+
const SECTION_KEYS = ['commands', 'skills', 'mcp', 'workflows', 'plugins', 'rules', 'hooks', 'promptcuts'];
|
|
118
|
+
/**
|
|
119
|
+
* Decide whether a section should render given the filter. If no flags are set,
|
|
120
|
+
* everything renders (current behavior). If any flag is set, only those sections
|
|
121
|
+
* render — flags are additive.
|
|
122
|
+
*/
|
|
123
|
+
function shouldRenderSection(key, filter) {
|
|
124
|
+
if (!filter)
|
|
125
|
+
return true;
|
|
126
|
+
const anySet = SECTION_KEYS.some((k) => filter[k]);
|
|
127
|
+
if (!anySet)
|
|
128
|
+
return true;
|
|
129
|
+
return filter[key] === true;
|
|
130
|
+
}
|
|
131
|
+
/** Trim a description to a column-friendly snippet. Strips newlines, collapses whitespace. */
|
|
132
|
+
function summarizeDescription(desc, maxLen = 80) {
|
|
133
|
+
if (!desc)
|
|
134
|
+
return '';
|
|
135
|
+
const cleaned = desc.replace(/\s+/g, ' ').trim();
|
|
136
|
+
if (cleaned.length <= maxLen)
|
|
137
|
+
return cleaned;
|
|
138
|
+
return cleaned.slice(0, maxLen - 1).trimEnd() + '…';
|
|
139
|
+
}
|
|
116
140
|
function getProfileSummaries(filterAgentId) {
|
|
117
141
|
return listProfiles()
|
|
118
142
|
.filter((profile) => !filterAgentId || profile.host.agent === filterAgentId)
|
|
@@ -338,6 +362,12 @@ async function showInstalledVersions(filterAgentId) {
|
|
|
338
362
|
// Otherwise it reflects install time (misleading "just now" for fresh installs).
|
|
339
363
|
const activeStr = vInfo && hasEmail ? formatLastActive(vInfo.lastActive) : '';
|
|
340
364
|
const hasActive = activeStr.length > 0;
|
|
365
|
+
const runDefaults = resolveRunDefaults(agentId, version);
|
|
366
|
+
const runDefaultBits = [];
|
|
367
|
+
if (runDefaults.mode)
|
|
368
|
+
runDefaultBits.push(`mode:${runDefaults.mode}`);
|
|
369
|
+
if (runDefaults.model)
|
|
370
|
+
runDefaultBits.push(`model:${runDefaults.model}`);
|
|
341
371
|
if (!hasEmail && !hasUsage) {
|
|
342
372
|
// Installed but never signed in
|
|
343
373
|
parts.push(chalk.gray('(not signed in — run ' + agent.cliCommand + ' to log in)'));
|
|
@@ -359,6 +389,9 @@ async function showInstalledVersions(filterAgentId) {
|
|
|
359
389
|
if (hasActive)
|
|
360
390
|
parts.push(activeStr);
|
|
361
391
|
}
|
|
392
|
+
if (runDefaultBits.length > 0) {
|
|
393
|
+
parts.push(chalk.gray(`run ${runDefaultBits.join(' ')}`));
|
|
394
|
+
}
|
|
362
395
|
console.log(parts.join(' '));
|
|
363
396
|
if (showPaths) {
|
|
364
397
|
const versionDir = getVersionDir(agentId, version);
|
|
@@ -568,7 +601,7 @@ async function showInstalledVersions(filterAgentId) {
|
|
|
568
601
|
* Show detailed resources for a specific agent version.
|
|
569
602
|
* Called when: `agents view claude@2.0.65` or `agents view claude@default`
|
|
570
603
|
*/
|
|
571
|
-
async function showAgentResources(agentId, requestedVersion) {
|
|
604
|
+
async function showAgentResources(agentId, requestedVersion, filter) {
|
|
572
605
|
const spinner = ora({ text: 'Loading...', isSilent: !process.stdout.isTTY }).start();
|
|
573
606
|
const cwd = process.cwd();
|
|
574
607
|
const agentsDir = getAgentsDir();
|
|
@@ -660,6 +693,8 @@ async function showAgentResources(agentId, requestedVersion) {
|
|
|
660
693
|
})),
|
|
661
694
|
skills: resources.skills.map(r => ({
|
|
662
695
|
...r,
|
|
696
|
+
// ruleCount of 0 is noise — every skill has 0 unless it ships subrules, which is rare.
|
|
697
|
+
ruleCount: r.ruleCount && r.ruleCount > 0 ? r.ruleCount : undefined,
|
|
663
698
|
syncState: r.scope === 'project' ? undefined : getSyncState(r.name, 'skills', skillsSync),
|
|
664
699
|
})),
|
|
665
700
|
skillErrors: resources.skillErrors,
|
|
@@ -705,7 +740,9 @@ async function showAgentResources(agentId, requestedVersion) {
|
|
|
705
740
|
: chalk.gray('[system]');
|
|
706
741
|
display += ` ${sourceTag}`;
|
|
707
742
|
const syncStr = r.syncState ? chalk.gray(` [${r.syncState}]`) : '';
|
|
708
|
-
|
|
743
|
+
const descSnippet = summarizeDescription(r.description);
|
|
744
|
+
const descStr = descSnippet ? chalk.gray(` ${descSnippet}`) : '';
|
|
745
|
+
console.log(` ${display}${syncStr}${descStr}`);
|
|
709
746
|
}
|
|
710
747
|
}
|
|
711
748
|
// Render promptcuts (cross-agent, not per-version). Shortcuts are layered
|
|
@@ -722,43 +759,53 @@ async function showAgentResources(agentId, requestedVersion) {
|
|
|
722
759
|
const label = `${count} shortcut${count === 1 ? '' : 's'}`;
|
|
723
760
|
console.log(` ${chalk.green(label).padEnd(24)} ${chalk.gray(formatPath(getEffectivePromptcutsPath(), cwd))}`);
|
|
724
761
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
home
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
console.log();
|
|
742
|
-
|
|
743
|
-
|
|
762
|
+
const anyFilterSet = filter && SECTION_KEYS.some((k) => filter[k]);
|
|
763
|
+
// 1. Agent CLI info — skip the header entirely when the user asked for a
|
|
764
|
+
// specific section. They want "nothing more or less."
|
|
765
|
+
if (!anyFilterSet) {
|
|
766
|
+
console.log(chalk.bold('Agent CLIs\n'));
|
|
767
|
+
const accountInfo = await getAccountInfo(agentId, home);
|
|
768
|
+
const usageInfo = await getUsageInfoForIdentity({
|
|
769
|
+
agentId,
|
|
770
|
+
home,
|
|
771
|
+
cliVersion: version,
|
|
772
|
+
info: accountInfo,
|
|
773
|
+
});
|
|
774
|
+
const emailStr = accountInfo.email ? chalk.cyan(` ${accountInfo.email}`) : '';
|
|
775
|
+
const status = chalk.green(version);
|
|
776
|
+
const usageStr = formatUsageSummary(accountInfo.plan, null);
|
|
777
|
+
const usagePart = usageStr ? ` ${usageStr}` : '';
|
|
778
|
+
console.log(` ${colorAgent(agentId)(AGENTS[agentId].name.padEnd(14))} ${status}${emailStr}${usagePart}`);
|
|
779
|
+
const usageLines = formatUsageSection(usageInfo);
|
|
780
|
+
if (usageLines.length > 0) {
|
|
781
|
+
console.log();
|
|
782
|
+
for (const line of usageLines) {
|
|
783
|
+
console.log(line);
|
|
784
|
+
}
|
|
744
785
|
}
|
|
745
786
|
}
|
|
746
787
|
// 2. Resources
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
if (
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
console.log(
|
|
788
|
+
if (shouldRenderSection('commands', filter)) {
|
|
789
|
+
renderSection('Commands', agentData.commands);
|
|
790
|
+
}
|
|
791
|
+
if (shouldRenderSection('skills', filter)) {
|
|
792
|
+
renderSection('Skills', agentData.skills);
|
|
793
|
+
// Show skill parse errors only when skills section is visible
|
|
794
|
+
if (agentData.skillErrors.length > 0) {
|
|
795
|
+
console.log(`\n ${chalk.red('Skill Errors')}:`);
|
|
796
|
+
for (const err of agentData.skillErrors) {
|
|
797
|
+
console.log(` ${chalk.red(err.name.padEnd(20))} ${chalk.gray(err.error)}`);
|
|
798
|
+
console.log(` ${chalk.gray(formatPath(err.path, cwd))}`);
|
|
799
|
+
}
|
|
755
800
|
}
|
|
756
801
|
}
|
|
757
|
-
|
|
758
|
-
|
|
802
|
+
if (shouldRenderSection('mcp', filter)) {
|
|
803
|
+
renderSection('MCP Servers', agentData.mcp);
|
|
804
|
+
}
|
|
805
|
+
if (shouldRenderSection('workflows', filter) && isCapable(agentId, 'workflows')) {
|
|
759
806
|
renderSection('Workflows', agentData.workflows);
|
|
760
807
|
}
|
|
761
|
-
if (isCapable(agentId, 'plugins')) {
|
|
808
|
+
if (shouldRenderSection('plugins', filter) && isCapable(agentId, 'plugins')) {
|
|
762
809
|
const plugins = discoverPlugins().filter(p => pluginSupportsAgent(p, agentId));
|
|
763
810
|
console.log(chalk.bold('\nPlugins\n'));
|
|
764
811
|
if (plugins.length === 0) {
|
|
@@ -847,11 +894,18 @@ async function showAgentResources(agentId, requestedVersion) {
|
|
|
847
894
|
}
|
|
848
895
|
}
|
|
849
896
|
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
897
|
+
if (shouldRenderSection('rules', filter)) {
|
|
898
|
+
renderRulesSection();
|
|
899
|
+
}
|
|
900
|
+
if (shouldRenderSection('hooks', filter)) {
|
|
901
|
+
renderSection('Hooks', agentData.hooks);
|
|
902
|
+
}
|
|
903
|
+
if (shouldRenderSection('promptcuts', filter)) {
|
|
904
|
+
renderPromptcuts();
|
|
905
|
+
}
|
|
906
|
+
// Show legend at the end if git repo exists and we showed all sections.
|
|
907
|
+
// Filtered single-section views skip it — noise for promptcuts or plugins.
|
|
908
|
+
if (hasGitRepo && !anyFilterSet) {
|
|
855
909
|
console.log();
|
|
856
910
|
console.log(chalk.gray('Legend:'), chalk.green('Tracked'), chalk.blue('Local-only'), chalk.yellow('Modified'), chalk.red('Deleted'));
|
|
857
911
|
}
|
|
@@ -1131,6 +1185,17 @@ export async function viewAction(agentArg, options) {
|
|
|
1131
1185
|
const prune = options?.prune === true;
|
|
1132
1186
|
const yes = options?.yes === true;
|
|
1133
1187
|
const dryRun = options?.dryRun === true;
|
|
1188
|
+
const filter = {
|
|
1189
|
+
commands: options?.commands,
|
|
1190
|
+
skills: options?.skills,
|
|
1191
|
+
mcp: options?.mcp,
|
|
1192
|
+
workflows: options?.workflows,
|
|
1193
|
+
plugins: options?.plugins,
|
|
1194
|
+
rules: options?.rules,
|
|
1195
|
+
hooks: options?.hooks,
|
|
1196
|
+
promptcuts: options?.promptcuts,
|
|
1197
|
+
};
|
|
1198
|
+
const filterIsSet = SECTION_KEYS.some((k) => filter[k]);
|
|
1134
1199
|
if (!agentArg) {
|
|
1135
1200
|
if (prune) {
|
|
1136
1201
|
await pruneDuplicates(undefined, yes, dryRun);
|
|
@@ -1180,7 +1245,12 @@ export async function viewAction(agentArg, options) {
|
|
|
1180
1245
|
}
|
|
1181
1246
|
if (requestedVersion) {
|
|
1182
1247
|
// Specific version requested: show detailed resources
|
|
1183
|
-
await showAgentResources(agentId, requestedVersion);
|
|
1248
|
+
await showAgentResources(agentId, requestedVersion, filter);
|
|
1249
|
+
}
|
|
1250
|
+
else if (filterIsSet) {
|
|
1251
|
+
// `agents view claude --skills` → fall through to detail view on default.
|
|
1252
|
+
// Section filters only make sense for the per-version detail view.
|
|
1253
|
+
await showAgentResources(agentId, 'default', filter);
|
|
1184
1254
|
}
|
|
1185
1255
|
else {
|
|
1186
1256
|
// Just agent name: show versions for that agent
|
|
@@ -1196,6 +1266,14 @@ export function registerViewCommand(program) {
|
|
|
1196
1266
|
.option('--prune', 'Remove older installed versions that share an account with a newer installed version. Skips the global default.')
|
|
1197
1267
|
.option('--dry-run', 'With --prune, show duplicate versions without deleting')
|
|
1198
1268
|
.option('-y, --yes', 'Skip the prune confirmation prompt.')
|
|
1269
|
+
.option('--commands', 'Show only commands in the detail view.')
|
|
1270
|
+
.option('--skills', 'Show only skills in the detail view.')
|
|
1271
|
+
.option('--mcp', 'Show only MCP servers in the detail view.')
|
|
1272
|
+
.option('--workflows', 'Show only workflows in the detail view.')
|
|
1273
|
+
.option('--plugins', 'Show only plugins in the detail view.')
|
|
1274
|
+
.option('--rules', 'Show only rules in the detail view.')
|
|
1275
|
+
.option('--hooks', 'Show only hooks in the detail view.')
|
|
1276
|
+
.option('--promptcuts', 'Show only promptcuts in the detail view.')
|
|
1199
1277
|
.addHelpText('after', `
|
|
1200
1278
|
Examples:
|
|
1201
1279
|
# Show all installed agents with versions, accounts, and usage
|
|
@@ -1216,6 +1294,11 @@ Examples:
|
|
|
1216
1294
|
agents view claude --prune
|
|
1217
1295
|
agents view claude --prune -y
|
|
1218
1296
|
|
|
1297
|
+
# Filter the detail view to a single section (combinable)
|
|
1298
|
+
agents view claude@default --skills
|
|
1299
|
+
agents view claude@default --plugins --workflows
|
|
1300
|
+
agents view claude --commands # implicitly the default version
|
|
1301
|
+
|
|
1219
1302
|
When to use:
|
|
1220
1303
|
- Checking which agents are installed and what their default versions are
|
|
1221
1304
|
- Seeing which account each version is logged into (useful for multi-account setups)
|