keating 0.3.6

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 (249) hide show
  1. package/README.md +334 -0
  2. package/SYSTEM.md +33 -0
  3. package/bin/keating.js +31 -0
  4. package/dist/src/cli/main.js +357 -0
  5. package/dist/src/cli/setup.js +197 -0
  6. package/dist/src/cli/web.js +84 -0
  7. package/dist/src/core/animation.js +304 -0
  8. package/dist/src/core/ax-optimizer.js +81 -0
  9. package/dist/src/core/ax-prompt-learner.js +59 -0
  10. package/dist/src/core/ax-trial.js +181 -0
  11. package/dist/src/core/benchmark.js +253 -0
  12. package/dist/src/core/commands.js +57 -0
  13. package/dist/src/core/config.js +120 -0
  14. package/dist/src/core/engagement.js +235 -0
  15. package/dist/src/core/env.js +9 -0
  16. package/dist/src/core/evolution.js +242 -0
  17. package/dist/src/core/flashcards.js +133 -0
  18. package/dist/src/core/learner-state.js +108 -0
  19. package/dist/src/core/lesson-plan.js +155 -0
  20. package/dist/src/core/map-elites.js +228 -0
  21. package/dist/src/core/map.js +89 -0
  22. package/dist/src/core/mastery.js +207 -0
  23. package/dist/src/core/paths.js +100 -0
  24. package/dist/src/core/pi-agent.js +82 -0
  25. package/dist/src/core/policy.js +79 -0
  26. package/dist/src/core/project.js +337 -0
  27. package/dist/src/core/projects.js +281 -0
  28. package/dist/src/core/prompt-evolution.js +344 -0
  29. package/dist/src/core/quiz.js +194 -0
  30. package/dist/src/core/random.js +19 -0
  31. package/dist/src/core/self-improve.js +425 -0
  32. package/dist/src/core/speech.js +54 -0
  33. package/dist/src/core/terminal.js +117 -0
  34. package/dist/src/core/theme.js +101 -0
  35. package/dist/src/core/topics.js +620 -0
  36. package/dist/src/core/types.js +1 -0
  37. package/dist/src/core/util.js +30 -0
  38. package/dist/src/core/verification.js +162 -0
  39. package/dist/src/pi/hyperteacher-extension.js +573 -0
  40. package/dist/src/runtime/pi.js +343 -0
  41. package/package.json +78 -0
  42. package/pi/prompts/bridge.md +14 -0
  43. package/pi/prompts/diagnose.md +15 -0
  44. package/pi/prompts/improve.md +39 -0
  45. package/pi/prompts/learn.md +21 -0
  46. package/pi/prompts/quiz.md +14 -0
  47. package/pi/skills/adaptive-teaching/SKILL.md +33 -0
  48. package/scripts/install/install.sh +308 -0
  49. package/web/dist/.well-known/llms.txt +44 -0
  50. package/web/dist/apple-touch-icon.svg +10 -0
  51. package/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  52. package/web/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  53. package/web/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  54. package/web/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  55. package/web/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  56. package/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  57. package/web/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  58. package/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  59. package/web/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  60. package/web/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  61. package/web/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  62. package/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  63. package/web/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  64. package/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  65. package/web/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  66. package/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  67. package/web/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  68. package/web/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  69. package/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  70. package/web/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  71. package/web/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  72. package/web/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  73. package/web/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  74. package/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  75. package/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  76. package/web/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  77. package/web/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  78. package/web/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  79. package/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  80. package/web/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  81. package/web/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  82. package/web/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  83. package/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  84. package/web/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  85. package/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  86. package/web/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  87. package/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  88. package/web/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  89. package/web/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  90. package/web/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  91. package/web/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  92. package/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  93. package/web/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  94. package/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  95. package/web/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  96. package/web/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  97. package/web/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  98. package/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  99. package/web/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  100. package/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  101. package/web/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  102. package/web/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  103. package/web/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  104. package/web/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  105. package/web/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  106. package/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  107. package/web/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  108. package/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  109. package/web/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  110. package/web/dist/assets/_baseFor-B_cjfMB6.js +1 -0
  111. package/web/dist/assets/anthropic-BT6Vfzb1.js +36 -0
  112. package/web/dist/assets/arc-x2nTilpc.js +1 -0
  113. package/web/dist/assets/architecture-YZFGNWBL-B1hlUWjX.js +1 -0
  114. package/web/dist/assets/architectureDiagram-Q4EWVU46-CMApWFyw.js +36 -0
  115. package/web/dist/assets/array-B9UHiPd-.js +1 -0
  116. package/web/dist/assets/azure-openai-responses-CommX3YJ.js +1 -0
  117. package/web/dist/assets/blockDiagram-DXYQGD6D-DOQbsNRY.js +132 -0
  118. package/web/dist/assets/c4Diagram-AHTNJAMY-VFfRZWWA.js +10 -0
  119. package/web/dist/assets/channel-KY2Tg8Ba.js +1 -0
  120. package/web/dist/assets/chunk-2KRD3SAO-B-AqvS0u.js +1 -0
  121. package/web/dist/assets/chunk-336JU56O-DlYgPyl6.js +2 -0
  122. package/web/dist/assets/chunk-426QAEUC-CsVoBkfR.js +1 -0
  123. package/web/dist/assets/chunk-4BX2VUAB-0Z13aFAn.js +1 -0
  124. package/web/dist/assets/chunk-4TB4RGXK-DqC0Zwm7.js +206 -0
  125. package/web/dist/assets/chunk-55IACEB6-CWE_u-IY.js +1 -0
  126. package/web/dist/assets/chunk-5FUZZQ4R-CApli0xX.js +62 -0
  127. package/web/dist/assets/chunk-5PVQY5BW-Cbzhfhln.js +2 -0
  128. package/web/dist/assets/chunk-67CJDMHE-Cx7uJS4d.js +1 -0
  129. package/web/dist/assets/chunk-7N4EOEYR-CYPNsFus.js +1 -0
  130. package/web/dist/assets/chunk-AA7GKIK3-rU0uhR_u.js +1 -0
  131. package/web/dist/assets/chunk-BSJP7CBP-5VmcfR4-.js +1 -0
  132. package/web/dist/assets/chunk-Bj-mKKzh.js +1 -0
  133. package/web/dist/assets/chunk-CIAEETIT-CHJ-L8H1.js +1 -0
  134. package/web/dist/assets/chunk-EDXVE4YY-DZHAJjMI.js +1 -0
  135. package/web/dist/assets/chunk-ENJZ2VHE-DbUDFa7w.js +10 -0
  136. package/web/dist/assets/chunk-FMBD7UC4-BsYE5e_h.js +15 -0
  137. package/web/dist/assets/chunk-FOC6F5B3-Cm6aoTv7.js +1 -0
  138. package/web/dist/assets/chunk-ICPOFSXX-C5eNZ4L6.js +123 -0
  139. package/web/dist/assets/chunk-K5T4RW27-R7dAJ4rq.js +94 -0
  140. package/web/dist/assets/chunk-KGLVRYIC-MO99YZXL.js +1 -0
  141. package/web/dist/assets/chunk-LIHQZDEY-DUJ656sT.js +1 -0
  142. package/web/dist/assets/chunk-ORNJ4GCN-DXuuEC1n.js +1 -0
  143. package/web/dist/assets/chunk-OYMX7WX6-pJlEprWq.js +231 -0
  144. package/web/dist/assets/chunk-QZHKN3VN-_pQxbbiW.js +1 -0
  145. package/web/dist/assets/chunk-U2HBQHQK-Mh_l9PLe.js +70 -0
  146. package/web/dist/assets/chunk-X2U36JSP-BOeiJW0w.js +1 -0
  147. package/web/dist/assets/chunk-XPW4576I-fQ9SDvr_.js +32 -0
  148. package/web/dist/assets/chunk-YZCP3GAM-eboO4P5S.js +1 -0
  149. package/web/dist/assets/chunk-ZZ45TVLE-Cky0eqlr.js +1 -0
  150. package/web/dist/assets/classDiagram-6PBFFD2Q-DEPsZSU3.js +1 -0
  151. package/web/dist/assets/classDiagram-v2-HSJHXN6E-DhmIOEpX.js +1 -0
  152. package/web/dist/assets/clone-DeTzYqo8.js +1 -0
  153. package/web/dist/assets/cose-bilkent-S5V4N54A-N4zWUJ7C.js +1 -0
  154. package/web/dist/assets/cytoscape.esm-BBMd0vGm.js +321 -0
  155. package/web/dist/assets/dagre-IpK1aoMm.js +1 -0
  156. package/web/dist/assets/dagre-KV5264BT-DCytJuju.js +4 -0
  157. package/web/dist/assets/defaultLocale-5eAKkKJC.js +1 -0
  158. package/web/dist/assets/diagram-5BDNPKRD-Cv4miBae.js +10 -0
  159. package/web/dist/assets/diagram-G4DWMVQ6-CtICKUFi.js +24 -0
  160. package/web/dist/assets/diagram-MMDJMWI5-Cn7aGorh.js +43 -0
  161. package/web/dist/assets/diagram-TYMM5635-CCUWDPsC.js +24 -0
  162. package/web/dist/assets/dist-Dm98VvTW.js +1 -0
  163. package/web/dist/assets/env-api-keys-BNlMKqxw.js +1 -0
  164. package/web/dist/assets/erDiagram-SMLLAGMA-uT88sBlT.js +85 -0
  165. package/web/dist/assets/event-stream-D33K9rpL.js +1 -0
  166. package/web/dist/assets/flatten-C-u5nd5-.js +1 -0
  167. package/web/dist/assets/flowDiagram-DWJPFMVM-Bl3O7S1m.js +162 -0
  168. package/web/dist/assets/ganttDiagram-T4ZO3ILL-B1FhwV45.js +292 -0
  169. package/web/dist/assets/gitGraph-7Q5UKJZL-Bc_7vzer.js +1 -0
  170. package/web/dist/assets/gitGraphDiagram-UUTBAWPF-DfW6svMS.js +106 -0
  171. package/web/dist/assets/github-copilot-headers-L39QqneT.js +1 -0
  172. package/web/dist/assets/google-BdYNeCP_.js +1 -0
  173. package/web/dist/assets/google-gemini-cli-DpxAL3K4.js +2 -0
  174. package/web/dist/assets/google-shared-DyQdgtsI.js +2 -0
  175. package/web/dist/assets/google-vertex-CKRybaXj.js +1 -0
  176. package/web/dist/assets/graphlib-CMTVFyOZ.js +1 -0
  177. package/web/dist/assets/hash-kZ2KD_no.js +1 -0
  178. package/web/dist/assets/index-Bdb7P7gx.css +2 -0
  179. package/web/dist/assets/index-DNxepp8B.js +2891 -0
  180. package/web/dist/assets/info-OMHHGYJF-BGcxeaZt.js +1 -0
  181. package/web/dist/assets/infoDiagram-42DDH7IO-BbES7X_c.js +2 -0
  182. package/web/dist/assets/init-DlZdxViB.js +1 -0
  183. package/web/dist/assets/isEmpty-DssUW35f.js +1 -0
  184. package/web/dist/assets/ishikawaDiagram-UXIWVN3A-DxQ28rho.js +70 -0
  185. package/web/dist/assets/journeyDiagram-VCZTEJTY-D0X8qQ0P.js +139 -0
  186. package/web/dist/assets/json-parse-C6tSeIxX.js +2 -0
  187. package/web/dist/assets/kanban-definition-6JOO6SKY-DWYfSlpl.js +89 -0
  188. package/web/dist/assets/katex-CyM-5LlM.js +265 -0
  189. package/web/dist/assets/line-CuHce5JG.js +1 -0
  190. package/web/dist/assets/linear-Ca0Vkwuj.js +1 -0
  191. package/web/dist/assets/mermaid-parser.core-Cy4iY_Dy.js +4 -0
  192. package/web/dist/assets/mermaid.core-6PGkQdYc.js +11 -0
  193. package/web/dist/assets/mindmap-definition-QFDTVHPH-BBnKdtQh.js +96 -0
  194. package/web/dist/assets/mistral-BWaUMIgd.js +7 -0
  195. package/web/dist/assets/openai-D4NSaQIs.js +16 -0
  196. package/web/dist/assets/openai-codex-responses-CHBgKhmb.js +7 -0
  197. package/web/dist/assets/openai-completions-kcXmmaHI.js +5 -0
  198. package/web/dist/assets/openai-responses-Cqq3H3p3.js +1 -0
  199. package/web/dist/assets/openai-responses-shared-CTNuo9ci.js +10 -0
  200. package/web/dist/assets/ordinal-_K3x1fkz.js +1 -0
  201. package/web/dist/assets/ort-wasm-simd-threaded.jsep-B0T3yYHD.wasm +0 -0
  202. package/web/dist/assets/packet-4T2RLAQJ-D35ZLSBH.js +1 -0
  203. package/web/dist/assets/path-6uRLdFF7.js +1 -0
  204. package/web/dist/assets/pdf.worker.min-Cpi8b8z3.mjs +28 -0
  205. package/web/dist/assets/pie-ZZUOXDRM-DRoETpJX.js +1 -0
  206. package/web/dist/assets/pieDiagram-DEJITSTG-DfMjfTQz.js +30 -0
  207. package/web/dist/assets/preload-helper-DSXbuxSR.js +1 -0
  208. package/web/dist/assets/quadrantDiagram-34T5L4WZ-DfBSEept.js +7 -0
  209. package/web/dist/assets/radar-PYXPWWZC-DLKxRJ0V.js +1 -0
  210. package/web/dist/assets/reduce-836A2NiQ.js +1 -0
  211. package/web/dist/assets/requirementDiagram-MS252O5E-BPkxJQkz.js +84 -0
  212. package/web/dist/assets/rough.esm-Djo4Abte.js +1 -0
  213. package/web/dist/assets/sankeyDiagram-XADWPNL6-He3x9tNT.js +10 -0
  214. package/web/dist/assets/sequenceDiagram-FGHM5R23-DfCDpvrT.js +157 -0
  215. package/web/dist/assets/src-DdOdIreR.js +1 -0
  216. package/web/dist/assets/stateDiagram-FHFEXIEX-fuww6347.js +1 -0
  217. package/web/dist/assets/stateDiagram-v2-QKLJ7IA2-U6voafO3.js +1 -0
  218. package/web/dist/assets/timeline-definition-GMOUNBTQ-BWunHgBC.js +120 -0
  219. package/web/dist/assets/transform-messages-CqKEdRVp.js +1 -0
  220. package/web/dist/assets/transformers.web-DKUtmSAi.js +2818 -0
  221. package/web/dist/assets/treeView-SZITEDCU-BCx0xSAm.js +1 -0
  222. package/web/dist/assets/treemap-W4RFUUIX-2CvghWJK.js +1 -0
  223. package/web/dist/assets/vennDiagram-DHZGUBPP-CBXRutSP.js +34 -0
  224. package/web/dist/assets/wardley-RL74JXVD-BkPL_mhd.js +1 -0
  225. package/web/dist/assets/wardleyDiagram-NUSXRM2D-DTcVscPH.js +20 -0
  226. package/web/dist/assets/web-CMKYLKbT.js +10 -0
  227. package/web/dist/assets/xychartDiagram-5P7HB3ND-CZLgX9Fe.js +7 -0
  228. package/web/dist/favicon.svg +10 -0
  229. package/web/dist/index.html +104 -0
  230. package/web/dist/keating-metaharness.pdf +10557 -3
  231. package/web/dist/llms.txt +44 -0
  232. package/web/dist/logo.png +0 -0
  233. package/web/dist/manifest.webmanifest +1 -0
  234. package/web/dist/og-image.png +0 -0
  235. package/web/dist/pwa-192x192.svg +10 -0
  236. package/web/dist/pwa-512x512.svg +10 -0
  237. package/web/dist/registerSW.js +1 -0
  238. package/web/dist/robots.txt +8 -0
  239. package/web/dist/sitemap.xml +39 -0
  240. package/web/dist/sw.js +1 -0
  241. package/web/dist/tapes/doctor.mp4 +0 -0
  242. package/web/dist/tapes/feedback-flow.mp4 +0 -0
  243. package/web/dist/tapes/improve-flow.mp4 +0 -0
  244. package/web/dist/tapes/intro.mp4 +0 -0
  245. package/web/dist/tapes/learning-flow.mp4 +0 -0
  246. package/web/dist/tapes/session-flow.mp4 +0 -0
  247. package/web/dist/tapes/teacher-flow.mp4 +0 -0
  248. package/web/dist/tapes/tests.mp4 +0 -0
  249. package/web/dist/workbox-66610c77.js +1 -0
@@ -0,0 +1,357 @@
1
+ import { access } from "node:fs/promises";
2
+ import { relative } from "node:path";
3
+ import { join } from "node:path";
4
+ import { spawnSync } from "node:child_process";
5
+ import { DEFAULT_KEATING_CONFIG, configPath, loadKeatingConfig, writeKeatingConfig } from "../core/config.js";
6
+ import { learnerStatePath } from "../core/paths.js";
7
+ import { loadLearnerState, recordFeedback, saveLearnerState } from "../core/learner-state.js";
8
+ import { animateTopicArtifact, autoImproveArtifact, benchPolicyArtifact, promptEvalArtifact, currentPolicySummary, ensureProjectScaffold, evolvePolicyArtifact, evolvePromptArtifact, improveArtifact, improveHistory, listArtifacts, mapTopicArtifact, planTopicArtifact, quizTopicArtifact, verifyTopicArtifact, timelineArtifact, dueTopicsArtifact } from "../core/project.js";
9
+ import { detectAiRuntime, launchShell } from "../runtime/pi.js";
10
+ import { serveWeb } from "./web.js";
11
+ import { color, bold, cliCommands } from "../core/theme.js";
12
+ import { printAsciiHeader } from "../core/terminal.js";
13
+ function printUsage() {
14
+ printAsciiHeader();
15
+ console.log(bold("primary", "Keating CLI") + " " + color.dim + color.sepia + "v0.3.6" + color.reset);
16
+ console.log(color.parchment + "The Hyperteacher — cognitive empowerment through Socratic AI." + color.reset);
17
+ console.log("");
18
+ console.log(cliCommands());
19
+ console.log("");
20
+ console.log(bold("primary", "General Commands"));
21
+ console.log(` ${color.primary}shell${color.reset} [initial prompt...] Launch the AI-powered hyperteacher shell`);
22
+ console.log(` ${color.primary}setup${color.reset} [--yes] Configure Keating for this project`);
23
+ console.log(` ${color.primary}doctor${color.reset} Inspect AI runtime and oxdraw availability`);
24
+ console.log(` ${color.primary}web${color.reset} [port] Start a local server for the browser UI`);
25
+ console.log(` ${color.primary}policy${color.reset} Print the active teaching policy`);
26
+ console.log(` ${color.primary}trace${color.reset} [substring] Browse debug traces and artifacts`);
27
+ console.log("");
28
+ }
29
+ function normalizeTopLevelShellArgs(args) {
30
+ if (args.length === 0 || !args[0]?.startsWith("-") || args[0] === "--help" || args[0] === "-h")
31
+ return null;
32
+ return args.map((arg) => arg === "--list-model" ? "--list-models" : arg);
33
+ }
34
+ function commandUsage(command, example) {
35
+ return new Error([
36
+ `${command} needs more input.`,
37
+ "",
38
+ "Recover with:",
39
+ ` ${example}`,
40
+ " keating help"
41
+ ].join("\n"));
42
+ }
43
+ function unknownCommand(command) {
44
+ return new Error([
45
+ `Unknown command: ${command}`,
46
+ "",
47
+ "Recover with:",
48
+ " keating help",
49
+ " keating setup",
50
+ " keating shell",
51
+ "",
52
+ "For model discovery, use:",
53
+ " keating --list-models"
54
+ ].join("\n"));
55
+ }
56
+ async function setupProject(cwd, args) {
57
+ const yes = args.includes("--yes") || args.includes("-y");
58
+ const existing = await loadKeatingConfig(cwd);
59
+ let next = {
60
+ ...DEFAULT_KEATING_CONFIG,
61
+ ...existing,
62
+ pi: {
63
+ ...DEFAULT_KEATING_CONFIG.pi,
64
+ ...existing.pi
65
+ },
66
+ speech: {
67
+ ...DEFAULT_KEATING_CONFIG.speech,
68
+ ...existing.speech
69
+ },
70
+ debug: {
71
+ ...DEFAULT_KEATING_CONFIG.debug,
72
+ ...existing.debug
73
+ }
74
+ };
75
+ if (!yes && process.stdin.isTTY && process.stdout.isTTY) {
76
+ const { runInteractiveSetup } = await import("./setup.js");
77
+ const answers = await runInteractiveSetup({
78
+ runtimePreference: next.pi.runtimePreference,
79
+ defaultProvider: next.pi.defaultProvider ?? "google",
80
+ defaultModel: next.pi.defaultModel ?? "gemini-3.1-pro-preview",
81
+ defaultThinking: next.pi.defaultThinking ?? "medium"
82
+ });
83
+ if (!answers) {
84
+ console.log(`${color.sepia}Setup cancelled.${color.reset}`);
85
+ return;
86
+ }
87
+ next = {
88
+ ...next,
89
+ pi: {
90
+ ...next.pi,
91
+ ...answers
92
+ }
93
+ };
94
+ }
95
+ else {
96
+ next = {
97
+ ...next,
98
+ pi: {
99
+ ...next.pi,
100
+ runtimePreference: DEFAULT_KEATING_CONFIG.pi.runtimePreference,
101
+ defaultProvider: DEFAULT_KEATING_CONFIG.pi.defaultProvider,
102
+ defaultModel: DEFAULT_KEATING_CONFIG.pi.defaultModel,
103
+ defaultThinking: DEFAULT_KEATING_CONFIG.pi.defaultThinking
104
+ }
105
+ };
106
+ }
107
+ await writeKeatingConfig(cwd, next);
108
+ console.log(`${color.ok}Wrote ${relative(cwd, configPath(cwd))}${color.reset}`);
109
+ console.log(`Run ${color.primary}keating doctor${color.reset} to verify the runtime, then ${color.primary}keating shell${color.reset}.`);
110
+ }
111
+ async function run() {
112
+ const rawArgs = process.argv.slice(2);
113
+ const cwd = process.cwd();
114
+ const topLevelShellArgs = normalizeTopLevelShellArgs(rawArgs);
115
+ if (topLevelShellArgs) {
116
+ const exitCode = await launchShell(cwd, topLevelShellArgs);
117
+ process.exitCode = exitCode;
118
+ return;
119
+ }
120
+ const [command = "shell", ...args] = rawArgs;
121
+ switch (command) {
122
+ case "setup": {
123
+ await setupProject(cwd, args);
124
+ return;
125
+ }
126
+ case "web": {
127
+ const port = args[0] ? parseInt(args[0], 10) : 3000;
128
+ await serveWeb(port);
129
+ return;
130
+ }
131
+ case "shell": {
132
+ const exitCode = await launchShell(cwd, args);
133
+ process.exitCode = exitCode;
134
+ return;
135
+ }
136
+ case "plan": {
137
+ const topic = args.join(" ").trim();
138
+ if (!topic)
139
+ throw commandUsage("plan", "keating plan derivative");
140
+ const artifact = await planTopicArtifact(cwd, topic);
141
+ console.log(relative(cwd, artifact.planPath));
142
+ return;
143
+ }
144
+ case "map": {
145
+ const topic = args.join(" ").trim();
146
+ if (!topic)
147
+ throw commandUsage("map", "keating map derivative");
148
+ const artifact = await mapTopicArtifact(cwd, topic);
149
+ console.log(relative(cwd, artifact.mmdPath));
150
+ if (artifact.svgPath)
151
+ console.log(relative(cwd, artifact.svgPath));
152
+ return;
153
+ }
154
+ case "animate": {
155
+ const topic = args.join(" ").trim();
156
+ if (!topic)
157
+ throw commandUsage("animate", "keating animate derivative");
158
+ const artifact = await animateTopicArtifact(cwd, topic);
159
+ console.log(relative(cwd, artifact.playerPath));
160
+ console.log(relative(cwd, artifact.scenePath));
161
+ console.log(relative(cwd, artifact.storyboardPath));
162
+ console.log(relative(cwd, artifact.manifestPath));
163
+ return;
164
+ }
165
+ case "verify": {
166
+ const topic = args.join(" ").trim();
167
+ if (!topic)
168
+ throw commandUsage("verify", "keating verify derivative");
169
+ const result = await verifyTopicArtifact(cwd, topic);
170
+ if (result.alreadyVerified) {
171
+ console.log(`Already verified: ${relative(cwd, result.checklistPath)}`);
172
+ }
173
+ else {
174
+ console.log(`Verification checklist: ${relative(cwd, result.checklistPath)}`);
175
+ }
176
+ return;
177
+ }
178
+ case "quiz": {
179
+ const topic = args.join(" ").trim();
180
+ if (!topic)
181
+ throw commandUsage("quiz", "keating quiz derivative");
182
+ const result = await quizTopicArtifact(cwd, topic);
183
+ console.log(relative(cwd, result.quizPath));
184
+ console.log(relative(cwd, result.answersPath));
185
+ return;
186
+ }
187
+ case "bench": {
188
+ const topic = args.join(" ").trim() || undefined;
189
+ const result = await benchPolicyArtifact(cwd, topic);
190
+ console.log(`${result.overallScore.toFixed(2)} ${relative(cwd, result.reportPath)}`);
191
+ if (result.tracePath)
192
+ console.log(relative(cwd, result.tracePath));
193
+ return;
194
+ }
195
+ case "evolve": {
196
+ const topic = args.join(" ").trim() || undefined;
197
+ const result = await evolvePolicyArtifact(cwd, topic);
198
+ console.log(`${result.bestScore.toFixed(2)} ${relative(cwd, result.reportPath)}`);
199
+ if (result.tracePath)
200
+ console.log(relative(cwd, result.tracePath));
201
+ return;
202
+ }
203
+ case "prompt-evolve": {
204
+ const promptName = args.join(" ").trim() || "learn";
205
+ const result = await evolvePromptArtifact(cwd, promptName);
206
+ console.log(`${result.bestScore.toFixed(2)} ${relative(cwd, result.reportPath)}`);
207
+ console.log(relative(cwd, result.evolvedPromptPath));
208
+ return;
209
+ }
210
+ case "prompt-eval": {
211
+ const promptContent = args.join(" ").trim();
212
+ if (!promptContent)
213
+ throw commandUsage("prompt-eval", "keating prompt-eval \"Start with a diagnostic question.\"");
214
+ const result = await promptEvalArtifact(cwd, promptContent);
215
+ console.log(`${result.score.toFixed(2)} ${relative(cwd, result.reportPath)}`);
216
+ return;
217
+ }
218
+ case "improve": {
219
+ if (args[0] === "history") {
220
+ const md = await improveHistory(cwd);
221
+ console.log(md);
222
+ return;
223
+ }
224
+ const artifact = await improveArtifact(cwd);
225
+ console.log(`Proposal: ${artifact.proposal.id}`);
226
+ console.log(`Targets: ${artifact.proposal.targets.map(t => t.file).join(", ")}`);
227
+ console.log(relative(cwd, artifact.proposalPath));
228
+ return;
229
+ }
230
+ case "auto-improve": {
231
+ const topic = args.join(" ").trim() || undefined;
232
+ const result = await autoImproveArtifact(cwd, topic);
233
+ const verdict = result.delta > 0
234
+ ? `${color.ok}IMPROVED by +${result.delta.toFixed(2)}${color.reset}`
235
+ : result.delta < -0.5
236
+ ? `${color.err}REGRESSED by ${result.delta.toFixed(2)}${color.reset}`
237
+ : `${color.sepia}NO SIGNIFICANT CHANGE (Δ${result.delta.toFixed(2)})${color.reset}`;
238
+ console.log(`Baseline: ${result.baselineScore.toFixed(2)} → After: ${result.afterScore.toFixed(2)} — ${verdict}`);
239
+ console.log(relative(cwd, result.reportPath));
240
+ return;
241
+ }
242
+ case "policy": {
243
+ await ensureProjectScaffold(cwd);
244
+ console.log(await currentPolicySummary(cwd));
245
+ return;
246
+ }
247
+ case "trace": {
248
+ await ensureProjectScaffold(cwd);
249
+ const query = args.join(" ").trim().toLowerCase();
250
+ const artifacts = await listArtifacts(cwd);
251
+ for (const artifact of artifacts) {
252
+ if (!query || artifact.path.toLowerCase().includes(query) || artifact.label.toLowerCase().includes(query)) {
253
+ console.log(artifact.path);
254
+ }
255
+ }
256
+ return;
257
+ }
258
+ case "learner-state": {
259
+ await ensureProjectScaffold(cwd);
260
+ const statePath = learnerStatePath(cwd);
261
+ const state = await loadLearnerState(statePath);
262
+ const upCount = state.feedback.filter((f) => f.signal === "thumbs-up").length;
263
+ const downCount = state.feedback.filter((f) => f.signal === "thumbs-down").length;
264
+ const confusedCount = state.feedback.filter((f) => f.signal === "confused").length;
265
+ console.log(`${bold("primary", "Learner Profile")}`);
266
+ console.log(` Sessions: ${state.sessions?.length ?? 0}`);
267
+ console.log(` Topics covered: ${state.coveredTopics.length}`);
268
+ for (const t of state.coveredTopics.slice(-10))
269
+ console.log(` - ${t.slug} (${t.domain})`);
270
+ console.log(` Feedback: 👍${upCount} 👎${downCount} 🤔${confusedCount}`);
271
+ console.log(` Misconceptions identified: ${state.identifiedMisconceptions.length}`);
272
+ return;
273
+ }
274
+ case "timeline": {
275
+ const { markdown } = await timelineArtifact(cwd);
276
+ console.log(markdown);
277
+ return;
278
+ }
279
+ case "due": {
280
+ const { markdown } = await dueTopicsArtifact(cwd);
281
+ console.log(markdown);
282
+ return;
283
+ }
284
+ case "feedback": {
285
+ const signalMap = {
286
+ up: "thumbs-up",
287
+ down: "thumbs-down",
288
+ confused: "confused"
289
+ };
290
+ const signal = signalMap[args[0]?.toLowerCase() ?? ""];
291
+ if (!signal) {
292
+ throw new Error([
293
+ "feedback needs a signal: up, down, or confused.",
294
+ "",
295
+ "Recover with:",
296
+ " keating feedback up derivative",
297
+ " keating feedback confused derivative --comment=\"lost at chain rule\""
298
+ ].join("\n"));
299
+ }
300
+ let comment;
301
+ const filtered = args.filter((arg) => {
302
+ if (arg.startsWith("--comment=")) {
303
+ comment = arg.slice("--comment=".length);
304
+ return false;
305
+ }
306
+ return true;
307
+ });
308
+ const topic = filtered.slice(1).join(" ") || "general";
309
+ const statePath = learnerStatePath(cwd);
310
+ const state = await loadLearnerState(statePath);
311
+ recordFeedback(state, topic, signal, comment);
312
+ await saveLearnerState(statePath, state);
313
+ const sigLabel = { "thumbs-up": "👍 up", "thumbs-down": "👎 down", "confused": "🤔 confused" }[signal];
314
+ const commentHint = comment ? ` (comment: "${comment}")` : "";
315
+ console.log(`${color.ok}Recorded ${sigLabel} feedback for "${topic}"${commentHint}.${color.reset}`);
316
+ return;
317
+ }
318
+ case "doctor": {
319
+ await ensureProjectScaffold(cwd);
320
+ const config = await loadKeatingConfig(cwd);
321
+ const runtime = await detectAiRuntime(cwd);
322
+ const oxdraw = spawnSync("which", ["oxdraw"], { encoding: "utf8" });
323
+ const manimWebPath = join(cwd, "node_modules", "manim-web", "dist", "index.js");
324
+ const manimWebInstalled = await access(manimWebPath).then(() => true, () => false);
325
+ console.log(`${bold("primary", "Keating Doctor")} ${color.sepia}diagnostic report${color.reset}\n`);
326
+ console.log(` ${color.cream}config_path${color.reset} ${color.sepia}${configPath(cwd)}${color.reset}`);
327
+ console.log(` ${color.cream}ai_runtime_preference${color.reset} ${color.primary}${config.pi.runtimePreference}${color.reset}`);
328
+ console.log(` ${color.cream}ai_default_provider${color.reset} ${config.pi.defaultProvider ?? color.sepia + "unset" + color.reset}`);
329
+ console.log(` ${color.cream}ai_default_model${color.reset} ${config.pi.defaultModel ?? color.sepia + "unset" + color.reset}`);
330
+ console.log(` ${color.cream}ai_default_thinking${color.reset} ${config.pi.defaultThinking ?? color.sepia + "unset" + color.reset}`);
331
+ console.log(` ${color.cream}debug_persist_traces${color.reset} ${color.primary}${String(config.debug.persistTraces)}${color.reset}`);
332
+ console.log(` ${color.cream}debug_trace_top_learners${color.reset} ${color.primary}${String(config.debug.traceTopLearners)}${color.reset}`);
333
+ console.log(` ${color.cream}debug_console_summary${color.reset} ${color.primary}${String(config.debug.consoleSummary)}${color.reset}`);
334
+ console.log(` ${color.cream}ai_runtime${color.reset} ${runtime.selected ? color.ok + runtime.selected.kind : color.err + "missing"}${color.reset}`);
335
+ console.log(` ${color.cream}ai_standalone${color.reset} ${runtime.standalone ? color.primary + runtime.standalone.command : color.err + "missing"}${color.reset}`);
336
+ console.log(` ${color.cream}ai_embedded${color.reset} ${runtime.embedded ? color.primary + (runtime.embedded.cliPath ?? runtime.embedded.command) : color.err + "missing"}${color.reset}`);
337
+ if (runtime.selected)
338
+ console.log(` ${color.cream}ai_command${color.reset} ${color.primary}${runtime.selected.command}${color.reset}`);
339
+ console.log(` ${color.cream}oxdraw${color.reset} ${oxdraw.status === 0 ? color.ok + oxdraw.stdout.trim() : color.err + "missing"}${color.reset}`);
340
+ console.log(` ${color.cream}manim_web${color.reset} ${manimWebInstalled ? color.ok + manimWebPath : color.err + "missing"}${color.reset}`);
341
+ return;
342
+ }
343
+ case "help":
344
+ case "--help":
345
+ case "-h": {
346
+ printUsage();
347
+ return;
348
+ }
349
+ default: {
350
+ throw unknownCommand(command);
351
+ }
352
+ }
353
+ }
354
+ run().catch((error) => {
355
+ console.error(`${color.err}${color.bold}Error:${color.reset} ${error instanceof Error ? error.message : String(error)}`);
356
+ process.exitCode = 1;
357
+ });
@@ -0,0 +1,197 @@
1
+ import React, { useState } from "react";
2
+ import { Box, Text, render, useApp, useInput } from "ink";
3
+ const PROVIDERS = [
4
+ { label: "Google", value: "google", hint: "Recommended" },
5
+ { label: "OpenAI", value: "openai" },
6
+ { label: "Anthropic", value: "anthropic" },
7
+ { label: "Custom", value: "custom", hint: "Type a provider name" }
8
+ ];
9
+ const MODELS_BY_PROVIDER = {
10
+ google: [
11
+ { label: "Gemini 3.1 Pro Preview", value: "gemini-3.1-pro-preview", hint: "Recommended" },
12
+ { label: "Gemini 3.1 Flash Live Preview", value: "gemini-3.1-flash-live-preview" },
13
+ { label: "Custom", value: "custom", hint: "Type a model name" }
14
+ ],
15
+ openai: [
16
+ { label: "GPT-5.2", value: "gpt-5.2" },
17
+ { label: "GPT-5.4", value: "gpt-5.4" },
18
+ { label: "Custom", value: "custom", hint: "Type a model name" }
19
+ ],
20
+ anthropic: [
21
+ { label: "Claude Sonnet 4.5", value: "claude-sonnet-4-5" },
22
+ { label: "Custom", value: "custom", hint: "Type a model name" }
23
+ ],
24
+ custom: [
25
+ { label: "Custom", value: "custom", hint: "Type a model name" }
26
+ ]
27
+ };
28
+ const THINKING = [
29
+ { label: "Medium", value: "medium", hint: "Recommended" },
30
+ { label: "Low", value: "low" },
31
+ { label: "High", value: "high" }
32
+ ];
33
+ const RUNTIMES = [
34
+ { label: "Prefer standalone", value: "prefer-standalone", hint: "Recommended" },
35
+ { label: "Embedded only", value: "embedded-only" },
36
+ { label: "Standalone only", value: "standalone-only" }
37
+ ];
38
+ function selectedIndex(choices, value) {
39
+ const index = choices.findIndex((choice) => choice.value === value);
40
+ return index >= 0 ? index : 0;
41
+ }
42
+ function Menu(props) {
43
+ useInput((input, key) => {
44
+ if (key.upArrow)
45
+ props.setSelected((props.selected - 1 + props.choices.length) % props.choices.length);
46
+ else if (key.downArrow)
47
+ props.setSelected((props.selected + 1) % props.choices.length);
48
+ else if (key.return || input === " ")
49
+ props.submit();
50
+ });
51
+ return React.createElement(Box, { flexDirection: "column" }, React.createElement(Text, { color: "green", bold: true }, props.title), ...props.choices.map((choice, index) => React.createElement(Text, { key: choice.value, color: index === props.selected ? "green" : undefined }, `${index === props.selected ? ">" : " "} ${choice.label}${choice.hint ? ` - ${choice.hint}` : ""}`)), React.createElement(Text, { dimColor: true }, "Use arrow keys and Enter."));
52
+ }
53
+ function TextPrompt(props) {
54
+ const [value, setValue] = useState(props.initialValue);
55
+ useInput((input, key) => {
56
+ if (key.return)
57
+ props.submit(value.trim() || props.initialValue);
58
+ else if (key.backspace || key.delete)
59
+ setValue((current) => current.slice(0, -1));
60
+ else if (!key.ctrl && input)
61
+ setValue((current) => current + input);
62
+ });
63
+ return React.createElement(Box, { flexDirection: "column" }, React.createElement(Text, { color: "green", bold: true }, props.title), React.createElement(Text, null, `> ${value}`), React.createElement(Text, { dimColor: true }, "Type a value and press Enter."));
64
+ }
65
+ function Summary(props) {
66
+ const choices = [
67
+ { label: "Write config", value: "yes" },
68
+ { label: "Go back", value: "back" }
69
+ ];
70
+ useInput((input, key) => {
71
+ if (key.upArrow || key.downArrow)
72
+ props.setSelected(props.selected === 0 ? 1 : 0);
73
+ else if (key.return || input === " ")
74
+ props.submit(choices[props.selected]?.value === "yes");
75
+ });
76
+ return React.createElement(Box, { flexDirection: "column" }, React.createElement(Text, { color: "green", bold: true }, "Confirm Keating setup"), React.createElement(Text, null, `Provider: ${props.answers.defaultProvider}`), React.createElement(Text, null, `Model: ${props.answers.defaultModel}`), React.createElement(Text, null, `Thinking: ${props.answers.defaultThinking}`), React.createElement(Text, null, `Runtime: ${props.answers.runtimePreference}`), React.createElement(Text, null, ""), ...choices.map((choice, index) => React.createElement(Text, { key: choice.value, color: index === props.selected ? "green" : undefined }, `${index === props.selected ? ">" : " "} ${choice.label}`)));
77
+ }
78
+ function SetupApp(props) {
79
+ const app = useApp();
80
+ const [step, setStep] = useState("provider");
81
+ const [answers, setAnswers] = useState(props.initial);
82
+ const [providerIndex, setProviderIndex] = useState(selectedIndex(PROVIDERS, props.initial.defaultProvider));
83
+ const [modelIndex, setModelIndex] = useState(0);
84
+ const [thinkingIndex, setThinkingIndex] = useState(selectedIndex(THINKING, props.initial.defaultThinking));
85
+ const [runtimeIndex, setRuntimeIndex] = useState(selectedIndex(RUNTIMES, props.initial.runtimePreference));
86
+ const [summaryIndex, setSummaryIndex] = useState(0);
87
+ useInput((input, key) => {
88
+ if (key.escape || (key.ctrl && input === "c"))
89
+ app.exit();
90
+ });
91
+ if (step === "provider") {
92
+ return React.createElement(Menu, {
93
+ title: "Choose default provider",
94
+ choices: PROVIDERS,
95
+ selected: providerIndex,
96
+ setSelected: setProviderIndex,
97
+ submit: () => {
98
+ const provider = PROVIDERS[providerIndex]?.value ?? "google";
99
+ if (provider === "custom")
100
+ setStep("customProvider");
101
+ else {
102
+ const models = MODELS_BY_PROVIDER[provider] ?? MODELS_BY_PROVIDER.custom;
103
+ setAnswers((current) => ({ ...current, defaultProvider: provider, defaultModel: models[0]?.value ?? current.defaultModel }));
104
+ setModelIndex(0);
105
+ setStep("model");
106
+ }
107
+ }
108
+ });
109
+ }
110
+ if (step === "customProvider") {
111
+ return React.createElement(TextPrompt, {
112
+ title: "Custom provider name",
113
+ initialValue: answers.defaultProvider === "custom" ? "google" : answers.defaultProvider,
114
+ submit: (value) => {
115
+ setAnswers((current) => ({ ...current, defaultProvider: value }));
116
+ setStep("customModel");
117
+ }
118
+ });
119
+ }
120
+ if (step === "model") {
121
+ const models = MODELS_BY_PROVIDER[answers.defaultProvider] ?? MODELS_BY_PROVIDER.custom;
122
+ return React.createElement(Menu, {
123
+ title: "Choose default model",
124
+ choices: models,
125
+ selected: modelIndex,
126
+ setSelected: setModelIndex,
127
+ submit: () => {
128
+ const model = models[modelIndex]?.value ?? "custom";
129
+ if (model === "custom")
130
+ setStep("customModel");
131
+ else {
132
+ setAnswers((current) => ({ ...current, defaultModel: model }));
133
+ setStep("thinking");
134
+ }
135
+ }
136
+ });
137
+ }
138
+ if (step === "customModel") {
139
+ return React.createElement(TextPrompt, {
140
+ title: "Custom model name",
141
+ initialValue: answers.defaultModel,
142
+ submit: (value) => {
143
+ setAnswers((current) => ({ ...current, defaultModel: value }));
144
+ setStep("thinking");
145
+ }
146
+ });
147
+ }
148
+ if (step === "thinking") {
149
+ return React.createElement(Menu, {
150
+ title: "Choose thinking effort",
151
+ choices: THINKING,
152
+ selected: thinkingIndex,
153
+ setSelected: setThinkingIndex,
154
+ submit: () => {
155
+ setAnswers((current) => ({ ...current, defaultThinking: THINKING[thinkingIndex]?.value ?? "medium" }));
156
+ setStep("runtime");
157
+ }
158
+ });
159
+ }
160
+ if (step === "runtime") {
161
+ return React.createElement(Menu, {
162
+ title: "Choose runtime preference",
163
+ choices: RUNTIMES,
164
+ selected: runtimeIndex,
165
+ setSelected: setRuntimeIndex,
166
+ submit: () => {
167
+ setAnswers((current) => ({ ...current, runtimePreference: RUNTIMES[runtimeIndex]?.value ?? "prefer-standalone" }));
168
+ setStep("summary");
169
+ }
170
+ });
171
+ }
172
+ return React.createElement(Summary, {
173
+ answers,
174
+ selected: summaryIndex,
175
+ setSelected: setSummaryIndex,
176
+ submit: (confirmed) => {
177
+ if (confirmed) {
178
+ props.onComplete(answers);
179
+ app.exit();
180
+ }
181
+ else {
182
+ setStep("provider");
183
+ }
184
+ }
185
+ });
186
+ }
187
+ export async function runInteractiveSetup(initial) {
188
+ let completed = null;
189
+ const instance = render(React.createElement(SetupApp, {
190
+ initial,
191
+ onComplete: (answers) => {
192
+ completed = answers;
193
+ }
194
+ }));
195
+ await instance.waitUntilExit();
196
+ return completed;
197
+ }
@@ -0,0 +1,84 @@
1
+ import { join, dirname } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { access, readdir, stat } from "node:fs/promises";
4
+ import { spawn } from "node:child_process";
5
+ import { color } from "../core/theme.js";
6
+ const WEB_SOURCE_PATHS = [
7
+ "src",
8
+ "public",
9
+ "index.html",
10
+ "package.json",
11
+ "vite.config.ts",
12
+ "nitro.config.ts",
13
+ ];
14
+ async function newestMtimeMs(path) {
15
+ try {
16
+ const entry = await stat(path);
17
+ if (!entry.isDirectory())
18
+ return entry.mtimeMs;
19
+ let newest = entry.mtimeMs;
20
+ const children = await readdir(path, { withFileTypes: true });
21
+ for (const child of children) {
22
+ newest = Math.max(newest, await newestMtimeMs(join(path, child.name)));
23
+ }
24
+ return newest;
25
+ }
26
+ catch {
27
+ return 0;
28
+ }
29
+ }
30
+ async function warnIfWebBuildIsStale(pkgRoot, nitroServerPath) {
31
+ const buildMtimeMs = (await stat(nitroServerPath)).mtimeMs;
32
+ const webRoot = join(pkgRoot, "web");
33
+ let newestSourceMtimeMs = 0;
34
+ for (const relativePath of WEB_SOURCE_PATHS) {
35
+ newestSourceMtimeMs = Math.max(newestSourceMtimeMs, await newestMtimeMs(join(webRoot, relativePath)));
36
+ }
37
+ if (newestSourceMtimeMs <= buildMtimeMs)
38
+ return;
39
+ console.warn(`${color.warn}Warning: web sources are newer than web/.output.${color.reset}`);
40
+ console.warn("`keating web` serves the last production build, not the live Vite source tree.");
41
+ console.warn("Use `mise run web` for hot reload, or rebuild with `bun run --cwd web build` before launching.");
42
+ }
43
+ /**
44
+ * Starts the Keating Web UI server using Nitro.
45
+ * Port-incrementing logic is now handled by Nitro's runtime
46
+ * or can be passed via the PORT environment variable.
47
+ */
48
+ export async function serveWeb(port = 3000) {
49
+ const currentDir = dirname(fileURLToPath(import.meta.url));
50
+ // Navigate from dist/src/cli/web.js to project root
51
+ const pkgRoot = join(currentDir, "..", "..", "..");
52
+ const nitroServerPath = join(pkgRoot, "web", ".output", "server", "index.mjs");
53
+ try {
54
+ await access(nitroServerPath);
55
+ }
56
+ catch (error) {
57
+ console.error(`${color.err}Error: Could not find Nitro server at ${nitroServerPath}${color.reset}`);
58
+ console.error("Please run the build command first:");
59
+ console.error(" bun run --cwd web build");
60
+ process.exit(1);
61
+ }
62
+ await warnIfWebBuildIsStale(pkgRoot, nitroServerPath);
63
+ process.stdout.write(`${color.ok}${color.bold} Keating Web Server ${color.reset} ${color.parchment}port ${port}${color.reset}\n`);
64
+ const env = {
65
+ ...process.env,
66
+ PORT: port.toString(),
67
+ NITRO_PORT: port.toString()
68
+ };
69
+ // Run the Nitro server in a child process
70
+ const child = spawn(process.execPath, [nitroServerPath], {
71
+ env,
72
+ stdio: "inherit",
73
+ });
74
+ child.on("error", (err) => {
75
+ console.error(`${color.err}Failed to start Nitro server:${color.reset}`, err);
76
+ });
77
+ // Handle termination signals
78
+ const cleanup = () => {
79
+ child.kill();
80
+ process.exit();
81
+ };
82
+ process.on("SIGINT", cleanup);
83
+ process.on("SIGTERM", cleanup);
84
+ }