utim-cli 2.4.0__tar.gz → 2.4.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. {utim_cli-2.4.0 → utim_cli-2.4.2}/CHANGELOG.md +20 -0
  2. {utim_cli-2.4.0/utim_cli.egg-info → utim_cli-2.4.2}/PKG-INFO +2 -1
  3. {utim_cli-2.4.0 → utim_cli-2.4.2}/pyproject.toml +2 -1
  4. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim/__init__.py +1 -1
  5. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/__init__.py +1 -1
  6. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/_version.py +1 -1
  7. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/agent.py +59 -1
  8. utim_cli-2.4.2/utim_cli/architecture_indexer.py +269 -0
  9. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/ask_helper.py +5 -4
  10. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/bootstrap.py +73 -0
  11. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/brain.py +178 -170
  12. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/client_utils.py +172 -164
  13. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/config.py +6 -1
  14. utim_cli-2.4.2/utim_cli/constants.py +118 -0
  15. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/context_pruner.py +45 -46
  16. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/crawler/engine.py +187 -14
  17. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/harbor.py +212 -201
  18. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/mcp_client.py +359 -359
  19. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/orchestrator.py +245 -106
  20. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/orchestrator_helpers.py +174 -177
  21. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/orchestrator_llm_transport.py +176 -67
  22. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/orchestrator_tool_exec.py +160 -8
  23. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/orchestrator_turn_history.py +191 -50
  24. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/plugins/schema.py +257 -257
  25. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/reflection.py +43 -4
  26. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/sdk/session.py +340 -340
  27. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/admin_auth.py +6 -0
  28. utim_cli-2.4.2/utim_cli/server/api_models_db.py +1287 -0
  29. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/auth.py +142 -22
  30. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/captcha.py +74 -7
  31. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/concurrency.py +40 -0
  32. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/db.py +369 -18
  33. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/docs_md/about.md +6 -6
  34. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/docs_md/docs.md +761 -761
  35. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/docs_md/pricing.md +24 -24
  36. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/docs_md/terms.md +1 -1
  37. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/email_utils.py +80 -1
  38. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/key_rotator.py +1806 -98
  39. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/logging_config.py +1 -1
  40. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/micro_batcher.py +343 -343
  41. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/models.py +629 -234
  42. utim_cli-2.4.2/utim_cli/server/platform_billing.py +217 -0
  43. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/pricing_updater.py +211 -198
  44. utim_cli-2.4.2/utim_cli/server/provider_balance.py +472 -0
  45. utim_cli-2.4.2/utim_cli/server/provider_discovery.py +546 -0
  46. utim_cli-2.4.2/utim_cli/server/provider_router.py +847 -0
  47. utim_cli-2.4.2/utim_cli/server/rate_limit.py +112 -0
  48. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/router.py +434 -237
  49. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/routes/__init__.py +10 -1
  50. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/routes/admin_db_routes.py +95 -0
  51. utim_cli-2.4.2/utim_cli/server/routes/admin_providers_routes.py +579 -0
  52. utim_cli-2.4.2/utim_cli/server/routes/api_gateway_extensions_routes.py +544 -0
  53. utim_cli-2.4.2/utim_cli/server/routes/api_key_routes.py +117 -0
  54. utim_cli-2.4.2/utim_cli/server/routes/api_models_admin_routes.py +967 -0
  55. utim_cli-2.4.2/utim_cli/server/routes/api_models_public_routes.py +2879 -0
  56. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/routes/auth_routes.py +100 -17
  57. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/routes/billing_routes.py +276 -276
  58. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/routes/completion_routes.py +656 -509
  59. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/routes/credit_routes.py +2 -2
  60. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/routes/feedback_routes.py +106 -88
  61. utim_cli-2.4.2/utim_cli/server/routes/provider_compat_routes.py +575 -0
  62. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/routes/quota_routes.py +165 -98
  63. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/routes/quota_share_routes.py +768 -768
  64. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/routes/rewards_routes.py +95 -62
  65. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/routes/session_routes.py +201 -201
  66. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/routes/share_routes.py +369 -369
  67. utim_cli-2.4.2/utim_cli/server/routes/wallet_routes.py +193 -0
  68. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/routes/webhook_routes.py +58 -7
  69. utim_cli-2.4.2/utim_cli/server/signup_guard.py +237 -0
  70. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/utils.py +354 -3
  71. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/state.py +1 -0
  72. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/subagent_manager.py +1621 -1548
  73. utim_cli-2.4.2/utim_cli/syntax_fix.py +715 -0
  74. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/tools.py +1104 -97
  75. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/tui/model_dialog.py +403 -93
  76. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/tui/resume_dialog.py +1 -1
  77. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/tui/tools_dialog.py +9 -1
  78. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/utim.py +355 -63
  79. utim_cli-2.4.2/utim_cli/voice.py +554 -0
  80. utim_cli-2.4.2/utim_cli/voice_coding.py +288 -0
  81. utim_cli-2.4.2/utim_cli/voice_events.py +409 -0
  82. utim_cli-2.4.2/utim_cli/voice_handler.py +695 -0
  83. utim_cli-2.4.2/utim_cli/voice_instant.py +233 -0
  84. utim_cli-2.4.2/utim_cli/voice_orchestrator.py +392 -0
  85. utim_cli-2.4.2/utim_cli/voice_session.py +964 -0
  86. utim_cli-2.4.2/utim_cli/voice_stt.py +502 -0
  87. utim_cli-2.4.2/utim_cli/voice_tts.py +955 -0
  88. {utim_cli-2.4.0 → utim_cli-2.4.2/utim_cli.egg-info}/PKG-INFO +2 -1
  89. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli.egg-info/SOURCES.txt +24 -0
  90. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli.egg-info/requires.txt +1 -0
  91. utim_cli-2.4.0/utim_cli/constants.py +0 -50
  92. utim_cli-2.4.0/utim_cli/server/rate_limit.py +0 -38
  93. {utim_cli-2.4.0 → utim_cli-2.4.2}/LICENSE +0 -0
  94. {utim_cli-2.4.0 → utim_cli-2.4.2}/MANIFEST.in +0 -0
  95. {utim_cli-2.4.0 → utim_cli-2.4.2}/README.md +0 -0
  96. {utim_cli-2.4.0 → utim_cli-2.4.2}/setup.cfg +0 -0
  97. {utim_cli-2.4.0 → utim_cli-2.4.2}/setup.py +0 -0
  98. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/_chip_test.py +0 -0
  99. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/_verify_repro.py +0 -0
  100. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/_verify_syntax.py +0 -0
  101. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/_wizard_smoke_test.py +0 -0
  102. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/auth.py +0 -0
  103. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/backup.py +0 -0
  104. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/billing.py +0 -0
  105. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/blender_agent.py +0 -0
  106. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/builtin/skills/utim-guide/SKILL.md +0 -0
  107. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/context_inspector.py +0 -0
  108. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/crawler/__init__.py +0 -0
  109. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/crawler/markdown_distiller.py +0 -0
  110. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/dependency_manager.py +0 -0
  111. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/doctor.py +0 -0
  112. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/evidence_registry.py +0 -0
  113. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/knowledge_graph.py +0 -0
  114. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/local_db.py +0 -0
  115. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/logger.py +0 -0
  116. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/mcp_clean_wrapper.py +0 -0
  117. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/mcp_registry.json +0 -0
  118. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/models.txt +0 -0
  119. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/personalities.py +0 -0
  120. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/platform_installer.py +0 -0
  121. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/plugins/__init__.py +0 -0
  122. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/plugins/hooks.py +0 -0
  123. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/plugins/loader.py +0 -0
  124. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/plugins/manager.py +0 -0
  125. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/plugins/registry.py +0 -0
  126. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/plugins/runtime.py +0 -0
  127. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/plugins/tool_loader.py +0 -0
  128. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/report.py +0 -0
  129. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/scrapy_search.py +0 -0
  130. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/sdk/__init__.py +0 -0
  131. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/sdk/config.py +0 -0
  132. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/sdk/context.py +0 -0
  133. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/sdk/events.py +0 -0
  134. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/sdk/handlers.py +0 -0
  135. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/sdk/sidecar.py +0 -0
  136. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/__init__.py +0 -0
  137. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/attribution.py +0 -0
  138. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/audit_log.py +0 -0
  139. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/batch_processor.py +0 -0
  140. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/cli_auth.py +0 -0
  141. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/docs_md/SECRET_PROVISIONING.md +0 -0
  142. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/docs_md/changelog.md +0 -0
  143. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/docs_md/features.md +0 -0
  144. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/docs_md/license.md +0 -0
  145. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/docs_md/privacy.md +0 -0
  146. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/docs_md/refund.md +0 -0
  147. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/docs_md/support.md +0 -0
  148. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/exchange_rate.py +0 -0
  149. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/firebase.py +0 -0
  150. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/fix_duplicate_users.py +0 -0
  151. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/history.py +0 -0
  152. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/local_llm.py +0 -0
  153. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/model_agent.py +0 -0
  154. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/plan_integrity.py +0 -0
  155. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/provision_build.py +0 -0
  156. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/rewards_engine.py +0 -0
  157. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/routes/admin_bonus_routes.py +0 -0
  158. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/routes/marketplace_routes.py +0 -0
  159. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/routes/referral_routes.py +0 -0
  160. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/routes/security_routes.py +0 -0
  161. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/security_alerts.py +0 -0
  162. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/server.py +0 -0
  163. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/server/storage_nodes.py +0 -0
  164. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/share.py +0 -0
  165. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/share_tui.py +0 -0
  166. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/situational_scoring.py +0 -0
  167. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/task_dispatcher.py +0 -0
  168. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/task_logger.py +0 -0
  169. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/tools_grep.py +0 -0
  170. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/tui/__init__.py +0 -0
  171. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/tui/compression_dialog.py +0 -0
  172. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/tui/feedback_dialog.py +0 -0
  173. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/tui/history_dialog.py +0 -0
  174. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/tui/marketplace_app_state.py +0 -0
  175. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/tui/marketplace_dialog.py +0 -0
  176. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/tui/marketplace_layout_engine.py +0 -0
  177. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/tui/mcp_dialog.py +0 -0
  178. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/tui/miniagents_dialog.py +0 -0
  179. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/tui/personality_dialog.py +0 -0
  180. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/tui/plugins_dialog.py +0 -0
  181. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/tui/publish_dialog.py +0 -0
  182. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/tui/quota_dialog.py +0 -0
  183. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/tui/quota_redeem_dialog.py +0 -0
  184. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/tui/quota_share_dialog.py +0 -0
  185. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/tui/rewards_tui.py +0 -0
  186. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/tui/skills_dialog.py +0 -0
  187. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/tui/subagents_dialog.py +0 -0
  188. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/tui/tasks_dialog.py +0 -0
  189. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/tui/thinking_display.py +0 -0
  190. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/tui/update_dialog.py +0 -0
  191. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/update_checker.py +0 -0
  192. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/utilities.py +0 -0
  193. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/utimmodel.txt +0 -0
  194. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/vector_memory.py +0 -0
  195. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/wheel.py +0 -0
  196. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli/workspace.py +0 -0
  197. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli.egg-info/dependency_links.txt +0 -0
  198. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli.egg-info/entry_points.txt +0 -0
  199. {utim_cli-2.4.0 → utim_cli-2.4.2}/utim_cli.egg-info/top_level.txt +0 -0
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.4.2] - 2026-10-13
4
+
5
+ ### 🤖 Sub-Agent Swarm Model Configuration & Hardcoded Lock
6
+ - **Swarm Model Settings in `/model`**: Added a dedicated configuration section in the `/model` dialog allowing users to choose the model used across all swarm subagents.
7
+ - **Strict User Lock**: When a specific model is selected, it is hardcoded—the main agent cannot override or change it, even if custom `model_id` parameters are passed to `invoke_subagents`.
8
+ - **Dynamic Auto Mode**: Added a "Let the agent decide (Auto)" option allowing the main agent to dynamically assign models per task or fall back to its active model.
9
+
10
+ ### ⚡ Non-Interactive Command Execution (`--hard-skip-perms`)
11
+ - **Hard Skip Permissions**: Added the `--hard-skip-perms` CLI flag, `UTIM_HARD_SKIP_PERMS=1` environment variable, and `/hard-skip-perms` slash command.
12
+ - **Unrestricted Command Execution**: Automatically approves terminal commands without prompting for user confirmation, even for unsafe or destructive commands.
13
+
14
+ ### 🐛 Bug Fixes
15
+ - **Premature Halting Fix**: Fixed an issue where the model abruptly stopped if it completed inspection tools and emitted an action promise without executing further mutation tools, now triggering an automatic continuation nudge.
16
+
17
+ ## [2.4.1] - 2026-09-18
18
+
19
+ ### Bug Fixes
20
+ - Fixed the `UnboundLocalError: rotator` crash when resolving failover providers.
21
+ - Fixed an Admin UI bug that zeroed out free model upstream prices in the Model Pricing & Provisioning interface.
22
+
3
23
  ## [2.4.0] - 2026-09-14
4
24
 
5
25
  ### ✨ 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.2
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
@@ -22,6 +22,7 @@ Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
23
  Requires-Dist: requests==2.31.0
24
24
  Requires-Dist: aiohttp>=3.9.5
25
+ Requires-Dist: httpx>=0.25.0
25
26
  Requires-Dist: python-dotenv==1.0.1
26
27
  Requires-Dist: urllib3==1.26.18
27
28
  Requires-Dist: charset-normalizer==3.3.2
@@ -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.2"
8
8
 
9
9
  description = "UTIM – Universal Terminal Intelligence Manager. A powerful agentic AI coding assistant for your terminal."
10
10
  readme = "README.md"
@@ -25,6 +25,7 @@ classifiers = [
25
25
  dependencies = [
26
26
  "requests==2.31.0",
27
27
  "aiohttp>=3.9.5",
28
+ "httpx>=0.25.0",
28
29
  "python-dotenv==1.0.1",
29
30
  "urllib3==1.26.18",
30
31
  "charset-normalizer==3.3.2",
@@ -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.2"
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.2"
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.2"
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):
@@ -440,6 +454,15 @@ class ReActAgent:
440
454
  "tool_calls": tool_calls,
441
455
  })
442
456
 
457
+ # Clear any stale vision images and videos from a previous iteration
458
+ try:
459
+ import utim_cli.tools as _tools_module
460
+ _tools_module._pending_vision_images.clear()
461
+ _tools_module._pending_vision_videos.clear()
462
+ _tools_module._active_model_id = self.model_id
463
+ except Exception:
464
+ pass
465
+
443
466
  # Execute each tool call
444
467
  for i, tc in enumerate(tool_calls):
445
468
  result = self._execute_tool_call(tc)
@@ -451,6 +474,41 @@ class ReActAgent:
451
474
  "tool_call_id": tc_id,
452
475
  "content": result,
453
476
  })
477
+
478
+ # Drain and inject vision images & native videos into conversation
479
+ try:
480
+ import utim_cli.tools as _tools_module
481
+ _vision_queue = getattr(_tools_module, "_pending_vision_images", [])
482
+ for _vimg in list(_vision_queue or []):
483
+ self.messages.append({
484
+ "role": "user",
485
+ "content": [
486
+ {"type": "text", "text": _vimg.get("group_header") or f"[{_vimg.get('meta', 'Image')}]"},
487
+ {
488
+ "type": "image_url",
489
+ "image_url": {"url": f"data:{_vimg['mime']};base64,{_vimg['b64']}"},
490
+ },
491
+ ],
492
+ })
493
+ if _vision_queue:
494
+ _vision_queue.clear()
495
+
496
+ _video_queue = getattr(_tools_module, "_pending_vision_videos", [])
497
+ for _vent in list(_video_queue or []):
498
+ self.messages.append({
499
+ "role": "user",
500
+ "content": [
501
+ {"type": "text", "text": _vent.get("header") or f"[Video: {_vent.get('filepath', '')}]"},
502
+ {
503
+ "type": "video_url",
504
+ "video_url": {"url": f"data:{_vent['mime']};base64,{_vent['b64']}"},
505
+ },
506
+ ],
507
+ })
508
+ if _video_queue:
509
+ _video_queue.clear()
510
+ except Exception:
511
+ pass
454
512
  else:
455
513
  self.console.print(f"\n[bold yellow]Agent paused after reaching maximum iterations ({max_iterations}).[/bold yellow]")
456
514
  self.console.print("[dim]You can type 'continue' to resume the task.[/dim]\n")
@@ -0,0 +1,269 @@
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(abs_path)
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
+ domain_links = ", ".join([
227
+ "[file:///{}]".format(str(arch_dir.absolute() / m).replace(chr(92), "/"))
228
+ for m in generated_mds
229
+ ])
230
+ conventions = [
231
+ "## Innate Project Architecture Rules",
232
+ "- **Organic Component Isolation**: The project is structured organically. Do not bloat central routers or core files.",
233
+ "- **Multifile Designs**: When adding large new features, ALWAYS create new standalone component files based on the existing directory structure.",
234
+ "- **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.",
235
+ "- **Available Domain Files**: " + domain_links
236
+ ]
237
+ (arch_dir / "conventions_summary.txt").write_text("\n".join(conventions), encoding="utf-8")
238
+
239
+ unique_nodes = {n["id"]: n for n in nodes}.values()
240
+
241
+ graph_data = {
242
+ "project_path": abs_path,
243
+ "project_type": project_type,
244
+ "total_files": len(inventory),
245
+ "nodes": list(unique_nodes),
246
+ "edges": edges,
247
+ "capabilities": ["Project orientation", "Graph traversal", "Symbol resolution"]
248
+ }
249
+
250
+ with open(brain_file, 'w', encoding='utf-8') as bf:
251
+ json.dump(graph_data, bf, indent=2)
252
+
253
+ component_bullets = "\n".join([
254
+ f"- {m}: [file:///{str(arch_dir.absolute() / m).replace(chr(92), '/')}]"
255
+ for m in generated_mds
256
+ ]) if generated_mds else "- None"
257
+
258
+ return (
259
+ f"✓ Architecture lock successfully generated at: {arch_dir}\n"
260
+ f"Identified stack: {', '.join(project_type) or 'Unknown'} | Indexed files: {len(inventory)}\n\n"
261
+ f"WHAT WAS PROVIDED:\n"
262
+ f"1. Structural Dependency Graph: `00_architecture_map.json` (all file nodes, module imports, and symbol definitions)\n"
263
+ f"2. Architecture Conventions: `conventions_summary.txt`\n"
264
+ f"3. Modular Component Blueprints ({len(generated_mds)} domain modules):\n{component_bullets}\n\n"
265
+ f"WHY THIS IS NEEDED & NEXT ACTION:\n"
266
+ f"- Zero-token structural grounding eliminates hallucinations and token-wasting blind repo exploration.\n"
267
+ f"- NEXT STEP: Use `read_file` on the relevant `component_<domain>.md` blueprint above matching your task to inspect exact function/class signatures and dependencies before reading or editing source code."
268
+ )
269
+
@@ -5,7 +5,8 @@ A dedicated, lightweight helper miniagent sharing the core UTIM LLM execution ar
5
5
  Provides real-time project insight, chat explanation, and conversational companionship
6
6
  without interrupting the main agent's active execution loop.
7
7
 
8
- Uses 'openrouter/free' as the primary model (first fallback), then falls back through all registered free models from the server model_registry.
8
+ Uses 'nvidia/nemotron-3.5-lightning:free' as the primary model (first fallback), then falls
9
+ back through all registered free models from the server model_registry.
9
10
  Has access to exactly one tool: 'read_chat'.
10
11
  """
11
12
 
@@ -27,16 +28,16 @@ from rich import box
27
28
 
28
29
  from utim_cli.client_utils import get_free_models_fallback_chain
29
30
 
30
- # Primary helper model = 'openrouter/free' (cheapest free router), then falls
31
+ # Primary helper model = 'nvidia/nemotron-3.5-lightning:free' (verified free model), then falls
31
32
  # back through the verified free models from the server DB registry.
32
33
  _HELPER_FALLBACK_CHAIN = get_free_models_fallback_chain()
33
- PRIMARY_HELPER_MODEL = _HELPER_FALLBACK_CHAIN[0] if _HELPER_FALLBACK_CHAIN else "openrouter/free"
34
+ PRIMARY_HELPER_MODEL = _HELPER_FALLBACK_CHAIN[0] if _HELPER_FALLBACK_CHAIN else "nvidia/nemotron-3.5-lightning:free"
34
35
 
35
36
 
36
37
 
37
38
  HELPER_SYSTEM_PROMPT = """<identity>
38
39
  You are the UTIM Sidecar Assistant, an embedded technical companion running alongside the active UTIM CLI session.
39
- - Your identity is strictly the UTIM Sidecar Assistant. Never identify as ox-alpha, ChatGPT, Claude, Qwen, DeepSeek, or other base models.
40
+ - Your identity is strictly the UTIM Sidecar Assistant. If asked what model you are running on, state your active model name accurately.
40
41
  </identity>
41
42
 
42
43
  Your mission is to provide real-time architectural insights, explain active agent operations, assess codebase states, and answer developer questions clearly and factually.
@@ -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:
@@ -46,11 +47,83 @@ def initialize_utim() -> str:
46
47
  _sync_global_experience(api_key, os.getcwd())
47
48
  except Exception:
48
49
  pass
50
+
51
+ # 6. Pre-warm instant phrase voices in background
52
+ try:
53
+ from utim_cli.voice_instant import warm_instant_phrases
54
+ warm_instant_phrases(background=True)
55
+ except Exception:
56
+ pass
49
57
 
50
58
  db_path = UTIM_DIR / 'utim_local.db'
51
59
  return str(db_path)
52
60
 
53
61
 
62
+ def _scaffold_local_ui_skill():
63
+ """Auto-generate UI design guide in the local workspace if .agents exists or it looks like a project."""
64
+ import os
65
+ from pathlib import Path
66
+
67
+ # Only scaffold if we are in a project directory or .agents already exists
68
+ if not (Path('.git').exists() or Path('.agents').exists() or Path('package.json').exists() or Path('requirements.txt').exists()):
69
+ return
70
+
71
+ try:
72
+ skill_dir = Path('.agents/skills/Ui design Guide')
73
+ skill_dir.mkdir(parents=True, exist_ok=True)
74
+ skill_md = skill_dir / 'SKILL.md'
75
+
76
+ if not skill_md.exists():
77
+ skill_content = """---
78
+ name: Ui design Guide
79
+ description: Directives and best practices for creating beautiful, modern, and highly polished web interfaces.
80
+ ---
81
+
82
+ # UI Design Guide
83
+
84
+ 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.
85
+
86
+ ## 1. Typography
87
+ - **Fonts**: Use modern, clean sans-serif fonts (e.g., Inter, Roboto, San Francisco, or system-ui).
88
+ - **Hierarchy**: Clearly distinguish between headings (H1, H2) and body text using font weights (e.g., bold headers, regular body) and sizes.
89
+ - **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.
90
+
91
+ ## 2. Layout & Whitespace (Negative Space)
92
+ - **Breathing Room**: Be generous with padding and margins. Do not cram elements together.
93
+ - **Alignment**: Use CSS Flexbox or Grid for perfect alignment. Center things deliberately.
94
+ - **Constraints**: Limit the maximum width of text containers (e.g., `max-w-3xl`) so lines don't stretch too far across wide screens.
95
+
96
+ ## 3. Colors & Theme
97
+ - **Backgrounds**: Use soft, off-white (e.g., `#f9fafb`) or rich dark (e.g., `#0f172a`) background colors instead of pure `#ffffff` or `#000000`.
98
+ - **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.
99
+ - **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);`).
100
+
101
+ ## 4. Components & Shapes
102
+ - **Corners**: Use consistent `border-radius` (e.g., `8px` or `12px` for cards/buttons, full pill-shapes for badges).
103
+ - **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)`).
104
+ - **Borders**: If using borders, make them very subtle (e.g., `1px solid rgba(255,255,255,0.1)` or `#e5e7eb`).
105
+
106
+ ## 5. Micro-Interactions & States
107
+ - **Transitions**: Apply `transition: all 0.2s ease;` to buttons, links, and interactive elements.
108
+ - **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.
109
+ - **Focus**: Remove default browser outlines and replace them with custom, aesthetic focus rings.
110
+
111
+ ## 6. Responsiveness
112
+ - Ensure all designs are mobile-friendly first.
113
+ - Use responsive units or media queries to collapse grids into single columns on smaller screens.
114
+ - Ensure tap targets (buttons, links) are at least `44px` tall on mobile devices.
115
+
116
+ ## Directives
117
+ - **Never use raw unstyled HTML** for a final output unless explicitly requested.
118
+ - **Use modern CSS frameworks** (like Tailwind CSS) if permitted by the project, or write clean, well-structured vanilla CSS using CSS Variables for theming.
119
+ - **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.
120
+ """
121
+ skill_md.write_text(skill_content, encoding='utf-8')
122
+
123
+ except Exception:
124
+ pass
125
+
126
+
54
127
  def _sync_global_experience(api_key: str, current_folder: str):
55
128
  """
56
129
  Track which project folder the user is currently in (server-side record only).