@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
@@ -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 * as path from 'path';
9
+ import { agentConfigDirName } from '../../agents.js';
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[];
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Shared layer-source resolution for writers.
3
+ *
4
+ * Layer precedence matches getResourceBases() in versions.ts. Project layer
5
+ * is intentionally EXCLUDED for commands/skills/hooks/subagents/permissions
6
+ * — those bodies become agent context, and a cloned public repo could ship
7
+ * one that coerces the agent on the next launch. Trusted layers only:
8
+ * user → system → extras.
9
+ */
10
+ import * as fs from 'fs';
11
+ import * as path from 'path';
12
+ import { getUserAgentsDir, getAgentsDir, getEnabledExtraRepos, getCommandsDir, getSkillsDir, getHooksDir } from '../../state.js';
13
+ import { safeJoin } from '../../paths.js';
14
+ /** Trusted source bases for content-like kinds. Project layer excluded. */
15
+ export function trustedSourceBases() {
16
+ return [
17
+ { dir: getUserAgentsDir() },
18
+ { dir: getAgentsDir() },
19
+ ...getEnabledExtraRepos().map((e) => ({ dir: e.dir })),
20
+ ];
21
+ }
22
+ function isLiveFile(p) {
23
+ try {
24
+ return fs.existsSync(p) && !fs.lstatSync(p).isSymbolicLink();
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ }
30
+ function isLiveDir(p) {
31
+ try {
32
+ return fs.existsSync(p) && !fs.lstatSync(p).isSymbolicLink() && fs.lstatSync(p).isDirectory();
33
+ }
34
+ catch {
35
+ return false;
36
+ }
37
+ }
38
+ /** Find the trusted source for a command markdown by name. */
39
+ export function resolveCommandSource(name) {
40
+ const candidates = [
41
+ safeJoin(path.join(getUserAgentsDir(), 'commands'), `${name}.md`),
42
+ safeJoin(getCommandsDir(), `${name}.md`),
43
+ ...getEnabledExtraRepos().map((e) => safeJoin(path.join(e.dir, 'commands'), `${name}.md`)),
44
+ ];
45
+ return candidates.find(isLiveFile) ?? null;
46
+ }
47
+ /** Find the trusted source directory for a skill by name. */
48
+ export function resolveSkillSource(name) {
49
+ const candidates = [
50
+ safeJoin(path.join(getUserAgentsDir(), 'skills'), name),
51
+ safeJoin(getSkillsDir(), name),
52
+ ...getEnabledExtraRepos().map((e) => safeJoin(path.join(e.dir, 'skills'), name)),
53
+ ];
54
+ return candidates.find(isLiveDir) ?? null;
55
+ }
56
+ /** Find the trusted source file for a hook by name. */
57
+ export function resolveHookSource(name) {
58
+ const candidates = [
59
+ safeJoin(path.join(getUserAgentsDir(), 'hooks'), name),
60
+ safeJoin(getHooksDir(), name),
61
+ ...getEnabledExtraRepos().map((e) => safeJoin(path.join(e.dir, 'hooks'), name)),
62
+ ];
63
+ return candidates.find(isLiveFile) ?? null;
64
+ }
65
+ /** All trusted command-skill source roots, used to dedup name collisions for commands-as-skills writes. */
66
+ export function trustedSkillRoots() {
67
+ return [
68
+ path.join(getUserAgentsDir(), 'skills'),
69
+ getSkillsDir(),
70
+ ...getEnabledExtraRepos().map((e) => path.join(e.dir, 'skills')),
71
+ ];
72
+ }
@@ -0,0 +1,3 @@
1
+ import type { AgentId } from '../../types.js';
2
+ import type { ResourceWriter } from './types.js';
3
+ export declare const subagentsWriters: Partial<Record<AgentId, ResourceWriter<string[]>>>;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Subagents writer. Claude flattens each subagent into a single .md file
3
+ * under `<agentDir>/agents/`. OpenClaw copies the full subagent directory
4
+ * (with AGENT.md renamed to AGENTS.md) into `<versionHome>/.openclaw/<name>/`.
5
+ *
6
+ * Source-side discovery is `listInstalledSubagents` from lib/subagents.ts —
7
+ * it reads user + system layers only (project layer excluded for the same
8
+ * defense as commands/skills/hooks).
9
+ */
10
+ import * as fs from 'fs';
11
+ import * as path from 'path';
12
+ import { capableAgents } from '../../capabilities.js';
13
+ import { listInstalledSubagents, transformSubagentForClaude, syncSubagentToOpenclaw } from '../../subagents.js';
14
+ import { safeJoin } from '../../paths.js';
15
+ import { lazyAgentMap } from './lazy-map.js';
16
+ function buildSubagentsWriter(agent) {
17
+ return {
18
+ kind: 'subagents',
19
+ agent,
20
+ write({ versionHome, selection }) {
21
+ const all = listInstalledSubagents();
22
+ const map = new Map(all.map(s => [s.name, s]));
23
+ const synced = [];
24
+ for (const name of selection) {
25
+ const sub = map.get(name);
26
+ if (!sub)
27
+ continue;
28
+ try {
29
+ if (agent === 'claude') {
30
+ const agentsDir = path.join(versionHome, '.claude', 'agents');
31
+ fs.mkdirSync(agentsDir, { recursive: true });
32
+ fs.writeFileSync(safeJoin(agentsDir, `${sub.name}.md`), transformSubagentForClaude(sub.path));
33
+ synced.push(sub.name);
34
+ }
35
+ else if (agent === 'openclaw') {
36
+ const target = safeJoin(path.join(versionHome, '.openclaw'), sub.name);
37
+ const r = syncSubagentToOpenclaw(sub.path, target);
38
+ if (r.success)
39
+ synced.push(sub.name);
40
+ }
41
+ }
42
+ catch { /* per-item sync failure: skip */ }
43
+ }
44
+ return { synced };
45
+ },
46
+ };
47
+ }
48
+ export const subagentsWriters = lazyAgentMap(() => {
49
+ const m = {};
50
+ for (const agent of capableAgents('subagents'))
51
+ m[agent] = buildSubagentsWriter(agent);
52
+ return m;
53
+ });