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,404 @@
1
+ """
2
+ CCT UI — dashboard.py (spec v0.7 Welcome Dashboard section): shown
3
+ "When no workspace exists" — large logo, version/GPU/model line, quick
4
+ actions, and recent-items lists, all centered.
5
+
6
+ Mounted via ConversationView.show_dashboard() (see conversation.py) in
7
+ place of the plain WelcomeBanner, so everything WelcomeBanner already
8
+ did (removed the instant a real turn arrives) still happens; this
9
+ widget is a richer replacement, not a parallel system.
10
+
11
+ Honest scope: "Recent Simulations", "Recent Calculations", "Pinned
12
+ Notes", and "Recent Quantum Projects" from the spec's list would each
13
+ need their own dedicated history store, and none of those exist yet
14
+ (only calc_terminal/workspace.py's generated-file categories and
15
+ calc_terminal/projects.py's recent-folders list are real, queryable
16
+ data today). Rather than inventing numbers, this dashboard shows the
17
+ sections it can back with real data (Recent Projects, Recent Files via
18
+ workspace.py, Session Notebooks via the existing history list,
19
+ Workspace/API/Ollama status) and a plain "Documentation" quick action;
20
+ it does not fabricate placeholder content for the rest.
21
+
22
+ v0.7.2 roadmap addition: when a workspace is open (`workspace_root`
23
+ given), a third "Project & Workspace" column shows real, computed
24
+ stats — see calc_terminal/project_stats.py for exactly what's real vs.
25
+ honestly reported as unavailable (CPU%, "Indexed Status" — this
26
+ codebase has no separate file index). refresh_stats() updates that
27
+ column in place (no full recompose) so ui/app.py can call it from an
28
+ eventbus subscription without re-rendering the whole dashboard.
29
+ """
30
+
31
+ if __name__ == "__main__":
32
+ print("This is a library file and is not meant to be run directly.")
33
+ import sys
34
+ sys.exit(1)
35
+
36
+ import os
37
+ import platform
38
+ import shutil
39
+
40
+ from .. import projects
41
+ from .. import workspace as cct_workspace
42
+ from .. import project_stats
43
+ from .. import timeline
44
+ from .. import todos
45
+ from .. import aicore
46
+ from .events import FolderOpened, FileOpenRequested, CommandExecuted
47
+
48
+ TEXTUAL_AVAILABLE = True
49
+ try:
50
+ from textual.containers import Vertical, Horizontal, Center
51
+ from textual.widgets import Static, Button
52
+ from . import theme_css
53
+ except Exception:
54
+ TEXTUAL_AVAILABLE = False
55
+
56
+
57
+ if TEXTUAL_AVAILABLE:
58
+
59
+ class _QuickAction(Button):
60
+ def __init__(self, label, action_id):
61
+ super().__init__(label, id=action_id, classes="cct-dash-action cct-btn-3d")
62
+
63
+ class WelcomeDashboard(Vertical):
64
+ """spec v0.7 Welcome Dashboard — v0.7.9.0: THE PRIMARY STARTUP
65
+ SCREEN. Every application launch lands here (right after the
66
+ animated Welcome modal), as does /clear and New Chat Session:
67
+ the composition opens with the large CAT block-art hero +
68
+ identity/ready lines (the branded centerpiece), then quick
69
+ actions, then the real-data columns.
70
+
71
+ State inputs handed in by CCTApp (this widget never reads
72
+ app/session state directly, same convention as BrandHeader/
73
+ StatusLine): logo_lines / version / model_label / notebook_count.
74
+ `workspace_root`, added for the v0.7.2 roadmap's Project &
75
+ Workspace Dashboard, is the one exception — real filesystem/git
76
+ stats need a real path.
77
+
78
+ v0.7.9.0 additions (spec: 'Dashboard and Welcome Screen always
79
+ available'):
80
+ * CAT ASCII hero replaces the old one-line text hero.
81
+ * Mode-aware ready line (set_mode) — always names the ACTUAL
82
+ persona.
83
+ * Live AI STATUS block (update_ai_status): Initializing /
84
+ Working… / Ready / Not configured. Model starts/reloads
85
+ NEVER open another dashboard — the existing one is updated
86
+ in place (requirement: prevent duplicate windows).
87
+ """
88
+
89
+ def __init__(self, logo_lines, version, model_label, notebook_count,
90
+ workspace_root=None, mode_key=None):
91
+ super().__init__(classes="cct-dashboard")
92
+ self._logo_lines = logo_lines
93
+ self._version = version
94
+ self._model_label = model_label
95
+ self._notebook_count = notebook_count
96
+ self._workspace_root = workspace_root
97
+ self._mode_key = mode_key
98
+ self._ai_state = None # live state text pushed by CCTApp
99
+
100
+ # ----------------------------------------------------- artwork --
101
+ def _art_lines(self):
102
+ """Pick the largest CAT word-art variant that fits the LIVE
103
+ chat-pane width — full block letters, compact half-block, or
104
+ a text wordmark on very narrow panes. Never overflows."""
105
+ try:
106
+ from .empty_state import _full_art, _COMPACT_ART
107
+ w = 0
108
+ try:
109
+ w = self.size.width or 0
110
+ except Exception:
111
+ w = 0
112
+ if w and w < 22:
113
+ return None # text-only wordmark
114
+ if w and w < 34:
115
+ return list(_COMPACT_ART) # compact variant
116
+ return [line.rstrip() for line in
117
+ (self._logo_lines or _full_art())]
118
+ except Exception:
119
+ return ["CAT"]
120
+
121
+ def _art_markup(self):
122
+ """Large CAT word art with a restrained top-to-bottom mode-
123
+ gradient tint (blended toward black in LIGHT mode so it stays
124
+ readable on white). Rendered as plain styled text inside a
125
+ Static — no input widget underneath, so it can never inherit
126
+ a selection/highlight background."""
127
+ try:
128
+ from .empty_state import _lerp_hex, _darken
129
+ from .. import theme as _theme
130
+ g_start, g_end = theme_css.gradient_hex()
131
+ if _theme.is_light():
132
+ g_start = _darken(g_start, 0.25)
133
+ g_end = _darken(g_end, 0.25)
134
+ art = self._art_lines()
135
+ if not art:
136
+ return f"[b]{g_start}CAT[/]"
137
+ n = max(1, len(art) - 1)
138
+ lines = []
139
+ for i, line in enumerate(art):
140
+ color = _lerp_hex(g_start, g_end, i / n)
141
+ lines.append(f"[{color} b]{line}[/]")
142
+ return "\n".join(lines)
143
+ except Exception:
144
+ return "[b]CAT[/]"
145
+
146
+ def _ready_markup(self):
147
+ faint = theme_css.current_hex("text-faint")
148
+ muted = theme_css.current_hex("text-muted")
149
+ success = theme_css.current_hex("success")
150
+ try:
151
+ from .cat_agent import mode_label_for
152
+ from .. import ai_modes as _am
153
+ label = mode_label_for(self._mode_key or _am.current_mode())
154
+ except Exception:
155
+ label = "CAT Agent"
156
+ return (f"[{success}]\u25cf[/] [{muted} b]{label}[/] "
157
+ f"[{faint}]ready \u00b7 type to start, / for commands[/]")
158
+
159
+ def _ai_status_markup(self):
160
+ """The live AI runtime line INSIDE the dashboard (spec #5):
161
+ model initialization/reload updates this line — the dashboard
162
+ itself is never duplicated."""
163
+ faint = theme_css.current_hex("text-faint")
164
+ success = theme_css.current_hex("success")
165
+ warning = theme_css.current_hex("warning")
166
+ try:
167
+ from .. import aicore as _aicore
168
+ cfg = _aicore.load_config() or {}
169
+ except Exception:
170
+ cfg = {}
171
+ provider = cfg.get("provider")
172
+ model = cfg.get("model") or ""
173
+ if not provider:
174
+ return (f"[{warning}]\u25cb[/] [{faint}]AI STATUS "
175
+ f"\u2014 not configured \u00b7 run /model[/]")
176
+ state = self._ai_state or "Ready"
177
+ return (f"[{success}]\u25cf[/] [{faint}]AI STATUS \u2014 [/]"
178
+ f"[{theme_css.current_hex('text')}]{provider} "
179
+ f"{model}[/][{faint}] \u00b7 {state}[/]")
180
+
181
+ def compose(self):
182
+ faint = theme_css.current_hex("text-faint")
183
+ muted = theme_css.current_hex("text-muted")
184
+
185
+ # --- CAT hero: block art + identity + ready (branded) ------
186
+ yield Static(self._art_markup(), id="cct-empty-art")
187
+ yield Static(f"[{muted} b]CAT v{self._version}[/]",
188
+ id="cct-empty-version", classes="cct-dash-line")
189
+ yield Static(
190
+ f"[{faint}]Chemistry \u2022 Coding \u2022 Intelligence[/]",
191
+ id="cct-empty-tagline", classes="cct-dash-line")
192
+ yield Static(self._ready_markup(), id="cct-empty-ready",
193
+ classes="cct-dash-line")
194
+ yield Static(
195
+ f"[{faint}]Create \u00b7 Analyze \u00b7 Build \u00b7 Debug "
196
+ f"\u00b7 Research[/]",
197
+ id="cct-empty-suggest", classes="cct-dash-line")
198
+
199
+ # --- live AI runtime status --------------------------------
200
+ yield Static(self._ai_status_markup(), id="cct-dash-ai-status",
201
+ classes="cct-dash-line")
202
+
203
+ with Horizontal(id="cct-dash-actions"):
204
+ yield _QuickAction("\U0001f4c1 Open Folder", "dash-open-folder")
205
+ yield _QuickAction("\U0001f4dd New Chat", "dash-new-chat")
206
+ yield _QuickAction("\u2699 Settings", "dash-settings")
207
+ yield _QuickAction("\U0001f4d6 Documentation", "dash-docs")
208
+
209
+ with Horizontal(id="cct-dash-columns"):
210
+ with Vertical(classes="cct-dash-col"):
211
+ yield Static("[b]Recent Projects[/b]", classes="cct-dash-col-title")
212
+ try:
213
+ recent = projects.recent()[:6]
214
+ except Exception:
215
+ recent = []
216
+ if recent:
217
+ for p in recent:
218
+ yield Static(f" \U0001f4c1 {os.path.basename(p.rstrip(os.sep)) or p}",
219
+ classes="cct-dash-row")
220
+ else:
221
+ yield Static(f"[{faint}]No projects opened yet.[/]", classes="cct-dash-row")
222
+
223
+ with Vertical(classes="cct-dash-col"):
224
+ yield Static("[b]Session Notebooks[/b]", classes="cct-dash-col-title")
225
+ yield Static(f" {self._notebook_count} solved this session", classes="cct-dash-row")
226
+ # v0.7.9.0 fix: workspace data collection is guarded —
227
+ # a read-only/protected root (e.g. launching `cat`
228
+ # from C:\WINDOWS\System32) must degrade to an honest
229
+ # 'unavailable' line, never PermissionError the whole
230
+ # startup compose.
231
+ try:
232
+ summary = cct_workspace.summary()
233
+ except Exception:
234
+ summary = []
235
+ if summary:
236
+ for cat, count, newest, when in summary[:5]:
237
+ yield Static(f" \U0001f4c4 {cat}: {count} ({newest}, {when})",
238
+ classes="cct-dash-row")
239
+ else:
240
+ yield Static(f"[{faint}]No generated files yet.[/]", classes="cct-dash-row")
241
+
242
+ if self._workspace_root:
243
+ with Vertical(classes="cct-dash-col"):
244
+ yield Static("[b]Project & Workspace[/b]", classes="cct-dash-col-title")
245
+ try:
246
+ stats_markup = self._stats_markup()
247
+ except Exception as e:
248
+ stats_markup = (
249
+ f" [{theme_css.current_hex('text-faint')}]"
250
+ f"Workspace stats unavailable: "
251
+ f"{type(e).__name__}[/]")
252
+ yield Static(stats_markup, id="cct-dash-stats-body")
253
+
254
+ def _stats_markup(self):
255
+ """The whole Project & Workspace column body as one markup
256
+ string, shared by compose() and refresh_stats() so the two
257
+ can never drift into showing different formatting for the
258
+ same numbers. (compose() wraps this in its own guard — a
259
+ failing scan degrades to an 'unavailable' line.)"""
260
+ from . import theme_css
261
+ faint = theme_css.current_hex("text-faint")
262
+ root = self._workspace_root
263
+ stats = project_stats.scan_workspace(root)
264
+ branch = project_stats.git_branch(root)
265
+ deps = project_stats.dependencies(root)
266
+ mem = project_stats.memory_usage_mb()
267
+ cpu = project_stats.cpu_percent()
268
+ config = aicore.load_config()
269
+ frac, done, total = todos.progress(root)
270
+ recent_events = timeline.events()[:5]
271
+
272
+ lines = [
273
+ f" {os.path.basename(root.rstrip(os.sep)) or root}",
274
+ f" [{faint}]{root}[/]",
275
+ f" {project_stats.human_size(stats['total_size'])}"
276
+ f"{'+' if stats['truncated'] else ''} \u00b7 "
277
+ f"{stats['total_files'] if stats['total_files'] is not None else '\u2014'} files",
278
+ ]
279
+ if stats["languages"]:
280
+ langs = ", ".join(f"{lang} ({n})" for lang, n in stats["languages"].items())
281
+ lines.append(f" [{faint}]Languages:[/] {langs}")
282
+ lines.append(f" [{faint}]Git branch:[/] {branch or '\u2014 (not a git repo)'}")
283
+ if deps:
284
+ lines.append(f" [{faint}]Dependencies:[/] {', '.join(deps[:6])}"
285
+ + (f" +{len(deps) - 6} more" if len(deps) > 6 else ""))
286
+ lines.append(f" [{faint}]Last modified:[/] {project_stats.human_age(stats['last_modified'])}")
287
+ lines.append(f" [{faint}]AI:[/] {config.get('provider', '\u2014')} "
288
+ f"({config.get('model', '\u2014')})")
289
+ lines.append(f" [{faint}]Memory:[/] "
290
+ f"{f'{mem:.0f} MB' if mem is not None else 'unavailable on this platform'}"
291
+ f" [{faint}]CPU:[/] {f'{cpu:.0f}%' if cpu is not None else 'unavailable'}")
292
+ if total:
293
+ lines.append(f" [{faint}]Todos:[/] {done}/{total} complete")
294
+ if recent_events:
295
+ lines.append(f" [b]Recent Activity[/b]")
296
+ for ev in recent_events:
297
+ lines.append(f" {ev.line()}")
298
+ return "\n".join(lines)
299
+
300
+ def refresh_stats(self):
301
+ """Called from ui/app.py after an eventbus publish (file
302
+ saved, AI reply finished, workspace opened/closed, todo
303
+ changed) — recomputes and repaints just the stats column,
304
+ not the whole dashboard. No-op if this dashboard has no
305
+ workspace column (nothing to refresh) or isn't mounted
306
+ (e.g. a chat has since started and the dashboard was
307
+ already torn down — see conversation.py's hide_welcome)."""
308
+ if not self._workspace_root:
309
+ return
310
+ try:
311
+ self.query_one("#cct-dash-stats-body", Static).update(self._stats_markup())
312
+ except Exception:
313
+ pass
314
+
315
+ # ------------------------------------- v0.7.9.0 live state APIs --
316
+ # Duck-typed by ConversationView.update_empty_state_mode /
317
+ # repaint_empty_state_theme / update_ai_status, so whichever
318
+ # welcome-slot widget is visible (this dashboard or the bare
319
+ # empty-state centerpiece) reacts to the same calls.
320
+
321
+ def set_mode(self, mode_key):
322
+ """The ACTUAL active AI mode changed: repaint the ready line
323
+ and the hero art tint (mode gradient) in place."""
324
+ self._mode_key = mode_key
325
+ try:
326
+ self.query_one("#cct-empty-art", Static).update(
327
+ self._art_markup())
328
+ self.query_one("#cct-empty-ready", Static).update(
329
+ self._ready_markup())
330
+ except Exception:
331
+ pass
332
+
333
+ def repaint_theme(self):
334
+ """Theme switched: re-read every color in place (light/dark
335
+ safe — markup is rebuilt from the current palette)."""
336
+ try:
337
+ self.query_one("#cct-empty-art", Static).update(
338
+ self._art_markup())
339
+ self.query_one("#cct-empty-version", Static).update(
340
+ f"[{theme_css.current_hex('text-muted')} b]CAT v"
341
+ f"{self._version}[/]")
342
+ self.query_one("#cct-empty-tagline", Static).update(
343
+ f"[{theme_css.current_hex('text-faint')}]Chemistry "
344
+ f"\u2022 Coding \u2022 Intelligence[/]")
345
+ self.query_one("#cct-empty-ready", Static).update(
346
+ self._ready_markup())
347
+ self.query_one("#cct-dash-ai-status", Static).update(
348
+ self._ai_status_markup())
349
+ except Exception:
350
+ pass
351
+
352
+ def update_ai_status(self, state=None, model_label=None):
353
+ """Live model-state line inside THIS dashboard (spec: model
354
+ starts/reloads must never open another dashboard). `state`
355
+ is e.g. 'Initializing', 'Loading model', 'Working\u2026',
356
+ 'Ready'; pass None to recompute from config."""
357
+ if state is not None:
358
+ self._ai_state = state
359
+ if model_label is not None:
360
+ self._model_label = model_label
361
+ try:
362
+ self.query_one("#cct-dash-ai-status", Static).update(
363
+ self._ai_status_markup())
364
+ except Exception:
365
+ pass
366
+
367
+ def on_button_pressed(self, event: Button.Pressed):
368
+ bid = event.button.id
369
+ if bid == "dash-open-folder":
370
+ self.post_message(FileOpenRequested("__open_folder_prompt__"))
371
+ elif bid == "dash-new-chat":
372
+ self.post_message(CommandExecuted("/clear"))
373
+ elif bid == "dash-settings":
374
+ self.post_message(CommandExecuted("/settings"))
375
+ elif bid == "dash-docs":
376
+ self.post_message(CommandExecuted("/help"))
377
+
378
+ def on_resize(self, event):
379
+ """Terminal / pane resized: re-pick art + make actions responsive
380
+ (Horizontal → Vertical stack when chat gets narrow from editor stretch)."""
381
+ try:
382
+ self.query_one("#cct-empty-art", Static).update(
383
+ self._art_markup())
384
+ except Exception:
385
+ pass
386
+ try:
387
+ w = self.size.width or 0
388
+ # When dashboard gets narrower than ~72 cols (editor stretched or sidebar open),
389
+ # stack quick-action buttons and columns vertically so they never overflow.
390
+ actions = self.query_one("#cct-dash-actions")
391
+ if w and w < 72:
392
+ actions.add_class("stacked")
393
+ else:
394
+ actions.remove_class("stacked")
395
+ columns = self.query_one("#cct-dash-columns")
396
+ if w and w < 72:
397
+ columns.add_class("stacked")
398
+ else:
399
+ columns.remove_class("stacked")
400
+ except Exception:
401
+ pass
402
+
403
+ else:
404
+ WelcomeDashboard = None