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,171 @@
1
+ """
2
+ CAT v0.8.a — Screen Capture Engine.
3
+
4
+ Manages getDisplayMedia() / getMedia() capture via the frontend.
5
+ The server side handles config, state, and fallback logic.
6
+ Actual pixel capture happens in the browser; the server receives
7
+ base64-encoded frames via WebSocket or HTTP POST.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import asyncio
13
+ import time
14
+ from dataclasses import dataclass, field
15
+ from enum import Enum
16
+ from typing import Any, Callable, Dict, List, Optional
17
+
18
+
19
+ class CaptureMode(str, Enum):
20
+ DISPLAY = "display" # getDisplayMedia — full screen / window / tab
21
+ MEDIA = "media" # getMedia with displaySurface hints
22
+ AUDIO_VIDEO = "audio_video"
23
+ AUDIO_ONLY = "audio_only"
24
+ TAB = "tab" # Browser tab capture
25
+ WINDOW = "window" # Specific window capture
26
+ NONE = "none" # No capture available
27
+
28
+
29
+ @dataclass
30
+ class CaptureConfig:
31
+ mode: CaptureMode = CaptureMode.DISPLAY
32
+ fps: float = 1.0
33
+ quality: int = 85
34
+ max_frames: int = 500
35
+ width: Optional[int] = None
36
+ height: Optional[int] = None
37
+ auto_analyze: bool = True
38
+ change_threshold: float = 0.05
39
+ blur_detection: bool = True
40
+ max_session_minutes: int = 60
41
+ privacy_mode: bool = True
42
+ suppress_notifications: bool = True
43
+ suppress_system_audio: bool = False
44
+ cursor_capture: bool = True
45
+ annotations_enabled: bool = True
46
+ saved_frames_max: int = 20
47
+ context_window_frames: int = 5
48
+
49
+ def to_dict(self) -> Dict[str, Any]:
50
+ return {
51
+ "mode": self.mode.value,
52
+ "fps": self.fps,
53
+ "quality": self.quality,
54
+ "max_frames": self.max_frames,
55
+ "width": self.width,
56
+ "height": self.height,
57
+ "auto_analyze": self.auto_analyze,
58
+ "change_threshold": self.change_threshold,
59
+ "blur_detection": self.blur_detection,
60
+ "max_session_minutes": self.max_session_minutes,
61
+ "privacy_mode": self.privacy_mode,
62
+ "suppress_notifications": self.suppress_notifications,
63
+ "cursor_capture": self.cursor_capture,
64
+ "annotations_enabled": self.annotations_enabled,
65
+ }
66
+
67
+
68
+ class CaptureEngine:
69
+ """Server-side capture controller.
70
+
71
+ The actual screen capture runs in the browser via getDisplayMedia().
72
+ This engine:
73
+ 1. Generates the frontend capture config/instructions
74
+ 2. Processes incoming frames from the browser
75
+ 3. Detects capture capability and falls back gracefully
76
+ """
77
+
78
+ def __init__(self, config: CaptureConfig):
79
+ self.config = config
80
+ self.paused = False
81
+ self._initialized = False
82
+ self._frame_callback: Optional[Callable] = None
83
+ self._stats = {
84
+ "frames_received": 0,
85
+ "frames_dropped": 0,
86
+ "bytes_received": 0,
87
+ "errors": 0,
88
+ "started_at": None,
89
+ }
90
+
91
+ async def initialize(self):
92
+ self._initialized = True
93
+ self._stats["started_at"] = time.time()
94
+
95
+ async def cleanup(self):
96
+ self._initialized = False
97
+ self._frame_callback = None
98
+
99
+ def get_capture_config(self) -> Dict[str, Any]:
100
+ """Return config dict for the frontend to initialize getDisplayMedia."""
101
+ display_surface = "monitor"
102
+ if self.config.mode == CaptureMode.TAB:
103
+ display_surface = "browser"
104
+ elif self.config.mode == CaptureMode.WINDOW:
105
+ display_surface = "window"
106
+
107
+ return {
108
+ "video": {
109
+ "displaySurface": display_surface,
110
+ "logicalSurface": True,
111
+ "cursor": "always" if self.config.cursor_capture else "never",
112
+ "noiseSuppression": not self.config.suppress_system_audio,
113
+ "suppressLocalAudioPlayback": self.config.suppress_system_audio,
114
+ "selfBrowserSurface": "include",
115
+ "systemAudio": "exclude" if self.config.suppress_system_audio else "include",
116
+ "preferCurrentTab": self.config.mode == CaptureMode.TAB,
117
+ },
118
+ "audio": self.config.mode in (
119
+ CaptureMode.AUDIO_VIDEO, CaptureMode.AUDIO_ONLY),
120
+ "videoConstraints": {
121
+ "width": {"ideal": self.config.width or 1920},
122
+ "height": {"ideal": self.config.height or 1080},
123
+ "frameRate": {"ideal": self.config.fps, "max": self.config.fps * 2},
124
+ },
125
+ }
126
+
127
+ def get_capability_report(self) -> Dict[str, Any]:
128
+ """Report what capture modes the frontend should attempt."""
129
+ return {
130
+ "display_api": True,
131
+ "media_api": True,
132
+ "audio_capture": self.config.mode in (
133
+ CaptureMode.AUDIO_VIDEO, CaptureMode.AUDIO_ONLY),
134
+ "tab_capture": True,
135
+ "window_capture": True,
136
+ "frame_rate": self.config.fps,
137
+ "max_resolution": f"{self.config.width or 1920}x{self.config.height or 1080}",
138
+ "browser_requirements": "getDisplayMedia() requires HTTPS or localhost",
139
+ }
140
+
141
+ def process_incoming_frame(self, frame_b64: str,
142
+ timestamp: Optional[float] = None,
143
+ metadata: Optional[Dict] = None) -> Dict[str, Any]:
144
+ """Process a base64 frame received from the browser."""
145
+ if self.paused:
146
+ self._stats["frames_dropped"] += 1
147
+ return {"processed": False, "reason": "paused"}
148
+
149
+ self._stats["frames_received"] += 1
150
+ self._stats["bytes_received"] += len(frame_b64)
151
+
152
+ return {
153
+ "processed": True,
154
+ "frame_index": self._stats["frames_received"],
155
+ "size_bytes": len(frame_b64),
156
+ "timestamp": timestamp or time.time(),
157
+ "metadata": metadata or {},
158
+ }
159
+
160
+ @property
161
+ def stats(self) -> Dict[str, Any]:
162
+ elapsed = 0.0
163
+ if self._stats["started_at"]:
164
+ elapsed = time.time() - self._stats["started_at"]
165
+ return {
166
+ **self._stats,
167
+ "elapsed_seconds": round(elapsed, 2),
168
+ "avg_fps": round(
169
+ self._stats["frames_received"] / max(elapsed, 0.01), 2
170
+ ),
171
+ }
@@ -0,0 +1,231 @@
1
+ """
2
+ CAT v0.8.a — Vision Context Engine.
3
+
4
+ Fuses screen frame + code context + project structure + chat history
5
+ into a single VisualContext object for AI analysis.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import base64
11
+ import time
12
+ from dataclasses import dataclass, field
13
+ from typing import Any, Dict, List, Optional
14
+
15
+
16
+ @dataclass
17
+ class VisualContext:
18
+ """Complete visual context for AI analysis."""
19
+ frame_b64: str
20
+ frame_meta: Dict[str, Any]
21
+ annotations: List[Dict[str, Any]]
22
+ code_context: Optional[Dict[str, Any]] = None
23
+ project_context: Optional[Dict[str, Any]] = None
24
+ chat_history: List[Dict[str, str]] = field(default_factory=list)
25
+ pointer_telemetry: Optional[Dict[str, Any]] = None
26
+ before_frame_b64: Optional[str] = None
27
+ before_meta: Optional[Dict[str, Any]] = None
28
+ analysis_prompt: str = ""
29
+ created_at: float = 0.0
30
+
31
+ def __post_init__(self):
32
+ if self.created_at == 0.0:
33
+ self.created_at = time.time()
34
+
35
+ def to_dict(self, include_frames: bool = False) -> Dict[str, Any]:
36
+ result = {
37
+ "frame_meta": self.frame_meta,
38
+ "annotations": self.annotations,
39
+ "code_context": self.code_context,
40
+ "project_context": self.project_context,
41
+ "chat_history": self.chat_history[-10:],
42
+ "pointer_telemetry": self.pointer_telemetry,
43
+ "analysis_prompt": self.analysis_prompt,
44
+ "created_at": self.created_at,
45
+ "has_frame": bool(self.frame_b64),
46
+ "has_before_frame": bool(self.before_frame_b64),
47
+ }
48
+ if include_frames:
49
+ result["frame_b64"] = self.frame_b64
50
+ result["before_frame_b64"] = self.before_frame_b64
51
+ return result
52
+
53
+ def to_ai_messages(self) -> List[Dict[str, Any]]:
54
+ """Build the message payload for the AI vision model."""
55
+ messages = []
56
+ system_parts = [
57
+ "You are CAT Vision Agent. Analyze the screen state and provide "
58
+ "actionable insights. Be specific about UI elements, errors, and "
59
+ "suggested fixes."
60
+ ]
61
+ if self.code_context:
62
+ system_parts.append(
63
+ f"\nActive file: {self.code_context.get('file_path', 'unknown')}"
64
+ )
65
+ if self.code_context.get("cursor_line"):
66
+ system_parts.append(
67
+ f"Cursor at line: {self.code_context['cursor_line']}"
68
+ )
69
+ if self.project_context:
70
+ system_parts.append(
71
+ f"Project type: {self.project_context.get('type', 'unknown')}"
72
+ )
73
+ messages.append({"role": "system", "content": "\n".join(system_parts)})
74
+
75
+ user_parts = []
76
+ if self.analysis_prompt:
77
+ user_parts.append(self.analysis_prompt)
78
+ else:
79
+ user_parts.append(
80
+ "Analyze this screen capture. Describe what you see, identify "
81
+ "any issues, and suggest next steps."
82
+ )
83
+
84
+ if self.pointer_telemetry:
85
+ pt = self.pointer_telemetry
86
+ if pt.get("active_element"):
87
+ user_parts.append(
88
+ f"\nFocused element: {pt['active_element']}"
89
+ )
90
+ if pt.get("cursor_moving"):
91
+ user_parts.append("\nUser is actively moving the cursor.")
92
+
93
+ if self.annotations:
94
+ ann_desc = []
95
+ for ann in self.annotations:
96
+ tool = ann.get("tool", "")
97
+ if tool == "text":
98
+ ann_desc.append(f"Text annotation: {ann.get('text', '')}")
99
+ elif tool == "arrow":
100
+ ann_desc.append("Arrow annotation")
101
+ elif tool == "highlight":
102
+ ann_desc.append("Highlighted region")
103
+ else:
104
+ ann_desc.append(f"{tool} annotation")
105
+ user_parts.append(
106
+ "\nUser annotations: " + "; ".join(ann_desc)
107
+ )
108
+
109
+ user_content = [
110
+ {"type": "text", "text": "\n".join(user_parts)}
111
+ ]
112
+
113
+ if self.frame_b64:
114
+ mime = "image/png"
115
+ user_content.append({
116
+ "type": "image_url",
117
+ "image_url": {
118
+ "url": f"data:{mime};base64,{self.frame_b64}",
119
+ "detail": "high",
120
+ }
121
+ })
122
+
123
+ if self.before_frame_b64:
124
+ user_content.append({
125
+ "type": "text",
126
+ "text": "\n--- BEFORE state (for comparison) ---"
127
+ })
128
+ user_content.append({
129
+ "type": "image_url",
130
+ "image_url": {
131
+ "url": f"data:image/png;base64,{self.before_frame_b64}",
132
+ "detail": "high",
133
+ }
134
+ })
135
+
136
+ messages.append({"role": "user", "content": user_content})
137
+ return messages
138
+
139
+
140
+ class VisionContextEngine:
141
+ """Builds VisualContext from multiple sources."""
142
+
143
+ def __init__(self):
144
+ self._code_context: Optional[Dict[str, Any]] = None
145
+ self._project_context: Optional[Dict[str, Any]] = None
146
+ self._chat_history: List[Dict[str, str]] = []
147
+ self._max_chat = 20
148
+
149
+ def set_code_context(self, file_path: str = "", content: str = "",
150
+ cursor_line: int = 0, language: str = "",
151
+ diagnostics: Optional[List[str]] = None):
152
+ self._code_context = {
153
+ "file_path": file_path,
154
+ "content": content[:5000] if content else "",
155
+ "cursor_line": cursor_line,
156
+ "language": language,
157
+ "diagnostics": diagnostics or [],
158
+ }
159
+
160
+ def set_project_context(self, project_type: str = "",
161
+ structure: Optional[Dict] = None,
162
+ dependencies: Optional[List[str]] = None):
163
+ self._project_context = {
164
+ "type": project_type,
165
+ "structure": structure or {},
166
+ "dependencies": dependencies or [],
167
+ }
168
+
169
+ def add_chat_turn(self, role: str, content: str):
170
+ self._chat_history.append({"role": role, "content": content[:2000]})
171
+ if len(self._chat_history) > self._max_chat:
172
+ self._chat_history = self._chat_history[-self._max_chat:]
173
+
174
+ def build_context(self, frame_b64: str,
175
+ frame_meta: Optional[Dict] = None,
176
+ annotations: Optional[List[Dict]] = None,
177
+ pointer_telemetry: Optional[Dict] = None,
178
+ analysis_prompt: str = "",
179
+ before_frame_b64: Optional[str] = None,
180
+ before_meta: Optional[Dict] = None) -> VisualContext:
181
+ return VisualContext(
182
+ frame_b64=frame_b64,
183
+ frame_meta=frame_meta or {},
184
+ annotations=annotations or [],
185
+ code_context=self._code_context,
186
+ project_context=self._project_context,
187
+ chat_history=list(self._chat_history),
188
+ pointer_telemetry=pointer_telemetry,
189
+ analysis_prompt=analysis_prompt,
190
+ before_frame_b64=before_frame_b64,
191
+ before_meta=before_meta,
192
+ )
193
+
194
+ def build_visual_debug_prompt(self, error_message: str = "",
195
+ user_intent: str = "") -> str:
196
+ parts = [
197
+ "VISUAL DEBUG SESSION — Analyze the current screen state.",
198
+ "The user is experiencing an issue or wants to understand what's on screen.",
199
+ ]
200
+ if error_message:
201
+ parts.append(f"\nError reported: {error_message}")
202
+ if user_intent:
203
+ parts.append(f"\nUser intent: {user_intent}")
204
+ parts.append(
205
+ "\nProvide:\n"
206
+ "1. What you see on screen (UI elements, text, state)\n"
207
+ "2. Any errors, warnings, or anomalies\n"
208
+ "3. Root cause hypothesis\n"
209
+ "4. Specific fix suggestion with code if applicable\n"
210
+ "5. Before/after comparison if previous frame is available"
211
+ )
212
+ return "\n".join(parts)
213
+
214
+ def build_code_review_prompt(self, file_path: str = "",
215
+ code: str = "") -> str:
216
+ parts = [
217
+ "CODE-VISUAL REVIEW — The screen shows the current code state.",
218
+ "Review the visible code and provide feedback.",
219
+ ]
220
+ if file_path:
221
+ parts.append(f"\nFile: {file_path}")
222
+ if code:
223
+ parts.append(f"\nCode preview:\n```\n{code[:3000]}\n```")
224
+ parts.append(
225
+ "\nAnalyze:\n"
226
+ "1. Code quality and correctness\n"
227
+ "2. Visual rendering issues (if web app)\n"
228
+ "3. UI/UX observations\n"
229
+ "4. Suggested improvements"
230
+ )
231
+ return "\n".join(parts)
@@ -0,0 +1,169 @@
1
+ """
2
+ CAT v0.8.a — Screen → Code Correlation (spec 20).
3
+
4
+ Maps a visual annotation/cursor region to likely project files by:
5
+ - DOM/preview context (if live preview is running)
6
+ - filename / component heuristics
7
+ - workspace search for component names near the annotation label
8
+
9
+ Never hallucinates an exact source location from pixels alone.
10
+ Returns a ranked list with confidence + evidence.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import os
16
+ import re
17
+ from pathlib import Path
18
+ from typing import Any, Dict, List, Optional, Tuple
19
+
20
+
21
+ def _norm_annotation_label(ann: Dict[str, Any]) -> str:
22
+ return (ann.get("text") or ann.get("label") or ann.get("description") or "").strip()
23
+
24
+
25
+ def _guess_component_from_label(label: str) -> List[str]:
26
+ """Extract likely component/file hints from a short label like 'Submit button glitches'."""
27
+ if not label:
28
+ return []
29
+ words = re.findall(r"[A-Za-z][A-Za-z0-9_-]*", label)
30
+ # keep capitalized or component-like words, also "button", "form", "submit"
31
+ hints = []
32
+ for w in words:
33
+ lw = w.lower()
34
+ if len(w) >= 3 and lw not in {"this", "that", "when", "with", "glitches", "broken", "jumps"}:
35
+ hints.append(w)
36
+ return hints[:8]
37
+
38
+
39
+ def _search_workspace(hints: List[str], root: str, max_hits: int = 12) -> List[Dict[str, Any]]:
40
+ """Cheap filename + grep search. Returns [{path, reason, score}]."""
41
+ if not root or not os.path.isdir(root):
42
+ return []
43
+ root_p = Path(root)
44
+ results: List[Dict[str, Any]] = []
45
+ # filename scan
46
+ all_files = []
47
+ for p in root_p.rglob("*"):
48
+ if p.is_file() and p.suffix.lower() in {".tsx",".ts",".jsx",".js",".py",".html",".css",".vue",".svelte"}:
49
+ if any(part.startswith(".") or part == "node_modules" or part == "__pycache__" for part in p.parts):
50
+ continue
51
+ all_files.append(p)
52
+ if len(all_files) > 8000:
53
+ break
54
+ for hint in hints:
55
+ hl = hint.lower()
56
+ for f in all_files:
57
+ name_l = f.name.lower()
58
+ if hl in name_l or hl.replace("-","") in name_l.replace("-",""):
59
+ results.append({"path": str(f.relative_to(root_p)), "reason": f"filename contains '{hint}'", "score": 0.7})
60
+ if len(results) >= max_hits:
61
+ return results
62
+ # content grep for component names (cheap, capped)
63
+ for hint in hints[:3]:
64
+ hl = hint.lower()
65
+ if len(hl) < 3:
66
+ continue
67
+ for f in all_files[:1200]:
68
+ try:
69
+ text = f.read_text(encoding="utf-8", errors="ignore")[:20000]
70
+ except Exception:
71
+ continue
72
+ if hl in text.lower():
73
+ # boost if it's a component definition
74
+ score = 0.6
75
+ if re.search(rf"(class|function|const)\s+{re.escape(hint)}\b", text):
76
+ score = 0.85
77
+ elif re.search(rf"<{re.escape(hint)}\b", text):
78
+ score = 0.75
79
+ results.append({"path": str(f.relative_to(root_p)), "reason": f"mentions '{hint}'", "score": score})
80
+ if len(results) >= max_hits:
81
+ return results
82
+ # dedupe, keep highest score per path
83
+ best: Dict[str, Dict] = {}
84
+ for r in results:
85
+ prev = best.get(r["path"])
86
+ if prev is None or r["score"] > prev["score"]:
87
+ best[r["path"]] = r
88
+ ranked = sorted(best.values(), key=lambda r: r["score"], reverse=True)
89
+ return ranked[:max_hits]
90
+
91
+
92
+ def correlate(
93
+ annotations: List[Dict[str, Any]],
94
+ cursor: Optional[Dict[str, Any]] = None,
95
+ preview_context: Optional[Dict[str, Any]] = None,
96
+ workspace_root: Optional[str] = None,
97
+ ) -> Dict[str, Any]:
98
+ """
99
+ Returns {
100
+ "has_mapping": bool,
101
+ "candidates": [{path, reason, score}],
102
+ "preview_hint": str | None,
103
+ "notes": str # honest about uncertainty
104
+ }
105
+ """
106
+ # preview / DOM hint is strongest — when live preview controller knows the entry URL + component
107
+ preview_hint = None
108
+ candidates: List[Dict[str, Any]] = []
109
+ notes_parts: List[str] = []
110
+
111
+ if preview_context:
112
+ # preview_context may contain entry file / component map produced by browser/preview.py
113
+ entry = preview_context.get("entry") or preview_context.get("url") or ""
114
+ if entry:
115
+ preview_hint = entry
116
+ candidates.append({"path": entry, "reason": "live preview entry", "score": 0.65})
117
+ dom = preview_context.get("dom_match") or preview_context.get("selector") or ""
118
+ if dom:
119
+ notes_parts.append(f"DOM context: {dom}")
120
+
121
+ # annotation labels → hints → workspace search
122
+ all_hints: List[str] = []
123
+ for ann in (annotations or []):
124
+ label = _norm_annotation_label(ann)
125
+ hints = _guess_component_from_label(label)
126
+ tool = ann.get("tool", "")
127
+ if tool:
128
+ hints.append(tool)
129
+ all_hints.extend(hints)
130
+
131
+ # also from cursor active_element
132
+ if cursor and cursor.get("active_element"):
133
+ all_hints.append(str(cursor["active_element"])[:40])
134
+
135
+ # dedupe hints
136
+ seen = set()
137
+ uniq_hints: List[str] = []
138
+ for h in all_hints:
139
+ hl = h.lower()
140
+ if hl not in seen:
141
+ seen.add(hl)
142
+ uniq_hints.append(h)
143
+
144
+ if uniq_hints and workspace_root:
145
+ hits = _search_workspace(uniq_hints, workspace_root, max_hits=10)
146
+ candidates.extend(hits)
147
+
148
+ # sort + dedupe candidates
149
+ best: Dict[str, Dict] = {}
150
+ for c in candidates:
151
+ prev = best.get(c["path"])
152
+ if prev is None or c["score"] > prev["score"]:
153
+ best[c["path"]] = c
154
+ ranked = sorted(best.values(), key=lambda c: c["score"], reverse=True)
155
+
156
+ has_mapping = bool(ranked and ranked[0]["score"] >= 0.6)
157
+ if not ranked:
158
+ notes_parts.append("No code mapping could be inferred from the current visual context.")
159
+ notes_parts.append("Try adding a short annotation label (e.g. 'Submit button') or opening the preview so CAT can see the component tree.")
160
+ elif not has_mapping:
161
+ notes_parts.append("Weak mapping only — candidates are guesses, not exact source locations.")
162
+
163
+ return {
164
+ "has_mapping": has_mapping,
165
+ "candidates": ranked[:8],
166
+ "preview_hint": preview_hint,
167
+ "hints_used": uniq_hints[:10],
168
+ "notes": " ".join(notes_parts),
169
+ }