@phnx-labs/agents-cli 1.20.4 → 1.20.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +48 -17
  3. package/dist/commands/cli.js +1 -1
  4. package/dist/commands/cloud.js +1 -1
  5. package/dist/commands/commands.js +2 -0
  6. package/dist/commands/doctor.js +1 -1
  7. package/dist/commands/exec.js +52 -16
  8. package/dist/commands/hooks.js +6 -6
  9. package/dist/commands/inspect.d.ts +26 -0
  10. package/dist/commands/inspect.js +590 -0
  11. package/dist/commands/mcp.js +17 -16
  12. package/dist/commands/models.js +1 -1
  13. package/dist/commands/packages.js +6 -4
  14. package/dist/commands/permissions.js +13 -12
  15. package/dist/commands/plugins.d.ts +13 -0
  16. package/dist/commands/plugins.js +100 -11
  17. package/dist/commands/prune.js +3 -2
  18. package/dist/commands/pull.d.ts +12 -5
  19. package/dist/commands/pull.js +26 -422
  20. package/dist/commands/push.d.ts +14 -0
  21. package/dist/commands/push.js +30 -0
  22. package/dist/commands/repo.d.ts +1 -1
  23. package/dist/commands/repo.js +155 -112
  24. package/dist/commands/resource-view.d.ts +2 -0
  25. package/dist/commands/resource-view.js +12 -3
  26. package/dist/commands/routines.js +32 -7
  27. package/dist/commands/rules.js +1 -1
  28. package/dist/commands/sessions.js +1 -0
  29. package/dist/commands/setup.d.ts +3 -3
  30. package/dist/commands/setup.js +15 -15
  31. package/dist/commands/skills.js +6 -5
  32. package/dist/commands/subagents.js +5 -4
  33. package/dist/commands/sync.d.ts +18 -5
  34. package/dist/commands/sync.js +251 -65
  35. package/dist/commands/teams.js +1 -0
  36. package/dist/commands/tmux.d.ts +25 -0
  37. package/dist/commands/tmux.js +415 -0
  38. package/dist/commands/trash.d.ts +2 -2
  39. package/dist/commands/trash.js +1 -1
  40. package/dist/commands/versions.js +2 -2
  41. package/dist/commands/view.js +9 -4
  42. package/dist/commands/workflows.js +4 -3
  43. package/dist/commands/worktree.d.ts +4 -5
  44. package/dist/commands/worktree.js +4 -4
  45. package/dist/index.js +68 -20
  46. package/dist/lib/agents.d.ts +19 -10
  47. package/dist/lib/agents.js +79 -25
  48. package/dist/lib/auto-pull-worker.d.ts +1 -1
  49. package/dist/lib/auto-pull-worker.js +2 -2
  50. package/dist/lib/auto-pull.d.ts +1 -1
  51. package/dist/lib/auto-pull.js +1 -1
  52. package/dist/lib/beta.d.ts +1 -1
  53. package/dist/lib/beta.js +1 -1
  54. package/dist/lib/capabilities.js +2 -0
  55. package/dist/lib/commands.d.ts +28 -1
  56. package/dist/lib/commands.js +125 -20
  57. package/dist/lib/doctor-diff.js +2 -2
  58. package/dist/lib/exec.d.ts +14 -0
  59. package/dist/lib/exec.js +39 -5
  60. package/dist/lib/fuzzy.d.ts +12 -2
  61. package/dist/lib/fuzzy.js +29 -4
  62. package/dist/lib/git.js +8 -1
  63. package/dist/lib/hooks.d.ts +2 -2
  64. package/dist/lib/hooks.js +97 -10
  65. package/dist/lib/mcp.js +32 -2
  66. package/dist/lib/migrate.d.ts +51 -0
  67. package/dist/lib/migrate.js +227 -1
  68. package/dist/lib/models.js +62 -15
  69. package/dist/lib/permissions.d.ts +36 -2
  70. package/dist/lib/permissions.js +217 -7
  71. package/dist/lib/plugin-marketplace.d.ts +98 -40
  72. package/dist/lib/plugin-marketplace.js +196 -93
  73. package/dist/lib/plugins.d.ts +21 -4
  74. package/dist/lib/plugins.js +130 -49
  75. package/dist/lib/profiles-presets.js +12 -12
  76. package/dist/lib/project-launch.d.ts +65 -0
  77. package/dist/lib/project-launch.js +367 -0
  78. package/dist/lib/pty-client.js +1 -1
  79. package/dist/lib/pty-server.d.ts +1 -1
  80. package/dist/lib/pty-server.js +1 -1
  81. package/dist/lib/refresh.d.ts +26 -0
  82. package/dist/lib/refresh.js +315 -0
  83. package/dist/lib/resource-patterns.d.ts +1 -1
  84. package/dist/lib/resource-patterns.js +1 -1
  85. package/dist/lib/resources/commands.js +2 -2
  86. package/dist/lib/resources/hooks.d.ts +1 -1
  87. package/dist/lib/resources/hooks.js +1 -1
  88. package/dist/lib/resources/mcp.d.ts +1 -1
  89. package/dist/lib/resources/mcp.js +5 -6
  90. package/dist/lib/resources/permissions.js +5 -2
  91. package/dist/lib/resources/rules.js +3 -2
  92. package/dist/lib/resources/skills.js +3 -2
  93. package/dist/lib/resources/types.d.ts +1 -1
  94. package/dist/lib/resources.js +2 -2
  95. package/dist/lib/rotate.d.ts +1 -1
  96. package/dist/lib/rotate.js +1 -1
  97. package/dist/lib/routines.d.ts +16 -4
  98. package/dist/lib/routines.js +67 -17
  99. package/dist/lib/rules/compile.js +22 -10
  100. package/dist/lib/rules/rules.js +3 -3
  101. package/dist/lib/runner.js +16 -3
  102. package/dist/lib/scheduler.js +15 -1
  103. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  104. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  105. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +9 -1
  106. package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
  107. package/dist/lib/secrets/linux.d.ts +44 -9
  108. package/dist/lib/secrets/linux.js +302 -48
  109. package/dist/lib/session/db.js +15 -2
  110. package/dist/lib/session/discover.js +118 -3
  111. package/dist/lib/session/parse.js +3 -0
  112. package/dist/lib/session/types.d.ts +1 -1
  113. package/dist/lib/session/types.js +1 -1
  114. package/dist/lib/shims.d.ts +10 -9
  115. package/dist/lib/shims.js +101 -50
  116. package/dist/lib/skills.d.ts +1 -1
  117. package/dist/lib/skills.js +10 -9
  118. package/dist/lib/staleness/detectors/commands.d.ts +3 -0
  119. package/dist/lib/staleness/detectors/commands.js +46 -0
  120. package/dist/lib/staleness/detectors/hooks.d.ts +3 -0
  121. package/dist/lib/staleness/detectors/hooks.js +44 -0
  122. package/dist/lib/staleness/detectors/mcp.d.ts +3 -0
  123. package/dist/lib/staleness/detectors/mcp.js +31 -0
  124. package/dist/lib/staleness/detectors/permissions.d.ts +3 -0
  125. package/dist/lib/staleness/detectors/permissions.js +201 -0
  126. package/dist/lib/staleness/detectors/plugins.d.ts +8 -0
  127. package/dist/lib/staleness/detectors/plugins.js +23 -0
  128. package/dist/lib/staleness/detectors/rules.d.ts +3 -0
  129. package/dist/lib/staleness/detectors/rules.js +34 -0
  130. package/dist/lib/staleness/detectors/skills.d.ts +3 -0
  131. package/dist/lib/staleness/detectors/skills.js +71 -0
  132. package/dist/lib/staleness/detectors/subagents.d.ts +3 -0
  133. package/dist/lib/staleness/detectors/subagents.js +50 -0
  134. package/dist/lib/staleness/detectors/types.d.ts +22 -0
  135. package/dist/lib/staleness/detectors/types.js +1 -0
  136. package/dist/lib/staleness/detectors/workflows.d.ts +3 -0
  137. package/dist/lib/staleness/detectors/workflows.js +28 -0
  138. package/dist/lib/staleness/registry.d.ts +26 -0
  139. package/dist/lib/staleness/registry.js +123 -0
  140. package/dist/lib/staleness/writers/commands.d.ts +3 -0
  141. package/dist/lib/staleness/writers/commands.js +111 -0
  142. package/dist/lib/staleness/writers/hooks.d.ts +3 -0
  143. package/dist/lib/staleness/writers/hooks.js +47 -0
  144. package/dist/lib/staleness/writers/kinds.d.ts +10 -0
  145. package/dist/lib/staleness/writers/kinds.js +15 -0
  146. package/dist/lib/staleness/writers/lazy-map.d.ts +13 -0
  147. package/dist/lib/staleness/writers/lazy-map.js +19 -0
  148. package/dist/lib/staleness/writers/mcp.d.ts +10 -0
  149. package/dist/lib/staleness/writers/mcp.js +19 -0
  150. package/dist/lib/staleness/writers/permissions.d.ts +13 -0
  151. package/dist/lib/staleness/writers/permissions.js +26 -0
  152. package/dist/lib/staleness/writers/plugins.d.ts +7 -0
  153. package/dist/lib/staleness/writers/plugins.js +31 -0
  154. package/dist/lib/staleness/writers/rules.d.ts +7 -0
  155. package/dist/lib/staleness/writers/rules.js +55 -0
  156. package/dist/lib/staleness/writers/skills.d.ts +3 -0
  157. package/dist/lib/staleness/writers/skills.js +81 -0
  158. package/dist/lib/staleness/writers/sources.d.ts +16 -0
  159. package/dist/lib/staleness/writers/sources.js +72 -0
  160. package/dist/lib/staleness/writers/subagents.d.ts +3 -0
  161. package/dist/lib/staleness/writers/subagents.js +53 -0
  162. package/dist/lib/staleness/writers/types.d.ts +36 -0
  163. package/dist/lib/staleness/writers/types.js +1 -0
  164. package/dist/lib/staleness/writers/workflows.d.ts +7 -0
  165. package/dist/lib/staleness/writers/workflows.js +31 -0
  166. package/dist/lib/state.d.ts +34 -11
  167. package/dist/lib/state.js +58 -13
  168. package/dist/lib/subagents.d.ts +0 -2
  169. package/dist/lib/subagents.js +6 -6
  170. package/dist/lib/teams/agents.js +1 -1
  171. package/dist/lib/teams/parsers.d.ts +1 -1
  172. package/dist/lib/tmux/binary.d.ts +67 -0
  173. package/dist/lib/tmux/binary.js +141 -0
  174. package/dist/lib/tmux/index.d.ts +8 -0
  175. package/dist/lib/tmux/index.js +8 -0
  176. package/dist/lib/tmux/paths.d.ts +17 -0
  177. package/dist/lib/tmux/paths.js +30 -0
  178. package/dist/lib/tmux/session.d.ts +122 -0
  179. package/dist/lib/tmux/session.js +305 -0
  180. package/dist/lib/types.d.ts +58 -7
  181. package/dist/lib/types.js +1 -1
  182. package/dist/lib/usage.js +1 -1
  183. package/dist/lib/versions.d.ts +4 -4
  184. package/dist/lib/versions.js +135 -493
  185. package/dist/lib/workflows.d.ts +2 -4
  186. package/dist/lib/workflows.js +3 -4
  187. package/package.json +2 -2
  188. package/scripts/postinstall.js +16 -63
  189. package/dist/commands/status.d.ts +0 -9
  190. package/dist/commands/status.js +0 -25
@@ -0,0 +1,315 @@
1
+ /**
2
+ * Materialization helpers — install manifest CLIs, register MCP servers,
3
+ * sync resources into installed version homes, register hooks, add shims to
4
+ * PATH, prompt for missing default versions, install declared host-CLIs.
5
+ *
6
+ * Used by `agents repo refresh` (user-facing) and any other caller that needs
7
+ * to re-derive local state from declared configuration. Does NOT do any git
8
+ * operations — that lives in `agents repo pull`.
9
+ */
10
+ import * as fs from 'fs';
11
+ import * as path from 'path';
12
+ import chalk from 'chalk';
13
+ import ora from 'ora';
14
+ import { select, confirm } from '@inquirer/prompts';
15
+ import { capableAgents } from './capabilities.js';
16
+ import { AGENTS, ALL_AGENT_IDS, getAllCliStates, registerMcpToTargets, agentLabel, } from './agents.js';
17
+ import { readManifest, MANIFEST_FILENAME } from './manifest.js';
18
+ import { getUserAgentsDir } from './state.js';
19
+ import { installVersion, listInstalledVersions, getGlobalDefault, setGlobalDefault, getVersionHomePath, syncResourcesToVersion, getAvailableResources, getActuallySyncedResources, getNewResources, getProjectOnlyResources, hasNewResources, promptNewResourceSelection, promptResourceSelection, resolveConfiguredAgentTargets, } from './versions.js';
20
+ import { listCliStatus, installCli, describeMethod, describeCheck, selectInstallMethod, } from './cli-resources.js';
21
+ import { ensureShimCurrent, isShimsInPath, addShimsToPath, getPathSetupInstructions, switchConfigSymlink, switchHomeFileSymlinks, } from './shims.js';
22
+ import { parseHookManifest, registerHooksToSettings } from './hooks.js';
23
+ import { isPromptCancelled } from '../commands/utils.js';
24
+ /**
25
+ * Old repo layout stored promptcuts under claude/promptcuts.yaml (agent-scoped).
26
+ * The new layout is `~/.agents/.system/promptcuts.yaml` at the repo root — the
27
+ * hook reads from a fixed path so it survives version upgrades. If the root
28
+ * file doesn't exist yet but an agent-scoped one does, hoist the first one found.
29
+ */
30
+ function migratePromptcutsToRoot(agentsDir) {
31
+ const rootPath = path.join(agentsDir, 'promptcuts.yaml');
32
+ if (fs.existsSync(rootPath))
33
+ return;
34
+ const agentDirs = ['claude', 'codex', 'cursor', 'gemini', 'opencode'];
35
+ for (const dir of agentDirs) {
36
+ const legacyPath = path.join(agentsDir, dir, 'promptcuts.yaml');
37
+ if (fs.existsSync(legacyPath)) {
38
+ try {
39
+ fs.renameSync(legacyPath, rootPath);
40
+ console.log(chalk.gray(`Moved ${dir}/promptcuts.yaml → promptcuts.yaml (repo root)`));
41
+ return;
42
+ }
43
+ catch {
44
+ // Best-effort migration; hook still works if the user moves it manually.
45
+ }
46
+ }
47
+ }
48
+ }
49
+ /**
50
+ * Re-materialize local state from declared configuration: install CLI versions,
51
+ * register MCP servers, sync resources to version homes, register hooks, add
52
+ * shims to PATH, prompt for missing defaults, install declared host-CLIs.
53
+ *
54
+ * Idempotent — safe to run repeatedly. No network operations.
55
+ */
56
+ export async function refresh(options = {}) {
57
+ const { agentFilter, skipPrompts = false, skipClis = false } = options;
58
+ const agentsDir = getUserAgentsDir();
59
+ migratePromptcutsToRoot(agentsDir);
60
+ const manifest = readManifest(agentsDir);
61
+ if (!manifest) {
62
+ console.log(chalk.gray(`No ${MANIFEST_FILENAME} found`));
63
+ }
64
+ // 1. Install/upgrade CLI versions from agents.yaml
65
+ if (!skipClis && manifest?.agents) {
66
+ console.log(chalk.bold('\nCLI Versions:\n'));
67
+ const cliAgents = Object.keys(manifest.agents);
68
+ for (const agentId of cliAgents) {
69
+ if (agentFilter && agentId !== agentFilter)
70
+ continue;
71
+ const agent = AGENTS[agentId];
72
+ if (!agent)
73
+ continue;
74
+ const cliSpinner = ora(`Checking ${agentLabel(agent.id)}...`).start();
75
+ const versions = listInstalledVersions(agentId);
76
+ const targetVersion = manifest.agents[agentId] || 'latest';
77
+ const result = await installVersion(agentId, targetVersion, (msg) => { cliSpinner.text = msg; });
78
+ if (result.success) {
79
+ const isNew = versions.length === 0;
80
+ if (isNew) {
81
+ cliSpinner.succeed(`Installed ${agentLabel(agent.id)}@${result.installedVersion}`);
82
+ }
83
+ else {
84
+ cliSpinner.succeed(`${agentLabel(agent.id)}@${result.installedVersion}`);
85
+ }
86
+ ensureShimCurrent(agentId);
87
+ }
88
+ else {
89
+ cliSpinner.warn(`${agentLabel(agent.id)}: ${result.error}`);
90
+ }
91
+ }
92
+ }
93
+ // 2. Register MCP servers
94
+ if (manifest?.mcp && Object.keys(manifest.mcp).length > 0) {
95
+ console.log(chalk.bold('\nMCP Servers:\n'));
96
+ for (const [name, config] of Object.entries(manifest.mcp)) {
97
+ if (!config.command || config.transport === 'http')
98
+ continue;
99
+ const scopedAgents = (config.agents ? [...config.agents] : [...capableAgents('mcp')]).filter((id) => !agentFilter || id === agentFilter);
100
+ const scopedVersions = config.agentVersions
101
+ ? Object.fromEntries(Object.entries(config.agentVersions).filter(([agentId]) => !agentFilter || agentId === agentFilter))
102
+ : undefined;
103
+ const targets = resolveConfiguredAgentTargets(scopedAgents, scopedVersions, capableAgents('mcp'));
104
+ const results = await registerMcpToTargets(targets, name, config.command, config.scope || 'user', config.transport || 'stdio');
105
+ for (const result of results) {
106
+ if (result.success) {
107
+ const label = result.version
108
+ ? `${agentLabel(result.agentId)}@${result.version}`
109
+ : agentLabel(result.agentId);
110
+ console.log(` ${chalk.green('+')} ${name} -> ${label}`);
111
+ }
112
+ }
113
+ }
114
+ }
115
+ // 3. Sync resources to default version homes
116
+ const cliStates = await getAllCliStates();
117
+ const agentsToSync = agentFilter ? [agentFilter] : ALL_AGENT_IDS;
118
+ const available = getAvailableResources();
119
+ for (const agentId of agentsToSync) {
120
+ if (!cliStates[agentId]?.installed && listInstalledVersions(agentId).length === 0)
121
+ continue;
122
+ const defaultVer = getGlobalDefault(agentId);
123
+ if (!defaultVer)
124
+ continue;
125
+ const actuallySynced = getActuallySyncedResources(agentId, defaultVer);
126
+ const newResources = getNewResources(available, actuallySynced, getProjectOnlyResources());
127
+ const hasAnySynced = actuallySynced.commands.length > 0 ||
128
+ actuallySynced.skills.length > 0 ||
129
+ actuallySynced.hooks.length > 0 ||
130
+ actuallySynced.memory.length > 0 ||
131
+ actuallySynced.mcp.length > 0 ||
132
+ actuallySynced.permissions.length > 0 ||
133
+ actuallySynced.plugins.length > 0;
134
+ try {
135
+ let selection;
136
+ if (skipPrompts) {
137
+ if (!hasAnySynced || hasNewResources(newResources, agentId)) {
138
+ selection = {
139
+ commands: 'all', skills: 'all', hooks: 'all', memory: 'all',
140
+ mcp: 'all', permissions: 'all', subagents: 'all', plugins: 'all',
141
+ };
142
+ }
143
+ }
144
+ else if (!hasAnySynced) {
145
+ console.log(chalk.yellow(`\n${agentLabel(agentId)}@${defaultVer} has no synced resources.`));
146
+ const userSelection = await promptResourceSelection(agentId);
147
+ if (userSelection)
148
+ selection = userSelection;
149
+ }
150
+ else if (hasNewResources(newResources, agentId, defaultVer)) {
151
+ console.log(chalk.cyan(`\n${agentLabel(agentId)}@${defaultVer}:`));
152
+ const userSelection = await promptNewResourceSelection(agentId, newResources, defaultVer);
153
+ if (userSelection)
154
+ selection = userSelection;
155
+ }
156
+ if (selection && Object.keys(selection).length > 0) {
157
+ const syncResult = syncResourcesToVersion(agentId, defaultVer, selection);
158
+ const synced = [];
159
+ if (syncResult.commands)
160
+ synced.push('commands');
161
+ if (syncResult.skills)
162
+ synced.push('skills');
163
+ if (syncResult.hooks)
164
+ synced.push('hooks');
165
+ if (syncResult.memory.length > 0)
166
+ synced.push('memory');
167
+ if (syncResult.permissions)
168
+ synced.push('permissions');
169
+ if (syncResult.mcp.length > 0)
170
+ synced.push('mcp');
171
+ if (syncResult.plugins.length > 0)
172
+ synced.push('plugins');
173
+ if (synced.length > 0) {
174
+ console.log(chalk.green(` Synced: ${synced.join(', ')}`));
175
+ }
176
+ }
177
+ }
178
+ catch (err) {
179
+ if (isPromptCancelled(err)) {
180
+ console.log(chalk.gray('Skipped resource selection'));
181
+ }
182
+ else {
183
+ throw err;
184
+ }
185
+ }
186
+ }
187
+ // 4. Register hooks as lifecycle events
188
+ const hookManifest = parseHookManifest();
189
+ if (Object.keys(hookManifest).length > 0) {
190
+ let hookRegistered = 0;
191
+ const hookAgents = new Set(capableAgents('hooks'));
192
+ for (const agentId of agentsToSync) {
193
+ if (!hookAgents.has(agentId))
194
+ continue;
195
+ const versions = listInstalledVersions(agentId);
196
+ const defaultVer = getGlobalDefault(agentId);
197
+ const targetVersions = defaultVer ? [defaultVer] : versions.slice(-1);
198
+ for (const ver of targetVersions) {
199
+ const home = getVersionHomePath(agentId, ver);
200
+ const result = registerHooksToSettings(agentId, home, hookManifest);
201
+ hookRegistered += result.registered.length;
202
+ for (const error of result.errors) {
203
+ console.log(chalk.yellow(` Hook warning: ${error}`));
204
+ }
205
+ }
206
+ }
207
+ if (hookRegistered > 0) {
208
+ console.log(chalk.green(`\nRegistered ${hookRegistered} hook lifecycle event(s)`));
209
+ }
210
+ }
211
+ // 5. Auto-add shims to PATH
212
+ if (!isShimsInPath()) {
213
+ const pathResult = addShimsToPath();
214
+ if (pathResult.success && !pathResult.alreadyPresent) {
215
+ console.log(chalk.green(`\nAdded shims to ~/${pathResult.rcFile}`));
216
+ console.log(chalk.gray('Restart your shell or run: source ~/' + pathResult.rcFile));
217
+ }
218
+ else if (!pathResult.success) {
219
+ console.log(chalk.yellow('\nCould not auto-add shims to PATH:'));
220
+ console.log(chalk.gray(getPathSetupInstructions()));
221
+ }
222
+ }
223
+ // 6. Prompt for missing default versions
224
+ if (!skipPrompts) {
225
+ const agentsNeedingDefault = [];
226
+ for (const agentId of agentsToSync) {
227
+ const versions = listInstalledVersions(agentId);
228
+ if (versions.length > 0 && !getGlobalDefault(agentId)) {
229
+ agentsNeedingDefault.push(agentId);
230
+ }
231
+ }
232
+ const selectedVersions = [];
233
+ for (const agentId of agentsNeedingDefault) {
234
+ const versions = listInstalledVersions(agentId);
235
+ const agent = AGENTS[agentId];
236
+ const shouldSwitch = await select({
237
+ message: `${agentLabel(agent.id)} has no default version. Set one now?`,
238
+ choices: [
239
+ { name: 'Yes, pick a version', value: 'pick' },
240
+ { name: 'Skip for now', value: 'skip' },
241
+ ],
242
+ });
243
+ if (shouldSwitch === 'pick') {
244
+ const selectedVersion = await select({
245
+ message: `Select ${agentLabel(agent.id)} version:`,
246
+ choices: versions.map((v) => ({ name: v, value: v })),
247
+ });
248
+ selectedVersions.push({ agentId, version: selectedVersion });
249
+ }
250
+ }
251
+ for (const { agentId, version } of selectedVersions) {
252
+ const agent = AGENTS[agentId];
253
+ setGlobalDefault(agentId, version);
254
+ const symlinkResult = await switchConfigSymlink(agentId, version);
255
+ if (!symlinkResult.success) {
256
+ console.log(chalk.yellow(`Warning: ${symlinkResult.error}`));
257
+ }
258
+ else if (symlinkResult.backupPath) {
259
+ console.log(chalk.gray(`Backed up existing config to: ${symlinkResult.backupPath}`));
260
+ }
261
+ switchHomeFileSymlinks(agentId, version);
262
+ console.log(chalk.green(`Set ${agentLabel(agent.id)}@${version} as default`));
263
+ }
264
+ }
265
+ // 7. Install declared host-CLIs
266
+ try {
267
+ const { statuses, errors } = listCliStatus(process.cwd());
268
+ for (const err of errors) {
269
+ console.log(chalk.yellow(` CLI manifest parse error: ${err.file}: ${err.reason}`));
270
+ }
271
+ const missing = statuses.filter((s) => !s.installed);
272
+ if (missing.length > 0) {
273
+ console.log(chalk.bold('\nDeclared CLIs missing from this host:'));
274
+ for (const s of missing) {
275
+ const method = selectInstallMethod(s.manifest);
276
+ const action = method ? describeMethod(method) : chalk.red('no compatible install method');
277
+ console.log(` ${chalk.cyan(s.manifest.name.padEnd(20))} ${chalk.gray(action)}`);
278
+ }
279
+ console.log('');
280
+ if (!skipPrompts) {
281
+ const proceed = await confirm({ message: `Install ${missing.length} missing CLI(s) now?`, default: true });
282
+ if (proceed) {
283
+ for (const s of missing) {
284
+ console.log(chalk.bold(`\n→ ${s.manifest.name}`));
285
+ const result = installCli(s.manifest);
286
+ if (result.error) {
287
+ console.log(chalk.red(` ${result.error}`));
288
+ continue;
289
+ }
290
+ if (result.installed) {
291
+ console.log(chalk.green(` installed`));
292
+ if (s.manifest.postInstall) {
293
+ console.log(chalk.gray(s.manifest.postInstall.trim().split('\n').map((l) => ' ' + l).join('\n')));
294
+ }
295
+ }
296
+ else {
297
+ console.log(chalk.yellow(` install ran but \`${describeCheck(s.manifest.check)}\` still fails`));
298
+ }
299
+ }
300
+ }
301
+ else {
302
+ console.log(chalk.gray(`Skipped. Run 'agents cli install' later.`));
303
+ }
304
+ }
305
+ else {
306
+ console.log(chalk.gray(`Run 'agents cli install' to install them.`));
307
+ }
308
+ }
309
+ }
310
+ catch (err) {
311
+ if (!isPromptCancelled(err)) {
312
+ console.log(chalk.yellow(`CLI install skipped: ${err.message}`));
313
+ }
314
+ }
315
+ }
@@ -2,7 +2,7 @@
2
2
  * Resource selection patterns for agents.yaml versions: entries.
3
3
  *
4
4
  * Pattern syntax: [!]source:name
5
- * "system:*" — all resources from ~/.agents-system/
5
+ * "system:*" — all resources from ~/.agents/.system/
6
6
  * "user:*" — all resources from ~/.agents/
7
7
  * "rush:*" — all resources from ~/.agents-rush/ (extra repo alias)
8
8
  * "project:*" — all resources from .agents/ in the project root
@@ -2,7 +2,7 @@
2
2
  * Resource selection patterns for agents.yaml versions: entries.
3
3
  *
4
4
  * Pattern syntax: [!]source:name
5
- * "system:*" — all resources from ~/.agents-system/
5
+ * "system:*" — all resources from ~/.agents/.system/
6
6
  * "user:*" — all resources from ~/.agents/
7
7
  * "rush:*" — all resources from ~/.agents-rush/ (extra repo alias)
8
8
  * "project:*" — all resources from .agents/ in the project root
@@ -8,7 +8,7 @@
8
8
  import * as fs from 'fs';
9
9
  import * as path from 'path';
10
10
  import { getProjectAgentsDir, getUserAgentsDir, getSystemAgentsDir, getEnabledExtraRepos, } from '../state.js';
11
- import { AGENTS } from '../agents.js';
11
+ import { AGENTS, agentConfigDirName } from '../agents.js';
12
12
  import { markdownToToml } from '../convert.js';
13
13
  /**
14
14
  * Get the commands directory for a given layer root.
@@ -164,7 +164,7 @@ export class CommandsHandler {
164
164
  sync(agent, versionHome, cwd) {
165
165
  const agentConfig = AGENTS[agent];
166
166
  const targetFormat = this.format(agent);
167
- const targetDir = path.join(versionHome, `.${agent}`, this.targetDir(agent));
167
+ const targetDir = path.join(versionHome, agentConfigDirName(agent), this.targetDir(agent));
168
168
  // Ensure target directory exists
169
169
  fs.mkdirSync(targetDir, { recursive: true });
170
170
  // Get all resolved commands
@@ -2,7 +2,7 @@
2
2
  * HooksHandler - ResourceHandler implementation for hooks.
3
3
  *
4
4
  * Hook declarations live in:
5
- * - System: ~/.agents-system/hooks.yaml (npm-shipped defaults)
5
+ * - System: ~/.agents/.system/hooks.yaml (npm-shipped defaults)
6
6
  * - User: `hooks:` section of ~/.agents/agents.yaml
7
7
  * - Project: <project>/.agents/hooks.yaml
8
8
  *
@@ -2,7 +2,7 @@
2
2
  * HooksHandler - ResourceHandler implementation for hooks.
3
3
  *
4
4
  * Hook declarations live in:
5
- * - System: ~/.agents-system/hooks.yaml (npm-shipped defaults)
5
+ * - System: ~/.agents/.system/hooks.yaml (npm-shipped defaults)
6
6
  * - User: `hooks:` section of ~/.agents/agents.yaml
7
7
  * - Project: <project>/.agents/hooks.yaml
8
8
  *
@@ -2,7 +2,7 @@
2
2
  * MCP resource handler - lists, resolves, and syncs MCP server configs across layers.
3
3
  *
4
4
  * MCP servers are stored as YAML files in mcp/ directories:
5
- * ~/.agents-system/mcp/ (system)
5
+ * ~/.agents/.system/mcp/ (system)
6
6
  * ~/.agents/mcp/ (user)
7
7
  * .agents/mcp/ (project)
8
8
  *
@@ -2,7 +2,7 @@
2
2
  * MCP resource handler - lists, resolves, and syncs MCP server configs across layers.
3
3
  *
4
4
  * MCP servers are stored as YAML files in mcp/ directories:
5
- * ~/.agents-system/mcp/ (system)
5
+ * ~/.agents/.system/mcp/ (system)
6
6
  * ~/.agents/mcp/ (user)
7
7
  * .agents/mcp/ (project)
8
8
  *
@@ -13,9 +13,8 @@ import * as fs from 'fs';
13
13
  import * as path from 'path';
14
14
  import * as yaml from 'yaml';
15
15
  import * as TOML from 'smol-toml';
16
+ import { capableAgents } from '../capabilities.js';
16
17
  import { getSystemMcpDir, getUserMcpDir, getProjectAgentsDir, getEnabledExtraRepos, } from '../state.js';
17
- /** Agents from resources/types.ts that support MCP. */
18
- const MCP_CAPABLE_AGENTS = ['claude', 'codex', 'gemini', 'cursor', 'opencode', 'openclaw', 'antigravity', 'grok'];
19
18
  /**
20
19
  * Parse an MCP YAML file into an McpItem.
21
20
  */
@@ -414,7 +413,7 @@ function syncToOpenClawConfig(configPath, items) {
414
413
  export const McpHandler = {
415
414
  kind: 'mcp',
416
415
  listAll(agent, cwd) {
417
- if (!MCP_CAPABLE_AGENTS.includes(agent)) {
416
+ if (!capableAgents('mcp').includes(agent)) {
418
417
  return [];
419
418
  }
420
419
  const results = new Map();
@@ -435,7 +434,7 @@ export const McpHandler = {
435
434
  return Array.from(results.values());
436
435
  },
437
436
  resolve(agent, name, cwd) {
438
- if (!MCP_CAPABLE_AGENTS.includes(agent)) {
437
+ if (!capableAgents('mcp').includes(agent)) {
439
438
  return null;
440
439
  }
441
440
  const layerDirs = getLayerDirs(cwd);
@@ -469,7 +468,7 @@ export const McpHandler = {
469
468
  return null;
470
469
  },
471
470
  sync(agent, versionHome, cwd) {
472
- if (!MCP_CAPABLE_AGENTS.includes(agent)) {
471
+ if (!capableAgents('mcp').includes(agent)) {
473
472
  return;
474
473
  }
475
474
  const items = this.listAll(agent, cwd);
@@ -9,7 +9,8 @@
9
9
  import * as fs from 'fs';
10
10
  import * as path from 'path';
11
11
  import { getSystemAgentsDir, getUserAgentsDir, getProjectAgentsDir, getEnabledExtraRepos, } from '../state.js';
12
- import { parsePermissionSet, applyPermissionsToVersion, mergePermissionSets, PERMISSIONS_CAPABLE_AGENTS, } from '../permissions.js';
12
+ import { parsePermissionSet, applyPermissionsToVersion, mergePermissionSets, } from '../permissions.js';
13
+ import { isCapable } from '../capabilities.js';
13
14
  /**
14
15
  * Get the permissions directory for a given layer root.
15
16
  */
@@ -66,6 +67,8 @@ function getAgentConfigPath(agent, versionHome) {
66
67
  return path.join(versionHome, '.codex', 'config.toml');
67
68
  case 'opencode':
68
69
  return path.join(versionHome, '.opencode', 'opencode.jsonc');
70
+ case 'kimi':
71
+ return path.join(versionHome, '.kimi-code', 'config.toml');
69
72
  default:
70
73
  return null;
71
74
  }
@@ -143,7 +146,7 @@ export const PermissionsHandler = {
143
146
  */
144
147
  sync(agent, versionHome, cwd) {
145
148
  // Only sync to agents that support permissions
146
- if (!PERMISSIONS_CAPABLE_AGENTS.includes(agent)) {
149
+ if (!isCapable(agent, 'allowlist')) {
147
150
  return;
148
151
  }
149
152
  const resolved = this.listAll(agent, cwd);
@@ -11,6 +11,7 @@
11
11
  import * as fs from 'fs';
12
12
  import * as path from 'path';
13
13
  import { getSystemRulesDir, getUserRulesDir, getProjectAgentsDir, getEnabledExtraRepos, } from '../state.js';
14
+ import { agentConfigDirName } from '../agents.js';
14
15
  const SUBRULES_DIR = 'subrules';
15
16
  const SUBRULES_README = 'README.md';
16
17
  /**
@@ -131,7 +132,7 @@ export const RulesHandler = {
131
132
  // Rules sync is handled by the compose module and syncResourcesToVersion.
132
133
  // This method ensures the agent config directory exists.
133
134
  // The actual composition and write happens in versions.ts via composeRulesFromState().
134
- const targetDir = path.join(versionHome, `.${agent}`);
135
+ const targetDir = path.join(versionHome, agentConfigDirName(agent));
135
136
  if (!fs.existsSync(targetDir)) {
136
137
  fs.mkdirSync(targetDir, { recursive: true });
137
138
  }
@@ -141,6 +142,6 @@ export const RulesHandler = {
141
142
  },
142
143
  targetDir(agent) {
143
144
  // Rules don't have a target subdirectory - they're written to the instructions file
144
- return `.${agent}`;
145
+ return agentConfigDirName(agent);
145
146
  },
146
147
  };
@@ -8,6 +8,7 @@ import * as fs from 'fs';
8
8
  import * as path from 'path';
9
9
  import * as yaml from 'yaml';
10
10
  import { getSystemSkillsDir, getUserSkillsDir, getProjectAgentsDir, getEnabledExtraRepos, } from '../state.js';
11
+ import { agentConfigDirName } from '../agents.js';
11
12
  /** Default provider uses the real state module. */
12
13
  const defaultProvider = {
13
14
  getSystemSkillsDir,
@@ -200,7 +201,7 @@ export function createSkillsHandler(provider = defaultProvider) {
200
201
  return null;
201
202
  },
202
203
  sync(agent, versionHome, cwd) {
203
- const targetDir = path.join(versionHome, `.${agent}`, 'skills');
204
+ const targetDir = path.join(versionHome, agentConfigDirName(agent), 'skills');
204
205
  // Ensure target directory exists
205
206
  if (!fs.existsSync(targetDir)) {
206
207
  fs.mkdirSync(targetDir, { recursive: true });
@@ -230,7 +231,7 @@ export function createSkillsHandler(provider = defaultProvider) {
230
231
  return 'md';
231
232
  },
232
233
  targetDir(agent) {
233
- return `.${agent}/skills`;
234
+ return `${agentConfigDirName(agent)}/skills`;
234
235
  },
235
236
  };
236
237
  }
@@ -5,7 +5,7 @@
5
5
  * - Union: All resources from all layers are combined
6
6
  * - Override on name conflict: Higher layer wins (project > user > system)
7
7
  */
8
- export type AgentId = 'claude' | 'codex' | 'gemini' | 'cursor' | 'opencode' | 'openclaw' | 'antigravity' | 'grok';
8
+ export type AgentId = 'claude' | 'codex' | 'gemini' | 'cursor' | 'opencode' | 'openclaw' | 'antigravity' | 'grok' | 'kimi';
9
9
  export type Layer = 'system' | 'user' | 'project';
10
10
  export type ResourceKind = 'command' | 'hook' | 'skill' | 'rule' | 'mcp' | 'permission' | 'subagent' | 'workflow';
11
11
  /** A resolved resource with its origin layer. */
@@ -12,7 +12,7 @@ import { listInstalledInstructionsWithScope } from './rules/rules.js';
12
12
  import { getEffectiveHome } from './versions.js';
13
13
  import { listMcpServerConfigs } from './mcp.js';
14
14
  import { WorkflowsHandler } from './resources/workflows.js';
15
- import { WORKFLOW_CAPABLE_AGENTS } from './workflows.js';
15
+ import { isCapable } from './capabilities.js';
16
16
  import { getProjectAgentsDir, getUserAgentsDir, getSystemAgentsDir, getEnabledExtraRepos, } from './state.js';
17
17
  /**
18
18
  * Resolve a single resource by kind + name using project > user > system precedence.
@@ -163,7 +163,7 @@ export function getAgentResources(agentId, options = {}) {
163
163
  }
164
164
  // Workflows (claude only)
165
165
  const workflows = [];
166
- if (WORKFLOW_CAPABLE_AGENTS.includes(agentId)) {
166
+ if (isCapable(agentId, 'workflows')) {
167
167
  for (const w of WorkflowsHandler.listAll(agentId, cwd)) {
168
168
  workflows.push({ name: w.name, path: w.path, scope: w.layer === 'project' ? 'project' : 'user' });
169
169
  }
@@ -38,7 +38,7 @@ export declare function getProjectRunStrategy(agent: AgentId, startPath: string)
38
38
  /**
39
39
  * Resolve the configured strategy. Lookup order:
40
40
  * 1. project-local agents.yaml (nearest to `startPath`)
41
- * 2. ~/.agents-system/agents.yaml
41
+ * 2. ~/.agents/.system/agents.yaml
42
42
  * 3. default: `available` (use the pinned default version when healthy,
43
43
  * otherwise fall through to a healthy account so a single rate-limited
44
44
  * account doesn't block the run).
@@ -55,7 +55,7 @@ export function getProjectRunStrategy(agent, startPath) {
55
55
  /**
56
56
  * Resolve the configured strategy. Lookup order:
57
57
  * 1. project-local agents.yaml (nearest to `startPath`)
58
- * 2. ~/.agents-system/agents.yaml
58
+ * 2. ~/.agents/.system/agents.yaml
59
59
  * 3. default: `available` (use the pinned default version when healthy,
60
60
  * otherwise fall through to a healthy account so a single rate-limited
61
61
  * account doesn't block the run).
@@ -32,6 +32,7 @@ export interface JobConfig {
32
32
  config?: Record<string, unknown>;
33
33
  version?: string;
34
34
  runOnce?: boolean;
35
+ endAt?: string;
35
36
  }
36
37
  /** Metadata for a single job execution, persisted as JSON in the run directory. */
37
38
  export interface RunMeta {
@@ -45,10 +46,19 @@ export interface RunMeta {
45
46
  completedAt: string | null;
46
47
  exitCode: number | null;
47
48
  }
48
- /** List all job configs from ~/.agents/routines/. */
49
- export declare function listJobs(): JobConfig[];
50
- /** Read a single job config by name. Returns null if not found. */
51
- export declare function readJob(name: string): JobConfig | null;
49
+ /**
50
+ * List all job configs, scanning project > user routine dirs.
51
+ * Project routines (`<project>/.agents/routines/`) shadow user routines of the
52
+ * same name. Project discovery is opt-in via `cwd`; the daemon (which calls
53
+ * `listJobs()` with no argument) only sees user routines.
54
+ */
55
+ export declare function listJobs(cwd?: string): JobConfig[];
56
+ /**
57
+ * Read a single job config by name, checking project > user.
58
+ * Project discovery is opt-in via `cwd`; daemon callers pass no argument and
59
+ * only resolve user routines.
60
+ */
61
+ export declare function readJob(name: string, cwd?: string): JobConfig | null;
52
62
  /** Write a job config to disk, omitting fields that match defaults. */
53
63
  export declare function writeJob(config: JobConfig): void;
54
64
  /** Delete a job config file by name. Returns true if the file existed. */
@@ -57,6 +67,8 @@ export declare function deleteJob(name: string): boolean;
57
67
  export declare function setJobEnabled(name: string, enabled: boolean): void;
58
68
  /** Validate a partial job config, returning a list of human-readable errors. */
59
69
  export declare function validateJob(config: Partial<JobConfig>): string[];
70
+ /** True when a job's endAt has already elapsed. False when endAt is unset or in the future. */
71
+ export declare function isPastEndAt(config: Pick<JobConfig, 'endAt'>, now?: Date): boolean;
60
72
  /** Expand built-in and user-defined template variables in a job's prompt string. */
61
73
  export declare function resolveJobPrompt(config: JobConfig): string;
62
74
  /** Parse a human-readable timeout string (e.g. "10m", "2h", "1h30m", "3d", "1w") into milliseconds.