@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.
Files changed (463) hide show
  1. package/dist/assets/abap-BdImnpbu.js +1 -0
  2. package/dist/assets/actionscript-3-CfeIJUat.js +1 -0
  3. package/dist/assets/ada-bCR0ucgS.js +1 -0
  4. package/dist/assets/andromeeda-C-Jbm3Hp.js +1 -0
  5. package/dist/assets/angular-html-CU67Zn6k.js +1 -0
  6. package/dist/assets/angular-ts-BwZT4LLn.js +1 -0
  7. package/dist/assets/apache-Pmp26Uib.js +1 -0
  8. package/dist/assets/apex-DDbsPZ6N.js +1 -0
  9. package/dist/assets/apl-B4CMkyY2.js +1 -0
  10. package/dist/assets/apl-dKokRX4l.js +1 -0
  11. package/dist/assets/applescript-Co6uUVPk.js +1 -0
  12. package/dist/assets/ara-BRHolxvo.js +1 -0
  13. package/dist/assets/asciiarmor-Df11BRmG.js +1 -0
  14. package/dist/assets/asciidoc-Dv7Oe6Be.js +1 -0
  15. package/dist/assets/asm-D_Q5rh1f.js +1 -0
  16. package/dist/assets/asn1-EdZsLKOL.js +1 -0
  17. package/dist/assets/asterisk-B-8jnY81.js +1 -0
  18. package/dist/assets/astro-CbQHKStN.js +1 -0
  19. package/dist/assets/aurora-x-D-2ljcwZ.js +1 -0
  20. package/dist/assets/awk-DMzUqQB5.js +1 -0
  21. package/dist/assets/ayu-dark-Cv9koXgw.js +1 -0
  22. package/dist/assets/ballerina-BFfxhgS-.js +1 -0
  23. package/dist/assets/bat-BkioyH1T.js +1 -0
  24. package/dist/assets/beancount-k_qm7-4y.js +1 -0
  25. package/dist/assets/berry-uYugtg8r.js +1 -0
  26. package/dist/assets/bibtex-CHM0blh-.js +1 -0
  27. package/dist/assets/bicep-Bmn6On1c.js +1 -0
  28. package/dist/assets/blade-DVc8C-J4.js +1 -0
  29. package/dist/assets/brainfuck-C4LP7Hcl.js +1 -0
  30. package/dist/assets/bsl-BO_Y6i37.js +1 -0
  31. package/dist/assets/c-BIGW1oBm.js +1 -0
  32. package/dist/assets/cadence-Bv_4Rxtq.js +1 -0
  33. package/dist/assets/cairo-KRGpt6FW.js +1 -0
  34. package/dist/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  35. package/dist/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  36. package/dist/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  37. package/dist/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  38. package/dist/assets/clarity-D53aC0YG.js +1 -0
  39. package/dist/assets/clike-B9uivgTg.js +1 -0
  40. package/dist/assets/clojure-BMjYHr_A.js +1 -0
  41. package/dist/assets/clojure-P80f7IUj.js +1 -0
  42. package/dist/assets/cmake-BQqOBYOt.js +1 -0
  43. package/dist/assets/cmake-D1j8_8rp.js +1 -0
  44. package/dist/assets/cobol-CWcv1MsR.js +1 -0
  45. package/dist/assets/cobol-nwyudZeR.js +1 -0
  46. package/dist/assets/codeowners-Bp6g37R7.js +1 -0
  47. package/dist/assets/codeql-DsOJ9woJ.js +1 -0
  48. package/dist/assets/coffee-Ch7k5sss.js +1 -0
  49. package/dist/assets/coffeescript-S37ZYGWr.js +1 -0
  50. package/dist/assets/common-lisp-Cg-RD9OK.js +1 -0
  51. package/dist/assets/commonlisp-DBKNyK5s.js +1 -0
  52. package/dist/assets/coq-DkFqJrB1.js +1 -0
  53. package/dist/assets/cpp-CofmeUqb.js +1 -0
  54. package/dist/assets/crystal-SjHAIU92.js +1 -0
  55. package/dist/assets/crystal-tKQVLTB8.js +1 -0
  56. package/dist/assets/csharp-K5feNrxe.js +1 -0
  57. package/dist/assets/css-BnMrqG3P.js +1 -0
  58. package/dist/assets/css-DPfMkruS.js +1 -0
  59. package/dist/assets/csv-fuZLfV_i.js +1 -0
  60. package/dist/assets/cue-D82EKSYY.js +1 -0
  61. package/dist/assets/cypher-COkxafJQ.js +1 -0
  62. package/dist/assets/cypher-C_CwsFkJ.js +1 -0
  63. package/dist/assets/d-85-TOEBH.js +1 -0
  64. package/dist/assets/d-pRatUO7H.js +1 -0
  65. package/dist/assets/dark-plus-C3mMm8J8.js +1 -0
  66. package/dist/assets/dart-CF10PKvl.js +1 -0
  67. package/dist/assets/dax-CEL-wOlO.js +1 -0
  68. package/dist/assets/desktop-BmXAJ9_W.js +1 -0
  69. package/dist/assets/diff-D97Zzqfu.js +1 -0
  70. package/dist/assets/diff-DbItnlRl.js +1 -0
  71. package/dist/assets/docker-BcOcwvcX.js +1 -0
  72. package/dist/assets/dockerfile-BKs6k2Af.js +1 -0
  73. package/dist/assets/dotenv-Da5cRb03.js +1 -0
  74. package/dist/assets/dracula-BzJJZx-M.js +1 -0
  75. package/dist/assets/dracula-soft-BXkSAIEj.js +1 -0
  76. package/dist/assets/dream-maker-BtqSS_iP.js +1 -0
  77. package/dist/assets/dtd-DF_7sFjM.js +1 -0
  78. package/dist/assets/dylan-DwRh75JA.js +1 -0
  79. package/dist/assets/ebnf-CDyGwa7X.js +1 -0
  80. package/dist/assets/ecl-Cabwm37j.js +1 -0
  81. package/dist/assets/edge-BkV0erSs.js +1 -0
  82. package/dist/assets/eiffel-CnydiIhH.js +1 -0
  83. package/dist/assets/elixir-CDX3lj18.js +1 -0
  84. package/dist/assets/elm-DbKCFpqz.js +1 -0
  85. package/dist/assets/elm-vLlmbW-K.js +1 -0
  86. package/dist/assets/emacs-lisp-C9XAeP06.js +1 -0
  87. package/dist/assets/erb-BOJIQeun.js +1 -0
  88. package/dist/assets/erlang-BNw1qcRV.js +1 -0
  89. package/dist/assets/erlang-DsQrWhSR.js +1 -0
  90. package/dist/assets/everforest-dark-BgDCqdQA.js +1 -0
  91. package/dist/assets/everforest-light-C8M2exoo.js +1 -0
  92. package/dist/assets/factor-kuTfRLto.js +1 -0
  93. package/dist/assets/fcl-Kvtd6kyn.js +1 -0
  94. package/dist/assets/fennel-BYunw83y.js +1 -0
  95. package/dist/assets/fish-BvzEVeQv.js +1 -0
  96. package/dist/assets/fluent-C4IJs8-o.js +1 -0
  97. package/dist/assets/forth-Ffai-XNe.js +1 -0
  98. package/dist/assets/fortran-DYz_wnZ1.js +1 -0
  99. package/dist/assets/fortran-fixed-form-BZjJHVRy.js +1 -0
  100. package/dist/assets/fortran-free-form-D22FLkUw.js +1 -0
  101. package/dist/assets/fsharp-CXgrBDvD.js +1 -0
  102. package/dist/assets/gas-Bneqetm1.js +1 -0
  103. package/dist/assets/gdresource-B7Tvp0Sc.js +1 -0
  104. package/dist/assets/gdscript-DTMYz4Jt.js +1 -0
  105. package/dist/assets/gdshader-DkwncUOv.js +1 -0
  106. package/dist/assets/genie-D0YGMca9.js +1 -0
  107. package/dist/assets/gherkin-DyxjwDmM.js +1 -0
  108. package/dist/assets/gherkin-heZmZLOM.js +1 -0
  109. package/dist/assets/git-commit-F4YmCXRG.js +1 -0
  110. package/dist/assets/git-rebase-r7XF79zn.js +1 -0
  111. package/dist/assets/github-dark-DHJKELXO.js +1 -0
  112. package/dist/assets/github-dark-default-Cuk6v7N8.js +1 -0
  113. package/dist/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  114. package/dist/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  115. package/dist/assets/github-light-DAi9KRSo.js +1 -0
  116. package/dist/assets/github-light-default-D7oLnXFd.js +1 -0
  117. package/dist/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  118. package/dist/assets/gleam-BspZqrRM.js +1 -0
  119. package/dist/assets/glimmer-js-Rg0-pVw9.js +1 -0
  120. package/dist/assets/glimmer-ts-U6CK756n.js +1 -0
  121. package/dist/assets/glsl-DplSGwfg.js +1 -0
  122. package/dist/assets/gnuplot-DdkO51Og.js +1 -0
  123. package/dist/assets/go-Dn2_MT6a.js +1 -0
  124. package/dist/assets/graphql-ChdNCCLP.js +1 -0
  125. package/dist/assets/groovy-D9Dt4D0W.js +1 -0
  126. package/dist/assets/groovy-gcz8RCvz.js +1 -0
  127. package/dist/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  128. package/dist/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  129. package/dist/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  130. package/dist/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  131. package/dist/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  132. package/dist/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  133. package/dist/assets/hack-CaT9iCJl.js +1 -0
  134. package/dist/assets/haml-B8DHNrY2.js +1 -0
  135. package/dist/assets/handlebars-BL8al0AC.js +1 -0
  136. package/dist/assets/haskell-Cw1EW3IL.js +1 -0
  137. package/dist/assets/haskell-Df6bDoY_.js +1 -0
  138. package/dist/assets/haxe-CzTSHFRz.js +1 -0
  139. package/dist/assets/haxe-H-WmDvRZ.js +1 -0
  140. package/dist/assets/hcl-BWvSN4gD.js +1 -0
  141. package/dist/assets/hjson-D5-asLiD.js +1 -0
  142. package/dist/assets/hlsl-D3lLCCz7.js +1 -0
  143. package/dist/assets/houston-DnULxvSX.js +1 -0
  144. package/dist/assets/html-GMplVEZG.js +1 -0
  145. package/dist/assets/html-derivative-BFtXZ54Q.js +1 -0
  146. package/dist/assets/http-DBlCnlav.js +1 -0
  147. package/dist/assets/http-jrhK8wxY.js +1 -0
  148. package/dist/assets/hurl-irOxFIW8.js +1 -0
  149. package/dist/assets/hxml-Bvhsp5Yf.js +1 -0
  150. package/dist/assets/hy-DFXneXwc.js +1 -0
  151. package/dist/assets/idl-BEugSyMb.js +1 -0
  152. package/dist/assets/imba-DGztddWO.js +1 -0
  153. package/dist/assets/index-8c6bEJ99.js +1 -0
  154. package/dist/assets/index-AbWe21oh.js +2 -0
  155. package/dist/assets/index-ArhptQw0.js +1 -0
  156. package/dist/assets/index-B1hpa--1.js +3 -0
  157. package/dist/assets/index-Bafja8o4.js +1 -0
  158. package/dist/assets/index-Bp00uZNc.js +1 -0
  159. package/dist/assets/index-BsTieXqQ.js +1 -0
  160. package/dist/assets/index-BvGAWAqS.js +1 -0
  161. package/dist/assets/index-CCfVkFzN.js +1 -0
  162. package/dist/assets/index-D-Urq2hl.css +1 -0
  163. package/dist/assets/index-D3mXuuih.js +1 -0
  164. package/dist/assets/index-DFOLYN6W.js +1 -0
  165. package/dist/assets/index-DpxkOmNJ.js +7 -0
  166. package/dist/assets/index-YZ-iXB95.js +309 -0
  167. package/dist/assets/index-eA_XNQ_L.js +1 -0
  168. package/dist/assets/index-ftYom_wU.js +1 -0
  169. package/dist/assets/index-gvPT4BlL.js +1 -0
  170. package/dist/assets/ini-BEwlwnbL.js +1 -0
  171. package/dist/assets/java-CylS5w8V.js +1 -0
  172. package/dist/assets/javascript-iXu5QeM3.js +1 -0
  173. package/dist/assets/javascript-wDzz0qaB.js +1 -0
  174. package/dist/assets/jinja-4LBKfQ-Z.js +1 -0
  175. package/dist/assets/jison-wvAkD_A8.js +1 -0
  176. package/dist/assets/json-Cp-IABpG.js +1 -0
  177. package/dist/assets/json5-C9tS-k6U.js +1 -0
  178. package/dist/assets/jsonc-Des-eS-w.js +1 -0
  179. package/dist/assets/jsonl-DcaNXYhu.js +1 -0
  180. package/dist/assets/jsonnet-DFQXde-d.js +1 -0
  181. package/dist/assets/jssm-C2t-YnRu.js +1 -0
  182. package/dist/assets/jsx-g9-lgVsj.js +1 -0
  183. package/dist/assets/julia-C8NyazO9.js +1 -0
  184. package/dist/assets/julia-DuME0IfC.js +1 -0
  185. package/dist/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  186. package/dist/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  187. package/dist/assets/kanagawa-wave-DWedfzmr.js +1 -0
  188. package/dist/assets/kdl-DV7GczEv.js +1 -0
  189. package/dist/assets/kotlin-BdnUsdx6.js +1 -0
  190. package/dist/assets/kusto-BvAqAH-y.js +1 -0
  191. package/dist/assets/laserwave-DUszq2jm.js +1 -0
  192. package/dist/assets/latex-BdAV_C_H.js +1 -0
  193. package/dist/assets/lean-Bc6EcWN3.js +1 -0
  194. package/dist/assets/less-B1dDrJ26.js +1 -0
  195. package/dist/assets/light-plus-B7mTdjB0.js +1 -0
  196. package/dist/assets/liquid-DYVedYrR.js +1 -0
  197. package/dist/assets/livescript-BwQOo05w.js +1 -0
  198. package/dist/assets/llvm-BtvRca6l.js +1 -0
  199. package/dist/assets/log-2UxHyX5q.js +1 -0
  200. package/dist/assets/logo-BtOb2qkB.js +1 -0
  201. package/dist/assets/lua-BbnMAYS6.js +1 -0
  202. package/dist/assets/lua-BgMRiT3U.js +1 -0
  203. package/dist/assets/luau-CXu1NL6O.js +1 -0
  204. package/dist/assets/make-CHLpvVh8.js +1 -0
  205. package/dist/assets/markdown-Cvjx9yec.js +1 -0
  206. package/dist/assets/marko-CPi9NSCl.js +1 -0
  207. package/dist/assets/material-theme-D5KoaKCx.js +1 -0
  208. package/dist/assets/material-theme-darker-BfHTSMKl.js +1 -0
  209. package/dist/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  210. package/dist/assets/material-theme-ocean-CyktbL80.js +1 -0
  211. package/dist/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  212. package/dist/assets/mathematica-DTrFuWx2.js +1 -0
  213. package/dist/assets/matlab-D7o27uSR.js +1 -0
  214. package/dist/assets/mbox-CNhZ1qSd.js +1 -0
  215. package/dist/assets/mdc-DUICxH0z.js +1 -0
  216. package/dist/assets/mdx-Cmh6b_Ma.js +1 -0
  217. package/dist/assets/mermaid-DKYwYmdq.js +1 -0
  218. package/dist/assets/min-dark-CafNBF8u.js +1 -0
  219. package/dist/assets/min-light-CTRr51gU.js +1 -0
  220. package/dist/assets/mipsasm-CKIfxQSi.js +1 -0
  221. package/dist/assets/mirc-CjQqDB4T.js +1 -0
  222. package/dist/assets/mllike-CXdrOF99.js +1 -0
  223. package/dist/assets/modelica-Dc1JOy9r.js +1 -0
  224. package/dist/assets/mojo-1DNp92w6.js +1 -0
  225. package/dist/assets/monokai-D4h5O-jR.js +1 -0
  226. package/dist/assets/move-Bu9oaDYs.js +1 -0
  227. package/dist/assets/mscgen-BA5vi2Kp.js +1 -0
  228. package/dist/assets/mumps-BT43cFF4.js +1 -0
  229. package/dist/assets/narrat-DRg8JJMk.js +1 -0
  230. package/dist/assets/nextflow-BrzmwbiE.js +1 -0
  231. package/dist/assets/nginx-DdIZxoE0.js +1 -0
  232. package/dist/assets/nginx-DknmC5AR.js +1 -0
  233. package/dist/assets/night-owl-C39BiMTA.js +1 -0
  234. package/dist/assets/nim-CVrawwO9.js +1 -0
  235. package/dist/assets/nix-c8nO5XWb.js +1 -0
  236. package/dist/assets/nord-Ddv68eIx.js +1 -0
  237. package/dist/assets/nsis-LdVXkNf5.js +1 -0
  238. package/dist/assets/ntriples-BfvgReVJ.js +1 -0
  239. package/dist/assets/nushell-C-sUppwS.js +1 -0
  240. package/dist/assets/objective-c-DXmwc3jG.js +1 -0
  241. package/dist/assets/objective-cpp-CLxacb5B.js +1 -0
  242. package/dist/assets/ocaml-C0hk2d4L.js +1 -0
  243. package/dist/assets/octave-Ck1zUtKM.js +1 -0
  244. package/dist/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  245. package/dist/assets/one-light-PoHY5YXO.js +1 -0
  246. package/dist/assets/openscad-C4EeE6gA.js +1 -0
  247. package/dist/assets/oz-BzwKVEFT.js +1 -0
  248. package/dist/assets/pascal--L3eBynH.js +1 -0
  249. package/dist/assets/pascal-D93ZcfNL.js +1 -0
  250. package/dist/assets/perl-C0TMdlhV.js +1 -0
  251. package/dist/assets/perl-CdXCOZ3F.js +1 -0
  252. package/dist/assets/php-CDn_0X-4.js +1 -0
  253. package/dist/assets/pig-CevX1Tat.js +1 -0
  254. package/dist/assets/pkl-u5AG7uiY.js +1 -0
  255. package/dist/assets/plastic-3e1v2bzS.js +1 -0
  256. package/dist/assets/plsql-ChMvpjG-.js +1 -0
  257. package/dist/assets/po-BTJTHyun.js +1 -0
  258. package/dist/assets/poimandres-CS3Unz2-.js +1 -0
  259. package/dist/assets/polar-C0HS_06l.js +1 -0
  260. package/dist/assets/postcss-CXtECtnM.js +1 -0
  261. package/dist/assets/powerquery-CEu0bR-o.js +1 -0
  262. package/dist/assets/powershell-CFHJl5sT.js +1 -0
  263. package/dist/assets/powershell-Dpen1YoG.js +1 -0
  264. package/dist/assets/prisma-Dd19v3D-.js +1 -0
  265. package/dist/assets/prolog-CbFg5uaA.js +1 -0
  266. package/dist/assets/properties-C78fOPTZ.js +1 -0
  267. package/dist/assets/proto-DyJlTyXw.js +1 -0
  268. package/dist/assets/protobuf-ChK-085T.js +1 -0
  269. package/dist/assets/pug-CGlum2m_.js +1 -0
  270. package/dist/assets/pug-DeIclll2.js +1 -0
  271. package/dist/assets/puppet-BMWR74SV.js +1 -0
  272. package/dist/assets/puppet-DMA9R1ak.js +1 -0
  273. package/dist/assets/purescript-CklMAg4u.js +1 -0
  274. package/dist/assets/python-B6aJPvgy.js +1 -0
  275. package/dist/assets/python-BuPzkPfP.js +1 -0
  276. package/dist/assets/q-pXgVlZs6.js +1 -0
  277. package/dist/assets/qml-3beO22l8.js +1 -0
  278. package/dist/assets/qmldir-C8lEn-DE.js +1 -0
  279. package/dist/assets/qss-IeuSbFQv.js +1 -0
  280. package/dist/assets/r-B6wPVr8A.js +1 -0
  281. package/dist/assets/r-DiinP2Uv.js +1 -0
  282. package/dist/assets/racket-BqYA7rlc.js +1 -0
  283. package/dist/assets/raku-DXvB9xmW.js +1 -0
  284. package/dist/assets/razor-CE9lU5zL.js +1 -0
  285. package/dist/assets/red-bN70gL4F.js +1 -0
  286. package/dist/assets/reg-C-SQnVFl.js +1 -0
  287. package/dist/assets/regexp-CDVJQ6XC.js +1 -0
  288. package/dist/assets/rel-C3B-1QV4.js +1 -0
  289. package/dist/assets/riscv-BM1_JUlF.js +1 -0
  290. package/dist/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
  291. package/dist/assets/rose-pine-moon-D4_iv3hh.js +1 -0
  292. package/dist/assets/rose-pine-qdsjHGoJ.js +1 -0
  293. package/dist/assets/rosmsg-BJDFO7_C.js +1 -0
  294. package/dist/assets/rpm-CTu-6PCP.js +1 -0
  295. package/dist/assets/rst-B0xPkSld.js +1 -0
  296. package/dist/assets/ruby-B2Rjki9n.js +1 -0
  297. package/dist/assets/ruby-BvKwtOVI.js +1 -0
  298. package/dist/assets/rust-B1yitclQ.js +1 -0
  299. package/dist/assets/sas-B4kiWyti.js +1 -0
  300. package/dist/assets/sas-cz2c8ADy.js +1 -0
  301. package/dist/assets/sass-Cj5Yp3dK.js +1 -0
  302. package/dist/assets/scala-C151Ov-r.js +1 -0
  303. package/dist/assets/scheme-C41bIUwD.js +1 -0
  304. package/dist/assets/scheme-C98Dy4si.js +1 -0
  305. package/dist/assets/scss-OYdSNvt2.js +1 -0
  306. package/dist/assets/sdbl-DVxCFoDh.js +1 -0
  307. package/dist/assets/shaderlab-Dg9Lc6iA.js +1 -0
  308. package/dist/assets/shell-CjFT_Tl9.js +1 -0
  309. package/dist/assets/shellscript-Yzrsuije.js +1 -0
  310. package/dist/assets/shellsession-BADoaaVG.js +1 -0
  311. package/dist/assets/sieve-C3Gn_uJK.js +1 -0
  312. package/dist/assets/simple-mode-GW_nhZxv.js +1 -0
  313. package/dist/assets/slack-dark-BthQWCQV.js +1 -0
  314. package/dist/assets/slack-ochin-DqwNpetd.js +1 -0
  315. package/dist/assets/smalltalk-BERRCDM3.js +1 -0
  316. package/dist/assets/smalltalk-CnHTOXQT.js +1 -0
  317. package/dist/assets/snazzy-light-Bw305WKR.js +1 -0
  318. package/dist/assets/solarized-dark-DXbdFlpD.js +1 -0
  319. package/dist/assets/solarized-light-L9t79GZl.js +1 -0
  320. package/dist/assets/solidity-rGO070M0.js +1 -0
  321. package/dist/assets/solr-DehyRSwq.js +1 -0
  322. package/dist/assets/soy-Brmx7dQM.js +1 -0
  323. package/dist/assets/sparql-DkYu6x3z.js +1 -0
  324. package/dist/assets/sparql-rVzFXLq3.js +1 -0
  325. package/dist/assets/splunk-BtCnVYZw.js +1 -0
  326. package/dist/assets/spreadsheet-BCZA_wO0.js +1 -0
  327. package/dist/assets/sql-BLtJtn59.js +1 -0
  328. package/dist/assets/sql-D0XecflT.js +1 -0
  329. package/dist/assets/ssh-config-_ykCGR6B.js +1 -0
  330. package/dist/assets/stata-BH5u7GGu.js +1 -0
  331. package/dist/assets/stex-C3f8Ysf7.js +1 -0
  332. package/dist/assets/stylus-B533Al4x.js +1 -0
  333. package/dist/assets/stylus-BEDo0Tqx.js +1 -0
  334. package/dist/assets/svelte-3Dk4HxPD.js +1 -0
  335. package/dist/assets/swift-BzpIVaGY.js +1 -0
  336. package/dist/assets/swift-Dg5xB15N.js +1 -0
  337. package/dist/assets/synthwave-84-CbfX1IO0.js +1 -0
  338. package/dist/assets/system-verilog-CnnmHF94.js +1 -0
  339. package/dist/assets/systemd-4A_iFExJ.js +1 -0
  340. package/dist/assets/talonscript-CkByrt1z.js +1 -0
  341. package/dist/assets/tasl-QIJgUcNo.js +1 -0
  342. package/dist/assets/tcl-DVfN8rqt.js +1 -0
  343. package/dist/assets/tcl-dwOrl1Do.js +1 -0
  344. package/dist/assets/templ-W15q3VgB.js +1 -0
  345. package/dist/assets/terraform-BETggiCN.js +1 -0
  346. package/dist/assets/tex-CxkMU7Pf.js +1 -0
  347. package/dist/assets/textile-CnDTJFAw.js +1 -0
  348. package/dist/assets/tiddlywiki-DO-Gjzrf.js +1 -0
  349. package/dist/assets/tiki-DGYXhP31.js +1 -0
  350. package/dist/assets/tokyo-night-hegEt444.js +1 -0
  351. package/dist/assets/toml-Bm5Em-hy.js +1 -0
  352. package/dist/assets/toml-vGWfd6FD.js +1 -0
  353. package/dist/assets/troff-wAsdV37c.js +1 -0
  354. package/dist/assets/ts-tags-zn1MmPIZ.js +1 -0
  355. package/dist/assets/tsv-B_m7g4N7.js +1 -0
  356. package/dist/assets/tsx-COt5Ahok.js +1 -0
  357. package/dist/assets/ttcn-CfJYG6tj.js +1 -0
  358. package/dist/assets/ttcn-cfg-B9xdYoR4.js +1 -0
  359. package/dist/assets/turtle-B1tBg_DP.js +1 -0
  360. package/dist/assets/turtle-BsS91CYL.js +1 -0
  361. package/dist/assets/twig-CO9l9SDP.js +1 -0
  362. package/dist/assets/typescript-BPQ3VLAy.js +1 -0
  363. package/dist/assets/typespec-BGHnOYBU.js +1 -0
  364. package/dist/assets/typst-DHCkPAjA.js +1 -0
  365. package/dist/assets/v-BcVCzyr7.js +1 -0
  366. package/dist/assets/vala-CsfeWuGM.js +1 -0
  367. package/dist/assets/vb-CmGdzxic.js +1 -0
  368. package/dist/assets/vb-D17OF-Vu.js +1 -0
  369. package/dist/assets/vbscript-BuJXcnF6.js +1 -0
  370. package/dist/assets/velocity-D8B20fx6.js +1 -0
  371. package/dist/assets/verilog-BQ8w6xss.js +1 -0
  372. package/dist/assets/verilog-C6RDOZhf.js +1 -0
  373. package/dist/assets/vesper-DU1UobuO.js +1 -0
  374. package/dist/assets/vhdl-CeAyd5Ju.js +1 -0
  375. package/dist/assets/vhdl-lSbBsy5d.js +1 -0
  376. package/dist/assets/viml-CJc9bBzg.js +1 -0
  377. package/dist/assets/vitesse-black-Bkuqu6BP.js +1 -0
  378. package/dist/assets/vitesse-dark-D0r3Knsf.js +1 -0
  379. package/dist/assets/vitesse-light-CVO1_9PV.js +1 -0
  380. package/dist/assets/vue-DnHKYNfI.js +1 -0
  381. package/dist/assets/vue-html-CChd_i61.js +1 -0
  382. package/dist/assets/vue-vine-8moa0y9V.js +1 -0
  383. package/dist/assets/vyper-CDx5xZoG.js +1 -0
  384. package/dist/assets/wasm-CG6Dc4jp.js +1 -0
  385. package/dist/assets/wasm-MzD3tlZU.js +1 -0
  386. package/dist/assets/webidl-ZXfAyPTL.js +1 -0
  387. package/dist/assets/wenyan-BV7otONQ.js +1 -0
  388. package/dist/assets/wgsl-Dx-B1_4e.js +1 -0
  389. package/dist/assets/wikitext-BhOHFoWU.js +1 -0
  390. package/dist/assets/wit-5i3qLPDT.js +1 -0
  391. package/dist/assets/wolfram-lXgVvXCa.js +1 -0
  392. package/dist/assets/xml-sdJ4AIDG.js +1 -0
  393. package/dist/assets/xquery-DzFWVndE.js +1 -0
  394. package/dist/assets/xsl-CtQFsRM5.js +1 -0
  395. package/dist/assets/yacas-BJ4BC0dw.js +1 -0
  396. package/dist/assets/yaml-Buea-lGh.js +1 -0
  397. package/dist/assets/z80-Hz9HOZM7.js +1 -0
  398. package/dist/assets/zenscript-DVFEvuxE.js +1 -0
  399. package/dist/assets/zig-VOosw3JB.js +1 -0
  400. package/dist/index.html +47 -0
  401. package/dist/logo.svg +8 -0
  402. package/dist/openspec_pixel_dark.svg +89 -0
  403. package/dist/openspec_pixel_light.svg +89 -0
  404. package/index.html +46 -0
  405. package/package.json +66 -0
  406. package/src/App.tsx +124 -0
  407. package/src/components/StaticModeBanner.tsx +46 -0
  408. package/src/components/change-overview.tsx +156 -0
  409. package/src/components/cli-terminal.tsx +93 -0
  410. package/src/components/code-editor.tsx +232 -0
  411. package/src/components/copyable-path.tsx +44 -0
  412. package/src/components/dialog.tsx +193 -0
  413. package/src/components/folder-editor-viewer.tsx +411 -0
  414. package/src/components/global-archive-modal.tsx +205 -0
  415. package/src/components/layout/desktop-sidebar.tsx +48 -0
  416. package/src/components/layout/index.ts +6 -0
  417. package/src/components/layout/mobile-header.tsx +91 -0
  418. package/src/components/layout/mobile-tabbar.tsx +20 -0
  419. package/src/components/layout/nav-items.ts +33 -0
  420. package/src/components/layout/root-layout.tsx +67 -0
  421. package/src/components/layout/status-bar.tsx +69 -0
  422. package/src/components/markdown-content.tsx +104 -0
  423. package/src/components/markdown-viewer.tsx +415 -0
  424. package/src/components/path-marquee.tsx +104 -0
  425. package/src/components/tabs.tsx +151 -0
  426. package/src/components/tasks-view.test.tsx +45 -0
  427. package/src/components/tasks-view.tsx +209 -0
  428. package/src/components/toc-context.tsx +177 -0
  429. package/src/components/toc.tsx +290 -0
  430. package/src/entry-client.tsx +47 -0
  431. package/src/index.css +481 -0
  432. package/src/lib/api-config.ts +72 -0
  433. package/src/lib/archive-modal-context.tsx +45 -0
  434. package/src/lib/codemirror-markdown-preview.ts +494 -0
  435. package/src/lib/format-time.ts +51 -0
  436. package/src/lib/shiki-highlighter.ts +47 -0
  437. package/src/lib/static-data-provider.ts +386 -0
  438. package/src/lib/static-mode.ts +158 -0
  439. package/src/lib/trpc.ts +107 -0
  440. package/src/lib/use-cli-runner.tsx +433 -0
  441. package/src/lib/use-dark-mode.ts +28 -0
  442. package/src/lib/use-server-status.ts +208 -0
  443. package/src/lib/use-subscription.ts +375 -0
  444. package/src/lib/use-tabs-status-by-query.ts +78 -0
  445. package/src/main.tsx +9 -0
  446. package/src/routes/archive-list.tsx +65 -0
  447. package/src/routes/archive-view.tsx +116 -0
  448. package/src/routes/change-list.tsx +63 -0
  449. package/src/routes/change-view.tsx +188 -0
  450. package/src/routes/dashboard.tsx +204 -0
  451. package/src/routes/project.tsx +272 -0
  452. package/src/routes/settings.tsx +816 -0
  453. package/src/routes/spec-list.tsx +49 -0
  454. package/src/routes/spec-view.tsx +164 -0
  455. package/src/ssg/entry-server.tsx +111 -0
  456. package/src/ssg/index.ts +6 -0
  457. package/src/ssg/prerender.ts +111 -0
  458. package/src/ssg/static-data-context.tsx +49 -0
  459. package/src/ssg/types.ts +5 -0
  460. package/src/test/setup.ts +1 -0
  461. package/src/vite-env.d.ts +6 -0
  462. package/tsconfig.json +18 -0
  463. 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
+ }