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,525 @@
1
+ """
2
+ CCT UI — ModeColorsPanel / ModesManager (v0.7.9.8).
3
+
4
+ User Section > Modes: fully customizable AI modes — change colour (gradient
5
+ auto), edit label/icon/purpose, create new modes, delete modes, reorder,
6
+ reset. Persists to ~/.cct_config.json via ai_modes.* and instantly repaints.
7
+
8
+ Design: responsive, easy to use, not in main menu (only User section).
9
+ """
10
+
11
+ if __name__ == "__main__":
12
+ print("This is a library file and is not meant to be run directly.")
13
+ import sys
14
+ sys.exit(1)
15
+
16
+ import re
17
+
18
+ TEXTUAL_AVAILABLE = True
19
+ try:
20
+ from textual.screen import Screen
21
+ from textual.containers import Vertical, Horizontal, VerticalScroll
22
+ from textual.widgets import Static, Button, Input, TextArea
23
+ from textual.binding import Binding
24
+ except Exception:
25
+ TEXTUAL_AVAILABLE = False
26
+
27
+ if TEXTUAL_AVAILABLE:
28
+ try:
29
+ from .. import ai_modes
30
+ except Exception:
31
+ ai_modes = None
32
+ try:
33
+ from . import theme_css
34
+ except Exception:
35
+ theme_css = None
36
+
37
+ _HEX_RE = re.compile(r"^#?[0-9a-fA-F]{6}$")
38
+ _KEY_RE = re.compile(r"^[a-z0-9_]{2,20}$")
39
+
40
+ _PRESETS = [
41
+ "#7aa2f7", "#bb9af7", "#e0af68", "#9ece6a", "#ff69b4", "#dc143c",
42
+ "#ff7f50", "#40e0d0", "#ffd700", "#8a2be2", "#00fa9a", "#ff6347",
43
+ "#1e90ff", "#ff1493", "#32cd32", "#ffa500",
44
+ ]
45
+
46
+ def _valid_hex(s):
47
+ return bool(_HEX_RE.match((s or "").strip()))
48
+
49
+ def _valid_key(k):
50
+ return bool(_KEY_RE.match((k or "").strip().lower()))
51
+
52
+ class _ModeRow(Horizontal):
53
+ """One mode's full editor row (colour + label/icon/purpose + actions)."""
54
+ def __init__(self, mode_key):
55
+ super().__init__(classes="mcc-row", id=f"mcc-row-{mode_key}")
56
+ self.mode_key = mode_key
57
+
58
+ def compose(self):
59
+ m = ai_modes.MODE_META.get(self.mode_key, {})
60
+ label = m.get("label", self.mode_key)
61
+ icon = m.get("icon", "")
62
+ purpose = m.get("purpose", "")[:80]
63
+ accent = ai_modes.accent_hex(self.mode_key)
64
+ g_start, g_end = ai_modes.gradient(self.mode_key)
65
+ is_current = (self.mode_key == ai_modes.current_mode())
66
+ cur_mark = "●" if is_current else "○"
67
+ # left: info + editable fields
68
+ with Vertical(classes="mcc-info"):
69
+ # header: current marker + icon + label (editable)
70
+ with Horizontal(classes="mcc-header"):
71
+ yield Static(f"[{accent}]{cur_mark}[/] {icon} {label}", classes="mcc-label", markup=True)
72
+ yield Static(f"[{accent}]{self.mode_key}[/]", classes="mcc-key", markup=True)
73
+ yield Static(purpose or "No purpose", classes="mcc-purpose")
74
+ yield Static(f"[{accent}]█[/] {accent} grad [{g_start}]█[/][{g_end}]█[/] {g_start}→{g_end}", classes="mcc-preview", markup=True)
75
+ # inline edit fields (collapsed by default, shown on Edit)
76
+ with Vertical(classes="mcc-editbox", id=f"mcc-edit-{self.mode_key}"):
77
+ with Horizontal(classes="mcc-edit-row"):
78
+ yield Input(value=label, placeholder="Label", id=f"mcc-label-{self.mode_key}", classes="mcc-input-sm")
79
+ yield Input(value=icon, placeholder="Icon", id=f"mcc-icon-{self.mode_key}", classes="mcc-input-sm mcc-input-icon")
80
+ yield Input(value=accent, placeholder="#rrggbb", id=f"mcc-input-{self.mode_key}", classes="mcc-input")
81
+ yield Input(value=purpose, placeholder="Purpose (one-line)", id=f"mcc-purpose-{self.mode_key}", classes="mcc-input-wide")
82
+ # right: actions
83
+ with Vertical(classes="mcc-actions"):
84
+ with Horizontal(classes="mcc-actions-row"):
85
+ yield Button("Save", id=f"mcc-save-{self.mode_key}", variant="primary", classes="cct-btn-sm")
86
+ yield Button("Delete", id=f"mcc-delete-{self.mode_key}", variant="error", classes="cct-btn-sm")
87
+ with Horizontal(classes="mcc-actions-row"):
88
+ yield Button("↑", id=f"mcc-up-{self.mode_key}", classes="cct-btn-sm")
89
+ yield Button("↓", id=f"mcc-down-{self.mode_key}", classes="cct-btn-sm")
90
+ yield Button("Reset", id=f"mcc-reset-{self.mode_key}", classes="cct-btn-sm")
91
+
92
+ def refresh_preview(self):
93
+ try:
94
+ m = ai_modes.MODE_META.get(self.mode_key, {})
95
+ label = m.get("label", self.mode_key)
96
+ icon = m.get("icon", "")
97
+ purpose = m.get("purpose", "")[:80]
98
+ accent = ai_modes.accent_hex(self.mode_key)
99
+ g_start, g_end = ai_modes.gradient(self.mode_key)
100
+ is_current = (self.mode_key == ai_modes.current_mode())
101
+ cur_mark = "●" if is_current else "○"
102
+ self.query_one(".mcc-label", Static).update(f"[{accent}]{cur_mark}[/] {icon} {label}")
103
+ self.query_one(".mcc-key", Static).update(f"[{accent}]{self.mode_key}[/]")
104
+ self.query_one(".mcc-purpose", Static).update(purpose or "No purpose")
105
+ self.query_one(".mcc-preview", Static).update(f"[{accent}]█[/] {accent} grad [{g_start}]█[/][{g_end}]█[/] {g_start}→{g_end}")
106
+ # update inputs to reflect new values (keep user edits if focused? just update)
107
+ try:
108
+ self.query_one(f"#mcc-input-{self.mode_key}", Input).value = accent
109
+ except Exception:
110
+ pass
111
+ try:
112
+ self.query_one(f"#mcc-label-{self.mode_key}", Input).value = label
113
+ except Exception:
114
+ pass
115
+ try:
116
+ self.query_one(f"#mcc-icon-{self.mode_key}", Input).value = icon
117
+ except Exception:
118
+ pass
119
+ try:
120
+ self.query_one(f"#mcc-purpose-{self.mode_key}", Input).value = purpose
121
+ except Exception:
122
+ pass
123
+ except Exception:
124
+ pass
125
+
126
+ # Alias for backward compat (old name)
127
+ _ModeColorRow = _ModeRow
128
+
129
+ class ModeColorsPanel(Screen):
130
+ """Modes manager — User Section only (not main menu). Full CRUD."""
131
+
132
+ CSS = """
133
+ ModeColorsPanel { align: center middle; background: $app-background 60%; }
134
+ #mcc-box {
135
+ width: 96; max-width: 98%; height: auto; max-height: 46;
136
+ background: $surface; border: round $border;
137
+ padding: 0; layout: vertical; overflow: hidden;
138
+ }
139
+ #mcc-titlebar {
140
+ height: 3; min-height: 3; padding: 1 2 0 2;
141
+ border-bottom: solid $border; layout: horizontal;
142
+ }
143
+ #mcc-title { width: 1fr; text-style: bold; }
144
+ #mcc-subtitle { color: $text-faint; height: auto; padding: 0 2 1 2; }
145
+ #mcc-presets {
146
+ height: auto; padding: 0 2 1 2; layout: horizontal;
147
+ border-bottom: solid $border;
148
+ }
149
+ #mcc-presets-label { width: auto; color: $text-faint; height: 1; padding-right: 1; }
150
+ .mcc-preset {
151
+ width: 3; min-width: 3; height: 1; min-height: 1; padding: 0;
152
+ margin-right: 1; border: none; content-align: center middle;
153
+ }
154
+ #mcc-create {
155
+ height: auto; padding: 1 2; border-bottom: solid $border; background: $surface-alt 20%;
156
+ }
157
+ #mcc-create-title { height: 1; text-style: bold; color: $accent; }
158
+ #mcc-create-row1 { height: 3; layout: horizontal; margin-top: 1; }
159
+ #mcc-create-row1 Input { width: 1fr; margin-right: 1; }
160
+ #mcc-create-row2 { height: 3; layout: horizontal; margin-top: 1; }
161
+ #mcc-create-row2 Input { width: 1fr; margin-right: 1; }
162
+ #mcc-create-row3 { height: 3; layout: horizontal; margin-top: 1; }
163
+ #mcc-create-row3 Input { width: 1fr; }
164
+ #mcc-create-actions { height: 3; layout: horizontal; margin-top: 1; }
165
+ #mcc-create-actions Button { margin-right: 1; }
166
+ #mcc-list {
167
+ height: 1fr; min-height: 10; max-height: 22;
168
+ overflow-y: auto; overflow-x: hidden;
169
+ scrollbar-gutter: stable; scrollbar-size: 1 1;
170
+ margin: 0 1; padding: 0;
171
+ }
172
+ .mcc-row {
173
+ height: auto; min-height: 7; padding: 1 1;
174
+ border-bottom: solid $border; border-left: thick transparent;
175
+ layout: horizontal; overflow: hidden;
176
+ }
177
+ .mcc-row:hover { border-left: thick $accent; background: $surface-alt 30%; }
178
+ .mcc-info { width: 1fr; height: auto; min-width: 24; }
179
+ .mcc-header { height: 1; layout: horizontal; }
180
+ .mcc-label { width: auto; height: 1; text-style: bold; margin-right: 1; }
181
+ .mcc-key { width: auto; height: 1; color: $text-faint; }
182
+ .mcc-purpose { width: auto; height: 1; color: $text-faint; }
183
+ .mcc-preview { width: auto; height: 1; color: $text-muted; }
184
+ .mcc-editbox { height: auto; margin-top: 1; display: none; }
185
+ .mcc-editbox.show { display: block; }
186
+ .mcc-edit-row { height: 3; layout: horizontal; margin-bottom: 1; }
187
+ .mcc-input-sm { width: 1fr; margin-right: 1; }
188
+ .mcc-input-icon { width: 10; min-width: 10; }
189
+ .mcc-input { width: 16; min-width: 16; height: 3; margin-right: 1; }
190
+ .mcc-input-wide { width: 100%; height: 3; }
191
+ .mcc-actions {
192
+ width: auto; height: auto; min-width: 24;
193
+ layout: vertical; align: center middle;
194
+ }
195
+ .mcc-actions-row { height: 3; layout: horizontal; margin-bottom: 1; }
196
+ .mcc-actions-row Button { min-width: 10; height: 3; margin-right: 1; }
197
+ #mcc-actions {
198
+ height: auto; padding: 1 2; border-top: solid $border;
199
+ layout: horizontal;
200
+ }
201
+ #mcc-actions-left { width: 1fr; layout: horizontal; }
202
+ #mcc-actions-left Button { margin-right: 1; }
203
+ #mcc-actions-right { width: auto; layout: horizontal; }
204
+ #mcc-actions-right Button { margin-left: 1; }
205
+ #mcc-hint { color: $text-faint; height: auto; padding: 0 2 1 2; text-align: center; }
206
+ #mcc-error { color: $error; height: 1; padding: 0 2; display: none; }
207
+ #mcc-error.show { display: block; }
208
+ .cct-compact .mcc-row { layout: vertical; min-height: 10; }
209
+ .cct-compact .mcc-actions { width: 100%; padding-top: 1; }
210
+ .cct-compact #mcc-box { width: 98; }
211
+ .cct-compact #mcc-create-row1 { layout: vertical; height: auto; }
212
+ .cct-compact #mcc-create-row1 Input { width: 100%; margin-bottom: 1; }
213
+ .cct-compact #mcc-create-row2 { layout: vertical; height: auto; }
214
+ .cct-compact #mcc-create-row2 Input { width: 100%; margin-bottom: 1; }
215
+ """
216
+
217
+ BINDINGS = [Binding("escape", "cancel", "Cancel")]
218
+
219
+ def compose(self):
220
+ with Vertical(id="mcc-box"):
221
+ with Horizontal(id="mcc-titlebar"):
222
+ yield Static("🎨 AI Modes — Manage", id="mcc-title")
223
+ yield Button("✕", id="mcc-close", classes="cct-ctrl")
224
+ yield Static("Create, edit, reorder, delete modes — colours auto-gradient. Only in User section.", id="mcc-subtitle")
225
+ with Horizontal(id="mcc-presets"):
226
+ yield Static("Presets:", id="mcc-presets-label")
227
+ for idx, hx in enumerate(_PRESETS):
228
+ btn = Button("█", id=f"mcc-preset-{idx}", classes="mcc-preset")
229
+ btn.tooltip = hx
230
+ btn._preset_hex = hx
231
+ yield btn
232
+ # create form
233
+ with Vertical(id="mcc-create"):
234
+ yield Static("+ Create New Mode", id="mcc-create-title")
235
+ with Horizontal(id="mcc-create-row1"):
236
+ yield Input(placeholder="key (a-z0-9_, 2-20)", id="mcc-new-key", classes="mcc-input-sm")
237
+ yield Input(placeholder="Label", id="mcc-new-label", classes="mcc-input-sm")
238
+ yield Input(placeholder="Icon", id="mcc-new-icon", classes="mcc-input-sm mcc-input-icon")
239
+ yield Input(placeholder="#rrggbb", id="mcc-new-color", classes="mcc-input")
240
+ with Horizontal(id="mcc-create-row2"):
241
+ yield Input(placeholder="Purpose (one-line)", id="mcc-new-purpose", classes="mcc-input-sm")
242
+ with Horizontal(id="mcc-create-actions"):
243
+ yield Button("Create", id="mcc-create-btn", variant="primary", classes="cct-btn-sm")
244
+ yield Button("Clear", id="mcc-create-clear", classes="cct-btn-sm")
245
+ yield Static("", id="mcc-error")
246
+ with VerticalScroll(id="mcc-list"):
247
+ for key in list(ai_modes.MODE_ORDER):
248
+ yield _ModeRow(key)
249
+ with Horizontal(id="mcc-actions"):
250
+ with Horizontal(id="mcc-actions-left"):
251
+ yield Button("↺ Reset All to Defaults", id="mcc-reset-all", classes="cct-btn-sm")
252
+ with Horizontal(id="mcc-actions-right"):
253
+ yield Button("Close", id="mcc-close2", variant="primary", classes="cct-btn-sm")
254
+ yield Static("Tip: Edit label/icon/purpose/colour then Save. ↑↓ to reorder. Delete removes mode (min 1).", id="mcc-hint")
255
+
256
+ def on_mount(self):
257
+ try:
258
+ self.query_one("#mcc-box").add_class("open")
259
+ except Exception:
260
+ pass
261
+ try:
262
+ for idx, hx in enumerate(_PRESETS):
263
+ btn = self.query_one(f"#mcc-preset-{idx}", Button)
264
+ btn.label = f"[{hx}]█[/]"
265
+ except Exception:
266
+ pass
267
+ if self.size.width < 90 or self.size.height < 34:
268
+ try:
269
+ self.query_one("#mcc-box").add_class("cct-compact")
270
+ except Exception:
271
+ pass
272
+ try:
273
+ self.query_one("#mcc-new-key", Input).focus()
274
+ except Exception:
275
+ pass
276
+
277
+ def _show_error(self, msg=""):
278
+ try:
279
+ lbl = self.query_one("#mcc-error", Static)
280
+ if msg:
281
+ lbl.update(msg)
282
+ lbl.add_class("show")
283
+ else:
284
+ lbl.remove_class("show")
285
+ except Exception:
286
+ pass
287
+
288
+ def _repaint_app(self):
289
+ try:
290
+ app = self.app
291
+ if hasattr(app, "_repaint_theme_layers"):
292
+ app._repaint_theme_layers()
293
+ else:
294
+ app.refresh_css(animate=False)
295
+ try:
296
+ if hasattr(app, "composer") and hasattr(app, "_current_ai_mode"):
297
+ app.composer.set_ai_mode(app._current_ai_mode)
298
+ except Exception:
299
+ pass
300
+ except Exception:
301
+ pass
302
+
303
+ def _refresh_list(self):
304
+ # rebuild list from current MODE_ORDER
305
+ try:
306
+ container = self.query_one("#mcc-list", VerticalScroll)
307
+ container.remove_children()
308
+ for key in list(ai_modes.MODE_ORDER):
309
+ container.mount(_ModeRow(key))
310
+ except Exception:
311
+ pass
312
+ self._repaint_app()
313
+
314
+ def _refresh_row(self, key):
315
+ try:
316
+ row = self.query_one(f"#mcc-row-{key}", _ModeRow)
317
+ row.refresh_preview()
318
+ except Exception:
319
+ pass
320
+ self._repaint_app()
321
+
322
+ def on_button_pressed(self, event):
323
+ bid = event.button.id or ""
324
+ if bid.startswith("mcc-preset-"):
325
+ try:
326
+ hx = getattr(event.button, "_preset_hex", None) or _PRESETS[int(bid.split("-")[-1])]
327
+ # paste into focused color input or new-color
328
+ focused = None
329
+ for k in list(ai_modes.MODE_ORDER):
330
+ try:
331
+ inp = self.query_one(f"#mcc-input-{k}", Input)
332
+ if inp.has_focus:
333
+ focused = inp
334
+ break
335
+ except Exception:
336
+ pass
337
+ if focused is None:
338
+ try:
339
+ new_inp = self.query_one("#mcc-new-color", Input)
340
+ if new_inp.has_focus or not focused:
341
+ focused = new_inp
342
+ except Exception:
343
+ pass
344
+ if focused is None:
345
+ cur = ai_modes.current_mode()
346
+ try:
347
+ focused = self.query_one(f"#mcc-input-{cur}", Input)
348
+ except Exception:
349
+ focused = self.query_one("#mcc-new-color", Input)
350
+ focused.value = hx
351
+ focused.focus()
352
+ self._show_error("")
353
+ except Exception:
354
+ pass
355
+ return
356
+ if bid in ("mcc-close", "mcc-close2"):
357
+ self.dismiss(None)
358
+ return
359
+ if bid == "mcc-reset-all":
360
+ ai_modes.reset_all_modes()
361
+ self._refresh_list()
362
+ try:
363
+ self.app._system_note("All modes reset to defaults")
364
+ except Exception:
365
+ pass
366
+ self._show_error("")
367
+ return
368
+ if bid == "mcc-create-clear":
369
+ for iid in ["#mcc-new-key", "#mcc-new-label", "#mcc-new-icon", "#mcc-new-color", "#mcc-new-purpose"]:
370
+ try:
371
+ self.query_one(iid, Input).value = ""
372
+ except Exception:
373
+ pass
374
+ self._show_error("")
375
+ return
376
+ if bid == "mcc-create-btn":
377
+ try:
378
+ key = self.query_one("#mcc-new-key", Input).value.strip().lower()
379
+ label = self.query_one("#mcc-new-label", Input).value.strip()
380
+ icon = self.query_one("#mcc-new-icon", Input).value.strip()
381
+ color = self.query_one("#mcc-new-color", Input).value.strip()
382
+ purpose = self.query_one("#mcc-new-purpose", Input).value.strip()
383
+ if not _valid_key(key):
384
+ self._show_error("Key: 2-20 chars a-z0-9_")
385
+ return
386
+ if not _valid_hex(color):
387
+ self._show_error(f"Invalid hex '{color}'")
388
+ return
389
+ ok, msg = ai_modes.create_mode(key, label or key.title(), icon or "●", color, purpose)
390
+ if ok:
391
+ self._show_error("")
392
+ self._refresh_list()
393
+ # clear create form
394
+ for iid in ["#mcc-new-key", "#mcc-new-label", "#mcc-new-icon", "#mcc-new-color", "#mcc-new-purpose"]:
395
+ try:
396
+ self.query_one(iid, Input).value = ""
397
+ except Exception:
398
+ pass
399
+ try:
400
+ self.app._system_note(f"Mode '{key}' created")
401
+ except Exception:
402
+ pass
403
+ else:
404
+ self._show_error(msg)
405
+ except Exception as e:
406
+ self._show_error(str(e))
407
+ return
408
+ if bid.startswith("mcc-save-"):
409
+ mode = bid.replace("mcc-save-", "")
410
+ try:
411
+ label = self.query_one(f"#mcc-label-{mode}", Input).value.strip()
412
+ icon = self.query_one(f"#mcc-icon-{mode}", Input).value.strip()
413
+ color = self.query_one(f"#mcc-input-{mode}", Input).value.strip()
414
+ purpose = self.query_one(f"#mcc-purpose-{mode}", Input).value.strip()
415
+ if color and not _valid_hex(color):
416
+ self._show_error(f"Invalid hex for {mode}: '{color}'")
417
+ return
418
+ # update each field if provided
419
+ ok = True
420
+ if label or icon or purpose or color:
421
+ # use update_mode with provided values; if blank keep old
422
+ ok = ai_modes.update_mode(mode,
423
+ label=label if label else None,
424
+ icon=icon if icon else None,
425
+ accent_hex=color if color and _valid_hex(color) else None,
426
+ purpose=purpose if purpose else None)
427
+ if ok:
428
+ self._show_error("")
429
+ self._refresh_row(mode)
430
+ try:
431
+ self.app._system_note(f"{mode} saved")
432
+ except Exception:
433
+ pass
434
+ else:
435
+ self._show_error(f"Could not save {mode}")
436
+ except Exception as e:
437
+ self._show_error(str(e))
438
+ return
439
+ if bid.startswith("mcc-delete-"):
440
+ mode = bid.replace("mcc-delete-", "")
441
+ ok, msg = ai_modes.delete_mode(mode)
442
+ if ok:
443
+ self._show_error("")
444
+ self._refresh_list()
445
+ try:
446
+ self.app._system_note(msg)
447
+ except Exception:
448
+ pass
449
+ else:
450
+ self._show_error(msg)
451
+ return
452
+ if bid.startswith("mcc-up-"):
453
+ mode = bid.replace("mcc-up-", "")
454
+ if ai_modes.move_mode(mode, -1):
455
+ self._refresh_list()
456
+ self._show_error("")
457
+ else:
458
+ self._show_error("Already at top")
459
+ return
460
+ if bid.startswith("mcc-down-"):
461
+ mode = bid.replace("mcc-down-", "")
462
+ if ai_modes.move_mode(mode, 1):
463
+ self._refresh_list()
464
+ self._show_error("")
465
+ else:
466
+ self._show_error("Already at bottom")
467
+ return
468
+ if bid.startswith("mcc-reset-"):
469
+ # careful: mcc-reset-all handled above, this is per-mode reset (only for built-ins)
470
+ mode = bid.replace("mcc-reset-", "")
471
+ if mode == "all":
472
+ return
473
+ if ai_modes.reset_mode_color(mode):
474
+ self._refresh_row(mode)
475
+ try:
476
+ self.app._system_note(f"{mode} colour reset")
477
+ except Exception:
478
+ pass
479
+ self._show_error("")
480
+ else:
481
+ # try full reset for custom modes? delete and recreate as default if was built-in?
482
+ self._show_error(f"Cannot reset '{mode}' (custom mode — delete to remove)")
483
+ return
484
+ if bid.startswith("mcc-preset"):
485
+ return
486
+
487
+ def on_input_submitted(self, event):
488
+ iid = getattr(event.input, "id", "") or ""
489
+ if iid.startswith("mcc-input-") and not iid.startswith("mcc-new"):
490
+ mode = iid.replace("mcc-input-", "")
491
+ try:
492
+ val = event.input.value.strip()
493
+ if not _valid_hex(val):
494
+ self._show_error(f"Invalid hex: '{val}'")
495
+ return
496
+ if ai_modes.update_mode(mode, accent_hex=val):
497
+ self._show_error("")
498
+ self._refresh_row(mode)
499
+ try:
500
+ self.app._system_note(f"{mode} colour → {val}")
501
+ except Exception:
502
+ pass
503
+ except Exception as e:
504
+ self._show_error(str(e))
505
+ elif iid == "mcc-new-color" or iid.startswith("mcc-new-"):
506
+ # enter in create form triggers create
507
+ try:
508
+ # simulate create btn
509
+ self.on_button_pressed(type("E", (), {"button": type("B", (), {"id": "mcc-create-btn"})(), "stop": lambda: None})())
510
+ except Exception:
511
+ pass
512
+
513
+ def action_cancel(self):
514
+ self.dismiss(None)
515
+
516
+ def on_key(self, event):
517
+ if event.key == "escape":
518
+ self.dismiss(None)
519
+
520
+ # Keep old name alias for any external import
521
+ ModeColorsPanel_OLD = ModeColorsPanel
522
+
523
+ else:
524
+ ModeColorsPanel = None
525
+ _ModeRow = None