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,287 @@
1
+ """
2
+ CCT — task_reports.py: Structured Task Completion Reports (v0.7.10 spec #25).
3
+
4
+ Provides structured reports when tasks complete, including:
5
+ - Files changed
6
+ - Tests run/pass/fail
7
+ - Build status
8
+ - Preview status
9
+ - Todo progress
10
+ - Duration
11
+ - Any errors encountered
12
+
13
+ These reports are shown in the chat as structured completion cards.
14
+ """
15
+
16
+ if __name__ == "__main__":
17
+ print("This is a library file and is not meant to be run directly.")
18
+ import sys
19
+ sys.exit(1)
20
+
21
+ import os
22
+ import time
23
+ from dataclasses import dataclass, field
24
+ from typing import List, Dict, Optional, Any
25
+ from enum import Enum
26
+
27
+
28
+ class TaskStatus(Enum):
29
+ """Status of a completed task."""
30
+ SUCCESS = "success"
31
+ PARTIAL = "partial"
32
+ FAILED = "failed"
33
+ CANCELLED = "cancelled"
34
+
35
+
36
+ class TaskType(Enum):
37
+ """Type of task that was completed."""
38
+ BUILD = "build"
39
+ TEST = "test"
40
+ DEPLOY = "deploy"
41
+ FILE_WRITE = "file_write"
42
+ FILE_EDIT = "file_edit"
43
+ PACKAGE_INSTALL = "package_install"
44
+ AI_GENERATION = "ai_generation"
45
+ CUSTOM = "custom"
46
+
47
+
48
+ @dataclass
49
+ class FileChange:
50
+ """A file that was changed during the task."""
51
+ path: str
52
+ change_type: str # "created", "modified", "deleted"
53
+ lines_added: int = 0
54
+ lines_removed: int = 0
55
+
56
+
57
+ @dataclass
58
+ class TestResult:
59
+ """Result of running tests."""
60
+ total: int = 0
61
+ passed: int = 0
62
+ failed: int = 0
63
+ skipped: int = 0
64
+ duration: float = 0.0
65
+ test_file: str = ""
66
+ error_message: str = ""
67
+
68
+
69
+ @dataclass
70
+ class BuildResult:
71
+ """Result of a build operation."""
72
+ success: bool = False
73
+ duration: float = 0.0
74
+ output: str = ""
75
+ error: str = ""
76
+ warnings: int = 0
77
+
78
+
79
+ @dataclass
80
+ class TaskReport:
81
+ """A structured report of a completed task."""
82
+ task_type: TaskType = TaskType.CUSTOM
83
+ status: TaskStatus = TaskStatus.SUCCESS
84
+ title: str = ""
85
+ description: str = ""
86
+
87
+ # Timing
88
+ start_time: float = field(default_factory=time.time)
89
+ end_time: float = 0.0
90
+ duration: float = 0.0
91
+
92
+ # Changes
93
+ files_changed: List[FileChange] = field(default_factory=list)
94
+
95
+ # Build/Test results
96
+ build_result: Optional[BuildResult] = None
97
+ test_result: Optional[TestResult] = None
98
+
99
+ # Preview
100
+ preview_url: str = ""
101
+ preview_active: bool = False
102
+
103
+ # Todo progress
104
+ todos_completed: int = 0
105
+ todos_total: int = 0
106
+
107
+ # Errors
108
+ errors: List[str] = field(default_factory=list)
109
+ warnings: List[str] = field(default_factory=list)
110
+
111
+ # Additional context
112
+ metadata: Dict[str, Any] = field(default_factory=dict)
113
+
114
+ def complete(self, status: TaskStatus = TaskStatus.SUCCESS):
115
+ """Mark the task as complete."""
116
+ self.end_time = time.time()
117
+ self.duration = self.end_time - self.start_time
118
+ self.status = status
119
+
120
+ def add_file_change(self, path: str, change_type: str,
121
+ lines_added: int = 0, lines_removed: int = 0):
122
+ """Add a file change to the report."""
123
+ self.files_changed.append(FileChange(
124
+ path=path,
125
+ change_type=change_type,
126
+ lines_added=lines_added,
127
+ lines_removed=lines_removed
128
+ ))
129
+
130
+ def add_error(self, error: str):
131
+ """Add an error to the report."""
132
+ self.errors.append(error)
133
+ if self.status == TaskStatus.SUCCESS:
134
+ self.status = TaskStatus.PARTIAL
135
+
136
+ def add_warning(self, warning: str):
137
+ """Add a warning to the report."""
138
+ self.warnings.append(warning)
139
+
140
+ def to_dict(self) -> Dict[str, Any]:
141
+ """Convert report to dictionary for serialization."""
142
+ return {
143
+ "task_type": self.task_type.value,
144
+ "status": self.status.value,
145
+ "title": self.title,
146
+ "description": self.description,
147
+ "duration": self.duration,
148
+ "files_changed": [
149
+ {
150
+ "path": fc.path,
151
+ "change_type": fc.change_type,
152
+ "lines_added": fc.lines_added,
153
+ "lines_removed": fc.lines_removed,
154
+ }
155
+ for fc in self.files_changed
156
+ ],
157
+ "build_result": {
158
+ "success": self.build_result.success,
159
+ "duration": self.build_result.duration,
160
+ "warnings": self.build_result.warnings,
161
+ } if self.build_result else None,
162
+ "test_result": {
163
+ "total": self.test_result.total,
164
+ "passed": self.test_result.passed,
165
+ "failed": self.test_result.failed,
166
+ "duration": self.test_result.duration,
167
+ } if self.test_result else None,
168
+ "preview_url": self.preview_url,
169
+ "preview_active": self.preview_active,
170
+ "todos_completed": self.todos_completed,
171
+ "todos_total": self.todos_total,
172
+ "errors": self.errors,
173
+ "warnings": self.warnings,
174
+ }
175
+
176
+ def to_markdown(self) -> str:
177
+ """Convert report to markdown for chat display."""
178
+ lines = []
179
+
180
+ # Status icon
181
+ status_icons = {
182
+ TaskStatus.SUCCESS: "✓",
183
+ TaskStatus.PARTIAL: "⚠",
184
+ TaskStatus.FAILED: "✗",
185
+ TaskStatus.CANCELLED: "⊘",
186
+ }
187
+ icon = status_icons.get(self.status, "?")
188
+
189
+ # Title
190
+ lines.append(f"### {icon} {self.title}")
191
+ if self.description:
192
+ lines.append(f"{self.description}")
193
+ lines.append("")
194
+
195
+ # Duration
196
+ if self.duration > 0:
197
+ lines.append(f"**Duration:** {self.duration:.1f}s")
198
+
199
+ # Files changed
200
+ if self.files_changed:
201
+ lines.append(f"**Files changed:** {len(self.files_changed)}")
202
+ for fc in self.files_changed[:5]: # Limit to 5 files
203
+ lines.append(f"- `{fc.path}` ({fc.change_type})")
204
+ if len(self.files_changed) > 5:
205
+ lines.append(f"- ... and {len(self.files_changed) - 5} more")
206
+
207
+ # Build result
208
+ if self.build_result:
209
+ status = "✓ passed" if self.build_result.success else "✗ failed"
210
+ lines.append(f"**Build:** {status} ({self.build_result.duration:.1f}s)")
211
+ if self.build_result.warnings:
212
+ lines.append(f"**Warnings:** {self.build_result.warnings}")
213
+
214
+ # Test result
215
+ if self.test_result:
216
+ lines.append(
217
+ f"**Tests:** {self.test_result.passed}/{self.test_result.total} passed"
218
+ )
219
+ if self.test_result.failed:
220
+ lines.append(f"**Failed:** {self.test_result.failed}")
221
+
222
+ # Preview
223
+ if self.preview_url:
224
+ lines.append(f"**Preview:** {self.preview_url}")
225
+
226
+ # Todo progress
227
+ if self.todos_total > 0:
228
+ lines.append(
229
+ f"**Todos:** {self.todos_completed}/{self.todos_total} completed"
230
+ )
231
+
232
+ # Errors
233
+ if self.errors:
234
+ lines.append("")
235
+ lines.append("**Errors:**")
236
+ for error in self.errors[:3]:
237
+ lines.append(f"- {error}")
238
+
239
+ # Warnings
240
+ if self.warnings:
241
+ lines.append("")
242
+ lines.append("**Warnings:**")
243
+ for warning in self.warnings[:3]:
244
+ lines.append(f"- {warning}")
245
+
246
+ return "\n".join(lines)
247
+
248
+
249
+ class TaskReportManager:
250
+ """Manages task completion reports."""
251
+
252
+ def __init__(self):
253
+ self._reports = []
254
+ self._max_reports = 100
255
+
256
+ def create_report(self, task_type: TaskType, title: str,
257
+ description: str = "") -> TaskReport:
258
+ """Create a new task report."""
259
+ report = TaskReport(
260
+ task_type=task_type,
261
+ title=title,
262
+ description=description,
263
+ )
264
+ self._reports.append(report)
265
+ if len(self._reports) > self._max_reports:
266
+ self._reports = self._reports[-self._max_reports:]
267
+ return report
268
+
269
+ def get_recent_reports(self, count: int = 10) -> List[TaskReport]:
270
+ """Get recent task reports."""
271
+ return self._reports[-count:]
272
+
273
+ def get_reports_by_type(self, task_type: TaskType) -> List[TaskReport]:
274
+ """Get reports filtered by task type."""
275
+ return [r for r in self._reports if r.task_type == task_type]
276
+
277
+
278
+ # Global instance
279
+ _task_report_manager = None
280
+
281
+
282
+ def get_task_report_manager() -> TaskReportManager:
283
+ """Get or create the global task report manager."""
284
+ global _task_report_manager
285
+ if _task_report_manager is None:
286
+ _task_report_manager = TaskReportManager()
287
+ return _task_report_manager
@@ -0,0 +1,201 @@
1
+ """
2
+ CCT — terminal host detection + native clipboard read.
3
+
4
+ CCT is a Textual chat IDE: it does not spawn or drive a shell/PTY, so a
5
+ "paste" is delivered by the terminal the user launched CCT in. This
6
+ module answers two questions the large-paste fix (v0.7.8.45) needs:
7
+
8
+ * Which shell is hosting CCT right now? (detect_host)
9
+ * What does the clipboard actually hold on Windows? (read_clipboard_text)
10
+
11
+ The PowerShell case: on Windows console hosts (conhost / Windows
12
+ Terminal, regardless of whether PowerShell or cmd is the shell), a very
13
+ large bracketed-paste payload can be truncated or dropped while being
14
+ written into the console input buffer — before Textual ever sees it.
15
+ The paste handler recovers by comparing the delivered event text against
16
+ the OS clipboard: when the clipboard is a strict prefix-superset of the
17
+ delivered text, the clipboard is the authoritative payload.
18
+
19
+ Everything here is best-effort; every function returns a safe default on
20
+ failure so the app never depends on this module succeeding.
21
+ """
22
+
23
+ if __name__ == "__main__":
24
+ print("This is a library file and is not meant to be run directly.")
25
+ import sys
26
+ sys.exit(1)
27
+
28
+ import os
29
+ import sys
30
+ import time
31
+
32
+ _CF_UNICODETEXT = 13
33
+ _cached_host = None
34
+ _cached_emulator = None
35
+
36
+
37
+ def detect_host():
38
+ """Lowercase name of the SHELL CCT is running inside:
39
+
40
+ 'powershell' | 'cmd' | 'bash' | 'zsh' | 'other'
41
+
42
+ Detection is env-var based (no subprocesses, no psutil) and cached —
43
+ the environment cannot change at runtime. `CCT_FORCE_HOST` overrides
44
+ it for tests and exotic setups.
45
+ """
46
+ global _cached_host
47
+ forced = os.environ.get("CCT_FORCE_HOST", "").strip().lower()
48
+ if forced:
49
+ _cached_host = forced
50
+ return _cached_host
51
+ if _cached_host:
52
+ return _cached_host
53
+ host = "other"
54
+ try:
55
+ shell = os.environ.get("SHELL", "")
56
+ if shell:
57
+ base = os.path.basename(shell).lower()
58
+ if "zsh" in base:
59
+ host = "zsh"
60
+ elif "bash" in base or base in ("sh", "dash"):
61
+ host = "bash"
62
+ if host == "other" and os.environ.get("PSModulePath"):
63
+ # Set by Windows PowerShell and pwsh (and only by them), so
64
+ # it is the reliable PowerShell fingerprint on Windows.
65
+ host = "powershell"
66
+ if host == "other":
67
+ comspec = os.environ.get("COMSPEC", "").lower()
68
+ if "powershell" in comspec:
69
+ host = "powershell"
70
+ elif "cmd" in comspec:
71
+ host = "cmd"
72
+ if host == "other" and sys.platform == "win32":
73
+ host = "cmd"
74
+ except Exception:
75
+ host = "other"
76
+ _cached_host = host
77
+ return host
78
+
79
+
80
+ def detect_emulator():
81
+ """Terminal-emulator name for diagnostics ('Windows Terminal',
82
+ 'ConEmu', 'vscode', 'Apple Terminal', 'xterm', ...) or 'other'.
83
+ Never raises, cached."""
84
+ global _cached_emulator
85
+ if _cached_emulator:
86
+ return _cached_emulator
87
+ emu = "other"
88
+ try:
89
+ if os.environ.get("WT_SESSION"):
90
+ emu = "Windows Terminal"
91
+ elif os.environ.get("ConEmuPID"):
92
+ emu = "ConEmu"
93
+ elif os.environ.get("TERM_PROGRAM"):
94
+ tp = os.environ["TERM_PROGRAM"].lower()
95
+ emu = "vscode" if "vscode" in tp else os.environ["TERM_PROGRAM"]
96
+ elif os.environ.get("__CF_USER_TEXT_ENCODING"):
97
+ emu = "Apple Terminal"
98
+ elif os.environ.get("TERM"):
99
+ emu = os.environ["TERM"]
100
+ except Exception:
101
+ emu = "other"
102
+ _cached_emulator = emu
103
+ return emu
104
+
105
+
106
+ def is_windows():
107
+ """True when CCT is running on a Windows console host (the only
108
+ place the clipboard-prefix paste recovery is needed)."""
109
+ return sys.platform == "win32"
110
+
111
+
112
+ def read_clipboard_text():
113
+ """Best-effort native clipboard read on Windows (user32
114
+ GetClipboardData, CF_UNICODETEXT via ctypes — no third-party
115
+ dependency, no subprocess). Returns the clipboard text as str, or
116
+ None when there is no text / the clipboard is busy / an error
117
+ occurs / the platform is not Windows."""
118
+ if sys.platform != "win32":
119
+ return None
120
+ try:
121
+ import ctypes
122
+ from ctypes import wintypes
123
+ user32 = ctypes.windll.user32
124
+ kernel32 = ctypes.windll.kernel32
125
+ # Restype/argtype declarations are mandatory: the default int
126
+ # restype truncates 64-bit HGLOBAL pointers returned by
127
+ # GetClipboardData/GlobalLock/GlobalSize.
128
+ user32.IsClipboardFormatAvailable.argtypes = [wintypes.UINT]
129
+ user32.IsClipboardFormatAvailable.restype = wintypes.BOOL
130
+ user32.GetClipboardData.argtypes = [wintypes.UINT]
131
+ user32.GetClipboardData.restype = ctypes.c_void_p
132
+ kernel32.GlobalSize.argtypes = [ctypes.c_void_p]
133
+ kernel32.GlobalSize.restype = ctypes.c_size_t
134
+ kernel32.GlobalLock.argtypes = [ctypes.c_void_p]
135
+ kernel32.GlobalLock.restype = ctypes.c_void_p
136
+ kernel32.GlobalUnlock.argtypes = [ctypes.c_void_p]
137
+ kernel32.GlobalUnlock.restype = wintypes.BOOL
138
+ text = None
139
+ for _ in range(3): # brief retries; the clipboard is often held
140
+ if user32.OpenClipboard(None):
141
+ break
142
+ time.sleep(0.015)
143
+ else:
144
+ return None
145
+ try:
146
+ if not user32.IsClipboardFormatAvailable(_CF_UNICODETEXT):
147
+ return None
148
+ handle = user32.GetClipboardData(_CF_UNICODETEXT)
149
+ if not handle:
150
+ return None
151
+ ptr = kernel32.GlobalLock(handle)
152
+ if not ptr:
153
+ return None
154
+ try:
155
+ size = kernel32.GlobalSize(handle)
156
+ if size <= 2:
157
+ return None
158
+ nchars = size // 2
159
+ arr = (ctypes.c_wchar * (nchars + 1)).from_address(ptr)
160
+ text = "".join(arr[:nchars]).rstrip("\x00")
161
+ finally:
162
+ kernel32.GlobalUnlock(handle)
163
+ finally:
164
+ user32.CloseClipboard()
165
+ return text
166
+ except Exception:
167
+ return None
168
+
169
+
170
+ def clipboard_payload_override(delivered):
171
+ """Windows paste-recovery decision (v0.7.8.45, PowerShell case).
172
+
173
+ Returns (payload, source) for the paste handler:
174
+
175
+ (delivered, 'terminal') — keep what the terminal
176
+ delivered (everything
177
+ else)
178
+ (clipboard, 'clipboard') — the Windows console
179
+ host truncated the
180
+ bracketed-paste payload
181
+ (clipboard is a strict
182
+ prefix-superset of the
183
+ delivered text), so the
184
+ clipboard is the
185
+ authoritative copy
186
+
187
+ `source` is the human-readable name used in debug logs.
188
+ """
189
+ if not is_windows():
190
+ return delivered, "terminal"
191
+ if not delivered:
192
+ # Host dropped the whole payload; the clipboard is the only
193
+ # copy left.
194
+ clip = read_clipboard_text()
195
+ if clip:
196
+ return clip, "clipboard (terminal dropped the payload)"
197
+ return delivered, "terminal"
198
+ clip = read_clipboard_text()
199
+ if clip and len(clip) > len(delivered) and clip.startswith(delivered):
200
+ return clip, "clipboard (terminal payload truncated)"
201
+ return delivered, "terminal"