@aexol/spectral 0.9.91 → 0.9.92
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.
- package/dist/extensions/desktop-screenshot/index.d.ts +17 -0
- package/dist/extensions/desktop-screenshot/index.d.ts.map +1 -0
- package/dist/extensions/desktop-screenshot/index.js +521 -0
- package/dist/sdk/coding-agent/core/extensions/native-extensions.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/extensions/native-extensions.js +11 -0
- package/dist/server/agent-bridge.d.ts.map +1 -1
- package/dist/server/agent-bridge.js +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Desktop Screenshot Extension
|
|
3
|
+
*
|
|
4
|
+
* Takes screenshots of the entire desktop or a specific application window
|
|
5
|
+
* using native OS commands. Zero external npm dependencies.
|
|
6
|
+
*
|
|
7
|
+
* Platforms:
|
|
8
|
+
* - macOS: screencapture + swift (CGWindowList)
|
|
9
|
+
* - Linux: import (ImageMagick) + wmctrl
|
|
10
|
+
* - Windows: PowerShell (System.Drawing + user32.dll)
|
|
11
|
+
*
|
|
12
|
+
* The screenshot is returned as a base64-encoded PNG that the spectral-vision
|
|
13
|
+
* extension automatically analyzes with a vision model.
|
|
14
|
+
*/
|
|
15
|
+
import type { ExtensionAPI } from "../../sdk/coding-agent/index.js";
|
|
16
|
+
export default function desktopScreenshotExtension(ext: ExtensionAPI): Promise<void>;
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/desktop-screenshot/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAOH,OAAO,KAAK,EAEX,YAAY,EAEZ,MAAM,iCAAiC,CAAC;AAiczC,wBAA8B,0BAA0B,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAoFzF"}
|
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Desktop Screenshot Extension
|
|
3
|
+
*
|
|
4
|
+
* Takes screenshots of the entire desktop or a specific application window
|
|
5
|
+
* using native OS commands. Zero external npm dependencies.
|
|
6
|
+
*
|
|
7
|
+
* Platforms:
|
|
8
|
+
* - macOS: screencapture + swift (CGWindowList)
|
|
9
|
+
* - Linux: import (ImageMagick) + wmctrl
|
|
10
|
+
* - Windows: PowerShell (System.Drawing + user32.dll)
|
|
11
|
+
*
|
|
12
|
+
* The screenshot is returned as a base64-encoded PNG that the spectral-vision
|
|
13
|
+
* extension automatically analyzes with a vision model.
|
|
14
|
+
*/
|
|
15
|
+
import { execFile } from "node:child_process";
|
|
16
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
17
|
+
import { tmpdir } from "node:os";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
import { randomUUID } from "node:crypto";
|
|
20
|
+
function tmpDir() {
|
|
21
|
+
const d = join(tmpdir(), "spectral-screenshots");
|
|
22
|
+
mkdirSync(d, { recursive: true });
|
|
23
|
+
return d;
|
|
24
|
+
}
|
|
25
|
+
// ===========================================================================
|
|
26
|
+
// macOS (darwin)
|
|
27
|
+
// ===========================================================================
|
|
28
|
+
function parseDarwinDisplaysOutput(output) {
|
|
29
|
+
const entries = [];
|
|
30
|
+
const entryPattern = /(\s*)(.*?):(.*)\n/g;
|
|
31
|
+
let match;
|
|
32
|
+
while ((match = entryPattern.exec(output)) !== null) {
|
|
33
|
+
entries.push({ indent: match[1].length, key: match[2].trim(), value: match[3].trim() });
|
|
34
|
+
}
|
|
35
|
+
function makeSubtree(currIndent, subtree, remaining) {
|
|
36
|
+
let entry;
|
|
37
|
+
while ((entry = remaining.shift())) {
|
|
38
|
+
if (entry.value === "") {
|
|
39
|
+
if (currIndent < entry.indent) {
|
|
40
|
+
let dedupKey = entry.key;
|
|
41
|
+
while (dedupKey in subtree)
|
|
42
|
+
dedupKey += "_1";
|
|
43
|
+
subtree[dedupKey] = {};
|
|
44
|
+
makeSubtree(entry.indent, subtree[dedupKey], remaining);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
remaining.unshift(entry);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
let dedupKey = entry.key;
|
|
53
|
+
while (dedupKey in subtree)
|
|
54
|
+
dedupKey += "_1";
|
|
55
|
+
subtree[dedupKey] = entry.value;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const tree = {};
|
|
60
|
+
makeSubtree(-1, tree, entries);
|
|
61
|
+
const gpuSection = tree["Graphics/Displays"];
|
|
62
|
+
if (!gpuSection)
|
|
63
|
+
return [];
|
|
64
|
+
const displays = [];
|
|
65
|
+
for (const gpuKey of Object.keys(gpuSection)) {
|
|
66
|
+
const gpu = gpuSection[gpuKey];
|
|
67
|
+
if (!gpu.Displays)
|
|
68
|
+
continue;
|
|
69
|
+
const dispMap = gpu.Displays;
|
|
70
|
+
for (const [name, props] of Object.entries(dispMap)) {
|
|
71
|
+
displays.push({ id: 0, name, primary: props["Main Display"] === "Yes" });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const primaries = displays.filter((d) => d.primary);
|
|
75
|
+
const nonPrimaries = displays.filter((d) => !d.primary);
|
|
76
|
+
return [...primaries, ...nonPrimaries].map((d, i) => ({ ...d, id: i }));
|
|
77
|
+
}
|
|
78
|
+
function listDarwinDisplays() {
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
execFile("system_profiler", ["SPDisplaysDataType"], { timeout: 10000 }, (err, stdout) => {
|
|
81
|
+
if (err)
|
|
82
|
+
return reject(err);
|
|
83
|
+
resolve(parseDarwinDisplaysOutput(stdout));
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function listDarwinWindows() {
|
|
88
|
+
const swiftSrc = [
|
|
89
|
+
"import Cocoa",
|
|
90
|
+
"let opts = CGWindowListOption(arrayLiteral: .optionOnScreenOnly, .excludeDesktopElements)",
|
|
91
|
+
"guard let windows = CGWindowListCopyWindowInfo(opts, kCGNullWindowID) as? [[String:Any]] else { exit(1) }",
|
|
92
|
+
"for w in windows {",
|
|
93
|
+
" let id = w[kCGWindowNumber as String] as? Int ?? 0",
|
|
94
|
+
" let owner = w[kCGWindowOwnerName as String] as? String ?? \"\"",
|
|
95
|
+
" let name = w[kCGWindowName as String] as? String ?? \"\"",
|
|
96
|
+
" let layer = w[kCGWindowLayer as String] as? Int ?? 999",
|
|
97
|
+
" if layer == 0 && !name.isEmpty { print(\"\\(id)\\t\\(owner)\\t\\(name)\") }",
|
|
98
|
+
"}",
|
|
99
|
+
].join("\n");
|
|
100
|
+
const swiftPath = join(tmpDir(), `${randomUUID()}.swift`);
|
|
101
|
+
writeFileSync(swiftPath, swiftSrc, "utf-8");
|
|
102
|
+
return new Promise((resolve, reject) => {
|
|
103
|
+
execFile("swift", [swiftPath], { timeout: 10000 }, (err, stdout) => {
|
|
104
|
+
if (err)
|
|
105
|
+
return reject(err);
|
|
106
|
+
const lines = stdout.trim().split("\n").filter(Boolean);
|
|
107
|
+
const windows = [];
|
|
108
|
+
for (const line of lines) {
|
|
109
|
+
const [idStr, ...rest] = line.split("\t");
|
|
110
|
+
const id = parseInt(idStr, 10);
|
|
111
|
+
if (!Number.isInteger(id))
|
|
112
|
+
continue;
|
|
113
|
+
const owner = rest[0] ?? "";
|
|
114
|
+
const title = rest.slice(1).join("\t");
|
|
115
|
+
windows.push({ id, owner, title });
|
|
116
|
+
}
|
|
117
|
+
resolve(windows);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
function darwinFullScreenshot(options) {
|
|
122
|
+
const format = options.format ?? "png";
|
|
123
|
+
const displayId = options.screen ?? 0;
|
|
124
|
+
return listDarwinDisplays().then((displays) => {
|
|
125
|
+
if (displays.length === 0)
|
|
126
|
+
throw new Error("No displays detected");
|
|
127
|
+
const maxId = displays.length - 1;
|
|
128
|
+
const id = typeof displayId === "string" ? parseInt(displayId, 10) : displayId;
|
|
129
|
+
if (!Number.isInteger(id) || id < 0 || id > maxId) {
|
|
130
|
+
throw new Error(`Invalid displayId: ${id} (valid: 0-${maxId})`);
|
|
131
|
+
}
|
|
132
|
+
const d = tmpDir();
|
|
133
|
+
const tmpPaths = [];
|
|
134
|
+
for (let i = 0; i <= id; i++)
|
|
135
|
+
tmpPaths.push(join(d, `${randomUUID()}.${format}`));
|
|
136
|
+
return new Promise((resolve, reject) => {
|
|
137
|
+
execFile("screencapture", ["-x", "-t", format, ...tmpPaths], { timeout: 15000 }, (err) => {
|
|
138
|
+
if (err)
|
|
139
|
+
return reject(err);
|
|
140
|
+
try {
|
|
141
|
+
resolve(readFileSync(tmpPaths[id]));
|
|
142
|
+
}
|
|
143
|
+
catch (e) {
|
|
144
|
+
reject(e);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
function darwinWindowScreenshot(windowId, options) {
|
|
151
|
+
const format = options.format ?? "png";
|
|
152
|
+
const outPath = join(tmpDir(), `${randomUUID()}.${format}`);
|
|
153
|
+
return new Promise((resolve, reject) => {
|
|
154
|
+
execFile("screencapture", ["-l", String(windowId), "-x", "-t", format, outPath], { timeout: 15000 }, (err) => {
|
|
155
|
+
if (err)
|
|
156
|
+
return reject(err);
|
|
157
|
+
try {
|
|
158
|
+
resolve(readFileSync(outPath));
|
|
159
|
+
}
|
|
160
|
+
catch (e) {
|
|
161
|
+
reject(e);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
function parseLinuxDisplaysOutput(output) {
|
|
167
|
+
return output
|
|
168
|
+
.split("\n")
|
|
169
|
+
.filter((line) => line.includes(" connected ") && /\dx\d/.test(line))
|
|
170
|
+
.map((line, index) => {
|
|
171
|
+
const parts = line.split(" ");
|
|
172
|
+
const name = parts[0];
|
|
173
|
+
const primary = parts[2] === "primary";
|
|
174
|
+
const crop = primary ? parts[3] : parts[2];
|
|
175
|
+
const resParts = crop.split(/[x+]/);
|
|
176
|
+
return {
|
|
177
|
+
id: name, name, primary,
|
|
178
|
+
width: Number(resParts[0]), height: Number(resParts[1]),
|
|
179
|
+
offsetX: Number(resParts[2]), offsetY: Number(resParts[3]),
|
|
180
|
+
crop,
|
|
181
|
+
};
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
function listLinuxDisplays() {
|
|
185
|
+
return new Promise((resolve, reject) => {
|
|
186
|
+
execFile("xrandr", ["--current"], { timeout: 10000 }, (err, stdout) => {
|
|
187
|
+
if (err)
|
|
188
|
+
return reject(err);
|
|
189
|
+
resolve(parseLinuxDisplaysOutput(stdout));
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
function listLinuxWindows() {
|
|
194
|
+
return new Promise((resolve, reject) => {
|
|
195
|
+
execFile("wmctrl", ["-l"], { timeout: 10000 }, (err, stdout) => {
|
|
196
|
+
if (err) {
|
|
197
|
+
return reject(new Error("wmctrl is not installed. Install: sudo apt install wmctrl (Debian/Ubuntu) " +
|
|
198
|
+
"or sudo dnf install wmctrl (Fedora)"));
|
|
199
|
+
}
|
|
200
|
+
const windows = [];
|
|
201
|
+
for (const line of stdout.trim().split("\n").filter(Boolean)) {
|
|
202
|
+
const parts = line.split(/\s+/);
|
|
203
|
+
if (parts.length < 5)
|
|
204
|
+
continue;
|
|
205
|
+
const id = parts[0];
|
|
206
|
+
const hostAndRest = parts.slice(2);
|
|
207
|
+
windows.push({ id, owner: hostAndRest[0] ?? "", title: hostAndRest.slice(1).join(" ") });
|
|
208
|
+
}
|
|
209
|
+
resolve(windows);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
function linuxFullScreenshot(options) {
|
|
214
|
+
return listLinuxDisplays().then((screens) => {
|
|
215
|
+
const screen = screens.find((s) => options.screen !== undefined ? String(s.id) === String(options.screen) : s.primary) ?? screens[0];
|
|
216
|
+
if (!screen)
|
|
217
|
+
throw new Error("No display found");
|
|
218
|
+
const format = options.format ?? "png";
|
|
219
|
+
return new Promise((resolve, reject) => {
|
|
220
|
+
execFile("import", ["-silent", "-window", "root", "-crop", screen.crop, `${format}:-`], {
|
|
221
|
+
encoding: "buffer",
|
|
222
|
+
maxBuffer: Math.max((screen.width ?? 1920) * (screen.height ?? 1080) * 8, 10 * 1024 * 1024),
|
|
223
|
+
timeout: 15000,
|
|
224
|
+
}, (err, stdout) => {
|
|
225
|
+
if (err)
|
|
226
|
+
return reject(err);
|
|
227
|
+
resolve(stdout);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
function linuxWindowScreenshot(windowId, options) {
|
|
233
|
+
const format = options.format ?? "png";
|
|
234
|
+
return new Promise((resolve, reject) => {
|
|
235
|
+
execFile("import", ["-silent", "-window", String(windowId), `${format}:-`], {
|
|
236
|
+
encoding: "buffer",
|
|
237
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
238
|
+
timeout: 15000,
|
|
239
|
+
}, (err, stdout) => {
|
|
240
|
+
if (err)
|
|
241
|
+
return reject(err);
|
|
242
|
+
resolve(stdout);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
// ===========================================================================
|
|
247
|
+
// Windows (PowerShell)
|
|
248
|
+
// ===========================================================================
|
|
249
|
+
function listWin32Windows() {
|
|
250
|
+
const psLines = [
|
|
251
|
+
'Add-Type @"',
|
|
252
|
+
"using System;",
|
|
253
|
+
"using System.Runtime.InteropServices;",
|
|
254
|
+
"using System.Text;",
|
|
255
|
+
"public class Win32Window {",
|
|
256
|
+
' [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();',
|
|
257
|
+
' [DllImport("user32.dll")] public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);',
|
|
258
|
+
' [DllImport("user32.dll")] public static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);',
|
|
259
|
+
' [DllImport("user32.dll")] public static extern int GetWindowTextLength(IntPtr hWnd);',
|
|
260
|
+
' [DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr hWnd);',
|
|
261
|
+
' [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);',
|
|
262
|
+
" public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);",
|
|
263
|
+
"}",
|
|
264
|
+
'"@',
|
|
265
|
+
"$windows = @()",
|
|
266
|
+
"$callback = [Win32Window+EnumWindowsProc]{",
|
|
267
|
+
" param($hWnd, $lParam)",
|
|
268
|
+
" if (-not [Win32Window]::IsWindowVisible($hWnd)) { return $true }",
|
|
269
|
+
" $len = [Win32Window]::GetWindowTextLength($hWnd)",
|
|
270
|
+
" if ($len -eq 0) { return $true }",
|
|
271
|
+
" $sb = New-Object System.Text.StringBuilder($len + 1)",
|
|
272
|
+
" [Win32Window]::GetWindowText($hWnd, $sb, $sb.Capacity) | Out-Null",
|
|
273
|
+
" $title = $sb.ToString()",
|
|
274
|
+
" if ([string]::IsNullOrWhiteSpace($title)) { return $true }",
|
|
275
|
+
" $pid = 0",
|
|
276
|
+
" [Win32Window]::GetWindowThreadProcessId($hWnd, [ref]$pid) | Out-Null",
|
|
277
|
+
' try { $proc = Get-Process -Id $pid -ErrorAction Stop; $owner = $proc.ProcessName } catch { $owner = "unknown" }',
|
|
278
|
+
" $tab = [char]9",
|
|
279
|
+
' Write-Output "$hWnd$tab$owner$tab$title"',
|
|
280
|
+
" return $true",
|
|
281
|
+
"}",
|
|
282
|
+
"[Win32Window]::EnumWindows($callback, [IntPtr]::Zero) | Out-Null",
|
|
283
|
+
];
|
|
284
|
+
const psScript = psLines.join("\r\n");
|
|
285
|
+
const psPath = join(tmpDir(), `${randomUUID()}.ps1`);
|
|
286
|
+
writeFileSync(psPath, psScript, "utf-8");
|
|
287
|
+
return new Promise((resolve, reject) => {
|
|
288
|
+
execFile("powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", psPath], { timeout: 15000 }, (err, stdout) => {
|
|
289
|
+
if (err)
|
|
290
|
+
return reject(err);
|
|
291
|
+
const windows = [];
|
|
292
|
+
for (const line of stdout.trim().split("\n").filter(Boolean)) {
|
|
293
|
+
const [id, owner, ...titleParts] = line.split("\t");
|
|
294
|
+
if (!id || !owner)
|
|
295
|
+
continue;
|
|
296
|
+
windows.push({ id, owner, title: titleParts.join("\t") });
|
|
297
|
+
}
|
|
298
|
+
resolve(windows);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
function win32FullScreenshot(options) {
|
|
303
|
+
const format = options.format ?? "png";
|
|
304
|
+
const formatUpper = format.toUpperCase();
|
|
305
|
+
const imageFormat = (formatUpper === "JPG" || formatUpper === "JPEG") ? "Jpeg" : "Png";
|
|
306
|
+
const outPath = join(tmpDir(), `${randomUUID()}.${format}`);
|
|
307
|
+
const psLines = [
|
|
308
|
+
"Add-Type -AssemblyName System.Drawing",
|
|
309
|
+
"Add-Type -AssemblyName System.Windows.Forms",
|
|
310
|
+
"$screens = [System.Windows.Forms.Screen]::AllScreens",
|
|
311
|
+
"$targetScreen = $screens[0]",
|
|
312
|
+
"$bounds = $targetScreen.Bounds",
|
|
313
|
+
"$bitmap = New-Object System.Drawing.Bitmap($bounds.Width, $bounds.Height)",
|
|
314
|
+
"$graphics = [System.Drawing.Graphics]::FromImage($bitmap)",
|
|
315
|
+
"$graphics.CopyFromScreen($bounds.Location, [System.Drawing.Point]::Empty, $bounds.Size)",
|
|
316
|
+
"$graphics.Dispose()",
|
|
317
|
+
`$bitmap.Save('${outPath.replace(/\\/g, "\\\\")}', [System.Drawing.Imaging.ImageFormat]::${imageFormat})`,
|
|
318
|
+
"$bitmap.Dispose()",
|
|
319
|
+
"Write-Output 'OK'",
|
|
320
|
+
];
|
|
321
|
+
const psScript = psLines.join("\r\n");
|
|
322
|
+
const psPath = join(tmpDir(), `${randomUUID()}.ps1`);
|
|
323
|
+
writeFileSync(psPath, psScript, "utf-8");
|
|
324
|
+
return new Promise((resolve, reject) => {
|
|
325
|
+
execFile("powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", psPath], { timeout: 15000 }, (err) => {
|
|
326
|
+
if (err)
|
|
327
|
+
return reject(new Error(`Screenshot failed: ${err.message}`));
|
|
328
|
+
try {
|
|
329
|
+
resolve(readFileSync(outPath));
|
|
330
|
+
}
|
|
331
|
+
catch (e) {
|
|
332
|
+
reject(e);
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
function win32WindowScreenshot(hwnd, options) {
|
|
338
|
+
const format = options.format ?? "png";
|
|
339
|
+
const formatUpper = format.toUpperCase();
|
|
340
|
+
const imageFormat = (formatUpper === "JPG" || formatUpper === "JPEG") ? "Jpeg" : "Png";
|
|
341
|
+
// Strip 0x prefix for PowerShell if present
|
|
342
|
+
const hwndHex = String(hwnd).replace(/^0x/i, "");
|
|
343
|
+
const outPath = join(tmpDir(), `${randomUUID()}.${format}`);
|
|
344
|
+
const psLines = [
|
|
345
|
+
"Add-Type -AssemblyName System.Drawing",
|
|
346
|
+
'Add-Type @"',
|
|
347
|
+
"using System;",
|
|
348
|
+
"using System.Runtime.InteropServices;",
|
|
349
|
+
"using System.Drawing;",
|
|
350
|
+
"public class Win32Capture {",
|
|
351
|
+
' [DllImport("user32.dll")] public static extern IntPtr GetWindowRect(IntPtr hWnd, ref RECT rect);',
|
|
352
|
+
' [DllImport("user32.dll")] public static extern bool PrintWindow(IntPtr hWnd, IntPtr hdcBlt, int nFlags);',
|
|
353
|
+
' [DllImport("user32.dll")] public static extern IntPtr GetWindowDC(IntPtr hWnd);',
|
|
354
|
+
' [DllImport("user32.dll")] public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);',
|
|
355
|
+
' [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr hWnd);',
|
|
356
|
+
' [DllImport("user32.dll")] public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);',
|
|
357
|
+
" public struct RECT { public int left, top, right, bottom; }",
|
|
358
|
+
"}",
|
|
359
|
+
'"@',
|
|
360
|
+
`$hwnd = [IntPtr]::new(0x${hwndHex})`,
|
|
361
|
+
"$empty = New-Object Win32Capture+RECT",
|
|
362
|
+
"[Win32Capture]::GetWindowRect($hwnd, [ref]$empty) | Out-Null",
|
|
363
|
+
"$w = $empty.right - $empty.left",
|
|
364
|
+
"$h = $empty.bottom - $empty.top",
|
|
365
|
+
'if ($w -le 0 -or $h -le 0) { throw "Window has zero size (minimized?)" }',
|
|
366
|
+
"$bitmap = New-Object System.Drawing.Bitmap($w, $h)",
|
|
367
|
+
"$graphics = [System.Drawing.Graphics]::FromImage($bitmap)",
|
|
368
|
+
"$hdc = $graphics.GetHdc()",
|
|
369
|
+
"[Win32Capture]::PrintWindow($hwnd, $hdc, 0) | Out-Null",
|
|
370
|
+
"$graphics.ReleaseHdc($hdc)",
|
|
371
|
+
"$graphics.Dispose()",
|
|
372
|
+
`$bitmap.Save('${outPath.replace(/\\/g, "\\\\")}', [System.Drawing.Imaging.ImageFormat]::${imageFormat})`,
|
|
373
|
+
"$bitmap.Dispose()",
|
|
374
|
+
"Write-Output 'OK'",
|
|
375
|
+
];
|
|
376
|
+
const psScript = psLines.join("\r\n");
|
|
377
|
+
const psPath = join(tmpDir(), `${randomUUID()}.ps1`);
|
|
378
|
+
writeFileSync(psPath, psScript, "utf-8");
|
|
379
|
+
return new Promise((resolve, reject) => {
|
|
380
|
+
execFile("powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", psPath], { timeout: 15000 }, (err) => {
|
|
381
|
+
if (err)
|
|
382
|
+
return reject(new Error(`Window screenshot failed: ${err.message}`));
|
|
383
|
+
try {
|
|
384
|
+
resolve(readFileSync(outPath));
|
|
385
|
+
}
|
|
386
|
+
catch (e) {
|
|
387
|
+
reject(e);
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
// ===========================================================================
|
|
393
|
+
// Unified API
|
|
394
|
+
// ===========================================================================
|
|
395
|
+
function screenshot(options = {}) {
|
|
396
|
+
switch (process.platform) {
|
|
397
|
+
case "darwin": return darwinFullScreenshot(options);
|
|
398
|
+
case "linux": return linuxFullScreenshot(options);
|
|
399
|
+
case "win32": return win32FullScreenshot(options);
|
|
400
|
+
default: return Promise.reject(new Error(`Unsupported platform: ${process.platform}`));
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
function screenshotWindow(windowId, options, platform) {
|
|
404
|
+
switch (platform) {
|
|
405
|
+
case "darwin": return darwinWindowScreenshot(windowId, options);
|
|
406
|
+
case "linux": return linuxWindowScreenshot(windowId, options);
|
|
407
|
+
case "win32": return win32WindowScreenshot(windowId, options);
|
|
408
|
+
default: return Promise.reject(new Error(`Window screenshot not supported on ${platform}`));
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
function listWindows() {
|
|
412
|
+
switch (process.platform) {
|
|
413
|
+
case "darwin": return listDarwinWindows();
|
|
414
|
+
case "linux": return listLinuxWindows();
|
|
415
|
+
case "win32": return listWin32Windows();
|
|
416
|
+
default: return Promise.reject(new Error(`Window listing not supported on ${process.platform}`));
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
// ===========================================================================
|
|
420
|
+
// Helpers
|
|
421
|
+
// ===========================================================================
|
|
422
|
+
function formatWindowList(windows, maxRows = 40) {
|
|
423
|
+
if (windows.length === 0)
|
|
424
|
+
return "No visible windows found.";
|
|
425
|
+
const rows = windows.slice(0, maxRows);
|
|
426
|
+
const idW = Math.max(4, ...rows.map((w) => String(w.id).length));
|
|
427
|
+
const ownerW = Math.max(5, ...rows.map((w) => w.owner.length));
|
|
428
|
+
const truncated = Math.min(60, 120 - idW - ownerW - 4);
|
|
429
|
+
const header = `Found ${windows.length} visible window(s)${windows.length > maxRows ? ` (showing first ${maxRows})` : ""}:\n\n${"ID".padEnd(idW)} ${"Owner".padEnd(ownerW)} Title\n${"─".repeat(idW)} ${"─".repeat(ownerW)} ${"─".repeat(truncated)}`;
|
|
430
|
+
const body = rows.map((w) => {
|
|
431
|
+
const title = w.title.length > truncated ? w.title.slice(0, truncated - 1) + "…" : w.title;
|
|
432
|
+
return `${String(w.id).padEnd(idW)} ${w.owner.padEnd(ownerW)} ${title}`;
|
|
433
|
+
}).join("\n");
|
|
434
|
+
return `${header}\n${body}`;
|
|
435
|
+
}
|
|
436
|
+
// ===========================================================================
|
|
437
|
+
// Extension registration
|
|
438
|
+
// ===========================================================================
|
|
439
|
+
export default async function desktopScreenshotExtension(ext) {
|
|
440
|
+
const screenshotTool = {
|
|
441
|
+
name: "desktop_screenshot",
|
|
442
|
+
label: "Desktop Screenshot",
|
|
443
|
+
description: "Take a screenshot of the entire desktop or a specific application window. " +
|
|
444
|
+
"Returns the image for AI vision analysis. " +
|
|
445
|
+
"Use 'list_windows' first to see window titles, then pass a matching " +
|
|
446
|
+
"'windowTitle' to capture only that window.",
|
|
447
|
+
promptSnippet: "`desktop_screenshot` — capture the entire screen or a specific window",
|
|
448
|
+
parameters: {
|
|
449
|
+
type: "object",
|
|
450
|
+
properties: {
|
|
451
|
+
format: { type: "string", enum: ["png", "jpg", "jpeg"], description: "Image format. Default: png." },
|
|
452
|
+
windowTitle: { type: "string", description: "Capture a specific window by title (case-insensitive substring match from list_windows). Omit to capture the entire desktop." },
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
async execute(_toolCallId, params) {
|
|
456
|
+
const p = params;
|
|
457
|
+
const format = p.format ?? "png";
|
|
458
|
+
const mimeType = (format === "jpg" || format === "jpeg") ? "image/jpeg" : "image/png";
|
|
459
|
+
const makeImageResult = (buf, label) => {
|
|
460
|
+
const base64 = buf.toString("base64");
|
|
461
|
+
return {
|
|
462
|
+
content: [
|
|
463
|
+
{ type: "text", text: `✓ ${label} (${format.toUpperCase()}, ${(buf.length / 1024).toFixed(1)} KB).\nThe image is attached and will be analyzed by the vision model.` },
|
|
464
|
+
{ type: "image", data: base64, mimeType },
|
|
465
|
+
],
|
|
466
|
+
details: { format, sizeBytes: buf.length, platform: process.platform },
|
|
467
|
+
};
|
|
468
|
+
};
|
|
469
|
+
const makeError = (msg) => ({
|
|
470
|
+
content: [{ type: "text", text: `❌ Screenshot failed: ${msg}` }],
|
|
471
|
+
details: { isError: true, error: msg },
|
|
472
|
+
});
|
|
473
|
+
try {
|
|
474
|
+
if (p.windowTitle) {
|
|
475
|
+
let windows;
|
|
476
|
+
try {
|
|
477
|
+
windows = await listWindows();
|
|
478
|
+
}
|
|
479
|
+
catch (e) {
|
|
480
|
+
return makeError(`Cannot list windows: ${e instanceof Error ? e.message : String(e)}`);
|
|
481
|
+
}
|
|
482
|
+
const search = p.windowTitle.toLowerCase();
|
|
483
|
+
const match = windows.find((w) => w.title.toLowerCase().includes(search));
|
|
484
|
+
if (!match) {
|
|
485
|
+
return {
|
|
486
|
+
content: [{ type: "text", text: `No window matching "${p.windowTitle}" found.\n\n${formatWindowList(windows, 25)}` }],
|
|
487
|
+
details: { isError: true, searched: p.windowTitle },
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
const buf = await screenshotWindow(match.id, { format }, process.platform);
|
|
491
|
+
return makeImageResult(buf, `Window "${match.title}" (${match.owner})`);
|
|
492
|
+
}
|
|
493
|
+
const buf = await screenshot({ format });
|
|
494
|
+
return makeImageResult(buf, "Desktop screenshot");
|
|
495
|
+
}
|
|
496
|
+
catch (err) {
|
|
497
|
+
return makeError(err instanceof Error ? err.message : String(err));
|
|
498
|
+
}
|
|
499
|
+
},
|
|
500
|
+
};
|
|
501
|
+
const listWindowsTool = {
|
|
502
|
+
name: "list_windows",
|
|
503
|
+
label: "List Windows",
|
|
504
|
+
description: "List all visible application windows with IDs, owners, and titles. " +
|
|
505
|
+
"Use before desktop_screenshot to find a window title for 'windowTitle'.",
|
|
506
|
+
promptSnippet: "`list_windows` — list visible application windows",
|
|
507
|
+
parameters: { type: "object", properties: {} },
|
|
508
|
+
async execute() {
|
|
509
|
+
try {
|
|
510
|
+
const windows = await listWindows();
|
|
511
|
+
return { content: [{ type: "text", text: formatWindowList(windows) }], details: { count: windows.length } };
|
|
512
|
+
}
|
|
513
|
+
catch (err) {
|
|
514
|
+
return { content: [{ type: "text", text: `❌ Failed to list windows: ${err instanceof Error ? err.message : String(err)}` }], details: { isError: true } };
|
|
515
|
+
}
|
|
516
|
+
},
|
|
517
|
+
};
|
|
518
|
+
ext.registerTool(screenshotTool);
|
|
519
|
+
ext.registerTool(listWindowsTool);
|
|
520
|
+
process.stderr.write(`[desktop-screenshot] Registered desktop_screenshot + list_windows (platform: ${process.platform}).\n`);
|
|
521
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-extensions.d.ts","sourceRoot":"","sources":["../../../../../src/sdk/coding-agent/core/extensions/native-extensions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,uBAAuB;IACtC,qEAAqE;IACrE,EAAE,EAAE,MAAM,CAAC;IACX,4DAA4D;IAC5D,KAAK,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,GAAG,YAAY,GAAG,aAAa,CAAC;IAChD;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,cAAc,EAAE,OAAO,CAAC;IACxB,mCAAmC;IACnC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,EAAE,uBAAuB,
|
|
1
|
+
{"version":3,"file":"native-extensions.d.ts","sourceRoot":"","sources":["../../../../../src/sdk/coding-agent/core/extensions/native-extensions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,uBAAuB;IACtC,qEAAqE;IACrE,EAAE,EAAE,MAAM,CAAC;IACX,4DAA4D;IAC5D,KAAK,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,GAAG,YAAY,GAAG,aAAa,CAAC;IAChD;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,cAAc,EAAE,OAAO,CAAC;IACxB,mCAAmC;IACnC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,EAAE,uBAAuB,EA0HtD,CAAC;AAEF;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,uBAAuB,GAAG,SAAS,CAElF;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAAC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAI1G;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC;IAClG,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC,CAUD"}
|
|
@@ -118,6 +118,17 @@ export const NATIVE_EXTENSIONS = [
|
|
|
118
118
|
defaultEnabled: true,
|
|
119
119
|
tags: ["pdf", "html", "documents", "playwright", "design"],
|
|
120
120
|
},
|
|
121
|
+
{
|
|
122
|
+
id: "desktop-screenshot",
|
|
123
|
+
label: "Desktop Screenshot",
|
|
124
|
+
description: "Takes a screenshot of the entire desktop screen using native OS commands " +
|
|
125
|
+
"(macOS: screencapture, Linux: ImageMagick/import, Windows: PowerShell). " +
|
|
126
|
+
"Returns the image for AI vision analysis — see open apps, errors, code, UI.",
|
|
127
|
+
category: "automation",
|
|
128
|
+
entryPath: "src/extensions/desktop-screenshot/index.ts",
|
|
129
|
+
defaultEnabled: true,
|
|
130
|
+
tags: ["screenshot", "desktop", "vision", "screen"],
|
|
131
|
+
},
|
|
121
132
|
];
|
|
122
133
|
/**
|
|
123
134
|
* Look up a native extension by id. Returns undefined for unknown ids.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-bridge.d.ts","sourceRoot":"","sources":["../../src/server/agent-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAaH,OAAO,EAQL,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EAEtB,MAAM,8BAA8B,CAAC;AAYtC,OAAO,EAEL,KAAK,aAAa,EACnB,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"agent-bridge.d.ts","sourceRoot":"","sources":["../../src/server/agent-bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -58,6 +58,7 @@ import { discoverPrimaryAgent, injectPrimaryAgentPrompt, } from "../agent/agents
|
|
|
58
58
|
import { loadAgentSettings, } from "./handlers/agent-settings.js";
|
|
59
59
|
import kanbanBridgeExtension from "../extensions/kanban-bridge.js";
|
|
60
60
|
import spectralVisionExtension from "../extensions/spectral-vision-fallback.js";
|
|
61
|
+
import desktopScreenshotExtension from "../extensions/desktop-screenshot/index.js";
|
|
61
62
|
import subagentExt from "../agent/index.js";
|
|
62
63
|
import designerExtension from "../designer/index.js";
|
|
63
64
|
import observationalMemory from "../memory/index.js";
|
|
@@ -618,6 +619,7 @@ export class AgentBridge {
|
|
|
618
619
|
emitSubagentEvent: (event) => this.emitAndBuffer(event),
|
|
619
620
|
});
|
|
620
621
|
});
|
|
622
|
+
extensionFactories.push(desktopScreenshotExtension);
|
|
621
623
|
// Register the bundled MCP adapter. Static import is required so the
|
|
622
624
|
// adapter is included in compiled standalone binaries (Bun build), where
|
|
623
625
|
// filesystem-based discovery via jiti fails because the source files are
|