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.
Files changed (731) hide show
  1. pythinker_code/CHANGELOG.md +16 -0
  2. pythinker_code/__init__.py +0 -0
  3. pythinker_code/__main__.py +92 -0
  4. pythinker_code/acp/AGENTS.md +93 -0
  5. pythinker_code/acp/__init__.py +13 -0
  6. pythinker_code/acp/convert.py +128 -0
  7. pythinker_code/acp/host.py +298 -0
  8. pythinker_code/acp/mcp.py +46 -0
  9. pythinker_code/acp/server.py +497 -0
  10. pythinker_code/acp/session.py +496 -0
  11. pythinker_code/acp/tools.py +167 -0
  12. pythinker_code/acp/types.py +13 -0
  13. pythinker_code/acp/version.py +45 -0
  14. pythinker_code/agents/default/agent.yaml +36 -0
  15. pythinker_code/agents/default/coder.yaml +25 -0
  16. pythinker_code/agents/default/explore.yaml +46 -0
  17. pythinker_code/agents/default/plan.yaml +30 -0
  18. pythinker_code/agents/default/system.md +164 -0
  19. pythinker_code/agents/okabe/agent.yaml +22 -0
  20. pythinker_code/agentspec.py +163 -0
  21. pythinker_code/app.py +820 -0
  22. pythinker_code/approval_runtime/__init__.py +29 -0
  23. pythinker_code/approval_runtime/models.py +42 -0
  24. pythinker_code/approval_runtime/runtime.py +235 -0
  25. pythinker_code/auth/__init__.py +25 -0
  26. pythinker_code/auth/anthropic_direct.py +207 -0
  27. pythinker_code/auth/deepseek.py +192 -0
  28. pythinker_code/auth/lm_studio.py +418 -0
  29. pythinker_code/auth/minimax.py +203 -0
  30. pythinker_code/auth/oauth.py +1122 -0
  31. pythinker_code/auth/ollama.py +293 -0
  32. pythinker_code/auth/openai.py +771 -0
  33. pythinker_code/auth/opencode_go.py +203 -0
  34. pythinker_code/auth/openrouter.py +225 -0
  35. pythinker_code/auth/platforms.py +466 -0
  36. pythinker_code/background/__init__.py +36 -0
  37. pythinker_code/background/agent_runner.py +231 -0
  38. pythinker_code/background/ids.py +19 -0
  39. pythinker_code/background/manager.py +650 -0
  40. pythinker_code/background/models.py +105 -0
  41. pythinker_code/background/store.py +237 -0
  42. pythinker_code/background/summary.py +66 -0
  43. pythinker_code/background/worker.py +209 -0
  44. pythinker_code/cli/__init__.py +1326 -0
  45. pythinker_code/cli/__main__.py +19 -0
  46. pythinker_code/cli/_lazy_group.py +238 -0
  47. pythinker_code/cli/export.py +322 -0
  48. pythinker_code/cli/info.py +62 -0
  49. pythinker_code/cli/mcp.py +349 -0
  50. pythinker_code/cli/plugin.py +351 -0
  51. pythinker_code/cli/toad.py +74 -0
  52. pythinker_code/cli/vis.py +38 -0
  53. pythinker_code/cli/web.py +80 -0
  54. pythinker_code/config.py +453 -0
  55. pythinker_code/constant.py +33 -0
  56. pythinker_code/exception.py +43 -0
  57. pythinker_code/hooks/__init__.py +4 -0
  58. pythinker_code/hooks/config.py +34 -0
  59. pythinker_code/hooks/engine.py +371 -0
  60. pythinker_code/hooks/events.py +190 -0
  61. pythinker_code/hooks/runner.py +89 -0
  62. pythinker_code/llm.py +412 -0
  63. pythinker_code/metadata.py +79 -0
  64. pythinker_code/notifications/__init__.py +33 -0
  65. pythinker_code/notifications/llm.py +77 -0
  66. pythinker_code/notifications/manager.py +145 -0
  67. pythinker_code/notifications/models.py +50 -0
  68. pythinker_code/notifications/notifier.py +41 -0
  69. pythinker_code/notifications/store.py +118 -0
  70. pythinker_code/notifications/wire.py +21 -0
  71. pythinker_code/plugin/__init__.py +124 -0
  72. pythinker_code/plugin/manager.py +153 -0
  73. pythinker_code/plugin/tool.py +173 -0
  74. pythinker_code/prompts/__init__.py +6 -0
  75. pythinker_code/prompts/compact.md +73 -0
  76. pythinker_code/prompts/init.md +21 -0
  77. pythinker_code/py.typed +0 -0
  78. pythinker_code/session.py +319 -0
  79. pythinker_code/session_fork.py +325 -0
  80. pythinker_code/session_state.py +132 -0
  81. pythinker_code/share.py +14 -0
  82. pythinker_code/skill/__init__.py +727 -0
  83. pythinker_code/skill/flow/__init__.py +99 -0
  84. pythinker_code/skill/flow/d2.py +482 -0
  85. pythinker_code/skill/flow/mermaid.py +266 -0
  86. pythinker_code/skills/pythinker-code-help/SKILL.md +54 -0
  87. pythinker_code/skills/skill-creator/SKILL.md +367 -0
  88. pythinker_code/soul/__init__.py +304 -0
  89. pythinker_code/soul/agent.py +520 -0
  90. pythinker_code/soul/approval.py +267 -0
  91. pythinker_code/soul/btw.py +214 -0
  92. pythinker_code/soul/compaction.py +189 -0
  93. pythinker_code/soul/context.py +339 -0
  94. pythinker_code/soul/denwarenji.py +39 -0
  95. pythinker_code/soul/dynamic_injection.py +84 -0
  96. pythinker_code/soul/dynamic_injections/__init__.py +0 -0
  97. pythinker_code/soul/dynamic_injections/auto_mode.py +72 -0
  98. pythinker_code/soul/dynamic_injections/plan_mode.py +239 -0
  99. pythinker_code/soul/message.py +92 -0
  100. pythinker_code/soul/pythinkersoul.py +1613 -0
  101. pythinker_code/soul/slash.py +340 -0
  102. pythinker_code/soul/toolset.py +788 -0
  103. pythinker_code/subagents/__init__.py +21 -0
  104. pythinker_code/subagents/builder.py +42 -0
  105. pythinker_code/subagents/core.py +86 -0
  106. pythinker_code/subagents/git_context.py +172 -0
  107. pythinker_code/subagents/models.py +54 -0
  108. pythinker_code/subagents/output.py +71 -0
  109. pythinker_code/subagents/registry.py +28 -0
  110. pythinker_code/subagents/runner.py +428 -0
  111. pythinker_code/subagents/store.py +196 -0
  112. pythinker_code/telemetry/__init__.py +211 -0
  113. pythinker_code/telemetry/config.py +54 -0
  114. pythinker_code/telemetry/crash.py +157 -0
  115. pythinker_code/telemetry/metrics.py +208 -0
  116. pythinker_code/telemetry/otel.py +240 -0
  117. pythinker_code/telemetry/sentry.py +167 -0
  118. pythinker_code/telemetry/sink.py +189 -0
  119. pythinker_code/tools/AGENTS.md +6 -0
  120. pythinker_code/tools/__init__.py +105 -0
  121. pythinker_code/tools/agent/__init__.py +277 -0
  122. pythinker_code/tools/agent/description.md +41 -0
  123. pythinker_code/tools/ask_user/__init__.py +159 -0
  124. pythinker_code/tools/ask_user/description.md +19 -0
  125. pythinker_code/tools/background/__init__.py +318 -0
  126. pythinker_code/tools/background/list.md +10 -0
  127. pythinker_code/tools/background/output.md +11 -0
  128. pythinker_code/tools/background/stop.md +8 -0
  129. pythinker_code/tools/display.py +46 -0
  130. pythinker_code/tools/dmail/__init__.py +38 -0
  131. pythinker_code/tools/dmail/dmail.md +17 -0
  132. pythinker_code/tools/file/__init__.py +30 -0
  133. pythinker_code/tools/file/glob.md +17 -0
  134. pythinker_code/tools/file/glob.py +160 -0
  135. pythinker_code/tools/file/grep.md +6 -0
  136. pythinker_code/tools/file/grep_local.py +589 -0
  137. pythinker_code/tools/file/plan_mode.py +45 -0
  138. pythinker_code/tools/file/read.md +16 -0
  139. pythinker_code/tools/file/read.py +300 -0
  140. pythinker_code/tools/file/read_media.md +24 -0
  141. pythinker_code/tools/file/read_media.py +217 -0
  142. pythinker_code/tools/file/replace.md +7 -0
  143. pythinker_code/tools/file/replace.py +195 -0
  144. pythinker_code/tools/file/utils.py +257 -0
  145. pythinker_code/tools/file/write.md +5 -0
  146. pythinker_code/tools/file/write.py +177 -0
  147. pythinker_code/tools/plan/__init__.py +327 -0
  148. pythinker_code/tools/plan/description.md +29 -0
  149. pythinker_code/tools/plan/enter.py +190 -0
  150. pythinker_code/tools/plan/enter_description.md +35 -0
  151. pythinker_code/tools/plan/heroes.py +277 -0
  152. pythinker_code/tools/shell/__init__.py +253 -0
  153. pythinker_code/tools/shell/bash.md +35 -0
  154. pythinker_code/tools/shell/powershell.md +30 -0
  155. pythinker_code/tools/test.py +55 -0
  156. pythinker_code/tools/think/__init__.py +21 -0
  157. pythinker_code/tools/think/think.md +1 -0
  158. pythinker_code/tools/todo/__init__.py +168 -0
  159. pythinker_code/tools/todo/set_todo_list.md +23 -0
  160. pythinker_code/tools/utils.py +199 -0
  161. pythinker_code/tools/web/__init__.py +4 -0
  162. pythinker_code/tools/web/fetch.md +1 -0
  163. pythinker_code/tools/web/fetch.py +189 -0
  164. pythinker_code/tools/web/search.md +1 -0
  165. pythinker_code/tools/web/search.py +163 -0
  166. pythinker_code/ui/__init__.py +0 -0
  167. pythinker_code/ui/acp/__init__.py +99 -0
  168. pythinker_code/ui/print/__init__.py +474 -0
  169. pythinker_code/ui/print/visualize.py +185 -0
  170. pythinker_code/ui/shell/__init__.py +1696 -0
  171. pythinker_code/ui/shell/console.py +109 -0
  172. pythinker_code/ui/shell/debug.py +190 -0
  173. pythinker_code/ui/shell/echo.py +17 -0
  174. pythinker_code/ui/shell/export_import.py +117 -0
  175. pythinker_code/ui/shell/keyboard.py +300 -0
  176. pythinker_code/ui/shell/mcp_status.py +113 -0
  177. pythinker_code/ui/shell/model_picker.py +318 -0
  178. pythinker_code/ui/shell/oauth.py +272 -0
  179. pythinker_code/ui/shell/placeholders.py +531 -0
  180. pythinker_code/ui/shell/prompt.py +2278 -0
  181. pythinker_code/ui/shell/replay.py +215 -0
  182. pythinker_code/ui/shell/session_picker.py +227 -0
  183. pythinker_code/ui/shell/setup.py +212 -0
  184. pythinker_code/ui/shell/slash.py +898 -0
  185. pythinker_code/ui/shell/startup.py +32 -0
  186. pythinker_code/ui/shell/task_browser.py +486 -0
  187. pythinker_code/ui/shell/update.py +350 -0
  188. pythinker_code/ui/shell/usage.py +291 -0
  189. pythinker_code/ui/shell/usage_adapters/__init__.py +50 -0
  190. pythinker_code/ui/shell/usage_adapters/anthropic_admin.py +233 -0
  191. pythinker_code/ui/shell/usage_adapters/base.py +72 -0
  192. pythinker_code/ui/shell/usage_adapters/deepseek.py +137 -0
  193. pythinker_code/ui/shell/usage_adapters/minimax.py +236 -0
  194. pythinker_code/ui/shell/usage_adapters/openai_admin.py +225 -0
  195. pythinker_code/ui/shell/usage_adapters/openai_chatgpt.py +241 -0
  196. pythinker_code/ui/shell/usage_adapters/opencode_go.py +232 -0
  197. pythinker_code/ui/shell/usage_adapters/openrouter.py +105 -0
  198. pythinker_code/ui/shell/usage_adapters/pythinker.py +189 -0
  199. pythinker_code/ui/shell/usage_adapters/pythinker_ai.py +50 -0
  200. pythinker_code/ui/shell/usage_render.py +150 -0
  201. pythinker_code/ui/shell/visualize/__init__.py +165 -0
  202. pythinker_code/ui/shell/visualize/_approval_panel.py +505 -0
  203. pythinker_code/ui/shell/visualize/_blocks.py +629 -0
  204. pythinker_code/ui/shell/visualize/_btw_panel.py +224 -0
  205. pythinker_code/ui/shell/visualize/_input_router.py +48 -0
  206. pythinker_code/ui/shell/visualize/_interactive.py +523 -0
  207. pythinker_code/ui/shell/visualize/_live_view.py +826 -0
  208. pythinker_code/ui/shell/visualize/_question_panel.py +586 -0
  209. pythinker_code/ui/theme.py +241 -0
  210. pythinker_code/usage_ratelimit_cache.py +175 -0
  211. pythinker_code/utils/__init__.py +0 -0
  212. pythinker_code/utils/aiohttp.py +24 -0
  213. pythinker_code/utils/aioqueue.py +72 -0
  214. pythinker_code/utils/broadcast.py +37 -0
  215. pythinker_code/utils/changelog.py +108 -0
  216. pythinker_code/utils/clipboard.py +246 -0
  217. pythinker_code/utils/datetime.py +64 -0
  218. pythinker_code/utils/diff.py +135 -0
  219. pythinker_code/utils/editor.py +91 -0
  220. pythinker_code/utils/environment.py +73 -0
  221. pythinker_code/utils/envvar.py +22 -0
  222. pythinker_code/utils/export.py +696 -0
  223. pythinker_code/utils/file_filter.py +375 -0
  224. pythinker_code/utils/frontmatter.py +70 -0
  225. pythinker_code/utils/io.py +27 -0
  226. pythinker_code/utils/logging.py +146 -0
  227. pythinker_code/utils/media_tags.py +29 -0
  228. pythinker_code/utils/message.py +24 -0
  229. pythinker_code/utils/path.py +199 -0
  230. pythinker_code/utils/proctitle.py +33 -0
  231. pythinker_code/utils/proxy.py +31 -0
  232. pythinker_code/utils/pyinstaller.py +45 -0
  233. pythinker_code/utils/rich/__init__.py +33 -0
  234. pythinker_code/utils/rich/columns.py +99 -0
  235. pythinker_code/utils/rich/diff_render.py +481 -0
  236. pythinker_code/utils/rich/markdown.py +900 -0
  237. pythinker_code/utils/rich/markdown_sample.md +108 -0
  238. pythinker_code/utils/rich/markdown_sample_short.md +2 -0
  239. pythinker_code/utils/rich/syntax.py +114 -0
  240. pythinker_code/utils/sensitive.py +54 -0
  241. pythinker_code/utils/server.py +121 -0
  242. pythinker_code/utils/signals.py +43 -0
  243. pythinker_code/utils/slashcmd.py +124 -0
  244. pythinker_code/utils/string.py +41 -0
  245. pythinker_code/utils/subprocess_env.py +73 -0
  246. pythinker_code/utils/term.py +168 -0
  247. pythinker_code/utils/typing.py +20 -0
  248. pythinker_code/vis/__init__.py +0 -0
  249. pythinker_code/vis/api/__init__.py +5 -0
  250. pythinker_code/vis/api/sessions.py +687 -0
  251. pythinker_code/vis/api/statistics.py +209 -0
  252. pythinker_code/vis/api/system.py +19 -0
  253. pythinker_code/vis/app.py +175 -0
  254. pythinker_code/vis/static/assets/highlighted-body-B3W2YXNL-D2MTYyJz.js +1 -0
  255. pythinker_code/vis/static/assets/index-CezafTt_.js +185 -0
  256. pythinker_code/vis/static/assets/index-DSRInNnm.css +1 -0
  257. pythinker_code/vis/static/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  258. pythinker_code/vis/static/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  259. pythinker_code/vis/static/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  260. pythinker_code/vis/static/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  261. pythinker_code/vis/static/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  262. pythinker_code/vis/static/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  263. pythinker_code/vis/static/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  264. pythinker_code/vis/static/index.html +17 -0
  265. pythinker_code/web/__init__.py +5 -0
  266. pythinker_code/web/api/__init__.py +15 -0
  267. pythinker_code/web/api/config.py +208 -0
  268. pythinker_code/web/api/open_in.py +199 -0
  269. pythinker_code/web/api/sessions.py +1225 -0
  270. pythinker_code/web/app.py +451 -0
  271. pythinker_code/web/auth.py +191 -0
  272. pythinker_code/web/models.py +98 -0
  273. pythinker_code/web/runner/__init__.py +5 -0
  274. pythinker_code/web/runner/messages.py +57 -0
  275. pythinker_code/web/runner/process.py +754 -0
  276. pythinker_code/web/runner/worker.py +97 -0
  277. pythinker_code/web/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  278. pythinker_code/web/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  279. pythinker_code/web/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  280. pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  281. pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  282. pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  283. pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  284. pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  285. pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  286. pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  287. pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  288. pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  289. pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  290. pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  291. pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  292. pythinker_code/web/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  293. pythinker_code/web/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  294. pythinker_code/web/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  295. pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  296. pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  297. pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  298. pythinker_code/web/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  299. pythinker_code/web/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  300. pythinker_code/web/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  301. pythinker_code/web/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  302. pythinker_code/web/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  303. pythinker_code/web/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  304. pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  305. pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  306. pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  307. pythinker_code/web/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  308. pythinker_code/web/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  309. pythinker_code/web/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  310. pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  311. pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  312. pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  313. pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  314. pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  315. pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  316. pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  317. pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  318. pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  319. pythinker_code/web/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  320. pythinker_code/web/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  321. pythinker_code/web/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  322. pythinker_code/web/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  323. pythinker_code/web/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  324. pythinker_code/web/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  325. pythinker_code/web/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  326. pythinker_code/web/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  327. pythinker_code/web/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  328. pythinker_code/web/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  329. pythinker_code/web/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  330. pythinker_code/web/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  331. pythinker_code/web/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  332. pythinker_code/web/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  333. pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  334. pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  335. pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  336. pythinker_code/web/static/assets/_baseUniq--dyU3g5v.js +1 -0
  337. pythinker_code/web/static/assets/abap-BdImnpbu.js +1 -0
  338. pythinker_code/web/static/assets/actionscript-3-CfeIJUat.js +1 -0
  339. pythinker_code/web/static/assets/ada-bCR0ucgS.js +1 -0
  340. pythinker_code/web/static/assets/andromeeda-C-Jbm3Hp.js +1 -0
  341. pythinker_code/web/static/assets/angular-html-CU67Zn6k.js +1 -0
  342. pythinker_code/web/static/assets/angular-ts-BwZT4LLn.js +1 -0
  343. pythinker_code/web/static/assets/apache-Pmp26Uib.js +1 -0
  344. pythinker_code/web/static/assets/apex-D8_7TLub.js +1 -0
  345. pythinker_code/web/static/assets/apl-dKokRX4l.js +1 -0
  346. pythinker_code/web/static/assets/applescript-Co6uUVPk.js +1 -0
  347. pythinker_code/web/static/assets/ara-BRHolxvo.js +1 -0
  348. pythinker_code/web/static/assets/arc-DkMjLpYa.js +1 -0
  349. pythinker_code/web/static/assets/architectureDiagram-VXUJARFQ-CHWVaGo9.js +36 -0
  350. pythinker_code/web/static/assets/asciidoc-Dv7Oe6Be.js +1 -0
  351. pythinker_code/web/static/assets/asm-D_Q5rh1f.js +1 -0
  352. pythinker_code/web/static/assets/astro-CbQHKStN.js +1 -0
  353. pythinker_code/web/static/assets/aurora-x-D-2ljcwZ.js +1 -0
  354. pythinker_code/web/static/assets/awk-DMzUqQB5.js +1 -0
  355. pythinker_code/web/static/assets/ayu-dark-CmMr59Fi.js +1 -0
  356. pythinker_code/web/static/assets/ballerina-BFfxhgS-.js +1 -0
  357. pythinker_code/web/static/assets/bat-BkioyH1T.js +1 -0
  358. pythinker_code/web/static/assets/beancount-k_qm7-4y.js +1 -0
  359. pythinker_code/web/static/assets/berry-uYugtg8r.js +1 -0
  360. pythinker_code/web/static/assets/bibtex-CHM0blh-.js +1 -0
  361. pythinker_code/web/static/assets/bicep-Bmn6On1c.js +1 -0
  362. pythinker_code/web/static/assets/blade-D4QpJJKB.js +1 -0
  363. pythinker_code/web/static/assets/blockDiagram-VD42YOAC-DzdKe497.js +122 -0
  364. pythinker_code/web/static/assets/bsl-BO_Y6i37.js +1 -0
  365. pythinker_code/web/static/assets/c-BIGW1oBm.js +1 -0
  366. pythinker_code/web/static/assets/c3-VCDPK7BO.js +1 -0
  367. pythinker_code/web/static/assets/c4Diagram-YG6GDRKO-D84Blotg.js +10 -0
  368. pythinker_code/web/static/assets/cadence-Bv_4Rxtq.js +1 -0
  369. pythinker_code/web/static/assets/cairo-KRGpt6FW.js +1 -0
  370. pythinker_code/web/static/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  371. pythinker_code/web/static/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  372. pythinker_code/web/static/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  373. pythinker_code/web/static/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  374. pythinker_code/web/static/assets/channel-CllSjjdl.js +1 -0
  375. pythinker_code/web/static/assets/chunk-4BX2VUAB-C9w8wleE.js +1 -0
  376. pythinker_code/web/static/assets/chunk-55IACEB6-YlYJ8HnF.js +1 -0
  377. pythinker_code/web/static/assets/chunk-B4BG7PRW-Bwtz_AHU.js +165 -0
  378. pythinker_code/web/static/assets/chunk-DI55MBZ5-BbEHkl8h.js +220 -0
  379. pythinker_code/web/static/assets/chunk-FMBD7UC4-BKPbvjLC.js +15 -0
  380. pythinker_code/web/static/assets/chunk-QN33PNHL-D73dQvKf.js +1 -0
  381. pythinker_code/web/static/assets/chunk-QZHKN3VN-zGiLKes_.js +1 -0
  382. pythinker_code/web/static/assets/chunk-TZMSLE5B-LHJCi2fy.js +1 -0
  383. pythinker_code/web/static/assets/clarity-D53aC0YG.js +1 -0
  384. pythinker_code/web/static/assets/classDiagram-2ON5EDUG-vX27iZwa.js +1 -0
  385. pythinker_code/web/static/assets/classDiagram-v2-WZHVMYZB-vX27iZwa.js +1 -0
  386. pythinker_code/web/static/assets/clojure-P80f7IUj.js +1 -0
  387. pythinker_code/web/static/assets/clone-DYBkaPm2.js +1 -0
  388. pythinker_code/web/static/assets/cmake-D1j8_8rp.js +1 -0
  389. pythinker_code/web/static/assets/cobol-nwyudZeR.js +1 -0
  390. pythinker_code/web/static/assets/code-block-IT6T5CEO-NtKViZGl.js +2 -0
  391. pythinker_code/web/static/assets/codeowners-Bp6g37R7.js +1 -0
  392. pythinker_code/web/static/assets/codeql-DsOJ9woJ.js +1 -0
  393. pythinker_code/web/static/assets/coffee-Ch7k5sss.js +1 -0
  394. pythinker_code/web/static/assets/common-lisp-Cg-RD9OK.js +1 -0
  395. pythinker_code/web/static/assets/coq-DkFqJrB1.js +1 -0
  396. pythinker_code/web/static/assets/cose-bilkent-S5V4N54A-DialjZpd.js +1 -0
  397. pythinker_code/web/static/assets/cpp-CofmeUqb.js +1 -0
  398. pythinker_code/web/static/assets/crystal-tKQVLTB8.js +1 -0
  399. pythinker_code/web/static/assets/csharp-K5feNrxe.js +1 -0
  400. pythinker_code/web/static/assets/css-DPfMkruS.js +1 -0
  401. pythinker_code/web/static/assets/csv-fuZLfV_i.js +1 -0
  402. pythinker_code/web/static/assets/cue-D82EKSYY.js +1 -0
  403. pythinker_code/web/static/assets/cypher-COkxafJQ.js +1 -0
  404. pythinker_code/web/static/assets/cytoscape.esm-C_Fzpdck.js +321 -0
  405. pythinker_code/web/static/assets/d-85-TOEBH.js +1 -0
  406. pythinker_code/web/static/assets/dagre-6UL2VRFP-DfuvkZZ7.js +4 -0
  407. pythinker_code/web/static/assets/dark-plus-C3mMm8J8.js +1 -0
  408. pythinker_code/web/static/assets/dart-CF10PKvl.js +1 -0
  409. pythinker_code/web/static/assets/dax-CEL-wOlO.js +1 -0
  410. pythinker_code/web/static/assets/defaultLocale-DX6XiGOO.js +1 -0
  411. pythinker_code/web/static/assets/desktop-BmXAJ9_W.js +1 -0
  412. pythinker_code/web/static/assets/diagram-PSM6KHXK-DLGctX3v.js +24 -0
  413. pythinker_code/web/static/assets/diagram-QEK2KX5R-DnxN6S0F.js +43 -0
  414. pythinker_code/web/static/assets/diagram-S2PKOQOG-Caq_Set9.js +24 -0
  415. pythinker_code/web/static/assets/diff-D97Zzqfu.js +1 -0
  416. pythinker_code/web/static/assets/docker-BcOcwvcX.js +1 -0
  417. pythinker_code/web/static/assets/dotenv-Da5cRb03.js +1 -0
  418. pythinker_code/web/static/assets/dracula-BzJJZx-M.js +1 -0
  419. pythinker_code/web/static/assets/dracula-soft-BXkSAIEj.js +1 -0
  420. pythinker_code/web/static/assets/dream-maker-BtqSS_iP.js +1 -0
  421. pythinker_code/web/static/assets/edge-BkV0erSs.js +1 -0
  422. pythinker_code/web/static/assets/elixir-CDX3lj18.js +1 -0
  423. pythinker_code/web/static/assets/elm-DbKCFpqz.js +1 -0
  424. pythinker_code/web/static/assets/emacs-lisp-C9XAeP06.js +1 -0
  425. pythinker_code/web/static/assets/erDiagram-Q2GNP2WA-BgTfALoK.js +60 -0
  426. pythinker_code/web/static/assets/erb-BOJIQeun.js +1 -0
  427. pythinker_code/web/static/assets/erlang-DsQrWhSR.js +1 -0
  428. pythinker_code/web/static/assets/everforest-dark-BgDCqdQA.js +1 -0
  429. pythinker_code/web/static/assets/everforest-light-C8M2exoo.js +1 -0
  430. pythinker_code/web/static/assets/fennel-BYunw83y.js +1 -0
  431. pythinker_code/web/static/assets/fish-BvzEVeQv.js +1 -0
  432. pythinker_code/web/static/assets/flowDiagram-NV44I4VS-QjW_fnGi.js +162 -0
  433. pythinker_code/web/static/assets/fluent-C4IJs8-o.js +1 -0
  434. pythinker_code/web/static/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
  435. pythinker_code/web/static/assets/fortran-free-form-BxgE0vQu.js +1 -0
  436. pythinker_code/web/static/assets/fsharp-CXgrBDvD.js +1 -0
  437. pythinker_code/web/static/assets/ganttDiagram-JELNMOA3-fqi8JFof.js +267 -0
  438. pythinker_code/web/static/assets/gdresource-B7Tvp0Sc.js +1 -0
  439. pythinker_code/web/static/assets/gdscript-DTMYz4Jt.js +1 -0
  440. pythinker_code/web/static/assets/gdshader-DkwncUOv.js +1 -0
  441. pythinker_code/web/static/assets/genie-D0YGMca9.js +1 -0
  442. pythinker_code/web/static/assets/gherkin-DyxjwDmM.js +1 -0
  443. pythinker_code/web/static/assets/git-commit-F4YmCXRG.js +1 -0
  444. pythinker_code/web/static/assets/git-rebase-r7XF79zn.js +1 -0
  445. pythinker_code/web/static/assets/gitGraphDiagram-NY62KEGX-i7o6VQ8x.js +65 -0
  446. pythinker_code/web/static/assets/github-dark-DHJKELXO.js +1 -0
  447. pythinker_code/web/static/assets/github-dark-default-Cuk6v7N8.js +1 -0
  448. pythinker_code/web/static/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  449. pythinker_code/web/static/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  450. pythinker_code/web/static/assets/github-light-DAi9KRSo.js +1 -0
  451. pythinker_code/web/static/assets/github-light-default-D7oLnXFd.js +1 -0
  452. pythinker_code/web/static/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  453. pythinker_code/web/static/assets/gleam-BspZqrRM.js +1 -0
  454. pythinker_code/web/static/assets/glimmer-js-Rg0-pVw9.js +1 -0
  455. pythinker_code/web/static/assets/glimmer-ts-U6CK756n.js +1 -0
  456. pythinker_code/web/static/assets/glsl-DplSGwfg.js +1 -0
  457. pythinker_code/web/static/assets/gn-n2N0HUVH.js +1 -0
  458. pythinker_code/web/static/assets/gnuplot-DdkO51Og.js +1 -0
  459. pythinker_code/web/static/assets/go-Dn2_MT6a.js +1 -0
  460. pythinker_code/web/static/assets/graph-C0vZK2pT.js +1 -0
  461. pythinker_code/web/static/assets/graphql-ChdNCCLP.js +1 -0
  462. pythinker_code/web/static/assets/groovy-gcz8RCvz.js +1 -0
  463. pythinker_code/web/static/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  464. pythinker_code/web/static/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  465. pythinker_code/web/static/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  466. pythinker_code/web/static/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  467. pythinker_code/web/static/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  468. pythinker_code/web/static/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  469. pythinker_code/web/static/assets/hack-CaT9iCJl.js +1 -0
  470. pythinker_code/web/static/assets/haml-B8DHNrY2.js +1 -0
  471. pythinker_code/web/static/assets/handlebars-BL8al0AC.js +1 -0
  472. pythinker_code/web/static/assets/haskell-Df6bDoY_.js +1 -0
  473. pythinker_code/web/static/assets/haxe-CzTSHFRz.js +1 -0
  474. pythinker_code/web/static/assets/hcl-BWvSN4gD.js +1 -0
  475. pythinker_code/web/static/assets/hjson-D5-asLiD.js +1 -0
  476. pythinker_code/web/static/assets/hlsl-D3lLCCz7.js +1 -0
  477. pythinker_code/web/static/assets/houston-DnULxvSX.js +1 -0
  478. pythinker_code/web/static/assets/html-GMplVEZG.js +1 -0
  479. pythinker_code/web/static/assets/html-derivative-BFtXZ54Q.js +1 -0
  480. pythinker_code/web/static/assets/http-jrhK8wxY.js +1 -0
  481. pythinker_code/web/static/assets/hurl-irOxFIW8.js +1 -0
  482. pythinker_code/web/static/assets/hxml-Bvhsp5Yf.js +1 -0
  483. pythinker_code/web/static/assets/hy-DFXneXwc.js +1 -0
  484. pythinker_code/web/static/assets/imba-DGztddWO.js +1 -0
  485. pythinker_code/web/static/assets/index-BYCCk6-K.js +153 -0
  486. pythinker_code/web/static/assets/index-BpoLgcEt.js +1 -0
  487. pythinker_code/web/static/assets/index-Cpy4G3uJ.js +2 -0
  488. pythinker_code/web/static/assets/index-CzV_vCfu.css +1 -0
  489. pythinker_code/web/static/assets/index-DI2oedCt.js +19 -0
  490. pythinker_code/web/static/assets/index-DdIkp80K.js +5 -0
  491. pythinker_code/web/static/assets/infoDiagram-WHAUD3N6-BMPpeZSM.js +2 -0
  492. pythinker_code/web/static/assets/ini-BEwlwnbL.js +1 -0
  493. pythinker_code/web/static/assets/init-Gi6I4Gst.js +1 -0
  494. pythinker_code/web/static/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  495. pythinker_code/web/static/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  496. pythinker_code/web/static/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  497. pythinker_code/web/static/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  498. pythinker_code/web/static/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  499. pythinker_code/web/static/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  500. pythinker_code/web/static/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  501. pythinker_code/web/static/assets/java-CylS5w8V.js +1 -0
  502. pythinker_code/web/static/assets/javascript-wDzz0qaB.js +1 -0
  503. pythinker_code/web/static/assets/jinja-4LBKfQ-Z.js +1 -0
  504. pythinker_code/web/static/assets/jison-wvAkD_A8.js +1 -0
  505. pythinker_code/web/static/assets/journeyDiagram-XKPGCS4Q-DAM7gngo.js +139 -0
  506. pythinker_code/web/static/assets/json-Cp-IABpG.js +1 -0
  507. pythinker_code/web/static/assets/json5-C9tS-k6U.js +1 -0
  508. pythinker_code/web/static/assets/jsonc-Des-eS-w.js +1 -0
  509. pythinker_code/web/static/assets/jsonl-DcaNXYhu.js +1 -0
  510. pythinker_code/web/static/assets/jsonnet-DFQXde-d.js +1 -0
  511. pythinker_code/web/static/assets/jssm-C2t-YnRu.js +1 -0
  512. pythinker_code/web/static/assets/jsx-g9-lgVsj.js +1 -0
  513. pythinker_code/web/static/assets/julia-CxzCAyBv.js +1 -0
  514. pythinker_code/web/static/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  515. pythinker_code/web/static/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  516. pythinker_code/web/static/assets/kanagawa-wave-DWedfzmr.js +1 -0
  517. pythinker_code/web/static/assets/kanban-definition-3W4ZIXB7-ChpBpV0k.js +89 -0
  518. pythinker_code/web/static/assets/katex-D2lIc1rk.css +1 -0
  519. pythinker_code/web/static/assets/kdl-DV7GczEv.js +1 -0
  520. pythinker_code/web/static/assets/kotlin-BdnUsdx6.js +1 -0
  521. pythinker_code/web/static/assets/kusto-DZf3V79B.js +1 -0
  522. pythinker_code/web/static/assets/laserwave-DUszq2jm.js +1 -0
  523. pythinker_code/web/static/assets/latex-B4uzh10-.js +1 -0
  524. pythinker_code/web/static/assets/layout-C3Jp1gKO.js +1 -0
  525. pythinker_code/web/static/assets/lean-BZvkOJ9d.js +1 -0
  526. pythinker_code/web/static/assets/less-B1dDrJ26.js +1 -0
  527. pythinker_code/web/static/assets/light-plus-B7mTdjB0.js +1 -0
  528. pythinker_code/web/static/assets/linear-BGHtL1N4.js +1 -0
  529. pythinker_code/web/static/assets/liquid-DYVedYrR.js +1 -0
  530. pythinker_code/web/static/assets/llvm-BtvRca6l.js +1 -0
  531. pythinker_code/web/static/assets/log-2UxHyX5q.js +1 -0
  532. pythinker_code/web/static/assets/logo-BtOb2qkB.js +1 -0
  533. pythinker_code/web/static/assets/lua-BbnMAYS6.js +1 -0
  534. pythinker_code/web/static/assets/luau-C-HG3fhB.js +1 -0
  535. pythinker_code/web/static/assets/make-CHLpvVh8.js +1 -0
  536. pythinker_code/web/static/assets/markdown-Cvjx9yec.js +1 -0
  537. pythinker_code/web/static/assets/marko-DZsq8hO1.js +1 -0
  538. pythinker_code/web/static/assets/material-theme-D5KoaKCx.js +1 -0
  539. pythinker_code/web/static/assets/material-theme-darker-BfHTSMKl.js +1 -0
  540. pythinker_code/web/static/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  541. pythinker_code/web/static/assets/material-theme-ocean-CyktbL80.js +1 -0
  542. pythinker_code/web/static/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  543. pythinker_code/web/static/assets/matlab-D7o27uSR.js +1 -0
  544. pythinker_code/web/static/assets/mdc-DUICxH0z.js +1 -0
  545. pythinker_code/web/static/assets/mdx-Cmh6b_Ma.js +1 -0
  546. pythinker_code/web/static/assets/mermaid-VLURNSYL-B2P5VJ9v.css +1 -0
  547. pythinker_code/web/static/assets/mermaid-VLURNSYL-C_HW6koB.js +465 -0
  548. pythinker_code/web/static/assets/mermaid-mWjccvbQ.js +1 -0
  549. pythinker_code/web/static/assets/mermaid.core-CnT4VrPC.js +191 -0
  550. pythinker_code/web/static/assets/min-Dn5VRVmX.js +1 -0
  551. pythinker_code/web/static/assets/min-dark-CafNBF8u.js +1 -0
  552. pythinker_code/web/static/assets/min-light-CTRr51gU.js +1 -0
  553. pythinker_code/web/static/assets/mindmap-definition-VGOIOE7T-x8EwhfIt.js +68 -0
  554. pythinker_code/web/static/assets/mipsasm-CKIfxQSi.js +1 -0
  555. pythinker_code/web/static/assets/mojo-B93PlW-d.js +1 -0
  556. pythinker_code/web/static/assets/monokai-D4h5O-jR.js +1 -0
  557. pythinker_code/web/static/assets/moonbit-Ba13S78F.js +1 -0
  558. pythinker_code/web/static/assets/move-Bu9oaDYs.js +1 -0
  559. pythinker_code/web/static/assets/narrat-DRg8JJMk.js +1 -0
  560. pythinker_code/web/static/assets/nextflow-BrzmwbiE.js +1 -0
  561. pythinker_code/web/static/assets/nginx-DknmC5AR.js +1 -0
  562. pythinker_code/web/static/assets/night-owl-C39BiMTA.js +1 -0
  563. pythinker_code/web/static/assets/nim-CVrawwO9.js +1 -0
  564. pythinker_code/web/static/assets/nix-CwoSXNpI.js +1 -0
  565. pythinker_code/web/static/assets/nord-Ddv68eIx.js +1 -0
  566. pythinker_code/web/static/assets/nushell-C-sUppwS.js +1 -0
  567. pythinker_code/web/static/assets/objective-c-DXmwc3jG.js +1 -0
  568. pythinker_code/web/static/assets/objective-cpp-CLxacb5B.js +1 -0
  569. pythinker_code/web/static/assets/ocaml-C0hk2d4L.js +1 -0
  570. pythinker_code/web/static/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  571. pythinker_code/web/static/assets/one-light-PoHY5YXO.js +1 -0
  572. pythinker_code/web/static/assets/openscad-C4EeE6gA.js +1 -0
  573. pythinker_code/web/static/assets/ordinal-Cboi1Yqb.js +1 -0
  574. pythinker_code/web/static/assets/pascal-D93ZcfNL.js +1 -0
  575. pythinker_code/web/static/assets/perl-C0TMdlhV.js +1 -0
  576. pythinker_code/web/static/assets/php-CDn_0X-4.js +1 -0
  577. pythinker_code/web/static/assets/pieDiagram-ADFJNKIX-DgxBKGz2.js +30 -0
  578. pythinker_code/web/static/assets/pkl-u5AG7uiY.js +1 -0
  579. pythinker_code/web/static/assets/plastic-3e1v2bzS.js +1 -0
  580. pythinker_code/web/static/assets/plsql-ChMvpjG-.js +1 -0
  581. pythinker_code/web/static/assets/po-BTJTHyun.js +1 -0
  582. pythinker_code/web/static/assets/poimandres-CS3Unz2-.js +1 -0
  583. pythinker_code/web/static/assets/polar-C0HS_06l.js +1 -0
  584. pythinker_code/web/static/assets/postcss-CXtECtnM.js +1 -0
  585. pythinker_code/web/static/assets/powerquery-CEu0bR-o.js +1 -0
  586. pythinker_code/web/static/assets/powershell-Dpen1YoG.js +1 -0
  587. pythinker_code/web/static/assets/prisma-Dd19v3D-.js +1 -0
  588. pythinker_code/web/static/assets/prolog-CbFg5uaA.js +1 -0
  589. pythinker_code/web/static/assets/proto-C7zT0LnQ.js +1 -0
  590. pythinker_code/web/static/assets/pug-CGlum2m_.js +1 -0
  591. pythinker_code/web/static/assets/puppet-BMWR74SV.js +1 -0
  592. pythinker_code/web/static/assets/purescript-CklMAg4u.js +1 -0
  593. pythinker_code/web/static/assets/python-B6aJPvgy.js +1 -0
  594. pythinker_code/web/static/assets/qml-3beO22l8.js +1 -0
  595. pythinker_code/web/static/assets/qmldir-C8lEn-DE.js +1 -0
  596. pythinker_code/web/static/assets/qss-IeuSbFQv.js +1 -0
  597. pythinker_code/web/static/assets/quadrantDiagram-AYHSOK5B-DSpe_dqk.js +7 -0
  598. pythinker_code/web/static/assets/r-Dspwwk_N.js +1 -0
  599. pythinker_code/web/static/assets/racket-BqYA7rlc.js +1 -0
  600. pythinker_code/web/static/assets/raku-DXvB9xmW.js +1 -0
  601. pythinker_code/web/static/assets/razor-C1TweQQi.js +1 -0
  602. pythinker_code/web/static/assets/red-bN70gL4F.js +1 -0
  603. pythinker_code/web/static/assets/reg-C-SQnVFl.js +1 -0
  604. pythinker_code/web/static/assets/regexp-CDVJQ6XC.js +1 -0
  605. pythinker_code/web/static/assets/rel-C3B-1QV4.js +1 -0
  606. pythinker_code/web/static/assets/requirementDiagram-UZGBJVZJ-8o9hozL-.js +64 -0
  607. pythinker_code/web/static/assets/riscv-BM1_JUlF.js +1 -0
  608. pythinker_code/web/static/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
  609. pythinker_code/web/static/assets/rose-pine-moon-D4_iv3hh.js +1 -0
  610. pythinker_code/web/static/assets/rose-pine-qdsjHGoJ.js +1 -0
  611. pythinker_code/web/static/assets/rosmsg-BJDFO7_C.js +1 -0
  612. pythinker_code/web/static/assets/rst-B0xPkSld.js +1 -0
  613. pythinker_code/web/static/assets/ruby-BvKwtOVI.js +1 -0
  614. pythinker_code/web/static/assets/rust-B1yitclQ.js +1 -0
  615. pythinker_code/web/static/assets/sankeyDiagram-TZEHDZUN-BLOSUixH.js +10 -0
  616. pythinker_code/web/static/assets/sas-cz2c8ADy.js +1 -0
  617. pythinker_code/web/static/assets/sass-Cj5Yp3dK.js +1 -0
  618. pythinker_code/web/static/assets/scala-C151Ov-r.js +1 -0
  619. pythinker_code/web/static/assets/scheme-C98Dy4si.js +1 -0
  620. pythinker_code/web/static/assets/scss-OYdSNvt2.js +1 -0
  621. pythinker_code/web/static/assets/sdbl-DVxCFoDh.js +1 -0
  622. pythinker_code/web/static/assets/sequenceDiagram-WL72ISMW-6F2G8JTU.js +145 -0
  623. pythinker_code/web/static/assets/shaderlab-Dg9Lc6iA.js +1 -0
  624. pythinker_code/web/static/assets/shellscript-Yzrsuije.js +1 -0
  625. pythinker_code/web/static/assets/shellsession-BADoaaVG.js +1 -0
  626. pythinker_code/web/static/assets/slack-dark-BthQWCQV.js +1 -0
  627. pythinker_code/web/static/assets/slack-ochin-DqwNpetd.js +1 -0
  628. pythinker_code/web/static/assets/smalltalk-BERRCDM3.js +1 -0
  629. pythinker_code/web/static/assets/snazzy-light-Bw305WKR.js +1 -0
  630. pythinker_code/web/static/assets/solarized-dark-DXbdFlpD.js +1 -0
  631. pythinker_code/web/static/assets/solarized-light-L9t79GZl.js +1 -0
  632. pythinker_code/web/static/assets/solidity-rGO070M0.js +1 -0
  633. pythinker_code/web/static/assets/soy-Brmx7dQM.js +1 -0
  634. pythinker_code/web/static/assets/sparql-rVzFXLq3.js +1 -0
  635. pythinker_code/web/static/assets/splunk-BtCnVYZw.js +1 -0
  636. pythinker_code/web/static/assets/sql-BLtJtn59.js +1 -0
  637. pythinker_code/web/static/assets/ssh-config-_ykCGR6B.js +1 -0
  638. pythinker_code/web/static/assets/stata-BH5u7GGu.js +1 -0
  639. pythinker_code/web/static/assets/stateDiagram-FKZM4ZOC-DP8xP0iJ.js +1 -0
  640. pythinker_code/web/static/assets/stateDiagram-v2-4FDKWEC3-1l6-EZNX.js +1 -0
  641. pythinker_code/web/static/assets/stylus-BEDo0Tqx.js +1 -0
  642. pythinker_code/web/static/assets/svelte-zxCyuUbr.js +1 -0
  643. pythinker_code/web/static/assets/swift-Dg5xB15N.js +1 -0
  644. pythinker_code/web/static/assets/synthwave-84-CbfX1IO0.js +1 -0
  645. pythinker_code/web/static/assets/system-verilog-CnnmHF94.js +1 -0
  646. pythinker_code/web/static/assets/systemd-4A_iFExJ.js +1 -0
  647. pythinker_code/web/static/assets/talonscript-CkByrt1z.js +1 -0
  648. pythinker_code/web/static/assets/tasl-QIJgUcNo.js +1 -0
  649. pythinker_code/web/static/assets/tcl-dwOrl1Do.js +1 -0
  650. pythinker_code/web/static/assets/templ-W15q3VgB.js +1 -0
  651. pythinker_code/web/static/assets/terraform-BETggiCN.js +1 -0
  652. pythinker_code/web/static/assets/tex-CvyZ59Mk.js +1 -0
  653. pythinker_code/web/static/assets/timeline-definition-IT6M3QCI-DMgruDfK.js +61 -0
  654. pythinker_code/web/static/assets/tokyo-night-hegEt444.js +1 -0
  655. pythinker_code/web/static/assets/toml-vGWfd6FD.js +1 -0
  656. pythinker_code/web/static/assets/treemap-KMMF4GRG-Bo9ZkrAK.js +128 -0
  657. pythinker_code/web/static/assets/ts-tags-zn1MmPIZ.js +1 -0
  658. pythinker_code/web/static/assets/tsv-B_m7g4N7.js +1 -0
  659. pythinker_code/web/static/assets/tsx-COt5Ahok.js +1 -0
  660. pythinker_code/web/static/assets/turtle-BsS91CYL.js +1 -0
  661. pythinker_code/web/static/assets/twig-CO9l9SDP.js +1 -0
  662. pythinker_code/web/static/assets/typescript-BPQ3VLAy.js +1 -0
  663. pythinker_code/web/static/assets/typespec-BGHnOYBU.js +1 -0
  664. pythinker_code/web/static/assets/typst-DHCkPAjA.js +1 -0
  665. pythinker_code/web/static/assets/v-BcVCzyr7.js +1 -0
  666. pythinker_code/web/static/assets/vala-CsfeWuGM.js +1 -0
  667. pythinker_code/web/static/assets/vb-D17OF-Vu.js +1 -0
  668. pythinker_code/web/static/assets/verilog-BQ8w6xss.js +1 -0
  669. pythinker_code/web/static/assets/vesper-DU1UobuO.js +1 -0
  670. pythinker_code/web/static/assets/vhdl-CeAyd5Ju.js +1 -0
  671. pythinker_code/web/static/assets/viml-CJc9bBzg.js +1 -0
  672. pythinker_code/web/static/assets/vitesse-black-Bkuqu6BP.js +1 -0
  673. pythinker_code/web/static/assets/vitesse-dark-D0r3Knsf.js +1 -0
  674. pythinker_code/web/static/assets/vitesse-light-CVO1_9PV.js +1 -0
  675. pythinker_code/web/static/assets/vue-DN_0RTcg.js +1 -0
  676. pythinker_code/web/static/assets/vue-html-AaS7Mt5G.js +1 -0
  677. pythinker_code/web/static/assets/vue-vine-CQOfvN7w.js +1 -0
  678. pythinker_code/web/static/assets/vyper-CDx5xZoG.js +1 -0
  679. pythinker_code/web/static/assets/wasm-CG6Dc4jp.js +1 -0
  680. pythinker_code/web/static/assets/wasm-MzD3tlZU.js +1 -0
  681. pythinker_code/web/static/assets/wenyan-BV7otONQ.js +1 -0
  682. pythinker_code/web/static/assets/wgsl-Dx-B1_4e.js +1 -0
  683. pythinker_code/web/static/assets/wikitext-BhOHFoWU.js +1 -0
  684. pythinker_code/web/static/assets/wit-5i3qLPDT.js +1 -0
  685. pythinker_code/web/static/assets/wolfram-lXgVvXCa.js +1 -0
  686. pythinker_code/web/static/assets/xml-sdJ4AIDG.js +1 -0
  687. pythinker_code/web/static/assets/xsl-CtQFsRM5.js +1 -0
  688. pythinker_code/web/static/assets/xychartDiagram-PRI3JC2R-GeF2johi.js +7 -0
  689. pythinker_code/web/static/assets/yaml-Buea-lGh.js +1 -0
  690. pythinker_code/web/static/assets/zenscript-DVFEvuxE.js +1 -0
  691. pythinker_code/web/static/assets/zig-VOosw3JB.js +1 -0
  692. pythinker_code/web/static/brand/apple-touch-icon.png +0 -0
  693. pythinker_code/web/static/brand/arctecture.webp +0 -0
  694. pythinker_code/web/static/brand/bimi-logo.svg +46 -0
  695. pythinker_code/web/static/brand/favicon.ico +0 -0
  696. pythinker_code/web/static/brand/fonts/dm-sans-latin-ext.woff2 +0 -0
  697. pythinker_code/web/static/brand/fonts/dm-sans-latin.woff2 +0 -0
  698. pythinker_code/web/static/brand/fonts/instrument-sans-latin-ext.woff2 +0 -0
  699. pythinker_code/web/static/brand/fonts/instrument-sans-latin.woff2 +0 -0
  700. pythinker_code/web/static/brand/fonts/instrument-serif-latin-ext.woff2 +0 -0
  701. pythinker_code/web/static/brand/fonts/instrument-serif-latin.woff2 +0 -0
  702. pythinker_code/web/static/brand/fonts/libre-baskerville-italic-latin-ext.woff2 +0 -0
  703. pythinker_code/web/static/brand/fonts/libre-baskerville-italic-latin.woff2 +0 -0
  704. pythinker_code/web/static/brand/fonts/libre-baskerville-latin-ext.woff2 +0 -0
  705. pythinker_code/web/static/brand/fonts/libre-baskerville-latin.woff2 +0 -0
  706. pythinker_code/web/static/brand/fonts/roboto-latin-ext.woff2 +0 -0
  707. pythinker_code/web/static/brand/fonts/roboto-latin.woff2 +0 -0
  708. pythinker_code/web/static/brand/icon-192.png +0 -0
  709. pythinker_code/web/static/brand/icon-512.png +0 -0
  710. pythinker_code/web/static/brand/icon.svg +1 -0
  711. pythinker_code/web/static/brand/logo.png +0 -0
  712. pythinker_code/web/static/brand/pythinker_animated.svg +79 -0
  713. pythinker_code/web/static/brand/robots.txt +4 -0
  714. pythinker_code/web/static/index.html +15 -0
  715. pythinker_code/web/static/logo.png +0 -0
  716. pythinker_code/web/store/__init__.py +1 -0
  717. pythinker_code/web/store/sessions.py +432 -0
  718. pythinker_code/wire/__init__.py +148 -0
  719. pythinker_code/wire/file.py +151 -0
  720. pythinker_code/wire/jsonrpc.py +263 -0
  721. pythinker_code/wire/protocol.py +2 -0
  722. pythinker_code/wire/root_hub.py +27 -0
  723. pythinker_code/wire/serde.py +26 -0
  724. pythinker_code/wire/server.py +1069 -0
  725. pythinker_code/wire/types.py +698 -0
  726. pythinker_code-2.0.0.dist-info/METADATA +660 -0
  727. pythinker_code-2.0.0.dist-info/RECORD +731 -0
  728. pythinker_code-2.0.0.dist-info/WHEEL +4 -0
  729. pythinker_code-2.0.0.dist-info/entry_points.txt +4 -0
  730. pythinker_code-2.0.0.dist-info/licenses/LICENSE +202 -0
  731. pythinker_code-2.0.0.dist-info/licenses/NOTICE +14 -0
@@ -0,0 +1,1326 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from importlib import import_module
5
+ from pathlib import Path
6
+ from typing import TYPE_CHECKING, Annotated, Any, Literal
7
+
8
+ import typer
9
+
10
+ if TYPE_CHECKING:
11
+ from pythinker_code.session import Session
12
+
13
+
14
+ class Reload(Exception):
15
+ """Reload configuration."""
16
+
17
+ def __init__(self, session_id: str | None = None, prefill_text: str | None = None):
18
+ super().__init__("reload")
19
+ self.session_id = session_id
20
+ self.prefill_text = prefill_text
21
+ self.source_session: Session | None = None
22
+
23
+
24
+ class SwitchToWeb(Exception):
25
+ """Switch to web interface."""
26
+
27
+ def __init__(self, session_id: str | None = None):
28
+ super().__init__("switch_to_web")
29
+ self.session_id = session_id
30
+
31
+
32
+ class SwitchToVis(Exception):
33
+ """Switch to vis (tracing visualizer) interface."""
34
+
35
+ def __init__(self, session_id: str | None = None):
36
+ super().__init__("switch_to_vis")
37
+ self.session_id = session_id
38
+
39
+
40
+ def load_config() -> Any:
41
+ from pythinker_code.config import load_config as impl
42
+
43
+ return impl()
44
+
45
+
46
+ def login_anthropic_api_key(*args: Any, **kwargs: Any) -> Any:
47
+ from pythinker_code.auth.anthropic_direct import login_anthropic_api_key as impl
48
+
49
+ return impl(*args, **kwargs)
50
+
51
+
52
+ def logout_anthropic(*args: Any, **kwargs: Any) -> Any:
53
+ from pythinker_code.auth.anthropic_direct import logout_anthropic as impl
54
+
55
+ return impl(*args, **kwargs)
56
+
57
+
58
+ def login_deepseek_api_key(*args: Any, **kwargs: Any) -> Any:
59
+ from pythinker_code.auth.deepseek import login_deepseek_api_key as impl
60
+
61
+ return impl(*args, **kwargs)
62
+
63
+
64
+ def logout_deepseek(*args: Any, **kwargs: Any) -> Any:
65
+ from pythinker_code.auth.deepseek import logout_deepseek as impl
66
+
67
+ return impl(*args, **kwargs)
68
+
69
+
70
+ def login_lm_studio(*args: Any, **kwargs: Any) -> Any:
71
+ from pythinker_code.auth.lm_studio import login_lm_studio as impl
72
+
73
+ return impl(*args, **kwargs)
74
+
75
+
76
+ def logout_lm_studio(*args: Any, **kwargs: Any) -> Any:
77
+ from pythinker_code.auth.lm_studio import logout_lm_studio as impl
78
+
79
+ return impl(*args, **kwargs)
80
+
81
+
82
+ def login_ollama(*args: Any, **kwargs: Any) -> Any:
83
+ from pythinker_code.auth.ollama import login_ollama as impl
84
+
85
+ return impl(*args, **kwargs)
86
+
87
+
88
+ def logout_ollama(*args: Any, **kwargs: Any) -> Any:
89
+ from pythinker_code.auth.ollama import logout_ollama as impl
90
+
91
+ return impl(*args, **kwargs)
92
+
93
+
94
+ def login_minimax_api_key(*args: Any, **kwargs: Any) -> Any:
95
+ from pythinker_code.auth.minimax import login_minimax_api_key as impl
96
+
97
+ return impl(*args, **kwargs)
98
+
99
+
100
+ def logout_minimax(*args: Any, **kwargs: Any) -> Any:
101
+ from pythinker_code.auth.minimax import logout_minimax as impl
102
+
103
+ return impl(*args, **kwargs)
104
+
105
+
106
+ def login_openai_api_key(*args: Any, **kwargs: Any) -> Any:
107
+ from pythinker_code.auth.openai import login_openai_api_key as impl
108
+
109
+ return impl(*args, **kwargs)
110
+
111
+
112
+ def login_openai_browser(*args: Any, **kwargs: Any) -> Any:
113
+ from pythinker_code.auth.openai import login_openai_browser as impl
114
+
115
+ return impl(*args, **kwargs)
116
+
117
+
118
+ def login_openai_headless(*args: Any, **kwargs: Any) -> Any:
119
+ from pythinker_code.auth.openai import login_openai_headless as impl
120
+
121
+ return impl(*args, **kwargs)
122
+
123
+
124
+ def logout_openai(*args: Any, **kwargs: Any) -> Any:
125
+ from pythinker_code.auth.openai import logout_openai as impl
126
+
127
+ return impl(*args, **kwargs)
128
+
129
+
130
+ def login_opencode_go_api_key(*args: Any, **kwargs: Any) -> Any:
131
+ from pythinker_code.auth.opencode_go import login_opencode_go_api_key as impl
132
+
133
+ return impl(*args, **kwargs)
134
+
135
+
136
+ def logout_opencode_go(*args: Any, **kwargs: Any) -> Any:
137
+ from pythinker_code.auth.opencode_go import logout_opencode_go as impl
138
+
139
+ return impl(*args, **kwargs)
140
+
141
+
142
+ def login_openrouter_api_key(*args: Any, **kwargs: Any) -> Any:
143
+ from pythinker_code.auth.openrouter import login_openrouter_api_key as impl
144
+
145
+ return impl(*args, **kwargs)
146
+
147
+
148
+ def logout_openrouter(*args: Any, **kwargs: Any) -> Any:
149
+ from pythinker_code.auth.openrouter import logout_openrouter as impl
150
+
151
+ return impl(*args, **kwargs)
152
+
153
+
154
+ LazySubcommandGroup = import_module(f"{__name__}._lazy_group").LazySubcommandGroup
155
+
156
+
157
+ cli = typer.Typer(
158
+ cls=LazySubcommandGroup,
159
+ epilog="""\b\
160
+ Documentation: https://mohamed-elkholy95.github.io/Pythinker-Code/\n
161
+ LLM friendly version: https://mohamed-elkholy95.github.io/Pythinker-Code/llms.txt""",
162
+ add_completion=False,
163
+ context_settings={"help_option_names": ["-h", "--help"]},
164
+ help="Pythinker, your next CLI agent.",
165
+ )
166
+
167
+ UIMode = Literal["shell", "print", "acp", "wire"]
168
+
169
+
170
+ class ExitCode:
171
+ SUCCESS = 0
172
+ FAILURE = 1
173
+ RETRYABLE = 75 # EX_TEMPFAIL from sysexits.h
174
+
175
+
176
+ InputFormat = Literal["text", "stream-json"]
177
+ OutputFormat = Literal["text", "stream-json"]
178
+
179
+
180
+ def _strip_session_id_suffix(title: str, session_id: str) -> str:
181
+ """Remove the trailing `` (session_id)`` suffix from a session title, if present."""
182
+ suffix = f" ({session_id})"
183
+ return title.rsplit(suffix, 1)[0] if title.endswith(suffix) else title
184
+
185
+
186
+ def _version_callback(value: bool) -> None:
187
+ if value:
188
+ from pythinker_code.constant import get_version
189
+
190
+ typer.echo(f"pythinker, version {get_version()}")
191
+ raise typer.Exit()
192
+
193
+
194
+ @cli.callback(invoke_without_command=True)
195
+ def pythinker(
196
+ ctx: typer.Context,
197
+ # Meta
198
+ version: Annotated[
199
+ bool,
200
+ typer.Option(
201
+ "--version",
202
+ "-V",
203
+ help="Show version and exit.",
204
+ callback=_version_callback,
205
+ is_eager=True,
206
+ ),
207
+ ] = False,
208
+ verbose: Annotated[
209
+ bool,
210
+ typer.Option(
211
+ "--verbose",
212
+ help="Print verbose information. Default: no.",
213
+ ),
214
+ ] = False,
215
+ debug: Annotated[
216
+ bool,
217
+ typer.Option(
218
+ "--debug",
219
+ help="Log debug information. Default: no.",
220
+ ),
221
+ ] = False,
222
+ # Basic configuration
223
+ local_work_dir: Annotated[
224
+ Path | None,
225
+ typer.Option(
226
+ "--work-dir",
227
+ "-w",
228
+ exists=True,
229
+ file_okay=False,
230
+ dir_okay=True,
231
+ readable=True,
232
+ writable=True,
233
+ help="Working directory for the agent. Default: current directory.",
234
+ ),
235
+ ] = None,
236
+ local_add_dirs: Annotated[
237
+ list[Path] | None,
238
+ typer.Option(
239
+ "--add-dir",
240
+ exists=True,
241
+ file_okay=False,
242
+ dir_okay=True,
243
+ readable=True,
244
+ help=(
245
+ "Add an additional directory to the workspace scope. "
246
+ "Can be specified multiple times."
247
+ ),
248
+ ),
249
+ ] = None,
250
+ session_id: Annotated[
251
+ str | None,
252
+ typer.Option(
253
+ "--session",
254
+ "--resume",
255
+ "-S",
256
+ "-r",
257
+ help=(
258
+ "Resume a session. "
259
+ "With ID: resume that session. "
260
+ "Without ID: interactively pick a session."
261
+ ),
262
+ ),
263
+ ] = None,
264
+ continue_: Annotated[
265
+ bool,
266
+ typer.Option(
267
+ "--continue",
268
+ "-C",
269
+ help="Continue the previous session for the working directory. Default: no.",
270
+ ),
271
+ ] = False,
272
+ config_string: Annotated[
273
+ str | None,
274
+ typer.Option(
275
+ "--config",
276
+ help="Config TOML/JSON string to load. Default: none.",
277
+ ),
278
+ ] = None,
279
+ config_file: Annotated[
280
+ Path | None,
281
+ typer.Option(
282
+ "--config-file",
283
+ exists=True,
284
+ file_okay=True,
285
+ dir_okay=False,
286
+ readable=True,
287
+ help="Config TOML/JSON file to load. Default: ~/.pythinker/config.toml.",
288
+ ),
289
+ ] = None,
290
+ model_name: Annotated[
291
+ str | None,
292
+ typer.Option(
293
+ "--model",
294
+ "-m",
295
+ help="LLM model to use. Default: default model set in config file.",
296
+ ),
297
+ ] = None,
298
+ thinking: Annotated[
299
+ bool | None,
300
+ typer.Option(
301
+ "--thinking/--no-thinking",
302
+ help="Enable thinking mode. Default: default thinking mode set in config file.",
303
+ ),
304
+ ] = None,
305
+ no_telemetry: Annotated[
306
+ bool,
307
+ typer.Option(
308
+ "--no-telemetry",
309
+ help=(
310
+ "Disable all anonymous usage telemetry and error reporting "
311
+ "(equivalent to setting PYTHINKER_DISABLE_TELEMETRY=1). "
312
+ "Default: telemetry on."
313
+ ),
314
+ ),
315
+ ] = False,
316
+ # Run mode
317
+ yolo: Annotated[
318
+ bool,
319
+ typer.Option(
320
+ "--yolo",
321
+ "--yes",
322
+ "-y",
323
+ "--auto-approve",
324
+ help="Automatically approve all actions. Default: no.",
325
+ ),
326
+ ] = False,
327
+ plan: Annotated[
328
+ bool,
329
+ typer.Option(
330
+ "--plan",
331
+ help="Start in plan mode. Default: no.",
332
+ ),
333
+ ] = False,
334
+ auto: Annotated[
335
+ bool,
336
+ typer.Option(
337
+ "--auto",
338
+ help=(
339
+ "Run in auto mode: no user is present, AskUserQuestion is auto-dismissed, "
340
+ "and tool calls are auto-approved. Use when running unattended "
341
+ "(scripts, CI, scheduled jobs). Default: no."
342
+ ),
343
+ ),
344
+ ] = False,
345
+ prompt: Annotated[
346
+ str | None,
347
+ typer.Option(
348
+ "--prompt",
349
+ "-p",
350
+ "--command",
351
+ "-c",
352
+ help="User prompt to the agent. Default: prompt interactively.",
353
+ ),
354
+ ] = None,
355
+ print_mode: Annotated[
356
+ bool,
357
+ typer.Option(
358
+ "--print",
359
+ help=(
360
+ "Run in print mode (non-interactive). Print mode auto-dismisses "
361
+ "AskUserQuestion and auto-approves tool calls for this invocation."
362
+ ),
363
+ ),
364
+ ] = False,
365
+ acp_mode: Annotated[
366
+ bool,
367
+ typer.Option(
368
+ "--acp",
369
+ help="(Deprecated, use `pythinker acp` instead) Run as ACP server.",
370
+ ),
371
+ ] = False,
372
+ wire_mode: Annotated[
373
+ bool,
374
+ typer.Option(
375
+ "--wire",
376
+ help="Run as Wire server (experimental).",
377
+ ),
378
+ ] = False,
379
+ input_format: Annotated[
380
+ InputFormat | None,
381
+ typer.Option(
382
+ "--input-format",
383
+ help=(
384
+ "Input format to use. Must be used with `--print` "
385
+ "and the input must be piped in via stdin. "
386
+ "Default: text."
387
+ ),
388
+ ),
389
+ ] = None,
390
+ output_format: Annotated[
391
+ OutputFormat | None,
392
+ typer.Option(
393
+ "--output-format",
394
+ help="Output format to use. Must be used with `--print`. Default: text.",
395
+ ),
396
+ ] = None,
397
+ final_message_only: Annotated[
398
+ bool,
399
+ typer.Option(
400
+ "--final-message-only",
401
+ help="Only print the final assistant message (print UI).",
402
+ ),
403
+ ] = False,
404
+ quiet: Annotated[
405
+ bool,
406
+ typer.Option(
407
+ "--quiet",
408
+ help="Alias for `--print --output-format text --final-message-only`.",
409
+ ),
410
+ ] = False,
411
+ # Customization
412
+ agent: Annotated[
413
+ Literal["default", "okabe"] | None,
414
+ typer.Option(
415
+ "--agent",
416
+ help="Builtin agent specification to use. Default: builtin default agent.",
417
+ ),
418
+ ] = None,
419
+ agent_file: Annotated[
420
+ Path | None,
421
+ typer.Option(
422
+ "--agent-file",
423
+ exists=True,
424
+ file_okay=True,
425
+ dir_okay=False,
426
+ readable=True,
427
+ help="Custom agent specification file. Default: builtin default agent.",
428
+ ),
429
+ ] = None,
430
+ mcp_config_file: Annotated[
431
+ list[Path] | None,
432
+ typer.Option(
433
+ "--mcp-config-file",
434
+ exists=True,
435
+ file_okay=True,
436
+ dir_okay=False,
437
+ readable=True,
438
+ help=(
439
+ "MCP config file to load. Add this option multiple times to specify multiple MCP "
440
+ "configs. Default: none."
441
+ ),
442
+ ),
443
+ ] = None,
444
+ mcp_config: Annotated[
445
+ list[str] | None,
446
+ typer.Option(
447
+ "--mcp-config",
448
+ help=(
449
+ "MCP config JSON to load. Add this option multiple times to specify multiple MCP "
450
+ "configs. Default: none."
451
+ ),
452
+ ),
453
+ ] = None,
454
+ local_skills_dir: Annotated[
455
+ list[Path] | None,
456
+ typer.Option(
457
+ "--skills-dir",
458
+ exists=True,
459
+ file_okay=False,
460
+ dir_okay=True,
461
+ readable=True,
462
+ help="Custom skills directories (repeatable). Overrides default discovery.",
463
+ ),
464
+ ] = None,
465
+ # Loop control
466
+ max_steps_per_turn: Annotated[
467
+ int | None,
468
+ typer.Option(
469
+ "--max-steps-per-turn",
470
+ min=1,
471
+ help="Maximum number of steps in one turn. Default: from config.",
472
+ ),
473
+ ] = None,
474
+ max_retries_per_step: Annotated[
475
+ int | None,
476
+ typer.Option(
477
+ "--max-retries-per-step",
478
+ min=1,
479
+ help="Maximum number of retries in one step. Default: from config.",
480
+ ),
481
+ ] = None,
482
+ max_ralph_iterations: Annotated[
483
+ int | None,
484
+ typer.Option(
485
+ "--max-ralph-iterations",
486
+ min=-1,
487
+ help=(
488
+ "Extra iterations after the first turn in Ralph mode. Use -1 for unlimited. "
489
+ "Default: from config."
490
+ ),
491
+ ),
492
+ ] = None,
493
+ ):
494
+ """Pythinker, your next CLI agent."""
495
+ import asyncio
496
+ import contextlib
497
+ import json
498
+
499
+ from pythinker_code.utils.proctitle import init_process_name
500
+
501
+ init_process_name("Pythinker")
502
+
503
+ if ctx.invoked_subcommand is not None:
504
+ return # skip rest if a subcommand is invoked
505
+
506
+ del version # handled in the callback
507
+
508
+ from pythinker_host.path import HostPath
509
+
510
+ from pythinker_code.agentspec import DEFAULT_AGENT_FILE, OKABE_AGENT_FILE
511
+ from pythinker_code.app import PythinkerCLI, enable_logging
512
+ from pythinker_code.config import Config, load_config_from_string
513
+ from pythinker_code.exception import ConfigError
514
+ from pythinker_code.hooks import events as hook_events
515
+ from pythinker_code.metadata import load_metadata, save_metadata
516
+ from pythinker_code.session import Session
517
+ from pythinker_code.ui.shell.startup import ShellStartupProgress
518
+ from pythinker_code.utils.logging import logger, open_original_stderr, redirect_stderr_to_logger
519
+
520
+ from .mcp import get_global_mcp_config_file
521
+
522
+ # Don't redirect stderr during argument parsing. Our stderr redirector
523
+ # replaces fd=2 with a pipe, which would swallow Click/Typer startup errors.
524
+ # Redirection is installed later, right before PythinkerCLI.create(), so that
525
+ # MCP server stderr noise is captured into logs from the start.
526
+ enable_logging(debug, redirect_stderr=False)
527
+
528
+ def _emit_fatal_error(message: str) -> None:
529
+ # Prefer writing to the original stderr fd even if we later redirect fd=2.
530
+ # This ensures fatal errors are visible to the user.
531
+ with open_original_stderr() as stream:
532
+ if stream is not None:
533
+ stream.write((message.rstrip() + "\n").encode("utf-8", errors="replace"))
534
+ stream.flush()
535
+ return
536
+ typer.echo(message, err=True)
537
+
538
+ # session_id states:
539
+ # None → not provided (new session)
540
+ # "" → --session/--resume without value (picker mode)
541
+ # "ID" → --session ID (resume specific session)
542
+ _picker_mode = session_id == ""
543
+ if session_id is not None:
544
+ session_id = session_id.strip() or None # treat whitespace-only as picker mode
545
+ if session_id is None:
546
+ _picker_mode = True
547
+
548
+ if quiet:
549
+ if acp_mode or wire_mode:
550
+ raise typer.BadParameter(
551
+ "Quiet mode cannot be combined with ACP or Wire UI",
552
+ param_hint="--quiet",
553
+ )
554
+ if output_format not in (None, "text"):
555
+ raise typer.BadParameter(
556
+ "Quiet mode implies `--output-format text`",
557
+ param_hint="--quiet",
558
+ )
559
+ print_mode = True
560
+ output_format = "text"
561
+ final_message_only = True
562
+
563
+ conflict_option_sets = [
564
+ {
565
+ "--print": print_mode,
566
+ "--acp": acp_mode,
567
+ "--wire": wire_mode,
568
+ },
569
+ {
570
+ "--agent": agent is not None,
571
+ "--agent-file": agent_file is not None,
572
+ },
573
+ {
574
+ "--continue": continue_,
575
+ "--session": session_id is not None or _picker_mode,
576
+ },
577
+ {
578
+ "--config": config_string is not None,
579
+ "--config-file": config_file is not None,
580
+ },
581
+ ]
582
+ for option_set in conflict_option_sets:
583
+ active_options = [flag for flag, active in option_set.items() if active]
584
+ if len(active_options) > 1:
585
+ raise typer.BadParameter(
586
+ f"Cannot combine {', '.join(active_options)}.",
587
+ param_hint=active_options[0],
588
+ )
589
+
590
+ if agent is not None:
591
+ match agent:
592
+ case "default":
593
+ agent_file = DEFAULT_AGENT_FILE
594
+ case "okabe":
595
+ agent_file = OKABE_AGENT_FILE
596
+
597
+ ui: UIMode = "shell"
598
+ if print_mode:
599
+ ui = "print"
600
+ elif acp_mode:
601
+ ui = "acp"
602
+ elif wire_mode:
603
+ ui = "wire"
604
+
605
+ if prompt is not None:
606
+ prompt = prompt.strip()
607
+ if not prompt:
608
+ raise typer.BadParameter("Prompt cannot be empty", param_hint="--prompt")
609
+
610
+ if input_format is not None and ui != "print":
611
+ raise typer.BadParameter(
612
+ "Input format is only supported for print UI",
613
+ param_hint="--input-format",
614
+ )
615
+ if output_format is not None and ui != "print":
616
+ raise typer.BadParameter(
617
+ "Output format is only supported for print UI",
618
+ param_hint="--output-format",
619
+ )
620
+ if final_message_only and ui != "print":
621
+ raise typer.BadParameter(
622
+ "Final-message-only output is only supported for print UI",
623
+ param_hint="--final-message-only",
624
+ )
625
+ if _picker_mode and ui != "shell":
626
+ raise typer.BadParameter(
627
+ "--session without a session ID is only supported for shell UI",
628
+ param_hint="--session",
629
+ )
630
+
631
+ config: Config | Path | None = None
632
+ if config_string is not None:
633
+ config_string = config_string.strip()
634
+ if not config_string:
635
+ raise typer.BadParameter("Config cannot be empty", param_hint="--config")
636
+ try:
637
+ config = load_config_from_string(config_string)
638
+ except ConfigError as e:
639
+ raise typer.BadParameter(str(e), param_hint="--config") from e
640
+ elif config_file is not None:
641
+ config = config_file
642
+
643
+ file_configs = list(mcp_config_file or [])
644
+ raw_mcp_config = list(mcp_config or [])
645
+
646
+ # Use default MCP config file if no MCP config is provided
647
+ if not file_configs:
648
+ default_mcp_file = get_global_mcp_config_file()
649
+ if default_mcp_file.exists():
650
+ file_configs.append(default_mcp_file)
651
+
652
+ try:
653
+ mcp_configs = [json.loads(conf.read_text(encoding="utf-8")) for conf in file_configs]
654
+ except json.JSONDecodeError as e:
655
+ raise typer.BadParameter(f"Invalid JSON: {e}", param_hint="--mcp-config-file") from e
656
+
657
+ try:
658
+ mcp_configs += [json.loads(conf) for conf in raw_mcp_config]
659
+ except json.JSONDecodeError as e:
660
+ raise typer.BadParameter(f"Invalid JSON: {e}", param_hint="--mcp-config") from e
661
+
662
+ # Honor --no-telemetry by exporting the env var before any subsystem (Sentry,
663
+ # OTel, sink) reads it during PythinkerCLI.create.
664
+ if no_telemetry:
665
+ os.environ["PYTHINKER_DISABLE_TELEMETRY"] = "1"
666
+
667
+ skills_dirs: list[HostPath] | None = None
668
+ if local_skills_dir:
669
+ skills_dirs = [HostPath.unsafe_from_local_path(p) for p in local_skills_dir]
670
+
671
+ work_dir = HostPath.unsafe_from_local_path(local_work_dir) if local_work_dir else HostPath.cwd()
672
+
673
+ # Tracks the most recently created/loaded session so that _reload_loop's
674
+ # exception handler can clean it up even when _run() fails before returning.
675
+ _latest_created_session: Session | None = None
676
+
677
+ async def _run(session_id: str | None, prefill_text: str | None = None) -> tuple[Session, int]:
678
+ """
679
+ Create/load session and run the CLI instance.
680
+
681
+ Returns:
682
+ The session and the exit code (0 = success, 1 = failure, 75 = retryable).
683
+ """
684
+ startup_progress = ShellStartupProgress(enabled=ui == "shell")
685
+ try:
686
+ startup_progress.update("Preparing session...")
687
+
688
+ # Track if we're resuming an existing session (vs creating new)
689
+ resumed = False
690
+
691
+ if session_id is not None:
692
+ session = await Session.find(work_dir, session_id)
693
+ if session is None:
694
+ logger.info(
695
+ "Session {session_id} not found, creating new session",
696
+ session_id=session_id,
697
+ )
698
+ session = await Session.create(work_dir, session_id)
699
+ else:
700
+ # Only count as "resumed" if the session has actual turns.
701
+ # Sessions created by /new, /undo (turn 0), /fork via Reload
702
+ # may have a custom_title but no wire content — treat as startup.
703
+ resumed = not session.wire_file.is_empty()
704
+ logger.info("Resuming session: {session_id}", session_id=session.id)
705
+ elif continue_:
706
+ session = await Session.continue_(work_dir)
707
+ if session is None:
708
+ raise typer.BadParameter(
709
+ "No previous session found for the working directory",
710
+ param_hint="--continue",
711
+ )
712
+ resumed = True # Continuing previous session
713
+ logger.info("Continuing previous session: {session_id}", session_id=session.id)
714
+ else:
715
+ session = await Session.create(work_dir)
716
+ logger.info("Created new session: {session_id}", session_id=session.id)
717
+
718
+ nonlocal _latest_created_session
719
+ _latest_created_session = session
720
+
721
+ # Add CLI-provided additional directories to session state
722
+ if local_add_dirs:
723
+ from pythinker_code.utils.path import is_within_directory
724
+
725
+ canonical_work_dir = work_dir.canonical()
726
+ changed = False
727
+ for d in local_add_dirs:
728
+ dir_path = HostPath.unsafe_from_local_path(d).canonical()
729
+ dir_str = str(dir_path)
730
+ # Skip dirs within work_dir (already accessible)
731
+ if is_within_directory(dir_path, canonical_work_dir):
732
+ logger.info(
733
+ "Skipping --add-dir {dir}: already within working directory",
734
+ dir=dir_str,
735
+ )
736
+ continue
737
+ if dir_str not in session.state.additional_dirs:
738
+ session.state.additional_dirs.append(dir_str)
739
+ changed = True
740
+ if changed:
741
+ session.save_state()
742
+
743
+ # Redirect stderr *before* PythinkerCLI.create() so that MCP server
744
+ # subprocesses (e.g. mcp-remote OAuth debug logs) write to the log
745
+ # file instead of polluting the user's terminal. CLI argument
746
+ # parsing has already succeeded at this point, so Typer/Click
747
+ # startup errors are no longer a concern. Fatal errors from
748
+ # create() are still visible because _emit_fatal_error() writes to
749
+ # the saved original stderr fd.
750
+ redirect_stderr_to_logger()
751
+
752
+ instance = await PythinkerCLI.create(
753
+ session,
754
+ config=config,
755
+ model_name=model_name,
756
+ thinking=thinking,
757
+ yolo=yolo,
758
+ auto=auto,
759
+ runtime_auto=ui == "print",
760
+ plan_mode=plan,
761
+ resumed=resumed,
762
+ agent_file=agent_file,
763
+ mcp_configs=mcp_configs,
764
+ skills_dirs=skills_dirs,
765
+ max_steps_per_turn=max_steps_per_turn,
766
+ max_retries_per_step=max_retries_per_step,
767
+ max_ralph_iterations=max_ralph_iterations,
768
+ startup_progress=startup_progress.update if ui == "shell" else None,
769
+ defer_mcp_loading=ui == "shell" and prompt is None,
770
+ ui_mode=ui,
771
+ )
772
+ startup_progress.stop()
773
+
774
+ # --- SessionStart hook ---
775
+ _session_source = "resume" if resumed else "startup"
776
+ await instance.soul.hook_engine.trigger(
777
+ "SessionStart",
778
+ matcher_value=_session_source,
779
+ input_data=hook_events.session_start(
780
+ session_id=session.id,
781
+ cwd=str(work_dir),
782
+ source=_session_source,
783
+ ),
784
+ )
785
+
786
+ # Install stderr redirection only after initialization succeeded, so runtime
787
+ # stderr noise is captured into logs without hiding startup failures.
788
+ redirect_stderr_to_logger()
789
+ preserve_background_tasks = False
790
+ try:
791
+ match ui:
792
+ case "shell":
793
+ shell_ok = await instance.run_shell(prompt, prefill_text=prefill_text)
794
+ exit_code = ExitCode.SUCCESS if shell_ok else ExitCode.FAILURE
795
+ case "print":
796
+ exit_code = await instance.run_print(
797
+ input_format or "text",
798
+ output_format or "text",
799
+ prompt,
800
+ final_only=final_message_only,
801
+ )
802
+ case "acp":
803
+ if prompt is not None:
804
+ logger.warning("ACP server ignores prompt argument")
805
+ await instance.run_acp()
806
+ exit_code = ExitCode.SUCCESS
807
+ case "wire":
808
+ if prompt is not None:
809
+ logger.warning("Wire server ignores prompt argument")
810
+ await instance.run_wire_stdio()
811
+ exit_code = ExitCode.SUCCESS
812
+ except Reload as e:
813
+ preserve_background_tasks = True
814
+ if e.session_id is None:
815
+ r = Reload(session_id=session.id, prefill_text=e.prefill_text)
816
+ r.source_session = session
817
+ raise r from e
818
+ e.source_session = session
819
+ raise
820
+ except SwitchToWeb:
821
+ preserve_background_tasks = True
822
+ raise
823
+ except SwitchToVis:
824
+ preserve_background_tasks = True
825
+ raise
826
+ finally:
827
+ # --- SessionEnd hook ---
828
+ with contextlib.suppress(Exception):
829
+ await asyncio.wait_for(
830
+ instance.soul.hook_engine.trigger(
831
+ "SessionEnd",
832
+ matcher_value="exit",
833
+ input_data=hook_events.session_end(
834
+ session_id=session.id,
835
+ cwd=str(work_dir),
836
+ reason="exit",
837
+ ),
838
+ ),
839
+ timeout=5,
840
+ )
841
+
842
+ if not preserve_background_tasks:
843
+ await instance.shutdown_background_tasks()
844
+ await instance.await_bg_tasks_shutdown()
845
+
846
+ return session, exit_code
847
+ finally:
848
+ startup_progress.stop()
849
+
850
+ async def _delete_empty_session(session: Session) -> None:
851
+ """Delete an empty session directory and clear last_session_id if it pointed to it."""
852
+ logger.info(
853
+ "Session {session_id} has empty context, removing it",
854
+ session_id=session.id,
855
+ )
856
+ await session.delete()
857
+ meta = load_metadata()
858
+ wdm = meta.get_work_dir_meta(session.work_dir)
859
+ if wdm is not None and wdm.last_session_id == session.id:
860
+ wdm.last_session_id = None
861
+ save_metadata(meta)
862
+
863
+ def _print_resume_hint(session: Session) -> None:
864
+ """Print a hint for resuming the session after exit."""
865
+ if not session.is_empty():
866
+ _emit_fatal_error(f"\nTo resume this session: pythinker -r {session.id}")
867
+
868
+ async def _post_run(last_session: Session, exit_code: int) -> None:
869
+ _print_resume_hint(last_session)
870
+ if last_session.is_empty():
871
+ # Always clean up empty sessions regardless of exit code
872
+ await _delete_empty_session(last_session)
873
+ elif exit_code == ExitCode.SUCCESS:
874
+ metadata = load_metadata()
875
+ work_dir_meta = metadata.get_work_dir_meta(last_session.work_dir)
876
+ if work_dir_meta is None:
877
+ logger.warning(
878
+ "Work dir metadata missing when marking last session, recreating: {work_dir}",
879
+ work_dir=last_session.work_dir,
880
+ )
881
+ work_dir_meta = metadata.new_work_dir_meta(last_session.work_dir)
882
+ work_dir_meta.last_session_id = last_session.id
883
+ save_metadata(metadata)
884
+
885
+ async def _reload_loop(session_id: str | None) -> tuple[str | None, int]:
886
+ """Run the main loop, handling Reload/SwitchToWeb/SwitchToVis.
887
+
888
+ Returns:
889
+ (switch_target, exit_code) where switch_target is "web", "vis",
890
+ or None if the session ended normally.
891
+ """
892
+ last_session: Session | None = None
893
+ prefill_text: str | None = None
894
+ try:
895
+ while True:
896
+ try:
897
+ last_session, exit_code = await _run(session_id, prefill_text=prefill_text)
898
+ break
899
+ except Reload as e:
900
+ # Clean up old empty session when switching to a different session
901
+ old = e.source_session
902
+ if old is not None and old.id != e.session_id and old.is_empty():
903
+ await _delete_empty_session(old)
904
+ last_session = None
905
+ else:
906
+ last_session = e.source_session
907
+ # Only print resume hint when switching to a different session
908
+ # (not for same-session reloads like /model, /theme, /reload)
909
+ if old is not None and e.session_id is not None and old.id != e.session_id:
910
+ _print_resume_hint(old)
911
+ session_id = e.session_id
912
+ prefill_text = e.prefill_text
913
+ continue
914
+ except SwitchToWeb as e:
915
+ if e.session_id is not None:
916
+ session = await Session.find(work_dir, e.session_id)
917
+ if session is not None:
918
+ await _post_run(session, ExitCode.SUCCESS)
919
+ return "web", ExitCode.SUCCESS
920
+ except SwitchToVis as e:
921
+ if e.session_id is not None:
922
+ session = await Session.find(work_dir, e.session_id)
923
+ if session is not None:
924
+ await _post_run(session, ExitCode.SUCCESS)
925
+ return "vis", ExitCode.SUCCESS
926
+ assert last_session is not None
927
+ await _post_run(last_session, exit_code)
928
+ return None, exit_code
929
+ except (SwitchToWeb, SwitchToVis):
930
+ # Currently handled inside the loop (return), but re-raise explicitly
931
+ # so the generic except below never treats them as unexpected errors.
932
+ raise
933
+ except Exception:
934
+ # Best-effort cleanup: _latest_created_session is the session from
935
+ # the most recent _run() call, which may have failed before returning.
936
+ # last_session is from a *previous* iteration and must not be touched.
937
+ if _latest_created_session is not None:
938
+ _print_resume_hint(_latest_created_session)
939
+ if _latest_created_session.is_empty():
940
+ with contextlib.suppress(Exception):
941
+ await _delete_empty_session(_latest_created_session)
942
+ raise
943
+
944
+ if _picker_mode:
945
+ from prompt_toolkit.shortcuts.choice_input import ChoiceInput
946
+ from rich.console import Console
947
+
948
+ from pythinker_code.utils.datetime import format_relative_time
949
+
950
+ async def _pick_session() -> str:
951
+ all_sessions = await Session.list(work_dir)
952
+ if not all_sessions:
953
+ Console().print("[yellow]No sessions found for the working directory.[/yellow]")
954
+ raise typer.Exit(0)
955
+
956
+ choices: list[tuple[str, str]] = []
957
+ for s in all_sessions:
958
+ time_str = format_relative_time(s.updated_at)
959
+ short_id = s.id[:8]
960
+ name = _strip_session_id_suffix(s.title, s.id)
961
+ label = f"{name} ({short_id}), {time_str}"
962
+ choices.append((s.id, label))
963
+
964
+ try:
965
+ selection = await ChoiceInput(
966
+ message="Select a session to resume"
967
+ " (↑↓ navigate, Enter select, Ctrl+C cancel):",
968
+ options=choices,
969
+ default=choices[0][0],
970
+ ).prompt_async()
971
+ except (EOFError, KeyboardInterrupt):
972
+ raise typer.Exit(0) from None
973
+
974
+ if not selection:
975
+ raise typer.Exit(0)
976
+
977
+ return selection
978
+
979
+ session_id = asyncio.run(_pick_session())
980
+
981
+ try:
982
+ switch_target, exit_code = asyncio.run(_reload_loop(session_id))
983
+ except (typer.BadParameter, typer.Exit):
984
+ # Let Typer/Click format these errors (rich panel + correct exit code).
985
+ raise
986
+ except Exception as exc:
987
+ import click
988
+
989
+ if isinstance(exc, click.ClickException):
990
+ # ClickException includes the errors Typer knows how to render; don't
991
+ # wrap them, or we'd lose the standard error UI and exit codes.
992
+ raise
993
+ logger.exception("Fatal error when running CLI")
994
+ if debug:
995
+ import traceback
996
+
997
+ # In debug mode, show full traceback for quick diagnosis.
998
+ _emit_fatal_error(traceback.format_exc())
999
+ else:
1000
+ from pythinker_code.share import get_share_dir
1001
+
1002
+ log_path = get_share_dir() / "logs" / "pythinker.log"
1003
+ # In non-debug mode, print a concise error and point users to logs.
1004
+ _emit_fatal_error(
1005
+ f"{exc}\n"
1006
+ f"See logs: {log_path}\n"
1007
+ "Run with --debug for full traceback, or run pythinker export to share diagnostics."
1008
+ )
1009
+ raise typer.Exit(code=1) from exc
1010
+ if switch_target in ("web", "vis"):
1011
+ from pythinker_code.utils.logging import restore_stderr
1012
+
1013
+ restore_stderr()
1014
+
1015
+ # Restore default SIGINT handler and terminal state after the shell's
1016
+ # asyncio.run() to ensure Ctrl+C works in the uvicorn web server.
1017
+ import signal
1018
+
1019
+ signal.signal(signal.SIGINT, signal.default_int_handler)
1020
+
1021
+ from pythinker_code.utils.term import ensure_tty_sane
1022
+
1023
+ ensure_tty_sane()
1024
+
1025
+ if switch_target == "web":
1026
+ from pythinker_code.web.app import run_web_server
1027
+
1028
+ run_web_server(open_browser=True)
1029
+ else:
1030
+ from pythinker_code.vis.app import run_vis_server
1031
+
1032
+ run_vis_server(open_browser=True)
1033
+ elif exit_code != ExitCode.SUCCESS:
1034
+ raise typer.Exit(code=exit_code)
1035
+
1036
+
1037
+ @cli.command()
1038
+ def login(
1039
+ json: bool = typer.Option(
1040
+ False,
1041
+ "--json",
1042
+ help="Emit OAuth events as JSON lines.",
1043
+ ),
1044
+ browser: bool = typer.Option(False, "--browser", help="Use OpenAI ChatGPT browser login."),
1045
+ headless: bool = typer.Option(
1046
+ False, "--headless", help="Use OpenAI ChatGPT device-code login."
1047
+ ),
1048
+ api_key: bool = typer.Option(False, "--api-key", help="Configure OpenAI with an API key."),
1049
+ opencode_go: bool = typer.Option(
1050
+ False, "--opencode-go", help="Configure OpenCode Go with an API key."
1051
+ ),
1052
+ minimax: bool = typer.Option(False, "--minimax", help="Configure MiniMax with an API key."),
1053
+ deepseek: bool = typer.Option(False, "--deepseek", help="Configure DeepSeek with an API key."),
1054
+ anthropic: bool = typer.Option(
1055
+ False, "--anthropic", help="Configure Anthropic with an API key."
1056
+ ),
1057
+ openrouter: bool = typer.Option(
1058
+ False, "--openrouter", help="Configure OpenRouter with an API key."
1059
+ ),
1060
+ lm_studio: bool = typer.Option(
1061
+ False, "--lm-studio", "--lmstudio", help="Configure LM Studio as a local provider."
1062
+ ),
1063
+ ollama: bool = typer.Option(False, "--ollama", help="Configure Ollama as a local provider."),
1064
+ base_url: str = typer.Option(
1065
+ "",
1066
+ "--base-url",
1067
+ help="Override the default base URL for --lm-studio or --ollama.",
1068
+ ),
1069
+ ) -> None:
1070
+ """Login with OpenAI, OpenCode Go, MiniMax, DeepSeek, Anthropic, or local providers."""
1071
+ import asyncio
1072
+
1073
+ from rich.console import Console
1074
+ from rich.status import Status
1075
+
1076
+ async def _run() -> bool:
1077
+ selected_modes = sum(
1078
+ bool(value)
1079
+ for value in (
1080
+ browser,
1081
+ headless,
1082
+ api_key,
1083
+ opencode_go,
1084
+ minimax,
1085
+ deepseek,
1086
+ anthropic,
1087
+ openrouter,
1088
+ lm_studio,
1089
+ ollama,
1090
+ )
1091
+ )
1092
+ if selected_modes > 1:
1093
+ typer.echo(
1094
+ "Choose only one of --browser, --headless, --api-key, "
1095
+ "--opencode-go, --minimax, --deepseek, --anthropic, --openrouter, "
1096
+ "--lm-studio, or --ollama.",
1097
+ err=True,
1098
+ )
1099
+ return False
1100
+
1101
+ config = load_config()
1102
+ if openrouter:
1103
+ key = typer.prompt("OpenRouter API key", hide_input=True).strip()
1104
+ events = login_openrouter_api_key(config, key)
1105
+ elif anthropic:
1106
+ key = typer.prompt("Anthropic API key", hide_input=True).strip()
1107
+ events = login_anthropic_api_key(config, key)
1108
+ elif deepseek:
1109
+ key = typer.prompt("DeepSeek API key", hide_input=True).strip()
1110
+ events = login_deepseek_api_key(config, key)
1111
+ elif minimax:
1112
+ key = typer.prompt("MiniMax API key", hide_input=True).strip()
1113
+ events = login_minimax_api_key(config, key)
1114
+ elif opencode_go:
1115
+ key = typer.prompt("OpenCode Go API key", hide_input=True).strip()
1116
+ events = login_opencode_go_api_key(config, key)
1117
+ elif api_key:
1118
+ key = typer.prompt("OpenAI API key", hide_input=True).strip()
1119
+ events = login_openai_api_key(config, key)
1120
+ elif lm_studio:
1121
+ events = login_lm_studio(
1122
+ config,
1123
+ base_url=(base_url.strip() or None),
1124
+ )
1125
+ elif ollama:
1126
+ events = login_ollama(
1127
+ config,
1128
+ base_url=(base_url.strip() or None),
1129
+ )
1130
+ elif headless:
1131
+ events = login_openai_headless(config)
1132
+ else:
1133
+ events = login_openai_browser(config, open_browser=True)
1134
+
1135
+ if json:
1136
+ ok = True
1137
+ async for event in events:
1138
+ typer.echo(event.json)
1139
+ if event.type == "error":
1140
+ ok = False
1141
+ return ok
1142
+
1143
+ console = Console()
1144
+ ok = True
1145
+ status: Status | None = None
1146
+ try:
1147
+ async for event in events:
1148
+ if event.type == "waiting":
1149
+ if status is None:
1150
+ status = console.status("Waiting for OpenAI authorization.")
1151
+ status.start()
1152
+ continue
1153
+ if status is not None:
1154
+ status.stop()
1155
+ status = None
1156
+ match event.type:
1157
+ case "error":
1158
+ style = "red"
1159
+ case "success":
1160
+ style = "green"
1161
+ case _:
1162
+ style = None
1163
+ console.print(event.message, markup=False, style=style)
1164
+ if event.type == "error":
1165
+ ok = False
1166
+ finally:
1167
+ if status is not None:
1168
+ status.stop()
1169
+ return ok
1170
+
1171
+ ok = asyncio.run(_run())
1172
+ if not ok:
1173
+ raise typer.Exit(code=1)
1174
+
1175
+
1176
+ @cli.command()
1177
+ def logout(
1178
+ json: bool = typer.Option(
1179
+ False,
1180
+ "--json",
1181
+ help="Emit OAuth events as JSON lines.",
1182
+ ),
1183
+ opencode_go: bool = typer.Option(False, "--opencode-go", help="Logout from OpenCode Go."),
1184
+ minimax: bool = typer.Option(False, "--minimax", help="Logout from MiniMax."),
1185
+ deepseek: bool = typer.Option(False, "--deepseek", help="Logout from DeepSeek."),
1186
+ anthropic: bool = typer.Option(False, "--anthropic", help="Logout from Anthropic."),
1187
+ openrouter: bool = typer.Option(False, "--openrouter", help="Logout from OpenRouter."),
1188
+ lm_studio: bool = typer.Option(
1189
+ False, "--lm-studio", "--lmstudio", help="Logout from LM Studio."
1190
+ ),
1191
+ ollama: bool = typer.Option(False, "--ollama", help="Logout from Ollama."),
1192
+ ) -> None:
1193
+ """Logout from OpenAI, OpenCode Go, MiniMax, DeepSeek, Anthropic, or local providers."""
1194
+ import asyncio
1195
+
1196
+ from rich.console import Console
1197
+
1198
+ async def _run() -> bool:
1199
+ ok = True
1200
+ selected_modes = (opencode_go, minimax, deepseek, anthropic, openrouter, lm_studio, ollama)
1201
+ if sum(bool(v) for v in selected_modes) > 1:
1202
+ typer.echo(
1203
+ "Choose only one of --opencode-go, --minimax, --deepseek, "
1204
+ "--anthropic, --openrouter, --lm-studio, or --ollama.",
1205
+ err=True,
1206
+ )
1207
+ return False
1208
+
1209
+ config = load_config()
1210
+ if openrouter:
1211
+ events = logout_openrouter(config)
1212
+ elif anthropic:
1213
+ events = logout_anthropic(config)
1214
+ elif deepseek:
1215
+ events = logout_deepseek(config)
1216
+ elif minimax:
1217
+ events = logout_minimax(config)
1218
+ elif opencode_go:
1219
+ events = logout_opencode_go(config)
1220
+ elif lm_studio:
1221
+ events = logout_lm_studio(config)
1222
+ elif ollama:
1223
+ events = logout_ollama(config)
1224
+ else:
1225
+ events = logout_openai(config)
1226
+ if json:
1227
+ async for event in events:
1228
+ typer.echo(event.json)
1229
+ if event.type == "error":
1230
+ ok = False
1231
+ return ok
1232
+
1233
+ console = Console()
1234
+ async for event in events:
1235
+ match event.type:
1236
+ case "error":
1237
+ style = "red"
1238
+ case "success":
1239
+ style = "green"
1240
+ case _:
1241
+ style = None
1242
+ console.print(event.message, markup=False, style=style)
1243
+ if event.type == "error":
1244
+ ok = False
1245
+ return ok
1246
+
1247
+ ok = asyncio.run(_run())
1248
+ if not ok:
1249
+ raise typer.Exit(code=1)
1250
+
1251
+
1252
+ @cli.command(context_settings={"allow_extra_args": True, "ignore_unknown_options": True})
1253
+ def term(
1254
+ ctx: typer.Context,
1255
+ ) -> None:
1256
+ """Run Toad TUI backed by Pythinker CLI ACP server."""
1257
+ from .toad import run_term
1258
+
1259
+ run_term(ctx)
1260
+
1261
+
1262
+ @cli.command()
1263
+ def acp():
1264
+ """Run Pythinker CLI ACP server."""
1265
+ from pythinker_code.acp import acp_main
1266
+
1267
+ acp_main()
1268
+
1269
+
1270
+ @cli.command(name="__background-task-worker", hidden=True)
1271
+ def background_task_worker(
1272
+ task_dir: Annotated[Path, typer.Option("--task-dir")],
1273
+ heartbeat_interval_ms: Annotated[int, typer.Option("--heartbeat-interval-ms")] = 5000,
1274
+ control_poll_interval_ms: Annotated[int, typer.Option("--control-poll-interval-ms")] = 500,
1275
+ kill_grace_period_ms: Annotated[int, typer.Option("--kill-grace-period-ms")] = 2000,
1276
+ ) -> None:
1277
+ """Run background task worker subprocess (internal)."""
1278
+ import asyncio
1279
+
1280
+ from pythinker_code.background import run_background_task_worker
1281
+ from pythinker_code.utils.proctitle import set_process_title
1282
+
1283
+ set_process_title("pythinker-code-bg-worker")
1284
+
1285
+ from pythinker_code.app import enable_logging
1286
+
1287
+ enable_logging(debug=False)
1288
+ asyncio.run(
1289
+ run_background_task_worker(
1290
+ task_dir,
1291
+ heartbeat_interval_ms=heartbeat_interval_ms,
1292
+ control_poll_interval_ms=control_poll_interval_ms,
1293
+ kill_grace_period_ms=kill_grace_period_ms,
1294
+ )
1295
+ )
1296
+
1297
+
1298
+ @cli.command(name="__web-worker", hidden=True)
1299
+ def web_worker(session_id: str) -> None:
1300
+ """Run web worker subprocess (internal)."""
1301
+ import asyncio
1302
+ from uuid import UUID
1303
+
1304
+ from pythinker_code.utils.proctitle import set_process_title
1305
+
1306
+ set_process_title("pythinker-code-worker")
1307
+
1308
+ from pythinker_code.app import enable_logging
1309
+ from pythinker_code.web.runner.worker import run_worker
1310
+
1311
+ try:
1312
+ parsed_session_id = UUID(session_id)
1313
+ except ValueError as exc:
1314
+ raise typer.BadParameter(f"Invalid session ID: {session_id}") from exc
1315
+
1316
+ enable_logging(debug=False)
1317
+ asyncio.run(run_worker(parsed_session_id))
1318
+
1319
+
1320
+ if __name__ == "__main__":
1321
+ import sys
1322
+
1323
+ if "pythinker_code.cli" not in sys.modules:
1324
+ sys.modules["pythinker_code.cli"] = sys.modules[__name__]
1325
+
1326
+ sys.exit(cli())