@phnx-labs/agents-cli 1.20.3 → 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 (193) hide show
  1. package/CHANGELOG.md +27 -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/import.js +90 -37
  10. package/dist/commands/inspect.d.ts +26 -0
  11. package/dist/commands/inspect.js +590 -0
  12. package/dist/commands/mcp.js +17 -16
  13. package/dist/commands/models.js +1 -1
  14. package/dist/commands/packages.js +6 -4
  15. package/dist/commands/permissions.js +13 -12
  16. package/dist/commands/plugins.d.ts +13 -0
  17. package/dist/commands/plugins.js +100 -11
  18. package/dist/commands/prune.js +3 -2
  19. package/dist/commands/pull.d.ts +12 -5
  20. package/dist/commands/pull.js +26 -422
  21. package/dist/commands/push.d.ts +14 -0
  22. package/dist/commands/push.js +30 -0
  23. package/dist/commands/repo.d.ts +1 -1
  24. package/dist/commands/repo.js +155 -112
  25. package/dist/commands/resource-view.d.ts +2 -0
  26. package/dist/commands/resource-view.js +12 -3
  27. package/dist/commands/routines.js +32 -7
  28. package/dist/commands/rules.js +1 -1
  29. package/dist/commands/sessions.js +1 -0
  30. package/dist/commands/setup.d.ts +3 -3
  31. package/dist/commands/setup.js +15 -15
  32. package/dist/commands/skills.js +6 -5
  33. package/dist/commands/subagents.js +5 -4
  34. package/dist/commands/sync.d.ts +18 -5
  35. package/dist/commands/sync.js +251 -65
  36. package/dist/commands/teams.js +1 -0
  37. package/dist/commands/tmux.d.ts +25 -0
  38. package/dist/commands/tmux.js +415 -0
  39. package/dist/commands/trash.d.ts +2 -2
  40. package/dist/commands/trash.js +1 -1
  41. package/dist/commands/versions.js +2 -2
  42. package/dist/commands/view.js +14 -4
  43. package/dist/commands/workflows.js +4 -3
  44. package/dist/commands/worktree.d.ts +4 -5
  45. package/dist/commands/worktree.js +4 -4
  46. package/dist/index.js +68 -20
  47. package/dist/lib/agents.d.ts +19 -10
  48. package/dist/lib/agents.js +102 -28
  49. package/dist/lib/auto-pull-worker.d.ts +1 -1
  50. package/dist/lib/auto-pull-worker.js +2 -2
  51. package/dist/lib/auto-pull.d.ts +1 -1
  52. package/dist/lib/auto-pull.js +1 -1
  53. package/dist/lib/beta.d.ts +1 -1
  54. package/dist/lib/beta.js +1 -1
  55. package/dist/lib/capabilities.js +2 -0
  56. package/dist/lib/commands.d.ts +28 -1
  57. package/dist/lib/commands.js +125 -20
  58. package/dist/lib/doctor-diff.js +2 -2
  59. package/dist/lib/exec.d.ts +14 -0
  60. package/dist/lib/exec.js +39 -5
  61. package/dist/lib/fuzzy.d.ts +12 -2
  62. package/dist/lib/fuzzy.js +29 -4
  63. package/dist/lib/git.js +8 -1
  64. package/dist/lib/hooks.d.ts +2 -2
  65. package/dist/lib/hooks.js +97 -10
  66. package/dist/lib/import.d.ts +21 -0
  67. package/dist/lib/import.js +55 -2
  68. package/dist/lib/mcp.js +32 -2
  69. package/dist/lib/migrate.d.ts +51 -0
  70. package/dist/lib/migrate.js +227 -1
  71. package/dist/lib/models.js +62 -15
  72. package/dist/lib/permissions.d.ts +36 -2
  73. package/dist/lib/permissions.js +217 -7
  74. package/dist/lib/plugin-marketplace.d.ts +108 -40
  75. package/dist/lib/plugin-marketplace.js +243 -94
  76. package/dist/lib/plugins.d.ts +21 -4
  77. package/dist/lib/plugins.js +130 -49
  78. package/dist/lib/profiles-presets.js +12 -12
  79. package/dist/lib/project-launch.d.ts +65 -0
  80. package/dist/lib/project-launch.js +367 -0
  81. package/dist/lib/pty-client.js +1 -1
  82. package/dist/lib/pty-server.d.ts +1 -1
  83. package/dist/lib/pty-server.js +28 -4
  84. package/dist/lib/refresh.d.ts +26 -0
  85. package/dist/lib/refresh.js +315 -0
  86. package/dist/lib/resource-patterns.d.ts +1 -1
  87. package/dist/lib/resource-patterns.js +1 -1
  88. package/dist/lib/resources/commands.js +2 -2
  89. package/dist/lib/resources/hooks.d.ts +1 -1
  90. package/dist/lib/resources/hooks.js +1 -1
  91. package/dist/lib/resources/mcp.d.ts +1 -1
  92. package/dist/lib/resources/mcp.js +5 -6
  93. package/dist/lib/resources/permissions.js +5 -2
  94. package/dist/lib/resources/rules.js +3 -2
  95. package/dist/lib/resources/skills.js +3 -2
  96. package/dist/lib/resources/types.d.ts +1 -1
  97. package/dist/lib/resources.js +2 -2
  98. package/dist/lib/rotate.d.ts +1 -1
  99. package/dist/lib/rotate.js +1 -1
  100. package/dist/lib/routines.d.ts +16 -4
  101. package/dist/lib/routines.js +67 -17
  102. package/dist/lib/rules/compile.js +22 -10
  103. package/dist/lib/rules/rules.js +3 -3
  104. package/dist/lib/runner.js +16 -3
  105. package/dist/lib/scheduler.js +15 -1
  106. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  107. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  108. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +9 -1
  109. package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
  110. package/dist/lib/secrets/linux.d.ts +44 -9
  111. package/dist/lib/secrets/linux.js +302 -48
  112. package/dist/lib/session/db.js +15 -2
  113. package/dist/lib/session/discover.js +118 -3
  114. package/dist/lib/session/parse.js +3 -0
  115. package/dist/lib/session/types.d.ts +1 -1
  116. package/dist/lib/session/types.js +1 -1
  117. package/dist/lib/shims.d.ts +10 -9
  118. package/dist/lib/shims.js +101 -50
  119. package/dist/lib/skills.d.ts +1 -1
  120. package/dist/lib/skills.js +10 -9
  121. package/dist/lib/staleness/detectors/commands.d.ts +3 -0
  122. package/dist/lib/staleness/detectors/commands.js +46 -0
  123. package/dist/lib/staleness/detectors/hooks.d.ts +3 -0
  124. package/dist/lib/staleness/detectors/hooks.js +44 -0
  125. package/dist/lib/staleness/detectors/mcp.d.ts +3 -0
  126. package/dist/lib/staleness/detectors/mcp.js +31 -0
  127. package/dist/lib/staleness/detectors/permissions.d.ts +3 -0
  128. package/dist/lib/staleness/detectors/permissions.js +201 -0
  129. package/dist/lib/staleness/detectors/plugins.d.ts +8 -0
  130. package/dist/lib/staleness/detectors/plugins.js +23 -0
  131. package/dist/lib/staleness/detectors/rules.d.ts +3 -0
  132. package/dist/lib/staleness/detectors/rules.js +34 -0
  133. package/dist/lib/staleness/detectors/skills.d.ts +3 -0
  134. package/dist/lib/staleness/detectors/skills.js +71 -0
  135. package/dist/lib/staleness/detectors/subagents.d.ts +3 -0
  136. package/dist/lib/staleness/detectors/subagents.js +50 -0
  137. package/dist/lib/staleness/detectors/types.d.ts +22 -0
  138. package/dist/lib/staleness/detectors/types.js +1 -0
  139. package/dist/lib/staleness/detectors/workflows.d.ts +3 -0
  140. package/dist/lib/staleness/detectors/workflows.js +28 -0
  141. package/dist/lib/staleness/registry.d.ts +26 -0
  142. package/dist/lib/staleness/registry.js +123 -0
  143. package/dist/lib/staleness/writers/commands.d.ts +3 -0
  144. package/dist/lib/staleness/writers/commands.js +111 -0
  145. package/dist/lib/staleness/writers/hooks.d.ts +3 -0
  146. package/dist/lib/staleness/writers/hooks.js +47 -0
  147. package/dist/lib/staleness/writers/kinds.d.ts +10 -0
  148. package/dist/lib/staleness/writers/kinds.js +15 -0
  149. package/dist/lib/staleness/writers/lazy-map.d.ts +13 -0
  150. package/dist/lib/staleness/writers/lazy-map.js +19 -0
  151. package/dist/lib/staleness/writers/mcp.d.ts +10 -0
  152. package/dist/lib/staleness/writers/mcp.js +19 -0
  153. package/dist/lib/staleness/writers/permissions.d.ts +13 -0
  154. package/dist/lib/staleness/writers/permissions.js +26 -0
  155. package/dist/lib/staleness/writers/plugins.d.ts +7 -0
  156. package/dist/lib/staleness/writers/plugins.js +31 -0
  157. package/dist/lib/staleness/writers/rules.d.ts +7 -0
  158. package/dist/lib/staleness/writers/rules.js +55 -0
  159. package/dist/lib/staleness/writers/skills.d.ts +3 -0
  160. package/dist/lib/staleness/writers/skills.js +81 -0
  161. package/dist/lib/staleness/writers/sources.d.ts +16 -0
  162. package/dist/lib/staleness/writers/sources.js +72 -0
  163. package/dist/lib/staleness/writers/subagents.d.ts +3 -0
  164. package/dist/lib/staleness/writers/subagents.js +53 -0
  165. package/dist/lib/staleness/writers/types.d.ts +36 -0
  166. package/dist/lib/staleness/writers/types.js +1 -0
  167. package/dist/lib/staleness/writers/workflows.d.ts +7 -0
  168. package/dist/lib/staleness/writers/workflows.js +31 -0
  169. package/dist/lib/state.d.ts +34 -11
  170. package/dist/lib/state.js +58 -13
  171. package/dist/lib/subagents.d.ts +0 -2
  172. package/dist/lib/subagents.js +6 -6
  173. package/dist/lib/teams/agents.js +1 -1
  174. package/dist/lib/teams/parsers.d.ts +1 -1
  175. package/dist/lib/tmux/binary.d.ts +67 -0
  176. package/dist/lib/tmux/binary.js +141 -0
  177. package/dist/lib/tmux/index.d.ts +8 -0
  178. package/dist/lib/tmux/index.js +8 -0
  179. package/dist/lib/tmux/paths.d.ts +17 -0
  180. package/dist/lib/tmux/paths.js +30 -0
  181. package/dist/lib/tmux/session.d.ts +122 -0
  182. package/dist/lib/tmux/session.js +305 -0
  183. package/dist/lib/types.d.ts +58 -7
  184. package/dist/lib/types.js +1 -1
  185. package/dist/lib/usage.js +1 -1
  186. package/dist/lib/versions.d.ts +4 -4
  187. package/dist/lib/versions.js +154 -491
  188. package/dist/lib/workflows.d.ts +2 -4
  189. package/dist/lib/workflows.js +3 -4
  190. package/package.json +7 -7
  191. package/scripts/postinstall.js +16 -63
  192. package/dist/commands/status.d.ts +0 -9
  193. package/dist/commands/status.js +0 -25
@@ -26,6 +26,30 @@ function resolveRepoPath(target) {
26
26
  import { ensureAgentsDir, getExtraRepoDir, getSystemAgentsDir, getUserAgentsDir, readMeta, resolveExtraRepoDir, updateMeta, } from '../lib/state.js';
27
27
  import { parseSource, pullRepo, commitAndPush, isGitRepo, isSystemRepoOrigin } from '../lib/git.js';
28
28
  import { DEFAULT_SYSTEM_REPO } from '../lib/types.js';
29
+ import { ALL_AGENT_IDS, isAgentName, resolveAgentName } from '../lib/agents.js';
30
+ import { refresh } from '../lib/refresh.js';
31
+ import { capableAgents } from '../lib/capabilities.js';
32
+ import { getGlobalDefault, getVersionHomePath, listInstalledVersions } from '../lib/versions.js';
33
+ import { syncAllMarketplaces } from '../lib/plugin-marketplace.js';
34
+ /**
35
+ * After a repo add/remove/enable/disable, reconcile each plugins-capable
36
+ * agent's default version against the new marketplace set. Re-synthesizes
37
+ * catalogs and known_marketplaces.json entries. Source-copy of plugins is
38
+ * out of scope here — full sync still goes through `agents repo refresh`.
39
+ */
40
+ function syncMarketplacesForDefaults() {
41
+ for (const agent of capableAgents('plugins')) {
42
+ const def = getGlobalDefault(agent);
43
+ if (!def)
44
+ continue;
45
+ if (!listInstalledVersions(agent).includes(def))
46
+ continue;
47
+ try {
48
+ syncAllMarketplaces(agent, getVersionHomePath(agent, def));
49
+ }
50
+ catch { /* best-effort */ }
51
+ }
52
+ }
29
53
  const ALIAS_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;
30
54
  /** Derive a default alias from a source URL (e.g. gh:foo/.agents-work -> agents-work). */
31
55
  function deriveAlias(source) {
@@ -42,16 +66,97 @@ function deriveAlias(source) {
42
66
  // becomes ~/.agents-<alias>/ on disk (e.g. ".agents-work" -> "work").
43
67
  return base.replace(/^\.+/, '').replace(/^agents-/, '') || 'repo';
44
68
  }
45
- /** Get the last commit short hash for a repo, or null if unavailable. */
46
- async function getShortCommit(repoDir) {
69
+ /**
70
+ * Categorized working-tree summary: `M:2 A:1 D:1 ?:3` — letters match the git
71
+ * porcelain shorthand (Modified / Added / Deleted / Renamed / Unmerged /
72
+ * Untracked). Zero counts are omitted so the column stays compact.
73
+ */
74
+ function formatDirtyCounts(status) {
75
+ const parts = [];
76
+ if (status.modified.length)
77
+ parts.push(chalk.yellow(`M:${status.modified.length}`));
78
+ if (status.created.length)
79
+ parts.push(chalk.green(`A:${status.created.length}`));
80
+ if (status.deleted.length)
81
+ parts.push(chalk.red(`D:${status.deleted.length}`));
82
+ if (status.renamed.length)
83
+ parts.push(chalk.cyan(`R:${status.renamed.length}`));
84
+ if (status.conflicted.length)
85
+ parts.push(chalk.magenta(`U:${status.conflicted.length}`));
86
+ if (status.not_added.length)
87
+ parts.push(chalk.gray(`?:${status.not_added.length}`));
88
+ return parts.join(' ');
89
+ }
90
+ /** Visible character width of a string with embedded ANSI color codes. */
91
+ function visibleWidth(s) {
92
+ return s.replace(/\[[0-9;]*m/g, '').length;
93
+ }
94
+ /** Pad string with trailing spaces to a target visible column width. */
95
+ function padVisible(s, width) {
96
+ return s + ' '.repeat(Math.max(0, width - visibleWidth(s)));
97
+ }
98
+ /**
99
+ * Render one row of the unified repo table: alias / branch / ahead-behind /
100
+ * dirty counts / url+commit. Used by `agents repo list` and the hidden
101
+ * `agents repo status` alias.
102
+ */
103
+ async function renderRepoRow(t) {
104
+ const aliasCol = chalk.cyan(t.alias.padEnd(12));
105
+ if (!fs.existsSync(t.dir)) {
106
+ return ` ${aliasCol} ${chalk.red('missing')} ${chalk.gray(t.dir)}`;
107
+ }
108
+ if (!isGitRepo(t.dir)) {
109
+ return ` ${aliasCol} ${chalk.gray('local (no git remote)')} ${chalk.gray(t.dir)}`;
110
+ }
47
111
  try {
48
- const git = simpleGit(repoDir);
49
- const log = await git.log({ maxCount: 1 });
50
- return log.latest?.hash.slice(0, 8) || null;
112
+ const git = simpleGit(t.dir);
113
+ const status = await git.status();
114
+ const branch = status.tracking || status.current || '(detached)';
115
+ const inSync = (status.ahead ?? 0) === 0 && (status.behind ?? 0) === 0;
116
+ const remoteRaw = inSync ? 'in sync' : `+${status.ahead ?? 0} -${status.behind ?? 0}`;
117
+ const remoteColored = inSync ? chalk.green(remoteRaw) : chalk.yellow(remoteRaw);
118
+ const remotePad = ' '.repeat(Math.max(0, 12 - remoteRaw.length));
119
+ const tree = status.isClean() ? chalk.green('clean') : formatDirtyCounts(status);
120
+ const remotes = await git.getRemotes(true);
121
+ const origin = remotes.find((r) => r.name === 'origin');
122
+ const url = origin?.refs?.fetch || '';
123
+ const commit = (await git.log({ maxCount: 1 })).latest?.hash.slice(0, 8) || '';
124
+ const tail = url
125
+ ? chalk.gray(`${url}${commit ? ` (${commit})` : ''}`)
126
+ : commit
127
+ ? chalk.gray(`(${commit})`)
128
+ : '';
129
+ return ` ${aliasCol} ${branch.padEnd(28)} ${remoteColored}${remotePad} ${padVisible(tree, 14)} ${tail}`;
51
130
  }
52
- catch {
53
- return null;
131
+ catch (err) {
132
+ return ` ${aliasCol} ${chalk.red('error')} ${err.message}`;
133
+ }
134
+ }
135
+ /**
136
+ * Shared action body for `agents repo list` and the hidden `agents repo status`
137
+ * alias. Prints a unified table: alias, branch, ahead/behind, dirty counts,
138
+ * remote URL and short commit hash.
139
+ */
140
+ async function listRepos(alias) {
141
+ const targets = collectRepoTargets(alias);
142
+ if (!targets) {
143
+ process.exitCode = 1;
144
+ return;
145
+ }
146
+ if (targets.length === 0) {
147
+ console.log(chalk.gray('No repos to show.'));
148
+ return;
149
+ }
150
+ console.log('');
151
+ console.log(` ${chalk.gray('REPO'.padEnd(12))} ${chalk.gray('BRANCH'.padEnd(28))} ${chalk.gray('REMOTE'.padEnd(12))} ${chalk.gray('LOCAL'.padEnd(14))} ${chalk.gray('URL')}`);
152
+ for (const t of targets) {
153
+ console.log(await renderRepoRow(t));
54
154
  }
155
+ const userDir = getUserAgentsDir();
156
+ if (!isGitRepo(userDir) && fs.existsSync(userDir)) {
157
+ console.log(chalk.gray('\n user repo has no git remote — scaffold one with: agents repo init'));
158
+ }
159
+ console.log('');
55
160
  }
56
161
  /** Register the `agents repo` command tree. */
57
162
  export function registerRepoCommands(program) {
@@ -201,6 +306,7 @@ export function registerRepoCommands(program) {
201
306
  if (parsed.type === 'local') {
202
307
  extras[alias] = { url: parsed.url, path: parsed.url, enabled: true };
203
308
  updateMeta({ extraRepos: extras });
309
+ syncMarketplacesForDefaults();
204
310
  console.log(chalk.green(`Registered local repo "${alias}" -> ${parsed.url}`));
205
311
  return;
206
312
  }
@@ -236,78 +342,16 @@ export function registerRepoCommands(program) {
236
342
  }
237
343
  extras[alias] = { url: parsed.url, path: targetDir, enabled: true };
238
344
  updateMeta({ extraRepos: extras });
345
+ syncMarketplacesForDefaults();
239
346
  console.log(chalk.gray(`\nRegistered as "${alias}". Skills and commands from this repo will be`));
240
347
  console.log(chalk.gray(`picked up automatically the next time you launch any agent.`));
241
348
  });
242
349
  repoCmd
243
- .command('list')
350
+ .command('list [alias]')
244
351
  .alias('ls')
245
- .description('Show all repos: system, user, and any registered extras')
246
- .action(async () => {
247
- const meta = readMeta();
248
- console.log('');
249
- // System repo
250
- const systemUrl = meta.source || DEFAULT_SYSTEM_REPO;
251
- const systemDir = getSystemAgentsDir();
252
- const systemOnDisk = fs.existsSync(systemDir);
253
- const systemIsGit = systemOnDisk && isGitRepo(systemDir);
254
- const systemCommit = systemIsGit ? await getShortCommit(systemDir) : null;
255
- const systemStatus = !systemOnDisk
256
- ? chalk.red('missing')
257
- : !systemIsGit
258
- ? chalk.yellow('not a git repo — run: agents setup')
259
- : chalk.green('cloned');
260
- const systemCommitLabel = systemCommit ? chalk.gray(`(${systemCommit})`) : '';
261
- console.log(chalk.bold('System (~/.agents-system/)'));
262
- console.log(` ${chalk.cyan('system'.padEnd(12))} ${systemUrl} ${systemStatus} ${systemCommitLabel}`);
263
- // User repo
264
- const userDir = getUserAgentsDir();
265
- const userOnDisk = fs.existsSync(userDir);
266
- const userIsGit = userOnDisk && isGitRepo(userDir);
267
- const userCommit = userIsGit ? await getShortCommit(userDir) : null;
268
- let userRemoteUrl = '';
269
- if (userIsGit) {
270
- try {
271
- const git = simpleGit(userDir);
272
- const remotes = await git.getRemotes(true);
273
- const origin = remotes.find(r => r.name === 'origin');
274
- userRemoteUrl = origin?.refs?.fetch || '';
275
- }
276
- catch { /* no remote */ }
277
- }
278
- const userStatus = !userOnDisk
279
- ? chalk.gray('not created')
280
- : !userIsGit
281
- ? chalk.gray('local (no remote)')
282
- : chalk.green('cloned');
283
- const userCommitLabel = userCommit ? chalk.gray(`(${userCommit})`) : '';
284
- console.log(chalk.bold('\nUser (~/.agents/)'));
285
- console.log(` ${chalk.cyan('user'.padEnd(12))} ${userRemoteUrl || '~/.agents/'} ${userStatus} ${userCommitLabel}`);
286
- if (!userIsGit) {
287
- console.log(chalk.gray(` ${''.padEnd(12)} scaffold one with: agents repo init`));
288
- }
289
- // Extra repos
290
- const extras = meta.extraRepos || {};
291
- const aliases = Object.keys(extras);
292
- console.log(chalk.bold('\nExtras:'));
293
- if (aliases.length === 0) {
294
- console.log(chalk.gray(' (none — add one with `agents repo add <source>`)\n'));
295
- return;
296
- }
297
- for (const alias of aliases) {
298
- const config = extras[alias];
299
- const dir = resolveExtraRepoDir(alias, config);
300
- const onDisk = fs.existsSync(dir);
301
- const commit = onDisk ? await getShortCommit(dir) : null;
302
- const status = !config.enabled
303
- ? chalk.yellow('disabled')
304
- : !onDisk
305
- ? chalk.red('missing')
306
- : chalk.green('enabled');
307
- const commitLabel = commit ? chalk.gray(`(${commit})`) : '';
308
- console.log(` ${chalk.cyan(alias.padEnd(12))} ${config.url} ${status} ${commitLabel}`);
309
- }
310
- console.log('');
352
+ .description('Show all repos: branch, ahead/behind, dirty counts, URL, commit.')
353
+ .action(async (alias) => {
354
+ await listRepos(alias);
311
355
  });
312
356
  repoCmd
313
357
  .command('remove <alias>')
@@ -335,6 +379,7 @@ export function registerRepoCommands(program) {
335
379
  }
336
380
  delete extras[alias];
337
381
  updateMeta({ extraRepos: extras });
382
+ syncMarketplacesForDefaults();
338
383
  console.log(chalk.green(`Removed "${alias}"`));
339
384
  });
340
385
  repoCmd
@@ -351,7 +396,7 @@ export function registerRepoCommands(program) {
351
396
  });
352
397
  repoCmd
353
398
  .command('pull [alias]')
354
- .description('Pull updates. Aliases: "system" (~/.agents-system/), "user" (~/.agents/), or any registered extra. No arg pulls all.')
399
+ .description('Pull updates. Aliases: "system" (~/.agents/.system/), "user" (~/.agents/), or any registered extra. No arg pulls all.')
355
400
  .action(async (alias) => {
356
401
  const targets = collectRepoTargets(alias);
357
402
  if (!targets) {
@@ -483,46 +528,43 @@ export function registerRepoCommands(program) {
483
528
  }
484
529
  });
485
530
  repoCmd
486
- .command('status [alias]')
487
- .description('Per-repo summary: branch, ahead/behind upstream, working-tree state')
488
- .action(async (alias) => {
489
- const targets = collectRepoTargets(alias) || [];
490
- if (targets.length === 0) {
491
- console.log(chalk.gray('No git repos found.'));
492
- return;
493
- }
494
- console.log('');
495
- console.log(` ${chalk.gray('REPO'.padEnd(12))} ${chalk.gray('BRANCH'.padEnd(28))} ${chalk.gray('REMOTE'.padEnd(12))} ${chalk.gray('LOCAL')}`);
496
- for (const t of targets) {
497
- if (!fs.existsSync(t.dir)) {
498
- console.log(` ${chalk.cyan(t.alias.padEnd(12))} ${chalk.red('missing')} ${chalk.gray(t.dir)}`);
499
- continue;
500
- }
501
- if (!isGitRepo(t.dir)) {
502
- console.log(` ${chalk.cyan(t.alias.padEnd(12))} ${chalk.gray('not a git repo')} ${chalk.gray(t.dir)}`);
503
- continue;
504
- }
505
- try {
506
- const git = simpleGit(t.dir);
507
- const status = await git.status();
508
- const branch = status.tracking || status.current || '(detached)';
509
- const remoteRaw = (status.ahead ?? 0) === 0 && (status.behind ?? 0) === 0
510
- ? 'in sync'
511
- : `+${status.ahead ?? 0} -${status.behind ?? 0}`;
512
- const remoteColored = (status.ahead ?? 0) === 0 && (status.behind ?? 0) === 0
513
- ? chalk.green(remoteRaw)
514
- : chalk.yellow(remoteRaw);
515
- const tree = status.isClean()
516
- ? chalk.green('clean')
517
- : chalk.yellow(`${status.files.length} uncommitted`);
518
- const remotePad = ' '.repeat(Math.max(0, 12 - remoteRaw.length));
519
- console.log(` ${chalk.cyan(t.alias.padEnd(12))} ${branch.padEnd(28)} ${remoteColored}${remotePad} ${tree}`);
531
+ .command('refresh [agent]')
532
+ .description('Re-materialize resources into installed agent version homes. No git, no network.')
533
+ .option('-y, --yes', 'Auto-sync everything without prompting')
534
+ .option('--skip-clis', 'Skip CLI version install/upgrade from agents.yaml')
535
+ .action(async (arg, options) => {
536
+ let agentFilter;
537
+ if (arg) {
538
+ if (!isAgentName(arg)) {
539
+ console.log(chalk.red(`Unknown agent "${arg}".`));
540
+ console.log(chalk.gray(`Available: ${ALL_AGENT_IDS.join(', ')}`));
541
+ process.exitCode = 1;
542
+ return;
520
543
  }
521
- catch (err) {
522
- console.log(` ${chalk.cyan(t.alias.padEnd(12))} ${chalk.red('error')} ${err.message}`);
544
+ agentFilter = resolveAgentName(arg);
545
+ }
546
+ try {
547
+ await refresh({
548
+ agentFilter,
549
+ skipPrompts: options.yes,
550
+ skipClis: options.skipClis,
551
+ });
552
+ console.log(chalk.green('\nRefresh complete'));
553
+ }
554
+ catch (err) {
555
+ if (isPromptCancelled(err)) {
556
+ console.log(chalk.yellow('\nCancelled'));
557
+ return;
523
558
  }
559
+ console.error(chalk.red(err.message));
560
+ process.exit(1);
524
561
  }
525
- console.log('');
562
+ });
563
+ repoCmd
564
+ .command('status [alias]', { hidden: true })
565
+ .description('Alias of `list` (kept for muscle memory).')
566
+ .action(async (alias) => {
567
+ await listRepos(alias);
526
568
  });
527
569
  }
528
570
  /**
@@ -564,5 +606,6 @@ async function toggle(alias, enabled) {
564
606
  }
565
607
  extras[alias] = { ...extras[alias], enabled };
566
608
  updateMeta({ extraRepos: extras });
609
+ syncMarketplacesForDefaults();
567
610
  console.log(chalk.green(`${enabled ? 'Enabled' : 'Disabled'} "${alias}"`));
568
611
  }
@@ -18,6 +18,7 @@ export interface ResourceRow {
18
18
  name: string;
19
19
  description?: string;
20
20
  extra?: string;
21
+ extra2?: string;
21
22
  targets: SyncTarget[];
22
23
  buildDetail: () => string;
23
24
  }
@@ -25,6 +26,7 @@ export interface ResourceViewOptions {
25
26
  resourcePlural: string;
26
27
  resourceSingular: string;
27
28
  extraLabel?: string;
29
+ extra2Label?: string;
28
30
  rows: ResourceRow[];
29
31
  emptyMessage: string;
30
32
  centralPath?: string;
@@ -68,10 +68,13 @@ function formatPickerRow(row, opts) {
68
68
  const extra = opts.extraLabel
69
69
  ? chalk.gray(padRight(row.extra ?? '-', 10))
70
70
  : '';
71
+ const extra2 = opts.extra2Label
72
+ ? chalk.gray(padRight(row.extra2 ?? '-', 16))
73
+ : '';
71
74
  const descRaw = row.description ? truncate(row.description, 40) : '';
72
75
  const desc = padRight(chalk.gray(descRaw), 42);
73
76
  const sync = formatSyncSummary(row.targets, opts);
74
- return `${name} ${extra}${desc} ${sync}`;
77
+ return `${name} ${extra}${extra2}${desc} ${sync}`;
75
78
  }
76
79
  /** Render resources as a plain-text table (used when output is piped). */
77
80
  function printResourceTable(opts) {
@@ -83,10 +86,13 @@ function printResourceTable(opts) {
83
86
  const extra = opts.extraLabel
84
87
  ? padRight(row.extra ?? '-', 10)
85
88
  : '';
89
+ const extra2 = opts.extra2Label
90
+ ? padRight(row.extra2 ?? '-', 16)
91
+ : '';
86
92
  const descRaw = row.description ? truncate(row.description, 40) : '-';
87
93
  const desc = padRight(chalk.gray(descRaw), 42);
88
94
  const sync = formatSyncSummary(row.targets, opts);
89
- console.log(`${name} ${extra}${desc} ${sync}`);
95
+ console.log(`${name} ${extra}${extra2}${desc} ${sync}`);
90
96
  }
91
97
  console.log();
92
98
  const summary = [
@@ -103,9 +109,12 @@ function buildTableHeader(opts) {
103
109
  const extra = opts.extraLabel
104
110
  ? chalk.bold(padRight(opts.extraLabel, 10))
105
111
  : '';
112
+ const extra2 = opts.extra2Label
113
+ ? chalk.bold(padRight(opts.extra2Label, 16))
114
+ : '';
106
115
  const desc = padRight(chalk.bold('Description'), 42);
107
116
  const sync = chalk.bold('Synced');
108
- return `${name} ${extra}${desc} ${sync}`;
117
+ return `${name} ${extra}${extra2}${desc} ${sync}`;
109
118
  }
110
119
  /** Compact sync summary: "everywhere", "14 of 16 installs", or "not installed". */
111
120
  function formatSyncSummary(targets, opts) {
@@ -43,9 +43,19 @@ function isPromptCancelled(err) {
43
43
  err.name === 'ExitPromptError' ||
44
44
  err.code === 'ERR_USE_AFTER_CLOSE'));
45
45
  }
46
- /** Interactive job picker. Returns the selected job name or null on cancel/empty. */
47
- async function pickJob(message, filter, alternatives = []) {
48
- let jobs = listAllJobs();
46
+ /**
47
+ * Interactive job picker. Returns the selected job name or null on cancel/empty.
48
+ *
49
+ * `cwd` is opt-in: pass `process.cwd()` only for inspect-class commands
50
+ * (`view`) whose backing operation tolerates project-layer entries. Mutation
51
+ * (`remove`/`edit`/`pause`/`resume`) and execution (`run`) callers omit it,
52
+ * which limits the picker — and therefore the user — to user-layer routines
53
+ * only. Without that guard, a cloned public repo's `.agents/routines/<name>.yml`
54
+ * would surface in `agents routines run`'s picker and execute with an
55
+ * attacker-supplied prompt under the user's Claude session.
56
+ */
57
+ async function pickJob(message, filter, alternatives = [], cwd) {
58
+ let jobs = listAllJobs(cwd);
49
59
  if (filter) {
50
60
  jobs = jobs.filter(filter);
51
61
  }
@@ -114,7 +124,7 @@ export function registerRoutinesCommands(program) {
114
124
  .command('list')
115
125
  .description('See all scheduled jobs, when they run next, and their last execution status')
116
126
  .action(() => {
117
- const jobs = listAllJobs();
127
+ const jobs = listAllJobs(process.cwd());
118
128
  if (jobs.length === 0) {
119
129
  console.log(chalk.gray('No jobs configured'));
120
130
  console.log(chalk.gray(' Add a job: agents routines add <path-to-job.yml>'));
@@ -149,7 +159,14 @@ export function registerRoutinesCommands(program) {
149
159
  for (const job of jobs) {
150
160
  const nextRun = scheduler.getNextRun(job.name);
151
161
  const nextStr = humanizeNextRun(nextRun ?? null, now, job.timezone);
152
- const schedStr = humanizeCron(job.schedule, job.timezone);
162
+ let schedStr = humanizeCron(job.schedule, job.timezone);
163
+ if (job.endAt) {
164
+ const end = new Date(job.endAt);
165
+ const endLabel = Number.isFinite(end.getTime())
166
+ ? end.toLocaleDateString()
167
+ : job.endAt;
168
+ schedStr = `${schedStr} (until ${endLabel})`;
169
+ }
153
170
  const latestRun = getLatestRun(job.name);
154
171
  const lastStatus = latestRun?.status || '-';
155
172
  const repoInfo = formatRepoLink(job.repo);
@@ -189,6 +206,7 @@ export function registerRoutinesCommands(program) {
189
206
  .option('-t, --timeout <timeout>', 'Kill the agent if it runs longer than this (e.g., 10m, 2h, 3d, 1w; max 1w)', '10m')
190
207
  .option('--timezone <tz>', 'Interpret schedule in this timezone (e.g., America/Los_Angeles)')
191
208
  .option('--at <time>', 'One-shot mode: run once at this time (e.g., "14:30" or "2026-02-24 09:00"), then disable')
209
+ .option('--end-at <iso>', 'Stop firing on or after this ISO 8601 timestamp (e.g., "2026-12-31T23:59:00Z"); routine auto-disables.')
192
210
  .option('--disabled', 'Create the routine but keep it paused (enable later with resume)')
193
211
  .action(async (nameOrPath, options) => {
194
212
  // Check if inline mode (has flags) or file mode
@@ -242,6 +260,7 @@ export function registerRoutinesCommands(program) {
242
260
  prompt: options.prompt,
243
261
  timezone: options.timezone,
244
262
  ...(runOnce ? { runOnce: true } : {}),
263
+ ...(options.endAt ? { endAt: options.endAt } : {}),
245
264
  };
246
265
  const errors = validateJob(config);
247
266
  if (errors.length > 0) {
@@ -329,11 +348,11 @@ export function registerRoutinesCommands(program) {
329
348
  .description('Show the full YAML configuration for a routine')
330
349
  .action(async (name) => {
331
350
  if (!name) {
332
- name = await pickJob('Select job to view', undefined, ['agents routines view <name>']) ?? undefined;
351
+ name = await pickJob('Select job to view', undefined, ['agents routines view <name>'], process.cwd()) ?? undefined;
333
352
  if (!name)
334
353
  return;
335
354
  }
336
- const job = readJob(name);
355
+ const job = readJob(name, process.cwd());
337
356
  if (!job) {
338
357
  console.log(chalk.red(`Job '${name}' not found`));
339
358
  process.exit(1);
@@ -430,6 +449,12 @@ export function registerRoutinesCommands(program) {
430
449
  if (!name)
431
450
  return;
432
451
  }
452
+ // Execution is intentionally user-only: a routine spawns a full agent
453
+ // session with a YAML-supplied prompt, so a cloned public repo's
454
+ // `.agents/routines/<name>.yml` would be a prompt-injection vector if
455
+ // `run` honored the project layer. `list` / `view` stay project-aware
456
+ // for inspection; `run`, `remove`, `edit`, `pause`, `resume` stay on
457
+ // the trusted user layer.
433
458
  const job = readJob(name);
434
459
  if (!job) {
435
460
  console.log(chalk.red(`Job '${name}' not found`));
@@ -556,7 +556,7 @@ Examples:
556
556
  }
557
557
  const presets = Array.from(presetSet).sort();
558
558
  if (presets.length === 0) {
559
- console.log(chalk.red('No presets found. Define presets in ~/.agents-system/rules/rules.yaml or ~/.agents/rules/rules.yaml.'));
559
+ console.log(chalk.red('No presets found. Define presets in ~/.agents/.system/rules/rules.yaml or ~/.agents/rules/rules.yaml.'));
560
560
  process.exit(1);
561
561
  }
562
562
  let chosen = options.preset;
@@ -738,6 +738,7 @@ export function buildResumeCommand(session) {
738
738
  case 'rush':
739
739
  case 'hermes':
740
740
  case 'grok':
741
+ case 'kimi':
741
742
  // Grok (and some others) sessions are captured artifacts, not resumable the same way.
742
743
  return null;
743
744
  }
@@ -2,10 +2,10 @@
2
2
  * First-run setup command.
3
3
  *
4
4
  * Registers the `agents setup` command which clones the system repo into
5
- * ~/.agents-system/ and installs agent CLIs with resource syncing.
5
+ * ~/.agents/.system/ and installs agent CLIs with resource syncing.
6
6
  */
7
7
  import type { Command } from 'commander';
8
- /** First-run setup. Clones ~/.agents-system/ from the system repo if needed. */
8
+ /** First-run setup. Clones ~/.agents/.system/ from the system repo if needed. */
9
9
  export declare function runSetup(program: Command, options?: {
10
10
  force?: boolean;
11
11
  suppressFooter?: boolean;
@@ -13,7 +13,7 @@ export declare function runSetup(program: Command, options?: {
13
13
  }): Promise<void>;
14
14
  /**
15
15
  * Ensure the system repo exists before running a command that needs it.
16
- * If ~/.agents-system/ is not a git repo AND we're in an interactive TTY,
16
+ * If ~/.agents/.system/ is not a git repo AND we're in an interactive TTY,
17
17
  * prompt the user to run setup now. In non-interactive mode, print a clear
18
18
  * error and exit.
19
19
  */
@@ -2,7 +2,7 @@
2
2
  * First-run setup command.
3
3
  *
4
4
  * Registers the `agents setup` command which clones the system repo into
5
- * ~/.agents-system/ and installs agent CLIs with resource syncing.
5
+ * ~/.agents/.system/ and installs agent CLIs with resource syncing.
6
6
  */
7
7
  import chalk from 'chalk';
8
8
  import ora from 'ora';
@@ -52,12 +52,12 @@ async function importAgent(agentId, version) {
52
52
  return { success: false, error: err.message };
53
53
  }
54
54
  }
55
- /** First-run setup. Clones ~/.agents-system/ from the system repo if needed. */
55
+ /** First-run setup. Clones ~/.agents/.system/ from the system repo if needed. */
56
56
  export async function runSetup(program, options = {}) {
57
57
  const agentsDir = getAgentsDir();
58
58
  const alreadyConfigured = isGitRepo(agentsDir);
59
59
  if (alreadyConfigured && !options.force) {
60
- console.log(chalk.gray('~/.agents-system/ is already set up.'));
60
+ console.log(chalk.gray('~/.agents/.system/ is already set up.'));
61
61
  console.log(chalk.gray('\nTo sync updates: agents repo pull system'));
62
62
  console.log(chalk.gray('To re-run setup: agents setup --force'));
63
63
  return;
@@ -73,10 +73,10 @@ export async function runSetup(program, options = {}) {
73
73
  if (options.systemRepo === false) {
74
74
  ensureAgentsDir();
75
75
  console.log(chalk.gray('Skipping system repo clone (--no-system-repo).'));
76
- console.log(chalk.gray(`Populate ~/.agents-system/ yourself before running other commands that depend on it.\n`));
76
+ console.log(chalk.gray(`Populate ~/.agents/.system/ yourself before running other commands that depend on it.\n`));
77
77
  }
78
78
  else {
79
- console.log(chalk.gray(`Cloning the system repo from ${systemRepoSlug(systemRepo)} into ~/.agents-system/.\n`));
79
+ console.log(chalk.gray(`Cloning the system repo from ${systemRepoSlug(systemRepo)} into ~/.agents/.system/.\n`));
80
80
  ensureAgentsDir();
81
81
  const spinner = ora('Cloning system repo...').start();
82
82
  if (isGitRepo(agentsDir)) {
@@ -93,7 +93,7 @@ export async function runSetup(program, options = {}) {
93
93
  // Check git is available
94
94
  try {
95
95
  const { execSync } = await import('child_process');
96
- execSync('which git', { stdio: 'ignore' });
96
+ execSync('git --version', { stdio: 'ignore' });
97
97
  }
98
98
  catch {
99
99
  spinner.fail('git is not installed');
@@ -175,7 +175,7 @@ export async function runSetup(program, options = {}) {
175
175
  }
176
176
  /**
177
177
  * Ensure the system repo exists before running a command that needs it.
178
- * If ~/.agents-system/ is not a git repo AND we're in an interactive TTY,
178
+ * If ~/.agents/.system/ is not a git repo AND we're in an interactive TTY,
179
179
  * prompt the user to run setup now. In non-interactive mode, print a clear
180
180
  * error and exit.
181
181
  */
@@ -203,23 +203,23 @@ export function registerSetupCommand(program) {
203
203
  const setupCmd = program
204
204
  .command('setup')
205
205
  .description('First-time setup. Clones a config repo and installs agent CLIs.')
206
- .option('-f, --force', 'Re-run setup even if ~/.agents-system/ already exists (use with caution)')
207
- .option('--no-system-repo', 'Skip cloning the system repo (you must populate ~/.agents-system/ yourself)');
206
+ .option('-f, --force', 'Re-run setup even if ~/.agents/.system/ already exists (use with caution)')
207
+ .option('--no-system-repo', 'Skip cloning the system repo (you must populate ~/.agents/.system/ yourself)');
208
208
  setHelpSections(setupCmd, {
209
209
  examples: `
210
- # First-time setup (clones the system repo into ~/.agents-system/)
210
+ # First-time setup (clones the system repo into ~/.agents/.system/)
211
211
  agents setup
212
212
 
213
- # Re-run after corruption or to repair ~/.agents-system/
213
+ # Re-run after corruption or to repair ~/.agents/.system/
214
214
  agents setup --force
215
215
  `,
216
216
  notes: `
217
217
  What it does:
218
- 1. Clones the system repo into ~/.agents-system/
219
- 2. Installs agent CLIs based on agents.yaml in that repo
220
- 3. Syncs commands, skills, hooks, and MCP servers to each version
218
+ 1. Clones the system repo into ~/.agents/.system/
219
+ 2. Imports any unmanaged agent installations it finds
221
220
 
222
- Non-interactive alternative: agents pull
221
+ To install CLIs from agents.yaml and sync resources into version homes:
222
+ agents repo refresh -y
223
223
  `,
224
224
  });
225
225
  setupCmd.action(async (options) => {
@@ -4,7 +4,8 @@ import * as fs from 'fs';
4
4
  import * as os from 'os';
5
5
  import * as path from 'path';
6
6
  import { select, checkbox } from '@inquirer/prompts';
7
- import { AGENTS, SKILLS_CAPABLE_AGENTS, resolveAgentName, formatAgentError, agentLabel, } from '../lib/agents.js';
7
+ import { AGENTS, resolveAgentName, formatAgentError, agentLabel, } from '../lib/agents.js';
8
+ import { capableAgents } from '../lib/capabilities.js';
8
9
  import { cloneRepo } from '../lib/git.js';
9
10
  import { discoverSkillsFromRepo, installSkillCentrally, listInstalledSkills, listInstalledSkillsWithScope, getSkillInfo, getSkillRules, getSkillsDir, countSkillFiles, tryParseSkillMetadata, diffVersionSkills, iterSkillsCapableVersions, removeSkillFromVersion, } from '../lib/skills.js';
10
11
  import { getGlobalDefault, resolveVersionAlias, syncResourcesToVersion, promptAgentVersionSelection, getVersionHomePath, } from '../lib/versions.js';
@@ -54,7 +55,7 @@ When to use:
54
55
  const resolved = resolveAgentName(parts[0]);
55
56
  if (!resolved) {
56
57
  spinner.stop();
57
- console.log(chalk.red(formatAgentError(parts[0], SKILLS_CAPABLE_AGENTS)));
58
+ console.log(chalk.red(formatAgentError(parts[0], capableAgents('skills'))));
58
59
  process.exit(1);
59
60
  }
60
61
  filterAgent = resolved;
@@ -251,7 +252,7 @@ Examples:
251
252
  let selectedAgents;
252
253
  let versionSelections;
253
254
  if (options.agents) {
254
- const result = await resolveAgentTargetsAutoInstalling(options.agents, SKILLS_CAPABLE_AGENTS, { yes: options.yes });
255
+ const result = await resolveAgentTargetsAutoInstalling(options.agents, capableAgents('skills'), { yes: options.yes });
255
256
  if (!result) {
256
257
  console.log(chalk.gray('Cancelled.'));
257
258
  return;
@@ -260,7 +261,7 @@ Examples:
260
261
  versionSelections = result.versionSelections;
261
262
  }
262
263
  else {
263
- const result = await promptAgentVersionSelection(SKILLS_CAPABLE_AGENTS, {
264
+ const result = await promptAgentVersionSelection(capableAgents('skills'), {
264
265
  skipPrompts: options.yes,
265
266
  });
266
267
  selectedAgents = result.selectedAgents;
@@ -441,7 +442,7 @@ Examples:
441
442
  const cwd = process.cwd();
442
443
  const allSkills = [];
443
444
  const seenNames = new Set();
444
- for (const agentId of SKILLS_CAPABLE_AGENTS) {
445
+ for (const agentId of capableAgents('skills')) {
445
446
  const skills = listInstalledSkillsWithScope(agentId, cwd);
446
447
  for (const skill of skills) {
447
448
  if (!seenNames.has(skill.name)) {