@phnx-labs/agents-cli 1.20.4 → 1.20.6
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 +19 -0
- package/README.md +49 -18
- package/dist/commands/browser.js +31 -4
- package/dist/commands/cli.js +1 -1
- package/dist/commands/cloud.js +1 -1
- package/dist/commands/commands.js +2 -0
- package/dist/commands/computer.js +10 -2
- package/dist/commands/defaults.d.ts +7 -0
- package/dist/commands/defaults.js +89 -0
- package/dist/commands/doctor.js +1 -1
- package/dist/commands/exec.js +73 -19
- package/dist/commands/hooks.js +6 -6
- package/dist/commands/inspect.d.ts +26 -0
- package/dist/commands/inspect.js +590 -0
- package/dist/commands/mcp.js +17 -16
- package/dist/commands/models.js +1 -1
- package/dist/commands/packages.js +6 -4
- package/dist/commands/permissions.js +13 -12
- package/dist/commands/plugins.d.ts +13 -0
- package/dist/commands/plugins.js +100 -11
- package/dist/commands/prune.js +3 -2
- package/dist/commands/pull.d.ts +12 -5
- package/dist/commands/pull.js +26 -422
- package/dist/commands/push.d.ts +14 -0
- package/dist/commands/push.js +30 -0
- package/dist/commands/repo.d.ts +1 -1
- package/dist/commands/repo.js +155 -112
- package/dist/commands/resource-view.d.ts +2 -0
- package/dist/commands/resource-view.js +12 -3
- package/dist/commands/routines.js +32 -7
- package/dist/commands/rules.js +4 -4
- package/dist/commands/secrets.js +46 -9
- package/dist/commands/sessions.js +1 -0
- package/dist/commands/setup.d.ts +3 -3
- package/dist/commands/setup.js +17 -17
- package/dist/commands/skills.js +6 -5
- package/dist/commands/subagents.js +5 -4
- package/dist/commands/sync.d.ts +18 -5
- package/dist/commands/sync.js +251 -65
- package/dist/commands/teams.js +109 -11
- package/dist/commands/tmux.d.ts +25 -0
- package/dist/commands/tmux.js +415 -0
- package/dist/commands/trash.d.ts +2 -2
- package/dist/commands/trash.js +1 -1
- package/dist/commands/versions.js +2 -2
- package/dist/commands/view.d.ts +12 -1
- package/dist/commands/view.js +128 -40
- package/dist/commands/workflows.js +4 -3
- package/dist/commands/worktree.d.ts +4 -5
- package/dist/commands/worktree.js +4 -4
- package/dist/index.js +106 -41
- package/dist/lib/agents.d.ts +23 -10
- package/dist/lib/agents.js +88 -25
- package/dist/lib/auto-pull-worker.d.ts +1 -1
- package/dist/lib/auto-pull-worker.js +2 -2
- package/dist/lib/auto-pull.d.ts +1 -1
- package/dist/lib/auto-pull.js +1 -1
- package/dist/lib/beta.d.ts +1 -1
- package/dist/lib/beta.js +1 -1
- package/dist/lib/browser/chrome.d.ts +10 -0
- package/dist/lib/browser/chrome.js +84 -3
- package/dist/lib/capabilities.js +2 -0
- package/dist/lib/commands.d.ts +28 -1
- package/dist/lib/commands.js +125 -20
- package/dist/lib/doctor-diff.js +2 -2
- package/dist/lib/exec.d.ts +14 -0
- package/dist/lib/exec.js +59 -5
- package/dist/lib/fuzzy.d.ts +12 -2
- package/dist/lib/fuzzy.js +29 -4
- package/dist/lib/git.js +8 -1
- package/dist/lib/hooks.d.ts +2 -2
- package/dist/lib/hooks.js +97 -10
- package/dist/lib/mcp.js +32 -2
- package/dist/lib/migrate.d.ts +51 -0
- package/dist/lib/migrate.js +233 -5
- package/dist/lib/models.js +62 -15
- package/dist/lib/permissions.d.ts +59 -2
- package/dist/lib/permissions.js +299 -7
- package/dist/lib/plugin-marketplace.d.ts +98 -40
- package/dist/lib/plugin-marketplace.js +196 -93
- package/dist/lib/plugins.d.ts +21 -4
- package/dist/lib/plugins.js +130 -49
- package/dist/lib/profiles-presets.js +12 -12
- package/dist/lib/project-launch.d.ts +70 -0
- package/dist/lib/project-launch.js +404 -0
- package/dist/lib/pty-client.js +1 -1
- package/dist/lib/pty-server.d.ts +1 -1
- package/dist/lib/pty-server.js +8 -5
- package/dist/lib/refresh.d.ts +26 -0
- package/dist/lib/refresh.js +315 -0
- package/dist/lib/resource-patterns.d.ts +1 -1
- package/dist/lib/resource-patterns.js +1 -1
- package/dist/lib/resources/commands.js +2 -2
- package/dist/lib/resources/hooks.d.ts +1 -1
- package/dist/lib/resources/hooks.js +1 -1
- package/dist/lib/resources/mcp.d.ts +1 -1
- package/dist/lib/resources/mcp.js +5 -6
- package/dist/lib/resources/permissions.js +5 -2
- package/dist/lib/resources/rules.js +3 -2
- package/dist/lib/resources/skills.js +3 -2
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/resources.d.ts +2 -0
- package/dist/lib/resources.js +4 -3
- package/dist/lib/rotate.d.ts +1 -1
- package/dist/lib/rotate.js +7 -19
- package/dist/lib/routines.d.ts +16 -4
- package/dist/lib/routines.js +67 -17
- package/dist/lib/rules/compile.js +22 -10
- package/dist/lib/rules/rules.js +3 -3
- 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/runner.js +16 -3
- package/dist/lib/scheduler.js +15 -1
- 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/Agents CLI.app/Contents/_CodeSignature/CodeResources +9 -1
- package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +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 +56 -9
- package/dist/lib/secrets/linux.js +327 -59
- package/dist/lib/session/db.js +15 -2
- package/dist/lib/session/discover.js +118 -3
- package/dist/lib/session/parse.js +3 -0
- package/dist/lib/session/types.d.ts +1 -1
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/shims.d.ts +18 -9
- package/dist/lib/shims.js +133 -50
- package/dist/lib/skills.d.ts +1 -1
- package/dist/lib/skills.js +10 -9
- package/dist/lib/staleness/detectors/commands.d.ts +3 -0
- package/dist/lib/staleness/detectors/commands.js +46 -0
- package/dist/lib/staleness/detectors/hooks.d.ts +3 -0
- package/dist/lib/staleness/detectors/hooks.js +44 -0
- package/dist/lib/staleness/detectors/mcp.d.ts +3 -0
- package/dist/lib/staleness/detectors/mcp.js +31 -0
- package/dist/lib/staleness/detectors/permissions.d.ts +3 -0
- package/dist/lib/staleness/detectors/permissions.js +201 -0
- package/dist/lib/staleness/detectors/plugins.d.ts +8 -0
- package/dist/lib/staleness/detectors/plugins.js +23 -0
- package/dist/lib/staleness/detectors/rules.d.ts +3 -0
- package/dist/lib/staleness/detectors/rules.js +34 -0
- package/dist/lib/staleness/detectors/skills.d.ts +3 -0
- package/dist/lib/staleness/detectors/skills.js +71 -0
- package/dist/lib/staleness/detectors/subagents.d.ts +3 -0
- package/dist/lib/staleness/detectors/subagents.js +50 -0
- package/dist/lib/staleness/detectors/types.d.ts +22 -0
- package/dist/lib/staleness/detectors/types.js +1 -0
- package/dist/lib/staleness/detectors/workflows.d.ts +3 -0
- package/dist/lib/staleness/detectors/workflows.js +28 -0
- package/dist/lib/staleness/registry.d.ts +26 -0
- package/dist/lib/staleness/registry.js +123 -0
- package/dist/lib/staleness/writers/commands.d.ts +3 -0
- package/dist/lib/staleness/writers/commands.js +111 -0
- package/dist/lib/staleness/writers/hooks.d.ts +3 -0
- package/dist/lib/staleness/writers/hooks.js +47 -0
- package/dist/lib/staleness/writers/kinds.d.ts +10 -0
- package/dist/lib/staleness/writers/kinds.js +15 -0
- package/dist/lib/staleness/writers/lazy-map.d.ts +13 -0
- package/dist/lib/staleness/writers/lazy-map.js +19 -0
- package/dist/lib/staleness/writers/mcp.d.ts +10 -0
- package/dist/lib/staleness/writers/mcp.js +19 -0
- package/dist/lib/staleness/writers/permissions.d.ts +13 -0
- package/dist/lib/staleness/writers/permissions.js +26 -0
- package/dist/lib/staleness/writers/plugins.d.ts +7 -0
- package/dist/lib/staleness/writers/plugins.js +31 -0
- package/dist/lib/staleness/writers/rules.d.ts +7 -0
- package/dist/lib/staleness/writers/rules.js +55 -0
- package/dist/lib/staleness/writers/skills.d.ts +3 -0
- package/dist/lib/staleness/writers/skills.js +81 -0
- package/dist/lib/staleness/writers/sources.d.ts +16 -0
- package/dist/lib/staleness/writers/sources.js +72 -0
- package/dist/lib/staleness/writers/subagents.d.ts +3 -0
- package/dist/lib/staleness/writers/subagents.js +53 -0
- package/dist/lib/staleness/writers/types.d.ts +36 -0
- package/dist/lib/staleness/writers/types.js +1 -0
- package/dist/lib/staleness/writers/workflows.d.ts +7 -0
- package/dist/lib/staleness/writers/workflows.js +31 -0
- package/dist/lib/state.d.ts +34 -11
- package/dist/lib/state.js +58 -13
- package/dist/lib/subagents.d.ts +0 -2
- package/dist/lib/subagents.js +6 -6
- package/dist/lib/teams/agents.js +1 -1
- package/dist/lib/teams/api.d.ts +67 -0
- package/dist/lib/teams/api.js +78 -0
- package/dist/lib/teams/parsers.d.ts +1 -1
- package/dist/lib/tmux/binary.d.ts +67 -0
- package/dist/lib/tmux/binary.js +141 -0
- package/dist/lib/tmux/index.d.ts +8 -0
- package/dist/lib/tmux/index.js +8 -0
- package/dist/lib/tmux/paths.d.ts +17 -0
- package/dist/lib/tmux/paths.js +30 -0
- package/dist/lib/tmux/session.d.ts +122 -0
- package/dist/lib/tmux/session.js +305 -0
- package/dist/lib/types.d.ts +73 -13
- package/dist/lib/types.js +1 -1
- package/dist/lib/usage.js +1 -1
- package/dist/lib/versions.d.ts +4 -4
- package/dist/lib/versions.js +138 -496
- package/dist/lib/workflows.d.ts +2 -4
- package/dist/lib/workflows.js +3 -4
- package/package.json +6 -3
- package/scripts/postinstall.js +16 -63
- package/dist/commands/status.d.ts +0 -9
- package/dist/commands/status.js +0 -25
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';
|
|
@@ -24,6 +24,7 @@ const AGENT_NAMES = {
|
|
|
24
24
|
opencode: 'OpenCode',
|
|
25
25
|
grok: 'Grok',
|
|
26
26
|
antigravity: 'Antigravity',
|
|
27
|
+
kimi: 'Kimi',
|
|
27
28
|
};
|
|
28
29
|
const VALID_AGENTS = Object.keys(AGENT_NAMES);
|
|
29
30
|
// 'full' kept as historical alias for 'skip'; normalized to 'skip' downstream.
|
|
@@ -381,6 +382,55 @@ async function resolveTeammateSessions(agents) {
|
|
|
381
382
|
}
|
|
382
383
|
return map;
|
|
383
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
|
+
}
|
|
384
434
|
// Render a team's status in the same format the `status` subcommand uses, so
|
|
385
435
|
// the interactive picker's Enter action drops the user into a familiar view.
|
|
386
436
|
async function printTeamStatus(team, result) {
|
|
@@ -408,6 +458,36 @@ async function printTeamStatus(team, result) {
|
|
|
408
458
|
console.log();
|
|
409
459
|
console.log(chalk.gray(`cursor: ${result.cursor}`));
|
|
410
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
|
+
}
|
|
411
491
|
// Classify a team into a single bucket for --status filtering.
|
|
412
492
|
// - empty: no teammates (created but nobody added yet)
|
|
413
493
|
// - waiting: only staged teammates — call `teams start` to kick them off
|
|
@@ -963,10 +1043,11 @@ export function registerTeamsCommands(program) {
|
|
|
963
1043
|
teams
|
|
964
1044
|
.command('status [team]')
|
|
965
1045
|
.aliases(['s', 'st', 'check'])
|
|
966
|
-
.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.")
|
|
967
1047
|
.option('-f, --filter <state>', 'Show only teammates in this state: running, completed, failed, stopped, or all (default: all)', 'all')
|
|
968
1048
|
.option('-s, --since <iso>', 'Cursor from a previous status call; only show updates after this timestamp (enables efficient polling)')
|
|
969
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.')
|
|
970
1051
|
.option('--json', 'Output machine-readable JSON')
|
|
971
1052
|
.action(async (team, opts) => {
|
|
972
1053
|
const filter = opts.filter;
|
|
@@ -983,8 +1064,15 @@ export function registerTeamsCommands(program) {
|
|
|
983
1064
|
const agents = opts.agentId
|
|
984
1065
|
? result.agents.filter((a) => a.agent_id.startsWith(opts.agentId))
|
|
985
1066
|
: result.agents;
|
|
1067
|
+
const filtered = { ...result, agents };
|
|
986
1068
|
if (isJsonMode(opts)) {
|
|
987
|
-
|
|
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));
|
|
988
1076
|
return;
|
|
989
1077
|
}
|
|
990
1078
|
const exists = await teamExists(team);
|
|
@@ -992,7 +1080,12 @@ export function registerTeamsCommands(program) {
|
|
|
992
1080
|
console.log(chalk.yellow(`No team called '${team}'. Create it with: agents teams create ${team}`));
|
|
993
1081
|
return;
|
|
994
1082
|
}
|
|
995
|
-
|
|
1083
|
+
if (opts.verbose) {
|
|
1084
|
+
await printTeamStatus(team, filtered);
|
|
1085
|
+
}
|
|
1086
|
+
else {
|
|
1087
|
+
printTeamSummary(team, toTaskStatusSummary(filtered));
|
|
1088
|
+
}
|
|
996
1089
|
}
|
|
997
1090
|
catch (err) {
|
|
998
1091
|
die(`Could not check on team ${team}: ${err.message}`);
|
|
@@ -1307,14 +1400,19 @@ export function registerTeamsCommands(program) {
|
|
|
1307
1400
|
teams
|
|
1308
1401
|
.command('logs [teammate]')
|
|
1309
1402
|
.alias('log')
|
|
1310
|
-
.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.")
|
|
1311
1404
|
.option('-n, --tail <n>', 'Show only the last N lines instead of the full log')
|
|
1312
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)')
|
|
1313
1407
|
.action(async (ref, opts) => {
|
|
1314
1408
|
const base = await getAgentsDir();
|
|
1315
|
-
//
|
|
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;
|
|
1316
1414
|
let agentId;
|
|
1317
|
-
if (!
|
|
1415
|
+
if (!teammateRef) {
|
|
1318
1416
|
const mgr = mkManager();
|
|
1319
1417
|
const picked = await pickTeammateOr(mgr, 'agents teams logs');
|
|
1320
1418
|
if (!picked)
|
|
@@ -1322,13 +1420,13 @@ export function registerTeamsCommands(program) {
|
|
|
1322
1420
|
agentId = picked.agentId;
|
|
1323
1421
|
}
|
|
1324
1422
|
else {
|
|
1325
|
-
const resolved = await resolveTeammateAcrossTeams(base,
|
|
1423
|
+
const resolved = await resolveTeammateAcrossTeams(base, teammateRef, opts.team);
|
|
1326
1424
|
if (resolved.kind === 'none') {
|
|
1327
|
-
die(`No notes on record for teammate '${
|
|
1425
|
+
die(`No notes on record for teammate '${teammateRef}'`, 2);
|
|
1328
1426
|
}
|
|
1329
1427
|
if (resolved.kind === 'ambiguous') {
|
|
1330
1428
|
const hints = resolved.candidates.map((c) => `${c.team}/${c.display}`).join(', ');
|
|
1331
|
-
die(`'${
|
|
1429
|
+
die(`'${teammateRef}' matches multiple teammates: ${hints}.\n` +
|
|
1332
1430
|
` Narrow it with --team <team>, or pass a UUID prefix.`, 2);
|
|
1333
1431
|
}
|
|
1334
1432
|
agentId = resolved.agentId;
|
|
@@ -1345,7 +1443,7 @@ export function registerTeamsCommands(program) {
|
|
|
1345
1443
|
process.stdout.write(lines.slice(-n).join('\n'));
|
|
1346
1444
|
}
|
|
1347
1445
|
catch {
|
|
1348
|
-
die(`No notes on record for teammate '${
|
|
1446
|
+
die(`No notes on record for teammate '${teammateRef ?? agentId}' (looked in ${logPath})`, 2);
|
|
1349
1447
|
}
|
|
1350
1448
|
});
|
|
1351
1449
|
// doctor
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agents tmux` — terminal multiplexer integration.
|
|
3
|
+
*
|
|
4
|
+
* Why this exists: the swarmify VS Code extension was hand-rolling tmux
|
|
5
|
+
* commands with brittle shell escaping (`extension/src/vscode/tmux.ts`).
|
|
6
|
+
* Lifting the orchestration into the CLI gives one source of truth that the
|
|
7
|
+
* extension, raw shells, `agents teams`, routines, and the Swarm MCP can all
|
|
8
|
+
* call into.
|
|
9
|
+
*
|
|
10
|
+
* Surface mirrors `agents pty`:
|
|
11
|
+
* agents tmux check
|
|
12
|
+
* agents tmux new <name> [--cmd ...] [--cwd DIR] [--replace] [--attach-existing] [--source S]
|
|
13
|
+
* agents tmux attach <name>
|
|
14
|
+
* agents tmux list [--json]
|
|
15
|
+
* agents tmux has <name>
|
|
16
|
+
* agents tmux split <name> <h|v> [--cmd ...] [--cwd DIR]
|
|
17
|
+
* agents tmux send <name>[:pane] <keys> [--no-enter] [--raw]
|
|
18
|
+
* agents tmux capture <name>[:pane] [--lines N] [--ansi]
|
|
19
|
+
* agents tmux info <name> [--json]
|
|
20
|
+
* agents tmux kill <name>
|
|
21
|
+
* agents tmux kill-all [--yes]
|
|
22
|
+
*/
|
|
23
|
+
import type { Command } from 'commander';
|
|
24
|
+
/** Register the `agents tmux` command tree. */
|
|
25
|
+
export declare function registerTmuxCommands(program: Command): void;
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agents tmux` — terminal multiplexer integration.
|
|
3
|
+
*
|
|
4
|
+
* Why this exists: the swarmify VS Code extension was hand-rolling tmux
|
|
5
|
+
* commands with brittle shell escaping (`extension/src/vscode/tmux.ts`).
|
|
6
|
+
* Lifting the orchestration into the CLI gives one source of truth that the
|
|
7
|
+
* extension, raw shells, `agents teams`, routines, and the Swarm MCP can all
|
|
8
|
+
* call into.
|
|
9
|
+
*
|
|
10
|
+
* Surface mirrors `agents pty`:
|
|
11
|
+
* agents tmux check
|
|
12
|
+
* agents tmux new <name> [--cmd ...] [--cwd DIR] [--replace] [--attach-existing] [--source S]
|
|
13
|
+
* agents tmux attach <name>
|
|
14
|
+
* agents tmux list [--json]
|
|
15
|
+
* agents tmux has <name>
|
|
16
|
+
* agents tmux split <name> <h|v> [--cmd ...] [--cwd DIR]
|
|
17
|
+
* agents tmux send <name>[:pane] <keys> [--no-enter] [--raw]
|
|
18
|
+
* agents tmux capture <name>[:pane] [--lines N] [--ansi]
|
|
19
|
+
* agents tmux info <name> [--json]
|
|
20
|
+
* agents tmux kill <name>
|
|
21
|
+
* agents tmux kill-all [--yes]
|
|
22
|
+
*/
|
|
23
|
+
import chalk from 'chalk';
|
|
24
|
+
import * as path from 'path';
|
|
25
|
+
import { setHelpSections } from '../lib/help.js';
|
|
26
|
+
import { assertTmuxAvailable, attachTmux, capturePane, createSession, getDefaultSocketPath, getTmuxVersion, hasSession, isTmuxInstalled, killAll, killSession, listSessions, readSessionMeta, sendKeys, splitPane, TmuxCommandError, TmuxSessionError, TmuxUnavailableError, } from '../lib/tmux/index.js';
|
|
27
|
+
/** Register the `agents tmux` command tree. */
|
|
28
|
+
export function registerTmuxCommands(program) {
|
|
29
|
+
const tmux = program
|
|
30
|
+
.command('tmux')
|
|
31
|
+
.description('Persistent terminal-multiplexer sessions for agents. Survive editor restarts, share with other tools.');
|
|
32
|
+
setHelpSections(tmux, {
|
|
33
|
+
examples: `
|
|
34
|
+
# Verify tmux is installed
|
|
35
|
+
agents tmux check
|
|
36
|
+
|
|
37
|
+
# Start a detached agent session
|
|
38
|
+
agents tmux new claude-debug --cmd "agents run claude" --cwd ~/code/myrepo
|
|
39
|
+
|
|
40
|
+
# Attach (replaces this shell with the tmux client)
|
|
41
|
+
agents tmux attach claude-debug
|
|
42
|
+
|
|
43
|
+
# Inspect remotely without attaching
|
|
44
|
+
agents tmux capture claude-debug --lines 200
|
|
45
|
+
|
|
46
|
+
# Send a slash command from a script
|
|
47
|
+
agents tmux send claude-debug "/clear"
|
|
48
|
+
|
|
49
|
+
# Clean up
|
|
50
|
+
agents tmux kill claude-debug
|
|
51
|
+
`,
|
|
52
|
+
notes: `
|
|
53
|
+
Storage:
|
|
54
|
+
Shared server socket: ~/.agents/.cache/helpers/tmux/server.sock
|
|
55
|
+
Per-session meta: ~/.agents/.cache/helpers/tmux/<name>.json
|
|
56
|
+
|
|
57
|
+
Session names accept [A-Za-z0-9_-] up to 64 characters. tmux disallows
|
|
58
|
+
'.' and ':' in names since those address windows and panes.
|
|
59
|
+
|
|
60
|
+
Existing tmux sessions you started outside of agents-cli are NOT
|
|
61
|
+
visible here unless you point them at the same socket via --socket.
|
|
62
|
+
`,
|
|
63
|
+
});
|
|
64
|
+
// ─── check ──────────────────────────────────────────────────────────────────
|
|
65
|
+
const checkCmd = tmux
|
|
66
|
+
.command('check')
|
|
67
|
+
.description('Check whether tmux is installed and report its version.')
|
|
68
|
+
.option('--json', 'Output as JSON');
|
|
69
|
+
checkCmd.action((opts) => {
|
|
70
|
+
const installed = isTmuxInstalled();
|
|
71
|
+
const version = installed ? getTmuxVersion() : null;
|
|
72
|
+
if (opts.json) {
|
|
73
|
+
console.log(JSON.stringify({ installed, version, socket: getDefaultSocketPath() }));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (installed) {
|
|
77
|
+
console.log(chalk.green('tmux:'), version ?? '(version unknown)');
|
|
78
|
+
console.log(chalk.gray(`socket: ${getDefaultSocketPath()}`));
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
console.log(chalk.yellow('tmux is not installed.'));
|
|
82
|
+
console.log(chalk.gray(process.platform === 'darwin'
|
|
83
|
+
? ' Install with: brew install tmux'
|
|
84
|
+
: ' Install with: apt install tmux (or your distro equivalent)'));
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
// ─── new ────────────────────────────────────────────────────────────────────
|
|
89
|
+
const newCmd = tmux
|
|
90
|
+
.command('new <name>')
|
|
91
|
+
.description('Start a detached tmux session running a command. The session persists until killed.')
|
|
92
|
+
.option('-c, --cmd <command>', 'Command to launch in the first pane (sh -c). Omit for an empty shell.')
|
|
93
|
+
.option('-d, --cwd <dir>', 'Working directory for the first pane')
|
|
94
|
+
.option('-w, --width <n>', 'Initial window width in columns (tmux clamps to client size on attach)')
|
|
95
|
+
.option('-h, --height <n>', 'Initial window height in rows')
|
|
96
|
+
.option('-s, --source <source>', 'Provenance label: cli|extension|teams|external', 'cli')
|
|
97
|
+
.option('--label <k=v...>', 'Free-form labels (repeatable)', collectLabel, {})
|
|
98
|
+
.option('--socket <path>', 'Use a custom socket (default: shared server)')
|
|
99
|
+
.option('--replace', 'Kill any existing session with the same name first')
|
|
100
|
+
.option('--attach-existing', 'Return the existing session if one with this name already exists')
|
|
101
|
+
.option('--json', 'Output session meta as JSON');
|
|
102
|
+
setHelpSections(newCmd, {
|
|
103
|
+
examples: `
|
|
104
|
+
# Detached agent session
|
|
105
|
+
agents tmux new claude-debug --cmd "agents run claude"
|
|
106
|
+
|
|
107
|
+
# Reuse the session if it already exists (idempotent)
|
|
108
|
+
agents tmux new pair --cmd "agents run claude" --attach-existing
|
|
109
|
+
|
|
110
|
+
# Replace a stale session
|
|
111
|
+
agents tmux new pair --cmd "agents run claude" --replace
|
|
112
|
+
`,
|
|
113
|
+
});
|
|
114
|
+
newCmd.action(async (name, opts) => {
|
|
115
|
+
await guardTmux(async () => {
|
|
116
|
+
const meta = await createSession({
|
|
117
|
+
name,
|
|
118
|
+
cmd: opts.cmd,
|
|
119
|
+
cwd: opts.cwd ? path.resolve(opts.cwd) : undefined,
|
|
120
|
+
width: opts.width ? parseInt(opts.width, 10) : undefined,
|
|
121
|
+
height: opts.height ? parseInt(opts.height, 10) : undefined,
|
|
122
|
+
source: validateSource(opts.source),
|
|
123
|
+
labels: Object.keys(opts.label).length > 0 ? opts.label : undefined,
|
|
124
|
+
socket: opts.socket,
|
|
125
|
+
replace: !!opts.replace,
|
|
126
|
+
attachExisting: !!opts.attachExisting,
|
|
127
|
+
});
|
|
128
|
+
if (opts.json) {
|
|
129
|
+
console.log(JSON.stringify(meta));
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
console.log(meta.name);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
// ─── attach ─────────────────────────────────────────────────────────────────
|
|
137
|
+
const attachCmd = tmux
|
|
138
|
+
.command('attach <name>')
|
|
139
|
+
.description('Attach to a running session. Replaces this shell with the tmux client until you detach (Ctrl-b d).')
|
|
140
|
+
.option('--socket <path>', 'Use a custom socket (default: shared server)');
|
|
141
|
+
attachCmd.action(async (name, opts) => {
|
|
142
|
+
await guardTmux(async () => {
|
|
143
|
+
const socket = opts.socket ?? getDefaultSocketPath();
|
|
144
|
+
if (!(await hasSession(name, socket))) {
|
|
145
|
+
console.error(chalk.red(`No tmux session named "${name}".`));
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
if (!process.stdout.isTTY) {
|
|
149
|
+
console.error(chalk.red('attach requires a TTY. Run this from an interactive shell.'));
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
const code = await attachTmux({ socket, args: ['attach-session', '-t', `=${name}`] });
|
|
153
|
+
process.exit(code);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
// ─── list ───────────────────────────────────────────────────────────────────
|
|
157
|
+
const listCmd = tmux
|
|
158
|
+
.command('list')
|
|
159
|
+
.alias('ls')
|
|
160
|
+
.description('List live tmux sessions on the shared server. Prunes stale meta files as a side effect.')
|
|
161
|
+
.option('--socket <path>', 'Use a custom socket (default: shared server)')
|
|
162
|
+
.option('--json', 'Output as JSON');
|
|
163
|
+
listCmd.action(async (opts) => {
|
|
164
|
+
await guardTmux(async () => {
|
|
165
|
+
const sessions = await listSessions({ socket: opts.socket });
|
|
166
|
+
if (opts.json) {
|
|
167
|
+
console.log(JSON.stringify(sessions));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (sessions.length === 0) {
|
|
171
|
+
console.log(chalk.gray('No tmux sessions.'));
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
for (const s of sessions) {
|
|
175
|
+
const age = formatAge(Date.now() / 1000 - s.createdAtTmux);
|
|
176
|
+
const cmd = s.meta?.cmd ? chalk.gray(` ${truncate(s.meta.cmd, 60)}`) : '';
|
|
177
|
+
const attached = s.attached ? chalk.green(' [attached]') : '';
|
|
178
|
+
console.log(` ${chalk.bold(s.name)} ${s.windows}w ${age}${attached}${cmd}`);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
// ─── has ────────────────────────────────────────────────────────────────────
|
|
183
|
+
tmux
|
|
184
|
+
.command('has <name>')
|
|
185
|
+
.description('Exit 0 if a session with this name exists, 1 otherwise. Useful in shell scripts.')
|
|
186
|
+
.option('--socket <path>', 'Use a custom socket (default: shared server)')
|
|
187
|
+
.action(async (name, opts) => {
|
|
188
|
+
await guardTmux(async () => {
|
|
189
|
+
const exists = await hasSession(name, opts.socket);
|
|
190
|
+
process.exit(exists ? 0 : 1);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
// ─── split ──────────────────────────────────────────────────────────────────
|
|
194
|
+
const splitCmd = tmux
|
|
195
|
+
.command('split <name> <direction>')
|
|
196
|
+
.description('Split the active pane of a session. Direction: h (left/right) or v (top/bottom).')
|
|
197
|
+
.option('-c, --cmd <command>', 'Command to launch in the new pane')
|
|
198
|
+
.option('-d, --cwd <dir>', 'Working directory for the new pane')
|
|
199
|
+
.option('--socket <path>', 'Use a custom socket (default: shared server)')
|
|
200
|
+
.option('--json', 'Output as JSON (returns new pane id like %3)');
|
|
201
|
+
setHelpSections(splitCmd, {
|
|
202
|
+
examples: `
|
|
203
|
+
# Split horizontally (panes side-by-side) and start a second agent
|
|
204
|
+
agents tmux split team h --cmd "agents run codex"
|
|
205
|
+
|
|
206
|
+
# Split vertically (panes stacked) — empty shell
|
|
207
|
+
agents tmux split team v
|
|
208
|
+
`,
|
|
209
|
+
});
|
|
210
|
+
splitCmd.action(async (name, direction, opts) => {
|
|
211
|
+
await guardTmux(async () => {
|
|
212
|
+
if (direction !== 'h' && direction !== 'v') {
|
|
213
|
+
console.error(chalk.red(`Invalid direction "${direction}". Use h or v.`));
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
const paneId = await splitPane({
|
|
217
|
+
name,
|
|
218
|
+
direction,
|
|
219
|
+
cmd: opts.cmd,
|
|
220
|
+
cwd: opts.cwd ? path.resolve(opts.cwd) : undefined,
|
|
221
|
+
socket: opts.socket,
|
|
222
|
+
});
|
|
223
|
+
if (opts.json) {
|
|
224
|
+
console.log(JSON.stringify({ paneId }));
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
console.log(paneId);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
// ─── send ───────────────────────────────────────────────────────────────────
|
|
232
|
+
const sendCmd = tmux
|
|
233
|
+
.command('send <target> <keys>')
|
|
234
|
+
.description('Send keystrokes to a session. Target is "name" or "name:pane" (e.g. team:%2 or team:1).')
|
|
235
|
+
.option('--no-enter', 'Do not append Enter after the keys')
|
|
236
|
+
.option('--raw', 'Send literally (-l) without tmux key-name interpretation (C-c, Enter, etc.)')
|
|
237
|
+
.option('--socket <path>', 'Use a custom socket (default: shared server)');
|
|
238
|
+
setHelpSections(sendCmd, {
|
|
239
|
+
examples: `
|
|
240
|
+
# Type a command into the session and press Enter
|
|
241
|
+
agents tmux send team "echo hello"
|
|
242
|
+
|
|
243
|
+
# Send Ctrl-C to interrupt
|
|
244
|
+
agents tmux send team "C-c"
|
|
245
|
+
|
|
246
|
+
# Send literal text (no key-name interpretation)
|
|
247
|
+
agents tmux send team "C-c" --raw
|
|
248
|
+
|
|
249
|
+
# Target a specific pane
|
|
250
|
+
agents tmux send team:%2 "ls -la"
|
|
251
|
+
`,
|
|
252
|
+
});
|
|
253
|
+
sendCmd.action(async (target, keys, opts) => {
|
|
254
|
+
await guardTmux(async () => {
|
|
255
|
+
const { name, pane } = splitTarget(target);
|
|
256
|
+
await sendKeys({
|
|
257
|
+
name,
|
|
258
|
+
pane,
|
|
259
|
+
keys,
|
|
260
|
+
noEnter: !opts.enter,
|
|
261
|
+
raw: !!opts.raw,
|
|
262
|
+
socket: opts.socket,
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
// ─── capture ────────────────────────────────────────────────────────────────
|
|
267
|
+
const captureCmd = tmux
|
|
268
|
+
.command('capture <target>')
|
|
269
|
+
.description('Print the contents of a pane. Target is "name" or "name:pane".')
|
|
270
|
+
.option('-l, --lines <n>', 'Include this many extra history lines above the visible screen')
|
|
271
|
+
.option('--ansi', 'Keep ANSI escape codes (default strips them)')
|
|
272
|
+
.option('--socket <path>', 'Use a custom socket (default: shared server)');
|
|
273
|
+
setHelpSections(captureCmd, {
|
|
274
|
+
examples: `
|
|
275
|
+
# See current screen
|
|
276
|
+
agents tmux capture team
|
|
277
|
+
|
|
278
|
+
# Include the last 500 history lines
|
|
279
|
+
agents tmux capture team --lines 500
|
|
280
|
+
|
|
281
|
+
# Target a specific pane
|
|
282
|
+
agents tmux capture team:%3
|
|
283
|
+
`,
|
|
284
|
+
});
|
|
285
|
+
captureCmd.action(async (target, opts) => {
|
|
286
|
+
await guardTmux(async () => {
|
|
287
|
+
const { name, pane } = splitTarget(target);
|
|
288
|
+
const text = await capturePane({
|
|
289
|
+
name,
|
|
290
|
+
pane,
|
|
291
|
+
lines: opts.lines ? parseInt(opts.lines, 10) : undefined,
|
|
292
|
+
ansi: !!opts.ansi,
|
|
293
|
+
socket: opts.socket,
|
|
294
|
+
});
|
|
295
|
+
process.stdout.write(text);
|
|
296
|
+
if (!text.endsWith('\n'))
|
|
297
|
+
process.stdout.write('\n');
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
// ─── info ───────────────────────────────────────────────────────────────────
|
|
301
|
+
const infoCmd = tmux
|
|
302
|
+
.command('info <name>')
|
|
303
|
+
.description('Show provenance for a session (cmd, cwd, created_at, source, labels).')
|
|
304
|
+
.option('--json', 'Output as JSON');
|
|
305
|
+
infoCmd.action(async (name, opts) => {
|
|
306
|
+
const meta = readSessionMeta(name);
|
|
307
|
+
if (!meta) {
|
|
308
|
+
console.error(chalk.yellow(`No meta for "${name}" (session may exist but wasn't created via agents tmux).`));
|
|
309
|
+
process.exit(1);
|
|
310
|
+
}
|
|
311
|
+
if (opts.json) {
|
|
312
|
+
console.log(JSON.stringify(meta, null, 2));
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
printMeta(meta);
|
|
316
|
+
});
|
|
317
|
+
// ─── kill ───────────────────────────────────────────────────────────────────
|
|
318
|
+
const killCmd = tmux
|
|
319
|
+
.command('kill <name>')
|
|
320
|
+
.description('Kill one tmux session. Idempotent — exits 0 even if the session was already gone.')
|
|
321
|
+
.option('--socket <path>', 'Use a custom socket (default: shared server)');
|
|
322
|
+
killCmd.action(async (name, opts) => {
|
|
323
|
+
await guardTmux(async () => {
|
|
324
|
+
const killed = await killSession(name, opts.socket);
|
|
325
|
+
if (!killed) {
|
|
326
|
+
console.log(chalk.gray(`No session "${name}" — nothing to do.`));
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
// ─── kill-all ───────────────────────────────────────────────────────────────
|
|
331
|
+
const killAllCmd = tmux
|
|
332
|
+
.command('kill-all')
|
|
333
|
+
.description('Kill every session on the shared server and remove the socket. Requires --yes.')
|
|
334
|
+
.option('--yes', 'Confirm — required, no interactive prompt')
|
|
335
|
+
.option('--socket <path>', 'Use a custom socket (default: shared server)');
|
|
336
|
+
killAllCmd.action(async (opts) => {
|
|
337
|
+
await guardTmux(async () => {
|
|
338
|
+
if (!opts.yes) {
|
|
339
|
+
console.error(chalk.red('Refusing to kill-all without --yes.'));
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
342
|
+
const n = await killAll(opts.socket);
|
|
343
|
+
console.log(chalk.gray(`Killed ${n} session${n === 1 ? '' : 's'}.`));
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
// ─── helpers ──────────────────────────────────────────────────────────────────
|
|
348
|
+
/** Wrap a command action so tmux-specific errors render cleanly instead of throwing. */
|
|
349
|
+
async function guardTmux(fn) {
|
|
350
|
+
try {
|
|
351
|
+
assertTmuxAvailable();
|
|
352
|
+
await fn();
|
|
353
|
+
}
|
|
354
|
+
catch (err) {
|
|
355
|
+
if (err instanceof TmuxUnavailableError) {
|
|
356
|
+
console.error(chalk.red(err.message));
|
|
357
|
+
process.exit(127);
|
|
358
|
+
}
|
|
359
|
+
if (err instanceof TmuxSessionError) {
|
|
360
|
+
console.error(chalk.red(err.message));
|
|
361
|
+
process.exit(1);
|
|
362
|
+
}
|
|
363
|
+
if (err instanceof TmuxCommandError) {
|
|
364
|
+
console.error(chalk.red(err.message));
|
|
365
|
+
process.exit(err.code ?? 1);
|
|
366
|
+
}
|
|
367
|
+
throw err;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/** Parse `name` or `name:pane` (pane may be `%id` or a numeric index). */
|
|
371
|
+
function splitTarget(target) {
|
|
372
|
+
const idx = target.indexOf(':');
|
|
373
|
+
if (idx === -1)
|
|
374
|
+
return { name: target };
|
|
375
|
+
return { name: target.slice(0, idx), pane: target.slice(idx + 1) || undefined };
|
|
376
|
+
}
|
|
377
|
+
function validateSource(s) {
|
|
378
|
+
if (s === 'cli' || s === 'extension' || s === 'teams' || s === 'external')
|
|
379
|
+
return s;
|
|
380
|
+
return 'cli';
|
|
381
|
+
}
|
|
382
|
+
function collectLabel(value, acc) {
|
|
383
|
+
const eq = value.indexOf('=');
|
|
384
|
+
if (eq === -1)
|
|
385
|
+
return acc;
|
|
386
|
+
acc[value.slice(0, eq)] = value.slice(eq + 1);
|
|
387
|
+
return acc;
|
|
388
|
+
}
|
|
389
|
+
function formatAge(secs) {
|
|
390
|
+
if (secs < 60)
|
|
391
|
+
return `${Math.max(0, Math.floor(secs))}s`;
|
|
392
|
+
const m = Math.floor(secs / 60);
|
|
393
|
+
if (m < 60)
|
|
394
|
+
return `${m}m`;
|
|
395
|
+
const h = Math.floor(m / 60);
|
|
396
|
+
return `${h}h ${m % 60}m`;
|
|
397
|
+
}
|
|
398
|
+
function truncate(s, n) {
|
|
399
|
+
return s.length > n ? `${s.slice(0, n - 1)}…` : s;
|
|
400
|
+
}
|
|
401
|
+
function printMeta(m) {
|
|
402
|
+
console.log(` ${chalk.bold('name')} ${m.name}`);
|
|
403
|
+
console.log(` ${chalk.bold('socket')} ${m.socket}`);
|
|
404
|
+
console.log(` ${chalk.bold('createdAt')} ${new Date(m.createdAt).toISOString()}`);
|
|
405
|
+
console.log(` ${chalk.bold('source')} ${m.source}`);
|
|
406
|
+
if (m.cwd)
|
|
407
|
+
console.log(` ${chalk.bold('cwd')} ${m.cwd}`);
|
|
408
|
+
if (m.cmd)
|
|
409
|
+
console.log(` ${chalk.bold('cmd')} ${m.cmd}`);
|
|
410
|
+
if (m.labels) {
|
|
411
|
+
for (const [k, v] of Object.entries(m.labels)) {
|
|
412
|
+
console.log(` ${chalk.bold('label')} ${k}=${v}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
package/dist/commands/trash.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `agents trash` — list and restore soft-deleted version directories.
|
|
3
3
|
*
|
|
4
|
-
* `removeVersion` moves a version dir to ~/.agents
|
|
4
|
+
* `removeVersion` moves a version dir to ~/.agents/.history/trash/versions/<agent>/<version>/<timestamp>/
|
|
5
5
|
* instead of hard-deleting. These commands let the user inspect what's there
|
|
6
6
|
* and put a soft-deleted version back. The trash never auto-expires; only
|
|
7
|
-
* `rm -rf ~/.agents
|
|
7
|
+
* `rm -rf ~/.agents/.history/trash/` removes bytes from disk.
|
|
8
8
|
*/
|
|
9
9
|
import type { Command } from 'commander';
|
|
10
10
|
export declare function registerTrashCommands(program: Command): void;
|
package/dist/commands/trash.js
CHANGED
|
@@ -143,7 +143,7 @@ export function registerTrashCommands(program) {
|
|
|
143
143
|
});
|
|
144
144
|
trash
|
|
145
145
|
.command('restore <target>')
|
|
146
|
-
.description('Restore a soft-deleted version (e.g. "claude@2.1.110") back to ~/.agents
|
|
146
|
+
.description('Restore a soft-deleted version (e.g. "claude@2.1.110") back to ~/.agents/.history/versions/')
|
|
147
147
|
.action((target) => {
|
|
148
148
|
const parsed = parseAgentVersion(target);
|
|
149
149
|
if (!parsed) {
|