@phnx-labs/agents-cli 1.20.3 → 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 (193) hide show
  1. package/CHANGELOG.md +27 -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/import.js +90 -37
  10. package/dist/commands/inspect.d.ts +26 -0
  11. package/dist/commands/inspect.js +590 -0
  12. package/dist/commands/mcp.js +17 -16
  13. package/dist/commands/models.js +1 -1
  14. package/dist/commands/packages.js +6 -4
  15. package/dist/commands/permissions.js +13 -12
  16. package/dist/commands/plugins.d.ts +13 -0
  17. package/dist/commands/plugins.js +100 -11
  18. package/dist/commands/prune.js +3 -2
  19. package/dist/commands/pull.d.ts +12 -5
  20. package/dist/commands/pull.js +26 -422
  21. package/dist/commands/push.d.ts +14 -0
  22. package/dist/commands/push.js +30 -0
  23. package/dist/commands/repo.d.ts +1 -1
  24. package/dist/commands/repo.js +155 -112
  25. package/dist/commands/resource-view.d.ts +2 -0
  26. package/dist/commands/resource-view.js +12 -3
  27. package/dist/commands/routines.js +32 -7
  28. package/dist/commands/rules.js +1 -1
  29. package/dist/commands/sessions.js +1 -0
  30. package/dist/commands/setup.d.ts +3 -3
  31. package/dist/commands/setup.js +15 -15
  32. package/dist/commands/skills.js +6 -5
  33. package/dist/commands/subagents.js +5 -4
  34. package/dist/commands/sync.d.ts +18 -5
  35. package/dist/commands/sync.js +251 -65
  36. package/dist/commands/teams.js +1 -0
  37. package/dist/commands/tmux.d.ts +25 -0
  38. package/dist/commands/tmux.js +415 -0
  39. package/dist/commands/trash.d.ts +2 -2
  40. package/dist/commands/trash.js +1 -1
  41. package/dist/commands/versions.js +2 -2
  42. package/dist/commands/view.js +14 -4
  43. package/dist/commands/workflows.js +4 -3
  44. package/dist/commands/worktree.d.ts +4 -5
  45. package/dist/commands/worktree.js +4 -4
  46. package/dist/index.js +68 -20
  47. package/dist/lib/agents.d.ts +19 -10
  48. package/dist/lib/agents.js +102 -28
  49. package/dist/lib/auto-pull-worker.d.ts +1 -1
  50. package/dist/lib/auto-pull-worker.js +2 -2
  51. package/dist/lib/auto-pull.d.ts +1 -1
  52. package/dist/lib/auto-pull.js +1 -1
  53. package/dist/lib/beta.d.ts +1 -1
  54. package/dist/lib/beta.js +1 -1
  55. package/dist/lib/capabilities.js +2 -0
  56. package/dist/lib/commands.d.ts +28 -1
  57. package/dist/lib/commands.js +125 -20
  58. package/dist/lib/doctor-diff.js +2 -2
  59. package/dist/lib/exec.d.ts +14 -0
  60. package/dist/lib/exec.js +39 -5
  61. package/dist/lib/fuzzy.d.ts +12 -2
  62. package/dist/lib/fuzzy.js +29 -4
  63. package/dist/lib/git.js +8 -1
  64. package/dist/lib/hooks.d.ts +2 -2
  65. package/dist/lib/hooks.js +97 -10
  66. package/dist/lib/import.d.ts +21 -0
  67. package/dist/lib/import.js +55 -2
  68. package/dist/lib/mcp.js +32 -2
  69. package/dist/lib/migrate.d.ts +51 -0
  70. package/dist/lib/migrate.js +227 -1
  71. package/dist/lib/models.js +62 -15
  72. package/dist/lib/permissions.d.ts +36 -2
  73. package/dist/lib/permissions.js +217 -7
  74. package/dist/lib/plugin-marketplace.d.ts +108 -40
  75. package/dist/lib/plugin-marketplace.js +243 -94
  76. package/dist/lib/plugins.d.ts +21 -4
  77. package/dist/lib/plugins.js +130 -49
  78. package/dist/lib/profiles-presets.js +12 -12
  79. package/dist/lib/project-launch.d.ts +65 -0
  80. package/dist/lib/project-launch.js +367 -0
  81. package/dist/lib/pty-client.js +1 -1
  82. package/dist/lib/pty-server.d.ts +1 -1
  83. package/dist/lib/pty-server.js +28 -4
  84. package/dist/lib/refresh.d.ts +26 -0
  85. package/dist/lib/refresh.js +315 -0
  86. package/dist/lib/resource-patterns.d.ts +1 -1
  87. package/dist/lib/resource-patterns.js +1 -1
  88. package/dist/lib/resources/commands.js +2 -2
  89. package/dist/lib/resources/hooks.d.ts +1 -1
  90. package/dist/lib/resources/hooks.js +1 -1
  91. package/dist/lib/resources/mcp.d.ts +1 -1
  92. package/dist/lib/resources/mcp.js +5 -6
  93. package/dist/lib/resources/permissions.js +5 -2
  94. package/dist/lib/resources/rules.js +3 -2
  95. package/dist/lib/resources/skills.js +3 -2
  96. package/dist/lib/resources/types.d.ts +1 -1
  97. package/dist/lib/resources.js +2 -2
  98. package/dist/lib/rotate.d.ts +1 -1
  99. package/dist/lib/rotate.js +1 -1
  100. package/dist/lib/routines.d.ts +16 -4
  101. package/dist/lib/routines.js +67 -17
  102. package/dist/lib/rules/compile.js +22 -10
  103. package/dist/lib/rules/rules.js +3 -3
  104. package/dist/lib/runner.js +16 -3
  105. package/dist/lib/scheduler.js +15 -1
  106. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  107. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  108. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +9 -1
  109. package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
  110. package/dist/lib/secrets/linux.d.ts +44 -9
  111. package/dist/lib/secrets/linux.js +302 -48
  112. package/dist/lib/session/db.js +15 -2
  113. package/dist/lib/session/discover.js +118 -3
  114. package/dist/lib/session/parse.js +3 -0
  115. package/dist/lib/session/types.d.ts +1 -1
  116. package/dist/lib/session/types.js +1 -1
  117. package/dist/lib/shims.d.ts +10 -9
  118. package/dist/lib/shims.js +101 -50
  119. package/dist/lib/skills.d.ts +1 -1
  120. package/dist/lib/skills.js +10 -9
  121. package/dist/lib/staleness/detectors/commands.d.ts +3 -0
  122. package/dist/lib/staleness/detectors/commands.js +46 -0
  123. package/dist/lib/staleness/detectors/hooks.d.ts +3 -0
  124. package/dist/lib/staleness/detectors/hooks.js +44 -0
  125. package/dist/lib/staleness/detectors/mcp.d.ts +3 -0
  126. package/dist/lib/staleness/detectors/mcp.js +31 -0
  127. package/dist/lib/staleness/detectors/permissions.d.ts +3 -0
  128. package/dist/lib/staleness/detectors/permissions.js +201 -0
  129. package/dist/lib/staleness/detectors/plugins.d.ts +8 -0
  130. package/dist/lib/staleness/detectors/plugins.js +23 -0
  131. package/dist/lib/staleness/detectors/rules.d.ts +3 -0
  132. package/dist/lib/staleness/detectors/rules.js +34 -0
  133. package/dist/lib/staleness/detectors/skills.d.ts +3 -0
  134. package/dist/lib/staleness/detectors/skills.js +71 -0
  135. package/dist/lib/staleness/detectors/subagents.d.ts +3 -0
  136. package/dist/lib/staleness/detectors/subagents.js +50 -0
  137. package/dist/lib/staleness/detectors/types.d.ts +22 -0
  138. package/dist/lib/staleness/detectors/types.js +1 -0
  139. package/dist/lib/staleness/detectors/workflows.d.ts +3 -0
  140. package/dist/lib/staleness/detectors/workflows.js +28 -0
  141. package/dist/lib/staleness/registry.d.ts +26 -0
  142. package/dist/lib/staleness/registry.js +123 -0
  143. package/dist/lib/staleness/writers/commands.d.ts +3 -0
  144. package/dist/lib/staleness/writers/commands.js +111 -0
  145. package/dist/lib/staleness/writers/hooks.d.ts +3 -0
  146. package/dist/lib/staleness/writers/hooks.js +47 -0
  147. package/dist/lib/staleness/writers/kinds.d.ts +10 -0
  148. package/dist/lib/staleness/writers/kinds.js +15 -0
  149. package/dist/lib/staleness/writers/lazy-map.d.ts +13 -0
  150. package/dist/lib/staleness/writers/lazy-map.js +19 -0
  151. package/dist/lib/staleness/writers/mcp.d.ts +10 -0
  152. package/dist/lib/staleness/writers/mcp.js +19 -0
  153. package/dist/lib/staleness/writers/permissions.d.ts +13 -0
  154. package/dist/lib/staleness/writers/permissions.js +26 -0
  155. package/dist/lib/staleness/writers/plugins.d.ts +7 -0
  156. package/dist/lib/staleness/writers/plugins.js +31 -0
  157. package/dist/lib/staleness/writers/rules.d.ts +7 -0
  158. package/dist/lib/staleness/writers/rules.js +55 -0
  159. package/dist/lib/staleness/writers/skills.d.ts +3 -0
  160. package/dist/lib/staleness/writers/skills.js +81 -0
  161. package/dist/lib/staleness/writers/sources.d.ts +16 -0
  162. package/dist/lib/staleness/writers/sources.js +72 -0
  163. package/dist/lib/staleness/writers/subagents.d.ts +3 -0
  164. package/dist/lib/staleness/writers/subagents.js +53 -0
  165. package/dist/lib/staleness/writers/types.d.ts +36 -0
  166. package/dist/lib/staleness/writers/types.js +1 -0
  167. package/dist/lib/staleness/writers/workflows.d.ts +7 -0
  168. package/dist/lib/staleness/writers/workflows.js +31 -0
  169. package/dist/lib/state.d.ts +34 -11
  170. package/dist/lib/state.js +58 -13
  171. package/dist/lib/subagents.d.ts +0 -2
  172. package/dist/lib/subagents.js +6 -6
  173. package/dist/lib/teams/agents.js +1 -1
  174. package/dist/lib/teams/parsers.d.ts +1 -1
  175. package/dist/lib/tmux/binary.d.ts +67 -0
  176. package/dist/lib/tmux/binary.js +141 -0
  177. package/dist/lib/tmux/index.d.ts +8 -0
  178. package/dist/lib/tmux/index.js +8 -0
  179. package/dist/lib/tmux/paths.d.ts +17 -0
  180. package/dist/lib/tmux/paths.js +30 -0
  181. package/dist/lib/tmux/session.d.ts +122 -0
  182. package/dist/lib/tmux/session.js +305 -0
  183. package/dist/lib/types.d.ts +58 -7
  184. package/dist/lib/types.js +1 -1
  185. package/dist/lib/usage.js +1 -1
  186. package/dist/lib/versions.d.ts +4 -4
  187. package/dist/lib/versions.js +154 -491
  188. package/dist/lib/workflows.d.ts +2 -4
  189. package/dist/lib/workflows.js +3 -4
  190. package/package.json +7 -7
  191. package/scripts/postinstall.js +16 -63
  192. package/dist/commands/status.d.ts +0 -9
  193. package/dist/commands/status.js +0 -25
@@ -13,7 +13,28 @@ export type CommandScope = 'user' | 'project';
13
13
  export interface CommandMetadata {
14
14
  name: string;
15
15
  description: string;
16
+ /** When set, sync only to these agents (aliases resolved at parse time). */
17
+ agents?: AgentId[];
18
+ /** Minimum agent CLI version (inclusive) for this command. */
19
+ since?: string;
20
+ /** Exclusive upper bound on agent CLI version for this command. */
21
+ until?: string;
16
22
  }
23
+ export type CommandApplyFailReason = 'unsupported' | 'agent_excluded' | 'too_old' | 'too_new';
24
+ export type CommandApplyResult = {
25
+ ok: true;
26
+ } | {
27
+ ok: false;
28
+ reason: CommandApplyFailReason;
29
+ need?: string;
30
+ };
31
+ /**
32
+ * Whether a slash command should sync to the given agent@version. Checks
33
+ * frontmatter `agents` / `since` / `until` after the agent-level commands
34
+ * (or commands-as-skills) capability gate.
35
+ */
36
+ export declare function commandAppliesTo(agent: AgentId, version: string, metadata: Pick<CommandMetadata, 'agents' | 'since' | 'until'> | null | undefined): CommandApplyResult;
37
+ export declare function explainCommandSkip(agent: AgentId, version: string, commandName: string, result: CommandApplyResult): string;
17
38
  /** Result of validating command metadata. */
18
39
  export interface ValidationResult {
19
40
  valid: boolean;
@@ -35,6 +56,8 @@ export interface InstalledCommand {
35
56
  path: string;
36
57
  description?: string;
37
58
  }
59
+ /** Parse command metadata (name, description, targeting) from YAML frontmatter or TOML headers. */
60
+ export declare function parseCommandMetadata(filePath: string): CommandMetadata | null;
38
61
  /** Discover all command markdown files in a repository's commands/ directory. */
39
62
  export declare function discoverCommands(repoPath: string): DiscoveredCommand[];
40
63
  /** Find the source path for a command in a repository. */
@@ -59,6 +82,8 @@ export interface VersionCommandDiff {
59
82
  toAdd: string[];
60
83
  toUpdate: string[];
61
84
  matched: string[];
85
+ /** Installed but excluded by frontmatter agents/since/until — safe to remove. */
86
+ toRemove: string[];
62
87
  orphans: string[];
63
88
  }
64
89
  /**
@@ -72,6 +97,8 @@ export declare function diffVersionCommands(agent: AgentId, version: string): Ve
72
97
  export declare function installCommandToVersion(agent: AgentId, version: string, commandName: string, method?: 'symlink' | 'copy'): {
73
98
  success: boolean;
74
99
  error?: string;
100
+ skipped?: boolean;
101
+ skipReason?: string;
75
102
  };
76
103
  /**
77
104
  * Remove a single command from a specific version home.
@@ -112,7 +139,7 @@ export declare function installCommandCentrally(sourcePath: string, commandName:
112
139
  warnings?: string[];
113
140
  };
114
141
  /**
115
- * List commands from user (~/.agents/commands/) and system (~/.agents-system/commands/) dirs.
142
+ * List commands from user (~/.agents/commands/) and system (~/.agents/.system/commands/) dirs.
116
143
  * User dir takes priority; deduplication preserves first occurrence.
117
144
  */
118
145
  export declare function listCentralCommands(): string[];
@@ -9,13 +9,78 @@
9
9
  import * as fs from 'fs';
10
10
  import * as path from 'path';
11
11
  import * as yaml from 'yaml';
12
- import { AGENTS, COMMANDS_CAPABLE_AGENTS, ensureCommandsDir } from './agents.js';
12
+ import { AGENTS, ensureCommandsDir, agentConfigDirName, resolveAgentName } from './agents.js';
13
+ import { capableAgents, isCapable, supports } from './capabilities.js';
13
14
  import { markdownToToml } from './convert.js';
14
15
  import { getCommandsDir, getUserCommandsDir, getEnabledExtraRepos, getProjectAgentsDir, getSkillsDir, getTrashCommandsDir } from './state.js';
15
- import { getEffectiveHome, getVersionHomePath, listInstalledVersions } from './versions.js';
16
+ import { getEffectiveHome, getVersionHomePath, listInstalledVersions, resolveVersion } from './versions.js';
16
17
  import { commandSkillMatches, installCommandSkillToVersion, listCommandSkillsInVersion, removeCommandSkillFromVersion, shouldInstallCommandAsSkill, } from './command-skills.js';
17
- /** Parse command metadata (name, description) from YAML frontmatter or TOML headers. */
18
- function parseCommandMetadata(filePath) {
18
+ function compareVersions(a, b) {
19
+ const aParts = a.split('.').map((n) => parseInt(n, 10) || 0);
20
+ const bParts = b.split('.').map((n) => parseInt(n, 10) || 0);
21
+ for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
22
+ const aVal = aParts[i] || 0;
23
+ const bVal = bParts[i] || 0;
24
+ if (aVal !== bVal)
25
+ return aVal - bVal;
26
+ }
27
+ return 0;
28
+ }
29
+ function parseAgentsField(raw) {
30
+ if (raw == null)
31
+ return undefined;
32
+ const tokens = Array.isArray(raw)
33
+ ? raw
34
+ : typeof raw === 'string'
35
+ ? raw.split(/[,\s]+/)
36
+ : [];
37
+ const out = [];
38
+ for (const token of tokens) {
39
+ const id = resolveAgentName(String(token).trim());
40
+ if (id && !out.includes(id))
41
+ out.push(id);
42
+ }
43
+ return out.length > 0 ? out : undefined;
44
+ }
45
+ /**
46
+ * Whether a slash command should sync to the given agent@version. Checks
47
+ * frontmatter `agents` / `since` / `until` after the agent-level commands
48
+ * (or commands-as-skills) capability gate.
49
+ */
50
+ export function commandAppliesTo(agent, version, metadata) {
51
+ if (!supports(agent, 'commands', version).ok && !shouldInstallCommandAsSkill(agent, version)) {
52
+ return { ok: false, reason: 'unsupported' };
53
+ }
54
+ if (metadata?.agents?.length && !metadata.agents.includes(agent)) {
55
+ return { ok: false, reason: 'agent_excluded' };
56
+ }
57
+ if (metadata?.since && compareVersions(version, metadata.since) < 0) {
58
+ return { ok: false, reason: 'too_old', need: `>= ${metadata.since}` };
59
+ }
60
+ if (metadata?.until && compareVersions(version, metadata.until) >= 0) {
61
+ return { ok: false, reason: 'too_new', need: `< ${metadata.until}` };
62
+ }
63
+ return { ok: true };
64
+ }
65
+ export function explainCommandSkip(agent, version, commandName, result) {
66
+ if (result.ok)
67
+ return '';
68
+ const tag = `${agent}@${version}`;
69
+ switch (result.reason) {
70
+ case 'unsupported':
71
+ return `${tag}: /${commandName} not supported for this agent version`;
72
+ case 'agent_excluded':
73
+ return `${tag}: /${commandName} excluded by command frontmatter agents list`;
74
+ case 'too_old':
75
+ return `${tag}: /${commandName} requires ${result.need}`;
76
+ case 'too_new':
77
+ return `${tag}: /${commandName} requires ${result.need}`;
78
+ default:
79
+ return `${tag}: /${commandName} skipped`;
80
+ }
81
+ }
82
+ /** Parse command metadata (name, description, targeting) from YAML frontmatter or TOML headers. */
83
+ export function parseCommandMetadata(filePath) {
19
84
  if (!fs.existsSync(filePath)) {
20
85
  return null;
21
86
  }
@@ -31,6 +96,9 @@ function parseCommandMetadata(filePath) {
31
96
  return {
32
97
  name: parsed.name || '',
33
98
  description: parsed.description || '',
99
+ agents: parseAgentsField(parsed.agents),
100
+ since: typeof parsed.since === 'string' ? parsed.since : undefined,
101
+ until: typeof parsed.until === 'string' ? parsed.until : undefined,
34
102
  };
35
103
  }
36
104
  }
@@ -126,10 +194,22 @@ export function installCommand(sourcePath, agentId, commandName, method = 'symli
126
194
  warnings: validation.warnings,
127
195
  };
128
196
  }
197
+ const pinnedVersion = resolveVersion(agentId, process.cwd());
198
+ if (pinnedVersion) {
199
+ const gate = commandAppliesTo(agentId, pinnedVersion, metadata);
200
+ if (!gate.ok) {
201
+ return {
202
+ path: '',
203
+ method: 'copy',
204
+ error: explainCommandSkip(agentId, pinnedVersion, commandName, gate),
205
+ warnings: validation.warnings,
206
+ };
207
+ }
208
+ }
129
209
  const agent = AGENTS[agentId];
130
210
  ensureCommandsDir(agentId);
131
211
  const home = getEffectiveHome(agentId);
132
- const commandsDir = path.join(home, `.${agentId}`, agent.commandsSubdir);
212
+ const commandsDir = path.join(home, agentConfigDirName(agentId), agent.commandsSubdir);
133
213
  fs.mkdirSync(commandsDir, { recursive: true });
134
214
  const ext = agent.format === 'toml' ? '.toml' : '.md';
135
215
  const targetPath = path.join(commandsDir, `${commandName}${ext}`);
@@ -157,14 +237,14 @@ export function installCommand(sourcePath, agentId, commandName, method = 'symli
157
237
  */
158
238
  export function getVersionCommandsDir(agent, version) {
159
239
  const home = getVersionHomePath(agent, version);
160
- return path.join(home, `.${agent}`, AGENTS[agent].commandsSubdir);
240
+ return path.join(home, agentConfigDirName(agent), AGENTS[agent].commandsSubdir);
161
241
  }
162
242
  /**
163
243
  * List command names (without extension) installed in a specific version home.
164
244
  */
165
245
  export function listCommandsInVersionHome(agent, version) {
166
246
  const versionHome = getVersionHomePath(agent, version);
167
- const agentDir = path.join(versionHome, `.${agent}`);
247
+ const agentDir = path.join(versionHome, agentConfigDirName(agent));
168
248
  if (shouldInstallCommandAsSkill(agent, version)) {
169
249
  return listCommandSkillsInVersion(agentDir);
170
250
  }
@@ -186,7 +266,7 @@ function versionCommandMatches(agent, version, commandName) {
186
266
  if (!fs.existsSync(sourcePath))
187
267
  return false;
188
268
  const versionHome = getVersionHomePath(agent, version);
189
- const agentDir = path.join(versionHome, `.${agent}`);
269
+ const agentDir = path.join(versionHome, agentConfigDirName(agent));
190
270
  if (shouldInstallCommandAsSkill(agent, version)) {
191
271
  return commandSkillMatches(agentDir, commandName, sourcePath);
192
272
  }
@@ -217,8 +297,13 @@ export function diffVersionCommands(agent, version) {
217
297
  const toAdd = [];
218
298
  const toUpdate = [];
219
299
  const matched = [];
300
+ const toRemove = [];
220
301
  const orphans = [];
221
302
  for (const name of central) {
303
+ const sourcePath = path.join(getCommandsDir(), `${name}.md`);
304
+ const metadata = parseCommandMetadata(sourcePath);
305
+ if (!commandAppliesTo(agent, version, metadata).ok)
306
+ continue;
222
307
  if (!installed.has(name)) {
223
308
  toAdd.push(name);
224
309
  }
@@ -230,10 +315,25 @@ export function diffVersionCommands(agent, version) {
230
315
  }
231
316
  }
232
317
  for (const name of installed) {
233
- if (!central.has(name))
318
+ if (!central.has(name)) {
234
319
  orphans.push(name);
320
+ continue;
321
+ }
322
+ const sourcePath = path.join(getCommandsDir(), `${name}.md`);
323
+ const metadata = parseCommandMetadata(sourcePath);
324
+ if (!commandAppliesTo(agent, version, metadata).ok) {
325
+ toRemove.push(name);
326
+ }
235
327
  }
236
- return { agent, version, toAdd: toAdd.sort(), toUpdate: toUpdate.sort(), matched, orphans: orphans.sort() };
328
+ return {
329
+ agent,
330
+ version,
331
+ toAdd: toAdd.sort(),
332
+ toUpdate: toUpdate.sort(),
333
+ matched,
334
+ toRemove: toRemove.sort(),
335
+ orphans: orphans.sort(),
336
+ };
237
337
  }
238
338
  /**
239
339
  * Install a single command from central into a specific version home.
@@ -244,8 +344,13 @@ export function installCommandToVersion(agent, version, commandName, method = 'c
244
344
  if (!fs.existsSync(sourcePath)) {
245
345
  return { success: false, error: `Command '${commandName}' not found in central` };
246
346
  }
347
+ const metadata = parseCommandMetadata(sourcePath);
348
+ const gate = commandAppliesTo(agent, version, metadata);
349
+ if (!gate.ok) {
350
+ return { success: true, skipped: true, skipReason: explainCommandSkip(agent, version, commandName, gate) };
351
+ }
247
352
  const versionHome = getVersionHomePath(agent, version);
248
- const agentDir = path.join(versionHome, `.${agent}`);
353
+ const agentDir = path.join(versionHome, agentConfigDirName(agent));
249
354
  if (shouldInstallCommandAsSkill(agent, version)) {
250
355
  return installCommandSkillToVersion(agentDir, commandName, sourcePath, [
251
356
  getSkillsDir(),
@@ -283,7 +388,7 @@ export function installCommandToVersion(agent, version, commandName, method = 'c
283
388
  */
284
389
  export function removeCommandFromVersion(agent, version, commandName) {
285
390
  const versionHome = getVersionHomePath(agent, version);
286
- const agentDir = path.join(versionHome, `.${agent}`);
391
+ const agentDir = path.join(versionHome, agentConfigDirName(agent));
287
392
  if (shouldInstallCommandAsSkill(agent, version)) {
288
393
  return removeCommandSkillFromVersion(agentDir, commandName);
289
394
  }
@@ -309,9 +414,9 @@ export function removeCommandFromVersion(agent, version, commandName) {
309
414
  */
310
415
  export function iterCommandsCapableVersions(filter) {
311
416
  const pairs = [];
312
- const agents = filter?.agent ? [filter.agent] : COMMANDS_CAPABLE_AGENTS;
417
+ const agents = filter?.agent ? [filter.agent] : capableAgents('commands');
313
418
  for (const agent of agents) {
314
- if (!COMMANDS_CAPABLE_AGENTS.includes(agent))
419
+ if (!isCapable(agent, 'commands'))
315
420
  continue;
316
421
  const versions = listInstalledVersions(agent);
317
422
  for (const version of versions) {
@@ -326,7 +431,7 @@ export function iterCommandsCapableVersions(filter) {
326
431
  export function uninstallCommand(agentId, commandName) {
327
432
  const agent = AGENTS[agentId];
328
433
  const home = getEffectiveHome(agentId);
329
- const commandsDir = path.join(home, `.${agentId}`, agent.commandsSubdir);
434
+ const commandsDir = path.join(home, agentConfigDirName(agentId), agent.commandsSubdir);
330
435
  const ext = agent.format === 'toml' ? '.toml' : '.md';
331
436
  const targetPath = path.join(commandsDir, `${commandName}${ext}`);
332
437
  if (fs.existsSync(targetPath)) {
@@ -339,7 +444,7 @@ export function uninstallCommand(agentId, commandName) {
339
444
  function listInstalledCommands(agentId) {
340
445
  const agent = AGENTS[agentId];
341
446
  const home = getEffectiveHome(agentId);
342
- const commandsDir = path.join(home, `.${agentId}`, agent.commandsSubdir);
447
+ const commandsDir = path.join(home, agentConfigDirName(agentId), agent.commandsSubdir);
343
448
  if (!fs.existsSync(commandsDir)) {
344
449
  return [];
345
450
  }
@@ -355,7 +460,7 @@ function listInstalledCommands(agentId) {
355
460
  function commandExists(agentId, commandName) {
356
461
  const agent = AGENTS[agentId];
357
462
  const home = getEffectiveHome(agentId);
358
- const commandsDir = path.join(home, `.${agentId}`, agent.commandsSubdir);
463
+ const commandsDir = path.join(home, agentConfigDirName(agentId), agent.commandsSubdir);
359
464
  const ext = agent.format === 'toml' ? '.toml' : '.md';
360
465
  const targetPath = path.join(commandsDir, `${commandName}${ext}`);
361
466
  return fs.existsSync(targetPath);
@@ -373,7 +478,7 @@ function normalizeContent(content) {
373
478
  function commandContentMatches(agentId, commandName, sourcePath) {
374
479
  const agent = AGENTS[agentId];
375
480
  const home = getEffectiveHome(agentId);
376
- const commandsDir = path.join(home, `.${agentId}`, agent.commandsSubdir);
481
+ const commandsDir = path.join(home, agentConfigDirName(agentId), agent.commandsSubdir);
377
482
  const ext = agent.format === 'toml' ? '.toml' : '.md';
378
483
  const installedPath = path.join(commandsDir, `${commandName}${ext}`);
379
484
  if (!fs.existsSync(installedPath) || !fs.existsSync(sourcePath)) {
@@ -455,7 +560,7 @@ export function listInstalledCommandsWithScope(agentId, cwd = process.cwd(), opt
455
560
  }
456
561
  // User-scoped commands (version-aware when home is provided)
457
562
  const home = options?.home || getEffectiveHome(agentId);
458
- const userCommandsDir = path.join(home, `.${agentId}`, agent.commandsSubdir);
563
+ const userCommandsDir = path.join(home, agentConfigDirName(agentId), agent.commandsSubdir);
459
564
  const userExts = [ext];
460
565
  const userCommands = listCommandsFromDir(userCommandsDir, userExts);
461
566
  for (const name of userCommands) {
@@ -509,7 +614,7 @@ export function installCommandCentrally(sourcePath, commandName) {
509
614
  }
510
615
  }
511
616
  /**
512
- * List commands from user (~/.agents/commands/) and system (~/.agents-system/commands/) dirs.
617
+ * List commands from user (~/.agents/commands/) and system (~/.agents/.system/commands/) dirs.
513
618
  * User dir takes priority; deduplication preserves first occurrence.
514
619
  */
515
620
  export function listCentralCommands() {
@@ -21,7 +21,7 @@
21
21
  */
22
22
  import * as fs from 'fs';
23
23
  import * as path from 'path';
24
- import { AGENTS } from './agents.js';
24
+ import { AGENTS, agentConfigDirName } from './agents.js';
25
25
  import { getProjectAgentsDir, getUserAgentsDir, getSystemAgentsDir, getEnabledExtraRepos, getResolvedRulesDir, getUserRulesDir, getEffectivePromptcutsPath, } from './state.js';
26
26
  import { getAvailableResources, getActuallySyncedResources, getVersionHomePath, } from './versions.js';
27
27
  import { markdownToToml } from './convert.js';
@@ -352,7 +352,7 @@ function expectedRuleContent(agent, name, sourcePath) {
352
352
  function diffRules(agent, version, cwd) {
353
353
  const agentConfig = AGENTS[agent];
354
354
  const versionHome = getVersionHomePath(agent, version);
355
- const configDir = path.join(versionHome, `.${agent}`);
355
+ const configDir = path.join(versionHome, agentConfigDirName(agent));
356
356
  const sourcesByName = listRulesNames(cwd);
357
357
  // Files actually present in the version home.
358
358
  const homeFiles = new Set();
@@ -23,6 +23,20 @@ export declare function normalizeMode(input: string | null | undefined): Mode;
23
23
  * - `plan` on an agent without plan support throws the same way.
24
24
  */
25
25
  export declare function resolveMode(agent: AgentId, requested: Mode): Mode;
26
+ /**
27
+ * The mode an agent should run in when the caller has no preference.
28
+ *
29
+ * Returns the first entry in the agent's `capabilities.modes` table — the
30
+ * declaration order is the source of truth for "the safest mode this agent
31
+ * supports." Agents that include `plan` list it first; agents like
32
+ * antigravity that have no read-only mode list `edit` first.
33
+ *
34
+ * Use this when the user did not pass `--mode` explicitly. When the user
35
+ * *did* pass `--mode plan` and the agent doesn't support it, call
36
+ * `resolveMode` instead so the user sees a loud error rather than a silent
37
+ * elevation from read-only to writable.
38
+ */
39
+ export declare function defaultModeFor(agent: AgentId): Mode;
26
40
  /** Reasoning effort levels passed to agents that support them. 'auto' defers to the agent's default. */
27
41
  export type ExecEffort = 'low' | 'medium' | 'high' | 'xhigh' | 'max' | 'auto';
28
42
  /** Options for spawning an agent process. Omitting `prompt` launches the CLI interactively. */
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. */
@@ -167,9 +185,9 @@ export const AGENT_COMMANDS = {
167
185
  // "workspace-write but no auto-approval" — closer to plan-as-restraint.
168
186
  // True read-only requires --sandbox read-only which we haven't wired.
169
187
  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'],
188
+ edit: ['--sandbox', 'workspace-write', '--dangerously-bypass-approvals-and-sandbox'],
189
+ // skip drops the sandbox entirely; --dangerously-bypass-approvals-and-sandbox then approves anything.
190
+ skip: ['--dangerously-bypass-approvals-and-sandbox'],
173
191
  },
174
192
  jsonFlags: ['--json'],
175
193
  modelFlag: '--model',
@@ -303,6 +321,17 @@ export const AGENT_COMMANDS = {
303
321
  jsonFlags: ['--output-format', 'streaming-json'],
304
322
  modelFlag: '--model',
305
323
  },
324
+ kimi: {
325
+ base: ['kimi-code'],
326
+ promptFlag: '-p',
327
+ modeFlags: {
328
+ plan: [],
329
+ edit: [],
330
+ skip: [],
331
+ },
332
+ jsonFlags: [],
333
+ modelFlag: '--model',
334
+ },
306
335
  };
307
336
  /** Assemble the full CLI argument array for an agent invocation. */
308
337
  export function buildExecCommand(options) {
@@ -314,9 +343,14 @@ export function buildExecCommand(options) {
314
343
  if (options.agent === 'codex' && interactive && cmd[1] === 'exec') {
315
344
  cmd.splice(1, 1);
316
345
  }
317
- // Use versioned alias if a specific version was requested (e.g., claude@2.1.98)
346
+ // Use versioned alias if a specific version was requested (e.g., claude@2.1.98).
347
+ // Resolve to the absolute path of the shim so spawn doesn't depend on PATH —
348
+ // on Linux installs where the shims dir isn't on PATH, spawning the bare
349
+ // versioned name fails with ENOENT even though `agents view` shows the agent.
318
350
  if (options.version && cmd.length > 0) {
319
- cmd[0] = `${cmd[0]}@${options.version}`;
351
+ const versionedName = `${cmd[0]}@${options.version}`;
352
+ const absPath = path.join(getShimsDir(), versionedName);
353
+ cmd[0] = fs.existsSync(absPath) ? absPath : versionedName;
320
354
  }
321
355
  // Add reasoning effort flags (before mode flags for codex -c positioning)
322
356
  // 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