hitloop 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.
- package/LICENSE +21 -0
- package/README.md +218 -2
- package/dist/agent.js +318 -0
- package/dist/cli.js +897 -0
- package/dist/client/assets/app.css +1 -0
- package/dist/client/assets/chunks/abap-EINPUMCJ.js +1 -0
- package/dist/client/assets/chunks/abnfDiagram-S7VZAUFO-W2CM2W6D.js +1 -0
- package/dist/client/assets/chunks/actionscript-3-O776AQUO.js +1 -0
- package/dist/client/assets/chunks/ada-WLOTXRTV.js +1 -0
- package/dist/client/assets/chunks/ahk-EF37A2DY.js +1 -0
- package/dist/client/assets/chunks/ahk2-FH4K3CKT.js +1 -0
- package/dist/client/assets/chunks/angular-html-ZALQFV2H.js +1 -0
- package/dist/client/assets/chunks/angular-ts-4IWMF7QO.js +1 -0
- package/dist/client/assets/chunks/apache-44AYOEWM.js +1 -0
- package/dist/client/assets/chunks/apex-XRYPID42.js +1 -0
- package/dist/client/assets/chunks/apl-5HBALFOI.js +1 -0
- package/dist/client/assets/chunks/applescript-2DZEHZEC.js +1 -0
- package/dist/client/assets/chunks/ara-ZGZQBLXL.js +1 -0
- package/dist/client/assets/chunks/architecture-WOLXFQ4H-EJGENNRL.js +1 -0
- package/dist/client/assets/chunks/architectureDiagram-YGSQEFNW-2VHWSKO6.js +36 -0
- package/dist/client/assets/chunks/asciidoc-GSQAKCPO.js +1 -0
- package/dist/client/assets/chunks/asm-BFURM5BK.js +1 -0
- package/dist/client/assets/chunks/astro-VJORWDZJ.js +1 -0
- package/dist/client/assets/chunks/awk-BSHTWXXX.js +1 -0
- package/dist/client/assets/chunks/ballerina-HAOQJ4B2.js +1 -0
- package/dist/client/assets/chunks/bat-OSH45EOF.js +1 -0
- package/dist/client/assets/chunks/beancount-RBATIHOV.js +1 -0
- package/dist/client/assets/chunks/berry-CZUIOUM4.js +1 -0
- package/dist/client/assets/chunks/bibtex-KCL2UWHE.js +1 -0
- package/dist/client/assets/chunks/bicep-SZJZB3SG.js +1 -0
- package/dist/client/assets/chunks/bird2-XBF5YQQV.js +1 -0
- package/dist/client/assets/chunks/blade-Q24LEVFC.js +1 -0
- package/dist/client/assets/chunks/blockDiagram-APKKGZFW-53LXSOOT.js +140 -0
- package/dist/client/assets/chunks/bpmnIcons-APHXDYXF-FMBRQNTY.js +1 -0
- package/dist/client/assets/chunks/bsl-TI44NWFB.js +1 -0
- package/dist/client/assets/chunks/c-NLHY4Y4N.js +1 -0
- package/dist/client/assets/chunks/c3-BUTEM6PO.js +1 -0
- package/dist/client/assets/chunks/c4Diagram-KJ2TOQU5-O53G4GQH.js +38 -0
- package/dist/client/assets/chunks/cadence-JIRTEU64.js +1 -0
- package/dist/client/assets/chunks/cairo-3W6NVDNW.js +1 -0
- package/dist/client/assets/chunks/chapel-BPBJ2E7K.js +1 -0
- package/dist/client/assets/chunks/chunk-25KVF5ZE.js +1 -0
- package/dist/client/assets/chunks/chunk-2CJKSQOI.js +1 -0
- package/dist/client/assets/chunks/chunk-2QIJRTWR.js +1 -0
- package/dist/client/assets/chunks/chunk-2ZCRJZGF.js +2 -0
- package/dist/client/assets/chunks/chunk-3ILNDZNY.js +213 -0
- package/dist/client/assets/chunks/chunk-3M2OOQ7S.js +1 -0
- package/dist/client/assets/chunks/chunk-3M57NTNK.js +1 -0
- package/dist/client/assets/chunks/chunk-3MR6DCZJ.js +1 -0
- package/dist/client/assets/chunks/chunk-3WGHS7F7.js +1 -0
- package/dist/client/assets/chunks/chunk-4JK75V33.js +1 -0
- package/dist/client/assets/chunks/chunk-4NXNVEWK.js +1 -0
- package/dist/client/assets/chunks/chunk-4RNNBDFQ.js +1 -0
- package/dist/client/assets/chunks/chunk-4T4NVH7V.js +1 -0
- package/dist/client/assets/chunks/chunk-4TFFBC35.js +1 -0
- package/dist/client/assets/chunks/chunk-4WYBE67P.js +1 -0
- package/dist/client/assets/chunks/chunk-56ITEUFA.js +1 -0
- package/dist/client/assets/chunks/chunk-5DXRE7IH.js +1 -0
- package/dist/client/assets/chunks/chunk-5IMADCNU.js +1 -0
- package/dist/client/assets/chunks/chunk-5XZPGKN4.js +1 -0
- package/dist/client/assets/chunks/chunk-6O32UKSP.js +1 -0
- package/dist/client/assets/chunks/chunk-7BAEB64Q.js +1 -0
- package/dist/client/assets/chunks/chunk-7FMWBHZO.js +1 -0
- package/dist/client/assets/chunks/chunk-7GFDITUT.js +1 -0
- package/dist/client/assets/chunks/chunk-7H4MLBHI.js +1 -0
- package/dist/client/assets/chunks/chunk-7OJ4MEEB.js +1 -0
- package/dist/client/assets/chunks/chunk-7RSEISFE.js +1 -0
- package/dist/client/assets/chunks/chunk-7YNZZQVT.js +1 -0
- package/dist/client/assets/chunks/chunk-AB7TIL2G.js +1 -0
- package/dist/client/assets/chunks/chunk-ADDF76FL.js +1 -0
- package/dist/client/assets/chunks/chunk-AF6UTCD4.js +321 -0
- package/dist/client/assets/chunks/chunk-AJ2K6ASU.js +1 -0
- package/dist/client/assets/chunks/chunk-AUBAENQZ.js +1 -0
- package/dist/client/assets/chunks/chunk-BJEYVZ65.js +1 -0
- package/dist/client/assets/chunks/chunk-BLRM6LXA.js +1 -0
- package/dist/client/assets/chunks/chunk-BOCTYCDZ.js +1 -0
- package/dist/client/assets/chunks/chunk-BU4VRYN7.js +1 -0
- package/dist/client/assets/chunks/chunk-CALYHV7D.js +1 -0
- package/dist/client/assets/chunks/chunk-CF4W3RZ4.js +1 -0
- package/dist/client/assets/chunks/chunk-DCZ2OBSL.js +1 -0
- package/dist/client/assets/chunks/chunk-DUFPBTHD.js +1 -0
- package/dist/client/assets/chunks/chunk-DUICIQBC.js +88 -0
- package/dist/client/assets/chunks/chunk-EH2N475V.js +1 -0
- package/dist/client/assets/chunks/chunk-EHLLNPZT.js +1 -0
- package/dist/client/assets/chunks/chunk-EIBFAW37.js +1 -0
- package/dist/client/assets/chunks/chunk-EO3ER3GN.js +1 -0
- package/dist/client/assets/chunks/chunk-FH24BIIA.js +1 -0
- package/dist/client/assets/chunks/chunk-FNOXGAL5.js +2 -0
- package/dist/client/assets/chunks/chunk-FTY6WLQF.js +3 -0
- package/dist/client/assets/chunks/chunk-FZFME6UK.js +1 -0
- package/dist/client/assets/chunks/chunk-FZJZZYYO.js +1 -0
- package/dist/client/assets/chunks/chunk-G4Z6L7OO.js +1 -0
- package/dist/client/assets/chunks/chunk-GNCEAN2B.js +1 -0
- package/dist/client/assets/chunks/chunk-GQJIKV3T.js +1 -0
- package/dist/client/assets/chunks/chunk-GUMNOKT2.js +1 -0
- package/dist/client/assets/chunks/chunk-GXRORAF3.js +1 -0
- package/dist/client/assets/chunks/chunk-H36ABDKY.js +1 -0
- package/dist/client/assets/chunks/chunk-HAR5NYVW.js +1 -0
- package/dist/client/assets/chunks/chunk-HIGPS4L2.js +1 -0
- package/dist/client/assets/chunks/chunk-HMOBGTME.js +1 -0
- package/dist/client/assets/chunks/chunk-HNO6SQ7M.js +1 -0
- package/dist/client/assets/chunks/chunk-HYVZ3GYN.js +1 -0
- package/dist/client/assets/chunks/chunk-I6TGFOQS.js +1 -0
- package/dist/client/assets/chunks/chunk-IHIOJK7I.js +1 -0
- package/dist/client/assets/chunks/chunk-IHJNUMSC.js +1 -0
- package/dist/client/assets/chunks/chunk-IP7PB2OC.js +1 -0
- package/dist/client/assets/chunks/chunk-ISDTMH2Y.js +1 -0
- package/dist/client/assets/chunks/chunk-J65M2LNT.js +1 -0
- package/dist/client/assets/chunks/chunk-JCWYZ7X2.js +62 -0
- package/dist/client/assets/chunks/chunk-JENWYTLI.js +1 -0
- package/dist/client/assets/chunks/chunk-JJ6YWJQS.js +1 -0
- package/dist/client/assets/chunks/chunk-JMUVGGX7.js +32 -0
- package/dist/client/assets/chunks/chunk-JNQUFMM4.js +1 -0
- package/dist/client/assets/chunks/chunk-JPTECOAL.js +1 -0
- package/dist/client/assets/chunks/chunk-JZNK4LKS.js +1 -0
- package/dist/client/assets/chunks/chunk-KPNXJBIW.js +1 -0
- package/dist/client/assets/chunks/chunk-KZO2GZL7.js +1 -0
- package/dist/client/assets/chunks/chunk-L2REESOQ.js +1 -0
- package/dist/client/assets/chunks/chunk-LJPJXR7R.js +1 -0
- package/dist/client/assets/chunks/chunk-LOEZMLCI.js +1 -0
- package/dist/client/assets/chunks/chunk-MLYC6FSE.js +1 -0
- package/dist/client/assets/chunks/chunk-MMDGEZYU.js +1 -0
- package/dist/client/assets/chunks/chunk-MYXE7CGE.js +1 -0
- package/dist/client/assets/chunks/chunk-NBH7YOBD.js +15 -0
- package/dist/client/assets/chunks/chunk-NCOVM3YF.js +1 -0
- package/dist/client/assets/chunks/chunk-NCWOBVU7.js +1 -0
- package/dist/client/assets/chunks/chunk-NLRIHHEH.js +1 -0
- package/dist/client/assets/chunks/chunk-NUPEOYK6.js +1 -0
- package/dist/client/assets/chunks/chunk-O4MVOA7X.js +1 -0
- package/dist/client/assets/chunks/chunk-OBHQQZ4H.js +1 -0
- package/dist/client/assets/chunks/chunk-OKPNJIIR.js +1 -0
- package/dist/client/assets/chunks/chunk-OLMPB57R.js +1 -0
- package/dist/client/assets/chunks/chunk-P2FYOAEV.js +1 -0
- package/dist/client/assets/chunks/chunk-PI3SNP4C.js +1 -0
- package/dist/client/assets/chunks/chunk-PRDTEROB.js +1 -0
- package/dist/client/assets/chunks/chunk-PZELHD2H.js +136 -0
- package/dist/client/assets/chunks/chunk-QR2Q34LM.js +1 -0
- package/dist/client/assets/chunks/chunk-R4GIXL6S.js +1 -0
- package/dist/client/assets/chunks/chunk-RORF4F5V.js +1 -0
- package/dist/client/assets/chunks/chunk-S73BYTLL.js +1 -0
- package/dist/client/assets/chunks/chunk-SFC5MRYR.js +1 -0
- package/dist/client/assets/chunks/chunk-SVM2H22K.js +2 -0
- package/dist/client/assets/chunks/chunk-SZ6UCGVD.js +1 -0
- package/dist/client/assets/chunks/chunk-SZYM6GIJ.js +127 -0
- package/dist/client/assets/chunks/chunk-TT5KJZKM.js +1 -0
- package/dist/client/assets/chunks/chunk-UFBKUYNH.js +1 -0
- package/dist/client/assets/chunks/chunk-UL57AD4T.js +1 -0
- package/dist/client/assets/chunks/chunk-UNEI7LMM.js +1 -0
- package/dist/client/assets/chunks/chunk-VNQBBFIY.js +1 -0
- package/dist/client/assets/chunks/chunk-WGVITPII.js +1 -0
- package/dist/client/assets/chunks/chunk-WRA6KJHX.js +10 -0
- package/dist/client/assets/chunks/chunk-WS5EHW43.js +1 -0
- package/dist/client/assets/chunks/chunk-XE6SLBBE.js +3 -0
- package/dist/client/assets/chunks/chunk-XF5T2P7U.js +72 -0
- package/dist/client/assets/chunks/chunk-XFRY5WKO.js +1 -0
- package/dist/client/assets/chunks/chunk-XWYV4OPA.js +1 -0
- package/dist/client/assets/chunks/chunk-YCE5ECKY.js +1 -0
- package/dist/client/assets/chunks/chunk-YIN5CSIF.js +1 -0
- package/dist/client/assets/chunks/chunk-YLFUX6XH.js +1 -0
- package/dist/client/assets/chunks/chunk-YNXJICZM.js +1 -0
- package/dist/client/assets/chunks/chunk-YOHF7NGD.js +1 -0
- package/dist/client/assets/chunks/chunk-YORIST7Z.js +1 -0
- package/dist/client/assets/chunks/chunk-YUL7CG4R.js +1 -0
- package/dist/client/assets/chunks/chunk-YUN7JSFJ.js +2 -0
- package/dist/client/assets/chunks/chunk-YZ7D2IT7.js +1 -0
- package/dist/client/assets/chunks/chunk-Z7LAPMFJ.js +1 -0
- package/dist/client/assets/chunks/chunk-ZE325LPC.js +1 -0
- package/dist/client/assets/chunks/chunk-ZN7TN6PO.js +1 -0
- package/dist/client/assets/chunks/chunk-ZST3EVPO.js +1 -0
- package/dist/client/assets/chunks/chunk-ZZHMDDZN.js +1 -0
- package/dist/client/assets/chunks/clarity-RKRYQ7WF.js +1 -0
- package/dist/client/assets/chunks/classDiagram-v2-L25IHNXF-Y67HVDZV.js +217 -0
- package/dist/client/assets/chunks/clojure-2X2DQECJ.js +1 -0
- package/dist/client/assets/chunks/cmake-LH5524XE.js +1 -0
- package/dist/client/assets/chunks/cobol-7DALQF3W.js +1 -0
- package/dist/client/assets/chunks/codeowners-52SZU5P5.js +1 -0
- package/dist/client/assets/chunks/codeql-EISH3KUP.js +1 -0
- package/dist/client/assets/chunks/coffee-VAWSGA7Y.js +1 -0
- package/dist/client/assets/chunks/common-lisp-4MVK2AN6.js +1 -0
- package/dist/client/assets/chunks/coq-4VRYZRZV.js +1 -0
- package/dist/client/assets/chunks/core-BRBMZSD2.js +12 -0
- package/dist/client/assets/chunks/cose-bilkent-JH36ORCC-BIOOHSAA.js +1 -0
- package/dist/client/assets/chunks/cpp-RFECMSKD.js +1 -0
- package/dist/client/assets/chunks/crystal-ZBGGFAOI.js +1 -0
- package/dist/client/assets/chunks/csharp-BZDPMKKW.js +1 -0
- package/dist/client/assets/chunks/css-3WWGM6UD.js +1 -0
- package/dist/client/assets/chunks/csv-AB4GNWSB.js +1 -0
- package/dist/client/assets/chunks/cue-2E46MZP2.js +1 -0
- package/dist/client/assets/chunks/cynefin-EF2NZ3EQ-TLFQLWUN.js +1 -0
- package/dist/client/assets/chunks/cynefinDiagram-L36ESFBY-FLPXCSSG.js +62 -0
- package/dist/client/assets/chunks/cypher-HR3ILFOL.js +1 -0
- package/dist/client/assets/chunks/d-TARSZPOL.js +1 -0
- package/dist/client/assets/chunks/dagre-SPEB5RXH-7EGEINJ2.js +4 -0
- package/dist/client/assets/chunks/dart-SALLJHVI.js +1 -0
- package/dist/client/assets/chunks/dax-WYMRGMXC.js +1 -0
- package/dist/client/assets/chunks/desktop-ND5CGLLJ.js +1 -0
- package/dist/client/assets/chunks/diagram-3B7GL73J-6USY4HZX.js +200 -0
- package/dist/client/assets/chunks/diagram-DNEMPZML-BNSOC4VW.js +24 -0
- package/dist/client/assets/chunks/diagram-L3MUGCDN-5V5FZYIG.js +41 -0
- package/dist/client/assets/chunks/diagram-LA4NIPWU-25VLY2CO.js +24 -0
- package/dist/client/assets/chunks/diagram-MHCOC4E2-G2LFP3JU.js +3 -0
- package/dist/client/assets/chunks/diagram-XPFHEONP-E6HBBIBV.js +30 -0
- package/dist/client/assets/chunks/diff-PWYGPM7B.js +1 -0
- package/dist/client/assets/chunks/docker-A4D7DT7W.js +1 -0
- package/dist/client/assets/chunks/dotenv-VWQWV2DY.js +1 -0
- package/dist/client/assets/chunks/dream-maker-M5YDQS35.js +1 -0
- package/dist/client/assets/chunks/ebnfDiagram-UZSYDL2S-BMXXZZPI.js +1 -0
- package/dist/client/assets/chunks/edge-3QXIDJHI.js +1 -0
- package/dist/client/assets/chunks/elixir-3OX2UHIZ.js +1 -0
- package/dist/client/assets/chunks/elk-XV6XMXQE-IPR65FON.js +27 -0
- package/dist/client/assets/chunks/elm-CTS65NCT.js +1 -0
- package/dist/client/assets/chunks/emacs-lisp-HHS2IDO7.js +1 -0
- package/dist/client/assets/chunks/engine-javascript-DNHSF67B.js +141 -0
- package/dist/client/assets/chunks/erDiagram-EYBAPDEH-3GEHFWHG.js +99 -0
- package/dist/client/assets/chunks/erb-ICYWK6HS.js +1 -0
- package/dist/client/assets/chunks/erlang-BDQPNCKX.js +1 -0
- package/dist/client/assets/chunks/eventmodeling-K75KTNOO-B27BJTZL.js +1 -0
- package/dist/client/assets/chunks/fennel-4ONBA2PH.js +1 -0
- package/dist/client/assets/chunks/fish-ATLPBZSD.js +1 -0
- package/dist/client/assets/chunks/flowDiagram-MALGAFMO-5PCLR4AE.js +1 -0
- package/dist/client/assets/chunks/fluent-GGMONBVW.js +1 -0
- package/dist/client/assets/chunks/fortran-fixed-form-5MSBDRWQ.js +1 -0
- package/dist/client/assets/chunks/fortran-free-form-TT3RBL75.js +1 -0
- package/dist/client/assets/chunks/fsharp-PAGHNW4O.js +1 -0
- package/dist/client/assets/chunks/ganttDiagram-23REXITY-VE5MVBTO.js +292 -0
- package/dist/client/assets/chunks/gdresource-5OTP326D.js +1 -0
- package/dist/client/assets/chunks/gdscript-VPWHSCLF.js +1 -0
- package/dist/client/assets/chunks/gdshader-FYLPXFE5.js +1 -0
- package/dist/client/assets/chunks/genie-UYMEEMA2.js +1 -0
- package/dist/client/assets/chunks/gherkin-SU7D5DMY.js +1 -0
- package/dist/client/assets/chunks/git-commit-5CF5QAG7.js +1 -0
- package/dist/client/assets/chunks/git-rebase-X72BAUZI.js +1 -0
- package/dist/client/assets/chunks/gitGraph-VSP46ZUC-J6CJ5YZF.js +1 -0
- package/dist/client/assets/chunks/gitGraphDiagram-STJD7YCW-7YJGT6ZP.js +106 -0
- package/dist/client/assets/chunks/gleam-LR42YRKO.js +1 -0
- package/dist/client/assets/chunks/glimmer-js-JPF67FJU.js +1 -0
- package/dist/client/assets/chunks/glimmer-ts-BYPAVIZT.js +1 -0
- package/dist/client/assets/chunks/glsl-GTGUULBN.js +1 -0
- package/dist/client/assets/chunks/gn-NMDQA4K3.js +1 -0
- package/dist/client/assets/chunks/gnuplot-UUAMYBCZ.js +1 -0
- package/dist/client/assets/chunks/go-4RERSTNP.js +1 -0
- package/dist/client/assets/chunks/graphql-5A6RK6YF.js +1 -0
- package/dist/client/assets/chunks/groovy-O7BJONCU.js +1 -0
- package/dist/client/assets/chunks/hack-7QGTNPDP.js +1 -0
- package/dist/client/assets/chunks/haml-ON5GAWN4.js +1 -0
- package/dist/client/assets/chunks/handlebars-6UOQSUBP.js +1 -0
- package/dist/client/assets/chunks/haskell-OIZXZ26Y.js +1 -0
- package/dist/client/assets/chunks/haxe-7FFVS4FO.js +1 -0
- package/dist/client/assets/chunks/hcl-XFUFQJHV.js +1 -0
- package/dist/client/assets/chunks/hjson-QDBHMTD5.js +1 -0
- package/dist/client/assets/chunks/hlsl-4LP25FFN.js +1 -0
- package/dist/client/assets/chunks/html-7RNQXAAV.js +1 -0
- package/dist/client/assets/chunks/html-derivative-ESOY72Q2.js +1 -0
- package/dist/client/assets/chunks/http-SAD4PMBD.js +1 -0
- package/dist/client/assets/chunks/hurl-ZSJJXPQH.js +1 -0
- package/dist/client/assets/chunks/hxml-HJCC37RK.js +1 -0
- package/dist/client/assets/chunks/hy-CLDE3P4K.js +1 -0
- package/dist/client/assets/chunks/imba-REB34U5V.js +1 -0
- package/dist/client/assets/chunks/info-OHQRW6UA-EQQKXR77.js +1 -0
- package/dist/client/assets/chunks/infoDiagram-KBXO4MBG-F6KGZCGS.js +2 -0
- package/dist/client/assets/chunks/ini-PDFEMGRA.js +1 -0
- package/dist/client/assets/chunks/ishikawaDiagram-M35NCXHK-5D6ZOZ34.js +70 -0
- package/dist/client/assets/chunks/java-KB6XENAD.js +1 -0
- package/dist/client/assets/chunks/javascript-3JX5T423.js +1 -0
- package/dist/client/assets/chunks/jinja-V4UNU6K4.js +1 -0
- package/dist/client/assets/chunks/jison-KH5XNLJP.js +1 -0
- package/dist/client/assets/chunks/journeyDiagram-OPLOXDXO-ANDOJZKW.js +139 -0
- package/dist/client/assets/chunks/json-HPXFRRPY.js +1 -0
- package/dist/client/assets/chunks/json5-LJM6TXWM.js +1 -0
- package/dist/client/assets/chunks/jsonc-UZWRU3JV.js +1 -0
- package/dist/client/assets/chunks/jsonl-RFZD5UN4.js +1 -0
- package/dist/client/assets/chunks/jsonnet-GFZABBL6.js +1 -0
- package/dist/client/assets/chunks/jssm-2YIGBISJ.js +1 -0
- package/dist/client/assets/chunks/jsx-KLV2ZWUT.js +1 -0
- package/dist/client/assets/chunks/julia-3TZDIPPO.js +1 -0
- package/dist/client/assets/chunks/just-UHCONBQS.js +1 -0
- package/dist/client/assets/chunks/kanban-definition-BZKZHSQY-GF5I5NVD.js +89 -0
- package/dist/client/assets/chunks/katex-PIN72PQZ.js +257 -0
- package/dist/client/assets/chunks/kdl-DARTFOCL.js +1 -0
- package/dist/client/assets/chunks/kotlin-7KT5DW6Y.js +1 -0
- package/dist/client/assets/chunks/kusto-EKYXZTC5.js +1 -0
- package/dist/client/assets/chunks/langs-GO3UQR4H.js +1 -0
- package/dist/client/assets/chunks/latex-T57MIHR5.js +1 -0
- package/dist/client/assets/chunks/lean-5QVPRMV6.js +1 -0
- package/dist/client/assets/chunks/less-K6QEH5TN.js +1 -0
- package/dist/client/assets/chunks/liquid-3LCNNEIV.js +1 -0
- package/dist/client/assets/chunks/llvm-O32ZIVRF.js +1 -0
- package/dist/client/assets/chunks/log-C4YPJ5BB.js +1 -0
- package/dist/client/assets/chunks/logo-JPMO43A6.js +1 -0
- package/dist/client/assets/chunks/lua-YKVSOG7K.js +1 -0
- package/dist/client/assets/chunks/luau-K4EECYSO.js +1 -0
- package/dist/client/assets/chunks/make-VHSYMHHW.js +1 -0
- package/dist/client/assets/chunks/markdown-GC5J46H4.js +1 -0
- package/dist/client/assets/chunks/marko-U45YFADC.js +1 -0
- package/dist/client/assets/chunks/matlab-AMOKC4LU.js +1 -0
- package/dist/client/assets/chunks/mdc-DIGA2UXP.js +1 -0
- package/dist/client/assets/chunks/mdx-BZUGCKQA.js +1 -0
- package/dist/client/assets/chunks/mermaid-WHDP6ZPA.js +1 -0
- package/dist/client/assets/chunks/mermaid.core-SQMPMUDN.js +10 -0
- package/dist/client/assets/chunks/mindmap-definition-I2QAS2VK-4NH5S56C.js +98 -0
- package/dist/client/assets/chunks/mipsasm-2S4CUGQV.js +1 -0
- package/dist/client/assets/chunks/mojo-ZOL2PNKT.js +1 -0
- package/dist/client/assets/chunks/moonbit-U3BDAIFE.js +1 -0
- package/dist/client/assets/chunks/move-YSXALBWA.js +1 -0
- package/dist/client/assets/chunks/narrat-DHFIAUJH.js +1 -0
- package/dist/client/assets/chunks/nextflow-ESAKNTI3.js +1 -0
- package/dist/client/assets/chunks/nextflow-groovy-FKDSPJUI.js +1 -0
- package/dist/client/assets/chunks/nginx-XYAYERLO.js +1 -0
- package/dist/client/assets/chunks/nim-XOWBVFDH.js +1 -0
- package/dist/client/assets/chunks/nix-RVSTQSHC.js +1 -0
- package/dist/client/assets/chunks/nsis-KMDUKQRL.js +1 -0
- package/dist/client/assets/chunks/nushell-SKJHHZ7U.js +1 -0
- package/dist/client/assets/chunks/objective-c-3B3KXQ7Q.js +1 -0
- package/dist/client/assets/chunks/objective-cpp-6JDULPJS.js +1 -0
- package/dist/client/assets/chunks/ocaml-TMRFXBVO.js +1 -0
- package/dist/client/assets/chunks/odin-IN3PLCK7.js +1 -0
- package/dist/client/assets/chunks/openscad-P52IKPNH.js +1 -0
- package/dist/client/assets/chunks/org-N3SJ2PIA.js +1 -0
- package/dist/client/assets/chunks/packet-JDAUHWVQ-4SWIKSKP.js +1 -0
- package/dist/client/assets/chunks/pascal-T7CUWCQ4.js +1 -0
- package/dist/client/assets/chunks/pegDiagram-FD4XPPHP-WDKZSEGN.js +1 -0
- package/dist/client/assets/chunks/perl-LCZ4KODH.js +1 -0
- package/dist/client/assets/chunks/php-4XHMBVR5.js +1 -0
- package/dist/client/assets/chunks/pie-XZMESJXO-PSSWOWGV.js +1 -0
- package/dist/client/assets/chunks/pieDiagram-J4AKIIQI-BDW5I5ON.js +39 -0
- package/dist/client/assets/chunks/pkl-OACG5B67.js +1 -0
- package/dist/client/assets/chunks/plsql-7E5C7CJX.js +1 -0
- package/dist/client/assets/chunks/po-ZSREFRRP.js +1 -0
- package/dist/client/assets/chunks/polar-4GBINVA7.js +1 -0
- package/dist/client/assets/chunks/postcss-B7RPVGFW.js +1 -0
- package/dist/client/assets/chunks/powerquery-6UFMTY4G.js +1 -0
- package/dist/client/assets/chunks/powershell-3STRPCNT.js +1 -0
- package/dist/client/assets/chunks/prisma-4TPSPIZ5.js +1 -0
- package/dist/client/assets/chunks/prolog-HEAYPKQW.js +1 -0
- package/dist/client/assets/chunks/proto-Y3Y3VKXM.js +1 -0
- package/dist/client/assets/chunks/pug-7PBPMG3M.js +1 -0
- package/dist/client/assets/chunks/puppet-FR35R4OI.js +1 -0
- package/dist/client/assets/chunks/purescript-OUHXQAOW.js +1 -0
- package/dist/client/assets/chunks/python-SNDG7FNU.js +1 -0
- package/dist/client/assets/chunks/qml-UVIOSWP4.js +1 -0
- package/dist/client/assets/chunks/qmldir-6JGUNYL5.js +1 -0
- package/dist/client/assets/chunks/qss-ZCC466IZ.js +1 -0
- package/dist/client/assets/chunks/quadrantDiagram-XTYBLID2-5VKXWVJ6.js +7 -0
- package/dist/client/assets/chunks/r-4XW4O3ZF.js +1 -0
- package/dist/client/assets/chunks/racket-YJAYXOOG.js +1 -0
- package/dist/client/assets/chunks/radar-ABXABTNO-3AXZCEY2.js +1 -0
- package/dist/client/assets/chunks/railroad-I3PHUGI6-2PLXYPOT.js +1 -0
- package/dist/client/assets/chunks/railroad-abnf-LNEFI6M7-M4DGICQX.js +1 -0
- package/dist/client/assets/chunks/railroad-ebnf-DWYD2UWJ-UEIHQMSZ.js +1 -0
- package/dist/client/assets/chunks/railroad-peg-JR7BVNR7-I7VLIBXM.js +1 -0
- package/dist/client/assets/chunks/railroadDiagram-MRQ6NCDL-Y6IYWEFL.js +1 -0
- package/dist/client/assets/chunks/raku-63NEL5JK.js +1 -0
- package/dist/client/assets/chunks/razor-J3CJP523.js +1 -0
- package/dist/client/assets/chunks/rbs-6GQLT7NW.js +1 -0
- package/dist/client/assets/chunks/reg-6Q3OEC2C.js +1 -0
- package/dist/client/assets/chunks/regexp-LPIMILT3.js +1 -0
- package/dist/client/assets/chunks/rel-TOQGUTHB.js +1 -0
- package/dist/client/assets/chunks/requirementDiagram-Y7LWUY3S-CIWXVVWJ.js +84 -0
- package/dist/client/assets/chunks/riscv-UELGBOXE.js +1 -0
- package/dist/client/assets/chunks/ron-FW7LCJJH.js +1 -0
- package/dist/client/assets/chunks/rosmsg-O63VHE3L.js +1 -0
- package/dist/client/assets/chunks/rst-3WJXGLA7.js +1 -0
- package/dist/client/assets/chunks/ruby-7S7L6JTH.js +1 -0
- package/dist/client/assets/chunks/rust-LIK4HRRI.js +1 -0
- package/dist/client/assets/chunks/sankeyDiagram-WGJFYUXA-OGNQPX5V.js +40 -0
- package/dist/client/assets/chunks/sas-FHDPZ2JH.js +1 -0
- package/dist/client/assets/chunks/sass-UI4HW2FN.js +1 -0
- package/dist/client/assets/chunks/scala-KN7RQSUJ.js +1 -0
- package/dist/client/assets/chunks/scheme-5R527YZ5.js +1 -0
- package/dist/client/assets/chunks/scss-KZ6DRE4A.js +1 -0
- package/dist/client/assets/chunks/sdbl-FHOCL5SL.js +1 -0
- package/dist/client/assets/chunks/sequenceDiagram-5NDRBG5M-QX2V24ZU.js +169 -0
- package/dist/client/assets/chunks/shaderlab-2TLHUS2H.js +1 -0
- package/dist/client/assets/chunks/shellscript-PIB6ZBDM.js +1 -0
- package/dist/client/assets/chunks/shellsession-5KL7NFNN.js +1 -0
- package/dist/client/assets/chunks/sizeCapture-INFHLROL-LCS2PIHM.js +1 -0
- package/dist/client/assets/chunks/smalltalk-JFLFU247.js +1 -0
- package/dist/client/assets/chunks/smithy-HONEXZAM.js +1 -0
- package/dist/client/assets/chunks/solidity-SJ3INJFZ.js +1 -0
- package/dist/client/assets/chunks/soy-ND4SWTM2.js +1 -0
- package/dist/client/assets/chunks/sparql-YB2JQBZD.js +1 -0
- package/dist/client/assets/chunks/splunk-UXIDDAWL.js +1 -0
- package/dist/client/assets/chunks/sql-K6OMG7SQ.js +1 -0
- package/dist/client/assets/chunks/ssh-config-I7KJY3KQ.js +1 -0
- package/dist/client/assets/chunks/stata-DNZ3DTFJ.js +1 -0
- package/dist/client/assets/chunks/stateDiagram-v2-FJ4FJNEA-LVAUWF3A.js +291 -0
- package/dist/client/assets/chunks/stylus-GWPR6IQY.js +1 -0
- package/dist/client/assets/chunks/surrealql-KQNHBWLC.js +1 -0
- package/dist/client/assets/chunks/svelte-YQKF25S4.js +1 -0
- package/dist/client/assets/chunks/swift-ZCIZUJXA.js +1 -0
- package/dist/client/assets/chunks/swimlanes-UMQ2AFYF-B3RQOARQ.js +1 -0
- package/dist/client/assets/chunks/swimlanesDiagram-OMPLPG4O-JOE33EOV.js +8 -0
- package/dist/client/assets/chunks/system-verilog-Y7E6FIO3.js +1 -0
- package/dist/client/assets/chunks/systemd-WHC7NBZP.js +1 -0
- package/dist/client/assets/chunks/talonscript-WC7SODAH.js +1 -0
- package/dist/client/assets/chunks/tasl-QHY2GTCB.js +1 -0
- package/dist/client/assets/chunks/tcl-VAMIL5PJ.js +1 -0
- package/dist/client/assets/chunks/temml-ISNSX3VV.js +96 -0
- package/dist/client/assets/chunks/templ-B3BHKWV4.js +1 -0
- package/dist/client/assets/chunks/terraform-Q4UEK6FK.js +1 -0
- package/dist/client/assets/chunks/tex-63OWCYAB.js +1 -0
- package/dist/client/assets/chunks/timeline-definition-SEUHKTQE-FDFTVAJG.js +120 -0
- package/dist/client/assets/chunks/toml-7Z4IPGES.js +1 -0
- package/dist/client/assets/chunks/treeView-D4JQ5SDB-ABSGRAGI.js +1 -0
- package/dist/client/assets/chunks/treemap-SAJKECNS-MYX2CBJN.js +1 -0
- package/dist/client/assets/chunks/ts-tags-DT62HE52.js +1 -0
- package/dist/client/assets/chunks/tsv-RTXHWADP.js +1 -0
- package/dist/client/assets/chunks/tsx-UANUBOC7.js +1 -0
- package/dist/client/assets/chunks/turtle-QVOUNUUL.js +1 -0
- package/dist/client/assets/chunks/twig-ZPC4BPDA.js +1 -0
- package/dist/client/assets/chunks/typescript-WGWKIMMW.js +1 -0
- package/dist/client/assets/chunks/typespec-TYCVR32J.js +1 -0
- package/dist/client/assets/chunks/typst-PIBFYUO6.js +1 -0
- package/dist/client/assets/chunks/usecaseDiagram-AFA6YXN4-KW7ADINR.js +370 -0
- package/dist/client/assets/chunks/v-3ERNWBSP.js +1 -0
- package/dist/client/assets/chunks/vala-UCBCT2GS.js +1 -0
- package/dist/client/assets/chunks/vb-DGI3VTPA.js +1 -0
- package/dist/client/assets/chunks/vennDiagram-NNMJR3RB-RDGLGOGM.js +34 -0
- package/dist/client/assets/chunks/verilog-G72NZROS.js +1 -0
- package/dist/client/assets/chunks/vhdl-PYGA77XE.js +1 -0
- package/dist/client/assets/chunks/viml-OOC5B5UU.js +1 -0
- package/dist/client/assets/chunks/vue-GTNHIOPG.js +1 -0
- package/dist/client/assets/chunks/vue-html-DKAMJV55.js +1 -0
- package/dist/client/assets/chunks/vue-vine-WTM5HREV.js +1 -0
- package/dist/client/assets/chunks/vyper-RHYEHOJM.js +1 -0
- package/dist/client/assets/chunks/wardley-7MLQ67FV-QJL2HSBT.js +1 -0
- package/dist/client/assets/chunks/wardleyDiagram-S7YFUSEH-A4XX3UJR.js +78 -0
- package/dist/client/assets/chunks/wasm-ZMM2Z5XV.js +1 -0
- package/dist/client/assets/chunks/wenyan-HF5UTKSJ.js +1 -0
- package/dist/client/assets/chunks/wgsl-O7D7OPO7.js +1 -0
- package/dist/client/assets/chunks/wikitext-T7BSWSKQ.js +1 -0
- package/dist/client/assets/chunks/wit-TKCHPPT6.js +1 -0
- package/dist/client/assets/chunks/wolfram-7MN66A7N.js +1 -0
- package/dist/client/assets/chunks/xml-NLTLAWOD.js +1 -0
- package/dist/client/assets/chunks/xsl-NZ4ECK3P.js +1 -0
- package/dist/client/assets/chunks/xychartDiagram-GGNKY7IX-OXJILWHR.js +7 -0
- package/dist/client/assets/chunks/yaml-HRJLEV3V.js +1 -0
- package/dist/client/assets/chunks/zenscript-4LLRSLPI.js +1 -0
- package/dist/client/assets/chunks/zig-Y7MUQFUO.js +1 -0
- package/dist/client/assets/icon.svg +1 -0
- package/dist/client/assets/main.js +254 -0
- package/dist/client/index.html +59 -0
- package/dist/diff.js +119 -0
- package/dist/fs/files.js +233 -0
- package/dist/fs/git.js +369 -0
- package/dist/fs/glob.js +89 -0
- package/dist/fs/notes.js +368 -0
- package/dist/fs/search.js +136 -0
- package/dist/fs/watch.js +69 -0
- package/dist/fs/zip.js +100 -0
- package/dist/hooks.js +65 -0
- package/dist/http.js +81 -0
- package/dist/hub/atomic.js +77 -0
- package/dist/hub/notify.js +239 -0
- package/dist/hub/registry.js +219 -0
- package/dist/mcp.js +500 -0
- package/dist/q/agent.js +215 -0
- package/dist/q/model.js +633 -0
- package/dist/q/schema.js +133 -0
- package/dist/q/store.js +413 -0
- package/dist/r/model.js +323 -0
- package/dist/r/schema.js +53 -0
- package/dist/r/store.js +489 -0
- package/dist/server.js +246 -0
- package/dist/settings.js +30 -0
- package/dist/workspace.js +639 -0
- package/package.json +73 -4
- package/schema/questions.schema.json +1670 -0
- package/schema/reviews.schema.json +151 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,897 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// hitl — Human In The Loop: an agent asks, shows and asks for approval; you answer, review and browse on your phone.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import crypto from 'node:crypto';
|
|
7
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
8
|
+
import { StoreError, ensureReadme } from './hub/atomic.js';
|
|
9
|
+
import { start, version } from './server.js';
|
|
10
|
+
import { describeSet, readAnswers, FILTERS } from './q/agent.js';
|
|
11
|
+
import { decisionsText, news, newsCounts, openProject, overviewText, parseDuration, readFeedback, readNotes, waitFor } from './agent.js';
|
|
12
|
+
import { clearHubState, configDir, ensureHub, forget, hubToken, keyed, links, projects, projectsPath, register, runningHub, normaliseBase, userConfig, userConfigPath, updateUserConfig, writeHubState } from './hub/registry.js';
|
|
13
|
+
import { setSchema } from './q/schema.js';
|
|
14
|
+
import { reviewSchema } from './r/schema.js';
|
|
15
|
+
import { serveMcp, defaultFrom } from './mcp.js';
|
|
16
|
+
import { normalise as normaliseSet } from './q/model.js';
|
|
17
|
+
import { normalise as normaliseReview } from './r/model.js';
|
|
18
|
+
import { Notifier, linkBase, notifyConfig, notifyEnabled, send } from './hub/notify.js';
|
|
19
|
+
import { projectSettings } from './settings.js';
|
|
20
|
+
import { hookStatus, launcher, setHooks } from './hooks.js';
|
|
21
|
+
const PORT = 4199;
|
|
22
|
+
const HELP = `hitl ${version} — Human In The Loop: an agent asks, shows and asks for approval; you answer, review
|
|
23
|
+
and browse the project on your phone or computer, and it reads what you said.
|
|
24
|
+
|
|
25
|
+
Serve the page:
|
|
26
|
+
hitl [options] one server for every project (the hub); adds this folder
|
|
27
|
+
hitl --solo [folder] just this folder, on its own server
|
|
28
|
+
-p, --port <n> port to try first (default ${PORT})
|
|
29
|
+
-H, --host <addr> address to listen on (default 0.0.0.0: your phone can reach it, with a token)
|
|
30
|
+
--local only this computer (127.0.0.1, no token)
|
|
31
|
+
--base <path> serve under a URL path, e.g. hitl or team/hitl (behind a proxy; in Git Bash leave
|
|
32
|
+
off the leading /, or it becomes a Windows path)
|
|
33
|
+
--token <t> use this token (default: one made once, kept in ${tildeOf(configDir())})
|
|
34
|
+
--no-token no token (anyone on your network can open it)
|
|
35
|
+
--allow-host <h> accept this host name too (repeatable; * for any)
|
|
36
|
+
-o, --open open it in your browser
|
|
37
|
+
--read-only look, don't answer or comment
|
|
38
|
+
File browsing (every project; a project's .hitl/config.json and .hitlignore can add to these):
|
|
39
|
+
-x, --exclude <globs> never show, serve, search or zip these (comma-separated, repeatable)
|
|
40
|
+
--hide <names> exact names to hide everywhere (.git always is)
|
|
41
|
+
--show-secrets show .env*, *.key, *.pem and id_rsa-style files
|
|
42
|
+
--max-file-mb <n> larger files aren't shown as text or searched (default 5)
|
|
43
|
+
--no-git don't use git --no-watch no live updates
|
|
44
|
+
--author <name> the name on your notes (default: git user.name)
|
|
45
|
+
|
|
46
|
+
For agents (and you), in a project folder (or -C <folder>):
|
|
47
|
+
hitl status every review, question set and note, with counts
|
|
48
|
+
hitl news [--peek] [--ids a,b] everything the human said since you last read (marks it read)
|
|
49
|
+
hitl wait [--any|--done|--all|--blocking] [ids…] [--timeout 30m] [--settle 30s]
|
|
50
|
+
exit once the human has said something, printing the news
|
|
51
|
+
hitl show <path…> [--title t] [--intro md] [--note md] [--mode review|show] [--to <review>]
|
|
52
|
+
[--lines 10-40] [--against HEAD] [--lang ts --name x.ts] ("-" reads stdin as code or text)
|
|
53
|
+
put files (or stdin) in front of the human; prints the link
|
|
54
|
+
hitl approve "<question>" [path…] [--details md] [--timeout 10m]
|
|
55
|
+
ask yes or no; exits 0 approved, 1 refused or changes asked, 2 no answer yet
|
|
56
|
+
hitl feedback <review> [--new|--all|--open] [--peek] [--json]
|
|
57
|
+
hitl ask <set> --file <questions.json> add questions to a set (or write .hitl/questions/<set>.json)
|
|
58
|
+
hitl show-set <set> [--full] a question set, one line per question
|
|
59
|
+
hitl answers <set> [--new|--all|--unacked|--changed|--blocking|--open] [--ids a,b] [--peek] [--json]
|
|
60
|
+
hitl notes [--new|--open|--all] [--peek] notes the human wrote on files, lines, pictures
|
|
61
|
+
hitl reply <set> <question> <message…> write on a question's thread
|
|
62
|
+
hitl reply <review> <comment> <message…> reply to a review comment (c12)
|
|
63
|
+
hitl reply --note <id> <message…> reply to a note
|
|
64
|
+
hitl ack <set|review> <id…> --note "…" answers / verdicts dealt with (the human sees the note)
|
|
65
|
+
hitl resolve [<review>] <id…> --note "…" resolve review comments, or notes (no review), saying what you did
|
|
66
|
+
hitl close <review|set> close it (it drops to the bottom of the list)
|
|
67
|
+
hitl decisions [words…] [--limit n] what the human decided before, newest first
|
|
68
|
+
hitl validate [id|file…] check question sets and reviews against their formats
|
|
69
|
+
hitl schema [questions|reviews] the formats, as JSON Schema
|
|
70
|
+
hitl url [review|set] links to the page (starts the hub if needed)
|
|
71
|
+
|
|
72
|
+
Setup:
|
|
73
|
+
hitl init [--hook] [--stop-hook] [--no-mcp] [--no-claude-md]
|
|
74
|
+
set up the folder, the MCP server for Claude Code, and a note in CLAUDE.md
|
|
75
|
+
--hook: tell Claude about news whenever you send it a message
|
|
76
|
+
--stop-hook: don't let Claude finish while a blocking question or approval is waiting
|
|
77
|
+
hitl hooks [on|off] [--stop] turn those hooks on or off later
|
|
78
|
+
hitl import [folder] bring in .questionme/ (questions, answers) and .review/ (notes)
|
|
79
|
+
hitl stop stop the hub
|
|
80
|
+
hitl projects [--forget <folder>] the folders the hub serves
|
|
81
|
+
hitl notify [ntfy [url]|webhook <url>|link <url>|test|off]
|
|
82
|
+
push notifications to your phone (the ntfy app, or a webhook)
|
|
83
|
+
hitl hook [prompt|stop] the Claude Code hooks themselves (prompt: news; stop: the gate)
|
|
84
|
+
hitl mcp the MCP server, on stdio
|
|
85
|
+
|
|
86
|
+
Everything lives in <project>/.hitl/: questions/, reviews/, answers/, notes/, media/.`;
|
|
87
|
+
function tildeOf(p) {
|
|
88
|
+
return p.startsWith(os.homedir()) ? '~' + p.slice(os.homedir().length) : p;
|
|
89
|
+
}
|
|
90
|
+
function fail(msg, code = 1) {
|
|
91
|
+
console.error(`hitl: ${msg}`);
|
|
92
|
+
process.exit(code);
|
|
93
|
+
}
|
|
94
|
+
const VALUED = new Set([
|
|
95
|
+
'--base', '-p', '--port', '-H', '--host', '--token', '--allow-host', '-C', '--folder', '--timeout', '--settle', '--ids', '--section', '--limit', '--offset', '--note', '--forget', '--questions',
|
|
96
|
+
'-x', '--exclude', '--hide', '--max-file-mb', '--author', '--title', '--intro', '--mode', '--to', '--lines', '--against', '--lang', '--name', '--details', '--file', '--from',
|
|
97
|
+
]);
|
|
98
|
+
const BOOLEAN = new Set([
|
|
99
|
+
'-h', '--help', '-v', '--version', '--solo', '--background', '--local', '--no-token', '-o', '--open', '--read-only', '--json', '--full', '--peek', '--all', '--new', '--unacked', '--changed',
|
|
100
|
+
'--blocking', '--any', '--done', '--quiet', '--hook', '--stop-hook', '--stop', '--no-mcp', '--no-claude-md', '--show-secrets', '--no-git', '--no-watch', '--open-only',
|
|
101
|
+
]);
|
|
102
|
+
const REPEAT = new Set(['--allow-host', '--ids', '-x', '--exclude', '--hide']);
|
|
103
|
+
const COMMANDS = new Set([
|
|
104
|
+
'serve', 'init', 'status', 'news', 'wait', 'show', 'review', 'approve', 'feedback', 'ask', 'show-set', 'answers', 'notes', 'reply', 'ack', 'resolve', 'close', 'decisions', 'validate', 'schema', 'url', 'stop',
|
|
105
|
+
'projects', 'notify', 'hook', 'hooks', 'import', 'mcp', 'help', 'list',
|
|
106
|
+
]);
|
|
107
|
+
function parse(argv) {
|
|
108
|
+
const flags = new Map();
|
|
109
|
+
const pos = [];
|
|
110
|
+
let cmd = '';
|
|
111
|
+
for (let i = 0; i < argv.length; i++) {
|
|
112
|
+
const a = argv[i] ?? '';
|
|
113
|
+
if (a === '--') {
|
|
114
|
+
pos.push(...argv.slice(i + 1));
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
if (a.startsWith('-') && a !== '-') {
|
|
118
|
+
const eq = a.startsWith('--') ? a.indexOf('=') : -1;
|
|
119
|
+
const name = eq > 0 ? a.slice(0, eq) : a;
|
|
120
|
+
if (VALUED.has(name)) {
|
|
121
|
+
const v = eq > 0 ? a.slice(eq + 1) : argv[++i];
|
|
122
|
+
if (v === undefined)
|
|
123
|
+
fail(`${name} needs a value`);
|
|
124
|
+
const prev = flags.get(name);
|
|
125
|
+
flags.set(name, typeof prev === 'string' && REPEAT.has(name) ? `${prev},${v}` : v);
|
|
126
|
+
}
|
|
127
|
+
else if (BOOLEAN.has(name))
|
|
128
|
+
flags.set(name, true);
|
|
129
|
+
else
|
|
130
|
+
fail(`unknown option ${name} (hitl --help lists every command and option)`);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (!cmd && !pos.length && COMMANDS.has(a))
|
|
134
|
+
cmd = a;
|
|
135
|
+
else
|
|
136
|
+
pos.push(a);
|
|
137
|
+
}
|
|
138
|
+
return { cmd: cmd || 'serve', pos, flags };
|
|
139
|
+
}
|
|
140
|
+
const args = parse(process.argv.slice(2));
|
|
141
|
+
const flag = (...names) => names.some((n) => args.flags.has(n));
|
|
142
|
+
const value = (...names) => {
|
|
143
|
+
for (const n of names) {
|
|
144
|
+
const v = args.flags.get(n);
|
|
145
|
+
if (typeof v === 'string')
|
|
146
|
+
return v;
|
|
147
|
+
}
|
|
148
|
+
return undefined;
|
|
149
|
+
};
|
|
150
|
+
const folder = () => path.resolve(value('-C', '--folder') ?? process.cwd());
|
|
151
|
+
const project = () => openProject(folder());
|
|
152
|
+
const need = (v, what) => v || fail(`which ${what}? (see hitl --help)`);
|
|
153
|
+
if (flag('-h', '--help') || args.cmd === 'help') {
|
|
154
|
+
console.log(HELP);
|
|
155
|
+
process.exit(0);
|
|
156
|
+
}
|
|
157
|
+
if (flag('-v', '--version')) {
|
|
158
|
+
console.log(version);
|
|
159
|
+
process.exit(0);
|
|
160
|
+
}
|
|
161
|
+
async function run() {
|
|
162
|
+
switch (args.cmd) {
|
|
163
|
+
case 'serve':
|
|
164
|
+
return serve();
|
|
165
|
+
case 'mcp':
|
|
166
|
+
return serveMcp();
|
|
167
|
+
case 'init':
|
|
168
|
+
return init();
|
|
169
|
+
case 'status':
|
|
170
|
+
case 'list':
|
|
171
|
+
if (flag('--json')) {
|
|
172
|
+
const p = project();
|
|
173
|
+
return void console.log(JSON.stringify({ folder: p.root, reviews: p.reviews.summaries(), questions: p.questions.summaries(), notes: { open: p.notes.all().filter((c) => c.status === 'open').length, total: p.notes.all().length }, news: newsCounts(p) }, null, 2));
|
|
174
|
+
}
|
|
175
|
+
return void console.log(overviewText(project()));
|
|
176
|
+
case 'news':
|
|
177
|
+
return void console.log(news(project(), { peek: flag('--peek'), ids: list(value('--ids')) }).text);
|
|
178
|
+
case 'wait':
|
|
179
|
+
return wait();
|
|
180
|
+
case 'show':
|
|
181
|
+
case 'review':
|
|
182
|
+
return show();
|
|
183
|
+
case 'approve':
|
|
184
|
+
return approve();
|
|
185
|
+
case 'feedback': {
|
|
186
|
+
const filter = flag('--all') ? 'all' : flag('--open') ? 'open' : 'new';
|
|
187
|
+
const r = readFeedback(project().reviews, need(args.pos[0], 'review'), { filter, peek: flag('--peek') });
|
|
188
|
+
return void console.log(flag('--json') ? JSON.stringify(r.data, null, 2) : r.text);
|
|
189
|
+
}
|
|
190
|
+
case 'ask':
|
|
191
|
+
return ask();
|
|
192
|
+
case 'show-set':
|
|
193
|
+
return void console.log(describeSet(project().questions, need(args.pos[0], 'set'), { full: flag('--full'), section: value('--section'), limit: num(value('--limit')), offset: num(value('--offset')), ids: list(value('--ids')) }));
|
|
194
|
+
case 'answers': {
|
|
195
|
+
const filter = (FILTERS.find((f) => flag('--' + f)) ?? 'new');
|
|
196
|
+
const r = readAnswers(project().questions, need(args.pos[0], 'set'), {
|
|
197
|
+
filter,
|
|
198
|
+
ids: list(value('--ids')),
|
|
199
|
+
section: value('--section'),
|
|
200
|
+
limit: num(value('--limit')),
|
|
201
|
+
offset: num(value('--offset')),
|
|
202
|
+
peek: flag('--peek'),
|
|
203
|
+
questions: (['none', 'short', 'full'].includes(value('--questions') ?? '') ? value('--questions') : undefined),
|
|
204
|
+
});
|
|
205
|
+
return void console.log(flag('--json') ? JSON.stringify(r.data, null, 2) : r.text);
|
|
206
|
+
}
|
|
207
|
+
case 'notes': {
|
|
208
|
+
const f = flag('--open') ? 'open' : flag('--all') ? 'all' : 'new';
|
|
209
|
+
const r = readNotes(project().notes, { filter: f, peek: flag('--peek') });
|
|
210
|
+
return void console.log(r.count ? r.text : f === 'new' ? 'No new notes.' : `No ${f} notes.`);
|
|
211
|
+
}
|
|
212
|
+
case 'reply':
|
|
213
|
+
return reply();
|
|
214
|
+
case 'ack': {
|
|
215
|
+
const [id, ...ids] = args.pos;
|
|
216
|
+
const p = project();
|
|
217
|
+
const target = need(id, 'set or review');
|
|
218
|
+
const done = p.reviews.has(target) ? p.reviews.ack(target, ids, value('--note') ?? '') : p.questions.ack(target, ids, value('--note') ?? '');
|
|
219
|
+
return void console.log(done.length ? `Marked ${done.length} done: ${done.join(', ')}` : 'None of those have answers or verdicts.');
|
|
220
|
+
}
|
|
221
|
+
case 'resolve': {
|
|
222
|
+
const p = project();
|
|
223
|
+
const [first, ...rest] = args.pos;
|
|
224
|
+
const status = flag('--open') ? 'open' : 'resolved';
|
|
225
|
+
const done = first && p.reviews.has(first) ? p.reviews.setStatus(first, rest, status, 'agent', value('--note') ?? '') : p.notes.resolve(args.pos, value('--note') ?? '', status);
|
|
226
|
+
return void console.log(done.length ? `${status === 'open' ? 'Reopened' : 'Resolved'} ${done.join(', ')}.` : 'None of those ids were found.');
|
|
227
|
+
}
|
|
228
|
+
case 'close': {
|
|
229
|
+
const p = project();
|
|
230
|
+
const id = need(args.pos[0], 'review or set');
|
|
231
|
+
if (p.reviews.has(id))
|
|
232
|
+
p.reviews.update(id, { status: 'closed' });
|
|
233
|
+
else if (p.questions.has(id))
|
|
234
|
+
p.questions.updateSet(id, { status: 'closed' });
|
|
235
|
+
else
|
|
236
|
+
fail(`no review or question set "${id}"`);
|
|
237
|
+
return void console.log(`Closed ${id}.`);
|
|
238
|
+
}
|
|
239
|
+
case 'decisions':
|
|
240
|
+
return void console.log(decisionsText(project(), { q: args.pos.join(' '), limit: num(value('--limit')) ?? 100 }));
|
|
241
|
+
case 'validate':
|
|
242
|
+
return validate();
|
|
243
|
+
case 'schema': {
|
|
244
|
+
const which = args.pos[0] ?? 'questions';
|
|
245
|
+
if (which !== 'questions' && which !== 'reviews')
|
|
246
|
+
fail('hitl schema questions | reviews');
|
|
247
|
+
return void console.log(JSON.stringify(which === 'reviews' ? reviewSchema : setSchema, null, 2));
|
|
248
|
+
}
|
|
249
|
+
case 'url': {
|
|
250
|
+
const root = folder();
|
|
251
|
+
if (fs.existsSync(path.join(root, '.hitl')))
|
|
252
|
+
register(root);
|
|
253
|
+
const hub = await ensureHub();
|
|
254
|
+
const key = keyed(projects()).find((k) => same(k.root, root))?.key;
|
|
255
|
+
const id = args.pos[0];
|
|
256
|
+
const p = project();
|
|
257
|
+
const where = id ? (p.reviews.has(id) ? `/r/${encodeURIComponent(id)}` : `/q/${encodeURIComponent(id)}`) : '';
|
|
258
|
+
const hash = key ? `#/p/${encodeURIComponent(key)}${where}` : '';
|
|
259
|
+
for (const l of links(hub.port, hub.host, hub.token, hash, hub.base))
|
|
260
|
+
console.log(l);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
case 'stop': {
|
|
264
|
+
const hub = await runningHub();
|
|
265
|
+
if (!hub)
|
|
266
|
+
return void console.log('The hitl hub is not running.');
|
|
267
|
+
try {
|
|
268
|
+
process.kill(hub.pid);
|
|
269
|
+
console.log(`Stopped the hitl hub (pid ${hub.pid}, port ${hub.port}).`);
|
|
270
|
+
}
|
|
271
|
+
catch (e) {
|
|
272
|
+
fail(`couldn't stop pid ${hub.pid}: ${e instanceof Error ? e.message : String(e)}`);
|
|
273
|
+
}
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
case 'projects': {
|
|
277
|
+
const f = value('--forget');
|
|
278
|
+
if (f)
|
|
279
|
+
return void console.log(forget(path.resolve(f)) ? `Forgot ${path.resolve(f)}.` : `${path.resolve(f)} wasn't in the list.`);
|
|
280
|
+
const all = keyed(projects());
|
|
281
|
+
if (!all.length)
|
|
282
|
+
return void console.log(`No projects yet (${tildeOf(projectsPath())}).`);
|
|
283
|
+
for (const p of all) {
|
|
284
|
+
const v = fs.existsSync(p.root) ? openProject(p.root) : undefined;
|
|
285
|
+
const n = v ? `${v.reviews.ids().length} reviews, ${v.questions.ids().length} question sets` : '(missing)';
|
|
286
|
+
console.log(`${p.key.padEnd(20)} ${tildeOf(p.root)} ${n}`);
|
|
287
|
+
}
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
case 'hook':
|
|
291
|
+
return hook();
|
|
292
|
+
case 'hooks':
|
|
293
|
+
return hooks();
|
|
294
|
+
case 'notify':
|
|
295
|
+
return notify();
|
|
296
|
+
case 'import':
|
|
297
|
+
return importOld();
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
// ── serve ──
|
|
301
|
+
async function serve() {
|
|
302
|
+
const cfg = userConfig();
|
|
303
|
+
const solo = flag('--solo');
|
|
304
|
+
const background = flag('--background');
|
|
305
|
+
const local = flag('--local');
|
|
306
|
+
const port = num(value('-p', '--port') ?? process.env['HITL_PORT']) ?? cfg.port ?? PORT;
|
|
307
|
+
const host = value('-H', '--host') ?? process.env['HITL_HOST'] ?? (local ? '127.0.0.1' : (cfg.host ?? '0.0.0.0'));
|
|
308
|
+
const token = flag('--no-token') || (local && !value('--token')) ? '' : (value('--token') ?? process.env['HITL_TOKEN'] ?? (cfg.token === false ? '' : (cfg.token ?? hubToken())));
|
|
309
|
+
const allowHosts = [...(cfg.allowHosts ?? []), ...list(value('--allow-host'))];
|
|
310
|
+
const base = normaliseBase(value('--base') ?? process.env['HITL_BASE'] ?? cfg.base ?? '/', value('--base') ? 'command line' : process.env['HITL_BASE'] ? 'HITL_BASE' : 'config.json');
|
|
311
|
+
const root = path.resolve(args.pos[0] ?? value('-C', '--folder') ?? process.cwd());
|
|
312
|
+
const overrides = {};
|
|
313
|
+
if (value('-x', '--exclude'))
|
|
314
|
+
overrides.exclude = list(value('-x', '--exclude'));
|
|
315
|
+
if (value('--hide'))
|
|
316
|
+
overrides.hide = list(value('--hide'));
|
|
317
|
+
if (flag('--show-secrets'))
|
|
318
|
+
overrides.showSecrets = true;
|
|
319
|
+
if (value('--max-file-mb'))
|
|
320
|
+
overrides.maxFileMb = num(value('--max-file-mb')) ?? 5;
|
|
321
|
+
if (flag('--no-git'))
|
|
322
|
+
overrides.git = false;
|
|
323
|
+
if (flag('--no-watch'))
|
|
324
|
+
overrides.watch = false;
|
|
325
|
+
if (value('--author'))
|
|
326
|
+
overrides.author = value('--author') ?? '';
|
|
327
|
+
if (flag('--read-only'))
|
|
328
|
+
overrides.readOnly = true;
|
|
329
|
+
if (!solo) {
|
|
330
|
+
if (args.pos[0] || !isHome(root))
|
|
331
|
+
register(root);
|
|
332
|
+
const running = await runningHub();
|
|
333
|
+
if (running) {
|
|
334
|
+
if (background)
|
|
335
|
+
return;
|
|
336
|
+
const key = keyed(projects()).find((k) => same(k.root, root))?.key;
|
|
337
|
+
console.log(`hitl is already running (pid ${running.pid}):`);
|
|
338
|
+
for (const l of links(running.port, running.host, running.token, key ? `#/p/${encodeURIComponent(key)}` : '', running.base))
|
|
339
|
+
console.log(' ' + l);
|
|
340
|
+
if ((running.base ?? '/') !== base)
|
|
341
|
+
console.log(` (it serves under ${running.base ?? '/'}, not ${base}: hitl stop, then start it again to change that)`);
|
|
342
|
+
console.log(' (hitl stop to stop it)');
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
const time = () => new Date().toLocaleTimeString();
|
|
347
|
+
const served = solo ? () => [{ key: keyFor(root), root }] : () => keyed(projects()).filter((p) => fs.existsSync(p.root));
|
|
348
|
+
let listening = port;
|
|
349
|
+
const views = new Map();
|
|
350
|
+
const viewOf = (key) => {
|
|
351
|
+
const p = served().find((x) => x.key === key);
|
|
352
|
+
if (!p)
|
|
353
|
+
return undefined;
|
|
354
|
+
let v = views.get(key);
|
|
355
|
+
if (!v || v.root !== path.resolve(p.root))
|
|
356
|
+
views.set(key, (v = openProject(p.root)));
|
|
357
|
+
return v;
|
|
358
|
+
};
|
|
359
|
+
const notifier = new Notifier(viewOf, () => listening, token, base, (line) => console.log(`[${time()}] ${line}`));
|
|
360
|
+
for (const p of served())
|
|
361
|
+
notifier.baseline(p.key);
|
|
362
|
+
const running = await start({
|
|
363
|
+
mode: solo ? 'solo' : 'hub',
|
|
364
|
+
projects: served,
|
|
365
|
+
...(solo ? {} : { watchList: projectsPath() }),
|
|
366
|
+
port,
|
|
367
|
+
host,
|
|
368
|
+
base,
|
|
369
|
+
token,
|
|
370
|
+
allowHosts,
|
|
371
|
+
settings: (r) => projectSettings(r, overrides),
|
|
372
|
+
onRequest: (key, c) => {
|
|
373
|
+
const v = viewOf(key);
|
|
374
|
+
if (c.side === 'human' && v) {
|
|
375
|
+
const n = newsCounts(v);
|
|
376
|
+
if (n.total)
|
|
377
|
+
console.log(`[${time()}] ${key}: ${n.total} new from the human (${[n.reviews && `${n.reviews} on reviews`, n.questions && `${n.questions} answers`, n.notes && `${n.notes} notes`].filter(Boolean).join(', ')}) → npx hitloop news`);
|
|
378
|
+
}
|
|
379
|
+
else if (c.side === 'agent')
|
|
380
|
+
console.log(`[${time()}] ${key}: ${c.kind === 'review' ? 'review' : 'questions'} ${c.id} changed`);
|
|
381
|
+
notifier.observe(key, c.kind, c.id);
|
|
382
|
+
},
|
|
383
|
+
onProject: (key) => notifier.baseline(key),
|
|
384
|
+
});
|
|
385
|
+
listening = running.port;
|
|
386
|
+
if (!solo)
|
|
387
|
+
writeHubState({ pid: process.pid, port: running.port, host, token, base, version, started: new Date().toISOString() });
|
|
388
|
+
const key = solo ? keyFor(root) : keyed(projects()).find((k) => same(k.root, root))?.key;
|
|
389
|
+
const hash = key ? `#/p/${encodeURIComponent(key)}` : '';
|
|
390
|
+
console.log(`hitl ${version} (Human In The Loop) — ${solo ? root : `${projects().length} project${projects().length === 1 ? '' : 's'} (${tildeOf(projectsPath())})`}`);
|
|
391
|
+
for (const l of links(running.port, host, token, hash, base))
|
|
392
|
+
console.log(' ' + l);
|
|
393
|
+
if (host === '127.0.0.1')
|
|
394
|
+
console.log(' (only this computer: drop --local to use it from your phone)');
|
|
395
|
+
if (!token && host !== '127.0.0.1')
|
|
396
|
+
console.log(' (no token: anyone on your network can open it)');
|
|
397
|
+
if (!background && fs.existsSync(path.join(root, '.hitl')))
|
|
398
|
+
console.log(' ' + overviewText(openProject(root)).split('\n').join('\n '));
|
|
399
|
+
const n = notifyConfig(cfg);
|
|
400
|
+
console.log(notifyEnabled(n) ? ` notifications: ${[n.ntfy ? 'ntfy' : '', n.webhook ? 'webhook' : ''].filter(Boolean).join(' + ')} (hitl notify test)` : ' notifications: off (hitl notify ntfy puts them on your phone)');
|
|
401
|
+
console.log(' every command and option: npx hitloop --help');
|
|
402
|
+
if (flag('-o', '--open'))
|
|
403
|
+
openBrowser(links(running.port, host, token, hash, base)[0] ?? '');
|
|
404
|
+
const stop = () => {
|
|
405
|
+
if (!solo)
|
|
406
|
+
clearHubState(process.pid);
|
|
407
|
+
void running.close().finally(() => process.exit(0));
|
|
408
|
+
setTimeout(() => process.exit(0), 1500).unref();
|
|
409
|
+
};
|
|
410
|
+
process.on('SIGINT', stop);
|
|
411
|
+
process.on('SIGTERM', stop);
|
|
412
|
+
process.on('exit', () => !solo && clearHubState(process.pid));
|
|
413
|
+
}
|
|
414
|
+
function keyFor(root) {
|
|
415
|
+
return (path
|
|
416
|
+
.basename(root)
|
|
417
|
+
.toLowerCase()
|
|
418
|
+
.replace(/[^a-z0-9._-]+/g, '-')
|
|
419
|
+
.replace(/^-+|-+$/g, '') || 'project');
|
|
420
|
+
}
|
|
421
|
+
const isHome = (p) => same(p, os.homedir());
|
|
422
|
+
function same(a, b) {
|
|
423
|
+
const n = (p) => (process.platform === 'win32' ? path.resolve(p).toLowerCase() : path.resolve(p));
|
|
424
|
+
return n(a) === n(b);
|
|
425
|
+
}
|
|
426
|
+
function openBrowser(url) {
|
|
427
|
+
const cmd = process.platform === 'win32' ? 'cmd' : process.platform === 'darwin' ? 'open' : 'xdg-open';
|
|
428
|
+
const a = process.platform === 'win32' ? ['/c', 'start', '', url.replace(/&/g, '^&')] : [url];
|
|
429
|
+
spawn(cmd, a, { stdio: 'ignore', detached: true, windowsHide: true }).on('error', () => undefined).unref();
|
|
430
|
+
}
|
|
431
|
+
async function linkLines(root, where) {
|
|
432
|
+
register(root);
|
|
433
|
+
try {
|
|
434
|
+
const hub = await ensureHub();
|
|
435
|
+
const key = keyed(projects()).find((k) => same(k.root, root))?.key ?? '';
|
|
436
|
+
return links(hub.port, hub.host, hub.token, key ? `#/p/${encodeURIComponent(key)}${where}` : '', hub.base);
|
|
437
|
+
}
|
|
438
|
+
catch (e) {
|
|
439
|
+
return [`(couldn't start the hitl server: ${e instanceof Error ? e.message : String(e)})`];
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
// ── show / approve / ask ──
|
|
443
|
+
function readStdin() {
|
|
444
|
+
try {
|
|
445
|
+
return fs.readFileSync(0, 'utf8');
|
|
446
|
+
}
|
|
447
|
+
catch {
|
|
448
|
+
return '';
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
function itemsFromArgs() {
|
|
452
|
+
const note = value('--note');
|
|
453
|
+
const items = [];
|
|
454
|
+
for (const a of args.pos.slice(args.cmd === 'approve' ? 1 : 0)) {
|
|
455
|
+
if (a === '-') {
|
|
456
|
+
const text = readStdin();
|
|
457
|
+
const lang = value('--lang');
|
|
458
|
+
const name = value('--name');
|
|
459
|
+
items.push(lang || name ? { code: text, ...(lang ? { lang } : {}), ...(name ? { name } : {}) } : /^(---|diff --git) /m.test(text) && /^@@ /m.test(text) ? { diff: text } : { text });
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
if (/^https?:\/\//.test(a)) {
|
|
463
|
+
items.push(/\.(png|jpe?g|gif|webp|avif|svg)(\?|$)/i.test(a) ? { image: a } : { link: a });
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
const abs = path.resolve(a);
|
|
467
|
+
if (!fs.existsSync(abs))
|
|
468
|
+
fail(`no such file: ${a}`);
|
|
469
|
+
const rel = path.relative(folder(), abs).split(path.sep).join('/');
|
|
470
|
+
if (rel.startsWith('..'))
|
|
471
|
+
fail(`${a} is outside the project (${folder()})`);
|
|
472
|
+
if (fs.statSync(abs).isDirectory())
|
|
473
|
+
fail(`${a} is a folder: name the files (or use a glob your shell expands)`);
|
|
474
|
+
items.push({ file: rel, ...(value('--lines') ? { lines: value('--lines') } : {}), ...(value('--against') ? { against: value('--against') } : {}) });
|
|
475
|
+
}
|
|
476
|
+
if (note && items[0])
|
|
477
|
+
items[0].note = note;
|
|
478
|
+
return items;
|
|
479
|
+
}
|
|
480
|
+
async function show() {
|
|
481
|
+
const p = project();
|
|
482
|
+
const items = itemsFromArgs();
|
|
483
|
+
if (!items.length)
|
|
484
|
+
fail('what should it show? hitl show <path…> (or - for stdin)');
|
|
485
|
+
const to = value('--to');
|
|
486
|
+
const from = value('--from') ?? defaultFrom(p.root);
|
|
487
|
+
let id;
|
|
488
|
+
if (to) {
|
|
489
|
+
const r = p.reviews.addItems(to, items);
|
|
490
|
+
id = to;
|
|
491
|
+
console.log(`Added ${r.ids.length} to "${to}": ${r.ids.join(', ')}`);
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
const mode = value('--mode') === 'show' || (args.cmd === 'show' && value('--mode') !== 'review') ? 'show' : 'review';
|
|
495
|
+
const r = p.reviews.create({ title: value('--title') ?? (items.length === 1 ? (items[0]?.file ?? 'Review') : `${items.length} things to review`), ...(value('--intro') ? { intro: value('--intro') } : {}), mode, ...(from ? { from } : {}) }, items);
|
|
496
|
+
id = r.id;
|
|
497
|
+
console.log(`Made ${mode === 'show' ? 'a showing' : 'a review'} "${r.id}": ${r.ids.join(', ')}`);
|
|
498
|
+
for (const e of [...r.normalised.errors, ...r.normalised.warnings])
|
|
499
|
+
console.log(' ' + e);
|
|
500
|
+
}
|
|
501
|
+
for (const l of await linkLines(p.root, `/r/${encodeURIComponent(id)}`))
|
|
502
|
+
console.log(' ' + l);
|
|
503
|
+
console.log(`Read what they say: npx hitloop feedback ${id} (wait: npx hitloop wait --done ${id})`);
|
|
504
|
+
}
|
|
505
|
+
async function approve() {
|
|
506
|
+
const p = project();
|
|
507
|
+
const question = need(args.pos[0], 'question');
|
|
508
|
+
const from = value('--from') ?? defaultFrom(p.root);
|
|
509
|
+
const r = p.reviews.create({ title: question, ...(value('--details') ? { intro: value('--details') } : {}), mode: 'approve', ...(from ? { from } : {}) }, itemsFromArgs());
|
|
510
|
+
console.log(`Asked "${r.id}": ${question}`);
|
|
511
|
+
for (const l of await linkLines(p.root, `/r/${encodeURIComponent(r.id)}`))
|
|
512
|
+
console.log(' ' + l);
|
|
513
|
+
const ctl = new AbortController();
|
|
514
|
+
process.on('SIGINT', () => ctl.abort());
|
|
515
|
+
const ok = await waitFor(p, { until: 'done', ids: [r.id], timeoutMs: parseDuration(value('--timeout'), 0), signal: ctl.signal });
|
|
516
|
+
const fb = p.reviews.feedback(r.id);
|
|
517
|
+
if (!ok || !fb.overall) {
|
|
518
|
+
console.log(`No answer yet. Wait again: npx hitloop wait --done ${r.id}`);
|
|
519
|
+
process.exit(2);
|
|
520
|
+
}
|
|
521
|
+
p.reviews.markRead(r.id);
|
|
522
|
+
const v = fb.overall.verdict;
|
|
523
|
+
console.log(`${v === 'ok' ? 'APPROVED' : v === 'no' ? 'REFUSED' : 'NOT YET: changes asked for'}${fb.overall.note ? ` — "${fb.overall.note}"` : ''}`);
|
|
524
|
+
process.exit(v === 'ok' ? 0 : 1);
|
|
525
|
+
}
|
|
526
|
+
async function ask() {
|
|
527
|
+
const p = project();
|
|
528
|
+
const set = need(args.pos[0], 'set');
|
|
529
|
+
if (p.reviews.has(set))
|
|
530
|
+
fail(`"${set}" is a review's id; pick another set id`);
|
|
531
|
+
const file = value('--file');
|
|
532
|
+
const raw = file === '-' || !file ? readStdin() : fs.readFileSync(file, 'utf8');
|
|
533
|
+
let data;
|
|
534
|
+
try {
|
|
535
|
+
data = JSON.parse(raw);
|
|
536
|
+
}
|
|
537
|
+
catch (e) {
|
|
538
|
+
fail(`not JSON: ${e instanceof Error ? e.message : String(e)}`);
|
|
539
|
+
}
|
|
540
|
+
const qs = Array.isArray(data) ? data : data && typeof data === 'object' && Array.isArray(data.questions) ? data.questions : [data];
|
|
541
|
+
const from = value('--from') ?? defaultFrom(p.root);
|
|
542
|
+
const { ids, normalised } = p.questions.addQuestions(set, qs, value('--section'), { title: value('--title'), from });
|
|
543
|
+
console.log(`Added ${ids.length} question${ids.length === 1 ? '' : 's'} to "${set}": ${ids.join(', ')}`);
|
|
544
|
+
for (const e of [...normalised.errors, ...normalised.warnings])
|
|
545
|
+
console.log(' ' + e);
|
|
546
|
+
for (const l of await linkLines(p.root, `/q/${encodeURIComponent(set)}`))
|
|
547
|
+
console.log(' ' + l);
|
|
548
|
+
}
|
|
549
|
+
function reply() {
|
|
550
|
+
const p = project();
|
|
551
|
+
const note = value('--note');
|
|
552
|
+
if (note) {
|
|
553
|
+
const c = p.notes.reply(note, 'agent', args.pos.join(' '), 'agent');
|
|
554
|
+
return void console.log(c ? `Replied on note ${note}.` : `No note "${note}".`);
|
|
555
|
+
}
|
|
556
|
+
const [target, id, ...words] = args.pos;
|
|
557
|
+
const t = need(target, 'set or review');
|
|
558
|
+
const msg = words.join(' ');
|
|
559
|
+
if (!msg)
|
|
560
|
+
fail('what should the reply say?');
|
|
561
|
+
if (p.reviews.has(t)) {
|
|
562
|
+
if (id && /^c\d+$/.test(id))
|
|
563
|
+
p.reviews.reply(t, id, 'agent', msg);
|
|
564
|
+
else
|
|
565
|
+
p.reviews.comment(t, { item: id ?? '', body: msg }, 'agent');
|
|
566
|
+
}
|
|
567
|
+
else
|
|
568
|
+
p.questions.message(t, need(id, 'question'), 'agent', msg);
|
|
569
|
+
console.log(`Replied on ${id}.`);
|
|
570
|
+
}
|
|
571
|
+
// ── wait ──
|
|
572
|
+
async function wait() {
|
|
573
|
+
const p = project();
|
|
574
|
+
const until = flag('--done') ? 'done' : flag('--all') ? 'all' : flag('--blocking') ? 'blocking' : 'any';
|
|
575
|
+
const ids = [...args.pos, ...list(value('--ids'))];
|
|
576
|
+
const timeoutMs = parseDuration(value('--timeout'), 0);
|
|
577
|
+
const settleMs = parseDuration(value('--settle'), until === 'any' ? 30_000 : 0);
|
|
578
|
+
const ctl = new AbortController();
|
|
579
|
+
process.on('SIGINT', () => ctl.abort());
|
|
580
|
+
process.on('SIGTERM', () => ctl.abort());
|
|
581
|
+
if (!flag('--quiet'))
|
|
582
|
+
console.log(`Waiting for ${until === 'any' ? 'anything new' : until === 'done' ? 'the human to finish' : until === 'all' ? 'every item and question' : 'the blocking questions'}${ids.length ? ` on ${ids.join(', ')}` : ' in ' + p.root}…`);
|
|
583
|
+
const ok = await waitFor(p, { until, ids, timeoutMs, settleMs, signal: ctl.signal });
|
|
584
|
+
if (!ok) {
|
|
585
|
+
console.log(ctl.signal.aborted ? 'Stopped waiting.' : `Still waiting after ${value('--timeout')}.`);
|
|
586
|
+
console.log(news(p, { peek: true, ids }).text);
|
|
587
|
+
process.exit(2);
|
|
588
|
+
}
|
|
589
|
+
console.log('The human has spoken. New since you last read:');
|
|
590
|
+
console.log(news(p, { peek: flag('--peek'), ids }).text);
|
|
591
|
+
}
|
|
592
|
+
// ── validate ──
|
|
593
|
+
function validate() {
|
|
594
|
+
const p = project();
|
|
595
|
+
const targets = args.pos.length ? args.pos : [...p.questions.ids(), ...p.reviews.ids()];
|
|
596
|
+
if (!targets.length)
|
|
597
|
+
return void console.log(`Nothing in ${path.join(p.root, '.hitl')} to check.`);
|
|
598
|
+
let bad = 0;
|
|
599
|
+
for (const t of targets) {
|
|
600
|
+
const isFile = t.endsWith('.json') && fs.existsSync(t);
|
|
601
|
+
const id = isFile ? path.basename(t, '.json') : t;
|
|
602
|
+
let errors = [], warnings = [], what = '';
|
|
603
|
+
const asReview = isFile ? /[\\/]reviews[\\/]/.test(path.resolve(t)) || (() => { try {
|
|
604
|
+
return 'items' in JSON.parse(fs.readFileSync(t, 'utf8'));
|
|
605
|
+
}
|
|
606
|
+
catch {
|
|
607
|
+
return false;
|
|
608
|
+
} })() : p.reviews.has(id);
|
|
609
|
+
try {
|
|
610
|
+
if (asReview) {
|
|
611
|
+
const n = isFile ? normaliseReview(id, JSON.parse(fs.readFileSync(t, 'utf8'))) : p.reviews.load(id);
|
|
612
|
+
if (!n)
|
|
613
|
+
throw new Error('no such review');
|
|
614
|
+
({ errors, warnings } = n);
|
|
615
|
+
what = `review, ${n.review.items.length} items`;
|
|
616
|
+
}
|
|
617
|
+
else {
|
|
618
|
+
const n = isFile ? normaliseSet(id, JSON.parse(fs.readFileSync(t, 'utf8'))) : p.questions.load(id);
|
|
619
|
+
if (!n)
|
|
620
|
+
throw new Error('no such question set or review');
|
|
621
|
+
({ errors, warnings } = n);
|
|
622
|
+
what = `questions, ${n.set.sections.reduce((s, x) => s + x.questions.length, 0)} of them`;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
catch (e) {
|
|
626
|
+
errors = [e instanceof Error ? e.message : String(e)];
|
|
627
|
+
}
|
|
628
|
+
console.log(`${errors.length ? '✗' : '✓'} ${id}${what ? ` (${what})` : ''}: ${errors.length} errors, ${warnings.length} warnings`);
|
|
629
|
+
for (const e of errors)
|
|
630
|
+
console.log(' error: ' + e);
|
|
631
|
+
for (const w of warnings)
|
|
632
|
+
console.log(' warning: ' + w);
|
|
633
|
+
if (errors.length)
|
|
634
|
+
bad++;
|
|
635
|
+
}
|
|
636
|
+
if (bad)
|
|
637
|
+
process.exit(1);
|
|
638
|
+
}
|
|
639
|
+
// ── notify ──
|
|
640
|
+
async function notify() {
|
|
641
|
+
const [what, arg] = args.pos;
|
|
642
|
+
const hubPort = () => readHub()?.port ?? PORT;
|
|
643
|
+
const show = () => {
|
|
644
|
+
const n = notifyConfig();
|
|
645
|
+
console.log(`Notifications (${tildeOf(userConfigPath())}):`);
|
|
646
|
+
console.log(` ntfy: ${n.ntfy ?? '(off)'}`);
|
|
647
|
+
console.log(` webhook: ${n.webhook ?? '(off)'}`);
|
|
648
|
+
console.log(` links: ${linkBase(n, hubPort(), '', readHub()?.base)} (hitl notify link <url> to change)`);
|
|
649
|
+
console.log(` batched: sent once agents have been quiet for ${n.quietSeconds ?? 20} s (approvals: at once)`);
|
|
650
|
+
if (!notifyEnabled(n))
|
|
651
|
+
console.log('\nTurn them on: hitl notify ntfy (then subscribe in the ntfy app)');
|
|
652
|
+
};
|
|
653
|
+
const test = async () => {
|
|
654
|
+
const n = notifyConfig();
|
|
655
|
+
if (!notifyEnabled(n))
|
|
656
|
+
fail('notifications are off: hitl notify ntfy (or webhook <url>) first');
|
|
657
|
+
await send(n, { title: 'hitl: notifications work', message: "You'll get one when an agent asks you something, shows you something, or waits for your OK.", url: linkBase(n, hubPort(), '', readHub()?.base), project: '', request: '', kind: 'test', newQuestions: 0, newItems: 0, approval: false, blocking: 0, replies: 0 });
|
|
658
|
+
console.log('Sent a test notification.');
|
|
659
|
+
};
|
|
660
|
+
switch (what) {
|
|
661
|
+
case undefined:
|
|
662
|
+
return show();
|
|
663
|
+
case 'ntfy': {
|
|
664
|
+
const url = arg ?? `https://ntfy.sh/hitl-${crypto.randomBytes(9).toString('base64url').toLowerCase().replace(/[^a-z0-9]/g, '')}`;
|
|
665
|
+
if (!/^https?:\/\/[^/]+\/[^/]+/.test(url))
|
|
666
|
+
fail('an ntfy topic URL looks like https://ntfy.sh/your-topic');
|
|
667
|
+
updateUserConfig((c) => (c.notify = { ...c.notify, ntfy: url }));
|
|
668
|
+
console.log(`✓ ntfy topic: ${url}`);
|
|
669
|
+
console.log(' On your phone: install the ntfy app (Android or iOS), tap +, and subscribe to');
|
|
670
|
+
console.log(` the topic "${new URL(url).pathname.slice(1)}"${new URL(url).host === 'ntfy.sh' ? '' : ` on the server ${new URL(url).origin}`}.`);
|
|
671
|
+
console.log(' Anyone who knows the topic can read it: notifications say what is waiting, never your answers.');
|
|
672
|
+
return test();
|
|
673
|
+
}
|
|
674
|
+
case 'webhook':
|
|
675
|
+
if (!arg || !/^https?:\/\//.test(arg))
|
|
676
|
+
fail('hitl notify webhook <https://…>');
|
|
677
|
+
updateUserConfig((c) => (c.notify = { ...c.notify, webhook: arg }));
|
|
678
|
+
console.log(`✓ webhook: ${arg}`);
|
|
679
|
+
return test();
|
|
680
|
+
case 'link':
|
|
681
|
+
if (!arg || !/^https?:\/\//.test(arg))
|
|
682
|
+
fail('hitl notify link <http://host:port> (where your phone opens hitl)');
|
|
683
|
+
updateUserConfig((c) => (c.notify = { ...c.notify, link: arg }));
|
|
684
|
+
return show();
|
|
685
|
+
case 'test':
|
|
686
|
+
return test();
|
|
687
|
+
case 'off':
|
|
688
|
+
updateUserConfig((c) => {
|
|
689
|
+
if (c.notify) {
|
|
690
|
+
delete c.notify.ntfy;
|
|
691
|
+
delete c.notify.webhook;
|
|
692
|
+
}
|
|
693
|
+
});
|
|
694
|
+
return void console.log('Notifications are off.');
|
|
695
|
+
}
|
|
696
|
+
fail('hitl notify [ntfy [url]|webhook <url>|link <url>|test|off]');
|
|
697
|
+
}
|
|
698
|
+
function readHub() {
|
|
699
|
+
try {
|
|
700
|
+
return JSON.parse(fs.readFileSync(path.join(configDir(), 'hub.json'), 'utf8'));
|
|
701
|
+
}
|
|
702
|
+
catch {
|
|
703
|
+
return undefined;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
// ── hooks for Claude Code (opt-in) ──
|
|
707
|
+
/** `hitl hook` (or `hook prompt`): news for Claude when you send a message. `hook stop`: the gate before it finishes. */
|
|
708
|
+
function hook() {
|
|
709
|
+
const p = project();
|
|
710
|
+
if (!fs.existsSync(path.join(p.root, '.hitl')))
|
|
711
|
+
return;
|
|
712
|
+
if (args.pos[0] === 'stop')
|
|
713
|
+
return stopHook(p);
|
|
714
|
+
const c = newsCounts(p);
|
|
715
|
+
if (!c.total)
|
|
716
|
+
return;
|
|
717
|
+
const parts = [c.reviews && `${c.reviews} on reviews`, c.questions && `${c.questions} answers`, c.notes && `${c.notes} notes`].filter(Boolean);
|
|
718
|
+
console.log(`hitl: the human has said ${c.total} new thing${c.total === 1 ? '' : 's'} (${parts.join(', ')}). Read them with the news tool or \`npx hitloop news\`.`);
|
|
719
|
+
}
|
|
720
|
+
function stopHook(p) {
|
|
721
|
+
let input = {};
|
|
722
|
+
try {
|
|
723
|
+
input = JSON.parse(readStdin() || '{}');
|
|
724
|
+
}
|
|
725
|
+
catch {
|
|
726
|
+
input = {};
|
|
727
|
+
}
|
|
728
|
+
// already kept going once because of this hook: let it stop, so it can't loop
|
|
729
|
+
if (input.stop_hook_active)
|
|
730
|
+
return;
|
|
731
|
+
const reasons = [];
|
|
732
|
+
const c = newsCounts(p);
|
|
733
|
+
if (c.total)
|
|
734
|
+
reasons.push(`the human said ${c.total} new thing${c.total === 1 ? '' : 's'} you haven't read (npx hitloop news)`);
|
|
735
|
+
const gates = p.reviews.summaries().filter((s) => s.mode === 'approve' && s.status === 'open' && !s.done);
|
|
736
|
+
if (gates.length)
|
|
737
|
+
reasons.push(`${gates.length} approval${gates.length === 1 ? ' is' : 's are'} still waiting (${gates.map((g) => g.id).join(', ')}): wait for ${gates.length === 1 ? 'it' : 'them'} with \`npx hitloop wait --done ${gates.map((g) => g.id).join(' ')}\` before doing what needs the OK`);
|
|
738
|
+
const blocking = p.questions.summaries().filter((s) => s.status === 'open' && s.blocking > 0);
|
|
739
|
+
if (blocking.length)
|
|
740
|
+
reasons.push(`blocking questions are unanswered in ${blocking.map((s) => s.id).join(', ')}: wait with \`npx hitloop wait --blocking ${blocking.map((s) => s.id).join(' ')}\``);
|
|
741
|
+
if (!reasons.length)
|
|
742
|
+
return;
|
|
743
|
+
console.log(JSON.stringify({ decision: 'block', reason: `hitl: before you finish, ${reasons.join('; and ')}.` }));
|
|
744
|
+
}
|
|
745
|
+
function hooks() {
|
|
746
|
+
const root = folder();
|
|
747
|
+
const what = args.pos[0];
|
|
748
|
+
if (what !== 'on' && what !== 'off') {
|
|
749
|
+
const h = hookStatus(root);
|
|
750
|
+
console.log(`News hook: ${h.prompt ? 'on' : 'off'}`);
|
|
751
|
+
console.log(`Stop hook: ${h.stop ? 'on' : 'off'}`);
|
|
752
|
+
console.log('Turn on: hitl hooks on [--stop] off: hitl hooks off [--stop]');
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
const on = what === 'on';
|
|
756
|
+
for (const l of setHooks(root, flag('--stop') ? { stop: on } : { prompt: on }))
|
|
757
|
+
console.log(l);
|
|
758
|
+
}
|
|
759
|
+
// ── init ──
|
|
760
|
+
const MARK_START = '<!-- hitl -->';
|
|
761
|
+
const MARK_END = '<!-- /hitl -->';
|
|
762
|
+
const SNIPPET = `${MARK_START}
|
|
763
|
+
## The human in the loop: hitl
|
|
764
|
+
|
|
765
|
+
Use hitl (Human In The Loop: the hitl MCP tools, or \`npx hitloop\`) to put things in front of the user on their phone
|
|
766
|
+
or computer instead of asking in chat:
|
|
767
|
+
|
|
768
|
+
- **More than about 4 questions**, or ones that need thought or pictures: \`add_questions\` (each with options, one
|
|
769
|
+
recommended, and \`why\`; mark what you can't go on without as \`blocking\`).
|
|
770
|
+
- **Something to look at or review** (files, diffs, screenshots, mockups): \`show\` (mode "review" asks a verdict per
|
|
771
|
+
item). Each call makes its own review.
|
|
772
|
+
- **A yes or no before something risky** (deploy, delete, spend): \`approve\`; it waits for the answer.
|
|
773
|
+
- Give the user the link the tool returns. Keep working on what doesn't depend on the answer; to wait, run
|
|
774
|
+
\`npx hitloop wait --any\` (or \`--done <id>\`) in the background.
|
|
775
|
+
- Read what they said with \`news\` (or \`npx hitloop news\`): answers, verdicts, comments on lines and pictures, and notes
|
|
776
|
+
they wrote on files. Then \`ack\` / \`resolve\` with a short note. Put their words into docs word for word.
|
|
777
|
+
- Check \`decisions\` before asking something they may have decided already. Say who's asking with \`from\`.
|
|
778
|
+
- \`npx hitloop --help\` lists every command and option; check it rather than guessing at flags.
|
|
779
|
+
${MARK_END}`;
|
|
780
|
+
function init() {
|
|
781
|
+
const root = path.resolve(args.pos[0] ?? value('-C', '--folder') ?? process.cwd());
|
|
782
|
+
const dir = path.join(root, '.hitl');
|
|
783
|
+
for (const d of ['questions', 'reviews', 'answers'])
|
|
784
|
+
fs.mkdirSync(path.join(dir, d), { recursive: true });
|
|
785
|
+
ensureReadme(dir);
|
|
786
|
+
register(root);
|
|
787
|
+
console.log(`✓ ${tildeOf(dir)}: questions/, reviews/, answers/ (the hub serves this folder now)`);
|
|
788
|
+
if (!flag('--no-claude-md')) {
|
|
789
|
+
const candidates = ['CLAUDE.md', 'AGENTS.md'].map((f) => path.join(root, f));
|
|
790
|
+
const file = candidates.find((f) => fs.existsSync(f)) ?? path.join(root, 'CLAUDE.md');
|
|
791
|
+
const text = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
|
|
792
|
+
const re = new RegExp(`${MARK_START}[\\s\\S]*?${MARK_END}`);
|
|
793
|
+
const next = re.test(text) ? text.replace(re, SNIPPET) : (text.trimEnd() ? text.trimEnd() + '\n\n' : '') + SNIPPET + '\n';
|
|
794
|
+
if (next !== text) {
|
|
795
|
+
fs.writeFileSync(file, next);
|
|
796
|
+
console.log(`✓ ${path.basename(file)}: how to use hitl ${re.test(text) ? 'updated' : 'added'}`);
|
|
797
|
+
}
|
|
798
|
+
else
|
|
799
|
+
console.log(`✓ ${path.basename(file)}: already says how to use hitl`);
|
|
800
|
+
}
|
|
801
|
+
const l = launcher();
|
|
802
|
+
if (!flag('--no-mcp')) {
|
|
803
|
+
const shell = process.platform === 'win32';
|
|
804
|
+
const has = spawnSync('claude', ['mcp', 'get', 'hitl'], { stdio: 'pipe', shell, timeout: 30_000 });
|
|
805
|
+
if (has.error || has.status === null)
|
|
806
|
+
console.log(`- Claude Code not found. Add the MCP server yourself: claude mcp add --scope user hitl -- ${l.line} mcp`);
|
|
807
|
+
else if (has.status === 0)
|
|
808
|
+
console.log('✓ MCP server: hitl is already set up in Claude Code');
|
|
809
|
+
else {
|
|
810
|
+
const add = spawnSync('claude', ['mcp', 'add', '--scope', 'user', 'hitl', '--', ...[l.command, ...l.args, 'mcp'].map((a) => (shell && /\s/.test(a) ? `"${a}"` : a))], { stdio: 'pipe', shell, timeout: 30_000 });
|
|
811
|
+
if (add.status === 0)
|
|
812
|
+
console.log('✓ MCP server: added to Claude Code for all your projects (restart Claude Code to load it)');
|
|
813
|
+
else
|
|
814
|
+
console.log(`✗ MCP server: claude mcp add failed (${String(add.stderr || add.stdout).trim()}). Run: claude mcp add --scope user hitl -- ${l.line} mcp`);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
if (flag('--hook') || flag('--stop-hook'))
|
|
818
|
+
for (const line of setHooks(root, { ...(flag('--hook') ? { prompt: true } : {}), ...(flag('--stop-hook') ? { stop: true } : {}) }))
|
|
819
|
+
console.log(line);
|
|
820
|
+
else
|
|
821
|
+
console.log('- Hooks: off (hitl hooks on: news when you send a message; hitl hooks on --stop: a gate before Claude finishes)');
|
|
822
|
+
if (fs.existsSync(path.join(root, '.questionme')) || fs.existsSync(path.join(root, '.review')))
|
|
823
|
+
console.log('- Found .questionme/ or .review/: hitl import brings them in');
|
|
824
|
+
console.log('\nOpen it on your phone: npx hitloop (or: hitl url)');
|
|
825
|
+
}
|
|
826
|
+
// ── import: questionme and folderview folders ──
|
|
827
|
+
function importOld() {
|
|
828
|
+
const root = path.resolve(args.pos[0] ?? value('-C', '--folder') ?? process.cwd());
|
|
829
|
+
const dir = path.join(root, '.hitl');
|
|
830
|
+
let n = 0;
|
|
831
|
+
const copyDir = (from, to, filter) => {
|
|
832
|
+
if (!fs.existsSync(from))
|
|
833
|
+
return;
|
|
834
|
+
fs.mkdirSync(to, { recursive: true });
|
|
835
|
+
for (const f of fs.readdirSync(from)) {
|
|
836
|
+
if (!filter(f))
|
|
837
|
+
continue;
|
|
838
|
+
const dest = path.join(to, f);
|
|
839
|
+
if (fs.existsSync(dest)) {
|
|
840
|
+
console.log(` skipped ${path.relative(root, dest)} (already there)`);
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
fs.copyFileSync(path.join(from, f), dest);
|
|
844
|
+
n++;
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
const qm = path.join(root, '.questionme');
|
|
848
|
+
copyDir(path.join(qm, 'sets'), path.join(dir, 'questions'), (f) => f.endsWith('.json'));
|
|
849
|
+
copyDir(path.join(qm, 'answers'), path.join(dir, 'answers'), (f) => f.endsWith('.json') || f.endsWith('.md'));
|
|
850
|
+
const rv = path.join(root, '.review');
|
|
851
|
+
const old = path.join(rv, 'comments.json');
|
|
852
|
+
if (fs.existsSync(old)) {
|
|
853
|
+
const notesDir = path.join(dir, 'notes');
|
|
854
|
+
const target = path.join(notesDir, 'notes.json');
|
|
855
|
+
let mine = {};
|
|
856
|
+
try {
|
|
857
|
+
mine = JSON.parse(fs.readFileSync(target, 'utf8'));
|
|
858
|
+
}
|
|
859
|
+
catch {
|
|
860
|
+
mine = {};
|
|
861
|
+
}
|
|
862
|
+
const theirs = JSON.parse(fs.readFileSync(old, 'utf8')).comments ?? [];
|
|
863
|
+
const have = new Set((mine.comments ?? []).map((c) => c.id));
|
|
864
|
+
const add = theirs.filter((c) => !have.has(c.id));
|
|
865
|
+
fs.mkdirSync(notesDir, { recursive: true });
|
|
866
|
+
const comments = [...(mine.comments ?? []), ...add];
|
|
867
|
+
fs.writeFileSync(target, JSON.stringify({ version: 1, seq: (mine.seq ?? 0) + add.length, readSeq: mine.readSeq ?? 0, comments }, null, 2) + '\n');
|
|
868
|
+
copyDir(path.join(rv, 'images'), path.join(notesDir, 'images'), () => true);
|
|
869
|
+
n += add.length;
|
|
870
|
+
console.log(` ${add.length} note${add.length === 1 ? '' : 's'} from .review/comments.json`);
|
|
871
|
+
}
|
|
872
|
+
ensureReadme(dir);
|
|
873
|
+
register(root);
|
|
874
|
+
console.log(n ? `✓ Imported ${n} file${n === 1 ? '' : 's'} and notes into ${tildeOf(dir)}. The old folders are untouched: delete them when you're happy.` : 'Nothing to import (no .questionme/ or .review/ here).');
|
|
875
|
+
}
|
|
876
|
+
// ── small helpers ──
|
|
877
|
+
function num(v) {
|
|
878
|
+
if (v === undefined)
|
|
879
|
+
return undefined;
|
|
880
|
+
const n = Number(v);
|
|
881
|
+
if (!Number.isFinite(n))
|
|
882
|
+
fail(`not a number: ${v}`);
|
|
883
|
+
return n;
|
|
884
|
+
}
|
|
885
|
+
function list(v) {
|
|
886
|
+
return (v ?? '')
|
|
887
|
+
.split(',')
|
|
888
|
+
.map((s) => s.trim())
|
|
889
|
+
.filter(Boolean);
|
|
890
|
+
}
|
|
891
|
+
// run last, once every helper above is defined
|
|
892
|
+
try {
|
|
893
|
+
await run();
|
|
894
|
+
}
|
|
895
|
+
catch (e) {
|
|
896
|
+
fail(e instanceof StoreError || e instanceof Error ? e.message : String(e));
|
|
897
|
+
}
|