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.
- {utim_cli-2.3.18 → utim_cli-2.3.19}/CHANGELOG.md +7 -0
- {utim_cli-2.3.18/utim_cli.egg-info → utim_cli-2.3.19}/PKG-INFO +1 -1
- {utim_cli-2.3.18 → utim_cli-2.3.19}/pyproject.toml +1 -1
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim/__init__.py +1 -1
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/__init__.py +1 -1
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/_version.py +1 -1
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/auth.py +13 -1
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/client_utils.py +3 -3
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/orchestrator.py +7 -2
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/model_dialog.py +101 -53
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/utim.py +3 -1
- {utim_cli-2.3.18 → utim_cli-2.3.19/utim_cli.egg-info}/PKG-INFO +1 -1
- {utim_cli-2.3.18 → utim_cli-2.3.19}/LICENSE +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/MANIFEST.in +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/README.md +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/setup.cfg +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/setup.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/_verify_repro.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/_verify_syntax.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/_wizard_smoke_test.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/agent.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/ask_helper.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/backup.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/billing.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/blender_agent.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/bootstrap.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/brain.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/builtin/skills/utim-guide/SKILL.md +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/config.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/constants.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/context_pruner.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/crawler/__init__.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/crawler/engine.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/crawler/markdown_distiller.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/dependency_manager.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/doctor.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/evidence_registry.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/harbor.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/knowledge_graph.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/local_db.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/logger.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/mcp_clean_wrapper.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/mcp_client.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/mcp_registry.json +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/models.txt +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/orchestrator_helpers.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/orchestrator_llm_transport.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/orchestrator_tool_exec.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/orchestrator_turn_history.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/personalities.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/platform_installer.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/plugins/__init__.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/plugins/hooks.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/plugins/loader.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/plugins/manager.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/plugins/registry.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/plugins/runtime.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/plugins/schema.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/plugins/tool_loader.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/reflection.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/report.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/scrapy_search.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/sdk/__init__.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/sdk/config.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/sdk/context.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/sdk/events.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/sdk/handlers.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/sdk/session.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/sdk/sidecar.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/__init__.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/admin_auth.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/attribution.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/audit_log.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/auth.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/batch_processor.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/captcha.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/cli_auth.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/concurrency.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/db.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/docs_md/SECRET_PROVISIONING.md +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/docs_md/about.md +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/docs_md/changelog.md +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/docs_md/docs.md +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/docs_md/features.md +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/docs_md/license.md +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/docs_md/pricing.md +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/docs_md/privacy.md +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/docs_md/refund.md +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/docs_md/support.md +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/docs_md/terms.md +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/email_utils.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/exchange_rate.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/firebase.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/fix_duplicate_users.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/history.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/key_rotator.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/local_llm.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/logging_config.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/micro_batcher.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/model_agent.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/models.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/pricing_updater.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/provision_build.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/rate_limit.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/rewards_engine.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/router.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/__init__.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/admin_bonus_routes.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/admin_db_routes.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/auth_routes.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/billing_routes.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/completion_routes.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/credit_routes.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/feedback_routes.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/marketplace_routes.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/quota_routes.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/quota_share_routes.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/referral_routes.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/rewards_routes.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/security_routes.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/session_routes.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/routes/share_routes.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/server.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/storage_nodes.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/server/utils.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/share.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/share_tui.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/situational_scoring.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/state.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/subagent_manager.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/task_dispatcher.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/task_logger.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tools.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tools_grep.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/__init__.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/feedback_dialog.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/history_dialog.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/marketplace_app_state.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/marketplace_dialog.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/marketplace_layout_engine.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/mcp_dialog.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/miniagents_dialog.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/personality_dialog.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/plugins_dialog.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/publish_dialog.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/quota_dialog.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/quota_redeem_dialog.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/quota_share_dialog.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/resume_dialog.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/rewards_tui.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/skills_dialog.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/subagents_dialog.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/tasks_dialog.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/thinking_display.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/tools_dialog.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/tui/update_dialog.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/update_checker.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/utilities.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/utimmodel.txt +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/vector_memory.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/wheel.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli/workspace.py +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli.egg-info/SOURCES.txt +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli.egg-info/dependency_links.txt +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli.egg-info/entry_points.txt +0 -0
- {utim_cli-2.3.18 → utim_cli-2.3.19}/utim_cli.egg-info/requires.txt +0 -0
- {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.
|
|
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
|
|
@@ -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.
|
|
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
|
|
@@ -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 "
|
|
185
|
-
params["tier"] =
|
|
184
|
+
_plan = str(config.get("user_plan") or "free").lower().strip()
|
|
185
|
+
params["tier"] = _plan
|
|
186
186
|
except Exception:
|
|
187
|
-
params["tier"] = "
|
|
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
|
|
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
|
|
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
|
-
|
|
14
|
-
|
|
13
|
+
_USER_TIER_CACHE: tuple[str, bool] | None = None
|
|
14
|
+
_USER_TIER_TS: float = 0.0
|
|
15
15
|
|
|
16
|
-
def
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
22
|
+
global _USER_TIER_CACHE, _USER_TIER_TS
|
|
25
23
|
now = _time.time()
|
|
26
|
-
if
|
|
27
|
-
return
|
|
28
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
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
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|