pythinker-code 0.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pythinker_code/CHANGELOG.md +60 -0
- pythinker_code/__init__.py +0 -0
- pythinker_code/__main__.py +97 -0
- pythinker_code/acp/AGENTS.md +93 -0
- pythinker_code/acp/__init__.py +13 -0
- pythinker_code/acp/convert.py +128 -0
- pythinker_code/acp/host.py +301 -0
- pythinker_code/acp/mcp.py +46 -0
- pythinker_code/acp/server.py +497 -0
- pythinker_code/acp/session.py +502 -0
- pythinker_code/acp/tools.py +174 -0
- pythinker_code/acp/types.py +13 -0
- pythinker_code/acp/version.py +45 -0
- pythinker_code/agents/default/agent.yaml +55 -0
- pythinker_code/agents/default/code_reviewer.yaml +47 -0
- pythinker_code/agents/default/coder.yaml +42 -0
- pythinker_code/agents/default/debugger.yaml +35 -0
- pythinker_code/agents/default/explore.yaml +59 -0
- pythinker_code/agents/default/implementer.yaml +46 -0
- pythinker_code/agents/default/plan.yaml +42 -0
- pythinker_code/agents/default/review.yaml +47 -0
- pythinker_code/agents/default/security_reviewer.yaml +37 -0
- pythinker_code/agents/default/system.md +192 -0
- pythinker_code/agents/default/verifier.yaml +46 -0
- pythinker_code/agents/okabe/agent.yaml +22 -0
- pythinker_code/agentspec.py +163 -0
- pythinker_code/app.py +847 -0
- pythinker_code/approval_runtime/__init__.py +29 -0
- pythinker_code/approval_runtime/models.py +42 -0
- pythinker_code/approval_runtime/runtime.py +235 -0
- pythinker_code/auth/__init__.py +25 -0
- pythinker_code/auth/anthropic_direct.py +207 -0
- pythinker_code/auth/deepseek.py +192 -0
- pythinker_code/auth/github_feedback.py +228 -0
- pythinker_code/auth/lm_studio.py +418 -0
- pythinker_code/auth/minimax.py +203 -0
- pythinker_code/auth/oauth.py +1145 -0
- pythinker_code/auth/ollama.py +293 -0
- pythinker_code/auth/openai.py +783 -0
- pythinker_code/auth/opencode_go.py +203 -0
- pythinker_code/auth/openrouter.py +225 -0
- pythinker_code/auth/platforms.py +475 -0
- pythinker_code/background/__init__.py +36 -0
- pythinker_code/background/agent_runner.py +231 -0
- pythinker_code/background/ids.py +19 -0
- pythinker_code/background/manager.py +668 -0
- pythinker_code/background/models.py +118 -0
- pythinker_code/background/store.py +243 -0
- pythinker_code/background/summary.py +66 -0
- pythinker_code/background/worker.py +209 -0
- pythinker_code/cli/__init__.py +1326 -0
- pythinker_code/cli/__main__.py +19 -0
- pythinker_code/cli/_lazy_group.py +268 -0
- pythinker_code/cli/debug.py +11 -0
- pythinker_code/cli/export.py +322 -0
- pythinker_code/cli/info.py +62 -0
- pythinker_code/cli/mcp.py +362 -0
- pythinker_code/cli/plugin.py +351 -0
- pythinker_code/cli/review.py +74 -0
- pythinker_code/cli/secscan.py +11 -0
- pythinker_code/cli/security_scan.py +35 -0
- pythinker_code/cli/toad.py +74 -0
- pythinker_code/cli/update.py +26 -0
- pythinker_code/cli/vis.py +38 -0
- pythinker_code/cli/web.py +80 -0
- pythinker_code/config.py +511 -0
- pythinker_code/constant.py +33 -0
- pythinker_code/events.py +106 -0
- pythinker_code/exception.py +43 -0
- pythinker_code/extensions.py +151 -0
- pythinker_code/hooks/__init__.py +4 -0
- pythinker_code/hooks/config.py +34 -0
- pythinker_code/hooks/engine.py +383 -0
- pythinker_code/hooks/events.py +190 -0
- pythinker_code/hooks/runner.py +92 -0
- pythinker_code/llm.py +441 -0
- pythinker_code/metadata.py +79 -0
- pythinker_code/notifications/__init__.py +33 -0
- pythinker_code/notifications/llm.py +77 -0
- pythinker_code/notifications/manager.py +145 -0
- pythinker_code/notifications/models.py +50 -0
- pythinker_code/notifications/notifier.py +41 -0
- pythinker_code/notifications/store.py +118 -0
- pythinker_code/notifications/wire.py +21 -0
- pythinker_code/plugin/__init__.py +124 -0
- pythinker_code/plugin/manager.py +166 -0
- pythinker_code/plugin/tool.py +173 -0
- pythinker_code/prompt_templates.py +181 -0
- pythinker_code/prompts/__init__.py +6 -0
- pythinker_code/prompts/compact.md +73 -0
- pythinker_code/prompts/init.md +21 -0
- pythinker_code/py.typed +0 -0
- pythinker_code/session.py +319 -0
- pythinker_code/session_fork.py +325 -0
- pythinker_code/session_state.py +132 -0
- pythinker_code/share.py +14 -0
- pythinker_code/skill/__init__.py +727 -0
- pythinker_code/skill/flow/__init__.py +99 -0
- pythinker_code/skill/flow/d2.py +482 -0
- pythinker_code/skill/flow/mermaid.py +266 -0
- pythinker_code/skills/pythinker-code-help/SKILL.md +54 -0
- pythinker_code/skills/skill-creator/SKILL.md +367 -0
- pythinker_code/soul/__init__.py +304 -0
- pythinker_code/soul/agent.py +552 -0
- pythinker_code/soul/approval.py +267 -0
- pythinker_code/soul/btw.py +220 -0
- pythinker_code/soul/compaction.py +189 -0
- pythinker_code/soul/context.py +339 -0
- pythinker_code/soul/denwarenji.py +39 -0
- pythinker_code/soul/dynamic_injection.py +84 -0
- pythinker_code/soul/dynamic_injections/__init__.py +0 -0
- pythinker_code/soul/dynamic_injections/auto_mode.py +72 -0
- pythinker_code/soul/dynamic_injections/plan_mode.py +239 -0
- pythinker_code/soul/message.py +92 -0
- pythinker_code/soul/permission.py +368 -0
- pythinker_code/soul/pythinkersoul.py +1763 -0
- pythinker_code/soul/slash.py +340 -0
- pythinker_code/soul/toolset.py +826 -0
- pythinker_code/subagents/__init__.py +21 -0
- pythinker_code/subagents/builder.py +43 -0
- pythinker_code/subagents/core.py +86 -0
- pythinker_code/subagents/discovery.py +234 -0
- pythinker_code/subagents/git_context.py +172 -0
- pythinker_code/subagents/models.py +56 -0
- pythinker_code/subagents/output.py +71 -0
- pythinker_code/subagents/registry.py +28 -0
- pythinker_code/subagents/runner.py +442 -0
- pythinker_code/subagents/store.py +200 -0
- pythinker_code/telemetry/__init__.py +217 -0
- pythinker_code/telemetry/config.py +113 -0
- pythinker_code/telemetry/crash.py +191 -0
- pythinker_code/telemetry/errors.py +113 -0
- pythinker_code/telemetry/metrics.py +208 -0
- pythinker_code/telemetry/otel.py +303 -0
- pythinker_code/telemetry/sentry.py +212 -0
- pythinker_code/telemetry/sink.py +189 -0
- pythinker_code/tools/AGENTS.md +6 -0
- pythinker_code/tools/__init__.py +105 -0
- pythinker_code/tools/agent/__init__.py +326 -0
- pythinker_code/tools/agent/description.md +65 -0
- pythinker_code/tools/ask_user/__init__.py +162 -0
- pythinker_code/tools/ask_user/description.md +19 -0
- pythinker_code/tools/background/__init__.py +318 -0
- pythinker_code/tools/background/list.md +10 -0
- pythinker_code/tools/background/output.md +11 -0
- pythinker_code/tools/background/stop.md +8 -0
- pythinker_code/tools/display.py +46 -0
- pythinker_code/tools/dmail/__init__.py +38 -0
- pythinker_code/tools/dmail/dmail.md +17 -0
- pythinker_code/tools/file/__init__.py +31 -0
- pythinker_code/tools/file/glob.md +17 -0
- pythinker_code/tools/file/glob.py +163 -0
- pythinker_code/tools/file/grep.md +6 -0
- pythinker_code/tools/file/grep_local.py +904 -0
- pythinker_code/tools/file/plan_mode.py +45 -0
- pythinker_code/tools/file/read.md +16 -0
- pythinker_code/tools/file/read.py +303 -0
- pythinker_code/tools/file/read_media.md +24 -0
- pythinker_code/tools/file/read_media.py +220 -0
- pythinker_code/tools/file/replace.md +7 -0
- pythinker_code/tools/file/replace.py +204 -0
- pythinker_code/tools/file/utils.py +257 -0
- pythinker_code/tools/file/write.md +5 -0
- pythinker_code/tools/file/write.py +186 -0
- pythinker_code/tools/plan/__init__.py +362 -0
- pythinker_code/tools/plan/description.md +29 -0
- pythinker_code/tools/plan/enter.py +193 -0
- pythinker_code/tools/plan/enter_description.md +35 -0
- pythinker_code/tools/plan/handoff.py +69 -0
- pythinker_code/tools/plan/heroes.py +277 -0
- pythinker_code/tools/shell/__init__.py +263 -0
- pythinker_code/tools/shell/bash.md +35 -0
- pythinker_code/tools/shell/powershell.md +30 -0
- pythinker_code/tools/test.py +55 -0
- pythinker_code/tools/think/__init__.py +21 -0
- pythinker_code/tools/think/think.md +1 -0
- pythinker_code/tools/todo/__init__.py +168 -0
- pythinker_code/tools/todo/set_todo_list.md +23 -0
- pythinker_code/tools/utils.py +200 -0
- pythinker_code/tools/web/__init__.py +4 -0
- pythinker_code/tools/web/fetch.md +1 -0
- pythinker_code/tools/web/fetch.py +261 -0
- pythinker_code/tools/web/search.md +1 -0
- pythinker_code/tools/web/search.py +163 -0
- pythinker_code/ui/__init__.py +0 -0
- pythinker_code/ui/acp/__init__.py +99 -0
- pythinker_code/ui/print/__init__.py +474 -0
- pythinker_code/ui/print/visualize.py +185 -0
- pythinker_code/ui/shell/__init__.py +1806 -0
- pythinker_code/ui/shell/components/__init__.py +110 -0
- pythinker_code/ui/shell/components/base.py +25 -0
- pythinker_code/ui/shell/components/bash_execution.py +249 -0
- pythinker_code/ui/shell/components/bordered_loader.py +62 -0
- pythinker_code/ui/shell/components/diff.py +308 -0
- pythinker_code/ui/shell/components/footer.py +231 -0
- pythinker_code/ui/shell/components/key_hints.py +27 -0
- pythinker_code/ui/shell/components/messages.py +152 -0
- pythinker_code/ui/shell/components/render_utils.py +198 -0
- pythinker_code/ui/shell/components/settings_list.py +369 -0
- pythinker_code/ui/shell/components/special_messages.py +125 -0
- pythinker_code/ui/shell/components/tool_execution.py +261 -0
- pythinker_code/ui/shell/console.py +109 -0
- pythinker_code/ui/shell/debug.py +190 -0
- pythinker_code/ui/shell/echo.py +30 -0
- pythinker_code/ui/shell/export_import.py +117 -0
- pythinker_code/ui/shell/keyboard.py +300 -0
- pythinker_code/ui/shell/keymap.py +84 -0
- pythinker_code/ui/shell/mcp_status.py +112 -0
- pythinker_code/ui/shell/model_picker.py +318 -0
- pythinker_code/ui/shell/oauth.py +273 -0
- pythinker_code/ui/shell/placeholders.py +578 -0
- pythinker_code/ui/shell/prompt.py +2888 -0
- pythinker_code/ui/shell/replay.py +215 -0
- pythinker_code/ui/shell/selector.py +364 -0
- pythinker_code/ui/shell/selectors/__init__.py +38 -0
- pythinker_code/ui/shell/selectors/extension.py +37 -0
- pythinker_code/ui/shell/selectors/oauth.py +63 -0
- pythinker_code/ui/shell/selectors/settings.py +349 -0
- pythinker_code/ui/shell/selectors/show_images.py +29 -0
- pythinker_code/ui/shell/selectors/theme.py +28 -0
- pythinker_code/ui/shell/selectors/thinking.py +42 -0
- pythinker_code/ui/shell/session_picker.py +227 -0
- pythinker_code/ui/shell/setup.py +212 -0
- pythinker_code/ui/shell/slash.py +1433 -0
- pythinker_code/ui/shell/spinner_words.py +222 -0
- pythinker_code/ui/shell/startup.py +32 -0
- pythinker_code/ui/shell/task_browser.py +486 -0
- pythinker_code/ui/shell/tool_renderers/__init__.py +197 -0
- pythinker_code/ui/shell/tool_renderers/_render_utils.py +168 -0
- pythinker_code/ui/shell/tool_renderers/agent.py +140 -0
- pythinker_code/ui/shell/tool_renderers/ask_user.py +93 -0
- pythinker_code/ui/shell/tool_renderers/background.py +144 -0
- pythinker_code/ui/shell/tool_renderers/bash.py +103 -0
- pythinker_code/ui/shell/tool_renderers/edit.py +163 -0
- pythinker_code/ui/shell/tool_renderers/find.py +81 -0
- pythinker_code/ui/shell/tool_renderers/generic.py +60 -0
- pythinker_code/ui/shell/tool_renderers/grep.py +98 -0
- pythinker_code/ui/shell/tool_renderers/plan.py +98 -0
- pythinker_code/ui/shell/tool_renderers/read.py +103 -0
- pythinker_code/ui/shell/tool_renderers/think.py +66 -0
- pythinker_code/ui/shell/tool_renderers/todo.py +164 -0
- pythinker_code/ui/shell/tool_renderers/web.py +128 -0
- pythinker_code/ui/shell/tool_renderers/write.py +102 -0
- pythinker_code/ui/shell/update.py +352 -0
- pythinker_code/ui/shell/usage.py +291 -0
- pythinker_code/ui/shell/usage_adapters/__init__.py +50 -0
- pythinker_code/ui/shell/usage_adapters/anthropic_admin.py +233 -0
- pythinker_code/ui/shell/usage_adapters/base.py +72 -0
- pythinker_code/ui/shell/usage_adapters/deepseek.py +137 -0
- pythinker_code/ui/shell/usage_adapters/minimax.py +236 -0
- pythinker_code/ui/shell/usage_adapters/openai_admin.py +225 -0
- pythinker_code/ui/shell/usage_adapters/openai_chatgpt.py +241 -0
- pythinker_code/ui/shell/usage_adapters/opencode_go.py +232 -0
- pythinker_code/ui/shell/usage_adapters/openrouter.py +105 -0
- pythinker_code/ui/shell/usage_adapters/pythinker.py +189 -0
- pythinker_code/ui/shell/usage_adapters/pythinker_ai.py +50 -0
- pythinker_code/ui/shell/usage_render.py +150 -0
- pythinker_code/ui/shell/visualize/__init__.py +165 -0
- pythinker_code/ui/shell/visualize/_approval_panel.py +539 -0
- pythinker_code/ui/shell/visualize/_blocks.py +802 -0
- pythinker_code/ui/shell/visualize/_btw_panel.py +227 -0
- pythinker_code/ui/shell/visualize/_input_router.py +48 -0
- pythinker_code/ui/shell/visualize/_interactive.py +531 -0
- pythinker_code/ui/shell/visualize/_live_view.py +891 -0
- pythinker_code/ui/shell/visualize/_question_panel.py +586 -0
- pythinker_code/ui/shell/visualize/_worklog.py +245 -0
- pythinker_code/ui/theme.py +395 -0
- pythinker_code/ui/tui_config.py +82 -0
- pythinker_code/usage_ratelimit_cache.py +175 -0
- pythinker_code/utils/__init__.py +0 -0
- pythinker_code/utils/aiohttp.py +24 -0
- pythinker_code/utils/aioqueue.py +72 -0
- pythinker_code/utils/broadcast.py +38 -0
- pythinker_code/utils/changelog.py +108 -0
- pythinker_code/utils/clipboard.py +246 -0
- pythinker_code/utils/datetime.py +64 -0
- pythinker_code/utils/diff.py +135 -0
- pythinker_code/utils/editor.py +91 -0
- pythinker_code/utils/environment.py +73 -0
- pythinker_code/utils/envvar.py +22 -0
- pythinker_code/utils/export.py +696 -0
- pythinker_code/utils/file_filter.py +375 -0
- pythinker_code/utils/frontmatter.py +70 -0
- pythinker_code/utils/io.py +27 -0
- pythinker_code/utils/logging.py +146 -0
- pythinker_code/utils/media_tags.py +29 -0
- pythinker_code/utils/message.py +24 -0
- pythinker_code/utils/path.py +199 -0
- pythinker_code/utils/proctitle.py +33 -0
- pythinker_code/utils/proxy.py +31 -0
- pythinker_code/utils/pyinstaller.py +45 -0
- pythinker_code/utils/rich/__init__.py +33 -0
- pythinker_code/utils/rich/columns.py +99 -0
- pythinker_code/utils/rich/diff_render.py +481 -0
- pythinker_code/utils/rich/markdown.py +935 -0
- pythinker_code/utils/rich/markdown_sample.md +108 -0
- pythinker_code/utils/rich/markdown_sample_short.md +2 -0
- pythinker_code/utils/rich/syntax.py +114 -0
- pythinker_code/utils/sensitive.py +54 -0
- pythinker_code/utils/server.py +121 -0
- pythinker_code/utils/signals.py +43 -0
- pythinker_code/utils/slashcmd.py +124 -0
- pythinker_code/utils/string.py +41 -0
- pythinker_code/utils/subprocess_env.py +83 -0
- pythinker_code/utils/term.py +168 -0
- pythinker_code/utils/typing.py +20 -0
- pythinker_code/vis/__init__.py +0 -0
- pythinker_code/vis/api/__init__.py +5 -0
- pythinker_code/vis/api/sessions.py +714 -0
- pythinker_code/vis/api/statistics.py +209 -0
- pythinker_code/vis/api/system.py +19 -0
- pythinker_code/vis/app.py +199 -0
- pythinker_code/vis/static/assets/highlighted-body-B3W2YXNL-CY1rtwrX.js +1 -0
- pythinker_code/vis/static/assets/index-DSRInNnm.css +1 -0
- pythinker_code/vis/static/assets/index-DgmTI2M_.js +185 -0
- pythinker_code/vis/static/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- pythinker_code/vis/static/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- pythinker_code/vis/static/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- pythinker_code/vis/static/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- pythinker_code/vis/static/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- pythinker_code/vis/static/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- pythinker_code/vis/static/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- pythinker_code/vis/static/index.html +17 -0
- pythinker_code/web/__init__.py +5 -0
- pythinker_code/web/api/__init__.py +15 -0
- pythinker_code/web/api/config.py +217 -0
- pythinker_code/web/api/open_in.py +233 -0
- pythinker_code/web/api/sessions.py +1256 -0
- pythinker_code/web/app.py +449 -0
- pythinker_code/web/auth.py +191 -0
- pythinker_code/web/models.py +98 -0
- pythinker_code/web/runner/__init__.py +5 -0
- pythinker_code/web/runner/messages.py +57 -0
- pythinker_code/web/runner/process.py +754 -0
- pythinker_code/web/runner/worker.py +97 -0
- pythinker_code/web/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- pythinker_code/web/static/assets/_baseUniq-DpSMr1jx.js +1 -0
- pythinker_code/web/static/assets/abap-BdImnpbu.js +1 -0
- pythinker_code/web/static/assets/actionscript-3-CfeIJUat.js +1 -0
- pythinker_code/web/static/assets/ada-bCR0ucgS.js +1 -0
- pythinker_code/web/static/assets/andromeeda-C-Jbm3Hp.js +1 -0
- pythinker_code/web/static/assets/angular-html-CU67Zn6k.js +1 -0
- pythinker_code/web/static/assets/angular-ts-BwZT4LLn.js +1 -0
- pythinker_code/web/static/assets/apache-Pmp26Uib.js +1 -0
- pythinker_code/web/static/assets/apex-D8_7TLub.js +1 -0
- pythinker_code/web/static/assets/apl-dKokRX4l.js +1 -0
- pythinker_code/web/static/assets/applescript-Co6uUVPk.js +1 -0
- pythinker_code/web/static/assets/ara-BRHolxvo.js +1 -0
- pythinker_code/web/static/assets/arc-DpsahJyV.js +1 -0
- pythinker_code/web/static/assets/architectureDiagram-VXUJARFQ-DqiRv9Eg.js +36 -0
- pythinker_code/web/static/assets/asciidoc-Dv7Oe6Be.js +1 -0
- pythinker_code/web/static/assets/asm-D_Q5rh1f.js +1 -0
- pythinker_code/web/static/assets/astro-CbQHKStN.js +1 -0
- pythinker_code/web/static/assets/aurora-x-D-2ljcwZ.js +1 -0
- pythinker_code/web/static/assets/awk-DMzUqQB5.js +1 -0
- pythinker_code/web/static/assets/ayu-dark-CmMr59Fi.js +1 -0
- pythinker_code/web/static/assets/ballerina-BFfxhgS-.js +1 -0
- pythinker_code/web/static/assets/bat-BkioyH1T.js +1 -0
- pythinker_code/web/static/assets/beancount-k_qm7-4y.js +1 -0
- pythinker_code/web/static/assets/berry-uYugtg8r.js +1 -0
- pythinker_code/web/static/assets/bibtex-CHM0blh-.js +1 -0
- pythinker_code/web/static/assets/bicep-Bmn6On1c.js +1 -0
- pythinker_code/web/static/assets/blade-D4QpJJKB.js +1 -0
- pythinker_code/web/static/assets/blockDiagram-VD42YOAC-WgtUvqbp.js +122 -0
- pythinker_code/web/static/assets/bsl-BO_Y6i37.js +1 -0
- pythinker_code/web/static/assets/c-BIGW1oBm.js +1 -0
- pythinker_code/web/static/assets/c3-VCDPK7BO.js +1 -0
- pythinker_code/web/static/assets/c4Diagram-YG6GDRKO-rK0RPuZd.js +10 -0
- pythinker_code/web/static/assets/cadence-Bv_4Rxtq.js +1 -0
- pythinker_code/web/static/assets/cairo-KRGpt6FW.js +1 -0
- pythinker_code/web/static/assets/catppuccin-frappe-DFWUc33u.js +1 -0
- pythinker_code/web/static/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
- pythinker_code/web/static/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
- pythinker_code/web/static/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
- pythinker_code/web/static/assets/channel-B0rlvkH-.js +1 -0
- pythinker_code/web/static/assets/chunk-4BX2VUAB-DIkMuLV-.js +1 -0
- pythinker_code/web/static/assets/chunk-55IACEB6-CORdm4k4.js +1 -0
- pythinker_code/web/static/assets/chunk-B4BG7PRW-D9xDhwHO.js +165 -0
- pythinker_code/web/static/assets/chunk-DI55MBZ5-BDmF9Bh-.js +220 -0
- pythinker_code/web/static/assets/chunk-FMBD7UC4-BCse_HmM.js +15 -0
- pythinker_code/web/static/assets/chunk-QN33PNHL-DCpBmTzA.js +1 -0
- pythinker_code/web/static/assets/chunk-QZHKN3VN-BqLuqobw.js +1 -0
- pythinker_code/web/static/assets/chunk-TZMSLE5B-8K2ogOKS.js +1 -0
- pythinker_code/web/static/assets/clarity-D53aC0YG.js +1 -0
- pythinker_code/web/static/assets/classDiagram-2ON5EDUG-D_ZHSii2.js +1 -0
- pythinker_code/web/static/assets/classDiagram-v2-WZHVMYZB-D_ZHSii2.js +1 -0
- pythinker_code/web/static/assets/clojure-P80f7IUj.js +1 -0
- pythinker_code/web/static/assets/clone-GSXejyY1.js +1 -0
- pythinker_code/web/static/assets/cmake-D1j8_8rp.js +1 -0
- pythinker_code/web/static/assets/cobol-nwyudZeR.js +1 -0
- pythinker_code/web/static/assets/code-block-IT6T5CEO-DWTFYA28.js +2 -0
- pythinker_code/web/static/assets/codeowners-Bp6g37R7.js +1 -0
- pythinker_code/web/static/assets/codeql-DsOJ9woJ.js +1 -0
- pythinker_code/web/static/assets/coffee-Ch7k5sss.js +1 -0
- pythinker_code/web/static/assets/common-lisp-Cg-RD9OK.js +1 -0
- pythinker_code/web/static/assets/coq-DkFqJrB1.js +1 -0
- pythinker_code/web/static/assets/cose-bilkent-S5V4N54A-BRI7ES-N.js +1 -0
- pythinker_code/web/static/assets/cpp-CofmeUqb.js +1 -0
- pythinker_code/web/static/assets/crystal-tKQVLTB8.js +1 -0
- pythinker_code/web/static/assets/csharp-K5feNrxe.js +1 -0
- pythinker_code/web/static/assets/css-DPfMkruS.js +1 -0
- pythinker_code/web/static/assets/csv-fuZLfV_i.js +1 -0
- pythinker_code/web/static/assets/cue-D82EKSYY.js +1 -0
- pythinker_code/web/static/assets/cypher-COkxafJQ.js +1 -0
- pythinker_code/web/static/assets/cytoscape.esm-B6BxUuKW.js +321 -0
- pythinker_code/web/static/assets/d-85-TOEBH.js +1 -0
- pythinker_code/web/static/assets/dagre-6UL2VRFP-Ci5GdWfi.js +4 -0
- pythinker_code/web/static/assets/dark-plus-C3mMm8J8.js +1 -0
- pythinker_code/web/static/assets/dart-CF10PKvl.js +1 -0
- pythinker_code/web/static/assets/dax-CEL-wOlO.js +1 -0
- pythinker_code/web/static/assets/defaultLocale-DX6XiGOO.js +1 -0
- pythinker_code/web/static/assets/desktop-BmXAJ9_W.js +1 -0
- pythinker_code/web/static/assets/diagram-PSM6KHXK-0hhAylV4.js +24 -0
- pythinker_code/web/static/assets/diagram-QEK2KX5R-8fxgaW6d.js +43 -0
- pythinker_code/web/static/assets/diagram-S2PKOQOG-FRr0_atE.js +24 -0
- pythinker_code/web/static/assets/diff-D97Zzqfu.js +1 -0
- pythinker_code/web/static/assets/docker-BcOcwvcX.js +1 -0
- pythinker_code/web/static/assets/dotenv-Da5cRb03.js +1 -0
- pythinker_code/web/static/assets/dracula-BzJJZx-M.js +1 -0
- pythinker_code/web/static/assets/dracula-soft-BXkSAIEj.js +1 -0
- pythinker_code/web/static/assets/dream-maker-BtqSS_iP.js +1 -0
- pythinker_code/web/static/assets/edge-BkV0erSs.js +1 -0
- pythinker_code/web/static/assets/elixir-CDX3lj18.js +1 -0
- pythinker_code/web/static/assets/elm-DbKCFpqz.js +1 -0
- pythinker_code/web/static/assets/emacs-lisp-C9XAeP06.js +1 -0
- pythinker_code/web/static/assets/erDiagram-Q2GNP2WA-B3T-hJUM.js +60 -0
- pythinker_code/web/static/assets/erb-BOJIQeun.js +1 -0
- pythinker_code/web/static/assets/erlang-DsQrWhSR.js +1 -0
- pythinker_code/web/static/assets/everforest-dark-BgDCqdQA.js +1 -0
- pythinker_code/web/static/assets/everforest-light-C8M2exoo.js +1 -0
- pythinker_code/web/static/assets/fennel-BYunw83y.js +1 -0
- pythinker_code/web/static/assets/fish-BvzEVeQv.js +1 -0
- pythinker_code/web/static/assets/flowDiagram-NV44I4VS-D0S3u7ot.js +162 -0
- pythinker_code/web/static/assets/fluent-C4IJs8-o.js +1 -0
- pythinker_code/web/static/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
- pythinker_code/web/static/assets/fortran-free-form-BxgE0vQu.js +1 -0
- pythinker_code/web/static/assets/fsharp-CXgrBDvD.js +1 -0
- pythinker_code/web/static/assets/ganttDiagram-JELNMOA3-CHrN2a23.js +267 -0
- pythinker_code/web/static/assets/gdresource-B7Tvp0Sc.js +1 -0
- pythinker_code/web/static/assets/gdscript-DTMYz4Jt.js +1 -0
- pythinker_code/web/static/assets/gdshader-DkwncUOv.js +1 -0
- pythinker_code/web/static/assets/genie-D0YGMca9.js +1 -0
- pythinker_code/web/static/assets/gherkin-DyxjwDmM.js +1 -0
- pythinker_code/web/static/assets/git-commit-F4YmCXRG.js +1 -0
- pythinker_code/web/static/assets/git-rebase-r7XF79zn.js +1 -0
- pythinker_code/web/static/assets/gitGraphDiagram-NY62KEGX-CfcXZWg0.js +65 -0
- pythinker_code/web/static/assets/github-dark-DHJKELXO.js +1 -0
- pythinker_code/web/static/assets/github-dark-default-Cuk6v7N8.js +1 -0
- pythinker_code/web/static/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
- pythinker_code/web/static/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
- pythinker_code/web/static/assets/github-light-DAi9KRSo.js +1 -0
- pythinker_code/web/static/assets/github-light-default-D7oLnXFd.js +1 -0
- pythinker_code/web/static/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
- pythinker_code/web/static/assets/gleam-BspZqrRM.js +1 -0
- pythinker_code/web/static/assets/glimmer-js-Rg0-pVw9.js +1 -0
- pythinker_code/web/static/assets/glimmer-ts-U6CK756n.js +1 -0
- pythinker_code/web/static/assets/glsl-DplSGwfg.js +1 -0
- pythinker_code/web/static/assets/gn-n2N0HUVH.js +1 -0
- pythinker_code/web/static/assets/gnuplot-DdkO51Og.js +1 -0
- pythinker_code/web/static/assets/go-Dn2_MT6a.js +1 -0
- pythinker_code/web/static/assets/graph-8jMJwCqE.js +1 -0
- pythinker_code/web/static/assets/graphql-ChdNCCLP.js +1 -0
- pythinker_code/web/static/assets/groovy-gcz8RCvz.js +1 -0
- pythinker_code/web/static/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
- pythinker_code/web/static/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
- pythinker_code/web/static/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
- pythinker_code/web/static/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
- pythinker_code/web/static/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
- pythinker_code/web/static/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
- pythinker_code/web/static/assets/hack-CaT9iCJl.js +1 -0
- pythinker_code/web/static/assets/haml-B8DHNrY2.js +1 -0
- pythinker_code/web/static/assets/handlebars-BL8al0AC.js +1 -0
- pythinker_code/web/static/assets/haskell-Df6bDoY_.js +1 -0
- pythinker_code/web/static/assets/haxe-CzTSHFRz.js +1 -0
- pythinker_code/web/static/assets/hcl-BWvSN4gD.js +1 -0
- pythinker_code/web/static/assets/hjson-D5-asLiD.js +1 -0
- pythinker_code/web/static/assets/hlsl-D3lLCCz7.js +1 -0
- pythinker_code/web/static/assets/houston-DnULxvSX.js +1 -0
- pythinker_code/web/static/assets/html-GMplVEZG.js +1 -0
- pythinker_code/web/static/assets/html-derivative-BFtXZ54Q.js +1 -0
- pythinker_code/web/static/assets/http-jrhK8wxY.js +1 -0
- pythinker_code/web/static/assets/hurl-irOxFIW8.js +1 -0
- pythinker_code/web/static/assets/hxml-Bvhsp5Yf.js +1 -0
- pythinker_code/web/static/assets/hy-DFXneXwc.js +1 -0
- pythinker_code/web/static/assets/imba-DGztddWO.js +1 -0
- pythinker_code/web/static/assets/index-BXrFnzMy.js +153 -0
- pythinker_code/web/static/assets/index-BpoLgcEt.js +1 -0
- pythinker_code/web/static/assets/index-BrfQJnRD.js +5 -0
- pythinker_code/web/static/assets/index-C4gFzubz.js +2 -0
- pythinker_code/web/static/assets/index-CzV_vCfu.css +1 -0
- pythinker_code/web/static/assets/index-DI2oedCt.js +19 -0
- pythinker_code/web/static/assets/infoDiagram-WHAUD3N6-DdxonBf3.js +2 -0
- pythinker_code/web/static/assets/ini-BEwlwnbL.js +1 -0
- pythinker_code/web/static/assets/init-Gi6I4Gst.js +1 -0
- pythinker_code/web/static/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- pythinker_code/web/static/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- pythinker_code/web/static/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- pythinker_code/web/static/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- pythinker_code/web/static/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- pythinker_code/web/static/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- pythinker_code/web/static/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- pythinker_code/web/static/assets/java-CylS5w8V.js +1 -0
- pythinker_code/web/static/assets/javascript-wDzz0qaB.js +1 -0
- pythinker_code/web/static/assets/jinja-4LBKfQ-Z.js +1 -0
- pythinker_code/web/static/assets/jison-wvAkD_A8.js +1 -0
- pythinker_code/web/static/assets/journeyDiagram-XKPGCS4Q-BXf4aQei.js +139 -0
- pythinker_code/web/static/assets/json-Cp-IABpG.js +1 -0
- pythinker_code/web/static/assets/json5-C9tS-k6U.js +1 -0
- pythinker_code/web/static/assets/jsonc-Des-eS-w.js +1 -0
- pythinker_code/web/static/assets/jsonl-DcaNXYhu.js +1 -0
- pythinker_code/web/static/assets/jsonnet-DFQXde-d.js +1 -0
- pythinker_code/web/static/assets/jssm-C2t-YnRu.js +1 -0
- pythinker_code/web/static/assets/jsx-g9-lgVsj.js +1 -0
- pythinker_code/web/static/assets/julia-CxzCAyBv.js +1 -0
- pythinker_code/web/static/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
- pythinker_code/web/static/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
- pythinker_code/web/static/assets/kanagawa-wave-DWedfzmr.js +1 -0
- pythinker_code/web/static/assets/kanban-definition-3W4ZIXB7-DLpPPOu8.js +89 -0
- pythinker_code/web/static/assets/katex-D2lIc1rk.css +1 -0
- pythinker_code/web/static/assets/kdl-DV7GczEv.js +1 -0
- pythinker_code/web/static/assets/kotlin-BdnUsdx6.js +1 -0
- pythinker_code/web/static/assets/kusto-DZf3V79B.js +1 -0
- pythinker_code/web/static/assets/laserwave-DUszq2jm.js +1 -0
- pythinker_code/web/static/assets/latex-B4uzh10-.js +1 -0
- pythinker_code/web/static/assets/layout-DH73UoAH.js +1 -0
- pythinker_code/web/static/assets/lean-BZvkOJ9d.js +1 -0
- pythinker_code/web/static/assets/less-B1dDrJ26.js +1 -0
- pythinker_code/web/static/assets/light-plus-B7mTdjB0.js +1 -0
- pythinker_code/web/static/assets/linear-bAer2-sK.js +1 -0
- pythinker_code/web/static/assets/liquid-DYVedYrR.js +1 -0
- pythinker_code/web/static/assets/llvm-BtvRca6l.js +1 -0
- pythinker_code/web/static/assets/log-2UxHyX5q.js +1 -0
- pythinker_code/web/static/assets/logo-BtOb2qkB.js +1 -0
- pythinker_code/web/static/assets/lua-BbnMAYS6.js +1 -0
- pythinker_code/web/static/assets/luau-C-HG3fhB.js +1 -0
- pythinker_code/web/static/assets/make-CHLpvVh8.js +1 -0
- pythinker_code/web/static/assets/markdown-Cvjx9yec.js +1 -0
- pythinker_code/web/static/assets/marko-DZsq8hO1.js +1 -0
- pythinker_code/web/static/assets/material-theme-D5KoaKCx.js +1 -0
- pythinker_code/web/static/assets/material-theme-darker-BfHTSMKl.js +1 -0
- pythinker_code/web/static/assets/material-theme-lighter-B0m2ddpp.js +1 -0
- pythinker_code/web/static/assets/material-theme-ocean-CyktbL80.js +1 -0
- pythinker_code/web/static/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
- pythinker_code/web/static/assets/matlab-D7o27uSR.js +1 -0
- pythinker_code/web/static/assets/mdc-DUICxH0z.js +1 -0
- pythinker_code/web/static/assets/mdx-Cmh6b_Ma.js +1 -0
- pythinker_code/web/static/assets/mermaid-VLURNSYL-B2P5VJ9v.css +1 -0
- pythinker_code/web/static/assets/mermaid-VLURNSYL-CuqbwKXv.js +465 -0
- pythinker_code/web/static/assets/mermaid-mWjccvbQ.js +1 -0
- pythinker_code/web/static/assets/mermaid.core-Nx-rTKiV.js +191 -0
- pythinker_code/web/static/assets/min-DbfD8Ywu.js +1 -0
- pythinker_code/web/static/assets/min-dark-CafNBF8u.js +1 -0
- pythinker_code/web/static/assets/min-light-CTRr51gU.js +1 -0
- pythinker_code/web/static/assets/mindmap-definition-VGOIOE7T-C6l761Ue.js +68 -0
- pythinker_code/web/static/assets/mipsasm-CKIfxQSi.js +1 -0
- pythinker_code/web/static/assets/mojo-B93PlW-d.js +1 -0
- pythinker_code/web/static/assets/monokai-D4h5O-jR.js +1 -0
- pythinker_code/web/static/assets/moonbit-Ba13S78F.js +1 -0
- pythinker_code/web/static/assets/move-Bu9oaDYs.js +1 -0
- pythinker_code/web/static/assets/narrat-DRg8JJMk.js +1 -0
- pythinker_code/web/static/assets/nextflow-BrzmwbiE.js +1 -0
- pythinker_code/web/static/assets/nginx-DknmC5AR.js +1 -0
- pythinker_code/web/static/assets/night-owl-C39BiMTA.js +1 -0
- pythinker_code/web/static/assets/nim-CVrawwO9.js +1 -0
- pythinker_code/web/static/assets/nix-CwoSXNpI.js +1 -0
- pythinker_code/web/static/assets/nord-Ddv68eIx.js +1 -0
- pythinker_code/web/static/assets/nushell-C-sUppwS.js +1 -0
- pythinker_code/web/static/assets/objective-c-DXmwc3jG.js +1 -0
- pythinker_code/web/static/assets/objective-cpp-CLxacb5B.js +1 -0
- pythinker_code/web/static/assets/ocaml-C0hk2d4L.js +1 -0
- pythinker_code/web/static/assets/one-dark-pro-DVMEJ2y_.js +1 -0
- pythinker_code/web/static/assets/one-light-PoHY5YXO.js +1 -0
- pythinker_code/web/static/assets/openscad-C4EeE6gA.js +1 -0
- pythinker_code/web/static/assets/ordinal-Cboi1Yqb.js +1 -0
- pythinker_code/web/static/assets/pascal-D93ZcfNL.js +1 -0
- pythinker_code/web/static/assets/perl-C0TMdlhV.js +1 -0
- pythinker_code/web/static/assets/php-CDn_0X-4.js +1 -0
- pythinker_code/web/static/assets/pieDiagram-ADFJNKIX-fNg41mT9.js +30 -0
- pythinker_code/web/static/assets/pkl-u5AG7uiY.js +1 -0
- pythinker_code/web/static/assets/plastic-3e1v2bzS.js +1 -0
- pythinker_code/web/static/assets/plsql-ChMvpjG-.js +1 -0
- pythinker_code/web/static/assets/po-BTJTHyun.js +1 -0
- pythinker_code/web/static/assets/poimandres-CS3Unz2-.js +1 -0
- pythinker_code/web/static/assets/polar-C0HS_06l.js +1 -0
- pythinker_code/web/static/assets/postcss-CXtECtnM.js +1 -0
- pythinker_code/web/static/assets/powerquery-CEu0bR-o.js +1 -0
- pythinker_code/web/static/assets/powershell-Dpen1YoG.js +1 -0
- pythinker_code/web/static/assets/prisma-Dd19v3D-.js +1 -0
- pythinker_code/web/static/assets/prolog-CbFg5uaA.js +1 -0
- pythinker_code/web/static/assets/proto-C7zT0LnQ.js +1 -0
- pythinker_code/web/static/assets/pug-CGlum2m_.js +1 -0
- pythinker_code/web/static/assets/puppet-BMWR74SV.js +1 -0
- pythinker_code/web/static/assets/purescript-CklMAg4u.js +1 -0
- pythinker_code/web/static/assets/python-B6aJPvgy.js +1 -0
- pythinker_code/web/static/assets/qml-3beO22l8.js +1 -0
- pythinker_code/web/static/assets/qmldir-C8lEn-DE.js +1 -0
- pythinker_code/web/static/assets/qss-IeuSbFQv.js +1 -0
- pythinker_code/web/static/assets/quadrantDiagram-AYHSOK5B-DJz3Kx87.js +7 -0
- pythinker_code/web/static/assets/r-Dspwwk_N.js +1 -0
- pythinker_code/web/static/assets/racket-BqYA7rlc.js +1 -0
- pythinker_code/web/static/assets/raku-DXvB9xmW.js +1 -0
- pythinker_code/web/static/assets/razor-C1TweQQi.js +1 -0
- pythinker_code/web/static/assets/red-bN70gL4F.js +1 -0
- pythinker_code/web/static/assets/reg-C-SQnVFl.js +1 -0
- pythinker_code/web/static/assets/regexp-CDVJQ6XC.js +1 -0
- pythinker_code/web/static/assets/rel-C3B-1QV4.js +1 -0
- pythinker_code/web/static/assets/requirementDiagram-UZGBJVZJ-B4SbrfE9.js +64 -0
- pythinker_code/web/static/assets/riscv-BM1_JUlF.js +1 -0
- pythinker_code/web/static/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
- pythinker_code/web/static/assets/rose-pine-moon-D4_iv3hh.js +1 -0
- pythinker_code/web/static/assets/rose-pine-qdsjHGoJ.js +1 -0
- pythinker_code/web/static/assets/rosmsg-BJDFO7_C.js +1 -0
- pythinker_code/web/static/assets/rst-B0xPkSld.js +1 -0
- pythinker_code/web/static/assets/ruby-BvKwtOVI.js +1 -0
- pythinker_code/web/static/assets/rust-B1yitclQ.js +1 -0
- pythinker_code/web/static/assets/sankeyDiagram-TZEHDZUN-CoSUjLAG.js +10 -0
- pythinker_code/web/static/assets/sas-cz2c8ADy.js +1 -0
- pythinker_code/web/static/assets/sass-Cj5Yp3dK.js +1 -0
- pythinker_code/web/static/assets/scala-C151Ov-r.js +1 -0
- pythinker_code/web/static/assets/scheme-C98Dy4si.js +1 -0
- pythinker_code/web/static/assets/scss-OYdSNvt2.js +1 -0
- pythinker_code/web/static/assets/sdbl-DVxCFoDh.js +1 -0
- pythinker_code/web/static/assets/sequenceDiagram-WL72ISMW-PjhBNHi3.js +145 -0
- pythinker_code/web/static/assets/shaderlab-Dg9Lc6iA.js +1 -0
- pythinker_code/web/static/assets/shellscript-Yzrsuije.js +1 -0
- pythinker_code/web/static/assets/shellsession-BADoaaVG.js +1 -0
- pythinker_code/web/static/assets/slack-dark-BthQWCQV.js +1 -0
- pythinker_code/web/static/assets/slack-ochin-DqwNpetd.js +1 -0
- pythinker_code/web/static/assets/smalltalk-BERRCDM3.js +1 -0
- pythinker_code/web/static/assets/snazzy-light-Bw305WKR.js +1 -0
- pythinker_code/web/static/assets/solarized-dark-DXbdFlpD.js +1 -0
- pythinker_code/web/static/assets/solarized-light-L9t79GZl.js +1 -0
- pythinker_code/web/static/assets/solidity-rGO070M0.js +1 -0
- pythinker_code/web/static/assets/soy-Brmx7dQM.js +1 -0
- pythinker_code/web/static/assets/sparql-rVzFXLq3.js +1 -0
- pythinker_code/web/static/assets/splunk-BtCnVYZw.js +1 -0
- pythinker_code/web/static/assets/sql-BLtJtn59.js +1 -0
- pythinker_code/web/static/assets/ssh-config-_ykCGR6B.js +1 -0
- pythinker_code/web/static/assets/stata-BH5u7GGu.js +1 -0
- pythinker_code/web/static/assets/stateDiagram-FKZM4ZOC-DOwESt8-.js +1 -0
- pythinker_code/web/static/assets/stateDiagram-v2-4FDKWEC3-yl3OHWiP.js +1 -0
- pythinker_code/web/static/assets/stylus-BEDo0Tqx.js +1 -0
- pythinker_code/web/static/assets/svelte-zxCyuUbr.js +1 -0
- pythinker_code/web/static/assets/swift-Dg5xB15N.js +1 -0
- pythinker_code/web/static/assets/synthwave-84-CbfX1IO0.js +1 -0
- pythinker_code/web/static/assets/system-verilog-CnnmHF94.js +1 -0
- pythinker_code/web/static/assets/systemd-4A_iFExJ.js +1 -0
- pythinker_code/web/static/assets/talonscript-CkByrt1z.js +1 -0
- pythinker_code/web/static/assets/tasl-QIJgUcNo.js +1 -0
- pythinker_code/web/static/assets/tcl-dwOrl1Do.js +1 -0
- pythinker_code/web/static/assets/templ-W15q3VgB.js +1 -0
- pythinker_code/web/static/assets/terraform-BETggiCN.js +1 -0
- pythinker_code/web/static/assets/tex-CvyZ59Mk.js +1 -0
- pythinker_code/web/static/assets/timeline-definition-IT6M3QCI-CkCLnAgi.js +61 -0
- pythinker_code/web/static/assets/tokyo-night-hegEt444.js +1 -0
- pythinker_code/web/static/assets/toml-vGWfd6FD.js +1 -0
- pythinker_code/web/static/assets/treemap-KMMF4GRG-CZS5XwTf.js +128 -0
- pythinker_code/web/static/assets/ts-tags-zn1MmPIZ.js +1 -0
- pythinker_code/web/static/assets/tsv-B_m7g4N7.js +1 -0
- pythinker_code/web/static/assets/tsx-COt5Ahok.js +1 -0
- pythinker_code/web/static/assets/turtle-BsS91CYL.js +1 -0
- pythinker_code/web/static/assets/twig-CO9l9SDP.js +1 -0
- pythinker_code/web/static/assets/typescript-BPQ3VLAy.js +1 -0
- pythinker_code/web/static/assets/typespec-BGHnOYBU.js +1 -0
- pythinker_code/web/static/assets/typst-DHCkPAjA.js +1 -0
- pythinker_code/web/static/assets/v-BcVCzyr7.js +1 -0
- pythinker_code/web/static/assets/vala-CsfeWuGM.js +1 -0
- pythinker_code/web/static/assets/vb-D17OF-Vu.js +1 -0
- pythinker_code/web/static/assets/verilog-BQ8w6xss.js +1 -0
- pythinker_code/web/static/assets/vesper-DU1UobuO.js +1 -0
- pythinker_code/web/static/assets/vhdl-CeAyd5Ju.js +1 -0
- pythinker_code/web/static/assets/viml-CJc9bBzg.js +1 -0
- pythinker_code/web/static/assets/vitesse-black-Bkuqu6BP.js +1 -0
- pythinker_code/web/static/assets/vitesse-dark-D0r3Knsf.js +1 -0
- pythinker_code/web/static/assets/vitesse-light-CVO1_9PV.js +1 -0
- pythinker_code/web/static/assets/vue-DN_0RTcg.js +1 -0
- pythinker_code/web/static/assets/vue-html-AaS7Mt5G.js +1 -0
- pythinker_code/web/static/assets/vue-vine-CQOfvN7w.js +1 -0
- pythinker_code/web/static/assets/vyper-CDx5xZoG.js +1 -0
- pythinker_code/web/static/assets/wasm-CG6Dc4jp.js +1 -0
- pythinker_code/web/static/assets/wasm-MzD3tlZU.js +1 -0
- pythinker_code/web/static/assets/wenyan-BV7otONQ.js +1 -0
- pythinker_code/web/static/assets/wgsl-Dx-B1_4e.js +1 -0
- pythinker_code/web/static/assets/wikitext-BhOHFoWU.js +1 -0
- pythinker_code/web/static/assets/wit-5i3qLPDT.js +1 -0
- pythinker_code/web/static/assets/wolfram-lXgVvXCa.js +1 -0
- pythinker_code/web/static/assets/xml-sdJ4AIDG.js +1 -0
- pythinker_code/web/static/assets/xsl-CtQFsRM5.js +1 -0
- pythinker_code/web/static/assets/xychartDiagram-PRI3JC2R-DkqqHNLh.js +7 -0
- pythinker_code/web/static/assets/yaml-Buea-lGh.js +1 -0
- pythinker_code/web/static/assets/zenscript-DVFEvuxE.js +1 -0
- pythinker_code/web/static/assets/zig-VOosw3JB.js +1 -0
- pythinker_code/web/static/brand/apple-touch-icon.png +0 -0
- pythinker_code/web/static/brand/arctecture.webp +0 -0
- pythinker_code/web/static/brand/bimi-logo.svg +46 -0
- pythinker_code/web/static/brand/favicon.ico +0 -0
- pythinker_code/web/static/brand/fonts/dm-sans-latin-ext.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/dm-sans-latin.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/instrument-sans-latin-ext.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/instrument-sans-latin.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/instrument-serif-latin-ext.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/instrument-serif-latin.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/libre-baskerville-italic-latin-ext.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/libre-baskerville-italic-latin.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/libre-baskerville-latin-ext.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/libre-baskerville-latin.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/roboto-latin-ext.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/roboto-latin.woff2 +0 -0
- pythinker_code/web/static/brand/icon-192.png +0 -0
- pythinker_code/web/static/brand/icon-512.png +0 -0
- pythinker_code/web/static/brand/icon.svg +1 -0
- pythinker_code/web/static/brand/logo.png +0 -0
- pythinker_code/web/static/brand/pythinker_animated.svg +79 -0
- pythinker_code/web/static/brand/robots.txt +4 -0
- pythinker_code/web/static/index.html +15 -0
- pythinker_code/web/static/logo.png +0 -0
- pythinker_code/web/store/__init__.py +1 -0
- pythinker_code/web/store/sessions.py +433 -0
- pythinker_code/wire/__init__.py +148 -0
- pythinker_code/wire/file.py +151 -0
- pythinker_code/wire/jsonrpc.py +263 -0
- pythinker_code/wire/protocol.py +2 -0
- pythinker_code/wire/root_hub.py +27 -0
- pythinker_code/wire/serde.py +26 -0
- pythinker_code/wire/server.py +1072 -0
- pythinker_code/wire/types.py +698 -0
- pythinker_code-0.8.0.dist-info/METADATA +706 -0
- pythinker_code-0.8.0.dist-info/RECORD +790 -0
- pythinker_code-0.8.0.dist-info/WHEEL +4 -0
- pythinker_code-0.8.0.dist-info/entry_points.txt +4 -0
- pythinker_code-0.8.0.dist-info/licenses/LICENSE +202 -0
- pythinker_code-0.8.0.dist-info/licenses/NOTICE +14 -0
|
@@ -0,0 +1,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)")
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""`pythinker review` — delegates to pythinker-review with active-model wiring."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any, cast
|
|
8
|
+
|
|
9
|
+
from pydantic import SecretStr
|
|
10
|
+
from pythinker_review.cli import review as upstream_review
|
|
11
|
+
from pythinker_review.llm.protocol import ReviewLLM
|
|
12
|
+
|
|
13
|
+
cli = upstream_review.app
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(slots=True)
|
|
17
|
+
class PythinkerActiveLLM:
|
|
18
|
+
"""Bridge pythinker-core's configured provider to the ReviewLLM protocol."""
|
|
19
|
+
|
|
20
|
+
chat_provider: Any
|
|
21
|
+
model_display_name: str
|
|
22
|
+
|
|
23
|
+
async def complete_json(self, *, system: str, user: str, timeout_s: float) -> str:
|
|
24
|
+
from pythinker_core import generate
|
|
25
|
+
from pythinker_core.message import Message
|
|
26
|
+
from pythinker_core.tooling.empty import EmptyToolset
|
|
27
|
+
|
|
28
|
+
result = await asyncio.wait_for(
|
|
29
|
+
generate(
|
|
30
|
+
self.chat_provider,
|
|
31
|
+
system,
|
|
32
|
+
EmptyToolset().tools,
|
|
33
|
+
[Message(role="user", content=user)],
|
|
34
|
+
),
|
|
35
|
+
timeout=timeout_s,
|
|
36
|
+
)
|
|
37
|
+
return result.message.extract_text()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def build_active_llm(*, model_name: str | None = None) -> ReviewLLM | None:
|
|
41
|
+
"""Build a ReviewLLM from the current Pythinker config, returning None when unset."""
|
|
42
|
+
from pythinker_code.auth.oauth import OAuthManager
|
|
43
|
+
from pythinker_code.config import LLMModel, LLMProvider, load_config
|
|
44
|
+
from pythinker_code.llm import augment_provider_with_env_vars, create_llm, model_display_name
|
|
45
|
+
|
|
46
|
+
config = load_config()
|
|
47
|
+
selected = model_name or config.default_model
|
|
48
|
+
if selected and selected in config.models:
|
|
49
|
+
model = config.models[selected].model_copy(deep=True)
|
|
50
|
+
provider = config.providers[model.provider].model_copy(deep=True)
|
|
51
|
+
else:
|
|
52
|
+
model = LLMModel(provider="", model="", max_context_size=100_000)
|
|
53
|
+
provider = LLMProvider(type="pythinker", base_url="", api_key=SecretStr(""))
|
|
54
|
+
augment_provider_with_env_vars(provider, model, provider_key=model.provider)
|
|
55
|
+
llm = create_llm(
|
|
56
|
+
provider,
|
|
57
|
+
model,
|
|
58
|
+
thinking=config.default_thinking,
|
|
59
|
+
session_id=None,
|
|
60
|
+
oauth=OAuthManager(config),
|
|
61
|
+
)
|
|
62
|
+
if llm is None:
|
|
63
|
+
return None
|
|
64
|
+
display = model_display_name(model.model, model)
|
|
65
|
+
return PythinkerActiveLLM(
|
|
66
|
+
chat_provider=cast(Any, llm.chat_provider), model_display_name=display
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _resolve_llm_with_active_model() -> ReviewLLM | None:
|
|
71
|
+
return build_active_llm()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
upstream_review.set_llm_resolver(_resolve_llm_with_active_model)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""`pythinker secscan` — active-model wrapper for pythinker-secscan."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pythinker_review.cli import secscan as upstream_secscan
|
|
6
|
+
|
|
7
|
+
# Importing review installs the active-model resolver used by secscan.
|
|
8
|
+
from pythinker_code.cli import review as review_wrapper
|
|
9
|
+
|
|
10
|
+
_REVIEW_CLI = review_wrapper.cli
|
|
11
|
+
cli = upstream_secscan.app
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""`pythinker security-scan` — active-model wrapper for Python-native Pythinker Security Scan."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from pythinker_review.cli import security_scan as upstream_security_scan
|
|
9
|
+
from pythinker_review.llm.fake import FakeReviewLLM
|
|
10
|
+
from pythinker_review.llm.protocol import ReviewLLM
|
|
11
|
+
|
|
12
|
+
# Importing review installs the active-model resolver shared by review/secscan.
|
|
13
|
+
from pythinker_code.cli import review as review_wrapper
|
|
14
|
+
|
|
15
|
+
_REVIEW_CLI = review_wrapper.cli
|
|
16
|
+
cli = upstream_security_scan.app
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _resolve_security_scan_llm() -> ReviewLLM:
|
|
20
|
+
adapter = review_wrapper.build_active_llm()
|
|
21
|
+
if adapter is not None:
|
|
22
|
+
return adapter
|
|
23
|
+
fake = os.environ.get("PYTHINKER_REVIEW_FAKE_LLM_RESPONSES")
|
|
24
|
+
if fake is not None:
|
|
25
|
+
return FakeReviewLLM(scripted=fake.split("\0") if fake else ["[]"])
|
|
26
|
+
typer.secho(
|
|
27
|
+
"No active model configured. Set PYTHINKER_REVIEW_FAKE_LLM_RESPONSES for tests, "
|
|
28
|
+
"or configure a Pythinker model before running `pythinker security-scan`.",
|
|
29
|
+
fg=typer.colors.RED,
|
|
30
|
+
err=True,
|
|
31
|
+
)
|
|
32
|
+
raise typer.Exit(code=3)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
upstream_security_scan.__dict__["_resolve_llm"] = _resolve_security_scan_llm
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import importlib.util
|
|
2
|
+
import shlex
|
|
3
|
+
import shutil
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _default_acp_command() -> list[str]:
|
|
12
|
+
argv0 = sys.argv[0]
|
|
13
|
+
if argv0:
|
|
14
|
+
resolved = shutil.which(argv0)
|
|
15
|
+
resolved_path = Path(resolved).expanduser() if resolved else Path(argv0).expanduser()
|
|
16
|
+
if (
|
|
17
|
+
resolved_path.exists()
|
|
18
|
+
and resolved_path.suffix != ".py"
|
|
19
|
+
and not resolved_path.name.startswith(("python", "pypy"))
|
|
20
|
+
):
|
|
21
|
+
return [str(resolved_path), "acp"]
|
|
22
|
+
|
|
23
|
+
return [sys.executable, "-m", "pythinker_code.cli", "acp"]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _default_toad_command() -> list[str]:
|
|
27
|
+
if sys.version_info < (3, 14):
|
|
28
|
+
typer.echo("`pythinker term` requires Python 3.14+ because Toad requires it.", err=True)
|
|
29
|
+
raise typer.Exit(code=1)
|
|
30
|
+
if importlib.util.find_spec("toad") is None:
|
|
31
|
+
typer.echo(
|
|
32
|
+
"Toad dependency is missing. Install pythinker-code with Python 3.14+ "
|
|
33
|
+
"to use `pythinker term`.",
|
|
34
|
+
err=True,
|
|
35
|
+
)
|
|
36
|
+
raise typer.Exit(code=1)
|
|
37
|
+
return [sys.executable, "-m", "toad.cli"]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _extract_project_dir(extra_args: list[str]) -> Path | None:
|
|
41
|
+
work_dir: str | None = None
|
|
42
|
+
idx = 0
|
|
43
|
+
while idx < len(extra_args):
|
|
44
|
+
arg = extra_args[idx]
|
|
45
|
+
if arg in ("--work-dir", "-w"):
|
|
46
|
+
if idx + 1 < len(extra_args):
|
|
47
|
+
work_dir = extra_args[idx + 1]
|
|
48
|
+
idx += 2
|
|
49
|
+
continue
|
|
50
|
+
elif arg.startswith("--work-dir=") or arg.startswith("-w="):
|
|
51
|
+
work_dir = arg.split("=", 1)[1]
|
|
52
|
+
elif arg.startswith("-w") and len(arg) > 2:
|
|
53
|
+
work_dir = arg[2:]
|
|
54
|
+
idx += 1
|
|
55
|
+
|
|
56
|
+
if not work_dir:
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
return Path(work_dir).expanduser().resolve()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def run_term(ctx: typer.Context) -> None:
|
|
63
|
+
extra_args = list(ctx.args)
|
|
64
|
+
acp_args = _default_acp_command()
|
|
65
|
+
acp_command = shlex.join(acp_args)
|
|
66
|
+
toad_parts = _default_toad_command()
|
|
67
|
+
args = [*toad_parts, "acp", acp_command]
|
|
68
|
+
project_dir = _extract_project_dir(extra_args)
|
|
69
|
+
if project_dir is not None:
|
|
70
|
+
args.append(str(project_dir))
|
|
71
|
+
|
|
72
|
+
result = subprocess.run(args)
|
|
73
|
+
if result.returncode != 0:
|
|
74
|
+
raise typer.Exit(code=result.returncode)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
cli = typer.Typer(help="Check for and install Pythinker CLI updates.")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@cli.callback(invoke_without_command=True)
|
|
12
|
+
def update(
|
|
13
|
+
check_only: Annotated[
|
|
14
|
+
bool,
|
|
15
|
+
typer.Option(
|
|
16
|
+
"--check",
|
|
17
|
+
help="Only check whether an update is available; don't install.",
|
|
18
|
+
),
|
|
19
|
+
] = False,
|
|
20
|
+
):
|
|
21
|
+
"""Check for and install Pythinker CLI updates."""
|
|
22
|
+
from pythinker_code.ui.shell.update import UpdateResult, do_update
|
|
23
|
+
|
|
24
|
+
result = asyncio.run(do_update(print=True, check_only=check_only))
|
|
25
|
+
if result in (UpdateResult.FAILED, UpdateResult.UNSUPPORTED):
|
|
26
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Vis command for Pythinker Agent Tracing Visualizer."""
|
|
2
|
+
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
cli = typer.Typer(help="Run Pythinker Agent Tracing Visualizer.")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@cli.callback(invoke_without_command=True)
|
|
11
|
+
def vis(
|
|
12
|
+
ctx: typer.Context,
|
|
13
|
+
host: Annotated[
|
|
14
|
+
str | None,
|
|
15
|
+
typer.Option("--host", "-h", help="Bind to specific IP address"),
|
|
16
|
+
] = None,
|
|
17
|
+
network: Annotated[
|
|
18
|
+
bool,
|
|
19
|
+
typer.Option("--network", "-n", help="Enable network access (bind to 0.0.0.0)"),
|
|
20
|
+
] = False,
|
|
21
|
+
port: Annotated[int, typer.Option("--port", "-p", help="Port to bind to")] = 5495,
|
|
22
|
+
open_browser: Annotated[
|
|
23
|
+
bool, typer.Option("--open/--no-open", help="Open browser automatically")
|
|
24
|
+
] = True,
|
|
25
|
+
reload: Annotated[bool, typer.Option("--reload", help="Enable auto-reload")] = False,
|
|
26
|
+
):
|
|
27
|
+
"""Launch the agent tracing visualizer."""
|
|
28
|
+
from pythinker_code.vis.app import run_vis_server
|
|
29
|
+
|
|
30
|
+
# Determine bind address (same logic as pythinker web)
|
|
31
|
+
if host:
|
|
32
|
+
bind_host = host
|
|
33
|
+
elif network:
|
|
34
|
+
bind_host = "0.0.0.0"
|
|
35
|
+
else:
|
|
36
|
+
bind_host = "127.0.0.1"
|
|
37
|
+
|
|
38
|
+
run_vis_server(host=bind_host, port=port, open_browser=open_browser, reload=reload)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Web UI command for Pythinker CLI."""
|
|
2
|
+
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
cli = typer.Typer(help="Run Pythinker CLI web interface.")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@cli.callback(invoke_without_command=True)
|
|
11
|
+
def web(
|
|
12
|
+
ctx: typer.Context,
|
|
13
|
+
host: Annotated[
|
|
14
|
+
str | None,
|
|
15
|
+
typer.Option("--host", "-h", help="Bind to specific IP address"),
|
|
16
|
+
] = None,
|
|
17
|
+
network: Annotated[
|
|
18
|
+
bool,
|
|
19
|
+
typer.Option("--network", "-n", help="Enable network access (bind to 0.0.0.0)"),
|
|
20
|
+
] = False,
|
|
21
|
+
port: Annotated[int, typer.Option("--port", "-p", help="Port to bind to")] = 5494,
|
|
22
|
+
reload: Annotated[bool, typer.Option("--reload", help="Enable auto-reload")] = False,
|
|
23
|
+
open_browser: Annotated[
|
|
24
|
+
bool, typer.Option("--open/--no-open", help="Open browser automatically")
|
|
25
|
+
] = True,
|
|
26
|
+
auth_token: Annotated[
|
|
27
|
+
str | None,
|
|
28
|
+
typer.Option("--auth-token", help="Bearer token for API authentication."),
|
|
29
|
+
] = None,
|
|
30
|
+
allowed_origins: Annotated[
|
|
31
|
+
str | None,
|
|
32
|
+
typer.Option(
|
|
33
|
+
"--allowed-origins",
|
|
34
|
+
help="Comma-separated list of allowed Origin values.",
|
|
35
|
+
),
|
|
36
|
+
] = None,
|
|
37
|
+
dangerously_omit_auth: Annotated[
|
|
38
|
+
bool,
|
|
39
|
+
typer.Option(
|
|
40
|
+
"--dangerously-omit-auth",
|
|
41
|
+
help="Disable auth checks (dangerous in public networks).",
|
|
42
|
+
),
|
|
43
|
+
] = False,
|
|
44
|
+
restrict_sensitive_apis: Annotated[
|
|
45
|
+
bool | None,
|
|
46
|
+
typer.Option(
|
|
47
|
+
"--restrict-sensitive-apis/--no-restrict-sensitive-apis",
|
|
48
|
+
help="Disable sensitive APIs (config write, open-in, file access limits).",
|
|
49
|
+
),
|
|
50
|
+
] = None,
|
|
51
|
+
lan_only: Annotated[
|
|
52
|
+
bool,
|
|
53
|
+
typer.Option(
|
|
54
|
+
"--lan-only/--public",
|
|
55
|
+
help="Only allow access from local network (default) or allow public access.",
|
|
56
|
+
),
|
|
57
|
+
] = True,
|
|
58
|
+
):
|
|
59
|
+
"""Run Pythinker CLI web interface."""
|
|
60
|
+
from pythinker_code.web.app import run_web_server
|
|
61
|
+
|
|
62
|
+
# Determine bind address
|
|
63
|
+
if host:
|
|
64
|
+
bind_host = host
|
|
65
|
+
elif network:
|
|
66
|
+
bind_host = "0.0.0.0"
|
|
67
|
+
else:
|
|
68
|
+
bind_host = "127.0.0.1"
|
|
69
|
+
|
|
70
|
+
run_web_server(
|
|
71
|
+
host=bind_host,
|
|
72
|
+
port=port,
|
|
73
|
+
reload=reload,
|
|
74
|
+
open_browser=open_browser,
|
|
75
|
+
auth_token=auth_token,
|
|
76
|
+
allowed_origins=allowed_origins,
|
|
77
|
+
dangerously_omit_auth=dangerously_omit_auth,
|
|
78
|
+
restrict_sensitive_apis=restrict_sensitive_apis,
|
|
79
|
+
lan_only=lan_only,
|
|
80
|
+
)
|