@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.
Files changed (190) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +48 -17
  3. package/dist/commands/cli.js +1 -1
  4. package/dist/commands/cloud.js +1 -1
  5. package/dist/commands/commands.js +2 -0
  6. package/dist/commands/doctor.js +1 -1
  7. package/dist/commands/exec.js +52 -16
  8. package/dist/commands/hooks.js +6 -6
  9. package/dist/commands/inspect.d.ts +26 -0
  10. package/dist/commands/inspect.js +590 -0
  11. package/dist/commands/mcp.js +17 -16
  12. package/dist/commands/models.js +1 -1
  13. package/dist/commands/packages.js +6 -4
  14. package/dist/commands/permissions.js +13 -12
  15. package/dist/commands/plugins.d.ts +13 -0
  16. package/dist/commands/plugins.js +100 -11
  17. package/dist/commands/prune.js +3 -2
  18. package/dist/commands/pull.d.ts +12 -5
  19. package/dist/commands/pull.js +26 -422
  20. package/dist/commands/push.d.ts +14 -0
  21. package/dist/commands/push.js +30 -0
  22. package/dist/commands/repo.d.ts +1 -1
  23. package/dist/commands/repo.js +155 -112
  24. package/dist/commands/resource-view.d.ts +2 -0
  25. package/dist/commands/resource-view.js +12 -3
  26. package/dist/commands/routines.js +32 -7
  27. package/dist/commands/rules.js +1 -1
  28. package/dist/commands/sessions.js +1 -0
  29. package/dist/commands/setup.d.ts +3 -3
  30. package/dist/commands/setup.js +15 -15
  31. package/dist/commands/skills.js +6 -5
  32. package/dist/commands/subagents.js +5 -4
  33. package/dist/commands/sync.d.ts +18 -5
  34. package/dist/commands/sync.js +251 -65
  35. package/dist/commands/teams.js +1 -0
  36. package/dist/commands/tmux.d.ts +25 -0
  37. package/dist/commands/tmux.js +415 -0
  38. package/dist/commands/trash.d.ts +2 -2
  39. package/dist/commands/trash.js +1 -1
  40. package/dist/commands/versions.js +2 -2
  41. package/dist/commands/view.js +9 -4
  42. package/dist/commands/workflows.js +4 -3
  43. package/dist/commands/worktree.d.ts +4 -5
  44. package/dist/commands/worktree.js +4 -4
  45. package/dist/index.js +68 -20
  46. package/dist/lib/agents.d.ts +19 -10
  47. package/dist/lib/agents.js +79 -25
  48. package/dist/lib/auto-pull-worker.d.ts +1 -1
  49. package/dist/lib/auto-pull-worker.js +2 -2
  50. package/dist/lib/auto-pull.d.ts +1 -1
  51. package/dist/lib/auto-pull.js +1 -1
  52. package/dist/lib/beta.d.ts +1 -1
  53. package/dist/lib/beta.js +1 -1
  54. package/dist/lib/capabilities.js +2 -0
  55. package/dist/lib/commands.d.ts +28 -1
  56. package/dist/lib/commands.js +125 -20
  57. package/dist/lib/doctor-diff.js +2 -2
  58. package/dist/lib/exec.d.ts +14 -0
  59. package/dist/lib/exec.js +39 -5
  60. package/dist/lib/fuzzy.d.ts +12 -2
  61. package/dist/lib/fuzzy.js +29 -4
  62. package/dist/lib/git.js +8 -1
  63. package/dist/lib/hooks.d.ts +2 -2
  64. package/dist/lib/hooks.js +97 -10
  65. package/dist/lib/mcp.js +32 -2
  66. package/dist/lib/migrate.d.ts +51 -0
  67. package/dist/lib/migrate.js +227 -1
  68. package/dist/lib/models.js +62 -15
  69. package/dist/lib/permissions.d.ts +36 -2
  70. package/dist/lib/permissions.js +217 -7
  71. package/dist/lib/plugin-marketplace.d.ts +98 -40
  72. package/dist/lib/plugin-marketplace.js +196 -93
  73. package/dist/lib/plugins.d.ts +21 -4
  74. package/dist/lib/plugins.js +130 -49
  75. package/dist/lib/profiles-presets.js +12 -12
  76. package/dist/lib/project-launch.d.ts +65 -0
  77. package/dist/lib/project-launch.js +367 -0
  78. package/dist/lib/pty-client.js +1 -1
  79. package/dist/lib/pty-server.d.ts +1 -1
  80. package/dist/lib/pty-server.js +1 -1
  81. package/dist/lib/refresh.d.ts +26 -0
  82. package/dist/lib/refresh.js +315 -0
  83. package/dist/lib/resource-patterns.d.ts +1 -1
  84. package/dist/lib/resource-patterns.js +1 -1
  85. package/dist/lib/resources/commands.js +2 -2
  86. package/dist/lib/resources/hooks.d.ts +1 -1
  87. package/dist/lib/resources/hooks.js +1 -1
  88. package/dist/lib/resources/mcp.d.ts +1 -1
  89. package/dist/lib/resources/mcp.js +5 -6
  90. package/dist/lib/resources/permissions.js +5 -2
  91. package/dist/lib/resources/rules.js +3 -2
  92. package/dist/lib/resources/skills.js +3 -2
  93. package/dist/lib/resources/types.d.ts +1 -1
  94. package/dist/lib/resources.js +2 -2
  95. package/dist/lib/rotate.d.ts +1 -1
  96. package/dist/lib/rotate.js +1 -1
  97. package/dist/lib/routines.d.ts +16 -4
  98. package/dist/lib/routines.js +67 -17
  99. package/dist/lib/rules/compile.js +22 -10
  100. package/dist/lib/rules/rules.js +3 -3
  101. package/dist/lib/runner.js +16 -3
  102. package/dist/lib/scheduler.js +15 -1
  103. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  104. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  105. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +9 -1
  106. package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
  107. package/dist/lib/secrets/linux.d.ts +44 -9
  108. package/dist/lib/secrets/linux.js +302 -48
  109. package/dist/lib/session/db.js +15 -2
  110. package/dist/lib/session/discover.js +118 -3
  111. package/dist/lib/session/parse.js +3 -0
  112. package/dist/lib/session/types.d.ts +1 -1
  113. package/dist/lib/session/types.js +1 -1
  114. package/dist/lib/shims.d.ts +10 -9
  115. package/dist/lib/shims.js +101 -50
  116. package/dist/lib/skills.d.ts +1 -1
  117. package/dist/lib/skills.js +10 -9
  118. package/dist/lib/staleness/detectors/commands.d.ts +3 -0
  119. package/dist/lib/staleness/detectors/commands.js +46 -0
  120. package/dist/lib/staleness/detectors/hooks.d.ts +3 -0
  121. package/dist/lib/staleness/detectors/hooks.js +44 -0
  122. package/dist/lib/staleness/detectors/mcp.d.ts +3 -0
  123. package/dist/lib/staleness/detectors/mcp.js +31 -0
  124. package/dist/lib/staleness/detectors/permissions.d.ts +3 -0
  125. package/dist/lib/staleness/detectors/permissions.js +201 -0
  126. package/dist/lib/staleness/detectors/plugins.d.ts +8 -0
  127. package/dist/lib/staleness/detectors/plugins.js +23 -0
  128. package/dist/lib/staleness/detectors/rules.d.ts +3 -0
  129. package/dist/lib/staleness/detectors/rules.js +34 -0
  130. package/dist/lib/staleness/detectors/skills.d.ts +3 -0
  131. package/dist/lib/staleness/detectors/skills.js +71 -0
  132. package/dist/lib/staleness/detectors/subagents.d.ts +3 -0
  133. package/dist/lib/staleness/detectors/subagents.js +50 -0
  134. package/dist/lib/staleness/detectors/types.d.ts +22 -0
  135. package/dist/lib/staleness/detectors/types.js +1 -0
  136. package/dist/lib/staleness/detectors/workflows.d.ts +3 -0
  137. package/dist/lib/staleness/detectors/workflows.js +28 -0
  138. package/dist/lib/staleness/registry.d.ts +26 -0
  139. package/dist/lib/staleness/registry.js +123 -0
  140. package/dist/lib/staleness/writers/commands.d.ts +3 -0
  141. package/dist/lib/staleness/writers/commands.js +111 -0
  142. package/dist/lib/staleness/writers/hooks.d.ts +3 -0
  143. package/dist/lib/staleness/writers/hooks.js +47 -0
  144. package/dist/lib/staleness/writers/kinds.d.ts +10 -0
  145. package/dist/lib/staleness/writers/kinds.js +15 -0
  146. package/dist/lib/staleness/writers/lazy-map.d.ts +13 -0
  147. package/dist/lib/staleness/writers/lazy-map.js +19 -0
  148. package/dist/lib/staleness/writers/mcp.d.ts +10 -0
  149. package/dist/lib/staleness/writers/mcp.js +19 -0
  150. package/dist/lib/staleness/writers/permissions.d.ts +13 -0
  151. package/dist/lib/staleness/writers/permissions.js +26 -0
  152. package/dist/lib/staleness/writers/plugins.d.ts +7 -0
  153. package/dist/lib/staleness/writers/plugins.js +31 -0
  154. package/dist/lib/staleness/writers/rules.d.ts +7 -0
  155. package/dist/lib/staleness/writers/rules.js +55 -0
  156. package/dist/lib/staleness/writers/skills.d.ts +3 -0
  157. package/dist/lib/staleness/writers/skills.js +81 -0
  158. package/dist/lib/staleness/writers/sources.d.ts +16 -0
  159. package/dist/lib/staleness/writers/sources.js +72 -0
  160. package/dist/lib/staleness/writers/subagents.d.ts +3 -0
  161. package/dist/lib/staleness/writers/subagents.js +53 -0
  162. package/dist/lib/staleness/writers/types.d.ts +36 -0
  163. package/dist/lib/staleness/writers/types.js +1 -0
  164. package/dist/lib/staleness/writers/workflows.d.ts +7 -0
  165. package/dist/lib/staleness/writers/workflows.js +31 -0
  166. package/dist/lib/state.d.ts +34 -11
  167. package/dist/lib/state.js +58 -13
  168. package/dist/lib/subagents.d.ts +0 -2
  169. package/dist/lib/subagents.js +6 -6
  170. package/dist/lib/teams/agents.js +1 -1
  171. package/dist/lib/teams/parsers.d.ts +1 -1
  172. package/dist/lib/tmux/binary.d.ts +67 -0
  173. package/dist/lib/tmux/binary.js +141 -0
  174. package/dist/lib/tmux/index.d.ts +8 -0
  175. package/dist/lib/tmux/index.js +8 -0
  176. package/dist/lib/tmux/paths.d.ts +17 -0
  177. package/dist/lib/tmux/paths.js +30 -0
  178. package/dist/lib/tmux/session.d.ts +122 -0
  179. package/dist/lib/tmux/session.js +305 -0
  180. package/dist/lib/types.d.ts +58 -7
  181. package/dist/lib/types.js +1 -1
  182. package/dist/lib/usage.js +1 -1
  183. package/dist/lib/versions.d.ts +4 -4
  184. package/dist/lib/versions.js +135 -493
  185. package/dist/lib/workflows.d.ts +2 -4
  186. package/dist/lib/workflows.js +3 -4
  187. package/package.json +2 -2
  188. package/scripts/postinstall.js +16 -63
  189. package/dist/commands/status.d.ts +0 -9
  190. 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
+ }
@@ -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-system/trash/versions/<agent>/<version>/<timestamp>/
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-system/trash/` removes bytes from disk.
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;
@@ -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-system/versions/')
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-system if it's a git repo with remote (silent on success)
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-system from remote'));
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;
@@ -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 { WORKFLOW_CAPABLE_AGENTS } from '../lib/workflows.js';
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 (WORKFLOW_CAPABLE_AGENTS.includes(agentId)) {
758
+ if (isCapable(agentId, 'workflows')) {
754
759
  renderSection('Workflows', agentData.workflows);
755
760
  }
756
- if (PLUGINS_CAPABLE_AGENTS.includes(agentId)) {
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 { WORKFLOW_CAPABLE_AGENTS, discoverWorkflowsFromRepo, installWorkflowCentrally, removeWorkflow, listInstalledWorkflows, listWorkflowsForAgent, removeWorkflowFromVersion, iterWorkflowsCapableVersions, } from '../lib/workflows.js';
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, WORKFLOW_CAPABLE_AGENTS, { yes: options.yes });
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(WORKFLOW_CAPABLE_AGENTS, {
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>/.history/worktrees/<terminal-id>, on a branch
14
- * named agent/<terminal-id>. The branch starts at HEAD of the parent repo.
15
- * .history/ mirrors the agents-cli runtime-state convention at ~/.agents/.history/
16
- * but scoped to the repo. .agents/ is reserved for project resources
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('.history', 'worktrees');
10
- const BRANCH_PREFIX = 'agent/';
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>/.history/worktrees/<terminal-id> on branch agent/<terminal-id>.
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 .history/worktrees/. Skips dirty or unpushed ones.')
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) => {