@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
package/dist/lib/hooks.js CHANGED
@@ -11,8 +11,8 @@ import * as fs from 'fs';
11
11
  import * as path from 'path';
12
12
  import * as yaml from 'yaml';
13
13
  import * as TOML from 'smol-toml';
14
- import { AGENTS, HOOKS_CAPABLE_AGENTS } from './agents.js';
15
- import { supports, explainSkip } from './capabilities.js';
14
+ import { AGENTS, agentConfigDirName } from './agents.js';
15
+ import { supports, explainSkip, capableAgents } from './capabilities.js';
16
16
  import { setGeminiAutoUpdateDisabled, updateGeminiSettings } from './gemini-settings.js';
17
17
  import { getHooksDir as getSystemHooksDir, getUserHooksDir, getUserAgentsDir, getSystemAgentsDir, getProjectAgentsDir, getTrashHooksDir, getEnabledExtraRepos } from './state.js';
18
18
  function getCentralHooksDir() { return getUserHooksDir(); }
@@ -117,7 +117,7 @@ function isExecutable(mode) {
117
117
  function getHooksDir(agentId) {
118
118
  const agent = AGENTS[agentId];
119
119
  const home = getEffectiveHome(agentId);
120
- return path.join(home, `.${agentId}`, agent.hooksDir);
120
+ return path.join(home, agentConfigDirName(agentId), agent.hooksDir);
121
121
  }
122
122
  function getProjectHooksDirs(agentId, cwd) {
123
123
  const agent = AGENTS[agentId];
@@ -324,7 +324,7 @@ export function listInstalledHooksWithScope(agentId, cwd = process.cwd(), option
324
324
  }
325
325
  // User-scoped hooks (version-aware when home is provided)
326
326
  const home = options?.home || getEffectiveHome(agentId);
327
- const userDir = path.join(home, `.${agentId}`, agent.hooksDir);
327
+ const userDir = path.join(home, agentConfigDirName(agentId), agent.hooksDir);
328
328
  const userHooks = listHookEntriesFromDir(userDir);
329
329
  for (const hook of userHooks) {
330
330
  addHook(hook, 'user', agentId);
@@ -363,7 +363,7 @@ export async function installHooks(source, agents, options = {}) {
363
363
  */
364
364
  export function getVersionHooksDir(agent, version) {
365
365
  const home = getVersionHomePath(agent, version);
366
- return path.join(home, `.${agent}`, AGENTS[agent].hooksDir);
366
+ return path.join(home, agentConfigDirName(agent), AGENTS[agent].hooksDir);
367
367
  }
368
368
  /**
369
369
  * List hook entries in a specific version home.
@@ -489,7 +489,7 @@ export function removeHookFromVersion(agent, version, hookName) {
489
489
  */
490
490
  export function iterHooksCapableVersions(filter) {
491
491
  const pairs = [];
492
- const hookAgents = HOOKS_CAPABLE_AGENTS;
492
+ const hookAgents = capableAgents('hooks');
493
493
  const agents = filter?.agent ? [filter.agent] : hookAgents;
494
494
  for (const agent of agents) {
495
495
  if (!hookAgents.includes(agent))
@@ -592,7 +592,7 @@ export async function installHooksCentrally(source) {
592
592
  return { installed, errors };
593
593
  }
594
594
  /**
595
- * List hooks from user (~/.agents/hooks/) and system (~/.agents-system/hooks/) dirs.
595
+ * List hooks from user (~/.agents/hooks/) and system (~/.agents/.system/hooks/) dirs.
596
596
  * User dir takes priority; deduplication preserves first occurrence.
597
597
  */
598
598
  export function listCentralHooks() {
@@ -609,7 +609,7 @@ export function listCentralHooks() {
609
609
  return results;
610
610
  }
611
611
  /**
612
- * Parse hook manifests. Reads system hooks from ~/.agents-system/hooks.yaml
612
+ * Parse hook manifests. Reads system hooks from ~/.agents/.system/hooks.yaml
613
613
  * (npm-shipped defaults) and user hooks from the `hooks:` section of
614
614
  * ~/.agents/agents.yaml. Merges with user-wins-on-key-collision precedence.
615
615
  * A user entry with `enabled: false` disables the system-shipped hook of
@@ -663,7 +663,7 @@ const CODEX_MATCHER_EVENTS = new Set(['PreToolUse', 'PostToolUse', 'SessionStart
663
663
  * Register hooks as lifecycle events in an agent's config.
664
664
  * Reads hooks.yaml manifest, merges into the agent's config file(s).
665
665
  * Only manages hooks whose command paths are under ~/.agents/hooks/ or
666
- * ~/.agents-system/hooks/. Does not remove user-added hooks.
666
+ * ~/.agents/.system/hooks/. Does not remove user-added hooks.
667
667
  *
668
668
  * @param agentsDirOverride - When provided, treats this single dir as the
669
669
  * only managed hook root. Used by tests to inject a temp path. In normal
@@ -702,7 +702,7 @@ export function registerHooksToSettings(agentId, versionHome, hookManifest, agen
702
702
  // Scripts are copied into the version home during sync — prefer that stable
703
703
  // local path so registered commands don't break when source dirs change.
704
704
  const localHooksDir = !overrideRoots
705
- ? path.join(versionHome, `.${agentId}`, AGENTS[agentId].hooksDir)
705
+ ? path.join(versionHome, agentConfigDirName(agentId), AGENTS[agentId].hooksDir)
706
706
  : null;
707
707
  const resolveScript = (script) => {
708
708
  if (overrideRoots) {
@@ -740,6 +740,9 @@ export function registerHooksToSettings(agentId, versionHome, hookManifest, agen
740
740
  if (agentId === 'grok') {
741
741
  return registerHooksForGrok(versionHome, manifest, resolveScript, managedPrefixes);
742
742
  }
743
+ if (agentId === 'kimi') {
744
+ return registerHooksForKimi(versionHome, manifest, resolveScript, managedPrefixes);
745
+ }
743
746
  return { registered: [], errors: [] };
744
747
  }
745
748
  /**
@@ -1217,3 +1220,87 @@ function registerHooksForGrok(versionHome, manifest, resolveScript, managedPrefi
1217
1220
  }
1218
1221
  return { registered, errors };
1219
1222
  }
1223
+ function registerHooksForKimi(versionHome, manifest, resolveScript, managedPrefixes) {
1224
+ const registered = [];
1225
+ const errors = [];
1226
+ const configPath = path.join(versionHome, '.kimi-code', 'config.toml');
1227
+ // Read existing config.toml
1228
+ let config = {};
1229
+ if (fs.existsSync(configPath)) {
1230
+ try {
1231
+ config = TOML.parse(fs.readFileSync(configPath, 'utf-8'));
1232
+ }
1233
+ catch {
1234
+ errors.push('Failed to parse config.toml');
1235
+ return { registered, errors };
1236
+ }
1237
+ }
1238
+ // Build set of current manifest command paths for GC
1239
+ const currentManifestPaths = new Set();
1240
+ for (const [hookName, hookDef] of Object.entries(manifest)) {
1241
+ if (!hookDef.events || hookDef.events.length === 0)
1242
+ continue;
1243
+ const resolved = resolveHookCommand(hookName, hookDef, resolveScript);
1244
+ if (resolved)
1245
+ currentManifestPaths.add(resolved);
1246
+ }
1247
+ // Remove stale managed hooks from existing hooks array
1248
+ let hooksArray = [];
1249
+ if (Array.isArray(config.hooks)) {
1250
+ hooksArray = config.hooks;
1251
+ }
1252
+ const filteredHooks = hooksArray.filter((h) => {
1253
+ const cmd = typeof h.command === 'string' ? h.command : '';
1254
+ if (!cmd)
1255
+ return true;
1256
+ const isManaged = managedPrefixes.some((p) => cmd.startsWith(p));
1257
+ if (!isManaged)
1258
+ return true;
1259
+ return currentManifestPaths.has(cmd);
1260
+ });
1261
+ // Add/update hooks from manifest
1262
+ for (const [name, hookDef] of Object.entries(manifest)) {
1263
+ if (!hookDef.events || hookDef.events.length === 0)
1264
+ continue;
1265
+ const commandPath = resolveHookCommand(name, hookDef, resolveScript);
1266
+ if (!commandPath) {
1267
+ errors.push(`${name}: script not found in user or system hooks dir`);
1268
+ continue;
1269
+ }
1270
+ const timeout = hookDef.timeout ?? 30;
1271
+ for (const event of hookDef.events) {
1272
+ const matcher = hookDef.matcher;
1273
+ // Find existing hook with same event, command, and matcher
1274
+ const existingIdx = filteredHooks.findIndex((h) => {
1275
+ const sameEvent = h.event === event;
1276
+ const sameCmd = h.command === commandPath;
1277
+ const sameMatcher = (h.matcher ?? '') === (matcher ?? '');
1278
+ return sameEvent && sameCmd && sameMatcher;
1279
+ });
1280
+ const hookEntry = {
1281
+ event,
1282
+ command: commandPath,
1283
+ timeout,
1284
+ };
1285
+ if (matcher) {
1286
+ hookEntry.matcher = matcher;
1287
+ }
1288
+ if (existingIdx >= 0) {
1289
+ filteredHooks[existingIdx] = hookEntry;
1290
+ }
1291
+ else {
1292
+ filteredHooks.push(hookEntry);
1293
+ }
1294
+ registered.push(`${name} -> ${event}`);
1295
+ }
1296
+ }
1297
+ config.hooks = filteredHooks;
1298
+ try {
1299
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
1300
+ fs.writeFileSync(configPath, TOML.stringify(config), 'utf-8');
1301
+ }
1302
+ catch (err) {
1303
+ errors.push(`Failed to write config.toml: ${err.message}`);
1304
+ }
1305
+ return { registered, errors };
1306
+ }
package/dist/lib/mcp.js CHANGED
@@ -13,7 +13,8 @@ import * as yaml from 'yaml';
13
13
  import { execFileSync } from 'child_process';
14
14
  import { getMcpDir, getUserMcpDir, getProjectAgentsDir, getVersionsDir } from './state.js';
15
15
  import { getBinaryPath, getVersionHomePath } from './versions.js';
16
- import { MCP_CAPABLE_AGENTS, AGENTS } from './agents.js';
16
+ import { AGENTS } from './agents.js';
17
+ import { isCapable } from './capabilities.js';
17
18
  import { setGeminiAutoUpdateDisabled, updateGeminiSettings } from './gemini-settings.js';
18
19
  /**
19
20
  * Parse an MCP server config from a YAML file.
@@ -312,6 +313,31 @@ function installMcpToCursorConfig(server, versionHome) {
312
313
  /**
313
314
  * Install MCP server to OpenCode config file.
314
315
  */
316
+ function installMcpToKimiConfig(server, versionHome) {
317
+ const configPath = path.join(versionHome, '.kimi-code', 'mcp.json');
318
+ let config = {};
319
+ if (fs.existsSync(configPath)) {
320
+ config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
321
+ }
322
+ if (!config.mcpServers || typeof config.mcpServers !== 'object') {
323
+ config.mcpServers = {};
324
+ }
325
+ const mcpServers = config.mcpServers;
326
+ if (server.config.transport === 'stdio') {
327
+ mcpServers[server.name] = {
328
+ command: server.config.command,
329
+ args: server.config.args || [],
330
+ env: server.config.env || {},
331
+ };
332
+ }
333
+ else {
334
+ mcpServers[server.name] = {
335
+ url: server.config.url,
336
+ };
337
+ }
338
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
339
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
340
+ }
315
341
  function installMcpToOpenCodeConfig(server, versionHome) {
316
342
  const configPath = path.join(versionHome, '.opencode', 'opencode.jsonc');
317
343
  let config = {};
@@ -354,7 +380,7 @@ function installMcpToOpenCodeConfig(server, versionHome) {
354
380
  * For others: edits config files directly
355
381
  */
356
382
  export function installMcpServers(agentId, version, versionHome, mcpNames, options = {}) {
357
- if (!MCP_CAPABLE_AGENTS.includes(agentId)) {
383
+ if (!isCapable(agentId, 'mcp')) {
358
384
  return { success: true, applied: [], errors: [] };
359
385
  }
360
386
  const servers = getMcpServersByName(mcpNames, { cwd: options.cwd });
@@ -394,6 +420,10 @@ export function installMcpServers(agentId, version, versionHome, mcpNames, optio
394
420
  // For now the general sync + toml editing via agents mcp works via the path helpers.
395
421
  applied.push(server.name);
396
422
  }
423
+ else if (agentId === 'kimi') {
424
+ installMcpToKimiConfig(server, versionHome);
425
+ applied.push(server.name);
426
+ }
397
427
  }
398
428
  catch (err) {
399
429
  const message = err.message;
@@ -4,5 +4,56 @@
4
4
  * Called from postinstall and as a command-time fallback from agents view/use/pull.
5
5
  * Each migration is guarded by an existence check so re-running is safe.
6
6
  */
7
+ /**
8
+ * Fold ~/.agents-system/ into ~/.agents/.system/.
9
+ *
10
+ * MUST run first in runMigration() — every other migrator reads SYSTEM_DIR
11
+ * (the new path), so the contents have to be there before they execute.
12
+ *
13
+ * Strategy:
14
+ * 1. If legacy dir doesn't exist or is already a symlink, no-op.
15
+ * 2. If new path doesn't exist yet, rename in one shot (fast path).
16
+ * 3. If both exist (mid-migration / re-run on partially-migrated state),
17
+ * merge legacy → new with new winning on collision, then drop legacy.
18
+ *
19
+ * After the contents move, the legacy path becomes a symlink → SYSTEM_DIR
20
+ * so external tooling that still references ~/.agents-system/ keeps
21
+ * resolving correctly. The symlink is harmless on its own and can be
22
+ * removed with `rm ~/.agents-system` once everything has updated.
23
+ *
24
+ * Idempotent: re-running converges to "contents at SYSTEM_DIR, symlink at
25
+ * LEGACY_SYSTEM_DIR" without duplicating data.
26
+ */
27
+ export declare function foldLegacySystemRepo(): void;
28
+ /**
29
+ * Rename the legacy `extras-extras/` plugin-marketplace dir to `agents-extras/`
30
+ * inside every installed agent version-home, and rewrite cross-references in
31
+ * `known_marketplaces.json` and the agent's `settings.json`.
32
+ *
33
+ * A previous dev build of `agents-cli` named the extras-aliased repo's
34
+ * synthesized marketplace dir `extras-extras` (double "extras" because the alias
35
+ * itself was `extras`). The new naming convention is `agents-<alias>`, so the
36
+ * directory should be `agents-extras`. Without this migration the orphan dir
37
+ * stays on disk and Claude Code loads two parallel marketplaces (the legacy
38
+ * `extras-extras` entry from `known_marketplaces.json` plus the freshly
39
+ * synthesized `agents-extras` from the new code path).
40
+ *
41
+ * Strategy per `<historyDir>/versions/<agent>/<ver>/home/.<agent>/plugins/`:
42
+ * 1. `marketplaces/extras-extras/` → `marketplaces/agents-extras/`
43
+ * (drops `extras-extras/` outright when `agents-extras/` already exists —
44
+ * previous incomplete migration ran)
45
+ * 2. Inside the renamed dir's `.claude-plugin/marketplace.json`, set
46
+ * `"name": "agents-extras"`.
47
+ * 3. In `<configDir>/plugins/known_marketplaces.json`, rename the
48
+ * `extras-extras` key to `agents-extras` and rewrite `source.path` /
49
+ * `installLocation`.
50
+ * 4. In `<configDir>/settings.json`'s `enabledPlugins`, rename every
51
+ * `<plugin>@extras-extras` key to `<plugin>@agents-extras` (preserving
52
+ * its boolean value, skipping if the new key already exists).
53
+ *
54
+ * Idempotent: re-running converges without further writes once everything is on
55
+ * the new name.
56
+ */
57
+ export declare function migrateExtrasExtrasToAgentsExtras(historyDir?: string): void;
7
58
  /** Run all idempotent migrations. Safe to call multiple times. */
8
59
  export declare function runMigration(): Promise<void>;
@@ -9,10 +9,74 @@ import * as path from 'path';
9
9
  import * as os from 'os';
10
10
  import * as yaml from 'yaml';
11
11
  const HOME = process.env.HOME ?? os.homedir();
12
- const SYSTEM_DIR = path.join(HOME, '.agents-system');
13
12
  const USER_DIR = path.join(HOME, '.agents');
13
+ /** Canonical system-repo location (post-fold). */
14
+ const SYSTEM_DIR = path.join(USER_DIR, '.system');
15
+ /** Legacy system-repo location — folded into SYSTEM_DIR by foldLegacySystemRepo(). */
16
+ const LEGACY_SYSTEM_DIR = path.join(HOME, '.agents-system');
14
17
  const HISTORY_DIR = path.join(USER_DIR, '.history');
15
18
  const CACHE_DIR = path.join(USER_DIR, '.cache');
19
+ /**
20
+ * Fold ~/.agents-system/ into ~/.agents/.system/.
21
+ *
22
+ * MUST run first in runMigration() — every other migrator reads SYSTEM_DIR
23
+ * (the new path), so the contents have to be there before they execute.
24
+ *
25
+ * Strategy:
26
+ * 1. If legacy dir doesn't exist or is already a symlink, no-op.
27
+ * 2. If new path doesn't exist yet, rename in one shot (fast path).
28
+ * 3. If both exist (mid-migration / re-run on partially-migrated state),
29
+ * merge legacy → new with new winning on collision, then drop legacy.
30
+ *
31
+ * After the contents move, the legacy path becomes a symlink → SYSTEM_DIR
32
+ * so external tooling that still references ~/.agents-system/ keeps
33
+ * resolving correctly. The symlink is harmless on its own and can be
34
+ * removed with `rm ~/.agents-system` once everything has updated.
35
+ *
36
+ * Idempotent: re-running converges to "contents at SYSTEM_DIR, symlink at
37
+ * LEGACY_SYSTEM_DIR" without duplicating data.
38
+ */
39
+ export function foldLegacySystemRepo() {
40
+ let legacyStat = null;
41
+ try {
42
+ legacyStat = fs.lstatSync(LEGACY_SYSTEM_DIR);
43
+ }
44
+ catch { /* missing */ }
45
+ if (!legacyStat)
46
+ return;
47
+ if (legacyStat.isSymbolicLink())
48
+ return;
49
+ if (!legacyStat.isDirectory())
50
+ return;
51
+ try {
52
+ fs.mkdirSync(USER_DIR, { recursive: true, mode: 0o700 });
53
+ }
54
+ catch { /* best-effort */ }
55
+ if (!fs.existsSync(SYSTEM_DIR)) {
56
+ try {
57
+ fs.renameSync(LEGACY_SYSTEM_DIR, SYSTEM_DIR);
58
+ try {
59
+ fs.symlinkSync(SYSTEM_DIR, LEGACY_SYSTEM_DIR);
60
+ }
61
+ catch { /* best-effort */ }
62
+ console.error('Folded ~/.agents-system/ into ~/.agents/.system/ (left back-compat symlink)');
63
+ return;
64
+ }
65
+ catch {
66
+ // Cross-device rename or perm issue — fall through to copy + remove.
67
+ }
68
+ }
69
+ try {
70
+ copyDirSkipExisting(LEGACY_SYSTEM_DIR, SYSTEM_DIR);
71
+ fs.rmSync(LEGACY_SYSTEM_DIR, { recursive: true, force: true });
72
+ try {
73
+ fs.symlinkSync(SYSTEM_DIR, LEGACY_SYSTEM_DIR);
74
+ }
75
+ catch { /* best-effort */ }
76
+ console.error('Merged ~/.agents-system/ into ~/.agents/.system/ (left back-compat symlink)');
77
+ }
78
+ catch { /* best-effort */ }
79
+ }
16
80
  /**
17
81
  * Move ~/.agents-system/agents.yaml -> ~/.agents/agents.yaml.
18
82
  * No-op if user file already exists or system file absent.
@@ -1461,8 +1525,166 @@ function migrateVersionResourcesToPatterns() {
1461
1525
  console.error('Migrated agents.yaml versions: entries to pattern format');
1462
1526
  }
1463
1527
  }
1528
+ /**
1529
+ * Rename the legacy `extras-extras/` plugin-marketplace dir to `agents-extras/`
1530
+ * inside every installed agent version-home, and rewrite cross-references in
1531
+ * `known_marketplaces.json` and the agent's `settings.json`.
1532
+ *
1533
+ * A previous dev build of `agents-cli` named the extras-aliased repo's
1534
+ * synthesized marketplace dir `extras-extras` (double "extras" because the alias
1535
+ * itself was `extras`). The new naming convention is `agents-<alias>`, so the
1536
+ * directory should be `agents-extras`. Without this migration the orphan dir
1537
+ * stays on disk and Claude Code loads two parallel marketplaces (the legacy
1538
+ * `extras-extras` entry from `known_marketplaces.json` plus the freshly
1539
+ * synthesized `agents-extras` from the new code path).
1540
+ *
1541
+ * Strategy per `<historyDir>/versions/<agent>/<ver>/home/.<agent>/plugins/`:
1542
+ * 1. `marketplaces/extras-extras/` → `marketplaces/agents-extras/`
1543
+ * (drops `extras-extras/` outright when `agents-extras/` already exists —
1544
+ * previous incomplete migration ran)
1545
+ * 2. Inside the renamed dir's `.claude-plugin/marketplace.json`, set
1546
+ * `"name": "agents-extras"`.
1547
+ * 3. In `<configDir>/plugins/known_marketplaces.json`, rename the
1548
+ * `extras-extras` key to `agents-extras` and rewrite `source.path` /
1549
+ * `installLocation`.
1550
+ * 4. In `<configDir>/settings.json`'s `enabledPlugins`, rename every
1551
+ * `<plugin>@extras-extras` key to `<plugin>@agents-extras` (preserving
1552
+ * its boolean value, skipping if the new key already exists).
1553
+ *
1554
+ * Idempotent: re-running converges without further writes once everything is on
1555
+ * the new name.
1556
+ */
1557
+ export function migrateExtrasExtrasToAgentsExtras(historyDir = HISTORY_DIR) {
1558
+ const versionsRoot = path.join(historyDir, 'versions');
1559
+ if (!fs.existsSync(versionsRoot))
1560
+ return;
1561
+ const OLD = 'extras-extras';
1562
+ const NEW = 'agents-extras';
1563
+ let agentEntries;
1564
+ try {
1565
+ agentEntries = fs.readdirSync(versionsRoot, { withFileTypes: true });
1566
+ }
1567
+ catch {
1568
+ return;
1569
+ }
1570
+ let renamedDirs = 0;
1571
+ let rewroteKnown = 0;
1572
+ let rewroteSettings = 0;
1573
+ for (const agentEntry of agentEntries) {
1574
+ if (!agentEntry.isDirectory())
1575
+ continue;
1576
+ const agentId = agentEntry.name;
1577
+ const agentVersionsDir = path.join(versionsRoot, agentId);
1578
+ let verEntries;
1579
+ try {
1580
+ verEntries = fs.readdirSync(agentVersionsDir, { withFileTypes: true });
1581
+ }
1582
+ catch {
1583
+ continue;
1584
+ }
1585
+ for (const ver of verEntries) {
1586
+ if (!ver.isDirectory())
1587
+ continue;
1588
+ const configDir = path.join(agentVersionsDir, ver.name, 'home', `.${agentId}`);
1589
+ const pluginsDir = path.join(configDir, 'plugins');
1590
+ if (!fs.existsSync(pluginsDir))
1591
+ continue;
1592
+ const marketplacesDir = path.join(pluginsDir, 'marketplaces');
1593
+ const oldDir = path.join(marketplacesDir, OLD);
1594
+ const newDir = path.join(marketplacesDir, NEW);
1595
+ const oldExists = fs.existsSync(oldDir);
1596
+ const newExists = fs.existsSync(newDir);
1597
+ if (oldExists && !newExists) {
1598
+ try {
1599
+ fs.renameSync(oldDir, newDir);
1600
+ renamedDirs++;
1601
+ }
1602
+ catch { /* best-effort, leave orphan */ }
1603
+ }
1604
+ else if (oldExists && newExists) {
1605
+ // Previous incomplete migration — drop the stale old dir.
1606
+ try {
1607
+ fs.rmSync(oldDir, { recursive: true, force: true });
1608
+ }
1609
+ catch { /* best-effort */ }
1610
+ }
1611
+ // Rewrite marketplace.json name field inside the (now) agents-extras dir.
1612
+ const marketplaceJson = path.join(newDir, '.claude-plugin', 'marketplace.json');
1613
+ if (fs.existsSync(marketplaceJson)) {
1614
+ try {
1615
+ const raw = fs.readFileSync(marketplaceJson, 'utf-8');
1616
+ const parsed = JSON.parse(raw);
1617
+ if (parsed?.name === OLD) {
1618
+ parsed.name = NEW;
1619
+ fs.writeFileSync(marketplaceJson, JSON.stringify(parsed, null, 2) + '\n', 'utf-8');
1620
+ }
1621
+ }
1622
+ catch { /* best-effort */ }
1623
+ }
1624
+ // Rewrite known_marketplaces.json key + path fields.
1625
+ const knownFile = path.join(pluginsDir, 'known_marketplaces.json');
1626
+ if (fs.existsSync(knownFile)) {
1627
+ try {
1628
+ const raw = fs.readFileSync(knownFile, 'utf-8');
1629
+ const known = JSON.parse(raw);
1630
+ if (known && typeof known === 'object' && OLD in known) {
1631
+ const entry = known[OLD];
1632
+ if (!(NEW in known)) {
1633
+ if (entry?.source?.path)
1634
+ entry.source.path = entry.source.path.split(OLD).join(NEW);
1635
+ if (entry?.installLocation)
1636
+ entry.installLocation = entry.installLocation.split(OLD).join(NEW);
1637
+ known[NEW] = entry;
1638
+ }
1639
+ delete known[OLD];
1640
+ fs.writeFileSync(knownFile, JSON.stringify(known, null, 2) + '\n', 'utf-8');
1641
+ rewroteKnown++;
1642
+ }
1643
+ }
1644
+ catch { /* best-effort */ }
1645
+ }
1646
+ // Rewrite settings.json enabledPlugins keys.
1647
+ const settingsFile = path.join(configDir, 'settings.json');
1648
+ if (fs.existsSync(settingsFile)) {
1649
+ try {
1650
+ const raw = fs.readFileSync(settingsFile, 'utf-8');
1651
+ const settings = JSON.parse(raw);
1652
+ const enabled = settings?.enabledPlugins;
1653
+ if (enabled && typeof enabled === 'object') {
1654
+ const suffix = `@${OLD}`;
1655
+ const newSuffix = `@${NEW}`;
1656
+ let changed = false;
1657
+ for (const key of Object.keys(enabled)) {
1658
+ if (!key.endsWith(suffix))
1659
+ continue;
1660
+ const renamed = key.slice(0, -suffix.length) + newSuffix;
1661
+ if (renamed in enabled) {
1662
+ delete enabled[key];
1663
+ }
1664
+ else {
1665
+ enabled[renamed] = enabled[key];
1666
+ delete enabled[key];
1667
+ }
1668
+ changed = true;
1669
+ }
1670
+ if (changed) {
1671
+ fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
1672
+ rewroteSettings++;
1673
+ }
1674
+ }
1675
+ }
1676
+ catch { /* best-effort */ }
1677
+ }
1678
+ }
1679
+ }
1680
+ if (renamedDirs > 0 || rewroteKnown > 0 || rewroteSettings > 0) {
1681
+ console.error(`Renamed extras-extras → agents-extras (dirs: ${renamedDirs}, known_marketplaces: ${rewroteKnown}, settings: ${rewroteSettings})`);
1682
+ }
1683
+ }
1464
1684
  /** Run all idempotent migrations. Safe to call multiple times. */
1465
1685
  export async function runMigration() {
1686
+ // MUST run first: every other migrator reads SYSTEM_DIR (the new path).
1687
+ foldLegacySystemRepo();
1466
1688
  migrateAgentsYaml();
1467
1689
  deleteSystemPromptsJson();
1468
1690
  migrateSystemConfigJson();
@@ -1503,6 +1725,10 @@ export async function runMigration() {
1503
1725
  await migrateSystemCloudToCache();
1504
1726
  dropDeadSystemArtifacts();
1505
1727
  warnSystemOrphans();
1728
+ // Rename the legacy extras-extras marketplace dir to agents-extras across every
1729
+ // installed version-home. Runs after migrateRuntimeToHistory so the version
1730
+ // homes are at their canonical HISTORY_DIR location.
1731
+ migrateExtrasExtrasToAgentsExtras();
1506
1732
  // Symlink repair runs LAST so it can find the post-move version homes.
1507
1733
  repairAgentConfigSymlinks();
1508
1734
  }
@@ -78,27 +78,72 @@ export function locateModelSource(agent, version) {
78
78
  return null;
79
79
  }
80
80
  if (agent === 'codex') {
81
+ // Codex's vendored binary has moved across releases:
82
+ // <=0.98: @openai/codex/vendor/<triple>/codex/codex
83
+ // 0.99..0.13: @openai/codex-<plat>-<arch>/vendor/<triple>/codex/codex
84
+ // 0.134+: @openai/codex-<plat>-<arch>/vendor/<triple>/bin/codex
85
+ // We probe all known shapes; first hit wins.
86
+ const triples = ['aarch64-apple-darwin', 'x86_64-apple-darwin', 'x86_64-unknown-linux-musl', 'aarch64-unknown-linux-musl', 'x86_64-pc-windows-msvc', 'aarch64-pc-windows-msvc'];
81
87
  const triple = currentTargetTriple();
82
- if (triple) {
83
- const platformPkg = path.join(versionDir, 'node_modules', '@openai', `codex-${triple.includes('apple') ? 'darwin' : triple.includes('linux') ? 'linux' : 'win32'}-${triple.includes('aarch64') ? 'arm64' : 'x64'}`, 'vendor', triple, 'codex', 'codex');
84
- if (fs.existsSync(platformPkg))
85
- return { path: platformPkg, kind: 'binary' };
86
- }
87
- // 0.98 layout: binary inside @openai/codex itself
88
- const triples = ['aarch64-apple-darwin', 'x86_64-apple-darwin', 'x86_64-unknown-linux-musl', 'aarch64-unknown-linux-musl'];
89
- for (const t of triples) {
90
- const p = path.join(versionDir, 'node_modules', '@openai', 'codex', 'vendor', t, 'codex', 'codex');
91
- if (fs.existsSync(p))
92
- return { path: p, kind: 'binary' };
88
+ const orderedTriples = triple ? [triple, ...triples.filter((t) => t !== triple)] : triples;
89
+ const platformPkgFor = (t) => `codex-${t.includes('apple') ? 'darwin' : t.includes('linux') ? 'linux' : 'win32'}-${t.includes('aarch64') ? 'arm64' : 'x64'}`;
90
+ for (const t of orderedTriples) {
91
+ const candidates = [
92
+ path.join(versionDir, 'node_modules', '@openai', platformPkgFor(t), 'vendor', t, 'bin', 'codex'),
93
+ path.join(versionDir, 'node_modules', '@openai', platformPkgFor(t), 'vendor', t, 'codex', 'codex'),
94
+ path.join(versionDir, 'node_modules', '@openai', 'codex', 'vendor', t, 'bin', 'codex'),
95
+ path.join(versionDir, 'node_modules', '@openai', 'codex', 'vendor', t, 'codex', 'codex'),
96
+ ];
97
+ for (const p of candidates) {
98
+ if (fs.existsSync(p))
99
+ return { path: p, kind: 'binary' };
100
+ }
93
101
  }
94
102
  return null;
95
103
  }
96
104
  if (agent === 'gemini') {
97
- // Gemini ships a clean ES module with all constants and aliases -- no need
98
- // to parse the minified CLI bundle.
105
+ // <=0.41 shipped a clean ES module at @google/gemini-cli-core/dist/src/config/models.js.
106
+ // 0.42+ inlines the same constants into a minified chunk under @google/gemini-cli/bundle/.
99
107
  const modelsJs = path.join(versionDir, 'node_modules', '@google', 'gemini-cli-core', 'dist', 'src', 'config', 'models.js');
100
108
  if (fs.existsSync(modelsJs))
101
109
  return { path: modelsJs, kind: 'js' };
110
+ const bundleDir = path.join(versionDir, 'node_modules', '@google', 'gemini-cli', 'bundle');
111
+ if (fs.existsSync(bundleDir)) {
112
+ try {
113
+ const entries = fs.readdirSync(bundleDir);
114
+ // The model constants live in a single chunk; pick the first .js file
115
+ // whose contents contain VALID_GEMINI_MODELS. Skip subdirectories.
116
+ for (const name of entries) {
117
+ if (!name.endsWith('.js'))
118
+ continue;
119
+ const full = path.join(bundleDir, name);
120
+ let stat;
121
+ try {
122
+ stat = fs.statSync(full);
123
+ }
124
+ catch {
125
+ continue;
126
+ }
127
+ if (!stat.isFile())
128
+ continue;
129
+ // Most chunks just re-export the constants; only the defining chunk
130
+ // actually has `var VALID_GEMINI_MODELS = ... new Set(`. Match that
131
+ // shape so we pick the chunk that the extractor can parse.
132
+ let body;
133
+ try {
134
+ body = fs.readFileSync(full, 'utf-8');
135
+ }
136
+ catch {
137
+ continue;
138
+ }
139
+ if (/var\s+VALID_GEMINI_MODELS\s*=[^\n]*new\s+Set/.test(body) &&
140
+ /var\s+DEFAULT_GEMINI_MODEL\s*=/.test(body)) {
141
+ return { path: full, kind: 'js' };
142
+ }
143
+ }
144
+ }
145
+ catch { /* unreadable bundle dir */ }
146
+ }
102
147
  return null;
103
148
  }
104
149
  if (agent === 'opencode') {
@@ -296,7 +341,9 @@ function extractCodexCatalog(text) {
296
341
  * would pollute the runtime and ES-module interop is awkward from a CJS build).
297
342
  */
298
343
  function extractGeminiCatalog(text) {
299
- const constRe = /export\s+const\s+([A-Z0-9_]+)\s*=\s*'([^']+)'/g;
344
+ // Old (<=0.41) layout used `export const FOO = 'bar';` in models.js.
345
+ // New (0.42+) bundle inlines the same constants as `var FOO = "bar";`.
346
+ const constRe = /(?:export\s+const|var)\s+([A-Z0-9_]+)\s*=\s*['"]([^'"]+)['"]/g;
300
347
  const constants = new Map();
301
348
  let m;
302
349
  while ((m = constRe.exec(text)) !== null) {
@@ -305,7 +352,7 @@ function extractGeminiCatalog(text) {
305
352
  // The set of ids the CLI accepts as "valid model names". Names (not values)
306
353
  // are listed inside `new Set([...])`, so we expand them via the constants map.
307
354
  const validIds = new Set();
308
- const setBlock = text.match(/VALID_GEMINI_MODELS\s*=\s*new\s+Set\(\[([\s\S]*?)\]\)/);
355
+ const setBlock = text.match(/VALID_GEMINI_MODELS\s*=\s*(?:\/\*[^*]*\*\/\s*)?new\s+Set\(\[([\s\S]*?)\]\)/);
309
356
  if (setBlock) {
310
357
  const nameRe = /([A-Z0-9_]+)/g;
311
358
  let nm;