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.
- {utim_cli-1.48.4 → utim_cli-2.0.0}/CHANGELOG.md +37 -0
- {utim_cli-1.48.4/utim_cli.egg-info → utim_cli-2.0.0}/PKG-INFO +1 -1
- {utim_cli-1.48.4 → utim_cli-2.0.0}/pyproject.toml +1 -1
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/__init__.py +1 -1
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/_version.py +1 -1
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/agent.py +16 -17
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/auth.py +19 -117
- utim_cli-2.0.0/utim_cli/billing.py +40 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/bootstrap.py +21 -9
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/client_utils.py +159 -87
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/config.py +14 -2
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/constants.py +0 -2
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/context_pruner.py +1 -1
- utim_cli-2.0.0/utim_cli/harbor.py +140 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/orchestrator.py +1035 -406
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/reflection.py +36 -8
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/auth.py +42 -1
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/db.py +240 -3
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/docs_md/docs.md +2 -3
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/models.py +531 -27
- utim_cli-2.0.0/utim_cli/server/pricing_updater.py +148 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/router.py +64 -3
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/routes/__init__.py +2 -1
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/routes/completion_routes.py +123 -37
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/routes/feedback_routes.py +4 -4
- utim_cli-2.0.0/utim_cli/server/routes/marketplace_routes.py +1430 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/routes/quota_routes.py +3 -3
- utim_cli-2.0.0/utim_cli/server/routes/security_routes.py +427 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/routes/share_routes.py +216 -211
- utim_cli-2.0.0/utim_cli/server/storage_nodes.py +439 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/tools.py +4909 -4784
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/tui/feedback_dialog.py +5 -9
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/tui/history_dialog.py +25 -56
- utim_cli-2.0.0/utim_cli/tui/marketplace_app_state.py +63 -0
- utim_cli-2.0.0/utim_cli/tui/marketplace_dialog.py +4180 -0
- utim_cli-2.0.0/utim_cli/tui/marketplace_layout_engine.py +182 -0
- utim_cli-2.0.0/utim_cli/tui/miniagents_dialog.py +369 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/tui/model_dialog.py +249 -41
- utim_cli-2.0.0/utim_cli/tui/publish_dialog.py +974 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/tui/resume_dialog.py +18 -7
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/tui/skills_dialog.py +66 -39
- utim_cli-2.0.0/utim_cli/tui/subagents_dialog.py +314 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/tui/tools_dialog.py +9 -3
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/utim.py +1183 -255
- {utim_cli-1.48.4 → utim_cli-2.0.0/utim_cli.egg-info}/PKG-INFO +1 -1
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli.egg-info/SOURCES.txt +11 -0
- utim_cli-1.48.4/utim_cli/server/pricing_updater.py +0 -100
- {utim_cli-1.48.4 → utim_cli-2.0.0}/LICENSE +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/MANIFEST.in +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/README.md +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/setup.cfg +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/setup.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/backup.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/blender_agent.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/doctor.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/knowledge_graph.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/local_db.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/logger.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/mcp_clean_wrapper.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/mcp_client.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/mcp_registry.json +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/report.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/scrapy_search.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/__init__.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/docs_md/about.md +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/docs_md/changelog.md +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/docs_md/features.md +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/docs_md/license.md +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/docs_md/pricing.md +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/docs_md/privacy.md +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/docs_md/refund.md +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/docs_md/support.md +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/docs_md/terms.md +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/email_utils.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/exchange_rate.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/firebase.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/history.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/logging_config.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/rate_limit.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/routes/auth_routes.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/routes/credit_routes.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/routes/quota_share_routes.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/routes/referral_routes.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/routes/session_routes.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/server/server.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/share.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/share_tui.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/situational_scoring.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/state.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/tui/__init__.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/tui/mcp_dialog.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/tui/quota_dialog.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/tui/quota_redeem_dialog.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/tui/quota_share_dialog.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/tui/update_dialog.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/utilities.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/vector_memory.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli/workspace.py +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli.egg-info/dependency_links.txt +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli.egg-info/entry_points.txt +0 -0
- {utim_cli-1.48.4 → utim_cli-2.0.0}/utim_cli.egg-info/requires.txt +0 -0
- {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
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "utim-cli"
|
|
7
|
-
version = "
|
|
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"
|
|
@@ -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
|
-
|
|
132
|
-
import platform
|
|
130
|
+
time.sleep(0.5)
|
|
133
131
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
139
|
+
# Restart UTIM right in the current terminal window
|
|
140
|
+
if _os.name == "nt":
|
|
140
141
|
try:
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 = [
|
|
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
|
-
|
|
276
|
-
if
|
|
277
|
-
|
|
278
|
-
|
|
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
|
|