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,898 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from collections.abc import Awaitable, Callable
|
|
5
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
6
|
+
|
|
7
|
+
from prompt_toolkit.shortcuts.choice_input import ChoiceInput
|
|
8
|
+
|
|
9
|
+
from pythinker_code.auth.platforms import get_platform_name_for_provider, refresh_managed_models
|
|
10
|
+
from pythinker_code.cli import Reload, SwitchToVis, SwitchToWeb
|
|
11
|
+
from pythinker_code.config import load_config, save_config
|
|
12
|
+
from pythinker_code.exception import ConfigError
|
|
13
|
+
from pythinker_code.session import Session
|
|
14
|
+
from pythinker_code.soul.pythinkersoul import PythinkerSoul
|
|
15
|
+
from pythinker_code.ui.shell.console import console
|
|
16
|
+
from pythinker_code.ui.shell.mcp_status import render_mcp_console
|
|
17
|
+
from pythinker_code.ui.shell.task_browser import TaskBrowserApp
|
|
18
|
+
from pythinker_code.utils.changelog import CHANGELOG
|
|
19
|
+
from pythinker_code.utils.logging import logger
|
|
20
|
+
from pythinker_code.utils.slashcmd import SlashCommand, SlashCommandRegistry
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from pythinker_code.ui.shell import Shell
|
|
24
|
+
|
|
25
|
+
type ShellSlashCmdFunc = Callable[[Shell, str], None | Awaitable[None]]
|
|
26
|
+
"""
|
|
27
|
+
A function that runs as a Shell-level slash command.
|
|
28
|
+
|
|
29
|
+
Raises:
|
|
30
|
+
Reload: When the configuration should be reloaded.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
registry = SlashCommandRegistry[ShellSlashCmdFunc]()
|
|
35
|
+
shell_mode_registry = SlashCommandRegistry[ShellSlashCmdFunc]()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def ensure_pythinker_soul(app: Shell) -> PythinkerSoul | None:
|
|
39
|
+
if not isinstance(app.soul, PythinkerSoul):
|
|
40
|
+
console.print("[red]PythinkerSoul required[/red]")
|
|
41
|
+
return None
|
|
42
|
+
return app.soul
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@registry.command(aliases=["quit"])
|
|
46
|
+
@shell_mode_registry.command(aliases=["quit"])
|
|
47
|
+
def exit(app: Shell, args: str):
|
|
48
|
+
"""Exit the application"""
|
|
49
|
+
# should be handled by `Shell`
|
|
50
|
+
raise NotImplementedError
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
SKILL_COMMAND_PREFIX = "skill:"
|
|
54
|
+
|
|
55
|
+
_KEYBOARD_SHORTCUTS = [
|
|
56
|
+
("Ctrl-X", "Toggle agent/shell mode"),
|
|
57
|
+
("Shift-Tab", "Toggle plan mode (read-only research)"),
|
|
58
|
+
("Ctrl-O", "Edit in external editor ($VISUAL/$EDITOR)"),
|
|
59
|
+
("Ctrl-J / Alt-Enter", "Insert newline"),
|
|
60
|
+
("Ctrl-V", "Paste (supports images)"),
|
|
61
|
+
("Ctrl-D", "Exit"),
|
|
62
|
+
("Ctrl-C", "Interrupt"),
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@registry.command(aliases=["h", "?"])
|
|
67
|
+
@shell_mode_registry.command(aliases=["h", "?"])
|
|
68
|
+
def help(app: Shell, args: str):
|
|
69
|
+
"""Show help information"""
|
|
70
|
+
from rich.console import Group, RenderableType
|
|
71
|
+
from rich.text import Text
|
|
72
|
+
|
|
73
|
+
from pythinker_code.utils.rich.columns import BulletColumns
|
|
74
|
+
|
|
75
|
+
def section(title: str, items: list[tuple[str, str]], color: str) -> BulletColumns:
|
|
76
|
+
lines: list[RenderableType] = [Text.from_markup(f"[bold]{title}:[/bold]")]
|
|
77
|
+
for name, desc in items:
|
|
78
|
+
lines.append(
|
|
79
|
+
BulletColumns(
|
|
80
|
+
Text.from_markup(f"[{color}]{name}[/{color}]: [grey50]{desc}[/grey50]"),
|
|
81
|
+
bullet_style=color,
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
return BulletColumns(Group(*lines))
|
|
85
|
+
|
|
86
|
+
renderables: list[RenderableType] = []
|
|
87
|
+
renderables.append(
|
|
88
|
+
BulletColumns(
|
|
89
|
+
Group(
|
|
90
|
+
Text.from_markup("[grey50]Help! I need somebody. Help! Not just anybody.[/grey50]"),
|
|
91
|
+
Text.from_markup("[grey50]Help! You know I need someone. Help![/grey50]"),
|
|
92
|
+
Text.from_markup("[grey50]\u2015 The Beatles, [italic]Help![/italic][/grey50]"),
|
|
93
|
+
),
|
|
94
|
+
bullet_style="grey50",
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
renderables.append(
|
|
98
|
+
BulletColumns(
|
|
99
|
+
Text(
|
|
100
|
+
"Sure, Pythinker is ready to help! "
|
|
101
|
+
"Just send me messages and I will help you get things done!"
|
|
102
|
+
),
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
commands: list[SlashCommand[Any]] = []
|
|
107
|
+
skills: list[SlashCommand[Any]] = []
|
|
108
|
+
for cmd in app.available_slash_commands.values():
|
|
109
|
+
if cmd.name.startswith(SKILL_COMMAND_PREFIX):
|
|
110
|
+
skills.append(cmd)
|
|
111
|
+
else:
|
|
112
|
+
commands.append(cmd)
|
|
113
|
+
|
|
114
|
+
renderables.append(section("Keyboard shortcuts", _KEYBOARD_SHORTCUTS, "yellow"))
|
|
115
|
+
renderables.append(
|
|
116
|
+
section(
|
|
117
|
+
"Slash commands",
|
|
118
|
+
[(c.slash_name(), c.description) for c in sorted(commands, key=lambda c: c.name)],
|
|
119
|
+
"blue",
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
if skills:
|
|
123
|
+
renderables.append(
|
|
124
|
+
section(
|
|
125
|
+
"Skills",
|
|
126
|
+
[(c.slash_name(), c.description) for c in sorted(skills, key=lambda c: c.name)],
|
|
127
|
+
"cyan",
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
with console.pager(styles=True):
|
|
132
|
+
console.print(Group(*renderables))
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@registry.command
|
|
136
|
+
@shell_mode_registry.command
|
|
137
|
+
def version(app: Shell, args: str):
|
|
138
|
+
"""Show version information"""
|
|
139
|
+
from pythinker_code.constant import VERSION
|
|
140
|
+
|
|
141
|
+
console.print(f"pythinker, version {VERSION}")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@registry.command
|
|
145
|
+
async def model(app: Shell, args: str):
|
|
146
|
+
"""Switch LLM model or thinking mode"""
|
|
147
|
+
from pythinker_code.llm import derive_model_capabilities
|
|
148
|
+
|
|
149
|
+
soul = ensure_pythinker_soul(app)
|
|
150
|
+
if soul is None:
|
|
151
|
+
return
|
|
152
|
+
config = soul.runtime.config
|
|
153
|
+
|
|
154
|
+
await refresh_managed_models(config)
|
|
155
|
+
|
|
156
|
+
if not config.models:
|
|
157
|
+
console.print('[yellow]No models configured, send "/login" to login.[/yellow]')
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
if not config.is_from_default_location:
|
|
161
|
+
console.print(
|
|
162
|
+
"[yellow]Model switching requires the default config file; "
|
|
163
|
+
"restart without --config/--config-file.[/yellow]"
|
|
164
|
+
)
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
# Find current model/thinking from runtime (may be overridden by --model/--thinking)
|
|
168
|
+
curr_model_cfg = soul.runtime.llm.model_config if soul.runtime.llm else None
|
|
169
|
+
curr_model_name: str | None = None
|
|
170
|
+
if curr_model_cfg is not None:
|
|
171
|
+
for name, model_cfg in config.models.items():
|
|
172
|
+
if model_cfg == curr_model_cfg:
|
|
173
|
+
curr_model_name = name
|
|
174
|
+
break
|
|
175
|
+
curr_thinking = soul.thinking
|
|
176
|
+
|
|
177
|
+
# Step 1: Pick a model — single grouped picker with type-to-filter.
|
|
178
|
+
from pythinker_code.ui.shell.model_picker import ModelPickerApp, build_provider_groups
|
|
179
|
+
|
|
180
|
+
groups = build_provider_groups(
|
|
181
|
+
config_models=config.models,
|
|
182
|
+
label_for=_provider_label,
|
|
183
|
+
)
|
|
184
|
+
selected_model_name = await ModelPickerApp(
|
|
185
|
+
groups=groups,
|
|
186
|
+
current_model_name=curr_model_name,
|
|
187
|
+
).run()
|
|
188
|
+
if not selected_model_name:
|
|
189
|
+
return
|
|
190
|
+
|
|
191
|
+
selected_model_cfg = config.models[selected_model_name]
|
|
192
|
+
selected_provider = config.providers.get(selected_model_cfg.provider)
|
|
193
|
+
if selected_provider is None:
|
|
194
|
+
console.print(f"[red]Provider not found: {selected_model_cfg.provider}[/red]")
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
# Step 2: Determine thinking mode
|
|
198
|
+
capabilities = derive_model_capabilities(selected_model_cfg)
|
|
199
|
+
new_thinking: bool
|
|
200
|
+
|
|
201
|
+
if "always_thinking" in capabilities:
|
|
202
|
+
new_thinking = True
|
|
203
|
+
elif "thinking" in capabilities:
|
|
204
|
+
thinking_choices: list[tuple[str, str]] = [
|
|
205
|
+
("off", "off" + (" (current)" if not curr_thinking else "")),
|
|
206
|
+
("on", "on" + (" (current)" if curr_thinking else "")),
|
|
207
|
+
]
|
|
208
|
+
try:
|
|
209
|
+
thinking_selection = await ChoiceInput(
|
|
210
|
+
message="Enable thinking mode? (↑↓ navigate, Enter select, Ctrl+C cancel):",
|
|
211
|
+
options=thinking_choices,
|
|
212
|
+
default="on" if curr_thinking else "off",
|
|
213
|
+
).prompt_async()
|
|
214
|
+
except (EOFError, KeyboardInterrupt):
|
|
215
|
+
return
|
|
216
|
+
|
|
217
|
+
if not thinking_selection:
|
|
218
|
+
return
|
|
219
|
+
|
|
220
|
+
new_thinking = thinking_selection == "on"
|
|
221
|
+
else:
|
|
222
|
+
new_thinking = False
|
|
223
|
+
|
|
224
|
+
# Check if anything changed
|
|
225
|
+
model_changed = curr_model_name != selected_model_name
|
|
226
|
+
thinking_changed = curr_thinking != new_thinking
|
|
227
|
+
selected_display = selected_model_cfg.display_name or selected_model_cfg.model
|
|
228
|
+
|
|
229
|
+
if not model_changed and not thinking_changed:
|
|
230
|
+
console.print(
|
|
231
|
+
f"[yellow]Already using {selected_display} "
|
|
232
|
+
f"with thinking {'on' if new_thinking else 'off'}.[/yellow]"
|
|
233
|
+
)
|
|
234
|
+
return
|
|
235
|
+
|
|
236
|
+
# Save and reload
|
|
237
|
+
prev_model = config.default_model
|
|
238
|
+
prev_thinking = config.default_thinking
|
|
239
|
+
config.default_model = selected_model_name
|
|
240
|
+
config.default_thinking = new_thinking
|
|
241
|
+
try:
|
|
242
|
+
config_for_save = load_config()
|
|
243
|
+
config_for_save.default_model = selected_model_name
|
|
244
|
+
config_for_save.default_thinking = new_thinking
|
|
245
|
+
save_config(config_for_save)
|
|
246
|
+
except (ConfigError, OSError) as exc:
|
|
247
|
+
config.default_model = prev_model
|
|
248
|
+
config.default_thinking = prev_thinking
|
|
249
|
+
console.print(f"[red]Failed to save config: {exc}[/red]")
|
|
250
|
+
return
|
|
251
|
+
|
|
252
|
+
from pythinker_code.telemetry import track
|
|
253
|
+
|
|
254
|
+
if model_changed:
|
|
255
|
+
track("model_switch", model=selected_model_name)
|
|
256
|
+
if thinking_changed:
|
|
257
|
+
track("thinking_toggle", enabled=new_thinking)
|
|
258
|
+
console.print(
|
|
259
|
+
f"[green]Switched to {selected_display} "
|
|
260
|
+
f"with thinking {'on' if new_thinking else 'off'}. "
|
|
261
|
+
"Reloading...[/green]"
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
# Pre-load LM Studio models so the user doesn't hit a 10-60s wait on
|
|
265
|
+
# the first message. Fire-and-forget on error — Reload still proceeds.
|
|
266
|
+
if model_changed and selected_model_cfg.provider == "managed:lm-studio":
|
|
267
|
+
await _preload_lm_studio_model(selected_provider, selected_model_cfg.model)
|
|
268
|
+
|
|
269
|
+
raise Reload(session_id=soul.runtime.session.id)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
_PROVIDER_LABEL_OVERRIDES = {
|
|
273
|
+
"managed:minimax-anthropic": "MiniMax",
|
|
274
|
+
"managed:opencode-go-openai": "OpenCode Go (OpenAI)",
|
|
275
|
+
"managed:opencode-go-anthropic": "OpenCode Go (Anthropic)",
|
|
276
|
+
"managed:deepseek": "DeepSeek",
|
|
277
|
+
"managed:anthropic": "Anthropic",
|
|
278
|
+
"managed:openrouter": "OpenRouter",
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _provider_label(provider_key: str) -> str:
|
|
283
|
+
if name := get_platform_name_for_provider(provider_key):
|
|
284
|
+
return name
|
|
285
|
+
if name := _PROVIDER_LABEL_OVERRIDES.get(provider_key):
|
|
286
|
+
return name
|
|
287
|
+
# Fall back: strip "managed:" prefix and humanize.
|
|
288
|
+
raw = provider_key.removeprefix("managed:")
|
|
289
|
+
return raw.replace("-", " ").title() if raw else provider_key
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
async def _preload_lm_studio_model(provider: Any, model_id: str) -> None:
|
|
293
|
+
"""Best-effort: ask LM Studio to load the model now."""
|
|
294
|
+
from rich.status import Status
|
|
295
|
+
|
|
296
|
+
from pythinker_code.auth.lm_studio import request_lm_studio_load
|
|
297
|
+
|
|
298
|
+
base_url = provider.base_url
|
|
299
|
+
api_key = provider.api_key.get_secret_value()
|
|
300
|
+
status_msg = f"Pre-loading {model_id} in LM Studio (this may take a moment)..."
|
|
301
|
+
status = Status(status_msg, console=console)
|
|
302
|
+
status.start()
|
|
303
|
+
try:
|
|
304
|
+
result = await request_lm_studio_load(base_url=base_url, model_id=model_id, api_key=api_key)
|
|
305
|
+
status.stop()
|
|
306
|
+
console.print(
|
|
307
|
+
f"[dim]LM Studio loaded {model_id} in "
|
|
308
|
+
f"{result.load_time_seconds:.1f}s (status={result.status}).[/dim]"
|
|
309
|
+
)
|
|
310
|
+
except Exception as exc:
|
|
311
|
+
status.stop()
|
|
312
|
+
console.print(
|
|
313
|
+
f"[yellow]LM Studio pre-load failed for {model_id}: {exc}[/yellow]\n"
|
|
314
|
+
"[dim]The chat will still try the model on first message.[/dim]"
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
@registry.command
|
|
319
|
+
@shell_mode_registry.command
|
|
320
|
+
async def editor(app: Shell, args: str):
|
|
321
|
+
"""Set default external editor for Ctrl-O"""
|
|
322
|
+
from pythinker_code.utils.editor import get_editor_command
|
|
323
|
+
|
|
324
|
+
soul = ensure_pythinker_soul(app)
|
|
325
|
+
if soul is None:
|
|
326
|
+
return
|
|
327
|
+
config = soul.runtime.config
|
|
328
|
+
config_file = config.source_file
|
|
329
|
+
if config_file is None:
|
|
330
|
+
console.print(
|
|
331
|
+
"[yellow]Editor switching is unavailable with inline --config; "
|
|
332
|
+
"use --config-file to persist this setting.[/yellow]"
|
|
333
|
+
)
|
|
334
|
+
return
|
|
335
|
+
|
|
336
|
+
current_editor = config.default_editor
|
|
337
|
+
|
|
338
|
+
# If args provided directly, use as editor command
|
|
339
|
+
if args.strip():
|
|
340
|
+
new_editor = args.strip()
|
|
341
|
+
else:
|
|
342
|
+
options: list[tuple[str, str]] = [
|
|
343
|
+
("code --wait", "VS Code (code --wait)"),
|
|
344
|
+
("vim", "Vim"),
|
|
345
|
+
("nano", "Nano"),
|
|
346
|
+
("", "Auto-detect (use $VISUAL/$EDITOR)"),
|
|
347
|
+
]
|
|
348
|
+
# Mark current selection
|
|
349
|
+
options = [
|
|
350
|
+
(val, label + (" ← current" if val == current_editor else "")) for val, label in options
|
|
351
|
+
]
|
|
352
|
+
|
|
353
|
+
try:
|
|
354
|
+
choice = cast(
|
|
355
|
+
str | None,
|
|
356
|
+
await ChoiceInput(
|
|
357
|
+
message="Select an editor (↑↓ navigate, Enter select, Ctrl+C cancel):",
|
|
358
|
+
options=options,
|
|
359
|
+
default=(
|
|
360
|
+
current_editor
|
|
361
|
+
if current_editor in {v for v, _ in options}
|
|
362
|
+
else "code --wait"
|
|
363
|
+
),
|
|
364
|
+
).prompt_async(),
|
|
365
|
+
)
|
|
366
|
+
except (EOFError, KeyboardInterrupt):
|
|
367
|
+
return
|
|
368
|
+
|
|
369
|
+
if choice is None:
|
|
370
|
+
return
|
|
371
|
+
new_editor = choice
|
|
372
|
+
|
|
373
|
+
# Validate the editor binary is available
|
|
374
|
+
if new_editor:
|
|
375
|
+
import shlex
|
|
376
|
+
import shutil
|
|
377
|
+
|
|
378
|
+
try:
|
|
379
|
+
parts = shlex.split(new_editor)
|
|
380
|
+
except ValueError:
|
|
381
|
+
console.print(f"[red]Invalid editor command: {new_editor}[/red]")
|
|
382
|
+
return
|
|
383
|
+
|
|
384
|
+
binary = parts[0]
|
|
385
|
+
if not shutil.which(binary):
|
|
386
|
+
console.print(
|
|
387
|
+
f"[yellow]Warning: '{binary}' not found in PATH. "
|
|
388
|
+
f"Saving anyway — make sure it's installed before using Ctrl-O.[/yellow]"
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
if new_editor == current_editor:
|
|
392
|
+
console.print(f"[yellow]Editor is already set to: {new_editor or 'auto-detect'}[/yellow]")
|
|
393
|
+
return
|
|
394
|
+
|
|
395
|
+
# Save to disk
|
|
396
|
+
try:
|
|
397
|
+
config_for_save = load_config(config_file)
|
|
398
|
+
config_for_save.default_editor = new_editor
|
|
399
|
+
save_config(config_for_save, config_file)
|
|
400
|
+
except (ConfigError, OSError) as exc:
|
|
401
|
+
console.print(f"[red]Failed to save config: {exc}[/red]")
|
|
402
|
+
return
|
|
403
|
+
|
|
404
|
+
# Sync in-memory config so Ctrl-O picks it up immediately
|
|
405
|
+
config.default_editor = new_editor
|
|
406
|
+
|
|
407
|
+
if new_editor:
|
|
408
|
+
console.print(f"[green]Editor set to: {new_editor}[/green]")
|
|
409
|
+
else:
|
|
410
|
+
resolved = get_editor_command()
|
|
411
|
+
label = " ".join(resolved) if resolved else "none"
|
|
412
|
+
console.print(f"[green]Editor set to auto-detect (resolved: {label})[/green]")
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
@registry.command(aliases=["release-notes"])
|
|
416
|
+
@shell_mode_registry.command(aliases=["release-notes"])
|
|
417
|
+
def changelog(app: Shell, args: str):
|
|
418
|
+
"""Show release notes"""
|
|
419
|
+
from rich.console import Group, RenderableType
|
|
420
|
+
from rich.text import Text
|
|
421
|
+
|
|
422
|
+
from pythinker_code.utils.rich.columns import BulletColumns
|
|
423
|
+
|
|
424
|
+
renderables: list[RenderableType] = []
|
|
425
|
+
for ver, entry in CHANGELOG.items():
|
|
426
|
+
title = f"[bold]{ver}[/bold]"
|
|
427
|
+
if entry.description:
|
|
428
|
+
title += f": {entry.description}"
|
|
429
|
+
|
|
430
|
+
lines: list[RenderableType] = [Text.from_markup(title)]
|
|
431
|
+
for item in entry.entries:
|
|
432
|
+
if item.lower().startswith("lib:"):
|
|
433
|
+
continue
|
|
434
|
+
lines.append(
|
|
435
|
+
BulletColumns(
|
|
436
|
+
Text.from_markup(f"[grey50]{item}[/grey50]"),
|
|
437
|
+
bullet_style="grey50",
|
|
438
|
+
),
|
|
439
|
+
)
|
|
440
|
+
renderables.append(BulletColumns(Group(*lines)))
|
|
441
|
+
|
|
442
|
+
with console.pager(styles=True):
|
|
443
|
+
console.print(Group(*renderables))
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
@registry.command
|
|
447
|
+
@shell_mode_registry.command
|
|
448
|
+
async def feedback(app: Shell, args: str):
|
|
449
|
+
"""Submit feedback to make Pythinker CLI better"""
|
|
450
|
+
import platform
|
|
451
|
+
import webbrowser
|
|
452
|
+
|
|
453
|
+
import aiohttp
|
|
454
|
+
|
|
455
|
+
from pythinker_code.auth import PYTHINKER_CODE_PLATFORM_ID
|
|
456
|
+
from pythinker_code.auth.platforms import get_platform_by_id, managed_provider_key
|
|
457
|
+
from pythinker_code.constant import VERSION
|
|
458
|
+
from pythinker_code.ui.shell.oauth import current_model_key
|
|
459
|
+
from pythinker_code.utils.aiohttp import new_client_session
|
|
460
|
+
|
|
461
|
+
ISSUE_URL = "https://github.com/mohamed-elkholy95/Pythinker-Code/issues"
|
|
462
|
+
|
|
463
|
+
def _fallback_to_issues():
|
|
464
|
+
if not webbrowser.open(ISSUE_URL):
|
|
465
|
+
console.print(f"Please submit feedback at [underline]{ISSUE_URL}[/underline].")
|
|
466
|
+
|
|
467
|
+
soul = ensure_pythinker_soul(app)
|
|
468
|
+
if soul is None:
|
|
469
|
+
_fallback_to_issues()
|
|
470
|
+
return
|
|
471
|
+
|
|
472
|
+
pythinker_platform = get_platform_by_id(PYTHINKER_CODE_PLATFORM_ID)
|
|
473
|
+
if pythinker_platform is None:
|
|
474
|
+
_fallback_to_issues()
|
|
475
|
+
return
|
|
476
|
+
|
|
477
|
+
provider = soul.runtime.config.providers.get(managed_provider_key(PYTHINKER_CODE_PLATFORM_ID))
|
|
478
|
+
if provider is None or provider.oauth is None:
|
|
479
|
+
_fallback_to_issues()
|
|
480
|
+
return
|
|
481
|
+
|
|
482
|
+
from prompt_toolkit import PromptSession
|
|
483
|
+
|
|
484
|
+
prompt_session: PromptSession[str] = PromptSession()
|
|
485
|
+
try:
|
|
486
|
+
content = await prompt_session.prompt_async("Enter your feedback: ")
|
|
487
|
+
except (EOFError, KeyboardInterrupt):
|
|
488
|
+
console.print("[grey50]Feedback cancelled.[/grey50]")
|
|
489
|
+
return
|
|
490
|
+
|
|
491
|
+
content = content.strip()
|
|
492
|
+
if not content:
|
|
493
|
+
console.print("[yellow]Feedback cannot be empty.[/yellow]")
|
|
494
|
+
return
|
|
495
|
+
|
|
496
|
+
api_key = soul.runtime.oauth.resolve_api_key(provider.api_key, provider.oauth)
|
|
497
|
+
feedback_url = f"{pythinker_platform.base_url.rstrip('/')}/feedback"
|
|
498
|
+
|
|
499
|
+
payload = {
|
|
500
|
+
"session_id": soul.runtime.session.id,
|
|
501
|
+
"content": content,
|
|
502
|
+
"version": VERSION,
|
|
503
|
+
"os": f"{platform.system()} {platform.release()}",
|
|
504
|
+
"model": current_model_key(soul),
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
with console.status("[cyan]Submitting feedback...[/cyan]"):
|
|
508
|
+
try:
|
|
509
|
+
async with (
|
|
510
|
+
new_client_session() as session,
|
|
511
|
+
session.post(
|
|
512
|
+
feedback_url,
|
|
513
|
+
json=payload,
|
|
514
|
+
headers={
|
|
515
|
+
"Authorization": f"Bearer {api_key}",
|
|
516
|
+
**(provider.custom_headers or {}),
|
|
517
|
+
},
|
|
518
|
+
raise_for_status=True,
|
|
519
|
+
),
|
|
520
|
+
):
|
|
521
|
+
pass
|
|
522
|
+
session_id = soul.runtime.session.id
|
|
523
|
+
from pythinker_code.telemetry import track
|
|
524
|
+
|
|
525
|
+
track("feedback_submitted")
|
|
526
|
+
console.print(
|
|
527
|
+
f"[green]Feedback submitted, thank you! Your session ID is: {session_id}[/green]"
|
|
528
|
+
)
|
|
529
|
+
except TimeoutError:
|
|
530
|
+
console.print("[red]Feedback submission timed out.[/red]")
|
|
531
|
+
_fallback_to_issues()
|
|
532
|
+
except aiohttp.ClientError as e:
|
|
533
|
+
status = getattr(e, "status", None)
|
|
534
|
+
if status:
|
|
535
|
+
msg = f"Failed to submit feedback (HTTP {status})."
|
|
536
|
+
else:
|
|
537
|
+
msg = "Network error, failed to submit feedback."
|
|
538
|
+
console.print(f"[red]{msg}[/red]")
|
|
539
|
+
_fallback_to_issues()
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
@registry.command(aliases=["reset"])
|
|
543
|
+
async def clear(app: Shell, args: str):
|
|
544
|
+
"""Clear the context"""
|
|
545
|
+
if ensure_pythinker_soul(app) is None:
|
|
546
|
+
return
|
|
547
|
+
from pythinker_code.telemetry import track
|
|
548
|
+
|
|
549
|
+
track("clear")
|
|
550
|
+
await app.run_soul_command("/clear")
|
|
551
|
+
raise Reload()
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
@registry.command
|
|
555
|
+
async def new(app: Shell, args: str):
|
|
556
|
+
"""Start a new session"""
|
|
557
|
+
soul = ensure_pythinker_soul(app)
|
|
558
|
+
if soul is None:
|
|
559
|
+
return
|
|
560
|
+
current_session = soul.runtime.session
|
|
561
|
+
work_dir = current_session.work_dir
|
|
562
|
+
# Clean up the current session if it has no content, so that chaining
|
|
563
|
+
# /new commands (or switching away before the first message) does not
|
|
564
|
+
# leave orphan empty session directories on disk.
|
|
565
|
+
if current_session.is_empty():
|
|
566
|
+
await current_session.delete()
|
|
567
|
+
session = await Session.create(work_dir)
|
|
568
|
+
from pythinker_code.telemetry import track
|
|
569
|
+
|
|
570
|
+
track("session_new")
|
|
571
|
+
console.print("[green]New session created. Switching...[/green]")
|
|
572
|
+
raise Reload(session_id=session.id)
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
@registry.command(name="title", aliases=["rename"])
|
|
576
|
+
async def title(app: Shell, args: str):
|
|
577
|
+
"""Set or show the session title"""
|
|
578
|
+
soul = ensure_pythinker_soul(app)
|
|
579
|
+
if soul is None:
|
|
580
|
+
return
|
|
581
|
+
session = soul.runtime.session
|
|
582
|
+
if not args.strip():
|
|
583
|
+
console.print(f"Session title: [bold]{session.title}[/bold]")
|
|
584
|
+
return
|
|
585
|
+
|
|
586
|
+
from pythinker_code.session_state import load_session_state, save_session_state
|
|
587
|
+
|
|
588
|
+
new_title = args.strip()[:200]
|
|
589
|
+
# Read-modify-write: load fresh state to avoid overwriting concurrent web changes
|
|
590
|
+
fresh = load_session_state(session.dir)
|
|
591
|
+
fresh.custom_title = new_title
|
|
592
|
+
fresh.title_generated = True
|
|
593
|
+
save_session_state(fresh, session.dir)
|
|
594
|
+
session.state.custom_title = new_title
|
|
595
|
+
session.state.title_generated = True
|
|
596
|
+
session.title = new_title
|
|
597
|
+
console.print(f"[green]Session title set to: {new_title}[/green]")
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
@registry.command(name="sessions", aliases=["resume"])
|
|
601
|
+
async def list_sessions(app: Shell, args: str):
|
|
602
|
+
"""List sessions and resume optionally"""
|
|
603
|
+
import shlex
|
|
604
|
+
|
|
605
|
+
from pythinker_code.ui.shell.session_picker import SessionPickerApp
|
|
606
|
+
|
|
607
|
+
soul = ensure_pythinker_soul(app)
|
|
608
|
+
if soul is None:
|
|
609
|
+
return
|
|
610
|
+
|
|
611
|
+
current_session = soul.runtime.session
|
|
612
|
+
result = await SessionPickerApp(
|
|
613
|
+
work_dir=current_session.work_dir,
|
|
614
|
+
current_session=current_session,
|
|
615
|
+
).run()
|
|
616
|
+
|
|
617
|
+
if result is None:
|
|
618
|
+
return
|
|
619
|
+
|
|
620
|
+
selection, selected_work_dir = result
|
|
621
|
+
|
|
622
|
+
if selection == current_session.id:
|
|
623
|
+
console.print("[yellow]You are already in this session.[/yellow]")
|
|
624
|
+
return
|
|
625
|
+
|
|
626
|
+
if selected_work_dir != current_session.work_dir:
|
|
627
|
+
cmd = f"pythinker --work-dir {shlex.quote(str(selected_work_dir))} --session {selection}"
|
|
628
|
+
console.print(f"[yellow]Session is in a different directory. Run:[/yellow]\n {cmd}")
|
|
629
|
+
return
|
|
630
|
+
|
|
631
|
+
from pythinker_code.telemetry import track
|
|
632
|
+
|
|
633
|
+
track("session_resume")
|
|
634
|
+
console.print(f"[green]Switching to session {selection}...[/green]")
|
|
635
|
+
raise Reload(session_id=selection)
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
@registry.command(name="task")
|
|
639
|
+
@shell_mode_registry.command(name="task")
|
|
640
|
+
async def task(app: Shell, args: str):
|
|
641
|
+
"""Browse and manage background tasks"""
|
|
642
|
+
soul = ensure_pythinker_soul(app)
|
|
643
|
+
if soul is None:
|
|
644
|
+
return
|
|
645
|
+
if args.strip():
|
|
646
|
+
console.print('[yellow]Usage: "/task" opens the interactive task browser.[/yellow]')
|
|
647
|
+
return
|
|
648
|
+
if soul.runtime.role != "root":
|
|
649
|
+
console.print("[yellow]Background tasks are only available from the root agent.[/yellow]")
|
|
650
|
+
return
|
|
651
|
+
|
|
652
|
+
await TaskBrowserApp(soul).run()
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
@registry.command
|
|
656
|
+
@shell_mode_registry.command
|
|
657
|
+
def theme(app: Shell, args: str):
|
|
658
|
+
"""Switch terminal color theme (dark/light)"""
|
|
659
|
+
from pythinker_code.ui.theme import get_active_theme
|
|
660
|
+
|
|
661
|
+
soul = ensure_pythinker_soul(app)
|
|
662
|
+
if soul is None:
|
|
663
|
+
return
|
|
664
|
+
|
|
665
|
+
current = get_active_theme()
|
|
666
|
+
arg = args.strip().lower()
|
|
667
|
+
|
|
668
|
+
if not arg:
|
|
669
|
+
console.print(f"Current theme: [bold]{current}[/bold]")
|
|
670
|
+
console.print("[grey50]Usage: /theme dark | /theme light[/grey50]")
|
|
671
|
+
return
|
|
672
|
+
|
|
673
|
+
if arg not in ("dark", "light"):
|
|
674
|
+
console.print(f"[red]Unknown theme: {arg}. Use 'dark' or 'light'.[/red]")
|
|
675
|
+
return
|
|
676
|
+
|
|
677
|
+
if arg == current:
|
|
678
|
+
console.print(f"[yellow]Already using {arg} theme.[/yellow]")
|
|
679
|
+
return
|
|
680
|
+
|
|
681
|
+
config_file = soul.runtime.config.source_file
|
|
682
|
+
if config_file is None:
|
|
683
|
+
console.print(
|
|
684
|
+
"[yellow]Theme switching requires a config file; "
|
|
685
|
+
"restart without --config to persist this setting.[/yellow]"
|
|
686
|
+
)
|
|
687
|
+
return
|
|
688
|
+
|
|
689
|
+
# Persist to disk first — only update in-memory state after success
|
|
690
|
+
try:
|
|
691
|
+
config_for_save = load_config(config_file)
|
|
692
|
+
config_for_save.theme = arg # type: ignore[assignment]
|
|
693
|
+
save_config(config_for_save, config_file)
|
|
694
|
+
except (ConfigError, OSError) as exc:
|
|
695
|
+
console.print(f"[red]Failed to save config: {exc}[/red]")
|
|
696
|
+
return
|
|
697
|
+
|
|
698
|
+
from pythinker_code.telemetry import track
|
|
699
|
+
|
|
700
|
+
track("theme_switch", theme=arg)
|
|
701
|
+
console.print(f"[green]Switched to {arg} theme. Reloading...[/green]")
|
|
702
|
+
raise Reload(session_id=soul.runtime.session.id)
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
@registry.command
|
|
706
|
+
def web(app: Shell, args: str):
|
|
707
|
+
"""Open Pythinker Web UI in browser"""
|
|
708
|
+
from pythinker_code.telemetry import track
|
|
709
|
+
|
|
710
|
+
track("web_opened")
|
|
711
|
+
soul = ensure_pythinker_soul(app)
|
|
712
|
+
session_id = soul.runtime.session.id if soul else None
|
|
713
|
+
raise SwitchToWeb(session_id=session_id)
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
@registry.command
|
|
717
|
+
def vis(app: Shell, args: str):
|
|
718
|
+
"""Open Pythinker Agent Tracing Visualizer in browser"""
|
|
719
|
+
from pythinker_code.telemetry import track
|
|
720
|
+
|
|
721
|
+
track("vis_opened")
|
|
722
|
+
soul = ensure_pythinker_soul(app)
|
|
723
|
+
session_id = soul.runtime.session.id if soul else None
|
|
724
|
+
raise SwitchToVis(session_id=session_id)
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
@registry.command
|
|
728
|
+
async def mcp(app: Shell, args: str):
|
|
729
|
+
"""Show MCP servers and tools"""
|
|
730
|
+
from rich.live import Live
|
|
731
|
+
|
|
732
|
+
soul = ensure_pythinker_soul(app)
|
|
733
|
+
if soul is None:
|
|
734
|
+
return
|
|
735
|
+
await soul.start_background_mcp_loading()
|
|
736
|
+
snapshot = soul.status.mcp_status
|
|
737
|
+
if snapshot is None:
|
|
738
|
+
console.print("[yellow]No MCP servers configured.[/yellow]")
|
|
739
|
+
return
|
|
740
|
+
|
|
741
|
+
if not snapshot.loading:
|
|
742
|
+
console.print(render_mcp_console(snapshot))
|
|
743
|
+
return
|
|
744
|
+
|
|
745
|
+
with Live(
|
|
746
|
+
render_mcp_console(snapshot),
|
|
747
|
+
console=console,
|
|
748
|
+
refresh_per_second=8,
|
|
749
|
+
transient=False,
|
|
750
|
+
) as live:
|
|
751
|
+
while True:
|
|
752
|
+
snapshot = soul.status.mcp_status
|
|
753
|
+
if snapshot is None:
|
|
754
|
+
break
|
|
755
|
+
live.update(render_mcp_console(snapshot), refresh=True)
|
|
756
|
+
if not snapshot.loading:
|
|
757
|
+
break
|
|
758
|
+
await asyncio.sleep(0.125)
|
|
759
|
+
try:
|
|
760
|
+
await soul.wait_for_background_mcp_loading()
|
|
761
|
+
except Exception as e:
|
|
762
|
+
logger.debug("MCP loading completed with error while rendering /mcp: {error}", error=e)
|
|
763
|
+
snapshot = soul.status.mcp_status
|
|
764
|
+
if snapshot is not None:
|
|
765
|
+
live.update(render_mcp_console(snapshot), refresh=True)
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
@registry.command
|
|
769
|
+
@shell_mode_registry.command
|
|
770
|
+
def hooks(app: Shell, args: str):
|
|
771
|
+
"""List configured hooks"""
|
|
772
|
+
soul = ensure_pythinker_soul(app)
|
|
773
|
+
if soul is None:
|
|
774
|
+
return
|
|
775
|
+
|
|
776
|
+
engine = soul.hook_engine
|
|
777
|
+
if not engine.summary:
|
|
778
|
+
console.print(
|
|
779
|
+
"[yellow]No hooks configured. "
|
|
780
|
+
"Add [[hooks]] sections to your config.toml to set up hooks.[/yellow]"
|
|
781
|
+
)
|
|
782
|
+
return
|
|
783
|
+
|
|
784
|
+
console.print()
|
|
785
|
+
console.print("[bold]Configured Hooks:[/bold]")
|
|
786
|
+
console.print()
|
|
787
|
+
|
|
788
|
+
for event, entries in engine.details().items():
|
|
789
|
+
console.print(f" [cyan]{event}[/cyan]: {len(entries)} hook(s)")
|
|
790
|
+
for entry in entries:
|
|
791
|
+
source_tag = f" [dim]({entry['source']})[/dim]" if entry["source"] == "wire" else ""
|
|
792
|
+
console.print(f" [dim]{entry['matcher']}[/dim] {entry['command']}{source_tag}")
|
|
793
|
+
|
|
794
|
+
console.print()
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
@registry.command
|
|
798
|
+
async def undo(app: Shell, args: str):
|
|
799
|
+
"""Undo: fork the session at a previous turn and retry"""
|
|
800
|
+
from pythinker_code.session_fork import enumerate_turns, fork_session
|
|
801
|
+
from pythinker_code.utils.string import shorten
|
|
802
|
+
|
|
803
|
+
soul = ensure_pythinker_soul(app)
|
|
804
|
+
if soul is None:
|
|
805
|
+
return
|
|
806
|
+
|
|
807
|
+
session = soul.runtime.session
|
|
808
|
+
wire_path = session.dir / "wire.jsonl"
|
|
809
|
+
turns = enumerate_turns(wire_path)
|
|
810
|
+
|
|
811
|
+
if not turns:
|
|
812
|
+
console.print("[yellow]No turns found in this session.[/yellow]")
|
|
813
|
+
return
|
|
814
|
+
|
|
815
|
+
# Build choices: each turn's first line, truncated
|
|
816
|
+
choices: list[tuple[str, str]] = []
|
|
817
|
+
for turn in turns:
|
|
818
|
+
first_line = turn.user_text.split("\n", 1)[0]
|
|
819
|
+
label = shorten(first_line, width=80, placeholder="...")
|
|
820
|
+
choices.append((str(turn.index), f"[{turn.index}] {label}"))
|
|
821
|
+
|
|
822
|
+
try:
|
|
823
|
+
selected = await ChoiceInput(
|
|
824
|
+
message="Select a turn to undo (↑↓ navigate, Enter select, Ctrl+C cancel):",
|
|
825
|
+
options=choices,
|
|
826
|
+
default=choices[-1][0],
|
|
827
|
+
).prompt_async()
|
|
828
|
+
except (EOFError, KeyboardInterrupt):
|
|
829
|
+
return
|
|
830
|
+
|
|
831
|
+
turn_index = int(selected)
|
|
832
|
+
|
|
833
|
+
# The selected turn is the one we want to redo — fork includes turns *before* it
|
|
834
|
+
selected_turn = turns[turn_index]
|
|
835
|
+
user_text = selected_turn.user_text
|
|
836
|
+
|
|
837
|
+
if turn_index == 0:
|
|
838
|
+
# Fork with no history — just the user text
|
|
839
|
+
new_session = await Session.create(session.work_dir)
|
|
840
|
+
new_session_id = new_session.id
|
|
841
|
+
# Set title to match the convention used by fork_session
|
|
842
|
+
from pythinker_code.session_state import load_session_state, save_session_state
|
|
843
|
+
|
|
844
|
+
new_state = load_session_state(new_session.dir)
|
|
845
|
+
new_state.custom_title = f"Undo: {session.title}"
|
|
846
|
+
new_state.title_generated = True
|
|
847
|
+
save_session_state(new_state, new_session.dir)
|
|
848
|
+
else:
|
|
849
|
+
# Fork includes turns 0..turn_index-1
|
|
850
|
+
fork_turn_index = turn_index - 1
|
|
851
|
+
new_session_id = await fork_session(
|
|
852
|
+
source_session_dir=session.dir,
|
|
853
|
+
work_dir=session.work_dir,
|
|
854
|
+
turn_index=fork_turn_index,
|
|
855
|
+
title_prefix="Undo",
|
|
856
|
+
source_title=session.title,
|
|
857
|
+
)
|
|
858
|
+
|
|
859
|
+
from pythinker_code.telemetry import track
|
|
860
|
+
|
|
861
|
+
track("undo")
|
|
862
|
+
console.print(f"[green]Forked at turn {turn_index}. Switching to new session...[/green]")
|
|
863
|
+
raise Reload(session_id=new_session_id, prefill_text=user_text)
|
|
864
|
+
|
|
865
|
+
|
|
866
|
+
@registry.command
|
|
867
|
+
async def fork(app: Shell, args: str):
|
|
868
|
+
"""Fork the current session (copy all history to a new session)"""
|
|
869
|
+
from pythinker_code.session_fork import fork_session
|
|
870
|
+
|
|
871
|
+
soul = ensure_pythinker_soul(app)
|
|
872
|
+
if soul is None:
|
|
873
|
+
return
|
|
874
|
+
|
|
875
|
+
session = soul.runtime.session
|
|
876
|
+
new_session_id = await fork_session(
|
|
877
|
+
source_session_dir=session.dir,
|
|
878
|
+
work_dir=session.work_dir,
|
|
879
|
+
turn_index=None,
|
|
880
|
+
title_prefix="Fork",
|
|
881
|
+
source_title=session.title,
|
|
882
|
+
)
|
|
883
|
+
|
|
884
|
+
from pythinker_code.telemetry import track
|
|
885
|
+
|
|
886
|
+
track("session_fork")
|
|
887
|
+
console.print("[green]Session forked. Switching to new session...[/green]")
|
|
888
|
+
raise Reload(session_id=new_session_id)
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
from . import ( # noqa: E402
|
|
892
|
+
debug, # noqa: F401 # type: ignore[reportUnusedImport]
|
|
893
|
+
export_import, # noqa: F401 # type: ignore[reportUnusedImport]
|
|
894
|
+
oauth, # noqa: F401 # type: ignore[reportUnusedImport]
|
|
895
|
+
setup, # noqa: F401 # type: ignore[reportUnusedImport]
|
|
896
|
+
update, # noqa: F401 # type: ignore[reportUnusedImport]
|
|
897
|
+
usage, # noqa: F401 # type: ignore[reportUnusedImport]
|
|
898
|
+
)
|