@phnx-labs/agents-cli 0.1.0 → 1.14.1

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 (554) hide show
  1. package/CHANGELOG.md +7 -1
  2. package/README.md +283 -372
  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 +8 -1
  8. package/dist/commands/cloud.js +108 -22
  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 +96 -27
  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 +16 -4
  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 +9 -1
  38. package/dist/commands/profiles.js +56 -7
  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 +58 -128
  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 +8 -1
  56. package/dist/commands/secrets.js +235 -63
  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 +188 -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 +317 -24
  83. package/dist/index.d.ts +7 -2
  84. package/dist/index.js +172 -34
  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 +197 -21
  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 +9 -3
  104. package/dist/lib/cloud/codex.js +53 -13
  105. package/dist/lib/cloud/factory.d.ts +8 -3
  106. package/dist/lib/cloud/factory.js +19 -3
  107. package/dist/lib/cloud/registry.d.ts +10 -1
  108. package/dist/lib/cloud/registry.js +14 -3
  109. package/dist/lib/cloud/rush.d.ts +63 -3
  110. package/dist/lib/cloud/rush.js +273 -20
  111. package/dist/lib/cloud/store.d.ts +13 -1
  112. package/dist/lib/cloud/store.js +23 -4
  113. package/dist/lib/cloud/stream.d.ts +6 -1
  114. package/dist/lib/cloud/stream.js +95 -39
  115. package/dist/lib/cloud/types.d.ts +153 -8
  116. package/dist/lib/cloud/types.js +34 -2
  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 +23 -6
  128. package/dist/lib/exec.js +53 -17
  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 +119 -101
  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 +10 -4
  156. package/dist/lib/models.js +36 -15
  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 +10 -1
  168. package/dist/lib/profiles-presets.js +9 -1
  169. package/dist/lib/profiles.d.ts +35 -1
  170. package/dist/lib/profiles.js +36 -15
  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 +40 -13
  180. package/dist/lib/rotate.js +238 -40
  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 → secrets/bundles.d.ts} +12 -2
  195. package/dist/lib/{secrets-bundles.js → secrets/bundles.js} +38 -17
  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 +12 -1
  203. package/dist/lib/session/artifacts.js +25 -5
  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 +16 -2
  219. package/dist/lib/session/types.js +10 -2
  220. package/dist/lib/shims.d.ts +64 -5
  221. package/dist/lib/shims.js +309 -47
  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 +112 -27
  227. package/dist/lib/state.js +320 -148
  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 +32 -1
  257. package/dist/lib/usage.js +70 -6
  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 +33 -4
  261. package/dist/lib/versions.js +376 -108
  262. package/package.json +32 -17
  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/cloud.d.ts.map +0 -1
  269. package/dist/commands/cloud.js.map +0 -1
  270. package/dist/commands/commands.d.ts.map +0 -1
  271. package/dist/commands/commands.js.map +0 -1
  272. package/dist/commands/daemon.d.ts.map +0 -1
  273. package/dist/commands/daemon.js.map +0 -1
  274. package/dist/commands/drive.d.ts.map +0 -1
  275. package/dist/commands/drive.js.map +0 -1
  276. package/dist/commands/exec.d.ts.map +0 -1
  277. package/dist/commands/exec.js.map +0 -1
  278. package/dist/commands/fork.d.ts.map +0 -1
  279. package/dist/commands/fork.js.map +0 -1
  280. package/dist/commands/hooks.d.ts.map +0 -1
  281. package/dist/commands/hooks.js.map +0 -1
  282. package/dist/commands/init.d.ts.map +0 -1
  283. package/dist/commands/init.js.map +0 -1
  284. package/dist/commands/mcp.d.ts.map +0 -1
  285. package/dist/commands/mcp.js.map +0 -1
  286. package/dist/commands/models.d.ts.map +0 -1
  287. package/dist/commands/models.js.map +0 -1
  288. package/dist/commands/packages.d.ts.map +0 -1
  289. package/dist/commands/packages.js.map +0 -1
  290. package/dist/commands/permissions.d.ts.map +0 -1
  291. package/dist/commands/permissions.js.map +0 -1
  292. package/dist/commands/plugins.d.ts.map +0 -1
  293. package/dist/commands/plugins.js.map +0 -1
  294. package/dist/commands/profiles.d.ts.map +0 -1
  295. package/dist/commands/profiles.js.map +0 -1
  296. package/dist/commands/pty.d.ts.map +0 -1
  297. package/dist/commands/pty.js.map +0 -1
  298. package/dist/commands/pull.d.ts.map +0 -1
  299. package/dist/commands/pull.js.map +0 -1
  300. package/dist/commands/push.d.ts +0 -3
  301. package/dist/commands/push.d.ts.map +0 -1
  302. package/dist/commands/push.js +0 -180
  303. package/dist/commands/push.js.map +0 -1
  304. package/dist/commands/refresh-memory.d.ts.map +0 -1
  305. package/dist/commands/refresh-memory.js.map +0 -1
  306. package/dist/commands/resource-view.d.ts.map +0 -1
  307. package/dist/commands/resource-view.js.map +0 -1
  308. package/dist/commands/routines.d.ts.map +0 -1
  309. package/dist/commands/routines.js.map +0 -1
  310. package/dist/commands/rules.d.ts.map +0 -1
  311. package/dist/commands/rules.js.map +0 -1
  312. package/dist/commands/secrets.d.ts.map +0 -1
  313. package/dist/commands/secrets.js.map +0 -1
  314. package/dist/commands/sessions-picker.d.ts.map +0 -1
  315. package/dist/commands/sessions-picker.js.map +0 -1
  316. package/dist/commands/sessions.d.ts.map +0 -1
  317. package/dist/commands/sessions.js.map +0 -1
  318. package/dist/commands/skills.d.ts.map +0 -1
  319. package/dist/commands/skills.js.map +0 -1
  320. package/dist/commands/status.d.ts.map +0 -1
  321. package/dist/commands/status.js.map +0 -1
  322. package/dist/commands/subagents.d.ts.map +0 -1
  323. package/dist/commands/subagents.js.map +0 -1
  324. package/dist/commands/sync.d.ts.map +0 -1
  325. package/dist/commands/sync.js.map +0 -1
  326. package/dist/commands/teams-picker.d.ts.map +0 -1
  327. package/dist/commands/teams-picker.js.map +0 -1
  328. package/dist/commands/teams.d.ts.map +0 -1
  329. package/dist/commands/teams.js.map +0 -1
  330. package/dist/commands/utils.d.ts.map +0 -1
  331. package/dist/commands/utils.js.map +0 -1
  332. package/dist/commands/versions.d.ts.map +0 -1
  333. package/dist/commands/versions.js.map +0 -1
  334. package/dist/commands/view.d.ts.map +0 -1
  335. package/dist/commands/view.js.map +0 -1
  336. package/dist/index.d.ts.map +0 -1
  337. package/dist/index.js.map +0 -1
  338. package/dist/lib/__tests__/bugfixes.test.d.ts +0 -2
  339. package/dist/lib/__tests__/bugfixes.test.d.ts.map +0 -1
  340. package/dist/lib/__tests__/bugfixes.test.js +0 -192
  341. package/dist/lib/__tests__/bugfixes.test.js.map +0 -1
  342. package/dist/lib/__tests__/exec.test.d.ts +0 -2
  343. package/dist/lib/__tests__/exec.test.d.ts.map +0 -1
  344. package/dist/lib/__tests__/exec.test.js +0 -446
  345. package/dist/lib/__tests__/exec.test.js.map +0 -1
  346. package/dist/lib/__tests__/git-sync.test.d.ts +0 -2
  347. package/dist/lib/__tests__/git-sync.test.d.ts.map +0 -1
  348. package/dist/lib/__tests__/git-sync.test.js +0 -138
  349. package/dist/lib/__tests__/git-sync.test.js.map +0 -1
  350. package/dist/lib/__tests__/hooks.test.d.ts +0 -2
  351. package/dist/lib/__tests__/hooks.test.d.ts.map +0 -1
  352. package/dist/lib/__tests__/hooks.test.js +0 -203
  353. package/dist/lib/__tests__/hooks.test.js.map +0 -1
  354. package/dist/lib/__tests__/memory-compile.test.d.ts +0 -2
  355. package/dist/lib/__tests__/memory-compile.test.d.ts.map +0 -1
  356. package/dist/lib/__tests__/memory-compile.test.js +0 -95
  357. package/dist/lib/__tests__/memory-compile.test.js.map +0 -1
  358. package/dist/lib/__tests__/models.test.d.ts +0 -2
  359. package/dist/lib/__tests__/models.test.d.ts.map +0 -1
  360. package/dist/lib/__tests__/models.test.js +0 -239
  361. package/dist/lib/__tests__/models.test.js.map +0 -1
  362. package/dist/lib/__tests__/rotate.test.d.ts +0 -2
  363. package/dist/lib/__tests__/rotate.test.d.ts.map +0 -1
  364. package/dist/lib/__tests__/rotate.test.js +0 -80
  365. package/dist/lib/__tests__/rotate.test.js.map +0 -1
  366. package/dist/lib/__tests__/secrets-bundles.test.d.ts +0 -2
  367. package/dist/lib/__tests__/secrets-bundles.test.d.ts.map +0 -1
  368. package/dist/lib/__tests__/secrets-bundles.test.js +0 -104
  369. package/dist/lib/__tests__/secrets-bundles.test.js.map +0 -1
  370. package/dist/lib/__tests__/secrets.test.d.ts +0 -2
  371. package/dist/lib/__tests__/secrets.test.d.ts.map +0 -1
  372. package/dist/lib/__tests__/secrets.test.js +0 -90
  373. package/dist/lib/__tests__/secrets.test.js.map +0 -1
  374. package/dist/lib/__tests__/shims.test.d.ts +0 -2
  375. package/dist/lib/__tests__/shims.test.d.ts.map +0 -1
  376. package/dist/lib/__tests__/shims.test.js +0 -39
  377. package/dist/lib/__tests__/shims.test.js.map +0 -1
  378. package/dist/lib/__tests__/usage.test.d.ts +0 -2
  379. package/dist/lib/__tests__/usage.test.d.ts.map +0 -1
  380. package/dist/lib/__tests__/usage.test.js +0 -220
  381. package/dist/lib/__tests__/usage.test.js.map +0 -1
  382. package/dist/lib/__tests__/versions.test.d.ts +0 -2
  383. package/dist/lib/__tests__/versions.test.d.ts.map +0 -1
  384. package/dist/lib/__tests__/versions.test.js +0 -63
  385. package/dist/lib/__tests__/versions.test.js.map +0 -1
  386. package/dist/lib/agents.d.ts.map +0 -1
  387. package/dist/lib/agents.js.map +0 -1
  388. package/dist/lib/artifact-actions.d.ts.map +0 -1
  389. package/dist/lib/artifact-actions.js.map +0 -1
  390. package/dist/lib/cloud/codex.d.ts.map +0 -1
  391. package/dist/lib/cloud/codex.js.map +0 -1
  392. package/dist/lib/cloud/factory.d.ts.map +0 -1
  393. package/dist/lib/cloud/factory.js.map +0 -1
  394. package/dist/lib/cloud/registry.d.ts.map +0 -1
  395. package/dist/lib/cloud/registry.js.map +0 -1
  396. package/dist/lib/cloud/rush.d.ts.map +0 -1
  397. package/dist/lib/cloud/rush.js.map +0 -1
  398. package/dist/lib/cloud/store.d.ts.map +0 -1
  399. package/dist/lib/cloud/store.js.map +0 -1
  400. package/dist/lib/cloud/stream.d.ts.map +0 -1
  401. package/dist/lib/cloud/stream.js.map +0 -1
  402. package/dist/lib/cloud/types.d.ts.map +0 -1
  403. package/dist/lib/cloud/types.js.map +0 -1
  404. package/dist/lib/commands.d.ts.map +0 -1
  405. package/dist/lib/commands.js.map +0 -1
  406. package/dist/lib/convert.d.ts.map +0 -1
  407. package/dist/lib/convert.js.map +0 -1
  408. package/dist/lib/daemon.d.ts.map +0 -1
  409. package/dist/lib/daemon.js.map +0 -1
  410. package/dist/lib/drive-sync.d.ts.map +0 -1
  411. package/dist/lib/drive-sync.js.map +0 -1
  412. package/dist/lib/exec.d.ts.map +0 -1
  413. package/dist/lib/exec.js.map +0 -1
  414. package/dist/lib/factory.d.ts +0 -57
  415. package/dist/lib/factory.d.ts.map +0 -1
  416. package/dist/lib/factory.js +0 -110
  417. package/dist/lib/factory.js.map +0 -1
  418. package/dist/lib/git.d.ts.map +0 -1
  419. package/dist/lib/git.js.map +0 -1
  420. package/dist/lib/help.d.ts.map +0 -1
  421. package/dist/lib/help.js.map +0 -1
  422. package/dist/lib/hooks.d.ts.map +0 -1
  423. package/dist/lib/hooks.js.map +0 -1
  424. package/dist/lib/manifest.d.ts.map +0 -1
  425. package/dist/lib/manifest.js.map +0 -1
  426. package/dist/lib/markdown.d.ts.map +0 -1
  427. package/dist/lib/markdown.js.map +0 -1
  428. package/dist/lib/mcp.d.ts.map +0 -1
  429. package/dist/lib/mcp.js.map +0 -1
  430. package/dist/lib/memory-compile.d.ts.map +0 -1
  431. package/dist/lib/memory-compile.js.map +0 -1
  432. package/dist/lib/memory.d.ts.map +0 -1
  433. package/dist/lib/memory.js.map +0 -1
  434. package/dist/lib/models.d.ts.map +0 -1
  435. package/dist/lib/models.js.map +0 -1
  436. package/dist/lib/permissions.d.ts.map +0 -1
  437. package/dist/lib/permissions.js.map +0 -1
  438. package/dist/lib/picker.d.ts.map +0 -1
  439. package/dist/lib/picker.js.map +0 -1
  440. package/dist/lib/plugins.d.ts.map +0 -1
  441. package/dist/lib/plugins.js.map +0 -1
  442. package/dist/lib/profiles-keychain.d.ts +0 -3
  443. package/dist/lib/profiles-keychain.d.ts.map +0 -1
  444. package/dist/lib/profiles-keychain.js +0 -10
  445. package/dist/lib/profiles-keychain.js.map +0 -1
  446. package/dist/lib/profiles-presets.d.ts.map +0 -1
  447. package/dist/lib/profiles-presets.js.map +0 -1
  448. package/dist/lib/profiles.d.ts.map +0 -1
  449. package/dist/lib/profiles.js.map +0 -1
  450. package/dist/lib/pty-client.d.ts.map +0 -1
  451. package/dist/lib/pty-client.js.map +0 -1
  452. package/dist/lib/pty-server.d.ts.map +0 -1
  453. package/dist/lib/pty-server.js.map +0 -1
  454. package/dist/lib/registry.d.ts.map +0 -1
  455. package/dist/lib/registry.js.map +0 -1
  456. package/dist/lib/resources.d.ts.map +0 -1
  457. package/dist/lib/resources.js.map +0 -1
  458. package/dist/lib/rotate.d.ts.map +0 -1
  459. package/dist/lib/rotate.js.map +0 -1
  460. package/dist/lib/routines.d.ts.map +0 -1
  461. package/dist/lib/routines.js.map +0 -1
  462. package/dist/lib/runner.d.ts.map +0 -1
  463. package/dist/lib/runner.js.map +0 -1
  464. package/dist/lib/sandbox.d.ts.map +0 -1
  465. package/dist/lib/sandbox.js.map +0 -1
  466. package/dist/lib/scheduler.d.ts.map +0 -1
  467. package/dist/lib/scheduler.js.map +0 -1
  468. package/dist/lib/secrets-bundles.d.ts.map +0 -1
  469. package/dist/lib/secrets-bundles.js.map +0 -1
  470. package/dist/lib/secrets.d.ts +0 -27
  471. package/dist/lib/secrets.d.ts.map +0 -1
  472. package/dist/lib/secrets.js +0 -127
  473. package/dist/lib/secrets.js.map +0 -1
  474. package/dist/lib/session/__tests__/db.test.d.ts +0 -2
  475. package/dist/lib/session/__tests__/db.test.d.ts.map +0 -1
  476. package/dist/lib/session/__tests__/db.test.js +0 -54
  477. package/dist/lib/session/__tests__/db.test.js.map +0 -1
  478. package/dist/lib/session/__tests__/discover.test.d.ts +0 -2
  479. package/dist/lib/session/__tests__/discover.test.d.ts.map +0 -1
  480. package/dist/lib/session/__tests__/discover.test.js +0 -63
  481. package/dist/lib/session/__tests__/discover.test.js.map +0 -1
  482. package/dist/lib/session/__tests__/prompt.test.d.ts +0 -2
  483. package/dist/lib/session/__tests__/prompt.test.d.ts.map +0 -1
  484. package/dist/lib/session/__tests__/prompt.test.js +0 -44
  485. package/dist/lib/session/__tests__/prompt.test.js.map +0 -1
  486. package/dist/lib/session/__tests__/render.test.d.ts +0 -2
  487. package/dist/lib/session/__tests__/render.test.d.ts.map +0 -1
  488. package/dist/lib/session/__tests__/render.test.js +0 -602
  489. package/dist/lib/session/__tests__/render.test.js.map +0 -1
  490. package/dist/lib/session/artifacts.d.ts.map +0 -1
  491. package/dist/lib/session/artifacts.js.map +0 -1
  492. package/dist/lib/session/db.d.ts.map +0 -1
  493. package/dist/lib/session/db.js.map +0 -1
  494. package/dist/lib/session/discover.d.ts.map +0 -1
  495. package/dist/lib/session/discover.js.map +0 -1
  496. package/dist/lib/session/parse.d.ts.map +0 -1
  497. package/dist/lib/session/parse.js.map +0 -1
  498. package/dist/lib/session/prompt.d.ts.map +0 -1
  499. package/dist/lib/session/prompt.js.map +0 -1
  500. package/dist/lib/session/prompt.test.d.ts +0 -2
  501. package/dist/lib/session/prompt.test.d.ts.map +0 -1
  502. package/dist/lib/session/prompt.test.js +0 -57
  503. package/dist/lib/session/prompt.test.js.map +0 -1
  504. package/dist/lib/session/render.d.ts.map +0 -1
  505. package/dist/lib/session/render.js.map +0 -1
  506. package/dist/lib/session/team-filter.d.ts.map +0 -1
  507. package/dist/lib/session/team-filter.js.map +0 -1
  508. package/dist/lib/session/team-filter.test.d.ts +0 -2
  509. package/dist/lib/session/team-filter.test.d.ts.map +0 -1
  510. package/dist/lib/session/team-filter.test.js +0 -157
  511. package/dist/lib/session/team-filter.test.js.map +0 -1
  512. package/dist/lib/session/types.d.ts.map +0 -1
  513. package/dist/lib/session/types.js.map +0 -1
  514. package/dist/lib/shims.d.ts.map +0 -1
  515. package/dist/lib/shims.js.map +0 -1
  516. package/dist/lib/skills.d.ts.map +0 -1
  517. package/dist/lib/skills.js.map +0 -1
  518. package/dist/lib/state.d.ts.map +0 -1
  519. package/dist/lib/state.js.map +0 -1
  520. package/dist/lib/subagents.d.ts.map +0 -1
  521. package/dist/lib/subagents.js.map +0 -1
  522. package/dist/lib/teams/agents.d.ts.map +0 -1
  523. package/dist/lib/teams/agents.js.map +0 -1
  524. package/dist/lib/teams/api.d.ts.map +0 -1
  525. package/dist/lib/teams/api.js.map +0 -1
  526. package/dist/lib/teams/cloud.d.ts +0 -11
  527. package/dist/lib/teams/cloud.d.ts.map +0 -1
  528. package/dist/lib/teams/cloud.js +0 -169
  529. package/dist/lib/teams/cloud.js.map +0 -1
  530. package/dist/lib/teams/debug.d.ts.map +0 -1
  531. package/dist/lib/teams/debug.js.map +0 -1
  532. package/dist/lib/teams/file_ops.d.ts.map +0 -1
  533. package/dist/lib/teams/file_ops.js.map +0 -1
  534. package/dist/lib/teams/parsers.d.ts.map +0 -1
  535. package/dist/lib/teams/parsers.js.map +0 -1
  536. package/dist/lib/teams/persistence.d.ts.map +0 -1
  537. package/dist/lib/teams/persistence.js.map +0 -1
  538. package/dist/lib/teams/ralph.d.ts +0 -8
  539. package/dist/lib/teams/ralph.d.ts.map +0 -1
  540. package/dist/lib/teams/ralph.js +0 -59
  541. package/dist/lib/teams/ralph.js.map +0 -1
  542. package/dist/lib/teams/registry.d.ts.map +0 -1
  543. package/dist/lib/teams/registry.js.map +0 -1
  544. package/dist/lib/teams/summarizer.d.ts.map +0 -1
  545. package/dist/lib/teams/summarizer.js.map +0 -1
  546. package/dist/lib/template.d.ts.map +0 -1
  547. package/dist/lib/template.js.map +0 -1
  548. package/dist/lib/types.d.ts.map +0 -1
  549. package/dist/lib/types.js.map +0 -1
  550. package/dist/lib/usage.d.ts.map +0 -1
  551. package/dist/lib/usage.js.map +0 -1
  552. package/dist/lib/versions.d.ts.map +0 -1
  553. package/dist/lib/versions.js.map +0 -1
  554. package/scripts/rebuild-sqlite.sh +0 -46
@@ -1,24 +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
20
  import * as yaml from 'yaml';
5
- import { exec } from 'child_process';
21
+ import { exec, execFile } from 'child_process';
6
22
  import { promisify } from 'util';
7
23
  import chalk from 'chalk';
8
24
  import * as TOML from 'smol-toml';
9
25
  import { checkbox, select } from '@inquirer/prompts';
10
- import { getVersionsDir, ensureAgentsDir, readMeta, writeMeta, getCommandsDir, getSkillsDir, getHooksDir, getMemoryDir, clearVersionResources, recordVersionResources, getProjectAgentsDir, getPromptcutsPath } from './state.js';
11
- import { AGENTS, getAccountEmail, MCP_CAPABLE_AGENTS, COMMANDS_CAPABLE_AGENTS, getMcpConfigPathForHome, parseMcpConfig, resolveAgentName, formatAgentError, CODEX_HOOKS_MIN_VERSION } from './agents.js';
12
- 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';
13
30
  import { installMcpServers } from './mcp.js';
14
31
  import { markdownToToml } from './convert.js';
15
- import { createVersionedAlias, removeVersionedAlias, getConfigSymlinkVersion } from './shims.js';
32
+ import { createVersionedAlias, removeVersionedAlias, getConfigSymlinkVersion, ensureClaudeInsideSymlink } from './shims.js';
16
33
  import { listInstalledSubagents, transformSubagentForClaude, syncSubagentToOpenclaw, SUBAGENT_CAPABLE_AGENTS } from './subagents.js';
17
34
  import { registerHooksToSettings } from './hooks.js';
35
+ import { supports, explainSkip } from './capabilities.js';
18
36
  import { discoverPlugins, syncPluginToVersion, isPluginSynced, pluginSupportsAgent, cleanOrphanedPluginSkills } from './plugins.js';
19
37
  import { compileMemoryForAgent } from './memory-compile.js';
38
+ import { loadSyncManifest, saveSyncManifest, buildManifest, isSyncStale } from './sync-manifest.js';
20
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. */
21
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})$/;
22
51
  /**
23
52
  * Get all available resources from ~/.agents/.
24
53
  */
@@ -35,12 +64,19 @@ export function getAvailableResources(cwd = process.cwd()) {
35
64
  promptcuts: false,
36
65
  };
37
66
  const projectAgentsDir = getProjectAgentsDir(cwd);
38
- const userBase = path.dirname(getCommandsDir());
67
+ const userBase = getUserAgentsDir();
68
+ const systemBase = getAgentsDir();
39
69
  const resourceBases = [];
40
70
  if (projectAgentsDir) {
41
71
  resourceBases.push({ scope: 'project', base: projectAgentsDir });
42
72
  }
43
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
+ }
44
80
  // Commands (*.md files)
45
81
  const commandNames = new Set();
46
82
  for (const { base } of resourceBases) {
@@ -81,22 +117,25 @@ export function getAvailableResources(cwd = process.cwd()) {
81
117
  }
82
118
  }
83
119
  result.hooks = Array.from(hookNames);
84
- // 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.
85
122
  const memoryNames = new Set();
86
123
  for (const { base } of resourceBases) {
87
- const memoryDir = path.join(base, 'memory');
88
- if (!fs.existsSync(memoryDir))
89
- continue;
90
- const names = fs.readdirSync(memoryDir)
91
- .filter(f => {
92
- if (!f.endsWith('.md'))
93
- return false;
94
- const stat = fs.lstatSync(path.join(memoryDir, f));
95
- return !stat.isSymbolicLink();
96
- })
97
- .map(f => f.replace(/\.md$/, ''));
98
- for (const name of names) {
99
- 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
+ }
100
139
  }
101
140
  }
102
141
  result.memory = Array.from(memoryNames);
@@ -151,12 +190,22 @@ export function getAvailableResources(cwd = process.cwd()) {
151
190
  result.promptcuts = fs.existsSync(getPromptcutsPath());
152
191
  return result;
153
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
+ }
154
198
  /**
155
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).
156
201
  */
157
202
  function skillDirsMatch(src, dest) {
158
203
  const entries = fs.readdirSync(src, { withFileTypes: true });
159
204
  for (const entry of entries) {
205
+ if (entry.isSymbolicLink())
206
+ continue;
207
+ if (shouldSkillEntryBeSkipped(entry.name))
208
+ continue;
160
209
  const srcPath = path.join(src, entry.name);
161
210
  const destPath = path.join(dest, entry.name);
162
211
  if (entry.isDirectory()) {
@@ -208,21 +257,27 @@ export function getActuallySyncedResources(agent, version, options = {}) {
208
257
  const skillsDir = path.join(configDir, 'skills');
209
258
  const centralSkillsDir = getSkillsDir();
210
259
  const projectSkillsDir = projectAgentsDir ? path.join(projectAgentsDir, 'skills') : null;
260
+ const userAgentsDir = getUserAgentsDir();
261
+ const extraRepos = getEnabledExtraRepos();
211
262
  if (fs.existsSync(skillsDir)) {
212
263
  const installedSkills = fs.readdirSync(skillsDir, { withFileTypes: true })
213
264
  .filter(d => d.isDirectory() && !d.name.startsWith('.'))
214
265
  .map(d => d.name);
215
266
  for (const skill of installedSkills) {
216
267
  const versionSkillDir = path.join(skillsDir, skill);
217
- const projectSourceDir = projectSkillsDir ? path.join(projectSkillsDir, skill) : null;
218
- const centralSkillDir = path.join(centralSkillsDir, skill);
219
- const hasProjectSource = projectSourceDir ? fs.existsSync(projectSourceDir) : false;
220
- const hasUserSource = fs.existsSync(centralSkillDir);
221
- 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.
222
278
  result.skills.push(skill);
223
279
  continue;
224
280
  }
225
- const sourceDir = hasProjectSource ? projectSourceDir : centralSkillDir;
226
281
  const allMatch = skillDirsMatch(sourceDir, versionSkillDir);
227
282
  if (allMatch) {
228
283
  result.skills.push(skill);
@@ -233,16 +288,19 @@ export function getActuallySyncedResources(agent, version, options = {}) {
233
288
  const hooksDir = path.join(configDir, 'hooks');
234
289
  const centralHooksDir = getHooksDir();
235
290
  const projectHooksDir = projectAgentsDir ? path.join(projectAgentsDir, 'hooks') : null;
291
+ const userHooksDir = path.join(userAgentsDir, 'hooks');
236
292
  if (fs.existsSync(hooksDir)) {
237
293
  const installedHooks = fs.readdirSync(hooksDir).filter(f => !f.startsWith('.'));
238
294
  for (const hook of installedHooks) {
239
295
  const projectFile = projectHooksDir ? path.join(projectHooksDir, hook) : null;
240
296
  const centralFile = path.join(centralHooksDir, hook);
297
+ const userFile = path.join(userHooksDir, hook);
241
298
  const versionFile = path.join(hooksDir, hook);
242
299
  const hasProject = projectFile ? fs.existsSync(projectFile) : false;
300
+ const hasUser = fs.existsSync(userFile);
243
301
  const hasCentral = fs.existsSync(centralFile);
244
- const sourceFile = hasProject ? projectFile : centralFile;
245
- if (!hasProject && !hasCentral) {
302
+ const sourceFile = hasProject ? projectFile : hasUser ? userFile : centralFile;
303
+ if (!hasProject && !hasCentral && !hasUser) {
246
304
  result.hooks.push(hook);
247
305
  continue;
248
306
  }
@@ -258,15 +316,19 @@ export function getActuallySyncedResources(agent, version, options = {}) {
258
316
  }
259
317
  }
260
318
  }
261
- // Memory - check which memory files are actually in sync (content matches)
262
- const memoryDir = getMemoryDir();
263
- 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();
264
323
  const memoryFiles = new Set();
265
324
  if (fs.existsSync(memoryDir)) {
266
- 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));
267
326
  }
268
327
  if (projectMemoryDir && fs.existsSync(projectMemoryDir)) {
269
- 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));
270
332
  }
271
333
  for (const file of memoryFiles) {
272
334
  const memName = file.replace(/\.md$/, '');
@@ -276,10 +338,12 @@ export function getActuallySyncedResources(agent, version, options = {}) {
276
338
  continue;
277
339
  const projectFile = projectMemoryDir ? path.join(projectMemoryDir, file) : null;
278
340
  const centralFile = path.join(memoryDir, file);
341
+ const userFile = path.join(userMemoryDir, file);
279
342
  const hasProject = projectFile ? fs.existsSync(projectFile) : false;
343
+ const hasUser = fs.existsSync(userFile);
280
344
  const hasCentral = fs.existsSync(centralFile);
281
- const sourceFile = hasProject ? projectFile : centralFile;
282
- if (!hasProject && !hasCentral) {
345
+ const sourceFile = hasProject ? projectFile : hasUser ? userFile : centralFile;
346
+ if (!hasProject && !hasCentral && !hasUser) {
283
347
  result.memory.push(memName);
284
348
  continue;
285
349
  }
@@ -653,8 +717,9 @@ export async function promptResourceSelection(agent) {
653
717
  if (selectedCategories.length === 0) {
654
718
  return {};
655
719
  }
656
- // If "Select All" was picked, sync everything without per-category prompts
657
- 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))) {
658
723
  for (const c of availableCategories) {
659
724
  selection[c.key] = 'all';
660
725
  }
@@ -738,6 +803,11 @@ export function parseAgentSpec(spec) {
738
803
  if (!AGENTS[agentName]) {
739
804
  return null;
740
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
+ }
741
811
  return {
742
812
  agent: agentName,
743
813
  version,
@@ -779,7 +849,7 @@ export async function getLatestNpmVersion(agent) {
779
849
  if (!agentConfig.npmPackage)
780
850
  return null;
781
851
  try {
782
- const { stdout } = await execAsync(`npm view ${agentConfig.npmPackage} version`);
852
+ const { stdout } = await execFileAsync('npm', ['view', agentConfig.npmPackage, 'version']);
783
853
  return stdout.trim();
784
854
  }
785
855
  catch {
@@ -863,9 +933,15 @@ export async function installVersion(agent, version, onProgress) {
863
933
  const packageSpec = version === 'latest'
864
934
  ? agentConfig.npmPackage
865
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
+ }
866
942
  try {
867
943
  onProgress?.(`Installing ${packageSpec}...`);
868
- const { stdout } = await execAsync(`npm install ${packageSpec}`, { cwd: versionDir });
944
+ const { stdout } = await execFileAsync('npm', ['install', packageSpec], { cwd: versionDir });
869
945
  // Determine the actual installed version
870
946
  let installedVersion = version;
871
947
  if (version === 'latest') {
@@ -883,8 +959,10 @@ export async function installVersion(agent, version, onProgress) {
883
959
  fs.renameSync(versionDir, actualVersionDir);
884
960
  }
885
961
  else {
886
- // Already exists, remove the 'latest' dir
887
- 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);
888
966
  }
889
967
  }
890
968
  }
@@ -896,25 +974,52 @@ export async function installVersion(agent, version, onProgress) {
896
974
  }
897
975
  // Create versioned alias (e.g., claude@2.0.65)
898
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
+ }
899
989
  return { success: true, installedVersion };
900
990
  }
901
991
  catch (err) {
902
- // 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.
903
994
  if (fs.existsSync(versionDir)) {
904
- fs.rmSync(versionDir, { recursive: true, force: true });
995
+ removeInstallArtifacts(versionDir);
905
996
  }
906
997
  return { success: false, installedVersion: version, error: err.message };
907
998
  }
908
999
  }
909
1000
  /**
910
- * 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.
911
1016
  */
912
1017
  export function removeVersion(agent, version) {
913
1018
  const versionDir = getVersionDir(agent, version);
914
1019
  if (!fs.existsSync(versionDir)) {
915
1020
  return false;
916
1021
  }
917
- fs.rmSync(versionDir, { recursive: true, force: true });
1022
+ removeInstallArtifacts(versionDir);
918
1023
  // Remove versioned alias (e.g., claude@2.0.65)
919
1024
  removeVersionedAlias(agent, version);
920
1025
  // Clear resource tracking for this version
@@ -945,7 +1050,9 @@ export function removeVersion(agent, version) {
945
1050
  return true;
946
1051
  }
947
1052
  /**
948
- * 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.
949
1056
  */
950
1057
  export function removeAllVersions(agent) {
951
1058
  const versions = listInstalledVersions(agent);
@@ -955,11 +1062,6 @@ export function removeAllVersions(agent) {
955
1062
  removed++;
956
1063
  }
957
1064
  }
958
- // Clean up the agent directory
959
- const agentDir = path.join(getVersionsDir(), agent);
960
- if (fs.existsSync(agentDir)) {
961
- fs.rmSync(agentDir, { recursive: true, force: true });
962
- }
963
1065
  return removed;
964
1066
  }
965
1067
  /**
@@ -978,10 +1080,61 @@ export function resolveVersion(agent, projectPath) {
978
1080
  return getGlobalDefault(agent);
979
1081
  }
980
1082
  /**
981
- * Get version specified in a project-root agents.yaml (not the user ~/.agents/agents.yaml).
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).
982
1135
  */
983
1136
  export function getProjectVersion(agent, startPath) {
984
- const userAgentsYaml = path.join(os.homedir(), '.agents', 'agents.yaml');
1137
+ const userAgentsYaml = path.join(getAgentsDir(), 'agents.yaml');
985
1138
  let dir = path.resolve(startPath);
986
1139
  while (dir !== path.dirname(dir)) {
987
1140
  const manifestPath = path.join(dir, 'agents.yaml');
@@ -1026,7 +1179,7 @@ export async function getInstalledVersion(agent, version) {
1026
1179
  return null;
1027
1180
  }
1028
1181
  try {
1029
- const { stdout } = await execAsync(`${binaryPath} --version`);
1182
+ const { stdout } = await execFileAsync(binaryPath, ['--version']);
1030
1183
  const match = stdout.match(/(\d+\.\d+\.\d+)/);
1031
1184
  return match ? match[1] : version;
1032
1185
  }
@@ -1069,10 +1222,28 @@ export function getResourceDiff(agent, version) {
1069
1222
  return 'none';
1070
1223
  }
1071
1224
  };
1072
- // Commands: check directory symlink (or individual files for Gemini)
1225
+ // Commands: check directory symlink (or individual files for Gemini / generated skills for newer Codex)
1073
1226
  const centralCommands = getCommandsDir();
1074
1227
  const commandsTarget = path.join(agentDir, agentConfig.commandsSubdir);
1075
- 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') {
1076
1247
  // Gemini: compare .md files in central vs .toml files in version
1077
1248
  if (fs.existsSync(centralCommands)) {
1078
1249
  const centralFiles = fs.readdirSync(centralCommands).filter(f => f.endsWith('.md'));
@@ -1136,10 +1307,10 @@ export function getResourceDiff(agent, version) {
1136
1307
  diff.hooks.dangling = ['hooks/'];
1137
1308
  }
1138
1309
  }
1139
- // Memory: check individual file symlinks
1140
- const centralMemory = getMemoryDir();
1310
+ // Rules: check individual file symlinks
1311
+ const centralMemory = getResolvedRulesDir();
1141
1312
  if (fs.existsSync(centralMemory)) {
1142
- 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);
1143
1314
  for (const file of memoryFiles) {
1144
1315
  const targetName = file === 'AGENTS.md' ? agentConfig.instructionsFile : file;
1145
1316
  const targetPath = path.join(agentDir, targetName);
@@ -1177,7 +1348,20 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
1177
1348
  const result = { commands: false, skills: false, hooks: false, memory: [], permissions: false, mcp: [], subagents: [], plugins: [] };
1178
1349
  const cwd = options.cwd || process.cwd();
1179
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();
1180
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
+ }
1181
1365
  // Helper: remove a path (symlink or real) if it exists
1182
1366
  const removePath = (p) => {
1183
1367
  try {
@@ -1196,12 +1380,16 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
1196
1380
  fs.mkdirSync(dest, { recursive: true });
1197
1381
  const entries = fs.readdirSync(src, { withFileTypes: true });
1198
1382
  for (const entry of entries) {
1199
- const srcPath = path.join(src, entry.name);
1200
- 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);
1201
1389
  if (entry.isDirectory()) {
1202
1390
  copyDir(srcPath, destPath);
1203
1391
  }
1204
- else {
1392
+ else if (entry.isFile()) {
1205
1393
  fs.copyFileSync(srcPath, destPath);
1206
1394
  }
1207
1395
  }
@@ -1210,8 +1398,10 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
1210
1398
  const resolveSelection = (sel, available) => {
1211
1399
  if (sel === 'all')
1212
1400
  return available;
1213
- if (Array.isArray(sel))
1214
- return sel;
1401
+ if (Array.isArray(sel)) {
1402
+ const availableSet = new Set(available);
1403
+ return sel.filter((item) => availableSet.has(item));
1404
+ }
1215
1405
  return [];
1216
1406
  };
1217
1407
  // Sync commands
@@ -1219,24 +1409,38 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
1219
1409
  ? resolveSelection(selection.commands, available.commands)
1220
1410
  : available.commands; // No selection = sync all
1221
1411
  if (commandsToSync.length > 0 && COMMANDS_CAPABLE_AGENTS.includes(agent)) {
1222
- const centralCommands = getCommandsDir();
1223
- const projectCommandsDir = projectAgentsDir ? path.join(projectAgentsDir, 'commands') : null;
1224
1412
  const commandsTarget = path.join(agentDir, agentConfig.commandsSubdir);
1225
- 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
+ }
1226
1420
  const syncedCommands = [];
1227
1421
  for (const cmd of commandsToSync) {
1228
- const projectSource = projectCommandsDir ? path.join(projectCommandsDir, `${cmd}.md`) : null;
1229
- const userSource = path.join(centralCommands, `${cmd}.md`);
1230
- const srcFile = projectSource && fs.existsSync(projectSource) ? projectSource : userSource;
1231
- if (!fs.existsSync(srcFile))
1422
+ const resolved = resolveResource('commands', `${cmd}.md`, cwd);
1423
+ if (!resolved || fs.lstatSync(resolved.path).isSymbolicLink())
1232
1424
  continue;
1233
- 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') {
1234
1438
  const content = fs.readFileSync(srcFile, 'utf-8');
1235
1439
  const tomlContent = markdownToToml(cmd, content);
1236
- fs.writeFileSync(path.join(commandsTarget, `${cmd}.toml`), tomlContent);
1440
+ fs.writeFileSync(safeJoin(commandsTarget, `${cmd}.toml`), tomlContent);
1237
1441
  }
1238
1442
  else {
1239
- fs.copyFileSync(srcFile, path.join(commandsTarget, `${cmd}.md`));
1443
+ fs.copyFileSync(srcFile, safeJoin(commandsTarget, `${cmd}.md`));
1240
1444
  }
1241
1445
  syncedCommands.push(cmd);
1242
1446
  }
@@ -1256,17 +1460,17 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
1256
1460
  ? resolveSelection(selection.skills, available.skills)
1257
1461
  : available.skills;
1258
1462
  if (skillsToSync.length > 0) {
1259
- const centralSkills = getSkillsDir();
1260
- const projectSkills = projectAgentsDir ? path.join(projectAgentsDir, 'skills') : null;
1261
1463
  const skillsTarget = path.join(agentDir, 'skills');
1262
1464
  fs.mkdirSync(skillsTarget, { recursive: true });
1263
1465
  const syncedSkills = [];
1264
1466
  for (const skill of skillsToSync) {
1265
- const projectSource = projectSkills ? path.join(projectSkills, skill) : null;
1266
- const srcDir = projectSource && fs.existsSync(projectSource) ? projectSource : path.join(centralSkills, skill);
1267
- 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)
1268
1472
  continue;
1269
- const destDir = path.join(skillsTarget, skill);
1473
+ const destDir = safeJoin(skillsTarget, skill);
1270
1474
  removePath(destDir);
1271
1475
  copyDir(srcDir, destDir);
1272
1476
  syncedSkills.push(skill);
@@ -1277,11 +1481,11 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
1277
1481
  }
1278
1482
  }
1279
1483
  }
1280
- // Sync hooks (if agent supports them)
1484
+ // Sync hooks (if agent supports them at this version)
1485
+ const hooksGate = supports(agent, 'hooks', version);
1281
1486
  if (agentConfig.supportsHooks) {
1282
- // Version gate: Codex hooks require >= CODEX_HOOKS_MIN_VERSION
1283
- if (agent === 'codex' && compareVersions(version, CODEX_HOOKS_MIN_VERSION) < 0) {
1284
- console.warn(`hooks skipped: codex@${version} < ${CODEX_HOOKS_MIN_VERSION}`);
1487
+ if (!hooksGate.ok) {
1488
+ console.warn(explainSkip(agent, 'hooks', hooksGate, version) + ' -- skipped');
1285
1489
  }
1286
1490
  else {
1287
1491
  const hooksToSync = selection
@@ -1289,28 +1493,53 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
1289
1493
  : available.hooks;
1290
1494
  if (hooksToSync.length > 0) {
1291
1495
  const centralHooks = getHooksDir();
1292
- const projectHooksDir = projectAgentsDir ? path.join(projectAgentsDir, 'hooks') : null;
1293
1496
  const hooksTarget = path.join(agentDir, 'hooks');
1294
1497
  fs.mkdirSync(hooksTarget, { recursive: true });
1295
1498
  const syncedHooks = [];
1296
1499
  for (const hook of hooksToSync) {
1297
- const projectSource = projectHooksDir ? path.join(projectHooksDir, hook) : null;
1298
- const srcFile = projectSource && fs.existsSync(projectSource) ? projectSource : path.join(centralHooks, hook);
1299
- 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)
1300
1513
  continue;
1301
- const destFile = path.join(hooksTarget, hook);
1514
+ const destFile = safeJoin(hooksTarget, hook);
1302
1515
  fs.copyFileSync(srcFile, destFile);
1303
1516
  fs.chmodSync(destFile, 0o755);
1304
1517
  syncedHooks.push(hook);
1305
1518
  }
1306
- // Remove orphan hook files that exist in version home but not in central
1519
+ // Remove orphan hook files that exist in version home but not in any trusted source.
1307
1520
  const centralHookNames = new Set(fs.existsSync(getHooksDir())
1308
1521
  ? fs.readdirSync(getHooksDir()).filter(f => !f.startsWith('.'))
1309
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
+ }
1310
1539
  if (fs.existsSync(hooksTarget)) {
1311
1540
  for (const file of fs.readdirSync(hooksTarget).filter(f => !f.startsWith('.'))) {
1312
1541
  if (!centralHookNames.has(file)) {
1313
- removePath(path.join(hooksTarget, file));
1542
+ removePath(safeJoin(hooksTarget, file));
1314
1543
  }
1315
1544
  }
1316
1545
  }
@@ -1318,7 +1547,9 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
1318
1547
  if (syncedHooks.length > 0) {
1319
1548
  recordVersionResources(agent, version, 'hooks', syncedHooks);
1320
1549
  }
1321
- 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') {
1322
1553
  registerHooksToSettings(agent, versionHome);
1323
1554
  }
1324
1555
  }
@@ -1329,19 +1560,23 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
1329
1560
  ? resolveSelection(selection.memory, available.memory)
1330
1561
  : available.memory;
1331
1562
  if (memoryToSync.length > 0 && COMMANDS_CAPABLE_AGENTS.includes(agent)) {
1332
- const centralMemory = getMemoryDir();
1333
- 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();
1334
1566
  const syncedMemory = [];
1335
1567
  const agentSupportsImports = !!agentConfig.capabilities.memoryImports;
1336
1568
  for (const mem of memoryToSync) {
1337
- const projectSource = projectMemoryDir ? path.join(projectMemoryDir, `${mem}.md`) : null;
1338
- const srcFile = projectSource && fs.existsSync(projectSource)
1339
- ? projectSource
1340
- : path.join(centralMemory, `${mem}.md`);
1341
- 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)
1342
1577
  continue;
1343
1578
  const targetName = mem === 'AGENTS' ? agentConfig.instructionsFile : `${mem}.md`;
1344
- const destFile = path.join(agentDir, targetName);
1579
+ const destFile = safeJoin(agentDir, targetName);
1345
1580
  removePath(destFile);
1346
1581
  // For the primary memory file (AGENTS.md), agents that don't natively
1347
1582
  // resolve @-imports get a compiled (inlined) copy + sidecar manifest.
@@ -1360,13 +1595,43 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
1360
1595
  recordVersionResources(agent, version, 'memory', syncedMemory);
1361
1596
  }
1362
1597
  }
1363
- // Apply permissions (if agent supports them)
1364
- // 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).
1365
1603
  const permissionGroups = discoverPermissionGroups();
1366
1604
  const allGroupNames = permissionGroups.map(g => g.name);
1367
- const permsToSync = selection
1368
- ? resolveSelection(selection.permissions, allGroupNames)
1369
- : (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
+ }
1370
1635
  if (permsToSync.length > 0 && PERMISSIONS_CAPABLE_AGENTS.includes(agent)) {
1371
1636
  // Build permissions from selected groups
1372
1637
  const builtPerms = buildPermissionsFromGroups(permsToSync);
@@ -1408,12 +1673,12 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
1408
1673
  const agentsDir = path.join(agentDir, 'agents');
1409
1674
  fs.mkdirSync(agentsDir, { recursive: true });
1410
1675
  const transformed = transformSubagentForClaude(subagent.path);
1411
- fs.writeFileSync(path.join(agentsDir, `${subagent.name}.md`), transformed);
1676
+ fs.writeFileSync(safeJoin(agentsDir, `${subagent.name}.md`), transformed);
1412
1677
  result.subagents.push(subagent.name);
1413
1678
  }
1414
1679
  else if (agent === 'openclaw') {
1415
1680
  // OpenClaw: copy full directory, rename AGENT.md -> AGENTS.md
1416
- const targetDir = path.join(versionHome, '.openclaw', subagent.name);
1681
+ const targetDir = safeJoin(path.join(versionHome, '.openclaw'), subagent.name);
1417
1682
  const syncResult = syncSubagentToOpenclaw(subagent.path, targetDir);
1418
1683
  if (syncResult.success) {
1419
1684
  result.subagents.push(subagent.name);
@@ -1449,6 +1714,10 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
1449
1714
  recordVersionResources(agent, version, 'plugins', result.plugins);
1450
1715
  }
1451
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
+ }
1452
1721
  return result;
1453
1722
  }
1454
1723
  /**
@@ -1734,4 +2003,3 @@ export async function promptAgentVersionSelection(availableAgents, options = {})
1734
2003
  }
1735
2004
  return { selectedAgents, versionSelections };
1736
2005
  }
1737
- //# sourceMappingURL=versions.js.map