utim-cli 2.1.2__tar.gz → 2.2.0__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 (127) hide show
  1. {utim_cli-2.1.2 → utim_cli-2.2.0}/CHANGELOG.md +5 -0
  2. {utim_cli-2.1.2/utim_cli.egg-info → utim_cli-2.2.0}/PKG-INFO +1 -1
  3. {utim_cli-2.1.2 → utim_cli-2.2.0}/pyproject.toml +1 -1
  4. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/__init__.py +1 -1
  5. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/_version.py +1 -1
  6. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/agent.py +95 -69
  7. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/auth.py +15 -2
  8. utim_cli-2.2.0/utim_cli/brain.py +734 -0
  9. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/mcp_clean_wrapper.py +28 -0
  10. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/orchestrator.py +1024 -550
  11. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/reflection.py +330 -253
  12. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/batch_processor.py +7 -7
  13. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/captcha.py +1 -2
  14. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/cli_auth.py +5 -1
  15. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/db.py +108 -3
  16. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/email_utils.py +509 -509
  17. utim_cli-2.2.0/utim_cli/server/local_llm.py +380 -0
  18. utim_cli-2.2.0/utim_cli/server/micro_batcher.py +280 -0
  19. utim_cli-2.2.0/utim_cli/server/model_agent.py +218 -0
  20. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/models.py +237 -20
  21. utim_cli-2.2.0/utim_cli/server/rewards_engine.py +403 -0
  22. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/router.py +17 -3
  23. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/routes/__init__.py +2 -3
  24. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/routes/auth_routes.py +8 -0
  25. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/routes/completion_routes.py +74 -42
  26. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/routes/credit_routes.py +8 -0
  27. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/routes/marketplace_routes.py +2 -2
  28. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/routes/quota_routes.py +33 -0
  29. utim_cli-2.2.0/utim_cli/server/routes/rewards_routes.py +558 -0
  30. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/routes/security_routes.py +1 -1
  31. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/routes/share_routes.py +146 -51
  32. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/server.py +60 -60
  33. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/storage_nodes.py +134 -0
  34. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/share.py +116 -47
  35. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/share_tui.py +12 -12
  36. utim_cli-2.2.0/utim_cli/situational_scoring.py +369 -0
  37. utim_cli-2.2.0/utim_cli/subagent_manager.py +366 -0
  38. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/tools.py +231 -78
  39. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/tui/feedback_dialog.py +34 -34
  40. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/tui/history_dialog.py +10 -10
  41. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/tui/marketplace_app_state.py +63 -63
  42. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/tui/marketplace_dialog.py +132 -132
  43. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/tui/marketplace_layout_engine.py +182 -182
  44. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/tui/mcp_dialog.py +627 -627
  45. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/tui/miniagents_dialog.py +369 -369
  46. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/tui/model_dialog.py +1789 -1909
  47. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/tui/publish_dialog.py +30 -30
  48. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/tui/quota_dialog.py +36 -36
  49. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/tui/quota_redeem_dialog.py +181 -181
  50. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/tui/quota_share_dialog.py +491 -491
  51. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/tui/resume_dialog.py +147 -147
  52. utim_cli-2.2.0/utim_cli/tui/rewards_tui.py +224 -0
  53. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/tui/skills_dialog.py +320 -320
  54. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/tui/subagents_dialog.py +314 -314
  55. utim_cli-2.2.0/utim_cli/tui/thinking_display.py +137 -0
  56. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/tui/tools_dialog.py +248 -248
  57. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/tui/update_dialog.py +2 -2
  58. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/utim.py +269 -137
  59. utim_cli-2.2.0/utim_cli/wheel.py +26 -0
  60. {utim_cli-2.1.2 → utim_cli-2.2.0/utim_cli.egg-info}/PKG-INFO +1 -1
  61. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli.egg-info/SOURCES.txt +9 -0
  62. utim_cli-2.1.2/utim_cli/server/micro_batcher.py +0 -95
  63. utim_cli-2.1.2/utim_cli/situational_scoring.py +0 -281
  64. {utim_cli-2.1.2 → utim_cli-2.2.0}/LICENSE +0 -0
  65. {utim_cli-2.1.2 → utim_cli-2.2.0}/MANIFEST.in +0 -0
  66. {utim_cli-2.1.2 → utim_cli-2.2.0}/README.md +0 -0
  67. {utim_cli-2.1.2 → utim_cli-2.2.0}/setup.cfg +0 -0
  68. {utim_cli-2.1.2 → utim_cli-2.2.0}/setup.py +0 -0
  69. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/_check_obj.py +0 -0
  70. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/_find_mcp.py +0 -0
  71. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/_find_plan.py +0 -0
  72. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/_find_tool.py +0 -0
  73. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/_patch_multimodal.py +0 -0
  74. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/_test_regex.py +0 -0
  75. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/backup.py +0 -0
  76. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/billing.py +0 -0
  77. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/blender_agent.py +0 -0
  78. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/bootstrap.py +0 -0
  79. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/client_utils.py +0 -0
  80. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/config.py +0 -0
  81. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/constants.py +0 -0
  82. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/context_pruner.py +0 -0
  83. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/doctor.py +0 -0
  84. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/harbor.py +0 -0
  85. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/knowledge_graph.py +0 -0
  86. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/local_db.py +0 -0
  87. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/logger.py +0 -0
  88. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/mcp_client.py +0 -0
  89. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/mcp_registry.json +0 -0
  90. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/report.py +0 -0
  91. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/scrapy_search.py +0 -0
  92. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/__init__.py +0 -0
  93. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/attribution.py +0 -0
  94. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/audit_log.py +0 -0
  95. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/auth.py +0 -0
  96. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/docs_md/SECRET_PROVISIONING.md +0 -0
  97. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/docs_md/about.md +0 -0
  98. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/docs_md/changelog.md +0 -0
  99. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/docs_md/docs.md +0 -0
  100. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/docs_md/features.md +0 -0
  101. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/docs_md/license.md +0 -0
  102. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/docs_md/pricing.md +0 -0
  103. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/docs_md/privacy.md +0 -0
  104. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/docs_md/refund.md +0 -0
  105. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/docs_md/support.md +0 -0
  106. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/docs_md/terms.md +0 -0
  107. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/exchange_rate.py +0 -0
  108. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/firebase.py +0 -0
  109. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/fix_duplicate_users.py +0 -0
  110. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/history.py +0 -0
  111. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/logging_config.py +0 -0
  112. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/pricing_updater.py +0 -0
  113. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/provision_build.py +0 -0
  114. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/rate_limit.py +0 -0
  115. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/routes/feedback_routes.py +0 -0
  116. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/routes/quota_share_routes.py +0 -0
  117. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/routes/referral_routes.py +0 -0
  118. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/server/routes/session_routes.py +0 -0
  119. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/state.py +0 -0
  120. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/tui/__init__.py +0 -0
  121. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/utilities.py +0 -0
  122. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/vector_memory.py +0 -0
  123. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli/workspace.py +0 -0
  124. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli.egg-info/dependency_links.txt +0 -0
  125. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli.egg-info/entry_points.txt +0 -0
  126. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli.egg-info/requires.txt +0 -0
  127. {utim_cli-2.1.2 → utim_cli-2.2.0}/utim_cli.egg-info/top_level.txt +0 -0
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.2.0] - 2026-08-13
4
+
5
+ ### 🛠️ Fixes
6
+ - Modified interface
7
+
3
8
  ## [2.1.2] - 2026-08-10
4
9
 
5
10
  ### 🛠️ Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: utim-cli
3
- Version: 2.1.2
3
+ Version: 2.2.0
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "utim-cli"
7
- version = "2.1.2"
7
+ version = "2.2.0"
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"
@@ -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.2"
8
+ __version__ = "2.2.0"
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.2"
14
+ VERSION = "2.2.0"
@@ -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: