mycode-cli 0.1.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 (404) hide show
  1. mycode/__init__.py +1 -0
  2. mycode/cli/__init__.py +1 -0
  3. mycode/cli/chat.py +650 -0
  4. mycode/cli/main.py +245 -0
  5. mycode/cli/render.py +693 -0
  6. mycode/cli/runtime.py +271 -0
  7. mycode/cli/theme.py +109 -0
  8. mycode/core/__init__.py +30 -0
  9. mycode/core/agent.py +515 -0
  10. mycode/core/config.py +551 -0
  11. mycode/core/messages.py +166 -0
  12. mycode/core/models.py +144 -0
  13. mycode/core/models_catalog.json +2090 -0
  14. mycode/core/providers/__init__.py +86 -0
  15. mycode/core/providers/anthropic_like.py +366 -0
  16. mycode/core/providers/base.py +351 -0
  17. mycode/core/providers/gemini.py +321 -0
  18. mycode/core/providers/openai_chat.py +356 -0
  19. mycode/core/providers/openai_responses.py +326 -0
  20. mycode/core/session.py +537 -0
  21. mycode/core/system_prompt.md +10 -0
  22. mycode/core/system_prompt.py +319 -0
  23. mycode/core/tools.py +898 -0
  24. mycode/server/__init__.py +1 -0
  25. mycode/server/app.py +52 -0
  26. mycode/server/deps.py +29 -0
  27. mycode/server/routers/__init__.py +7 -0
  28. mycode/server/routers/chat.py +320 -0
  29. mycode/server/routers/sessions.py +77 -0
  30. mycode/server/routers/workspaces.py +92 -0
  31. mycode/server/run_manager.py +195 -0
  32. mycode/server/schemas.py +66 -0
  33. mycode/server/static/assets/EditDiff-C1ql7kft.js +12 -0
  34. mycode/server/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  35. mycode/server/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  36. mycode/server/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  37. mycode/server/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  38. mycode/server/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  39. mycode/server/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  40. mycode/server/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  41. mycode/server/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  42. mycode/server/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  43. mycode/server/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  44. mycode/server/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  45. mycode/server/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  46. mycode/server/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  47. mycode/server/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  48. mycode/server/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  49. mycode/server/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  50. mycode/server/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  51. mycode/server/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  52. mycode/server/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  53. mycode/server/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  54. mycode/server/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  55. mycode/server/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  56. mycode/server/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  57. mycode/server/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  58. mycode/server/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  59. mycode/server/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  60. mycode/server/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  61. mycode/server/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  62. mycode/server/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  63. mycode/server/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  64. mycode/server/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  65. mycode/server/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  66. mycode/server/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  67. mycode/server/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  68. mycode/server/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  69. mycode/server/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  70. mycode/server/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  71. mycode/server/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  72. mycode/server/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  73. mycode/server/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  74. mycode/server/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  75. mycode/server/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  76. mycode/server/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  77. mycode/server/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  78. mycode/server/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  79. mycode/server/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  80. mycode/server/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  81. mycode/server/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  82. mycode/server/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  83. mycode/server/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  84. mycode/server/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  85. mycode/server/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  86. mycode/server/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  87. mycode/server/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  88. mycode/server/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  89. mycode/server/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  90. mycode/server/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  91. mycode/server/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  92. mycode/server/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  93. mycode/server/static/assets/abap-BdImnpbu.js +1 -0
  94. mycode/server/static/assets/actionscript-3-CoDkCxhg.js +1 -0
  95. mycode/server/static/assets/ada-bCR0ucgS.js +1 -0
  96. mycode/server/static/assets/andromeeda-C4gqWexZ.js +1 -0
  97. mycode/server/static/assets/angular-html-DA-rfuFy.js +1 -0
  98. mycode/server/static/assets/angular-ts-BrjP3tb8.js +1 -0
  99. mycode/server/static/assets/apache-Pmp26Uib.js +1 -0
  100. mycode/server/static/assets/apex-D8_7TLub.js +1 -0
  101. mycode/server/static/assets/apl-CORt7UWP.js +1 -0
  102. mycode/server/static/assets/applescript-Co6uUVPk.js +1 -0
  103. mycode/server/static/assets/ara-BRHolxvo.js +1 -0
  104. mycode/server/static/assets/asciidoc-Ve4PFQV2.js +1 -0
  105. mycode/server/static/assets/asm-D_Q5rh1f.js +1 -0
  106. mycode/server/static/assets/astro-HNnZUWAn.js +1 -0
  107. mycode/server/static/assets/aurora-x-D-2ljcwZ.js +1 -0
  108. mycode/server/static/assets/auto-render-xntwXHOX.js +261 -0
  109. mycode/server/static/assets/awk-DMzUqQB5.js +1 -0
  110. mycode/server/static/assets/ayu-dark-DYE7WIF3.js +1 -0
  111. mycode/server/static/assets/ayu-light-BA47KaF1.js +1 -0
  112. mycode/server/static/assets/ayu-mirage-32ctXXKs.js +1 -0
  113. mycode/server/static/assets/ballerina-BFfxhgS-.js +1 -0
  114. mycode/server/static/assets/bat-BkioyH1T.js +1 -0
  115. mycode/server/static/assets/beancount-k_qm7-4y.js +1 -0
  116. mycode/server/static/assets/berry-uYugtg8r.js +1 -0
  117. mycode/server/static/assets/bibtex-CHM0blh-.js +1 -0
  118. mycode/server/static/assets/bicep-Bmn6On1c.js +1 -0
  119. mycode/server/static/assets/bird2-BIv1doCn.js +1 -0
  120. mycode/server/static/assets/blade-BjGOyj-B.js +1 -0
  121. mycode/server/static/assets/bsl-BO_Y6i37.js +1 -0
  122. mycode/server/static/assets/c-BIGW1oBm.js +1 -0
  123. mycode/server/static/assets/c3-eo99z4R2.js +1 -0
  124. mycode/server/static/assets/cadence-Bv_4Rxtq.js +1 -0
  125. mycode/server/static/assets/cairo-KRGpt6FW.js +1 -0
  126. mycode/server/static/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  127. mycode/server/static/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  128. mycode/server/static/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  129. mycode/server/static/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  130. mycode/server/static/assets/clarity-D53aC0YG.js +1 -0
  131. mycode/server/static/assets/clojure-P80f7IUj.js +1 -0
  132. mycode/server/static/assets/cmake-D1j8_8rp.js +1 -0
  133. mycode/server/static/assets/cobol-nBiQ_Alo.js +1 -0
  134. mycode/server/static/assets/codeowners-Bp6g37R7.js +1 -0
  135. mycode/server/static/assets/codeql-DsOJ9woJ.js +1 -0
  136. mycode/server/static/assets/coffee-Ch7k5sss.js +1 -0
  137. mycode/server/static/assets/common-lisp-Cg-RD9OK.js +1 -0
  138. mycode/server/static/assets/coq-DkFqJrB1.js +1 -0
  139. mycode/server/static/assets/cpp-CofmeUqb.js +1 -0
  140. mycode/server/static/assets/crystal-DNxU26gB.js +1 -0
  141. mycode/server/static/assets/csharp-COcwbKMJ.js +1 -0
  142. mycode/server/static/assets/css-CLj8gQPS.js +1 -0
  143. mycode/server/static/assets/csv-fuZLfV_i.js +1 -0
  144. mycode/server/static/assets/cue-D82EKSYY.js +1 -0
  145. mycode/server/static/assets/cypher-COkxafJQ.js +1 -0
  146. mycode/server/static/assets/d-85-TOEBH.js +1 -0
  147. mycode/server/static/assets/dark-plus-C3mMm8J8.js +1 -0
  148. mycode/server/static/assets/dart-bE4Kk8sk.js +1 -0
  149. mycode/server/static/assets/dax-CEL-wOlO.js +1 -0
  150. mycode/server/static/assets/desktop-BmXAJ9_W.js +1 -0
  151. mycode/server/static/assets/diff-D97Zzqfu.js +1 -0
  152. mycode/server/static/assets/docker-BcOcwvcX.js +1 -0
  153. mycode/server/static/assets/dotenv-Da5cRb03.js +1 -0
  154. mycode/server/static/assets/dracula-BzJJZx-M.js +1 -0
  155. mycode/server/static/assets/dracula-soft-BXkSAIEj.js +1 -0
  156. mycode/server/static/assets/dream-maker-BtqSS_iP.js +1 -0
  157. mycode/server/static/assets/edge-FbVlp4U3.js +1 -0
  158. mycode/server/static/assets/elixir-CkH2-t6x.js +1 -0
  159. mycode/server/static/assets/elm-DbKCFpqz.js +1 -0
  160. mycode/server/static/assets/emacs-lisp-CXvaQtF9.js +1 -0
  161. mycode/server/static/assets/erb-BYCe7drp.js +1 -0
  162. mycode/server/static/assets/erlang-DsQrWhSR.js +1 -0
  163. mycode/server/static/assets/everforest-dark-BgDCqdQA.js +1 -0
  164. mycode/server/static/assets/everforest-light-C8M2exoo.js +1 -0
  165. mycode/server/static/assets/fennel-BYunw83y.js +1 -0
  166. mycode/server/static/assets/fish-BvzEVeQv.js +1 -0
  167. mycode/server/static/assets/fluent-C4IJs8-o.js +1 -0
  168. mycode/server/static/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
  169. mycode/server/static/assets/fortran-free-form-BxgE0vQu.js +1 -0
  170. mycode/server/static/assets/fsharp-CXgrBDvD.js +1 -0
  171. mycode/server/static/assets/gdresource-BOOCDP_w.js +1 -0
  172. mycode/server/static/assets/gdscript-C5YyOfLZ.js +1 -0
  173. mycode/server/static/assets/gdshader-DkwncUOv.js +1 -0
  174. mycode/server/static/assets/genie-D0YGMca9.js +1 -0
  175. mycode/server/static/assets/gherkin-DyxjwDmM.js +1 -0
  176. mycode/server/static/assets/git-commit-F4YmCXRG.js +1 -0
  177. mycode/server/static/assets/git-rebase-r7XF79zn.js +1 -0
  178. mycode/server/static/assets/github-dark-DHJKELXO.js +1 -0
  179. mycode/server/static/assets/github-dark-default-Cuk6v7N8.js +1 -0
  180. mycode/server/static/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  181. mycode/server/static/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  182. mycode/server/static/assets/github-light-DAi9KRSo.js +1 -0
  183. mycode/server/static/assets/github-light-default-D7oLnXFd.js +1 -0
  184. mycode/server/static/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  185. mycode/server/static/assets/gleam-BspZqrRM.js +1 -0
  186. mycode/server/static/assets/glimmer-js-ByusRIyA.js +1 -0
  187. mycode/server/static/assets/glimmer-ts-BfAWNZQY.js +1 -0
  188. mycode/server/static/assets/glsl-DplSGwfg.js +1 -0
  189. mycode/server/static/assets/gn-n2N0HUVH.js +1 -0
  190. mycode/server/static/assets/gnuplot-DdkO51Og.js +1 -0
  191. mycode/server/static/assets/go-C27-OAKa.js +1 -0
  192. mycode/server/static/assets/graphql-ChdNCCLP.js +1 -0
  193. mycode/server/static/assets/groovy-gcz8RCvz.js +1 -0
  194. mycode/server/static/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  195. mycode/server/static/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  196. mycode/server/static/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  197. mycode/server/static/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  198. mycode/server/static/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  199. mycode/server/static/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  200. mycode/server/static/assets/hack-i7_Ulhet.js +1 -0
  201. mycode/server/static/assets/haml-D5jkg6IW.js +1 -0
  202. mycode/server/static/assets/handlebars-BpdQsYii.js +1 -0
  203. mycode/server/static/assets/haskell-Df6bDoY_.js +1 -0
  204. mycode/server/static/assets/haxe-CzTSHFRz.js +1 -0
  205. mycode/server/static/assets/hcl-BWvSN4gD.js +1 -0
  206. mycode/server/static/assets/hjson-D5-asLiD.js +1 -0
  207. mycode/server/static/assets/hlsl-D3lLCCz7.js +1 -0
  208. mycode/server/static/assets/horizon-BUw7H-hv.js +1 -0
  209. mycode/server/static/assets/horizon-bright-CUuTKBJd.js +1 -0
  210. mycode/server/static/assets/houston-DnULxvSX.js +1 -0
  211. mycode/server/static/assets/html-derivative-DlHx6ybY.js +1 -0
  212. mycode/server/static/assets/html-pp8916En.js +1 -0
  213. mycode/server/static/assets/http-jrhK8wxY.js +1 -0
  214. mycode/server/static/assets/hurl-irOxFIW8.js +1 -0
  215. mycode/server/static/assets/hxml-Bvhsp5Yf.js +1 -0
  216. mycode/server/static/assets/hy-DFXneXwc.js +1 -0
  217. mycode/server/static/assets/imba-DGztddWO.js +1 -0
  218. mycode/server/static/assets/index-B4e4WQPq.css +1 -0
  219. mycode/server/static/assets/index-C2xTNJGd.js +203 -0
  220. mycode/server/static/assets/ini-BEwlwnbL.js +1 -0
  221. mycode/server/static/assets/java-CylS5w8V.js +1 -0
  222. mycode/server/static/assets/javascript-wDzz0qaB.js +1 -0
  223. mycode/server/static/assets/jinja-f2NsQr07.js +1 -0
  224. mycode/server/static/assets/jison-wvAkD_A8.js +1 -0
  225. mycode/server/static/assets/json-Cp-IABpG.js +1 -0
  226. mycode/server/static/assets/json5-C9tS-k6U.js +1 -0
  227. mycode/server/static/assets/jsonc-Des-eS-w.js +1 -0
  228. mycode/server/static/assets/jsonl-DcaNXYhu.js +1 -0
  229. mycode/server/static/assets/jsonnet-DFQXde-d.js +1 -0
  230. mycode/server/static/assets/jssm-C2t-YnRu.js +1 -0
  231. mycode/server/static/assets/jsx-g9-lgVsj.js +1 -0
  232. mycode/server/static/assets/julia-CxzCAyBv.js +1 -0
  233. mycode/server/static/assets/just-VxiPbLrw.js +1 -0
  234. mycode/server/static/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  235. mycode/server/static/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  236. mycode/server/static/assets/kanagawa-wave-DWedfzmr.js +1 -0
  237. mycode/server/static/assets/katex-DnJR2-55.css +1 -0
  238. mycode/server/static/assets/kdl-DV7GczEv.js +1 -0
  239. mycode/server/static/assets/kotlin-BdnUsdx6.js +1 -0
  240. mycode/server/static/assets/kusto-wEQ09or8.js +1 -0
  241. mycode/server/static/assets/laserwave-DUszq2jm.js +1 -0
  242. mycode/server/static/assets/latex-CWtU0Tv5.js +1 -0
  243. mycode/server/static/assets/lean-BZvkOJ9d.js +1 -0
  244. mycode/server/static/assets/less-B1dDrJ26.js +1 -0
  245. mycode/server/static/assets/light-plus-B7mTdjB0.js +1 -0
  246. mycode/server/static/assets/liquid-C0sCDyMI.js +1 -0
  247. mycode/server/static/assets/llvm-DjAJT7YJ.js +1 -0
  248. mycode/server/static/assets/log-2UxHyX5q.js +1 -0
  249. mycode/server/static/assets/logo-BtOb2qkB.js +1 -0
  250. mycode/server/static/assets/lua-BaeVxFsk.js +1 -0
  251. mycode/server/static/assets/luau-C-HG3fhB.js +1 -0
  252. mycode/server/static/assets/make-CHLpvVh8.js +1 -0
  253. mycode/server/static/assets/markdown-Cvjx9yec.js +1 -0
  254. mycode/server/static/assets/marko-DjSrsDqO.js +1 -0
  255. mycode/server/static/assets/material-theme-D5KoaKCx.js +1 -0
  256. mycode/server/static/assets/material-theme-darker-BfHTSMKl.js +1 -0
  257. mycode/server/static/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  258. mycode/server/static/assets/material-theme-ocean-CyktbL80.js +1 -0
  259. mycode/server/static/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  260. mycode/server/static/assets/matlab-D7o27uSR.js +1 -0
  261. mycode/server/static/assets/mdc-DTYItulj.js +1 -0
  262. mycode/server/static/assets/mdx-Cmh6b_Ma.js +1 -0
  263. mycode/server/static/assets/mermaid-mWjccvbQ.js +1 -0
  264. mycode/server/static/assets/min-dark-CafNBF8u.js +1 -0
  265. mycode/server/static/assets/min-light-CTRr51gU.js +1 -0
  266. mycode/server/static/assets/mipsasm-CKIfxQSi.js +1 -0
  267. mycode/server/static/assets/mojo-rZm6bMo-.js +1 -0
  268. mycode/server/static/assets/monokai-D4h5O-jR.js +1 -0
  269. mycode/server/static/assets/moonbit-_H4v1dQx.js +1 -0
  270. mycode/server/static/assets/move-IF9eRakj.js +1 -0
  271. mycode/server/static/assets/narrat-DRg8JJMk.js +1 -0
  272. mycode/server/static/assets/nextflow-C-mBbutL.js +1 -0
  273. mycode/server/static/assets/nextflow-groovy-vE_lwT2v.js +1 -0
  274. mycode/server/static/assets/nginx-BpAMiNFr.js +1 -0
  275. mycode/server/static/assets/night-owl-C39BiMTA.js +1 -0
  276. mycode/server/static/assets/night-owl-light-CMTm3GFP.js +1 -0
  277. mycode/server/static/assets/nim-BIad80T-.js +1 -0
  278. mycode/server/static/assets/nix-CwoSXNpI.js +1 -0
  279. mycode/server/static/assets/nord-Ddv68eIx.js +1 -0
  280. mycode/server/static/assets/nushell-Cz2AlsmD.js +1 -0
  281. mycode/server/static/assets/objective-c-DXmwc3jG.js +1 -0
  282. mycode/server/static/assets/objective-cpp-CLxacb5B.js +1 -0
  283. mycode/server/static/assets/ocaml-C0hk2d4L.js +1 -0
  284. mycode/server/static/assets/odin-BBf5iR-q.js +1 -0
  285. mycode/server/static/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  286. mycode/server/static/assets/one-light-C3Wv6jpd.js +1 -0
  287. mycode/server/static/assets/openscad-C4EeE6gA.js +1 -0
  288. mycode/server/static/assets/pascal-D93ZcfNL.js +1 -0
  289. mycode/server/static/assets/perl-NvoQZIq0.js +1 -0
  290. mycode/server/static/assets/php-R6g_5hLQ.js +1 -0
  291. mycode/server/static/assets/pkl-u5AG7uiY.js +1 -0
  292. mycode/server/static/assets/plastic-3e1v2bzS.js +1 -0
  293. mycode/server/static/assets/plsql-ChMvpjG-.js +1 -0
  294. mycode/server/static/assets/po-BTJTHyun.js +1 -0
  295. mycode/server/static/assets/poimandres-CS3Unz2-.js +1 -0
  296. mycode/server/static/assets/polar-C0HS_06l.js +1 -0
  297. mycode/server/static/assets/postcss-CXtECtnM.js +1 -0
  298. mycode/server/static/assets/powerquery-CEu0bR-o.js +1 -0
  299. mycode/server/static/assets/powershell-Dpen1YoG.js +1 -0
  300. mycode/server/static/assets/prisma-Dd19v3D-.js +1 -0
  301. mycode/server/static/assets/prolog-CbFg5uaA.js +1 -0
  302. mycode/server/static/assets/proto-C7zT0LnQ.js +1 -0
  303. mycode/server/static/assets/pug-DKIMFp6K.js +1 -0
  304. mycode/server/static/assets/puppet-BMWR74SV.js +1 -0
  305. mycode/server/static/assets/purescript-CklMAg4u.js +1 -0
  306. mycode/server/static/assets/python-B6aJPvgy.js +1 -0
  307. mycode/server/static/assets/qml-3beO22l8.js +1 -0
  308. mycode/server/static/assets/qmldir-C8lEn-DE.js +1 -0
  309. mycode/server/static/assets/qss-IeuSbFQv.js +1 -0
  310. mycode/server/static/assets/r-Dspwwk_N.js +1 -0
  311. mycode/server/static/assets/racket-BqYA7rlc.js +1 -0
  312. mycode/server/static/assets/raku-DXvB9xmW.js +1 -0
  313. mycode/server/static/assets/razor-BDqjjVU7.js +1 -0
  314. mycode/server/static/assets/red-bN70gL4F.js +1 -0
  315. mycode/server/static/assets/reg-C-SQnVFl.js +1 -0
  316. mycode/server/static/assets/regexp-CDVJQ6XC.js +1 -0
  317. mycode/server/static/assets/rel-C3B-1QV4.js +1 -0
  318. mycode/server/static/assets/riscv-BM1_JUlF.js +1 -0
  319. mycode/server/static/assets/ron-D8l8udqQ.js +1 -0
  320. mycode/server/static/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
  321. mycode/server/static/assets/rose-pine-moon-D4_iv3hh.js +1 -0
  322. mycode/server/static/assets/rose-pine-qdsjHGoJ.js +1 -0
  323. mycode/server/static/assets/rosmsg-BJDFO7_C.js +1 -0
  324. mycode/server/static/assets/rst-CRjBmOyv.js +1 -0
  325. mycode/server/static/assets/ruby-Wjq7vjNf.js +1 -0
  326. mycode/server/static/assets/rust-B1yitclQ.js +1 -0
  327. mycode/server/static/assets/sas-cz2c8ADy.js +1 -0
  328. mycode/server/static/assets/sass-Cj5Yp3dK.js +1 -0
  329. mycode/server/static/assets/scala-C151Ov-r.js +1 -0
  330. mycode/server/static/assets/scheme-C98Dy4si.js +1 -0
  331. mycode/server/static/assets/scss-D5BDwBP9.js +1 -0
  332. mycode/server/static/assets/sdbl-DVxCFoDh.js +1 -0
  333. mycode/server/static/assets/shaderlab-Dg9Lc6iA.js +1 -0
  334. mycode/server/static/assets/shellscript-Yzrsuije.js +1 -0
  335. mycode/server/static/assets/shellsession-BADoaaVG.js +1 -0
  336. mycode/server/static/assets/slack-dark-BthQWCQV.js +1 -0
  337. mycode/server/static/assets/slack-ochin-DqwNpetd.js +1 -0
  338. mycode/server/static/assets/smalltalk-BERRCDM3.js +1 -0
  339. mycode/server/static/assets/snazzy-light-Bw305WKR.js +1 -0
  340. mycode/server/static/assets/solarized-dark-DXbdFlpD.js +1 -0
  341. mycode/server/static/assets/solarized-light-L9t79GZl.js +1 -0
  342. mycode/server/static/assets/solidity-rGO070M0.js +1 -0
  343. mycode/server/static/assets/soy-8wufbnw4.js +1 -0
  344. mycode/server/static/assets/sparql-rVzFXLq3.js +1 -0
  345. mycode/server/static/assets/splunk-BtCnVYZw.js +1 -0
  346. mycode/server/static/assets/sql-BLtJtn59.js +1 -0
  347. mycode/server/static/assets/ssh-config-_ykCGR6B.js +1 -0
  348. mycode/server/static/assets/stata-BH5u7GGu.js +1 -0
  349. mycode/server/static/assets/stylus-BEDo0Tqx.js +1 -0
  350. mycode/server/static/assets/surrealql-Bq5Q-fJD.js +1 -0
  351. mycode/server/static/assets/svelte-Cy7k_4gC.js +1 -0
  352. mycode/server/static/assets/swift-D82vCrfD.js +1 -0
  353. mycode/server/static/assets/synthwave-84-CbfX1IO0.js +1 -0
  354. mycode/server/static/assets/system-verilog-CnnmHF94.js +1 -0
  355. mycode/server/static/assets/systemd-4A_iFExJ.js +1 -0
  356. mycode/server/static/assets/talonscript-CkByrt1z.js +1 -0
  357. mycode/server/static/assets/tasl-QIJgUcNo.js +1 -0
  358. mycode/server/static/assets/tcl-dwOrl1Do.js +1 -0
  359. mycode/server/static/assets/templ-DhtptRzy.js +1 -0
  360. mycode/server/static/assets/terraform-BETggiCN.js +1 -0
  361. mycode/server/static/assets/tex-idrVyKtj.js +1 -0
  362. mycode/server/static/assets/tokyo-night-hegEt444.js +1 -0
  363. mycode/server/static/assets/toml-vGWfd6FD.js +1 -0
  364. mycode/server/static/assets/ts-tags-DQrlYJgV.js +1 -0
  365. mycode/server/static/assets/tsv-B_m7g4N7.js +1 -0
  366. mycode/server/static/assets/tsx-COt5Ahok.js +1 -0
  367. mycode/server/static/assets/turtle-BsS91CYL.js +1 -0
  368. mycode/server/static/assets/twig-xg9kU7Mw.js +1 -0
  369. mycode/server/static/assets/typescript-BPQ3VLAy.js +1 -0
  370. mycode/server/static/assets/typespec-CAFt9gP4.js +1 -0
  371. mycode/server/static/assets/typst-DHCkPAjA.js +1 -0
  372. mycode/server/static/assets/v-BcVCzyr7.js +1 -0
  373. mycode/server/static/assets/vala-CsfeWuGM.js +1 -0
  374. mycode/server/static/assets/vb-D17OF-Vu.js +1 -0
  375. mycode/server/static/assets/verilog-BQ8w6xss.js +1 -0
  376. mycode/server/static/assets/vesper-DU1UobuO.js +1 -0
  377. mycode/server/static/assets/vhdl-CeAyd5Ju.js +1 -0
  378. mycode/server/static/assets/viml-CJc9bBzg.js +1 -0
  379. mycode/server/static/assets/vitesse-black-Bkuqu6BP.js +1 -0
  380. mycode/server/static/assets/vitesse-dark-D0r3Knsf.js +1 -0
  381. mycode/server/static/assets/vitesse-light-CVO1_9PV.js +1 -0
  382. mycode/server/static/assets/vue-D2xRrEX4.js +1 -0
  383. mycode/server/static/assets/vue-html-AaS7Mt5G.js +1 -0
  384. mycode/server/static/assets/vue-vine-BoDAl6tE.js +1 -0
  385. mycode/server/static/assets/vyper-CDx5xZoG.js +1 -0
  386. mycode/server/static/assets/wasm-CG6Dc4jp.js +1 -0
  387. mycode/server/static/assets/wasm-MzD3tlZU.js +1 -0
  388. mycode/server/static/assets/wenyan-BV7otONQ.js +1 -0
  389. mycode/server/static/assets/wgsl-Dx-B1_4e.js +1 -0
  390. mycode/server/static/assets/wikitext-BhOHFoWU.js +1 -0
  391. mycode/server/static/assets/wit-5i3qLPDT.js +1 -0
  392. mycode/server/static/assets/wolfram-lXgVvXCa.js +1 -0
  393. mycode/server/static/assets/xml-sdJ4AIDG.js +1 -0
  394. mycode/server/static/assets/xsl-CtQFsRM5.js +1 -0
  395. mycode/server/static/assets/yaml-Buea-lGh.js +1 -0
  396. mycode/server/static/assets/zenscript-DVFEvuxE.js +1 -0
  397. mycode/server/static/assets/zig-VOosw3JB.js +1 -0
  398. mycode/server/static/favicon_slashes.svg +12 -0
  399. mycode/server/static/index.html +35 -0
  400. mycode_cli-0.1.0.dist-info/METADATA +186 -0
  401. mycode_cli-0.1.0.dist-info/RECORD +404 -0
  402. mycode_cli-0.1.0.dist-info/WHEEL +4 -0
  403. mycode_cli-0.1.0.dist-info/entry_points.txt +2 -0
  404. mycode_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
mycode/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """mycode — minimal coding agent."""
mycode/cli/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """CLI package for mycode."""
mycode/cli/chat.py ADDED
@@ -0,0 +1,650 @@
1
+ """Interactive terminal chat for the CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import html
7
+ import re
8
+ import shlex
9
+ from base64 import b64encode
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ from prompt_toolkit import PromptSession
14
+ from prompt_toolkit.application import Application, get_app
15
+ from prompt_toolkit.completion import Completer, Completion
16
+ from prompt_toolkit.formatted_text import ANSI
17
+ from prompt_toolkit.history import FileHistory
18
+ from prompt_toolkit.key_binding import KeyBindings
19
+ from prompt_toolkit.keys import Keys
20
+ from prompt_toolkit.layout import Layout
21
+ from prompt_toolkit.widgets import RadioList
22
+ from rich.text import Text
23
+
24
+ from mycode.core.agent import Agent
25
+ from mycode.core.config import resolve_mycode_home
26
+ from mycode.core.messages import build_message, image_block, text_block
27
+ from mycode.core.session import SessionStore
28
+ from mycode.core.tools import detect_image_mime_type, resolve_path
29
+
30
+ from .render import ReplyRenderer, TerminalView, format_local_timestamp
31
+ from .runtime import (
32
+ REASONING_EFFORT_OPTIONS,
33
+ append_session_message,
34
+ clone_agent,
35
+ get_provider_option,
36
+ list_model_options,
37
+ list_provider_options,
38
+ supports_reasoning_effort,
39
+ update_agent_runtime,
40
+ update_reasoning_effort,
41
+ )
42
+ from .theme import MUTED, PROMPT_CHAR, TERMINAL_THEME, TOOL_MARKER
43
+
44
+ _PROMPT = ANSI(f"\033[1m\033[34m{PROMPT_CHAR}\033[0m ")
45
+
46
+ _COMMAND_HELP = (
47
+ ("/clear", "Clear conversation"),
48
+ ("/new", "New session"),
49
+ ("/resume", "Switch session"),
50
+ ("/rewind", "Rewind to a previous message"),
51
+ ("/provider", "Switch provider"),
52
+ ("/model", "Switch model"),
53
+ ("/effort", "Set reasoning effort"),
54
+ ("/q", "Quit"),
55
+ )
56
+ _SLASH_COMMANDS = tuple(command for command, _ in _COMMAND_HELP)
57
+ # Only treat `@path` as a reference when it starts a standalone token.
58
+ _AT_PATH_RE = re.compile(r"(?<!\S)@(?:(?P<quote>['\"])(?P<quoted>[^'\"]*)|(?P<plain>[^\s'\"]*))$")
59
+
60
+
61
+ # Style for the focused row in the inline selector.
62
+ _FOCUSED_STYLE = "bold blue" if TERMINAL_THEME == "light" else "bold cyan"
63
+
64
+
65
+ class _InlineRadioList[T](RadioList):
66
+ """Arrow-key list that shows > on the focused item and exits on Enter."""
67
+
68
+ def _handle_enter(self) -> None:
69
+ # Only called by Enter/Space (not arrows), so safe to exit.
70
+ self.current_value = self.values[self._selected_index][0]
71
+ get_app().exit(result=self.current_value)
72
+
73
+ def _get_text_fragments(self):
74
+ # Override rendering: show > based on focus, not checked state.
75
+ result: list[tuple[str, str]] = []
76
+ for i, (_value, text) in enumerate(self.values):
77
+ focused = i == self._selected_index
78
+ style = _FOCUSED_STYLE if focused else ""
79
+ result.append((style, "> " if focused else " "))
80
+ result.append((style, str(text)))
81
+ result.append(("", "\n"))
82
+ result.pop() # remove trailing newline
83
+ return result
84
+
85
+
86
+ async def choose[T](options: list[tuple[T, str]], *, default: T | None = None) -> T | None:
87
+ """Inline arrow-key selector. Returns the selected value or None on cancel."""
88
+
89
+ radio = _InlineRadioList(
90
+ values=options,
91
+ default=default,
92
+ show_scrollbar=False,
93
+ show_cursor=False,
94
+ )
95
+
96
+ kb = KeyBindings()
97
+
98
+ @kb.add("c-c")
99
+ @kb.add("escape")
100
+ def _cancel(event) -> None:
101
+ event.app.exit(result=None)
102
+
103
+ app: Application[T | None] = Application(
104
+ layout=Layout(radio),
105
+ key_bindings=kb,
106
+ full_screen=False,
107
+ )
108
+ return await app.run_async()
109
+
110
+
111
+ class _PromptCompleter(Completer):
112
+ """Complete slash commands and explicit `@path` references for the prompt."""
113
+
114
+ _COMMANDS = dict(_COMMAND_HELP)
115
+
116
+ def __init__(self, *, cwd: str | None = None) -> None:
117
+ self._cwd = cwd
118
+
119
+ def get_completions(self, document, complete_event):
120
+ text_before_cursor = document.text_before_cursor
121
+ text = text_before_cursor.lstrip()
122
+ if self._cwd:
123
+ match = _AT_PATH_RE.search(text_before_cursor)
124
+ if match:
125
+ quote = str(match.group("quote") or "")
126
+ query = str(match.group("quoted") or match.group("plain") or "")
127
+ # Complete only real paths under the current working directory.
128
+ if query == "~":
129
+ base_prefix = "~/"
130
+ partial = ""
131
+ base_dir = Path("~").expanduser()
132
+ elif query.endswith("/"):
133
+ base_prefix = query
134
+ partial = ""
135
+ base_dir = Path(resolve_path(query or ".", cwd=self._cwd))
136
+ else:
137
+ head, sep, tail = query.rpartition("/")
138
+ base_prefix = f"{head}{sep}" if sep else ""
139
+ partial = tail if sep else query
140
+ base_dir = Path(resolve_path(base_prefix or ".", cwd=self._cwd))
141
+
142
+ if base_dir.is_dir():
143
+ for entry in sorted(base_dir.iterdir(), key=lambda item: (not item.is_dir(), item.name.lower())):
144
+ if partial and not entry.name.startswith(partial):
145
+ continue
146
+ candidate = f"{base_prefix}{entry.name}{'/' if entry.is_dir() else ''}"
147
+ replacement = "@" + shlex.quote(candidate)
148
+ if quote:
149
+ replacement = f"@{quote}{candidate}"
150
+ if not entry.is_dir():
151
+ replacement += quote
152
+ yield Completion(
153
+ replacement,
154
+ start_position=-len(match.group(0)),
155
+ display="@" + candidate,
156
+ display_meta="dir" if entry.is_dir() else "file",
157
+ )
158
+ return
159
+
160
+ if not text.startswith("/"):
161
+ return
162
+ for cmd, desc in self._COMMANDS.items():
163
+ if cmd.startswith(text) and cmd != text:
164
+ yield Completion(cmd, start_position=-len(text), display_meta=desc)
165
+
166
+
167
+ def _rewrite_pasted_file_paths(text: str) -> str | None:
168
+ """Rewrite pasted file paths into explicit `@path` references."""
169
+
170
+ normalized = text.replace("\r\n", "\n").replace("\r", "\n").strip()
171
+ if not normalized:
172
+ return None
173
+ try:
174
+ tokens = shlex.split(normalized, posix=True)
175
+ except ValueError:
176
+ return None
177
+ if not tokens:
178
+ return None
179
+ paths = [Path(token).expanduser() for token in tokens]
180
+ if not all(path.is_file() for path in paths):
181
+ return None
182
+ return " ".join(f"@{shlex.quote(str(path))}" for path in paths)
183
+
184
+
185
+ def _build_chat_key_bindings() -> KeyBindings:
186
+ """Build key bindings for the main chat prompt."""
187
+ kb = KeyBindings()
188
+
189
+ kb.add("c-l")(lambda event: event.app.renderer.clear())
190
+
191
+ # In multiline mode the default Enter inserts a newline; override it to submit.
192
+ kb.add("enter", eager=True)(lambda event: event.current_buffer.validate_and_handle())
193
+
194
+ # Esc+Enter (Meta+Enter) inserts a newline for multiline input.
195
+ kb.add("escape", "enter")(lambda event: event.current_buffer.insert_text("\n"))
196
+
197
+ @kb.add(Keys.BracketedPaste, eager=True)
198
+ def _handle_bracketed_paste(event) -> None:
199
+ pasted = event.data.replace("\r\n", "\n").replace("\r", "\n")
200
+ event.current_buffer.insert_text(_rewrite_pasted_file_paths(pasted) or pasted)
201
+
202
+ return kb
203
+
204
+
205
+ def history_file_path() -> str:
206
+ """Return the path used by prompt-toolkit to store CLI history."""
207
+
208
+ path = resolve_mycode_home() / "cli_history"
209
+ path.parent.mkdir(parents=True, exist_ok=True)
210
+ return str(path)
211
+
212
+
213
+ class TerminalChat:
214
+ """Own the interactive TUI session, including slash commands and rendering."""
215
+
216
+ def __init__(
217
+ self,
218
+ *,
219
+ agent: Agent,
220
+ store: SessionStore,
221
+ session_id: str,
222
+ view: TerminalView | None = None,
223
+ ) -> None:
224
+ self.agent = agent
225
+ self.store = store
226
+ self.session_id = session_id
227
+ self.view = view or TerminalView()
228
+ self.prompt_session = PromptSession(
229
+ history=FileHistory(history_file_path()),
230
+ completer=_PromptCompleter(cwd=self.agent.cwd),
231
+ key_bindings=_build_chat_key_bindings(),
232
+ multiline=True,
233
+ prompt_continuation=" ",
234
+ )
235
+
236
+ async def run(self) -> None:
237
+ """Run the interactive chat loop until the user exits the terminal UI."""
238
+
239
+ prefill = ""
240
+ while True:
241
+ self.view.console.print()
242
+
243
+ try:
244
+ user_input = await self.prompt_session.prompt_async(_PROMPT, default=prefill)
245
+ except KeyboardInterrupt:
246
+ prefill = ""
247
+ continue
248
+ except EOFError:
249
+ self.view.console.print("\n[dim]bye[/dim]")
250
+ return
251
+ finally:
252
+ prefill = ""
253
+
254
+ user_input = user_input.strip()
255
+ if not user_input:
256
+ continue
257
+
258
+ result = await self._handle_command(user_input)
259
+ if result == "exit":
260
+ return
261
+ if isinstance(result, str):
262
+ # Command wants to prefill the next prompt (e.g. /rewind).
263
+ prefill = result
264
+ continue
265
+ if result:
266
+ continue
267
+
268
+ self.view.console.print()
269
+ renderer = ReplyRenderer(self.view.console)
270
+ user_message = self._build_user_message(user_input)
271
+ try:
272
+ await renderer.render(self.agent, user_message, on_persist=self._persist_message)
273
+ except (KeyboardInterrupt, asyncio.CancelledError):
274
+ self.agent.cancel()
275
+ renderer.cancel()
276
+ # Python 3.11+: uncancel the task so the loop can continue after Ctrl+C.
277
+ task = asyncio.current_task()
278
+ if task is not None:
279
+ try:
280
+ task.uncancel()
281
+ except AttributeError:
282
+ pass # Python < 3.11
283
+
284
+ def _build_user_message(self, text: str) -> dict[str, Any]:
285
+ """Build one user message with the raw prompt first, then resolved attachments.
286
+
287
+ Text files are appended as extra text blocks in their final provider-facing
288
+ form. Images are appended as image blocks. Only explicit `@path` tokens
289
+ that resolve to real files are attached.
290
+ """
291
+
292
+ blocks = [text_block(text)]
293
+ try:
294
+ tokens = shlex.split(text.replace("\r\n", "\n").replace("\r", "\n"), posix=True)
295
+ except ValueError:
296
+ return build_message("user", blocks)
297
+
298
+ seen: set[str] = set()
299
+ for token in tokens:
300
+ if not token.startswith("@") or token == "@":
301
+ continue
302
+
303
+ path = Path(resolve_path(token[1:], cwd=self.agent.cwd))
304
+ if not path.is_file():
305
+ continue
306
+
307
+ path_text = str(path)
308
+ if path_text in seen:
309
+ continue
310
+ seen.add(path_text)
311
+
312
+ image_mime_type = detect_image_mime_type(path)
313
+ if image_mime_type:
314
+ image_data = b64encode(path.read_bytes()).decode("utf-8")
315
+ blocks.append(image_block(image_data, mime_type=image_mime_type, name=path.name))
316
+ continue
317
+
318
+ # Reuse the existing read tool so attached text files follow the same
319
+ # UTF-8, truncation, and long-line rules as agent-initiated reads.
320
+ result = self.agent.tools.read(path=path_text)
321
+ if result.is_error:
322
+ continue
323
+
324
+ blocks.append(
325
+ text_block(
326
+ f'<file name="{html.escape(path_text, quote=True)}">\n{result.model_text}\n</file>',
327
+ meta={"attachment": True, "path": path_text},
328
+ )
329
+ )
330
+
331
+ return build_message("user", blocks)
332
+
333
+ async def _persist_message(self, message: dict[str, Any]) -> None:
334
+ """Persist one streamed message into the active session."""
335
+
336
+ await append_session_message(self.store, self.session_id, message, agent=self.agent)
337
+
338
+ async def _handle_command(self, text: str) -> str | bool:
339
+ """Handle a slash command. Returns "exit" to quit, True if consumed, False otherwise."""
340
+
341
+ # Non-slash exit aliases.
342
+ if text in ("exit", "quit"):
343
+ self.view.console.print("[dim]bye[/dim]")
344
+ return "exit"
345
+
346
+ if not text.startswith("/"):
347
+ return False
348
+
349
+ command, _, argument = text.partition(" ")
350
+ argument = argument.strip()
351
+ matches = [candidate for candidate in _SLASH_COMMANDS if candidate.startswith(command)]
352
+ if len(matches) == 1:
353
+ command = matches[0]
354
+
355
+ match command:
356
+ case "/q":
357
+ self.view.console.print("[dim]bye[/dim]")
358
+ return "exit"
359
+ case "/c" | "/clear":
360
+ await self.store.clear_session(self.session_id)
361
+ self.agent.clear()
362
+ self.view.console.print(f"[green]{TOOL_MARKER}[/green] [dim]cleared[/dim]")
363
+ case "/new":
364
+ await self._start_new_session()
365
+ case "/rewind":
366
+ prefill = await self._rewind()
367
+ if prefill:
368
+ return prefill
369
+ case "/resume":
370
+ await self._resume_session()
371
+ case "/provider":
372
+ if argument:
373
+ await self._apply_provider_change(argument)
374
+ else:
375
+ await self._switch_provider()
376
+ case "/model":
377
+ if argument:
378
+ await self._apply_model_change(argument)
379
+ else:
380
+ await self._switch_model()
381
+ case "/effort":
382
+ if argument:
383
+ self._apply_effort_change(argument)
384
+ else:
385
+ await self._switch_effort()
386
+ case _:
387
+ self._print_help()
388
+
389
+ return True
390
+
391
+ def _print_help(self) -> None:
392
+ commands = [
393
+ ("/c, /clear", "Clear conversation"),
394
+ ("/new", "New session"),
395
+ ("/resume", "Switch session"),
396
+ ("/rewind", "Rewind to a previous message"),
397
+ ("/provider [name]", "Switch provider"),
398
+ ("/model [name]", "Switch model"),
399
+ ("/effort [level]", "Set reasoning effort"),
400
+ ("/q", "Quit"),
401
+ ]
402
+ self.view.console.print()
403
+ for cmd, desc in commands:
404
+ line = Text()
405
+ line.append(f" {cmd:<20}", style="bold")
406
+ line.append(desc, style=MUTED)
407
+ self.view.console.print(line)
408
+
409
+ def _print_runtime_status(self, action: str, value: str, *, changed: bool) -> None:
410
+ """Print the result of a runtime-only change."""
411
+
412
+ if changed:
413
+ self.view.console.print(f"[green]{TOOL_MARKER}[/green] [dim]{action} →[/dim] {value}")
414
+ return
415
+ self.view.console.print(f"[green]{TOOL_MARKER}[/green] [dim]already using[/dim] {value}")
416
+
417
+ def _supports_effort_or_warn(self) -> bool:
418
+ """Return whether the current model supports reasoning effort."""
419
+
420
+ if supports_reasoning_effort(self.agent):
421
+ return True
422
+ self.view.console.print("[dim]current model does not support reasoning effort[/dim]")
423
+ return False
424
+
425
+ async def _start_new_session(self) -> None:
426
+ """Start a fresh session while keeping the current runtime settings."""
427
+
428
+ data = self.store.draft_session(
429
+ None,
430
+ provider=self.agent.provider,
431
+ model=self.agent.model,
432
+ cwd=self.agent.cwd,
433
+ api_base=self.agent.api_base,
434
+ )
435
+ session = data.get("session") or {}
436
+ self.session_id = str(session.get("id") or "")
437
+ self.agent = clone_agent(self.agent, store=self.store, session_id=self.session_id, messages=[])
438
+ self.view.print_header(
439
+ provider=self.agent.provider,
440
+ model=self.agent.model,
441
+ session=session,
442
+ mode="new",
443
+ message_count=0,
444
+ reasoning_effort=self.agent.reasoning_effort,
445
+ )
446
+
447
+ async def _rewind(self) -> str | None:
448
+ """Rewind the conversation to a chosen user message.
449
+
450
+ Shows an interactive selector of all real user text messages.
451
+ Selecting one truncates the in-memory conversation to the slice before
452
+ that user message index and appends a rewind marker to the session log.
453
+ Returns the original message text to prefill the next prompt.
454
+ """
455
+ messages = self.agent.messages
456
+ if not messages:
457
+ self.view.console.print("[dim]nothing to rewind[/dim]")
458
+ return None
459
+
460
+ # Collect real user text messages (skip synthetic compact summaries
461
+ # and tool-result-only user messages).
462
+ user_turns: list[tuple[int, str]] = [] # (message_index, full_text)
463
+ for i, msg in enumerate(messages):
464
+ if msg.get("role") != "user":
465
+ continue
466
+ if (msg.get("meta") or {}).get("synthetic"):
467
+ continue
468
+ blocks = msg.get("content") or []
469
+ text = ""
470
+ for b in blocks:
471
+ if isinstance(b, dict) and b.get("type") == "text" and b.get("text"):
472
+ text = str(b["text"]).strip()
473
+ break
474
+ if text:
475
+ user_turns.append((i, text))
476
+
477
+ if not user_turns:
478
+ self.view.console.print("[dim]no user messages to rewind to[/dim]")
479
+ return None
480
+
481
+ # Build selector options — most recent first.
482
+ options: list[tuple[int, str]] = []
483
+ for msg_index, text in reversed(user_turns):
484
+ preview = text.replace("\n", " ")[:60]
485
+ if len(text) > 60:
486
+ preview += "..."
487
+ options.append((msg_index, preview))
488
+
489
+ selected = await choose(options)
490
+ if selected is None:
491
+ return None
492
+
493
+ # Look up the full text of the selected message for prefill.
494
+ original_text = ""
495
+ for msg_index, text in user_turns:
496
+ if msg_index == selected:
497
+ original_text = text
498
+ break
499
+
500
+ # Persist the rewind event and truncate in-memory messages.
501
+ await self.store.append_rewind(self.session_id, selected)
502
+ self.agent.messages = messages[:selected]
503
+
504
+ self.view.console.print(f"[green]{TOOL_MARKER}[/green] [dim]rewound[/dim]")
505
+ if self.agent.messages:
506
+ self.view.print_history_preview(self.agent.messages)
507
+ else:
508
+ self.view.console.print("[dim]conversation is now empty[/dim]")
509
+
510
+ return original_text
511
+
512
+ async def _resume_session(self) -> None:
513
+ """Switch to another saved session in the current workspace."""
514
+
515
+ sessions = await self.store.list_sessions(cwd=self.agent.cwd)
516
+ sessions = [s for s in sessions if s.get("id") != self.session_id]
517
+ if not sessions:
518
+ self.view.console.print("[dim]no other sessions in this workspace[/dim]")
519
+ return
520
+
521
+ options: list[tuple[dict[str, Any], str]] = []
522
+ for s in sessions:
523
+ title = str(s.get("title") or "New chat")[:40]
524
+ ts = format_local_timestamp(str(s.get("updated_at") or ""), "%m-%d %H:%M")
525
+ label = f"{title} {ts}" if ts else title
526
+ options.append((s, label))
527
+
528
+ session = await choose(options)
529
+ if session is None:
530
+ return
531
+
532
+ self.session_id = str(session.get("id") or "")
533
+ data = await self.store.load_session(self.session_id)
534
+ if not data:
535
+ self.view.console.print("[red]failed to load session[/red]")
536
+ return
537
+ messages = data.get("messages") or []
538
+ loaded_session = data.get("session") or session
539
+ self.agent = clone_agent(self.agent, store=self.store, session_id=self.session_id, messages=messages)
540
+ self.view.print_header(
541
+ provider=self.agent.provider,
542
+ model=self.agent.model,
543
+ session=loaded_session,
544
+ mode="resumed",
545
+ message_count=len(messages),
546
+ reasoning_effort=self.agent.reasoning_effort,
547
+ )
548
+ self.view.print_history_preview(messages)
549
+
550
+ async def _switch_provider(self) -> None:
551
+ """Prompt for a configured provider and apply it to the active agent."""
552
+
553
+ options = list_provider_options(self.agent.settings)
554
+ current = get_provider_option(self.agent.settings, provider=self.agent.provider, api_base=self.agent.api_base)
555
+
556
+ choices: list[tuple[str, str]] = []
557
+ for option in options:
558
+ models = " ".join(option.models[:3])
559
+ if len(option.models) > 3:
560
+ models += f" +{len(option.models) - 3}"
561
+ choices.append((option.name, f"{option.name} {models}"))
562
+
563
+ selected = await choose(choices, default=current.name if current else None)
564
+ if selected is not None:
565
+ await self._apply_provider_change(selected)
566
+
567
+ async def _switch_model(self) -> None:
568
+ """Prompt for a model supported by the current provider runtime."""
569
+
570
+ models = list_model_options(
571
+ self.agent.settings,
572
+ provider=self.agent.provider,
573
+ api_base=self.agent.api_base,
574
+ current_model=self.agent.model,
575
+ )
576
+ if not models:
577
+ self.view.console.print("[dim]no configured models for the current provider[/dim]")
578
+ return
579
+
580
+ choices = [(m, m) for m in models]
581
+ selected = await choose(choices, default=self.agent.model)
582
+ if selected is not None:
583
+ await self._apply_model_change(selected)
584
+
585
+ async def _apply_provider_change(self, provider_name: str) -> None:
586
+ """Switch the active provider, keeping session history unchanged."""
587
+
588
+ try:
589
+ changed = await update_agent_runtime(
590
+ self.agent,
591
+ provider_name=provider_name,
592
+ model=None,
593
+ )
594
+ except ValueError as exc:
595
+ self.view.console.print(f"[red]{exc}[/red]")
596
+ return
597
+
598
+ label = f"{self.agent.provider} / {self.agent.model}"
599
+ if self.agent.reasoning_effort:
600
+ label += f" [effort: {self.agent.reasoning_effort}]"
601
+ self._print_runtime_status("provider/model", label, changed=changed)
602
+
603
+ async def _apply_model_change(self, model_name: str) -> None:
604
+ """Switch the active model for the current provider runtime."""
605
+
606
+ current = get_provider_option(self.agent.settings, provider=self.agent.provider, api_base=self.agent.api_base)
607
+ provider_name = current.name if current else self.agent.provider
608
+
609
+ try:
610
+ changed = await update_agent_runtime(
611
+ self.agent,
612
+ provider_name=provider_name,
613
+ model=model_name,
614
+ )
615
+ except ValueError as exc:
616
+ self.view.console.print(f"[red]{exc}[/red]")
617
+ return
618
+
619
+ self._print_runtime_status("model", self.agent.model, changed=changed)
620
+
621
+ async def _switch_effort(self) -> None:
622
+ """Prompt for a reasoning effort level."""
623
+
624
+ if not self._supports_effort_or_warn():
625
+ return
626
+
627
+ current = self.agent.reasoning_effort or "auto"
628
+ choices = [(o, o) for o in REASONING_EFFORT_OPTIONS]
629
+ selected = await choose(choices, default=current)
630
+ if selected is not None:
631
+ self._apply_effort_change(selected)
632
+
633
+ def _apply_effort_change(self, effort: str) -> None:
634
+ """Apply a reasoning effort change to the active agent."""
635
+
636
+ if not self._supports_effort_or_warn():
637
+ return
638
+
639
+ cleaned = effort.strip().lower()
640
+ if cleaned in ("auto", ""):
641
+ resolved = None
642
+ elif cleaned in REASONING_EFFORT_OPTIONS:
643
+ resolved = cleaned
644
+ else:
645
+ self.view.console.print(f"[red]unknown effort: {effort}[/red]")
646
+ return
647
+
648
+ changed = update_reasoning_effort(self.agent, resolved)
649
+ display = resolved or "default"
650
+ self._print_runtime_status("effort", display, changed=changed)