@zuzuucodes/cli 1.4.0 → 1.6.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/bin/zuzuu.mjs +20 -4
- package/package.json +1 -1
- package/web-app/dist/auth.js +91 -0
- package/web-app/dist/server.js +16 -79
- package/web-app/dist/zuzuu-cli.js +124 -0
- package/web-app/dist/{zuzuu-api.js → zuzuu-routes.js} +158 -133
- package/web-app/web-dist/assets/CommandPalette-DhBdR7X3.js +45 -0
- package/web-app/web-dist/assets/DiffTab-CqxwSjI2.js +1 -0
- package/web-app/web-dist/assets/EditorPane-94QPFR9R.js +41 -0
- package/web-app/web-dist/assets/MonacoFile-D76epTrG.js +1 -0
- package/web-app/web-dist/assets/angular-html-BVBpGdXr.js +1 -0
- package/web-app/web-dist/assets/{angular-ts-CD_OonCa.js → angular-ts-BfdufMKP.js} +1 -1
- package/web-app/web-dist/assets/{apl-uOGC3x4e.js → apl-DWBSSoBH.js} +1 -1
- package/web-app/web-dist/assets/{astro-B6ybQmWG.js → astro-3LtMP0Sq.js} +1 -1
- package/web-app/web-dist/assets/{blade-B1QGRlVx.js → blade-llJRbbtR.js} +1 -1
- package/web-app/web-dist/assets/c-Wt1voDr2.js +1 -0
- package/web-app/web-dist/assets/{cobol-BgqgtYWn.js → cobol-x_HIyl2P.js} +1 -1
- package/web-app/web-dist/assets/{coffee-0wIRKYlr.js → coffee-CThvmt4R.js} +1 -1
- package/web-app/web-dist/assets/cpp-NtAeskI3.js +1 -0
- package/web-app/web-dist/assets/{crystal-CyTK3qFN.js → crystal-DNu_sX0G.js} +1 -1
- package/web-app/web-dist/assets/css-DJp_X0uY.js +1 -0
- package/web-app/web-dist/assets/{cssMode-B9jnrWOz.js → cssMode-ByQBaInt.js} +1 -1
- package/web-app/web-dist/assets/dist-DQqjtuhV.js +153 -0
- package/web-app/web-dist/assets/{edge-CvML9pwC.js → edge-ozw5tpLl.js} +1 -1
- package/web-app/web-dist/assets/{editor.api2-BmGoRSl4.js → editor.api2-C7skgoRB.js} +1 -1
- package/web-app/web-dist/assets/{elixir-CrjqTiSc.js → elixir-VhA6FeZt.js} +1 -1
- package/web-app/web-dist/assets/{elm-C4JtJ0Au.js → elm-dREJmIFz.js} +1 -1
- package/web-app/web-dist/assets/{erb-Cmeb-29V.js → erb-CIg6G69l.js} +1 -1
- package/web-app/web-dist/assets/{freemarker2-B5LAi19B.js → freemarker2-CBBwP9JV.js} +1 -1
- package/web-app/web-dist/assets/{git-rebase-CXqdToiP.js → git-rebase-B44mJPta.js} +1 -1
- package/web-app/web-dist/assets/{glimmer-js-Kq-kdTyV.js → glimmer-js-vH_gHG0-.js} +1 -1
- package/web-app/web-dist/assets/{glimmer-ts-D0RKLJNf.js → glimmer-ts--abOzSAQ.js} +1 -1
- package/web-app/web-dist/assets/glsl-Dv5r7kPw.js +1 -0
- package/web-app/web-dist/assets/graphql-CB4jsw2E.js +1 -0
- package/web-app/web-dist/assets/{hack-trjVF3Po.js → hack-DvEYX148.js} +1 -1
- package/web-app/web-dist/assets/haml-zE6W3STP.js +1 -0
- package/web-app/web-dist/assets/{handlebars-B8_x7Zx7.js → handlebars-CzBR2SDs.js} +1 -1
- package/web-app/web-dist/assets/{handlebars-g7ZhGhI_.js → handlebars-tXdfxEd6.js} +1 -1
- package/web-app/web-dist/assets/html-C8UlPnhE.js +1 -0
- package/web-app/web-dist/assets/{html-CfvRMgoC.js → html-DgPn1QYH.js} +1 -1
- package/web-app/web-dist/assets/{html-derivative-BYX_F_XH.js → html-derivative-CY6NRz-J.js} +1 -1
- package/web-app/web-dist/assets/{htmlMode-Bi8vSvwb.js → htmlMode-BtdIDgA2.js} +1 -1
- package/web-app/web-dist/assets/{http-BIVDpHT-.js → http-Cyd7bS_S.js} +1 -1
- package/web-app/web-dist/assets/{hurl-CFsshMju.js → hurl-CWPsiEpf.js} +1 -1
- package/web-app/web-dist/assets/index-B27_WOhS.css +2 -0
- package/web-app/web-dist/assets/index-De6DWTZM.js +7 -0
- package/web-app/web-dist/assets/java-CGc3VwQr.js +1 -0
- package/web-app/web-dist/assets/{javascript-Bxx2wV4w.js → javascript-5m05n-Be.js} +1 -1
- package/web-app/web-dist/assets/javascript-CUt1pgmJ.js +1 -0
- package/web-app/web-dist/assets/{jinja-_ZS5zWwe.js → jinja-CD-Z-FLd.js} +1 -1
- package/web-app/web-dist/assets/{jison-D8mMEpcs.js → jison-imPNup1l.js} +1 -1
- package/web-app/web-dist/assets/json-Bg9ijW3F.js +1 -0
- package/web-app/web-dist/assets/{jsonMode-C6ELX5GM.js → jsonMode-BG32YnTY.js} +1 -1
- package/web-app/web-dist/assets/jsx-CY6oMTks.js +1 -0
- package/web-app/web-dist/assets/{julia-D4h2DZrs.js → julia-Dc3O-irA.js} +1 -1
- package/web-app/web-dist/assets/{just-bMqQi3xg.js → just-BhOq_Kbv.js} +1 -1
- package/web-app/web-dist/assets/{latex-DThYi3CX.js → latex-Cu4Y1d5w.js} +1 -1
- package/web-app/web-dist/assets/lib-KIOQTlcs.js +1 -0
- package/web-app/web-dist/assets/{liquid-CUjzzP4r.js → liquid-3ZnQzTbs.js} +1 -1
- package/web-app/web-dist/assets/{liquid-CesB-zzl.js → liquid-CvXMrjlQ.js} +1 -1
- package/web-app/web-dist/assets/{lspLanguageFeatures-gTnJsses.js → lspLanguageFeatures-6KXALSrl.js} +1 -1
- package/web-app/web-dist/assets/lua-BjLEUjKY.js +1 -0
- package/web-app/web-dist/assets/{marko-yoGoLK2m.js → marko-DvhNOisQ.js} +1 -1
- package/web-app/web-dist/assets/{mdc-BvtXU6eH.js → mdc-Bm9TpL1X.js} +1 -1
- package/web-app/web-dist/assets/{mdx-DrXGQbNB.js → mdx-DffTEkNE.js} +1 -1
- package/web-app/web-dist/assets/{monaco-setup-CsR6EfHe.js → monaco-setup-DM3A5_VI.js} +3 -3
- package/web-app/web-dist/assets/{nginx-DoUz032F.js → nginx-Bhc82uuv.js} +1 -1
- package/web-app/web-dist/assets/{nim-B0Pl8B4R.js → nim-DXTVBFnF.js} +1 -1
- package/web-app/web-dist/assets/{perl-D2tfAALb.js → perl-C7veXV9z.js} +1 -1
- package/web-app/web-dist/assets/{php-BImCcX5X.js → php-BRiuMnnr.js} +1 -1
- package/web-app/web-dist/assets/{pug-BcnpC8P_.js → pug-C5hz5LQ7.js} +1 -1
- package/web-app/web-dist/assets/{python-ypRCBnvu.js → python-DyLAD3Wt.js} +1 -1
- package/web-app/web-dist/assets/{qml-DFDAunHY.js → qml-BdUV3aTS.js} +1 -1
- package/web-app/web-dist/assets/r-8R7vtdQc.js +1 -0
- package/web-app/web-dist/assets/{razor-aqrhpwqZ.js → razor-C49xQTPQ.js} +1 -1
- package/web-app/web-dist/assets/{razor-1_376SZM.js → razor-DRL52XO2.js} +1 -1
- package/web-app/web-dist/assets/react-vendor-CCIEwYL0.js +9 -0
- package/web-app/web-dist/assets/regexp-Omp9DhTb.js +1 -0
- package/web-app/web-dist/assets/{rst-2vG6f11Y.js → rst-BHX71KW9.js} +1 -1
- package/web-app/web-dist/assets/{ruby-Dj6bCFXR.js → ruby-B--HzjGU.js} +1 -1
- package/web-app/web-dist/assets/{sas-BhVZ4qL2.js → sas-DrLaYOK_.js} +1 -1
- package/web-app/web-dist/assets/scss-DdSxiZKl.js +1 -0
- package/web-app/web-dist/assets/shellscript-DwcUjJBL.js +1 -0
- package/web-app/web-dist/assets/{shellsession-CyO2fnhB.js → shellsession-CPZkydE6.js} +1 -1
- package/web-app/web-dist/assets/{soy-DIkw6E88.js → soy-Br5FhD7c.js} +1 -1
- package/web-app/web-dist/assets/sql-DNssxck8.js +1 -0
- package/web-app/web-dist/assets/{stata-DvkM932O.js → stata-DXn1tqOr.js} +1 -1
- package/web-app/web-dist/assets/{surrealql-B4-Q8tqV.js → surrealql-IeLNQw0f.js} +1 -1
- package/web-app/web-dist/assets/{svelte-p6yBy-Ki.js → svelte-DOdLCIlh.js} +1 -1
- package/web-app/web-dist/assets/{templ-C7EkuiZr.js → templ-CIwIngms.js} +1 -1
- package/web-app/web-dist/assets/{tex-DkmD8uFC.js → tex-D8QMumu5.js} +1 -1
- package/web-app/web-dist/assets/{ts-tags-U-hncHg4.js → ts-tags-BMVY4q-l.js} +1 -1
- package/web-app/web-dist/assets/{tsMode-a8OvovQd.js → tsMode-BndVBac5.js} +1 -1
- package/web-app/web-dist/assets/tsx-5Eka4NBX.js +1 -0
- package/web-app/web-dist/assets/{twig-CU0OP-IA.js → twig-C8o_5mgw.js} +1 -1
- package/web-app/web-dist/assets/{typescript-DnLjiKtn.js → typescript-B1w9vqKF.js} +1 -1
- package/web-app/web-dist/assets/typescript-DOu2WMV5.js +1 -0
- package/web-app/web-dist/assets/{vue-Db7nY3ba.js → vue-BU18DNDL.js} +1 -1
- package/web-app/web-dist/assets/{vue-html-BvAbiAw1.js → vue-html-BeluIYX0.js} +1 -1
- package/web-app/web-dist/assets/{vue-vine-BEaIQIlA.js → vue-vine-DGUAbOCX.js} +1 -1
- package/web-app/web-dist/assets/{xml-an4Nuuqq.js → xml-D8uAlVv5.js} +1 -1
- package/web-app/web-dist/assets/xml-DIqSwXR3.js +1 -0
- package/web-app/web-dist/assets/{xsl-D3NQgH22.js → xsl-Ct_-YIAy.js} +1 -1
- package/web-app/web-dist/assets/xterm-B1ffpRuj.js +36 -0
- package/web-app/web-dist/assets/xterm-addons-psDEiUMC.js +136 -0
- package/web-app/web-dist/assets/{yaml-Diiu6O9P.js → yaml-Bb7jXyQv.js} +1 -1
- package/web-app/web-dist/assets/yaml-DTtCYNlS.js +1 -0
- package/web-app/web-dist/index.html +6 -3
- package/zuzuu/actions/convert.mjs +10 -9
- package/zuzuu/actions/dispatch.mjs +12 -7
- package/zuzuu/actions/inbox.mjs +5 -5
- package/zuzuu/actions/manifest.mjs +48 -30
- package/zuzuu/actions/schema.mjs +9 -3
- package/zuzuu/actions/trail.mjs +1 -1
- package/zuzuu/commands/act-author.mjs +23 -13
- package/zuzuu/commands/act.mjs +4 -6
- package/zuzuu/commands/capture.mjs +2 -2
- package/zuzuu/commands/code.mjs +2 -2
- package/zuzuu/commands/digest.mjs +2 -2
- package/zuzuu/commands/distill.mjs +15 -16
- package/zuzuu/commands/doctor.mjs +41 -19
- package/zuzuu/commands/enable.mjs +1 -1
- package/zuzuu/commands/eval.mjs +3 -36
- package/zuzuu/commands/explain.mjs +4 -4
- package/zuzuu/commands/faculty.mjs +158 -0
- package/zuzuu/commands/generation.mjs +5 -8
- package/zuzuu/commands/hook.mjs +14 -12
- package/zuzuu/commands/inbox.mjs +1 -6
- package/zuzuu/commands/init.mjs +18 -4
- package/zuzuu/commands/knowledge.mjs +1 -1
- package/zuzuu/commands/migrations/home.mjs +96 -0
- package/zuzuu/commands/migrations/index.mjs +48 -0
- package/zuzuu/commands/migrations/items.mjs +360 -0
- package/zuzuu/commands/migrations/proposals.mjs +100 -0
- package/zuzuu/commands/proposals.mjs +131 -0
- package/zuzuu/commands/review.mjs +13 -227
- package/zuzuu/commands/session.mjs +8 -2
- package/zuzuu/commands/sessions.mjs +159 -0
- package/zuzuu/commands/status.mjs +3 -3
- package/zuzuu/commands/trace.mjs +1 -1
- package/zuzuu/{capture-core.mjs → core/capture-core.mjs} +3 -3
- package/zuzuu/{store.mjs → core/store.mjs} +1 -1
- package/zuzuu/digest/compose.mjs +96 -0
- package/zuzuu/eval/score.mjs +14 -1
- package/zuzuu/faculties/actions/index.mjs +283 -0
- package/zuzuu/faculties/guardrails/index.mjs +320 -0
- package/zuzuu/faculties/instructions/index.mjs +288 -0
- package/zuzuu/faculties/knowledge/index.mjs +185 -0
- package/zuzuu/faculties/memory/index.mjs +124 -0
- package/zuzuu/faculty/envelope.mjs +290 -0
- package/zuzuu/faculty/generation/read.mjs +206 -0
- package/zuzuu/faculty/generation/write.mjs +207 -0
- package/zuzuu/faculty/items.mjs +81 -0
- package/zuzuu/faculty/module.mjs +74 -0
- package/zuzuu/faculty/pending.mjs +63 -0
- package/zuzuu/faculty/registry.mjs +204 -18
- package/zuzuu/faculty/render.mjs +59 -0
- package/zuzuu/faculty/trail.mjs +1 -1
- package/zuzuu/guardrails/engine.mjs +137 -0
- package/zuzuu/{scaffold.mjs → home/scaffold.mjs} +110 -39
- package/zuzuu/knowledge/items.mjs +56 -91
- package/zuzuu/live/install.mjs +1 -1
- package/zuzuu/live/live-store.mjs +2 -2
- package/zuzuu/live/reconcile.mjs +2 -2
- package/zuzuu/sessions/git.mjs +47 -0
- package/zuzuu/{session-git.mjs → sessions/session-git.mjs} +5 -43
- package/web-app/web-dist/assets/DiffTab-BuWonUNJ.js +0 -1
- package/web-app/web-dist/assets/MonacoFile-CL3DhFKG.js +0 -1
- package/web-app/web-dist/assets/angular-html-CmT26mqM.js +0 -1
- package/web-app/web-dist/assets/c-BvoqrSVH.js +0 -1
- package/web-app/web-dist/assets/cpp-BXsk94m0.js +0 -1
- package/web-app/web-dist/assets/css-Z8oOGxII.js +0 -1
- package/web-app/web-dist/assets/dist-ChcDQ_7s.js +0 -153
- package/web-app/web-dist/assets/glsl-KwyfU2aa.js +0 -1
- package/web-app/web-dist/assets/graphql-DSeOUAa2.js +0 -1
- package/web-app/web-dist/assets/haml-azVoxQRV.js +0 -1
- package/web-app/web-dist/assets/html-D_7P5S4m.js +0 -1
- package/web-app/web-dist/assets/index--5yy8RbA.js +0 -267
- package/web-app/web-dist/assets/index-BVG4hgk7.css +0 -2
- package/web-app/web-dist/assets/java-D4RbCvBe.js +0 -1
- package/web-app/web-dist/assets/javascript-Cb010CKM.js +0 -1
- package/web-app/web-dist/assets/json-DWgqV4D1.js +0 -1
- package/web-app/web-dist/assets/jsx-CZjSJa1f.js +0 -1
- package/web-app/web-dist/assets/lua-TGj_6NzO.js +0 -1
- package/web-app/web-dist/assets/r-fCpuAR7u.js +0 -1
- package/web-app/web-dist/assets/regexp-B4yxx-Ty.js +0 -1
- package/web-app/web-dist/assets/scss-QdjMO_xV.js +0 -1
- package/web-app/web-dist/assets/shellscript-BnlgeVVx.js +0 -1
- package/web-app/web-dist/assets/sql-DGnQv6iD.js +0 -1
- package/web-app/web-dist/assets/tsx-MJ0-9sYG.js +0 -1
- package/web-app/web-dist/assets/typescript-C17ZkDe8.js +0 -1
- package/web-app/web-dist/assets/xml-CA9lHFQV.js +0 -1
- package/web-app/web-dist/assets/yaml-CwRYMJka.js +0 -1
- package/zuzuu/actions/adapter.mjs +0 -130
- package/zuzuu/commands/migrate.mjs +0 -225
- package/zuzuu/digest.mjs +0 -149
- package/zuzuu/faculty/generation.mjs +0 -392
- package/zuzuu/guardrails/adapter.mjs +0 -134
- package/zuzuu/guardrails.mjs +0 -89
- package/zuzuu/instructions/adapter.mjs +0 -93
- package/zuzuu/knowledge/adapter.mjs +0 -99
- package/zuzuu/memory/adapter.mjs +0 -121
- package/zuzuu/miners/actions.mjs +0 -118
- package/zuzuu/miners/guardrails.mjs +0 -179
- package/zuzuu/miners/instructions.mjs +0 -157
- package/zuzuu/miners/knowledge.mjs +0 -25
- package/zuzuu/miners/memory.mjs +0 -27
- package/zuzuu/miners/registry.mjs +0 -31
- /package/web-app/web-dist/assets/{chunk-QTnfLwEv.js → rolldown-runtime-QTnfLwEv.js} +0 -0
- /package/zuzuu/{session.mjs → core/session.mjs} +0 -0
- /package/zuzuu/{inject.mjs → home/inject.mjs} +0 -0
package/bin/zuzuu.mjs
CHANGED
|
@@ -21,11 +21,12 @@ import { doctor } from '../zuzuu/commands/doctor.mjs';
|
|
|
21
21
|
import { enable, disable } from '../zuzuu/commands/enable.mjs';
|
|
22
22
|
import { runHook } from '../zuzuu/commands/hook.mjs';
|
|
23
23
|
import { remember, recall, knowledge } from '../zuzuu/commands/knowledge.mjs';
|
|
24
|
-
import { review
|
|
24
|
+
import { review } from '../zuzuu/commands/review.mjs';
|
|
25
|
+
import { proposals } from '../zuzuu/commands/proposals.mjs';
|
|
25
26
|
import { distill } from '../zuzuu/commands/distill.mjs';
|
|
26
27
|
import { digest } from '../zuzuu/commands/digest.mjs';
|
|
27
28
|
import { act } from '../zuzuu/commands/act.mjs';
|
|
28
|
-
import { migrate } from '../zuzuu/commands/
|
|
29
|
+
import { migrate } from '../zuzuu/commands/migrations/index.mjs';
|
|
29
30
|
import { generation } from '../zuzuu/commands/generation.mjs';
|
|
30
31
|
import { evalCmd } from '../zuzuu/commands/eval.mjs';
|
|
31
32
|
import { code } from '../zuzuu/commands/code.mjs';
|
|
@@ -33,6 +34,8 @@ import { web } from '../zuzuu/commands/web.mjs';
|
|
|
33
34
|
import { explain } from '../zuzuu/commands/explain.mjs';
|
|
34
35
|
import { inbox } from '../zuzuu/commands/inbox.mjs';
|
|
35
36
|
import { session } from '../zuzuu/commands/session.mjs';
|
|
37
|
+
import { sessions } from '../zuzuu/commands/sessions.mjs';
|
|
38
|
+
import { faculty } from '../zuzuu/commands/faculty.mjs';
|
|
36
39
|
|
|
37
40
|
function parseArgs(argv) {
|
|
38
41
|
const a = { _: [] };
|
|
@@ -74,6 +77,13 @@ usage: zuzuu <command> [options]
|
|
|
74
77
|
recall "query" [--type t] [--attr k=v] [--related-to id] [--semantic]
|
|
75
78
|
search knowledge: lexical · graph · semantic
|
|
76
79
|
knowledge reindex|audit rebuild the search index · check registry/items health
|
|
80
|
+
faculty items <f> [--json|--jsonl]
|
|
81
|
+
list a faculty's envelope items (one doc · one line per item)
|
|
82
|
+
faculty schema <f> [--json]
|
|
83
|
+
print a faculty's payload schema (JSON-Schema subset)
|
|
84
|
+
faculty manifest <f> [--json]
|
|
85
|
+
print a faculty's module manifest (faculty.json)
|
|
86
|
+
faculty overview [--json] every faculty in one shot: ui + counts + top items + pending
|
|
77
87
|
digest [--json] [--budget N]
|
|
78
88
|
print the session-start grounding brief
|
|
79
89
|
act [list|show <slug>|new <slug>|schema <slug>]
|
|
@@ -94,8 +104,12 @@ usage: zuzuu <command> [options]
|
|
|
94
104
|
disable remove the background hooks
|
|
95
105
|
session [status|merge|continue|discard]
|
|
96
106
|
the invisible session branch (one per agent session)
|
|
107
|
+
sessions [--json] recorded sessions with lifecycle state labels
|
|
108
|
+
session inspect <id> [--json]
|
|
109
|
+
one session: trace summary + per-faculty mined signals
|
|
97
110
|
eval [--faculty f] rank pending proposals by eval score, highest first
|
|
98
|
-
migrate [--home]
|
|
111
|
+
migrate [--home|--items] one-time migrators: proposal schema · --home moves agent/ → .zuzuu/
|
|
112
|
+
· --items rewrites legacy faculty shapes → the envelope standard
|
|
99
113
|
doctor environment + session health (reconciles lost sessions)
|
|
100
114
|
explain [topic] the 5 faculties + how graduation works
|
|
101
115
|
version print version
|
|
@@ -117,7 +131,7 @@ switch (cmd) {
|
|
|
117
131
|
case 'knowledge': await knowledge(args); break;
|
|
118
132
|
case 'digest': digest(args); break;
|
|
119
133
|
case 'act': act(args); break;
|
|
120
|
-
case 'distill': distill(args); break;
|
|
134
|
+
case 'distill': await distill(args); break;
|
|
121
135
|
case 'inbox': inbox(args); break;
|
|
122
136
|
case 'review': await review(args); break;
|
|
123
137
|
case 'proposals': proposals(args); break;
|
|
@@ -128,6 +142,8 @@ switch (cmd) {
|
|
|
128
142
|
case 'disable': disable(args); break;
|
|
129
143
|
case 'hook': runHook(args._[0], { host: args.host, session: args.session }); break;
|
|
130
144
|
case 'session': session(args); break;
|
|
145
|
+
case 'sessions': sessions(args); break;
|
|
146
|
+
case 'faculty': faculty(args); break;
|
|
131
147
|
case 'eval': evalCmd(args); break;
|
|
132
148
|
case 'migrate': migrate(args); break;
|
|
133
149
|
case 'generation': generation(args); break;
|
package/package.json
CHANGED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// The daemon's auth + request-origin gates, extracted from server.ts:
|
|
2
|
+
// Host-header allowlist (DNS rebinding), Origin allowlist (cross-site WS
|
|
3
|
+
// hijacking / CSRF), and token-in-URL → HttpOnly cookie auth. One AuthGate
|
|
4
|
+
// instance guards both the Hono HTTP app and the WS upgrade path.
|
|
5
|
+
import crypto from "node:crypto";
|
|
6
|
+
import { getCookie, setCookie } from "hono/cookie";
|
|
7
|
+
const AUTH_COOKIE = "webcode_auth";
|
|
8
|
+
const COOKIE_MAX_AGE = 30 * 24 * 3600;
|
|
9
|
+
export class AuthGate {
|
|
10
|
+
authSessions = new Set();
|
|
11
|
+
allowedHosts;
|
|
12
|
+
allowedOrigins;
|
|
13
|
+
token;
|
|
14
|
+
constructor(cfg) {
|
|
15
|
+
this.token = cfg.token;
|
|
16
|
+
const hostNames = ["127.0.0.1", "localhost", "[::1]"];
|
|
17
|
+
this.allowedHosts = new Set(hostNames.flatMap((h) => [h, `${h}:${cfg.port}`]));
|
|
18
|
+
this.allowedOrigins = new Set([
|
|
19
|
+
...hostNames.map((h) => `http://${h}:${cfg.port}`),
|
|
20
|
+
...(cfg.extraOrigins ?? []),
|
|
21
|
+
]);
|
|
22
|
+
// hosted: also accept the public hostname (Fly's edge sets Host to it);
|
|
23
|
+
// Host/Origin defense stays on, just widened to the one public origin.
|
|
24
|
+
if (cfg.publicHost) {
|
|
25
|
+
this.allowedHosts.add(cfg.publicHost.toLowerCase());
|
|
26
|
+
this.allowedOrigins.add(`https://${cfg.publicHost}`);
|
|
27
|
+
this.allowedOrigins.add(`http://${cfg.publicHost}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Host allowlist defeats DNS rebinding: rebinding changes DNS, not the Host header. */
|
|
31
|
+
hostAllowed(host) {
|
|
32
|
+
return !!host && this.allowedHosts.has(host.toLowerCase());
|
|
33
|
+
}
|
|
34
|
+
/** Origin allowlist defeats cross-site WS hijacking / CSRF from arbitrary websites. */
|
|
35
|
+
originAllowed(origin) {
|
|
36
|
+
return origin === undefined || this.allowedOrigins.has(origin);
|
|
37
|
+
}
|
|
38
|
+
/** WS upgrade path: is the request's cookie an authenticated session? */
|
|
39
|
+
cookieAuthed(cookieHeader) {
|
|
40
|
+
if (!cookieHeader)
|
|
41
|
+
return false;
|
|
42
|
+
const match = /(?:^|;\s*)webcode_auth=([^;]+)/.exec(cookieHeader);
|
|
43
|
+
return !!match && this.authSessions.has(match[1]);
|
|
44
|
+
}
|
|
45
|
+
/** App-wide gate: Host/Origin allowlists + the ?token= → cookie exchange. */
|
|
46
|
+
gate() {
|
|
47
|
+
return async (c, next) => {
|
|
48
|
+
if (!this.hostAllowed(c.req.header("host"))) {
|
|
49
|
+
return c.text("forbidden host", 403);
|
|
50
|
+
}
|
|
51
|
+
if (!this.originAllowed(c.req.header("origin"))) {
|
|
52
|
+
return c.text("forbidden origin", 403);
|
|
53
|
+
}
|
|
54
|
+
// Token exchange: any page request carrying ?token= gets a cookie.
|
|
55
|
+
const token = c.req.query("token");
|
|
56
|
+
if (token && !c.req.path.startsWith("/api/")) {
|
|
57
|
+
if (!timingSafeEqualStr(token, this.token))
|
|
58
|
+
return c.text("invalid token", 403);
|
|
59
|
+
const secret = crypto.randomBytes(24).toString("base64url");
|
|
60
|
+
this.authSessions.add(secret);
|
|
61
|
+
setCookie(c, AUTH_COOKIE, secret, {
|
|
62
|
+
httpOnly: true,
|
|
63
|
+
sameSite: "Strict",
|
|
64
|
+
path: "/",
|
|
65
|
+
maxAge: COOKIE_MAX_AGE,
|
|
66
|
+
});
|
|
67
|
+
const url = new URL(c.req.url);
|
|
68
|
+
url.searchParams.delete("token");
|
|
69
|
+
// /auth?token=… exists so the Vite dev server can proxy the
|
|
70
|
+
// exchange; land on the app root afterwards either way.
|
|
71
|
+
const dest = url.pathname === "/auth" ? "/" : url.pathname + url.search;
|
|
72
|
+
return c.redirect(dest);
|
|
73
|
+
}
|
|
74
|
+
await next();
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** /api/* gate: only cookie-authenticated sessions pass. */
|
|
78
|
+
requireAuth() {
|
|
79
|
+
return async (c, next) => {
|
|
80
|
+
if (!this.authSessions.has(getCookie(c, AUTH_COOKIE) ?? "")) {
|
|
81
|
+
return c.json({ error: "unauthorized" }, 401);
|
|
82
|
+
}
|
|
83
|
+
await next();
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function timingSafeEqualStr(a, b) {
|
|
88
|
+
const ba = Buffer.from(a);
|
|
89
|
+
const bb = Buffer.from(b);
|
|
90
|
+
return ba.length === bb.length && crypto.timingSafeEqual(ba, bb);
|
|
91
|
+
}
|
package/web-app/dist/server.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import crypto from "node:crypto";
|
|
2
1
|
import fs from "node:fs";
|
|
3
2
|
import fsp from "node:fs/promises";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
import { Readable } from "node:stream";
|
|
6
5
|
import { Hono } from "hono";
|
|
7
|
-
import { getCookie, setCookie } from "hono/cookie";
|
|
8
6
|
import { serve } from "@hono/node-server";
|
|
9
7
|
import { WebSocketServer } from "ws";
|
|
10
8
|
import { execFile } from "node:child_process";
|
|
11
9
|
import { promisify } from "node:util";
|
|
12
10
|
import { SessionManager } from "./sessions.js";
|
|
11
|
+
import { AuthGate } from "./auth.js";
|
|
13
12
|
import { createFsApi } from "./fs-api.js";
|
|
14
|
-
import { createZuzuuApi
|
|
13
|
+
import { createZuzuuApi } from "./zuzuu-routes.js";
|
|
14
|
+
import { runZuzuuMut } from "./zuzuu-cli.js";
|
|
15
15
|
import { search } from "./search.js";
|
|
16
16
|
import { listFiles } from "./file-list.js";
|
|
17
17
|
import { listWorkflows, saveWorkflow } from "./workflows.js";
|
|
@@ -23,8 +23,6 @@ const execFileAsync = promisify(execFile);
|
|
|
23
23
|
import { handleTermSocket } from "./ws-term.js";
|
|
24
24
|
import { handleFsSocket } from "./ws-fs.js";
|
|
25
25
|
import { PathError, resolveSafe, safeJoin } from "./safe-path.js";
|
|
26
|
-
const AUTH_COOKIE = "webcode_auth";
|
|
27
|
-
const COOKIE_MAX_AGE = 30 * 24 * 3600;
|
|
28
26
|
/** Host CLIs an agent/command session may run. Argv-spawned, never a shell. */
|
|
29
27
|
const DEFAULT_COMMAND_ALLOWLIST = ["claude", "gemini", "codex", "pi", "opencode", "zuzuu"];
|
|
30
28
|
const STATIC_MIME = {
|
|
@@ -45,9 +43,7 @@ export class WebcodeServer {
|
|
|
45
43
|
/** mutable workspace root — switchable at runtime via switchTo() */
|
|
46
44
|
root;
|
|
47
45
|
startedAt = Date.now();
|
|
48
|
-
|
|
49
|
-
allowedHosts;
|
|
50
|
-
allowedOrigins;
|
|
46
|
+
auth;
|
|
51
47
|
commandAllowlist;
|
|
52
48
|
server = null;
|
|
53
49
|
constructor(cfg) {
|
|
@@ -55,19 +51,12 @@ export class WebcodeServer {
|
|
|
55
51
|
this.root = cfg.root;
|
|
56
52
|
this.commandAllowlist = new Set(cfg.commandAllowlist ?? DEFAULT_COMMAND_ALLOWLIST);
|
|
57
53
|
this.sessions = new SessionManager(cfg.root);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
...
|
|
62
|
-
...(cfg.
|
|
63
|
-
|
|
64
|
-
// hosted: also accept the public hostname (Fly's edge sets Host to it);
|
|
65
|
-
// Host/Origin defense stays on, just widened to the one public origin.
|
|
66
|
-
if (cfg.publicHost) {
|
|
67
|
-
this.allowedHosts.add(cfg.publicHost.toLowerCase());
|
|
68
|
-
this.allowedOrigins.add(`https://${cfg.publicHost}`);
|
|
69
|
-
this.allowedOrigins.add(`http://${cfg.publicHost}`);
|
|
70
|
-
}
|
|
54
|
+
this.auth = new AuthGate({
|
|
55
|
+
port: cfg.port,
|
|
56
|
+
token: cfg.token,
|
|
57
|
+
...(cfg.extraOrigins !== undefined ? { extraOrigins: cfg.extraOrigins } : {}),
|
|
58
|
+
...(cfg.publicHost !== undefined ? { publicHost: cfg.publicHost } : {}),
|
|
59
|
+
});
|
|
71
60
|
this.app = this.buildApp();
|
|
72
61
|
}
|
|
73
62
|
/**
|
|
@@ -101,60 +90,13 @@ export class WebcodeServer {
|
|
|
101
90
|
return { cliAbsent: true };
|
|
102
91
|
return { ok: false, ...(r.stderr !== undefined ? { stderr: r.stderr } : {}), ...(r.data !== undefined ? { refusal: r.data } : {}) };
|
|
103
92
|
}
|
|
104
|
-
// ── security gates ─────────────────────────────────────────────────
|
|
105
|
-
/** Host allowlist defeats DNS rebinding: rebinding changes DNS, not the Host header. */
|
|
106
|
-
hostAllowed(host) {
|
|
107
|
-
return !!host && this.allowedHosts.has(host.toLowerCase());
|
|
108
|
-
}
|
|
109
|
-
/** Origin allowlist defeats cross-site WS hijacking / CSRF from arbitrary websites. */
|
|
110
|
-
originAllowed(origin) {
|
|
111
|
-
return origin === undefined || this.allowedOrigins.has(origin);
|
|
112
|
-
}
|
|
113
|
-
cookieAuthed(cookieHeader) {
|
|
114
|
-
if (!cookieHeader)
|
|
115
|
-
return false;
|
|
116
|
-
const match = /(?:^|;\s*)webcode_auth=([^;]+)/.exec(cookieHeader);
|
|
117
|
-
return !!match && this.authSessions.has(match[1]);
|
|
118
|
-
}
|
|
119
93
|
// ── HTTP app ───────────────────────────────────────────────────────
|
|
120
94
|
buildApp() {
|
|
121
95
|
const { cfg } = this;
|
|
122
96
|
const app = new Hono();
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
if (!this.originAllowed(c.req.header("origin"))) {
|
|
128
|
-
return c.text("forbidden origin", 403);
|
|
129
|
-
}
|
|
130
|
-
// Token exchange: any page request carrying ?token= gets a cookie.
|
|
131
|
-
const token = c.req.query("token");
|
|
132
|
-
if (token && !c.req.path.startsWith("/api/")) {
|
|
133
|
-
if (!timingSafeEqualStr(token, cfg.token))
|
|
134
|
-
return c.text("invalid token", 403);
|
|
135
|
-
const secret = crypto.randomBytes(24).toString("base64url");
|
|
136
|
-
this.authSessions.add(secret);
|
|
137
|
-
setCookie(c, AUTH_COOKIE, secret, {
|
|
138
|
-
httpOnly: true,
|
|
139
|
-
sameSite: "Strict",
|
|
140
|
-
path: "/",
|
|
141
|
-
maxAge: COOKIE_MAX_AGE,
|
|
142
|
-
});
|
|
143
|
-
const url = new URL(c.req.url);
|
|
144
|
-
url.searchParams.delete("token");
|
|
145
|
-
// /auth?token=… exists so the Vite dev server can proxy the
|
|
146
|
-
// exchange; land on the app root afterwards either way.
|
|
147
|
-
const dest = url.pathname === "/auth" ? "/" : url.pathname + url.search;
|
|
148
|
-
return c.redirect(dest);
|
|
149
|
-
}
|
|
150
|
-
await next();
|
|
151
|
-
});
|
|
152
|
-
app.use("/api/*", async (c, next) => {
|
|
153
|
-
if (!this.authSessions.has(getCookie(c, AUTH_COOKIE) ?? "")) {
|
|
154
|
-
return c.json({ error: "unauthorized" }, 401);
|
|
155
|
-
}
|
|
156
|
-
await next();
|
|
157
|
-
});
|
|
97
|
+
// security gates live in auth.ts: Host/Origin allowlists + token→cookie
|
|
98
|
+
app.use("*", this.auth.gate());
|
|
99
|
+
app.use("/api/*", this.auth.requireAuth());
|
|
158
100
|
app.get("/api/workspace", (c) => {
|
|
159
101
|
const body = {
|
|
160
102
|
root: this.root,
|
|
@@ -434,11 +376,11 @@ export class WebcodeServer {
|
|
|
434
376
|
socket.write(`HTTP/1.1 ${status} ${msg}\r\nConnection: close\r\n\r\n`);
|
|
435
377
|
socket.destroy();
|
|
436
378
|
};
|
|
437
|
-
if (!this.hostAllowed(req.headers.host))
|
|
379
|
+
if (!this.auth.hostAllowed(req.headers.host))
|
|
438
380
|
return reject(403, "Forbidden");
|
|
439
|
-
if (!this.originAllowed(req.headers.origin))
|
|
381
|
+
if (!this.auth.originAllowed(req.headers.origin))
|
|
440
382
|
return reject(403, "Forbidden");
|
|
441
|
-
if (!this.cookieAuthed(req.headers.cookie))
|
|
383
|
+
if (!this.auth.cookieAuthed(req.headers.cookie))
|
|
442
384
|
return reject(401, "Unauthorized");
|
|
443
385
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
444
386
|
const termMatch = /^\/ws\/term\/([0-9a-f]+)$/.exec(url.pathname);
|
|
@@ -462,8 +404,3 @@ export class WebcodeServer {
|
|
|
462
404
|
this.server?.close();
|
|
463
405
|
}
|
|
464
406
|
}
|
|
465
|
-
function timingSafeEqualStr(a, b) {
|
|
466
|
-
const ba = Buffer.from(a);
|
|
467
|
-
const bb = Buffer.from(b);
|
|
468
|
-
return ba.length === bb.length && crypto.timingSafeEqual(ba, bb);
|
|
469
|
-
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// The zuzuu CLI spawn layer — the ONLY place the daemon shells out to the
|
|
2
|
+
// `zuzuu` binary. Two flavours:
|
|
3
|
+
// runZuzuu — reads: any failure (absent, non-zero, unparseable) → null;
|
|
4
|
+
// callers degrade to file-read fallbacks.
|
|
5
|
+
// runZuzuuMut — mutations + CLI-only reads: failures are distinguished
|
|
6
|
+
// (binary absent vs command failed + stderr tail) so routes
|
|
7
|
+
// can answer 503 vs 502.
|
|
8
|
+
// Always argv arrays (never a shell), time-boxed, cwd-scoped to the workspace.
|
|
9
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
10
|
+
/** Spawn `zuzuu <args> --json` in `root`. Returns parsed JSON, or null on any
|
|
11
|
+
* failure (binary absent, non-zero exit, unparseable). Read-only + time-boxed. */
|
|
12
|
+
export function runZuzuu(root, args, opts = {}) {
|
|
13
|
+
const binary = opts.binary ?? "zuzuu";
|
|
14
|
+
const timeoutMs = opts.timeoutMs ?? 5000;
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
let out = "";
|
|
17
|
+
let done = false;
|
|
18
|
+
const finish = (v) => { if (!done) {
|
|
19
|
+
done = true;
|
|
20
|
+
resolve(v);
|
|
21
|
+
} };
|
|
22
|
+
let child;
|
|
23
|
+
try {
|
|
24
|
+
child = spawn(binary, [...args, "--json"], { cwd: root, stdio: ["ignore", "pipe", "ignore"] });
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
finish(null);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const timer = setTimeout(() => { try {
|
|
31
|
+
child.kill();
|
|
32
|
+
}
|
|
33
|
+
catch { /* noop */ } finish(null); }, timeoutMs);
|
|
34
|
+
child.stdout?.on("data", (b) => { out += b.toString(); });
|
|
35
|
+
child.on("error", () => { clearTimeout(timer); finish(null); });
|
|
36
|
+
child.on("close", (code) => {
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
if (code !== 0)
|
|
39
|
+
return finish(null);
|
|
40
|
+
try {
|
|
41
|
+
finish(JSON.parse(out));
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
finish(null);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const STDERR_TAIL = 2048;
|
|
50
|
+
/** Spawn `zuzuu <args> --json` where the caller needs to distinguish failures
|
|
51
|
+
* (mutations, and reads with no file fallback). Unlike runZuzuu: binary
|
|
52
|
+
* absent vs command failed (with a stderr tail) are separate results, so
|
|
53
|
+
* routes can answer 503 vs 502. Stdout must parse as JSON on success. */
|
|
54
|
+
export function runZuzuuMut(root, args, opts = {}) {
|
|
55
|
+
const binary = opts.binary ?? "zuzuu";
|
|
56
|
+
const timeoutMs = opts.timeoutMs ?? 10_000;
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
let out = "";
|
|
59
|
+
let err = "";
|
|
60
|
+
let done = false;
|
|
61
|
+
const finish = (v) => { if (!done) {
|
|
62
|
+
done = true;
|
|
63
|
+
resolve(v);
|
|
64
|
+
} };
|
|
65
|
+
let child;
|
|
66
|
+
try {
|
|
67
|
+
child = spawn(binary, [...args, "--json"], { cwd: root, stdio: ["ignore", "pipe", "pipe"] });
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
finish({ ok: false, code: "absent" });
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const timer = setTimeout(() => {
|
|
74
|
+
try {
|
|
75
|
+
child.kill();
|
|
76
|
+
}
|
|
77
|
+
catch { /* noop */ }
|
|
78
|
+
finish({ ok: false, code: "failed", stderr: "zuzuu timed out" });
|
|
79
|
+
}, timeoutMs);
|
|
80
|
+
child.stdout?.on("data", (b) => { out += b.toString(); });
|
|
81
|
+
child.stderr?.on("data", (b) => {
|
|
82
|
+
err += b.toString();
|
|
83
|
+
if (err.length > STDERR_TAIL)
|
|
84
|
+
err = err.slice(-STDERR_TAIL);
|
|
85
|
+
});
|
|
86
|
+
child.on("error", (e) => {
|
|
87
|
+
clearTimeout(timer);
|
|
88
|
+
if (e.code === "ENOENT")
|
|
89
|
+
finish({ ok: false, code: "absent" });
|
|
90
|
+
else
|
|
91
|
+
finish({ ok: false, code: "failed", stderr: e.message });
|
|
92
|
+
});
|
|
93
|
+
child.on("close", (code) => {
|
|
94
|
+
clearTimeout(timer);
|
|
95
|
+
if (code !== 0) {
|
|
96
|
+
// zuzuu prints structured JSON even on refusals (exit 1, e.g.
|
|
97
|
+
// empty-squash-with-checkpoints) — keep it so the UI can act on reason.
|
|
98
|
+
try {
|
|
99
|
+
const parsed = JSON.parse(out);
|
|
100
|
+
return finish({ ok: false, code: "failed", stderr: err.slice(-STDERR_TAIL), data: parsed });
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return finish({ ok: false, code: "failed", stderr: err.slice(-STDERR_TAIL) });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
finish({ ok: true, data: JSON.parse(out) });
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
finish({ ok: false, code: "failed", stderr: "unparseable JSON from zuzuu" });
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/** Best-effort: is the zuzuu binary runnable? */
|
|
116
|
+
export function binAvailable(binary) {
|
|
117
|
+
try {
|
|
118
|
+
const r = spawnSync(binary, ["version"], { stdio: "ignore", timeout: 3000 });
|
|
119
|
+
return !r.error && r.status === 0;
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|