pythinker-code 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pythinker_code/CHANGELOG.md +16 -0
- pythinker_code/__init__.py +0 -0
- pythinker_code/__main__.py +92 -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 +298 -0
- pythinker_code/acp/mcp.py +46 -0
- pythinker_code/acp/server.py +497 -0
- pythinker_code/acp/session.py +496 -0
- pythinker_code/acp/tools.py +167 -0
- pythinker_code/acp/types.py +13 -0
- pythinker_code/acp/version.py +45 -0
- pythinker_code/agents/default/agent.yaml +36 -0
- pythinker_code/agents/default/coder.yaml +25 -0
- pythinker_code/agents/default/explore.yaml +46 -0
- pythinker_code/agents/default/plan.yaml +30 -0
- pythinker_code/agents/default/system.md +164 -0
- pythinker_code/agents/okabe/agent.yaml +22 -0
- pythinker_code/agentspec.py +163 -0
- pythinker_code/app.py +820 -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/lm_studio.py +418 -0
- pythinker_code/auth/minimax.py +203 -0
- pythinker_code/auth/oauth.py +1122 -0
- pythinker_code/auth/ollama.py +293 -0
- pythinker_code/auth/openai.py +771 -0
- pythinker_code/auth/opencode_go.py +203 -0
- pythinker_code/auth/openrouter.py +225 -0
- pythinker_code/auth/platforms.py +466 -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 +650 -0
- pythinker_code/background/models.py +105 -0
- pythinker_code/background/store.py +237 -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 +238 -0
- pythinker_code/cli/export.py +322 -0
- pythinker_code/cli/info.py +62 -0
- pythinker_code/cli/mcp.py +349 -0
- pythinker_code/cli/plugin.py +351 -0
- pythinker_code/cli/toad.py +74 -0
- pythinker_code/cli/vis.py +38 -0
- pythinker_code/cli/web.py +80 -0
- pythinker_code/config.py +453 -0
- pythinker_code/constant.py +33 -0
- pythinker_code/exception.py +43 -0
- pythinker_code/hooks/__init__.py +4 -0
- pythinker_code/hooks/config.py +34 -0
- pythinker_code/hooks/engine.py +371 -0
- pythinker_code/hooks/events.py +190 -0
- pythinker_code/hooks/runner.py +89 -0
- pythinker_code/llm.py +412 -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 +153 -0
- pythinker_code/plugin/tool.py +173 -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 +520 -0
- pythinker_code/soul/approval.py +267 -0
- pythinker_code/soul/btw.py +214 -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/pythinkersoul.py +1613 -0
- pythinker_code/soul/slash.py +340 -0
- pythinker_code/soul/toolset.py +788 -0
- pythinker_code/subagents/__init__.py +21 -0
- pythinker_code/subagents/builder.py +42 -0
- pythinker_code/subagents/core.py +86 -0
- pythinker_code/subagents/git_context.py +172 -0
- pythinker_code/subagents/models.py +54 -0
- pythinker_code/subagents/output.py +71 -0
- pythinker_code/subagents/registry.py +28 -0
- pythinker_code/subagents/runner.py +428 -0
- pythinker_code/subagents/store.py +196 -0
- pythinker_code/telemetry/__init__.py +211 -0
- pythinker_code/telemetry/config.py +54 -0
- pythinker_code/telemetry/crash.py +157 -0
- pythinker_code/telemetry/metrics.py +208 -0
- pythinker_code/telemetry/otel.py +240 -0
- pythinker_code/telemetry/sentry.py +167 -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 +277 -0
- pythinker_code/tools/agent/description.md +41 -0
- pythinker_code/tools/ask_user/__init__.py +159 -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 +30 -0
- pythinker_code/tools/file/glob.md +17 -0
- pythinker_code/tools/file/glob.py +160 -0
- pythinker_code/tools/file/grep.md +6 -0
- pythinker_code/tools/file/grep_local.py +589 -0
- pythinker_code/tools/file/plan_mode.py +45 -0
- pythinker_code/tools/file/read.md +16 -0
- pythinker_code/tools/file/read.py +300 -0
- pythinker_code/tools/file/read_media.md +24 -0
- pythinker_code/tools/file/read_media.py +217 -0
- pythinker_code/tools/file/replace.md +7 -0
- pythinker_code/tools/file/replace.py +195 -0
- pythinker_code/tools/file/utils.py +257 -0
- pythinker_code/tools/file/write.md +5 -0
- pythinker_code/tools/file/write.py +177 -0
- pythinker_code/tools/plan/__init__.py +327 -0
- pythinker_code/tools/plan/description.md +29 -0
- pythinker_code/tools/plan/enter.py +190 -0
- pythinker_code/tools/plan/enter_description.md +35 -0
- pythinker_code/tools/plan/heroes.py +277 -0
- pythinker_code/tools/shell/__init__.py +253 -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 +199 -0
- pythinker_code/tools/web/__init__.py +4 -0
- pythinker_code/tools/web/fetch.md +1 -0
- pythinker_code/tools/web/fetch.py +189 -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 +1696 -0
- pythinker_code/ui/shell/console.py +109 -0
- pythinker_code/ui/shell/debug.py +190 -0
- pythinker_code/ui/shell/echo.py +17 -0
- pythinker_code/ui/shell/export_import.py +117 -0
- pythinker_code/ui/shell/keyboard.py +300 -0
- pythinker_code/ui/shell/mcp_status.py +113 -0
- pythinker_code/ui/shell/model_picker.py +318 -0
- pythinker_code/ui/shell/oauth.py +272 -0
- pythinker_code/ui/shell/placeholders.py +531 -0
- pythinker_code/ui/shell/prompt.py +2278 -0
- pythinker_code/ui/shell/replay.py +215 -0
- pythinker_code/ui/shell/session_picker.py +227 -0
- pythinker_code/ui/shell/setup.py +212 -0
- pythinker_code/ui/shell/slash.py +898 -0
- pythinker_code/ui/shell/startup.py +32 -0
- pythinker_code/ui/shell/task_browser.py +486 -0
- pythinker_code/ui/shell/update.py +350 -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 +505 -0
- pythinker_code/ui/shell/visualize/_blocks.py +629 -0
- pythinker_code/ui/shell/visualize/_btw_panel.py +224 -0
- pythinker_code/ui/shell/visualize/_input_router.py +48 -0
- pythinker_code/ui/shell/visualize/_interactive.py +523 -0
- pythinker_code/ui/shell/visualize/_live_view.py +826 -0
- pythinker_code/ui/shell/visualize/_question_panel.py +586 -0
- pythinker_code/ui/theme.py +241 -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 +37 -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 +900 -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 +73 -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 +687 -0
- pythinker_code/vis/api/statistics.py +209 -0
- pythinker_code/vis/api/system.py +19 -0
- pythinker_code/vis/app.py +175 -0
- pythinker_code/vis/static/assets/highlighted-body-B3W2YXNL-D2MTYyJz.js +1 -0
- pythinker_code/vis/static/assets/index-CezafTt_.js +185 -0
- pythinker_code/vis/static/assets/index-DSRInNnm.css +1 -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 +208 -0
- pythinker_code/web/api/open_in.py +199 -0
- pythinker_code/web/api/sessions.py +1225 -0
- pythinker_code/web/app.py +451 -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--dyU3g5v.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-DkMjLpYa.js +1 -0
- pythinker_code/web/static/assets/architectureDiagram-VXUJARFQ-CHWVaGo9.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-DzdKe497.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-D84Blotg.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-CllSjjdl.js +1 -0
- pythinker_code/web/static/assets/chunk-4BX2VUAB-C9w8wleE.js +1 -0
- pythinker_code/web/static/assets/chunk-55IACEB6-YlYJ8HnF.js +1 -0
- pythinker_code/web/static/assets/chunk-B4BG7PRW-Bwtz_AHU.js +165 -0
- pythinker_code/web/static/assets/chunk-DI55MBZ5-BbEHkl8h.js +220 -0
- pythinker_code/web/static/assets/chunk-FMBD7UC4-BKPbvjLC.js +15 -0
- pythinker_code/web/static/assets/chunk-QN33PNHL-D73dQvKf.js +1 -0
- pythinker_code/web/static/assets/chunk-QZHKN3VN-zGiLKes_.js +1 -0
- pythinker_code/web/static/assets/chunk-TZMSLE5B-LHJCi2fy.js +1 -0
- pythinker_code/web/static/assets/clarity-D53aC0YG.js +1 -0
- pythinker_code/web/static/assets/classDiagram-2ON5EDUG-vX27iZwa.js +1 -0
- pythinker_code/web/static/assets/classDiagram-v2-WZHVMYZB-vX27iZwa.js +1 -0
- pythinker_code/web/static/assets/clojure-P80f7IUj.js +1 -0
- pythinker_code/web/static/assets/clone-DYBkaPm2.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-NtKViZGl.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-DialjZpd.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-C_Fzpdck.js +321 -0
- pythinker_code/web/static/assets/d-85-TOEBH.js +1 -0
- pythinker_code/web/static/assets/dagre-6UL2VRFP-DfuvkZZ7.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-DLGctX3v.js +24 -0
- pythinker_code/web/static/assets/diagram-QEK2KX5R-DnxN6S0F.js +43 -0
- pythinker_code/web/static/assets/diagram-S2PKOQOG-Caq_Set9.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-BgTfALoK.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-QjW_fnGi.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-fqi8JFof.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-i7o6VQ8x.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-C0vZK2pT.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-BYCCk6-K.js +153 -0
- pythinker_code/web/static/assets/index-BpoLgcEt.js +1 -0
- pythinker_code/web/static/assets/index-Cpy4G3uJ.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/index-DdIkp80K.js +5 -0
- pythinker_code/web/static/assets/infoDiagram-WHAUD3N6-BMPpeZSM.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-DAM7gngo.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-ChpBpV0k.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-C3Jp1gKO.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-BGHtL1N4.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-C_HW6koB.js +465 -0
- pythinker_code/web/static/assets/mermaid-mWjccvbQ.js +1 -0
- pythinker_code/web/static/assets/mermaid.core-CnT4VrPC.js +191 -0
- pythinker_code/web/static/assets/min-Dn5VRVmX.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-x8EwhfIt.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-DgxBKGz2.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-DSpe_dqk.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-8o9hozL-.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-BLOSUixH.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-6F2G8JTU.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-DP8xP0iJ.js +1 -0
- pythinker_code/web/static/assets/stateDiagram-v2-4FDKWEC3-1l6-EZNX.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-DMgruDfK.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-Bo9ZkrAK.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-GeF2johi.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 +432 -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 +1069 -0
- pythinker_code/wire/types.py +698 -0
- pythinker_code-2.0.0.dist-info/METADATA +660 -0
- pythinker_code-2.0.0.dist-info/RECORD +731 -0
- pythinker_code-2.0.0.dist-info/WHEEL +4 -0
- pythinker_code-2.0.0.dist-info/entry_points.txt +4 -0
- pythinker_code-2.0.0.dist-info/licenses/LICENSE +202 -0
- pythinker_code-2.0.0.dist-info/licenses/NOTICE +14 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""OpenTelemetry (OTLP HTTP) integration.
|
|
2
|
+
|
|
3
|
+
Sends traces, metrics, and logs to the pythinker edge OTel collector at
|
|
4
|
+
``otel.pythinker.com``. The collector forwards to SigNoz internally; clients
|
|
5
|
+
authenticate with a bearer token (embedded as a default, env-overridable).
|
|
6
|
+
|
|
7
|
+
Public API:
|
|
8
|
+
- ``init(version, ui_mode, ...)`` — wire up Tracer/Meter/LoggerProvider.
|
|
9
|
+
- ``get_tracer()`` — get a tracer for span creation.
|
|
10
|
+
- ``get_meter()`` — get the meter for instrument creation.
|
|
11
|
+
- ``emit_log(name, attributes, severity)`` — push a structured log event.
|
|
12
|
+
- ``shutdown()`` — flush exporters at process exit.
|
|
13
|
+
|
|
14
|
+
Uninitialized state is safe: ``get_tracer`` / ``get_meter`` return no-op
|
|
15
|
+
instances and ``emit_log`` becomes a no-op when init wasn't called or the kill
|
|
16
|
+
switch is on.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import logging
|
|
22
|
+
import platform
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
from opentelemetry import metrics, trace
|
|
26
|
+
from opentelemetry._logs import SeverityNumber, set_logger_provider
|
|
27
|
+
from opentelemetry._logs._internal import LogRecord
|
|
28
|
+
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
|
|
29
|
+
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
|
|
30
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
|
31
|
+
from opentelemetry.metrics import Meter
|
|
32
|
+
from opentelemetry.sdk._logs import LoggerProvider
|
|
33
|
+
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
|
|
34
|
+
from opentelemetry.sdk.metrics import MeterProvider
|
|
35
|
+
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
|
|
36
|
+
from opentelemetry.sdk.resources import Resource
|
|
37
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
38
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
39
|
+
from opentelemetry.trace import Tracer
|
|
40
|
+
|
|
41
|
+
from pythinker_code.telemetry.config import is_disabled, otel_endpoint, otel_ingest_token
|
|
42
|
+
|
|
43
|
+
_TRACER_NAME = "pythinker-code"
|
|
44
|
+
_initialized: bool = False
|
|
45
|
+
_tracer: Tracer | None = None
|
|
46
|
+
_meter: Meter | None = None
|
|
47
|
+
_meter_provider: MeterProvider | None = None
|
|
48
|
+
_logger_provider: LoggerProvider | None = None
|
|
49
|
+
_log = logging.getLogger(__name__)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _bearer_headers() -> dict[str, str]:
|
|
53
|
+
return {"Authorization": f"Bearer {otel_ingest_token()}"}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _resource(*, version: str, ui_mode: str, device_id: str | None) -> Resource:
|
|
57
|
+
"""Build the static resource attributes for every emitted span/log."""
|
|
58
|
+
attrs: dict[str, Any] = {
|
|
59
|
+
"service.name": "pythinker-code",
|
|
60
|
+
"service.version": version or "unknown",
|
|
61
|
+
"deployment.environment": "production",
|
|
62
|
+
"ui.mode": ui_mode or "shell",
|
|
63
|
+
"host.arch": platform.machine(),
|
|
64
|
+
"os.type": platform.system().lower(),
|
|
65
|
+
"process.runtime.name": "python",
|
|
66
|
+
"process.runtime.version": platform.python_version(),
|
|
67
|
+
}
|
|
68
|
+
if device_id:
|
|
69
|
+
# Treat device_id as a stable user-pseudonym (same role as Sentry user.id).
|
|
70
|
+
attrs["user.id"] = device_id
|
|
71
|
+
return Resource.create(attrs)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def init(
|
|
75
|
+
*,
|
|
76
|
+
version: str,
|
|
77
|
+
ui_mode: str,
|
|
78
|
+
device_id: str | None = None,
|
|
79
|
+
) -> bool:
|
|
80
|
+
"""Idempotently install the OTel SDK with OTLP HTTP exporters."""
|
|
81
|
+
global _initialized, _tracer, _meter, _meter_provider, _logger_provider
|
|
82
|
+
if _initialized:
|
|
83
|
+
return True
|
|
84
|
+
if is_disabled():
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
endpoint = otel_endpoint()
|
|
88
|
+
if not endpoint:
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
resource = _resource(version=version, ui_mode=ui_mode, device_id=device_id)
|
|
92
|
+
headers = _bearer_headers()
|
|
93
|
+
|
|
94
|
+
# --- Traces ---
|
|
95
|
+
span_exporter = OTLPSpanExporter(
|
|
96
|
+
endpoint=f"{endpoint}/v1/traces",
|
|
97
|
+
headers=headers,
|
|
98
|
+
timeout=10,
|
|
99
|
+
)
|
|
100
|
+
tracer_provider = TracerProvider(resource=resource)
|
|
101
|
+
tracer_provider.add_span_processor(BatchSpanProcessor(span_exporter))
|
|
102
|
+
trace.set_tracer_provider(tracer_provider)
|
|
103
|
+
_tracer = tracer_provider.get_tracer(_TRACER_NAME, version)
|
|
104
|
+
|
|
105
|
+
# --- Metrics ---
|
|
106
|
+
metric_exporter = OTLPMetricExporter(
|
|
107
|
+
endpoint=f"{endpoint}/v1/metrics",
|
|
108
|
+
headers=headers,
|
|
109
|
+
timeout=10,
|
|
110
|
+
)
|
|
111
|
+
# 30s export interval keeps backend volume low for a CLI that's typically
|
|
112
|
+
# invoked for short bursts; final flush at process exit catches the tail.
|
|
113
|
+
metric_reader = PeriodicExportingMetricReader(metric_exporter, export_interval_millis=30_000)
|
|
114
|
+
_meter_provider = MeterProvider(resource=resource, metric_readers=[metric_reader])
|
|
115
|
+
metrics.set_meter_provider(_meter_provider)
|
|
116
|
+
_meter = _meter_provider.get_meter(_TRACER_NAME, version)
|
|
117
|
+
|
|
118
|
+
# --- Logs ---
|
|
119
|
+
log_exporter = OTLPLogExporter(
|
|
120
|
+
endpoint=f"{endpoint}/v1/logs",
|
|
121
|
+
headers=headers,
|
|
122
|
+
timeout=10,
|
|
123
|
+
)
|
|
124
|
+
_logger_provider = LoggerProvider(resource=resource)
|
|
125
|
+
_logger_provider.add_log_record_processor(BatchLogRecordProcessor(log_exporter))
|
|
126
|
+
set_logger_provider(_logger_provider)
|
|
127
|
+
|
|
128
|
+
# Pre-create the metric instruments so call sites pay nothing on the hot
|
|
129
|
+
# path. Imported here to avoid a circular dependency at module load time.
|
|
130
|
+
from pythinker_code.telemetry import metrics as _m
|
|
131
|
+
|
|
132
|
+
_m.bind(_meter)
|
|
133
|
+
|
|
134
|
+
_initialized = True
|
|
135
|
+
_log.debug("OTel SDK initialized at %s", endpoint)
|
|
136
|
+
return True
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def get_tracer() -> Tracer:
|
|
140
|
+
"""Return the active tracer, or the global no-op tracer when uninitialized."""
|
|
141
|
+
if _tracer is not None:
|
|
142
|
+
return _tracer
|
|
143
|
+
return trace.get_tracer(_TRACER_NAME)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def get_meter() -> Meter:
|
|
147
|
+
"""Return the active meter, or the global no-op meter when uninitialized."""
|
|
148
|
+
if _meter is not None:
|
|
149
|
+
return _meter
|
|
150
|
+
return metrics.get_meter(_TRACER_NAME)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def start_span(name: str, attributes: dict[str, Any] | None = None) -> Any:
|
|
154
|
+
"""Convenience: ``with start_span("pythinker.turn", {...}) as span:``.
|
|
155
|
+
|
|
156
|
+
When OTel is uninitialized this returns a no-op span (the global tracer's
|
|
157
|
+
no-op behaviour) — call sites stay clean and pay nothing in the disabled
|
|
158
|
+
case. The span is the *current* span inside the with block, so child spans
|
|
159
|
+
automatically nest.
|
|
160
|
+
"""
|
|
161
|
+
return get_tracer().start_as_current_span(name, attributes=attributes or {})
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# ---------------------------------------------------------------------------
|
|
165
|
+
# Log emission
|
|
166
|
+
# ---------------------------------------------------------------------------
|
|
167
|
+
|
|
168
|
+
_SEVERITY_BY_NAME = {
|
|
169
|
+
"debug": (SeverityNumber.DEBUG, "DEBUG"),
|
|
170
|
+
"info": (SeverityNumber.INFO, "INFO"),
|
|
171
|
+
"warn": (SeverityNumber.WARN, "WARN"),
|
|
172
|
+
"warning": (SeverityNumber.WARN, "WARN"),
|
|
173
|
+
"error": (SeverityNumber.ERROR, "ERROR"),
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def emit_log(
|
|
178
|
+
*,
|
|
179
|
+
name: str,
|
|
180
|
+
attributes: dict[str, Any] | None = None,
|
|
181
|
+
severity: str = "info",
|
|
182
|
+
timestamp_ns: int | None = None,
|
|
183
|
+
) -> None:
|
|
184
|
+
"""Emit a structured OTel LogRecord. Used as the OTLP backend for ``track()``.
|
|
185
|
+
|
|
186
|
+
The ``name`` becomes the log body; properties + context become attributes.
|
|
187
|
+
Safe to call before or after init: pre-init calls are dropped silently.
|
|
188
|
+
"""
|
|
189
|
+
if not _initialized or _logger_provider is None:
|
|
190
|
+
return
|
|
191
|
+
try:
|
|
192
|
+
sev_number, sev_text = _SEVERITY_BY_NAME.get(
|
|
193
|
+
severity.lower(), (SeverityNumber.INFO, "INFO")
|
|
194
|
+
)
|
|
195
|
+
record = LogRecord(
|
|
196
|
+
timestamp=timestamp_ns,
|
|
197
|
+
observed_timestamp=timestamp_ns,
|
|
198
|
+
severity_number=sev_number,
|
|
199
|
+
severity_text=sev_text,
|
|
200
|
+
body=name,
|
|
201
|
+
attributes=dict(attributes or {}),
|
|
202
|
+
)
|
|
203
|
+
# Use the SDK's logger directly so we don't need to bridge through the
|
|
204
|
+
# stdlib logging module (which has its own handlers we don't want to
|
|
205
|
+
# collide with).
|
|
206
|
+
otel_logger = _logger_provider.get_logger(_TRACER_NAME)
|
|
207
|
+
otel_logger.emit(record)
|
|
208
|
+
except Exception:
|
|
209
|
+
# Never propagate.
|
|
210
|
+
pass
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
# ---------------------------------------------------------------------------
|
|
214
|
+
# Shutdown
|
|
215
|
+
# ---------------------------------------------------------------------------
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def shutdown(timeout_millis: int = 2000) -> None:
|
|
219
|
+
"""Flush all three providers. Called at process exit."""
|
|
220
|
+
if not _initialized:
|
|
221
|
+
return
|
|
222
|
+
try:
|
|
223
|
+
tp = trace.get_tracer_provider()
|
|
224
|
+
# The global provider may be the no-op when init failed; guard with
|
|
225
|
+
# hasattr instead of an isinstance check that pulls in private SDK
|
|
226
|
+
# types.
|
|
227
|
+
if hasattr(tp, "shutdown"):
|
|
228
|
+
tp.shutdown() # type: ignore[attr-defined]
|
|
229
|
+
except Exception:
|
|
230
|
+
pass
|
|
231
|
+
try:
|
|
232
|
+
if _meter_provider is not None:
|
|
233
|
+
_meter_provider.shutdown()
|
|
234
|
+
except Exception:
|
|
235
|
+
pass
|
|
236
|
+
try:
|
|
237
|
+
if _logger_provider is not None:
|
|
238
|
+
_logger_provider.shutdown()
|
|
239
|
+
except Exception:
|
|
240
|
+
pass
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Sentry / Bugsink integration.
|
|
2
|
+
|
|
3
|
+
Initializes ``sentry_sdk`` so unhandled exceptions, asyncio task failures, and
|
|
4
|
+
explicit ``capture_exception`` calls flow to Bugsink at ``errors.pythinker.com``.
|
|
5
|
+
The integration is additive: the existing ``crash.py`` excepthook still emits a
|
|
6
|
+
privacy-respecting ``crash`` event for in-process counting.
|
|
7
|
+
|
|
8
|
+
Privacy posture:
|
|
9
|
+
- ``send_default_pii=False`` — Sentry will not auto-capture user info from
|
|
10
|
+
request frames or local variables it considers personal.
|
|
11
|
+
- ``before_send`` strips file paths to package-relative form so site-packages
|
|
12
|
+
layouts don't reveal the user's home directory.
|
|
13
|
+
- Loop integrations are explicitly enabled; everything else inherits Sentry's
|
|
14
|
+
safe defaults.
|
|
15
|
+
|
|
16
|
+
Opt out: ``PYTHINKER_DISABLE_TELEMETRY=1``.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import contextlib
|
|
22
|
+
import os
|
|
23
|
+
import re
|
|
24
|
+
from typing import Any, cast
|
|
25
|
+
|
|
26
|
+
import sentry_sdk
|
|
27
|
+
from sentry_sdk.integrations.asyncio import AsyncioIntegration
|
|
28
|
+
from sentry_sdk.integrations.dedupe import DedupeIntegration
|
|
29
|
+
from sentry_sdk.integrations.excepthook import ExcepthookIntegration
|
|
30
|
+
from sentry_sdk.types import Event, Hint
|
|
31
|
+
|
|
32
|
+
from pythinker_code.telemetry.config import is_disabled, sentry_dsn
|
|
33
|
+
|
|
34
|
+
_initialized: bool = False
|
|
35
|
+
|
|
36
|
+
# File-path scrubbing: collapse anything before "site-packages/" or
|
|
37
|
+
# "pythinker_code/" to "<env>" so Sentry stack frames don't expose home dirs.
|
|
38
|
+
_PATH_SCRUB = re.compile(
|
|
39
|
+
r"^(.*?)(site-packages|pythinker_code|src/pythinker_code)/",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _scrub_path(path: str) -> str:
|
|
44
|
+
return _PATH_SCRUB.sub(r"<env>/\2/", path)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _before_send(event: Event, hint: Hint) -> Event | None:
|
|
48
|
+
"""Sentry hook applied to every outgoing event.
|
|
49
|
+
|
|
50
|
+
Drops absolute file paths (replaces the prefix above ``site-packages/`` or
|
|
51
|
+
``pythinker_code/`` with ``<env>``) and removes a couple of fields known to
|
|
52
|
+
leak local context.
|
|
53
|
+
"""
|
|
54
|
+
# Strip server name (usually the user's hostname).
|
|
55
|
+
event.pop("server_name", None)
|
|
56
|
+
|
|
57
|
+
exception_data = event.get("exception")
|
|
58
|
+
if exception_data is None:
|
|
59
|
+
return event
|
|
60
|
+
values = exception_data.get("values")
|
|
61
|
+
if not isinstance(values, list):
|
|
62
|
+
return event
|
|
63
|
+
|
|
64
|
+
for exception_raw in cast(list[Any], values):
|
|
65
|
+
if not isinstance(exception_raw, dict):
|
|
66
|
+
continue
|
|
67
|
+
exception = cast(dict[str, Any], exception_raw)
|
|
68
|
+
stacktrace_raw = exception.get("stacktrace")
|
|
69
|
+
if not isinstance(stacktrace_raw, dict):
|
|
70
|
+
continue
|
|
71
|
+
stacktrace = cast(dict[str, Any], stacktrace_raw)
|
|
72
|
+
frames = stacktrace.get("frames")
|
|
73
|
+
if not isinstance(frames, list):
|
|
74
|
+
continue
|
|
75
|
+
for frame_raw in cast(list[Any], frames):
|
|
76
|
+
if not isinstance(frame_raw, dict):
|
|
77
|
+
continue
|
|
78
|
+
frame = cast(dict[str, Any], frame_raw)
|
|
79
|
+
abs_path = frame.get("abs_path")
|
|
80
|
+
if isinstance(abs_path, str):
|
|
81
|
+
frame["abs_path"] = _scrub_path(abs_path)
|
|
82
|
+
filename = frame.get("filename")
|
|
83
|
+
if isinstance(filename, str):
|
|
84
|
+
frame["filename"] = _scrub_path(filename)
|
|
85
|
+
return event
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def init(
|
|
89
|
+
*,
|
|
90
|
+
version: str,
|
|
91
|
+
environment: str | None = None,
|
|
92
|
+
device_id: str | None = None,
|
|
93
|
+
extra_tags: dict[str, str] | None = None,
|
|
94
|
+
) -> bool:
|
|
95
|
+
"""Initialize ``sentry_sdk`` once per process. Returns True if active."""
|
|
96
|
+
global _initialized
|
|
97
|
+
if _initialized:
|
|
98
|
+
return True
|
|
99
|
+
if is_disabled():
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
dsn = sentry_dsn()
|
|
103
|
+
if not dsn:
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
env = environment or os.environ.get("PYTHINKER_ENV") or "production"
|
|
107
|
+
|
|
108
|
+
sentry_sdk.init(
|
|
109
|
+
dsn=dsn,
|
|
110
|
+
release=f"pythinker-code@{version}" if version else None,
|
|
111
|
+
environment=env,
|
|
112
|
+
# Dev-mode defaults: capture every error, no traces (OTel handles
|
|
113
|
+
# those). Tune later when distributing to users.
|
|
114
|
+
traces_sample_rate=0.0,
|
|
115
|
+
profiles_sample_rate=0.0,
|
|
116
|
+
send_default_pii=False,
|
|
117
|
+
attach_stacktrace=True,
|
|
118
|
+
max_breadcrumbs=50,
|
|
119
|
+
# Only the integrations that catch unhandled errors. Skip stdlib
|
|
120
|
+
# integrations (logging, atexit) so we don't double-emit alongside
|
|
121
|
+
# OTel logs.
|
|
122
|
+
default_integrations=False,
|
|
123
|
+
integrations=[
|
|
124
|
+
ExcepthookIntegration(always_run=False),
|
|
125
|
+
AsyncioIntegration(),
|
|
126
|
+
DedupeIntegration(),
|
|
127
|
+
],
|
|
128
|
+
before_send=_before_send,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
if device_id:
|
|
132
|
+
sentry_sdk.set_user({"id": device_id})
|
|
133
|
+
if extra_tags:
|
|
134
|
+
for k, v in extra_tags.items():
|
|
135
|
+
sentry_sdk.set_tag(k, v)
|
|
136
|
+
|
|
137
|
+
_initialized = True
|
|
138
|
+
return True
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def capture_exception(exc: BaseException) -> None:
|
|
142
|
+
"""Forward an exception to Sentry/Bugsink. Safe to call when uninitialized
|
|
143
|
+
(becomes a no-op). Telemetry must never raise into the host program."""
|
|
144
|
+
if not _initialized:
|
|
145
|
+
return
|
|
146
|
+
with contextlib.suppress(Exception):
|
|
147
|
+
sentry_sdk.capture_exception(exc)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def add_breadcrumb(
|
|
151
|
+
*, category: str, message: str, level: str = "info", data: dict[str, Any] | None = None
|
|
152
|
+
) -> None:
|
|
153
|
+
"""Append a breadcrumb to the Sentry scope (visible in the next captured event)."""
|
|
154
|
+
if not _initialized:
|
|
155
|
+
return
|
|
156
|
+
with contextlib.suppress(Exception):
|
|
157
|
+
sentry_sdk.add_breadcrumb(category=category, message=message, level=level, data=data or {})
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def flush(timeout_seconds: float = 2.0) -> None:
|
|
161
|
+
"""Block until pending events are sent or the timeout expires."""
|
|
162
|
+
if not _initialized:
|
|
163
|
+
return
|
|
164
|
+
with contextlib.suppress(Exception):
|
|
165
|
+
client = sentry_sdk.get_client()
|
|
166
|
+
if client:
|
|
167
|
+
client.flush(timeout=timeout_seconds)
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""EventSink: opt-out check, context enrichment, buffer management, timed flush.
|
|
2
|
+
|
|
3
|
+
Forwards every accepted event to OpenTelemetry as a structured log record.
|
|
4
|
+
The earlier custom-HTTP transport that posted to ``telemetry-logs.pythinker.com``
|
|
5
|
+
was retired in the SigNoz migration — the OTel exporter inside ``otel.py`` now
|
|
6
|
+
handles batching, retries, and disk-spool semantics on its own.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
import locale
|
|
13
|
+
import os
|
|
14
|
+
import platform
|
|
15
|
+
import threading
|
|
16
|
+
from typing import Any, cast
|
|
17
|
+
|
|
18
|
+
from pythinker_code.utils.logging import logger
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _assert_primitive(scope: str, key: str, value: Any) -> None:
|
|
22
|
+
"""Telemetry attribute values must be primitives. Catches accidental
|
|
23
|
+
nested dicts/lists before they reach the OTel SDK serializer."""
|
|
24
|
+
if value is None or isinstance(value, (bool, int, float, str)):
|
|
25
|
+
return
|
|
26
|
+
raise TypeError(f"telemetry {scope}.{key} must be primitive, got {type(value).__name__}")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _flatten_event(event: dict[str, Any]) -> dict[str, Any]:
|
|
30
|
+
"""Expand ``properties``/``context`` sub-dicts into flat ``property.*`` /
|
|
31
|
+
``context.*`` keys for OTel attributes. Top-level fields pass through.
|
|
32
|
+
|
|
33
|
+
Raises ``TypeError`` on nested values inside properties/context.
|
|
34
|
+
"""
|
|
35
|
+
out: dict[str, Any] = {}
|
|
36
|
+
for key, value in event.items():
|
|
37
|
+
if key == "properties":
|
|
38
|
+
properties = cast(dict[str, Any], value) if isinstance(value, dict) else {}
|
|
39
|
+
for pk, pv in properties.items():
|
|
40
|
+
_assert_primitive("property", pk, pv)
|
|
41
|
+
out[f"property.{pk}"] = pv
|
|
42
|
+
elif key == "context":
|
|
43
|
+
context = cast(dict[str, Any], value) if isinstance(value, dict) else {}
|
|
44
|
+
for ck, cv in context.items():
|
|
45
|
+
_assert_primitive("context", ck, cv)
|
|
46
|
+
out[f"context.{ck}"] = cv
|
|
47
|
+
else:
|
|
48
|
+
out[key] = value
|
|
49
|
+
return out
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class EventSink:
|
|
53
|
+
"""Buffers telemetry events and flushes them in batches to OTel logs."""
|
|
54
|
+
|
|
55
|
+
FLUSH_INTERVAL_S = 30.0
|
|
56
|
+
FLUSH_THRESHOLD = 50
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
*,
|
|
61
|
+
version: str = "",
|
|
62
|
+
model: str = "",
|
|
63
|
+
ui_mode: str = "shell",
|
|
64
|
+
) -> None:
|
|
65
|
+
self._buffer: list[dict[str, Any]] = []
|
|
66
|
+
self._lock = threading.Lock()
|
|
67
|
+
self._flush_task: asyncio.Task[None] | None = None
|
|
68
|
+
# Static context enrichment
|
|
69
|
+
self._context: dict[str, Any] = {
|
|
70
|
+
"version": version,
|
|
71
|
+
"runtime": "python",
|
|
72
|
+
"platform": platform.system().lower(),
|
|
73
|
+
"arch": platform.machine(),
|
|
74
|
+
"python_version": platform.python_version(),
|
|
75
|
+
"os_version": platform.release(),
|
|
76
|
+
"ci": bool(os.environ.get("CI")),
|
|
77
|
+
"locale": locale.getlocale()[0] or "",
|
|
78
|
+
"terminal": os.environ.get("TERM_PROGRAM", ""),
|
|
79
|
+
}
|
|
80
|
+
self._model = model
|
|
81
|
+
self._ui_mode = ui_mode
|
|
82
|
+
|
|
83
|
+
def accept(self, event: dict[str, Any]) -> None:
|
|
84
|
+
"""Accept an event into the buffer. Non-blocking, thread-safe."""
|
|
85
|
+
# Enrich with static context (copy to avoid mutating the caller's dict)
|
|
86
|
+
ctx = {**self._context, "ui_mode": self._ui_mode}
|
|
87
|
+
if self._model:
|
|
88
|
+
ctx["model"] = self._model
|
|
89
|
+
enriched = {**event, "context": ctx}
|
|
90
|
+
|
|
91
|
+
with self._lock:
|
|
92
|
+
self._buffer.append(enriched)
|
|
93
|
+
should_flush = len(self._buffer) >= self.FLUSH_THRESHOLD
|
|
94
|
+
|
|
95
|
+
if should_flush:
|
|
96
|
+
self._schedule_async_flush()
|
|
97
|
+
|
|
98
|
+
def start_periodic_flush(self, loop: asyncio.AbstractEventLoop | None = None) -> None:
|
|
99
|
+
"""Start a background task that flushes every FLUSH_INTERVAL_S seconds."""
|
|
100
|
+
if self._flush_task is not None:
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
async def _periodic() -> None:
|
|
104
|
+
try:
|
|
105
|
+
while True:
|
|
106
|
+
await asyncio.sleep(self.FLUSH_INTERVAL_S)
|
|
107
|
+
await self._flush_async()
|
|
108
|
+
except asyncio.CancelledError:
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
if loop is None:
|
|
112
|
+
loop = asyncio.get_running_loop()
|
|
113
|
+
self._flush_task = loop.create_task(_periodic())
|
|
114
|
+
|
|
115
|
+
async def retry_disk_events(self) -> None:
|
|
116
|
+
"""Compatibility shim — disk retries now happen inside the OTel
|
|
117
|
+
exporter. No-op kept so existing callers don't break."""
|
|
118
|
+
|
|
119
|
+
def clear_buffer(self) -> None:
|
|
120
|
+
"""Discard all buffered events without sending them."""
|
|
121
|
+
with self._lock:
|
|
122
|
+
self._buffer.clear()
|
|
123
|
+
|
|
124
|
+
def stop_periodic_flush(self) -> None:
|
|
125
|
+
"""Cancel the periodic flush task."""
|
|
126
|
+
if self._flush_task is not None:
|
|
127
|
+
self._flush_task.cancel()
|
|
128
|
+
self._flush_task = None
|
|
129
|
+
|
|
130
|
+
async def flush(self) -> None:
|
|
131
|
+
"""Async flush: send all buffered events."""
|
|
132
|
+
await self._flush_async()
|
|
133
|
+
|
|
134
|
+
def flush_sync(self) -> None:
|
|
135
|
+
"""Synchronous flush for atexit / signal handlers.
|
|
136
|
+
|
|
137
|
+
Drops the in-process buffer into the OTel pipeline. Network I/O is
|
|
138
|
+
scheduled by the BatchLogRecordProcessor; the OTel SDK's own shutdown
|
|
139
|
+
(called from ``otel.shutdown``) waits for that batch to drain.
|
|
140
|
+
"""
|
|
141
|
+
with self._lock:
|
|
142
|
+
if not self._buffer:
|
|
143
|
+
return
|
|
144
|
+
events = list(self._buffer)
|
|
145
|
+
self._buffer.clear()
|
|
146
|
+
self._emit_to_otel(events)
|
|
147
|
+
|
|
148
|
+
async def _flush_async(self) -> None:
|
|
149
|
+
"""Take all buffered events and forward them to OTel logs."""
|
|
150
|
+
with self._lock:
|
|
151
|
+
if not self._buffer:
|
|
152
|
+
return
|
|
153
|
+
events = list(self._buffer)
|
|
154
|
+
self._buffer.clear()
|
|
155
|
+
self._emit_to_otel(events)
|
|
156
|
+
|
|
157
|
+
def _emit_to_otel(self, events: list[dict[str, Any]]) -> None:
|
|
158
|
+
if not events:
|
|
159
|
+
return
|
|
160
|
+
try:
|
|
161
|
+
from pythinker_code.telemetry import otel as _otel
|
|
162
|
+
|
|
163
|
+
for event in events:
|
|
164
|
+
ts = event.get("timestamp")
|
|
165
|
+
ts_ns = int(ts * 1_000_000_000) if isinstance(ts, (int, float)) else None
|
|
166
|
+
try:
|
|
167
|
+
attrs = _flatten_event(event)
|
|
168
|
+
except TypeError as exc:
|
|
169
|
+
# Schema violation — drop, never retry.
|
|
170
|
+
logger.debug("Telemetry event dropped (non-primitive attr): {err}", err=exc)
|
|
171
|
+
continue
|
|
172
|
+
attrs.pop("event", None)
|
|
173
|
+
attrs.pop("timestamp", None)
|
|
174
|
+
_otel.emit_log(
|
|
175
|
+
name=str(event.get("event") or "event"),
|
|
176
|
+
attributes=attrs,
|
|
177
|
+
timestamp_ns=ts_ns,
|
|
178
|
+
)
|
|
179
|
+
except Exception:
|
|
180
|
+
logger.debug("OTel flush failed; events dropped")
|
|
181
|
+
|
|
182
|
+
def _schedule_async_flush(self) -> None:
|
|
183
|
+
"""Schedule an async flush from any thread."""
|
|
184
|
+
try:
|
|
185
|
+
loop = asyncio.get_running_loop()
|
|
186
|
+
loop.create_task(self._flush_async())
|
|
187
|
+
except RuntimeError:
|
|
188
|
+
# No running event loop — will be flushed by periodic task or on exit
|
|
189
|
+
pass
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Pythinker CLI Tools
|
|
2
|
+
|
|
3
|
+
## Guidelines
|
|
4
|
+
|
|
5
|
+
- Tools should not refer to types in `pythinker_code/wire/` unless they are explicitly implementing a UI / runtime bridge. When importing things like `ToolReturnValue` or `DisplayBlock`, prefer `pythinker_core.tooling`.
|
|
6
|
+
- Current bridge exceptions include `ask_user`, `plan`, and `file` media/display helpers; keep new tool logic in `pythinker_core.tooling` unless it must emit wire-facing requests or display blocks.
|