@phnx-labs/agents-cli 1.20.4 → 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 (190) hide show
  1. package/CHANGELOG.md +19 -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/inspect.d.ts +26 -0
  10. package/dist/commands/inspect.js +590 -0
  11. package/dist/commands/mcp.js +17 -16
  12. package/dist/commands/models.js +1 -1
  13. package/dist/commands/packages.js +6 -4
  14. package/dist/commands/permissions.js +13 -12
  15. package/dist/commands/plugins.d.ts +13 -0
  16. package/dist/commands/plugins.js +100 -11
  17. package/dist/commands/prune.js +3 -2
  18. package/dist/commands/pull.d.ts +12 -5
  19. package/dist/commands/pull.js +26 -422
  20. package/dist/commands/push.d.ts +14 -0
  21. package/dist/commands/push.js +30 -0
  22. package/dist/commands/repo.d.ts +1 -1
  23. package/dist/commands/repo.js +155 -112
  24. package/dist/commands/resource-view.d.ts +2 -0
  25. package/dist/commands/resource-view.js +12 -3
  26. package/dist/commands/routines.js +32 -7
  27. package/dist/commands/rules.js +1 -1
  28. package/dist/commands/sessions.js +1 -0
  29. package/dist/commands/setup.d.ts +3 -3
  30. package/dist/commands/setup.js +15 -15
  31. package/dist/commands/skills.js +6 -5
  32. package/dist/commands/subagents.js +5 -4
  33. package/dist/commands/sync.d.ts +18 -5
  34. package/dist/commands/sync.js +251 -65
  35. package/dist/commands/teams.js +1 -0
  36. package/dist/commands/tmux.d.ts +25 -0
  37. package/dist/commands/tmux.js +415 -0
  38. package/dist/commands/trash.d.ts +2 -2
  39. package/dist/commands/trash.js +1 -1
  40. package/dist/commands/versions.js +2 -2
  41. package/dist/commands/view.js +9 -4
  42. package/dist/commands/workflows.js +4 -3
  43. package/dist/commands/worktree.d.ts +4 -5
  44. package/dist/commands/worktree.js +4 -4
  45. package/dist/index.js +68 -20
  46. package/dist/lib/agents.d.ts +19 -10
  47. package/dist/lib/agents.js +79 -25
  48. package/dist/lib/auto-pull-worker.d.ts +1 -1
  49. package/dist/lib/auto-pull-worker.js +2 -2
  50. package/dist/lib/auto-pull.d.ts +1 -1
  51. package/dist/lib/auto-pull.js +1 -1
  52. package/dist/lib/beta.d.ts +1 -1
  53. package/dist/lib/beta.js +1 -1
  54. package/dist/lib/capabilities.js +2 -0
  55. package/dist/lib/commands.d.ts +28 -1
  56. package/dist/lib/commands.js +125 -20
  57. package/dist/lib/doctor-diff.js +2 -2
  58. package/dist/lib/exec.d.ts +14 -0
  59. package/dist/lib/exec.js +39 -5
  60. package/dist/lib/fuzzy.d.ts +12 -2
  61. package/dist/lib/fuzzy.js +29 -4
  62. package/dist/lib/git.js +8 -1
  63. package/dist/lib/hooks.d.ts +2 -2
  64. package/dist/lib/hooks.js +97 -10
  65. package/dist/lib/mcp.js +32 -2
  66. package/dist/lib/migrate.d.ts +51 -0
  67. package/dist/lib/migrate.js +227 -1
  68. package/dist/lib/models.js +62 -15
  69. package/dist/lib/permissions.d.ts +36 -2
  70. package/dist/lib/permissions.js +217 -7
  71. package/dist/lib/plugin-marketplace.d.ts +98 -40
  72. package/dist/lib/plugin-marketplace.js +196 -93
  73. package/dist/lib/plugins.d.ts +21 -4
  74. package/dist/lib/plugins.js +130 -49
  75. package/dist/lib/profiles-presets.js +12 -12
  76. package/dist/lib/project-launch.d.ts +65 -0
  77. package/dist/lib/project-launch.js +367 -0
  78. package/dist/lib/pty-client.js +1 -1
  79. package/dist/lib/pty-server.d.ts +1 -1
  80. package/dist/lib/pty-server.js +1 -1
  81. package/dist/lib/refresh.d.ts +26 -0
  82. package/dist/lib/refresh.js +315 -0
  83. package/dist/lib/resource-patterns.d.ts +1 -1
  84. package/dist/lib/resource-patterns.js +1 -1
  85. package/dist/lib/resources/commands.js +2 -2
  86. package/dist/lib/resources/hooks.d.ts +1 -1
  87. package/dist/lib/resources/hooks.js +1 -1
  88. package/dist/lib/resources/mcp.d.ts +1 -1
  89. package/dist/lib/resources/mcp.js +5 -6
  90. package/dist/lib/resources/permissions.js +5 -2
  91. package/dist/lib/resources/rules.js +3 -2
  92. package/dist/lib/resources/skills.js +3 -2
  93. package/dist/lib/resources/types.d.ts +1 -1
  94. package/dist/lib/resources.js +2 -2
  95. package/dist/lib/rotate.d.ts +1 -1
  96. package/dist/lib/rotate.js +1 -1
  97. package/dist/lib/routines.d.ts +16 -4
  98. package/dist/lib/routines.js +67 -17
  99. package/dist/lib/rules/compile.js +22 -10
  100. package/dist/lib/rules/rules.js +3 -3
  101. package/dist/lib/runner.js +16 -3
  102. package/dist/lib/scheduler.js +15 -1
  103. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  104. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  105. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +9 -1
  106. package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
  107. package/dist/lib/secrets/linux.d.ts +44 -9
  108. package/dist/lib/secrets/linux.js +302 -48
  109. package/dist/lib/session/db.js +15 -2
  110. package/dist/lib/session/discover.js +118 -3
  111. package/dist/lib/session/parse.js +3 -0
  112. package/dist/lib/session/types.d.ts +1 -1
  113. package/dist/lib/session/types.js +1 -1
  114. package/dist/lib/shims.d.ts +10 -9
  115. package/dist/lib/shims.js +101 -50
  116. package/dist/lib/skills.d.ts +1 -1
  117. package/dist/lib/skills.js +10 -9
  118. package/dist/lib/staleness/detectors/commands.d.ts +3 -0
  119. package/dist/lib/staleness/detectors/commands.js +46 -0
  120. package/dist/lib/staleness/detectors/hooks.d.ts +3 -0
  121. package/dist/lib/staleness/detectors/hooks.js +44 -0
  122. package/dist/lib/staleness/detectors/mcp.d.ts +3 -0
  123. package/dist/lib/staleness/detectors/mcp.js +31 -0
  124. package/dist/lib/staleness/detectors/permissions.d.ts +3 -0
  125. package/dist/lib/staleness/detectors/permissions.js +201 -0
  126. package/dist/lib/staleness/detectors/plugins.d.ts +8 -0
  127. package/dist/lib/staleness/detectors/plugins.js +23 -0
  128. package/dist/lib/staleness/detectors/rules.d.ts +3 -0
  129. package/dist/lib/staleness/detectors/rules.js +34 -0
  130. package/dist/lib/staleness/detectors/skills.d.ts +3 -0
  131. package/dist/lib/staleness/detectors/skills.js +71 -0
  132. package/dist/lib/staleness/detectors/subagents.d.ts +3 -0
  133. package/dist/lib/staleness/detectors/subagents.js +50 -0
  134. package/dist/lib/staleness/detectors/types.d.ts +22 -0
  135. package/dist/lib/staleness/detectors/types.js +1 -0
  136. package/dist/lib/staleness/detectors/workflows.d.ts +3 -0
  137. package/dist/lib/staleness/detectors/workflows.js +28 -0
  138. package/dist/lib/staleness/registry.d.ts +26 -0
  139. package/dist/lib/staleness/registry.js +123 -0
  140. package/dist/lib/staleness/writers/commands.d.ts +3 -0
  141. package/dist/lib/staleness/writers/commands.js +111 -0
  142. package/dist/lib/staleness/writers/hooks.d.ts +3 -0
  143. package/dist/lib/staleness/writers/hooks.js +47 -0
  144. package/dist/lib/staleness/writers/kinds.d.ts +10 -0
  145. package/dist/lib/staleness/writers/kinds.js +15 -0
  146. package/dist/lib/staleness/writers/lazy-map.d.ts +13 -0
  147. package/dist/lib/staleness/writers/lazy-map.js +19 -0
  148. package/dist/lib/staleness/writers/mcp.d.ts +10 -0
  149. package/dist/lib/staleness/writers/mcp.js +19 -0
  150. package/dist/lib/staleness/writers/permissions.d.ts +13 -0
  151. package/dist/lib/staleness/writers/permissions.js +26 -0
  152. package/dist/lib/staleness/writers/plugins.d.ts +7 -0
  153. package/dist/lib/staleness/writers/plugins.js +31 -0
  154. package/dist/lib/staleness/writers/rules.d.ts +7 -0
  155. package/dist/lib/staleness/writers/rules.js +55 -0
  156. package/dist/lib/staleness/writers/skills.d.ts +3 -0
  157. package/dist/lib/staleness/writers/skills.js +81 -0
  158. package/dist/lib/staleness/writers/sources.d.ts +16 -0
  159. package/dist/lib/staleness/writers/sources.js +72 -0
  160. package/dist/lib/staleness/writers/subagents.d.ts +3 -0
  161. package/dist/lib/staleness/writers/subagents.js +53 -0
  162. package/dist/lib/staleness/writers/types.d.ts +36 -0
  163. package/dist/lib/staleness/writers/types.js +1 -0
  164. package/dist/lib/staleness/writers/workflows.d.ts +7 -0
  165. package/dist/lib/staleness/writers/workflows.js +31 -0
  166. package/dist/lib/state.d.ts +34 -11
  167. package/dist/lib/state.js +58 -13
  168. package/dist/lib/subagents.d.ts +0 -2
  169. package/dist/lib/subagents.js +6 -6
  170. package/dist/lib/teams/agents.js +1 -1
  171. package/dist/lib/teams/parsers.d.ts +1 -1
  172. package/dist/lib/tmux/binary.d.ts +67 -0
  173. package/dist/lib/tmux/binary.js +141 -0
  174. package/dist/lib/tmux/index.d.ts +8 -0
  175. package/dist/lib/tmux/index.js +8 -0
  176. package/dist/lib/tmux/paths.d.ts +17 -0
  177. package/dist/lib/tmux/paths.js +30 -0
  178. package/dist/lib/tmux/session.d.ts +122 -0
  179. package/dist/lib/tmux/session.js +305 -0
  180. package/dist/lib/types.d.ts +58 -7
  181. package/dist/lib/types.js +1 -1
  182. package/dist/lib/usage.js +1 -1
  183. package/dist/lib/versions.d.ts +4 -4
  184. package/dist/lib/versions.js +135 -493
  185. package/dist/lib/workflows.d.ts +2 -4
  186. package/dist/lib/workflows.js +3 -4
  187. package/package.json +2 -2
  188. package/scripts/postinstall.js +16 -63
  189. package/dist/commands/status.d.ts +0 -9
  190. package/dist/commands/status.js +0 -25
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Permissions detector — inspects the agent's native permission storage and
3
+ * reports the permission GROUP names that have been applied.
4
+ *
5
+ * For claude/opencode the detector intersects with discovered groups (a group
6
+ * is "applied" if any of its allow/deny rules are present). For Codex / Gemini /
7
+ * Antigravity / Grok the on-disk format is lossy — once any group has been
8
+ * applied the storage doesn't carry per-group provenance back, so we report
9
+ * "all known groups applied" when any permission artifact is present. This
10
+ * matches the existing behavior in versions.ts:445-518 (extended to the
11
+ * agents that were previously silent-skipped).
12
+ */
13
+ import * as fs from 'fs';
14
+ import * as path from 'path';
15
+ import * as TOML from 'smol-toml';
16
+ import { capableAgents } from '../../capabilities.js';
17
+ import { discoverPermissionGroups, buildPermissionsFromGroups, CODEX_RULES_FILENAME, } from '../../permissions.js';
18
+ import { lazyAgentMap } from '../writers/lazy-map.js';
19
+ function buildClaudeDetector() {
20
+ return {
21
+ kind: 'permissions',
22
+ agent: 'claude',
23
+ list({ versionHome }) {
24
+ const settingsPath = path.join(versionHome, '.claude', 'settings.json');
25
+ if (!fs.existsSync(settingsPath))
26
+ return [];
27
+ try {
28
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
29
+ const allowRules = settings.permissions?.allow || [];
30
+ const denyRules = settings.permissions?.deny || [];
31
+ if (allowRules.length === 0 && denyRules.length === 0)
32
+ return [];
33
+ const groups = discoverPermissionGroups();
34
+ const applied = [];
35
+ for (const group of groups) {
36
+ const built = buildPermissionsFromGroups([group.name]);
37
+ // Empty groups (header files) count as synced when anything is applied.
38
+ if (built.allow.length === 0 && (!built.deny || built.deny.length === 0)) {
39
+ applied.push(group.name);
40
+ continue;
41
+ }
42
+ const hasAllow = built.allow.some(r => allowRules.includes(r));
43
+ const hasDeny = built.deny?.some(r => denyRules.includes(r)) || false;
44
+ if (hasAllow || hasDeny)
45
+ applied.push(group.name);
46
+ }
47
+ return applied;
48
+ }
49
+ catch {
50
+ return [];
51
+ }
52
+ },
53
+ };
54
+ }
55
+ function buildCodexDetector() {
56
+ return {
57
+ kind: 'permissions',
58
+ agent: 'codex',
59
+ list({ versionHome }) {
60
+ const codexConfigPath = path.join(versionHome, '.codex', 'config.toml');
61
+ const codexRulesPath = path.join(versionHome, '.codex', 'rules', CODEX_RULES_FILENAME);
62
+ const hasConfig = fs.existsSync(codexConfigPath);
63
+ const hasRules = fs.existsSync(codexRulesPath);
64
+ if (!hasConfig && !hasRules)
65
+ return [];
66
+ try {
67
+ let hasPermKeys = false;
68
+ if (hasConfig) {
69
+ const config = TOML.parse(fs.readFileSync(codexConfigPath, 'utf-8'));
70
+ hasPermKeys = !!(config.approval_policy || config.sandbox_mode || config.sandbox_workspace_write);
71
+ }
72
+ if (hasPermKeys || hasRules) {
73
+ return discoverPermissionGroups().map(g => g.name);
74
+ }
75
+ }
76
+ catch { /* parse fail */ }
77
+ return [];
78
+ },
79
+ };
80
+ }
81
+ function buildOpenCodeDetector() {
82
+ return {
83
+ kind: 'permissions',
84
+ agent: 'opencode',
85
+ list({ versionHome }) {
86
+ const opencodeConfigPath = path.join(versionHome, '.opencode', 'opencode.jsonc');
87
+ if (!fs.existsSync(opencodeConfigPath))
88
+ return [];
89
+ try {
90
+ const content = fs.readFileSync(opencodeConfigPath, 'utf-8');
91
+ const stripped = content.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
92
+ const config = JSON.parse(stripped);
93
+ if (config.permission && Object.keys(config.permission.bash || {}).length > 0) {
94
+ return discoverPermissionGroups().map(g => g.name);
95
+ }
96
+ }
97
+ catch { /* parse fail */ }
98
+ return [];
99
+ },
100
+ };
101
+ }
102
+ function buildGeminiDetector() {
103
+ return {
104
+ kind: 'permissions',
105
+ agent: 'gemini',
106
+ list({ versionHome }) {
107
+ const settingsPath = path.join(versionHome, '.gemini', 'settings.json');
108
+ if (!fs.existsSync(settingsPath))
109
+ return [];
110
+ try {
111
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
112
+ const allowed = settings?.tools?.allowed;
113
+ if (Array.isArray(allowed) && allowed.length > 0) {
114
+ return discoverPermissionGroups().map(g => g.name);
115
+ }
116
+ }
117
+ catch { /* parse fail */ }
118
+ return [];
119
+ },
120
+ };
121
+ }
122
+ function buildAntigravityDetector() {
123
+ return {
124
+ kind: 'permissions',
125
+ agent: 'antigravity',
126
+ list({ versionHome }) {
127
+ const settingsPath = path.join(versionHome, '.gemini', 'antigravity-cli', 'settings.json');
128
+ if (!fs.existsSync(settingsPath))
129
+ return [];
130
+ try {
131
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
132
+ const perms = settings?.permissions;
133
+ const hasAllow = Array.isArray(perms?.allow) && perms.allow.length > 0;
134
+ const hasDeny = Array.isArray(perms?.deny) && perms.deny.length > 0;
135
+ if (hasAllow || hasDeny) {
136
+ return discoverPermissionGroups().map(g => g.name);
137
+ }
138
+ }
139
+ catch { /* parse fail */ }
140
+ return [];
141
+ },
142
+ };
143
+ }
144
+ function buildGrokDetector() {
145
+ return {
146
+ kind: 'permissions',
147
+ agent: 'grok',
148
+ list({ versionHome }) {
149
+ const configPath = path.join(versionHome, '.grok', 'config.toml');
150
+ if (!fs.existsSync(configPath))
151
+ return [];
152
+ try {
153
+ const config = TOML.parse(fs.readFileSync(configPath, 'utf-8'));
154
+ const perm = config.permission;
155
+ if (perm && Array.isArray(perm.rules) && perm.rules.length > 0) {
156
+ return discoverPermissionGroups().map(g => g.name);
157
+ }
158
+ }
159
+ catch { /* parse fail */ }
160
+ return [];
161
+ },
162
+ };
163
+ }
164
+ function buildKimiDetector() {
165
+ return {
166
+ kind: 'permissions',
167
+ agent: 'kimi',
168
+ list({ versionHome }) {
169
+ const configPath = path.join(versionHome, '.kimi-code', 'config.toml');
170
+ if (!fs.existsSync(configPath))
171
+ return [];
172
+ try {
173
+ const config = TOML.parse(fs.readFileSync(configPath, 'utf-8'));
174
+ const perm = config.permission;
175
+ if (perm && Array.isArray(perm.rules) && perm.rules.length > 0) {
176
+ return discoverPermissionGroups().map(g => g.name);
177
+ }
178
+ }
179
+ catch { /* parse fail */ }
180
+ return [];
181
+ },
182
+ };
183
+ }
184
+ const handlers = {
185
+ claude: buildClaudeDetector,
186
+ codex: buildCodexDetector,
187
+ opencode: buildOpenCodeDetector,
188
+ gemini: buildGeminiDetector,
189
+ antigravity: buildAntigravityDetector,
190
+ grok: buildGrokDetector,
191
+ kimi: buildKimiDetector,
192
+ };
193
+ export const permissionsDetectors = lazyAgentMap(() => {
194
+ const m = {};
195
+ for (const agent of capableAgents('allowlist')) {
196
+ const f = handlers[agent];
197
+ if (f)
198
+ m[agent] = f();
199
+ }
200
+ return m;
201
+ });
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Plugins detector — for each discovered plugin, ask `isPluginSynced` whether
3
+ * its expected artifacts are present in the version home. Mirrors
4
+ * versions.ts:541-549.
5
+ */
6
+ import type { AgentId } from '../../types.js';
7
+ import type { ResourceDetector } from './types.js';
8
+ export declare const pluginsDetectors: Partial<Record<AgentId, ResourceDetector>>;
@@ -0,0 +1,23 @@
1
+ import { capableAgents } from '../../capabilities.js';
2
+ import { discoverPlugins, isPluginSynced } from '../../plugins.js';
3
+ import { lazyAgentMap } from '../writers/lazy-map.js';
4
+ function buildPluginsDetector(agent) {
5
+ return {
6
+ kind: 'plugins',
7
+ agent,
8
+ list({ versionHome }) {
9
+ const synced = [];
10
+ for (const plugin of discoverPlugins()) {
11
+ if (isPluginSynced(plugin, agent, versionHome))
12
+ synced.push(plugin.name);
13
+ }
14
+ return synced;
15
+ },
16
+ };
17
+ }
18
+ export const pluginsDetectors = lazyAgentMap(() => {
19
+ const m = {};
20
+ for (const agent of capableAgents('plugins'))
21
+ m[agent] = buildPluginsDetector(agent);
22
+ return m;
23
+ });
@@ -0,0 +1,3 @@
1
+ import type { AgentId } from '../../types.js';
2
+ import type { ResourceDetector } from './types.js';
3
+ export declare const rulesDetectors: Partial<Record<AgentId, ResourceDetector>>;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Rules detector — reports whether the composed instructions file exists in
3
+ * the version home. Uses the active rules preset name as the detected
4
+ * "resource name" so the diff stays meaningful (one preset = one synced
5
+ * name, mirroring what getAvailableResources surfaces under `memory`).
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 { getActiveRulesPreset } from '../../state.js';
12
+ import { lazyAgentMap } from '../writers/lazy-map.js';
13
+ function buildRulesDetector(agent) {
14
+ return {
15
+ kind: 'rules',
16
+ agent,
17
+ list({ version, versionHome }) {
18
+ const cap = AGENTS[agent].capabilities.rules;
19
+ if (cap === false)
20
+ return [];
21
+ const agentDir = path.join(versionHome, agentConfigDirName(agent));
22
+ const instrFile = path.join(agentDir, cap.file);
23
+ if (!fs.existsSync(instrFile))
24
+ return [];
25
+ return [getActiveRulesPreset(agent, version)];
26
+ },
27
+ };
28
+ }
29
+ export const rulesDetectors = lazyAgentMap(() => {
30
+ const m = {};
31
+ for (const agent of capableAgents('rules'))
32
+ m[agent] = buildRulesDetector(agent);
33
+ return m;
34
+ });
@@ -0,0 +1,3 @@
1
+ import type { AgentId } from '../../types.js';
2
+ import type { ResourceDetector } from './types.js';
3
+ export declare const skillsDetectors: Partial<Record<AgentId, ResourceDetector>>;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Skills detector — names of skill directories materialized in the version
3
+ * home that match the central source content. Mirrors versions.ts:359-389.
4
+ */
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import { AGENTS, agentConfigDirName } from '../../agents.js';
8
+ import { capableAgents } from '../../capabilities.js';
9
+ import { resolveSkillSource } from '../writers/sources.js';
10
+ import { lazyAgentMap } from '../writers/lazy-map.js';
11
+ const SKILL_COPY_IGNORE = new Set(['.DS_Store', '.git', '.gitignore', '.venv', '__pycache__', 'node_modules']);
12
+ function skillDirsMatch(src, dest) {
13
+ const entries = fs.readdirSync(src, { withFileTypes: true });
14
+ for (const entry of entries) {
15
+ if (entry.isSymbolicLink())
16
+ continue;
17
+ if (SKILL_COPY_IGNORE.has(entry.name))
18
+ continue;
19
+ const srcPath = path.join(src, entry.name);
20
+ const destPath = path.join(dest, entry.name);
21
+ if (entry.isDirectory()) {
22
+ if (!fs.existsSync(destPath))
23
+ return false;
24
+ if (!skillDirsMatch(srcPath, destPath))
25
+ return false;
26
+ }
27
+ else {
28
+ if (!fs.existsSync(destPath))
29
+ return false;
30
+ if (fs.readFileSync(srcPath, 'utf-8') !== fs.readFileSync(destPath, 'utf-8'))
31
+ return false;
32
+ }
33
+ }
34
+ return true;
35
+ }
36
+ function buildSkillsDetector(agent) {
37
+ return {
38
+ kind: 'skills',
39
+ agent,
40
+ list({ versionHome }) {
41
+ const skillsDir = path.join(versionHome, agentConfigDirName(agent), 'skills');
42
+ if (!fs.existsSync(skillsDir))
43
+ return [];
44
+ const installed = fs.readdirSync(skillsDir, { withFileTypes: true })
45
+ .filter(d => d.isDirectory() && !d.name.startsWith('.'))
46
+ .map(d => d.name);
47
+ const synced = [];
48
+ for (const name of installed) {
49
+ const src = resolveSkillSource(name);
50
+ if (!src) {
51
+ // True orphan — no source. Still count so cleanup knows it's accounted for.
52
+ synced.push(name);
53
+ continue;
54
+ }
55
+ if (skillDirsMatch(src, path.join(skillsDir, name))) {
56
+ synced.push(name);
57
+ }
58
+ }
59
+ return synced;
60
+ },
61
+ };
62
+ }
63
+ export const skillsDetectors = lazyAgentMap(() => {
64
+ const m = {};
65
+ for (const agent of capableAgents('skills')) {
66
+ if (AGENTS[agent].nativeAgentsSkillsDir)
67
+ continue;
68
+ m[agent] = buildSkillsDetector(agent);
69
+ }
70
+ return m;
71
+ });
@@ -0,0 +1,3 @@
1
+ import type { AgentId } from '../../types.js';
2
+ import type { ResourceDetector } from './types.js';
3
+ export declare const subagentsDetectors: Partial<Record<AgentId, ResourceDetector>>;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Subagents detector. Claude: flat .md files under `<agentDir>/agents/`.
3
+ * OpenClaw: subdirectories containing AGENTS.md under `<versionHome>/.openclaw/`.
4
+ * Mirrors versions.ts:521-539.
5
+ */
6
+ import * as fs from 'fs';
7
+ import * as path from 'path';
8
+ import { capableAgents } from '../../capabilities.js';
9
+ import { lazyAgentMap } from '../writers/lazy-map.js';
10
+ function buildClaudeDetector() {
11
+ return {
12
+ kind: 'subagents',
13
+ agent: 'claude',
14
+ list({ versionHome }) {
15
+ const agentsDir = path.join(versionHome, '.claude', 'agents');
16
+ if (!fs.existsSync(agentsDir))
17
+ return [];
18
+ return fs.readdirSync(agentsDir)
19
+ .filter(f => f.endsWith('.md'))
20
+ .map(f => f.replace('.md', ''));
21
+ },
22
+ };
23
+ }
24
+ function buildOpenclawDetector() {
25
+ return {
26
+ kind: 'subagents',
27
+ agent: 'openclaw',
28
+ list({ versionHome }) {
29
+ const openclawDir = path.join(versionHome, '.openclaw');
30
+ if (!fs.existsSync(openclawDir))
31
+ return [];
32
+ return fs.readdirSync(openclawDir, { withFileTypes: true })
33
+ .filter(d => d.isDirectory() && fs.existsSync(path.join(openclawDir, d.name, 'AGENTS.md')))
34
+ .map(d => d.name);
35
+ },
36
+ };
37
+ }
38
+ const handlers = {
39
+ claude: buildClaudeDetector,
40
+ openclaw: buildOpenclawDetector,
41
+ };
42
+ export const subagentsDetectors = lazyAgentMap(() => {
43
+ const m = {};
44
+ for (const agent of capableAgents('subagents')) {
45
+ const f = handlers[agent];
46
+ if (f)
47
+ m[agent] = f();
48
+ }
49
+ return m;
50
+ });
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Per-(kind, agent) detector contract.
3
+ *
4
+ * A detector inspects a version home and reports which resource names of a
5
+ * given kind are materialized there. The aggregator at
6
+ * `getActuallySyncedResources` calls one detector per kind/agent pair to build
7
+ * the "what is actually on disk" view, which is then diffed against
8
+ * "what is available" to drive the resource prompt in `agents view`.
9
+ */
10
+ import type { AgentId } from '../../types.js';
11
+ import type { ResourceKind } from '../writers/kinds.js';
12
+ export interface DetectArgs {
13
+ version: string;
14
+ versionHome: string;
15
+ /** Working directory — needed by detectors that resolve project state. */
16
+ cwd: string;
17
+ }
18
+ export interface ResourceDetector {
19
+ readonly kind: ResourceKind;
20
+ readonly agent: AgentId;
21
+ list(args: DetectArgs): string[];
22
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import type { AgentId } from '../../types.js';
2
+ import type { ResourceDetector } from './types.js';
3
+ export declare const workflowsDetectors: Partial<Record<AgentId, ResourceDetector>>;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Workflows detector — scans `{versionHome}/workflows/` for subdirectories
3
+ * containing WORKFLOW.md. Mirrors versions.ts:551-558.
4
+ */
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import { capableAgents } from '../../capabilities.js';
8
+ import { lazyAgentMap } from '../writers/lazy-map.js';
9
+ function buildWorkflowsDetector(agent) {
10
+ return {
11
+ kind: 'workflows',
12
+ agent,
13
+ list({ versionHome }) {
14
+ const workflowsDir = path.join(versionHome, 'workflows');
15
+ if (!fs.existsSync(workflowsDir))
16
+ return [];
17
+ return fs.readdirSync(workflowsDir, { withFileTypes: true })
18
+ .filter(d => d.isDirectory() && fs.existsSync(path.join(workflowsDir, d.name, 'WORKFLOW.md')))
19
+ .map(d => d.name);
20
+ },
21
+ };
22
+ }
23
+ export const workflowsDetectors = lazyAgentMap(() => {
24
+ const m = {};
25
+ for (const agent of capableAgents('workflows'))
26
+ m[agent] = buildWorkflowsDetector(agent);
27
+ return m;
28
+ });
@@ -0,0 +1,26 @@
1
+ import type { AgentId } from '../types.js';
2
+ import { type ResourceKind } from './writers/kinds.js';
3
+ import type { ResourceWriter } from './writers/types.js';
4
+ import type { ResourceDetector } from './detectors/types.js';
5
+ import type { RulesSelection } from './writers/rules.js';
6
+ export type { ResourceKind } from './writers/kinds.js';
7
+ export { kindToCapability, ALL_RESOURCE_KINDS } from './writers/kinds.js';
8
+ export type { ResourceWriter, WriteArgs, WriteResult } from './writers/types.js';
9
+ export type { ResourceDetector, DetectArgs } from './detectors/types.js';
10
+ export type { RulesSelection } from './writers/rules.js';
11
+ /** Per-kind selection payload. Most kinds are string[]; rules is special. */
12
+ export type SelectionFor<K extends ResourceKind> = K extends 'rules' ? RulesSelection : string[];
13
+ export declare const WRITERS: Record<ResourceKind, Partial<Record<AgentId, ResourceWriter<any>>>>;
14
+ export declare const DETECTORS: Record<ResourceKind, Partial<Record<AgentId, ResourceDetector>>>;
15
+ /**
16
+ * Verify every supported (agent, kind) pair has both a writer and a
17
+ * detector. Deferred from module-import time to the first `getWriter` /
18
+ * `getDetector` call to dodge the agents.ts ↔ versions.ts ↔ registry.ts
19
+ * import cycle (the same one the lazy writer/detector maps work around).
20
+ * Idempotent — runs at most once per process.
21
+ */
22
+ export declare function assertRegistryComplete(): void;
23
+ /** Return the writer for (kind, agent), or undefined if unsupported. */
24
+ export declare function getWriter<K extends ResourceKind>(kind: K, agent: AgentId): ResourceWriter<SelectionFor<K>> | undefined;
25
+ /** Return the detector for (kind, agent), or undefined if unsupported. */
26
+ export declare function getDetector(kind: ResourceKind, agent: AgentId): ResourceDetector | undefined;
@@ -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[]>>>;