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,727 @@
|
|
|
1
|
+
"""Skill specification discovery and loading utilities."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from collections.abc import Callable, Iterable, Iterator, Sequence
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Literal, cast
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
12
|
+
from pythinker_host import get_current_host
|
|
13
|
+
from pythinker_host.local import local_host
|
|
14
|
+
from pythinker_host.path import HostPath
|
|
15
|
+
|
|
16
|
+
from pythinker_code.skill.flow import Flow, FlowError
|
|
17
|
+
from pythinker_code.skill.flow.d2 import parse_d2_flowchart
|
|
18
|
+
from pythinker_code.skill.flow.mermaid import parse_mermaid_flowchart
|
|
19
|
+
from pythinker_code.utils.frontmatter import parse_frontmatter
|
|
20
|
+
from pythinker_code.utils.logging import logger
|
|
21
|
+
|
|
22
|
+
SkillType = Literal["standard", "flow"]
|
|
23
|
+
|
|
24
|
+
SkillScope = Literal["builtin", "user", "project", "extra"]
|
|
25
|
+
"""Where a skill was discovered from.
|
|
26
|
+
|
|
27
|
+
- ``builtin``: bundled with pythinker-code
|
|
28
|
+
- ``user``: from the user's home (``~/.pythinker/skills``, ``~/.agents/skills``, ...)
|
|
29
|
+
- ``project``: from the current project's working directory
|
|
30
|
+
(``<work_dir>/.pythinker/skills``, ``<work_dir>/.agents/skills``, ...)
|
|
31
|
+
- ``extra``: from ``extra_skill_dirs`` config or ``--skills-dir`` override
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True, slots=True)
|
|
36
|
+
class ScopedSkillsRoot:
|
|
37
|
+
"""A skills directory paired with the scope it belongs to."""
|
|
38
|
+
|
|
39
|
+
root: HostPath
|
|
40
|
+
scope: SkillScope
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_builtin_skills_dir() -> Path:
|
|
44
|
+
"""
|
|
45
|
+
Get the built-in skills directory path.
|
|
46
|
+
"""
|
|
47
|
+
if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
|
|
48
|
+
# Running in a PyInstaller bundle; use _MEIPASS to locate bundled resources
|
|
49
|
+
# reliably on all platforms (avoids __file__ path issues in frozen envs on Windows)
|
|
50
|
+
meipass = cast(str, sys._MEIPASS) # pyright: ignore[reportAttributeAccessIssue,reportUnknownMemberType]
|
|
51
|
+
return Path(meipass) / "pythinker_code" / "skills"
|
|
52
|
+
return Path(__file__).parent.parent / "skills"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _get_user_generic_skills_dir_candidates() -> tuple[HostPath, ...]:
|
|
56
|
+
"""
|
|
57
|
+
Get user-level generic skills directory candidates in priority order.
|
|
58
|
+
|
|
59
|
+
Generic group: ``~/.config/agents/skills`` > ``~/.agents/skills``
|
|
60
|
+
"""
|
|
61
|
+
return (
|
|
62
|
+
HostPath.home() / ".config" / "agents" / "skills",
|
|
63
|
+
HostPath.home() / ".agents" / "skills",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _get_user_brand_skills_dir_candidates() -> tuple[HostPath, ...]:
|
|
68
|
+
"""
|
|
69
|
+
Get user-level brand skills directory candidates in priority order.
|
|
70
|
+
|
|
71
|
+
Brand group: ``~/.pythinker/skills`` > ``~/.claude/skills`` > ``~/.codex/skills``
|
|
72
|
+
"""
|
|
73
|
+
return (
|
|
74
|
+
HostPath.home() / ".pythinker" / "skills",
|
|
75
|
+
HostPath.home() / ".claude" / "skills",
|
|
76
|
+
HostPath.home() / ".codex" / "skills",
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _get_project_generic_skills_dir_candidates(work_dir: HostPath) -> tuple[HostPath, ...]:
|
|
81
|
+
"""
|
|
82
|
+
Get project-level generic skills directory candidates.
|
|
83
|
+
|
|
84
|
+
Generic group: ``.agents/skills``
|
|
85
|
+
"""
|
|
86
|
+
return (work_dir / ".agents" / "skills",)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _get_project_brand_skills_dir_candidates(work_dir: HostPath) -> tuple[HostPath, ...]:
|
|
90
|
+
"""
|
|
91
|
+
Get project-level brand skills directory candidates in priority order.
|
|
92
|
+
|
|
93
|
+
Brand group: ``.pythinker/skills`` > ``.claude/skills`` > ``.codex/skills``
|
|
94
|
+
"""
|
|
95
|
+
return (
|
|
96
|
+
work_dir / ".pythinker" / "skills",
|
|
97
|
+
work_dir / ".claude" / "skills",
|
|
98
|
+
work_dir / ".codex" / "skills",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _supports_builtin_skills() -> bool:
|
|
103
|
+
"""Return True when the active Host backend can read bundled skills."""
|
|
104
|
+
current_name = get_current_host().name
|
|
105
|
+
return current_name in (local_host.name, "acp")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
async def find_first_existing_dir(candidates: Iterable[HostPath]) -> HostPath | None:
|
|
109
|
+
"""
|
|
110
|
+
Return the first existing directory from candidates.
|
|
111
|
+
"""
|
|
112
|
+
for candidate in candidates:
|
|
113
|
+
if await candidate.is_dir():
|
|
114
|
+
return candidate
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
async def find_user_skills_dirs(
|
|
119
|
+
*,
|
|
120
|
+
merge_brands: bool = False,
|
|
121
|
+
) -> list[HostPath]:
|
|
122
|
+
"""
|
|
123
|
+
Return user-level skills directories from both brand and generic groups.
|
|
124
|
+
|
|
125
|
+
The brand group comes first because brand-specific directories have
|
|
126
|
+
higher specificity. When *merge_brands* is ``False`` (default), only the
|
|
127
|
+
first existing brand directory is used. When ``True``, all existing brand
|
|
128
|
+
directories are included (priority order: pythinker > claude > codex).
|
|
129
|
+
"""
|
|
130
|
+
dirs: list[HostPath] = []
|
|
131
|
+
if merge_brands:
|
|
132
|
+
for candidate in _get_user_brand_skills_dir_candidates():
|
|
133
|
+
if await candidate.is_dir():
|
|
134
|
+
dirs.append(candidate)
|
|
135
|
+
else:
|
|
136
|
+
if brand := await find_first_existing_dir(
|
|
137
|
+
_get_user_brand_skills_dir_candidates(),
|
|
138
|
+
):
|
|
139
|
+
dirs.append(brand)
|
|
140
|
+
if generic := await find_first_existing_dir(
|
|
141
|
+
_get_user_generic_skills_dir_candidates(),
|
|
142
|
+
):
|
|
143
|
+
dirs.append(generic)
|
|
144
|
+
return dirs
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
async def find_project_skills_dirs(
|
|
148
|
+
work_dir: HostPath,
|
|
149
|
+
*,
|
|
150
|
+
merge_brands: bool = False,
|
|
151
|
+
) -> list[HostPath]:
|
|
152
|
+
"""
|
|
153
|
+
Return project-level skills directories from both brand and generic groups.
|
|
154
|
+
|
|
155
|
+
Discovery starts at the **project root** (the nearest ``.git`` ancestor
|
|
156
|
+
of ``work_dir``), so launching pythinker-code from a subdirectory — for example
|
|
157
|
+
a monorepo package — still surfaces skills defined at the repository root.
|
|
158
|
+
Falls back to ``work_dir`` itself when no ``.git`` marker is found, to
|
|
159
|
+
avoid accidentally walking up into unrelated parent trees.
|
|
160
|
+
|
|
161
|
+
The brand group comes first because brand-specific directories have
|
|
162
|
+
higher specificity. When *merge_brands* is ``False`` (default), only the
|
|
163
|
+
first existing brand directory is used. When ``True``, all existing brand
|
|
164
|
+
directories are included (priority order: pythinker > claude > codex).
|
|
165
|
+
"""
|
|
166
|
+
from pythinker_code.utils.path import find_project_root
|
|
167
|
+
|
|
168
|
+
work_dir = await find_project_root(work_dir)
|
|
169
|
+
dirs: list[HostPath] = []
|
|
170
|
+
brand_candidates = _get_project_brand_skills_dir_candidates(work_dir)
|
|
171
|
+
if merge_brands:
|
|
172
|
+
for candidate in brand_candidates:
|
|
173
|
+
if await candidate.is_dir():
|
|
174
|
+
dirs.append(candidate)
|
|
175
|
+
else:
|
|
176
|
+
if brand := await find_first_existing_dir(brand_candidates):
|
|
177
|
+
dirs.append(brand)
|
|
178
|
+
generic_candidates = _get_project_generic_skills_dir_candidates(work_dir)
|
|
179
|
+
if generic := await find_first_existing_dir(generic_candidates):
|
|
180
|
+
dirs.append(generic)
|
|
181
|
+
return dirs
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
async def resolve_skills_roots(
|
|
185
|
+
work_dir: HostPath,
|
|
186
|
+
*,
|
|
187
|
+
skills_dirs: Sequence[HostPath] | None = None,
|
|
188
|
+
merge_brands: bool = False,
|
|
189
|
+
extra_skill_dirs: Sequence[str] | None = None,
|
|
190
|
+
) -> list[ScopedSkillsRoot]:
|
|
191
|
+
"""Resolve layered skill roots with their scope labels.
|
|
192
|
+
|
|
193
|
+
Scope labels let the system-prompt renderer group skills so the model can
|
|
194
|
+
tell a user-scope skill from a project-scope one. Roots are ordered
|
|
195
|
+
**highest priority first** — ``discover_skills_from_roots`` keeps the first
|
|
196
|
+
match per skill name, so this ordering controls which scope "wins" when the
|
|
197
|
+
same skill name exists in several places:
|
|
198
|
+
|
|
199
|
+
Project > User > Extra(config) > Extra(plugins) > Built-in
|
|
200
|
+
|
|
201
|
+
``extra_skill_dirs`` is **additive** (not an override) and scoped as
|
|
202
|
+
``extra``; each entry may be:
|
|
203
|
+
|
|
204
|
+
- An absolute path (used as-is)
|
|
205
|
+
- ``~/...`` (expanded against ``$HOME``)
|
|
206
|
+
- A relative path (resolved against the **project root** — the nearest
|
|
207
|
+
``.git`` directory above ``work_dir``, or ``work_dir`` itself if none)
|
|
208
|
+
|
|
209
|
+
Non-existent entries are silently dropped. Duplicates collapse to one.
|
|
210
|
+
"""
|
|
211
|
+
from pythinker_code.plugin.manager import get_plugins_dir
|
|
212
|
+
from pythinker_code.utils.path import find_project_root
|
|
213
|
+
|
|
214
|
+
scoped: list[ScopedSkillsRoot] = []
|
|
215
|
+
seen: set[str] = set()
|
|
216
|
+
|
|
217
|
+
def _append(root: HostPath, scope: SkillScope) -> None:
|
|
218
|
+
# Dedupe so symlinks, ``..`` segments, and trailing slashes don't
|
|
219
|
+
# produce phantom duplicate entries in the system prompt. Note that
|
|
220
|
+
# ``HostPath.canonical()`` only normalizes path segments; it does NOT
|
|
221
|
+
# resolve symlinks. So on local backends we walk symlinks via
|
|
222
|
+
# ``pathlib.Path.resolve()`` first, then fall through to
|
|
223
|
+
# ``canonical()`` for ``..`` / trailing-slash normalization. Non-local
|
|
224
|
+
# backends keep the canonical-only path because ``Path.resolve()``
|
|
225
|
+
# would walk the wrong filesystem.
|
|
226
|
+
resolved = root
|
|
227
|
+
if get_current_host().name == local_host.name:
|
|
228
|
+
try:
|
|
229
|
+
local_resolved = Path(str(root)).resolve()
|
|
230
|
+
except OSError:
|
|
231
|
+
# Keep the original path; canonical() below still does what it can.
|
|
232
|
+
pass
|
|
233
|
+
else:
|
|
234
|
+
resolved = HostPath.unsafe_from_local_path(local_resolved)
|
|
235
|
+
try:
|
|
236
|
+
canon = resolved.canonical()
|
|
237
|
+
except OSError:
|
|
238
|
+
canon = resolved
|
|
239
|
+
key = str(canon)
|
|
240
|
+
if key in seen:
|
|
241
|
+
return
|
|
242
|
+
seen.add(key)
|
|
243
|
+
scoped.append(ScopedSkillsRoot(root=canon, scope=scope))
|
|
244
|
+
|
|
245
|
+
if skills_dirs:
|
|
246
|
+
# --skills-dir overrides user/project auto-discovery, but runs at the
|
|
247
|
+
# top of priority order so its skills take precedence, matching the
|
|
248
|
+
# "closer to the user's explicit intent, higher priority" principle.
|
|
249
|
+
for d in skills_dirs:
|
|
250
|
+
_append(d, "extra")
|
|
251
|
+
else:
|
|
252
|
+
for d in await find_project_skills_dirs(work_dir, merge_brands=merge_brands):
|
|
253
|
+
_append(d, "project")
|
|
254
|
+
for d in await find_user_skills_dirs(merge_brands=merge_brands):
|
|
255
|
+
_append(d, "user")
|
|
256
|
+
|
|
257
|
+
if extra_skill_dirs:
|
|
258
|
+
project_root = await find_project_root(work_dir)
|
|
259
|
+
for raw in extra_skill_dirs:
|
|
260
|
+
resolved = _resolve_extra_skill_dir(raw, project_root)
|
|
261
|
+
if resolved is None:
|
|
262
|
+
continue
|
|
263
|
+
try:
|
|
264
|
+
is_dir = await resolved.is_dir()
|
|
265
|
+
except OSError as exc:
|
|
266
|
+
logger.info(
|
|
267
|
+
"Skipping extra_skill_dirs entry {path}: {error}",
|
|
268
|
+
path=resolved,
|
|
269
|
+
error=exc,
|
|
270
|
+
)
|
|
271
|
+
continue
|
|
272
|
+
if not is_dir:
|
|
273
|
+
continue
|
|
274
|
+
_append(resolved, "extra")
|
|
275
|
+
|
|
276
|
+
# Plugins are always discoverable; treat as "extra" origin for prompt
|
|
277
|
+
# grouping but place them below config-declared extras (user intent wins).
|
|
278
|
+
plugins_path = get_plugins_dir()
|
|
279
|
+
try:
|
|
280
|
+
plugins_is_dir = plugins_path.is_dir()
|
|
281
|
+
except OSError:
|
|
282
|
+
plugins_is_dir = False
|
|
283
|
+
if plugins_is_dir:
|
|
284
|
+
_append(HostPath.unsafe_from_local_path(plugins_path), "extra")
|
|
285
|
+
|
|
286
|
+
if _supports_builtin_skills():
|
|
287
|
+
_append(
|
|
288
|
+
HostPath.unsafe_from_local_path(get_builtin_skills_dir()),
|
|
289
|
+
"builtin",
|
|
290
|
+
)
|
|
291
|
+
return scoped
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _resolve_extra_skill_dir(raw: str, project_root: HostPath) -> HostPath | None:
|
|
295
|
+
"""Resolve a single ``extra_skill_dirs`` entry to a HostPath, or None on error.
|
|
296
|
+
|
|
297
|
+
Expands ``~``; treats non-absolute entries as relative to *project_root*.
|
|
298
|
+
"""
|
|
299
|
+
if not raw:
|
|
300
|
+
return None
|
|
301
|
+
try:
|
|
302
|
+
p = Path(raw).expanduser()
|
|
303
|
+
except (RuntimeError, OSError):
|
|
304
|
+
# ``expanduser`` can raise on malformed HOME or platform-specific
|
|
305
|
+
# oddities; treat any failure as a dropped entry rather than let it
|
|
306
|
+
# kill skill discovery.
|
|
307
|
+
return None
|
|
308
|
+
if p.is_absolute():
|
|
309
|
+
return HostPath.unsafe_from_local_path(p)
|
|
310
|
+
return HostPath.unsafe_from_local_path(Path(str(project_root)) / p)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def normalize_skill_name(name: str) -> str:
|
|
314
|
+
"""Normalize a skill name for lookup."""
|
|
315
|
+
return name.casefold()
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def index_skills(skills: Iterable[Skill]) -> dict[str, Skill]:
|
|
319
|
+
"""Build a lookup table for skills by normalized name."""
|
|
320
|
+
return {normalize_skill_name(skill.name): skill for skill in skills}
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
async def discover_skills_from_roots(
|
|
324
|
+
scoped_roots: Iterable[ScopedSkillsRoot],
|
|
325
|
+
) -> list[Skill]:
|
|
326
|
+
"""Discover skills from scope-labelled roots.
|
|
327
|
+
|
|
328
|
+
When the same skill name appears in multiple roots, the **first occurrence
|
|
329
|
+
wins** (higher priority roots come first in :func:`resolve_skills_roots`'s
|
|
330
|
+
output). Each returned :class:`Skill` carries the scope of the root it came
|
|
331
|
+
from.
|
|
332
|
+
"""
|
|
333
|
+
skills_by_name: dict[str, Skill] = {}
|
|
334
|
+
for scoped in scoped_roots:
|
|
335
|
+
for skill in await discover_skills(scoped.root, scope=scoped.scope):
|
|
336
|
+
skills_by_name.setdefault(normalize_skill_name(skill.name), skill)
|
|
337
|
+
return sorted(skills_by_name.values(), key=lambda s: s.name)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
_SCOPE_HEADINGS: tuple[tuple[SkillScope, str], ...] = (
|
|
341
|
+
("project", "Project"),
|
|
342
|
+
("user", "User"),
|
|
343
|
+
("extra", "Extra"),
|
|
344
|
+
("builtin", "Built-in"),
|
|
345
|
+
)
|
|
346
|
+
"""Scope headings for the system-prompt skills block, in the order they appear.
|
|
347
|
+
|
|
348
|
+
Project first (most specific), then User, Extra, and finally Built-in (least
|
|
349
|
+
specific). Matches the priority the model should give when multiple scopes
|
|
350
|
+
define a skill with the same name.
|
|
351
|
+
"""
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def format_skills_for_prompt(skills: Iterable[Skill]) -> str:
|
|
355
|
+
"""Render *skills* grouped by scope for injection into the system prompt.
|
|
356
|
+
|
|
357
|
+
Output layout::
|
|
358
|
+
|
|
359
|
+
### Project
|
|
360
|
+
- <name>
|
|
361
|
+
- Path: <skill_md_file>
|
|
362
|
+
- Description: <description>
|
|
363
|
+
|
|
364
|
+
### User
|
|
365
|
+
- ...
|
|
366
|
+
|
|
367
|
+
Empty scope groups are omitted. The model uses these headings to distinguish
|
|
368
|
+
user-scope and project-scope skills when responding to prompts like
|
|
369
|
+
"the skill in my project".
|
|
370
|
+
"""
|
|
371
|
+
grouped: dict[SkillScope, list[Skill]] = {s: [] for s, _ in _SCOPE_HEADINGS}
|
|
372
|
+
for skill in skills:
|
|
373
|
+
grouped.setdefault(skill.scope, []).append(skill)
|
|
374
|
+
|
|
375
|
+
sections: list[str] = []
|
|
376
|
+
for scope, heading in _SCOPE_HEADINGS:
|
|
377
|
+
bucket = grouped.get(scope) or []
|
|
378
|
+
if not bucket:
|
|
379
|
+
continue
|
|
380
|
+
lines = [f"### {heading}"]
|
|
381
|
+
for skill in sorted(bucket, key=lambda s: s.name):
|
|
382
|
+
lines.append(f"- {skill.name}")
|
|
383
|
+
lines.append(f" - Path: {skill.skill_md_file}")
|
|
384
|
+
lines.append(f" - Description: {skill.description}")
|
|
385
|
+
sections.append("\n".join(lines))
|
|
386
|
+
|
|
387
|
+
if not sections:
|
|
388
|
+
return "No skills found."
|
|
389
|
+
return "\n\n".join(sections)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
async def read_skill_text(skill: Skill) -> str | None:
|
|
393
|
+
"""Read the SKILL.md contents for a skill."""
|
|
394
|
+
try:
|
|
395
|
+
return (await skill.skill_md_file.read_text(encoding="utf-8")).strip()
|
|
396
|
+
except OSError as exc:
|
|
397
|
+
logger.warning(
|
|
398
|
+
"Failed to read skill file {path}: {error}",
|
|
399
|
+
path=skill.skill_md_file,
|
|
400
|
+
error=exc,
|
|
401
|
+
)
|
|
402
|
+
return None
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
class Skill(BaseModel):
|
|
406
|
+
"""Information about a single skill."""
|
|
407
|
+
|
|
408
|
+
model_config = ConfigDict(extra="ignore", arbitrary_types_allowed=True)
|
|
409
|
+
|
|
410
|
+
name: str
|
|
411
|
+
description: str
|
|
412
|
+
type: SkillType = "standard"
|
|
413
|
+
dir: HostPath
|
|
414
|
+
"""The skill's resource directory. For subdirectory-form skills this is the
|
|
415
|
+
per-skill directory; for flat ``.md`` skills it is the parent skills root."""
|
|
416
|
+
skill_md_file: HostPath
|
|
417
|
+
"""Path to the markdown file that holds the skill body. For subdirectory
|
|
418
|
+
skills this is ``dir/SKILL.md``; for flat skills this is the ``.md`` file
|
|
419
|
+
itself."""
|
|
420
|
+
flow: Flow | None = None
|
|
421
|
+
scope: SkillScope = Field(...)
|
|
422
|
+
"""Which scope this skill was discovered from. Required; discovery always
|
|
423
|
+
stamps it. The system-prompt renderer groups skills by this label so the
|
|
424
|
+
model can tell user-scope from project-scope skills."""
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
async def discover_skills(
|
|
428
|
+
skills_dir: HostPath,
|
|
429
|
+
*,
|
|
430
|
+
scope: SkillScope,
|
|
431
|
+
) -> list[Skill]:
|
|
432
|
+
"""Discover all skills in the given directory.
|
|
433
|
+
|
|
434
|
+
Two layouts are supported side by side:
|
|
435
|
+
|
|
436
|
+
1. **Subdirectory**: ``<skills_dir>/<name>/SKILL.md`` — the canonical layout.
|
|
437
|
+
2. **Flat**: ``<skills_dir>/<name>.md`` — a single-file skill. Handy for
|
|
438
|
+
users migrating from agent tooling that stores skills as flat markdown
|
|
439
|
+
files.
|
|
440
|
+
|
|
441
|
+
When both forms share the same normalized name in one directory, the
|
|
442
|
+
subdirectory form wins and a warning is emitted.
|
|
443
|
+
|
|
444
|
+
*scope* is stamped onto each discovered :class:`Skill` so the system-prompt
|
|
445
|
+
renderer can group skills by where they came from (user / project / extra /
|
|
446
|
+
builtin).
|
|
447
|
+
"""
|
|
448
|
+
try:
|
|
449
|
+
is_dir = await skills_dir.is_dir()
|
|
450
|
+
except OSError as exc:
|
|
451
|
+
logger.warning(
|
|
452
|
+
"Cannot stat skills directory {path}, skipping: {error}",
|
|
453
|
+
path=skills_dir,
|
|
454
|
+
error=exc,
|
|
455
|
+
)
|
|
456
|
+
return []
|
|
457
|
+
if not is_dir:
|
|
458
|
+
return []
|
|
459
|
+
|
|
460
|
+
skills_by_name: dict[str, Skill] = {}
|
|
461
|
+
|
|
462
|
+
# Pass 1: subdirectory form (canonical).
|
|
463
|
+
try:
|
|
464
|
+
async for entry in skills_dir.iterdir():
|
|
465
|
+
try:
|
|
466
|
+
if not await entry.is_dir():
|
|
467
|
+
continue
|
|
468
|
+
skill_md = entry / "SKILL.md"
|
|
469
|
+
if not await skill_md.is_file():
|
|
470
|
+
continue
|
|
471
|
+
content = await skill_md.read_text(encoding="utf-8")
|
|
472
|
+
except OSError as exc:
|
|
473
|
+
logger.info(
|
|
474
|
+
"Skipping unreadable skill entry {path}: {error}",
|
|
475
|
+
path=entry,
|
|
476
|
+
error=exc,
|
|
477
|
+
)
|
|
478
|
+
continue
|
|
479
|
+
try:
|
|
480
|
+
skill = parse_skill_text(
|
|
481
|
+
content, dir_path=entry, skill_md_file=skill_md, scope=scope
|
|
482
|
+
)
|
|
483
|
+
except Exception as exc:
|
|
484
|
+
logger.info("Skipping invalid skill at {}: {}", skill_md, exc)
|
|
485
|
+
continue
|
|
486
|
+
skills_by_name[normalize_skill_name(skill.name)] = skill
|
|
487
|
+
except OSError as exc:
|
|
488
|
+
logger.warning(
|
|
489
|
+
"Failed to iterate skills directory {path}: {error}",
|
|
490
|
+
path=skills_dir,
|
|
491
|
+
error=exc,
|
|
492
|
+
)
|
|
493
|
+
return sorted(skills_by_name.values(), key=lambda s: s.name)
|
|
494
|
+
|
|
495
|
+
# Pass 2: flat ``.md`` form, skipping names already claimed by a subdir.
|
|
496
|
+
try:
|
|
497
|
+
async for entry in skills_dir.iterdir():
|
|
498
|
+
try:
|
|
499
|
+
if await entry.is_dir():
|
|
500
|
+
continue
|
|
501
|
+
except OSError as exc:
|
|
502
|
+
logger.info(
|
|
503
|
+
"Skipping unstattable entry {path}: {error}",
|
|
504
|
+
path=entry,
|
|
505
|
+
error=exc,
|
|
506
|
+
)
|
|
507
|
+
continue
|
|
508
|
+
if not entry.name.lower().endswith(".md"):
|
|
509
|
+
continue
|
|
510
|
+
# A bare ``SKILL.md`` at the top of skills_dir is a stray marker file,
|
|
511
|
+
# not a skill — it has no per-skill directory to associate with.
|
|
512
|
+
if entry.name.upper() == "SKILL.MD":
|
|
513
|
+
continue
|
|
514
|
+
|
|
515
|
+
try:
|
|
516
|
+
content = await entry.read_text(encoding="utf-8")
|
|
517
|
+
skill = parse_skill_text(
|
|
518
|
+
content,
|
|
519
|
+
dir_path=skills_dir,
|
|
520
|
+
skill_md_file=entry,
|
|
521
|
+
scope=scope,
|
|
522
|
+
flat_file=entry,
|
|
523
|
+
)
|
|
524
|
+
except Exception as exc:
|
|
525
|
+
logger.info("Skipping invalid flat skill at {}: {}", entry, exc)
|
|
526
|
+
continue
|
|
527
|
+
|
|
528
|
+
key = normalize_skill_name(skill.name)
|
|
529
|
+
if key in skills_by_name:
|
|
530
|
+
logger.warning(
|
|
531
|
+
"Flat skill {flat} shadowed by subdirectory skill of the same "
|
|
532
|
+
"name at {sub}; the subdirectory version is used.",
|
|
533
|
+
flat=entry,
|
|
534
|
+
sub=skills_by_name[key].dir,
|
|
535
|
+
)
|
|
536
|
+
continue
|
|
537
|
+
skills_by_name[key] = skill
|
|
538
|
+
except OSError as exc:
|
|
539
|
+
logger.warning(
|
|
540
|
+
"Failed to iterate skills directory for flat scan {path}: {error}",
|
|
541
|
+
path=skills_dir,
|
|
542
|
+
error=exc,
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
return sorted(skills_by_name.values(), key=lambda s: s.name)
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
_DESCRIPTION_FALLBACK_MAX_LEN = 240
|
|
549
|
+
"""Max length for the body-derived description fallback. Longer first lines are
|
|
550
|
+
truncated with an ellipsis. The spec caps ``description`` at 1024 chars, but
|
|
551
|
+
for a fallback used when the user forgot to set one, a tighter cap keeps the
|
|
552
|
+
system prompt compact.
|
|
553
|
+
"""
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def parse_skill_text(
|
|
557
|
+
content: str,
|
|
558
|
+
*,
|
|
559
|
+
dir_path: HostPath,
|
|
560
|
+
skill_md_file: HostPath,
|
|
561
|
+
scope: SkillScope,
|
|
562
|
+
flat_file: HostPath | None = None,
|
|
563
|
+
) -> Skill:
|
|
564
|
+
"""Parse skill markdown content to extract name and description.
|
|
565
|
+
|
|
566
|
+
*flat_file* is passed only to compute the default ``name`` from the filename
|
|
567
|
+
stem when there is no frontmatter ``name``. *skill_md_file* points at the
|
|
568
|
+
actual markdown file (``dir_path/SKILL.md`` for subdir skills, the ``.md``
|
|
569
|
+
itself for flat ones) and is stamped onto the returned :class:`Skill`.
|
|
570
|
+
|
|
571
|
+
The ``description`` resolution applies the same three-step chain regardless
|
|
572
|
+
of form: frontmatter ``description`` → first non-empty body line →
|
|
573
|
+
``"No description provided."``.
|
|
574
|
+
"""
|
|
575
|
+
frontmatter = parse_frontmatter(content) or {}
|
|
576
|
+
|
|
577
|
+
default_name = _strip_md_suffix(flat_file.name) if flat_file is not None else dir_path.name
|
|
578
|
+
name = frontmatter.get("name") or default_name
|
|
579
|
+
|
|
580
|
+
description = frontmatter.get("description")
|
|
581
|
+
if not description:
|
|
582
|
+
body_fallback = _first_meaningful_line(content)
|
|
583
|
+
description = _truncate(body_fallback) if body_fallback else "No description provided."
|
|
584
|
+
|
|
585
|
+
skill_type = frontmatter.get("type") or "standard"
|
|
586
|
+
if skill_type not in ("standard", "flow"):
|
|
587
|
+
raise ValueError(f'Invalid skill type "{skill_type}"')
|
|
588
|
+
flow = None
|
|
589
|
+
if skill_type == "flow":
|
|
590
|
+
try:
|
|
591
|
+
flow = _parse_flow_from_skill(content)
|
|
592
|
+
except ValueError as exc:
|
|
593
|
+
logger.error("Failed to parse flow skill {name}: {error}", name=name, error=exc)
|
|
594
|
+
skill_type = "standard"
|
|
595
|
+
flow = None
|
|
596
|
+
|
|
597
|
+
return Skill(
|
|
598
|
+
name=name,
|
|
599
|
+
description=description,
|
|
600
|
+
type=skill_type,
|
|
601
|
+
dir=dir_path,
|
|
602
|
+
skill_md_file=skill_md_file,
|
|
603
|
+
flow=flow,
|
|
604
|
+
scope=scope,
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
def _strip_md_suffix(filename: str) -> str:
|
|
609
|
+
"""Return *filename* without a trailing ``.md`` (case-insensitive)."""
|
|
610
|
+
if filename.lower().endswith(".md"):
|
|
611
|
+
return filename[: -len(".md")]
|
|
612
|
+
return filename
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
def _truncate(text: str, limit: int = _DESCRIPTION_FALLBACK_MAX_LEN) -> str:
|
|
616
|
+
"""Clip *text* to *limit* chars, appending an ellipsis if truncated."""
|
|
617
|
+
text = text.strip()
|
|
618
|
+
if len(text) <= limit:
|
|
619
|
+
return text
|
|
620
|
+
# Reserve one char for the ellipsis
|
|
621
|
+
return text[: limit - 1].rstrip() + "…"
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
def _first_meaningful_line(content: str) -> str | None:
|
|
625
|
+
"""Return the first non-empty line of *content*'s body, or None.
|
|
626
|
+
|
|
627
|
+
Uses :func:`strip_frontmatter` to drop any leading YAML block, so the
|
|
628
|
+
frontmatter-skipping logic lives in one place (see
|
|
629
|
+
:mod:`pythinker_code.utils.frontmatter`). A malformed frontmatter opener that
|
|
630
|
+
never closes leaves ``strip_frontmatter`` a no-op; skip standalone
|
|
631
|
+
``---`` delimiter lines here so the stray opener does not silently
|
|
632
|
+
become the fallback description.
|
|
633
|
+
"""
|
|
634
|
+
from pythinker_code.utils.frontmatter import strip_frontmatter
|
|
635
|
+
|
|
636
|
+
body = strip_frontmatter(content)
|
|
637
|
+
for line in body.splitlines():
|
|
638
|
+
stripped = line.strip()
|
|
639
|
+
if not stripped or stripped == "---":
|
|
640
|
+
continue
|
|
641
|
+
return stripped
|
|
642
|
+
return None
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
def _parse_flow_from_skill(content: str) -> Flow:
|
|
646
|
+
for lang, code in _iter_fenced_codeblocks(content):
|
|
647
|
+
if lang == "mermaid":
|
|
648
|
+
return _parse_flow_block(parse_mermaid_flowchart, code)
|
|
649
|
+
if lang == "d2":
|
|
650
|
+
return _parse_flow_block(parse_d2_flowchart, code)
|
|
651
|
+
raise ValueError("Flow skills require a mermaid or d2 code block in SKILL.md.")
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
def _parse_flow_block(parser: Callable[[str], Flow], code: str) -> Flow:
|
|
655
|
+
try:
|
|
656
|
+
return parser(code)
|
|
657
|
+
except FlowError as exc:
|
|
658
|
+
raise ValueError(f"Invalid flow diagram: {exc}") from exc
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
def _iter_fenced_codeblocks(content: str) -> Iterator[tuple[str, str]]:
|
|
662
|
+
fence = ""
|
|
663
|
+
fence_char = ""
|
|
664
|
+
lang = ""
|
|
665
|
+
buf: list[str] = []
|
|
666
|
+
in_block = False
|
|
667
|
+
|
|
668
|
+
for line in content.splitlines():
|
|
669
|
+
stripped = line.lstrip()
|
|
670
|
+
if not in_block:
|
|
671
|
+
if match := _parse_fence_open(stripped):
|
|
672
|
+
fence, fence_char, info = match
|
|
673
|
+
lang = _normalize_code_lang(info)
|
|
674
|
+
in_block = True
|
|
675
|
+
buf = []
|
|
676
|
+
continue
|
|
677
|
+
|
|
678
|
+
if _is_fence_close(stripped, fence_char, len(fence)):
|
|
679
|
+
yield lang, "\n".join(buf).strip("\n")
|
|
680
|
+
in_block = False
|
|
681
|
+
fence = ""
|
|
682
|
+
fence_char = ""
|
|
683
|
+
lang = ""
|
|
684
|
+
buf = []
|
|
685
|
+
continue
|
|
686
|
+
|
|
687
|
+
buf.append(line)
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
def _normalize_code_lang(info: str) -> str:
|
|
691
|
+
if not info:
|
|
692
|
+
return ""
|
|
693
|
+
lang = info.split()[0].strip().lower()
|
|
694
|
+
if lang.startswith("{") and lang.endswith("}"):
|
|
695
|
+
lang = lang[1:-1].strip()
|
|
696
|
+
return lang
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
def _parse_fence_open(line: str) -> tuple[str, str, str] | None:
|
|
700
|
+
if not line or line[0] not in ("`", "~"):
|
|
701
|
+
return None
|
|
702
|
+
fence_char = line[0]
|
|
703
|
+
count = 0
|
|
704
|
+
for ch in line:
|
|
705
|
+
if ch == fence_char:
|
|
706
|
+
count += 1
|
|
707
|
+
else:
|
|
708
|
+
break
|
|
709
|
+
if count < 3:
|
|
710
|
+
return None
|
|
711
|
+
fence = fence_char * count
|
|
712
|
+
info = line[count:].strip()
|
|
713
|
+
return fence, fence_char, info
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
def _is_fence_close(line: str, fence_char: str, fence_len: int) -> bool:
|
|
717
|
+
if not fence_char or not line or line[0] != fence_char:
|
|
718
|
+
return False
|
|
719
|
+
count = 0
|
|
720
|
+
for ch in line:
|
|
721
|
+
if ch == fence_char:
|
|
722
|
+
count += 1
|
|
723
|
+
else:
|
|
724
|
+
break
|
|
725
|
+
if count < fence_len:
|
|
726
|
+
return False
|
|
727
|
+
return not line[count:].strip()
|