@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
@@ -15,7 +15,7 @@ import { execFile } from 'child_process';
15
15
  import { promisify } from 'util';
16
16
  import { getAgentsDir, getHistoryDir } from '../state.js';
17
17
  const execFileAsync = promisify(execFile);
18
- import { AGENTS, getCliVersion } from '../agents.js';
18
+ import { AGENTS, agentConfigDirName, getCliVersion } from '../agents.js';
19
19
  import { walkForFiles } from '../fs-walk.js';
20
20
  import { getConfigSymlinkVersion } from '../shims.js';
21
21
  import { SESSION_AGENTS } from './types.js';
@@ -61,6 +61,7 @@ export async function discoverSessions(options) {
61
61
  case 'openclaw': return scanOpenClawIncremental();
62
62
  case 'rush': return scanRushIncremental(onProgress);
63
63
  case 'hermes': return scanHermesIncremental(onProgress);
64
+ case 'kimi': return scanKimiIncremental(onProgress);
64
65
  }
65
66
  }));
66
67
  }
@@ -201,14 +202,18 @@ export function getAgentSessionDirs(agent, subdir) {
201
202
  resolved.add(key);
202
203
  dirs.push(dir);
203
204
  }
204
- addDir(path.join(HOME, `.${agent}`, subdir));
205
+ // Config-dir name relative to home — handles nested layouts (antigravity
206
+ // .gemini/antigravity-cli) and ~/.config agents (amp, goose) as well as kimi
207
+ // (.kimi-code). Falls back to `.${agent}` for ids not in the registry.
208
+ const configDirName = agent in AGENTS ? agentConfigDirName(agent) : `.${agent}`;
209
+ addDir(path.join(HOME, configDirName, subdir));
205
210
  for (const root of VERSIONS_ROOTS) {
206
211
  const versionsBase = path.join(root, 'versions', agent);
207
212
  if (!fs.existsSync(versionsBase))
208
213
  continue;
209
214
  try {
210
215
  for (const version of fs.readdirSync(versionsBase)) {
211
- addDir(path.join(versionsBase, version, 'home', `.${agent}`, subdir));
216
+ addDir(path.join(versionsBase, version, 'home', configDirName, subdir));
212
217
  }
213
218
  }
214
219
  catch { /* dir unreadable */ }
@@ -1574,6 +1579,116 @@ function sumKnownNumbers(values) {
1574
1579
  // ---------------------------------------------------------------------------
1575
1580
  // Time range parsing
1576
1581
  // ---------------------------------------------------------------------------
1582
+ // ---------------------------------------------------------------------------
1583
+ // Kimi
1584
+ // ---------------------------------------------------------------------------
1585
+ // Kimi stores sessions under ~/.kimi-code/sessions/<workdir_hash>/session_<uuid>/.
1586
+ // Each session has state.json (metadata) and agents/main/wire.jsonl (conversation).
1587
+ // A session_index.jsonl at ~/.kimi-code/ maps session IDs to directories.
1588
+ /** Incrementally re-scan changed Kimi session state.json files and upsert into the DB. */
1589
+ async function scanKimiIncremental(onProgress) {
1590
+ const filePaths = [];
1591
+ for (const sessionsDir of getAgentSessionDirs('kimi', 'sessions')) {
1592
+ if (!fs.existsSync(sessionsDir))
1593
+ continue;
1594
+ let workDirNames;
1595
+ try {
1596
+ workDirNames = fs.readdirSync(sessionsDir);
1597
+ }
1598
+ catch {
1599
+ continue;
1600
+ }
1601
+ for (const workDirName of workDirNames) {
1602
+ const workDir = path.join(sessionsDir, workDirName);
1603
+ const stat = safeStatSync(workDir);
1604
+ if (!stat?.isDirectory())
1605
+ continue;
1606
+ let sessionNames;
1607
+ try {
1608
+ sessionNames = fs.readdirSync(workDir);
1609
+ }
1610
+ catch {
1611
+ continue;
1612
+ }
1613
+ for (const sessionName of sessionNames) {
1614
+ if (!sessionName.startsWith('session_'))
1615
+ continue;
1616
+ const statePath = path.join(workDir, sessionName, 'state.json');
1617
+ if (!fs.existsSync(statePath))
1618
+ continue;
1619
+ filePaths.push(statePath);
1620
+ }
1621
+ }
1622
+ }
1623
+ const changed = filterChangedFiles(filePaths);
1624
+ if (changed.length === 0)
1625
+ return;
1626
+ onProgress?.({ agent: 'kimi', parsed: 0, total: changed.length });
1627
+ const scanEntries = [];
1628
+ const touched = [];
1629
+ const seen = new Set();
1630
+ let parsed = 0;
1631
+ for (const { filePath, scan } of changed) {
1632
+ try {
1633
+ const result = readKimiMeta(filePath);
1634
+ if (result && !seen.has(result.meta.id)) {
1635
+ seen.add(result.meta.id);
1636
+ scanEntries.push({ meta: result.meta, content: result.content, scan });
1637
+ }
1638
+ else {
1639
+ touched.push({ filePath, scan });
1640
+ }
1641
+ }
1642
+ catch {
1643
+ touched.push({ filePath, scan });
1644
+ }
1645
+ parsed++;
1646
+ onProgress?.({ agent: 'kimi', parsed, total: changed.length });
1647
+ }
1648
+ upsertSessionsBatch(scanEntries);
1649
+ recordScans(touched);
1650
+ }
1651
+ /** Parse a single Kimi session state.json file to extract session metadata. */
1652
+ function readKimiMeta(filePath) {
1653
+ let state;
1654
+ try {
1655
+ state = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
1656
+ }
1657
+ catch {
1658
+ return null;
1659
+ }
1660
+ const sessionDir = path.dirname(filePath);
1661
+ const sessionId = path.basename(sessionDir);
1662
+ if (!sessionId.startsWith('session_'))
1663
+ return null;
1664
+ const title = typeof state.title === 'string' ? state.title : undefined;
1665
+ const lastPrompt = typeof state.lastPrompt === 'string' ? state.lastPrompt : undefined;
1666
+ const topic = title || lastPrompt || undefined;
1667
+ const createdAt = typeof state.createdAt === 'string' ? state.createdAt : undefined;
1668
+ const updatedAt = typeof state.updatedAt === 'string' ? state.updatedAt : undefined;
1669
+ const timestamp = updatedAt || createdAt;
1670
+ const shortId = sessionId.replace(/^session_/, '').slice(0, 8);
1671
+ // Try to infer project from session directory path
1672
+ // ~/.kimi-code/sessions/<workdir_hash>/session_<uuid>/
1673
+ const workDirName = path.basename(path.dirname(sessionDir));
1674
+ let project;
1675
+ if (workDirName.startsWith('wd_')) {
1676
+ const parts = workDirName.slice(3).split('_');
1677
+ if (parts.length >= 2) {
1678
+ project = parts.slice(0, -1).join('/');
1679
+ }
1680
+ }
1681
+ const meta = {
1682
+ id: sessionId,
1683
+ shortId,
1684
+ agent: 'kimi',
1685
+ timestamp,
1686
+ project,
1687
+ filePath,
1688
+ topic,
1689
+ };
1690
+ return { meta, content: lastPrompt || '' };
1691
+ }
1577
1692
  /** Parse a time filter string (relative like '7d' or ISO timestamp) into epoch milliseconds. */
1578
1693
  export function parseTimeFilter(input) {
1579
1694
  const relativeMatch = input.match(/^(\d+)([mhdw])$/i);
@@ -105,6 +105,9 @@ export function parseSession(filePath, agent) {
105
105
  case 'hermes':
106
106
  events = parseHermes(filePath);
107
107
  break;
108
+ case 'kimi':
109
+ events = [];
110
+ break; // Kimi event parsing not implemented yet — discover.ts builds metadata only
108
111
  }
109
112
  // Chokepoint: every string field that originated in an untrusted session
110
113
  // file gets stripped of terminal escapes here, so renderers downstream can
@@ -7,7 +7,7 @@
7
7
  * speaks these types.
8
8
  */
9
9
  /** Agents that store session data on disk and can be discovered by `agents sessions`. */
10
- export type SessionAgentId = 'claude' | 'codex' | 'gemini' | 'opencode' | 'openclaw' | 'rush' | 'hermes' | 'grok';
10
+ export type SessionAgentId = 'claude' | 'codex' | 'gemini' | 'opencode' | 'openclaw' | 'rush' | 'hermes' | 'grok' | 'kimi';
11
11
  /** All agents with session discovery support, in display order. */
12
12
  export declare const SESSION_AGENTS: SessionAgentId[];
13
13
  /** A single normalized event within a session (message, tool call, thinking, etc.). */
@@ -7,4 +7,4 @@
7
7
  * speaks these types.
8
8
  */
9
9
  /** All agents with session discovery support, in display order. */
10
- export const SESSION_AGENTS = ['claude', 'codex', 'gemini', 'opencode', 'openclaw', 'rush', 'hermes', 'grok'];
10
+ export const SESSION_AGENTS = ['claude', 'codex', 'gemini', 'opencode', 'openclaw', 'rush', 'hermes', 'grok', 'kimi'];
@@ -62,8 +62,22 @@ export interface ConflictInfo {
62
62
  * v15 — remove foreground resource sync / rules refresh from launch shims.
63
63
  * Version homes are reconciled by agents-cli management commands; the
64
64
  * shim hot path only resolves a version and execs the agent binary.
65
+ * v16 — re-introduce project-scoped compile to the shim hot path via
66
+ * `agents sync --launch`. This stays fast (filesystem-only): compiles
67
+ * project rules, mirrors workspace resources, and synthesizes the
68
+ * scoped plugin marketplaces (agents-cli/agents-system/extras-<alias>/
69
+ * agents-project). Version-home reconciliation stays out of the hot
70
+ * path — management commands still own that.
71
+ * v17 — bash-side skip-fast sentinel under ~/.agents/.cache/launch-sync/.
72
+ * When the sentinel mtime is newer than every source dir, exec the
73
+ * agent binary directly without spawning node. Cuts steady-state
74
+ * hot-path latency from ~680ms (node startup + module init) to ~11ms
75
+ * (a few stat calls). Node writes the sentinel after each successful
76
+ * sync. Documented limitation: POSIX dir mtime only updates on
77
+ * top-level entry add/remove — deep edits to plugin contents won't
78
+ * trigger auto-resync, run `agents sync` for that.
65
79
  */
66
- export declare const SHIM_SCHEMA_VERSION = 15;
80
+ export declare const SHIM_SCHEMA_VERSION = 17;
67
81
  /**
68
82
  * Generate the full bash shim script for the given agent. The returned string
69
83
  * is written to ~/.agents/shims/{cliCommand} and made executable.
@@ -223,14 +237,9 @@ export declare function getPathShadowingExecutable(agent: AgentId): string | nul
223
237
  export declare function removeLegacyUserShim(agent: AgentId, overrides?: {
224
238
  homeDir?: string;
225
239
  }): boolean;
226
- /**
227
- * Check if the agent's CLI command is shadowed by a shell alias.
228
- *
229
- * Shell aliases live in the user's session and aren't visible from a Node.js
230
- * child process. We do a best-effort scan of common RC files for `alias
231
- * <command>=` patterns. Returns false when detection is inconclusive.
232
- */
233
- export declare function hasAliasShadowingShim(agent: AgentId): boolean;
240
+ export declare function hasAliasShadowingShim(agent: AgentId, overrides?: {
241
+ homeDir?: string;
242
+ }): boolean;
234
243
  /**
235
244
  * Check if shims directory is in PATH.
236
245
  */
package/dist/lib/shims.js CHANGED
@@ -186,8 +186,22 @@ async function promptConflictStrategy(conflictInfos) {
186
186
  * v15 — remove foreground resource sync / rules refresh from launch shims.
187
187
  * Version homes are reconciled by agents-cli management commands; the
188
188
  * shim hot path only resolves a version and execs the agent binary.
189
+ * v16 — re-introduce project-scoped compile to the shim hot path via
190
+ * `agents sync --launch`. This stays fast (filesystem-only): compiles
191
+ * project rules, mirrors workspace resources, and synthesizes the
192
+ * scoped plugin marketplaces (agents-cli/agents-system/extras-<alias>/
193
+ * agents-project). Version-home reconciliation stays out of the hot
194
+ * path — management commands still own that.
195
+ * v17 — bash-side skip-fast sentinel under ~/.agents/.cache/launch-sync/.
196
+ * When the sentinel mtime is newer than every source dir, exec the
197
+ * agent binary directly without spawning node. Cuts steady-state
198
+ * hot-path latency from ~680ms (node startup + module init) to ~11ms
199
+ * (a few stat calls). Node writes the sentinel after each successful
200
+ * sync. Documented limitation: POSIX dir mtime only updates on
201
+ * top-level entry add/remove — deep edits to plugin contents won't
202
+ * trigger auto-resync, run `agents sync` for that.
189
203
  */
190
- export const SHIM_SCHEMA_VERSION = 15;
204
+ export const SHIM_SCHEMA_VERSION = 17;
191
205
  /** Internal marker string used to embed the schema version in shim scripts. */
192
206
  const SHIM_VERSION_MARKER = 'agents-shim-version:';
193
207
  function shellQuote(value) {
@@ -244,14 +258,19 @@ export COPILOT_HOME="$VERSION_DIR/home/${configDirName}"
244
258
  # This gives agents-cli full versioned isolation + resource sync for grok.
245
259
  export GROK_HOME="$VERSION_DIR/home/.grok"
246
260
  `
247
- : '';
261
+ : agent === 'kimi'
262
+ ? `
263
+ # Kimi Code CLI honors KIMI_CODE_HOME to relocate ~/.kimi-code (config.toml,
264
+ # mcp.json, sessions, skills, hooks). Point it at the versioned home.
265
+ export KIMI_CODE_HOME="$VERSION_DIR/home/${configDirName}"
266
+ `
267
+ : '';
248
268
  const launchArgs = agent === 'codex' ? ' -c check_for_update_on_startup=false' : '';
249
269
  return `#!/bin/bash
250
270
  # Auto-generated by agents-cli - do not edit
251
271
  # Shim for ${agentConfig.name}
252
272
  # ${SHIM_VERSION_MARKER} ${SHIM_SCHEMA_VERSION}
253
273
 
254
- AGENTS_SYSTEM_DIR="$HOME/.agents-system"
255
274
  AGENTS_USER_DIR="$HOME/.agents"
256
275
  AGENTS_BIN=${agentsBin}
257
276
  AGENT="${agent}"
@@ -262,10 +281,10 @@ if [ -z "$AGENTS_BIN" ] || [ ! -x "$AGENTS_BIN" ]; then
262
281
  exit 127
263
282
  fi
264
283
 
265
- # Find project agents.yaml walking up from cwd (skip $HOME/.agents-system/agents.yaml)
284
+ # Find project agents.yaml walking up from cwd (skip $HOME/.agents/agents.yaml)
266
285
  find_project_version() {
267
286
  local dir="$PWD"
268
- local user_agents_yaml="$AGENTS_SYSTEM_DIR/agents.yaml"
287
+ local user_agents_yaml="$AGENTS_USER_DIR/agents.yaml"
269
288
  while [ "$dir" != "/" ]; do
270
289
  local candidate="$dir/agents.yaml"
271
290
  if [ -f "$candidate" ] && [ "$candidate" != "$user_agents_yaml" ]; then
@@ -384,6 +403,16 @@ if [ "$AGENT" = "grok" ]; then
384
403
  # Last resort: whatever is on PATH (user may have installed grok globally)
385
404
  BINARY=$(command -v grok 2>/dev/null || echo "")
386
405
  fi
406
+ # Kimi special case: binary lives in ~/.kimi-code/bin/, not node_modules.
407
+ # We still use the agents-cli version dir purely for KIMI_CODE_HOME isolation.
408
+ elif [ "$AGENT" = "kimi" ]; then
409
+ KIMI_BINARY="$HOME/.kimi-code/bin/kimi"
410
+ if [ -x "$KIMI_BINARY" ]; then
411
+ BINARY="$KIMI_BINARY"
412
+ else
413
+ # Last resort: whatever is on PATH
414
+ BINARY=$(command -v kimi 2>/dev/null || echo "")
415
+ fi
387
416
  else
388
417
  BINARY="$VERSION_DIR/node_modules/.bin/$CLI_COMMAND"
389
418
  fi
@@ -451,6 +480,34 @@ fi
451
480
 
452
481
  ${managedEnv}
453
482
 
483
+ # Project-scoped compile (rules, workspace resources, scoped plugin marketplaces).
484
+ # Skip-fast: if a sentinel from the last sync exists and is newer than all
485
+ # source dirs (project .agents/, user plugins, system plugins), exec the
486
+ # agent binary directly without spawning node. Cuts steady-state hot-path
487
+ # latency from ~680ms (node startup + agents-cli module init) to ~11ms (a
488
+ # handful of stat calls). Never blocks launch on failure of the sync itself.
489
+ #
490
+ # Known limitation: POSIX dir mtime updates only on entry add/remove at that
491
+ # level. Deep edits to existing plugin contents (e.g. editing a SKILL.md
492
+ # inside a plugin) won't bump the parent dir's mtime — the marketplace copy
493
+ # stays stale until \`agents sync\` runs explicitly or a top-level entry
494
+ # changes. Advanced users hot-iterating on plugins know to run sync.
495
+ PROJECT_SLUG=\$(printf '%s' "\$PWD" | tr / _ | tr ' ' _)
496
+ LAUNCH_SENTINEL="\$AGENTS_USER_DIR/.cache/launch-sync/\${AGENT}@\${VERSION}@\${PROJECT_SLUG}"
497
+ LAUNCH_SKIP=0
498
+ if [ -f "\$LAUNCH_SENTINEL" ]; then
499
+ LAUNCH_SKIP=1
500
+ for LAUNCH_SRC in "\$PWD/.agents" "\$AGENTS_USER_DIR/plugins" "\$AGENTS_USER_DIR/.system/plugins"; do
501
+ if [ -e "\$LAUNCH_SRC" ] && [ "\$LAUNCH_SRC" -nt "\$LAUNCH_SENTINEL" ]; then
502
+ LAUNCH_SKIP=0
503
+ break
504
+ fi
505
+ done
506
+ fi
507
+ if [ "\$LAUNCH_SKIP" = "0" ]; then
508
+ "\$AGENTS_BIN" sync --agent "\$AGENT" --agent-version "\$VERSION" --launch --cwd "\$PWD" --quiet 2>/dev/null || true
509
+ fi
510
+
454
511
  exec "$BINARY"${launchArgs} "$@"
455
512
  `;
456
513
  }
@@ -540,7 +597,13 @@ export CODEX_HOME="$HOME/.agents/.history/versions/${agent}/${version}/home/${co
540
597
  # version MCP and session state are isolated.
541
598
  export COPILOT_HOME="$HOME/.agents/.history/versions/${agent}/${version}/home/${configDirName}"
542
599
  `
543
- : '';
600
+ : agent === 'kimi'
601
+ ? `
602
+ # Kimi Code CLI honors KIMI_CODE_HOME to relocate ~/.kimi-code (config.toml,
603
+ # mcp.json, sessions, skills, hooks). Point direct aliases at the versioned home.
604
+ export KIMI_CODE_HOME="$HOME/.agents/.history/versions/${agent}/${version}/home/${configDirName}"
605
+ `
606
+ : '';
544
607
  const launchArgs = agent === 'codex' ? ' -c check_for_update_on_startup=false' : '';
545
608
  return `#!/bin/bash
546
609
  # Auto-generated by agents-cli - do not edit
@@ -754,7 +817,7 @@ export async function switchConfigSymlink(agent, version) {
754
817
  // Different target - update it
755
818
  fs.unlinkSync(configPath);
756
819
  fs.mkdirSync(path.dirname(configPath), { recursive: true });
757
- fs.symlinkSync(versionConfigPath, configPath);
820
+ fs.symlinkSync(versionConfigPath, configPath, process.platform === 'win32' ? 'junction' : undefined);
758
821
  return { success: true };
759
822
  }
760
823
  else if (stat.isDirectory()) {
@@ -766,8 +829,25 @@ export async function switchConfigSymlink(agent, version) {
766
829
  const finalBackupPath = path.join(agentBackupDir, String(timestamp));
767
830
  fs.mkdirSync(agentBackupDir, { recursive: true });
768
831
  fs.renameSync(configPath, finalBackupPath);
832
+ // Session JSONLs that lived under the old configPath have just moved to
833
+ // finalBackupPath on disk. Rewrite any DB rows pointing at the old prefix
834
+ // so querySessions stops returning phantom rows (issue #136). The
835
+ // discoverer at src/lib/session/discover.ts already scans backup dirs, so
836
+ // future indexer runs will find the new files — this just keeps the
837
+ // existing rows valid in the meantime.
838
+ //
839
+ // Dynamic import so loading shims.ts doesn't transitively open the
840
+ // sessions DB — many tests partially mock state.js and would break.
841
+ try {
842
+ const { updateSessionFilePaths } = await import('./session/db.js');
843
+ updateSessionFilePaths(configPath, finalBackupPath);
844
+ }
845
+ catch (err) {
846
+ console.error(`Warning: failed to update session file_paths after backing up ${configPath}: ` +
847
+ `${err.message}. Stale rows may appear in session listings until the next scan.`);
848
+ }
769
849
  // Create symlink (parent already exists since the dir we just moved was here)
770
- fs.symlinkSync(versionConfigPath, configPath);
850
+ fs.symlinkSync(versionConfigPath, configPath, process.platform === 'win32' ? 'junction' : undefined);
771
851
  return { success: true, backupPath: finalBackupPath };
772
852
  }
773
853
  else {
@@ -780,7 +860,7 @@ export async function switchConfigSymlink(agent, version) {
780
860
  // For nested layouts (e.g., ~/.gemini/antigravity-cli) the parent dir
781
861
  // may also be missing if the parent agent (Gemini) is not installed.
782
862
  fs.mkdirSync(path.dirname(configPath), { recursive: true });
783
- fs.symlinkSync(versionConfigPath, configPath);
863
+ fs.symlinkSync(versionConfigPath, configPath, process.platform === 'win32' ? 'junction' : undefined);
784
864
  return { success: true };
785
865
  }
786
866
  return { success: false, error: err.message };
@@ -1251,23 +1331,53 @@ export function removeLegacyUserShim(agent, overrides) {
1251
1331
  * Shell aliases live in the user's session and aren't visible from a Node.js
1252
1332
  * child process. We do a best-effort scan of common RC files for `alias
1253
1333
  * <command>=` patterns. Returns false when detection is inconclusive.
1334
+ *
1335
+ * Tracks the LAST `alias` / `unalias` action for this command per rc file —
1336
+ * a trailing `unalias codex` cancels an earlier `alias codex=...`, and
1337
+ * `unalias` can name multiple commands on one line. Without this, an
1338
+ * `alias` line elsewhere in the file would surface as a false positive
1339
+ * (e.g. seen in zshrc setups that conditionally clear an alias later).
1254
1340
  */
1255
- export function hasAliasShadowingShim(agent) {
1341
+ function escapeRegex(value) {
1342
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1343
+ }
1344
+ /** Walk rc lines in order; a later `unalias` clears an earlier `alias`. */
1345
+ function isAliasActiveInRcContent(content, cliCommand) {
1346
+ let active = false;
1347
+ const aliasPattern = new RegExp(`^\\s*alias\\s+${escapeRegex(cliCommand)}\\s*=`);
1348
+ for (const line of content.split('\n')) {
1349
+ const trimmed = line.trim();
1350
+ if (!trimmed || trimmed.startsWith('#'))
1351
+ continue;
1352
+ if (aliasPattern.test(line)) {
1353
+ active = true;
1354
+ continue;
1355
+ }
1356
+ const unaliasMatch = trimmed.match(/^unalias\s+(.+)$/);
1357
+ if (!unaliasMatch)
1358
+ continue;
1359
+ const tokens = unaliasMatch[1].split(/\s+/).filter((token) => !token.startsWith('-'));
1360
+ if (tokens.includes(cliCommand)) {
1361
+ active = false;
1362
+ }
1363
+ }
1364
+ return active;
1365
+ }
1366
+ export function hasAliasShadowingShim(agent, overrides) {
1256
1367
  const cliCommand = AGENTS[agent].cliCommand;
1257
- const HOME = os.homedir();
1368
+ const homeDir = overrides?.homeDir ?? os.homedir();
1258
1369
  const rcFiles = [
1259
- path.join(HOME, '.zshrc'),
1260
- path.join(HOME, '.bashrc'),
1261
- path.join(HOME, '.bash_profile'),
1262
- path.join(HOME, '.profile'),
1370
+ path.join(homeDir, '.zshrc'),
1371
+ path.join(homeDir, '.bashrc'),
1372
+ path.join(homeDir, '.bash_profile'),
1373
+ path.join(homeDir, '.profile'),
1263
1374
  ];
1264
- const pattern = new RegExp(`^\\s*alias\\s+${cliCommand}\\s*=`, 'm');
1265
1375
  for (const rcFile of rcFiles) {
1266
1376
  try {
1267
1377
  if (!fs.existsSync(rcFile))
1268
1378
  continue;
1269
1379
  const content = fs.readFileSync(rcFile, 'utf-8');
1270
- if (pattern.test(content))
1380
+ if (isAliasActiveInRcContent(content, cliCommand))
1271
1381
  return true;
1272
1382
  }
1273
1383
  catch {
@@ -1303,6 +1413,7 @@ function isShimPathCommandLine(line, shimsDir) {
1303
1413
  const markerRegexes = exactMarkers.map((marker) => new RegExp(`${escapeRegex(marker)}(?=$|[:\\s])`));
1304
1414
  const suffixRegexes = [
1305
1415
  /\/\.agents-system\/shims(?=$|[:\s])/,
1416
+ /\/\.agents\/\.cache\/shims(?=$|[:\s])/,
1306
1417
  /\/\.agents\/shims(?=$|[:\s])/,
1307
1418
  ];
1308
1419
  const touchesShimPath = [...markerRegexes, ...suffixRegexes].some((pattern) => pattern.test(normalized));
@@ -1364,10 +1475,10 @@ export function getPathSetupInstructions() {
1364
1475
  return `Add to ~/.config/fish/config.fish:
1365
1476
  fish_add_path ${shimsDir}`;
1366
1477
  }
1367
- return `Add to ~/${rcFile} (AFTER any nvm/node setup):
1478
+ return `Add to the end of ~/${rcFile} (after any nvm/node setup and agent installers):
1368
1479
  export PATH="${shimsDir}:$PATH"
1369
1480
 
1370
- IMPORTANT: Shims must come FIRST in PATH to override global installs.
1481
+ IMPORTANT: Shims must be the last PATH prepend in your shell config to override global installs.
1371
1482
 
1372
1483
  Then restart your shell or run:
1373
1484
  source ~/${rcFile}`;
@@ -1398,25 +1509,6 @@ export function addShimsToPath(overrides) {
1398
1509
  exportBlock = `# agents-cli: version-managed agent CLIs\nexport PATH="${shimsDir}:$PATH"\n`;
1399
1510
  }
1400
1511
  const contentWithoutShimLines = stripShimPathLines(content, shimsDir);
1401
- // Find insertion point - AFTER node/nvm/fnm setup if present, otherwise append.
1402
- const insertAfterPatterns = [
1403
- /^export NVM_DIR=/m,
1404
- /^source.*nvm/m,
1405
- /^\[ -s.*nvm/m,
1406
- /^eval.*fnm/m,
1407
- /^export PATH.*node/m,
1408
- /^export PATH.*npm/m,
1409
- ];
1410
- let insertIndex = -1;
1411
- const lines = contentWithoutShimLines.split('\n');
1412
- let offset = 0;
1413
- for (const line of lines) {
1414
- const lineWithNewline = `${line}\n`;
1415
- if (insertAfterPatterns.some((pattern) => pattern.test(line))) {
1416
- insertIndex = offset + lineWithNewline.length;
1417
- }
1418
- offset += lineWithNewline.length;
1419
- }
1420
1512
  // Write the updated content
1421
1513
  try {
1422
1514
  // Ensure parent directories exist (especially for fish: ~/.config/fish/)
@@ -1424,18 +1516,9 @@ export function addShimsToPath(overrides) {
1424
1516
  if (!fs.existsSync(rcDir)) {
1425
1517
  fs.mkdirSync(rcDir, { recursive: true });
1426
1518
  }
1427
- let newContent;
1428
- if (insertIndex >= 0) {
1429
- // Insert after the last node/nvm/fnm-related line so our prepend wins.
1430
- const before = contentWithoutShimLines.slice(0, insertIndex);
1431
- const separator = before.length > 0 && !before.endsWith('\n\n') ? '\n' : '';
1432
- newContent = before + separator + exportBlock + contentWithoutShimLines.slice(insertIndex);
1433
- }
1434
- else {
1435
- // Append to end
1436
- const separator = contentWithoutShimLines.length > 0 && !contentWithoutShimLines.endsWith('\n') ? '\n' : '';
1437
- newContent = contentWithoutShimLines + separator + exportBlock;
1438
- }
1519
+ // Append at EOF so later installer PATH prepends cannot shadow the shims.
1520
+ const separator = contentWithoutShimLines.length > 0 && !contentWithoutShimLines.endsWith('\n') ? '\n' : '';
1521
+ let newContent = contentWithoutShimLines + separator + exportBlock;
1439
1522
  newContent = newContent.replace(/\n{2,}$/g, '\n');
1440
1523
  if (newContent === content) {
1441
1524
  return { success: true, alreadyPresent: true, rcFile };
@@ -73,7 +73,7 @@ export declare function skillExists(agentId: AgentId, skillName: string): boolea
73
73
  */
74
74
  export declare function skillContentMatches(agentId: AgentId, skillName: string, sourcePath: string): boolean;
75
75
  /**
76
- * List skill names from user (~/.agents/skills/) and system (~/.agents-system/skills/) dirs.
76
+ * List skill names from user (~/.agents/skills/) and system (~/.agents/.system/skills/) dirs.
77
77
  * User dir takes priority; deduplication preserves first occurrence.
78
78
  */
79
79
  export declare function listCentralSkills(): string[];
@@ -10,7 +10,8 @@ import * as fs from 'fs';
10
10
  import * as path from 'path';
11
11
  import * as os from 'os';
12
12
  import * as yaml from 'yaml';
13
- import { SKILLS_CAPABLE_AGENTS, ensureSkillsDir } from './agents.js';
13
+ import { ensureSkillsDir, agentConfigDirName } from './agents.js';
14
+ import { capableAgents, isCapable } from './capabilities.js';
14
15
  import { getUserSkillsDir, getSkillsDir as getSystemSkillsDir, getProjectAgentsDir, getEnabledExtraRepos, getTrashSkillsDir } from './state.js';
15
16
  import { getEffectiveHome, getVersionHomePath, listInstalledVersions } from './versions.js';
16
17
  import { emit } from './events.js';
@@ -27,7 +28,7 @@ export function ensureCentralSkillsDir() {
27
28
  }
28
29
  export function getAgentSkillsDir(agentId) {
29
30
  const home = getEffectiveHome(agentId);
30
- return path.join(home, `.${agentId}`, 'skills');
31
+ return path.join(home, agentConfigDirName(agentId), 'skills');
31
32
  }
32
33
  export function getProjectSkillsDir(agentId, cwd = process.cwd()) {
33
34
  const dirs = [];
@@ -227,7 +228,7 @@ export function installSkill(sourcePath, skillName, agents, method = 'symlink')
227
228
  }
228
229
  // Symlink to each agent
229
230
  for (const agentId of agents) {
230
- if (!SKILLS_CAPABLE_AGENTS.includes(agentId)) {
231
+ if (!isCapable(agentId, 'skills')) {
231
232
  continue;
232
233
  }
233
234
  ensureSkillsDir(agentId);
@@ -328,7 +329,7 @@ export function skillContentMatches(agentId, skillName, sourcePath) {
328
329
  }
329
330
  }
330
331
  /**
331
- * List skill names from user (~/.agents/skills/) and system (~/.agents-system/skills/) dirs.
332
+ * List skill names from user (~/.agents/skills/) and system (~/.agents/.system/skills/) dirs.
332
333
  * User dir takes priority; deduplication preserves first occurrence.
333
334
  */
334
335
  export function listCentralSkills() {
@@ -388,7 +389,7 @@ export function listAllSkills() {
388
389
  */
389
390
  export function getVersionSkillsDir(agent, version) {
390
391
  const home = getVersionHomePath(agent, version);
391
- return path.join(home, `.${agent}`, 'skills');
392
+ return path.join(home, agentConfigDirName(agent), 'skills');
392
393
  }
393
394
  /**
394
395
  * List skill names installed in a specific version home.
@@ -586,9 +587,9 @@ export function removeSkillFromVersion(agent, version, skillName) {
586
587
  */
587
588
  export function iterSkillsCapableVersions(filter) {
588
589
  const pairs = [];
589
- const agents = filter?.agent ? [filter.agent] : SKILLS_CAPABLE_AGENTS;
590
+ const agents = filter?.agent ? [filter.agent] : capableAgents('skills');
590
591
  for (const agent of agents) {
591
- if (!SKILLS_CAPABLE_AGENTS.includes(agent))
592
+ if (!capableAgents('skills').includes(agent))
592
593
  continue;
593
594
  const versions = listInstalledVersions(agent);
594
595
  for (const version of versions) {
@@ -606,7 +607,7 @@ export function uninstallSkill(skillName) {
606
607
  return { success: false, error: `Skill '${skillName}' not found` };
607
608
  }
608
609
  // Remove from all agents
609
- for (const agentId of SKILLS_CAPABLE_AGENTS) {
610
+ for (const agentId of capableAgents('skills')) {
610
611
  const agentSkillPath = path.join(getAgentSkillsDir(agentId), skillName);
611
612
  if (fs.existsSync(agentSkillPath)) {
612
613
  try {
@@ -727,7 +728,7 @@ export function listInstalledSkillsWithScope(agentId, cwd = process.cwd(), optio
727
728
  }
728
729
  // User-scoped skills (version-aware when home is provided)
729
730
  const userSkillsDir = options?.home
730
- ? path.join(options.home, `.${agentId}`, 'skills')
731
+ ? path.join(options.home, agentConfigDirName(agentId), 'skills')
731
732
  : getAgentSkillsDir(agentId);
732
733
  if (fs.existsSync(userSkillsDir)) {
733
734
  try {
@@ -0,0 +1,3 @@
1
+ import type { AgentId } from '../../types.js';
2
+ import type { ResourceDetector } from './types.js';
3
+ export declare const commandsDetectors: Partial<Record<AgentId, ResourceDetector>>;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Commands detector — mirrors versions.ts:343-357. Inspects the version home,
3
+ * returns command names. Honors the commands-as-skills marker for grok and
4
+ * Codex >= 0.117.0; falls back to scanning `{agentDir}/<commandsSubdir>/` for
5
+ * the native path.
6
+ */
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ import { AGENTS, agentConfigDirName } from '../../agents.js';
10
+ import { shouldInstallCommandAsSkill, listCommandSkillsInVersion } from '../../command-skills.js';
11
+ import { lazyAgentMap } from '../writers/lazy-map.js';
12
+ function buildCommandsDetector(agent) {
13
+ return {
14
+ kind: 'commands',
15
+ agent,
16
+ list({ version, versionHome }) {
17
+ const agentConfig = AGENTS[agent];
18
+ const agentDir = path.join(versionHome, agentConfigDirName(agent));
19
+ if (shouldInstallCommandAsSkill(agent, version)) {
20
+ return listCommandSkillsInVersion(agentDir);
21
+ }
22
+ const commandsDir = path.join(agentDir, agentConfig.commandsSubdir);
23
+ if (!fs.existsSync(commandsDir))
24
+ return [];
25
+ const ext = agentConfig.format === 'toml' ? '.toml' : '.md';
26
+ return fs.readdirSync(commandsDir)
27
+ .filter(f => f.endsWith(ext))
28
+ .map(f => f.replace(new RegExp(`\\${ext}$`), ''));
29
+ },
30
+ };
31
+ }
32
+ // Detector registration mirrors writers/commands.ts — see that file for the
33
+ // openclaw vs grok asymmetry.
34
+ export const commandsDetectors = lazyAgentMap(() => {
35
+ const m = {};
36
+ for (const id of Object.keys(AGENTS)) {
37
+ const cfg = AGENTS[id];
38
+ if (cfg.capabilities.commands === false && (!cfg.commandsSubdir || cfg.commandsSubdir === '') && id !== 'grok')
39
+ continue;
40
+ const hasCommands = cfg.capabilities.commands !== false;
41
+ const hasSkills = cfg.capabilities.skills !== false;
42
+ if (hasCommands || hasSkills)
43
+ m[id] = buildCommandsDetector(id);
44
+ }
45
+ return m;
46
+ });