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.
- calc_terminal/__init__.py +14 -0
- calc_terminal/__main__.py +14 -0
- calc_terminal/activity.py +1334 -0
- calc_terminal/agent.py +3387 -0
- calc_terminal/agent_runtime.py +519 -0
- calc_terminal/ai_context.py +447 -0
- calc_terminal/ai_modes.py +752 -0
- calc_terminal/ai_personalization.py +286 -0
- calc_terminal/ai_preview_feedback.py +213 -0
- calc_terminal/aicore.py +2572 -0
- calc_terminal/anim.py +367 -0
- calc_terminal/app.py +3685 -0
- calc_terminal/art.py +639 -0
- calc_terminal/atomsim.py +368 -0
- calc_terminal/attachments.py +743 -0
- calc_terminal/benchmark_system.py +414 -0
- calc_terminal/browser/__init__.py +36 -0
- calc_terminal/browser/browser_state.py +346 -0
- calc_terminal/browser/devserver.py +176 -0
- calc_terminal/browser/engine.py +494 -0
- calc_terminal/browser/navigation.py +84 -0
- calc_terminal/browser/preview.py +429 -0
- calc_terminal/browser/preview_entry.py +95 -0
- calc_terminal/browser/project_detector.py +144 -0
- calc_terminal/browser/server.py +449 -0
- calc_terminal/browser/state.py +75 -0
- calc_terminal/browser/watcher.py +99 -0
- calc_terminal/browser_gui/__init__.py +1 -0
- calc_terminal/browser_gui/__main__.py +3 -0
- calc_terminal/browser_gui/launcher.py +173 -0
- calc_terminal/browser_gui/playwright_browser.py +117 -0
- calc_terminal/browser_gui/qt_browser.py +1501 -0
- calc_terminal/browser_gui/webview_browser.py +57 -0
- calc_terminal/capabilities/__init__.py +35 -0
- calc_terminal/capabilities/adapters/__init__.py +33 -0
- calc_terminal/capabilities/adapters/bioinformatics.py +204 -0
- calc_terminal/capabilities/adapters/browser_adapter.py +205 -0
- calc_terminal/capabilities/adapters/filesystem.py +206 -0
- calc_terminal/capabilities/adapters/git_adapter.py +202 -0
- calc_terminal/capabilities/adapters/jupyter_adapter.py +138 -0
- calc_terminal/capabilities/adapters/ml_frameworks.py +158 -0
- calc_terminal/capabilities/adapters/platforms.py +200 -0
- calc_terminal/capabilities/adapters/python_exec.py +93 -0
- calc_terminal/capabilities/adapters/quantum_adapter.py +150 -0
- calc_terminal/capabilities/adapters/scientific_comp.py +123 -0
- calc_terminal/capabilities/adapters/structural_bio.py +161 -0
- calc_terminal/capabilities/adapters/terminal.py +99 -0
- calc_terminal/capabilities/bus.py +178 -0
- calc_terminal/capabilities/discovery.py +207 -0
- calc_terminal/capabilities/schema.py +221 -0
- calc_terminal/cat.ico +0 -0
- calc_terminal/cat_browser.py +2018 -0
- calc_terminal/chat_store.py +703 -0
- calc_terminal/cli.py +1178 -0
- calc_terminal/code_editor.py +640 -0
- calc_terminal/collaboration.py +723 -0
- calc_terminal/commands_data.py +139 -0
- calc_terminal/compatibility_engine.py +352 -0
- calc_terminal/compute/__init__.py +31 -0
- calc_terminal/compute/fabric.py +350 -0
- calc_terminal/config.py +227 -0
- calc_terminal/core/__init__.py +41 -0
- calc_terminal/core/checkpoint.py +156 -0
- calc_terminal/core/input/__init__.py +45 -0
- calc_terminal/core/mode_registry.py +300 -0
- calc_terminal/core/project_graph.py +172 -0
- calc_terminal/core/recovery.py +129 -0
- calc_terminal/core/security_layer.py +112 -0
- calc_terminal/core/task_graph.py +202 -0
- calc_terminal/core/unified_runtime.py +184 -0
- calc_terminal/core/verification.py +257 -0
- calc_terminal/customization.py +1566 -0
- calc_terminal/derivations.py +153 -0
- calc_terminal/device_control.py +263 -0
- calc_terminal/diagnostics/__init__.py +27 -0
- calc_terminal/diagnostics/doctor_engine.py +382 -0
- calc_terminal/diagnostics/self_test.py +247 -0
- calc_terminal/doctor.py +519 -0
- calc_terminal/easter_eggs.py +274 -0
- calc_terminal/editor/__init__.py +1 -0
- calc_terminal/editor/actions.py +263 -0
- calc_terminal/editor/commands.py +160 -0
- calc_terminal/editor/shortcuts.py +226 -0
- calc_terminal/engine.py +259 -0
- calc_terminal/errors.py +120 -0
- calc_terminal/event_stream.py +146 -0
- calc_terminal/eventbus.py +133 -0
- calc_terminal/extensions.py +733 -0
- calc_terminal/fallback_cli.py +1321 -0
- calc_terminal/first_run.py +265 -0
- calc_terminal/fomoji_auth.py +1043 -0
- calc_terminal/formulas.py +82 -0
- calc_terminal/fs_cache.py +121 -0
- calc_terminal/fs_watcher.py +277 -0
- calc_terminal/game.py +193 -0
- calc_terminal/gen1.py +5 -0
- calc_terminal/generators.py +245 -0
- calc_terminal/gestures/__init__.py +42 -0
- calc_terminal/gestures/bindings.py +175 -0
- calc_terminal/gestures/manager.py +477 -0
- calc_terminal/goodbye.py +363 -0
- calc_terminal/gpu3d.py +290 -0
- calc_terminal/graphs.py +358 -0
- calc_terminal/hardware_analyzer.py +440 -0
- calc_terminal/host/__init__.py +30 -0
- calc_terminal/host/browser_manager.py +187 -0
- calc_terminal/host/desktop.py +1386 -0
- calc_terminal/host/launcher.py +395 -0
- calc_terminal/host/terminal.py +279 -0
- calc_terminal/identity.py +216 -0
- calc_terminal/input/__init__.py +54 -0
- calc_terminal/input/capabilities.py +258 -0
- calc_terminal/input/focus.py +87 -0
- calc_terminal/input/gestures.py +64 -0
- calc_terminal/input/pointer.py +114 -0
- calc_terminal/input/touch.py +345 -0
- calc_terminal/keys.py +84 -0
- calc_terminal/live_automation.py +165 -0
- calc_terminal/mathtext.py +433 -0
- calc_terminal/mcp.py +386 -0
- calc_terminal/memory.py +337 -0
- calc_terminal/memory_v2.py +479 -0
- calc_terminal/metrics.py +333 -0
- calc_terminal/mode_detection.py +146 -0
- calc_terminal/model.py +2431 -0
- calc_terminal/model_router.py +665 -0
- calc_terminal/models/__init__.py +0 -0
- calc_terminal/models/active_state.py +187 -0
- calc_terminal/models/dynamic_registry.py +584 -0
- calc_terminal/models/manager.py +781 -0
- calc_terminal/models/model_metadata.json +3526 -0
- calc_terminal/models/profiles.py +194 -0
- calc_terminal/models/registry.py +265 -0
- calc_terminal/models/schema.py +197 -0
- calc_terminal/models/validator.py +287 -0
- calc_terminal/models/verification_engine.py +368 -0
- calc_terminal/native_picker.py +215 -0
- calc_terminal/ollama_catalog.py +279 -0
- calc_terminal/ollama_download.py +233 -0
- calc_terminal/orchestrator.py +304 -0
- calc_terminal/package_research.py +322 -0
- calc_terminal/packages.py +1024 -0
- calc_terminal/pc_specs.py +116 -0
- calc_terminal/permissions.py +334 -0
- calc_terminal/pet.py +106 -0
- calc_terminal/pipeline.py +505 -0
- calc_terminal/platform/__init__.py +491 -0
- calc_terminal/platform/desktop.py +491 -0
- calc_terminal/platform/web.py +781 -0
- calc_terminal/preview/__init__.py +1 -0
- calc_terminal/preview/dev_server.py +303 -0
- calc_terminal/preview/diagnostics.py +131 -0
- calc_terminal/preview/live_reload.py +66 -0
- calc_terminal/preview/manager.py +129 -0
- calc_terminal/project_stats.py +209 -0
- calc_terminal/projects.py +328 -0
- calc_terminal/providers/__init__.py +0 -0
- calc_terminal/providers/adapters/__init__.py +80 -0
- calc_terminal/providers/adapters/anthropic_adapter.py +127 -0
- calc_terminal/providers/adapters/base.py +106 -0
- calc_terminal/providers/adapters/chinese_adapters.py +420 -0
- calc_terminal/providers/adapters/gemini_adapter.py +101 -0
- calc_terminal/providers/adapters/ollama_adapter.py +83 -0
- calc_terminal/providers/adapters/openai_adapter.py +159 -0
- calc_terminal/providers/adapters/other_adapters.py +246 -0
- calc_terminal/providers/anthropic_provider.py +172 -0
- calc_terminal/providers/auto_update.py +416 -0
- calc_terminal/providers/base_provider.py +105 -0
- calc_terminal/providers/discovery_manager.py +207 -0
- calc_terminal/providers/gemini_provider.py +178 -0
- calc_terminal/providers/lifecycle.py +767 -0
- calc_terminal/providers/ollama_adapter.py +707 -0
- calc_terminal/providers/openai_provider.py +254 -0
- calc_terminal/providers/provider_manager.py +1827 -0
- calc_terminal/providers/providers.json +4075 -0
- calc_terminal/reactionsim.py +279 -0
- calc_terminal/registry.py +337 -0
- calc_terminal/report.py +162 -0
- calc_terminal/research/__init__.py +45 -0
- calc_terminal/research/artifact_intel.py +126 -0
- calc_terminal/research/data_lineage.py +123 -0
- calc_terminal/research/experiment_ledger.py +303 -0
- calc_terminal/research/reproducibility.py +131 -0
- calc_terminal/resilience/__init__.py +47 -0
- calc_terminal/resilience/agent_state.py +121 -0
- calc_terminal/resilience/capability_matcher.py +174 -0
- calc_terminal/resilience/circuit_breaker.py +158 -0
- calc_terminal/resilience/failover_engine.py +230 -0
- calc_terminal/resilience/health_monitor.py +192 -0
- calc_terminal/resilience/ollama_adapter.py +125 -0
- calc_terminal/resilience/orchestrator.py +312 -0
- calc_terminal/resilience/types.py +134 -0
- calc_terminal/sandbox.py +98 -0
- calc_terminal/scires.py +558 -0
- calc_terminal/security_scanner.py +126 -0
- calc_terminal/session.py +294 -0
- calc_terminal/sim3d.py +206 -0
- calc_terminal/solver.py +276 -0
- calc_terminal/sound.py +127 -0
- calc_terminal/task_reports.py +287 -0
- calc_terminal/terminal_host.py +201 -0
- calc_terminal/terminal_identity.py +411 -0
- calc_terminal/test_ai_mode_reliability.py +344 -0
- calc_terminal/test_browser.py +368 -0
- calc_terminal/test_code_editor_upgrade.py +485 -0
- calc_terminal/test_customization.py +1148 -0
- calc_terminal/test_customization_ui.py +612 -0
- calc_terminal/test_dynamic_registry.py +304 -0
- calc_terminal/test_extensions.py +436 -0
- calc_terminal/test_overhaul.py +557 -0
- calc_terminal/test_project_detect.py +255 -0
- calc_terminal/test_root_cause_fix.py +527 -0
- calc_terminal/test_stability.py +532 -0
- calc_terminal/test_terminal_identity.py +132 -0
- calc_terminal/test_v079_speed.py +460 -0
- calc_terminal/theme.py +1107 -0
- calc_terminal/timeline.py +139 -0
- calc_terminal/todos.py +246 -0
- calc_terminal/tool_call_normalizer.py +419 -0
- calc_terminal/tui.py +104 -0
- calc_terminal/ui/__init__.py +8 -0
- calc_terminal/ui/activity_panel.py +231 -0
- calc_terminal/ui/activity_stream_panel.py +238 -0
- calc_terminal/ui/animations.py +122 -0
- calc_terminal/ui/app.py +7271 -0
- calc_terminal/ui/attach_panel.py +597 -0
- calc_terminal/ui/attachments.py +424 -0
- calc_terminal/ui/backup_panel.py +810 -0
- calc_terminal/ui/browser_shell.py +887 -0
- calc_terminal/ui/cat_agent.py +357 -0
- calc_terminal/ui/chats_panel.py +899 -0
- calc_terminal/ui/command_palette.py +125 -0
- calc_terminal/ui/command_palette_modal.py +166 -0
- calc_terminal/ui/composer.py +1141 -0
- calc_terminal/ui/context_menu.py +197 -0
- calc_terminal/ui/conversation.py +1435 -0
- calc_terminal/ui/customization_panel.py +1229 -0
- calc_terminal/ui/dashboard.py +404 -0
- calc_terminal/ui/design_system.py +557 -0
- calc_terminal/ui/diff_panel.py +213 -0
- calc_terminal/ui/editor.py +2102 -0
- calc_terminal/ui/empty_state.py +302 -0
- calc_terminal/ui/events.py +487 -0
- calc_terminal/ui/extensions_panel.py +815 -0
- calc_terminal/ui/footer.py +166 -0
- calc_terminal/ui/gestures_panel.py +383 -0
- calc_terminal/ui/goodbye_screen.py +100 -0
- calc_terminal/ui/header.py +1034 -0
- calc_terminal/ui/help_panel.py +254 -0
- calc_terminal/ui/live_activities.py +914 -0
- calc_terminal/ui/mcp_panel.py +570 -0
- calc_terminal/ui/memory_center.py +524 -0
- calc_terminal/ui/mode_colors_panel.py +525 -0
- calc_terminal/ui/nav_screens.py +747 -0
- calc_terminal/ui/ollama_panel.py +536 -0
- calc_terminal/ui/palette.py +221 -0
- calc_terminal/ui/permission_panel.py +269 -0
- calc_terminal/ui/personalization_panel.py +517 -0
- calc_terminal/ui/personalize_center.py +1568 -0
- calc_terminal/ui/preview_panel.py +441 -0
- calc_terminal/ui/resizers.py +402 -0
- calc_terminal/ui/sidebar.py +1285 -0
- calc_terminal/ui/statusbar.py +168 -0
- calc_terminal/ui/theme_css.py +1396 -0
- calc_terminal/ui/thinking.py +226 -0
- calc_terminal/ui/timeline_panel.py +102 -0
- calc_terminal/ui/todo_panel.py +193 -0
- calc_terminal/ui/viewport.py +136 -0
- calc_terminal/ui/vision_panel.py +489 -0
- calc_terminal/ui/welcome_modal.py +343 -0
- calc_terminal/ui/widgets.py +160 -0
- calc_terminal/ui/workspace.py +831 -0
- calc_terminal/viewers/__init__.py +1 -0
- calc_terminal/viewers/document_viewer.py +252 -0
- calc_terminal/viewers/image_viewer.py +241 -0
- calc_terminal/viewers/pdf_viewer.py +203 -0
- calc_terminal/viewers/presentation_viewer.py +164 -0
- calc_terminal/viewers/registry.py +120 -0
- calc_terminal/viewers/spreadsheet_viewer.py +204 -0
- calc_terminal/vision/__init__.py +89 -0
- calc_terminal/vision/analysis.py +194 -0
- calc_terminal/vision/annotations.py +297 -0
- calc_terminal/vision/capture.py +171 -0
- calc_terminal/vision/context.py +231 -0
- calc_terminal/vision/correlation.py +169 -0
- calc_terminal/vision/cursor.py +258 -0
- calc_terminal/vision/events.py +66 -0
- calc_terminal/vision/frame_pipeline.py +259 -0
- calc_terminal/vision/priority.py +218 -0
- calc_terminal/vision/provider.py +180 -0
- calc_terminal/vision/safety.py +149 -0
- calc_terminal/vision/session.py +281 -0
- calc_terminal/vision/verify.py +162 -0
- calc_terminal/vision.py +514 -0
- calc_terminal/vscode_integration.py +113 -0
- calc_terminal/web/__init__.py +8 -0
- calc_terminal/web/cat_runtime.py +710 -0
- calc_terminal/web/server.py +2891 -0
- calc_terminal/web/static/css/app.css +3152 -0
- calc_terminal/web/static/icons/badge-72.png +0 -0
- calc_terminal/web/static/icons/cat.ico +0 -0
- calc_terminal/web/static/icons/icon-128.png +0 -0
- calc_terminal/web/static/icons/icon-144.png +0 -0
- calc_terminal/web/static/icons/icon-152.png +0 -0
- calc_terminal/web/static/icons/icon-192.png +0 -0
- calc_terminal/web/static/icons/icon-384.png +0 -0
- calc_terminal/web/static/icons/icon-512.png +0 -0
- calc_terminal/web/static/icons/icon-72.png +0 -0
- calc_terminal/web/static/icons/icon-96.png +0 -0
- calc_terminal/web/static/icons/icon.svg +34 -0
- calc_terminal/web/static/icons/new-project.png +0 -0
- calc_terminal/web/static/icons/open-project.png +0 -0
- calc_terminal/web/static/index.html +734 -0
- calc_terminal/web/static/js/app.js +2403 -0
- calc_terminal/web/static/manifest.json +88 -0
- calc_terminal/web/static/sw.js +230 -0
- calc_terminal/workflow_engine.py +769 -0
- calc_terminal/workspace.py +593 -0
- calc_terminal/workspace_index.py +385 -0
- cct_cli-0.7.9.0.dist-info/METADATA +210 -0
- cct_cli-0.7.9.0.dist-info/RECORD +325 -0
- cct_cli-0.7.9.0.dist-info/WHEEL +5 -0
- cct_cli-0.7.9.0.dist-info/entry_points.txt +4 -0
- cct_cli-0.7.9.0.dist-info/licenses/LICENSE +21 -0
- 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"
|