@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
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Background sync for tracked git repos:
3
- * - System repo (~/.agents-system/) is read-only locally — fast-forward auto-pull is safe.
3
+ * - System repo (~/.agents/.system/) is read-only locally — fast-forward auto-pull is safe.
4
4
  * - User repo (~/.agents/) and enabled extras may have local commits, so we only
5
5
  * `git fetch` and write a status marker. Next CLI invocation surfaces a one-line
6
6
  * notice if upstream is ahead. Pulling is left to the user via `agents repo pull`.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Background sync for tracked git repos:
3
- * - System repo (~/.agents-system/) is read-only locally — fast-forward auto-pull is safe.
3
+ * - System repo (~/.agents/.system/) is read-only locally — fast-forward auto-pull is safe.
4
4
  * - User repo (~/.agents/) and enabled extras may have local commits, so we only
5
5
  * `git fetch` and write a status marker. Next CLI invocation surfaces a one-line
6
6
  * notice if upstream is ahead. Pulling is left to the user via `agents repo pull`.
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Preview features live in the git-trackable user repo (~/.agents/agents.yaml)
5
5
  * when present, and otherwise fall back to the local system state file
6
- * (~/.agents-system/agents.yaml). This keeps opt-ins portable for users with a
6
+ * (~/.agents/.system/agents.yaml). This keeps opt-ins portable for users with a
7
7
  * personal agents repo without mixing them into unrelated version capability
8
8
  * checks.
9
9
  */
package/dist/lib/beta.js CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Preview features live in the git-trackable user repo (~/.agents/agents.yaml)
5
5
  * when present, and otherwise fall back to the local system state file
6
- * (~/.agents-system/agents.yaml). This keeps opt-ins portable for users with a
6
+ * (~/.agents/.system/agents.yaml). This keeps opt-ins portable for users with a
7
7
  * personal agents repo without mixing them into unrelated version capability
8
8
  * checks.
9
9
  */
@@ -1,5 +1,15 @@
1
1
  import type { ChromeOptions } from './types.js';
2
2
  import type { BrowserType } from './types.js';
3
+ /**
4
+ * True when `binaryPath` is a shebang script rather than a native browser
5
+ * executable. The Linux distro launchers (`/usr/bin/brave-browser`, …) are such
6
+ * scripts; `launchBrowser` can't drive one over `--remote-debugging-pipe` (see
7
+ * resolveBrowserBinary). `profiles doctor` uses this to flag a profile whose
8
+ * binary resolves to a wrapper we couldn't unwrap. Shebang scripts are a
9
+ * Linux/Unix concept — returns false on Windows/macOS app bundles.
10
+ */
11
+ export declare function isLauncherScript(binaryPath: string): boolean;
12
+ export declare function resolveBrowserBinary(binaryPath: string): string;
3
13
  export declare function findBrowserPath(browserType: BrowserType, customBinary?: string): string;
4
14
  /**
5
15
  * Walk the per-platform priority list and return the first browser that's
@@ -42,12 +42,90 @@ const BROWSER_PATHS = {
42
42
  custom: [],
43
43
  },
44
44
  };
45
+ /**
46
+ * On Debian/Ubuntu the canonical launchers under `/usr/bin`
47
+ * (`brave-browser`, `google-chrome`, `chromium`) are not the browser ELF —
48
+ * they're `#!/bin/bash` wrapper scripts (the upstream Chromium wrapper) that,
49
+ * as their final step, run the real binary as a NON-exec child:
50
+ *
51
+ * exec < /dev/null
52
+ * exec > >(exec cat)
53
+ * exec 2> >(exec cat >&2)
54
+ * "$HERE/brave" "$@" || true
55
+ *
56
+ * That breaks `launchBrowser`'s `--remote-debugging-pipe` transport two ways:
57
+ * the std-fd sanitization (and the extra `cat` process-substitution children)
58
+ * disturbs the inherited CDP pipe on fd 3/4, and the pid we record is the
59
+ * wrapper's, not the browser's. The symptom is `read ECONNRESET` /
60
+ * `CDP connection closed` right after spawn (issue #229).
61
+ *
62
+ * Follow the wrapper to the ELF it execs. The wrapper sets
63
+ * `HERE="dirname(readlink -f "$0")"` and invokes `"$HERE/<name>"`, so we
64
+ * resolve the script path, scan for that invocation line, and join the two.
65
+ * Returns the original path untouched when it's already an ELF, when it's not
66
+ * a resolvable wrapper, or on any non-Linux platform.
67
+ */
68
+ function readsAsShebangScript(binaryPath) {
69
+ let fd;
70
+ try {
71
+ fd = fs.openSync(binaryPath, 'r');
72
+ }
73
+ catch {
74
+ return false;
75
+ }
76
+ try {
77
+ const head = Buffer.alloc(2);
78
+ fs.readSync(fd, head, 0, 2, 0);
79
+ // ELF binaries start with 0x7f 'E'; shebang scripts with '#!'.
80
+ return head[0] === 0x23 && head[1] === 0x21;
81
+ }
82
+ finally {
83
+ fs.closeSync(fd);
84
+ }
85
+ }
86
+ /**
87
+ * True when `binaryPath` is a shebang script rather than a native browser
88
+ * executable. The Linux distro launchers (`/usr/bin/brave-browser`, …) are such
89
+ * scripts; `launchBrowser` can't drive one over `--remote-debugging-pipe` (see
90
+ * resolveBrowserBinary). `profiles doctor` uses this to flag a profile whose
91
+ * binary resolves to a wrapper we couldn't unwrap. Shebang scripts are a
92
+ * Linux/Unix concept — returns false on Windows/macOS app bundles.
93
+ */
94
+ export function isLauncherScript(binaryPath) {
95
+ if (os.platform() === 'win32')
96
+ return false;
97
+ return readsAsShebangScript(binaryPath);
98
+ }
99
+ export function resolveBrowserBinary(binaryPath) {
100
+ if (os.platform() !== 'linux')
101
+ return binaryPath;
102
+ // Only shebang scripts need unwrapping; a real ELF passes straight through.
103
+ if (!readsAsShebangScript(binaryPath))
104
+ return binaryPath;
105
+ let script;
106
+ let realScriptPath;
107
+ try {
108
+ realScriptPath = fs.realpathSync(binaryPath);
109
+ script = fs.readFileSync(realScriptPath, 'utf8');
110
+ }
111
+ catch {
112
+ return binaryPath;
113
+ }
114
+ // Match the Chromium wrapper's launch line: `"$HERE/<name>" "$@"`, optionally
115
+ // prefixed with `exec -a "$0"`. The captured name is the real ELF, sitting in
116
+ // the same directory as the resolved wrapper.
117
+ const match = script.match(/"\$HERE\/([A-Za-z0-9._-]+)"\s+"\$@"/);
118
+ if (!match)
119
+ return binaryPath;
120
+ const realBinary = path.join(path.dirname(realScriptPath), match[1]);
121
+ return fs.existsSync(realBinary) ? realBinary : binaryPath;
122
+ }
45
123
  export function findBrowserPath(browserType, customBinary) {
46
124
  if (customBinary) {
47
125
  if (!fs.existsSync(customBinary)) {
48
126
  throw new Error(`Custom binary not found: ${customBinary}`);
49
127
  }
50
- return customBinary;
128
+ return resolveBrowserBinary(customBinary);
51
129
  }
52
130
  if (browserType === 'custom') {
53
131
  throw new Error('browser: custom requires a binary path in the profile');
@@ -60,9 +138,12 @@ export function findBrowserPath(browserType, customBinary) {
60
138
  const candidates = platformPaths[browserType] || [];
61
139
  for (const p of candidates) {
62
140
  if (fs.existsSync(p)) {
63
- return p;
141
+ return resolveBrowserBinary(p);
64
142
  }
65
143
  }
144
+ if (browserType === 'comet' && platform !== 'darwin') {
145
+ throw new Error('Browser "comet" is macOS-only (Comet is a macOS Chromium fork). Use chrome, chromium, brave, or edge on this platform.');
146
+ }
66
147
  throw new Error(`Browser "${browserType}" not found. Install it first.`);
67
148
  }
68
149
  // Per-platform Chromium-family priority list for "no --profile" auto-pick.
@@ -98,7 +179,7 @@ export function findFirstInstalledBrowser(platform = os.platform()) {
98
179
  const candidates = platformPaths[browserType] || [];
99
180
  for (const p of candidates) {
100
181
  if (fs.existsSync(p)) {
101
- return { browserType, binary: p };
182
+ return { browserType, binary: resolveBrowserBinary(p) };
102
183
  }
103
184
  }
104
185
  }
@@ -46,6 +46,8 @@ export function supports(agent, cap, version) {
46
46
  return { ok: false, reason: 'unsupported' };
47
47
  if (c === true)
48
48
  return { ok: true };
49
+ if ('file' in c)
50
+ return { ok: true };
49
51
  if (!version)
50
52
  return { ok: true };
51
53
  if (c.since && compareVersions(version, c.since) < 0) {
@@ -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. */