@danneillouven/remote-workspace 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/.env.example +32 -0
- package/README.md +196 -2
- package/bin/remote-workspace.mjs +29 -0
- package/dist/assets/add-project-screen-IOzaLXTc.js +1 -0
- package/dist/assets/apl-gfMMiTNf.js +1 -0
- package/dist/assets/arrow-left-B5CLiIbq.js +1 -0
- package/dist/assets/asciiarmor-C_eisQCp.js +1 -0
- package/dist/assets/asn1-DCl_8MCW.js +1 -0
- package/dist/assets/asterisk-BEn0Cwpx.js +1 -0
- package/dist/assets/brainfuck-D5EjA2JK.js +1 -0
- package/dist/assets/client-BaUFYZ28.js +63 -0
- package/dist/assets/clike-xvONpD0Z.js +1 -0
- package/dist/assets/clojure-BflJGmDX.js +1 -0
- package/dist/assets/cmake-84nbDH0I.js +1 -0
- package/dist/assets/cobol-DHLQUbN7.js +1 -0
- package/dist/assets/codemirror-DRp8cs-R.js +14 -0
- package/dist/assets/coffeescript-De_zI4J0.js +1 -0
- package/dist/assets/commonlisp-DgAKBe0r.js +1 -0
- package/dist/assets/copy-button-DlOU9KRy.js +1 -0
- package/dist/assets/corner-down-left-B7l-mMrb.js +1 -0
- package/dist/assets/crystal-Bsdphhtm.js +1 -0
- package/dist/assets/css-CnaYoQib.js +1 -0
- package/dist/assets/cypher-p9eYesGn.js +1 -0
- package/dist/assets/d-DWkQyNnU.js +1 -0
- package/dist/assets/diff-ChtP43wD.js +1 -0
- package/dist/assets/dist-3tAAASIu.js +1 -0
- package/dist/assets/dist-AXYnLL_F.js +9 -0
- package/dist/assets/dist-BGyMVrYk.js +11 -0
- package/dist/assets/dist-BN1wE2xy.js +1 -0
- package/dist/assets/dist-CDAjPDtI.js +1 -0
- package/dist/assets/dist-CJBq646Z.js +1 -0
- package/dist/assets/dist-CL_zGtiO.js +1 -0
- package/dist/assets/dist-CM8yt5f_.js +1 -0
- package/dist/assets/dist-CYNLiE2B.js +1 -0
- package/dist/assets/dist-CyOGylYO.js +1 -0
- package/dist/assets/dist-D-bo6lHS.js +1 -0
- package/dist/assets/dist-D7xXmJTH.js +1 -0
- package/dist/assets/dist-DGmHtnYo.js +6 -0
- package/dist/assets/dist-DOcORAD_.js +1 -0
- package/dist/assets/dist-DdeR5kYT.js +2 -0
- package/dist/assets/dist-DyYo49jM.js +1 -0
- package/dist/assets/dist-FOmLcFeD.js +1 -0
- package/dist/assets/dist-FUrprsRT.js +1 -0
- package/dist/assets/dist-JuM3aKGD.js +1 -0
- package/dist/assets/dist-S0tn--lj.js +1 -0
- package/dist/assets/dist-aVl135DR.js +23 -0
- package/dist/assets/dist-y7gdVNBt.js +1 -0
- package/dist/assets/dockerfile-DJkHQgkl.js +1 -0
- package/dist/assets/dtd-DTF72YNO.js +1 -0
- package/dist/assets/dylan-S7ucR4pR.js +1 -0
- package/dist/assets/ebnf-D4c0_ac3.js +1 -0
- package/dist/assets/ecl-BVIeWmwY.js +1 -0
- package/dist/assets/eiffel-BEjRio4Q.js +1 -0
- package/dist/assets/elm-Cshzl8qu.js +1 -0
- package/dist/assets/empty-state-CkET1cGs.js +1 -0
- package/dist/assets/erlang-BV0c4Hdn.js +1 -0
- package/dist/assets/eye-2RNiMHO-.js +1 -0
- package/dist/assets/factor-LKH5gN8L.js +1 -0
- package/dist/assets/fcl-ClOhbFR7.js +1 -0
- package/dist/assets/field-CYhxBv9B.js +1 -0
- package/dist/assets/forth-D6RzAl64.js +1 -0
- package/dist/assets/fortran-Bt6PBEDR.js +1 -0
- package/dist/assets/gas-BHEdbvp9.js +1 -0
- package/dist/assets/gherkin-oBAE_ms0.js +1 -0
- package/dist/assets/groovy-CUpZ7dEl.js +1 -0
- package/dist/assets/haskell-BRsoo5mP.js +1 -0
- package/dist/assets/haxe-DjVOVvNP.js +1 -0
- package/dist/assets/host-wizard-screen-Bs7pSgJk.js +1 -0
- package/dist/assets/http-CLVgA2GD.js +1 -0
- package/dist/assets/idl-BUZw3wgd.js +1 -0
- package/dist/assets/index-B5KQ4hDp.css +2 -0
- package/dist/assets/index-Cz0KzQeb.js +11 -0
- package/dist/assets/javascript-Bu8mWwtz.js +1 -0
- package/dist/assets/julia-CQfgDRfP.js +1 -0
- package/dist/assets/key-round-Cl7KguqM.js +1 -0
- package/dist/assets/layout.store-b9Rb2T9G.js +1 -0
- package/dist/assets/livescript-BXxG0Yva.js +1 -0
- package/dist/assets/login-screen-Co0lYHk4.js +1 -0
- package/dist/assets/lua-DPCEkfD_.js +1 -0
- package/dist/assets/manifest-CxKlFp1I.js +1 -0
- package/dist/assets/markdown-view-BQbaAkAk.js +29 -0
- package/dist/assets/markdown-view-aK-tdIwk.css +1 -0
- package/dist/assets/material-icon-RrSZDDw2.js +1 -0
- package/dist/assets/mathematica-u8YMmbU0.js +1 -0
- package/dist/assets/mbox-BEMVcqjs.js +1 -0
- package/dist/assets/middleware-C1xnEbcF.js +1 -0
- package/dist/assets/mirc-C9zpzAZU.js +1 -0
- package/dist/assets/mllike-B45xgp2S.js +1 -0
- package/dist/assets/modelica-CqHI_q-D.js +1 -0
- package/dist/assets/mscgen-qgSLujhx.js +1 -0
- package/dist/assets/mumps-BSzj6Xyz.js +1 -0
- package/dist/assets/nginx-8H7HVH4w.js +1 -0
- package/dist/assets/nsis-viPvXFiq.js +1 -0
- package/dist/assets/ntriples-CzMBulkp.js +1 -0
- package/dist/assets/octave-D2Q8cUp1.js +1 -0
- package/dist/assets/oz-BwTct_5T.js +1 -0
- package/dist/assets/page-shell-BSEpDNSs.js +1 -0
- package/dist/assets/pascal-Dp_KdFgX.js +1 -0
- package/dist/assets/password-input-CSjwFwJE.js +1 -0
- package/dist/assets/perl-BkSkiI9m.js +1 -0
- package/dist/assets/pig-AF4Hwhju.js +1 -0
- package/dist/assets/port-links-8g2kBRfp.js +1 -0
- package/dist/assets/powershell-D8WpQPaX.js +1 -0
- package/dist/assets/projects-screen-C3yWG8mw.js +1 -0
- package/dist/assets/properties-C357ku5U.js +1 -0
- package/dist/assets/protobuf-D1ij_kNL.js +1 -0
- package/dist/assets/pug-Dwd_FmgJ.js +1 -0
- package/dist/assets/puppet-BZyAS7I4.js +1 -0
- package/dist/assets/python-B_1Jd8HM.js +1 -0
- package/dist/assets/q-B9NhS7L_.js +1 -0
- package/dist/assets/query-D1fTvh27.js +1 -0
- package/dist/assets/r-CxDmU1I4.js +1 -0
- package/dist/assets/rpm-gpDK5sbt.js +1 -0
- package/dist/assets/ruby-ChfNwlCY.js +1 -0
- package/dist/assets/sas-C2OyD2Y6.js +1 -0
- package/dist/assets/scheme-D9qYN0V5.js +1 -0
- package/dist/assets/settings-screen-BM1Y4v3n.js +1 -0
- package/dist/assets/setup-screen--Rk5RNBd.js +1 -0
- package/dist/assets/shell-DwuoZtxw.js +1 -0
- package/dist/assets/sieve-tLWDfEkZ.js +1 -0
- package/dist/assets/simple-mode-DXiLM1-G.js +1 -0
- package/dist/assets/smalltalk-BLp5-jwC.js +1 -0
- package/dist/assets/solr-B78wSQJQ.js +1 -0
- package/dist/assets/sparkles-rMOjCpPO.js +1 -0
- package/dist/assets/sparql-Dz-mlCAC.js +1 -0
- package/dist/assets/spinner-Dhw2p8cg.js +1 -0
- package/dist/assets/spreadsheet-CTIncYxj.js +1 -0
- package/dist/assets/sql-DuQMFDvy.js +1 -0
- package/dist/assets/square-terminal-DL7ubCPR.js +1 -0
- package/dist/assets/stex-DNzd62Q_.js +1 -0
- package/dist/assets/stylus-BOVgKnng.js +1 -0
- package/dist/assets/swift-DKB6_1j6.js +1 -0
- package/dist/assets/tcl-BGfruO1u.js +1 -0
- package/dist/assets/textile-C4jVoHG1.js +1 -0
- package/dist/assets/tiddlywiki-VlUiK86J.js +1 -0
- package/dist/assets/tiki-DTszW0OQ.js +1 -0
- package/dist/assets/toml-BPTmHmyx.js +1 -0
- package/dist/assets/triangle-alert-CKXjnNZK.js +1 -0
- package/dist/assets/troff-C2dAgh7P.js +1 -0
- package/dist/assets/ttcn-PPSTk1ow.js +1 -0
- package/dist/assets/ttcn-cfg-2b4Gb5sE.js +1 -0
- package/dist/assets/turtle-Cx3rYX2g.js +1 -0
- package/dist/assets/use-visual-viewport-CHPXxqd5.js +1 -0
- package/dist/assets/useQuery-CTJ-5_ol.js +1 -0
- package/dist/assets/vb-BthdM_4N.js +1 -0
- package/dist/assets/vbscript-D9f6rSsU.js +1 -0
- package/dist/assets/velocity-CqftRsjg.js +1 -0
- package/dist/assets/verilog-CjI9Y6RI.js +1 -0
- package/dist/assets/vhdl-kS471soi.js +1 -0
- package/dist/assets/webidl-D5jiJvOU.js +1 -0
- package/dist/assets/workspace-screen-FWzP6Hvt.js +8 -0
- package/dist/assets/xquery-BKJx4mf9.js +1 -0
- package/dist/assets/xterm-BrP-ENHg.css +1 -0
- package/dist/assets/xterm-OPnvFLvt.js +22 -0
- package/dist/assets/yacas-CXXgtw0p.js +1 -0
- package/dist/assets/z80-Co3PbHdE.js +1 -0
- package/dist/favicon.svg +1 -0
- package/dist/index.html +29 -0
- package/dist/ios-viewport.js +9 -0
- package/dist/material-icons/3d.svg +1 -0
- package/dist/material-icons/abap.svg +1 -0
- package/dist/material-icons/abc.svg +1 -0
- package/dist/material-icons/actionscript.svg +1 -0
- package/dist/material-icons/ada.svg +1 -0
- package/dist/material-icons/adobe-illustrator.svg +1 -0
- package/dist/material-icons/adobe-illustrator_light.svg +1 -0
- package/dist/material-icons/adobe-photoshop.svg +1 -0
- package/dist/material-icons/adobe-photoshop_light.svg +1 -0
- package/dist/material-icons/adobe-swc.svg +1 -0
- package/dist/material-icons/adonis.svg +1 -0
- package/dist/material-icons/advpl-include.clone.svg +1 -0
- package/dist/material-icons/advpl-ptm.clone.svg +1 -0
- package/dist/material-icons/advpl-tlpp.clone.svg +1 -0
- package/dist/material-icons/advpl.svg +1 -0
- package/dist/material-icons/agent.svg +1 -0
- package/dist/material-icons/ahk2.clone.svg +1 -0
- package/dist/material-icons/amplify.svg +1 -0
- package/dist/material-icons/android.svg +1 -0
- package/dist/material-icons/angular-component.clone.svg +1 -0
- package/dist/material-icons/angular-directive.clone.svg +1 -0
- package/dist/material-icons/angular-guard.clone.svg +1 -0
- package/dist/material-icons/angular-interceptor.clone.svg +1 -0
- package/dist/material-icons/angular-pipe.clone.svg +1 -0
- package/dist/material-icons/angular-resolver.clone.svg +1 -0
- package/dist/material-icons/angular-service.clone.svg +1 -0
- package/dist/material-icons/angular.svg +1 -0
- package/dist/material-icons/antlr.svg +1 -0
- package/dist/material-icons/apiblueprint.svg +1 -0
- package/dist/material-icons/apollo.svg +1 -0
- package/dist/material-icons/applescript.svg +1 -0
- package/dist/material-icons/apps-script.svg +1 -0
- package/dist/material-icons/appveyor.svg +1 -0
- package/dist/material-icons/appwrite.svg +1 -0
- package/dist/material-icons/architecture.svg +1 -0
- package/dist/material-icons/arduino.svg +1 -0
- package/dist/material-icons/asciidoc.svg +1 -0
- package/dist/material-icons/assembly.svg +1 -0
- package/dist/material-icons/astro-config.svg +1 -0
- package/dist/material-icons/astro.svg +1 -0
- package/dist/material-icons/astyle.svg +1 -0
- package/dist/material-icons/audio.svg +1 -0
- package/dist/material-icons/aurelia.svg +1 -0
- package/dist/material-icons/authors.svg +1 -0
- package/dist/material-icons/auto.svg +1 -0
- package/dist/material-icons/auto_light.svg +1 -0
- package/dist/material-icons/autohotkey.svg +1 -0
- package/dist/material-icons/autoit.svg +1 -0
- package/dist/material-icons/azure-pipelines.svg +1 -0
- package/dist/material-icons/azure.svg +1 -0
- package/dist/material-icons/babel.svg +1 -0
- package/dist/material-icons/ballerina.svg +1 -0
- package/dist/material-icons/bashly-hook.svg +1 -0
- package/dist/material-icons/bashly-settings.clone.svg +1 -0
- package/dist/material-icons/bashly-settings_light.clone.svg +1 -0
- package/dist/material-icons/bashly-strings.clone.svg +1 -0
- package/dist/material-icons/bashly-strings_light.clone.svg +1 -0
- package/dist/material-icons/bashly.svg +1 -0
- package/dist/material-icons/bazel.svg +1 -0
- package/dist/material-icons/bbx.svg +1 -0
- package/dist/material-icons/beancount.svg +1 -0
- package/dist/material-icons/bench-js.svg +1 -0
- package/dist/material-icons/bench-jsx.svg +1 -0
- package/dist/material-icons/bench-ts.svg +1 -0
- package/dist/material-icons/bibliography.svg +1 -0
- package/dist/material-icons/bibtex-style.svg +1 -0
- package/dist/material-icons/bicep.svg +1 -0
- package/dist/material-icons/biome.svg +1 -0
- package/dist/material-icons/bitbucket.svg +1 -0
- package/dist/material-icons/bithound.svg +1 -0
- package/dist/material-icons/blender.svg +1 -0
- package/dist/material-icons/blink.svg +1 -0
- package/dist/material-icons/blink_light.svg +1 -0
- package/dist/material-icons/blitz.svg +1 -0
- package/dist/material-icons/bower.svg +1 -0
- package/dist/material-icons/brainfuck.svg +1 -0
- package/dist/material-icons/browserlist.svg +1 -0
- package/dist/material-icons/browserlist_light.svg +1 -0
- package/dist/material-icons/bruno.svg +1 -0
- package/dist/material-icons/buck.svg +1 -0
- package/dist/material-icons/bucklescript.svg +1 -0
- package/dist/material-icons/buildkite.svg +1 -0
- package/dist/material-icons/bun.svg +1 -0
- package/dist/material-icons/bun_light.svg +1 -0
- package/dist/material-icons/c.svg +1 -0
- package/dist/material-icons/c3.svg +1 -0
- package/dist/material-icons/cabal.svg +1 -0
- package/dist/material-icons/caddy.svg +1 -0
- package/dist/material-icons/cadence.svg +1 -0
- package/dist/material-icons/cairo.svg +1 -0
- package/dist/material-icons/cake.svg +1 -0
- package/dist/material-icons/capacitor.svg +1 -0
- package/dist/material-icons/capnp.svg +1 -0
- package/dist/material-icons/cbx.svg +1 -0
- package/dist/material-icons/cds.svg +1 -0
- package/dist/material-icons/certificate.svg +1 -0
- package/dist/material-icons/changelog.svg +1 -0
- package/dist/material-icons/chess.svg +1 -0
- package/dist/material-icons/chess_light.svg +1 -0
- package/dist/material-icons/chromatic.svg +1 -0
- package/dist/material-icons/chrome.svg +1 -0
- package/dist/material-icons/circleci.svg +1 -0
- package/dist/material-icons/circleci_light.svg +1 -0
- package/dist/material-icons/citation.svg +1 -0
- package/dist/material-icons/clangd.svg +1 -0
- package/dist/material-icons/claude.svg +1 -0
- package/dist/material-icons/cline.svg +1 -0
- package/dist/material-icons/clojure.svg +1 -0
- package/dist/material-icons/cloudfoundry.svg +1 -0
- package/dist/material-icons/cmake.svg +1 -0
- package/dist/material-icons/coala.svg +1 -0
- package/dist/material-icons/cobol.svg +1 -0
- package/dist/material-icons/coconut.svg +1 -0
- package/dist/material-icons/code-climate.svg +1 -0
- package/dist/material-icons/code-climate_light.svg +1 -0
- package/dist/material-icons/codecov.svg +1 -0
- package/dist/material-icons/codeowners.svg +1 -0
- package/dist/material-icons/coderabbit-ai.svg +1 -0
- package/dist/material-icons/coffee.svg +1 -0
- package/dist/material-icons/coldfusion.svg +1 -0
- package/dist/material-icons/coloredpetrinets.svg +1 -0
- package/dist/material-icons/command.svg +1 -0
- package/dist/material-icons/commitizen.svg +1 -0
- package/dist/material-icons/commitlint.svg +1 -0
- package/dist/material-icons/concourse.svg +1 -0
- package/dist/material-icons/conduct.svg +1 -0
- package/dist/material-icons/console.svg +1 -0
- package/dist/material-icons/container.clone.svg +1 -0
- package/dist/material-icons/contentlayer.svg +1 -0
- package/dist/material-icons/context.svg +1 -0
- package/dist/material-icons/contributing.svg +1 -0
- package/dist/material-icons/controller.svg +1 -0
- package/dist/material-icons/copilot.svg +1 -0
- package/dist/material-icons/copilot_light.svg +1 -0
- package/dist/material-icons/cpp.svg +1 -0
- package/dist/material-icons/craco.svg +1 -0
- package/dist/material-icons/credits.svg +1 -0
- package/dist/material-icons/crystal.svg +1 -0
- package/dist/material-icons/crystal_light.svg +1 -0
- package/dist/material-icons/csharp.svg +1 -0
- package/dist/material-icons/css-map.svg +1 -0
- package/dist/material-icons/css.svg +1 -0
- package/dist/material-icons/cucumber.svg +1 -0
- package/dist/material-icons/cuda.svg +1 -0
- package/dist/material-icons/cue.svg +1 -0
- package/dist/material-icons/cursor.svg +1 -0
- package/dist/material-icons/cursor_light.svg +1 -0
- package/dist/material-icons/cypress.svg +1 -0
- package/dist/material-icons/d.svg +1 -0
- package/dist/material-icons/dart.svg +1 -0
- package/dist/material-icons/dart_generated.svg +1 -0
- package/dist/material-icons/database.svg +1 -0
- package/dist/material-icons/deepsource.svg +1 -0
- package/dist/material-icons/denizenscript.svg +1 -0
- package/dist/material-icons/deno.svg +1 -0
- package/dist/material-icons/deno_light.svg +1 -0
- package/dist/material-icons/dependabot.svg +1 -0
- package/dist/material-icons/dependencies-update.svg +1 -0
- package/dist/material-icons/dhall.svg +1 -0
- package/dist/material-icons/diff.svg +1 -0
- package/dist/material-icons/dinophp.svg +1 -0
- package/dist/material-icons/disc.svg +1 -0
- package/dist/material-icons/django.svg +1 -0
- package/dist/material-icons/dll.svg +1 -0
- package/dist/material-icons/docker.svg +1 -0
- package/dist/material-icons/doctex-installer.svg +1 -0
- package/dist/material-icons/doctex.clone.svg +1 -0
- package/dist/material-icons/document.svg +1 -0
- package/dist/material-icons/dotjs.svg +1 -0
- package/dist/material-icons/drawio.svg +1 -0
- package/dist/material-icons/drizzle.svg +1 -0
- package/dist/material-icons/drone.svg +1 -0
- package/dist/material-icons/drone_light.svg +1 -0
- package/dist/material-icons/dtx.clone.svg +1 -0
- package/dist/material-icons/duc.svg +1 -0
- package/dist/material-icons/dune.svg +1 -0
- package/dist/material-icons/edge.svg +1 -0
- package/dist/material-icons/editorconfig.svg +1 -0
- package/dist/material-icons/ejs.svg +1 -0
- package/dist/material-icons/elixir.svg +1 -0
- package/dist/material-icons/elm.svg +1 -0
- package/dist/material-icons/email.svg +1 -0
- package/dist/material-icons/ember.svg +1 -0
- package/dist/material-icons/epub.svg +1 -0
- package/dist/material-icons/erlang.svg +1 -0
- package/dist/material-icons/esbuild.svg +1 -0
- package/dist/material-icons/eslint.svg +1 -0
- package/dist/material-icons/excalidraw.svg +1 -0
- package/dist/material-icons/exe.svg +1 -0
- package/dist/material-icons/expo.svg +1 -0
- package/dist/material-icons/expo_light.svg +1 -0
- package/dist/material-icons/fastlane.svg +1 -0
- package/dist/material-icons/favicon.svg +1 -0
- package/dist/material-icons/figma.svg +1 -0
- package/dist/material-icons/file.svg +1 -0
- package/dist/material-icons/firebase.svg +1 -0
- package/dist/material-icons/flash.svg +1 -0
- package/dist/material-icons/flow.svg +1 -0
- package/dist/material-icons/folder-admin-open.svg +1 -0
- package/dist/material-icons/folder-admin.svg +1 -0
- package/dist/material-icons/folder-android-open.svg +1 -0
- package/dist/material-icons/folder-android.svg +1 -0
- package/dist/material-icons/folder-angular-open.svg +1 -0
- package/dist/material-icons/folder-angular.svg +1 -0
- package/dist/material-icons/folder-animation-open.svg +1 -0
- package/dist/material-icons/folder-animation.svg +1 -0
- package/dist/material-icons/folder-ansible-open.svg +1 -0
- package/dist/material-icons/folder-ansible.svg +1 -0
- package/dist/material-icons/folder-api-open.svg +1 -0
- package/dist/material-icons/folder-api.svg +1 -0
- package/dist/material-icons/folder-apollo-open.svg +1 -0
- package/dist/material-icons/folder-apollo.svg +1 -0
- package/dist/material-icons/folder-app-open.svg +1 -0
- package/dist/material-icons/folder-app.svg +1 -0
- package/dist/material-icons/folder-appwrite-open.svg +1 -0
- package/dist/material-icons/folder-appwrite.svg +1 -0
- package/dist/material-icons/folder-archive-open.svg +1 -0
- package/dist/material-icons/folder-archive.svg +1 -0
- package/dist/material-icons/folder-assembly-open.svg +1 -0
- package/dist/material-icons/folder-assembly.svg +1 -0
- package/dist/material-icons/folder-astro-open.svg +1 -0
- package/dist/material-icons/folder-astro.svg +1 -0
- package/dist/material-icons/folder-atom-open.svg +1 -0
- package/dist/material-icons/folder-atom.svg +1 -0
- package/dist/material-icons/folder-attachment-open.svg +1 -0
- package/dist/material-icons/folder-attachment.svg +1 -0
- package/dist/material-icons/folder-audio-open.svg +1 -0
- package/dist/material-icons/folder-audio.svg +1 -0
- package/dist/material-icons/folder-aurelia-open.svg +1 -0
- package/dist/material-icons/folder-aurelia.svg +1 -0
- package/dist/material-icons/folder-aws-open.svg +1 -0
- package/dist/material-icons/folder-aws.svg +1 -0
- package/dist/material-icons/folder-azure-pipelines-open.svg +1 -0
- package/dist/material-icons/folder-azure-pipelines.svg +1 -0
- package/dist/material-icons/folder-backup-open.svg +1 -0
- package/dist/material-icons/folder-backup.svg +1 -0
- package/dist/material-icons/folder-base-open.svg +1 -0
- package/dist/material-icons/folder-base.svg +1 -0
- package/dist/material-icons/folder-batch-open.svg +1 -0
- package/dist/material-icons/folder-batch.svg +1 -0
- package/dist/material-icons/folder-benchmark-open.svg +1 -0
- package/dist/material-icons/folder-benchmark.svg +1 -0
- package/dist/material-icons/folder-bibliography-open.svg +1 -0
- package/dist/material-icons/folder-bibliography.svg +1 -0
- package/dist/material-icons/folder-bicep-open.svg +1 -0
- package/dist/material-icons/folder-bicep.svg +1 -0
- package/dist/material-icons/folder-blender-open.svg +1 -0
- package/dist/material-icons/folder-blender.svg +1 -0
- package/dist/material-icons/folder-bloc-open.svg +1 -0
- package/dist/material-icons/folder-bloc.svg +1 -0
- package/dist/material-icons/folder-bower-open.svg +1 -0
- package/dist/material-icons/folder-bower.svg +1 -0
- package/dist/material-icons/folder-buildkite-open.svg +1 -0
- package/dist/material-icons/folder-buildkite.svg +1 -0
- package/dist/material-icons/folder-cart-open.svg +1 -0
- package/dist/material-icons/folder-cart.svg +1 -0
- package/dist/material-icons/folder-changesets-open.svg +1 -0
- package/dist/material-icons/folder-changesets.svg +1 -0
- package/dist/material-icons/folder-ci-open.svg +1 -0
- package/dist/material-icons/folder-ci.svg +1 -0
- package/dist/material-icons/folder-circleci-open.svg +1 -0
- package/dist/material-icons/folder-circleci.svg +1 -0
- package/dist/material-icons/folder-class-open.svg +1 -0
- package/dist/material-icons/folder-class.svg +1 -0
- package/dist/material-icons/folder-claude-open.svg +1 -0
- package/dist/material-icons/folder-claude.svg +1 -0
- package/dist/material-icons/folder-client-open.svg +1 -0
- package/dist/material-icons/folder-client.svg +1 -0
- package/dist/material-icons/folder-cline-open.svg +1 -0
- package/dist/material-icons/folder-cline.svg +1 -0
- package/dist/material-icons/folder-cloud-functions-open.svg +1 -0
- package/dist/material-icons/folder-cloud-functions.svg +1 -0
- package/dist/material-icons/folder-cloudflare-open.svg +1 -0
- package/dist/material-icons/folder-cloudflare.svg +1 -0
- package/dist/material-icons/folder-cluster-open.svg +1 -0
- package/dist/material-icons/folder-cluster.svg +1 -0
- package/dist/material-icons/folder-cobol-open.svg +1 -0
- package/dist/material-icons/folder-cobol.svg +1 -0
- package/dist/material-icons/folder-command-open.svg +1 -0
- package/dist/material-icons/folder-command.svg +1 -0
- package/dist/material-icons/folder-components-open.svg +1 -0
- package/dist/material-icons/folder-components.svg +1 -0
- package/dist/material-icons/folder-config-open.svg +1 -0
- package/dist/material-icons/folder-config.svg +1 -0
- package/dist/material-icons/folder-connection-open.svg +1 -0
- package/dist/material-icons/folder-connection.svg +1 -0
- package/dist/material-icons/folder-console-open.svg +1 -0
- package/dist/material-icons/folder-console.svg +1 -0
- package/dist/material-icons/folder-constant-open.svg +1 -0
- package/dist/material-icons/folder-constant.svg +1 -0
- package/dist/material-icons/folder-container-open.svg +1 -0
- package/dist/material-icons/folder-container.svg +1 -0
- package/dist/material-icons/folder-content-open.svg +1 -0
- package/dist/material-icons/folder-content.svg +1 -0
- package/dist/material-icons/folder-context-open.svg +1 -0
- package/dist/material-icons/folder-context.svg +1 -0
- package/dist/material-icons/folder-contract-open.svg +1 -0
- package/dist/material-icons/folder-contract.svg +1 -0
- package/dist/material-icons/folder-controller-open.svg +1 -0
- package/dist/material-icons/folder-controller.svg +1 -0
- package/dist/material-icons/folder-core-open.svg +1 -0
- package/dist/material-icons/folder-core.svg +1 -0
- package/dist/material-icons/folder-coverage-open.svg +1 -0
- package/dist/material-icons/folder-coverage.svg +1 -0
- package/dist/material-icons/folder-css-open.svg +1 -0
- package/dist/material-icons/folder-css.svg +1 -0
- package/dist/material-icons/folder-cue-open.svg +1 -0
- package/dist/material-icons/folder-cue.svg +1 -0
- package/dist/material-icons/folder-cursor-open.svg +1 -0
- package/dist/material-icons/folder-cursor-open_light.svg +1 -0
- package/dist/material-icons/folder-cursor.svg +1 -0
- package/dist/material-icons/folder-cursor_light.svg +1 -0
- package/dist/material-icons/folder-custom-open.svg +1 -0
- package/dist/material-icons/folder-custom.svg +1 -0
- package/dist/material-icons/folder-cypress-open.svg +1 -0
- package/dist/material-icons/folder-cypress.svg +1 -0
- package/dist/material-icons/folder-dal-open.svg +1 -0
- package/dist/material-icons/folder-dal.svg +1 -0
- package/dist/material-icons/folder-dart-open.svg +1 -0
- package/dist/material-icons/folder-dart.svg +1 -0
- package/dist/material-icons/folder-database-open.svg +1 -0
- package/dist/material-icons/folder-database.svg +1 -0
- package/dist/material-icons/folder-debug-open.svg +1 -0
- package/dist/material-icons/folder-debug.svg +1 -0
- package/dist/material-icons/folder-decorators-open.svg +1 -0
- package/dist/material-icons/folder-decorators.svg +1 -0
- package/dist/material-icons/folder-delta-open.svg +1 -0
- package/dist/material-icons/folder-delta.svg +1 -0
- package/dist/material-icons/folder-deprecated-open.clone.svg +1 -0
- package/dist/material-icons/folder-deprecated.clone.svg +1 -0
- package/dist/material-icons/folder-desktop-open.svg +1 -0
- package/dist/material-icons/folder-desktop.svg +1 -0
- package/dist/material-icons/folder-development-open.clone.svg +1 -0
- package/dist/material-icons/folder-development.clone.svg +1 -0
- package/dist/material-icons/folder-directive-open.svg +1 -0
- package/dist/material-icons/folder-directive.svg +1 -0
- package/dist/material-icons/folder-dist-open.svg +1 -0
- package/dist/material-icons/folder-dist.svg +1 -0
- package/dist/material-icons/folder-docker-open.svg +1 -0
- package/dist/material-icons/folder-docker.svg +1 -0
- package/dist/material-icons/folder-docs-open.svg +1 -0
- package/dist/material-icons/folder-docs.svg +1 -0
- package/dist/material-icons/folder-download-open.svg +1 -0
- package/dist/material-icons/folder-download.svg +1 -0
- package/dist/material-icons/folder-drizzle-open.svg +1 -0
- package/dist/material-icons/folder-drizzle.svg +1 -0
- package/dist/material-icons/folder-dump-open.svg +1 -0
- package/dist/material-icons/folder-dump.svg +1 -0
- package/dist/material-icons/folder-eas-open.clone.svg +1 -0
- package/dist/material-icons/folder-eas.clone.svg +1 -0
- package/dist/material-icons/folder-element-open.svg +1 -0
- package/dist/material-icons/folder-element.svg +1 -0
- package/dist/material-icons/folder-enum-open.svg +1 -0
- package/dist/material-icons/folder-enum.svg +1 -0
- package/dist/material-icons/folder-environment-open.svg +1 -0
- package/dist/material-icons/folder-environment.svg +1 -0
- package/dist/material-icons/folder-error-open.svg +1 -0
- package/dist/material-icons/folder-error.svg +1 -0
- package/dist/material-icons/folder-eslint-open.svg +1 -0
- package/dist/material-icons/folder-eslint.svg +1 -0
- package/dist/material-icons/folder-event-open.svg +1 -0
- package/dist/material-icons/folder-event.svg +1 -0
- package/dist/material-icons/folder-examples-open.svg +1 -0
- package/dist/material-icons/folder-examples.svg +1 -0
- package/dist/material-icons/folder-expo-open.svg +1 -0
- package/dist/material-icons/folder-expo.svg +1 -0
- package/dist/material-icons/folder-export-open.svg +1 -0
- package/dist/material-icons/folder-export.svg +1 -0
- package/dist/material-icons/folder-fastlane-open.svg +1 -0
- package/dist/material-icons/folder-fastlane.svg +1 -0
- package/dist/material-icons/folder-favicon-open.svg +1 -0
- package/dist/material-icons/folder-favicon.svg +1 -0
- package/dist/material-icons/folder-features-open.svg +1 -0
- package/dist/material-icons/folder-features.svg +1 -0
- package/dist/material-icons/folder-filter-open.svg +1 -0
- package/dist/material-icons/folder-filter.svg +1 -0
- package/dist/material-icons/folder-firebase-open.svg +1 -0
- package/dist/material-icons/folder-firebase.svg +1 -0
- package/dist/material-icons/folder-firestore-open.svg +1 -0
- package/dist/material-icons/folder-firestore.svg +1 -0
- package/dist/material-icons/folder-flow-open.svg +1 -0
- package/dist/material-icons/folder-flow.svg +1 -0
- package/dist/material-icons/folder-flutter-open.svg +1 -0
- package/dist/material-icons/folder-flutter.svg +1 -0
- package/dist/material-icons/folder-font-open.svg +1 -0
- package/dist/material-icons/folder-font.svg +1 -0
- package/dist/material-icons/folder-forgejo-open.svg +1 -0
- package/dist/material-icons/folder-forgejo.svg +1 -0
- package/dist/material-icons/folder-form-open.svg +1 -0
- package/dist/material-icons/folder-form.svg +1 -0
- package/dist/material-icons/folder-functions-open.svg +1 -0
- package/dist/material-icons/folder-functions.svg +1 -0
- package/dist/material-icons/folder-gamemaker-open.svg +1 -0
- package/dist/material-icons/folder-gamemaker.svg +1 -0
- package/dist/material-icons/folder-gemini-ai-open.svg +1 -0
- package/dist/material-icons/folder-gemini-ai.svg +1 -0
- package/dist/material-icons/folder-generator-open.svg +1 -0
- package/dist/material-icons/folder-generator.svg +1 -0
- package/dist/material-icons/folder-gh-workflows-open.svg +1 -0
- package/dist/material-icons/folder-gh-workflows.svg +1 -0
- package/dist/material-icons/folder-git-open.svg +1 -0
- package/dist/material-icons/folder-git.svg +1 -0
- package/dist/material-icons/folder-gitea-open.svg +1 -0
- package/dist/material-icons/folder-gitea-workflows-open.clone.svg +1 -0
- package/dist/material-icons/folder-gitea-workflows.clone.svg +1 -0
- package/dist/material-icons/folder-gitea.svg +1 -0
- package/dist/material-icons/folder-github-open.svg +1 -0
- package/dist/material-icons/folder-github.svg +1 -0
- package/dist/material-icons/folder-gitlab-open.svg +1 -0
- package/dist/material-icons/folder-gitlab.svg +1 -0
- package/dist/material-icons/folder-global-open.svg +1 -0
- package/dist/material-icons/folder-global.svg +1 -0
- package/dist/material-icons/folder-go-open.svg +1 -0
- package/dist/material-icons/folder-go.svg +1 -0
- package/dist/material-icons/folder-godot-open.svg +1 -0
- package/dist/material-icons/folder-godot.svg +1 -0
- package/dist/material-icons/folder-gradle-open.svg +1 -0
- package/dist/material-icons/folder-gradle.svg +1 -0
- package/dist/material-icons/folder-graphql-open.svg +1 -0
- package/dist/material-icons/folder-graphql.svg +1 -0
- package/dist/material-icons/folder-guard-open.svg +1 -0
- package/dist/material-icons/folder-guard.svg +1 -0
- package/dist/material-icons/folder-gulp-open.svg +1 -0
- package/dist/material-icons/folder-gulp.svg +1 -0
- package/dist/material-icons/folder-helm-open.svg +1 -0
- package/dist/material-icons/folder-helm.svg +1 -0
- package/dist/material-icons/folder-helper-open.svg +1 -0
- package/dist/material-icons/folder-helper.svg +1 -0
- package/dist/material-icons/folder-home-open.svg +1 -0
- package/dist/material-icons/folder-home.svg +1 -0
- package/dist/material-icons/folder-hook-open.svg +1 -0
- package/dist/material-icons/folder-hook.svg +1 -0
- package/dist/material-icons/folder-husky-open.svg +1 -0
- package/dist/material-icons/folder-husky.svg +1 -0
- package/dist/material-icons/folder-i18n-open.svg +1 -0
- package/dist/material-icons/folder-i18n.svg +1 -0
- package/dist/material-icons/folder-images-open.svg +1 -0
- package/dist/material-icons/folder-images.svg +1 -0
- package/dist/material-icons/folder-import-open.svg +1 -0
- package/dist/material-icons/folder-import.svg +1 -0
- package/dist/material-icons/folder-include-open.svg +1 -0
- package/dist/material-icons/folder-include.svg +1 -0
- package/dist/material-icons/folder-input-open.svg +1 -0
- package/dist/material-icons/folder-input.svg +1 -0
- package/dist/material-icons/folder-instructions-open.clone.svg +1 -0
- package/dist/material-icons/folder-instructions.clone.svg +1 -0
- package/dist/material-icons/folder-intellij-open.svg +1 -0
- package/dist/material-icons/folder-intellij-open_light.svg +1 -0
- package/dist/material-icons/folder-intellij.svg +1 -0
- package/dist/material-icons/folder-intellij_light.svg +1 -0
- package/dist/material-icons/folder-interceptor-open.svg +1 -0
- package/dist/material-icons/folder-interceptor.svg +1 -0
- package/dist/material-icons/folder-interface-open.svg +1 -0
- package/dist/material-icons/folder-interface.svg +1 -0
- package/dist/material-icons/folder-ios-open.svg +1 -0
- package/dist/material-icons/folder-ios.svg +1 -0
- package/dist/material-icons/folder-java-open.svg +1 -0
- package/dist/material-icons/folder-java.svg +1 -0
- package/dist/material-icons/folder-javascript-open.svg +1 -0
- package/dist/material-icons/folder-javascript.svg +1 -0
- package/dist/material-icons/folder-jinja-open.svg +1 -0
- package/dist/material-icons/folder-jinja-open_light.svg +1 -0
- package/dist/material-icons/folder-jinja.svg +1 -0
- package/dist/material-icons/folder-jinja_light.svg +1 -0
- package/dist/material-icons/folder-job-open.svg +1 -0
- package/dist/material-icons/folder-job.svg +1 -0
- package/dist/material-icons/folder-json-open.svg +1 -0
- package/dist/material-icons/folder-json.svg +1 -0
- package/dist/material-icons/folder-jupyter-open.svg +1 -0
- package/dist/material-icons/folder-jupyter.svg +1 -0
- package/dist/material-icons/folder-keys-open.svg +1 -0
- package/dist/material-icons/folder-keys.svg +1 -0
- package/dist/material-icons/folder-kotlin-open.svg +1 -0
- package/dist/material-icons/folder-kotlin.svg +1 -0
- package/dist/material-icons/folder-kubernetes-open.svg +1 -0
- package/dist/material-icons/folder-kubernetes.svg +1 -0
- package/dist/material-icons/folder-kusto-open.svg +1 -0
- package/dist/material-icons/folder-kusto.svg +1 -0
- package/dist/material-icons/folder-layout-open.svg +1 -0
- package/dist/material-icons/folder-layout.svg +1 -0
- package/dist/material-icons/folder-lefthook-open.svg +1 -0
- package/dist/material-icons/folder-lefthook.svg +1 -0
- package/dist/material-icons/folder-less-open.svg +1 -0
- package/dist/material-icons/folder-less.svg +1 -0
- package/dist/material-icons/folder-lib-open.svg +1 -0
- package/dist/material-icons/folder-lib.svg +1 -0
- package/dist/material-icons/folder-license-open.svg +1 -0
- package/dist/material-icons/folder-license.svg +1 -0
- package/dist/material-icons/folder-link-open.svg +1 -0
- package/dist/material-icons/folder-link.svg +1 -0
- package/dist/material-icons/folder-linux-open.svg +1 -0
- package/dist/material-icons/folder-linux.svg +1 -0
- package/dist/material-icons/folder-liquibase-open.svg +1 -0
- package/dist/material-icons/folder-liquibase.svg +1 -0
- package/dist/material-icons/folder-log-open.svg +1 -0
- package/dist/material-icons/folder-log.svg +1 -0
- package/dist/material-icons/folder-lottie-open.svg +1 -0
- package/dist/material-icons/folder-lottie.svg +1 -0
- package/dist/material-icons/folder-lua-open.svg +1 -0
- package/dist/material-icons/folder-lua.svg +1 -0
- package/dist/material-icons/folder-luau-open.svg +1 -0
- package/dist/material-icons/folder-luau.svg +1 -0
- package/dist/material-icons/folder-macos-open.svg +1 -0
- package/dist/material-icons/folder-macos.svg +1 -0
- package/dist/material-icons/folder-mail-open.svg +1 -0
- package/dist/material-icons/folder-mail.svg +1 -0
- package/dist/material-icons/folder-mappings-open.svg +1 -0
- package/dist/material-icons/folder-mappings.svg +1 -0
- package/dist/material-icons/folder-markdown-open.svg +1 -0
- package/dist/material-icons/folder-markdown.svg +1 -0
- package/dist/material-icons/folder-mercurial-open.svg +1 -0
- package/dist/material-icons/folder-mercurial.svg +1 -0
- package/dist/material-icons/folder-messages-open.svg +1 -0
- package/dist/material-icons/folder-messages.svg +1 -0
- package/dist/material-icons/folder-meta-open.svg +1 -0
- package/dist/material-icons/folder-meta.svg +1 -0
- package/dist/material-icons/folder-metro-open.svg +1 -0
- package/dist/material-icons/folder-metro.svg +1 -0
- package/dist/material-icons/folder-middleware-open.svg +1 -0
- package/dist/material-icons/folder-middleware.svg +1 -0
- package/dist/material-icons/folder-migrations-open.svg +1 -0
- package/dist/material-icons/folder-migrations.svg +1 -0
- package/dist/material-icons/folder-mjml-open.svg +1 -0
- package/dist/material-icons/folder-mjml.svg +1 -0
- package/dist/material-icons/folder-mobile-open.svg +1 -0
- package/dist/material-icons/folder-mobile.svg +1 -0
- package/dist/material-icons/folder-mock-open.svg +1 -0
- package/dist/material-icons/folder-mock.svg +1 -0
- package/dist/material-icons/folder-mojo-open.svg +1 -0
- package/dist/material-icons/folder-mojo.svg +1 -0
- package/dist/material-icons/folder-molecule-open.svg +1 -0
- package/dist/material-icons/folder-molecule.svg +1 -0
- package/dist/material-icons/folder-moon-open.svg +1 -0
- package/dist/material-icons/folder-moon.svg +1 -0
- package/dist/material-icons/folder-netlify-open.svg +1 -0
- package/dist/material-icons/folder-netlify.svg +1 -0
- package/dist/material-icons/folder-next-open.svg +1 -0
- package/dist/material-icons/folder-next.svg +1 -0
- package/dist/material-icons/folder-nginx-open.svg +1 -0
- package/dist/material-icons/folder-nginx.svg +1 -0
- package/dist/material-icons/folder-ngrx-actions-open.clone.svg +1 -0
- package/dist/material-icons/folder-ngrx-actions.clone.svg +1 -0
- package/dist/material-icons/folder-ngrx-effects-open.clone.svg +1 -0
- package/dist/material-icons/folder-ngrx-effects.clone.svg +1 -0
- package/dist/material-icons/folder-ngrx-entities-open.clone.svg +1 -0
- package/dist/material-icons/folder-ngrx-entities.clone.svg +1 -0
- package/dist/material-icons/folder-ngrx-reducer-open.clone.svg +1 -0
- package/dist/material-icons/folder-ngrx-reducer.clone.svg +1 -0
- package/dist/material-icons/folder-ngrx-selectors-open.clone.svg +1 -0
- package/dist/material-icons/folder-ngrx-selectors.clone.svg +1 -0
- package/dist/material-icons/folder-ngrx-state-open.clone.svg +1 -0
- package/dist/material-icons/folder-ngrx-state.clone.svg +1 -0
- package/dist/material-icons/folder-ngrx-store-open.svg +1 -0
- package/dist/material-icons/folder-ngrx-store.svg +1 -0
- package/dist/material-icons/folder-node-open.svg +1 -0
- package/dist/material-icons/folder-node.svg +1 -0
- package/dist/material-icons/folder-nuxt-open.svg +1 -0
- package/dist/material-icons/folder-nuxt.svg +1 -0
- package/dist/material-icons/folder-obsidian-open.svg +1 -0
- package/dist/material-icons/folder-obsidian.svg +1 -0
- package/dist/material-icons/folder-open.svg +1 -0
- package/dist/material-icons/folder-opencode-open.svg +1 -0
- package/dist/material-icons/folder-opencode.svg +1 -0
- package/dist/material-icons/folder-organism-open.svg +1 -0
- package/dist/material-icons/folder-organism.svg +1 -0
- package/dist/material-icons/folder-other-open.svg +1 -0
- package/dist/material-icons/folder-other.svg +1 -0
- package/dist/material-icons/folder-packages-open.svg +1 -0
- package/dist/material-icons/folder-packages.svg +1 -0
- package/dist/material-icons/folder-pdf-open.svg +1 -0
- package/dist/material-icons/folder-pdf.svg +1 -0
- package/dist/material-icons/folder-pdm-open.svg +1 -0
- package/dist/material-icons/folder-pdm.svg +1 -0
- package/dist/material-icons/folder-php-open.svg +1 -0
- package/dist/material-icons/folder-php.svg +1 -0
- package/dist/material-icons/folder-phpmailer-open.svg +1 -0
- package/dist/material-icons/folder-phpmailer.svg +1 -0
- package/dist/material-icons/folder-pipe-open.svg +1 -0
- package/dist/material-icons/folder-pipe.svg +1 -0
- package/dist/material-icons/folder-plastic-open.svg +1 -0
- package/dist/material-icons/folder-plastic.svg +1 -0
- package/dist/material-icons/folder-plugin-open.svg +1 -0
- package/dist/material-icons/folder-plugin.svg +1 -0
- package/dist/material-icons/folder-policy-open.svg +1 -0
- package/dist/material-icons/folder-policy.svg +1 -0
- package/dist/material-icons/folder-postman-open.svg +1 -0
- package/dist/material-icons/folder-postman.svg +1 -0
- package/dist/material-icons/folder-powershell-open.svg +1 -0
- package/dist/material-icons/folder-powershell.svg +1 -0
- package/dist/material-icons/folder-prisma-open.svg +1 -0
- package/dist/material-icons/folder-prisma.svg +1 -0
- package/dist/material-icons/folder-private-open.svg +1 -0
- package/dist/material-icons/folder-private.svg +1 -0
- package/dist/material-icons/folder-project-open.svg +1 -0
- package/dist/material-icons/folder-project.svg +1 -0
- package/dist/material-icons/folder-prompts-open.svg +1 -0
- package/dist/material-icons/folder-prompts.svg +1 -0
- package/dist/material-icons/folder-proto-open.svg +1 -0
- package/dist/material-icons/folder-proto.svg +1 -0
- package/dist/material-icons/folder-public-open.svg +1 -0
- package/dist/material-icons/folder-public.svg +1 -0
- package/dist/material-icons/folder-python-open.svg +1 -0
- package/dist/material-icons/folder-python.svg +1 -0
- package/dist/material-icons/folder-pytorch-open.svg +1 -0
- package/dist/material-icons/folder-pytorch.svg +1 -0
- package/dist/material-icons/folder-quasar-open.svg +1 -0
- package/dist/material-icons/folder-quasar.svg +1 -0
- package/dist/material-icons/folder-queue-open.svg +1 -0
- package/dist/material-icons/folder-queue.svg +1 -0
- package/dist/material-icons/folder-r-open.svg +1 -0
- package/dist/material-icons/folder-r.svg +1 -0
- package/dist/material-icons/folder-react-components-open.svg +1 -0
- package/dist/material-icons/folder-react-components.svg +1 -0
- package/dist/material-icons/folder-redis-open.clone.svg +1 -0
- package/dist/material-icons/folder-redis.clone.svg +1 -0
- package/dist/material-icons/folder-redux-actions-open.clone.svg +1 -0
- package/dist/material-icons/folder-redux-actions.clone.svg +1 -0
- package/dist/material-icons/folder-redux-reducer-open.svg +1 -0
- package/dist/material-icons/folder-redux-reducer.svg +1 -0
- package/dist/material-icons/folder-redux-selector-open.clone.svg +1 -0
- package/dist/material-icons/folder-redux-selector.clone.svg +1 -0
- package/dist/material-icons/folder-redux-store-open.clone.svg +1 -0
- package/dist/material-icons/folder-redux-store.clone.svg +1 -0
- package/dist/material-icons/folder-redux-toolkit-open.clone.svg +1 -0
- package/dist/material-icons/folder-redux-toolkit.clone.svg +1 -0
- package/dist/material-icons/folder-repository-open.svg +1 -0
- package/dist/material-icons/folder-repository.svg +1 -0
- package/dist/material-icons/folder-resolver-open.svg +1 -0
- package/dist/material-icons/folder-resolver.svg +1 -0
- package/dist/material-icons/folder-resource-open.svg +1 -0
- package/dist/material-icons/folder-resource.svg +1 -0
- package/dist/material-icons/folder-review-open.svg +1 -0
- package/dist/material-icons/folder-review.svg +1 -0
- package/dist/material-icons/folder-robot-open.svg +1 -0
- package/dist/material-icons/folder-robot.svg +1 -0
- package/dist/material-icons/folder-root-open.svg +1 -0
- package/dist/material-icons/folder-root.svg +1 -0
- package/dist/material-icons/folder-routes-open.svg +1 -0
- package/dist/material-icons/folder-routes.svg +1 -0
- package/dist/material-icons/folder-rules-open.svg +1 -0
- package/dist/material-icons/folder-rules.svg +1 -0
- package/dist/material-icons/folder-rust-open.svg +1 -0
- package/dist/material-icons/folder-rust.svg +1 -0
- package/dist/material-icons/folder-salt-open.svg +1 -0
- package/dist/material-icons/folder-salt.svg +1 -0
- package/dist/material-icons/folder-sandbox-open.svg +1 -0
- package/dist/material-icons/folder-sandbox.svg +1 -0
- package/dist/material-icons/folder-sass-open.svg +1 -0
- package/dist/material-icons/folder-sass.svg +1 -0
- package/dist/material-icons/folder-scala-open.svg +1 -0
- package/dist/material-icons/folder-scala.svg +1 -0
- package/dist/material-icons/folder-scons-open.svg +1 -0
- package/dist/material-icons/folder-scons.svg +1 -0
- package/dist/material-icons/folder-scrap-open.clone.svg +1 -0
- package/dist/material-icons/folder-scrap.clone.svg +1 -0
- package/dist/material-icons/folder-scripts-open.svg +1 -0
- package/dist/material-icons/folder-scripts.svg +1 -0
- package/dist/material-icons/folder-secure-open.svg +1 -0
- package/dist/material-icons/folder-secure.svg +1 -0
- package/dist/material-icons/folder-seeders-open.svg +1 -0
- package/dist/material-icons/folder-seeders.svg +1 -0
- package/dist/material-icons/folder-server-open.svg +1 -0
- package/dist/material-icons/folder-server.svg +1 -0
- package/dist/material-icons/folder-serverless-open.svg +1 -0
- package/dist/material-icons/folder-serverless.svg +1 -0
- package/dist/material-icons/folder-shader-open.svg +1 -0
- package/dist/material-icons/folder-shader.svg +1 -0
- package/dist/material-icons/folder-shared-open.svg +1 -0
- package/dist/material-icons/folder-shared.svg +1 -0
- package/dist/material-icons/folder-simulations-open.svg +1 -0
- package/dist/material-icons/folder-simulations.svg +1 -0
- package/dist/material-icons/folder-skills-open.svg +1 -0
- package/dist/material-icons/folder-skills.svg +1 -0
- package/dist/material-icons/folder-snapcraft-open.svg +1 -0
- package/dist/material-icons/folder-snapcraft.svg +1 -0
- package/dist/material-icons/folder-snippet-open.svg +1 -0
- package/dist/material-icons/folder-snippet.svg +1 -0
- package/dist/material-icons/folder-src-open.svg +1 -0
- package/dist/material-icons/folder-src-tauri-open.svg +1 -0
- package/dist/material-icons/folder-src-tauri.svg +1 -0
- package/dist/material-icons/folder-src.svg +1 -0
- package/dist/material-icons/folder-stack-open.svg +1 -0
- package/dist/material-icons/folder-stack.svg +1 -0
- package/dist/material-icons/folder-stencil-open.svg +1 -0
- package/dist/material-icons/folder-stencil.svg +1 -0
- package/dist/material-icons/folder-store-open.svg +1 -0
- package/dist/material-icons/folder-store.svg +1 -0
- package/dist/material-icons/folder-storybook-open.svg +1 -0
- package/dist/material-icons/folder-storybook.svg +1 -0
- package/dist/material-icons/folder-stylus-open.svg +1 -0
- package/dist/material-icons/folder-stylus.svg +1 -0
- package/dist/material-icons/folder-sublime-open.svg +1 -0
- package/dist/material-icons/folder-sublime.svg +1 -0
- package/dist/material-icons/folder-supabase-open.svg +1 -0
- package/dist/material-icons/folder-supabase.svg +1 -0
- package/dist/material-icons/folder-svelte-open.svg +1 -0
- package/dist/material-icons/folder-svelte.svg +1 -0
- package/dist/material-icons/folder-svg-open.svg +1 -0
- package/dist/material-icons/folder-svg.svg +1 -0
- package/dist/material-icons/folder-syntax-open.svg +1 -0
- package/dist/material-icons/folder-syntax.svg +1 -0
- package/dist/material-icons/folder-target-open.svg +1 -0
- package/dist/material-icons/folder-target.svg +1 -0
- package/dist/material-icons/folder-taskfile-open.svg +1 -0
- package/dist/material-icons/folder-taskfile.svg +1 -0
- package/dist/material-icons/folder-tasks-open.svg +1 -0
- package/dist/material-icons/folder-tasks.svg +1 -0
- package/dist/material-icons/folder-television-open.svg +1 -0
- package/dist/material-icons/folder-television.svg +1 -0
- package/dist/material-icons/folder-temp-open.svg +1 -0
- package/dist/material-icons/folder-temp.svg +1 -0
- package/dist/material-icons/folder-template-open.svg +1 -0
- package/dist/material-icons/folder-template.svg +1 -0
- package/dist/material-icons/folder-terraform-open.svg +1 -0
- package/dist/material-icons/folder-terraform.svg +1 -0
- package/dist/material-icons/folder-test-open.svg +1 -0
- package/dist/material-icons/folder-test.svg +1 -0
- package/dist/material-icons/folder-theme-open.svg +1 -0
- package/dist/material-icons/folder-theme.svg +1 -0
- package/dist/material-icons/folder-toc-open.svg +1 -0
- package/dist/material-icons/folder-toc.svg +1 -0
- package/dist/material-icons/folder-tools-open.svg +1 -0
- package/dist/material-icons/folder-tools.svg +1 -0
- package/dist/material-icons/folder-trash-open.svg +1 -0
- package/dist/material-icons/folder-trash.svg +1 -0
- package/dist/material-icons/folder-trigger-open.svg +1 -0
- package/dist/material-icons/folder-trigger.svg +1 -0
- package/dist/material-icons/folder-turborepo-open.svg +1 -0
- package/dist/material-icons/folder-turborepo.svg +1 -0
- package/dist/material-icons/folder-typescript-open.svg +1 -0
- package/dist/material-icons/folder-typescript.svg +1 -0
- package/dist/material-icons/folder-ui-open.svg +1 -0
- package/dist/material-icons/folder-ui.svg +1 -0
- package/dist/material-icons/folder-unity-open.svg +1 -0
- package/dist/material-icons/folder-unity.svg +1 -0
- package/dist/material-icons/folder-update-open.svg +1 -0
- package/dist/material-icons/folder-update.svg +1 -0
- package/dist/material-icons/folder-upload-open.svg +1 -0
- package/dist/material-icons/folder-upload.svg +1 -0
- package/dist/material-icons/folder-utils-open.svg +1 -0
- package/dist/material-icons/folder-utils.svg +1 -0
- package/dist/material-icons/folder-vercel-open.svg +1 -0
- package/dist/material-icons/folder-vercel.svg +1 -0
- package/dist/material-icons/folder-verdaccio-open.svg +1 -0
- package/dist/material-icons/folder-verdaccio.svg +1 -0
- package/dist/material-icons/folder-video-open.svg +1 -0
- package/dist/material-icons/folder-video.svg +1 -0
- package/dist/material-icons/folder-views-open.svg +1 -0
- package/dist/material-icons/folder-views.svg +1 -0
- package/dist/material-icons/folder-vm-open.svg +1 -0
- package/dist/material-icons/folder-vm.svg +1 -0
- package/dist/material-icons/folder-vscode-open.svg +1 -0
- package/dist/material-icons/folder-vscode.svg +1 -0
- package/dist/material-icons/folder-vue-directives-open.svg +1 -0
- package/dist/material-icons/folder-vue-directives.svg +1 -0
- package/dist/material-icons/folder-vue-open.svg +1 -0
- package/dist/material-icons/folder-vue.svg +1 -0
- package/dist/material-icons/folder-vuepress-open.svg +1 -0
- package/dist/material-icons/folder-vuepress.svg +1 -0
- package/dist/material-icons/folder-vuex-store-open.svg +1 -0
- package/dist/material-icons/folder-vuex-store.svg +1 -0
- package/dist/material-icons/folder-wakatime-open.svg +1 -0
- package/dist/material-icons/folder-wakatime.svg +1 -0
- package/dist/material-icons/folder-webpack-open.svg +1 -0
- package/dist/material-icons/folder-webpack.svg +1 -0
- package/dist/material-icons/folder-windows-open.svg +1 -0
- package/dist/material-icons/folder-windows.svg +1 -0
- package/dist/material-icons/folder-wordpress-open.svg +1 -0
- package/dist/material-icons/folder-wordpress.svg +1 -0
- package/dist/material-icons/folder-yarn-open.svg +1 -0
- package/dist/material-icons/folder-yarn.svg +1 -0
- package/dist/material-icons/folder-zeabur-open.svg +1 -0
- package/dist/material-icons/folder-zeabur.svg +1 -0
- package/dist/material-icons/folder-zed-open.svg +1 -0
- package/dist/material-icons/folder-zed.svg +1 -0
- package/dist/material-icons/folder.svg +1 -0
- package/dist/material-icons/font.svg +1 -0
- package/dist/material-icons/forth.svg +1 -0
- package/dist/material-icons/fortran.svg +1 -0
- package/dist/material-icons/foxpro.svg +1 -0
- package/dist/material-icons/freemarker.svg +1 -0
- package/dist/material-icons/fsharp.svg +1 -0
- package/dist/material-icons/fusebox.svg +1 -0
- package/dist/material-icons/gamemaker.svg +1 -0
- package/dist/material-icons/garden.svg +1 -0
- package/dist/material-icons/gatsby.svg +1 -0
- package/dist/material-icons/gcp.svg +1 -0
- package/dist/material-icons/gemfile.svg +1 -0
- package/dist/material-icons/gemini-ai.svg +1 -0
- package/dist/material-icons/gemini.svg +1 -0
- package/dist/material-icons/git.svg +1 -0
- package/dist/material-icons/github-actions-workflow.svg +1 -0
- package/dist/material-icons/github-sponsors.svg +1 -0
- package/dist/material-icons/gitlab.svg +1 -0
- package/dist/material-icons/gitpod.svg +1 -0
- package/dist/material-icons/gleam.svg +1 -0
- package/dist/material-icons/gnuplot.svg +1 -0
- package/dist/material-icons/go-mod.svg +1 -0
- package/dist/material-icons/go.svg +1 -0
- package/dist/material-icons/go_gopher.svg +1 -0
- package/dist/material-icons/godot-assets.svg +1 -0
- package/dist/material-icons/godot.svg +1 -0
- package/dist/material-icons/google.svg +1 -0
- package/dist/material-icons/gradle.svg +1 -0
- package/dist/material-icons/grafana-alloy.svg +1 -0
- package/dist/material-icons/grain.svg +1 -0
- package/dist/material-icons/graphcool.svg +1 -0
- package/dist/material-icons/graphql.svg +1 -0
- package/dist/material-icons/gridsome.svg +1 -0
- package/dist/material-icons/groovy.svg +1 -0
- package/dist/material-icons/grunt.svg +1 -0
- package/dist/material-icons/gulp.svg +1 -0
- package/dist/material-icons/h.svg +1 -0
- package/dist/material-icons/hack.svg +1 -0
- package/dist/material-icons/hadolint.svg +1 -0
- package/dist/material-icons/haml.svg +1 -0
- package/dist/material-icons/handlebars.svg +1 -0
- package/dist/material-icons/happo.svg +1 -0
- package/dist/material-icons/hardhat.svg +1 -0
- package/dist/material-icons/harmonix.svg +1 -0
- package/dist/material-icons/haskell.svg +1 -0
- package/dist/material-icons/haxe.svg +1 -0
- package/dist/material-icons/hcl.svg +1 -0
- package/dist/material-icons/hcl_light.svg +1 -0
- package/dist/material-icons/helm.svg +1 -0
- package/dist/material-icons/heroku.svg +1 -0
- package/dist/material-icons/hex.svg +1 -0
- package/dist/material-icons/hip.svg +1 -0
- package/dist/material-icons/histoire.svg +1 -0
- package/dist/material-icons/hjson.svg +1 -0
- package/dist/material-icons/horusec.svg +1 -0
- package/dist/material-icons/hosts.svg +1 -0
- package/dist/material-icons/hosts_light.svg +1 -0
- package/dist/material-icons/hpp.svg +1 -0
- package/dist/material-icons/html.svg +1 -0
- package/dist/material-icons/http.svg +1 -0
- package/dist/material-icons/huff.svg +1 -0
- package/dist/material-icons/huff_light.svg +1 -0
- package/dist/material-icons/hurl.svg +1 -0
- package/dist/material-icons/husky.svg +1 -0
- package/dist/material-icons/i18n.svg +1 -0
- package/dist/material-icons/idris.svg +1 -0
- package/dist/material-icons/ifanr-cloud.svg +1 -0
- package/dist/material-icons/image.svg +1 -0
- package/dist/material-icons/imba.svg +1 -0
- package/dist/material-icons/installation.svg +1 -0
- package/dist/material-icons/instructions.clone.svg +1 -0
- package/dist/material-icons/ionic.svg +1 -0
- package/dist/material-icons/istanbul.svg +1 -0
- package/dist/material-icons/jar.svg +1 -0
- package/dist/material-icons/java.svg +1 -0
- package/dist/material-icons/javaclass.svg +1 -0
- package/dist/material-icons/javascript-map.svg +1 -0
- package/dist/material-icons/javascript.svg +1 -0
- package/dist/material-icons/jenkins.svg +1 -0
- package/dist/material-icons/jest.svg +1 -0
- package/dist/material-icons/jinja.svg +1 -0
- package/dist/material-icons/jinja_light.svg +1 -0
- package/dist/material-icons/jsconfig.svg +1 -0
- package/dist/material-icons/json.svg +1 -0
- package/dist/material-icons/json_schema.svg +1 -0
- package/dist/material-icons/jsr.svg +1 -0
- package/dist/material-icons/jsr_light.svg +1 -0
- package/dist/material-icons/julia.svg +1 -0
- package/dist/material-icons/jupyter.svg +1 -0
- package/dist/material-icons/just.svg +1 -0
- package/dist/material-icons/karma.svg +1 -0
- package/dist/material-icons/kcl.svg +1 -0
- package/dist/material-icons/key.svg +1 -0
- package/dist/material-icons/keystatic.svg +1 -0
- package/dist/material-icons/kivy.svg +1 -0
- package/dist/material-icons/kl.svg +1 -0
- package/dist/material-icons/knip.svg +1 -0
- package/dist/material-icons/kotlin.svg +1 -0
- package/dist/material-icons/kubernetes.svg +1 -0
- package/dist/material-icons/kusto.svg +1 -0
- package/dist/material-icons/label.svg +1 -0
- package/dist/material-icons/laravel.svg +1 -0
- package/dist/material-icons/latex-class.clone.svg +1 -0
- package/dist/material-icons/latex-package.clone.svg +1 -0
- package/dist/material-icons/latex.clone.svg +1 -0
- package/dist/material-icons/latexmk.svg +1 -0
- package/dist/material-icons/lbx.svg +1 -0
- package/dist/material-icons/lean.svg +1 -0
- package/dist/material-icons/lefthook.svg +1 -0
- package/dist/material-icons/lerna.svg +1 -0
- package/dist/material-icons/less.svg +1 -0
- package/dist/material-icons/liara.svg +1 -0
- package/dist/material-icons/lib.svg +1 -0
- package/dist/material-icons/license.svg +1 -0
- package/dist/material-icons/lighthouse.svg +1 -0
- package/dist/material-icons/lilypond.svg +1 -0
- package/dist/material-icons/lintstaged.svg +1 -0
- package/dist/material-icons/liquid.svg +1 -0
- package/dist/material-icons/lisp.svg +1 -0
- package/dist/material-icons/livescript.svg +1 -0
- package/dist/material-icons/lock.svg +1 -0
- package/dist/material-icons/log.svg +1 -0
- package/dist/material-icons/lolcode.svg +1 -0
- package/dist/material-icons/lottie.svg +1 -0
- package/dist/material-icons/lua.svg +1 -0
- package/dist/material-icons/luau.svg +1 -0
- package/dist/material-icons/lynx.svg +1 -0
- package/dist/material-icons/lyric.svg +1 -0
- package/dist/material-icons/macaulay2.svg +1 -0
- package/dist/material-icons/makefile.svg +1 -0
- package/dist/material-icons/markdoc-config.svg +1 -0
- package/dist/material-icons/markdoc.svg +1 -0
- package/dist/material-icons/markdown.svg +1 -0
- package/dist/material-icons/markdownlint.svg +1 -0
- package/dist/material-icons/markojs.svg +1 -0
- package/dist/material-icons/mathematica.svg +1 -0
- package/dist/material-icons/matlab.svg +1 -0
- package/dist/material-icons/maven.svg +1 -0
- package/dist/material-icons/mdsvex.svg +1 -0
- package/dist/material-icons/mdx.svg +1 -0
- package/dist/material-icons/mercurial.svg +1 -0
- package/dist/material-icons/merlin.svg +1 -0
- package/dist/material-icons/mermaid.svg +1 -0
- package/dist/material-icons/meson.svg +1 -0
- package/dist/material-icons/metro.svg +1 -0
- package/dist/material-icons/minecraft-fabric.svg +1 -0
- package/dist/material-icons/minecraft.svg +1 -0
- package/dist/material-icons/mint.svg +1 -0
- package/dist/material-icons/mist.clone.svg +1 -0
- package/dist/material-icons/mjml.svg +1 -0
- package/dist/material-icons/mocha.svg +1 -0
- package/dist/material-icons/modernizr.svg +1 -0
- package/dist/material-icons/mojo.svg +1 -0
- package/dist/material-icons/moon.svg +1 -0
- package/dist/material-icons/moonscript.svg +1 -0
- package/dist/material-icons/mrpack.svg +1 -0
- package/dist/material-icons/mxml.svg +1 -0
- package/dist/material-icons/nano-staged.svg +1 -0
- package/dist/material-icons/nano-staged_light.svg +1 -0
- package/dist/material-icons/ndst.svg +1 -0
- package/dist/material-icons/nest-controller.clone.svg +1 -0
- package/dist/material-icons/nest-decorator.clone.svg +1 -0
- package/dist/material-icons/nest-filter.clone.svg +1 -0
- package/dist/material-icons/nest-gateway.clone.svg +1 -0
- package/dist/material-icons/nest-guard.clone.svg +1 -0
- package/dist/material-icons/nest-interceptor.clone.svg +1 -0
- package/dist/material-icons/nest-middleware.clone.svg +1 -0
- package/dist/material-icons/nest-module.clone.svg +1 -0
- package/dist/material-icons/nest-pipe.clone.svg +1 -0
- package/dist/material-icons/nest-resolver.clone.svg +1 -0
- package/dist/material-icons/nest-service.clone.svg +1 -0
- package/dist/material-icons/nest.svg +1 -0
- package/dist/material-icons/netlify.svg +1 -0
- package/dist/material-icons/netlify_light.svg +1 -0
- package/dist/material-icons/next.svg +1 -0
- package/dist/material-icons/next_light.svg +1 -0
- package/dist/material-icons/nginx.svg +1 -0
- package/dist/material-icons/ngrx-actions.svg +1 -0
- package/dist/material-icons/ngrx-effects.svg +1 -0
- package/dist/material-icons/ngrx-entity.svg +1 -0
- package/dist/material-icons/ngrx-reducer.svg +1 -0
- package/dist/material-icons/ngrx-selectors.svg +1 -0
- package/dist/material-icons/ngrx-state.svg +1 -0
- package/dist/material-icons/nim.svg +1 -0
- package/dist/material-icons/nix.svg +1 -0
- package/dist/material-icons/nodejs.svg +1 -0
- package/dist/material-icons/nodejs_alt.svg +1 -0
- package/dist/material-icons/nodemon.svg +1 -0
- package/dist/material-icons/npm.svg +1 -0
- package/dist/material-icons/nuget.svg +1 -0
- package/dist/material-icons/nunjucks.svg +1 -0
- package/dist/material-icons/nuxt.svg +1 -0
- package/dist/material-icons/nx.svg +1 -0
- package/dist/material-icons/objective-c.svg +1 -0
- package/dist/material-icons/objective-cpp.svg +1 -0
- package/dist/material-icons/ocaml.svg +1 -0
- package/dist/material-icons/odin.svg +1 -0
- package/dist/material-icons/onnx.svg +1 -0
- package/dist/material-icons/opa.svg +1 -0
- package/dist/material-icons/opam.svg +1 -0
- package/dist/material-icons/openapi.svg +1 -0
- package/dist/material-icons/openapi_light.svg +1 -0
- package/dist/material-icons/opencode.svg +1 -0
- package/dist/material-icons/opencode_light.svg +1 -0
- package/dist/material-icons/opentofu.svg +1 -0
- package/dist/material-icons/opentofu_light.svg +1 -0
- package/dist/material-icons/otne.svg +1 -0
- package/dist/material-icons/oxc.svg +1 -0
- package/dist/material-icons/packship.svg +1 -0
- package/dist/material-icons/palette.svg +1 -0
- package/dist/material-icons/panda.svg +1 -0
- package/dist/material-icons/parcel.svg +1 -0
- package/dist/material-icons/pascal.svg +1 -0
- package/dist/material-icons/pawn.svg +1 -0
- package/dist/material-icons/payload.svg +1 -0
- package/dist/material-icons/payload_light.svg +1 -0
- package/dist/material-icons/pdf.svg +1 -0
- package/dist/material-icons/pdm.svg +1 -0
- package/dist/material-icons/percy.svg +1 -0
- package/dist/material-icons/perl.svg +1 -0
- package/dist/material-icons/php-cs-fixer.svg +1 -0
- package/dist/material-icons/php.svg +1 -0
- package/dist/material-icons/php_elephant.svg +1 -0
- package/dist/material-icons/php_elephant_pink.svg +1 -0
- package/dist/material-icons/phpstan.svg +1 -0
- package/dist/material-icons/phpunit.svg +1 -0
- package/dist/material-icons/pinejs.svg +1 -0
- package/dist/material-icons/pipeline.svg +1 -0
- package/dist/material-icons/pkl.svg +1 -0
- package/dist/material-icons/plastic.svg +1 -0
- package/dist/material-icons/playwright.svg +1 -0
- package/dist/material-icons/plop.svg +1 -0
- package/dist/material-icons/pm2-ecosystem.svg +1 -0
- package/dist/material-icons/pnpm.svg +1 -0
- package/dist/material-icons/pnpm_light.svg +1 -0
- package/dist/material-icons/poetry.svg +1 -0
- package/dist/material-icons/postcss.svg +1 -0
- package/dist/material-icons/posthtml.svg +1 -0
- package/dist/material-icons/powerpoint.svg +1 -0
- package/dist/material-icons/powershell.svg +1 -0
- package/dist/material-icons/pre-commit.svg +1 -0
- package/dist/material-icons/prettier.svg +1 -0
- package/dist/material-icons/prisma.svg +1 -0
- package/dist/material-icons/processing.svg +1 -0
- package/dist/material-icons/prolog.svg +1 -0
- package/dist/material-icons/prompt.svg +1 -0
- package/dist/material-icons/proto.svg +1 -0
- package/dist/material-icons/protractor.svg +1 -0
- package/dist/material-icons/pug.svg +1 -0
- package/dist/material-icons/puppet.svg +1 -0
- package/dist/material-icons/puppeteer.svg +1 -0
- package/dist/material-icons/purescript.svg +1 -0
- package/dist/material-icons/python-misc.svg +1 -0
- package/dist/material-icons/python.svg +1 -0
- package/dist/material-icons/pytorch.svg +1 -0
- package/dist/material-icons/qsharp.svg +1 -0
- package/dist/material-icons/quarto.svg +1 -0
- package/dist/material-icons/quasar.svg +1 -0
- package/dist/material-icons/quokka.svg +1 -0
- package/dist/material-icons/qwik.svg +1 -0
- package/dist/material-icons/r.svg +1 -0
- package/dist/material-icons/racket.svg +1 -0
- package/dist/material-icons/raml.svg +1 -0
- package/dist/material-icons/razor.svg +1 -0
- package/dist/material-icons/rbxmk.svg +1 -0
- package/dist/material-icons/rc.svg +1 -0
- package/dist/material-icons/react.svg +1 -0
- package/dist/material-icons/react_ts.svg +1 -0
- package/dist/material-icons/readme.svg +1 -0
- package/dist/material-icons/reason.svg +1 -0
- package/dist/material-icons/red.svg +1 -0
- package/dist/material-icons/redux-action.svg +1 -0
- package/dist/material-icons/redux-reducer.svg +1 -0
- package/dist/material-icons/redux-selector.svg +1 -0
- package/dist/material-icons/redux-store.svg +1 -0
- package/dist/material-icons/regedit.svg +1 -0
- package/dist/material-icons/remark.svg +1 -0
- package/dist/material-icons/remix.svg +1 -0
- package/dist/material-icons/remix_light.svg +1 -0
- package/dist/material-icons/renovate.svg +1 -0
- package/dist/material-icons/replit.svg +1 -0
- package/dist/material-icons/rescript-interface.svg +1 -0
- package/dist/material-icons/rescript.svg +1 -0
- package/dist/material-icons/restql.svg +1 -0
- package/dist/material-icons/riot.svg +1 -0
- package/dist/material-icons/roadmap.svg +1 -0
- package/dist/material-icons/roblox.svg +1 -0
- package/dist/material-icons/robot.svg +1 -0
- package/dist/material-icons/robots.svg +1 -0
- package/dist/material-icons/rocket.svg +1 -0
- package/dist/material-icons/rojo.svg +1 -0
- package/dist/material-icons/rolldown.svg +1 -0
- package/dist/material-icons/rollup.svg +1 -0
- package/dist/material-icons/rome.svg +1 -0
- package/dist/material-icons/routing.svg +1 -0
- package/dist/material-icons/rspec.svg +1 -0
- package/dist/material-icons/rstack.svg +1 -0
- package/dist/material-icons/rubocop.svg +1 -0
- package/dist/material-icons/rubocop_light.svg +1 -0
- package/dist/material-icons/ruby.svg +1 -0
- package/dist/material-icons/ruff.svg +1 -0
- package/dist/material-icons/rust.svg +1 -0
- package/dist/material-icons/salesforce.svg +1 -0
- package/dist/material-icons/salt.svg +1 -0
- package/dist/material-icons/san.svg +1 -0
- package/dist/material-icons/sas.svg +1 -0
- package/dist/material-icons/sass.svg +1 -0
- package/dist/material-icons/sbt.svg +1 -0
- package/dist/material-icons/scala.svg +1 -0
- package/dist/material-icons/scheme.svg +1 -0
- package/dist/material-icons/scons.svg +1 -0
- package/dist/material-icons/scons_light.svg +1 -0
- package/dist/material-icons/screwdriver.svg +1 -0
- package/dist/material-icons/search.svg +1 -0
- package/dist/material-icons/semantic-release.svg +1 -0
- package/dist/material-icons/semantic-release_light.svg +1 -0
- package/dist/material-icons/semgrep.svg +1 -0
- package/dist/material-icons/sentry.svg +1 -0
- package/dist/material-icons/sequelize.svg +1 -0
- package/dist/material-icons/serverless.svg +1 -0
- package/dist/material-icons/settings.svg +1 -0
- package/dist/material-icons/shader.svg +1 -0
- package/dist/material-icons/shellcheck.svg +1 -0
- package/dist/material-icons/shellcheck_light.svg +1 -0
- package/dist/material-icons/silverstripe.svg +1 -0
- package/dist/material-icons/simulink.svg +1 -0
- package/dist/material-icons/siyuan.svg +1 -0
- package/dist/material-icons/sketch.svg +1 -0
- package/dist/material-icons/skill.svg +1 -0
- package/dist/material-icons/slim.svg +1 -0
- package/dist/material-icons/slint.svg +1 -0
- package/dist/material-icons/slug.svg +1 -0
- package/dist/material-icons/smarty.svg +1 -0
- package/dist/material-icons/sml.svg +1 -0
- package/dist/material-icons/snakemake.svg +1 -0
- package/dist/material-icons/snapcraft.svg +1 -0
- package/dist/material-icons/snowpack.svg +1 -0
- package/dist/material-icons/snowpack_light.svg +1 -0
- package/dist/material-icons/snyk.svg +1 -0
- package/dist/material-icons/solidity.svg +1 -0
- package/dist/material-icons/sonarcloud.svg +1 -0
- package/dist/material-icons/spwn.svg +1 -0
- package/dist/material-icons/stackblitz.svg +1 -0
- package/dist/material-icons/stan.svg +1 -0
- package/dist/material-icons/steadybit.svg +1 -0
- package/dist/material-icons/stencil.svg +1 -0
- package/dist/material-icons/stitches.svg +1 -0
- package/dist/material-icons/stitches_light.svg +1 -0
- package/dist/material-icons/storybook.svg +1 -0
- package/dist/material-icons/stryker.svg +1 -0
- package/dist/material-icons/sty.clone.svg +1 -0
- package/dist/material-icons/stylable.svg +1 -0
- package/dist/material-icons/stylelint.svg +1 -0
- package/dist/material-icons/stylelint_light.svg +1 -0
- package/dist/material-icons/stylus.svg +1 -0
- package/dist/material-icons/sublime.svg +1 -0
- package/dist/material-icons/subtitles.svg +1 -0
- package/dist/material-icons/supabase.svg +1 -0
- package/dist/material-icons/svelte.svg +1 -0
- package/dist/material-icons/svelte_js.clone.svg +1 -0
- package/dist/material-icons/svelte_ts.clone.svg +1 -0
- package/dist/material-icons/svg.svg +1 -0
- package/dist/material-icons/svgo.svg +1 -0
- package/dist/material-icons/svgr.svg +1 -0
- package/dist/material-icons/swagger.svg +1 -0
- package/dist/material-icons/sway.svg +1 -0
- package/dist/material-icons/swc.svg +1 -0
- package/dist/material-icons/swift.svg +1 -0
- package/dist/material-icons/syncpack.svg +1 -0
- package/dist/material-icons/systemd.svg +1 -0
- package/dist/material-icons/systemd_light.svg +1 -0
- package/dist/material-icons/table.svg +1 -0
- package/dist/material-icons/tailwindcss.svg +1 -0
- package/dist/material-icons/tape.clone.svg +1 -0
- package/dist/material-icons/taskfile.svg +1 -0
- package/dist/material-icons/tauri.svg +1 -0
- package/dist/material-icons/taze.svg +1 -0
- package/dist/material-icons/tcl.svg +1 -0
- package/dist/material-icons/teal.svg +1 -0
- package/dist/material-icons/templ.svg +1 -0
- package/dist/material-icons/template.svg +1 -0
- package/dist/material-icons/terraform.svg +1 -0
- package/dist/material-icons/test-js.svg +1 -0
- package/dist/material-icons/test-jsx.svg +1 -0
- package/dist/material-icons/test-ts.svg +1 -0
- package/dist/material-icons/tex.svg +1 -0
- package/dist/material-icons/textlint.svg +1 -0
- package/dist/material-icons/tilt.svg +1 -0
- package/dist/material-icons/tldraw.svg +1 -0
- package/dist/material-icons/tldraw_light.svg +1 -0
- package/dist/material-icons/tobi.svg +1 -0
- package/dist/material-icons/tobimake.svg +1 -0
- package/dist/material-icons/toc.svg +1 -0
- package/dist/material-icons/todo.svg +1 -0
- package/dist/material-icons/toml.svg +1 -0
- package/dist/material-icons/toml_light.svg +1 -0
- package/dist/material-icons/toon.svg +1 -0
- package/dist/material-icons/travis.svg +1 -0
- package/dist/material-icons/tree.svg +1 -0
- package/dist/material-icons/trigger.svg +1 -0
- package/dist/material-icons/tsconfig.svg +1 -0
- package/dist/material-icons/tsdoc.svg +1 -0
- package/dist/material-icons/tsdown.svg +1 -0
- package/dist/material-icons/tsil.svg +1 -0
- package/dist/material-icons/tune.svg +1 -0
- package/dist/material-icons/turborepo.svg +1 -0
- package/dist/material-icons/turborepo_light.svg +1 -0
- package/dist/material-icons/twig.svg +1 -0
- package/dist/material-icons/twine.svg +1 -0
- package/dist/material-icons/typedoc.svg +1 -0
- package/dist/material-icons/typescript-def.svg +1 -0
- package/dist/material-icons/typescript.svg +1 -0
- package/dist/material-icons/typst.svg +1 -0
- package/dist/material-icons/uiua.svg +1 -0
- package/dist/material-icons/umi.svg +1 -0
- package/dist/material-icons/uml.svg +1 -0
- package/dist/material-icons/uml_light.svg +1 -0
- package/dist/material-icons/unity.svg +1 -0
- package/dist/material-icons/unlicense.svg +1 -0
- package/dist/material-icons/unocss.svg +1 -0
- package/dist/material-icons/url.svg +1 -0
- package/dist/material-icons/uv.svg +1 -0
- package/dist/material-icons/vagrant.svg +1 -0
- package/dist/material-icons/vala.svg +1 -0
- package/dist/material-icons/vanilla-extract.svg +1 -0
- package/dist/material-icons/varnish.svg +1 -0
- package/dist/material-icons/vedic.svg +1 -0
- package/dist/material-icons/velite.svg +1 -0
- package/dist/material-icons/velocity.svg +1 -0
- package/dist/material-icons/vercel.svg +1 -0
- package/dist/material-icons/vercel_light.svg +1 -0
- package/dist/material-icons/verdaccio.svg +1 -0
- package/dist/material-icons/verified.svg +1 -0
- package/dist/material-icons/verilog.svg +1 -0
- package/dist/material-icons/verse.svg +1 -0
- package/dist/material-icons/verse_light.svg +1 -0
- package/dist/material-icons/vfl.svg +1 -0
- package/dist/material-icons/video.svg +1 -0
- package/dist/material-icons/vim.svg +1 -0
- package/dist/material-icons/virtual.svg +1 -0
- package/dist/material-icons/visualstudio.svg +1 -0
- package/dist/material-icons/vite.svg +1 -0
- package/dist/material-icons/vitest.svg +1 -0
- package/dist/material-icons/vlang.svg +1 -0
- package/dist/material-icons/vscode.svg +1 -0
- package/dist/material-icons/vue-config.svg +1 -0
- package/dist/material-icons/vue.svg +1 -0
- package/dist/material-icons/vuex-store.svg +1 -0
- package/dist/material-icons/wakatime.svg +1 -0
- package/dist/material-icons/wakatime_light.svg +1 -0
- package/dist/material-icons/wallaby.svg +1 -0
- package/dist/material-icons/wally.svg +1 -0
- package/dist/material-icons/warp.svg +1 -0
- package/dist/material-icons/warp_light.svg +1 -0
- package/dist/material-icons/watchman.svg +1 -0
- package/dist/material-icons/webassembly.svg +1 -0
- package/dist/material-icons/webhint.svg +1 -0
- package/dist/material-icons/webpack.svg +1 -0
- package/dist/material-icons/wepy.svg +1 -0
- package/dist/material-icons/werf.svg +1 -0
- package/dist/material-icons/windicss.svg +1 -0
- package/dist/material-icons/wolframlanguage.svg +1 -0
- package/dist/material-icons/word.svg +1 -0
- package/dist/material-icons/wrangler.svg +1 -0
- package/dist/material-icons/wxt.svg +1 -0
- package/dist/material-icons/xaml.svg +1 -0
- package/dist/material-icons/xmake.svg +1 -0
- package/dist/material-icons/xml.svg +1 -0
- package/dist/material-icons/yaml.svg +1 -0
- package/dist/material-icons/yang.svg +1 -0
- package/dist/material-icons/yarn.svg +1 -0
- package/dist/material-icons/zeabur.svg +1 -0
- package/dist/material-icons/zeabur_light.svg +1 -0
- package/dist/material-icons/zig.svg +1 -0
- package/dist/material-icons/zip.svg +1 -0
- package/dist/theme-init.js +10 -0
- package/docs/architecture.md +311 -0
- package/docs/configuration.md +141 -0
- package/docs/deployment.md +117 -0
- package/docs/security-upgrade.md +35 -0
- package/package.json +81 -4
- package/runtime/server/src/app.js +71 -0
- package/runtime/server/src/config/config.js +173 -0
- package/runtime/server/src/lib/concurrency.js +44 -0
- package/runtime/server/src/lib/errors.js +39 -0
- package/runtime/server/src/lib/http-range.js +27 -0
- package/runtime/server/src/lib/shell-quote.js +14 -0
- package/runtime/server/src/lib/validation.js +11 -0
- package/runtime/server/src/main.js +70 -0
- package/runtime/server/src/modules/auth/auth-security.js +104 -0
- package/runtime/server/src/modules/auth/auth-service.js +96 -0
- package/runtime/server/src/modules/auth/auth.routes.js +108 -0
- package/runtime/server/src/modules/auth/login-limiter.js +40 -0
- package/runtime/server/src/modules/auth/password.js +29 -0
- package/runtime/server/src/modules/auth/session-tokens.js +90 -0
- package/runtime/server/src/modules/commit-message/commit-message-generator.js +203 -0
- package/runtime/server/src/modules/commit-message/occ-config.js +53 -0
- package/runtime/server/src/modules/exec/command-runner.js +91 -0
- package/runtime/server/src/modules/filesystem/file-system-driver.js +1 -0
- package/runtime/server/src/modules/filesystem/local-driver.js +95 -0
- package/runtime/server/src/modules/filesystem/restricted-driver.js +114 -0
- package/runtime/server/src/modules/filesystem/sftp-driver.js +105 -0
- package/runtime/server/src/modules/filesystem/workspace-file-service.js +328 -0
- package/runtime/server/src/modules/filesystem/workspace-path.js +31 -0
- package/runtime/server/src/modules/git/git-parsers.js +229 -0
- package/runtime/server/src/modules/git/git-service.js +597 -0
- package/runtime/server/src/modules/git/git.routes.js +68 -0
- package/runtime/server/src/modules/hosts/directory-browser.js +36 -0
- package/runtime/server/src/modules/hosts/host-service.js +260 -0
- package/runtime/server/src/modules/hosts/host.routes.js +31 -0
- package/runtime/server/src/modules/hosts/key-installer.js +58 -0
- package/runtime/server/src/modules/ssh/host-key-probe.js +64 -0
- package/runtime/server/src/modules/ssh/host-key-verifier.js +153 -0
- package/runtime/server/src/modules/ssh/ssh-config.js +1 -0
- package/runtime/server/src/modules/ssh/ssh-connection-manager.js +190 -0
- package/runtime/server/src/modules/ssh/ssh-connection.js +93 -0
- package/runtime/server/src/modules/store/app-store.js +138 -0
- package/runtime/server/src/modules/terminal/output-buffer.js +54 -0
- package/runtime/server/src/modules/terminal/pty.js +1 -0
- package/runtime/server/src/modules/terminal/ssh-pty-provider.js +91 -0
- package/runtime/server/src/modules/terminal/terminal-manager.js +70 -0
- package/runtime/server/src/modules/terminal/terminal-session.js +124 -0
- package/runtime/server/src/modules/terminal/terminal-socket.js +170 -0
- package/runtime/server/src/modules/terminal/terminal.routes.js +19 -0
- package/runtime/server/src/modules/workspace/workspace-registry.js +231 -0
- package/runtime/server/src/modules/workspace/workspace.routes.js +97 -0
- package/runtime/server/src/scripts/reset-password.js +21 -0
- package/runtime/server/src/services.js +35 -0
- package/runtime/src/schema/api.js +30 -0
- package/runtime/src/schema/auth.js +20 -0
- package/runtime/src/schema/git.js +233 -0
- package/runtime/src/schema/hosts.js +115 -0
- package/runtime/src/schema/terminal.js +48 -0
- package/runtime/src/schema/workspace.js +151 -0
package/package.json
CHANGED
|
@@ -1,6 +1,83 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danneillouven/remote-workspace",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "node scripts/dev.mjs",
|
|
7
|
+
"dev:web": "vite",
|
|
8
|
+
"dev:server": "node --watch --env-file-if-exists=.env server/src/main.ts",
|
|
9
|
+
"build": "tsc -b && vite build && tsc -p tsconfig.package.json",
|
|
10
|
+
"start": "npm run build && npm run serve",
|
|
11
|
+
"serve": "NODE_ENV=production node --env-file-if-exists=.env server/src/main.ts",
|
|
12
|
+
"start:tunnel": "npm run build && node --env-file-if-exists=.env scripts/tunnel.mjs",
|
|
13
|
+
"typecheck": "tsc -b",
|
|
14
|
+
"lint": "oxlint",
|
|
15
|
+
"test": "vitest run",
|
|
16
|
+
"test:watch": "vitest",
|
|
17
|
+
"reset-password": "node --env-file-if-exists=.env server/src/scripts/reset-password.ts",
|
|
18
|
+
"preview": "vite preview",
|
|
19
|
+
"prepack": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@codemirror/commands": "^6.11.1",
|
|
23
|
+
"@codemirror/language": "^6.13.1",
|
|
24
|
+
"@codemirror/language-data": "^6.5.2",
|
|
25
|
+
"@codemirror/search": "^6.7.2",
|
|
26
|
+
"@codemirror/state": "^6.7.6",
|
|
27
|
+
"@codemirror/view": "^6.43.15",
|
|
28
|
+
"@fastify/cookie": "^11.1.3",
|
|
29
|
+
"@fastify/static": "^10.1.7",
|
|
30
|
+
"@fastify/websocket": "^11.3.4",
|
|
31
|
+
"@lezer/highlight": "^1.2.5",
|
|
32
|
+
"@tailwindcss/vite": "^4.3.3",
|
|
33
|
+
"@tanstack/react-query": "^5.104.4",
|
|
34
|
+
"@tanstack/react-router": "^1.170.45",
|
|
35
|
+
"@xterm/addon-fit": "^0.11.0",
|
|
36
|
+
"@xterm/addon-unicode11": "^0.9.0",
|
|
37
|
+
"@xterm/xterm": "^6.0.0",
|
|
38
|
+
"axios": "^1.20.0",
|
|
39
|
+
"fastify": "^5.12.7",
|
|
40
|
+
"lucide-react": "^1.61.0",
|
|
41
|
+
"material-icon-theme": "^5.39.0",
|
|
42
|
+
"react": "^19.2.8",
|
|
43
|
+
"react-dom": "^19.2.8",
|
|
44
|
+
"react-markdown": "^10.1.0",
|
|
45
|
+
"remark-gfm": "^4.0.1",
|
|
46
|
+
"ssh2": "^1.17.0",
|
|
47
|
+
"tailwindcss": "^4.3.3",
|
|
48
|
+
"zod": "^4.6.7",
|
|
49
|
+
"zustand": "^5.0.15"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@testing-library/dom": "^10.4.2",
|
|
53
|
+
"@testing-library/react": "^16.3.3",
|
|
54
|
+
"@types/node": "^24.19.2",
|
|
55
|
+
"@types/react": "^19.2.18",
|
|
56
|
+
"@types/react-dom": "^19.2.7",
|
|
57
|
+
"@types/ssh2": "^1.15.6",
|
|
58
|
+
"@types/ws": "^8.18.2",
|
|
59
|
+
"@vitejs/plugin-react": "^6.1.1",
|
|
60
|
+
"jsdom": "^29.1.1",
|
|
61
|
+
"oxlint": "^1.81.0",
|
|
62
|
+
"typescript": "~6.0.2",
|
|
63
|
+
"vite": "^8.3.0",
|
|
64
|
+
"vitest": "^5.0.5",
|
|
65
|
+
"ws": "^8.22.0"
|
|
66
|
+
},
|
|
67
|
+
"engines": {
|
|
68
|
+
"node": ">=24"
|
|
69
|
+
},
|
|
70
|
+
"bin": {
|
|
71
|
+
"remote-workspace": "./bin/remote-workspace.mjs"
|
|
72
|
+
},
|
|
73
|
+
"publishConfig": {
|
|
74
|
+
"access": "public"
|
|
75
|
+
},
|
|
76
|
+
"files": [
|
|
77
|
+
"bin/",
|
|
78
|
+
"dist/",
|
|
79
|
+
"runtime/",
|
|
80
|
+
"docs/",
|
|
81
|
+
".env.example"
|
|
82
|
+
]
|
|
83
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fastifyCookie from '@fastify/cookie';
|
|
3
|
+
import fastifyStatic from '@fastify/static';
|
|
4
|
+
import fastifyWebsocket from '@fastify/websocket';
|
|
5
|
+
import Fastify, {} from 'fastify';
|
|
6
|
+
import { isAppError } from "./lib/errors.js";
|
|
7
|
+
import { registerAuthGuards, registerSecurityHeaders } from "./modules/auth/auth-security.js";
|
|
8
|
+
import { registerAuthRoutes } from "./modules/auth/auth.routes.js";
|
|
9
|
+
import { registerGitRoutes } from "./modules/git/git.routes.js";
|
|
10
|
+
import { registerHostRoutes } from "./modules/hosts/host.routes.js";
|
|
11
|
+
import { registerTerminalRoutes } from "./modules/terminal/terminal.routes.js";
|
|
12
|
+
import { registerWorkspaceRoutes } from "./modules/workspace/workspace.routes.js";
|
|
13
|
+
function errorBody(code, message) {
|
|
14
|
+
return { error: { code, message } };
|
|
15
|
+
}
|
|
16
|
+
export function createServer(config, logger) {
|
|
17
|
+
return Fastify({
|
|
18
|
+
logger: logger ?? {
|
|
19
|
+
level: config.logLevel,
|
|
20
|
+
redact: ['req.headers.cookie', 'req.headers.authorization', 'res.headers["set-cookie"]'],
|
|
21
|
+
},
|
|
22
|
+
// Only trust X-Forwarded-* from a reverse proxy on this machine (e.g. `tailscale serve`).
|
|
23
|
+
trustProxy: config.trustProxy ? ['127.0.0.1', '::1'] : false,
|
|
24
|
+
bodyLimit: 1024 * 1024,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
export async function registerApp(app, config, services) {
|
|
28
|
+
await app.register(fastifyCookie);
|
|
29
|
+
await app.register(fastifyWebsocket, { options: { maxPayload: 1024 * 1024 } });
|
|
30
|
+
registerSecurityHeaders(app, config);
|
|
31
|
+
registerAuthGuards(app, config, services.tokens);
|
|
32
|
+
app.setErrorHandler((error, request, reply) => {
|
|
33
|
+
if (isAppError(error)) {
|
|
34
|
+
if (error.statusCode >= 500)
|
|
35
|
+
request.log.error({ err: error.cause ?? error, code: error.code }, error.message);
|
|
36
|
+
return reply.status(error.statusCode).send(errorBody(error.code, error.message));
|
|
37
|
+
}
|
|
38
|
+
if (error.statusCode && error.statusCode >= 400 && error.statusCode < 500) {
|
|
39
|
+
return reply.status(error.statusCode).send(errorBody('BAD_REQUEST', error.message));
|
|
40
|
+
}
|
|
41
|
+
// Never leak internal details or stack traces to the browser.
|
|
42
|
+
request.log.error({ err: error }, 'unhandled error');
|
|
43
|
+
return reply.status(500).send(errorBody('INTERNAL', 'Internal server error.'));
|
|
44
|
+
});
|
|
45
|
+
app.get('/api/health', async () => ({ status: 'ok' }));
|
|
46
|
+
registerAuthRoutes(app, config, services.auth, services.tokens, services.limiter);
|
|
47
|
+
registerHostRoutes(app, services.hosts, services.workspaces);
|
|
48
|
+
registerWorkspaceRoutes(app, services.workspaces, config.limits.maxFileBytes);
|
|
49
|
+
registerGitRoutes(app, services.workspaces, services.commitMessages);
|
|
50
|
+
registerTerminalRoutes(app, services.terminals, services.tokens);
|
|
51
|
+
const staticDir = config.staticDir;
|
|
52
|
+
if (staticDir) {
|
|
53
|
+
await app.register(fastifyStatic, {
|
|
54
|
+
root: staticDir,
|
|
55
|
+
wildcard: false,
|
|
56
|
+
setHeaders(res, filePath) {
|
|
57
|
+
const hashedAsset = path.relative(staticDir, filePath).startsWith(`assets${path.sep}`);
|
|
58
|
+
res.header('Cache-Control', hashedAsset ? 'public, max-age=31536000, immutable' : 'no-cache');
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
app.setNotFoundHandler((request, reply) => {
|
|
63
|
+
const isApi = request.url.startsWith('/api/') || request.url.startsWith('/ws/');
|
|
64
|
+
if (staticDir && !isApi && request.method === 'GET') {
|
|
65
|
+
// Client-side routes (e.g. /login, /setup) fall back to the SPA entry point.
|
|
66
|
+
reply.header('Cache-Control', 'no-cache');
|
|
67
|
+
return reply.type('text/html').sendFile('index.html');
|
|
68
|
+
}
|
|
69
|
+
return reply.status(404).send(errorBody('NOT_FOUND', 'Not found.'));
|
|
70
|
+
});
|
|
71
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { accessSync, constants, existsSync } from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
export class ConfigError extends Error {
|
|
6
|
+
issues;
|
|
7
|
+
constructor(issues) {
|
|
8
|
+
super(`Invalid configuration:\n${issues.map((issue) => ` - ${issue}`).join('\n')}`);
|
|
9
|
+
this.name = 'ConfigError';
|
|
10
|
+
this.issues = issues;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
const optionalString = z
|
|
14
|
+
.string()
|
|
15
|
+
.trim()
|
|
16
|
+
.optional()
|
|
17
|
+
.transform((value) => (value ? value : undefined));
|
|
18
|
+
const booleanString = z
|
|
19
|
+
.enum(['true', 'false', '1', '0', 'yes', 'no'])
|
|
20
|
+
.transform((value) => value === 'true' || value === '1' || value === 'yes');
|
|
21
|
+
const originList = z
|
|
22
|
+
.string()
|
|
23
|
+
.trim()
|
|
24
|
+
.optional()
|
|
25
|
+
.transform((value, ctx) => {
|
|
26
|
+
const origins = [];
|
|
27
|
+
for (const raw of (value ?? '').split(',').map((item) => item.trim()).filter(Boolean)) {
|
|
28
|
+
try {
|
|
29
|
+
const url = new URL(raw);
|
|
30
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:')
|
|
31
|
+
throw new Error('protocol');
|
|
32
|
+
if (!['localhost', '127.0.0.1', '[::1]'].includes(url.hostname) && !url.hostname.endsWith('.ts.net'))
|
|
33
|
+
throw new Error('private origin');
|
|
34
|
+
origins.push(url.origin);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
ctx.addIssue({ code: 'custom', message: `"${raw}" is not a valid private http(s) origin (use localhost or a *.ts.net address)` });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return origins;
|
|
41
|
+
});
|
|
42
|
+
const pathList = z.string().optional().transform((value, ctx) => {
|
|
43
|
+
try {
|
|
44
|
+
const result = z.array(z.string().min(1).refine((item) => item.startsWith('/') && !item.includes('\0'))).parse(JSON.parse(value ?? '[]'));
|
|
45
|
+
return [...new Set(result.map((item) => path.posix.normalize(item)))];
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
ctx.addIssue({ code: 'custom', message: 'must be a JSON array of absolute directory paths' });
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
const envSchema = z.object({
|
|
53
|
+
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
|
|
54
|
+
HOST: z.enum(['127.0.0.1', '::1']).default('127.0.0.1'),
|
|
55
|
+
PORT: z.coerce.number().int().min(1).max(65_535).default(4280),
|
|
56
|
+
LOG_LEVEL: z.enum(['fatal', 'error', 'warn', 'info', 'debug', 'trace', 'silent']).default('info'),
|
|
57
|
+
// Trusts X-Forwarded-* only from proxies on this machine (e.g. `tailscale serve`).
|
|
58
|
+
TRUST_PROXY: booleanString.default(true),
|
|
59
|
+
ALLOWED_ORIGINS: originList,
|
|
60
|
+
// The tunnel's own hostname, so unlike ALLOWED_ORIGINS it may be public; HTTPS only (cookies are Secure).
|
|
61
|
+
PUBLIC_URL: z
|
|
62
|
+
.string()
|
|
63
|
+
.trim()
|
|
64
|
+
.optional()
|
|
65
|
+
.transform((value, ctx) => {
|
|
66
|
+
if (!value)
|
|
67
|
+
return [];
|
|
68
|
+
try {
|
|
69
|
+
const url = new URL(value);
|
|
70
|
+
if (url.protocol !== 'https:' || value.includes(','))
|
|
71
|
+
throw new Error('not a single https URL');
|
|
72
|
+
return [url.origin];
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
ctx.addIssue({ code: 'custom', message: `"${value}" must be a single https:// URL` });
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
}),
|
|
79
|
+
DATA_DIR: optionalString,
|
|
80
|
+
LOCAL_WORKSPACE_ROOTS: pathList,
|
|
81
|
+
SSH_WORKSPACE_ROOTS: pathList,
|
|
82
|
+
AI_COMMIT_ENABLED: booleanString.default(false),
|
|
83
|
+
SESSION_TTL_HOURS: z.coerce.number().positive().max(24).default(8),
|
|
84
|
+
SESSION_IDLE_MINUTES: z.coerce.number().positive().max(120).default(30),
|
|
85
|
+
COOKIE_SECURE: z.enum(['auto', 'true', 'false']).default('auto'),
|
|
86
|
+
SSH_READY_TIMEOUT_MS: z.coerce.number().int().min(1000).max(120_000).default(20_000),
|
|
87
|
+
SSH_KEEPALIVE_INTERVAL_MS: z.coerce.number().int().min(0).max(300_000).default(15_000),
|
|
88
|
+
SSH_KNOWN_HOSTS_PATH: optionalString,
|
|
89
|
+
TERMINAL_TERM: z.string().trim().regex(/^[A-Za-z0-9._+-]+$/).default('xterm-256color'),
|
|
90
|
+
TERMINAL_MAX_SESSIONS: z.coerce.number().int().min(1).max(64).default(10),
|
|
91
|
+
TERMINAL_REPLAY_BUFFER_BYTES: z.coerce.number().int().min(16 * 1024).max(16 * 1024 * 1024).default(512 * 1024),
|
|
92
|
+
TERMINAL_ORPHAN_TIMEOUT_MINUTES: z.coerce.number().min(0).max(7 * 24 * 60).default(0),
|
|
93
|
+
MAX_FILE_BYTES: z.coerce.number().int().min(1024).max(64 * 1024 * 1024).default(2 * 1024 * 1024),
|
|
94
|
+
MAX_IMAGE_BYTES: z.coerce.number().int().min(1024).max(256 * 1024 * 1024).default(20 * 1024 * 1024),
|
|
95
|
+
MAX_DIFF_BYTES: z.coerce.number().int().min(1024).max(64 * 1024 * 1024).default(4 * 1024 * 1024),
|
|
96
|
+
MAX_DIR_ENTRIES: z.coerce.number().int().min(100).max(100_000).default(5000),
|
|
97
|
+
STATIC_DIR: optionalString,
|
|
98
|
+
});
|
|
99
|
+
export function expandHome(value) {
|
|
100
|
+
return value === '~' || value.startsWith('~/') ? path.join(os.homedir(), value.slice(1)) : value;
|
|
101
|
+
}
|
|
102
|
+
/** Parse and validate configuration. Throws ConfigError listing every problem. */
|
|
103
|
+
export function loadConfig(source = process.env, cwd = process.cwd()) {
|
|
104
|
+
const parsed = envSchema.safeParse(source);
|
|
105
|
+
if (!parsed.success) {
|
|
106
|
+
throw new ConfigError(parsed.error.issues.map((issue) => `${issue.path.join('.') || 'config'}: ${issue.message}`));
|
|
107
|
+
}
|
|
108
|
+
const env = parsed.data;
|
|
109
|
+
const issues = [];
|
|
110
|
+
// Serve the built frontend when it exists, so `npm start` needs no configuration.
|
|
111
|
+
let staticDir = null;
|
|
112
|
+
if (env.STATIC_DIR) {
|
|
113
|
+
staticDir = path.resolve(cwd, env.STATIC_DIR);
|
|
114
|
+
try {
|
|
115
|
+
accessSync(path.join(staticDir, 'index.html'), constants.R_OK);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
issues.push(`STATIC_DIR: ${staticDir}/index.html not found (run \`npm run build\` first)`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else if (env.NODE_ENV !== 'test' && existsSync(path.join(cwd, 'dist', 'index.html'))) {
|
|
122
|
+
staticDir = path.join(cwd, 'dist');
|
|
123
|
+
}
|
|
124
|
+
if (env.NODE_ENV === 'production' && env.COOKIE_SECURE === 'false')
|
|
125
|
+
issues.push('COOKIE_SECURE: cannot disable secure cookies in production');
|
|
126
|
+
if (issues.length > 0)
|
|
127
|
+
throw new ConfigError(issues);
|
|
128
|
+
return {
|
|
129
|
+
env: env.NODE_ENV,
|
|
130
|
+
host: env.HOST,
|
|
131
|
+
port: env.PORT,
|
|
132
|
+
logLevel: env.LOG_LEVEL,
|
|
133
|
+
trustProxy: env.TRUST_PROXY,
|
|
134
|
+
allowedOrigins: [...new Set([...env.ALLOWED_ORIGINS, ...env.PUBLIC_URL])],
|
|
135
|
+
publicUrl: env.PUBLIC_URL[0] ?? null,
|
|
136
|
+
dataDir: path.resolve(cwd, expandHome(env.DATA_DIR ?? 'data')),
|
|
137
|
+
workspace: {
|
|
138
|
+
localRoots: env.LOCAL_WORKSPACE_ROOTS,
|
|
139
|
+
sshRoots: env.SSH_WORKSPACE_ROOTS,
|
|
140
|
+
protectedPaths: [
|
|
141
|
+
path.resolve(cwd), path.resolve(cwd, expandHome(env.DATA_DIR ?? 'data')),
|
|
142
|
+
...['.ssh', '.config', '.aws', '.azure', '.kube', '.gnupg', '.claude', '.codex'].map((name) => path.join(os.homedir(), name)),
|
|
143
|
+
'/proc', '/dev', '/sys',
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
aiCommitEnabled: env.AI_COMMIT_ENABLED,
|
|
147
|
+
auth: {
|
|
148
|
+
sessionTtlMs: env.SESSION_TTL_HOURS * 60 * 60 * 1000,
|
|
149
|
+
sessionIdleMs: env.SESSION_IDLE_MINUTES * 60 * 1000,
|
|
150
|
+
cookieSecure: env.COOKIE_SECURE === 'auto' ? 'auto' : env.COOKIE_SECURE === 'true',
|
|
151
|
+
},
|
|
152
|
+
ssh: {
|
|
153
|
+
readyTimeoutMs: env.SSH_READY_TIMEOUT_MS,
|
|
154
|
+
keepaliveIntervalMs: env.SSH_KEEPALIVE_INTERVAL_MS,
|
|
155
|
+
agentSocket: source.SSH_AUTH_SOCK || null,
|
|
156
|
+
knownHostsPath: expandHome(env.SSH_KNOWN_HOSTS_PATH ?? '~/.ssh/known_hosts'),
|
|
157
|
+
},
|
|
158
|
+
terminal: {
|
|
159
|
+
term: env.TERMINAL_TERM,
|
|
160
|
+
maxSessions: env.TERMINAL_MAX_SESSIONS,
|
|
161
|
+
replayBufferBytes: env.TERMINAL_REPLAY_BUFFER_BYTES,
|
|
162
|
+
orphanTimeoutMs: env.TERMINAL_ORPHAN_TIMEOUT_MINUTES * 60 * 1000,
|
|
163
|
+
},
|
|
164
|
+
limits: {
|
|
165
|
+
maxFileBytes: env.MAX_FILE_BYTES,
|
|
166
|
+
maxImageBytes: env.MAX_IMAGE_BYTES,
|
|
167
|
+
maxDiffBytes: env.MAX_DIFF_BYTES,
|
|
168
|
+
maxDirEntries: env.MAX_DIR_ENTRIES,
|
|
169
|
+
maxGitChanges: 5000,
|
|
170
|
+
},
|
|
171
|
+
staticDir,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** Minimal FIFO semaphore used to cap concurrent SSH channels. */
|
|
2
|
+
export class Semaphore {
|
|
3
|
+
#available;
|
|
4
|
+
#waiters = [];
|
|
5
|
+
constructor(permits) {
|
|
6
|
+
this.#available = permits;
|
|
7
|
+
}
|
|
8
|
+
async run(task) {
|
|
9
|
+
await this.#acquire();
|
|
10
|
+
try {
|
|
11
|
+
return await task();
|
|
12
|
+
}
|
|
13
|
+
finally {
|
|
14
|
+
this.#release();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
#acquire() {
|
|
18
|
+
if (this.#available > 0) {
|
|
19
|
+
this.#available -= 1;
|
|
20
|
+
return Promise.resolve();
|
|
21
|
+
}
|
|
22
|
+
return new Promise((resolve) => this.#waiters.push(resolve));
|
|
23
|
+
}
|
|
24
|
+
#release() {
|
|
25
|
+
const next = this.#waiters.shift();
|
|
26
|
+
if (next)
|
|
27
|
+
next();
|
|
28
|
+
else
|
|
29
|
+
this.#available += 1;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Map over items with at most `limit` promises in flight, preserving order. */
|
|
33
|
+
export async function mapWithConcurrency(items, limit, mapper) {
|
|
34
|
+
const results = new Array(items.length);
|
|
35
|
+
let index = 0;
|
|
36
|
+
const worker = async () => {
|
|
37
|
+
while (index < items.length) {
|
|
38
|
+
const current = index++;
|
|
39
|
+
results[current] = await mapper(items[current]);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
|
|
43
|
+
return results;
|
|
44
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const defaultStatus = {
|
|
2
|
+
BAD_REQUEST: 400,
|
|
3
|
+
UNAUTHORIZED: 401,
|
|
4
|
+
FORBIDDEN: 403,
|
|
5
|
+
NOT_FOUND: 404,
|
|
6
|
+
CONFLICT: 409,
|
|
7
|
+
SETUP_REQUIRED: 409,
|
|
8
|
+
PATH_OUTSIDE_WORKSPACE: 403,
|
|
9
|
+
PERMISSION_DENIED: 403,
|
|
10
|
+
NOT_A_DIRECTORY: 400,
|
|
11
|
+
NOT_A_FILE: 400,
|
|
12
|
+
REMOTE_UNAVAILABLE: 503,
|
|
13
|
+
TIMEOUT: 504,
|
|
14
|
+
NOT_A_REPOSITORY: 404,
|
|
15
|
+
GIT_ERROR: 500,
|
|
16
|
+
GENERATION_FAILED: 502,
|
|
17
|
+
TERMINAL_UNAVAILABLE: 503,
|
|
18
|
+
TERMINAL_LIMIT: 429,
|
|
19
|
+
SESSION_NOT_FOUND: 404,
|
|
20
|
+
TOO_MANY_REQUESTS: 429,
|
|
21
|
+
INTERNAL: 500,
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Error whose `message` is safe to show to the browser. Anything that is not
|
|
25
|
+
* an AppError is reported to clients as a generic INTERNAL error.
|
|
26
|
+
*/
|
|
27
|
+
export class AppError extends Error {
|
|
28
|
+
code;
|
|
29
|
+
statusCode;
|
|
30
|
+
constructor(code, message, options) {
|
|
31
|
+
super(message, { cause: options?.cause });
|
|
32
|
+
this.name = 'AppError';
|
|
33
|
+
this.code = code;
|
|
34
|
+
this.statusCode = options?.statusCode ?? defaultStatus[code];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function isAppError(error) {
|
|
38
|
+
return error instanceof AppError;
|
|
39
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a `Range: bytes=...` request header against a file of `size` bytes.
|
|
3
|
+
* Returns null to send the whole file (no header, an unsupported unit, or several
|
|
4
|
+
* ranges, which a server may answer in full) and 'unsatisfiable' for a 416.
|
|
5
|
+
*/
|
|
6
|
+
export function parseByteRange(header, size) {
|
|
7
|
+
if (!header || size === 0)
|
|
8
|
+
return null;
|
|
9
|
+
const match = /^bytes=(\d*)-(\d*)$/.exec(header.trim());
|
|
10
|
+
if (!match)
|
|
11
|
+
return null;
|
|
12
|
+
const [, first, last] = match;
|
|
13
|
+
if (first === '' && last === '')
|
|
14
|
+
return null;
|
|
15
|
+
if (first === '') {
|
|
16
|
+
// Suffix range: the last N bytes.
|
|
17
|
+
const length = Number(last);
|
|
18
|
+
if (length === 0)
|
|
19
|
+
return 'unsatisfiable';
|
|
20
|
+
return { start: Math.max(0, size - length), end: size - 1 };
|
|
21
|
+
}
|
|
22
|
+
const start = Number(first);
|
|
23
|
+
const end = last === '' ? size - 1 : Math.min(Number(last), size - 1);
|
|
24
|
+
if (start >= size || end < start)
|
|
25
|
+
return 'unsatisfiable';
|
|
26
|
+
return { start, end };
|
|
27
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quote a single argument for a POSIX shell (sh, bash, zsh, dash, ksh).
|
|
3
|
+
* Everything is wrapped in single quotes; embedded single quotes are closed,
|
|
4
|
+
* escaped, and reopened. NUL bytes cannot be represented and are rejected.
|
|
5
|
+
*/
|
|
6
|
+
export function quoteShellArg(value) {
|
|
7
|
+
if (value.includes('\0')) {
|
|
8
|
+
throw new Error('Shell arguments must not contain NUL bytes');
|
|
9
|
+
}
|
|
10
|
+
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
11
|
+
}
|
|
12
|
+
export function buildShellCommand(argv) {
|
|
13
|
+
return argv.map(quoteShellArg).join(' ');
|
|
14
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { AppError } from "./errors.js";
|
|
2
|
+
/** Parse untrusted input, converting validation failures into a 400 AppError. */
|
|
3
|
+
export function parseInput(schema, input) {
|
|
4
|
+
const result = schema.safeParse(input);
|
|
5
|
+
if (!result.success) {
|
|
6
|
+
const issue = result.error.issues[0];
|
|
7
|
+
const where = issue?.path.length ? `${issue.path.join('.')}: ` : '';
|
|
8
|
+
throw new AppError('BAD_REQUEST', `${where}${issue?.message ?? 'Invalid request.'}`);
|
|
9
|
+
}
|
|
10
|
+
return result.data;
|
|
11
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { createServer, registerApp } from "./app.js";
|
|
3
|
+
import { ConfigError, loadConfig } from "./config/config.js";
|
|
4
|
+
import { AppStore, StoreError } from "./modules/store/app-store.js";
|
|
5
|
+
import { createServices } from "./services.js";
|
|
6
|
+
function fail(message) {
|
|
7
|
+
console.error(message);
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
function readConfig() {
|
|
11
|
+
try {
|
|
12
|
+
return loadConfig();
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
if (error instanceof ConfigError)
|
|
16
|
+
fail(`${error.message}\n\nSee .env.example and docs/configuration.md.`);
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function openStore(dataDir) {
|
|
21
|
+
try {
|
|
22
|
+
return AppStore.open(dataDir);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
if (error instanceof StoreError)
|
|
26
|
+
fail(error.message);
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const LOOPBACK = ['127.0.0.1', '::1', 'localhost', '0.0.0.0', '::'];
|
|
31
|
+
/** Human-readable startup hints on stdout (separate from the JSON logs). */
|
|
32
|
+
function printBanner(config, setupToken) {
|
|
33
|
+
const serverUrl = `http://${LOOPBACK.includes(config.host) ? 'localhost' : config.host}:${config.port}`;
|
|
34
|
+
// Without a built frontend the UI comes from the Vite dev server (npm run dev).
|
|
35
|
+
const uiUrl = config.staticDir ? serverUrl : 'http://localhost:5173';
|
|
36
|
+
const lines = ['', ' Remote Workspace is running', ` ➜ Open: ${uiUrl}`];
|
|
37
|
+
if (config.publicUrl)
|
|
38
|
+
lines.push(` ➜ Public: ${config.publicUrl}`);
|
|
39
|
+
if (setupToken) {
|
|
40
|
+
lines.push('', ' First-time setup — open this one-time link to create your password:', ` ➜ ${uiUrl}/setup#token=${setupToken}`, ...(config.publicUrl && config.staticDir ? [` ➜ ${config.publicUrl}/setup#token=${setupToken}`] : []), ` (token also saved in ${path.join(config.dataDir, 'setup-token')}; it stops working after setup)`);
|
|
41
|
+
}
|
|
42
|
+
if (!config.staticDir)
|
|
43
|
+
lines.push('', ' No built frontend found: start the UI with `npm run dev`, or run `npm start` to build and serve it.');
|
|
44
|
+
process.stdout.write(`${lines.join('\n')}\n\n`);
|
|
45
|
+
}
|
|
46
|
+
const config = readConfig();
|
|
47
|
+
const store = openStore(config.dataDir);
|
|
48
|
+
const app = createServer(config);
|
|
49
|
+
const services = createServices(config, store, app.log);
|
|
50
|
+
app.addHook('onClose', async () => services.dispose());
|
|
51
|
+
await registerApp(app, config, services);
|
|
52
|
+
process.on('unhandledRejection', (reason) => app.log.error({ err: reason }, 'unhandled promise rejection'));
|
|
53
|
+
let shuttingDown = false;
|
|
54
|
+
async function shutdown(signal) {
|
|
55
|
+
if (shuttingDown)
|
|
56
|
+
return;
|
|
57
|
+
shuttingDown = true;
|
|
58
|
+
app.log.info({ signal }, 'shutting down');
|
|
59
|
+
setTimeout(() => process.exit(1), 10_000).unref();
|
|
60
|
+
await app.close();
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
process.once('SIGINT', (signal) => void shutdown(signal));
|
|
64
|
+
process.once('SIGTERM', (signal) => void shutdown(signal));
|
|
65
|
+
await app.listen({ host: config.host, port: config.port });
|
|
66
|
+
if (!['127.0.0.1', '::1', 'localhost'].includes(config.host)) {
|
|
67
|
+
app.log.warn({ host: config.host }, 'listening on a non-loopback address; make sure it is only reachable from your tailnet');
|
|
68
|
+
}
|
|
69
|
+
app.log.info({ dataDir: config.dataDir, hosts: store.state.hosts.length, projects: store.state.workspaces.length, setupRequired: services.auth.setupRequired }, 'remote workspace ready');
|
|
70
|
+
printBanner(config, services.auth.setupToken);
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { isIP } from 'node:net';
|
|
2
|
+
import { AppError } from "../../lib/errors.js";
|
|
3
|
+
export const SESSION_COOKIE = 'rw_session';
|
|
4
|
+
const PUBLIC_API_ROUTES = new Set(['/api/health', '/api/auth/login', '/api/auth/session', '/api/auth/setup']);
|
|
5
|
+
function requestPath(request) {
|
|
6
|
+
const queryIndex = request.url.indexOf('?');
|
|
7
|
+
return queryIndex === -1 ? request.url : request.url.slice(0, queryIndex);
|
|
8
|
+
}
|
|
9
|
+
function isTrustedHostname(hostname) {
|
|
10
|
+
const bare = hostname.replace(/^\[|\]$/g, '').toLowerCase();
|
|
11
|
+
return bare === 'localhost' || bare.endsWith('.localhost') || isIP(bare) !== 0 || bare.endsWith('.ts.net');
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Whether a browser Origin may make state-changing requests. Accepted:
|
|
15
|
+
* - origins listed in ALLOWED_ORIGINS;
|
|
16
|
+
* - same-origin requests (Origin matches the Host the browser used) when that
|
|
17
|
+
* host is a loopback name, an IP literal, or a Tailscale *.ts.net name.
|
|
18
|
+
* A DNS-rebinding page always carries the attacker's own domain in both Origin
|
|
19
|
+
* and Host, so it fails the hostname check; other sites fail the equality check.
|
|
20
|
+
*/
|
|
21
|
+
export function isTrustedOrigin(origin, requestHost, allowedOrigins) {
|
|
22
|
+
if (!origin)
|
|
23
|
+
return false;
|
|
24
|
+
if (allowedOrigins.has(origin))
|
|
25
|
+
return true;
|
|
26
|
+
let url;
|
|
27
|
+
try {
|
|
28
|
+
url = new URL(origin);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:')
|
|
34
|
+
return false;
|
|
35
|
+
return url.host === requestHost.toLowerCase() && isTrustedHostname(url.hostname);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Global request guards for /api and /ws:
|
|
39
|
+
* - state-changing requests and WebSocket upgrades must carry an allowed Origin
|
|
40
|
+
* (CSRF and cross-site WebSocket hijacking protection);
|
|
41
|
+
* - everything except a few public endpoints requires a valid session cookie.
|
|
42
|
+
* The static SPA bundle is public: it contains no data or secrets.
|
|
43
|
+
*/
|
|
44
|
+
export function registerAuthGuards(app, config, tokens) {
|
|
45
|
+
const allowedOrigins = new Set(config.allowedOrigins);
|
|
46
|
+
app.addHook('onRequest', async (request) => {
|
|
47
|
+
const path = requestPath(request);
|
|
48
|
+
const isApi = path === '/api' || path.startsWith('/api/');
|
|
49
|
+
const isSocket = path.startsWith('/ws/');
|
|
50
|
+
if (!isApi && !isSocket)
|
|
51
|
+
return;
|
|
52
|
+
if (request.headers['sec-fetch-site'] === 'cross-site') {
|
|
53
|
+
throw new AppError('FORBIDDEN', 'Cross-site requests are not allowed.');
|
|
54
|
+
}
|
|
55
|
+
const unsafe = isSocket || (request.method !== 'GET' && request.method !== 'HEAD' && request.method !== 'OPTIONS');
|
|
56
|
+
if (unsafe) {
|
|
57
|
+
const origin = request.headers.origin;
|
|
58
|
+
if (!isTrustedOrigin(origin, request.host, allowedOrigins)) {
|
|
59
|
+
request.log.warn({ origin: origin ?? null, host: request.host, path }, 'rejected request from disallowed origin');
|
|
60
|
+
throw new AppError('FORBIDDEN', 'Origin not allowed. Add the address you use to ALLOWED_ORIGINS.');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (PUBLIC_API_ROUTES.has(path))
|
|
64
|
+
return;
|
|
65
|
+
const token = request.cookies[SESSION_COOKIE];
|
|
66
|
+
if (!tokens.verify(token)) {
|
|
67
|
+
throw new AppError('UNAUTHORIZED', 'Authentication required.');
|
|
68
|
+
}
|
|
69
|
+
if (unsafe && !isSocket)
|
|
70
|
+
tokens.touch(token);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function contentSecurityPolicy(config) {
|
|
74
|
+
const socketOrigins = config.allowedOrigins.map((origin) => origin.replace(/^http/, 'ws'));
|
|
75
|
+
return [
|
|
76
|
+
"default-src 'self'",
|
|
77
|
+
"script-src 'self'",
|
|
78
|
+
// CodeMirror and xterm.js inject runtime styles.
|
|
79
|
+
"style-src 'self' 'unsafe-inline'",
|
|
80
|
+
"img-src 'self' data:",
|
|
81
|
+
"font-src 'self' data:",
|
|
82
|
+
['connect-src', "'self'", ...socketOrigins].join(' '),
|
|
83
|
+
"worker-src 'self' blob:",
|
|
84
|
+
"frame-ancestors 'none'",
|
|
85
|
+
"base-uri 'none'",
|
|
86
|
+
"form-action 'self'",
|
|
87
|
+
"object-src 'none'",
|
|
88
|
+
].join('; ');
|
|
89
|
+
}
|
|
90
|
+
export function registerSecurityHeaders(app, config) {
|
|
91
|
+
const csp = contentSecurityPolicy(config);
|
|
92
|
+
app.addHook('onSend', async (request, reply, payload) => {
|
|
93
|
+
reply.header('X-Content-Type-Options', 'nosniff');
|
|
94
|
+
reply.header('Referrer-Policy', 'no-referrer');
|
|
95
|
+
reply.header('X-Frame-Options', 'DENY');
|
|
96
|
+
reply.header('Cross-Origin-Opener-Policy', 'same-origin');
|
|
97
|
+
if (requestPath(request).startsWith('/api/'))
|
|
98
|
+
reply.header('Cache-Control', 'no-store');
|
|
99
|
+
const contentType = reply.getHeader('content-type');
|
|
100
|
+
if (typeof contentType === 'string' && contentType.startsWith('text/html'))
|
|
101
|
+
reply.header('Content-Security-Policy', csp);
|
|
102
|
+
return payload;
|
|
103
|
+
});
|
|
104
|
+
}
|