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.
- aidd/__init__.py +11 -0
- aidd/cli.py +141 -0
- aidd/server.py +54 -0
- aidd/tools/__init__.py +155 -0
- aidd/tools/base.py +18 -0
- aidd/tools/code_analysis.py +703 -0
- aidd/tools/code_execution.py +321 -0
- aidd/tools/directory_tools.py +289 -0
- aidd/tools/file_tools.py +784 -0
- aidd/tools/get_active_apps_tool.py +455 -0
- aidd/tools/get_available_windows_tool.py +395 -0
- aidd/tools/git_tools.py +687 -0
- aidd/tools/image_tools.py +127 -0
- aidd/tools/path_tools.py +86 -0
- aidd/tools/screenshot_tool.py +1029 -0
- aidd/tools/state.py +47 -0
- aidd/tools/system_tools.py +190 -0
- skydeckai_code-0.1.23.dist-info/METADATA +628 -0
- skydeckai_code-0.1.23.dist-info/RECORD +22 -0
- skydeckai_code-0.1.23.dist-info/WHEEL +4 -0
- skydeckai_code-0.1.23.dist-info/entry_points.txt +3 -0
- skydeckai_code-0.1.23.dist-info/licenses/LICENSE +201 -0
@@ -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))]
|