utim-cli 1.48.2__tar.gz → 1.48.4__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 (90) hide show
  1. {utim_cli-1.48.2 → utim_cli-1.48.4}/CHANGELOG.md +11 -1
  2. {utim_cli-1.48.2/utim_cli.egg-info → utim_cli-1.48.4}/PKG-INFO +1 -1
  3. {utim_cli-1.48.2 → utim_cli-1.48.4}/pyproject.toml +1 -1
  4. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/__init__.py +1 -1
  5. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/_version.py +1 -1
  6. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/blender_agent.py +27 -134
  7. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/client_utils.py +5 -2
  8. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/orchestrator.py +87 -25
  9. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/auth.py +5 -0
  10. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/db.py +9 -21
  11. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/routes/completion_routes.py +406 -129
  12. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/routes/quota_routes.py +1 -1
  13. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/routes/quota_share_routes.py +2 -1
  14. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/routes/share_routes.py +0 -2
  15. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/tools.py +395 -25
  16. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/tui/quota_redeem_dialog.py +20 -3
  17. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/tui/quota_share_dialog.py +22 -4
  18. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/tui/tools_dialog.py +5 -26
  19. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/utim.py +34 -3
  20. {utim_cli-1.48.2 → utim_cli-1.48.4/utim_cli.egg-info}/PKG-INFO +1 -1
  21. {utim_cli-1.48.2 → utim_cli-1.48.4}/LICENSE +0 -0
  22. {utim_cli-1.48.2 → utim_cli-1.48.4}/MANIFEST.in +0 -0
  23. {utim_cli-1.48.2 → utim_cli-1.48.4}/README.md +0 -0
  24. {utim_cli-1.48.2 → utim_cli-1.48.4}/setup.cfg +0 -0
  25. {utim_cli-1.48.2 → utim_cli-1.48.4}/setup.py +0 -0
  26. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/agent.py +0 -0
  27. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/auth.py +0 -0
  28. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/backup.py +0 -0
  29. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/bootstrap.py +0 -0
  30. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/config.py +0 -0
  31. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/constants.py +0 -0
  32. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/context_pruner.py +0 -0
  33. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/doctor.py +0 -0
  34. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/knowledge_graph.py +0 -0
  35. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/local_db.py +0 -0
  36. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/logger.py +0 -0
  37. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/mcp_clean_wrapper.py +0 -0
  38. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/mcp_client.py +0 -0
  39. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/mcp_registry.json +0 -0
  40. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/reflection.py +0 -0
  41. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/report.py +0 -0
  42. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/scrapy_search.py +0 -0
  43. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/__init__.py +0 -0
  44. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/docs_md/about.md +0 -0
  45. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/docs_md/changelog.md +0 -0
  46. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/docs_md/docs.md +0 -0
  47. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/docs_md/features.md +0 -0
  48. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/docs_md/license.md +0 -0
  49. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/docs_md/pricing.md +0 -0
  50. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/docs_md/privacy.md +0 -0
  51. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/docs_md/refund.md +0 -0
  52. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/docs_md/support.md +0 -0
  53. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/docs_md/terms.md +0 -0
  54. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/email_utils.py +0 -0
  55. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/exchange_rate.py +0 -0
  56. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/firebase.py +0 -0
  57. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/history.py +0 -0
  58. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/logging_config.py +0 -0
  59. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/models.py +0 -0
  60. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/pricing_updater.py +0 -0
  61. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/rate_limit.py +0 -0
  62. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/router.py +0 -0
  63. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/routes/__init__.py +0 -0
  64. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/routes/auth_routes.py +0 -0
  65. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/routes/credit_routes.py +0 -0
  66. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/routes/feedback_routes.py +0 -0
  67. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/routes/referral_routes.py +0 -0
  68. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/routes/session_routes.py +0 -0
  69. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/server/server.py +0 -0
  70. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/share.py +0 -0
  71. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/share_tui.py +0 -0
  72. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/situational_scoring.py +0 -0
  73. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/state.py +0 -0
  74. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/tui/__init__.py +0 -0
  75. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/tui/feedback_dialog.py +0 -0
  76. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/tui/history_dialog.py +0 -0
  77. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/tui/mcp_dialog.py +0 -0
  78. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/tui/model_dialog.py +0 -0
  79. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/tui/quota_dialog.py +0 -0
  80. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/tui/resume_dialog.py +0 -0
  81. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/tui/skills_dialog.py +0 -0
  82. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/tui/update_dialog.py +0 -0
  83. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/utilities.py +0 -0
  84. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/vector_memory.py +0 -0
  85. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli/workspace.py +0 -0
  86. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli.egg-info/SOURCES.txt +0 -0
  87. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli.egg-info/dependency_links.txt +0 -0
  88. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli.egg-info/entry_points.txt +0 -0
  89. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli.egg-info/requires.txt +0 -0
  90. {utim_cli-1.48.2 → utim_cli-1.48.4}/utim_cli.egg-info/top_level.txt +0 -0
@@ -1,8 +1,18 @@
1
1
  # Changelog
2
2
 
3
- ## [1.48.2] - 2026-07-23
3
+ ## [1.48.4] - 2026-07-25
4
+
5
+ - **Minor Bug Fixes**: Under-the-hood optimization and resolution of minor dependency issues.
6
+
7
+ ## [1.48.3] - 2026-07-25
4
8
 
5
9
  ### Fixed
10
+ - **Blender 3D Tools**: Fully restored normal functionality and re-enabled the local Blender asset generation workflow and agent scripts.
11
+ - **Windows Shell Sanitization**: Resolved issues with Unix commands like `mkdir -p` and `rm -rf` failing on native Windows PowerShell by introducing automatic translation to `New-Item` and `Remove-Item`.
12
+ - **Command Redirection Safeguards**: Automatically blocks Bash-style here-docs (`<<`) on Windows with a helpful prompt to guide models to use proper file-write tools.
13
+ - **UI Abort and Confirmation Fixes**: Removed the 30-second background timeout on confirmation dialogs to prevent thread synchronization freeze states. Fixed signal-handling bugs so pressing Ctrl+C or Esc immediately cancels active HTTP streams and releases the CLI layout.
14
+
15
+ ## [1.48.2] - 2026-07-23
6
16
  - **Minor Bug Fixes**: Under-the-hood optimization and resolution of minor dependency issues.
7
17
 
8
18
  ## [1.48.1] - 2026-07-23
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: utim-cli
3
- Version: 1.48.2
3
+ Version: 1.48.4
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.2"
7
+ version = "1.48.4"
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.2"
8
+ __version__ = "1.48.4"
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.2"
14
+ VERSION = "1.48.4"
@@ -1079,143 +1079,36 @@ def _phase3_execute(
1079
1079
  f"Last error:\n{last_error}"
1080
1080
  )
1081
1081
 
1082
-
1083
- # ---------------------------------------------------------------------------
1084
- # Public entry point
1085
- # ---------------------------------------------------------------------------
1086
-
1087
1082
  def blender_agent_create_from_image(
1088
- image_path: str,
1089
- name: str,
1083
+ image_path: Optional[str] = None,
1084
+ prompt: Optional[str] = None,
1085
+ name: Optional[str] = None,
1090
1086
  output_path: Optional[str] = None,
1091
- output_format: str = "blend",
1087
+ output_format: str = "glb",
1092
1088
  ) -> str:
1093
- """Create a detailed 3-D model from an image using the 4-phase Blender pipeline.
1094
-
1095
- Parameters
1096
- ----------
1097
- image_path:
1098
- Absolute or relative path to the source image (PNG, JPG, WEBP, BMP).
1099
- name:
1100
- Base name for the Blender object and the exported file (no extension).
1101
- output_path:
1102
- Directory where the exported file will be saved.
1103
- Defaults to ``blender_assets/`` in the current working directory.
1104
- output_format:
1105
- ``"blend"``, ``"obj"``, ``"glb"``, or ``"fbx"``. Defaults to ``"blend"``.
1106
-
1107
- Returns
1108
- -------
1109
- str
1110
- A human-readable progress log including the path to the exported
1111
- file, or an error description.
1112
- """
1113
- return "Error: The Blender 3D tool is currently Under Maintenance."
1114
-
1115
- output_dir = output_path or os.path.join(os.getcwd(), ".utim_tmp", "blender_assets")
1116
- assets_dir = pathlib.Path(output_dir).absolute()
1117
- assets_dir.mkdir(parents=True, exist_ok=True)
1118
-
1119
- safe_name = re.sub(r"[^\w\-]", "_", name)
1120
- export_filename = f"{safe_name}_{uuid.uuid4().hex[:8]}.{output_format}"
1121
- export_path = str(assets_dir / export_filename)
1122
-
1123
- tmp_dir = pathlib.Path(".utim_tmp/blender")
1124
- tmp_dir.mkdir(parents=True, exist_ok=True)
1125
-
1126
- log_lines: List[str] = []
1127
-
1128
- def _log(msg: str) -> None:
1129
- log_lines.append(msg)
1130
-
1131
- # Time tracking
1132
- timings = {}
1133
- overall_start = time.time()
1134
-
1135
- def _time_log(phase: str) -> float:
1136
- elapsed = time.time() - overall_start
1137
- timings[phase] = elapsed
1138
- return elapsed
1139
-
1140
- # ── Phase 0: Local image analysis ────────────────────────────────────────
1141
- _log("[Blender Agent] Phase 0: Local image analysis...")
1142
- phase0_start = time.time()
1143
- scene_brief = _phase0_local_analysis(image_path)
1144
- _time_log("Phase 0")
1145
- _log(
1146
- f" Resolution : {scene_brief.get('width')}x{scene_brief.get('height')} px\n"
1147
- f" Brightness : {scene_brief.get('brightness')}\n"
1148
- f" Dom. colours: {len(scene_brief.get('dominant_colors', []))} found"
1149
- )
1089
+ """Create a detailed 3-D model from an image or text prompt using the direct Tripo API.
1150
1090
 
1151
- # ── Phase 1: Vision-LLM scene understanding ───────────────────────────────
1152
- _log("[Blender Agent] Phase 1: Vision-LLM scene analysis...")
1153
- phase1_start = time.time()
1154
- try:
1155
- scene_data, assembly_plan = _phase1_vision(image_path, scene_brief)
1156
- except Exception as exc:
1157
- _time_log("Phase 1")
1158
- return "\n".join(log_lines) + f"\n[Blender Agent] Phase 1 FAILED.\nReason: {exc}"
1159
- _time_log("Phase 1")
1160
-
1161
- part_count = len(scene_data.get("parts", []))
1162
- tattoos = scene_data.get("face_details", {}).get("tattoos", [])
1163
- _log(
1164
- f" Scene type : {scene_data.get('scene_type', 'unknown')}\n"
1165
- f" Subject : {scene_data.get('subject_label', 'unknown')}\n"
1166
- f" Description : {scene_data.get('description', '')[:120]}...\n"
1167
- f" Parts found : {part_count}\n"
1168
- f" Hair style : {scene_data.get('hair', {}).get('style', 'n/a')}\n"
1169
- f" Has tattoos : {len(tattoos) if isinstance(tattoos, list) else bool(tattoos)}\n"
1170
- f" Clothing : {len(scene_data.get('clothing', []))} item(s)\n"
1171
- f" Objects : {len(scene_data.get('objects', []))} background objects"
1172
- )
1091
+ Saves the resulting model to the blender_assets/ folder inside .utim_tmp/ directory.
1092
+ """
1093
+ from utim_cli.tools import generate_3d_model
1094
+ import os
1095
+ import re
1096
+ import uuid
1173
1097
 
1174
- # Save the scene description JSON alongside the script for inspection
1175
- json_path = tmp_dir / f"scene_{safe_name}.json"
1176
- json_path.write_text(json.dumps(scene_data, indent=2), encoding="utf-8")
1177
- _log(f" Scene JSON : {json_path}")
1098
+ if not image_path and not prompt:
1099
+ return "Error: Please provide either an image_path or a text prompt for 3D model generation."
1178
1100
 
1179
- # ── Phase 2: Blender script generation ───────────────────────────────────
1180
- _log("[Blender Agent] Phase 2: Generating Blender Python script...")
1181
- phase2_start = time.time()
1182
- try:
1183
- script = _phase2_generate_script(
1184
- scene_data=scene_data,
1185
- assembly_plan=assembly_plan,
1186
- image_path=image_path,
1187
- name=safe_name,
1188
- export_path=export_path,
1189
- export_format=output_format,
1190
- )
1191
- except Exception as exc:
1192
- _time_log("Phase 2")
1193
- return "\n".join(log_lines) + f"\n[Blender Agent] Phase 2 FAILED.\nReason: {exc}"
1194
- _time_log("Phase 2")
1195
-
1196
- script_preview_path = tmp_dir / f"gen_{safe_name}_preview.py"
1197
- script_preview_path.write_text(script, encoding="utf-8")
1198
- _log(f" Script saved: {script_preview_path}")
1199
-
1200
- # ── Phase 3: Execute & validate ───────────────────────────────────────────
1201
- _log("[Blender Agent] Phase 3: Running Blender headless...")
1202
- phase3_start = time.time()
1203
- try:
1204
- final_path = _phase3_execute(
1205
- script=script,
1206
- scene_data=scene_data,
1207
- image_path=image_path,
1208
- name=safe_name,
1209
- export_path=export_path,
1210
- export_format=output_format,
1211
- tmp_dir=tmp_dir,
1212
- )
1213
- except Exception as exc:
1214
- _time_log("Phase 3")
1215
- return "\n".join(log_lines) + f"\n[Blender Agent] Phase 3 FAILED.\nReason: {exc}"
1216
- _time_log("Phase 3")
1217
-
1218
- total_elapsed = time.time() - overall_start
1219
- _log(f"[Blender Agent] SUCCESS - exported to: {final_path}")
1220
- _log(f"[Blender Agent] Total elapsed time: {total_elapsed:.1f}s")
1221
- return "\n".join(log_lines)
1101
+ task_type = "image_to_model" if image_path else "text_to_model"
1102
+ safe_name = name or f"blender_{uuid.uuid4().hex[:8]}"
1103
+
1104
+ # Map 'blend' or other formats to glb if needed
1105
+ tripo_format = output_format if output_format in ("glb", "obj") else "glb"
1106
+ res = generate_3d_model(
1107
+ type=task_type,
1108
+ prompt=prompt,
1109
+ image_path=image_path,
1110
+ name=safe_name,
1111
+ output_format=tripo_format
1112
+ )
1113
+
1114
+ return f"[Blender Agent] Tripo direct API generation response:\n{res}"
@@ -258,7 +258,7 @@ def parse_xml_tool_calls(content: str) -> tuple[str | None, list[dict]]:
258
258
 
259
259
  args = {}
260
260
  # Pattern 1: <arg_key>key</arg_key>\s*<arg_value>value</arg_value>
261
- arg_pattern = re.compile(r'<arg_key>([^<]+)</arg_key>\s*<arg_value>([^<]*)</arg_value>')
261
+ arg_pattern = re.compile(r'<arg_key>([^<]+)</arg_key>\s*<arg_value>([\s\S]*?)</arg_value>')
262
262
  for arg_match in arg_pattern.finditer(block):
263
263
  args[arg_match.group(1).strip()] = arg_match.group(2).strip()
264
264
 
@@ -269,9 +269,12 @@ def parse_xml_tool_calls(content: str) -> tuple[str | None, list[dict]]:
269
269
 
270
270
  # Standardize argument name for run_command
271
271
  if tool_name == "run_command":
272
- for wrong_key in ["CommandLine", "cmd", "shell", "script"]:
272
+ for wrong_key in ["CommandLine", "commandLine", "command_line", "cmd_line", "cmd", "shell", "script"]:
273
273
  if wrong_key in args and "command" not in args:
274
274
  args["command"] = args.pop(wrong_key)
275
+ for path_key in ["cwd", "directory", "folder", "path"]:
276
+ if path_key in args and "dir_path" not in args:
277
+ args["dir_path"] = args.pop(path_key)
275
278
  for bg_key in ["background", "is_background", "bg"]:
276
279
  if bg_key in args:
277
280
  val = args.pop(bg_key)
@@ -714,18 +714,18 @@ def _get_dynamic_directives(is_compressed: bool, messages: Optional[List[Dict]]
714
714
  directives.append("2. **Planning/Autonomous**: No tools are currently enabled. Do NOT attempt to call any tools.")
715
715
 
716
716
  if has_plan_project and has_run_command:
717
- directives.append("3. **Think-Create-Verify**: Formulate a plan in text (or use the `plan_project` tool for complex architectural parts). Verify with `run_command` (build/test/run), do not just read.")
717
+ directives.append("3. **Think-Create-Verify**: Start executing right away. You do NOT need to write a plan or preamble in text before calling tools. Output tool calls immediately, then explain results or check tests. Verify with `run_command` (build/test/run), do not just read.")
718
718
  elif has_plan_project:
719
- directives.append("3. **Think-Create-Verify**: Formulate a plan in text (or use the `plan_project` tool for complex architectural parts) before writing.")
719
+ directives.append("3. **Think-Create-Verify**: Start executing right away. You do NOT need to write a plan or preamble in text before calling tools. Output tool calls immediately, then explain results or check tests.")
720
720
  elif has_run_command:
721
- directives.append("3. **Think-Create-Verify**: Formulate a plan in text before writing. Verify with `run_command` (build/test/run), do not just read.")
721
+ directives.append("3. **Think-Create-Verify**: Start executing right away. You do NOT need to write a plan or preamble in text before calling tools. Output tool calls immediately, then explain results or check tests. Verify with `run_command` (build/test/run), do not just read.")
722
722
  else:
723
- directives.append("3. **Think-Create-Verify**: Formulate a plan in text before writing.")
723
+ directives.append("3. **Think-Create-Verify**: Start executing right away. You do NOT need to write a plan or preamble in text before calling tools. Output tool calls immediately, then explain results or check tests.")
724
724
 
725
725
  directives.append(f"4. **Manifesto**: Safety and coding standards are in {_posix}/UTIM.md.")
726
726
  directives.append("5. **Output**: Be concise and professional. Summarize all changes and test results when complete. Speak colloquially and warmly. If you notice other code smells, potential bugs, or logical issues in the code snippets you read during execution, list them as proposed improvements in your final summary for the user instead of fixing them immediately.")
727
- directives.append("6. **Execution**: Create and follow a detailed todo list to the end.")
728
- directives.append("7. **Tool Calling**: Use native function-calling. No raw JSON, `<think>` tags, or raw tool markup in text responses.")
727
+ directives.append("6. **Execution**: Avoid announcing what you are going to do or writing text-only planning steps. Just invoke the appropriate tool call directly. You may explain your reasoning in the same message alongside the tool call if needed, but the tool call MUST be present if there is work to do.")
728
+ directives.append("7. **Tool Calling**: Use native function-calling. Avoid conversational preamble. Do not output raw JSON, `<think>` tags, or raw tool markup in text responses.")
729
729
  directives.append(f"8. **Premium Web Design**: Style web UIs per `{_posix}/DESIGN.md` (premium typography, HSL colors, glassmorphism, animations).")
730
730
  directives.append("9. **Hint Guidance**: Follow any `[Secret Hint Guidance: <instruction>]` prefix in the user prompt as a high-priority system directive.")
731
731
  directives.append("10. **Speed & Safety Balance**: Trust your edits. Do NOT waste tokens/time calling `read_file` or `view_file` immediately after writing or editing just to verify. Instead, verify safety instantly by running a compilation, build, or test command via `run_command`. Only re-read files if compilation/tests fail or return errors.")
@@ -750,18 +750,18 @@ def _get_dynamic_directives(is_compressed: bool, messages: Optional[List[Dict]]
750
750
  directives.append("2. **Planning/Autonomous Tooling**: No tools are currently enabled. Do NOT attempt to use any tools.")
751
751
 
752
752
  if has_plan_project and has_run_command:
753
- directives.append("3. **Think-Create-Verify**: Formulate a technical plan in text (or use the `plan_project` tool if a highly detailed sub-plan is needed for complex sections). Track tasks, write code, and verify with execution (`run_command` -> build/test/run) rather than just reading files.")
753
+ directives.append("3. **Think-Create-Verify**: Start executing right away. You do NOT need to write a detailed plan or preamble in text before calling tools. Output tool calls immediately, then explain results or check tests. Verify with execution (`run_command` -> build/test/run) rather than just reading files.")
754
754
  elif has_plan_project:
755
- directives.append("3. **Think-Create-Verify**: Formulate a technical plan in text (or use the `plan_project` tool if a highly detailed sub-plan is needed for complex sections) before taking any mutating actions.")
755
+ directives.append("3. **Think-Create-Verify**: Start executing right away. You do NOT need to write a detailed plan or preamble in text before calling tools. Output tool calls immediately, then explain results or check tests.")
756
756
  elif has_run_command:
757
- directives.append("3. **Think-Create-Verify**: Formulate a technical plan in text before taking any mutating actions. Verify with execution (`run_command` -> build/test/run) rather than just reading files.")
757
+ directives.append("3. **Think-Create-Verify**: Start executing right away. You do NOT need to write a detailed plan or preamble in text before calling tools. Output tool calls immediately, then explain results or check tests. Verify with execution (`run_command` -> build/test/run) rather than just reading files.")
758
758
  else:
759
- directives.append("3. **Think-Create-Verify**: Formulate a technical plan in text before taking any mutating actions.")
759
+ directives.append("3. **Think-Create-Verify**: Start executing right away. You do NOT need to write a detailed plan or preamble in text before calling tools. Output tool calls immediately, then explain results or check tests.")
760
760
 
761
761
  directives.append(f"4. **Manifesto Reference**: Detailed engineering rules, coding standards, and safety/sandbox instructions are in {_posix}/UTIM.md. Read/reference it only when specific guidance is needed.")
762
762
  directives.append("5. **Output**: Be concise and professional. Summarize all changes and test results when complete. Speak colloquially and warmly in the user's language. If you notice other code smells, potential bugs, or logical issues in the code snippets you read during execution, list them as proposed improvements in your final summary for the user instead of fixing them immediately.")
763
- directives.append("6. **Execution**: When executing tools to get the project ready always create a todo list and follow them to the end. Add todos based on detailed steps.")
764
- directives.append("7. **Tool Calling format**: You MUST use the native JSON-schema function-calling mechanism provided by the API to invoke tools. NEVER output raw JSON, <think to=...> tags, <|message|> tags, or other raw tool-call markup in your text response. Any tool usage must be strictly structured via the API.")
763
+ directives.append("6. **Execution**: Avoid announcing what you are going to do or writing text-only planning steps. Just invoke the appropriate tool call directly. You may explain your reasoning in the same message alongside the tool call if needed, but the tool call MUST be present if there is work to do.")
764
+ directives.append("7. **Tool Calling format**: You MUST use the native JSON-schema function-calling mechanism provided by the API to invoke tools. Avoid conversational preamble. NEVER output raw JSON, <think to=...> tags, <|message|> tags, or other raw tool-call markup in your text response. Any tool usage must be strictly structured via the API.")
765
765
  directives.append(f"8. **Premium Web Design**: When creating, updating, or styling web user interfaces, read/reference `{_posix}/DESIGN.md` to apply modern visual design conventions (such as premium typography, HSL color themes, glassmorphism, and smooth animations) instead of default browser styles or raw colors.")
766
766
  directives.append("9. **Secret Hint Guidance**: If a user message begins with `[Secret Hint Guidance: <instruction>]`, you MUST treat that instruction as a high-priority system-level override directive. Customize your reasoning, tool selection, explanation detail, and output formatting to strictly adhere to that instruction throughout the entire task execution.")
767
767
  directives.append("10. **Speed & Safety Balance**: Trust your edits. Do NOT waste tokens/time calling `read_file` or `view_file` immediately after writing or editing just to verify. Instead, verify safety instantly by running a compilation, build, or test command via `run_command`. Only re-read files if compilation/tests fail or return errors.")
@@ -2040,6 +2040,15 @@ class Orchestrator:
2040
2040
  finally:
2041
2041
  self.active_response = None
2042
2042
 
2043
+ if self.cancel_event.is_set():
2044
+ return {
2045
+ "role": "assistant",
2046
+ "content": "[Aborted by user]",
2047
+ "tool_calls": None,
2048
+ "was_cut_off": True,
2049
+ "aborted": True,
2050
+ }, False
2051
+
2043
2052
  # Flush any remaining text in _line_buf and table_buf
2044
2053
  flush_table()
2045
2054
  if _line_buf and not silent:
@@ -3084,10 +3093,21 @@ class Orchestrator:
3084
3093
  ("edit_file", "path"): "filepath",
3085
3094
  ("edit_file", "file"): "filepath",
3086
3095
  ("edit_file", "filename"): "filepath",
3087
- # run_command: model sometimes uses 'cmd'
3088
- ("run_command", "cmd"): "command",
3089
- ("run_command", "shell"): "command",
3090
- ("run_command", "script"): "command",
3096
+ # run_command: model sometimes uses 'cmd' or 'CommandLine'
3097
+ ("run_command", "cmd"): "command",
3098
+ ("run_command", "shell"): "command",
3099
+ ("run_command", "script"): "command",
3100
+ ("run_command", "CommandLine"): "command",
3101
+ ("run_command", "commandLine"): "command",
3102
+ ("run_command", "command_line"): "command",
3103
+ ("run_command", "cmd_line"): "command",
3104
+ ("run_command", "cwd"): "dir_path",
3105
+ ("run_command", "directory"): "dir_path",
3106
+ ("run_command", "folder"): "dir_path",
3107
+ ("run_command", "path"): "dir_path",
3108
+ ("run_command", "background"): "is_background",
3109
+ ("run_command", "bg"): "is_background",
3110
+ ("run_command", "run_in_background"):"is_background",
3091
3111
  # web_search: model sometimes uses 'prompt'
3092
3112
  ("web_search", "prompt"): "query",
3093
3113
  ("web_search", "text"): "query",
@@ -4166,10 +4186,12 @@ class Orchestrator:
4166
4186
 
4167
4187
 
4168
4188
 
4169
- def run_task(self, user_message: str, max_iterations: int = 500) -> None:
4189
+ def run_task(self, user_message: str, max_iterations: int = 35) -> None:
4170
4190
  """Append user_message to history and run the full ReAct loop until the
4171
4191
  model stops issuing tool calls or we hit max_iterations.
4172
4192
  """
4193
+ from utim_cli.config import config
4194
+ max_iterations = config.get("max_iterations", max_iterations)
4173
4195
  self.turn_step_timings = []
4174
4196
  self._tool_failure_counts = {}
4175
4197
 
@@ -4611,27 +4633,43 @@ class Orchestrator:
4611
4633
  )
4612
4634
  break
4613
4635
 
4636
+ # To prevent history pollution and break repeating patterns, check if the last
4637
+ # message in history was a previous nudge. If so, remove the previous nudge-and-response pair
4638
+ # so they don't multiply in the LLM's context.
4639
+ if len(self.messages) >= 2:
4640
+ last_user = self.messages[-1]
4641
+ if last_user.get("role") == "user" and any(marker in (last_user.get("content") or "") for marker in [
4642
+ "Stop announcing your intentions",
4643
+ "Stop describing plans",
4644
+ "You described what you were going to do",
4645
+ "You just executed tool(s)",
4646
+ "You stopped without writing anything"
4647
+ ]):
4648
+ self.messages.pop() # Pop the previous user nudge
4649
+ if self.messages and self.messages[-1].get("role") == "assistant":
4650
+ self.messages.pop() # Pop the previous lazy assistant response
4651
+
4614
4652
  # Build a context-aware continuation nudge
4615
4653
  if _had_tools_this_turn:
4616
4654
  tool_list = ", ".join(dict.fromkeys(reversed(_recent_tool_names))) # dedupe, preserve order
4617
4655
  nudge = (
4618
4656
  f"You just executed tool(s) [{tool_list}]. You then wrote a short response without "
4619
- f"calling any further tools. If the task is incomplete, you MUST output the required JSON tool calls. "
4620
- f"Do NOT just tell me what you are going to do — actually DO IT by calling the tool. "
4657
+ f"calling any further tools. Stop describing plans or announcing intentions. "
4658
+ f"If the task is incomplete, you MUST output the required JSON tool calls to DO IT. "
4621
4659
  f"If the task is truly complete, provide a comprehensive final summary."
4622
4660
  )
4623
4661
  elif _is_lazy_transition:
4624
4662
  # First-step lazy: model announced what it would do but didn't call any tools
4625
4663
  nudge = (
4626
- "You described what you were going to do but did not actually do it. "
4627
- "Stop announcing your intentions — immediately call the appropriate tool(s) right now. "
4628
- "Do NOT write 'I will now...' or 'Let me...' and then stop. "
4629
- "Just output the tool call directly."
4664
+ "Stop describing plans, listing todos, or announcing your intentions in text. "
4665
+ "You MUST immediately execute the appropriate tool call(s) right now. "
4666
+ "Do NOT write conversational filler, explanations, or 'I will now...' statements. "
4667
+ "Output the tool call JSON directly."
4630
4668
  )
4631
4669
  else:
4632
4670
  nudge = (
4633
4671
  "You stopped without writing anything or taking action. The user's request was: "
4634
- f"\"{user_message[:200]}\". Please provide a substantive response or take action."
4672
+ f"\"{user_message[:200]}\". Stop describing plans — output the tool call JSON directly."
4635
4673
  )
4636
4674
 
4637
4675
  self.console.print(f"\n[dim yellow]⚠ Model provided lazy response without tools. Auto-nudging (attempt {_empty_response_streak}/3)...[/dim yellow]")
@@ -4894,9 +4932,33 @@ class Orchestrator:
4894
4932
  "content": warning_text
4895
4933
  })
4896
4934
  self.console.print(f"\n[bold red]⚠️ Tool Loop Intercepted: Nudging agent to prevent repeated `{func_name}` failures.[/bold red]")
4935
+
4936
+ # Also track general failures by tool name to catch cases where the model
4937
+ # changes arguments slightly but keeps failing the same tool type.
4938
+ if not hasattr(self, "_tool_name_failure_counts"):
4939
+ self._tool_name_failure_counts = {}
4940
+
4941
+ name_count = self._tool_name_failure_counts.get(func_name, 0) + 1
4942
+ self._tool_name_failure_counts[func_name] = name_count
4943
+
4944
+ if name_count >= 4:
4945
+ self._tool_name_failure_counts[func_name] = 0
4946
+ warning_text = (
4947
+ f"⚠️ [SYSTEM NOTICE] The tool `{func_name}` has failed 4 times with different arguments in this turn. "
4948
+ f"Your general approach with `{func_name}` is failing. Please check if your file paths are absolute, "
4949
+ f"if directories exist, or if command flags are correct. You MUST alter your strategy or verify "
4950
+ f"path structure before calling `{func_name}` again."
4951
+ )
4952
+ self.messages.append({
4953
+ "role": "user",
4954
+ "content": warning_text
4955
+ })
4956
+ self.console.print(f"\n[bold red]⚠️ Tool Type Failure Loop Intercepted: Nudging agent for `{func_name}` general failures.[/bold red]")
4897
4957
  else:
4898
- # Successful tool run clears the failure count for this fingerprint
4958
+ # Successful tool run clears the failure counts
4899
4959
  self._tool_failure_counts.pop(fingerprint, None)
4960
+ if hasattr(self, "_tool_name_failure_counts"):
4961
+ self._tool_name_failure_counts.pop(func_name, None)
4900
4962
 
4901
4963
  # Attempt to compress context if requested or if token limit is breached.
4902
4964
  # turn_msg_start is passed so that after compression rewrites
@@ -163,6 +163,11 @@ def process_user_refills(db: Session, user_id: str) -> None:
163
163
  unused_credits = max(0.0, cycle_allowance - current_cycle_used)
164
164
  if due > 1:
165
165
  unused_credits += cycle_allowance * (due - 1)
166
+
167
+ # Cap refills to remaining monthly plan credits after direct deductions
168
+ unallocated_deducted = getattr(sub, "unallocated_deducted", 0.0) or 0.0
169
+ remaining_pool = max(0.0, plan.credits_per_month - (sub.refills_processed * cycle_allowance) - unallocated_deducted)
170
+ unused_credits = min(unused_credits, remaining_pool)
166
171
 
167
172
  credit = db.query(Credit).filter(Credit.user_id == user_id).first()
168
173
  if not credit:
@@ -88,7 +88,7 @@ class Plan(Base):
88
88
  __tablename__ = "plans"
89
89
 
90
90
  id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
91
- name = Column(String(50), unique=True, nullable=False) # "free", "hobby", "pro", "team", "enterprise"
91
+ name = Column(String(50), unique=True, nullable=False) # "free", "hobby", "pro", "max", "ultimate"
92
92
  display_name = Column(String(100), nullable=False)
93
93
  price_inr = Column(Integer, default=0, nullable=False)
94
94
  credits_per_month = Column(Integer, nullable=False)
@@ -118,6 +118,7 @@ class UserSubscription(Base):
118
118
  last_refill_at = Column(DateTime, nullable=True)
119
119
  # Overrides the default per-cycle credit allocation after a quota-share deduction
120
120
  refill_rate_override = Column(Float, nullable=True)
121
+ unallocated_deducted = Column(Float, default=0.0, nullable=False)
121
122
  created_at = Column(DateTime, default=datetime.datetime.utcnow, nullable=False)
122
123
  updated_at = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow, nullable=False)
123
124
 
@@ -630,12 +631,14 @@ def init_db(silent: bool = True) -> None:
630
631
  _run_migration("users", "referral_code", "ALTER TABLE users ADD COLUMN IF NOT EXISTS referral_code VARCHAR(50)", "postgres add referral_code")
631
632
  _run_migration("feedbacks", "chat_history", "ALTER TABLE feedbacks ADD COLUMN IF NOT EXISTS chat_history TEXT", "postgres add chat_history")
632
633
  _run_migration("user_subscriptions", "refill_rate_override", "ALTER TABLE user_subscriptions ADD COLUMN IF NOT EXISTS refill_rate_override FLOAT", "postgres add refill_rate_override")
634
+ _run_migration("user_subscriptions", "unallocated_deducted", "ALTER TABLE user_subscriptions ADD COLUMN IF NOT EXISTS unallocated_deducted FLOAT DEFAULT 0.0 NOT NULL", "postgres add unallocated_deducted")
633
635
  else:
634
636
  # SQLite: no IF NOT EXISTS on ADD COLUMN, swallow duplicate errors
635
637
  _run_migration("user_subscriptions", "refills_processed", "ALTER TABLE user_subscriptions ADD COLUMN refills_processed INTEGER DEFAULT 0 NOT NULL", "sqlite add refills_processed")
636
638
  _run_migration("user_subscriptions", "last_refill_at", "ALTER TABLE user_subscriptions ADD COLUMN last_refill_at TIMESTAMP", "sqlite add last_refill_at")
637
639
  _run_migration("user_subscriptions", "current_cycle_used", "ALTER TABLE user_subscriptions ADD COLUMN current_cycle_used FLOAT DEFAULT 0.0 NOT NULL", "sqlite add current_cycle_used")
638
640
  _run_migration("user_subscriptions", "refill_rate_override", "ALTER TABLE user_subscriptions ADD COLUMN refill_rate_override FLOAT", "sqlite add refill_rate_override")
641
+ _run_migration("user_subscriptions", "unallocated_deducted", "ALTER TABLE user_subscriptions ADD COLUMN unallocated_deducted FLOAT DEFAULT 0.0 NOT NULL", "sqlite add unallocated_deducted")
639
642
  _run_migration("credits", "bonus_balance", "ALTER TABLE credits ADD COLUMN bonus_balance FLOAT DEFAULT 0.0 NOT NULL", "sqlite add bonus_balance")
640
643
  _run_migration("credits", "bonus_limit", "ALTER TABLE credits ADD COLUMN bonus_limit FLOAT DEFAULT 0.0 NOT NULL", "sqlite add bonus_limit")
641
644
  _run_migration("credits", "free_monthly_used", "ALTER TABLE credits ADD COLUMN free_monthly_used FLOAT DEFAULT 0.0 NOT NULL", "sqlite add free_monthly_used")
@@ -693,24 +696,6 @@ def init_db(silent: bool = True) -> None:
693
696
  allowed_models="all",
694
697
  max_context_k=1024
695
698
  ),
696
- Plan(
697
- id="team",
698
- name="team",
699
- display_name="Team",
700
- price_inr=3500,
701
- credits_per_month=45000,
702
- allowed_models="all",
703
- max_context_k=1024
704
- ),
705
- Plan(
706
- id="enterprise",
707
- name="enterprise",
708
- display_name="Enterprise",
709
- price_inr=5000,
710
- credits_per_month=80000,
711
- allowed_models="all",
712
- max_context_k=1024
713
- ),
714
699
  Plan(
715
700
  id="ultimate",
716
701
  name="ultimate",
@@ -724,6 +709,9 @@ def init_db(silent: bool = True) -> None:
724
709
  db.add_all(plans)
725
710
  db.commit()
726
711
  else:
712
+ # Delete legacy plans if present to prevent confusion
713
+ db.query(Plan).filter(Plan.id.in_(["team", "enterprise"])).delete(synchronize_session=False)
714
+
727
715
  # Update existing plan details
728
716
  pro_plan = db.query(Plan).filter(Plan.id == "pro").first()
729
717
  if pro_plan:
@@ -817,8 +805,8 @@ def get_max_bonus_limit(plan_id: str) -> float:
817
805
  return 50000.0
818
806
  elif pid == "pro":
819
807
  return 1500000.0
820
- elif pid in ("max", "team"):
808
+ elif pid == "max":
821
809
  return 3000000.0
822
- elif pid in ("ultimate", "enterprise"):
810
+ elif pid == "ultimate":
823
811
  return 4500000.0
824
812
  return 20000.0