utim-cli 2.3.17__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.17 → utim_cli-2.3.19}/CHANGELOG.md +13 -0
- {utim_cli-2.3.17/utim_cli.egg-info → utim_cli-2.3.19}/PKG-INFO +1 -1
- {utim_cli-2.3.17 → utim_cli-2.3.19}/pyproject.toml +1 -1
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim/__init__.py +1 -1
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/__init__.py +1 -1
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/_version.py +1 -1
- utim_cli-2.3.19/utim_cli/_wizard_smoke_test.py +93 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/auth.py +13 -1
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/bootstrap.py +48 -3
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/client_utils.py +79 -3
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/constants.py +21 -1
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/mcp_client.py +12 -1
- utim_cli-2.3.19/utim_cli/models.txt +38350 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/orchestrator.py +18 -15
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/orchestrator_helpers.py +113 -2
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/orchestrator_llm_transport.py +214 -44
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/orchestrator_tool_exec.py +9 -4
- utim_cli-2.3.19/utim_cli/plugins/loader.py +177 -0
- utim_cli-2.3.19/utim_cli/plugins/manager.py +558 -0
- utim_cli-2.3.19/utim_cli/plugins/runtime.py +175 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/plugins/schema.py +33 -6
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/reflection.py +10 -14
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/key_rotator.py +576 -19
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/models.py +286 -20
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/router.py +255 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/routes/completion_routes.py +121 -49
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/routes/quota_routes.py +44 -10
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/utils.py +95 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/tools.py +164 -4
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/tui/model_dialog.py +119 -41
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/tui/plugins_dialog.py +319 -219
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/update_checker.py +46 -6
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/utim.py +161 -6
- utim_cli-2.3.19/utim_cli/utimmodel.txt +133 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19/utim_cli.egg-info}/PKG-INFO +1 -1
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli.egg-info/SOURCES.txt +5 -0
- utim_cli-2.3.17/utim_cli/plugins/manager.py +0 -629
- {utim_cli-2.3.17 → utim_cli-2.3.19}/LICENSE +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/MANIFEST.in +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/README.md +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/setup.cfg +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/setup.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/_verify_repro.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/_verify_syntax.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/agent.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/ask_helper.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/backup.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/billing.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/blender_agent.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/brain.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/builtin/skills/utim-guide/SKILL.md +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/config.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/context_pruner.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/crawler/__init__.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/crawler/engine.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/crawler/markdown_distiller.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/dependency_manager.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/doctor.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/evidence_registry.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/harbor.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/knowledge_graph.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/local_db.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/logger.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/mcp_clean_wrapper.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/mcp_registry.json +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/orchestrator_turn_history.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/personalities.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/platform_installer.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/plugins/__init__.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/plugins/hooks.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/plugins/registry.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/plugins/tool_loader.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/report.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/scrapy_search.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/sdk/__init__.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/sdk/config.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/sdk/context.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/sdk/events.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/sdk/handlers.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/sdk/session.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/sdk/sidecar.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/__init__.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/admin_auth.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/attribution.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/audit_log.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/auth.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/batch_processor.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/captcha.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/cli_auth.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/concurrency.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/db.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/docs_md/SECRET_PROVISIONING.md +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/docs_md/about.md +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/docs_md/changelog.md +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/docs_md/docs.md +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/docs_md/features.md +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/docs_md/license.md +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/docs_md/pricing.md +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/docs_md/privacy.md +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/docs_md/refund.md +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/docs_md/support.md +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/docs_md/terms.md +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/email_utils.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/exchange_rate.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/firebase.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/fix_duplicate_users.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/history.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/local_llm.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/logging_config.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/micro_batcher.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/model_agent.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/pricing_updater.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/provision_build.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/rate_limit.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/rewards_engine.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/routes/__init__.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/routes/admin_bonus_routes.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/routes/admin_db_routes.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/routes/auth_routes.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/routes/billing_routes.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/routes/credit_routes.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/routes/feedback_routes.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/routes/marketplace_routes.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/routes/quota_share_routes.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/routes/referral_routes.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/routes/rewards_routes.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/routes/security_routes.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/routes/session_routes.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/routes/share_routes.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/server.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/server/storage_nodes.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/share.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/share_tui.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/situational_scoring.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/state.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/subagent_manager.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/task_dispatcher.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/task_logger.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/tools_grep.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/tui/__init__.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/tui/feedback_dialog.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/tui/history_dialog.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/tui/marketplace_app_state.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/tui/marketplace_dialog.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/tui/marketplace_layout_engine.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/tui/mcp_dialog.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/tui/miniagents_dialog.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/tui/personality_dialog.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/tui/publish_dialog.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/tui/quota_dialog.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/tui/quota_redeem_dialog.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/tui/quota_share_dialog.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/tui/resume_dialog.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/tui/rewards_tui.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/tui/skills_dialog.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/tui/subagents_dialog.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/tui/tasks_dialog.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/tui/thinking_display.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/tui/tools_dialog.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/tui/update_dialog.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/utilities.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/vector_memory.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/wheel.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli/workspace.py +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli.egg-info/dependency_links.txt +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli.egg-info/entry_points.txt +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli.egg-info/requires.txt +0 -0
- {utim_cli-2.3.17 → utim_cli-2.3.19}/utim_cli.egg-info/top_level.txt +0 -0
|
@@ -1,5 +1,18 @@
|
|
|
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
|
+
|
|
10
|
+
## [2.3.18] - 2026-09-02
|
|
11
|
+
|
|
12
|
+
### 🎁 New Free Models Added & Tool Sanitization Fixes
|
|
13
|
+
- Added new free models with key rotation
|
|
14
|
+
- Fixed Google Gemini tool schema sanitization to prevent 400 Bad Request errors
|
|
15
|
+
|
|
3
16
|
## [2.3.17] - 2026-08-31
|
|
4
17
|
|
|
5
18
|
### ✨ Premium Model Visibility for Bonus-Credit Users
|
|
@@ -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
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Smoke test for the new plugin scaffold wizard.
|
|
2
|
+
|
|
3
|
+
Simulates a user filling out the wizard end-to-end and verifies that the
|
|
4
|
+
generated plugin directory + plugin.json contain the basic fields.
|
|
5
|
+
"""
|
|
6
|
+
import os, sys, json, shutil, tempfile
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from unittest.mock import patch
|
|
9
|
+
|
|
10
|
+
# Set up an isolated UTIM home before importing the dialog
|
|
11
|
+
TEST_HOME = Path(tempfile.mkdtemp(prefix="utim_wiz_test_"))
|
|
12
|
+
os.environ["UTIM_HOME"] = str(TEST_HOME)
|
|
13
|
+
os.environ["XDG_CONFIG_HOME"] = str(TEST_HOME)
|
|
14
|
+
os.environ["XDG_DATA_HOME"] = str(TEST_HOME)
|
|
15
|
+
|
|
16
|
+
sys.path.insert(0, ".")
|
|
17
|
+
from utim_cli.plugins.manager import PluginManager
|
|
18
|
+
from utim_cli.tui.plugins_dialog import _run_plugin_scaffold_wizard
|
|
19
|
+
|
|
20
|
+
mgr = PluginManager()
|
|
21
|
+
# Redirect workspace plugins dir via monkey-patch (property is read-only)
|
|
22
|
+
import utim_cli.plugins.manager as mgr_mod
|
|
23
|
+
real_prop = mgr_mod.PluginManager.workspace_plugins_dir
|
|
24
|
+
fake_workspace_plugins = TEST_HOME / "plugins"
|
|
25
|
+
fake_workspace_plugins.mkdir(parents=True, exist_ok=True)
|
|
26
|
+
def _fake_workspace_dir(self):
|
|
27
|
+
return fake_workspace_plugins
|
|
28
|
+
mgr_mod.PluginManager.workspace_plugins_dir = property(_fake_workspace_dir)
|
|
29
|
+
|
|
30
|
+
# Sequence of inputs the user will type — matches the current 3-step wizard:
|
|
31
|
+
# Step 1: Plugin name
|
|
32
|
+
# Step 2: Description
|
|
33
|
+
# Step 3: Choice (1-4), scope (y/n), confirm (y/n), enable (y/n)
|
|
34
|
+
inputs = iter([
|
|
35
|
+
# Step 1 — Plugin name
|
|
36
|
+
"my-cloud-tools",
|
|
37
|
+
# Step 2 — Description
|
|
38
|
+
"Awesome cloud dev tools",
|
|
39
|
+
# Step 3 — Choice 1 (Skill module)
|
|
40
|
+
"1",
|
|
41
|
+
# Scope: workspace (n = not global)
|
|
42
|
+
"n",
|
|
43
|
+
# Generate now?
|
|
44
|
+
"y",
|
|
45
|
+
# Enable now?
|
|
46
|
+
"y",
|
|
47
|
+
])
|
|
48
|
+
|
|
49
|
+
# Use prompt_toolkit's print_formatted_text + fake_prompter via pt_prompt monkeypatch
|
|
50
|
+
from prompt_toolkit import prompt as pt_prompt
|
|
51
|
+
original = pt_prompt
|
|
52
|
+
def fake_prompt(message="", **kw):
|
|
53
|
+
try:
|
|
54
|
+
v = next(inputs)
|
|
55
|
+
except StopIteration:
|
|
56
|
+
raise EOFError
|
|
57
|
+
return v
|
|
58
|
+
|
|
59
|
+
# Patch the import inside the dialog module
|
|
60
|
+
import utim_cli.tui.plugins_dialog as pd
|
|
61
|
+
with patch.object(pd, "pt_prompt", side_effect=fake_prompt):
|
|
62
|
+
# Also patch the bare `from prompt_toolkit import prompt` reference if any
|
|
63
|
+
try:
|
|
64
|
+
pd._run_plugin_scaffold_wizard(mgr)
|
|
65
|
+
except (StopIteration, EOFError):
|
|
66
|
+
print("WIZARD: ran out of input (likely fine if it finished early)")
|
|
67
|
+
|
|
68
|
+
# Verify the generated plugin
|
|
69
|
+
plugin_dir = mgr.workspace_plugins_dir / "my-cloud-tools"
|
|
70
|
+
print("=" * 60)
|
|
71
|
+
print(f"Plugin dir exists: {plugin_dir.exists()}")
|
|
72
|
+
if plugin_dir.exists():
|
|
73
|
+
print("Files:")
|
|
74
|
+
for p in sorted(plugin_dir.rglob("*")):
|
|
75
|
+
if p.is_file():
|
|
76
|
+
print(f" {p.relative_to(plugin_dir)}")
|
|
77
|
+
print("=" * 60)
|
|
78
|
+
manifest = json.loads((plugin_dir / "plugin.json").read_text(encoding="utf-8"))
|
|
79
|
+
print("plugin.json manifest:")
|
|
80
|
+
print(json.dumps(manifest, indent=2))
|
|
81
|
+
print("=" * 60)
|
|
82
|
+
|
|
83
|
+
# Assertions
|
|
84
|
+
assert manifest["name"] == "my-cloud-tools"
|
|
85
|
+
assert manifest["description"] == "Awesome cloud dev tools"
|
|
86
|
+
assert manifest["author"] == "Developer"
|
|
87
|
+
assert manifest["license"] == "MIT"
|
|
88
|
+
assert len(manifest["skills"]) == 1
|
|
89
|
+
assert manifest["skills"][0] == "skills/my-cloud-tools-workflow/SKILL.md"
|
|
90
|
+
print("✅ ALL ASSERTIONS PASSED")
|
|
91
|
+
else:
|
|
92
|
+
print("❌ Plugin directory was not created")
|
|
93
|
+
sys.exit(1)
|
|
@@ -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("")
|
|
@@ -292,9 +292,54 @@ def scan_available_skills(force_reload: bool = False):
|
|
|
292
292
|
try:
|
|
293
293
|
from utim_cli.plugins.manager import get_plugin_manager
|
|
294
294
|
p_mgr = get_plugin_manager()
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
295
|
+
active_skills_dict = p_mgr.get_active_skills()
|
|
296
|
+
p_items = active_skills_dict.items() if isinstance(active_skills_dict, dict) else [(s.get("name", s.get("raw_name")), s) for s in active_skills_dict]
|
|
297
|
+
for p_skill_name, p_skill_data in p_items:
|
|
298
|
+
if p_skill_name not in disabled:
|
|
299
|
+
s_path = p_skill_data.get("path")
|
|
300
|
+
s_content = p_skill_data.get("content", "")
|
|
301
|
+
s_desc = p_skill_data.get("description", "")
|
|
302
|
+
s_raw_name = p_skill_data.get("raw_name", p_skill_name)
|
|
303
|
+
|
|
304
|
+
if not s_desc and s_content and s_content.startswith("---"):
|
|
305
|
+
parts = s_content.split("---", 2)
|
|
306
|
+
if len(parts) >= 3:
|
|
307
|
+
dm = re.search(r"^description:\s*(.+)$", parts[1], re.MULTILINE)
|
|
308
|
+
if dm:
|
|
309
|
+
s_desc = dm.group(1).strip().strip("\"'")
|
|
310
|
+
|
|
311
|
+
words = set()
|
|
312
|
+
for word in p_skill_name.replace('-', ' ').replace('_', ' ').replace(':', ' ').split():
|
|
313
|
+
if len(word) > 2:
|
|
314
|
+
words.add(word.lower())
|
|
315
|
+
for word in s_raw_name.replace('-', ' ').replace('_', ' ').split():
|
|
316
|
+
if len(word) > 2:
|
|
317
|
+
words.add(word.lower())
|
|
318
|
+
for word in s_desc.replace(',', ' ').replace('.', ' ').replace('(', ' ').replace(')', ' ').split():
|
|
319
|
+
w = word.lower().strip()
|
|
320
|
+
if len(w) > 2 and w not in stop_words:
|
|
321
|
+
words.add(w)
|
|
322
|
+
|
|
323
|
+
size_bytes = 0
|
|
324
|
+
try:
|
|
325
|
+
if hasattr(s_path, "stat"):
|
|
326
|
+
size_bytes = s_path.stat().st_size
|
|
327
|
+
except Exception:
|
|
328
|
+
size_bytes = len(s_content.encode("utf-8")) if s_content else 0
|
|
329
|
+
|
|
330
|
+
skill_entry = {
|
|
331
|
+
"path": s_path,
|
|
332
|
+
"name": p_skill_name,
|
|
333
|
+
"folder_name": s_raw_name,
|
|
334
|
+
"frontmatter_name": s_raw_name,
|
|
335
|
+
"description": s_desc,
|
|
336
|
+
"keywords": list(words),
|
|
337
|
+
"content": s_content,
|
|
338
|
+
"size_bytes": size_bytes,
|
|
339
|
+
}
|
|
340
|
+
skills[p_skill_name] = skill_entry
|
|
341
|
+
if s_raw_name not in skills and s_raw_name not in disabled:
|
|
342
|
+
skills[s_raw_name] = skill_entry
|
|
298
343
|
except Exception:
|
|
299
344
|
pass
|
|
300
345
|
|
|
@@ -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()
|
|
@@ -572,6 +572,82 @@ def proxy_openrouter_request(json_data: dict, stream: bool = False, timeout=None
|
|
|
572
572
|
return requests.post("https://openrouter.ai/api/v1/chat/completions", json=json_data, headers=headers, stream=stream or json_data.get("stream"), timeout=timeout, verify=config.verify_ssl)
|
|
573
573
|
|
|
574
574
|
|
|
575
|
+
def sanitize_tools_for_api(tools: Optional[List[Dict[str, Any]]], model_id: Optional[str] = None) -> Optional[List[Dict[str, Any]]]:
|
|
576
|
+
"""Sanitize and validate JSON schemas in tool declarations.
|
|
577
|
+
|
|
578
|
+
Removes unsupported JSON schema keywords (like $schema, additionalProperties,
|
|
579
|
+
propertyNames, patternProperties, definitions), ensures arrays have valid 'items' schemas,
|
|
580
|
+
and converts uppercase / invalid type declarations that cause Google Gemini / GLM / OpenAPI
|
|
581
|
+
validators to reject payloads with 400 'Invalid JSON payload' errors.
|
|
582
|
+
"""
|
|
583
|
+
if not tools:
|
|
584
|
+
return tools
|
|
585
|
+
|
|
586
|
+
TYPE_MAP = {
|
|
587
|
+
"STRING": "string",
|
|
588
|
+
"NUMBER": "number",
|
|
589
|
+
"INTEGER": "integer",
|
|
590
|
+
"BOOLEAN": "boolean",
|
|
591
|
+
"ARRAY": "array",
|
|
592
|
+
"OBJECT": "object",
|
|
593
|
+
"NULL": "null"
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
def _clean_schema_node(node):
|
|
597
|
+
if not isinstance(node, dict):
|
|
598
|
+
return node
|
|
599
|
+
cleaned = {}
|
|
600
|
+
disallowed_keys = {
|
|
601
|
+
"$schema", "$id", "definitions", "$defs", "propertyNames",
|
|
602
|
+
"patternProperties", "additionalProperties", "unevaluatedProperties",
|
|
603
|
+
}
|
|
604
|
+
for k, v in node.items():
|
|
605
|
+
if k in disallowed_keys:
|
|
606
|
+
continue
|
|
607
|
+
|
|
608
|
+
# Standardize JSON schema type names to lowercase
|
|
609
|
+
if k == "type":
|
|
610
|
+
if isinstance(v, str):
|
|
611
|
+
v = TYPE_MAP.get(v, v.lower())
|
|
612
|
+
elif isinstance(v, list):
|
|
613
|
+
v = [TYPE_MAP.get(item, item.lower()) if isinstance(item, str) else item for item in v]
|
|
614
|
+
|
|
615
|
+
if isinstance(v, dict):
|
|
616
|
+
cleaned[k] = _clean_schema_node(v)
|
|
617
|
+
elif isinstance(v, list):
|
|
618
|
+
cleaned[k] = [_clean_schema_node(item) if isinstance(item, dict) else item for item in v]
|
|
619
|
+
else:
|
|
620
|
+
cleaned[k] = v
|
|
621
|
+
|
|
622
|
+
# Strict validator fix: If type is 'array' and 'items' is missing or empty dict, default items to string or object
|
|
623
|
+
if cleaned.get("type") == "array":
|
|
624
|
+
items = cleaned.get("items")
|
|
625
|
+
if not items or items == {}:
|
|
626
|
+
cleaned["items"] = {"type": "string"}
|
|
627
|
+
elif isinstance(items, dict):
|
|
628
|
+
cleaned["items"] = _clean_schema_node(items)
|
|
629
|
+
|
|
630
|
+
# Strict validator fix: If type is 'object', ensure properties is a dict if present
|
|
631
|
+
if cleaned.get("type") == "object":
|
|
632
|
+
if "properties" in cleaned and not isinstance(cleaned["properties"], dict):
|
|
633
|
+
cleaned["properties"] = {}
|
|
634
|
+
|
|
635
|
+
return cleaned
|
|
636
|
+
|
|
637
|
+
sanitized_tools = []
|
|
638
|
+
for tool in tools:
|
|
639
|
+
if not isinstance(tool, dict):
|
|
640
|
+
continue
|
|
641
|
+
tool_copy = dict(tool)
|
|
642
|
+
if "function" in tool_copy and isinstance(tool_copy["function"], dict):
|
|
643
|
+
fn_copy = dict(tool_copy["function"])
|
|
644
|
+
if "parameters" in fn_copy and isinstance(fn_copy["parameters"], dict):
|
|
645
|
+
fn_copy["parameters"] = _clean_schema_node(fn_copy["parameters"])
|
|
646
|
+
tool_copy["function"] = fn_copy
|
|
647
|
+
sanitized_tools.append(tool_copy)
|
|
648
|
+
return sanitized_tools
|
|
649
|
+
|
|
650
|
+
|
|
575
651
|
def sanitize_messages_for_api(messages, model_id: Optional[str] = None):
|
|
576
652
|
"""Normalize conversation history into a provider-safe shape before sending.
|
|
577
653
|
|
|
@@ -11,7 +11,26 @@ SUBAGENT_DEFAULTS = {
|
|
|
11
11
|
"analyze_image": "google/gemma-4-31b-it:free",
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
APINEX_MODEL_IDS = {
|
|
15
|
+
"claude/opus-5",
|
|
16
|
+
"claude/sonnet-5",
|
|
17
|
+
"deepseek/v4-flash-0731",
|
|
18
|
+
"deepseek/v4-pro-0813",
|
|
19
|
+
"free/deepseek-v4-flash-0731",
|
|
20
|
+
"free/deepseek-v4-pro-0813",
|
|
21
|
+
"free/glm-5.3-flash",
|
|
22
|
+
"free/gemini-3.1-pro",
|
|
23
|
+
"free/gemini-3.7-flash",
|
|
24
|
+
"gemini/3.1-pro",
|
|
25
|
+
"gemini/3.7-flash",
|
|
26
|
+
"kimi/k3",
|
|
27
|
+
"gpt/5.6-luna",
|
|
28
|
+
"gpt/5.6-sol",
|
|
29
|
+
"gpt/5.6-terra",
|
|
30
|
+
"glm/5.3",
|
|
31
|
+
"glm/5.3-flash",
|
|
32
|
+
"grok/4.6",
|
|
33
|
+
}
|
|
15
34
|
|
|
16
35
|
|
|
17
36
|
import sys
|
|
@@ -19,3 +38,4 @@ _IS_LEGACY_WIN = (sys.platform == "win32" and "WT_SESSION" not in os.environ)
|
|
|
19
38
|
PROMPT_SYMBOL = ">" if _IS_LEGACY_WIN else "❯"
|
|
20
39
|
ARROW_SYMBOL = "->" if _IS_LEGACY_WIN else "➔"
|
|
21
40
|
|
|
41
|
+
|
|
@@ -213,7 +213,18 @@ class MCPManager:
|
|
|
213
213
|
servers = config.get("mcpServers") or {}
|
|
214
214
|
if not isinstance(servers, dict):
|
|
215
215
|
servers = {}
|
|
216
|
-
|
|
216
|
+
|
|
217
|
+
# Merge active MCP servers declared by enabled plugins
|
|
218
|
+
try:
|
|
219
|
+
from utim_cli.plugins.manager import get_plugin_manager
|
|
220
|
+
plugin_mcp = get_plugin_manager().get_active_mcp_servers()
|
|
221
|
+
if isinstance(plugin_mcp, dict):
|
|
222
|
+
for srv_name, srv_conf in plugin_mcp.items():
|
|
223
|
+
if srv_name not in servers:
|
|
224
|
+
servers[srv_name] = srv_conf
|
|
225
|
+
except Exception:
|
|
226
|
+
pass
|
|
227
|
+
|
|
217
228
|
for name, srv_config in servers.items():
|
|
218
229
|
command = srv_config.get("command")
|
|
219
230
|
args = srv_config.get("args", [])
|