@iowarp/clio-coder 0.3.6 → 0.3.8

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 (323) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +22 -6
  3. package/dist/{acp-2BEHC4DL.js → acp-U67UHUK2.js} +14 -14
  4. package/dist/{agents-LNNFTM53.js → agents-YU6SGALZ.js} +42 -35
  5. package/dist/assets/codewiki.json +1 -1
  6. package/dist/{auth-KXXFI2VS.js → auth-5ZPJOIVG.js} +32 -24
  7. package/dist/builtins-C6JMZVV6.js +17 -0
  8. package/dist/{chunk-E25LMLRW.js → chunk-26LEYJZH.js} +2 -2
  9. package/dist/{chunk-TSHXZTOQ.js → chunk-2HEJ2F35.js} +5 -5
  10. package/dist/{chunk-PBTHKCPN.js → chunk-2HFZQUHL.js} +7 -7
  11. package/dist/{chunk-FO5ZOVUY.js → chunk-3BINW3FP.js} +5 -5
  12. package/dist/{chunk-4OC57DA6.js → chunk-4DGYLA73.js} +53 -2
  13. package/dist/{chunk-CKXWIANG.js → chunk-4SPRNWDE.js} +18 -16
  14. package/dist/{chunk-E2ER4LJF.js → chunk-5C3AQNDW.js} +25 -1
  15. package/dist/{chunk-XF5N4U5A.js → chunk-5DHKRSMQ.js} +9 -8
  16. package/dist/{chunk-43AOLP7E.js → chunk-5FR74PWO.js} +2 -1
  17. package/dist/{chunk-EKY57CSP.js → chunk-5H3GB5BO.js} +68 -772
  18. package/dist/{chunk-6XXKFVSN.js → chunk-5Q2VVUKB.js} +4 -4
  19. package/dist/{chunk-DJVECN66.js → chunk-7RFXX52T.js} +295 -3705
  20. package/dist/{chunk-ZXF4XRKW.js → chunk-7RGZWPB6.js} +158 -8
  21. package/dist/{chunk-FYYLNIL5.js → chunk-A2NJGIB3.js} +2 -2
  22. package/dist/{chunk-XXQNGV4M.js → chunk-A3WNZD3P.js} +548 -72
  23. package/dist/{chunk-VEZEGCGW.js → chunk-B5CSFE7B.js} +23 -21
  24. package/dist/{chunk-2SFS6XQE.js → chunk-DGSYXYMX.js} +3 -2
  25. package/dist/chunk-DR52UMZW.js +21 -0
  26. package/dist/{chunk-ZWLZP4ZT.js → chunk-DYIM5TJT.js} +98 -12
  27. package/dist/{chunk-QKMUKYO7.js → chunk-E77JEWSD.js} +270 -125
  28. package/dist/{chunk-LYF7OHWH.js → chunk-EMYUUSFG.js} +12 -467
  29. package/dist/{chunk-4VP4KH3K.js → chunk-EQ63NRB7.js} +8 -8
  30. package/dist/chunk-FBVTI2TJ.js +518 -0
  31. package/dist/{chunk-R46L2BIR.js → chunk-FHJEP5SW.js} +19 -25
  32. package/dist/{chunk-WR67VIZY.js → chunk-GN57SG4G.js} +66 -8
  33. package/dist/{chunk-RD5U66HV.js → chunk-GPIEI3LY.js} +9 -9
  34. package/dist/{chunk-PCZJO5TI.js → chunk-GU2UIAFZ.js} +13 -178
  35. package/dist/chunk-GWS3VEIW.js +195 -0
  36. package/dist/chunk-H7IXIC72.js +103 -0
  37. package/dist/{chunk-3BPUFZDL.js → chunk-HLE42MG7.js} +3 -3
  38. package/dist/{chunk-24I7BN55.js → chunk-IFBNV6H6.js} +3 -3
  39. package/dist/{chunk-QNQHSOLF.js → chunk-IGWKHNIQ.js} +114 -55
  40. package/dist/chunk-IIZWH4XA.js +172 -0
  41. package/dist/{chunk-AD2SYQYC.js → chunk-IJ7RPIYJ.js} +124 -6
  42. package/dist/chunk-J3YUBZWY.js +382 -0
  43. package/dist/{chunk-5JGRAMKL.js → chunk-JOZYP4GM.js} +8 -6
  44. package/dist/{chunk-OH3TOQTB.js → chunk-K4XHGFR5.js} +751 -15
  45. package/dist/{chunk-EYPA3EGJ.js → chunk-KTYTFRMB.js} +178 -14
  46. package/dist/chunk-LU7P4LHA.js +33 -0
  47. package/dist/chunk-ME6CCNFO.js +108 -0
  48. package/dist/chunk-MXKJU4JB.js +1100 -0
  49. package/dist/{chunk-RY3LY4J5.js → chunk-N22QMJKY.js} +21 -18
  50. package/dist/chunk-NMPKI6XL.js +3006 -0
  51. package/dist/chunk-NUGM5KR6.js +165 -0
  52. package/dist/{chunk-22NAGB7X.js → chunk-P43ETTHK.js} +5 -94
  53. package/dist/{chunk-NILBFAPG.js → chunk-PMDBGQSJ.js} +2 -2
  54. package/dist/chunk-PT7HYKEM.js +165 -0
  55. package/dist/chunk-RVG5JXAL.js +41 -0
  56. package/dist/{chunk-GEYXPTRF.js → chunk-RWSI4YD7.js} +2 -2
  57. package/dist/{chunk-4BPJXDWC.js → chunk-TANS5ZJS.js} +35 -19
  58. package/dist/{verifiers-NCBTHHN2.js → chunk-TB5666IT.js} +67 -324
  59. package/dist/{chunk-QM3F2GKX.js → chunk-TLQJPP24.js} +7927 -8088
  60. package/dist/{chunk-G7MUEIGA.js → chunk-TT36MB5S.js} +2 -1
  61. package/dist/chunk-TTHACPOM.js +961 -0
  62. package/dist/{chunk-6US73PDB.js → chunk-TYPGUK6W.js} +7 -7
  63. package/dist/{chunk-MFFY33HR.js → chunk-U6MBIEMB.js} +554 -209
  64. package/dist/chunk-VAWNZU7Z.js +242 -0
  65. package/dist/{chunk-IR4CFBFN.js → chunk-VCBR6CU7.js} +12 -12
  66. package/dist/{chunk-WHJYKASB.js → chunk-VHN4MY6O.js} +2 -2
  67. package/dist/{chunk-XYDYPRZI.js → chunk-VWZOAB7K.js} +10 -10
  68. package/dist/{chunk-ZRGEBJ4T.js → chunk-WLFILSD5.js} +48 -48
  69. package/dist/{chunk-K7T3E2SR.js → chunk-WNIJTQQK.js} +12 -11
  70. package/dist/{chunk-CYQKWTG3.js → chunk-WSB3FPX7.js} +68 -20
  71. package/dist/{chunk-KHSFENX2.js → chunk-WWCZ5F23.js} +116 -14
  72. package/dist/{chunk-XE2VEJHX.js → chunk-WXY7KU3G.js} +2 -2
  73. package/dist/{chunk-PPAMZ32Z.js → chunk-XK56QHLX.js} +6 -1
  74. package/dist/{chunk-KOHPCX4K.js → chunk-XWSF374K.js} +5 -5
  75. package/dist/{chunk-CJUB2JJ2.js → chunk-YS5VLNH5.js} +10 -10
  76. package/dist/{chunk-ZZMN5OM4.js → chunk-ZNLWCMVZ.js} +2 -2
  77. package/dist/{chunk-WHGPSPT5.js → chunk-ZVJ5BLO2.js} +2 -2
  78. package/dist/cli/index.js +33 -31
  79. package/dist/{clio-M2KGYUFZ.js → clio-QVTYJ57A.js} +10 -10
  80. package/dist/{code-nav-GQNL7XA6.js → code-nav-FGGFIE7L.js} +5 -5
  81. package/dist/codewiki/build-worker.js +4 -4
  82. package/dist/{components-5TTYYX6G.js → components-ZFA3SAER.js} +9 -9
  83. package/dist/{config-XUUYQIWO.js → config-LW5IJFQN.js} +65 -55
  84. package/dist/{configure-IHJ7YOMV.js → configure-7XIZCOU4.js} +29 -24
  85. package/dist/{context-75MIWW3U.js → context-L3WL3X7K.js} +54 -43
  86. package/dist/{context-ZQ7SIFJV.js → context-N52ZA626.js} +30 -14
  87. package/dist/{context-74JLXAWD.js → context-Y6Y7QPR6.js} +12 -12
  88. package/dist/{context-clear-GYKWNUML.js → context-clear-MBQRLSDQ.js} +54 -43
  89. package/dist/{context-index-SSR5ECNE.js → context-index-HVMFQHK3.js} +5 -5
  90. package/dist/{context-working-set-UX5KEP4J.js → context-working-set-GS6DSO7F.js} +18 -18
  91. package/dist/{dispatch-runner-GIJBHNFL.js → dispatch-runner-22ZCNOM3.js} +367 -79
  92. package/dist/{docs-6FZSCG5B.js → docs-7LQ23DLM.js} +9 -9
  93. package/dist/{doctor-SVJ5BZCW.js → doctor-M7YEDGAE.js} +27 -23
  94. package/dist/{eval-CG6LLBLD.js → eval-BEC2WHDA.js} +73 -20
  95. package/dist/{evidence-ZYFIEN42.js → evidence-REJUMSKM.js} +70 -59
  96. package/dist/{evolve-QGEXEMDW.js → evolve-PY5ZBA5K.js} +50 -39
  97. package/dist/{extensions-ADGNCJJD.js → extensions-HVKU65YU.js} +7 -7
  98. package/dist/{fleet-S5R4ZOQY.js → fleet-7WZEWRFA.js} +236 -377
  99. package/dist/fleet-commands-UVHWM76J.js +70 -0
  100. package/dist/fleet-graph-6ULH7PES.js +125 -0
  101. package/dist/fleet-new-RDVJLHHH.js +48 -0
  102. package/dist/{fleet-preflight-BHSNPBMH.js → fleet-preflight-J53T6CCE.js} +5 -5
  103. package/dist/fleet-validate-72PC4SLA.js +79 -0
  104. package/dist/{init-5DRU55YR.js → init-OG3TPGQG.js} +69 -57
  105. package/dist/library-CNTMPLRF.js +217 -0
  106. package/dist/{memory-7YKKR6UC.js → memory-6IS7F275.js} +52 -41
  107. package/dist/{models-ZPOLRU2C.js → models-ENRJDA5W.js} +37 -31
  108. package/dist/{monitor-US5F5YGZ.js → monitor-XLDVO7TN.js} +79 -42
  109. package/dist/{orchestrator-E2AL4T5N.js → orchestrator-6KSPYRHA.js} +4126 -762
  110. package/dist/{paths-E7KYAQWE.js → paths-DBXMZMDU.js} +6 -6
  111. package/dist/registry-LG64LTF4.js +11 -0
  112. package/dist/{reset-KZ652EK6.js → reset-RZ4ER727.js} +12 -12
  113. package/dist/{run-SRNBKDWD.js → run-Y2CNK5RU.js} +89 -76
  114. package/dist/{share-CGZE33UP.js → share-A55GYP6Z.js} +37 -13
  115. package/dist/{skills-S2X4DLY5.js → skills-ALC5J6AT.js} +29 -13
  116. package/dist/{skills-eval-W2GGIC4R.js → skills-eval-JPBEBYQU.js} +60 -48
  117. package/dist/support-MIETYA5E.js +38 -0
  118. package/dist/{targets-54SWINWB.js → targets-VGNXIR3S.js} +46 -36
  119. package/dist/{terminal-lease-SAIF2OGY.js → terminal-lease-WOBR64YA.js} +4 -4
  120. package/dist/{uninstall-BVLWXKBT.js → uninstall-ZJF5H5ZN.js} +9 -9
  121. package/dist/{upgrade-JKAR27XC.js → upgrade-FUSUAGHR.js} +31 -28
  122. package/dist/{usage-MSAWCLX4.js → usage-N4MKVHKD.js} +140 -68
  123. package/dist/verifiers-YAWOJ3H2.js +336 -0
  124. package/dist/{verify-X5HDROLA.js → verify-LTDHYBGY.js} +10 -9
  125. package/dist/{wiki-generate-GUSOQ6ZP.js → wiki-generate-6M7GHTBJ.js} +75 -62
  126. package/dist/worker/entry.js +67 -57
  127. package/docs/README.md +3 -2
  128. package/docs/acp.md +1 -1
  129. package/docs/alcf-provider.md +1 -1
  130. package/docs/architecture.md +2 -2
  131. package/docs/artifact-versions.md +11 -6
  132. package/docs/built-in-agents.md +26 -2
  133. package/docs/capacity-and-scheduling.md +1 -1
  134. package/docs/commands-and-modes.md +83 -3
  135. package/docs/configuration-and-targets.md +86 -4
  136. package/docs/context-engine.md +1 -1
  137. package/docs/development-pipeline.md +1 -1
  138. package/docs/dispatch-architecture-rationale.md +1 -1
  139. package/docs/documentation-coverage.md +3 -3
  140. package/docs/documentation-guide.md +3 -3
  141. package/docs/eval-runner.md +1 -1
  142. package/docs/evals-internal.md +1 -1
  143. package/docs/evidence-and-memory.md +74 -10
  144. package/docs/evolution.md +1 -1
  145. package/docs/exit-codes-and-output.md +4 -1
  146. package/docs/extensions-and-sharing.md +6 -2
  147. package/docs/fleet-demo-runbook.md +2 -2
  148. package/docs/fleet-dispatch.md +228 -16
  149. package/docs/git-commit-provenance.md +2 -2
  150. package/docs/glossary.md +22 -2
  151. package/docs/installation-and-lifecycle.md +2 -2
  152. package/docs/middleware-and-components.md +2 -1
  153. package/docs/model-catalog.md +1 -1
  154. package/docs/observability.md +56 -9
  155. package/docs/proactive-memory.md +1 -1
  156. package/docs/prompt-envelope-and-tools.md +4 -2
  157. package/docs/provider-adapter-cookbook.md +1 -1
  158. package/docs/release-cut-checklist.md +83 -65
  159. package/docs/resource-library.md +59 -0
  160. package/docs/safety-model.md +2 -2
  161. package/docs/scientific-validation.md +3 -3
  162. package/docs/session-lifecycle.md +37 -1
  163. package/docs/skills-marketplace.md +16 -3
  164. package/docs/tool-usage.md +14 -7
  165. package/docs/trace-store.md +1 -1
  166. package/docs/troubleshooting.md +1 -1
  167. package/docs/tui-design.md +1 -1
  168. package/docs/worker-dispatch-mechanics.md +3 -3
  169. package/package.json +1 -2
  170. package/src/cli/argv.ts +5 -0
  171. package/src/cli/configure.ts +107 -23
  172. package/src/cli/doctor.ts +5 -1
  173. package/src/cli/evidence.ts +30 -25
  174. package/src/cli/fleet-commands.ts +37 -0
  175. package/src/cli/fleet-graph.ts +102 -0
  176. package/src/cli/fleet-new.ts +36 -0
  177. package/src/cli/fleet-preflight.ts +111 -0
  178. package/src/cli/fleet-validate.ts +30 -0
  179. package/src/cli/fleet.ts +173 -335
  180. package/src/cli/index.ts +3 -1
  181. package/src/cli/library.ts +190 -0
  182. package/src/cli/share.ts +13 -1
  183. package/src/cli/shared.ts +1 -0
  184. package/src/cli/targets.ts +4 -1
  185. package/src/cli/usage.ts +111 -19
  186. package/src/cli/validate-model.ts +60 -5
  187. package/src/core/bus-events.ts +29 -0
  188. package/src/core/commit-attribution.ts +4 -4
  189. package/src/core/config.ts +130 -0
  190. package/src/core/defaults.ts +81 -0
  191. package/src/core/path-boundary.ts +100 -0
  192. package/src/domains/agents/builtins/architect.md +1 -0
  193. package/src/domains/agents/builtins/oracle.md +33 -0
  194. package/src/domains/agents/catalog.ts +13 -1
  195. package/src/domains/agents/extension.ts +2 -11
  196. package/src/domains/agents/fleet-contract.ts +304 -24
  197. package/src/domains/agents/index.ts +14 -0
  198. package/src/domains/agents/recipe.ts +7 -1
  199. package/src/domains/agents/registry.ts +73 -5
  200. package/src/domains/agents/result-contract.ts +360 -15
  201. package/src/domains/agents/write-boundary.ts +15 -50
  202. package/src/domains/config/classify.ts +4 -0
  203. package/src/domains/context/project-rules.ts +51 -1
  204. package/src/domains/dispatch/active-route-planner.ts +14 -0
  205. package/src/domains/dispatch/assignment-reconcile.ts +22 -5
  206. package/src/domains/dispatch/assignment-store.ts +151 -14
  207. package/src/domains/dispatch/backoff.ts +2 -1
  208. package/src/domains/dispatch/capability-match.ts +1 -0
  209. package/src/domains/dispatch/checkout-writer-lease.ts +175 -0
  210. package/src/domains/dispatch/contract.ts +48 -0
  211. package/src/domains/dispatch/delegation-plan.ts +164 -0
  212. package/src/domains/dispatch/execution-plan.ts +76 -5
  213. package/src/domains/dispatch/execution-role.ts +12 -2
  214. package/src/domains/dispatch/execution-scheduler.ts +183 -67
  215. package/src/domains/dispatch/extension.ts +412 -74
  216. package/src/domains/dispatch/fleet-gate.ts +14 -0
  217. package/src/domains/dispatch/fleet-plan.ts +63 -3
  218. package/src/domains/dispatch/fleet-run.ts +791 -0
  219. package/src/domains/dispatch/gate-role-prompts.ts +47 -0
  220. package/src/domains/dispatch/host-verification.ts +178 -0
  221. package/src/domains/dispatch/index.ts +41 -1
  222. package/src/domains/dispatch/intent-requirements.ts +40 -0
  223. package/src/domains/dispatch/intent.ts +235 -0
  224. package/src/domains/dispatch/path-scope.ts +370 -0
  225. package/src/domains/dispatch/receipt-integrity.ts +9 -4
  226. package/src/domains/dispatch/state.ts +36 -3
  227. package/src/domains/dispatch/types.ts +64 -12
  228. package/src/domains/dispatch/validation.ts +69 -6
  229. package/src/domains/dispatch/write-boundary-enforcer.ts +45 -0
  230. package/src/domains/dispatch/write-boundary.ts +201 -22
  231. package/src/domains/eval/metrics/evidence.ts +79 -2
  232. package/src/domains/eval/runners/clio-run.ts +12 -2
  233. package/src/domains/evidence/build.ts +69 -11
  234. package/src/domains/evidence/index.ts +21 -0
  235. package/src/domains/evidence/provenance.ts +46 -11
  236. package/src/domains/evidence/trust-projection.ts +274 -0
  237. package/src/domains/evidence/trust-status.ts +155 -18
  238. package/src/domains/evidence/types.ts +4 -0
  239. package/src/domains/extensions/discovery.ts +88 -1
  240. package/src/domains/extensions/resources.ts +20 -8
  241. package/src/domains/extensions/state.ts +6 -2
  242. package/src/domains/extensions/types.ts +4 -1
  243. package/src/domains/lifecycle/doctor.ts +140 -1
  244. package/src/domains/middleware/index.ts +15 -0
  245. package/src/domains/middleware/watchdog.ts +281 -0
  246. package/src/domains/observability/contract.ts +3 -1
  247. package/src/domains/observability/cost.ts +12 -1
  248. package/src/domains/observability/extension.ts +2 -2
  249. package/src/domains/observability/index.ts +10 -0
  250. package/src/domains/observability/out-of-turn-usage.ts +223 -0
  251. package/src/domains/prompts/contract.ts +3 -5
  252. package/src/domains/providers/extension.ts +30 -2
  253. package/src/domains/resources/common-loader.ts +3 -0
  254. package/src/domains/resources/index.ts +20 -0
  255. package/src/domains/resources/library.ts +326 -0
  256. package/src/domains/resources/prompts/loader.ts +119 -14
  257. package/src/domains/resources/skills/marketplace.ts +37 -12
  258. package/src/domains/safety/policy-engine.ts +5 -5
  259. package/src/domains/safety/run-effects.ts +64 -1
  260. package/src/domains/safety/scope.ts +7 -12
  261. package/src/domains/session/handoff.ts +629 -0
  262. package/src/domains/share/archive.ts +67 -2
  263. package/src/engine/acp/server.ts +4 -1
  264. package/src/engine/prompt-templates.ts +18 -1
  265. package/src/engine/worker-runtime.ts +6 -3
  266. package/src/entry/orchestrator.ts +37 -0
  267. package/src/interactive/bus-notices.ts +26 -0
  268. package/src/interactive/chat-loop.ts +235 -1
  269. package/src/interactive/chat-renderer.ts +22 -0
  270. package/src/interactive/cost-overlay.ts +31 -3
  271. package/src/interactive/council-dispatch.ts +30 -0
  272. package/src/interactive/council-grid.ts +213 -0
  273. package/src/interactive/council.ts +99 -0
  274. package/src/interactive/dispatch-board.ts +311 -17
  275. package/src/interactive/fleet-run-preview.ts +307 -0
  276. package/src/interactive/footer/notifications.ts +219 -0
  277. package/src/interactive/handoff-round.ts +56 -0
  278. package/src/interactive/interactive-application.ts +43 -1
  279. package/src/interactive/interactive-event-projection.ts +23 -1
  280. package/src/interactive/interactive-slash-runtime.ts +52 -2
  281. package/src/interactive/interactive-subscriptions.ts +14 -2
  282. package/src/interactive/oracle.ts +179 -0
  283. package/src/interactive/overlay-ask-user-lifecycle.ts +6 -0
  284. package/src/interactive/overlay-general-openers.ts +190 -1
  285. package/src/interactive/overlay-key-routing.ts +17 -1
  286. package/src/interactive/overlay-lifecycle.ts +41 -1
  287. package/src/interactive/overlay-permission-lifecycle.ts +10 -0
  288. package/src/interactive/overlay-resource-openers.ts +11 -3
  289. package/src/interactive/overlay-session-lifecycle.ts +234 -2
  290. package/src/interactive/overlays/fleet-run-approval.ts +208 -0
  291. package/src/interactive/overlays/handoff-review.ts +185 -0
  292. package/src/interactive/overlays/library-install-confirm.ts +151 -0
  293. package/src/interactive/overlays/list-overlay.ts +168 -2
  294. package/src/interactive/overlays/settings.ts +221 -30
  295. package/src/interactive/overlays/side-question.ts +139 -0
  296. package/src/interactive/overlays/skills-hub.ts +401 -15
  297. package/src/interactive/side-question.ts +171 -0
  298. package/src/interactive/slash-commands.ts +439 -7
  299. package/src/interactive/slash-spec.ts +19 -6
  300. package/src/interactive/theme/tokens.ts +30 -0
  301. package/src/interactive/turn-middleware.ts +15 -1
  302. package/src/interactive/view/artifacts.ts +42 -9
  303. package/src/interactive/view/view-overlay.ts +15 -3
  304. package/src/interactive/watchdog-run.ts +75 -0
  305. package/src/interactive/worker-receipts.ts +14 -2
  306. package/src/interactive/worker-share.ts +56 -1
  307. package/src/interactive/worker-stream.ts +15 -0
  308. package/src/tools/bootstrap.ts +3 -0
  309. package/src/tools/compete-worktrees.ts +13 -79
  310. package/src/tools/dispatch-admission.ts +251 -18
  311. package/src/tools/dispatch-arguments.ts +84 -1
  312. package/src/tools/dispatch-plan.ts +165 -6
  313. package/src/tools/dispatch-runner.ts +364 -23
  314. package/src/tools/dispatch-types.ts +20 -1
  315. package/src/tools/dispatch.ts +72 -2
  316. package/src/tools/monitor.ts +29 -0
  317. package/src/tools/profiles.ts +18 -4
  318. package/src/tools/task-worktree.ts +238 -0
  319. package/src/tools/verify/authoring.ts +61 -1
  320. package/src/tools/verify/scripts.ts +62 -0
  321. package/src/tools/worker-evidence.ts +21 -14
  322. package/src/worker/spec-contract.ts +3 -1
  323. package/dist/chunk-HC4CLZ2Y.js +0 -68
@@ -0,0 +1,151 @@
1
+ /**
2
+ * The resource library's install confirmation, as an overlay.
3
+ *
4
+ * `clio-coder library add` prints every destination and SHA-256 hash and writes
5
+ * nothing until `--yes` is present. This overlay is that same gate for the
6
+ * Skills Hub: it states every path the install would write and the hash it
7
+ * would write there, and nothing reaches disk until the operator presses Enter.
8
+ * Esc leaves the plan unexecuted, so a cancelled confirmation is a run in which
9
+ * no file changed.
10
+ *
11
+ * The body is a pure function of the subject so the wording is testable without
12
+ * a TUI.
13
+ */
14
+
15
+ import {
16
+ type Component,
17
+ isKeyRelease,
18
+ matchesKey,
19
+ type OverlayHandle,
20
+ type TUI,
21
+ truncateToWidth,
22
+ wrapTextWithAnsi,
23
+ } from "../../engine/tui.js";
24
+ import { buildResponsiveHint, FocusBox, showClioOverlayFrame } from "../overlay-frame.js";
25
+ import { clioTheme, rule } from "../theme/index.js";
26
+
27
+ export const LIBRARY_INSTALL_CONFIRM_TITLE = "Library install";
28
+
29
+ const MIN_WIDTH = 48;
30
+ const MAX_WIDTH = 110;
31
+
32
+ /** One planned write, in the terms `library add` reports it. */
33
+ export interface LibraryInstallWrite {
34
+ ref: string;
35
+ path: string;
36
+ sha256: string;
37
+ }
38
+
39
+ export interface LibraryInstallConfirmSubject {
40
+ /** The typed reference the operator asked for, e.g. `fleet:release`. */
41
+ entryRef: string;
42
+ /** Every write, in dependency order, the requested entry last. */
43
+ writes: ReadonlyArray<LibraryInstallWrite>;
44
+ /** Requirements this confirmation would install alongside the entry. */
45
+ requirements: ReadonlyArray<string>;
46
+ /** Requirements already on disk, named so the plan accounts for all of them. */
47
+ satisfied: ReadonlyArray<string>;
48
+ }
49
+
50
+ export interface OpenLibraryInstallConfirmOverlayOptions {
51
+ subject: LibraryInstallConfirmSubject;
52
+ /** Live terminal width, so the box tracks the window it opened in. */
53
+ columns: number;
54
+ /** Enter: perform exactly the writes this body named. */
55
+ onAccept: () => void;
56
+ /** Esc: nothing is written. */
57
+ onCancel: () => void;
58
+ }
59
+
60
+ function confirmOverlayWidth(columns: number): number {
61
+ return Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, columns - 4));
62
+ }
63
+
64
+ /** Render the plan: what is being installed, alongside what, and to where. */
65
+ export function formatLibraryInstallConfirmBody(subject: LibraryInstallConfirmSubject, width: number): string[] {
66
+ const theme = clioTheme();
67
+ const contentWidth = Math.max(1, Math.floor(width));
68
+ const rows: string[] = [];
69
+
70
+ const headline =
71
+ subject.requirements.length > 0
72
+ ? `install ${subject.entryRef} with its requirements: ${subject.requirements.join(", ")}`
73
+ : `install ${subject.entryRef}`;
74
+ for (const line of wrapTextWithAnsi(theme.fg("accent", headline), contentWidth)) rows.push(line);
75
+ rows.push(rule(theme, contentWidth));
76
+
77
+ for (const write of subject.writes) {
78
+ rows.push(theme.fg("muted", truncateToWidth(`${write.ref} → ${write.path}`, contentWidth, "…", false)));
79
+ rows.push(theme.fg("dim", truncateToWidth(` sha256 ${write.sha256}`, contentWidth, "…", false)));
80
+ }
81
+
82
+ rows.push(rule(theme, contentWidth));
83
+ const satisfied = subject.satisfied.length > 0 ? subject.satisfied.join(", ") : "none";
84
+ for (const line of wrapTextWithAnsi(theme.fg("info", `satisfied requirements: ${satisfied}`), contentWidth)) {
85
+ rows.push(line);
86
+ }
87
+ for (const line of wrapTextWithAnsi(
88
+ theme.fg("dim", `${subject.writes.length} file or files are written only after Enter; Esc writes nothing`),
89
+ contentWidth,
90
+ )) {
91
+ rows.push(line);
92
+ }
93
+ return rows;
94
+ }
95
+
96
+ class LibraryInstallConfirmBody implements Component {
97
+ constructor(private readonly subject: LibraryInstallConfirmSubject) {}
98
+
99
+ render(width: number): string[] {
100
+ return formatLibraryInstallConfirmBody(this.subject, width);
101
+ }
102
+
103
+ invalidate(): void {}
104
+ }
105
+
106
+ export function openLibraryInstallConfirmOverlay(
107
+ tui: TUI,
108
+ options: OpenLibraryInstallConfirmOverlayOptions,
109
+ ): OverlayHandle {
110
+ let settled = false;
111
+
112
+ const accept = (): void => {
113
+ if (settled) return;
114
+ settled = true;
115
+ options.onAccept();
116
+ };
117
+ const cancel = (): void => {
118
+ if (settled) return;
119
+ settled = true;
120
+ options.onCancel();
121
+ };
122
+
123
+ const focus = new FocusBox(new LibraryInstallConfirmBody(options.subject), {
124
+ // Keys are matched by name, never by raw bytes: under the kitty keyboard
125
+ // protocol Esc arrives as CSI 27 u, and a byte comparison against "\x1b"
126
+ // left the overlay unanswerable. Everything unmatched is swallowed.
127
+ onInput: (data: string): void => {
128
+ if (settled || isKeyRelease(data)) return;
129
+ if (matchesKey(data, "enter")) {
130
+ accept();
131
+ return;
132
+ }
133
+ if (matchesKey(data, "escape")) cancel();
134
+ },
135
+ });
136
+
137
+ const handle = showClioOverlayFrame(tui, focus, {
138
+ anchor: "center",
139
+ width: confirmOverlayWidth(options.columns),
140
+ title: LIBRARY_INSTALL_CONFIRM_TITLE,
141
+ footerHint: buildResponsiveHint([{ key: "Enter", verb: "install" }], { key: "Esc", verb: "cancel" }),
142
+ });
143
+
144
+ return {
145
+ ...handle,
146
+ hide(): void {
147
+ cancel();
148
+ handle.hide();
149
+ },
150
+ };
151
+ }
@@ -32,9 +32,34 @@ export interface ListOverlayItem {
32
32
  detail?: (width: number) => string[];
33
33
  }
34
34
 
35
+ /**
36
+ * One tab of a tabbed list overlay.
37
+ *
38
+ * A tab owns its own row set and rebuilds it on demand, so switching tabs
39
+ * re-reads whatever the rows describe rather than serving a snapshot taken when
40
+ * the overlay opened. The counts drawn in the tab bar come from the same call,
41
+ * which is why an install that lands on one tab corrects every tab's count.
42
+ */
43
+ export interface ListOverlayTab {
44
+ id: string;
45
+ label: string;
46
+ items: () => ReadonlyArray<ListOverlayItem>;
47
+ }
48
+
35
49
  export interface ListOverlayOptions {
36
50
  title: string;
37
51
  items: ReadonlyArray<ListOverlayItem>;
52
+ /**
53
+ * Tabs, drawn as a bar above the filter row and switched with ←/→, which is
54
+ * the key vocabulary the Settings Center already uses to move between
55
+ * sections. Omitted leaves the overlay untabbed and every tab code path
56
+ * inert.
57
+ */
58
+ tabs?: ReadonlyArray<ListOverlayTab>;
59
+ /** Tab the overlay opens on; defaults to the first. */
60
+ activeTabId?: string;
61
+ /** Notified after ←/→ changes the active tab. */
62
+ onTabChange?: (tabId: string) => void;
38
63
  /** Enables the type-to-filter input row. */
39
64
  filterable?: boolean;
40
65
  /** Pre-applied filter text (e.g. /skill <query> in milestone 04). */
@@ -91,6 +116,10 @@ export class ListOverlayView implements Component {
91
116
  private renderMemo: { key: string; lines: string[] } | null = null;
92
117
  /** Bumped on every keystroke routed into the filter input; part of the memo key. */
93
118
  private inputEpoch = 0;
119
+ /** Active tab id, or the empty string on an untabbed overlay. */
120
+ private activeTabId = "";
121
+ /** Row count per tab id, refreshed whenever the tab set is rebuilt. */
122
+ private tabCounts = new Map<string, number>();
94
123
 
95
124
  constructor(
96
125
  private readonly options: ListOverlayOptions,
@@ -104,6 +133,89 @@ export class ListOverlayView implements Component {
104
133
  if (options.initialFilter) {
105
134
  this.input.setValue(options.initialFilter);
106
135
  }
136
+ const tabs = options.tabs;
137
+ if (tabs && tabs.length > 0) {
138
+ const requested = tabs.find((tab) => tab.id === options.activeTabId);
139
+ this.activeTabId = (requested ?? tabs[0])?.id ?? "";
140
+ this.refreshTabs();
141
+ }
142
+ }
143
+
144
+ /** The tab the overlay is on, or undefined when it is untabbed. */
145
+ activeTab(): ListOverlayTab | undefined {
146
+ return this.options.tabs?.find((tab) => tab.id === this.activeTabId);
147
+ }
148
+
149
+ /**
150
+ * Rebuild every tab's rows, adopt the active tab's, and record each count.
151
+ *
152
+ * Every tab is rebuilt rather than only the active one because the tab bar
153
+ * states each tab's count, and an install performed on one tab can satisfy a
154
+ * requirement another tab reports. A stale count there is the same lie a
155
+ * stale row is.
156
+ */
157
+ refreshTabs(): void {
158
+ const tabs = this.options.tabs;
159
+ if (!tabs || tabs.length === 0) return;
160
+ const counts = new Map<string, number>();
161
+ let activeItems: ReadonlyArray<ListOverlayItem> = [];
162
+ for (const tab of tabs) {
163
+ const rows = tab.items();
164
+ counts.set(tab.id, rows.length);
165
+ if (tab.id === this.activeTabId) activeItems = rows;
166
+ }
167
+ this.tabCounts = counts;
168
+ this.setItems(activeItems);
169
+ }
170
+
171
+ /** Switch tabs and rebuild. A tab id this overlay does not carry is ignored. */
172
+ setActiveTab(tabId: string): void {
173
+ const tabs = this.options.tabs;
174
+ if (!tabs?.some((tab) => tab.id === tabId)) return;
175
+ this.activeTabId = tabId;
176
+ this.refreshTabs();
177
+ // After the rows, not before: setItems re-anchors the cursor on the id it
178
+ // was on, and on a tab switch that id belongs to a list the operator has
179
+ // left. A new tab starts at its first row.
180
+ this.selectedIndex = 0;
181
+ this.listScrollOffset = 0;
182
+ this.detailScrollOffset = 0;
183
+ this.renderMemo = null;
184
+ }
185
+
186
+ /**
187
+ * The frame title. A tabbed overlay names the tab it is on, so the border
188
+ * and the rows below it describe the same thing.
189
+ */
190
+ title(): string {
191
+ const tab = this.activeTab();
192
+ return tab === undefined ? this.options.title : `${this.options.title} · ${tab.label}`;
193
+ }
194
+
195
+ private stepTab(delta: number): boolean {
196
+ const tabs = this.options.tabs;
197
+ if (!tabs || tabs.length === 0) return false;
198
+ const current = tabs.findIndex((tab) => tab.id === this.activeTabId);
199
+ const next = tabs[((((current === -1 ? 0 : current) + delta) % tabs.length) + tabs.length) % tabs.length];
200
+ if (!next || next.id === this.activeTabId) return false;
201
+ this.setActiveTab(next.id);
202
+ this.options.onTabChange?.(next.id);
203
+ this.onChange();
204
+ return true;
205
+ }
206
+
207
+ /** The tab bar: every tab with its count, the active one in the accent token. */
208
+ private renderTabBar(width: number): string[] {
209
+ const tabs = this.options.tabs;
210
+ if (!tabs || tabs.length === 0) return [];
211
+ const theme = clioTheme();
212
+ const parts = tabs.map((tab) => {
213
+ const text = `${tab.label} ${this.tabCounts.get(tab.id) ?? 0}`;
214
+ return tab.id === this.activeTabId
215
+ ? theme.style("accent", `${GLYPH.cursor} ${text}`, { bold: true })
216
+ : theme.fg("dim", ` ${text}`);
217
+ });
218
+ return [this.padLine(parts.join(theme.fg("frame", " │ ")), width)];
107
219
  }
108
220
 
109
221
  /**
@@ -170,12 +282,27 @@ export class ListOverlayView implements Component {
170
282
  this.options.onClose();
171
283
  }
172
284
 
285
+ /**
286
+ * The tab entry a tabbed footer carries. The verb names the active tab and
287
+ * how many rows it holds, so the count a reader sees is the count of the tab
288
+ * they are on rather than of the overlay as a whole.
289
+ */
290
+ private tabHintEntry(): HintEntry | null {
291
+ const tab = this.activeTab();
292
+ if (tab === undefined) return null;
293
+ const count = this.tabCounts.get(tab.id) ?? this.items.length;
294
+ return { key: "←→", verb: `tab · ${count} ${tab.label.toLowerCase()}`, short: "tab", critical: true };
295
+ }
296
+
173
297
  getHint(): string {
298
+ const tabEntry = this.tabHintEntry();
174
299
  // A list with no rows has no row to select, invoke, or act on. Offering
175
300
  // those keys anyway is the same lie the empty state exists to stop telling.
176
- if (this.items.length === 0) return buildHint([], "close");
301
+ // A tab key is the exception: on an empty tab it is the way to a full one.
302
+ if (this.items.length === 0) return buildHint(tabEntry ? [tabEntry] : [], "close");
177
303
 
178
304
  const hintEntries: HintEntry[] = [];
305
+ if (tabEntry) hintEntries.push(tabEntry);
179
306
  hintEntries.push({ key: "↑↓", verb: "select" });
180
307
  if (this.options.filterable) {
181
308
  hintEntries.push(FILTER_HINT);
@@ -396,11 +523,14 @@ export class ListOverlayView implements Component {
396
523
  this.detailScrollOffset,
397
524
  this.inputEpoch,
398
525
  this.itemsEpoch,
526
+ this.activeTabId,
399
527
  ].join("|");
400
528
  if (this.renderMemo?.key === memoKey) return this.renderMemo.lines;
401
529
 
402
530
  const lines: string[] = [];
403
531
 
532
+ lines.push(...this.renderTabBar(width));
533
+
404
534
  if (this.options.filterable) {
405
535
  this.input.focused = this.isFilterFocused;
406
536
  const inputLines = this.input.render(width);
@@ -442,6 +572,20 @@ export class ListOverlayView implements Component {
442
572
  }
443
573
 
444
574
  handleInput(data: string): void {
575
+ // ←/→ switch tabs from either pane, ahead of the filter input, which is
576
+ // the arrangement the Settings Center already uses to move between
577
+ // sections. On an untabbed overlay both keys fall through untouched.
578
+ if (this.options.tabs && this.options.tabs.length > 0) {
579
+ if (matchesKey(data, "left")) {
580
+ this.stepTab(-1);
581
+ return;
582
+ }
583
+ if (matchesKey(data, "right")) {
584
+ this.stepTab(1);
585
+ return;
586
+ }
587
+ }
588
+
445
589
  // PgDn / Ctrl+D and PgUp / Ctrl+U scroll the detail pane, but only
446
590
  // while one is visible; otherwise the keys fall through untouched.
447
591
  if (this.detailPaneVisible()) {
@@ -587,6 +731,15 @@ export interface ListOverlayHandle extends OverlayHandle {
587
731
  * the frame from before its own change until some other key moves the memo key.
588
732
  */
589
733
  setItems(items: ReadonlyArray<ListOverlayItem>): void;
734
+ /**
735
+ * Rebuild every tab's rows in place, keeping the active tab and the
736
+ * operator's place in it. Inert on an untabbed overlay.
737
+ */
738
+ refreshTabs(): void;
739
+ /** Switch tabs programmatically. An id this overlay does not carry is ignored. */
740
+ setActiveTab(tabId: string): void;
741
+ /** The active tab's id, or the empty string on an untabbed overlay. */
742
+ activeTabId(): string;
590
743
  }
591
744
 
592
745
  export function openListOverlay(tui: TUI, options: ListOverlayOptions): ListOverlayHandle {
@@ -594,7 +747,9 @@ export function openListOverlay(tui: TUI, options: ListOverlayOptions): ListOver
594
747
  const handle = showClioOverlayFrame(tui, view, {
595
748
  anchor: "center",
596
749
  width: 100,
597
- title: options.title,
750
+ // A function, not the string: on a tabbed overlay the title names the tab
751
+ // and must be re-read after every switch.
752
+ title: () => view.title(),
598
753
  footerHint: () => view.getHint(),
599
754
  });
600
755
  return Object.assign(handle, {
@@ -602,5 +757,16 @@ export function openListOverlay(tui: TUI, options: ListOverlayOptions): ListOver
602
757
  view.setItems(items);
603
758
  tui.requestRender();
604
759
  },
760
+ refreshTabs(): void {
761
+ view.refreshTabs();
762
+ tui.requestRender();
763
+ },
764
+ setActiveTab(tabId: string): void {
765
+ view.setActiveTab(tabId);
766
+ tui.requestRender();
767
+ },
768
+ activeTabId(): string {
769
+ return view.activeTab()?.id ?? "";
770
+ },
605
771
  });
606
772
  }