cct-cli 0.7.9.0__py3-none-any.whl

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 (325) hide show
  1. calc_terminal/__init__.py +14 -0
  2. calc_terminal/__main__.py +14 -0
  3. calc_terminal/activity.py +1334 -0
  4. calc_terminal/agent.py +3387 -0
  5. calc_terminal/agent_runtime.py +519 -0
  6. calc_terminal/ai_context.py +447 -0
  7. calc_terminal/ai_modes.py +752 -0
  8. calc_terminal/ai_personalization.py +286 -0
  9. calc_terminal/ai_preview_feedback.py +213 -0
  10. calc_terminal/aicore.py +2572 -0
  11. calc_terminal/anim.py +367 -0
  12. calc_terminal/app.py +3685 -0
  13. calc_terminal/art.py +639 -0
  14. calc_terminal/atomsim.py +368 -0
  15. calc_terminal/attachments.py +743 -0
  16. calc_terminal/benchmark_system.py +414 -0
  17. calc_terminal/browser/__init__.py +36 -0
  18. calc_terminal/browser/browser_state.py +346 -0
  19. calc_terminal/browser/devserver.py +176 -0
  20. calc_terminal/browser/engine.py +494 -0
  21. calc_terminal/browser/navigation.py +84 -0
  22. calc_terminal/browser/preview.py +429 -0
  23. calc_terminal/browser/preview_entry.py +95 -0
  24. calc_terminal/browser/project_detector.py +144 -0
  25. calc_terminal/browser/server.py +449 -0
  26. calc_terminal/browser/state.py +75 -0
  27. calc_terminal/browser/watcher.py +99 -0
  28. calc_terminal/browser_gui/__init__.py +1 -0
  29. calc_terminal/browser_gui/__main__.py +3 -0
  30. calc_terminal/browser_gui/launcher.py +173 -0
  31. calc_terminal/browser_gui/playwright_browser.py +117 -0
  32. calc_terminal/browser_gui/qt_browser.py +1501 -0
  33. calc_terminal/browser_gui/webview_browser.py +57 -0
  34. calc_terminal/capabilities/__init__.py +35 -0
  35. calc_terminal/capabilities/adapters/__init__.py +33 -0
  36. calc_terminal/capabilities/adapters/bioinformatics.py +204 -0
  37. calc_terminal/capabilities/adapters/browser_adapter.py +205 -0
  38. calc_terminal/capabilities/adapters/filesystem.py +206 -0
  39. calc_terminal/capabilities/adapters/git_adapter.py +202 -0
  40. calc_terminal/capabilities/adapters/jupyter_adapter.py +138 -0
  41. calc_terminal/capabilities/adapters/ml_frameworks.py +158 -0
  42. calc_terminal/capabilities/adapters/platforms.py +200 -0
  43. calc_terminal/capabilities/adapters/python_exec.py +93 -0
  44. calc_terminal/capabilities/adapters/quantum_adapter.py +150 -0
  45. calc_terminal/capabilities/adapters/scientific_comp.py +123 -0
  46. calc_terminal/capabilities/adapters/structural_bio.py +161 -0
  47. calc_terminal/capabilities/adapters/terminal.py +99 -0
  48. calc_terminal/capabilities/bus.py +178 -0
  49. calc_terminal/capabilities/discovery.py +207 -0
  50. calc_terminal/capabilities/schema.py +221 -0
  51. calc_terminal/cat.ico +0 -0
  52. calc_terminal/cat_browser.py +2018 -0
  53. calc_terminal/chat_store.py +703 -0
  54. calc_terminal/cli.py +1178 -0
  55. calc_terminal/code_editor.py +640 -0
  56. calc_terminal/collaboration.py +723 -0
  57. calc_terminal/commands_data.py +139 -0
  58. calc_terminal/compatibility_engine.py +352 -0
  59. calc_terminal/compute/__init__.py +31 -0
  60. calc_terminal/compute/fabric.py +350 -0
  61. calc_terminal/config.py +227 -0
  62. calc_terminal/core/__init__.py +41 -0
  63. calc_terminal/core/checkpoint.py +156 -0
  64. calc_terminal/core/input/__init__.py +45 -0
  65. calc_terminal/core/mode_registry.py +300 -0
  66. calc_terminal/core/project_graph.py +172 -0
  67. calc_terminal/core/recovery.py +129 -0
  68. calc_terminal/core/security_layer.py +112 -0
  69. calc_terminal/core/task_graph.py +202 -0
  70. calc_terminal/core/unified_runtime.py +184 -0
  71. calc_terminal/core/verification.py +257 -0
  72. calc_terminal/customization.py +1566 -0
  73. calc_terminal/derivations.py +153 -0
  74. calc_terminal/device_control.py +263 -0
  75. calc_terminal/diagnostics/__init__.py +27 -0
  76. calc_terminal/diagnostics/doctor_engine.py +382 -0
  77. calc_terminal/diagnostics/self_test.py +247 -0
  78. calc_terminal/doctor.py +519 -0
  79. calc_terminal/easter_eggs.py +274 -0
  80. calc_terminal/editor/__init__.py +1 -0
  81. calc_terminal/editor/actions.py +263 -0
  82. calc_terminal/editor/commands.py +160 -0
  83. calc_terminal/editor/shortcuts.py +226 -0
  84. calc_terminal/engine.py +259 -0
  85. calc_terminal/errors.py +120 -0
  86. calc_terminal/event_stream.py +146 -0
  87. calc_terminal/eventbus.py +133 -0
  88. calc_terminal/extensions.py +733 -0
  89. calc_terminal/fallback_cli.py +1321 -0
  90. calc_terminal/first_run.py +265 -0
  91. calc_terminal/fomoji_auth.py +1043 -0
  92. calc_terminal/formulas.py +82 -0
  93. calc_terminal/fs_cache.py +121 -0
  94. calc_terminal/fs_watcher.py +277 -0
  95. calc_terminal/game.py +193 -0
  96. calc_terminal/gen1.py +5 -0
  97. calc_terminal/generators.py +245 -0
  98. calc_terminal/gestures/__init__.py +42 -0
  99. calc_terminal/gestures/bindings.py +175 -0
  100. calc_terminal/gestures/manager.py +477 -0
  101. calc_terminal/goodbye.py +363 -0
  102. calc_terminal/gpu3d.py +290 -0
  103. calc_terminal/graphs.py +358 -0
  104. calc_terminal/hardware_analyzer.py +440 -0
  105. calc_terminal/host/__init__.py +30 -0
  106. calc_terminal/host/browser_manager.py +187 -0
  107. calc_terminal/host/desktop.py +1386 -0
  108. calc_terminal/host/launcher.py +395 -0
  109. calc_terminal/host/terminal.py +279 -0
  110. calc_terminal/identity.py +216 -0
  111. calc_terminal/input/__init__.py +54 -0
  112. calc_terminal/input/capabilities.py +258 -0
  113. calc_terminal/input/focus.py +87 -0
  114. calc_terminal/input/gestures.py +64 -0
  115. calc_terminal/input/pointer.py +114 -0
  116. calc_terminal/input/touch.py +345 -0
  117. calc_terminal/keys.py +84 -0
  118. calc_terminal/live_automation.py +165 -0
  119. calc_terminal/mathtext.py +433 -0
  120. calc_terminal/mcp.py +386 -0
  121. calc_terminal/memory.py +337 -0
  122. calc_terminal/memory_v2.py +479 -0
  123. calc_terminal/metrics.py +333 -0
  124. calc_terminal/mode_detection.py +146 -0
  125. calc_terminal/model.py +2431 -0
  126. calc_terminal/model_router.py +665 -0
  127. calc_terminal/models/__init__.py +0 -0
  128. calc_terminal/models/active_state.py +187 -0
  129. calc_terminal/models/dynamic_registry.py +584 -0
  130. calc_terminal/models/manager.py +781 -0
  131. calc_terminal/models/model_metadata.json +3526 -0
  132. calc_terminal/models/profiles.py +194 -0
  133. calc_terminal/models/registry.py +265 -0
  134. calc_terminal/models/schema.py +197 -0
  135. calc_terminal/models/validator.py +287 -0
  136. calc_terminal/models/verification_engine.py +368 -0
  137. calc_terminal/native_picker.py +215 -0
  138. calc_terminal/ollama_catalog.py +279 -0
  139. calc_terminal/ollama_download.py +233 -0
  140. calc_terminal/orchestrator.py +304 -0
  141. calc_terminal/package_research.py +322 -0
  142. calc_terminal/packages.py +1024 -0
  143. calc_terminal/pc_specs.py +116 -0
  144. calc_terminal/permissions.py +334 -0
  145. calc_terminal/pet.py +106 -0
  146. calc_terminal/pipeline.py +505 -0
  147. calc_terminal/platform/__init__.py +491 -0
  148. calc_terminal/platform/desktop.py +491 -0
  149. calc_terminal/platform/web.py +781 -0
  150. calc_terminal/preview/__init__.py +1 -0
  151. calc_terminal/preview/dev_server.py +303 -0
  152. calc_terminal/preview/diagnostics.py +131 -0
  153. calc_terminal/preview/live_reload.py +66 -0
  154. calc_terminal/preview/manager.py +129 -0
  155. calc_terminal/project_stats.py +209 -0
  156. calc_terminal/projects.py +328 -0
  157. calc_terminal/providers/__init__.py +0 -0
  158. calc_terminal/providers/adapters/__init__.py +80 -0
  159. calc_terminal/providers/adapters/anthropic_adapter.py +127 -0
  160. calc_terminal/providers/adapters/base.py +106 -0
  161. calc_terminal/providers/adapters/chinese_adapters.py +420 -0
  162. calc_terminal/providers/adapters/gemini_adapter.py +101 -0
  163. calc_terminal/providers/adapters/ollama_adapter.py +83 -0
  164. calc_terminal/providers/adapters/openai_adapter.py +159 -0
  165. calc_terminal/providers/adapters/other_adapters.py +246 -0
  166. calc_terminal/providers/anthropic_provider.py +172 -0
  167. calc_terminal/providers/auto_update.py +416 -0
  168. calc_terminal/providers/base_provider.py +105 -0
  169. calc_terminal/providers/discovery_manager.py +207 -0
  170. calc_terminal/providers/gemini_provider.py +178 -0
  171. calc_terminal/providers/lifecycle.py +767 -0
  172. calc_terminal/providers/ollama_adapter.py +707 -0
  173. calc_terminal/providers/openai_provider.py +254 -0
  174. calc_terminal/providers/provider_manager.py +1827 -0
  175. calc_terminal/providers/providers.json +4075 -0
  176. calc_terminal/reactionsim.py +279 -0
  177. calc_terminal/registry.py +337 -0
  178. calc_terminal/report.py +162 -0
  179. calc_terminal/research/__init__.py +45 -0
  180. calc_terminal/research/artifact_intel.py +126 -0
  181. calc_terminal/research/data_lineage.py +123 -0
  182. calc_terminal/research/experiment_ledger.py +303 -0
  183. calc_terminal/research/reproducibility.py +131 -0
  184. calc_terminal/resilience/__init__.py +47 -0
  185. calc_terminal/resilience/agent_state.py +121 -0
  186. calc_terminal/resilience/capability_matcher.py +174 -0
  187. calc_terminal/resilience/circuit_breaker.py +158 -0
  188. calc_terminal/resilience/failover_engine.py +230 -0
  189. calc_terminal/resilience/health_monitor.py +192 -0
  190. calc_terminal/resilience/ollama_adapter.py +125 -0
  191. calc_terminal/resilience/orchestrator.py +312 -0
  192. calc_terminal/resilience/types.py +134 -0
  193. calc_terminal/sandbox.py +98 -0
  194. calc_terminal/scires.py +558 -0
  195. calc_terminal/security_scanner.py +126 -0
  196. calc_terminal/session.py +294 -0
  197. calc_terminal/sim3d.py +206 -0
  198. calc_terminal/solver.py +276 -0
  199. calc_terminal/sound.py +127 -0
  200. calc_terminal/task_reports.py +287 -0
  201. calc_terminal/terminal_host.py +201 -0
  202. calc_terminal/terminal_identity.py +411 -0
  203. calc_terminal/test_ai_mode_reliability.py +344 -0
  204. calc_terminal/test_browser.py +368 -0
  205. calc_terminal/test_code_editor_upgrade.py +485 -0
  206. calc_terminal/test_customization.py +1148 -0
  207. calc_terminal/test_customization_ui.py +612 -0
  208. calc_terminal/test_dynamic_registry.py +304 -0
  209. calc_terminal/test_extensions.py +436 -0
  210. calc_terminal/test_overhaul.py +557 -0
  211. calc_terminal/test_project_detect.py +255 -0
  212. calc_terminal/test_root_cause_fix.py +527 -0
  213. calc_terminal/test_stability.py +532 -0
  214. calc_terminal/test_terminal_identity.py +132 -0
  215. calc_terminal/test_v079_speed.py +460 -0
  216. calc_terminal/theme.py +1107 -0
  217. calc_terminal/timeline.py +139 -0
  218. calc_terminal/todos.py +246 -0
  219. calc_terminal/tool_call_normalizer.py +419 -0
  220. calc_terminal/tui.py +104 -0
  221. calc_terminal/ui/__init__.py +8 -0
  222. calc_terminal/ui/activity_panel.py +231 -0
  223. calc_terminal/ui/activity_stream_panel.py +238 -0
  224. calc_terminal/ui/animations.py +122 -0
  225. calc_terminal/ui/app.py +7271 -0
  226. calc_terminal/ui/attach_panel.py +597 -0
  227. calc_terminal/ui/attachments.py +424 -0
  228. calc_terminal/ui/backup_panel.py +810 -0
  229. calc_terminal/ui/browser_shell.py +887 -0
  230. calc_terminal/ui/cat_agent.py +357 -0
  231. calc_terminal/ui/chats_panel.py +899 -0
  232. calc_terminal/ui/command_palette.py +125 -0
  233. calc_terminal/ui/command_palette_modal.py +166 -0
  234. calc_terminal/ui/composer.py +1141 -0
  235. calc_terminal/ui/context_menu.py +197 -0
  236. calc_terminal/ui/conversation.py +1435 -0
  237. calc_terminal/ui/customization_panel.py +1229 -0
  238. calc_terminal/ui/dashboard.py +404 -0
  239. calc_terminal/ui/design_system.py +557 -0
  240. calc_terminal/ui/diff_panel.py +213 -0
  241. calc_terminal/ui/editor.py +2102 -0
  242. calc_terminal/ui/empty_state.py +302 -0
  243. calc_terminal/ui/events.py +487 -0
  244. calc_terminal/ui/extensions_panel.py +815 -0
  245. calc_terminal/ui/footer.py +166 -0
  246. calc_terminal/ui/gestures_panel.py +383 -0
  247. calc_terminal/ui/goodbye_screen.py +100 -0
  248. calc_terminal/ui/header.py +1034 -0
  249. calc_terminal/ui/help_panel.py +254 -0
  250. calc_terminal/ui/live_activities.py +914 -0
  251. calc_terminal/ui/mcp_panel.py +570 -0
  252. calc_terminal/ui/memory_center.py +524 -0
  253. calc_terminal/ui/mode_colors_panel.py +525 -0
  254. calc_terminal/ui/nav_screens.py +747 -0
  255. calc_terminal/ui/ollama_panel.py +536 -0
  256. calc_terminal/ui/palette.py +221 -0
  257. calc_terminal/ui/permission_panel.py +269 -0
  258. calc_terminal/ui/personalization_panel.py +517 -0
  259. calc_terminal/ui/personalize_center.py +1568 -0
  260. calc_terminal/ui/preview_panel.py +441 -0
  261. calc_terminal/ui/resizers.py +402 -0
  262. calc_terminal/ui/sidebar.py +1285 -0
  263. calc_terminal/ui/statusbar.py +168 -0
  264. calc_terminal/ui/theme_css.py +1396 -0
  265. calc_terminal/ui/thinking.py +226 -0
  266. calc_terminal/ui/timeline_panel.py +102 -0
  267. calc_terminal/ui/todo_panel.py +193 -0
  268. calc_terminal/ui/viewport.py +136 -0
  269. calc_terminal/ui/vision_panel.py +489 -0
  270. calc_terminal/ui/welcome_modal.py +343 -0
  271. calc_terminal/ui/widgets.py +160 -0
  272. calc_terminal/ui/workspace.py +831 -0
  273. calc_terminal/viewers/__init__.py +1 -0
  274. calc_terminal/viewers/document_viewer.py +252 -0
  275. calc_terminal/viewers/image_viewer.py +241 -0
  276. calc_terminal/viewers/pdf_viewer.py +203 -0
  277. calc_terminal/viewers/presentation_viewer.py +164 -0
  278. calc_terminal/viewers/registry.py +120 -0
  279. calc_terminal/viewers/spreadsheet_viewer.py +204 -0
  280. calc_terminal/vision/__init__.py +89 -0
  281. calc_terminal/vision/analysis.py +194 -0
  282. calc_terminal/vision/annotations.py +297 -0
  283. calc_terminal/vision/capture.py +171 -0
  284. calc_terminal/vision/context.py +231 -0
  285. calc_terminal/vision/correlation.py +169 -0
  286. calc_terminal/vision/cursor.py +258 -0
  287. calc_terminal/vision/events.py +66 -0
  288. calc_terminal/vision/frame_pipeline.py +259 -0
  289. calc_terminal/vision/priority.py +218 -0
  290. calc_terminal/vision/provider.py +180 -0
  291. calc_terminal/vision/safety.py +149 -0
  292. calc_terminal/vision/session.py +281 -0
  293. calc_terminal/vision/verify.py +162 -0
  294. calc_terminal/vision.py +514 -0
  295. calc_terminal/vscode_integration.py +113 -0
  296. calc_terminal/web/__init__.py +8 -0
  297. calc_terminal/web/cat_runtime.py +710 -0
  298. calc_terminal/web/server.py +2891 -0
  299. calc_terminal/web/static/css/app.css +3152 -0
  300. calc_terminal/web/static/icons/badge-72.png +0 -0
  301. calc_terminal/web/static/icons/cat.ico +0 -0
  302. calc_terminal/web/static/icons/icon-128.png +0 -0
  303. calc_terminal/web/static/icons/icon-144.png +0 -0
  304. calc_terminal/web/static/icons/icon-152.png +0 -0
  305. calc_terminal/web/static/icons/icon-192.png +0 -0
  306. calc_terminal/web/static/icons/icon-384.png +0 -0
  307. calc_terminal/web/static/icons/icon-512.png +0 -0
  308. calc_terminal/web/static/icons/icon-72.png +0 -0
  309. calc_terminal/web/static/icons/icon-96.png +0 -0
  310. calc_terminal/web/static/icons/icon.svg +34 -0
  311. calc_terminal/web/static/icons/new-project.png +0 -0
  312. calc_terminal/web/static/icons/open-project.png +0 -0
  313. calc_terminal/web/static/index.html +734 -0
  314. calc_terminal/web/static/js/app.js +2403 -0
  315. calc_terminal/web/static/manifest.json +88 -0
  316. calc_terminal/web/static/sw.js +230 -0
  317. calc_terminal/workflow_engine.py +769 -0
  318. calc_terminal/workspace.py +593 -0
  319. calc_terminal/workspace_index.py +385 -0
  320. cct_cli-0.7.9.0.dist-info/METADATA +210 -0
  321. cct_cli-0.7.9.0.dist-info/RECORD +325 -0
  322. cct_cli-0.7.9.0.dist-info/WHEEL +5 -0
  323. cct_cli-0.7.9.0.dist-info/entry_points.txt +4 -0
  324. cct_cli-0.7.9.0.dist-info/licenses/LICENSE +21 -0
  325. cct_cli-0.7.9.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,411 @@
1
+ """
2
+ calc_terminal/terminal_identity.py
3
+ ==================================
4
+ Centralized Terminal & Tab Identity management for CAT CLI.
5
+
6
+ Guarantees that the active terminal tab and window title display:
7
+ 🐱 CAT CLI (or CAT CLI)
8
+ across Windows Terminal, Command Prompt (cmd.exe), PowerShell, and POSIX
9
+ terminals, preventing generic host shell names ('Command Prompt',
10
+ 'PowerShell', 'cmd.exe', 'pwsh') from leaking or polluting the tab/window.
11
+
12
+ Features:
13
+ - Emits UTF-8 OSC 0 and OSC 2 escape sequences safely without Windows cp1252 crashes.
14
+ - Calls Win32 kernel32.SetConsoleTitleW for conhost / ConPTY consistency.
15
+ - Loads and sets the official CAT logo (.ico) via WM_SETICON on the console HWND.
16
+ - Detects and registers the CAT CLI profile in Windows Terminal settings.json.
17
+ - Runs a lightweight TitleGuard daemon thread to recover if external processes
18
+ (e.g. child cmd.exe / npm / git) overwrite the console title.
19
+ """
20
+
21
+ import atexit
22
+ import os
23
+ import sys
24
+ import threading
25
+ import time
26
+ from typing import Optional, Tuple
27
+
28
+ BRAND_NAME = "CAT CLI"
29
+ BRAND_EMOJI = "🐱"
30
+ DEFAULT_TITLE = f"{BRAND_EMOJI} {BRAND_NAME}"
31
+ PLAIN_TITLE = BRAND_NAME
32
+ WT_CAT_GUID = "{a3f2b4c1-d8e7-4f9a-8b1c-3d5e7f9a2b4c}"
33
+
34
+ _current_active_title: str = DEFAULT_TITLE
35
+ _title_guard_thread: Optional[threading.Thread] = None
36
+ _title_guard_stop_event = threading.Event()
37
+ _title_guard_lock = threading.Lock()
38
+ _title_saved: bool = False
39
+
40
+
41
+ def _get_local_appdata_dir() -> str:
42
+ local_app = os.environ.get("LOCALAPPDATA")
43
+ if local_app:
44
+ base = os.path.join(local_app, "CCT")
45
+ else:
46
+ base = os.path.expanduser("~/.cct")
47
+ try:
48
+ os.makedirs(base, exist_ok=True)
49
+ except Exception:
50
+ pass
51
+ return base
52
+
53
+
54
+ def get_icon_path() -> Optional[str]:
55
+ """Return the absolute path to the multi-resolution CAT .ico file,
56
+ ensuring it exists in a stable location."""
57
+ # 1. Check local CCT app data
58
+ local_dir = _get_local_appdata_dir()
59
+ local_ico = os.path.join(local_dir, "cat.ico")
60
+ if os.path.isfile(local_ico) and os.path.getsize(local_ico) > 0:
61
+ return os.path.abspath(local_ico)
62
+
63
+ # 2. Check bundled package paths
64
+ pkg_dir = os.path.dirname(os.path.abspath(__file__))
65
+ candidates = [
66
+ os.path.join(pkg_dir, "cat.ico"),
67
+ os.path.join(pkg_dir, "web", "static", "icons", "cat.ico"),
68
+ ]
69
+ for c in candidates:
70
+ if os.path.isfile(c) and os.path.getsize(c) > 0:
71
+ # Copy to local appdata for persistent access by external apps (Windows Terminal)
72
+ try:
73
+ import shutil
74
+ shutil.copy2(c, local_ico)
75
+ return os.path.abspath(local_ico)
76
+ except Exception:
77
+ return os.path.abspath(c)
78
+
79
+ # 3. Try generating from icon-512.png if PIL is available
80
+ png_source = os.path.join(pkg_dir, "web", "static", "icons", "icon-512.png")
81
+ if os.path.isfile(png_source):
82
+ try:
83
+ from PIL import Image
84
+ img = Image.open(png_source)
85
+ sizes = [(16, 16), (24, 24), (32, 32), (48, 48), (64, 64), (128, 128), (256, 256)]
86
+ img.save(local_ico, sizes=sizes)
87
+ return os.path.abspath(local_ico)
88
+ except Exception:
89
+ pass
90
+
91
+ return None
92
+
93
+
94
+ def apply_console_icon(ico_path: Optional[str] = None) -> bool:
95
+ """Set the Windows console window icon via Win32 WM_SETICON."""
96
+ if sys.platform != "win32":
97
+ return False
98
+ try:
99
+ import ctypes
100
+ kernel32 = ctypes.windll.kernel32
101
+ user32 = ctypes.windll.user32
102
+
103
+ hwnd = kernel32.GetConsoleWindow()
104
+ if not hwnd:
105
+ return False
106
+
107
+ if not ico_path:
108
+ ico_path = get_icon_path()
109
+ if not ico_path or not os.path.isfile(ico_path):
110
+ return False
111
+
112
+ IMAGE_ICON = 1
113
+ LR_LOADFROMFILE = 0x00000010
114
+ WM_SETICON = 0x0080
115
+ ICON_SMALL = 0
116
+ ICON_BIG = 1
117
+
118
+ # Load small (16x16) and big (32x32) icons
119
+ hicon_small = user32.LoadImageW(None, ico_path, IMAGE_ICON, 16, 16, LR_LOADFROMFILE)
120
+ hicon_big = user32.LoadImageW(None, ico_path, IMAGE_ICON, 32, 32, LR_LOADFROMFILE)
121
+
122
+ success = False
123
+ if hicon_small:
124
+ user32.SendMessageW(hwnd, WM_SETICON, ICON_SMALL, hicon_small)
125
+ success = True
126
+ if hicon_big:
127
+ user32.SendMessageW(hwnd, WM_SETICON, ICON_BIG, hicon_big)
128
+ success = True
129
+ return success
130
+ except Exception:
131
+ return False
132
+
133
+
134
+ def _safe_write_osc(text: str) -> bool:
135
+ """Write OSC title escape sequences using raw UTF-8 bytes to prevent
136
+ Windows default cp1252 UnicodeEncodeError crashes."""
137
+ try:
138
+ if os.environ.get("TERM", "") == "dumb":
139
+ return False
140
+
141
+ is_tty = False
142
+ try:
143
+ is_tty = bool(getattr(sys.stdout, "isatty", lambda: False)())
144
+ except Exception:
145
+ is_tty = False
146
+
147
+ if not is_tty:
148
+ return False
149
+
150
+ # Clean control characters
151
+ clean = "".join(ch for ch in str(text) if ch.isprintable() and ch not in "\x07\x1b")[:100]
152
+
153
+ # Push title once on xterm if supported
154
+ global _title_saved
155
+ push_seq = b"\x1b[22t" if not _title_saved else b""
156
+ if not _title_saved:
157
+ _title_saved = True
158
+
159
+ # OSC 0 (window & icon) and OSC 2 (window title)
160
+ osc_seq = f"\x1b]0;{clean}\x07\x1b]2;{clean}\x07".encode("utf-8")
161
+
162
+ payload = push_seq + osc_seq
163
+ # Prefer writing directly to buffer for UTF-8 reliability
164
+ buf = getattr(sys.stdout, "buffer", None)
165
+ if buf is not None:
166
+ buf.write(payload)
167
+ buf.flush()
168
+ else:
169
+ try:
170
+ sys.stdout.write(f"\x1b]0;{clean}\x07")
171
+ sys.stdout.flush()
172
+ except UnicodeEncodeError:
173
+ fallback = clean.encode("ascii", errors="ignore").decode("ascii").strip()
174
+ sys.stdout.write(f"\x1b]0;{fallback}\x07")
175
+ sys.stdout.flush()
176
+ return True
177
+ except Exception:
178
+ return False
179
+
180
+
181
+ def set_terminal_title(title: Optional[str] = None, with_icon: bool = True) -> bool:
182
+ """Set the terminal tab and window title to '🐱 CAT CLI' (or custom title).
183
+ Centralized function that handles:
184
+ 1. OSC 0 and OSC 2 terminal sequences (UTF-8 safe)
185
+ 2. Win32 SetConsoleTitleW
186
+ 3. Win32 console WM_SETICON (on Windows)
187
+ """
188
+ global _current_active_title
189
+
190
+ if title is None or not str(title).strip():
191
+ chosen = DEFAULT_TITLE if with_icon else PLAIN_TITLE
192
+ else:
193
+ t = str(title).strip()
194
+ # If the user passed a sub-title like 'Starting' or 'Ready', brand it nicely
195
+ if t.lower() in ("starting", "ready", "running", "exiting"):
196
+ chosen = f"{DEFAULT_TITLE} — {t.capitalize()}"
197
+ elif not (t.startswith(BRAND_EMOJI) or BRAND_NAME in t):
198
+ chosen = f"{DEFAULT_TITLE} — {t}"
199
+ else:
200
+ chosen = t
201
+
202
+ _current_active_title = chosen
203
+
204
+ # 1. OSC sequences (for Windows Terminal, modern conhost, Mintty, VS Code, iTerm, etc.)
205
+ osc_ok = _safe_write_osc(chosen)
206
+
207
+ # 2. Win32 SetConsoleTitleW (for classic conhost, cmd.exe, PowerShell, and ConPTY)
208
+ win32_ok = False
209
+ if sys.platform == "win32":
210
+ try:
211
+ import ctypes
212
+ ctypes.windll.kernel32.SetConsoleTitleW(chosen)
213
+ win32_ok = True
214
+ apply_console_icon()
215
+ except Exception:
216
+ pass
217
+
218
+ return osc_ok or win32_ok
219
+
220
+
221
+ def restore_terminal_title() -> bool:
222
+ """Restore previous terminal title on exit (xterm title stack pop)."""
223
+ global _title_saved
224
+ if not _title_saved:
225
+ return False
226
+ try:
227
+ buf = getattr(sys.stdout, "buffer", None)
228
+ if buf is not None:
229
+ buf.write(b"\x1b[23t")
230
+ buf.flush()
231
+ else:
232
+ sys.stdout.write("\x1b[23t")
233
+ sys.stdout.flush()
234
+ _title_saved = False
235
+ return True
236
+ except Exception:
237
+ _title_saved = False
238
+ return False
239
+
240
+
241
+ def get_current_console_title() -> Optional[str]:
242
+ """Retrieve the current console title via Win32 GetConsoleTitleW."""
243
+ if sys.platform != "win32":
244
+ return None
245
+ try:
246
+ import ctypes
247
+ buf = ctypes.create_unicode_buffer(1024)
248
+ length = ctypes.windll.kernel32.GetConsoleTitleW(buf, 1024)
249
+ if length > 0:
250
+ return buf.value
251
+ except Exception:
252
+ pass
253
+ return None
254
+
255
+
256
+ def _is_polluted_title(current: Optional[str]) -> bool:
257
+ """Check if the title currently contains a generic shell or subprocess name."""
258
+ if not current:
259
+ return True
260
+ s = current.strip().lower()
261
+ # If it contains CAT CLI, it's good
262
+ if "cat cli" in s or "coding agent terminal" in s:
263
+ return False
264
+ # Polluted shell names
265
+ polluters = [
266
+ "command prompt",
267
+ "powershell",
268
+ "cmd.exe",
269
+ "pwsh",
270
+ "windows powershell",
271
+ "npm",
272
+ "node",
273
+ "git",
274
+ "python",
275
+ ]
276
+ return any(p in s for p in polluters)
277
+
278
+
279
+ def _title_guard_worker(interval: float = 1.0):
280
+ """Background worker that continuously asserts the CAT CLI title
281
+ if an external subprocess or shell clobbers it."""
282
+ while not _title_guard_stop_event.is_set():
283
+ try:
284
+ if sys.platform == "win32":
285
+ curr = get_current_console_title()
286
+ if _is_polluted_title(curr):
287
+ set_terminal_title(_current_active_title)
288
+ except Exception:
289
+ pass
290
+ _title_guard_stop_event.wait(interval)
291
+
292
+
293
+ def start_title_guard(interval: float = 1.0):
294
+ """Start the background thread that guards against title pollution."""
295
+ global _title_guard_thread
296
+ with _title_guard_lock:
297
+ if _title_guard_thread is not None and _title_guard_thread.is_alive():
298
+ return
299
+ _title_guard_stop_event.clear()
300
+ _title_guard_thread = threading.Thread(
301
+ target=_title_guard_worker,
302
+ args=(interval,),
303
+ name="CAT-TitleGuard",
304
+ daemon=True,
305
+ )
306
+ _title_guard_thread.start()
307
+
308
+
309
+ def stop_title_guard():
310
+ """Stop the title guard thread."""
311
+ with _title_guard_lock:
312
+ _title_guard_stop_event.set()
313
+
314
+
315
+ # Ensure title guard stops cleanly on exit
316
+ atexit.register(stop_title_guard)
317
+
318
+
319
+ def find_windows_terminal_settings() -> Optional[str]:
320
+ """Locate the active Windows Terminal settings.json file."""
321
+ if sys.platform != "win32":
322
+ return None
323
+
324
+ local_app = os.environ.get("LOCALAPPDATA", "")
325
+ candidates = [
326
+ # Windows Terminal (Stable)
327
+ os.path.join(local_app, "Packages", "Microsoft.WindowsTerminal_8wekyb3d8bbwe", "LocalState", "settings.json"),
328
+ # Windows Terminal (Preview)
329
+ os.path.join(local_app, "Packages", "Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe", "LocalState", "settings.json"),
330
+ # Unpackaged / Scoop / Winget portable
331
+ os.path.join(local_app, "Microsoft", "Windows Terminal", "settings.json"),
332
+ ]
333
+ for c in candidates:
334
+ if os.path.isfile(c):
335
+ return c
336
+ return None
337
+
338
+
339
+ def configure_windows_terminal_profile() -> Tuple[bool, str]:
340
+ """Register or update the dedicated 'CAT CLI' profile in Windows Terminal settings.json.
341
+ Sets:
342
+ - name: "CAT CLI"
343
+ - commandline: "cat"
344
+ - icon: path to cat.ico
345
+ - tabTitle: "CAT CLI"
346
+ - suppressApplicationTitle: false
347
+ """
348
+ settings_file = find_windows_terminal_settings()
349
+ if not settings_file:
350
+ return False, "Windows Terminal settings.json not found."
351
+
352
+ ico_path = get_icon_path()
353
+ if not ico_path:
354
+ return False, "CAT icon (.ico) not available."
355
+
356
+ try:
357
+ import json
358
+ with open(settings_file, "r", encoding="utf-8-sig") as f:
359
+ data = json.load(f)
360
+
361
+ profiles = data.setdefault("profiles", {})
362
+ plist = profiles.setdefault("list", [])
363
+
364
+ # Find existing profile
365
+ target_profile = None
366
+ for p in plist:
367
+ if p.get("guid") == WT_CAT_GUID or p.get("name") == BRAND_NAME:
368
+ target_profile = p
369
+ break
370
+
371
+ if target_profile is None:
372
+ target_profile = {
373
+ "guid": WT_CAT_GUID,
374
+ "name": BRAND_NAME,
375
+ "commandline": "cat",
376
+ "icon": ico_path,
377
+ "tabTitle": BRAND_NAME,
378
+ "suppressApplicationTitle": False,
379
+ }
380
+ plist.append(target_profile)
381
+ action_desc = "Created new 'CAT CLI' profile"
382
+ else:
383
+ target_profile["name"] = BRAND_NAME
384
+ target_profile["icon"] = ico_path
385
+ target_profile["tabTitle"] = BRAND_NAME
386
+ action_desc = "Updated existing 'CAT CLI' profile"
387
+
388
+ # Atomic write back
389
+ tmp_file = settings_file + ".tmp"
390
+ with open(tmp_file, "w", encoding="utf-8") as f:
391
+ json.dump(data, f, indent=4)
392
+ os.replace(tmp_file, settings_file)
393
+ return True, f"{action_desc} in Windows Terminal ({settings_file})."
394
+ except Exception as e:
395
+ return False, f"Failed to configure Windows Terminal profile: {e}"
396
+
397
+
398
+ def init_terminal_identity():
399
+ """High-level initialization called on CAT CLI startup."""
400
+ # 1. Set the initial title immediately
401
+ set_terminal_title()
402
+
403
+ # 2. Configure Windows Terminal profile if running on Windows
404
+ if sys.platform == "win32":
405
+ try:
406
+ configure_windows_terminal_profile()
407
+ except Exception:
408
+ pass
409
+
410
+ # 3. Start title guard to protect against subprocess/shell pollution
411
+ start_title_guard()