pythinker-code 0.8.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 (790) hide show
  1. pythinker_code/CHANGELOG.md +60 -0
  2. pythinker_code/__init__.py +0 -0
  3. pythinker_code/__main__.py +97 -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 +301 -0
  8. pythinker_code/acp/mcp.py +46 -0
  9. pythinker_code/acp/server.py +497 -0
  10. pythinker_code/acp/session.py +502 -0
  11. pythinker_code/acp/tools.py +174 -0
  12. pythinker_code/acp/types.py +13 -0
  13. pythinker_code/acp/version.py +45 -0
  14. pythinker_code/agents/default/agent.yaml +55 -0
  15. pythinker_code/agents/default/code_reviewer.yaml +47 -0
  16. pythinker_code/agents/default/coder.yaml +42 -0
  17. pythinker_code/agents/default/debugger.yaml +35 -0
  18. pythinker_code/agents/default/explore.yaml +59 -0
  19. pythinker_code/agents/default/implementer.yaml +46 -0
  20. pythinker_code/agents/default/plan.yaml +42 -0
  21. pythinker_code/agents/default/review.yaml +47 -0
  22. pythinker_code/agents/default/security_reviewer.yaml +37 -0
  23. pythinker_code/agents/default/system.md +192 -0
  24. pythinker_code/agents/default/verifier.yaml +46 -0
  25. pythinker_code/agents/okabe/agent.yaml +22 -0
  26. pythinker_code/agentspec.py +163 -0
  27. pythinker_code/app.py +847 -0
  28. pythinker_code/approval_runtime/__init__.py +29 -0
  29. pythinker_code/approval_runtime/models.py +42 -0
  30. pythinker_code/approval_runtime/runtime.py +235 -0
  31. pythinker_code/auth/__init__.py +25 -0
  32. pythinker_code/auth/anthropic_direct.py +207 -0
  33. pythinker_code/auth/deepseek.py +192 -0
  34. pythinker_code/auth/github_feedback.py +228 -0
  35. pythinker_code/auth/lm_studio.py +418 -0
  36. pythinker_code/auth/minimax.py +203 -0
  37. pythinker_code/auth/oauth.py +1145 -0
  38. pythinker_code/auth/ollama.py +293 -0
  39. pythinker_code/auth/openai.py +783 -0
  40. pythinker_code/auth/opencode_go.py +203 -0
  41. pythinker_code/auth/openrouter.py +225 -0
  42. pythinker_code/auth/platforms.py +475 -0
  43. pythinker_code/background/__init__.py +36 -0
  44. pythinker_code/background/agent_runner.py +231 -0
  45. pythinker_code/background/ids.py +19 -0
  46. pythinker_code/background/manager.py +668 -0
  47. pythinker_code/background/models.py +118 -0
  48. pythinker_code/background/store.py +243 -0
  49. pythinker_code/background/summary.py +66 -0
  50. pythinker_code/background/worker.py +209 -0
  51. pythinker_code/cli/__init__.py +1326 -0
  52. pythinker_code/cli/__main__.py +19 -0
  53. pythinker_code/cli/_lazy_group.py +268 -0
  54. pythinker_code/cli/debug.py +11 -0
  55. pythinker_code/cli/export.py +322 -0
  56. pythinker_code/cli/info.py +62 -0
  57. pythinker_code/cli/mcp.py +362 -0
  58. pythinker_code/cli/plugin.py +351 -0
  59. pythinker_code/cli/review.py +74 -0
  60. pythinker_code/cli/secscan.py +11 -0
  61. pythinker_code/cli/security_scan.py +35 -0
  62. pythinker_code/cli/toad.py +74 -0
  63. pythinker_code/cli/update.py +26 -0
  64. pythinker_code/cli/vis.py +38 -0
  65. pythinker_code/cli/web.py +80 -0
  66. pythinker_code/config.py +511 -0
  67. pythinker_code/constant.py +33 -0
  68. pythinker_code/events.py +106 -0
  69. pythinker_code/exception.py +43 -0
  70. pythinker_code/extensions.py +151 -0
  71. pythinker_code/hooks/__init__.py +4 -0
  72. pythinker_code/hooks/config.py +34 -0
  73. pythinker_code/hooks/engine.py +383 -0
  74. pythinker_code/hooks/events.py +190 -0
  75. pythinker_code/hooks/runner.py +92 -0
  76. pythinker_code/llm.py +441 -0
  77. pythinker_code/metadata.py +79 -0
  78. pythinker_code/notifications/__init__.py +33 -0
  79. pythinker_code/notifications/llm.py +77 -0
  80. pythinker_code/notifications/manager.py +145 -0
  81. pythinker_code/notifications/models.py +50 -0
  82. pythinker_code/notifications/notifier.py +41 -0
  83. pythinker_code/notifications/store.py +118 -0
  84. pythinker_code/notifications/wire.py +21 -0
  85. pythinker_code/plugin/__init__.py +124 -0
  86. pythinker_code/plugin/manager.py +166 -0
  87. pythinker_code/plugin/tool.py +173 -0
  88. pythinker_code/prompt_templates.py +181 -0
  89. pythinker_code/prompts/__init__.py +6 -0
  90. pythinker_code/prompts/compact.md +73 -0
  91. pythinker_code/prompts/init.md +21 -0
  92. pythinker_code/py.typed +0 -0
  93. pythinker_code/session.py +319 -0
  94. pythinker_code/session_fork.py +325 -0
  95. pythinker_code/session_state.py +132 -0
  96. pythinker_code/share.py +14 -0
  97. pythinker_code/skill/__init__.py +727 -0
  98. pythinker_code/skill/flow/__init__.py +99 -0
  99. pythinker_code/skill/flow/d2.py +482 -0
  100. pythinker_code/skill/flow/mermaid.py +266 -0
  101. pythinker_code/skills/pythinker-code-help/SKILL.md +54 -0
  102. pythinker_code/skills/skill-creator/SKILL.md +367 -0
  103. pythinker_code/soul/__init__.py +304 -0
  104. pythinker_code/soul/agent.py +552 -0
  105. pythinker_code/soul/approval.py +267 -0
  106. pythinker_code/soul/btw.py +220 -0
  107. pythinker_code/soul/compaction.py +189 -0
  108. pythinker_code/soul/context.py +339 -0
  109. pythinker_code/soul/denwarenji.py +39 -0
  110. pythinker_code/soul/dynamic_injection.py +84 -0
  111. pythinker_code/soul/dynamic_injections/__init__.py +0 -0
  112. pythinker_code/soul/dynamic_injections/auto_mode.py +72 -0
  113. pythinker_code/soul/dynamic_injections/plan_mode.py +239 -0
  114. pythinker_code/soul/message.py +92 -0
  115. pythinker_code/soul/permission.py +368 -0
  116. pythinker_code/soul/pythinkersoul.py +1763 -0
  117. pythinker_code/soul/slash.py +340 -0
  118. pythinker_code/soul/toolset.py +826 -0
  119. pythinker_code/subagents/__init__.py +21 -0
  120. pythinker_code/subagents/builder.py +43 -0
  121. pythinker_code/subagents/core.py +86 -0
  122. pythinker_code/subagents/discovery.py +234 -0
  123. pythinker_code/subagents/git_context.py +172 -0
  124. pythinker_code/subagents/models.py +56 -0
  125. pythinker_code/subagents/output.py +71 -0
  126. pythinker_code/subagents/registry.py +28 -0
  127. pythinker_code/subagents/runner.py +442 -0
  128. pythinker_code/subagents/store.py +200 -0
  129. pythinker_code/telemetry/__init__.py +217 -0
  130. pythinker_code/telemetry/config.py +113 -0
  131. pythinker_code/telemetry/crash.py +191 -0
  132. pythinker_code/telemetry/errors.py +113 -0
  133. pythinker_code/telemetry/metrics.py +208 -0
  134. pythinker_code/telemetry/otel.py +303 -0
  135. pythinker_code/telemetry/sentry.py +212 -0
  136. pythinker_code/telemetry/sink.py +189 -0
  137. pythinker_code/tools/AGENTS.md +6 -0
  138. pythinker_code/tools/__init__.py +105 -0
  139. pythinker_code/tools/agent/__init__.py +326 -0
  140. pythinker_code/tools/agent/description.md +65 -0
  141. pythinker_code/tools/ask_user/__init__.py +162 -0
  142. pythinker_code/tools/ask_user/description.md +19 -0
  143. pythinker_code/tools/background/__init__.py +318 -0
  144. pythinker_code/tools/background/list.md +10 -0
  145. pythinker_code/tools/background/output.md +11 -0
  146. pythinker_code/tools/background/stop.md +8 -0
  147. pythinker_code/tools/display.py +46 -0
  148. pythinker_code/tools/dmail/__init__.py +38 -0
  149. pythinker_code/tools/dmail/dmail.md +17 -0
  150. pythinker_code/tools/file/__init__.py +31 -0
  151. pythinker_code/tools/file/glob.md +17 -0
  152. pythinker_code/tools/file/glob.py +163 -0
  153. pythinker_code/tools/file/grep.md +6 -0
  154. pythinker_code/tools/file/grep_local.py +904 -0
  155. pythinker_code/tools/file/plan_mode.py +45 -0
  156. pythinker_code/tools/file/read.md +16 -0
  157. pythinker_code/tools/file/read.py +303 -0
  158. pythinker_code/tools/file/read_media.md +24 -0
  159. pythinker_code/tools/file/read_media.py +220 -0
  160. pythinker_code/tools/file/replace.md +7 -0
  161. pythinker_code/tools/file/replace.py +204 -0
  162. pythinker_code/tools/file/utils.py +257 -0
  163. pythinker_code/tools/file/write.md +5 -0
  164. pythinker_code/tools/file/write.py +186 -0
  165. pythinker_code/tools/plan/__init__.py +362 -0
  166. pythinker_code/tools/plan/description.md +29 -0
  167. pythinker_code/tools/plan/enter.py +193 -0
  168. pythinker_code/tools/plan/enter_description.md +35 -0
  169. pythinker_code/tools/plan/handoff.py +69 -0
  170. pythinker_code/tools/plan/heroes.py +277 -0
  171. pythinker_code/tools/shell/__init__.py +263 -0
  172. pythinker_code/tools/shell/bash.md +35 -0
  173. pythinker_code/tools/shell/powershell.md +30 -0
  174. pythinker_code/tools/test.py +55 -0
  175. pythinker_code/tools/think/__init__.py +21 -0
  176. pythinker_code/tools/think/think.md +1 -0
  177. pythinker_code/tools/todo/__init__.py +168 -0
  178. pythinker_code/tools/todo/set_todo_list.md +23 -0
  179. pythinker_code/tools/utils.py +200 -0
  180. pythinker_code/tools/web/__init__.py +4 -0
  181. pythinker_code/tools/web/fetch.md +1 -0
  182. pythinker_code/tools/web/fetch.py +261 -0
  183. pythinker_code/tools/web/search.md +1 -0
  184. pythinker_code/tools/web/search.py +163 -0
  185. pythinker_code/ui/__init__.py +0 -0
  186. pythinker_code/ui/acp/__init__.py +99 -0
  187. pythinker_code/ui/print/__init__.py +474 -0
  188. pythinker_code/ui/print/visualize.py +185 -0
  189. pythinker_code/ui/shell/__init__.py +1806 -0
  190. pythinker_code/ui/shell/components/__init__.py +110 -0
  191. pythinker_code/ui/shell/components/base.py +25 -0
  192. pythinker_code/ui/shell/components/bash_execution.py +249 -0
  193. pythinker_code/ui/shell/components/bordered_loader.py +62 -0
  194. pythinker_code/ui/shell/components/diff.py +308 -0
  195. pythinker_code/ui/shell/components/footer.py +231 -0
  196. pythinker_code/ui/shell/components/key_hints.py +27 -0
  197. pythinker_code/ui/shell/components/messages.py +152 -0
  198. pythinker_code/ui/shell/components/render_utils.py +198 -0
  199. pythinker_code/ui/shell/components/settings_list.py +369 -0
  200. pythinker_code/ui/shell/components/special_messages.py +125 -0
  201. pythinker_code/ui/shell/components/tool_execution.py +261 -0
  202. pythinker_code/ui/shell/console.py +109 -0
  203. pythinker_code/ui/shell/debug.py +190 -0
  204. pythinker_code/ui/shell/echo.py +30 -0
  205. pythinker_code/ui/shell/export_import.py +117 -0
  206. pythinker_code/ui/shell/keyboard.py +300 -0
  207. pythinker_code/ui/shell/keymap.py +84 -0
  208. pythinker_code/ui/shell/mcp_status.py +112 -0
  209. pythinker_code/ui/shell/model_picker.py +318 -0
  210. pythinker_code/ui/shell/oauth.py +273 -0
  211. pythinker_code/ui/shell/placeholders.py +578 -0
  212. pythinker_code/ui/shell/prompt.py +2888 -0
  213. pythinker_code/ui/shell/replay.py +215 -0
  214. pythinker_code/ui/shell/selector.py +364 -0
  215. pythinker_code/ui/shell/selectors/__init__.py +38 -0
  216. pythinker_code/ui/shell/selectors/extension.py +37 -0
  217. pythinker_code/ui/shell/selectors/oauth.py +63 -0
  218. pythinker_code/ui/shell/selectors/settings.py +349 -0
  219. pythinker_code/ui/shell/selectors/show_images.py +29 -0
  220. pythinker_code/ui/shell/selectors/theme.py +28 -0
  221. pythinker_code/ui/shell/selectors/thinking.py +42 -0
  222. pythinker_code/ui/shell/session_picker.py +227 -0
  223. pythinker_code/ui/shell/setup.py +212 -0
  224. pythinker_code/ui/shell/slash.py +1433 -0
  225. pythinker_code/ui/shell/spinner_words.py +222 -0
  226. pythinker_code/ui/shell/startup.py +32 -0
  227. pythinker_code/ui/shell/task_browser.py +486 -0
  228. pythinker_code/ui/shell/tool_renderers/__init__.py +197 -0
  229. pythinker_code/ui/shell/tool_renderers/_render_utils.py +168 -0
  230. pythinker_code/ui/shell/tool_renderers/agent.py +140 -0
  231. pythinker_code/ui/shell/tool_renderers/ask_user.py +93 -0
  232. pythinker_code/ui/shell/tool_renderers/background.py +144 -0
  233. pythinker_code/ui/shell/tool_renderers/bash.py +103 -0
  234. pythinker_code/ui/shell/tool_renderers/edit.py +163 -0
  235. pythinker_code/ui/shell/tool_renderers/find.py +81 -0
  236. pythinker_code/ui/shell/tool_renderers/generic.py +60 -0
  237. pythinker_code/ui/shell/tool_renderers/grep.py +98 -0
  238. pythinker_code/ui/shell/tool_renderers/plan.py +98 -0
  239. pythinker_code/ui/shell/tool_renderers/read.py +103 -0
  240. pythinker_code/ui/shell/tool_renderers/think.py +66 -0
  241. pythinker_code/ui/shell/tool_renderers/todo.py +164 -0
  242. pythinker_code/ui/shell/tool_renderers/web.py +128 -0
  243. pythinker_code/ui/shell/tool_renderers/write.py +102 -0
  244. pythinker_code/ui/shell/update.py +352 -0
  245. pythinker_code/ui/shell/usage.py +291 -0
  246. pythinker_code/ui/shell/usage_adapters/__init__.py +50 -0
  247. pythinker_code/ui/shell/usage_adapters/anthropic_admin.py +233 -0
  248. pythinker_code/ui/shell/usage_adapters/base.py +72 -0
  249. pythinker_code/ui/shell/usage_adapters/deepseek.py +137 -0
  250. pythinker_code/ui/shell/usage_adapters/minimax.py +236 -0
  251. pythinker_code/ui/shell/usage_adapters/openai_admin.py +225 -0
  252. pythinker_code/ui/shell/usage_adapters/openai_chatgpt.py +241 -0
  253. pythinker_code/ui/shell/usage_adapters/opencode_go.py +232 -0
  254. pythinker_code/ui/shell/usage_adapters/openrouter.py +105 -0
  255. pythinker_code/ui/shell/usage_adapters/pythinker.py +189 -0
  256. pythinker_code/ui/shell/usage_adapters/pythinker_ai.py +50 -0
  257. pythinker_code/ui/shell/usage_render.py +150 -0
  258. pythinker_code/ui/shell/visualize/__init__.py +165 -0
  259. pythinker_code/ui/shell/visualize/_approval_panel.py +539 -0
  260. pythinker_code/ui/shell/visualize/_blocks.py +802 -0
  261. pythinker_code/ui/shell/visualize/_btw_panel.py +227 -0
  262. pythinker_code/ui/shell/visualize/_input_router.py +48 -0
  263. pythinker_code/ui/shell/visualize/_interactive.py +531 -0
  264. pythinker_code/ui/shell/visualize/_live_view.py +891 -0
  265. pythinker_code/ui/shell/visualize/_question_panel.py +586 -0
  266. pythinker_code/ui/shell/visualize/_worklog.py +245 -0
  267. pythinker_code/ui/theme.py +395 -0
  268. pythinker_code/ui/tui_config.py +82 -0
  269. pythinker_code/usage_ratelimit_cache.py +175 -0
  270. pythinker_code/utils/__init__.py +0 -0
  271. pythinker_code/utils/aiohttp.py +24 -0
  272. pythinker_code/utils/aioqueue.py +72 -0
  273. pythinker_code/utils/broadcast.py +38 -0
  274. pythinker_code/utils/changelog.py +108 -0
  275. pythinker_code/utils/clipboard.py +246 -0
  276. pythinker_code/utils/datetime.py +64 -0
  277. pythinker_code/utils/diff.py +135 -0
  278. pythinker_code/utils/editor.py +91 -0
  279. pythinker_code/utils/environment.py +73 -0
  280. pythinker_code/utils/envvar.py +22 -0
  281. pythinker_code/utils/export.py +696 -0
  282. pythinker_code/utils/file_filter.py +375 -0
  283. pythinker_code/utils/frontmatter.py +70 -0
  284. pythinker_code/utils/io.py +27 -0
  285. pythinker_code/utils/logging.py +146 -0
  286. pythinker_code/utils/media_tags.py +29 -0
  287. pythinker_code/utils/message.py +24 -0
  288. pythinker_code/utils/path.py +199 -0
  289. pythinker_code/utils/proctitle.py +33 -0
  290. pythinker_code/utils/proxy.py +31 -0
  291. pythinker_code/utils/pyinstaller.py +45 -0
  292. pythinker_code/utils/rich/__init__.py +33 -0
  293. pythinker_code/utils/rich/columns.py +99 -0
  294. pythinker_code/utils/rich/diff_render.py +481 -0
  295. pythinker_code/utils/rich/markdown.py +935 -0
  296. pythinker_code/utils/rich/markdown_sample.md +108 -0
  297. pythinker_code/utils/rich/markdown_sample_short.md +2 -0
  298. pythinker_code/utils/rich/syntax.py +114 -0
  299. pythinker_code/utils/sensitive.py +54 -0
  300. pythinker_code/utils/server.py +121 -0
  301. pythinker_code/utils/signals.py +43 -0
  302. pythinker_code/utils/slashcmd.py +124 -0
  303. pythinker_code/utils/string.py +41 -0
  304. pythinker_code/utils/subprocess_env.py +83 -0
  305. pythinker_code/utils/term.py +168 -0
  306. pythinker_code/utils/typing.py +20 -0
  307. pythinker_code/vis/__init__.py +0 -0
  308. pythinker_code/vis/api/__init__.py +5 -0
  309. pythinker_code/vis/api/sessions.py +714 -0
  310. pythinker_code/vis/api/statistics.py +209 -0
  311. pythinker_code/vis/api/system.py +19 -0
  312. pythinker_code/vis/app.py +199 -0
  313. pythinker_code/vis/static/assets/highlighted-body-B3W2YXNL-CY1rtwrX.js +1 -0
  314. pythinker_code/vis/static/assets/index-DSRInNnm.css +1 -0
  315. pythinker_code/vis/static/assets/index-DgmTI2M_.js +185 -0
  316. pythinker_code/vis/static/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  317. pythinker_code/vis/static/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  318. pythinker_code/vis/static/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  319. pythinker_code/vis/static/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  320. pythinker_code/vis/static/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  321. pythinker_code/vis/static/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  322. pythinker_code/vis/static/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  323. pythinker_code/vis/static/index.html +17 -0
  324. pythinker_code/web/__init__.py +5 -0
  325. pythinker_code/web/api/__init__.py +15 -0
  326. pythinker_code/web/api/config.py +217 -0
  327. pythinker_code/web/api/open_in.py +233 -0
  328. pythinker_code/web/api/sessions.py +1256 -0
  329. pythinker_code/web/app.py +449 -0
  330. pythinker_code/web/auth.py +191 -0
  331. pythinker_code/web/models.py +98 -0
  332. pythinker_code/web/runner/__init__.py +5 -0
  333. pythinker_code/web/runner/messages.py +57 -0
  334. pythinker_code/web/runner/process.py +754 -0
  335. pythinker_code/web/runner/worker.py +97 -0
  336. pythinker_code/web/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  337. pythinker_code/web/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  338. pythinker_code/web/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  339. pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  340. pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  341. pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  342. pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  343. pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  344. pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  345. pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  346. pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  347. pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  348. pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  349. pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  350. pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  351. pythinker_code/web/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  352. pythinker_code/web/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  353. pythinker_code/web/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  354. pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  355. pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  356. pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  357. pythinker_code/web/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  358. pythinker_code/web/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  359. pythinker_code/web/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  360. pythinker_code/web/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  361. pythinker_code/web/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  362. pythinker_code/web/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  363. pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  364. pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  365. pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  366. pythinker_code/web/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  367. pythinker_code/web/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  368. pythinker_code/web/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  369. pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  370. pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  371. pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  372. pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  373. pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  374. pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  375. pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  376. pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  377. pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  378. pythinker_code/web/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  379. pythinker_code/web/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  380. pythinker_code/web/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  381. pythinker_code/web/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  382. pythinker_code/web/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  383. pythinker_code/web/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  384. pythinker_code/web/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  385. pythinker_code/web/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  386. pythinker_code/web/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  387. pythinker_code/web/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  388. pythinker_code/web/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  389. pythinker_code/web/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  390. pythinker_code/web/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  391. pythinker_code/web/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  392. pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  393. pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  394. pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  395. pythinker_code/web/static/assets/_baseUniq-DpSMr1jx.js +1 -0
  396. pythinker_code/web/static/assets/abap-BdImnpbu.js +1 -0
  397. pythinker_code/web/static/assets/actionscript-3-CfeIJUat.js +1 -0
  398. pythinker_code/web/static/assets/ada-bCR0ucgS.js +1 -0
  399. pythinker_code/web/static/assets/andromeeda-C-Jbm3Hp.js +1 -0
  400. pythinker_code/web/static/assets/angular-html-CU67Zn6k.js +1 -0
  401. pythinker_code/web/static/assets/angular-ts-BwZT4LLn.js +1 -0
  402. pythinker_code/web/static/assets/apache-Pmp26Uib.js +1 -0
  403. pythinker_code/web/static/assets/apex-D8_7TLub.js +1 -0
  404. pythinker_code/web/static/assets/apl-dKokRX4l.js +1 -0
  405. pythinker_code/web/static/assets/applescript-Co6uUVPk.js +1 -0
  406. pythinker_code/web/static/assets/ara-BRHolxvo.js +1 -0
  407. pythinker_code/web/static/assets/arc-DpsahJyV.js +1 -0
  408. pythinker_code/web/static/assets/architectureDiagram-VXUJARFQ-DqiRv9Eg.js +36 -0
  409. pythinker_code/web/static/assets/asciidoc-Dv7Oe6Be.js +1 -0
  410. pythinker_code/web/static/assets/asm-D_Q5rh1f.js +1 -0
  411. pythinker_code/web/static/assets/astro-CbQHKStN.js +1 -0
  412. pythinker_code/web/static/assets/aurora-x-D-2ljcwZ.js +1 -0
  413. pythinker_code/web/static/assets/awk-DMzUqQB5.js +1 -0
  414. pythinker_code/web/static/assets/ayu-dark-CmMr59Fi.js +1 -0
  415. pythinker_code/web/static/assets/ballerina-BFfxhgS-.js +1 -0
  416. pythinker_code/web/static/assets/bat-BkioyH1T.js +1 -0
  417. pythinker_code/web/static/assets/beancount-k_qm7-4y.js +1 -0
  418. pythinker_code/web/static/assets/berry-uYugtg8r.js +1 -0
  419. pythinker_code/web/static/assets/bibtex-CHM0blh-.js +1 -0
  420. pythinker_code/web/static/assets/bicep-Bmn6On1c.js +1 -0
  421. pythinker_code/web/static/assets/blade-D4QpJJKB.js +1 -0
  422. pythinker_code/web/static/assets/blockDiagram-VD42YOAC-WgtUvqbp.js +122 -0
  423. pythinker_code/web/static/assets/bsl-BO_Y6i37.js +1 -0
  424. pythinker_code/web/static/assets/c-BIGW1oBm.js +1 -0
  425. pythinker_code/web/static/assets/c3-VCDPK7BO.js +1 -0
  426. pythinker_code/web/static/assets/c4Diagram-YG6GDRKO-rK0RPuZd.js +10 -0
  427. pythinker_code/web/static/assets/cadence-Bv_4Rxtq.js +1 -0
  428. pythinker_code/web/static/assets/cairo-KRGpt6FW.js +1 -0
  429. pythinker_code/web/static/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  430. pythinker_code/web/static/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  431. pythinker_code/web/static/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  432. pythinker_code/web/static/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  433. pythinker_code/web/static/assets/channel-B0rlvkH-.js +1 -0
  434. pythinker_code/web/static/assets/chunk-4BX2VUAB-DIkMuLV-.js +1 -0
  435. pythinker_code/web/static/assets/chunk-55IACEB6-CORdm4k4.js +1 -0
  436. pythinker_code/web/static/assets/chunk-B4BG7PRW-D9xDhwHO.js +165 -0
  437. pythinker_code/web/static/assets/chunk-DI55MBZ5-BDmF9Bh-.js +220 -0
  438. pythinker_code/web/static/assets/chunk-FMBD7UC4-BCse_HmM.js +15 -0
  439. pythinker_code/web/static/assets/chunk-QN33PNHL-DCpBmTzA.js +1 -0
  440. pythinker_code/web/static/assets/chunk-QZHKN3VN-BqLuqobw.js +1 -0
  441. pythinker_code/web/static/assets/chunk-TZMSLE5B-8K2ogOKS.js +1 -0
  442. pythinker_code/web/static/assets/clarity-D53aC0YG.js +1 -0
  443. pythinker_code/web/static/assets/classDiagram-2ON5EDUG-D_ZHSii2.js +1 -0
  444. pythinker_code/web/static/assets/classDiagram-v2-WZHVMYZB-D_ZHSii2.js +1 -0
  445. pythinker_code/web/static/assets/clojure-P80f7IUj.js +1 -0
  446. pythinker_code/web/static/assets/clone-GSXejyY1.js +1 -0
  447. pythinker_code/web/static/assets/cmake-D1j8_8rp.js +1 -0
  448. pythinker_code/web/static/assets/cobol-nwyudZeR.js +1 -0
  449. pythinker_code/web/static/assets/code-block-IT6T5CEO-DWTFYA28.js +2 -0
  450. pythinker_code/web/static/assets/codeowners-Bp6g37R7.js +1 -0
  451. pythinker_code/web/static/assets/codeql-DsOJ9woJ.js +1 -0
  452. pythinker_code/web/static/assets/coffee-Ch7k5sss.js +1 -0
  453. pythinker_code/web/static/assets/common-lisp-Cg-RD9OK.js +1 -0
  454. pythinker_code/web/static/assets/coq-DkFqJrB1.js +1 -0
  455. pythinker_code/web/static/assets/cose-bilkent-S5V4N54A-BRI7ES-N.js +1 -0
  456. pythinker_code/web/static/assets/cpp-CofmeUqb.js +1 -0
  457. pythinker_code/web/static/assets/crystal-tKQVLTB8.js +1 -0
  458. pythinker_code/web/static/assets/csharp-K5feNrxe.js +1 -0
  459. pythinker_code/web/static/assets/css-DPfMkruS.js +1 -0
  460. pythinker_code/web/static/assets/csv-fuZLfV_i.js +1 -0
  461. pythinker_code/web/static/assets/cue-D82EKSYY.js +1 -0
  462. pythinker_code/web/static/assets/cypher-COkxafJQ.js +1 -0
  463. pythinker_code/web/static/assets/cytoscape.esm-B6BxUuKW.js +321 -0
  464. pythinker_code/web/static/assets/d-85-TOEBH.js +1 -0
  465. pythinker_code/web/static/assets/dagre-6UL2VRFP-Ci5GdWfi.js +4 -0
  466. pythinker_code/web/static/assets/dark-plus-C3mMm8J8.js +1 -0
  467. pythinker_code/web/static/assets/dart-CF10PKvl.js +1 -0
  468. pythinker_code/web/static/assets/dax-CEL-wOlO.js +1 -0
  469. pythinker_code/web/static/assets/defaultLocale-DX6XiGOO.js +1 -0
  470. pythinker_code/web/static/assets/desktop-BmXAJ9_W.js +1 -0
  471. pythinker_code/web/static/assets/diagram-PSM6KHXK-0hhAylV4.js +24 -0
  472. pythinker_code/web/static/assets/diagram-QEK2KX5R-8fxgaW6d.js +43 -0
  473. pythinker_code/web/static/assets/diagram-S2PKOQOG-FRr0_atE.js +24 -0
  474. pythinker_code/web/static/assets/diff-D97Zzqfu.js +1 -0
  475. pythinker_code/web/static/assets/docker-BcOcwvcX.js +1 -0
  476. pythinker_code/web/static/assets/dotenv-Da5cRb03.js +1 -0
  477. pythinker_code/web/static/assets/dracula-BzJJZx-M.js +1 -0
  478. pythinker_code/web/static/assets/dracula-soft-BXkSAIEj.js +1 -0
  479. pythinker_code/web/static/assets/dream-maker-BtqSS_iP.js +1 -0
  480. pythinker_code/web/static/assets/edge-BkV0erSs.js +1 -0
  481. pythinker_code/web/static/assets/elixir-CDX3lj18.js +1 -0
  482. pythinker_code/web/static/assets/elm-DbKCFpqz.js +1 -0
  483. pythinker_code/web/static/assets/emacs-lisp-C9XAeP06.js +1 -0
  484. pythinker_code/web/static/assets/erDiagram-Q2GNP2WA-B3T-hJUM.js +60 -0
  485. pythinker_code/web/static/assets/erb-BOJIQeun.js +1 -0
  486. pythinker_code/web/static/assets/erlang-DsQrWhSR.js +1 -0
  487. pythinker_code/web/static/assets/everforest-dark-BgDCqdQA.js +1 -0
  488. pythinker_code/web/static/assets/everforest-light-C8M2exoo.js +1 -0
  489. pythinker_code/web/static/assets/fennel-BYunw83y.js +1 -0
  490. pythinker_code/web/static/assets/fish-BvzEVeQv.js +1 -0
  491. pythinker_code/web/static/assets/flowDiagram-NV44I4VS-D0S3u7ot.js +162 -0
  492. pythinker_code/web/static/assets/fluent-C4IJs8-o.js +1 -0
  493. pythinker_code/web/static/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
  494. pythinker_code/web/static/assets/fortran-free-form-BxgE0vQu.js +1 -0
  495. pythinker_code/web/static/assets/fsharp-CXgrBDvD.js +1 -0
  496. pythinker_code/web/static/assets/ganttDiagram-JELNMOA3-CHrN2a23.js +267 -0
  497. pythinker_code/web/static/assets/gdresource-B7Tvp0Sc.js +1 -0
  498. pythinker_code/web/static/assets/gdscript-DTMYz4Jt.js +1 -0
  499. pythinker_code/web/static/assets/gdshader-DkwncUOv.js +1 -0
  500. pythinker_code/web/static/assets/genie-D0YGMca9.js +1 -0
  501. pythinker_code/web/static/assets/gherkin-DyxjwDmM.js +1 -0
  502. pythinker_code/web/static/assets/git-commit-F4YmCXRG.js +1 -0
  503. pythinker_code/web/static/assets/git-rebase-r7XF79zn.js +1 -0
  504. pythinker_code/web/static/assets/gitGraphDiagram-NY62KEGX-CfcXZWg0.js +65 -0
  505. pythinker_code/web/static/assets/github-dark-DHJKELXO.js +1 -0
  506. pythinker_code/web/static/assets/github-dark-default-Cuk6v7N8.js +1 -0
  507. pythinker_code/web/static/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  508. pythinker_code/web/static/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  509. pythinker_code/web/static/assets/github-light-DAi9KRSo.js +1 -0
  510. pythinker_code/web/static/assets/github-light-default-D7oLnXFd.js +1 -0
  511. pythinker_code/web/static/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  512. pythinker_code/web/static/assets/gleam-BspZqrRM.js +1 -0
  513. pythinker_code/web/static/assets/glimmer-js-Rg0-pVw9.js +1 -0
  514. pythinker_code/web/static/assets/glimmer-ts-U6CK756n.js +1 -0
  515. pythinker_code/web/static/assets/glsl-DplSGwfg.js +1 -0
  516. pythinker_code/web/static/assets/gn-n2N0HUVH.js +1 -0
  517. pythinker_code/web/static/assets/gnuplot-DdkO51Og.js +1 -0
  518. pythinker_code/web/static/assets/go-Dn2_MT6a.js +1 -0
  519. pythinker_code/web/static/assets/graph-8jMJwCqE.js +1 -0
  520. pythinker_code/web/static/assets/graphql-ChdNCCLP.js +1 -0
  521. pythinker_code/web/static/assets/groovy-gcz8RCvz.js +1 -0
  522. pythinker_code/web/static/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  523. pythinker_code/web/static/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  524. pythinker_code/web/static/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  525. pythinker_code/web/static/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  526. pythinker_code/web/static/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  527. pythinker_code/web/static/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  528. pythinker_code/web/static/assets/hack-CaT9iCJl.js +1 -0
  529. pythinker_code/web/static/assets/haml-B8DHNrY2.js +1 -0
  530. pythinker_code/web/static/assets/handlebars-BL8al0AC.js +1 -0
  531. pythinker_code/web/static/assets/haskell-Df6bDoY_.js +1 -0
  532. pythinker_code/web/static/assets/haxe-CzTSHFRz.js +1 -0
  533. pythinker_code/web/static/assets/hcl-BWvSN4gD.js +1 -0
  534. pythinker_code/web/static/assets/hjson-D5-asLiD.js +1 -0
  535. pythinker_code/web/static/assets/hlsl-D3lLCCz7.js +1 -0
  536. pythinker_code/web/static/assets/houston-DnULxvSX.js +1 -0
  537. pythinker_code/web/static/assets/html-GMplVEZG.js +1 -0
  538. pythinker_code/web/static/assets/html-derivative-BFtXZ54Q.js +1 -0
  539. pythinker_code/web/static/assets/http-jrhK8wxY.js +1 -0
  540. pythinker_code/web/static/assets/hurl-irOxFIW8.js +1 -0
  541. pythinker_code/web/static/assets/hxml-Bvhsp5Yf.js +1 -0
  542. pythinker_code/web/static/assets/hy-DFXneXwc.js +1 -0
  543. pythinker_code/web/static/assets/imba-DGztddWO.js +1 -0
  544. pythinker_code/web/static/assets/index-BXrFnzMy.js +153 -0
  545. pythinker_code/web/static/assets/index-BpoLgcEt.js +1 -0
  546. pythinker_code/web/static/assets/index-BrfQJnRD.js +5 -0
  547. pythinker_code/web/static/assets/index-C4gFzubz.js +2 -0
  548. pythinker_code/web/static/assets/index-CzV_vCfu.css +1 -0
  549. pythinker_code/web/static/assets/index-DI2oedCt.js +19 -0
  550. pythinker_code/web/static/assets/infoDiagram-WHAUD3N6-DdxonBf3.js +2 -0
  551. pythinker_code/web/static/assets/ini-BEwlwnbL.js +1 -0
  552. pythinker_code/web/static/assets/init-Gi6I4Gst.js +1 -0
  553. pythinker_code/web/static/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  554. pythinker_code/web/static/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  555. pythinker_code/web/static/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  556. pythinker_code/web/static/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  557. pythinker_code/web/static/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  558. pythinker_code/web/static/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  559. pythinker_code/web/static/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  560. pythinker_code/web/static/assets/java-CylS5w8V.js +1 -0
  561. pythinker_code/web/static/assets/javascript-wDzz0qaB.js +1 -0
  562. pythinker_code/web/static/assets/jinja-4LBKfQ-Z.js +1 -0
  563. pythinker_code/web/static/assets/jison-wvAkD_A8.js +1 -0
  564. pythinker_code/web/static/assets/journeyDiagram-XKPGCS4Q-BXf4aQei.js +139 -0
  565. pythinker_code/web/static/assets/json-Cp-IABpG.js +1 -0
  566. pythinker_code/web/static/assets/json5-C9tS-k6U.js +1 -0
  567. pythinker_code/web/static/assets/jsonc-Des-eS-w.js +1 -0
  568. pythinker_code/web/static/assets/jsonl-DcaNXYhu.js +1 -0
  569. pythinker_code/web/static/assets/jsonnet-DFQXde-d.js +1 -0
  570. pythinker_code/web/static/assets/jssm-C2t-YnRu.js +1 -0
  571. pythinker_code/web/static/assets/jsx-g9-lgVsj.js +1 -0
  572. pythinker_code/web/static/assets/julia-CxzCAyBv.js +1 -0
  573. pythinker_code/web/static/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  574. pythinker_code/web/static/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  575. pythinker_code/web/static/assets/kanagawa-wave-DWedfzmr.js +1 -0
  576. pythinker_code/web/static/assets/kanban-definition-3W4ZIXB7-DLpPPOu8.js +89 -0
  577. pythinker_code/web/static/assets/katex-D2lIc1rk.css +1 -0
  578. pythinker_code/web/static/assets/kdl-DV7GczEv.js +1 -0
  579. pythinker_code/web/static/assets/kotlin-BdnUsdx6.js +1 -0
  580. pythinker_code/web/static/assets/kusto-DZf3V79B.js +1 -0
  581. pythinker_code/web/static/assets/laserwave-DUszq2jm.js +1 -0
  582. pythinker_code/web/static/assets/latex-B4uzh10-.js +1 -0
  583. pythinker_code/web/static/assets/layout-DH73UoAH.js +1 -0
  584. pythinker_code/web/static/assets/lean-BZvkOJ9d.js +1 -0
  585. pythinker_code/web/static/assets/less-B1dDrJ26.js +1 -0
  586. pythinker_code/web/static/assets/light-plus-B7mTdjB0.js +1 -0
  587. pythinker_code/web/static/assets/linear-bAer2-sK.js +1 -0
  588. pythinker_code/web/static/assets/liquid-DYVedYrR.js +1 -0
  589. pythinker_code/web/static/assets/llvm-BtvRca6l.js +1 -0
  590. pythinker_code/web/static/assets/log-2UxHyX5q.js +1 -0
  591. pythinker_code/web/static/assets/logo-BtOb2qkB.js +1 -0
  592. pythinker_code/web/static/assets/lua-BbnMAYS6.js +1 -0
  593. pythinker_code/web/static/assets/luau-C-HG3fhB.js +1 -0
  594. pythinker_code/web/static/assets/make-CHLpvVh8.js +1 -0
  595. pythinker_code/web/static/assets/markdown-Cvjx9yec.js +1 -0
  596. pythinker_code/web/static/assets/marko-DZsq8hO1.js +1 -0
  597. pythinker_code/web/static/assets/material-theme-D5KoaKCx.js +1 -0
  598. pythinker_code/web/static/assets/material-theme-darker-BfHTSMKl.js +1 -0
  599. pythinker_code/web/static/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  600. pythinker_code/web/static/assets/material-theme-ocean-CyktbL80.js +1 -0
  601. pythinker_code/web/static/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  602. pythinker_code/web/static/assets/matlab-D7o27uSR.js +1 -0
  603. pythinker_code/web/static/assets/mdc-DUICxH0z.js +1 -0
  604. pythinker_code/web/static/assets/mdx-Cmh6b_Ma.js +1 -0
  605. pythinker_code/web/static/assets/mermaid-VLURNSYL-B2P5VJ9v.css +1 -0
  606. pythinker_code/web/static/assets/mermaid-VLURNSYL-CuqbwKXv.js +465 -0
  607. pythinker_code/web/static/assets/mermaid-mWjccvbQ.js +1 -0
  608. pythinker_code/web/static/assets/mermaid.core-Nx-rTKiV.js +191 -0
  609. pythinker_code/web/static/assets/min-DbfD8Ywu.js +1 -0
  610. pythinker_code/web/static/assets/min-dark-CafNBF8u.js +1 -0
  611. pythinker_code/web/static/assets/min-light-CTRr51gU.js +1 -0
  612. pythinker_code/web/static/assets/mindmap-definition-VGOIOE7T-C6l761Ue.js +68 -0
  613. pythinker_code/web/static/assets/mipsasm-CKIfxQSi.js +1 -0
  614. pythinker_code/web/static/assets/mojo-B93PlW-d.js +1 -0
  615. pythinker_code/web/static/assets/monokai-D4h5O-jR.js +1 -0
  616. pythinker_code/web/static/assets/moonbit-Ba13S78F.js +1 -0
  617. pythinker_code/web/static/assets/move-Bu9oaDYs.js +1 -0
  618. pythinker_code/web/static/assets/narrat-DRg8JJMk.js +1 -0
  619. pythinker_code/web/static/assets/nextflow-BrzmwbiE.js +1 -0
  620. pythinker_code/web/static/assets/nginx-DknmC5AR.js +1 -0
  621. pythinker_code/web/static/assets/night-owl-C39BiMTA.js +1 -0
  622. pythinker_code/web/static/assets/nim-CVrawwO9.js +1 -0
  623. pythinker_code/web/static/assets/nix-CwoSXNpI.js +1 -0
  624. pythinker_code/web/static/assets/nord-Ddv68eIx.js +1 -0
  625. pythinker_code/web/static/assets/nushell-C-sUppwS.js +1 -0
  626. pythinker_code/web/static/assets/objective-c-DXmwc3jG.js +1 -0
  627. pythinker_code/web/static/assets/objective-cpp-CLxacb5B.js +1 -0
  628. pythinker_code/web/static/assets/ocaml-C0hk2d4L.js +1 -0
  629. pythinker_code/web/static/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  630. pythinker_code/web/static/assets/one-light-PoHY5YXO.js +1 -0
  631. pythinker_code/web/static/assets/openscad-C4EeE6gA.js +1 -0
  632. pythinker_code/web/static/assets/ordinal-Cboi1Yqb.js +1 -0
  633. pythinker_code/web/static/assets/pascal-D93ZcfNL.js +1 -0
  634. pythinker_code/web/static/assets/perl-C0TMdlhV.js +1 -0
  635. pythinker_code/web/static/assets/php-CDn_0X-4.js +1 -0
  636. pythinker_code/web/static/assets/pieDiagram-ADFJNKIX-fNg41mT9.js +30 -0
  637. pythinker_code/web/static/assets/pkl-u5AG7uiY.js +1 -0
  638. pythinker_code/web/static/assets/plastic-3e1v2bzS.js +1 -0
  639. pythinker_code/web/static/assets/plsql-ChMvpjG-.js +1 -0
  640. pythinker_code/web/static/assets/po-BTJTHyun.js +1 -0
  641. pythinker_code/web/static/assets/poimandres-CS3Unz2-.js +1 -0
  642. pythinker_code/web/static/assets/polar-C0HS_06l.js +1 -0
  643. pythinker_code/web/static/assets/postcss-CXtECtnM.js +1 -0
  644. pythinker_code/web/static/assets/powerquery-CEu0bR-o.js +1 -0
  645. pythinker_code/web/static/assets/powershell-Dpen1YoG.js +1 -0
  646. pythinker_code/web/static/assets/prisma-Dd19v3D-.js +1 -0
  647. pythinker_code/web/static/assets/prolog-CbFg5uaA.js +1 -0
  648. pythinker_code/web/static/assets/proto-C7zT0LnQ.js +1 -0
  649. pythinker_code/web/static/assets/pug-CGlum2m_.js +1 -0
  650. pythinker_code/web/static/assets/puppet-BMWR74SV.js +1 -0
  651. pythinker_code/web/static/assets/purescript-CklMAg4u.js +1 -0
  652. pythinker_code/web/static/assets/python-B6aJPvgy.js +1 -0
  653. pythinker_code/web/static/assets/qml-3beO22l8.js +1 -0
  654. pythinker_code/web/static/assets/qmldir-C8lEn-DE.js +1 -0
  655. pythinker_code/web/static/assets/qss-IeuSbFQv.js +1 -0
  656. pythinker_code/web/static/assets/quadrantDiagram-AYHSOK5B-DJz3Kx87.js +7 -0
  657. pythinker_code/web/static/assets/r-Dspwwk_N.js +1 -0
  658. pythinker_code/web/static/assets/racket-BqYA7rlc.js +1 -0
  659. pythinker_code/web/static/assets/raku-DXvB9xmW.js +1 -0
  660. pythinker_code/web/static/assets/razor-C1TweQQi.js +1 -0
  661. pythinker_code/web/static/assets/red-bN70gL4F.js +1 -0
  662. pythinker_code/web/static/assets/reg-C-SQnVFl.js +1 -0
  663. pythinker_code/web/static/assets/regexp-CDVJQ6XC.js +1 -0
  664. pythinker_code/web/static/assets/rel-C3B-1QV4.js +1 -0
  665. pythinker_code/web/static/assets/requirementDiagram-UZGBJVZJ-B4SbrfE9.js +64 -0
  666. pythinker_code/web/static/assets/riscv-BM1_JUlF.js +1 -0
  667. pythinker_code/web/static/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
  668. pythinker_code/web/static/assets/rose-pine-moon-D4_iv3hh.js +1 -0
  669. pythinker_code/web/static/assets/rose-pine-qdsjHGoJ.js +1 -0
  670. pythinker_code/web/static/assets/rosmsg-BJDFO7_C.js +1 -0
  671. pythinker_code/web/static/assets/rst-B0xPkSld.js +1 -0
  672. pythinker_code/web/static/assets/ruby-BvKwtOVI.js +1 -0
  673. pythinker_code/web/static/assets/rust-B1yitclQ.js +1 -0
  674. pythinker_code/web/static/assets/sankeyDiagram-TZEHDZUN-CoSUjLAG.js +10 -0
  675. pythinker_code/web/static/assets/sas-cz2c8ADy.js +1 -0
  676. pythinker_code/web/static/assets/sass-Cj5Yp3dK.js +1 -0
  677. pythinker_code/web/static/assets/scala-C151Ov-r.js +1 -0
  678. pythinker_code/web/static/assets/scheme-C98Dy4si.js +1 -0
  679. pythinker_code/web/static/assets/scss-OYdSNvt2.js +1 -0
  680. pythinker_code/web/static/assets/sdbl-DVxCFoDh.js +1 -0
  681. pythinker_code/web/static/assets/sequenceDiagram-WL72ISMW-PjhBNHi3.js +145 -0
  682. pythinker_code/web/static/assets/shaderlab-Dg9Lc6iA.js +1 -0
  683. pythinker_code/web/static/assets/shellscript-Yzrsuije.js +1 -0
  684. pythinker_code/web/static/assets/shellsession-BADoaaVG.js +1 -0
  685. pythinker_code/web/static/assets/slack-dark-BthQWCQV.js +1 -0
  686. pythinker_code/web/static/assets/slack-ochin-DqwNpetd.js +1 -0
  687. pythinker_code/web/static/assets/smalltalk-BERRCDM3.js +1 -0
  688. pythinker_code/web/static/assets/snazzy-light-Bw305WKR.js +1 -0
  689. pythinker_code/web/static/assets/solarized-dark-DXbdFlpD.js +1 -0
  690. pythinker_code/web/static/assets/solarized-light-L9t79GZl.js +1 -0
  691. pythinker_code/web/static/assets/solidity-rGO070M0.js +1 -0
  692. pythinker_code/web/static/assets/soy-Brmx7dQM.js +1 -0
  693. pythinker_code/web/static/assets/sparql-rVzFXLq3.js +1 -0
  694. pythinker_code/web/static/assets/splunk-BtCnVYZw.js +1 -0
  695. pythinker_code/web/static/assets/sql-BLtJtn59.js +1 -0
  696. pythinker_code/web/static/assets/ssh-config-_ykCGR6B.js +1 -0
  697. pythinker_code/web/static/assets/stata-BH5u7GGu.js +1 -0
  698. pythinker_code/web/static/assets/stateDiagram-FKZM4ZOC-DOwESt8-.js +1 -0
  699. pythinker_code/web/static/assets/stateDiagram-v2-4FDKWEC3-yl3OHWiP.js +1 -0
  700. pythinker_code/web/static/assets/stylus-BEDo0Tqx.js +1 -0
  701. pythinker_code/web/static/assets/svelte-zxCyuUbr.js +1 -0
  702. pythinker_code/web/static/assets/swift-Dg5xB15N.js +1 -0
  703. pythinker_code/web/static/assets/synthwave-84-CbfX1IO0.js +1 -0
  704. pythinker_code/web/static/assets/system-verilog-CnnmHF94.js +1 -0
  705. pythinker_code/web/static/assets/systemd-4A_iFExJ.js +1 -0
  706. pythinker_code/web/static/assets/talonscript-CkByrt1z.js +1 -0
  707. pythinker_code/web/static/assets/tasl-QIJgUcNo.js +1 -0
  708. pythinker_code/web/static/assets/tcl-dwOrl1Do.js +1 -0
  709. pythinker_code/web/static/assets/templ-W15q3VgB.js +1 -0
  710. pythinker_code/web/static/assets/terraform-BETggiCN.js +1 -0
  711. pythinker_code/web/static/assets/tex-CvyZ59Mk.js +1 -0
  712. pythinker_code/web/static/assets/timeline-definition-IT6M3QCI-CkCLnAgi.js +61 -0
  713. pythinker_code/web/static/assets/tokyo-night-hegEt444.js +1 -0
  714. pythinker_code/web/static/assets/toml-vGWfd6FD.js +1 -0
  715. pythinker_code/web/static/assets/treemap-KMMF4GRG-CZS5XwTf.js +128 -0
  716. pythinker_code/web/static/assets/ts-tags-zn1MmPIZ.js +1 -0
  717. pythinker_code/web/static/assets/tsv-B_m7g4N7.js +1 -0
  718. pythinker_code/web/static/assets/tsx-COt5Ahok.js +1 -0
  719. pythinker_code/web/static/assets/turtle-BsS91CYL.js +1 -0
  720. pythinker_code/web/static/assets/twig-CO9l9SDP.js +1 -0
  721. pythinker_code/web/static/assets/typescript-BPQ3VLAy.js +1 -0
  722. pythinker_code/web/static/assets/typespec-BGHnOYBU.js +1 -0
  723. pythinker_code/web/static/assets/typst-DHCkPAjA.js +1 -0
  724. pythinker_code/web/static/assets/v-BcVCzyr7.js +1 -0
  725. pythinker_code/web/static/assets/vala-CsfeWuGM.js +1 -0
  726. pythinker_code/web/static/assets/vb-D17OF-Vu.js +1 -0
  727. pythinker_code/web/static/assets/verilog-BQ8w6xss.js +1 -0
  728. pythinker_code/web/static/assets/vesper-DU1UobuO.js +1 -0
  729. pythinker_code/web/static/assets/vhdl-CeAyd5Ju.js +1 -0
  730. pythinker_code/web/static/assets/viml-CJc9bBzg.js +1 -0
  731. pythinker_code/web/static/assets/vitesse-black-Bkuqu6BP.js +1 -0
  732. pythinker_code/web/static/assets/vitesse-dark-D0r3Knsf.js +1 -0
  733. pythinker_code/web/static/assets/vitesse-light-CVO1_9PV.js +1 -0
  734. pythinker_code/web/static/assets/vue-DN_0RTcg.js +1 -0
  735. pythinker_code/web/static/assets/vue-html-AaS7Mt5G.js +1 -0
  736. pythinker_code/web/static/assets/vue-vine-CQOfvN7w.js +1 -0
  737. pythinker_code/web/static/assets/vyper-CDx5xZoG.js +1 -0
  738. pythinker_code/web/static/assets/wasm-CG6Dc4jp.js +1 -0
  739. pythinker_code/web/static/assets/wasm-MzD3tlZU.js +1 -0
  740. pythinker_code/web/static/assets/wenyan-BV7otONQ.js +1 -0
  741. pythinker_code/web/static/assets/wgsl-Dx-B1_4e.js +1 -0
  742. pythinker_code/web/static/assets/wikitext-BhOHFoWU.js +1 -0
  743. pythinker_code/web/static/assets/wit-5i3qLPDT.js +1 -0
  744. pythinker_code/web/static/assets/wolfram-lXgVvXCa.js +1 -0
  745. pythinker_code/web/static/assets/xml-sdJ4AIDG.js +1 -0
  746. pythinker_code/web/static/assets/xsl-CtQFsRM5.js +1 -0
  747. pythinker_code/web/static/assets/xychartDiagram-PRI3JC2R-DkqqHNLh.js +7 -0
  748. pythinker_code/web/static/assets/yaml-Buea-lGh.js +1 -0
  749. pythinker_code/web/static/assets/zenscript-DVFEvuxE.js +1 -0
  750. pythinker_code/web/static/assets/zig-VOosw3JB.js +1 -0
  751. pythinker_code/web/static/brand/apple-touch-icon.png +0 -0
  752. pythinker_code/web/static/brand/arctecture.webp +0 -0
  753. pythinker_code/web/static/brand/bimi-logo.svg +46 -0
  754. pythinker_code/web/static/brand/favicon.ico +0 -0
  755. pythinker_code/web/static/brand/fonts/dm-sans-latin-ext.woff2 +0 -0
  756. pythinker_code/web/static/brand/fonts/dm-sans-latin.woff2 +0 -0
  757. pythinker_code/web/static/brand/fonts/instrument-sans-latin-ext.woff2 +0 -0
  758. pythinker_code/web/static/brand/fonts/instrument-sans-latin.woff2 +0 -0
  759. pythinker_code/web/static/brand/fonts/instrument-serif-latin-ext.woff2 +0 -0
  760. pythinker_code/web/static/brand/fonts/instrument-serif-latin.woff2 +0 -0
  761. pythinker_code/web/static/brand/fonts/libre-baskerville-italic-latin-ext.woff2 +0 -0
  762. pythinker_code/web/static/brand/fonts/libre-baskerville-italic-latin.woff2 +0 -0
  763. pythinker_code/web/static/brand/fonts/libre-baskerville-latin-ext.woff2 +0 -0
  764. pythinker_code/web/static/brand/fonts/libre-baskerville-latin.woff2 +0 -0
  765. pythinker_code/web/static/brand/fonts/roboto-latin-ext.woff2 +0 -0
  766. pythinker_code/web/static/brand/fonts/roboto-latin.woff2 +0 -0
  767. pythinker_code/web/static/brand/icon-192.png +0 -0
  768. pythinker_code/web/static/brand/icon-512.png +0 -0
  769. pythinker_code/web/static/brand/icon.svg +1 -0
  770. pythinker_code/web/static/brand/logo.png +0 -0
  771. pythinker_code/web/static/brand/pythinker_animated.svg +79 -0
  772. pythinker_code/web/static/brand/robots.txt +4 -0
  773. pythinker_code/web/static/index.html +15 -0
  774. pythinker_code/web/static/logo.png +0 -0
  775. pythinker_code/web/store/__init__.py +1 -0
  776. pythinker_code/web/store/sessions.py +433 -0
  777. pythinker_code/wire/__init__.py +148 -0
  778. pythinker_code/wire/file.py +151 -0
  779. pythinker_code/wire/jsonrpc.py +263 -0
  780. pythinker_code/wire/protocol.py +2 -0
  781. pythinker_code/wire/root_hub.py +27 -0
  782. pythinker_code/wire/serde.py +26 -0
  783. pythinker_code/wire/server.py +1072 -0
  784. pythinker_code/wire/types.py +698 -0
  785. pythinker_code-0.8.0.dist-info/METADATA +706 -0
  786. pythinker_code-0.8.0.dist-info/RECORD +790 -0
  787. pythinker_code-0.8.0.dist-info/WHEEL +4 -0
  788. pythinker_code-0.8.0.dist-info/entry_points.txt +4 -0
  789. pythinker_code-0.8.0.dist-info/licenses/LICENSE +202 -0
  790. pythinker_code-0.8.0.dist-info/licenses/NOTICE +14 -0
@@ -0,0 +1,217 @@
1
+ """
2
+ Telemetry event tracking for pythinker-code.
3
+
4
+ This module has NO dependencies on other pythinker_code modules to avoid import cycles.
5
+ track() can be called at any point during startup, even before the sink is attached.
6
+ Events are buffered in memory and flushed once the sink is ready.
7
+
8
+ Usage:
9
+ from pythinker_code.telemetry import track, set_context, attach_sink
10
+
11
+ # Early in startup — events queue in memory
12
+ track("first_launch")
13
+
14
+ # After app init — attach sink to start flushing
15
+ set_context(device_id="abc", session_id="def")
16
+ attach_sink(sink)
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import asyncio
22
+ import atexit
23
+ import time
24
+ import uuid
25
+ from collections import deque
26
+ from contextlib import suppress
27
+ from typing import TYPE_CHECKING, Any
28
+
29
+ if TYPE_CHECKING:
30
+ from pythinker_code.telemetry.sink import EventSink
31
+
32
+ # ---------------------------------------------------------------------------
33
+ # Module-level state (zero dependencies)
34
+ # ---------------------------------------------------------------------------
35
+
36
+ _MAX_QUEUE_SIZE = 1000
37
+ """Maximum number of events to buffer before sink is attached."""
38
+
39
+ _event_queue: deque[dict[str, Any]] = deque(maxlen=_MAX_QUEUE_SIZE)
40
+ """Events buffered before sink is attached."""
41
+
42
+ _device_id: str | None = None
43
+ _session_id: str | None = None
44
+ _client_info: tuple[str, str | None] | None = None
45
+ """(name, version) tuple, set atomically via set_client_info."""
46
+ _MAX_SESSION_STARTED_CACHE = 1024
47
+ _session_started_sessions: set[str] = set()
48
+ _session_started_order: deque[str] = deque()
49
+ """Bounded session ids that already emitted the session_started event in this process."""
50
+ _sink: EventSink | None = None
51
+ _disabled: bool = False
52
+
53
+
54
+ def set_context(*, device_id: str, session_id: str) -> None:
55
+ """Set device and session identifiers. Call once after app init."""
56
+ global _device_id, _session_id
57
+ _device_id = device_id
58
+ _session_id = session_id
59
+
60
+
61
+ def set_client_info(*, name: str, version: str | None = None) -> None:
62
+ """Set the wire/acp client name and version (e.g. VSCode 1.90.0, zed 0.180.0).
63
+
64
+ Called by wire/acp servers after receiving the client's initialize message.
65
+ Values are passed through verbatim — backend is responsible for any
66
+ validation, normalization or alerting on anomalous values.
67
+ """
68
+ global _client_info
69
+ if not name:
70
+ return
71
+ _client_info = (name, version)
72
+
73
+
74
+ def get_client_info() -> tuple[str, str | None] | None:
75
+ """Return the current (name, version) tuple, or None if unset.
76
+
77
+ Used by session-level telemetry to attribute wire/acp sessions.
78
+ """
79
+ return _client_info
80
+
81
+
82
+ def track_session_started_once(
83
+ *,
84
+ ui_mode: str,
85
+ resumed: bool,
86
+ client_name: str | None = None,
87
+ client_version: str | None = None,
88
+ ) -> None:
89
+ """Emit one session_started event for the current session in this process."""
90
+ session_id = _session_id
91
+ if not session_id or session_id in _session_started_sessions:
92
+ return
93
+
94
+ ui = (ui_mode or "unknown").strip().lower()
95
+ name = client_name
96
+ version = client_version
97
+ if name is None and ui in {"wire", "acp"}:
98
+ client_info = get_client_info()
99
+ if client_info is not None:
100
+ name, version = client_info
101
+ if not name:
102
+ name = ui or "unknown"
103
+
104
+ _session_started_sessions.add(session_id)
105
+ _session_started_order.append(session_id)
106
+ while len(_session_started_order) > _MAX_SESSION_STARTED_CACHE:
107
+ expired = _session_started_order.popleft()
108
+ _session_started_sessions.discard(expired)
109
+ track(
110
+ "session_started",
111
+ client_name=name,
112
+ client_version=version,
113
+ ui_mode=ui,
114
+ resumed=resumed,
115
+ )
116
+
117
+ if _sink is not None:
118
+ with suppress(Exception):
119
+ asyncio.get_running_loop().create_task(_sink.flush())
120
+
121
+
122
+ def disable() -> None:
123
+ """Permanently disable telemetry for this process. Events are silently dropped."""
124
+ global _disabled
125
+ _disabled = True
126
+ _event_queue.clear()
127
+ if _sink is not None:
128
+ _sink.clear_buffer()
129
+
130
+
131
+ def attach_sink(sink: EventSink) -> None:
132
+ """Attach the event sink and drain any queued events.
133
+
134
+ Multi-session ACP mode calls ``PythinkerCLI.create()`` per session, which
135
+ means ``attach_sink`` runs again while a previous sink may hold
136
+ un-flushed buffered events. Flush the old sink synchronously (writes
137
+ any pending events to the disk fallback) before replacing it, so
138
+ earlier sessions' events are not silently orphaned.
139
+ """
140
+ global _sink
141
+ if _sink is not None and _sink is not sink:
142
+ # flush_sync already swallows its own transport failures;
143
+ # ``suppress`` guards against truly unexpected errors so sink
144
+ # replacement is never blocked by a flaky predecessor.
145
+ with suppress(Exception):
146
+ _sink.flush_sync()
147
+ _sink = sink
148
+ # Drain events that were queued before sink was ready,
149
+ # backfilling device_id/session_id for events tracked before set_context().
150
+ if _event_queue:
151
+ for event in _event_queue:
152
+ if event.get("device_id") is None:
153
+ event["device_id"] = _device_id
154
+ if event.get("session_id") is None:
155
+ event["session_id"] = _session_id
156
+ _sink.accept(event)
157
+ _event_queue.clear()
158
+
159
+
160
+ def track(event: str, **properties: bool | int | float | str | None) -> None:
161
+ """
162
+ Record a telemetry event.
163
+
164
+ This function is non-blocking: it appends to an in-memory list.
165
+ Safe to call from synchronous prompt_toolkit key handlers.
166
+
167
+ Args:
168
+ event: Event name (e.g. "input_command").
169
+ **properties: Event properties. String values should only be used for
170
+ known enum-like values (command names, mode names, error types).
171
+ NEVER pass user input, file paths, or code snippets.
172
+ """
173
+ if _disabled:
174
+ return
175
+
176
+ record = {
177
+ "event_id": uuid.uuid4().hex,
178
+ "device_id": _device_id,
179
+ "session_id": _session_id,
180
+ "event": event,
181
+ "timestamp": time.time(),
182
+ "properties": properties if properties else {},
183
+ }
184
+
185
+ if _sink is not None:
186
+ _sink.accept(record)
187
+ else:
188
+ _event_queue.append(record)
189
+
190
+
191
+ def get_sink() -> EventSink | None:
192
+ """Return the current sink, or None if not attached."""
193
+ return _sink
194
+
195
+
196
+ def flush_sync() -> None:
197
+ """Synchronously flush any buffered events. Called on exit.
198
+
199
+ Drains the in-process buffer first (so any pending ``track()`` events get
200
+ handed to the OTel exporter), then waits for both Sentry and OTel to
201
+ finish their network round-trips.
202
+ """
203
+ if _sink is not None:
204
+ _sink.flush_sync()
205
+ # Flush vendor SDKs last — they take the network hit.
206
+ try:
207
+ from pythinker_code.telemetry import otel as _otel
208
+ from pythinker_code.telemetry import sentry as _sentry
209
+
210
+ _sentry.flush(timeout_seconds=2.0)
211
+ _otel.shutdown(timeout_millis=2000)
212
+ except Exception:
213
+ pass
214
+
215
+
216
+ # Register atexit handler to flush remaining events on normal exit
217
+ atexit.register(flush_sync)
@@ -0,0 +1,113 @@
1
+ """Telemetry endpoint configuration.
2
+
3
+ Defaults point at the pythinker-operated Bugsink + SigNoz infrastructure.
4
+ Sentry/Bugsink DSNs are designed to be public; the OTLP bearer token is embedded
5
+ following the same industry convention used by Datadog RUM and PostHog public
6
+ keys, mitigated server-side by rate limiting and PII scrubbing at the edge
7
+ collector.
8
+
9
+ Override any value at runtime with the matching environment variable. Telemetry
10
+ is off by default; enable it with ``PYTHINKER_ENABLE_TELEMETRY=1``. Disable
11
+ telemetry explicitly with ``PYTHINKER_DISABLE_TELEMETRY=1``.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import os
17
+ import sys
18
+
19
+ # ---------------------------------------------------------------------------
20
+ # Bugsink (Sentry-protocol error tracking)
21
+ # ---------------------------------------------------------------------------
22
+
23
+ DEFAULT_SENTRY_DSN = "https://ab578ebdf2f24c279d9e866ee190574c@errors.pythinker.com/1"
24
+
25
+ # ---------------------------------------------------------------------------
26
+ # SigNoz via the pythinker edge OTel collector
27
+ # ---------------------------------------------------------------------------
28
+
29
+ DEFAULT_OTEL_ENDPOINT = "https://otel.pythinker.com"
30
+ DEFAULT_OTEL_INGEST_TOKEN = "83e2d8f0cb72c6c0f8896b40cf68de6e67bfad895a61729b36bc27e594d66d69"
31
+
32
+ # ---------------------------------------------------------------------------
33
+ # Resolution
34
+ # ---------------------------------------------------------------------------
35
+
36
+
37
+ def sentry_dsn() -> str:
38
+ """Resolve the Sentry/Bugsink DSN, honoring env override and explicit empty."""
39
+ return os.environ.get("PYTHINKER_SENTRY_DSN", DEFAULT_SENTRY_DSN)
40
+
41
+
42
+ def otel_endpoint() -> str:
43
+ """Resolve the OTLP HTTP endpoint base URL (no trailing slash)."""
44
+ return os.environ.get("PYTHINKER_OTEL_ENDPOINT", DEFAULT_OTEL_ENDPOINT).rstrip("/")
45
+
46
+
47
+ def otel_ingest_token() -> str:
48
+ """Resolve the bearer token presented to the edge collector."""
49
+ return os.environ.get("PYTHINKER_OTEL_TOKEN", DEFAULT_OTEL_INGEST_TOKEN)
50
+
51
+
52
+ _TRUTHY = {"1", "true", "yes", "on"}
53
+
54
+
55
+ def _env_truthy(name: str) -> bool:
56
+ return os.environ.get(name, "").strip().lower() in _TRUTHY
57
+
58
+
59
+ def is_test_environment() -> bool:
60
+ """Return True when running under pytest unless explicitly overridden.
61
+
62
+ Unit tests deliberately raise synthetic exceptions (``boom``, ``disk full``,
63
+ cancelled subagents, and so on). Those are useful for local assertions but
64
+ must never be exported to the production Bugsink/SigNoz projects.
65
+ """
66
+ if _env_truthy("PYTHINKER_FORCE_TELEMETRY_IN_TESTS"):
67
+ return False
68
+ return "PYTEST_CURRENT_TEST" in os.environ or "pytest" in sys.modules
69
+
70
+
71
+ def is_disabled() -> bool:
72
+ """Master kill switch for external Sentry/OTel emission.
73
+
74
+ Telemetry is opt-in: ``PYTHINKER_ENABLE_TELEMETRY=1`` must be set before
75
+ any external Sentry/OTel emission is attempted. ``PYTHINKER_DISABLE_TELEMETRY=1``
76
+ remains an explicit kill switch, and pytest is treated as disabled by default
77
+ so test suites cannot leak deliberate test failures to production telemetry
78
+ backends.
79
+ """
80
+ if _env_truthy("PYTHINKER_DISABLE_TELEMETRY"):
81
+ return True
82
+ if is_test_environment():
83
+ return True
84
+ return not _env_truthy("PYTHINKER_ENABLE_TELEMETRY")
85
+
86
+
87
+ # ---------------------------------------------------------------------------
88
+ # Sampling
89
+ # ---------------------------------------------------------------------------
90
+
91
+ DEFAULT_OTEL_TRACE_SAMPLE_RATE = 1.0
92
+ """Default fraction of root-trace spans to record. 1.0 = always-on; 0.0 = none."""
93
+
94
+
95
+ def otel_trace_sample_rate() -> float:
96
+ """Resolve the OTel trace sampling rate.
97
+
98
+ Honors ``PYTHINKER_OTEL_TRACE_SAMPLE_RATE``. Clamped to ``[0.0, 1.0]``.
99
+ Malformed input falls back to the default rather than disabling tracing
100
+ or raising — telemetry config must never break the host program.
101
+ """
102
+ raw = os.environ.get("PYTHINKER_OTEL_TRACE_SAMPLE_RATE", "").strip()
103
+ if not raw:
104
+ return DEFAULT_OTEL_TRACE_SAMPLE_RATE
105
+ try:
106
+ rate = float(raw)
107
+ except ValueError:
108
+ return DEFAULT_OTEL_TRACE_SAMPLE_RATE
109
+ if rate < 0.0:
110
+ return 0.0
111
+ if rate > 1.0:
112
+ return 1.0
113
+ return rate
@@ -0,0 +1,191 @@
1
+ """Crash telemetry: capture uncaught exceptions via sys.excepthook and
2
+ asyncio's exception handler, emit a ``crash`` event for in-process counting,
3
+ forward the full exception to Sentry/Bugsink for debugging, then delegate to
4
+ the original handler so the traceback still gets printed.
5
+
6
+ Privacy posture:
7
+ - The OTel ``crash`` event still carries only the exception *class name*,
8
+ phase, and source — same as before.
9
+ - The Sentry capture path includes the stack trace (path-scrubbed by the
10
+ sentry module's ``before_send`` hook).
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import asyncio
16
+ import sys
17
+ from types import TracebackType
18
+ from typing import Any
19
+
20
+ from pythinker_code.utils.logging import logger
21
+
22
+ # ---------------------------------------------------------------------------
23
+ # Phase tracking
24
+ # ---------------------------------------------------------------------------
25
+
26
+ _phase: str = "startup"
27
+ """Coarse lifecycle bucket recorded on each crash event.
28
+
29
+ Valid values: ``startup`` (before app init finishes), ``runtime``
30
+ (normal operation), ``shutdown`` (after the main entry point returns).
31
+ """
32
+
33
+
34
+ def set_phase(phase: str) -> None:
35
+ """Update the current lifecycle phase. Called by app entry points."""
36
+ global _phase
37
+ _phase = phase
38
+
39
+
40
+ # ---------------------------------------------------------------------------
41
+ # Filters
42
+ # ---------------------------------------------------------------------------
43
+
44
+
45
+ def _should_ignore_for_excepthook(exc_type: type[BaseException]) -> bool:
46
+ """Return True for exceptions that are not programming bugs.
47
+
48
+ - KeyboardInterrupt: Ctrl+C, already covered by the ``cancel`` event.
49
+ - SystemExit: deliberate exit, not a crash.
50
+ - click.ClickException (UsageError / BadParameter / ...): user-facing
51
+ CLI input errors, not program bugs.
52
+ """
53
+ if issubclass(exc_type, (KeyboardInterrupt, SystemExit)):
54
+ return True
55
+ try:
56
+ import click
57
+
58
+ if issubclass(exc_type, click.exceptions.ClickException):
59
+ return True
60
+ except ImportError:
61
+ pass
62
+ return False
63
+
64
+
65
+ def _should_ignore_for_asyncio(exc: BaseException) -> bool:
66
+ """Return True for normal async control-flow exceptions.
67
+
68
+ ``asyncio.QueueShutDown`` is raised by Python's queue API when a wire stream
69
+ is intentionally closed. UI consumers catch it, but vendor asyncio
70
+ integrations can still observe the task boundary, so treat it like
71
+ cancellation rather than a crash.
72
+ """
73
+ if isinstance(exc, asyncio.CancelledError):
74
+ return True
75
+ queue_shutdown = getattr(asyncio, "QueueShutDown", None)
76
+ return queue_shutdown is not None and isinstance(exc, queue_shutdown)
77
+
78
+
79
+ def _should_suppress_asyncio_context(context: dict[str, Any]) -> bool:
80
+ """Return True for asyncio-handler contexts that should stay invisible.
81
+
82
+ Prompt-toolkit displays asyncio default-handler output in an overlay. During
83
+ Esc/interrupt cleanup, some cancelled/GC'd tasks can call the loop exception
84
+ handler with no ``exception`` field at all, which renders as
85
+ ``Unhandled exception in event loop: Exception None`` and steals focus until
86
+ ENTER is pressed. With no exception object there is nothing actionable to
87
+ report, so suppress the default handler for that known-noisy context.
88
+ """
89
+ exc = context.get("exception")
90
+ if exc is None:
91
+ return True
92
+ return isinstance(exc, BaseException) and _should_ignore_for_asyncio(exc)
93
+
94
+
95
+ # ---------------------------------------------------------------------------
96
+ # sys.excepthook
97
+ # ---------------------------------------------------------------------------
98
+
99
+ _original_excepthook: Any = None
100
+
101
+
102
+ def _excepthook(
103
+ exc_type: type[BaseException],
104
+ exc: BaseException,
105
+ tb: TracebackType | None,
106
+ ) -> None:
107
+ if not _should_ignore_for_excepthook(exc_type):
108
+ # Any failure inside telemetry must not mask the underlying crash.
109
+ try:
110
+ from pythinker_code.telemetry import sentry as _sentry
111
+ from pythinker_code.telemetry import track
112
+
113
+ track(
114
+ "crash",
115
+ error_type=exc_type.__name__,
116
+ where=_phase,
117
+ source="excepthook",
118
+ )
119
+ _sentry.capture_exception(exc)
120
+ except Exception:
121
+ logger.debug("Telemetry crash capture failed", exc_info=True)
122
+
123
+ # Always delegate so the traceback is still printed.
124
+ handler = _original_excepthook if _original_excepthook is not None else sys.__excepthook__
125
+ handler(exc_type, exc, tb)
126
+
127
+
128
+ def install_crash_handlers() -> None:
129
+ """Install the process-level ``sys.excepthook``.
130
+
131
+ Idempotent: repeated calls are no-ops. Should be called as early as
132
+ possible in the entry point so startup-phase exceptions are captured.
133
+ """
134
+ global _original_excepthook
135
+ if sys.excepthook is _excepthook:
136
+ return
137
+ _original_excepthook = sys.excepthook
138
+ sys.excepthook = _excepthook
139
+
140
+
141
+ # ---------------------------------------------------------------------------
142
+ # asyncio exception handler
143
+ # ---------------------------------------------------------------------------
144
+
145
+ _original_asyncio_handler: Any = None
146
+
147
+
148
+ def _asyncio_handler(
149
+ loop: asyncio.AbstractEventLoop,
150
+ context: dict[str, Any],
151
+ ) -> None:
152
+ if _should_suppress_asyncio_context(context):
153
+ return
154
+
155
+ exc = context.get("exception")
156
+ if exc is not None:
157
+ try:
158
+ from pythinker_code.telemetry import sentry as _sentry
159
+ from pythinker_code.telemetry import track
160
+
161
+ track(
162
+ "crash",
163
+ error_type=type(exc).__name__,
164
+ where=_phase,
165
+ source="asyncio_task",
166
+ )
167
+ _sentry.capture_exception(exc)
168
+ except Exception:
169
+ logger.debug("Telemetry crash capture failed", exc_info=True)
170
+
171
+ # Delegate so the original logging behavior (or custom handler) runs.
172
+ if _original_asyncio_handler is not None:
173
+ _original_asyncio_handler(loop, context)
174
+ else:
175
+ loop.default_exception_handler(context)
176
+
177
+
178
+ def install_asyncio_handler(loop: asyncio.AbstractEventLoop | None = None) -> None:
179
+ """Install the crash handler on the given (or current running) loop.
180
+
181
+ Idempotent on the same loop. If a custom handler was already installed,
182
+ it is preserved and still invoked after the crash event is recorded.
183
+ """
184
+ global _original_asyncio_handler
185
+ if loop is None:
186
+ loop = asyncio.get_running_loop()
187
+ current = loop.get_exception_handler()
188
+ if current is _asyncio_handler:
189
+ return
190
+ _original_asyncio_handler = current
191
+ loop.set_exception_handler(_asyncio_handler)
@@ -0,0 +1,113 @@
1
+ """Helper for reporting handled exceptions to Sentry/Bugsink + OTel.
2
+
3
+ Use this at any except-block site that intentionally turns an exception into
4
+ a graceful user-facing error result (a ``ToolError``, a TUI message, a logged
5
+ warning). The site keeps its existing failure-rendering behaviour; the helper
6
+ additionally informs the monitoring stack so dashboards can see the failure
7
+ rate, class, and bucket.
8
+
9
+ Privacy posture
10
+ ---------------
11
+ Only pass primitive enum-like attributes through ``**attrs`` (tool name,
12
+ class names, mode flags). The OTel ``error`` event is forwarded verbatim, so
13
+ **never** pass user input, file paths, or code snippets there. Sentry is fine
14
+ with full exception data — its ``before_send`` hook already scrubs paths and
15
+ strips PII before transmission.
16
+
17
+ Both forwarding paths are wrapped in :func:`contextlib.suppress` because
18
+ telemetry must never break the host program.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import contextlib
24
+ import re
25
+ import time
26
+ from collections import deque
27
+ from dataclasses import dataclass
28
+ from typing import Any
29
+
30
+ from pythinker_code.telemetry import sentry as _sentry
31
+ from pythinker_code.telemetry import track
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # Process-local ring buffer of recent errors
35
+ # ---------------------------------------------------------------------------
36
+ # Keeps just enough metadata to populate the ``/report-error`` slash command
37
+ # without retaining the full exception object (which can hold large frames
38
+ # and references). Class name + a short, redacted message is what shows in
39
+ # the buffer; the *full* scrubbed stack is already in Sentry/Bugsink.
40
+
41
+ _RECENT_BUFFER_SIZE = 10
42
+ _ABSOLUTE_PATH_RE = re.compile(r"(?<!\w)(?:/[\w .~+@%=-]+)+|[A-Za-z]:\\(?:[^\\\r\n]+\\?)+")
43
+
44
+
45
+ def _redact_recent_message(message: str) -> str:
46
+ return _ABSOLUTE_PATH_RE.sub("<path>", message)[:200]
47
+
48
+
49
+ @dataclass(frozen=True, slots=True)
50
+ class RecentError:
51
+ timestamp: float
52
+ site: str
53
+ exc_class: str
54
+ message: str # truncated to 200 chars
55
+ tool: str | None
56
+
57
+
58
+ _recent: deque[RecentError] = deque(maxlen=_RECENT_BUFFER_SIZE)
59
+
60
+
61
+ def recent_errors() -> list[RecentError]:
62
+ """Snapshot of the most-recent reported errors (oldest first)."""
63
+ return list(_recent)
64
+
65
+
66
+ def clear_recent_errors() -> None:
67
+ """Drop the recent-errors buffer. Used by tests and by ``/report-error``
68
+ after a successful submission."""
69
+ _recent.clear()
70
+
71
+
72
+ def report_handled_error(
73
+ exc: BaseException,
74
+ *,
75
+ site: str,
76
+ tool: str | None = None,
77
+ **attrs: bool | int | float | str | None,
78
+ ) -> None:
79
+ """Forward a caught-and-rendered exception to Sentry + the OTel error stream.
80
+
81
+ Args:
82
+ exc: The exception that was caught at the call site.
83
+ site: Stable identifier for the catch site, e.g. ``"tool.read"`` or
84
+ ``"auth.oauth.refresh"``. Used to bucket failures in dashboards.
85
+ Must be a stable enum-like string, not a free-form message.
86
+ tool: Optional tool name when the site is a tool implementation
87
+ (e.g. ``"ReadFile"``). Forwarded as a separate property so SigNoz
88
+ queries can group by tool without parsing ``site``.
89
+ **attrs: Additional primitive enum-like attributes. Booleans, numbers
90
+ and short strings only. Values must not contain user input,
91
+ absolute paths, or code snippets.
92
+ """
93
+ properties: dict[str, Any] = {
94
+ "site": site,
95
+ "exc_class": type(exc).__name__,
96
+ }
97
+ if tool is not None:
98
+ properties["tool"] = tool
99
+ properties.update(attrs)
100
+ with contextlib.suppress(Exception):
101
+ track("error", **properties)
102
+ with contextlib.suppress(Exception):
103
+ _sentry.capture_exception(exc)
104
+ with contextlib.suppress(Exception):
105
+ _recent.append(
106
+ RecentError(
107
+ timestamp=time.time(),
108
+ site=site,
109
+ exc_class=type(exc).__name__,
110
+ message=_redact_recent_message(str(exc)),
111
+ tool=tool,
112
+ )
113
+ )