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,589 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The local version of the Grep tool using ripgrep.
|
|
3
|
+
Be cautious that `HostPath` is not used in this implementation.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import os
|
|
8
|
+
import platform
|
|
9
|
+
import re
|
|
10
|
+
import shutil
|
|
11
|
+
import stat
|
|
12
|
+
import tarfile
|
|
13
|
+
import tempfile
|
|
14
|
+
import zipfile
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import override
|
|
17
|
+
|
|
18
|
+
import aiohttp
|
|
19
|
+
from pydantic import BaseModel, Field
|
|
20
|
+
from pythinker_core.tooling import CallableTool2, ToolError, ToolReturnValue
|
|
21
|
+
|
|
22
|
+
import pythinker_code
|
|
23
|
+
from pythinker_code.share import get_share_dir
|
|
24
|
+
from pythinker_code.tools.utils import ToolResultBuilder, load_desc
|
|
25
|
+
from pythinker_code.utils.aiohttp import new_client_session
|
|
26
|
+
from pythinker_code.utils.logging import logger
|
|
27
|
+
from pythinker_code.utils.sensitive import is_sensitive_file, sensitive_file_warning
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Params(BaseModel):
|
|
31
|
+
pattern: str = Field(
|
|
32
|
+
description="The regular expression pattern to search for in file contents"
|
|
33
|
+
)
|
|
34
|
+
path: str = Field(
|
|
35
|
+
description=(
|
|
36
|
+
"File or directory to search in. Defaults to current working directory. "
|
|
37
|
+
"If specified, it must be an absolute path."
|
|
38
|
+
),
|
|
39
|
+
default=".",
|
|
40
|
+
)
|
|
41
|
+
glob: str | None = Field(
|
|
42
|
+
description=(
|
|
43
|
+
"Glob pattern to filter files (e.g. `*.js`, `*.{ts,tsx}`). No filter by default."
|
|
44
|
+
),
|
|
45
|
+
default=None,
|
|
46
|
+
)
|
|
47
|
+
output_mode: str = Field(
|
|
48
|
+
description=(
|
|
49
|
+
"`content`: Show matching lines (supports `-B`, `-A`, `-C`, `-n`, `head_limit`); "
|
|
50
|
+
"`files_with_matches`: Show file paths (supports `head_limit`); "
|
|
51
|
+
"`count_matches`: Show total number of matches. "
|
|
52
|
+
"Defaults to `files_with_matches`."
|
|
53
|
+
),
|
|
54
|
+
default="files_with_matches",
|
|
55
|
+
)
|
|
56
|
+
before_context: int | None = Field(
|
|
57
|
+
alias="-B",
|
|
58
|
+
description=(
|
|
59
|
+
"Number of lines to show before each match (the `-B` option). "
|
|
60
|
+
"Requires `output_mode` to be `content`."
|
|
61
|
+
),
|
|
62
|
+
default=None,
|
|
63
|
+
)
|
|
64
|
+
after_context: int | None = Field(
|
|
65
|
+
alias="-A",
|
|
66
|
+
description=(
|
|
67
|
+
"Number of lines to show after each match (the `-A` option). "
|
|
68
|
+
"Requires `output_mode` to be `content`."
|
|
69
|
+
),
|
|
70
|
+
default=None,
|
|
71
|
+
)
|
|
72
|
+
context: int | None = Field(
|
|
73
|
+
alias="-C",
|
|
74
|
+
description=(
|
|
75
|
+
"Number of lines to show before and after each match (the `-C` option). "
|
|
76
|
+
"Requires `output_mode` to be `content`."
|
|
77
|
+
),
|
|
78
|
+
default=None,
|
|
79
|
+
)
|
|
80
|
+
line_number: bool = Field(
|
|
81
|
+
alias="-n",
|
|
82
|
+
description=(
|
|
83
|
+
"Show line numbers in output (the `-n` option). "
|
|
84
|
+
"Requires `output_mode` to be `content`. Defaults to true."
|
|
85
|
+
),
|
|
86
|
+
default=True,
|
|
87
|
+
)
|
|
88
|
+
ignore_case: bool = Field(
|
|
89
|
+
alias="-i",
|
|
90
|
+
description="Case insensitive search (the `-i` option).",
|
|
91
|
+
default=False,
|
|
92
|
+
)
|
|
93
|
+
type: str | None = Field(
|
|
94
|
+
description=(
|
|
95
|
+
"File type to search. Examples: py, rust, js, ts, go, java, etc. "
|
|
96
|
+
"More efficient than `glob` for standard file types."
|
|
97
|
+
),
|
|
98
|
+
default=None,
|
|
99
|
+
)
|
|
100
|
+
head_limit: int | None = Field(
|
|
101
|
+
description=(
|
|
102
|
+
"Limit output to first N lines/entries, equivalent to `| head -N`. "
|
|
103
|
+
"Works across all output modes: content (limits output lines), "
|
|
104
|
+
"files_with_matches (limits file paths), count_matches (limits count entries). "
|
|
105
|
+
"Defaults to 250. "
|
|
106
|
+
"Pass 0 for unlimited (use sparingly — large result sets waste context)."
|
|
107
|
+
),
|
|
108
|
+
default=250,
|
|
109
|
+
ge=0,
|
|
110
|
+
)
|
|
111
|
+
offset: int = Field(
|
|
112
|
+
description=(
|
|
113
|
+
"Skip first N lines/entries before applying head_limit, "
|
|
114
|
+
"equivalent to `| tail -n +N | head -N`. "
|
|
115
|
+
"Works across all output modes. Defaults to 0."
|
|
116
|
+
),
|
|
117
|
+
default=0,
|
|
118
|
+
ge=0,
|
|
119
|
+
)
|
|
120
|
+
multiline: bool = Field(
|
|
121
|
+
description=(
|
|
122
|
+
"Enable multiline mode where `.` matches newlines and patterns can span "
|
|
123
|
+
"lines (the `-U` and `--multiline-dotall` options). "
|
|
124
|
+
"By default, multiline mode is disabled."
|
|
125
|
+
),
|
|
126
|
+
default=False,
|
|
127
|
+
)
|
|
128
|
+
include_ignored: bool = Field(
|
|
129
|
+
description=(
|
|
130
|
+
"Include files that are ignored by `.gitignore`, `.ignore`, and other ignore "
|
|
131
|
+
"rules. Useful for searching gitignored artifacts such as build outputs "
|
|
132
|
+
"(e.g. `dist/`, `build/`) or `node_modules`. Sensitive files (like `.env`) "
|
|
133
|
+
"remain filtered by the sensitive-file protection layer. Defaults to false."
|
|
134
|
+
),
|
|
135
|
+
default=False,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
RG_VERSION = "15.0.0"
|
|
140
|
+
RG_BASE_URL = "http://cdn.pythinker.com/binaries/pythinker-code/rg"
|
|
141
|
+
RG_TIMEOUT = 20 # seconds
|
|
142
|
+
RG_MAX_BUFFER = 20_000_000 # 20MB stdout/stderr buffer limit
|
|
143
|
+
RG_KILL_GRACE = 5 # seconds: SIGTERM → SIGKILL
|
|
144
|
+
_RG_DOWNLOAD_LOCK = asyncio.Lock()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _rg_binary_name() -> str:
|
|
148
|
+
return "rg.exe" if platform.system() == "Windows" else "rg"
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _find_existing_rg(bin_name: str) -> Path | None:
|
|
152
|
+
share_bin = get_share_dir() / "bin" / bin_name
|
|
153
|
+
if share_bin.is_file():
|
|
154
|
+
return share_bin
|
|
155
|
+
|
|
156
|
+
assert pythinker_code.__file__ is not None
|
|
157
|
+
local_dep = Path(pythinker_code.__file__).parent / "deps" / "bin" / bin_name
|
|
158
|
+
if local_dep.is_file():
|
|
159
|
+
return local_dep
|
|
160
|
+
|
|
161
|
+
system_rg = shutil.which("rg")
|
|
162
|
+
if system_rg:
|
|
163
|
+
return Path(system_rg)
|
|
164
|
+
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _detect_target() -> str | None:
|
|
169
|
+
sys_name = platform.system()
|
|
170
|
+
mach = platform.machine().lower()
|
|
171
|
+
|
|
172
|
+
if mach in ("x86_64", "amd64"):
|
|
173
|
+
arch = "x86_64"
|
|
174
|
+
elif mach in ("arm64", "aarch64"):
|
|
175
|
+
arch = "aarch64"
|
|
176
|
+
else:
|
|
177
|
+
logger.error("Unsupported architecture for ripgrep: {mach}", mach=mach)
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
if sys_name == "Darwin":
|
|
181
|
+
os_name = "apple-darwin"
|
|
182
|
+
elif sys_name == "Linux":
|
|
183
|
+
os_name = "unknown-linux-musl" if arch == "x86_64" else "unknown-linux-gnu"
|
|
184
|
+
elif sys_name == "Windows":
|
|
185
|
+
os_name = "pc-windows-msvc"
|
|
186
|
+
else:
|
|
187
|
+
logger.error("Unsupported operating system for ripgrep: {sys_name}", sys_name=sys_name)
|
|
188
|
+
return None
|
|
189
|
+
|
|
190
|
+
return f"{arch}-{os_name}"
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
async def _download_and_install_rg(bin_name: str) -> Path:
|
|
194
|
+
target = _detect_target()
|
|
195
|
+
if not target:
|
|
196
|
+
raise RuntimeError("Unsupported platform for ripgrep download")
|
|
197
|
+
|
|
198
|
+
is_windows = "windows" in target
|
|
199
|
+
archive_ext = "zip" if is_windows else "tar.gz"
|
|
200
|
+
filename = f"ripgrep-{RG_VERSION}-{target}.{archive_ext}"
|
|
201
|
+
url = f"{RG_BASE_URL}/{filename}"
|
|
202
|
+
logger.info("Downloading ripgrep from {url}", url=url)
|
|
203
|
+
|
|
204
|
+
share_bin_dir = get_share_dir() / "bin"
|
|
205
|
+
share_bin_dir.mkdir(parents=True, exist_ok=True)
|
|
206
|
+
destination = share_bin_dir / bin_name
|
|
207
|
+
|
|
208
|
+
# Downloading the ripgrep binary can be slow on constrained networks.
|
|
209
|
+
download_timeout = aiohttp.ClientTimeout(total=600, sock_read=60, sock_connect=15)
|
|
210
|
+
async with new_client_session(timeout=download_timeout) as session:
|
|
211
|
+
with tempfile.TemporaryDirectory(prefix="pythinker-rg-") as tmpdir:
|
|
212
|
+
tar_path = Path(tmpdir) / filename
|
|
213
|
+
|
|
214
|
+
try:
|
|
215
|
+
async with session.get(url) as resp:
|
|
216
|
+
resp.raise_for_status()
|
|
217
|
+
with open(tar_path, "wb") as fh:
|
|
218
|
+
async for chunk in resp.content.iter_chunked(1024 * 64):
|
|
219
|
+
if chunk:
|
|
220
|
+
fh.write(chunk)
|
|
221
|
+
except (aiohttp.ClientError, TimeoutError) as exc:
|
|
222
|
+
raise RuntimeError("Failed to download ripgrep binary") from exc
|
|
223
|
+
|
|
224
|
+
try:
|
|
225
|
+
if is_windows:
|
|
226
|
+
with zipfile.ZipFile(tar_path, "r") as zf:
|
|
227
|
+
member_name = next(
|
|
228
|
+
(name for name in zf.namelist() if Path(name).name == bin_name),
|
|
229
|
+
None,
|
|
230
|
+
)
|
|
231
|
+
if not member_name:
|
|
232
|
+
raise RuntimeError("Ripgrep binary not found in archive")
|
|
233
|
+
with zf.open(member_name) as source, open(destination, "wb") as dest_fh:
|
|
234
|
+
shutil.copyfileobj(source, dest_fh)
|
|
235
|
+
else:
|
|
236
|
+
with tarfile.open(tar_path, "r:gz") as tar:
|
|
237
|
+
member = next(
|
|
238
|
+
(m for m in tar.getmembers() if Path(m.name).name == bin_name),
|
|
239
|
+
None,
|
|
240
|
+
)
|
|
241
|
+
if not member:
|
|
242
|
+
raise RuntimeError("Ripgrep binary not found in archive")
|
|
243
|
+
extracted = tar.extractfile(member)
|
|
244
|
+
if not extracted:
|
|
245
|
+
raise RuntimeError("Failed to extract ripgrep binary")
|
|
246
|
+
with open(destination, "wb") as dest_fh:
|
|
247
|
+
shutil.copyfileobj(extracted, dest_fh)
|
|
248
|
+
except (zipfile.BadZipFile, tarfile.TarError, OSError) as exc:
|
|
249
|
+
raise RuntimeError("Failed to extract ripgrep archive") from exc
|
|
250
|
+
|
|
251
|
+
destination.chmod(destination.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
252
|
+
logger.info("Installed ripgrep to {destination}", destination=destination)
|
|
253
|
+
return destination
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
async def _ensure_rg_path() -> str:
|
|
257
|
+
bin_name = _rg_binary_name()
|
|
258
|
+
existing = _find_existing_rg(bin_name)
|
|
259
|
+
if existing:
|
|
260
|
+
return str(existing)
|
|
261
|
+
|
|
262
|
+
async with _RG_DOWNLOAD_LOCK:
|
|
263
|
+
existing = _find_existing_rg(bin_name)
|
|
264
|
+
if existing:
|
|
265
|
+
return str(existing)
|
|
266
|
+
|
|
267
|
+
downloaded = await _download_and_install_rg(bin_name)
|
|
268
|
+
return str(downloaded)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _build_rg_args(rg_path: str, params: Params, *, single_threaded: bool = False) -> list[str]:
|
|
272
|
+
"""Build ripgrep command-line arguments from Params."""
|
|
273
|
+
args: list[str] = [rg_path]
|
|
274
|
+
|
|
275
|
+
# Fixed args
|
|
276
|
+
if params.output_mode != "content":
|
|
277
|
+
args.extend(["--max-columns", "500"])
|
|
278
|
+
args.append("--hidden")
|
|
279
|
+
if params.include_ignored:
|
|
280
|
+
args.append("--no-ignore")
|
|
281
|
+
for vcs_dir in (".git", ".svn", ".hg", ".bzr", ".jj", ".sl"):
|
|
282
|
+
args.extend(["--glob", f"!{vcs_dir}"])
|
|
283
|
+
|
|
284
|
+
if single_threaded:
|
|
285
|
+
args.extend(["-j", "1"])
|
|
286
|
+
|
|
287
|
+
# Search options
|
|
288
|
+
if params.ignore_case:
|
|
289
|
+
args.append("--ignore-case")
|
|
290
|
+
if params.multiline:
|
|
291
|
+
args.extend(["--multiline", "--multiline-dotall"])
|
|
292
|
+
|
|
293
|
+
# Content display options (only for content mode)
|
|
294
|
+
if params.output_mode == "content":
|
|
295
|
+
if params.before_context is not None:
|
|
296
|
+
args.extend(["--before-context", str(params.before_context)])
|
|
297
|
+
if params.after_context is not None:
|
|
298
|
+
args.extend(["--after-context", str(params.after_context)])
|
|
299
|
+
if params.context is not None:
|
|
300
|
+
args.extend(["--context", str(params.context)])
|
|
301
|
+
if params.line_number:
|
|
302
|
+
args.append("--line-number")
|
|
303
|
+
|
|
304
|
+
# File filtering options
|
|
305
|
+
if params.glob:
|
|
306
|
+
args.extend(["--glob", params.glob])
|
|
307
|
+
if params.type:
|
|
308
|
+
args.extend(["--type", params.type])
|
|
309
|
+
|
|
310
|
+
# Output mode
|
|
311
|
+
if params.output_mode == "files_with_matches":
|
|
312
|
+
args.append("--files-with-matches")
|
|
313
|
+
elif params.output_mode == "count_matches":
|
|
314
|
+
args.append("--count-matches")
|
|
315
|
+
|
|
316
|
+
# Separate pattern from flags to avoid ambiguity (e.g. pattern starting with -)
|
|
317
|
+
args.append("--")
|
|
318
|
+
args.append(params.pattern)
|
|
319
|
+
args.append(os.path.expanduser(params.path))
|
|
320
|
+
|
|
321
|
+
return args
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
async def _read_stream(
|
|
325
|
+
stream: asyncio.StreamReader,
|
|
326
|
+
buffer: bytearray,
|
|
327
|
+
limit: int,
|
|
328
|
+
truncated_flag: list[bool] | None = None,
|
|
329
|
+
) -> bool:
|
|
330
|
+
"""Incrementally read from stream into buffer, up to limit bytes.
|
|
331
|
+
|
|
332
|
+
After hitting the limit, continues draining the pipe (discarding data)
|
|
333
|
+
so the child process doesn't block on a full pipe buffer.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
truncated_flag: If provided, truncated_flag[0] is set to True at the
|
|
337
|
+
moment truncation occurs (synchronously, before the next await).
|
|
338
|
+
This ensures the flag is available even if the coroutine is
|
|
339
|
+
cancelled by asyncio.wait_for timeout.
|
|
340
|
+
|
|
341
|
+
Returns True if output was truncated (exceeded limit).
|
|
342
|
+
"""
|
|
343
|
+
truncated = False
|
|
344
|
+
while True:
|
|
345
|
+
chunk = await stream.read(65536)
|
|
346
|
+
if not chunk:
|
|
347
|
+
break
|
|
348
|
+
if len(buffer) < limit:
|
|
349
|
+
needed = limit - len(buffer)
|
|
350
|
+
buffer.extend(chunk[:needed])
|
|
351
|
+
if len(chunk) > needed:
|
|
352
|
+
truncated = True
|
|
353
|
+
if truncated_flag is not None:
|
|
354
|
+
truncated_flag[0] = True
|
|
355
|
+
else:
|
|
356
|
+
truncated = True
|
|
357
|
+
if truncated_flag is not None:
|
|
358
|
+
truncated_flag[0] = True
|
|
359
|
+
return truncated
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
async def _kill_process(process: asyncio.subprocess.Process) -> None:
|
|
363
|
+
"""Two-phase kill: SIGTERM → grace period → SIGKILL."""
|
|
364
|
+
process.terminate()
|
|
365
|
+
try:
|
|
366
|
+
await asyncio.wait_for(process.wait(), timeout=RG_KILL_GRACE)
|
|
367
|
+
except TimeoutError:
|
|
368
|
+
process.kill()
|
|
369
|
+
await process.wait()
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def _is_eagain(stderr: str) -> bool:
|
|
373
|
+
return "os error 11" in stderr or "Resource temporarily unavailable" in stderr
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _strip_path_prefix(output: str, search_base: str) -> str:
|
|
377
|
+
"""Strip search_base prefix from each line to produce relative paths."""
|
|
378
|
+
prefix = search_base.rstrip("/\\") + os.sep
|
|
379
|
+
return "\n".join(
|
|
380
|
+
line[len(prefix) :] if line.startswith(prefix) else line for line in output.split("\n")
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
class Grep(CallableTool2[Params]):
|
|
385
|
+
name: str = "Grep"
|
|
386
|
+
description: str = load_desc(Path(__file__).parent / "grep.md")
|
|
387
|
+
params: type[Params] = Params
|
|
388
|
+
|
|
389
|
+
@override
|
|
390
|
+
async def __call__(self, params: Params, *, _retry: bool = False) -> ToolReturnValue:
|
|
391
|
+
try:
|
|
392
|
+
builder = ToolResultBuilder()
|
|
393
|
+
message = ""
|
|
394
|
+
|
|
395
|
+
# Build rg command
|
|
396
|
+
rg_path = await _ensure_rg_path()
|
|
397
|
+
logger.debug("Using ripgrep binary: {rg_bin}", rg_bin=rg_path)
|
|
398
|
+
args = _build_rg_args(rg_path, params, single_threaded=_retry)
|
|
399
|
+
|
|
400
|
+
# Execute search as async subprocess (non-blocking, cancellable)
|
|
401
|
+
process = await asyncio.create_subprocess_exec(
|
|
402
|
+
*args,
|
|
403
|
+
stdout=asyncio.subprocess.PIPE,
|
|
404
|
+
stderr=asyncio.subprocess.PIPE,
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
# Stream stdout/stderr incrementally with buffer limit
|
|
408
|
+
stdout_buf = bytearray()
|
|
409
|
+
stderr_buf = bytearray()
|
|
410
|
+
timed_out = False
|
|
411
|
+
stdout_truncated_flag: list[bool] = [False]
|
|
412
|
+
|
|
413
|
+
try:
|
|
414
|
+
assert process.stdout is not None
|
|
415
|
+
assert process.stderr is not None
|
|
416
|
+
await asyncio.wait_for(
|
|
417
|
+
asyncio.gather(
|
|
418
|
+
_read_stream(
|
|
419
|
+
process.stdout, stdout_buf, RG_MAX_BUFFER, stdout_truncated_flag
|
|
420
|
+
),
|
|
421
|
+
_read_stream(process.stderr, stderr_buf, RG_MAX_BUFFER),
|
|
422
|
+
),
|
|
423
|
+
timeout=RG_TIMEOUT,
|
|
424
|
+
)
|
|
425
|
+
await process.wait()
|
|
426
|
+
except asyncio.CancelledError:
|
|
427
|
+
await _kill_process(process)
|
|
428
|
+
raise
|
|
429
|
+
except TimeoutError:
|
|
430
|
+
await _kill_process(process)
|
|
431
|
+
timed_out = True
|
|
432
|
+
|
|
433
|
+
output = stdout_buf.decode("utf-8", errors="replace")
|
|
434
|
+
stderr_str = stderr_buf.decode("utf-8", errors="replace")
|
|
435
|
+
|
|
436
|
+
# truncated_flag is set synchronously inside _read_stream at
|
|
437
|
+
# the moment of truncation, so it's available even after timeout.
|
|
438
|
+
buffer_truncated = stdout_truncated_flag[0]
|
|
439
|
+
|
|
440
|
+
# Drop last incomplete line if buffer was truncated
|
|
441
|
+
if buffer_truncated:
|
|
442
|
+
last_nl = output.rfind("\n")
|
|
443
|
+
output = output[:last_nl] if last_nl >= 0 else ""
|
|
444
|
+
message = "Output exceeded buffer limit. Some results omitted."
|
|
445
|
+
|
|
446
|
+
# Timeout: return partial results if available, otherwise error
|
|
447
|
+
if timed_out:
|
|
448
|
+
if not output.strip():
|
|
449
|
+
return ToolError(
|
|
450
|
+
message=(
|
|
451
|
+
f"Grep timed out after {RG_TIMEOUT}s. "
|
|
452
|
+
"Try a more specific path or pattern."
|
|
453
|
+
),
|
|
454
|
+
brief="Grep timed out",
|
|
455
|
+
)
|
|
456
|
+
timeout_msg = f"Grep timed out after {RG_TIMEOUT}s. Partial results returned."
|
|
457
|
+
message = f"{message} {timeout_msg}" if message else timeout_msg
|
|
458
|
+
|
|
459
|
+
# rg exit codes: 0=matches found, 1=no matches, 2+=error
|
|
460
|
+
if not timed_out and process.returncode not in (0, 1):
|
|
461
|
+
# EAGAIN: retry once with single-threaded mode
|
|
462
|
+
if not _retry and _is_eagain(stderr_str):
|
|
463
|
+
logger.warning("rg EAGAIN error, retrying with -j 1")
|
|
464
|
+
return await self.__call__(params, _retry=True)
|
|
465
|
+
return ToolError(
|
|
466
|
+
message=f"Failed to grep. Error: {stderr_str}",
|
|
467
|
+
brief="Failed to grep",
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
# --- Post-processing pipeline ---
|
|
471
|
+
|
|
472
|
+
# Step 1: mtime sorting (files_with_matches only, skip on timeout)
|
|
473
|
+
if not timed_out and params.output_mode == "files_with_matches":
|
|
474
|
+
lines = [x for x in output.split("\n") if x.strip()]
|
|
475
|
+
lines.sort(
|
|
476
|
+
key=lambda p: os.path.getmtime(p) if os.path.exists(p) else 0,
|
|
477
|
+
reverse=True,
|
|
478
|
+
)
|
|
479
|
+
output = "\n".join(lines)
|
|
480
|
+
|
|
481
|
+
# Step 2: shorten paths to relative (prefix stripping)
|
|
482
|
+
search_base = os.path.abspath(os.path.expanduser(params.path))
|
|
483
|
+
if os.path.isfile(search_base):
|
|
484
|
+
search_base = os.path.dirname(search_base)
|
|
485
|
+
output = _strip_path_prefix(output, search_base)
|
|
486
|
+
|
|
487
|
+
# Step 3: filter sensitive files from output
|
|
488
|
+
# Regex for ripgrep content lines: path:linenum:text (match) or
|
|
489
|
+
# path-linenum-text (context). The separator is `:` or `-` followed
|
|
490
|
+
# by digits then the same separator again.
|
|
491
|
+
_RG_LINE_RE = re.compile(r"^(.*?)([:\-])(\d+)\2")
|
|
492
|
+
|
|
493
|
+
out_lines = output.split("\n")
|
|
494
|
+
filtered_paths: list[str] = []
|
|
495
|
+
kept_lines: list[str] = []
|
|
496
|
+
sensitive_path_set: set[str] = set()
|
|
497
|
+
for line in out_lines:
|
|
498
|
+
if params.output_mode == "content":
|
|
499
|
+
# Match lines: "file.py:10:matched text"
|
|
500
|
+
# Context lines: "file.py-10-context text"
|
|
501
|
+
# Separator: "--"
|
|
502
|
+
if line == "--":
|
|
503
|
+
kept_lines.append(line)
|
|
504
|
+
continue
|
|
505
|
+
m = _RG_LINE_RE.match(line)
|
|
506
|
+
file_path = m.group(1) if m else line
|
|
507
|
+
elif params.output_mode == "count_matches":
|
|
508
|
+
# Count lines: "file.py:42"
|
|
509
|
+
idx = line.rfind(":")
|
|
510
|
+
file_path = line[:idx] if idx > 0 else line
|
|
511
|
+
else:
|
|
512
|
+
# files_with_matches: pure path per line
|
|
513
|
+
file_path = line
|
|
514
|
+
|
|
515
|
+
if file_path and is_sensitive_file(file_path):
|
|
516
|
+
if file_path not in sensitive_path_set:
|
|
517
|
+
sensitive_path_set.add(file_path)
|
|
518
|
+
filtered_paths.append(file_path)
|
|
519
|
+
else:
|
|
520
|
+
kept_lines.append(line)
|
|
521
|
+
|
|
522
|
+
if filtered_paths:
|
|
523
|
+
# Remove trailing "--" separators left after filtering
|
|
524
|
+
while kept_lines and kept_lines[-1] == "--":
|
|
525
|
+
kept_lines.pop()
|
|
526
|
+
output = "\n".join(kept_lines)
|
|
527
|
+
warning = sensitive_file_warning(filtered_paths)
|
|
528
|
+
message = f"{message} {warning}" if message else warning
|
|
529
|
+
|
|
530
|
+
# Step 4: count_matches summary (before pagination, on full results)
|
|
531
|
+
lines = output.split("\n")
|
|
532
|
+
if lines and lines[-1] == "":
|
|
533
|
+
lines = lines[:-1]
|
|
534
|
+
|
|
535
|
+
if params.output_mode == "count_matches":
|
|
536
|
+
total_matches = 0
|
|
537
|
+
total_files = 0
|
|
538
|
+
for line in lines:
|
|
539
|
+
idx = line.rfind(":")
|
|
540
|
+
if idx > 0:
|
|
541
|
+
try:
|
|
542
|
+
total_matches += int(line[idx + 1 :])
|
|
543
|
+
total_files += 1
|
|
544
|
+
except ValueError:
|
|
545
|
+
pass
|
|
546
|
+
count_summary = (
|
|
547
|
+
f"Found {total_matches} total occurrences across {total_files} files."
|
|
548
|
+
)
|
|
549
|
+
message = f"{message} {count_summary}" if message else count_summary
|
|
550
|
+
|
|
551
|
+
# Step 5: offset + head_limit pagination
|
|
552
|
+
if params.offset > 0:
|
|
553
|
+
lines = lines[params.offset :]
|
|
554
|
+
|
|
555
|
+
effective_limit = params.head_limit
|
|
556
|
+
if effective_limit and len(lines) > effective_limit:
|
|
557
|
+
total = len(lines) + params.offset
|
|
558
|
+
lines = lines[:effective_limit]
|
|
559
|
+
output = "\n".join(lines)
|
|
560
|
+
truncation_msg = (
|
|
561
|
+
f"Results truncated to {effective_limit} lines (total: {total}). "
|
|
562
|
+
f"Use offset={params.offset + effective_limit} to see more."
|
|
563
|
+
)
|
|
564
|
+
message = f"{message} {truncation_msg}" if message else truncation_msg
|
|
565
|
+
else:
|
|
566
|
+
output = "\n".join(lines)
|
|
567
|
+
|
|
568
|
+
if not output and not buffer_truncated:
|
|
569
|
+
no_match_msg = "No matches found"
|
|
570
|
+
if message:
|
|
571
|
+
no_match_msg = f"{no_match_msg}. {message}"
|
|
572
|
+
return builder.ok(message=no_match_msg)
|
|
573
|
+
|
|
574
|
+
builder.write(output)
|
|
575
|
+
return builder.ok(message=message)
|
|
576
|
+
|
|
577
|
+
except asyncio.CancelledError:
|
|
578
|
+
raise
|
|
579
|
+
except Exception as e:
|
|
580
|
+
logger.warning(
|
|
581
|
+
"Grep failed: pattern={pattern}, path={path}: {error}",
|
|
582
|
+
pattern=params.pattern,
|
|
583
|
+
path=params.path,
|
|
584
|
+
error=e,
|
|
585
|
+
)
|
|
586
|
+
return ToolError(
|
|
587
|
+
message=f"Failed to grep. Error: {str(e)}",
|
|
588
|
+
brief="Failed to grep",
|
|
589
|
+
)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from pythinker_core.tooling import ToolError
|
|
8
|
+
from pythinker_host.path import HostPath
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class PlanEditTarget:
|
|
13
|
+
active: bool
|
|
14
|
+
plan_path: Path | None
|
|
15
|
+
is_plan_target: bool
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def inspect_plan_edit_target(
|
|
19
|
+
path: HostPath,
|
|
20
|
+
*,
|
|
21
|
+
plan_mode_checker: Callable[[], bool] | None,
|
|
22
|
+
plan_file_path_getter: Callable[[], Path | None] | None,
|
|
23
|
+
) -> PlanEditTarget | ToolError:
|
|
24
|
+
"""Resolve whether a file edit is targeting the current plan artifact."""
|
|
25
|
+
if plan_mode_checker is None or not plan_mode_checker():
|
|
26
|
+
return PlanEditTarget(active=False, plan_path=None, is_plan_target=False)
|
|
27
|
+
|
|
28
|
+
plan_path = plan_file_path_getter() if plan_file_path_getter is not None else None
|
|
29
|
+
if plan_path is None:
|
|
30
|
+
return ToolError(
|
|
31
|
+
message="Plan mode is active, but the current plan file is unavailable.",
|
|
32
|
+
brief="Plan file unavailable",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
canonical_plan_path = HostPath(str(plan_path)).canonical()
|
|
36
|
+
if str(path) != str(canonical_plan_path):
|
|
37
|
+
return ToolError(
|
|
38
|
+
message=(
|
|
39
|
+
"Plan mode is active. You may only edit the current plan file: "
|
|
40
|
+
f"`{canonical_plan_path}`."
|
|
41
|
+
),
|
|
42
|
+
brief="Plan mode restriction",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
return PlanEditTarget(active=True, plan_path=plan_path, is_plan_target=True)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Read text content from a file.
|
|
2
|
+
|
|
3
|
+
**Tips:**
|
|
4
|
+
- Make sure you follow the description of each tool parameter.
|
|
5
|
+
- A `<system>` tag will be given before the read file content.
|
|
6
|
+
- The system will notify you when there is anything wrong when reading the file.
|
|
7
|
+
- This tool is a tool that you typically want to use in parallel. Always read multiple files in one response when possible.
|
|
8
|
+
- This tool can only read text files. To read images or videos, use other appropriate tools. To list directories, use the Glob tool or `ls` command via the Shell tool. To read other file types, use appropriate commands via the Shell tool.
|
|
9
|
+
- If the file doesn't exist or path is invalid, an error will be returned.
|
|
10
|
+
- If you want to search for a certain content/pattern, prefer Grep tool over ReadFile.
|
|
11
|
+
- Content will be returned with a line number before each line like `cat -n` format.
|
|
12
|
+
- Use `line_offset` and `n_lines` parameters when you only need to read a part of the file.
|
|
13
|
+
- Use negative `line_offset` to read from the end of the file (e.g. `line_offset=-100` reads the last 100 lines). This is useful for viewing the tail of log files. The absolute value cannot exceed ${MAX_LINES}.
|
|
14
|
+
- The tool always returns the total number of lines in the file in its message, which you can use to plan subsequent reads.
|
|
15
|
+
- The maximum number of lines that can be read at once is ${MAX_LINES}.
|
|
16
|
+
- Any lines longer than ${MAX_LINE_LENGTH} characters will be truncated, ending with "...".
|