skydeckai-code 0.1.23__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.
@@ -0,0 +1,455 @@
1
+ import json
2
+ import platform
3
+ import subprocess
4
+ from typing import Any, Dict, List
5
+
6
+ from mcp import types
7
+
8
+ # Import platform-specific libraries if available
9
+ try:
10
+ import Quartz
11
+ QUARTZ_AVAILABLE = True
12
+ except ImportError:
13
+ QUARTZ_AVAILABLE = False
14
+
15
+ try:
16
+ import pygetwindow as gw
17
+ PYGETWINDOW_AVAILABLE = True
18
+ except ImportError:
19
+ PYGETWINDOW_AVAILABLE = False
20
+
21
+
22
+ def get_active_apps_tool():
23
+ """Define the get_active_apps tool."""
24
+ return {
25
+ "name": "get_active_apps",
26
+ "description": "Get a list of currently active applications running on the user's system. "
27
+ "WHEN TO USE: When you need to understand what software the user is currently working with, "
28
+ "gain context about their active applications, provide application-specific assistance, or "
29
+ "troubleshoot issues related to running programs. Especially useful for providing targeted "
30
+ "help based on what the user is actively using. "
31
+ "WHEN NOT TO USE: When you need information about specific windows rather than applications "
32
+ "(use get_available_windows instead), when you need a screenshot of what's on screen "
33
+ "(use capture_screenshot instead), or when application context isn't relevant to the task at hand. "
34
+ "RETURNS: JSON object containing platform information, success status, count of applications, "
35
+ "and an array of application objects. Each application object includes name, has_windows flag, "
36
+ "and when details are requested, information about visible windows. Works on macOS, Windows, "
37
+ "and Linux, with platform-specific implementation details.",
38
+ "inputSchema": {
39
+ "type": "object",
40
+ "properties": {
41
+ "with_details": {
42
+ "type": "boolean",
43
+ "description": "Whether to include additional details about each application. When true, returns extra "
44
+ "information like window_count, visible_windows with their names and dimensions. When false, "
45
+ "returns a simpler list with just application names and whether they have windows. Default is False."
46
+ }
47
+ },
48
+ "required": []
49
+ },
50
+ }
51
+
52
+
53
+ def _get_active_apps_macos(with_details: bool = False) -> List[Dict[str, Any]]:
54
+ """Get a list of currently active applications on macOS."""
55
+ active_apps = []
56
+
57
+ # Attempt to use Quartz directly first, as it's more reliable
58
+ if QUARTZ_AVAILABLE:
59
+ try:
60
+ window_list = Quartz.CGWindowListCopyWindowInfo(
61
+ Quartz.kCGWindowListOptionOnScreenOnly,
62
+ Quartz.kCGNullWindowID
63
+ )
64
+
65
+ # Create a map of app names to their details
66
+ app_map = {}
67
+ for window in window_list:
68
+ owner = window.get('kCGWindowOwnerName', '')
69
+
70
+ # Skip empty app names or system components we don't want to include
71
+ if not owner or owner in ["SystemUIServer", "osascript"]:
72
+ continue
73
+
74
+ # Create new entry for this app if we haven't seen it before
75
+ if owner not in app_map:
76
+ app_map[owner] = {
77
+ "name": owner,
78
+ "has_windows": False,
79
+ "window_count": 0,
80
+ "visible_windows": [] if with_details else None
81
+ }
82
+
83
+ # Count this window
84
+ app_map[owner]["window_count"] += 1
85
+
86
+ # Check if this is a visible application window
87
+ layer = window.get('kCGWindowLayer', 999)
88
+ name = window.get('kCGWindowName', '')
89
+
90
+ # Layer 0 typically indicates a standard application window
91
+ if layer <= 0:
92
+ app_map[owner]["has_windows"] = True
93
+
94
+ # Add details about this window if detailed info was requested
95
+ if with_details and name:
96
+ app_map[owner]["visible_windows"].append({
97
+ "name": name,
98
+ "id": window.get('kCGWindowNumber', 0)
99
+ })
100
+
101
+ # Convert the map to a list
102
+ active_apps = list(app_map.values())
103
+
104
+ # If we got results from Quartz, we're done
105
+ if active_apps:
106
+ return active_apps
107
+
108
+ except Exception as e:
109
+ print(f"Error getting applications with Quartz: {str(e)}")
110
+
111
+ # Fall back to AppleScript if Quartz failed or isn't available
112
+ if not active_apps:
113
+ try:
114
+ # Modified AppleScript that tries to avoid including itself
115
+ script = '''
116
+ tell application "System Events"
117
+ set appList to {}
118
+ set allProcesses to application processes whose background only is false
119
+
120
+ repeat with proc in allProcesses
121
+ set procName to name of proc
122
+ set procVisible to (windows of proc is not {})
123
+
124
+ # Skip the scripting process itself
125
+ if procName is not "osascript" and procName is not "System Events" then
126
+ set end of appList to {name:procName, has_windows:procVisible}
127
+ end if
128
+ end repeat
129
+
130
+ return appList
131
+ end tell
132
+ '''
133
+
134
+ result = subprocess.run(["osascript", "-e", script], capture_output=True, text=True)
135
+ if result.returncode == 0:
136
+ # Parse the output from AppleScript
137
+ output = result.stdout.strip()
138
+ if output:
139
+ # AppleScript returns a list of records, we need to parse it
140
+ lines = output.split(", {")
141
+ for i, line in enumerate(lines):
142
+ if i == 0:
143
+ line = line.replace("{", "")
144
+ if i == len(lines) - 1:
145
+ line = line.replace("}", "")
146
+
147
+ if "name:" in line and "has_windows:" in line:
148
+ parts = line.split(", ")
149
+ app_info = {}
150
+ for part in parts:
151
+ if "name:" in part:
152
+ app_info["name"] = part.replace("name:", "").strip()
153
+ elif "has_windows:" in part:
154
+ app_info["has_windows"] = part.replace("has_windows:", "").strip().lower() == "true"
155
+
156
+ if app_info:
157
+ active_apps.append(app_info)
158
+ except Exception as e:
159
+ print(f"Error getting apps with AppleScript: {str(e)}")
160
+
161
+ # Add window details if requested and if we got results from AppleScript
162
+ if active_apps and with_details and QUARTZ_AVAILABLE:
163
+ try:
164
+ window_list = Quartz.CGWindowListCopyWindowInfo(
165
+ Quartz.kCGWindowListOptionOnScreenOnly,
166
+ Quartz.kCGNullWindowID
167
+ )
168
+
169
+ # Create a map of app names to window details
170
+ app_details = {}
171
+ for window in window_list:
172
+ owner = window.get('kCGWindowOwnerName', '')
173
+ if not owner:
174
+ continue
175
+
176
+ if owner not in app_details:
177
+ app_details[owner] = {
178
+ "window_count": 0,
179
+ "windows": []
180
+ }
181
+
182
+ app_details[owner]["window_count"] += 1
183
+
184
+ # Add window details if this is a visible window
185
+ layer = window.get('kCGWindowLayer', 999)
186
+ name = window.get('kCGWindowName', '')
187
+
188
+ if layer <= 0 and name:
189
+ app_details[owner]["windows"].append({
190
+ "name": name,
191
+ "id": window.get('kCGWindowNumber', 0)
192
+ })
193
+
194
+ # Enhance the active_apps list with these details
195
+ for app in active_apps:
196
+ app_name = app["name"]
197
+ if app_name in app_details:
198
+ app["window_count"] = app_details[app_name]["window_count"]
199
+ app["visible_windows"] = app_details[app_name]["windows"]
200
+ except Exception as e:
201
+ print(f"Error getting window details with Quartz: {str(e)}")
202
+
203
+ return active_apps
204
+
205
+
206
+ def _get_active_apps_windows(with_details: bool = False) -> List[Dict[str, Any]]:
207
+ """Get a list of currently active applications on Windows."""
208
+ active_apps = []
209
+
210
+ # Basic list without details
211
+ if not with_details:
212
+ try:
213
+ # Use a PowerShell command to get running applications
214
+ script = '''
215
+ Get-Process | Where-Object {$_.MainWindowTitle -ne ""} |
216
+ Select-Object ProcessName, MainWindowTitle |
217
+ ConvertTo-Json
218
+ '''
219
+
220
+ cmd = ["powershell", "-Command", script]
221
+ process = subprocess.run(cmd, capture_output=True, text=True)
222
+
223
+ if process.returncode == 0:
224
+ try:
225
+ apps_data = json.loads(process.stdout)
226
+ # Handle single item (not in a list)
227
+ if isinstance(apps_data, dict):
228
+ apps_data = [apps_data]
229
+
230
+ for app in apps_data:
231
+ active_apps.append({
232
+ "name": app.get("ProcessName", ""),
233
+ "has_windows": True,
234
+ "window_title": app.get("MainWindowTitle", "")
235
+ })
236
+ except json.JSONDecodeError:
237
+ print("Failed to parse JSON from PowerShell output")
238
+ except Exception as e:
239
+ print(f"Error getting basic app list on Windows: {str(e)}")
240
+
241
+ # More detailed list with PyGetWindow if available
242
+ elif PYGETWINDOW_AVAILABLE:
243
+ try:
244
+ # Get the list of windows
245
+ all_windows = gw.getAllWindows()
246
+
247
+ # Group by application (approximate, since we only have window titles)
248
+ app_windows = {}
249
+
250
+ for window in all_windows:
251
+ if not window.title:
252
+ continue
253
+
254
+ # Try to extract application name from window title
255
+ # This is an approximation and might not be accurate for all applications
256
+ title_parts = window.title.split(' - ')
257
+ app_name = title_parts[-1] if len(title_parts) > 1 else window.title
258
+
259
+ if app_name not in app_windows:
260
+ app_windows[app_name] = {
261
+ "name": app_name,
262
+ "has_windows": True,
263
+ "window_count": 0,
264
+ "visible_windows": []
265
+ }
266
+
267
+ app_windows[app_name]["window_count"] += 1
268
+ app_windows[app_name]["visible_windows"].append({
269
+ "name": window.title,
270
+ "width": window.width,
271
+ "height": window.height
272
+ })
273
+
274
+ active_apps = list(app_windows.values())
275
+ except Exception as e:
276
+ print(f"Error getting detailed app list with PyGetWindow: {str(e)}")
277
+
278
+ # Fallback to a basic PowerShell approach
279
+ if not active_apps:
280
+ try:
281
+ script = '''
282
+ Get-Process | Where-Object {$_.MainWindowHandle -ne 0} |
283
+ Select-Object ProcessName | ConvertTo-Json
284
+ '''
285
+
286
+ cmd = ["powershell", "-Command", script]
287
+ process = subprocess.run(cmd, capture_output=True, text=True)
288
+
289
+ if process.returncode == 0:
290
+ try:
291
+ apps_data = json.loads(process.stdout)
292
+ # Handle single item (not in a list)
293
+ if isinstance(apps_data, dict):
294
+ apps_data = [apps_data]
295
+
296
+ for app in apps_data:
297
+ active_apps.append({
298
+ "name": app.get("ProcessName", ""),
299
+ "has_windows": True
300
+ })
301
+ except json.JSONDecodeError:
302
+ print("Failed to parse JSON from PowerShell output")
303
+ except Exception as e:
304
+ print(f"Error getting fallback app list on Windows: {str(e)}")
305
+
306
+ return active_apps
307
+
308
+
309
+ def _get_active_apps_linux(with_details: bool = False) -> List[Dict[str, Any]]:
310
+ """Get a list of currently active applications on Linux."""
311
+ active_apps = []
312
+
313
+ # Try using wmctrl if available
314
+ try:
315
+ # Check if wmctrl is installed
316
+ check_process = subprocess.run(["which", "wmctrl"], capture_output=True)
317
+ if check_process.returncode == 0:
318
+ # Get window list with wmctrl
319
+ wmctrl_process = subprocess.run(["wmctrl", "-l"], capture_output=True, text=True)
320
+
321
+ if wmctrl_process.returncode == 0:
322
+ window_data = wmctrl_process.stdout.strip().split('\n')
323
+
324
+ # Process each window line
325
+ app_windows = {}
326
+ for line in window_data:
327
+ if not line:
328
+ continue
329
+
330
+ parts = line.split(None, 3) # Split by whitespace, max 3 splits
331
+ if len(parts) >= 4:
332
+ window_id, desktop, host, title = parts
333
+
334
+ # Try to determine app name from window title
335
+ app_name = title.split(' - ')[-1] if ' - ' in title else title
336
+
337
+ if app_name not in app_windows:
338
+ app_windows[app_name] = {
339
+ "name": app_name,
340
+ "has_windows": True,
341
+ "window_count": 0,
342
+ "visible_windows": []
343
+ }
344
+
345
+ app_windows[app_name]["window_count"] += 1
346
+
347
+ if with_details:
348
+ app_windows[app_name]["visible_windows"].append({
349
+ "name": title,
350
+ "id": window_id,
351
+ "desktop": desktop
352
+ })
353
+
354
+ active_apps = list(app_windows.values())
355
+ except Exception as e:
356
+ print(f"Error getting apps with wmctrl: {str(e)}")
357
+
358
+ # If wmctrl failed or isn't available, try using ps
359
+ if not active_apps:
360
+ try:
361
+ # List GUI applications
362
+ cmd = ["ps", "-e", "-o", "comm="]
363
+ process = subprocess.run(cmd, capture_output=True, text=True)
364
+
365
+ if process.returncode == 0:
366
+ all_processes = process.stdout.strip().split('\n')
367
+
368
+ # Filter for likely GUI applications (very basic heuristic)
369
+ gui_indicators = ["-bin", "x11", "gtk", "qt", "wayland", "gnome", "kde"]
370
+
371
+ for proc in all_processes:
372
+ proc = proc.strip()
373
+ if not proc:
374
+ continue
375
+
376
+ # Skip system processes that typically don't have UIs
377
+ if proc.startswith(("ps", "bash", "sh", "zsh", "systemd", "login", "dbus")):
378
+ continue
379
+
380
+ # Include if it looks like a GUI app
381
+ if any(indicator in proc.lower() for indicator in gui_indicators) or "/" not in proc:
382
+ active_apps.append({
383
+ "name": proc,
384
+ "has_windows": True # Assuming these have windows, though we can't be sure
385
+ })
386
+ except Exception as e:
387
+ print(f"Error getting apps with ps: {str(e)}")
388
+
389
+ return active_apps
390
+
391
+
392
+ def get_active_apps(with_details: bool = False) -> Dict[str, Any]:
393
+ """
394
+ Get a list of currently active applications on the user's system.
395
+
396
+ Args:
397
+ with_details: Whether to include additional details about each application
398
+
399
+ Returns:
400
+ Dictionary with platform, success status, and list of active applications
401
+ """
402
+ system_name = platform.system().lower()
403
+
404
+ # Get active apps based on platform
405
+ if system_name == "darwin" or system_name == "macos":
406
+ active_apps = _get_active_apps_macos(with_details)
407
+ elif system_name == "windows":
408
+ active_apps = _get_active_apps_windows(with_details)
409
+ elif system_name == "linux":
410
+ active_apps = _get_active_apps_linux(with_details)
411
+ else:
412
+ return {
413
+ "success": False,
414
+ "platform": system_name,
415
+ "error": f"Unsupported platform: {system_name}. This tool currently supports macOS, Windows, and Linux.",
416
+ "apps": []
417
+ }
418
+
419
+ # If no apps were found, provide a descriptive error message
420
+ if not active_apps:
421
+ error_message = "No active applications could be detected. "
422
+ if system_name == "darwin":
423
+ error_message += ("This is most likely due to missing screen recording permissions. "
424
+ "Please go to System Settings > Privacy & Security > Screen Recording "
425
+ "and ensure that your terminal or IDE application has permission to record the screen.")
426
+ elif system_name == "windows":
427
+ error_message += "This might be due to insufficient permissions or no applications with visible windows."
428
+ elif system_name == "linux":
429
+ error_message += "This might be due to wmctrl not being installed or no applications with visible windows."
430
+
431
+ return {
432
+ "success": False,
433
+ "platform": system_name,
434
+ "error": error_message,
435
+ "apps": []
436
+ }
437
+
438
+ # Sort by name
439
+ active_apps.sort(key=lambda app: app.get("name", "").lower())
440
+
441
+ return {
442
+ "success": True,
443
+ "platform": system_name,
444
+ "app_count": len(active_apps),
445
+ "apps": active_apps
446
+ }
447
+
448
+
449
+ async def handle_get_active_apps(arguments: dict) -> List[types.TextContent]:
450
+ """Handle getting active applications."""
451
+ with_details = arguments.get("with_details", False)
452
+
453
+ result = get_active_apps(with_details)
454
+
455
+ return [types.TextContent(type="text", text=json.dumps(result, indent=2))]