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.
Files changed (179) hide show
  1. {utim_cli-2.3.26 → utim_cli-2.4.1}/CHANGELOG.md +20 -0
  2. {utim_cli-2.3.26/utim_cli.egg-info → utim_cli-2.4.1}/PKG-INFO +1 -1
  3. {utim_cli-2.3.26 → utim_cli-2.4.1}/pyproject.toml +1 -1
  4. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim/__init__.py +1 -1
  5. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/__init__.py +1 -1
  6. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/_version.py +1 -1
  7. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/agent.py +49 -7
  8. utim_cli-2.4.1/utim_cli/architecture_indexer.py +251 -0
  9. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/bootstrap.py +683 -580
  10. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/brain.py +33 -2
  11. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/client_utils.py +272 -118
  12. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/constants.py +3 -7
  13. utim_cli-2.4.1/utim_cli/context_inspector.py +341 -0
  14. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/context_pruner.py +644 -327
  15. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/crawler/engine.py +3 -1
  16. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/orchestrator.py +190 -57
  17. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/orchestrator_helpers.py +160 -77
  18. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/orchestrator_llm_transport.py +168 -45
  19. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/orchestrator_tool_exec.py +281 -3
  20. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/orchestrator_turn_history.py +435 -266
  21. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/auth.py +93 -50
  22. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/db.py +233 -24
  23. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/docs_md/about.md +6 -6
  24. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/docs_md/docs.md +761 -761
  25. utim_cli-2.4.1/utim_cli/server/docs_md/pricing.md +24 -0
  26. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/docs_md/terms.md +1 -1
  27. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/key_rotator.py +1696 -188
  28. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/models.py +141 -31
  29. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/plan_integrity.py +9 -7
  30. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/router.py +70 -265
  31. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/__init__.py +2 -1
  32. utim_cli-2.4.1/utim_cli/server/routes/admin_providers_routes.py +511 -0
  33. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/completion_routes.py +510 -511
  34. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/quota_routes.py +180 -64
  35. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/utils.py +198 -65
  36. utim_cli-2.4.1/utim_cli/syntax_fix.py +715 -0
  37. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tools.py +739 -74
  38. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/compression_dialog.py +11 -11
  39. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/model_dialog.py +203 -3
  40. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/plugins_dialog.py +11 -0
  41. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/tools_dialog.py +291 -273
  42. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/utim.py +290 -67
  43. utim_cli-2.4.1/utim_cli/voice.py +537 -0
  44. utim_cli-2.4.1/utim_cli/voice_handler.py +0 -0
  45. {utim_cli-2.3.26 → utim_cli-2.4.1/utim_cli.egg-info}/PKG-INFO +1 -1
  46. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli.egg-info/SOURCES.txt +6 -0
  47. utim_cli-2.3.26/utim_cli/server/docs_md/pricing.md +0 -19
  48. {utim_cli-2.3.26 → utim_cli-2.4.1}/LICENSE +0 -0
  49. {utim_cli-2.3.26 → utim_cli-2.4.1}/MANIFEST.in +0 -0
  50. {utim_cli-2.3.26 → utim_cli-2.4.1}/README.md +0 -0
  51. {utim_cli-2.3.26 → utim_cli-2.4.1}/setup.cfg +0 -0
  52. {utim_cli-2.3.26 → utim_cli-2.4.1}/setup.py +0 -0
  53. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/_chip_test.py +0 -0
  54. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/_verify_repro.py +0 -0
  55. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/_verify_syntax.py +0 -0
  56. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/_wizard_smoke_test.py +0 -0
  57. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/ask_helper.py +0 -0
  58. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/auth.py +0 -0
  59. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/backup.py +0 -0
  60. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/billing.py +0 -0
  61. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/blender_agent.py +0 -0
  62. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/builtin/skills/utim-guide/SKILL.md +0 -0
  63. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/config.py +0 -0
  64. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/crawler/__init__.py +0 -0
  65. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/crawler/markdown_distiller.py +0 -0
  66. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/dependency_manager.py +0 -0
  67. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/doctor.py +0 -0
  68. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/evidence_registry.py +0 -0
  69. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/harbor.py +0 -0
  70. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/knowledge_graph.py +0 -0
  71. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/local_db.py +0 -0
  72. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/logger.py +0 -0
  73. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/mcp_clean_wrapper.py +0 -0
  74. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/mcp_client.py +0 -0
  75. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/mcp_registry.json +0 -0
  76. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/models.txt +0 -0
  77. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/personalities.py +0 -0
  78. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/platform_installer.py +0 -0
  79. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/plugins/__init__.py +0 -0
  80. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/plugins/hooks.py +0 -0
  81. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/plugins/loader.py +0 -0
  82. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/plugins/manager.py +0 -0
  83. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/plugins/registry.py +0 -0
  84. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/plugins/runtime.py +0 -0
  85. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/plugins/schema.py +0 -0
  86. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/plugins/tool_loader.py +0 -0
  87. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/reflection.py +0 -0
  88. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/report.py +0 -0
  89. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/scrapy_search.py +0 -0
  90. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/sdk/__init__.py +0 -0
  91. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/sdk/config.py +0 -0
  92. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/sdk/context.py +0 -0
  93. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/sdk/events.py +0 -0
  94. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/sdk/handlers.py +0 -0
  95. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/sdk/session.py +0 -0
  96. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/sdk/sidecar.py +0 -0
  97. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/__init__.py +0 -0
  98. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/admin_auth.py +0 -0
  99. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/attribution.py +0 -0
  100. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/audit_log.py +0 -0
  101. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/batch_processor.py +0 -0
  102. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/captcha.py +0 -0
  103. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/cli_auth.py +0 -0
  104. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/concurrency.py +0 -0
  105. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/docs_md/SECRET_PROVISIONING.md +0 -0
  106. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/docs_md/changelog.md +0 -0
  107. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/docs_md/features.md +0 -0
  108. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/docs_md/license.md +0 -0
  109. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/docs_md/privacy.md +0 -0
  110. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/docs_md/refund.md +0 -0
  111. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/docs_md/support.md +0 -0
  112. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/email_utils.py +0 -0
  113. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/exchange_rate.py +0 -0
  114. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/firebase.py +0 -0
  115. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/fix_duplicate_users.py +0 -0
  116. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/history.py +0 -0
  117. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/local_llm.py +0 -0
  118. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/logging_config.py +0 -0
  119. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/micro_batcher.py +0 -0
  120. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/model_agent.py +0 -0
  121. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/pricing_updater.py +0 -0
  122. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/provision_build.py +0 -0
  123. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/rate_limit.py +0 -0
  124. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/rewards_engine.py +0 -0
  125. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/admin_bonus_routes.py +0 -0
  126. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/admin_db_routes.py +0 -0
  127. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/auth_routes.py +0 -0
  128. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/billing_routes.py +0 -0
  129. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/credit_routes.py +0 -0
  130. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/feedback_routes.py +0 -0
  131. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/marketplace_routes.py +0 -0
  132. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/quota_share_routes.py +0 -0
  133. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/referral_routes.py +0 -0
  134. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/rewards_routes.py +0 -0
  135. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/security_routes.py +0 -0
  136. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/session_routes.py +0 -0
  137. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/share_routes.py +0 -0
  138. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/routes/webhook_routes.py +0 -0
  139. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/security_alerts.py +0 -0
  140. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/server.py +0 -0
  141. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/server/storage_nodes.py +0 -0
  142. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/share.py +0 -0
  143. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/share_tui.py +0 -0
  144. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/situational_scoring.py +0 -0
  145. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/state.py +0 -0
  146. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/subagent_manager.py +0 -0
  147. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/task_dispatcher.py +0 -0
  148. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/task_logger.py +0 -0
  149. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tools_grep.py +0 -0
  150. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/__init__.py +0 -0
  151. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/feedback_dialog.py +0 -0
  152. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/history_dialog.py +0 -0
  153. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/marketplace_app_state.py +0 -0
  154. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/marketplace_dialog.py +0 -0
  155. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/marketplace_layout_engine.py +0 -0
  156. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/mcp_dialog.py +0 -0
  157. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/miniagents_dialog.py +0 -0
  158. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/personality_dialog.py +0 -0
  159. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/publish_dialog.py +0 -0
  160. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/quota_dialog.py +0 -0
  161. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/quota_redeem_dialog.py +0 -0
  162. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/quota_share_dialog.py +0 -0
  163. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/resume_dialog.py +0 -0
  164. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/rewards_tui.py +0 -0
  165. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/skills_dialog.py +0 -0
  166. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/subagents_dialog.py +0 -0
  167. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/tasks_dialog.py +0 -0
  168. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/thinking_display.py +0 -0
  169. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/tui/update_dialog.py +0 -0
  170. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/update_checker.py +0 -0
  171. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/utilities.py +0 -0
  172. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/utimmodel.txt +0 -0
  173. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/vector_memory.py +0 -0
  174. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/wheel.py +0 -0
  175. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli/workspace.py +0 -0
  176. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli.egg-info/dependency_links.txt +0 -0
  177. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli.egg-info/entry_points.txt +0 -0
  178. {utim_cli-2.3.26 → utim_cli-2.4.1}/utim_cli.egg-info/requires.txt +0 -0
  179. {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.26
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "utim-cli"
7
- version = "2.3.26"
7
+ version = "2.4.1"
8
8
 
9
9
  description = "UTIM – Universal Terminal Intelligence Manager. A powerful agentic AI coding assistant for your terminal."
10
10
  readme = "README.md"
@@ -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.3.26"
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
@@ -5,7 +5,7 @@ try:
5
5
  from utim_cli._version import VERSION as __version__
6
6
  except ImportError:
7
7
  # Fallback during first-install / editable-install edge cases
8
- __version__ = "2.3.26"
8
+ __version__ = "2.4.1"
9
9
 
10
10
 
11
11
  import builtins, os, pathlib, urllib.parse, re
@@ -11,6 +11,6 @@
11
11
  # from here so every surface stays in sync automatically.
12
12
  # ─────────────────────────────────────────────────────────────────────────────
13
13
 
14
- VERSION = "2.3.26"
14
+ VERSION = "2.4.1"
15
15
 
16
16
 
@@ -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
- result = tool_functions[tool_name](**arguments)
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
- return f"Error executing {tool_name}: {str(e)}"
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=getattr(self, "model", None))
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": tc.get("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'}.")