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.
- pythinker_code/CHANGELOG.md +60 -0
- pythinker_code/__init__.py +0 -0
- pythinker_code/__main__.py +97 -0
- pythinker_code/acp/AGENTS.md +93 -0
- pythinker_code/acp/__init__.py +13 -0
- pythinker_code/acp/convert.py +128 -0
- pythinker_code/acp/host.py +301 -0
- pythinker_code/acp/mcp.py +46 -0
- pythinker_code/acp/server.py +497 -0
- pythinker_code/acp/session.py +502 -0
- pythinker_code/acp/tools.py +174 -0
- pythinker_code/acp/types.py +13 -0
- pythinker_code/acp/version.py +45 -0
- pythinker_code/agents/default/agent.yaml +55 -0
- pythinker_code/agents/default/code_reviewer.yaml +47 -0
- pythinker_code/agents/default/coder.yaml +42 -0
- pythinker_code/agents/default/debugger.yaml +35 -0
- pythinker_code/agents/default/explore.yaml +59 -0
- pythinker_code/agents/default/implementer.yaml +46 -0
- pythinker_code/agents/default/plan.yaml +42 -0
- pythinker_code/agents/default/review.yaml +47 -0
- pythinker_code/agents/default/security_reviewer.yaml +37 -0
- pythinker_code/agents/default/system.md +192 -0
- pythinker_code/agents/default/verifier.yaml +46 -0
- pythinker_code/agents/okabe/agent.yaml +22 -0
- pythinker_code/agentspec.py +163 -0
- pythinker_code/app.py +847 -0
- pythinker_code/approval_runtime/__init__.py +29 -0
- pythinker_code/approval_runtime/models.py +42 -0
- pythinker_code/approval_runtime/runtime.py +235 -0
- pythinker_code/auth/__init__.py +25 -0
- pythinker_code/auth/anthropic_direct.py +207 -0
- pythinker_code/auth/deepseek.py +192 -0
- pythinker_code/auth/github_feedback.py +228 -0
- pythinker_code/auth/lm_studio.py +418 -0
- pythinker_code/auth/minimax.py +203 -0
- pythinker_code/auth/oauth.py +1145 -0
- pythinker_code/auth/ollama.py +293 -0
- pythinker_code/auth/openai.py +783 -0
- pythinker_code/auth/opencode_go.py +203 -0
- pythinker_code/auth/openrouter.py +225 -0
- pythinker_code/auth/platforms.py +475 -0
- pythinker_code/background/__init__.py +36 -0
- pythinker_code/background/agent_runner.py +231 -0
- pythinker_code/background/ids.py +19 -0
- pythinker_code/background/manager.py +668 -0
- pythinker_code/background/models.py +118 -0
- pythinker_code/background/store.py +243 -0
- pythinker_code/background/summary.py +66 -0
- pythinker_code/background/worker.py +209 -0
- pythinker_code/cli/__init__.py +1326 -0
- pythinker_code/cli/__main__.py +19 -0
- pythinker_code/cli/_lazy_group.py +268 -0
- pythinker_code/cli/debug.py +11 -0
- pythinker_code/cli/export.py +322 -0
- pythinker_code/cli/info.py +62 -0
- pythinker_code/cli/mcp.py +362 -0
- pythinker_code/cli/plugin.py +351 -0
- pythinker_code/cli/review.py +74 -0
- pythinker_code/cli/secscan.py +11 -0
- pythinker_code/cli/security_scan.py +35 -0
- pythinker_code/cli/toad.py +74 -0
- pythinker_code/cli/update.py +26 -0
- pythinker_code/cli/vis.py +38 -0
- pythinker_code/cli/web.py +80 -0
- pythinker_code/config.py +511 -0
- pythinker_code/constant.py +33 -0
- pythinker_code/events.py +106 -0
- pythinker_code/exception.py +43 -0
- pythinker_code/extensions.py +151 -0
- pythinker_code/hooks/__init__.py +4 -0
- pythinker_code/hooks/config.py +34 -0
- pythinker_code/hooks/engine.py +383 -0
- pythinker_code/hooks/events.py +190 -0
- pythinker_code/hooks/runner.py +92 -0
- pythinker_code/llm.py +441 -0
- pythinker_code/metadata.py +79 -0
- pythinker_code/notifications/__init__.py +33 -0
- pythinker_code/notifications/llm.py +77 -0
- pythinker_code/notifications/manager.py +145 -0
- pythinker_code/notifications/models.py +50 -0
- pythinker_code/notifications/notifier.py +41 -0
- pythinker_code/notifications/store.py +118 -0
- pythinker_code/notifications/wire.py +21 -0
- pythinker_code/plugin/__init__.py +124 -0
- pythinker_code/plugin/manager.py +166 -0
- pythinker_code/plugin/tool.py +173 -0
- pythinker_code/prompt_templates.py +181 -0
- pythinker_code/prompts/__init__.py +6 -0
- pythinker_code/prompts/compact.md +73 -0
- pythinker_code/prompts/init.md +21 -0
- pythinker_code/py.typed +0 -0
- pythinker_code/session.py +319 -0
- pythinker_code/session_fork.py +325 -0
- pythinker_code/session_state.py +132 -0
- pythinker_code/share.py +14 -0
- pythinker_code/skill/__init__.py +727 -0
- pythinker_code/skill/flow/__init__.py +99 -0
- pythinker_code/skill/flow/d2.py +482 -0
- pythinker_code/skill/flow/mermaid.py +266 -0
- pythinker_code/skills/pythinker-code-help/SKILL.md +54 -0
- pythinker_code/skills/skill-creator/SKILL.md +367 -0
- pythinker_code/soul/__init__.py +304 -0
- pythinker_code/soul/agent.py +552 -0
- pythinker_code/soul/approval.py +267 -0
- pythinker_code/soul/btw.py +220 -0
- pythinker_code/soul/compaction.py +189 -0
- pythinker_code/soul/context.py +339 -0
- pythinker_code/soul/denwarenji.py +39 -0
- pythinker_code/soul/dynamic_injection.py +84 -0
- pythinker_code/soul/dynamic_injections/__init__.py +0 -0
- pythinker_code/soul/dynamic_injections/auto_mode.py +72 -0
- pythinker_code/soul/dynamic_injections/plan_mode.py +239 -0
- pythinker_code/soul/message.py +92 -0
- pythinker_code/soul/permission.py +368 -0
- pythinker_code/soul/pythinkersoul.py +1763 -0
- pythinker_code/soul/slash.py +340 -0
- pythinker_code/soul/toolset.py +826 -0
- pythinker_code/subagents/__init__.py +21 -0
- pythinker_code/subagents/builder.py +43 -0
- pythinker_code/subagents/core.py +86 -0
- pythinker_code/subagents/discovery.py +234 -0
- pythinker_code/subagents/git_context.py +172 -0
- pythinker_code/subagents/models.py +56 -0
- pythinker_code/subagents/output.py +71 -0
- pythinker_code/subagents/registry.py +28 -0
- pythinker_code/subagents/runner.py +442 -0
- pythinker_code/subagents/store.py +200 -0
- pythinker_code/telemetry/__init__.py +217 -0
- pythinker_code/telemetry/config.py +113 -0
- pythinker_code/telemetry/crash.py +191 -0
- pythinker_code/telemetry/errors.py +113 -0
- pythinker_code/telemetry/metrics.py +208 -0
- pythinker_code/telemetry/otel.py +303 -0
- pythinker_code/telemetry/sentry.py +212 -0
- pythinker_code/telemetry/sink.py +189 -0
- pythinker_code/tools/AGENTS.md +6 -0
- pythinker_code/tools/__init__.py +105 -0
- pythinker_code/tools/agent/__init__.py +326 -0
- pythinker_code/tools/agent/description.md +65 -0
- pythinker_code/tools/ask_user/__init__.py +162 -0
- pythinker_code/tools/ask_user/description.md +19 -0
- pythinker_code/tools/background/__init__.py +318 -0
- pythinker_code/tools/background/list.md +10 -0
- pythinker_code/tools/background/output.md +11 -0
- pythinker_code/tools/background/stop.md +8 -0
- pythinker_code/tools/display.py +46 -0
- pythinker_code/tools/dmail/__init__.py +38 -0
- pythinker_code/tools/dmail/dmail.md +17 -0
- pythinker_code/tools/file/__init__.py +31 -0
- pythinker_code/tools/file/glob.md +17 -0
- pythinker_code/tools/file/glob.py +163 -0
- pythinker_code/tools/file/grep.md +6 -0
- pythinker_code/tools/file/grep_local.py +904 -0
- pythinker_code/tools/file/plan_mode.py +45 -0
- pythinker_code/tools/file/read.md +16 -0
- pythinker_code/tools/file/read.py +303 -0
- pythinker_code/tools/file/read_media.md +24 -0
- pythinker_code/tools/file/read_media.py +220 -0
- pythinker_code/tools/file/replace.md +7 -0
- pythinker_code/tools/file/replace.py +204 -0
- pythinker_code/tools/file/utils.py +257 -0
- pythinker_code/tools/file/write.md +5 -0
- pythinker_code/tools/file/write.py +186 -0
- pythinker_code/tools/plan/__init__.py +362 -0
- pythinker_code/tools/plan/description.md +29 -0
- pythinker_code/tools/plan/enter.py +193 -0
- pythinker_code/tools/plan/enter_description.md +35 -0
- pythinker_code/tools/plan/handoff.py +69 -0
- pythinker_code/tools/plan/heroes.py +277 -0
- pythinker_code/tools/shell/__init__.py +263 -0
- pythinker_code/tools/shell/bash.md +35 -0
- pythinker_code/tools/shell/powershell.md +30 -0
- pythinker_code/tools/test.py +55 -0
- pythinker_code/tools/think/__init__.py +21 -0
- pythinker_code/tools/think/think.md +1 -0
- pythinker_code/tools/todo/__init__.py +168 -0
- pythinker_code/tools/todo/set_todo_list.md +23 -0
- pythinker_code/tools/utils.py +200 -0
- pythinker_code/tools/web/__init__.py +4 -0
- pythinker_code/tools/web/fetch.md +1 -0
- pythinker_code/tools/web/fetch.py +261 -0
- pythinker_code/tools/web/search.md +1 -0
- pythinker_code/tools/web/search.py +163 -0
- pythinker_code/ui/__init__.py +0 -0
- pythinker_code/ui/acp/__init__.py +99 -0
- pythinker_code/ui/print/__init__.py +474 -0
- pythinker_code/ui/print/visualize.py +185 -0
- pythinker_code/ui/shell/__init__.py +1806 -0
- pythinker_code/ui/shell/components/__init__.py +110 -0
- pythinker_code/ui/shell/components/base.py +25 -0
- pythinker_code/ui/shell/components/bash_execution.py +249 -0
- pythinker_code/ui/shell/components/bordered_loader.py +62 -0
- pythinker_code/ui/shell/components/diff.py +308 -0
- pythinker_code/ui/shell/components/footer.py +231 -0
- pythinker_code/ui/shell/components/key_hints.py +27 -0
- pythinker_code/ui/shell/components/messages.py +152 -0
- pythinker_code/ui/shell/components/render_utils.py +198 -0
- pythinker_code/ui/shell/components/settings_list.py +369 -0
- pythinker_code/ui/shell/components/special_messages.py +125 -0
- pythinker_code/ui/shell/components/tool_execution.py +261 -0
- pythinker_code/ui/shell/console.py +109 -0
- pythinker_code/ui/shell/debug.py +190 -0
- pythinker_code/ui/shell/echo.py +30 -0
- pythinker_code/ui/shell/export_import.py +117 -0
- pythinker_code/ui/shell/keyboard.py +300 -0
- pythinker_code/ui/shell/keymap.py +84 -0
- pythinker_code/ui/shell/mcp_status.py +112 -0
- pythinker_code/ui/shell/model_picker.py +318 -0
- pythinker_code/ui/shell/oauth.py +273 -0
- pythinker_code/ui/shell/placeholders.py +578 -0
- pythinker_code/ui/shell/prompt.py +2888 -0
- pythinker_code/ui/shell/replay.py +215 -0
- pythinker_code/ui/shell/selector.py +364 -0
- pythinker_code/ui/shell/selectors/__init__.py +38 -0
- pythinker_code/ui/shell/selectors/extension.py +37 -0
- pythinker_code/ui/shell/selectors/oauth.py +63 -0
- pythinker_code/ui/shell/selectors/settings.py +349 -0
- pythinker_code/ui/shell/selectors/show_images.py +29 -0
- pythinker_code/ui/shell/selectors/theme.py +28 -0
- pythinker_code/ui/shell/selectors/thinking.py +42 -0
- pythinker_code/ui/shell/session_picker.py +227 -0
- pythinker_code/ui/shell/setup.py +212 -0
- pythinker_code/ui/shell/slash.py +1433 -0
- pythinker_code/ui/shell/spinner_words.py +222 -0
- pythinker_code/ui/shell/startup.py +32 -0
- pythinker_code/ui/shell/task_browser.py +486 -0
- pythinker_code/ui/shell/tool_renderers/__init__.py +197 -0
- pythinker_code/ui/shell/tool_renderers/_render_utils.py +168 -0
- pythinker_code/ui/shell/tool_renderers/agent.py +140 -0
- pythinker_code/ui/shell/tool_renderers/ask_user.py +93 -0
- pythinker_code/ui/shell/tool_renderers/background.py +144 -0
- pythinker_code/ui/shell/tool_renderers/bash.py +103 -0
- pythinker_code/ui/shell/tool_renderers/edit.py +163 -0
- pythinker_code/ui/shell/tool_renderers/find.py +81 -0
- pythinker_code/ui/shell/tool_renderers/generic.py +60 -0
- pythinker_code/ui/shell/tool_renderers/grep.py +98 -0
- pythinker_code/ui/shell/tool_renderers/plan.py +98 -0
- pythinker_code/ui/shell/tool_renderers/read.py +103 -0
- pythinker_code/ui/shell/tool_renderers/think.py +66 -0
- pythinker_code/ui/shell/tool_renderers/todo.py +164 -0
- pythinker_code/ui/shell/tool_renderers/web.py +128 -0
- pythinker_code/ui/shell/tool_renderers/write.py +102 -0
- pythinker_code/ui/shell/update.py +352 -0
- pythinker_code/ui/shell/usage.py +291 -0
- pythinker_code/ui/shell/usage_adapters/__init__.py +50 -0
- pythinker_code/ui/shell/usage_adapters/anthropic_admin.py +233 -0
- pythinker_code/ui/shell/usage_adapters/base.py +72 -0
- pythinker_code/ui/shell/usage_adapters/deepseek.py +137 -0
- pythinker_code/ui/shell/usage_adapters/minimax.py +236 -0
- pythinker_code/ui/shell/usage_adapters/openai_admin.py +225 -0
- pythinker_code/ui/shell/usage_adapters/openai_chatgpt.py +241 -0
- pythinker_code/ui/shell/usage_adapters/opencode_go.py +232 -0
- pythinker_code/ui/shell/usage_adapters/openrouter.py +105 -0
- pythinker_code/ui/shell/usage_adapters/pythinker.py +189 -0
- pythinker_code/ui/shell/usage_adapters/pythinker_ai.py +50 -0
- pythinker_code/ui/shell/usage_render.py +150 -0
- pythinker_code/ui/shell/visualize/__init__.py +165 -0
- pythinker_code/ui/shell/visualize/_approval_panel.py +539 -0
- pythinker_code/ui/shell/visualize/_blocks.py +802 -0
- pythinker_code/ui/shell/visualize/_btw_panel.py +227 -0
- pythinker_code/ui/shell/visualize/_input_router.py +48 -0
- pythinker_code/ui/shell/visualize/_interactive.py +531 -0
- pythinker_code/ui/shell/visualize/_live_view.py +891 -0
- pythinker_code/ui/shell/visualize/_question_panel.py +586 -0
- pythinker_code/ui/shell/visualize/_worklog.py +245 -0
- pythinker_code/ui/theme.py +395 -0
- pythinker_code/ui/tui_config.py +82 -0
- pythinker_code/usage_ratelimit_cache.py +175 -0
- pythinker_code/utils/__init__.py +0 -0
- pythinker_code/utils/aiohttp.py +24 -0
- pythinker_code/utils/aioqueue.py +72 -0
- pythinker_code/utils/broadcast.py +38 -0
- pythinker_code/utils/changelog.py +108 -0
- pythinker_code/utils/clipboard.py +246 -0
- pythinker_code/utils/datetime.py +64 -0
- pythinker_code/utils/diff.py +135 -0
- pythinker_code/utils/editor.py +91 -0
- pythinker_code/utils/environment.py +73 -0
- pythinker_code/utils/envvar.py +22 -0
- pythinker_code/utils/export.py +696 -0
- pythinker_code/utils/file_filter.py +375 -0
- pythinker_code/utils/frontmatter.py +70 -0
- pythinker_code/utils/io.py +27 -0
- pythinker_code/utils/logging.py +146 -0
- pythinker_code/utils/media_tags.py +29 -0
- pythinker_code/utils/message.py +24 -0
- pythinker_code/utils/path.py +199 -0
- pythinker_code/utils/proctitle.py +33 -0
- pythinker_code/utils/proxy.py +31 -0
- pythinker_code/utils/pyinstaller.py +45 -0
- pythinker_code/utils/rich/__init__.py +33 -0
- pythinker_code/utils/rich/columns.py +99 -0
- pythinker_code/utils/rich/diff_render.py +481 -0
- pythinker_code/utils/rich/markdown.py +935 -0
- pythinker_code/utils/rich/markdown_sample.md +108 -0
- pythinker_code/utils/rich/markdown_sample_short.md +2 -0
- pythinker_code/utils/rich/syntax.py +114 -0
- pythinker_code/utils/sensitive.py +54 -0
- pythinker_code/utils/server.py +121 -0
- pythinker_code/utils/signals.py +43 -0
- pythinker_code/utils/slashcmd.py +124 -0
- pythinker_code/utils/string.py +41 -0
- pythinker_code/utils/subprocess_env.py +83 -0
- pythinker_code/utils/term.py +168 -0
- pythinker_code/utils/typing.py +20 -0
- pythinker_code/vis/__init__.py +0 -0
- pythinker_code/vis/api/__init__.py +5 -0
- pythinker_code/vis/api/sessions.py +714 -0
- pythinker_code/vis/api/statistics.py +209 -0
- pythinker_code/vis/api/system.py +19 -0
- pythinker_code/vis/app.py +199 -0
- pythinker_code/vis/static/assets/highlighted-body-B3W2YXNL-CY1rtwrX.js +1 -0
- pythinker_code/vis/static/assets/index-DSRInNnm.css +1 -0
- pythinker_code/vis/static/assets/index-DgmTI2M_.js +185 -0
- pythinker_code/vis/static/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- pythinker_code/vis/static/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- pythinker_code/vis/static/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- pythinker_code/vis/static/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- pythinker_code/vis/static/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- pythinker_code/vis/static/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- pythinker_code/vis/static/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- pythinker_code/vis/static/index.html +17 -0
- pythinker_code/web/__init__.py +5 -0
- pythinker_code/web/api/__init__.py +15 -0
- pythinker_code/web/api/config.py +217 -0
- pythinker_code/web/api/open_in.py +233 -0
- pythinker_code/web/api/sessions.py +1256 -0
- pythinker_code/web/app.py +449 -0
- pythinker_code/web/auth.py +191 -0
- pythinker_code/web/models.py +98 -0
- pythinker_code/web/runner/__init__.py +5 -0
- pythinker_code/web/runner/messages.py +57 -0
- pythinker_code/web/runner/process.py +754 -0
- pythinker_code/web/runner/worker.py +97 -0
- pythinker_code/web/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- pythinker_code/web/static/assets/_baseUniq-DpSMr1jx.js +1 -0
- pythinker_code/web/static/assets/abap-BdImnpbu.js +1 -0
- pythinker_code/web/static/assets/actionscript-3-CfeIJUat.js +1 -0
- pythinker_code/web/static/assets/ada-bCR0ucgS.js +1 -0
- pythinker_code/web/static/assets/andromeeda-C-Jbm3Hp.js +1 -0
- pythinker_code/web/static/assets/angular-html-CU67Zn6k.js +1 -0
- pythinker_code/web/static/assets/angular-ts-BwZT4LLn.js +1 -0
- pythinker_code/web/static/assets/apache-Pmp26Uib.js +1 -0
- pythinker_code/web/static/assets/apex-D8_7TLub.js +1 -0
- pythinker_code/web/static/assets/apl-dKokRX4l.js +1 -0
- pythinker_code/web/static/assets/applescript-Co6uUVPk.js +1 -0
- pythinker_code/web/static/assets/ara-BRHolxvo.js +1 -0
- pythinker_code/web/static/assets/arc-DpsahJyV.js +1 -0
- pythinker_code/web/static/assets/architectureDiagram-VXUJARFQ-DqiRv9Eg.js +36 -0
- pythinker_code/web/static/assets/asciidoc-Dv7Oe6Be.js +1 -0
- pythinker_code/web/static/assets/asm-D_Q5rh1f.js +1 -0
- pythinker_code/web/static/assets/astro-CbQHKStN.js +1 -0
- pythinker_code/web/static/assets/aurora-x-D-2ljcwZ.js +1 -0
- pythinker_code/web/static/assets/awk-DMzUqQB5.js +1 -0
- pythinker_code/web/static/assets/ayu-dark-CmMr59Fi.js +1 -0
- pythinker_code/web/static/assets/ballerina-BFfxhgS-.js +1 -0
- pythinker_code/web/static/assets/bat-BkioyH1T.js +1 -0
- pythinker_code/web/static/assets/beancount-k_qm7-4y.js +1 -0
- pythinker_code/web/static/assets/berry-uYugtg8r.js +1 -0
- pythinker_code/web/static/assets/bibtex-CHM0blh-.js +1 -0
- pythinker_code/web/static/assets/bicep-Bmn6On1c.js +1 -0
- pythinker_code/web/static/assets/blade-D4QpJJKB.js +1 -0
- pythinker_code/web/static/assets/blockDiagram-VD42YOAC-WgtUvqbp.js +122 -0
- pythinker_code/web/static/assets/bsl-BO_Y6i37.js +1 -0
- pythinker_code/web/static/assets/c-BIGW1oBm.js +1 -0
- pythinker_code/web/static/assets/c3-VCDPK7BO.js +1 -0
- pythinker_code/web/static/assets/c4Diagram-YG6GDRKO-rK0RPuZd.js +10 -0
- pythinker_code/web/static/assets/cadence-Bv_4Rxtq.js +1 -0
- pythinker_code/web/static/assets/cairo-KRGpt6FW.js +1 -0
- pythinker_code/web/static/assets/catppuccin-frappe-DFWUc33u.js +1 -0
- pythinker_code/web/static/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
- pythinker_code/web/static/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
- pythinker_code/web/static/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
- pythinker_code/web/static/assets/channel-B0rlvkH-.js +1 -0
- pythinker_code/web/static/assets/chunk-4BX2VUAB-DIkMuLV-.js +1 -0
- pythinker_code/web/static/assets/chunk-55IACEB6-CORdm4k4.js +1 -0
- pythinker_code/web/static/assets/chunk-B4BG7PRW-D9xDhwHO.js +165 -0
- pythinker_code/web/static/assets/chunk-DI55MBZ5-BDmF9Bh-.js +220 -0
- pythinker_code/web/static/assets/chunk-FMBD7UC4-BCse_HmM.js +15 -0
- pythinker_code/web/static/assets/chunk-QN33PNHL-DCpBmTzA.js +1 -0
- pythinker_code/web/static/assets/chunk-QZHKN3VN-BqLuqobw.js +1 -0
- pythinker_code/web/static/assets/chunk-TZMSLE5B-8K2ogOKS.js +1 -0
- pythinker_code/web/static/assets/clarity-D53aC0YG.js +1 -0
- pythinker_code/web/static/assets/classDiagram-2ON5EDUG-D_ZHSii2.js +1 -0
- pythinker_code/web/static/assets/classDiagram-v2-WZHVMYZB-D_ZHSii2.js +1 -0
- pythinker_code/web/static/assets/clojure-P80f7IUj.js +1 -0
- pythinker_code/web/static/assets/clone-GSXejyY1.js +1 -0
- pythinker_code/web/static/assets/cmake-D1j8_8rp.js +1 -0
- pythinker_code/web/static/assets/cobol-nwyudZeR.js +1 -0
- pythinker_code/web/static/assets/code-block-IT6T5CEO-DWTFYA28.js +2 -0
- pythinker_code/web/static/assets/codeowners-Bp6g37R7.js +1 -0
- pythinker_code/web/static/assets/codeql-DsOJ9woJ.js +1 -0
- pythinker_code/web/static/assets/coffee-Ch7k5sss.js +1 -0
- pythinker_code/web/static/assets/common-lisp-Cg-RD9OK.js +1 -0
- pythinker_code/web/static/assets/coq-DkFqJrB1.js +1 -0
- pythinker_code/web/static/assets/cose-bilkent-S5V4N54A-BRI7ES-N.js +1 -0
- pythinker_code/web/static/assets/cpp-CofmeUqb.js +1 -0
- pythinker_code/web/static/assets/crystal-tKQVLTB8.js +1 -0
- pythinker_code/web/static/assets/csharp-K5feNrxe.js +1 -0
- pythinker_code/web/static/assets/css-DPfMkruS.js +1 -0
- pythinker_code/web/static/assets/csv-fuZLfV_i.js +1 -0
- pythinker_code/web/static/assets/cue-D82EKSYY.js +1 -0
- pythinker_code/web/static/assets/cypher-COkxafJQ.js +1 -0
- pythinker_code/web/static/assets/cytoscape.esm-B6BxUuKW.js +321 -0
- pythinker_code/web/static/assets/d-85-TOEBH.js +1 -0
- pythinker_code/web/static/assets/dagre-6UL2VRFP-Ci5GdWfi.js +4 -0
- pythinker_code/web/static/assets/dark-plus-C3mMm8J8.js +1 -0
- pythinker_code/web/static/assets/dart-CF10PKvl.js +1 -0
- pythinker_code/web/static/assets/dax-CEL-wOlO.js +1 -0
- pythinker_code/web/static/assets/defaultLocale-DX6XiGOO.js +1 -0
- pythinker_code/web/static/assets/desktop-BmXAJ9_W.js +1 -0
- pythinker_code/web/static/assets/diagram-PSM6KHXK-0hhAylV4.js +24 -0
- pythinker_code/web/static/assets/diagram-QEK2KX5R-8fxgaW6d.js +43 -0
- pythinker_code/web/static/assets/diagram-S2PKOQOG-FRr0_atE.js +24 -0
- pythinker_code/web/static/assets/diff-D97Zzqfu.js +1 -0
- pythinker_code/web/static/assets/docker-BcOcwvcX.js +1 -0
- pythinker_code/web/static/assets/dotenv-Da5cRb03.js +1 -0
- pythinker_code/web/static/assets/dracula-BzJJZx-M.js +1 -0
- pythinker_code/web/static/assets/dracula-soft-BXkSAIEj.js +1 -0
- pythinker_code/web/static/assets/dream-maker-BtqSS_iP.js +1 -0
- pythinker_code/web/static/assets/edge-BkV0erSs.js +1 -0
- pythinker_code/web/static/assets/elixir-CDX3lj18.js +1 -0
- pythinker_code/web/static/assets/elm-DbKCFpqz.js +1 -0
- pythinker_code/web/static/assets/emacs-lisp-C9XAeP06.js +1 -0
- pythinker_code/web/static/assets/erDiagram-Q2GNP2WA-B3T-hJUM.js +60 -0
- pythinker_code/web/static/assets/erb-BOJIQeun.js +1 -0
- pythinker_code/web/static/assets/erlang-DsQrWhSR.js +1 -0
- pythinker_code/web/static/assets/everforest-dark-BgDCqdQA.js +1 -0
- pythinker_code/web/static/assets/everforest-light-C8M2exoo.js +1 -0
- pythinker_code/web/static/assets/fennel-BYunw83y.js +1 -0
- pythinker_code/web/static/assets/fish-BvzEVeQv.js +1 -0
- pythinker_code/web/static/assets/flowDiagram-NV44I4VS-D0S3u7ot.js +162 -0
- pythinker_code/web/static/assets/fluent-C4IJs8-o.js +1 -0
- pythinker_code/web/static/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
- pythinker_code/web/static/assets/fortran-free-form-BxgE0vQu.js +1 -0
- pythinker_code/web/static/assets/fsharp-CXgrBDvD.js +1 -0
- pythinker_code/web/static/assets/ganttDiagram-JELNMOA3-CHrN2a23.js +267 -0
- pythinker_code/web/static/assets/gdresource-B7Tvp0Sc.js +1 -0
- pythinker_code/web/static/assets/gdscript-DTMYz4Jt.js +1 -0
- pythinker_code/web/static/assets/gdshader-DkwncUOv.js +1 -0
- pythinker_code/web/static/assets/genie-D0YGMca9.js +1 -0
- pythinker_code/web/static/assets/gherkin-DyxjwDmM.js +1 -0
- pythinker_code/web/static/assets/git-commit-F4YmCXRG.js +1 -0
- pythinker_code/web/static/assets/git-rebase-r7XF79zn.js +1 -0
- pythinker_code/web/static/assets/gitGraphDiagram-NY62KEGX-CfcXZWg0.js +65 -0
- pythinker_code/web/static/assets/github-dark-DHJKELXO.js +1 -0
- pythinker_code/web/static/assets/github-dark-default-Cuk6v7N8.js +1 -0
- pythinker_code/web/static/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
- pythinker_code/web/static/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
- pythinker_code/web/static/assets/github-light-DAi9KRSo.js +1 -0
- pythinker_code/web/static/assets/github-light-default-D7oLnXFd.js +1 -0
- pythinker_code/web/static/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
- pythinker_code/web/static/assets/gleam-BspZqrRM.js +1 -0
- pythinker_code/web/static/assets/glimmer-js-Rg0-pVw9.js +1 -0
- pythinker_code/web/static/assets/glimmer-ts-U6CK756n.js +1 -0
- pythinker_code/web/static/assets/glsl-DplSGwfg.js +1 -0
- pythinker_code/web/static/assets/gn-n2N0HUVH.js +1 -0
- pythinker_code/web/static/assets/gnuplot-DdkO51Og.js +1 -0
- pythinker_code/web/static/assets/go-Dn2_MT6a.js +1 -0
- pythinker_code/web/static/assets/graph-8jMJwCqE.js +1 -0
- pythinker_code/web/static/assets/graphql-ChdNCCLP.js +1 -0
- pythinker_code/web/static/assets/groovy-gcz8RCvz.js +1 -0
- pythinker_code/web/static/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
- pythinker_code/web/static/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
- pythinker_code/web/static/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
- pythinker_code/web/static/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
- pythinker_code/web/static/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
- pythinker_code/web/static/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
- pythinker_code/web/static/assets/hack-CaT9iCJl.js +1 -0
- pythinker_code/web/static/assets/haml-B8DHNrY2.js +1 -0
- pythinker_code/web/static/assets/handlebars-BL8al0AC.js +1 -0
- pythinker_code/web/static/assets/haskell-Df6bDoY_.js +1 -0
- pythinker_code/web/static/assets/haxe-CzTSHFRz.js +1 -0
- pythinker_code/web/static/assets/hcl-BWvSN4gD.js +1 -0
- pythinker_code/web/static/assets/hjson-D5-asLiD.js +1 -0
- pythinker_code/web/static/assets/hlsl-D3lLCCz7.js +1 -0
- pythinker_code/web/static/assets/houston-DnULxvSX.js +1 -0
- pythinker_code/web/static/assets/html-GMplVEZG.js +1 -0
- pythinker_code/web/static/assets/html-derivative-BFtXZ54Q.js +1 -0
- pythinker_code/web/static/assets/http-jrhK8wxY.js +1 -0
- pythinker_code/web/static/assets/hurl-irOxFIW8.js +1 -0
- pythinker_code/web/static/assets/hxml-Bvhsp5Yf.js +1 -0
- pythinker_code/web/static/assets/hy-DFXneXwc.js +1 -0
- pythinker_code/web/static/assets/imba-DGztddWO.js +1 -0
- pythinker_code/web/static/assets/index-BXrFnzMy.js +153 -0
- pythinker_code/web/static/assets/index-BpoLgcEt.js +1 -0
- pythinker_code/web/static/assets/index-BrfQJnRD.js +5 -0
- pythinker_code/web/static/assets/index-C4gFzubz.js +2 -0
- pythinker_code/web/static/assets/index-CzV_vCfu.css +1 -0
- pythinker_code/web/static/assets/index-DI2oedCt.js +19 -0
- pythinker_code/web/static/assets/infoDiagram-WHAUD3N6-DdxonBf3.js +2 -0
- pythinker_code/web/static/assets/ini-BEwlwnbL.js +1 -0
- pythinker_code/web/static/assets/init-Gi6I4Gst.js +1 -0
- pythinker_code/web/static/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- pythinker_code/web/static/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- pythinker_code/web/static/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- pythinker_code/web/static/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- pythinker_code/web/static/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- pythinker_code/web/static/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- pythinker_code/web/static/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- pythinker_code/web/static/assets/java-CylS5w8V.js +1 -0
- pythinker_code/web/static/assets/javascript-wDzz0qaB.js +1 -0
- pythinker_code/web/static/assets/jinja-4LBKfQ-Z.js +1 -0
- pythinker_code/web/static/assets/jison-wvAkD_A8.js +1 -0
- pythinker_code/web/static/assets/journeyDiagram-XKPGCS4Q-BXf4aQei.js +139 -0
- pythinker_code/web/static/assets/json-Cp-IABpG.js +1 -0
- pythinker_code/web/static/assets/json5-C9tS-k6U.js +1 -0
- pythinker_code/web/static/assets/jsonc-Des-eS-w.js +1 -0
- pythinker_code/web/static/assets/jsonl-DcaNXYhu.js +1 -0
- pythinker_code/web/static/assets/jsonnet-DFQXde-d.js +1 -0
- pythinker_code/web/static/assets/jssm-C2t-YnRu.js +1 -0
- pythinker_code/web/static/assets/jsx-g9-lgVsj.js +1 -0
- pythinker_code/web/static/assets/julia-CxzCAyBv.js +1 -0
- pythinker_code/web/static/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
- pythinker_code/web/static/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
- pythinker_code/web/static/assets/kanagawa-wave-DWedfzmr.js +1 -0
- pythinker_code/web/static/assets/kanban-definition-3W4ZIXB7-DLpPPOu8.js +89 -0
- pythinker_code/web/static/assets/katex-D2lIc1rk.css +1 -0
- pythinker_code/web/static/assets/kdl-DV7GczEv.js +1 -0
- pythinker_code/web/static/assets/kotlin-BdnUsdx6.js +1 -0
- pythinker_code/web/static/assets/kusto-DZf3V79B.js +1 -0
- pythinker_code/web/static/assets/laserwave-DUszq2jm.js +1 -0
- pythinker_code/web/static/assets/latex-B4uzh10-.js +1 -0
- pythinker_code/web/static/assets/layout-DH73UoAH.js +1 -0
- pythinker_code/web/static/assets/lean-BZvkOJ9d.js +1 -0
- pythinker_code/web/static/assets/less-B1dDrJ26.js +1 -0
- pythinker_code/web/static/assets/light-plus-B7mTdjB0.js +1 -0
- pythinker_code/web/static/assets/linear-bAer2-sK.js +1 -0
- pythinker_code/web/static/assets/liquid-DYVedYrR.js +1 -0
- pythinker_code/web/static/assets/llvm-BtvRca6l.js +1 -0
- pythinker_code/web/static/assets/log-2UxHyX5q.js +1 -0
- pythinker_code/web/static/assets/logo-BtOb2qkB.js +1 -0
- pythinker_code/web/static/assets/lua-BbnMAYS6.js +1 -0
- pythinker_code/web/static/assets/luau-C-HG3fhB.js +1 -0
- pythinker_code/web/static/assets/make-CHLpvVh8.js +1 -0
- pythinker_code/web/static/assets/markdown-Cvjx9yec.js +1 -0
- pythinker_code/web/static/assets/marko-DZsq8hO1.js +1 -0
- pythinker_code/web/static/assets/material-theme-D5KoaKCx.js +1 -0
- pythinker_code/web/static/assets/material-theme-darker-BfHTSMKl.js +1 -0
- pythinker_code/web/static/assets/material-theme-lighter-B0m2ddpp.js +1 -0
- pythinker_code/web/static/assets/material-theme-ocean-CyktbL80.js +1 -0
- pythinker_code/web/static/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
- pythinker_code/web/static/assets/matlab-D7o27uSR.js +1 -0
- pythinker_code/web/static/assets/mdc-DUICxH0z.js +1 -0
- pythinker_code/web/static/assets/mdx-Cmh6b_Ma.js +1 -0
- pythinker_code/web/static/assets/mermaid-VLURNSYL-B2P5VJ9v.css +1 -0
- pythinker_code/web/static/assets/mermaid-VLURNSYL-CuqbwKXv.js +465 -0
- pythinker_code/web/static/assets/mermaid-mWjccvbQ.js +1 -0
- pythinker_code/web/static/assets/mermaid.core-Nx-rTKiV.js +191 -0
- pythinker_code/web/static/assets/min-DbfD8Ywu.js +1 -0
- pythinker_code/web/static/assets/min-dark-CafNBF8u.js +1 -0
- pythinker_code/web/static/assets/min-light-CTRr51gU.js +1 -0
- pythinker_code/web/static/assets/mindmap-definition-VGOIOE7T-C6l761Ue.js +68 -0
- pythinker_code/web/static/assets/mipsasm-CKIfxQSi.js +1 -0
- pythinker_code/web/static/assets/mojo-B93PlW-d.js +1 -0
- pythinker_code/web/static/assets/monokai-D4h5O-jR.js +1 -0
- pythinker_code/web/static/assets/moonbit-Ba13S78F.js +1 -0
- pythinker_code/web/static/assets/move-Bu9oaDYs.js +1 -0
- pythinker_code/web/static/assets/narrat-DRg8JJMk.js +1 -0
- pythinker_code/web/static/assets/nextflow-BrzmwbiE.js +1 -0
- pythinker_code/web/static/assets/nginx-DknmC5AR.js +1 -0
- pythinker_code/web/static/assets/night-owl-C39BiMTA.js +1 -0
- pythinker_code/web/static/assets/nim-CVrawwO9.js +1 -0
- pythinker_code/web/static/assets/nix-CwoSXNpI.js +1 -0
- pythinker_code/web/static/assets/nord-Ddv68eIx.js +1 -0
- pythinker_code/web/static/assets/nushell-C-sUppwS.js +1 -0
- pythinker_code/web/static/assets/objective-c-DXmwc3jG.js +1 -0
- pythinker_code/web/static/assets/objective-cpp-CLxacb5B.js +1 -0
- pythinker_code/web/static/assets/ocaml-C0hk2d4L.js +1 -0
- pythinker_code/web/static/assets/one-dark-pro-DVMEJ2y_.js +1 -0
- pythinker_code/web/static/assets/one-light-PoHY5YXO.js +1 -0
- pythinker_code/web/static/assets/openscad-C4EeE6gA.js +1 -0
- pythinker_code/web/static/assets/ordinal-Cboi1Yqb.js +1 -0
- pythinker_code/web/static/assets/pascal-D93ZcfNL.js +1 -0
- pythinker_code/web/static/assets/perl-C0TMdlhV.js +1 -0
- pythinker_code/web/static/assets/php-CDn_0X-4.js +1 -0
- pythinker_code/web/static/assets/pieDiagram-ADFJNKIX-fNg41mT9.js +30 -0
- pythinker_code/web/static/assets/pkl-u5AG7uiY.js +1 -0
- pythinker_code/web/static/assets/plastic-3e1v2bzS.js +1 -0
- pythinker_code/web/static/assets/plsql-ChMvpjG-.js +1 -0
- pythinker_code/web/static/assets/po-BTJTHyun.js +1 -0
- pythinker_code/web/static/assets/poimandres-CS3Unz2-.js +1 -0
- pythinker_code/web/static/assets/polar-C0HS_06l.js +1 -0
- pythinker_code/web/static/assets/postcss-CXtECtnM.js +1 -0
- pythinker_code/web/static/assets/powerquery-CEu0bR-o.js +1 -0
- pythinker_code/web/static/assets/powershell-Dpen1YoG.js +1 -0
- pythinker_code/web/static/assets/prisma-Dd19v3D-.js +1 -0
- pythinker_code/web/static/assets/prolog-CbFg5uaA.js +1 -0
- pythinker_code/web/static/assets/proto-C7zT0LnQ.js +1 -0
- pythinker_code/web/static/assets/pug-CGlum2m_.js +1 -0
- pythinker_code/web/static/assets/puppet-BMWR74SV.js +1 -0
- pythinker_code/web/static/assets/purescript-CklMAg4u.js +1 -0
- pythinker_code/web/static/assets/python-B6aJPvgy.js +1 -0
- pythinker_code/web/static/assets/qml-3beO22l8.js +1 -0
- pythinker_code/web/static/assets/qmldir-C8lEn-DE.js +1 -0
- pythinker_code/web/static/assets/qss-IeuSbFQv.js +1 -0
- pythinker_code/web/static/assets/quadrantDiagram-AYHSOK5B-DJz3Kx87.js +7 -0
- pythinker_code/web/static/assets/r-Dspwwk_N.js +1 -0
- pythinker_code/web/static/assets/racket-BqYA7rlc.js +1 -0
- pythinker_code/web/static/assets/raku-DXvB9xmW.js +1 -0
- pythinker_code/web/static/assets/razor-C1TweQQi.js +1 -0
- pythinker_code/web/static/assets/red-bN70gL4F.js +1 -0
- pythinker_code/web/static/assets/reg-C-SQnVFl.js +1 -0
- pythinker_code/web/static/assets/regexp-CDVJQ6XC.js +1 -0
- pythinker_code/web/static/assets/rel-C3B-1QV4.js +1 -0
- pythinker_code/web/static/assets/requirementDiagram-UZGBJVZJ-B4SbrfE9.js +64 -0
- pythinker_code/web/static/assets/riscv-BM1_JUlF.js +1 -0
- pythinker_code/web/static/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
- pythinker_code/web/static/assets/rose-pine-moon-D4_iv3hh.js +1 -0
- pythinker_code/web/static/assets/rose-pine-qdsjHGoJ.js +1 -0
- pythinker_code/web/static/assets/rosmsg-BJDFO7_C.js +1 -0
- pythinker_code/web/static/assets/rst-B0xPkSld.js +1 -0
- pythinker_code/web/static/assets/ruby-BvKwtOVI.js +1 -0
- pythinker_code/web/static/assets/rust-B1yitclQ.js +1 -0
- pythinker_code/web/static/assets/sankeyDiagram-TZEHDZUN-CoSUjLAG.js +10 -0
- pythinker_code/web/static/assets/sas-cz2c8ADy.js +1 -0
- pythinker_code/web/static/assets/sass-Cj5Yp3dK.js +1 -0
- pythinker_code/web/static/assets/scala-C151Ov-r.js +1 -0
- pythinker_code/web/static/assets/scheme-C98Dy4si.js +1 -0
- pythinker_code/web/static/assets/scss-OYdSNvt2.js +1 -0
- pythinker_code/web/static/assets/sdbl-DVxCFoDh.js +1 -0
- pythinker_code/web/static/assets/sequenceDiagram-WL72ISMW-PjhBNHi3.js +145 -0
- pythinker_code/web/static/assets/shaderlab-Dg9Lc6iA.js +1 -0
- pythinker_code/web/static/assets/shellscript-Yzrsuije.js +1 -0
- pythinker_code/web/static/assets/shellsession-BADoaaVG.js +1 -0
- pythinker_code/web/static/assets/slack-dark-BthQWCQV.js +1 -0
- pythinker_code/web/static/assets/slack-ochin-DqwNpetd.js +1 -0
- pythinker_code/web/static/assets/smalltalk-BERRCDM3.js +1 -0
- pythinker_code/web/static/assets/snazzy-light-Bw305WKR.js +1 -0
- pythinker_code/web/static/assets/solarized-dark-DXbdFlpD.js +1 -0
- pythinker_code/web/static/assets/solarized-light-L9t79GZl.js +1 -0
- pythinker_code/web/static/assets/solidity-rGO070M0.js +1 -0
- pythinker_code/web/static/assets/soy-Brmx7dQM.js +1 -0
- pythinker_code/web/static/assets/sparql-rVzFXLq3.js +1 -0
- pythinker_code/web/static/assets/splunk-BtCnVYZw.js +1 -0
- pythinker_code/web/static/assets/sql-BLtJtn59.js +1 -0
- pythinker_code/web/static/assets/ssh-config-_ykCGR6B.js +1 -0
- pythinker_code/web/static/assets/stata-BH5u7GGu.js +1 -0
- pythinker_code/web/static/assets/stateDiagram-FKZM4ZOC-DOwESt8-.js +1 -0
- pythinker_code/web/static/assets/stateDiagram-v2-4FDKWEC3-yl3OHWiP.js +1 -0
- pythinker_code/web/static/assets/stylus-BEDo0Tqx.js +1 -0
- pythinker_code/web/static/assets/svelte-zxCyuUbr.js +1 -0
- pythinker_code/web/static/assets/swift-Dg5xB15N.js +1 -0
- pythinker_code/web/static/assets/synthwave-84-CbfX1IO0.js +1 -0
- pythinker_code/web/static/assets/system-verilog-CnnmHF94.js +1 -0
- pythinker_code/web/static/assets/systemd-4A_iFExJ.js +1 -0
- pythinker_code/web/static/assets/talonscript-CkByrt1z.js +1 -0
- pythinker_code/web/static/assets/tasl-QIJgUcNo.js +1 -0
- pythinker_code/web/static/assets/tcl-dwOrl1Do.js +1 -0
- pythinker_code/web/static/assets/templ-W15q3VgB.js +1 -0
- pythinker_code/web/static/assets/terraform-BETggiCN.js +1 -0
- pythinker_code/web/static/assets/tex-CvyZ59Mk.js +1 -0
- pythinker_code/web/static/assets/timeline-definition-IT6M3QCI-CkCLnAgi.js +61 -0
- pythinker_code/web/static/assets/tokyo-night-hegEt444.js +1 -0
- pythinker_code/web/static/assets/toml-vGWfd6FD.js +1 -0
- pythinker_code/web/static/assets/treemap-KMMF4GRG-CZS5XwTf.js +128 -0
- pythinker_code/web/static/assets/ts-tags-zn1MmPIZ.js +1 -0
- pythinker_code/web/static/assets/tsv-B_m7g4N7.js +1 -0
- pythinker_code/web/static/assets/tsx-COt5Ahok.js +1 -0
- pythinker_code/web/static/assets/turtle-BsS91CYL.js +1 -0
- pythinker_code/web/static/assets/twig-CO9l9SDP.js +1 -0
- pythinker_code/web/static/assets/typescript-BPQ3VLAy.js +1 -0
- pythinker_code/web/static/assets/typespec-BGHnOYBU.js +1 -0
- pythinker_code/web/static/assets/typst-DHCkPAjA.js +1 -0
- pythinker_code/web/static/assets/v-BcVCzyr7.js +1 -0
- pythinker_code/web/static/assets/vala-CsfeWuGM.js +1 -0
- pythinker_code/web/static/assets/vb-D17OF-Vu.js +1 -0
- pythinker_code/web/static/assets/verilog-BQ8w6xss.js +1 -0
- pythinker_code/web/static/assets/vesper-DU1UobuO.js +1 -0
- pythinker_code/web/static/assets/vhdl-CeAyd5Ju.js +1 -0
- pythinker_code/web/static/assets/viml-CJc9bBzg.js +1 -0
- pythinker_code/web/static/assets/vitesse-black-Bkuqu6BP.js +1 -0
- pythinker_code/web/static/assets/vitesse-dark-D0r3Knsf.js +1 -0
- pythinker_code/web/static/assets/vitesse-light-CVO1_9PV.js +1 -0
- pythinker_code/web/static/assets/vue-DN_0RTcg.js +1 -0
- pythinker_code/web/static/assets/vue-html-AaS7Mt5G.js +1 -0
- pythinker_code/web/static/assets/vue-vine-CQOfvN7w.js +1 -0
- pythinker_code/web/static/assets/vyper-CDx5xZoG.js +1 -0
- pythinker_code/web/static/assets/wasm-CG6Dc4jp.js +1 -0
- pythinker_code/web/static/assets/wasm-MzD3tlZU.js +1 -0
- pythinker_code/web/static/assets/wenyan-BV7otONQ.js +1 -0
- pythinker_code/web/static/assets/wgsl-Dx-B1_4e.js +1 -0
- pythinker_code/web/static/assets/wikitext-BhOHFoWU.js +1 -0
- pythinker_code/web/static/assets/wit-5i3qLPDT.js +1 -0
- pythinker_code/web/static/assets/wolfram-lXgVvXCa.js +1 -0
- pythinker_code/web/static/assets/xml-sdJ4AIDG.js +1 -0
- pythinker_code/web/static/assets/xsl-CtQFsRM5.js +1 -0
- pythinker_code/web/static/assets/xychartDiagram-PRI3JC2R-DkqqHNLh.js +7 -0
- pythinker_code/web/static/assets/yaml-Buea-lGh.js +1 -0
- pythinker_code/web/static/assets/zenscript-DVFEvuxE.js +1 -0
- pythinker_code/web/static/assets/zig-VOosw3JB.js +1 -0
- pythinker_code/web/static/brand/apple-touch-icon.png +0 -0
- pythinker_code/web/static/brand/arctecture.webp +0 -0
- pythinker_code/web/static/brand/bimi-logo.svg +46 -0
- pythinker_code/web/static/brand/favicon.ico +0 -0
- pythinker_code/web/static/brand/fonts/dm-sans-latin-ext.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/dm-sans-latin.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/instrument-sans-latin-ext.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/instrument-sans-latin.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/instrument-serif-latin-ext.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/instrument-serif-latin.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/libre-baskerville-italic-latin-ext.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/libre-baskerville-italic-latin.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/libre-baskerville-latin-ext.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/libre-baskerville-latin.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/roboto-latin-ext.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/roboto-latin.woff2 +0 -0
- pythinker_code/web/static/brand/icon-192.png +0 -0
- pythinker_code/web/static/brand/icon-512.png +0 -0
- pythinker_code/web/static/brand/icon.svg +1 -0
- pythinker_code/web/static/brand/logo.png +0 -0
- pythinker_code/web/static/brand/pythinker_animated.svg +79 -0
- pythinker_code/web/static/brand/robots.txt +4 -0
- pythinker_code/web/static/index.html +15 -0
- pythinker_code/web/static/logo.png +0 -0
- pythinker_code/web/store/__init__.py +1 -0
- pythinker_code/web/store/sessions.py +433 -0
- pythinker_code/wire/__init__.py +148 -0
- pythinker_code/wire/file.py +151 -0
- pythinker_code/wire/jsonrpc.py +263 -0
- pythinker_code/wire/protocol.py +2 -0
- pythinker_code/wire/root_hub.py +27 -0
- pythinker_code/wire/serde.py +26 -0
- pythinker_code/wire/server.py +1072 -0
- pythinker_code/wire/types.py +698 -0
- pythinker_code-0.8.0.dist-info/METADATA +706 -0
- pythinker_code-0.8.0.dist-info/RECORD +790 -0
- pythinker_code-0.8.0.dist-info/WHEEL +4 -0
- pythinker_code-0.8.0.dist-info/entry_points.txt +4 -0
- pythinker_code-0.8.0.dist-info/licenses/LICENSE +202 -0
- 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
|
+
)
|