@phnx-labs/agents-cli 1.20.4 → 1.20.5
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 +48 -17
- package/dist/commands/cli.js +1 -1
- package/dist/commands/cloud.js +1 -1
- package/dist/commands/commands.js +2 -0
- package/dist/commands/doctor.js +1 -1
- package/dist/commands/exec.js +52 -16
- 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 +1 -1
- package/dist/commands/sessions.js +1 -0
- package/dist/commands/setup.d.ts +3 -3
- package/dist/commands/setup.js +15 -15
- 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 +1 -0
- 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.js +9 -4
- 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 +68 -20
- package/dist/lib/agents.d.ts +19 -10
- package/dist/lib/agents.js +79 -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/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 +39 -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 +227 -1
- package/dist/lib/models.js +62 -15
- package/dist/lib/permissions.d.ts +36 -2
- package/dist/lib/permissions.js +217 -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 +65 -0
- package/dist/lib/project-launch.js +367 -0
- package/dist/lib/pty-client.js +1 -1
- package/dist/lib/pty-server.d.ts +1 -1
- package/dist/lib/pty-server.js +1 -1
- 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.js +2 -2
- package/dist/lib/rotate.d.ts +1 -1
- package/dist/lib/rotate.js +1 -1
- 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/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/linux.d.ts +44 -9
- package/dist/lib/secrets/linux.js +302 -48
- 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 +10 -9
- package/dist/lib/shims.js +101 -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/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 +58 -7
- 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 +135 -493
- package/dist/lib/workflows.d.ts +2 -4
- package/dist/lib/workflows.js +3 -4
- package/package.json +2 -2
- package/scripts/postinstall.js +16 -63
- package/dist/commands/status.d.ts +0 -9
- package/dist/commands/status.js +0 -25
|
@@ -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) {
|
|
@@ -461,11 +461,11 @@ export function registerVersionsCommands(program) {
|
|
|
461
461
|
useCmd.action(async (agentArg, versionArg, options) => {
|
|
462
462
|
try {
|
|
463
463
|
const skipPrompts = options.yes || !isInteractiveTerminal();
|
|
464
|
-
// Auto-pull ~/.agents
|
|
464
|
+
// Auto-pull ~/.agents/.system if it's a git repo with remote (silent on success)
|
|
465
465
|
const agentsDir = getAgentsDir();
|
|
466
466
|
const pullResult = await tryAutoPull(agentsDir);
|
|
467
467
|
if (pullResult.pulled) {
|
|
468
|
-
console.log(chalk.gray('Synced ~/.agents
|
|
468
|
+
console.log(chalk.gray('Synced ~/.agents/.system from remote'));
|
|
469
469
|
}
|
|
470
470
|
// Support both "claude 2.0.65" and "claude@2.0.65" formats
|
|
471
471
|
let agent;
|
package/dist/commands/view.js
CHANGED
|
@@ -8,15 +8,15 @@ import { readManifest } from '../lib/manifest.js';
|
|
|
8
8
|
import { listInstalledVersions, listInstalledVersionDirs, getGlobalDefault, getVersionHomePath, getVersionDir, resolveVersionAlias, getAvailableResources, getActuallySyncedResources, getNewResources, getProjectOnlyResources, hasNewResources, promptNewResourceSelection, syncResourcesToVersion, removeVersion, printTrashFooter, } from '../lib/versions.js';
|
|
9
9
|
import { getShimsDir, isShimsInPath, ensureVersionedAliasCurrent, removeShim, } from '../lib/shims.js';
|
|
10
10
|
import { getAgentResources } from '../lib/resources.js';
|
|
11
|
-
import {
|
|
11
|
+
import { isCapable } from '../lib/capabilities.js';
|
|
12
12
|
import { discoverPlugins, pluginSupportsAgent } from '../lib/plugins.js';
|
|
13
|
-
import { PLUGINS_CAPABLE_AGENTS } from '../lib/agents.js';
|
|
14
13
|
import { getAgentsDir, getUserAgentsDir, getEffectivePromptcutsPath, readMergedPromptcuts } from '../lib/state.js';
|
|
15
14
|
import { isGitRepo, getGitSyncStatus } from '../lib/git.js';
|
|
16
15
|
import { getCentralRulesFileName } from '../lib/rules/rules.js';
|
|
17
16
|
import { composeRulesFromState } from '../lib/rules/compose.js';
|
|
18
17
|
import { getConfiguredRunStrategy } from '../lib/rotate.js';
|
|
19
18
|
import { listProfiles, profileSummary } from '../lib/profiles.js';
|
|
19
|
+
import { loadManifest, isStale } from '../lib/staleness/index.js';
|
|
20
20
|
import { confirm } from '@inquirer/prompts';
|
|
21
21
|
import { formatPath, isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
22
22
|
/**
|
|
@@ -519,6 +519,11 @@ async function showInstalledVersions(filterAgentId) {
|
|
|
519
519
|
if (filterAgentId && versionManaged.length > 0) {
|
|
520
520
|
const defaultVersion = getGlobalDefault(filterAgentId);
|
|
521
521
|
if (defaultVersion) {
|
|
522
|
+
const manifest = loadManifest(filterAgentId, defaultVersion);
|
|
523
|
+
const cwd = process.cwd();
|
|
524
|
+
if (manifest && !isStale(manifest, filterAgentId, defaultVersion, cwd)) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
522
527
|
const available = getAvailableResources();
|
|
523
528
|
const synced = getActuallySyncedResources(filterAgentId, defaultVersion);
|
|
524
529
|
const projectOnly = getProjectOnlyResources();
|
|
@@ -750,10 +755,10 @@ async function showAgentResources(agentId, requestedVersion) {
|
|
|
750
755
|
}
|
|
751
756
|
}
|
|
752
757
|
renderSection('MCP Servers', agentData.mcp);
|
|
753
|
-
if (
|
|
758
|
+
if (isCapable(agentId, 'workflows')) {
|
|
754
759
|
renderSection('Workflows', agentData.workflows);
|
|
755
760
|
}
|
|
756
|
-
if (
|
|
761
|
+
if (isCapable(agentId, 'plugins')) {
|
|
757
762
|
const plugins = discoverPlugins().filter(p => pluginSupportsAgent(p, agentId));
|
|
758
763
|
console.log(chalk.bold('\nPlugins\n'));
|
|
759
764
|
if (plugins.length === 0) {
|
|
@@ -5,8 +5,9 @@ import * as os from 'os';
|
|
|
5
5
|
import * as path from 'path';
|
|
6
6
|
import { select, checkbox } from '@inquirer/prompts';
|
|
7
7
|
import { resolveAgentName, agentLabel } from '../lib/agents.js';
|
|
8
|
+
import { capableAgents } from '../lib/capabilities.js';
|
|
8
9
|
import { cloneRepo } from '../lib/git.js';
|
|
9
|
-
import {
|
|
10
|
+
import { discoverWorkflowsFromRepo, installWorkflowCentrally, removeWorkflow, listInstalledWorkflows, listWorkflowsForAgent, removeWorkflowFromVersion, iterWorkflowsCapableVersions, } from '../lib/workflows.js';
|
|
10
11
|
import { getVersionHomePath, getGlobalDefault, resolveVersionAlias, syncResourcesToVersion, promptAgentVersionSelection, } from '../lib/versions.js';
|
|
11
12
|
import { recordVersionResources, getUserWorkflowsDir } from '../lib/state.js';
|
|
12
13
|
import { isPromptCancelled, isInteractiveTerminal, requireInteractiveSelection, printWithPager, promptRemovalTargets, parseCommaSeparatedList, resolveAgentTargetsAutoInstalling, } from './utils.js';
|
|
@@ -210,7 +211,7 @@ Examples:
|
|
|
210
211
|
let selectedAgents;
|
|
211
212
|
let versionSelections;
|
|
212
213
|
if (options.agents) {
|
|
213
|
-
const result = await resolveAgentTargetsAutoInstalling(options.agents,
|
|
214
|
+
const result = await resolveAgentTargetsAutoInstalling(options.agents, capableAgents('workflows'), { yes: options.yes });
|
|
214
215
|
if (!result) {
|
|
215
216
|
console.log(chalk.gray('Cancelled.'));
|
|
216
217
|
return;
|
|
@@ -219,7 +220,7 @@ Examples:
|
|
|
219
220
|
versionSelections = result.versionSelections;
|
|
220
221
|
}
|
|
221
222
|
else {
|
|
222
|
-
const result = await promptAgentVersionSelection(
|
|
223
|
+
const result = await promptAgentVersionSelection(capableAgents('workflows'), {
|
|
223
224
|
skipPrompts: options.yes,
|
|
224
225
|
});
|
|
225
226
|
selectedAgents = result.selectedAgents;
|
|
@@ -10,11 +10,10 @@
|
|
|
10
10
|
* agents worktree release <terminal-id> -> removes if clean + merged
|
|
11
11
|
* agents worktree prune -> removes every clean+merged one
|
|
12
12
|
*
|
|
13
|
-
* Worktrees live at <repo>/.
|
|
14
|
-
* named
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* (skills, hooks, commands) per the agents-cli DotAgents repo layout.
|
|
13
|
+
* Worktrees live at <repo>/.agents/worktrees/<terminal-id>, on a branch
|
|
14
|
+
* named agents/<terminal-id>. The branch starts at HEAD of the parent repo.
|
|
15
|
+
* This matches the agent-system rule that keeps all coding-agent worktrees
|
|
16
|
+
* under the repo-local .agents/ state directory.
|
|
18
17
|
*/
|
|
19
18
|
import type { Command } from 'commander';
|
|
20
19
|
export declare function registerWorktreeCommands(program: Command): void;
|
|
@@ -6,8 +6,8 @@ import * as fsSync from 'fs';
|
|
|
6
6
|
import * as path from 'path';
|
|
7
7
|
import { setHelpSections } from '../lib/help.js';
|
|
8
8
|
const execFileAsync = promisify(execFile);
|
|
9
|
-
const WORKTREE_SUBDIR = path.join('.
|
|
10
|
-
const BRANCH_PREFIX = '
|
|
9
|
+
const WORKTREE_SUBDIR = path.join('.agents', 'worktrees');
|
|
10
|
+
const BRANCH_PREFIX = 'agents/';
|
|
11
11
|
function die(msg, code = 1) {
|
|
12
12
|
console.error(chalk.red(msg));
|
|
13
13
|
process.exit(code);
|
|
@@ -165,7 +165,7 @@ export function registerWorktreeCommands(program) {
|
|
|
165
165
|
agents worktree prune
|
|
166
166
|
`,
|
|
167
167
|
notes: `
|
|
168
|
-
Worktrees live at <repo>/.
|
|
168
|
+
Worktrees live at <repo>/.agents/worktrees/<terminal-id> on branch agents/<terminal-id>.
|
|
169
169
|
Use --force on 'release' to skip safety checks (DANGEROUS — discards unpushed work).
|
|
170
170
|
`,
|
|
171
171
|
});
|
|
@@ -198,7 +198,7 @@ export function registerWorktreeCommands(program) {
|
|
|
198
198
|
}
|
|
199
199
|
});
|
|
200
200
|
wt.command('prune')
|
|
201
|
-
.description('Try to release every agent worktree under .
|
|
201
|
+
.description('Try to release every agent worktree under .agents/worktrees/. Skips dirty or unpushed ones.')
|
|
202
202
|
.option('--root <path>', 'Repo root (defaults to current working directory)')
|
|
203
203
|
.option('--dry-run', 'Report what would be removed without touching anything')
|
|
204
204
|
.action(async (opts) => {
|