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,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