utim-cli 2.4.0__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 (178) hide show
  1. {utim_cli-2.4.0 → utim_cli-2.4.1}/CHANGELOG.md +6 -0
  2. {utim_cli-2.4.0/utim_cli.egg-info → utim_cli-2.4.1}/PKG-INFO +1 -1
  3. {utim_cli-2.4.0 → utim_cli-2.4.1}/pyproject.toml +1 -1
  4. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim/__init__.py +1 -1
  5. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/__init__.py +1 -1
  6. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/_version.py +1 -1
  7. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/agent.py +15 -1
  8. utim_cli-2.4.1/utim_cli/architecture_indexer.py +251 -0
  9. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/bootstrap.py +66 -0
  10. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/brain.py +33 -2
  11. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/client_utils.py +9 -138
  12. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/constants.py +2 -2
  13. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/orchestrator.py +53 -9
  14. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/orchestrator_helpers.py +91 -64
  15. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/orchestrator_llm_transport.py +42 -18
  16. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/orchestrator_tool_exec.py +126 -0
  17. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/orchestrator_turn_history.py +26 -16
  18. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/db.py +44 -0
  19. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/docs_md/about.md +6 -6
  20. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/docs_md/docs.md +761 -761
  21. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/docs_md/terms.md +1 -1
  22. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/key_rotator.py +1680 -97
  23. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/models.py +102 -14
  24. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/router.py +72 -119
  25. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/__init__.py +2 -1
  26. utim_cli-2.4.1/utim_cli/server/routes/admin_providers_routes.py +511 -0
  27. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/completion_routes.py +422 -427
  28. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/quota_routes.py +46 -8
  29. utim_cli-2.4.1/utim_cli/syntax_fix.py +715 -0
  30. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tools.py +53 -20
  31. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/model_dialog.py +58 -20
  32. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/utim.py +25 -3
  33. utim_cli-2.4.1/utim_cli/voice.py +537 -0
  34. utim_cli-2.4.1/utim_cli/voice_handler.py +0 -0
  35. {utim_cli-2.4.0 → utim_cli-2.4.1/utim_cli.egg-info}/PKG-INFO +1 -1
  36. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli.egg-info/SOURCES.txt +5 -0
  37. {utim_cli-2.4.0 → utim_cli-2.4.1}/LICENSE +0 -0
  38. {utim_cli-2.4.0 → utim_cli-2.4.1}/MANIFEST.in +0 -0
  39. {utim_cli-2.4.0 → utim_cli-2.4.1}/README.md +0 -0
  40. {utim_cli-2.4.0 → utim_cli-2.4.1}/setup.cfg +0 -0
  41. {utim_cli-2.4.0 → utim_cli-2.4.1}/setup.py +0 -0
  42. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/_chip_test.py +0 -0
  43. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/_verify_repro.py +0 -0
  44. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/_verify_syntax.py +0 -0
  45. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/_wizard_smoke_test.py +0 -0
  46. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/ask_helper.py +0 -0
  47. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/auth.py +0 -0
  48. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/backup.py +0 -0
  49. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/billing.py +0 -0
  50. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/blender_agent.py +0 -0
  51. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/builtin/skills/utim-guide/SKILL.md +0 -0
  52. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/config.py +0 -0
  53. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/context_inspector.py +0 -0
  54. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/context_pruner.py +0 -0
  55. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/crawler/__init__.py +0 -0
  56. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/crawler/engine.py +0 -0
  57. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/crawler/markdown_distiller.py +0 -0
  58. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/dependency_manager.py +0 -0
  59. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/doctor.py +0 -0
  60. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/evidence_registry.py +0 -0
  61. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/harbor.py +0 -0
  62. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/knowledge_graph.py +0 -0
  63. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/local_db.py +0 -0
  64. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/logger.py +0 -0
  65. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/mcp_clean_wrapper.py +0 -0
  66. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/mcp_client.py +0 -0
  67. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/mcp_registry.json +0 -0
  68. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/models.txt +0 -0
  69. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/personalities.py +0 -0
  70. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/platform_installer.py +0 -0
  71. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/plugins/__init__.py +0 -0
  72. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/plugins/hooks.py +0 -0
  73. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/plugins/loader.py +0 -0
  74. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/plugins/manager.py +0 -0
  75. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/plugins/registry.py +0 -0
  76. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/plugins/runtime.py +0 -0
  77. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/plugins/schema.py +0 -0
  78. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/plugins/tool_loader.py +0 -0
  79. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/reflection.py +0 -0
  80. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/report.py +0 -0
  81. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/scrapy_search.py +0 -0
  82. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/sdk/__init__.py +0 -0
  83. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/sdk/config.py +0 -0
  84. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/sdk/context.py +0 -0
  85. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/sdk/events.py +0 -0
  86. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/sdk/handlers.py +0 -0
  87. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/sdk/session.py +0 -0
  88. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/sdk/sidecar.py +0 -0
  89. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/__init__.py +0 -0
  90. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/admin_auth.py +0 -0
  91. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/attribution.py +0 -0
  92. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/audit_log.py +0 -0
  93. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/auth.py +0 -0
  94. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/batch_processor.py +0 -0
  95. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/captcha.py +0 -0
  96. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/cli_auth.py +0 -0
  97. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/concurrency.py +0 -0
  98. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/docs_md/SECRET_PROVISIONING.md +0 -0
  99. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/docs_md/changelog.md +0 -0
  100. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/docs_md/features.md +0 -0
  101. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/docs_md/license.md +0 -0
  102. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/docs_md/pricing.md +0 -0
  103. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/docs_md/privacy.md +0 -0
  104. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/docs_md/refund.md +0 -0
  105. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/docs_md/support.md +0 -0
  106. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/email_utils.py +0 -0
  107. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/exchange_rate.py +0 -0
  108. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/firebase.py +0 -0
  109. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/fix_duplicate_users.py +0 -0
  110. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/history.py +0 -0
  111. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/local_llm.py +0 -0
  112. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/logging_config.py +0 -0
  113. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/micro_batcher.py +0 -0
  114. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/model_agent.py +0 -0
  115. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/plan_integrity.py +0 -0
  116. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/pricing_updater.py +0 -0
  117. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/provision_build.py +0 -0
  118. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/rate_limit.py +0 -0
  119. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/rewards_engine.py +0 -0
  120. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/admin_bonus_routes.py +0 -0
  121. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/admin_db_routes.py +0 -0
  122. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/auth_routes.py +0 -0
  123. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/billing_routes.py +0 -0
  124. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/credit_routes.py +0 -0
  125. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/feedback_routes.py +0 -0
  126. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/marketplace_routes.py +0 -0
  127. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/quota_share_routes.py +0 -0
  128. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/referral_routes.py +0 -0
  129. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/rewards_routes.py +0 -0
  130. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/security_routes.py +0 -0
  131. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/session_routes.py +0 -0
  132. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/share_routes.py +0 -0
  133. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/routes/webhook_routes.py +0 -0
  134. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/security_alerts.py +0 -0
  135. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/server.py +0 -0
  136. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/storage_nodes.py +0 -0
  137. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/server/utils.py +0 -0
  138. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/share.py +0 -0
  139. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/share_tui.py +0 -0
  140. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/situational_scoring.py +0 -0
  141. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/state.py +0 -0
  142. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/subagent_manager.py +0 -0
  143. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/task_dispatcher.py +0 -0
  144. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/task_logger.py +0 -0
  145. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tools_grep.py +0 -0
  146. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/__init__.py +0 -0
  147. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/compression_dialog.py +0 -0
  148. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/feedback_dialog.py +0 -0
  149. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/history_dialog.py +0 -0
  150. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/marketplace_app_state.py +0 -0
  151. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/marketplace_dialog.py +0 -0
  152. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/marketplace_layout_engine.py +0 -0
  153. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/mcp_dialog.py +0 -0
  154. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/miniagents_dialog.py +0 -0
  155. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/personality_dialog.py +0 -0
  156. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/plugins_dialog.py +0 -0
  157. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/publish_dialog.py +0 -0
  158. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/quota_dialog.py +0 -0
  159. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/quota_redeem_dialog.py +0 -0
  160. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/quota_share_dialog.py +0 -0
  161. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/resume_dialog.py +0 -0
  162. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/rewards_tui.py +0 -0
  163. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/skills_dialog.py +0 -0
  164. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/subagents_dialog.py +0 -0
  165. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/tasks_dialog.py +0 -0
  166. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/thinking_display.py +0 -0
  167. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/tools_dialog.py +0 -0
  168. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/tui/update_dialog.py +0 -0
  169. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/update_checker.py +0 -0
  170. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/utilities.py +0 -0
  171. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/utimmodel.txt +0 -0
  172. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/vector_memory.py +0 -0
  173. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/wheel.py +0 -0
  174. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli/workspace.py +0 -0
  175. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli.egg-info/dependency_links.txt +0 -0
  176. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli.egg-info/entry_points.txt +0 -0
  177. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli.egg-info/requires.txt +0 -0
  178. {utim_cli-2.4.0 → utim_cli-2.4.1}/utim_cli.egg-info/top_level.txt +0 -0
@@ -1,5 +1,11 @@
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
+
3
9
  ## [2.4.0] - 2026-09-14
4
10
 
5
11
  ### ✨ New Features & Enhancements
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: utim-cli
3
- Version: 2.4.0
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.4.0"
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.4.0"
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.4.0"
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.4.0"
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
@@ -293,7 +294,20 @@ class ReActAgent:
293
294
  num_lines = math.ceil(getattr(self, "_last_running_indicator_len", 40) / max(1, width))
294
295
  for _ in range(num_lines):
295
296
  self.console.print("\033[F\033[K", end="")
296
- 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}"
297
311
 
298
312
 
299
313
  def run(self, max_iterations: int = 500, show_tools: bool = True):
@@ -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'}.")
@@ -37,6 +37,7 @@ def initialize_utim() -> str:
37
37
 
38
38
  # 4. Auto-create skills directory and default skills/rules
39
39
  _write_skills_and_agents()
40
+ _scaffold_local_ui_skill()
40
41
 
41
42
  # 5. Sync global/local experience database across project folders
42
43
  try:
@@ -51,6 +52,71 @@ def initialize_utim() -> str:
51
52
  return str(db_path)
52
53
 
53
54
 
55
+ def _scaffold_local_ui_skill():
56
+ """Auto-generate UI design guide in the local workspace if .agents exists or it looks like a project."""
57
+ import os
58
+ from pathlib import Path
59
+
60
+ # Only scaffold if we are in a project directory or .agents already exists
61
+ if not (Path('.git').exists() or Path('.agents').exists() or Path('package.json').exists() or Path('requirements.txt').exists()):
62
+ return
63
+
64
+ try:
65
+ skill_dir = Path('.agents/skills/Ui design Guide')
66
+ skill_dir.mkdir(parents=True, exist_ok=True)
67
+ skill_md = skill_dir / 'SKILL.md'
68
+
69
+ if not skill_md.exists():
70
+ skill_content = """---
71
+ name: Ui design Guide
72
+ description: Directives and best practices for creating beautiful, modern, and highly polished web interfaces.
73
+ ---
74
+
75
+ # UI Design Guide
76
+
77
+ When the user asks you to generate something related to design, build a website, or perform a UI overhaul, you MUST apply these modern UI/UX principles to ensure the result is professional, beautiful, and highly polished. Do not settle for default browser styles.
78
+
79
+ ## 1. Typography
80
+ - **Fonts**: Use modern, clean sans-serif fonts (e.g., Inter, Roboto, San Francisco, or system-ui).
81
+ - **Hierarchy**: Clearly distinguish between headings (H1, H2) and body text using font weights (e.g., bold headers, regular body) and sizes.
82
+ - **Readability**: Keep body text line-height around `1.5` to `1.6`. Use slightly muted colors for body text (e.g., `#4b5563` on light themes or `#a1a1aa` on dark themes) rather than stark black or pure white.
83
+
84
+ ## 2. Layout & Whitespace (Negative Space)
85
+ - **Breathing Room**: Be generous with padding and margins. Do not cram elements together.
86
+ - **Alignment**: Use CSS Flexbox or Grid for perfect alignment. Center things deliberately.
87
+ - **Constraints**: Limit the maximum width of text containers (e.g., `max-w-3xl`) so lines don't stretch too far across wide screens.
88
+
89
+ ## 3. Colors & Theme
90
+ - **Backgrounds**: Use soft, off-white (e.g., `#f9fafb`) or rich dark (e.g., `#0f172a`) background colors instead of pure `#ffffff` or `#000000`.
91
+ - **Accents**: Pick a primary brand color (e.g., a vibrant blue or purple) and use it sparingly for primary buttons, active states, and important links.
92
+ - **Gradients & Glassmorphism**: Where appropriate, use subtle background gradients or translucent frosted-glass effects (e.g., `backdrop-filter: blur(10px); background: rgba(255, 255, 255, 0.1);`).
93
+
94
+ ## 4. Components & Shapes
95
+ - **Corners**: Use consistent `border-radius` (e.g., `8px` or `12px` for cards/buttons, full pill-shapes for badges).
96
+ - **Shadows**: Add soft, diffuse box-shadows to cards and dropdowns to create depth and elevation. Avoid harsh, dark shadows (e.g., use `box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1)`).
97
+ - **Borders**: If using borders, make them very subtle (e.g., `1px solid rgba(255,255,255,0.1)` or `#e5e7eb`).
98
+
99
+ ## 5. Micro-Interactions & States
100
+ - **Transitions**: Apply `transition: all 0.2s ease;` to buttons, links, and interactive elements.
101
+ - **Hover/Active**: Always provide visual feedback when an element is hovered (e.g., slightly lighten/darken the background, or lift the element up by 2px) or clicked.
102
+ - **Focus**: Remove default browser outlines and replace them with custom, aesthetic focus rings.
103
+
104
+ ## 6. Responsiveness
105
+ - Ensure all designs are mobile-friendly first.
106
+ - Use responsive units or media queries to collapse grids into single columns on smaller screens.
107
+ - Ensure tap targets (buttons, links) are at least `44px` tall on mobile devices.
108
+
109
+ ## Directives
110
+ - **Never use raw unstyled HTML** for a final output unless explicitly requested.
111
+ - **Use modern CSS frameworks** (like Tailwind CSS) if permitted by the project, or write clean, well-structured vanilla CSS using CSS Variables for theming.
112
+ - **Review your design**: Before finalizing the code, ask yourself: "Does this look like a premium modern SaaS product or a 1990s web page?" Adjust accordingly.
113
+ """
114
+ skill_md.write_text(skill_content, encoding='utf-8')
115
+
116
+ except Exception:
117
+ pass
118
+
119
+
54
120
  def _sync_global_experience(api_key: str, current_folder: str):
55
121
  """
56
122
  Track which project folder the user is currently in (server-side record only).
@@ -104,7 +104,9 @@ def _brain_db_path() -> str:
104
104
  def _project_id(workspace_path: Optional[str] = None) -> str:
105
105
  """Stable short hex ID for the current workspace directory."""
106
106
  cwd = workspace_path or os.getcwd()
107
- return "proj_" + hashlib.sha1(cwd.encode()).hexdigest()[:10]
107
+ # Normalize path to ensure the same directory always yields the same hash
108
+ normalized = os.path.normcase(os.path.normpath(os.path.abspath(cwd)))
109
+ return "proj_" + hashlib.sha1(normalized.encode('utf-8')).hexdigest()[:10]
108
110
 
109
111
  def _project_dir(project_id: str) -> Path:
110
112
  d = _brain_root() / "projects" / project_id
@@ -904,6 +906,16 @@ def reason_over_memories(user_prompt: str, memories: List[Dict]) -> str:
904
906
 
905
907
  # ── Context Injection Helper (called by orchestrator) ─────────────────────────
906
908
 
909
+ _CURRENT_RESOLVED_PROJ_ID = None
910
+
911
+ def get_current_resolved_project() -> Tuple[str, Path]:
912
+ """Return the globally resolved project id and path from the last system prompt injection, or fallback to current workspace."""
913
+ global _CURRENT_RESOLVED_PROJ_ID
914
+ if _CURRENT_RESOLVED_PROJ_ID:
915
+ return _CURRENT_RESOLVED_PROJ_ID, _project_dir(_CURRENT_RESOLVED_PROJ_ID)
916
+ pid = _project_id()
917
+ return pid, _project_dir(pid)
918
+
907
919
  def find_most_relevant_brain_project(query: str = "") -> Tuple[str, Path]:
908
920
  """
909
921
  Find the most relevant brain project folder using local MiniLM vector search.
@@ -911,16 +923,19 @@ def find_most_relevant_brain_project(query: str = "") -> Tuple[str, Path]:
911
923
  embeddings table for the project with the highest similarity score.
912
924
  Falls back to the current workspace project directory.
913
925
  """
926
+ global _CURRENT_RESOLVED_PROJ_ID
914
927
  current_pid = _project_id()
915
928
  current_dir = _project_dir(current_pid)
916
929
 
917
930
  if not query or not query.strip():
931
+ _CURRENT_RESOLVED_PROJ_ID = current_pid
918
932
  return current_pid, current_dir
919
933
 
920
934
  try:
921
935
  brain_root = _brain_root()
922
936
  projects_dir = brain_root / "projects"
923
937
  if not projects_dir.exists():
938
+ _CURRENT_RESOLVED_PROJ_ID = current_pid
924
939
  return current_pid, current_dir
925
940
 
926
941
  # Run MiniLM embedding search against brain_embeddings to score projects
@@ -931,10 +946,12 @@ def find_most_relevant_brain_project(query: str = "") -> Tuple[str, Path]:
931
946
  conn.close()
932
947
 
933
948
  if not rows:
949
+ _CURRENT_RESOLVED_PROJ_ID = current_pid
934
950
  return current_pid, current_dir
935
951
 
936
952
  q_vecs = _embed([query.strip()])
937
953
  if not q_vecs:
954
+ _CURRENT_RESOLVED_PROJ_ID = current_pid
938
955
  return current_pid, current_dir
939
956
 
940
957
  q_vec = q_vecs[0]
@@ -958,11 +975,13 @@ def find_most_relevant_brain_project(query: str = "") -> Tuple[str, Path]:
958
975
  best_pid = max(project_scores.items(), key=lambda x: x[1])[0]
959
976
  best_dir = _project_dir(best_pid)
960
977
  if best_dir.exists():
978
+ _CURRENT_RESOLVED_PROJ_ID = best_pid
961
979
  return best_pid, best_dir
962
980
 
963
981
  except Exception:
964
982
  pass
965
983
 
984
+ _CURRENT_RESOLVED_PROJ_ID = current_pid
966
985
  return current_pid, current_dir
967
986
 
968
987
 
@@ -986,11 +1005,23 @@ def get_brain_context_prompt(user_prompt: str = "") -> str:
986
1005
  proj_url = str(proj_dir.absolute()).replace("\\", "/")
987
1006
  root_url = str(brain_root.absolute()).replace("\\", "/")
988
1007
 
1008
+ conventions = ""
1009
+ # Find the most recent architecture snapshot and inject conventions
1010
+ try:
1011
+ arch_dirs = sorted(proj_dir.glob("arch_*"), key=os.path.getmtime, reverse=True)
1012
+ if arch_dirs:
1013
+ conv_file = arch_dirs[0] / "conventions_summary.txt"
1014
+ if conv_file.exists():
1015
+ conventions = f"\n\n### PROJECT CONVENTIONS ###\n{conv_file.read_text(encoding='utf-8')}\n"
1016
+ except Exception:
1017
+ pass
1018
+
989
1019
  return (
990
1020
  f"\n\n### BRAIN MEMORY DIRECTORY ###\n"
991
1021
  f"Project brain directory: [brain](file:///{proj_url})\n"
992
1022
  f"Global brain directory: [global_brain](file:///{root_url})\n"
993
- f"Note: The paths above are directories. To explore historical project memories, architecture snapshots, or conventions, call `list_directory('{proj_url}')` and `read_file` on the memory files inside it.\n"
1023
+ f"Note: If you need to know about the project, check the brain files in the directory above - you will get exactly what you need. If something is missing, you can freely check the actual source code files.\n"
1024
+ f"{conventions}"
994
1025
  )
995
1026
  except Exception:
996
1027
  return ""