@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
package/dist/lib/exec.js CHANGED
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { spawn } from 'child_process';
8
8
  import { randomUUID } from 'crypto';
9
+ import * as fs from 'fs';
9
10
  import * as path from 'path';
10
11
  import { ALL_MODES } from './types.js';
11
12
  import { AGENTS } from './agents.js';
@@ -14,6 +15,7 @@ import { getVersionHomePath, isVersionInstalled, resolveVersion } from './versio
14
15
  import { resolveModel, buildReasoningFlags } from './models.js';
15
16
  import { maybeRotate, createTimer, redactPrompt, redactArgs } from './events.js';
16
17
  import { sanitizeProcessEnv } from './secrets/bundles.js';
18
+ import { getShimsDir } from './state.js';
17
19
  /**
18
20
  * Map a raw mode string (CLI flag, YAML field, env var) to the canonical Mode.
19
21
  *
@@ -53,6 +55,22 @@ export function resolveMode(agent, requested) {
53
55
  }
54
56
  throw new Error(`${agent} does not support '${requested}' mode. Supported modes: ${supported.join(', ')}.`);
55
57
  }
58
+ /**
59
+ * The mode an agent should run in when the caller has no preference.
60
+ *
61
+ * Returns the first entry in the agent's `capabilities.modes` table — the
62
+ * declaration order is the source of truth for "the safest mode this agent
63
+ * supports." Agents that include `plan` list it first; agents like
64
+ * antigravity that have no read-only mode list `edit` first.
65
+ *
66
+ * Use this when the user did not pass `--mode` explicitly. When the user
67
+ * *did* pass `--mode plan` and the agent doesn't support it, call
68
+ * `resolveMode` instead so the user sees a loud error rather than a silent
69
+ * elevation from read-only to writable.
70
+ */
71
+ export function defaultModeFor(agent) {
72
+ return AGENTS[agent].capabilities.modes[0];
73
+ }
56
74
  /** Pattern for valid environment variable names (C identifier rules). */
57
75
  const EXEC_ENV_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
58
76
  /** Parse a single KEY=VALUE string into a tuple, validating the key name. */
@@ -100,6 +118,7 @@ export function buildExecEnv(options) {
100
118
  }
101
119
  delete result.CODEX_HOME;
102
120
  delete result.COPILOT_HOME;
121
+ delete result.KIMI_CODE_HOME;
103
122
  }
104
123
  else if (options.agent === 'codex') {
105
124
  const cwd = options.cwd || process.cwd();
@@ -112,6 +131,7 @@ export function buildExecEnv(options) {
112
131
  }
113
132
  delete result.CLAUDE_CONFIG_DIR;
114
133
  delete result.COPILOT_HOME;
134
+ delete result.KIMI_CODE_HOME;
115
135
  }
116
136
  else if (options.agent === 'copilot') {
117
137
  // Copilot honors COPILOT_HOME (relocates ~/.copilot, including settings,
@@ -127,11 +147,28 @@ export function buildExecEnv(options) {
127
147
  }
128
148
  delete result.CLAUDE_CONFIG_DIR;
129
149
  delete result.CODEX_HOME;
150
+ delete result.KIMI_CODE_HOME;
151
+ }
152
+ else if (options.agent === 'kimi') {
153
+ // Kimi honors KIMI_CODE_HOME (relocates ~/.kimi-code, including config,
154
+ // skills, hooks, sessions). Pin it at the per-version home.
155
+ const cwd = options.cwd || process.cwd();
156
+ const resolvedVersion = options.version ?? resolveVersion('kimi', cwd);
157
+ const version = options.version
158
+ ? resolvedVersion
159
+ : (resolvedVersion && isVersionInstalled('kimi', resolvedVersion) ? resolvedVersion : null);
160
+ if (version) {
161
+ result.KIMI_CODE_HOME = path.join(getVersionHomePath('kimi', version), '.kimi-code');
162
+ }
163
+ delete result.CLAUDE_CONFIG_DIR;
164
+ delete result.CODEX_HOME;
165
+ delete result.COPILOT_HOME;
130
166
  }
131
167
  else {
132
168
  delete result.CLAUDE_CONFIG_DIR;
133
169
  delete result.CODEX_HOME;
134
170
  delete result.COPILOT_HOME;
171
+ delete result.KIMI_CODE_HOME;
135
172
  }
136
173
  return {
137
174
  ...result,
@@ -167,9 +204,9 @@ export const AGENT_COMMANDS = {
167
204
  // "workspace-write but no auto-approval" — closer to plan-as-restraint.
168
205
  // True read-only requires --sandbox read-only which we haven't wired.
169
206
  plan: ['--sandbox', 'workspace-write'],
170
- edit: ['--sandbox', 'workspace-write', '--full-auto'],
171
- // skip drops the sandbox entirely; --full-auto then approves anything.
172
- skip: ['--full-auto'],
207
+ edit: ['--sandbox', 'workspace-write', '--dangerously-bypass-approvals-and-sandbox'],
208
+ // skip drops the sandbox entirely; --dangerously-bypass-approvals-and-sandbox then approves anything.
209
+ skip: ['--dangerously-bypass-approvals-and-sandbox'],
173
210
  },
174
211
  jsonFlags: ['--json'],
175
212
  modelFlag: '--model',
@@ -303,6 +340,18 @@ export const AGENT_COMMANDS = {
303
340
  jsonFlags: ['--output-format', 'streaming-json'],
304
341
  modelFlag: '--model',
305
342
  },
343
+ kimi: {
344
+ base: ['kimi'],
345
+ promptFlag: '-p',
346
+ modeFlags: {
347
+ plan: ['--plan'],
348
+ edit: [],
349
+ auto: ['--auto'],
350
+ skip: ['--yolo'],
351
+ },
352
+ jsonFlags: ['--output-format', 'stream-json'],
353
+ modelFlag: '--model',
354
+ },
306
355
  };
307
356
  /** Assemble the full CLI argument array for an agent invocation. */
308
357
  export function buildExecCommand(options) {
@@ -314,9 +363,14 @@ export function buildExecCommand(options) {
314
363
  if (options.agent === 'codex' && interactive && cmd[1] === 'exec') {
315
364
  cmd.splice(1, 1);
316
365
  }
317
- // Use versioned alias if a specific version was requested (e.g., claude@2.1.98)
366
+ // Use versioned alias if a specific version was requested (e.g., claude@2.1.98).
367
+ // Resolve to the absolute path of the shim so spawn doesn't depend on PATH —
368
+ // on Linux installs where the shims dir isn't on PATH, spawning the bare
369
+ // versioned name fails with ENOENT even though `agents view` shows the agent.
318
370
  if (options.version && cmd.length > 0) {
319
- cmd[0] = `${cmd[0]}@${options.version}`;
371
+ const versionedName = `${cmd[0]}@${options.version}`;
372
+ const absPath = path.join(getShimsDir(), versionedName);
373
+ cmd[0] = fs.existsSync(absPath) ? absPath : versionedName;
320
374
  }
321
375
  // Add reasoning effort flags (before mode flags for codex -c positioning)
322
376
  // For codex, -c must come before 'exec' subcommand, so we insert at position 1
@@ -4,11 +4,20 @@
4
4
  */
5
5
  /** Levenshtein edit distance between two strings. */
6
6
  export declare function levenshtein(a: string, b: string): number;
7
+ /**
8
+ * Damerau-Levenshtein (optimal string alignment) distance.
9
+ * Counts a transposition of two adjacent characters as a single edit,
10
+ * so `cladue` -> `claude` is distance 1, matching the user's notion of
11
+ * "one misspelling."
12
+ */
13
+ export declare function damerauLevenshtein(a: string, b: string): number;
7
14
  export interface FuzzyOptions {
8
15
  /** Absolute max edit distance allowed. */
9
16
  maxDistance?: number;
10
17
  /** Max ratio of distance to input length. If set, effective threshold = min(maxDistance, floor(len * maxRatio)). */
11
18
  maxRatio?: number;
19
+ /** Use Damerau-Levenshtein (transposition = 1 edit) instead of plain Levenshtein. */
20
+ damerau?: boolean;
12
21
  }
13
22
  /**
14
23
  * Fuzzy match an input string against a list of candidates.
@@ -20,9 +29,10 @@ export declare function fuzzyMatch<T extends string>(input: string, candidates:
20
29
  * Based on pairwise distance analysis of candidate pools.
21
30
  */
22
31
  export declare const FUZZY_PRESETS: {
23
- /** Agents: min pairwise dist=3 (opencode/openclaw), safe tolerance=2 */
32
+ /** Agents: 1 mistype (insertion/deletion/substitution/transposition). Damerau so `cladue`->`claude` is 1. */
24
33
  readonly agents: {
25
- readonly maxDistance: 2;
34
+ readonly maxDistance: 1;
35
+ readonly damerau: true;
26
36
  };
27
37
  /** Modes: plan/edit/full all at dist=4, lenient */
28
38
  readonly modes: {
package/dist/lib/fuzzy.js CHANGED
@@ -19,12 +19,36 @@ export function levenshtein(a, b) {
19
19
  }
20
20
  return dp[m][n];
21
21
  }
22
+ /**
23
+ * Damerau-Levenshtein (optimal string alignment) distance.
24
+ * Counts a transposition of two adjacent characters as a single edit,
25
+ * so `cladue` -> `claude` is distance 1, matching the user's notion of
26
+ * "one misspelling."
27
+ */
28
+ export function damerauLevenshtein(a, b) {
29
+ const m = a.length, n = b.length;
30
+ if (m === 0)
31
+ return n;
32
+ if (n === 0)
33
+ return m;
34
+ const dp = Array.from({ length: m + 1 }, (_, i) => Array.from({ length: n + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0)));
35
+ for (let i = 1; i <= m; i++) {
36
+ for (let j = 1; j <= n; j++) {
37
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
38
+ dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
39
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
40
+ dp[i][j] = Math.min(dp[i][j], dp[i - 2][j - 2] + 1);
41
+ }
42
+ }
43
+ }
44
+ return dp[m][n];
45
+ }
22
46
  /**
23
47
  * Fuzzy match an input string against a list of candidates.
24
48
  * Returns the single best match within tolerance, or null if no match or ambiguous.
25
49
  */
26
50
  export function fuzzyMatch(input, candidates, options = {}) {
27
- const { maxDistance = 2, maxRatio } = options;
51
+ const { maxDistance = 2, maxRatio, damerau = false } = options;
28
52
  const lower = input.toLowerCase();
29
53
  // Reject inputs that are too short - they're too ambiguous
30
54
  if (lower.length < 3)
@@ -33,10 +57,11 @@ export function fuzzyMatch(input, candidates, options = {}) {
33
57
  const threshold = maxRatio
34
58
  ? Math.min(maxDistance, Math.floor(lower.length * maxRatio))
35
59
  : maxDistance;
60
+ const distance = damerau ? damerauLevenshtein : levenshtein;
36
61
  // Find all candidates within threshold (excluding exact matches)
37
62
  const matches = [];
38
63
  for (const candidate of candidates) {
39
- const dist = levenshtein(lower, candidate.toLowerCase());
64
+ const dist = distance(lower, candidate.toLowerCase());
40
65
  if (dist > 0 && dist <= threshold) {
41
66
  matches.push({ candidate, dist });
42
67
  }
@@ -55,8 +80,8 @@ export function fuzzyMatch(input, candidates, options = {}) {
55
80
  * Based on pairwise distance analysis of candidate pools.
56
81
  */
57
82
  export const FUZZY_PRESETS = {
58
- /** Agents: min pairwise dist=3 (opencode/openclaw), safe tolerance=2 */
59
- agents: { maxDistance: 2 },
83
+ /** Agents: 1 mistype (insertion/deletion/substitution/transposition). Damerau so `cladue`->`claude` is 1. */
84
+ agents: { maxDistance: 1, damerau: true },
60
85
  /** Modes: plan/edit/full all at dist=4, lenient */
61
86
  modes: { maxDistance: 2 },
62
87
  /** Efforts: high/xhigh at dist=1, must be strict */
package/dist/lib/git.js CHANGED
@@ -32,7 +32,14 @@ function installGithooksSymlinks(repoDir) {
32
32
  if (fs.lstatSync(dest, { throwIfNoEntry: false })) {
33
33
  fs.rmSync(dest);
34
34
  }
35
- fs.symlinkSync(target, dest);
35
+ try {
36
+ fs.symlinkSync(target, dest);
37
+ }
38
+ catch (err) {
39
+ // Windows requires Developer Mode or elevated privileges for symlinks; skip gracefully.
40
+ if (err.code !== 'EPERM')
41
+ throw err;
42
+ }
36
43
  }
37
44
  }
38
45
  /**
@@ -110,12 +110,12 @@ export declare function installHooksCentrally(source: string): Promise<{
110
110
  errors: string[];
111
111
  }>;
112
112
  /**
113
- * List hooks from user (~/.agents/hooks/) and system (~/.agents-system/hooks/) dirs.
113
+ * List hooks from user (~/.agents/hooks/) and system (~/.agents/.system/hooks/) dirs.
114
114
  * User dir takes priority; deduplication preserves first occurrence.
115
115
  */
116
116
  export declare function listCentralHooks(): HookEntry[];
117
117
  /**
118
- * Parse hook manifests. Reads system hooks from ~/.agents-system/hooks.yaml
118
+ * Parse hook manifests. Reads system hooks from ~/.agents/.system/hooks.yaml
119
119
  * (npm-shipped defaults) and user hooks from the `hooks:` section of
120
120
  * ~/.agents/agents.yaml. Merges with user-wins-on-key-collision precedence.
121
121
  * A user entry with `enabled: false` disables the system-shipped hook of
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>;