@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
@@ -78,27 +78,72 @@ export function locateModelSource(agent, version) {
78
78
  return null;
79
79
  }
80
80
  if (agent === 'codex') {
81
+ // Codex's vendored binary has moved across releases:
82
+ // <=0.98: @openai/codex/vendor/<triple>/codex/codex
83
+ // 0.99..0.13: @openai/codex-<plat>-<arch>/vendor/<triple>/codex/codex
84
+ // 0.134+: @openai/codex-<plat>-<arch>/vendor/<triple>/bin/codex
85
+ // We probe all known shapes; first hit wins.
86
+ const triples = ['aarch64-apple-darwin', 'x86_64-apple-darwin', 'x86_64-unknown-linux-musl', 'aarch64-unknown-linux-musl', 'x86_64-pc-windows-msvc', 'aarch64-pc-windows-msvc'];
81
87
  const triple = currentTargetTriple();
82
- if (triple) {
83
- const platformPkg = path.join(versionDir, 'node_modules', '@openai', `codex-${triple.includes('apple') ? 'darwin' : triple.includes('linux') ? 'linux' : 'win32'}-${triple.includes('aarch64') ? 'arm64' : 'x64'}`, 'vendor', triple, 'codex', 'codex');
84
- if (fs.existsSync(platformPkg))
85
- return { path: platformPkg, kind: 'binary' };
86
- }
87
- // 0.98 layout: binary inside @openai/codex itself
88
- const triples = ['aarch64-apple-darwin', 'x86_64-apple-darwin', 'x86_64-unknown-linux-musl', 'aarch64-unknown-linux-musl'];
89
- for (const t of triples) {
90
- const p = path.join(versionDir, 'node_modules', '@openai', 'codex', 'vendor', t, 'codex', 'codex');
91
- if (fs.existsSync(p))
92
- return { path: p, kind: 'binary' };
88
+ const orderedTriples = triple ? [triple, ...triples.filter((t) => t !== triple)] : triples;
89
+ const platformPkgFor = (t) => `codex-${t.includes('apple') ? 'darwin' : t.includes('linux') ? 'linux' : 'win32'}-${t.includes('aarch64') ? 'arm64' : 'x64'}`;
90
+ for (const t of orderedTriples) {
91
+ const candidates = [
92
+ path.join(versionDir, 'node_modules', '@openai', platformPkgFor(t), 'vendor', t, 'bin', 'codex'),
93
+ path.join(versionDir, 'node_modules', '@openai', platformPkgFor(t), 'vendor', t, 'codex', 'codex'),
94
+ path.join(versionDir, 'node_modules', '@openai', 'codex', 'vendor', t, 'bin', 'codex'),
95
+ path.join(versionDir, 'node_modules', '@openai', 'codex', 'vendor', t, 'codex', 'codex'),
96
+ ];
97
+ for (const p of candidates) {
98
+ if (fs.existsSync(p))
99
+ return { path: p, kind: 'binary' };
100
+ }
93
101
  }
94
102
  return null;
95
103
  }
96
104
  if (agent === 'gemini') {
97
- // Gemini ships a clean ES module with all constants and aliases -- no need
98
- // to parse the minified CLI bundle.
105
+ // <=0.41 shipped a clean ES module at @google/gemini-cli-core/dist/src/config/models.js.
106
+ // 0.42+ inlines the same constants into a minified chunk under @google/gemini-cli/bundle/.
99
107
  const modelsJs = path.join(versionDir, 'node_modules', '@google', 'gemini-cli-core', 'dist', 'src', 'config', 'models.js');
100
108
  if (fs.existsSync(modelsJs))
101
109
  return { path: modelsJs, kind: 'js' };
110
+ const bundleDir = path.join(versionDir, 'node_modules', '@google', 'gemini-cli', 'bundle');
111
+ if (fs.existsSync(bundleDir)) {
112
+ try {
113
+ const entries = fs.readdirSync(bundleDir);
114
+ // The model constants live in a single chunk; pick the first .js file
115
+ // whose contents contain VALID_GEMINI_MODELS. Skip subdirectories.
116
+ for (const name of entries) {
117
+ if (!name.endsWith('.js'))
118
+ continue;
119
+ const full = path.join(bundleDir, name);
120
+ let stat;
121
+ try {
122
+ stat = fs.statSync(full);
123
+ }
124
+ catch {
125
+ continue;
126
+ }
127
+ if (!stat.isFile())
128
+ continue;
129
+ // Most chunks just re-export the constants; only the defining chunk
130
+ // actually has `var VALID_GEMINI_MODELS = ... new Set(`. Match that
131
+ // shape so we pick the chunk that the extractor can parse.
132
+ let body;
133
+ try {
134
+ body = fs.readFileSync(full, 'utf-8');
135
+ }
136
+ catch {
137
+ continue;
138
+ }
139
+ if (/var\s+VALID_GEMINI_MODELS\s*=[^\n]*new\s+Set/.test(body) &&
140
+ /var\s+DEFAULT_GEMINI_MODEL\s*=/.test(body)) {
141
+ return { path: full, kind: 'js' };
142
+ }
143
+ }
144
+ }
145
+ catch { /* unreadable bundle dir */ }
146
+ }
102
147
  return null;
103
148
  }
104
149
  if (agent === 'opencode') {
@@ -296,7 +341,9 @@ function extractCodexCatalog(text) {
296
341
  * would pollute the runtime and ES-module interop is awkward from a CJS build).
297
342
  */
298
343
  function extractGeminiCatalog(text) {
299
- const constRe = /export\s+const\s+([A-Z0-9_]+)\s*=\s*'([^']+)'/g;
344
+ // Old (<=0.41) layout used `export const FOO = 'bar';` in models.js.
345
+ // New (0.42+) bundle inlines the same constants as `var FOO = "bar";`.
346
+ const constRe = /(?:export\s+const|var)\s+([A-Z0-9_]+)\s*=\s*['"]([^'"]+)['"]/g;
300
347
  const constants = new Map();
301
348
  let m;
302
349
  while ((m = constRe.exec(text)) !== null) {
@@ -305,7 +352,7 @@ function extractGeminiCatalog(text) {
305
352
  // The set of ids the CLI accepts as "valid model names". Names (not values)
306
353
  // are listed inside `new Set([...])`, so we expand them via the constants map.
307
354
  const validIds = new Set();
308
- const setBlock = text.match(/VALID_GEMINI_MODELS\s*=\s*new\s+Set\(\[([\s\S]*?)\]\)/);
355
+ const setBlock = text.match(/VALID_GEMINI_MODELS\s*=\s*(?:\/\*[^*]*\*\/\s*)?new\s+Set\(\[([\s\S]*?)\]\)/);
309
356
  if (setBlock) {
310
357
  const nameRe = /([A-Z0-9_]+)/g;
311
358
  let nm;
@@ -1,6 +1,4 @@
1
1
  import type { AgentId, PermissionSet, InstalledPermission, ClaudePermissions, OpenCodePermissions, CodexPermissions } from './types.js';
2
- /** Agents that support the permissions subsystem. */
3
- export declare const PERMISSIONS_CAPABLE_AGENTS: AgentId[];
4
2
  /** Filename used for Codex Starlark deny-rules generated from permission groups. */
5
3
  export declare const CODEX_RULES_FILENAME = "agents-deny.rules";
6
4
  export type ParsedRules = PermissionSet;
@@ -110,6 +108,42 @@ export declare function convertToGeminiFormat(set: PermissionSet): {
110
108
  allowed: string[];
111
109
  };
112
110
  };
111
+ /**
112
+ * Convert canonical permission set to Antigravity format.
113
+ * Antigravity reads ~/.gemini/antigravity-cli/settings.json with
114
+ * { permissions: { allow: [...], deny: [...] } }
115
+ * where each entry is action-namespaced: command(...), read_file(...),
116
+ * write_file(...), read_url(...), mcp(...).
117
+ * Bash maps to `command`, Read to `read_file`, Write to `write_file`,
118
+ * WebFetch to `read_url`. Other canonical tools are skipped.
119
+ * Note: Antigravity matches `command(npm install)` as an exact string,
120
+ * not a prefix — `Bash(npm:*)` becomes `command(npm *)` which is a glob
121
+ * but Antigravity upstream has a known exact-match bug for some forms.
122
+ */
123
+ export declare function convertToAntigravityFormat(set: PermissionSet): {
124
+ permissions: {
125
+ allow: string[];
126
+ deny?: string[];
127
+ };
128
+ };
129
+ /**
130
+ * Convert canonical permission set to Grok format.
131
+ * Grok reads ~/.grok/config.toml with
132
+ * [permission]
133
+ * rules = [ { action = "allow", tool = "bash", pattern = "git *" }, ... ]
134
+ * Tool names are lowercase: bash, read, edit, grep, mcptool, webfetch.
135
+ * Canonical Write maps to Grok's `edit` tool.
136
+ */
137
+ export declare function convertToGrokFormat(set: PermissionSet): {
138
+ permission: {
139
+ rules: GrokRule[];
140
+ };
141
+ };
142
+ export type GrokRule = {
143
+ action: 'allow' | 'deny';
144
+ tool: string;
145
+ pattern?: string;
146
+ };
113
147
  /**
114
148
  * Convert canonical permission set to OpenCode format.
115
149
  * OpenCode uses: { permission: { bash: { "git *": "allow", "rm *": "deny" } } }
@@ -14,14 +14,15 @@ import * as yaml from 'yaml';
14
14
  import * as TOML from 'smol-toml';
15
15
  import { getPermissionsDir, getUserPermissionsDir, ensureAgentsDir } from './state.js';
16
16
  import { safeJoin } from './paths.js';
17
+ import { agentConfigDirName } from './agents.js';
17
18
  import { updateGeminiSettings } from './gemini-settings.js';
18
19
  const HOME = os.homedir();
19
- /** Agents that support the permissions subsystem. */
20
- // antigravity: permissions in ~/.gemini/antigravity-cli/settings.json under
21
- // `permissions: { allow: [...], deny: [...] }`. Serializer is a follow-up.
22
- // grok: permissions via --allow/--deny CLI flags or [permission] block in
23
- // ~/.grok/config.toml. Serializer is a follow-up.
24
- export const PERMISSIONS_CAPABLE_AGENTS = ['claude', 'codex', 'opencode', 'antigravity', 'grok', 'gemini'];
20
+ // PERMISSIONS_CAPABLE_AGENTS removed use `capableAgents('allowlist')`
21
+ // from lib/capabilities.ts. The capability matrix on AgentConfig is the
22
+ // single source of truth. (Per-agent native format details:
23
+ // antigravity ~/.gemini/antigravity-cli/settings.json `permissions.{allow,deny}`
24
+ // grok → ~/.grok/config.toml `[permission].rules`
25
+ // the writer in `applyPermissionsToVersion` handles the format dispatch.)
25
26
  /** Filename used for Codex Starlark deny-rules generated from permission groups. */
26
27
  export const CODEX_RULES_FILENAME = 'agents-deny.rules';
27
28
  export function containsBroadGrants(rules) {
@@ -446,6 +447,116 @@ export function convertToGeminiFormat(set) {
446
447
  }
447
448
  return { tools: { allowed: Array.from(allowed) } };
448
449
  }
450
+ /**
451
+ * Strip Claude's `:*` subcommand-wildcard suffix and return a space-glob form.
452
+ * "mq:*" -> "mq *", "git status" -> "git status", "*" -> "*".
453
+ * Used by serializers whose native pattern grammar uses ` *` instead of `:*`.
454
+ */
455
+ function normalizeBashPattern(pattern) {
456
+ if (pattern === '*' || pattern === '**')
457
+ return '*';
458
+ if (pattern.endsWith(':*'))
459
+ return pattern.slice(0, -2) + ' *';
460
+ return pattern;
461
+ }
462
+ /**
463
+ * Convert canonical permission set to Antigravity format.
464
+ * Antigravity reads ~/.gemini/antigravity-cli/settings.json with
465
+ * { permissions: { allow: [...], deny: [...] } }
466
+ * where each entry is action-namespaced: command(...), read_file(...),
467
+ * write_file(...), read_url(...), mcp(...).
468
+ * Bash maps to `command`, Read to `read_file`, Write to `write_file`,
469
+ * WebFetch to `read_url`. Other canonical tools are skipped.
470
+ * Note: Antigravity matches `command(npm install)` as an exact string,
471
+ * not a prefix — `Bash(npm:*)` becomes `command(npm *)` which is a glob
472
+ * but Antigravity upstream has a known exact-match bug for some forms.
473
+ */
474
+ export function convertToAntigravityFormat(set) {
475
+ const allow = serializeAntigravityEntries(set.allow);
476
+ const deny = set.deny ? serializeAntigravityEntries(set.deny) : [];
477
+ return {
478
+ permissions: {
479
+ allow,
480
+ ...(deny.length ? { deny } : {}),
481
+ },
482
+ };
483
+ }
484
+ function serializeAntigravityEntries(perms) {
485
+ const out = new Set();
486
+ for (const perm of perms) {
487
+ if (BLANKET_BASH_FORMS.has(perm)) {
488
+ out.add('command(*)');
489
+ continue;
490
+ }
491
+ const parsed = parseCanonicalPattern(perm);
492
+ if (!parsed)
493
+ continue;
494
+ const action = ANTIGRAVITY_ACTION_BY_TOOL[parsed.tool];
495
+ if (!action)
496
+ continue;
497
+ if (parsed.tool === 'bash') {
498
+ out.add(`${action}(${normalizeBashPattern(parsed.pattern)})`);
499
+ }
500
+ else {
501
+ const p = parsed.pattern === '**' ? '*' : parsed.pattern;
502
+ out.add(`${action}(${p})`);
503
+ }
504
+ }
505
+ return Array.from(out);
506
+ }
507
+ const ANTIGRAVITY_ACTION_BY_TOOL = {
508
+ bash: 'command',
509
+ read: 'read_file',
510
+ write: 'write_file',
511
+ webfetch: 'read_url',
512
+ };
513
+ /**
514
+ * Convert canonical permission set to Grok format.
515
+ * Grok reads ~/.grok/config.toml with
516
+ * [permission]
517
+ * rules = [ { action = "allow", tool = "bash", pattern = "git *" }, ... ]
518
+ * Tool names are lowercase: bash, read, edit, grep, mcptool, webfetch.
519
+ * Canonical Write maps to Grok's `edit` tool.
520
+ */
521
+ export function convertToGrokFormat(set) {
522
+ const rules = [];
523
+ for (const perm of set.allow) {
524
+ const rule = canonicalToGrokRule(perm, 'allow');
525
+ if (rule)
526
+ rules.push(rule);
527
+ }
528
+ if (set.deny) {
529
+ for (const perm of set.deny) {
530
+ const rule = canonicalToGrokRule(perm, 'deny');
531
+ if (rule)
532
+ rules.push(rule);
533
+ }
534
+ }
535
+ return { permission: { rules } };
536
+ }
537
+ const GROK_TOOL_BY_CANONICAL = {
538
+ bash: 'bash',
539
+ read: 'read',
540
+ write: 'edit',
541
+ grep: 'grep',
542
+ webfetch: 'webfetch',
543
+ };
544
+ function canonicalToGrokRule(perm, action) {
545
+ if (BLANKET_BASH_FORMS.has(perm)) {
546
+ return { action, tool: 'bash', pattern: '*' };
547
+ }
548
+ const parsed = parseCanonicalPattern(perm);
549
+ if (!parsed)
550
+ return null;
551
+ const tool = GROK_TOOL_BY_CANONICAL[parsed.tool];
552
+ if (!tool)
553
+ return null;
554
+ const pattern = parsed.tool === 'bash' ? normalizeBashPattern(parsed.pattern) : parsed.pattern;
555
+ if (pattern === '' || pattern === undefined) {
556
+ return { action, tool };
557
+ }
558
+ return { action, tool, pattern };
559
+ }
449
560
  /**
450
561
  * Convert canonical permission set to OpenCode format.
451
562
  * OpenCode uses: { permission: { bash: { "git *": "allow", "rm *": "deny" } } }
@@ -825,7 +936,7 @@ function applyPermissionsToAgent(agentId, set, scope = 'user', cwd, merge = true
825
936
  * This writes to {versionHome}/.{agent}/settings.json (or equivalent).
826
937
  */
827
938
  export function applyPermissionsToVersion(agentId, set, versionHome, merge = true) {
828
- const configDir = path.join(versionHome, `.${agentId}`);
939
+ const configDir = path.join(versionHome, agentConfigDirName(agentId));
829
940
  try {
830
941
  fs.mkdirSync(configDir, { recursive: true });
831
942
  if (agentId === 'claude') {
@@ -931,6 +1042,105 @@ export function applyPermissionsToVersion(agentId, set, versionHome, merge = tru
931
1042
  });
932
1043
  return { success: true };
933
1044
  }
1045
+ if (agentId === 'antigravity') {
1046
+ const antigravityPerms = convertToAntigravityFormat(set);
1047
+ const settingsPath = path.join(versionHome, '.gemini', 'antigravity-cli', 'settings.json');
1048
+ updateGeminiSettings(settingsPath, (settings) => {
1049
+ const perms = (typeof settings.permissions === 'object' && settings.permissions !== null && !Array.isArray(settings.permissions))
1050
+ ? settings.permissions
1051
+ : {};
1052
+ if (merge) {
1053
+ const existingAllow = Array.isArray(perms.allow) ? perms.allow : [];
1054
+ const existingDeny = Array.isArray(perms.deny) ? perms.deny : [];
1055
+ perms.allow = Array.from(new Set([...existingAllow, ...antigravityPerms.permissions.allow]));
1056
+ const mergedDeny = Array.from(new Set([...existingDeny, ...(antigravityPerms.permissions.deny ?? [])]));
1057
+ if (mergedDeny.length)
1058
+ perms.deny = mergedDeny;
1059
+ else
1060
+ delete perms.deny;
1061
+ }
1062
+ else {
1063
+ perms.allow = antigravityPerms.permissions.allow;
1064
+ if (antigravityPerms.permissions.deny?.length)
1065
+ perms.deny = antigravityPerms.permissions.deny;
1066
+ else
1067
+ delete perms.deny;
1068
+ }
1069
+ settings.permissions = perms;
1070
+ });
1071
+ return { success: true };
1072
+ }
1073
+ if (agentId === 'grok') {
1074
+ const grokPerms = convertToGrokFormat(set);
1075
+ const configPath = path.join(versionHome, '.grok', 'config.toml');
1076
+ let config = {};
1077
+ if (fs.existsSync(configPath)) {
1078
+ config = TOML.parse(fs.readFileSync(configPath, 'utf-8'));
1079
+ }
1080
+ const existingPermission = (typeof config.permission === 'object' && config.permission !== null && !Array.isArray(config.permission))
1081
+ ? config.permission
1082
+ : {};
1083
+ if (merge) {
1084
+ const existingRules = Array.isArray(existingPermission.rules) ? existingPermission.rules : [];
1085
+ const seen = new Set();
1086
+ const dedup = [];
1087
+ for (const r of [...existingRules, ...grokPerms.permission.rules]) {
1088
+ const key = `${r.action}|${r.tool}|${r.pattern ?? ''}`;
1089
+ if (seen.has(key))
1090
+ continue;
1091
+ seen.add(key);
1092
+ dedup.push(r);
1093
+ }
1094
+ existingPermission.rules = dedup;
1095
+ }
1096
+ else {
1097
+ existingPermission.rules = grokPerms.permission.rules;
1098
+ }
1099
+ config.permission = existingPermission;
1100
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
1101
+ fs.writeFileSync(configPath, TOML.stringify(config), 'utf-8');
1102
+ return { success: true };
1103
+ }
1104
+ if (agentId === 'kimi') {
1105
+ const configPath = path.join(versionHome, '.kimi-code', 'config.toml');
1106
+ let config = {};
1107
+ if (fs.existsSync(configPath)) {
1108
+ config = TOML.parse(fs.readFileSync(configPath, 'utf-8'));
1109
+ }
1110
+ const newRules = [];
1111
+ for (const allow of set.allow) {
1112
+ newRules.push({ decision: 'allow', pattern: allow });
1113
+ }
1114
+ for (const deny of set.deny || []) {
1115
+ newRules.push({ decision: 'deny', pattern: deny });
1116
+ }
1117
+ if (merge) {
1118
+ const existingPermission = (typeof config.permission === 'object' && config.permission !== null && !Array.isArray(config.permission))
1119
+ ? config.permission
1120
+ : {};
1121
+ const existingRules = Array.isArray(existingPermission.rules)
1122
+ ? existingPermission.rules
1123
+ : [];
1124
+ const seen = new Set();
1125
+ const dedup = [];
1126
+ for (const r of [...existingRules, ...newRules]) {
1127
+ if (!r.decision || !r.pattern)
1128
+ continue;
1129
+ const key = `${r.decision}|${r.pattern}`;
1130
+ if (seen.has(key))
1131
+ continue;
1132
+ seen.add(key);
1133
+ dedup.push({ decision: r.decision, pattern: r.pattern });
1134
+ }
1135
+ config.permission = { rules: dedup };
1136
+ }
1137
+ else {
1138
+ config.permission = { rules: newRules };
1139
+ }
1140
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
1141
+ fs.writeFileSync(configPath, TOML.stringify(config), 'utf-8');
1142
+ return { success: true };
1143
+ }
934
1144
  return { success: false, error: `Agent '${agentId}' does not support permissions` };
935
1145
  }
936
1146
  catch (err) {
@@ -1,23 +1,40 @@
1
1
  /**
2
- * Native plugin marketplace install path for Claude / OpenClaw.
2
+ * Native plugin marketplaces for Claude / OpenClaw — one per DotAgents repo.
3
3
  *
4
- * Plugins managed by agents-cli are exposed as a synthetic local marketplace
5
- * named "agents-cli" under each version's plugin directory:
4
+ * Every DotAgents repo that holds plugins synthesizes its OWN synthetic local
5
+ * marketplace under each version's plugin directory, named after the repo:
6
6
  *
7
7
  * <versionHome>/.{claude,openclaw}/plugins/
8
- * known_marketplaces.json # registers the "agents-cli" marketplace
9
- * marketplaces/agents-cli/
10
- * .claude-plugin/marketplace.json # synthesized catalog
11
- * plugins/<plugin>/ # copied plugin source
8
+ * known_marketplaces.json # registers each repo's marketplace
9
+ * marketplaces/agents-cli/ # ~/.agents/plugins/* (user repo)
10
+ * marketplaces/agents-<alias>/ # ~/.agents-<alias>/plugins/* (extra repo)
11
+ * marketplaces/agents-project/ # <cwd>/.agents/plugins/* (project repo)
12
+ * .claude-plugin/marketplace.json # synthesized catalog
13
+ * plugins/<plugin>/ # copied plugin source
12
14
  *
13
- * Plus the version's settings.json gets `enabledPlugins["<plugin>@agents-cli"] = true`.
15
+ * Plus the version's settings.json gets
16
+ * `enabledPlugins["<plugin>@<marketplace>"] = true`.
14
17
  *
15
- * This produces native `/plugin:skill` slash namespacing, visibility in `/plugins`,
16
- * and `/plugin enable|disable` support matching the Claude Code spec at
17
- * https://code.claude.com/docs/en/plugins and /plugin-marketplaces.
18
+ * This produces native `/plugin:skill` slash namespacing, visibility in
19
+ * `/plugins`, `/plugin enable|disable` support, AND honest attribution (the
20
+ * user can see which repo each plugin came from) — matching the Claude Code
21
+ * spec at https://code.claude.com/docs/en/plugins and /plugin-marketplaces.
22
+ *
23
+ * The naming policy lives in one place — marketplaceNameFor(). Source-side
24
+ * discovery (discoverMarketplaces) and per-version synthesis (syncMarketplaceManifest
25
+ * / registerMarketplace / syncAllMarketplaces) all key off a MarketplaceSpec so
26
+ * the catalog name and on-disk layout are derived, never hard-coded per call.
27
+ */
28
+ import type { AgentId, DiscoveredPlugin, MarketplaceSpec, DiscoveredMarketplace } from './types.js';
29
+ /**
30
+ * Canonical name for the user-repo marketplace (~/.agents/plugins/). Kept as an
31
+ * exported constant for callers that operate on the user repo directly and for
32
+ * the `marketplaces/agents-cli/` on-disk path that existing installs already have.
18
33
  */
19
- import type { AgentId, DiscoveredPlugin } from './types.js';
20
34
  export declare const MARKETPLACE_NAME = "agents-cli";
35
+ export declare const SYSTEM_MARKETPLACE_NAME = "agents-system";
36
+ /** Marketplace name for <cwd>/.agents/plugins/*. */
37
+ export declare const PROJECT_MARKETPLACE_NAME = "agents-project";
21
38
  interface MarketplacePluginEntry {
22
39
  name: string;
23
40
  source: string;
@@ -28,7 +45,7 @@ interface MarketplacePluginEntry {
28
45
  email?: string;
29
46
  };
30
47
  }
31
- interface MarketplaceManifest {
48
+ export interface MarketplaceManifest {
32
49
  $schema?: string;
33
50
  name: string;
34
51
  description?: string;
@@ -38,58 +55,109 @@ interface MarketplaceManifest {
38
55
  };
39
56
  plugins: MarketplacePluginEntry[];
40
57
  }
41
- export declare function marketplaceRoot(agent: AgentId, versionHome: string): string;
42
- export declare function marketplaceManifestPath(agent: AgentId, versionHome: string): string;
43
- export declare function pluginInstallDir(plugin: DiscoveredPlugin, agent: AgentId, versionHome: string): string;
58
+ /** Result of synthesizing + registering one marketplace via syncAllMarketplaces. */
59
+ export interface SyncAllResult {
60
+ spec: MarketplaceSpec;
61
+ name: string;
62
+ plugins: number;
63
+ }
64
+ /**
65
+ * Map a MarketplaceSpec to its catalog name. This is the ONLY place that
66
+ * encodes the repo → name policy; every other function derives the name here.
67
+ */
68
+ export declare function marketplaceNameFor(spec: MarketplaceSpec): string;
69
+ /**
70
+ * Discover every DotAgents repo that contributes plugins, in precedence order
71
+ * (user, then each enabled extra repo, then the project repo when cwd has one).
72
+ * No agent / version is involved — this walks source-side plugin roots only.
73
+ *
74
+ * A repo is included when its plugins/ directory exists on disk. The user repo
75
+ * is always probed; extras come from getEnabledExtraRepos() (already filtered to
76
+ * enabled + on-disk repos); the project repo is included only when
77
+ * <cwd>/.agents/plugins/ exists.
78
+ */
79
+ export declare function discoverMarketplaces(opts?: {
80
+ cwd?: string;
81
+ }): DiscoveredMarketplace[];
82
+ export declare function marketplaceRoot(specOrName: MarketplaceSpec | string, agent: AgentId, versionHome: string): string;
83
+ export declare function marketplaceManifestPath(specOrName: MarketplaceSpec | string, agent: AgentId, versionHome: string): string;
84
+ export declare function pluginInstallDir(plugin: DiscoveredPlugin, specOrName: MarketplaceSpec | string, agent: AgentId, versionHome: string): string;
44
85
  export declare function knownMarketplacesPath(agent: AgentId, versionHome: string): string;
45
86
  /**
46
- * Copy plugin source into marketplace install dir.
47
- * Source of truth remains ~/.agents/plugins/<name>/ — this is a per-version snapshot.
87
+ * Copy plugin source into the marketplace install dir for the given spec.
88
+ * Source of truth remains the plugin's source dir — this is a per-version snapshot.
89
+ *
90
+ * Symlinks pointing OUTSIDE the plugin source root are dropped. They show up
91
+ * when plugin authors (legitimately) link prompt-side references to sibling
92
+ * codebases — e.g. the rush plugin's `app -> ../../../rush/app` for @app/...
93
+ * autocomplete in user prompts. Faithfully copying those symlinks pollutes
94
+ * the marketplace with gigabytes of node_modules / .next / brand-asset video
95
+ * that the consumer (Claude Code, OpenClaw) then walks during plugin
96
+ * discovery — which is the documented cause of multi-minute startup hangs.
97
+ *
98
+ * Internal symlinks (target stays inside the plugin root) are preserved.
48
99
  */
49
- export declare function copyPluginToMarketplace(plugin: DiscoveredPlugin, agent: AgentId, versionHome: string): string;
100
+ export declare function copyPluginToMarketplace(plugin: DiscoveredPlugin, spec: MarketplaceSpec | string, agent: AgentId, versionHome: string): string;
50
101
  /**
51
- * Re-synthesize <marketplace>/.claude-plugin/marketplace.json from the list of
52
- * plugins installed under <marketplace>/plugins/. Always run after add or remove
53
- * so the manifest stays in lockstep with on-disk contents.
102
+ * Re-synthesize <marketplace>/.claude-plugin/marketplace.json from the plugins
103
+ * already installed under <marketplace>/plugins/. Always run after add or remove
104
+ * so the manifest stays in lockstep with on-disk contents. Returns the manifest
105
+ * it wrote, or null when the marketplace has no plugins dir yet.
54
106
  */
55
- export declare function syncMarketplaceManifest(agent: AgentId, versionHome: string): MarketplaceManifest | null;
107
+ export declare function syncMarketplaceManifest(spec: MarketplaceSpec, agent: AgentId, versionHome: string): MarketplaceManifest | null;
56
108
  /**
57
- * Register the agents-cli marketplace in known_marketplaces.json so Claude Code
58
- * discovers it on startup. Idempotent: re-running just refreshes lastUpdated.
109
+ * Register a marketplace in known_marketplaces.json so Claude Code discovers
110
+ * it on startup. Idempotent: re-running just refreshes lastUpdated. Other
111
+ * marketplaces' entries are preserved untouched.
59
112
  */
60
- export declare function registerMarketplace(agent: AgentId, versionHome: string): void;
113
+ export declare function registerMarketplace(spec: MarketplaceSpec, agent: AgentId, versionHome: string): void;
61
114
  /**
62
- * Drop the agents-cli marketplace entry from known_marketplaces.json.
63
- * Called when the last plugin under it is removed.
115
+ * Drop a marketplace entry from known_marketplaces.json. Called when the last
116
+ * plugin under it is removed. Removes only its own entry; deletes the file only
117
+ * when no entries remain.
118
+ */
119
+ export declare function unregisterMarketplace(specOrName: MarketplaceSpec | string, agent: AgentId, versionHome: string): void;
120
+ /**
121
+ * Discover every source-side marketplace, then for each one re-synthesize its
122
+ * catalog from the plugins already copied under the version home and register
123
+ * it in known_marketplaces.json. Returns one result per marketplace that has at
124
+ * least one plugin installed.
125
+ *
126
+ * Copying plugin source into a marketplace is the caller's responsibility
127
+ * (copyPluginToMarketplace / syncPluginToVersion) — this reconciles catalogs +
128
+ * registrations across all repos once the copies are in place. Marketplaces
129
+ * whose version-home plugins dir is empty or absent are skipped, so we never
130
+ * register a known_marketplace pointing at a directory with no catalog.
64
131
  */
65
- export declare function unregisterMarketplace(agent: AgentId, versionHome: string): void;
132
+ export declare function syncAllMarketplaces(agent: AgentId, versionHome: string, opts?: {
133
+ cwd?: string;
134
+ }): SyncAllResult[];
66
135
  /**
67
136
  * Mark a plugin as enabled in <versionHome>/.{agent}/settings.json under
68
- * enabledPlugins["<plugin>@agents-cli"]: true. Reads, mutates, writes —
69
- * preserving every other key.
137
+ * enabledPlugins["<plugin>@<marketplace>"]: true. Reads, mutates, writes —
138
+ * preserving every other key. Trust/exec-surface gating is the caller's
139
+ * responsibility (plugins.ts owns plugin capability inspection).
70
140
  */
71
- export declare function enablePluginInSettings(pluginName: string, agent: AgentId, versionHome: string, options?: {
72
- allowExecSurfaces?: boolean;
73
- }): void;
141
+ export declare function addPluginToSettings(pluginName: string, marketplaceName: string, agent: AgentId, versionHome: string): void;
74
142
  /**
75
- * Remove the enabledPlugins key for this plugin. Inverse of enablePluginInSettings.
143
+ * Remove the enabledPlugins key for this plugin. Inverse of addPluginToSettings.
76
144
  */
77
- export declare function disablePluginInSettings(pluginName: string, agent: AgentId, versionHome: string): void;
145
+ export declare function removePluginFromSettings(pluginName: string, marketplaceName: string, agent: AgentId, versionHome: string): void;
78
146
  /**
79
147
  * Remove a plugin's installed marketplace directory. Returns true if the dir
80
148
  * existed and was removed.
81
149
  */
82
- export declare function removePluginFromMarketplace(pluginName: string, agent: AgentId, versionHome: string): boolean;
150
+ export declare function removePluginFromMarketplace(pluginName: string, specOrName: MarketplaceSpec | string, agent: AgentId, versionHome: string): boolean;
83
151
  /**
84
152
  * Return true if the marketplace has no plugins left under it.
85
153
  */
86
- export declare function marketplaceIsEmpty(agent: AgentId, versionHome: string): boolean;
154
+ export declare function marketplaceIsEmpty(specOrName: MarketplaceSpec | string, agent: AgentId, versionHome: string): boolean;
87
155
  /**
88
156
  * Drop the entire marketplace directory. Called after the last plugin removal.
89
157
  */
90
- export declare function removeEmptyMarketplaceDir(agent: AgentId, versionHome: string): void;
158
+ export declare function removeEmptyMarketplaceDir(specOrName: MarketplaceSpec | string, agent: AgentId, versionHome: string): void;
91
159
  /**
92
160
  * Detect whether a plugin is installed via the native marketplace path.
93
161
  */
94
- export declare function isInstalledInMarketplace(pluginName: string, agent: AgentId, versionHome: string): boolean;
162
+ export declare function isInstalledInMarketplace(pluginName: string, specOrName: MarketplaceSpec | string, agent: AgentId, versionHome: string): boolean;
95
163
  export {};