micro-cc 0.2.0__tar.gz → 0.2.2__tar.gz

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 (158) hide show
  1. {micro_cc-0.2.0 → micro_cc-0.2.2}/PKG-INFO +1 -1
  2. {micro_cc-0.2.0 → micro_cc-0.2.2}/pyproject.toml +1 -1
  3. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/claude_loop_.py +20 -3
  4. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/models/anthropic.py +13 -2
  5. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/models/litellm.py +11 -1
  6. micro_cc-0.2.2/src/micro_cc/models/ollama.py +372 -0
  7. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/screens/microcc-styles.tcss +5 -1
  8. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/screens/window_overlay_.py +43 -10
  9. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/start_live_.py +374 -24
  10. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/tools/browser_tool_.py +2 -2
  11. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/tools/computer_tool_.py +2 -2
  12. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/utils/helpers.py +3 -1
  13. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/utils/msg_store_.py +47 -0
  14. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/utils/tokenization_simple.py +4 -4
  15. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc.egg-info/PKG-INFO +1 -1
  16. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc.egg-info/SOURCES.txt +1 -0
  17. {micro_cc-0.2.0 → micro_cc-0.2.2}/README.md +0 -0
  18. {micro_cc-0.2.0 → micro_cc-0.2.2}/setup.cfg +0 -0
  19. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/__init__.py +0 -0
  20. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/browser/__init__.py +0 -0
  21. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/browser/_cookies.py +0 -0
  22. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/browser/_md_convert.py +0 -0
  23. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/browser/browser_manager.py +0 -0
  24. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/browser/simpletextbrowser.py +0 -0
  25. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/cache/__init__.py +0 -0
  26. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/cache/redis_cache.py +0 -0
  27. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/execute_tool.py +0 -0
  28. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/models/__init__.py +0 -0
  29. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/models/schema.py +0 -0
  30. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/screens/__init__.py +0 -0
  31. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/screens/md_render_.py +0 -0
  32. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/SKILL.md +0 -0
  33. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/ArsenalSC-OFL.txt +0 -0
  34. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
  35. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
  36. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/BigShoulders-OFL.txt +0 -0
  37. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
  38. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/Boldonse-OFL.txt +0 -0
  39. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/Boldonse-Regular.ttf +0 -0
  40. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
  41. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +0 -0
  42. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
  43. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
  44. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
  45. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/CrimsonPro-OFL.txt +0 -0
  46. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
  47. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/DMMono-OFL.txt +0 -0
  48. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/DMMono-Regular.ttf +0 -0
  49. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/EricaOne-OFL.txt +0 -0
  50. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/EricaOne-Regular.ttf +0 -0
  51. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/GeistMono-Bold.ttf +0 -0
  52. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/GeistMono-OFL.txt +0 -0
  53. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/GeistMono-Regular.ttf +0 -0
  54. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/Gloock-OFL.txt +0 -0
  55. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/Gloock-Regular.ttf +0 -0
  56. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
  57. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/IBMPlexMono-OFL.txt +0 -0
  58. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
  59. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
  60. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
  61. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
  62. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
  63. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
  64. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
  65. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
  66. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +0 -0
  67. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
  68. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
  69. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
  70. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/Italiana-OFL.txt +0 -0
  71. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/Italiana-Regular.ttf +0 -0
  72. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
  73. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +0 -0
  74. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
  75. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/Jura-Light.ttf +0 -0
  76. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/Jura-Medium.ttf +0 -0
  77. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/Jura-OFL.txt +0 -0
  78. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/LibreBaskerville-OFL.txt +0 -0
  79. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
  80. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
  81. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
  82. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
  83. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/Lora-OFL.txt +0 -0
  84. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
  85. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/NationalPark-Bold.ttf +0 -0
  86. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/NationalPark-OFL.txt +0 -0
  87. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/NationalPark-Regular.ttf +0 -0
  88. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +0 -0
  89. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
  90. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
  91. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/Outfit-OFL.txt +0 -0
  92. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
  93. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
  94. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/PixelifySans-OFL.txt +0 -0
  95. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/PoiretOne-OFL.txt +0 -0
  96. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
  97. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
  98. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/RedHatMono-OFL.txt +0 -0
  99. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
  100. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/Silkscreen-OFL.txt +0 -0
  101. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
  102. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
  103. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/SmoochSans-OFL.txt +0 -0
  104. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
  105. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/Tektur-OFL.txt +0 -0
  106. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
  107. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/WorkSans-Bold.ttf +0 -0
  108. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
  109. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/WorkSans-Italic.ttf +0 -0
  110. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/WorkSans-OFL.txt +0 -0
  111. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/WorkSans-Regular.ttf +0 -0
  112. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt +0 -0
  113. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
  114. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/github/SKILL.md +0 -0
  115. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/pptx/SKILL.md +0 -0
  116. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/pptx/editing.md +0 -0
  117. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/pptx/pptxgenjs.md +0 -0
  118. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/pptx/scripts/__init__.py +0 -0
  119. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/pptx/scripts/add_slide.py +0 -0
  120. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/pptx/scripts/clean.py +0 -0
  121. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/pptx/scripts/office/helpers/__init__.py +0 -0
  122. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/pptx/scripts/office/helpers/merge_runs.py +0 -0
  123. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/pptx/scripts/office/helpers/simplify_redlines.py +0 -0
  124. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/pptx/scripts/office/pack.py +0 -0
  125. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/pptx/scripts/office/soffice.py +0 -0
  126. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/pptx/scripts/office/unpack.py +0 -0
  127. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/pptx/scripts/office/validate.py +0 -0
  128. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/pptx/scripts/office/validators/__init__.py +0 -0
  129. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/pptx/scripts/office/validators/base.py +0 -0
  130. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/pptx/scripts/office/validators/docx.py +0 -0
  131. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/pptx/scripts/office/validators/pptx.py +0 -0
  132. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/pptx/scripts/office/validators/redlining.py +0 -0
  133. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/pptx/scripts/thumbnail.py +0 -0
  134. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/skill_loader.py +0 -0
  135. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/skills/xlsx/SKILL.md +0 -0
  136. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/tests/__init__.py +0 -0
  137. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/tests/test_messages.py +0 -0
  138. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/tools/__init__.py +0 -0
  139. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/tools/bash_tool.py +0 -0
  140. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/tools/file_tools_.py +0 -0
  141. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/tools/mcp_client_.py +0 -0
  142. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/tools/plan_tools_.py +0 -0
  143. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/tools/search_tool_.py +0 -0
  144. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/tools/skill_tools_.py +0 -0
  145. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/tools/vision_tools_.py +0 -0
  146. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/tools/web_tools_.py +0 -0
  147. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/utils/__init__.py +0 -0
  148. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/utils/annotate_.py +0 -0
  149. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/utils/claude_md_loader.py +0 -0
  150. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/utils/file_watcher_.py +0 -0
  151. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/utils/process_tracker.py +0 -0
  152. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/utils/tokenization.py +0 -0
  153. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/utils/utils.py +0 -0
  154. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc/web_.py +0 -0
  155. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc.egg-info/dependency_links.txt +0 -0
  156. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc.egg-info/entry_points.txt +0 -0
  157. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc.egg-info/requires.txt +0 -0
  158. {micro_cc-0.2.0 → micro_cc-0.2.2}/src/micro_cc.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: micro-cc
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Harness that gives frontier models full system access — shell, filesystem, browser, MCP — running directly on the metal.
5
5
  Author: George Salapa
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "micro-cc"
7
- version = "0.2.00"
7
+ version = "0.2.02"
8
8
  description = "Harness that gives frontier models full system access — shell, filesystem, browser, MCP — running directly on the metal."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -11,6 +11,7 @@ from micro_cc.execute_tool import execute_tool_call
11
11
  from micro_cc.models.schema import function_to_schema
12
12
  from micro_cc.models.anthropic import a_model_call
13
13
  from micro_cc.models.litellm import l_model_call
14
+ from micro_cc.models.ollama import o_model_call
14
15
  from micro_cc.skills.skill_loader import get_skill_summary
15
16
  from micro_cc.tools.bash_tool import bash_
16
17
  from micro_cc.tools.file_tools_ import read_, write_, edit_, glob_, grep_
@@ -28,7 +29,7 @@ from micro_cc.tools.search_tool_ import (
28
29
  from micro_cc.tools.vision_tools_ import vision
29
30
  from micro_cc.tools.mcp_client_ import resolve_mcp_for_litellm, call_mcp_tool
30
31
  from micro_cc.utils.msg_store_ import load_summary
31
- from micro_cc.utils.tokenization_simple import token_cutter
32
+ from micro_cc.utils.tokenization_simple import token_cutter, token_stats
32
33
  from micro_cc.utils.helpers import tokenizer
33
34
  from micro_cc.utils.claude_md_loader import load_claude_md_file
34
35
  from micro_cc.utils.helpers import get_endpoint
@@ -47,10 +48,10 @@ async def claude_loop(
47
48
  project_dir,
48
49
  watcher=None,
49
50
  model="sonnet-4.6",
51
+ max_tokens=80000,
50
52
  ):
51
53
  """micro cc"""
52
54
 
53
- max_tokens = 80000
54
55
  end_resp = get_endpoint()
55
56
  redis_state = RedisStateManager()
56
57
 
@@ -191,7 +192,16 @@ make_plan, update_step, show_full_plan, add_step
191
192
  trimmed_loop_msgs = system_context + plan_msg + trimmed_loop_msgs
192
193
 
193
194
  try:
194
- if end_resp == "LiteLLM":
195
+ if end_resp == "Ollama":
196
+ stream = await o_model_call(
197
+ input=trimmed_loop_msgs,
198
+ model=model,
199
+ tools=tool_schemas,
200
+ mcp_openai_tools=mcp_openai_tools or None,
201
+ thinking=True,
202
+ stream=True,
203
+ )
204
+ elif end_resp == "LiteLLM":
195
205
  stream = await l_model_call(
196
206
  input=trimmed_loop_msgs,
197
207
  model=model,
@@ -215,6 +225,7 @@ make_plan, update_step, show_full_plan, add_step
215
225
  break
216
226
 
217
227
  response = None
228
+ api_usage = {}
218
229
  async for event in stream:
219
230
  if event["type"] == "text_delta":
220
231
  yield {"type": "text_delta", "content": event["text"]}
@@ -222,6 +233,12 @@ make_plan, update_step, show_full_plan, add_step
222
233
  yield {"type": "thinking_delta", "content": event["thinking"]}
223
234
  elif event["type"] == "response":
224
235
  response = event["response"]
236
+ api_usage = event.get("usage", {})
237
+
238
+ if api_usage.get("input"):
239
+ token_stats["input"] = api_usage["input"]
240
+ if api_usage.get("output"):
241
+ token_stats["output"] += api_usage["output"]
225
242
 
226
243
  if response is None:
227
244
  yield {"type": "error", "message": "model call failed after retries"}
@@ -34,9 +34,15 @@ async def _wrap_stream(raw_stream):
34
34
  thinking_signature = ""
35
35
  tool_blocks = []
36
36
  current_tool = None
37
+ usage = {}
37
38
 
38
39
  async for event in raw_stream:
39
- if event.type == "content_block_start":
40
+ if event.type == "message_start":
41
+ u = getattr(event.message, "usage", None)
42
+ if u:
43
+ usage["input"] = getattr(u, "input_tokens", 0)
44
+
45
+ elif event.type == "content_block_start":
40
46
  if event.content_block.type == "tool_use":
41
47
  current_tool = {"id": event.content_block.id, "name": event.content_block.name, "input_json": ""}
42
48
 
@@ -59,6 +65,11 @@ async def _wrap_stream(raw_stream):
59
65
  tool_blocks.append(current_tool | {"input": args})
60
66
  current_tool = None
61
67
 
68
+ elif event.type == "message_delta":
69
+ u = getattr(event, "usage", None)
70
+ if u:
71
+ usage["output"] = getattr(u, "output_tokens", 0)
72
+
62
73
  blocks = []
63
74
  if thinking:
64
75
  blocks.append(ContentBlock(type="thinking", thinking=thinking, signature=thinking_signature))
@@ -67,7 +78,7 @@ async def _wrap_stream(raw_stream):
67
78
  for tb in tool_blocks:
68
79
  blocks.append(ContentBlock(type="tool_use", name=tb["name"], input=tb["input"], id=tb["id"]))
69
80
 
70
- yield {"type": "response", "response": Response(content=blocks)}
81
+ yield {"type": "response", "response": Response(content=blocks), "usage": usage}
71
82
 
72
83
  async def a_model_call(
73
84
  input: Union[List[Dict[str, Any]], str],
@@ -218,6 +218,7 @@ async def _wrap_stream(openai_stream):
218
218
  thinking = ""
219
219
  thinking_signature = ""
220
220
  tool_calls = {} # index -> {id, name, arguments_str}
221
+ usage = {}
221
222
 
222
223
  async for chunk in openai_stream:
223
224
  delta = chunk.choices[0].delta
@@ -246,6 +247,12 @@ async def _wrap_stream(openai_stream):
246
247
  if tc.function and tc.function.arguments:
247
248
  tool_calls[idx]["args"] += tc.function.arguments
248
249
 
250
+ # Usage — LiteLLM sends it on the final chunk
251
+ u = getattr(chunk, "usage", None)
252
+ if u:
253
+ usage["input"] = getattr(u, "prompt_tokens", 0)
254
+ usage["output"] = getattr(u, "completion_tokens", 0)
255
+
249
256
  # Stream done — build final Response for claude_loop_
250
257
  blocks = []
251
258
  if thinking:
@@ -262,7 +269,7 @@ async def _wrap_stream(openai_stream):
262
269
  ContentBlock(type="tool_use", name=tc["name"], input=args, id=tc["id"])
263
270
  )
264
271
 
265
- yield {"type": "response", "response": Response(content=blocks)}
272
+ yield {"type": "response", "response": Response(content=blocks), "usage": usage}
266
273
 
267
274
 
268
275
  # ---------------------------------------------------------------------------
@@ -332,6 +339,9 @@ async def l_model_call(
332
339
  "messages": messages,
333
340
  }
334
341
 
342
+ if stream:
343
+ api_params["stream_options"] = {"include_usage": True}
344
+
335
345
  if thinking:
336
346
  api_params["reasoning_effort"] = "high"
337
347
 
@@ -0,0 +1,372 @@
1
+ """Ollama model caller — fully standalone.
2
+
3
+ Talks to a locally running Ollama daemon via its OpenAI-compatible
4
+ endpoint at http://localhost:11434/v1.
5
+
6
+ Handled internally — no dependency on other model callers:
7
+ - Anthropic message format → OpenAI chat format conversion
8
+ - Tool schema conversion (Anthropic shape → OpenAI function shape)
9
+ - Streaming wrapper that yields Anthropic-shaped events
10
+ - Ollama-specific quirks:
11
+ * thinking field is `reasoning` (not `reasoning_content` like LiteLLM/DeepSeek)
12
+ * final stream chunk with include_usage has empty `choices` list
13
+ * `think: True` passed via extra_body toggles reasoning mode
14
+ * `num_ctx` passed via extra_body.options overrides the 4K default
15
+
16
+ Env vars:
17
+ OLLAMA_BASE_URL default http://localhost:11434/v1
18
+ OLLAMA_MODEL user picks any tag pulled via `ollama pull <tag>`
19
+ OLLAMA_NUM_CTX default 32768 — fits Qwen3-14B KV cache in ~5GB
20
+ OLLAMA_MAX_OUTPUT default 4096 — generation budget per response
21
+ """
22
+
23
+ from typing import List, Dict, Any, Optional, Union
24
+ from openai import AsyncOpenAI
25
+ from dotenv import load_dotenv
26
+ from dataclasses import dataclass, field
27
+ import asyncio
28
+ import json
29
+ import os
30
+
31
+ load_dotenv(os.path.expanduser("~/.micro-cc/.env"))
32
+ load_dotenv()
33
+
34
+
35
+ # ---------------------------------------------------------------------------
36
+ # Response shape — same as Anthropic SDK so claude_loop_.py works unchanged
37
+ # ---------------------------------------------------------------------------
38
+
39
+ @dataclass
40
+ class ContentBlock:
41
+ type: str
42
+ text: str = ""
43
+ thinking: str = ""
44
+ signature: str = ""
45
+ name: str = ""
46
+ input: dict = field(default_factory=dict)
47
+ id: str = ""
48
+
49
+
50
+ @dataclass
51
+ class Response:
52
+ content: list = field(default_factory=list)
53
+
54
+
55
+ # ---------------------------------------------------------------------------
56
+ # Converters — Anthropic format → OpenAI chat format
57
+ # ---------------------------------------------------------------------------
58
+
59
+ def _tools_to_openai(anthropic_tools, mcp_openai_tools=None):
60
+ """Anthropic tool schemas + MCP tools → OpenAI function-calling format."""
61
+ out = []
62
+ for t in anthropic_tools:
63
+ out.append({
64
+ "type": "function",
65
+ "function": {
66
+ "name": t["name"],
67
+ "description": t.get("description", ""),
68
+ "parameters": t.get("input_schema", {}),
69
+ },
70
+ })
71
+ if mcp_openai_tools:
72
+ out.extend(mcp_openai_tools)
73
+ return out or None
74
+
75
+
76
+ def _msgs_to_openai(anthropic_msgs):
77
+ """Convert Anthropic-format message list to OpenAI chat format.
78
+
79
+ Handles: system, user (string/multimodal/tool_result),
80
+ assistant (string/tool_use blocks). Thinking blocks are dropped —
81
+ local models can't replay signed thinking, and Ollama's OpenAI-compat
82
+ endpoint doesn't accept them on the resend path.
83
+ """
84
+ out = []
85
+ for msg in anthropic_msgs:
86
+ role = msg.get("role")
87
+ content = msg.get("content", "")
88
+
89
+ if role == "system":
90
+ out.append({"role": "system", "content": content})
91
+ continue
92
+
93
+ # --- Assistant with content blocks ---
94
+ if role == "assistant" and isinstance(content, list):
95
+ text_parts = []
96
+ tool_calls = []
97
+ for block in content:
98
+ if not isinstance(block, dict):
99
+ continue
100
+ btype = block.get("type")
101
+ if btype == "text":
102
+ text_parts.append(block["text"])
103
+ elif btype == "tool_use":
104
+ tool_calls.append({
105
+ "id": block["id"],
106
+ "type": "function",
107
+ "function": {
108
+ "name": block["name"],
109
+ "arguments": json.dumps(block["input"]),
110
+ },
111
+ })
112
+ # thinking blocks: intentionally skipped for Ollama
113
+
114
+ m = {
115
+ "role": "assistant",
116
+ "content": "\n".join(text_parts) if text_parts else None,
117
+ }
118
+ if tool_calls:
119
+ m["tool_calls"] = tool_calls
120
+ out.append(m)
121
+ continue
122
+
123
+ # --- User with content blocks (tool_result, text, image, document) ---
124
+ if role == "user" and isinstance(content, list):
125
+ tool_results = []
126
+ other_parts = []
127
+
128
+ for block in content:
129
+ if not isinstance(block, dict):
130
+ continue
131
+ btype = block.get("type")
132
+ if btype == "tool_result":
133
+ tool_results.append({
134
+ "role": "tool",
135
+ "tool_call_id": block["tool_use_id"],
136
+ "content": block.get("content", ""),
137
+ })
138
+ elif btype == "text":
139
+ other_parts.append({"type": "text", "text": block["text"]})
140
+ elif btype == "image":
141
+ source = block.get("source", {})
142
+ media = source.get("media_type", "image/jpeg")
143
+ other_parts.append({
144
+ "type": "image_url",
145
+ "image_url": {
146
+ "url": f"data:{media};base64,{source.get('data', '')}",
147
+ },
148
+ })
149
+ elif btype == "document":
150
+ other_parts.append({
151
+ "type": "text",
152
+ "text": "[PDF document attached]",
153
+ })
154
+
155
+ out.extend(tool_results)
156
+
157
+ if other_parts:
158
+ if len(other_parts) == 1 and other_parts[0].get("type") == "text":
159
+ out.append({"role": "user", "content": other_parts[0]["text"]})
160
+ else:
161
+ out.append({"role": "user", "content": other_parts})
162
+ continue
163
+
164
+ # --- Simple string content ---
165
+ out.append({"role": role, "content": content})
166
+
167
+ return out
168
+
169
+
170
+ # ---------------------------------------------------------------------------
171
+ # Response wrappers — OpenAI shape → Anthropic-like Response
172
+ # ---------------------------------------------------------------------------
173
+
174
+ def _extract_reasoning(obj) -> str:
175
+ """Ollama surfaces thinking on `reasoning` via model_dump; OpenAI client
176
+ doesn't type that attribute, so fall back to model_dump().get('reasoning')."""
177
+ rc = getattr(obj, "reasoning", None)
178
+ if rc:
179
+ return rc
180
+ try:
181
+ dumped = obj.model_dump()
182
+ return dumped.get("reasoning") or ""
183
+ except Exception:
184
+ return ""
185
+
186
+
187
+ def _wrap_response(openai_resp):
188
+ """Non-streaming OpenAI ChatCompletion → Anthropic-like Response."""
189
+ choice = openai_resp.choices[0]
190
+ msg = choice.message
191
+ blocks = []
192
+
193
+ reasoning = _extract_reasoning(msg)
194
+ if reasoning:
195
+ blocks.append(ContentBlock(type="thinking", thinking=reasoning, signature=""))
196
+
197
+ if msg.content:
198
+ blocks.append(ContentBlock(type="text", text=msg.content))
199
+
200
+ if msg.tool_calls:
201
+ for tc in msg.tool_calls:
202
+ try:
203
+ args = json.loads(tc.function.arguments)
204
+ except (json.JSONDecodeError, TypeError):
205
+ args = {}
206
+ blocks.append(ContentBlock(
207
+ type="tool_use",
208
+ name=tc.function.name,
209
+ input=args,
210
+ id=tc.id,
211
+ ))
212
+
213
+ return Response(content=blocks)
214
+
215
+
216
+ async def _wrap_stream(openai_stream):
217
+ """Accumulate OpenAI chunks, yield Anthropic-shaped events,
218
+ finish with a final Response containing all content blocks."""
219
+ text = ""
220
+ thinking = ""
221
+ tool_calls = {} # index → {id, name, args}
222
+ usage = {}
223
+
224
+ async for chunk in openai_stream:
225
+ # Final usage-only chunks have empty choices (Ollama sends this
226
+ # when stream_options.include_usage is on).
227
+ if not chunk.choices:
228
+ u = getattr(chunk, "usage", None)
229
+ if u:
230
+ usage["input"] = getattr(u, "prompt_tokens", 0)
231
+ usage["output"] = getattr(u, "completion_tokens", 0)
232
+ continue
233
+
234
+ delta = chunk.choices[0].delta
235
+
236
+ # Visible text
237
+ if delta.content:
238
+ text += delta.content
239
+ yield {"type": "text_delta", "text": delta.content}
240
+
241
+ # Thinking — Ollama uses `reasoning`, not `reasoning_content`
242
+ rc = _extract_reasoning(delta)
243
+ if rc:
244
+ thinking += rc
245
+ yield {"type": "thinking_delta", "thinking": rc}
246
+
247
+ # Tool calls — must buffer arguments across chunks
248
+ if delta.tool_calls:
249
+ for tc in delta.tool_calls:
250
+ idx = tc.index
251
+ if idx not in tool_calls:
252
+ tool_calls[idx] = {
253
+ "id": tc.id or "",
254
+ "name": tc.function.name if tc.function else "",
255
+ "args": "",
256
+ }
257
+ if tc.function and tc.function.arguments:
258
+ tool_calls[idx]["args"] += tc.function.arguments
259
+
260
+ # Some Ollama builds attach usage on the final non-empty chunk
261
+ u = getattr(chunk, "usage", None)
262
+ if u:
263
+ usage["input"] = getattr(u, "prompt_tokens", 0)
264
+ usage["output"] = getattr(u, "completion_tokens", 0)
265
+
266
+ blocks = []
267
+ if thinking:
268
+ blocks.append(ContentBlock(type="thinking", thinking=thinking, signature=""))
269
+ if text:
270
+ blocks.append(ContentBlock(type="text", text=text))
271
+ for tc in tool_calls.values():
272
+ args = json.loads(tc["args"]) if tc["args"] else {}
273
+ blocks.append(ContentBlock(type="tool_use", name=tc["name"], input=args, id=tc["id"]))
274
+
275
+ yield {"type": "response", "response": Response(content=blocks), "usage": usage}
276
+
277
+
278
+ # ---------------------------------------------------------------------------
279
+ # Main entry point — same signature pattern as models/anthropic.py
280
+ # ---------------------------------------------------------------------------
281
+
282
+ async def o_model_call(
283
+ input: Union[List[Dict[str, Any]], str],
284
+ model: str = None,
285
+ encoded_image: Optional[Union[str, List[str]]] = None,
286
+ tools: Optional[List[Dict[str, Any]]] = None,
287
+ mcp_openai_tools: list = None,
288
+ stream: bool = False,
289
+ thinking: bool = False,
290
+ max_tokens: int = None,
291
+ client_timeout: int = 480,
292
+ pdf: str = None,
293
+ retries: int = 3,
294
+ ):
295
+ base_url = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434/v1")
296
+ model = model or os.getenv("OLLAMA_MODEL")
297
+ num_ctx = int(os.getenv("OLLAMA_NUM_CTX", "32768"))
298
+ if max_tokens is None:
299
+ max_tokens = int(os.getenv("OLLAMA_MAX_OUTPUT", "4096"))
300
+
301
+ client = AsyncOpenAI(
302
+ base_url=base_url,
303
+ api_key="ollama", # dummy — Ollama ignores the key
304
+ timeout=client_timeout,
305
+ )
306
+ base_sleep = 2
307
+
308
+ # ---- Build messages ----
309
+ messages = []
310
+ if pdf:
311
+ messages = [{"role": "user", "content": f"[PDF document attached]\n\n{input}"}]
312
+ elif encoded_image:
313
+ content = []
314
+ imgs = [encoded_image] if isinstance(encoded_image, str) else encoded_image
315
+ for img in imgs:
316
+ content.append({
317
+ "type": "image_url",
318
+ "image_url": {"url": f"data:image/jpeg;base64,{img}"},
319
+ })
320
+ content.append({"type": "text", "text": input})
321
+ messages = [{"role": "user", "content": content}]
322
+ elif isinstance(input, str):
323
+ messages = [{"role": "user", "content": input}]
324
+ else:
325
+ messages = _msgs_to_openai(input)
326
+
327
+ # ---- API parameters ----
328
+ # Ollama-specific: num_ctx in extra_body.options, think toggle at root
329
+ extra_body = {"options": {"num_ctx": num_ctx}}
330
+ if thinking:
331
+ extra_body["think"] = True
332
+
333
+ api_params = {
334
+ "model": model,
335
+ "max_tokens": max_tokens,
336
+ "stream": stream,
337
+ "messages": messages,
338
+ "extra_body": extra_body,
339
+ }
340
+
341
+ if stream:
342
+ api_params["stream_options"] = {"include_usage": True}
343
+
344
+ if tools:
345
+ openai_tools = _tools_to_openai(tools, mcp_openai_tools=mcp_openai_tools)
346
+ if openai_tools:
347
+ api_params["tools"] = openai_tools
348
+ api_params["tool_choice"] = "auto"
349
+
350
+ # ---- Call with retries ----
351
+ for attempt in range(retries):
352
+ try:
353
+ response = await client.chat.completions.create(**api_params)
354
+ if stream:
355
+ return _wrap_stream(response)
356
+ else:
357
+ return _wrap_response(response)
358
+
359
+ except Exception as e:
360
+ import traceback
361
+ print(f"\n[ollama model_call]: {e}", flush=True)
362
+ traceback.print_exc()
363
+ if attempt < retries - 1:
364
+ sleep_time = base_sleep * (2 ** attempt)
365
+ print(
366
+ f"\n[ollama model_call]: Retrying in {sleep_time}s (attempt {attempt + 1}/{retries})..."
367
+ )
368
+ await asyncio.sleep(sleep_time)
369
+ else:
370
+ print(f"\n[ollama model_call]: Failed after {retries} attempts")
371
+
372
+ return None
@@ -24,12 +24,16 @@ PromptInput {
24
24
  border-left: none;
25
25
  border-right: none;
26
26
  }
27
+ PromptInput > .text-area--cursor {
28
+ color: $background;
29
+ background: white;
30
+ }
27
31
  #loader {
28
32
  height: 1;
29
33
  display: none;
30
34
  color: orange;
31
35
  }
32
- #model-picker {
36
+ #model-picker, #rewind-picker, #slash-picker {
33
37
  display: none;
34
38
  height: auto;
35
39
  max-height: 6;
@@ -14,7 +14,7 @@ BANNER = """\
14
14
  [dim]───────[/] [bold]on the metal[/] [dim]─────────────────────[/]
15
15
  [dim italic]Knowledge work is by extension a coding problem.[/]
16
16
 
17
- [white]enter[/] [dim]submit[/] [dim]·[/] [white]esc[/] [dim]interrupt[/] [dim]·[/] [white]/model[/] [dim]switch[/] [dim]·[/] [white]/copy[/] [dim]clipboard[/] [dim]·[/] [white]/clear[/] [dim]reset[/] [dim]·[/] [white]/exit[/] [dim]quit[/]
17
+ [white]enter[/] [dim]submit[/] [dim]·[/] [white]esc[/] [dim]interrupt[/] [dim]·[/] [white]/model[/] [dim]switch[/] [dim]·[/] [white]/rewind[/] [dim]undo[/] [dim]·[/] [white]/copy[/] [dim]clipboard[/] [dim]·[/] [white]/clear[/] [dim]reset[/] [dim]·[/] [white]/exit[/] [dim]quit[/]
18
18
  """
19
19
 
20
20
 
@@ -37,6 +37,8 @@ class MessageRow(Static):
37
37
  t = msg["type"]
38
38
  if t == "user":
39
39
  return Text(f"› {msg['content']}", style="bold")
40
+ elif t == "user_queued":
41
+ return Text(f"⋯queued {msg['content']}", style="dim italic #ffA500")
40
42
  elif t == "text":
41
43
  return render_md(msg["content"])
42
44
  elif t == "thinking":
@@ -45,19 +47,50 @@ class MessageRow(Static):
45
47
  render_md(msg["content"], dim=True),
46
48
  )
47
49
  elif t == "tool_call":
48
- if msg.get("result") is None:
49
- return Text(f"⟐ {msg['name']} ⋯", style="yellow")
50
- else:
51
- return Group(
52
- Text(f"⟐ {msg['name']}", style="green"),
53
- Text(f" {msg.get('result', '')}", style="dim"),
54
- )
50
+ name = msg['name']
51
+ result = msg.get('result')
52
+ if result is None:
53
+ return Text(f"⟐ {name} ⋯", style="yellow")
54
+ return Text(f"⟐ {name} → {str(result)[:40]}", style="dim")
55
55
  elif t == "error":
56
56
  return Text(f"△ {msg['content']}", style="red")
57
57
  elif t == "approval":
58
+ inp = msg.get('input', {})
59
+ name = msg['name']
60
+ _sep = Text("·" * 40, style="dim")
61
+ if name == "bash_":
62
+ code = inp.get('command', str(inp))
63
+ return Group(
64
+ Text(f"◇ {name} [y/N]", style="bold yellow"),
65
+ render_md(f"```bash\n{code}\n```"),
66
+ _sep,
67
+ )
68
+ elif name == "edit_":
69
+ parts = [Text(f"◇ {name} [y/N]", style="bold yellow")]
70
+ fp = inp.get('file_path', '')
71
+ if fp:
72
+ parts.append(Text(f" {fp}", style="dim"))
73
+ old = inp.get('old_string', '')
74
+ new = inp.get('new_string', '')
75
+ if old or new:
76
+ diff = f"```diff\n- {old}\n+ {new}\n```" if old else f"```\n{new}\n```"
77
+ parts.append(render_md(diff))
78
+ parts.append(_sep)
79
+ return Group(*parts)
80
+ elif name == "write_":
81
+ parts = [Text(f"◇ {name} [y/N]", style="bold yellow")]
82
+ fp = inp.get('file_path', '')
83
+ if fp:
84
+ parts.append(Text(f" {fp}", style="dim"))
85
+ content = inp.get('content', '')
86
+ if content:
87
+ preview = content[:500] + ("…" if len(content) > 500 else "")
88
+ parts.append(render_md(f"```\n{preview}\n```"))
89
+ parts.append(_sep)
90
+ return Group(*parts)
58
91
  return Group(
59
- Text(f"◇ {msg['name']} [y/N]", style="bold yellow"),
60
- Text(f" {msg.get('input', {})}", style="dim"),
92
+ Text(f"◇ {name} [y/N]", style="bold yellow"),
93
+ Text(f" {str(inp)[:80]}", style="dim"),
61
94
  )
62
95
  return Text("")
63
96