pythinker-code 2.0.0__py3-none-any.whl

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 (731) hide show
  1. pythinker_code/CHANGELOG.md +16 -0
  2. pythinker_code/__init__.py +0 -0
  3. pythinker_code/__main__.py +92 -0
  4. pythinker_code/acp/AGENTS.md +93 -0
  5. pythinker_code/acp/__init__.py +13 -0
  6. pythinker_code/acp/convert.py +128 -0
  7. pythinker_code/acp/host.py +298 -0
  8. pythinker_code/acp/mcp.py +46 -0
  9. pythinker_code/acp/server.py +497 -0
  10. pythinker_code/acp/session.py +496 -0
  11. pythinker_code/acp/tools.py +167 -0
  12. pythinker_code/acp/types.py +13 -0
  13. pythinker_code/acp/version.py +45 -0
  14. pythinker_code/agents/default/agent.yaml +36 -0
  15. pythinker_code/agents/default/coder.yaml +25 -0
  16. pythinker_code/agents/default/explore.yaml +46 -0
  17. pythinker_code/agents/default/plan.yaml +30 -0
  18. pythinker_code/agents/default/system.md +164 -0
  19. pythinker_code/agents/okabe/agent.yaml +22 -0
  20. pythinker_code/agentspec.py +163 -0
  21. pythinker_code/app.py +820 -0
  22. pythinker_code/approval_runtime/__init__.py +29 -0
  23. pythinker_code/approval_runtime/models.py +42 -0
  24. pythinker_code/approval_runtime/runtime.py +235 -0
  25. pythinker_code/auth/__init__.py +25 -0
  26. pythinker_code/auth/anthropic_direct.py +207 -0
  27. pythinker_code/auth/deepseek.py +192 -0
  28. pythinker_code/auth/lm_studio.py +418 -0
  29. pythinker_code/auth/minimax.py +203 -0
  30. pythinker_code/auth/oauth.py +1122 -0
  31. pythinker_code/auth/ollama.py +293 -0
  32. pythinker_code/auth/openai.py +771 -0
  33. pythinker_code/auth/opencode_go.py +203 -0
  34. pythinker_code/auth/openrouter.py +225 -0
  35. pythinker_code/auth/platforms.py +466 -0
  36. pythinker_code/background/__init__.py +36 -0
  37. pythinker_code/background/agent_runner.py +231 -0
  38. pythinker_code/background/ids.py +19 -0
  39. pythinker_code/background/manager.py +650 -0
  40. pythinker_code/background/models.py +105 -0
  41. pythinker_code/background/store.py +237 -0
  42. pythinker_code/background/summary.py +66 -0
  43. pythinker_code/background/worker.py +209 -0
  44. pythinker_code/cli/__init__.py +1326 -0
  45. pythinker_code/cli/__main__.py +19 -0
  46. pythinker_code/cli/_lazy_group.py +238 -0
  47. pythinker_code/cli/export.py +322 -0
  48. pythinker_code/cli/info.py +62 -0
  49. pythinker_code/cli/mcp.py +349 -0
  50. pythinker_code/cli/plugin.py +351 -0
  51. pythinker_code/cli/toad.py +74 -0
  52. pythinker_code/cli/vis.py +38 -0
  53. pythinker_code/cli/web.py +80 -0
  54. pythinker_code/config.py +453 -0
  55. pythinker_code/constant.py +33 -0
  56. pythinker_code/exception.py +43 -0
  57. pythinker_code/hooks/__init__.py +4 -0
  58. pythinker_code/hooks/config.py +34 -0
  59. pythinker_code/hooks/engine.py +371 -0
  60. pythinker_code/hooks/events.py +190 -0
  61. pythinker_code/hooks/runner.py +89 -0
  62. pythinker_code/llm.py +412 -0
  63. pythinker_code/metadata.py +79 -0
  64. pythinker_code/notifications/__init__.py +33 -0
  65. pythinker_code/notifications/llm.py +77 -0
  66. pythinker_code/notifications/manager.py +145 -0
  67. pythinker_code/notifications/models.py +50 -0
  68. pythinker_code/notifications/notifier.py +41 -0
  69. pythinker_code/notifications/store.py +118 -0
  70. pythinker_code/notifications/wire.py +21 -0
  71. pythinker_code/plugin/__init__.py +124 -0
  72. pythinker_code/plugin/manager.py +153 -0
  73. pythinker_code/plugin/tool.py +173 -0
  74. pythinker_code/prompts/__init__.py +6 -0
  75. pythinker_code/prompts/compact.md +73 -0
  76. pythinker_code/prompts/init.md +21 -0
  77. pythinker_code/py.typed +0 -0
  78. pythinker_code/session.py +319 -0
  79. pythinker_code/session_fork.py +325 -0
  80. pythinker_code/session_state.py +132 -0
  81. pythinker_code/share.py +14 -0
  82. pythinker_code/skill/__init__.py +727 -0
  83. pythinker_code/skill/flow/__init__.py +99 -0
  84. pythinker_code/skill/flow/d2.py +482 -0
  85. pythinker_code/skill/flow/mermaid.py +266 -0
  86. pythinker_code/skills/pythinker-code-help/SKILL.md +54 -0
  87. pythinker_code/skills/skill-creator/SKILL.md +367 -0
  88. pythinker_code/soul/__init__.py +304 -0
  89. pythinker_code/soul/agent.py +520 -0
  90. pythinker_code/soul/approval.py +267 -0
  91. pythinker_code/soul/btw.py +214 -0
  92. pythinker_code/soul/compaction.py +189 -0
  93. pythinker_code/soul/context.py +339 -0
  94. pythinker_code/soul/denwarenji.py +39 -0
  95. pythinker_code/soul/dynamic_injection.py +84 -0
  96. pythinker_code/soul/dynamic_injections/__init__.py +0 -0
  97. pythinker_code/soul/dynamic_injections/auto_mode.py +72 -0
  98. pythinker_code/soul/dynamic_injections/plan_mode.py +239 -0
  99. pythinker_code/soul/message.py +92 -0
  100. pythinker_code/soul/pythinkersoul.py +1613 -0
  101. pythinker_code/soul/slash.py +340 -0
  102. pythinker_code/soul/toolset.py +788 -0
  103. pythinker_code/subagents/__init__.py +21 -0
  104. pythinker_code/subagents/builder.py +42 -0
  105. pythinker_code/subagents/core.py +86 -0
  106. pythinker_code/subagents/git_context.py +172 -0
  107. pythinker_code/subagents/models.py +54 -0
  108. pythinker_code/subagents/output.py +71 -0
  109. pythinker_code/subagents/registry.py +28 -0
  110. pythinker_code/subagents/runner.py +428 -0
  111. pythinker_code/subagents/store.py +196 -0
  112. pythinker_code/telemetry/__init__.py +211 -0
  113. pythinker_code/telemetry/config.py +54 -0
  114. pythinker_code/telemetry/crash.py +157 -0
  115. pythinker_code/telemetry/metrics.py +208 -0
  116. pythinker_code/telemetry/otel.py +240 -0
  117. pythinker_code/telemetry/sentry.py +167 -0
  118. pythinker_code/telemetry/sink.py +189 -0
  119. pythinker_code/tools/AGENTS.md +6 -0
  120. pythinker_code/tools/__init__.py +105 -0
  121. pythinker_code/tools/agent/__init__.py +277 -0
  122. pythinker_code/tools/agent/description.md +41 -0
  123. pythinker_code/tools/ask_user/__init__.py +159 -0
  124. pythinker_code/tools/ask_user/description.md +19 -0
  125. pythinker_code/tools/background/__init__.py +318 -0
  126. pythinker_code/tools/background/list.md +10 -0
  127. pythinker_code/tools/background/output.md +11 -0
  128. pythinker_code/tools/background/stop.md +8 -0
  129. pythinker_code/tools/display.py +46 -0
  130. pythinker_code/tools/dmail/__init__.py +38 -0
  131. pythinker_code/tools/dmail/dmail.md +17 -0
  132. pythinker_code/tools/file/__init__.py +30 -0
  133. pythinker_code/tools/file/glob.md +17 -0
  134. pythinker_code/tools/file/glob.py +160 -0
  135. pythinker_code/tools/file/grep.md +6 -0
  136. pythinker_code/tools/file/grep_local.py +589 -0
  137. pythinker_code/tools/file/plan_mode.py +45 -0
  138. pythinker_code/tools/file/read.md +16 -0
  139. pythinker_code/tools/file/read.py +300 -0
  140. pythinker_code/tools/file/read_media.md +24 -0
  141. pythinker_code/tools/file/read_media.py +217 -0
  142. pythinker_code/tools/file/replace.md +7 -0
  143. pythinker_code/tools/file/replace.py +195 -0
  144. pythinker_code/tools/file/utils.py +257 -0
  145. pythinker_code/tools/file/write.md +5 -0
  146. pythinker_code/tools/file/write.py +177 -0
  147. pythinker_code/tools/plan/__init__.py +327 -0
  148. pythinker_code/tools/plan/description.md +29 -0
  149. pythinker_code/tools/plan/enter.py +190 -0
  150. pythinker_code/tools/plan/enter_description.md +35 -0
  151. pythinker_code/tools/plan/heroes.py +277 -0
  152. pythinker_code/tools/shell/__init__.py +253 -0
  153. pythinker_code/tools/shell/bash.md +35 -0
  154. pythinker_code/tools/shell/powershell.md +30 -0
  155. pythinker_code/tools/test.py +55 -0
  156. pythinker_code/tools/think/__init__.py +21 -0
  157. pythinker_code/tools/think/think.md +1 -0
  158. pythinker_code/tools/todo/__init__.py +168 -0
  159. pythinker_code/tools/todo/set_todo_list.md +23 -0
  160. pythinker_code/tools/utils.py +199 -0
  161. pythinker_code/tools/web/__init__.py +4 -0
  162. pythinker_code/tools/web/fetch.md +1 -0
  163. pythinker_code/tools/web/fetch.py +189 -0
  164. pythinker_code/tools/web/search.md +1 -0
  165. pythinker_code/tools/web/search.py +163 -0
  166. pythinker_code/ui/__init__.py +0 -0
  167. pythinker_code/ui/acp/__init__.py +99 -0
  168. pythinker_code/ui/print/__init__.py +474 -0
  169. pythinker_code/ui/print/visualize.py +185 -0
  170. pythinker_code/ui/shell/__init__.py +1696 -0
  171. pythinker_code/ui/shell/console.py +109 -0
  172. pythinker_code/ui/shell/debug.py +190 -0
  173. pythinker_code/ui/shell/echo.py +17 -0
  174. pythinker_code/ui/shell/export_import.py +117 -0
  175. pythinker_code/ui/shell/keyboard.py +300 -0
  176. pythinker_code/ui/shell/mcp_status.py +113 -0
  177. pythinker_code/ui/shell/model_picker.py +318 -0
  178. pythinker_code/ui/shell/oauth.py +272 -0
  179. pythinker_code/ui/shell/placeholders.py +531 -0
  180. pythinker_code/ui/shell/prompt.py +2278 -0
  181. pythinker_code/ui/shell/replay.py +215 -0
  182. pythinker_code/ui/shell/session_picker.py +227 -0
  183. pythinker_code/ui/shell/setup.py +212 -0
  184. pythinker_code/ui/shell/slash.py +898 -0
  185. pythinker_code/ui/shell/startup.py +32 -0
  186. pythinker_code/ui/shell/task_browser.py +486 -0
  187. pythinker_code/ui/shell/update.py +350 -0
  188. pythinker_code/ui/shell/usage.py +291 -0
  189. pythinker_code/ui/shell/usage_adapters/__init__.py +50 -0
  190. pythinker_code/ui/shell/usage_adapters/anthropic_admin.py +233 -0
  191. pythinker_code/ui/shell/usage_adapters/base.py +72 -0
  192. pythinker_code/ui/shell/usage_adapters/deepseek.py +137 -0
  193. pythinker_code/ui/shell/usage_adapters/minimax.py +236 -0
  194. pythinker_code/ui/shell/usage_adapters/openai_admin.py +225 -0
  195. pythinker_code/ui/shell/usage_adapters/openai_chatgpt.py +241 -0
  196. pythinker_code/ui/shell/usage_adapters/opencode_go.py +232 -0
  197. pythinker_code/ui/shell/usage_adapters/openrouter.py +105 -0
  198. pythinker_code/ui/shell/usage_adapters/pythinker.py +189 -0
  199. pythinker_code/ui/shell/usage_adapters/pythinker_ai.py +50 -0
  200. pythinker_code/ui/shell/usage_render.py +150 -0
  201. pythinker_code/ui/shell/visualize/__init__.py +165 -0
  202. pythinker_code/ui/shell/visualize/_approval_panel.py +505 -0
  203. pythinker_code/ui/shell/visualize/_blocks.py +629 -0
  204. pythinker_code/ui/shell/visualize/_btw_panel.py +224 -0
  205. pythinker_code/ui/shell/visualize/_input_router.py +48 -0
  206. pythinker_code/ui/shell/visualize/_interactive.py +523 -0
  207. pythinker_code/ui/shell/visualize/_live_view.py +826 -0
  208. pythinker_code/ui/shell/visualize/_question_panel.py +586 -0
  209. pythinker_code/ui/theme.py +241 -0
  210. pythinker_code/usage_ratelimit_cache.py +175 -0
  211. pythinker_code/utils/__init__.py +0 -0
  212. pythinker_code/utils/aiohttp.py +24 -0
  213. pythinker_code/utils/aioqueue.py +72 -0
  214. pythinker_code/utils/broadcast.py +37 -0
  215. pythinker_code/utils/changelog.py +108 -0
  216. pythinker_code/utils/clipboard.py +246 -0
  217. pythinker_code/utils/datetime.py +64 -0
  218. pythinker_code/utils/diff.py +135 -0
  219. pythinker_code/utils/editor.py +91 -0
  220. pythinker_code/utils/environment.py +73 -0
  221. pythinker_code/utils/envvar.py +22 -0
  222. pythinker_code/utils/export.py +696 -0
  223. pythinker_code/utils/file_filter.py +375 -0
  224. pythinker_code/utils/frontmatter.py +70 -0
  225. pythinker_code/utils/io.py +27 -0
  226. pythinker_code/utils/logging.py +146 -0
  227. pythinker_code/utils/media_tags.py +29 -0
  228. pythinker_code/utils/message.py +24 -0
  229. pythinker_code/utils/path.py +199 -0
  230. pythinker_code/utils/proctitle.py +33 -0
  231. pythinker_code/utils/proxy.py +31 -0
  232. pythinker_code/utils/pyinstaller.py +45 -0
  233. pythinker_code/utils/rich/__init__.py +33 -0
  234. pythinker_code/utils/rich/columns.py +99 -0
  235. pythinker_code/utils/rich/diff_render.py +481 -0
  236. pythinker_code/utils/rich/markdown.py +900 -0
  237. pythinker_code/utils/rich/markdown_sample.md +108 -0
  238. pythinker_code/utils/rich/markdown_sample_short.md +2 -0
  239. pythinker_code/utils/rich/syntax.py +114 -0
  240. pythinker_code/utils/sensitive.py +54 -0
  241. pythinker_code/utils/server.py +121 -0
  242. pythinker_code/utils/signals.py +43 -0
  243. pythinker_code/utils/slashcmd.py +124 -0
  244. pythinker_code/utils/string.py +41 -0
  245. pythinker_code/utils/subprocess_env.py +73 -0
  246. pythinker_code/utils/term.py +168 -0
  247. pythinker_code/utils/typing.py +20 -0
  248. pythinker_code/vis/__init__.py +0 -0
  249. pythinker_code/vis/api/__init__.py +5 -0
  250. pythinker_code/vis/api/sessions.py +687 -0
  251. pythinker_code/vis/api/statistics.py +209 -0
  252. pythinker_code/vis/api/system.py +19 -0
  253. pythinker_code/vis/app.py +175 -0
  254. pythinker_code/vis/static/assets/highlighted-body-B3W2YXNL-D2MTYyJz.js +1 -0
  255. pythinker_code/vis/static/assets/index-CezafTt_.js +185 -0
  256. pythinker_code/vis/static/assets/index-DSRInNnm.css +1 -0
  257. pythinker_code/vis/static/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  258. pythinker_code/vis/static/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  259. pythinker_code/vis/static/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  260. pythinker_code/vis/static/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  261. pythinker_code/vis/static/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  262. pythinker_code/vis/static/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  263. pythinker_code/vis/static/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  264. pythinker_code/vis/static/index.html +17 -0
  265. pythinker_code/web/__init__.py +5 -0
  266. pythinker_code/web/api/__init__.py +15 -0
  267. pythinker_code/web/api/config.py +208 -0
  268. pythinker_code/web/api/open_in.py +199 -0
  269. pythinker_code/web/api/sessions.py +1225 -0
  270. pythinker_code/web/app.py +451 -0
  271. pythinker_code/web/auth.py +191 -0
  272. pythinker_code/web/models.py +98 -0
  273. pythinker_code/web/runner/__init__.py +5 -0
  274. pythinker_code/web/runner/messages.py +57 -0
  275. pythinker_code/web/runner/process.py +754 -0
  276. pythinker_code/web/runner/worker.py +97 -0
  277. pythinker_code/web/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  278. pythinker_code/web/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  279. pythinker_code/web/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  280. pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  281. pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  282. pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  283. pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  284. pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  285. pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  286. pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  287. pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  288. pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  289. pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  290. pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  291. pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  292. pythinker_code/web/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  293. pythinker_code/web/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  294. pythinker_code/web/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  295. pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  296. pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  297. pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  298. pythinker_code/web/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  299. pythinker_code/web/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  300. pythinker_code/web/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  301. pythinker_code/web/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  302. pythinker_code/web/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  303. pythinker_code/web/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  304. pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  305. pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  306. pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  307. pythinker_code/web/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  308. pythinker_code/web/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  309. pythinker_code/web/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  310. pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  311. pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  312. pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  313. pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  314. pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  315. pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  316. pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  317. pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  318. pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  319. pythinker_code/web/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  320. pythinker_code/web/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  321. pythinker_code/web/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  322. pythinker_code/web/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  323. pythinker_code/web/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  324. pythinker_code/web/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  325. pythinker_code/web/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  326. pythinker_code/web/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  327. pythinker_code/web/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  328. pythinker_code/web/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  329. pythinker_code/web/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  330. pythinker_code/web/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  331. pythinker_code/web/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  332. pythinker_code/web/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  333. pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  334. pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  335. pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  336. pythinker_code/web/static/assets/_baseUniq--dyU3g5v.js +1 -0
  337. pythinker_code/web/static/assets/abap-BdImnpbu.js +1 -0
  338. pythinker_code/web/static/assets/actionscript-3-CfeIJUat.js +1 -0
  339. pythinker_code/web/static/assets/ada-bCR0ucgS.js +1 -0
  340. pythinker_code/web/static/assets/andromeeda-C-Jbm3Hp.js +1 -0
  341. pythinker_code/web/static/assets/angular-html-CU67Zn6k.js +1 -0
  342. pythinker_code/web/static/assets/angular-ts-BwZT4LLn.js +1 -0
  343. pythinker_code/web/static/assets/apache-Pmp26Uib.js +1 -0
  344. pythinker_code/web/static/assets/apex-D8_7TLub.js +1 -0
  345. pythinker_code/web/static/assets/apl-dKokRX4l.js +1 -0
  346. pythinker_code/web/static/assets/applescript-Co6uUVPk.js +1 -0
  347. pythinker_code/web/static/assets/ara-BRHolxvo.js +1 -0
  348. pythinker_code/web/static/assets/arc-DkMjLpYa.js +1 -0
  349. pythinker_code/web/static/assets/architectureDiagram-VXUJARFQ-CHWVaGo9.js +36 -0
  350. pythinker_code/web/static/assets/asciidoc-Dv7Oe6Be.js +1 -0
  351. pythinker_code/web/static/assets/asm-D_Q5rh1f.js +1 -0
  352. pythinker_code/web/static/assets/astro-CbQHKStN.js +1 -0
  353. pythinker_code/web/static/assets/aurora-x-D-2ljcwZ.js +1 -0
  354. pythinker_code/web/static/assets/awk-DMzUqQB5.js +1 -0
  355. pythinker_code/web/static/assets/ayu-dark-CmMr59Fi.js +1 -0
  356. pythinker_code/web/static/assets/ballerina-BFfxhgS-.js +1 -0
  357. pythinker_code/web/static/assets/bat-BkioyH1T.js +1 -0
  358. pythinker_code/web/static/assets/beancount-k_qm7-4y.js +1 -0
  359. pythinker_code/web/static/assets/berry-uYugtg8r.js +1 -0
  360. pythinker_code/web/static/assets/bibtex-CHM0blh-.js +1 -0
  361. pythinker_code/web/static/assets/bicep-Bmn6On1c.js +1 -0
  362. pythinker_code/web/static/assets/blade-D4QpJJKB.js +1 -0
  363. pythinker_code/web/static/assets/blockDiagram-VD42YOAC-DzdKe497.js +122 -0
  364. pythinker_code/web/static/assets/bsl-BO_Y6i37.js +1 -0
  365. pythinker_code/web/static/assets/c-BIGW1oBm.js +1 -0
  366. pythinker_code/web/static/assets/c3-VCDPK7BO.js +1 -0
  367. pythinker_code/web/static/assets/c4Diagram-YG6GDRKO-D84Blotg.js +10 -0
  368. pythinker_code/web/static/assets/cadence-Bv_4Rxtq.js +1 -0
  369. pythinker_code/web/static/assets/cairo-KRGpt6FW.js +1 -0
  370. pythinker_code/web/static/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  371. pythinker_code/web/static/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  372. pythinker_code/web/static/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  373. pythinker_code/web/static/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  374. pythinker_code/web/static/assets/channel-CllSjjdl.js +1 -0
  375. pythinker_code/web/static/assets/chunk-4BX2VUAB-C9w8wleE.js +1 -0
  376. pythinker_code/web/static/assets/chunk-55IACEB6-YlYJ8HnF.js +1 -0
  377. pythinker_code/web/static/assets/chunk-B4BG7PRW-Bwtz_AHU.js +165 -0
  378. pythinker_code/web/static/assets/chunk-DI55MBZ5-BbEHkl8h.js +220 -0
  379. pythinker_code/web/static/assets/chunk-FMBD7UC4-BKPbvjLC.js +15 -0
  380. pythinker_code/web/static/assets/chunk-QN33PNHL-D73dQvKf.js +1 -0
  381. pythinker_code/web/static/assets/chunk-QZHKN3VN-zGiLKes_.js +1 -0
  382. pythinker_code/web/static/assets/chunk-TZMSLE5B-LHJCi2fy.js +1 -0
  383. pythinker_code/web/static/assets/clarity-D53aC0YG.js +1 -0
  384. pythinker_code/web/static/assets/classDiagram-2ON5EDUG-vX27iZwa.js +1 -0
  385. pythinker_code/web/static/assets/classDiagram-v2-WZHVMYZB-vX27iZwa.js +1 -0
  386. pythinker_code/web/static/assets/clojure-P80f7IUj.js +1 -0
  387. pythinker_code/web/static/assets/clone-DYBkaPm2.js +1 -0
  388. pythinker_code/web/static/assets/cmake-D1j8_8rp.js +1 -0
  389. pythinker_code/web/static/assets/cobol-nwyudZeR.js +1 -0
  390. pythinker_code/web/static/assets/code-block-IT6T5CEO-NtKViZGl.js +2 -0
  391. pythinker_code/web/static/assets/codeowners-Bp6g37R7.js +1 -0
  392. pythinker_code/web/static/assets/codeql-DsOJ9woJ.js +1 -0
  393. pythinker_code/web/static/assets/coffee-Ch7k5sss.js +1 -0
  394. pythinker_code/web/static/assets/common-lisp-Cg-RD9OK.js +1 -0
  395. pythinker_code/web/static/assets/coq-DkFqJrB1.js +1 -0
  396. pythinker_code/web/static/assets/cose-bilkent-S5V4N54A-DialjZpd.js +1 -0
  397. pythinker_code/web/static/assets/cpp-CofmeUqb.js +1 -0
  398. pythinker_code/web/static/assets/crystal-tKQVLTB8.js +1 -0
  399. pythinker_code/web/static/assets/csharp-K5feNrxe.js +1 -0
  400. pythinker_code/web/static/assets/css-DPfMkruS.js +1 -0
  401. pythinker_code/web/static/assets/csv-fuZLfV_i.js +1 -0
  402. pythinker_code/web/static/assets/cue-D82EKSYY.js +1 -0
  403. pythinker_code/web/static/assets/cypher-COkxafJQ.js +1 -0
  404. pythinker_code/web/static/assets/cytoscape.esm-C_Fzpdck.js +321 -0
  405. pythinker_code/web/static/assets/d-85-TOEBH.js +1 -0
  406. pythinker_code/web/static/assets/dagre-6UL2VRFP-DfuvkZZ7.js +4 -0
  407. pythinker_code/web/static/assets/dark-plus-C3mMm8J8.js +1 -0
  408. pythinker_code/web/static/assets/dart-CF10PKvl.js +1 -0
  409. pythinker_code/web/static/assets/dax-CEL-wOlO.js +1 -0
  410. pythinker_code/web/static/assets/defaultLocale-DX6XiGOO.js +1 -0
  411. pythinker_code/web/static/assets/desktop-BmXAJ9_W.js +1 -0
  412. pythinker_code/web/static/assets/diagram-PSM6KHXK-DLGctX3v.js +24 -0
  413. pythinker_code/web/static/assets/diagram-QEK2KX5R-DnxN6S0F.js +43 -0
  414. pythinker_code/web/static/assets/diagram-S2PKOQOG-Caq_Set9.js +24 -0
  415. pythinker_code/web/static/assets/diff-D97Zzqfu.js +1 -0
  416. pythinker_code/web/static/assets/docker-BcOcwvcX.js +1 -0
  417. pythinker_code/web/static/assets/dotenv-Da5cRb03.js +1 -0
  418. pythinker_code/web/static/assets/dracula-BzJJZx-M.js +1 -0
  419. pythinker_code/web/static/assets/dracula-soft-BXkSAIEj.js +1 -0
  420. pythinker_code/web/static/assets/dream-maker-BtqSS_iP.js +1 -0
  421. pythinker_code/web/static/assets/edge-BkV0erSs.js +1 -0
  422. pythinker_code/web/static/assets/elixir-CDX3lj18.js +1 -0
  423. pythinker_code/web/static/assets/elm-DbKCFpqz.js +1 -0
  424. pythinker_code/web/static/assets/emacs-lisp-C9XAeP06.js +1 -0
  425. pythinker_code/web/static/assets/erDiagram-Q2GNP2WA-BgTfALoK.js +60 -0
  426. pythinker_code/web/static/assets/erb-BOJIQeun.js +1 -0
  427. pythinker_code/web/static/assets/erlang-DsQrWhSR.js +1 -0
  428. pythinker_code/web/static/assets/everforest-dark-BgDCqdQA.js +1 -0
  429. pythinker_code/web/static/assets/everforest-light-C8M2exoo.js +1 -0
  430. pythinker_code/web/static/assets/fennel-BYunw83y.js +1 -0
  431. pythinker_code/web/static/assets/fish-BvzEVeQv.js +1 -0
  432. pythinker_code/web/static/assets/flowDiagram-NV44I4VS-QjW_fnGi.js +162 -0
  433. pythinker_code/web/static/assets/fluent-C4IJs8-o.js +1 -0
  434. pythinker_code/web/static/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
  435. pythinker_code/web/static/assets/fortran-free-form-BxgE0vQu.js +1 -0
  436. pythinker_code/web/static/assets/fsharp-CXgrBDvD.js +1 -0
  437. pythinker_code/web/static/assets/ganttDiagram-JELNMOA3-fqi8JFof.js +267 -0
  438. pythinker_code/web/static/assets/gdresource-B7Tvp0Sc.js +1 -0
  439. pythinker_code/web/static/assets/gdscript-DTMYz4Jt.js +1 -0
  440. pythinker_code/web/static/assets/gdshader-DkwncUOv.js +1 -0
  441. pythinker_code/web/static/assets/genie-D0YGMca9.js +1 -0
  442. pythinker_code/web/static/assets/gherkin-DyxjwDmM.js +1 -0
  443. pythinker_code/web/static/assets/git-commit-F4YmCXRG.js +1 -0
  444. pythinker_code/web/static/assets/git-rebase-r7XF79zn.js +1 -0
  445. pythinker_code/web/static/assets/gitGraphDiagram-NY62KEGX-i7o6VQ8x.js +65 -0
  446. pythinker_code/web/static/assets/github-dark-DHJKELXO.js +1 -0
  447. pythinker_code/web/static/assets/github-dark-default-Cuk6v7N8.js +1 -0
  448. pythinker_code/web/static/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  449. pythinker_code/web/static/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  450. pythinker_code/web/static/assets/github-light-DAi9KRSo.js +1 -0
  451. pythinker_code/web/static/assets/github-light-default-D7oLnXFd.js +1 -0
  452. pythinker_code/web/static/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  453. pythinker_code/web/static/assets/gleam-BspZqrRM.js +1 -0
  454. pythinker_code/web/static/assets/glimmer-js-Rg0-pVw9.js +1 -0
  455. pythinker_code/web/static/assets/glimmer-ts-U6CK756n.js +1 -0
  456. pythinker_code/web/static/assets/glsl-DplSGwfg.js +1 -0
  457. pythinker_code/web/static/assets/gn-n2N0HUVH.js +1 -0
  458. pythinker_code/web/static/assets/gnuplot-DdkO51Og.js +1 -0
  459. pythinker_code/web/static/assets/go-Dn2_MT6a.js +1 -0
  460. pythinker_code/web/static/assets/graph-C0vZK2pT.js +1 -0
  461. pythinker_code/web/static/assets/graphql-ChdNCCLP.js +1 -0
  462. pythinker_code/web/static/assets/groovy-gcz8RCvz.js +1 -0
  463. pythinker_code/web/static/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  464. pythinker_code/web/static/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  465. pythinker_code/web/static/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  466. pythinker_code/web/static/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  467. pythinker_code/web/static/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  468. pythinker_code/web/static/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  469. pythinker_code/web/static/assets/hack-CaT9iCJl.js +1 -0
  470. pythinker_code/web/static/assets/haml-B8DHNrY2.js +1 -0
  471. pythinker_code/web/static/assets/handlebars-BL8al0AC.js +1 -0
  472. pythinker_code/web/static/assets/haskell-Df6bDoY_.js +1 -0
  473. pythinker_code/web/static/assets/haxe-CzTSHFRz.js +1 -0
  474. pythinker_code/web/static/assets/hcl-BWvSN4gD.js +1 -0
  475. pythinker_code/web/static/assets/hjson-D5-asLiD.js +1 -0
  476. pythinker_code/web/static/assets/hlsl-D3lLCCz7.js +1 -0
  477. pythinker_code/web/static/assets/houston-DnULxvSX.js +1 -0
  478. pythinker_code/web/static/assets/html-GMplVEZG.js +1 -0
  479. pythinker_code/web/static/assets/html-derivative-BFtXZ54Q.js +1 -0
  480. pythinker_code/web/static/assets/http-jrhK8wxY.js +1 -0
  481. pythinker_code/web/static/assets/hurl-irOxFIW8.js +1 -0
  482. pythinker_code/web/static/assets/hxml-Bvhsp5Yf.js +1 -0
  483. pythinker_code/web/static/assets/hy-DFXneXwc.js +1 -0
  484. pythinker_code/web/static/assets/imba-DGztddWO.js +1 -0
  485. pythinker_code/web/static/assets/index-BYCCk6-K.js +153 -0
  486. pythinker_code/web/static/assets/index-BpoLgcEt.js +1 -0
  487. pythinker_code/web/static/assets/index-Cpy4G3uJ.js +2 -0
  488. pythinker_code/web/static/assets/index-CzV_vCfu.css +1 -0
  489. pythinker_code/web/static/assets/index-DI2oedCt.js +19 -0
  490. pythinker_code/web/static/assets/index-DdIkp80K.js +5 -0
  491. pythinker_code/web/static/assets/infoDiagram-WHAUD3N6-BMPpeZSM.js +2 -0
  492. pythinker_code/web/static/assets/ini-BEwlwnbL.js +1 -0
  493. pythinker_code/web/static/assets/init-Gi6I4Gst.js +1 -0
  494. pythinker_code/web/static/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  495. pythinker_code/web/static/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  496. pythinker_code/web/static/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  497. pythinker_code/web/static/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  498. pythinker_code/web/static/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  499. pythinker_code/web/static/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  500. pythinker_code/web/static/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  501. pythinker_code/web/static/assets/java-CylS5w8V.js +1 -0
  502. pythinker_code/web/static/assets/javascript-wDzz0qaB.js +1 -0
  503. pythinker_code/web/static/assets/jinja-4LBKfQ-Z.js +1 -0
  504. pythinker_code/web/static/assets/jison-wvAkD_A8.js +1 -0
  505. pythinker_code/web/static/assets/journeyDiagram-XKPGCS4Q-DAM7gngo.js +139 -0
  506. pythinker_code/web/static/assets/json-Cp-IABpG.js +1 -0
  507. pythinker_code/web/static/assets/json5-C9tS-k6U.js +1 -0
  508. pythinker_code/web/static/assets/jsonc-Des-eS-w.js +1 -0
  509. pythinker_code/web/static/assets/jsonl-DcaNXYhu.js +1 -0
  510. pythinker_code/web/static/assets/jsonnet-DFQXde-d.js +1 -0
  511. pythinker_code/web/static/assets/jssm-C2t-YnRu.js +1 -0
  512. pythinker_code/web/static/assets/jsx-g9-lgVsj.js +1 -0
  513. pythinker_code/web/static/assets/julia-CxzCAyBv.js +1 -0
  514. pythinker_code/web/static/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  515. pythinker_code/web/static/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  516. pythinker_code/web/static/assets/kanagawa-wave-DWedfzmr.js +1 -0
  517. pythinker_code/web/static/assets/kanban-definition-3W4ZIXB7-ChpBpV0k.js +89 -0
  518. pythinker_code/web/static/assets/katex-D2lIc1rk.css +1 -0
  519. pythinker_code/web/static/assets/kdl-DV7GczEv.js +1 -0
  520. pythinker_code/web/static/assets/kotlin-BdnUsdx6.js +1 -0
  521. pythinker_code/web/static/assets/kusto-DZf3V79B.js +1 -0
  522. pythinker_code/web/static/assets/laserwave-DUszq2jm.js +1 -0
  523. pythinker_code/web/static/assets/latex-B4uzh10-.js +1 -0
  524. pythinker_code/web/static/assets/layout-C3Jp1gKO.js +1 -0
  525. pythinker_code/web/static/assets/lean-BZvkOJ9d.js +1 -0
  526. pythinker_code/web/static/assets/less-B1dDrJ26.js +1 -0
  527. pythinker_code/web/static/assets/light-plus-B7mTdjB0.js +1 -0
  528. pythinker_code/web/static/assets/linear-BGHtL1N4.js +1 -0
  529. pythinker_code/web/static/assets/liquid-DYVedYrR.js +1 -0
  530. pythinker_code/web/static/assets/llvm-BtvRca6l.js +1 -0
  531. pythinker_code/web/static/assets/log-2UxHyX5q.js +1 -0
  532. pythinker_code/web/static/assets/logo-BtOb2qkB.js +1 -0
  533. pythinker_code/web/static/assets/lua-BbnMAYS6.js +1 -0
  534. pythinker_code/web/static/assets/luau-C-HG3fhB.js +1 -0
  535. pythinker_code/web/static/assets/make-CHLpvVh8.js +1 -0
  536. pythinker_code/web/static/assets/markdown-Cvjx9yec.js +1 -0
  537. pythinker_code/web/static/assets/marko-DZsq8hO1.js +1 -0
  538. pythinker_code/web/static/assets/material-theme-D5KoaKCx.js +1 -0
  539. pythinker_code/web/static/assets/material-theme-darker-BfHTSMKl.js +1 -0
  540. pythinker_code/web/static/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  541. pythinker_code/web/static/assets/material-theme-ocean-CyktbL80.js +1 -0
  542. pythinker_code/web/static/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  543. pythinker_code/web/static/assets/matlab-D7o27uSR.js +1 -0
  544. pythinker_code/web/static/assets/mdc-DUICxH0z.js +1 -0
  545. pythinker_code/web/static/assets/mdx-Cmh6b_Ma.js +1 -0
  546. pythinker_code/web/static/assets/mermaid-VLURNSYL-B2P5VJ9v.css +1 -0
  547. pythinker_code/web/static/assets/mermaid-VLURNSYL-C_HW6koB.js +465 -0
  548. pythinker_code/web/static/assets/mermaid-mWjccvbQ.js +1 -0
  549. pythinker_code/web/static/assets/mermaid.core-CnT4VrPC.js +191 -0
  550. pythinker_code/web/static/assets/min-Dn5VRVmX.js +1 -0
  551. pythinker_code/web/static/assets/min-dark-CafNBF8u.js +1 -0
  552. pythinker_code/web/static/assets/min-light-CTRr51gU.js +1 -0
  553. pythinker_code/web/static/assets/mindmap-definition-VGOIOE7T-x8EwhfIt.js +68 -0
  554. pythinker_code/web/static/assets/mipsasm-CKIfxQSi.js +1 -0
  555. pythinker_code/web/static/assets/mojo-B93PlW-d.js +1 -0
  556. pythinker_code/web/static/assets/monokai-D4h5O-jR.js +1 -0
  557. pythinker_code/web/static/assets/moonbit-Ba13S78F.js +1 -0
  558. pythinker_code/web/static/assets/move-Bu9oaDYs.js +1 -0
  559. pythinker_code/web/static/assets/narrat-DRg8JJMk.js +1 -0
  560. pythinker_code/web/static/assets/nextflow-BrzmwbiE.js +1 -0
  561. pythinker_code/web/static/assets/nginx-DknmC5AR.js +1 -0
  562. pythinker_code/web/static/assets/night-owl-C39BiMTA.js +1 -0
  563. pythinker_code/web/static/assets/nim-CVrawwO9.js +1 -0
  564. pythinker_code/web/static/assets/nix-CwoSXNpI.js +1 -0
  565. pythinker_code/web/static/assets/nord-Ddv68eIx.js +1 -0
  566. pythinker_code/web/static/assets/nushell-C-sUppwS.js +1 -0
  567. pythinker_code/web/static/assets/objective-c-DXmwc3jG.js +1 -0
  568. pythinker_code/web/static/assets/objective-cpp-CLxacb5B.js +1 -0
  569. pythinker_code/web/static/assets/ocaml-C0hk2d4L.js +1 -0
  570. pythinker_code/web/static/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  571. pythinker_code/web/static/assets/one-light-PoHY5YXO.js +1 -0
  572. pythinker_code/web/static/assets/openscad-C4EeE6gA.js +1 -0
  573. pythinker_code/web/static/assets/ordinal-Cboi1Yqb.js +1 -0
  574. pythinker_code/web/static/assets/pascal-D93ZcfNL.js +1 -0
  575. pythinker_code/web/static/assets/perl-C0TMdlhV.js +1 -0
  576. pythinker_code/web/static/assets/php-CDn_0X-4.js +1 -0
  577. pythinker_code/web/static/assets/pieDiagram-ADFJNKIX-DgxBKGz2.js +30 -0
  578. pythinker_code/web/static/assets/pkl-u5AG7uiY.js +1 -0
  579. pythinker_code/web/static/assets/plastic-3e1v2bzS.js +1 -0
  580. pythinker_code/web/static/assets/plsql-ChMvpjG-.js +1 -0
  581. pythinker_code/web/static/assets/po-BTJTHyun.js +1 -0
  582. pythinker_code/web/static/assets/poimandres-CS3Unz2-.js +1 -0
  583. pythinker_code/web/static/assets/polar-C0HS_06l.js +1 -0
  584. pythinker_code/web/static/assets/postcss-CXtECtnM.js +1 -0
  585. pythinker_code/web/static/assets/powerquery-CEu0bR-o.js +1 -0
  586. pythinker_code/web/static/assets/powershell-Dpen1YoG.js +1 -0
  587. pythinker_code/web/static/assets/prisma-Dd19v3D-.js +1 -0
  588. pythinker_code/web/static/assets/prolog-CbFg5uaA.js +1 -0
  589. pythinker_code/web/static/assets/proto-C7zT0LnQ.js +1 -0
  590. pythinker_code/web/static/assets/pug-CGlum2m_.js +1 -0
  591. pythinker_code/web/static/assets/puppet-BMWR74SV.js +1 -0
  592. pythinker_code/web/static/assets/purescript-CklMAg4u.js +1 -0
  593. pythinker_code/web/static/assets/python-B6aJPvgy.js +1 -0
  594. pythinker_code/web/static/assets/qml-3beO22l8.js +1 -0
  595. pythinker_code/web/static/assets/qmldir-C8lEn-DE.js +1 -0
  596. pythinker_code/web/static/assets/qss-IeuSbFQv.js +1 -0
  597. pythinker_code/web/static/assets/quadrantDiagram-AYHSOK5B-DSpe_dqk.js +7 -0
  598. pythinker_code/web/static/assets/r-Dspwwk_N.js +1 -0
  599. pythinker_code/web/static/assets/racket-BqYA7rlc.js +1 -0
  600. pythinker_code/web/static/assets/raku-DXvB9xmW.js +1 -0
  601. pythinker_code/web/static/assets/razor-C1TweQQi.js +1 -0
  602. pythinker_code/web/static/assets/red-bN70gL4F.js +1 -0
  603. pythinker_code/web/static/assets/reg-C-SQnVFl.js +1 -0
  604. pythinker_code/web/static/assets/regexp-CDVJQ6XC.js +1 -0
  605. pythinker_code/web/static/assets/rel-C3B-1QV4.js +1 -0
  606. pythinker_code/web/static/assets/requirementDiagram-UZGBJVZJ-8o9hozL-.js +64 -0
  607. pythinker_code/web/static/assets/riscv-BM1_JUlF.js +1 -0
  608. pythinker_code/web/static/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
  609. pythinker_code/web/static/assets/rose-pine-moon-D4_iv3hh.js +1 -0
  610. pythinker_code/web/static/assets/rose-pine-qdsjHGoJ.js +1 -0
  611. pythinker_code/web/static/assets/rosmsg-BJDFO7_C.js +1 -0
  612. pythinker_code/web/static/assets/rst-B0xPkSld.js +1 -0
  613. pythinker_code/web/static/assets/ruby-BvKwtOVI.js +1 -0
  614. pythinker_code/web/static/assets/rust-B1yitclQ.js +1 -0
  615. pythinker_code/web/static/assets/sankeyDiagram-TZEHDZUN-BLOSUixH.js +10 -0
  616. pythinker_code/web/static/assets/sas-cz2c8ADy.js +1 -0
  617. pythinker_code/web/static/assets/sass-Cj5Yp3dK.js +1 -0
  618. pythinker_code/web/static/assets/scala-C151Ov-r.js +1 -0
  619. pythinker_code/web/static/assets/scheme-C98Dy4si.js +1 -0
  620. pythinker_code/web/static/assets/scss-OYdSNvt2.js +1 -0
  621. pythinker_code/web/static/assets/sdbl-DVxCFoDh.js +1 -0
  622. pythinker_code/web/static/assets/sequenceDiagram-WL72ISMW-6F2G8JTU.js +145 -0
  623. pythinker_code/web/static/assets/shaderlab-Dg9Lc6iA.js +1 -0
  624. pythinker_code/web/static/assets/shellscript-Yzrsuije.js +1 -0
  625. pythinker_code/web/static/assets/shellsession-BADoaaVG.js +1 -0
  626. pythinker_code/web/static/assets/slack-dark-BthQWCQV.js +1 -0
  627. pythinker_code/web/static/assets/slack-ochin-DqwNpetd.js +1 -0
  628. pythinker_code/web/static/assets/smalltalk-BERRCDM3.js +1 -0
  629. pythinker_code/web/static/assets/snazzy-light-Bw305WKR.js +1 -0
  630. pythinker_code/web/static/assets/solarized-dark-DXbdFlpD.js +1 -0
  631. pythinker_code/web/static/assets/solarized-light-L9t79GZl.js +1 -0
  632. pythinker_code/web/static/assets/solidity-rGO070M0.js +1 -0
  633. pythinker_code/web/static/assets/soy-Brmx7dQM.js +1 -0
  634. pythinker_code/web/static/assets/sparql-rVzFXLq3.js +1 -0
  635. pythinker_code/web/static/assets/splunk-BtCnVYZw.js +1 -0
  636. pythinker_code/web/static/assets/sql-BLtJtn59.js +1 -0
  637. pythinker_code/web/static/assets/ssh-config-_ykCGR6B.js +1 -0
  638. pythinker_code/web/static/assets/stata-BH5u7GGu.js +1 -0
  639. pythinker_code/web/static/assets/stateDiagram-FKZM4ZOC-DP8xP0iJ.js +1 -0
  640. pythinker_code/web/static/assets/stateDiagram-v2-4FDKWEC3-1l6-EZNX.js +1 -0
  641. pythinker_code/web/static/assets/stylus-BEDo0Tqx.js +1 -0
  642. pythinker_code/web/static/assets/svelte-zxCyuUbr.js +1 -0
  643. pythinker_code/web/static/assets/swift-Dg5xB15N.js +1 -0
  644. pythinker_code/web/static/assets/synthwave-84-CbfX1IO0.js +1 -0
  645. pythinker_code/web/static/assets/system-verilog-CnnmHF94.js +1 -0
  646. pythinker_code/web/static/assets/systemd-4A_iFExJ.js +1 -0
  647. pythinker_code/web/static/assets/talonscript-CkByrt1z.js +1 -0
  648. pythinker_code/web/static/assets/tasl-QIJgUcNo.js +1 -0
  649. pythinker_code/web/static/assets/tcl-dwOrl1Do.js +1 -0
  650. pythinker_code/web/static/assets/templ-W15q3VgB.js +1 -0
  651. pythinker_code/web/static/assets/terraform-BETggiCN.js +1 -0
  652. pythinker_code/web/static/assets/tex-CvyZ59Mk.js +1 -0
  653. pythinker_code/web/static/assets/timeline-definition-IT6M3QCI-DMgruDfK.js +61 -0
  654. pythinker_code/web/static/assets/tokyo-night-hegEt444.js +1 -0
  655. pythinker_code/web/static/assets/toml-vGWfd6FD.js +1 -0
  656. pythinker_code/web/static/assets/treemap-KMMF4GRG-Bo9ZkrAK.js +128 -0
  657. pythinker_code/web/static/assets/ts-tags-zn1MmPIZ.js +1 -0
  658. pythinker_code/web/static/assets/tsv-B_m7g4N7.js +1 -0
  659. pythinker_code/web/static/assets/tsx-COt5Ahok.js +1 -0
  660. pythinker_code/web/static/assets/turtle-BsS91CYL.js +1 -0
  661. pythinker_code/web/static/assets/twig-CO9l9SDP.js +1 -0
  662. pythinker_code/web/static/assets/typescript-BPQ3VLAy.js +1 -0
  663. pythinker_code/web/static/assets/typespec-BGHnOYBU.js +1 -0
  664. pythinker_code/web/static/assets/typst-DHCkPAjA.js +1 -0
  665. pythinker_code/web/static/assets/v-BcVCzyr7.js +1 -0
  666. pythinker_code/web/static/assets/vala-CsfeWuGM.js +1 -0
  667. pythinker_code/web/static/assets/vb-D17OF-Vu.js +1 -0
  668. pythinker_code/web/static/assets/verilog-BQ8w6xss.js +1 -0
  669. pythinker_code/web/static/assets/vesper-DU1UobuO.js +1 -0
  670. pythinker_code/web/static/assets/vhdl-CeAyd5Ju.js +1 -0
  671. pythinker_code/web/static/assets/viml-CJc9bBzg.js +1 -0
  672. pythinker_code/web/static/assets/vitesse-black-Bkuqu6BP.js +1 -0
  673. pythinker_code/web/static/assets/vitesse-dark-D0r3Knsf.js +1 -0
  674. pythinker_code/web/static/assets/vitesse-light-CVO1_9PV.js +1 -0
  675. pythinker_code/web/static/assets/vue-DN_0RTcg.js +1 -0
  676. pythinker_code/web/static/assets/vue-html-AaS7Mt5G.js +1 -0
  677. pythinker_code/web/static/assets/vue-vine-CQOfvN7w.js +1 -0
  678. pythinker_code/web/static/assets/vyper-CDx5xZoG.js +1 -0
  679. pythinker_code/web/static/assets/wasm-CG6Dc4jp.js +1 -0
  680. pythinker_code/web/static/assets/wasm-MzD3tlZU.js +1 -0
  681. pythinker_code/web/static/assets/wenyan-BV7otONQ.js +1 -0
  682. pythinker_code/web/static/assets/wgsl-Dx-B1_4e.js +1 -0
  683. pythinker_code/web/static/assets/wikitext-BhOHFoWU.js +1 -0
  684. pythinker_code/web/static/assets/wit-5i3qLPDT.js +1 -0
  685. pythinker_code/web/static/assets/wolfram-lXgVvXCa.js +1 -0
  686. pythinker_code/web/static/assets/xml-sdJ4AIDG.js +1 -0
  687. pythinker_code/web/static/assets/xsl-CtQFsRM5.js +1 -0
  688. pythinker_code/web/static/assets/xychartDiagram-PRI3JC2R-GeF2johi.js +7 -0
  689. pythinker_code/web/static/assets/yaml-Buea-lGh.js +1 -0
  690. pythinker_code/web/static/assets/zenscript-DVFEvuxE.js +1 -0
  691. pythinker_code/web/static/assets/zig-VOosw3JB.js +1 -0
  692. pythinker_code/web/static/brand/apple-touch-icon.png +0 -0
  693. pythinker_code/web/static/brand/arctecture.webp +0 -0
  694. pythinker_code/web/static/brand/bimi-logo.svg +46 -0
  695. pythinker_code/web/static/brand/favicon.ico +0 -0
  696. pythinker_code/web/static/brand/fonts/dm-sans-latin-ext.woff2 +0 -0
  697. pythinker_code/web/static/brand/fonts/dm-sans-latin.woff2 +0 -0
  698. pythinker_code/web/static/brand/fonts/instrument-sans-latin-ext.woff2 +0 -0
  699. pythinker_code/web/static/brand/fonts/instrument-sans-latin.woff2 +0 -0
  700. pythinker_code/web/static/brand/fonts/instrument-serif-latin-ext.woff2 +0 -0
  701. pythinker_code/web/static/brand/fonts/instrument-serif-latin.woff2 +0 -0
  702. pythinker_code/web/static/brand/fonts/libre-baskerville-italic-latin-ext.woff2 +0 -0
  703. pythinker_code/web/static/brand/fonts/libre-baskerville-italic-latin.woff2 +0 -0
  704. pythinker_code/web/static/brand/fonts/libre-baskerville-latin-ext.woff2 +0 -0
  705. pythinker_code/web/static/brand/fonts/libre-baskerville-latin.woff2 +0 -0
  706. pythinker_code/web/static/brand/fonts/roboto-latin-ext.woff2 +0 -0
  707. pythinker_code/web/static/brand/fonts/roboto-latin.woff2 +0 -0
  708. pythinker_code/web/static/brand/icon-192.png +0 -0
  709. pythinker_code/web/static/brand/icon-512.png +0 -0
  710. pythinker_code/web/static/brand/icon.svg +1 -0
  711. pythinker_code/web/static/brand/logo.png +0 -0
  712. pythinker_code/web/static/brand/pythinker_animated.svg +79 -0
  713. pythinker_code/web/static/brand/robots.txt +4 -0
  714. pythinker_code/web/static/index.html +15 -0
  715. pythinker_code/web/static/logo.png +0 -0
  716. pythinker_code/web/store/__init__.py +1 -0
  717. pythinker_code/web/store/sessions.py +432 -0
  718. pythinker_code/wire/__init__.py +148 -0
  719. pythinker_code/wire/file.py +151 -0
  720. pythinker_code/wire/jsonrpc.py +263 -0
  721. pythinker_code/wire/protocol.py +2 -0
  722. pythinker_code/wire/root_hub.py +27 -0
  723. pythinker_code/wire/serde.py +26 -0
  724. pythinker_code/wire/server.py +1069 -0
  725. pythinker_code/wire/types.py +698 -0
  726. pythinker_code-2.0.0.dist-info/METADATA +660 -0
  727. pythinker_code-2.0.0.dist-info/RECORD +731 -0
  728. pythinker_code-2.0.0.dist-info/WHEEL +4 -0
  729. pythinker_code-2.0.0.dist-info/entry_points.txt +4 -0
  730. pythinker_code-2.0.0.dist-info/licenses/LICENSE +202 -0
  731. pythinker_code-2.0.0.dist-info/licenses/NOTICE +14 -0
@@ -0,0 +1,727 @@
1
+ """Skill specification discovery and loading utilities."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from collections.abc import Callable, Iterable, Iterator, Sequence
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+ from typing import Literal, cast
10
+
11
+ from pydantic import BaseModel, ConfigDict, Field
12
+ from pythinker_host import get_current_host
13
+ from pythinker_host.local import local_host
14
+ from pythinker_host.path import HostPath
15
+
16
+ from pythinker_code.skill.flow import Flow, FlowError
17
+ from pythinker_code.skill.flow.d2 import parse_d2_flowchart
18
+ from pythinker_code.skill.flow.mermaid import parse_mermaid_flowchart
19
+ from pythinker_code.utils.frontmatter import parse_frontmatter
20
+ from pythinker_code.utils.logging import logger
21
+
22
+ SkillType = Literal["standard", "flow"]
23
+
24
+ SkillScope = Literal["builtin", "user", "project", "extra"]
25
+ """Where a skill was discovered from.
26
+
27
+ - ``builtin``: bundled with pythinker-code
28
+ - ``user``: from the user's home (``~/.pythinker/skills``, ``~/.agents/skills``, ...)
29
+ - ``project``: from the current project's working directory
30
+ (``<work_dir>/.pythinker/skills``, ``<work_dir>/.agents/skills``, ...)
31
+ - ``extra``: from ``extra_skill_dirs`` config or ``--skills-dir`` override
32
+ """
33
+
34
+
35
+ @dataclass(frozen=True, slots=True)
36
+ class ScopedSkillsRoot:
37
+ """A skills directory paired with the scope it belongs to."""
38
+
39
+ root: HostPath
40
+ scope: SkillScope
41
+
42
+
43
+ def get_builtin_skills_dir() -> Path:
44
+ """
45
+ Get the built-in skills directory path.
46
+ """
47
+ if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
48
+ # Running in a PyInstaller bundle; use _MEIPASS to locate bundled resources
49
+ # reliably on all platforms (avoids __file__ path issues in frozen envs on Windows)
50
+ meipass = cast(str, sys._MEIPASS) # pyright: ignore[reportAttributeAccessIssue,reportUnknownMemberType]
51
+ return Path(meipass) / "pythinker_code" / "skills"
52
+ return Path(__file__).parent.parent / "skills"
53
+
54
+
55
+ def _get_user_generic_skills_dir_candidates() -> tuple[HostPath, ...]:
56
+ """
57
+ Get user-level generic skills directory candidates in priority order.
58
+
59
+ Generic group: ``~/.config/agents/skills`` > ``~/.agents/skills``
60
+ """
61
+ return (
62
+ HostPath.home() / ".config" / "agents" / "skills",
63
+ HostPath.home() / ".agents" / "skills",
64
+ )
65
+
66
+
67
+ def _get_user_brand_skills_dir_candidates() -> tuple[HostPath, ...]:
68
+ """
69
+ Get user-level brand skills directory candidates in priority order.
70
+
71
+ Brand group: ``~/.pythinker/skills`` > ``~/.claude/skills`` > ``~/.codex/skills``
72
+ """
73
+ return (
74
+ HostPath.home() / ".pythinker" / "skills",
75
+ HostPath.home() / ".claude" / "skills",
76
+ HostPath.home() / ".codex" / "skills",
77
+ )
78
+
79
+
80
+ def _get_project_generic_skills_dir_candidates(work_dir: HostPath) -> tuple[HostPath, ...]:
81
+ """
82
+ Get project-level generic skills directory candidates.
83
+
84
+ Generic group: ``.agents/skills``
85
+ """
86
+ return (work_dir / ".agents" / "skills",)
87
+
88
+
89
+ def _get_project_brand_skills_dir_candidates(work_dir: HostPath) -> tuple[HostPath, ...]:
90
+ """
91
+ Get project-level brand skills directory candidates in priority order.
92
+
93
+ Brand group: ``.pythinker/skills`` > ``.claude/skills`` > ``.codex/skills``
94
+ """
95
+ return (
96
+ work_dir / ".pythinker" / "skills",
97
+ work_dir / ".claude" / "skills",
98
+ work_dir / ".codex" / "skills",
99
+ )
100
+
101
+
102
+ def _supports_builtin_skills() -> bool:
103
+ """Return True when the active Host backend can read bundled skills."""
104
+ current_name = get_current_host().name
105
+ return current_name in (local_host.name, "acp")
106
+
107
+
108
+ async def find_first_existing_dir(candidates: Iterable[HostPath]) -> HostPath | None:
109
+ """
110
+ Return the first existing directory from candidates.
111
+ """
112
+ for candidate in candidates:
113
+ if await candidate.is_dir():
114
+ return candidate
115
+ return None
116
+
117
+
118
+ async def find_user_skills_dirs(
119
+ *,
120
+ merge_brands: bool = False,
121
+ ) -> list[HostPath]:
122
+ """
123
+ Return user-level skills directories from both brand and generic groups.
124
+
125
+ The brand group comes first because brand-specific directories have
126
+ higher specificity. When *merge_brands* is ``False`` (default), only the
127
+ first existing brand directory is used. When ``True``, all existing brand
128
+ directories are included (priority order: pythinker > claude > codex).
129
+ """
130
+ dirs: list[HostPath] = []
131
+ if merge_brands:
132
+ for candidate in _get_user_brand_skills_dir_candidates():
133
+ if await candidate.is_dir():
134
+ dirs.append(candidate)
135
+ else:
136
+ if brand := await find_first_existing_dir(
137
+ _get_user_brand_skills_dir_candidates(),
138
+ ):
139
+ dirs.append(brand)
140
+ if generic := await find_first_existing_dir(
141
+ _get_user_generic_skills_dir_candidates(),
142
+ ):
143
+ dirs.append(generic)
144
+ return dirs
145
+
146
+
147
+ async def find_project_skills_dirs(
148
+ work_dir: HostPath,
149
+ *,
150
+ merge_brands: bool = False,
151
+ ) -> list[HostPath]:
152
+ """
153
+ Return project-level skills directories from both brand and generic groups.
154
+
155
+ Discovery starts at the **project root** (the nearest ``.git`` ancestor
156
+ of ``work_dir``), so launching pythinker-code from a subdirectory — for example
157
+ a monorepo package — still surfaces skills defined at the repository root.
158
+ Falls back to ``work_dir`` itself when no ``.git`` marker is found, to
159
+ avoid accidentally walking up into unrelated parent trees.
160
+
161
+ The brand group comes first because brand-specific directories have
162
+ higher specificity. When *merge_brands* is ``False`` (default), only the
163
+ first existing brand directory is used. When ``True``, all existing brand
164
+ directories are included (priority order: pythinker > claude > codex).
165
+ """
166
+ from pythinker_code.utils.path import find_project_root
167
+
168
+ work_dir = await find_project_root(work_dir)
169
+ dirs: list[HostPath] = []
170
+ brand_candidates = _get_project_brand_skills_dir_candidates(work_dir)
171
+ if merge_brands:
172
+ for candidate in brand_candidates:
173
+ if await candidate.is_dir():
174
+ dirs.append(candidate)
175
+ else:
176
+ if brand := await find_first_existing_dir(brand_candidates):
177
+ dirs.append(brand)
178
+ generic_candidates = _get_project_generic_skills_dir_candidates(work_dir)
179
+ if generic := await find_first_existing_dir(generic_candidates):
180
+ dirs.append(generic)
181
+ return dirs
182
+
183
+
184
+ async def resolve_skills_roots(
185
+ work_dir: HostPath,
186
+ *,
187
+ skills_dirs: Sequence[HostPath] | None = None,
188
+ merge_brands: bool = False,
189
+ extra_skill_dirs: Sequence[str] | None = None,
190
+ ) -> list[ScopedSkillsRoot]:
191
+ """Resolve layered skill roots with their scope labels.
192
+
193
+ Scope labels let the system-prompt renderer group skills so the model can
194
+ tell a user-scope skill from a project-scope one. Roots are ordered
195
+ **highest priority first** — ``discover_skills_from_roots`` keeps the first
196
+ match per skill name, so this ordering controls which scope "wins" when the
197
+ same skill name exists in several places:
198
+
199
+ Project > User > Extra(config) > Extra(plugins) > Built-in
200
+
201
+ ``extra_skill_dirs`` is **additive** (not an override) and scoped as
202
+ ``extra``; each entry may be:
203
+
204
+ - An absolute path (used as-is)
205
+ - ``~/...`` (expanded against ``$HOME``)
206
+ - A relative path (resolved against the **project root** — the nearest
207
+ ``.git`` directory above ``work_dir``, or ``work_dir`` itself if none)
208
+
209
+ Non-existent entries are silently dropped. Duplicates collapse to one.
210
+ """
211
+ from pythinker_code.plugin.manager import get_plugins_dir
212
+ from pythinker_code.utils.path import find_project_root
213
+
214
+ scoped: list[ScopedSkillsRoot] = []
215
+ seen: set[str] = set()
216
+
217
+ def _append(root: HostPath, scope: SkillScope) -> None:
218
+ # Dedupe so symlinks, ``..`` segments, and trailing slashes don't
219
+ # produce phantom duplicate entries in the system prompt. Note that
220
+ # ``HostPath.canonical()`` only normalizes path segments; it does NOT
221
+ # resolve symlinks. So on local backends we walk symlinks via
222
+ # ``pathlib.Path.resolve()`` first, then fall through to
223
+ # ``canonical()`` for ``..`` / trailing-slash normalization. Non-local
224
+ # backends keep the canonical-only path because ``Path.resolve()``
225
+ # would walk the wrong filesystem.
226
+ resolved = root
227
+ if get_current_host().name == local_host.name:
228
+ try:
229
+ local_resolved = Path(str(root)).resolve()
230
+ except OSError:
231
+ # Keep the original path; canonical() below still does what it can.
232
+ pass
233
+ else:
234
+ resolved = HostPath.unsafe_from_local_path(local_resolved)
235
+ try:
236
+ canon = resolved.canonical()
237
+ except OSError:
238
+ canon = resolved
239
+ key = str(canon)
240
+ if key in seen:
241
+ return
242
+ seen.add(key)
243
+ scoped.append(ScopedSkillsRoot(root=canon, scope=scope))
244
+
245
+ if skills_dirs:
246
+ # --skills-dir overrides user/project auto-discovery, but runs at the
247
+ # top of priority order so its skills take precedence, matching the
248
+ # "closer to the user's explicit intent, higher priority" principle.
249
+ for d in skills_dirs:
250
+ _append(d, "extra")
251
+ else:
252
+ for d in await find_project_skills_dirs(work_dir, merge_brands=merge_brands):
253
+ _append(d, "project")
254
+ for d in await find_user_skills_dirs(merge_brands=merge_brands):
255
+ _append(d, "user")
256
+
257
+ if extra_skill_dirs:
258
+ project_root = await find_project_root(work_dir)
259
+ for raw in extra_skill_dirs:
260
+ resolved = _resolve_extra_skill_dir(raw, project_root)
261
+ if resolved is None:
262
+ continue
263
+ try:
264
+ is_dir = await resolved.is_dir()
265
+ except OSError as exc:
266
+ logger.info(
267
+ "Skipping extra_skill_dirs entry {path}: {error}",
268
+ path=resolved,
269
+ error=exc,
270
+ )
271
+ continue
272
+ if not is_dir:
273
+ continue
274
+ _append(resolved, "extra")
275
+
276
+ # Plugins are always discoverable; treat as "extra" origin for prompt
277
+ # grouping but place them below config-declared extras (user intent wins).
278
+ plugins_path = get_plugins_dir()
279
+ try:
280
+ plugins_is_dir = plugins_path.is_dir()
281
+ except OSError:
282
+ plugins_is_dir = False
283
+ if plugins_is_dir:
284
+ _append(HostPath.unsafe_from_local_path(plugins_path), "extra")
285
+
286
+ if _supports_builtin_skills():
287
+ _append(
288
+ HostPath.unsafe_from_local_path(get_builtin_skills_dir()),
289
+ "builtin",
290
+ )
291
+ return scoped
292
+
293
+
294
+ def _resolve_extra_skill_dir(raw: str, project_root: HostPath) -> HostPath | None:
295
+ """Resolve a single ``extra_skill_dirs`` entry to a HostPath, or None on error.
296
+
297
+ Expands ``~``; treats non-absolute entries as relative to *project_root*.
298
+ """
299
+ if not raw:
300
+ return None
301
+ try:
302
+ p = Path(raw).expanduser()
303
+ except (RuntimeError, OSError):
304
+ # ``expanduser`` can raise on malformed HOME or platform-specific
305
+ # oddities; treat any failure as a dropped entry rather than let it
306
+ # kill skill discovery.
307
+ return None
308
+ if p.is_absolute():
309
+ return HostPath.unsafe_from_local_path(p)
310
+ return HostPath.unsafe_from_local_path(Path(str(project_root)) / p)
311
+
312
+
313
+ def normalize_skill_name(name: str) -> str:
314
+ """Normalize a skill name for lookup."""
315
+ return name.casefold()
316
+
317
+
318
+ def index_skills(skills: Iterable[Skill]) -> dict[str, Skill]:
319
+ """Build a lookup table for skills by normalized name."""
320
+ return {normalize_skill_name(skill.name): skill for skill in skills}
321
+
322
+
323
+ async def discover_skills_from_roots(
324
+ scoped_roots: Iterable[ScopedSkillsRoot],
325
+ ) -> list[Skill]:
326
+ """Discover skills from scope-labelled roots.
327
+
328
+ When the same skill name appears in multiple roots, the **first occurrence
329
+ wins** (higher priority roots come first in :func:`resolve_skills_roots`'s
330
+ output). Each returned :class:`Skill` carries the scope of the root it came
331
+ from.
332
+ """
333
+ skills_by_name: dict[str, Skill] = {}
334
+ for scoped in scoped_roots:
335
+ for skill in await discover_skills(scoped.root, scope=scoped.scope):
336
+ skills_by_name.setdefault(normalize_skill_name(skill.name), skill)
337
+ return sorted(skills_by_name.values(), key=lambda s: s.name)
338
+
339
+
340
+ _SCOPE_HEADINGS: tuple[tuple[SkillScope, str], ...] = (
341
+ ("project", "Project"),
342
+ ("user", "User"),
343
+ ("extra", "Extra"),
344
+ ("builtin", "Built-in"),
345
+ )
346
+ """Scope headings for the system-prompt skills block, in the order they appear.
347
+
348
+ Project first (most specific), then User, Extra, and finally Built-in (least
349
+ specific). Matches the priority the model should give when multiple scopes
350
+ define a skill with the same name.
351
+ """
352
+
353
+
354
+ def format_skills_for_prompt(skills: Iterable[Skill]) -> str:
355
+ """Render *skills* grouped by scope for injection into the system prompt.
356
+
357
+ Output layout::
358
+
359
+ ### Project
360
+ - <name>
361
+ - Path: <skill_md_file>
362
+ - Description: <description>
363
+
364
+ ### User
365
+ - ...
366
+
367
+ Empty scope groups are omitted. The model uses these headings to distinguish
368
+ user-scope and project-scope skills when responding to prompts like
369
+ "the skill in my project".
370
+ """
371
+ grouped: dict[SkillScope, list[Skill]] = {s: [] for s, _ in _SCOPE_HEADINGS}
372
+ for skill in skills:
373
+ grouped.setdefault(skill.scope, []).append(skill)
374
+
375
+ sections: list[str] = []
376
+ for scope, heading in _SCOPE_HEADINGS:
377
+ bucket = grouped.get(scope) or []
378
+ if not bucket:
379
+ continue
380
+ lines = [f"### {heading}"]
381
+ for skill in sorted(bucket, key=lambda s: s.name):
382
+ lines.append(f"- {skill.name}")
383
+ lines.append(f" - Path: {skill.skill_md_file}")
384
+ lines.append(f" - Description: {skill.description}")
385
+ sections.append("\n".join(lines))
386
+
387
+ if not sections:
388
+ return "No skills found."
389
+ return "\n\n".join(sections)
390
+
391
+
392
+ async def read_skill_text(skill: Skill) -> str | None:
393
+ """Read the SKILL.md contents for a skill."""
394
+ try:
395
+ return (await skill.skill_md_file.read_text(encoding="utf-8")).strip()
396
+ except OSError as exc:
397
+ logger.warning(
398
+ "Failed to read skill file {path}: {error}",
399
+ path=skill.skill_md_file,
400
+ error=exc,
401
+ )
402
+ return None
403
+
404
+
405
+ class Skill(BaseModel):
406
+ """Information about a single skill."""
407
+
408
+ model_config = ConfigDict(extra="ignore", arbitrary_types_allowed=True)
409
+
410
+ name: str
411
+ description: str
412
+ type: SkillType = "standard"
413
+ dir: HostPath
414
+ """The skill's resource directory. For subdirectory-form skills this is the
415
+ per-skill directory; for flat ``.md`` skills it is the parent skills root."""
416
+ skill_md_file: HostPath
417
+ """Path to the markdown file that holds the skill body. For subdirectory
418
+ skills this is ``dir/SKILL.md``; for flat skills this is the ``.md`` file
419
+ itself."""
420
+ flow: Flow | None = None
421
+ scope: SkillScope = Field(...)
422
+ """Which scope this skill was discovered from. Required; discovery always
423
+ stamps it. The system-prompt renderer groups skills by this label so the
424
+ model can tell user-scope from project-scope skills."""
425
+
426
+
427
+ async def discover_skills(
428
+ skills_dir: HostPath,
429
+ *,
430
+ scope: SkillScope,
431
+ ) -> list[Skill]:
432
+ """Discover all skills in the given directory.
433
+
434
+ Two layouts are supported side by side:
435
+
436
+ 1. **Subdirectory**: ``<skills_dir>/<name>/SKILL.md`` — the canonical layout.
437
+ 2. **Flat**: ``<skills_dir>/<name>.md`` — a single-file skill. Handy for
438
+ users migrating from agent tooling that stores skills as flat markdown
439
+ files.
440
+
441
+ When both forms share the same normalized name in one directory, the
442
+ subdirectory form wins and a warning is emitted.
443
+
444
+ *scope* is stamped onto each discovered :class:`Skill` so the system-prompt
445
+ renderer can group skills by where they came from (user / project / extra /
446
+ builtin).
447
+ """
448
+ try:
449
+ is_dir = await skills_dir.is_dir()
450
+ except OSError as exc:
451
+ logger.warning(
452
+ "Cannot stat skills directory {path}, skipping: {error}",
453
+ path=skills_dir,
454
+ error=exc,
455
+ )
456
+ return []
457
+ if not is_dir:
458
+ return []
459
+
460
+ skills_by_name: dict[str, Skill] = {}
461
+
462
+ # Pass 1: subdirectory form (canonical).
463
+ try:
464
+ async for entry in skills_dir.iterdir():
465
+ try:
466
+ if not await entry.is_dir():
467
+ continue
468
+ skill_md = entry / "SKILL.md"
469
+ if not await skill_md.is_file():
470
+ continue
471
+ content = await skill_md.read_text(encoding="utf-8")
472
+ except OSError as exc:
473
+ logger.info(
474
+ "Skipping unreadable skill entry {path}: {error}",
475
+ path=entry,
476
+ error=exc,
477
+ )
478
+ continue
479
+ try:
480
+ skill = parse_skill_text(
481
+ content, dir_path=entry, skill_md_file=skill_md, scope=scope
482
+ )
483
+ except Exception as exc:
484
+ logger.info("Skipping invalid skill at {}: {}", skill_md, exc)
485
+ continue
486
+ skills_by_name[normalize_skill_name(skill.name)] = skill
487
+ except OSError as exc:
488
+ logger.warning(
489
+ "Failed to iterate skills directory {path}: {error}",
490
+ path=skills_dir,
491
+ error=exc,
492
+ )
493
+ return sorted(skills_by_name.values(), key=lambda s: s.name)
494
+
495
+ # Pass 2: flat ``.md`` form, skipping names already claimed by a subdir.
496
+ try:
497
+ async for entry in skills_dir.iterdir():
498
+ try:
499
+ if await entry.is_dir():
500
+ continue
501
+ except OSError as exc:
502
+ logger.info(
503
+ "Skipping unstattable entry {path}: {error}",
504
+ path=entry,
505
+ error=exc,
506
+ )
507
+ continue
508
+ if not entry.name.lower().endswith(".md"):
509
+ continue
510
+ # A bare ``SKILL.md`` at the top of skills_dir is a stray marker file,
511
+ # not a skill — it has no per-skill directory to associate with.
512
+ if entry.name.upper() == "SKILL.MD":
513
+ continue
514
+
515
+ try:
516
+ content = await entry.read_text(encoding="utf-8")
517
+ skill = parse_skill_text(
518
+ content,
519
+ dir_path=skills_dir,
520
+ skill_md_file=entry,
521
+ scope=scope,
522
+ flat_file=entry,
523
+ )
524
+ except Exception as exc:
525
+ logger.info("Skipping invalid flat skill at {}: {}", entry, exc)
526
+ continue
527
+
528
+ key = normalize_skill_name(skill.name)
529
+ if key in skills_by_name:
530
+ logger.warning(
531
+ "Flat skill {flat} shadowed by subdirectory skill of the same "
532
+ "name at {sub}; the subdirectory version is used.",
533
+ flat=entry,
534
+ sub=skills_by_name[key].dir,
535
+ )
536
+ continue
537
+ skills_by_name[key] = skill
538
+ except OSError as exc:
539
+ logger.warning(
540
+ "Failed to iterate skills directory for flat scan {path}: {error}",
541
+ path=skills_dir,
542
+ error=exc,
543
+ )
544
+
545
+ return sorted(skills_by_name.values(), key=lambda s: s.name)
546
+
547
+
548
+ _DESCRIPTION_FALLBACK_MAX_LEN = 240
549
+ """Max length for the body-derived description fallback. Longer first lines are
550
+ truncated with an ellipsis. The spec caps ``description`` at 1024 chars, but
551
+ for a fallback used when the user forgot to set one, a tighter cap keeps the
552
+ system prompt compact.
553
+ """
554
+
555
+
556
+ def parse_skill_text(
557
+ content: str,
558
+ *,
559
+ dir_path: HostPath,
560
+ skill_md_file: HostPath,
561
+ scope: SkillScope,
562
+ flat_file: HostPath | None = None,
563
+ ) -> Skill:
564
+ """Parse skill markdown content to extract name and description.
565
+
566
+ *flat_file* is passed only to compute the default ``name`` from the filename
567
+ stem when there is no frontmatter ``name``. *skill_md_file* points at the
568
+ actual markdown file (``dir_path/SKILL.md`` for subdir skills, the ``.md``
569
+ itself for flat ones) and is stamped onto the returned :class:`Skill`.
570
+
571
+ The ``description`` resolution applies the same three-step chain regardless
572
+ of form: frontmatter ``description`` → first non-empty body line →
573
+ ``"No description provided."``.
574
+ """
575
+ frontmatter = parse_frontmatter(content) or {}
576
+
577
+ default_name = _strip_md_suffix(flat_file.name) if flat_file is not None else dir_path.name
578
+ name = frontmatter.get("name") or default_name
579
+
580
+ description = frontmatter.get("description")
581
+ if not description:
582
+ body_fallback = _first_meaningful_line(content)
583
+ description = _truncate(body_fallback) if body_fallback else "No description provided."
584
+
585
+ skill_type = frontmatter.get("type") or "standard"
586
+ if skill_type not in ("standard", "flow"):
587
+ raise ValueError(f'Invalid skill type "{skill_type}"')
588
+ flow = None
589
+ if skill_type == "flow":
590
+ try:
591
+ flow = _parse_flow_from_skill(content)
592
+ except ValueError as exc:
593
+ logger.error("Failed to parse flow skill {name}: {error}", name=name, error=exc)
594
+ skill_type = "standard"
595
+ flow = None
596
+
597
+ return Skill(
598
+ name=name,
599
+ description=description,
600
+ type=skill_type,
601
+ dir=dir_path,
602
+ skill_md_file=skill_md_file,
603
+ flow=flow,
604
+ scope=scope,
605
+ )
606
+
607
+
608
+ def _strip_md_suffix(filename: str) -> str:
609
+ """Return *filename* without a trailing ``.md`` (case-insensitive)."""
610
+ if filename.lower().endswith(".md"):
611
+ return filename[: -len(".md")]
612
+ return filename
613
+
614
+
615
+ def _truncate(text: str, limit: int = _DESCRIPTION_FALLBACK_MAX_LEN) -> str:
616
+ """Clip *text* to *limit* chars, appending an ellipsis if truncated."""
617
+ text = text.strip()
618
+ if len(text) <= limit:
619
+ return text
620
+ # Reserve one char for the ellipsis
621
+ return text[: limit - 1].rstrip() + "…"
622
+
623
+
624
+ def _first_meaningful_line(content: str) -> str | None:
625
+ """Return the first non-empty line of *content*'s body, or None.
626
+
627
+ Uses :func:`strip_frontmatter` to drop any leading YAML block, so the
628
+ frontmatter-skipping logic lives in one place (see
629
+ :mod:`pythinker_code.utils.frontmatter`). A malformed frontmatter opener that
630
+ never closes leaves ``strip_frontmatter`` a no-op; skip standalone
631
+ ``---`` delimiter lines here so the stray opener does not silently
632
+ become the fallback description.
633
+ """
634
+ from pythinker_code.utils.frontmatter import strip_frontmatter
635
+
636
+ body = strip_frontmatter(content)
637
+ for line in body.splitlines():
638
+ stripped = line.strip()
639
+ if not stripped or stripped == "---":
640
+ continue
641
+ return stripped
642
+ return None
643
+
644
+
645
+ def _parse_flow_from_skill(content: str) -> Flow:
646
+ for lang, code in _iter_fenced_codeblocks(content):
647
+ if lang == "mermaid":
648
+ return _parse_flow_block(parse_mermaid_flowchart, code)
649
+ if lang == "d2":
650
+ return _parse_flow_block(parse_d2_flowchart, code)
651
+ raise ValueError("Flow skills require a mermaid or d2 code block in SKILL.md.")
652
+
653
+
654
+ def _parse_flow_block(parser: Callable[[str], Flow], code: str) -> Flow:
655
+ try:
656
+ return parser(code)
657
+ except FlowError as exc:
658
+ raise ValueError(f"Invalid flow diagram: {exc}") from exc
659
+
660
+
661
+ def _iter_fenced_codeblocks(content: str) -> Iterator[tuple[str, str]]:
662
+ fence = ""
663
+ fence_char = ""
664
+ lang = ""
665
+ buf: list[str] = []
666
+ in_block = False
667
+
668
+ for line in content.splitlines():
669
+ stripped = line.lstrip()
670
+ if not in_block:
671
+ if match := _parse_fence_open(stripped):
672
+ fence, fence_char, info = match
673
+ lang = _normalize_code_lang(info)
674
+ in_block = True
675
+ buf = []
676
+ continue
677
+
678
+ if _is_fence_close(stripped, fence_char, len(fence)):
679
+ yield lang, "\n".join(buf).strip("\n")
680
+ in_block = False
681
+ fence = ""
682
+ fence_char = ""
683
+ lang = ""
684
+ buf = []
685
+ continue
686
+
687
+ buf.append(line)
688
+
689
+
690
+ def _normalize_code_lang(info: str) -> str:
691
+ if not info:
692
+ return ""
693
+ lang = info.split()[0].strip().lower()
694
+ if lang.startswith("{") and lang.endswith("}"):
695
+ lang = lang[1:-1].strip()
696
+ return lang
697
+
698
+
699
+ def _parse_fence_open(line: str) -> tuple[str, str, str] | None:
700
+ if not line or line[0] not in ("`", "~"):
701
+ return None
702
+ fence_char = line[0]
703
+ count = 0
704
+ for ch in line:
705
+ if ch == fence_char:
706
+ count += 1
707
+ else:
708
+ break
709
+ if count < 3:
710
+ return None
711
+ fence = fence_char * count
712
+ info = line[count:].strip()
713
+ return fence, fence_char, info
714
+
715
+
716
+ def _is_fence_close(line: str, fence_char: str, fence_len: int) -> bool:
717
+ if not fence_char or not line or line[0] != fence_char:
718
+ return False
719
+ count = 0
720
+ for ch in line:
721
+ if ch == fence_char:
722
+ count += 1
723
+ else:
724
+ break
725
+ if count < fence_len:
726
+ return False
727
+ return not line[count:].strip()