@phnx-labs/agents-cli 1.12.0 → 1.14.2

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 (496) hide show
  1. package/CHANGELOG.md +7 -1
  2. package/README.md +308 -297
  3. package/dist/commands/alias.d.ts +11 -0
  4. package/dist/commands/alias.js +117 -0
  5. package/dist/commands/beta.d.ts +2 -0
  6. package/dist/commands/beta.js +53 -0
  7. package/dist/commands/cloud.d.ts +10 -0
  8. package/dist/commands/cloud.js +408 -0
  9. package/dist/commands/commands.d.ts +9 -1
  10. package/dist/commands/commands.js +24 -172
  11. package/dist/commands/daemon.d.ts +8 -1
  12. package/dist/commands/daemon.js +13 -5
  13. package/dist/commands/doctor.d.ts +15 -0
  14. package/dist/commands/doctor.js +132 -0
  15. package/dist/commands/drive.d.ts +8 -1
  16. package/dist/commands/drive.js +20 -3
  17. package/dist/commands/exec.d.ts +8 -1
  18. package/dist/commands/exec.js +207 -20
  19. package/dist/commands/factory.d.ts +19 -0
  20. package/dist/commands/factory.js +71 -0
  21. package/dist/commands/fork.d.ts +8 -1
  22. package/dist/commands/fork.js +11 -4
  23. package/dist/commands/hooks.d.ts +9 -1
  24. package/dist/commands/hooks.js +30 -182
  25. package/dist/commands/init.d.ts +15 -1
  26. package/dist/commands/init.js +168 -74
  27. package/dist/commands/mcp.d.ts +9 -1
  28. package/dist/commands/mcp.js +11 -7
  29. package/dist/commands/models.d.ts +8 -1
  30. package/dist/commands/models.js +45 -6
  31. package/dist/commands/packages.d.ts +8 -1
  32. package/dist/commands/packages.js +13 -7
  33. package/dist/commands/permissions.d.ts +9 -1
  34. package/dist/commands/permissions.js +3 -3
  35. package/dist/commands/plugins.d.ts +8 -1
  36. package/dist/commands/plugins.js +13 -2
  37. package/dist/commands/profiles.d.ts +11 -0
  38. package/dist/commands/profiles.js +291 -0
  39. package/dist/commands/prune.d.ts +22 -0
  40. package/dist/commands/prune.js +191 -0
  41. package/dist/commands/pty.d.ts +1 -1
  42. package/dist/commands/pty.js +2 -1
  43. package/dist/commands/pull.d.ts +8 -1
  44. package/dist/commands/pull.js +93 -129
  45. package/dist/commands/refresh-memory.d.ts +7 -1
  46. package/dist/commands/refresh-memory.js +7 -1
  47. package/dist/commands/repo.d.ts +15 -0
  48. package/dist/commands/repo.js +570 -0
  49. package/dist/commands/resource-view.d.ts +10 -3
  50. package/dist/commands/resource-view.js +18 -5
  51. package/dist/commands/routines.d.ts +8 -1
  52. package/dist/commands/routines.js +17 -4
  53. package/dist/commands/rules.d.ts +9 -1
  54. package/dist/commands/rules.js +16 -11
  55. package/dist/commands/secrets.d.ts +10 -0
  56. package/dist/commands/secrets.js +518 -0
  57. package/dist/commands/sessions-picker.d.ts +2 -1
  58. package/dist/commands/sessions-picker.js +88 -11
  59. package/dist/commands/sessions-tail.d.ts +19 -0
  60. package/dist/commands/sessions-tail.js +235 -0
  61. package/dist/commands/sessions.d.ts +2 -1
  62. package/dist/commands/sessions.js +288 -7
  63. package/dist/commands/skills.d.ts +9 -1
  64. package/dist/commands/skills.js +28 -178
  65. package/dist/commands/status.d.ts +7 -1
  66. package/dist/commands/status.js +7 -1
  67. package/dist/commands/subagents.d.ts +8 -1
  68. package/dist/commands/subagents.js +11 -1
  69. package/dist/commands/sync.d.ts +8 -1
  70. package/dist/commands/sync.js +8 -1
  71. package/dist/commands/teams-picker.d.ts +4 -1
  72. package/dist/commands/teams-picker.js +55 -3
  73. package/dist/commands/teams.d.ts +15 -1
  74. package/dist/commands/teams.js +323 -69
  75. package/dist/commands/usage.d.ts +11 -0
  76. package/dist/commands/usage.js +60 -0
  77. package/dist/commands/utils.d.ts +6 -1
  78. package/dist/commands/utils.js +6 -1
  79. package/dist/commands/versions.d.ts +8 -1
  80. package/dist/commands/versions.js +4 -3
  81. package/dist/commands/view.d.ts +47 -2
  82. package/dist/commands/view.js +353 -20
  83. package/dist/index.d.ts +7 -2
  84. package/dist/index.js +205 -38
  85. package/dist/lib/acp/client.d.ts +31 -0
  86. package/dist/lib/acp/client.js +117 -0
  87. package/dist/lib/acp/harnesses.d.ts +26 -0
  88. package/dist/lib/acp/harnesses.js +65 -0
  89. package/dist/lib/acp/run.d.ts +18 -0
  90. package/dist/lib/acp/run.js +39 -0
  91. package/dist/lib/agents.d.ts +74 -2
  92. package/dist/lib/agents.js +207 -23
  93. package/dist/lib/artifact-actions.d.ts +8 -4
  94. package/dist/lib/artifact-actions.js +8 -6
  95. package/dist/lib/auto-pull-worker.d.ts +11 -0
  96. package/dist/lib/auto-pull-worker.js +121 -0
  97. package/dist/lib/auto-pull.d.ts +31 -0
  98. package/dist/lib/auto-pull.js +97 -0
  99. package/dist/lib/beta.d.ts +23 -0
  100. package/dist/lib/beta.js +90 -0
  101. package/dist/lib/capabilities.d.ts +29 -0
  102. package/dist/lib/capabilities.js +74 -0
  103. package/dist/lib/cloud/codex.d.ts +25 -0
  104. package/dist/lib/cloud/codex.js +250 -0
  105. package/dist/lib/cloud/factory.d.ts +31 -0
  106. package/dist/lib/cloud/factory.js +53 -0
  107. package/dist/lib/cloud/registry.d.ts +15 -0
  108. package/dist/lib/cloud/registry.js +67 -0
  109. package/dist/lib/cloud/rush.d.ts +75 -0
  110. package/dist/lib/cloud/rush.js +438 -0
  111. package/dist/lib/cloud/store.d.ts +22 -0
  112. package/dist/lib/cloud/store.js +115 -0
  113. package/dist/lib/cloud/stream.d.ts +23 -0
  114. package/dist/lib/cloud/stream.js +194 -0
  115. package/dist/lib/cloud/types.d.ts +205 -0
  116. package/dist/lib/cloud/types.js +34 -0
  117. package/dist/lib/command-skills.d.ts +20 -0
  118. package/dist/lib/command-skills.js +142 -0
  119. package/dist/lib/commands.d.ts +22 -2
  120. package/dist/lib/commands.js +51 -11
  121. package/dist/lib/convert.d.ts +10 -1
  122. package/dist/lib/convert.js +9 -1
  123. package/dist/lib/daemon.d.ts +21 -1
  124. package/dist/lib/daemon.js +97 -4
  125. package/dist/lib/drive-sync.d.ts +18 -1
  126. package/dist/lib/drive-sync.js +57 -15
  127. package/dist/lib/exec.d.ts +25 -5
  128. package/dist/lib/exec.js +72 -27
  129. package/dist/lib/fs-walk.d.ts +2 -0
  130. package/dist/lib/fs-walk.js +40 -0
  131. package/dist/lib/fuzzy.d.ts +53 -0
  132. package/dist/lib/fuzzy.js +72 -0
  133. package/dist/lib/gemini-settings.d.ts +4 -0
  134. package/dist/lib/gemini-settings.js +33 -0
  135. package/dist/lib/git.d.ts +12 -2
  136. package/dist/lib/git.js +17 -6
  137. package/dist/lib/help.d.ts +20 -1
  138. package/dist/lib/help.js +45 -6
  139. package/dist/lib/hooks/match.d.ts +32 -0
  140. package/dist/lib/hooks/match.js +120 -0
  141. package/dist/lib/hooks.d.ts +17 -4
  142. package/dist/lib/hooks.js +191 -21
  143. package/dist/lib/manifest.d.ts +6 -1
  144. package/dist/lib/manifest.js +15 -4
  145. package/dist/lib/markdown.d.ts +0 -1
  146. package/dist/lib/markdown.js +6 -1
  147. package/dist/lib/mcp.d.ts +0 -1
  148. package/dist/lib/mcp.js +29 -33
  149. package/dist/lib/memory-compile.d.ts +13 -3
  150. package/dist/lib/memory-compile.js +31 -9
  151. package/dist/lib/memory.d.ts +14 -7
  152. package/dist/lib/memory.js +67 -38
  153. package/dist/lib/migrate.d.ts +8 -0
  154. package/dist/lib/migrate.js +85 -0
  155. package/dist/lib/models.d.ts +25 -11
  156. package/dist/lib/models.js +405 -16
  157. package/dist/lib/onepassword.d.ts +63 -0
  158. package/dist/lib/onepassword.js +186 -0
  159. package/dist/lib/paths.d.ts +8 -0
  160. package/dist/lib/paths.js +20 -0
  161. package/dist/lib/permissions.d.ts +24 -2
  162. package/dist/lib/permissions.js +117 -48
  163. package/dist/lib/picker.d.ts +10 -1
  164. package/dist/lib/picker.js +15 -1
  165. package/dist/lib/plugins.d.ts +7 -1
  166. package/dist/lib/plugins.js +10 -1
  167. package/dist/lib/profiles-presets.d.ts +24 -0
  168. package/dist/lib/profiles-presets.js +103 -0
  169. package/dist/lib/profiles.d.ts +69 -0
  170. package/dist/lib/profiles.js +144 -0
  171. package/dist/lib/pty-client.d.ts +1 -1
  172. package/dist/lib/pty-client.js +0 -1
  173. package/dist/lib/pty-server.d.ts +16 -2
  174. package/dist/lib/pty-server.js +92 -3
  175. package/dist/lib/registry.d.ts +23 -3
  176. package/dist/lib/registry.js +153 -8
  177. package/dist/lib/resources.d.ts +28 -1
  178. package/dist/lib/resources.js +79 -1
  179. package/dist/lib/rotate.d.ts +89 -0
  180. package/dist/lib/rotate.js +327 -0
  181. package/dist/lib/routines.d.ts +29 -1
  182. package/dist/lib/routines.js +32 -5
  183. package/dist/lib/runner.d.ts +14 -1
  184. package/dist/lib/runner.js +22 -3
  185. package/dist/lib/sandbox.d.ts +16 -1
  186. package/dist/lib/sandbox.js +39 -16
  187. package/dist/lib/scheduler.d.ts +8 -1
  188. package/dist/lib/scheduler.js +8 -1
  189. package/dist/lib/secrets/AgentsKeychain.app/Contents/CodeResources +0 -0
  190. package/dist/lib/secrets/AgentsKeychain.app/Contents/Info.plist +22 -0
  191. package/dist/lib/secrets/AgentsKeychain.app/Contents/MacOS/AgentsKeychain +0 -0
  192. package/dist/lib/secrets/AgentsKeychain.app/Contents/_CodeSignature/CodeResources +123 -0
  193. package/dist/lib/secrets/AgentsKeychain.app/Contents/embedded.provisionprofile +0 -0
  194. package/dist/lib/secrets/bundles.d.ts +39 -0
  195. package/dist/lib/secrets/bundles.js +189 -0
  196. package/dist/lib/secrets/index.d.ts +55 -0
  197. package/dist/lib/secrets/index.js +211 -0
  198. package/dist/lib/secrets/profiles.d.ts +10 -0
  199. package/dist/lib/secrets/profiles.js +13 -0
  200. package/dist/lib/session/active.d.ts +43 -0
  201. package/dist/lib/session/active.js +392 -0
  202. package/dist/lib/session/artifacts.d.ts +16 -0
  203. package/dist/lib/session/artifacts.js +95 -0
  204. package/dist/lib/session/cloud.d.ts +30 -0
  205. package/dist/lib/session/cloud.js +121 -0
  206. package/dist/lib/session/db.d.ts +23 -2
  207. package/dist/lib/session/db.js +76 -12
  208. package/dist/lib/session/discover.d.ts +19 -4
  209. package/dist/lib/session/discover.js +344 -48
  210. package/dist/lib/session/parse.d.ts +28 -1
  211. package/dist/lib/session/parse.js +267 -9
  212. package/dist/lib/session/prompt.d.ts +9 -1
  213. package/dist/lib/session/prompt.js +17 -3
  214. package/dist/lib/session/render.d.ts +13 -1
  215. package/dist/lib/session/render.js +20 -1
  216. package/dist/lib/session/team-filter.d.ts +9 -1
  217. package/dist/lib/session/team-filter.js +11 -2
  218. package/dist/lib/session/types.d.ts +24 -2
  219. package/dist/lib/session/types.js +10 -2
  220. package/dist/lib/shims.d.ts +93 -5
  221. package/dist/lib/shims.js +380 -67
  222. package/dist/lib/skills.d.ts +27 -2
  223. package/dist/lib/skills.js +127 -65
  224. package/dist/lib/sqlite.d.ts +43 -0
  225. package/dist/lib/sqlite.js +94 -0
  226. package/dist/lib/state.d.ts +114 -22
  227. package/dist/lib/state.js +323 -138
  228. package/dist/lib/subagents.d.ts +9 -1
  229. package/dist/lib/subagents.js +70 -63
  230. package/dist/lib/sync-manifest.d.ts +81 -0
  231. package/dist/lib/sync-manifest.js +450 -0
  232. package/dist/lib/teams/agents.d.ts +103 -5
  233. package/dist/lib/teams/agents.js +414 -91
  234. package/dist/lib/teams/api.d.ts +26 -3
  235. package/dist/lib/teams/api.js +63 -3
  236. package/dist/lib/teams/debug.d.ts +6 -1
  237. package/dist/lib/teams/debug.js +6 -1
  238. package/dist/lib/teams/file_ops.d.ts +7 -1
  239. package/dist/lib/teams/file_ops.js +7 -1
  240. package/dist/lib/teams/index.d.ts +15 -0
  241. package/dist/lib/teams/index.js +14 -0
  242. package/dist/lib/teams/parsers.d.ts +4 -1
  243. package/dist/lib/teams/parsers.js +11 -1
  244. package/dist/lib/teams/persistence.d.ts +15 -1
  245. package/dist/lib/teams/persistence.js +102 -20
  246. package/dist/lib/teams/registry.d.ts +12 -1
  247. package/dist/lib/teams/registry.js +116 -33
  248. package/dist/lib/teams/summarizer.d.ts +15 -1
  249. package/dist/lib/teams/summarizer.js +14 -1
  250. package/dist/lib/teams/supervisor.d.ts +48 -0
  251. package/dist/lib/teams/supervisor.js +73 -0
  252. package/dist/lib/template.d.ts +8 -6
  253. package/dist/lib/template.js +8 -6
  254. package/dist/lib/types.d.ts +147 -8
  255. package/dist/lib/types.js +26 -3
  256. package/dist/lib/usage.d.ts +48 -1
  257. package/dist/lib/usage.js +97 -16
  258. package/dist/lib/version-duplicates.d.ts +21 -0
  259. package/dist/lib/version-duplicates.js +90 -0
  260. package/dist/lib/versions.d.ts +39 -4
  261. package/dist/lib/versions.js +401 -111
  262. package/package.json +33 -18
  263. package/scripts/postinstall.js +126 -30
  264. package/dist/commands/__tests__/sessions.test.d.ts +0 -2
  265. package/dist/commands/__tests__/sessions.test.d.ts.map +0 -1
  266. package/dist/commands/__tests__/sessions.test.js +0 -636
  267. package/dist/commands/__tests__/sessions.test.js.map +0 -1
  268. package/dist/commands/commands.d.ts.map +0 -1
  269. package/dist/commands/commands.js.map +0 -1
  270. package/dist/commands/daemon.d.ts.map +0 -1
  271. package/dist/commands/daemon.js.map +0 -1
  272. package/dist/commands/drive.d.ts.map +0 -1
  273. package/dist/commands/drive.js.map +0 -1
  274. package/dist/commands/exec.d.ts.map +0 -1
  275. package/dist/commands/exec.js.map +0 -1
  276. package/dist/commands/fork.d.ts.map +0 -1
  277. package/dist/commands/fork.js.map +0 -1
  278. package/dist/commands/hooks.d.ts.map +0 -1
  279. package/dist/commands/hooks.js.map +0 -1
  280. package/dist/commands/init.d.ts.map +0 -1
  281. package/dist/commands/init.js.map +0 -1
  282. package/dist/commands/mcp.d.ts.map +0 -1
  283. package/dist/commands/mcp.js.map +0 -1
  284. package/dist/commands/models.d.ts.map +0 -1
  285. package/dist/commands/models.js.map +0 -1
  286. package/dist/commands/packages.d.ts.map +0 -1
  287. package/dist/commands/packages.js.map +0 -1
  288. package/dist/commands/permissions.d.ts.map +0 -1
  289. package/dist/commands/permissions.js.map +0 -1
  290. package/dist/commands/plugins.d.ts.map +0 -1
  291. package/dist/commands/plugins.js.map +0 -1
  292. package/dist/commands/pty.d.ts.map +0 -1
  293. package/dist/commands/pty.js.map +0 -1
  294. package/dist/commands/pull.d.ts.map +0 -1
  295. package/dist/commands/pull.js.map +0 -1
  296. package/dist/commands/push.d.ts +0 -3
  297. package/dist/commands/push.d.ts.map +0 -1
  298. package/dist/commands/push.js +0 -180
  299. package/dist/commands/push.js.map +0 -1
  300. package/dist/commands/refresh-memory.d.ts.map +0 -1
  301. package/dist/commands/refresh-memory.js.map +0 -1
  302. package/dist/commands/resource-view.d.ts.map +0 -1
  303. package/dist/commands/resource-view.js.map +0 -1
  304. package/dist/commands/routines.d.ts.map +0 -1
  305. package/dist/commands/routines.js.map +0 -1
  306. package/dist/commands/rules.d.ts.map +0 -1
  307. package/dist/commands/rules.js.map +0 -1
  308. package/dist/commands/sessions-picker.d.ts.map +0 -1
  309. package/dist/commands/sessions-picker.js.map +0 -1
  310. package/dist/commands/sessions.d.ts.map +0 -1
  311. package/dist/commands/sessions.js.map +0 -1
  312. package/dist/commands/skills.d.ts.map +0 -1
  313. package/dist/commands/skills.js.map +0 -1
  314. package/dist/commands/status.d.ts.map +0 -1
  315. package/dist/commands/status.js.map +0 -1
  316. package/dist/commands/subagents.d.ts.map +0 -1
  317. package/dist/commands/subagents.js.map +0 -1
  318. package/dist/commands/sync.d.ts.map +0 -1
  319. package/dist/commands/sync.js.map +0 -1
  320. package/dist/commands/teams-picker.d.ts.map +0 -1
  321. package/dist/commands/teams-picker.js.map +0 -1
  322. package/dist/commands/teams.d.ts.map +0 -1
  323. package/dist/commands/teams.js.map +0 -1
  324. package/dist/commands/utils.d.ts.map +0 -1
  325. package/dist/commands/utils.js.map +0 -1
  326. package/dist/commands/versions.d.ts.map +0 -1
  327. package/dist/commands/versions.js.map +0 -1
  328. package/dist/commands/view.d.ts.map +0 -1
  329. package/dist/commands/view.js.map +0 -1
  330. package/dist/index.d.ts.map +0 -1
  331. package/dist/index.js.map +0 -1
  332. package/dist/lib/__tests__/bugfixes.test.d.ts +0 -2
  333. package/dist/lib/__tests__/bugfixes.test.d.ts.map +0 -1
  334. package/dist/lib/__tests__/bugfixes.test.js +0 -192
  335. package/dist/lib/__tests__/bugfixes.test.js.map +0 -1
  336. package/dist/lib/__tests__/exec.test.d.ts +0 -2
  337. package/dist/lib/__tests__/exec.test.d.ts.map +0 -1
  338. package/dist/lib/__tests__/exec.test.js +0 -446
  339. package/dist/lib/__tests__/exec.test.js.map +0 -1
  340. package/dist/lib/__tests__/git-sync.test.d.ts +0 -2
  341. package/dist/lib/__tests__/git-sync.test.d.ts.map +0 -1
  342. package/dist/lib/__tests__/git-sync.test.js +0 -138
  343. package/dist/lib/__tests__/git-sync.test.js.map +0 -1
  344. package/dist/lib/__tests__/hooks.test.d.ts +0 -2
  345. package/dist/lib/__tests__/hooks.test.d.ts.map +0 -1
  346. package/dist/lib/__tests__/hooks.test.js +0 -203
  347. package/dist/lib/__tests__/hooks.test.js.map +0 -1
  348. package/dist/lib/__tests__/memory-compile.test.d.ts +0 -2
  349. package/dist/lib/__tests__/memory-compile.test.d.ts.map +0 -1
  350. package/dist/lib/__tests__/memory-compile.test.js +0 -95
  351. package/dist/lib/__tests__/memory-compile.test.js.map +0 -1
  352. package/dist/lib/__tests__/models.test.d.ts +0 -2
  353. package/dist/lib/__tests__/models.test.d.ts.map +0 -1
  354. package/dist/lib/__tests__/models.test.js +0 -184
  355. package/dist/lib/__tests__/models.test.js.map +0 -1
  356. package/dist/lib/__tests__/usage.test.d.ts +0 -2
  357. package/dist/lib/__tests__/usage.test.d.ts.map +0 -1
  358. package/dist/lib/__tests__/usage.test.js +0 -218
  359. package/dist/lib/__tests__/usage.test.js.map +0 -1
  360. package/dist/lib/agents.d.ts.map +0 -1
  361. package/dist/lib/agents.js.map +0 -1
  362. package/dist/lib/artifact-actions.d.ts.map +0 -1
  363. package/dist/lib/artifact-actions.js.map +0 -1
  364. package/dist/lib/commands.d.ts.map +0 -1
  365. package/dist/lib/commands.js.map +0 -1
  366. package/dist/lib/convert.d.ts.map +0 -1
  367. package/dist/lib/convert.js.map +0 -1
  368. package/dist/lib/daemon.d.ts.map +0 -1
  369. package/dist/lib/daemon.js.map +0 -1
  370. package/dist/lib/drive-sync.d.ts.map +0 -1
  371. package/dist/lib/drive-sync.js.map +0 -1
  372. package/dist/lib/exec.d.ts.map +0 -1
  373. package/dist/lib/exec.js.map +0 -1
  374. package/dist/lib/factory.d.ts +0 -57
  375. package/dist/lib/factory.d.ts.map +0 -1
  376. package/dist/lib/factory.js +0 -110
  377. package/dist/lib/factory.js.map +0 -1
  378. package/dist/lib/git.d.ts.map +0 -1
  379. package/dist/lib/git.js.map +0 -1
  380. package/dist/lib/help.d.ts.map +0 -1
  381. package/dist/lib/help.js.map +0 -1
  382. package/dist/lib/hooks.d.ts.map +0 -1
  383. package/dist/lib/hooks.js.map +0 -1
  384. package/dist/lib/manifest.d.ts.map +0 -1
  385. package/dist/lib/manifest.js.map +0 -1
  386. package/dist/lib/markdown.d.ts.map +0 -1
  387. package/dist/lib/markdown.js.map +0 -1
  388. package/dist/lib/mcp.d.ts.map +0 -1
  389. package/dist/lib/mcp.js.map +0 -1
  390. package/dist/lib/memory-compile.d.ts.map +0 -1
  391. package/dist/lib/memory-compile.js.map +0 -1
  392. package/dist/lib/memory.d.ts.map +0 -1
  393. package/dist/lib/memory.js.map +0 -1
  394. package/dist/lib/models.d.ts.map +0 -1
  395. package/dist/lib/models.js.map +0 -1
  396. package/dist/lib/permissions.d.ts.map +0 -1
  397. package/dist/lib/permissions.js.map +0 -1
  398. package/dist/lib/picker.d.ts.map +0 -1
  399. package/dist/lib/picker.js.map +0 -1
  400. package/dist/lib/plugins.d.ts.map +0 -1
  401. package/dist/lib/plugins.js.map +0 -1
  402. package/dist/lib/pty-client.d.ts.map +0 -1
  403. package/dist/lib/pty-client.js.map +0 -1
  404. package/dist/lib/pty-server.d.ts.map +0 -1
  405. package/dist/lib/pty-server.js.map +0 -1
  406. package/dist/lib/registry.d.ts.map +0 -1
  407. package/dist/lib/registry.js.map +0 -1
  408. package/dist/lib/resources.d.ts.map +0 -1
  409. package/dist/lib/resources.js.map +0 -1
  410. package/dist/lib/routines.d.ts.map +0 -1
  411. package/dist/lib/routines.js.map +0 -1
  412. package/dist/lib/runner.d.ts.map +0 -1
  413. package/dist/lib/runner.js.map +0 -1
  414. package/dist/lib/sandbox.d.ts.map +0 -1
  415. package/dist/lib/sandbox.js.map +0 -1
  416. package/dist/lib/scheduler.d.ts.map +0 -1
  417. package/dist/lib/scheduler.js.map +0 -1
  418. package/dist/lib/session/__tests__/db.test.d.ts +0 -2
  419. package/dist/lib/session/__tests__/db.test.d.ts.map +0 -1
  420. package/dist/lib/session/__tests__/db.test.js +0 -54
  421. package/dist/lib/session/__tests__/db.test.js.map +0 -1
  422. package/dist/lib/session/__tests__/discover.test.d.ts +0 -2
  423. package/dist/lib/session/__tests__/discover.test.d.ts.map +0 -1
  424. package/dist/lib/session/__tests__/discover.test.js +0 -63
  425. package/dist/lib/session/__tests__/discover.test.js.map +0 -1
  426. package/dist/lib/session/__tests__/prompt.test.d.ts +0 -2
  427. package/dist/lib/session/__tests__/prompt.test.d.ts.map +0 -1
  428. package/dist/lib/session/__tests__/prompt.test.js +0 -44
  429. package/dist/lib/session/__tests__/prompt.test.js.map +0 -1
  430. package/dist/lib/session/__tests__/render.test.d.ts +0 -2
  431. package/dist/lib/session/__tests__/render.test.d.ts.map +0 -1
  432. package/dist/lib/session/__tests__/render.test.js +0 -602
  433. package/dist/lib/session/__tests__/render.test.js.map +0 -1
  434. package/dist/lib/session/db.d.ts.map +0 -1
  435. package/dist/lib/session/db.js.map +0 -1
  436. package/dist/lib/session/discover.d.ts.map +0 -1
  437. package/dist/lib/session/discover.js.map +0 -1
  438. package/dist/lib/session/parse.d.ts.map +0 -1
  439. package/dist/lib/session/parse.js.map +0 -1
  440. package/dist/lib/session/prompt.d.ts.map +0 -1
  441. package/dist/lib/session/prompt.js.map +0 -1
  442. package/dist/lib/session/prompt.test.d.ts +0 -2
  443. package/dist/lib/session/prompt.test.d.ts.map +0 -1
  444. package/dist/lib/session/prompt.test.js +0 -57
  445. package/dist/lib/session/prompt.test.js.map +0 -1
  446. package/dist/lib/session/render.d.ts.map +0 -1
  447. package/dist/lib/session/render.js.map +0 -1
  448. package/dist/lib/session/team-filter.d.ts.map +0 -1
  449. package/dist/lib/session/team-filter.js.map +0 -1
  450. package/dist/lib/session/team-filter.test.d.ts +0 -2
  451. package/dist/lib/session/team-filter.test.d.ts.map +0 -1
  452. package/dist/lib/session/team-filter.test.js +0 -157
  453. package/dist/lib/session/team-filter.test.js.map +0 -1
  454. package/dist/lib/session/types.d.ts.map +0 -1
  455. package/dist/lib/session/types.js.map +0 -1
  456. package/dist/lib/shims.d.ts.map +0 -1
  457. package/dist/lib/shims.js.map +0 -1
  458. package/dist/lib/skills.d.ts.map +0 -1
  459. package/dist/lib/skills.js.map +0 -1
  460. package/dist/lib/state.d.ts.map +0 -1
  461. package/dist/lib/state.js.map +0 -1
  462. package/dist/lib/subagents.d.ts.map +0 -1
  463. package/dist/lib/subagents.js.map +0 -1
  464. package/dist/lib/teams/agents.d.ts.map +0 -1
  465. package/dist/lib/teams/agents.js.map +0 -1
  466. package/dist/lib/teams/api.d.ts.map +0 -1
  467. package/dist/lib/teams/api.js.map +0 -1
  468. package/dist/lib/teams/cloud.d.ts +0 -11
  469. package/dist/lib/teams/cloud.d.ts.map +0 -1
  470. package/dist/lib/teams/cloud.js +0 -169
  471. package/dist/lib/teams/cloud.js.map +0 -1
  472. package/dist/lib/teams/debug.d.ts.map +0 -1
  473. package/dist/lib/teams/debug.js.map +0 -1
  474. package/dist/lib/teams/file_ops.d.ts.map +0 -1
  475. package/dist/lib/teams/file_ops.js.map +0 -1
  476. package/dist/lib/teams/parsers.d.ts.map +0 -1
  477. package/dist/lib/teams/parsers.js.map +0 -1
  478. package/dist/lib/teams/persistence.d.ts.map +0 -1
  479. package/dist/lib/teams/persistence.js.map +0 -1
  480. package/dist/lib/teams/ralph.d.ts +0 -8
  481. package/dist/lib/teams/ralph.d.ts.map +0 -1
  482. package/dist/lib/teams/ralph.js +0 -59
  483. package/dist/lib/teams/ralph.js.map +0 -1
  484. package/dist/lib/teams/registry.d.ts.map +0 -1
  485. package/dist/lib/teams/registry.js.map +0 -1
  486. package/dist/lib/teams/summarizer.d.ts.map +0 -1
  487. package/dist/lib/teams/summarizer.js.map +0 -1
  488. package/dist/lib/template.d.ts.map +0 -1
  489. package/dist/lib/template.js.map +0 -1
  490. package/dist/lib/types.d.ts.map +0 -1
  491. package/dist/lib/types.js.map +0 -1
  492. package/dist/lib/usage.d.ts.map +0 -1
  493. package/dist/lib/usage.js.map +0 -1
  494. package/dist/lib/versions.d.ts.map +0 -1
  495. package/dist/lib/versions.js.map +0 -1
  496. package/scripts/rebuild-sqlite.sh +0 -46
@@ -1,23 +1,53 @@
1
+ /**
2
+ * Version management module for agents-cli.
3
+ *
4
+ * Handles installing, removing, listing, and switching between agent CLI versions.
5
+ * Each version is installed into an isolated directory under ~/.agents-system/versions/{agent}/{version}/
6
+ * with its own HOME directory for config isolation. Resources (commands, skills, hooks, memory,
7
+ * MCP servers, permissions, subagents, plugins) from ~/.agents/ are synced into version homes
8
+ * via copies or conversions (not symlinks).
9
+ *
10
+ * Key responsibilities:
11
+ * - Version lifecycle: install, remove, list, resolve (project-level or global default)
12
+ * - Resource discovery: scan ~/.agents/ for available resources across all types
13
+ * - Resource sync: copy/convert resources into a version's isolated config directory
14
+ * - Diff and reconciliation: detect new/unsynced resources and prompt users to sync them
15
+ * - Agent/version target resolution: parse agent@version specs from CLI flags
16
+ */
1
17
  import * as fs from 'fs';
2
18
  import * as path from 'path';
3
19
  import * as os from 'os';
4
- import { exec } from 'child_process';
20
+ import * as yaml from 'yaml';
21
+ import { exec, execFile } from 'child_process';
5
22
  import { promisify } from 'util';
6
23
  import chalk from 'chalk';
7
24
  import * as TOML from 'smol-toml';
8
25
  import { checkbox, select } from '@inquirer/prompts';
9
- import { getVersionsDir, ensureAgentsDir, readMeta, writeMeta, getCommandsDir, getSkillsDir, getHooksDir, getMemoryDir, clearVersionResources, recordVersionResources, getProjectAgentsDir } from './state.js';
10
- import { AGENTS, getAccountEmail, MCP_CAPABLE_AGENTS, COMMANDS_CAPABLE_AGENTS, getMcpConfigPathForHome, parseMcpConfig, resolveAgentName, formatAgentError, CODEX_HOOKS_MIN_VERSION } from './agents.js';
11
- import { applyPermissionsToVersion as applyPermsToVersion, PERMISSIONS_CAPABLE_AGENTS, discoverPermissionGroups, buildPermissionsFromGroups, CODEX_RULES_FILENAME } from './permissions.js';
26
+ import { getVersionsDir, ensureAgentsDir, readMeta, writeMeta, getCommandsDir, getSkillsDir, getHooksDir, getResolvedRulesDir, getUserRulesDir, clearVersionResources, recordVersionResources, getProjectAgentsDir, getPromptcutsPath, getEnabledExtraRepos, getAgentsDir, getUserAgentsDir } from './state.js';
27
+ import { resolveResource } from './resources.js';
28
+ import { AGENTS, getAccountEmail, MCP_CAPABLE_AGENTS, COMMANDS_CAPABLE_AGENTS, getMcpConfigPathForHome, parseMcpConfig, resolveAgentName, formatAgentError } from './agents.js';
29
+ import { applyPermissionsToVersion as applyPermsToVersion, PERMISSIONS_CAPABLE_AGENTS, discoverPermissionGroups, buildPermissionsFromGroups, CODEX_RULES_FILENAME, getActivePermissionSetName, readPermissionSetRecipe, PERMISSION_SET_ENV_VAR } from './permissions.js';
12
30
  import { installMcpServers } from './mcp.js';
13
31
  import { markdownToToml } from './convert.js';
14
- import { createVersionedAlias, removeVersionedAlias, getConfigSymlinkVersion } from './shims.js';
32
+ import { createVersionedAlias, removeVersionedAlias, getConfigSymlinkVersion, ensureClaudeInsideSymlink } from './shims.js';
15
33
  import { listInstalledSubagents, transformSubagentForClaude, syncSubagentToOpenclaw, SUBAGENT_CAPABLE_AGENTS } from './subagents.js';
16
34
  import { registerHooksToSettings } from './hooks.js';
35
+ import { supports, explainSkip } from './capabilities.js';
17
36
  import { discoverPlugins, syncPluginToVersion, isPluginSynced, pluginSupportsAgent, cleanOrphanedPluginSkills } from './plugins.js';
18
37
  import { compileMemoryForAgent } from './memory-compile.js';
38
+ import { loadSyncManifest, saveSyncManifest, buildManifest, isSyncStale } from './sync-manifest.js';
19
39
  import { PLUGINS_CAPABLE_AGENTS } from './agents.js';
40
+ import { safeJoin } from './paths.js';
41
+ import { installCommandSkillToVersion, listCommandSkillsInVersion, shouldInstallCommandAsSkill } from './command-skills.js';
42
+ /** Promisified exec for running shell commands. */
20
43
  const execAsync = promisify(exec);
44
+ const execFileAsync = promisify(execFile);
45
+ const RULES_DOC_FILENAME = 'README.md';
46
+ // Strict shape for an agent version string. Anything outside this is rejected
47
+ // at parse time so it can't reach an exec/shell boundary or get interpolated
48
+ // into a generated bash alias. Must allow "latest" plus npm-dist-tag /
49
+ // semver-shaped values (digits, dots, dashes, +, _).
50
+ const VERSION_RE = /^(?:latest|[A-Za-z0-9._+-]{1,64})$/;
21
51
  /**
22
52
  * Get all available resources from ~/.agents/.
23
53
  */
@@ -31,14 +61,22 @@ export function getAvailableResources(cwd = process.cwd()) {
31
61
  permissions: [],
32
62
  subagents: [],
33
63
  plugins: [],
64
+ promptcuts: false,
34
65
  };
35
66
  const projectAgentsDir = getProjectAgentsDir(cwd);
36
- const userBase = path.dirname(getCommandsDir());
67
+ const userBase = getUserAgentsDir();
68
+ const systemBase = getAgentsDir();
37
69
  const resourceBases = [];
38
70
  if (projectAgentsDir) {
39
71
  resourceBases.push({ scope: 'project', base: projectAgentsDir });
40
72
  }
41
73
  resourceBases.push({ scope: 'user', base: userBase });
74
+ resourceBases.push({ scope: 'user', base: systemBase });
75
+ // Extra DotAgent repos registered via `agents repo add`. Ordered last so
76
+ // project/user/system names win on collision.
77
+ for (const extra of getEnabledExtraRepos()) {
78
+ resourceBases.push({ scope: 'user', base: extra.dir });
79
+ }
42
80
  // Commands (*.md files)
43
81
  const commandNames = new Set();
44
82
  for (const { base } of resourceBases) {
@@ -79,22 +117,25 @@ export function getAvailableResources(cwd = process.cwd()) {
79
117
  }
80
118
  }
81
119
  result.hooks = Array.from(hookNames);
82
- // Memory (*.md files, excluding symlinks)
120
+ // Rules (*.md files, excluding symlinks and README)
121
+ // Scan 'rules/' first (canonical), then 'memory/' (legacy name) for backward compat.
83
122
  const memoryNames = new Set();
84
123
  for (const { base } of resourceBases) {
85
- const memoryDir = path.join(base, 'memory');
86
- if (!fs.existsSync(memoryDir))
87
- continue;
88
- const names = fs.readdirSync(memoryDir)
89
- .filter(f => {
90
- if (!f.endsWith('.md'))
91
- return false;
92
- const stat = fs.lstatSync(path.join(memoryDir, f));
93
- return !stat.isSymbolicLink();
94
- })
95
- .map(f => f.replace(/\.md$/, ''));
96
- for (const name of names) {
97
- memoryNames.add(name);
124
+ for (const subdir of ['rules', 'memory']) {
125
+ const memoryDir = path.join(base, subdir);
126
+ if (!fs.existsSync(memoryDir))
127
+ continue;
128
+ const names = fs.readdirSync(memoryDir)
129
+ .filter(f => {
130
+ if (!f.endsWith('.md') || f === RULES_DOC_FILENAME)
131
+ return false;
132
+ const stat = fs.lstatSync(path.join(memoryDir, f));
133
+ return !stat.isSymbolicLink();
134
+ })
135
+ .map(f => f.replace(/\.md$/, ''));
136
+ for (const name of names) {
137
+ memoryNames.add(name);
138
+ }
98
139
  }
99
140
  }
100
141
  result.memory = Array.from(memoryNames);
@@ -143,14 +184,28 @@ export function getAvailableResources(cwd = process.cwd()) {
143
184
  // Plugins (directories with .claude-plugin/plugin.json)
144
185
  const allPlugins = discoverPlugins();
145
186
  result.plugins = allPlugins.map(p => p.name);
187
+ // Promptcuts — single file at ~/.agents/promptcuts.yaml, not per-agent.
188
+ // Project-scoped .agents/promptcuts.yaml is intentionally not supported
189
+ // (user-global shortcuts only — they follow the user, not the repo).
190
+ result.promptcuts = fs.existsSync(getPromptcutsPath());
146
191
  return result;
147
192
  }
193
+ // Files/dirs that are never synced into a version home (OS metadata, local tooling).
194
+ const SKILL_COPY_IGNORE = new Set(['.DS_Store', '.git', '.gitignore', '.venv', '__pycache__', 'node_modules']);
195
+ function shouldSkillEntryBeSkipped(name) {
196
+ return SKILL_COPY_IGNORE.has(name);
197
+ }
148
198
  /**
149
199
  * Recursively compare two directories: every file in src must exist in dest with identical content.
200
+ * Skips the same entries that copyDir skips (symlinks and SKILL_COPY_IGNORE members).
150
201
  */
151
202
  function skillDirsMatch(src, dest) {
152
203
  const entries = fs.readdirSync(src, { withFileTypes: true });
153
204
  for (const entry of entries) {
205
+ if (entry.isSymbolicLink())
206
+ continue;
207
+ if (shouldSkillEntryBeSkipped(entry.name))
208
+ continue;
154
209
  const srcPath = path.join(src, entry.name);
155
210
  const destPath = path.join(dest, entry.name);
156
211
  if (entry.isDirectory()) {
@@ -188,6 +243,7 @@ export function getActuallySyncedResources(agent, version, options = {}) {
188
243
  permissions: [],
189
244
  subagents: [],
190
245
  plugins: [],
246
+ promptcuts: false,
191
247
  };
192
248
  // Commands - check what files exist in version home
193
249
  const commandsDir = path.join(configDir, agentConfig.commandsSubdir);
@@ -201,21 +257,27 @@ export function getActuallySyncedResources(agent, version, options = {}) {
201
257
  const skillsDir = path.join(configDir, 'skills');
202
258
  const centralSkillsDir = getSkillsDir();
203
259
  const projectSkillsDir = projectAgentsDir ? path.join(projectAgentsDir, 'skills') : null;
260
+ const userAgentsDir = getUserAgentsDir();
261
+ const extraRepos = getEnabledExtraRepos();
204
262
  if (fs.existsSync(skillsDir)) {
205
263
  const installedSkills = fs.readdirSync(skillsDir, { withFileTypes: true })
206
264
  .filter(d => d.isDirectory() && !d.name.startsWith('.'))
207
265
  .map(d => d.name);
208
266
  for (const skill of installedSkills) {
209
267
  const versionSkillDir = path.join(skillsDir, skill);
210
- const projectSourceDir = projectSkillsDir ? path.join(projectSkillsDir, skill) : null;
211
- const centralSkillDir = path.join(centralSkillsDir, skill);
212
- const hasProjectSource = projectSourceDir ? fs.existsSync(projectSourceDir) : false;
213
- const hasUserSource = fs.existsSync(centralSkillDir);
214
- if (!hasProjectSource && !hasUserSource) {
268
+ const sourceCandidates = [
269
+ projectSkillsDir ? path.join(projectSkillsDir, skill) : null,
270
+ path.join(userAgentsDir, 'skills', skill),
271
+ path.join(centralSkillsDir, skill),
272
+ ...extraRepos.map((e) => path.join(e.dir, 'skills', skill)),
273
+ ];
274
+ const sourceDir = sourceCandidates.find((p) => p && fs.existsSync(p)) || null;
275
+ if (!sourceDir) {
276
+ // True orphan — no source in project, primary, or any extra. Still
277
+ // count as synced so version-home cleanup knows it's accounted for.
215
278
  result.skills.push(skill);
216
279
  continue;
217
280
  }
218
- const sourceDir = hasProjectSource ? projectSourceDir : centralSkillDir;
219
281
  const allMatch = skillDirsMatch(sourceDir, versionSkillDir);
220
282
  if (allMatch) {
221
283
  result.skills.push(skill);
@@ -226,16 +288,19 @@ export function getActuallySyncedResources(agent, version, options = {}) {
226
288
  const hooksDir = path.join(configDir, 'hooks');
227
289
  const centralHooksDir = getHooksDir();
228
290
  const projectHooksDir = projectAgentsDir ? path.join(projectAgentsDir, 'hooks') : null;
291
+ const userHooksDir = path.join(userAgentsDir, 'hooks');
229
292
  if (fs.existsSync(hooksDir)) {
230
293
  const installedHooks = fs.readdirSync(hooksDir).filter(f => !f.startsWith('.'));
231
294
  for (const hook of installedHooks) {
232
295
  const projectFile = projectHooksDir ? path.join(projectHooksDir, hook) : null;
233
296
  const centralFile = path.join(centralHooksDir, hook);
297
+ const userFile = path.join(userHooksDir, hook);
234
298
  const versionFile = path.join(hooksDir, hook);
235
299
  const hasProject = projectFile ? fs.existsSync(projectFile) : false;
300
+ const hasUser = fs.existsSync(userFile);
236
301
  const hasCentral = fs.existsSync(centralFile);
237
- const sourceFile = hasProject ? projectFile : centralFile;
238
- if (!hasProject && !hasCentral) {
302
+ const sourceFile = hasProject ? projectFile : hasUser ? userFile : centralFile;
303
+ if (!hasProject && !hasCentral && !hasUser) {
239
304
  result.hooks.push(hook);
240
305
  continue;
241
306
  }
@@ -251,15 +316,19 @@ export function getActuallySyncedResources(agent, version, options = {}) {
251
316
  }
252
317
  }
253
318
  }
254
- // Memory - check which memory files are actually in sync (content matches)
255
- const memoryDir = getMemoryDir();
256
- const projectMemoryDir = projectAgentsDir ? path.join(projectAgentsDir, 'memory') : null;
319
+ // Rules - check which instruction files are actually in sync (content matches)
320
+ const memoryDir = getResolvedRulesDir();
321
+ const projectMemoryDir = projectAgentsDir ? path.join(projectAgentsDir, 'rules') : null;
322
+ const userMemoryDir = getUserRulesDir();
257
323
  const memoryFiles = new Set();
258
324
  if (fs.existsSync(memoryDir)) {
259
- fs.readdirSync(memoryDir).filter(f => f.endsWith('.md')).forEach(f => memoryFiles.add(f));
325
+ fs.readdirSync(memoryDir).filter(f => f.endsWith('.md') && f !== RULES_DOC_FILENAME).forEach(f => memoryFiles.add(f));
260
326
  }
261
327
  if (projectMemoryDir && fs.existsSync(projectMemoryDir)) {
262
- fs.readdirSync(projectMemoryDir).filter(f => f.endsWith('.md')).forEach(f => memoryFiles.add(f));
328
+ fs.readdirSync(projectMemoryDir).filter(f => f.endsWith('.md') && f !== RULES_DOC_FILENAME).forEach(f => memoryFiles.add(f));
329
+ }
330
+ if (fs.existsSync(userMemoryDir)) {
331
+ fs.readdirSync(userMemoryDir).filter(f => f.endsWith('.md') && f !== RULES_DOC_FILENAME).forEach(f => memoryFiles.add(f));
263
332
  }
264
333
  for (const file of memoryFiles) {
265
334
  const memName = file.replace(/\.md$/, '');
@@ -269,10 +338,12 @@ export function getActuallySyncedResources(agent, version, options = {}) {
269
338
  continue;
270
339
  const projectFile = projectMemoryDir ? path.join(projectMemoryDir, file) : null;
271
340
  const centralFile = path.join(memoryDir, file);
341
+ const userFile = path.join(userMemoryDir, file);
272
342
  const hasProject = projectFile ? fs.existsSync(projectFile) : false;
343
+ const hasUser = fs.existsSync(userFile);
273
344
  const hasCentral = fs.existsSync(centralFile);
274
- const sourceFile = hasProject ? projectFile : centralFile;
275
- if (!hasProject && !hasCentral) {
345
+ const sourceFile = hasProject ? projectFile : hasUser ? userFile : centralFile;
346
+ if (!hasProject && !hasCentral && !hasUser) {
276
347
  result.memory.push(memName);
277
348
  continue;
278
349
  }
@@ -421,6 +492,9 @@ export function getNewResources(available, actuallySynced) {
421
492
  permissions: available.permissions.filter(p => !actuallySynced.permissions.includes(p)),
422
493
  subagents: available.subagents.filter(s => !actuallySynced.subagents.includes(s)),
423
494
  plugins: available.plugins.filter(p => !actuallySynced.plugins.includes(p)),
495
+ // Promptcuts aren't version-scoped — the hook reads ~/.agents/promptcuts.yaml
496
+ // directly, so there is never a "new" per-version state to reconcile.
497
+ promptcuts: false,
424
498
  };
425
499
  }
426
500
  /**
@@ -643,8 +717,9 @@ export async function promptResourceSelection(agent) {
643
717
  if (selectedCategories.length === 0) {
644
718
  return {};
645
719
  }
646
- // If "Select All" was picked, sync everything without per-category prompts
647
- if (selectedCategories.includes(SELECT_ALL_KEY)) {
720
+ // If "Select All" was picked, or all individual categories are selected, sync everything without per-category prompts
721
+ const allCategoryKeys = availableCategories.map(c => c.key);
722
+ if (selectedCategories.includes(SELECT_ALL_KEY) || allCategoryKeys.every(k => selectedCategories.includes(k))) {
648
723
  for (const c of availableCategories) {
649
724
  selection[c.key] = 'all';
650
725
  }
@@ -728,6 +803,11 @@ export function parseAgentSpec(spec) {
728
803
  if (!AGENTS[agentName]) {
729
804
  return null;
730
805
  }
806
+ // Reject any version string that could escape an exec context or a
807
+ // bash-shim interpolation. Real agent versions are semver-shaped or "latest".
808
+ if (!VERSION_RE.test(version)) {
809
+ return null;
810
+ }
731
811
  return {
732
812
  agent: agentName,
733
813
  version,
@@ -769,7 +849,7 @@ export async function getLatestNpmVersion(agent) {
769
849
  if (!agentConfig.npmPackage)
770
850
  return null;
771
851
  try {
772
- const { stdout } = await execAsync(`npm view ${agentConfig.npmPackage} version`);
852
+ const { stdout } = await execFileAsync('npm', ['view', agentConfig.npmPackage, 'version']);
773
853
  return stdout.trim();
774
854
  }
775
855
  catch {
@@ -853,9 +933,15 @@ export async function installVersion(agent, version, onProgress) {
853
933
  const packageSpec = version === 'latest'
854
934
  ? agentConfig.npmPackage
855
935
  : `${agentConfig.npmPackage}@${version}`;
936
+ // Defense-in-depth: even if a future caller bypasses parseAgentSpec, the
937
+ // version string never reaches /bin/sh because we use execFile (argv form)
938
+ // and re-validate here.
939
+ if (version !== 'latest' && !VERSION_RE.test(version)) {
940
+ throw new Error(`Invalid version: ${JSON.stringify(version)}`);
941
+ }
856
942
  try {
857
943
  onProgress?.(`Installing ${packageSpec}...`);
858
- const { stdout } = await execAsync(`npm install ${packageSpec}`, { cwd: versionDir });
944
+ const { stdout } = await execFileAsync('npm', ['install', packageSpec], { cwd: versionDir });
859
945
  // Determine the actual installed version
860
946
  let installedVersion = version;
861
947
  if (version === 'latest') {
@@ -873,8 +959,10 @@ export async function installVersion(agent, version, onProgress) {
873
959
  fs.renameSync(versionDir, actualVersionDir);
874
960
  }
875
961
  else {
876
- // Already exists, remove the 'latest' dir
877
- fs.rmSync(versionDir, { recursive: true, force: true });
962
+ // Already exists drop the 'latest' install artifacts but keep
963
+ // `home/` (may contain conversation history from sessions that
964
+ // ran while the user was on `latest`).
965
+ removeInstallArtifacts(versionDir);
878
966
  }
879
967
  }
880
968
  }
@@ -886,25 +974,52 @@ export async function installVersion(agent, version, onProgress) {
886
974
  }
887
975
  // Create versioned alias (e.g., claude@2.0.65)
888
976
  createVersionedAlias(agent, installedVersion);
977
+ // Claude reads its global config from CLAUDE_CONFIG_DIR/.claude.json —
978
+ // i.e. inside the per-version .claude dir — while the rest of agents-cli
979
+ // manages the home-level file. Symlink INSIDE to OUTSIDE so Claude and
980
+ // agents-cli see the same content.
981
+ if (agent === 'claude') {
982
+ try {
983
+ ensureClaudeInsideSymlink(installedVersion);
984
+ }
985
+ catch {
986
+ /* non-fatal; the install itself succeeded */
987
+ }
988
+ }
889
989
  return { success: true, installedVersion };
890
990
  }
891
991
  catch (err) {
892
- // Clean up on failure
992
+ // Clean up on failure — preserve `home/` in case a prior install left
993
+ // conversation history behind that we must not wipe on a failed reinstall.
893
994
  if (fs.existsSync(versionDir)) {
894
- fs.rmSync(versionDir, { recursive: true, force: true });
995
+ removeInstallArtifacts(versionDir);
895
996
  }
896
997
  return { success: false, installedVersion: version, error: err.message };
897
998
  }
898
999
  }
899
1000
  /**
900
- * Remove a specific version of an agent.
1001
+ * Remove install artifacts from a version directory, preserving `home/` which
1002
+ * contains the user's conversation history, sessions, history.jsonl, tasks,
1003
+ * todos, file-history, etc. Called by removeVersion so that uninstalling a
1004
+ * version never deletes the user's transcripts.
1005
+ */
1006
+ function removeInstallArtifacts(versionDir) {
1007
+ for (const entry of fs.readdirSync(versionDir)) {
1008
+ if (entry === 'home')
1009
+ continue;
1010
+ fs.rmSync(path.join(versionDir, entry), { recursive: true, force: true });
1011
+ }
1012
+ }
1013
+ /**
1014
+ * Remove a specific version of an agent. Preserves `home/` under the version
1015
+ * directory so conversation history survives reinstalls.
901
1016
  */
902
1017
  export function removeVersion(agent, version) {
903
1018
  const versionDir = getVersionDir(agent, version);
904
1019
  if (!fs.existsSync(versionDir)) {
905
1020
  return false;
906
1021
  }
907
- fs.rmSync(versionDir, { recursive: true, force: true });
1022
+ removeInstallArtifacts(versionDir);
908
1023
  // Remove versioned alias (e.g., claude@2.0.65)
909
1024
  removeVersionedAlias(agent, version);
910
1025
  // Clear resource tracking for this version
@@ -935,7 +1050,9 @@ export function removeVersion(agent, version) {
935
1050
  return true;
936
1051
  }
937
1052
  /**
938
- * Remove all versions of an agent.
1053
+ * Remove all versions of an agent. Preserves each version's `home/` directory
1054
+ * so conversation history is never deleted; the per-version folders (now
1055
+ * containing only `home/`) remain under the agent dir.
939
1056
  */
940
1057
  export function removeAllVersions(agent) {
941
1058
  const versions = listInstalledVersions(agent);
@@ -945,11 +1062,6 @@ export function removeAllVersions(agent) {
945
1062
  removed++;
946
1063
  }
947
1064
  }
948
- // Clean up the agent directory
949
- const agentDir = path.join(getVersionsDir(), agent);
950
- if (fs.existsSync(agentDir)) {
951
- fs.rmSync(agentDir, { recursive: true, force: true });
952
- }
953
1065
  return removed;
954
1066
  }
955
1067
  /**
@@ -968,19 +1080,71 @@ export function resolveVersion(agent, projectPath) {
968
1080
  return getGlobalDefault(agent);
969
1081
  }
970
1082
  /**
971
- * Get version specified in project manifest.
1083
+ * Normalize a user-supplied @version token across CLI subcommands.
1084
+ *
1085
+ * undefined / "" / "default" -> undefined (caller falls back to project pin or global default)
1086
+ * "latest" -> highest installed version (process.exit if none installed)
1087
+ * "x.y.z" (installed) -> "x.y.z"
1088
+ * "x.y.z" (not installed) -> process.exit with installed-list hint
1089
+ *
1090
+ * Use this anywhere the user can type `agents <cmd> claude@<token>` to keep the
1091
+ * vocabulary consistent. Subcommands with different semantics for `latest`
1092
+ * (install/remove/use, where `latest` means npm-latest) keep their existing
1093
+ * parsing.
1094
+ */
1095
+ export function resolveVersionAlias(agent, raw) {
1096
+ if (!raw || raw === 'default')
1097
+ return undefined;
1098
+ if (raw === 'latest') {
1099
+ const installed = listInstalledVersions(agent);
1100
+ if (installed.length === 0) {
1101
+ console.error(chalk.red(`No ${agent} versions installed.`));
1102
+ console.error(chalk.gray(`Install one: agents versions install ${agent}`));
1103
+ process.exit(1);
1104
+ }
1105
+ return installed[installed.length - 1];
1106
+ }
1107
+ if (!isVersionInstalled(agent, raw)) {
1108
+ const installed = listInstalledVersions(agent);
1109
+ console.error(chalk.red(`${agent}@${raw} is not installed.`));
1110
+ if (installed.length > 0) {
1111
+ console.error(chalk.gray(`Installed: ${installed.join(', ')}`));
1112
+ }
1113
+ console.error(chalk.gray(`Install it: agents versions install ${agent}@${raw}`));
1114
+ process.exit(1);
1115
+ }
1116
+ return raw;
1117
+ }
1118
+ /**
1119
+ * Loose variant of resolveVersionAlias for record-filter contexts (sessions,
1120
+ * team history). Same `default`/`latest` semantics, but explicit versions
1121
+ * pass through unchanged so historical records of uninstalled versions remain
1122
+ * queryable.
1123
+ */
1124
+ export function resolveVersionAliasLoose(agent, raw) {
1125
+ if (!raw || raw === 'default')
1126
+ return undefined;
1127
+ if (raw === 'latest') {
1128
+ const installed = listInstalledVersions(agent);
1129
+ return installed.length > 0 ? installed[installed.length - 1] : undefined;
1130
+ }
1131
+ return raw;
1132
+ }
1133
+ /**
1134
+ * Get version specified in a project-root agents.yaml (not the user ~/.agents-system/agents.yaml).
972
1135
  */
973
1136
  export function getProjectVersion(agent, startPath) {
1137
+ const userAgentsYaml = path.join(getAgentsDir(), 'agents.yaml');
974
1138
  let dir = path.resolve(startPath);
975
1139
  while (dir !== path.dirname(dir)) {
976
- const manifestPath = path.join(dir, '.agents', 'agents.yaml');
977
- if (fs.existsSync(manifestPath)) {
1140
+ const manifestPath = path.join(dir, 'agents.yaml');
1141
+ if (manifestPath !== userAgentsYaml && fs.existsSync(manifestPath)) {
978
1142
  try {
979
1143
  const content = fs.readFileSync(manifestPath, 'utf-8');
980
- // Simple YAML parsing for agents section (flat format: claude: "1.5.0")
981
- const agentMatch = content.match(new RegExp(`^\\s+${agent}:\\s*['"]?([^'"\n]+)['"]?`, 'm'));
982
- if (agentMatch) {
983
- return agentMatch[1].trim();
1144
+ const parsed = yaml.parse(content);
1145
+ const version = parsed?.agents?.[agent];
1146
+ if (typeof version === 'string' && version.trim()) {
1147
+ return version.trim();
984
1148
  }
985
1149
  }
986
1150
  catch {
@@ -1015,7 +1179,7 @@ export async function getInstalledVersion(agent, version) {
1015
1179
  return null;
1016
1180
  }
1017
1181
  try {
1018
- const { stdout } = await execAsync(`${binaryPath} --version`);
1182
+ const { stdout } = await execFileAsync(binaryPath, ['--version']);
1019
1183
  const match = stdout.match(/(\d+\.\d+\.\d+)/);
1020
1184
  return match ? match[1] : version;
1021
1185
  }
@@ -1058,10 +1222,28 @@ export function getResourceDiff(agent, version) {
1058
1222
  return 'none';
1059
1223
  }
1060
1224
  };
1061
- // Commands: check directory symlink (or individual files for Gemini)
1225
+ // Commands: check directory symlink (or individual files for Gemini / generated skills for newer Codex)
1062
1226
  const centralCommands = getCommandsDir();
1063
1227
  const commandsTarget = path.join(agentDir, agentConfig.commandsSubdir);
1064
- if (agentConfig.format === 'toml') {
1228
+ if (shouldInstallCommandAsSkill(agent, version)) {
1229
+ const centralFiles = fs.existsSync(centralCommands)
1230
+ ? fs.readdirSync(centralCommands).filter(f => f.endsWith('.md'))
1231
+ : [];
1232
+ const centralNames = new Set(centralFiles.map(f => f.replace('.md', '')));
1233
+ const versionNames = new Set(listCommandSkillsInVersion(agentDir));
1234
+ for (const file of centralFiles) {
1235
+ const name = file.replace('.md', '');
1236
+ if (!versionNames.has(name)) {
1237
+ diff.commands.added.push(file);
1238
+ }
1239
+ }
1240
+ for (const name of versionNames) {
1241
+ if (!centralNames.has(name)) {
1242
+ diff.commands.dangling.push(`${name}.md`);
1243
+ }
1244
+ }
1245
+ }
1246
+ else if (agentConfig.format === 'toml') {
1065
1247
  // Gemini: compare .md files in central vs .toml files in version
1066
1248
  if (fs.existsSync(centralCommands)) {
1067
1249
  const centralFiles = fs.readdirSync(centralCommands).filter(f => f.endsWith('.md'));
@@ -1125,10 +1307,10 @@ export function getResourceDiff(agent, version) {
1125
1307
  diff.hooks.dangling = ['hooks/'];
1126
1308
  }
1127
1309
  }
1128
- // Memory: check individual file symlinks
1129
- const centralMemory = getMemoryDir();
1310
+ // Rules: check individual file symlinks
1311
+ const centralMemory = getResolvedRulesDir();
1130
1312
  if (fs.existsSync(centralMemory)) {
1131
- const memoryFiles = fs.readdirSync(centralMemory).filter(f => f.endsWith('.md'));
1313
+ const memoryFiles = fs.readdirSync(centralMemory).filter(f => f.endsWith('.md') && f !== RULES_DOC_FILENAME);
1132
1314
  for (const file of memoryFiles) {
1133
1315
  const targetName = file === 'AGENTS.md' ? agentConfig.instructionsFile : file;
1134
1316
  const targetPath = path.join(agentDir, targetName);
@@ -1166,7 +1348,20 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
1166
1348
  const result = { commands: false, skills: false, hooks: false, memory: [], permissions: false, mcp: [], subagents: [], plugins: [] };
1167
1349
  const cwd = options.cwd || process.cwd();
1168
1350
  const projectAgentsDir = options.projectDir || getProjectAgentsDir(cwd);
1351
+ const userAgentsDir = getUserAgentsDir();
1352
+ // Extra DotAgent repos registered via `agents repo add`. Looked up last so
1353
+ // project/user/system repos win on name collisions.
1354
+ const extraRepos = getEnabledExtraRepos();
1169
1355
  const available = getAvailableResources(cwd);
1356
+ // Fast guard: skip the entire sync when no selection is active and nothing
1357
+ // has changed since the last full sync. Drops steady-state cost from ~16s
1358
+ // (unconditional file copies) to ~2ms (stat calls + manifest read).
1359
+ if (!selection && !options.force) {
1360
+ const manifest = loadSyncManifest(agent, version);
1361
+ if (manifest && !isSyncStale(manifest, available, agent, version, cwd)) {
1362
+ return { commands: false, skills: false, hooks: false, memory: [], permissions: false, mcp: [], subagents: [], plugins: [] };
1363
+ }
1364
+ }
1170
1365
  // Helper: remove a path (symlink or real) if it exists
1171
1366
  const removePath = (p) => {
1172
1367
  try {
@@ -1185,12 +1380,16 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
1185
1380
  fs.mkdirSync(dest, { recursive: true });
1186
1381
  const entries = fs.readdirSync(src, { withFileTypes: true });
1187
1382
  for (const entry of entries) {
1188
- const srcPath = path.join(src, entry.name);
1189
- const destPath = path.join(dest, entry.name);
1383
+ if (entry.isSymbolicLink())
1384
+ continue;
1385
+ if (shouldSkillEntryBeSkipped(entry.name))
1386
+ continue;
1387
+ const srcPath = safeJoin(src, entry.name);
1388
+ const destPath = safeJoin(dest, entry.name);
1190
1389
  if (entry.isDirectory()) {
1191
1390
  copyDir(srcPath, destPath);
1192
1391
  }
1193
- else {
1392
+ else if (entry.isFile()) {
1194
1393
  fs.copyFileSync(srcPath, destPath);
1195
1394
  }
1196
1395
  }
@@ -1199,8 +1398,10 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
1199
1398
  const resolveSelection = (sel, available) => {
1200
1399
  if (sel === 'all')
1201
1400
  return available;
1202
- if (Array.isArray(sel))
1203
- return sel;
1401
+ if (Array.isArray(sel)) {
1402
+ const availableSet = new Set(available);
1403
+ return sel.filter((item) => availableSet.has(item));
1404
+ }
1204
1405
  return [];
1205
1406
  };
1206
1407
  // Sync commands
@@ -1208,24 +1409,38 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
1208
1409
  ? resolveSelection(selection.commands, available.commands)
1209
1410
  : available.commands; // No selection = sync all
1210
1411
  if (commandsToSync.length > 0 && COMMANDS_CAPABLE_AGENTS.includes(agent)) {
1211
- const centralCommands = getCommandsDir();
1212
- const projectCommandsDir = projectAgentsDir ? path.join(projectAgentsDir, 'commands') : null;
1213
1412
  const commandsTarget = path.join(agentDir, agentConfig.commandsSubdir);
1214
- fs.mkdirSync(commandsTarget, { recursive: true });
1413
+ const commandsAsSkills = shouldInstallCommandAsSkill(agent, version);
1414
+ if (commandsAsSkills) {
1415
+ removePath(commandsTarget);
1416
+ }
1417
+ else {
1418
+ fs.mkdirSync(commandsTarget, { recursive: true });
1419
+ }
1215
1420
  const syncedCommands = [];
1216
1421
  for (const cmd of commandsToSync) {
1217
- const projectSource = projectCommandsDir ? path.join(projectCommandsDir, `${cmd}.md`) : null;
1218
- const userSource = path.join(centralCommands, `${cmd}.md`);
1219
- const srcFile = projectSource && fs.existsSync(projectSource) ? projectSource : userSource;
1220
- if (!fs.existsSync(srcFile))
1422
+ const resolved = resolveResource('commands', `${cmd}.md`, cwd);
1423
+ if (!resolved || fs.lstatSync(resolved.path).isSymbolicLink())
1221
1424
  continue;
1222
- if (agentConfig.format === 'toml') {
1425
+ const srcFile = resolved.path;
1426
+ if (commandsAsSkills) {
1427
+ const skillSourceDirs = [
1428
+ projectAgentsDir ? path.join(projectAgentsDir, 'skills') : null,
1429
+ path.join(userAgentsDir, 'skills'),
1430
+ getSkillsDir(),
1431
+ ...extraRepos.map((e) => path.join(e.dir, 'skills')),
1432
+ ];
1433
+ const installed = installCommandSkillToVersion(agentDir, cmd, srcFile, skillSourceDirs);
1434
+ if (!installed.success)
1435
+ continue;
1436
+ }
1437
+ else if (agentConfig.format === 'toml') {
1223
1438
  const content = fs.readFileSync(srcFile, 'utf-8');
1224
1439
  const tomlContent = markdownToToml(cmd, content);
1225
- fs.writeFileSync(path.join(commandsTarget, `${cmd}.toml`), tomlContent);
1440
+ fs.writeFileSync(safeJoin(commandsTarget, `${cmd}.toml`), tomlContent);
1226
1441
  }
1227
1442
  else {
1228
- fs.copyFileSync(srcFile, path.join(commandsTarget, `${cmd}.md`));
1443
+ fs.copyFileSync(srcFile, safeJoin(commandsTarget, `${cmd}.md`));
1229
1444
  }
1230
1445
  syncedCommands.push(cmd);
1231
1446
  }
@@ -1245,17 +1460,17 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
1245
1460
  ? resolveSelection(selection.skills, available.skills)
1246
1461
  : available.skills;
1247
1462
  if (skillsToSync.length > 0) {
1248
- const centralSkills = getSkillsDir();
1249
- const projectSkills = projectAgentsDir ? path.join(projectAgentsDir, 'skills') : null;
1250
1463
  const skillsTarget = path.join(agentDir, 'skills');
1251
1464
  fs.mkdirSync(skillsTarget, { recursive: true });
1252
1465
  const syncedSkills = [];
1253
1466
  for (const skill of skillsToSync) {
1254
- const projectSource = projectSkills ? path.join(projectSkills, skill) : null;
1255
- const srcDir = projectSource && fs.existsSync(projectSource) ? projectSource : path.join(centralSkills, skill);
1256
- if (!fs.existsSync(srcDir))
1467
+ const resolved = resolveResource('skills', skill, cwd);
1468
+ const srcDir = resolved && fs.existsSync(resolved.path) && fs.lstatSync(resolved.path).isDirectory()
1469
+ ? resolved.path
1470
+ : null;
1471
+ if (!srcDir)
1257
1472
  continue;
1258
- const destDir = path.join(skillsTarget, skill);
1473
+ const destDir = safeJoin(skillsTarget, skill);
1259
1474
  removePath(destDir);
1260
1475
  copyDir(srcDir, destDir);
1261
1476
  syncedSkills.push(skill);
@@ -1266,11 +1481,11 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
1266
1481
  }
1267
1482
  }
1268
1483
  }
1269
- // Sync hooks (if agent supports them)
1484
+ // Sync hooks (if agent supports them at this version)
1485
+ const hooksGate = supports(agent, 'hooks', version);
1270
1486
  if (agentConfig.supportsHooks) {
1271
- // Version gate: Codex hooks require >= CODEX_HOOKS_MIN_VERSION
1272
- if (agent === 'codex' && compareVersions(version, CODEX_HOOKS_MIN_VERSION) < 0) {
1273
- console.warn(`hooks skipped: codex@${version} < ${CODEX_HOOKS_MIN_VERSION}`);
1487
+ if (!hooksGate.ok) {
1488
+ console.warn(explainSkip(agent, 'hooks', hooksGate, version) + ' -- skipped');
1274
1489
  }
1275
1490
  else {
1276
1491
  const hooksToSync = selection
@@ -1278,25 +1493,63 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
1278
1493
  : available.hooks;
1279
1494
  if (hooksToSync.length > 0) {
1280
1495
  const centralHooks = getHooksDir();
1281
- const projectHooksDir = projectAgentsDir ? path.join(projectAgentsDir, 'hooks') : null;
1282
1496
  const hooksTarget = path.join(agentDir, 'hooks');
1283
1497
  fs.mkdirSync(hooksTarget, { recursive: true });
1284
1498
  const syncedHooks = [];
1285
1499
  for (const hook of hooksToSync) {
1286
- const projectSource = projectHooksDir ? path.join(projectHooksDir, hook) : null;
1287
- const srcFile = projectSource && fs.existsSync(projectSource) ? projectSource : path.join(centralHooks, hook);
1288
- if (!fs.existsSync(srcFile))
1500
+ // Hooks are executable shell scripts that run on agent events. We
1501
+ // intentionally do NOT pull from the project's own .agents/hooks/
1502
+ // directory: that would let any cloned public repo plant an
1503
+ // executable that fires the next time the user runs `agents use`
1504
+ // inside that repo. Hooks must come from the user's central
1505
+ // ~/.agents/hooks/ or an explicitly enabled extra repo.
1506
+ const candidates = [
1507
+ safeJoin(path.join(userAgentsDir, 'hooks'), hook),
1508
+ safeJoin(centralHooks, hook),
1509
+ ...extraRepos.map((e) => safeJoin(path.join(e.dir, 'hooks'), hook)),
1510
+ ];
1511
+ const srcFile = candidates.find((p) => p && fs.existsSync(p) && !fs.lstatSync(p).isSymbolicLink()) || null;
1512
+ if (!srcFile)
1289
1513
  continue;
1290
- const destFile = path.join(hooksTarget, hook);
1514
+ const destFile = safeJoin(hooksTarget, hook);
1291
1515
  fs.copyFileSync(srcFile, destFile);
1292
1516
  fs.chmodSync(destFile, 0o755);
1293
1517
  syncedHooks.push(hook);
1294
1518
  }
1519
+ // Remove orphan hook files that exist in version home but not in any trusted source.
1520
+ const centralHookNames = new Set(fs.existsSync(getHooksDir())
1521
+ ? fs.readdirSync(getHooksDir()).filter(f => !f.startsWith('.'))
1522
+ : []);
1523
+ {
1524
+ const hooksDir = path.join(userAgentsDir, 'hooks');
1525
+ if (fs.existsSync(hooksDir)) {
1526
+ for (const file of fs.readdirSync(hooksDir).filter(f => !f.startsWith('.'))) {
1527
+ centralHookNames.add(file);
1528
+ }
1529
+ }
1530
+ }
1531
+ for (const extra of extraRepos) {
1532
+ const hooksDir = path.join(extra.dir, 'hooks');
1533
+ if (fs.existsSync(hooksDir)) {
1534
+ for (const file of fs.readdirSync(hooksDir).filter(f => !f.startsWith('.'))) {
1535
+ centralHookNames.add(file);
1536
+ }
1537
+ }
1538
+ }
1539
+ if (fs.existsSync(hooksTarget)) {
1540
+ for (const file of fs.readdirSync(hooksTarget).filter(f => !f.startsWith('.'))) {
1541
+ if (!centralHookNames.has(file)) {
1542
+ removePath(safeJoin(hooksTarget, file));
1543
+ }
1544
+ }
1545
+ }
1295
1546
  result.hooks = syncedHooks.length > 0;
1296
1547
  if (syncedHooks.length > 0) {
1297
1548
  recordVersionResources(agent, version, 'hooks', syncedHooks);
1298
1549
  }
1299
- if (agent === 'claude' || agent === 'codex') {
1550
+ // Register hooks into agent-native settings.json/hooks.json. Gemini
1551
+ // shipped hooks in 0.26.0; gate already passed above so this is safe.
1552
+ if (agent === 'claude' || agent === 'codex' || agent === 'gemini') {
1300
1553
  registerHooksToSettings(agent, versionHome);
1301
1554
  }
1302
1555
  }
@@ -1307,19 +1560,23 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
1307
1560
  ? resolveSelection(selection.memory, available.memory)
1308
1561
  : available.memory;
1309
1562
  if (memoryToSync.length > 0 && COMMANDS_CAPABLE_AGENTS.includes(agent)) {
1310
- const centralMemory = getMemoryDir();
1311
- const projectMemoryDir = projectAgentsDir ? path.join(projectAgentsDir, 'memory') : null;
1563
+ const centralMemory = getResolvedRulesDir();
1564
+ const projectMemoryDir = projectAgentsDir ? path.join(projectAgentsDir, 'rules') : null;
1565
+ const userMemoryDir = getUserRulesDir();
1312
1566
  const syncedMemory = [];
1313
1567
  const agentSupportsImports = !!agentConfig.capabilities.memoryImports;
1314
1568
  for (const mem of memoryToSync) {
1315
- const projectSource = projectMemoryDir ? path.join(projectMemoryDir, `${mem}.md`) : null;
1316
- const srcFile = projectSource && fs.existsSync(projectSource)
1317
- ? projectSource
1318
- : path.join(centralMemory, `${mem}.md`);
1319
- if (!fs.existsSync(srcFile))
1569
+ const candidates = [
1570
+ projectMemoryDir ? safeJoin(projectMemoryDir, `${mem}.md`) : null,
1571
+ safeJoin(userMemoryDir, `${mem}.md`),
1572
+ safeJoin(centralMemory, `${mem}.md`),
1573
+ ...extraRepos.map((e) => safeJoin(path.join(e.dir, 'rules'), `${mem}.md`)),
1574
+ ];
1575
+ const srcFile = candidates.find((p) => p && fs.existsSync(p) && !fs.lstatSync(p).isSymbolicLink()) || null;
1576
+ if (!srcFile)
1320
1577
  continue;
1321
1578
  const targetName = mem === 'AGENTS' ? agentConfig.instructionsFile : `${mem}.md`;
1322
- const destFile = path.join(agentDir, targetName);
1579
+ const destFile = safeJoin(agentDir, targetName);
1323
1580
  removePath(destFile);
1324
1581
  // For the primary memory file (AGENTS.md), agents that don't natively
1325
1582
  // resolve @-imports get a compiled (inlined) copy + sidecar manifest.
@@ -1338,13 +1595,43 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
1338
1595
  recordVersionResources(agent, version, 'memory', syncedMemory);
1339
1596
  }
1340
1597
  }
1341
- // Apply permissions (if agent supports them)
1342
- // Permissions are now stored as groups in ~/.agents/permissions/groups/
1598
+ // Apply permissions (if agent supports them).
1599
+ // Groups live in ~/.agents/permissions/groups/. Optional recipes in
1600
+ // ~/.agents/permissions/sets/<name>.yaml pick a subset via `includes:`.
1601
+ // If AGENTS_PERMISSION_SET is set, we resolve that recipe and use its
1602
+ // includes list as the group filter (intersected with groups on disk).
1343
1603
  const permissionGroups = discoverPermissionGroups();
1344
1604
  const allGroupNames = permissionGroups.map(g => g.name);
1345
- const permsToSync = selection
1346
- ? resolveSelection(selection.permissions, allGroupNames)
1347
- : (PERMISSIONS_CAPABLE_AGENTS.includes(agent) ? allGroupNames : []);
1605
+ const activeSetName = getActivePermissionSetName();
1606
+ let setFilteredGroups = null;
1607
+ if (activeSetName) {
1608
+ const recipe = readPermissionSetRecipe(activeSetName);
1609
+ if (recipe) {
1610
+ const available = new Set(allGroupNames);
1611
+ setFilteredGroups = recipe.includes.filter(g => available.has(g));
1612
+ }
1613
+ else {
1614
+ console.warn(`${PERMISSION_SET_ENV_VAR}=${activeSetName} but no recipe at ~/.agents/permissions/sets/${activeSetName}.yaml — falling back to all groups`);
1615
+ }
1616
+ }
1617
+ let permsToSync;
1618
+ if (selection) {
1619
+ permsToSync = resolveSelection(selection.permissions, allGroupNames);
1620
+ // If a set recipe is active, the recipe's includes list always wins —
1621
+ // even when the caller passed an explicit array via selection. Without
1622
+ // this intersection, `agents add`'s buildAutomaticSelection would pass
1623
+ // every group name discovered on disk (including 99-deny), bypassing
1624
+ // the sandbox filter.
1625
+ if (setFilteredGroups) {
1626
+ const filterSet = new Set(setFilteredGroups);
1627
+ permsToSync = permsToSync.filter(g => filterSet.has(g));
1628
+ }
1629
+ }
1630
+ else {
1631
+ permsToSync = PERMISSIONS_CAPABLE_AGENTS.includes(agent)
1632
+ ? (setFilteredGroups ?? allGroupNames)
1633
+ : [];
1634
+ }
1348
1635
  if (permsToSync.length > 0 && PERMISSIONS_CAPABLE_AGENTS.includes(agent)) {
1349
1636
  // Build permissions from selected groups
1350
1637
  const builtPerms = buildPermissionsFromGroups(permsToSync);
@@ -1386,12 +1673,12 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
1386
1673
  const agentsDir = path.join(agentDir, 'agents');
1387
1674
  fs.mkdirSync(agentsDir, { recursive: true });
1388
1675
  const transformed = transformSubagentForClaude(subagent.path);
1389
- fs.writeFileSync(path.join(agentsDir, `${subagent.name}.md`), transformed);
1676
+ fs.writeFileSync(safeJoin(agentsDir, `${subagent.name}.md`), transformed);
1390
1677
  result.subagents.push(subagent.name);
1391
1678
  }
1392
1679
  else if (agent === 'openclaw') {
1393
1680
  // OpenClaw: copy full directory, rename AGENT.md -> AGENTS.md
1394
- const targetDir = path.join(versionHome, '.openclaw', subagent.name);
1681
+ const targetDir = safeJoin(path.join(versionHome, '.openclaw'), subagent.name);
1395
1682
  const syncResult = syncSubagentToOpenclaw(subagent.path, targetDir);
1396
1683
  if (syncResult.success) {
1397
1684
  result.subagents.push(subagent.name);
@@ -1427,6 +1714,10 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
1427
1714
  recordVersionResources(agent, version, 'plugins', result.plugins);
1428
1715
  }
1429
1716
  }
1717
+ // Write manifest after a successful full sync so the next launch can skip this work.
1718
+ if (!selection) {
1719
+ saveSyncManifest(agent, version, buildManifest(agent, version, available, cwd));
1720
+ }
1430
1721
  return result;
1431
1722
  }
1432
1723
  /**
@@ -1712,4 +2003,3 @@ export async function promptAgentVersionSelection(availableAgents, options = {})
1712
2003
  }
1713
2004
  return { selectedAgents, versionSelections };
1714
2005
  }
1715
- //# sourceMappingURL=versions.js.map