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,1326 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from importlib import import_module
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import TYPE_CHECKING, Annotated, Any, Literal
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from pythinker_code.session import Session
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Reload(Exception):
|
|
15
|
+
"""Reload configuration."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, session_id: str | None = None, prefill_text: str | None = None):
|
|
18
|
+
super().__init__("reload")
|
|
19
|
+
self.session_id = session_id
|
|
20
|
+
self.prefill_text = prefill_text
|
|
21
|
+
self.source_session: Session | None = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SwitchToWeb(Exception):
|
|
25
|
+
"""Switch to web interface."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, session_id: str | None = None):
|
|
28
|
+
super().__init__("switch_to_web")
|
|
29
|
+
self.session_id = session_id
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SwitchToVis(Exception):
|
|
33
|
+
"""Switch to vis (tracing visualizer) interface."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, session_id: str | None = None):
|
|
36
|
+
super().__init__("switch_to_vis")
|
|
37
|
+
self.session_id = session_id
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def load_config() -> Any:
|
|
41
|
+
from pythinker_code.config import load_config as impl
|
|
42
|
+
|
|
43
|
+
return impl()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def login_anthropic_api_key(*args: Any, **kwargs: Any) -> Any:
|
|
47
|
+
from pythinker_code.auth.anthropic_direct import login_anthropic_api_key as impl
|
|
48
|
+
|
|
49
|
+
return impl(*args, **kwargs)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def logout_anthropic(*args: Any, **kwargs: Any) -> Any:
|
|
53
|
+
from pythinker_code.auth.anthropic_direct import logout_anthropic as impl
|
|
54
|
+
|
|
55
|
+
return impl(*args, **kwargs)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def login_deepseek_api_key(*args: Any, **kwargs: Any) -> Any:
|
|
59
|
+
from pythinker_code.auth.deepseek import login_deepseek_api_key as impl
|
|
60
|
+
|
|
61
|
+
return impl(*args, **kwargs)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def logout_deepseek(*args: Any, **kwargs: Any) -> Any:
|
|
65
|
+
from pythinker_code.auth.deepseek import logout_deepseek as impl
|
|
66
|
+
|
|
67
|
+
return impl(*args, **kwargs)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def login_lm_studio(*args: Any, **kwargs: Any) -> Any:
|
|
71
|
+
from pythinker_code.auth.lm_studio import login_lm_studio as impl
|
|
72
|
+
|
|
73
|
+
return impl(*args, **kwargs)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def logout_lm_studio(*args: Any, **kwargs: Any) -> Any:
|
|
77
|
+
from pythinker_code.auth.lm_studio import logout_lm_studio as impl
|
|
78
|
+
|
|
79
|
+
return impl(*args, **kwargs)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def login_ollama(*args: Any, **kwargs: Any) -> Any:
|
|
83
|
+
from pythinker_code.auth.ollama import login_ollama as impl
|
|
84
|
+
|
|
85
|
+
return impl(*args, **kwargs)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def logout_ollama(*args: Any, **kwargs: Any) -> Any:
|
|
89
|
+
from pythinker_code.auth.ollama import logout_ollama as impl
|
|
90
|
+
|
|
91
|
+
return impl(*args, **kwargs)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def login_minimax_api_key(*args: Any, **kwargs: Any) -> Any:
|
|
95
|
+
from pythinker_code.auth.minimax import login_minimax_api_key as impl
|
|
96
|
+
|
|
97
|
+
return impl(*args, **kwargs)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def logout_minimax(*args: Any, **kwargs: Any) -> Any:
|
|
101
|
+
from pythinker_code.auth.minimax import logout_minimax as impl
|
|
102
|
+
|
|
103
|
+
return impl(*args, **kwargs)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def login_openai_api_key(*args: Any, **kwargs: Any) -> Any:
|
|
107
|
+
from pythinker_code.auth.openai import login_openai_api_key as impl
|
|
108
|
+
|
|
109
|
+
return impl(*args, **kwargs)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def login_openai_browser(*args: Any, **kwargs: Any) -> Any:
|
|
113
|
+
from pythinker_code.auth.openai import login_openai_browser as impl
|
|
114
|
+
|
|
115
|
+
return impl(*args, **kwargs)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def login_openai_headless(*args: Any, **kwargs: Any) -> Any:
|
|
119
|
+
from pythinker_code.auth.openai import login_openai_headless as impl
|
|
120
|
+
|
|
121
|
+
return impl(*args, **kwargs)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def logout_openai(*args: Any, **kwargs: Any) -> Any:
|
|
125
|
+
from pythinker_code.auth.openai import logout_openai as impl
|
|
126
|
+
|
|
127
|
+
return impl(*args, **kwargs)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def login_opencode_go_api_key(*args: Any, **kwargs: Any) -> Any:
|
|
131
|
+
from pythinker_code.auth.opencode_go import login_opencode_go_api_key as impl
|
|
132
|
+
|
|
133
|
+
return impl(*args, **kwargs)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def logout_opencode_go(*args: Any, **kwargs: Any) -> Any:
|
|
137
|
+
from pythinker_code.auth.opencode_go import logout_opencode_go as impl
|
|
138
|
+
|
|
139
|
+
return impl(*args, **kwargs)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def login_openrouter_api_key(*args: Any, **kwargs: Any) -> Any:
|
|
143
|
+
from pythinker_code.auth.openrouter import login_openrouter_api_key as impl
|
|
144
|
+
|
|
145
|
+
return impl(*args, **kwargs)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def logout_openrouter(*args: Any, **kwargs: Any) -> Any:
|
|
149
|
+
from pythinker_code.auth.openrouter import logout_openrouter as impl
|
|
150
|
+
|
|
151
|
+
return impl(*args, **kwargs)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
LazySubcommandGroup = import_module(f"{__name__}._lazy_group").LazySubcommandGroup
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
cli = typer.Typer(
|
|
158
|
+
cls=LazySubcommandGroup,
|
|
159
|
+
epilog="""\b\
|
|
160
|
+
Documentation: https://mohamed-elkholy95.github.io/Pythinker-Code/\n
|
|
161
|
+
LLM friendly version: https://mohamed-elkholy95.github.io/Pythinker-Code/llms.txt""",
|
|
162
|
+
add_completion=False,
|
|
163
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
164
|
+
help="Pythinker, your next CLI agent.",
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
UIMode = Literal["shell", "print", "acp", "wire"]
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class ExitCode:
|
|
171
|
+
SUCCESS = 0
|
|
172
|
+
FAILURE = 1
|
|
173
|
+
RETRYABLE = 75 # EX_TEMPFAIL from sysexits.h
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
InputFormat = Literal["text", "stream-json"]
|
|
177
|
+
OutputFormat = Literal["text", "stream-json"]
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _strip_session_id_suffix(title: str, session_id: str) -> str:
|
|
181
|
+
"""Remove the trailing `` (session_id)`` suffix from a session title, if present."""
|
|
182
|
+
suffix = f" ({session_id})"
|
|
183
|
+
return title.rsplit(suffix, 1)[0] if title.endswith(suffix) else title
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _version_callback(value: bool) -> None:
|
|
187
|
+
if value:
|
|
188
|
+
from pythinker_code.constant import get_version
|
|
189
|
+
|
|
190
|
+
typer.echo(f"pythinker, version {get_version()}")
|
|
191
|
+
raise typer.Exit()
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@cli.callback(invoke_without_command=True)
|
|
195
|
+
def pythinker(
|
|
196
|
+
ctx: typer.Context,
|
|
197
|
+
# Meta
|
|
198
|
+
version: Annotated[
|
|
199
|
+
bool,
|
|
200
|
+
typer.Option(
|
|
201
|
+
"--version",
|
|
202
|
+
"-V",
|
|
203
|
+
help="Show version and exit.",
|
|
204
|
+
callback=_version_callback,
|
|
205
|
+
is_eager=True,
|
|
206
|
+
),
|
|
207
|
+
] = False,
|
|
208
|
+
verbose: Annotated[
|
|
209
|
+
bool,
|
|
210
|
+
typer.Option(
|
|
211
|
+
"--verbose",
|
|
212
|
+
help="Print verbose information. Default: no.",
|
|
213
|
+
),
|
|
214
|
+
] = False,
|
|
215
|
+
debug: Annotated[
|
|
216
|
+
bool,
|
|
217
|
+
typer.Option(
|
|
218
|
+
"--debug",
|
|
219
|
+
help="Log debug information. Default: no.",
|
|
220
|
+
),
|
|
221
|
+
] = False,
|
|
222
|
+
# Basic configuration
|
|
223
|
+
local_work_dir: Annotated[
|
|
224
|
+
Path | None,
|
|
225
|
+
typer.Option(
|
|
226
|
+
"--work-dir",
|
|
227
|
+
"-w",
|
|
228
|
+
exists=True,
|
|
229
|
+
file_okay=False,
|
|
230
|
+
dir_okay=True,
|
|
231
|
+
readable=True,
|
|
232
|
+
writable=True,
|
|
233
|
+
help="Working directory for the agent. Default: current directory.",
|
|
234
|
+
),
|
|
235
|
+
] = None,
|
|
236
|
+
local_add_dirs: Annotated[
|
|
237
|
+
list[Path] | None,
|
|
238
|
+
typer.Option(
|
|
239
|
+
"--add-dir",
|
|
240
|
+
exists=True,
|
|
241
|
+
file_okay=False,
|
|
242
|
+
dir_okay=True,
|
|
243
|
+
readable=True,
|
|
244
|
+
help=(
|
|
245
|
+
"Add an additional directory to the workspace scope. "
|
|
246
|
+
"Can be specified multiple times."
|
|
247
|
+
),
|
|
248
|
+
),
|
|
249
|
+
] = None,
|
|
250
|
+
session_id: Annotated[
|
|
251
|
+
str | None,
|
|
252
|
+
typer.Option(
|
|
253
|
+
"--session",
|
|
254
|
+
"--resume",
|
|
255
|
+
"-S",
|
|
256
|
+
"-r",
|
|
257
|
+
help=(
|
|
258
|
+
"Resume a session. "
|
|
259
|
+
"With ID: resume that session. "
|
|
260
|
+
"Without ID: interactively pick a session."
|
|
261
|
+
),
|
|
262
|
+
),
|
|
263
|
+
] = None,
|
|
264
|
+
continue_: Annotated[
|
|
265
|
+
bool,
|
|
266
|
+
typer.Option(
|
|
267
|
+
"--continue",
|
|
268
|
+
"-C",
|
|
269
|
+
help="Continue the previous session for the working directory. Default: no.",
|
|
270
|
+
),
|
|
271
|
+
] = False,
|
|
272
|
+
config_string: Annotated[
|
|
273
|
+
str | None,
|
|
274
|
+
typer.Option(
|
|
275
|
+
"--config",
|
|
276
|
+
help="Config TOML/JSON string to load. Default: none.",
|
|
277
|
+
),
|
|
278
|
+
] = None,
|
|
279
|
+
config_file: Annotated[
|
|
280
|
+
Path | None,
|
|
281
|
+
typer.Option(
|
|
282
|
+
"--config-file",
|
|
283
|
+
exists=True,
|
|
284
|
+
file_okay=True,
|
|
285
|
+
dir_okay=False,
|
|
286
|
+
readable=True,
|
|
287
|
+
help="Config TOML/JSON file to load. Default: ~/.pythinker/config.toml.",
|
|
288
|
+
),
|
|
289
|
+
] = None,
|
|
290
|
+
model_name: Annotated[
|
|
291
|
+
str | None,
|
|
292
|
+
typer.Option(
|
|
293
|
+
"--model",
|
|
294
|
+
"-m",
|
|
295
|
+
help="LLM model to use. Default: default model set in config file.",
|
|
296
|
+
),
|
|
297
|
+
] = None,
|
|
298
|
+
thinking: Annotated[
|
|
299
|
+
bool | None,
|
|
300
|
+
typer.Option(
|
|
301
|
+
"--thinking/--no-thinking",
|
|
302
|
+
help="Enable thinking mode. Default: default thinking mode set in config file.",
|
|
303
|
+
),
|
|
304
|
+
] = None,
|
|
305
|
+
no_telemetry: Annotated[
|
|
306
|
+
bool,
|
|
307
|
+
typer.Option(
|
|
308
|
+
"--no-telemetry",
|
|
309
|
+
help=(
|
|
310
|
+
"Disable all anonymous usage telemetry and error reporting "
|
|
311
|
+
"(equivalent to setting PYTHINKER_DISABLE_TELEMETRY=1). "
|
|
312
|
+
"Default: telemetry on."
|
|
313
|
+
),
|
|
314
|
+
),
|
|
315
|
+
] = False,
|
|
316
|
+
# Run mode
|
|
317
|
+
yolo: Annotated[
|
|
318
|
+
bool,
|
|
319
|
+
typer.Option(
|
|
320
|
+
"--yolo",
|
|
321
|
+
"--yes",
|
|
322
|
+
"-y",
|
|
323
|
+
"--auto-approve",
|
|
324
|
+
help="Automatically approve all actions. Default: no.",
|
|
325
|
+
),
|
|
326
|
+
] = False,
|
|
327
|
+
plan: Annotated[
|
|
328
|
+
bool,
|
|
329
|
+
typer.Option(
|
|
330
|
+
"--plan",
|
|
331
|
+
help="Start in plan mode. Default: no.",
|
|
332
|
+
),
|
|
333
|
+
] = False,
|
|
334
|
+
auto: Annotated[
|
|
335
|
+
bool,
|
|
336
|
+
typer.Option(
|
|
337
|
+
"--auto",
|
|
338
|
+
help=(
|
|
339
|
+
"Run in auto mode: no user is present, AskUserQuestion is auto-dismissed, "
|
|
340
|
+
"and tool calls are auto-approved. Use when running unattended "
|
|
341
|
+
"(scripts, CI, scheduled jobs). Default: no."
|
|
342
|
+
),
|
|
343
|
+
),
|
|
344
|
+
] = False,
|
|
345
|
+
prompt: Annotated[
|
|
346
|
+
str | None,
|
|
347
|
+
typer.Option(
|
|
348
|
+
"--prompt",
|
|
349
|
+
"-p",
|
|
350
|
+
"--command",
|
|
351
|
+
"-c",
|
|
352
|
+
help="User prompt to the agent. Default: prompt interactively.",
|
|
353
|
+
),
|
|
354
|
+
] = None,
|
|
355
|
+
print_mode: Annotated[
|
|
356
|
+
bool,
|
|
357
|
+
typer.Option(
|
|
358
|
+
"--print",
|
|
359
|
+
help=(
|
|
360
|
+
"Run in print mode (non-interactive). Print mode auto-dismisses "
|
|
361
|
+
"AskUserQuestion and auto-approves tool calls for this invocation."
|
|
362
|
+
),
|
|
363
|
+
),
|
|
364
|
+
] = False,
|
|
365
|
+
acp_mode: Annotated[
|
|
366
|
+
bool,
|
|
367
|
+
typer.Option(
|
|
368
|
+
"--acp",
|
|
369
|
+
help="(Deprecated, use `pythinker acp` instead) Run as ACP server.",
|
|
370
|
+
),
|
|
371
|
+
] = False,
|
|
372
|
+
wire_mode: Annotated[
|
|
373
|
+
bool,
|
|
374
|
+
typer.Option(
|
|
375
|
+
"--wire",
|
|
376
|
+
help="Run as Wire server (experimental).",
|
|
377
|
+
),
|
|
378
|
+
] = False,
|
|
379
|
+
input_format: Annotated[
|
|
380
|
+
InputFormat | None,
|
|
381
|
+
typer.Option(
|
|
382
|
+
"--input-format",
|
|
383
|
+
help=(
|
|
384
|
+
"Input format to use. Must be used with `--print` "
|
|
385
|
+
"and the input must be piped in via stdin. "
|
|
386
|
+
"Default: text."
|
|
387
|
+
),
|
|
388
|
+
),
|
|
389
|
+
] = None,
|
|
390
|
+
output_format: Annotated[
|
|
391
|
+
OutputFormat | None,
|
|
392
|
+
typer.Option(
|
|
393
|
+
"--output-format",
|
|
394
|
+
help="Output format to use. Must be used with `--print`. Default: text.",
|
|
395
|
+
),
|
|
396
|
+
] = None,
|
|
397
|
+
final_message_only: Annotated[
|
|
398
|
+
bool,
|
|
399
|
+
typer.Option(
|
|
400
|
+
"--final-message-only",
|
|
401
|
+
help="Only print the final assistant message (print UI).",
|
|
402
|
+
),
|
|
403
|
+
] = False,
|
|
404
|
+
quiet: Annotated[
|
|
405
|
+
bool,
|
|
406
|
+
typer.Option(
|
|
407
|
+
"--quiet",
|
|
408
|
+
help="Alias for `--print --output-format text --final-message-only`.",
|
|
409
|
+
),
|
|
410
|
+
] = False,
|
|
411
|
+
# Customization
|
|
412
|
+
agent: Annotated[
|
|
413
|
+
Literal["default", "okabe"] | None,
|
|
414
|
+
typer.Option(
|
|
415
|
+
"--agent",
|
|
416
|
+
help="Builtin agent specification to use. Default: builtin default agent.",
|
|
417
|
+
),
|
|
418
|
+
] = None,
|
|
419
|
+
agent_file: Annotated[
|
|
420
|
+
Path | None,
|
|
421
|
+
typer.Option(
|
|
422
|
+
"--agent-file",
|
|
423
|
+
exists=True,
|
|
424
|
+
file_okay=True,
|
|
425
|
+
dir_okay=False,
|
|
426
|
+
readable=True,
|
|
427
|
+
help="Custom agent specification file. Default: builtin default agent.",
|
|
428
|
+
),
|
|
429
|
+
] = None,
|
|
430
|
+
mcp_config_file: Annotated[
|
|
431
|
+
list[Path] | None,
|
|
432
|
+
typer.Option(
|
|
433
|
+
"--mcp-config-file",
|
|
434
|
+
exists=True,
|
|
435
|
+
file_okay=True,
|
|
436
|
+
dir_okay=False,
|
|
437
|
+
readable=True,
|
|
438
|
+
help=(
|
|
439
|
+
"MCP config file to load. Add this option multiple times to specify multiple MCP "
|
|
440
|
+
"configs. Default: none."
|
|
441
|
+
),
|
|
442
|
+
),
|
|
443
|
+
] = None,
|
|
444
|
+
mcp_config: Annotated[
|
|
445
|
+
list[str] | None,
|
|
446
|
+
typer.Option(
|
|
447
|
+
"--mcp-config",
|
|
448
|
+
help=(
|
|
449
|
+
"MCP config JSON to load. Add this option multiple times to specify multiple MCP "
|
|
450
|
+
"configs. Default: none."
|
|
451
|
+
),
|
|
452
|
+
),
|
|
453
|
+
] = None,
|
|
454
|
+
local_skills_dir: Annotated[
|
|
455
|
+
list[Path] | None,
|
|
456
|
+
typer.Option(
|
|
457
|
+
"--skills-dir",
|
|
458
|
+
exists=True,
|
|
459
|
+
file_okay=False,
|
|
460
|
+
dir_okay=True,
|
|
461
|
+
readable=True,
|
|
462
|
+
help="Custom skills directories (repeatable). Overrides default discovery.",
|
|
463
|
+
),
|
|
464
|
+
] = None,
|
|
465
|
+
# Loop control
|
|
466
|
+
max_steps_per_turn: Annotated[
|
|
467
|
+
int | None,
|
|
468
|
+
typer.Option(
|
|
469
|
+
"--max-steps-per-turn",
|
|
470
|
+
min=1,
|
|
471
|
+
help="Maximum number of steps in one turn. Default: from config.",
|
|
472
|
+
),
|
|
473
|
+
] = None,
|
|
474
|
+
max_retries_per_step: Annotated[
|
|
475
|
+
int | None,
|
|
476
|
+
typer.Option(
|
|
477
|
+
"--max-retries-per-step",
|
|
478
|
+
min=1,
|
|
479
|
+
help="Maximum number of retries in one step. Default: from config.",
|
|
480
|
+
),
|
|
481
|
+
] = None,
|
|
482
|
+
max_ralph_iterations: Annotated[
|
|
483
|
+
int | None,
|
|
484
|
+
typer.Option(
|
|
485
|
+
"--max-ralph-iterations",
|
|
486
|
+
min=-1,
|
|
487
|
+
help=(
|
|
488
|
+
"Extra iterations after the first turn in Ralph mode. Use -1 for unlimited. "
|
|
489
|
+
"Default: from config."
|
|
490
|
+
),
|
|
491
|
+
),
|
|
492
|
+
] = None,
|
|
493
|
+
):
|
|
494
|
+
"""Pythinker, your next CLI agent."""
|
|
495
|
+
import asyncio
|
|
496
|
+
import contextlib
|
|
497
|
+
import json
|
|
498
|
+
|
|
499
|
+
from pythinker_code.utils.proctitle import init_process_name
|
|
500
|
+
|
|
501
|
+
init_process_name("Pythinker")
|
|
502
|
+
|
|
503
|
+
if ctx.invoked_subcommand is not None:
|
|
504
|
+
return # skip rest if a subcommand is invoked
|
|
505
|
+
|
|
506
|
+
del version # handled in the callback
|
|
507
|
+
|
|
508
|
+
from pythinker_host.path import HostPath
|
|
509
|
+
|
|
510
|
+
from pythinker_code.agentspec import DEFAULT_AGENT_FILE, OKABE_AGENT_FILE
|
|
511
|
+
from pythinker_code.app import PythinkerCLI, enable_logging
|
|
512
|
+
from pythinker_code.config import Config, load_config_from_string
|
|
513
|
+
from pythinker_code.exception import ConfigError
|
|
514
|
+
from pythinker_code.hooks import events as hook_events
|
|
515
|
+
from pythinker_code.metadata import load_metadata, save_metadata
|
|
516
|
+
from pythinker_code.session import Session
|
|
517
|
+
from pythinker_code.ui.shell.startup import ShellStartupProgress
|
|
518
|
+
from pythinker_code.utils.logging import logger, open_original_stderr, redirect_stderr_to_logger
|
|
519
|
+
|
|
520
|
+
from .mcp import get_global_mcp_config_file
|
|
521
|
+
|
|
522
|
+
# Don't redirect stderr during argument parsing. Our stderr redirector
|
|
523
|
+
# replaces fd=2 with a pipe, which would swallow Click/Typer startup errors.
|
|
524
|
+
# Redirection is installed later, right before PythinkerCLI.create(), so that
|
|
525
|
+
# MCP server stderr noise is captured into logs from the start.
|
|
526
|
+
enable_logging(debug, redirect_stderr=False)
|
|
527
|
+
|
|
528
|
+
def _emit_fatal_error(message: str) -> None:
|
|
529
|
+
# Prefer writing to the original stderr fd even if we later redirect fd=2.
|
|
530
|
+
# This ensures fatal errors are visible to the user.
|
|
531
|
+
with open_original_stderr() as stream:
|
|
532
|
+
if stream is not None:
|
|
533
|
+
stream.write((message.rstrip() + "\n").encode("utf-8", errors="replace"))
|
|
534
|
+
stream.flush()
|
|
535
|
+
return
|
|
536
|
+
typer.echo(message, err=True)
|
|
537
|
+
|
|
538
|
+
# session_id states:
|
|
539
|
+
# None → not provided (new session)
|
|
540
|
+
# "" → --session/--resume without value (picker mode)
|
|
541
|
+
# "ID" → --session ID (resume specific session)
|
|
542
|
+
_picker_mode = session_id == ""
|
|
543
|
+
if session_id is not None:
|
|
544
|
+
session_id = session_id.strip() or None # treat whitespace-only as picker mode
|
|
545
|
+
if session_id is None:
|
|
546
|
+
_picker_mode = True
|
|
547
|
+
|
|
548
|
+
if quiet:
|
|
549
|
+
if acp_mode or wire_mode:
|
|
550
|
+
raise typer.BadParameter(
|
|
551
|
+
"Quiet mode cannot be combined with ACP or Wire UI",
|
|
552
|
+
param_hint="--quiet",
|
|
553
|
+
)
|
|
554
|
+
if output_format not in (None, "text"):
|
|
555
|
+
raise typer.BadParameter(
|
|
556
|
+
"Quiet mode implies `--output-format text`",
|
|
557
|
+
param_hint="--quiet",
|
|
558
|
+
)
|
|
559
|
+
print_mode = True
|
|
560
|
+
output_format = "text"
|
|
561
|
+
final_message_only = True
|
|
562
|
+
|
|
563
|
+
conflict_option_sets = [
|
|
564
|
+
{
|
|
565
|
+
"--print": print_mode,
|
|
566
|
+
"--acp": acp_mode,
|
|
567
|
+
"--wire": wire_mode,
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
"--agent": agent is not None,
|
|
571
|
+
"--agent-file": agent_file is not None,
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
"--continue": continue_,
|
|
575
|
+
"--session": session_id is not None or _picker_mode,
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
"--config": config_string is not None,
|
|
579
|
+
"--config-file": config_file is not None,
|
|
580
|
+
},
|
|
581
|
+
]
|
|
582
|
+
for option_set in conflict_option_sets:
|
|
583
|
+
active_options = [flag for flag, active in option_set.items() if active]
|
|
584
|
+
if len(active_options) > 1:
|
|
585
|
+
raise typer.BadParameter(
|
|
586
|
+
f"Cannot combine {', '.join(active_options)}.",
|
|
587
|
+
param_hint=active_options[0],
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
if agent is not None:
|
|
591
|
+
match agent:
|
|
592
|
+
case "default":
|
|
593
|
+
agent_file = DEFAULT_AGENT_FILE
|
|
594
|
+
case "okabe":
|
|
595
|
+
agent_file = OKABE_AGENT_FILE
|
|
596
|
+
|
|
597
|
+
ui: UIMode = "shell"
|
|
598
|
+
if print_mode:
|
|
599
|
+
ui = "print"
|
|
600
|
+
elif acp_mode:
|
|
601
|
+
ui = "acp"
|
|
602
|
+
elif wire_mode:
|
|
603
|
+
ui = "wire"
|
|
604
|
+
|
|
605
|
+
if prompt is not None:
|
|
606
|
+
prompt = prompt.strip()
|
|
607
|
+
if not prompt:
|
|
608
|
+
raise typer.BadParameter("Prompt cannot be empty", param_hint="--prompt")
|
|
609
|
+
|
|
610
|
+
if input_format is not None and ui != "print":
|
|
611
|
+
raise typer.BadParameter(
|
|
612
|
+
"Input format is only supported for print UI",
|
|
613
|
+
param_hint="--input-format",
|
|
614
|
+
)
|
|
615
|
+
if output_format is not None and ui != "print":
|
|
616
|
+
raise typer.BadParameter(
|
|
617
|
+
"Output format is only supported for print UI",
|
|
618
|
+
param_hint="--output-format",
|
|
619
|
+
)
|
|
620
|
+
if final_message_only and ui != "print":
|
|
621
|
+
raise typer.BadParameter(
|
|
622
|
+
"Final-message-only output is only supported for print UI",
|
|
623
|
+
param_hint="--final-message-only",
|
|
624
|
+
)
|
|
625
|
+
if _picker_mode and ui != "shell":
|
|
626
|
+
raise typer.BadParameter(
|
|
627
|
+
"--session without a session ID is only supported for shell UI",
|
|
628
|
+
param_hint="--session",
|
|
629
|
+
)
|
|
630
|
+
|
|
631
|
+
config: Config | Path | None = None
|
|
632
|
+
if config_string is not None:
|
|
633
|
+
config_string = config_string.strip()
|
|
634
|
+
if not config_string:
|
|
635
|
+
raise typer.BadParameter("Config cannot be empty", param_hint="--config")
|
|
636
|
+
try:
|
|
637
|
+
config = load_config_from_string(config_string)
|
|
638
|
+
except ConfigError as e:
|
|
639
|
+
raise typer.BadParameter(str(e), param_hint="--config") from e
|
|
640
|
+
elif config_file is not None:
|
|
641
|
+
config = config_file
|
|
642
|
+
|
|
643
|
+
file_configs = list(mcp_config_file or [])
|
|
644
|
+
raw_mcp_config = list(mcp_config or [])
|
|
645
|
+
|
|
646
|
+
# Use default MCP config file if no MCP config is provided
|
|
647
|
+
if not file_configs:
|
|
648
|
+
default_mcp_file = get_global_mcp_config_file()
|
|
649
|
+
if default_mcp_file.exists():
|
|
650
|
+
file_configs.append(default_mcp_file)
|
|
651
|
+
|
|
652
|
+
try:
|
|
653
|
+
mcp_configs = [json.loads(conf.read_text(encoding="utf-8")) for conf in file_configs]
|
|
654
|
+
except json.JSONDecodeError as e:
|
|
655
|
+
raise typer.BadParameter(f"Invalid JSON: {e}", param_hint="--mcp-config-file") from e
|
|
656
|
+
|
|
657
|
+
try:
|
|
658
|
+
mcp_configs += [json.loads(conf) for conf in raw_mcp_config]
|
|
659
|
+
except json.JSONDecodeError as e:
|
|
660
|
+
raise typer.BadParameter(f"Invalid JSON: {e}", param_hint="--mcp-config") from e
|
|
661
|
+
|
|
662
|
+
# Honor --no-telemetry by exporting the env var before any subsystem (Sentry,
|
|
663
|
+
# OTel, sink) reads it during PythinkerCLI.create.
|
|
664
|
+
if no_telemetry:
|
|
665
|
+
os.environ["PYTHINKER_DISABLE_TELEMETRY"] = "1"
|
|
666
|
+
|
|
667
|
+
skills_dirs: list[HostPath] | None = None
|
|
668
|
+
if local_skills_dir:
|
|
669
|
+
skills_dirs = [HostPath.unsafe_from_local_path(p) for p in local_skills_dir]
|
|
670
|
+
|
|
671
|
+
work_dir = HostPath.unsafe_from_local_path(local_work_dir) if local_work_dir else HostPath.cwd()
|
|
672
|
+
|
|
673
|
+
# Tracks the most recently created/loaded session so that _reload_loop's
|
|
674
|
+
# exception handler can clean it up even when _run() fails before returning.
|
|
675
|
+
_latest_created_session: Session | None = None
|
|
676
|
+
|
|
677
|
+
async def _run(session_id: str | None, prefill_text: str | None = None) -> tuple[Session, int]:
|
|
678
|
+
"""
|
|
679
|
+
Create/load session and run the CLI instance.
|
|
680
|
+
|
|
681
|
+
Returns:
|
|
682
|
+
The session and the exit code (0 = success, 1 = failure, 75 = retryable).
|
|
683
|
+
"""
|
|
684
|
+
startup_progress = ShellStartupProgress(enabled=ui == "shell")
|
|
685
|
+
try:
|
|
686
|
+
startup_progress.update("Preparing session...")
|
|
687
|
+
|
|
688
|
+
# Track if we're resuming an existing session (vs creating new)
|
|
689
|
+
resumed = False
|
|
690
|
+
|
|
691
|
+
if session_id is not None:
|
|
692
|
+
session = await Session.find(work_dir, session_id)
|
|
693
|
+
if session is None:
|
|
694
|
+
logger.info(
|
|
695
|
+
"Session {session_id} not found, creating new session",
|
|
696
|
+
session_id=session_id,
|
|
697
|
+
)
|
|
698
|
+
session = await Session.create(work_dir, session_id)
|
|
699
|
+
else:
|
|
700
|
+
# Only count as "resumed" if the session has actual turns.
|
|
701
|
+
# Sessions created by /new, /undo (turn 0), /fork via Reload
|
|
702
|
+
# may have a custom_title but no wire content — treat as startup.
|
|
703
|
+
resumed = not session.wire_file.is_empty()
|
|
704
|
+
logger.info("Resuming session: {session_id}", session_id=session.id)
|
|
705
|
+
elif continue_:
|
|
706
|
+
session = await Session.continue_(work_dir)
|
|
707
|
+
if session is None:
|
|
708
|
+
raise typer.BadParameter(
|
|
709
|
+
"No previous session found for the working directory",
|
|
710
|
+
param_hint="--continue",
|
|
711
|
+
)
|
|
712
|
+
resumed = True # Continuing previous session
|
|
713
|
+
logger.info("Continuing previous session: {session_id}", session_id=session.id)
|
|
714
|
+
else:
|
|
715
|
+
session = await Session.create(work_dir)
|
|
716
|
+
logger.info("Created new session: {session_id}", session_id=session.id)
|
|
717
|
+
|
|
718
|
+
nonlocal _latest_created_session
|
|
719
|
+
_latest_created_session = session
|
|
720
|
+
|
|
721
|
+
# Add CLI-provided additional directories to session state
|
|
722
|
+
if local_add_dirs:
|
|
723
|
+
from pythinker_code.utils.path import is_within_directory
|
|
724
|
+
|
|
725
|
+
canonical_work_dir = work_dir.canonical()
|
|
726
|
+
changed = False
|
|
727
|
+
for d in local_add_dirs:
|
|
728
|
+
dir_path = HostPath.unsafe_from_local_path(d).canonical()
|
|
729
|
+
dir_str = str(dir_path)
|
|
730
|
+
# Skip dirs within work_dir (already accessible)
|
|
731
|
+
if is_within_directory(dir_path, canonical_work_dir):
|
|
732
|
+
logger.info(
|
|
733
|
+
"Skipping --add-dir {dir}: already within working directory",
|
|
734
|
+
dir=dir_str,
|
|
735
|
+
)
|
|
736
|
+
continue
|
|
737
|
+
if dir_str not in session.state.additional_dirs:
|
|
738
|
+
session.state.additional_dirs.append(dir_str)
|
|
739
|
+
changed = True
|
|
740
|
+
if changed:
|
|
741
|
+
session.save_state()
|
|
742
|
+
|
|
743
|
+
# Redirect stderr *before* PythinkerCLI.create() so that MCP server
|
|
744
|
+
# subprocesses (e.g. mcp-remote OAuth debug logs) write to the log
|
|
745
|
+
# file instead of polluting the user's terminal. CLI argument
|
|
746
|
+
# parsing has already succeeded at this point, so Typer/Click
|
|
747
|
+
# startup errors are no longer a concern. Fatal errors from
|
|
748
|
+
# create() are still visible because _emit_fatal_error() writes to
|
|
749
|
+
# the saved original stderr fd.
|
|
750
|
+
redirect_stderr_to_logger()
|
|
751
|
+
|
|
752
|
+
instance = await PythinkerCLI.create(
|
|
753
|
+
session,
|
|
754
|
+
config=config,
|
|
755
|
+
model_name=model_name,
|
|
756
|
+
thinking=thinking,
|
|
757
|
+
yolo=yolo,
|
|
758
|
+
auto=auto,
|
|
759
|
+
runtime_auto=ui == "print",
|
|
760
|
+
plan_mode=plan,
|
|
761
|
+
resumed=resumed,
|
|
762
|
+
agent_file=agent_file,
|
|
763
|
+
mcp_configs=mcp_configs,
|
|
764
|
+
skills_dirs=skills_dirs,
|
|
765
|
+
max_steps_per_turn=max_steps_per_turn,
|
|
766
|
+
max_retries_per_step=max_retries_per_step,
|
|
767
|
+
max_ralph_iterations=max_ralph_iterations,
|
|
768
|
+
startup_progress=startup_progress.update if ui == "shell" else None,
|
|
769
|
+
defer_mcp_loading=ui == "shell" and prompt is None,
|
|
770
|
+
ui_mode=ui,
|
|
771
|
+
)
|
|
772
|
+
startup_progress.stop()
|
|
773
|
+
|
|
774
|
+
# --- SessionStart hook ---
|
|
775
|
+
_session_source = "resume" if resumed else "startup"
|
|
776
|
+
await instance.soul.hook_engine.trigger(
|
|
777
|
+
"SessionStart",
|
|
778
|
+
matcher_value=_session_source,
|
|
779
|
+
input_data=hook_events.session_start(
|
|
780
|
+
session_id=session.id,
|
|
781
|
+
cwd=str(work_dir),
|
|
782
|
+
source=_session_source,
|
|
783
|
+
),
|
|
784
|
+
)
|
|
785
|
+
|
|
786
|
+
# Install stderr redirection only after initialization succeeded, so runtime
|
|
787
|
+
# stderr noise is captured into logs without hiding startup failures.
|
|
788
|
+
redirect_stderr_to_logger()
|
|
789
|
+
preserve_background_tasks = False
|
|
790
|
+
try:
|
|
791
|
+
match ui:
|
|
792
|
+
case "shell":
|
|
793
|
+
shell_ok = await instance.run_shell(prompt, prefill_text=prefill_text)
|
|
794
|
+
exit_code = ExitCode.SUCCESS if shell_ok else ExitCode.FAILURE
|
|
795
|
+
case "print":
|
|
796
|
+
exit_code = await instance.run_print(
|
|
797
|
+
input_format or "text",
|
|
798
|
+
output_format or "text",
|
|
799
|
+
prompt,
|
|
800
|
+
final_only=final_message_only,
|
|
801
|
+
)
|
|
802
|
+
case "acp":
|
|
803
|
+
if prompt is not None:
|
|
804
|
+
logger.warning("ACP server ignores prompt argument")
|
|
805
|
+
await instance.run_acp()
|
|
806
|
+
exit_code = ExitCode.SUCCESS
|
|
807
|
+
case "wire":
|
|
808
|
+
if prompt is not None:
|
|
809
|
+
logger.warning("Wire server ignores prompt argument")
|
|
810
|
+
await instance.run_wire_stdio()
|
|
811
|
+
exit_code = ExitCode.SUCCESS
|
|
812
|
+
except Reload as e:
|
|
813
|
+
preserve_background_tasks = True
|
|
814
|
+
if e.session_id is None:
|
|
815
|
+
r = Reload(session_id=session.id, prefill_text=e.prefill_text)
|
|
816
|
+
r.source_session = session
|
|
817
|
+
raise r from e
|
|
818
|
+
e.source_session = session
|
|
819
|
+
raise
|
|
820
|
+
except SwitchToWeb:
|
|
821
|
+
preserve_background_tasks = True
|
|
822
|
+
raise
|
|
823
|
+
except SwitchToVis:
|
|
824
|
+
preserve_background_tasks = True
|
|
825
|
+
raise
|
|
826
|
+
finally:
|
|
827
|
+
# --- SessionEnd hook ---
|
|
828
|
+
with contextlib.suppress(Exception):
|
|
829
|
+
await asyncio.wait_for(
|
|
830
|
+
instance.soul.hook_engine.trigger(
|
|
831
|
+
"SessionEnd",
|
|
832
|
+
matcher_value="exit",
|
|
833
|
+
input_data=hook_events.session_end(
|
|
834
|
+
session_id=session.id,
|
|
835
|
+
cwd=str(work_dir),
|
|
836
|
+
reason="exit",
|
|
837
|
+
),
|
|
838
|
+
),
|
|
839
|
+
timeout=5,
|
|
840
|
+
)
|
|
841
|
+
|
|
842
|
+
if not preserve_background_tasks:
|
|
843
|
+
await instance.shutdown_background_tasks()
|
|
844
|
+
await instance.await_bg_tasks_shutdown()
|
|
845
|
+
|
|
846
|
+
return session, exit_code
|
|
847
|
+
finally:
|
|
848
|
+
startup_progress.stop()
|
|
849
|
+
|
|
850
|
+
async def _delete_empty_session(session: Session) -> None:
|
|
851
|
+
"""Delete an empty session directory and clear last_session_id if it pointed to it."""
|
|
852
|
+
logger.info(
|
|
853
|
+
"Session {session_id} has empty context, removing it",
|
|
854
|
+
session_id=session.id,
|
|
855
|
+
)
|
|
856
|
+
await session.delete()
|
|
857
|
+
meta = load_metadata()
|
|
858
|
+
wdm = meta.get_work_dir_meta(session.work_dir)
|
|
859
|
+
if wdm is not None and wdm.last_session_id == session.id:
|
|
860
|
+
wdm.last_session_id = None
|
|
861
|
+
save_metadata(meta)
|
|
862
|
+
|
|
863
|
+
def _print_resume_hint(session: Session) -> None:
|
|
864
|
+
"""Print a hint for resuming the session after exit."""
|
|
865
|
+
if not session.is_empty():
|
|
866
|
+
_emit_fatal_error(f"\nTo resume this session: pythinker -r {session.id}")
|
|
867
|
+
|
|
868
|
+
async def _post_run(last_session: Session, exit_code: int) -> None:
|
|
869
|
+
_print_resume_hint(last_session)
|
|
870
|
+
if last_session.is_empty():
|
|
871
|
+
# Always clean up empty sessions regardless of exit code
|
|
872
|
+
await _delete_empty_session(last_session)
|
|
873
|
+
elif exit_code == ExitCode.SUCCESS:
|
|
874
|
+
metadata = load_metadata()
|
|
875
|
+
work_dir_meta = metadata.get_work_dir_meta(last_session.work_dir)
|
|
876
|
+
if work_dir_meta is None:
|
|
877
|
+
logger.warning(
|
|
878
|
+
"Work dir metadata missing when marking last session, recreating: {work_dir}",
|
|
879
|
+
work_dir=last_session.work_dir,
|
|
880
|
+
)
|
|
881
|
+
work_dir_meta = metadata.new_work_dir_meta(last_session.work_dir)
|
|
882
|
+
work_dir_meta.last_session_id = last_session.id
|
|
883
|
+
save_metadata(metadata)
|
|
884
|
+
|
|
885
|
+
async def _reload_loop(session_id: str | None) -> tuple[str | None, int]:
|
|
886
|
+
"""Run the main loop, handling Reload/SwitchToWeb/SwitchToVis.
|
|
887
|
+
|
|
888
|
+
Returns:
|
|
889
|
+
(switch_target, exit_code) where switch_target is "web", "vis",
|
|
890
|
+
or None if the session ended normally.
|
|
891
|
+
"""
|
|
892
|
+
last_session: Session | None = None
|
|
893
|
+
prefill_text: str | None = None
|
|
894
|
+
try:
|
|
895
|
+
while True:
|
|
896
|
+
try:
|
|
897
|
+
last_session, exit_code = await _run(session_id, prefill_text=prefill_text)
|
|
898
|
+
break
|
|
899
|
+
except Reload as e:
|
|
900
|
+
# Clean up old empty session when switching to a different session
|
|
901
|
+
old = e.source_session
|
|
902
|
+
if old is not None and old.id != e.session_id and old.is_empty():
|
|
903
|
+
await _delete_empty_session(old)
|
|
904
|
+
last_session = None
|
|
905
|
+
else:
|
|
906
|
+
last_session = e.source_session
|
|
907
|
+
# Only print resume hint when switching to a different session
|
|
908
|
+
# (not for same-session reloads like /model, /theme, /reload)
|
|
909
|
+
if old is not None and e.session_id is not None and old.id != e.session_id:
|
|
910
|
+
_print_resume_hint(old)
|
|
911
|
+
session_id = e.session_id
|
|
912
|
+
prefill_text = e.prefill_text
|
|
913
|
+
continue
|
|
914
|
+
except SwitchToWeb as e:
|
|
915
|
+
if e.session_id is not None:
|
|
916
|
+
session = await Session.find(work_dir, e.session_id)
|
|
917
|
+
if session is not None:
|
|
918
|
+
await _post_run(session, ExitCode.SUCCESS)
|
|
919
|
+
return "web", ExitCode.SUCCESS
|
|
920
|
+
except SwitchToVis as e:
|
|
921
|
+
if e.session_id is not None:
|
|
922
|
+
session = await Session.find(work_dir, e.session_id)
|
|
923
|
+
if session is not None:
|
|
924
|
+
await _post_run(session, ExitCode.SUCCESS)
|
|
925
|
+
return "vis", ExitCode.SUCCESS
|
|
926
|
+
assert last_session is not None
|
|
927
|
+
await _post_run(last_session, exit_code)
|
|
928
|
+
return None, exit_code
|
|
929
|
+
except (SwitchToWeb, SwitchToVis):
|
|
930
|
+
# Currently handled inside the loop (return), but re-raise explicitly
|
|
931
|
+
# so the generic except below never treats them as unexpected errors.
|
|
932
|
+
raise
|
|
933
|
+
except Exception:
|
|
934
|
+
# Best-effort cleanup: _latest_created_session is the session from
|
|
935
|
+
# the most recent _run() call, which may have failed before returning.
|
|
936
|
+
# last_session is from a *previous* iteration and must not be touched.
|
|
937
|
+
if _latest_created_session is not None:
|
|
938
|
+
_print_resume_hint(_latest_created_session)
|
|
939
|
+
if _latest_created_session.is_empty():
|
|
940
|
+
with contextlib.suppress(Exception):
|
|
941
|
+
await _delete_empty_session(_latest_created_session)
|
|
942
|
+
raise
|
|
943
|
+
|
|
944
|
+
if _picker_mode:
|
|
945
|
+
from prompt_toolkit.shortcuts.choice_input import ChoiceInput
|
|
946
|
+
from rich.console import Console
|
|
947
|
+
|
|
948
|
+
from pythinker_code.utils.datetime import format_relative_time
|
|
949
|
+
|
|
950
|
+
async def _pick_session() -> str:
|
|
951
|
+
all_sessions = await Session.list(work_dir)
|
|
952
|
+
if not all_sessions:
|
|
953
|
+
Console().print("[yellow]No sessions found for the working directory.[/yellow]")
|
|
954
|
+
raise typer.Exit(0)
|
|
955
|
+
|
|
956
|
+
choices: list[tuple[str, str]] = []
|
|
957
|
+
for s in all_sessions:
|
|
958
|
+
time_str = format_relative_time(s.updated_at)
|
|
959
|
+
short_id = s.id[:8]
|
|
960
|
+
name = _strip_session_id_suffix(s.title, s.id)
|
|
961
|
+
label = f"{name} ({short_id}), {time_str}"
|
|
962
|
+
choices.append((s.id, label))
|
|
963
|
+
|
|
964
|
+
try:
|
|
965
|
+
selection = await ChoiceInput(
|
|
966
|
+
message="Select a session to resume"
|
|
967
|
+
" (↑↓ navigate, Enter select, Ctrl+C cancel):",
|
|
968
|
+
options=choices,
|
|
969
|
+
default=choices[0][0],
|
|
970
|
+
).prompt_async()
|
|
971
|
+
except (EOFError, KeyboardInterrupt):
|
|
972
|
+
raise typer.Exit(0) from None
|
|
973
|
+
|
|
974
|
+
if not selection:
|
|
975
|
+
raise typer.Exit(0)
|
|
976
|
+
|
|
977
|
+
return selection
|
|
978
|
+
|
|
979
|
+
session_id = asyncio.run(_pick_session())
|
|
980
|
+
|
|
981
|
+
try:
|
|
982
|
+
switch_target, exit_code = asyncio.run(_reload_loop(session_id))
|
|
983
|
+
except (typer.BadParameter, typer.Exit):
|
|
984
|
+
# Let Typer/Click format these errors (rich panel + correct exit code).
|
|
985
|
+
raise
|
|
986
|
+
except Exception as exc:
|
|
987
|
+
import click
|
|
988
|
+
|
|
989
|
+
if isinstance(exc, click.ClickException):
|
|
990
|
+
# ClickException includes the errors Typer knows how to render; don't
|
|
991
|
+
# wrap them, or we'd lose the standard error UI and exit codes.
|
|
992
|
+
raise
|
|
993
|
+
logger.exception("Fatal error when running CLI")
|
|
994
|
+
if debug:
|
|
995
|
+
import traceback
|
|
996
|
+
|
|
997
|
+
# In debug mode, show full traceback for quick diagnosis.
|
|
998
|
+
_emit_fatal_error(traceback.format_exc())
|
|
999
|
+
else:
|
|
1000
|
+
from pythinker_code.share import get_share_dir
|
|
1001
|
+
|
|
1002
|
+
log_path = get_share_dir() / "logs" / "pythinker.log"
|
|
1003
|
+
# In non-debug mode, print a concise error and point users to logs.
|
|
1004
|
+
_emit_fatal_error(
|
|
1005
|
+
f"{exc}\n"
|
|
1006
|
+
f"See logs: {log_path}\n"
|
|
1007
|
+
"Run with --debug for full traceback, or run pythinker export to share diagnostics."
|
|
1008
|
+
)
|
|
1009
|
+
raise typer.Exit(code=1) from exc
|
|
1010
|
+
if switch_target in ("web", "vis"):
|
|
1011
|
+
from pythinker_code.utils.logging import restore_stderr
|
|
1012
|
+
|
|
1013
|
+
restore_stderr()
|
|
1014
|
+
|
|
1015
|
+
# Restore default SIGINT handler and terminal state after the shell's
|
|
1016
|
+
# asyncio.run() to ensure Ctrl+C works in the uvicorn web server.
|
|
1017
|
+
import signal
|
|
1018
|
+
|
|
1019
|
+
signal.signal(signal.SIGINT, signal.default_int_handler)
|
|
1020
|
+
|
|
1021
|
+
from pythinker_code.utils.term import ensure_tty_sane
|
|
1022
|
+
|
|
1023
|
+
ensure_tty_sane()
|
|
1024
|
+
|
|
1025
|
+
if switch_target == "web":
|
|
1026
|
+
from pythinker_code.web.app import run_web_server
|
|
1027
|
+
|
|
1028
|
+
run_web_server(open_browser=True)
|
|
1029
|
+
else:
|
|
1030
|
+
from pythinker_code.vis.app import run_vis_server
|
|
1031
|
+
|
|
1032
|
+
run_vis_server(open_browser=True)
|
|
1033
|
+
elif exit_code != ExitCode.SUCCESS:
|
|
1034
|
+
raise typer.Exit(code=exit_code)
|
|
1035
|
+
|
|
1036
|
+
|
|
1037
|
+
@cli.command()
|
|
1038
|
+
def login(
|
|
1039
|
+
json: bool = typer.Option(
|
|
1040
|
+
False,
|
|
1041
|
+
"--json",
|
|
1042
|
+
help="Emit OAuth events as JSON lines.",
|
|
1043
|
+
),
|
|
1044
|
+
browser: bool = typer.Option(False, "--browser", help="Use OpenAI ChatGPT browser login."),
|
|
1045
|
+
headless: bool = typer.Option(
|
|
1046
|
+
False, "--headless", help="Use OpenAI ChatGPT device-code login."
|
|
1047
|
+
),
|
|
1048
|
+
api_key: bool = typer.Option(False, "--api-key", help="Configure OpenAI with an API key."),
|
|
1049
|
+
opencode_go: bool = typer.Option(
|
|
1050
|
+
False, "--opencode-go", help="Configure OpenCode Go with an API key."
|
|
1051
|
+
),
|
|
1052
|
+
minimax: bool = typer.Option(False, "--minimax", help="Configure MiniMax with an API key."),
|
|
1053
|
+
deepseek: bool = typer.Option(False, "--deepseek", help="Configure DeepSeek with an API key."),
|
|
1054
|
+
anthropic: bool = typer.Option(
|
|
1055
|
+
False, "--anthropic", help="Configure Anthropic with an API key."
|
|
1056
|
+
),
|
|
1057
|
+
openrouter: bool = typer.Option(
|
|
1058
|
+
False, "--openrouter", help="Configure OpenRouter with an API key."
|
|
1059
|
+
),
|
|
1060
|
+
lm_studio: bool = typer.Option(
|
|
1061
|
+
False, "--lm-studio", "--lmstudio", help="Configure LM Studio as a local provider."
|
|
1062
|
+
),
|
|
1063
|
+
ollama: bool = typer.Option(False, "--ollama", help="Configure Ollama as a local provider."),
|
|
1064
|
+
base_url: str = typer.Option(
|
|
1065
|
+
"",
|
|
1066
|
+
"--base-url",
|
|
1067
|
+
help="Override the default base URL for --lm-studio or --ollama.",
|
|
1068
|
+
),
|
|
1069
|
+
) -> None:
|
|
1070
|
+
"""Login with OpenAI, OpenCode Go, MiniMax, DeepSeek, Anthropic, or local providers."""
|
|
1071
|
+
import asyncio
|
|
1072
|
+
|
|
1073
|
+
from rich.console import Console
|
|
1074
|
+
from rich.status import Status
|
|
1075
|
+
|
|
1076
|
+
async def _run() -> bool:
|
|
1077
|
+
selected_modes = sum(
|
|
1078
|
+
bool(value)
|
|
1079
|
+
for value in (
|
|
1080
|
+
browser,
|
|
1081
|
+
headless,
|
|
1082
|
+
api_key,
|
|
1083
|
+
opencode_go,
|
|
1084
|
+
minimax,
|
|
1085
|
+
deepseek,
|
|
1086
|
+
anthropic,
|
|
1087
|
+
openrouter,
|
|
1088
|
+
lm_studio,
|
|
1089
|
+
ollama,
|
|
1090
|
+
)
|
|
1091
|
+
)
|
|
1092
|
+
if selected_modes > 1:
|
|
1093
|
+
typer.echo(
|
|
1094
|
+
"Choose only one of --browser, --headless, --api-key, "
|
|
1095
|
+
"--opencode-go, --minimax, --deepseek, --anthropic, --openrouter, "
|
|
1096
|
+
"--lm-studio, or --ollama.",
|
|
1097
|
+
err=True,
|
|
1098
|
+
)
|
|
1099
|
+
return False
|
|
1100
|
+
|
|
1101
|
+
config = load_config()
|
|
1102
|
+
if openrouter:
|
|
1103
|
+
key = typer.prompt("OpenRouter API key", hide_input=True).strip()
|
|
1104
|
+
events = login_openrouter_api_key(config, key)
|
|
1105
|
+
elif anthropic:
|
|
1106
|
+
key = typer.prompt("Anthropic API key", hide_input=True).strip()
|
|
1107
|
+
events = login_anthropic_api_key(config, key)
|
|
1108
|
+
elif deepseek:
|
|
1109
|
+
key = typer.prompt("DeepSeek API key", hide_input=True).strip()
|
|
1110
|
+
events = login_deepseek_api_key(config, key)
|
|
1111
|
+
elif minimax:
|
|
1112
|
+
key = typer.prompt("MiniMax API key", hide_input=True).strip()
|
|
1113
|
+
events = login_minimax_api_key(config, key)
|
|
1114
|
+
elif opencode_go:
|
|
1115
|
+
key = typer.prompt("OpenCode Go API key", hide_input=True).strip()
|
|
1116
|
+
events = login_opencode_go_api_key(config, key)
|
|
1117
|
+
elif api_key:
|
|
1118
|
+
key = typer.prompt("OpenAI API key", hide_input=True).strip()
|
|
1119
|
+
events = login_openai_api_key(config, key)
|
|
1120
|
+
elif lm_studio:
|
|
1121
|
+
events = login_lm_studio(
|
|
1122
|
+
config,
|
|
1123
|
+
base_url=(base_url.strip() or None),
|
|
1124
|
+
)
|
|
1125
|
+
elif ollama:
|
|
1126
|
+
events = login_ollama(
|
|
1127
|
+
config,
|
|
1128
|
+
base_url=(base_url.strip() or None),
|
|
1129
|
+
)
|
|
1130
|
+
elif headless:
|
|
1131
|
+
events = login_openai_headless(config)
|
|
1132
|
+
else:
|
|
1133
|
+
events = login_openai_browser(config, open_browser=True)
|
|
1134
|
+
|
|
1135
|
+
if json:
|
|
1136
|
+
ok = True
|
|
1137
|
+
async for event in events:
|
|
1138
|
+
typer.echo(event.json)
|
|
1139
|
+
if event.type == "error":
|
|
1140
|
+
ok = False
|
|
1141
|
+
return ok
|
|
1142
|
+
|
|
1143
|
+
console = Console()
|
|
1144
|
+
ok = True
|
|
1145
|
+
status: Status | None = None
|
|
1146
|
+
try:
|
|
1147
|
+
async for event in events:
|
|
1148
|
+
if event.type == "waiting":
|
|
1149
|
+
if status is None:
|
|
1150
|
+
status = console.status("Waiting for OpenAI authorization.")
|
|
1151
|
+
status.start()
|
|
1152
|
+
continue
|
|
1153
|
+
if status is not None:
|
|
1154
|
+
status.stop()
|
|
1155
|
+
status = None
|
|
1156
|
+
match event.type:
|
|
1157
|
+
case "error":
|
|
1158
|
+
style = "red"
|
|
1159
|
+
case "success":
|
|
1160
|
+
style = "green"
|
|
1161
|
+
case _:
|
|
1162
|
+
style = None
|
|
1163
|
+
console.print(event.message, markup=False, style=style)
|
|
1164
|
+
if event.type == "error":
|
|
1165
|
+
ok = False
|
|
1166
|
+
finally:
|
|
1167
|
+
if status is not None:
|
|
1168
|
+
status.stop()
|
|
1169
|
+
return ok
|
|
1170
|
+
|
|
1171
|
+
ok = asyncio.run(_run())
|
|
1172
|
+
if not ok:
|
|
1173
|
+
raise typer.Exit(code=1)
|
|
1174
|
+
|
|
1175
|
+
|
|
1176
|
+
@cli.command()
|
|
1177
|
+
def logout(
|
|
1178
|
+
json: bool = typer.Option(
|
|
1179
|
+
False,
|
|
1180
|
+
"--json",
|
|
1181
|
+
help="Emit OAuth events as JSON lines.",
|
|
1182
|
+
),
|
|
1183
|
+
opencode_go: bool = typer.Option(False, "--opencode-go", help="Logout from OpenCode Go."),
|
|
1184
|
+
minimax: bool = typer.Option(False, "--minimax", help="Logout from MiniMax."),
|
|
1185
|
+
deepseek: bool = typer.Option(False, "--deepseek", help="Logout from DeepSeek."),
|
|
1186
|
+
anthropic: bool = typer.Option(False, "--anthropic", help="Logout from Anthropic."),
|
|
1187
|
+
openrouter: bool = typer.Option(False, "--openrouter", help="Logout from OpenRouter."),
|
|
1188
|
+
lm_studio: bool = typer.Option(
|
|
1189
|
+
False, "--lm-studio", "--lmstudio", help="Logout from LM Studio."
|
|
1190
|
+
),
|
|
1191
|
+
ollama: bool = typer.Option(False, "--ollama", help="Logout from Ollama."),
|
|
1192
|
+
) -> None:
|
|
1193
|
+
"""Logout from OpenAI, OpenCode Go, MiniMax, DeepSeek, Anthropic, or local providers."""
|
|
1194
|
+
import asyncio
|
|
1195
|
+
|
|
1196
|
+
from rich.console import Console
|
|
1197
|
+
|
|
1198
|
+
async def _run() -> bool:
|
|
1199
|
+
ok = True
|
|
1200
|
+
selected_modes = (opencode_go, minimax, deepseek, anthropic, openrouter, lm_studio, ollama)
|
|
1201
|
+
if sum(bool(v) for v in selected_modes) > 1:
|
|
1202
|
+
typer.echo(
|
|
1203
|
+
"Choose only one of --opencode-go, --minimax, --deepseek, "
|
|
1204
|
+
"--anthropic, --openrouter, --lm-studio, or --ollama.",
|
|
1205
|
+
err=True,
|
|
1206
|
+
)
|
|
1207
|
+
return False
|
|
1208
|
+
|
|
1209
|
+
config = load_config()
|
|
1210
|
+
if openrouter:
|
|
1211
|
+
events = logout_openrouter(config)
|
|
1212
|
+
elif anthropic:
|
|
1213
|
+
events = logout_anthropic(config)
|
|
1214
|
+
elif deepseek:
|
|
1215
|
+
events = logout_deepseek(config)
|
|
1216
|
+
elif minimax:
|
|
1217
|
+
events = logout_minimax(config)
|
|
1218
|
+
elif opencode_go:
|
|
1219
|
+
events = logout_opencode_go(config)
|
|
1220
|
+
elif lm_studio:
|
|
1221
|
+
events = logout_lm_studio(config)
|
|
1222
|
+
elif ollama:
|
|
1223
|
+
events = logout_ollama(config)
|
|
1224
|
+
else:
|
|
1225
|
+
events = logout_openai(config)
|
|
1226
|
+
if json:
|
|
1227
|
+
async for event in events:
|
|
1228
|
+
typer.echo(event.json)
|
|
1229
|
+
if event.type == "error":
|
|
1230
|
+
ok = False
|
|
1231
|
+
return ok
|
|
1232
|
+
|
|
1233
|
+
console = Console()
|
|
1234
|
+
async for event in events:
|
|
1235
|
+
match event.type:
|
|
1236
|
+
case "error":
|
|
1237
|
+
style = "red"
|
|
1238
|
+
case "success":
|
|
1239
|
+
style = "green"
|
|
1240
|
+
case _:
|
|
1241
|
+
style = None
|
|
1242
|
+
console.print(event.message, markup=False, style=style)
|
|
1243
|
+
if event.type == "error":
|
|
1244
|
+
ok = False
|
|
1245
|
+
return ok
|
|
1246
|
+
|
|
1247
|
+
ok = asyncio.run(_run())
|
|
1248
|
+
if not ok:
|
|
1249
|
+
raise typer.Exit(code=1)
|
|
1250
|
+
|
|
1251
|
+
|
|
1252
|
+
@cli.command(context_settings={"allow_extra_args": True, "ignore_unknown_options": True})
|
|
1253
|
+
def term(
|
|
1254
|
+
ctx: typer.Context,
|
|
1255
|
+
) -> None:
|
|
1256
|
+
"""Run Toad TUI backed by Pythinker CLI ACP server."""
|
|
1257
|
+
from .toad import run_term
|
|
1258
|
+
|
|
1259
|
+
run_term(ctx)
|
|
1260
|
+
|
|
1261
|
+
|
|
1262
|
+
@cli.command()
|
|
1263
|
+
def acp():
|
|
1264
|
+
"""Run Pythinker CLI ACP server."""
|
|
1265
|
+
from pythinker_code.acp import acp_main
|
|
1266
|
+
|
|
1267
|
+
acp_main()
|
|
1268
|
+
|
|
1269
|
+
|
|
1270
|
+
@cli.command(name="__background-task-worker", hidden=True)
|
|
1271
|
+
def background_task_worker(
|
|
1272
|
+
task_dir: Annotated[Path, typer.Option("--task-dir")],
|
|
1273
|
+
heartbeat_interval_ms: Annotated[int, typer.Option("--heartbeat-interval-ms")] = 5000,
|
|
1274
|
+
control_poll_interval_ms: Annotated[int, typer.Option("--control-poll-interval-ms")] = 500,
|
|
1275
|
+
kill_grace_period_ms: Annotated[int, typer.Option("--kill-grace-period-ms")] = 2000,
|
|
1276
|
+
) -> None:
|
|
1277
|
+
"""Run background task worker subprocess (internal)."""
|
|
1278
|
+
import asyncio
|
|
1279
|
+
|
|
1280
|
+
from pythinker_code.background import run_background_task_worker
|
|
1281
|
+
from pythinker_code.utils.proctitle import set_process_title
|
|
1282
|
+
|
|
1283
|
+
set_process_title("pythinker-code-bg-worker")
|
|
1284
|
+
|
|
1285
|
+
from pythinker_code.app import enable_logging
|
|
1286
|
+
|
|
1287
|
+
enable_logging(debug=False)
|
|
1288
|
+
asyncio.run(
|
|
1289
|
+
run_background_task_worker(
|
|
1290
|
+
task_dir,
|
|
1291
|
+
heartbeat_interval_ms=heartbeat_interval_ms,
|
|
1292
|
+
control_poll_interval_ms=control_poll_interval_ms,
|
|
1293
|
+
kill_grace_period_ms=kill_grace_period_ms,
|
|
1294
|
+
)
|
|
1295
|
+
)
|
|
1296
|
+
|
|
1297
|
+
|
|
1298
|
+
@cli.command(name="__web-worker", hidden=True)
|
|
1299
|
+
def web_worker(session_id: str) -> None:
|
|
1300
|
+
"""Run web worker subprocess (internal)."""
|
|
1301
|
+
import asyncio
|
|
1302
|
+
from uuid import UUID
|
|
1303
|
+
|
|
1304
|
+
from pythinker_code.utils.proctitle import set_process_title
|
|
1305
|
+
|
|
1306
|
+
set_process_title("pythinker-code-worker")
|
|
1307
|
+
|
|
1308
|
+
from pythinker_code.app import enable_logging
|
|
1309
|
+
from pythinker_code.web.runner.worker import run_worker
|
|
1310
|
+
|
|
1311
|
+
try:
|
|
1312
|
+
parsed_session_id = UUID(session_id)
|
|
1313
|
+
except ValueError as exc:
|
|
1314
|
+
raise typer.BadParameter(f"Invalid session ID: {session_id}") from exc
|
|
1315
|
+
|
|
1316
|
+
enable_logging(debug=False)
|
|
1317
|
+
asyncio.run(run_worker(parsed_session_id))
|
|
1318
|
+
|
|
1319
|
+
|
|
1320
|
+
if __name__ == "__main__":
|
|
1321
|
+
import sys
|
|
1322
|
+
|
|
1323
|
+
if "pythinker_code.cli" not in sys.modules:
|
|
1324
|
+
sys.modules["pythinker_code.cli"] = sys.modules[__name__]
|
|
1325
|
+
|
|
1326
|
+
sys.exit(cli())
|