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,349 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Annotated, Any, Literal
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
cli = typer.Typer(help="Manage MCP server configurations.")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_global_mcp_config_file() -> Path:
|
|
11
|
+
"""Get the global MCP config file path."""
|
|
12
|
+
from pythinker_code.share import get_share_dir
|
|
13
|
+
|
|
14
|
+
return get_share_dir() / "mcp.json"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _load_mcp_config() -> dict[str, Any]:
|
|
18
|
+
"""Load MCP config from global mcp config file."""
|
|
19
|
+
from fastmcp.mcp_config import MCPConfig
|
|
20
|
+
from pydantic import ValidationError
|
|
21
|
+
|
|
22
|
+
mcp_file = get_global_mcp_config_file()
|
|
23
|
+
if not mcp_file.exists():
|
|
24
|
+
return {"mcpServers": {}}
|
|
25
|
+
try:
|
|
26
|
+
config = json.loads(mcp_file.read_text(encoding="utf-8"))
|
|
27
|
+
except json.JSONDecodeError as e:
|
|
28
|
+
raise typer.BadParameter(f"Invalid JSON in MCP config file '{mcp_file}': {e}") from e
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
MCPConfig.model_validate(config)
|
|
32
|
+
except ValidationError as e:
|
|
33
|
+
raise typer.BadParameter(f"Invalid MCP config in '{mcp_file}': {e}") from e
|
|
34
|
+
|
|
35
|
+
return config
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _save_mcp_config(config: dict[str, Any]) -> None:
|
|
39
|
+
"""Save MCP config to default file."""
|
|
40
|
+
mcp_file = get_global_mcp_config_file()
|
|
41
|
+
mcp_file.write_text(json.dumps(config, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _get_mcp_server(name: str, *, require_remote: bool = False) -> dict[str, Any]:
|
|
45
|
+
"""Get MCP server config by name."""
|
|
46
|
+
config = _load_mcp_config()
|
|
47
|
+
servers = config.get("mcpServers", {})
|
|
48
|
+
if name not in servers:
|
|
49
|
+
typer.echo(f"MCP server '{name}' not found.", err=True)
|
|
50
|
+
raise typer.Exit(code=1)
|
|
51
|
+
server = servers[name]
|
|
52
|
+
if require_remote and "url" not in server:
|
|
53
|
+
typer.echo(f"MCP server '{name}' is not a remote server.", err=True)
|
|
54
|
+
raise typer.Exit(code=1)
|
|
55
|
+
return server
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _parse_key_value_pairs(
|
|
59
|
+
items: list[str], option_name: str, *, separator: str = "=", strip_whitespace: bool = False
|
|
60
|
+
) -> dict[str, str]:
|
|
61
|
+
"""Parse key/value pairs from CLI options."""
|
|
62
|
+
parsed: dict[str, str] = {}
|
|
63
|
+
for item in items:
|
|
64
|
+
if separator not in item:
|
|
65
|
+
typer.echo(
|
|
66
|
+
f"Invalid {option_name} format: {item} (expected KEY{separator}VALUE).",
|
|
67
|
+
err=True,
|
|
68
|
+
)
|
|
69
|
+
raise typer.Exit(code=1)
|
|
70
|
+
key, value = item.split(separator, 1)
|
|
71
|
+
if strip_whitespace:
|
|
72
|
+
key, value = key.strip(), value.strip()
|
|
73
|
+
if not key:
|
|
74
|
+
typer.echo(f"Invalid {option_name} format: {item} (empty key).", err=True)
|
|
75
|
+
raise typer.Exit(code=1)
|
|
76
|
+
parsed[key] = value
|
|
77
|
+
return parsed
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
Transport = Literal["stdio", "http"]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@cli.command(
|
|
84
|
+
"add",
|
|
85
|
+
epilog="""
|
|
86
|
+
Examples:\n
|
|
87
|
+
\n
|
|
88
|
+
# Add streamable HTTP server:\n
|
|
89
|
+
pythinker mcp add --transport http context7 https://mcp.context7.com/mcp --header \"CONTEXT7_API_KEY: ctx7sk-your-key\"\n
|
|
90
|
+
\n
|
|
91
|
+
# Add streamable HTTP server with OAuth authorization:\n
|
|
92
|
+
pythinker mcp add --transport http --auth oauth linear https://mcp.linear.app/mcp\n
|
|
93
|
+
\n
|
|
94
|
+
# Add stdio server:\n
|
|
95
|
+
pythinker mcp add --transport stdio chrome-devtools -- npx chrome-devtools-mcp@latest
|
|
96
|
+
""".strip(), # noqa: E501
|
|
97
|
+
)
|
|
98
|
+
def mcp_add(
|
|
99
|
+
name: Annotated[
|
|
100
|
+
str,
|
|
101
|
+
typer.Argument(help="Name of the MCP server to add."),
|
|
102
|
+
],
|
|
103
|
+
server_args: Annotated[
|
|
104
|
+
list[str] | None,
|
|
105
|
+
typer.Argument(
|
|
106
|
+
metavar="TARGET_OR_COMMAND...",
|
|
107
|
+
help="For http: server URL. For stdio: command to run (prefix with `--`).",
|
|
108
|
+
),
|
|
109
|
+
] = None,
|
|
110
|
+
transport: Annotated[
|
|
111
|
+
Transport,
|
|
112
|
+
typer.Option(
|
|
113
|
+
"--transport",
|
|
114
|
+
"-t",
|
|
115
|
+
help="Transport type for the MCP server. Default: stdio.",
|
|
116
|
+
),
|
|
117
|
+
] = "stdio",
|
|
118
|
+
env: Annotated[
|
|
119
|
+
list[str] | None,
|
|
120
|
+
typer.Option(
|
|
121
|
+
"--env",
|
|
122
|
+
"-e",
|
|
123
|
+
help="Environment variables in KEY=VALUE format. Can be specified multiple times.",
|
|
124
|
+
),
|
|
125
|
+
] = None,
|
|
126
|
+
header: Annotated[
|
|
127
|
+
list[str] | None,
|
|
128
|
+
typer.Option(
|
|
129
|
+
"--header",
|
|
130
|
+
"-H",
|
|
131
|
+
help="HTTP headers in KEY:VALUE format. Can be specified multiple times.",
|
|
132
|
+
),
|
|
133
|
+
] = None,
|
|
134
|
+
auth: Annotated[
|
|
135
|
+
str | None,
|
|
136
|
+
typer.Option(
|
|
137
|
+
"--auth",
|
|
138
|
+
"-a",
|
|
139
|
+
help="Authorization type (e.g., 'oauth').",
|
|
140
|
+
),
|
|
141
|
+
] = None,
|
|
142
|
+
):
|
|
143
|
+
"""Add an MCP server."""
|
|
144
|
+
config = _load_mcp_config()
|
|
145
|
+
server_args = server_args or []
|
|
146
|
+
|
|
147
|
+
if transport not in {"stdio", "http"}:
|
|
148
|
+
typer.echo(f"Unsupported transport: {transport}.", err=True)
|
|
149
|
+
raise typer.Exit(code=1)
|
|
150
|
+
|
|
151
|
+
if transport == "stdio":
|
|
152
|
+
if not server_args:
|
|
153
|
+
typer.echo(
|
|
154
|
+
"For stdio transport, provide the command to start the MCP server after `--`.",
|
|
155
|
+
err=True,
|
|
156
|
+
)
|
|
157
|
+
raise typer.Exit(code=1)
|
|
158
|
+
if header:
|
|
159
|
+
typer.echo("--header is only valid for http transport.", err=True)
|
|
160
|
+
raise typer.Exit(code=1)
|
|
161
|
+
if auth:
|
|
162
|
+
typer.echo("--auth is only valid for http transport.", err=True)
|
|
163
|
+
raise typer.Exit(code=1)
|
|
164
|
+
command, *command_args = server_args
|
|
165
|
+
server_config: dict[str, Any] = {"command": command, "args": command_args}
|
|
166
|
+
if env:
|
|
167
|
+
server_config["env"] = _parse_key_value_pairs(env, "env")
|
|
168
|
+
else:
|
|
169
|
+
if env:
|
|
170
|
+
typer.echo("--env is only supported for stdio transport.", err=True)
|
|
171
|
+
raise typer.Exit(code=1)
|
|
172
|
+
if not server_args:
|
|
173
|
+
typer.echo("URL is required for http transport.", err=True)
|
|
174
|
+
raise typer.Exit(code=1)
|
|
175
|
+
if len(server_args) > 1:
|
|
176
|
+
typer.echo(
|
|
177
|
+
"Multiple targets provided. Supply a single URL for http transport.",
|
|
178
|
+
err=True,
|
|
179
|
+
)
|
|
180
|
+
raise typer.Exit(code=1)
|
|
181
|
+
server_config = {"url": server_args[0], "transport": "http"}
|
|
182
|
+
if header:
|
|
183
|
+
server_config["headers"] = _parse_key_value_pairs(
|
|
184
|
+
header, "header", separator=":", strip_whitespace=True
|
|
185
|
+
)
|
|
186
|
+
if auth:
|
|
187
|
+
server_config["auth"] = auth
|
|
188
|
+
|
|
189
|
+
if "mcpServers" not in config:
|
|
190
|
+
config["mcpServers"] = {}
|
|
191
|
+
config["mcpServers"][name] = server_config
|
|
192
|
+
_save_mcp_config(config)
|
|
193
|
+
typer.echo(f"Added MCP server '{name}' to {get_global_mcp_config_file()}.")
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@cli.command("remove")
|
|
197
|
+
def mcp_remove(
|
|
198
|
+
name: Annotated[
|
|
199
|
+
str,
|
|
200
|
+
typer.Argument(help="Name of the MCP server to remove."),
|
|
201
|
+
],
|
|
202
|
+
):
|
|
203
|
+
"""Remove an MCP server."""
|
|
204
|
+
_get_mcp_server(name)
|
|
205
|
+
config = _load_mcp_config()
|
|
206
|
+
del config["mcpServers"][name]
|
|
207
|
+
_save_mcp_config(config)
|
|
208
|
+
typer.echo(f"Removed MCP server '{name}' from {get_global_mcp_config_file()}.")
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _has_oauth_tokens(server_url: str) -> bool:
|
|
212
|
+
"""Check if OAuth tokens exist for the server."""
|
|
213
|
+
import asyncio
|
|
214
|
+
|
|
215
|
+
async def _check() -> bool:
|
|
216
|
+
try:
|
|
217
|
+
from fastmcp.client.auth.oauth import FileTokenStorage
|
|
218
|
+
|
|
219
|
+
storage = FileTokenStorage(server_url=server_url)
|
|
220
|
+
tokens = await storage.get_tokens()
|
|
221
|
+
return tokens is not None
|
|
222
|
+
except Exception:
|
|
223
|
+
return False
|
|
224
|
+
|
|
225
|
+
return asyncio.run(_check())
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@cli.command("list")
|
|
229
|
+
def mcp_list():
|
|
230
|
+
"""List all MCP servers."""
|
|
231
|
+
config_file = get_global_mcp_config_file()
|
|
232
|
+
config = _load_mcp_config()
|
|
233
|
+
servers: dict[str, Any] = config.get("mcpServers", {})
|
|
234
|
+
|
|
235
|
+
typer.echo(f"MCP config file: {config_file}")
|
|
236
|
+
if not servers:
|
|
237
|
+
typer.echo("No MCP servers configured.")
|
|
238
|
+
return
|
|
239
|
+
|
|
240
|
+
for name, server in servers.items():
|
|
241
|
+
if "command" in server:
|
|
242
|
+
cmd = server["command"]
|
|
243
|
+
cmd_args = " ".join(server.get("args", []))
|
|
244
|
+
line = f"{name} (stdio): {cmd} {cmd_args}".rstrip()
|
|
245
|
+
elif "url" in server:
|
|
246
|
+
transport = server.get("transport") or "http"
|
|
247
|
+
if transport == "streamable-http":
|
|
248
|
+
transport = "http"
|
|
249
|
+
line = f"{name} ({transport}): {server['url']}"
|
|
250
|
+
if server.get("auth") == "oauth" and not _has_oauth_tokens(server["url"]):
|
|
251
|
+
line += " [authorization required - run: pythinker mcp auth " + name + "]"
|
|
252
|
+
else:
|
|
253
|
+
line = f"{name}: {server}"
|
|
254
|
+
typer.echo(f" {line}")
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@cli.command("auth")
|
|
258
|
+
def mcp_auth(
|
|
259
|
+
name: Annotated[
|
|
260
|
+
str,
|
|
261
|
+
typer.Argument(help="Name of the MCP server to authorize."),
|
|
262
|
+
],
|
|
263
|
+
):
|
|
264
|
+
"""Authorize with an OAuth-enabled MCP server."""
|
|
265
|
+
import asyncio
|
|
266
|
+
|
|
267
|
+
server = _get_mcp_server(name, require_remote=True)
|
|
268
|
+
if server.get("auth") != "oauth":
|
|
269
|
+
typer.echo(f"MCP server '{name}' does not use OAuth. Add with --auth oauth.", err=True)
|
|
270
|
+
raise typer.Exit(code=1)
|
|
271
|
+
|
|
272
|
+
async def _auth() -> None:
|
|
273
|
+
import fastmcp
|
|
274
|
+
|
|
275
|
+
typer.echo(f"Authorizing with '{name}'...")
|
|
276
|
+
typer.echo("A browser window will open for authorization.")
|
|
277
|
+
|
|
278
|
+
client = fastmcp.Client({"mcpServers": {name: server}})
|
|
279
|
+
try:
|
|
280
|
+
async with client:
|
|
281
|
+
tools = await client.list_tools()
|
|
282
|
+
typer.echo(f"Successfully authorized with '{name}'.")
|
|
283
|
+
typer.echo(f"Available tools: {len(tools)}")
|
|
284
|
+
except Exception as e:
|
|
285
|
+
typer.echo(f"Authorization failed: {type(e).__name__}: {e}", err=True)
|
|
286
|
+
raise typer.Exit(code=1) from None
|
|
287
|
+
|
|
288
|
+
asyncio.run(_auth())
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
@cli.command("reset-auth")
|
|
292
|
+
def mcp_reset_auth(
|
|
293
|
+
name: Annotated[
|
|
294
|
+
str,
|
|
295
|
+
typer.Argument(help="Name of the MCP server to reset authorization."),
|
|
296
|
+
],
|
|
297
|
+
):
|
|
298
|
+
"""Reset OAuth authorization for an MCP server (clear cached tokens)."""
|
|
299
|
+
server = _get_mcp_server(name, require_remote=True)
|
|
300
|
+
|
|
301
|
+
try:
|
|
302
|
+
from fastmcp.client.auth.oauth import FileTokenStorage
|
|
303
|
+
|
|
304
|
+
storage = FileTokenStorage(server_url=server["url"])
|
|
305
|
+
storage.clear()
|
|
306
|
+
typer.echo(f"OAuth tokens cleared for '{name}'.")
|
|
307
|
+
except ImportError:
|
|
308
|
+
typer.echo("OAuth support not available.", err=True)
|
|
309
|
+
raise typer.Exit(code=1) from None
|
|
310
|
+
except Exception as e:
|
|
311
|
+
typer.echo(f"Failed to clear tokens: {type(e).__name__}: {e}", err=True)
|
|
312
|
+
raise typer.Exit(code=1) from None
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
@cli.command("test")
|
|
316
|
+
def mcp_test(
|
|
317
|
+
name: Annotated[
|
|
318
|
+
str,
|
|
319
|
+
typer.Argument(help="Name of the MCP server to test."),
|
|
320
|
+
],
|
|
321
|
+
):
|
|
322
|
+
"""Test connection to an MCP server and list available tools."""
|
|
323
|
+
import asyncio
|
|
324
|
+
|
|
325
|
+
server = _get_mcp_server(name)
|
|
326
|
+
|
|
327
|
+
async def _test() -> None:
|
|
328
|
+
import fastmcp
|
|
329
|
+
|
|
330
|
+
typer.echo(f"Testing connection to '{name}'...")
|
|
331
|
+
client = fastmcp.Client({"mcpServers": {name: server}})
|
|
332
|
+
|
|
333
|
+
try:
|
|
334
|
+
async with client:
|
|
335
|
+
tools = await client.list_tools()
|
|
336
|
+
typer.echo(f"✓ Connected to '{name}'")
|
|
337
|
+
typer.echo(f" Available tools: {len(tools)}")
|
|
338
|
+
if tools:
|
|
339
|
+
typer.echo(" Tools:")
|
|
340
|
+
for tool in tools:
|
|
341
|
+
desc = tool.description or ""
|
|
342
|
+
if len(desc) > 50:
|
|
343
|
+
desc = desc[:47] + "..."
|
|
344
|
+
typer.echo(f" - {tool.name}: {desc}")
|
|
345
|
+
except Exception as e:
|
|
346
|
+
typer.echo(f"✗ Connection failed: {type(e).__name__}: {e}", err=True)
|
|
347
|
+
raise typer.Exit(code=1) from None
|
|
348
|
+
|
|
349
|
+
asyncio.run(_test())
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"""CLI commands for plugin management."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from pythinker_code.plugin import PluginError
|
|
11
|
+
|
|
12
|
+
cli = typer.Typer(help="Manage plugins.")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _parse_git_url(target: str) -> tuple[str, str | None, str | None]:
|
|
16
|
+
"""Parse a git URL into (clone_url, subpath, branch).
|
|
17
|
+
|
|
18
|
+
Splits .git URLs at the .git boundary. For GitHub/GitLab short URLs,
|
|
19
|
+
treats the first two path segments as owner/repo and the rest as subpath.
|
|
20
|
+
Strips ``tree/{branch}/`` or ``-/tree/{branch}/`` prefixes from
|
|
21
|
+
browser-copied URLs and returns the branch name.
|
|
22
|
+
"""
|
|
23
|
+
# Path 1: URL contains .git followed by / or end-of-string
|
|
24
|
+
idx = target.find(".git/")
|
|
25
|
+
if idx == -1 and target.endswith(".git"):
|
|
26
|
+
return target, None, None
|
|
27
|
+
if idx != -1:
|
|
28
|
+
clone_url = target[: idx + 4] # up to and including ".git"
|
|
29
|
+
rest = target[idx + 5 :] # after ".git/"
|
|
30
|
+
subpath = rest.strip("/") or None
|
|
31
|
+
return clone_url, subpath, None
|
|
32
|
+
|
|
33
|
+
# Path 2: GitHub/GitLab short URL (no .git)
|
|
34
|
+
from urllib.parse import urlparse
|
|
35
|
+
|
|
36
|
+
parsed = urlparse(target)
|
|
37
|
+
segments = [s for s in parsed.path.split("/") if s]
|
|
38
|
+
if len(segments) < 2:
|
|
39
|
+
return target, None, None
|
|
40
|
+
|
|
41
|
+
owner_repo = "/".join(segments[:2])
|
|
42
|
+
clone_url = f"{parsed.scheme}://{parsed.netloc}/{owner_repo}"
|
|
43
|
+
rest_segments = segments[2:]
|
|
44
|
+
|
|
45
|
+
# GitLab uses /-/tree/{branch}/, strip leading "-"
|
|
46
|
+
if rest_segments and rest_segments[0] == "-":
|
|
47
|
+
rest_segments = rest_segments[1:]
|
|
48
|
+
|
|
49
|
+
# Strip tree/{branch}/ prefix and extract branch
|
|
50
|
+
branch: str | None = None
|
|
51
|
+
if len(rest_segments) >= 2 and rest_segments[0] == "tree":
|
|
52
|
+
branch = rest_segments[1]
|
|
53
|
+
rest_segments = rest_segments[2:]
|
|
54
|
+
|
|
55
|
+
subpath = "/".join(rest_segments) or None
|
|
56
|
+
return clone_url, subpath, branch
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _extract_zip_to_plugin(zip_path: Path, tmp: Path) -> tuple[Path, Path]:
|
|
60
|
+
"""Extract zip_path into tmp and locate the plugin directory.
|
|
61
|
+
|
|
62
|
+
Returns ``(plugin_dir, tmp)`` for cleanup by the caller. Rejects zip
|
|
63
|
+
members whose paths escape ``tmp``. Searches the extraction root and
|
|
64
|
+
one level deep for ``plugin.json``.
|
|
65
|
+
"""
|
|
66
|
+
import shutil
|
|
67
|
+
import zipfile
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
with zipfile.ZipFile(zip_path, "r") as zf:
|
|
71
|
+
for member in zf.namelist():
|
|
72
|
+
member_path = (tmp / member).resolve()
|
|
73
|
+
if not member_path.is_relative_to(tmp.resolve()):
|
|
74
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
75
|
+
typer.echo(f"Error: zip contains unsafe path: {member}", err=True)
|
|
76
|
+
raise typer.Exit(1)
|
|
77
|
+
zf.extractall(tmp)
|
|
78
|
+
except zipfile.BadZipFile as exc:
|
|
79
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
80
|
+
typer.echo(f"Error: invalid zip archive: {exc}", err=True)
|
|
81
|
+
raise typer.Exit(1) from exc
|
|
82
|
+
|
|
83
|
+
for candidate in [tmp] + sorted(tmp.iterdir()):
|
|
84
|
+
if candidate.is_dir() and (candidate / "plugin.json").exists():
|
|
85
|
+
return candidate, tmp
|
|
86
|
+
dirs = [d for d in tmp.iterdir() if d.is_dir() and not d.name.startswith("_")]
|
|
87
|
+
if len(dirs) == 1 and (dirs[0] / "plugin.json").exists():
|
|
88
|
+
return dirs[0], tmp
|
|
89
|
+
|
|
90
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
91
|
+
typer.echo("Error: No plugin.json found in zip", err=True)
|
|
92
|
+
raise typer.Exit(1)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _resolve_source(target: str) -> tuple[Path, Path | None]:
|
|
96
|
+
"""Resolve plugin source to (local_dir, tmp_to_cleanup).
|
|
97
|
+
|
|
98
|
+
Returns the source directory and an optional temp directory that
|
|
99
|
+
the caller must clean up after use.
|
|
100
|
+
"""
|
|
101
|
+
import shutil
|
|
102
|
+
import tempfile
|
|
103
|
+
from urllib.parse import urlparse
|
|
104
|
+
|
|
105
|
+
# HTTP(S) URL pointing to a .zip — download then extract.
|
|
106
|
+
# Checked before the git-URL branch so GitHub/GitLab archive links
|
|
107
|
+
# like .../archive/refs/heads/main.zip take this path.
|
|
108
|
+
parsed = urlparse(target)
|
|
109
|
+
if parsed.scheme in ("http", "https") and parsed.path.lower().endswith(".zip"):
|
|
110
|
+
import httpx
|
|
111
|
+
|
|
112
|
+
tmp = Path(tempfile.mkdtemp(prefix="pythinker-plugin-"))
|
|
113
|
+
zip_path = tmp / "_download.zip"
|
|
114
|
+
typer.echo(f"Downloading {target}...")
|
|
115
|
+
try:
|
|
116
|
+
with httpx.stream("GET", target, follow_redirects=True, timeout=60.0) as resp:
|
|
117
|
+
resp.raise_for_status()
|
|
118
|
+
with zip_path.open("wb") as f:
|
|
119
|
+
for chunk in resp.iter_bytes():
|
|
120
|
+
f.write(chunk)
|
|
121
|
+
except httpx.HTTPError as exc:
|
|
122
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
123
|
+
typer.echo(f"Error: download failed: {exc}", err=True)
|
|
124
|
+
raise typer.Exit(1) from exc
|
|
125
|
+
|
|
126
|
+
return _extract_zip_to_plugin(zip_path, tmp)
|
|
127
|
+
|
|
128
|
+
# Git URL
|
|
129
|
+
if target.startswith(("https://", "git@", "http://")) and (
|
|
130
|
+
".git/" in target
|
|
131
|
+
or target.endswith(".git")
|
|
132
|
+
or "github.com/" in target
|
|
133
|
+
or "gitlab.com/" in target
|
|
134
|
+
):
|
|
135
|
+
import subprocess
|
|
136
|
+
|
|
137
|
+
clone_url, subpath, branch = _parse_git_url(target)
|
|
138
|
+
|
|
139
|
+
tmp = Path(tempfile.mkdtemp(prefix="pythinker-plugin-"))
|
|
140
|
+
typer.echo(f"Cloning {clone_url}...")
|
|
141
|
+
clone_cmd = ["git", "clone", "--depth", "1"]
|
|
142
|
+
if branch:
|
|
143
|
+
clone_cmd += ["--branch", branch]
|
|
144
|
+
clone_cmd += [clone_url, str(tmp / "repo")]
|
|
145
|
+
result = subprocess.run(
|
|
146
|
+
clone_cmd,
|
|
147
|
+
capture_output=True,
|
|
148
|
+
text=True,
|
|
149
|
+
encoding="utf-8",
|
|
150
|
+
errors="replace",
|
|
151
|
+
)
|
|
152
|
+
if result.returncode != 0:
|
|
153
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
154
|
+
typer.echo(
|
|
155
|
+
f"Error: git clone failed: {result.stderr.strip()}",
|
|
156
|
+
err=True,
|
|
157
|
+
)
|
|
158
|
+
raise typer.Exit(1)
|
|
159
|
+
|
|
160
|
+
repo_root = tmp / "repo"
|
|
161
|
+
|
|
162
|
+
if subpath:
|
|
163
|
+
source = (repo_root / subpath).resolve()
|
|
164
|
+
if not source.is_relative_to(repo_root.resolve()):
|
|
165
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
166
|
+
typer.echo(
|
|
167
|
+
f"Error: subpath escapes repository: {subpath}",
|
|
168
|
+
err=True,
|
|
169
|
+
)
|
|
170
|
+
raise typer.Exit(1)
|
|
171
|
+
if not source.is_dir():
|
|
172
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
173
|
+
typer.echo(
|
|
174
|
+
f"Error: subpath '{subpath}' not found in repository",
|
|
175
|
+
err=True,
|
|
176
|
+
)
|
|
177
|
+
raise typer.Exit(1)
|
|
178
|
+
if not (source / "plugin.json").exists():
|
|
179
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
180
|
+
typer.echo(
|
|
181
|
+
f"Error: no plugin.json in '{subpath}'",
|
|
182
|
+
err=True,
|
|
183
|
+
)
|
|
184
|
+
raise typer.Exit(1)
|
|
185
|
+
return source, tmp
|
|
186
|
+
|
|
187
|
+
# No subpath — check root first
|
|
188
|
+
if (repo_root / "plugin.json").exists():
|
|
189
|
+
return repo_root, tmp
|
|
190
|
+
|
|
191
|
+
# Scan one level for available plugins
|
|
192
|
+
available = sorted(
|
|
193
|
+
d.name for d in repo_root.iterdir() if d.is_dir() and (d / "plugin.json").exists()
|
|
194
|
+
)
|
|
195
|
+
if available:
|
|
196
|
+
names = "\n".join(f" - {n}" for n in available)
|
|
197
|
+
typer.echo(
|
|
198
|
+
f"Error: No plugin.json at repository root. "
|
|
199
|
+
f"Available plugins:\n{names}\n"
|
|
200
|
+
f"Use: pythinker plugin install <url>/<plugin-name>",
|
|
201
|
+
err=True,
|
|
202
|
+
)
|
|
203
|
+
else:
|
|
204
|
+
typer.echo(
|
|
205
|
+
"Error: No plugin.json found in repository",
|
|
206
|
+
err=True,
|
|
207
|
+
)
|
|
208
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
209
|
+
raise typer.Exit(1)
|
|
210
|
+
|
|
211
|
+
p = Path(target).expanduser().resolve()
|
|
212
|
+
|
|
213
|
+
# Zip file
|
|
214
|
+
if p.is_file() and p.suffix == ".zip":
|
|
215
|
+
tmp = Path(tempfile.mkdtemp(prefix="pythinker-plugin-"))
|
|
216
|
+
typer.echo(f"Extracting {p.name}...")
|
|
217
|
+
return _extract_zip_to_plugin(p, tmp)
|
|
218
|
+
|
|
219
|
+
# Local directory
|
|
220
|
+
if p.is_dir():
|
|
221
|
+
return p, None
|
|
222
|
+
|
|
223
|
+
typer.echo(
|
|
224
|
+
f"Error: {target} is not a directory, zip file, zip URL, or git URL",
|
|
225
|
+
err=True,
|
|
226
|
+
)
|
|
227
|
+
raise typer.Exit(1)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@cli.command("install")
|
|
231
|
+
def install_cmd(
|
|
232
|
+
target: Annotated[
|
|
233
|
+
str,
|
|
234
|
+
typer.Argument(help="Plugin source: directory, .zip file, .zip URL, or git URL"),
|
|
235
|
+
],
|
|
236
|
+
) -> None:
|
|
237
|
+
"""Install a plugin and inject host configuration."""
|
|
238
|
+
import shutil
|
|
239
|
+
|
|
240
|
+
from pythinker_code.config import load_config
|
|
241
|
+
from pythinker_code.constant import VERSION
|
|
242
|
+
from pythinker_code.plugin.manager import get_plugins_dir, install_plugin
|
|
243
|
+
|
|
244
|
+
source, tmp_dir = _resolve_source(target)
|
|
245
|
+
|
|
246
|
+
try:
|
|
247
|
+
config = load_config()
|
|
248
|
+
|
|
249
|
+
from pythinker_code.auth.oauth import OAuthManager
|
|
250
|
+
from pythinker_code.llm import augment_provider_with_env_vars
|
|
251
|
+
from pythinker_code.plugin.manager import collect_host_values
|
|
252
|
+
|
|
253
|
+
# Apply env var overrides (install runs outside normal startup)
|
|
254
|
+
if config.default_model and config.default_model in config.models:
|
|
255
|
+
model = config.models[config.default_model]
|
|
256
|
+
if model.provider in config.providers:
|
|
257
|
+
augment_provider_with_env_vars(
|
|
258
|
+
config.providers[model.provider], model, provider_key=model.provider
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
oauth = OAuthManager(config)
|
|
262
|
+
host_values = collect_host_values(config, oauth)
|
|
263
|
+
|
|
264
|
+
if not host_values.get("api_key"):
|
|
265
|
+
typer.echo(
|
|
266
|
+
"Warning: No LLM provider configured. "
|
|
267
|
+
"Plugins requiring API key injection will fail. "
|
|
268
|
+
"Run 'pythinker login' or configure a provider first.",
|
|
269
|
+
err=True,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
spec = install_plugin(
|
|
273
|
+
source=source,
|
|
274
|
+
plugins_dir=get_plugins_dir(),
|
|
275
|
+
host_values=host_values,
|
|
276
|
+
host_name="pythinker-code",
|
|
277
|
+
host_version=VERSION,
|
|
278
|
+
)
|
|
279
|
+
except PluginError as exc:
|
|
280
|
+
typer.echo(f"Error: {exc}", err=True)
|
|
281
|
+
raise typer.Exit(1) from exc
|
|
282
|
+
finally:
|
|
283
|
+
# Clean up temp directory from zip/git extraction
|
|
284
|
+
if tmp_dir is not None:
|
|
285
|
+
shutil.rmtree(tmp_dir, ignore_errors=True)
|
|
286
|
+
|
|
287
|
+
typer.echo(f"Installed plugin '{spec.name}' v{spec.version}")
|
|
288
|
+
if spec.runtime:
|
|
289
|
+
typer.echo(f" runtime: host={spec.runtime.host}, version={spec.runtime.host_version}")
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
@cli.command("list")
|
|
293
|
+
def list_cmd() -> None:
|
|
294
|
+
"""List installed plugins."""
|
|
295
|
+
from pythinker_code.plugin.manager import get_plugins_dir, list_plugins
|
|
296
|
+
|
|
297
|
+
plugins = list_plugins(get_plugins_dir())
|
|
298
|
+
if not plugins:
|
|
299
|
+
typer.echo("No plugins installed.")
|
|
300
|
+
return
|
|
301
|
+
|
|
302
|
+
for p in plugins:
|
|
303
|
+
status = "installed" if p.runtime else "not configured"
|
|
304
|
+
typer.echo(f" {p.name} v{p.version} ({status})")
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
@cli.command("remove")
|
|
308
|
+
def remove_cmd(
|
|
309
|
+
name: Annotated[str, typer.Argument(help="Plugin name to remove")],
|
|
310
|
+
) -> None:
|
|
311
|
+
"""Remove an installed plugin."""
|
|
312
|
+
from pythinker_code.plugin.manager import get_plugins_dir, remove_plugin
|
|
313
|
+
|
|
314
|
+
try:
|
|
315
|
+
remove_plugin(name, get_plugins_dir())
|
|
316
|
+
except PluginError as exc:
|
|
317
|
+
typer.echo(f"Error: {exc}", err=True)
|
|
318
|
+
raise typer.Exit(1) from exc
|
|
319
|
+
|
|
320
|
+
typer.echo(f"Removed plugin '{name}'")
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
@cli.command("info")
|
|
324
|
+
def info_cmd(
|
|
325
|
+
name: Annotated[str, typer.Argument(help="Plugin name")],
|
|
326
|
+
) -> None:
|
|
327
|
+
"""Show plugin details."""
|
|
328
|
+
from pythinker_code.plugin import parse_plugin_json
|
|
329
|
+
from pythinker_code.plugin.manager import get_plugins_dir
|
|
330
|
+
|
|
331
|
+
plugin_json = get_plugins_dir() / name / "plugin.json"
|
|
332
|
+
if not plugin_json.exists():
|
|
333
|
+
typer.echo(f"Error: Plugin '{name}' not found", err=True)
|
|
334
|
+
raise typer.Exit(1)
|
|
335
|
+
|
|
336
|
+
try:
|
|
337
|
+
spec = parse_plugin_json(plugin_json)
|
|
338
|
+
except PluginError as exc:
|
|
339
|
+
typer.echo(f"Error: {exc}", err=True)
|
|
340
|
+
raise typer.Exit(1) from exc
|
|
341
|
+
|
|
342
|
+
typer.echo(f"Name: {spec.name}")
|
|
343
|
+
typer.echo(f"Version: {spec.version}")
|
|
344
|
+
typer.echo(f"Description: {spec.description or '(none)'}")
|
|
345
|
+
typer.echo(f"Config file: {spec.config_file or '(none)'}")
|
|
346
|
+
if spec.inject:
|
|
347
|
+
typer.echo(f"Inject: {', '.join(f'{k} <- {v}' for k, v in spec.inject.items())}")
|
|
348
|
+
if spec.runtime:
|
|
349
|
+
typer.echo(f"Runtime: host={spec.runtime.host}, version={spec.runtime.host_version}")
|
|
350
|
+
else:
|
|
351
|
+
typer.echo("Runtime: (not installed via host)")
|