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/cli/render.py ADDED
@@ -0,0 +1,693 @@
1
+ """Rendering helpers for the terminal CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import time
6
+ from collections.abc import Awaitable, Callable
7
+ from datetime import datetime
8
+ from difflib import SequenceMatcher
9
+ from typing import Any
10
+
11
+ from rich.console import Console, ConsoleOptions, RenderResult
12
+ from rich.live import Live
13
+ from rich.markdown import CodeBlock as _RichCodeBlock
14
+ from rich.markdown import Heading as _RichHeading
15
+ from rich.markdown import Markdown
16
+ from rich.spinner import Spinner
17
+ from rich.syntax import Syntax
18
+ from rich.table import Table
19
+ from rich.text import Text
20
+ from rich.theme import Theme
21
+
22
+ from mycode.core.agent import Agent
23
+ from mycode.core.messages import flatten_message_text
24
+
25
+ from .theme import (
26
+ ACCENT,
27
+ CODE_THEME,
28
+ ERROR,
29
+ ERROR_MARKER,
30
+ MUTED,
31
+ PROMPT_CHAR,
32
+ PROVIDER,
33
+ STATS,
34
+ SUCCESS,
35
+ TERMINAL_THEME,
36
+ THINKING,
37
+ THINKING_SYMBOL,
38
+ TOOL_MARKER,
39
+ TOOL_NAME,
40
+ WARNING,
41
+ )
42
+
43
+ # Override Rich's default inline-code style ("bold cyan on black") to remove
44
+ # the hardcoded background color that clashes with terminal themes.
45
+ _THEME = Theme(
46
+ {
47
+ "markdown.code": "bold blue" if TERMINAL_THEME == "light" else "bold cyan",
48
+ "markdown.code_block": "blue" if TERMINAL_THEME == "light" else "cyan",
49
+ }
50
+ )
51
+
52
+ console = Console(highlight=False, theme=_THEME)
53
+
54
+
55
+ class _LeftHeading(_RichHeading):
56
+ """Heading variant that left-aligns all heading levels."""
57
+
58
+ def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
59
+ text = self.text
60
+ text.justify = "left"
61
+ if self.tag == "h1":
62
+ yield Text("")
63
+ yield text
64
+ yield Text("")
65
+ elif self.tag == "h2":
66
+ yield Text("")
67
+ yield text
68
+ else:
69
+ yield text
70
+
71
+
72
+ class _CleanCodeBlock(_RichCodeBlock):
73
+ """Code block that uses the terminal background instead of the theme background."""
74
+
75
+ def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
76
+ code = str(self.text).rstrip()
77
+ yield Syntax(
78
+ code,
79
+ self.lexer_name,
80
+ theme=self.theme,
81
+ word_wrap=True,
82
+ padding=0,
83
+ background_color="default",
84
+ )
85
+
86
+
87
+ class _LeftMarkdown(Markdown):
88
+ """Markdown subclass with left-aligned headings and clean code blocks."""
89
+
90
+ elements = {
91
+ **Markdown.elements,
92
+ "heading_open": _LeftHeading,
93
+ "fence": _CleanCodeBlock,
94
+ "code_block": _CleanCodeBlock,
95
+ }
96
+
97
+
98
+ _TOOL_OUTPUT_MAX_LINES = 5
99
+
100
+ # Maps built-in tool names to the argument key most useful as a one-line preview.
101
+ _TOOL_PREVIEW_KEY: dict[str, str] = {
102
+ "read": "path",
103
+ "write": "path",
104
+ "edit": "path",
105
+ "bash": "command",
106
+ }
107
+
108
+
109
+ def format_local_timestamp(value: str, display_format: str) -> str:
110
+ """Format an ISO timestamp with a simple local fallback."""
111
+
112
+ if not value:
113
+ return ""
114
+ try:
115
+ timestamp = datetime.fromisoformat(value.replace("Z", "+00:00"))
116
+ return timestamp.astimezone().strftime(display_format)
117
+ except ValueError:
118
+ return value[:16].replace("T", " ")
119
+
120
+
121
+ def _format_usage(usage: dict[str, Any]) -> str:
122
+ """Format token usage into a compact string."""
123
+ input_t = usage.get("input_tokens") or usage.get("prompt_tokens") or 0
124
+ output_t = usage.get("output_tokens") or usage.get("completion_tokens") or 0
125
+ total = input_t + output_t
126
+ if total:
127
+ return f"{total:,} tokens"
128
+ return ""
129
+
130
+
131
+ class TerminalView:
132
+ """Print static CLI output such as headers, previews, and session lists."""
133
+
134
+ def __init__(self, output: Console | None = None) -> None:
135
+ self.console = output or console
136
+
137
+ def print_header(
138
+ self,
139
+ *,
140
+ provider: str,
141
+ model: str,
142
+ session: dict[str, Any],
143
+ mode: str,
144
+ message_count: int,
145
+ reasoning_effort: str | None = None,
146
+ ) -> None:
147
+ """Print the current session header shown above the interactive chat."""
148
+
149
+ self.console.print()
150
+
151
+ title = session.get("title") or ""
152
+ session_id = str(session.get("id") or "")[:8]
153
+
154
+ line = Text()
155
+ line.append("mycode", style=ACCENT)
156
+ line.append(" · ", style=MUTED)
157
+ line.append(provider, style=PROVIDER)
158
+ line.append(" / ", style=MUTED)
159
+ line.append(model)
160
+ if reasoning_effort:
161
+ line.append(" · ", style=MUTED)
162
+ line.append(reasoning_effort, style=MUTED)
163
+ if session_id:
164
+ line.append(" · ", style=MUTED)
165
+ line.append(session_id, style=MUTED)
166
+ self.console.print(line)
167
+
168
+ if mode == "resumed":
169
+ meta = Text()
170
+ meta.append("resumed", style=WARNING)
171
+ if title and title != "New chat":
172
+ meta.append(" · ", style=MUTED)
173
+ meta.append(title, style=MUTED)
174
+ if message_count:
175
+ meta.append(" · ", style=MUTED)
176
+ meta.append(f"{message_count} msgs", style=MUTED)
177
+ self.console.print(meta)
178
+
179
+ def print_history_preview(self, messages: list[dict[str, Any]]) -> None:
180
+ """Print recent conversation turns for resumed sessions."""
181
+
182
+ turns = self.history_preview_entries(messages)
183
+ if not turns:
184
+ return
185
+
186
+ self.console.print(Text("recent", style=MUTED))
187
+ for turn in turns:
188
+ self.console.print()
189
+ for kind, content in turn:
190
+ if kind == "user":
191
+ lines = str(content).splitlines() or [""]
192
+ first = Text()
193
+ first.append(f"{PROMPT_CHAR} ", style=ACCENT)
194
+ first.append(lines[0])
195
+ self.console.print(first)
196
+ for line in lines[1:]:
197
+ self.console.print(Text(f" {line}"))
198
+ elif kind == "text":
199
+ self.console.print(_LeftMarkdown(str(content), code_theme=CODE_THEME))
200
+ else:
201
+ name, args = content
202
+ preview = ""
203
+ if isinstance(args, dict) and args:
204
+ key = _TOOL_PREVIEW_KEY.get(name.lower())
205
+ raw = args.get(key) if key else next(iter(args.values()), "")
206
+ preview = str(raw or "")
207
+ if len(preview) > 60:
208
+ preview = preview[:60] + "…"
209
+
210
+ line = Text()
211
+ line.append(f"{TOOL_MARKER} ", style=SUCCESS)
212
+ line.append(name.capitalize(), style=TOOL_NAME)
213
+ if preview:
214
+ line.append(f" {preview}", style=MUTED)
215
+ self.console.print(line)
216
+
217
+ def print_session_list(
218
+ self,
219
+ sessions: list[dict[str, Any]],
220
+ *,
221
+ include_cwd: bool = False,
222
+ current_session_id: str | None = None,
223
+ heading: str = "sessions",
224
+ ) -> None:
225
+ """Print saved sessions in a compact table for selection commands."""
226
+
227
+ if not sessions:
228
+ self.console.print(Text("no sessions found", style=MUTED))
229
+ return
230
+
231
+ self.console.print(Text(f"{heading} ({len(sessions)})", style=MUTED))
232
+ self.console.print()
233
+
234
+ title_limit = 24 if include_cwd else 40
235
+ model_limit = 18 if include_cwd else 24
236
+ cwd_limit = 32 if include_cwd else 48
237
+
238
+ table = Table(box=None, show_header=False, padding=(0, 2, 0, 0), expand=False)
239
+ table.add_column(no_wrap=True) # marker
240
+ table.add_column(no_wrap=True) # index
241
+ table.add_column(no_wrap=True) # session id
242
+ table.add_column(no_wrap=True) # timestamp
243
+ table.add_column() # title
244
+ table.add_column(no_wrap=True) # model
245
+ if include_cwd:
246
+ table.add_column() # cwd
247
+
248
+ for index, session in enumerate(sessions, start=1):
249
+ session_id = str(session.get("id") or "-")
250
+ is_current = bool(current_session_id and session_id == current_session_id)
251
+
252
+ marker = Text("●", style=SUCCESS) if is_current else Text(" ")
253
+ idx = Text(str(index), style=MUTED)
254
+ sid = Text(session_id[:12], style=MUTED)
255
+ ts = Text(self._format_timestamp(str(session.get("updated_at") or "")), style=MUTED)
256
+ title = Text(self._shorten(str(session.get("title") or "New chat"), limit=title_limit))
257
+
258
+ model = str(session.get("model") or "")
259
+ model_text = Text(
260
+ f"[{self._shorten(model, limit=model_limit)}]" if model else "",
261
+ style=MUTED,
262
+ )
263
+
264
+ row: list[Any] = [marker, idx, sid, ts, title, model_text]
265
+ if include_cwd:
266
+ cwd = str(session.get("cwd") or "")
267
+ row.append(Text(self._shorten(cwd, limit=cwd_limit), style=MUTED))
268
+
269
+ table.add_row(*row)
270
+
271
+ self.console.print(table)
272
+
273
+ def history_preview_entries(
274
+ self,
275
+ messages: list[dict[str, Any]],
276
+ *,
277
+ limit: int = 3,
278
+ ) -> list[list[tuple[str, Any]]]:
279
+ """Return the last few readable conversation turns for resumed sessions."""
280
+
281
+ turns: list[list[tuple[str, Any]]] = []
282
+
283
+ for message in messages:
284
+ role = message.get("role")
285
+ content = message.get("content")
286
+
287
+ if role == "user":
288
+ if (message.get("meta") or {}).get("synthetic"):
289
+ continue
290
+ # Use the shared flattener so attached file payload blocks stay out
291
+ # of the readable history preview.
292
+ text = flatten_message_text(message, include_thinking=False)
293
+ if not isinstance(content, list):
294
+ text = text or str(content or "").strip()
295
+ if text:
296
+ turns.append([("user", text)])
297
+ continue
298
+
299
+ if role != "assistant":
300
+ continue
301
+
302
+ parts: list[tuple[str, Any]] = []
303
+ if isinstance(content, list):
304
+ for block in content:
305
+ if not isinstance(block, dict):
306
+ continue
307
+ if block.get("type") == "text":
308
+ text = str(block.get("text") or "").strip()
309
+ if text:
310
+ parts.append(("text", text))
311
+ elif block.get("type") == "tool_use":
312
+ parts.append(("tool", (str(block.get("name") or "tool"), block.get("input"))))
313
+ else:
314
+ text = str(content or "").strip()
315
+ if text:
316
+ parts.append(("text", text))
317
+
318
+ if not parts:
319
+ continue
320
+ if not turns:
321
+ turns.append([])
322
+ turns[-1].extend(parts)
323
+
324
+ return turns if limit <= 0 else turns[-limit:]
325
+
326
+ @staticmethod
327
+ def _shorten(value: str, *, limit: int = 96) -> str:
328
+ text = " ".join((value or "").split())
329
+ if len(text) <= limit:
330
+ return text
331
+ return text[: limit - 1] + "…"
332
+
333
+ @staticmethod
334
+ def _format_timestamp(value: str) -> str:
335
+ return format_local_timestamp(value, "%Y-%m-%d %H:%M") or "-"
336
+
337
+
338
+ class ReplyRenderer:
339
+ """Render one assistant reply, including thinking and tool output."""
340
+
341
+ def __init__(self, output: Console | None = None, *, live_mode: bool = True) -> None:
342
+ self._console = output or console
343
+ self._live_mode = live_mode
344
+ self._live: Live | None = None
345
+ self._reasoning: list[str] = []
346
+ self._text: list[str] = []
347
+ self._text_started = False
348
+ self._printed_static_reasoning = False
349
+ # Timing & stats
350
+ self._response_start_time: float | None = None
351
+ self._thinking_start_time: float | None = None
352
+ self._thinking_collapsed = False
353
+ self._had_prior_output = False
354
+ self._tool_output_count = 0
355
+ self._tool_start_time: float | None = None
356
+ self._tool_name: str = ""
357
+ self._tool_args: dict[str, Any] = {}
358
+ self._tool_buffered = False
359
+ self._usage: dict[str, Any] | None = None
360
+
361
+ async def render(
362
+ self,
363
+ agent: Agent,
364
+ message: str | dict[str, Any],
365
+ *,
366
+ on_persist: Callable[[dict[str, Any]], Awaitable[None]] | None = None,
367
+ ) -> int:
368
+ """Stream one assistant turn to the terminal and return its exit code."""
369
+
370
+ exit_code = 0
371
+ self._response_start_time = time.monotonic()
372
+
373
+ async def _tracking_persist(msg: dict[str, Any]) -> None:
374
+ if msg.get("role") == "assistant":
375
+ meta = msg.get("meta") or {}
376
+ usage = meta.get("usage")
377
+ if usage:
378
+ self._usage = usage
379
+ if on_persist:
380
+ await on_persist(msg)
381
+
382
+ if self._live_mode:
383
+ self._ensure_live()
384
+
385
+ async for event in agent.achat(message, on_persist=_tracking_persist):
386
+ match event.type:
387
+ case "reasoning":
388
+ self.reasoning(event.data.get("delta", ""))
389
+ case "text":
390
+ self.text(event.data.get("delta", ""))
391
+ case "tool_start":
392
+ tool_call = event.data.get("tool_call") or {}
393
+ self.tool_start(tool_call.get("name", ""), tool_call.get("input") or {})
394
+ case "tool_output":
395
+ self.tool_output(event.data.get("output", ""))
396
+ case "tool_done":
397
+ model_text = str(event.data.get("model_text") or "")
398
+ display_text = str(event.data.get("display_text") or "")
399
+ is_error = bool(event.data.get("is_error"))
400
+ self.tool_done(model_text, display_text, is_error=is_error)
401
+ if is_error:
402
+ exit_code = 1
403
+ case "compact":
404
+ self.compact(event.data.get("message", ""))
405
+ case "error":
406
+ exit_code = 1
407
+ self.error(event.data.get("message", ""))
408
+
409
+ self.finish()
410
+ return exit_code
411
+
412
+ def reasoning(self, chunk: str) -> None:
413
+ """Handle one streamed reasoning chunk from the agent."""
414
+
415
+ if self._thinking_start_time is None:
416
+ self._thinking_start_time = time.monotonic()
417
+ self._reasoning.append(chunk)
418
+ if self._live_mode:
419
+ self._ensure_live()
420
+ self._update()
421
+
422
+ def text(self, chunk: str) -> None:
423
+ """Handle one streamed assistant text chunk."""
424
+
425
+ self._finalize_reasoning_phase()
426
+ if not self._text_started and self._had_prior_output:
427
+ self._console.print()
428
+ self._text_started = True
429
+ if self._live_mode:
430
+ self._text.append(chunk)
431
+ self._ensure_live()
432
+ self._update()
433
+ elif chunk:
434
+ self._console.print(chunk, end="", markup=False, highlight=False)
435
+
436
+ def tool_start(self, name: str, args: dict[str, Any]) -> None:
437
+ """Render the start of a tool call."""
438
+
439
+ self._finalize_reasoning_phase()
440
+ self._reset_stream_state()
441
+ if not self._live_mode:
442
+ self._console.print()
443
+
444
+ self._tool_start_time = time.monotonic()
445
+ self._tool_output_count = 0
446
+ self._tool_name = name
447
+ self._tool_args = args
448
+
449
+ # Bash streams output — print header immediately.
450
+ # Other tools are fast — defer to tool_done for single-line display.
451
+ if name.lower() == "bash":
452
+ self._print_tool_header(name, args)
453
+ self._tool_buffered = False
454
+ else:
455
+ self._tool_buffered = True
456
+
457
+ def tool_output(self, line: str) -> None:
458
+ """Render one streamed output line from a running tool."""
459
+
460
+ if not line:
461
+ return
462
+ self._tool_output_count += 1
463
+ if self._tool_output_count <= _TOOL_OUTPUT_MAX_LINES:
464
+ text = Text(" ", style=MUTED)
465
+ text.append(line, style=MUTED)
466
+ self._console.print(text)
467
+
468
+ def tool_done(self, model_text: str, display_text: str, *, is_error: bool) -> None:
469
+ """Render the final tool result."""
470
+ shown_text = display_text or model_text
471
+
472
+ elapsed = 0.0
473
+ if self._tool_start_time is not None:
474
+ elapsed = time.monotonic() - self._tool_start_time
475
+ self._tool_start_time = None
476
+ duration = f"{elapsed:.1f}s" if elapsed >= 0.5 else ""
477
+
478
+ if self._tool_buffered:
479
+ # Non-streaming tools (read, write, edit): single-line output
480
+ self._tool_buffered = False
481
+ if is_error:
482
+ self._print_tool_header(self._tool_name, self._tool_args)
483
+ first_line = shown_text.split("\n", 1)[0][:100]
484
+ self._console.print(Text(f" {first_line}", style=ERROR))
485
+ else:
486
+ suffix = self._format_edit_suffix(self._tool_name, self._tool_args)
487
+ self._print_tool_header(self._tool_name, self._tool_args, suffix=suffix)
488
+ else:
489
+ # Bash: streaming tool
490
+ if is_error and self._tool_output_count == 0:
491
+ first_line = shown_text.split("\n", 1)[0][:100]
492
+ self._console.print(Text(f" {first_line}", style=ERROR))
493
+ else:
494
+ parts: list[str] = []
495
+ truncated = self._tool_output_count - _TOOL_OUTPUT_MAX_LINES
496
+ if truncated > 0:
497
+ parts.append(f"+{truncated} lines")
498
+ if duration:
499
+ parts.append(duration)
500
+ if parts:
501
+ self._console.print(Text(f" {' · '.join(parts)}", style=MUTED))
502
+
503
+ self._had_prior_output = True
504
+
505
+ def _print_tool_header(
506
+ self,
507
+ name: str,
508
+ args: dict[str, Any],
509
+ *,
510
+ suffix: Text | None = None,
511
+ ) -> None:
512
+ """Print the ``⏺ Name preview [suffix]`` tool header line."""
513
+
514
+ preview = ""
515
+ if args:
516
+ key = _TOOL_PREVIEW_KEY.get(name.lower())
517
+ raw = args.get(key) if key else next(iter(args.values()), "")
518
+ preview = str(raw or "")
519
+ if len(preview) > 60:
520
+ preview = preview[:60] + "…"
521
+
522
+ text = Text()
523
+ text.append(f"{TOOL_MARKER} ", style=SUCCESS)
524
+ text.append(name.capitalize(), style=TOOL_NAME)
525
+ if preview:
526
+ text.append(f" {preview}", style=MUTED)
527
+ if suffix:
528
+ text.append(" ")
529
+ text.append_text(suffix)
530
+ self._console.print(text)
531
+
532
+ @staticmethod
533
+ def _format_edit_suffix(name: str, args: dict[str, Any]) -> Text | None:
534
+ """Return the real added/removed line counts for one edit call."""
535
+
536
+ if name.lower() != "edit":
537
+ return None
538
+
539
+ old_text = args.get("oldText")
540
+ new_text = args.get("newText")
541
+ if not isinstance(old_text, str) or not isinstance(new_text, str):
542
+ return None
543
+
544
+ added = 0
545
+ removed = 0
546
+ for tag, old_start, old_end, new_start, new_end in SequenceMatcher(
547
+ None,
548
+ old_text.splitlines(),
549
+ new_text.splitlines(),
550
+ ).get_opcodes():
551
+ if tag in {"replace", "delete"}:
552
+ removed += old_end - old_start
553
+ if tag in {"replace", "insert"}:
554
+ added += new_end - new_start
555
+
556
+ suffix = Text()
557
+ suffix.append(f"+{added}", style="green")
558
+ suffix.append(f" −{removed}", style="red")
559
+ return suffix
560
+
561
+ def compact(self, message: str) -> None:
562
+ """Render a context compaction notification."""
563
+
564
+ self._finalize_reasoning_phase()
565
+ self._reset_stream_state()
566
+ text = Text(f"⟳ {message}", style=MUTED)
567
+ self._console.print(text)
568
+
569
+ def error(self, message: str) -> None:
570
+ """Render a terminal-visible error message for the current turn."""
571
+
572
+ self._finalize_reasoning_phase()
573
+ self._reset_stream_state()
574
+ text = Text(f"{ERROR_MARKER} ", style=ERROR)
575
+ text.append(message, style=ERROR)
576
+ self._console.print(text)
577
+
578
+ def cancel(self) -> None:
579
+ """Render a cancellation marker and reset transient state."""
580
+
581
+ self._finalize_reasoning_phase()
582
+ self._reset_stream_state()
583
+ self._console.print(Text("cancelled", style=MUTED))
584
+
585
+ def finish(self) -> None:
586
+ """Flush the current turn and print timing or token statistics."""
587
+
588
+ self._finalize_reasoning_phase()
589
+ self._reset_stream_state()
590
+
591
+ parts: list[str] = []
592
+ if self._response_start_time is not None:
593
+ elapsed = time.monotonic() - self._response_start_time
594
+ parts.append(f"{elapsed:.1f}s")
595
+ if self._usage:
596
+ token_str = _format_usage(self._usage)
597
+ if token_str:
598
+ parts.append(token_str)
599
+
600
+ if parts:
601
+ self._console.print(Text(" " + " · ".join(parts), style=STATS))
602
+
603
+ if not self._live_mode:
604
+ self._console.print()
605
+
606
+ # -- Internal helpers ----------------------------------------------------
607
+
608
+ def _finalize_reasoning_phase(self) -> None:
609
+ """Finish the reasoning phase before text, tools, or final output."""
610
+
611
+ if self._live_mode:
612
+ self._collapse_thinking()
613
+ else:
614
+ self._print_static_reasoning()
615
+
616
+ def _collapse_thinking(self) -> None:
617
+ """In live mode: stop the spinner and print a one-line summary."""
618
+ if self._thinking_collapsed or not self._reasoning:
619
+ return
620
+ self._thinking_collapsed = True
621
+
622
+ if self._live is not None:
623
+ self._live.transient = True
624
+ self._live.stop()
625
+ self._live = None
626
+
627
+ duration = ""
628
+ if self._thinking_start_time is not None:
629
+ elapsed = time.monotonic() - self._thinking_start_time
630
+ duration = f" · {elapsed:.1f}s"
631
+
632
+ self._console.print(Text(f"{THINKING_SYMBOL} thought{duration}", style=THINKING))
633
+ self._had_prior_output = True
634
+ self._reasoning.clear()
635
+
636
+ def _print_static_reasoning(self) -> None:
637
+ """Non-live mode: print full reasoning content."""
638
+ if self._live_mode or self._printed_static_reasoning or not self._reasoning:
639
+ return
640
+
641
+ duration = ""
642
+ if self._thinking_start_time is not None:
643
+ elapsed = time.monotonic() - self._thinking_start_time
644
+ duration = f" · {elapsed:.1f}s"
645
+
646
+ self._console.print(Text(f"{THINKING_SYMBOL} thinking{duration}", style=THINKING))
647
+ self._console.print("".join(self._reasoning), style="dim")
648
+ self._printed_static_reasoning = True
649
+ self._had_prior_output = True
650
+
651
+ def _ensure_live(self) -> None:
652
+ if self._live is None:
653
+ self._live = Live(self._build_live_renderable(), console=self._console, refresh_per_second=12)
654
+ self._live.start()
655
+
656
+ def _update(self) -> None:
657
+ if self._live is not None:
658
+ self._live.update(self._build_live_renderable())
659
+
660
+ def _reset_stream_state(self) -> None:
661
+ """Stop live rendering and clear transient buffers for the current phase."""
662
+
663
+ if self._live is not None:
664
+ self._live.stop()
665
+ self._live = None
666
+ self._reasoning.clear()
667
+ self._text.clear()
668
+ self._printed_static_reasoning = False
669
+ self._thinking_collapsed = False
670
+ self._thinking_start_time = None
671
+
672
+ def _build_live_renderable(self):
673
+ """Build the Rich renderable used while a reply is streaming."""
674
+
675
+ # No content yet: plain spinner
676
+ if not self._reasoning and not self._text:
677
+ return Spinner("dots", style="dim")
678
+
679
+ # Thinking in progress: show rolling preview of reasoning content
680
+ if self._reasoning and not self._text:
681
+ content = " ".join("".join(self._reasoning).split())
682
+ if content:
683
+ preview = content[-80:].strip()
684
+ if len(content) > 80:
685
+ preview = "…" + preview
686
+ return Spinner("dots", text=Text(f" {preview}", style=THINKING), style="dim")
687
+ return Spinner("dots", text=Text(" thinking…", style=THINKING), style="dim")
688
+
689
+ # Text streaming: render as markdown (thinking already collapsed)
690
+ if self._text:
691
+ return _LeftMarkdown("".join(self._text), code_theme=CODE_THEME)
692
+
693
+ return Spinner("dots", style="dim")