rovecode 0.3.3 → 0.4.0-beta.3

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 (495) hide show
  1. package/CHANGELOG.md +527 -0
  2. package/README.md +46 -48
  3. package/bin/rovecode.js +24 -0
  4. package/dist/cli/app-j6gn14w3.js +2 -0
  5. package/dist/cli/ask-user-cwstt8fz.js +2 -0
  6. package/dist/cli/auth-login-9bbp9915.js +2 -0
  7. package/dist/cli/auth-m8p9grty.js +2 -0
  8. package/dist/cli/bench-16zqdms5.js +9 -0
  9. package/dist/cli/catalog-1xchffa4.js +2 -0
  10. package/dist/cli/cli-1n1zb64f.js +2 -0
  11. package/dist/cli/client-2t9gjkck.js +2 -0
  12. package/dist/cli/commands-exafvm2b.js +2 -0
  13. package/dist/cli/connect-6zde0kn3.js +2 -0
  14. package/dist/cli/context-cmd-5t43wgqt.js +2 -0
  15. package/dist/cli/context-report-kt01pw8y.js +2 -0
  16. package/dist/cli/count-remote-ap7x3vh6.js +2 -0
  17. package/dist/cli/design-ne5zszyh.js +2 -0
  18. package/dist/cli/dispatch-2r5myxye.js +2 -0
  19. package/dist/cli/doctor-ws4fh4tn.js +3 -0
  20. package/dist/cli/executor-bdrjn634.js +2 -0
  21. package/dist/cli/export-1mxb9g5p.js +2 -0
  22. package/dist/cli/files-g104xghh.js +2 -0
  23. package/dist/cli/gauntlet-07xrjpj7.js +2 -0
  24. package/dist/cli/gauntlet-runner-xvy64436.js +10 -0
  25. package/dist/cli/gauntlet-wave3-jm91yt5w.js +5 -0
  26. package/dist/cli/gauntlet-wave4-r13py7p1.js +14 -0
  27. package/dist/cli/hashline-znvrat11.js +2 -0
  28. package/dist/cli/http-xafw6fsh.js +143 -0
  29. package/dist/cli/index-1sgjm25y.js +2 -0
  30. package/dist/cli/init-g2m0tn4m.js +51 -0
  31. package/dist/cli/install-avaqjjqq.js +2 -0
  32. package/dist/cli/loop-mmpfft01.js +2 -0
  33. package/dist/cli/main-0904f6ps.js +5 -0
  34. package/dist/cli/main-0ab9fc26.js +9 -0
  35. package/dist/cli/main-0jys2ccn.js +3 -0
  36. package/dist/cli/main-0mtcdbs7.js +3 -0
  37. package/dist/cli/main-0z1w2zsg.js +3 -0
  38. package/dist/cli/main-1dchs7xv.js +18 -0
  39. package/dist/cli/main-1ereejm1.js +3 -0
  40. package/dist/cli/main-1k1kw6b5.js +3 -0
  41. package/dist/cli/main-27y4sm2k.js +38 -0
  42. package/dist/cli/main-2wwjex5j.js +58 -0
  43. package/dist/cli/main-2yeveeve.js +6 -0
  44. package/dist/cli/main-2yfck9b5.js +3 -0
  45. package/dist/cli/main-2zmzgkwh.js +3 -0
  46. package/dist/cli/main-351pz3z7.js +7 -0
  47. package/dist/cli/main-3gjqfh7a.js +6 -0
  48. package/dist/cli/main-3nf3kgve.js +3 -0
  49. package/dist/cli/main-3pjrb2hd.js +3 -0
  50. package/dist/cli/main-3rxcvgna.js +19 -0
  51. package/dist/cli/main-4b3jgy66.js +19 -0
  52. package/dist/cli/main-4wndhjdc.js +7 -0
  53. package/dist/cli/main-4xcmvxnk.js +3 -0
  54. package/dist/cli/main-5tbz0wbz.js +4 -0
  55. package/dist/cli/main-5ywnwthm.js +3 -0
  56. package/dist/cli/main-6b62vkz0.js +14 -0
  57. package/dist/cli/main-6dnk69vp.js +3 -0
  58. package/dist/cli/main-6genrmhs.js +136 -0
  59. package/dist/cli/main-73g7eff4.js +15 -0
  60. package/dist/cli/main-7c5thhjd.js +5 -0
  61. package/dist/cli/main-7rn6bqje.js +3 -0
  62. package/dist/cli/main-80haw7qk.js +4 -0
  63. package/dist/cli/main-875s60s2.js +4 -0
  64. package/dist/cli/main-8kjxbpw4.js +8 -0
  65. package/dist/cli/main-90ds1z4e.js +10 -0
  66. package/dist/cli/main-9etavkew.js +3 -0
  67. package/dist/cli/main-a9njrkk1.js +3 -0
  68. package/dist/cli/main-aecrjq2d.js +12 -0
  69. package/dist/cli/main-ck9asesq.js +9 -0
  70. package/dist/cli/main-cta9racd.js +4 -0
  71. package/dist/cli/main-ddv7j2ag.js +3 -0
  72. package/dist/cli/main-dfreez27.js +10 -0
  73. package/dist/cli/main-f7rw7des.js +3 -0
  74. package/dist/cli/main-ggcn7rd7.js +5 -0
  75. package/dist/cli/main-gzkmycnv.js +3 -0
  76. package/dist/cli/main-hq51jg8v.js +18 -0
  77. package/dist/cli/main-jft389w9.js +8 -0
  78. package/dist/cli/main-k1eqkg83.js +3 -0
  79. package/dist/cli/main-k2y8a2aw.js +9 -0
  80. package/dist/cli/main-kcpbykxz.js +4 -0
  81. package/dist/cli/main-kd488vje.js +22 -0
  82. package/dist/cli/main-kh32yvgk.js +5 -0
  83. package/dist/cli/main-kqxnqjnv.js +25 -0
  84. package/dist/cli/main-kyn0xnsg.js +3 -0
  85. package/dist/cli/main-m1kk6fp5.js +21 -0
  86. package/dist/cli/main-mv40pcr2.js +4 -0
  87. package/dist/cli/main-n0t3973w.js +3 -0
  88. package/dist/cli/main-nqveez48.js +4 -0
  89. package/dist/cli/main-pknhvrmj.js +3 -0
  90. package/dist/cli/main-pn1w7a7j.js +3 -0
  91. package/dist/cli/main-prxxs70n.js +4 -0
  92. package/dist/cli/main-q3vsesf9.js +3 -0
  93. package/dist/cli/main-qsevpgsv.js +3 -0
  94. package/dist/cli/main-rdgdw24b.js +25 -0
  95. package/dist/cli/main-rfth4tbm.js +16 -0
  96. package/dist/cli/main-rg0wn0xf.js +5 -0
  97. package/dist/cli/main-sdmxhtv8.js +4 -0
  98. package/dist/cli/main-skbp13js.js +18 -0
  99. package/dist/cli/main-t4xnd213.js +7 -0
  100. package/dist/cli/main-vqak588n.js +4 -0
  101. package/dist/cli/main-w2n1303f.js +9 -0
  102. package/dist/cli/main-wbrdspr2.js +5 -0
  103. package/dist/cli/main-wsrg79c1.js +7 -0
  104. package/dist/cli/main-x4r0fne4.js +5 -0
  105. package/dist/cli/main-xea2f3tn.js +6 -0
  106. package/dist/cli/main-xg704a3c.js +3 -0
  107. package/dist/cli/main-xvnrabfp.js +16 -0
  108. package/dist/cli/main-xy53xf0r.js +4 -0
  109. package/dist/cli/main-y1fqy60y.js +3 -0
  110. package/dist/cli/main-yn8cd281.js +34 -0
  111. package/dist/cli/main-yr0ksc0h.js +4 -0
  112. package/dist/cli/main-z2ex2vyf.js +4 -0
  113. package/dist/cli/main-z3aayzvq.js +3 -0
  114. package/dist/cli/main-zaqh35jg.js +3 -0
  115. package/dist/cli/main-zc2e8e46.js +4 -0
  116. package/dist/cli/main-zzrfw6cf.js +13 -0
  117. package/dist/cli/main.js +280 -0
  118. package/dist/cli/market-cmd-e14kmx9n.js +5 -0
  119. package/dist/cli/mcp-login-wq7ktdek.js +2 -0
  120. package/dist/cli/mcp-market-cmd-9mg3jecy.js +2 -0
  121. package/dist/cli/notify-b7qc0cjb.js +2 -0
  122. package/dist/cli/oauth-z8whcgfx.js +2 -0
  123. package/dist/cli/output-b3ewj3ps.js +16 -0
  124. package/dist/cli/profiles-6mr5he5e.js +2 -0
  125. package/dist/cli/provider-config-g7j42q8x.js +2 -0
  126. package/dist/cli/provider-jr1y8vvm.js +2 -0
  127. package/dist/cli/registry-s8yk86g0.js +2 -0
  128. package/dist/cli/registry-t6p8d4mn.js +2 -0
  129. package/dist/cli/repl-bajwe1mh.js +11 -0
  130. package/dist/cli/resume-rwn9nz7y.js +2 -0
  131. package/dist/cli/run-flags-nah7ndpt.js +2 -0
  132. package/dist/cli/runtime-n7gafzhb.js +2 -0
  133. package/dist/cli/sandbox-config-emdy18x4.js +2 -0
  134. package/dist/cli/server-b0nvs2bn.js +5 -0
  135. package/dist/cli/session-arg-y75wd4kj.js +2 -0
  136. package/dist/cli/session-j62evmjq.js +2 -0
  137. package/dist/cli/sessions-cmd-tsnwz0ns.js +7 -0
  138. package/dist/cli/settings-df10wfez.js +2 -0
  139. package/dist/cli/setup-jzvv72fg.js +2 -0
  140. package/dist/cli/sextant-smoke-37m81ke6.js +5 -0
  141. package/dist/cli/skills-cmd-gjxnxnhx.js +2 -0
  142. package/dist/cli/smoke-p7748apt.js +8 -0
  143. package/dist/cli/start-chat-s4st3mm0.js +12 -0
  144. package/dist/cli/stream-gmeyewds.js +2 -0
  145. package/dist/cli/task-gh0kkp3n.js +2 -0
  146. package/dist/cli/tasks-z1kfpe8e.js +2 -0
  147. package/dist/cli/thinking-0eqkrz6t.js +2 -0
  148. package/dist/cli/todo-5brcrt9m.js +2 -0
  149. package/dist/cli/tools-7pzm0vj9.js +2 -0
  150. package/dist/cli/tools-s635p6s8.js +2 -0
  151. package/dist/cli/trust-cmd-cjav8zgm.js +2 -0
  152. package/dist/cli/update-check-pt31bm2f.js +2 -0
  153. package/dist/cli/update-cmd-tk131s9t.js +2 -0
  154. package/dist/cli/voice-56nabd8d.js +2 -0
  155. package/dist/cli/webfetch-xd8q596m.js +2 -0
  156. package/dist/cli/websearch-5hkf98k1.js +2 -0
  157. package/dist/cli/workflow-cmd-cy3cvzjp.js +4 -0
  158. package/dist/cli/workspace-q10g5z3e.js +2 -0
  159. package/dist/lib/index.js +62 -0
  160. package/dist/lib/models-index.json +1 -0
  161. package/dist/lib/plugins.js +55 -0
  162. package/dist/lib/providers.js +17 -0
  163. package/dist/lib/public-api.js +20 -0
  164. package/dist/lib/sdk.js +360 -0
  165. package/package.json +84 -72
  166. package/bin/rovecode.ts +0 -21
  167. package/src/acp/server.ts +0 -374
  168. package/src/cli/auth-login.ts +0 -122
  169. package/src/cli/connect.ts +0 -244
  170. package/src/cli/context-cmd.ts +0 -199
  171. package/src/cli/dispatch.ts +0 -82
  172. package/src/cli/doctor.ts +0 -362
  173. package/src/cli/export.ts +0 -276
  174. package/src/cli/help.ts +0 -293
  175. package/src/cli/is-tui-invocation.ts +0 -8
  176. package/src/cli/main.ts +0 -583
  177. package/src/cli/market-cmd.ts +0 -658
  178. package/src/cli/mcp-login.ts +0 -141
  179. package/src/cli/mcp-market-cmd.ts +0 -302
  180. package/src/cli/output.ts +0 -382
  181. package/src/cli/repl.ts +0 -250
  182. package/src/cli/repomap-root.ts +0 -14
  183. package/src/cli/resume.ts +0 -57
  184. package/src/cli/run-flags.ts +0 -43
  185. package/src/cli/run-limits.ts +0 -78
  186. package/src/cli/runtime.ts +0 -931
  187. package/src/cli/session-arg.ts +0 -30
  188. package/src/cli/sessions-cmd.ts +0 -145
  189. package/src/cli/setup.ts +0 -153
  190. package/src/cli/skills-cmd.ts +0 -194
  191. package/src/cli/start-chat.ts +0 -65
  192. package/src/cli/trust-cmd.ts +0 -52
  193. package/src/coding/bash.ts +0 -148
  194. package/src/coding/checkpoints.ts +0 -327
  195. package/src/coding/diff.ts +0 -138
  196. package/src/coding/files.ts +0 -341
  197. package/src/coding/hashline.ts +0 -274
  198. package/src/coding/lsp-gate.ts +0 -254
  199. package/src/coding/lsp-servers.ts +0 -147
  200. package/src/coding/lsp.ts +0 -283
  201. package/src/coding/repomap-cache.ts +0 -99
  202. package/src/coding/repomap-files.ts +0 -192
  203. package/src/coding/repomap.ts +0 -481
  204. package/src/core/agents.ts +0 -255
  205. package/src/core/compaction.ts +0 -259
  206. package/src/core/config.ts +0 -289
  207. package/src/core/context-report.ts +0 -228
  208. package/src/core/context.ts +0 -60
  209. package/src/core/count-remote.ts +0 -107
  210. package/src/core/execpolicy-rules.ts +0 -196
  211. package/src/core/execpolicy.ts +0 -385
  212. package/src/core/executor.ts +0 -454
  213. package/src/core/guardrails.ts +0 -400
  214. package/src/core/hooks.ts +0 -411
  215. package/src/core/images.ts +0 -230
  216. package/src/core/intro.ts +0 -266
  217. package/src/core/loop.ts +0 -567
  218. package/src/core/modes.ts +0 -372
  219. package/src/core/orchestrator.ts +0 -245
  220. package/src/core/proc-group.ts +0 -48
  221. package/src/core/project-trust.ts +0 -98
  222. package/src/core/reflection.ts +0 -165
  223. package/src/core/sandbox-config.ts +0 -186
  224. package/src/core/session-id.ts +0 -24
  225. package/src/core/session-images.ts +0 -73
  226. package/src/core/session-ops.ts +0 -183
  227. package/src/core/session-text.ts +0 -29
  228. package/src/core/session.ts +0 -469
  229. package/src/core/settings.ts +0 -170
  230. package/src/core/tasks.ts +0 -646
  231. package/src/core/token-scale.ts +0 -108
  232. package/src/core/tools.ts +0 -309
  233. package/src/core/trust.ts +0 -104
  234. package/src/core/types.ts +0 -330
  235. package/src/core/update-check.ts +0 -171
  236. package/src/core/usage.ts +0 -204
  237. package/src/core/validate.ts +0 -121
  238. package/src/core/verify-gate.ts +0 -159
  239. package/src/core/verify.ts +0 -236
  240. package/src/core/voice.ts +0 -158
  241. package/src/core/win-job.ts +0 -183
  242. package/src/core/workspace.ts +0 -184
  243. package/src/design/audit.ts +0 -797
  244. package/src/design/direction.ts +0 -190
  245. package/src/design/rules.ts +0 -157
  246. package/src/eval/bench.ts +0 -150
  247. package/src/eval/gauntlet-runner.ts +0 -215
  248. package/src/eval/gauntlet-support.ts +0 -84
  249. package/src/eval/gauntlet-wave3.ts +0 -269
  250. package/src/eval/gauntlet-wave4.ts +0 -217
  251. package/src/eval/gauntlet.ts +0 -253
  252. package/src/index.ts +0 -17
  253. package/src/lanes/agy.ts +0 -95
  254. package/src/lanes/approval.ts +0 -24
  255. package/src/lanes/claude.ts +0 -129
  256. package/src/lanes/codex.ts +0 -127
  257. package/src/lanes/events.ts +0 -130
  258. package/src/lanes/job.ts +0 -142
  259. package/src/lanes/opencode.ts +0 -122
  260. package/src/lanes/process.ts +0 -184
  261. package/src/lanes/progress.ts +0 -183
  262. package/src/lanes/registry.ts +0 -178
  263. package/src/lanes/runner.ts +0 -124
  264. package/src/lanes/types.ts +0 -112
  265. package/src/market/catalogs/mcp-docs.json +0 -111
  266. package/src/market/catalogs/plugins.json +0 -111
  267. package/src/market/catalogs/skills.json +0 -478
  268. package/src/market/clone.ts +0 -72
  269. package/src/market/context-cost.ts +0 -121
  270. package/src/market/digest.ts +0 -106
  271. package/src/market/index.ts +0 -22
  272. package/src/market/install.ts +0 -578
  273. package/src/market/manifest.ts +0 -187
  274. package/src/market/prereq.ts +0 -145
  275. package/src/market/registry.ts +0 -363
  276. package/src/market/resolve.ts +0 -111
  277. package/src/market/types.ts +0 -236
  278. package/src/market/validate.ts +0 -227
  279. package/src/mcp/client.ts +0 -449
  280. package/src/mcp/config.ts +0 -252
  281. package/src/mcp/local-package.ts +0 -211
  282. package/src/mcp/market-catalog.ts +0 -84
  283. package/src/mcp/market-install.ts +0 -289
  284. package/src/mcp/market.ts +0 -362
  285. package/src/mcp/oauth.ts +0 -251
  286. package/src/mcp/prompts-resources.ts +0 -249
  287. package/src/mcp/shared.ts +0 -149
  288. package/src/mcp/status.ts +0 -67
  289. package/src/mcp/tools.ts +0 -275
  290. package/src/mcp/transport.ts +0 -122
  291. package/src/mcp/trust.ts +0 -25
  292. package/src/memory/blocks.ts +0 -278
  293. package/src/memory/recall.ts +0 -355
  294. package/src/memory/scope.ts +0 -182
  295. package/src/memory/store.ts +0 -105
  296. package/src/memory/tools.ts +0 -99
  297. package/src/plugins/cli.ts +0 -119
  298. package/src/plugins/discover.ts +0 -108
  299. package/src/plugins/index.ts +0 -50
  300. package/src/plugins/install.ts +0 -184
  301. package/src/plugins/load.ts +0 -124
  302. package/src/plugins/manifest.ts +0 -92
  303. package/src/plugins/state.ts +0 -83
  304. package/src/providers/auth.ts +0 -408
  305. package/src/providers/cache.ts +0 -223
  306. package/src/providers/catalog-local.ts +0 -160
  307. package/src/providers/catalog.ts +0 -421
  308. package/src/providers/middleware-context.ts +0 -86
  309. package/src/providers/middleware.ts +0 -373
  310. package/src/providers/model-list.ts +0 -23
  311. package/src/providers/oauth/common.ts +0 -105
  312. package/src/providers/oauth/device-code.ts +0 -107
  313. package/src/providers/oauth/github-copilot.ts +0 -146
  314. package/src/providers/oauth/loopback.ts +0 -158
  315. package/src/providers/oauth/openai.ts +0 -163
  316. package/src/providers/oauth/openrouter.ts +0 -89
  317. package/src/providers/oauth/pkce.ts +0 -45
  318. package/src/providers/oauth/registry.ts +0 -39
  319. package/src/providers/oauth/seam.ts +0 -89
  320. package/src/providers/profile-glm53.ts +0 -111
  321. package/src/providers/profile-sonnet5-persona.ts +0 -65
  322. package/src/providers/profile-sonnet5-voice.ts +0 -23
  323. package/src/providers/profiles.ts +0 -156
  324. package/src/providers/provider-config.ts +0 -311
  325. package/src/providers/registry.ts +0 -333
  326. package/src/providers/responses.ts +0 -209
  327. package/src/providers/retry.ts +0 -234
  328. package/src/providers/router.ts +0 -294
  329. package/src/providers/sse.ts +0 -26
  330. package/src/providers/stream-errors.ts +0 -117
  331. package/src/providers/stream.ts +0 -566
  332. package/src/providers/thinking.ts +0 -189
  333. package/src/providers/wire-messages.ts +0 -129
  334. package/src/providers/wire-responses.ts +0 -79
  335. package/src/providers/wire-select.ts +0 -53
  336. package/src/server/http.ts +0 -291
  337. package/src/server/openapi.ts +0 -246
  338. package/src/sextant/card-hits.ts +0 -102
  339. package/src/sextant/card-keys.ts +0 -55
  340. package/src/sextant/context-source.ts +0 -157
  341. package/src/sextant/crew-cards.ts +0 -350
  342. package/src/sextant/draw-agents.ts +0 -273
  343. package/src/sextant/draw-code.ts +0 -388
  344. package/src/sextant/draw-context.ts +0 -222
  345. package/src/sextant/draw-frame.ts +0 -164
  346. package/src/sextant/draw-market.ts +0 -573
  347. package/src/sextant/draw-messages.ts +0 -386
  348. package/src/sextant/draw-pet.ts +0 -230
  349. package/src/sextant/draw-plan.ts +0 -187
  350. package/src/sextant/draw-tabs.ts +0 -85
  351. package/src/sextant/draw-util.ts +0 -65
  352. package/src/sextant/draw-wizard.ts +0 -378
  353. package/src/sextant/engine.ts +0 -230
  354. package/src/sextant/frame-hits.ts +0 -25
  355. package/src/sextant/frame.ts +0 -101
  356. package/src/sextant/git-status.ts +0 -197
  357. package/src/sextant/grid.ts +0 -59
  358. package/src/sextant/input.ts +0 -119
  359. package/src/sextant/keys.ts +0 -521
  360. package/src/sextant/layout.ts +0 -86
  361. package/src/sextant/local-commands.ts +0 -169
  362. package/src/sextant/market-source.ts +0 -287
  363. package/src/sextant/mentions.ts +0 -200
  364. package/src/sextant/message-hits.ts +0 -26
  365. package/src/sextant/model.ts +0 -387
  366. package/src/sextant/overlays.ts +0 -456
  367. package/src/sextant/panel-hits.ts +0 -38
  368. package/src/sextant/pet.ts +0 -399
  369. package/src/sextant/screen.ts +0 -324
  370. package/src/sextant/scroll-hits.ts +0 -66
  371. package/src/sextant/scrollbar.ts +0 -82
  372. package/src/sextant/sextant-bridge.ts +0 -174
  373. package/src/sextant/sextant-cards.ts +0 -142
  374. package/src/sextant/sextant-diff-base.ts +0 -63
  375. package/src/sextant/sextant-files.ts +0 -154
  376. package/src/sextant/sextant-frame-loop.ts +0 -335
  377. package/src/sextant/sextant-renderer.ts +0 -574
  378. package/src/sextant/sextant-repo.ts +0 -140
  379. package/src/sextant/theme.ts +0 -66
  380. package/src/sextant/tool-rows.ts +0 -189
  381. package/src/sextant/types.ts +0 -493
  382. package/src/skills/index.ts +0 -387
  383. package/src/skills/pack.ts +0 -220
  384. package/src/skills/spec.ts +0 -162
  385. package/src/skills/tools.ts +0 -69
  386. package/src/skills/versioned.ts +0 -227
  387. package/src/telemetry/otel-export.ts +0 -122
  388. package/src/telemetry/otel-lanes.ts +0 -89
  389. package/src/telemetry/otel-logs.ts +0 -131
  390. package/src/telemetry/otel-metrics.ts +0 -136
  391. package/src/telemetry/otel.ts +0 -397
  392. package/src/telemetry/otlp.ts +0 -76
  393. package/src/tools/ask-user.ts +0 -156
  394. package/src/tools/bash-bg.ts +0 -94
  395. package/src/tools/bash-jobs.ts +0 -237
  396. package/src/tools/design.ts +0 -151
  397. package/src/tools/evalcell.ts +0 -338
  398. package/src/tools/html-text.ts +0 -139
  399. package/src/tools/provider.ts +0 -149
  400. package/src/tools/task.ts +0 -250
  401. package/src/tools/todo.ts +0 -320
  402. package/src/tools/webfetch.ts +0 -332
  403. package/src/tools/websearch.ts +0 -359
  404. package/src/tui/agents-cmd.ts +0 -41
  405. package/src/tui/app.ts +0 -749
  406. package/src/tui/attach.ts +0 -127
  407. package/src/tui/boot-notes.ts +0 -41
  408. package/src/tui/builtin-prompts.ts +0 -59
  409. package/src/tui/checkpoints-cmd.ts +0 -70
  410. package/src/tui/clipboard-image.ts +0 -81
  411. package/src/tui/clipboard.ts +0 -78
  412. package/src/tui/commands.ts +0 -283
  413. package/src/tui/config-view.ts +0 -53
  414. package/src/tui/context-cmds.ts +0 -282
  415. package/src/tui/cost.ts +0 -108
  416. package/src/tui/crash-guard.ts +0 -173
  417. package/src/tui/focus-terminal.ts +0 -34
  418. package/src/tui/git-cmds.ts +0 -273
  419. package/src/tui/git-plain.ts +0 -58
  420. package/src/tui/info-cmd.ts +0 -150
  421. package/src/tui/input-plain.ts +0 -76
  422. package/src/tui/mcp-cmd.ts +0 -128
  423. package/src/tui/memory-note.ts +0 -77
  424. package/src/tui/modes-cmd.ts +0 -45
  425. package/src/tui/notify-seq.ts +0 -100
  426. package/src/tui/notify.ts +0 -318
  427. package/src/tui/overlays.ts +0 -97
  428. package/src/tui/pi-renderer.ts +0 -428
  429. package/src/tui/providers-cmd.ts +0 -377
  430. package/src/tui/reasoning-view.ts +0 -56
  431. package/src/tui/renderer.ts +0 -128
  432. package/src/tui/replay-marker.ts +0 -29
  433. package/src/tui/session-cmd.ts +0 -148
  434. package/src/tui/session-manage.ts +0 -95
  435. package/src/tui/sextant-attach.ts +0 -102
  436. package/src/tui/sextant-io.ts +0 -202
  437. package/src/tui/sextant-smoke.ts +0 -110
  438. package/src/tui/shell-cmd.ts +0 -158
  439. package/src/tui/smoke.ts +0 -72
  440. package/src/tui/staged-terminal.ts +0 -50
  441. package/src/tui/startup.ts +0 -12
  442. package/src/tui/theme.ts +0 -59
  443. package/src/tui/todo-label.ts +0 -7
  444. package/src/tui/trust-card.ts +0 -107
  445. package/src/tui/tui-commands.ts +0 -87
  446. package/tsconfig.json +0 -30
  447. package/vendor/pi-tui/LICENSE +0 -21
  448. package/vendor/pi-tui/PATCHES.md +0 -12
  449. package/vendor/pi-tui/PROVENANCE.md +0 -12
  450. package/vendor/pi-tui/README.upstream.md +0 -854
  451. package/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  452. package/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  453. package/vendor/pi-tui/src/alt-screen-search.ts +0 -158
  454. package/vendor/pi-tui/src/autocomplete.ts +0 -827
  455. package/vendor/pi-tui/src/components/alt-screen-flash.ts +0 -52
  456. package/vendor/pi-tui/src/components/box.ts +0 -138
  457. package/vendor/pi-tui/src/components/cancellable-loader.ts +0 -41
  458. package/vendor/pi-tui/src/components/editor.ts +0 -2364
  459. package/vendor/pi-tui/src/components/h-stack.ts +0 -45
  460. package/vendor/pi-tui/src/components/image.ts +0 -128
  461. package/vendor/pi-tui/src/components/input.ts +0 -448
  462. package/vendor/pi-tui/src/components/loader.ts +0 -93
  463. package/vendor/pi-tui/src/components/markdown.ts +0 -1016
  464. package/vendor/pi-tui/src/components/scroll-view.ts +0 -217
  465. package/vendor/pi-tui/src/components/select-list.ts +0 -230
  466. package/vendor/pi-tui/src/components/settings-list.ts +0 -277
  467. package/vendor/pi-tui/src/components/spacer.ts +0 -29
  468. package/vendor/pi-tui/src/components/stack.ts +0 -155
  469. package/vendor/pi-tui/src/components/text.ts +0 -108
  470. package/vendor/pi-tui/src/components/truncated-text.ts +0 -66
  471. package/vendor/pi-tui/src/components/v-stack.ts +0 -34
  472. package/vendor/pi-tui/src/editor-component.ts +0 -75
  473. package/vendor/pi-tui/src/fuzzy.ts +0 -138
  474. package/vendor/pi-tui/src/index.ts +0 -149
  475. package/vendor/pi-tui/src/keybindings.ts +0 -321
  476. package/vendor/pi-tui/src/keys.ts +0 -1402
  477. package/vendor/pi-tui/src/kill-ring.ts +0 -47
  478. package/vendor/pi-tui/src/latex.ts +0 -1381
  479. package/vendor/pi-tui/src/layout-node.ts +0 -52
  480. package/vendor/pi-tui/src/layout.ts +0 -411
  481. package/vendor/pi-tui/src/native-modifiers.ts +0 -60
  482. package/vendor/pi-tui/src/native-module-path.ts +0 -32
  483. package/vendor/pi-tui/src/stdin-buffer.ts +0 -445
  484. package/vendor/pi-tui/src/terminal-colors.ts +0 -74
  485. package/vendor/pi-tui/src/terminal-image.ts +0 -701
  486. package/vendor/pi-tui/src/terminal.ts +0 -554
  487. package/vendor/pi-tui/src/tui-alt-screen.ts +0 -1379
  488. package/vendor/pi-tui/src/tui-main-screen.ts +0 -655
  489. package/vendor/pi-tui/src/tui.ts +0 -1264
  490. package/vendor/pi-tui/src/undo-stack.ts +0 -29
  491. package/vendor/pi-tui/src/utils.ts +0 -1327
  492. package/vendor/pi-tui/src/word-navigation.ts +0 -118
  493. package/vendor/pi-tui/test/test-themes.ts +0 -39
  494. package/vendor/pi-tui/test/virtual-terminal.ts +0 -219
  495. /package/{src/providers → dist/cli}/models-index.json +0 -0
@@ -1,387 +0,0 @@
1
- /** Skills module (hermes pattern): SKILL.md files with frontmatter index,
2
- * mtime+size manifest invalidation, usage sidecars, deterministic staleness.
3
- * No LLM consolidation — lifecycle is a pure function of usage age.
4
- *
5
- * agentskills.io shape (port #72): the frontmatter block is still read by parseFrontmatter below — which folds
6
- * `>`/`|` block scalars, the form 3 of the 19 anthropics/skills descriptions are written in — and the FIELDS are
7
- * then validated by spec.ts validateSkillMeta in LENIENT mode: an unreadable file, a missing block and an
8
- * empty/missing description are INVALID (not loaded); every other spec deviation — a description over 1024 chars,
9
- * a missing or non-conforming name, name ≠ directory, compatibility over 500, a non-string metadata value, an
10
- * unknown key — LOADS with a line in ScanResult.warnings. Before that, such a file either loaded silently wrong or
11
- * vanished from the index with nothing said. A missing `name:` falls back to the directory name.
12
- *
13
- * A directory holding SKILL.md is a LEAF: find() does not descend into it, so a skill's own
14
- * `references/example/SKILL.md` is not a second indexed skill (it used to be, which let a skill that merely
15
- * documents itself inflate the prompt index). Dot-dirs are skipped, which is also what keeps the installer's
16
- * `.install-<token>` / `.old-<token>` staging invisible to a concurrent scan (pack.ts).
17
- *
18
- * Trust, stated as a DECISION and not an omission: a project `.rovecode/skills/` is walked whether or not the
19
- * checkout is trusted. This repository's trust gate (core/trust.ts) exists to stop a cloned repo from making us
20
- * RUN code — hooks, sandbox.json, the command-bearing settings keys, project MCP servers — and a skill is prompt
21
- * text, which is a different class of problem. What is still open, and should be written down rather than implied:
22
- * a cloned repo's skill DESCRIPTIONS do reach the system prompt through the index, so this is a prompt-injection
23
- * path, and `.rovecode/memory/MEMORY.md` is the same path by another route. Both want ONE answer (withhold from the
24
- * prompt with a boot note, self-trusting our own writes) rather than two half-answers in one binary, so the seam is
25
- * here — SkillStoreOptions.walkProject — and deliberately not wired to anything yet. `allowed-tools` is likewise
26
- * parsed and carried as DATA: nothing enforces it, and skills-spec.test.ts pins that, because a field that reads
27
- * like a permission is one refactor away from being a security claim we never implemented. */
28
-
29
- import {
30
- existsSync, readFileSync, readdirSync, renameSync, statSync, utimesSync, writeFileSync,
31
- } from "node:fs";
32
- import { rovecodeHome } from "../providers/auth.ts";
33
- import { basename, dirname, join, resolve } from "node:path";
34
- import { parseSkillFrontmatter, validateSkillMeta, type SkillFrontmatter } from "./spec.ts";
35
-
36
- /** The spec view of a file parseFrontmatter already read: its flat fields (block scalars folded) with the
37
- * shapes and the `metadata:` map that only the spec walker can see. The two readers agree on every flat
38
- * key; where they differ — an indented `metadata:` map, a list — the spec walker is the one that knows. */
39
- function specViewOf(fm: Record<string, string>, raw: string): SkillFrontmatter {
40
- const spec = parseSkillFrontmatter(raw);
41
- const fields = { ...(spec?.fields ?? {}), ...fm }; // parseFrontmatter wins on flat values (its block-scalar folding)
42
- return {
43
- fields,
44
- shapes: spec?.shapes ?? Object.fromEntries(Object.keys(fm).map((k) => [k, "scalar" as const])),
45
- metadata: spec?.metadata ?? {},
46
- metadataNonString: spec?.metadataNonString ?? [],
47
- body: spec?.body ?? "",
48
- };
49
- }
50
-
51
- export const SKILL_FILE = "SKILL.md";
52
- /** the two per-skill sidecars (usage below; the versioned.ts edit ledger) — never resources, never packed */
53
- export const SIDECAR_SUFFIXES: readonly string[] = [".usage.json", ".versions.jsonl"];
54
- export const isSidecarName = (name: string): boolean => SIDECAR_SUFFIXES.some((x) => name.endsWith(x));
55
- export const MAX_DESCRIPTION_CHARS = 60;
56
- export const STALE_AFTER_MS = 1000 * 60 * 60 * 24 * 90; // 90 days
57
-
58
- export type SkillScope = "project" | "global";
59
-
60
- export interface Skill {
61
- name: string;
62
- /** one line for the system-prompt index: clipped to MAX_DESCRIPTION_CHARS on a word boundary */
63
- description: string;
64
- /** the description as written, however long — what `skill_view` and the market show */
65
- fullDescription: string;
66
- /** top-level `version`, else `metadata.version`, else "0.0.0" */
67
- version: string;
68
- /** absolute path of the SKILL.md */
69
- path: string;
70
- /** absolute skill directory — relative paths inside the skill resolve against it */
71
- dir: string;
72
- /** content after the frontmatter block */
73
- body: string;
74
- scope: SkillScope;
75
- mtimeMs: number;
76
- size: number;
77
- license?: string;
78
- compatibility?: string;
79
- /** the `metadata:` map (string → string) */
80
- metadata: Record<string, string>;
81
- /** `allowed-tools` whitespace-split — DATA ONLY: nothing enforces it (see the header) */
82
- allowedTools: string[];
83
- }
84
-
85
- export interface InvalidSkill { path: string; reason: string }
86
- /** a skill that LOADED with a spec deviation (lenient mode) */
87
- export interface SkillWarning { path: string; reason: string }
88
-
89
- export interface SkillUsage {
90
- viewCount: number;
91
- lastViewedAt: number;
92
- }
93
-
94
- export interface ScanChanges {
95
- added: string[];
96
- removed: string[];
97
- changed: string[];
98
- }
99
-
100
- const IGNORED_DIRS: Record<string, true> = { node_modules: true, ".git": true };
101
- export interface ScanResult {
102
- skills: Skill[];
103
- invalid: InvalidSkill[];
104
- /** loaded-with-warning files of this scan (added / changed only, like `invalid`) */
105
- warnings: SkillWarning[];
106
- changes: ScanChanges;
107
- }
108
-
109
- /** The skills dir of a scope: `<cwd>/.rovecode/skills` or `<user home>/skills` (ROVECODE_HOME honoured).
110
- * The ONE place the two joins live, so the CLI and the store cannot disagree (mcpConfigPath precedent). */
111
- export function skillsDir(cwd: string, scope: SkillScope): string {
112
- return scope === "project" ? join(cwd, ".rovecode", "skills") : join(rovecodeHome(), "skills");
113
- }
114
-
115
- export interface SkillStoreOptions {
116
- /** project skills dir (default: <cwd>/.rovecode/skills) */
117
- projectDir?: string;
118
- /** global skills dir; null disables (default: ~/.rovecode/skills) */
119
- globalDir?: string | null;
120
- /** max directory depth when walking for SKILL.md (default 3) */
121
- maxDepth?: number;
122
- /** the seam named in the header: false leaves the project dir out of every scan. Nothing sets it today —
123
- * skills are prompt text, and the trust gate is about running code. Default true. */
124
- walkProject?: boolean;
125
- /** more roots — a plugin's skills folder (src/plugins) — walked like the two above, tagged with the
126
- * scope the plugin itself has (a project plugin's skills win a name clash as the project's own would) */
127
- extraDirs?: readonly { dir: string; scope: SkillScope }[];
128
- }
129
-
130
- // ---------- Frontmatter ----------
131
-
132
- /** Split `---\nkey: value` frontmatter from body. Returns null when no block.
133
- *
134
- * Values may be a plain scalar, or a YAML block scalar — `key: >` / `|` / `>-` / `|-` followed by
135
- * more-indented lines, which real SKILL.md files use for anything longer than a phrase. Measured on
136
- * anthropics/skills (2026-09-05): 3 of 19 skills write their description that way, and reading only
137
- * the header line gave `description: ">"` — not an error, just a skill whose one-line summary is a
138
- * punctuation mark, with its continuation lines mistaken for keys of their own. That is worse than
139
- * rejecting it, because nothing says anything went wrong. Folded (`>`) joins its lines with a space,
140
- * literal (`|`) keeps the newlines, a trailing `-` strips the final newline. The rest of YAML —
141
- * anchors, nested maps, lists — is still out of scope: this is a frontmatter reader, not a parser. */
142
- export function parseFrontmatter(text: string): { fm: Record<string, string>; body: string } | null {
143
- if (!text.startsWith("---")) return null;
144
- const end = text.indexOf("\n---", 3);
145
- if (end === -1) return null;
146
- const closeEnd = end + 1 + text.slice(end + 1).indexOf("\n") + 1; // past the closing `---` line
147
- const block = text.slice(4, end); // skip leading `---\n`
148
- const body = text.slice(closeEnd).replace(/^\r?\n/, "");
149
- const fm: Record<string, string> = {};
150
- const lines = block.split(/\r?\n/);
151
- for (let i = 0; i < lines.length; i++) {
152
- const line = lines[i] as string;
153
- if (/^\s/.test(line)) continue; // an orphan continuation is not a key of its own
154
- const c = line.indexOf(":");
155
- if (c === -1) continue;
156
- const k = line.slice(0, c).trim();
157
- if (!k) continue;
158
- const rest = line.slice(c + 1).trim();
159
- const m = /^([|>])([+-]?)$/.exec(rest);
160
- if (m === null) { fm[k] = rest.replace(/^["']|["']$/g, ""); continue; }
161
- const folded = m[1] === ">";
162
- const owned: string[] = [];
163
- while (i + 1 < lines.length) {
164
- const next = lines[i + 1] as string;
165
- if (next.trim() !== "" && !/^\s/.test(next)) break; // back to column 0: the block ended
166
- owned.push(next.replace(/^\s+/, ""));
167
- i++;
168
- }
169
- while (owned.length > 0 && owned[owned.length - 1] === "") owned.pop();
170
- fm[k] = folded ? owned.join(" ").replace(/\s+/g, " ").trim() : owned.join("\n");
171
- }
172
- return { fm, body };
173
- }
174
-
175
- /** Clip to the index budget on a word boundary, with an ellipsis so a reader can tell it was cut. */
176
- export function clipDescription(text: string): string {
177
- const one = text.replace(/\s+/g, " ").trim();
178
- if (one.length <= MAX_DESCRIPTION_CHARS) return one;
179
- const cut = one.slice(0, MAX_DESCRIPTION_CHARS - 1);
180
- const sp = cut.lastIndexOf(" ");
181
- return `${(sp > MAX_DESCRIPTION_CHARS / 2 ? cut.slice(0, sp) : cut).replace(/[,;:.]$/, "")}…`;
182
- }
183
-
184
- /** Lenient load: `invalid` only for an unreadable file, a missing block or an empty description; every other
185
- * spec deviation is a warning and the skill loads (see the header). */
186
- function parseSkillFile(path: string, scope: SkillScope): { skill: Skill; warnings: string[] } | { invalid: InvalidSkill } {
187
- let text: string;
188
- try {
189
- text = readFileSync(path, "utf8");
190
- } catch (e) {
191
- return { invalid: { path, reason: `unreadable: ${(e as Error).message}` } };
192
- }
193
- const parsed = parseFrontmatter(text);
194
- if (!parsed) return { invalid: { path, reason: "missing frontmatter block" } };
195
- const dir = dirname(path);
196
- // the spec field set over the SAME reader as before: parseFrontmatter's flat map plus its block scalars,
197
- // spec.ts's `metadata:` walk for the nested map, then the lenient checks
198
- const v = validateSkillMeta(specViewOf(parsed.fm, text), basename(dir), "lenient");
199
- if (v.errors.length > 0) return { invalid: { path, reason: v.errors.join("; ") } };
200
- const description = v.meta.description;
201
- // A long description is CLIPPED for the index, never a reason to reject the skill. The cap exists to
202
- // protect the system prompt (50 skills x a 950-char description is 47 KB of prefix), and clipping
203
- // protects it just as well while a hard reject throws away a working skill for being wordy. Measured
204
- // on anthropics/skills (2026-09-05): 16 of 19 real skills are over the cap, median 319 chars, longest
205
- // 950 — the whole public corpus would have been unusable. The two other places rovecode reads a
206
- // description already clip (plugins/manifest.ts, tui/commands.ts); this was the outlier.
207
- const st = statSync(path);
208
- return {
209
- skill: {
210
- name: v.meta.name,
211
- description: clipDescription(description),
212
- fullDescription: description,
213
- version: v.meta.version || "0.0.0",
214
- path, dir, body: parsed.body, scope,
215
- mtimeMs: st.mtimeMs, size: st.size,
216
- ...(v.meta.license !== undefined ? { license: v.meta.license } : {}),
217
- ...(v.meta.compatibility !== undefined ? { compatibility: v.meta.compatibility } : {}),
218
- metadata: v.meta.metadata,
219
- allowedTools: v.meta.allowedTools,
220
- },
221
- warnings: v.warnings,
222
- };
223
- }
224
-
225
- // ---------- Usage sidecar ----------
226
-
227
- export function usagePath(skillFile: string): string {
228
- return skillFile + ".usage.json";
229
- }
230
-
231
- export function readUsage(skillFile: string): SkillUsage | undefined {
232
- const p = usagePath(skillFile);
233
- if (!existsSync(p)) return undefined;
234
- try {
235
- const raw = JSON.parse(readFileSync(p, "utf8")) as Partial<SkillUsage>;
236
- return {
237
- viewCount: typeof raw.viewCount === "number" ? raw.viewCount : 0,
238
- lastViewedAt: typeof raw.lastViewedAt === "number" ? raw.lastViewedAt : 0,
239
- };
240
- } catch {
241
- return undefined; // corrupt sidecar → treat as never used
242
- }
243
- }
244
-
245
- /** Atomic write: temp file + rename, so a concurrent reader never sees a partial sidecar. */
246
- export function writeUsageAtomic(skillFile: string, usage: SkillUsage): void {
247
- const p = usagePath(skillFile);
248
- const tmp = p + ".tmp";
249
- writeFileSync(tmp, JSON.stringify(usage, null, 2));
250
- renameSync(tmp, p);
251
- }
252
-
253
- // ---------- Lifecycle ----------
254
-
255
- /** Deterministic: stale when last view (or, never viewed, file mtime) is older than 90d. */
256
- export function skillLifecycle(
257
- skill: Pick<Skill, "mtimeMs">,
258
- usage: SkillUsage | undefined,
259
- now: number,
260
- ): "active" | "stale" {
261
- const lastActivity = usage && usage.viewCount > 0 ? usage.lastViewedAt : skill.mtimeMs;
262
- return now - lastActivity > STALE_AFTER_MS ? "stale" : "active";
263
- }
264
-
265
- // ---------- Store ----------
266
-
267
- export class SkillStore {
268
- private readonly projectDir: string;
269
- private readonly globalDir: string | null;
270
- private readonly maxDepth: number;
271
- /** path → parsed skill (cache valid while manifest matches) */
272
- private cache = new Map<string, Skill>();
273
- /** path → `mtimeMs:size` (hermes manifest invalidation) */
274
- private manifest = new Map<string, string>();
275
-
276
- constructor(readonly cwd: string, opts: SkillStoreOptions = {}) {
277
- this.projectDir = resolve(cwd, opts.projectDir ?? join(".rovecode", "skills"));
278
- this.globalDir = opts.globalDir === null
279
- ? null
280
- // rovecodeHome(), not homedir(): ROVECODE_HOME is how every other part of rovecode finds its
281
- // config — hooks, settings, plugins, credentials — and skills were the one that ignored it. So a
282
- // user who set it read skills from a directory nothing else used, and every test that isolates
283
- // itself with a scratch home silently indexed the DEVELOPER's real skills instead. That is how
284
- // this was found: installing one skill into a real home broke a plugin test that never touches
285
- // skills, and the failure named a skill the test had never heard of.
286
- : resolve(opts.globalDir ?? join(rovecodeHome(), "skills"));
287
- this.maxDepth = opts.maxDepth ?? 3;
288
- this.walkProject = opts.walkProject !== false;
289
- this.extraDirs = (opts.extraDirs ?? []).map(({ dir, scope }) => [resolve(cwd, dir), scope]);
290
- }
291
- private readonly extraDirs: Array<[string, SkillScope]>;
292
- private readonly walkProject: boolean;
293
-
294
- private dirs(): Array<[string, SkillScope]> {
295
- const d: Array<[string, SkillScope]> = this.walkProject ? [[this.projectDir, "project"]] : [];
296
- if (this.globalDir && existsSync(this.globalDir)) d.push([this.globalDir, "global"]);
297
- for (const [dir, scope] of this.extraDirs) if (existsSync(dir)) d.push([dir, scope]);
298
- return d;
299
- }
300
-
301
- private find(root: string, dir: string = root, depth: number = 0): string[] {
302
- let entries;
303
- try {
304
- entries = readdirSync(dir, { withFileTypes: true });
305
- } catch {
306
- return [];
307
- }
308
- // A dir holding SKILL.md is a LEAF: its sub-dirs are that skill's own resources, never skills of their own.
309
- if (entries.some((e) => e.isFile() && e.name === SKILL_FILE)) return [join(dir, SKILL_FILE)];
310
- const found: string[] = [];
311
- for (const e of entries) {
312
- const p = join(dir, e.name);
313
- if (e.isFile() && e.name === SKILL_FILE) found.push(p);
314
- else if (e.isDirectory() && depth < this.maxDepth && !IGNORED_DIRS[e.name] && !e.name.startsWith(".")) {
315
- found.push(...this.find(root, p, depth + 1));
316
- }
317
- }
318
- return found;
319
- }
320
-
321
- /** Rescan: stat every SKILL.md, diff the manifest, reparse only added/changed files. */
322
- scan(): ScanResult {
323
- const next = new Map<string, string>();
324
- const scopeOf = new Map<string, SkillScope>(); // by root, not by path prefix: a plugin's project skills live outside <cwd>/.rovecode/skills
325
- for (const [dir, scope] of this.dirs()) {
326
- for (const p of this.find(dir)) {
327
- try {
328
- const st = statSync(p);
329
- next.set(p, `${st.mtimeMs}:${st.size}`);
330
- if (!scopeOf.has(p)) scopeOf.set(p, scope);
331
- } catch { /* vanished between readdir and stat */ }
332
- }
333
- }
334
- const changes: ScanChanges = { added: [], removed: [], changed: [] };
335
- for (const [p, sig] of next) {
336
- if (!this.manifest.has(p)) changes.added.push(p);
337
- else if (this.manifest.get(p) !== sig) changes.changed.push(p);
338
- }
339
- for (const p of this.manifest.keys()) if (!next.has(p)) changes.removed.push(p);
340
-
341
- const invalid: InvalidSkill[] = [];
342
- const warnings: SkillWarning[] = [];
343
- for (const p of [...changes.added, ...changes.changed]) {
344
- const scope = scopeOf.get(p) ?? (p.startsWith(this.projectDir) ? "project" : "global");
345
- const r = parseSkillFile(p, scope);
346
- if ("skill" in r) {
347
- this.cache.set(p, r.skill);
348
- for (const reason of r.warnings) warnings.push({ path: p, reason });
349
- } else { this.cache.delete(p); invalid.push(r.invalid); }
350
- }
351
- for (const p of changes.removed) this.cache.delete(p);
352
- this.manifest = next;
353
- return { skills: this.list(), invalid, warnings, changes };
354
- }
355
-
356
- /** Project scope wins on name collisions with the global dir. */
357
- list(): Skill[] {
358
- const byName = new Map<string, Skill>();
359
- for (const s of this.cache.values()) {
360
- const prev = byName.get(s.name);
361
- if (!prev || (prev.scope === "global" && s.scope === "project")) byName.set(s.name, s);
362
- }
363
- return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
364
- }
365
-
366
- get(name: string): Skill | undefined {
367
- return this.list().find((s) => s.name === name);
368
- }
369
-
370
- usage(name: string): SkillUsage | undefined {
371
- const s = this.get(name);
372
- return s ? readUsage(s.path) : undefined;
373
- }
374
-
375
- /** Record one view. Returns the updated sidecar contents. */
376
- bumpUsage(skill: Skill, now: number = Date.now()): SkillUsage {
377
- const prev = readUsage(skill.path);
378
- const usage: SkillUsage = { viewCount: (prev?.viewCount ?? 0) + 1, lastViewedAt: now };
379
- writeUsageAtomic(skill.path, usage);
380
- return usage;
381
- }
382
- }
383
-
384
- /** Touch a skill file's mtime (same size) so manifest invalidation is observable in tests. */
385
- export function touchSkillFile(path: string, at: Date = new Date()): void {
386
- utimesSync(path, at, at);
387
- }
@@ -1,220 +0,0 @@
1
- /** Skill packaging + install (port #72): `<name>.tar.gz` through the Bun.Archive built-in (no tar / zip dependency)
2
- * and a staged install finished by a rename swap. Pattern sources (no code copied): opencode-2026
3
- * skill/discovery.ts:94-124 (MIT — stage into a hidden `.tmp-<token>` dir, validate, rename the old skill aside,
4
- * rename staging in, remove the old; on failure put the old back) and the house writeUsageAtomic temp + rename
5
- * idiom (index.ts). The agentskills.io specification defines NO archive format: a tar.gz holding ONE top-level
6
- * directory named after the skill is rovecode's convention (`.skill` zips are neither produced nor consumed).
7
- *
8
- * Safety: every archive entry is checked BEFORE anything is written — the `files()` keys (checkArchiveEntries: `..`,
9
- * absolute, drive letter, backslash, empty segment, archive shape) AND the raw tar headers (findLinkMember: a symlink,
10
- * hard-link, device or fifo member is refused — `files()` lists regular files only, so those members are invisible to
11
- * the key check). The staged tree is then written file-by-file from that validated Map (materialise); libarchive's
12
- * `extract` is never called: on POSIX it would create a symlink member and write the next entry THROUGH it, outside
13
- * the staging dir, and Bun 1.3.14's `extract` also silently flattens `../x` INTO the target and drops `C:/x` (both
14
- * probed). The observable pin is "exit 2 and nothing under the skills dir changed". Staging is a dot-dir inside the
15
- * skills dir (`.install-<token>`: invisible to the scanner, same volume as the target so the rename is atomic); the
16
- * staged SKILL.md must load through the SAME lenient loader as a scan with zero invalid files before anything is renamed.
17
- * File bytes go into the archive via readFileSync — Bun issue #28459 (open): `Bun.file()` inputs yield EMPTY entries.
18
- * Pure fs + archive: cli/skills-cmd.ts owns argv, io and exit codes (SkillCmdError.code: 1 refused · 2 io / unsafe). */
19
-
20
- import { copyFileSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
21
- import { basename, dirname, join, resolve } from "node:path";
22
- import { gunzipSync } from "node:zlib";
23
- import { isSidecarName, SKILL_FILE, SkillStore } from "./index.ts";
24
-
25
- export const MAX_ARCHIVE_ENTRIES = 2000;
26
- export const MAX_ARCHIVE_BYTES = 64 * 1024 * 1024;
27
- /** gunzip output cap for the header walk: a 64 MiB skill plus its headers fits, a header-only gzip bomb does not */
28
- export const INFLATE_CAP = 2 * MAX_ARCHIVE_BYTES;
29
- const SKIP_DIRS = new Set([".git", "node_modules"]);
30
-
31
- /** a refused (1) or failed (2) skills command; the CLI prints the message and exits with `code` */
32
- export class SkillCmdError extends Error {
33
- constructor(message: string, readonly code: 1 | 2) {
34
- super(message);
35
- }
36
- }
37
-
38
- /** Every regular file under `dir` as sorted posix-relative paths; usage / version sidecars, .git and node_modules
39
- * skipped; a symlink anywhere → SkillCmdError(1) (an archive must not carry links, a copy must not follow them). */
40
- export function collectSkillFiles(dir: string): string[] {
41
- const out: string[] = [];
42
- const walk = (d: string, rel: string): void => {
43
- for (const e of readdirSync(d, { withFileTypes: true })) {
44
- const r = rel === "" ? e.name : `${rel}/${e.name}`;
45
- const st = lstatSync(join(d, e.name));
46
- if (st.isSymbolicLink()) throw new SkillCmdError(`symlink refused: ${r}`, 1);
47
- if (st.isDirectory()) { if (!SKIP_DIRS.has(e.name)) walk(join(d, e.name), r); }
48
- else if (st.isFile() && !isSidecarName(e.name)) out.push(r);
49
- }
50
- };
51
- walk(dir, "");
52
- return out.sort();
53
- }
54
-
55
- /** gzip tarball bytes: `<name>/<rel>` for every collected file, keys sorted (posix), contents via readFileSync. */
56
- export async function packSkill(dir: string, name: string): Promise<Uint8Array> {
57
- const entries: Record<string, Uint8Array> = {};
58
- for (const rel of collectSkillFiles(dir)) entries[`${name}/${rel}`] = readFileSync(join(dir, rel));
59
- return new Bun.Archive(entries, { compress: "gzip" }).bytes();
60
- }
61
-
62
- /** The pre-write rules over `files()` keys: the reason the archive is refused, or null when it may be unpacked. */
63
- export function checkArchiveEntries(keys: readonly string[]): string | null {
64
- if (keys.length === 0) return "archive is empty";
65
- if (keys.length > MAX_ARCHIVE_ENTRIES) return `archive has ${keys.length} entries (max ${MAX_ARCHIVE_ENTRIES})`;
66
- const tops = new Set<string>();
67
- for (const key of keys) {
68
- const shown = JSON.stringify(key);
69
- if (key.includes("\\")) return `unsafe entry ${shown}: backslash`;
70
- if (key.startsWith("/")) return `unsafe entry ${shown}: absolute path`;
71
- if (/^[A-Za-z]:/.test(key)) return `unsafe entry ${shown}: drive letter`;
72
- const segs = key.split("/");
73
- if (segs.some((s) => s === "")) return `unsafe entry ${shown}: empty path segment`;
74
- if (segs.some((s) => s === ".." || s === ".")) return `unsafe entry ${shown}: relative segment`;
75
- if (segs.length < 2) return `entry ${shown} is not inside a top-level directory`;
76
- tops.add(segs[0]!);
77
- }
78
- if (tops.size !== 1) return `archive must hold exactly one top-level directory (found ${[...tops].sort().join(", ")})`;
79
- const top = [...tops][0]!;
80
- if (!keys.includes(`${top}/${SKILL_FILE}`)) return `top-level directory ${JSON.stringify(top)} has no ${SKILL_FILE}`;
81
- return null;
82
- }
83
-
84
- /** Member types a skill archive may not carry — `files()` never lists them, so an install would silently drop a hard
85
- * link and (on POSIX, under `extract`) follow a symlink out of the staging dir. */
86
- const LINK_TYPES: Record<string, string> = { "1": "hard link", "2": "symlink", "3": "character device", "4": "block device", "6": "fifo" };
87
-
88
- /** Walk the raw ustar headers and name the first link / device / fifo member, or null. gzip (1f 8b) is inflated under
89
- * INFLATE_CAP (past it → a refusal); a body that is not ustar at its first header, or a size field the walk cannot
90
- * read, returns null — `files()` already accepted the archive and materialise() writes only its regular-file keys, so
91
- * the walk is what NAMES a malformed archive, not what keeps a missed member harmless. */
92
- export function findLinkMember(bytes: Uint8Array): string | null {
93
- let tar: Uint8Array = bytes;
94
- if (bytes[0] === 0x1f && bytes[1] === 0x8b) {
95
- try { tar = gunzipSync(bytes, { maxOutputLength: INFLATE_CAP }); }
96
- catch (e) { return (e as NodeJS.ErrnoException).code === "ERR_BUFFER_TOO_LARGE" ? `archive inflates past ${INFLATE_CAP} bytes` : null; }
97
- }
98
- const text = (off: number, len: number): string => new TextDecoder().decode(tar.subarray(off, off + len)).replace(/\0[\s\S]*$/, "");
99
- if (text(257, 5) !== "ustar") return null;
100
- for (let off = 0; off + 512 <= tar.length && tar[off] !== 0; ) { // an all-zero block (empty name) ends the archive
101
- const size = parseInt(text(off + 124, 12).trim() || "0", 8);
102
- if (!Number.isFinite(size)) return null; // base-256 size or garbage: leave it to files()
103
- const kind = LINK_TYPES[String.fromCharCode(tar[off + 156]!)];
104
- if (kind !== undefined) {
105
- const prefix = text(off + 345, 155);
106
- return `unsafe entry ${JSON.stringify(prefix === "" ? text(off, 100) : `${prefix}/${text(off, 100)}`)}: ${kind} member`;
107
- }
108
- off += 512 + Math.ceil(size / 512) * 512;
109
- }
110
- return null;
111
- }
112
-
113
- /** Write the validated regular-file entries under `dir` — these keys are the ONLY thing that ever reaches the disk
114
- * (no `extract`: no link members, no directory members, no mode bits from the archive). */
115
- async function materialise(files: Map<string, File>, dir: string): Promise<void> {
116
- for (const [key, file] of files) {
117
- const path = join(dir, key);
118
- mkdirSync(dirname(path), { recursive: true });
119
- writeFileSync(path, new Uint8Array(await file.arrayBuffer()));
120
- }
121
- }
122
-
123
- export interface InstallOptions {
124
- /** replace an existing `<skills dir>/<name>` (rename-swap; the old copy is restored when the swap fails) */
125
- force?: boolean;
126
- /** test seam: the per-file copy used while staging a directory (default copyFileSync) */
127
- copyFile?: (src: string, dst: string) => void;
128
- /** test seam: runs after the old skill was moved aside and before staging moves in — a throw exercises the rollback */
129
- onSwap?: () => void;
130
- }
131
- export interface InstallResult { name: string; version: string; path: string; warnings: string[] }
132
-
133
- const token = (): string => Math.random().toString(36).slice(2, 10);
134
-
135
- function stagingDir(skillsDir: string): string {
136
- mkdirSync(skillsDir, { recursive: true });
137
- const dir = join(skillsDir, `.install-${token()}`);
138
- mkdirSync(dir);
139
- return dir;
140
- }
141
-
142
- function removeDir(dir: string): void {
143
- for (let attempt = 1; ; attempt++) {
144
- try { rmSync(dir, { recursive: true, force: true }); return; }
145
- catch (e) { if (attempt >= 5) throw e; Bun.sleepSync(40 * attempt); } // Windows: a closing handle inside the tree
146
- }
147
- }
148
-
149
- /** Copy a skill directory (sidecars excluded) into the skills dir under its LOADED name. */
150
- export function installFromDir(src: string, skillsDir: string, opts: InstallOptions = {}): InstallResult {
151
- const from = resolve(src);
152
- if (!existsSync(join(from, SKILL_FILE))) throw new SkillCmdError(`${from} has no ${SKILL_FILE}`, 2);
153
- const files = collectSkillFiles(from);
154
- const staging = stagingDir(skillsDir);
155
- try {
156
- const dest = join(staging, basename(from)); // the source basename, so a missing `name:` falls back to the right dir
157
- const copy = opts.copyFile ?? copyFileSync;
158
- for (const rel of files) {
159
- const to = join(dest, rel);
160
- mkdirSync(dirname(to), { recursive: true });
161
- copy(join(from, rel), to);
162
- }
163
- return finish(staging, skillsDir, opts);
164
- } catch (e) {
165
- removeDir(staging);
166
- throw e;
167
- }
168
- }
169
-
170
- /** Unpack a tar.gz body: keys AND tar headers checked BEFORE anything is written, the tree written from the validated
171
- * Map (never `extract`), then the same loader as a scan, then the rename swap. */
172
- export async function installFromBytes(bytes: Uint8Array, skillsDir: string, opts: InstallOptions = {}): Promise<InstallResult> {
173
- if (bytes.length > MAX_ARCHIVE_BYTES) throw new SkillCmdError(`archive is ${bytes.length} bytes (max ${MAX_ARCHIVE_BYTES})`, 2);
174
- let files: Map<string, File>;
175
- try { files = await new Bun.Archive(bytes).files(); }
176
- catch (e) { throw new SkillCmdError(`not a tar.gz archive: ${e instanceof Error ? e.message : String(e)}`, 2); }
177
- const reason = checkArchiveEntries([...files.keys()]) ?? findLinkMember(bytes);
178
- if (reason !== null) throw new SkillCmdError(reason, 2);
179
- let total = 0;
180
- for (const f of files.values()) total += f.size;
181
- if (total > MAX_ARCHIVE_BYTES) throw new SkillCmdError(`archive unpacks to ${total} bytes (max ${MAX_ARCHIVE_BYTES})`, 2);
182
- const staging = stagingDir(skillsDir);
183
- try {
184
- await materialise(files, staging);
185
- return finish(staging, skillsDir, opts);
186
- } catch (e) {
187
- removeDir(staging);
188
- throw e;
189
- }
190
- }
191
-
192
- /** Validate the staged tree with the runtime's loader, then swap it into place. Throws with staging still present
193
- * (the callers remove it); after a failed swap the old skill is back and no `.old-*` dir remains. */
194
- function finish(staging: string, skillsDir: string, opts: InstallOptions): InstallResult {
195
- const r = new SkillStore(skillsDir, { projectDir: staging, globalDir: null }).scan();
196
- if (r.invalid.length > 0) throw new SkillCmdError(`staged skill is invalid: ${r.invalid.map((i) => i.reason).join("; ")}`, 2);
197
- if (r.skills.length !== 1) throw new SkillCmdError(`expected exactly one skill, found ${r.skills.length}`, 2);
198
- const skill = r.skills[0]!;
199
- if (!/^[^/\\]+$/.test(skill.name) || skill.name === "." || skill.name === ".." || skill.name.startsWith(".")) {
200
- throw new SkillCmdError(`refusing skill name ${JSON.stringify(skill.name)}`, 2);
201
- }
202
- const target = join(skillsDir, skill.name);
203
- const warnings = r.warnings.map((w) => w.reason);
204
- if (existsSync(target)) {
205
- if (!opts.force) throw new SkillCmdError(`${target} already exists (use --force to replace it)`, 1);
206
- const old = join(skillsDir, `.old-${token()}`);
207
- renameSync(target, old);
208
- try {
209
- opts.onSwap?.();
210
- renameSync(skill.dir, target);
211
- } catch (e) {
212
- rmSync(target, { recursive: true, force: true });
213
- renameSync(old, target);
214
- throw e;
215
- }
216
- removeDir(old);
217
- } else renameSync(skill.dir, target);
218
- removeDir(staging);
219
- return { name: skill.name, version: skill.version, path: target, warnings };
220
- }