utim-cli 2.3.18__tar.gz → 2.3.19__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 (167) hide show
  1. {utim_cli-2.3.18 → utim_cli-2.3.19}/CHANGELOG.md +7 -0
  2. {utim_cli-2.3.18/utim_cli.egg-info → utim_cli-2.3.19}/PKG-INFO +1 -1
  3. {utim_cli-2.3.18 → utim_cli-2.3.19}/pyproject.toml +1 -1
  4. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim/__init__.py +1 -1
  5. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/__init__.py +1 -1
  6. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/_version.py +1 -1
  7. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/auth.py +13 -1
  8. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/client_utils.py +3 -3
  9. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/orchestrator.py +7 -2
  10. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/model_dialog.py +101 -53
  11. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/utim.py +3 -1
  12. {utim_cli-2.3.18 → utim_cli-2.3.19/utim_cli.egg-info}/PKG-INFO +1 -1
  13. {utim_cli-2.3.18 → utim_cli-2.3.19}/LICENSE +0 -0
  14. {utim_cli-2.3.18 → utim_cli-2.3.19}/MANIFEST.in +0 -0
  15. {utim_cli-2.3.18 → utim_cli-2.3.19}/README.md +0 -0
  16. {utim_cli-2.3.18 → utim_cli-2.3.19}/setup.cfg +0 -0
  17. {utim_cli-2.3.18 → utim_cli-2.3.19}/setup.py +0 -0
  18. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/_verify_repro.py +0 -0
  19. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/_verify_syntax.py +0 -0
  20. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/_wizard_smoke_test.py +0 -0
  21. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/agent.py +0 -0
  22. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/ask_helper.py +0 -0
  23. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/backup.py +0 -0
  24. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/billing.py +0 -0
  25. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/blender_agent.py +0 -0
  26. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/bootstrap.py +0 -0
  27. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/brain.py +0 -0
  28. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/builtin/skills/utim-guide/SKILL.md +0 -0
  29. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/config.py +0 -0
  30. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/constants.py +0 -0
  31. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/context_pruner.py +0 -0
  32. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/crawler/__init__.py +0 -0
  33. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/crawler/engine.py +0 -0
  34. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/crawler/markdown_distiller.py +0 -0
  35. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/dependency_manager.py +0 -0
  36. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/doctor.py +0 -0
  37. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/evidence_registry.py +0 -0
  38. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/harbor.py +0 -0
  39. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/knowledge_graph.py +0 -0
  40. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/local_db.py +0 -0
  41. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/logger.py +0 -0
  42. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/mcp_clean_wrapper.py +0 -0
  43. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/mcp_client.py +0 -0
  44. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/mcp_registry.json +0 -0
  45. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/models.txt +0 -0
  46. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/orchestrator_helpers.py +0 -0
  47. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/orchestrator_llm_transport.py +0 -0
  48. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/orchestrator_tool_exec.py +0 -0
  49. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/orchestrator_turn_history.py +0 -0
  50. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/personalities.py +0 -0
  51. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/platform_installer.py +0 -0
  52. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/plugins/__init__.py +0 -0
  53. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/plugins/hooks.py +0 -0
  54. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/plugins/loader.py +0 -0
  55. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/plugins/manager.py +0 -0
  56. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/plugins/registry.py +0 -0
  57. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/plugins/runtime.py +0 -0
  58. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/plugins/schema.py +0 -0
  59. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/plugins/tool_loader.py +0 -0
  60. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/reflection.py +0 -0
  61. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/report.py +0 -0
  62. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/scrapy_search.py +0 -0
  63. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/sdk/__init__.py +0 -0
  64. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/sdk/config.py +0 -0
  65. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/sdk/context.py +0 -0
  66. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/sdk/events.py +0 -0
  67. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/sdk/handlers.py +0 -0
  68. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/sdk/session.py +0 -0
  69. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/sdk/sidecar.py +0 -0
  70. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/__init__.py +0 -0
  71. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/admin_auth.py +0 -0
  72. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/attribution.py +0 -0
  73. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/audit_log.py +0 -0
  74. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/auth.py +0 -0
  75. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/batch_processor.py +0 -0
  76. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/captcha.py +0 -0
  77. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/cli_auth.py +0 -0
  78. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/concurrency.py +0 -0
  79. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/db.py +0 -0
  80. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/docs_md/SECRET_PROVISIONING.md +0 -0
  81. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/docs_md/about.md +0 -0
  82. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/docs_md/changelog.md +0 -0
  83. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/docs_md/docs.md +0 -0
  84. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/docs_md/features.md +0 -0
  85. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/docs_md/license.md +0 -0
  86. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/docs_md/pricing.md +0 -0
  87. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/docs_md/privacy.md +0 -0
  88. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/docs_md/refund.md +0 -0
  89. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/docs_md/support.md +0 -0
  90. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/docs_md/terms.md +0 -0
  91. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/email_utils.py +0 -0
  92. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/exchange_rate.py +0 -0
  93. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/firebase.py +0 -0
  94. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/fix_duplicate_users.py +0 -0
  95. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/history.py +0 -0
  96. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/key_rotator.py +0 -0
  97. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/local_llm.py +0 -0
  98. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/logging_config.py +0 -0
  99. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/micro_batcher.py +0 -0
  100. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/model_agent.py +0 -0
  101. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/models.py +0 -0
  102. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/pricing_updater.py +0 -0
  103. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/provision_build.py +0 -0
  104. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/rate_limit.py +0 -0
  105. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/rewards_engine.py +0 -0
  106. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/router.py +0 -0
  107. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/__init__.py +0 -0
  108. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/admin_bonus_routes.py +0 -0
  109. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/admin_db_routes.py +0 -0
  110. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/auth_routes.py +0 -0
  111. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/billing_routes.py +0 -0
  112. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/completion_routes.py +0 -0
  113. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/credit_routes.py +0 -0
  114. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/feedback_routes.py +0 -0
  115. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/marketplace_routes.py +0 -0
  116. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/quota_routes.py +0 -0
  117. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/quota_share_routes.py +0 -0
  118. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/referral_routes.py +0 -0
  119. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/rewards_routes.py +0 -0
  120. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/security_routes.py +0 -0
  121. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/session_routes.py +0 -0
  122. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/share_routes.py +0 -0
  123. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/server.py +0 -0
  124. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/storage_nodes.py +0 -0
  125. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/utils.py +0 -0
  126. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/share.py +0 -0
  127. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/share_tui.py +0 -0
  128. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/situational_scoring.py +0 -0
  129. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/state.py +0 -0
  130. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/subagent_manager.py +0 -0
  131. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/task_dispatcher.py +0 -0
  132. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/task_logger.py +0 -0
  133. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tools.py +0 -0
  134. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tools_grep.py +0 -0
  135. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/__init__.py +0 -0
  136. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/feedback_dialog.py +0 -0
  137. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/history_dialog.py +0 -0
  138. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/marketplace_app_state.py +0 -0
  139. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/marketplace_dialog.py +0 -0
  140. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/marketplace_layout_engine.py +0 -0
  141. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/mcp_dialog.py +0 -0
  142. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/miniagents_dialog.py +0 -0
  143. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/personality_dialog.py +0 -0
  144. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/plugins_dialog.py +0 -0
  145. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/publish_dialog.py +0 -0
  146. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/quota_dialog.py +0 -0
  147. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/quota_redeem_dialog.py +0 -0
  148. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/quota_share_dialog.py +0 -0
  149. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/resume_dialog.py +0 -0
  150. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/rewards_tui.py +0 -0
  151. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/skills_dialog.py +0 -0
  152. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/subagents_dialog.py +0 -0
  153. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/tasks_dialog.py +0 -0
  154. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/thinking_display.py +0 -0
  155. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/tools_dialog.py +0 -0
  156. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/update_dialog.py +0 -0
  157. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/update_checker.py +0 -0
  158. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/utilities.py +0 -0
  159. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/utimmodel.txt +0 -0
  160. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/vector_memory.py +0 -0
  161. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/wheel.py +0 -0
  162. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/workspace.py +0 -0
  163. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli.egg-info/SOURCES.txt +0 -0
  164. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli.egg-info/dependency_links.txt +0 -0
  165. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli.egg-info/entry_points.txt +0 -0
  166. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli.egg-info/requires.txt +0 -0
  167. {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli.egg-info/top_level.txt +0 -0
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.3.19] - 2026-09-02
4
+
5
+ ### 🛠️ Model Dialog Tier Sync & Free Model Preservation
6
+ - Synchronized user plan resolution on account switch to prevent paid models leaking on free accounts
7
+ - Fixed free models disappearing when bonus quota is active
8
+ - Removed default selected model on fresh startup and added configuration prompts
9
+
3
10
  ## [2.3.18] - 2026-09-02
4
11
 
5
12
  ### 🎁 New Free Models Added & Tool Sanitization Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: utim-cli
3
- Version: 2.3.18
3
+ Version: 2.3.19
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.18"
7
+ version = "2.3.19"
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.18"
17
+ __version__ = "2.3.19"
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.18"
8
+ __version__ = "2.3.19"
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.18"
14
+ VERSION = "2.3.19"
15
15
 
16
16
 
@@ -495,8 +495,20 @@ def login(restart: bool = False) -> None:
495
495
  config.set("token", "device_flow")
496
496
  config.set("email", body.get("email", ""))
497
497
  config.set("uid", body.get("user_id", ""))
498
- config.set("firebase_uid", body.get("firebase_uid", ""))
499
498
  config.set("name", body.get("display_name") or (body.get("email", "") or "").split("@")[0])
499
+ plan_name = body.get("plan")
500
+ if not plan_name and body.get("api_key"):
501
+ try:
502
+ _vr = requests.get(
503
+ f"{SERVER_URL}/api/user-plan",
504
+ headers={"X-API-Key": body["api_key"]},
505
+ timeout=3.0,
506
+ )
507
+ if _vr.status_code == 200:
508
+ plan_name = (_vr.json() or {}).get("plan")
509
+ except Exception:
510
+ pass
511
+ config.set("user_plan", (plan_name or "free").lower())
500
512
  if body.get("api_key"):
501
513
  config.set("api_key", body["api_key"])
502
514
  raw_print("")
@@ -181,10 +181,10 @@ def fetch_remote_models_catalog(timeout: float = 3.0) -> dict:
181
181
  params = {}
182
182
  try:
183
183
  from utim_cli.config import config
184
- _plan = (config.get("user_plan") or "all")
185
- params["tier"] = str(_plan).lower().strip()
184
+ _plan = str(config.get("user_plan") or "free").lower().strip()
185
+ params["tier"] = _plan
186
186
  except Exception:
187
- params["tier"] = "all"
187
+ params["tier"] = "free"
188
188
  resp = requests.get(url, params=params, headers=get_auth_headers(), timeout=timeout)
189
189
  if resp.status_code == 200:
190
190
  data = resp.json()
@@ -139,9 +139,9 @@ class Orchestrator(LLMTransportMixin, TurnHistoryMixin, ToolExecutionMixin):
139
139
  pass
140
140
  self.server_url = "https://api.utim.dev"
141
141
  self.session_id: Optional[str] = None
142
- # Primary model — load from config or fall back to DEFAULT_MODEL
142
+ # Primary model — load from config (no default model selected on first startup)
143
143
  saved_main_model = config.get("main_model") or config.get("model")
144
- self.model_id: str = saved_main_model if saved_main_model else DEFAULT_MODEL
144
+ self.model_id: Optional[str] = saved_main_model if saved_main_model else None
145
145
  self._current_line_len = 0
146
146
  self.tool_results: List[Dict[str, Any]] = []
147
147
  self.turn_step_timings: List[Dict[str, Any]] = []
@@ -501,6 +501,11 @@ class Orchestrator(LLMTransportMixin, TurnHistoryMixin, ToolExecutionMixin):
501
501
  self._subagent_tool_override = override_tools
502
502
  max_iterations = 500 # Continuous execution mode with milestone nudges instead of hard pauses
503
503
 
504
+ # If no model is configured for the main agent, prompt the user to select one
505
+ if not self.model_id or not str(self.model_id).strip():
506
+ self.console.print('\nPlease select a model by running "/model Then main agent"\n')
507
+ return
508
+
504
509
  self.turn_step_timings = []
505
510
  self._tool_failure_counts = {}
506
511
  self._tool_name_failure_counts = {}
@@ -10,22 +10,22 @@ _LIVE_OPENROUTER_FREE_CACHE: set = set()
10
10
  _LAST_LIVE_FETCH_TIME = 0.0
11
11
  _LIVE_FETCH_LOCK = threading.Lock() # single-flight: only one thread fetches at a time
12
12
 
13
- _BONUS_CREDITS_CACHE: bool | None = None
14
- _BONUS_CREDITS_TS: float = 0.0
13
+ _USER_TIER_CACHE: tuple[str, bool] | None = None
14
+ _USER_TIER_TS: float = 0.0
15
15
 
16
- def user_has_bonus_credits() -> bool:
17
- """True when the user's account holds an available bonus-credit balance.
18
-
19
- Per business rule: free-plan users with bonus credits must SEE and USE
20
- premium models (billed against the bonus balance) until it is exhausted.
21
- Cached for 90 s to keep dialogs snappy. Never raises.
16
+ def resolve_user_tier_and_bonus(force_refresh: bool = False) -> tuple[str, bool]:
17
+ """Deterministically resolve the active user's plan ('free' vs paid) and bonus eligibility.
18
+ Refreshes config['user_plan'] using the live API key so switching accounts
19
+ immediately synchronizes plan gating.
22
20
  """
23
21
  import time as _time
24
- global _BONUS_CREDITS_CACHE, _BONUS_CREDITS_TS
22
+ global _USER_TIER_CACHE, _USER_TIER_TS
25
23
  now = _time.time()
26
- if _BONUS_CREDITS_CACHE is not None and (now - _BONUS_CREDITS_TS) < 90.0:
27
- return bool(_BONUS_CREDITS_CACHE)
28
- ok = False
24
+ if not force_refresh and _USER_TIER_CACHE is not None and (now - _USER_TIER_TS) < 30.0:
25
+ return _USER_TIER_CACHE
26
+
27
+ plan_name = "free"
28
+ has_bonus = False
29
29
  try:
30
30
  api_key = config.get("api_key")
31
31
  if api_key:
@@ -38,18 +38,46 @@ def user_has_bonus_credits() -> bool:
38
38
  )
39
39
  if resp.status_code == 200:
40
40
  data = resp.json() or {}
41
+ is_subscribed = bool(data.get("is_subscribed", False))
42
+ raw_plan = str(data.get("plan", "free")).lower().strip()
43
+
44
+ bonus_balance = 0.0
41
45
  for _k in ("free_bonus_balance", "bonus_balance", "bonus"):
42
46
  try:
43
- if float(data.get(_k) or 0.0) > 0.0:
44
- ok = True
47
+ val = float(data.get(_k) or 0.0)
48
+ if val > 0.0:
49
+ bonus_balance = val
45
50
  break
46
51
  except (TypeError, ValueError):
47
- continue
52
+ pass
53
+ has_bonus = bonus_balance > 0.0
54
+
55
+ if is_subscribed and raw_plan != "free":
56
+ plan_name = raw_plan
57
+ else:
58
+ plan_name = "free"
59
+
60
+ config.set("user_plan", plan_name)
61
+ _USER_TIER_CACHE = (plan_name, has_bonus)
62
+ _USER_TIER_TS = now
63
+ return _USER_TIER_CACHE
48
64
  except Exception:
49
- ok = False
50
- _BONUS_CREDITS_CACHE = ok
51
- _BONUS_CREDITS_TS = now
52
- return ok
65
+ pass
66
+
67
+ cached = str(config.get("user_plan") or "free").lower().strip()
68
+ _USER_TIER_CACHE = (cached, False)
69
+ _USER_TIER_TS = now
70
+ return _USER_TIER_CACHE
71
+
72
+ def user_has_bonus_credits() -> bool:
73
+ """True when the user's account holds an available bonus-credit balance.
74
+
75
+ Per business rule: free-plan users with bonus credits must SEE and USE
76
+ premium models (billed against the bonus balance) until it is exhausted.
77
+ Cached for snappy response. Never raises.
78
+ """
79
+ _, has_bonus = resolve_user_tier_and_bonus()
80
+ return has_bonus
53
81
 
54
82
  def fetch_active_openrouter_free_models() -> set[str]:
55
83
  """Fetch live free models directly from OpenRouter API to filter out removed/stale models."""
@@ -428,6 +456,9 @@ def _dialog_modelsettings(orchestrator):
428
456
  import copy
429
457
 
430
458
  model_id = orchestrator.model_id
459
+ if not model_id:
460
+ console.print('\n[yellow]Please select a model by running "/model Then main agent"[/yellow]\n')
461
+ return
431
462
 
432
463
  init_model_settings(model_id)
433
464
  initial_settings = config.get(f"model_settings_{model_id}") or {}
@@ -567,7 +598,7 @@ def _dialog_model(orchestrator):
567
598
  from utim_cli.utim import _run_mcp_search_list_dialog
568
599
 
569
600
  while True:
570
- main_model = orchestrator.model_id
601
+ main_model = orchestrator.model_id or "Not Selected"
571
602
 
572
603
  rows = [
573
604
  {"key": "main", "label": "Configure Main Agent Model", "desc": f"Currently: {main_model}"},
@@ -624,8 +655,9 @@ def _dialog_subagents_menu(orchestrator):
624
655
  return "None (main agent writes prompt)"
625
656
  return val or default
626
657
 
658
+ plan_name, has_bonus = resolve_user_tier_and_bonus()
627
659
  has_custom_key = bool(os.getenv("OPENROUTER_API_KEY") or config.get("openrouter_api_key"))
628
- is_paid = is_user_paid() or (str(config.get("user_plan") or "free").lower() != "free") or has_custom_key
660
+ is_paid = (plan_name != "free") or has_bonus or has_custom_key
629
661
 
630
662
  rows = [
631
663
  {"key": "web_search", "label": "Deep Research Agent (web_search)",
@@ -755,8 +787,7 @@ def _dialog_model_main(orchestrator, target="main"):
755
787
  return
756
788
 
757
789
  # ── Define allowed primary and helper models based on target ──────────────
758
- plan_name = str(config.get("user_plan") or "free").lower()
759
- _has_bonus = user_has_bonus_credits()
790
+ plan_name, _has_bonus = resolve_user_tier_and_bonus(force_refresh=True)
760
791
  # Business rule: bonus credits unlock premium model VISIBILITY (and usage,
761
792
  # billed exclusively against the bonus balance) for free-plan users until
762
793
  # the balance is exhausted.
@@ -773,12 +804,18 @@ def _dialog_model_main(orchestrator, target="main"):
773
804
 
774
805
  # ── Fetch model catalog directly from Server DB ─────────────────────────
775
806
  from utim_cli.constants import SERVER_URL
807
+ from utim_cli.auth import get_auth_headers
776
808
  server_catalog: dict = {}
777
809
  db_models_raw: list = []
778
810
 
779
811
  try:
780
812
  import requests as _req
781
- cat_resp = _req.get(f"{SERVER_URL.rstrip('/')}/models/catalog", timeout=3.5)
813
+ cat_resp = _req.get(
814
+ f"{SERVER_URL.rstrip('/')}/models/catalog",
815
+ params={"tier": "all" if is_paid_plan else "free"},
816
+ headers=get_auth_headers(),
817
+ timeout=3.5,
818
+ )
782
819
  if cat_resp.status_code == 200:
783
820
  server_catalog = cat_resp.json() or {}
784
821
  except Exception:
@@ -841,25 +878,32 @@ def _dialog_model_main(orchestrator, target="main"):
841
878
  # is from a local fallback), restrict paid-tier models for free users,
842
879
  # and hide APINEX models from paid users.
843
880
  from utim_cli.constants import APINEX_MODEL_IDS
844
- if not is_paid_plan:
845
- def _is_free_item(item: dict) -> bool:
846
- if not item:
847
- return True
848
- mid = str(item.get("model_id") or item.get("id") or "")
849
- # APINEX models are designated exclusively for free plan users
850
- if str(item.get("provider", "")).lower() == "apinex" or mid in APINEX_MODEL_IDS:
851
- return True
852
- if item.get("is_free"):
853
- return True
854
- if str(item.get("tier", "")).lower() == "free":
855
- return True
856
- if mid.endswith(":free") or mid.startswith("free/"):
857
- return True
858
- return False
859
- _before = len(candidates)
860
- candidates = [c for c in candidates if _is_free_item(c)]
861
- if len(candidates) < _before:
862
- pass # filtered silently; no logger in this module
881
+ is_actual_paid_subscription = (plan_name != "free")
882
+ if not is_actual_paid_subscription:
883
+ # User is on the Free plan:
884
+ if not _has_bonus:
885
+ # No bonus credits: restrict list to free models + APINEX models only
886
+ def _is_free_item(item: dict) -> bool:
887
+ if not item:
888
+ return True
889
+ mid = str(item.get("model_id") or item.get("id") or "")
890
+ # APINEX models are designated exclusively for free plan users
891
+ if str(item.get("provider", "")).lower() == "apinex" or mid in APINEX_MODEL_IDS:
892
+ return True
893
+ if item.get("is_free"):
894
+ return True
895
+ if str(item.get("tier", "")).lower() == "free":
896
+ return True
897
+ if mid.endswith(":free") or mid.startswith("free/"):
898
+ return True
899
+ return False
900
+ _before = len(candidates)
901
+ candidates = [c for c in candidates if _is_free_item(c)]
902
+ if len(candidates) < _before:
903
+ pass # filtered silently; no logger in this module
904
+ else:
905
+ # Free plan with bonus quota: retain ALL free and APINEX models, plus unlocked premium models
906
+ pass
863
907
  else:
864
908
  # Paid plan users: APINEX models only show up for free plan users, hidden on paid plans
865
909
  candidates = [
@@ -903,16 +947,20 @@ def _dialog_model_main(orchestrator, target="main"):
903
947
  if has_text_output:
904
948
  candidates.append(sm)
905
949
 
906
- if not is_paid_plan:
907
- candidates = [
908
- c for c in candidates
909
- if str(c.get("provider", "")).lower() == "apinex"
910
- or str(c.get("model_id") or c.get("id") or "") in APINEX_MODEL_IDS
911
- or c.get("is_free")
912
- or str(c.get("tier", "")).lower() == "free"
913
- or str(c.get("model_id", "")).endswith(":free")
914
- or str(c.get("model_id", "")).startswith("free/")
915
- ]
950
+ is_actual_paid_subscription = (plan_name != "free")
951
+ if not is_actual_paid_subscription:
952
+ if not _has_bonus:
953
+ candidates = [
954
+ c for c in candidates
955
+ if str(c.get("provider", "")).lower() == "apinex"
956
+ or str(c.get("model_id") or c.get("id") or "") in APINEX_MODEL_IDS
957
+ or c.get("is_free")
958
+ or str(c.get("tier", "")).lower() == "free"
959
+ or str(c.get("model_id", "")).endswith(":free")
960
+ or str(c.get("model_id", "")).startswith("free/")
961
+ ]
962
+ else:
963
+ pass
916
964
  else:
917
965
  candidates = [
918
966
  c for c in candidates
@@ -1384,8 +1384,10 @@ def _get_footer_line2_right():
1384
1384
  or STATE.get("model")
1385
1385
  or config.get("main_model")
1386
1386
  or config.get("model")
1387
- or DEFAULT_MODEL
1387
+ or ""
1388
1388
  )
1389
+ if not curr_model:
1390
+ return [('class:ft-cwd', 'No Model Selected ')]
1389
1391
  settings = config.get(f"model_settings_{curr_model}") or {}
1390
1392
  effort = (
1391
1393
  settings.get("reasoning_effort")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: utim-cli
3
- Version: 2.3.18
3
+ Version: 2.3.19
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes