utim-cli 1.48.4__tar.gz → 2.0.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 (102) hide show
  1. {utim_cli-1.48.4 → utim_cli-2.0.0}/CHANGELOG.md +37 -0
  2. {utim_cli-1.48.4/utim_cli.egg-info → utim_cli-2.0.0}/PKG-INFO +1 -1
  3. {utim_cli-1.48.4 → utim_cli-2.0.0}/pyproject.toml +1 -1
  4. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/__init__.py +1 -1
  5. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/_version.py +1 -1
  6. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/agent.py +16 -17
  7. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/auth.py +19 -117
  8. utim_cli-2.0.0/utim_cli/billing.py +40 -0
  9. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/bootstrap.py +21 -9
  10. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/client_utils.py +159 -87
  11. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/config.py +14 -2
  12. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/constants.py +0 -2
  13. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/context_pruner.py +1 -1
  14. utim_cli-2.0.0/utim_cli/harbor.py +140 -0
  15. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/orchestrator.py +1035 -406
  16. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/reflection.py +36 -8
  17. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/auth.py +42 -1
  18. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/db.py +240 -3
  19. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/docs_md/docs.md +2 -3
  20. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/models.py +531 -27
  21. utim_cli-2.0.0/utim_cli/server/pricing_updater.py +148 -0
  22. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/router.py +64 -3
  23. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/routes/__init__.py +2 -1
  24. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/routes/completion_routes.py +123 -37
  25. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/routes/feedback_routes.py +4 -4
  26. utim_cli-2.0.0/utim_cli/server/routes/marketplace_routes.py +1430 -0
  27. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/routes/quota_routes.py +3 -3
  28. utim_cli-2.0.0/utim_cli/server/routes/security_routes.py +427 -0
  29. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/routes/share_routes.py +216 -211
  30. utim_cli-2.0.0/utim_cli/server/storage_nodes.py +439 -0
  31. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/tools.py +4909 -4784
  32. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/tui/feedback_dialog.py +5 -9
  33. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/tui/history_dialog.py +25 -56
  34. utim_cli-2.0.0/utim_cli/tui/marketplace_app_state.py +63 -0
  35. utim_cli-2.0.0/utim_cli/tui/marketplace_dialog.py +4180 -0
  36. utim_cli-2.0.0/utim_cli/tui/marketplace_layout_engine.py +182 -0
  37. utim_cli-2.0.0/utim_cli/tui/miniagents_dialog.py +369 -0
  38. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/tui/model_dialog.py +249 -41
  39. utim_cli-2.0.0/utim_cli/tui/publish_dialog.py +974 -0
  40. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/tui/resume_dialog.py +18 -7
  41. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/tui/skills_dialog.py +66 -39
  42. utim_cli-2.0.0/utim_cli/tui/subagents_dialog.py +314 -0
  43. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/tui/tools_dialog.py +9 -3
  44. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/utim.py +1183 -255
  45. {utim_cli-1.48.4 → utim_cli-2.0.0/utim_cli.egg-info}/PKG-INFO +1 -1
  46. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli.egg-info/SOURCES.txt +11 -0
  47. utim_cli-1.48.4/utim_cli/server/pricing_updater.py +0 -100
  48. {utim_cli-1.48.4 → utim_cli-2.0.0}/LICENSE +0 -0
  49. {utim_cli-1.48.4 → utim_cli-2.0.0}/MANIFEST.in +0 -0
  50. {utim_cli-1.48.4 → utim_cli-2.0.0}/README.md +0 -0
  51. {utim_cli-1.48.4 → utim_cli-2.0.0}/setup.cfg +0 -0
  52. {utim_cli-1.48.4 → utim_cli-2.0.0}/setup.py +0 -0
  53. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/backup.py +0 -0
  54. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/blender_agent.py +0 -0
  55. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/doctor.py +0 -0
  56. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/knowledge_graph.py +0 -0
  57. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/local_db.py +0 -0
  58. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/logger.py +0 -0
  59. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/mcp_clean_wrapper.py +0 -0
  60. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/mcp_client.py +0 -0
  61. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/mcp_registry.json +0 -0
  62. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/report.py +0 -0
  63. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/scrapy_search.py +0 -0
  64. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/__init__.py +0 -0
  65. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/docs_md/about.md +0 -0
  66. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/docs_md/changelog.md +0 -0
  67. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/docs_md/features.md +0 -0
  68. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/docs_md/license.md +0 -0
  69. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/docs_md/pricing.md +0 -0
  70. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/docs_md/privacy.md +0 -0
  71. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/docs_md/refund.md +0 -0
  72. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/docs_md/support.md +0 -0
  73. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/docs_md/terms.md +0 -0
  74. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/email_utils.py +0 -0
  75. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/exchange_rate.py +0 -0
  76. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/firebase.py +0 -0
  77. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/history.py +0 -0
  78. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/logging_config.py +0 -0
  79. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/rate_limit.py +0 -0
  80. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/routes/auth_routes.py +0 -0
  81. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/routes/credit_routes.py +0 -0
  82. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/routes/quota_share_routes.py +0 -0
  83. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/routes/referral_routes.py +0 -0
  84. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/routes/session_routes.py +0 -0
  85. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/server.py +0 -0
  86. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/share.py +0 -0
  87. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/share_tui.py +0 -0
  88. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/situational_scoring.py +0 -0
  89. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/state.py +0 -0
  90. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/tui/__init__.py +0 -0
  91. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/tui/mcp_dialog.py +0 -0
  92. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/tui/quota_dialog.py +0 -0
  93. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/tui/quota_redeem_dialog.py +0 -0
  94. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/tui/quota_share_dialog.py +0 -0
  95. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/tui/update_dialog.py +0 -0
  96. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/utilities.py +0 -0
  97. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/vector_memory.py +0 -0
  98. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/workspace.py +0 -0
  99. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli.egg-info/dependency_links.txt +0 -0
  100. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli.egg-info/entry_points.txt +0 -0
  101. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli.egg-info/requires.txt +0 -0
  102. {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli.egg-info/top_level.txt +0 -0
@@ -1,7 +1,44 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.0.0] - 2026-08-04 - UTIM V2.0 RELEASE
4
+
5
+ ### 🚀 Major Release: UTIM V2.0 — Creators Ecosystem & Adaptive Intelligence
6
+
7
+ UTIM V2.0 brings a massive architectural leap, introducing the global Creators Marketplace ecosystem, dynamic context compression scaling, script-based miniagents, and modular workspace skills.
8
+
9
+ ### 🏪 Creators Marketplace
10
+ - **Creators Ecosystem**: Browse, search, install, purchase, and publish custom skills and script-based miniagents globally directly from the CLI (`/marketplace`).
11
+ - **Publisher Profile & Wallet**: Built-in profile management (`display_name`, `bio`, `avatar_emoji`), earnings tracking, UPI/Bank payout withdrawal requests, and admin approval workflows.
12
+ - **Action-Gated Profile Setup**: Instant unauthenticated browsing for all users. Profile setup is required strictly when purchasing, reviewing, installing, or publishing extensions.
13
+ - **Clean Full-Screen Dialogs**: Upgraded all text prompt inputs to full-screen TUI buffers to prevent stdout text leakage into terminal scrollback.
14
+
15
+ ### 🧠 Dynamic Context Compression Based on Model's Context Window
16
+ - **Adaptive Compression Scaling**: Context compression automatically adjusts its periodic iteration interval based on the model's context window:
17
+ - **`< 200k` context**: every `10` iterations
18
+ - **`200k – 500k` context**: every `20` iterations
19
+ - **`500k – 1M` context**: every `30` iterations
20
+ - **`1M+` context**: every `45` iterations
21
+ - **Real-Time Model Detection**: Switching models dynamically recalculates both compression thresholds and iteration intervals in real-time.
22
+ - **Folder Size-Based Miniagent Model Selection**: Miniagents dynamically select primary models based on package folder size (`inclusionai/ling-3.0-flash:free` for <100KB, `nvidia/nemotron-3-ultra-550b-a55b:free` for 100-150KB, `deepseek/deepseek-v4-flash-0731` for 150-300KB, and `openai/gpt-5.6-luna-pro` for ≥300KB).
23
+
24
+ ### 🤖 Create Miniagents
25
+ - **Interactive Miniagents Manager**: Full TUI wizard (`/miniagents`) to view, design, test, and delete script-based executable miniagent tools.
26
+ - **`@miniagents` Prompt Integration**: AI-assisted architecture prompt guides UTIM to craft complete `agent.json`, `agent.py` / `agent.js`, and `README.md` files.
27
+
28
+ ### 🎯 Create Skills
29
+ - **Modular Workspace Skills**: Interactive skills manager (`/skills`) to author domain-specific guidance packs (`SKILL.md`).
30
+ - **`@skills` Tagging & Context Injection**: Tag `@<skill>` or `@skills` to inject context-aware guidelines into LLM prompts on demand.
31
+ - **Strict Skill Validation**: Enforcement of strict directory structure for published skills (ONLY `SKILL.md` and `README.md` allowed; no extra files or subdirectories).
32
+ - **Skill Security Scanning Suite**: Configured `inclusionai/ling-3.0-flash:free` (primary) and `deepseek/deepseek-v4-flash-0731` (fallback) for skill verification checks.
33
+
34
+ ## [1.48.5] - 2026-07-25
35
+
36
+ ### Fixed
37
+ - **Minor Bug Fixes & Modifications**: Resolved TUI thinking indicator rendering issues, fixed dialog loop upon cancellation, improved chat tag distinction with timestamps, and fixed per-session usage tracking across conversation resumes and web UI displays.
38
+
3
39
  ## [1.48.4] - 2026-07-25
4
40
 
41
+ ### Fixed
5
42
  - **Minor Bug Fixes**: Under-the-hood optimization and resolution of minor dependency issues.
6
43
 
7
44
  ## [1.48.3] - 2026-07-25
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: utim-cli
3
- Version: 1.48.4
3
+ Version: 2.0.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 = "1.48.4"
7
+ version = "2.0.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__ = "1.48.4"
8
+ __version__ = "2.0.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 = "1.48.4"
14
+ VERSION = "2.0.0"
@@ -1,8 +1,8 @@
1
1
  import os
2
- import json
3
- import sys
4
- import time
5
- from .tools import get_tools
2
+ import json
3
+ import sys
4
+ import time
5
+ from .tools import get_tools
6
6
  from rich.console import Console
7
7
  from rich.live import Live
8
8
  from rich.spinner import Spinner
@@ -26,7 +26,6 @@ TOOL_META = {
26
26
  "delete_file": {"icon": "🗑️ ", "verb": "DeleteFile", "color": PURPLE},
27
27
  "run_command": {"icon": "⚡", "verb": "RunCommand", "color": YELLOW},
28
28
  "list_directory": {"icon": "📁", "verb": "ReadFolder", "color": PURPLE},
29
- "blender_create_object": {"icon": "🧊", "verb": "Create3D", "color": PURPLE},
30
29
  }
31
30
 
32
31
  TIPS = [
@@ -51,18 +50,18 @@ class ReActAgent:
51
50
  if not api_key:
52
51
  self.console.print("[dim]⚠ Warning: OPENROUTER_API_KEY is not set.[/dim]")
53
52
 
54
- try:
55
- from openai import OpenAI
56
- except ImportError as exc:
57
- raise RuntimeError(
58
- "The legacy ReActAgent requires the optional OpenAI SDK. "
59
- "Install it with: pip install 'utim-cli[legacy-agent]'"
60
- ) from exc
61
-
62
- self.client = OpenAI(
63
- base_url="https://openrouter.ai/api/v1",
64
- api_key=api_key or "sk-fake-key-for-init",
65
- )
53
+ try:
54
+ from openai import OpenAI
55
+ except ImportError as exc:
56
+ raise RuntimeError(
57
+ "The legacy ReActAgent requires the optional OpenAI SDK. "
58
+ "Install it with: pip install 'utim-cli[legacy-agent]'"
59
+ ) from exc
60
+
61
+ self.client = OpenAI(
62
+ base_url="https://openrouter.ai/api/v1",
63
+ api_key=api_key or "sk-fake-key-for-init",
64
+ )
66
65
  self.messages = [{"role": "system", "content": system_prompt}]
67
66
  self.start_time = None
68
67
 
@@ -90,7 +90,7 @@ class _AuthCallbackHandler(http.server.BaseHTTPRequestHandler):
90
90
 
91
91
  # ── Public login() function ───────────────────────────────────────────────────
92
92
 
93
- def login() -> None:
93
+ def login(restart: bool = True) -> None:
94
94
  """
95
95
  Sign in via the Device Authorization Flow.
96
96
 
@@ -122,131 +122,31 @@ def login() -> None:
122
122
  else:
123
123
  args.append(arg)
124
124
 
125
- # Resolve target command
126
125
  if args[0].strip('"').lower().endswith("utim.py"):
127
126
  cmd = [sys.executable, "-m", "utim_cli.utim"] + args[1:]
128
127
  else:
129
128
  cmd = [args[0].strip('"')] + args[1:]
130
129
 
131
- import subprocess
132
- import platform
130
+ time.sleep(0.5)
133
131
 
134
- cwd = _os.getcwd()
135
- cmd_str = subprocess.list2cmdline(cmd)
136
- system = platform.system()
137
- launched = False
132
+ # Clear current terminal screen & scrollback buffer
133
+ if _os.name == "nt":
134
+ _os.system("cls")
135
+ else:
136
+ sys.__stdout__.write("\x1b[2J\x1b[3J\x1b[H")
137
+ sys.__stdout__.flush()
138
138
 
139
- if system == "Windows":
139
+ # Restart UTIM right in the current terminal window
140
+ if _os.name == "nt":
140
141
  try:
141
- # Run cmd.exe to call start command which spawns a new window (keep open via /k)
142
- run_cmd = f'start cmd.exe /k "{cmd_str}"'
143
- subprocess.Popen(run_cmd, shell=True, cwd=cwd)
144
- raw_print(f"\n {GREEN}{BOLD}✓ Authorized! Opened UTIM in a new terminal window.{RESET}\n")
145
- launched = True
142
+ code = _sub.call(cmd)
143
+ sys.exit(code)
146
144
  except Exception:
147
145
  pass
148
- elif system == "Darwin":
149
- try:
150
- escaped_cwd = cwd.replace('"', '\\"')
151
- escaped_cmd = cmd_str.replace('"', '\\"')
152
- osa_script = f'tell application "Terminal" to do script "cd \\"{escaped_cwd}\\" && {escaped_cmd}"'
153
- subprocess.Popen(["osascript", "-e", osa_script])
154
- raw_print(f"\n {GREEN}{BOLD}✓ Authorized! Opened UTIM in a new Terminal window.{RESET}\n")
155
- launched = True
156
- except Exception:
157
- pass
158
- else:
159
- # Linux: try gnome-terminal, konsole, xterm
160
- for term in ["gnome-terminal", "konsole", "xterm"]:
161
- try:
162
- if term == "gnome-terminal":
163
- subprocess.Popen(["gnome-terminal", "--working-directory", cwd, "--", "bash", "-c", cmd_str])
164
- elif term == "konsole":
165
- subprocess.Popen(["konsole", "--workdir", cwd, "-e", "bash", "-c", cmd_str])
166
- else:
167
- subprocess.Popen(["xterm", "-e", f"cd '{cwd}' && {cmd_str}"])
168
- raw_print(f"\n {GREEN}{BOLD}✓ Authorized! Opened UTIM in a new terminal window.{RESET}\n")
169
- launched = True
170
- break
171
- except Exception:
172
- continue
173
-
174
- if launched:
175
- if not _IS_TEST:
176
- # Close the previous terminal tab/window
177
- if system == "Windows":
178
- try:
179
- # Traverse up process tree to find parent shell (powershell, cmd, node)
180
- import ctypes
181
- import signal
182
- TH32CS_SNAPPROCESS = 0x00000002
183
- class PROCESSENTRY32(ctypes.Structure):
184
- _fields_ = [("dwSize", ctypes.c_ulong),
185
- ("cntUsage", ctypes.c_ulong),
186
- ("th32ProcessID", ctypes.c_ulong),
187
- ("th32DefaultHeapID", ctypes.c_void_p),
188
- ("th32ModuleID", ctypes.c_ulong),
189
- ("cntThreads", ctypes.c_ulong),
190
- ("th32ParentProcessID", ctypes.c_ulong),
191
- ("pcPriClassBase", ctypes.c_long),
192
- ("dwFlags", ctypes.c_ulong),
193
- ("szExeFile", ctypes.c_char * 260)]
194
-
195
- hProcessSnap = ctypes.windll.kernel32.CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
196
- if hProcessSnap != -1:
197
- pe32 = PROCESSENTRY32()
198
- pe32.dwSize = ctypes.sizeof(PROCESSENTRY32)
199
- parent_map = {}
200
- if ctypes.windll.kernel32.Process32First(hProcessSnap, ctypes.byref(pe32)):
201
- while True:
202
- name = pe32.szExeFile.decode('utf-8', errors='ignore').lower()
203
- parent_map[pe32.th32ProcessID] = (pe32.th32ParentProcessID, name)
204
- if not ctypes.windll.kernel32.Process32Next(hProcessSnap, ctypes.byref(pe32)):
205
- break
206
- ctypes.windll.kernel32.CloseHandle(hProcessSnap)
207
-
208
- curr = _os.getpid()
209
- to_kill = []
210
- for _ in range(10):
211
- if curr not in parent_map:
212
- break
213
- parent_id, name = parent_map[curr]
214
- if any(sh in name for sh in ["powershell", "pwsh", "cmd.exe", "node.exe", "bash"]):
215
- to_kill.append(curr)
216
- if "explorer" in name:
217
- break
218
- curr = parent_id
219
-
220
- for pid in reversed(to_kill):
221
- try:
222
- _os.kill(pid, signal.SIGTERM)
223
- except Exception:
224
- pass
225
- except Exception:
226
- pass
227
- elif system == "Darwin":
228
- try:
229
- osa_script = 'tell application "Terminal" to close current tab of front window'
230
- subprocess.Popen(["osascript", "-e", osa_script])
231
- except Exception:
232
- pass
233
- else:
234
- try:
235
- import signal
236
- _os.kill(_os.getppid(), signal.SIGHUP)
237
- except Exception:
238
- pass
239
- sys.exit(0)
240
- else:
241
- # Fallback to in-place restart
242
- raw_print(f" {DIM}Restarting UTIM CLI to load configuration…{RESET}\n")
243
- if _os.name == "nt":
244
- try:
245
- code = subprocess.call(cmd)
246
- sys.exit(code)
247
- except Exception:
248
- pass
146
+ try:
249
147
  _os.execv(cmd[0], cmd)
148
+ except Exception:
149
+ sys.exit(0)
250
150
 
251
151
  # Sleep briefly to ensure prompt_toolkit's terminal-suspension sequences are flushed
252
152
  time.sleep(0.3)
@@ -340,7 +240,8 @@ def login() -> None:
340
240
  raw_print(f"\n {GREEN}✓ Authorized! Welcome back.{RESET}")
341
241
  raw_print(f" Signed in as {BLUE}{body.get('email', '')}{RESET}\n")
342
242
 
343
- restart_process()
243
+ if restart:
244
+ restart_process()
344
245
  return
345
246
  else:
346
247
  raw_print(f"\n \033[1;31m✗ Invalid API Key. Please try again.\033[0m\n")
@@ -371,7 +272,8 @@ def login() -> None:
371
272
  raw_print(f" Signed in as {BLUE}{body.get('email', '')}{RESET}")
372
273
  raw_print(f" Credits: {body.get('credits', 0):,.0f} UTIM\n")
373
274
 
374
- restart_process()
275
+ if restart:
276
+ restart_process()
375
277
  return
376
278
  else:
377
279
  raw_print(f" {YELLOW}⚠ Authorization pending. Please complete it at: {BLUE}{verify_url}{RESET}")
@@ -0,0 +1,40 @@
1
+ import json
2
+ import os
3
+
4
+ class CreditManager:
5
+ def __init__(self, config_path=None):
6
+ if config_path is None:
7
+ config_path = os.path.join(".utim_tmp", "utim_state.json")
8
+ self.config_path = config_path
9
+ self.costs = {
10
+ "gemini": 1, # 1 credit per 1000 tokens
11
+ "claude": 15, # 15 credits per 1000 tokens (more expensive)
12
+ "codex": 10 # 10 credits per 1000 tokens
13
+ }
14
+ self.state = self._load_state()
15
+
16
+ def _load_state(self):
17
+ if os.path.exists(self.config_path):
18
+ with open(self.config_path, "r") as f:
19
+ return json.load(f)
20
+ return {"credits": 0} # Start with 0 free credits
21
+
22
+ def save_state(self):
23
+ dir_name = os.path.dirname(self.config_path)
24
+ if dir_name:
25
+ os.makedirs(dir_name, exist_ok=True)
26
+ with open(self.config_path, "w") as f:
27
+ json.dump(self.state, f, indent=4)
28
+
29
+ def has_credits(self, agent_name, estimate_tokens=1000):
30
+ cost = (estimate_tokens / 1000) * self.costs.get(agent_name, 1)
31
+ return self.state["credits"] >= cost
32
+
33
+ def deduct_credits(self, agent_name, actual_tokens):
34
+ cost = (actual_tokens / 1000) * self.costs.get(agent_name, 1)
35
+ self.state["credits"] -= cost
36
+ self.save_state()
37
+ return cost
38
+
39
+ def get_balance(self):
40
+ return self.state["credits"]
@@ -73,11 +73,11 @@ def _sync_global_experience(api_key: str, current_folder: str):
73
73
  def _write_skills_and_agents():
74
74
  """Create the empty skills/ directory inside ~/.utim/. Skills are built by the reflection engine."""
75
75
  try:
76
- skills_dir = Path('.utim_tmp/skills')
76
+ skills_dir = UTIM_DIR / 'skills'
77
77
  skills_dir.mkdir(parents=True, exist_ok=True)
78
78
  except Exception as e:
79
79
  from utim_cli.logger import log_error
80
- log_error("bootstrap", "Failed to create skills directory in .utim_tmp/", e)
80
+ log_error("bootstrap", "Failed to create skills directory in ~/.utim/", e)
81
81
 
82
82
  def _write_default_design_md(md_path: Path):
83
83
  content = """# Premium Web Design System (Aesthetics Cheat-Sheet)
@@ -130,7 +130,7 @@ def _write_utim_md():
130
130
 
131
131
  def scan_available_skills():
132
132
  """
133
- Scans ~/.utim/skills and .agents/skills for directories containing SKILL.md.
133
+ Scans ~/.utim/skills, .utim/skills, and .agents/skills for directories containing SKILL.md.
134
134
  Skills listed in config['disabled_skills'] are excluded.
135
135
  Returns:
136
136
  dict: A dictionary mapping skill_name to dict containing path, name, description, and keywords.
@@ -145,7 +145,15 @@ def scan_available_skills():
145
145
  disabled = []
146
146
 
147
147
  skills = {}
148
- paths_to_check = [Path('.utim_tmp/skills'), Path('.agents/skills')]
148
+ paths_to_check = [
149
+ UTIM_DIR / 'skills',
150
+ UTIM_DIR / 'agentskills',
151
+ Path.home() / '.utim' / 'skills',
152
+ Path.home() / '.utim' / 'agentskills',
153
+ Path('.utim/skills'),
154
+ Path('.utim/agentskills'),
155
+ Path('.agents/skills'),
156
+ ]
149
157
 
150
158
  for base_dir in paths_to_check:
151
159
  if not base_dir.exists():
@@ -263,7 +271,6 @@ def get_subagent_rag_context(subagent_name: str, query: str = "") -> str:
263
271
  try:
264
272
  # Map subagent name to the most relevant skill file
265
273
  mapping = {
266
- "project_res": "software-architecture",
267
274
  "plan_project": "llm-orchestration",
268
275
  "web_search": "cli-ux-patterns",
269
276
  "generate_image": "web-design-premium"
@@ -272,10 +279,15 @@ def get_subagent_rag_context(subagent_name: str, query: str = "") -> str:
272
279
  if not skill_name:
273
280
  return ""
274
281
 
275
- skill_path = Path('.utim_tmp/skills') / skill_name / 'SKILL.md'
276
- if not skill_path.exists():
277
- from pathlib import Path
278
- skill_path = Path('.agents/skills') / skill_name / 'SKILL.md'
282
+ skills = scan_available_skills()
283
+ if skill_name in skills:
284
+ skill_path = skills[skill_name]["path"]
285
+ else:
286
+ skill_path = UTIM_DIR / 'skills' / skill_name / 'SKILL.md'
287
+ if not skill_path.exists():
288
+ skill_path = UTIM_DIR / 'agentskills' / skill_name / 'SKILL.md'
289
+ if not skill_path.exists():
290
+ skill_path = Path('.agents/skills') / skill_name / 'SKILL.md'
279
291
  if not skill_path.exists():
280
292
  return ""
281
293