utim-cli 2.3.17__tar.gz → 2.3.18__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 (168) hide show
  1. {utim_cli-2.3.17 → utim_cli-2.3.18}/CHANGELOG.md +6 -0
  2. {utim_cli-2.3.17/utim_cli.egg-info → utim_cli-2.3.18}/PKG-INFO +1 -1
  3. {utim_cli-2.3.17 → utim_cli-2.3.18}/pyproject.toml +1 -1
  4. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim/__init__.py +1 -1
  5. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/__init__.py +1 -1
  6. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/_version.py +1 -1
  7. utim_cli-2.3.18/utim_cli/_wizard_smoke_test.py +93 -0
  8. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/bootstrap.py +48 -3
  9. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/client_utils.py +76 -0
  10. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/constants.py +21 -1
  11. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/mcp_client.py +12 -1
  12. utim_cli-2.3.18/utim_cli/models.txt +38350 -0
  13. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/orchestrator.py +11 -13
  14. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/orchestrator_helpers.py +113 -2
  15. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/orchestrator_llm_transport.py +214 -44
  16. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/orchestrator_tool_exec.py +9 -4
  17. utim_cli-2.3.18/utim_cli/plugins/loader.py +177 -0
  18. utim_cli-2.3.18/utim_cli/plugins/manager.py +558 -0
  19. utim_cli-2.3.18/utim_cli/plugins/runtime.py +175 -0
  20. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/plugins/schema.py +33 -6
  21. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/reflection.py +10 -14
  22. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/key_rotator.py +576 -19
  23. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/models.py +286 -20
  24. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/router.py +255 -0
  25. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/routes/completion_routes.py +121 -49
  26. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/routes/quota_routes.py +44 -10
  27. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/utils.py +95 -0
  28. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/tools.py +164 -4
  29. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/tui/model_dialog.py +33 -3
  30. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/tui/plugins_dialog.py +319 -219
  31. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/update_checker.py +46 -6
  32. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/utim.py +158 -5
  33. utim_cli-2.3.18/utim_cli/utimmodel.txt +133 -0
  34. {utim_cli-2.3.17 → utim_cli-2.3.18/utim_cli.egg-info}/PKG-INFO +1 -1
  35. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli.egg-info/SOURCES.txt +5 -0
  36. utim_cli-2.3.17/utim_cli/plugins/manager.py +0 -629
  37. {utim_cli-2.3.17 → utim_cli-2.3.18}/LICENSE +0 -0
  38. {utim_cli-2.3.17 → utim_cli-2.3.18}/MANIFEST.in +0 -0
  39. {utim_cli-2.3.17 → utim_cli-2.3.18}/README.md +0 -0
  40. {utim_cli-2.3.17 → utim_cli-2.3.18}/setup.cfg +0 -0
  41. {utim_cli-2.3.17 → utim_cli-2.3.18}/setup.py +0 -0
  42. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/_verify_repro.py +0 -0
  43. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/_verify_syntax.py +0 -0
  44. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/agent.py +0 -0
  45. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/ask_helper.py +0 -0
  46. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/auth.py +0 -0
  47. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/backup.py +0 -0
  48. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/billing.py +0 -0
  49. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/blender_agent.py +0 -0
  50. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/brain.py +0 -0
  51. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/builtin/skills/utim-guide/SKILL.md +0 -0
  52. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/config.py +0 -0
  53. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/context_pruner.py +0 -0
  54. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/crawler/__init__.py +0 -0
  55. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/crawler/engine.py +0 -0
  56. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/crawler/markdown_distiller.py +0 -0
  57. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/dependency_manager.py +0 -0
  58. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/doctor.py +0 -0
  59. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/evidence_registry.py +0 -0
  60. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/harbor.py +0 -0
  61. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/knowledge_graph.py +0 -0
  62. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/local_db.py +0 -0
  63. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/logger.py +0 -0
  64. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/mcp_clean_wrapper.py +0 -0
  65. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/mcp_registry.json +0 -0
  66. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/orchestrator_turn_history.py +0 -0
  67. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/personalities.py +0 -0
  68. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/platform_installer.py +0 -0
  69. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/plugins/__init__.py +0 -0
  70. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/plugins/hooks.py +0 -0
  71. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/plugins/registry.py +0 -0
  72. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/plugins/tool_loader.py +0 -0
  73. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/report.py +0 -0
  74. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/scrapy_search.py +0 -0
  75. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/sdk/__init__.py +0 -0
  76. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/sdk/config.py +0 -0
  77. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/sdk/context.py +0 -0
  78. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/sdk/events.py +0 -0
  79. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/sdk/handlers.py +0 -0
  80. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/sdk/session.py +0 -0
  81. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/sdk/sidecar.py +0 -0
  82. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/__init__.py +0 -0
  83. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/admin_auth.py +0 -0
  84. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/attribution.py +0 -0
  85. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/audit_log.py +0 -0
  86. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/auth.py +0 -0
  87. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/batch_processor.py +0 -0
  88. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/captcha.py +0 -0
  89. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/cli_auth.py +0 -0
  90. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/concurrency.py +0 -0
  91. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/db.py +0 -0
  92. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/docs_md/SECRET_PROVISIONING.md +0 -0
  93. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/docs_md/about.md +0 -0
  94. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/docs_md/changelog.md +0 -0
  95. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/docs_md/docs.md +0 -0
  96. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/docs_md/features.md +0 -0
  97. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/docs_md/license.md +0 -0
  98. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/docs_md/pricing.md +0 -0
  99. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/docs_md/privacy.md +0 -0
  100. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/docs_md/refund.md +0 -0
  101. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/docs_md/support.md +0 -0
  102. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/docs_md/terms.md +0 -0
  103. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/email_utils.py +0 -0
  104. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/exchange_rate.py +0 -0
  105. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/firebase.py +0 -0
  106. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/fix_duplicate_users.py +0 -0
  107. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/history.py +0 -0
  108. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/local_llm.py +0 -0
  109. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/logging_config.py +0 -0
  110. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/micro_batcher.py +0 -0
  111. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/model_agent.py +0 -0
  112. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/pricing_updater.py +0 -0
  113. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/provision_build.py +0 -0
  114. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/rate_limit.py +0 -0
  115. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/rewards_engine.py +0 -0
  116. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/routes/__init__.py +0 -0
  117. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/routes/admin_bonus_routes.py +0 -0
  118. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/routes/admin_db_routes.py +0 -0
  119. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/routes/auth_routes.py +0 -0
  120. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/routes/billing_routes.py +0 -0
  121. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/routes/credit_routes.py +0 -0
  122. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/routes/feedback_routes.py +0 -0
  123. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/routes/marketplace_routes.py +0 -0
  124. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/routes/quota_share_routes.py +0 -0
  125. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/routes/referral_routes.py +0 -0
  126. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/routes/rewards_routes.py +0 -0
  127. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/routes/security_routes.py +0 -0
  128. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/routes/session_routes.py +0 -0
  129. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/routes/share_routes.py +0 -0
  130. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/server.py +0 -0
  131. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/server/storage_nodes.py +0 -0
  132. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/share.py +0 -0
  133. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/share_tui.py +0 -0
  134. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/situational_scoring.py +0 -0
  135. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/state.py +0 -0
  136. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/subagent_manager.py +0 -0
  137. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/task_dispatcher.py +0 -0
  138. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/task_logger.py +0 -0
  139. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/tools_grep.py +0 -0
  140. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/tui/__init__.py +0 -0
  141. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/tui/feedback_dialog.py +0 -0
  142. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/tui/history_dialog.py +0 -0
  143. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/tui/marketplace_app_state.py +0 -0
  144. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/tui/marketplace_dialog.py +0 -0
  145. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/tui/marketplace_layout_engine.py +0 -0
  146. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/tui/mcp_dialog.py +0 -0
  147. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/tui/miniagents_dialog.py +0 -0
  148. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/tui/personality_dialog.py +0 -0
  149. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/tui/publish_dialog.py +0 -0
  150. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/tui/quota_dialog.py +0 -0
  151. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/tui/quota_redeem_dialog.py +0 -0
  152. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/tui/quota_share_dialog.py +0 -0
  153. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/tui/resume_dialog.py +0 -0
  154. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/tui/rewards_tui.py +0 -0
  155. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/tui/skills_dialog.py +0 -0
  156. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/tui/subagents_dialog.py +0 -0
  157. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/tui/tasks_dialog.py +0 -0
  158. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/tui/thinking_display.py +0 -0
  159. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/tui/tools_dialog.py +0 -0
  160. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/tui/update_dialog.py +0 -0
  161. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/utilities.py +0 -0
  162. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/vector_memory.py +0 -0
  163. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/wheel.py +0 -0
  164. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli/workspace.py +0 -0
  165. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli.egg-info/dependency_links.txt +0 -0
  166. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli.egg-info/entry_points.txt +0 -0
  167. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli.egg-info/requires.txt +0 -0
  168. {utim_cli-2.3.17 → utim_cli-2.3.18}/utim_cli.egg-info/top_level.txt +0 -0
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.3.18] - 2026-09-02
4
+
5
+ ### 🎁 New Free Models Added & Tool Sanitization Fixes
6
+ - Added new free models with key rotation
7
+ - Fixed Google Gemini tool schema sanitization to prevent 400 Bad Request errors
8
+
3
9
  ## [2.3.17] - 2026-08-31
4
10
 
5
11
  ### ✨ 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.17
3
+ Version: 2.3.18
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.17"
7
+ version = "2.3.18"
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.17"
17
+ __version__ = "2.3.18"
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.17"
8
+ __version__ = "2.3.18"
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.17"
14
+ VERSION = "2.3.18"
15
15
 
16
16
 
@@ -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)
@@ -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
- for p_skill_name, p_skill_data in p_mgr.get_active_skills().items():
296
- if p_skill_name not in skills and p_skill_name not in disabled:
297
- skills[p_skill_name] = p_skill_data
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
 
@@ -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", [])