@phnx-labs/agents-cli 1.18.5 → 1.19.0

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 (105) hide show
  1. package/CHANGELOG.md +13 -2
  2. package/README.md +22 -20
  3. package/dist/commands/browser.js +25 -2
  4. package/dist/commands/cloud.js +3 -3
  5. package/dist/commands/computer.d.ts +6 -0
  6. package/dist/commands/computer.js +477 -0
  7. package/dist/commands/doctor.js +19 -17
  8. package/dist/commands/exec.js +37 -59
  9. package/dist/commands/factory.js +12 -5
  10. package/dist/commands/import.js +6 -1
  11. package/dist/commands/mcp.js +9 -4
  12. package/dist/commands/packages.d.ts +3 -0
  13. package/dist/commands/packages.js +20 -12
  14. package/dist/commands/permissions.d.ts +2 -0
  15. package/dist/commands/permissions.js +20 -1
  16. package/dist/commands/plugins.d.ts +2 -0
  17. package/dist/commands/plugins.js +23 -4
  18. package/dist/commands/profiles.js +1 -1
  19. package/dist/commands/pty.js +126 -112
  20. package/dist/commands/pull.js +29 -25
  21. package/dist/commands/repo.js +24 -26
  22. package/dist/commands/routines.js +29 -26
  23. package/dist/commands/secrets.js +66 -73
  24. package/dist/commands/sessions-tail.js +21 -22
  25. package/dist/commands/sessions.js +36 -68
  26. package/dist/commands/setup.js +20 -24
  27. package/dist/commands/teams.js +30 -39
  28. package/dist/commands/versions.js +60 -68
  29. package/dist/commands/worktree.d.ts +20 -0
  30. package/dist/commands/worktree.js +242 -0
  31. package/dist/computer.d.ts +2 -0
  32. package/dist/computer.js +7 -0
  33. package/dist/index.js +70 -26
  34. package/dist/lib/agents.d.ts +4 -1
  35. package/dist/lib/agents.js +23 -5
  36. package/dist/lib/browser/cdp.d.ts +15 -1
  37. package/dist/lib/browser/cdp.js +77 -8
  38. package/dist/lib/browser/chrome.js +17 -24
  39. package/dist/lib/browser/drivers/ssh.d.ts +1 -0
  40. package/dist/lib/browser/drivers/ssh.js +20 -8
  41. package/dist/lib/browser/ipc.js +38 -5
  42. package/dist/lib/browser/profiles.js +34 -2
  43. package/dist/lib/browser/runtime-state.d.ts +1 -2
  44. package/dist/lib/browser/runtime-state.js +11 -3
  45. package/dist/lib/browser/service.d.ts +5 -0
  46. package/dist/lib/browser/service.js +32 -4
  47. package/dist/lib/browser/types.d.ts +1 -1
  48. package/dist/lib/browser/upload.d.ts +2 -0
  49. package/dist/lib/browser/upload.js +34 -0
  50. package/dist/lib/cloud/rush.d.ts +2 -1
  51. package/dist/lib/cloud/rush.js +28 -9
  52. package/dist/lib/computer-rpc.d.ts +24 -0
  53. package/dist/lib/computer-rpc.js +263 -0
  54. package/dist/lib/daemon.js +7 -7
  55. package/dist/lib/exec.d.ts +2 -1
  56. package/dist/lib/exec.js +3 -2
  57. package/dist/lib/fs-atomic.d.ts +18 -0
  58. package/dist/lib/fs-atomic.js +76 -0
  59. package/dist/lib/git.js +2 -4
  60. package/dist/lib/help.d.ts +15 -0
  61. package/dist/lib/help.js +41 -0
  62. package/dist/lib/hooks/match.d.ts +1 -0
  63. package/dist/lib/hooks/match.js +57 -12
  64. package/dist/lib/hooks.d.ts +1 -0
  65. package/dist/lib/hooks.js +27 -10
  66. package/dist/lib/import.d.ts +1 -0
  67. package/dist/lib/import.js +7 -0
  68. package/dist/lib/manifest.js +27 -1
  69. package/dist/lib/mcp.d.ts +14 -0
  70. package/dist/lib/mcp.js +79 -14
  71. package/dist/lib/migrate.js +3 -3
  72. package/dist/lib/models.js +3 -1
  73. package/dist/lib/permissions.d.ts +5 -0
  74. package/dist/lib/permissions.js +35 -0
  75. package/dist/lib/plugin-marketplace.d.ts +3 -1
  76. package/dist/lib/plugin-marketplace.js +36 -1
  77. package/dist/lib/plugins.d.ts +19 -1
  78. package/dist/lib/plugins.js +99 -8
  79. package/dist/lib/redact.d.ts +4 -0
  80. package/dist/lib/redact.js +18 -0
  81. package/dist/lib/registry.d.ts +2 -0
  82. package/dist/lib/registry.js +15 -0
  83. package/dist/lib/sandbox.js +15 -5
  84. package/dist/lib/secrets/bundles.d.ts +7 -12
  85. package/dist/lib/secrets/bundles.js +45 -29
  86. package/dist/lib/secrets/index.js +4 -4
  87. package/dist/lib/session/cloud.d.ts +2 -0
  88. package/dist/lib/session/cloud.js +34 -6
  89. package/dist/lib/session/parse.js +7 -2
  90. package/dist/lib/session/render.d.ts +4 -1
  91. package/dist/lib/session/render.js +81 -35
  92. package/dist/lib/shims.d.ts +5 -2
  93. package/dist/lib/shims.js +29 -7
  94. package/dist/lib/state.d.ts +5 -5
  95. package/dist/lib/state.js +43 -13
  96. package/dist/lib/teams/agents.d.ts +1 -1
  97. package/dist/lib/teams/agents.js +2 -2
  98. package/dist/lib/types.d.ts +4 -3
  99. package/dist/lib/types.js +0 -2
  100. package/dist/lib/versions.js +65 -40
  101. package/dist/lib/workflows.d.ts +7 -0
  102. package/dist/lib/workflows.js +42 -1
  103. package/npm-shrinkwrap.json +3256 -0
  104. package/package.json +32 -26
  105. package/scripts/postinstall.js +8 -2
@@ -19,61 +19,65 @@ import chalk from 'chalk';
19
19
  import { ptyRequest, unescapeInput } from '../lib/pty-client.js';
20
20
  import { isPtyServerRunning, runPtyServer, getPtyPidPath, getPtyLogPath } from '../lib/pty-server.js';
21
21
  import * as fs from 'fs';
22
+ import { setHelpSections } from '../lib/help.js';
22
23
  /** Register the `agents pty` command tree. */
23
24
  export function registerPtyCommands(program) {
24
25
  const pty = program
25
26
  .command('pty')
26
- .description('Drive interactive terminal programs from AI agents. Use this for REPLs, TUIs, or anything needing a real terminal.')
27
- .addHelpText('after', `
28
- Typical session workflow:
29
- # Start a new terminal session (returns a session ID)
30
- SID=$(agents pty start)
27
+ .description('Drive interactive terminal programs from AI agents. Use this for REPLs, TUIs, or anything needing a real terminal.');
28
+ setHelpSections(pty, {
29
+ examples: `
30
+ # Start a session (returns a session ID)
31
+ SID=$(agents pty start)
31
32
 
32
- # Run a command (non-blocking, returns immediately)
33
- agents pty exec $SID "python3"
33
+ # Run a command (non-blocking)
34
+ agents pty exec $SID "python3"
34
35
 
35
- # Wait a moment, then see what's on screen (clean text, no ANSI)
36
- sleep 1 && agents pty screen $SID
36
+ # Wait, then see what's on screen (clean text, no ANSI)
37
+ sleep 1 && agents pty screen $SID
37
38
 
38
- # Send input (supports escape sequences: \\n \\t \\e \\xHH)
39
- agents pty write $SID "print('hello')\\n"
39
+ # Type input (supports escapes: \\n \\t \\e \\xHH)
40
+ agents pty write $SID "print('hello')\\n"
40
41
 
41
- # Read the output again
42
- agents pty screen $SID
42
+ # Read again
43
+ agents pty screen $SID
43
44
 
44
- # Clean up when done
45
- agents pty stop $SID
45
+ # Clean up
46
+ agents pty stop $SID
47
+ `,
48
+ notes: `
49
+ Use cases:
50
+ - Drive REPLs (python, node, irb) from agent code
51
+ - Automate TUI programs (npm init, interactive wizards)
52
+ - Test CLI tools that require a real PTY
53
+ - Run the 'agents' CLI itself from another agent
46
54
 
47
- Use cases:
48
- - Drive REPLs (python, node, irb) from agent code
49
- - Automate TUI programs (npm init, interactive wizards)
50
- - Test CLI tools that require a real PTY
51
- - Run the 'agents' CLI itself from another agent
52
-
53
- The PTY server auto-starts on first use and runs in the background.
54
- Use 'agents pty server status' to check health.
55
- `);
55
+ The PTY server auto-starts on first use and runs in the background.
56
+ Health: 'agents pty server status'.
57
+ `,
58
+ });
56
59
  // --- Session lifecycle ---
57
- pty
60
+ const startCmd = pty
58
61
  .command('start')
59
62
  .description('Start a new PTY session and return its ID. The session persists until you stop it.')
60
63
  .option('-r, --rows <n>', 'Terminal height in rows', '24')
61
64
  .option('-c, --cols <n>', 'Terminal width in columns', '120')
62
65
  .option('-s, --shell <shell>', 'Shell to launch (defaults to $SHELL, e.g., zsh, bash)')
63
66
  .option('-d, --cwd <dir>', 'Working directory for the shell')
64
- .option('--json', 'Output full session metadata as JSON')
65
- .addHelpText('after', `
66
- Examples:
67
- # Start a session and capture its ID
68
- SID=$(agents pty start)
67
+ .option('--json', 'Output full session metadata as JSON');
68
+ setHelpSections(startCmd, {
69
+ examples: `
70
+ # Start a session and capture its ID
71
+ SID=$(agents pty start)
69
72
 
70
- # Start a Python REPL in a specific directory
71
- agents pty start --shell python3 --cwd /tmp
73
+ # Start a Python REPL in a specific directory
74
+ agents pty start --shell python3 --cwd /tmp
72
75
 
73
- # Start a wider terminal for TUI programs
74
- agents pty start --cols 160 --rows 40
75
- `)
76
- .action(async (opts) => {
76
+ # Start a wider terminal for TUI programs
77
+ agents pty start --cols 160 --rows 40
78
+ `,
79
+ });
80
+ startCmd.action(async (opts) => {
77
81
  const params = {
78
82
  rows: parseInt(opts.rows, 10),
79
83
  cols: parseInt(opts.cols, 10),
@@ -94,23 +98,24 @@ Examples:
94
98
  console.log(res.id);
95
99
  }
96
100
  });
97
- pty
101
+ const execCmd = pty
98
102
  .command('exec <id> <command>')
99
103
  .description('Send a command to a PTY session. Returns immediately (non-blocking). Use screen or read to see output.')
100
104
  .option('--wait <ms>', 'Wait this many milliseconds then return the screen (convenience for quick commands)', '0')
101
- .option('--json', 'Output as JSON')
102
- .addHelpText('after', `
103
- Examples:
104
- # Run a command and return immediately
105
- agents pty exec $SID "ls -la"
105
+ .option('--json', 'Output as JSON');
106
+ setHelpSections(execCmd, {
107
+ examples: `
108
+ # Run a command and return immediately
109
+ agents pty exec $SID "ls -la"
106
110
 
107
- # Run a command and wait 500ms to see output
108
- agents pty exec $SID "git status" --wait 500
111
+ # Run a command and wait 500ms to see output
112
+ agents pty exec $SID "git status" --wait 500
109
113
 
110
- # Start a long-running process (returns right away)
111
- agents pty exec $SID "npm run dev"
112
- `)
113
- .action(async (id, command, opts) => {
114
+ # Start a long-running process (returns right away)
115
+ agents pty exec $SID "npm run dev"
116
+ `,
117
+ });
118
+ execCmd.action(async (id, command, opts) => {
114
119
  const res = await ptyRequest('exec', id, { command });
115
120
  if (!res.ok) {
116
121
  console.error(chalk.red(res.error));
@@ -132,20 +137,21 @@ Examples:
132
137
  console.log(JSON.stringify(res));
133
138
  }
134
139
  });
135
- pty
140
+ const readCmd = pty
136
141
  .command('read <id>')
137
142
  .description('Read raw output from the PTY (includes ANSI codes). Use screen for clean text instead.')
138
143
  .option('-m, --ms <ms>', 'Wait up to this many milliseconds for new output (50-5000)', '200')
139
- .option('--json', 'Output as JSON')
140
- .addHelpText('after', `
141
- Examples:
142
- # Read pending output (wait up to 200ms)
143
- agents pty read $SID
144
+ .option('--json', 'Output as JSON');
145
+ setHelpSections(readCmd, {
146
+ examples: `
147
+ # Read pending output (wait up to 200ms)
148
+ agents pty read $SID
144
149
 
145
- # Read with longer wait for slow commands
146
- agents pty read $SID --ms 1000
147
- `)
148
- .action(async (id, opts) => {
150
+ # Read with longer wait for slow commands
151
+ agents pty read $SID --ms 1000
152
+ `,
153
+ });
154
+ readCmd.action(async (id, opts) => {
149
155
  const ms = parseInt(opts.ms, 10);
150
156
  const res = await ptyRequest('read', id, { ms });
151
157
  if (!res.ok) {
@@ -160,29 +166,30 @@ Examples:
160
166
  process.stdout.write(res.output);
161
167
  }
162
168
  });
163
- pty
169
+ const writeCmd = pty
164
170
  .command('write <id> <input>')
165
171
  .description('Send keystrokes to the PTY (like typing into the terminal). Processes escape sequences by default.')
166
172
  .option('--raw', 'Send input literally without processing \\n \\t \\e \\xHH escape codes')
167
- .option('--json', 'Output as JSON')
168
- .addHelpText('after', `
169
- Examples:
170
- # Send Enter key
171
- agents pty write $SID "\\n"
173
+ .option('--json', 'Output as JSON');
174
+ setHelpSections(writeCmd, {
175
+ examples: `
176
+ # Send Enter key
177
+ agents pty write $SID "\\n"
172
178
 
173
- # Type a command and press Enter
174
- agents pty write $SID "ls -la\\n"
179
+ # Type a command and press Enter
180
+ agents pty write $SID "ls -la\\n"
175
181
 
176
- # Send Ctrl-C (interrupt signal)
177
- agents pty write $SID "\\x03"
182
+ # Send Ctrl-C (interrupt signal)
183
+ agents pty write $SID "\\x03"
178
184
 
179
- # Send Escape key
180
- agents pty write $SID "\\e"
185
+ # Send Escape key
186
+ agents pty write $SID "\\e"
181
187
 
182
- # Send literal backslash-n (not newline)
183
- agents pty write $SID "\\\\n" --raw
184
- `)
185
- .action(async (id, input, opts) => {
188
+ # Send literal backslash-n (not newline)
189
+ agents pty write $SID "\\\\n" --raw
190
+ `,
191
+ });
192
+ writeCmd.action(async (id, input, opts) => {
186
193
  const processed = opts.raw ? input : unescapeInput(input);
187
194
  const res = await ptyRequest('write', id, { input: processed });
188
195
  if (!res.ok) {
@@ -193,21 +200,23 @@ Examples:
193
200
  console.log(JSON.stringify(res));
194
201
  }
195
202
  });
196
- pty
203
+ const screenCmd = pty
197
204
  .command('screen <id>')
198
205
  .description('Render the terminal screen as clean text (no ANSI codes). This is what a human sees looking at the terminal.')
199
- .option('--json', 'Output as JSON (includes cursor position and dimensions)')
200
- .addHelpText('after', `
201
- Examples:
202
- # See what's currently on screen
203
- agents pty screen $SID
204
-
205
- # Get screen with cursor position as JSON
206
- agents pty screen $SID --json
206
+ .option('--json', 'Output as JSON (includes cursor position and dimensions)');
207
+ setHelpSections(screenCmd, {
208
+ examples: `
209
+ # See what's currently on screen
210
+ agents pty screen $SID
207
211
 
208
- Use this (not read) when you want clean text for an LLM to parse.
209
- `)
210
- .action(async (id, opts) => {
212
+ # Get screen with cursor position as JSON
213
+ agents pty screen $SID --json
214
+ `,
215
+ notes: `
216
+ Use this (not read) when you want clean text for an LLM to parse.
217
+ `,
218
+ });
219
+ screenCmd.action(async (id, opts) => {
211
220
  const res = await ptyRequest('screen', id);
212
221
  if (!res.ok) {
213
222
  console.error(chalk.red(res.error));
@@ -220,21 +229,22 @@ Use this (not read) when you want clean text for an LLM to parse.
220
229
  console.log(res.screen);
221
230
  }
222
231
  });
223
- pty
232
+ const signalCmd = pty
224
233
  .command('signal <id> [signal]')
225
- .description('Send a POSIX signal to the running process. Defaults to INT (Ctrl-C).')
226
- .addHelpText('after', `
227
- Examples:
228
- # Send Ctrl-C (interrupt)
229
- agents pty signal $SID INT
234
+ .description('Send a POSIX signal to the running process. Defaults to INT (Ctrl-C).');
235
+ setHelpSections(signalCmd, {
236
+ examples: `
237
+ # Send Ctrl-C (interrupt)
238
+ agents pty signal $SID INT
230
239
 
231
- # Terminate gracefully
232
- agents pty signal $SID TERM
240
+ # Terminate gracefully
241
+ agents pty signal $SID TERM
233
242
 
234
- # Force kill
235
- agents pty signal $SID KILL
236
- `)
237
- .action(async (id, signal) => {
243
+ # Force kill
244
+ agents pty signal $SID KILL
245
+ `,
246
+ });
247
+ signalCmd.action(async (id, signal) => {
238
248
  const res = await ptyRequest('signal', id, { signal: signal || 'INT' });
239
249
  if (!res.ok) {
240
250
  console.error(chalk.red(res.error));
@@ -259,14 +269,16 @@ Examples:
259
269
  }
260
270
  console.log(`${res.cols}x${res.rows}`);
261
271
  });
262
- pty
272
+ const stopCmd = pty
263
273
  .command('stop <id>')
264
- .description('Stop a PTY session and clean up. The session ID becomes invalid.')
265
- .addHelpText('after', `
266
- Example:
267
- agents pty stop $SID
268
- `)
269
- .action(async (id) => {
274
+ .description('Stop a PTY session and clean up. The session ID becomes invalid.');
275
+ setHelpSections(stopCmd, {
276
+ examples: `
277
+ # Stop a session by ID
278
+ agents pty stop $SID
279
+ `,
280
+ });
281
+ stopCmd.action(async (id) => {
270
282
  const res = await ptyRequest('stop', id);
271
283
  if (!res.ok) {
272
284
  console.error(chalk.red(res.error));
@@ -274,15 +286,17 @@ Example:
274
286
  }
275
287
  });
276
288
  // --- Session listing ---
277
- pty
289
+ const listCmd = pty
278
290
  .command('list')
279
291
  .description('List all active PTY sessions (running or idle).')
280
- .option('--json', 'Output as JSON')
281
- .addHelpText('after', `
282
- Example:
283
- agents pty list
284
- `)
285
- .action(async (opts) => {
292
+ .option('--json', 'Output as JSON');
293
+ setHelpSections(listCmd, {
294
+ examples: `
295
+ # List all active sessions
296
+ agents pty list
297
+ `,
298
+ });
299
+ listCmd.action(async (opts) => {
286
300
  const res = await ptyRequest('list');
287
301
  if (!res.ok) {
288
302
  console.error(chalk.red(res.error));
@@ -16,6 +16,7 @@ import * as path from 'path';
16
16
  import { installVersion, listInstalledVersions, getGlobalDefault, setGlobalDefault, getVersionHomePath, syncResourcesToVersion, getAvailableResources, getActuallySyncedResources, getNewResources, hasNewResources, promptNewResourceSelection, promptResourceSelection, resolveConfiguredAgentTargets, } from '../lib/versions.js';
17
17
  import { ensureShimCurrent, isShimsInPath, addShimsToPath, getPathSetupInstructions, switchConfigSymlink, switchHomeFileSymlinks, } from '../lib/shims.js';
18
18
  import { parseHookManifest, registerHooksToSettings } from '../lib/hooks.js';
19
+ import { setHelpSections } from '../lib/help.js';
19
20
  import { select } from '@inquirer/prompts';
20
21
  import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
21
22
  /**
@@ -45,37 +46,40 @@ function migratePromptcutsToRoot(agentsDir) {
45
46
  }
46
47
  /** Register the `agents pull` command. */
47
48
  export function registerPullCommand(program) {
48
- program
49
+ const pullCmd = program
49
50
  .command('pull [agent]')
50
- .description('Pull your user repo at ~/.agents/ and refresh installed agent versions.')
51
+ .description('Sync your user repo at ~/.agents/ and refresh installed agent CLIs. (Deprecated — prefer \'agents repo pull\' and \'agents setup\'.)')
51
52
  .option('-y, --yes', 'Auto-sync all resources without prompting')
52
- .option('--skip-clis', 'Pull config changes but do not install or upgrade agent CLIs')
53
- .addHelpText('after', `
54
- Examples:
55
- # First time: clone the system repo into ~/.agents-system/
56
- agents pull
53
+ .option('--skip-clis', 'Pull config changes but do not install or upgrade agent CLIs');
54
+ setHelpSections(pullCmd, {
55
+ examples: `
56
+ # First time: clone the system repo into ~/.agents-system/
57
+ agents pull
57
58
 
58
- # Sync only one agent's config
59
- agents pull claude
59
+ # Sync only one agent's config
60
+ agents pull claude
60
61
 
61
- # Non-interactive sync (for scripts / CI)
62
- agents pull -y
62
+ # Non-interactive (scripts / CI)
63
+ agents pull -y
63
64
 
64
- When to use:
65
- - Initial setup: clone the system repo to a new machine
66
- - Daily sync: pull the latest system skills, commands, or MCP servers
67
- - Per-agent: sync just one agent's config without touching others
65
+ # Sync config without touching installed CLI versions
66
+ agents pull --skip-clis
67
+ `,
68
+ notes: `
69
+ Deprecated. Use:
70
+ agents setup first-time setup
71
+ agents repo pull force-sync now
72
+ agents repo push push your user repo
68
73
 
69
- What it syncs:
70
- - CLI versions listed in agents.yaml
71
- - Commands, skills, hooks from the repo
72
- - MCP server configs
73
- - Memory/rules files
74
- - Permissions groups
75
-
76
- Skip CLI installs with --skip-clis when you only want config updates, not version changes.
77
- `)
78
- .action(async (arg1, options) => {
74
+ What it syncs:
75
+ - CLI versions listed in agents.yaml
76
+ - Commands, skills, hooks
77
+ - MCP server configs
78
+ - Memory/rules files
79
+ - Permissions groups
80
+ `,
81
+ });
82
+ pullCmd.action(async (arg1, options) => {
79
83
  // Deprecation banner — `agents pull` is on its way out. agents-cli now
80
84
  // auto-syncs the system repo in the background and surfaces upstream
81
85
  // changes for user/extra repos as one-line notices. Repo lifecycle is
@@ -6,6 +6,7 @@ import * as os from 'os';
6
6
  import simpleGit from 'simple-git';
7
7
  import { confirm, input } from '@inquirer/prompts';
8
8
  import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
9
+ import { setHelpSections } from '../lib/help.js';
9
10
  const HOME = os.homedir();
10
11
  /**
11
12
  * Resolve a target argument to an absolute path.
@@ -56,38 +57,35 @@ async function getShortCommit(repoDir) {
56
57
  export function registerRepoCommands(program) {
57
58
  const repoCmd = program
58
59
  .command('repo')
59
- .description('Manage extra DotAgent repos alongside ~/.agents/ (for private or team skills)')
60
- .addHelpText('after', `
61
- Managed extras live at ~/.agents-<alias>/ as peer dirs to ~/.agents/. User-owned
62
- repos can also live anywhere and be registered by path. Their skills, commands,
63
- and hooks merge into agent version homes after the user repo's — so ~/.agents/
64
- wins on name collisions.
60
+ .description('Manage extra DotAgent repos alongside ~/.agents/ (for private or team skills).');
61
+ setHelpSections(repoCmd, {
62
+ examples: `
63
+ # Scaffold an editable repo (default: ~/.agents/)
64
+ agents repo init
65
65
 
66
- Examples:
67
- # Scaffold your own editable repo (default: ~/.agents/)
68
- agents repo init
66
+ # Scaffold a named repo at ~/.agents-work/
67
+ agents repo init work
69
68
 
70
- # Scaffold a named repo (creates ~/.agents-work/)
71
- agents repo init work
69
+ # Register an existing repo (clones to ~/.agents-<alias>/)
70
+ agents repo add gh:yourname/.agents-work
72
71
 
73
- # Scaffold at a custom path
74
- agents repo init ~/my-agents
72
+ # Register with a custom alias
73
+ agents repo add git@github.com:acme/team-skills.git --as acme
75
74
 
76
- # Add a private repo for work-only skills
77
- agents repo add gh:yourname/.agents-work
75
+ # See what's registered
76
+ agents repo list
78
77
 
79
- # Add with a custom alias
80
- agents repo add git@github.com:acme/team-skills.git --as acme
78
+ # Temporarily disable without deleting
79
+ agents repo disable acme
80
+ `,
81
+ notes: `
82
+ Managed extras live at ~/.agents-<alias>/ as peer dirs to ~/.agents/. User-owned
83
+ repos can also live anywhere and be registered by path.
81
84
 
82
- # Show all registered repos
83
- agents repo list
84
-
85
- # Temporarily disable without deleting
86
- agents repo disable acme
87
-
88
- # Unregister it
89
- agents repo remove acme
90
- `);
85
+ Resolution: skills/commands/hooks merge into agent version homes after the user
86
+ repo's, so ~/.agents/ wins on name collisions.
87
+ `,
88
+ });
91
89
  repoCmd
92
90
  .command('init [target]')
93
91
  .description('Create a user-owned repo from a template and register it as an extra')
@@ -17,6 +17,7 @@ import { safeJoin } from '../lib/paths.js';
17
17
  import { executeJob } from '../lib/runner.js';
18
18
  import { JobScheduler } from '../lib/scheduler.js';
19
19
  import { isInteractiveTerminal, requireInteractiveSelection } from './utils.js';
20
+ import { setHelpSections } from '../lib/help.js';
20
21
  /** Start or reload the background scheduler so newly-added jobs fire on time. */
21
22
  function ensureSchedulerRunning() {
22
23
  if (isDaemonRunning()) {
@@ -75,36 +76,38 @@ async function pickJob(message, filter, alternatives = []) {
75
76
  export function registerRoutinesCommands(program) {
76
77
  const routinesCmd = program
77
78
  .command('routines')
78
- .description('Schedule agents to run on a cron schedule or at a specific time. The scheduler auto-starts on first add.')
79
- .addHelpText('after', `
80
- A routine is a YAML file that schedules an agent invocation. It specifies:
81
- - which agent to run (claude, codex, gemini, etc.)
82
- - when to run (cron schedule or one-shot time)
83
- - what task to give the agent (the prompt)
84
- - execution constraints (mode, effort, timeout)
79
+ .description('Schedule agents to run on a cron schedule or at a specific time. The scheduler auto-starts on first add.');
80
+ setHelpSections(routinesCmd, {
81
+ examples: `
82
+ # Cron routine: Claude every weekday at 9 AM (scheduler auto-starts)
83
+ agents routines add daily-standup --schedule "0 9 * * 1-5" --agent claude --prompt "Draft standup from git log"
85
84
 
86
- A background scheduler fires routines on their schedule. It auto-starts the first
87
- time you add a routine; control it manually with 'agents routines start|stop|status'.
85
+ # One-shot: Codex tomorrow at 2:30 PM, then never again
86
+ agents routines add hotfix-review --at "14:30" --agent codex --prompt "Review hotfix PR #42"
88
87
 
89
- Examples:
90
- # Create a routine that runs Claude every weekday at 9 AM (scheduler auto-starts)
91
- agents routines add daily-standup --schedule "0 9 * * 1-5" --agent claude --prompt "Draft standup update from git log"
88
+ # Create from YAML (for complex routines with multiple settings)
89
+ agents routines add weekly-report.yml
92
90
 
93
- # One-shot routine: run Codex tomorrow at 2:30 PM, then never again
94
- agents routines add hotfix-review --at "14:30" --agent codex --prompt "Review hotfix PR #42"
91
+ # List all routines and their next run times
92
+ agents routines list
95
93
 
96
- # Create from a YAML file (for complex routines with multiple settings)
97
- agents routines add weekly-report.yml
94
+ # Run a routine right now in the foreground (ignores schedule)
95
+ agents routines run daily-standup
98
96
 
99
- # See all routines and their next run times
100
- agents routines list
97
+ # Check whether the scheduler is running
98
+ agents routines status
99
+ `,
100
+ notes: `
101
+ A routine is a YAML file that schedules an agent invocation. It specifies:
102
+ - which agent to run (claude, codex, gemini, ...)
103
+ - when to run (cron schedule or one-shot time)
104
+ - what task to give the agent (the prompt)
105
+ - execution constraints (mode, effort, timeout)
101
106
 
102
- # Check whether the scheduler is running
103
- agents routines status
104
-
105
- # Test a routine immediately in the foreground (ignores schedule)
106
- agents routines run daily-standup
107
- `);
107
+ The background scheduler auto-starts the first time you add a routine.
108
+ Manage it with 'agents routines start|stop|status'.
109
+ `,
110
+ });
108
111
  routinesCmd
109
112
  .command('list')
110
113
  .description('See all scheduled jobs, when they run next, and their last execution status')
@@ -570,10 +573,10 @@ Examples:
570
573
  .option('-f, --follow', 'Stream log output in real time (like tail -f)')
571
574
  .action(async (options) => {
572
575
  if (options.follow) {
573
- const { exec: execCb } = await import('child_process');
576
+ const { spawn } = await import('child_process');
574
577
  const { getDaemonDir } = await import('../lib/state.js');
575
578
  const logPath = path.join(getDaemonDir(), 'logs.jsonl');
576
- const child = execCb(`tail -f "${logPath}"`);
579
+ const child = spawn('tail', ['-f', logPath]);
577
580
  child.stdout?.pipe(process.stdout);
578
581
  child.stderr?.pipe(process.stderr);
579
582
  child.on('exit', () => process.exit(0));