utim-cli 2.2.0__tar.gz → 2.2.2__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-2.2.0 → utim_cli-2.2.2}/CHANGELOG.md +17 -0
- {utim_cli-2.2.0/utim_cli.egg-info → utim_cli-2.2.2}/PKG-INFO +3 -4
- {utim_cli-2.2.0 → utim_cli-2.2.2}/pyproject.toml +4 -4
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/__init__.py +1 -1
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/_version.py +2 -1
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/bootstrap.py +155 -23
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/brain.py +1 -1
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/client_utils.py +1 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/config.py +2 -1
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/context_pruner.py +470 -40
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/harbor.py +75 -13
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/mcp_client.py +6 -1
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/orchestrator.py +448 -222
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/reflection.py +101 -90
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/auth.py +9 -3
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/cli_auth.py +29 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/db.py +62 -1
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/docs_md/refund.md +2 -2
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/email_utils.py +182 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/model_agent.py +90 -75
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/models.py +218 -72
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/pricing_updater.py +4 -8
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/rewards_engine.py +4 -2
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/router.py +176 -3
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/routes/__init__.py +6 -1
- utim_cli-2.2.2/utim_cli/server/routes/billing_routes.py +262 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/routes/completion_routes.py +10 -1
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/routes/credit_routes.py +33 -1
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/routes/quota_routes.py +48 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/routes/referral_routes.py +11 -2
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/routes/rewards_routes.py +47 -22
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/routes/share_routes.py +18 -12
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/share.py +165 -29
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/share_tui.py +154 -7
- utim_cli-2.2.2/utim_cli/subagent_manager.py +724 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/tools.py +124 -27
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/tui/feedback_dialog.py +47 -47
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/tui/history_dialog.py +213 -211
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/tui/mcp_dialog.py +16 -14
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/tui/model_dialog.py +137 -158
- utim_cli-2.2.2/utim_cli/tui/quota_dialog.py +72 -0
- utim_cli-2.2.2/utim_cli/tui/quota_redeem_dialog.py +207 -0
- utim_cli-2.2.2/utim_cli/tui/quota_share_dialog.py +446 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/tui/rewards_tui.py +15 -4
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/tui/skills_dialog.py +154 -138
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/tui/subagents_dialog.py +15 -15
- utim_cli-2.2.2/utim_cli/tui/tools_dialog.py +273 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/utim.py +53 -84
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/wheel.py +1 -2
- {utim_cli-2.2.0 → utim_cli-2.2.2/utim_cli.egg-info}/PKG-INFO +3 -4
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli.egg-info/SOURCES.txt +1 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli.egg-info/requires.txt +0 -1
- utim_cli-2.2.0/utim_cli/subagent_manager.py +0 -366
- utim_cli-2.2.0/utim_cli/tui/quota_dialog.py +0 -36
- utim_cli-2.2.0/utim_cli/tui/quota_redeem_dialog.py +0 -181
- utim_cli-2.2.0/utim_cli/tui/quota_share_dialog.py +0 -491
- utim_cli-2.2.0/utim_cli/tui/tools_dialog.py +0 -248
- {utim_cli-2.2.0 → utim_cli-2.2.2}/LICENSE +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/MANIFEST.in +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/README.md +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/setup.cfg +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/setup.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/_check_obj.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/_find_mcp.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/_find_plan.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/_find_tool.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/_patch_multimodal.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/_test_regex.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/agent.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/auth.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/backup.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/billing.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/blender_agent.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/constants.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/doctor.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/knowledge_graph.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/local_db.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/logger.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/mcp_clean_wrapper.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/mcp_registry.json +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/report.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/scrapy_search.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/__init__.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/attribution.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/audit_log.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/batch_processor.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/captcha.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/docs_md/SECRET_PROVISIONING.md +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/docs_md/about.md +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/docs_md/changelog.md +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/docs_md/docs.md +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/docs_md/features.md +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/docs_md/license.md +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/docs_md/pricing.md +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/docs_md/privacy.md +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/docs_md/support.md +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/docs_md/terms.md +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/exchange_rate.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/firebase.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/fix_duplicate_users.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/history.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/local_llm.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/logging_config.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/micro_batcher.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/provision_build.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/rate_limit.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/routes/auth_routes.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/routes/feedback_routes.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/routes/marketplace_routes.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/routes/quota_share_routes.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/routes/security_routes.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/routes/session_routes.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/server.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/server/storage_nodes.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/situational_scoring.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/state.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/tui/__init__.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/tui/marketplace_app_state.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/tui/marketplace_dialog.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/tui/marketplace_layout_engine.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/tui/miniagents_dialog.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/tui/publish_dialog.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/tui/resume_dialog.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/tui/thinking_display.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/tui/update_dialog.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/utilities.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/vector_memory.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli/workspace.py +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli.egg-info/dependency_links.txt +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli.egg-info/entry_points.txt +0 -0
- {utim_cli-2.2.0 → utim_cli-2.2.2}/utim_cli.egg-info/top_level.txt +0 -0
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.2.2] - 2026-08-15
|
|
4
|
+
|
|
5
|
+
### 🚀 Features & Enhancements
|
|
6
|
+
- **Order History & Invoices**: Added comprehensive order history and tax invoice viewing with printable receipts on the pricing page.
|
|
7
|
+
- **Automated Refund Request Workflow**: Policy-enforced refund claim submission system with automatic admin email alerts and user autoresponder (3-day money-back guarantee window).
|
|
8
|
+
- **Zero-Flicker Configuration Dialogs**: Upgraded Tools, Model Settings, and Skills configuration dialogs to in-memory live toggling with single-commit on exit, eliminating screen flashes.
|
|
9
|
+
- **TUI Dialog Polish**: Refined `/rewind`, `/undo`, `/quotashare`, and `/redeem` interactive dialog workflows.
|
|
10
|
+
|
|
11
|
+
## [2.2.1] - 2026-08-13
|
|
12
|
+
|
|
13
|
+
### 🛠️ Fixes & Minor Polish
|
|
14
|
+
- **TUI Input Divider Line**: Updated input area horizontal divider line to extend full width across the terminal screen (`shutil.get_terminal_size().columns`) with line-wrap protection.
|
|
15
|
+
- **Subagent Swarm Visualizer Mobile Responsiveness**: Added mobile responsive breakpoint styles (`.st-swarm-canvas`), card container bounds, text truncation, and flex badge anchors (`DISPATCHER`, `DONE`, etc.) to prevent layout overflow on mobile screens.
|
|
16
|
+
- **OS-Specific Requirements Architecture**: Added dedicated lightweight requirement files (`requirements_android.txt`, `requirements_windows.txt`, `requirements_desktop.txt`, `requirements_full.txt`) and updated installer scripts (`install.sh`, `setup_utim1.py`) to select pure-Python requirements for Android/Termux without PyPI binary wheel dependencies.
|
|
17
|
+
- **Reflection Pipeline Primary Model**: Updated reflection and experience extraction model fallback chain to use `openrouter/free` as the primary model (`REFLECTION_PRIMARY_MODEL`).
|
|
18
|
+
- **Correction Fast-Path Engine**: Removed keyword-matching gate on correction detection, delegating judgment directly to the reflection LLM for zero false negatives.
|
|
19
|
+
|
|
3
20
|
## [2.2.0] - 2026-08-13
|
|
4
21
|
|
|
5
22
|
### 🛠️ Fixes
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: utim-cli
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.2
|
|
4
4
|
Summary: UTIM – Universal Terminal Intelligence Manager. A powerful agentic AI coding assistant for your terminal.
|
|
5
|
-
License:
|
|
5
|
+
License: Emend AI Proprietary EULA
|
|
6
6
|
Project-URL: Homepage, https://utim.dev
|
|
7
7
|
Project-URL: Documentation, https://utim.dev/docs
|
|
8
8
|
Project-URL: Changelog, https://github.com/emendai/utim/blob/main/CHANGELOG.md
|
|
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.10
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
-
Classifier: License ::
|
|
15
|
+
Classifier: License :: Other/Proprietary License
|
|
16
16
|
Classifier: Operating System :: OS Independent
|
|
17
17
|
Classifier: Development Status :: 5 - Production/Stable
|
|
18
18
|
Classifier: Environment :: Console
|
|
@@ -31,7 +31,6 @@ 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
|
|
35
34
|
Provides-Extra: search
|
|
36
35
|
Requires-Dist: scrapy>=2.11.0; extra == "search"
|
|
37
36
|
Requires-Dist: scrapy-playwright>=0.0.34; extra == "search"
|
|
@@ -4,18 +4,18 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "utim-cli"
|
|
7
|
-
version = "2.2.
|
|
7
|
+
version = "2.2.2"
|
|
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"
|
|
11
|
-
license = { text = "
|
|
11
|
+
license = { text = "Emend AI Proprietary EULA" }
|
|
12
12
|
classifiers = [
|
|
13
13
|
"Programming Language :: Python :: 3",
|
|
14
14
|
"Programming Language :: Python :: 3.9",
|
|
15
15
|
"Programming Language :: Python :: 3.10",
|
|
16
16
|
"Programming Language :: Python :: 3.11",
|
|
17
17
|
"Programming Language :: Python :: 3.12",
|
|
18
|
-
"License ::
|
|
18
|
+
"License :: Other/Proprietary License",
|
|
19
19
|
"Operating System :: OS Independent",
|
|
20
20
|
"Development Status :: 5 - Production/Stable",
|
|
21
21
|
"Environment :: Console",
|
|
@@ -33,7 +33,6 @@ 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",
|
|
37
36
|
]
|
|
38
37
|
|
|
39
38
|
[project.urls]
|
|
@@ -104,4 +103,5 @@ markers = [
|
|
|
104
103
|
"smoke: fast install validation tests (no live credentials required)",
|
|
105
104
|
"slow: tests that make network calls or take more than 10 seconds",
|
|
106
105
|
"integration: tests requiring live credentials or a running UTIM account",
|
|
106
|
+
"benchmark: UTIM benchmark suite — tests all 9 agent benchmark families",
|
|
107
107
|
]
|
|
@@ -146,13 +146,15 @@ def scan_available_skills():
|
|
|
146
146
|
|
|
147
147
|
skills = {}
|
|
148
148
|
paths_to_check = [
|
|
149
|
+
Path('.agents/skills'),
|
|
150
|
+
Path('.agentskills'),
|
|
151
|
+
Path('.agents'),
|
|
152
|
+
Path('.utim/skills'),
|
|
153
|
+
Path('.utim/agentskills'),
|
|
149
154
|
UTIM_DIR / 'skills',
|
|
150
155
|
UTIM_DIR / 'agentskills',
|
|
151
156
|
Path.home() / '.utim' / 'skills',
|
|
152
157
|
Path.home() / '.utim' / 'agentskills',
|
|
153
|
-
Path('.utim/skills'),
|
|
154
|
-
Path('.utim/agentskills'),
|
|
155
|
-
Path('.agents/skills'),
|
|
156
158
|
]
|
|
157
159
|
|
|
158
160
|
for base_dir in paths_to_check:
|
|
@@ -216,28 +218,156 @@ def scan_available_skills():
|
|
|
216
218
|
}
|
|
217
219
|
return skills
|
|
218
220
|
|
|
221
|
+
|
|
222
|
+
DOMAIN_INTENT_MAP = {
|
|
223
|
+
"terminal-ui-design": [
|
|
224
|
+
"tui", "rich", "console", "prompt_toolkit", "layout", "dialog", "menu",
|
|
225
|
+
"scrollbar", "terminal ui", "cli ui", "ansi", "color scheme", "quotashare", "redeem"
|
|
226
|
+
],
|
|
227
|
+
"software-architecture": [
|
|
228
|
+
"architecture", "refactor", "modular", "decouple", "state management",
|
|
229
|
+
"router", "fastapi", "sqlite", "orm", "dependency injection", "package structure", "module design"
|
|
230
|
+
],
|
|
231
|
+
"mcp-server-development": [
|
|
232
|
+
"mcp", "model context protocol", "mcp server", "stdio", "json-rpc", "jsonrpc",
|
|
233
|
+
"connection pool", "protocol wrapper", "tool handler", "tool server"
|
|
234
|
+
],
|
|
235
|
+
"async-python": [
|
|
236
|
+
"async", "asyncio", "coroutine", "event loop", "threadsafe", "subprocess stream",
|
|
237
|
+
"deadlock", "lock", "thread", "concurrent", "non-blocking", "await", "to_thread"
|
|
238
|
+
],
|
|
239
|
+
"llm-orchestration": [
|
|
240
|
+
"orchestrat", "llm loop", "prompt", "context prun", "compaction", "rag",
|
|
241
|
+
"token budget", "overclock", "memory retrieval", "react loop", "reflection", "system prompt",
|
|
242
|
+
"compression", "context envelope", "modelcontext"
|
|
243
|
+
],
|
|
244
|
+
"cli-ux-patterns": [
|
|
245
|
+
"cli ux", "user confirmation", "dry-run", "undo", "scroll pollution",
|
|
246
|
+
"tty", "non-interactive", "cli prompt", "command line experience"
|
|
247
|
+
],
|
|
248
|
+
"web-design-premium": [
|
|
249
|
+
"web design", "html", "css", "vite", "react", "frontend", "glassmorphism",
|
|
250
|
+
"landing page", "tailwind", "responsive", "hsl", "seo"
|
|
251
|
+
],
|
|
252
|
+
"creative-scrollytelling": [
|
|
253
|
+
"scrollytelling", "kinetic", "split-door", "viewport animation", "kinetic sweep"
|
|
254
|
+
],
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def rank_relevant_skills(
|
|
259
|
+
query_text: str,
|
|
260
|
+
skills: dict = None,
|
|
261
|
+
min_score: float = 14.0,
|
|
262
|
+
max_results: int = 2,
|
|
263
|
+
) -> list:
|
|
264
|
+
"""
|
|
265
|
+
High-precision BM25 & Domain-Intent Skill Ranker.
|
|
266
|
+
|
|
267
|
+
Prevents weak/random skill injection by evaluating:
|
|
268
|
+
1. Domain Intent Triggers: Matches architectural problem domains to canonical skills.
|
|
269
|
+
2. Exact Phrase & Slug Matching: High-weight bonus for exact multi-word / hyphenated concepts.
|
|
270
|
+
3. Inverse Document Frequency (IDF): Suppresses generic words ('python', 'code', 'data', 'user')
|
|
271
|
+
and elevates domain-specific technical terminology ('coroutine', 'prompt_toolkit', 'mcp').
|
|
272
|
+
4. Minimum Activation Threshold: Rejects any skill below min_score to prevent context pollution.
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
List of (skill_name, score) sorted in descending order of relevance.
|
|
276
|
+
"""
|
|
277
|
+
import math
|
|
278
|
+
import re
|
|
279
|
+
|
|
280
|
+
if not query_text or not query_text.strip():
|
|
281
|
+
return []
|
|
282
|
+
|
|
283
|
+
if skills is None:
|
|
284
|
+
skills = scan_available_skills()
|
|
285
|
+
if not skills:
|
|
286
|
+
return []
|
|
287
|
+
|
|
288
|
+
query_lower = query_text.lower()
|
|
289
|
+
query_tokens = [w for w in re.split(r"[^\w\d\-]+", query_lower) if len(w) >= 3]
|
|
290
|
+
if not query_tokens:
|
|
291
|
+
return []
|
|
292
|
+
|
|
293
|
+
# 1. Compute document frequency across all skills to calculate IDF
|
|
294
|
+
total_docs = len(skills)
|
|
295
|
+
doc_freq = {}
|
|
296
|
+
for s_name, s_info in skills.items():
|
|
297
|
+
doc_words = set(s_info.get("keywords", []))
|
|
298
|
+
for w in s_info.get("name", "").lower().split():
|
|
299
|
+
if len(w) >= 3:
|
|
300
|
+
doc_words.add(w)
|
|
301
|
+
for w in doc_words:
|
|
302
|
+
doc_freq[w] = doc_freq.get(w, 0) + 1
|
|
303
|
+
|
|
304
|
+
def _idf(word: str) -> float:
|
|
305
|
+
df = doc_freq.get(word, 0)
|
|
306
|
+
# Suppress over-represented words across >= 12 skills
|
|
307
|
+
if df >= 12:
|
|
308
|
+
return 0.1
|
|
309
|
+
return max(0.2, math.log((total_docs - df + 0.5) / (df + 0.5) + 1.0))
|
|
310
|
+
|
|
311
|
+
scored_skills = []
|
|
312
|
+
|
|
313
|
+
for skill_name, skill_info in skills.items():
|
|
314
|
+
score = 0.0
|
|
315
|
+
s_name_lower = skill_name.lower()
|
|
316
|
+
s_title_lower = skill_info.get("name", "").lower()
|
|
317
|
+
s_desc_lower = skill_info.get("description", "").lower()
|
|
318
|
+
|
|
319
|
+
# Extract token sets to ensure exact word matches (prevent substring false-positives like 'tell' in 'scrollytelling')
|
|
320
|
+
s_name_tokens = set(re.split(r"[^\w\d]+", s_name_lower))
|
|
321
|
+
s_title_tokens = set(re.split(r"[^\w\d]+", s_title_lower))
|
|
322
|
+
s_desc_tokens = set(re.split(r"[^\w\d]+", s_desc_lower))
|
|
323
|
+
|
|
324
|
+
# ── Check 1: Domain Intent Mapping (Authoritative Trigger Boost) ──
|
|
325
|
+
for target_skill, triggers in DOMAIN_INTENT_MAP.items():
|
|
326
|
+
if target_skill in s_name_lower:
|
|
327
|
+
for trig in triggers:
|
|
328
|
+
if re.search(r"\b" + re.escape(trig) + r"\b", query_lower):
|
|
329
|
+
score += 35.0
|
|
330
|
+
break
|
|
331
|
+
|
|
332
|
+
# ── Check 2: Exact Name / Hyphenated Slug Matching ───────────────
|
|
333
|
+
if re.search(r"\b" + re.escape(s_name_lower) + r"\b", query_lower):
|
|
334
|
+
score += 40.0
|
|
335
|
+
elif s_name_lower.replace("-", " ") in query_lower or s_name_lower.replace("_", " ") in query_lower:
|
|
336
|
+
score += 30.0
|
|
337
|
+
|
|
338
|
+
# ── Check 3: High-Entropy Keyword TF-IDF Matching ─────────────────
|
|
339
|
+
skill_kw = set(skill_info.get("keywords", []))
|
|
340
|
+
for tok in query_tokens:
|
|
341
|
+
if tok in skill_kw:
|
|
342
|
+
score += 8.0 * _idf(tok)
|
|
343
|
+
if tok in s_name_tokens or tok in s_title_tokens:
|
|
344
|
+
score += 6.0 * _idf(tok)
|
|
345
|
+
elif tok in s_desc_tokens:
|
|
346
|
+
score += 2.0 * _idf(tok)
|
|
347
|
+
|
|
348
|
+
# ── Check 4: Penalize Generic Auto-Learned Skills ────────────────
|
|
349
|
+
if any(prefix in s_name_lower for prefix in ["user-", "user_", "communication", "interaction", "conversation", "response-"]):
|
|
350
|
+
# Only allow if strongly and explicitly triggered
|
|
351
|
+
score *= 0.50
|
|
352
|
+
|
|
353
|
+
if score >= min_score:
|
|
354
|
+
scored_skills.append((skill_name, score))
|
|
355
|
+
|
|
356
|
+
scored_skills.sort(key=lambda x: x[1], reverse=True)
|
|
357
|
+
return scored_skills[:max_results]
|
|
358
|
+
|
|
359
|
+
|
|
219
360
|
def get_rag_context(user_prompt: str = "") -> str:
|
|
220
|
-
"""Get RAG context string for system prompt injection by matching user_prompt
|
|
361
|
+
"""Get RAG context string for system prompt injection by matching user_prompt using rank_relevant_skills."""
|
|
221
362
|
try:
|
|
222
363
|
from utim_cli.state import STATE
|
|
223
364
|
STATE["injected_contexts"] = []
|
|
224
365
|
|
|
225
366
|
skills = scan_available_skills()
|
|
226
|
-
|
|
227
|
-
user_prompt_lower = user_prompt.lower()
|
|
367
|
+
ranked = rank_relevant_skills(user_prompt, skills=skills, min_score=14.0, max_results=2)
|
|
228
368
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if any(kw in user_prompt_lower for kw in skill_info["keywords"]):
|
|
232
|
-
matched = True
|
|
233
|
-
if not matched:
|
|
234
|
-
if skill_name.replace("-", " ") in user_prompt_lower or skill_name in user_prompt_lower:
|
|
235
|
-
matched = True
|
|
236
|
-
if matched:
|
|
237
|
-
matched_skills.append(skill_name)
|
|
238
|
-
|
|
239
|
-
# If no keywords matched, default to AGENTS.md
|
|
240
|
-
if not matched_skills:
|
|
369
|
+
# If no skills passed threshold, default to AGENTS.md
|
|
370
|
+
if not ranked:
|
|
241
371
|
agents_path = UTIM_DIR / 'AGENTS.md'
|
|
242
372
|
if not agents_path.exists():
|
|
243
373
|
from pathlib import Path
|
|
@@ -250,7 +380,7 @@ def get_rag_context(user_prompt: str = "") -> str:
|
|
|
250
380
|
return ""
|
|
251
381
|
|
|
252
382
|
context = ""
|
|
253
|
-
for skill_name in
|
|
383
|
+
for skill_name, score in ranked:
|
|
254
384
|
skill_info = skills[skill_name]
|
|
255
385
|
content = skill_info["content"]
|
|
256
386
|
if content.startswith('---'):
|
|
@@ -258,7 +388,7 @@ def get_rag_context(user_prompt: str = "") -> str:
|
|
|
258
388
|
if len(parts) >= 3:
|
|
259
389
|
content = parts[2].strip()
|
|
260
390
|
STATE["injected_contexts"].append(content)
|
|
261
|
-
context += f"\n### RELEVANT CORE SKILL: {skill_name.upper()} ###\n{content[:1500]}\n"
|
|
391
|
+
context += f"\n### RELEVANT CORE SKILL: {skill_name.upper()} (Relevance: {score:.1f}) ###\n{content[:1500]}\n"
|
|
262
392
|
|
|
263
393
|
return context
|
|
264
394
|
except Exception as e:
|
|
@@ -283,11 +413,13 @@ def get_subagent_rag_context(subagent_name: str, query: str = "") -> str:
|
|
|
283
413
|
if skill_name in skills:
|
|
284
414
|
skill_path = skills[skill_name]["path"]
|
|
285
415
|
else:
|
|
286
|
-
skill_path =
|
|
416
|
+
skill_path = Path('.agents/skills') / skill_name / 'SKILL.md'
|
|
287
417
|
if not skill_path.exists():
|
|
288
|
-
skill_path =
|
|
418
|
+
skill_path = Path('.agentskills') / skill_name / 'SKILL.md'
|
|
289
419
|
if not skill_path.exists():
|
|
290
|
-
skill_path =
|
|
420
|
+
skill_path = UTIM_DIR / 'skills' / skill_name / 'SKILL.md'
|
|
421
|
+
if not skill_path.exists():
|
|
422
|
+
skill_path = UTIM_DIR / 'agentskills' / skill_name / 'SKILL.md'
|
|
291
423
|
if not skill_path.exists():
|
|
292
424
|
return ""
|
|
293
425
|
|
|
@@ -87,7 +87,7 @@ QWEN_BRAIN_MODEL = "qwen/qwen2.5-1.5b-instruct" # fast reasoning model on
|
|
|
87
87
|
try:
|
|
88
88
|
from utim_cli.reflection import REFLECTION_MODELS
|
|
89
89
|
except Exception:
|
|
90
|
-
REFLECTION_MODELS = [
|
|
90
|
+
REFLECTION_MODELS = ["openrouter/free", QWEN_BRAIN_MODEL]
|
|
91
91
|
|
|
92
92
|
# ── Paths ──────────────────────────────────────────────────────────────────────
|
|
93
93
|
|