micro-cc 0.2.30__tar.gz → 0.2.32__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.
- {micro_cc-0.2.30 → micro_cc-0.2.32}/PKG-INFO +1 -4
- {micro_cc-0.2.30 → micro_cc-0.2.32}/README.md +0 -2
- {micro_cc-0.2.30 → micro_cc-0.2.32}/pyproject.toml +1 -2
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/browser/_md_convert.py +20 -6
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/screens/microcc-styles.tcss +1 -1
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/start_live_.py +121 -148
- micro_cc-0.2.32/src/micro_cc/utils/helpers.py +133 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc.egg-info/PKG-INFO +1 -4
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc.egg-info/SOURCES.txt +1 -2
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc.egg-info/requires.txt +0 -1
- micro_cc-0.2.30/src/micro_cc/utils/helpers.py +0 -101
- micro_cc-0.2.30/src/micro_cc/utils/utils.py +0 -46
- {micro_cc-0.2.30 → micro_cc-0.2.32}/setup.cfg +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/__init__.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/browser/__init__.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/browser/_cookies.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/browser/browser_manager.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/browser/simpletextbrowser.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/cache/__init__.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/cache/redis_cache.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/claude_loop_.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/code_test/__init__.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/code_test/run_token_cutter.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/code_test/test_messages.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/execute_tool.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/models/__init__.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/models/anthropic.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/models/litellm.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/models/ollama.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/models/schema.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/screens/__init__.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/screens/md_render_.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/screens/window_overlay_.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/SKILL.md +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/ArsenalSC-OFL.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/BigShoulders-OFL.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/Boldonse-OFL.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/Boldonse-Regular.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/CrimsonPro-OFL.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/DMMono-OFL.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/DMMono-Regular.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/EricaOne-OFL.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/EricaOne-Regular.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/GeistMono-Bold.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/GeistMono-OFL.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/GeistMono-Regular.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/Gloock-OFL.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/Gloock-Regular.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/IBMPlexMono-OFL.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/Italiana-OFL.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/Italiana-Regular.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/Jura-Light.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/Jura-Medium.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/Jura-OFL.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/LibreBaskerville-OFL.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/Lora-OFL.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/NationalPark-Bold.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/NationalPark-OFL.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/NationalPark-Regular.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/Outfit-OFL.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/PixelifySans-OFL.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/PoiretOne-OFL.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/RedHatMono-OFL.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/Silkscreen-OFL.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/SmoochSans-OFL.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/Tektur-OFL.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/WorkSans-Bold.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/WorkSans-Italic.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/WorkSans-OFL.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/WorkSans-Regular.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/github/SKILL.md +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/pptx/SKILL.md +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/pptx/editing.md +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/pptx/pptxgenjs.md +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/pptx/scripts/__init__.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/pptx/scripts/add_slide.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/pptx/scripts/clean.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/pptx/scripts/office/helpers/__init__.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/pptx/scripts/office/helpers/merge_runs.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/pptx/scripts/office/helpers/simplify_redlines.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/pptx/scripts/office/pack.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/pptx/scripts/office/soffice.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/pptx/scripts/office/unpack.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/pptx/scripts/office/validate.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/pptx/scripts/office/validators/__init__.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/pptx/scripts/office/validators/base.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/pptx/scripts/office/validators/docx.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/pptx/scripts/office/validators/pptx.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/pptx/scripts/office/validators/redlining.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/pptx/scripts/thumbnail.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/skill_loader.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/skills/xlsx/SKILL.md +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/tools/__init__.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/tools/bash_tool.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/tools/browser_tool_.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/tools/computer_tool_.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/tools/file_tools_.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/tools/mcp_client_.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/tools/memory_tool_.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/tools/search_tool_.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/tools/skill_tools_.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/tools/todo_tools_.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/tools/vision_tools_.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/tools/web_tools_.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/utils/__init__.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/utils/annotate_.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/utils/claude_md_loader.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/utils/file_watcher_.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/utils/memory_store_.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/utils/msg_store_.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/utils/process_tracker.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/utils/tokenization_simple.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc/web_.py +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc.egg-info/dependency_links.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/src/micro_cc.egg-info/entry_points.txt +0 -0
- {micro_cc-0.2.30 → micro_cc-0.2.32}/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.
|
|
3
|
+
Version: 0.2.32
|
|
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
|
|
@@ -21,7 +21,6 @@ Requires-Dist: mcp>=1.26
|
|
|
21
21
|
Requires-Dist: numpy>=2.4
|
|
22
22
|
Requires-Dist: openai>=2.16
|
|
23
23
|
Requires-Dist: openpyxl>=3.1
|
|
24
|
-
Requires-Dist: pandas>=3.0
|
|
25
24
|
Requires-Dist: pathvalidate>=3.3
|
|
26
25
|
Requires-Dist: pdfminer.six>=20260107
|
|
27
26
|
Requires-Dist: pillow>=12.0
|
|
@@ -60,8 +59,6 @@ Frontier models with full system access — shell, filesystem, browser, MCP —
|
|
|
60
59
|
|
|
61
60
|
## Install
|
|
62
61
|
|
|
63
|
-
Please use `iTerm2`. Highly recommended terminal UI.
|
|
64
|
-
|
|
65
62
|
```bash
|
|
66
63
|
pip install micro-cc
|
|
67
64
|
```
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "micro-cc"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.32"
|
|
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"
|
|
@@ -27,7 +27,6 @@ dependencies = [
|
|
|
27
27
|
"numpy>=2.4",
|
|
28
28
|
"openai>=2.16",
|
|
29
29
|
"openpyxl>=3.1",
|
|
30
|
-
"pandas>=3.0",
|
|
31
30
|
"pathvalidate>=3.3",
|
|
32
31
|
"pdfminer.six>=20260107",
|
|
33
32
|
"pillow>=12.0",
|
|
@@ -6,7 +6,6 @@ from micro_cc.models.anthropic import a_model_call
|
|
|
6
6
|
from micro_cc.models.litellm import l_model_call
|
|
7
7
|
from bs4 import BeautifulSoup
|
|
8
8
|
import pdfminer.high_level
|
|
9
|
-
import pandas as pd
|
|
10
9
|
import markdownify
|
|
11
10
|
import subprocess
|
|
12
11
|
import traceback
|
|
@@ -456,12 +455,27 @@ class XlsxConverter(HtmlConverter):
|
|
|
456
455
|
if extension.lower() not in [".xlsx", ".xls"]:
|
|
457
456
|
return None
|
|
458
457
|
|
|
459
|
-
|
|
458
|
+
# Imported lazily — this is the only xlsx-reading path in the app,
|
|
459
|
+
# no reason to pay openpyxl's cost on every startup for a feature
|
|
460
|
+
# most sessions never touch.
|
|
461
|
+
import openpyxl
|
|
462
|
+
|
|
463
|
+
workbook = openpyxl.load_workbook(local_path, data_only=True, read_only=True)
|
|
460
464
|
md_content = ""
|
|
461
|
-
for
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
+
for sheet_name in workbook.sheetnames:
|
|
466
|
+
rows = list(workbook[sheet_name].iter_rows(values_only=True))
|
|
467
|
+
md_content += f"## {sheet_name}\n"
|
|
468
|
+
if rows:
|
|
469
|
+
header, *body = rows
|
|
470
|
+
cell = lambda tag, v: f"<{tag}>{'' if v is None else v}</{tag}>"
|
|
471
|
+
html_content = "<table><thead><tr>"
|
|
472
|
+
html_content += "".join(cell("th", v) for v in header)
|
|
473
|
+
html_content += "</tr></thead><tbody>"
|
|
474
|
+
for row in body:
|
|
475
|
+
html_content += "<tr>" + "".join(cell("td", v) for v in row) + "</tr>"
|
|
476
|
+
html_content += "</tbody></table>"
|
|
477
|
+
md_content += self._convert(html_content).text_content.strip() + "\n\n"
|
|
478
|
+
workbook.close()
|
|
465
479
|
|
|
466
480
|
return DocumentConverterResult(
|
|
467
481
|
title=None,
|
|
@@ -33,7 +33,7 @@ PromptInput > .text-area--cursor {
|
|
|
33
33
|
display: none;
|
|
34
34
|
color: green;
|
|
35
35
|
}
|
|
36
|
-
#model-picker, #rewind-picker, #slash-picker, #dangerous-picker {
|
|
36
|
+
#model-picker, #rewind-picker, #slash-picker, #dangerous-picker, #login-picker {
|
|
37
37
|
display: none;
|
|
38
38
|
height: auto;
|
|
39
39
|
max-height: 10;
|
|
@@ -4,9 +4,12 @@ import re
|
|
|
4
4
|
import subprocess
|
|
5
5
|
import sys
|
|
6
6
|
import platform
|
|
7
|
+
|
|
8
|
+
from textual.getters import app
|
|
7
9
|
from micro_cc.claude_loop_ import claude_loop, DEFAULT_DANGEROUS, GATEABLE_TOOLS
|
|
8
10
|
from micro_cc.utils.msg_store_ import load_msgs, rewind_msgs, summarize_and_trim, store_msgs, erase_msgs, erase_summary
|
|
9
11
|
from micro_cc.utils.tokenization_simple import token_stats
|
|
12
|
+
from micro_cc.utils.helpers import has_configured_endpoint, apply_login, check_ollama
|
|
10
13
|
from micro_cc.tools.browser_tool_ import close_browser
|
|
11
14
|
from micro_cc.utils.process_tracker import init as init_process_tracker
|
|
12
15
|
from micro_cc.cache.redis_cache import RedisStateManager
|
|
@@ -52,6 +55,7 @@ class PromptInput(TextArea):
|
|
|
52
55
|
("/copy", "clipboard"),
|
|
53
56
|
("/dangerous", "approve"),
|
|
54
57
|
("/exit", "quit"),
|
|
58
|
+
("/login", "endpoint"),
|
|
55
59
|
("/model", "switch"),
|
|
56
60
|
("/rewind", "undo"),
|
|
57
61
|
]
|
|
@@ -129,6 +133,20 @@ class MicroApp(App):
|
|
|
129
133
|
|
|
130
134
|
MODEL_OPTIONS = ["sonnet-5", "opus-4.8", "opus-4.7", "opus-4.6", "sonnet-4.6", "haiku-4.5"]
|
|
131
135
|
|
|
136
|
+
LOGIN_SEQUENCE = {
|
|
137
|
+
"Anthropic": ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "SERPAPI_KEY"],
|
|
138
|
+
"LiteLLM": ["LITELLM_BASE_URL", "LITELLM_API_KEY", "SERPAPI_KEY"],
|
|
139
|
+
"Ollama": ["OLLAMA_MODEL", "SERPAPI_KEY"],
|
|
140
|
+
}
|
|
141
|
+
LOGIN_PROMPTS = {
|
|
142
|
+
"ANTHROPIC_API_KEY": "Paste ANTHROPIC_API_KEY (sk-ant-...)",
|
|
143
|
+
"OPENAI_API_KEY": "Paste OPENAI_API_KEY (embeddings for search_tools)",
|
|
144
|
+
"LITELLM_BASE_URL": "Paste your LiteLLM proxy base URL",
|
|
145
|
+
"LITELLM_API_KEY": "Paste your LITELLM_API_KEY",
|
|
146
|
+
"OLLAMA_MODEL": "Model tag already pulled, e.g. qwen3:14b",
|
|
147
|
+
"SERPAPI_KEY": "SERPAPI_KEY — optional, Enter to skip",
|
|
148
|
+
}
|
|
149
|
+
|
|
132
150
|
_current_model = "sonnet-4.6"
|
|
133
151
|
_trim_budget = 80000
|
|
134
152
|
|
|
@@ -172,7 +190,7 @@ class MicroApp(App):
|
|
|
172
190
|
|
|
173
191
|
# How to discover what you can style
|
|
174
192
|
|
|
175
|
-
# Every Textual widget exposes a set of CSS properties.
|
|
193
|
+
# Every Textual widget exposes a set of CSS properties.
|
|
176
194
|
|
|
177
195
|
# For PromptInput (inherits from TextArea):
|
|
178
196
|
# PromptInput {
|
|
@@ -227,6 +245,12 @@ class MicroApp(App):
|
|
|
227
245
|
## "working on…" nudge (above the input) while a query is active
|
|
228
246
|
self._working_timer = None
|
|
229
247
|
self._spin_i = 0
|
|
248
|
+
# api keys — _login_stage is None, or the exact env-var name currently
|
|
249
|
+
# being collected (see LOGIN_SEQUENCE); _login_values accumulates
|
|
250
|
+
# {env_var_name: typed_value} across the /login flow
|
|
251
|
+
self._login_stage = None
|
|
252
|
+
self._login_provider = None
|
|
253
|
+
self._login_values = {}
|
|
230
254
|
init_process_tracker()
|
|
231
255
|
|
|
232
256
|
_pending_approval = None # asyncio.Event when waiting
|
|
@@ -234,6 +258,52 @@ class MicroApp(App):
|
|
|
234
258
|
_streaming = None # active StreamingRow widget
|
|
235
259
|
_SPINNER = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
|
|
236
260
|
|
|
261
|
+
def _handle_login_input(self, query: str) -> None:
|
|
262
|
+
key_name = self._login_stage
|
|
263
|
+
self._login_values[key_name] = query
|
|
264
|
+
|
|
265
|
+
sequence = self.LOGIN_SEQUENCE[self._login_provider]
|
|
266
|
+
next_index = sequence.index(key_name) + 1
|
|
267
|
+
|
|
268
|
+
if next_index < len(sequence):
|
|
269
|
+
self._login_stage = sequence[next_index]
|
|
270
|
+
self.query_one(PromptInput).placeholder = self.LOGIN_PROMPTS[self._login_stage]
|
|
271
|
+
return
|
|
272
|
+
|
|
273
|
+
if self._login_provider == "Ollama":
|
|
274
|
+
model = self._login_values.get("OLLAMA_MODEL", "").strip()
|
|
275
|
+
error = check_ollama(model)
|
|
276
|
+
if error:
|
|
277
|
+
# Don't finish the flow — bounce back to the same field so the
|
|
278
|
+
# user can retype the tag instead of restarting /login.
|
|
279
|
+
self.notify(error, title="micro-cc", severity="error", timeout=8)
|
|
280
|
+
self._login_stage = "OLLAMA_MODEL"
|
|
281
|
+
self.query_one(PromptInput).placeholder = self.LOGIN_PROMPTS["OLLAMA_MODEL"]
|
|
282
|
+
return
|
|
283
|
+
# get_endpoint()/has_configured_endpoint() key off OLLAMA_BASE_URL,
|
|
284
|
+
# not OLLAMA_MODEL — must persist it even though we never asked
|
|
285
|
+
# for it (default localhost, same as the old .env template).
|
|
286
|
+
self._login_values.setdefault("OLLAMA_BASE_URL", "http://localhost:11434/v1")
|
|
287
|
+
# Instance attrs, not class attrs — this app instance already
|
|
288
|
+
# exists, so there's no reason to mutate MicroApp itself (that
|
|
289
|
+
# was only ever done pre-instantiation, in the old start_()).
|
|
290
|
+
self._current_model = model
|
|
291
|
+
self._trim_budget = int(os.getenv("MICRO_CC_TRIM_BUDGET", "16384"))
|
|
292
|
+
# #model-picker was populated once from MODEL_OPTIONS at compose()
|
|
293
|
+
# time — reassigning self.MODEL_OPTIONS now wouldn't touch the
|
|
294
|
+
# already-mounted widget, so rebuild its options directly.
|
|
295
|
+
model_picker = self.query_one("#model-picker")
|
|
296
|
+
model_picker.clear_options()
|
|
297
|
+
model_picker.add_option(Option(model, id=model))
|
|
298
|
+
|
|
299
|
+
apply_login({k: v for k, v in self._login_values.items() if v}) # drop blanks (skipped SERPAPI)
|
|
300
|
+
self.notify(f"{self._login_provider} configured", title="micro-cc")
|
|
301
|
+
self._login_stage = None
|
|
302
|
+
self._login_provider = None
|
|
303
|
+
self._login_values = {}
|
|
304
|
+
self.query_one(PromptInput).placeholder = ""
|
|
305
|
+
self._refresh_status()
|
|
306
|
+
|
|
237
307
|
def on_paste(self, event) -> None:
|
|
238
308
|
prompt = self.query_one(PromptInput)
|
|
239
309
|
if not prompt.has_focus:
|
|
@@ -263,6 +333,7 @@ class MicroApp(App):
|
|
|
263
333
|
yield OptionList(id="rewind-picker")
|
|
264
334
|
yield SelectionList(id="dangerous-picker")
|
|
265
335
|
yield OptionList(id="slash-picker")
|
|
336
|
+
yield OptionList(id="login-picker")
|
|
266
337
|
yield Static("", id="working") # task nudge, sits just above input
|
|
267
338
|
yield PromptInput(id="prompt")
|
|
268
339
|
yield Static("", id="statusbar")
|
|
@@ -394,13 +465,26 @@ class MicroApp(App):
|
|
|
394
465
|
|
|
395
466
|
def action_cancel_query(self):
|
|
396
467
|
# If a picker is open, ESC just closes it — no cancel
|
|
397
|
-
for picker_id in ("#model-picker", "#rewind-picker", "#dangerous-picker", "#slash-picker"):
|
|
468
|
+
for picker_id in ("#model-picker", "#rewind-picker", "#dangerous-picker", "#slash-picker", "#login-picker"):
|
|
398
469
|
picker = self.query_one(picker_id)
|
|
399
470
|
if picker.display:
|
|
400
471
|
picker.display = False
|
|
401
472
|
self.query_one(PromptInput).focus()
|
|
402
473
|
return
|
|
403
474
|
|
|
475
|
+
# Mid-/login field entry (provider already picked, so the picker
|
|
476
|
+
# above is already closed) — drop the in-progress state. apply_login()
|
|
477
|
+
# only ever runs at the end of a completed sequence, so bailing out
|
|
478
|
+
# here means .env is never touched.
|
|
479
|
+
if self._login_stage is not None:
|
|
480
|
+
self._login_stage = None
|
|
481
|
+
self._login_provider = None
|
|
482
|
+
self._login_values = {}
|
|
483
|
+
prompt = self.query_one(PromptInput)
|
|
484
|
+
prompt.placeholder = ""
|
|
485
|
+
prompt.focus()
|
|
486
|
+
return
|
|
487
|
+
|
|
404
488
|
self.workers.cancel_all()
|
|
405
489
|
# Clear pending approval so next submit isn't swallowed
|
|
406
490
|
if self._pending_approval is not None:
|
|
@@ -680,6 +764,7 @@ class MicroApp(App):
|
|
|
680
764
|
# exclusive=True flag cancels any previous worker on the same node before starting this one.
|
|
681
765
|
|
|
682
766
|
async def on_prompt_input_submitted(self, event):
|
|
767
|
+
|
|
683
768
|
# replaces your prompt_toolkit loop
|
|
684
769
|
query = event.value.strip()
|
|
685
770
|
query = re.sub(
|
|
@@ -689,6 +774,23 @@ class MicroApp(App):
|
|
|
689
774
|
)
|
|
690
775
|
PromptInput._paste_store.clear()
|
|
691
776
|
PromptInput._paste_id = 0
|
|
777
|
+
|
|
778
|
+
# api keys setting menu
|
|
779
|
+
if self._login_stage is not None:
|
|
780
|
+
self._handle_login_input(query)
|
|
781
|
+
self.query_one(PromptInput).clear()
|
|
782
|
+
return
|
|
783
|
+
|
|
784
|
+
if query.lower() == "/login":
|
|
785
|
+
picker = self.query_one("#login-picker")
|
|
786
|
+
picker.clear_options()
|
|
787
|
+
for name in ("Anthropic", "LiteLLM", "Ollama"):
|
|
788
|
+
picker.add_option(Option(name, id=name))
|
|
789
|
+
picker.display = True
|
|
790
|
+
picker.focus()
|
|
791
|
+
self.query_one(PromptInput).clear()
|
|
792
|
+
return
|
|
793
|
+
|
|
692
794
|
if self._pending_approval is not None and not self._pending_approval.is_set():
|
|
693
795
|
self._approval_result = query.lower() in ("", "y", "yes")
|
|
694
796
|
self._pending_approval.set()
|
|
@@ -796,6 +898,14 @@ class MicroApp(App):
|
|
|
796
898
|
# Normal query — mount user message row, then run loop
|
|
797
899
|
await self._mount_row({"type": "user", "content": query})
|
|
798
900
|
self.query_one(PromptInput).clear()
|
|
901
|
+
|
|
902
|
+
# preflight check
|
|
903
|
+
if not has_configured_endpoint():
|
|
904
|
+
err = {"type": "error", "content": "No API endpoint configured — run /login"}
|
|
905
|
+
await self._mount_row(err)
|
|
906
|
+
self.query_one(PromptInput).clear()
|
|
907
|
+
return
|
|
908
|
+
|
|
799
909
|
self._query_active = True
|
|
800
910
|
self.run_worker(self.do_query(query), exclusive=True)
|
|
801
911
|
|
|
@@ -811,6 +921,15 @@ class MicroApp(App):
|
|
|
811
921
|
prompt.focus()
|
|
812
922
|
prompt.post_message(PromptInput.Submitted(prompt))
|
|
813
923
|
|
|
924
|
+
elif picker_id == "login-picker":
|
|
925
|
+
self._login_provider = str(event.option.prompt)
|
|
926
|
+
self._login_stage = self.LOGIN_SEQUENCE[str(event.option.prompt)][0]
|
|
927
|
+
self.query_one("#login-picker").display = False
|
|
928
|
+
prompt = self.query_one(PromptInput)
|
|
929
|
+
prompt.placeholder = self.LOGIN_PROMPTS[self._login_stage]
|
|
930
|
+
self._refresh_status()
|
|
931
|
+
self.query_one(PromptInput).focus()
|
|
932
|
+
|
|
814
933
|
elif picker_id == "model-picker":
|
|
815
934
|
self._current_model = str(event.option.prompt)
|
|
816
935
|
self.query_one("#model-picker").display = False
|
|
@@ -1013,8 +1132,6 @@ class MicroApp(App):
|
|
|
1013
1132
|
self._pending_approval = None
|
|
1014
1133
|
self._finalize_streaming()
|
|
1015
1134
|
|
|
1016
|
-
# self.query_one(PromptInput).disabled = False ## now we are queueing all user msgs
|
|
1017
|
-
|
|
1018
1135
|
self._set_busy(False)
|
|
1019
1136
|
self.query_one(PromptInput).focus()
|
|
1020
1137
|
self._refresh_status()
|
|
@@ -1040,154 +1157,10 @@ class MicroApp(App):
|
|
|
1040
1157
|
self._state_mgr.stop_cleanup_task()
|
|
1041
1158
|
|
|
1042
1159
|
|
|
1043
|
-
OLLAMA_ENV_BLOCK = """\
|
|
1044
|
-
# OR Option 3 Ollama (local open-weight main loop)
|
|
1045
|
-
# Install: brew install ollama && brew services start ollama
|
|
1046
|
-
# Pull a model — pick one that fits your RAM:
|
|
1047
|
-
# ollama pull qwen3:4b (2.5GB, any Mac — smoke test baseline)
|
|
1048
|
-
# ollama pull qwen3:8b (5.2GB, good on 16GB+)
|
|
1049
|
-
# ollama pull qwen3:14b (9.3GB, needs 24GB+ comfortably)
|
|
1050
|
-
# ollama pull qwen3-coder:30b (19GB MoE, needs 36GB+, best coder)
|
|
1051
|
-
# Then uncomment below and set OLLAMA_MODEL to the tag you pulled.
|
|
1052
|
-
# OLLAMA_BASE_URL=http://localhost:11434/v1
|
|
1053
|
-
# OLLAMA_MODEL=qwen3:4b
|
|
1054
|
-
#
|
|
1055
|
-
# Context budget — local models are much smaller than Sonnet (200K).
|
|
1056
|
-
# KV cache grows linearly with context. Smaller model = more headroom.
|
|
1057
|
-
# qwen3:4b → NUM_CTX=16384, TRIM=12288 (safe everywhere)
|
|
1058
|
-
# qwen3:8b → NUM_CTX=24576, TRIM=20480
|
|
1059
|
-
# qwen3:14b → NUM_CTX=32768, TRIM=28000 (36GB Macs only)
|
|
1060
|
-
# OLLAMA_NUM_CTX=16384
|
|
1061
|
-
# OLLAMA_MAX_OUTPUT=4096
|
|
1062
|
-
# MICRO_CC_TRIM_BUDGET=12288
|
|
1063
|
-
#
|
|
1064
|
-
# IMPORTANT — Ollama handles only the main reasoning loop. Vision, web
|
|
1065
|
-
# search, and semantic tool discovery still hit cloud APIs. You must keep
|
|
1066
|
-
# OPENAI_API_KEY set even when using Ollama:
|
|
1067
|
-
# - search_tools uses embeddings (OpenAI text-embedding-3-small)
|
|
1068
|
-
# - vision tool disabled on Ollama
|
|
1069
|
-
# OPENAI_API_KEY=sk-proj-...
|
|
1070
|
-
"""
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
ENV_EXAMPLE = f"""\
|
|
1074
|
-
# Choose one only:
|
|
1075
|
-
# Choose which to use uncomment by removing # and add API keys
|
|
1076
|
-
|
|
1077
|
-
# Option 1 Anthropic for models + OpenAI for embeddings
|
|
1078
|
-
# ANTHROPIC_API_KEY=sk-ant-
|
|
1079
|
-
# OPENAI_API_KEY=sk-proj-
|
|
1080
|
-
|
|
1081
|
-
# OR Option 2 if using LiteLLM proxy for Bedrock Anthropic
|
|
1082
|
-
# LITELLM_BASE_URL=https://your-proxy.com
|
|
1083
|
-
# LITELLM_API_KEY=sk-...
|
|
1084
|
-
|
|
1085
|
-
{OLLAMA_ENV_BLOCK}
|
|
1086
|
-
# Shared always needed for web-search
|
|
1087
|
-
# SERPAPI_KEY=
|
|
1088
|
-
"""
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
1160
|
from importlib.metadata import version as _pkg_version
|
|
1092
1161
|
__version__ = _pkg_version("micro-cc")
|
|
1093
1162
|
|
|
1094
|
-
|
|
1095
|
-
def _ensure_home_dir():
|
|
1096
|
-
"""Create ~/.micro-cc with example .env on first run.
|
|
1097
|
-
|
|
1098
|
-
For existing installs, append new option blocks (e.g. Ollama) to the
|
|
1099
|
-
user's .env non-destructively — we only append if the sentinel string
|
|
1100
|
-
is absent. Their existing keys and customizations are untouched.
|
|
1101
|
-
"""
|
|
1102
|
-
home = os.path.expanduser("~/.micro-cc")
|
|
1103
|
-
os.makedirs(os.path.join(home, "projects"), exist_ok=True)
|
|
1104
|
-
env_path = os.path.join(home, ".env")
|
|
1105
|
-
|
|
1106
|
-
if not os.path.exists(env_path):
|
|
1107
|
-
with open(env_path, "w") as f:
|
|
1108
|
-
f.write(ENV_EXAMPLE)
|
|
1109
|
-
print(f"\n Created {env_path} — add your API keys before starting.\n")
|
|
1110
|
-
sys.exit(0)
|
|
1111
|
-
|
|
1112
|
-
# Upgrade path — existing .env, append new option blocks if missing.
|
|
1113
|
-
with open(env_path) as f:
|
|
1114
|
-
current = f.read()
|
|
1115
|
-
|
|
1116
|
-
if "OLLAMA_BASE_URL" not in current:
|
|
1117
|
-
with open(env_path, "a") as f:
|
|
1118
|
-
if not current.endswith("\n"):
|
|
1119
|
-
f.write("\n")
|
|
1120
|
-
f.write("\n" + OLLAMA_ENV_BLOCK)
|
|
1121
|
-
print(f"\n ✓ micro-cc added a new option to {env_path}")
|
|
1122
|
-
print(" Option 3: Ollama (local open-weight models). Your existing")
|
|
1123
|
-
print(" config is untouched — open the file to review the new block.\n")
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
def _preflight_ollama():
|
|
1127
|
-
"""Validate Ollama setup when OLLAMA_BASE_URL is set.
|
|
1128
|
-
|
|
1129
|
-
Checks: OLLAMA_MODEL set, daemon reachable, model pulled.
|
|
1130
|
-
Prints friendly onboarding on failure, exits cleanly.
|
|
1131
|
-
Returns (model, trim_budget) for the caller to apply to MicroApp.
|
|
1132
|
-
"""
|
|
1133
|
-
import urllib.request
|
|
1134
|
-
import json as _json
|
|
1135
|
-
|
|
1136
|
-
base_url = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434/v1")
|
|
1137
|
-
tags_url = base_url.rstrip("/").removesuffix("/v1") + "/api/tags"
|
|
1138
|
-
model = os.getenv("OLLAMA_MODEL")
|
|
1139
|
-
|
|
1140
|
-
if not model:
|
|
1141
|
-
print("\n[micro-cc] Ollama selected but OLLAMA_MODEL is not set.")
|
|
1142
|
-
print("[micro-cc] Edit ~/.micro-cc/.env and set e.g. OLLAMA_MODEL=qwen3:14b\n")
|
|
1143
|
-
sys.exit(1)
|
|
1144
|
-
|
|
1145
|
-
try:
|
|
1146
|
-
with urllib.request.urlopen(tags_url, timeout=2) as resp:
|
|
1147
|
-
data = _json.loads(resp.read())
|
|
1148
|
-
except Exception:
|
|
1149
|
-
print(f"\n[micro-cc] Ollama daemon not reachable at {base_url}")
|
|
1150
|
-
print("[micro-cc] Install and start Ollama:")
|
|
1151
|
-
print(" brew install ollama")
|
|
1152
|
-
print(" brew services start ollama # auto-starts on login, runs in background")
|
|
1153
|
-
print("[micro-cc] Then pull the model you want:")
|
|
1154
|
-
print(f" ollama pull {model}\n")
|
|
1155
|
-
sys.exit(1)
|
|
1156
|
-
|
|
1157
|
-
tags = [m.get("name", "") for m in data.get("models", [])]
|
|
1158
|
-
if model not in tags:
|
|
1159
|
-
print(f"\n[micro-cc] Ollama model '{model}' is not pulled on this machine.")
|
|
1160
|
-
print(f"[micro-cc] Run: ollama pull {model}")
|
|
1161
|
-
if tags:
|
|
1162
|
-
print("[micro-cc] Currently pulled:")
|
|
1163
|
-
for t in tags:
|
|
1164
|
-
print(f" {t}")
|
|
1165
|
-
print()
|
|
1166
|
-
sys.exit(1)
|
|
1167
|
-
|
|
1168
|
-
num_ctx = int(os.getenv("OLLAMA_NUM_CTX", "32768"))
|
|
1169
|
-
max_out = int(os.getenv("OLLAMA_MAX_OUTPUT", "4096"))
|
|
1170
|
-
trim = int(os.getenv("MICRO_CC_TRIM_BUDGET", str(max(num_ctx - max_out, 4096))))
|
|
1171
|
-
|
|
1172
|
-
print(f"\n[micro-cc] ✓ Ollama ready — model={model}")
|
|
1173
|
-
print(f"[micro-cc] num_ctx={num_ctx} trim_budget={trim} max_output={max_out}")
|
|
1174
|
-
print("[micro-cc] Local models have much smaller context than Sonnet (200K).")
|
|
1175
|
-
print("[micro-cc] Qwen3 native is 40K — we reserve 32K input + 4K output so KV cache")
|
|
1176
|
-
print("[micro-cc] fits in RAM. Context will fill faster. Override via OLLAMA_NUM_CTX /")
|
|
1177
|
-
print("[micro-cc] MICRO_CC_TRIM_BUDGET in ~/.micro-cc/.env.\n")
|
|
1178
|
-
|
|
1179
|
-
return model, trim
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
1163
|
def start_():
|
|
1183
|
-
_ensure_home_dir()
|
|
1184
|
-
|
|
1185
|
-
from micro_cc.utils.helpers import get_endpoint
|
|
1186
|
-
if get_endpoint() == "Ollama":
|
|
1187
|
-
ollama_model, ollama_trim = _preflight_ollama()
|
|
1188
|
-
MicroApp._current_model = ollama_model
|
|
1189
|
-
MicroApp.MODEL_OPTIONS = [ollama_model]
|
|
1190
|
-
MicroApp._trim_budget = ollama_trim
|
|
1191
1164
|
|
|
1192
1165
|
if len(sys.argv) > 1:
|
|
1193
1166
|
project_dir = os.path.abspath(sys.argv[1])
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
from dotenv import load_dotenv
|
|
2
|
+
from PIL import Image
|
|
3
|
+
import tiktoken
|
|
4
|
+
import base64
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import io
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
############################################################################################################
|
|
12
|
+
|
|
13
|
+
load_dotenv(os.path.expanduser("~/.micro-cc/.env"))
|
|
14
|
+
load_dotenv()
|
|
15
|
+
|
|
16
|
+
############################################################################################################
|
|
17
|
+
|
|
18
|
+
def get_endpoint() -> str:
|
|
19
|
+
"""Infer endpoint from env vars. Ollama > Anthropic > LiteLLM (explicit local wins)."""
|
|
20
|
+
if os.getenv("OLLAMA_BASE_URL"):
|
|
21
|
+
return "Ollama"
|
|
22
|
+
if os.getenv("ANTHROPIC_API_KEY"):
|
|
23
|
+
return "Anthropic"
|
|
24
|
+
if os.getenv("LITELLM_BASE_URL") and os.getenv("LITELLM_API_KEY"):
|
|
25
|
+
return "LiteLLM"
|
|
26
|
+
return "Anthropic"
|
|
27
|
+
|
|
28
|
+
############################################################################################################
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def has_configured_endpoint() -> bool:
|
|
32
|
+
return bool(
|
|
33
|
+
os.getenv("ANTHROPIC_API_KEY")
|
|
34
|
+
or (os.getenv("LITELLM_BASE_URL") and os.getenv("LITELLM_API_KEY"))
|
|
35
|
+
or os.getenv("OLLAMA_BASE_URL")
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
############################################################################################################
|
|
40
|
+
|
|
41
|
+
_ROUTING_KEYS = ["OLLAMA_BASE_URL", "ANTHROPIC_API_KEY", "LITELLM_BASE_URL", "LITELLM_API_KEY"]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def apply_login(values: dict):
|
|
45
|
+
"""Recreate ~/.micro-cc/.env from `values` — /login always sends the
|
|
46
|
+
full field set for the chosen provider, so each login replaces the file
|
|
47
|
+
instead of accumulating stale keys from a previous provider."""
|
|
48
|
+
for key in _ROUTING_KEYS:
|
|
49
|
+
os.environ.pop(key, None) # clear this session's env, not just the file
|
|
50
|
+
os.environ.update(values)
|
|
51
|
+
|
|
52
|
+
env_path = os.path.expanduser("~/.micro-cc/.env")
|
|
53
|
+
with open(env_path, "w") as f:
|
|
54
|
+
for k, v in values.items():
|
|
55
|
+
f.write(f"{k}={v}\n")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
############################################################################################################
|
|
59
|
+
##tokenizer
|
|
60
|
+
# tiktoken.get_encoding() parses a ~1.6MB BPE rank file (~2s) — deferred to
|
|
61
|
+
# first actual use instead of import time. Callers use get_tokenizer();
|
|
62
|
+
# start_live_.py also warms this in a background thread right after the app
|
|
63
|
+
# mounts, so it's usually ready before the first message needs it.
|
|
64
|
+
|
|
65
|
+
_tokenizer = None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def get_tokenizer():
|
|
69
|
+
global _tokenizer
|
|
70
|
+
if _tokenizer is None:
|
|
71
|
+
_tokenizer = tiktoken.get_encoding("cl100k_base")
|
|
72
|
+
return _tokenizer
|
|
73
|
+
|
|
74
|
+
############################################################################################################
|
|
75
|
+
##dirs
|
|
76
|
+
|
|
77
|
+
WORK_FOLDER = os.path.join(os.getcwd(), "workspace/")
|
|
78
|
+
|
|
79
|
+
############################################################################################################
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def sanitize_and_encode_image_(img_data):
|
|
83
|
+
try:
|
|
84
|
+
if isinstance(img_data, str) and os.path.exists(img_data):
|
|
85
|
+
with Image.open(img_data) as img:
|
|
86
|
+
img = img.convert("RGB")
|
|
87
|
+
buffer = io.BytesIO()
|
|
88
|
+
img.save(buffer, format="JPEG")
|
|
89
|
+
return base64.b64encode(buffer.getvalue()).decode("utf-8")
|
|
90
|
+
else:
|
|
91
|
+
with Image.open(io.BytesIO(img_data)) as img:
|
|
92
|
+
img = img.convert("RGB")
|
|
93
|
+
buffer = io.BytesIO()
|
|
94
|
+
img.save(buffer, format="JPEG")
|
|
95
|
+
return base64.b64encode(buffer.getvalue()).decode("utf-8")
|
|
96
|
+
except Exception as e:
|
|
97
|
+
print(f"Image encoding error: {e}")
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
#################################
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def check_ollama(model: str, base_url: str = None) -> str:
|
|
104
|
+
"""Validate Ollama daemon reachable and model pulled.
|
|
105
|
+
|
|
106
|
+
Non-fatal — called from inside /login while the TUI is already running,
|
|
107
|
+
so it returns a message instead of printing+sys.exit like a CLI preflight
|
|
108
|
+
would. Empty string means OK.
|
|
109
|
+
"""
|
|
110
|
+
import urllib.request
|
|
111
|
+
import json as _json
|
|
112
|
+
|
|
113
|
+
if not model:
|
|
114
|
+
return "Enter a model tag, e.g. qwen3:14b"
|
|
115
|
+
|
|
116
|
+
base_url = base_url or os.getenv("OLLAMA_BASE_URL", "http://localhost:11434/v1")
|
|
117
|
+
tags_url = base_url.rstrip("/").removesuffix("/v1") + "/api/tags"
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
with urllib.request.urlopen(tags_url, timeout=2) as resp:
|
|
121
|
+
data = _json.loads(resp.read())
|
|
122
|
+
except Exception:
|
|
123
|
+
return (
|
|
124
|
+
f"Ollama daemon not reachable at {base_url}. "
|
|
125
|
+
f"Run: brew install ollama && brew services start ollama, then: ollama pull {model}"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
tags = [m.get("name", "") for m in data.get("models", [])]
|
|
129
|
+
if model not in tags:
|
|
130
|
+
pulled = ", ".join(tags) if tags else "none"
|
|
131
|
+
return f"Model '{model}' not pulled — run: ollama pull {model} (pulled: {pulled})"
|
|
132
|
+
|
|
133
|
+
return ""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: micro-cc
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.32
|
|
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
|
|
@@ -21,7 +21,6 @@ Requires-Dist: mcp>=1.26
|
|
|
21
21
|
Requires-Dist: numpy>=2.4
|
|
22
22
|
Requires-Dist: openai>=2.16
|
|
23
23
|
Requires-Dist: openpyxl>=3.1
|
|
24
|
-
Requires-Dist: pandas>=3.0
|
|
25
24
|
Requires-Dist: pathvalidate>=3.3
|
|
26
25
|
Requires-Dist: pdfminer.six>=20260107
|
|
27
26
|
Requires-Dist: pillow>=12.0
|
|
@@ -60,8 +59,6 @@ Frontier models with full system access — shell, filesystem, browser, MCP —
|
|
|
60
59
|
|
|
61
60
|
## Install
|
|
62
61
|
|
|
63
|
-
Please use `iTerm2`. Highly recommended terminal UI.
|
|
64
|
-
|
|
65
62
|
```bash
|
|
66
63
|
pip install micro-cc
|
|
67
64
|
```
|
|
@@ -154,5 +154,4 @@ src/micro_cc/utils/helpers.py
|
|
|
154
154
|
src/micro_cc/utils/memory_store_.py
|
|
155
155
|
src/micro_cc/utils/msg_store_.py
|
|
156
156
|
src/micro_cc/utils/process_tracker.py
|
|
157
|
-
src/micro_cc/utils/tokenization_simple.py
|
|
158
|
-
src/micro_cc/utils/utils.py
|
|
157
|
+
src/micro_cc/utils/tokenization_simple.py
|