srcly 0.1.0__tar.gz → 0.1.4__tar.gz
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.
- {srcly-0.1.0 → srcly-0.1.4}/PKG-INFO +1 -1
- srcly-0.1.4/app/.gitignore +1 -0
- srcly-0.1.4/app/__init__.py +9 -0
- srcly-0.1.4/app/config.py +29 -0
- srcly-0.1.4/app/main.py +73 -0
- srcly-0.1.4/app/models.py +21 -0
- srcly-0.1.4/app/routers/analysis.py +77 -0
- srcly-0.1.4/app/routers/files.py +72 -0
- srcly-0.1.4/app/run.py +87 -0
- srcly-0.1.4/app/services/analysis.py +167 -0
- srcly-0.1.4/app/services/cache.py +28 -0
- srcly-0.1.4/app/static/assets/index-CBBfoGZS.css +1 -0
- srcly-0.1.4/app/static/assets/index-DCyN-sgG.js +18 -0
- srcly-0.1.4/app/static/index.html +17 -0
- {srcly-0.1.0 → srcly-0.1.4}/pyproject.toml +2 -2
- srcly-0.1.0/app/static/assets/index-CIVGRltC.css +0 -1
- srcly-0.1.0/app/static/assets/index-KFE7Rt2Z.js +0 -18
- srcly-0.1.0/app/static/index.html +0 -14
- {srcly-0.1.0 → srcly-0.1.4}/.gitignore +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/README.md +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/abap-BdImnpbu.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/actionscript-3-CfeIJUat.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/ada-bCR0ucgS.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/andromeeda-C-Jbm3Hp.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/angular-html-CU67Zn6k.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/angular-ts-BwZT4LLn.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/apache-Pmp26Uib.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/apex-DhZLUxFE.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/apl-dKokRX4l.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/applescript-Co6uUVPk.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/ara-BRHolxvo.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/asciidoc-Dv7Oe6Be.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/asm-D_Q5rh1f.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/astro-CbQHKStN.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/aurora-x-D-2ljcwZ.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/awk-DMzUqQB5.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/ayu-dark-Cv9koXgw.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/ballerina-BFfxhgS-.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/bat-BkioyH1T.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/beancount-k_qm7-4y.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/berry-D08WgyRC.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/bibtex-CHM0blh-.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/bicep-Bmn6On1c.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/blade-DVc8C-J4.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/bsl-BO_Y6i37.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/c-BIGW1oBm.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/cadence-Bv_4Rxtq.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/cairo-KRGpt6FW.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/catppuccin-frappe-DFWUc33u.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/catppuccin-latte-C9dUb6Cb.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/catppuccin-macchiato-DQyhUUbL.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/catppuccin-mocha-D87Tk5Gz.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/clarity-D53aC0YG.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/clojure-P80f7IUj.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/cmake-D1j8_8rp.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/cobol-nwyudZeR.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/codeowners-Bp6g37R7.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/codeql-DsOJ9woJ.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/coffee-Ch7k5sss.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/common-lisp-Cg-RD9OK.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/coq-DkFqJrB1.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/cpp-CofmeUqb.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/crystal-tKQVLTB8.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/csharp-CX12Zw3r.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/css-DPfMkruS.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/csv-fuZLfV_i.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/cue-D82EKSYY.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/cypher-COkxafJQ.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/d-85-TOEBH.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/dark-plus-eOWES_5F.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/dart-CF10PKvl.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/dax-CEL-wOlO.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/desktop-BmXAJ9_W.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/diff-D97Zzqfu.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/docker-BcOcwvcX.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/dotenv-Da5cRb03.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/dracula-BzJJZx-M.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/dracula-soft-BXkSAIEj.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/dream-maker-BtqSS_iP.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/edge-BkV0erSs.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/elixir-CDX3lj18.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/elm-DbKCFpqz.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/emacs-lisp-C9XAeP06.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/erb-BOJIQeun.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/erlang-DsQrWhSR.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/everforest-dark-BgDCqdQA.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/everforest-light-C8M2exoo.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/fennel-BYunw83y.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/fish-BvzEVeQv.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/fluent-C4IJs8-o.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/fortran-fixed-form-BZjJHVRy.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/fortran-free-form-D22FLkUw.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/fsharp-CXgrBDvD.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/gdresource-B7Tvp0Sc.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/gdscript-DTMYz4Jt.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/gdshader-DkwncUOv.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/genie-D0YGMca9.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/gherkin-DyxjwDmM.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/git-commit-F4YmCXRG.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/git-rebase-r7XF79zn.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/github-dark-DHJKELXO.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/github-dark-default-Cuk6v7N8.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/github-dark-dimmed-DH5Ifo-i.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/github-dark-high-contrast-E3gJ1_iC.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/github-light-DAi9KRSo.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/github-light-default-D7oLnXFd.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/github-light-high-contrast-BfjtVDDH.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/gleam-BspZqrRM.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/glimmer-js-Rg0-pVw9.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/glimmer-ts-U6CK756n.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/glsl-DplSGwfg.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/gnuplot-DdkO51Og.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/go-Dn2_MT6a.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/graphql-ChdNCCLP.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/groovy-gcz8RCvz.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/gruvbox-dark-hard-CFHQjOhq.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/gruvbox-dark-medium-GsRaNv29.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/gruvbox-dark-soft-CVdnzihN.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/gruvbox-light-hard-CH1njM8p.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/gruvbox-light-medium-DRw_LuNl.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/gruvbox-light-soft-hJgmCMqR.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/hack-CaT9iCJl.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/haml-B8DHNrY2.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/handlebars-BL8al0AC.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/haskell-Df6bDoY_.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/haxe-CzTSHFRz.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/hcl-BWvSN4gD.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/hjson-D5-asLiD.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/hlsl-D3lLCCz7.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/houston-DnULxvSX.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/html-GMplVEZG.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/html-derivative-BFtXZ54Q.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/http-jrhK8wxY.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/hurl-irOxFIW8.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/hxml-Bvhsp5Yf.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/hy-DFXneXwc.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/imba-DGztddWO.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/ini-BEwlwnbL.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/java-CylS5w8V.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/javascript-wDzz0qaB.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/jinja-4LBKfQ-Z.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/jison-wvAkD_A8.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/json-Cp-IABpG.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/json5-C9tS-k6U.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/jsonc-Des-eS-w.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/jsonl-DcaNXYhu.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/jsonnet-DFQXde-d.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/jssm-C2t-YnRu.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/jsx-g9-lgVsj.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/julia-C8NyazO9.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/kanagawa-dragon-CkXjmgJE.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/kanagawa-lotus-CfQXZHmo.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/kanagawa-wave-DWedfzmr.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/kdl-DV7GczEv.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/kotlin-BdnUsdx6.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/kusto-BvAqAH-y.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/laserwave-DUszq2jm.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/latex-BUKiar2Z.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/lean-DP1Csr6i.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/less-B1dDrJ26.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/light-plus-B7mTdjB0.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/liquid-DYVedYrR.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/llvm-BtvRca6l.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/log-2UxHyX5q.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/logo-BtOb2qkB.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/lua-BbnMAYS6.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/luau-CXu1NL6O.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/make-CHLpvVh8.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/markdown-Cvjx9yec.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/marko-CPi9NSCl.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/material-theme-D5KoaKCx.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/material-theme-darker-BfHTSMKl.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/material-theme-lighter-B0m2ddpp.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/material-theme-ocean-CyktbL80.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/material-theme-palenight-Csfq5Kiy.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/matlab-D7o27uSR.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/mdc-DUICxH0z.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/mdx-Cmh6b_Ma.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/mermaid-DKYwYmdq.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/min-dark-CafNBF8u.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/min-light-CTRr51gU.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/mipsasm-CKIfxQSi.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/mojo-1DNp92w6.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/monokai-D4h5O-jR.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/move-Bu9oaDYs.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/narrat-DRg8JJMk.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/nextflow-CUEJCptM.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/nginx-DknmC5AR.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/night-owl-C39BiMTA.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/nim-CVrawwO9.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/nix-BbRYJGeE.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/nord-Ddv68eIx.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/nushell-C-sUppwS.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/objective-c-DXmwc3jG.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/objective-cpp-CLxacb5B.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/ocaml-C0hk2d4L.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/one-dark-pro-DVMEJ2y_.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/one-light-PoHY5YXO.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/pascal-D93ZcfNL.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/perl-C0TMdlhV.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/php-CDn_0X-4.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/pkl-u5AG7uiY.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/plastic-3e1v2bzS.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/plsql-ChMvpjG-.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/po-BTJTHyun.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/poimandres-CS3Unz2-.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/polar-C0HS_06l.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/postcss-CXtECtnM.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/powerquery-CEu0bR-o.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/powershell-Dpen1YoG.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/prisma-Dd19v3D-.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/prolog-CbFg5uaA.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/proto-DyJlTyXw.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/pug-CGlum2m_.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/puppet-BMWR74SV.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/purescript-CklMAg4u.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/python-B6aJPvgy.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/qml-3beO22l8.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/qmldir-C8lEn-DE.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/qss-IeuSbFQv.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/r-DiinP2Uv.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/racket-BqYA7rlc.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/raku-DXvB9xmW.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/razor-WgofotgN.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/red-bN70gL4F.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/reg-C-SQnVFl.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/regexp-CDVJQ6XC.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/rel-C3B-1QV4.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/riscv-BM1_JUlF.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/rose-pine-BHrmToEH.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/rose-pine-dawn-CnK8MTSM.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/rose-pine-moon-NleAzG8P.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/rosmsg-BJDFO7_C.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/rst-B0xPkSld.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/ruby-BvKwtOVI.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/rust-B1yitclQ.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/sas-cz2c8ADy.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/sass-Cj5Yp3dK.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/scala-C151Ov-r.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/scheme-C98Dy4si.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/scss-OYdSNvt2.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/sdbl-DVxCFoDh.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/shaderlab-Dg9Lc6iA.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/shellscript-Yzrsuije.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/shellsession-BADoaaVG.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/slack-dark-BthQWCQV.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/slack-ochin-DqwNpetd.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/smalltalk-BERRCDM3.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/snazzy-light-Bw305WKR.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/solarized-dark-DXbdFlpD.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/solarized-light-L9t79GZl.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/solidity-BbcW6ACK.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/soy-Brmx7dQM.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/sparql-rVzFXLq3.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/splunk-BtCnVYZw.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/sql-BLtJtn59.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/ssh-config-_ykCGR6B.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/stata-BH5u7GGu.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/stylus-BEDo0Tqx.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/svelte-3Dk4HxPD.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/swift-Dg5xB15N.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/synthwave-84-CbfX1IO0.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/system-verilog-CnnmHF94.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/systemd-4A_iFExJ.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/talonscript-CkByrt1z.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/tasl-QIJgUcNo.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/tcl-dwOrl1Do.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/templ-W15q3VgB.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/terraform-BETggiCN.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/tex-Cppo0RY3.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/tokyo-night-hegEt444.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/toml-vGWfd6FD.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/ts-tags-zn1MmPIZ.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/tsv-B_m7g4N7.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/tsx-COt5Ahok.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/turtle-BsS91CYL.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/twig-CO9l9SDP.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/typescript-BPQ3VLAy.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/typespec-Df68jz8_.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/typst-DHCkPAjA.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/v-BcVCzyr7.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/vala-CsfeWuGM.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/vb-D17OF-Vu.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/verilog-BQ8w6xss.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/vesper-DU1UobuO.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/vhdl-CeAyd5Ju.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/viml-CJc9bBzg.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/vitesse-black-Bkuqu6BP.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/vitesse-dark-D0r3Knsf.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/vitesse-light-CVO1_9PV.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/vue-CCoi5OLL.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/vue-html-DAAvJJDi.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/vue-vine-_Ih-lPRR.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/vyper-CDx5xZoG.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/wasm-CG6Dc4jp.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/wasm-MzD3tlZU.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/wenyan-BV7otONQ.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/wgsl-Dx-B1_4e.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/wikitext-BhOHFoWU.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/wit-5i3qLPDT.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/wolfram-lXgVvXCa.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/xml-sdJ4AIDG.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/xsl-CtQFsRM5.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/yaml-Buea-lGh.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/zenscript-DVFEvuxE.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/zig-VOosw3JB.js +0 -0
- {srcly-0.1.0 → srcly-0.1.4}/app/static/vite.svg +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
static
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from typing import Set
|
|
2
|
+
|
|
3
|
+
IGNORE_DIRS: Set[str] = {
|
|
4
|
+
'.git',
|
|
5
|
+
'node_modules',
|
|
6
|
+
'venv',
|
|
7
|
+
'.venv',
|
|
8
|
+
'__pycache__',
|
|
9
|
+
'dist',
|
|
10
|
+
'build',
|
|
11
|
+
'.next',
|
|
12
|
+
'coverage',
|
|
13
|
+
'.idea',
|
|
14
|
+
'.vscode',
|
|
15
|
+
'target',
|
|
16
|
+
'out',
|
|
17
|
+
'android',
|
|
18
|
+
'ios',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
IGNORE_FILES: Set[str] = {
|
|
22
|
+
'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'cargo.lock',
|
|
23
|
+
'poetry.lock', 'Gemfile.lock', 'composer.lock', 'mix.lock'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
IGNORE_EXTENSIONS: Set[str] = {
|
|
27
|
+
'.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico', '.pdf',
|
|
28
|
+
'.zip', '.tar', '.gz', '.map', '.min.js', '.min.css', '.json', '.xml', '.txt', '.md'
|
|
29
|
+
}
|
srcly-0.1.4/app/main.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from fastapi import FastAPI
|
|
2
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
3
|
+
from fastapi.staticfiles import StaticFiles
|
|
4
|
+
from importlib import resources
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
from app.routers import analysis, files
|
|
9
|
+
|
|
10
|
+
app = FastAPI(
|
|
11
|
+
title="Srcly Server",
|
|
12
|
+
description="API for static code analysis and file serving.",
|
|
13
|
+
version="1.0.0",
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# Configure CORS
|
|
17
|
+
app.add_middleware(
|
|
18
|
+
CORSMiddleware,
|
|
19
|
+
allow_origins=["*"], # Allow all origins for local development
|
|
20
|
+
allow_credentials=True,
|
|
21
|
+
allow_methods=["*"],
|
|
22
|
+
allow_headers=["*"],
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Include Routers
|
|
26
|
+
app.include_router(analysis.router)
|
|
27
|
+
app.include_router(files.router)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _find_static_dir() -> str | None:
|
|
31
|
+
"""
|
|
32
|
+
Locate the directory that contains the built SPA assets.
|
|
33
|
+
|
|
34
|
+
Priority:
|
|
35
|
+
1. Packaged assets under the installed `app` package (for PyPI/uvx use).
|
|
36
|
+
2. Local `client/dist` folder relative to the repository layout (for dev).
|
|
37
|
+
"""
|
|
38
|
+
# 1) Packaged assets (wheel installed)
|
|
39
|
+
try:
|
|
40
|
+
package_root = resources.files("app")
|
|
41
|
+
static_dir = package_root / "static"
|
|
42
|
+
if static_dir.is_dir():
|
|
43
|
+
return os.fspath(static_dir)
|
|
44
|
+
except Exception:
|
|
45
|
+
# If anything goes wrong, fall back to dev lookup
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
# 2) Dev layout: ../client/dist from this file
|
|
49
|
+
dev_dist = (
|
|
50
|
+
Path(__file__)
|
|
51
|
+
.resolve()
|
|
52
|
+
.parent.parent # server/app -> server
|
|
53
|
+
/ ".."
|
|
54
|
+
/ "client"
|
|
55
|
+
/ "dist"
|
|
56
|
+
).resolve()
|
|
57
|
+
if dev_dist.is_dir():
|
|
58
|
+
return str(dev_dist)
|
|
59
|
+
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# Serve SPA if we can find built assets
|
|
64
|
+
static_dir = _find_static_dir()
|
|
65
|
+
if static_dir:
|
|
66
|
+
app.mount("/", StaticFiles(directory=static_dir, html=True), name="static")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@app.get("/api-status")
|
|
70
|
+
async def root():
|
|
71
|
+
return {
|
|
72
|
+
"message": "Srcly server is running. Visit /docs for API documentation."
|
|
73
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
|
|
4
|
+
class Metrics(BaseModel):
|
|
5
|
+
loc: int = 0
|
|
6
|
+
# Cyclomatic complexity can be fractional (e.g. average complexity),
|
|
7
|
+
# so we store it as a float to match the analysis values.
|
|
8
|
+
complexity: float = 0.0
|
|
9
|
+
function_count: int = 0
|
|
10
|
+
|
|
11
|
+
class Node(BaseModel):
|
|
12
|
+
name: str
|
|
13
|
+
type: str # "folder", "file", "function", "code_fragment"
|
|
14
|
+
path: str
|
|
15
|
+
metrics: Metrics
|
|
16
|
+
# Use default_factory to avoid sharing the same list across instances
|
|
17
|
+
children: List["Node"] = Field(default_factory=list)
|
|
18
|
+
|
|
19
|
+
model_config = {
|
|
20
|
+
"populate_by_name": True
|
|
21
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from fastapi import APIRouter, HTTPException
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from app.models import Node
|
|
6
|
+
from app.services import analysis, cache
|
|
7
|
+
from app.config import IGNORE_DIRS
|
|
8
|
+
|
|
9
|
+
router = APIRouter(prefix="/api/analysis", tags=["analysis"])
|
|
10
|
+
|
|
11
|
+
# TODO: Make this configurable via env var or request
|
|
12
|
+
ROOT_PATH = Path.cwd()
|
|
13
|
+
|
|
14
|
+
@router.get("", response_model=Node)
|
|
15
|
+
async def get_analysis(path: str = None):
|
|
16
|
+
"""
|
|
17
|
+
Get the static analysis of the codebase.
|
|
18
|
+
Returns cached result if available, otherwise triggers a scan.
|
|
19
|
+
"""
|
|
20
|
+
target_path = Path(path) if path else ROOT_PATH
|
|
21
|
+
|
|
22
|
+
if not target_path.exists():
|
|
23
|
+
# Fallback or error? Let's error if explicit path is invalid
|
|
24
|
+
if path:
|
|
25
|
+
raise HTTPException(status_code=404, detail="Path not found")
|
|
26
|
+
target_path = ROOT_PATH
|
|
27
|
+
|
|
28
|
+
cached_tree = cache.load_analysis(target_path)
|
|
29
|
+
if cached_tree:
|
|
30
|
+
return cached_tree
|
|
31
|
+
|
|
32
|
+
# If no cache, run scan synchronously (for now, could be async/background)
|
|
33
|
+
tree = analysis.scan_codebase(target_path)
|
|
34
|
+
cache.save_analysis(target_path, tree)
|
|
35
|
+
return tree
|
|
36
|
+
|
|
37
|
+
@router.post("/refresh", response_model=Node)
|
|
38
|
+
async def refresh_analysis():
|
|
39
|
+
"""
|
|
40
|
+
Force a re-scan of the codebase.
|
|
41
|
+
"""
|
|
42
|
+
tree = analysis.scan_codebase(ROOT_PATH)
|
|
43
|
+
cache.save_analysis(ROOT_PATH, tree)
|
|
44
|
+
return tree
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@router.get("/context")
|
|
48
|
+
async def get_analysis_context():
|
|
49
|
+
"""
|
|
50
|
+
Return basic information about the current analysis root directory.
|
|
51
|
+
|
|
52
|
+
This is used by the client to offer a one-click "analyze current folder"
|
|
53
|
+
option on first load.
|
|
54
|
+
"""
|
|
55
|
+
root = ROOT_PATH
|
|
56
|
+
file_count = 0
|
|
57
|
+
folder_count = 0
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
61
|
+
# Apply ignore rules for directories to keep the estimate reasonable
|
|
62
|
+
dirnames[:] = [
|
|
63
|
+
d for d in dirnames
|
|
64
|
+
if d not in IGNORE_DIRS and not d.startswith(".")
|
|
65
|
+
]
|
|
66
|
+
folder_count += len(dirnames)
|
|
67
|
+
file_count += len(filenames)
|
|
68
|
+
except Exception:
|
|
69
|
+
# If anything goes wrong, fall back to zeros but still report the path.
|
|
70
|
+
file_count = 0
|
|
71
|
+
folder_count = 0
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
"root_path": str(root),
|
|
75
|
+
"file_count": file_count,
|
|
76
|
+
"folder_count": folder_count,
|
|
77
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from fastapi import APIRouter, HTTPException, Query
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from fastapi.responses import PlainTextResponse
|
|
4
|
+
|
|
5
|
+
router = APIRouter(prefix="/api/files", tags=["files"])
|
|
6
|
+
|
|
7
|
+
@router.get("/content", response_class=PlainTextResponse)
|
|
8
|
+
async def get_file_content(path: str = Query(..., description="Absolute path to the file")):
|
|
9
|
+
"""
|
|
10
|
+
Get the raw content of a file.
|
|
11
|
+
"""
|
|
12
|
+
file_path = Path(path)
|
|
13
|
+
|
|
14
|
+
if not file_path.exists():
|
|
15
|
+
raise HTTPException(status_code=404, detail="File not found")
|
|
16
|
+
|
|
17
|
+
if not file_path.is_file():
|
|
18
|
+
raise HTTPException(status_code=400, detail="Path is not a file")
|
|
19
|
+
|
|
20
|
+
# Security check: In a real app, we'd want to restrict this to the project root.
|
|
21
|
+
# For this local tool, we'll allow reading any file as requested, but maybe warn?
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
return file_path.read_text(encoding="utf-8")
|
|
25
|
+
except Exception as e:
|
|
26
|
+
raise HTTPException(status_code=500, detail=f"Failed to read file: {str(e)}")
|
|
27
|
+
|
|
28
|
+
@router.get("/suggest")
|
|
29
|
+
async def suggest_files(path: str = Query(..., description="Path to list contents of")):
|
|
30
|
+
"""
|
|
31
|
+
List files and directories in the given path for auto-suggestion.
|
|
32
|
+
"""
|
|
33
|
+
try:
|
|
34
|
+
# Handle empty path or root
|
|
35
|
+
if not path or path == ".":
|
|
36
|
+
p = Path.cwd()
|
|
37
|
+
else:
|
|
38
|
+
p = Path(path)
|
|
39
|
+
|
|
40
|
+
if not p.exists():
|
|
41
|
+
# If path doesn't exist, try parent
|
|
42
|
+
p = p.parent
|
|
43
|
+
|
|
44
|
+
if not p.exists() or not p.is_dir():
|
|
45
|
+
return {"items": [], "current": str(p)}
|
|
46
|
+
|
|
47
|
+
items = []
|
|
48
|
+
for item in p.iterdir():
|
|
49
|
+
try:
|
|
50
|
+
# Skip hidden files/dirs
|
|
51
|
+
if item.name.startswith('.'):
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
items.append({
|
|
55
|
+
"name": item.name,
|
|
56
|
+
"path": str(item.absolute()),
|
|
57
|
+
"type": "folder" if item.is_dir() else "file"
|
|
58
|
+
})
|
|
59
|
+
except PermissionError:
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
# Sort: folders first, then files
|
|
63
|
+
items.sort(key=lambda x: (x["type"] != "folder", x["name"].lower()))
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
"items": items,
|
|
67
|
+
"current": str(p.absolute())
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
except Exception as e:
|
|
71
|
+
print(f"Error in suggest: {e}")
|
|
72
|
+
return {"items": [], "error": str(e)}
|
srcly-0.1.4/app/run.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import threading
|
|
4
|
+
import time
|
|
5
|
+
import webbrowser
|
|
6
|
+
|
|
7
|
+
import uvicorn
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _open_browser_later(url: str, delay: float = 1.0) -> None:
|
|
11
|
+
"""
|
|
12
|
+
Open the default web browser after a short delay.
|
|
13
|
+
|
|
14
|
+
This lets the server start first so the page is reachable.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def _worker() -> None:
|
|
18
|
+
time.sleep(delay)
|
|
19
|
+
try:
|
|
20
|
+
webbrowser.open(url)
|
|
21
|
+
except Exception:
|
|
22
|
+
# Don't crash the CLI if opening the browser fails (e.g. headless env)
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
thread = threading.Thread(target=_worker, daemon=True)
|
|
26
|
+
thread.start()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def main(argv: list[str] | None = None) -> None:
|
|
30
|
+
"""
|
|
31
|
+
Entry point for the CLI.
|
|
32
|
+
|
|
33
|
+
- Uses the current working directory (or a provided path) as the codebase root.
|
|
34
|
+
- Starts the FastAPI server.
|
|
35
|
+
- Opens the default browser to the app URL.
|
|
36
|
+
"""
|
|
37
|
+
parser = argparse.ArgumentParser(
|
|
38
|
+
prog="srcly",
|
|
39
|
+
description=(
|
|
40
|
+
"Interactive codebase treemap and metrics viewer. "
|
|
41
|
+
"By default, analyzes the current working directory."
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
parser.add_argument(
|
|
45
|
+
"path",
|
|
46
|
+
nargs="?",
|
|
47
|
+
default=".",
|
|
48
|
+
help="Path to the codebase to analyze (default: current directory).",
|
|
49
|
+
)
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
"--host",
|
|
52
|
+
default="127.0.0.1",
|
|
53
|
+
help="Host interface to bind the server to (default: 127.0.0.1).",
|
|
54
|
+
)
|
|
55
|
+
parser.add_argument(
|
|
56
|
+
"--port",
|
|
57
|
+
type=int,
|
|
58
|
+
default=8000,
|
|
59
|
+
help="Port to run the server on (default: 8000).",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
args = parser.parse_args(argv)
|
|
63
|
+
|
|
64
|
+
target_path = os.path.abspath(args.path)
|
|
65
|
+
if not os.path.exists(target_path):
|
|
66
|
+
raise SystemExit(f"Path does not exist: {target_path}")
|
|
67
|
+
|
|
68
|
+
# Change working directory so the API defaults to this path.
|
|
69
|
+
os.chdir(target_path)
|
|
70
|
+
print(f"📂 Analyzing codebase at: {target_path}")
|
|
71
|
+
|
|
72
|
+
url = f"http://{args.host}:{args.port}"
|
|
73
|
+
print(f"🚀 Starting server at {url}")
|
|
74
|
+
print(" Press Ctrl+C to stop.")
|
|
75
|
+
|
|
76
|
+
_open_browser_later(url)
|
|
77
|
+
|
|
78
|
+
uvicorn.run(
|
|
79
|
+
"app.main:app",
|
|
80
|
+
host=args.host,
|
|
81
|
+
port=args.port,
|
|
82
|
+
reload=False,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
if __name__ == "__main__":
|
|
87
|
+
main()
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import lizard
|
|
3
|
+
import concurrent.futures
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from app.models import Node, Metrics
|
|
6
|
+
from app.config import IGNORE_DIRS, IGNORE_FILES, IGNORE_EXTENSIONS
|
|
7
|
+
|
|
8
|
+
def find_repo_root(start_path: Path) -> Path:
|
|
9
|
+
current = start_path.resolve()
|
|
10
|
+
for parent in [current, *current.parents]:
|
|
11
|
+
if (parent / ".git").exists(): return parent
|
|
12
|
+
return current
|
|
13
|
+
|
|
14
|
+
def create_node(name: str, node_type: str, path: str) -> Node:
|
|
15
|
+
return Node(
|
|
16
|
+
name=name,
|
|
17
|
+
type=node_type,
|
|
18
|
+
path=path,
|
|
19
|
+
metrics=Metrics(),
|
|
20
|
+
children=[]
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
def attach_file_metrics(node: Node, file_info) -> None:
|
|
24
|
+
total_loc = file_info.nloc
|
|
25
|
+
node.metrics.loc = total_loc
|
|
26
|
+
node.metrics.complexity = file_info.average_cyclomatic_complexity
|
|
27
|
+
node.metrics.function_count = len(file_info.function_list)
|
|
28
|
+
|
|
29
|
+
# Calculate sum of function LOCs
|
|
30
|
+
func_sum_loc = 0
|
|
31
|
+
|
|
32
|
+
for func in file_info.function_list:
|
|
33
|
+
func_node = create_node(func.name, "function", f"{node.path}::{func.name}")
|
|
34
|
+
func_node.metrics.loc = func.nloc
|
|
35
|
+
func_node.metrics.complexity = func.cyclomatic_complexity
|
|
36
|
+
# Functions don't have children in this model
|
|
37
|
+
node.children.append(func_node)
|
|
38
|
+
func_sum_loc += func.nloc
|
|
39
|
+
|
|
40
|
+
# CRITICAL FIX: Add a virtual node for the "Glue Code" (Imports, Exports, Global Vars)
|
|
41
|
+
remainder = total_loc - func_sum_loc
|
|
42
|
+
if remainder > 0:
|
|
43
|
+
misc_node = create_node("(misc/imports)", "code_fragment", f"{node.path}::__misc__")
|
|
44
|
+
misc_node.metrics.loc = remainder
|
|
45
|
+
misc_node.metrics.complexity = 0 # Glue code is usually simple
|
|
46
|
+
node.children.append(misc_node)
|
|
47
|
+
|
|
48
|
+
def aggregate_metrics(node: Node) -> Metrics:
|
|
49
|
+
if not node.children: return node.metrics
|
|
50
|
+
|
|
51
|
+
total_loc = 0
|
|
52
|
+
max_complexity = 0
|
|
53
|
+
total_funcs = 0
|
|
54
|
+
|
|
55
|
+
for child in node.children:
|
|
56
|
+
child_metrics = aggregate_metrics(child)
|
|
57
|
+
total_loc += child_metrics.loc
|
|
58
|
+
max_complexity = max(max_complexity, child_metrics.complexity)
|
|
59
|
+
total_funcs += child_metrics.function_count
|
|
60
|
+
|
|
61
|
+
# For Folders: Sum of children
|
|
62
|
+
# For Files: We trust the attach_file_metrics logic (which includes __misc__)
|
|
63
|
+
if node.type == "folder":
|
|
64
|
+
node.metrics.loc = total_loc
|
|
65
|
+
node.metrics.complexity = max_complexity
|
|
66
|
+
node.metrics.function_count = total_funcs
|
|
67
|
+
|
|
68
|
+
return node.metrics
|
|
69
|
+
|
|
70
|
+
def analyze_single_file(file_path: str):
|
|
71
|
+
"""
|
|
72
|
+
Wrapper to analyze a single file safely.
|
|
73
|
+
Must be top-level for multiprocessing pickling.
|
|
74
|
+
"""
|
|
75
|
+
try:
|
|
76
|
+
# We can't easily print from here to the main stdout without buffering issues in some envs,
|
|
77
|
+
# but returning the result allows the main process to log.
|
|
78
|
+
# However, for true parallelism, we might want to log here if we want to see it start.
|
|
79
|
+
# But 'done' log is more important.
|
|
80
|
+
return lizard.analyze_file(file_path)
|
|
81
|
+
except Exception as e:
|
|
82
|
+
# Return error info instead of crashing
|
|
83
|
+
return {"error": str(e), "filename": file_path}
|
|
84
|
+
|
|
85
|
+
def scan_codebase(root_path: Path) -> Node:
|
|
86
|
+
print(f"🔍 Scanning: {root_path}", flush=True)
|
|
87
|
+
|
|
88
|
+
files_to_scan = []
|
|
89
|
+
# Load .gitignore patterns if present
|
|
90
|
+
gitignore_path = root_path / ".gitignore"
|
|
91
|
+
gitignore_patterns = []
|
|
92
|
+
if gitignore_path.is_file():
|
|
93
|
+
with open(gitignore_path, "r") as f:
|
|
94
|
+
for line in f:
|
|
95
|
+
line = line.strip()
|
|
96
|
+
if line and not line.startswith("#"):
|
|
97
|
+
gitignore_patterns.append(line)
|
|
98
|
+
|
|
99
|
+
for root_dir, dirs, files in os.walk(root_path):
|
|
100
|
+
# Apply ignore dirs from config and .gitignore (if pattern matches dir)
|
|
101
|
+
dirs[:] = [d for d in dirs if d not in IGNORE_DIRS and not any(Path(root_dir, d).match(p) for p in gitignore_patterns)]
|
|
102
|
+
for file in files:
|
|
103
|
+
if file in IGNORE_FILES: continue
|
|
104
|
+
if Path(file).suffix in IGNORE_EXTENSIONS: continue
|
|
105
|
+
file_path = Path(root_dir) / file
|
|
106
|
+
rel_path = file_path.relative_to(root_path)
|
|
107
|
+
# Skip if matches any .gitignore pattern
|
|
108
|
+
if any(rel_path.match(p) for p in gitignore_patterns):
|
|
109
|
+
continue
|
|
110
|
+
files_to_scan.append(str(file_path))
|
|
111
|
+
|
|
112
|
+
print(f"📂 Analyzing {len(files_to_scan)} source files...", flush=True)
|
|
113
|
+
|
|
114
|
+
analysis_results = []
|
|
115
|
+
|
|
116
|
+
# Use ProcessPoolExecutor for better control and per-file error handling
|
|
117
|
+
# lizard.analyze_files was opaque and crashing the whole pool on one error
|
|
118
|
+
with concurrent.futures.ProcessPoolExecutor(max_workers=4) as executor:
|
|
119
|
+
future_to_file = {executor.submit(analyze_single_file, f): f for f in files_to_scan}
|
|
120
|
+
|
|
121
|
+
completed_count = 0
|
|
122
|
+
total_count = len(files_to_scan)
|
|
123
|
+
|
|
124
|
+
for future in concurrent.futures.as_completed(future_to_file):
|
|
125
|
+
file = future_to_file[future]
|
|
126
|
+
completed_count += 1
|
|
127
|
+
try:
|
|
128
|
+
result = future.result()
|
|
129
|
+
if isinstance(result, dict) and "error" in result:
|
|
130
|
+
print(f"❌ [{completed_count}/{total_count}] Error analyzing {file}: {result['error']}", flush=True)
|
|
131
|
+
else:
|
|
132
|
+
# Success
|
|
133
|
+
print(f"✅ [{completed_count}/{total_count}] Analyzed {file}", flush=True)
|
|
134
|
+
analysis_results.append(result)
|
|
135
|
+
except Exception as exc:
|
|
136
|
+
print(f"❌ [{completed_count}/{total_count}] Exception analyzing {file}: {exc}", flush=True)
|
|
137
|
+
|
|
138
|
+
tree_root = create_node("root", "folder", str(root_path))
|
|
139
|
+
node_map = {str(root_path): tree_root}
|
|
140
|
+
|
|
141
|
+
for file_info in analysis_results:
|
|
142
|
+
path_obj = Path(file_info.filename)
|
|
143
|
+
try: rel_path = path_obj.relative_to(root_path)
|
|
144
|
+
except ValueError: continue
|
|
145
|
+
|
|
146
|
+
parts = rel_path.parts
|
|
147
|
+
current_node = tree_root
|
|
148
|
+
current_path = root_path
|
|
149
|
+
|
|
150
|
+
# Build Folder Tree
|
|
151
|
+
for part in parts[:-1]:
|
|
152
|
+
next_path = current_path / part
|
|
153
|
+
next_path_str = str(next_path)
|
|
154
|
+
if next_path_str not in node_map:
|
|
155
|
+
new_folder = create_node(part, "folder", next_path_str)
|
|
156
|
+
current_node.children.append(new_folder)
|
|
157
|
+
node_map[next_path_str] = new_folder
|
|
158
|
+
current_node = node_map[next_path_str]
|
|
159
|
+
current_path = next_path
|
|
160
|
+
|
|
161
|
+
# Add File
|
|
162
|
+
file_node = create_node(parts[-1], "file", str(path_obj))
|
|
163
|
+
attach_file_metrics(file_node, file_info)
|
|
164
|
+
current_node.children.append(file_node)
|
|
165
|
+
|
|
166
|
+
aggregate_metrics(tree_root)
|
|
167
|
+
return tree_root
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from app.models import Node
|
|
5
|
+
|
|
6
|
+
CACHE_FILE_NAME = "codebase_mri.json"
|
|
7
|
+
|
|
8
|
+
def get_cache_path(root_path: Path) -> Path:
|
|
9
|
+
return root_path / CACHE_FILE_NAME
|
|
10
|
+
|
|
11
|
+
def save_analysis(root_path: Path, tree: Node) -> None:
|
|
12
|
+
cache_path = get_cache_path(root_path)
|
|
13
|
+
with open(cache_path, "w") as f:
|
|
14
|
+
f.write(tree.model_dump_json(indent=2, by_alias=True))
|
|
15
|
+
print(f"✅ Saved scan to {cache_path}")
|
|
16
|
+
|
|
17
|
+
def load_analysis(root_path: Path) -> Optional[Node]:
|
|
18
|
+
cache_path = get_cache_path(root_path)
|
|
19
|
+
if not cache_path.exists():
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
with open(cache_path, "r") as f:
|
|
24
|
+
data = json.load(f)
|
|
25
|
+
return Node.model_validate(data)
|
|
26
|
+
except Exception as e:
|
|
27
|
+
print(f"⚠️ Failed to load cache: {e}")
|
|
28
|
+
return None
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-duration:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-100:oklch(93.6% .032 17.717);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-900:oklch(39.6% .141 25.723);--color-green-600:oklch(62.7% .194 149.214);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-xl:36rem;--container-2xl:42rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--font-weight-semibold:600;--font-weight-bold:700;--tracking-wide:.025em;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.inset-0{inset:calc(var(--spacing)*0)}.top-full{top:100%}.right-0{right:calc(var(--spacing)*0)}.right-4{right:calc(var(--spacing)*4)}.bottom-4{bottom:calc(var(--spacing)*4)}.left-0{left:calc(var(--spacing)*0)}.z-40{z-index:40}.z-50{z-index:50}.container{width:100%}@media(min-width:40rem){.container{max-width:40rem}}@media(min-width:48rem){.container{max-width:48rem}}@media(min-width:64rem){.container{max-width:64rem}}@media(min-width:80rem){.container{max-width:80rem}}@media(min-width:96rem){.container{max-width:96rem}}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-4{margin-top:calc(var(--spacing)*4)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.ml-4{margin-left:calc(var(--spacing)*4)}.ml-auto{margin-left:auto}.flex{display:flex}.h-2{height:calc(var(--spacing)*2)}.h-\[80vh\]{height:80vh}.h-full{height:100%}.h-screen{height:100vh}.max-h-64{max-height:calc(var(--spacing)*64)}.max-h-96{max-height:calc(var(--spacing)*96)}.w-2{width:calc(var(--spacing)*2)}.w-\[90vw\]{width:90vw}.w-\[350px\]{width:350px}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-xl{max-width:var(--container-xl)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-\[16rem\]{min-width:16rem}.flex-1{flex:1}.cursor-pointer{cursor:pointer}.resize{resize:both}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.border{border-style:var(--tw-border-style);border-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-\[\#333\]{border-color:#333}.border-\[\#555\]{border-color:#555}.border-gray-600{border-color:var(--color-gray-600)}.border-gray-700{border-color:var(--color-gray-700)}.border-red-700{border-color:var(--color-red-700)}.bg-\[\#1e1e1e\]{background-color:#1e1e1e}.bg-\[\#121212\]{background-color:#121212}.bg-\[\#252526\]{background-color:#252526}.bg-black\/50{background-color:#00000080}@supports (color:color-mix(in lab,red,red)){.bg-black\/50{background-color:color-mix(in oklab,var(--color-black)50%,transparent)}}.bg-black\/60{background-color:#0009}@supports (color:color-mix(in lab,red,red)){.bg-black\/60{background-color:color-mix(in oklab,var(--color-black)60%,transparent)}}.bg-blue-600{background-color:var(--color-blue-600)}.bg-gray-700{background-color:var(--color-gray-700)}.bg-gray-800{background-color:var(--color-gray-800)}.bg-gray-900{background-color:var(--color-gray-900)}.bg-green-600{background-color:var(--color-green-600)}.bg-red-600{background-color:var(--color-red-600)}.bg-red-700{background-color:var(--color-red-700)}.bg-red-900\/70{background-color:#82181ab3}@supports (color:color-mix(in lab,red,red)){.bg-red-900\/70{background-color:color-mix(in oklab,var(--color-red-900)70%,transparent)}}.bg-red-900\/80{background-color:#82181acc}@supports (color:color-mix(in lab,red,red)){.bg-red-900\/80{background-color:color-mix(in oklab,var(--color-red-900)80%,transparent)}}.p-2{padding:calc(var(--spacing)*2)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[11px\]{font-size:11px}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.whitespace-nowrap{white-space:nowrap}.text-blue-500{color:var(--color-blue-500)}.text-gray-100{color:var(--color-gray-100)}.text-gray-200{color:var(--color-gray-200)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-red-100{color:var(--color-red-100)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.italic{font-style:italic}.opacity-0{opacity:0}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.outline-none{--tw-outline-style:none;outline-style:none}@media(hover:hover){.hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.hover\:bg-gray-600:hover{background-color:var(--color-gray-600)}.hover\:bg-gray-700:hover{background-color:var(--color-gray-700)}.hover\:bg-gray-800:hover{background-color:var(--color-gray-800)}.hover\:bg-red-600:hover{background-color:var(--color-red-600)}.hover\:text-white:hover{color:var(--color-white)}}.focus\:border-blue-500:focus{border-color:var(--color-blue-500)}}:root{color-scheme:light dark;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:Inter,system-ui,Avenir,Helvetica,Arial,sans-serif;font-weight:400;line-height:1.5}body{background-color:var(--color-gray-900);min-width:320px;min-height:100vh;color:var(--color-white);margin:0}.code-modal-content .shiki{border-radius:.5rem;margin:0;font-size:13px;line-height:.7}.code-modal-content .shiki code{counter-reset:line}.code-modal-content pre,.code-modal-content code{margin:0;padding-top:0;padding-bottom:0;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.code-modal-content .shiki .line{padding-left:2.5rem;display:block;position:relative}.code-modal-content .shiki .line:empty:after{content:" ";visibility:hidden}.code-modal-content .shiki .line:before{counter-increment:line;content:counter(line);color:#9ca3afcc;text-align:right;width:1.75rem;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.75rem;position:absolute;top:0;left:0}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-duration{syntax:"*";inherits:false}
|