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,710 @@
1
+ """
2
+ CatRuntime — Unified CAT Core Adapter Layer for Fatty CAT.
3
+
4
+ Single source of truth:
5
+ - Wraps existing CAT core modules (aicore, agent, ai_modes, providers, models,
6
+ permissions, memory, customization, extensions, preview, etc.)
7
+ - Provides a clean, typed internal API consumed by Fatty CAT's web server and UI
8
+ - NEVER rewrites or duplicates business logic — exposes CAT core dynamically.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import asyncio
14
+ import os
15
+ import sys
16
+ import time
17
+ from pathlib import Path
18
+ from typing import Any, AsyncGenerator, Dict, List, Optional, Tuple
19
+
20
+ # Ensure package is on sys.path (source-checkout only; pip-installed CAT
21
+ # already has `calc_terminal` importable).
22
+ try:
23
+ import calc_terminal as _ct_check # noqa: F401
24
+ except Exception:
25
+ _CAT_ROOT = Path(__file__).parent.parent
26
+ if str(_CAT_ROOT) not in sys.path:
27
+ sys.path.insert(0, str(_CAT_ROOT))
28
+
29
+ # CAT Core Imports
30
+ from calc_terminal import aicore, ai_modes, commands_data, config as cct_config, identity
31
+ from calc_terminal import memory, permissions as cct_perms
32
+ from calc_terminal.models import manager as model_manager
33
+ from calc_terminal.providers import provider_manager
34
+ from calc_terminal import ollama_catalog, ollama_download
35
+ from calc_terminal import customization, extensions as cct_ext
36
+ from calc_terminal import workspace as ws_module, projects as proj_module
37
+ from calc_terminal.viewers import registry as viewer_registry
38
+ from calc_terminal import eventbus, event_stream
39
+
40
+
41
+ class CatRuntime:
42
+ """Canonical adapter around CAT Core functionality."""
43
+
44
+ def __init__(self):
45
+ self.start_time = time.time()
46
+ self.request_count = 0
47
+ self.error_count = 0
48
+ self.recent_events: List[Dict[str, Any]] = []
49
+ self._wire_events()
50
+
51
+ def _wire_events(self):
52
+ """Wire into CAT eventbus to track recent activity."""
53
+ def _on_bus_event(topic: str, **kwargs):
54
+ entry = {
55
+ "topic": topic,
56
+ "timestamp": time.time(),
57
+ "data": {k: str(v)[:150] for k, v in kwargs.items()}
58
+ }
59
+ self.recent_events.append(entry)
60
+ if len(self.recent_events) > 100:
61
+ self.recent_events.pop(0)
62
+
63
+ for topic in (
64
+ eventbus.FILE_CREATED, eventbus.FILE_SAVED, eventbus.FILE_DELETED,
65
+ eventbus.AI_RESPONSE_GENERATED, eventbus.TERMINAL_COMMAND,
66
+ eventbus.WORKSPACE_OPENED, eventbus.MODE_SWITCHED,
67
+ eventbus.PACKAGE_INSTALLED, eventbus.TODO_CREATED, eventbus.TODO_COMPLETED
68
+ ):
69
+ eventbus.bus.subscribe(topic, lambda t=topic, **kw: _on_bus_event(t, **kw))
70
+
71
+ # ─────────────────────────────────────────────────────────────
72
+ # 1. Capability Discovery & Version Contract
73
+ # ─────────────────────────────────────────────────────────────
74
+
75
+ def get_capabilities(self) -> Dict[str, Any]:
76
+ """Return the dynamic CAT capability registry and version contract."""
77
+ cfg = aicore.load_config()
78
+ v_reg = viewer_registry.ViewerRegistry()
79
+
80
+ # Tools summary
81
+ tools_list = [
82
+ {"id": "list_formulas", "name": "List Formulas", "category": "math"},
83
+ {"id": "solve_formula", "name": "Solve Formula", "category": "math"},
84
+ {"id": "solve_custom", "name": "Solve Custom", "category": "math"},
85
+ {"id": "calculate", "name": "Safe Calculator", "category": "math"},
86
+ {"id": "generate_numerical", "name": "Generate Numerical", "category": "science"},
87
+ {"id": "plot_preset", "name": "Plot Preset", "category": "graph"},
88
+ {"id": "plot_function", "name": "Plot Function 2D", "category": "graph"},
89
+ {"id": "plot_surface", "name": "Plot Surface 3D", "category": "graph"},
90
+ {"id": "atom_2d", "name": "2D Atom Simulation", "category": "simulation"},
91
+ {"id": "atom_3d", "name": "3D Atom Simulation", "category": "simulation"},
92
+ {"id": "orbital", "name": "Quantum Orbital", "category": "simulation"},
93
+ {"id": "orbital_grid", "name": "Orbital Grid Chart", "category": "simulation"},
94
+ {"id": "bloch", "name": "Bloch Sphere Qubit", "category": "quantum"},
95
+ {"id": "bonding", "name": "Chemical Bonding Map", "category": "science"},
96
+ {"id": "web_search", "name": "Live Web Search", "category": "research"},
97
+ {"id": "deep_research", "name": "Deep Research Synthesis", "category": "research"},
98
+ {"id": "read_attachment", "name": "Read Attachment", "category": "file"},
99
+ {"id": "read_file", "name": "Read File", "category": "file"},
100
+ {"id": "write_file", "name": "Write File", "category": "file"},
101
+ {"id": "create_folder", "name": "Create Folder", "category": "file"},
102
+ {"id": "delete_file", "name": "Delete File", "category": "file"},
103
+ {"id": "rename_file", "name": "Rename File", "category": "file"},
104
+ {"id": "edit_file", "name": "Edit File", "category": "file"},
105
+ {"id": "search_workspace", "name": "Search Workspace", "category": "file"},
106
+ {"id": "list_directory", "name": "List Directory", "category": "file"},
107
+ {"id": "inspect_project", "name": "Inspect Project Structure", "category": "project"},
108
+ {"id": "archive_list", "name": "List Archive Contents", "category": "archive"},
109
+ {"id": "archive_extract", "name": "Extract Archive", "category": "archive"},
110
+ {"id": "archive_add_entries", "name": "Add to Archive", "category": "archive"},
111
+ {"id": "archive_delete_entries", "name": "Delete from Archive", "category": "archive"},
112
+ {"id": "archive_repack", "name": "Repack Archive", "category": "archive"},
113
+ {"id": "archive_validate", "name": "Validate Archive", "category": "archive"},
114
+ {"id": "run_terminal", "name": "Run Terminal Command", "category": "execution"},
115
+ {"id": "run_build", "name": "Run Project Build", "category": "execution"},
116
+ {"id": "run_tests", "name": "Run Test Suite", "category": "execution"},
117
+ {"id": "install_packages", "name": "Install Packages", "category": "execution"},
118
+ {"id": "device_action", "name": "Device Control Action", "category": "system"},
119
+ ]
120
+
121
+ # All 103 commands
122
+ cmds = [{"command": c[0], "description": c[1]} for c in commands_data.COMMANDS]
123
+
124
+ # Modes
125
+ modes_dict = {}
126
+ for k in ai_modes.MODE_ORDER:
127
+ m = ai_modes.MODE_META.get(k, {})
128
+ modes_dict[k] = {
129
+ "key": k,
130
+ "label": m.get("label", k.title()),
131
+ "icon": m.get("icon", "●"),
132
+ "accent": ai_modes._rgb_to_hex(m.get("accent", (122, 162, 247))),
133
+ "gradient": [
134
+ ai_modes._rgb_to_hex(m["gradient"][0]),
135
+ ai_modes._rgb_to_hex(m["gradient"][1])
136
+ ] if m.get("gradient") else ["#5d84f0", "#91beff"],
137
+ "purpose": m.get("purpose", ""),
138
+ }
139
+
140
+ # Extensions
141
+ ext_list = []
142
+ for ext in cct_ext.BUILTIN_EXTENSIONS.values():
143
+ eid = ext.get("id", "")
144
+ ext_list.append({
145
+ "id": eid,
146
+ "name": ext.get("name", eid),
147
+ "installed": cct_ext.is_installed(eid),
148
+ "enabled": cct_ext.is_enabled(eid),
149
+ "icon": ext.get("icon", "🧩"),
150
+ "description": ext.get("description", ""),
151
+ })
152
+
153
+ return {
154
+ "version_contract": {
155
+ "cat_version": identity.APP_VERSION,
156
+ "api_version": "1.0.0",
157
+ "fatty_cat_version": "1.0.0",
158
+ },
159
+ "active_mode": ai_modes.current_mode(),
160
+ "modes": modes_dict,
161
+ "mode_order": list(ai_modes.MODE_ORDER),
162
+ "active_provider": cfg.get("provider", "openai"),
163
+ "active_model": cfg.get("model", "gpt-4o"),
164
+ "has_api_key": bool(cfg.get("api_key")),
165
+ "providers_count": len(model_manager.load_providers()),
166
+ "commands": cmds,
167
+ "native_ui_commands": list(commands_data.NATIVE_UI_COMMANDS),
168
+ "tools": tools_list,
169
+ "viewers": list(v_reg._providers.keys()),
170
+ "extensions": ext_list,
171
+ "features": [
172
+ "chat", "agent", "build", "plan", "research", "debugger", "notebook",
173
+ "files", "code_editor", "terminal", "browser", "live_preview",
174
+ "models", "providers", "ollama", "permissions", "memory",
175
+ "customization", "extensions", "vision", "diagnostics", "fomoji_auth"
176
+ ],
177
+ }
178
+
179
+ # ─────────────────────────────────────────────────────────────
180
+ # 2. AI Execution & Streaming
181
+ # ─────────────────────────────────────────────────────────────
182
+
183
+ async def stream_chat(
184
+ self,
185
+ message: str,
186
+ mode: str = "notebook",
187
+ history: Optional[List[Dict[str, str]]] = None,
188
+ project_path: Optional[str] = None
189
+ ) -> AsyncGenerator[Dict[str, Any], None]:
190
+ """Stream chat tokens directly from aicore.stream_ai()."""
191
+ self.request_count += 1
192
+ if project_path:
193
+ ws_module.set_active_project(project_path)
194
+
195
+ cfg = aicore.load_config()
196
+ system_prompt = ai_modes.system_prompt_for(mode)
197
+
198
+ loop = asyncio.get_event_loop()
199
+ queue: asyncio.Queue[Tuple[str, Any]] = asyncio.Queue()
200
+
201
+ def _worker():
202
+ try:
203
+ for chunk in aicore.stream_ai(
204
+ prompt=message,
205
+ system_prompt=system_prompt,
206
+ history=history,
207
+ config=cfg,
208
+ on_failover=lambda msg: loop.call_soon_threadsafe(
209
+ queue.put_nowait, ("failover", msg)
210
+ ),
211
+ size_class="normal"
212
+ ):
213
+ loop.call_soon_threadsafe(queue.put_nowait, ("token", chunk))
214
+ loop.call_soon_threadsafe(queue.put_nowait, ("done", None))
215
+ except Exception as e:
216
+ self.error_count += 1
217
+ loop.call_soon_threadsafe(queue.put_nowait, ("error", str(e)))
218
+
219
+ thread = asyncio.to_thread(_worker)
220
+ task = asyncio.create_task(thread)
221
+
222
+ try:
223
+ while True:
224
+ kind, data = await queue.get()
225
+ if kind == "token":
226
+ yield {"type": "token", "text": data}
227
+ elif kind == "failover":
228
+ yield {"type": "thinking", "text": f"Failover: {data}"}
229
+ elif kind == "error":
230
+ yield {"type": "error", "error": data}
231
+ break
232
+ elif kind == "done":
233
+ yield {"type": "done"}
234
+ break
235
+ finally:
236
+ await task
237
+
238
+ def run_agent_turn(
239
+ self,
240
+ message: str,
241
+ mode: str = "agent",
242
+ max_steps: int = 15,
243
+ project_path: Optional[str] = None
244
+ ) -> Dict[str, Any]:
245
+ """Run a full step-by-step agent loop using agent.run_agent()."""
246
+ self.request_count += 1
247
+ if project_path:
248
+ ws_module.set_active_project(project_path)
249
+
250
+ from calc_terminal.agent import run_agent as _cct_run_agent
251
+
252
+ try:
253
+ agent_mode = "agent" if mode in ("agent", "build", "plan") else "ai"
254
+ text, steps, meta = _cct_run_agent(
255
+ message,
256
+ max_steps=max_steps,
257
+ verbose=False,
258
+ mode=agent_mode
259
+ )
260
+
261
+ formatted_steps = []
262
+ for s in steps:
263
+ if len(s) >= 3:
264
+ formatted_steps.append({
265
+ "tool": s[0],
266
+ "args": s[1] if isinstance(s[1], dict) else {},
267
+ "result": str(s[2])[:800],
268
+ })
269
+
270
+ return {
271
+ "success": True,
272
+ "response": text or "",
273
+ "steps": formatted_steps,
274
+ "meta": meta or {},
275
+ }
276
+ except Exception as e:
277
+ self.error_count += 1
278
+ return {
279
+ "success": False,
280
+ "response": f"Agent execution error: {e}",
281
+ "steps": [],
282
+ "meta": {},
283
+ }
284
+
285
+ # ─────────────────────────────────────────────────────────────
286
+ # 3. AI Modes Management
287
+ # ─────────────────────────────────────────────────────────────
288
+
289
+ def get_modes(self) -> Dict[str, Any]:
290
+ """Get snapshot of current modes and active mode."""
291
+ return {
292
+ "current": ai_modes.current_mode(),
293
+ "order": list(ai_modes.MODE_ORDER),
294
+ "modes": {
295
+ k: {
296
+ "key": k,
297
+ "label": v.get("label", k.title()),
298
+ "icon": v.get("icon", "●"),
299
+ "accent": ai_modes._rgb_to_hex(v.get("accent", (122, 162, 247))),
300
+ "gradient": [
301
+ ai_modes._rgb_to_hex(v["gradient"][0]),
302
+ ai_modes._rgb_to_hex(v["gradient"][1])
303
+ ] if v.get("gradient") else ["#5d84f0", "#91beff"],
304
+ "purpose": v.get("purpose", ""),
305
+ "system_prompt": v.get("system_prompt", ""),
306
+ }
307
+ for k, v in ai_modes.MODE_META.items()
308
+ }
309
+ }
310
+
311
+ def set_mode(self, mode_key: str) -> str:
312
+ """Switch active AI mode."""
313
+ res = ai_modes.set_mode(mode_key)
314
+ eventbus.bus.publish(eventbus.MODE_SWITCHED, mode=res)
315
+ return res
316
+
317
+ def create_custom_mode(
318
+ self,
319
+ key: str,
320
+ label: str,
321
+ icon: str,
322
+ accent_hex: str,
323
+ purpose: str = "",
324
+ system_prompt: str = ""
325
+ ) -> Tuple[bool, str]:
326
+ """Create user-defined mode via ai_modes.create_mode()."""
327
+ return ai_modes.create_mode(key, label, icon, accent_hex, purpose, system_prompt)
328
+
329
+ def delete_custom_mode(self, key: str) -> Tuple[bool, str]:
330
+ """Delete custom mode."""
331
+ return ai_modes.delete_mode(key)
332
+
333
+ # ─────────────────────────────────────────────────────────────
334
+ # 4. Providers & Models Management
335
+ # ─────────────────────────────────────────────────────────────
336
+
337
+ def list_providers(self) -> List[Dict[str, Any]]:
338
+ """List all providers from providers.json."""
339
+ all_provs = model_manager.load_providers()
340
+ cfg = aicore.load_config()
341
+ active_id = cfg.get("provider", "openai")
342
+
343
+ out = []
344
+ for p in all_provs:
345
+ item = dict(p)
346
+ item["is_active"] = (p.get("id") == active_id)
347
+ out.append(item)
348
+ return out
349
+
350
+ def get_models_for_provider(self, provider_id: str, force_refresh: bool = False) -> List[Dict[str, Any]]:
351
+ """Get models for provider using CAT model manager with caching."""
352
+ cfg = aicore.load_config()
353
+ models = model_manager.get_models(provider_id, config=cfg, force_refresh=force_refresh)
354
+ active_model = cfg.get("model", "")
355
+
356
+ out = []
357
+ for m_id in models:
358
+ meta = model_manager.get_model_metadata(m_id) or {}
359
+ out.append({
360
+ "id": m_id,
361
+ "name": meta.get("name", m_id),
362
+ "is_active": (m_id == active_model),
363
+ "family": meta.get("family", ""),
364
+ "description": meta.get("desc", meta.get("description", "")),
365
+ "context_window": meta.get("context", meta.get("context_window", 4096)),
366
+ "capabilities": meta.get("caps", meta.get("capabilities", [])),
367
+ "free": meta.get("free", False),
368
+ })
369
+ return out
370
+
371
+ def switch_model(self, provider_id: str, model_id: str, api_key: Optional[str] = None) -> Dict[str, Any]:
372
+ """Switch active provider and model in CAT config."""
373
+ cfg = aicore.load_config()
374
+ cfg["provider"] = provider_id
375
+ cfg["model"] = model_id
376
+ if api_key is not None:
377
+ cfg["api_key"] = api_key
378
+ aicore.save_config(cfg)
379
+ provider_manager.save_config(cfg)
380
+ return {"success": True, "provider": provider_id, "model": model_id}
381
+
382
+ # ─────────────────────────────────────────────────────────────
383
+ # 5. Ollama Support
384
+ # ─────────────────────────────────────────────────────────────
385
+
386
+ def get_ollama_status(self) -> Dict[str, Any]:
387
+ """Check Ollama installation and connection status."""
388
+ base_url = ollama_download.ollama_base_url()
389
+ installed = ollama_download.is_ollama_installed()
390
+ running = ollama_download.is_ollama_running(base_url=base_url)
391
+ installed_models = []
392
+ if running:
393
+ try:
394
+ installed_models = provider_manager.refresh_ollama_models(base_url=base_url)
395
+ except Exception:
396
+ pass
397
+ return {
398
+ "installed": installed,
399
+ "running": running,
400
+ "base_url": base_url,
401
+ "installed_models": installed_models,
402
+ }
403
+
404
+ def get_ollama_catalog(self, query: str = "") -> List[Dict[str, Any]]:
405
+ """Return curated Ollama models from ollama_catalog.py."""
406
+ if query:
407
+ return ollama_catalog.search_models(query)
408
+ return ollama_catalog.all_models()
409
+
410
+ def ensure_ollama_started(self) -> Tuple[bool, str]:
411
+ """Start local Ollama serve process if installed and not running."""
412
+ return ollama_download.ensure_ollama_running(auto_start=True)
413
+
414
+ # ─────────────────────────────────────────────────────────────
415
+ # 6. Command Dispatcher
416
+ # ─────────────────────────────────────────────────────────────
417
+
418
+ def get_commands(self) -> List[Dict[str, str]]:
419
+ """Return list of all 103 slash commands."""
420
+ return [{"command": c[0], "description": c[1]} for c in commands_data.COMMANDS]
421
+
422
+ def dispatch_command(self, cmd_line: str) -> Dict[str, Any]:
423
+ """Dispatch a slash command to CAT handlers."""
424
+ cmd = cmd_line.strip()
425
+ parts = cmd.split(maxsplit=1)
426
+ base = parts[0].lower()
427
+ arg = parts[1].strip() if len(parts) > 1 else ""
428
+
429
+ if base in ("/about", "about"):
430
+ return {"command": base, "output": f"FATTY CAT v{identity.APP_VERSION} (Source: {identity.APP_NAME})"}
431
+
432
+ if base in ("/browser", "/browse", "/cat", "browser", "browse", "cat"):
433
+ try:
434
+ from calc_terminal.host.launcher import launch_cat_host, can_launch_host
435
+ if can_launch_host():
436
+ target = arg or "about:home"
437
+ launch_cat_host(start_browser_url=target, start_mode="browser", block=False)
438
+ return {"command": base, "output": f"Launched CAT Browser: {target}"}
439
+ return {"command": base, "output": "CAT Browser requires PySide6 on host."}
440
+ except Exception as e:
441
+ return {"command": base, "output": f"Error launching CAT Browser: {e}"}
442
+
443
+ if base in ("/help", "help"):
444
+ return {
445
+ "command": base,
446
+ "output": "Available commands:\n" + "\n".join(f" {c[0]:<20} {c[1]}" for c in commands_data.COMMANDS[:30]) + "\n ...and 70+ more. Use Command Palette (Ctrl+K) to browse all."
447
+ }
448
+
449
+ if base in ("/tokens", "tokens"):
450
+ usage = aicore.get_session_usage()
451
+ return {"command": base, "output": f"Tokens: {usage.get('total_tokens', 0)} used | Requests: {usage.get('requests', 0)}"}
452
+
453
+ if base in ("/calculator", "/calc", "calc"):
454
+ if not arg:
455
+ return {"command": base, "output": "Usage: /calc <expression> e.g. /calc 2.5 * log10(100)"}
456
+ from calc_terminal.agent import _tool_calculate
457
+ res = _tool_calculate({"expression": arg})
458
+ return {"command": base, "output": res}
459
+
460
+ if base in ("/formulas", "formulas"):
461
+ from calc_terminal.agent import _tool_list_formulas
462
+ return {"command": base, "output": _tool_list_formulas({})}
463
+
464
+ if base in ("/solve", "solve"):
465
+ if not arg:
466
+ return {"command": base, "output": "Usage: /solve <formula_key> values={...} or /solve formula=... values={...} solve_for=..."}
467
+ from calc_terminal.solver import FORMULA_LIBRARY
468
+ matches = [k for k in FORMULA_LIBRARY if arg.lower() in k.lower()]
469
+ if matches:
470
+ info = FORMULA_LIBRARY[matches[0]]
471
+ return {"command": base, "output": f"Formula: {info[0]}\nEquation: {info[2]}\nCategory: {info[1]}"}
472
+ return {"command": base, "output": f"No formula found matching '{arg}'."}
473
+
474
+ if base in ("/workspace", "workspace"):
475
+ root = ws_module.active_project() or ws_module.root_dir()
476
+ return {"command": base, "output": f"Active workspace: {root}"}
477
+
478
+ if base in ("/mode", "mode"):
479
+ if arg and arg in ai_modes.MODE_META:
480
+ new_m = self.set_mode(arg)
481
+ return {"command": base, "output": f"Switched AI mode to {new_m.upper()}."}
482
+ return {"command": base, "output": f"Current mode: {ai_modes.current_mode().upper()}\nAvailable: {', '.join(ai_modes.MODE_ORDER)}"}
483
+
484
+ if base in ("/memory", "/m", "memory"):
485
+ from calc_terminal import memory as _cct_mem
486
+ if arg.lower() == "clear":
487
+ _cct_mem.clear()
488
+ return {"command": base, "output": "Persistent memory cleared."}
489
+ facts = _cct_mem.get_facts() if hasattr(_cct_mem, "get_facts") else []
490
+ topics = _cct_mem.get_topics() if hasattr(_cct_mem, "get_topics") else []
491
+ out = f"Persistent Facts ({len(facts)}):\n"
492
+ for f in facts:
493
+ txt = f if isinstance(f, str) else f.get("fact", str(f))
494
+ out += f" • {txt}\n"
495
+ if topics:
496
+ out += f"\nTop Topics: {', '.join(topics[:5])}"
497
+ return {"command": base, "output": out or "No facts remembered yet."}
498
+
499
+ if base in ("/extensions", "extensions"):
500
+ exts = self.list_extensions()
501
+ out = "CAT Extensions:\n"
502
+ for e in exts:
503
+ status = "✓ Enabled" if e.get("enabled") else "○ Disabled"
504
+ out += f" {e.get('name', e.get('id')):<30} {status}\n"
505
+ return {"command": base, "output": out}
506
+
507
+ if base in ("/atomsim", "/atom", "atomsim"):
508
+ from calc_terminal.agent import _tool_simulate_atom_2d
509
+ return {"command": base, "output": _tool_simulate_atom_2d({"element": arg or "H"})}
510
+
511
+ if base in ("/orbitals", "orbitals"):
512
+ from calc_terminal.agent import _tool_simulate_orbital
513
+ return {"command": base, "output": _tool_simulate_orbital({"n": 2, "l": 1, "m": 0})}
514
+
515
+ if base in ("/graph", "graph"):
516
+ from calc_terminal.agent import _tool_plot_preset
517
+ preset_name = arg or "first_order_kinetics"
518
+ return {"command": base, "output": _tool_plot_preset({"preset": preset_name})}
519
+
520
+ if base in ("/model", "model"):
521
+ cfg = aicore.load_config()
522
+ if arg:
523
+ self.switch_model(cfg.get("provider", "ollama"), arg)
524
+ return {"command": base, "output": f"Switched model to {arg}."}
525
+ return {"command": base, "output": f"Active provider: {cfg.get('provider', 'ollama').upper()} · Model: {cfg.get('model', 'deepseek-r1')}"}
526
+
527
+ if base in ("/ai-verify", "/verify", "verify"):
528
+ cfg = aicore.load_config()
529
+ prov = cfg.get("provider", "ollama")
530
+ mod = cfg.get("model", "deepseek-r1")
531
+ try:
532
+ ok = aicore.verify_connection(prov, config=cfg)
533
+ status = "✓ Connection Successful" if ok else "⚠ Connection Failed"
534
+ except Exception as e:
535
+ status = f"⚠ Verification Error: {e}"
536
+ return {"command": base, "output": f"AI Connectivity Test for {prov.upper()} ({mod}): {status}"}
537
+
538
+ return {
539
+ "command": base,
540
+ "output": f"Command {base} dispatched. Type /help or press Ctrl+K for command details."
541
+ }
542
+
543
+ # ─────────────────────────────────────────────────────────────
544
+ # 7. Permissions
545
+ # ─────────────────────────────────────────────────────────────
546
+
547
+ def get_permissions(self) -> Dict[str, Any]:
548
+ """Get current permission manager snapshot."""
549
+ return {
550
+ "mode": cct_perms.perm_manager.mode,
551
+ "mode_label": cct_perms.perm_manager.mode_label(),
552
+ "permissions": [
553
+ {"key": k, "label": l, "enabled": v}
554
+ for k, l, v in cct_perms.perm_manager.snapshot()
555
+ ],
556
+ "log": [
557
+ {"time": t, "key": k, "action": a, "decision": d}
558
+ for t, k, a, d in cct_perms.perm_manager.log[-30:]
559
+ ],
560
+ }
561
+
562
+ def set_permission_mode(self, mode: str) -> Dict[str, Any]:
563
+ cct_perms.perm_manager.set_mode(mode)
564
+ return {"mode": cct_perms.perm_manager.mode, "label": cct_perms.perm_manager.mode_label()}
565
+
566
+ def toggle_permission(self, key: str, enabled: bool) -> bool:
567
+ return cct_perms.perm_manager.set(key, enabled)
568
+
569
+ def decide_permission(self, key: str, decision: str, action_text: str = "") -> bool:
570
+ return cct_perms.perm_manager.decide(key, decision, action_text)
571
+
572
+ # ─────────────────────────────────────────────────────────────
573
+ # 8. Memory
574
+ # ─────────────────────────────────────────────────────────────
575
+
576
+ def get_memory_facts(self) -> List[str]:
577
+ data = memory.load()
578
+ return data.get("facts", [])
579
+
580
+ def add_memory_fact(self, fact: str):
581
+ memory.add_fact(fact)
582
+
583
+ def remove_memory_fact(self, index: int):
584
+ data = memory.load()
585
+ facts = data.get("facts", [])
586
+ if 0 <= index < len(facts):
587
+ facts.pop(index)
588
+ data["facts"] = facts
589
+ memory.save(data)
590
+
591
+ def get_memory_activity(self) -> List[Dict[str, Any]]:
592
+ data = memory.load()
593
+ return data.get("activity", [])
594
+
595
+ def get_memory_topics(self) -> Dict[str, int]:
596
+ data = memory.load()
597
+ return data.get("topics", {})
598
+
599
+ # ─────────────────────────────────────────────────────────────
600
+ # 9. Customization & UI Layout
601
+ # ─────────────────────────────────────────────────────────────
602
+
603
+ def get_customization(self) -> Dict[str, Any]:
604
+ """Get entire layout and style customization state."""
605
+ return customization.get_customization().to_dict()
606
+
607
+ def update_layout_component(self, comp_id: str, updates: Dict[str, Any]) -> bool:
608
+ """Update layout positioning or size for a UI panel."""
609
+ cust = customization.get_customization()
610
+ return cust.layout.update_component(comp_id, updates)
611
+
612
+ def reset_customization(self) -> bool:
613
+ """Reset layout and styling to factory defaults."""
614
+ return customization.get_customization().reset_to_defaults()
615
+
616
+ # ─────────────────────────────────────────────────────────────
617
+ # 10. Extensions
618
+ # ─────────────────────────────────────────────────────────────
619
+
620
+ def list_extensions(self) -> List[Dict[str, Any]]:
621
+ return cct_ext.list_extensions()
622
+
623
+ def toggle_extension(self, ext_id: str, enable: bool) -> bool:
624
+ if enable:
625
+ return cct_ext.enable(ext_id)
626
+ return cct_ext.disable(ext_id)
627
+
628
+ def create_extension(self, **kwargs) -> Tuple[bool, Dict[str, Any]]:
629
+ return cct_ext.create_extension(**kwargs)
630
+
631
+ # ─────────────────────────────────────────────────────────────
632
+ # 11. Science & Notebook
633
+ # ─────────────────────────────────────────────────────────────
634
+
635
+ def solve_formula(self, key: str, values: Dict[str, float]) -> Dict[str, Any]:
636
+ from calc_terminal import solver
637
+ try:
638
+ solve_for, result, name, cat, eq = solver.solve_library_formula(key, values)
639
+ return {
640
+ "success": True,
641
+ "name": name,
642
+ "category": cat,
643
+ "equation": eq,
644
+ "solve_for": solve_for,
645
+ "result": result,
646
+ }
647
+ except Exception as e:
648
+ return {"success": False, "error": str(e)}
649
+
650
+ def get_derivation(self, keyword: str) -> Dict[str, Any]:
651
+ from calc_terminal import derivations
652
+ text = derivations.get_derivation(keyword)
653
+ if text:
654
+ return {"success": True, "derivation": text}
655
+ return {"success": False, "error": f"No derivation found for '{keyword}'"}
656
+
657
+ def get_atom_sim(self, element: str) -> Dict[str, Any]:
658
+ from calc_terminal import atomsim
659
+ z = atomsim.resolve_element(element)
660
+ if z is None:
661
+ return {"success": False, "error": f"Unknown element '{element}'"}
662
+ sym, name, shells, mass = atomsim.ELEMENTS[z]
663
+ return {
664
+ "success": True,
665
+ "z": z,
666
+ "symbol": sym,
667
+ "name": name,
668
+ "shells": shells,
669
+ "mass": mass,
670
+ }
671
+
672
+ # ─────────────────────────────────────────────────────────────
673
+ # 12. Diagnostics
674
+ # ─────────────────────────────────────────────────────────────
675
+
676
+ def get_diagnostics(self) -> Dict[str, Any]:
677
+ """System diagnostics, version contract, runtime statistics."""
678
+ cfg = aicore.load_config()
679
+ uptime_sec = int(time.time() - self.start_time)
680
+
681
+ # Hardware metrics
682
+ specs = {}
683
+ try:
684
+ from calc_terminal import pc_specs
685
+ specs = pc_specs.get_specs()
686
+ except Exception:
687
+ pass
688
+
689
+ return {
690
+ "contract": {
691
+ "cat_version": identity.APP_VERSION,
692
+ "api_version": "1.0.0",
693
+ "fatty_cat_version": "1.0.0",
694
+ },
695
+ "runtime": {
696
+ "uptime_seconds": uptime_sec,
697
+ "request_count": self.request_count,
698
+ "error_count": self.error_count,
699
+ "active_workspace": ws_module.active_project() or ws_module.root_dir(),
700
+ "active_provider": cfg.get("provider", "openai"),
701
+ "active_model": cfg.get("model", "gpt-4o"),
702
+ "active_mode": ai_modes.current_mode(),
703
+ },
704
+ "hardware": specs,
705
+ "recent_events": self.recent_events[-25:],
706
+ }
707
+
708
+
709
+ # Singleton runtime adapter instance
710
+ runtime = CatRuntime()