@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
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Writer + detector registry for resource sync.
3
+ *
4
+ * Two parallel maps keyed by (ResourceKind, AgentId). At module import time
5
+ * (which fires once when the CLI boots) we verify that EVERY supported
6
+ * (agent, kind) pair has both a writer and a detector. Missing entries
7
+ * throw immediately — silent-skip bugs become startup errors.
8
+ *
9
+ * - Adding a new agent? Either declare every supported kind with a writer
10
+ * here OR mark the kind `false` in `AgentConfig.capabilities`.
11
+ * - Adding a new kind? Declare it on every agent's capabilities and add a
12
+ * writer module.
13
+ *
14
+ * The capability matrix in `lib/agents.ts:AGENTS` is the single source of
15
+ * truth for "is this (agent, kind) supported?". The assertion below maps the
16
+ * matrix to required registry entries.
17
+ */
18
+ import { AGENTS } from '../agents.js';
19
+ import { supports } from '../capabilities.js';
20
+ import { ALL_RESOURCE_KINDS, kindToCapability, } from './writers/kinds.js';
21
+ import { commandsWriters } from './writers/commands.js';
22
+ import { skillsWriters } from './writers/skills.js';
23
+ import { hooksWriters } from './writers/hooks.js';
24
+ import { rulesWriters } from './writers/rules.js';
25
+ import { mcpWriters } from './writers/mcp.js';
26
+ import { permissionsWriters } from './writers/permissions.js';
27
+ import { subagentsWriters } from './writers/subagents.js';
28
+ import { pluginsWriters } from './writers/plugins.js';
29
+ import { workflowsWriters } from './writers/workflows.js';
30
+ import { commandsDetectors } from './detectors/commands.js';
31
+ import { skillsDetectors } from './detectors/skills.js';
32
+ import { hooksDetectors } from './detectors/hooks.js';
33
+ import { rulesDetectors } from './detectors/rules.js';
34
+ import { mcpDetectors } from './detectors/mcp.js';
35
+ import { permissionsDetectors } from './detectors/permissions.js';
36
+ import { subagentsDetectors } from './detectors/subagents.js';
37
+ import { pluginsDetectors } from './detectors/plugins.js';
38
+ import { workflowsDetectors } from './detectors/workflows.js';
39
+ export { kindToCapability, ALL_RESOURCE_KINDS } from './writers/kinds.js';
40
+ /* eslint-disable @typescript-eslint/no-explicit-any */
41
+ export const WRITERS = {
42
+ commands: commandsWriters,
43
+ skills: skillsWriters,
44
+ hooks: hooksWriters,
45
+ rules: rulesWriters,
46
+ mcp: mcpWriters,
47
+ permissions: permissionsWriters,
48
+ subagents: subagentsWriters,
49
+ plugins: pluginsWriters,
50
+ workflows: workflowsWriters,
51
+ };
52
+ /* eslint-enable @typescript-eslint/no-explicit-any */
53
+ export const DETECTORS = {
54
+ commands: commandsDetectors,
55
+ skills: skillsDetectors,
56
+ hooks: hooksDetectors,
57
+ rules: rulesDetectors,
58
+ mcp: mcpDetectors,
59
+ permissions: permissionsDetectors,
60
+ subagents: subagentsDetectors,
61
+ plugins: pluginsDetectors,
62
+ workflows: workflowsDetectors,
63
+ };
64
+ /**
65
+ * Kinds excluded from the assertion. Skills + native-skills-dir agents are
66
+ * the only legitimate gap — Gemini reads `~/.agents/skills/` natively, so
67
+ * it deliberately has no per-version writer/detector. The orchestrator
68
+ * handles that case by clearing the version-home skills dir before launch.
69
+ * Anywhere else, a missing entry is a real bug.
70
+ */
71
+ function isExempt(agent, kind) {
72
+ if (kind === 'skills' && AGENTS[agent].nativeAgentsSkillsDir)
73
+ return true;
74
+ return false;
75
+ }
76
+ let assertionFired = false;
77
+ /**
78
+ * Verify every supported (agent, kind) pair has both a writer and a
79
+ * detector. Deferred from module-import time to the first `getWriter` /
80
+ * `getDetector` call to dodge the agents.ts ↔ versions.ts ↔ registry.ts
81
+ * import cycle (the same one the lazy writer/detector maps work around).
82
+ * Idempotent — runs at most once per process.
83
+ */
84
+ export function assertRegistryComplete() {
85
+ if (assertionFired)
86
+ return;
87
+ assertionFired = true;
88
+ const missing = [];
89
+ for (const kind of ALL_RESOURCE_KINDS) {
90
+ const cap = kindToCapability(kind);
91
+ for (const agent of Object.keys(AGENTS)) {
92
+ if (!supports(agent, cap).ok)
93
+ continue;
94
+ if (isExempt(agent, kind))
95
+ continue;
96
+ const lacks = [];
97
+ if (!WRITERS[kind][agent])
98
+ lacks.push('writer');
99
+ if (!DETECTORS[kind][agent])
100
+ lacks.push('detector');
101
+ if (lacks.length > 0)
102
+ missing.push({ kind, agent, missing: lacks });
103
+ }
104
+ }
105
+ if (missing.length > 0) {
106
+ const detail = missing
107
+ .map((m) => ` - ${m.kind}/${m.agent}: missing ${m.missing.join(' and ')}`)
108
+ .join('\n');
109
+ throw new Error(`staleness/registry: missing required (kind, agent) entries:\n${detail}\n` +
110
+ `Fix: register the entry in src/lib/staleness/{writers,detectors}/<kind>.ts, OR ` +
111
+ `mark the capability false in AGENTS.${missing[0].agent}.capabilities.`);
112
+ }
113
+ }
114
+ /** Return the writer for (kind, agent), or undefined if unsupported. */
115
+ export function getWriter(kind, agent) {
116
+ assertRegistryComplete();
117
+ return WRITERS[kind][agent];
118
+ }
119
+ /** Return the detector for (kind, agent), or undefined if unsupported. */
120
+ export function getDetector(kind, agent) {
121
+ assertRegistryComplete();
122
+ return DETECTORS[kind][agent];
123
+ }
@@ -0,0 +1,3 @@
1
+ import type { AgentId } from '../../types.js';
2
+ import type { ResourceWriter } from './types.js';
3
+ export declare const commandsWriters: Partial<Record<AgentId, ResourceWriter<string[]>>>;
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Commands writer.
3
+ *
4
+ * Two physical formats, picked per-(agent, version) at write time:
5
+ *
6
+ * - command-as-skill — fires when `shouldInstallCommandAsSkill(agent, version)`
7
+ * is true. Used for grok (no native commands, but skills/) and Codex
8
+ * >= 0.117.0 (commands capability ends, skills capability remains).
9
+ * Writes `{agentDir}/skills/<name>/SKILL.md` with the `agents_command`
10
+ * marker; the agent picks it up as a slash-command equivalent.
11
+ *
12
+ * - native command file — `{agentDir}/<commandsSubdir>/<name>.md` (or .toml
13
+ * when the agent's format is toml). Standard path for Claude, Codex
14
+ * < 0.117.0, Gemini, Cursor, OpenCode, Copilot, Amp, Kiro, Roo,
15
+ * Antigravity.
16
+ *
17
+ * Source resolution is `resolveCommandSource` (user → system → extras —
18
+ * project layer intentionally excluded).
19
+ */
20
+ import * as fs from 'fs';
21
+ import * as path from 'path';
22
+ import { AGENTS, agentConfigDirName } from '../../agents.js';
23
+ import { supports } from '../../capabilities.js';
24
+ import { safeJoin } from '../../paths.js';
25
+ import { markdownToToml } from '../../convert.js';
26
+ import { commandAppliesTo, parseCommandMetadata } from '../../commands.js';
27
+ import { installCommandSkillToVersion, shouldInstallCommandAsSkill } from '../../command-skills.js';
28
+ import { resolveCommandSource, trustedSkillRoots } from './sources.js';
29
+ import { lazyAgentMap } from './lazy-map.js';
30
+ function buildCommandsWriter(agent) {
31
+ return {
32
+ kind: 'commands',
33
+ agent,
34
+ write({ version, versionHome, selection }) {
35
+ const agentConfig = AGENTS[agent];
36
+ const agentDir = path.join(versionHome, agentConfigDirName(agent));
37
+ const commandsAsSkills = shouldInstallCommandAsSkill(agent, version);
38
+ const supportsCommands = supports(agent, 'commands', version).ok;
39
+ // Writers fire only after supports() OR commands-as-skills says yes —
40
+ // both paths produce a usable result here.
41
+ if (!commandsAsSkills && !supportsCommands) {
42
+ throw new Error(`commands writer reached for ${agent}@${version} with no path (cmd=false, asSkill=false)`);
43
+ }
44
+ const skillRoots = trustedSkillRoots();
45
+ const commandsTarget = path.join(agentDir, agentConfig.commandsSubdir);
46
+ if (!commandsAsSkills) {
47
+ fs.mkdirSync(commandsTarget, { recursive: true });
48
+ }
49
+ const synced = [];
50
+ for (const cmd of selection) {
51
+ const srcFile = resolveCommandSource(cmd);
52
+ if (!srcFile)
53
+ continue;
54
+ const metadata = parseCommandMetadata(srcFile);
55
+ if (!commandAppliesTo(agent, version, metadata).ok)
56
+ continue;
57
+ if (commandsAsSkills) {
58
+ const installed = installCommandSkillToVersion(agentDir, cmd, srcFile, skillRoots);
59
+ if (!installed.success)
60
+ continue;
61
+ }
62
+ else if (agentConfig.format === 'toml') {
63
+ const content = fs.readFileSync(srcFile, 'utf-8');
64
+ const tomlContent = markdownToToml(cmd, content);
65
+ fs.writeFileSync(safeJoin(commandsTarget, `${cmd}.toml`), tomlContent);
66
+ }
67
+ else {
68
+ fs.copyFileSync(srcFile, safeJoin(commandsTarget, `${cmd}.md`));
69
+ }
70
+ synced.push(cmd);
71
+ }
72
+ return { synced };
73
+ },
74
+ };
75
+ }
76
+ // Built lazily on first access — see lazy-map.ts for the cycle rationale.
77
+ //
78
+ // Registration covers two cases:
79
+ // - native commands (claude, codex < 0.117.0, gemini, etc.) — `commands` cap
80
+ // - commands-as-skills (grok, codex >= 0.117.0)
81
+ //
82
+ // Agents that have skills but use a NATIVE non-file slash-command system
83
+ // (openclaw → Gateway-based commands) are NOT registered. The signal is an
84
+ // empty `commandsSubdir`: there's no directory to write to AND the agent
85
+ // doesn't want commands-as-skills either (it has its own runtime command
86
+ // resolver).
87
+ export const commandsWriters = lazyAgentMap(() => {
88
+ const m = {};
89
+ for (const id of Object.keys(AGENTS)) {
90
+ const cfg = AGENTS[id];
91
+ if (cfg.capabilities.commands === false && (!cfg.skillsDir || cfg.skillsDir === ''))
92
+ continue;
93
+ // Native non-file slash-command runtime — no version-home write.
94
+ if (cfg.capabilities.commands === false && (!cfg.commandsSubdir || cfg.commandsSubdir === '')) {
95
+ // Grok has empty commandsSubdir AND wants commands-as-skills.
96
+ // Distinguish: grok has skillsDir set; openclaw also has skillsDir, so we
97
+ // can't use that. The cleanest signal is the agent's `cliCommand` set —
98
+ // openclaw flags `commands: false` AND has its Gateway runtime, while
99
+ // grok flags `commands: false` because grok's slash commands are skills.
100
+ // We opt in explicitly: only grok takes commands-as-skills today.
101
+ if (id !== 'grok')
102
+ continue;
103
+ }
104
+ const hasCommands = cfg.capabilities.commands !== false;
105
+ const hasSkills = cfg.capabilities.skills !== false;
106
+ if (hasCommands || hasSkills) {
107
+ m[id] = buildCommandsWriter(id);
108
+ }
109
+ }
110
+ return m;
111
+ });
@@ -0,0 +1,3 @@
1
+ import type { AgentId } from '../../types.js';
2
+ import type { ResourceWriter } from './types.js';
3
+ export declare const hooksWriters: Partial<Record<AgentId, ResourceWriter<string[]>>>;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Hooks writer — copies trusted hook script files into `{agentDir}/hooks/`.
3
+ * Caller filters by `supports(agent, 'hooks', version)` before invoking.
4
+ * Orphan sweep (delete hooks in version-home that aren't in `availableNames`)
5
+ * stays in the orchestrator since it depends on the broader available set.
6
+ */
7
+ import * as fs from 'fs';
8
+ import { agentConfigDirName } from '../../agents.js';
9
+ import * as path from 'path';
10
+ import { capableAgents } from '../../capabilities.js';
11
+ import { safeJoin } from '../../paths.js';
12
+ import { registerHooksToSettings } from '../../hooks.js';
13
+ import { resolveHookSource } from './sources.js';
14
+ import { lazyAgentMap } from './lazy-map.js';
15
+ function buildHooksWriter(agent) {
16
+ return {
17
+ kind: 'hooks',
18
+ agent,
19
+ write({ versionHome, selection }) {
20
+ const agentDir = path.join(versionHome, agentConfigDirName(agent));
21
+ const hooksTarget = path.join(agentDir, 'hooks');
22
+ fs.mkdirSync(hooksTarget, { recursive: true });
23
+ const synced = [];
24
+ for (const hook of selection) {
25
+ const srcFile = resolveHookSource(hook);
26
+ if (!srcFile)
27
+ continue;
28
+ const destFile = safeJoin(hooksTarget, hook);
29
+ fs.copyFileSync(srcFile, destFile);
30
+ fs.chmodSync(destFile, 0o755);
31
+ synced.push(hook);
32
+ }
33
+ // Native hook registration in settings.json/hooks.json. Grok auto-
34
+ // discovers from ~/.grok/hooks/ so the file copy is sufficient.
35
+ if (agent === 'claude' || agent === 'codex' || agent === 'gemini' || agent === 'antigravity' || agent === 'kimi') {
36
+ registerHooksToSettings(agent, versionHome);
37
+ }
38
+ return { synced };
39
+ },
40
+ };
41
+ }
42
+ export const hooksWriters = lazyAgentMap(() => {
43
+ const m = {};
44
+ for (const agent of capableAgents('hooks'))
45
+ m[agent] = buildHooksWriter(agent);
46
+ return m;
47
+ });
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Canonical list of resource kinds dispatched through the writer/detector
3
+ * registry. Each name is the capability name on AgentConfig — except
4
+ * "permissions", which maps to the legacy capability name "allowlist".
5
+ */
6
+ import type { CapabilityName } from '../../types.js';
7
+ export type ResourceKind = 'commands' | 'skills' | 'hooks' | 'rules' | 'mcp' | 'permissions' | 'subagents' | 'plugins' | 'workflows';
8
+ export declare const ALL_RESOURCE_KINDS: readonly ResourceKind[];
9
+ /** Map kind -> capability name on AgentConfig.capabilities. */
10
+ export declare function kindToCapability(kind: ResourceKind): CapabilityName;
@@ -0,0 +1,15 @@
1
+ export const ALL_RESOURCE_KINDS = [
2
+ 'commands',
3
+ 'skills',
4
+ 'hooks',
5
+ 'rules',
6
+ 'mcp',
7
+ 'permissions',
8
+ 'subagents',
9
+ 'plugins',
10
+ 'workflows',
11
+ ];
12
+ /** Map kind -> capability name on AgentConfig.capabilities. */
13
+ export function kindToCapability(kind) {
14
+ return kind === 'permissions' ? 'allowlist' : kind;
15
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Lazy-built per-agent map.
3
+ *
4
+ * The writer/detector modules form a circular dependency with agents.ts:
5
+ * agents.ts → versions.ts → staleness/registry.ts → writers/<kind>.ts →
6
+ * (capableAgents/AGENTS via capabilities or agents directly). If a writer
7
+ * module iterates AGENTS at module top-level it fires before the AGENTS
8
+ * const is initialized through the cycle. Wrapping the per-agent map in a
9
+ * lazily-evaluated Proxy defers every read until call time, by which point
10
+ * the cycle has resolved.
11
+ */
12
+ import type { AgentId } from '../../types.js';
13
+ export declare function lazyAgentMap<T>(build: () => Partial<Record<AgentId, T>>): Partial<Record<AgentId, T>>;
@@ -0,0 +1,19 @@
1
+ export function lazyAgentMap(build) {
2
+ let cache = null;
3
+ const ensure = () => {
4
+ if (!cache)
5
+ cache = build();
6
+ return cache;
7
+ };
8
+ return new Proxy({}, {
9
+ get(_t, prop) { return ensure()[prop]; },
10
+ has(_t, prop) { return prop in ensure(); },
11
+ ownKeys() { return Reflect.ownKeys(ensure()); },
12
+ getOwnPropertyDescriptor(_t, prop) {
13
+ const m = ensure();
14
+ if (prop in m)
15
+ return { configurable: true, enumerable: true, value: m[prop] };
16
+ return undefined;
17
+ },
18
+ });
19
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * MCP writer — thin dispatcher into `installMcpServers` from `lib/mcp.ts`.
3
+ *
4
+ * The per-agent format handling (Claude CLI, Codex TOML, Cursor JSON, etc.)
5
+ * lives in lib/mcp.ts; we keep it there to avoid the import cycle with
6
+ * `versions.ts`. The writer simply hands the selection to that function.
7
+ */
8
+ import type { AgentId } from '../../types.js';
9
+ import type { ResourceWriter } from './types.js';
10
+ export declare const mcpWriters: Partial<Record<AgentId, ResourceWriter<string[]>>>;
@@ -0,0 +1,19 @@
1
+ import { capableAgents } from '../../capabilities.js';
2
+ import { installMcpServers } from '../../mcp.js';
3
+ import { lazyAgentMap } from './lazy-map.js';
4
+ function buildMcpWriter(agent) {
5
+ return {
6
+ kind: 'mcp',
7
+ agent,
8
+ write({ version, versionHome, selection, cwd }) {
9
+ const r = installMcpServers(agent, version, versionHome, selection, { cwd });
10
+ return { synced: r.applied };
11
+ },
12
+ };
13
+ }
14
+ export const mcpWriters = lazyAgentMap(() => {
15
+ const m = {};
16
+ for (const agent of capableAgents('mcp'))
17
+ m[agent] = buildMcpWriter(agent);
18
+ return m;
19
+ });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Permissions writer — selection is a list of permission GROUP names. We
3
+ * build the PermissionSet from the discovered groups, then dispatch into the
4
+ * per-agent format writer in `lib/permissions.ts:applyPermissionsToVersion`.
5
+ *
6
+ * The per-agent format work lives in lib/permissions.ts because the format
7
+ * conversions (Claude settings.json vs Codex TOML+rules vs Gemini tools vs
8
+ * Antigravity permissions{} vs Grok [permission].rules) are tightly coupled
9
+ * to the converters defined alongside them.
10
+ */
11
+ import type { AgentId } from '../../types.js';
12
+ import type { ResourceWriter } from './types.js';
13
+ export declare const permissionsWriters: Partial<Record<AgentId, ResourceWriter<string[]>>>;
@@ -0,0 +1,26 @@
1
+ import { capableAgents } from '../../capabilities.js';
2
+ import { applyPermissionsToVersion as applyPermsToVersion, buildPermissionsFromGroups, } from '../../permissions.js';
3
+ import { lazyAgentMap } from './lazy-map.js';
4
+ function buildPermissionsWriter(agent) {
5
+ return {
6
+ kind: 'permissions',
7
+ agent,
8
+ write({ versionHome, selection }) {
9
+ if (selection.length === 0)
10
+ return { synced: [] };
11
+ const built = buildPermissionsFromGroups(selection);
12
+ const hasAllow = built.allow.length > 0;
13
+ const hasDeny = (built.deny?.length ?? 0) > 0;
14
+ if (!hasAllow && !hasDeny)
15
+ return { synced: [] };
16
+ const r = applyPermsToVersion(agent, built, versionHome, true);
17
+ return { synced: r.success ? selection : [] };
18
+ },
19
+ };
20
+ }
21
+ export const permissionsWriters = lazyAgentMap(() => {
22
+ const m = {};
23
+ for (const agent of capableAgents('allowlist'))
24
+ m[agent] = buildPermissionsWriter(agent);
25
+ return m;
26
+ });
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Plugins writer — thin wrapper around `syncPluginToVersion`. Discovery and
3
+ * per-agent format work lives in lib/plugins.ts.
4
+ */
5
+ import type { AgentId } from '../../types.js';
6
+ import type { ResourceWriter } from './types.js';
7
+ export declare const pluginsWriters: Partial<Record<AgentId, ResourceWriter<string[]>>>;
@@ -0,0 +1,31 @@
1
+ import { capableAgents } from '../../capabilities.js';
2
+ import { discoverPlugins, syncPluginToVersion, pluginSupportsAgent, cleanOrphanedPluginSkills } from '../../plugins.js';
3
+ import { lazyAgentMap } from './lazy-map.js';
4
+ function buildPluginsWriter(agent) {
5
+ return {
6
+ kind: 'plugins',
7
+ agent,
8
+ write({ version, versionHome, selection }) {
9
+ const all = discoverPlugins();
10
+ const map = new Map(all.map(p => [p.name, p]));
11
+ // Clean orphan plugin-skills from plugins that no longer exist.
12
+ cleanOrphanedPluginSkills(agent, versionHome, new Set(all.map(p => p.name)));
13
+ const synced = [];
14
+ for (const name of selection) {
15
+ const plugin = map.get(name);
16
+ if (!plugin || !pluginSupportsAgent(plugin, agent))
17
+ continue;
18
+ const r = syncPluginToVersion(plugin, agent, versionHome, { version });
19
+ if (r.success)
20
+ synced.push(name);
21
+ }
22
+ return { synced };
23
+ },
24
+ };
25
+ }
26
+ export const pluginsWriters = lazyAgentMap(() => {
27
+ const m = {};
28
+ for (const agent of capableAgents('plugins'))
29
+ m[agent] = buildPluginsWriter(agent);
30
+ return m;
31
+ });
@@ -0,0 +1,7 @@
1
+ import type { AgentId } from '../../types.js';
2
+ import type { ResourceWriter } from './types.js';
3
+ export interface RulesSelection {
4
+ /** Preset name to compose. Empty string falls through to `"default"` in the composer. */
5
+ preset: string;
6
+ }
7
+ export declare const rulesWriters: Partial<Record<AgentId, ResourceWriter<RulesSelection>>>;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Rules writer — composes one instruction file per supported agent.
3
+ *
4
+ * Single-target per agent (RulesCapability is `{ file: string } | false`).
5
+ * The composer in `lib/rules/compose.ts` handles all four layers
6
+ * (project > user > extras > system). We never write the project layer into
7
+ * the version home — it's resolved at agents-run time into the workspace
8
+ * AGENTS.md by `compileRulesForProject`.
9
+ *
10
+ * Selection shape: `{ preset: string }`. Use `getActiveRulesPreset(agent,
11
+ * version)` to derive the preset when the caller has no override.
12
+ */
13
+ import * as fs from 'fs';
14
+ import * as path from 'path';
15
+ import { AGENTS, agentConfigDirName } from '../../agents.js';
16
+ import { capableAgents } from '../../capabilities.js';
17
+ import { composeRulesFromState } from '../../rules/compose.js';
18
+ import { lazyAgentMap } from './lazy-map.js';
19
+ function buildRulesWriter(agent) {
20
+ return {
21
+ kind: 'rules',
22
+ agent,
23
+ write({ versionHome, selection }) {
24
+ const cap = AGENTS[agent].capabilities.rules;
25
+ if (cap === false) {
26
+ throw new Error(`rules writer reached for ${agent} (rules: false)`);
27
+ }
28
+ const targetName = cap.file;
29
+ const composed = composeRulesFromState({ preset: selection.preset || undefined });
30
+ const agentDir = path.join(versionHome, agentConfigDirName(agent));
31
+ // `cap.file` is a trusted constant from AGENTS table; openclaw ships a
32
+ // nested path (`workspace/AGENTS.md`) so we use path.join rather than
33
+ // safeJoin (which rejects path separators in `name`).
34
+ const destFile = path.join(agentDir, targetName);
35
+ fs.mkdirSync(path.dirname(destFile), { recursive: true });
36
+ // Remove any pre-existing symlink before writing — a stale symlink
37
+ // could point at a deleted source and writeFileSync would chase it
38
+ // to nothing.
39
+ try {
40
+ const st = fs.lstatSync(destFile);
41
+ if (st.isSymbolicLink() || st.isFile())
42
+ fs.unlinkSync(destFile);
43
+ }
44
+ catch { /* destination did not exist */ }
45
+ fs.writeFileSync(destFile, composed.content);
46
+ return { synced: [targetName] };
47
+ },
48
+ };
49
+ }
50
+ export const rulesWriters = lazyAgentMap(() => {
51
+ const m = {};
52
+ for (const agent of capableAgents('rules'))
53
+ m[agent] = buildRulesWriter(agent);
54
+ return m;
55
+ });
@@ -0,0 +1,3 @@
1
+ import type { AgentId } from '../../types.js';
2
+ import type { ResourceWriter } from './types.js';
3
+ export declare const skillsWriters: Partial<Record<AgentId, ResourceWriter<string[]>>>;
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Skills writer — copies each selected skill directory into
3
+ * `{agentDir}/skills/<name>/`. Agents flagged `nativeAgentsSkillsDir` (Gemini)
4
+ * read directly from `~/.agents/skills/` and have no writer registered; the
5
+ * sync orchestrator clears their version-home skills dir.
6
+ */
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ import { AGENTS, agentConfigDirName } from '../../agents.js';
10
+ import { capableAgents } from '../../capabilities.js';
11
+ import { safeJoin } from '../../paths.js';
12
+ import { resolveSkillSource } from './sources.js';
13
+ import { lazyAgentMap } from './lazy-map.js';
14
+ const SKILL_COPY_IGNORE = new Set(['.DS_Store', '.git', '.gitignore', '.venv', '__pycache__', 'node_modules']);
15
+ function copyDir(src, dest) {
16
+ fs.mkdirSync(dest, { recursive: true });
17
+ const entries = fs.readdirSync(src, { withFileTypes: true });
18
+ for (const entry of entries) {
19
+ if (entry.isSymbolicLink())
20
+ continue;
21
+ if (SKILL_COPY_IGNORE.has(entry.name))
22
+ continue;
23
+ const s = safeJoin(src, entry.name);
24
+ const d = safeJoin(dest, entry.name);
25
+ if (entry.isDirectory()) {
26
+ copyDir(s, d);
27
+ }
28
+ else if (entry.isFile()) {
29
+ fs.copyFileSync(s, d);
30
+ }
31
+ }
32
+ }
33
+ function removePath(p) {
34
+ try {
35
+ const st = fs.lstatSync(p);
36
+ if (st.isSymbolicLink() || st.isFile())
37
+ fs.unlinkSync(p);
38
+ else if (st.isDirectory())
39
+ fs.rmSync(p, { recursive: true, force: true });
40
+ }
41
+ catch { /* already gone */ }
42
+ }
43
+ function buildSkillsWriter(agent) {
44
+ return {
45
+ kind: 'skills',
46
+ agent,
47
+ write({ versionHome, selection }) {
48
+ const agentDir = path.join(versionHome, agentConfigDirName(agent));
49
+ const skillsTarget = path.join(agentDir, 'skills');
50
+ try {
51
+ if (fs.lstatSync(skillsTarget).isSymbolicLink()) {
52
+ removePath(skillsTarget);
53
+ }
54
+ }
55
+ catch { /* does not exist yet */ }
56
+ fs.mkdirSync(skillsTarget, { recursive: true });
57
+ const synced = [];
58
+ for (const skill of selection) {
59
+ const srcDir = resolveSkillSource(skill);
60
+ if (!srcDir)
61
+ continue;
62
+ const destDir = safeJoin(skillsTarget, skill);
63
+ removePath(destDir);
64
+ copyDir(srcDir, destDir);
65
+ synced.push(skill);
66
+ }
67
+ return { synced };
68
+ },
69
+ };
70
+ }
71
+ export const skillsWriters = lazyAgentMap(() => {
72
+ const m = {};
73
+ for (const agent of capableAgents('skills')) {
74
+ // Agents that natively read ~/.agents/skills/ don't get a version-home
75
+ // write — the orchestrator clears the dir for them.
76
+ if (AGENTS[agent].nativeAgentsSkillsDir)
77
+ continue;
78
+ m[agent] = buildSkillsWriter(agent);
79
+ }
80
+ return m;
81
+ });
@@ -0,0 +1,16 @@
1
+ export type EnabledExtra = {
2
+ alias: string;
3
+ dir: string;
4
+ };
5
+ /** Trusted source bases for content-like kinds. Project layer excluded. */
6
+ export declare function trustedSourceBases(): {
7
+ dir: string;
8
+ }[];
9
+ /** Find the trusted source for a command markdown by name. */
10
+ export declare function resolveCommandSource(name: string): string | null;
11
+ /** Find the trusted source directory for a skill by name. */
12
+ export declare function resolveSkillSource(name: string): string | null;
13
+ /** Find the trusted source file for a hook by name. */
14
+ export declare function resolveHookSource(name: string): string | null;
15
+ /** All trusted command-skill source roots, used to dedup name collisions for commands-as-skills writes. */
16
+ export declare function trustedSkillRoots(): string[];