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,343 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CCT UI — WelcomeModal (v0.7.8.61 rebuild: ASCII CCT identity).
|
|
3
|
+
|
|
4
|
+
A one-time, dismissible welcome screen shown on first launch of each
|
|
5
|
+
version. v0.7.8.61 changes:
|
|
6
|
+
|
|
7
|
+
* the CCT ASCII logo (the same box-drawing "CCT" mark the main app
|
|
8
|
+
shows on launch) is the hero — color-pulsed between the active
|
|
9
|
+
mode's gradient stops;
|
|
10
|
+
* a short staged boot animation plays on mount ("Initializing CCT…
|
|
11
|
+
██░░" progress bar → "✓ CCT ready") and is interruptible — any
|
|
12
|
+
key, click, or button press dismisses immediately;
|
|
13
|
+
* the highlights list is kept honest (only features this build
|
|
14
|
+
actually shipped) and scrolls when the terminal is short;
|
|
15
|
+
* "Let's Go →" is the primary call-to-action.
|
|
16
|
+
|
|
17
|
+
Shown at most once per version: a tiny marker file (~/.cct_welcome_seen)
|
|
18
|
+
remembers the last version the user dismisses. Dismissing marks today's
|
|
19
|
+
version; reopening mid-version is possible from the Main Menu's
|
|
20
|
+
Dashboard without re-flagging anything.
|
|
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
|
+
|
|
30
|
+
TEXTUAL_AVAILABLE = True
|
|
31
|
+
try:
|
|
32
|
+
from textual.containers import Vertical, Horizontal
|
|
33
|
+
from textual.screen import Screen
|
|
34
|
+
from textual.widgets import Static, Button
|
|
35
|
+
from textual.binding import Binding
|
|
36
|
+
except Exception:
|
|
37
|
+
TEXTUAL_AVAILABLE = False
|
|
38
|
+
|
|
39
|
+
_MARKER = os.path.join(os.path.expanduser("~"), ".cct_welcome_seen")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _version():
|
|
43
|
+
"""The welcome marker is keyed to the app's real version so a new
|
|
44
|
+
build automatically shows its once-per-version notice — the old
|
|
45
|
+
hardcoded '0.7.8.45' here could drift out of sync with
|
|
46
|
+
calc_terminal/app.py.VERSION (which is exactly the 'welcome screen
|
|
47
|
+
appears every launch' failure mode: marker written for 0.7.8.45
|
|
48
|
+
while the app reports a higher version, so has_seen_version()
|
|
49
|
+
never matched)."""
|
|
50
|
+
try:
|
|
51
|
+
from .. import app as _backend_app
|
|
52
|
+
v = getattr(_backend_app, "VERSION", None)
|
|
53
|
+
if v:
|
|
54
|
+
return v
|
|
55
|
+
except Exception:
|
|
56
|
+
pass
|
|
57
|
+
return "0.7.9.0"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
_VERSION = _version()
|
|
61
|
+
def _safe_int(s):
|
|
62
|
+
try:
|
|
63
|
+
return int(s)
|
|
64
|
+
except (ValueError, TypeError):
|
|
65
|
+
return 0
|
|
66
|
+
|
|
67
|
+
_VERSION_TUPLE = tuple(_safe_int(p) for p in _VERSION.split("."))
|
|
68
|
+
|
|
69
|
+
_HIGHLIGHTS = ()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def has_seen_version() -> bool:
|
|
73
|
+
try:
|
|
74
|
+
if os.path.exists(_MARKER):
|
|
75
|
+
with open(_MARKER, "r", encoding="utf-8") as f:
|
|
76
|
+
seen = f.read().strip()
|
|
77
|
+
return tuple(int(p) for p in seen.split(".")) >= _VERSION_TUPLE
|
|
78
|
+
except Exception:
|
|
79
|
+
pass
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def mark_seen():
|
|
84
|
+
try:
|
|
85
|
+
with open(_MARKER, "w", encoding="utf-8") as f:
|
|
86
|
+
f.write(_VERSION)
|
|
87
|
+
except Exception:
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
if TEXTUAL_AVAILABLE:
|
|
92
|
+
from . import theme_css
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _logo_lines():
|
|
96
|
+
"""The app's own ASCII CCT mark (same source the dashboard and
|
|
97
|
+
the conversation welcome banner use). Imported lazily — the
|
|
98
|
+
backend app module is heavy and must not load at ui import."""
|
|
99
|
+
try:
|
|
100
|
+
from ..app import LOGO
|
|
101
|
+
if LOGO:
|
|
102
|
+
return list(LOGO)
|
|
103
|
+
except Exception:
|
|
104
|
+
pass
|
|
105
|
+
try:
|
|
106
|
+
from ..model import CCT_LOGO
|
|
107
|
+
if CCT_LOGO:
|
|
108
|
+
return list(CCT_LOGO)
|
|
109
|
+
except Exception:
|
|
110
|
+
pass
|
|
111
|
+
return ["CAT"]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class WelcomeModal(Screen):
|
|
115
|
+
"""Once-per-version welcome dialog. Displays the CCT ASCII mark,
|
|
116
|
+
a brief interruptible boot animation, version highlights, and a
|
|
117
|
+
call-to-action. Dismissing any way marks the version seen."""
|
|
118
|
+
|
|
119
|
+
CSS = """
|
|
120
|
+
WelcomeModal { align: center middle; background: $app-background 70%; }
|
|
121
|
+
#wm-box {
|
|
122
|
+
width: 82; height: auto; max-height: 94%;
|
|
123
|
+
background: $surface; border: tall $border-active $border; padding: 0 2;
|
|
124
|
+
opacity: 0; offset-y: 1;
|
|
125
|
+
transition: opacity 150ms, offset 180ms;
|
|
126
|
+
}
|
|
127
|
+
#wm-box.open { opacity: 1; offset-y: 0; }
|
|
128
|
+
#wm-titlebar { height: 3; padding: 1 0 0 0; border-bottom: solid $border; }
|
|
129
|
+
#wm-title { text-style: bold; width: 1fr; }
|
|
130
|
+
#wm-logo { height: auto; content-align: center top; padding-top: 1; }
|
|
131
|
+
#wm-boot { height: 2; padding: 1 0 0 0; }
|
|
132
|
+
#wm-countdown { height: 1; text-align: center; color: $text-faint; }
|
|
133
|
+
#wm-subtitle { color: $text-muted; height: 2; padding-top: 1; }
|
|
134
|
+
#wm-hardware { height: 2; color: $text-muted; padding: 0; }
|
|
135
|
+
.wm-h2 { color: $text-faint; height: 2; padding: 1 0 0 0; }
|
|
136
|
+
#wm-body { height: auto; padding-bottom: 1; overflow-y: auto;
|
|
137
|
+
scrollbar-gutter: stable; scrollbar-size-vertical: 1;
|
|
138
|
+
scrollbar-size-horizontal: 0; scrollbar-color: $border $surface; }
|
|
139
|
+
.wm-row { height: 3; color: $text-muted; }
|
|
140
|
+
.wm-icon { width: 4; }
|
|
141
|
+
.wm-name { text-style: bold; width: 16; }
|
|
142
|
+
#wm-actions { height: 5; padding: 1 0 1 0; border-top: solid $border;
|
|
143
|
+
align-horizontal: center; }
|
|
144
|
+
#wm-actions Button {
|
|
145
|
+
border: tall #c4b5fd #4c1d95;
|
|
146
|
+
background: #7c3aed;
|
|
147
|
+
color: #ffffff;
|
|
148
|
+
text-style: bold;
|
|
149
|
+
min-width: 22;
|
|
150
|
+
}
|
|
151
|
+
#wm-actions Button:hover {
|
|
152
|
+
border: tall #ddd6fe #5b21b6;
|
|
153
|
+
background: #8b5cf6;
|
|
154
|
+
}
|
|
155
|
+
#wm-actions Button.-active {
|
|
156
|
+
border: tall #4c1d95 #c4b5fd;
|
|
157
|
+
offset-y: 1;
|
|
158
|
+
}
|
|
159
|
+
.wm-hint { color: $text-faint; padding-top: 1; height: 2; }
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
BINDINGS = [Binding("escape", "cancel", "Close")]
|
|
163
|
+
|
|
164
|
+
_BOOT_STEPS = 14
|
|
165
|
+
_BOOT_INTERVAL = 0.09 # ~1.3s total
|
|
166
|
+
_PULSE_INTERVAL = 0.65
|
|
167
|
+
|
|
168
|
+
def compose(self):
|
|
169
|
+
self._logo_lines = _logo_lines()
|
|
170
|
+
with Vertical(id="wm-box"):
|
|
171
|
+
with Horizontal(id="wm-titlebar"):
|
|
172
|
+
yield Static(f"\U0001f680 Welcome to CAT v{_VERSION}", id="wm-title")
|
|
173
|
+
yield Static("", id="wm-logo")
|
|
174
|
+
yield Static("", id="wm-boot")
|
|
175
|
+
yield Static("", id="wm-countdown")
|
|
176
|
+
yield Static("Creator: Kazi Zillani \u00b7 Coding \u00b7 Agents \u00b7 Intelligence \u2014 the terminal for builders.",
|
|
177
|
+
id="wm-subtitle")
|
|
178
|
+
try:
|
|
179
|
+
from ..hardware_analyzer import HardwareAnalyzer
|
|
180
|
+
prof = HardwareAnalyzer.analyze()
|
|
181
|
+
gpu_str = f"{prof.gpu_name[:22]} ({prof.vram_gb:.1f}GB {prof.gpu_backend})" if prof.has_gpu else "CPU"
|
|
182
|
+
hw_line = f"⚡ [{theme_css.current_hex('accent')} b]Device Profile:[/] {prof.cpu_model[:26]} \u00b7 {prof.ram_total_gb:.0f}GB RAM \u00b7 {gpu_str} \u00b7 [{theme_css.current_hex('success')} b]Tier: {prof.local_ai_tier.value}[/]"
|
|
183
|
+
yield Static(hw_line, id="wm-hardware")
|
|
184
|
+
except Exception:
|
|
185
|
+
pass
|
|
186
|
+
if _HIGHLIGHTS:
|
|
187
|
+
yield Static("What's new in this build:", classes="wm-h2")
|
|
188
|
+
with Vertical(id="wm-body"):
|
|
189
|
+
for icon, name, desc in _HIGHLIGHTS:
|
|
190
|
+
yield Static(
|
|
191
|
+
f"[{theme_css.current_hex('accent')}]{icon}[/] "
|
|
192
|
+
f"[{theme_css.current_hex('text')}]{name}[/] \u00b7 {desc}",
|
|
193
|
+
classes="wm-row")
|
|
194
|
+
yield Static("Auto-continuing to Dashboard — press Esc/Enter to skip",
|
|
195
|
+
classes="wm-hint")
|
|
196
|
+
|
|
197
|
+
def _accent_hex(self, role="accent"):
|
|
198
|
+
try:
|
|
199
|
+
return theme_css.current_hex(role)
|
|
200
|
+
except Exception:
|
|
201
|
+
return "#888888"
|
|
202
|
+
|
|
203
|
+
def _logo_colors(self):
|
|
204
|
+
"""Alternating gradient stops give the logo a slow
|
|
205
|
+
shimmer; the boot bar uses the flat accent."""
|
|
206
|
+
try:
|
|
207
|
+
start, end = theme_css.gradient_hex()
|
|
208
|
+
except Exception:
|
|
209
|
+
start = end = self._accent_hex()
|
|
210
|
+
if getattr(self, "_pulse_step", 0) % 2 == 0:
|
|
211
|
+
return start, end
|
|
212
|
+
return end, start
|
|
213
|
+
|
|
214
|
+
def _draw_logo(self):
|
|
215
|
+
start, _end = self._logo_colors()
|
|
216
|
+
try:
|
|
217
|
+
logo = "\n".join(f"[{start} b]{line}[/]" for line in self._logo_lines)
|
|
218
|
+
self.query_one("#wm-logo", Static).update(logo)
|
|
219
|
+
except Exception:
|
|
220
|
+
pass
|
|
221
|
+
|
|
222
|
+
def _update_boot(self):
|
|
223
|
+
filled = getattr(self, "_boot_step", 0)
|
|
224
|
+
bar = "\u2588" * filled + "\u2591" * (self._BOOT_STEPS - filled)
|
|
225
|
+
try:
|
|
226
|
+
self.query_one("#wm-boot", Static).update(
|
|
227
|
+
f"[{self._accent_hex()} b]Initializing CAT\u2026[/] {bar}")
|
|
228
|
+
except Exception:
|
|
229
|
+
pass
|
|
230
|
+
|
|
231
|
+
def _tick_boot(self):
|
|
232
|
+
self._boot_step = getattr(self, "_boot_step", 0) + 1
|
|
233
|
+
if self._boot_step >= self._BOOT_STEPS:
|
|
234
|
+
try:
|
|
235
|
+
self._boot_timer.stop()
|
|
236
|
+
except Exception:
|
|
237
|
+
pass
|
|
238
|
+
try:
|
|
239
|
+
self.query_one("#wm-boot", Static).update(
|
|
240
|
+
f"[{self._accent_hex()} b]\u2713 CAT ready[/]"
|
|
241
|
+
f" [{self._accent_hex('text-faint')}]Opening "
|
|
242
|
+
f"Dashboard\u2026[/]")
|
|
243
|
+
except Exception:
|
|
244
|
+
pass
|
|
245
|
+
# Animated countdown to close (shows time remaining)
|
|
246
|
+
self._countdown = 5
|
|
247
|
+
self._update_countdown()
|
|
248
|
+
try:
|
|
249
|
+
self._countdown_timer = self.set_interval(1.0, self._tick_countdown)
|
|
250
|
+
except Exception:
|
|
251
|
+
pass
|
|
252
|
+
self.set_timer(5.0, self._auto_continue)
|
|
253
|
+
return
|
|
254
|
+
self._update_boot()
|
|
255
|
+
|
|
256
|
+
def _update_countdown(self):
|
|
257
|
+
try:
|
|
258
|
+
c = getattr(self, "_countdown", 0)
|
|
259
|
+
# Pulsing dot animation + time
|
|
260
|
+
dot = "●" if c % 2 == 0 else "○"
|
|
261
|
+
self.query_one("#wm-countdown", Static).update(
|
|
262
|
+
f"[{self._accent_hex()}]{dot}[/] Closing in {c}s — press Esc to stay \u2026 [{self._accent_hex('text-faint')}]{'█' * c + '░' * (5 - c)}[/]")
|
|
263
|
+
except Exception:
|
|
264
|
+
pass
|
|
265
|
+
|
|
266
|
+
def _tick_countdown(self):
|
|
267
|
+
c = getattr(self, "_countdown", 0) - 1
|
|
268
|
+
self._countdown = max(0, c)
|
|
269
|
+
self._update_countdown()
|
|
270
|
+
if c <= 0:
|
|
271
|
+
try:
|
|
272
|
+
self._countdown_timer.stop()
|
|
273
|
+
except Exception:
|
|
274
|
+
pass
|
|
275
|
+
|
|
276
|
+
def _auto_continue(self):
|
|
277
|
+
try:
|
|
278
|
+
if self.is_running:
|
|
279
|
+
self.dismiss("auto")
|
|
280
|
+
except Exception:
|
|
281
|
+
pass
|
|
282
|
+
|
|
283
|
+
def _tick_pulse(self):
|
|
284
|
+
self._pulse_step = getattr(self, "_pulse_step", 0) + 1
|
|
285
|
+
self._draw_logo()
|
|
286
|
+
|
|
287
|
+
def on_mount(self):
|
|
288
|
+
self.call_after_refresh(lambda: self.query_one("#wm-box").add_class("open"))
|
|
289
|
+
self.call_after_refresh(self._fit)
|
|
290
|
+
self._pulse_step = 0
|
|
291
|
+
self._boot_step = 0
|
|
292
|
+
self._draw_logo()
|
|
293
|
+
self._update_boot()
|
|
294
|
+
self._boot_timer = self.set_interval(self._BOOT_INTERVAL, self._tick_boot)
|
|
295
|
+
self._pulse_timer = self.set_interval(self._PULSE_INTERVAL, self._tick_pulse)
|
|
296
|
+
# v0.7.8.65: mark the version as seen the moment the modal is
|
|
297
|
+
# SHOWN, not only when it is dismissed. Quitting the app with
|
|
298
|
+
# Ctrl+Q (or closing the terminal) while the modal was up used
|
|
299
|
+
# to skip the dismiss handler, so the marker was never written
|
|
300
|
+
# and the welcome screen reappeared on every single launch.
|
|
301
|
+
mark_seen()
|
|
302
|
+
|
|
303
|
+
def on_unmount(self):
|
|
304
|
+
for timer in ("_boot_timer", "_pulse_timer", "_countdown_timer"):
|
|
305
|
+
t = getattr(self, timer, None)
|
|
306
|
+
if t is not None:
|
|
307
|
+
try:
|
|
308
|
+
t.stop()
|
|
309
|
+
except Exception:
|
|
310
|
+
pass
|
|
311
|
+
|
|
312
|
+
def on_resize(self, event):
|
|
313
|
+
self._fit()
|
|
314
|
+
|
|
315
|
+
def _fit(self):
|
|
316
|
+
try:
|
|
317
|
+
self.call_after_refresh(
|
|
318
|
+
lambda: theme_css.fit_dialog(self, "wm-box", "wm-body"))
|
|
319
|
+
except Exception:
|
|
320
|
+
pass
|
|
321
|
+
|
|
322
|
+
def on_key(self, event):
|
|
323
|
+
if event.key == "escape":
|
|
324
|
+
self.dismiss(None)
|
|
325
|
+
|
|
326
|
+
def action_cancel(self):
|
|
327
|
+
self.dismiss(None)
|
|
328
|
+
|
|
329
|
+
def on_button_pressed(self, event):
|
|
330
|
+
self.dismiss(None)
|
|
331
|
+
|
|
332
|
+
def on_click(self, event):
|
|
333
|
+
if getattr(event, "widget", None) in (self, None):
|
|
334
|
+
self.dismiss(None)
|
|
335
|
+
|
|
336
|
+
def dismiss(self, result=None):
|
|
337
|
+
"""Any dismissal counts as 'seen this version' — the modal
|
|
338
|
+
is a once-per-version notice, not a persistent dialog."""
|
|
339
|
+
mark_seen()
|
|
340
|
+
return super().dismiss(result)
|
|
341
|
+
|
|
342
|
+
else:
|
|
343
|
+
WelcomeModal = None
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CCT UI — small shared presentational widgets with no owner module of
|
|
3
|
+
their own: a generic dismissible Chip (attachments.py builds
|
|
4
|
+
AttachmentChip/PasteChip on top of this) and a StatusPill used by both
|
|
5
|
+
the footer and inline conversation notices. Render state, dispatch
|
|
6
|
+
events — nothing here decides what an action means.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
if __name__ == "__main__":
|
|
10
|
+
print("This is a library file and is not meant to be run directly.")
|
|
11
|
+
import sys
|
|
12
|
+
sys.exit(1)
|
|
13
|
+
|
|
14
|
+
TEXTUAL_AVAILABLE = True
|
|
15
|
+
try:
|
|
16
|
+
from textual.containers import Horizontal
|
|
17
|
+
from textual.widgets import Static, Button
|
|
18
|
+
except Exception:
|
|
19
|
+
TEXTUAL_AVAILABLE = False
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
if TEXTUAL_AVAILABLE:
|
|
23
|
+
import time as _time
|
|
24
|
+
|
|
25
|
+
class _ChipLabel(Static):
|
|
26
|
+
"""The clickable label half of a Chip. Its own on_click handler
|
|
27
|
+
(rather than one on the parent) means a click on the × button —
|
|
28
|
+
which consumes its own click as a Button.Pressed — never also
|
|
29
|
+
triggers the label's action.
|
|
30
|
+
|
|
31
|
+
Double-click support (v0.8.1): two clicks on the label within
|
|
32
|
+
0.5s invoke `on_double_click` instead of the single-click
|
|
33
|
+
action. Textual's Click event carries no chain counter, so the
|
|
34
|
+
timing lives here."""
|
|
35
|
+
|
|
36
|
+
DOUBLE_CLICK_SECONDS = 0.5
|
|
37
|
+
|
|
38
|
+
def __init__(self, text, chip_id, on_click, on_double_click=None):
|
|
39
|
+
super().__init__(text, classes="cct-chip-label")
|
|
40
|
+
self.chip_id = chip_id
|
|
41
|
+
self._on_click = on_click
|
|
42
|
+
self._on_double_click = on_double_click
|
|
43
|
+
self._last_click_at = 0.0
|
|
44
|
+
|
|
45
|
+
def set_label(self, text):
|
|
46
|
+
self.update(text)
|
|
47
|
+
|
|
48
|
+
def on_click(self, event):
|
|
49
|
+
now = _time.monotonic()
|
|
50
|
+
is_double = (now - self._last_click_at) <= self.DOUBLE_CLICK_SECONDS
|
|
51
|
+
self._last_click_at = 0.0 if is_double else now
|
|
52
|
+
if is_double and self._on_double_click:
|
|
53
|
+
self._on_double_click(self.chip_id)
|
|
54
|
+
return
|
|
55
|
+
if is_double:
|
|
56
|
+
# a double click with no handler still counts as one click
|
|
57
|
+
pass
|
|
58
|
+
if self._on_click:
|
|
59
|
+
self._on_click(self.chip_id)
|
|
60
|
+
|
|
61
|
+
class _ChipClose(Static):
|
|
62
|
+
"""Crisp borderless close icon for chips. Subclasses Static so it never
|
|
63
|
+
inherits Button height: 3 rules from theme_css or clips into empty brackets."""
|
|
64
|
+
|
|
65
|
+
DEFAULT_CSS = """
|
|
66
|
+
_ChipClose {
|
|
67
|
+
width: 3;
|
|
68
|
+
min-width: 3;
|
|
69
|
+
max-width: 3;
|
|
70
|
+
height: 1;
|
|
71
|
+
min-height: 1;
|
|
72
|
+
max-height: 1;
|
|
73
|
+
padding: 0;
|
|
74
|
+
margin: 0;
|
|
75
|
+
background: transparent;
|
|
76
|
+
color: $text-muted;
|
|
77
|
+
content-align: center middle;
|
|
78
|
+
}
|
|
79
|
+
_ChipClose:hover {
|
|
80
|
+
color: $error;
|
|
81
|
+
background: $error 20%;
|
|
82
|
+
text-style: bold;
|
|
83
|
+
}
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
def __init__(self, chip_id, on_delete=None):
|
|
87
|
+
super().__init__("\u2715", classes="cct-chip-close", id=f"chip-close-{chip_id}")
|
|
88
|
+
self.chip_id = chip_id
|
|
89
|
+
self._on_delete = on_delete
|
|
90
|
+
self.tooltip = "Remove attachment (\u2715)"
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def label(self):
|
|
94
|
+
return "\u2715"
|
|
95
|
+
|
|
96
|
+
def on_click(self, event):
|
|
97
|
+
event.stop()
|
|
98
|
+
if self._on_delete:
|
|
99
|
+
self._on_delete(self.chip_id)
|
|
100
|
+
|
|
101
|
+
class Chip(Horizontal):
|
|
102
|
+
"""One dismissible chip: a clickable label + a small × button.
|
|
103
|
+
Used directly for generic chips, and as the base shape for
|
|
104
|
+
AttachmentChip / PasteChip in ui/attachments.py."""
|
|
105
|
+
|
|
106
|
+
def __init__(self, chip_id, label_text, *, on_click=None, on_delete=None,
|
|
107
|
+
on_double_click=None, classes="cct-chip"):
|
|
108
|
+
super().__init__(classes=classes)
|
|
109
|
+
self.chip_id = chip_id
|
|
110
|
+
self._label_text = label_text
|
|
111
|
+
self._on_click = on_click
|
|
112
|
+
self._on_delete = on_delete
|
|
113
|
+
self._on_double_click = on_double_click
|
|
114
|
+
|
|
115
|
+
def compose(self):
|
|
116
|
+
yield _ChipLabel(self._label_text, self.chip_id, self._on_click,
|
|
117
|
+
on_double_click=self._on_double_click)
|
|
118
|
+
yield _ChipClose(self.chip_id, on_delete=self._on_delete)
|
|
119
|
+
|
|
120
|
+
def update_label(self, text):
|
|
121
|
+
"""v0.7.8.1: swap a chip's label in place (attachment status
|
|
122
|
+
chips use this to go reading -> ready/failed)."""
|
|
123
|
+
self._label_text = text
|
|
124
|
+
try:
|
|
125
|
+
label = self.query_one(_ChipLabel)
|
|
126
|
+
except Exception:
|
|
127
|
+
return
|
|
128
|
+
label.set_label(text)
|
|
129
|
+
|
|
130
|
+
def on_button_pressed(self, event):
|
|
131
|
+
if event.button.id == f"chip-close-{self.chip_id}":
|
|
132
|
+
event.stop()
|
|
133
|
+
if self._on_delete:
|
|
134
|
+
self._on_delete(self.chip_id)
|
|
135
|
+
|
|
136
|
+
class StatusPill(Static):
|
|
137
|
+
"""One small `label value` pill with a tone color (ready/warn/err),
|
|
138
|
+
used by ui/footer.py's StatusLine and inline system notices.
|
|
139
|
+
|
|
140
|
+
Rich markup (used for the colored `value` span) needs a literal
|
|
141
|
+
color, not a Textual `$variable` — so this pulls the hex straight
|
|
142
|
+
from theme_css.current_hex(), the same single palette source
|
|
143
|
+
every CSS rule in this package resolves against.
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
_TONE_ROLE = {"ready": "success", "warn": "warning", "err": "error", "": "text-muted"}
|
|
147
|
+
|
|
148
|
+
def __init__(self, label, value, tone=""):
|
|
149
|
+
from . import theme_css
|
|
150
|
+
color = theme_css.current_hex(self._TONE_ROLE.get(tone, "text-muted"))
|
|
151
|
+
super().__init__(f"{label} [{color}]{value}[/]", classes="cct-status-pill")
|
|
152
|
+
|
|
153
|
+
def set_value(self, value, tone=""):
|
|
154
|
+
from . import theme_css
|
|
155
|
+
color = theme_css.current_hex(self._TONE_ROLE.get(tone, "text-muted"))
|
|
156
|
+
self.update(f"[{color}]{value}[/]")
|
|
157
|
+
|
|
158
|
+
else:
|
|
159
|
+
Chip = None
|
|
160
|
+
StatusPill = None
|