utim-cli 2.1.1__tar.gz → 2.1.3__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 (125) hide show
  1. {utim_cli-2.1.1 → utim_cli-2.1.3}/CHANGELOG.md +14 -0
  2. utim_cli-2.1.3/LICENSE +26 -0
  3. {utim_cli-2.1.1/utim_cli.egg-info → utim_cli-2.1.3}/PKG-INFO +2 -1
  4. {utim_cli-2.1.1 → utim_cli-2.1.3}/pyproject.toml +4 -1
  5. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/__init__.py +1 -1
  6. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/_version.py +1 -1
  7. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/agent.py +95 -69
  8. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/auth.py +15 -2
  9. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/blender_agent.py +2 -1
  10. utim_cli-2.1.3/utim_cli/brain.py +744 -0
  11. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/client_utils.py +20 -3
  12. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/config.py +4 -2
  13. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/constants.py +2 -0
  14. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/context_pruner.py +7 -3
  15. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/mcp_clean_wrapper.py +28 -0
  16. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/orchestrator.py +553 -343
  17. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/reflection.py +225 -3
  18. utim_cli-2.1.3/utim_cli/server/batch_processor.py +119 -0
  19. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/captcha.py +1 -2
  20. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/cli_auth.py +5 -1
  21. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/db.py +108 -3
  22. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/email_utils.py +509 -509
  23. utim_cli-2.1.3/utim_cli/server/local_llm.py +236 -0
  24. utim_cli-2.1.3/utim_cli/server/micro_batcher.py +95 -0
  25. utim_cli-2.1.3/utim_cli/server/model_agent.py +218 -0
  26. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/models.py +242 -29
  27. utim_cli-2.1.3/utim_cli/server/rewards_engine.py +403 -0
  28. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/router.py +29 -3
  29. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/routes/__init__.py +2 -2
  30. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/routes/auth_routes.py +8 -0
  31. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/routes/completion_routes.py +135 -40
  32. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/routes/credit_routes.py +8 -0
  33. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/routes/marketplace_routes.py +2 -2
  34. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/routes/quota_routes.py +33 -0
  35. utim_cli-2.1.3/utim_cli/server/routes/rewards_routes.py +558 -0
  36. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/routes/security_routes.py +1 -1
  37. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/routes/share_routes.py +146 -51
  38. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/server.py +60 -56
  39. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/storage_nodes.py +134 -0
  40. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/share.py +116 -47
  41. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/share_tui.py +12 -12
  42. utim_cli-2.1.3/utim_cli/situational_scoring.py +359 -0
  43. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/tools.py +186 -60
  44. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/tui/feedback_dialog.py +433 -419
  45. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/tui/marketplace_app_state.py +63 -63
  46. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/tui/marketplace_dialog.py +132 -132
  47. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/tui/marketplace_layout_engine.py +182 -182
  48. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/tui/mcp_dialog.py +627 -627
  49. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/tui/miniagents_dialog.py +369 -369
  50. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/tui/model_dialog.py +1701 -1902
  51. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/tui/publish_dialog.py +30 -30
  52. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/tui/quota_redeem_dialog.py +181 -181
  53. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/tui/quota_share_dialog.py +491 -491
  54. utim_cli-2.1.3/utim_cli/tui/rewards_tui.py +224 -0
  55. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/tui/skills_dialog.py +320 -320
  56. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/tui/subagents_dialog.py +314 -314
  57. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/tui/tools_dialog.py +248 -248
  58. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/tui/update_dialog.py +1 -1
  59. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/utim.py +165 -114
  60. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/vector_memory.py +10 -1
  61. utim_cli-2.1.3/utim_cli/wheel.py +26 -0
  62. {utim_cli-2.1.1 → utim_cli-2.1.3/utim_cli.egg-info}/PKG-INFO +2 -1
  63. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli.egg-info/SOURCES.txt +9 -0
  64. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli.egg-info/entry_points.txt +1 -0
  65. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli.egg-info/requires.txt +1 -0
  66. utim_cli-2.1.1/LICENSE +0 -21
  67. utim_cli-2.1.1/utim_cli/situational_scoring.py +0 -278
  68. {utim_cli-2.1.1 → utim_cli-2.1.3}/MANIFEST.in +0 -0
  69. {utim_cli-2.1.1 → utim_cli-2.1.3}/README.md +0 -0
  70. {utim_cli-2.1.1 → utim_cli-2.1.3}/setup.cfg +0 -0
  71. {utim_cli-2.1.1 → utim_cli-2.1.3}/setup.py +0 -0
  72. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/_check_obj.py +0 -0
  73. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/_find_mcp.py +0 -0
  74. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/_find_plan.py +0 -0
  75. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/_find_tool.py +0 -0
  76. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/_patch_multimodal.py +0 -0
  77. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/_test_regex.py +0 -0
  78. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/backup.py +0 -0
  79. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/billing.py +0 -0
  80. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/bootstrap.py +0 -0
  81. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/doctor.py +0 -0
  82. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/harbor.py +0 -0
  83. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/knowledge_graph.py +0 -0
  84. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/local_db.py +0 -0
  85. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/logger.py +0 -0
  86. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/mcp_client.py +0 -0
  87. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/mcp_registry.json +0 -0
  88. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/report.py +0 -0
  89. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/scrapy_search.py +0 -0
  90. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/__init__.py +0 -0
  91. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/attribution.py +0 -0
  92. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/audit_log.py +0 -0
  93. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/auth.py +0 -0
  94. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/docs_md/SECRET_PROVISIONING.md +0 -0
  95. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/docs_md/about.md +0 -0
  96. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/docs_md/changelog.md +0 -0
  97. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/docs_md/docs.md +0 -0
  98. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/docs_md/features.md +0 -0
  99. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/docs_md/license.md +0 -0
  100. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/docs_md/pricing.md +0 -0
  101. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/docs_md/privacy.md +0 -0
  102. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/docs_md/refund.md +0 -0
  103. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/docs_md/support.md +0 -0
  104. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/docs_md/terms.md +0 -0
  105. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/exchange_rate.py +0 -0
  106. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/firebase.py +0 -0
  107. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/fix_duplicate_users.py +0 -0
  108. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/history.py +0 -0
  109. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/logging_config.py +0 -0
  110. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/pricing_updater.py +0 -0
  111. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/provision_build.py +0 -0
  112. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/rate_limit.py +0 -0
  113. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/routes/feedback_routes.py +0 -0
  114. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/routes/quota_share_routes.py +0 -0
  115. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/routes/referral_routes.py +0 -0
  116. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/server/routes/session_routes.py +0 -0
  117. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/state.py +0 -0
  118. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/tui/__init__.py +0 -0
  119. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/tui/history_dialog.py +0 -0
  120. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/tui/quota_dialog.py +0 -0
  121. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/tui/resume_dialog.py +0 -0
  122. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/utilities.py +0 -0
  123. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli/workspace.py +0 -0
  124. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli.egg-info/dependency_links.txt +0 -0
  125. {utim_cli-2.1.1 → utim_cli-2.1.3}/utim_cli.egg-info/top_level.txt +0 -0
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.1.3] - 2026-08-12
4
+
5
+ ### 🛠️ Fixes
6
+ - Minor modifications and Bug fixes
7
+
8
+ ## [2.1.2] - 2026-08-10
9
+
10
+ ### 🛠️ Fixes
11
+ - **Fixed `grep_search` auto-regex promotion bug**: Queries containing regex metacharacters (`|`, `\`, `*`, etc.) were silently auto-promoted to regex mode even when `is_regex=False`, causing literal searches to fail with "No matches found." Now `is_regex=False` always means literal search, matching the documented "Literal-first semantics."
12
+ - **Made ripgrep an automatic dependency**: Added `ripgrep>=0.13.0` to core dependencies in `pyproject.toml` and `requirements.txt`. The `_grep_find_ripgrep()` function now falls back to the `ripgrep` PyPI package (which bundles a prebuilt binary) when ripgrep isn't found on the system PATH, ensuring fast searches work out of the box.
13
+ - **Increased context compression interval from 25 to 35 iterations**: Reduced compression frequency to conserve context resources and improve performance during long agent sessions.
14
+ - **Reduced batch poll interval from 5s to 0.3s**: Improved responsiveness of batch status checks in `BatchAPIProcessor.poll_and_retrieve_results()`.
15
+ - **Cleaned up network error messages in feedback dialog**: Stripped verbose `HTTPSConnectionPool` technical details from error messages, now showing clean messages like "Connection timed out. Please check your network and try again."
16
+
3
17
  ## [2.1.1] - 2026-08-06
4
18
 
5
19
  ### 🛠️ Fixes
utim_cli-2.1.3/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ UTIM PROPRIETARY SOFTWARE END USER LICENSE AGREEMENT (EULA)
2
+
3
+ Copyright (c) 2026 Emend AI. All Rights Reserved.
4
+
5
+ IMPORTANT: READ THIS LICENSE AGREEMENT CAREFULLY BEFORE DOWNLOADING, INSTALLING, OR USING THE UTIM CLI SOFTWARE.
6
+
7
+ 1. PROPRIETARY SOFTWARE
8
+ UTIM CLI ("Software") is proprietary commercial software developed and owned exclusively by Emend AI ("Licensor"). The Software is licensed, not sold, to you ("Licensee") under the terms and conditions of this Agreement.
9
+
10
+ 2. LICENSE GRANT
11
+ Subject to compliance with this Agreement and payment of applicable subscription or usage fees:
12
+ - Free Tier License: Licensor grants Licensee a limited, non-exclusive, non-transferable, revocable license to install and use the Software for personal, open-source learning, and non-commercial evaluation purposes.
13
+ - Commercial Tier License: Paid subscribers (Hobby, Pro, Max, Ultimate Tiers) or users with active Bring Your Own Key (BYOK) configurations are granted a non-exclusive, non-transferable license to use the Software for commercial software engineering, corporate repositories, and professional client work.
14
+
15
+ 3. RESTRICTIONS
16
+ Licensee shall NOT:
17
+ - Reverse engineer, decompile, disassemble, modify, or attempt to discover the source code or underlying client orchestrator mechanisms of compiled Software binaries.
18
+ - Circumvent rate limits, credit accounting controls, or 5-hour quota refill mechanisms.
19
+ - Sublicense, resell, rent, lease, or distribute the Software to third parties without prior written authorization.
20
+ - Create duplicate or automated accounts to harvest free promotional credit allocations.
21
+
22
+ 4. INTELLECTUAL PROPERTY
23
+ All title, ownership, brand trademarks, domain adaptation engines, AST parsing logic, and intellectual property rights in and to the Software remain the exclusive property of Emend AI. Licensee retains 100% full ownership of all user prompts submitted and code files generated in their local workspaces.
24
+
25
+ 5. NO WARRANTY & LIMITATION OF LIABILITY
26
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. IN NO EVENT SHALL EMEND AI OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES (INCLUDING DATA LOSS, REPOSITORY CORRUPTION, OR API BILLING COSTS) ARISING OUT OF THE USE OF OR INABILITY TO USE THE SOFTWARE.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: utim-cli
3
- Version: 2.1.1
3
+ Version: 2.1.3
4
4
  Summary: UTIM – Universal Terminal Intelligence Manager. A powerful agentic AI coding assistant for your terminal.
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://utim.dev
@@ -31,6 +31,7 @@ Requires-Dist: rich>=13.0.0
31
31
  Requires-Dist: prompt_toolkit>=3.0.0
32
32
  Requires-Dist: nest-asyncio>=1.5.0
33
33
  Requires-Dist: sqlalchemy>=2.0.0
34
+ Requires-Dist: ripgrep>=0.13.0
34
35
  Provides-Extra: search
35
36
  Requires-Dist: scrapy>=2.11.0; extra == "search"
36
37
  Requires-Dist: scrapy-playwright>=0.0.34; extra == "search"
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "utim-cli"
7
- version = "2.1.1"
7
+ version = "2.1.3"
8
8
  description = "UTIM – Universal Terminal Intelligence Manager. A powerful agentic AI coding assistant for your terminal."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -33,6 +33,7 @@ dependencies = [
33
33
  "prompt_toolkit>=3.0.0",
34
34
  "nest-asyncio>=1.5.0",
35
35
  "sqlalchemy>=2.0.0",
36
+ "ripgrep>=0.13.0",
36
37
  ]
37
38
 
38
39
  [project.urls]
@@ -88,6 +89,8 @@ memory = [
88
89
 
89
90
  [project.scripts]
90
91
  utim = "utim_cli.utim:main_cli_entry"
92
+ utimlite = "utim_cli.utim:main_cli_entry"
93
+
91
94
 
92
95
  [tool.setuptools.packages.find]
93
96
  include = ["utim_cli*"]
@@ -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.1.1"
8
+ __version__ = "2.1.3"
9
9
 
10
10
  import builtins, os, pathlib, urllib.parse, re
11
11
 
@@ -11,4 +11,4 @@
11
11
  # from here so every surface stays in sync automatically.
12
12
  # ─────────────────────────────────────────────────────────────────────────────
13
13
 
14
- VERSION = "2.1.1"
14
+ VERSION = "2.1.3"
@@ -7,7 +7,6 @@ from rich.console import Console
7
7
  from rich.live import Live
8
8
  from rich.spinner import Spinner
9
9
  from rich.markdown import Markdown
10
- from rich.panel import Panel
11
10
  from rich.text import Text
12
11
  from rich.rule import Rule
13
12
  from rich.syntax import Syntax
@@ -19,23 +18,16 @@ BLUE = "#42bcf5"
19
18
  YELLOW = "#f9e2af"
20
19
 
21
20
  TOOL_META = {
22
- "read_file": {"icon": "📄", "verb": "ReadFile", "color": BLUE},
23
- "write_file": {"icon": "✏️", "verb": "WriteFile", "color": YELLOW},
24
- "edit_file": {"icon": "✂️", "verb": "EditFile", "color": YELLOW},
25
- "move_file": {"icon": "📦", "verb": "MoveFile", "color": BLUE},
26
- "delete_file": {"icon": "🗑️ ", "verb": "DeleteFile", "color": PURPLE},
27
- "run_command": {"icon": "⚡", "verb": "RunCommand", "color": YELLOW},
28
- "list_directory": {"icon": "📁", "verb": "ReadFolder", "color": PURPLE},
21
+ "read_file": {"icon": "", "verb": "Read", "color": BLUE},
22
+ "write_file": {"icon": "", "verb": "Write", "color": YELLOW},
23
+ "edit_file": {"icon": "", "verb": "Edit", "color": YELLOW},
24
+ "move_file": {"icon": "", "verb": "Move", "color": BLUE},
25
+ "delete_file": {"icon": "", "verb": "Delete", "color": PURPLE},
26
+ "run_command": {"icon": "", "verb": "Bash", "color": YELLOW},
27
+ "list_directory": {"icon": "", "verb": "ListDir", "color": PURPLE},
29
28
  }
30
29
 
31
- TIPS = [
32
- "Tip: Use /help to see all available commands and the help panel",
33
- "Tip: Save your current conversation with /resume save <tag>",
34
- "Tip: Clear conversation history with /clear",
35
- "Tip: Check your quota limits and credit usage with /usage",
36
- "Tip: Enable or disable individual tools with /tools",
37
- "Tip: Switch models with /model",
38
- ]
30
+ TIPS = []
39
31
 
40
32
  class ReActAgent:
41
33
  """A standalone agent that can reason and execute tools with streaming."""
@@ -48,7 +40,7 @@ class ReActAgent:
48
40
 
49
41
  api_key = os.getenv("OPENROUTER_API_KEY") or os.getenv("OPENRouter_API_KEY")
50
42
  if not api_key:
51
- self.console.print("[dim]⚠ Warning: OPENROUTER_API_KEY is not set.[/dim]")
43
+ self.console.print("[dim]Warning: OPENROUTER_API_KEY is not set.[/dim]")
52
44
 
53
45
  try:
54
46
  from openai import OpenAI
@@ -80,23 +72,23 @@ class ReActAgent:
80
72
  return f"{elapsed // 60}m {elapsed % 60}s" if elapsed >= 60 else f"{elapsed}s"
81
73
 
82
74
  def _next_tip(self):
75
+ if not TIPS:
76
+ return ""
83
77
  tip = TIPS[self._tip_index % len(TIPS)]
84
78
  self._tip_index += 1
85
79
  return tip
86
80
 
81
+
87
82
  def _render_tool_panel(self, func_name: str, args: dict, result: str):
88
- """Render a rich panel for a completed tool call."""
89
- meta = TOOL_META.get(func_name, {"icon": "●", "verb": func_name, "color": "white"})
90
- icon = meta["icon"]
91
- verb = meta["verb"]
92
- color = meta["color"]
83
+ """Render a completed tool call in Antigravity inline style — no box, no border.
93
84
 
94
- # Header line
95
- header = Text()
96
- header.append("✓ ", style=f"bold {color}")
97
- header.append(verb, style=f"bold {color}")
85
+ Line 1: \u25cf Verb(path/arg)
86
+ Line 2: \u2514 summary
87
+ """
88
+ meta = TOOL_META.get(func_name, {"icon": "\u25cf", "verb": func_name, "color": "white"})
89
+ verb = meta["verb"]
90
+ color = meta["color"]
98
91
 
99
- # Show the primary argument (filepath or command)
100
92
  primary_arg = (
101
93
  args.get("filepath")
102
94
  or args.get("command")
@@ -104,51 +96,37 @@ class ReActAgent:
104
96
  or args.get("src")
105
97
  or ""
106
98
  )
107
- if primary_arg:
108
- header.append(f" {primary_arg}", style="white")
109
99
 
110
- # Body
111
- body = Text()
112
-
113
- if func_name == "write_file":
114
- body.append(result, style="dim")
100
+ # Line 1: \u25cf Verb(arg)
101
+ header = Text()
102
+ header.append(verb, style="bold white")
103
+ if primary_arg:
104
+ header.append(f"({primary_arg})", style="dim white")
105
+ header.append(" (ctrl+o to expand)", style="dim #585b70")
115
106
 
116
- elif func_name == "edit_file":
117
- body.append(result, style="dim")
107
+ # Line 2: \u2514 summary
108
+ summary = Text()
109
+ summary.append(" \u2514 ", style="dim #585b70")
118
110
 
119
- elif func_name == "read_file":
111
+ if func_name == "read_file":
120
112
  lines = result.split("\n")
121
- body.append(f"Read {len(lines)} lines", style="dim")
122
-
113
+ summary.append(f"{len(lines)} lines", style="dim white")
114
+ elif func_name in ("write_file", "edit_file"):
115
+ first = result.strip().splitlines()[0][:80] if result.strip() else "done"
116
+ summary.append(first, style="dim white")
123
117
  elif func_name == "run_command":
124
118
  out = str(result).strip()
125
- if len(out) > 300:
126
- out = out[:300] + "\n[…truncated]"
127
- body.append(out, style="dim")
128
-
119
+ first = out.splitlines()[0][:80] if out else "(no output)"
120
+ summary.append(first, style="dim white")
129
121
  elif func_name == "list_directory":
130
122
  items = [l for l in str(result).split("\n") if l.strip()]
131
- body.append(f"Listed {max(0, len(items)-1)} item(s).", style="dim")
132
-
133
- elif func_name == "move_file":
134
- body.append(result, style="dim")
135
-
136
- elif func_name == "delete_file":
137
- body.append(result, style="dim")
138
-
123
+ summary.append(f"{max(0, len(items)-1)} items", style="dim white")
139
124
  else:
140
125
  out = str(result).strip()
141
- if len(out) > 300:
142
- out = out[:300] + "\n[…]"
143
- body.append(out, style="dim")
126
+ summary.append((out.splitlines()[0][:80] if out else "done"), style="dim white")
144
127
 
145
- content = header + "\n" + body
146
-
147
- self.console.print(Panel(
148
- content,
149
- border_style=color,
150
- expand=False,
151
- ))
128
+ self.console.print(header)
129
+ self.console.print(summary)
152
130
 
153
131
  def _execute_tool_call(self, tool_call):
154
132
  """Execute a single tool call by name."""
@@ -196,23 +174,53 @@ class ReActAgent:
196
174
 
197
175
  arguments = json.loads(tool_call["function"]["arguments"])
198
176
 
199
- # Show the tool is running
177
+ # Show the tool is running - Antigravity style: ● Verb(arg) (ctrl+o to expand)
200
178
  meta = TOOL_META.get(tool_name, {"icon": "●", "verb": tool_name, "color": "white"})
201
- self.console.print(f" {meta['icon']} {meta['verb']} running...", style=f"dim {meta['color']}")
179
+ _pre = Text()
180
+ _pre.append(meta['verb'], style="bold white")
181
+ primary_arg = (
182
+ arguments.get("filepath")
183
+ or arguments.get("command")
184
+ or arguments.get("path")
185
+ or ""
186
+ )
187
+ if primary_arg:
188
+ _pre.append(f"({primary_arg})", style="dim white")
189
+ _pre.append(" (ctrl+o to expand)", style="dim #585b70")
190
+ self.console.print(_pre)
191
+ pre_plain = f"{meta['verb']}({primary_arg}) (ctrl+o to expand)" if primary_arg else f"{meta['verb']} (ctrl+o to expand)"
192
+ self._last_running_indicator_len = len(pre_plain)
202
193
 
203
194
  if "__" in tool_name:
204
195
  server_name, actual_tool_name = tool_name.split("__", 1)
205
196
  try:
206
197
  from utim_cli.mcp_client import mcp_manager
207
198
  if server_name in mcp_manager.sessions:
208
- self.console.print(f" 🔌 Calling MCP tool {server_name} ➔ {actual_tool_name}...", style="dim #cba6f7")
199
+ self.console.print(f" Calling MCP tool {server_name} ➔ {actual_tool_name}...", style="dim #cba6f7")
209
200
  result = mcp_manager.call_tool(server_name, actual_tool_name, arguments)
210
201
 
211
202
  # Temporarily register metadata for render
212
- TOOL_META[tool_name] = {"icon": "🔌", "verb": f"Calling {server_name} ➔ {actual_tool_name}", "color": "#cba6f7"}
203
+ TOOL_META[tool_name] = {"icon": "", "verb": f"Calling {server_name} ➔ {actual_tool_name}", "color": "#cba6f7"}
204
+ # Erase the running line first
205
+ try:
206
+ width = self.console.size.width
207
+ except Exception:
208
+ width = 80
209
+ import math
210
+ num_lines = math.ceil(getattr(self, "_last_running_indicator_len", 40) / max(1, width))
211
+ for _ in range(num_lines):
212
+ self.console.print("\033[F\033[K", end="")
213
213
  self._render_tool_panel(tool_name, arguments, result)
214
214
  return str(result)
215
215
  except Exception as e:
216
+ try:
217
+ width = self.console.size.width
218
+ except Exception:
219
+ width = 80
220
+ import math
221
+ num_lines = math.ceil(getattr(self, "_last_running_indicator_len", 40) / max(1, width))
222
+ for _ in range(num_lines):
223
+ self.console.print("\033[F\033[K", end="")
216
224
  return f"Error executing MCP tool {tool_name}: {str(e)}"
217
225
 
218
226
  utim_tools, tool_functions = get_tools()
@@ -224,11 +232,29 @@ class ReActAgent:
224
232
 
225
233
  try:
226
234
  result = tool_functions[tool_name](**arguments)
235
+ # Erase the running line first
236
+ try:
237
+ width = self.console.size.width
238
+ except Exception:
239
+ width = 80
240
+ import math
241
+ num_lines = math.ceil(getattr(self, "_last_running_indicator_len", 40) / max(1, width))
242
+ for _ in range(num_lines):
243
+ self.console.print("\033[F\033[K", end="")
227
244
  self._render_tool_panel(tool_name, arguments, result)
228
245
  return str(result)
229
246
  except Exception as e:
247
+ try:
248
+ width = self.console.size.width
249
+ except Exception:
250
+ width = 80
251
+ import math
252
+ num_lines = math.ceil(getattr(self, "_last_running_indicator_len", 40) / max(1, width))
253
+ for _ in range(num_lines):
254
+ self.console.print("\033[F\033[K", end="")
230
255
  return f"Error executing {tool_name}: {str(e)}"
231
256
 
257
+
232
258
  def run(self, max_iterations: int = 500, show_tools: bool = True):
233
259
  """Run the agent loop with streaming."""
234
260
  self.start_time = time.time()
@@ -323,17 +349,17 @@ class ReActAgent:
323
349
  "content": result,
324
350
  })
325
351
  else:
326
- self.console.print(f"\n[bold yellow]⚠ Agent paused after reaching maximum iterations ({max_iterations}).[/bold yellow]")
352
+ self.console.print(f"\n[bold yellow]Agent paused after reaching maximum iterations ({max_iterations}).[/bold yellow]")
327
353
  self.console.print("[dim]You can type 'continue' to resume the task.[/dim]\n")
328
354
 
329
355
  elapsed = self.get_elapsed_time()
330
356
  tip = self._next_tip()
331
- self.console.print(Rule(f"[dim]⚙ {elapsed} • {tip}[/dim]"))
357
+ self.console.print(Rule(f"[dim]{elapsed}[/dim]", style="dim"))
332
358
 
333
359
  def list_tools(self):
334
360
  """Display all available tools in a formatted table."""
335
361
  self.console.print()
336
- self.console.print(Rule("[bold accent]🔧 Available Tools[/bold accent]"))
362
+ self.console.print(Rule("[bold accent]Available Tools[/bold accent]"))
337
363
  self.console.print()
338
364
 
339
365
  headers = ["Tool", "Description"]
@@ -374,7 +400,7 @@ class ReActAgent:
374
400
  desc_str = f"{status_tag} [dim]{desc}[/dim]" if is_disabled else f"[dim]{desc}[/dim]"
375
401
 
376
402
  if t_type == "mcp":
377
- tool_str = f" 🔌 {name.ljust(col_widths[0] - 4)} {desc_str}"
403
+ tool_str = f" {name.ljust(col_widths[0] - 4)} {desc_str}"
378
404
  self.console.print(f"[#cba6f7]{tool_str}[/#cba6f7]")
379
405
  else:
380
406
  meta = TOOL_META.get(name, {"icon": "●", "color": "white"})
@@ -38,6 +38,19 @@ YELLOW = "#f9e2af"
38
38
  SERVER_URL = os.environ.get("UTIM_SERVER_URL", "https://api.utim.dev")
39
39
  WEB_URL = os.environ.get("UTIM_WEB_URL", "https://utim.dev")
40
40
 
41
+ def get_api_url() -> str:
42
+ """Get base server URL from environment or fallback default."""
43
+ return os.environ.get("UTIM_SERVER_URL", SERVER_URL)
44
+
45
+ def get_auth_headers() -> dict[str, str]:
46
+ """Get request headers containing current user API key."""
47
+ api_key = config.get("api_key") or ""
48
+ headers = {"Content-Type": "application/json"}
49
+ if api_key:
50
+ headers["X-API-Key"] = api_key
51
+ headers["Authorization"] = f"Bearer {api_key}"
52
+ return headers
53
+
41
54
  # Firebase project config (public — safe to embed in CLI)
42
55
  FIREBASE_PROJECT_ID = "u-t-i-m-39c26"
43
56
  FIREBASE_API_KEY = "AIzaSyAV-L3jY6dS3wXMMNGnYnPTX3IuqBFqK4E"
@@ -201,7 +214,7 @@ def login(restart: bool = True) -> None:
201
214
  pass
202
215
 
203
216
  # Print compact Termux/Android friendly instructions
204
- raw_print(f"\n {BOLD}🔑 Device Sign-In Required:{RESET}")
217
+ raw_print(f"\n {BOLD}Device Sign-In Required:{RESET}")
205
218
  raw_print(f" 1. Open link: {BLUE}{verify_url}{RESET}")
206
219
  if clipboard_notice:
207
220
  raw_print(f" {clipboard_notice.strip()}")
@@ -276,7 +289,7 @@ def login(restart: bool = True) -> None:
276
289
  restart_process()
277
290
  return
278
291
  else:
279
- raw_print(f" {YELLOW}⚠ Authorization pending. Please complete it at: {BLUE}{verify_url}{RESET}")
292
+ raw_print(f" {YELLOW}Authorization pending. Please complete it at: {BLUE}{verify_url}{RESET}")
280
293
  else:
281
294
  raw_print(f"\n \033[1;31m✗ Verification failed (HTTP {pr.status_code}). Please try again.\033[0m\n")
282
295
  except Exception as e:
@@ -63,12 +63,13 @@ _VISION_MODELS: List[str] = [
63
63
  ]
64
64
 
65
65
  _CODE_MODELS: List[str] = [
66
- "poolside/laguna-m.1:free",
67
66
  DEFAULT_MODEL,
67
+ "cohere/north-mini-code:free",
68
68
  "openrouter/free"
69
69
  ]
70
70
 
71
71
 
72
+
72
73
  def _get_vision_models() -> List[str]:
73
74
  """Return vision model list, with user override prepended if configured."""
74
75
  try: