@openspecui/web 0.9.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/dist/assets/abap-BdImnpbu.js +1 -0
- package/dist/assets/actionscript-3-CfeIJUat.js +1 -0
- package/dist/assets/ada-bCR0ucgS.js +1 -0
- package/dist/assets/andromeeda-C-Jbm3Hp.js +1 -0
- package/dist/assets/angular-html-CU67Zn6k.js +1 -0
- package/dist/assets/angular-ts-BwZT4LLn.js +1 -0
- package/dist/assets/apache-Pmp26Uib.js +1 -0
- package/dist/assets/apex-DDbsPZ6N.js +1 -0
- package/dist/assets/apl-B4CMkyY2.js +1 -0
- package/dist/assets/apl-dKokRX4l.js +1 -0
- package/dist/assets/applescript-Co6uUVPk.js +1 -0
- package/dist/assets/ara-BRHolxvo.js +1 -0
- package/dist/assets/asciiarmor-Df11BRmG.js +1 -0
- package/dist/assets/asciidoc-Dv7Oe6Be.js +1 -0
- package/dist/assets/asm-D_Q5rh1f.js +1 -0
- package/dist/assets/asn1-EdZsLKOL.js +1 -0
- package/dist/assets/asterisk-B-8jnY81.js +1 -0
- package/dist/assets/astro-CbQHKStN.js +1 -0
- package/dist/assets/aurora-x-D-2ljcwZ.js +1 -0
- package/dist/assets/awk-DMzUqQB5.js +1 -0
- package/dist/assets/ayu-dark-Cv9koXgw.js +1 -0
- package/dist/assets/ballerina-BFfxhgS-.js +1 -0
- package/dist/assets/bat-BkioyH1T.js +1 -0
- package/dist/assets/beancount-k_qm7-4y.js +1 -0
- package/dist/assets/berry-uYugtg8r.js +1 -0
- package/dist/assets/bibtex-CHM0blh-.js +1 -0
- package/dist/assets/bicep-Bmn6On1c.js +1 -0
- package/dist/assets/blade-DVc8C-J4.js +1 -0
- package/dist/assets/brainfuck-C4LP7Hcl.js +1 -0
- package/dist/assets/bsl-BO_Y6i37.js +1 -0
- package/dist/assets/c-BIGW1oBm.js +1 -0
- package/dist/assets/cadence-Bv_4Rxtq.js +1 -0
- package/dist/assets/cairo-KRGpt6FW.js +1 -0
- package/dist/assets/catppuccin-frappe-DFWUc33u.js +1 -0
- package/dist/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
- package/dist/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
- package/dist/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
- package/dist/assets/clarity-D53aC0YG.js +1 -0
- package/dist/assets/clike-B9uivgTg.js +1 -0
- package/dist/assets/clojure-BMjYHr_A.js +1 -0
- package/dist/assets/clojure-P80f7IUj.js +1 -0
- package/dist/assets/cmake-BQqOBYOt.js +1 -0
- package/dist/assets/cmake-D1j8_8rp.js +1 -0
- package/dist/assets/cobol-CWcv1MsR.js +1 -0
- package/dist/assets/cobol-nwyudZeR.js +1 -0
- package/dist/assets/codeowners-Bp6g37R7.js +1 -0
- package/dist/assets/codeql-DsOJ9woJ.js +1 -0
- package/dist/assets/coffee-Ch7k5sss.js +1 -0
- package/dist/assets/coffeescript-S37ZYGWr.js +1 -0
- package/dist/assets/common-lisp-Cg-RD9OK.js +1 -0
- package/dist/assets/commonlisp-DBKNyK5s.js +1 -0
- package/dist/assets/coq-DkFqJrB1.js +1 -0
- package/dist/assets/cpp-CofmeUqb.js +1 -0
- package/dist/assets/crystal-SjHAIU92.js +1 -0
- package/dist/assets/crystal-tKQVLTB8.js +1 -0
- package/dist/assets/csharp-K5feNrxe.js +1 -0
- package/dist/assets/css-BnMrqG3P.js +1 -0
- package/dist/assets/css-DPfMkruS.js +1 -0
- package/dist/assets/csv-fuZLfV_i.js +1 -0
- package/dist/assets/cue-D82EKSYY.js +1 -0
- package/dist/assets/cypher-COkxafJQ.js +1 -0
- package/dist/assets/cypher-C_CwsFkJ.js +1 -0
- package/dist/assets/d-85-TOEBH.js +1 -0
- package/dist/assets/d-pRatUO7H.js +1 -0
- package/dist/assets/dark-plus-C3mMm8J8.js +1 -0
- package/dist/assets/dart-CF10PKvl.js +1 -0
- package/dist/assets/dax-CEL-wOlO.js +1 -0
- package/dist/assets/desktop-BmXAJ9_W.js +1 -0
- package/dist/assets/diff-D97Zzqfu.js +1 -0
- package/dist/assets/diff-DbItnlRl.js +1 -0
- package/dist/assets/docker-BcOcwvcX.js +1 -0
- package/dist/assets/dockerfile-BKs6k2Af.js +1 -0
- package/dist/assets/dotenv-Da5cRb03.js +1 -0
- package/dist/assets/dracula-BzJJZx-M.js +1 -0
- package/dist/assets/dracula-soft-BXkSAIEj.js +1 -0
- package/dist/assets/dream-maker-BtqSS_iP.js +1 -0
- package/dist/assets/dtd-DF_7sFjM.js +1 -0
- package/dist/assets/dylan-DwRh75JA.js +1 -0
- package/dist/assets/ebnf-CDyGwa7X.js +1 -0
- package/dist/assets/ecl-Cabwm37j.js +1 -0
- package/dist/assets/edge-BkV0erSs.js +1 -0
- package/dist/assets/eiffel-CnydiIhH.js +1 -0
- package/dist/assets/elixir-CDX3lj18.js +1 -0
- package/dist/assets/elm-DbKCFpqz.js +1 -0
- package/dist/assets/elm-vLlmbW-K.js +1 -0
- package/dist/assets/emacs-lisp-C9XAeP06.js +1 -0
- package/dist/assets/erb-BOJIQeun.js +1 -0
- package/dist/assets/erlang-BNw1qcRV.js +1 -0
- package/dist/assets/erlang-DsQrWhSR.js +1 -0
- package/dist/assets/everforest-dark-BgDCqdQA.js +1 -0
- package/dist/assets/everforest-light-C8M2exoo.js +1 -0
- package/dist/assets/factor-kuTfRLto.js +1 -0
- package/dist/assets/fcl-Kvtd6kyn.js +1 -0
- package/dist/assets/fennel-BYunw83y.js +1 -0
- package/dist/assets/fish-BvzEVeQv.js +1 -0
- package/dist/assets/fluent-C4IJs8-o.js +1 -0
- package/dist/assets/forth-Ffai-XNe.js +1 -0
- package/dist/assets/fortran-DYz_wnZ1.js +1 -0
- package/dist/assets/fortran-fixed-form-BZjJHVRy.js +1 -0
- package/dist/assets/fortran-free-form-D22FLkUw.js +1 -0
- package/dist/assets/fsharp-CXgrBDvD.js +1 -0
- package/dist/assets/gas-Bneqetm1.js +1 -0
- package/dist/assets/gdresource-B7Tvp0Sc.js +1 -0
- package/dist/assets/gdscript-DTMYz4Jt.js +1 -0
- package/dist/assets/gdshader-DkwncUOv.js +1 -0
- package/dist/assets/genie-D0YGMca9.js +1 -0
- package/dist/assets/gherkin-DyxjwDmM.js +1 -0
- package/dist/assets/gherkin-heZmZLOM.js +1 -0
- package/dist/assets/git-commit-F4YmCXRG.js +1 -0
- package/dist/assets/git-rebase-r7XF79zn.js +1 -0
- package/dist/assets/github-dark-DHJKELXO.js +1 -0
- package/dist/assets/github-dark-default-Cuk6v7N8.js +1 -0
- package/dist/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
- package/dist/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
- package/dist/assets/github-light-DAi9KRSo.js +1 -0
- package/dist/assets/github-light-default-D7oLnXFd.js +1 -0
- package/dist/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
- package/dist/assets/gleam-BspZqrRM.js +1 -0
- package/dist/assets/glimmer-js-Rg0-pVw9.js +1 -0
- package/dist/assets/glimmer-ts-U6CK756n.js +1 -0
- package/dist/assets/glsl-DplSGwfg.js +1 -0
- package/dist/assets/gnuplot-DdkO51Og.js +1 -0
- package/dist/assets/go-Dn2_MT6a.js +1 -0
- package/dist/assets/graphql-ChdNCCLP.js +1 -0
- package/dist/assets/groovy-D9Dt4D0W.js +1 -0
- package/dist/assets/groovy-gcz8RCvz.js +1 -0
- package/dist/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
- package/dist/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
- package/dist/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
- package/dist/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
- package/dist/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
- package/dist/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
- package/dist/assets/hack-CaT9iCJl.js +1 -0
- package/dist/assets/haml-B8DHNrY2.js +1 -0
- package/dist/assets/handlebars-BL8al0AC.js +1 -0
- package/dist/assets/haskell-Cw1EW3IL.js +1 -0
- package/dist/assets/haskell-Df6bDoY_.js +1 -0
- package/dist/assets/haxe-CzTSHFRz.js +1 -0
- package/dist/assets/haxe-H-WmDvRZ.js +1 -0
- package/dist/assets/hcl-BWvSN4gD.js +1 -0
- package/dist/assets/hjson-D5-asLiD.js +1 -0
- package/dist/assets/hlsl-D3lLCCz7.js +1 -0
- package/dist/assets/houston-DnULxvSX.js +1 -0
- package/dist/assets/html-GMplVEZG.js +1 -0
- package/dist/assets/html-derivative-BFtXZ54Q.js +1 -0
- package/dist/assets/http-DBlCnlav.js +1 -0
- package/dist/assets/http-jrhK8wxY.js +1 -0
- package/dist/assets/hurl-irOxFIW8.js +1 -0
- package/dist/assets/hxml-Bvhsp5Yf.js +1 -0
- package/dist/assets/hy-DFXneXwc.js +1 -0
- package/dist/assets/idl-BEugSyMb.js +1 -0
- package/dist/assets/imba-DGztddWO.js +1 -0
- package/dist/assets/index-8c6bEJ99.js +1 -0
- package/dist/assets/index-AbWe21oh.js +2 -0
- package/dist/assets/index-ArhptQw0.js +1 -0
- package/dist/assets/index-B1hpa--1.js +3 -0
- package/dist/assets/index-Bafja8o4.js +1 -0
- package/dist/assets/index-Bp00uZNc.js +1 -0
- package/dist/assets/index-BsTieXqQ.js +1 -0
- package/dist/assets/index-BvGAWAqS.js +1 -0
- package/dist/assets/index-CCfVkFzN.js +1 -0
- package/dist/assets/index-D-Urq2hl.css +1 -0
- package/dist/assets/index-D3mXuuih.js +1 -0
- package/dist/assets/index-DFOLYN6W.js +1 -0
- package/dist/assets/index-DpxkOmNJ.js +7 -0
- package/dist/assets/index-YZ-iXB95.js +309 -0
- package/dist/assets/index-eA_XNQ_L.js +1 -0
- package/dist/assets/index-ftYom_wU.js +1 -0
- package/dist/assets/index-gvPT4BlL.js +1 -0
- package/dist/assets/ini-BEwlwnbL.js +1 -0
- package/dist/assets/java-CylS5w8V.js +1 -0
- package/dist/assets/javascript-iXu5QeM3.js +1 -0
- package/dist/assets/javascript-wDzz0qaB.js +1 -0
- package/dist/assets/jinja-4LBKfQ-Z.js +1 -0
- package/dist/assets/jison-wvAkD_A8.js +1 -0
- package/dist/assets/json-Cp-IABpG.js +1 -0
- package/dist/assets/json5-C9tS-k6U.js +1 -0
- package/dist/assets/jsonc-Des-eS-w.js +1 -0
- package/dist/assets/jsonl-DcaNXYhu.js +1 -0
- package/dist/assets/jsonnet-DFQXde-d.js +1 -0
- package/dist/assets/jssm-C2t-YnRu.js +1 -0
- package/dist/assets/jsx-g9-lgVsj.js +1 -0
- package/dist/assets/julia-C8NyazO9.js +1 -0
- package/dist/assets/julia-DuME0IfC.js +1 -0
- package/dist/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
- package/dist/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
- package/dist/assets/kanagawa-wave-DWedfzmr.js +1 -0
- package/dist/assets/kdl-DV7GczEv.js +1 -0
- package/dist/assets/kotlin-BdnUsdx6.js +1 -0
- package/dist/assets/kusto-BvAqAH-y.js +1 -0
- package/dist/assets/laserwave-DUszq2jm.js +1 -0
- package/dist/assets/latex-BdAV_C_H.js +1 -0
- package/dist/assets/lean-Bc6EcWN3.js +1 -0
- package/dist/assets/less-B1dDrJ26.js +1 -0
- package/dist/assets/light-plus-B7mTdjB0.js +1 -0
- package/dist/assets/liquid-DYVedYrR.js +1 -0
- package/dist/assets/livescript-BwQOo05w.js +1 -0
- package/dist/assets/llvm-BtvRca6l.js +1 -0
- package/dist/assets/log-2UxHyX5q.js +1 -0
- package/dist/assets/logo-BtOb2qkB.js +1 -0
- package/dist/assets/lua-BbnMAYS6.js +1 -0
- package/dist/assets/lua-BgMRiT3U.js +1 -0
- package/dist/assets/luau-CXu1NL6O.js +1 -0
- package/dist/assets/make-CHLpvVh8.js +1 -0
- package/dist/assets/markdown-Cvjx9yec.js +1 -0
- package/dist/assets/marko-CPi9NSCl.js +1 -0
- package/dist/assets/material-theme-D5KoaKCx.js +1 -0
- package/dist/assets/material-theme-darker-BfHTSMKl.js +1 -0
- package/dist/assets/material-theme-lighter-B0m2ddpp.js +1 -0
- package/dist/assets/material-theme-ocean-CyktbL80.js +1 -0
- package/dist/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
- package/dist/assets/mathematica-DTrFuWx2.js +1 -0
- package/dist/assets/matlab-D7o27uSR.js +1 -0
- package/dist/assets/mbox-CNhZ1qSd.js +1 -0
- package/dist/assets/mdc-DUICxH0z.js +1 -0
- package/dist/assets/mdx-Cmh6b_Ma.js +1 -0
- package/dist/assets/mermaid-DKYwYmdq.js +1 -0
- package/dist/assets/min-dark-CafNBF8u.js +1 -0
- package/dist/assets/min-light-CTRr51gU.js +1 -0
- package/dist/assets/mipsasm-CKIfxQSi.js +1 -0
- package/dist/assets/mirc-CjQqDB4T.js +1 -0
- package/dist/assets/mllike-CXdrOF99.js +1 -0
- package/dist/assets/modelica-Dc1JOy9r.js +1 -0
- package/dist/assets/mojo-1DNp92w6.js +1 -0
- package/dist/assets/monokai-D4h5O-jR.js +1 -0
- package/dist/assets/move-Bu9oaDYs.js +1 -0
- package/dist/assets/mscgen-BA5vi2Kp.js +1 -0
- package/dist/assets/mumps-BT43cFF4.js +1 -0
- package/dist/assets/narrat-DRg8JJMk.js +1 -0
- package/dist/assets/nextflow-BrzmwbiE.js +1 -0
- package/dist/assets/nginx-DdIZxoE0.js +1 -0
- package/dist/assets/nginx-DknmC5AR.js +1 -0
- package/dist/assets/night-owl-C39BiMTA.js +1 -0
- package/dist/assets/nim-CVrawwO9.js +1 -0
- package/dist/assets/nix-c8nO5XWb.js +1 -0
- package/dist/assets/nord-Ddv68eIx.js +1 -0
- package/dist/assets/nsis-LdVXkNf5.js +1 -0
- package/dist/assets/ntriples-BfvgReVJ.js +1 -0
- package/dist/assets/nushell-C-sUppwS.js +1 -0
- package/dist/assets/objective-c-DXmwc3jG.js +1 -0
- package/dist/assets/objective-cpp-CLxacb5B.js +1 -0
- package/dist/assets/ocaml-C0hk2d4L.js +1 -0
- package/dist/assets/octave-Ck1zUtKM.js +1 -0
- package/dist/assets/one-dark-pro-DVMEJ2y_.js +1 -0
- package/dist/assets/one-light-PoHY5YXO.js +1 -0
- package/dist/assets/openscad-C4EeE6gA.js +1 -0
- package/dist/assets/oz-BzwKVEFT.js +1 -0
- package/dist/assets/pascal--L3eBynH.js +1 -0
- package/dist/assets/pascal-D93ZcfNL.js +1 -0
- package/dist/assets/perl-C0TMdlhV.js +1 -0
- package/dist/assets/perl-CdXCOZ3F.js +1 -0
- package/dist/assets/php-CDn_0X-4.js +1 -0
- package/dist/assets/pig-CevX1Tat.js +1 -0
- package/dist/assets/pkl-u5AG7uiY.js +1 -0
- package/dist/assets/plastic-3e1v2bzS.js +1 -0
- package/dist/assets/plsql-ChMvpjG-.js +1 -0
- package/dist/assets/po-BTJTHyun.js +1 -0
- package/dist/assets/poimandres-CS3Unz2-.js +1 -0
- package/dist/assets/polar-C0HS_06l.js +1 -0
- package/dist/assets/postcss-CXtECtnM.js +1 -0
- package/dist/assets/powerquery-CEu0bR-o.js +1 -0
- package/dist/assets/powershell-CFHJl5sT.js +1 -0
- package/dist/assets/powershell-Dpen1YoG.js +1 -0
- package/dist/assets/prisma-Dd19v3D-.js +1 -0
- package/dist/assets/prolog-CbFg5uaA.js +1 -0
- package/dist/assets/properties-C78fOPTZ.js +1 -0
- package/dist/assets/proto-DyJlTyXw.js +1 -0
- package/dist/assets/protobuf-ChK-085T.js +1 -0
- package/dist/assets/pug-CGlum2m_.js +1 -0
- package/dist/assets/pug-DeIclll2.js +1 -0
- package/dist/assets/puppet-BMWR74SV.js +1 -0
- package/dist/assets/puppet-DMA9R1ak.js +1 -0
- package/dist/assets/purescript-CklMAg4u.js +1 -0
- package/dist/assets/python-B6aJPvgy.js +1 -0
- package/dist/assets/python-BuPzkPfP.js +1 -0
- package/dist/assets/q-pXgVlZs6.js +1 -0
- package/dist/assets/qml-3beO22l8.js +1 -0
- package/dist/assets/qmldir-C8lEn-DE.js +1 -0
- package/dist/assets/qss-IeuSbFQv.js +1 -0
- package/dist/assets/r-B6wPVr8A.js +1 -0
- package/dist/assets/r-DiinP2Uv.js +1 -0
- package/dist/assets/racket-BqYA7rlc.js +1 -0
- package/dist/assets/raku-DXvB9xmW.js +1 -0
- package/dist/assets/razor-CE9lU5zL.js +1 -0
- package/dist/assets/red-bN70gL4F.js +1 -0
- package/dist/assets/reg-C-SQnVFl.js +1 -0
- package/dist/assets/regexp-CDVJQ6XC.js +1 -0
- package/dist/assets/rel-C3B-1QV4.js +1 -0
- package/dist/assets/riscv-BM1_JUlF.js +1 -0
- package/dist/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
- package/dist/assets/rose-pine-moon-D4_iv3hh.js +1 -0
- package/dist/assets/rose-pine-qdsjHGoJ.js +1 -0
- package/dist/assets/rosmsg-BJDFO7_C.js +1 -0
- package/dist/assets/rpm-CTu-6PCP.js +1 -0
- package/dist/assets/rst-B0xPkSld.js +1 -0
- package/dist/assets/ruby-B2Rjki9n.js +1 -0
- package/dist/assets/ruby-BvKwtOVI.js +1 -0
- package/dist/assets/rust-B1yitclQ.js +1 -0
- package/dist/assets/sas-B4kiWyti.js +1 -0
- package/dist/assets/sas-cz2c8ADy.js +1 -0
- package/dist/assets/sass-Cj5Yp3dK.js +1 -0
- package/dist/assets/scala-C151Ov-r.js +1 -0
- package/dist/assets/scheme-C41bIUwD.js +1 -0
- package/dist/assets/scheme-C98Dy4si.js +1 -0
- package/dist/assets/scss-OYdSNvt2.js +1 -0
- package/dist/assets/sdbl-DVxCFoDh.js +1 -0
- package/dist/assets/shaderlab-Dg9Lc6iA.js +1 -0
- package/dist/assets/shell-CjFT_Tl9.js +1 -0
- package/dist/assets/shellscript-Yzrsuije.js +1 -0
- package/dist/assets/shellsession-BADoaaVG.js +1 -0
- package/dist/assets/sieve-C3Gn_uJK.js +1 -0
- package/dist/assets/simple-mode-GW_nhZxv.js +1 -0
- package/dist/assets/slack-dark-BthQWCQV.js +1 -0
- package/dist/assets/slack-ochin-DqwNpetd.js +1 -0
- package/dist/assets/smalltalk-BERRCDM3.js +1 -0
- package/dist/assets/smalltalk-CnHTOXQT.js +1 -0
- package/dist/assets/snazzy-light-Bw305WKR.js +1 -0
- package/dist/assets/solarized-dark-DXbdFlpD.js +1 -0
- package/dist/assets/solarized-light-L9t79GZl.js +1 -0
- package/dist/assets/solidity-rGO070M0.js +1 -0
- package/dist/assets/solr-DehyRSwq.js +1 -0
- package/dist/assets/soy-Brmx7dQM.js +1 -0
- package/dist/assets/sparql-DkYu6x3z.js +1 -0
- package/dist/assets/sparql-rVzFXLq3.js +1 -0
- package/dist/assets/splunk-BtCnVYZw.js +1 -0
- package/dist/assets/spreadsheet-BCZA_wO0.js +1 -0
- package/dist/assets/sql-BLtJtn59.js +1 -0
- package/dist/assets/sql-D0XecflT.js +1 -0
- package/dist/assets/ssh-config-_ykCGR6B.js +1 -0
- package/dist/assets/stata-BH5u7GGu.js +1 -0
- package/dist/assets/stex-C3f8Ysf7.js +1 -0
- package/dist/assets/stylus-B533Al4x.js +1 -0
- package/dist/assets/stylus-BEDo0Tqx.js +1 -0
- package/dist/assets/svelte-3Dk4HxPD.js +1 -0
- package/dist/assets/swift-BzpIVaGY.js +1 -0
- package/dist/assets/swift-Dg5xB15N.js +1 -0
- package/dist/assets/synthwave-84-CbfX1IO0.js +1 -0
- package/dist/assets/system-verilog-CnnmHF94.js +1 -0
- package/dist/assets/systemd-4A_iFExJ.js +1 -0
- package/dist/assets/talonscript-CkByrt1z.js +1 -0
- package/dist/assets/tasl-QIJgUcNo.js +1 -0
- package/dist/assets/tcl-DVfN8rqt.js +1 -0
- package/dist/assets/tcl-dwOrl1Do.js +1 -0
- package/dist/assets/templ-W15q3VgB.js +1 -0
- package/dist/assets/terraform-BETggiCN.js +1 -0
- package/dist/assets/tex-CxkMU7Pf.js +1 -0
- package/dist/assets/textile-CnDTJFAw.js +1 -0
- package/dist/assets/tiddlywiki-DO-Gjzrf.js +1 -0
- package/dist/assets/tiki-DGYXhP31.js +1 -0
- package/dist/assets/tokyo-night-hegEt444.js +1 -0
- package/dist/assets/toml-Bm5Em-hy.js +1 -0
- package/dist/assets/toml-vGWfd6FD.js +1 -0
- package/dist/assets/troff-wAsdV37c.js +1 -0
- package/dist/assets/ts-tags-zn1MmPIZ.js +1 -0
- package/dist/assets/tsv-B_m7g4N7.js +1 -0
- package/dist/assets/tsx-COt5Ahok.js +1 -0
- package/dist/assets/ttcn-CfJYG6tj.js +1 -0
- package/dist/assets/ttcn-cfg-B9xdYoR4.js +1 -0
- package/dist/assets/turtle-B1tBg_DP.js +1 -0
- package/dist/assets/turtle-BsS91CYL.js +1 -0
- package/dist/assets/twig-CO9l9SDP.js +1 -0
- package/dist/assets/typescript-BPQ3VLAy.js +1 -0
- package/dist/assets/typespec-BGHnOYBU.js +1 -0
- package/dist/assets/typst-DHCkPAjA.js +1 -0
- package/dist/assets/v-BcVCzyr7.js +1 -0
- package/dist/assets/vala-CsfeWuGM.js +1 -0
- package/dist/assets/vb-CmGdzxic.js +1 -0
- package/dist/assets/vb-D17OF-Vu.js +1 -0
- package/dist/assets/vbscript-BuJXcnF6.js +1 -0
- package/dist/assets/velocity-D8B20fx6.js +1 -0
- package/dist/assets/verilog-BQ8w6xss.js +1 -0
- package/dist/assets/verilog-C6RDOZhf.js +1 -0
- package/dist/assets/vesper-DU1UobuO.js +1 -0
- package/dist/assets/vhdl-CeAyd5Ju.js +1 -0
- package/dist/assets/vhdl-lSbBsy5d.js +1 -0
- package/dist/assets/viml-CJc9bBzg.js +1 -0
- package/dist/assets/vitesse-black-Bkuqu6BP.js +1 -0
- package/dist/assets/vitesse-dark-D0r3Knsf.js +1 -0
- package/dist/assets/vitesse-light-CVO1_9PV.js +1 -0
- package/dist/assets/vue-DnHKYNfI.js +1 -0
- package/dist/assets/vue-html-CChd_i61.js +1 -0
- package/dist/assets/vue-vine-8moa0y9V.js +1 -0
- package/dist/assets/vyper-CDx5xZoG.js +1 -0
- package/dist/assets/wasm-CG6Dc4jp.js +1 -0
- package/dist/assets/wasm-MzD3tlZU.js +1 -0
- package/dist/assets/webidl-ZXfAyPTL.js +1 -0
- package/dist/assets/wenyan-BV7otONQ.js +1 -0
- package/dist/assets/wgsl-Dx-B1_4e.js +1 -0
- package/dist/assets/wikitext-BhOHFoWU.js +1 -0
- package/dist/assets/wit-5i3qLPDT.js +1 -0
- package/dist/assets/wolfram-lXgVvXCa.js +1 -0
- package/dist/assets/xml-sdJ4AIDG.js +1 -0
- package/dist/assets/xquery-DzFWVndE.js +1 -0
- package/dist/assets/xsl-CtQFsRM5.js +1 -0
- package/dist/assets/yacas-BJ4BC0dw.js +1 -0
- package/dist/assets/yaml-Buea-lGh.js +1 -0
- package/dist/assets/z80-Hz9HOZM7.js +1 -0
- package/dist/assets/zenscript-DVFEvuxE.js +1 -0
- package/dist/assets/zig-VOosw3JB.js +1 -0
- package/dist/index.html +47 -0
- package/dist/logo.svg +8 -0
- package/dist/openspec_pixel_dark.svg +89 -0
- package/dist/openspec_pixel_light.svg +89 -0
- package/index.html +46 -0
- package/package.json +66 -0
- package/src/App.tsx +124 -0
- package/src/components/StaticModeBanner.tsx +46 -0
- package/src/components/change-overview.tsx +156 -0
- package/src/components/cli-terminal.tsx +93 -0
- package/src/components/code-editor.tsx +232 -0
- package/src/components/copyable-path.tsx +44 -0
- package/src/components/dialog.tsx +193 -0
- package/src/components/folder-editor-viewer.tsx +411 -0
- package/src/components/global-archive-modal.tsx +205 -0
- package/src/components/layout/desktop-sidebar.tsx +48 -0
- package/src/components/layout/index.ts +6 -0
- package/src/components/layout/mobile-header.tsx +91 -0
- package/src/components/layout/mobile-tabbar.tsx +20 -0
- package/src/components/layout/nav-items.ts +33 -0
- package/src/components/layout/root-layout.tsx +67 -0
- package/src/components/layout/status-bar.tsx +69 -0
- package/src/components/markdown-content.tsx +104 -0
- package/src/components/markdown-viewer.tsx +415 -0
- package/src/components/path-marquee.tsx +104 -0
- package/src/components/tabs.tsx +151 -0
- package/src/components/tasks-view.test.tsx +45 -0
- package/src/components/tasks-view.tsx +209 -0
- package/src/components/toc-context.tsx +177 -0
- package/src/components/toc.tsx +290 -0
- package/src/entry-client.tsx +47 -0
- package/src/index.css +481 -0
- package/src/lib/api-config.ts +72 -0
- package/src/lib/archive-modal-context.tsx +45 -0
- package/src/lib/codemirror-markdown-preview.ts +494 -0
- package/src/lib/format-time.ts +51 -0
- package/src/lib/shiki-highlighter.ts +47 -0
- package/src/lib/static-data-provider.ts +386 -0
- package/src/lib/static-mode.ts +158 -0
- package/src/lib/trpc.ts +107 -0
- package/src/lib/use-cli-runner.tsx +433 -0
- package/src/lib/use-dark-mode.ts +28 -0
- package/src/lib/use-server-status.ts +208 -0
- package/src/lib/use-subscription.ts +375 -0
- package/src/lib/use-tabs-status-by-query.ts +78 -0
- package/src/main.tsx +9 -0
- package/src/routes/archive-list.tsx +65 -0
- package/src/routes/archive-view.tsx +116 -0
- package/src/routes/change-list.tsx +63 -0
- package/src/routes/change-view.tsx +188 -0
- package/src/routes/dashboard.tsx +204 -0
- package/src/routes/project.tsx +272 -0
- package/src/routes/settings.tsx +816 -0
- package/src/routes/spec-list.tsx +49 -0
- package/src/routes/spec-view.tsx +164 -0
- package/src/ssg/entry-server.tsx +111 -0
- package/src/ssg/index.ts +6 -0
- package/src/ssg/prerender.ts +111 -0
- package/src/ssg/static-data-context.tsx +49 -0
- package/src/ssg/types.ts +5 -0
- package/src/test/setup.ts +1 -0
- package/src/vite-env.d.ts +6 -0
- package/tsconfig.json +18 -0
- package/vite.config.ts +58 -0
|
@@ -0,0 +1,816 @@
|
|
|
1
|
+
import { CliTerminal } from '@/components/cli-terminal'
|
|
2
|
+
import { CopyablePath } from '@/components/copyable-path'
|
|
3
|
+
import { Dialog } from '@/components/dialog'
|
|
4
|
+
import { getApiBaseUrl } from '@/lib/api-config'
|
|
5
|
+
import { isStaticMode } from '@/lib/static-mode'
|
|
6
|
+
import { trpc, trpcClient } from '@/lib/trpc'
|
|
7
|
+
import { useCliRunner } from '@/lib/use-cli-runner'
|
|
8
|
+
import { useServerStatus } from '@/lib/use-server-status'
|
|
9
|
+
import { useConfigSubscription, useConfiguredToolsSubscription } from '@/lib/use-subscription'
|
|
10
|
+
import { useMutation, useQuery } from '@tanstack/react-query'
|
|
11
|
+
import {
|
|
12
|
+
ArrowUp,
|
|
13
|
+
Check,
|
|
14
|
+
CheckCircle,
|
|
15
|
+
Download,
|
|
16
|
+
FolderOpen,
|
|
17
|
+
FolderPlus,
|
|
18
|
+
Loader2,
|
|
19
|
+
Monitor,
|
|
20
|
+
Moon,
|
|
21
|
+
Settings as SettingsIcon,
|
|
22
|
+
Sun,
|
|
23
|
+
Terminal,
|
|
24
|
+
Wifi,
|
|
25
|
+
WifiOff,
|
|
26
|
+
XCircle,
|
|
27
|
+
} from 'lucide-react'
|
|
28
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
29
|
+
|
|
30
|
+
type Theme = 'light' | 'dark' | 'system'
|
|
31
|
+
|
|
32
|
+
function getStoredTheme(): Theme {
|
|
33
|
+
const stored = localStorage.getItem('theme')
|
|
34
|
+
if (stored === 'light' || stored === 'dark' || stored === 'system') {
|
|
35
|
+
return stored
|
|
36
|
+
}
|
|
37
|
+
return 'system'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function applyTheme(theme: Theme) {
|
|
41
|
+
const root = document.documentElement
|
|
42
|
+
if (theme === 'system') {
|
|
43
|
+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
44
|
+
root.classList.toggle('dark', prefersDark)
|
|
45
|
+
} else {
|
|
46
|
+
root.classList.toggle('dark', theme === 'dark')
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function Settings() {
|
|
51
|
+
const [theme, setTheme] = useState<Theme>(getStoredTheme)
|
|
52
|
+
const [apiUrl, setApiUrl] = useState(getApiBaseUrl() || '')
|
|
53
|
+
const [cliCommand, setCliCommand] = useState('')
|
|
54
|
+
const [selectedTools, setSelectedTools] = useState<string[]>([])
|
|
55
|
+
const [showInitModal, setShowInitModal] = useState(false)
|
|
56
|
+
const [showInstallModal, setShowInstallModal] = useState(false)
|
|
57
|
+
const [initTools, setInitTools] = useState<string[] | 'all' | 'none'>('none')
|
|
58
|
+
|
|
59
|
+
const initRunner = useCliRunner()
|
|
60
|
+
const installRunner = useCliRunner()
|
|
61
|
+
|
|
62
|
+
const {
|
|
63
|
+
lines: initLines,
|
|
64
|
+
status: initStatus,
|
|
65
|
+
commands: initCommands,
|
|
66
|
+
cancel: cancelInit,
|
|
67
|
+
reset: resetInit,
|
|
68
|
+
} = initRunner
|
|
69
|
+
|
|
70
|
+
const initBorderVariant =
|
|
71
|
+
initStatus === 'error' ? 'error' : initStatus === 'success' ? 'success' : 'default'
|
|
72
|
+
|
|
73
|
+
const {
|
|
74
|
+
lines: installLines,
|
|
75
|
+
status: installStatus,
|
|
76
|
+
commands: installCommands,
|
|
77
|
+
cancel: cancelInstall,
|
|
78
|
+
reset: resetInstall,
|
|
79
|
+
} = installRunner
|
|
80
|
+
|
|
81
|
+
const installBorderVariant =
|
|
82
|
+
installStatus === 'error' ? 'error' : installStatus === 'success' ? 'success' : 'default'
|
|
83
|
+
|
|
84
|
+
// 服务器状态(包含项目路径)
|
|
85
|
+
const serverStatus = useServerStatus()
|
|
86
|
+
|
|
87
|
+
// In static mode, only show appearance settings
|
|
88
|
+
const inStaticMode = isStaticMode()
|
|
89
|
+
|
|
90
|
+
// 订阅配置
|
|
91
|
+
const { data: config } = useConfigSubscription()
|
|
92
|
+
|
|
93
|
+
// 嗅探全局 CLI(每次进入 settings 页面都会重新嗅探)
|
|
94
|
+
// Skip in static mode
|
|
95
|
+
const {
|
|
96
|
+
data: cliSniffResult,
|
|
97
|
+
isLoading: isSniffingCli,
|
|
98
|
+
refetch: resniffCli,
|
|
99
|
+
} = useQuery({
|
|
100
|
+
...trpc.cli.sniffGlobalCli.queryOptions(),
|
|
101
|
+
// 每次进入页面都重新嗅探
|
|
102
|
+
staleTime: 0,
|
|
103
|
+
gcTime: 0,
|
|
104
|
+
enabled: !inStaticMode,
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
// CLI 可用性检查(基于配置或嗅探结果)
|
|
108
|
+
// Skip in static mode
|
|
109
|
+
const {
|
|
110
|
+
data: cliAvailability,
|
|
111
|
+
isLoading: isCheckingCli,
|
|
112
|
+
refetch: recheckCli,
|
|
113
|
+
} = useQuery({
|
|
114
|
+
...trpc.cli.checkAvailability.queryOptions(),
|
|
115
|
+
enabled: !inStaticMode,
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
// 获取所有工具列表(包括 available: false 的)
|
|
119
|
+
// Skip in static mode
|
|
120
|
+
const { data: allTools } = useQuery({
|
|
121
|
+
...trpc.cli.getAllTools.queryOptions(),
|
|
122
|
+
enabled: !inStaticMode,
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
// 分组:available: true 的工具和 available: false 的工具
|
|
126
|
+
const nativeTools = useMemo(() => allTools?.filter((t) => t.available) ?? [], [allTools])
|
|
127
|
+
const otherTools = useMemo(() => allTools?.filter((t) => !t.available) ?? [], [allTools])
|
|
128
|
+
const cliSupportedToolIds = useMemo(() => nativeTools.map((t) => t.value), [nativeTools])
|
|
129
|
+
const cliSupportedTools = useMemo(() => new Set(cliSupportedToolIds), [cliSupportedToolIds])
|
|
130
|
+
const selectableToolIds = cliSupportedToolIds
|
|
131
|
+
const cliSupportedToolsKey = useMemo(() => cliSupportedToolIds.join('|'), [cliSupportedToolIds])
|
|
132
|
+
const lastSyncedToolsKeyRef = useRef<string>('')
|
|
133
|
+
|
|
134
|
+
// 订阅已配置的工具列表(响应式)
|
|
135
|
+
const { data: configuredTools } = useConfiguredToolsSubscription()
|
|
136
|
+
|
|
137
|
+
// 同步配置到本地状态(只有用户配置了才显示)
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
// 只有当配置中有值时才同步到 input
|
|
140
|
+
if (config?.cli?.command) {
|
|
141
|
+
setCliCommand(config.cli.command)
|
|
142
|
+
} else {
|
|
143
|
+
// 用户没有配置时,清空 input
|
|
144
|
+
setCliCommand('')
|
|
145
|
+
}
|
|
146
|
+
}, [config?.cli?.command])
|
|
147
|
+
|
|
148
|
+
// 安装完成后重新嗅探
|
|
149
|
+
const handleInstallSuccess = useCallback(() => {
|
|
150
|
+
// 重新嗅探全局 CLI
|
|
151
|
+
resniffCli()
|
|
152
|
+
// 重新检查 CLI 可用性
|
|
153
|
+
recheckCli()
|
|
154
|
+
// 关闭安装模态框
|
|
155
|
+
setShowInstallModal(false)
|
|
156
|
+
}, [resniffCli, recheckCli])
|
|
157
|
+
|
|
158
|
+
// 计算显示的 placeholder
|
|
159
|
+
const cliPlaceholder = cliSniffResult?.hasGlobal
|
|
160
|
+
? `openspec (v${cliSniffResult.version || 'detected'})`
|
|
161
|
+
: 'npx @fission-ai/openspec'
|
|
162
|
+
|
|
163
|
+
// 同步已配置的工具到选中状态
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
if (!configuredTools || configuredTools.length === 0) return
|
|
166
|
+
const next = [...configuredTools.filter((t) => cliSupportedTools.has(t))].sort()
|
|
167
|
+
const signature = `${cliSupportedToolsKey}|${next.join(',')}`
|
|
168
|
+
if (signature === lastSyncedToolsKeyRef.current) return
|
|
169
|
+
lastSyncedToolsKeyRef.current = signature
|
|
170
|
+
setSelectedTools(next)
|
|
171
|
+
}, [configuredTools, cliSupportedToolsKey])
|
|
172
|
+
|
|
173
|
+
// 打开 init modal
|
|
174
|
+
const startInit = (tools: string[] | 'all' | 'none') => {
|
|
175
|
+
setInitTools(tools)
|
|
176
|
+
setShowInitModal(true)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Modal lifecycle: auto start streams when打开
|
|
180
|
+
// Keep state clean when关闭
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
if (!showInitModal) {
|
|
183
|
+
cancelInit()
|
|
184
|
+
resetInit()
|
|
185
|
+
}
|
|
186
|
+
}, [showInitModal, cancelInit, resetInit])
|
|
187
|
+
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
if (!showInitModal) return
|
|
190
|
+
const tools = initTools
|
|
191
|
+
const toolsArg = Array.isArray(tools) ? tools.join(',') : tools
|
|
192
|
+
initCommands.replaceAll([{ command: 'openspec', args: ['init', '--tools', toolsArg] }])
|
|
193
|
+
}, [initCommands, initTools, showInitModal])
|
|
194
|
+
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
if (showInstallModal) {
|
|
197
|
+
installCommands.replaceAll([
|
|
198
|
+
{ command: 'npm', args: ['install', '-g', '@fission-ai/openspec'] },
|
|
199
|
+
])
|
|
200
|
+
installCommands.runAll()
|
|
201
|
+
} else {
|
|
202
|
+
cancelInstall()
|
|
203
|
+
resetInstall()
|
|
204
|
+
}
|
|
205
|
+
}, [showInstallModal, installCommands, cancelInstall, resetInstall])
|
|
206
|
+
|
|
207
|
+
// 判断工具是否已配置(不可取消)
|
|
208
|
+
const isToolConfigured = (tool: string) => configuredTools?.includes(tool) ?? false
|
|
209
|
+
|
|
210
|
+
// 切换工具选择(已配置的工具不能取消)
|
|
211
|
+
const toggleTool = (tool: string) => {
|
|
212
|
+
if (!cliSupportedTools.has(tool)) return
|
|
213
|
+
if (isToolConfigured(tool)) return // 已配置的工具不能取消
|
|
214
|
+
setSelectedTools((prev) =>
|
|
215
|
+
prev.includes(tool) ? prev.filter((t) => t !== tool) : [...prev, tool]
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// 全选/取消全选(保留已配置的工具)
|
|
220
|
+
const toggleAllTools = () => {
|
|
221
|
+
if (!allTools) return
|
|
222
|
+
const selectableToolIds = allTools.filter((t) => t.available).map((t) => t.value)
|
|
223
|
+
const unconfiguredTools = selectableToolIds.filter((t) => !isToolConfigured(t))
|
|
224
|
+
const allUnconfiguredSelected = unconfiguredTools.every((t) => selectedTools.includes(t))
|
|
225
|
+
|
|
226
|
+
if (allUnconfiguredSelected) {
|
|
227
|
+
// 取消所有未配置的工具,保留已配置的
|
|
228
|
+
setSelectedTools(configuredTools ?? [])
|
|
229
|
+
} else {
|
|
230
|
+
// 全选所有可用工具
|
|
231
|
+
setSelectedTools([...selectableToolIds])
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// 计算新工具数量(未配置但已选中的)
|
|
236
|
+
const newToolsCount = selectedTools.filter(
|
|
237
|
+
(t) => cliSupportedTools.has(t) && !isToolConfigured(t)
|
|
238
|
+
).length
|
|
239
|
+
|
|
240
|
+
const handleCloseInit = () => {
|
|
241
|
+
setShowInitModal(false)
|
|
242
|
+
cancelInit()
|
|
243
|
+
resetInit()
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const handleCloseInstall = () => {
|
|
247
|
+
setShowInstallModal(false)
|
|
248
|
+
cancelInstall()
|
|
249
|
+
resetInstall()
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// 保存 CLI 命令配置
|
|
253
|
+
const saveCliCommandMutation = useMutation({
|
|
254
|
+
mutationFn: (command: string) => trpcClient.config.setCliCommand.mutate({ command }),
|
|
255
|
+
onSuccess: () => {
|
|
256
|
+
recheckCli()
|
|
257
|
+
},
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
useEffect(() => {
|
|
261
|
+
applyTheme(theme)
|
|
262
|
+
localStorage.setItem('theme', theme)
|
|
263
|
+
}, [theme])
|
|
264
|
+
|
|
265
|
+
// Listen for system theme changes
|
|
266
|
+
useEffect(() => {
|
|
267
|
+
if (theme !== 'system') return
|
|
268
|
+
|
|
269
|
+
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
|
270
|
+
const handler = () => applyTheme('system')
|
|
271
|
+
mediaQuery.addEventListener('change', handler)
|
|
272
|
+
return () => mediaQuery.removeEventListener('change', handler)
|
|
273
|
+
}, [theme])
|
|
274
|
+
|
|
275
|
+
const handleApiUrlChange = () => {
|
|
276
|
+
const currentUrl = new URL(window.location.href)
|
|
277
|
+
if (apiUrl) {
|
|
278
|
+
currentUrl.searchParams.set('api', apiUrl)
|
|
279
|
+
} else {
|
|
280
|
+
currentUrl.searchParams.delete('api')
|
|
281
|
+
}
|
|
282
|
+
window.location.href = currentUrl.toString()
|
|
283
|
+
}
|
|
284
|
+
const [loading, setLoading] = useState(true)
|
|
285
|
+
useEffect(() => {
|
|
286
|
+
setLoading(false)
|
|
287
|
+
}, [])
|
|
288
|
+
if (loading) {
|
|
289
|
+
return <div className="route-loading animate-pulse">Loading settings...</div>
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return (
|
|
293
|
+
<div className="max-w-2xl space-y-8">
|
|
294
|
+
<h1 className="font-nav flex items-center gap-2 text-2xl font-bold">
|
|
295
|
+
<SettingsIcon className="h-6 w-6 shrink-0" />
|
|
296
|
+
Settings
|
|
297
|
+
</h1>
|
|
298
|
+
|
|
299
|
+
{/* Theme */}
|
|
300
|
+
<section className="space-y-4">
|
|
301
|
+
<h2 className="text-lg font-semibold">Appearance</h2>
|
|
302
|
+
<div className="border-border rounded-lg border p-4">
|
|
303
|
+
<label className="mb-3 block text-sm font-medium">Theme</label>
|
|
304
|
+
<div className="flex gap-2">
|
|
305
|
+
<button
|
|
306
|
+
onClick={() => setTheme('light')}
|
|
307
|
+
className={`flex items-center gap-2 rounded-md border px-4 py-2 transition-colors ${
|
|
308
|
+
theme === 'light'
|
|
309
|
+
? 'border-primary bg-primary text-primary-foreground'
|
|
310
|
+
: 'border-border hover:bg-muted'
|
|
311
|
+
}`}
|
|
312
|
+
>
|
|
313
|
+
<Sun className="h-4 w-4" />
|
|
314
|
+
Light
|
|
315
|
+
</button>
|
|
316
|
+
<button
|
|
317
|
+
onClick={() => setTheme('dark')}
|
|
318
|
+
className={`flex items-center gap-2 rounded-md border px-4 py-2 transition-colors ${
|
|
319
|
+
theme === 'dark'
|
|
320
|
+
? 'border-primary bg-primary text-primary-foreground'
|
|
321
|
+
: 'border-border hover:bg-muted'
|
|
322
|
+
}`}
|
|
323
|
+
>
|
|
324
|
+
<Moon className="h-4 w-4" />
|
|
325
|
+
Dark
|
|
326
|
+
</button>
|
|
327
|
+
<button
|
|
328
|
+
onClick={() => setTheme('system')}
|
|
329
|
+
className={`flex items-center gap-2 rounded-md border px-4 py-2 transition-colors ${
|
|
330
|
+
theme === 'system'
|
|
331
|
+
? 'border-primary bg-primary text-primary-foreground'
|
|
332
|
+
: 'border-border hover:bg-muted'
|
|
333
|
+
}`}
|
|
334
|
+
>
|
|
335
|
+
<Monitor className="h-4 w-4" />
|
|
336
|
+
System
|
|
337
|
+
</button>
|
|
338
|
+
</div>
|
|
339
|
+
</div>
|
|
340
|
+
</section>
|
|
341
|
+
|
|
342
|
+
{/* Only show other sections in dynamic mode */}
|
|
343
|
+
{!inStaticMode && (
|
|
344
|
+
<>
|
|
345
|
+
{/* Project Directory */}
|
|
346
|
+
<section className="space-y-4">
|
|
347
|
+
<h2 className="text-lg font-semibold">Project Directory</h2>
|
|
348
|
+
<div className="border-border rounded-lg border p-4">
|
|
349
|
+
<div className="flex items-start gap-2">
|
|
350
|
+
<FolderOpen className="text-muted-foreground mt-1 h-4 w-4 shrink-0" />
|
|
351
|
+
{serverStatus.projectDir ? (
|
|
352
|
+
<CopyablePath path={serverStatus.projectDir} className="flex-1" />
|
|
353
|
+
) : (
|
|
354
|
+
<span className="text-muted-foreground text-sm">Loading...</span>
|
|
355
|
+
)}
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|
|
358
|
+
</section>
|
|
359
|
+
|
|
360
|
+
{/* CLI Configuration */}
|
|
361
|
+
<section className="space-y-4">
|
|
362
|
+
<h2 className="text-lg font-semibold">CLI Configuration</h2>
|
|
363
|
+
<div className="border-border space-y-4 rounded-lg border p-4">
|
|
364
|
+
{/* Global CLI Detection */}
|
|
365
|
+
<div>
|
|
366
|
+
<label className="mb-2 block text-sm font-medium">Global CLI Detection</label>
|
|
367
|
+
<div className="mb-2 flex items-center gap-2">
|
|
368
|
+
{isSniffingCli ? (
|
|
369
|
+
<span className="text-muted-foreground flex items-center gap-2 text-sm">
|
|
370
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
371
|
+
Detecting global openspec command...
|
|
372
|
+
</span>
|
|
373
|
+
) : cliSniffResult?.hasGlobal ? (
|
|
374
|
+
<div className="flex flex-col gap-1">
|
|
375
|
+
<span className="flex items-center gap-2 text-sm text-green-600">
|
|
376
|
+
<CheckCircle className="h-4 w-4" />
|
|
377
|
+
Global CLI installed:{' '}
|
|
378
|
+
<code className="bg-muted rounded px-1">
|
|
379
|
+
openspec {cliSniffResult.version}
|
|
380
|
+
</code>
|
|
381
|
+
</span>
|
|
382
|
+
{cliSniffResult.hasUpdate && cliSniffResult.latestVersion && (
|
|
383
|
+
<span className="flex items-center gap-2 text-sm text-amber-600">
|
|
384
|
+
<ArrowUp className="h-4 w-4" />
|
|
385
|
+
Update available:{' '}
|
|
386
|
+
<code className="bg-muted rounded px-1">
|
|
387
|
+
v{cliSniffResult.latestVersion}
|
|
388
|
+
</code>
|
|
389
|
+
</span>
|
|
390
|
+
)}
|
|
391
|
+
</div>
|
|
392
|
+
) : (
|
|
393
|
+
<div className="flex flex-col gap-1">
|
|
394
|
+
<span className="flex items-center gap-2 text-sm text-yellow-600">
|
|
395
|
+
<XCircle className="h-4 w-4" />
|
|
396
|
+
Global CLI not found
|
|
397
|
+
</span>
|
|
398
|
+
{cliSniffResult?.latestVersion && (
|
|
399
|
+
<span className="text-muted-foreground text-xs">
|
|
400
|
+
Latest version:{' '}
|
|
401
|
+
<code className="bg-muted rounded px-1">
|
|
402
|
+
v{cliSniffResult.latestVersion}
|
|
403
|
+
</code>
|
|
404
|
+
</span>
|
|
405
|
+
)}
|
|
406
|
+
</div>
|
|
407
|
+
)}
|
|
408
|
+
</div>
|
|
409
|
+
{/* 显示安装/更新按钮:当没有全局 CLI 或有更新可用时 */}
|
|
410
|
+
{!isSniffingCli && (!cliSniffResult?.hasGlobal || cliSniffResult?.hasUpdate) && (
|
|
411
|
+
<div className="flex items-center gap-2">
|
|
412
|
+
<button
|
|
413
|
+
onClick={() => setShowInstallModal(true)}
|
|
414
|
+
className="bg-primary text-primary-foreground flex items-center gap-2 rounded-md px-3 py-1.5 text-sm hover:opacity-90"
|
|
415
|
+
>
|
|
416
|
+
{cliSniffResult?.hasUpdate ? (
|
|
417
|
+
<>
|
|
418
|
+
<ArrowUp className="h-4 w-4" />
|
|
419
|
+
Update to v{cliSniffResult.latestVersion}
|
|
420
|
+
</>
|
|
421
|
+
) : (
|
|
422
|
+
<>
|
|
423
|
+
<Download className="h-4 w-4" />
|
|
424
|
+
Install Globally
|
|
425
|
+
</>
|
|
426
|
+
)}
|
|
427
|
+
</button>
|
|
428
|
+
<span className="text-muted-foreground text-xs">
|
|
429
|
+
Run:{' '}
|
|
430
|
+
<code className="bg-muted rounded px-1">
|
|
431
|
+
npm install -g @fission-ai/openspec
|
|
432
|
+
</code>
|
|
433
|
+
</span>
|
|
434
|
+
</div>
|
|
435
|
+
)}
|
|
436
|
+
{cliSniffResult?.error && (
|
|
437
|
+
<p className="mt-1 text-sm text-red-500">
|
|
438
|
+
Detection error: {cliSniffResult.error}
|
|
439
|
+
</p>
|
|
440
|
+
)}
|
|
441
|
+
</div>
|
|
442
|
+
|
|
443
|
+
{/* CLI Command Override */}
|
|
444
|
+
<div className="border-border border-t pt-3">
|
|
445
|
+
<label className="mb-2 block text-sm font-medium">
|
|
446
|
+
Custom CLI Command (Optional)
|
|
447
|
+
</label>
|
|
448
|
+
<p className="text-muted-foreground mb-3 text-sm">
|
|
449
|
+
Override the auto-detected CLI command. Leave empty to use the detected default.
|
|
450
|
+
</p>
|
|
451
|
+
<div className="flex gap-2">
|
|
452
|
+
<input
|
|
453
|
+
type="text"
|
|
454
|
+
value={cliCommand}
|
|
455
|
+
onChange={(e) => setCliCommand(e.target.value)}
|
|
456
|
+
placeholder={cliPlaceholder}
|
|
457
|
+
className="border-border bg-background text-foreground flex-1 rounded-md border px-3 py-2 font-mono text-sm"
|
|
458
|
+
/>
|
|
459
|
+
<button
|
|
460
|
+
onClick={() => saveCliCommandMutation.mutate(cliCommand)}
|
|
461
|
+
disabled={
|
|
462
|
+
saveCliCommandMutation.isPending ||
|
|
463
|
+
cliCommand === (config?.cli?.command ?? '')
|
|
464
|
+
}
|
|
465
|
+
className="bg-primary text-primary-foreground rounded-md px-4 py-2 hover:opacity-90 disabled:opacity-50"
|
|
466
|
+
>
|
|
467
|
+
{saveCliCommandMutation.isPending ? 'Saving...' : 'Save'}
|
|
468
|
+
</button>
|
|
469
|
+
</div>
|
|
470
|
+
{config?.cli?.command && (
|
|
471
|
+
<p className="text-muted-foreground mt-2 text-xs">
|
|
472
|
+
Currently using custom command:{' '}
|
|
473
|
+
<code className="bg-muted rounded px-1">{config.cli.command}</code>
|
|
474
|
+
</p>
|
|
475
|
+
)}
|
|
476
|
+
</div>
|
|
477
|
+
|
|
478
|
+
{/* CLI Status */}
|
|
479
|
+
<div className="border-border border-t pt-3">
|
|
480
|
+
<div className="flex items-center gap-2">
|
|
481
|
+
<Terminal className="text-muted-foreground h-4 w-4" />
|
|
482
|
+
<span className="text-sm font-medium">CLI Status:</span>
|
|
483
|
+
{isCheckingCli ? (
|
|
484
|
+
<span className="text-muted-foreground flex items-center gap-1 text-sm">
|
|
485
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
486
|
+
Checking...
|
|
487
|
+
</span>
|
|
488
|
+
) : cliAvailability?.available ? (
|
|
489
|
+
<span className="flex items-center gap-1 text-sm text-green-600">
|
|
490
|
+
<CheckCircle className="h-4 w-4" />
|
|
491
|
+
Available {cliAvailability.version && `(${cliAvailability.version})`}
|
|
492
|
+
</span>
|
|
493
|
+
) : (
|
|
494
|
+
<span className="flex items-center gap-1 text-sm text-red-600">
|
|
495
|
+
<XCircle className="h-4 w-4" />
|
|
496
|
+
Not available
|
|
497
|
+
</span>
|
|
498
|
+
)}
|
|
499
|
+
</div>
|
|
500
|
+
{cliAvailability && !cliAvailability.available && cliAvailability.error && (
|
|
501
|
+
<p className="text-muted-foreground ml-6 mt-1 text-sm">{cliAvailability.error}</p>
|
|
502
|
+
)}
|
|
503
|
+
</div>
|
|
504
|
+
</div>
|
|
505
|
+
</section>
|
|
506
|
+
|
|
507
|
+
{/* API Configuration */}
|
|
508
|
+
<section className="space-y-4">
|
|
509
|
+
<h2 className="text-lg font-semibold">API Configuration</h2>
|
|
510
|
+
<div className="border-border space-y-4 rounded-lg border p-4">
|
|
511
|
+
<div>
|
|
512
|
+
<label className="mb-2 block text-sm font-medium">API Server URL</label>
|
|
513
|
+
<p className="text-muted-foreground mb-3 text-sm">
|
|
514
|
+
Leave empty for same-origin requests. Set a custom URL to connect to a different
|
|
515
|
+
server.
|
|
516
|
+
</p>
|
|
517
|
+
<div className="flex gap-2">
|
|
518
|
+
<input
|
|
519
|
+
type="text"
|
|
520
|
+
value={apiUrl}
|
|
521
|
+
onChange={(e) => setApiUrl(e.target.value)}
|
|
522
|
+
placeholder={window.location.origin}
|
|
523
|
+
className="border-border bg-background text-foreground flex-1 rounded-md border px-3 py-2"
|
|
524
|
+
/>
|
|
525
|
+
<button
|
|
526
|
+
onClick={handleApiUrlChange}
|
|
527
|
+
className="bg-primary text-primary-foreground rounded-md px-4 py-2 hover:opacity-90"
|
|
528
|
+
>
|
|
529
|
+
Apply
|
|
530
|
+
</button>
|
|
531
|
+
</div>
|
|
532
|
+
{getApiBaseUrl() && (
|
|
533
|
+
<p className="text-muted-foreground mt-2 text-sm">
|
|
534
|
+
Current: <code className="bg-muted rounded px-1">{getApiBaseUrl()}</code>
|
|
535
|
+
</p>
|
|
536
|
+
)}
|
|
537
|
+
</div>
|
|
538
|
+
</div>
|
|
539
|
+
</section>
|
|
540
|
+
|
|
541
|
+
{/* File Watcher Info */}
|
|
542
|
+
<section className="space-y-4">
|
|
543
|
+
<h2 className="text-lg font-semibold">File Watcher</h2>
|
|
544
|
+
<div className="border-border rounded-lg border p-4">
|
|
545
|
+
<p className="text-muted-foreground mb-3 text-sm">
|
|
546
|
+
File watcher is configured on the server side. Check the status bar at the bottom of
|
|
547
|
+
the page to see if file watching is enabled.
|
|
548
|
+
</p>
|
|
549
|
+
<div className="flex items-center gap-2 text-sm">
|
|
550
|
+
<Wifi className="h-4 w-4 text-green-500" />
|
|
551
|
+
<span>Enabled: Real-time updates when files change</span>
|
|
552
|
+
</div>
|
|
553
|
+
<div className="mt-2 flex items-center gap-2 text-sm">
|
|
554
|
+
<WifiOff className="h-4 w-4 text-yellow-500" />
|
|
555
|
+
<span>Disabled: Manual refresh required</span>
|
|
556
|
+
</div>
|
|
557
|
+
<p className="text-muted-foreground mt-3 text-sm">
|
|
558
|
+
To disable file watching, restart the server with{' '}
|
|
559
|
+
<code className="bg-muted rounded px-1">--no-watch</code> flag.
|
|
560
|
+
</p>
|
|
561
|
+
</div>
|
|
562
|
+
</section>
|
|
563
|
+
|
|
564
|
+
{/* Initialize OpenSpec */}
|
|
565
|
+
<section className="space-y-4">
|
|
566
|
+
<h2 className="text-lg font-semibold">Initialize OpenSpec</h2>
|
|
567
|
+
<div className="border-border space-y-4 rounded-lg border p-4">
|
|
568
|
+
<p className="text-muted-foreground text-sm">
|
|
569
|
+
Create the OpenSpec directory structure in the current project. This will create{' '}
|
|
570
|
+
<code className="bg-muted rounded px-1">openspec/</code> with specs, changes, and
|
|
571
|
+
archive directories.
|
|
572
|
+
</p>
|
|
573
|
+
|
|
574
|
+
{/* Tool Selection */}
|
|
575
|
+
<div className="space-y-4">
|
|
576
|
+
<div className="flex items-center justify-between">
|
|
577
|
+
<label className="text-sm font-medium">AI Tools Configuration</label>
|
|
578
|
+
<button onClick={toggleAllTools} className="text-primary text-xs hover:underline">
|
|
579
|
+
{selectedTools.filter((t) => selectableToolIds.includes(t)).length ===
|
|
580
|
+
selectableToolIds.length
|
|
581
|
+
? 'Deselect All'
|
|
582
|
+
: 'Select All'}
|
|
583
|
+
</button>
|
|
584
|
+
</div>
|
|
585
|
+
<p className="text-muted-foreground text-sm">
|
|
586
|
+
Select which AI tools to configure. Already configured tools cannot be deselected.
|
|
587
|
+
</p>
|
|
588
|
+
|
|
589
|
+
{/* Natively supported providers */}
|
|
590
|
+
<div>
|
|
591
|
+
<p className="text-muted-foreground mb-2 text-xs font-medium">
|
|
592
|
+
Natively supported providers (✔ OpenSpec custom slash commands available)
|
|
593
|
+
</p>
|
|
594
|
+
<div className="grid grid-cols-2 gap-2 sm:grid-cols-3 md:grid-cols-4">
|
|
595
|
+
{nativeTools.map((tool) => {
|
|
596
|
+
const configured = isToolConfigured(tool.value)
|
|
597
|
+
const selected = selectedTools.includes(tool.value)
|
|
598
|
+
return (
|
|
599
|
+
<button
|
|
600
|
+
key={tool.value}
|
|
601
|
+
onClick={() => toggleTool(tool.value)}
|
|
602
|
+
disabled={configured}
|
|
603
|
+
title={configured ? 'Already configured' : tool.name}
|
|
604
|
+
className={`flex items-center gap-1.5 rounded border px-2.5 py-1.5 text-left text-xs transition-colors ${
|
|
605
|
+
configured
|
|
606
|
+
? 'cursor-not-allowed border-green-500/50 bg-green-500/10 text-green-600'
|
|
607
|
+
: selected
|
|
608
|
+
? 'border-primary bg-primary/10 text-primary'
|
|
609
|
+
: 'border-border hover:bg-muted'
|
|
610
|
+
}`}
|
|
611
|
+
>
|
|
612
|
+
{(configured || selected) && (
|
|
613
|
+
<Check
|
|
614
|
+
className={`h-3 w-3 shrink-0 ${configured ? 'text-green-600' : ''}`}
|
|
615
|
+
/>
|
|
616
|
+
)}
|
|
617
|
+
<span className="truncate">{tool.name}</span>
|
|
618
|
+
</button>
|
|
619
|
+
)
|
|
620
|
+
})}
|
|
621
|
+
</div>
|
|
622
|
+
</div>
|
|
623
|
+
|
|
624
|
+
{/* Other tools (Universal AGENTS.md) */}
|
|
625
|
+
{otherTools.length > 0 && (
|
|
626
|
+
<div>
|
|
627
|
+
<p className="text-muted-foreground mb-2 text-xs font-medium">
|
|
628
|
+
Other tools (use Universal AGENTS.md for Amp, VS Code, GitHub Copilot, …)
|
|
629
|
+
</p>
|
|
630
|
+
<div className="flex flex-wrap gap-2">
|
|
631
|
+
{otherTools.map((tool) => {
|
|
632
|
+
const configured = isToolConfigured(tool.value)
|
|
633
|
+
const selected = selectedTools.includes(tool.value)
|
|
634
|
+
// 对于 Universal AGENTS.md,显示为 "Universal AGENTS.md (always available)"
|
|
635
|
+
const displayName =
|
|
636
|
+
tool.value === 'agents' ? 'Universal AGENTS.md' : tool.name
|
|
637
|
+
const annotation = tool.value === 'agents' ? 'always available' : undefined
|
|
638
|
+
return (
|
|
639
|
+
<button
|
|
640
|
+
key={tool.value}
|
|
641
|
+
onClick={() => toggleTool(tool.value)}
|
|
642
|
+
disabled={configured || !tool.available}
|
|
643
|
+
title={
|
|
644
|
+
configured
|
|
645
|
+
? 'Already configured'
|
|
646
|
+
: !tool.available
|
|
647
|
+
? 'This helper is auto-detected via AGENTS.md (no init needed)'
|
|
648
|
+
: tool.name
|
|
649
|
+
}
|
|
650
|
+
className={`flex items-center gap-1.5 rounded border px-2.5 py-1.5 text-xs transition-colors ${
|
|
651
|
+
configured
|
|
652
|
+
? 'cursor-not-allowed border-green-500/50 bg-green-500/10 text-green-600'
|
|
653
|
+
: !tool.available
|
|
654
|
+
? 'border-border text-muted-foreground cursor-not-allowed border-dashed'
|
|
655
|
+
: selected
|
|
656
|
+
? 'border-primary bg-primary/10 text-primary'
|
|
657
|
+
: 'border-border hover:bg-muted'
|
|
658
|
+
}`}
|
|
659
|
+
>
|
|
660
|
+
{(configured || selected) && (
|
|
661
|
+
<Check
|
|
662
|
+
className={`h-3 w-3 shrink-0 ${configured ? 'text-green-600' : ''}`}
|
|
663
|
+
/>
|
|
664
|
+
)}
|
|
665
|
+
<span>{displayName}</span>
|
|
666
|
+
{annotation && (
|
|
667
|
+
<span className="text-muted-foreground">({annotation})</span>
|
|
668
|
+
)}
|
|
669
|
+
</button>
|
|
670
|
+
)
|
|
671
|
+
})}
|
|
672
|
+
</div>
|
|
673
|
+
</div>
|
|
674
|
+
)}
|
|
675
|
+
|
|
676
|
+
{configuredTools && configuredTools.length > 0 && (
|
|
677
|
+
<p className="text-muted-foreground text-xs">
|
|
678
|
+
<span className="inline-flex items-center gap-1">
|
|
679
|
+
<span className="h-2 w-2 rounded-full bg-green-500/50" />
|
|
680
|
+
{configuredTools.length} tool{configuredTools.length > 1 ? 's' : ''} already
|
|
681
|
+
configured
|
|
682
|
+
</span>
|
|
683
|
+
</p>
|
|
684
|
+
)}
|
|
685
|
+
</div>
|
|
686
|
+
|
|
687
|
+
{/* Init Buttons */}
|
|
688
|
+
<div className="border-border flex flex-wrap gap-2 border-t pt-2">
|
|
689
|
+
<button
|
|
690
|
+
onClick={() => {
|
|
691
|
+
const tools = selectedTools.filter((t) => cliSupportedTools.has(t))
|
|
692
|
+
startInit(tools.length > 0 ? tools : 'none')
|
|
693
|
+
}}
|
|
694
|
+
className="bg-primary text-primary-foreground flex items-center gap-2 rounded-md px-4 py-2 hover:opacity-90"
|
|
695
|
+
>
|
|
696
|
+
<FolderPlus className="h-4 w-4" />
|
|
697
|
+
{newToolsCount > 0
|
|
698
|
+
? `Add ${newToolsCount} new tool${newToolsCount > 1 ? 's' : ''}`
|
|
699
|
+
: selectedTools.length > 0
|
|
700
|
+
? 'Refresh configuration'
|
|
701
|
+
: 'Initialize (no tools)'}
|
|
702
|
+
</button>
|
|
703
|
+
<button
|
|
704
|
+
onClick={() => startInit('all')}
|
|
705
|
+
className="border-border hover:bg-muted rounded-md border px-4 py-2"
|
|
706
|
+
>
|
|
707
|
+
Initialize with All Tools
|
|
708
|
+
</button>
|
|
709
|
+
</div>
|
|
710
|
+
</div>
|
|
711
|
+
</section>
|
|
712
|
+
</>
|
|
713
|
+
)}
|
|
714
|
+
|
|
715
|
+
{/* Init Terminal Dialog - only in dynamic mode */}
|
|
716
|
+
{!inStaticMode && (
|
|
717
|
+
<>
|
|
718
|
+
<Dialog
|
|
719
|
+
open={showInitModal}
|
|
720
|
+
onClose={handleCloseInit}
|
|
721
|
+
bodyClassName="max-h-[70vh]"
|
|
722
|
+
borderVariant={initBorderVariant}
|
|
723
|
+
title={
|
|
724
|
+
<div className="flex items-center gap-2">
|
|
725
|
+
<Terminal className="h-4 w-4" />
|
|
726
|
+
<span className="font-semibold">Initialize OpenSpec</span>
|
|
727
|
+
</div>
|
|
728
|
+
}
|
|
729
|
+
footer={
|
|
730
|
+
<div className="flex items-center gap-2">
|
|
731
|
+
<button
|
|
732
|
+
onClick={handleCloseInit}
|
|
733
|
+
className="bg-muted hover:bg-muted/80 rounded-md px-4 py-2 disabled:cursor-not-allowed disabled:opacity-50"
|
|
734
|
+
disabled={initStatus === 'running'}
|
|
735
|
+
>
|
|
736
|
+
Close
|
|
737
|
+
</button>
|
|
738
|
+
{initStatus !== 'success' && (
|
|
739
|
+
<button
|
|
740
|
+
onClick={() => initCommands.runAll()}
|
|
741
|
+
className="bg-primary text-primary-foreground rounded-md px-4 py-2 disabled:cursor-not-allowed disabled:opacity-50"
|
|
742
|
+
disabled={initStatus === 'running'}
|
|
743
|
+
>
|
|
744
|
+
{initStatus === 'running' ? (
|
|
745
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
746
|
+
) : (
|
|
747
|
+
'Run init'
|
|
748
|
+
)}
|
|
749
|
+
</button>
|
|
750
|
+
)}
|
|
751
|
+
</div>
|
|
752
|
+
}
|
|
753
|
+
>
|
|
754
|
+
<CliTerminal lines={initLines} />
|
|
755
|
+
</Dialog>
|
|
756
|
+
|
|
757
|
+
{/* Install / Update CLI Dialog */}
|
|
758
|
+
<Dialog
|
|
759
|
+
open={showInstallModal}
|
|
760
|
+
onClose={handleCloseInstall}
|
|
761
|
+
bodyClassName="max-h-[70vh]"
|
|
762
|
+
borderVariant={installBorderVariant}
|
|
763
|
+
title={
|
|
764
|
+
<div className="flex items-center gap-2">
|
|
765
|
+
<Download className="h-4 w-4" />
|
|
766
|
+
<span className="font-semibold">
|
|
767
|
+
{cliSniffResult?.hasUpdate
|
|
768
|
+
? 'Update OpenSpec CLI'
|
|
769
|
+
: 'Install OpenSpec CLI Globally'}
|
|
770
|
+
</span>
|
|
771
|
+
</div>
|
|
772
|
+
}
|
|
773
|
+
footer={
|
|
774
|
+
<div className="flex items-center gap-2">
|
|
775
|
+
<button
|
|
776
|
+
onClick={handleCloseInstall}
|
|
777
|
+
className="bg-muted hover:bg-muted/80 rounded-md px-4 py-2 disabled:cursor-not-allowed disabled:opacity-50"
|
|
778
|
+
disabled={installStatus === 'running'}
|
|
779
|
+
>
|
|
780
|
+
Close
|
|
781
|
+
</button>
|
|
782
|
+
<button
|
|
783
|
+
onClick={() => {
|
|
784
|
+
handleCloseInstall()
|
|
785
|
+
handleInstallSuccess()
|
|
786
|
+
}}
|
|
787
|
+
className="bg-primary text-primary-foreground rounded-md px-4 py-2 disabled:cursor-not-allowed disabled:opacity-50"
|
|
788
|
+
disabled={installStatus !== 'success'}
|
|
789
|
+
>
|
|
790
|
+
Re-detect CLI
|
|
791
|
+
</button>
|
|
792
|
+
</div>
|
|
793
|
+
}
|
|
794
|
+
>
|
|
795
|
+
<CliTerminal lines={installLines} />
|
|
796
|
+
|
|
797
|
+
{installStatus === 'success' && (
|
|
798
|
+
<div className="border-border bg-muted/40 mt-3 rounded border px-3 py-2 text-sm">
|
|
799
|
+
<div className="flex items-center gap-2 text-green-600">
|
|
800
|
+
<CheckCircle className="h-4 w-4" />
|
|
801
|
+
{cliSniffResult?.hasUpdate
|
|
802
|
+
? `OpenSpec CLI updated to v${cliSniffResult?.latestVersion ?? ''}`
|
|
803
|
+
: 'OpenSpec CLI installed globally'}
|
|
804
|
+
</div>
|
|
805
|
+
<p className="text-muted-foreground text-xs">
|
|
806
|
+
You can now run the "openspec" command directly. Click "Re-detect CLI" to refresh
|
|
807
|
+
status.
|
|
808
|
+
</p>
|
|
809
|
+
</div>
|
|
810
|
+
)}
|
|
811
|
+
</Dialog>
|
|
812
|
+
</>
|
|
813
|
+
)}
|
|
814
|
+
</div>
|
|
815
|
+
)
|
|
816
|
+
}
|