utim-cli 2.3.26__tar.gz → 2.4.1__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.
- {utim_cli-2.3.26 → utim_cli-2.4.1}/CHANGELOG.md +20 -0
- {utim_cli-2.3.26/utim_cli.egg-info → utim_cli-2.4.1}/PKG-INFO +1 -1
- {utim_cli-2.3.26 → utim_cli-2.4.1}/pyproject.toml +1 -1
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim/__init__.py +1 -1
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/__init__.py +1 -1
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/_version.py +1 -1
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/agent.py +49 -7
- utim_cli-2.4.1/utim_cli/architecture_indexer.py +251 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/bootstrap.py +683 -580
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/brain.py +33 -2
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/client_utils.py +272 -118
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/constants.py +3 -7
- utim_cli-2.4.1/utim_cli/context_inspector.py +341 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/context_pruner.py +644 -327
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/crawler/engine.py +3 -1
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/orchestrator.py +190 -57
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/orchestrator_helpers.py +160 -77
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/orchestrator_llm_transport.py +168 -45
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/orchestrator_tool_exec.py +281 -3
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/orchestrator_turn_history.py +435 -266
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/auth.py +93 -50
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/db.py +233 -24
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/docs_md/about.md +6 -6
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/docs_md/docs.md +761 -761
- utim_cli-2.4.1/utim_cli/server/docs_md/pricing.md +24 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/docs_md/terms.md +1 -1
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/key_rotator.py +1696 -188
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/models.py +141 -31
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/plan_integrity.py +9 -7
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/router.py +70 -265
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/__init__.py +2 -1
- utim_cli-2.4.1/utim_cli/server/routes/admin_providers_routes.py +511 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/completion_routes.py +510 -511
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/quota_routes.py +180 -64
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/utils.py +198 -65
- utim_cli-2.4.1/utim_cli/syntax_fix.py +715 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tools.py +739 -74
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/compression_dialog.py +11 -11
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/model_dialog.py +203 -3
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/plugins_dialog.py +11 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/tools_dialog.py +291 -273
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/utim.py +290 -67
- utim_cli-2.4.1/utim_cli/voice.py +537 -0
- utim_cli-2.4.1/utim_cli/voice_handler.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1/utim_cli.egg-info}/PKG-INFO +1 -1
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli.egg-info/SOURCES.txt +6 -0
- utim_cli-2.3.26/utim_cli/server/docs_md/pricing.md +0 -19
- {utim_cli-2.3.26 → utim_cli-2.4.1}/LICENSE +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/MANIFEST.in +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/README.md +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/setup.cfg +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/setup.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/_chip_test.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/_verify_repro.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/_verify_syntax.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/_wizard_smoke_test.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/ask_helper.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/auth.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/backup.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/billing.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/blender_agent.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/builtin/skills/utim-guide/SKILL.md +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/config.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/crawler/__init__.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/crawler/markdown_distiller.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/dependency_manager.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/doctor.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/evidence_registry.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/harbor.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/knowledge_graph.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/local_db.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/logger.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/mcp_clean_wrapper.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/mcp_client.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/mcp_registry.json +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/models.txt +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/personalities.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/platform_installer.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/plugins/__init__.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/plugins/hooks.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/plugins/loader.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/plugins/manager.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/plugins/registry.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/plugins/runtime.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/plugins/schema.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/plugins/tool_loader.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/reflection.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/report.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/scrapy_search.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/sdk/__init__.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/sdk/config.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/sdk/context.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/sdk/events.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/sdk/handlers.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/sdk/session.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/sdk/sidecar.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/__init__.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/admin_auth.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/attribution.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/audit_log.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/batch_processor.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/captcha.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/cli_auth.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/concurrency.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/docs_md/SECRET_PROVISIONING.md +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/docs_md/changelog.md +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/docs_md/features.md +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/docs_md/license.md +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/docs_md/privacy.md +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/docs_md/refund.md +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/docs_md/support.md +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/email_utils.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/exchange_rate.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/firebase.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/fix_duplicate_users.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/history.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/local_llm.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/logging_config.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/micro_batcher.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/model_agent.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/pricing_updater.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/provision_build.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/rate_limit.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/rewards_engine.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/admin_bonus_routes.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/admin_db_routes.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/auth_routes.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/billing_routes.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/credit_routes.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/feedback_routes.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/marketplace_routes.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/quota_share_routes.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/referral_routes.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/rewards_routes.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/security_routes.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/session_routes.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/share_routes.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/webhook_routes.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/security_alerts.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/server.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/storage_nodes.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/share.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/share_tui.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/situational_scoring.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/state.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/subagent_manager.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/task_dispatcher.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/task_logger.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tools_grep.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/__init__.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/feedback_dialog.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/history_dialog.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/marketplace_app_state.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/marketplace_dialog.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/marketplace_layout_engine.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/mcp_dialog.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/miniagents_dialog.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/personality_dialog.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/publish_dialog.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/quota_dialog.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/quota_redeem_dialog.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/quota_share_dialog.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/resume_dialog.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/rewards_tui.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/skills_dialog.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/subagents_dialog.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/tasks_dialog.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/thinking_display.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/update_dialog.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/update_checker.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/utilities.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/utimmodel.txt +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/vector_memory.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/wheel.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/workspace.py +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli.egg-info/dependency_links.txt +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli.egg-info/entry_points.txt +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli.egg-info/requires.txt +0 -0
- {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli.egg-info/top_level.txt +0 -0
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.4.1] - 2026-09-18
|
|
4
|
+
|
|
5
|
+
### Bug Fixes
|
|
6
|
+
- Fixed the `UnboundLocalError: rotator` crash when resolving failover providers.
|
|
7
|
+
- Fixed an Admin UI bug that zeroed out free model upstream prices in the Model Pricing & Provisioning interface.
|
|
8
|
+
|
|
9
|
+
## [2.4.0] - 2026-09-14
|
|
10
|
+
|
|
11
|
+
### ✨ New Features & Enhancements
|
|
12
|
+
- Added native file tools for binary documents (Excel/`.xlsx`, Word/`.docx`, PDF/`.pdf`).
|
|
13
|
+
- Implemented `@mcp` sticky tags for automated daemon-like MCP server configuration without background processes.
|
|
14
|
+
- Eliminated typing input latency and UI lag by migrating `os.path.exists` checks to async background threads.
|
|
15
|
+
- Refactored Context Monitor with retro-active gap logic to instantly handle dynamic interval thresholds and sudden context bloat.
|
|
16
|
+
|
|
17
|
+
### 🐛 Bug Fixes
|
|
18
|
+
- Fixed the CSV sniffer crashing when writing markdown tables to binary files.
|
|
19
|
+
- Fixed Windows path spacing errors during inline Python execution.
|
|
20
|
+
- Fixed UI rendering bugs where multi-line markdown boxes wouldn't flush to screen until refreshed.
|
|
21
|
+
|
|
22
|
+
|
|
3
23
|
## [2.3.26] - 2026-09-08
|
|
4
24
|
|
|
5
25
|
### 🛡️ Zero Rate-Limit Failover & Low-Latency Key Rotation
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: utim-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.1
|
|
4
4
|
Summary: UTIM – Universal Terminal Intelligence Manager. A powerful agentic AI coding assistant for your terminal.
|
|
5
5
|
License: Emend AI Proprietary EULA
|
|
6
6
|
Project-URL: Homepage, https://utim.dev
|
|
@@ -14,7 +14,7 @@ Quickstart:
|
|
|
14
14
|
try:
|
|
15
15
|
from utim_cli._version import VERSION as __version__
|
|
16
16
|
except ImportError:
|
|
17
|
-
__version__ = "2.
|
|
17
|
+
__version__ = "2.4.1"
|
|
18
18
|
|
|
19
19
|
# Developer-friendly core classes (similar to `import genai` / `import openai`)
|
|
20
20
|
from utim_cli.sdk.session import UtimSession as Session
|
|
@@ -3,6 +3,7 @@ import json
|
|
|
3
3
|
import sys
|
|
4
4
|
import time
|
|
5
5
|
from .tools import get_tools
|
|
6
|
+
from .syntax_fix import attempt_syntax_repair
|
|
6
7
|
from rich.console import Console
|
|
7
8
|
from rich.live import Live
|
|
8
9
|
from rich.spinner import Spinner
|
|
@@ -182,6 +183,25 @@ class ReActAgent:
|
|
|
182
183
|
"cmd": "run_command",
|
|
183
184
|
"execute_command": "run_command",
|
|
184
185
|
"view_file": "read_file",
|
|
186
|
+
"write_to_file": "write_file",
|
|
187
|
+
"create_file": "write_file",
|
|
188
|
+
"save_file": "write_file",
|
|
189
|
+
"str_replace_editor": "edit_file",
|
|
190
|
+
"replace_file_content": "edit_file",
|
|
191
|
+
"modify_file": "edit_file",
|
|
192
|
+
"update_file": "edit_file",
|
|
193
|
+
"edit": "edit_file",
|
|
194
|
+
"list_dir": "list_directory",
|
|
195
|
+
"dir": "list_directory",
|
|
196
|
+
"ls": "list_directory",
|
|
197
|
+
"search": "grep_search",
|
|
198
|
+
"search_codebase": "grep_search",
|
|
199
|
+
"grep": "grep_search",
|
|
200
|
+
"find_files": "find_by_name",
|
|
201
|
+
"find_file": "find_by_name",
|
|
202
|
+
"websearch": "web_search",
|
|
203
|
+
"search_web": "web_search",
|
|
204
|
+
"crawl": "crawl_web",
|
|
185
205
|
}
|
|
186
206
|
if tool_name in _TOOL_NAME_ALIASES:
|
|
187
207
|
tool_name = _TOOL_NAME_ALIASES[tool_name]
|
|
@@ -249,7 +269,11 @@ class ReActAgent:
|
|
|
249
269
|
return f"Error: Tool '{tool_name}' not found."
|
|
250
270
|
|
|
251
271
|
try:
|
|
252
|
-
|
|
272
|
+
import inspect
|
|
273
|
+
sig = inspect.signature(tool_functions[tool_name])
|
|
274
|
+
has_var_kw = any(p.kind == inspect.Parameter.VAR_KEYWORD for p in sig.parameters.values())
|
|
275
|
+
filtered_args = dict(arguments) if has_var_kw else {k: v for k, v in arguments.items() if k in sig.parameters}
|
|
276
|
+
result = tool_functions[tool_name](**filtered_args)
|
|
253
277
|
# Erase the running line first
|
|
254
278
|
try:
|
|
255
279
|
width = self.console.size.width
|
|
@@ -270,7 +294,20 @@ class ReActAgent:
|
|
|
270
294
|
num_lines = math.ceil(getattr(self, "_last_running_indicator_len", 40) / max(1, width))
|
|
271
295
|
for _ in range(num_lines):
|
|
272
296
|
self.console.print("\033[F\033[K", end="")
|
|
273
|
-
|
|
297
|
+
|
|
298
|
+
# Auto-repair: if the tool failed due to a syntax error in the
|
|
299
|
+
# generated code, attempt an "on the go" syntax fix before
|
|
300
|
+
# reporting the error.
|
|
301
|
+
error_msg = str(e)
|
|
302
|
+
repaired = None
|
|
303
|
+
try:
|
|
304
|
+
repaired = attempt_syntax_repair(error_msg, self.console)
|
|
305
|
+
except Exception:
|
|
306
|
+
repaired = None
|
|
307
|
+
if repaired:
|
|
308
|
+
return repaired
|
|
309
|
+
|
|
310
|
+
return f"Error executing {tool_name}: {error_msg}"
|
|
274
311
|
|
|
275
312
|
|
|
276
313
|
def run(self, max_iterations: int = 500, show_tools: bool = True):
|
|
@@ -282,6 +319,7 @@ class ReActAgent:
|
|
|
282
319
|
tool_calls = []
|
|
283
320
|
current_tool_call = None
|
|
284
321
|
|
|
322
|
+
from utim_cli.client_utils import sanitize_tools_for_api, sanitize_messages_for_api, repair_and_normalize_tool_call
|
|
285
323
|
utim_tools, tool_functions = get_tools()
|
|
286
324
|
all_tools = utim_tools
|
|
287
325
|
|
|
@@ -291,11 +329,11 @@ class ReActAgent:
|
|
|
291
329
|
"stream": True,
|
|
292
330
|
}
|
|
293
331
|
if all_tools:
|
|
294
|
-
kwargs["tools"] = all_tools
|
|
332
|
+
kwargs["tools"] = sanitize_tools_for_api(all_tools, model_id=self.model_id)
|
|
295
333
|
|
|
296
334
|
# Provider-safety pass: drop null/empty `tool_calls` keys from
|
|
297
335
|
# history (strict providers 400 on `messages.N.tool_calls: null`).
|
|
298
|
-
kwargs["messages"] = sanitize_messages_for_api(kwargs.get("messages") or [], model_id=
|
|
336
|
+
kwargs["messages"] = sanitize_messages_for_api(kwargs.get("messages") or [], model_id=self.model_id)
|
|
299
337
|
|
|
300
338
|
# Call the LLM with streaming (retry once on transport errors so a
|
|
301
339
|
# mid-stream failure doesn't silently kill the whole run)
|
|
@@ -365,6 +403,9 @@ class ReActAgent:
|
|
|
365
403
|
assistant_content = parsed_content or ""
|
|
366
404
|
tool_calls.extend(parsed_tool_calls)
|
|
367
405
|
|
|
406
|
+
# Repair and normalize all tool calls (names, IDs, types, arguments)
|
|
407
|
+
tool_calls = [repair_and_normalize_tool_call(tc) for tc in tool_calls if tc]
|
|
408
|
+
|
|
368
409
|
# Print any text content generated by the assistant
|
|
369
410
|
if assistant_content.strip():
|
|
370
411
|
self.console.print()
|
|
@@ -414,13 +455,14 @@ class ReActAgent:
|
|
|
414
455
|
})
|
|
415
456
|
|
|
416
457
|
# Execute each tool call
|
|
417
|
-
for tc in tool_calls:
|
|
458
|
+
for i, tc in enumerate(tool_calls):
|
|
418
459
|
result = self._execute_tool_call(tc)
|
|
419
460
|
|
|
420
|
-
# Add tool result to messages
|
|
461
|
+
# Add tool result to messages with guaranteed matching tool_call_id
|
|
462
|
+
tc_id = tc.get("id") or f"call_{i}_{int(time.time()*1000)}"
|
|
421
463
|
self.messages.append({
|
|
422
464
|
"role": "tool",
|
|
423
|
-
"tool_call_id":
|
|
465
|
+
"tool_call_id": tc_id,
|
|
424
466
|
"content": result,
|
|
425
467
|
})
|
|
426
468
|
else:
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import json
|
|
4
|
+
import hashlib
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import ast
|
|
7
|
+
|
|
8
|
+
def get_project_hash(project_path: str) -> str:
|
|
9
|
+
return hashlib.sha256(os.path.abspath(project_path).encode('utf-8')).hexdigest()[:12]
|
|
10
|
+
|
|
11
|
+
def _ignore_dir(dname: str) -> bool:
|
|
12
|
+
ignored = {'.git', 'node_modules', 'venv', '.venv', '__pycache__', 'dist', 'build', '.next', '.utim'}
|
|
13
|
+
return dname in ignored
|
|
14
|
+
|
|
15
|
+
def index_python_file(filepath: Path) -> dict:
|
|
16
|
+
try:
|
|
17
|
+
content = filepath.read_text(encoding='utf-8', errors='ignore')
|
|
18
|
+
tree = ast.parse(content)
|
|
19
|
+
imports = []
|
|
20
|
+
symbols = []
|
|
21
|
+
lines = content.split('\n')
|
|
22
|
+
|
|
23
|
+
for node in ast.walk(tree):
|
|
24
|
+
if isinstance(node, ast.Import):
|
|
25
|
+
for alias in node.names: imports.append(alias.name)
|
|
26
|
+
elif isinstance(node, ast.ImportFrom):
|
|
27
|
+
if node.module: imports.append(node.module)
|
|
28
|
+
elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
29
|
+
docstring = ast.get_docstring(node)
|
|
30
|
+
args = [a.arg for a in node.args.args]
|
|
31
|
+
sig = f"({', '.join(args)})"
|
|
32
|
+
start = max(0, node.lineno - 1)
|
|
33
|
+
end = min(len(lines), start + 6)
|
|
34
|
+
snippet = "\n".join(lines[start:end])
|
|
35
|
+
if len(lines) > end: snippet += "\n # ..."
|
|
36
|
+
symbols.append({"type": "function", "name": node.name, "line": node.lineno, "docstring": docstring, "signature": sig, "snippet": snippet})
|
|
37
|
+
elif isinstance(node, ast.ClassDef):
|
|
38
|
+
docstring = ast.get_docstring(node)
|
|
39
|
+
start = max(0, node.lineno - 1)
|
|
40
|
+
end = min(len(lines), start + 5)
|
|
41
|
+
snippet = "\n".join(lines[start:end])
|
|
42
|
+
if len(lines) > end: snippet += "\n # ..."
|
|
43
|
+
symbols.append({"type": "class", "name": node.name, "line": node.lineno, "docstring": docstring, "snippet": snippet})
|
|
44
|
+
return {"imports": list(set(imports)), "symbols": symbols}
|
|
45
|
+
except Exception:
|
|
46
|
+
return {"imports": [], "symbols": []}
|
|
47
|
+
|
|
48
|
+
def index_js_ts_file(filepath: Path) -> dict:
|
|
49
|
+
try:
|
|
50
|
+
content = filepath.read_text(encoding='utf-8', errors='ignore')
|
|
51
|
+
imports = re.findall(r'import\s+.*?from\s+[\'"](.*?)[\'"]', content)
|
|
52
|
+
symbols = []
|
|
53
|
+
lines = content.split('\n')
|
|
54
|
+
for i, line in enumerate(lines):
|
|
55
|
+
match = re.search(r'(?:export\s+)?(?:const|let|var|function|class)\s+([a-zA-Z0-9_]+)', line)
|
|
56
|
+
if match:
|
|
57
|
+
name = match.group(1)
|
|
58
|
+
snippet = "\n".join(lines[i:min(len(lines), i+6)])
|
|
59
|
+
if len(lines) > i+6: snippet += "\n // ..."
|
|
60
|
+
symbols.append({"type": "export", "name": name, "line": i+1, "snippet": snippet, "signature": ""})
|
|
61
|
+
return {"imports": list(set(imports)), "symbols": symbols}
|
|
62
|
+
except Exception:
|
|
63
|
+
return {"imports": [], "symbols": []}
|
|
64
|
+
|
|
65
|
+
def index_generic_file(filepath: Path) -> dict:
|
|
66
|
+
try:
|
|
67
|
+
content = filepath.read_text(encoding='utf-8', errors='ignore')
|
|
68
|
+
symbols = []
|
|
69
|
+
lines = content.split('\n')
|
|
70
|
+
for i, line in enumerate(lines):
|
|
71
|
+
s_line = line.strip()
|
|
72
|
+
name = None
|
|
73
|
+
|
|
74
|
+
# 1. Standard OOP / Procedural / DevOps / Smart Contracts
|
|
75
|
+
m1 = re.search(r'^(?:public |private |protected |export |func |fn |class |struct |interface |def |sub |function |contract |resource |module )\s*([a-zA-Z0-9_]+)\s*(?:\{|\(|:|"|\')', s_line)
|
|
76
|
+
# 2. SQL Data Definition
|
|
77
|
+
m2 = re.search(r'^CREATE\s+(?:TABLE|PROCEDURE|VIEW|FUNCTION|TRIGGER)\s+([a-zA-Z0-9_]+)', s_line, re.IGNORECASE)
|
|
78
|
+
# 3. Bash / Shell scripts
|
|
79
|
+
m3 = re.search(r'^([a-zA-Z0-9_]+)\s*\(\)\s*\{', s_line)
|
|
80
|
+
# 4. Lisp / Clojure / Scheme
|
|
81
|
+
m4 = re.search(r'^\(\s*def[a-zA-Z0-9_]*\s+([a-zA-Z0-9_\-]+)', s_line)
|
|
82
|
+
|
|
83
|
+
if m1: name = m1.group(1)
|
|
84
|
+
elif m2: name = m2.group(1)
|
|
85
|
+
elif m3: name = m3.group(1)
|
|
86
|
+
elif m4: name = m4.group(1)
|
|
87
|
+
|
|
88
|
+
if name and len(name) > 2 and name not in ("if", "for", "while", "switch", "catch", "return", "else", "elif"):
|
|
89
|
+
snippet = "\n".join(lines[i:min(len(lines), i+6)])
|
|
90
|
+
if len(lines) > i+6: snippet += "\n // ..."
|
|
91
|
+
symbols.append({"type": "symbol", "name": name, "line": i+1, "snippet": snippet, "signature": ""})
|
|
92
|
+
return {"imports": [], "symbols": symbols}
|
|
93
|
+
except Exception:
|
|
94
|
+
return {"imports": [], "symbols": []}
|
|
95
|
+
|
|
96
|
+
def architecture_lock(project_path: str = ".") -> str:
|
|
97
|
+
import uuid
|
|
98
|
+
from utim_cli.brain import get_current_resolved_project
|
|
99
|
+
abs_path = os.path.abspath(project_path)
|
|
100
|
+
if not os.path.isdir(abs_path):
|
|
101
|
+
return f"Error: Project path '{abs_path}' does not exist or is not a directory."
|
|
102
|
+
|
|
103
|
+
proj_id, proj_dir = get_current_resolved_project()
|
|
104
|
+
arch_hash = uuid.uuid4().hex[:8]
|
|
105
|
+
arch_dir = proj_dir / f"arch_{arch_hash}"
|
|
106
|
+
arch_dir.mkdir(parents=True, exist_ok=True)
|
|
107
|
+
brain_file = arch_dir / "00_architecture_map.json"
|
|
108
|
+
|
|
109
|
+
inventory = []
|
|
110
|
+
project_type = []
|
|
111
|
+
|
|
112
|
+
# Generic stack identification heuristics
|
|
113
|
+
if (Path(abs_path) / "package.json").exists(): project_type.append("Node.js")
|
|
114
|
+
if (Path(abs_path) / "requirements.txt").exists() or (Path(abs_path) / "pyproject.toml").exists(): project_type.append("Python")
|
|
115
|
+
if (Path(abs_path) / "go.mod").exists(): project_type.append("Go")
|
|
116
|
+
if (Path(abs_path) / "Cargo.toml").exists(): project_type.append("Rust")
|
|
117
|
+
if (Path(abs_path) / "composer.json").exists(): project_type.append("PHP")
|
|
118
|
+
if (Path(abs_path) / "Gemfile").exists(): project_type.append("Ruby")
|
|
119
|
+
|
|
120
|
+
# Known binaries to skip indexing completely
|
|
121
|
+
BINARY_EXTS = {
|
|
122
|
+
".png", ".jpg", ".jpeg", ".gif", ".ico", ".svg", ".mp4", ".mp3", ".wav",
|
|
123
|
+
".zip", ".tar", ".gz", ".rar", ".7z", ".exe", ".dll", ".so", ".dylib",
|
|
124
|
+
".bin", ".dat", ".db", ".sqlite", ".pyc", ".class", ".o", ".a", ".lib",
|
|
125
|
+
".pdf", ".doc", ".docx", ".xls", ".xlsx"
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
for root, dirs, files in os.walk(abs_path):
|
|
129
|
+
dirs[:] = [d for d in dirs if not _ignore_dir(d)]
|
|
130
|
+
for f in files:
|
|
131
|
+
if f.startswith('.'): continue
|
|
132
|
+
fpath = Path(root) / f
|
|
133
|
+
rel_path = str(fpath.relative_to(abs_path)).replace('\\', '/')
|
|
134
|
+
|
|
135
|
+
file_data = {
|
|
136
|
+
"file": rel_path,
|
|
137
|
+
"size": fpath.stat().st_size if fpath.exists() else 0,
|
|
138
|
+
"extension": fpath.suffix,
|
|
139
|
+
"relationships": []
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
# Universal Language Routing
|
|
143
|
+
if fpath.suffix == ".py":
|
|
144
|
+
data = index_python_file(fpath)
|
|
145
|
+
elif fpath.suffix in (".js", ".ts", ".jsx", ".tsx", ".mjs", ".cjs"):
|
|
146
|
+
data = index_js_ts_file(fpath)
|
|
147
|
+
elif fpath.suffix.lower() not in BINARY_EXTS:
|
|
148
|
+
data = index_generic_file(fpath)
|
|
149
|
+
else:
|
|
150
|
+
data = {"imports": [], "symbols": []}
|
|
151
|
+
|
|
152
|
+
file_data["imports"] = data["imports"]
|
|
153
|
+
file_data["symbols"] = data["symbols"]
|
|
154
|
+
inventory.append(file_data)
|
|
155
|
+
|
|
156
|
+
nodes = []
|
|
157
|
+
edges = []
|
|
158
|
+
nodes.append({"id": "PROJECT_ROOT", "type": "project", "path": abs_path})
|
|
159
|
+
|
|
160
|
+
def get_cluster_name(file_path: str) -> str:
|
|
161
|
+
parts = file_path.replace('\\', '/').split('/')
|
|
162
|
+
ignore_dirs = {'src', 'lib', 'app', 'main', 'bin', 'scripts', 'tests', 'test', 'public', 'assets', 'static', 'dist', 'build'}
|
|
163
|
+
|
|
164
|
+
# 1. Find the most meaningful parent directory
|
|
165
|
+
for part in reversed(parts[:-1]):
|
|
166
|
+
clean_part = part.lower().strip()
|
|
167
|
+
if clean_part and clean_part not in ignore_dirs:
|
|
168
|
+
return clean_part
|
|
169
|
+
|
|
170
|
+
# 2. Fallback to filename if it's unique
|
|
171
|
+
filename = parts[-1].split('.')[0].lower()
|
|
172
|
+
if filename not in {'index', 'main', 'app', 'core'}:
|
|
173
|
+
return filename
|
|
174
|
+
|
|
175
|
+
return "core"
|
|
176
|
+
|
|
177
|
+
clusters = {}
|
|
178
|
+
|
|
179
|
+
for item in inventory:
|
|
180
|
+
file_id = item["file"]
|
|
181
|
+
nodes.append({"id": file_id, "type": "file", "extension": item.get("extension"), "size": item.get("size")})
|
|
182
|
+
edges.append({"source": "PROJECT_ROOT", "target": file_id, "relation": "contains"})
|
|
183
|
+
|
|
184
|
+
for imp in item.get("imports", []):
|
|
185
|
+
nodes.append({"id": imp, "type": "module"})
|
|
186
|
+
edges.append({"source": file_id, "target": imp, "relation": "imports"})
|
|
187
|
+
|
|
188
|
+
cluster_name = get_cluster_name(file_id)
|
|
189
|
+
if cluster_name not in clusters:
|
|
190
|
+
clusters[cluster_name] = []
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
content = (Path(abs_path) / file_id).read_text(encoding='utf-8', errors='ignore')
|
|
194
|
+
content_lower = content.lower()
|
|
195
|
+
file_lower = file_id.lower()
|
|
196
|
+
if "if __name__ ==" in content or "int main(" in content or "func main(" in content or "app.listen" in content_lower:
|
|
197
|
+
clusters[cluster_name].append(f"### File: `{file_id}` (Entrypoint/Server)\n*This file contains application startup or core routing.*\n")
|
|
198
|
+
except:
|
|
199
|
+
pass
|
|
200
|
+
|
|
201
|
+
for sym in item.get("symbols", []):
|
|
202
|
+
sym_id = f"{file_id}::{sym['name']}"
|
|
203
|
+
nodes.append({"id": sym_id, "type": sym["type"], "name": sym["name"], "line": sym.get("line")})
|
|
204
|
+
edges.append({"source": file_id, "target": sym_id, "relation": "defines"})
|
|
205
|
+
|
|
206
|
+
name = sym.get("name", "")
|
|
207
|
+
doc = sym.get("docstring") or ""
|
|
208
|
+
snippet = sym.get("snippet") or ""
|
|
209
|
+
sig = sym.get("signature", "")
|
|
210
|
+
|
|
211
|
+
md_block = f"### `{name}{sig}` in `{file_id}`\n"
|
|
212
|
+
if doc: md_block += f"**Docstring:** {doc}\n"
|
|
213
|
+
md_block += f"```text\n{snippet}\n```\n"
|
|
214
|
+
clusters[cluster_name].append(md_block)
|
|
215
|
+
|
|
216
|
+
# Dynamically generate markdown files based on organic clusters
|
|
217
|
+
generated_mds = []
|
|
218
|
+
for cname, blocks in clusters.items():
|
|
219
|
+
if not blocks: continue
|
|
220
|
+
safe_cname = re.sub(r'[^a-z0-9_]', '_', cname)
|
|
221
|
+
md_content = f"# 🧩 Architecture Component: {cname.title()}\n\n" + "\n".join(blocks)
|
|
222
|
+
md_filename = f"component_{safe_cname}.md"
|
|
223
|
+
(arch_dir / md_filename).write_text(md_content, encoding="utf-8")
|
|
224
|
+
generated_mds.append(md_filename)
|
|
225
|
+
|
|
226
|
+
conventions = [
|
|
227
|
+
"## Innate Project Architecture Rules",
|
|
228
|
+
"- **Organic Component Isolation**: The project is structured organically. Do not bloat central routers or core files.",
|
|
229
|
+
"- **Multifile Designs**: When adding large new features, ALWAYS create new standalone component files based on the existing directory structure.",
|
|
230
|
+
"- **MANDATORY ARCHITECTURE REVIEW**: You MUST use `read_file` on the relevant domain files below BEFORE exploring the source code manually. They contain the exact structural blueprints you need.",
|
|
231
|
+
f"- **Available Domain Files**: {', '.join([f'[file:///{str(arch_dir.absolute() / m).replace(chr(92), "/")}]' for m in generated_mds])}"
|
|
232
|
+
]
|
|
233
|
+
(arch_dir / "conventions_summary.txt").write_text("\n".join(conventions), encoding="utf-8")
|
|
234
|
+
|
|
235
|
+
unique_nodes = {n["id"]: n for n in nodes}.values()
|
|
236
|
+
|
|
237
|
+
graph_data = {
|
|
238
|
+
"project_path": abs_path,
|
|
239
|
+
"project_type": project_type,
|
|
240
|
+
"total_files": len(inventory),
|
|
241
|
+
"nodes": list(unique_nodes),
|
|
242
|
+
"edges": edges,
|
|
243
|
+
"capabilities": ["Project orientation", "Graph traversal", "Symbol resolution"]
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
with open(brain_file, 'w', encoding='utf-8') as bf:
|
|
247
|
+
json.dump(graph_data, bf, indent=2)
|
|
248
|
+
|
|
249
|
+
return (f"Architecture lock successfully generated at: {arch_dir}\n"
|
|
250
|
+
f"Generated 00_architecture_map.json and detailed Markdown component files.\n"
|
|
251
|
+
f"Indexed {len(inventory)} files. Identified stack: {', '.join(project_type) or 'Unknown'}.")
|