codeloop-mcp-server 0.1.48 → 0.1.49
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/auth/critical_floors.d.ts +8 -4
- package/dist/auth/critical_floors.d.ts.map +1 -1
- package/dist/auth/critical_floors.js +13 -17
- package/dist/auth/critical_floors.js.map +1 -1
- package/dist/auth/init_hint_cache.d.ts +35 -0
- package/dist/auth/init_hint_cache.d.ts.map +1 -0
- package/dist/auth/init_hint_cache.js +143 -0
- package/dist/auth/init_hint_cache.js.map +1 -0
- package/dist/evidence/screenshot_diff.d.ts +23 -0
- package/dist/evidence/screenshot_diff.d.ts.map +1 -1
- package/dist/evidence/screenshot_diff.js +46 -13
- package/dist/evidence/screenshot_diff.js.map +1 -1
- package/dist/index.js +168 -11
- package/dist/index.js.map +1 -1
- package/dist/runners/csproj_output_path.d.ts +22 -0
- package/dist/runners/csproj_output_path.d.ts.map +1 -0
- package/dist/runners/csproj_output_path.js +108 -0
- package/dist/runners/csproj_output_path.js.map +1 -0
- package/dist/runners/png_dims.d.ts +20 -0
- package/dist/runners/png_dims.d.ts.map +1 -0
- package/dist/runners/png_dims.js +58 -0
- package/dist/runners/png_dims.js.map +1 -0
- package/dist/runners/window_manager.d.ts +17 -4
- package/dist/runners/window_manager.d.ts.map +1 -1
- package/dist/runners/window_manager.js +135 -22
- package/dist/runners/window_manager.js.map +1 -1
- package/dist/tools/design_compare.d.ts.map +1 -1
- package/dist/tools/design_compare.js +14 -0
- package/dist/tools/design_compare.js.map +1 -1
- package/dist/tools/desktop_app_mode.d.ts +48 -0
- package/dist/tools/desktop_app_mode.d.ts.map +1 -0
- package/dist/tools/desktop_app_mode.js +86 -0
- package/dist/tools/desktop_app_mode.js.map +1 -0
- package/dist/tools/self_test.d.ts +40 -0
- package/dist/tools/self_test.d.ts.map +1 -0
- package/dist/tools/self_test.js +205 -0
- package/dist/tools/self_test.js.map +1 -0
- package/dist/tools/verify.d.ts.map +1 -1
- package/dist/tools/verify.js +4 -5
- package/dist/tools/verify.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scans every `.csproj` / `.vbproj` under `projectAbs` (depth-capped
|
|
3
|
+
* to keep wide repos cheap) and returns the union of relative or
|
|
4
|
+
* absolute output paths declared via `<OutputPath>` and
|
|
5
|
+
* `<BaseOutputPath>`. The returned paths are normalised: backslash
|
|
6
|
+
* separators preserved on Windows-style hits, MSBuild variables like
|
|
7
|
+
* `$(Configuration)` collapsed to `Release` (so the helper never
|
|
8
|
+
* yields an unresolvable path), and trailing slashes stripped.
|
|
9
|
+
*
|
|
10
|
+
* Photometry-DB E2E 7+8: shipped with
|
|
11
|
+
* <BaseOutputPath>artifacts\bin\Release\</BaseOutputPath>
|
|
12
|
+
* which the pre-0.1.49 launchDesktopApp ignored — every
|
|
13
|
+
* codeloop_launch_app call returned "no .exe found" and the agent
|
|
14
|
+
* fell back to capturing the IDE.
|
|
15
|
+
*
|
|
16
|
+
* The helper takes its fs primitives as injected functions so the
|
|
17
|
+
* caller (window_manager) keeps its existing dynamic-import shape and
|
|
18
|
+
* we don't have to import `fs` at the top of the module twice.
|
|
19
|
+
*/
|
|
20
|
+
export function collectCsprojOutputPaths(projectAbs, pjoin, existsSync, statSync, readdirSync) {
|
|
21
|
+
const out = new Set();
|
|
22
|
+
const csprojFiles = [];
|
|
23
|
+
const visit = (dir, depth) => {
|
|
24
|
+
if (depth > 4 || csprojFiles.length >= 100)
|
|
25
|
+
return;
|
|
26
|
+
let entries;
|
|
27
|
+
try {
|
|
28
|
+
entries = readdirSync(dir);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
for (const e of entries) {
|
|
34
|
+
if (e === "node_modules" || e === "bin" || e === "obj" || e === ".git" ||
|
|
35
|
+
e === "publish" || e === "dist" || e === ".next" || e === ".cache" ||
|
|
36
|
+
e === "artifacts") {
|
|
37
|
+
// Note: we DO scan artifacts in `bin`/`publish`/`dist` mode
|
|
38
|
+
// through standardSubs, but we don't recurse INTO them
|
|
39
|
+
// looking for more .csproj files.
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const full = pjoin(dir, e);
|
|
43
|
+
let st;
|
|
44
|
+
try {
|
|
45
|
+
st = statSync(full);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (st.isDirectory())
|
|
51
|
+
visit(full, depth + 1);
|
|
52
|
+
else if (e.endsWith(".csproj") || e.endsWith(".vbproj")) {
|
|
53
|
+
csprojFiles.push(full);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
if (existsSync(projectAbs))
|
|
58
|
+
visit(projectAbs, 0);
|
|
59
|
+
for (const proj of csprojFiles) {
|
|
60
|
+
let content = "";
|
|
61
|
+
try {
|
|
62
|
+
// We avoid pulling readFileSync from fs — the caller already
|
|
63
|
+
// has it. Use a local require if available (we're in CJS via
|
|
64
|
+
// ts-node / esbuild bundle); otherwise read via dynamic import.
|
|
65
|
+
// Simplest: read via fs.readFileSync indirectly by trying to
|
|
66
|
+
// open the file with statSync first and bailing if huge.
|
|
67
|
+
const st = statSync(proj);
|
|
68
|
+
if (!st.isFile() || st.size > 1024 * 1024)
|
|
69
|
+
continue;
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
71
|
+
content = require("fs").readFileSync(proj, "utf-8");
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const matches = [
|
|
77
|
+
...content.matchAll(/<OutputPath[^>]*>([^<]+)<\/OutputPath>/gi),
|
|
78
|
+
...content.matchAll(/<BaseOutputPath[^>]*>([^<]+)<\/BaseOutputPath>/gi),
|
|
79
|
+
];
|
|
80
|
+
for (const m of matches) {
|
|
81
|
+
let raw = (m[1] || "").trim();
|
|
82
|
+
if (!raw)
|
|
83
|
+
continue;
|
|
84
|
+
// Resolve common MSBuild variables to safe defaults so the
|
|
85
|
+
// resulting path is at least walkable. The actual output is
|
|
86
|
+
// newest-mtime-wins, so picking Release here is fine.
|
|
87
|
+
raw = raw
|
|
88
|
+
.replace(/\$\(Configuration\)/gi, "Release")
|
|
89
|
+
.replace(/\$\(Platform\)/gi, "AnyCPU")
|
|
90
|
+
.replace(/\$\(TargetFramework\)/gi, "")
|
|
91
|
+
.replace(/\$\(MSBuildProjectName\)/gi, "")
|
|
92
|
+
.replace(/[\\/]+$/, "");
|
|
93
|
+
// If <OutputPath> is relative, it lives next to the .csproj
|
|
94
|
+
// (MSBuild semantics). Resolve relative to the project file's
|
|
95
|
+
// directory so we walk the right place.
|
|
96
|
+
if (!/^[A-Za-z]:[\\/]/.test(raw) && !raw.startsWith("/") && !raw.startsWith("\\")) {
|
|
97
|
+
const projDir = proj.substring(0, Math.max(proj.lastIndexOf("\\"), proj.lastIndexOf("/")));
|
|
98
|
+
const resolved = pjoin(projDir, raw);
|
|
99
|
+
out.add(resolved);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
out.add(raw);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return Array.from(out);
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=csproj_output_path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csproj_output_path.js","sourceRoot":"","sources":["../../src/runners/csproj_output_path.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads the IHDR width/height of a PNG file without decoding the
|
|
3
|
+
* pixel stream. Used by codeloop_interact's `coords: "screenshot"`
|
|
4
|
+
* mode to scale agent-supplied coordinates from the dimensions of
|
|
5
|
+
* the captured PNG (which the MCP transport may have downscaled
|
|
6
|
+
* before the agent ever saw it) up to the window's actual pixel
|
|
7
|
+
* dimensions.
|
|
8
|
+
*
|
|
9
|
+
* The PNG layout is fixed: 8-byte magic header + 8-byte IHDR chunk
|
|
10
|
+
* length+type + 4-byte BE width at offset 16 + 4-byte BE height at
|
|
11
|
+
* offset 20.
|
|
12
|
+
*
|
|
13
|
+
* Returns `null` on any failure (missing file, truncated, not a
|
|
14
|
+
* PNG); callers fall back to the un-scaled translation path.
|
|
15
|
+
*/
|
|
16
|
+
export declare function readPngDims(filePath: string): {
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
} | null;
|
|
20
|
+
//# sourceMappingURL=png_dims.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"png_dims.d.ts","sourceRoot":"","sources":["../../src/runners/png_dims.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAkCtF"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { existsSync, openSync, readSync, closeSync } from "fs";
|
|
2
|
+
/**
|
|
3
|
+
* Reads the IHDR width/height of a PNG file without decoding the
|
|
4
|
+
* pixel stream. Used by codeloop_interact's `coords: "screenshot"`
|
|
5
|
+
* mode to scale agent-supplied coordinates from the dimensions of
|
|
6
|
+
* the captured PNG (which the MCP transport may have downscaled
|
|
7
|
+
* before the agent ever saw it) up to the window's actual pixel
|
|
8
|
+
* dimensions.
|
|
9
|
+
*
|
|
10
|
+
* The PNG layout is fixed: 8-byte magic header + 8-byte IHDR chunk
|
|
11
|
+
* length+type + 4-byte BE width at offset 16 + 4-byte BE height at
|
|
12
|
+
* offset 20.
|
|
13
|
+
*
|
|
14
|
+
* Returns `null` on any failure (missing file, truncated, not a
|
|
15
|
+
* PNG); callers fall back to the un-scaled translation path.
|
|
16
|
+
*/
|
|
17
|
+
export function readPngDims(filePath) {
|
|
18
|
+
if (!existsSync(filePath))
|
|
19
|
+
return null;
|
|
20
|
+
let fd;
|
|
21
|
+
try {
|
|
22
|
+
fd = openSync(filePath, "r");
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const buf = Buffer.alloc(24);
|
|
29
|
+
const bytesRead = readSync(fd, buf, 0, 24, 0);
|
|
30
|
+
if (bytesRead < 24)
|
|
31
|
+
return null;
|
|
32
|
+
// PNG magic header
|
|
33
|
+
if (buf[0] !== 0x89 || buf[1] !== 0x50 || buf[2] !== 0x4e || buf[3] !== 0x47 ||
|
|
34
|
+
buf[4] !== 0x0d || buf[5] !== 0x0a || buf[6] !== 0x1a || buf[7] !== 0x0a) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
// IHDR chunk type at bytes 12-15 ("IHDR")
|
|
38
|
+
if (buf[12] !== 0x49 || buf[13] !== 0x48 || buf[14] !== 0x44 || buf[15] !== 0x52) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const width = buf.readUInt32BE(16);
|
|
42
|
+
const height = buf.readUInt32BE(20);
|
|
43
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
return { width, height };
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
try {
|
|
53
|
+
closeSync(fd);
|
|
54
|
+
}
|
|
55
|
+
catch { /* ignore */ }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=png_dims.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"png_dims.js","sourceRoot":"","sources":["../../src/runners/png_dims.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAE/D;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,WAAW,CAAC,QAAgB;IAC1C,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,IAAI,EAAU,CAAC;IACf,IAAI,CAAC;QACH,EAAE,GAAG,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC7B,MAAM,SAAS,GAAG,QAAQ,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QAC9C,IAAI,SAAS,GAAG,EAAE;YAAE,OAAO,IAAI,CAAC;QAChC,mBAAmB;QACnB,IACE,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI;YACxE,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,EACxE,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,0CAA0C;QAC1C,IAAI,GAAG,CAAC,EAAE,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC;YACjF,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;QACnC,MAAM,MAAM,GAAG,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;QACpC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;YACrF,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;YAAS,CAAC;QACT,IAAI,CAAC;YAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;IAC/C,CAAC;AACH,CAAC"}
|
|
@@ -32,15 +32,28 @@ export declare function buildWindowsProcessLookup(appName: string): string;
|
|
|
32
32
|
*/
|
|
33
33
|
export declare function bringAppToFront(appName: string): Promise<string>;
|
|
34
34
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
35
|
+
* Window bounds in screen pixels, plus optional DPI scale factors so
|
|
36
|
+
* codeloop_interact can translate logical-pixel coords from a captured
|
|
37
|
+
* screenshot to physical-pixel coords for the OS click APIs.
|
|
38
|
+
*
|
|
39
|
+
* dpi_x / dpi_y are physical-pixel-per-logical ratios (1.0 = 96 DPI
|
|
40
|
+
* baseline; 2.0 = 200% / Retina). Omitted when we couldn't read the
|
|
41
|
+
* value cleanly — translateXY treats absence as 1.0 and clicks
|
|
42
|
+
* unscaled, which preserves legacy behaviour.
|
|
37
43
|
*/
|
|
38
|
-
export
|
|
44
|
+
export type WindowBounds = {
|
|
39
45
|
x: number;
|
|
40
46
|
y: number;
|
|
41
47
|
width: number;
|
|
42
48
|
height: number;
|
|
43
|
-
|
|
49
|
+
dpi_x?: number;
|
|
50
|
+
dpi_y?: number;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Get the window bounds (position + size) for cropping video capture.
|
|
54
|
+
* Returns { x, y, width, height, dpi_x?, dpi_y? } in screen points.
|
|
55
|
+
*/
|
|
56
|
+
export declare function getWindowBounds(appName: string): Promise<WindowBounds | null>;
|
|
44
57
|
/**
|
|
45
58
|
* Click at screen coordinates using CGEvent (macOS), user32.dll (Windows),
|
|
46
59
|
* or xdotool (Linux). These are low-level HID events that Flutter and all
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"window_manager.d.ts","sourceRoot":"","sources":["../../src/runners/window_manager.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"window_manager.d.ts","sourceRoot":"","sources":["../../src/runners/window_manager.ts"],"names":[],"mappings":"AAMA;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAM1E;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAajE;AAiED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAKtE;AAkGD;;;;;;;;;GASG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;GAGG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAMnF;AAkKD;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA+C5E;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA6CvF;AAID,wBAAsB,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAGnE;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAI5D;AAED,wBAAsB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAG9D;AAED,wBAAsB,QAAQ,CAC5B,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,GAAE,MAAY,GACvE,OAAO,CAAC,OAAO,CAAC,CAQlB;AAID,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAGrE;AAED,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAGxE;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA+NjF;AAyDD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGxD;AAED,wBAAgB,oBAAoB,IAAI,MAAM,EAAE,CAE/C;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CASrE;AAID,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAyB7D;AAED,wBAAsB,gBAAgB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,MAAM,GAAE,MAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CAyD9I;AAED,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAqDlF;AAED,wBAAsB,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA4CjF;AAED,wBAAsB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAiC5E;AAUD,wBAAsB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAiEhE;AAED,wBAAsB,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,GAAE,MAAY,GAAG,OAAO,CAAC,OAAO,CAAC,CAyDzH;AAED,wBAAsB,mBAAmB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,UAAU,GAAE,MAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CA6C3G;AAID,wBAAsB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAGtE;AAED,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAM/D;AAED,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAGjE;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAKvE;AAED,wBAAsB,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,SAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQpH;AAED,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAGjE;AAID,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAG/D;AAED,wBAAsB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAI3G;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,CAEtD;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,CAEtD;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,CAEtD;AAED,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAI5E;AAED,wBAAsB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,UAAU,GAAE,MAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAEpG;AAED,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAGtE;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAGrE;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAGxE;AAED,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAGhF;AAED,wBAAsB,SAAS,CAAC,SAAS,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAKpE;AAID,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,kBAAkB,GAAG,eAAe,GAAG,SAAS,CAAC;AAoCtF;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAc1F;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAgC1E;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAa1E;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO1F;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAM9E;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA+B3E"}
|
|
@@ -2,6 +2,7 @@ import { platform, tmpdir } from "os";
|
|
|
2
2
|
import { writeFileSync, unlinkSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { runCommand, checkToolAvailable } from "./base.js";
|
|
5
|
+
import { collectCsprojOutputPaths } from "./csproj_output_path.js";
|
|
5
6
|
/**
|
|
6
7
|
* Cross-platform window ID lookup.
|
|
7
8
|
* macOS: CGWindowListCopyWindowInfo via Swift
|
|
@@ -194,7 +195,7 @@ async function bringToFrontLinux(appName) {
|
|
|
194
195
|
}
|
|
195
196
|
/**
|
|
196
197
|
* Get the window bounds (position + size) for cropping video capture.
|
|
197
|
-
* Returns { x, y, width, height } in screen points.
|
|
198
|
+
* Returns { x, y, width, height, dpi_x?, dpi_y? } in screen points.
|
|
198
199
|
*/
|
|
199
200
|
export async function getWindowBounds(appName) {
|
|
200
201
|
const os = platform();
|
|
@@ -208,11 +209,17 @@ export async function getWindowBounds(appName) {
|
|
|
208
209
|
}
|
|
209
210
|
async function getWindowBoundsMacOS(appName) {
|
|
210
211
|
const tmpFile = join(tmpdir(), `codeloop_winbounds_${Date.now()}.swift`);
|
|
212
|
+
// We also ask NSScreen for backingScaleFactor so codeloop_interact
|
|
213
|
+
// can scale logical-pixel screenshots back to physical pixels for
|
|
214
|
+
// CGEvent (which expects logical pts on macOS — backingScale 1.0
|
|
215
|
+
// is the right answer for clicking, but we still emit it so the
|
|
216
|
+
// value is observable in window_bounds for diagnostics).
|
|
211
217
|
const swiftCode = [
|
|
212
218
|
'import Cocoa',
|
|
213
219
|
'let options: CGWindowListOption = [.optionOnScreenOnly, .excludeDesktopElements]',
|
|
214
220
|
'guard let windowList = CGWindowListCopyWindowInfo(options, kCGNullWindowID) as? [[String: Any]] else { exit(1) }',
|
|
215
221
|
`let search = "${appName}".lowercased()`,
|
|
222
|
+
'let scale = NSScreen.main?.backingScaleFactor ?? 1.0',
|
|
216
223
|
'for window in windowList {',
|
|
217
224
|
' let owner = window[kCGWindowOwnerName as String] as? String ?? ""',
|
|
218
225
|
' if owner.lowercased().contains(search) {',
|
|
@@ -222,7 +229,7 @@ async function getWindowBoundsMacOS(appName) {
|
|
|
222
229
|
' let w = bounds["Width"] as? Int ?? 0',
|
|
223
230
|
' let h = bounds["Height"] as? Int ?? 0',
|
|
224
231
|
' if w > 50 && h > 50 {',
|
|
225
|
-
' print("\\(x),\\(y),\\(w),\\(h)")',
|
|
232
|
+
' print("\\(x),\\(y),\\(w),\\(h),\\(scale)")',
|
|
226
233
|
' exit(0)',
|
|
227
234
|
' }',
|
|
228
235
|
' }',
|
|
@@ -234,8 +241,23 @@ async function getWindowBoundsMacOS(appName) {
|
|
|
234
241
|
const result = await runCommand("swift", [tmpFile], process.cwd(), undefined, undefined, SWIFT_TIMEOUT_MS);
|
|
235
242
|
if (result.exit_code === 0 && result.stdout.trim()) {
|
|
236
243
|
const parts = result.stdout.trim().split(",").map(Number);
|
|
237
|
-
if (parts.length
|
|
238
|
-
|
|
244
|
+
if (parts.length >= 4 && parts.slice(0, 4).every(n => !isNaN(n))) {
|
|
245
|
+
const out = {
|
|
246
|
+
x: parts[0],
|
|
247
|
+
y: parts[1],
|
|
248
|
+
width: parts[2],
|
|
249
|
+
height: parts[3],
|
|
250
|
+
};
|
|
251
|
+
// CGEvent on macOS expects LOGICAL points, not physical
|
|
252
|
+
// pixels. So we report dpi_x / dpi_y as 1.0 even on
|
|
253
|
+
// Retina displays — translateXY uses 1.0 to preserve the
|
|
254
|
+
// legacy unscaled behaviour, which is correct for macOS.
|
|
255
|
+
// We still emit the backingScaleFactor for diagnostics in
|
|
256
|
+
// case future work (e.g. scaling screenshot dims) needs it.
|
|
257
|
+
if (parts.length >= 5 && !isNaN(parts[4]) && parts[4] > 0) {
|
|
258
|
+
out._backing_scale = parts[4];
|
|
259
|
+
}
|
|
260
|
+
return out;
|
|
239
261
|
}
|
|
240
262
|
}
|
|
241
263
|
}
|
|
@@ -248,6 +270,14 @@ async function getWindowBoundsMacOS(appName) {
|
|
|
248
270
|
return null;
|
|
249
271
|
}
|
|
250
272
|
async function getWindowBoundsWindows(appName) {
|
|
273
|
+
// GetDpiForWindow (Win10 1607+) returns the per-monitor DPI of the
|
|
274
|
+
// window's HWND. Divide by 96 to convert to a physical-per-logical
|
|
275
|
+
// ratio (1.0 = 100%, 1.5 = 150% scaling, 2.0 = 200%).
|
|
276
|
+
//
|
|
277
|
+
// user32.dll's mouse_event / SendInput consumes PHYSICAL pixels
|
|
278
|
+
// when the process is per-monitor DPI-aware (which is the .NET 4.6+
|
|
279
|
+
// default), so a screenshot taken at logical pixels needs to be
|
|
280
|
+
// multiplied by this factor before being passed to clickAtPosition.
|
|
251
281
|
const script = `
|
|
252
282
|
Add-Type @"
|
|
253
283
|
using System;
|
|
@@ -256,20 +286,43 @@ public class Win32Bounds {
|
|
|
256
286
|
[StructLayout(LayoutKind.Sequential)]
|
|
257
287
|
public struct RECT { public int Left; public int Top; public int Right; public int Bottom; }
|
|
258
288
|
[DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
|
|
289
|
+
[DllImport("user32.dll")] public static extern uint GetDpiForWindow(IntPtr hWnd);
|
|
259
290
|
}
|
|
260
291
|
"@
|
|
261
292
|
${buildWindowsProcessLookup(appName)}
|
|
262
293
|
if ($proc) {
|
|
263
294
|
$rect = New-Object Win32Bounds+RECT
|
|
264
295
|
[Win32Bounds]::GetWindowRect($proc.MainWindowHandle, [ref]$rect) | Out-Null
|
|
265
|
-
|
|
296
|
+
$dpi = 96
|
|
297
|
+
try {
|
|
298
|
+
$dpi = [Win32Bounds]::GetDpiForWindow($proc.MainWindowHandle)
|
|
299
|
+
if (-not $dpi -or $dpi -le 0) { $dpi = 96 }
|
|
300
|
+
} catch { $dpi = 96 }
|
|
301
|
+
Write-Output "$($rect.Left),$($rect.Top),$($rect.Right - $rect.Left),$($rect.Bottom - $rect.Top),$dpi"
|
|
266
302
|
}
|
|
267
303
|
`;
|
|
268
304
|
const result = await runCommand("powershell", ["-NoProfile", "-Command", script], process.cwd());
|
|
269
305
|
if (result.exit_code === 0 && result.stdout.trim()) {
|
|
270
306
|
const parts = result.stdout.trim().split(",").map(Number);
|
|
271
|
-
if (parts.length
|
|
272
|
-
|
|
307
|
+
if (parts.length >= 4 && parts.slice(0, 4).every(n => !isNaN(n))) {
|
|
308
|
+
const out = {
|
|
309
|
+
x: parts[0],
|
|
310
|
+
y: parts[1],
|
|
311
|
+
width: parts[2],
|
|
312
|
+
height: parts[3],
|
|
313
|
+
};
|
|
314
|
+
if (parts.length >= 5 && !isNaN(parts[4]) && parts[4] > 0) {
|
|
315
|
+
const ratio = parts[4] / 96;
|
|
316
|
+
// Only surface the DPI when the user is at non-100%
|
|
317
|
+
// scaling. translateXY treats absence as 1.0; we keep the
|
|
318
|
+
// payload tight by not emitting a redundant 1.0 on every
|
|
319
|
+
// call.
|
|
320
|
+
if (Math.abs(ratio - 1) > 0.01) {
|
|
321
|
+
out.dpi_x = ratio;
|
|
322
|
+
out.dpi_y = ratio;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return out;
|
|
273
326
|
}
|
|
274
327
|
}
|
|
275
328
|
return null;
|
|
@@ -297,8 +350,19 @@ async function getWindowBoundsLinux(appName) {
|
|
|
297
350
|
else if (key === "HEIGHT")
|
|
298
351
|
h = parseInt(val, 10);
|
|
299
352
|
}
|
|
300
|
-
if (w > 0 && h > 0)
|
|
301
|
-
|
|
353
|
+
if (w > 0 && h > 0) {
|
|
354
|
+
const out = { x, y, width: w, height: h };
|
|
355
|
+
// X11 GDK_SCALE / GDK_DPI_SCALE drives the per-display scaling on
|
|
356
|
+
// most desktop distros. xdotool reports DEVICE pixels, so we only
|
|
357
|
+
// surface a non-1.0 dpi when the env explicitly sets it (which
|
|
358
|
+
// matches what xdotool's click target expects too).
|
|
359
|
+
const gdkScale = parseFloat(process.env.GDK_SCALE || "");
|
|
360
|
+
if (Number.isFinite(gdkScale) && gdkScale > 0 && Math.abs(gdkScale - 1) > 0.01) {
|
|
361
|
+
out.dpi_x = gdkScale;
|
|
362
|
+
out.dpi_y = gdkScale;
|
|
363
|
+
}
|
|
364
|
+
return out;
|
|
365
|
+
}
|
|
302
366
|
return null;
|
|
303
367
|
}
|
|
304
368
|
/**
|
|
@@ -541,8 +605,27 @@ export async function launchDesktopApp(appName, projectDir) {
|
|
|
541
605
|
? appName.toLowerCase()
|
|
542
606
|
: appName.toLowerCase() + ".exe";
|
|
543
607
|
const candidates = [];
|
|
544
|
-
|
|
545
|
-
|
|
608
|
+
// Standard convention paths.
|
|
609
|
+
const standardSubs = ["publish", "bin", "build", "dist", "out"];
|
|
610
|
+
// Custom <OutputPath> / <BaseOutputPath> from any .csproj in the
|
|
611
|
+
// project. Photometry-DB ships with
|
|
612
|
+
// <BaseOutputPath>artifacts\bin\Release\…</BaseOutputPath>
|
|
613
|
+
// and pre-0.1.49 launchDesktopApp would not find that path
|
|
614
|
+
// because it only walked the convention subs above. Extracting
|
|
615
|
+
// the path here also covers projects with
|
|
616
|
+
// <OutputPath>D:\builds\$(Configuration)\</OutputPath>
|
|
617
|
+
// common on enterprise build configs.
|
|
618
|
+
const csprojOutputPaths = collectCsprojOutputPaths(projectAbs, pjoin, existsSync, statSync, readdirSync);
|
|
619
|
+
const allSubs = Array.from(new Set([
|
|
620
|
+
...standardSubs,
|
|
621
|
+
...csprojOutputPaths,
|
|
622
|
+
]));
|
|
623
|
+
for (const sub of allSubs) {
|
|
624
|
+
// sub may be relative (resolve against projectAbs) or
|
|
625
|
+
// absolute (absolute custom OutputPath).
|
|
626
|
+
const d = sub.startsWith("\\") || /^[A-Za-z]:[\\/]/.test(sub) || sub.startsWith("/")
|
|
627
|
+
? sub
|
|
628
|
+
: pjoin(projectAbs, sub);
|
|
546
629
|
if (existsSync(d)) {
|
|
547
630
|
candidates.push(...findRecursive(d, (f) => f.toLowerCase() === want).filter((p) => isExecCandidate(p, ".exe")));
|
|
548
631
|
}
|
|
@@ -557,20 +640,50 @@ export async function launchDesktopApp(appName, projectDir) {
|
|
|
557
640
|
}
|
|
558
641
|
});
|
|
559
642
|
const exe = candidates[0];
|
|
560
|
-
if (
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
643
|
+
if (exe) {
|
|
644
|
+
try {
|
|
645
|
+
const child = spawn(exe, [], { detached: true, stdio: "ignore", cwd: pjoin(exe, "..") });
|
|
646
|
+
child.unref();
|
|
647
|
+
return { launched: true, command: exe, pid: child.pid };
|
|
648
|
+
}
|
|
649
|
+
catch (e) {
|
|
650
|
+
return { launched: false, command: exe, reason: e.message };
|
|
651
|
+
}
|
|
565
652
|
}
|
|
653
|
+
// Last-resort fallback for MSIX / Microsoft Store / Start-menu
|
|
654
|
+
// installed apps (e.g. WinUI 3 packaged apps and any .NET MAUI
|
|
655
|
+
// packaged release). `Get-StartApps` returns the AppUserModelID
|
|
656
|
+
// we can launch via `explorer.exe shell:AppsFolder\<AUMID>`.
|
|
566
657
|
try {
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
658
|
+
const psLiteral = appName.replace(/'/g, "''");
|
|
659
|
+
const regexLiteral = appName
|
|
660
|
+
.replace(/'/g, "''")
|
|
661
|
+
.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
662
|
+
const lookupScript = `
|
|
663
|
+
$apps = Get-StartApps | Where-Object { $_.Name -eq '${psLiteral}' -or $_.Name -match '${regexLiteral}' }
|
|
664
|
+
$first = $apps | Select-Object -First 1
|
|
665
|
+
if ($first) { Write-Output $first.AppID }
|
|
666
|
+
`;
|
|
667
|
+
const lookup = await runCommand("powershell", ["-NoProfile", "-Command", lookupScript], projectAbs);
|
|
668
|
+
const aumid = lookup.exit_code === 0 ? lookup.stdout.trim() : "";
|
|
669
|
+
if (aumid) {
|
|
670
|
+
const launchCmd = `explorer.exe shell:AppsFolder\\${aumid}`;
|
|
671
|
+
const launch = await runCommand("powershell", ["-NoProfile", "-Command", `Start-Process -FilePath 'explorer.exe' -ArgumentList 'shell:AppsFolder\\${aumid}'`], projectAbs);
|
|
672
|
+
if (launch.exit_code === 0) {
|
|
673
|
+
return { launched: true, command: launchCmd };
|
|
674
|
+
}
|
|
675
|
+
return {
|
|
676
|
+
launched: false,
|
|
677
|
+
command: launchCmd,
|
|
678
|
+
reason: launch.stderr || "Get-StartApps lookup found AUMID but Start-Process failed",
|
|
679
|
+
};
|
|
680
|
+
}
|
|
573
681
|
}
|
|
682
|
+
catch { /* fall through to error message below */ }
|
|
683
|
+
return {
|
|
684
|
+
launched: false,
|
|
685
|
+
reason: `No .exe found under ${projectAbs}\\{publish|bin|build|dist|out} (plus any <OutputPath> from .csproj) matching '${want}', and \`Get-StartApps\` did not match an MSIX / Store-installed app named '${appName}'. Build the project first (e.g. \`dotnet publish -c Release\`) or set evidence.target_app in .codeloop/config.json to a file that exists.`,
|
|
686
|
+
};
|
|
574
687
|
}
|
|
575
688
|
if (os === "darwin") {
|
|
576
689
|
const wantApp = appName.toLowerCase().endsWith(".app")
|