@waishnav/devspace 1.0.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/README.md +146 -0
- package/dist/cli.js +331 -0
- package/dist/config.js +188 -0
- package/dist/db/client.js +22 -0
- package/dist/db/schema.js +29 -0
- package/dist/git-worktrees.js +134 -0
- package/dist/git.js +41 -0
- package/dist/logger.js +69 -0
- package/dist/oauth-provider.js +266 -0
- package/dist/pi-tools.js +81 -0
- package/dist/review-checkpoints.js +142 -0
- package/dist/roots.js +34 -0
- package/dist/server.js +1148 -0
- package/dist/skills.js +45 -0
- package/dist/ui/.vite/manifest.json +2520 -0
- package/dist/ui/assets/abap-CLvhMVsD.js +1 -0
- package/dist/ui/assets/actionscript-3--17pq3dv.js +1 -0
- package/dist/ui/assets/ada-C5qYipkI.js +1 -0
- package/dist/ui/assets/andromeeda-vGVdxbeo.js +1 -0
- package/dist/ui/assets/angular-html-Di31D1W0.js +1 -0
- package/dist/ui/assets/angular-ts-BKuSFrBS.js +1 -0
- package/dist/ui/assets/apache-U0d_L8uA.js +1 -0
- package/dist/ui/assets/apex-CGTLDQj6.js +1 -0
- package/dist/ui/assets/apl-DgqYbYoh.js +1 -0
- package/dist/ui/assets/applescript-CCn79oCD.js +1 -0
- package/dist/ui/assets/ara-4CJ0cIlV.js +1 -0
- package/dist/ui/assets/asciidoc-DE70LPWp.js +1 -0
- package/dist/ui/assets/asm-Cmm7eHzH.js +1 -0
- package/dist/ui/assets/astro-B19gi6Os.js +1 -0
- package/dist/ui/assets/aurora-x-CDeNXAV0.js +1 -0
- package/dist/ui/assets/awk-BWXHIvNe.js +1 -0
- package/dist/ui/assets/ayu-dark-DluEY0Gj.js +1 -0
- package/dist/ui/assets/ayu-light-C3h-C4tm.js +1 -0
- package/dist/ui/assets/ayu-mirage-Bqwy1Gya.js +1 -0
- package/dist/ui/assets/ballerina-B7ZEbQpA.js +1 -0
- package/dist/ui/assets/bat-Bo4NYOV-.js +1 -0
- package/dist/ui/assets/beancount-D-usSTwE.js +1 -0
- package/dist/ui/assets/berry-DKpUyyne.js +1 -0
- package/dist/ui/assets/bibtex-Ci_nEsc7.js +1 -0
- package/dist/ui/assets/bicep-CUHmPFLl.js +1 -0
- package/dist/ui/assets/bird2-C2hNVINV.js +1 -0
- package/dist/ui/assets/blade-BKHjA4oS.js +1 -0
- package/dist/ui/assets/bsl-BkkzgIyY.js +1 -0
- package/dist/ui/assets/c-FjyGimBn.js +1 -0
- package/dist/ui/assets/c3-CnJL0r0V.js +1 -0
- package/dist/ui/assets/cadence-CQ2zXKGN.js +1 -0
- package/dist/ui/assets/cairo-DLTphjLi.js +1 -0
- package/dist/ui/assets/catppuccin-frappe-3VR1Za6u.js +1 -0
- package/dist/ui/assets/catppuccin-latte-DwIHMF0Q.js +1 -0
- package/dist/ui/assets/catppuccin-macchiato-DYnBP6_5.js +1 -0
- package/dist/ui/assets/catppuccin-mocha-DYhrFGRu.js +1 -0
- package/dist/ui/assets/clarity-SemFz856.js +1 -0
- package/dist/ui/assets/clojure-DqKBuwfJ.js +1 -0
- package/dist/ui/assets/cmake-Bj61d0ZC.js +1 -0
- package/dist/ui/assets/cobol-DlmtgjjX.js +1 -0
- package/dist/ui/assets/codeowners-C8r90Shi.js +1 -0
- package/dist/ui/assets/codeql-oeQT6MSM.js +1 -0
- package/dist/ui/assets/coffee-BlTKLwcf.js +1 -0
- package/dist/ui/assets/common-lisp-Cv5bFMCO.js +1 -0
- package/dist/ui/assets/coq-BrsZFFmf.js +1 -0
- package/dist/ui/assets/cpp-DtVUwoaa.js +1 -0
- package/dist/ui/assets/crystal-C0TLGTHr.js +1 -0
- package/dist/ui/assets/csharp-Ct8U2NOr.js +1 -0
- package/dist/ui/assets/css-FssmIjiX.js +1 -0
- package/dist/ui/assets/csv-Dx-8-gkx.js +1 -0
- package/dist/ui/assets/cue-CE9AQfxI.js +1 -0
- package/dist/ui/assets/cypher-ClKdZ_lG.js +1 -0
- package/dist/ui/assets/d-qD-0Kul2.js +1 -0
- package/dist/ui/assets/dark-plus-Cs2F2srj.js +1 -0
- package/dist/ui/assets/dart-DkHntEIa.js +1 -0
- package/dist/ui/assets/dax-BkyTk9wS.js +1 -0
- package/dist/ui/assets/desktop-Dlh5hvp9.js +1 -0
- package/dist/ui/assets/diff-woXpYk--.js +1 -0
- package/dist/ui/assets/docker-IyjqRm3v.js +1 -0
- package/dist/ui/assets/dotenv-_5a1GRtc.js +1 -0
- package/dist/ui/assets/dracula-BHWKrbxM.js +1 -0
- package/dist/ui/assets/dracula-soft-5eyTD99u.js +1 -0
- package/dist/ui/assets/dream-maker-DW3nJb8Q.js +1 -0
- package/dist/ui/assets/edge-_m-12TBz.js +1 -0
- package/dist/ui/assets/elixir-RKJED3b7.js +1 -0
- package/dist/ui/assets/elm-CzMSD9Oq.js +1 -0
- package/dist/ui/assets/emacs-lisp-C9PiwqqW.js +1 -0
- package/dist/ui/assets/erb-Cqpp-3vK.js +1 -0
- package/dist/ui/assets/erlang-Cphh6RMH.js +1 -0
- package/dist/ui/assets/everforest-dark-sB-x3p7T.js +1 -0
- package/dist/ui/assets/everforest-light-Df2xbC6M.js +1 -0
- package/dist/ui/assets/fennel-DQxkIbk2.js +1 -0
- package/dist/ui/assets/fish-BJitypiv.js +1 -0
- package/dist/ui/assets/fluent-C03EYrpw.js +1 -0
- package/dist/ui/assets/fortran-fixed-form-DEKoE2YW.js +1 -0
- package/dist/ui/assets/fortran-free-form-CYNrtFtB.js +1 -0
- package/dist/ui/assets/fsharp-D13ZGOAj.js +1 -0
- package/dist/ui/assets/gdresource-C0sCabJj.js +1 -0
- package/dist/ui/assets/gdscript-Cp2uCuqX.js +1 -0
- package/dist/ui/assets/gdshader-CBce3t8t.js +1 -0
- package/dist/ui/assets/genie-CV2tkWYe.js +1 -0
- package/dist/ui/assets/gherkin-DExj1W_8.js +1 -0
- package/dist/ui/assets/git-commit-BSykSTBG.js +1 -0
- package/dist/ui/assets/git-rebase-DLS2r2oj.js +1 -0
- package/dist/ui/assets/github-dark-C-LZuMrd.js +1 -0
- package/dist/ui/assets/github-dark-default-DXG-b-1a.js +1 -0
- package/dist/ui/assets/github-dark-dimmed-Bx1FflLF.js +1 -0
- package/dist/ui/assets/github-dark-high-contrast-B_tTalzw.js +1 -0
- package/dist/ui/assets/github-light-EUqPIrTm.js +1 -0
- package/dist/ui/assets/github-light-default-BXViO-2h.js +1 -0
- package/dist/ui/assets/github-light-high-contrast-B68TUdTA.js +1 -0
- package/dist/ui/assets/gleam-CSRkHgEL.js +1 -0
- package/dist/ui/assets/glimmer-js-B1t-JGK1.js +1 -0
- package/dist/ui/assets/glimmer-ts-CazygCNf.js +1 -0
- package/dist/ui/assets/glsl-gBg2vKZ-.js +1 -0
- package/dist/ui/assets/gn-ilITqXS6.js +1 -0
- package/dist/ui/assets/gnuplot-7GGW24-e.js +1 -0
- package/dist/ui/assets/go-BJwz_mda.js +1 -0
- package/dist/ui/assets/graphql-C8u6MNtl.js +1 -0
- package/dist/ui/assets/groovy-CacY0gHj.js +1 -0
- package/dist/ui/assets/gruvbox-dark-hard-C820rvS2.js +1 -0
- package/dist/ui/assets/gruvbox-dark-medium-BPjhmG05.js +1 -0
- package/dist/ui/assets/gruvbox-dark-soft-MrdJrrXF.js +1 -0
- package/dist/ui/assets/gruvbox-light-hard-BC_s9l72.js +1 -0
- package/dist/ui/assets/gruvbox-light-medium-BAWPOn9u.js +1 -0
- package/dist/ui/assets/gruvbox-light-soft-BSMLrYjP.js +1 -0
- package/dist/ui/assets/hack-CgKuqejW.js +1 -0
- package/dist/ui/assets/haml-I5WU4_Rg.js +1 -0
- package/dist/ui/assets/handlebars-DGf6O3Q6.js +1 -0
- package/dist/ui/assets/haskell-D8IpX4py.js +1 -0
- package/dist/ui/assets/haxe-OTjmBuCE.js +1 -0
- package/dist/ui/assets/hcl-Dh228itO.js +1 -0
- package/dist/ui/assets/heavy-payload-Bp6srDbj.js +4 -0
- package/dist/ui/assets/hjson-CxZEssPk.js +1 -0
- package/dist/ui/assets/hlsl-Cvrh5tZx.js +1 -0
- package/dist/ui/assets/horizon-CE9ld1lL.js +1 -0
- package/dist/ui/assets/horizon-bright-Br1oVSNq.js +1 -0
- package/dist/ui/assets/houston-CsvMBhTu.js +1 -0
- package/dist/ui/assets/html-BrIuweS5.js +1 -0
- package/dist/ui/assets/html-derivative-CflP_1S6.js +1 -0
- package/dist/ui/assets/http-qgGkv9Ds.js +1 -0
- package/dist/ui/assets/hurl-CantyQ5z.js +1 -0
- package/dist/ui/assets/hxml-B0Qn7Nwc.js +1 -0
- package/dist/ui/assets/hy-CZbG8q4J.js +1 -0
- package/dist/ui/assets/imba-DsUTQ-LC.js +1 -0
- package/dist/ui/assets/ini-B5eOa1yu.js +1 -0
- package/dist/ui/assets/java-DqStxnBY.js +1 -0
- package/dist/ui/assets/javascript-CJ1ZUMNM.js +1 -0
- package/dist/ui/assets/jinja-Ta-y-8tW.js +1 -0
- package/dist/ui/assets/jison-DZStAtdf.js +1 -0
- package/dist/ui/assets/json-Bfn_pgvS.js +1 -0
- package/dist/ui/assets/json5-BR5RXkoi.js +1 -0
- package/dist/ui/assets/jsonc-CYpm1nAK.js +1 -0
- package/dist/ui/assets/jsonl-CmCQp5Yx.js +1 -0
- package/dist/ui/assets/jsonnet-CJTPZ8u_.js +1 -0
- package/dist/ui/assets/jssm-DXw9l8Rf.js +1 -0
- package/dist/ui/assets/jsx-CFi5D1JI.js +1 -0
- package/dist/ui/assets/julia-CmuPb_FV.js +1 -0
- package/dist/ui/assets/just-xoDaYyy4.js +1 -0
- package/dist/ui/assets/kanagawa-dragon-CXtmUGW6.js +1 -0
- package/dist/ui/assets/kanagawa-lotus-BN08jTvb.js +1 -0
- package/dist/ui/assets/kanagawa-wave-CTweb8Dz.js +1 -0
- package/dist/ui/assets/kdl-CsD5j6eV.js +1 -0
- package/dist/ui/assets/kotlin-DhhofPvG.js +1 -0
- package/dist/ui/assets/kusto-BUv0MjJC.js +1 -0
- package/dist/ui/assets/laserwave-C_8bwKvT.js +1 -0
- package/dist/ui/assets/latex-C7jbQT4s.js +1 -0
- package/dist/ui/assets/lean-CewbzKMR.js +1 -0
- package/dist/ui/assets/less-DVTAwKKz.js +1 -0
- package/dist/ui/assets/light-plus-DVQuIRkW.js +1 -0
- package/dist/ui/assets/liquid-DyVckOju.js +1 -0
- package/dist/ui/assets/llvm-Cm23YOpf.js +1 -0
- package/dist/ui/assets/log-BNLmms1o.js +1 -0
- package/dist/ui/assets/logo-Cluzi2Zq.js +1 -0
- package/dist/ui/assets/lua-CQGEo-kr.js +1 -0
- package/dist/ui/assets/luau-CNKltnaQ.js +1 -0
- package/dist/ui/assets/make-Dixweg8N.js +1 -0
- package/dist/ui/assets/markdown-BYOwaDjH.js +1 -0
- package/dist/ui/assets/marko-CF3FIlcL.js +1 -0
- package/dist/ui/assets/material-theme-Bm3Qr25_.js +1 -0
- package/dist/ui/assets/material-theme-darker-2IIEA8gg.js +1 -0
- package/dist/ui/assets/material-theme-lighter-uhdI0v04.js +1 -0
- package/dist/ui/assets/material-theme-ocean-CHQ94UKr.js +1 -0
- package/dist/ui/assets/material-theme-palenight-B5W6OYN7.js +1 -0
- package/dist/ui/assets/matlab-D7qyCx1q.js +1 -0
- package/dist/ui/assets/mdc-DJfBh8KU.js +1 -0
- package/dist/ui/assets/mdx-DQZ5AkYe.js +1 -0
- package/dist/ui/assets/mermaid-Bk4SNUv9.js +1 -0
- package/dist/ui/assets/min-dark-BSWPekZh.js +1 -0
- package/dist/ui/assets/min-light-DDpmG2fV.js +1 -0
- package/dist/ui/assets/mipsasm-BMqwQI7S.js +1 -0
- package/dist/ui/assets/mojo-BgCJLMeH.js +1 -0
- package/dist/ui/assets/monokai-CdkpiU2Y.js +1 -0
- package/dist/ui/assets/moonbit-CaWjb8XO.js +1 -0
- package/dist/ui/assets/move-B1IS1UjX.js +1 -0
- package/dist/ui/assets/narrat-_X_XdTYD.js +1 -0
- package/dist/ui/assets/nextflow-Bbiyy34d.js +1 -0
- package/dist/ui/assets/nextflow-groovy-Dc_ddanL.js +1 -0
- package/dist/ui/assets/nginx-CmN5T-d3.js +1 -0
- package/dist/ui/assets/night-owl-DhmEMT88.js +1 -0
- package/dist/ui/assets/night-owl-light-eJ-hLW7d.js +1 -0
- package/dist/ui/assets/nim-a_705mqZ.js +1 -0
- package/dist/ui/assets/nix-IvuFDN5E.js +1 -0
- package/dist/ui/assets/nord-Cb4Vim4T.js +1 -0
- package/dist/ui/assets/nushell-DcLAeLz5.js +1 -0
- package/dist/ui/assets/objective-c-D1A_Heim.js +1 -0
- package/dist/ui/assets/objective-cpp-BsSzOQcm.js +1 -0
- package/dist/ui/assets/ocaml-O90oeIOV.js +1 -0
- package/dist/ui/assets/odin-B1RWQWA5.js +1 -0
- package/dist/ui/assets/one-dark-pro-CLwyXe_n.js +1 -0
- package/dist/ui/assets/one-light-D7Lr4KcI.js +1 -0
- package/dist/ui/assets/openscad-BUDT5pXO.js +1 -0
- package/dist/ui/assets/pascal-4ZHwLPI5.js +1 -0
- package/dist/ui/assets/perl-BFxFi55O.js +1 -0
- package/dist/ui/assets/php-C9Uf0Vo_.js +1 -0
- package/dist/ui/assets/pierre-dark-sU4Zdns8.js +1 -0
- package/dist/ui/assets/pierre-dark-soft-HXgun5vs.js +1 -0
- package/dist/ui/assets/pierre-light-DWBk51d8.js +1 -0
- package/dist/ui/assets/pierre-light-soft-CodEzPxf.js +1 -0
- package/dist/ui/assets/pkl-ot-7Btpt.js +1 -0
- package/dist/ui/assets/plastic-DQwYfKfQ.js +1 -0
- package/dist/ui/assets/plsql-DGHpHOYJ.js +1 -0
- package/dist/ui/assets/po-BiJDBrnU.js +1 -0
- package/dist/ui/assets/poimandres-DRFjx7u4.js +1 -0
- package/dist/ui/assets/polar-C7UOKdEL.js +1 -0
- package/dist/ui/assets/postcss-BXeXVLqQ.js +1 -0
- package/dist/ui/assets/powerquery-DNMTfnFr.js +1 -0
- package/dist/ui/assets/powershell-DshXNtvi.js +1 -0
- package/dist/ui/assets/prisma-BsRQq5mF.js +1 -0
- package/dist/ui/assets/prolog-iXnhIJG7.js +1 -0
- package/dist/ui/assets/proto-DB4EqR-F.js +1 -0
- package/dist/ui/assets/pug-DQIrtlYh.js +1 -0
- package/dist/ui/assets/puppet-CDv2pdJW.js +1 -0
- package/dist/ui/assets/purescript-9MfHhQsQ.js +1 -0
- package/dist/ui/assets/python-gzcpVVnB.js +1 -0
- package/dist/ui/assets/qml-CpI_Asuw.js +1 -0
- package/dist/ui/assets/qmldir-DCQb3MpD.js +1 -0
- package/dist/ui/assets/qss-Fe1Jh2GI.js +1 -0
- package/dist/ui/assets/r-hYZdyIMH.js +1 -0
- package/dist/ui/assets/racket-DcIDlBhZ.js +1 -0
- package/dist/ui/assets/raku-B3gFvitq.js +1 -0
- package/dist/ui/assets/razor-CbXx350T.js +1 -0
- package/dist/ui/assets/red-CJ3rzSJv.js +1 -0
- package/dist/ui/assets/reg-CRGYupPL.js +1 -0
- package/dist/ui/assets/regexp-BdfO5R2u.js +1 -0
- package/dist/ui/assets/rel-BtDbiS_P.js +1 -0
- package/dist/ui/assets/review-payload-CC-3-Lb9.js +1 -0
- package/dist/ui/assets/riscv-Ckw8ddFX.js +1 -0
- package/dist/ui/assets/ron-VUp2lXgN.js +1 -0
- package/dist/ui/assets/rose-pine-BthvhNj6.js +1 -0
- package/dist/ui/assets/rose-pine-dawn-Dg85fqjY.js +1 -0
- package/dist/ui/assets/rose-pine-moon-hon4tzzS.js +1 -0
- package/dist/ui/assets/rosmsg-CAekHB0j.js +1 -0
- package/dist/ui/assets/rst-CP6mAm3Y.js +1 -0
- package/dist/ui/assets/ruby-8tjXzrRr.js +1 -0
- package/dist/ui/assets/rust-Cfkwpbl8.js +1 -0
- package/dist/ui/assets/sas-rqWHY37U.js +1 -0
- package/dist/ui/assets/sass-DXrisJhu.js +1 -0
- package/dist/ui/assets/scala-DKOlJaKm.js +1 -0
- package/dist/ui/assets/scheme-DQCgrYNe.js +1 -0
- package/dist/ui/assets/scss-aGYgGui9.js +1 -0
- package/dist/ui/assets/sdbl-bTVj8UrX.js +1 -0
- package/dist/ui/assets/shaderlab-TOUzSsQk.js +1 -0
- package/dist/ui/assets/shellscript-CYr5JWBI.js +1 -0
- package/dist/ui/assets/shellsession-CO3ljRR7.js +1 -0
- package/dist/ui/assets/slack-dark-DnToyrRv.js +1 -0
- package/dist/ui/assets/slack-ochin-B2OO5cIa.js +1 -0
- package/dist/ui/assets/smalltalk-B16xEiuN.js +1 -0
- package/dist/ui/assets/snazzy-light-4G7pJPwS.js +1 -0
- package/dist/ui/assets/solarized-dark-DV17i1UV.js +1 -0
- package/dist/ui/assets/solarized-light-DSh2HLQt.js +1 -0
- package/dist/ui/assets/solidity-CKzVLygQ.js +1 -0
- package/dist/ui/assets/soy-C4_rLmHV.js +1 -0
- package/dist/ui/assets/sparql-D_iOobhT.js +1 -0
- package/dist/ui/assets/splunk-BC2Px7Mm.js +1 -0
- package/dist/ui/assets/sql-2jcbKHJ9.js +1 -0
- package/dist/ui/assets/ssh-config-BgfXC-Er.js +1 -0
- package/dist/ui/assets/stata-Dbr0lM_v.js +1 -0
- package/dist/ui/assets/stylus-B6D30XZt.js +1 -0
- package/dist/ui/assets/surrealql-Cp0eMxMo.js +1 -0
- package/dist/ui/assets/svelte-BfsT9lcS.js +1 -0
- package/dist/ui/assets/swift-DonLKvLd.js +1 -0
- package/dist/ui/assets/synthwave-84-nFMaYfgc.js +1 -0
- package/dist/ui/assets/system-verilog-DJ5XKQeo.js +1 -0
- package/dist/ui/assets/systemd-BxMlprV5.js +1 -0
- package/dist/ui/assets/talonscript-CohzipZa.js +1 -0
- package/dist/ui/assets/tasl-DMoTqEGO.js +1 -0
- package/dist/ui/assets/tcl-CZd0xW_V.js +1 -0
- package/dist/ui/assets/templ-BBOwNqeW.js +1 -0
- package/dist/ui/assets/terraform-DswuEJGm.js +1 -0
- package/dist/ui/assets/tex-CPmn_RsX.js +1 -0
- package/dist/ui/assets/tokyo-night-oM2G3aXe.js +1 -0
- package/dist/ui/assets/toml-CcmNWLt0.js +1 -0
- package/dist/ui/assets/ts-tags-DMILlUz_.js +1 -0
- package/dist/ui/assets/tsv-sltzmVWM.js +1 -0
- package/dist/ui/assets/tsx-CUP2Lfiz.js +1 -0
- package/dist/ui/assets/turtle-ByJddavk.js +1 -0
- package/dist/ui/assets/twig-DEZ_7SDX.js +1 -0
- package/dist/ui/assets/typescript-DhaMQEhQ.js +1 -0
- package/dist/ui/assets/typespec-B88KGewJ.js +1 -0
- package/dist/ui/assets/typst-DI99ib-x.js +1 -0
- package/dist/ui/assets/useFileDiffInstance-Bo6irrE6.js +244 -0
- package/dist/ui/assets/v-DETTlOr0.js +1 -0
- package/dist/ui/assets/vala-zf12oZj6.js +1 -0
- package/dist/ui/assets/vb-Djn5o6TS.js +1 -0
- package/dist/ui/assets/verilog-CiiDBU1e.js +1 -0
- package/dist/ui/assets/vesper-D5bVUKB1.js +1 -0
- package/dist/ui/assets/vhdl-BroJfC0k.js +1 -0
- package/dist/ui/assets/viml-DvXPmvsu.js +1 -0
- package/dist/ui/assets/vitesse-black-fwtXNY1n.js +1 -0
- package/dist/ui/assets/vitesse-dark-BZCL-v6S.js +1 -0
- package/dist/ui/assets/vitesse-light-VbXTXTou.js +1 -0
- package/dist/ui/assets/vue-CAaS6wtt.js +1 -0
- package/dist/ui/assets/vue-html-D3Etensv.js +1 -0
- package/dist/ui/assets/vue-vine-DNRKj0Lh.js +1 -0
- package/dist/ui/assets/vyper-CgoNMtux.js +1 -0
- package/dist/ui/assets/wasm-BnjxR4X6.js +1 -0
- package/dist/ui/assets/wasm-ByWQv1Qj.js +1 -0
- package/dist/ui/assets/wenyan-C8pVoKbM.js +1 -0
- package/dist/ui/assets/wgsl-BsKzXJz4.js +1 -0
- package/dist/ui/assets/wikitext-ClFFjSW2.js +1 -0
- package/dist/ui/assets/wit-DdvCle-K.js +1 -0
- package/dist/ui/assets/wolfram-DLL8P-h_.js +1 -0
- package/dist/ui/assets/workspace-app-8--oTbCd.css +1 -0
- package/dist/ui/assets/workspace-app-BLeU1qC_.js +118 -0
- package/dist/ui/assets/xml-M-X6I2aP.js +1 -0
- package/dist/ui/assets/xsl-CR0ZlrL7.js +1 -0
- package/dist/ui/assets/yaml-pI4xDDgj.js +1 -0
- package/dist/ui/assets/zenscript-BnlCZFoB.js +1 -0
- package/dist/ui/assets/zig-CMLA9XwU.js +1 -0
- package/dist/ui/workspace-app.html +15 -0
- package/dist/user-config.js +57 -0
- package/dist/workspace-store.js +125 -0
- package/dist/workspaces.js +226 -0
- package/docs/assets/devspace-logo-light.png +0 -0
- package/docs/assets/devspace-screenshot.png +0 -0
- package/docs/chatgpt-coding-workflow.md +143 -0
- package/docs/configuration.md +129 -0
- package/docs/gotchas.md +214 -0
- package/docs/security.md +100 -0
- package/docs/setup.md +132 -0
- package/package.json +66 -0
- package/scripts/build-release.mjs +89 -0
- package/scripts/ensure-native-deps.mjs +29 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { index, primaryKey, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
|
2
|
+
export const workspaceSessions = sqliteTable("workspace_sessions", {
|
|
3
|
+
id: text("id").primaryKey(),
|
|
4
|
+
root: text("root").notNull(),
|
|
5
|
+
status: text("status").notNull().default("active"),
|
|
6
|
+
mode: text("mode").notNull().default("checkout"),
|
|
7
|
+
sourceRoot: text("source_root"),
|
|
8
|
+
baseRef: text("base_ref"),
|
|
9
|
+
baseSha: text("base_sha"),
|
|
10
|
+
managed: text("managed").notNull().default("false"),
|
|
11
|
+
createdAt: text("created_at").notNull(),
|
|
12
|
+
lastUsedAt: text("last_used_at").notNull(),
|
|
13
|
+
}, (table) => [
|
|
14
|
+
index("workspace_sessions_root_idx").on(table.root, table.lastUsedAt),
|
|
15
|
+
index("workspace_sessions_status_idx").on(table.status, table.lastUsedAt),
|
|
16
|
+
]);
|
|
17
|
+
export const loadedAgentFiles = sqliteTable("loaded_agent_files", {
|
|
18
|
+
workspaceSessionId: text("workspace_session_id")
|
|
19
|
+
.notNull()
|
|
20
|
+
.references(() => workspaceSessions.id, { onDelete: "cascade" }),
|
|
21
|
+
path: text("path").notNull(),
|
|
22
|
+
contentHash: text("content_hash").notNull(),
|
|
23
|
+
content: text("content").notNull(),
|
|
24
|
+
loadedAt: text("loaded_at").notNull(),
|
|
25
|
+
lastSeenAt: text("last_seen_at").notNull(),
|
|
26
|
+
}, (table) => [
|
|
27
|
+
primaryKey({ columns: [table.workspaceSessionId, table.path] }),
|
|
28
|
+
index("loaded_agent_files_path_idx").on(table.path),
|
|
29
|
+
]);
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
import { mkdir, realpath, rm, stat } from "node:fs/promises";
|
|
5
|
+
import { basename, join, relative, resolve } from "node:path";
|
|
6
|
+
import { assertAllowedPath, isPathInsideRoot } from "./roots.js";
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
export class GitWorktreeError extends Error {
|
|
9
|
+
code;
|
|
10
|
+
constructor(code, message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.code = code;
|
|
13
|
+
this.name = "GitWorktreeError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export async function createManagedWorktree(input) {
|
|
17
|
+
const sourcePath = assertAllowedPath(input.sourcePath, input.config.allowedRoots);
|
|
18
|
+
try {
|
|
19
|
+
const sourceStats = await stat(sourcePath);
|
|
20
|
+
if (!sourceStats.isDirectory()) {
|
|
21
|
+
throw new GitWorktreeError("GIT_REPOSITORY_NOT_FOUND", `Cannot open workspace in worktree mode because the source path is not a directory: ${input.sourcePath}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
if (error instanceof GitWorktreeError)
|
|
26
|
+
throw error;
|
|
27
|
+
throw new GitWorktreeError("GIT_REPOSITORY_NOT_FOUND", `Cannot open workspace in worktree mode because the source path does not exist: ${input.sourcePath}`);
|
|
28
|
+
}
|
|
29
|
+
const sourceRoot = await resolveGitRoot(sourcePath, input.config.allowedRoots);
|
|
30
|
+
const baseRef = input.baseRef ?? "HEAD";
|
|
31
|
+
const baseSha = await resolveBaseCommit(sourceRoot, baseRef);
|
|
32
|
+
const dirtySource = (await git(["status", "--porcelain=v1"], sourceRoot)).trim().length > 0;
|
|
33
|
+
const worktreePath = managedWorktreePath({
|
|
34
|
+
worktreeRoot: input.config.worktreeRoot,
|
|
35
|
+
repoRoot: sourceRoot,
|
|
36
|
+
});
|
|
37
|
+
await mkdir(input.config.worktreeRoot, { recursive: true });
|
|
38
|
+
assertAllowedPath(worktreePath, [input.config.worktreeRoot]);
|
|
39
|
+
try {
|
|
40
|
+
await git(["worktree", "add", "--detach", worktreePath, baseSha], sourceRoot);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
await rm(worktreePath, { recursive: true, force: true });
|
|
44
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
45
|
+
throw new GitWorktreeError("GIT_WORKTREE_CREATE_FAILED", `Git failed to create the managed worktree. ${message}`);
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
sourceRoot,
|
|
49
|
+
path: worktreePath,
|
|
50
|
+
baseRef,
|
|
51
|
+
baseSha,
|
|
52
|
+
dirtySource,
|
|
53
|
+
detached: true,
|
|
54
|
+
managed: true,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
async function resolveGitRoot(path, allowedRoots) {
|
|
58
|
+
try {
|
|
59
|
+
const output = await git(["rev-parse", "--show-toplevel"], path);
|
|
60
|
+
return await assertGitRootAllowed(output.trim(), allowedRoots);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (isGitUnavailable(error)) {
|
|
64
|
+
throw new GitWorktreeError("GIT_NOT_AVAILABLE", "Cannot open workspace in worktree mode because Git is not available on this machine.");
|
|
65
|
+
}
|
|
66
|
+
throw new GitWorktreeError("GIT_REPOSITORY_NOT_FOUND", `Cannot open workspace in worktree mode because this path is not inside a Git repository: ${path}. Use mode=\"checkout\" to work directly in this directory, or initialize Git and create an initial commit first.`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async function assertGitRootAllowed(gitRoot, allowedRoots) {
|
|
70
|
+
try {
|
|
71
|
+
return assertAllowedPath(gitRoot, allowedRoots);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
const canonicalGitRoot = await realpath(gitRoot);
|
|
75
|
+
for (const allowedRoot of allowedRoots) {
|
|
76
|
+
const canonicalAllowedRoot = await realpath(allowedRoot).catch(() => undefined);
|
|
77
|
+
if (!canonicalAllowedRoot || !isPathInsideRoot(canonicalGitRoot, canonicalAllowedRoot)) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const logicalGitRoot = resolve(allowedRoot, relative(canonicalAllowedRoot, canonicalGitRoot));
|
|
81
|
+
return assertAllowedPath(logicalGitRoot, allowedRoots);
|
|
82
|
+
}
|
|
83
|
+
return assertAllowedPath(canonicalGitRoot, allowedRoots);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function resolveBaseCommit(sourceRoot, baseRef) {
|
|
87
|
+
try {
|
|
88
|
+
return (await git(["rev-parse", "--verify", `${baseRef}^{commit}`], sourceRoot)).trim();
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
if (baseRef === "HEAD") {
|
|
92
|
+
throw new GitWorktreeError("GIT_REPOSITORY_HAS_NO_COMMITS", "Cannot open workspace in worktree mode because the repository has no commits yet. Create an initial commit first, or use mode=\"checkout\".");
|
|
93
|
+
}
|
|
94
|
+
throw new GitWorktreeError("GIT_INVALID_BASE_REF", `Cannot open workspace in worktree mode because baseRef ${JSON.stringify(baseRef)} does not resolve to a commit.`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function managedWorktreePath(input) {
|
|
98
|
+
const repoName = sanitizePathSegment(basename(input.repoRoot)) || "repo";
|
|
99
|
+
const worktreeId = randomBytes(4).toString("hex");
|
|
100
|
+
return join(input.worktreeRoot, `${repoName}-${worktreeId}`);
|
|
101
|
+
}
|
|
102
|
+
function sanitizePathSegment(value) {
|
|
103
|
+
return value
|
|
104
|
+
.replace(/[^a-zA-Z0-9._-]+/g, "-")
|
|
105
|
+
.replace(/^-+|-+$/g, "")
|
|
106
|
+
.slice(0, 80);
|
|
107
|
+
}
|
|
108
|
+
async function git(args, cwd) {
|
|
109
|
+
try {
|
|
110
|
+
const { stdout } = await execFileAsync("git", args, {
|
|
111
|
+
cwd,
|
|
112
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
113
|
+
});
|
|
114
|
+
return stdout;
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
if (isGitUnavailable(error))
|
|
118
|
+
throw error;
|
|
119
|
+
const stderr = typeof error === "object" && error && "stderr" in error
|
|
120
|
+
? String(error.stderr ?? "").trim()
|
|
121
|
+
: "";
|
|
122
|
+
const stdout = typeof error === "object" && error && "stdout" in error
|
|
123
|
+
? String(error.stdout ?? "").trim()
|
|
124
|
+
: "";
|
|
125
|
+
const details = stderr || stdout || (error instanceof Error ? error.message : String(error));
|
|
126
|
+
throw new Error(details);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function isGitUnavailable(error) {
|
|
130
|
+
return Boolean(typeof error === "object" &&
|
|
131
|
+
error &&
|
|
132
|
+
"code" in error &&
|
|
133
|
+
error.code === "ENOENT");
|
|
134
|
+
}
|
package/dist/git.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
export async function git(cwd, args, options = {}) {
|
|
6
|
+
const { stdout, stderr } = await execFileAsync("git", args, {
|
|
7
|
+
cwd,
|
|
8
|
+
env: options.env ? { ...process.env, ...options.env } : process.env,
|
|
9
|
+
maxBuffer: options.maxBuffer ?? 10 * 1024 * 1024,
|
|
10
|
+
});
|
|
11
|
+
return { stdout, stderr };
|
|
12
|
+
}
|
|
13
|
+
export async function getGitEligibility(cwd) {
|
|
14
|
+
try {
|
|
15
|
+
await git(cwd, ["rev-parse", "--is-inside-work-tree"]);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return {
|
|
19
|
+
ok: false,
|
|
20
|
+
reason: "not_git",
|
|
21
|
+
message: "workspace is not inside a git repository",
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const gitRoot = (await git(cwd, ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
25
|
+
try {
|
|
26
|
+
await git(gitRoot, ["rev-parse", "--verify", "--quiet", "HEAD^{commit}"]);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return {
|
|
30
|
+
ok: false,
|
|
31
|
+
gitRoot,
|
|
32
|
+
reason: "no_head",
|
|
33
|
+
message: "repository has no HEAD commit",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return { ok: true, gitRoot };
|
|
37
|
+
}
|
|
38
|
+
export function safeWorkspaceRefSegment(workspaceId) {
|
|
39
|
+
const safe = workspaceId.replace(/[^A-Za-z0-9._-]/g, "-");
|
|
40
|
+
return safe.length > 0 ? safe : createHash("sha256").update(workspaceId).digest("hex").slice(0, 16);
|
|
41
|
+
}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const LEVEL_WEIGHT = {
|
|
2
|
+
silent: 0,
|
|
3
|
+
error: 1,
|
|
4
|
+
warn: 2,
|
|
5
|
+
info: 3,
|
|
6
|
+
debug: 4,
|
|
7
|
+
};
|
|
8
|
+
export function shouldLog(config, level) {
|
|
9
|
+
return LEVEL_WEIGHT[config.level] >= LEVEL_WEIGHT[level];
|
|
10
|
+
}
|
|
11
|
+
export function logEvent(config, level, event, fields = {}) {
|
|
12
|
+
if (!shouldLog(config, level))
|
|
13
|
+
return;
|
|
14
|
+
const entry = {
|
|
15
|
+
ts: new Date().toISOString(),
|
|
16
|
+
level,
|
|
17
|
+
event,
|
|
18
|
+
...fields,
|
|
19
|
+
};
|
|
20
|
+
const line = config.format === "pretty" ? formatPretty(entry) : JSON.stringify(entry);
|
|
21
|
+
if (level === "error") {
|
|
22
|
+
console.error(line);
|
|
23
|
+
}
|
|
24
|
+
else if (level === "warn") {
|
|
25
|
+
console.warn(line);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
console.log(line);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function requestIp(req, trustProxy) {
|
|
32
|
+
if (trustProxy) {
|
|
33
|
+
const cfConnectingIp = firstHeaderValue(req.header("cf-connecting-ip"));
|
|
34
|
+
if (cfConnectingIp)
|
|
35
|
+
return cfConnectingIp;
|
|
36
|
+
const forwardedFor = firstHeaderValue(req.header("x-forwarded-for"));
|
|
37
|
+
if (forwardedFor)
|
|
38
|
+
return forwardedFor;
|
|
39
|
+
}
|
|
40
|
+
return req.ip ?? req.socket.remoteAddress;
|
|
41
|
+
}
|
|
42
|
+
export function requestPath(req) {
|
|
43
|
+
return req.path || req.url.split("?")[0] || req.url;
|
|
44
|
+
}
|
|
45
|
+
export function sessionIdPrefix(sessionId) {
|
|
46
|
+
return sessionId ? sessionId.slice(0, 8) : undefined;
|
|
47
|
+
}
|
|
48
|
+
export function commandPreview(command) {
|
|
49
|
+
const normalized = command.replace(/\s+/g, " ").trim();
|
|
50
|
+
return normalized.length > 120 ? `${normalized.slice(0, 117)}...` : normalized;
|
|
51
|
+
}
|
|
52
|
+
function firstHeaderValue(value) {
|
|
53
|
+
return value?.split(",")[0]?.trim() || undefined;
|
|
54
|
+
}
|
|
55
|
+
function formatPretty(entry) {
|
|
56
|
+
const ts = String(entry.ts);
|
|
57
|
+
const level = String(entry.level).toUpperCase();
|
|
58
|
+
const event = String(entry.event);
|
|
59
|
+
const rest = Object.entries(entry)
|
|
60
|
+
.filter(([key, value]) => !["ts", "level", "event"].includes(key) && value !== undefined)
|
|
61
|
+
.map(([key, value]) => `${key}=${formatPrettyValue(value)}`)
|
|
62
|
+
.join(" ");
|
|
63
|
+
return rest ? `${ts} ${level} ${event} ${rest}` : `${ts} ${level} ${event}`;
|
|
64
|
+
}
|
|
65
|
+
function formatPrettyValue(value) {
|
|
66
|
+
if (typeof value === "string")
|
|
67
|
+
return JSON.stringify(value);
|
|
68
|
+
return JSON.stringify(value);
|
|
69
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { timingSafeEqual, randomBytes, randomUUID, createHash } from "node:crypto";
|
|
2
|
+
import { AccessDeniedError, InvalidGrantError, InvalidRequestError, InvalidTokenError } from "@modelcontextprotocol/sdk/server/auth/errors.js";
|
|
3
|
+
import { checkResourceAllowed, resourceUrlFromServerUrl } from "@modelcontextprotocol/sdk/shared/auth-utils.js";
|
|
4
|
+
const CODE_TTL_MS = 5 * 60 * 1000;
|
|
5
|
+
function randomToken() {
|
|
6
|
+
return randomBytes(32).toString("base64url");
|
|
7
|
+
}
|
|
8
|
+
function safeEquals(a, b) {
|
|
9
|
+
const left = Buffer.from(a);
|
|
10
|
+
const right = Buffer.from(b);
|
|
11
|
+
if (left.byteLength !== right.byteLength)
|
|
12
|
+
return false;
|
|
13
|
+
return timingSafeEqual(left, right);
|
|
14
|
+
}
|
|
15
|
+
function htmlEscape(value) {
|
|
16
|
+
return value
|
|
17
|
+
.replaceAll("&", "&")
|
|
18
|
+
.replaceAll("<", "<")
|
|
19
|
+
.replaceAll(">", ">")
|
|
20
|
+
.replaceAll('"', """)
|
|
21
|
+
.replaceAll("'", "'");
|
|
22
|
+
}
|
|
23
|
+
function formHtml(params) {
|
|
24
|
+
const scopeText = params.scopes.length > 0 ? params.scopes.join(" ") : "devspace";
|
|
25
|
+
const resourceText = params.resource?.href ?? "DevSpace MCP endpoint";
|
|
26
|
+
const error = params.error
|
|
27
|
+
? `<p class="error">${htmlEscape(params.error)}</p>`
|
|
28
|
+
: "";
|
|
29
|
+
const hiddenFields = Object.entries(params.fields)
|
|
30
|
+
.filter((entry) => entry[1] !== undefined)
|
|
31
|
+
.map(([name, value]) => ` <input type="hidden" name="${htmlEscape(name)}" value="${htmlEscape(value)}" />`)
|
|
32
|
+
.join("\n");
|
|
33
|
+
return `<!doctype html>
|
|
34
|
+
<html lang="en">
|
|
35
|
+
<head>
|
|
36
|
+
<meta charset="utf-8" />
|
|
37
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
38
|
+
<title>Connect DevSpace</title>
|
|
39
|
+
<style>
|
|
40
|
+
body { font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; margin: 0; background: #0f172a; color: #e2e8f0; }
|
|
41
|
+
main { max-width: 440px; margin: 12vh auto; padding: 32px; background: #111827; border: 1px solid #334155; border-radius: 18px; box-shadow: 0 24px 80px rgba(0,0,0,.35); }
|
|
42
|
+
h1 { margin: 0 0 12px; font-size: 28px; }
|
|
43
|
+
p { line-height: 1.5; color: #cbd5e1; }
|
|
44
|
+
dl { padding: 16px; background: #020617; border-radius: 12px; }
|
|
45
|
+
dt { color: #94a3b8; font-size: 12px; text-transform: uppercase; letter-spacing: .06em; }
|
|
46
|
+
dd { margin: 4px 0 12px; word-break: break-word; }
|
|
47
|
+
label { display: block; margin: 18px 0 8px; font-weight: 600; }
|
|
48
|
+
input { box-sizing: border-box; width: 100%; padding: 12px 14px; border-radius: 10px; border: 1px solid #475569; background: #020617; color: #e2e8f0; font-size: 16px; }
|
|
49
|
+
button { margin-top: 18px; width: 100%; border: 0; border-radius: 10px; padding: 12px 14px; font-weight: 700; color: #020617; background: #38bdf8; cursor: pointer; }
|
|
50
|
+
.error { color: #fecaca; background: #7f1d1d; border-radius: 10px; padding: 10px 12px; }
|
|
51
|
+
.warning { color: #fde68a; }
|
|
52
|
+
</style>
|
|
53
|
+
</head>
|
|
54
|
+
<body>
|
|
55
|
+
<main>
|
|
56
|
+
<h1>Connect DevSpace</h1>
|
|
57
|
+
<p class="warning">Only approve this if you are intentionally connecting your own ChatGPT or MCP client to this local machine.</p>
|
|
58
|
+
${error}
|
|
59
|
+
<dl>
|
|
60
|
+
<dt>Client</dt><dd>${htmlEscape(params.clientName)}</dd>
|
|
61
|
+
<dt>Scope</dt><dd>${htmlEscape(scopeText)}</dd>
|
|
62
|
+
<dt>Resource</dt><dd>${htmlEscape(resourceText)}</dd>
|
|
63
|
+
</dl>
|
|
64
|
+
<form method="post">
|
|
65
|
+
${hiddenFields}
|
|
66
|
+
<label for="owner_token">Owner password</label>
|
|
67
|
+
<input id="owner_token" name="owner_token" type="password" autocomplete="current-password" autofocus required />
|
|
68
|
+
<button type="submit">Authorize DevSpace</button>
|
|
69
|
+
</form>
|
|
70
|
+
</main>
|
|
71
|
+
</body>
|
|
72
|
+
</html>`;
|
|
73
|
+
}
|
|
74
|
+
function requestedScopesAllowed(requested, supported) {
|
|
75
|
+
return requested.every((scope) => supported.includes(scope));
|
|
76
|
+
}
|
|
77
|
+
function redirectHostAllowed(redirectUri, allowedHosts) {
|
|
78
|
+
let parsed;
|
|
79
|
+
try {
|
|
80
|
+
parsed = new URL(redirectUri);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
if (["localhost", "127.0.0.1", "[::1]"].includes(parsed.hostname))
|
|
86
|
+
return true;
|
|
87
|
+
return allowedHosts.includes(parsed.hostname);
|
|
88
|
+
}
|
|
89
|
+
export class InMemoryOAuthClientsStore {
|
|
90
|
+
allowedRedirectHosts;
|
|
91
|
+
clients = new Map();
|
|
92
|
+
constructor(allowedRedirectHosts) {
|
|
93
|
+
this.allowedRedirectHosts = allowedRedirectHosts;
|
|
94
|
+
}
|
|
95
|
+
getClient(clientId) {
|
|
96
|
+
return this.clients.get(clientId);
|
|
97
|
+
}
|
|
98
|
+
registerClient(client) {
|
|
99
|
+
if (!client.redirect_uris.every((uri) => redirectHostAllowed(uri, this.allowedRedirectHosts))) {
|
|
100
|
+
throw new InvalidRequestError("Client redirect_uri is not allowed for this DevSpace server");
|
|
101
|
+
}
|
|
102
|
+
const now = Math.floor(Date.now() / 1000);
|
|
103
|
+
const registered = {
|
|
104
|
+
...client,
|
|
105
|
+
client_id: `devspace-${randomUUID()}`,
|
|
106
|
+
client_id_issued_at: now,
|
|
107
|
+
token_endpoint_auth_method: client.token_endpoint_auth_method ?? "none",
|
|
108
|
+
grant_types: client.grant_types ?? ["authorization_code", "refresh_token"],
|
|
109
|
+
response_types: client.response_types ?? ["code"],
|
|
110
|
+
};
|
|
111
|
+
this.clients.set(registered.client_id, registered);
|
|
112
|
+
return registered;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
export class SingleUserOAuthProvider {
|
|
116
|
+
config;
|
|
117
|
+
clientsStore;
|
|
118
|
+
codes = new Map();
|
|
119
|
+
accessTokens = new Map();
|
|
120
|
+
refreshTokens = new Map();
|
|
121
|
+
resourceServerUrl;
|
|
122
|
+
constructor(config, resourceServerUrl) {
|
|
123
|
+
this.config = config;
|
|
124
|
+
this.resourceServerUrl = resourceUrlFromServerUrl(resourceServerUrl);
|
|
125
|
+
this.clientsStore = new InMemoryOAuthClientsStore(config.allowedRedirectHosts);
|
|
126
|
+
}
|
|
127
|
+
async authorize(client, params, res) {
|
|
128
|
+
if (!params.resource || !checkResourceAllowed({ requestedResource: params.resource, configuredResource: this.resourceServerUrl })) {
|
|
129
|
+
throw new InvalidRequestError("Invalid or missing OAuth resource");
|
|
130
|
+
}
|
|
131
|
+
if (!requestedScopesAllowed(params.scopes ?? [], this.config.scopes)) {
|
|
132
|
+
throw new InvalidRequestError("Requested scope is not supported");
|
|
133
|
+
}
|
|
134
|
+
if (res.req.method !== "POST") {
|
|
135
|
+
res.status(200).setHeader("Content-Type", "text/html; charset=utf-8");
|
|
136
|
+
res.send(formHtml({
|
|
137
|
+
clientName: client.client_name ?? client.client_id,
|
|
138
|
+
scopes: params.scopes ?? this.config.scopes,
|
|
139
|
+
resource: params.resource,
|
|
140
|
+
fields: authorizationFormFields(client, params),
|
|
141
|
+
}));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const providedToken = String(res.req.body?.owner_token ?? "");
|
|
145
|
+
if (!safeEquals(providedToken, this.config.ownerToken)) {
|
|
146
|
+
res.status(401).setHeader("Content-Type", "text/html; charset=utf-8");
|
|
147
|
+
res.send(formHtml({
|
|
148
|
+
error: "The Owner password was not accepted.",
|
|
149
|
+
clientName: client.client_name ?? client.client_id,
|
|
150
|
+
scopes: params.scopes ?? this.config.scopes,
|
|
151
|
+
resource: params.resource,
|
|
152
|
+
fields: authorizationFormFields(client, params),
|
|
153
|
+
}));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const code = `code-${randomUUID()}`;
|
|
157
|
+
this.codes.set(code, {
|
|
158
|
+
clientId: client.client_id,
|
|
159
|
+
params,
|
|
160
|
+
expiresAtMs: Date.now() + CODE_TTL_MS,
|
|
161
|
+
});
|
|
162
|
+
const redirectUrl = new URL(params.redirectUri);
|
|
163
|
+
redirectUrl.searchParams.set("code", code);
|
|
164
|
+
if (params.state !== undefined)
|
|
165
|
+
redirectUrl.searchParams.set("state", params.state);
|
|
166
|
+
res.redirect(302, redirectUrl.href);
|
|
167
|
+
}
|
|
168
|
+
async challengeForAuthorizationCode(client, authorizationCode) {
|
|
169
|
+
const record = this.validCodeRecord(client, authorizationCode);
|
|
170
|
+
return record.params.codeChallenge;
|
|
171
|
+
}
|
|
172
|
+
async exchangeAuthorizationCode(client, authorizationCode, _codeVerifier, redirectUri, resource) {
|
|
173
|
+
const record = this.validCodeRecord(client, authorizationCode);
|
|
174
|
+
if (redirectUri && redirectUri !== record.params.redirectUri) {
|
|
175
|
+
throw new InvalidGrantError("redirect_uri does not match the authorization request");
|
|
176
|
+
}
|
|
177
|
+
if (resource && !checkResourceAllowed({ requestedResource: resource, configuredResource: this.resourceServerUrl })) {
|
|
178
|
+
throw new InvalidGrantError("Invalid resource");
|
|
179
|
+
}
|
|
180
|
+
this.codes.delete(authorizationCode);
|
|
181
|
+
return this.issueTokens(client.client_id, record.params.scopes ?? this.config.scopes, record.params.resource);
|
|
182
|
+
}
|
|
183
|
+
async exchangeRefreshToken(client, refreshToken, scopes, resource) {
|
|
184
|
+
const record = this.refreshTokens.get(hashToken(refreshToken));
|
|
185
|
+
if (!record || record.clientId !== client.client_id || record.expiresAt < Math.floor(Date.now() / 1000)) {
|
|
186
|
+
throw new InvalidGrantError("Invalid refresh token");
|
|
187
|
+
}
|
|
188
|
+
if (resource && !checkResourceAllowed({ requestedResource: resource, configuredResource: this.resourceServerUrl })) {
|
|
189
|
+
throw new InvalidGrantError("Invalid resource");
|
|
190
|
+
}
|
|
191
|
+
const requestedScopes = scopes ?? record.scopes;
|
|
192
|
+
if (!requestedScopes.every((scope) => record.scopes.includes(scope))) {
|
|
193
|
+
throw new AccessDeniedError("Refresh token cannot grant requested scopes");
|
|
194
|
+
}
|
|
195
|
+
this.refreshTokens.delete(hashToken(refreshToken));
|
|
196
|
+
return this.issueTokens(client.client_id, requestedScopes, resource ?? record.resource);
|
|
197
|
+
}
|
|
198
|
+
async verifyAccessToken(token) {
|
|
199
|
+
const record = this.accessTokens.get(hashToken(token));
|
|
200
|
+
if (!record || record.expiresAt < Math.floor(Date.now() / 1000)) {
|
|
201
|
+
throw new InvalidTokenError("Invalid or expired access token");
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
token,
|
|
205
|
+
clientId: record.clientId,
|
|
206
|
+
scopes: record.scopes,
|
|
207
|
+
expiresAt: record.expiresAt,
|
|
208
|
+
resource: record.resource,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
async revokeToken(_client, request) {
|
|
212
|
+
const hashed = hashToken(request.token);
|
|
213
|
+
this.accessTokens.delete(hashed);
|
|
214
|
+
this.refreshTokens.delete(hashed);
|
|
215
|
+
}
|
|
216
|
+
validCodeRecord(client, authorizationCode) {
|
|
217
|
+
const record = this.codes.get(authorizationCode);
|
|
218
|
+
if (!record || record.clientId !== client.client_id || record.expiresAtMs < Date.now()) {
|
|
219
|
+
throw new InvalidGrantError("Invalid authorization code");
|
|
220
|
+
}
|
|
221
|
+
return record;
|
|
222
|
+
}
|
|
223
|
+
issueTokens(clientId, scopes, resource) {
|
|
224
|
+
const now = Math.floor(Date.now() / 1000);
|
|
225
|
+
const accessToken = randomToken();
|
|
226
|
+
const refreshToken = randomToken();
|
|
227
|
+
const accessExpiresAt = now + this.config.accessTokenTtlSeconds;
|
|
228
|
+
const refreshExpiresAt = now + this.config.refreshTokenTtlSeconds;
|
|
229
|
+
this.accessTokens.set(hashToken(accessToken), {
|
|
230
|
+
token: accessToken,
|
|
231
|
+
clientId,
|
|
232
|
+
scopes,
|
|
233
|
+
expiresAt: accessExpiresAt,
|
|
234
|
+
resource,
|
|
235
|
+
});
|
|
236
|
+
this.refreshTokens.set(hashToken(refreshToken), {
|
|
237
|
+
token: refreshToken,
|
|
238
|
+
clientId,
|
|
239
|
+
scopes,
|
|
240
|
+
expiresAt: refreshExpiresAt,
|
|
241
|
+
resource,
|
|
242
|
+
});
|
|
243
|
+
return {
|
|
244
|
+
access_token: accessToken,
|
|
245
|
+
token_type: "bearer",
|
|
246
|
+
expires_in: this.config.accessTokenTtlSeconds,
|
|
247
|
+
refresh_token: refreshToken,
|
|
248
|
+
scope: scopes.join(" "),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function authorizationFormFields(client, params) {
|
|
253
|
+
return {
|
|
254
|
+
response_type: "code",
|
|
255
|
+
client_id: client.client_id,
|
|
256
|
+
redirect_uri: params.redirectUri,
|
|
257
|
+
code_challenge: params.codeChallenge,
|
|
258
|
+
code_challenge_method: "S256",
|
|
259
|
+
scope: params.scopes?.join(" "),
|
|
260
|
+
state: params.state,
|
|
261
|
+
resource: params.resource?.href,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function hashToken(token) {
|
|
265
|
+
return createHash("sha256").update(token).digest("base64url");
|
|
266
|
+
}
|
package/dist/pi-tools.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { createBashTool, createEditTool, createFindTool, createGrepTool, createLsTool, createReadTool, createWriteTool, } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { resolveAllowedPath } from "./roots.js";
|
|
3
|
+
function toMcpContent(result) {
|
|
4
|
+
return result.content.map((content) => {
|
|
5
|
+
if (content.type === "text") {
|
|
6
|
+
return { type: "text", text: content.text };
|
|
7
|
+
}
|
|
8
|
+
return {
|
|
9
|
+
type: "image",
|
|
10
|
+
data: content.data,
|
|
11
|
+
mimeType: content.mimeType,
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function formatToolError(error) {
|
|
16
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
17
|
+
return [{ type: "text", text: message }];
|
|
18
|
+
}
|
|
19
|
+
async function runTool(execute, input, context) {
|
|
20
|
+
try {
|
|
21
|
+
const result = await execute(input);
|
|
22
|
+
return {
|
|
23
|
+
content: toMcpContent(result),
|
|
24
|
+
details: result.details,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
return { content: formatToolError(error), isError: true };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export async function readFileTool(input, context) {
|
|
32
|
+
const path = resolveAllowedPath(input.path, context.cwd, context.readRoots ?? [context.root]);
|
|
33
|
+
const tool = createReadTool(context.cwd);
|
|
34
|
+
return runTool((params) => tool.execute("read_file", params), {
|
|
35
|
+
path,
|
|
36
|
+
offset: input.offset,
|
|
37
|
+
limit: input.limit,
|
|
38
|
+
}, context);
|
|
39
|
+
}
|
|
40
|
+
export async function writeFileTool(input, context) {
|
|
41
|
+
const path = resolveAllowedPath(input.path, context.cwd, [context.root]);
|
|
42
|
+
const tool = createWriteTool(context.cwd);
|
|
43
|
+
return runTool((params) => tool.execute("write_file", params), {
|
|
44
|
+
path,
|
|
45
|
+
content: input.content,
|
|
46
|
+
}, context);
|
|
47
|
+
}
|
|
48
|
+
export async function editFileTool(input, context) {
|
|
49
|
+
const path = resolveAllowedPath(input.path, context.cwd, [context.root]);
|
|
50
|
+
const tool = createEditTool(context.cwd);
|
|
51
|
+
return runTool((params) => tool.execute("edit_file", params), {
|
|
52
|
+
path,
|
|
53
|
+
edits: input.edits,
|
|
54
|
+
}, context);
|
|
55
|
+
}
|
|
56
|
+
export async function grepFilesTool(input, context) {
|
|
57
|
+
if (input.path)
|
|
58
|
+
resolveAllowedPath(input.path, context.cwd, [context.root]);
|
|
59
|
+
const tool = createGrepTool(context.cwd);
|
|
60
|
+
return runTool((params) => tool.execute("grep_files", params), input, context);
|
|
61
|
+
}
|
|
62
|
+
export async function findFilesTool(input, context) {
|
|
63
|
+
if (input.path)
|
|
64
|
+
resolveAllowedPath(input.path, context.cwd, [context.root]);
|
|
65
|
+
const tool = createFindTool(context.cwd);
|
|
66
|
+
return runTool((params) => tool.execute("find_files", params), input, context);
|
|
67
|
+
}
|
|
68
|
+
export async function listDirectoryTool(input, context) {
|
|
69
|
+
if (input.path)
|
|
70
|
+
resolveAllowedPath(input.path, context.cwd, [context.root]);
|
|
71
|
+
const tool = createLsTool(context.cwd);
|
|
72
|
+
return runTool((params) => tool.execute("list_directory", params), input, context);
|
|
73
|
+
}
|
|
74
|
+
export async function runShellTool(input, context) {
|
|
75
|
+
const tool = createBashTool(context.cwd);
|
|
76
|
+
const timeout = input.timeout === undefined ? 30 : Math.min(input.timeout, 300);
|
|
77
|
+
return runTool((params) => tool.execute("run_shell", params), {
|
|
78
|
+
command: input.command,
|
|
79
|
+
timeout,
|
|
80
|
+
}, context);
|
|
81
|
+
}
|