@phnx-labs/agents-cli 1.20.71 → 1.20.73

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 (238) hide show
  1. package/CHANGELOG.md +325 -1
  2. package/README.md +40 -4
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/activity.d.ts +12 -0
  5. package/dist/commands/activity.js +68 -0
  6. package/dist/commands/apply.d.ts +13 -0
  7. package/dist/commands/apply.js +28 -3
  8. package/dist/commands/browser.js +1 -1
  9. package/dist/commands/cloud.js +21 -11
  10. package/dist/commands/commands.js +2 -0
  11. package/dist/commands/doctor.js +8 -4
  12. package/dist/commands/events.d.ts +11 -10
  13. package/dist/commands/events.js +30 -21
  14. package/dist/commands/exec.d.ts +34 -0
  15. package/dist/commands/exec.js +337 -39
  16. package/dist/commands/feed.d.ts +13 -0
  17. package/dist/commands/feed.js +130 -29
  18. package/dist/commands/hq.d.ts +2 -0
  19. package/dist/commands/hq.js +58 -0
  20. package/dist/commands/lease.d.ts +19 -0
  21. package/dist/commands/lease.js +138 -11
  22. package/dist/commands/login.d.ts +2 -0
  23. package/dist/commands/login.js +123 -0
  24. package/dist/commands/logs.js +39 -10
  25. package/dist/commands/mcp.js +2 -0
  26. package/dist/commands/mine.d.ts +27 -0
  27. package/dist/commands/mine.js +207 -0
  28. package/dist/commands/monitors.js +79 -49
  29. package/dist/commands/plugins.js +1 -0
  30. package/dist/commands/profiles.js +158 -0
  31. package/dist/commands/repo.js +2 -60
  32. package/dist/commands/resource-view.d.ts +2 -0
  33. package/dist/commands/resource-view.js +8 -0
  34. package/dist/commands/resources.d.ts +5 -0
  35. package/dist/commands/resources.js +95 -0
  36. package/dist/commands/routines.d.ts +2 -0
  37. package/dist/commands/routines.js +139 -37
  38. package/dist/commands/secrets.js +234 -47
  39. package/dist/commands/send.d.ts +15 -0
  40. package/dist/commands/send.js +62 -0
  41. package/dist/commands/sessions-tail.d.ts +1 -0
  42. package/dist/commands/sessions-tail.js +20 -9
  43. package/dist/commands/sessions.d.ts +6 -0
  44. package/dist/commands/sessions.js +26 -3
  45. package/dist/commands/setup-mine.d.ts +18 -0
  46. package/dist/commands/setup-mine.js +106 -0
  47. package/dist/commands/setup-share.js +2 -2
  48. package/dist/commands/setup.js +3 -1
  49. package/dist/commands/share.d.ts +8 -1
  50. package/dist/commands/share.js +122 -37
  51. package/dist/commands/skills.js +2 -0
  52. package/dist/commands/ssh.d.ts +9 -0
  53. package/dist/commands/ssh.js +196 -26
  54. package/dist/commands/status.js +8 -2
  55. package/dist/commands/subagents.js +3 -1
  56. package/dist/commands/sync.js +1 -1
  57. package/dist/commands/teams.js +11 -2
  58. package/dist/commands/uninstall.d.ts +11 -0
  59. package/dist/commands/uninstall.js +158 -0
  60. package/dist/commands/utils.d.ts +29 -0
  61. package/dist/commands/utils.js +24 -0
  62. package/dist/commands/versions.js +120 -11
  63. package/dist/index.js +83 -12
  64. package/dist/lib/activity.d.ts +124 -0
  65. package/dist/lib/activity.js +542 -0
  66. package/dist/lib/agents.js +16 -5
  67. package/dist/lib/artifact-actions.d.ts +1 -1
  68. package/dist/lib/artifact-actions.js +1 -1
  69. package/dist/lib/ask-classifier.d.ts +2 -1
  70. package/dist/lib/ask-classifier.js +3 -3
  71. package/dist/lib/auth-health.d.ts +32 -4
  72. package/dist/lib/auth-health.js +40 -1
  73. package/dist/lib/auto-pull.js +8 -1
  74. package/dist/lib/brand.d.ts +40 -0
  75. package/dist/lib/brand.js +122 -0
  76. package/dist/lib/browser/drivers/ssh.js +4 -7
  77. package/dist/lib/browser/service.js +6 -8
  78. package/dist/lib/channels/providers/index.d.ts +2 -0
  79. package/dist/lib/channels/providers/index.js +20 -0
  80. package/dist/lib/channels/providers/mailbox.d.ts +2 -0
  81. package/dist/lib/channels/providers/mailbox.js +26 -0
  82. package/dist/lib/channels/providers/openclaw-telegram.d.ts +2 -0
  83. package/dist/lib/channels/providers/openclaw-telegram.js +34 -0
  84. package/dist/lib/channels/providers/rush.d.ts +6 -0
  85. package/dist/lib/channels/providers/rush.js +55 -0
  86. package/dist/lib/channels/registry.d.ts +42 -0
  87. package/dist/lib/channels/registry.js +20 -0
  88. package/dist/lib/channels/resolve.d.ts +11 -0
  89. package/dist/lib/channels/resolve.js +12 -0
  90. package/dist/lib/cli-resources.d.ts +18 -0
  91. package/dist/lib/cli-resources.js +53 -4
  92. package/dist/lib/cloud/codex.js +6 -3
  93. package/dist/lib/cloud/factory.d.ts +1 -0
  94. package/dist/lib/cloud/factory.js +10 -4
  95. package/dist/lib/cloud/rush.d.ts +3 -1
  96. package/dist/lib/cloud/rush.js +6 -1
  97. package/dist/lib/cloud/types.d.ts +2 -0
  98. package/dist/lib/codex-home.d.ts +35 -0
  99. package/dist/lib/codex-home.js +136 -0
  100. package/dist/lib/crabbox/cli.d.ts +44 -0
  101. package/dist/lib/crabbox/cli.js +120 -20
  102. package/dist/lib/crabbox/lease.d.ts +52 -1
  103. package/dist/lib/crabbox/lease.js +117 -16
  104. package/dist/lib/crabbox/progress.d.ts +31 -0
  105. package/dist/lib/crabbox/progress.js +76 -0
  106. package/dist/lib/crabbox/runtimes.d.ts +4 -0
  107. package/dist/lib/crabbox/runtimes.js +33 -8
  108. package/dist/lib/crabbox/setup-copy.d.ts +87 -0
  109. package/dist/lib/crabbox/setup-copy.js +127 -0
  110. package/dist/lib/daemon.d.ts +14 -5
  111. package/dist/lib/daemon.js +52 -9
  112. package/dist/lib/drive-sync.js +6 -3
  113. package/dist/lib/event-stream.d.ts +36 -0
  114. package/dist/lib/event-stream.js +68 -0
  115. package/dist/lib/events.d.ts +1 -1
  116. package/dist/lib/exec.d.ts +11 -0
  117. package/dist/lib/exec.js +23 -2
  118. package/dist/lib/feed-policy.d.ts +1 -1
  119. package/dist/lib/feed-policy.js +14 -9
  120. package/dist/lib/feed-ranking.d.ts +32 -0
  121. package/dist/lib/feed-ranking.js +224 -0
  122. package/dist/lib/feed.d.ts +35 -2
  123. package/dist/lib/feed.js +66 -3
  124. package/dist/lib/fleet/auth-sync.d.ts +53 -0
  125. package/dist/lib/fleet/auth-sync.js +92 -0
  126. package/dist/lib/fleet/remote-login.d.ts +187 -0
  127. package/dist/lib/fleet/remote-login.js +556 -0
  128. package/dist/lib/format.d.ts +30 -3
  129. package/dist/lib/format.js +34 -5
  130. package/dist/lib/fs-atomic.d.ts +7 -2
  131. package/dist/lib/fs-atomic.js +8 -12
  132. package/dist/lib/git.d.ts +16 -0
  133. package/dist/lib/git.js +79 -2
  134. package/dist/lib/heal.js +11 -3
  135. package/dist/lib/hosts/logs.d.ts +12 -0
  136. package/dist/lib/hosts/logs.js +18 -0
  137. package/dist/lib/hosts/progress.d.ts +28 -10
  138. package/dist/lib/hosts/progress.js +79 -22
  139. package/dist/lib/hosts/remote-cmd.js +4 -0
  140. package/dist/lib/hq/floor.d.ts +87 -0
  141. package/dist/lib/hq/floor.js +226 -0
  142. package/dist/lib/menubar/install-menubar.js +14 -20
  143. package/dist/lib/notify.d.ts +1 -0
  144. package/dist/lib/notify.js +3 -3
  145. package/dist/lib/open-url.d.ts +2 -0
  146. package/dist/lib/open-url.js +19 -0
  147. package/dist/lib/openclaw-keychain.d.ts +56 -0
  148. package/dist/lib/openclaw-keychain.js +236 -0
  149. package/dist/lib/overdue.js +10 -0
  150. package/dist/lib/permissions.d.ts +44 -1
  151. package/dist/lib/permissions.js +284 -7
  152. package/dist/lib/project-launch.d.ts +6 -12
  153. package/dist/lib/project-launch.js +13 -228
  154. package/dist/lib/project-resources.d.ts +7 -0
  155. package/dist/lib/project-resources.js +291 -0
  156. package/dist/lib/pty-client.d.ts +27 -0
  157. package/dist/lib/pty-client.js +136 -10
  158. package/dist/lib/refresh.js +14 -10
  159. package/dist/lib/resource-profiles.d.ts +26 -0
  160. package/dist/lib/resource-profiles.js +157 -0
  161. package/dist/lib/resources/permissions.js +7 -1
  162. package/dist/lib/resources/types.d.ts +1 -1
  163. package/dist/lib/resources.d.ts +1 -1
  164. package/dist/lib/resources.js +32 -3
  165. package/dist/lib/routines.d.ts +11 -0
  166. package/dist/lib/routines.js +56 -15
  167. package/dist/lib/runner.d.ts +1 -0
  168. package/dist/lib/runner.js +66 -5
  169. package/dist/lib/secrets/bundles.d.ts +16 -3
  170. package/dist/lib/secrets/bundles.js +206 -37
  171. package/dist/lib/secrets/icloud-import.d.ts +2 -2
  172. package/dist/lib/secrets/icloud-import.js +9 -6
  173. package/dist/lib/secrets/mcp.js +1 -1
  174. package/dist/lib/secrets/vault-age-helper.d.ts +1 -0
  175. package/dist/lib/secrets/vault-age-helper.js +34 -0
  176. package/dist/lib/secrets/vault.d.ts +49 -0
  177. package/dist/lib/secrets/vault.js +397 -0
  178. package/dist/lib/self-heal/checks/path.js +3 -1
  179. package/dist/lib/self-heal/checks/shadowing.js +10 -2
  180. package/dist/lib/self-heal/checks/shims.js +19 -11
  181. package/dist/lib/session/cloud.d.ts +2 -2
  182. package/dist/lib/session/cloud.js +2 -2
  183. package/dist/lib/session/db.d.ts +4 -0
  184. package/dist/lib/session/db.js +44 -7
  185. package/dist/lib/session/discover.d.ts +2 -0
  186. package/dist/lib/session/discover.js +114 -3
  187. package/dist/lib/session/stream-render.d.ts +3 -0
  188. package/dist/lib/session/stream-render.js +130 -0
  189. package/dist/lib/session/types.d.ts +6 -0
  190. package/dist/lib/share/analytics.d.ts +13 -0
  191. package/dist/lib/share/analytics.js +45 -0
  192. package/dist/lib/share/config.d.ts +19 -5
  193. package/dist/lib/share/config.js +44 -8
  194. package/dist/lib/share/provision.d.ts +58 -6
  195. package/dist/lib/share/provision.js +97 -22
  196. package/dist/lib/share/publish.d.ts +36 -13
  197. package/dist/lib/share/publish.js +55 -8
  198. package/dist/lib/share/worker-template.js +83 -17
  199. package/dist/lib/shims.d.ts +36 -0
  200. package/dist/lib/shims.js +120 -16
  201. package/dist/lib/ssh-exec.d.ts +14 -0
  202. package/dist/lib/ssh-exec.js +57 -0
  203. package/dist/lib/staleness/detectors/hooks.js +25 -1
  204. package/dist/lib/staleness/detectors/permissions.js +66 -0
  205. package/dist/lib/staleness/detectors/workflows.js +20 -0
  206. package/dist/lib/staleness/writers/hooks.js +58 -4
  207. package/dist/lib/staleness/writers/permissions.js +2 -2
  208. package/dist/lib/staleness/writers/skills.js +1 -1
  209. package/dist/lib/staleness/writers/sources.d.ts +10 -1
  210. package/dist/lib/staleness/writers/sources.js +68 -1
  211. package/dist/lib/star-nudge.d.ts +45 -0
  212. package/dist/lib/star-nudge.js +91 -0
  213. package/dist/lib/startup/command-registry.d.ts +7 -1
  214. package/dist/lib/startup/command-registry.js +19 -3
  215. package/dist/lib/state.d.ts +2 -0
  216. package/dist/lib/state.js +3 -0
  217. package/dist/lib/subagents-registry.d.ts +2 -0
  218. package/dist/lib/subagents-registry.js +3 -0
  219. package/dist/lib/teams/parsers.js +214 -6
  220. package/dist/lib/teams/supervisor.d.ts +7 -0
  221. package/dist/lib/teams/supervisor.js +2 -1
  222. package/dist/lib/template.d.ts +1 -1
  223. package/dist/lib/template.js +1 -1
  224. package/dist/lib/triggers/webhook.js +36 -3
  225. package/dist/lib/types.d.ts +73 -0
  226. package/dist/lib/uninstall.d.ts +84 -0
  227. package/dist/lib/uninstall.js +347 -0
  228. package/dist/lib/usage.d.ts +13 -1
  229. package/dist/lib/usage.js +32 -14
  230. package/dist/lib/version.d.ts +40 -0
  231. package/dist/lib/version.js +94 -16
  232. package/dist/lib/versions.d.ts +27 -0
  233. package/dist/lib/versions.js +248 -79
  234. package/dist/lib/workflows.d.ts +2 -0
  235. package/dist/lib/workflows.js +88 -0
  236. package/package.json +2 -1
  237. package/dist/commands/daemon.d.ts +0 -10
  238. package/dist/commands/daemon.js +0 -121
@@ -8,18 +8,12 @@
8
8
  * (+ per-agent symlinks). Delegates to compileRulesForProject, which is
9
9
  * the same helper management-side `agents sync` uses.
10
10
  *
11
- * 2. Mirror project resources from `<cwd>/.agents/{subagents,commands,skills}`
12
- * into the agent's workspace-local discovery dirs (`<cwd>/.claude/agents/`,
13
- * `<cwd>/.claude/commands/`, `<cwd>/.claude/skills/`). File-level symlinks
14
- * so edits in the source dir are live without re-sync. Skips any dest
15
- * entry that already exists and isn't one of our symlinks (don't-clobber).
16
- * v1: claude-only. Other agents have varying workspace conventions (amp
17
- * uses `~/.config/amp`, antigravity uses `~/.gemini/antigravity-cli`,
18
- * codex/gemini/cursor lack subagent support entirely) — they're follow-up
19
- * material. NOTE: `.mcp.json` is intentionally NOT auto-symlinked from
20
- * the launch path — that's a supply-chain surface (cloning a hostile
21
- * repo would auto-register an attacker MCP server). Belongs to full
22
- * `agents sync` with explicit opt-in, not the hot path.
11
+ * 2. Copy project resources from `<cwd>/.agents/{commands,skills,subagents,workflows}`
12
+ * into the agent's workspace-local discovery dir (`<cwd>/.claude/`,
13
+ * `<cwd>/.codex/`, etc.) with an ownership manifest at
14
+ * `<cwd>/.{agent}/.agents-managed.json`. The manifest is the only clobber
15
+ * authority: paths it lists are removed and refreshed, pre-existing paths it
16
+ * does not list are skipped as user-owned.
23
17
  *
24
18
  * 3. Synthesize four scope-grouped plugin marketplaces under the version's
25
19
  * `<versionHome>/.{agent}/plugins/marketplaces/` (for plugin-capable agents):
@@ -48,8 +42,8 @@ import { supports } from './capabilities.js';
48
42
  import { getEnabledExtraRepos, getExtraPluginsDir, getPluginsDir, getProjectAgentsDir, getProjectPluginsDir, getSystemPluginsDir, } from './state.js';
49
43
  import { getVersionHomePath } from './versions.js';
50
44
  import { toPortableKey } from './platform/index.js';
51
- import { transformSubagentForClaude } from './subagents.js';
52
45
  import { compileRulesForProject } from './rules/compile.js';
46
+ import { syncProjectResourcesToAgent } from './project-resources.js';
53
47
  import { discoverPluginsInDir, hasPluginExecSurfaces, inspectPluginCapabilities } from './plugins.js';
54
48
  import { MARKETPLACE_NAME, PROJECT_MARKETPLACE_NAME, SYSTEM_MARKETPLACE_NAME, addPluginToSettings, copyPluginToMarketplace, marketplaceNameFor, marketplaceRoot, pluginInstallDir, registerMarketplace, removePluginFromSettings, syncMarketplaceManifest, } from './plugin-marketplace.js';
55
49
  /**
@@ -77,9 +71,12 @@ export function runLaunchSync(opts) {
77
71
  // Don't fail launch on a malformed project rules.yaml.
78
72
  }
79
73
  // Step 2: workspace resource mirror
80
- const mirror = mirrorWorkspaceResources(opts.cwd, opts.agent);
81
- result.workspaceLinks = mirror.links;
82
- result.workspaceSkipped = mirror.skipped;
74
+ const projectAgentsDir = getProjectAgentsDir(opts.cwd);
75
+ if (projectAgentsDir) {
76
+ const projectResources = syncProjectResourcesToAgent(opts.agent, opts.version, projectAgentsDir);
77
+ result.workspaceLinks = projectResources.synced.length;
78
+ result.workspaceSkipped = projectResources.skipped;
79
+ }
83
80
  // Step 3: scoped plugin marketplaces
84
81
  result.marketplaces = synthesizeScopedMarketplaces(opts.agent, opts.version, opts.cwd);
85
82
  // Touch the shim's skip-fast sentinel. Best-effort — if this fails the
@@ -117,218 +114,6 @@ function touchLaunchSentinel(agent, version, cwd) {
117
114
  // best-effort
118
115
  }
119
116
  }
120
- const CLAUDE_MIRROR_PLANS = [
121
- { srcSubdir: 'subagents', destSubdir: 'agents', mode: 'subagent-write' },
122
- { srcSubdir: 'commands', destSubdir: 'commands', mode: 'file-symlink' },
123
- { srcSubdir: 'skills', destSubdir: 'skills', mode: 'dir-symlink' },
124
- ];
125
- /**
126
- * Marker prepended-as-trailing-comment to every subagent file WE generate.
127
- * It's an HTML comment — invisible to the markdown the agent reads — placed on
128
- * the last line so it never disturbs the leading `---` frontmatter block.
129
- *
130
- * Ownership rule (the one don't-clobber decision for written, non-symlink
131
- * files): we only overwrite a `.claude/agents/<name>.md` whose content carries
132
- * this marker. A user-authored file (no marker) or a symlink at the dest is
133
- * left untouched. A marker beats an mtime/sidecar sentinel because it travels
134
- * with the file across copies and git, and needs no out-of-band state.
135
- */
136
- const GENERATED_SUBAGENT_MARKER = '<!-- agents-cli:generated-subagent';
137
- function mirrorWorkspaceResources(cwd, agent) {
138
- // v1: claude-only. Other agents have workspace conventions we haven't
139
- // mapped (amp: ~/.config/amp; antigravity: ~/.gemini/antigravity-cli;
140
- // codex/gemini/cursor: no subagent support per capabilities). Adding them
141
- // requires per-agent workspaceDirName + capability gates — follow-up.
142
- if (agent !== 'claude')
143
- return { links: 0, skipped: [] };
144
- const projectAgentsDir = getProjectAgentsDir(cwd);
145
- if (!projectAgentsDir)
146
- return { links: 0, skipped: [] };
147
- const agentWorkspaceDir = path.join(cwd, '.claude');
148
- const projectAgentsResolved = (() => {
149
- try {
150
- return fs.realpathSync(projectAgentsDir);
151
- }
152
- catch {
153
- return path.resolve(projectAgentsDir);
154
- }
155
- })();
156
- let links = 0;
157
- const skipped = [];
158
- // Mirror subagents / commands / skills. mcp.json is intentionally excluded
159
- // — see header doc, it's a supply-chain surface.
160
- for (const plan of CLAUDE_MIRROR_PLANS) {
161
- const srcDir = path.join(projectAgentsDir, plan.srcSubdir);
162
- if (!fs.existsSync(srcDir))
163
- continue;
164
- const destDir = path.join(agentWorkspaceDir, plan.destSubdir);
165
- fs.mkdirSync(destDir, { recursive: true });
166
- // Subagents flatten N source files into one written .md — not a symlink.
167
- if (plan.mode === 'subagent-write') {
168
- const r = writeProjectSubagents(srcDir, destDir, cwd);
169
- links += r.links;
170
- skipped.push(...r.skipped);
171
- continue;
172
- }
173
- const entries = fs.readdirSync(srcDir, { withFileTypes: true });
174
- for (const entry of entries) {
175
- if (entry.name.startsWith('.'))
176
- continue;
177
- if (plan.mode === 'dir-symlink' && !entry.isDirectory())
178
- continue;
179
- if (plan.mode === 'file-symlink' && !entry.isFile() && !entry.isSymbolicLink())
180
- continue;
181
- const srcPath = path.join(srcDir, entry.name);
182
- const destPath = path.join(destDir, entry.name);
183
- if (replaceWithSymlinkIfOwned(srcPath, destPath, projectAgentsResolved)) {
184
- links += 1;
185
- }
186
- else {
187
- skipped.push(path.relative(cwd, destPath));
188
- }
189
- }
190
- }
191
- return { links, skipped };
192
- }
193
- /**
194
- * Mirror project subagents into `<cwd>/.claude/agents/`. The canonical source
195
- * shape is a DIRECTORY containing AGENT.md (e.g. `.agents/subagents/probe/AGENT.md`)
196
- * — confirmed by the detector (versions.ts) and lister (subagents.ts). Each
197
- * such directory is flattened via transformSubagentForClaude (the exact writer
198
- * the version-home sync uses) into a single `<name>.md`, then written under an
199
- * ownership marker so a re-launch refreshes our file but never clobbers a
200
- * user-authored one.
201
- *
202
- * Returns the same {links, skipped} shape the symlink path reports, so the
203
- * caller's accounting is uniform across resource kinds.
204
- */
205
- function writeProjectSubagents(srcDir, destDir, cwd) {
206
- let links = 0;
207
- const skipped = [];
208
- for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
209
- if (entry.name.startsWith('.'))
210
- continue;
211
- if (!entry.isDirectory())
212
- continue;
213
- const subagentDir = path.join(srcDir, entry.name);
214
- if (!fs.existsSync(path.join(subagentDir, 'AGENT.md')))
215
- continue;
216
- const destPath = path.join(destDir, `${entry.name}.md`);
217
- if (writeSubagentIfOwned(subagentDir, destPath)) {
218
- links += 1;
219
- }
220
- else {
221
- skipped.push(path.relative(cwd, destPath));
222
- }
223
- }
224
- return { links, skipped };
225
- }
226
- /**
227
- * Write a flattened subagent file at `destPath`, refusing to clobber user state.
228
- *
229
- * - dest missing → write fresh.
230
- * - dest is our generation → overwrite (refresh; carries GENERATED_SUBAGENT_MARKER).
231
- * - dest is a symlink / any
232
- * non-regular file → SKIP (user state we don't own).
233
- * - dest is a regular file
234
- * without our marker → SKIP (hand-authored .claude/agents/<name>.md).
235
- *
236
- * Returns true when our file is present (written now or already current),
237
- * false when we left a user-owned dest alone.
238
- */
239
- function writeSubagentIfOwned(subagentDir, destPath) {
240
- let existing = null;
241
- let destLstat = null;
242
- try {
243
- destLstat = fs.lstatSync(destPath);
244
- }
245
- catch { /* missing — write fresh */ }
246
- if (destLstat) {
247
- if (!destLstat.isFile())
248
- return false; // symlink/dir/etc. — user state
249
- try {
250
- existing = fs.readFileSync(destPath, 'utf-8');
251
- }
252
- catch {
253
- return false;
254
- }
255
- if (!existing.includes(GENERATED_SUBAGENT_MARKER))
256
- return false; // hand-authored
257
- }
258
- let body;
259
- try {
260
- body = transformSubagentForClaude(subagentDir);
261
- }
262
- catch {
263
- return false; // malformed AGENT.md — don't write a broken file
264
- }
265
- const content = `${body}\n\n${GENERATED_SUBAGENT_MARKER} — edit .agents/subagents/${path.basename(subagentDir)}/ instead -->\n`;
266
- // Skip-fast: identical content already on disk → no write (keeps mtime stable).
267
- if (existing === content)
268
- return true;
269
- try {
270
- fs.mkdirSync(path.dirname(destPath), { recursive: true });
271
- fs.writeFileSync(destPath, content);
272
- }
273
- catch {
274
- return false;
275
- }
276
- return true;
277
- }
278
- /**
279
- * Create or refresh a symlink at `destPath` pointing at `srcPath`. Returns
280
- * true if we wrote (or already had) the link, false if we skipped because
281
- * the destination is user-owned (regular file, directory, or symlink pointing
282
- * outside the project's `.agents/` tree, or a dangling symlink — treated as
283
- * user state we don't yet understand).
284
- *
285
- * Skip-fast: if the destination is already a symlink resolving to the
286
- * project-agents tree AND its target matches srcPath, no write happens.
287
- */
288
- function replaceWithSymlinkIfOwned(srcPath, destPath, projectAgentsResolved) {
289
- let destLstat = null;
290
- try {
291
- destLstat = fs.lstatSync(destPath);
292
- }
293
- catch { /* missing — write fresh */ }
294
- if (destLstat) {
295
- if (!destLstat.isSymbolicLink()) {
296
- return false;
297
- }
298
- let destTargetReal = null;
299
- try {
300
- destTargetReal = fs.realpathSync(destPath);
301
- }
302
- catch { /* dangling */ }
303
- // Dangling symlink → user-owned in-progress state; do not clobber.
304
- if (destTargetReal === null) {
305
- return false;
306
- }
307
- if (destTargetReal !== projectAgentsResolved && !destTargetReal.startsWith(projectAgentsResolved + path.sep)) {
308
- return false;
309
- }
310
- let srcReal = null;
311
- try {
312
- srcReal = fs.realpathSync(srcPath);
313
- }
314
- catch { /* src vanished mid-launch */ }
315
- if (srcReal !== null && destTargetReal === srcReal) {
316
- return true; // already correct
317
- }
318
- try {
319
- fs.unlinkSync(destPath);
320
- }
321
- catch { /* ignore */ }
322
- }
323
- try {
324
- fs.mkdirSync(path.dirname(destPath), { recursive: true });
325
- fs.symlinkSync(srcPath, destPath);
326
- }
327
- catch {
328
- return false;
329
- }
330
- return true;
331
- }
332
117
  function makeScope(spec, pluginsDir, autoEnableExecSurfaces, precedence) {
333
118
  return { spec, marketplaceName: marketplaceNameFor(spec), pluginsDir, autoEnableExecSurfaces, precedence };
334
119
  }
@@ -0,0 +1,7 @@
1
+ import type { AgentId } from './types.js';
2
+ export interface ProjectResourceSyncResult {
3
+ synced: string[];
4
+ skipped: string[];
5
+ }
6
+ export declare function projectAgentRoot(projectRoot: string, agent: AgentId): string;
7
+ export declare function syncProjectResourcesToAgent(agent: AgentId, version: string, projectAgentsDir: string): ProjectResourceSyncResult;
@@ -0,0 +1,291 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { AGENTS, agentConfigDirName } from './agents.js';
4
+ import { supports } from './capabilities.js';
5
+ import { buildCommandSkillContent, commandSkillName, readSkillSourceCommandMarker, shouldInstallCommandAsSkill } from './command-skills.js';
6
+ import { commandAppliesTo, parseCommandMetadata } from './commands.js';
7
+ import { markdownToToml } from './convert.js';
8
+ import { safeJoin } from './paths.js';
9
+ import { subagentTarget } from './subagents-registry.js';
10
+ import { parseSubagentFrontmatter } from './subagents.js';
11
+ import { syncWorkflowToVersion } from './workflows.js';
12
+ const MANIFEST_FILE = '.agents-managed.json';
13
+ const MANIFEST_VERSION = 1;
14
+ const COPY_IGNORE = new Set(['.DS_Store', '.git', '.gitignore', '.venv', '__pycache__', 'node_modules']);
15
+ export function projectAgentRoot(projectRoot, agent) {
16
+ return path.join(projectRoot, agentConfigDirName(agent));
17
+ }
18
+ export function syncProjectResourcesToAgent(agent, version, projectAgentsDir) {
19
+ const projectRoot = path.dirname(projectAgentsDir);
20
+ const agentRoot = projectAgentRoot(projectRoot, agent);
21
+ const manifest = loadProjectManifest(agentRoot);
22
+ const result = { synced: [], skipped: [] };
23
+ const next = new Set();
24
+ if (manifest) {
25
+ for (const rel of manifest.paths)
26
+ removeManagedPath(agentRoot, rel);
27
+ }
28
+ syncProjectCommands(agent, version, projectAgentsDir, agentRoot, result, next);
29
+ syncProjectSkills(agent, version, projectAgentsDir, agentRoot, result, next);
30
+ syncProjectSubagents(agent, version, projectAgentsDir, projectRoot, agentRoot, manifest, result, next);
31
+ syncProjectWorkflows(agent, version, projectAgentsDir, projectRoot, agentRoot, result, next);
32
+ if (next.size > 0 || manifest) {
33
+ writeProjectManifest(agentRoot, Array.from(next).sort());
34
+ }
35
+ return result;
36
+ }
37
+ function loadProjectManifest(agentRoot) {
38
+ try {
39
+ const raw = JSON.parse(fs.readFileSync(path.join(agentRoot, MANIFEST_FILE), 'utf-8'));
40
+ if (raw.v !== MANIFEST_VERSION || !Array.isArray(raw.paths))
41
+ return null;
42
+ if (!raw.paths.every((p) => typeof p === 'string' && p.length > 0))
43
+ return null;
44
+ return raw;
45
+ }
46
+ catch {
47
+ return null;
48
+ }
49
+ }
50
+ function writeProjectManifest(agentRoot, paths) {
51
+ fs.mkdirSync(agentRoot, { recursive: true });
52
+ const p = path.join(agentRoot, MANIFEST_FILE);
53
+ const tmp = p + '.tmp';
54
+ fs.writeFileSync(tmp, JSON.stringify({ v: MANIFEST_VERSION, paths }, null, 2));
55
+ fs.renameSync(tmp, p);
56
+ }
57
+ function removeManagedPath(agentRoot, rel) {
58
+ if (path.isAbsolute(rel) || rel.includes('..'))
59
+ return;
60
+ const target = path.resolve(agentRoot, rel);
61
+ const root = path.resolve(agentRoot);
62
+ if (target !== root && !target.startsWith(root + path.sep))
63
+ return;
64
+ removePath(target);
65
+ }
66
+ function removePath(p) {
67
+ try {
68
+ const st = fs.lstatSync(p);
69
+ if (st.isSymbolicLink() || st.isFile())
70
+ fs.unlinkSync(p);
71
+ else if (st.isDirectory())
72
+ fs.rmSync(p, { recursive: true, force: true });
73
+ }
74
+ catch {
75
+ // already absent
76
+ }
77
+ }
78
+ function pathExists(p) {
79
+ try {
80
+ fs.lstatSync(p);
81
+ return true;
82
+ }
83
+ catch {
84
+ return false;
85
+ }
86
+ }
87
+ function copyDir(src, dest) {
88
+ fs.mkdirSync(dest, { recursive: true });
89
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
90
+ if (entry.isSymbolicLink() || COPY_IGNORE.has(entry.name))
91
+ continue;
92
+ const s = safeJoin(src, entry.name);
93
+ const d = safeJoin(dest, entry.name);
94
+ if (entry.isDirectory())
95
+ copyDir(s, d);
96
+ else if (entry.isFile())
97
+ fs.copyFileSync(s, d);
98
+ }
99
+ }
100
+ function projectEntries(projectAgentsDir, kind) {
101
+ const dir = path.join(projectAgentsDir, kind);
102
+ try {
103
+ return fs.readdirSync(dir, { withFileTypes: true }).filter((entry) => !entry.name.startsWith('.'));
104
+ }
105
+ catch {
106
+ return [];
107
+ }
108
+ }
109
+ function record(kind, name, relPaths, result, manifestPaths) {
110
+ result.synced.push(`${kind}/${name}`);
111
+ for (const rel of relPaths)
112
+ manifestPaths.add(rel);
113
+ }
114
+ function skip(dest, projectRoot, result) {
115
+ const rel = path.relative(projectRoot, dest);
116
+ result.skipped.push(rel);
117
+ console.warn(`Skipping project resource target ${rel}: already exists and is user-owned`);
118
+ }
119
+ function syncProjectCommands(agent, version, projectAgentsDir, agentRoot, result, manifestPaths) {
120
+ const cfg = AGENTS[agent];
121
+ const commandsAsSkills = shouldInstallCommandAsSkill(agent, version);
122
+ const supportsCommands = supports(agent, 'commands', version).ok;
123
+ if (!commandsAsSkills && !supportsCommands)
124
+ return;
125
+ const projectRoot = path.dirname(projectAgentsDir);
126
+ for (const entry of projectEntries(projectAgentsDir, 'commands')) {
127
+ if (!entry.isFile() || !entry.name.endsWith('.md'))
128
+ continue;
129
+ const name = entry.name.slice(0, -'.md'.length);
130
+ const srcFile = path.join(projectAgentsDir, 'commands', entry.name);
131
+ const metadata = parseCommandMetadata(srcFile);
132
+ if (!commandAppliesTo(agent, version, metadata).ok)
133
+ continue;
134
+ if (commandsAsSkills) {
135
+ const sourceMarker = readSkillSourceCommandMarker(name, [path.join(projectAgentsDir, 'skills')]);
136
+ if (pathExists(path.join(projectAgentsDir, 'skills', name)) && sourceMarker !== name)
137
+ continue;
138
+ const skillName = commandSkillName(name);
139
+ const rel = path.join('skills', skillName);
140
+ const destDir = path.join(agentRoot, rel);
141
+ if (pathExists(destDir)) {
142
+ skip(destDir, projectRoot, result);
143
+ continue;
144
+ }
145
+ fs.mkdirSync(destDir, { recursive: true });
146
+ fs.writeFileSync(path.join(destDir, 'SKILL.md'), buildCommandSkillContent(name, srcFile), 'utf-8');
147
+ record('commands', name, [rel], result, manifestPaths);
148
+ continue;
149
+ }
150
+ const ext = cfg.format === 'toml' ? '.toml' : '.md';
151
+ const rel = path.join(cfg.commandsSubdir, `${name}${ext}`);
152
+ const destFile = path.join(agentRoot, rel);
153
+ if (pathExists(destFile)) {
154
+ skip(destFile, projectRoot, result);
155
+ continue;
156
+ }
157
+ fs.mkdirSync(path.dirname(destFile), { recursive: true });
158
+ if (cfg.format === 'toml') {
159
+ fs.writeFileSync(destFile, markdownToToml(name, fs.readFileSync(srcFile, 'utf-8')), 'utf-8');
160
+ }
161
+ else {
162
+ fs.copyFileSync(srcFile, destFile);
163
+ }
164
+ record('commands', name, [rel], result, manifestPaths);
165
+ }
166
+ }
167
+ function syncProjectSkills(agent, version, projectAgentsDir, agentRoot, result, manifestPaths) {
168
+ if (!supports(agent, 'skills', version).ok)
169
+ return;
170
+ const projectRoot = path.dirname(projectAgentsDir);
171
+ for (const entry of projectEntries(projectAgentsDir, 'skills')) {
172
+ if (!entry.isDirectory())
173
+ continue;
174
+ const srcDir = path.join(projectAgentsDir, 'skills', entry.name);
175
+ if (!fs.existsSync(path.join(srcDir, 'SKILL.md')))
176
+ continue;
177
+ const rel = path.join('skills', entry.name);
178
+ const destDir = path.join(agentRoot, rel);
179
+ if (pathExists(destDir)) {
180
+ skip(destDir, projectRoot, result);
181
+ continue;
182
+ }
183
+ copyDir(srcDir, destDir);
184
+ record('skills', entry.name, [rel], result, manifestPaths);
185
+ }
186
+ }
187
+ function readProjectSubagents(projectAgentsDir) {
188
+ const map = new Map();
189
+ for (const entry of projectEntries(projectAgentsDir, 'subagents')) {
190
+ if (!entry.isDirectory())
191
+ continue;
192
+ const dir = path.join(projectAgentsDir, 'subagents', entry.name);
193
+ const agentMd = path.join(dir, 'AGENT.md');
194
+ if (!fs.existsSync(agentMd))
195
+ continue;
196
+ const frontmatter = parseSubagentFrontmatter(agentMd);
197
+ if (!frontmatter)
198
+ continue;
199
+ const files = fs.readdirSync(dir).filter((f) => f.endsWith('.md')).sort();
200
+ map.set(entry.name, { name: entry.name, path: dir, files, frontmatter });
201
+ }
202
+ return map;
203
+ }
204
+ function syncProjectSubagents(agent, version, projectAgentsDir, projectRoot, agentRoot, manifest, result, manifestPaths) {
205
+ if (!supports(agent, 'subagents', version).ok)
206
+ return;
207
+ const target = subagentTarget(agent);
208
+ if (!target)
209
+ return;
210
+ const all = readProjectSubagents(projectAgentsDir);
211
+ const dir = target.dir(projectRoot);
212
+ const targetDirRel = path.relative(agentRoot, dir);
213
+ const hadManagedTarget = manifest?.paths.some((rel) => rel === targetDirRel || rel.startsWith(targetDirRel + path.sep)) ?? false;
214
+ for (const sub of all.values()) {
215
+ const occupied = target.occupied(dir, sub.name);
216
+ const existing = occupied.find((entry) => pathExists(entry.path));
217
+ if (existing) {
218
+ skip(existing.path, projectRoot, result);
219
+ continue;
220
+ }
221
+ try {
222
+ target.write(dir, sub);
223
+ record('subagents', sub.name, occupied.map((entry) => path.relative(agentRoot, entry.path)), result, manifestPaths);
224
+ }
225
+ catch {
226
+ // Malformed source or unsupported transform; skip this item.
227
+ }
228
+ }
229
+ const syncedNames = result.synced
230
+ .filter((name) => name.startsWith('subagents/'))
231
+ .map((name) => name.slice('subagents/'.length))
232
+ .filter((name) => all.has(name))
233
+ .map((name) => all.get(name));
234
+ if (target.finalize && (syncedNames.length > 0 || hadManagedTarget)) {
235
+ target.finalize(dir, syncedNames);
236
+ for (const entry of target.finalizeOccupied?.(dir) ?? []) {
237
+ manifestPaths.add(path.relative(agentRoot, entry.path));
238
+ }
239
+ }
240
+ }
241
+ function workflowManagedRelPaths(agent, projectRoot, name, workflowDir) {
242
+ if (agent === 'kimi')
243
+ return [path.join('.kimi-code', 'skills', name)];
244
+ if (agent === 'goose') {
245
+ const rels = [path.join('.config', 'goose', 'recipes', `${name}.yaml`)];
246
+ const subagentsDir = path.join(workflowDir, 'subagents');
247
+ let hasSubagents = false;
248
+ try {
249
+ hasSubagents = fs.readdirSync(subagentsDir).some((f) => f.endsWith('.md'));
250
+ }
251
+ catch {
252
+ hasSubagents = false;
253
+ }
254
+ if (hasSubagents)
255
+ rels.push(path.join('.config', 'goose', 'recipes', `${name}.subrecipes`));
256
+ return rels;
257
+ }
258
+ if (agent === 'openclaw')
259
+ return [path.join('.openclaw', 'workflows', `${name}.lobster`)];
260
+ return [path.join(agentConfigDirName(agent), 'workflows', name)];
261
+ }
262
+ function syncProjectWorkflows(agent, version, projectAgentsDir, projectRoot, agentRoot, result, manifestPaths) {
263
+ if (!supports(agent, 'workflows', version).ok)
264
+ return;
265
+ if (agent === 'antigravity')
266
+ return;
267
+ for (const entry of projectEntries(projectAgentsDir, 'workflows')) {
268
+ if (!entry.isDirectory())
269
+ continue;
270
+ const workflowDir = path.join(projectAgentsDir, 'workflows', entry.name);
271
+ if (!fs.existsSync(path.join(workflowDir, 'WORKFLOW.md')))
272
+ continue;
273
+ const rels = workflowManagedRelPaths(agent, projectRoot, entry.name, workflowDir);
274
+ const existing = rels.map((rel) => path.join(projectRoot, rel)).find((dest) => pathExists(dest));
275
+ if (existing) {
276
+ skip(existing, projectRoot, result);
277
+ continue;
278
+ }
279
+ let success = false;
280
+ if (agent === 'kimi' || agent === 'goose' || agent === 'openclaw') {
281
+ success = syncWorkflowToVersion(workflowDir, entry.name, agent, projectRoot).success;
282
+ }
283
+ else {
284
+ copyDir(workflowDir, path.join(agentRoot, 'workflows', entry.name));
285
+ success = true;
286
+ }
287
+ if (success) {
288
+ record('workflows', entry.name, rels.map((rel) => path.relative(agentRoot, path.join(projectRoot, rel))), result, manifestPaths);
289
+ }
290
+ }
291
+ }
@@ -4,6 +4,14 @@
4
4
  * Thin client that connects to the PTY sidecar server over unix socket.
5
5
  * Each call opens a connection, sends a JSON request, reads the JSON response, and closes.
6
6
  */
7
+ export interface ServerSpawnArgs {
8
+ bin: string;
9
+ args: string[];
10
+ }
11
+ export interface ServerExit {
12
+ code: number | null;
13
+ signal: NodeJS.Signals | null;
14
+ }
7
15
  /** JSON response envelope from the PTY server. */
8
16
  export interface PtyResponse {
9
17
  ok: boolean;
@@ -15,6 +23,25 @@ export interface PtyResponse {
15
23
  * Auto-starts the server if not running.
16
24
  */
17
25
  export declare function ptyRequest(action: string, id?: string, params?: Record<string, any>): Promise<PtyResponse>;
26
+ export declare function getServerSpawnArgs(options?: {
27
+ isStandaloneExecutable?: boolean;
28
+ /** Physical binary path (test seam). Defaults to `process.execPath`. */
29
+ execPath?: string;
30
+ /** Locate a real `node` (test seam). Defaults to `which/where node`. */
31
+ resolveNode?: () => string | undefined;
32
+ /** File-existence check (test seam). Defaults to `fs.existsSync`. */
33
+ fileExists?: (p: string) => boolean;
34
+ }): ServerSpawnArgs;
35
+ export declare function isBunStandaloneExecutable(moduleUrl?: string): boolean;
36
+ export declare function readRecentLogLines(logPath: string, maxLines?: number): string[];
37
+ export declare function buildPtyStartFailureMessage(details: {
38
+ timeoutMs: number;
39
+ spawn: ServerSpawnArgs;
40
+ exit?: ServerExit;
41
+ spawnError?: Error;
42
+ lastReadinessError?: Error;
43
+ logPath: string;
44
+ }): string;
18
45
  /**
19
46
  * Parse escape sequences in user input strings.
20
47
  * Handles: \n \r \t \e \xHH \\