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.
Files changed (307) hide show
  1. {srcly-0.1.0 → srcly-0.1.4}/PKG-INFO +1 -1
  2. srcly-0.1.4/app/.gitignore +1 -0
  3. srcly-0.1.4/app/__init__.py +9 -0
  4. srcly-0.1.4/app/config.py +29 -0
  5. srcly-0.1.4/app/main.py +73 -0
  6. srcly-0.1.4/app/models.py +21 -0
  7. srcly-0.1.4/app/routers/analysis.py +77 -0
  8. srcly-0.1.4/app/routers/files.py +72 -0
  9. srcly-0.1.4/app/run.py +87 -0
  10. srcly-0.1.4/app/services/analysis.py +167 -0
  11. srcly-0.1.4/app/services/cache.py +28 -0
  12. srcly-0.1.4/app/static/assets/index-CBBfoGZS.css +1 -0
  13. srcly-0.1.4/app/static/assets/index-DCyN-sgG.js +18 -0
  14. srcly-0.1.4/app/static/index.html +17 -0
  15. {srcly-0.1.0 → srcly-0.1.4}/pyproject.toml +2 -2
  16. srcly-0.1.0/app/static/assets/index-CIVGRltC.css +0 -1
  17. srcly-0.1.0/app/static/assets/index-KFE7Rt2Z.js +0 -18
  18. srcly-0.1.0/app/static/index.html +0 -14
  19. {srcly-0.1.0 → srcly-0.1.4}/.gitignore +0 -0
  20. {srcly-0.1.0 → srcly-0.1.4}/README.md +0 -0
  21. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/abap-BdImnpbu.js +0 -0
  22. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/actionscript-3-CfeIJUat.js +0 -0
  23. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/ada-bCR0ucgS.js +0 -0
  24. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/andromeeda-C-Jbm3Hp.js +0 -0
  25. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/angular-html-CU67Zn6k.js +0 -0
  26. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/angular-ts-BwZT4LLn.js +0 -0
  27. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/apache-Pmp26Uib.js +0 -0
  28. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/apex-DhZLUxFE.js +0 -0
  29. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/apl-dKokRX4l.js +0 -0
  30. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/applescript-Co6uUVPk.js +0 -0
  31. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/ara-BRHolxvo.js +0 -0
  32. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/asciidoc-Dv7Oe6Be.js +0 -0
  33. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/asm-D_Q5rh1f.js +0 -0
  34. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/astro-CbQHKStN.js +0 -0
  35. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/aurora-x-D-2ljcwZ.js +0 -0
  36. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/awk-DMzUqQB5.js +0 -0
  37. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/ayu-dark-Cv9koXgw.js +0 -0
  38. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/ballerina-BFfxhgS-.js +0 -0
  39. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/bat-BkioyH1T.js +0 -0
  40. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/beancount-k_qm7-4y.js +0 -0
  41. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/berry-D08WgyRC.js +0 -0
  42. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/bibtex-CHM0blh-.js +0 -0
  43. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/bicep-Bmn6On1c.js +0 -0
  44. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/blade-DVc8C-J4.js +0 -0
  45. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/bsl-BO_Y6i37.js +0 -0
  46. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/c-BIGW1oBm.js +0 -0
  47. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/cadence-Bv_4Rxtq.js +0 -0
  48. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/cairo-KRGpt6FW.js +0 -0
  49. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/catppuccin-frappe-DFWUc33u.js +0 -0
  50. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/catppuccin-latte-C9dUb6Cb.js +0 -0
  51. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/catppuccin-macchiato-DQyhUUbL.js +0 -0
  52. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/catppuccin-mocha-D87Tk5Gz.js +0 -0
  53. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/clarity-D53aC0YG.js +0 -0
  54. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/clojure-P80f7IUj.js +0 -0
  55. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/cmake-D1j8_8rp.js +0 -0
  56. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/cobol-nwyudZeR.js +0 -0
  57. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/codeowners-Bp6g37R7.js +0 -0
  58. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/codeql-DsOJ9woJ.js +0 -0
  59. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/coffee-Ch7k5sss.js +0 -0
  60. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/common-lisp-Cg-RD9OK.js +0 -0
  61. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/coq-DkFqJrB1.js +0 -0
  62. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/cpp-CofmeUqb.js +0 -0
  63. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/crystal-tKQVLTB8.js +0 -0
  64. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/csharp-CX12Zw3r.js +0 -0
  65. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/css-DPfMkruS.js +0 -0
  66. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/csv-fuZLfV_i.js +0 -0
  67. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/cue-D82EKSYY.js +0 -0
  68. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/cypher-COkxafJQ.js +0 -0
  69. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/d-85-TOEBH.js +0 -0
  70. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/dark-plus-eOWES_5F.js +0 -0
  71. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/dart-CF10PKvl.js +0 -0
  72. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/dax-CEL-wOlO.js +0 -0
  73. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/desktop-BmXAJ9_W.js +0 -0
  74. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/diff-D97Zzqfu.js +0 -0
  75. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/docker-BcOcwvcX.js +0 -0
  76. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/dotenv-Da5cRb03.js +0 -0
  77. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/dracula-BzJJZx-M.js +0 -0
  78. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/dracula-soft-BXkSAIEj.js +0 -0
  79. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/dream-maker-BtqSS_iP.js +0 -0
  80. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/edge-BkV0erSs.js +0 -0
  81. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/elixir-CDX3lj18.js +0 -0
  82. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/elm-DbKCFpqz.js +0 -0
  83. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/emacs-lisp-C9XAeP06.js +0 -0
  84. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/erb-BOJIQeun.js +0 -0
  85. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/erlang-DsQrWhSR.js +0 -0
  86. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/everforest-dark-BgDCqdQA.js +0 -0
  87. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/everforest-light-C8M2exoo.js +0 -0
  88. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/fennel-BYunw83y.js +0 -0
  89. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/fish-BvzEVeQv.js +0 -0
  90. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/fluent-C4IJs8-o.js +0 -0
  91. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/fortran-fixed-form-BZjJHVRy.js +0 -0
  92. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/fortran-free-form-D22FLkUw.js +0 -0
  93. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/fsharp-CXgrBDvD.js +0 -0
  94. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/gdresource-B7Tvp0Sc.js +0 -0
  95. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/gdscript-DTMYz4Jt.js +0 -0
  96. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/gdshader-DkwncUOv.js +0 -0
  97. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/genie-D0YGMca9.js +0 -0
  98. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/gherkin-DyxjwDmM.js +0 -0
  99. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/git-commit-F4YmCXRG.js +0 -0
  100. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/git-rebase-r7XF79zn.js +0 -0
  101. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/github-dark-DHJKELXO.js +0 -0
  102. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/github-dark-default-Cuk6v7N8.js +0 -0
  103. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/github-dark-dimmed-DH5Ifo-i.js +0 -0
  104. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/github-dark-high-contrast-E3gJ1_iC.js +0 -0
  105. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/github-light-DAi9KRSo.js +0 -0
  106. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/github-light-default-D7oLnXFd.js +0 -0
  107. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/github-light-high-contrast-BfjtVDDH.js +0 -0
  108. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/gleam-BspZqrRM.js +0 -0
  109. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/glimmer-js-Rg0-pVw9.js +0 -0
  110. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/glimmer-ts-U6CK756n.js +0 -0
  111. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/glsl-DplSGwfg.js +0 -0
  112. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/gnuplot-DdkO51Og.js +0 -0
  113. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/go-Dn2_MT6a.js +0 -0
  114. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/graphql-ChdNCCLP.js +0 -0
  115. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/groovy-gcz8RCvz.js +0 -0
  116. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/gruvbox-dark-hard-CFHQjOhq.js +0 -0
  117. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/gruvbox-dark-medium-GsRaNv29.js +0 -0
  118. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/gruvbox-dark-soft-CVdnzihN.js +0 -0
  119. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/gruvbox-light-hard-CH1njM8p.js +0 -0
  120. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/gruvbox-light-medium-DRw_LuNl.js +0 -0
  121. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/gruvbox-light-soft-hJgmCMqR.js +0 -0
  122. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/hack-CaT9iCJl.js +0 -0
  123. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/haml-B8DHNrY2.js +0 -0
  124. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/handlebars-BL8al0AC.js +0 -0
  125. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/haskell-Df6bDoY_.js +0 -0
  126. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/haxe-CzTSHFRz.js +0 -0
  127. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/hcl-BWvSN4gD.js +0 -0
  128. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/hjson-D5-asLiD.js +0 -0
  129. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/hlsl-D3lLCCz7.js +0 -0
  130. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/houston-DnULxvSX.js +0 -0
  131. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/html-GMplVEZG.js +0 -0
  132. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/html-derivative-BFtXZ54Q.js +0 -0
  133. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/http-jrhK8wxY.js +0 -0
  134. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/hurl-irOxFIW8.js +0 -0
  135. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/hxml-Bvhsp5Yf.js +0 -0
  136. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/hy-DFXneXwc.js +0 -0
  137. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/imba-DGztddWO.js +0 -0
  138. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/ini-BEwlwnbL.js +0 -0
  139. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/java-CylS5w8V.js +0 -0
  140. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/javascript-wDzz0qaB.js +0 -0
  141. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/jinja-4LBKfQ-Z.js +0 -0
  142. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/jison-wvAkD_A8.js +0 -0
  143. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/json-Cp-IABpG.js +0 -0
  144. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/json5-C9tS-k6U.js +0 -0
  145. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/jsonc-Des-eS-w.js +0 -0
  146. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/jsonl-DcaNXYhu.js +0 -0
  147. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/jsonnet-DFQXde-d.js +0 -0
  148. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/jssm-C2t-YnRu.js +0 -0
  149. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/jsx-g9-lgVsj.js +0 -0
  150. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/julia-C8NyazO9.js +0 -0
  151. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/kanagawa-dragon-CkXjmgJE.js +0 -0
  152. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/kanagawa-lotus-CfQXZHmo.js +0 -0
  153. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/kanagawa-wave-DWedfzmr.js +0 -0
  154. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/kdl-DV7GczEv.js +0 -0
  155. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/kotlin-BdnUsdx6.js +0 -0
  156. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/kusto-BvAqAH-y.js +0 -0
  157. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/laserwave-DUszq2jm.js +0 -0
  158. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/latex-BUKiar2Z.js +0 -0
  159. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/lean-DP1Csr6i.js +0 -0
  160. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/less-B1dDrJ26.js +0 -0
  161. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/light-plus-B7mTdjB0.js +0 -0
  162. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/liquid-DYVedYrR.js +0 -0
  163. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/llvm-BtvRca6l.js +0 -0
  164. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/log-2UxHyX5q.js +0 -0
  165. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/logo-BtOb2qkB.js +0 -0
  166. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/lua-BbnMAYS6.js +0 -0
  167. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/luau-CXu1NL6O.js +0 -0
  168. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/make-CHLpvVh8.js +0 -0
  169. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/markdown-Cvjx9yec.js +0 -0
  170. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/marko-CPi9NSCl.js +0 -0
  171. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/material-theme-D5KoaKCx.js +0 -0
  172. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/material-theme-darker-BfHTSMKl.js +0 -0
  173. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/material-theme-lighter-B0m2ddpp.js +0 -0
  174. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/material-theme-ocean-CyktbL80.js +0 -0
  175. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/material-theme-palenight-Csfq5Kiy.js +0 -0
  176. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/matlab-D7o27uSR.js +0 -0
  177. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/mdc-DUICxH0z.js +0 -0
  178. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/mdx-Cmh6b_Ma.js +0 -0
  179. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/mermaid-DKYwYmdq.js +0 -0
  180. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/min-dark-CafNBF8u.js +0 -0
  181. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/min-light-CTRr51gU.js +0 -0
  182. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/mipsasm-CKIfxQSi.js +0 -0
  183. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/mojo-1DNp92w6.js +0 -0
  184. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/monokai-D4h5O-jR.js +0 -0
  185. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/move-Bu9oaDYs.js +0 -0
  186. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/narrat-DRg8JJMk.js +0 -0
  187. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/nextflow-CUEJCptM.js +0 -0
  188. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/nginx-DknmC5AR.js +0 -0
  189. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/night-owl-C39BiMTA.js +0 -0
  190. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/nim-CVrawwO9.js +0 -0
  191. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/nix-BbRYJGeE.js +0 -0
  192. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/nord-Ddv68eIx.js +0 -0
  193. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/nushell-C-sUppwS.js +0 -0
  194. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/objective-c-DXmwc3jG.js +0 -0
  195. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/objective-cpp-CLxacb5B.js +0 -0
  196. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/ocaml-C0hk2d4L.js +0 -0
  197. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/one-dark-pro-DVMEJ2y_.js +0 -0
  198. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/one-light-PoHY5YXO.js +0 -0
  199. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/pascal-D93ZcfNL.js +0 -0
  200. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/perl-C0TMdlhV.js +0 -0
  201. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/php-CDn_0X-4.js +0 -0
  202. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/pkl-u5AG7uiY.js +0 -0
  203. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/plastic-3e1v2bzS.js +0 -0
  204. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/plsql-ChMvpjG-.js +0 -0
  205. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/po-BTJTHyun.js +0 -0
  206. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/poimandres-CS3Unz2-.js +0 -0
  207. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/polar-C0HS_06l.js +0 -0
  208. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/postcss-CXtECtnM.js +0 -0
  209. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/powerquery-CEu0bR-o.js +0 -0
  210. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/powershell-Dpen1YoG.js +0 -0
  211. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/prisma-Dd19v3D-.js +0 -0
  212. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/prolog-CbFg5uaA.js +0 -0
  213. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/proto-DyJlTyXw.js +0 -0
  214. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/pug-CGlum2m_.js +0 -0
  215. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/puppet-BMWR74SV.js +0 -0
  216. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/purescript-CklMAg4u.js +0 -0
  217. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/python-B6aJPvgy.js +0 -0
  218. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/qml-3beO22l8.js +0 -0
  219. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/qmldir-C8lEn-DE.js +0 -0
  220. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/qss-IeuSbFQv.js +0 -0
  221. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/r-DiinP2Uv.js +0 -0
  222. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/racket-BqYA7rlc.js +0 -0
  223. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/raku-DXvB9xmW.js +0 -0
  224. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/razor-WgofotgN.js +0 -0
  225. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/red-bN70gL4F.js +0 -0
  226. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/reg-C-SQnVFl.js +0 -0
  227. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/regexp-CDVJQ6XC.js +0 -0
  228. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/rel-C3B-1QV4.js +0 -0
  229. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/riscv-BM1_JUlF.js +0 -0
  230. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/rose-pine-BHrmToEH.js +0 -0
  231. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/rose-pine-dawn-CnK8MTSM.js +0 -0
  232. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/rose-pine-moon-NleAzG8P.js +0 -0
  233. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/rosmsg-BJDFO7_C.js +0 -0
  234. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/rst-B0xPkSld.js +0 -0
  235. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/ruby-BvKwtOVI.js +0 -0
  236. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/rust-B1yitclQ.js +0 -0
  237. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/sas-cz2c8ADy.js +0 -0
  238. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/sass-Cj5Yp3dK.js +0 -0
  239. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/scala-C151Ov-r.js +0 -0
  240. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/scheme-C98Dy4si.js +0 -0
  241. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/scss-OYdSNvt2.js +0 -0
  242. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/sdbl-DVxCFoDh.js +0 -0
  243. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/shaderlab-Dg9Lc6iA.js +0 -0
  244. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/shellscript-Yzrsuije.js +0 -0
  245. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/shellsession-BADoaaVG.js +0 -0
  246. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/slack-dark-BthQWCQV.js +0 -0
  247. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/slack-ochin-DqwNpetd.js +0 -0
  248. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/smalltalk-BERRCDM3.js +0 -0
  249. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/snazzy-light-Bw305WKR.js +0 -0
  250. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/solarized-dark-DXbdFlpD.js +0 -0
  251. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/solarized-light-L9t79GZl.js +0 -0
  252. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/solidity-BbcW6ACK.js +0 -0
  253. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/soy-Brmx7dQM.js +0 -0
  254. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/sparql-rVzFXLq3.js +0 -0
  255. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/splunk-BtCnVYZw.js +0 -0
  256. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/sql-BLtJtn59.js +0 -0
  257. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/ssh-config-_ykCGR6B.js +0 -0
  258. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/stata-BH5u7GGu.js +0 -0
  259. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/stylus-BEDo0Tqx.js +0 -0
  260. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/svelte-3Dk4HxPD.js +0 -0
  261. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/swift-Dg5xB15N.js +0 -0
  262. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/synthwave-84-CbfX1IO0.js +0 -0
  263. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/system-verilog-CnnmHF94.js +0 -0
  264. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/systemd-4A_iFExJ.js +0 -0
  265. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/talonscript-CkByrt1z.js +0 -0
  266. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/tasl-QIJgUcNo.js +0 -0
  267. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/tcl-dwOrl1Do.js +0 -0
  268. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/templ-W15q3VgB.js +0 -0
  269. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/terraform-BETggiCN.js +0 -0
  270. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/tex-Cppo0RY3.js +0 -0
  271. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/tokyo-night-hegEt444.js +0 -0
  272. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/toml-vGWfd6FD.js +0 -0
  273. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/ts-tags-zn1MmPIZ.js +0 -0
  274. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/tsv-B_m7g4N7.js +0 -0
  275. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/tsx-COt5Ahok.js +0 -0
  276. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/turtle-BsS91CYL.js +0 -0
  277. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/twig-CO9l9SDP.js +0 -0
  278. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/typescript-BPQ3VLAy.js +0 -0
  279. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/typespec-Df68jz8_.js +0 -0
  280. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/typst-DHCkPAjA.js +0 -0
  281. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/v-BcVCzyr7.js +0 -0
  282. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/vala-CsfeWuGM.js +0 -0
  283. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/vb-D17OF-Vu.js +0 -0
  284. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/verilog-BQ8w6xss.js +0 -0
  285. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/vesper-DU1UobuO.js +0 -0
  286. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/vhdl-CeAyd5Ju.js +0 -0
  287. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/viml-CJc9bBzg.js +0 -0
  288. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/vitesse-black-Bkuqu6BP.js +0 -0
  289. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/vitesse-dark-D0r3Knsf.js +0 -0
  290. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/vitesse-light-CVO1_9PV.js +0 -0
  291. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/vue-CCoi5OLL.js +0 -0
  292. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/vue-html-DAAvJJDi.js +0 -0
  293. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/vue-vine-_Ih-lPRR.js +0 -0
  294. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/vyper-CDx5xZoG.js +0 -0
  295. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/wasm-CG6Dc4jp.js +0 -0
  296. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/wasm-MzD3tlZU.js +0 -0
  297. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/wenyan-BV7otONQ.js +0 -0
  298. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/wgsl-Dx-B1_4e.js +0 -0
  299. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/wikitext-BhOHFoWU.js +0 -0
  300. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/wit-5i3qLPDT.js +0 -0
  301. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/wolfram-lXgVvXCa.js +0 -0
  302. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/xml-sdJ4AIDG.js +0 -0
  303. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/xsl-CtQFsRM5.js +0 -0
  304. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/yaml-Buea-lGh.js +0 -0
  305. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/zenscript-DVFEvuxE.js +0 -0
  306. {srcly-0.1.0 → srcly-0.1.4}/app/static/assets/zig-VOosw3JB.js +0 -0
  307. {srcly-0.1.0 → srcly-0.1.4}/app/static/vite.svg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: srcly
3
- Version: 0.1.0
3
+ Version: 0.1.4
4
4
  Summary: Interactive codebase treemap and metrics viewer.
5
5
  Requires-Python: >=3.13
6
6
  Requires-Dist: fastapi>=0.121.3
@@ -0,0 +1 @@
1
+ static
@@ -0,0 +1,9 @@
1
+ """
2
+ Srcly FastAPI application package.
3
+
4
+ This file marks the `app` directory as a Python package so that the
5
+ `app.run:main` console script entry point works correctly when the
6
+ project is built and installed from its wheel.
7
+ """
8
+
9
+
@@ -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
+ }
@@ -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}