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,123 @@
1
+ """
2
+ Scientific Computing Capability Adapter per §43:
3
+ - matlab.execute
4
+ - numpy.compute
5
+ - scipy.optimize
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import shutil
11
+ import subprocess
12
+ from typing import Any, Dict, Optional, Tuple
13
+
14
+ from ..schema import (
15
+ AvailabilityStatus,
16
+ Capability,
17
+ CapabilityCategory,
18
+ CapabilitySpec,
19
+ ExecutionResult,
20
+ )
21
+
22
+
23
+ def _check_matlab_health() -> Tuple[AvailabilityStatus, str]:
24
+ if shutil.which("matlab"):
25
+ return AvailabilityStatus.AVAILABLE, "MATLAB CLI executable found"
26
+ try:
27
+ import matlab.engine # noqa: F401
28
+ return AvailabilityStatus.AVAILABLE, "MATLAB Python Engine available"
29
+ except ImportError:
30
+ return AvailabilityStatus.SOFTWARE_NOT_FOUND, "MATLAB is not installed on this system"
31
+
32
+
33
+ def _check_scientific_health() -> Tuple[AvailabilityStatus, str]:
34
+ try:
35
+ import numpy # noqa: F401
36
+ import scipy # noqa: F401
37
+ return AvailabilityStatus.AVAILABLE, f"NumPy {numpy.__version__}, SciPy {scipy.__version__} ready"
38
+ except ImportError as e:
39
+ return AvailabilityStatus.SOFTWARE_NOT_FOUND, f"Scientific packages missing: {e}"
40
+
41
+
42
+ def _matlab_execute_handler(args: Dict[str, Any], context: Optional[Dict[str, Any]] = None) -> ExecutionResult:
43
+ script = args.get("script", "")
44
+ code = args.get("code", "")
45
+ if not script and not code:
46
+ return ExecutionResult(success=False, error="script or code argument is required")
47
+
48
+ exe = shutil.which("matlab")
49
+ if not exe:
50
+ return ExecutionResult(
51
+ success=False,
52
+ error="MATLAB executable not found on host machine. Please install and license MATLAB.",
53
+ status=AvailabilityStatus.SOFTWARE_NOT_FOUND,
54
+ )
55
+
56
+ matlab_cmd = f"-batch \"{code}\"" if code else f"-batch \"run('{script}')\""
57
+ try:
58
+ proc = subprocess.run([exe, "-nodisplay", "-nosplash", "-batch", code or f"run('{script}')"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=120)
59
+ return ExecutionResult(
60
+ success=(proc.returncode == 0),
61
+ output=proc.stdout,
62
+ error=proc.stderr if proc.returncode != 0 else None,
63
+ metadata={"exit_code": proc.returncode},
64
+ )
65
+ except Exception as e:
66
+ return ExecutionResult(success=False, error=f"MATLAB execution failed: {e}")
67
+
68
+
69
+ def _numpy_compute_handler(args: Dict[str, Any], context: Optional[Dict[str, Any]] = None) -> ExecutionResult:
70
+ expr = args.get("expression", "")
71
+ if not expr:
72
+ return ExecutionResult(success=False, error="expression argument is required")
73
+
74
+ try:
75
+ import numpy as np
76
+ # Evaluate safely within numpy scope
77
+ scope = {"np": np}
78
+ # Disallow dangerous builtins
79
+ res = eval(expr, {"__builtins__": {}}, scope)
80
+ if isinstance(res, np.ndarray):
81
+ out = {
82
+ "shape": list(res.shape),
83
+ "dtype": str(res.dtype),
84
+ "data": res.tolist() if res.size <= 1000 else "Array too large to serialize; summary displayed",
85
+ "mean": float(np.mean(res)) if res.size > 0 else None,
86
+ }
87
+ else:
88
+ out = res
89
+ return ExecutionResult(success=True, output=out)
90
+ except Exception as e:
91
+ return ExecutionResult(success=False, error=f"NumPy computation error: {e}")
92
+
93
+
94
+ def register_scientific_capabilities(bus):
95
+ bus.register(Capability(
96
+ spec=CapabilitySpec(
97
+ name="matlab.execute",
98
+ version="1.0.0",
99
+ category=CapabilityCategory.SCIENTIFIC,
100
+ description="Execute MATLAB script or expressions in non-display batch mode.",
101
+ input_schema={"code": "string?", "script": "string?"},
102
+ output_schema={"output": "string"},
103
+ permissions=["execute_terminal"],
104
+ documentation="Executes MATLAB commands where licensed.",
105
+ ),
106
+ handler=_matlab_execute_handler,
107
+ health_checker=_check_matlab_health,
108
+ ))
109
+
110
+ bus.register(Capability(
111
+ spec=CapabilitySpec(
112
+ name="numpy.compute",
113
+ version="1.0.0",
114
+ category=CapabilityCategory.SCIENTIFIC,
115
+ description="Perform numerical array calculations using NumPy.",
116
+ input_schema={"expression": "string"},
117
+ output_schema={"result": "any"},
118
+ permissions=["read_workspace"],
119
+ documentation="Evaluates mathematical numpy expressions.",
120
+ ),
121
+ handler=_numpy_compute_handler,
122
+ health_checker=_check_scientific_health,
123
+ ))
@@ -0,0 +1,161 @@
1
+ """
2
+ Structural Biology Capability Adapter per §44:
3
+ - pymol.open
4
+ - chimerax.open
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ import shutil
11
+ import subprocess
12
+ from typing import Any, Dict, Optional, Tuple
13
+
14
+ from ..schema import (
15
+ AvailabilityStatus,
16
+ Capability,
17
+ CapabilityCategory,
18
+ CapabilitySpec,
19
+ ExecutionResult,
20
+ )
21
+
22
+
23
+ def _find_pymol() -> Optional[str]:
24
+ cmd = shutil.which("pymol")
25
+ if cmd:
26
+ return cmd
27
+ candidates = [
28
+ r"C:\Program Files\PyMOL\PyMOLWin.exe",
29
+ r"C:\Program Files\PyMOL\PyMOL.exe",
30
+ r"C:\Program Files (x86)\Schrodinger\PyMOL\PyMOLWin.exe",
31
+ "/usr/bin/pymol",
32
+ "/usr/local/bin/pymol",
33
+ "/Applications/PyMOL.app/Contents/MacOS/PyMOL",
34
+ ]
35
+ for c in candidates:
36
+ if os.path.exists(c):
37
+ return c
38
+ return None
39
+
40
+
41
+ def _find_chimerax() -> Optional[str]:
42
+ cmd = shutil.which("chimerax")
43
+ if cmd:
44
+ return cmd
45
+ candidates = [
46
+ r"C:\Program Files\ChimeraX\bin\ChimeraX.exe",
47
+ r"C:\Program Files\ChimeraX 1.7\bin\ChimeraX.exe",
48
+ "/usr/bin/chimerax",
49
+ "/usr/local/bin/chimerax",
50
+ "/Applications/ChimeraX.app/Contents/MacOS/ChimeraX",
51
+ ]
52
+ for c in candidates:
53
+ if os.path.exists(c):
54
+ return c
55
+ return None
56
+
57
+
58
+ def _check_structural_health() -> Tuple[AvailabilityStatus, str]:
59
+ pymol = _find_pymol()
60
+ chimerax = _find_chimerax()
61
+ if pymol or chimerax:
62
+ found = []
63
+ if pymol:
64
+ found.append(f"PyMOL: {pymol}")
65
+ if chimerax:
66
+ found.append(f"ChimeraX: {chimerax}")
67
+ return AvailabilityStatus.AVAILABLE, ", ".join(found)
68
+ return AvailabilityStatus.SOFTWARE_NOT_FOUND, "Neither PyMOL nor ChimeraX executable found on system"
69
+
70
+
71
+ def _pymol_open_handler(args: Dict[str, Any], context: Optional[Dict[str, Any]] = None) -> ExecutionResult:
72
+ structure_path = args.get("path", "")
73
+ if not structure_path:
74
+ return ExecutionResult(success=False, error="path argument (PDB/mmCIF) is required")
75
+ structure_path = os.path.abspath(os.path.expanduser(structure_path))
76
+ if not os.path.exists(structure_path):
77
+ return ExecutionResult(success=False, error=f"Structure file not found: {structure_path}")
78
+
79
+ exe = _find_pymol()
80
+ if not exe:
81
+ return ExecutionResult(
82
+ success=False,
83
+ error="PyMOL executable not found on host machine. Please install PyMOL.",
84
+ status=AvailabilityStatus.SOFTWARE_NOT_FOUND,
85
+ )
86
+
87
+ script = args.get("script", "")
88
+ cmd = [exe, structure_path]
89
+ if script:
90
+ cmd.extend(["-d", script])
91
+
92
+ try:
93
+ # Launch non-blocking
94
+ subprocess.Popen(cmd)
95
+ return ExecutionResult(
96
+ success=True,
97
+ output=f"Opened {structure_path} in PyMOL ({exe})",
98
+ metadata={"executable": exe, "structure": structure_path},
99
+ )
100
+ except Exception as e:
101
+ return ExecutionResult(success=False, error=f"Failed to launch PyMOL: {e}")
102
+
103
+
104
+ def _chimerax_open_handler(args: Dict[str, Any], context: Optional[Dict[str, Any]] = None) -> ExecutionResult:
105
+ structure_path = args.get("path", "")
106
+ if not structure_path:
107
+ return ExecutionResult(success=False, error="path argument (PDB/mmCIF) is required")
108
+ structure_path = os.path.abspath(os.path.expanduser(structure_path))
109
+ if not os.path.exists(structure_path):
110
+ return ExecutionResult(success=False, error=f"Structure file not found: {structure_path}")
111
+
112
+ exe = _find_chimerax()
113
+ if not exe:
114
+ return ExecutionResult(
115
+ success=False,
116
+ error="ChimeraX executable not found on host machine. Please install ChimeraX.",
117
+ status=AvailabilityStatus.SOFTWARE_NOT_FOUND,
118
+ )
119
+
120
+ cmd = [exe, structure_path]
121
+ try:
122
+ subprocess.Popen(cmd)
123
+ return ExecutionResult(
124
+ success=True,
125
+ output=f"Opened {structure_path} in ChimeraX ({exe})",
126
+ metadata={"executable": exe, "structure": structure_path},
127
+ )
128
+ except Exception as e:
129
+ return ExecutionResult(success=False, error=f"Failed to launch ChimeraX: {e}")
130
+
131
+
132
+ def register_structural_bio_capabilities(bus):
133
+ bus.register(Capability(
134
+ spec=CapabilitySpec(
135
+ name="pymol.open",
136
+ version="1.0.0",
137
+ category=CapabilityCategory.BIO,
138
+ description="Open protein/molecular structure in PyMOL for 3D visualization.",
139
+ input_schema={"path": "string", "script": "string?"},
140
+ output_schema={"status": "string"},
141
+ permissions=["execute_terminal"],
142
+ documentation="Launches PyMOL with the target structure.",
143
+ ),
144
+ handler=_pymol_open_handler,
145
+ health_checker=_check_structural_health,
146
+ ))
147
+
148
+ bus.register(Capability(
149
+ spec=CapabilitySpec(
150
+ name="chimerax.open",
151
+ version="1.0.0",
152
+ category=CapabilityCategory.BIO,
153
+ description="Open biomolecular structure in UCSF ChimeraX for visualization and analysis.",
154
+ input_schema={"path": "string"},
155
+ output_schema={"status": "string"},
156
+ permissions=["execute_terminal"],
157
+ documentation="Launches ChimeraX with the target structure.",
158
+ ),
159
+ handler=_chimerax_open_handler,
160
+ health_checker=_check_structural_health,
161
+ ))
@@ -0,0 +1,99 @@
1
+ """
2
+ Terminal / Process Execution Capability Adapter:
3
+ - terminal.execute
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import os
9
+ import subprocess
10
+ import time
11
+ from typing import Any, Dict, Optional, Tuple
12
+
13
+ from ..schema import (
14
+ AvailabilityStatus,
15
+ Capability,
16
+ CapabilityCategory,
17
+ CapabilitySpec,
18
+ ExecutionResult,
19
+ )
20
+
21
+
22
+ def _check_terminal_health() -> Tuple[AvailabilityStatus, str]:
23
+ return AvailabilityStatus.AVAILABLE, "System shell execution available"
24
+
25
+
26
+ def _execute_handler(args: Dict[str, Any], context: Optional[Dict[str, Any]] = None) -> ExecutionResult:
27
+ command = args.get("command", "")
28
+ if not command:
29
+ return ExecutionResult(success=False, error="command argument is required")
30
+
31
+ cwd = args.get("cwd", os.getcwd())
32
+ timeout = float(args.get("timeout", 60.0))
33
+ env_overrides = args.get("env", {})
34
+
35
+ env = os.environ.copy()
36
+ if isinstance(env_overrides, dict):
37
+ env.update(env_overrides)
38
+
39
+ t0 = time.perf_counter()
40
+ try:
41
+ proc = subprocess.run(
42
+ command,
43
+ cwd=cwd,
44
+ env=env,
45
+ shell=True,
46
+ stdout=subprocess.PIPE,
47
+ stderr=subprocess.PIPE,
48
+ text=True,
49
+ timeout=timeout,
50
+ )
51
+ duration = round((time.perf_counter() - t0) * 1000.0, 2)
52
+ success = (proc.returncode == 0)
53
+
54
+ return ExecutionResult(
55
+ success=success,
56
+ output=proc.stdout,
57
+ error=proc.stderr if not success else None,
58
+ duration_ms=duration,
59
+ metadata={
60
+ "exit_code": proc.returncode,
61
+ "command": command,
62
+ "cwd": cwd,
63
+ "stdout": proc.stdout,
64
+ "stderr": proc.stderr,
65
+ },
66
+ )
67
+ except subprocess.TimeoutExpired as e:
68
+ duration = round((time.perf_counter() - t0) * 1000.0, 2)
69
+ return ExecutionResult(
70
+ success=False,
71
+ error=f"Command timed out after {timeout} seconds",
72
+ duration_ms=duration,
73
+ metadata={"exit_code": -1, "command": command, "timeout": timeout},
74
+ )
75
+ except Exception as e:
76
+ duration = round((time.perf_counter() - t0) * 1000.0, 2)
77
+ return ExecutionResult(
78
+ success=False,
79
+ error=str(e),
80
+ duration_ms=duration,
81
+ metadata={"exit_code": -1, "command": command},
82
+ )
83
+
84
+
85
+ def register_terminal_capabilities(bus):
86
+ bus.register(Capability(
87
+ spec=CapabilitySpec(
88
+ name="terminal.execute",
89
+ version="1.0.0",
90
+ category=CapabilityCategory.TERMINAL,
91
+ description="Execute a shell command with stdout, stderr and exit code capture.",
92
+ input_schema={"command": "string", "cwd": "string?", "timeout": "number?", "env": "object?"},
93
+ output_schema={"output": "string", "exit_code": "integer"},
94
+ permissions=["execute_terminal"],
95
+ documentation="Executes a command line string in the host shell.",
96
+ ),
97
+ handler=_execute_handler,
98
+ health_checker=_check_terminal_health,
99
+ ))
@@ -0,0 +1,178 @@
1
+ """
2
+ CAT Capability Bus — Central Architecture per §4, §56, §57.
3
+ Modes request capabilities through this bus; tools and integrations are never
4
+ owned privately by individual modes.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ import threading
11
+ import time
12
+ from typing import Any, Callable, Dict, List, Optional, Tuple, Union
13
+
14
+ from .schema import (
15
+ AvailabilityStatus,
16
+ Capability,
17
+ CapabilityCategory,
18
+ CapabilitySpec,
19
+ ExecutionError,
20
+ ExecutionResult,
21
+ )
22
+
23
+ _LOG = logging.getLogger("cct.capabilities")
24
+
25
+
26
+ class CapabilityBus:
27
+ """Central registry and execution dispatch for all CAT capabilities."""
28
+
29
+ _instance: Optional[CapabilityBus] = None
30
+ _lock = threading.Lock()
31
+
32
+ def __new__(cls) -> CapabilityBus:
33
+ with cls._lock:
34
+ if cls._instance is None:
35
+ cls._instance = super(CapabilityBus, cls).__new__(cls)
36
+ cls._instance._capabilities: Dict[str, Capability] = {}
37
+ cls._instance._initialized = False
38
+ return cls._instance
39
+
40
+ def register(self, capability: Capability) -> None:
41
+ """Register a new capability in the bus."""
42
+ name = capability.name.strip().lower()
43
+ self._capabilities[name] = capability
44
+ _LOG.debug("Registered capability: %s (version %s)", name, capability.spec.version)
45
+
46
+ def get(self, name: str) -> Optional[Capability]:
47
+ """Look up a capability by canonical name."""
48
+ canonical = (name or "").strip().lower()
49
+ return self._capabilities.get(canonical)
50
+
51
+ def get_capability(self, name: str) -> Optional[Capability]:
52
+ """Look up a capability by canonical name."""
53
+ return self.get(name)
54
+
55
+ def has(self, name: str) -> bool:
56
+ """Check whether a capability is registered."""
57
+ return name.strip().lower() in self._capabilities
58
+
59
+ def list_capabilities(
60
+ self,
61
+ category: Optional[Union[CapabilityCategory, str]] = None,
62
+ status: Optional[AvailabilityStatus] = None,
63
+ ) -> List[Capability]:
64
+ """Return registered capabilities filtered by category or status."""
65
+ caps = list(self._capabilities.values())
66
+ if category:
67
+ cat_val = category.value if isinstance(category, CapabilityCategory) else str(category).lower()
68
+ caps = [c for c in caps if c.spec.category.value.lower() == cat_val]
69
+ if status:
70
+ caps = [c for c in caps if c.status == status]
71
+ return sorted(caps, key=lambda c: c.name)
72
+
73
+ def execute(
74
+ self,
75
+ name: str,
76
+ args: Optional[Dict[str, Any]] = None,
77
+ context: Optional[Dict[str, Any]] = None,
78
+ task_id: str = "",
79
+ mode: str = "",
80
+ permission_callback: Optional[Callable[[str, str, str, str], str]] = None,
81
+ ) -> ExecutionResult:
82
+ """Execute a capability with permission checking and event emission."""
83
+ canonical = (name or "").strip().lower()
84
+ cap = self.get(canonical)
85
+ if not cap:
86
+ return ExecutionResult(
87
+ success=False,
88
+ error=f"Capability '{canonical}' is not registered in the Capability Bus.",
89
+ status=AvailabilityStatus.UNAVAILABLE,
90
+ )
91
+
92
+ args = args or {}
93
+ context = context or {}
94
+
95
+ # 1. Permission check
96
+ if cap.spec.permissions:
97
+ try:
98
+ from .. import permissions as perm
99
+ for req_perm in cap.spec.permissions:
100
+ if perm.manager.refused_by_always_deny(req_perm):
101
+ return ExecutionResult(
102
+ success=False,
103
+ error=f"Permission denied: {req_perm} is permanently denied for this session.",
104
+ status=AvailabilityStatus.PERMISSION_DENIED,
105
+ )
106
+ if perm.manager.needs_prompt(req_perm):
107
+ decision = "allow_once"
108
+ if permission_callback:
109
+ decision = permission_callback(
110
+ req_perm,
111
+ f"Execute {canonical}",
112
+ str(args.get("path", "") or args.get("target", "")),
113
+ f"Required by capability {canonical}",
114
+ )
115
+ if not perm.manager.decide(req_perm, decision, canonical, "Capability execution"):
116
+ return ExecutionResult(
117
+ success=False,
118
+ error=f"Permission rejected by user for capability '{canonical}'.",
119
+ status=AvailabilityStatus.PERMISSION_DENIED,
120
+ )
121
+ except Exception as e:
122
+ _LOG.warning("Permission validation check failed: %s", e)
123
+
124
+ # 2. Emit ToolStarted event
125
+ try:
126
+ from ..workflow_engine import workflow_engine, EVENT_TOOL_STARTED, ExecutionEvent, STATE_RUNNING
127
+ workflow_engine.record_tool_start(
128
+ tool=canonical,
129
+ args=args,
130
+ turn_id=task_id or context.get("turn_id", ""),
131
+ )
132
+ except Exception:
133
+ pass
134
+
135
+ # 3. Execution
136
+ result = cap.execute(args, context)
137
+
138
+ # 4. Attach Provenance
139
+ result.provenance = {
140
+ "capability": canonical,
141
+ "version": cap.spec.version,
142
+ "task_id": task_id,
143
+ "mode": mode,
144
+ "timestamp": time.time(),
145
+ "duration_ms": result.duration_ms,
146
+ "success": result.success,
147
+ }
148
+
149
+ # 5. Emit ToolFinished event
150
+ try:
151
+ from ..workflow_engine import workflow_engine
152
+ workflow_engine.record_tool_completion(
153
+ tool=canonical,
154
+ result=str(result.output if result.success else result.error)[:200],
155
+ turn_id=task_id or context.get("turn_id", ""),
156
+ duration_ms=int(result.duration_ms),
157
+ )
158
+ except Exception:
159
+ pass
160
+
161
+ return result
162
+
163
+ def health_check_all(self, force: bool = False) -> Dict[str, Dict[str, Any]]:
164
+ """Run health check across all registered capabilities."""
165
+ report = {}
166
+ for name, cap in self._capabilities.items():
167
+ status, message = cap.check_health(force=force)
168
+ report[name] = {
169
+ "category": cap.spec.category.value,
170
+ "status": status.value,
171
+ "message": message,
172
+ "version": cap.spec.version,
173
+ "auth_required": cap.spec.auth_required,
174
+ }
175
+ return report
176
+
177
+
178
+ capability_bus = CapabilityBus()