@computesdk/loops 0.0.0-stage → 0.1.0

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 (380) hide show
  1. package/API.md +106 -0
  2. package/README.md +221 -2
  3. package/bin/loops.mjs +188 -0
  4. package/bin/pi-loops-ui.mjs +4 -0
  5. package/client.ts +208 -0
  6. package/dist/assets/abap-BdImnpbu.js +1 -0
  7. package/dist/assets/abnfDiagram-VCTEODGH-DPUvPEpH.js +1 -0
  8. package/dist/assets/actionscript-3-B3316cI-.js +1 -0
  9. package/dist/assets/ada-BIOnG6TC.js +1 -0
  10. package/dist/assets/ahk-BrbAagtO.js +1 -0
  11. package/dist/assets/ahk2-8Zs4aa1G.js +1 -0
  12. package/dist/assets/andromeeda-C4gqWexZ.js +1 -0
  13. package/dist/assets/angular-html-D_SYSyRt.js +1 -0
  14. package/dist/assets/angular-ts-DGq0tZX5.js +1 -0
  15. package/dist/assets/apache-Pmp26Uib.js +1 -0
  16. package/dist/assets/apex-DhZFqWV2.js +1 -0
  17. package/dist/assets/apl-ChzvTHjC.js +1 -0
  18. package/dist/assets/applescript-Co6uUVPk.js +1 -0
  19. package/dist/assets/ara-BRHolxvo.js +1 -0
  20. package/dist/assets/arc-C71M1We_.js +1 -0
  21. package/dist/assets/architectureDiagram-5GKGNRK7-C3PLmrM3.js +36 -0
  22. package/dist/assets/asciidoc-CSVQ5wI8.js +1 -0
  23. package/dist/assets/asm-D_Q5rh1f.js +1 -0
  24. package/dist/assets/astro-DTjqU0_L.js +1 -0
  25. package/dist/assets/aurora-x-D-2ljcwZ.js +1 -0
  26. package/dist/assets/awk-DMzUqQB5.js +1 -0
  27. package/dist/assets/ayu-dark-DYE7WIF3.js +1 -0
  28. package/dist/assets/ayu-light-BA47KaF1.js +1 -0
  29. package/dist/assets/ayu-mirage-32ctXXKs.js +1 -0
  30. package/dist/assets/ballerina-BFfxhgS-.js +1 -0
  31. package/dist/assets/bat-CickPsom.js +1 -0
  32. package/dist/assets/beancount-BgSHGh8x.js +1 -0
  33. package/dist/assets/berry-uYugtg8r.js +1 -0
  34. package/dist/assets/bibtex-CHM0blh-.js +1 -0
  35. package/dist/assets/bicep-Bmn6On1c.js +1 -0
  36. package/dist/assets/bird2-Bx8U0n9b.js +1 -0
  37. package/dist/assets/blade-k33SngOZ.js +1 -0
  38. package/dist/assets/blockDiagram-I7D4REHJ-DFshwmKE.js +129 -0
  39. package/dist/assets/bsl-DlhNcFeZ.js +1 -0
  40. package/dist/assets/c-BIGW1oBm.js +1 -0
  41. package/dist/assets/c3-Dp5svz6Z.js +1 -0
  42. package/dist/assets/c4Diagram-7LVT6UL2-Dh29QjGI.js +38 -0
  43. package/dist/assets/cadence-Bv_4Rxtq.js +1 -0
  44. package/dist/assets/cairo-KRGpt6FW.js +1 -0
  45. package/dist/assets/catppuccin-frappe-CZL1YF0i.js +1 -0
  46. package/dist/assets/catppuccin-latte-DH-8KZSZ.js +1 -0
  47. package/dist/assets/catppuccin-macchiato-B7yYVSCf.js +1 -0
  48. package/dist/assets/catppuccin-mocha-Ct7hS0mc.js +1 -0
  49. package/dist/assets/channel-6AEG2wiQ.js +1 -0
  50. package/dist/assets/chapel-DTp_pixX.js +1 -0
  51. package/dist/assets/chunk-2Q5K7J3B-qZ7USlsH.js +1 -0
  52. package/dist/assets/chunk-5VM5RSS4-YQpRymiv.js +15 -0
  53. package/dist/assets/chunk-F27PBJKO-D2O95m9O.js +1 -0
  54. package/dist/assets/chunk-IMKFNOWR-CftSjfdl.js +231 -0
  55. package/dist/assets/chunk-JWPE2WC7-_1wtK-EO.js +1 -0
  56. package/dist/assets/chunk-POPQ4Y6H-DJ2Xmp8X.js +1 -0
  57. package/dist/assets/chunk-SVP7TREG-CAtSHcje.js +88 -0
  58. package/dist/assets/chunk-TICWLB2K-1BASqzB5.js +206 -0
  59. package/dist/assets/chunk-XXDRQBXY-CWs6lP_i.js +1 -0
  60. package/dist/assets/clarity-Dn5IMItf.js +1 -0
  61. package/dist/assets/classDiagram-ZZMXUADV-WtwW3JUM.js +1 -0
  62. package/dist/assets/classDiagram-v2-VYDZK3BY-WtwW3JUM.js +1 -0
  63. package/dist/assets/clojure-P80f7IUj.js +1 -0
  64. package/dist/assets/cmake-D1j8_8rp.js +1 -0
  65. package/dist/assets/cobol-BBKr1ja6.js +1 -0
  66. package/dist/assets/codeowners-Bp6g37R7.js +1 -0
  67. package/dist/assets/codeql-DsOJ9woJ.js +1 -0
  68. package/dist/assets/coffee-Ch7k5sss.js +1 -0
  69. package/dist/assets/common-lisp-Cg-RD9OK.js +1 -0
  70. package/dist/assets/coq-BqXF5kFF.js +1 -0
  71. package/dist/assets/cose-bilkent-JH36ORCC-9dCpEIjz.js +1 -0
  72. package/dist/assets/cpp-BMRokrvK.js +1 -0
  73. package/dist/assets/crystal-LUwhHfY5.js +1 -0
  74. package/dist/assets/csharp-B_TYUkTI.js +1 -0
  75. package/dist/assets/css-Z72CF80r.js +1 -0
  76. package/dist/assets/csv-fuZLfV_i.js +1 -0
  77. package/dist/assets/cue-D82EKSYY.js +1 -0
  78. package/dist/assets/cynefin-OW5HDTMX-EFt_R8HZ.js +166 -0
  79. package/dist/assets/cynefinDiagram-5FMLGOSQ-BuManjb1.js +62 -0
  80. package/dist/assets/cypher-COkxafJQ.js +1 -0
  81. package/dist/assets/cytoscape.esm-BDkkwuE4.js +321 -0
  82. package/dist/assets/d-85-TOEBH.js +1 -0
  83. package/dist/assets/dagre-GXQ25YYZ-MovLAGVL.js +4 -0
  84. package/dist/assets/dark-plus-C3mMm8J8.js +1 -0
  85. package/dist/assets/dart-CtKP3W-j.js +1 -0
  86. package/dist/assets/dax-CEL-wOlO.js +1 -0
  87. package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  88. package/dist/assets/desktop-BmXAJ9_W.js +1 -0
  89. package/dist/assets/diagram-S7CK7UJ4-BiJw5LQ6.js +30 -0
  90. package/dist/assets/diagram-UQ7AKVKN-DRsX_xa6.js +41 -0
  91. package/dist/assets/diagram-VSXAHHWV-B7m6F8Dm.js +3 -0
  92. package/dist/assets/diagram-VX7I27RA-x4wioeVA.js +24 -0
  93. package/dist/assets/diagram-Z3DM3KII-0KYjyvJF.js +24 -0
  94. package/dist/assets/diff-D97Zzqfu.js +1 -0
  95. package/dist/assets/docker-BcOcwvcX.js +1 -0
  96. package/dist/assets/dotenv-De0Btta0.js +1 -0
  97. package/dist/assets/dracula-BzJJZx-M.js +1 -0
  98. package/dist/assets/dracula-soft-BXkSAIEj.js +1 -0
  99. package/dist/assets/dream-maker-BtqSS_iP.js +1 -0
  100. package/dist/assets/ebnfDiagram-PWID7BFC-BZXmt3oU.js +1 -0
  101. package/dist/assets/edge-CJaD-t2L.js +1 -0
  102. package/dist/assets/elixir-DkJLSxtU.js +1 -0
  103. package/dist/assets/elm-DbKCFpqz.js +1 -0
  104. package/dist/assets/emacs-lisp-DaIrSCgJ.js +1 -0
  105. package/dist/assets/erDiagram-RLTQ6QDP-CrfcYtD6.js +99 -0
  106. package/dist/assets/erb-CSMaCouu.js +1 -0
  107. package/dist/assets/erlang-DsQrWhSR.js +1 -0
  108. package/dist/assets/everforest-dark-BgDCqdQA.js +1 -0
  109. package/dist/assets/everforest-light-C8M2exoo.js +1 -0
  110. package/dist/assets/fennel-BYunw83y.js +1 -0
  111. package/dist/assets/fish-BvzEVeQv.js +1 -0
  112. package/dist/assets/flowDiagram-HODETNUW-BqudKoEy.js +168 -0
  113. package/dist/assets/fluent-C4IJs8-o.js +1 -0
  114. package/dist/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
  115. package/dist/assets/fortran-free-form-BxgE0vQu.js +1 -0
  116. package/dist/assets/fsharp-CXgrBDvD.js +1 -0
  117. package/dist/assets/ganttDiagram-EL5Y4UJY-OBWjueb8.js +292 -0
  118. package/dist/assets/gdresource-TyuKm33G.js +1 -0
  119. package/dist/assets/gdscript-DqcFQ5yU.js +1 -0
  120. package/dist/assets/gdshader-DkwncUOv.js +1 -0
  121. package/dist/assets/genie-D0YGMca9.js +1 -0
  122. package/dist/assets/gherkin-DyxjwDmM.js +1 -0
  123. package/dist/assets/git-commit-F4YmCXRG.js +1 -0
  124. package/dist/assets/git-rebase-r7XF79zn.js +1 -0
  125. package/dist/assets/gitGraphDiagram-WWUBYQGX-B30e199R.js +106 -0
  126. package/dist/assets/github-dark-DHJKELXO.js +1 -0
  127. package/dist/assets/github-dark-default-Cuk6v7N8.js +1 -0
  128. package/dist/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  129. package/dist/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  130. package/dist/assets/github-light-DAi9KRSo.js +1 -0
  131. package/dist/assets/github-light-default-D7oLnXFd.js +1 -0
  132. package/dist/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  133. package/dist/assets/gleam-BspZqrRM.js +1 -0
  134. package/dist/assets/glimmer-js-Drrc9bTt.js +1 -0
  135. package/dist/assets/glimmer-ts-zeybXsvi.js +1 -0
  136. package/dist/assets/glsl-DplSGwfg.js +1 -0
  137. package/dist/assets/gn-8iBmfbXw.js +1 -0
  138. package/dist/assets/gnuplot-DdkO51Og.js +1 -0
  139. package/dist/assets/go-C27-OAKa.js +1 -0
  140. package/dist/assets/graphql-ChdNCCLP.js +1 -0
  141. package/dist/assets/groovy-gcz8RCvz.js +1 -0
  142. package/dist/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  143. package/dist/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  144. package/dist/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  145. package/dist/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  146. package/dist/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  147. package/dist/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  148. package/dist/assets/hack-Ck6-gGSS.js +1 -0
  149. package/dist/assets/haml-GN_XdMnF.js +1 -0
  150. package/dist/assets/handlebars-DPT5vKgj.js +1 -0
  151. package/dist/assets/haskell-Df6bDoY_.js +1 -0
  152. package/dist/assets/haxe-CfZj7gIn.js +1 -0
  153. package/dist/assets/hcl-BWvSN4gD.js +1 -0
  154. package/dist/assets/highlighted-body-JLMTFACH-DapGkHdb.js +1 -0
  155. package/dist/assets/hjson-D5-asLiD.js +1 -0
  156. package/dist/assets/hlsl-D3lLCCz7.js +1 -0
  157. package/dist/assets/horizon-BUw7H-hv.js +1 -0
  158. package/dist/assets/horizon-bright-CUuTKBJd.js +1 -0
  159. package/dist/assets/houston-DnULxvSX.js +1 -0
  160. package/dist/assets/html-BlS82ilI.js +1 -0
  161. package/dist/assets/html-derivative-DZhk3IEZ.js +1 -0
  162. package/dist/assets/http-jrhK8wxY.js +1 -0
  163. package/dist/assets/hurl-irOxFIW8.js +1 -0
  164. package/dist/assets/hxml-2-FPmUDs.js +1 -0
  165. package/dist/assets/hy-DFXneXwc.js +1 -0
  166. package/dist/assets/imba-DGztddWO.js +1 -0
  167. package/dist/assets/index-C_bKTXmh.js +900 -0
  168. package/dist/assets/index-hKoeppfe.css +1 -0
  169. package/dist/assets/infoDiagram-27XIBGKW-DPwVvB4K.js +2 -0
  170. package/dist/assets/ini-BEwlwnbL.js +1 -0
  171. package/dist/assets/init-Gi6I4Gst.js +1 -0
  172. package/dist/assets/ishikawaDiagram-5VMMS53U-B0fvRdQL.js +70 -0
  173. package/dist/assets/java-CylS5w8V.js +1 -0
  174. package/dist/assets/javascript-wDzz0qaB.js +1 -0
  175. package/dist/assets/jinja-DFQZVq3b.js +1 -0
  176. package/dist/assets/jison-wvAkD_A8.js +1 -0
  177. package/dist/assets/journeyDiagram-3NMN7TZE-CZBLF7ld.js +139 -0
  178. package/dist/assets/json-Cp-IABpG.js +1 -0
  179. package/dist/assets/json5-C9tS-k6U.js +1 -0
  180. package/dist/assets/jsonc-Des-eS-w.js +1 -0
  181. package/dist/assets/jsonl-DcaNXYhu.js +1 -0
  182. package/dist/assets/jsonnet-DFQXde-d.js +1 -0
  183. package/dist/assets/jssm-C2t-YnRu.js +1 -0
  184. package/dist/assets/jsx-g9-lgVsj.js +1 -0
  185. package/dist/assets/julia-Cu79e8SL.js +1 -0
  186. package/dist/assets/just-CMxyWJJc.js +1 -0
  187. package/dist/assets/kanagawa-dragon-BuwD2xS4.js +1 -0
  188. package/dist/assets/kanagawa-lotus-C3DzagqV.js +1 -0
  189. package/dist/assets/kanagawa-wave-DFGoQZhC.js +1 -0
  190. package/dist/assets/kanban-definition-UXKFOSKX-wTBJWh9T.js +89 -0
  191. package/dist/assets/kdl-DV7GczEv.js +1 -0
  192. package/dist/assets/kotlin-BdnUsdx6.js +1 -0
  193. package/dist/assets/kusto-wEQ09or8.js +1 -0
  194. package/dist/assets/laserwave-DUszq2jm.js +1 -0
  195. package/dist/assets/latex-DJVikpzR.js +1 -0
  196. package/dist/assets/layout-DYBs-gVA.js +1 -0
  197. package/dist/assets/lean-BZvkOJ9d.js +1 -0
  198. package/dist/assets/less-B1dDrJ26.js +1 -0
  199. package/dist/assets/light-plus-B7mTdjB0.js +1 -0
  200. package/dist/assets/linear-DTE3zVP_.js +1 -0
  201. package/dist/assets/liquid-B-duXHXw.js +1 -0
  202. package/dist/assets/llvm-BZoOZj88.js +1 -0
  203. package/dist/assets/log-2UxHyX5q.js +1 -0
  204. package/dist/assets/logo-BtOb2qkB.js +1 -0
  205. package/dist/assets/lua-BaeVxFsk.js +1 -0
  206. package/dist/assets/luau-nOZpfvQi.js +1 -0
  207. package/dist/assets/make-CHLpvVh8.js +1 -0
  208. package/dist/assets/markdown-Cvjx9yec.js +1 -0
  209. package/dist/assets/marko-BYgDmDgU.js +1 -0
  210. package/dist/assets/material-theme-D5KoaKCx.js +1 -0
  211. package/dist/assets/material-theme-darker-BfHTSMKl.js +1 -0
  212. package/dist/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  213. package/dist/assets/material-theme-ocean-CyktbL80.js +1 -0
  214. package/dist/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  215. package/dist/assets/matlab-D7o27uSR.js +1 -0
  216. package/dist/assets/mdc-DF4WjDMn.js +1 -0
  217. package/dist/assets/mdx-Cmh6b_Ma.js +1 -0
  218. package/dist/assets/mermaid-CQcHuHx7.js +1 -0
  219. package/dist/assets/min-dark-CafNBF8u.js +1 -0
  220. package/dist/assets/min-light-CTRr51gU.js +1 -0
  221. package/dist/assets/mindmap-definition-YA3MSWOX-tV3HPpzU.js +96 -0
  222. package/dist/assets/mipsasm-CKIfxQSi.js +1 -0
  223. package/dist/assets/mojo-DJz3ZmWd.js +1 -0
  224. package/dist/assets/monokai-CR4piiQ-.js +1 -0
  225. package/dist/assets/moonbit-Bag4Rp_g.js +1 -0
  226. package/dist/assets/move-el3G9tDJ.js +1 -0
  227. package/dist/assets/narrat-DRg8JJMk.js +1 -0
  228. package/dist/assets/nextflow-BCxp2Aob.js +1 -0
  229. package/dist/assets/nextflow-groovy-CedDknKX.js +1 -0
  230. package/dist/assets/nginx-BpAMiNFr.js +1 -0
  231. package/dist/assets/night-owl-C39BiMTA.js +1 -0
  232. package/dist/assets/night-owl-light-CMTm3GFP.js +1 -0
  233. package/dist/assets/nim-nV83PyhX.js +1 -0
  234. package/dist/assets/nix-CwoSXNpI.js +1 -0
  235. package/dist/assets/nord-Ddv68eIx.js +1 -0
  236. package/dist/assets/nsis-BlV79W_Q.js +1 -0
  237. package/dist/assets/nushell-BXD5iret.js +1 -0
  238. package/dist/assets/objective-c-DXmwc3jG.js +1 -0
  239. package/dist/assets/objective-cpp-CLxacb5B.js +1 -0
  240. package/dist/assets/ocaml-C0hk2d4L.js +1 -0
  241. package/dist/assets/odin-BBf5iR-q.js +1 -0
  242. package/dist/assets/one-dark-pro-CxGP3dtY.js +1 -0
  243. package/dist/assets/one-light-C3Wv6jpd.js +1 -0
  244. package/dist/assets/openscad-C4EeE6gA.js +1 -0
  245. package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  246. package/dist/assets/org-6a7ccXN_.js +1 -0
  247. package/dist/assets/pascal-D93ZcfNL.js +1 -0
  248. package/dist/assets/pegDiagram-XKGWAZYB-B6Yznbb5.js +1 -0
  249. package/dist/assets/perl-5klg-GXX.js +1 -0
  250. package/dist/assets/php-DoruYMF0.js +1 -0
  251. package/dist/assets/pieDiagram-E7YTZNPT-B6QW8pJH.js +39 -0
  252. package/dist/assets/pkl-u5AG7uiY.js +1 -0
  253. package/dist/assets/plastic-3e1v2bzS.js +1 -0
  254. package/dist/assets/plsql-ChMvpjG-.js +1 -0
  255. package/dist/assets/po-BTJTHyun.js +1 -0
  256. package/dist/assets/poimandres-CS3Unz2-.js +1 -0
  257. package/dist/assets/polar-C0HS_06l.js +1 -0
  258. package/dist/assets/postcss-CXtECtnM.js +1 -0
  259. package/dist/assets/powerquery-CEu0bR-o.js +1 -0
  260. package/dist/assets/powershell-Dd0kTxKP.js +1 -0
  261. package/dist/assets/prisma-Vru482bI.js +1 -0
  262. package/dist/assets/prolog-CbFg5uaA.js +1 -0
  263. package/dist/assets/proto-C7zT0LnQ.js +1 -0
  264. package/dist/assets/pug-BtYPkAgq.js +1 -0
  265. package/dist/assets/puppet-BMWR74SV.js +1 -0
  266. package/dist/assets/purescript-CklMAg4u.js +1 -0
  267. package/dist/assets/python-B6aJPvgy.js +1 -0
  268. package/dist/assets/qml-3beO22l8.js +1 -0
  269. package/dist/assets/qmldir-C8lEn-DE.js +1 -0
  270. package/dist/assets/qss-IeuSbFQv.js +1 -0
  271. package/dist/assets/quadrantDiagram-AXDQQJYC-DgHGTtj0.js +7 -0
  272. package/dist/assets/r-Cf5RLm7j.js +1 -0
  273. package/dist/assets/racket-BqYA7rlc.js +1 -0
  274. package/dist/assets/railroadDiagram-O6MQD6OU-CgqVFEBK.js +1 -0
  275. package/dist/assets/raku-DXvB9xmW.js +1 -0
  276. package/dist/assets/razor-CyjPwjZj.js +1 -0
  277. package/dist/assets/rbs-CpoqiR4B.js +1 -0
  278. package/dist/assets/red-DMbB5_pp.js +1 -0
  279. package/dist/assets/reg-C-SQnVFl.js +1 -0
  280. package/dist/assets/regexp-CDVJQ6XC.js +1 -0
  281. package/dist/assets/rel-C3B-1QV4.js +1 -0
  282. package/dist/assets/requirementDiagram-BXWQKSXE-FmXMY-6j.js +84 -0
  283. package/dist/assets/riscv-BM1_JUlF.js +1 -0
  284. package/dist/assets/ron-D8l8udqQ.js +1 -0
  285. package/dist/assets/rose-pine-5qJOZa0Y.js +1 -0
  286. package/dist/assets/rose-pine-dawn-zx0QlTCp.js +1 -0
  287. package/dist/assets/rose-pine-moon-C9pEdX9L.js +1 -0
  288. package/dist/assets/rosmsg-BJDFO7_C.js +1 -0
  289. package/dist/assets/rst-CVOjT0e7.js +1 -0
  290. package/dist/assets/ruby-C8rp0wew.js +1 -0
  291. package/dist/assets/rust-DPvrY3IQ.js +1 -0
  292. package/dist/assets/sankeyDiagram-P5KCCOFB-AVMEYVKy.js +40 -0
  293. package/dist/assets/sas-BGn39htG.js +1 -0
  294. package/dist/assets/sass-Cj5Yp3dK.js +1 -0
  295. package/dist/assets/scala-CqE71os6.js +1 -0
  296. package/dist/assets/scheme-C98Dy4si.js +1 -0
  297. package/dist/assets/scss-Cw6XMfVs.js +1 -0
  298. package/dist/assets/sdbl-DVxCFoDh.js +1 -0
  299. package/dist/assets/sequenceDiagram-WJ2MYXX4-uOObReYi.js +162 -0
  300. package/dist/assets/shaderlab-Dg9Lc6iA.js +1 -0
  301. package/dist/assets/shellscript-Yzrsuije.js +1 -0
  302. package/dist/assets/shellsession-BADoaaVG.js +1 -0
  303. package/dist/assets/sizeCapture-INFHLROL-BPOPqqxk.js +1 -0
  304. package/dist/assets/slack-dark-BthQWCQV.js +1 -0
  305. package/dist/assets/slack-ochin-DqwNpetd.js +1 -0
  306. package/dist/assets/smalltalk-BOQMe2GC.js +1 -0
  307. package/dist/assets/smithy-cds9vsN8.js +1 -0
  308. package/dist/assets/snazzy-light-Bw305WKR.js +1 -0
  309. package/dist/assets/solarized-dark-UcubFVte.js +1 -0
  310. package/dist/assets/solarized-light-DCG28T6R.js +1 -0
  311. package/dist/assets/solidity-DijEV5ha.js +1 -0
  312. package/dist/assets/soy-zscZBeg6.js +1 -0
  313. package/dist/assets/sparql-rVzFXLq3.js +1 -0
  314. package/dist/assets/splunk-BtCnVYZw.js +1 -0
  315. package/dist/assets/sql-DkvoVjww.js +1 -0
  316. package/dist/assets/ssh-config-_ykCGR6B.js +1 -0
  317. package/dist/assets/stata-DACIj5bg.js +1 -0
  318. package/dist/assets/stateDiagram-D77RDMKH-C1lXHNdu.js +1 -0
  319. package/dist/assets/stateDiagram-v2-MP3YSRHH-BHg8O_NZ.js +1 -0
  320. package/dist/assets/stylus-BEDo0Tqx.js +1 -0
  321. package/dist/assets/surrealql-Cjom0U5J.js +1 -0
  322. package/dist/assets/svelte-D84K1R2Q.js +1 -0
  323. package/dist/assets/swift-C2oV4EkX.js +1 -0
  324. package/dist/assets/swimlanes-42K2YHIH-C52GOi05.js +1 -0
  325. package/dist/assets/swimlanesDiagram-VR7AAH4N-DLB_mWZL.js +8 -0
  326. package/dist/assets/synthwave-84-CbfX1IO0.js +1 -0
  327. package/dist/assets/system-verilog-0hqHdDBg.js +1 -0
  328. package/dist/assets/systemd-4A_iFExJ.js +1 -0
  329. package/dist/assets/talonscript-CkByrt1z.js +1 -0
  330. package/dist/assets/tasl-QIJgUcNo.js +1 -0
  331. package/dist/assets/tcl-dwOrl1Do.js +1 -0
  332. package/dist/assets/templ-Vpw5XpSW.js +1 -0
  333. package/dist/assets/terraform-BETggiCN.js +1 -0
  334. package/dist/assets/tex-D96PA37w.js +1 -0
  335. package/dist/assets/timeline-definition-24CTP7MA-T0BhAPL-.js +120 -0
  336. package/dist/assets/tokyo-night-hegEt444.js +1 -0
  337. package/dist/assets/toml-vGWfd6FD.js +1 -0
  338. package/dist/assets/ts-tags-Ls60c9xG.js +1 -0
  339. package/dist/assets/tsv-B_m7g4N7.js +1 -0
  340. package/dist/assets/tsx-COt5Ahok.js +1 -0
  341. package/dist/assets/turtle-BsS91CYL.js +1 -0
  342. package/dist/assets/twig-D9-LOF2p.js +1 -0
  343. package/dist/assets/typescript-BPQ3VLAy.js +1 -0
  344. package/dist/assets/typespec-ClX509-Q.js +1 -0
  345. package/dist/assets/typst-DamkJZ1Q.js +1 -0
  346. package/dist/assets/v-BGw2Nkan.js +1 -0
  347. package/dist/assets/vala-CsfeWuGM.js +1 -0
  348. package/dist/assets/vb-Cu-pLBUe.js +1 -0
  349. package/dist/assets/vennDiagram-4TSXK5OY-DSGCn4iB.js +34 -0
  350. package/dist/assets/verilog-nZwndyjY.js +1 -0
  351. package/dist/assets/vesper-DRje8inN.js +1 -0
  352. package/dist/assets/vhdl-CeAyd5Ju.js +1 -0
  353. package/dist/assets/viml-CJc9bBzg.js +1 -0
  354. package/dist/assets/vitesse-black-Bkuqu6BP.js +1 -0
  355. package/dist/assets/vitesse-dark-D0r3Knsf.js +1 -0
  356. package/dist/assets/vitesse-light-CVO1_9PV.js +1 -0
  357. package/dist/assets/vue-DSqPwMUe.js +1 -0
  358. package/dist/assets/vue-html-AaS7Mt5G.js +1 -0
  359. package/dist/assets/vue-vine-C0RcJ6UU.js +1 -0
  360. package/dist/assets/vyper-CDx5xZoG.js +1 -0
  361. package/dist/assets/wardleyDiagram-VM6X3IG4-BaCYx_03.js +78 -0
  362. package/dist/assets/wasm-CG6Dc4jp.js +1 -0
  363. package/dist/assets/wasm-MzD3tlZU.js +1 -0
  364. package/dist/assets/wenyan-BV7otONQ.js +1 -0
  365. package/dist/assets/wgsl-Dx-B1_4e.js +1 -0
  366. package/dist/assets/wikitext-BhOHFoWU.js +1 -0
  367. package/dist/assets/wit-5i3qLPDT.js +1 -0
  368. package/dist/assets/wolfram-lXgVvXCa.js +1 -0
  369. package/dist/assets/xml-sdJ4AIDG.js +1 -0
  370. package/dist/assets/xsl-CtQFsRM5.js +1 -0
  371. package/dist/assets/xychartDiagram-S5SC5T6Z-CgPwXvFv.js +7 -0
  372. package/dist/assets/yaml-Buea-lGh.js +1 -0
  373. package/dist/assets/zenscript-DVFEvuxE.js +1 -0
  374. package/dist/assets/zig-VOosw3JB.js +1 -0
  375. package/dist/fonts/geist-mono.woff2 +0 -0
  376. package/dist/fonts/geist-sans.woff2 +0 -0
  377. package/dist/index.html +14 -0
  378. package/dist/server.mjs +4787 -0
  379. package/package.json +76 -4
  380. package/src/shared/types.ts +180 -0
package/API.md ADDED
@@ -0,0 +1,106 @@
1
+ # The API
2
+
3
+ The SPA in this package is one client of this API. Another app can be another one — that is the point of the split:
4
+ everything a UI needs is HTTP, and the rendering is yours.
5
+
6
+ - **Base**: `http://<host>:<port>/api/v1` (the same routes also answer on `/api`, which is what this package's own
7
+ client uses).
8
+ - **Auth**: `Authorization: Bearer <token>`. `GET /api/v1/meta` and `GET /api/health` are open. A server on loopback
9
+ may have no token at all; a server that listens on anything else always does.
10
+ - **Errors**: a failed request is `{"error": "..."}` with a 4xx/5xx. Success bodies are JSON unless noted.
11
+ - **CORS**: any origin unless the server was started with `--allow-origin a,b`. The token is the authorization, not a
12
+ cookie, so credentials are never sent implicitly.
13
+
14
+ A typed client with no dependencies lives in [`client.ts`](./client.ts):
15
+
16
+ ```ts
17
+ import { LoopsClient } from "@computesdk/pi-loops-ui";
18
+
19
+ const loops = new LoopsClient({ server: "http://box:3000", token: process.env.LOOPS_TOKEN! });
20
+ const off = loops.events(chat, {
21
+ state: (s) => render(s), // whole picture, every change
22
+ error: (e) => console.warn(e), // the stream reconnects on its own
23
+ });
24
+ await loops.prompt(chat, "make search() faster; bench is node bench.mjs");
25
+ ```
26
+
27
+ ## Reading
28
+
29
+ | Route | Returns |
30
+ |---|---|
31
+ | `GET /meta` | name, api version, package version, host, whether a token is required, and the workspaces |
32
+ | `GET /workspaces` | `Workspace[]`: `{ id, path, sessionDir, title }` — the repos this server may loop in |
33
+ | `GET /chats?ws=` | `ChatInfo[]`: `{ id, title, last, ws, summary }`, newest first, all repos or one |
34
+ | `GET /state?chat=` | `ChatState`: `{ chat, title, items, live, runs, questions, dialogs, workspaces, ws, proposals }` |
35
+ | `GET /chats/:id/runs` | `Run[]` for a canvas, without the transcript |
36
+ | `GET /config?chat=` | `{ cwd, keys: [{ key, value, source }] }` — each setting with the file it came from |
37
+ | `GET /session?run=&name=` | one attempt's transcript (`ParentItem[]`) |
38
+ | `GET /diff?run=&name=` | `text/plain` unified diff (`name` is an attempt label or a style; `best` works too) |
39
+ | `GET /shot?run=&attempt=&f=` | `image/png` — an attempt's screenshot |
40
+ | `GET /sheet?run=&f=` | `image/png` — the round's contact sheet |
41
+ | `GET /size?run=` | `{ bytes, dirs }` — what a run's worktrees hold (what `clean` would free) |
42
+ | `GET /ui?chat=` | `extension_ui_request[]` still waiting for an answer |
43
+
44
+ ### The event stream
45
+
46
+ ```
47
+ GET /chats/:id/events Accept: text/event-stream
48
+
49
+ id: 6f3a… ← the session entry cursor
50
+ event: state ← the same object GET /state returns
51
+ event: proposals ← an agent rewrote .pi/loops.json and the server held it
52
+ event: ping ← keep-alive, every 20s
53
+ ```
54
+
55
+ The first frame is always a full `state`, so a client paints immediately and never needs a second call to bootstrap.
56
+ On reconnect send `Last-Event-ID: <the last id you saw>`; the server answers with a snapshot stamped with the newest
57
+ cursor, so nothing is missed while the client was away. `EventSource` cannot set headers — use `fetch` + a stream
58
+ reader (the client above does) so the token can travel in `Authorization`.
59
+
60
+ ## Writing
61
+
62
+ | Route | Body | Effect |
63
+ |---|---|---|
64
+ | `POST /chat` | `{ ws? }` | new chat in a workspace (default: the server's repo) |
65
+ | `POST /rename` | `{ chat, name }` | names the Pi session, so the TUI sees it too |
66
+ | `POST /chat/delete` | `{ chat }` | deletes the conversation; its loops stay in the repo |
67
+ | `POST /workspaces` | `{ path }` | add a repo (path on the server's machine) |
68
+ | `POST /workspaces/remove` | `{ id }` | forget a repo (not the one the server was started in) |
69
+ | `POST /prompt` | `{ chat, text, behavior? }` | send a message; `behavior` is `steer`/`followUp` while running |
70
+ | `POST /abort` | `{ chat }` | stop the active turn |
71
+ | `POST /answer` | `{ chat, question, choice }` | answer a queued question (a "needs you" card) |
72
+ | `POST /ui` | `{ chat, id, response }` | answer an extension dialog: `{ value }`, `{ confirmed }` or `{ cancelled }` |
73
+ | `POST /steer` | `{ chat, run, action, … }` | engine actions, below |
74
+ | `POST /config` | `{ chat, key, value }` | write one setting (the engine's own writer) |
75
+ | `POST /doctor` | `{ chat }` | boot the app, run the check, ask the judge to read an image |
76
+ | `POST /talk` | `{ chat, run, name, text }` | message the attempt that is running |
77
+ | `POST /live` | `{ run }` | run the winner, return `{ url }` (one slot per server) |
78
+ | `POST /live/stop` | `{ run }` | stop it |
79
+
80
+ `POST /steer` — the actions a UI offers on a run:
81
+
82
+ ```json
83
+ { "chat": "chat-…", "run": "loop-…", "action": "take", "attempt": "R1-bold" }
84
+ { "chat": "chat-…", "run": "loop-…", "action": "apply" }
85
+ { "chat": "chat-…", "run": "loop-…", "action": "stop" }
86
+ { "chat": "chat-…", "run": "loop-…", "action": "clean" }
87
+ { "chat": "chat-…", "run": "loop-…", "action": "rule", "rule": "never touch test.mjs" }
88
+ { "chat": "chat-…", "run": "loop-…", "action": "talk", "attempt": "R1-safe", "text": "keep going" }
89
+ { "chat": "chat-…", "run": "loop-…", "action": "more", "text": 2 }
90
+ ```
91
+
92
+ `take` continues from an attempt even if the judge rejected it, `apply` lands the winner in the checkout and commits,
93
+ `clean` frees the attempt worktrees (the winner's stays), `more` asks the agent for another round. Responses say
94
+ `{"ok": true, "sent": "<disposition>"}`; `sent: "handled"` means an extension consumed the prompt and no run started.
95
+
96
+ ## The loop lifecycle, for a client
97
+
98
+ 1. `prompt` → the agent may call `loop`. In a repo with no config it first raises a **dialog** (`select`: save/change/
99
+ discuss the setup) — answer it with `POST /ui`, or the tool call waits forever.
100
+ 2. `loop` returns as soon as the round starts; the round card arrives later as a chat message. Follow both through the
101
+ event stream: `state.live` is the turn, `state.runs[].rounds[].inProgress` is the round, `state.dialogs` are the
102
+ questions that block.
103
+ 3. When the round is judged, `runs[].questions` gains a `veto` / `close` / `stall` / `apply` question. Answer it with
104
+ `POST /answer` — the answer becomes a message to the agent, which acts (`take`, `apply`, …).
105
+ 4. Scores, per-attempt outcomes and the judge's reasons are in the round entry; screenshots and diffs are the
106
+ `GET /shot|/diff|/sheet` routes above.
package/README.md CHANGED
@@ -1,3 +1,222 @@
1
- # Temporary Holding Version
1
+ # @computesdk/loops
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Loops for people who do not live in a terminal: a `loops` CLI and a localhost web UI, both clients of
4
+ [`@computesdk/pi-loops`](../pi-loops). The extension is the foundation — it owns the loops, the records and every
5
+ capability. This package owns none of that; it drives the engine.
6
+
7
+ ```sh
8
+ loops status # what ran here, read straight from the records (no pi process, no tokens)
9
+ loops doctor --live # can a round work here? boots serve, runs check, asks the judge to read an image
10
+ loops run "<goal>" # a round — the one verb that costs tokens
11
+ loops clean | apply | take | rule | questions | answer
12
+ loops serve # the browser app for this repo, and the URL to open
13
+ ```
14
+
15
+ Installed from npm it is one command, and that is the one to hand someone:
16
+
17
+ ```sh
18
+ npx @computesdk/loops serve
19
+ ```
20
+
21
+ It is a **client of a real Pi session**, not a re-implementation of one. There is no database and no poll loop:
22
+ the session is the durable record, and `.pi/loops/` is a rebuildable index plus the worktrees it points at.
23
+
24
+ | What the UI shows | Where it comes from |
25
+ |---|---|
26
+ | the transcript | `get_messages` from the chat's `pi --mode rpc` child |
27
+ | a loop, its rounds, its scores | the `loops-state` snapshot and `loops-round` entries the extension appends to that session |
28
+ | the current winner / applied commit | the newest `loops-state` snapshot (`best`, `winner`, `baseCommit`) |
29
+ | a round in flight | the `loops-progress` entries it appends while it works (throttled; phase changes always) |
30
+ | questions for you | `loops-question` / `loops-answer` entries — append-only, so nothing is rewritten |
31
+ | the brief / approve card | the extension's `ctx.ui.select`, as an `extension_ui_request` |
32
+ | diffs, screenshots, sheets | read-only routes over the run's worktrees and archived shots under `.pi/loops/` |
33
+
34
+ A chat's repo is also recoverable from Pi itself: each session header records its `cwd`, and the session dir names the
35
+ workspace. `workspaces.json` is a cache — if it is lost or stale, the server rebuilds the chat-to-repo binding from the
36
+ sessions on startup, and writes it atomically.
37
+
38
+ ## Local only, by design
39
+
40
+ The server binds `127.0.0.1` and refuses any other `--host`. It is not a service: it can prompt an agent that has
41
+ `bash` on this machine, and it serves code that attempts wrote. There is no auth because there is no remote. If you need
42
+ it from another device, tunnel to it (`ssh -L 8001:127.0.0.1:8001 …`) rather than exposing the port.
43
+
44
+ ## Run it
45
+
46
+ Start it once for the machine, then point it at repos from the tab bar:
47
+
48
+ ```sh
49
+ npx @computesdk/loops serve # from npm — the one command, from any repo
50
+ # from a checkout:
51
+ loops serve # or: node server/index.ts --port 8001
52
+ # or: pi-loops-ui --port 8001 --cwd ~/code/some-repo (seeds one tab)
53
+ ```
54
+
55
+ It ships as one bundled server plus the built SPA, so there is no build step on the other end. The `pi-loops` extension
56
+ is a runtime dependency and is resolved out of `node_modules`; `pi` itself still has to be on the machine for a round
57
+ to run, which the boot says out loud rather than at the first prompt.
58
+
59
+ It prints the address and opens it:
60
+
61
+ ```
62
+ open http://127.0.0.1:8001
63
+ loopback only
64
+ ```
65
+
66
+ `--no-open` (or `LOOPS_UI_OPEN=0`) prints the address without launching a browser. When the URL carries a token — off
67
+ loopback — the opened address is the attach form, `/?server=…&token=…`, which the app adopts once and strips from the
68
+ address bar.
69
+
70
+ A loop needs the engine, so the boot says when it is missing rather than leaving you to find out on the first prompt:
71
+ if `pi` is not on PATH, or an `--extensions` path does not exist, it prints what is wrong and the fix under `!! a loop
72
+ cannot run here yet`, and `/api/health` reports `ok: false` with the same list. The app still serves — you can add
73
+ repos and read what already ran.
74
+
75
+ A **tab is a repo** ("workspace"): its chats, its runs, its `.pi/loops.json`. `+` adds a repo — the path is resolved
76
+ server-side and must be a directory — and `×` closes it; loops and chats stay on disk. Workspaces, the tab you had
77
+ open, and chats are remembered in `~/.pi/loops-ui`, so a restart resumes everything. A chat remembers its repo, so
78
+ its `pi --mode rpc` child spawns in that directory with that directory's session dir.
79
+
80
+ Flags (each has a `LOOPS_UI_*` environment variable):
81
+
82
+ | flag | default | meaning |
83
+ |---|---|---|
84
+ | `--port` | `8001` | where the app listens |
85
+ | `--cwd` | `process.cwd()` | the repo the loops work in |
86
+ | `--pi` | `pi` | the Pi executable each chat spawns |
87
+ | `--extensions` | `../pi-loops/extensions/loop.ts` | comma-separated `-e` paths for the child |
88
+ | `--data` | `~/.pi/loops-ui` | workspaces.json and the chats of every repo |
89
+ | `--host` | `127.0.0.1` | loopback only; anything else is refused (this drives an agent with tools) |
90
+ | `--model` | Pi's default | the model every chat starts on |
91
+ | `--no-open` | off | print the URL without opening a browser (`LOOPS_UI_OPEN=0`) |
92
+ | `--dev` | off | proxy the SPA to Vite on `:5173` (`npm run dev`) |
93
+
94
+ ## Attach to a server on another machine
95
+
96
+ The SPA is a client. By default it talks to the process that served it; it can be pointed at another one:
97
+
98
+ ```sh
99
+ # on the box that owns the repos — off-loopback listening needs a token
100
+ node server/index.ts --port 3000 --host 0.0.0.0 --token # prints the URL + token once
101
+
102
+ # anywhere else: either open the served UI at box:3000, or attach this one to it
103
+ http://box:3000/?server=http://box:3000&token=<printed>
104
+ ```
105
+
106
+ The `server`/`token` pair is adopted once and stripped from the address bar; after that the sidebar footer names the
107
+ server you are attached to (click it to change). A URL that lands in a log is not a problem because the token lives in
108
+ `localStorage` afterwards, but treat it as the password it is: `~/.pi/loops-ui/token`, mode 0600, `--token` to rotate.
109
+
110
+ **The API is the product.** Anything that wants its own chat UI, its own loop canvas, or its own needs-you inbox should
111
+ use it rather than this SPA: see [`API.md`](./API.md) for every route and the loop lifecycle, and [`client.ts`](./client.ts)
112
+ for a typed client (no dependencies) that can be imported straight into another app.
113
+
114
+ The routes are the contract, not the SPA: `GET /api/workspaces`, `/api/chats?ws=`, `/api/state?chat=`, `/api/config?chat=`,
115
+ `POST /api/chat|prompt|steer|answer|ui|config|live`, and **one event stream per chat**:
116
+
117
+ ```
118
+ GET /api/chats/:id/events Accept: text/event-stream
119
+ id: <session entry id> ← the cursor; send it back as Last-Event-ID after a drop
120
+ event: state ← the same snapshot /api/state returns
121
+ event: ping ← keep-alive every 20s
122
+ ```
123
+
124
+ SSE over `fetch` (not `EventSource`) so the token can travel in a header, and so a reconnect resumes from the cursor
125
+ instead of starting blind. Any origin may attach (CORS is answered); the token is the authorization.
126
+
127
+ ## What it is made of
128
+
129
+ ```
130
+ server/
131
+ index.ts Hono: /api/state, /api/prompt, /api/ui, /api/steer, /api/shot, /api/diff, /ws, static
132
+ chats.ts one Chat per session: spawns `pi --mode rpc`, folds its events into one snapshot
133
+ pi-rpc.ts the RPC client: JSONL framing, id correlation, extension dialogs
134
+ runs.ts the durable session entries first, the `.pi/loops` index as fallback -> the shapes the canvas draws
135
+ workspaces.ts the repos this process may touch; chats are bound to one
136
+ config.ts settings with provenance, agent-write proposals, and the doctor checks
137
+ live.ts one live server for the winner, on demand
138
+ src/
139
+ shared/ the wire types both halves use
140
+ client/ the SPA (React 19 + Tailwind v4), ported from the POC at ~/factory/mock
141
+ ```
142
+
143
+ ### Why an event stream and not a poll
144
+
145
+ A loop runs **in the background** in Pi: the `loop` tool returns as soon as the round starts, and the round card arrives
146
+ later as a message. In a terminal that is fine — the status line and the card are enough. A canvas wants more, so the
147
+ extension appends a `loops-progress` entry (at most one per 1.5s, always on a phase change) and calls `setStatus`, which
148
+ reaches us as an `extension_ui_request`. Either one wakes the server, which re-reads the session with a cursor
149
+ (`get_entries?since=`). One event in, at most one cheap read out — and a browser that reloads mid-round rebuilds the same
150
+ picture from the entries alone.
151
+
152
+ ### Proof that the judge saw the picture
153
+
154
+ A judge that never received an image used to score a visual round anyway — the visible symptom was
155
+ `reasons: "Image unavailable"` next to a 5–5 "tie". Two mechanisms make that impossible to miss:
156
+
157
+ - **Declared capability is checked first.** A judge (or panel member) whose model does not claim image input is
158
+ refused before an attempt runs, with the setting to change. No model, no round.
159
+ - **Every judged screenshot carries a code in its top-right corner.** The judge must read it back
160
+ (`"tokens": {"<label>": "<code>"}`); one edit apart counts as a misread, anything else does not count at all. A
161
+ candidate whose code was not read back is `unseen`: it gets **no score**, the round is marked **inconclusive**
162
+ (never "even"), and a question is raised — retry, switch the judge, or keep the current best.
163
+
164
+ The code lives in the pixels only, so it cannot be echoed from the prompt. `lib/shot.ts` does the stamping
165
+ (`stamp`, `canaryImage`), `lib/judge.ts` the comparison (`sameCode`), and `probeVision()` can settle a model's
166
+ capability offline — not inside a tool call, where creating a session waits on the runtime the turn holds.
167
+
168
+ ### Live: one server, on demand
169
+
170
+ A screenshot is a still of an app that already runs — `lib/shot.ts` starts `serve` inside an attempt's worktree to take
171
+ it. `Run it` keeps that same server alive for the winner instead: one slot, started when you ask, killed when you stop
172
+ it, after ten idle minutes, or when this process exits. It is offered only when the repo has `serve` in
173
+ `.pi/loops.json`. The iframe gets an opaque origin (`sandbox`, no `allow-same-origin`), because the code inside it was
174
+ written by an attempt.
175
+
176
+ ### Judging happens in a child process
177
+
178
+ An in-session `prompt(text, { images })` does not deliver the picture on this Pi — the message carries the block, the
179
+ provider request does not — so a judge that has to look at screenshots is asked through `pi --print` (the same binary,
180
+ model and auth, with `@file` arguments, which do carry images). Two plumbing rules matter there and both cost a bug
181
+ each: close stdin (`ignore`) or `--print` waits for EOF forever, and parse **stdout only** (pi warns on stderr).
182
+
183
+ ### The checkout is not the attempts' to touch
184
+
185
+ An attempt has bash and inherits the repo's absolute path, so it can edit or commit into your checkout instead of its
186
+ worktree. Every round therefore checks, after the attempts: did HEAD move, did the working tree gain changes, while
187
+ every attempt's own worktree stayed at the base commit? If so the round is inconclusive (nothing of theirs exists to
188
+ compare), your tree is put back, and the card says what happened — the loop's promise is that your checkout is
189
+ untouched until `loop_apply`.
190
+
191
+ ### Cleanup
192
+
193
+ Worktrees are scratch copies: the commit lives in the repo and on the branch `loops/<run>/rN-<style>`, so removing a
194
+ worktree loses only what the attempt left uncommitted. `clean up` (or `/loop clean [run|all]`) removes the attempts'
195
+ worktrees, drops the losing branches and runs `git worktree prune`, keeping the winner's worktree, the durable session
196
+ snapshot, the JSON index, the sessions and the archived screenshots. `loop_apply` does it for the losers as soon as a
197
+ winner lands.
198
+
199
+ ### Settings that change under you
200
+
201
+ `.pi/loops.json` decides which models judge, what the checks are and how much runs. The server watches it and the app
202
+ shows what changed (`models judge exe/… → particle/…`) while it is open — an agent editing config is a real event, not
203
+ a detail.
204
+
205
+ ### Actions
206
+
207
+ The buttons talk to the engine, not to a model: `/loop take`, `/loop apply`, `/loop stop`, `/loop rule`, `/loop talk`,
208
+ `/loop answer`. Anything that needs judgement ("two more rounds", "steer it this way") is sent to the agent as a message,
209
+ because that is a decision the agent owns.
210
+
211
+ ## Parity
212
+
213
+ `npm run shots` writes `./shots/` at 1440, 1024 and 430 wide. The pass condition for this port was the screens the POC at
214
+ `~/factory/mock` shows, at the same density; see [`PARITY.md`](./PARITY.md) for the state-by-state comparison.
215
+
216
+ ## Development
217
+
218
+ ```sh
219
+ npm run dev # Hono on :8001 proxying the SPA to Vite on :5173
220
+ npm run check # typecheck the server and the client separately
221
+ npm run shots # headless-chrome screenshots of every state
222
+ ```
package/bin/loops.mjs ADDED
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env node
2
+ // `loops` — the foundation without a TUI.
3
+ //
4
+ // Two kinds of verb, and the difference matters:
5
+ // • bookkeeping (status) reads the records the engine writes; no pi process, no tokens, scriptable
6
+ // • everything else asks the engine to do it, by running the same `/loop …` command a person would type.
7
+ // The CLI owns no loop logic. If it ever computed a run itself there would be two answers to "what is a run",
8
+ // and the one an agent used would disagree with the one you printed.
9
+ import fs from "node:fs";
10
+ import path from "node:path";
11
+ import os from "node:os";
12
+ import { fileURLToPath } from "node:url";
13
+ import { spawn, spawnSync } from "node:child_process";
14
+
15
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
16
+ const PKG = path.resolve(HERE, "..");
17
+ const argv = process.argv.slice(2);
18
+ const flag = (name) => argv.includes(`--${name}`);
19
+ const opt = (name, fallback = undefined) => {
20
+ const i = argv.indexOf(`--${name}`);
21
+ return i >= 0 ? argv[i + 1] : fallback;
22
+ };
23
+ /** Arguments that are not flags or flag values. */
24
+ function positional() {
25
+ const out = [];
26
+ const takesValue = new Set(["cwd", "port", "extension", "pi", "run", "attempt", "option", "goal"]);
27
+ for (let i = 0; i < argv.length; i++) {
28
+ if (argv[i].startsWith("--")) { if (takesValue.has(argv[i].slice(2))) i++; continue; }
29
+ out.push(argv[i]);
30
+ }
31
+ return out;
32
+ }
33
+
34
+ const CWD = path.resolve(opt("cwd", process.cwd()));
35
+ const STATE_DIR = path.join(CWD, ".pi", "loops");
36
+ const PI = opt("pi", process.env.LOOPS_PI ?? "pi");
37
+
38
+ /** The extension this CLI drives: the installed package if we can find it, else the sibling checkout. */
39
+ function extensionPath() {
40
+ const explicit = opt("extension", process.env.LOOPS_EXTENSION);
41
+ if (explicit) return path.resolve(explicit);
42
+ const candidates = [
43
+ path.resolve(PKG, "../pi-loops/extensions/loop.ts"), // monorepo checkout
44
+ path.resolve(PKG, "node_modules/@computesdk/pi-loops/extensions/loop.ts"), // installed next to us
45
+ path.resolve(HERE, "../../../pi-loops/extensions/loop.ts"),
46
+ ];
47
+ for (const c of candidates) if (fs.existsSync(c)) return c;
48
+ // npm keeps the dependency flat: ask Node where that package actually is
49
+ try {
50
+ const req = spawnSync(process.execPath, ["-e", "console.log(require.resolve('@computesdk/pi-loops/package.json'))"], { encoding: "utf8", cwd: PKG });
51
+ const found = req.stdout?.trim();
52
+ if (found) return path.join(path.dirname(found), "extensions", "loop.ts");
53
+ } catch {}
54
+ return null;
55
+ }
56
+
57
+ const usage = `loops — hill-climbing loops for a repo, without the TUI
58
+
59
+ loops status [--json] [--run <id>] what ran here: goal, rounds, best, whether it is still working
60
+ loops doctor [--live] [--json] can a round work here? (--live boots serve, runs check, tests the judge)
61
+ loops config every setting in force, and which file it came from
62
+ loops run "<goal>" [loop options] start a round (costs tokens; this one needs a model)
63
+ loops questions decisions waiting for you
64
+ loops answer <id> <option> answer one
65
+ loops take <R1-bold> [run] keep that attempt and climb from it
66
+ loops rule <run|last> "<rule>" add a judging rule
67
+ loops apply [run] land the winner in your checkout
68
+ loops clean [run|all] remove attempt worktrees (commits and the winner stay)
69
+ loops serve [--port 8001] [--no-open]
70
+ the browser app for this repo (localhost only), prints the URL and opens it
71
+ loops ui same thing, by the older name
72
+
73
+ --cwd <repo> the checkout to work on (default: this directory)
74
+ --extension <p> the pi-loops extension to load
75
+ --pi <path> the pi executable
76
+ --json machine-readable output where the verb supports it
77
+
78
+ Anything that needs a model runs the same /loop command you would type in pi.`;
79
+
80
+ const say = (line = "") => process.stderr.write(`${line}\n`);
81
+
82
+ /** Runs are read straight off the records the engine writes: no process, no tokens. */
83
+ function runs({ run } = {}) {
84
+ if (!fs.existsSync(STATE_DIR)) return [];
85
+ const out = [];
86
+ for (const f of fs.readdirSync(STATE_DIR).filter((f) => f.endsWith(".json"))) {
87
+ try {
88
+ const s = JSON.parse(fs.readFileSync(path.join(STATE_DIR, f), "utf8"));
89
+ if (!s?.id || (run && s.id !== run)) continue;
90
+ const judged = (s.rounds ?? []).filter((r) => r.judge);
91
+ out.push({
92
+ id: s.id, goal: s.goal, kind: s.kind, status: s.status,
93
+ rounds: (s.rounds ?? []).length, judged: judged.length,
94
+ best: s.best?.label ?? null, bestRound: s.best?.round ?? null, score: s.best?.score ?? null,
95
+ unit: s.metric?.unit ?? "", escaped: (s.rounds ?? []).some((r) => r.escaped),
96
+ inconclusive: (s.rounds ?? []).some((r) => r.inconclusive),
97
+ startedAt: s.rounds?.[0]?.startedAt ?? null, endedAt: s.rounds?.at(-1)?.endedAt ?? null,
98
+ file: path.relative(CWD, path.join(STATE_DIR, f)),
99
+ });
100
+ } catch {}
101
+ }
102
+ return out.sort((a, b) => String(b.startedAt ?? "").localeCompare(String(a.startedAt ?? "")));
103
+ }
104
+
105
+ const fmt = (r) => {
106
+ const score = r.score == null ? "–" : `${r.score}${r.unit ? ` ${r.unit}` : ""}`;
107
+ const state = r.status === "running" ? "running" : r.escaped ? "ran in your checkout" : r.inconclusive ? "inconclusive" : r.status;
108
+ return `${r.id} ${r.kind.padEnd(6)} ${state.padEnd(20)} r${r.rounds}/${r.judged} ${r.best ?? "–"} ${score}`.trimEnd() + `\n ${r.goal ?? ""}`;
109
+ };
110
+
111
+ function status() {
112
+ const list = runs({ run: opt("run") });
113
+ if (flag("json")) return process.stdout.write(JSON.stringify(list, null, 2) + "\n");
114
+ if (!list.length) return say(`no runs in ${path.relative(process.cwd(), STATE_DIR) || STATE_DIR} — start one with: loops run "<goal>"`);
115
+ for (const r of list) say(fmt(r) + "\n");
116
+ const live = list.filter((r) => r.status === "running").length;
117
+ say(`${list.length} run${list.length === 1 ? "" : "s"}${live ? `, ${live} running` : ""}`);
118
+ }
119
+
120
+ /* ---------- the verbs that ask pi ---------- */
121
+
122
+ function commandFor(verb) {
123
+ const p = positional().slice(1); // drop the verb itself
124
+ switch (verb) {
125
+ case "questions": return "/loop questions";
126
+ case "answer": return `/loop answer ${p[0] ?? ""} ${p.slice(1).join(" ")}`.trim();
127
+ case "take": return `/loop take ${p[0] ?? ""} ${p[1] ?? ""}`.trim();
128
+ case "rule": return `/loop rule ${p[0] ?? "last"} ${p.slice(1).join(" ")}`;
129
+ case "apply": return `/loop apply ${p[0] ?? ""}`.trim();
130
+ case "clean": return `/loop clean ${p[0] ?? ""}`.trim();
131
+ case "stop": return "/loop stop";
132
+ case "config": return "/loop config";
133
+ case "doctor": return flag("live") ? "/loop doctor live" : "/loop doctor";
134
+ default: return null;
135
+ }
136
+ }
137
+
138
+ /** Run one /loop command in a pi child and pass its output straight through. */
139
+ function askEngine(command) {
140
+ const ext = extensionPath();
141
+ if (!ext) {
142
+ say(`cannot find the pi-loops extension — pass --extension <path to extensions/loop.ts>`);
143
+ return 2;
144
+ }
145
+ const args = ["-p", "-e", ext, command];
146
+ const child = spawn(PI, args, { cwd: CWD, stdio: ["ignore", "inherit", "inherit"], env: { ...process.env, PI_OFFLINE: process.env.PI_OFFLINE ?? "1" } });
147
+ return new Promise((resolve) => child.on("close", (code) => resolve(code ?? 1)));
148
+ }
149
+
150
+ /** A round needs a model, so this is the one verb that spends tokens. */
151
+ function runRound() {
152
+ const goal = positional()[1] ?? opt("goal");
153
+ if (!goal) { say(`usage: loops run "<goal>" [--bench "<cmd>"] [--check "<cmd>"] [--rounds n]`); return 2; }
154
+ const parts = [goal];
155
+ if (opt("check")) parts.push(`check is ${opt("check")}`);
156
+ if (opt("bench")) parts.push(`bench is ${opt("bench")} and prints ${opt("unit", "ms")}`);
157
+ say(`starting a round — this one costs tokens\n`);
158
+ return askEngine(parts.join("; "));
159
+ }
160
+
161
+ async function main() {
162
+ const verb = positional()[0] ?? "";
163
+ if (!verb || verb === "help" || flag("help")) { say(usage); return 0; }
164
+ if (verb === "status") { status(); return 0; }
165
+ if (verb === "ui" || verb === "serve") {
166
+ // Published, the server is one bundled file — Node refuses to strip types inside node_modules, so the .ts is only
167
+ // the entry in a checkout, where node's stripper runs it directly. Same flags either way.
168
+ const bundle = path.join(PKG, "dist", "server.mjs");
169
+ const entry = fs.existsSync(bundle)
170
+ ? [bundle]
171
+ : ["--experimental-strip-types", path.join(PKG, "server", "index.ts")];
172
+ const args = [...entry, "--cwd", CWD, "--port", String(opt("port", "8001"))];
173
+ if (flag("no-open")) args.push("--no-open");
174
+ // The extension is a runtime dependency, so it resolves on an install as well as in this workspace; without it the
175
+ // server's own boot check says so instead of the first prompt failing.
176
+ const ext = extensionPath();
177
+ if (ext) args.push("--extensions", ext);
178
+ const child = spawn(process.execPath, args, { stdio: "inherit", env: process.env });
179
+ return new Promise((resolve) => child.on("close", (code) => resolve(code ?? 1)));
180
+ }
181
+ if (verb === "run") return runRound();
182
+ const command = commandFor(verb);
183
+ if (command) return askEngine(command);
184
+ say(`unknown command "${verb}"\n\n${usage}`);
185
+ return 2;
186
+ }
187
+
188
+ process.exitCode = await main();
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ // The old name, kept so nothing breaks: `pi-loops-ui --port 8001` is `loops ui --port 8001`.
3
+ process.argv.splice(2, 0, "ui");
4
+ await import("./loops.mjs");