utim-cli 2.3.14__tar.gz → 2.3.16__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 (166) hide show
  1. {utim_cli-2.3.14 → utim_cli-2.3.16}/CHANGELOG.md +10 -0
  2. {utim_cli-2.3.14/utim_cli.egg-info → utim_cli-2.3.16}/PKG-INFO +1 -1
  3. {utim_cli-2.3.14 → utim_cli-2.3.16}/pyproject.toml +2 -2
  4. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim/__init__.py +1 -1
  5. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/__init__.py +1 -1
  6. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/_version.py +1 -1
  7. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/agent.py +7 -0
  8. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/bootstrap.py +47 -1
  9. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/brain.py +1 -1
  10. utim_cli-2.3.16/utim_cli/builtin/skills/utim-guide/SKILL.md +37 -0
  11. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/client_utils.py +164 -6
  12. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/config.py +33 -27
  13. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/context_pruner.py +101 -27
  14. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/crawler/markdown_distiller.py +258 -251
  15. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/orchestrator.py +2 -0
  16. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/orchestrator_helpers.py +3 -0
  17. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/orchestrator_llm_transport.py +59 -24
  18. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/orchestrator_tool_exec.py +24 -28
  19. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/orchestrator_turn_history.py +40 -0
  20. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/plugins/manager.py +135 -43
  21. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/reflection.py +2 -7
  22. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/auth.py +51 -5
  23. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/cli_auth.py +6 -1
  24. utim_cli-2.3.16/utim_cli/server/email_utils.py +917 -0
  25. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/history.py +6 -2
  26. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/key_rotator.py +1055 -710
  27. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/models.py +396 -95
  28. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/pricing_updater.py +3 -3
  29. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/router.py +7 -2
  30. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/routes/__init__.py +21 -20
  31. utim_cli-2.3.16/utim_cli/server/routes/admin_bonus_routes.py +184 -0
  32. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/routes/completion_routes.py +101 -37
  33. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/routes/referral_routes.py +308 -308
  34. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/routes/security_routes.py +7 -4
  35. utim_cli-2.3.16/utim_cli/server/utils.py +283 -0
  36. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/tools.py +95 -43
  37. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/tui/feedback_dialog.py +87 -14
  38. utim_cli-2.3.16/utim_cli/tui/plugins_dialog.py +756 -0
  39. utim_cli-2.3.16/utim_cli/update_checker.py +545 -0
  40. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/utim.py +156 -34
  41. {utim_cli-2.3.14 → utim_cli-2.3.16/utim_cli.egg-info}/PKG-INFO +1 -1
  42. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli.egg-info/SOURCES.txt +4 -0
  43. utim_cli-2.3.14/utim_cli/server/email_utils.py +0 -691
  44. utim_cli-2.3.14/utim_cli/tui/plugins_dialog.py +0 -351
  45. {utim_cli-2.3.14 → utim_cli-2.3.16}/LICENSE +0 -0
  46. {utim_cli-2.3.14 → utim_cli-2.3.16}/MANIFEST.in +0 -0
  47. {utim_cli-2.3.14 → utim_cli-2.3.16}/README.md +0 -0
  48. {utim_cli-2.3.14 → utim_cli-2.3.16}/setup.cfg +0 -0
  49. {utim_cli-2.3.14 → utim_cli-2.3.16}/setup.py +0 -0
  50. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/_verify_repro.py +0 -0
  51. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/_verify_syntax.py +0 -0
  52. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/ask_helper.py +0 -0
  53. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/auth.py +0 -0
  54. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/backup.py +0 -0
  55. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/billing.py +0 -0
  56. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/blender_agent.py +0 -0
  57. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/constants.py +0 -0
  58. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/crawler/__init__.py +0 -0
  59. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/crawler/engine.py +0 -0
  60. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/dependency_manager.py +0 -0
  61. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/doctor.py +0 -0
  62. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/evidence_registry.py +0 -0
  63. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/harbor.py +0 -0
  64. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/knowledge_graph.py +0 -0
  65. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/local_db.py +0 -0
  66. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/logger.py +0 -0
  67. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/mcp_clean_wrapper.py +0 -0
  68. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/mcp_client.py +0 -0
  69. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/mcp_registry.json +0 -0
  70. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/models.txt +0 -0
  71. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/personalities.py +0 -0
  72. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/platform_installer.py +0 -0
  73. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/plugins/__init__.py +0 -0
  74. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/plugins/hooks.py +0 -0
  75. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/plugins/registry.py +0 -0
  76. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/plugins/schema.py +0 -0
  77. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/plugins/tool_loader.py +0 -0
  78. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/report.py +0 -0
  79. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/scrapy_search.py +0 -0
  80. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/sdk/__init__.py +0 -0
  81. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/sdk/config.py +0 -0
  82. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/sdk/context.py +0 -0
  83. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/sdk/events.py +0 -0
  84. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/sdk/handlers.py +0 -0
  85. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/sdk/session.py +0 -0
  86. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/sdk/sidecar.py +0 -0
  87. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/__init__.py +0 -0
  88. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/admin_auth.py +0 -0
  89. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/attribution.py +0 -0
  90. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/audit_log.py +0 -0
  91. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/batch_processor.py +0 -0
  92. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/captcha.py +0 -0
  93. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/concurrency.py +0 -0
  94. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/db.py +0 -0
  95. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/docs_md/SECRET_PROVISIONING.md +0 -0
  96. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/docs_md/about.md +0 -0
  97. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/docs_md/changelog.md +0 -0
  98. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/docs_md/docs.md +0 -0
  99. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/docs_md/features.md +0 -0
  100. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/docs_md/license.md +0 -0
  101. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/docs_md/pricing.md +0 -0
  102. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/docs_md/privacy.md +0 -0
  103. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/docs_md/refund.md +0 -0
  104. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/docs_md/support.md +0 -0
  105. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/docs_md/terms.md +0 -0
  106. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/exchange_rate.py +0 -0
  107. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/firebase.py +0 -0
  108. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/fix_duplicate_users.py +0 -0
  109. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/local_llm.py +0 -0
  110. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/logging_config.py +0 -0
  111. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/micro_batcher.py +0 -0
  112. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/model_agent.py +0 -0
  113. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/provision_build.py +0 -0
  114. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/rate_limit.py +0 -0
  115. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/rewards_engine.py +0 -0
  116. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/routes/admin_db_routes.py +0 -0
  117. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/routes/auth_routes.py +0 -0
  118. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/routes/billing_routes.py +0 -0
  119. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/routes/credit_routes.py +0 -0
  120. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/routes/feedback_routes.py +0 -0
  121. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/routes/marketplace_routes.py +0 -0
  122. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/routes/quota_routes.py +0 -0
  123. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/routes/quota_share_routes.py +0 -0
  124. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/routes/rewards_routes.py +0 -0
  125. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/routes/session_routes.py +0 -0
  126. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/routes/share_routes.py +0 -0
  127. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/server.py +0 -0
  128. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/server/storage_nodes.py +0 -0
  129. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/share.py +0 -0
  130. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/share_tui.py +0 -0
  131. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/situational_scoring.py +0 -0
  132. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/state.py +0 -0
  133. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/subagent_manager.py +0 -0
  134. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/task_dispatcher.py +0 -0
  135. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/task_logger.py +0 -0
  136. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/tools_grep.py +0 -0
  137. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/tui/__init__.py +0 -0
  138. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/tui/history_dialog.py +0 -0
  139. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/tui/marketplace_app_state.py +0 -0
  140. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/tui/marketplace_dialog.py +0 -0
  141. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/tui/marketplace_layout_engine.py +0 -0
  142. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/tui/mcp_dialog.py +0 -0
  143. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/tui/miniagents_dialog.py +0 -0
  144. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/tui/model_dialog.py +0 -0
  145. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/tui/personality_dialog.py +0 -0
  146. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/tui/publish_dialog.py +0 -0
  147. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/tui/quota_dialog.py +0 -0
  148. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/tui/quota_redeem_dialog.py +0 -0
  149. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/tui/quota_share_dialog.py +0 -0
  150. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/tui/resume_dialog.py +0 -0
  151. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/tui/rewards_tui.py +0 -0
  152. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/tui/skills_dialog.py +0 -0
  153. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/tui/subagents_dialog.py +0 -0
  154. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/tui/tasks_dialog.py +0 -0
  155. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/tui/thinking_display.py +0 -0
  156. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/tui/tools_dialog.py +0 -0
  157. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/tui/update_dialog.py +0 -0
  158. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/utilities.py +0 -0
  159. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/utimmodel.txt +0 -0
  160. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/vector_memory.py +0 -0
  161. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/wheel.py +0 -0
  162. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli/workspace.py +0 -0
  163. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli.egg-info/dependency_links.txt +0 -0
  164. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli.egg-info/entry_points.txt +0 -0
  165. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli.egg-info/requires.txt +0 -0
  166. {utim_cli-2.3.14 → utim_cli-2.3.16}/utim_cli.egg-info/top_level.txt +0 -0
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.3.16] - 2026-08-29
4
+
5
+ ### 🛠️ Minor Enhancements and Bug Fixes
6
+ - Minor Enhancements and Bug Fixes
7
+
8
+ ## [2.3.15] - 2026-08-29
9
+
10
+ ### 🛠️ Minor Enhancements and Tool Call Bug Fixes
11
+ - Minor enhancements and fixed tool call bugs
12
+
3
13
  ## [2.3.14] - 2026-08-28
4
14
 
5
15
  ### ⚡ Request Queue & Scaled Free Capacity
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: utim-cli
3
- Version: 2.3.14
3
+ Version: 2.3.16
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.14"
7
+ version = "2.3.16"
8
8
 
9
9
  description = "UTIM – Universal Terminal Intelligence Manager. A powerful agentic AI coding assistant for your terminal."
10
10
  readme = "README.md"
@@ -97,7 +97,7 @@ include = ["utim*", "utim_cli*"]
97
97
  exclude = ["tests*", "landing*", "scripts*"]
98
98
 
99
99
  [tool.setuptools.package-data]
100
- utim_cli = ["mcp_registry.json", "_version.py", "utimmodel.txt", "models.txt", "server/docs_md/*.md"]
100
+ utim_cli = ["mcp_registry.json", "_version.py", "utimmodel.txt", "models.txt", "server/docs_md/*.md", "builtin/**/*", "builtin/skills/**/*"]
101
101
 
102
102
 
103
103
  [tool.pytest.ini_options]
@@ -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.14"
17
+ __version__ = "2.3.16"
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.14"
8
+ __version__ = "2.3.16"
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.14"
14
+ VERSION = "2.3.16"
15
15
 
16
16
 
@@ -168,6 +168,13 @@ class ReActAgent:
168
168
  arguments[key] = val
169
169
  tool_name = actual_name
170
170
 
171
+ # Collapse duplicated tool names (e.g. 'read_fileread_file', 'read_file read_file',
172
+ # 'run_commandrun_command') produced by buggy model/proxy tool-call serialization.
173
+ import re as _re_dup
174
+ _dup_match = _re_dup.match(r'^([a-zA-Z_]\w*)[\s_]*\1$', tool_name)
175
+ if _dup_match:
176
+ tool_name = _dup_match.group(1)
177
+
171
178
  # Map common alias tool names to actual UTIM CLI tool names
172
179
  _TOOL_NAME_ALIASES = {
173
180
  "shell": "run_command",
@@ -74,10 +74,54 @@ def _sync_global_experience(api_key: str, current_folder: str):
74
74
  pass
75
75
 
76
76
  def _write_skills_and_agents():
77
- """Create the empty skills/ directory inside ~/.utim/. Skills are built by the reflection engine."""
77
+ """Create the skills/ directory inside ~/.utim/ and auto-seed built-in guides."""
78
78
  try:
79
79
  skills_dir = UTIM_DIR / 'skills'
80
80
  skills_dir.mkdir(parents=True, exist_ok=True)
81
+
82
+ guide_dir = skills_dir / 'utim-guide'
83
+ guide_dir.mkdir(parents=True, exist_ok=True)
84
+ guide_md = guide_dir / 'SKILL.md'
85
+ if not guide_md.exists():
86
+ guide_content = """---
87
+ name: utim-guide
88
+ description: Comprehensive reference for UTIM CLI features, keybindings, commands (/model, /rewind, /tools, /tasks, /mcp, /doctor), model switching, and session controls.
89
+ ---
90
+
91
+ # UTIM CLI User Guide & Command Reference
92
+
93
+ When users ask how to use, configure, or control UTIM CLI, or ask you to switch models or perform CLI operations, explain and reference these native commands:
94
+
95
+ ## 1. Model Switching & Configuration
96
+ - **Switch Model**: Type `/model` or press `Ctrl+M` in the terminal to open the interactive model selector (browse catalog, switch to free/paid models, import BYOK OpenRouter/Anthropic/OpenAI keys, or connect custom OpenAI-compatible endpoints).
97
+ - **Model Settings**: Type `/modelsettings` to customize max token limits, temperature, or context envelopes per model.
98
+ - **API Keys / BYOK**: Type `/auth` or configure OpenRouter / custom provider keys via `/model`.
99
+
100
+ ## 2. Session & History Navigation
101
+ - **Rollback & Rewind**: Type `/rewind` to open the interactive checkpoint rollback menu (rewinds conversation history and reverts filesystem changes).
102
+ - **Undo / Redo**: Type `/undo` or `/redo` to quickly revert or reapply recent file modifications.
103
+ - **Resume Sessions**: Type `/resume` to search and load past saved sessions.
104
+ - **Clear / Fresh Session**: Type `/clear` or `/new` to start a clean new session.
105
+ - **Context Compaction**: Type `/compact` to manually compact session context.
106
+
107
+ ## 3. Tool & Terminal Controls
108
+ - **Tool Toggle Menu**: Type `/tools` to interactively enable or disable specific tools (e.g. `run_command`, `write_file`, `web_search`).
109
+ - **Expand / Collapse Output**: Press `Ctrl+O` in the terminal to toggle expanding or collapsing tool execution details and `<think>` reasoning blocks.
110
+ - **Abort / Cancel**: Press `Ctrl+C` to immediately cancel active model generation or tool execution.
111
+ - **Background Tasks**: Type `/tasks` or `/bg` to list running background processes and timers. Use `/kill <id>` or `/stop <id>` to terminate them.
112
+
113
+ ## 4. MCP, Skills & Subagents
114
+ - **Model Context Protocol (MCP)**: Type `/mcp` to manage MCP server connections, view active tools, or install new MCP servers.
115
+ - **Skills & Subagents**: Type `/skills` to browse active skill playbooks, or `/subagents` / `/miniagents` to invoke specialized subagent workflows.
116
+ - **Marketplace**: Type `/marketplace` to explore community tools and extensions.
117
+
118
+ ## 5. Account & Diagnostics
119
+ - **Login / Logout**: Type `/login` or `/logout` to manage your UTIM Community account.
120
+ - **Quota & Balance**: Type `/quota` to check remaining credits, `/usage` for model token breakdown, `/quotashare` to share quota, or `/redeem` to redeem shared access keys.
121
+ - **Diagnostics**: Type `/doctor` to test environment health, API connectivity, and database integrity.
122
+ - **Support Report**: Type `/report` to create a diagnostic support bundle.
123
+ """
124
+ guide_md.write_text(guide_content, encoding='utf-8')
81
125
  except Exception as e:
82
126
  from utim_cli.logger import log_error
83
127
  log_error("bootstrap", "Failed to create skills directory in ~/.utim/", e)
@@ -157,7 +201,9 @@ def scan_available_skills(force_reload: bool = False):
157
201
  disabled = []
158
202
 
159
203
  skills = {}
204
+ pkg_builtin_skills = Path(__file__).resolve().parent / "builtin" / "skills"
160
205
  paths_to_check = [
206
+ pkg_builtin_skills,
161
207
  Path('.agents/skills'),
162
208
  Path('.agentskills'),
163
209
  Path('.agents'),
@@ -990,7 +990,7 @@ def get_brain_context_prompt(user_prompt: str = "") -> str:
990
990
  f"\n\n### BRAIN MEMORY DIRECTORY ###\n"
991
991
  f"Project brain directory: [brain](file:///{proj_url})\n"
992
992
  f"Global brain directory: [global_brain](file:///{root_url})\n"
993
- f"If you need historical project details, architecture snapshots, or conventions, you can view the files in the brain directory.\n"
993
+ f"Note: The paths above are directories. To explore historical project memories, architecture snapshots, or conventions, call `list_directory('{proj_url}')` and `read_file` on the memory files inside it.\n"
994
994
  )
995
995
  except Exception:
996
996
  return ""
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: utim-guide
3
+ description: Comprehensive reference for UTIM CLI features, keybindings, commands (/model, /rewind, /tools, /tasks, /mcp, /doctor), model switching, and session controls.
4
+ ---
5
+
6
+ # UTIM CLI User Guide & Command Reference
7
+
8
+ When users ask how to use, configure, or control UTIM CLI, or ask you to switch models or perform CLI operations, explain and reference these native commands:
9
+
10
+ ## 1. Model Switching & Configuration
11
+ - **Switch Model**: Type `/model` or press `Ctrl+M` in the terminal to open the interactive model selector (browse catalog, switch between free/paid models, import BYOK OpenRouter/Anthropic/OpenAI keys, or connect custom OpenAI-compatible endpoints).
12
+ - **Model Settings**: Type `/modelsettings` to customize max token limits, temperature, or context envelopes per model.
13
+ - **API Keys / BYOK**: Type `/auth` or configure OpenRouter / custom provider keys via `/model`.
14
+
15
+ ## 2. Session & History Navigation
16
+ - **Rollback & Rewind**: Type `/rewind` to open the interactive checkpoint rollback menu (rewinds conversation history and reverts filesystem changes).
17
+ - **Undo / Redo**: Type `/undo` or `/redo` to quickly revert or reapply recent file modifications.
18
+ - **Resume Sessions**: Type `/resume` to search and load past saved sessions.
19
+ - **Clear / Fresh Session**: Type `/clear` or `/new` to start a clean new session.
20
+ - **Context Compaction**: Type `/compact` to manually compact session context.
21
+
22
+ ## 3. Tool & Terminal Controls
23
+ - **Tool Toggle Menu**: Type `/tools` to interactively enable or disable specific tools (e.g. `run_command`, `write_file`, `web_search`).
24
+ - **Expand / Collapse Output**: Press `Ctrl+O` in the terminal to toggle expanding or collapsing tool execution details and `<think>` reasoning blocks.
25
+ - **Abort / Cancel**: Press `Ctrl+C` to immediately cancel active model generation or tool execution.
26
+ - **Background Tasks**: Type `/tasks` or `/bg` to list running background processes and timers. Use `/kill <id>` or `/stop <id>` to terminate them.
27
+
28
+ ## 4. MCP, Skills & Subagents
29
+ - **Model Context Protocol (MCP)**: Type `/mcp` to manage MCP server connections, view active tools, or install new MCP servers.
30
+ - **Skills & Subagents**: Type `/skills` to browse active skill playbooks, or `/subagents` / `/miniagents` to invoke specialized subagent workflows.
31
+ - **Marketplace**: Type `/marketplace` to explore community tools and extensions.
32
+
33
+ ## 5. Account & Diagnostics
34
+ - **Login / Logout**: Type `/login` or `/logout` to manage your UTIM Community account.
35
+ - **Quota & Balance**: Type `/quota` to check remaining credits, `/usage` for model token breakdown, `/quotashare` to share quota, or `/redeem` to redeem shared access keys.
36
+ - **Diagnostics**: Type `/doctor` to test environment health, API connectivity, and database integrity.
37
+ - **Support Report**: Type `/report` to create a diagnostic support bundle.
@@ -678,7 +678,8 @@ def sanitize_messages_for_api(messages, model_id: Optional[str] = None):
678
678
  fn["arguments"] = "{}"
679
679
  elif not isinstance(args, str):
680
680
  try:
681
- fn["arguments"] = json.dumps(args)
681
+ clean_args = _sanitize_json(args)
682
+ fn["arguments"] = json.dumps(clean_args, allow_nan=False)
682
683
  except (TypeError, ValueError):
683
684
  fn["arguments"] = "{}"
684
685
  c["function"] = fn
@@ -856,14 +857,171 @@ def parse_xml_tool_calls(content: str) -> tuple[str | None, list[dict]]:
856
857
  }
857
858
  })
858
859
 
859
- parts.append(content[last_idx:])
860
- cleaned_content = "".join(parts).strip()
861
- if not cleaned_content:
862
- cleaned_content = None
863
-
864
860
  return cleaned_content, tool_calls
865
861
 
866
862
 
863
+ def merge_tool_call_delta(entry: dict, tc_delta: Any) -> None:
864
+ """
865
+ Safely accumulates an incoming streaming tool call delta chunk into the target entry.
866
+ Prevents duplicate tool names (e.g. 'read_fileread_file') and duplicate cumulative
867
+ argument buffers (which produce invalid concatenated JSON).
868
+ """
869
+ if not entry or not isinstance(entry, dict):
870
+ return
871
+
872
+ if "function" not in entry or not isinstance(entry["function"], dict):
873
+ entry["function"] = {"name": "", "arguments": ""}
874
+
875
+ # 1. Tool Call ID
876
+ tc_id = tc_delta.get("id") if isinstance(tc_delta, dict) else getattr(tc_delta, "id", None)
877
+ if tc_id and not entry.get("id"):
878
+ entry["id"] = tc_id
879
+
880
+ # 2. Function object
881
+ fn = tc_delta.get("function") if isinstance(tc_delta, dict) else getattr(tc_delta, "function", None)
882
+ if not fn:
883
+ return
884
+
885
+ incoming_name = fn.get("name") if isinstance(fn, dict) else getattr(fn, "name", None)
886
+ incoming_args = fn.get("arguments") if isinstance(fn, dict) else getattr(fn, "arguments", None)
887
+
888
+ # Function Name:
889
+ if incoming_name:
890
+ curr_name = entry["function"].get("name", "")
891
+ if not curr_name:
892
+ entry["function"]["name"] = str(incoming_name)
893
+ elif curr_name == incoming_name:
894
+ pass # Provider resent the exact same complete tool name; do not duplicate
895
+ elif incoming_name.startswith(curr_name):
896
+ entry["function"]["name"] = str(incoming_name) # Provider sent full/longer name
897
+ elif curr_name.startswith(incoming_name):
898
+ pass # Existing name is already more complete
899
+ else:
900
+ # Genuine chunk continuation
901
+ entry["function"]["name"] += str(incoming_name)
902
+
903
+ # Function Arguments:
904
+ if incoming_args is not None:
905
+ incoming_args_str = str(incoming_args)
906
+ curr_args = entry["function"].get("arguments", "")
907
+ if not curr_args:
908
+ entry["function"]["arguments"] = incoming_args_str
909
+ elif curr_args == incoming_args_str:
910
+ pass # Provider resent the same arguments buffer
911
+ elif incoming_args_str.startswith(curr_args):
912
+ # Provider sent cumulative buffer instead of delta
913
+ entry["function"]["arguments"] = incoming_args_str
914
+ else:
915
+ # Standard delta chunk
916
+ entry["function"]["arguments"] += incoming_args_str
917
+
918
+
919
+ def repair_and_normalize_tool_call(tc: dict) -> dict:
920
+ """
921
+ Sanitizes, de-duplicates and repairs tool call names and JSON arguments.
922
+ Fixes doubled tool names ('read_fileread_file' -> 'read_file'), alias names,
923
+ concatenated JSON objects, unclosed braces, and attribute-style strings.
924
+ """
925
+ if not tc or not isinstance(tc, dict):
926
+ return tc
927
+
928
+ import re, json
929
+
930
+ KNOWN_TOOLS = {
931
+ "read_file", "write_file", "edit_file", "delete_file", "move_file",
932
+ "run_command", "list_directory", "get_background_output", "send_background_input",
933
+ "stop_background_process", "list_background_processes", "web_search",
934
+ "plan_project", "manage_todos", "generate_image", "grep_search", "view_file", "search"
935
+ }
936
+ ALIASES = {
937
+ "view_file": "read_file",
938
+ "search": "grep_search",
939
+ "search_codebase": "grep_search",
940
+ "bash": "run_command",
941
+ "cmd": "run_command",
942
+ "shell": "run_command",
943
+ "execute_command": "run_command",
944
+ }
945
+
946
+ fn = tc.get("function")
947
+ if not fn or not isinstance(fn, dict):
948
+ return tc
949
+
950
+ raw_name = str(fn.get("name", "")).strip()
951
+ raw_args = fn.get("arguments", "{}")
952
+
953
+ # 1. Clean and normalize tool name
954
+ for known in KNOWN_TOOLS:
955
+ if raw_name.startswith(known):
956
+ raw_name = known
957
+ break
958
+ if raw_name == known + known:
959
+ raw_name = known
960
+ break
961
+
962
+ if raw_name in ALIASES:
963
+ raw_name = ALIASES[raw_name]
964
+
965
+ fn["name"] = raw_name
966
+
967
+ # 2. Repair and validate arguments JSON
968
+ if isinstance(raw_args, dict):
969
+ fn["arguments"] = json.dumps(raw_args)
970
+ return tc
971
+
972
+ raw_args_str = str(raw_args).strip()
973
+ if not raw_args_str or raw_args_str in ("{}", "null", "None"):
974
+ fn["arguments"] = "{}"
975
+ return tc
976
+
977
+ # Try standard JSON parsing
978
+ try:
979
+ parsed = json.loads(raw_args_str)
980
+ if isinstance(parsed, dict):
981
+ fn["arguments"] = json.dumps(parsed)
982
+ return tc
983
+ except json.JSONDecodeError:
984
+ pass
985
+
986
+ # Recovery 1: Check for concatenated JSON objects (e.g. {"filepath": "foo.py"}{"filepath": "foo.py"})
987
+ match = re.search(r'(\{[\s\S]*?\})(?:\{|$)', raw_args_str)
988
+ if match:
989
+ try:
990
+ parsed = json.loads(match.group(1))
991
+ if isinstance(parsed, dict):
992
+ fn["arguments"] = json.dumps(parsed)
993
+ return tc
994
+ except json.JSONDecodeError:
995
+ pass
996
+
997
+ # Recovery 2: XML / key="value" attribute string fallback
998
+ attrs = re.findall(
999
+ r'(\w+)\s*=\s*(?:"((?:[^"\\]|\\.)*)"|\'((?:[^\'\\]|\\.)*)\'|(\S+))',
1000
+ raw_args_str,
1001
+ )
1002
+ if attrs:
1003
+ recovered = {}
1004
+ for key, v1, v2, v3 in attrs:
1005
+ val = v1 or v2 or v3 or ""
1006
+ val = val.replace('\\"', '"').replace("\\'", "'").replace("\\n", "\n").replace("\\t", "\t")
1007
+ recovered[key] = val
1008
+ if recovered:
1009
+ fn["arguments"] = json.dumps(recovered)
1010
+ return tc
1011
+
1012
+ # Recovery 3: Missing trailing closing brace (e.g. {"filepath": "foo.py" )
1013
+ if raw_args_str.startswith("{") and not raw_args_str.endswith("}"):
1014
+ try:
1015
+ parsed = json.loads(raw_args_str + "}")
1016
+ if isinstance(parsed, dict):
1017
+ fn["arguments"] = json.dumps(parsed)
1018
+ return tc
1019
+ except json.JSONDecodeError:
1020
+ pass
1021
+
1022
+ return tc
1023
+
1024
+
867
1025
  # ── Process-level session-usage runtime sentinel ────────────────────────────
868
1026
  _RUNTIME_SESSION_KEY: str = ""
869
1027
 
@@ -230,15 +230,7 @@ def get_all_utim_dirs() -> List[pathlib.Path]:
230
230
  if "pytest" in sys.argv[0] or "py.test" in sys.argv[0] or "PYTEST_CURRENT_TEST" in os.environ:
231
231
  _add(pathlib.Path('.utim'))
232
232
 
233
- # 3. Current workspace / project directories
234
- try:
235
- cwd = pathlib.Path.cwd()
236
- _add(cwd / ".utim")
237
- _add(cwd / ".agents")
238
- except Exception:
239
- pass
240
-
241
- # 4. Standard User Home variants (handles Windows username differences, Git Bash, MSYS, Cygwin)
233
+ # 3. Standard User Home variants (handles Windows username differences, Linux /home/<user>, macOS /Users/<user>, Git Bash, MSYS, Cygwin)
242
234
  user_home_paths = []
243
235
  if os.environ.get("USERPROFILE"):
244
236
  user_home_paths.append(pathlib.Path(os.environ["USERPROFILE"]))
@@ -263,20 +255,28 @@ def get_all_utim_dirs() -> List[pathlib.Path]:
263
255
  _add(h / ".utim")
264
256
  _add(h / ".agents")
265
257
 
258
+ # 4. Current workspace / project directories
259
+ try:
260
+ cwd = pathlib.Path.cwd()
261
+ _add(cwd / ".utim")
262
+ _add(cwd / ".agents")
263
+ except Exception:
264
+ pass
265
+
266
266
  return candidates
267
267
 
268
268
 
269
269
  def get_utim_dir() -> pathlib.Path:
270
- """Return the centralized .utim directory path.
271
- Prioritizes explicit environment variables, existing .utim directories,
272
- and falls back cleanly to the primary user profile directory.
270
+ """Return the centralized, authoritative .utim directory path for the current user.
271
+ Dynamically resolves across all OS environments (Windows, macOS, Linux, Termux)
272
+ and user profiles.
273
273
  """
274
274
  import sys
275
275
  if "pytest" in sys.argv[0] or "py.test" in sys.argv[0] or "PYTEST_CURRENT_TEST" in os.environ:
276
- return pathlib.Path('.utim')
276
+ return pathlib.Path('.utim').resolve()
277
277
 
278
- # Check explicit env var
279
- for env_var in ("UTIM_HOME", "UTIM_DIR", "UTIM_PATH"):
278
+ # 1. Check explicit environment variables
279
+ for env_var in ("UTIM_HOME", "UTIM_DIR", "UTIM_PATH", "AGY_HOME", "ANTIGRAVITY_HOME"):
280
280
  val = os.environ.get(env_var)
281
281
  if val:
282
282
  try:
@@ -286,26 +286,32 @@ def get_utim_dir() -> pathlib.Path:
286
286
  except Exception:
287
287
  pass
288
288
 
289
- # Check existing .utim directories from all candidate paths
290
- all_dirs = get_all_utim_dirs()
291
- for d in all_dirs:
292
- if d.exists() and d.is_dir() and d.name == ".utim":
293
- return d
294
-
295
- # Default fallback to primary user home
289
+ # 2. Dynamic Primary User Home directory (~/.utim)
296
290
  try:
297
291
  if os.environ.get("USERPROFILE"):
298
- fallback = pathlib.Path(os.environ["USERPROFILE"]) / ".utim"
292
+ home_utim = pathlib.Path(os.environ["USERPROFILE"]) / ".utim"
293
+ elif os.environ.get("HOME"):
294
+ home_utim = pathlib.Path(os.environ["HOME"]) / ".utim"
295
+ elif os.environ.get("HOMEDRIVE") and os.environ.get("HOMEPATH"):
296
+ home_utim = pathlib.Path(os.environ["HOMEDRIVE"] + os.environ["HOMEPATH"]) / ".utim"
299
297
  else:
300
- fallback = pathlib.Path.home() / ".utim"
298
+ home_utim = pathlib.Path.home() / ".utim"
301
299
  except Exception:
302
- fallback = pathlib.Path(os.path.expanduser("~")) / ".utim"
300
+ home_utim = pathlib.Path(os.path.expanduser("~")) / ".utim"
303
301
 
304
302
  try:
305
- fallback.mkdir(parents=True, exist_ok=True)
303
+ home_utim.mkdir(parents=True, exist_ok=True)
304
+ return home_utim
306
305
  except Exception:
307
306
  pass
308
- return fallback
307
+
308
+ # 3. Candidate fallback if user home is read-only or inaccessible
309
+ all_dirs = get_all_utim_dirs()
310
+ for d in all_dirs:
311
+ if d.exists() and d.is_dir() and d.name == ".utim":
312
+ return d
313
+
314
+ return home_utim
309
315
 
310
316
 
311
317
  class Config: