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,491 @@
1
+ """
2
+ CAT Platform Abstraction Layer
3
+
4
+ Provides a unified interface for platform-specific operations:
5
+ - Desktop (native Python)
6
+ - Web/PWA (browser via WebAssembly or server-bridged)
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import os
12
+ import sys
13
+ from abc import ABC, abstractmethod
14
+ from dataclasses import dataclass
15
+ from pathlib import Path
16
+ from typing import Any, BinaryIO, List, Optional, Union
17
+ from enum import Enum
18
+
19
+
20
+ class PlatformType(Enum):
21
+ DESKTOP = "desktop"
22
+ WEB = "web"
23
+ PWA = "pwa"
24
+
25
+
26
+ @dataclass
27
+ class PlatformInfo:
28
+ type: PlatformType
29
+ name: str
30
+ version: str
31
+ is_mobile: bool
32
+ supports_file_system_access: bool
33
+ supports_notifications: bool
34
+ supports_background_sync: bool
35
+ user_agent: str = ""
36
+
37
+
38
+ class FileHandle(ABC):
39
+ """Abstract file handle for platform-agnostic file operations."""
40
+
41
+ @abstractmethod
42
+ async def read(self, size: int = -1) -> bytes:
43
+ pass
44
+
45
+ @abstractmethod
46
+ async def write(self, data: Union[bytes, str]) -> int:
47
+ pass
48
+
49
+ @abstractmethod
50
+ async def close(self) -> None:
51
+ pass
52
+
53
+ @abstractmethod
54
+ async def seek(self, offset: int, whence: int = 0) -> int:
55
+ pass
56
+
57
+ @abstractmethod
58
+ def tell(self) -> int:
59
+ pass
60
+
61
+
62
+ class DirectoryHandle(ABC):
63
+ """Abstract directory handle for platform-agnostic directory operations."""
64
+
65
+ @abstractmethod
66
+ async def get_file_handle(self, name: str, create: bool = False) -> FileHandle:
67
+ pass
68
+
69
+ @abstractmethod
70
+ async def get_directory_handle(self, name: str, create: bool = False) -> DirectoryHandle:
71
+ pass
72
+
73
+ @abstractmethod
74
+ async def remove_entry(self, name: str, recursive: bool = False) -> None:
75
+ pass
76
+
77
+ @abstractmethod
78
+ async def list_entries(self) -> List[str]:
79
+ pass
80
+
81
+
82
+ class PlatformBase(ABC):
83
+ """Abstract base class for platform implementations."""
84
+
85
+ @property
86
+ @abstractmethod
87
+ def info(self) -> PlatformInfo:
88
+ pass
89
+
90
+ # --- Filesystem ---
91
+
92
+ @abstractmethod
93
+ async def get_root_directory(self) -> DirectoryHandle:
94
+ """Get the root directory handle (workspace root for web, home for desktop)."""
95
+ pass
96
+
97
+ @abstractmethod
98
+ async def pick_directory(self, title: str = "Select folder") -> Optional[DirectoryHandle]:
99
+ """Show directory picker dialog."""
100
+ pass
101
+
102
+ @abstractmethod
103
+ async def pick_file(self, title: str = "Select file", accept: Optional[dict] = None) -> Optional[FileHandle]:
104
+ """Show file picker dialog."""
105
+ pass
106
+
107
+ @abstractmethod
108
+ async def resolve_path(self, path: str) -> str:
109
+ """Resolve a path to absolute form."""
110
+ pass
111
+
112
+ @abstractmethod
113
+ async def path_exists(self, path: str) -> bool:
114
+ """Check if path exists."""
115
+ pass
116
+
117
+ @abstractmethod
118
+ async def is_directory(self, path: str) -> bool:
119
+ """Check if path is a directory."""
120
+ pass
121
+
122
+ @abstractmethod
123
+ async def is_file(self, path: str) -> bool:
124
+ """Check if path is a file."""
125
+ pass
126
+
127
+ @abstractmethod
128
+ async def list_directory(self, path: str) -> List[str]:
129
+ """List directory contents."""
130
+ pass
131
+
132
+ @abstractmethod
133
+ async def read_file(self, path: str, encoding: Optional[str] = None) -> Union[str, bytes]:
134
+ """Read file contents."""
135
+ pass
136
+
137
+ @abstractmethod
138
+ async def write_file(self, path: str, data: Union[str, bytes], encoding: Optional[str] = None) -> int:
139
+ """Write file contents."""
140
+ pass
141
+
142
+ @abstractmethod
143
+ async def delete_file(self, path: str) -> None:
144
+ """Delete a file."""
145
+ pass
146
+
147
+ @abstractmethod
148
+ async def delete_directory(self, path: str, recursive: bool = False) -> None:
149
+ """Delete a directory."""
150
+ pass
151
+
152
+ @abstractmethod
153
+ async def create_directory(self, path: str, parents: bool = True) -> None:
154
+ """Create a directory."""
155
+ pass
156
+
157
+ @abstractmethod
158
+ async def copy_file(self, src: str, dst: str) -> None:
159
+ """Copy a file."""
160
+ pass
161
+
162
+ @abstractmethod
163
+ async def move_file(self, src: str, dst: str) -> None:
164
+ """Move/rename a file."""
165
+ pass
166
+
167
+ @abstractmethod
168
+ async def get_file_size(self, path: str) -> int:
169
+ """Get file size in bytes."""
170
+ pass
171
+
172
+ @abstractmethod
173
+ async def get_file_mtime(self, path: str) -> float:
174
+ """Get file modification time."""
175
+ pass
176
+
177
+ # --- Process Execution ---
178
+
179
+ @abstractmethod
180
+ async def run_command(
181
+ self,
182
+ cmd: Union[str, List[str]],
183
+ cwd: Optional[str] = None,
184
+ env: Optional[dict] = None,
185
+ capture_output: bool = True,
186
+ timeout: Optional[float] = None,
187
+ ) -> "CommandResult":
188
+ """Run a command and return result."""
189
+ pass
190
+
191
+ @abstractmethod
192
+ async def start_process(
193
+ self,
194
+ cmd: Union[str, List[str]],
195
+ cwd: Optional[str] = None,
196
+ env: Optional[dict] = None,
197
+ ) -> "ProcessHandle":
198
+ """Start a background process."""
199
+ pass
200
+
201
+ # --- Clipboard ---
202
+
203
+ @abstractmethod
204
+ async def read_clipboard(self) -> str:
205
+ """Read text from clipboard."""
206
+ pass
207
+
208
+ @abstractmethod
209
+ async def write_clipboard(self, text: str) -> None:
210
+ """Write text to clipboard."""
211
+ pass
212
+
213
+ # --- Notifications ---
214
+
215
+ @abstractmethod
216
+ async def show_notification(
217
+ self,
218
+ title: str,
219
+ body: str,
220
+ icon: Optional[str] = None,
221
+ ) -> None:
222
+ """Show a system notification."""
223
+ pass
224
+
225
+ @abstractmethod
226
+ async def request_notification_permission(self) -> bool:
227
+ """Request permission for notifications."""
228
+ pass
229
+
230
+ # --- Browser/URL ---
231
+
232
+ @abstractmethod
233
+ async def open_url(self, url: str) -> bool:
234
+ """Open a URL in the default browser."""
235
+ pass
236
+
237
+ # --- Keyboard/Input ---
238
+
239
+ @abstractmethod
240
+ async def show_keyboard(self) -> None:
241
+ """Show virtual keyboard (mobile)."""
242
+ pass
243
+
244
+ @abstractmethod
245
+ async def hide_keyboard(self) -> None:
246
+ """Hide virtual keyboard (mobile)."""
247
+ pass
248
+
249
+ # --- Storage ---
250
+
251
+ @abstractmethod
252
+ async def get_storage_estimate(self) -> dict:
253
+ """Get storage usage estimate."""
254
+ pass
255
+
256
+ @abstractmethod
257
+ async def request_persistent_storage(self) -> bool:
258
+ """Request persistent storage (web)."""
259
+ pass
260
+
261
+ # --- App Lifecycle ---
262
+
263
+ @abstractmethod
264
+ async def minimize_app(self) -> None:
265
+ """Minimize the app."""
266
+ pass
267
+
268
+ @abstractmethod
269
+ async def close_app(self) -> None:
270
+ """Close the app."""
271
+ pass
272
+
273
+ # --- Platform-specific ---
274
+
275
+ @abstractmethod
276
+ def get_env(self, key: str, default: Optional[str] = None) -> Optional[str]:
277
+ """Get environment variable."""
278
+ pass
279
+
280
+ @abstractmethod
281
+ def set_env(self, key: str, value: str) -> None:
282
+ """Set environment variable."""
283
+ pass
284
+
285
+
286
+ @dataclass
287
+ class CommandResult:
288
+ returncode: int
289
+ stdout: str
290
+ stderr: str
291
+ success: bool
292
+
293
+
294
+ class ProcessHandle(ABC):
295
+ """Handle for a running background process."""
296
+
297
+ @abstractmethod
298
+ async def wait(self, timeout: Optional[float] = None) -> CommandResult:
299
+ pass
300
+
301
+ @abstractmethod
302
+ async def terminate(self) -> None:
303
+ pass
304
+
305
+ @abstractmethod
306
+ async def kill(self) -> None:
307
+ pass
308
+
309
+ @property
310
+ @abstractmethod
311
+ def pid(self) -> Optional[int]:
312
+ pass
313
+
314
+ @property
315
+ @abstractmethod
316
+ def returncode(self) -> Optional[int]:
317
+ pass
318
+
319
+
320
+ # Global platform instance
321
+ _platform: Optional[PlatformBase] = None
322
+
323
+
324
+ def get_platform() -> PlatformBase:
325
+ """Get the current platform implementation."""
326
+ global _platform
327
+ if _platform is None:
328
+ _platform = _detect_platform()
329
+ return _platform
330
+
331
+
332
+ def set_platform(platform: PlatformBase) -> None:
333
+ """Set the platform implementation (for testing/override)."""
334
+ global _platform
335
+ _platform = platform
336
+
337
+
338
+ def _detect_platform() -> PlatformBase:
339
+ """Auto-detect the platform."""
340
+ # Check if running in browser (Pyodide/WASM)
341
+ if "pyodide" in sys.modules or hasattr(sys, "implementation") and sys.implementation.name == "pyodide":
342
+ from .web import WebPlatform
343
+ return WebPlatform()
344
+
345
+ # Check if running in a PWA context (served via HTTP)
346
+ if os.environ.get("CAT_PLATFORM") == "web":
347
+ from .web import WebPlatform
348
+ return WebPlatform()
349
+
350
+ # Default to desktop
351
+ from .desktop import DesktopPlatform
352
+ return DesktopPlatform()
353
+
354
+
355
+ # Convenience functions that delegate to the platform
356
+ async def get_root_directory() -> DirectoryHandle:
357
+ return await get_platform().get_root_directory()
358
+
359
+
360
+ async def pick_directory(title: str = "Select folder") -> Optional[DirectoryHandle]:
361
+ return await get_platform().pick_directory(title)
362
+
363
+
364
+ async def pick_file(title: str = "Select file", accept: Optional[dict] = None) -> Optional[FileHandle]:
365
+ return await get_platform().pick_file(title, accept)
366
+
367
+
368
+ async def resolve_path(path: str) -> str:
369
+ return await get_platform().resolve_path(path)
370
+
371
+
372
+ async def path_exists(path: str) -> bool:
373
+ return await get_platform().path_exists(path)
374
+
375
+
376
+ async def is_directory(path: str) -> bool:
377
+ return await get_platform().is_directory(path)
378
+
379
+
380
+ async def is_file(path: str) -> bool:
381
+ return await get_platform().is_file(path)
382
+
383
+
384
+ async def list_directory(path: str) -> List[str]:
385
+ return await get_platform().list_directory(path)
386
+
387
+
388
+ async def read_file(path: str, encoding: Optional[str] = None) -> Union[str, bytes]:
389
+ return await get_platform().read_file(path, encoding)
390
+
391
+
392
+ async def write_file(path: str, data: Union[str, bytes], encoding: Optional[str] = None) -> int:
393
+ return await get_platform().write_file(path, data, encoding)
394
+
395
+
396
+ async def delete_file(path: str) -> None:
397
+ return await get_platform().delete_file(path)
398
+
399
+
400
+ async def delete_directory(path: str, recursive: bool = False) -> None:
401
+ return await get_platform().delete_directory(path, recursive)
402
+
403
+
404
+ async def create_directory(path: str, parents: bool = True) -> None:
405
+ return await get_platform().create_directory(path, parents)
406
+
407
+
408
+ async def copy_file(src: str, dst: str) -> None:
409
+ return await get_platform().copy_file(src, dst)
410
+
411
+
412
+ async def move_file(src: str, dst: str) -> None:
413
+ return await get_platform().move_file(src, dst)
414
+
415
+
416
+ async def get_file_size(path: str) -> int:
417
+ return await get_platform().get_file_size(path)
418
+
419
+
420
+ async def get_file_mtime(path: str) -> float:
421
+ return await get_platform().get_file_mtime(path)
422
+
423
+
424
+ async def run_command(
425
+ cmd: Union[str, List[str]],
426
+ cwd: Optional[str] = None,
427
+ env: Optional[dict] = None,
428
+ capture_output: bool = True,
429
+ timeout: Optional[float] = None,
430
+ ) -> CommandResult:
431
+ return await get_platform().run_command(cmd, cwd, env, capture_output, timeout)
432
+
433
+
434
+ async def start_process(
435
+ cmd: Union[str, List[str]],
436
+ cwd: Optional[str] = None,
437
+ env: Optional[dict] = None,
438
+ ) -> ProcessHandle:
439
+ return await get_platform().start_process(cmd, cwd, env)
440
+
441
+
442
+ async def read_clipboard() -> str:
443
+ return await get_platform().read_clipboard()
444
+
445
+
446
+ async def write_clipboard(text: str) -> None:
447
+ return await get_platform().write_clipboard(text)
448
+
449
+
450
+ async def show_notification(title: str, body: str, icon: Optional[str] = None) -> None:
451
+ return await get_platform().show_notification(title, body, icon)
452
+
453
+
454
+ async def request_notification_permission() -> bool:
455
+ return await get_platform().request_notification_permission()
456
+
457
+
458
+ async def open_url(url: str) -> bool:
459
+ return await get_platform().open_url(url)
460
+
461
+
462
+ async def show_keyboard() -> None:
463
+ return await get_platform().show_keyboard()
464
+
465
+
466
+ async def hide_keyboard() -> None:
467
+ return await get_platform().hide_keyboard()
468
+
469
+
470
+ async def get_storage_estimate() -> dict:
471
+ return await get_platform().get_storage_estimate()
472
+
473
+
474
+ async def request_persistent_storage() -> bool:
475
+ return await get_platform().request_persistent_storage()
476
+
477
+
478
+ async def minimize_app() -> None:
479
+ return await get_platform().minimize_app()
480
+
481
+
482
+ async def close_app() -> None:
483
+ return await get_platform().close_app()
484
+
485
+
486
+ def get_env(key: str, default: Optional[str] = None) -> Optional[str]:
487
+ return get_platform().get_env(key, default)
488
+
489
+
490
+ def set_env(key: str, value: str) -> None:
491
+ return get_platform().set_env(key, value)