pythinker-code 0.8.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (790) hide show
  1. pythinker_code/CHANGELOG.md +60 -0
  2. pythinker_code/__init__.py +0 -0
  3. pythinker_code/__main__.py +97 -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 +301 -0
  8. pythinker_code/acp/mcp.py +46 -0
  9. pythinker_code/acp/server.py +497 -0
  10. pythinker_code/acp/session.py +502 -0
  11. pythinker_code/acp/tools.py +174 -0
  12. pythinker_code/acp/types.py +13 -0
  13. pythinker_code/acp/version.py +45 -0
  14. pythinker_code/agents/default/agent.yaml +55 -0
  15. pythinker_code/agents/default/code_reviewer.yaml +47 -0
  16. pythinker_code/agents/default/coder.yaml +42 -0
  17. pythinker_code/agents/default/debugger.yaml +35 -0
  18. pythinker_code/agents/default/explore.yaml +59 -0
  19. pythinker_code/agents/default/implementer.yaml +46 -0
  20. pythinker_code/agents/default/plan.yaml +42 -0
  21. pythinker_code/agents/default/review.yaml +47 -0
  22. pythinker_code/agents/default/security_reviewer.yaml +37 -0
  23. pythinker_code/agents/default/system.md +192 -0
  24. pythinker_code/agents/default/verifier.yaml +46 -0
  25. pythinker_code/agents/okabe/agent.yaml +22 -0
  26. pythinker_code/agentspec.py +163 -0
  27. pythinker_code/app.py +847 -0
  28. pythinker_code/approval_runtime/__init__.py +29 -0
  29. pythinker_code/approval_runtime/models.py +42 -0
  30. pythinker_code/approval_runtime/runtime.py +235 -0
  31. pythinker_code/auth/__init__.py +25 -0
  32. pythinker_code/auth/anthropic_direct.py +207 -0
  33. pythinker_code/auth/deepseek.py +192 -0
  34. pythinker_code/auth/github_feedback.py +228 -0
  35. pythinker_code/auth/lm_studio.py +418 -0
  36. pythinker_code/auth/minimax.py +203 -0
  37. pythinker_code/auth/oauth.py +1145 -0
  38. pythinker_code/auth/ollama.py +293 -0
  39. pythinker_code/auth/openai.py +783 -0
  40. pythinker_code/auth/opencode_go.py +203 -0
  41. pythinker_code/auth/openrouter.py +225 -0
  42. pythinker_code/auth/platforms.py +475 -0
  43. pythinker_code/background/__init__.py +36 -0
  44. pythinker_code/background/agent_runner.py +231 -0
  45. pythinker_code/background/ids.py +19 -0
  46. pythinker_code/background/manager.py +668 -0
  47. pythinker_code/background/models.py +118 -0
  48. pythinker_code/background/store.py +243 -0
  49. pythinker_code/background/summary.py +66 -0
  50. pythinker_code/background/worker.py +209 -0
  51. pythinker_code/cli/__init__.py +1326 -0
  52. pythinker_code/cli/__main__.py +19 -0
  53. pythinker_code/cli/_lazy_group.py +268 -0
  54. pythinker_code/cli/debug.py +11 -0
  55. pythinker_code/cli/export.py +322 -0
  56. pythinker_code/cli/info.py +62 -0
  57. pythinker_code/cli/mcp.py +362 -0
  58. pythinker_code/cli/plugin.py +351 -0
  59. pythinker_code/cli/review.py +74 -0
  60. pythinker_code/cli/secscan.py +11 -0
  61. pythinker_code/cli/security_scan.py +35 -0
  62. pythinker_code/cli/toad.py +74 -0
  63. pythinker_code/cli/update.py +26 -0
  64. pythinker_code/cli/vis.py +38 -0
  65. pythinker_code/cli/web.py +80 -0
  66. pythinker_code/config.py +511 -0
  67. pythinker_code/constant.py +33 -0
  68. pythinker_code/events.py +106 -0
  69. pythinker_code/exception.py +43 -0
  70. pythinker_code/extensions.py +151 -0
  71. pythinker_code/hooks/__init__.py +4 -0
  72. pythinker_code/hooks/config.py +34 -0
  73. pythinker_code/hooks/engine.py +383 -0
  74. pythinker_code/hooks/events.py +190 -0
  75. pythinker_code/hooks/runner.py +92 -0
  76. pythinker_code/llm.py +441 -0
  77. pythinker_code/metadata.py +79 -0
  78. pythinker_code/notifications/__init__.py +33 -0
  79. pythinker_code/notifications/llm.py +77 -0
  80. pythinker_code/notifications/manager.py +145 -0
  81. pythinker_code/notifications/models.py +50 -0
  82. pythinker_code/notifications/notifier.py +41 -0
  83. pythinker_code/notifications/store.py +118 -0
  84. pythinker_code/notifications/wire.py +21 -0
  85. pythinker_code/plugin/__init__.py +124 -0
  86. pythinker_code/plugin/manager.py +166 -0
  87. pythinker_code/plugin/tool.py +173 -0
  88. pythinker_code/prompt_templates.py +181 -0
  89. pythinker_code/prompts/__init__.py +6 -0
  90. pythinker_code/prompts/compact.md +73 -0
  91. pythinker_code/prompts/init.md +21 -0
  92. pythinker_code/py.typed +0 -0
  93. pythinker_code/session.py +319 -0
  94. pythinker_code/session_fork.py +325 -0
  95. pythinker_code/session_state.py +132 -0
  96. pythinker_code/share.py +14 -0
  97. pythinker_code/skill/__init__.py +727 -0
  98. pythinker_code/skill/flow/__init__.py +99 -0
  99. pythinker_code/skill/flow/d2.py +482 -0
  100. pythinker_code/skill/flow/mermaid.py +266 -0
  101. pythinker_code/skills/pythinker-code-help/SKILL.md +54 -0
  102. pythinker_code/skills/skill-creator/SKILL.md +367 -0
  103. pythinker_code/soul/__init__.py +304 -0
  104. pythinker_code/soul/agent.py +552 -0
  105. pythinker_code/soul/approval.py +267 -0
  106. pythinker_code/soul/btw.py +220 -0
  107. pythinker_code/soul/compaction.py +189 -0
  108. pythinker_code/soul/context.py +339 -0
  109. pythinker_code/soul/denwarenji.py +39 -0
  110. pythinker_code/soul/dynamic_injection.py +84 -0
  111. pythinker_code/soul/dynamic_injections/__init__.py +0 -0
  112. pythinker_code/soul/dynamic_injections/auto_mode.py +72 -0
  113. pythinker_code/soul/dynamic_injections/plan_mode.py +239 -0
  114. pythinker_code/soul/message.py +92 -0
  115. pythinker_code/soul/permission.py +368 -0
  116. pythinker_code/soul/pythinkersoul.py +1763 -0
  117. pythinker_code/soul/slash.py +340 -0
  118. pythinker_code/soul/toolset.py +826 -0
  119. pythinker_code/subagents/__init__.py +21 -0
  120. pythinker_code/subagents/builder.py +43 -0
  121. pythinker_code/subagents/core.py +86 -0
  122. pythinker_code/subagents/discovery.py +234 -0
  123. pythinker_code/subagents/git_context.py +172 -0
  124. pythinker_code/subagents/models.py +56 -0
  125. pythinker_code/subagents/output.py +71 -0
  126. pythinker_code/subagents/registry.py +28 -0
  127. pythinker_code/subagents/runner.py +442 -0
  128. pythinker_code/subagents/store.py +200 -0
  129. pythinker_code/telemetry/__init__.py +217 -0
  130. pythinker_code/telemetry/config.py +113 -0
  131. pythinker_code/telemetry/crash.py +191 -0
  132. pythinker_code/telemetry/errors.py +113 -0
  133. pythinker_code/telemetry/metrics.py +208 -0
  134. pythinker_code/telemetry/otel.py +303 -0
  135. pythinker_code/telemetry/sentry.py +212 -0
  136. pythinker_code/telemetry/sink.py +189 -0
  137. pythinker_code/tools/AGENTS.md +6 -0
  138. pythinker_code/tools/__init__.py +105 -0
  139. pythinker_code/tools/agent/__init__.py +326 -0
  140. pythinker_code/tools/agent/description.md +65 -0
  141. pythinker_code/tools/ask_user/__init__.py +162 -0
  142. pythinker_code/tools/ask_user/description.md +19 -0
  143. pythinker_code/tools/background/__init__.py +318 -0
  144. pythinker_code/tools/background/list.md +10 -0
  145. pythinker_code/tools/background/output.md +11 -0
  146. pythinker_code/tools/background/stop.md +8 -0
  147. pythinker_code/tools/display.py +46 -0
  148. pythinker_code/tools/dmail/__init__.py +38 -0
  149. pythinker_code/tools/dmail/dmail.md +17 -0
  150. pythinker_code/tools/file/__init__.py +31 -0
  151. pythinker_code/tools/file/glob.md +17 -0
  152. pythinker_code/tools/file/glob.py +163 -0
  153. pythinker_code/tools/file/grep.md +6 -0
  154. pythinker_code/tools/file/grep_local.py +904 -0
  155. pythinker_code/tools/file/plan_mode.py +45 -0
  156. pythinker_code/tools/file/read.md +16 -0
  157. pythinker_code/tools/file/read.py +303 -0
  158. pythinker_code/tools/file/read_media.md +24 -0
  159. pythinker_code/tools/file/read_media.py +220 -0
  160. pythinker_code/tools/file/replace.md +7 -0
  161. pythinker_code/tools/file/replace.py +204 -0
  162. pythinker_code/tools/file/utils.py +257 -0
  163. pythinker_code/tools/file/write.md +5 -0
  164. pythinker_code/tools/file/write.py +186 -0
  165. pythinker_code/tools/plan/__init__.py +362 -0
  166. pythinker_code/tools/plan/description.md +29 -0
  167. pythinker_code/tools/plan/enter.py +193 -0
  168. pythinker_code/tools/plan/enter_description.md +35 -0
  169. pythinker_code/tools/plan/handoff.py +69 -0
  170. pythinker_code/tools/plan/heroes.py +277 -0
  171. pythinker_code/tools/shell/__init__.py +263 -0
  172. pythinker_code/tools/shell/bash.md +35 -0
  173. pythinker_code/tools/shell/powershell.md +30 -0
  174. pythinker_code/tools/test.py +55 -0
  175. pythinker_code/tools/think/__init__.py +21 -0
  176. pythinker_code/tools/think/think.md +1 -0
  177. pythinker_code/tools/todo/__init__.py +168 -0
  178. pythinker_code/tools/todo/set_todo_list.md +23 -0
  179. pythinker_code/tools/utils.py +200 -0
  180. pythinker_code/tools/web/__init__.py +4 -0
  181. pythinker_code/tools/web/fetch.md +1 -0
  182. pythinker_code/tools/web/fetch.py +261 -0
  183. pythinker_code/tools/web/search.md +1 -0
  184. pythinker_code/tools/web/search.py +163 -0
  185. pythinker_code/ui/__init__.py +0 -0
  186. pythinker_code/ui/acp/__init__.py +99 -0
  187. pythinker_code/ui/print/__init__.py +474 -0
  188. pythinker_code/ui/print/visualize.py +185 -0
  189. pythinker_code/ui/shell/__init__.py +1806 -0
  190. pythinker_code/ui/shell/components/__init__.py +110 -0
  191. pythinker_code/ui/shell/components/base.py +25 -0
  192. pythinker_code/ui/shell/components/bash_execution.py +249 -0
  193. pythinker_code/ui/shell/components/bordered_loader.py +62 -0
  194. pythinker_code/ui/shell/components/diff.py +308 -0
  195. pythinker_code/ui/shell/components/footer.py +231 -0
  196. pythinker_code/ui/shell/components/key_hints.py +27 -0
  197. pythinker_code/ui/shell/components/messages.py +152 -0
  198. pythinker_code/ui/shell/components/render_utils.py +198 -0
  199. pythinker_code/ui/shell/components/settings_list.py +369 -0
  200. pythinker_code/ui/shell/components/special_messages.py +125 -0
  201. pythinker_code/ui/shell/components/tool_execution.py +261 -0
  202. pythinker_code/ui/shell/console.py +109 -0
  203. pythinker_code/ui/shell/debug.py +190 -0
  204. pythinker_code/ui/shell/echo.py +30 -0
  205. pythinker_code/ui/shell/export_import.py +117 -0
  206. pythinker_code/ui/shell/keyboard.py +300 -0
  207. pythinker_code/ui/shell/keymap.py +84 -0
  208. pythinker_code/ui/shell/mcp_status.py +112 -0
  209. pythinker_code/ui/shell/model_picker.py +318 -0
  210. pythinker_code/ui/shell/oauth.py +273 -0
  211. pythinker_code/ui/shell/placeholders.py +578 -0
  212. pythinker_code/ui/shell/prompt.py +2888 -0
  213. pythinker_code/ui/shell/replay.py +215 -0
  214. pythinker_code/ui/shell/selector.py +364 -0
  215. pythinker_code/ui/shell/selectors/__init__.py +38 -0
  216. pythinker_code/ui/shell/selectors/extension.py +37 -0
  217. pythinker_code/ui/shell/selectors/oauth.py +63 -0
  218. pythinker_code/ui/shell/selectors/settings.py +349 -0
  219. pythinker_code/ui/shell/selectors/show_images.py +29 -0
  220. pythinker_code/ui/shell/selectors/theme.py +28 -0
  221. pythinker_code/ui/shell/selectors/thinking.py +42 -0
  222. pythinker_code/ui/shell/session_picker.py +227 -0
  223. pythinker_code/ui/shell/setup.py +212 -0
  224. pythinker_code/ui/shell/slash.py +1433 -0
  225. pythinker_code/ui/shell/spinner_words.py +222 -0
  226. pythinker_code/ui/shell/startup.py +32 -0
  227. pythinker_code/ui/shell/task_browser.py +486 -0
  228. pythinker_code/ui/shell/tool_renderers/__init__.py +197 -0
  229. pythinker_code/ui/shell/tool_renderers/_render_utils.py +168 -0
  230. pythinker_code/ui/shell/tool_renderers/agent.py +140 -0
  231. pythinker_code/ui/shell/tool_renderers/ask_user.py +93 -0
  232. pythinker_code/ui/shell/tool_renderers/background.py +144 -0
  233. pythinker_code/ui/shell/tool_renderers/bash.py +103 -0
  234. pythinker_code/ui/shell/tool_renderers/edit.py +163 -0
  235. pythinker_code/ui/shell/tool_renderers/find.py +81 -0
  236. pythinker_code/ui/shell/tool_renderers/generic.py +60 -0
  237. pythinker_code/ui/shell/tool_renderers/grep.py +98 -0
  238. pythinker_code/ui/shell/tool_renderers/plan.py +98 -0
  239. pythinker_code/ui/shell/tool_renderers/read.py +103 -0
  240. pythinker_code/ui/shell/tool_renderers/think.py +66 -0
  241. pythinker_code/ui/shell/tool_renderers/todo.py +164 -0
  242. pythinker_code/ui/shell/tool_renderers/web.py +128 -0
  243. pythinker_code/ui/shell/tool_renderers/write.py +102 -0
  244. pythinker_code/ui/shell/update.py +352 -0
  245. pythinker_code/ui/shell/usage.py +291 -0
  246. pythinker_code/ui/shell/usage_adapters/__init__.py +50 -0
  247. pythinker_code/ui/shell/usage_adapters/anthropic_admin.py +233 -0
  248. pythinker_code/ui/shell/usage_adapters/base.py +72 -0
  249. pythinker_code/ui/shell/usage_adapters/deepseek.py +137 -0
  250. pythinker_code/ui/shell/usage_adapters/minimax.py +236 -0
  251. pythinker_code/ui/shell/usage_adapters/openai_admin.py +225 -0
  252. pythinker_code/ui/shell/usage_adapters/openai_chatgpt.py +241 -0
  253. pythinker_code/ui/shell/usage_adapters/opencode_go.py +232 -0
  254. pythinker_code/ui/shell/usage_adapters/openrouter.py +105 -0
  255. pythinker_code/ui/shell/usage_adapters/pythinker.py +189 -0
  256. pythinker_code/ui/shell/usage_adapters/pythinker_ai.py +50 -0
  257. pythinker_code/ui/shell/usage_render.py +150 -0
  258. pythinker_code/ui/shell/visualize/__init__.py +165 -0
  259. pythinker_code/ui/shell/visualize/_approval_panel.py +539 -0
  260. pythinker_code/ui/shell/visualize/_blocks.py +802 -0
  261. pythinker_code/ui/shell/visualize/_btw_panel.py +227 -0
  262. pythinker_code/ui/shell/visualize/_input_router.py +48 -0
  263. pythinker_code/ui/shell/visualize/_interactive.py +531 -0
  264. pythinker_code/ui/shell/visualize/_live_view.py +891 -0
  265. pythinker_code/ui/shell/visualize/_question_panel.py +586 -0
  266. pythinker_code/ui/shell/visualize/_worklog.py +245 -0
  267. pythinker_code/ui/theme.py +395 -0
  268. pythinker_code/ui/tui_config.py +82 -0
  269. pythinker_code/usage_ratelimit_cache.py +175 -0
  270. pythinker_code/utils/__init__.py +0 -0
  271. pythinker_code/utils/aiohttp.py +24 -0
  272. pythinker_code/utils/aioqueue.py +72 -0
  273. pythinker_code/utils/broadcast.py +38 -0
  274. pythinker_code/utils/changelog.py +108 -0
  275. pythinker_code/utils/clipboard.py +246 -0
  276. pythinker_code/utils/datetime.py +64 -0
  277. pythinker_code/utils/diff.py +135 -0
  278. pythinker_code/utils/editor.py +91 -0
  279. pythinker_code/utils/environment.py +73 -0
  280. pythinker_code/utils/envvar.py +22 -0
  281. pythinker_code/utils/export.py +696 -0
  282. pythinker_code/utils/file_filter.py +375 -0
  283. pythinker_code/utils/frontmatter.py +70 -0
  284. pythinker_code/utils/io.py +27 -0
  285. pythinker_code/utils/logging.py +146 -0
  286. pythinker_code/utils/media_tags.py +29 -0
  287. pythinker_code/utils/message.py +24 -0
  288. pythinker_code/utils/path.py +199 -0
  289. pythinker_code/utils/proctitle.py +33 -0
  290. pythinker_code/utils/proxy.py +31 -0
  291. pythinker_code/utils/pyinstaller.py +45 -0
  292. pythinker_code/utils/rich/__init__.py +33 -0
  293. pythinker_code/utils/rich/columns.py +99 -0
  294. pythinker_code/utils/rich/diff_render.py +481 -0
  295. pythinker_code/utils/rich/markdown.py +935 -0
  296. pythinker_code/utils/rich/markdown_sample.md +108 -0
  297. pythinker_code/utils/rich/markdown_sample_short.md +2 -0
  298. pythinker_code/utils/rich/syntax.py +114 -0
  299. pythinker_code/utils/sensitive.py +54 -0
  300. pythinker_code/utils/server.py +121 -0
  301. pythinker_code/utils/signals.py +43 -0
  302. pythinker_code/utils/slashcmd.py +124 -0
  303. pythinker_code/utils/string.py +41 -0
  304. pythinker_code/utils/subprocess_env.py +83 -0
  305. pythinker_code/utils/term.py +168 -0
  306. pythinker_code/utils/typing.py +20 -0
  307. pythinker_code/vis/__init__.py +0 -0
  308. pythinker_code/vis/api/__init__.py +5 -0
  309. pythinker_code/vis/api/sessions.py +714 -0
  310. pythinker_code/vis/api/statistics.py +209 -0
  311. pythinker_code/vis/api/system.py +19 -0
  312. pythinker_code/vis/app.py +199 -0
  313. pythinker_code/vis/static/assets/highlighted-body-B3W2YXNL-CY1rtwrX.js +1 -0
  314. pythinker_code/vis/static/assets/index-DSRInNnm.css +1 -0
  315. pythinker_code/vis/static/assets/index-DgmTI2M_.js +185 -0
  316. pythinker_code/vis/static/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  317. pythinker_code/vis/static/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  318. pythinker_code/vis/static/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  319. pythinker_code/vis/static/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  320. pythinker_code/vis/static/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  321. pythinker_code/vis/static/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  322. pythinker_code/vis/static/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  323. pythinker_code/vis/static/index.html +17 -0
  324. pythinker_code/web/__init__.py +5 -0
  325. pythinker_code/web/api/__init__.py +15 -0
  326. pythinker_code/web/api/config.py +217 -0
  327. pythinker_code/web/api/open_in.py +233 -0
  328. pythinker_code/web/api/sessions.py +1256 -0
  329. pythinker_code/web/app.py +449 -0
  330. pythinker_code/web/auth.py +191 -0
  331. pythinker_code/web/models.py +98 -0
  332. pythinker_code/web/runner/__init__.py +5 -0
  333. pythinker_code/web/runner/messages.py +57 -0
  334. pythinker_code/web/runner/process.py +754 -0
  335. pythinker_code/web/runner/worker.py +97 -0
  336. pythinker_code/web/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  337. pythinker_code/web/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  338. pythinker_code/web/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  339. pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  340. pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  341. pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  342. pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  343. pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  344. pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  345. pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  346. pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  347. pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  348. pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  349. pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  350. pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  351. pythinker_code/web/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  352. pythinker_code/web/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  353. pythinker_code/web/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  354. pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  355. pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  356. pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  357. pythinker_code/web/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  358. pythinker_code/web/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  359. pythinker_code/web/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  360. pythinker_code/web/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  361. pythinker_code/web/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  362. pythinker_code/web/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  363. pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  364. pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  365. pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  366. pythinker_code/web/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  367. pythinker_code/web/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  368. pythinker_code/web/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  369. pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  370. pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  371. pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  372. pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  373. pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  374. pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  375. pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  376. pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  377. pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  378. pythinker_code/web/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  379. pythinker_code/web/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  380. pythinker_code/web/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  381. pythinker_code/web/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  382. pythinker_code/web/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  383. pythinker_code/web/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  384. pythinker_code/web/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  385. pythinker_code/web/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  386. pythinker_code/web/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  387. pythinker_code/web/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  388. pythinker_code/web/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  389. pythinker_code/web/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  390. pythinker_code/web/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  391. pythinker_code/web/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  392. pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  393. pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  394. pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  395. pythinker_code/web/static/assets/_baseUniq-DpSMr1jx.js +1 -0
  396. pythinker_code/web/static/assets/abap-BdImnpbu.js +1 -0
  397. pythinker_code/web/static/assets/actionscript-3-CfeIJUat.js +1 -0
  398. pythinker_code/web/static/assets/ada-bCR0ucgS.js +1 -0
  399. pythinker_code/web/static/assets/andromeeda-C-Jbm3Hp.js +1 -0
  400. pythinker_code/web/static/assets/angular-html-CU67Zn6k.js +1 -0
  401. pythinker_code/web/static/assets/angular-ts-BwZT4LLn.js +1 -0
  402. pythinker_code/web/static/assets/apache-Pmp26Uib.js +1 -0
  403. pythinker_code/web/static/assets/apex-D8_7TLub.js +1 -0
  404. pythinker_code/web/static/assets/apl-dKokRX4l.js +1 -0
  405. pythinker_code/web/static/assets/applescript-Co6uUVPk.js +1 -0
  406. pythinker_code/web/static/assets/ara-BRHolxvo.js +1 -0
  407. pythinker_code/web/static/assets/arc-DpsahJyV.js +1 -0
  408. pythinker_code/web/static/assets/architectureDiagram-VXUJARFQ-DqiRv9Eg.js +36 -0
  409. pythinker_code/web/static/assets/asciidoc-Dv7Oe6Be.js +1 -0
  410. pythinker_code/web/static/assets/asm-D_Q5rh1f.js +1 -0
  411. pythinker_code/web/static/assets/astro-CbQHKStN.js +1 -0
  412. pythinker_code/web/static/assets/aurora-x-D-2ljcwZ.js +1 -0
  413. pythinker_code/web/static/assets/awk-DMzUqQB5.js +1 -0
  414. pythinker_code/web/static/assets/ayu-dark-CmMr59Fi.js +1 -0
  415. pythinker_code/web/static/assets/ballerina-BFfxhgS-.js +1 -0
  416. pythinker_code/web/static/assets/bat-BkioyH1T.js +1 -0
  417. pythinker_code/web/static/assets/beancount-k_qm7-4y.js +1 -0
  418. pythinker_code/web/static/assets/berry-uYugtg8r.js +1 -0
  419. pythinker_code/web/static/assets/bibtex-CHM0blh-.js +1 -0
  420. pythinker_code/web/static/assets/bicep-Bmn6On1c.js +1 -0
  421. pythinker_code/web/static/assets/blade-D4QpJJKB.js +1 -0
  422. pythinker_code/web/static/assets/blockDiagram-VD42YOAC-WgtUvqbp.js +122 -0
  423. pythinker_code/web/static/assets/bsl-BO_Y6i37.js +1 -0
  424. pythinker_code/web/static/assets/c-BIGW1oBm.js +1 -0
  425. pythinker_code/web/static/assets/c3-VCDPK7BO.js +1 -0
  426. pythinker_code/web/static/assets/c4Diagram-YG6GDRKO-rK0RPuZd.js +10 -0
  427. pythinker_code/web/static/assets/cadence-Bv_4Rxtq.js +1 -0
  428. pythinker_code/web/static/assets/cairo-KRGpt6FW.js +1 -0
  429. pythinker_code/web/static/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  430. pythinker_code/web/static/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  431. pythinker_code/web/static/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  432. pythinker_code/web/static/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  433. pythinker_code/web/static/assets/channel-B0rlvkH-.js +1 -0
  434. pythinker_code/web/static/assets/chunk-4BX2VUAB-DIkMuLV-.js +1 -0
  435. pythinker_code/web/static/assets/chunk-55IACEB6-CORdm4k4.js +1 -0
  436. pythinker_code/web/static/assets/chunk-B4BG7PRW-D9xDhwHO.js +165 -0
  437. pythinker_code/web/static/assets/chunk-DI55MBZ5-BDmF9Bh-.js +220 -0
  438. pythinker_code/web/static/assets/chunk-FMBD7UC4-BCse_HmM.js +15 -0
  439. pythinker_code/web/static/assets/chunk-QN33PNHL-DCpBmTzA.js +1 -0
  440. pythinker_code/web/static/assets/chunk-QZHKN3VN-BqLuqobw.js +1 -0
  441. pythinker_code/web/static/assets/chunk-TZMSLE5B-8K2ogOKS.js +1 -0
  442. pythinker_code/web/static/assets/clarity-D53aC0YG.js +1 -0
  443. pythinker_code/web/static/assets/classDiagram-2ON5EDUG-D_ZHSii2.js +1 -0
  444. pythinker_code/web/static/assets/classDiagram-v2-WZHVMYZB-D_ZHSii2.js +1 -0
  445. pythinker_code/web/static/assets/clojure-P80f7IUj.js +1 -0
  446. pythinker_code/web/static/assets/clone-GSXejyY1.js +1 -0
  447. pythinker_code/web/static/assets/cmake-D1j8_8rp.js +1 -0
  448. pythinker_code/web/static/assets/cobol-nwyudZeR.js +1 -0
  449. pythinker_code/web/static/assets/code-block-IT6T5CEO-DWTFYA28.js +2 -0
  450. pythinker_code/web/static/assets/codeowners-Bp6g37R7.js +1 -0
  451. pythinker_code/web/static/assets/codeql-DsOJ9woJ.js +1 -0
  452. pythinker_code/web/static/assets/coffee-Ch7k5sss.js +1 -0
  453. pythinker_code/web/static/assets/common-lisp-Cg-RD9OK.js +1 -0
  454. pythinker_code/web/static/assets/coq-DkFqJrB1.js +1 -0
  455. pythinker_code/web/static/assets/cose-bilkent-S5V4N54A-BRI7ES-N.js +1 -0
  456. pythinker_code/web/static/assets/cpp-CofmeUqb.js +1 -0
  457. pythinker_code/web/static/assets/crystal-tKQVLTB8.js +1 -0
  458. pythinker_code/web/static/assets/csharp-K5feNrxe.js +1 -0
  459. pythinker_code/web/static/assets/css-DPfMkruS.js +1 -0
  460. pythinker_code/web/static/assets/csv-fuZLfV_i.js +1 -0
  461. pythinker_code/web/static/assets/cue-D82EKSYY.js +1 -0
  462. pythinker_code/web/static/assets/cypher-COkxafJQ.js +1 -0
  463. pythinker_code/web/static/assets/cytoscape.esm-B6BxUuKW.js +321 -0
  464. pythinker_code/web/static/assets/d-85-TOEBH.js +1 -0
  465. pythinker_code/web/static/assets/dagre-6UL2VRFP-Ci5GdWfi.js +4 -0
  466. pythinker_code/web/static/assets/dark-plus-C3mMm8J8.js +1 -0
  467. pythinker_code/web/static/assets/dart-CF10PKvl.js +1 -0
  468. pythinker_code/web/static/assets/dax-CEL-wOlO.js +1 -0
  469. pythinker_code/web/static/assets/defaultLocale-DX6XiGOO.js +1 -0
  470. pythinker_code/web/static/assets/desktop-BmXAJ9_W.js +1 -0
  471. pythinker_code/web/static/assets/diagram-PSM6KHXK-0hhAylV4.js +24 -0
  472. pythinker_code/web/static/assets/diagram-QEK2KX5R-8fxgaW6d.js +43 -0
  473. pythinker_code/web/static/assets/diagram-S2PKOQOG-FRr0_atE.js +24 -0
  474. pythinker_code/web/static/assets/diff-D97Zzqfu.js +1 -0
  475. pythinker_code/web/static/assets/docker-BcOcwvcX.js +1 -0
  476. pythinker_code/web/static/assets/dotenv-Da5cRb03.js +1 -0
  477. pythinker_code/web/static/assets/dracula-BzJJZx-M.js +1 -0
  478. pythinker_code/web/static/assets/dracula-soft-BXkSAIEj.js +1 -0
  479. pythinker_code/web/static/assets/dream-maker-BtqSS_iP.js +1 -0
  480. pythinker_code/web/static/assets/edge-BkV0erSs.js +1 -0
  481. pythinker_code/web/static/assets/elixir-CDX3lj18.js +1 -0
  482. pythinker_code/web/static/assets/elm-DbKCFpqz.js +1 -0
  483. pythinker_code/web/static/assets/emacs-lisp-C9XAeP06.js +1 -0
  484. pythinker_code/web/static/assets/erDiagram-Q2GNP2WA-B3T-hJUM.js +60 -0
  485. pythinker_code/web/static/assets/erb-BOJIQeun.js +1 -0
  486. pythinker_code/web/static/assets/erlang-DsQrWhSR.js +1 -0
  487. pythinker_code/web/static/assets/everforest-dark-BgDCqdQA.js +1 -0
  488. pythinker_code/web/static/assets/everforest-light-C8M2exoo.js +1 -0
  489. pythinker_code/web/static/assets/fennel-BYunw83y.js +1 -0
  490. pythinker_code/web/static/assets/fish-BvzEVeQv.js +1 -0
  491. pythinker_code/web/static/assets/flowDiagram-NV44I4VS-D0S3u7ot.js +162 -0
  492. pythinker_code/web/static/assets/fluent-C4IJs8-o.js +1 -0
  493. pythinker_code/web/static/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
  494. pythinker_code/web/static/assets/fortran-free-form-BxgE0vQu.js +1 -0
  495. pythinker_code/web/static/assets/fsharp-CXgrBDvD.js +1 -0
  496. pythinker_code/web/static/assets/ganttDiagram-JELNMOA3-CHrN2a23.js +267 -0
  497. pythinker_code/web/static/assets/gdresource-B7Tvp0Sc.js +1 -0
  498. pythinker_code/web/static/assets/gdscript-DTMYz4Jt.js +1 -0
  499. pythinker_code/web/static/assets/gdshader-DkwncUOv.js +1 -0
  500. pythinker_code/web/static/assets/genie-D0YGMca9.js +1 -0
  501. pythinker_code/web/static/assets/gherkin-DyxjwDmM.js +1 -0
  502. pythinker_code/web/static/assets/git-commit-F4YmCXRG.js +1 -0
  503. pythinker_code/web/static/assets/git-rebase-r7XF79zn.js +1 -0
  504. pythinker_code/web/static/assets/gitGraphDiagram-NY62KEGX-CfcXZWg0.js +65 -0
  505. pythinker_code/web/static/assets/github-dark-DHJKELXO.js +1 -0
  506. pythinker_code/web/static/assets/github-dark-default-Cuk6v7N8.js +1 -0
  507. pythinker_code/web/static/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  508. pythinker_code/web/static/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  509. pythinker_code/web/static/assets/github-light-DAi9KRSo.js +1 -0
  510. pythinker_code/web/static/assets/github-light-default-D7oLnXFd.js +1 -0
  511. pythinker_code/web/static/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  512. pythinker_code/web/static/assets/gleam-BspZqrRM.js +1 -0
  513. pythinker_code/web/static/assets/glimmer-js-Rg0-pVw9.js +1 -0
  514. pythinker_code/web/static/assets/glimmer-ts-U6CK756n.js +1 -0
  515. pythinker_code/web/static/assets/glsl-DplSGwfg.js +1 -0
  516. pythinker_code/web/static/assets/gn-n2N0HUVH.js +1 -0
  517. pythinker_code/web/static/assets/gnuplot-DdkO51Og.js +1 -0
  518. pythinker_code/web/static/assets/go-Dn2_MT6a.js +1 -0
  519. pythinker_code/web/static/assets/graph-8jMJwCqE.js +1 -0
  520. pythinker_code/web/static/assets/graphql-ChdNCCLP.js +1 -0
  521. pythinker_code/web/static/assets/groovy-gcz8RCvz.js +1 -0
  522. pythinker_code/web/static/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  523. pythinker_code/web/static/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  524. pythinker_code/web/static/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  525. pythinker_code/web/static/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  526. pythinker_code/web/static/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  527. pythinker_code/web/static/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  528. pythinker_code/web/static/assets/hack-CaT9iCJl.js +1 -0
  529. pythinker_code/web/static/assets/haml-B8DHNrY2.js +1 -0
  530. pythinker_code/web/static/assets/handlebars-BL8al0AC.js +1 -0
  531. pythinker_code/web/static/assets/haskell-Df6bDoY_.js +1 -0
  532. pythinker_code/web/static/assets/haxe-CzTSHFRz.js +1 -0
  533. pythinker_code/web/static/assets/hcl-BWvSN4gD.js +1 -0
  534. pythinker_code/web/static/assets/hjson-D5-asLiD.js +1 -0
  535. pythinker_code/web/static/assets/hlsl-D3lLCCz7.js +1 -0
  536. pythinker_code/web/static/assets/houston-DnULxvSX.js +1 -0
  537. pythinker_code/web/static/assets/html-GMplVEZG.js +1 -0
  538. pythinker_code/web/static/assets/html-derivative-BFtXZ54Q.js +1 -0
  539. pythinker_code/web/static/assets/http-jrhK8wxY.js +1 -0
  540. pythinker_code/web/static/assets/hurl-irOxFIW8.js +1 -0
  541. pythinker_code/web/static/assets/hxml-Bvhsp5Yf.js +1 -0
  542. pythinker_code/web/static/assets/hy-DFXneXwc.js +1 -0
  543. pythinker_code/web/static/assets/imba-DGztddWO.js +1 -0
  544. pythinker_code/web/static/assets/index-BXrFnzMy.js +153 -0
  545. pythinker_code/web/static/assets/index-BpoLgcEt.js +1 -0
  546. pythinker_code/web/static/assets/index-BrfQJnRD.js +5 -0
  547. pythinker_code/web/static/assets/index-C4gFzubz.js +2 -0
  548. pythinker_code/web/static/assets/index-CzV_vCfu.css +1 -0
  549. pythinker_code/web/static/assets/index-DI2oedCt.js +19 -0
  550. pythinker_code/web/static/assets/infoDiagram-WHAUD3N6-DdxonBf3.js +2 -0
  551. pythinker_code/web/static/assets/ini-BEwlwnbL.js +1 -0
  552. pythinker_code/web/static/assets/init-Gi6I4Gst.js +1 -0
  553. pythinker_code/web/static/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  554. pythinker_code/web/static/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  555. pythinker_code/web/static/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  556. pythinker_code/web/static/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  557. pythinker_code/web/static/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  558. pythinker_code/web/static/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  559. pythinker_code/web/static/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  560. pythinker_code/web/static/assets/java-CylS5w8V.js +1 -0
  561. pythinker_code/web/static/assets/javascript-wDzz0qaB.js +1 -0
  562. pythinker_code/web/static/assets/jinja-4LBKfQ-Z.js +1 -0
  563. pythinker_code/web/static/assets/jison-wvAkD_A8.js +1 -0
  564. pythinker_code/web/static/assets/journeyDiagram-XKPGCS4Q-BXf4aQei.js +139 -0
  565. pythinker_code/web/static/assets/json-Cp-IABpG.js +1 -0
  566. pythinker_code/web/static/assets/json5-C9tS-k6U.js +1 -0
  567. pythinker_code/web/static/assets/jsonc-Des-eS-w.js +1 -0
  568. pythinker_code/web/static/assets/jsonl-DcaNXYhu.js +1 -0
  569. pythinker_code/web/static/assets/jsonnet-DFQXde-d.js +1 -0
  570. pythinker_code/web/static/assets/jssm-C2t-YnRu.js +1 -0
  571. pythinker_code/web/static/assets/jsx-g9-lgVsj.js +1 -0
  572. pythinker_code/web/static/assets/julia-CxzCAyBv.js +1 -0
  573. pythinker_code/web/static/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  574. pythinker_code/web/static/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  575. pythinker_code/web/static/assets/kanagawa-wave-DWedfzmr.js +1 -0
  576. pythinker_code/web/static/assets/kanban-definition-3W4ZIXB7-DLpPPOu8.js +89 -0
  577. pythinker_code/web/static/assets/katex-D2lIc1rk.css +1 -0
  578. pythinker_code/web/static/assets/kdl-DV7GczEv.js +1 -0
  579. pythinker_code/web/static/assets/kotlin-BdnUsdx6.js +1 -0
  580. pythinker_code/web/static/assets/kusto-DZf3V79B.js +1 -0
  581. pythinker_code/web/static/assets/laserwave-DUszq2jm.js +1 -0
  582. pythinker_code/web/static/assets/latex-B4uzh10-.js +1 -0
  583. pythinker_code/web/static/assets/layout-DH73UoAH.js +1 -0
  584. pythinker_code/web/static/assets/lean-BZvkOJ9d.js +1 -0
  585. pythinker_code/web/static/assets/less-B1dDrJ26.js +1 -0
  586. pythinker_code/web/static/assets/light-plus-B7mTdjB0.js +1 -0
  587. pythinker_code/web/static/assets/linear-bAer2-sK.js +1 -0
  588. pythinker_code/web/static/assets/liquid-DYVedYrR.js +1 -0
  589. pythinker_code/web/static/assets/llvm-BtvRca6l.js +1 -0
  590. pythinker_code/web/static/assets/log-2UxHyX5q.js +1 -0
  591. pythinker_code/web/static/assets/logo-BtOb2qkB.js +1 -0
  592. pythinker_code/web/static/assets/lua-BbnMAYS6.js +1 -0
  593. pythinker_code/web/static/assets/luau-C-HG3fhB.js +1 -0
  594. pythinker_code/web/static/assets/make-CHLpvVh8.js +1 -0
  595. pythinker_code/web/static/assets/markdown-Cvjx9yec.js +1 -0
  596. pythinker_code/web/static/assets/marko-DZsq8hO1.js +1 -0
  597. pythinker_code/web/static/assets/material-theme-D5KoaKCx.js +1 -0
  598. pythinker_code/web/static/assets/material-theme-darker-BfHTSMKl.js +1 -0
  599. pythinker_code/web/static/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  600. pythinker_code/web/static/assets/material-theme-ocean-CyktbL80.js +1 -0
  601. pythinker_code/web/static/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  602. pythinker_code/web/static/assets/matlab-D7o27uSR.js +1 -0
  603. pythinker_code/web/static/assets/mdc-DUICxH0z.js +1 -0
  604. pythinker_code/web/static/assets/mdx-Cmh6b_Ma.js +1 -0
  605. pythinker_code/web/static/assets/mermaid-VLURNSYL-B2P5VJ9v.css +1 -0
  606. pythinker_code/web/static/assets/mermaid-VLURNSYL-CuqbwKXv.js +465 -0
  607. pythinker_code/web/static/assets/mermaid-mWjccvbQ.js +1 -0
  608. pythinker_code/web/static/assets/mermaid.core-Nx-rTKiV.js +191 -0
  609. pythinker_code/web/static/assets/min-DbfD8Ywu.js +1 -0
  610. pythinker_code/web/static/assets/min-dark-CafNBF8u.js +1 -0
  611. pythinker_code/web/static/assets/min-light-CTRr51gU.js +1 -0
  612. pythinker_code/web/static/assets/mindmap-definition-VGOIOE7T-C6l761Ue.js +68 -0
  613. pythinker_code/web/static/assets/mipsasm-CKIfxQSi.js +1 -0
  614. pythinker_code/web/static/assets/mojo-B93PlW-d.js +1 -0
  615. pythinker_code/web/static/assets/monokai-D4h5O-jR.js +1 -0
  616. pythinker_code/web/static/assets/moonbit-Ba13S78F.js +1 -0
  617. pythinker_code/web/static/assets/move-Bu9oaDYs.js +1 -0
  618. pythinker_code/web/static/assets/narrat-DRg8JJMk.js +1 -0
  619. pythinker_code/web/static/assets/nextflow-BrzmwbiE.js +1 -0
  620. pythinker_code/web/static/assets/nginx-DknmC5AR.js +1 -0
  621. pythinker_code/web/static/assets/night-owl-C39BiMTA.js +1 -0
  622. pythinker_code/web/static/assets/nim-CVrawwO9.js +1 -0
  623. pythinker_code/web/static/assets/nix-CwoSXNpI.js +1 -0
  624. pythinker_code/web/static/assets/nord-Ddv68eIx.js +1 -0
  625. pythinker_code/web/static/assets/nushell-C-sUppwS.js +1 -0
  626. pythinker_code/web/static/assets/objective-c-DXmwc3jG.js +1 -0
  627. pythinker_code/web/static/assets/objective-cpp-CLxacb5B.js +1 -0
  628. pythinker_code/web/static/assets/ocaml-C0hk2d4L.js +1 -0
  629. pythinker_code/web/static/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  630. pythinker_code/web/static/assets/one-light-PoHY5YXO.js +1 -0
  631. pythinker_code/web/static/assets/openscad-C4EeE6gA.js +1 -0
  632. pythinker_code/web/static/assets/ordinal-Cboi1Yqb.js +1 -0
  633. pythinker_code/web/static/assets/pascal-D93ZcfNL.js +1 -0
  634. pythinker_code/web/static/assets/perl-C0TMdlhV.js +1 -0
  635. pythinker_code/web/static/assets/php-CDn_0X-4.js +1 -0
  636. pythinker_code/web/static/assets/pieDiagram-ADFJNKIX-fNg41mT9.js +30 -0
  637. pythinker_code/web/static/assets/pkl-u5AG7uiY.js +1 -0
  638. pythinker_code/web/static/assets/plastic-3e1v2bzS.js +1 -0
  639. pythinker_code/web/static/assets/plsql-ChMvpjG-.js +1 -0
  640. pythinker_code/web/static/assets/po-BTJTHyun.js +1 -0
  641. pythinker_code/web/static/assets/poimandres-CS3Unz2-.js +1 -0
  642. pythinker_code/web/static/assets/polar-C0HS_06l.js +1 -0
  643. pythinker_code/web/static/assets/postcss-CXtECtnM.js +1 -0
  644. pythinker_code/web/static/assets/powerquery-CEu0bR-o.js +1 -0
  645. pythinker_code/web/static/assets/powershell-Dpen1YoG.js +1 -0
  646. pythinker_code/web/static/assets/prisma-Dd19v3D-.js +1 -0
  647. pythinker_code/web/static/assets/prolog-CbFg5uaA.js +1 -0
  648. pythinker_code/web/static/assets/proto-C7zT0LnQ.js +1 -0
  649. pythinker_code/web/static/assets/pug-CGlum2m_.js +1 -0
  650. pythinker_code/web/static/assets/puppet-BMWR74SV.js +1 -0
  651. pythinker_code/web/static/assets/purescript-CklMAg4u.js +1 -0
  652. pythinker_code/web/static/assets/python-B6aJPvgy.js +1 -0
  653. pythinker_code/web/static/assets/qml-3beO22l8.js +1 -0
  654. pythinker_code/web/static/assets/qmldir-C8lEn-DE.js +1 -0
  655. pythinker_code/web/static/assets/qss-IeuSbFQv.js +1 -0
  656. pythinker_code/web/static/assets/quadrantDiagram-AYHSOK5B-DJz3Kx87.js +7 -0
  657. pythinker_code/web/static/assets/r-Dspwwk_N.js +1 -0
  658. pythinker_code/web/static/assets/racket-BqYA7rlc.js +1 -0
  659. pythinker_code/web/static/assets/raku-DXvB9xmW.js +1 -0
  660. pythinker_code/web/static/assets/razor-C1TweQQi.js +1 -0
  661. pythinker_code/web/static/assets/red-bN70gL4F.js +1 -0
  662. pythinker_code/web/static/assets/reg-C-SQnVFl.js +1 -0
  663. pythinker_code/web/static/assets/regexp-CDVJQ6XC.js +1 -0
  664. pythinker_code/web/static/assets/rel-C3B-1QV4.js +1 -0
  665. pythinker_code/web/static/assets/requirementDiagram-UZGBJVZJ-B4SbrfE9.js +64 -0
  666. pythinker_code/web/static/assets/riscv-BM1_JUlF.js +1 -0
  667. pythinker_code/web/static/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
  668. pythinker_code/web/static/assets/rose-pine-moon-D4_iv3hh.js +1 -0
  669. pythinker_code/web/static/assets/rose-pine-qdsjHGoJ.js +1 -0
  670. pythinker_code/web/static/assets/rosmsg-BJDFO7_C.js +1 -0
  671. pythinker_code/web/static/assets/rst-B0xPkSld.js +1 -0
  672. pythinker_code/web/static/assets/ruby-BvKwtOVI.js +1 -0
  673. pythinker_code/web/static/assets/rust-B1yitclQ.js +1 -0
  674. pythinker_code/web/static/assets/sankeyDiagram-TZEHDZUN-CoSUjLAG.js +10 -0
  675. pythinker_code/web/static/assets/sas-cz2c8ADy.js +1 -0
  676. pythinker_code/web/static/assets/sass-Cj5Yp3dK.js +1 -0
  677. pythinker_code/web/static/assets/scala-C151Ov-r.js +1 -0
  678. pythinker_code/web/static/assets/scheme-C98Dy4si.js +1 -0
  679. pythinker_code/web/static/assets/scss-OYdSNvt2.js +1 -0
  680. pythinker_code/web/static/assets/sdbl-DVxCFoDh.js +1 -0
  681. pythinker_code/web/static/assets/sequenceDiagram-WL72ISMW-PjhBNHi3.js +145 -0
  682. pythinker_code/web/static/assets/shaderlab-Dg9Lc6iA.js +1 -0
  683. pythinker_code/web/static/assets/shellscript-Yzrsuije.js +1 -0
  684. pythinker_code/web/static/assets/shellsession-BADoaaVG.js +1 -0
  685. pythinker_code/web/static/assets/slack-dark-BthQWCQV.js +1 -0
  686. pythinker_code/web/static/assets/slack-ochin-DqwNpetd.js +1 -0
  687. pythinker_code/web/static/assets/smalltalk-BERRCDM3.js +1 -0
  688. pythinker_code/web/static/assets/snazzy-light-Bw305WKR.js +1 -0
  689. pythinker_code/web/static/assets/solarized-dark-DXbdFlpD.js +1 -0
  690. pythinker_code/web/static/assets/solarized-light-L9t79GZl.js +1 -0
  691. pythinker_code/web/static/assets/solidity-rGO070M0.js +1 -0
  692. pythinker_code/web/static/assets/soy-Brmx7dQM.js +1 -0
  693. pythinker_code/web/static/assets/sparql-rVzFXLq3.js +1 -0
  694. pythinker_code/web/static/assets/splunk-BtCnVYZw.js +1 -0
  695. pythinker_code/web/static/assets/sql-BLtJtn59.js +1 -0
  696. pythinker_code/web/static/assets/ssh-config-_ykCGR6B.js +1 -0
  697. pythinker_code/web/static/assets/stata-BH5u7GGu.js +1 -0
  698. pythinker_code/web/static/assets/stateDiagram-FKZM4ZOC-DOwESt8-.js +1 -0
  699. pythinker_code/web/static/assets/stateDiagram-v2-4FDKWEC3-yl3OHWiP.js +1 -0
  700. pythinker_code/web/static/assets/stylus-BEDo0Tqx.js +1 -0
  701. pythinker_code/web/static/assets/svelte-zxCyuUbr.js +1 -0
  702. pythinker_code/web/static/assets/swift-Dg5xB15N.js +1 -0
  703. pythinker_code/web/static/assets/synthwave-84-CbfX1IO0.js +1 -0
  704. pythinker_code/web/static/assets/system-verilog-CnnmHF94.js +1 -0
  705. pythinker_code/web/static/assets/systemd-4A_iFExJ.js +1 -0
  706. pythinker_code/web/static/assets/talonscript-CkByrt1z.js +1 -0
  707. pythinker_code/web/static/assets/tasl-QIJgUcNo.js +1 -0
  708. pythinker_code/web/static/assets/tcl-dwOrl1Do.js +1 -0
  709. pythinker_code/web/static/assets/templ-W15q3VgB.js +1 -0
  710. pythinker_code/web/static/assets/terraform-BETggiCN.js +1 -0
  711. pythinker_code/web/static/assets/tex-CvyZ59Mk.js +1 -0
  712. pythinker_code/web/static/assets/timeline-definition-IT6M3QCI-CkCLnAgi.js +61 -0
  713. pythinker_code/web/static/assets/tokyo-night-hegEt444.js +1 -0
  714. pythinker_code/web/static/assets/toml-vGWfd6FD.js +1 -0
  715. pythinker_code/web/static/assets/treemap-KMMF4GRG-CZS5XwTf.js +128 -0
  716. pythinker_code/web/static/assets/ts-tags-zn1MmPIZ.js +1 -0
  717. pythinker_code/web/static/assets/tsv-B_m7g4N7.js +1 -0
  718. pythinker_code/web/static/assets/tsx-COt5Ahok.js +1 -0
  719. pythinker_code/web/static/assets/turtle-BsS91CYL.js +1 -0
  720. pythinker_code/web/static/assets/twig-CO9l9SDP.js +1 -0
  721. pythinker_code/web/static/assets/typescript-BPQ3VLAy.js +1 -0
  722. pythinker_code/web/static/assets/typespec-BGHnOYBU.js +1 -0
  723. pythinker_code/web/static/assets/typst-DHCkPAjA.js +1 -0
  724. pythinker_code/web/static/assets/v-BcVCzyr7.js +1 -0
  725. pythinker_code/web/static/assets/vala-CsfeWuGM.js +1 -0
  726. pythinker_code/web/static/assets/vb-D17OF-Vu.js +1 -0
  727. pythinker_code/web/static/assets/verilog-BQ8w6xss.js +1 -0
  728. pythinker_code/web/static/assets/vesper-DU1UobuO.js +1 -0
  729. pythinker_code/web/static/assets/vhdl-CeAyd5Ju.js +1 -0
  730. pythinker_code/web/static/assets/viml-CJc9bBzg.js +1 -0
  731. pythinker_code/web/static/assets/vitesse-black-Bkuqu6BP.js +1 -0
  732. pythinker_code/web/static/assets/vitesse-dark-D0r3Knsf.js +1 -0
  733. pythinker_code/web/static/assets/vitesse-light-CVO1_9PV.js +1 -0
  734. pythinker_code/web/static/assets/vue-DN_0RTcg.js +1 -0
  735. pythinker_code/web/static/assets/vue-html-AaS7Mt5G.js +1 -0
  736. pythinker_code/web/static/assets/vue-vine-CQOfvN7w.js +1 -0
  737. pythinker_code/web/static/assets/vyper-CDx5xZoG.js +1 -0
  738. pythinker_code/web/static/assets/wasm-CG6Dc4jp.js +1 -0
  739. pythinker_code/web/static/assets/wasm-MzD3tlZU.js +1 -0
  740. pythinker_code/web/static/assets/wenyan-BV7otONQ.js +1 -0
  741. pythinker_code/web/static/assets/wgsl-Dx-B1_4e.js +1 -0
  742. pythinker_code/web/static/assets/wikitext-BhOHFoWU.js +1 -0
  743. pythinker_code/web/static/assets/wit-5i3qLPDT.js +1 -0
  744. pythinker_code/web/static/assets/wolfram-lXgVvXCa.js +1 -0
  745. pythinker_code/web/static/assets/xml-sdJ4AIDG.js +1 -0
  746. pythinker_code/web/static/assets/xsl-CtQFsRM5.js +1 -0
  747. pythinker_code/web/static/assets/xychartDiagram-PRI3JC2R-DkqqHNLh.js +7 -0
  748. pythinker_code/web/static/assets/yaml-Buea-lGh.js +1 -0
  749. pythinker_code/web/static/assets/zenscript-DVFEvuxE.js +1 -0
  750. pythinker_code/web/static/assets/zig-VOosw3JB.js +1 -0
  751. pythinker_code/web/static/brand/apple-touch-icon.png +0 -0
  752. pythinker_code/web/static/brand/arctecture.webp +0 -0
  753. pythinker_code/web/static/brand/bimi-logo.svg +46 -0
  754. pythinker_code/web/static/brand/favicon.ico +0 -0
  755. pythinker_code/web/static/brand/fonts/dm-sans-latin-ext.woff2 +0 -0
  756. pythinker_code/web/static/brand/fonts/dm-sans-latin.woff2 +0 -0
  757. pythinker_code/web/static/brand/fonts/instrument-sans-latin-ext.woff2 +0 -0
  758. pythinker_code/web/static/brand/fonts/instrument-sans-latin.woff2 +0 -0
  759. pythinker_code/web/static/brand/fonts/instrument-serif-latin-ext.woff2 +0 -0
  760. pythinker_code/web/static/brand/fonts/instrument-serif-latin.woff2 +0 -0
  761. pythinker_code/web/static/brand/fonts/libre-baskerville-italic-latin-ext.woff2 +0 -0
  762. pythinker_code/web/static/brand/fonts/libre-baskerville-italic-latin.woff2 +0 -0
  763. pythinker_code/web/static/brand/fonts/libre-baskerville-latin-ext.woff2 +0 -0
  764. pythinker_code/web/static/brand/fonts/libre-baskerville-latin.woff2 +0 -0
  765. pythinker_code/web/static/brand/fonts/roboto-latin-ext.woff2 +0 -0
  766. pythinker_code/web/static/brand/fonts/roboto-latin.woff2 +0 -0
  767. pythinker_code/web/static/brand/icon-192.png +0 -0
  768. pythinker_code/web/static/brand/icon-512.png +0 -0
  769. pythinker_code/web/static/brand/icon.svg +1 -0
  770. pythinker_code/web/static/brand/logo.png +0 -0
  771. pythinker_code/web/static/brand/pythinker_animated.svg +79 -0
  772. pythinker_code/web/static/brand/robots.txt +4 -0
  773. pythinker_code/web/static/index.html +15 -0
  774. pythinker_code/web/static/logo.png +0 -0
  775. pythinker_code/web/store/__init__.py +1 -0
  776. pythinker_code/web/store/sessions.py +433 -0
  777. pythinker_code/wire/__init__.py +148 -0
  778. pythinker_code/wire/file.py +151 -0
  779. pythinker_code/wire/jsonrpc.py +263 -0
  780. pythinker_code/wire/protocol.py +2 -0
  781. pythinker_code/wire/root_hub.py +27 -0
  782. pythinker_code/wire/serde.py +26 -0
  783. pythinker_code/wire/server.py +1072 -0
  784. pythinker_code/wire/types.py +698 -0
  785. pythinker_code-0.8.0.dist-info/METADATA +706 -0
  786. pythinker_code-0.8.0.dist-info/RECORD +790 -0
  787. pythinker_code-0.8.0.dist-info/WHEEL +4 -0
  788. pythinker_code-0.8.0.dist-info/entry_points.txt +4 -0
  789. pythinker_code-0.8.0.dist-info/licenses/LICENSE +202 -0
  790. pythinker_code-0.8.0.dist-info/licenses/NOTICE +14 -0
@@ -0,0 +1,1433 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from collections.abc import Awaitable, Callable
5
+ from typing import TYPE_CHECKING, Any, cast
6
+
7
+ from prompt_toolkit.shortcuts.choice_input import ChoiceInput
8
+
9
+ from pythinker_code.auth.platforms import get_platform_name_for_provider, refresh_managed_models
10
+ from pythinker_code.cli import Reload, SwitchToVis, SwitchToWeb
11
+ from pythinker_code.config import load_config, save_config
12
+ from pythinker_code.exception import ConfigError
13
+ from pythinker_code.session import Session
14
+ from pythinker_code.soul.pythinkersoul import PythinkerSoul
15
+ from pythinker_code.ui.shell.console import console
16
+ from pythinker_code.ui.shell.mcp_status import render_mcp_console
17
+ from pythinker_code.ui.shell.task_browser import TaskBrowserApp
18
+ from pythinker_code.utils.changelog import CHANGELOG
19
+ from pythinker_code.utils.logging import logger
20
+ from pythinker_code.utils.slashcmd import SlashCommand, SlashCommandRegistry
21
+
22
+ if TYPE_CHECKING:
23
+ from pythinker_code.ui.shell import Shell
24
+
25
+ type ShellSlashCmdFunc = Callable[[Shell, str], None | Awaitable[None]]
26
+ """
27
+ A function that runs as a Shell-level slash command.
28
+
29
+ Raises:
30
+ Reload: When the configuration should be reloaded.
31
+ """
32
+
33
+
34
+ registry = SlashCommandRegistry[ShellSlashCmdFunc]()
35
+ shell_mode_registry = SlashCommandRegistry[ShellSlashCmdFunc]()
36
+
37
+
38
+ def ensure_pythinker_soul(app: Shell) -> PythinkerSoul | None:
39
+ if not isinstance(app.soul, PythinkerSoul):
40
+ console.print("[red]PythinkerSoul required[/red]")
41
+ return None
42
+ return app.soul
43
+
44
+
45
+ @registry.command(aliases=["quit"])
46
+ @shell_mode_registry.command(aliases=["quit"])
47
+ def exit(app: Shell, args: str):
48
+ """Exit the application"""
49
+ # should be handled by `Shell`
50
+ raise NotImplementedError
51
+
52
+
53
+ SKILL_COMMAND_PREFIX = "skill:"
54
+
55
+ _KEYBOARD_SHORTCUTS = [
56
+ ("Ctrl-X", "Toggle agent/shell mode"),
57
+ ("Shift-Tab", "Toggle plan mode (read-only research)"),
58
+ ("Ctrl-O", "Edit in external editor ($VISUAL/$EDITOR)"),
59
+ ("Ctrl-J / Alt-Enter", "Insert newline"),
60
+ ("Ctrl-V", "Paste (supports images)"),
61
+ ("Ctrl-D", "Exit"),
62
+ ("Ctrl-C", "Interrupt"),
63
+ ]
64
+
65
+
66
+ @registry.command(aliases=["h", "?"])
67
+ @shell_mode_registry.command(aliases=["h", "?"])
68
+ def help(app: Shell, args: str):
69
+ """Show help information"""
70
+ from rich.console import Group, RenderableType
71
+ from rich.text import Text
72
+
73
+ from pythinker_code.utils.rich.columns import BulletColumns
74
+
75
+ def section(title: str, items: list[tuple[str, str]], color: str) -> BulletColumns:
76
+ lines: list[RenderableType] = [Text.from_markup(f"[bold]{title}:[/bold]")]
77
+ for name, desc in items:
78
+ lines.append(
79
+ BulletColumns(
80
+ Text.from_markup(f"[{color}]{name}[/{color}]: [grey50]{desc}[/grey50]"),
81
+ bullet_style=color,
82
+ )
83
+ )
84
+ return BulletColumns(Group(*lines))
85
+
86
+ renderables: list[RenderableType] = []
87
+ renderables.append(
88
+ BulletColumns(
89
+ Group(
90
+ Text.from_markup("[grey50]Help! I need somebody. Help! Not just anybody.[/grey50]"),
91
+ Text.from_markup("[grey50]Help! You know I need someone. Help![/grey50]"),
92
+ Text.from_markup("[grey50]\u2015 The Beatles, [italic]Help![/italic][/grey50]"),
93
+ ),
94
+ bullet_style="grey50",
95
+ )
96
+ )
97
+ renderables.append(
98
+ BulletColumns(
99
+ Text(
100
+ "Sure, Pythinker is ready to help! "
101
+ "Just send me messages and I will help you get things done!"
102
+ ),
103
+ )
104
+ )
105
+
106
+ commands: list[SlashCommand[Any]] = []
107
+ skills: list[SlashCommand[Any]] = []
108
+ for cmd in app.available_slash_commands.values():
109
+ if cmd.name.startswith(SKILL_COMMAND_PREFIX):
110
+ skills.append(cmd)
111
+ else:
112
+ commands.append(cmd)
113
+
114
+ renderables.append(section("Keyboard shortcuts", _KEYBOARD_SHORTCUTS, "yellow"))
115
+ renderables.append(
116
+ section(
117
+ "Slash commands",
118
+ [(c.slash_name(), c.description) for c in sorted(commands, key=lambda c: c.name)],
119
+ "blue",
120
+ )
121
+ )
122
+ if skills:
123
+ renderables.append(
124
+ section(
125
+ "Skills",
126
+ [(c.slash_name(), c.description) for c in sorted(skills, key=lambda c: c.name)],
127
+ "cyan",
128
+ )
129
+ )
130
+
131
+ with console.pager(styles=True):
132
+ console.print(Group(*renderables))
133
+
134
+
135
+ @registry.command
136
+ @shell_mode_registry.command
137
+ def version(app: Shell, args: str):
138
+ """Show version information"""
139
+ from pythinker_code.constant import VERSION
140
+
141
+ console.print(f"pythinker, version {VERSION}")
142
+
143
+
144
+ @registry.command
145
+ @shell_mode_registry.command
146
+ def agents(app: Shell, args: str):
147
+ """List available subagent types"""
148
+ from rich import box
149
+ from rich.panel import Panel
150
+ from rich.table import Table
151
+ from rich.text import Text
152
+
153
+ soul = ensure_pythinker_soul(app)
154
+ if soul is None:
155
+ return
156
+
157
+ labor_market = getattr(soul.runtime, "labor_market", None)
158
+ builtin_types = getattr(labor_market, "builtin_types", {}) or {}
159
+ type_defs = sorted(builtin_types.values(), key=lambda item: item.name)
160
+ if not type_defs:
161
+ console.print("[yellow]No subagents are registered for this agent.[/yellow]")
162
+ return
163
+
164
+ table = Table.grid(expand=True)
165
+ table.add_column(ratio=1, no_wrap=True)
166
+ table.add_column(ratio=4)
167
+ table.add_column(ratio=1, no_wrap=True)
168
+ table.add_column(ratio=2, no_wrap=True)
169
+ table.add_row(
170
+ Text("agent", style="bold cyan"),
171
+ Text("when to use", style="bold"),
172
+ Text("model", style="bold"),
173
+ Text("tools", style="bold"),
174
+ )
175
+ for type_def in type_defs:
176
+ tool_policy = getattr(type_def, "tool_policy", None)
177
+ tool_label = (
178
+ f"allow {len(tool_policy.tools)}"
179
+ if tool_policy is not None and tool_policy.mode == "allowlist"
180
+ else "inherit"
181
+ )
182
+ table.add_row(
183
+ Text(type_def.name, style="cyan"),
184
+ Text(type_def.when_to_use or type_def.description or "—", style="grey70"),
185
+ Text(type_def.default_model or "inherit", style="grey58"),
186
+ Text(tool_label, style="grey58"),
187
+ )
188
+
189
+ footer = Text("Use the Agent tool with subagent_type=<agent>, or ask Pythinker to delegate.")
190
+ footer.stylize("grey58")
191
+ console.print(
192
+ Panel(
193
+ table,
194
+ title="[bold cyan]Agents[/bold cyan]",
195
+ subtitle=footer,
196
+ border_style="cyan",
197
+ box=box.ROUNDED,
198
+ )
199
+ )
200
+
201
+
202
+ @registry.command
203
+ async def model(app: Shell, args: str):
204
+ """Switch LLM model or thinking mode"""
205
+ from pythinker_code.llm import derive_model_capabilities
206
+
207
+ soul = ensure_pythinker_soul(app)
208
+ if soul is None:
209
+ return
210
+ config = soul.runtime.config
211
+
212
+ await refresh_managed_models(config)
213
+
214
+ if not config.models:
215
+ console.print('[yellow]No models configured, send "/login" to login.[/yellow]')
216
+ return
217
+
218
+ if not config.is_from_default_location:
219
+ console.print(
220
+ "[yellow]Model switching requires the default config file; "
221
+ "restart without --config/--config-file.[/yellow]"
222
+ )
223
+ return
224
+
225
+ # Find current model/thinking from runtime (may be overridden by --model/--thinking)
226
+ curr_model_cfg = soul.runtime.llm.model_config if soul.runtime.llm else None
227
+ curr_model_name: str | None = None
228
+ if curr_model_cfg is not None:
229
+ for name, model_cfg in config.models.items():
230
+ if model_cfg == curr_model_cfg:
231
+ curr_model_name = name
232
+ break
233
+ curr_thinking = soul.thinking
234
+
235
+ # Step 1: Pick a model — single grouped picker with type-to-filter.
236
+ from pythinker_code.ui.shell.model_picker import ModelPickerApp, build_provider_groups
237
+
238
+ groups = build_provider_groups(
239
+ config_models=config.models,
240
+ label_for=_provider_label,
241
+ )
242
+ selected_model_name = await ModelPickerApp(
243
+ groups=groups,
244
+ current_model_name=curr_model_name,
245
+ ).run()
246
+ if not selected_model_name:
247
+ return
248
+
249
+ selected_model_cfg = config.models[selected_model_name]
250
+ selected_provider = config.providers.get(selected_model_cfg.provider)
251
+ if selected_provider is None:
252
+ console.print(f"[red]Provider not found: {selected_model_cfg.provider}[/red]")
253
+ return
254
+
255
+ # Step 2: Determine thinking mode
256
+ capabilities = derive_model_capabilities(selected_model_cfg)
257
+ new_thinking: bool
258
+
259
+ if "always_thinking" in capabilities:
260
+ new_thinking = True
261
+ elif "thinking" in capabilities:
262
+ from pythinker_code.ui.shell.selectors.thinking import ThinkingLevel, run_thinking_selector
263
+
264
+ _curr_level: ThinkingLevel = "high" if curr_thinking else "off"
265
+ _level = await run_thinking_selector(
266
+ current_level=_curr_level,
267
+ available_levels=["off", "minimal", "low", "medium", "high", "xhigh"],
268
+ )
269
+ if _level is None:
270
+ return
271
+
272
+ new_thinking = _level != "off"
273
+ else:
274
+ new_thinking = False
275
+
276
+ # Check if anything changed
277
+ model_changed = curr_model_name != selected_model_name
278
+ thinking_changed = curr_thinking != new_thinking
279
+ selected_display = selected_model_cfg.display_name or selected_model_cfg.model
280
+
281
+ if not model_changed and not thinking_changed:
282
+ console.print(
283
+ f"[yellow]Already using {selected_display} "
284
+ f"with thinking {'on' if new_thinking else 'off'}.[/yellow]"
285
+ )
286
+ return
287
+
288
+ # Save and reload
289
+ prev_model = config.default_model
290
+ prev_thinking = config.default_thinking
291
+ config.default_model = selected_model_name
292
+ config.default_thinking = new_thinking
293
+ try:
294
+ config_for_save = load_config()
295
+ config_for_save.default_model = selected_model_name
296
+ config_for_save.default_thinking = new_thinking
297
+ save_config(config_for_save)
298
+ except (ConfigError, OSError) as exc:
299
+ config.default_model = prev_model
300
+ config.default_thinking = prev_thinking
301
+ console.print(f"[red]Failed to save config: {exc}[/red]")
302
+ return
303
+
304
+ from pythinker_code.telemetry import track
305
+
306
+ if model_changed:
307
+ track("model_switch", model=selected_model_name)
308
+ if thinking_changed:
309
+ track("thinking_toggle", enabled=new_thinking)
310
+ console.print(
311
+ f"[green]Switched to {selected_display} "
312
+ f"with thinking {'on' if new_thinking else 'off'}. "
313
+ "Reloading...[/green]"
314
+ )
315
+
316
+ # Pre-load LM Studio models so the user doesn't hit a 10-60s wait on
317
+ # the first message. Fire-and-forget on error — Reload still proceeds.
318
+ if model_changed and selected_model_cfg.provider == "managed:lm-studio":
319
+ await _preload_lm_studio_model(selected_provider, selected_model_cfg.model)
320
+
321
+ raise Reload(session_id=soul.runtime.session.id)
322
+
323
+
324
+ _PROVIDER_LABEL_OVERRIDES = {
325
+ "managed:minimax-anthropic": "MiniMax",
326
+ "managed:opencode-go-openai": "OpenCode Go (OpenAI)",
327
+ "managed:opencode-go-anthropic": "OpenCode Go (Anthropic)",
328
+ "managed:deepseek": "DeepSeek",
329
+ "managed:anthropic": "Anthropic",
330
+ "managed:openrouter": "OpenRouter",
331
+ }
332
+
333
+
334
+ def _provider_label(provider_key: str) -> str:
335
+ if name := get_platform_name_for_provider(provider_key):
336
+ return name
337
+ if name := _PROVIDER_LABEL_OVERRIDES.get(provider_key):
338
+ return name
339
+ # Fall back: strip "managed:" prefix and humanize.
340
+ raw = provider_key.removeprefix("managed:")
341
+ return raw.replace("-", " ").title() if raw else provider_key
342
+
343
+
344
+ async def _preload_lm_studio_model(provider: Any, model_id: str) -> None:
345
+ """Best-effort: ask LM Studio to load the model now."""
346
+ from rich.status import Status
347
+
348
+ from pythinker_code.auth.lm_studio import request_lm_studio_load
349
+
350
+ base_url = provider.base_url
351
+ api_key = provider.api_key.get_secret_value()
352
+ status_msg = f"Pre-loading {model_id} in LM Studio (this may take a moment)..."
353
+ status = Status(status_msg, console=console)
354
+ status.start()
355
+ try:
356
+ result = await request_lm_studio_load(base_url=base_url, model_id=model_id, api_key=api_key)
357
+ status.stop()
358
+ console.print(
359
+ f"[dim]LM Studio loaded {model_id} in "
360
+ f"{result.load_time_seconds:.1f}s (status={result.status}).[/dim]"
361
+ )
362
+ except Exception as exc:
363
+ status.stop()
364
+ console.print(
365
+ f"[yellow]LM Studio pre-load failed for {model_id}: {exc}[/yellow]\n"
366
+ "[dim]The chat will still try the model on first message.[/dim]"
367
+ )
368
+
369
+
370
+ @registry.command
371
+ @shell_mode_registry.command
372
+ async def editor(app: Shell, args: str):
373
+ """Set default external editor for Ctrl-O"""
374
+ from pythinker_code.utils.editor import get_editor_command
375
+
376
+ soul = ensure_pythinker_soul(app)
377
+ if soul is None:
378
+ return
379
+ config = soul.runtime.config
380
+ config_file = config.source_file
381
+ if config_file is None:
382
+ console.print(
383
+ "[yellow]Editor switching is unavailable with inline --config; "
384
+ "use --config-file to persist this setting.[/yellow]"
385
+ )
386
+ return
387
+
388
+ current_editor = config.default_editor
389
+
390
+ # If args provided directly, use as editor command
391
+ if args.strip():
392
+ new_editor = args.strip()
393
+ else:
394
+ options: list[tuple[str, str]] = [
395
+ ("code --wait", "VS Code (code --wait)"),
396
+ ("vim", "Vim"),
397
+ ("nano", "Nano"),
398
+ ("", "Auto-detect (use $VISUAL/$EDITOR)"),
399
+ ]
400
+ # Mark current selection
401
+ options = [
402
+ (val, label + (" ← current" if val == current_editor else "")) for val, label in options
403
+ ]
404
+
405
+ try:
406
+ choice = cast(
407
+ str | None,
408
+ await ChoiceInput(
409
+ message="Select an editor (↑↓ navigate, Enter select, Ctrl+C cancel):",
410
+ options=options,
411
+ default=(
412
+ current_editor
413
+ if current_editor in {v for v, _ in options}
414
+ else "code --wait"
415
+ ),
416
+ ).prompt_async(),
417
+ )
418
+ except (EOFError, KeyboardInterrupt):
419
+ return
420
+
421
+ if choice is None:
422
+ return
423
+ new_editor = choice
424
+
425
+ # Validate the editor binary is available
426
+ if new_editor:
427
+ import shlex
428
+ import shutil
429
+
430
+ try:
431
+ parts = shlex.split(new_editor)
432
+ except ValueError:
433
+ console.print(f"[red]Invalid editor command: {new_editor}[/red]")
434
+ return
435
+
436
+ binary = parts[0]
437
+ if not shutil.which(binary):
438
+ console.print(
439
+ f"[yellow]Warning: '{binary}' not found in PATH. "
440
+ f"Saving anyway — make sure it's installed before using Ctrl-O.[/yellow]"
441
+ )
442
+
443
+ if new_editor == current_editor:
444
+ console.print(f"[yellow]Editor is already set to: {new_editor or 'auto-detect'}[/yellow]")
445
+ return
446
+
447
+ # Save to disk
448
+ try:
449
+ config_for_save = load_config(config_file)
450
+ config_for_save.default_editor = new_editor
451
+ save_config(config_for_save, config_file)
452
+ except (ConfigError, OSError) as exc:
453
+ console.print(f"[red]Failed to save config: {exc}[/red]")
454
+ return
455
+
456
+ # Sync in-memory config so Ctrl-O picks it up immediately
457
+ config.default_editor = new_editor
458
+
459
+ if new_editor:
460
+ console.print(f"[green]Editor set to: {new_editor}[/green]")
461
+ else:
462
+ resolved = get_editor_command()
463
+ label = " ".join(resolved) if resolved else "none"
464
+ console.print(f"[green]Editor set to auto-detect (resolved: {label})[/green]")
465
+
466
+
467
+ @registry.command(aliases=["release-notes"])
468
+ @shell_mode_registry.command(aliases=["release-notes"])
469
+ def changelog(app: Shell, args: str):
470
+ """Show release notes"""
471
+ from rich.console import Group, RenderableType
472
+ from rich.text import Text
473
+
474
+ from pythinker_code.utils.rich.columns import BulletColumns
475
+
476
+ renderables: list[RenderableType] = []
477
+ for ver, entry in CHANGELOG.items():
478
+ title = f"[bold]{ver}[/bold]"
479
+ if entry.description:
480
+ title += f": {entry.description}"
481
+
482
+ lines: list[RenderableType] = [Text.from_markup(title)]
483
+ for item in entry.entries:
484
+ if item.lower().startswith("lib:"):
485
+ continue
486
+ lines.append(
487
+ BulletColumns(
488
+ Text.from_markup(f"[grey50]{item}[/grey50]"),
489
+ bullet_style="grey50",
490
+ ),
491
+ )
492
+ renderables.append(BulletColumns(Group(*lines)))
493
+
494
+ with console.pager(styles=True):
495
+ console.print(Group(*renderables))
496
+
497
+
498
+ def _feedback_destination(soul: PythinkerSoul) -> tuple[str, dict[str, str]] | None:
499
+ """Resolve the endpoint and headers for explicit user feedback submissions."""
500
+ import os
501
+
502
+ from pythinker_code.auth import PYTHINKER_CODE_PLATFORM_ID
503
+ from pythinker_code.auth.platforms import get_platform_by_id, managed_provider_key
504
+
505
+ headers: dict[str, str] = {}
506
+ feedback_config = soul.runtime.config.feedback
507
+
508
+ endpoint_url = os.getenv("PYTHINKER_FEEDBACK_URL", "").strip()
509
+ if not endpoint_url:
510
+ endpoint_url = feedback_config.endpoint_url.strip()
511
+
512
+ if endpoint_url:
513
+ if feedback_config.custom_headers:
514
+ headers.update(feedback_config.custom_headers)
515
+ api_key = os.getenv("PYTHINKER_FEEDBACK_API_KEY", "").strip()
516
+ if not api_key and feedback_config.api_key is not None:
517
+ api_key = feedback_config.api_key.get_secret_value()
518
+ if api_key:
519
+ headers["Authorization"] = f"Bearer {api_key}"
520
+ return endpoint_url, headers
521
+
522
+ pythinker_platform = get_platform_by_id(PYTHINKER_CODE_PLATFORM_ID)
523
+ if pythinker_platform is None:
524
+ return None
525
+
526
+ provider = soul.runtime.config.providers.get(managed_provider_key(PYTHINKER_CODE_PLATFORM_ID))
527
+ if provider is not None:
528
+ if provider.custom_headers:
529
+ headers.update(provider.custom_headers)
530
+ api_key = provider.api_key.get_secret_value()
531
+ if provider.oauth is not None:
532
+ api_key = soul.runtime.oauth.resolve_api_key(provider.api_key, provider.oauth)
533
+ if api_key:
534
+ headers["Authorization"] = f"Bearer {api_key}"
535
+
536
+ return f"{pythinker_platform.base_url.rstrip('/')}/feedback", headers
537
+
538
+
539
+ def _feedback_github_config(soul: PythinkerSoul) -> tuple[str, str] | None:
540
+ """Return GitHub OAuth client_id and repo when direct user-owned issues are enabled."""
541
+ import os
542
+
543
+ feedback_config = soul.runtime.config.feedback
544
+ client_id = os.getenv("PYTHINKER_FEEDBACK_GITHUB_CLIENT_ID", "").strip()
545
+ if not client_id:
546
+ client_id = feedback_config.github_client_id.strip()
547
+ repo = os.getenv("PYTHINKER_FEEDBACK_GITHUB_REPO", "").strip()
548
+ if not repo:
549
+ repo = feedback_config.github_repo.strip()
550
+ if not client_id or not repo:
551
+ return None
552
+ return client_id, repo
553
+
554
+
555
+ def _feedback_issue_title(payload: dict[str, str | None]) -> str:
556
+ version = f" {payload['version']}" if payload.get("version") else ""
557
+ session = payload.get("session_id") or ""
558
+ suffix = f" ({session[:8]})" if session else ""
559
+ return f"[Pythinker CLI] Feedback{version}{suffix}"
560
+
561
+
562
+ def _feedback_issue_body(payload: dict[str, str | None]) -> str:
563
+ return "\n".join(
564
+ [
565
+ "## User submission",
566
+ "",
567
+ payload.get("content") or "_(no comment)_",
568
+ "",
569
+ "## Context",
570
+ "",
571
+ "- Type: feedback",
572
+ f"- Session: {payload.get('session_id') or 'unknown'}",
573
+ f"- Version: {payload.get('version') or 'unknown'}",
574
+ f"- OS: {payload.get('os') or 'unknown'}",
575
+ f"- Model: {payload.get('model') or 'unknown'}",
576
+ ]
577
+ )
578
+
579
+
580
+ @registry.command
581
+ @shell_mode_registry.command
582
+ async def feedback(app: Shell, args: str):
583
+ """Submit feedback to make Pythinker CLI better"""
584
+ import platform
585
+ import webbrowser
586
+
587
+ import aiohttp
588
+
589
+ from pythinker_code.constant import VERSION
590
+ from pythinker_code.ui.shell.oauth import current_model_key
591
+ from pythinker_code.utils.aiohttp import new_client_session
592
+
593
+ ISSUE_URL = "https://github.com/mohamed-elkholy95/Pythinker-Code/issues"
594
+
595
+ def _fallback_to_issues():
596
+ if not webbrowser.open(ISSUE_URL):
597
+ console.print(f"Please submit feedback at [underline]{ISSUE_URL}[/underline].")
598
+
599
+ soul = ensure_pythinker_soul(app)
600
+ if soul is None:
601
+ _fallback_to_issues()
602
+ return
603
+
604
+ github_config = _feedback_github_config(soul)
605
+ destination = None if github_config is not None else _feedback_destination(soul)
606
+ if github_config is None and destination is None:
607
+ _fallback_to_issues()
608
+ return
609
+
610
+ from prompt_toolkit import PromptSession
611
+
612
+ prompt_session: PromptSession[str] = PromptSession()
613
+ try:
614
+ content = await prompt_session.prompt_async("Enter your feedback: ")
615
+ except (EOFError, KeyboardInterrupt):
616
+ console.print("[grey50]Feedback cancelled.[/grey50]")
617
+ return
618
+
619
+ content = content.strip()
620
+ if not content:
621
+ console.print("[yellow]Feedback cannot be empty.[/yellow]")
622
+ return
623
+
624
+ payload = {
625
+ "session_id": soul.runtime.session.id,
626
+ "content": content,
627
+ "version": VERSION,
628
+ "os": f"{platform.system()} {platform.release()}",
629
+ "model": current_model_key(soul),
630
+ }
631
+
632
+ if github_config is not None:
633
+ client_id, repo = github_config
634
+ from pythinker_code.auth.github_feedback import (
635
+ GitHubFeedbackError,
636
+ create_github_issue,
637
+ load_github_feedback_token,
638
+ login_github_feedback,
639
+ star_github_repo,
640
+ )
641
+
642
+ try:
643
+ token = load_github_feedback_token()
644
+ if token is None:
645
+ console.print("[cyan]GitHub login required to create the issue as you.[/cyan]")
646
+ async for event in login_github_feedback(client_id):
647
+ if event.type == "waiting":
648
+ console.print(event.message, markup=False)
649
+ elif event.type in {"verification_url", "success", "error"}:
650
+ style = None
651
+ if event.type == "success":
652
+ style = "green"
653
+ elif event.type == "error":
654
+ style = "red"
655
+ console.print(event.message, markup=False, style=style)
656
+ token = load_github_feedback_token()
657
+ if token is None:
658
+ console.print("[red]GitHub login did not produce a usable token.[/red]")
659
+ return
660
+ with console.status("[cyan]Creating GitHub issue...[/cyan]"):
661
+ issue = await create_github_issue(
662
+ repo,
663
+ token,
664
+ title=_feedback_issue_title(payload),
665
+ body=_feedback_issue_body(payload),
666
+ )
667
+ from pythinker_code.telemetry import track
668
+
669
+ track("feedback_submitted", destination="github")
670
+ if issue.html_url:
671
+ console.print(f"[green]GitHub issue created:[/green] {issue.html_url}")
672
+ else:
673
+ console.print("[green]GitHub issue created.[/green]")
674
+
675
+ try:
676
+ star_answer = await prompt_session.prompt_async(
677
+ "Do you like Pythinker CLI? Star the GitHub repo? [y/N]: "
678
+ )
679
+ except (EOFError, KeyboardInterrupt):
680
+ star_answer = ""
681
+ if star_answer.strip().lower() in {"y", "yes"}:
682
+ try:
683
+ with console.status("[cyan]Starring GitHub repo...[/cyan]"):
684
+ await star_github_repo(repo, token)
685
+ track("github_repo_starred")
686
+ console.print("[green]Thanks for starring the repo![/green]")
687
+ except (GitHubFeedbackError, TimeoutError, aiohttp.ClientError) as e:
688
+ console.print(f"[yellow]Could not star the repo: {e}[/yellow]")
689
+ except (GitHubFeedbackError, TimeoutError, aiohttp.ClientError) as e:
690
+ console.print(f"[red]Failed to create GitHub issue: {e}[/red]")
691
+ _fallback_to_issues()
692
+ return
693
+
694
+ assert destination is not None
695
+ feedback_url, headers = destination
696
+
697
+ with console.status("[cyan]Submitting feedback...[/cyan]"):
698
+ try:
699
+ async with (
700
+ new_client_session() as session,
701
+ session.post(
702
+ feedback_url,
703
+ json=payload,
704
+ headers=headers,
705
+ raise_for_status=True,
706
+ ),
707
+ ):
708
+ pass
709
+ session_id = soul.runtime.session.id
710
+ from pythinker_code.telemetry import track
711
+
712
+ track("feedback_submitted")
713
+ console.print(
714
+ f"[green]Feedback submitted, thank you! Your session ID is: {session_id}[/green]"
715
+ )
716
+ except TimeoutError:
717
+ console.print("[red]Feedback submission timed out.[/red]")
718
+ _fallback_to_issues()
719
+ except aiohttp.ClientError as e:
720
+ status = getattr(e, "status", None)
721
+ if status:
722
+ msg = f"Failed to submit feedback (HTTP {status})."
723
+ else:
724
+ msg = "Network error, failed to submit feedback."
725
+ console.print(f"[red]{msg}[/red]")
726
+ _fallback_to_issues()
727
+
728
+
729
+ @registry.command(aliases=["report-error", "report"])
730
+ @shell_mode_registry.command(aliases=["report-error", "report"])
731
+ async def report_error(app: Shell, args: str):
732
+ """Submit a report about an error you hit, with a snapshot of recent failures."""
733
+ import platform
734
+ import webbrowser
735
+
736
+ import aiohttp
737
+
738
+ from pythinker_code.constant import VERSION
739
+ from pythinker_code.telemetry.errors import clear_recent_errors, recent_errors
740
+ from pythinker_code.ui.shell.oauth import current_model_key
741
+ from pythinker_code.utils.aiohttp import new_client_session
742
+
743
+ ISSUE_URL = "https://github.com/mohamed-elkholy95/Pythinker-Code/issues"
744
+
745
+ def _fallback_to_issues():
746
+ if not webbrowser.open(ISSUE_URL):
747
+ console.print(f"Please file the report at [underline]{ISSUE_URL}[/underline].")
748
+
749
+ soul = ensure_pythinker_soul(app)
750
+ if soul is None:
751
+ _fallback_to_issues()
752
+ return
753
+
754
+ destination = _feedback_destination(soul)
755
+ if destination is None:
756
+ _fallback_to_issues()
757
+ return
758
+ feedback_url, headers = destination
759
+
760
+ errors = recent_errors()
761
+ if errors:
762
+ console.print("[bold]Recent errors this session (most recent last):[/bold]")
763
+ for i, err in enumerate(errors, start=1):
764
+ tool_part = f" (tool={err.tool})" if err.tool else ""
765
+ console.print(f" [dim]{i}.[/dim] [cyan]{err.site}[/cyan]{tool_part}: {err.exc_class}")
766
+ else:
767
+ console.print(
768
+ "[grey50]No errors recorded this session. "
769
+ "You can still attach a comment describing what went wrong.[/grey50]"
770
+ )
771
+
772
+ from prompt_toolkit import PromptSession
773
+
774
+ prompt_session: PromptSession[str] = PromptSession()
775
+ try:
776
+ comment = await prompt_session.prompt_async("Describe what went wrong (Ctrl-C to cancel): ")
777
+ except (EOFError, KeyboardInterrupt):
778
+ console.print("[grey50]Report cancelled.[/grey50]")
779
+ return
780
+
781
+ comment = comment.strip()
782
+ if not comment and not errors:
783
+ console.print("[yellow]Nothing to report. Cancelled.[/yellow]")
784
+ return
785
+
786
+ payload = {
787
+ "session_id": soul.runtime.session.id,
788
+ "type": "error",
789
+ "content": comment,
790
+ "version": VERSION,
791
+ "os": f"{platform.system()} {platform.release()}",
792
+ "model": current_model_key(soul),
793
+ "recent_errors": [
794
+ {
795
+ "timestamp": err.timestamp,
796
+ "site": err.site,
797
+ "exc_class": err.exc_class,
798
+ "message": err.message,
799
+ "tool": err.tool,
800
+ }
801
+ for err in errors
802
+ ],
803
+ }
804
+
805
+ with console.status("[cyan]Submitting error report...[/cyan]"):
806
+ try:
807
+ async with (
808
+ new_client_session() as session,
809
+ session.post(
810
+ feedback_url,
811
+ json=payload,
812
+ headers=headers,
813
+ raise_for_status=True,
814
+ ),
815
+ ):
816
+ pass
817
+ from pythinker_code.telemetry import track
818
+
819
+ track("error_report_submitted", error_count=len(errors), has_comment=bool(comment))
820
+ clear_recent_errors()
821
+ console.print(
822
+ f"[green]Error report submitted. Session ID: {soul.runtime.session.id}[/green]"
823
+ )
824
+ except TimeoutError:
825
+ console.print("[red]Submission timed out.[/red]")
826
+ _fallback_to_issues()
827
+ except aiohttp.ClientError as e:
828
+ status = getattr(e, "status", None)
829
+ msg = (
830
+ f"Failed to submit error report (HTTP {status})."
831
+ if status
832
+ else "Network error, failed to submit error report."
833
+ )
834
+ console.print(f"[red]{msg}[/red]")
835
+ _fallback_to_issues()
836
+
837
+
838
+ @registry.command(aliases=["reset"])
839
+ async def clear(app: Shell, args: str):
840
+ """Clear the context"""
841
+ if ensure_pythinker_soul(app) is None:
842
+ return
843
+ from pythinker_code.telemetry import track
844
+
845
+ track("clear")
846
+ await app.run_soul_command("/clear")
847
+ raise Reload()
848
+
849
+
850
+ @registry.command
851
+ async def new(app: Shell, args: str):
852
+ """Start a new session"""
853
+ soul = ensure_pythinker_soul(app)
854
+ if soul is None:
855
+ return
856
+ current_session = soul.runtime.session
857
+ work_dir = current_session.work_dir
858
+ # Clean up the current session if it has no content, so that chaining
859
+ # /new commands (or switching away before the first message) does not
860
+ # leave orphan empty session directories on disk.
861
+ if current_session.is_empty():
862
+ await current_session.delete()
863
+ session = await Session.create(work_dir)
864
+ from pythinker_code.telemetry import track
865
+
866
+ track("session_new")
867
+ console.print("[green]New session created. Switching...[/green]")
868
+ raise Reload(session_id=session.id)
869
+
870
+
871
+ @registry.command(name="title", aliases=["rename"])
872
+ async def title(app: Shell, args: str):
873
+ """Set or show the session title"""
874
+ soul = ensure_pythinker_soul(app)
875
+ if soul is None:
876
+ return
877
+ session = soul.runtime.session
878
+ if not args.strip():
879
+ console.print(f"Session title: [bold]{session.title}[/bold]")
880
+ return
881
+
882
+ from pythinker_code.session_state import load_session_state, save_session_state
883
+
884
+ new_title = args.strip()[:200]
885
+ # Read-modify-write: load fresh state to avoid overwriting concurrent web changes
886
+ fresh = load_session_state(session.dir)
887
+ fresh.custom_title = new_title
888
+ fresh.title_generated = True
889
+ save_session_state(fresh, session.dir)
890
+ session.state.custom_title = new_title
891
+ session.state.title_generated = True
892
+ session.title = new_title
893
+ console.print(f"[green]Session title set to: {new_title}[/green]")
894
+
895
+
896
+ @registry.command(name="sessions", aliases=["resume"])
897
+ async def list_sessions(app: Shell, args: str):
898
+ """List sessions and resume optionally"""
899
+ import shlex
900
+
901
+ from pythinker_code.ui.shell.session_picker import SessionPickerApp
902
+
903
+ soul = ensure_pythinker_soul(app)
904
+ if soul is None:
905
+ return
906
+
907
+ current_session = soul.runtime.session
908
+ result = await SessionPickerApp(
909
+ work_dir=current_session.work_dir,
910
+ current_session=current_session,
911
+ ).run()
912
+
913
+ if result is None:
914
+ return
915
+
916
+ selection, selected_work_dir = result
917
+
918
+ if selection == current_session.id:
919
+ console.print("[yellow]You are already in this session.[/yellow]")
920
+ return
921
+
922
+ if selected_work_dir != current_session.work_dir:
923
+ cmd = f"pythinker --work-dir {shlex.quote(str(selected_work_dir))} --session {selection}"
924
+ console.print(f"[yellow]Session is in a different directory. Run:[/yellow]\n {cmd}")
925
+ return
926
+
927
+ from pythinker_code.telemetry import track
928
+
929
+ track("session_resume")
930
+ console.print(f"[green]Switching to session {selection}...[/green]")
931
+ raise Reload(session_id=selection)
932
+
933
+
934
+ @registry.command(name="task")
935
+ @shell_mode_registry.command(name="task")
936
+ async def task(app: Shell, args: str):
937
+ """Browse and manage background tasks"""
938
+ soul = ensure_pythinker_soul(app)
939
+ if soul is None:
940
+ return
941
+ if args.strip():
942
+ console.print('[yellow]Usage: "/task" opens the interactive task browser.[/yellow]')
943
+ return
944
+ if soul.runtime.role != "root":
945
+ console.print("[yellow]Background tasks are only available from the root agent.[/yellow]")
946
+ return
947
+
948
+ await TaskBrowserApp(soul).run()
949
+
950
+
951
+ @registry.command
952
+ @shell_mode_registry.command
953
+ async def theme(app: Shell, args: str) -> None:
954
+ """Switch terminal color theme — interactive picker when no args given"""
955
+ from pythinker_code.ui.theme import get_active_theme
956
+
957
+ soul = ensure_pythinker_soul(app)
958
+ if soul is None:
959
+ return
960
+
961
+ current = get_active_theme()
962
+ arg = args.strip().lower()
963
+
964
+ if not arg:
965
+ from pythinker_code.ui.shell.selectors.theme import run_theme_selector
966
+
967
+ chosen = await run_theme_selector(
968
+ current_theme=current,
969
+ available_themes=["dark", "light"],
970
+ )
971
+ if chosen is None or chosen == current:
972
+ return
973
+ arg = chosen
974
+
975
+ if arg not in ("dark", "light"):
976
+ console.print(f"[red]Unknown theme: {arg}. Use 'dark' or 'light'.[/red]")
977
+ return
978
+
979
+ if arg == current:
980
+ console.print(f"[yellow]Already using {arg} theme.[/yellow]")
981
+ return
982
+
983
+ config_file = soul.runtime.config.source_file
984
+ if config_file is None:
985
+ console.print(
986
+ "[yellow]Theme switching requires a config file; "
987
+ "restart without --config to persist this setting.[/yellow]"
988
+ )
989
+ return
990
+
991
+ try:
992
+ config_for_save = load_config(config_file)
993
+ config_for_save.theme = arg # type: ignore[assignment]
994
+ save_config(config_for_save, config_file)
995
+ except (ConfigError, OSError) as exc:
996
+ console.print(f"[red]Failed to save config: {exc}[/red]")
997
+ return
998
+
999
+ from pythinker_code.telemetry import track
1000
+
1001
+ track("theme_switch", theme=arg)
1002
+ console.print(f"[green]Switched to {arg} theme. Reloading...[/green]")
1003
+ raise Reload(session_id=soul.runtime.session.id)
1004
+
1005
+
1006
+ @registry.command
1007
+ @shell_mode_registry.command
1008
+ async def thinking(app: Shell, args: str) -> None:
1009
+ """Switch thinking level — interactive picker"""
1010
+ soul = ensure_pythinker_soul(app)
1011
+ if soul is None:
1012
+ return
1013
+
1014
+ from pythinker_code.ui.shell.selectors.thinking import ThinkingLevel, run_thinking_selector
1015
+
1016
+ curr_level: ThinkingLevel = "high" if soul.thinking else "off"
1017
+ level = await run_thinking_selector(
1018
+ current_level=curr_level,
1019
+ available_levels=["off", "minimal", "low", "medium", "high", "xhigh"],
1020
+ )
1021
+ if level is None:
1022
+ return
1023
+
1024
+ new_thinking = level != "off"
1025
+ if new_thinking == soul.thinking:
1026
+ console.print("[yellow]Thinking setting unchanged.[/yellow]")
1027
+ return
1028
+
1029
+ config_file = soul.runtime.config.source_file
1030
+ if config_file is None:
1031
+ console.print(
1032
+ "[yellow]Thinking requires a config file; "
1033
+ "restart without --config to persist this setting.[/yellow]"
1034
+ )
1035
+ return
1036
+
1037
+ try:
1038
+ config_for_save = load_config(config_file)
1039
+ config_for_save.default_thinking = new_thinking
1040
+ save_config(config_for_save, config_file)
1041
+ except (ConfigError, OSError) as exc:
1042
+ console.print(f"[red]Failed to save config: {exc}[/red]")
1043
+ return
1044
+
1045
+ from pythinker_code.telemetry import track
1046
+
1047
+ track("thinking_toggle", enabled=new_thinking)
1048
+ console.print(
1049
+ f"[green]Thinking {'enabled' if new_thinking else 'disabled'}. Reloading...[/green]"
1050
+ )
1051
+ raise Reload(session_id=soul.runtime.session.id)
1052
+
1053
+
1054
+ @registry.command
1055
+ @shell_mode_registry.command
1056
+ def keys(app: Shell, args: str):
1057
+ """List keyboard shortcuts (semantic keymap)"""
1058
+ from rich.console import Group, RenderableType
1059
+ from rich.table import Table
1060
+ from rich.text import Text
1061
+
1062
+ from pythinker_code.ui.shell.keymap import all_keybindings
1063
+
1064
+ bindings = all_keybindings()
1065
+ if not bindings:
1066
+ console.print("[yellow]No keybindings registered.[/yellow]")
1067
+ return
1068
+
1069
+ table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
1070
+ table.add_column("ID", style="cyan", no_wrap=True)
1071
+ table.add_column("Keys", style="bold")
1072
+
1073
+ for name in sorted(bindings):
1074
+ keys_text = "/".join(bindings[name])
1075
+ table.add_row(name, keys_text)
1076
+
1077
+ blocks: list[RenderableType] = [
1078
+ Text.from_markup("[bold]Keyboard shortcuts[/bold]"),
1079
+ table,
1080
+ ]
1081
+ console.print(Group(*blocks))
1082
+
1083
+
1084
+ @registry.command
1085
+ @shell_mode_registry.command
1086
+ def tui(app: Shell, args: str):
1087
+ """Show or set the TUI style: card or pythinker"""
1088
+ from pythinker_code.ui.tui_config import get_active_tui_style, set_active_tui_style
1089
+
1090
+ soul = ensure_pythinker_soul(app)
1091
+ if soul is None:
1092
+ return
1093
+
1094
+ current = get_active_tui_style()
1095
+ arg = args.strip().lower()
1096
+
1097
+ # Accept "/tui card", "/tui style card", "/tui set card" — all natural shapes.
1098
+ parts = arg.split()
1099
+ if parts and parts[0] in ("style", "set"):
1100
+ parts = parts[1:]
1101
+ target = parts[0] if parts else ""
1102
+
1103
+ if not target:
1104
+ console.print(f"Current TUI style: [bold]{current}[/bold]")
1105
+ console.print("[grey50]Usage: /tui card | /tui pythinker[/grey50]")
1106
+ return
1107
+
1108
+ if target not in ("card", "pythinker"):
1109
+ console.print(f"[red]Unknown style: {target}. Use 'card' or 'pythinker'.[/red]")
1110
+ return
1111
+
1112
+ if target == current:
1113
+ console.print(f"[yellow]Already using {target} style.[/yellow]")
1114
+ return
1115
+
1116
+ config_file = soul.runtime.config.source_file
1117
+ if config_file is None:
1118
+ # Apply in-memory only — useful for one-off testing without a persisted config.
1119
+ set_active_tui_style(target)
1120
+ console.print(
1121
+ f"[green]Switched to {target} style for this session "
1122
+ "(no config file to persist).[/green]"
1123
+ )
1124
+ return
1125
+
1126
+ try:
1127
+ config_for_save = load_config(config_file)
1128
+ config_for_save.tui.style = target # type: ignore[assignment]
1129
+ save_config(config_for_save, config_file)
1130
+ except (ConfigError, OSError) as exc:
1131
+ console.print(f"[red]Failed to save config: {exc}[/red]")
1132
+ return
1133
+
1134
+ set_active_tui_style(target)
1135
+ if target == "card":
1136
+ # Make sure renderers are present immediately for current session.
1137
+ from pythinker_code.ui.shell.tool_renderers import register_builtin_renderers
1138
+
1139
+ register_builtin_renderers()
1140
+
1141
+ from pythinker_code.telemetry import track
1142
+
1143
+ track("tui_style_switch", style=target)
1144
+ console.print(f"[green]Switched to {target} style. Reloading...[/green]")
1145
+ raise Reload(session_id=soul.runtime.session.id)
1146
+
1147
+
1148
+ @registry.command
1149
+ @shell_mode_registry.command
1150
+ async def settings(app: Shell, args: str):
1151
+ """Open the interactive settings panel; use `/settings show` for read-only view"""
1152
+ from rich.console import Group, RenderableType
1153
+ from rich.table import Table
1154
+ from rich.text import Text
1155
+
1156
+ from pythinker_code.ui.theme import get_active_theme
1157
+ from pythinker_code.ui.tui_config import get_active_tui_style
1158
+
1159
+ soul = ensure_pythinker_soul(app)
1160
+ if soul is None:
1161
+ return
1162
+
1163
+ config = soul.runtime.config
1164
+ mode = args.strip().lower()
1165
+
1166
+ def print_settings_table() -> None:
1167
+ table = Table(show_header=False, box=None, padding=(0, 2))
1168
+ table.add_column(style="cyan", no_wrap=True)
1169
+ table.add_column()
1170
+
1171
+ table.add_row("Theme", get_active_theme())
1172
+ table.add_row("TUI style", get_active_tui_style())
1173
+ table.add_row("Default model", config.default_model or "(none)")
1174
+ table.add_row("Telemetry", "on" if config.telemetry else "off")
1175
+ table.add_row("Default thinking", "on" if config.default_thinking else "off")
1176
+ table.add_row("Show thinking stream", "on" if config.show_thinking_stream else "off")
1177
+ table.add_row("Default yolo", "on" if config.default_yolo else "off")
1178
+ table.add_row("Default plan mode", "on" if config.default_plan_mode else "off")
1179
+ if config.source_file is not None:
1180
+ table.add_row("Config file", str(config.source_file))
1181
+ else:
1182
+ table.add_row("Config file", "(none — runtime overrides only)")
1183
+
1184
+ blocks: list[RenderableType] = [Text.from_markup("[bold]Settings[/bold]"), table]
1185
+ console.print(Group(*blocks))
1186
+ console.print(
1187
+ "[grey50]Tip: /settings opens the interactive panel; "
1188
+ "/theme, /tui, /model, /keys for related controls.[/grey50]"
1189
+ )
1190
+
1191
+ if mode in {"show", "list", "view"}:
1192
+ print_settings_table()
1193
+ return
1194
+ if mode:
1195
+ console.print("[yellow]Usage: /settings [show|list][/yellow]")
1196
+ return
1197
+
1198
+ config_file = config.source_file
1199
+ if config_file is None:
1200
+ print_settings_table()
1201
+ console.print(
1202
+ "[yellow]Interactive settings require a config file; "
1203
+ "restart without --config text to persist settings.[/yellow]"
1204
+ )
1205
+ return
1206
+
1207
+ from pythinker_code.ui.shell.selectors.settings import (
1208
+ apply_settings_changes,
1209
+ run_settings_selector,
1210
+ )
1211
+
1212
+ try:
1213
+ config_for_save = load_config(config_file)
1214
+ except (ConfigError, OSError) as exc:
1215
+ console.print(f"[red]Failed to load config: {exc}[/red]")
1216
+ return
1217
+
1218
+ changes = await run_settings_selector(config_for_save)
1219
+ if changes is None:
1220
+ return
1221
+
1222
+ changed_ids = apply_settings_changes(config_for_save, changes)
1223
+ if not changed_ids:
1224
+ console.print("[yellow]Settings unchanged.[/yellow]")
1225
+ return
1226
+
1227
+ try:
1228
+ save_config(config_for_save, config_file)
1229
+ except (ConfigError, OSError) as exc:
1230
+ console.print(f"[red]Failed to save config: {exc}[/red]")
1231
+ return
1232
+
1233
+ from pythinker_code.telemetry import track
1234
+
1235
+ track("settings_update", changed=",".join(changed_ids), count=len(changed_ids))
1236
+ console.print(f"[green]Updated {len(changed_ids)} setting(s). Reloading...[/green]")
1237
+ raise Reload(session_id=soul.runtime.session.id)
1238
+
1239
+
1240
+ @registry.command
1241
+ def web(app: Shell, args: str):
1242
+ """Open Pythinker Web UI in browser"""
1243
+ from pythinker_code.telemetry import track
1244
+
1245
+ track("web_opened")
1246
+ soul = ensure_pythinker_soul(app)
1247
+ session_id = soul.runtime.session.id if soul else None
1248
+ raise SwitchToWeb(session_id=session_id)
1249
+
1250
+
1251
+ @registry.command
1252
+ def vis(app: Shell, args: str):
1253
+ """Open Pythinker Agent Tracing Visualizer in browser"""
1254
+ from pythinker_code.telemetry import track
1255
+
1256
+ track("vis_opened")
1257
+ soul = ensure_pythinker_soul(app)
1258
+ session_id = soul.runtime.session.id if soul else None
1259
+ raise SwitchToVis(session_id=session_id)
1260
+
1261
+
1262
+ @registry.command
1263
+ async def mcp(app: Shell, args: str):
1264
+ """Show MCP servers and tools"""
1265
+ from rich.live import Live
1266
+
1267
+ soul = ensure_pythinker_soul(app)
1268
+ if soul is None:
1269
+ return
1270
+ await soul.start_background_mcp_loading()
1271
+ snapshot = soul.status.mcp_status
1272
+ if snapshot is None:
1273
+ console.print("[yellow]No MCP servers configured.[/yellow]")
1274
+ return
1275
+
1276
+ if not snapshot.loading:
1277
+ console.print(render_mcp_console(snapshot))
1278
+ return
1279
+
1280
+ with Live(
1281
+ render_mcp_console(snapshot),
1282
+ console=console,
1283
+ refresh_per_second=8,
1284
+ transient=False,
1285
+ ) as live:
1286
+ while True:
1287
+ snapshot = soul.status.mcp_status
1288
+ if snapshot is None:
1289
+ break
1290
+ live.update(render_mcp_console(snapshot), refresh=True)
1291
+ if not snapshot.loading:
1292
+ break
1293
+ await asyncio.sleep(0.125)
1294
+ try:
1295
+ await soul.wait_for_background_mcp_loading()
1296
+ except Exception as e:
1297
+ logger.debug("MCP loading completed with error while rendering /mcp: {error}", error=e)
1298
+ snapshot = soul.status.mcp_status
1299
+ if snapshot is not None:
1300
+ live.update(render_mcp_console(snapshot), refresh=True)
1301
+
1302
+
1303
+ @registry.command
1304
+ @shell_mode_registry.command
1305
+ def hooks(app: Shell, args: str):
1306
+ """List configured hooks"""
1307
+ soul = ensure_pythinker_soul(app)
1308
+ if soul is None:
1309
+ return
1310
+
1311
+ engine = soul.hook_engine
1312
+ if not engine.summary:
1313
+ console.print(
1314
+ "[yellow]No hooks configured. "
1315
+ "Add [[hooks]] sections to your config.toml to set up hooks.[/yellow]"
1316
+ )
1317
+ return
1318
+
1319
+ console.print()
1320
+ console.print("[bold]Configured Hooks:[/bold]")
1321
+ console.print()
1322
+
1323
+ for event, entries in engine.details().items():
1324
+ console.print(f" [cyan]{event}[/cyan]: {len(entries)} hook(s)")
1325
+ for entry in entries:
1326
+ source_tag = f" [dim]({entry['source']})[/dim]" if entry["source"] == "wire" else ""
1327
+ console.print(f" [dim]{entry['matcher']}[/dim] {entry['command']}{source_tag}")
1328
+
1329
+ console.print()
1330
+
1331
+
1332
+ @registry.command
1333
+ async def undo(app: Shell, args: str):
1334
+ """Undo: fork the session at a previous turn and retry"""
1335
+ from pythinker_code.session_fork import enumerate_turns, fork_session
1336
+ from pythinker_code.utils.string import shorten
1337
+
1338
+ soul = ensure_pythinker_soul(app)
1339
+ if soul is None:
1340
+ return
1341
+
1342
+ session = soul.runtime.session
1343
+ wire_path = session.dir / "wire.jsonl"
1344
+ turns = enumerate_turns(wire_path)
1345
+
1346
+ if not turns:
1347
+ console.print("[yellow]No turns found in this session.[/yellow]")
1348
+ return
1349
+
1350
+ # Build choices: each turn's first line, truncated
1351
+ choices: list[tuple[str, str]] = []
1352
+ for turn in turns:
1353
+ first_line = turn.user_text.split("\n", 1)[0]
1354
+ label = shorten(first_line, width=80, placeholder="...")
1355
+ choices.append((str(turn.index), f"[{turn.index}] {label}"))
1356
+
1357
+ try:
1358
+ selected = await ChoiceInput(
1359
+ message="Select a turn to undo (↑↓ navigate, Enter select, Ctrl+C cancel):",
1360
+ options=choices,
1361
+ default=choices[-1][0],
1362
+ ).prompt_async()
1363
+ except (EOFError, KeyboardInterrupt):
1364
+ return
1365
+
1366
+ turn_index = int(selected)
1367
+
1368
+ # The selected turn is the one we want to redo — fork includes turns *before* it
1369
+ selected_turn = turns[turn_index]
1370
+ user_text = selected_turn.user_text
1371
+
1372
+ if turn_index == 0:
1373
+ # Fork with no history — just the user text
1374
+ new_session = await Session.create(session.work_dir)
1375
+ new_session_id = new_session.id
1376
+ # Set title to match the convention used by fork_session
1377
+ from pythinker_code.session_state import load_session_state, save_session_state
1378
+
1379
+ new_state = load_session_state(new_session.dir)
1380
+ new_state.custom_title = f"Undo: {session.title}"
1381
+ new_state.title_generated = True
1382
+ save_session_state(new_state, new_session.dir)
1383
+ else:
1384
+ # Fork includes turns 0..turn_index-1
1385
+ fork_turn_index = turn_index - 1
1386
+ new_session_id = await fork_session(
1387
+ source_session_dir=session.dir,
1388
+ work_dir=session.work_dir,
1389
+ turn_index=fork_turn_index,
1390
+ title_prefix="Undo",
1391
+ source_title=session.title,
1392
+ )
1393
+
1394
+ from pythinker_code.telemetry import track
1395
+
1396
+ track("undo")
1397
+ console.print(f"[green]Forked at turn {turn_index}. Switching to new session...[/green]")
1398
+ raise Reload(session_id=new_session_id, prefill_text=user_text)
1399
+
1400
+
1401
+ @registry.command
1402
+ async def fork(app: Shell, args: str):
1403
+ """Fork the current session (copy all history to a new session)"""
1404
+ from pythinker_code.session_fork import fork_session
1405
+
1406
+ soul = ensure_pythinker_soul(app)
1407
+ if soul is None:
1408
+ return
1409
+
1410
+ session = soul.runtime.session
1411
+ new_session_id = await fork_session(
1412
+ source_session_dir=session.dir,
1413
+ work_dir=session.work_dir,
1414
+ turn_index=None,
1415
+ title_prefix="Fork",
1416
+ source_title=session.title,
1417
+ )
1418
+
1419
+ from pythinker_code.telemetry import track
1420
+
1421
+ track("session_fork")
1422
+ console.print("[green]Session forked. Switching to new session...[/green]")
1423
+ raise Reload(session_id=new_session_id)
1424
+
1425
+
1426
+ from . import ( # noqa: E402
1427
+ debug, # noqa: F401 # type: ignore[reportUnusedImport]
1428
+ export_import, # noqa: F401 # type: ignore[reportUnusedImport]
1429
+ oauth, # noqa: F401 # type: ignore[reportUnusedImport]
1430
+ setup, # noqa: F401 # type: ignore[reportUnusedImport]
1431
+ update, # noqa: F401 # type: ignore[reportUnusedImport]
1432
+ usage, # noqa: F401 # type: ignore[reportUnusedImport]
1433
+ )