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,350 @@
1
+ """
2
+ CAT Compute Fabric per §25, §26, §87-§90:
3
+ - Heterogeneous compute target abstraction (Local CPU, Local GPU, Ollama, Docker, WSL, SSH, Slurm, Cloud)
4
+ - Honest environment probe and status determination (zero mocking)
5
+ - Intelligent compute routing based on resource requirements (CPU, RAM, GPU, container, cluster)
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import enum
11
+ import json
12
+ import os
13
+ import platform
14
+ import shutil
15
+ import subprocess
16
+ import time
17
+ from dataclasses import dataclass, field
18
+ from typing import Any, Dict, List, Optional, Tuple
19
+
20
+ from calc_terminal.hardware_analyzer import HardwareAnalyzer
21
+
22
+
23
+ class ComputeTargetType(str, enum.Enum):
24
+ LOCAL_CPU = "local_cpu"
25
+ LOCAL_GPU = "local_gpu"
26
+ OLLAMA = "ollama"
27
+ DOCKER = "docker"
28
+ WSL = "wsl"
29
+ SSH = "ssh"
30
+ SLURM = "slurm"
31
+ CLOUD = "cloud"
32
+
33
+
34
+ class ComputeTargetStatus(str, enum.Enum):
35
+ AVAILABLE = "available"
36
+ UNAVAILABLE = "unavailable"
37
+ AUTHENTICATION_REQUIRED = "auth_required"
38
+ CONFIG_REQUIRED = "config_required"
39
+
40
+
41
+ @dataclass
42
+ class ComputeTarget:
43
+ target_id: str
44
+ target_type: ComputeTargetType
45
+ name: str
46
+ status: ComputeTargetStatus = ComputeTargetStatus.UNAVAILABLE
47
+ hardware_details: Dict[str, Any] = field(default_factory=dict)
48
+ metadata: Dict[str, Any] = field(default_factory=dict)
49
+
50
+ def probe(self) -> ComputeTargetStatus:
51
+ """Real honest check of target availability without mocking."""
52
+ return self.status
53
+
54
+ def execute(
55
+ self,
56
+ command: str,
57
+ working_dir: Optional[str] = None,
58
+ env: Optional[Dict[str, str]] = None,
59
+ timeout: int = 120,
60
+ ) -> Dict[str, Any]:
61
+ """Execute command on this compute target."""
62
+ if self.status != ComputeTargetStatus.AVAILABLE:
63
+ return {
64
+ "success": False,
65
+ "error": f"Target {self.target_id} is not available (Status: {self.status.value})",
66
+ "return_code": -1,
67
+ "stdout": "",
68
+ "stderr": f"Target {self.name} is unavailable.",
69
+ }
70
+
71
+ cwd = working_dir or os.getcwd()
72
+ full_env = dict(os.environ)
73
+ if env:
74
+ full_env.update(env)
75
+
76
+ start_time = time.time()
77
+ try:
78
+ res = subprocess.run(
79
+ command,
80
+ shell=True,
81
+ cwd=cwd,
82
+ env=full_env,
83
+ capture_output=True,
84
+ text=True,
85
+ timeout=timeout,
86
+ )
87
+ return {
88
+ "success": res.returncode == 0,
89
+ "return_code": res.returncode,
90
+ "stdout": res.stdout,
91
+ "stderr": res.stderr,
92
+ "duration_seconds": round(time.time() - start_time, 3),
93
+ }
94
+ except subprocess.TimeoutExpired:
95
+ return {
96
+ "success": False,
97
+ "error": f"Command timed out after {timeout}s",
98
+ "return_code": -1,
99
+ "stdout": "",
100
+ "stderr": f"Execution timed out on {self.target_id}",
101
+ "duration_seconds": round(time.time() - start_time, 3),
102
+ }
103
+ except Exception as ex:
104
+ return {
105
+ "success": False,
106
+ "error": str(ex),
107
+ "return_code": -1,
108
+ "stdout": "",
109
+ "stderr": str(ex),
110
+ "duration_seconds": round(time.time() - start_time, 3),
111
+ }
112
+
113
+ def to_dict(self) -> Dict[str, Any]:
114
+ return {
115
+ "target_id": self.target_id,
116
+ "target_type": self.target_type.value,
117
+ "name": self.name,
118
+ "status": self.status.value,
119
+ "hardware_details": self.hardware_details,
120
+ "metadata": self.metadata,
121
+ }
122
+
123
+
124
+ class LocalCPUTarget(ComputeTarget):
125
+ def __init__(self):
126
+ super().__init__(
127
+ target_id="local_cpu",
128
+ target_type=ComputeTargetType.LOCAL_CPU,
129
+ name="Local CPU Host",
130
+ )
131
+ self.probe()
132
+
133
+ def probe(self) -> ComputeTargetStatus:
134
+ try:
135
+ profile = HardwareAnalyzer.analyze()
136
+ self.hardware_details = {
137
+ "cpu_model": profile.cpu_model,
138
+ "logical_cores": profile.cpu_threads,
139
+ "ram_total_gb": profile.ram_total_gb,
140
+ "ram_available_gb": profile.ram_available_gb,
141
+ "os": f"{profile.os_name} {profile.os_version}",
142
+ }
143
+ self.status = ComputeTargetStatus.AVAILABLE
144
+ except Exception:
145
+ self.status = ComputeTargetStatus.AVAILABLE
146
+ self.hardware_details = {
147
+ "logical_cores": os.cpu_count() or 1,
148
+ "os": platform.platform(),
149
+ }
150
+ return self.status
151
+
152
+
153
+ class LocalGPUTarget(ComputeTarget):
154
+ def __init__(self):
155
+ super().__init__(
156
+ target_id="local_gpu",
157
+ target_type=ComputeTargetType.LOCAL_GPU,
158
+ name="Local GPU Accelerator",
159
+ )
160
+ self.probe()
161
+
162
+ def probe(self) -> ComputeTargetStatus:
163
+ try:
164
+ profile = HardwareAnalyzer.analyze()
165
+ if profile.has_gpu:
166
+ self.hardware_details = {
167
+ "gpu_name": profile.gpu_name,
168
+ "backend": profile.gpu_backend,
169
+ "vram_gb": profile.vram_gb,
170
+ }
171
+ self.status = ComputeTargetStatus.AVAILABLE
172
+ else:
173
+ self.hardware_details = {"gpu_name": "None", "backend": "None", "vram_gb": 0.0}
174
+ self.status = ComputeTargetStatus.UNAVAILABLE
175
+ except Exception:
176
+ self.status = ComputeTargetStatus.UNAVAILABLE
177
+ return self.status
178
+
179
+
180
+ class OllamaTarget(ComputeTarget):
181
+ def __init__(self):
182
+ super().__init__(
183
+ target_id="ollama",
184
+ target_type=ComputeTargetType.OLLAMA,
185
+ name="Ollama Local Inference",
186
+ )
187
+ self.probe()
188
+
189
+ def probe(self) -> ComputeTargetStatus:
190
+ # Check CLI presence first
191
+ ollama_bin = shutil.which("ollama")
192
+ if not ollama_bin:
193
+ self.status = ComputeTargetStatus.UNAVAILABLE
194
+ return self.status
195
+
196
+ # Try fast ping to local endpoint
197
+ try:
198
+ import urllib.request
199
+ req = urllib.request.Request("http://127.0.0.1:11434/api/tags", headers={"User-Agent": "CAT"})
200
+ with urllib.request.urlopen(req, timeout=1.0) as resp:
201
+ if resp.status == 200:
202
+ self.status = ComputeTargetStatus.AVAILABLE
203
+ data = json.loads(resp.read().decode("utf-8"))
204
+ models = [m.get("name") for m in data.get("models", [])]
205
+ self.metadata = {"models": models}
206
+ return self.status
207
+ except Exception:
208
+ pass
209
+
210
+ self.status = ComputeTargetStatus.CONFIG_REQUIRED
211
+ self.metadata = {"notes": "Ollama installed but daemon is not running on port 11434"}
212
+ return self.status
213
+
214
+
215
+ class DockerTarget(ComputeTarget):
216
+ def __init__(self):
217
+ super().__init__(
218
+ target_id="docker",
219
+ target_type=ComputeTargetType.DOCKER,
220
+ name="Docker Container Runtime",
221
+ )
222
+ self.probe()
223
+
224
+ def probe(self) -> ComputeTargetStatus:
225
+ docker_bin = shutil.which("docker")
226
+ if not docker_bin:
227
+ self.status = ComputeTargetStatus.UNAVAILABLE
228
+ return self.status
229
+
230
+ try:
231
+ res = subprocess.run(
232
+ ["docker", "info"],
233
+ capture_output=True,
234
+ text=True,
235
+ timeout=2.0,
236
+ )
237
+ if res.returncode == 0:
238
+ self.status = ComputeTargetStatus.AVAILABLE
239
+ else:
240
+ self.status = ComputeTargetStatus.CONFIG_REQUIRED
241
+ self.metadata = {"notes": "Docker installed but daemon is not responding"}
242
+ except Exception:
243
+ self.status = ComputeTargetStatus.CONFIG_REQUIRED
244
+ return self.status
245
+
246
+
247
+ class WSLTarget(ComputeTarget):
248
+ def __init__(self):
249
+ super().__init__(
250
+ target_id="wsl",
251
+ target_type=ComputeTargetType.WSL,
252
+ name="Windows Subsystem for Linux (WSL2)",
253
+ )
254
+ self.probe()
255
+
256
+ def probe(self) -> ComputeTargetStatus:
257
+ if platform.system().lower() != "windows":
258
+ self.status = ComputeTargetStatus.UNAVAILABLE
259
+ return self.status
260
+
261
+ wsl_bin = shutil.which("wsl")
262
+ if not wsl_bin:
263
+ self.status = ComputeTargetStatus.UNAVAILABLE
264
+ return self.status
265
+
266
+ try:
267
+ res = subprocess.run(
268
+ ["wsl", "--status"],
269
+ capture_output=True,
270
+ text=True,
271
+ timeout=2.0,
272
+ )
273
+ if res.returncode == 0:
274
+ self.status = ComputeTargetStatus.AVAILABLE
275
+ else:
276
+ self.status = ComputeTargetStatus.CONFIG_REQUIRED
277
+ except Exception:
278
+ self.status = ComputeTargetStatus.CONFIG_REQUIRED
279
+ return self.status
280
+
281
+
282
+ class ComputeFabric:
283
+ """Unified Compute Fabric coordinating targets and job routing per §25, §26."""
284
+
285
+ def __init__(self):
286
+ self._targets: Dict[str, ComputeTarget] = {}
287
+ self.discover_targets()
288
+
289
+ def register_target(self, target: ComputeTarget):
290
+ self._targets[target.target_id] = target
291
+
292
+ def discover_targets(self):
293
+ """Discover and initialize standard compute targets."""
294
+ self.register_target(LocalCPUTarget())
295
+ self.register_target(LocalGPUTarget())
296
+ self.register_target(OllamaTarget())
297
+ self.register_target(DockerTarget())
298
+ self.register_target(WSLTarget())
299
+
300
+ def get_target(self, target_id: str) -> Optional[ComputeTarget]:
301
+ return self._targets.get(target_id)
302
+
303
+ def list_targets(self) -> List[ComputeTarget]:
304
+ return list(self._targets.values())
305
+
306
+ def get_available_targets(self) -> List[ComputeTarget]:
307
+ return [t for t in self._targets.values() if t.status == ComputeTargetStatus.AVAILABLE]
308
+
309
+ def route_task(self, requirements: Dict[str, Any]) -> Tuple[ComputeTarget, Optional[str]]:
310
+ """
311
+ Intelligently route task to best compute target per §87.
312
+ Returns (target, warning_or_note).
313
+ """
314
+ # 1. Check GPU requirement
315
+ if requirements.get("requires_gpu"):
316
+ gpu = self.get_target("local_gpu")
317
+ if gpu and gpu.status == ComputeTargetStatus.AVAILABLE:
318
+ return gpu, None
319
+ # Fall back to CPU with clear warning
320
+ cpu = self.get_target("local_cpu") or LocalCPUTarget()
321
+ return cpu, "Task requires GPU, but local GPU is unavailable. Routing to Local CPU."
322
+
323
+ # 2. Check Docker requirement
324
+ if requirements.get("container") or requirements.get("docker_image"):
325
+ docker = self.get_target("docker")
326
+ if docker and docker.status == ComputeTargetStatus.AVAILABLE:
327
+ return docker, None
328
+ cpu = self.get_target("local_cpu") or LocalCPUTarget()
329
+ return cpu, "Task requested container environment, but Docker is unavailable. Falling back to Host CPU."
330
+
331
+ # 3. Check WSL requirement
332
+ if requirements.get("wsl"):
333
+ wsl = self.get_target("wsl")
334
+ if wsl and wsl.status == ComputeTargetStatus.AVAILABLE:
335
+ return wsl, None
336
+ cpu = self.get_target("local_cpu") or LocalCPUTarget()
337
+ return cpu, "WSL requested but unavailable. Falling back to Host CPU."
338
+
339
+ # 4. Check Ollama requirement
340
+ if requirements.get("ollama"):
341
+ ollama = self.get_target("ollama")
342
+ if ollama and ollama.status == ComputeTargetStatus.AVAILABLE:
343
+ return ollama, None
344
+
345
+ # Default to CPU
346
+ cpu = self.get_target("local_cpu") or LocalCPUTarget()
347
+ return cpu, None
348
+
349
+
350
+ compute_fabric = ComputeFabric()
@@ -0,0 +1,227 @@
1
+ """
2
+ CCT centralized configuration (spec section 13).
3
+
4
+ Before this module, settings were scattered: aicore.py wrote its own
5
+ ~/.cct_ai_config.json for provider/model, theme.py wrote its own
6
+ ~/.cct_theme.json for the color theme, memory.py had its own store for
7
+ facts/activity, and things like animation speed, history limits, or a
8
+ feedback-recipient address weren't stored anywhere — they were literal
9
+ constants inline in whichever file used them.
10
+
11
+ This module is the single source of truth going forward: one
12
+ dataclass (`CCTConfig`), one file (`~/.cct_config.json`), one
13
+ get/set/save API. It does NOT yet rip out aicore.py's or theme.py's
14
+ own persistence — that's real surgery on files that currently work,
15
+ and doing it carelessly under time pressure is how you break a
16
+ working AI connection. What it DOES do, today:
17
+
18
+ - defines the full settings schema from the spec (provider, mode,
19
+ theme, accent colors, animation speed, terminal appearance,
20
+ startup page, model, ollama/openrouter endpoints, workspace dir,
21
+ history limits, memory limits, simulation settings, feedback email)
22
+ - persists it centrally and exposes get_config()/save_config()
23
+ - is the fallback default source: theme.py's loader now asks this
24
+ module for the configured default *before* falling back to its
25
+ own hardcoded "dark" (see theme.load_saved_theme) — a real,
26
+ working integration point, not parallel dead code
27
+ - is ready for the Settings UI (cmd_settings) to read/write against
28
+ instead of ad hoc prompts
29
+
30
+ Migrating aicore.py's provider/model storage and theme.py's per-theme
31
+ file fully onto this module is the next increment, tracked in
32
+ CHANGELOG_v0.6.3_architecture.md — not done silently, not skipped
33
+ silently.
34
+ """
35
+
36
+ import json
37
+ import os
38
+ from dataclasses import dataclass, field, asdict, fields
39
+ from typing import Optional
40
+
41
+ CONFIG_PATH = os.path.join(os.path.expanduser("~"), ".cct_config.json")
42
+
43
+
44
+ @dataclass
45
+ class SimulationSettings:
46
+ frame_delay: float = 0.06
47
+ default_max_frames: int = 600
48
+ default_orbital_points: int = 2200
49
+ monte_carlo_batch: int = 6
50
+
51
+
52
+ @dataclass
53
+ class CCTConfig:
54
+ # --- feedback ---
55
+ feedback_email: Optional[str] = None # recipient; None = unset
56
+ feedback_backend: str = "none" # "none" | "smtp" | "api"
57
+
58
+ # --- AI ---
59
+ default_ai_provider: str = "openai" # openai|claude|gemini|groq|openrouter|ollama|custom
60
+ default_ai_mode: str = "notebook" # notebook|research|plan|build|debugger|agent
61
+ default_model: str = "" # provider-specific model id; "" = provider default
62
+ privacy_policy: str = "local_first" # local_first | cloud_first | best_available | never_cloud
63
+ enable_model_fallback: bool = True # auto failover across local/cloud model chain
64
+ ollama_endpoint: str = "http://localhost:11434"
65
+ openrouter_endpoint: str = "https://openrouter.ai/api/v1"
66
+ model_cache_ttl_hours: float = 24.0 # how long cache/models/ lists stay fresh before auto-refresh
67
+
68
+ # --- AI request lifecycle (v0.7.9.5) --------------------------------
69
+ # Total-request deadlines by prompt class, in seconds. A streaming
70
+ # response is NEVER cut off mid-tokens just because it's long: the
71
+ # total deadline only fires when nothing useful has been produced,
72
+ # while model_idle_timeout bounds silence BETWEEN tokens.
73
+ timeout_simple: int = 30 # trivial/fast-path prompts
74
+ timeout_normal: int = 60 # regular chat turns
75
+ timeout_large: int = 300 # agent / build / pipeline work
76
+ model_idle_timeout: int = 45 # max silence between stream chunks
77
+ model_max_retries_per_provider: int = 1 # extra attempts per provider
78
+ model_provider_cooldown: float = 20.0 # seconds a failing provider sits out
79
+ model_retry_backoff_base: float = 0.6 # exponential backoff base (0.6s, 1.2s, ...)
80
+
81
+ # --- appearance ---
82
+ default_theme: str = "dark"
83
+ accent_colors: dict = field(default_factory=lambda: {
84
+ "notebook": "#7aa2f7", # blue (ai_modes MODE_META notebook)
85
+ "agent": "#bb9af7", # purple (agent)
86
+ "build": "#e0af68", # orange (build)
87
+ "plan": "#9ece6a", # green (plan)
88
+ "research": "#ff69b4", # hot pink (research)
89
+ "debugger": "#dc143c", # crimson (debugger)
90
+ })
91
+ accent_gradients: dict = field(default_factory=dict) # optional explicit gradients {mode: [start_hex, end_hex]}
92
+ # --- full mode customization (v0.7.9.8) ---
93
+ # When non-empty, these completely replace ai_modes.MODE_META / MODE_ORDER
94
+ # (snapshot of current modes). Empty = use built-in defaults + accent_colors overrides.
95
+ user_modes: dict = field(default_factory=dict) # {key: {label, icon, accent, gradient:[s,e], purpose, system_prompt?}}
96
+ user_mode_order: list = field(default_factory=list) # ordered keys matching user_modes
97
+ # --- extensions (v0.7.9.10) ---
98
+ # Gestures / Vision / Personalize are installable extensions: enabled => show in main menu,
99
+ # disabled/uninstalled => hidden until re-enabled via Extensions panel.
100
+ # Empty dict = all enabled (backward compat). Explicit False = disabled/uninstalled.
101
+ extensions: dict = field(default_factory=dict) # e.g. {"gestures": true, "vision": false, "personalize": true}
102
+ animation_speed: float = 1.0 # multiplier; 0.5 = slower, 2.0 = faster
103
+ terminal_appearance: str = "auto" # "auto" | "ansi256" | "truecolor"
104
+ startup_page: str = "welcome" # "welcome" | "last_session" | "notebook"
105
+ sidebar_collapsed: bool = False # Explorer sidebar open/closed (persisted toggle state)
106
+
107
+ # --- permissions ---
108
+ default_permission_mode: str = "ask" # ask|restricted|full
109
+
110
+ # --- workspace / data ---
111
+ workspace_directory: str = field(
112
+ default_factory=lambda: os.path.join(os.path.expanduser("~"), "cct_workspace"))
113
+ history_limit: int = 500
114
+ memory_limit: int = 2000
115
+
116
+ simulation: SimulationSettings = field(default_factory=SimulationSettings)
117
+
118
+ # ---------------------------------------------------------- helpers --
119
+ def to_dict(self):
120
+ d = asdict(self)
121
+ return d
122
+
123
+ @classmethod
124
+ def from_dict(cls, d):
125
+ d = dict(d or {})
126
+ sim = d.pop("simulation", None) or {}
127
+ known = {f.name for f in fields(cls)}
128
+ d = {k: v for k, v in d.items() if k in known}
129
+ cfg = cls(**d)
130
+ if sim:
131
+ for k, v in sim.items():
132
+ if hasattr(cfg.simulation, k):
133
+ setattr(cfg.simulation, k, v)
134
+ return cfg
135
+
136
+ def get(self, dotted_key, default=None):
137
+ """e.g. get('accent_colors.build') or get('simulation.frame_delay')"""
138
+ obj = self
139
+ for part in dotted_key.split("."):
140
+ if isinstance(obj, dict):
141
+ if part not in obj:
142
+ return default
143
+ obj = obj[part]
144
+ elif hasattr(obj, part):
145
+ obj = getattr(obj, part)
146
+ else:
147
+ return default
148
+ return obj
149
+
150
+ def set(self, dotted_key, value):
151
+ parts = dotted_key.split(".")
152
+ obj = self
153
+ for part in parts[:-1]:
154
+ obj = obj[part] if isinstance(obj, dict) else getattr(obj, part)
155
+ last = parts[-1]
156
+ if isinstance(obj, dict):
157
+ obj[last] = value
158
+ else:
159
+ setattr(obj, last, value)
160
+
161
+
162
+ _cached = None
163
+
164
+
165
+ def _seed_from_aicore(cfg):
166
+ """If the user already configured a provider via /ai (aicore.py's
167
+ own ~/.cct_ai_config.json) before this central config existed, use
168
+ its non-secret fields (provider/model/ollama_url) as the initial
169
+ defaults here instead of showing the generic hardcoded ones. Reads
170
+ the file directly rather than importing aicore, so config.py stays
171
+ the lighter-weight module with no dependency on it."""
172
+ aicore_file = os.path.join(os.path.expanduser("~"), ".cct_ai_config.json")
173
+ if not os.path.exists(aicore_file):
174
+ return cfg
175
+ try:
176
+ with open(aicore_file, "r", encoding="utf-8") as f:
177
+ ai = json.load(f)
178
+ except (json.JSONDecodeError, OSError):
179
+ return cfg
180
+ if ai.get("provider"):
181
+ cfg.default_ai_provider = ai["provider"]
182
+ if ai.get("model"):
183
+ cfg.default_model = ai["model"]
184
+ if ai.get("ollama_url"):
185
+ cfg.ollama_endpoint = ai["ollama_url"]
186
+ return cfg
187
+
188
+
189
+ def load_config():
190
+ """Read ~/.cct_config.json, filling in defaults for anything missing
191
+ or malformed. Never raises — a corrupt config file degrades to
192
+ defaults rather than crashing the app."""
193
+ global _cached
194
+ if _cached is not None:
195
+ return _cached
196
+ cfg = CCTConfig()
197
+ if os.path.exists(CONFIG_PATH):
198
+ try:
199
+ with open(CONFIG_PATH, "r", encoding="utf-8") as f:
200
+ raw = json.load(f)
201
+ cfg = CCTConfig.from_dict(raw)
202
+ except (json.JSONDecodeError, OSError, TypeError, ValueError):
203
+ pass
204
+ else:
205
+ # first run ever — no point re-asking for a provider the user
206
+ # already set up through /ai before this module existed
207
+ cfg = _seed_from_aicore(cfg)
208
+ _cached = cfg
209
+ return cfg
210
+
211
+
212
+ def save_config(cfg=None):
213
+ """Persist the given (or currently cached) config to disk. Returns
214
+ True on success, False if the write failed (e.g. read-only home)."""
215
+ global _cached
216
+ cfg = cfg or _cached or CCTConfig()
217
+ _cached = cfg
218
+ try:
219
+ with open(CONFIG_PATH, "w", encoding="utf-8") as f:
220
+ json.dump(cfg.to_dict(), f, indent=2)
221
+ return True
222
+ except OSError:
223
+ return False
224
+
225
+
226
+ def get_config():
227
+ return load_config()
@@ -0,0 +1,41 @@
1
+ """
2
+ CAT Core Package — re-exports core architectural modules and singletons.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from calc_terminal.core.mode_registry import ModeRegistry, mode_registry
8
+ from calc_terminal.core.task_graph import TaskGraph, TaskNode, TaskStatus
9
+ from calc_terminal.core.verification import RealityEngine, reality_engine
10
+ from calc_terminal.core.unified_runtime import UnifiedAgentRuntime, unified_runtime
11
+ from calc_terminal.core.project_graph import ProjectGraph, project_graph, SymbolInfo
12
+ from calc_terminal.core.checkpoint import CheckpointManager, checkpoint_manager, Checkpoint
13
+ from calc_terminal.core.recovery import RecoveryEngine, recovery_engine, RecoverySnapshot
14
+ from calc_terminal.core.security_layer import SecurityLayer, security_layer, PermissionTier
15
+
16
+ from .. import input
17
+
18
+ __all__ = [
19
+ "input",
20
+ "ModeRegistry",
21
+ "mode_registry",
22
+ "TaskGraph",
23
+ "TaskNode",
24
+ "TaskStatus",
25
+ "RealityEngine",
26
+ "reality_engine",
27
+ "UnifiedAgentRuntime",
28
+ "unified_runtime",
29
+ "ProjectGraph",
30
+ "project_graph",
31
+ "SymbolInfo",
32
+ "CheckpointManager",
33
+ "checkpoint_manager",
34
+ "Checkpoint",
35
+ "RecoveryEngine",
36
+ "recovery_engine",
37
+ "RecoverySnapshot",
38
+ "SecurityLayer",
39
+ "security_layer",
40
+ "PermissionTier",
41
+ ]