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,89 @@
1
+ """
2
+ CAT v0.8.a Vision Agent — screen capture, frame pipeline, annotation,
3
+ visual context, and before/after verification.
4
+
5
+ Usage (server):
6
+ from calc_terminal.vision.session import VisionSessionManager
7
+ from calc_terminal.vision.context import VisionContextEngine
8
+ from calc_terminal.vision.verify import BeforeAfterVerifier
9
+
10
+ Usage (frontend JS calls /api/vision/* endpoints on the server).
11
+ """
12
+
13
+ __version__ = "0.8.a"
14
+
15
+ from .session import VisionSessionManager, VisionSession, SessionState
16
+ from .capture import CaptureEngine, CaptureMode, CaptureConfig
17
+ from .frame_pipeline import FramePipeline, FrameMeta, ChangeLevel
18
+ from .annotations import AnnotationEngine, Annotation, AnnotationTool
19
+ from .context import VisionContextEngine, VisualContext
20
+ from .verify import BeforeAfterVerifier, VerificationResult
21
+ from .safety import PrivacyController, SafetyConfig
22
+ from .provider import VisionProviderRouter
23
+ from .cursor import CursorTracker, PointerEvent, DwellRegion
24
+ from .priority import FramePriorityEngine, PriorityLevel
25
+ from . import events as vision_events
26
+
27
+ __all__ = [
28
+ "VisionSessionManager", "VisionSession", "SessionState",
29
+ "CaptureEngine", "CaptureMode", "CaptureConfig",
30
+ "FramePipeline", "FrameMeta", "ChangeLevel",
31
+ "AnnotationEngine", "Annotation", "AnnotationTool",
32
+ "VisionContextEngine", "VisualContext",
33
+ "BeforeAfterVerifier", "VerificationResult",
34
+ "PrivacyController", "SafetyConfig",
35
+ "VisionProviderRouter",
36
+ "CursorTracker", "PointerEvent", "DwellRegion",
37
+ "FramePriorityEngine", "PriorityLevel",
38
+ "vision_events",
39
+ ]
40
+
41
+ # ---------------------------------------------------------------------------
42
+ # Legacy image-pipeline compatibility (calc_terminal/vision.py)
43
+ # The file calc_terminal/vision.py (image validation/normalization +
44
+ # vision-capable model routing) and the package calc_terminal/vision/
45
+ # (screen-capture agent) share the import name. Python prefers the
46
+ # package directory, so `import calc_terminal.vision` would hide the file.
47
+ # Re-expose the file's public symbols here so `from calc_terminal import
48
+ # vision` and `vision.pil_available()` keep working without a rename.
49
+ try:
50
+ import importlib.util as _ilu
51
+ import pathlib as _pl
52
+ _vision_file = _pl.Path(__file__).with_name("vision.py")
53
+ # When this package is loaded as `calc_terminal.vision`, the file is
54
+ # `calc_terminal/vision.py`'s sibling (one directory up).
55
+ if not _vision_file.exists():
56
+ _vision_file = _pl.Path(__file__).parent.parent / "vision.py"
57
+ # fallback: the classic location is calc_terminal/vision.py next
58
+ # to the package directory.
59
+ if not _vision_file.exists():
60
+ _vision_file = _pl.Path(__file__).parent / "vision.py"
61
+ if _vision_file.exists():
62
+ _spec = _ilu.spec_from_file_location(
63
+ "calc_terminal._vision_image", str(_vision_file))
64
+ _mod = _ilu.module_from_spec(_spec)
65
+ _spec.loader.exec_module(_mod) # type: ignore[union-attr]
66
+ # Re-export the image pipeline API
67
+ for _name in (
68
+ "MAX_DIMENSION", "MAX_BYTES", "JPEG_QUALITY",
69
+ "PROVIDER_NATIVE", "ALL_IMAGE_EXTS",
70
+ "pil_available", "ocr_available", "ImageError",
71
+ "validate", "classify_image", "inspect",
72
+ "normalize", "encode_for_model",
73
+ "analysis_prompt", "BASE_ANALYSIS_PROMPT",
74
+ "run_local_ocr", "find_vision_config", "analyze",
75
+ ):
76
+ if hasattr(_mod, _name):
77
+ globals()[_name] = getattr(_mod, _name)
78
+ # Backward-compat: `vision.vision` self-reference (some probes do
79
+ # `from calc_terminal import vision; vision.vision`?)
80
+ __all__.extend([n for n in (
81
+ "pil_available", "ocr_available", "ImageError",
82
+ "validate", "classify_image", "inspect",
83
+ "normalize", "encode_for_model",
84
+ "analysis_prompt", "BASE_ANALYSIS_PROMPT",
85
+ "run_local_ocr", "find_vision_config", "analyze",
86
+ "MAX_DIMENSION", "MAX_BYTES",
87
+ ) if n not in __all__])
88
+ except Exception:
89
+ pass
@@ -0,0 +1,194 @@
1
+ """
2
+ CAT v0.8.a — AI Analysis Modes (spec 19).
3
+
4
+ Three internal levels:
5
+ QUICK — low latency, current frame + current annotation + recent interaction
6
+ DEEP — selected frame(s) + annotations + cursor + project + chat
7
+ DEBUG — full pipeline: vision → identify UI region → locate code → propose fix → apply → verify
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import time
13
+ from dataclasses import dataclass, field
14
+ from enum import Enum
15
+ from typing import Any, Dict, List, Optional
16
+
17
+
18
+ class AnalysisLevel(str, Enum):
19
+ QUICK = "quick"
20
+ DEEP = "deep"
21
+ DEBUG = "debug"
22
+
23
+
24
+ @dataclass
25
+ class AnalysisRequest:
26
+ level: AnalysisLevel
27
+ session_id: str
28
+ prompt: str = ""
29
+ include_code: bool = False
30
+ include_before_after: bool = False
31
+ error_message: str = ""
32
+ user_intent: str = ""
33
+
34
+
35
+ @dataclass
36
+ class AnalysisResult:
37
+ level: AnalysisLevel
38
+ analysis: str
39
+ frame_index: Optional[int] = None
40
+ confidence: float = 0.0
41
+ findings: List[str] = field(default_factory=list)
42
+ suggested_fix: Optional[str] = None
43
+ related_files: List[str] = field(default_factory=list)
44
+ duration_ms: float = 0.0
45
+ provider: str = ""
46
+ model: str = ""
47
+
48
+
49
+ def build_system_prompt(level: AnalysisLevel) -> str:
50
+ if level == AnalysisLevel.QUICK:
51
+ return (
52
+ "You are CAT Vision — QUICK mode. Low latency.\n"
53
+ "Analyze the current frame + current annotation + recent cursor context.\n"
54
+ "Be concise, point to the immediate issue, suggest one next step."
55
+ )
56
+ if level == AnalysisLevel.DEEP:
57
+ return (
58
+ "You are CAT Vision — DEEP analysis. Thorough reasoning.\n"
59
+ "Analyze selected frame(s), annotations, cursor context, project/code context, and chat history.\n"
60
+ "Explain what you see, root cause, and a specific fix with file/function references when possible."
61
+ )
62
+ # DEBUG
63
+ return (
64
+ "You are CAT Vision — DEBUG mode. Full engineering workflow.\n"
65
+ "Pipeline: Vision → Identify UI region → Determine likely behavior → Inspect project\n"
66
+ "→ Locate related code → Reproduce where possible → Propose fix → Wait for permission → Verify.\n"
67
+ "Correlate the visual region with project code. If exact mapping is unavailable, say so — never hallucinate file locations.\n"
68
+ "Propose a concrete code change and flag the permission gate."
69
+ )
70
+
71
+
72
+ def build_user_prompt(req: AnalysisRequest, ctx: Dict[str, Any]) -> str:
73
+ parts: List[str] = []
74
+ if req.level == AnalysisLevel.QUICK:
75
+ parts.append(req.prompt or "Quick analyze: what is visible and what should the user check?")
76
+ if ctx.get("annotation_summary"):
77
+ parts.append(f"Annotation: {ctx['annotation_summary']}")
78
+ if ctx.get("cursor_summary"):
79
+ parts.append(f"Cursor: {ctx['cursor_summary']}")
80
+ elif req.level == AnalysisLevel.DEEP:
81
+ parts.append(req.prompt or "Deep analyze this screen. What is wrong and how to fix it?")
82
+ if req.error_message:
83
+ parts.append(f"Error reported: {req.error_message}")
84
+ if req.user_intent:
85
+ parts.append(f"User intent: {req.user_intent}")
86
+ if ctx.get("annotation_summary"):
87
+ parts.append(f"Annotations: {ctx['annotation_summary']}")
88
+ if ctx.get("cursor_summary"):
89
+ parts.append(f"Cursor context: {ctx['cursor_summary']}")
90
+ if ctx.get("project_hint"):
91
+ parts.append(f"Project: {ctx['project_hint']}")
92
+ else: # DEBUG
93
+ parts.append("VISUAL DEBUG SESSION — full pipeline.")
94
+ if req.prompt:
95
+ parts.append(f"User: {req.prompt}")
96
+ if req.error_message:
97
+ parts.append(f"Error: {req.error_message}")
98
+ if req.user_intent:
99
+ parts.append(f"Intent: {req.user_intent}")
100
+ parts.append(
101
+ "Provide:\n"
102
+ "1. What you see (UI elements, text, state)\n"
103
+ "2. Errors/warnings/anomalies\n"
104
+ "3. Root cause hypothesis with code reference if locatable\n"
105
+ "4. Specific fix suggestion with code\n"
106
+ "5. Observation: say when exact source mapping is unavailable\n"
107
+ "6. Do NOT apply fixes yourself — propose and wait for permission"
108
+ )
109
+ return "\n\n".join(parts)
110
+
111
+
112
+ def run_analysis(req: AnalysisRequest, visual_ctx, config: Optional[Dict] = None) -> AnalysisResult:
113
+ """Run one analysis level against a VisualContext (vision/context.py)."""
114
+ t0 = time.time()
115
+ system_prompt = build_system_prompt(req.level)
116
+ # build minimal ctx hints from visual_ctx
117
+ ctx_hints: Dict[str, Any] = {}
118
+ try:
119
+ if visual_ctx.annotations:
120
+ ctx_hints["annotation_summary"] = "; ".join(
121
+ f"{a.get('tool','?')}" for a in visual_ctx.annotations[:5]
122
+ )
123
+ if visual_ctx.pointer_telemetry:
124
+ pt = visual_ctx.pointer_telemetry
125
+ ctx_hints["cursor_summary"] = f"active={pt.get('active_element','')} moving={pt.get('cursor_moving',False)}"
126
+ if visual_ctx.project_context:
127
+ ctx_hints["project_hint"] = visual_ctx.project_context.get("type", "")
128
+ except Exception:
129
+ pass
130
+ user_prompt = build_user_prompt(req, ctx_hints)
131
+ # enrich visual_ctx with our prompts before calling provider
132
+ visual_ctx.analysis_prompt = user_prompt
133
+ # route
134
+ try:
135
+ from .provider import VisionProviderRouter
136
+ from .. import aicore
137
+ from ..attachments import Attachment
138
+ import base64
139
+ router = VisionProviderRouter(config)
140
+ cfg, caps = router.find_vision_capable(config) or (None, None)
141
+ if cfg is None:
142
+ return AnalysisResult(level=req.level, analysis="No vision-capable model configured. Run /model to choose one.", duration_ms=(time.time()-t0)*1000)
143
+ # Build attachment from frame_b64
144
+ att = Attachment.__new__(Attachment)
145
+ att.id = f"att-vision-{req.level.value}-{int(time.time()*1000)%1000000}"
146
+ att.name = "vision_frame"
147
+ att.path = ""
148
+ att.extension = ".png"
149
+ att.mime_type = "image/png"
150
+ att.size = len(visual_ctx.frame_b64 or "")
151
+ att.kind = "image"
152
+ att.content = "[vision frame]"
153
+ att.metadata = {"inline_b64": visual_ctx.frame_b64 or ""}
154
+ att.extraction_status = "ready"
155
+ att.error = None
156
+ att.created_at = time.time()
157
+ att.source = "vision_analysis"
158
+ # Attachments: frame + optional before frame
159
+ attachments = [att]
160
+ if req.include_before_after and visual_ctx.before_frame_b64:
161
+ att2 = Attachment.__new__(Attachment)
162
+ att2.id = "att-vision-before"
163
+ att2.name = "before_frame"
164
+ att2.path = ""
165
+ att2.extension = ".png"
166
+ att2.mime_type = "image/png"
167
+ att2.size = len(visual_ctx.before_frame_b64)
168
+ att2.kind = "image"
169
+ att2.content = "[before frame]"
170
+ att2.metadata = {"inline_b64": visual_ctx.before_frame_b64}
171
+ att2.extraction_status = "ready"
172
+ att2.error = None
173
+ att2.created_at = time.time()
174
+ att2.source = "vision_verify"
175
+ attachments.append(att2)
176
+ analysis_text = aicore.query_ai(
177
+ user_prompt,
178
+ system_prompt=system_prompt,
179
+ history=None,
180
+ config=cfg,
181
+ attachments=attachments,
182
+ size_class="large" if req.level != AnalysisLevel.QUICK else "normal",
183
+ )
184
+ return AnalysisResult(
185
+ level=req.level,
186
+ analysis=analysis_text or "",
187
+ frame_index=None,
188
+ confidence=0.7,
189
+ duration_ms=(time.time()-t0)*1000,
190
+ provider=cfg.get("provider",""),
191
+ model=cfg.get("model",""),
192
+ )
193
+ except Exception as e:
194
+ return AnalysisResult(level=req.level, analysis=f"Analysis failed: {e}", duration_ms=(time.time()-t0)*1000)
@@ -0,0 +1,297 @@
1
+ """
2
+ CAT v0.8.a — Annotation Engine.
3
+
4
+ Handles pen, arrow, rect, circle, text, and highlight annotations.
5
+ Manages undo/redo stack, serialization, and frame annotation binding.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import time
11
+ import uuid
12
+ from dataclasses import dataclass, field
13
+ from enum import Enum
14
+ from typing import Any, Dict, List, Optional, Tuple
15
+
16
+
17
+ class AnnotationTool(str, Enum):
18
+ PEN = "pen"
19
+ ARROW = "arrow"
20
+ RECT = "rect"
21
+ CIRCLE = "circle"
22
+ TEXT = "text"
23
+ HIGHLIGHT = "highlight"
24
+ CROSSHAIR = "crosshair"
25
+ ERASER = "eraser"
26
+
27
+
28
+ @dataclass
29
+ class Color:
30
+ r: int = 255
31
+ g: int = 0
32
+ b: int = 0
33
+ a: float = 1.0
34
+
35
+ def to_css(self) -> str:
36
+ return f"rgba({self.r},{self.g},{self.b},{self.a})"
37
+
38
+ def to_hex(self) -> str:
39
+ return f"#{self.r:02x}{self.g:02x}{self.b:02x}"
40
+
41
+ def to_dict(self) -> Dict[str, Any]:
42
+ return {"r": self.r, "g": self.g, "b": self.b, "a": self.a}
43
+
44
+ @classmethod
45
+ def from_hex(cls, hex_str: str) -> "Color":
46
+ h = hex_str.lstrip("#")
47
+ if len(h) == 3:
48
+ h = "".join(c * 2 for c in h)
49
+ if len(h) == 6:
50
+ return cls(int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16))
51
+ elif len(h) == 8:
52
+ return cls(
53
+ int(h[0:2], 16), int(h[2:4], 16),
54
+ int(h[4:6], 16), int(h[6:8], 16) / 255.0
55
+ )
56
+ return cls()
57
+
58
+ @classmethod
59
+ def from_dict(cls, d: Dict) -> "Color":
60
+ return cls(d.get("r", 255), d.get("g", 0), d.get("b", 0), d.get("a", 1.0))
61
+
62
+
63
+ @dataclass
64
+ class Annotation:
65
+ id: str
66
+ tool: AnnotationTool
67
+ points: List[Tuple[float, float]]
68
+ color: Color
69
+ line_width: float
70
+ text: str = ""
71
+ text_position: Optional[Tuple[float, float]] = None
72
+ opacity: float = 1.0
73
+ created_at: float = 0.0
74
+ frame_index: int = -1
75
+ bounding_box: Optional[Dict[str, float]] = None
76
+
77
+ def to_dict(self) -> Dict[str, Any]:
78
+ return {
79
+ "id": self.id,
80
+ "tool": self.tool.value,
81
+ "points": [[p[0], p[1]] for p in self.points],
82
+ "color": self.color.to_dict(),
83
+ "line_width": self.line_width,
84
+ "text": self.text,
85
+ "text_position": list(self.text_position) if self.text_position else None,
86
+ "opacity": self.opacity,
87
+ "created_at": self.created_at,
88
+ "frame_index": self.frame_index,
89
+ "bounding_box": self.bounding_box,
90
+ }
91
+
92
+ @classmethod
93
+ def from_dict(cls, d: Dict) -> "Annotation":
94
+ return cls(
95
+ id=d.get("id", str(uuid.uuid4())),
96
+ tool=AnnotationTool(d.get("tool", "pen")),
97
+ points=[(p[0], p[1]) for p in d.get("points", [])],
98
+ color=Color.from_dict(d.get("color", {})),
99
+ line_width=d.get("line_width", 3),
100
+ text=d.get("text", ""),
101
+ text_position=tuple(d["text_position"]) if d.get("text_position") else None,
102
+ opacity=d.get("opacity", 1.0),
103
+ created_at=d.get("created_at", 0.0),
104
+ frame_index=d.get("frame_index", -1),
105
+ bounding_box=d.get("bounding_box"),
106
+ )
107
+
108
+
109
+ class AnnotationEngine:
110
+ """Manages annotation creation, undo/redo, and serialization."""
111
+
112
+ def __init__(self):
113
+ self.annotations: List[Annotation] = []
114
+ self._undo_stack: List[Annotation] = []
115
+ self._redo_stack: List[Annotation] = []
116
+ self._max_undo = 50
117
+ self._default_color = Color(255, 0, 0)
118
+ self._default_width = 3.0
119
+
120
+ def set_default_color(self, color: Color):
121
+ self._default_color = color
122
+
123
+ def set_default_width(self, width: float):
124
+ self._default_width = width
125
+
126
+ def add_annotation(self, tool: AnnotationTool,
127
+ points: List[Tuple[float, float]],
128
+ color: Optional[Color] = None,
129
+ line_width: Optional[float] = None,
130
+ text: str = "",
131
+ frame_index: int = -1,
132
+ annotation_id: Optional[str] = None) -> Annotation:
133
+ ann = Annotation(
134
+ id=annotation_id or str(uuid.uuid4()),
135
+ tool=tool,
136
+ points=points,
137
+ color=color or self._default_color,
138
+ line_width=line_width or self._default_width,
139
+ text=text,
140
+ text_position=points[0] if points else None,
141
+ created_at=time.time(),
142
+ frame_index=frame_index,
143
+ bounding_box=self._compute_bbox(points),
144
+ )
145
+ self.annotations.append(ann)
146
+ self._undo_stack.append(ann)
147
+ if len(self._undo_stack) > self._max_undo:
148
+ self._undo_stack.pop(0)
149
+ self._redo_stack.clear()
150
+ return ann
151
+
152
+ def undo(self) -> Optional[Annotation]:
153
+ if not self._undo_stack:
154
+ return None
155
+ ann = self._undo_stack.pop()
156
+ self.annotations = [a for a in self.annotations if a.id != ann.id]
157
+ self._redo_stack.append(ann)
158
+ return ann
159
+
160
+ def redo(self) -> Optional[Annotation]:
161
+ if not self._redo_stack:
162
+ return None
163
+ ann = self._redo_stack.pop()
164
+ self.annotations.append(ann)
165
+ self._undo_stack.append(ann)
166
+ return ann
167
+
168
+ def clear(self):
169
+ self.annotations.clear()
170
+ self._undo_stack.clear()
171
+ self._redo_stack.clear()
172
+
173
+ def clear_frame(self, frame_index: int):
174
+ self.annotations = [
175
+ a for a in self.annotations if a.frame_index != frame_index
176
+ ]
177
+
178
+ def update_annotation(self, annotation_id: str, **kwargs) -> Optional[Annotation]:
179
+ for ann in self.annotations:
180
+ if ann.id == annotation_id:
181
+ for key, val in kwargs.items():
182
+ if key == "color" and isinstance(val, dict):
183
+ ann.color = Color.from_dict(val)
184
+ elif key == "points" and isinstance(val, list):
185
+ ann.points = [(p[0], p[1]) for p in val]
186
+ ann.bounding_box = self._compute_bbox(ann.points)
187
+ elif hasattr(ann, key):
188
+ setattr(ann, key, val)
189
+ return ann
190
+ return None
191
+
192
+ def delete_annotation(self, annotation_id: str) -> bool:
193
+ before = len(self.annotations)
194
+ self.annotations = [a for a in self.annotations if a.id != annotation_id]
195
+ return len(self.annotations) < before
196
+
197
+ def get_annotations_for_frame(self, frame_index: int) -> List[Dict]:
198
+ return [a.to_dict() for a in self.annotations if a.frame_index == frame_index]
199
+
200
+ def get_pointer_annotation(self, x: float, y: float,
201
+ radius: float = 15.0) -> Optional[Annotation]:
202
+ """Find annotation near a pointer position."""
203
+ for ann in reversed(self.annotations):
204
+ if ann.tool == AnnotationTool.CROSSHAIR:
205
+ continue
206
+ if ann.bounding_box:
207
+ bb = ann.bounding_box
208
+ if (bb["x"] - radius <= x <= bb["x"] + bb["width"] + radius and
209
+ bb["y"] - radius <= y <= bb["y"] + bb["height"] + radius):
210
+ return ann
211
+ return None
212
+
213
+ def to_list(self) -> List[Dict]:
214
+ return [a.to_dict() for a in self.annotations]
215
+
216
+ def from_list(self, data: List[Dict]):
217
+ self.clear()
218
+ for d in data:
219
+ self.annotations.append(Annotation.from_dict(d))
220
+ self._undo_stack = list(self.annotations)
221
+ self._redo_stack.clear()
222
+
223
+ def _compute_bbox(self, points: List[Tuple[float, float]]) -> Optional[Dict[str, float]]:
224
+ if not points:
225
+ return None
226
+ xs = [p[0] for p in points]
227
+ ys = [p[1] for p in points]
228
+ return {
229
+ "x": min(xs), "y": min(ys),
230
+ "width": max(xs) - min(xs),
231
+ "height": max(ys) - min(ys),
232
+ }
233
+
234
+ def export_svg(self, width: int = 1920, height: int = 1080) -> str:
235
+ lines = [
236
+ f'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" height="{height}">',
237
+ ]
238
+ for ann in self.annotations:
239
+ color_hex = ann.color.to_hex()
240
+ opacity = ann.opacity
241
+ if ann.tool == AnnotationTool.PEN and len(ann.points) >= 2:
242
+ path_d = "M " + " L ".join(
243
+ f"{p[0]:.1f},{p[1]:.1f}" for p in ann.points
244
+ )
245
+ lines.append(
246
+ f' <path d="{path_d}" stroke="{color_hex}" '
247
+ f'stroke-width="{ann.line_width}" fill="none" '
248
+ f'opacity="{opacity}" stroke-linecap="round"/>'
249
+ )
250
+ elif ann.tool == AnnotationTool.ARROW and len(ann.points) >= 2:
251
+ x1, y1 = ann.points[0]
252
+ x2, y2 = ann.points[-1]
253
+ lines.append(
254
+ f' <line x1="{x1:.1f}" y1="{y1:.1f}" x2="{x2:.1f}" y2="{y2:.1f}" '
255
+ f'stroke="{color_hex}" stroke-width="{ann.line_width}" '
256
+ f'opacity="{opacity}" marker-end="url(#arrowhead)"/>'
257
+ )
258
+ elif ann.tool == AnnotationTool.RECT and len(ann.points) >= 2:
259
+ x1, y1 = ann.points[0]
260
+ x2, y2 = ann.points[-1]
261
+ lines.append(
262
+ f' <rect x="{min(x1,x2):.1f}" y="{min(y1,y2):.1f}" '
263
+ f'width="{abs(x2-x1):.1f}" height="{abs(y2-y1):.1f}" '
264
+ f'stroke="{color_hex}" stroke-width="{ann.line_width}" '
265
+ f'fill="none" opacity="{opacity}"/>'
266
+ )
267
+ elif ann.tool == AnnotationTool.CIRCLE and len(ann.points) >= 2:
268
+ x1, y1 = ann.points[0]
269
+ x2, y2 = ann.points[-1]
270
+ cx = (x1 + x2) / 2
271
+ cy = (y1 + y2) / 2
272
+ rx = abs(x2 - x1) / 2
273
+ ry = abs(y2 - y1) / 2
274
+ lines.append(
275
+ f' <ellipse cx="{cx:.1f}" cy="{cy:.1f}" rx="{rx:.1f}" ry="{ry:.1f}" '
276
+ f'stroke="{color_hex}" stroke-width="{ann.line_width}" '
277
+ f'fill="none" opacity="{opacity}"/>'
278
+ )
279
+ elif ann.tool == AnnotationTool.TEXT and ann.text_position:
280
+ tx, ty = ann.text_position
281
+ escaped = ann.text.replace("&", "&amp;").replace("<", "&lt;")
282
+ lines.append(
283
+ f' <text x="{tx:.1f}" y="{ty:.1f}" fill="{color_hex}" '
284
+ f'font-size="{ann.line_width * 5:.0f}" opacity="{opacity}">'
285
+ f'{escaped}</text>'
286
+ )
287
+ elif ann.tool == AnnotationTool.HIGHLIGHT and len(ann.points) >= 2:
288
+ x1, y1 = ann.points[0]
289
+ x2, y2 = ann.points[-1]
290
+ highlight_color = Color(ann.color.r, ann.color.g, ann.color.b, 0.3)
291
+ lines.append(
292
+ f' <rect x="{min(x1,x2):.1f}" y="{min(y1,y2):.1f}" '
293
+ f'width="{abs(x2-x1):.1f}" height="{abs(y2-y1):.1f}" '
294
+ f'fill="{highlight_color.to_hex()}" opacity="{opacity}"/>'
295
+ )
296
+ lines.append("</svg>")
297
+ return "\n".join(lines)