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,524 @@
1
+ """
2
+ CCT UI — memory_center.py: Memory Center panel for viewing and managing
3
+ stored memories from memory_v2.MemoryManager. Real Screen push following
4
+ the same patterns as todo_panel.py and timeline_panel.py.
5
+
6
+ Features:
7
+ - Table of all memories with Category, Content (truncated), Importance,
8
+ Confidence, Created, and Actions columns
9
+ - Filter by category dropdown
10
+ - Search box to filter by text content
11
+ - Edit button per row: inline edit mode
12
+ - Delete button per row with confirmation
13
+ - Clear category / Clear all with confirmation
14
+ - Export / Import placeholders
15
+ - Memory stats summary at top
16
+ """
17
+
18
+ if __name__ == "__main__":
19
+ print("This is a library file and is not meant to be run directly.")
20
+ import sys
21
+ sys.exit(1)
22
+
23
+ import json
24
+ import time
25
+ import os
26
+
27
+ from .. import memory_v2
28
+
29
+ TEXTUAL_AVAILABLE = True
30
+ try:
31
+ from textual.containers import Vertical, Horizontal, VerticalScroll
32
+ from textual.screen import Screen
33
+ from textual.widgets import Static, DataTable, Button, Input, Select, Label
34
+ except Exception:
35
+ TEXTUAL_AVAILABLE = False
36
+
37
+
38
+ def _fmt_ts(ts):
39
+ """Format a unix timestamp to a short human-readable string."""
40
+ if not ts:
41
+ return "-"
42
+ try:
43
+ return time.strftime("%Y-%m-%d %H:%M", time.localtime(ts))
44
+ except Exception:
45
+ return "-"
46
+
47
+
48
+ def _truncate(text, maxlen=60):
49
+ """Truncate text to *maxlen*, adding an ellipsis if needed."""
50
+ text = text.replace("\n", " ")
51
+ if len(text) <= maxlen:
52
+ return text
53
+ return text[: maxlen - 1] + "\u2026"
54
+
55
+
56
+ if TEXTUAL_AVAILABLE:
57
+
58
+ class MemoryCenterScreen(Screen):
59
+ """Full-screen panel showing all stored memories with filtering,
60
+ search, edit, delete, clear, and export/import actions."""
61
+
62
+ CSS = """
63
+ MemoryCenterScreen { align: center middle; background: $app-background 60%; }
64
+ #cct-mem-box {
65
+ width: 92%; height: 88%; background: $surface;
66
+ border: round $border; padding: 0;
67
+ overflow: hidden;
68
+ }
69
+ .cct-mem-titlebar {
70
+ height: 3; padding: 1 2 0 2;
71
+ border-bottom: solid $border;
72
+ }
73
+ .cct-mem-title { text-style: bold; width: 1fr; color: $text; }
74
+ .cct-mem-close {
75
+ width: 3; min-width: 3; height: 1; padding: 0;
76
+ background: transparent; color: $text-faint; border: none;
77
+ }
78
+ .cct-mem-close:hover { color: $error; }
79
+
80
+ #cct-mem-stats {
81
+ height: 3; padding: 0 2; color: $text-muted;
82
+ border-bottom: solid $border;
83
+ }
84
+
85
+ #cct-mem-controls {
86
+ height: 3; padding: 1 2; gap: 1;
87
+ border-bottom: solid $border;
88
+ }
89
+ #cct-mem-controls Label { color: $text-faint; width: auto; height: 1; padding-top: 1; }
90
+ #cct-mem-category-filter { width: 28; }
91
+ #cct-mem-search { width: 1fr; }
92
+
93
+ #cct-mem-table-wrap { height: 1fr; }
94
+ #cct-mem-table { height: 1fr; }
95
+ #cct-mem-table > DataTable { height: 1fr; }
96
+
97
+ .cct-mem-actions {
98
+ height: 3; padding: 0 2; gap: 1;
99
+ border-top: solid $border;
100
+ align-horizontal: right;
101
+ }
102
+ .cct-mem-actions Button { margin-left: 1; }
103
+
104
+ #cct-mem-empty {
105
+ color: $text-faint; padding: 2 4; height: 1fr;
106
+ content-align: center middle;
107
+ }
108
+
109
+ .cct-mem-edit-row {
110
+ height: 3; padding: 0 2; gap: 1;
111
+ border-top: solid $border;
112
+ }
113
+ .cct-mem-edit-row Input { width: 1fr; }
114
+ .cct-mem-edit-row Button { min-width: 10; }
115
+ """
116
+
117
+ CATEGORY_CHOICES = [
118
+ ("All", "__all__"),
119
+ ("User Prefs", "user_preferences"),
120
+ ("Programming Prefs", "programming_preferences"),
121
+ ("Project Prefs", "project_preferences"),
122
+ ("Important Instructions", "important_instructions"),
123
+ ("Workflow Prefs", "workflow_preferences"),
124
+ ("Conversation Facts", "conversation_facts"),
125
+ ("Project Facts", "project_facts"),
126
+ ("Custom", "custom"),
127
+ ]
128
+
129
+ def __init__(self):
130
+ super().__init__()
131
+ self._manager = memory_v2.MemoryManager()
132
+ self._filter_category = "__all__"
133
+ self._search_query = ""
134
+ self._memories = []
135
+ self._editing_id = None
136
+
137
+ def compose(self):
138
+ with Vertical(id="cct-mem-box"):
139
+ with Horizontal(classes="cct-mem-titlebar"):
140
+ yield Static("[b]Memory Center[/b]", classes="cct-mem-title")
141
+ yield Button("\u2715", id="cct-mem-close",
142
+ classes="cct-mem-close")
143
+ yield Static("", id="cct-mem-stats")
144
+ with Horizontal(id="cct-mem-controls"):
145
+ yield Label("Category:")
146
+ yield Select(self.CATEGORY_CHOICES, value="__all__",
147
+ id="cct-mem-category-filter", allow_blank=False)
148
+ yield Input(placeholder="Search memories\u2026",
149
+ id="cct-mem-search")
150
+ yield VerticalScroll(id="cct-mem-table-wrap")
151
+ with Horizontal(classes="cct-mem-actions"):
152
+ yield Button("Export", id="cct-mem-export",
153
+ classes="cct-btn-sm")
154
+ yield Button("Import", id="cct-mem-import",
155
+ classes="cct-btn-sm")
156
+ yield Button("Clear Category", id="cct-mem-clear-cat",
157
+ variant="error", classes="cct-btn-sm")
158
+ yield Button("Clear All", id="cct-mem-clear-all",
159
+ variant="error")
160
+
161
+ def on_mount(self):
162
+ self._load_memories()
163
+
164
+ # ── data loading ──────────────────────────────────────────────
165
+
166
+ def _load_memories(self):
167
+ """Load memories from MemoryManager, apply filters, populate table."""
168
+ if self._filter_category == "__all__":
169
+ self._memories = self._manager.get_all_memories()
170
+ else:
171
+ self._memories = self._manager.get_memories_by_category(
172
+ self._filter_category
173
+ )
174
+
175
+ if self._search_query:
176
+ q = self._search_query.lower()
177
+ self._memories = [
178
+ m for m in self._memories if q in m.content.lower()
179
+ ]
180
+
181
+ self._update_stats()
182
+ self._render_table()
183
+
184
+ def _update_stats(self):
185
+ """Update the stats summary line."""
186
+ all_mems = self._manager.get_all_memories()
187
+ total = len(all_mems)
188
+ cats = {}
189
+ for m in all_mems:
190
+ cats[m.category] = cats.get(m.category, 0) + 1
191
+ parts = [f"Total: {total}"]
192
+ for cat in sorted(cats):
193
+ short = cat.replace("_", " ").title()
194
+ parts.append(f"{short}: {cats[cat]}")
195
+ shown = len(self._memories)
196
+ if shown != total:
197
+ parts.append(f"Showing: {shown}")
198
+ self.query_one("#cct-mem-stats", Static).update(
199
+ " ".join(parts)
200
+ )
201
+
202
+ def _render_table(self):
203
+ """Populate the DataTable with current memories."""
204
+ wrap = self.query_one("#cct-mem-table-wrap", VerticalScroll)
205
+ for child in list(wrap.children):
206
+ child.remove()
207
+
208
+ if not self._memories:
209
+ wrap.mount(Static("No memories stored.", id="cct-mem-empty"))
210
+ return
211
+
212
+ table = DataTable(id="cct-mem-table")
213
+ table.add_columns(
214
+ "Category", "Content", "Importance", "Confidence", "Created", "Actions"
215
+ )
216
+ table.cursor_type = "row"
217
+
218
+ for mem in self._memories:
219
+ cat_short = mem.category.replace("_", " ").title()
220
+ content_disp = _truncate(mem.content, 55)
221
+ imp = f"{mem.importance:.0%}"
222
+ conf = f"{mem.confidence:.0%}"
223
+ created = _fmt_ts(mem.created_at)
224
+ table.add_row(
225
+ cat_short, content_disp, imp, conf, created,
226
+ "", # actions column — buttons handled via click
227
+ key=mem.id,
228
+ )
229
+
230
+ wrap.mount(table)
231
+
232
+ # Mount action buttons for each row after the table
233
+ for mem in self._memories:
234
+ row_bar = Horizontal(classes="cct-mem-row-actions")
235
+ row_bar._mem_id = mem.id
236
+ yield_btn = row_bar.mount # will call below
237
+ yield_btn(Button("Edit", id=f"cct-mem-edit-{mem.id}",
238
+ classes="cct-btn-sm"))
239
+ yield_btn(Button("Del", id=f"cct-mem-del-{mem.id}",
240
+ variant="error", classes="cct-btn-sm"))
241
+ wrap.mount(row_bar)
242
+
243
+ # ── actions ───────────────────────────────────────────────────
244
+
245
+ def action_filter_category(self, category):
246
+ """Filter memories by the selected category."""
247
+ self._filter_category = category
248
+ self._load_memories()
249
+
250
+ def action_search(self, query):
251
+ """Search memories by text content."""
252
+ self._search_query = query
253
+ self._load_memories()
254
+
255
+ def action_delete_memory(self, memory_id):
256
+ """Delete a memory by id after confirmation."""
257
+ mem = next((m for m in self._memories if m.id == memory_id), None)
258
+ if not mem:
259
+ return
260
+ content_short = _truncate(mem.content, 40)
261
+ self._confirm_action(
262
+ f"Delete memory: \"{content_short}\"?",
263
+ lambda: self._do_delete(memory_id),
264
+ )
265
+
266
+ def _do_delete(self, memory_id):
267
+ self._manager.delete_memory(memory_id)
268
+ if self._editing_id == memory_id:
269
+ self._editing_id = None
270
+ self._hide_edit_row()
271
+ self._load_memories()
272
+
273
+ def action_edit_memory(self, memory_id):
274
+ """Enter inline edit mode for a memory."""
275
+ mem = next((m for m in self._memories if m.id == memory_id), None)
276
+ if not mem:
277
+ return
278
+ self._editing_id = memory_id
279
+ self._show_edit_row(mem)
280
+
281
+ def _show_edit_row(self, mem):
282
+ """Show an inline edit row at the bottom of the panel."""
283
+ self._hide_edit_row()
284
+ wrap = self.query_one("#cct-mem-table-wrap", VerticalScroll)
285
+ edit_row = Horizontal(classes="cct-mem-edit-row", id="cct-mem-edit-row")
286
+ edit_row.mount(Input(value=mem.content, id="cct-mem-edit-input"))
287
+ edit_row.mount(Button("Save", id="cct-mem-edit-save"))
288
+ edit_row.mount(Button("Cancel", id="cct-mem-edit-cancel"))
289
+ wrap.mount(edit_row)
290
+
291
+ def _hide_edit_row(self):
292
+ """Remove the inline edit row if present."""
293
+ try:
294
+ row = self.query_one("#cct-mem-edit-row")
295
+ row.remove()
296
+ except Exception:
297
+ pass
298
+
299
+ def action_save_edit(self):
300
+ """Save the inline-edited memory content."""
301
+ if not self._editing_id:
302
+ return
303
+ try:
304
+ inp = self.query_one("#cct-mem-edit-input", Input)
305
+ new_content = inp.value.strip()
306
+ except Exception:
307
+ return
308
+ if new_content:
309
+ self._manager.update_memory(self._editing_id, content=new_content)
310
+ self._editing_id = None
311
+ self._hide_edit_row()
312
+ self._load_memories()
313
+
314
+ def action_cancel_edit(self):
315
+ """Cancel inline editing."""
316
+ self._editing_id = None
317
+ self._hide_edit_row()
318
+
319
+ def action_clear_category(self):
320
+ """Clear all memories in the currently filtered category."""
321
+ if self._filter_category == "__all__":
322
+ self._confirm_action(
323
+ "Select a specific category before using Clear Category.",
324
+ None,
325
+ )
326
+ return
327
+ count = len(self._manager.get_memories_by_category(
328
+ self._filter_category
329
+ ))
330
+ cat_name = self._filter_category.replace("_", " ").title()
331
+ self._confirm_action(
332
+ f"Delete all {count} memories in \"{cat_name}\"?",
333
+ lambda: self._do_clear_category(),
334
+ )
335
+
336
+ def _do_clear_category(self):
337
+ mems = self._manager.get_memories_by_category(self._filter_category)
338
+ for m in mems:
339
+ self._manager.delete_memory(m.id)
340
+ self._load_memories()
341
+
342
+ def action_clear_all(self):
343
+ """Clear every stored memory with confirmation."""
344
+ all_mems = self._manager.get_all_memories()
345
+ count = len(all_mems)
346
+ self._confirm_action(
347
+ f"Delete ALL {count} memories? This cannot be undone.",
348
+ lambda: self._do_clear_all(),
349
+ )
350
+
351
+ def _do_clear_all(self):
352
+ mems = self._manager.get_all_memories()
353
+ for m in mems:
354
+ self._manager.delete_memory(m.id)
355
+ self._load_memories()
356
+
357
+ def action_export_memories(self):
358
+ """Export all memories to a JSON file (placeholder)."""
359
+ mems = self._manager.get_all_memories()
360
+ data = [m.to_dict() for m in mems]
361
+ out_path = os.path.join(
362
+ os.path.expanduser("~"), ".cct_memory_v2", "export.json"
363
+ )
364
+ try:
365
+ with open(out_path, "w", encoding="utf-8") as f:
366
+ json.dump(data, f, indent=2, ensure_ascii=False)
367
+ self.query_one("#cct-mem-stats", Static).update(
368
+ f"Exported {len(data)} memories to {out_path}"
369
+ )
370
+ except Exception as exc:
371
+ self.query_one("#cct-mem-stats", Static).update(
372
+ f"Export failed: {exc}"
373
+ )
374
+
375
+ def action_import_memories(self):
376
+ """Import memories from a JSON file (placeholder)."""
377
+ in_path = os.path.join(
378
+ os.path.expanduser("~"), ".cct_memory_v2", "export.json"
379
+ )
380
+ if not os.path.isfile(in_path):
381
+ self.query_one("#cct-mem-stats", Static).update(
382
+ "No import file found at ~/.cct_memory_v2/export.json"
383
+ )
384
+ return
385
+ try:
386
+ with open(in_path, "r", encoding="utf-8") as f:
387
+ data = json.load(f)
388
+ imported = 0
389
+ for entry in data:
390
+ cat = entry.get("category", "custom")
391
+ content = entry.get("content", "")
392
+ if cat in memory_v2.VALID_CATEGORIES and content:
393
+ self._manager.store_memory(
394
+ category=cat,
395
+ content=content,
396
+ importance=entry.get("importance", 0.5),
397
+ confidence=entry.get("confidence", 1.0),
398
+ source=entry.get("source", "import"),
399
+ )
400
+ imported += 1
401
+ self._load_memories()
402
+ self.query_one("#cct-mem-stats", Static).update(
403
+ f"Imported {imported} memories"
404
+ )
405
+ except Exception as exc:
406
+ self.query_one("#cct-mem-stats", Static).update(
407
+ f"Import failed: {exc}"
408
+ )
409
+
410
+ def action_close(self):
411
+ """Close the Memory Center screen."""
412
+ self.dismiss(None)
413
+
414
+ # ── confirmation dialog (simple inline) ───────────────────────
415
+
416
+ def _confirm_action(self, message, on_confirm):
417
+ """Show a simple inline confirmation bar.
418
+
419
+ Pops a horizontal bar at the bottom of the panel with the
420
+ message and Yes/No buttons. *on_confirm* is called on Yes
421
+ (may be None for informational messages).
422
+ """
423
+ try:
424
+ old = self.query_one("#cct-mem-confirm-bar")
425
+ old.remove()
426
+ except Exception:
427
+ pass
428
+
429
+ bar = Horizontal(id="cct-mem-confirm-bar", classes="cct-mem-edit-row")
430
+ bar.mount(Static(message, classes="cct-mem-confirm-msg"))
431
+ if on_confirm:
432
+ yes_btn = Button("Yes", id="cct-mem-confirm-yes",
433
+ variant="error", classes="cct-btn-sm")
434
+ yes_btn._on_confirm = on_confirm
435
+ bar.mount(yes_btn)
436
+ bar.mount(Button("No", id="cct-mem-confirm-no", classes="cct-btn-sm"))
437
+ self.query_one("#cct-mem-box").mount(bar)
438
+
439
+ # ── event handlers ────────────────────────────────────────────
440
+
441
+ def on_button_pressed(self, event):
442
+ bid = event.button.id or ""
443
+
444
+ if bid == "cct-mem-close":
445
+ self.action_close()
446
+ return
447
+
448
+ if bid == "cct-mem-export":
449
+ self.action_export_memories()
450
+ return
451
+
452
+ if bid == "cct-mem-import":
453
+ self.action_import_memories()
454
+ return
455
+
456
+ if bid == "cct-mem-clear-cat":
457
+ self.action_clear_category()
458
+ return
459
+
460
+ if bid == "cct-mem-clear-all":
461
+ self.action_clear_all()
462
+ return
463
+
464
+ if bid.startswith("cct-mem-edit-") and not bid.startswith("cct-mem-edit-row"):
465
+ mem_id = bid[len("cct-mem-edit-"):]
466
+ self.action_edit_memory(mem_id)
467
+ return
468
+
469
+ if bid.startswith("cct-mem-del-"):
470
+ mem_id = bid[len("cct-mem-del-"):]
471
+ self.action_delete_memory(mem_id)
472
+ return
473
+
474
+ if bid == "cct-mem-edit-save":
475
+ self.action_save_edit()
476
+ return
477
+
478
+ if bid == "cct-mem-edit-cancel":
479
+ self.action_cancel_edit()
480
+ return
481
+
482
+ if bid == "cct-mem-confirm-yes":
483
+ on_confirm = getattr(event.button, "_on_confirm", None)
484
+ try:
485
+ self.query_one("#cct-mem-confirm-bar").remove()
486
+ except Exception:
487
+ pass
488
+ if on_confirm:
489
+ on_confirm()
490
+ return
491
+
492
+ if bid == "cct-mem-confirm-no":
493
+ try:
494
+ self.query_one("#cct-mem-confirm-bar").remove()
495
+ except Exception:
496
+ pass
497
+ return
498
+
499
+ def on_input_changed(self, event):
500
+ if event.input.id == "cct-mem-search":
501
+ self.action_search(event.value)
502
+
503
+ def on_input_submitted(self, event):
504
+ if event.input.id == "cct-mem-search":
505
+ self.action_search(event.value)
506
+ elif event.input.id == "cct-mem-edit-input":
507
+ self.action_save_edit()
508
+
509
+ def on_select_changed(self, event):
510
+ if event.select.id == "cct-mem-category-filter":
511
+ self.action_filter_category(event.value)
512
+
513
+ def on_key(self, event):
514
+ if event.key == "escape":
515
+ if self._editing_id:
516
+ self.action_cancel_edit()
517
+ else:
518
+ self.dismiss(None)
519
+
520
+
521
+ def open_memory_center(app):
522
+ """Push the MemoryCenterScreen onto the app's screen stack."""
523
+ if TEXTUAL_AVAILABLE:
524
+ app.push_screen(MemoryCenterScreen())