@phnx-labs/agents-cli 1.20.4 → 1.20.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +49 -18
  3. package/dist/commands/browser.js +31 -4
  4. package/dist/commands/cli.js +1 -1
  5. package/dist/commands/cloud.js +1 -1
  6. package/dist/commands/commands.js +2 -0
  7. package/dist/commands/computer.js +10 -2
  8. package/dist/commands/defaults.d.ts +7 -0
  9. package/dist/commands/defaults.js +89 -0
  10. package/dist/commands/doctor.js +1 -1
  11. package/dist/commands/exec.js +73 -19
  12. package/dist/commands/hooks.js +6 -6
  13. package/dist/commands/inspect.d.ts +26 -0
  14. package/dist/commands/inspect.js +590 -0
  15. package/dist/commands/mcp.js +17 -16
  16. package/dist/commands/models.js +1 -1
  17. package/dist/commands/packages.js +6 -4
  18. package/dist/commands/permissions.js +13 -12
  19. package/dist/commands/plugins.d.ts +13 -0
  20. package/dist/commands/plugins.js +100 -11
  21. package/dist/commands/prune.js +3 -2
  22. package/dist/commands/pull.d.ts +12 -5
  23. package/dist/commands/pull.js +26 -422
  24. package/dist/commands/push.d.ts +14 -0
  25. package/dist/commands/push.js +30 -0
  26. package/dist/commands/repo.d.ts +1 -1
  27. package/dist/commands/repo.js +155 -112
  28. package/dist/commands/resource-view.d.ts +2 -0
  29. package/dist/commands/resource-view.js +12 -3
  30. package/dist/commands/routines.js +32 -7
  31. package/dist/commands/rules.js +4 -4
  32. package/dist/commands/secrets.js +46 -9
  33. package/dist/commands/sessions.js +1 -0
  34. package/dist/commands/setup.d.ts +3 -3
  35. package/dist/commands/setup.js +17 -17
  36. package/dist/commands/skills.js +6 -5
  37. package/dist/commands/subagents.js +5 -4
  38. package/dist/commands/sync.d.ts +18 -5
  39. package/dist/commands/sync.js +251 -65
  40. package/dist/commands/teams.js +109 -11
  41. package/dist/commands/tmux.d.ts +25 -0
  42. package/dist/commands/tmux.js +415 -0
  43. package/dist/commands/trash.d.ts +2 -2
  44. package/dist/commands/trash.js +1 -1
  45. package/dist/commands/versions.js +2 -2
  46. package/dist/commands/view.d.ts +12 -1
  47. package/dist/commands/view.js +128 -40
  48. package/dist/commands/workflows.js +4 -3
  49. package/dist/commands/worktree.d.ts +4 -5
  50. package/dist/commands/worktree.js +4 -4
  51. package/dist/index.js +106 -41
  52. package/dist/lib/agents.d.ts +23 -10
  53. package/dist/lib/agents.js +88 -25
  54. package/dist/lib/auto-pull-worker.d.ts +1 -1
  55. package/dist/lib/auto-pull-worker.js +2 -2
  56. package/dist/lib/auto-pull.d.ts +1 -1
  57. package/dist/lib/auto-pull.js +1 -1
  58. package/dist/lib/beta.d.ts +1 -1
  59. package/dist/lib/beta.js +1 -1
  60. package/dist/lib/browser/chrome.d.ts +10 -0
  61. package/dist/lib/browser/chrome.js +84 -3
  62. package/dist/lib/capabilities.js +2 -0
  63. package/dist/lib/commands.d.ts +28 -1
  64. package/dist/lib/commands.js +125 -20
  65. package/dist/lib/doctor-diff.js +2 -2
  66. package/dist/lib/exec.d.ts +14 -0
  67. package/dist/lib/exec.js +59 -5
  68. package/dist/lib/fuzzy.d.ts +12 -2
  69. package/dist/lib/fuzzy.js +29 -4
  70. package/dist/lib/git.js +8 -1
  71. package/dist/lib/hooks.d.ts +2 -2
  72. package/dist/lib/hooks.js +97 -10
  73. package/dist/lib/mcp.js +32 -2
  74. package/dist/lib/migrate.d.ts +51 -0
  75. package/dist/lib/migrate.js +233 -5
  76. package/dist/lib/models.js +62 -15
  77. package/dist/lib/permissions.d.ts +59 -2
  78. package/dist/lib/permissions.js +299 -7
  79. package/dist/lib/plugin-marketplace.d.ts +98 -40
  80. package/dist/lib/plugin-marketplace.js +196 -93
  81. package/dist/lib/plugins.d.ts +21 -4
  82. package/dist/lib/plugins.js +130 -49
  83. package/dist/lib/profiles-presets.js +12 -12
  84. package/dist/lib/project-launch.d.ts +70 -0
  85. package/dist/lib/project-launch.js +404 -0
  86. package/dist/lib/pty-client.js +1 -1
  87. package/dist/lib/pty-server.d.ts +1 -1
  88. package/dist/lib/pty-server.js +8 -5
  89. package/dist/lib/refresh.d.ts +26 -0
  90. package/dist/lib/refresh.js +315 -0
  91. package/dist/lib/resource-patterns.d.ts +1 -1
  92. package/dist/lib/resource-patterns.js +1 -1
  93. package/dist/lib/resources/commands.js +2 -2
  94. package/dist/lib/resources/hooks.d.ts +1 -1
  95. package/dist/lib/resources/hooks.js +1 -1
  96. package/dist/lib/resources/mcp.d.ts +1 -1
  97. package/dist/lib/resources/mcp.js +5 -6
  98. package/dist/lib/resources/permissions.js +5 -2
  99. package/dist/lib/resources/rules.js +3 -2
  100. package/dist/lib/resources/skills.js +3 -2
  101. package/dist/lib/resources/types.d.ts +1 -1
  102. package/dist/lib/resources.d.ts +2 -0
  103. package/dist/lib/resources.js +4 -3
  104. package/dist/lib/rotate.d.ts +1 -1
  105. package/dist/lib/rotate.js +7 -19
  106. package/dist/lib/routines.d.ts +16 -4
  107. package/dist/lib/routines.js +67 -17
  108. package/dist/lib/rules/compile.js +22 -10
  109. package/dist/lib/rules/rules.js +3 -3
  110. package/dist/lib/run-config.d.ts +9 -0
  111. package/dist/lib/run-config.js +35 -0
  112. package/dist/lib/run-defaults.d.ts +42 -0
  113. package/dist/lib/run-defaults.js +180 -0
  114. package/dist/lib/runner.js +16 -3
  115. package/dist/lib/scheduler.js +15 -1
  116. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  117. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  118. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +9 -1
  119. package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
  120. package/dist/lib/secrets/install-helper.d.ts +11 -3
  121. package/dist/lib/secrets/install-helper.js +48 -6
  122. package/dist/lib/secrets/linux.d.ts +56 -9
  123. package/dist/lib/secrets/linux.js +327 -59
  124. package/dist/lib/session/db.js +15 -2
  125. package/dist/lib/session/discover.js +118 -3
  126. package/dist/lib/session/parse.js +3 -0
  127. package/dist/lib/session/types.d.ts +1 -1
  128. package/dist/lib/session/types.js +1 -1
  129. package/dist/lib/shims.d.ts +18 -9
  130. package/dist/lib/shims.js +133 -50
  131. package/dist/lib/skills.d.ts +1 -1
  132. package/dist/lib/skills.js +10 -9
  133. package/dist/lib/staleness/detectors/commands.d.ts +3 -0
  134. package/dist/lib/staleness/detectors/commands.js +46 -0
  135. package/dist/lib/staleness/detectors/hooks.d.ts +3 -0
  136. package/dist/lib/staleness/detectors/hooks.js +44 -0
  137. package/dist/lib/staleness/detectors/mcp.d.ts +3 -0
  138. package/dist/lib/staleness/detectors/mcp.js +31 -0
  139. package/dist/lib/staleness/detectors/permissions.d.ts +3 -0
  140. package/dist/lib/staleness/detectors/permissions.js +201 -0
  141. package/dist/lib/staleness/detectors/plugins.d.ts +8 -0
  142. package/dist/lib/staleness/detectors/plugins.js +23 -0
  143. package/dist/lib/staleness/detectors/rules.d.ts +3 -0
  144. package/dist/lib/staleness/detectors/rules.js +34 -0
  145. package/dist/lib/staleness/detectors/skills.d.ts +3 -0
  146. package/dist/lib/staleness/detectors/skills.js +71 -0
  147. package/dist/lib/staleness/detectors/subagents.d.ts +3 -0
  148. package/dist/lib/staleness/detectors/subagents.js +50 -0
  149. package/dist/lib/staleness/detectors/types.d.ts +22 -0
  150. package/dist/lib/staleness/detectors/types.js +1 -0
  151. package/dist/lib/staleness/detectors/workflows.d.ts +3 -0
  152. package/dist/lib/staleness/detectors/workflows.js +28 -0
  153. package/dist/lib/staleness/registry.d.ts +26 -0
  154. package/dist/lib/staleness/registry.js +123 -0
  155. package/dist/lib/staleness/writers/commands.d.ts +3 -0
  156. package/dist/lib/staleness/writers/commands.js +111 -0
  157. package/dist/lib/staleness/writers/hooks.d.ts +3 -0
  158. package/dist/lib/staleness/writers/hooks.js +47 -0
  159. package/dist/lib/staleness/writers/kinds.d.ts +10 -0
  160. package/dist/lib/staleness/writers/kinds.js +15 -0
  161. package/dist/lib/staleness/writers/lazy-map.d.ts +13 -0
  162. package/dist/lib/staleness/writers/lazy-map.js +19 -0
  163. package/dist/lib/staleness/writers/mcp.d.ts +10 -0
  164. package/dist/lib/staleness/writers/mcp.js +19 -0
  165. package/dist/lib/staleness/writers/permissions.d.ts +13 -0
  166. package/dist/lib/staleness/writers/permissions.js +26 -0
  167. package/dist/lib/staleness/writers/plugins.d.ts +7 -0
  168. package/dist/lib/staleness/writers/plugins.js +31 -0
  169. package/dist/lib/staleness/writers/rules.d.ts +7 -0
  170. package/dist/lib/staleness/writers/rules.js +55 -0
  171. package/dist/lib/staleness/writers/skills.d.ts +3 -0
  172. package/dist/lib/staleness/writers/skills.js +81 -0
  173. package/dist/lib/staleness/writers/sources.d.ts +16 -0
  174. package/dist/lib/staleness/writers/sources.js +72 -0
  175. package/dist/lib/staleness/writers/subagents.d.ts +3 -0
  176. package/dist/lib/staleness/writers/subagents.js +53 -0
  177. package/dist/lib/staleness/writers/types.d.ts +36 -0
  178. package/dist/lib/staleness/writers/types.js +1 -0
  179. package/dist/lib/staleness/writers/workflows.d.ts +7 -0
  180. package/dist/lib/staleness/writers/workflows.js +31 -0
  181. package/dist/lib/state.d.ts +34 -11
  182. package/dist/lib/state.js +58 -13
  183. package/dist/lib/subagents.d.ts +0 -2
  184. package/dist/lib/subagents.js +6 -6
  185. package/dist/lib/teams/agents.js +1 -1
  186. package/dist/lib/teams/api.d.ts +67 -0
  187. package/dist/lib/teams/api.js +78 -0
  188. package/dist/lib/teams/parsers.d.ts +1 -1
  189. package/dist/lib/tmux/binary.d.ts +67 -0
  190. package/dist/lib/tmux/binary.js +141 -0
  191. package/dist/lib/tmux/index.d.ts +8 -0
  192. package/dist/lib/tmux/index.js +8 -0
  193. package/dist/lib/tmux/paths.d.ts +17 -0
  194. package/dist/lib/tmux/paths.js +30 -0
  195. package/dist/lib/tmux/session.d.ts +122 -0
  196. package/dist/lib/tmux/session.js +305 -0
  197. package/dist/lib/types.d.ts +73 -13
  198. package/dist/lib/types.js +1 -1
  199. package/dist/lib/usage.js +1 -1
  200. package/dist/lib/versions.d.ts +4 -4
  201. package/dist/lib/versions.js +138 -496
  202. package/dist/lib/workflows.d.ts +2 -4
  203. package/dist/lib/workflows.js +3 -4
  204. package/package.json +6 -3
  205. package/scripts/postinstall.js +16 -63
  206. package/dist/commands/status.d.ts +0 -9
  207. 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`));
@@ -4,7 +4,7 @@ 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, ALL_AGENT_IDS, resolveAgentName, formatAgentError, agentLabel, } from '../lib/agents.js';
7
+ import { AGENTS, agentConfigDirName, ALL_AGENT_IDS, resolveAgentName, formatAgentError, agentLabel, } from '../lib/agents.js';
8
8
  import { cloneRepo } from '../lib/git.js';
9
9
  import { discoverInstructionsFromRepo, discoverRuleFilesFromRepo, installInstructionsCentrally, uninstallInstructions, listInstalledInstructionsWithScope, instructionsExists, getInstructionsContent, listCentralRules, } from '../lib/rules/rules.js';
10
10
  import { listInstalledVersions, getGlobalDefault, resolveVersionAlias, syncResourcesToVersion, promptAgentVersionSelection, getVersionHomePath, } from '../lib/versions.js';
@@ -426,7 +426,7 @@ Examples:
426
426
  return;
427
427
  }
428
428
  const home = getVersionHomePath(agentId, requestedVersion);
429
- const filePath = path.join(home, `.${agentId}`, AGENTS[agentId].instructionsFile);
429
+ const filePath = path.join(home, agentConfigDirName(agentId), AGENTS[agentId].instructionsFile);
430
430
  if (!fs.existsSync(filePath)) {
431
431
  console.log(chalk.yellow(`No user rules found for ${agentLabel(agentId)}@${requestedVersion}`));
432
432
  return;
@@ -486,7 +486,7 @@ Examples:
486
486
  }
487
487
  const home = getVersionHomePath(agentId, requestedVersion);
488
488
  const agent = AGENTS[agentId];
489
- const filePath = path.join(home, `.${agentId}`, agent.instructionsFile);
489
+ const filePath = path.join(home, agentConfigDirName(agentId), agent.instructionsFile);
490
490
  if (fs.existsSync(filePath)) {
491
491
  fs.unlinkSync(filePath);
492
492
  console.log(chalk.green(`Removed ${agent.instructionsFile} from ${agentLabel(agent.id)}@${requestedVersion}`));
@@ -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;
@@ -1052,15 +1052,15 @@ Examples:
1052
1052
  password += charClass[charIndex];
1053
1053
  }
1054
1054
  if (opts.copy) {
1055
- const { spawn } = await import('child_process');
1056
- const proc = spawn('pbcopy', [], { stdio: ['pipe', 'inherit', 'inherit'] });
1057
- proc.stdin.write(password);
1058
- proc.stdin.end();
1059
- await new Promise((resolve, reject) => {
1060
- proc.on('close', (code) => code === 0 ? resolve() : reject(new Error('pbcopy failed')));
1061
- proc.on('error', reject);
1062
- });
1063
- console.log(chalk.green(`Password copied to clipboard (${length} chars)`));
1055
+ try {
1056
+ await copyToClipboard(password);
1057
+ console.log(chalk.green(`Password copied to clipboard (${length} chars)`));
1058
+ }
1059
+ catch (err) {
1060
+ console.error(chalk.red(`Clipboard copy failed: ${err.message}`));
1061
+ console.error(chalk.gray('Re-run without --copy to print the password instead.'));
1062
+ process.exitCode = 1;
1063
+ }
1064
1064
  }
1065
1065
  else {
1066
1066
  console.log(password);
@@ -1069,3 +1069,40 @@ Examples:
1069
1069
  registerSecretsSyncCommands(cmd);
1070
1070
  registerSecretsMigrateAclCommand(cmd);
1071
1071
  }
1072
+ /**
1073
+ * Copy text to the system clipboard, cross-platform.
1074
+ * macOS: `pbcopy`. Windows: `clip`. Linux: tries `wl-copy` (Wayland), then
1075
+ * `xclip`, then `xsel` (X11). Throws with an install hint if none are present.
1076
+ */
1077
+ async function copyToClipboard(text) {
1078
+ const { spawn } = await import('child_process');
1079
+ const candidates = process.platform === 'darwin'
1080
+ ? [['pbcopy', []]]
1081
+ : process.platform === 'win32'
1082
+ ? [['clip', []]]
1083
+ : [
1084
+ ['wl-copy', []],
1085
+ ['xclip', ['-selection', 'clipboard']],
1086
+ ['xsel', ['--clipboard', '--input']],
1087
+ ];
1088
+ let lastErr = null;
1089
+ for (const [cmd, args] of candidates) {
1090
+ try {
1091
+ await new Promise((resolve, reject) => {
1092
+ const proc = spawn(cmd, args, { stdio: ['pipe', 'ignore', 'ignore'] });
1093
+ proc.on('error', reject);
1094
+ proc.on('close', (code) => (code === 0 ? resolve() : reject(new Error(`${cmd} exited ${code}`))));
1095
+ proc.stdin.write(text);
1096
+ proc.stdin.end();
1097
+ });
1098
+ return;
1099
+ }
1100
+ catch (err) {
1101
+ lastErr = err;
1102
+ }
1103
+ }
1104
+ const hint = process.platform === 'linux'
1105
+ ? ' Install one: wl-clipboard (Wayland) or xclip / xsel (X11).'
1106
+ : '';
1107
+ throw new Error(`no clipboard tool available (${lastErr?.message ?? 'none found'}).${hint}`);
1108
+ }
@@ -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
  */