clipqa-mcp 0.2.1
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/LICENSE +21 -0
- package/README.md +152 -0
- package/build/artifacts.d.ts +4 -0
- package/build/artifacts.d.ts.map +1 -0
- package/build/artifacts.js +24 -0
- package/build/artifacts.js.map +1 -0
- package/build/backends.d.ts +16 -0
- package/build/backends.d.ts.map +1 -0
- package/build/backends.js +48 -0
- package/build/backends.js.map +1 -0
- package/build/config.d.ts +7 -0
- package/build/config.d.ts.map +1 -0
- package/build/config.js +15 -0
- package/build/config.js.map +1 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +25 -0
- package/build/index.js.map +1 -0
- package/build/media.d.ts +13 -0
- package/build/media.d.ts.map +1 -0
- package/build/media.js +139 -0
- package/build/media.js.map +1 -0
- package/build/process-runner.d.ts +9 -0
- package/build/process-runner.d.ts.map +1 -0
- package/build/process-runner.js +25 -0
- package/build/process-runner.js.map +1 -0
- package/build/recorder.d.ts +42 -0
- package/build/recorder.d.ts.map +1 -0
- package/build/recorder.js +272 -0
- package/build/recorder.js.map +1 -0
- package/build/replay.d.ts +10 -0
- package/build/replay.d.ts.map +1 -0
- package/build/replay.js +39 -0
- package/build/replay.js.map +1 -0
- package/build/sampling.d.ts +13 -0
- package/build/sampling.d.ts.map +1 -0
- package/build/sampling.js +52 -0
- package/build/sampling.js.map +1 -0
- package/build/server.d.ts +5 -0
- package/build/server.d.ts.map +1 -0
- package/build/server.js +360 -0
- package/build/server.js.map +1 -0
- package/build/session-store.d.ts +16 -0
- package/build/session-store.d.ts.map +1 -0
- package/build/session-store.js +93 -0
- package/build/session-store.js.map +1 -0
- package/build/types.d.ts +46 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/build/visual.d.ts +62 -0
- package/build/visual.d.ts.map +1 -0
- package/build/visual.js +145 -0
- package/build/visual.js.map +1 -0
- package/build/windows.d.ts +4 -0
- package/build/windows.d.ts.map +1 -0
- package/build/windows.js +60 -0
- package/build/windows.js.map +1 -0
- package/docs/assets/banner.png +0 -0
- package/package.json +67 -0
- package/scripts/install-wgc.ps1 +40 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { MediaProcessor } from "./media.js";
|
|
2
|
+
export interface Region {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
}
|
|
8
|
+
export interface Difference {
|
|
9
|
+
meanDifference: number;
|
|
10
|
+
peakTileDifference: number;
|
|
11
|
+
ssim: number;
|
|
12
|
+
changedFraction: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function difference(a: Uint8Array, b: Uint8Array, width: number, height: number, ignore?: Region[]): Difference;
|
|
15
|
+
export interface VisualIndex {
|
|
16
|
+
fps: number;
|
|
17
|
+
decodedFrames: number;
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
changes: Array<{
|
|
21
|
+
timestamp: number;
|
|
22
|
+
score: number;
|
|
23
|
+
}>;
|
|
24
|
+
warnings: string[];
|
|
25
|
+
}
|
|
26
|
+
export declare function indexVideo(ffmpeg: string, video: string, start: number, end: number, fps?: number): Promise<VisualIndex>;
|
|
27
|
+
export declare function compareVideos(media: MediaProcessor, before: string, after: string, directory: string, options: {
|
|
28
|
+
beforeStart: number;
|
|
29
|
+
afterStart: number;
|
|
30
|
+
seconds: number;
|
|
31
|
+
samples: number;
|
|
32
|
+
threshold: number;
|
|
33
|
+
ignore: Region[];
|
|
34
|
+
}): Promise<{
|
|
35
|
+
schemaVersion: number;
|
|
36
|
+
options: {
|
|
37
|
+
beforeStart: number;
|
|
38
|
+
afterStart: number;
|
|
39
|
+
seconds: number;
|
|
40
|
+
samples: number;
|
|
41
|
+
threshold: number;
|
|
42
|
+
ignore: Region[];
|
|
43
|
+
};
|
|
44
|
+
changedSamples: number;
|
|
45
|
+
rows: (Difference & {
|
|
46
|
+
beforeSeconds: number;
|
|
47
|
+
afterSeconds: number;
|
|
48
|
+
score: number;
|
|
49
|
+
changed: boolean;
|
|
50
|
+
})[];
|
|
51
|
+
note: string;
|
|
52
|
+
previewSample: (Difference & {
|
|
53
|
+
beforeSeconds: number;
|
|
54
|
+
afterSeconds: number;
|
|
55
|
+
score: number;
|
|
56
|
+
changed: boolean;
|
|
57
|
+
}) | undefined;
|
|
58
|
+
previewPath: string;
|
|
59
|
+
reportPath: string;
|
|
60
|
+
jsonPath: string;
|
|
61
|
+
}>;
|
|
62
|
+
//# sourceMappingURL=visual.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visual.d.ts","sourceRoot":"","sources":["../src/visual.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,WAAW,MAAM;IAAG,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AAC/E,MAAM,WAAW,UAAU;IAAG,cAAc,EAAE,MAAM,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE;AAGzH,wBAAgB,UAAU,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,EAAO,GAAG,UAAU,CAsBzH;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAClE,OAAO,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;CAC1E;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,SAAK,GAAG,OAAO,CAAC,WAAW,CAAC,CA8C1H;AAED,wBAAsB,aAAa,CAAC,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE;;;qBAAnG,MAAM;oBAAc,MAAM;iBAAW,MAAM;iBAAW,MAAM;mBAAa,MAAM;gBAAU,MAAM,EAAE;;;;uBAGpL,MAAM;sBAAgB,MAAM;eAAS,MAAM;iBAAW,OAAO;;;;;;;;;;;;GAiC9G"}
|
package/build/visual.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import sharp from "sharp";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
// Local SSIM plus RGB error catches both structural and flat-color changes.
|
|
7
|
+
export function difference(a, b, width, height, ignore = []) {
|
|
8
|
+
if (a.length !== width * height * 3 || b.length !== a.length)
|
|
9
|
+
throw new Error("Frame dimensions must match.");
|
|
10
|
+
let sum = 0, pixels = 0, changed = 0, peak = 0, ssimSum = 0, tiles = 0;
|
|
11
|
+
for (let y = 0; y < height; y += 8)
|
|
12
|
+
for (let x = 0; x < width; x += 8) {
|
|
13
|
+
let n = 0, sx = 0, sy = 0, xx = 0, yy = 0, xy = 0, delta = 0;
|
|
14
|
+
for (let j = y; j < Math.min(y + 8, height); j++)
|
|
15
|
+
for (let i = x; i < Math.min(x + 8, width); i++) {
|
|
16
|
+
if (ignore.some(r => i / width >= r.x && i / width < r.x + r.width && j / height >= r.y && j / height < r.y + r.height))
|
|
17
|
+
continue;
|
|
18
|
+
const k = (j * width + i) * 3;
|
|
19
|
+
const u = .2126 * a[k] + .7152 * a[k + 1] + .0722 * a[k + 2];
|
|
20
|
+
const v = .2126 * b[k] + .7152 * b[k + 1] + .0722 * b[k + 2];
|
|
21
|
+
const d = (Math.abs(a[k] - b[k]) + Math.abs(a[k + 1] - b[k + 1]) + Math.abs(a[k + 2] - b[k + 2])) / (3 * 255);
|
|
22
|
+
delta += d;
|
|
23
|
+
sum += d;
|
|
24
|
+
pixels++;
|
|
25
|
+
if (d > .04)
|
|
26
|
+
changed++;
|
|
27
|
+
n++;
|
|
28
|
+
sx += u;
|
|
29
|
+
sy += v;
|
|
30
|
+
xx += u * u;
|
|
31
|
+
yy += v * v;
|
|
32
|
+
xy += u * v;
|
|
33
|
+
}
|
|
34
|
+
if (!n)
|
|
35
|
+
continue;
|
|
36
|
+
const mx = sx / n, my = sy / n;
|
|
37
|
+
const vx = Math.max(0, xx / n - mx * mx), vy = Math.max(0, yy / n - my * my), cov = xy / n - mx * my;
|
|
38
|
+
ssimSum += ((2 * mx * my + 6.5025) * (2 * cov + 58.5225)) / ((mx * mx + my * my + 6.5025) * (vx + vy + 58.5225));
|
|
39
|
+
tiles++;
|
|
40
|
+
peak = Math.max(peak, delta / n);
|
|
41
|
+
}
|
|
42
|
+
if (!pixels)
|
|
43
|
+
throw new Error("Ignore regions cover the entire frame.");
|
|
44
|
+
return { meanDifference: sum / pixels, peakTileDifference: peak, ssim: ssimSum / tiles, changedFraction: changed / pixels };
|
|
45
|
+
}
|
|
46
|
+
export async function indexVideo(ffmpeg, video, start, end, fps = 15) {
|
|
47
|
+
if (!(end > start) || end - start > 3600 || fps < 1 || fps > 60)
|
|
48
|
+
throw new Error("Index a valid range up to one hour at 1–60 FPS.");
|
|
49
|
+
const stat = await fs.stat(video);
|
|
50
|
+
const cache = `${video}.index-${start}-${end}-${fps}.json`;
|
|
51
|
+
const key = `${stat.size}:${stat.mtimeMs}:v1`;
|
|
52
|
+
const cached = await fs.readFile(cache, "utf8").then(JSON.parse).catch(() => undefined);
|
|
53
|
+
if (cached?.key === key)
|
|
54
|
+
return cached.index;
|
|
55
|
+
const width = 160, height = 90, bytes = width * height * 3;
|
|
56
|
+
const index = await new Promise((resolve, reject) => {
|
|
57
|
+
const child = spawn(ffmpeg, ["-hide_banner", "-loglevel", "error", "-ss", String(start), "-protocol_whitelist", "file,pipe", "-i", video, "-t", String(end - start), "-an", "-vf", `fps=${fps},scale=${width}:${height}`, "-pix_fmt", "rgb24", "-f", "rawvideo", "pipe:1"], { windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
|
|
58
|
+
let pending = Buffer.alloc(0), previous, count = 0, stderr = "", staticFrames = 0, darkFrames = 0;
|
|
59
|
+
const bins = new Map();
|
|
60
|
+
const timeout = setTimeout(() => { child.kill(); reject(new Error("Visual indexing timed out; inspect a shorter range.")); }, 120_000);
|
|
61
|
+
child.stderr.on("data", chunk => { stderr = (stderr + String(chunk)).slice(-2000); });
|
|
62
|
+
child.stdout.on("data", (chunk) => {
|
|
63
|
+
pending = Buffer.concat([pending, chunk]);
|
|
64
|
+
while (pending.length >= bytes) {
|
|
65
|
+
const frame = pending.subarray(0, bytes);
|
|
66
|
+
let brightness = 0;
|
|
67
|
+
for (const value of frame)
|
|
68
|
+
brightness += value;
|
|
69
|
+
if (brightness / bytes < 3)
|
|
70
|
+
darkFrames++;
|
|
71
|
+
if (previous) {
|
|
72
|
+
const d = difference(previous, frame, width, height);
|
|
73
|
+
const score = Math.max(d.peakTileDifference, 1 - d.ssim);
|
|
74
|
+
if (d.meanDifference < .0005)
|
|
75
|
+
staticFrames++;
|
|
76
|
+
if (score >= .035) {
|
|
77
|
+
const timestamp = start + count / fps, bin = Math.floor((timestamp - start) * 2);
|
|
78
|
+
if (!bins.has(bin) || bins.get(bin).score < score)
|
|
79
|
+
bins.set(bin, { timestamp, score });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
previous = Buffer.from(frame);
|
|
83
|
+
pending = pending.subarray(bytes);
|
|
84
|
+
count++;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
child.on("error", e => { clearTimeout(timeout); reject(e); });
|
|
88
|
+
child.on("close", code => {
|
|
89
|
+
clearTimeout(timeout);
|
|
90
|
+
if (code || !count || pending.length)
|
|
91
|
+
return reject(new Error(`Visual indexing failed: ${stderr || "no complete frames"}`));
|
|
92
|
+
const warnings = [];
|
|
93
|
+
if (darkFrames / count > .95)
|
|
94
|
+
warnings.push("Almost all frames are black; check permissions, protected content, and target rendering.");
|
|
95
|
+
if (staticFrames / Math.max(1, count - 1) > .98)
|
|
96
|
+
warnings.push("Nearly static recording: may be an idle application, a frozen capture, or a minimized target. This is not a defect diagnosis.");
|
|
97
|
+
const changes = [...bins.values()].sort((a, b) => b.score - a.score).slice(0, 256).sort((a, b) => a.timestamp - b.timestamp);
|
|
98
|
+
resolve({ fps, decodedFrames: count, width, height, changes, warnings });
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
const temp = `${cache}.${randomUUID()}.tmp`;
|
|
102
|
+
await fs.writeFile(temp, JSON.stringify({ key, index }));
|
|
103
|
+
await fs.rename(temp, cache);
|
|
104
|
+
return index;
|
|
105
|
+
}
|
|
106
|
+
export async function compareVideos(media, before, after, directory, options) {
|
|
107
|
+
if (options.seconds <= 0 || options.samples < 2 || options.samples > 60)
|
|
108
|
+
throw new Error("Compare a positive duration with 2–60 samples.");
|
|
109
|
+
await fs.mkdir(directory, { recursive: true });
|
|
110
|
+
const rows = [];
|
|
111
|
+
const htmlRows = [];
|
|
112
|
+
for (let i = 0; i < options.samples; i++) {
|
|
113
|
+
const offset = i * options.seconds / options.samples;
|
|
114
|
+
const a = path.join(directory, `before-${i}.png`), b = path.join(directory, `after-${i}.png`);
|
|
115
|
+
await media.extractFrame(before, options.beforeStart + offset, a, 640);
|
|
116
|
+
await media.extractFrame(after, options.afterStart + offset, b, 640);
|
|
117
|
+
const raw = async (file) => sharp(file).resize(320, 180, { fit: "contain", background: "black" }).removeAlpha().raw().toBuffer();
|
|
118
|
+
const [left, right] = await Promise.all([raw(a), raw(b)]);
|
|
119
|
+
const metrics = difference(left, right, 320, 180, options.ignore);
|
|
120
|
+
const score = Math.max(metrics.peakTileDifference, 1 - metrics.ssim);
|
|
121
|
+
const row = { beforeSeconds: options.beforeStart + offset, afterSeconds: options.afterStart + offset, ...metrics, score, changed: score >= options.threshold };
|
|
122
|
+
rows.push(row);
|
|
123
|
+
const mask = Buffer.alloc(left.length);
|
|
124
|
+
for (let p = 0; p < left.length; p += 3) {
|
|
125
|
+
const x = (p / 3 % 320) / 320, y = Math.floor(p / 3 / 320) / 180;
|
|
126
|
+
const ignored = options.ignore.some(r => x >= r.x && x < r.x + r.width && y >= r.y && y < r.y + r.height);
|
|
127
|
+
const delta = Math.max(Math.abs(left[p] - right[p]), Math.abs(left[p + 1] - right[p + 1]), Math.abs(left[p + 2] - right[p + 2]));
|
|
128
|
+
mask[p] = ignored ? 0 : Math.min(255, delta * 4);
|
|
129
|
+
mask[p + 1] = ignored ? 0 : delta;
|
|
130
|
+
}
|
|
131
|
+
const heatmap = await sharp(mask, { raw: { width: 320, height: 180, channels: 3 } }).png().toBuffer();
|
|
132
|
+
await fs.writeFile(path.join(directory, `difference-${i}.png`), heatmap);
|
|
133
|
+
const [aData, bData] = await Promise.all([fs.readFile(a), fs.readFile(b)]);
|
|
134
|
+
htmlRows.push(`<section><h2>${row.beforeSeconds.toFixed(3)}s → ${row.afterSeconds.toFixed(3)}s · ${row.changed ? "Changed" : "Within threshold"}</h2><p>SSIM ${metrics.ssim.toFixed(4)} · changed pixels ${(metrics.changedFraction * 100).toFixed(2)}%</p><div>${[aData, bData, heatmap].map((data, j) => `<figure><img alt="${["Before", "After", "Difference"][j]}" src="data:image/png;base64,${data.toString("base64")}"><figcaption>${["Before", "After", "Difference"][j]}</figcaption></figure>`).join("")}</div></section>`);
|
|
135
|
+
}
|
|
136
|
+
const report = { schemaVersion: 1, options, changedSamples: rows.filter(row => row.changed).length, rows, note: "Visual differences are candidates for review, not confirmed regressions. Only sampled timestamps are compared; motion between them may be missed." };
|
|
137
|
+
await fs.writeFile(path.join(directory, "report.json"), JSON.stringify(report, null, 2));
|
|
138
|
+
await fs.writeFile(path.join(directory, "report.html"), `<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>ClipQA comparison</title><style>body{font:16px system-ui;background:#0d1117;color:#e6edf3;margin:32px}section{border-top:1px solid #30363d;padding:16px 0}div{display:flex;flex-wrap:wrap;gap:12px}figure{margin:0;flex:1;min-width:240px}img{width:100%}figcaption{color:#8b949e}h2{font-size:18px}</style><h1>ClipQA comparison</h1><p>${report.changedSamples}/${rows.length} samples changed. ${report.note}</p>${htmlRows.join("")}</html>`);
|
|
139
|
+
const worst = rows.reduce((best, row, i) => row.score > rows[best].score ? i : best, 0);
|
|
140
|
+
const previewPath = path.join(directory, "preview.jpg");
|
|
141
|
+
const previews = await Promise.all(["before", "after", "difference"].map(async (name, i) => ({ input: await sharp(path.join(directory, `${name}-${worst}.png`)).resize(320, 180, { fit: "contain", background: "black" }).png().toBuffer(), left: i * 320, top: 0 })));
|
|
142
|
+
await sharp({ create: { width: 960, height: 180, channels: 3, background: "black" } }).composite(previews).jpeg({ quality: 85 }).toFile(previewPath);
|
|
143
|
+
return { ...report, previewSample: rows[worst], previewPath, reportPath: path.join(directory, "report.html"), jsonPath: path.join(directory, "report.json") };
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=visual.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visual.js","sourceRoot":"","sources":["../src/visual.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windows.d.ts","sourceRoot":"","sources":["../src/windows.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAmC7C,wBAAsB,WAAW,CAC/B,MAAM,EAAE,aAAa,EACrB,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAAC,UAAU,EAAE,CAAC,CA2BvB"}
|
package/build/windows.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const WINDOWS_SCRIPT = `
|
|
2
|
+
[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding $false
|
|
3
|
+
Add-Type -TypeDefinition @'
|
|
4
|
+
using System;
|
|
5
|
+
using System.Text;
|
|
6
|
+
using System.Collections.Generic;
|
|
7
|
+
using System.Runtime.InteropServices;
|
|
8
|
+
public class ClipWindow { public string title; public string hwnd; public string process; public int pid; public bool minimized; }
|
|
9
|
+
public static class ClipWindows {
|
|
10
|
+
delegate bool Callback(IntPtr h, IntPtr p);
|
|
11
|
+
[DllImport("user32.dll")] static extern bool EnumWindows(Callback callback, IntPtr p);
|
|
12
|
+
[DllImport("user32.dll")] static extern bool IsWindowVisible(IntPtr h);
|
|
13
|
+
[DllImport("user32.dll")] static extern bool IsIconic(IntPtr h);
|
|
14
|
+
[DllImport("user32.dll", CharSet=CharSet.Unicode)] static extern int GetWindowText(IntPtr h, StringBuilder text, int count);
|
|
15
|
+
[DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
|
|
16
|
+
public static List<ClipWindow> All() {
|
|
17
|
+
var result = new List<ClipWindow>();
|
|
18
|
+
EnumWindows((h,p) => {
|
|
19
|
+
if (!IsWindowVisible(h)) return true;
|
|
20
|
+
var text = new StringBuilder(2048); GetWindowText(h, text, text.Capacity);
|
|
21
|
+
if (text.Length == 0) return true;
|
|
22
|
+
uint pid; GetWindowThreadProcessId(h, out pid);
|
|
23
|
+
string process = ""; try { process = System.Diagnostics.Process.GetProcessById((int)pid).ProcessName; } catch {}
|
|
24
|
+
result.Add(new ClipWindow { title=text.ToString(), hwnd=h.ToInt64().ToString(), process=process, pid=(int)pid, minimized=IsIconic(h) });
|
|
25
|
+
return true;
|
|
26
|
+
}, IntPtr.Zero);
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
'@
|
|
31
|
+
@([ClipWindows]::All()) | Sort-Object title | ConvertTo-Json -Compress
|
|
32
|
+
`;
|
|
33
|
+
export async function listWindows(runner, platform = process.platform) {
|
|
34
|
+
if (platform === "linux") {
|
|
35
|
+
const result = await runner.run("wmctrl", ["-lp"]);
|
|
36
|
+
if (result.code)
|
|
37
|
+
throw new Error("Install wmctrl for X11 window discovery, or use xwininfo and provide windowId directly.");
|
|
38
|
+
return result.stdout.split(/\r?\n/).flatMap(line => {
|
|
39
|
+
const m = /^(0x[\da-f]+)\s+-?\d+\s+(\d+)\s+\S+\s+(.+)$/i.exec(line);
|
|
40
|
+
return m ? [{ hwnd: m[1], title: m[3], pid: Number(m[2]), process: "X11", windowId: m[1] }] : [];
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
if (platform !== "win32")
|
|
44
|
+
throw new Error("Use list_sources to select a macOS display. Window-only discovery is available on Windows and X11.");
|
|
45
|
+
const result = await runner.run("powershell.exe", [
|
|
46
|
+
"-NoLogo",
|
|
47
|
+
"-NoProfile",
|
|
48
|
+
"-NonInteractive",
|
|
49
|
+
"-Command",
|
|
50
|
+
WINDOWS_SCRIPT,
|
|
51
|
+
]);
|
|
52
|
+
if (result.code !== 0) {
|
|
53
|
+
throw new Error(`Unable to list windows: ${result.stderr.trim()}`);
|
|
54
|
+
}
|
|
55
|
+
if (!result.stdout.trim())
|
|
56
|
+
return [];
|
|
57
|
+
const parsed = JSON.parse(result.stdout);
|
|
58
|
+
return (Array.isArray(parsed) ? parsed : [parsed]).filter((window) => Boolean(window.title && window.hwnd));
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=windows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windows.js","sourceRoot":"","sources":["../src/windows.ts"],"names":[],"mappings":"AAGA,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BtB,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,MAAqB,EACrB,QAAQ,GAAoB,OAAO,CAAC,QAAQ;IAE5C,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;QACnD,IAAI,MAAM,CAAC,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,yFAAyF,CAAC,CAAC;QAC5H,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;YACjD,MAAM,CAAC,GAAG,8CAA8C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAE,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAE,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACtG,CAAC,CAAC,CAAC;IACL,CAAC;IACD,IAAI,QAAQ,KAAK,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,oGAAoG,CAAC,CAAC;IAEhJ,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,gBAAgB,EAAE;QAChD,SAAS;QACT,YAAY;QACZ,iBAAiB;QACjB,UAAU;QACV,cAAc;KACf,CAAC,CAAC;IACH,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,2BAA2B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACrE,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,CAAC;IAErC,MAAM,MAAM,GAA8B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACpE,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CACvD,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,CACjD,CAAC;AACJ,CAAC"}
|
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "clipqa-mcp",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Background window recording and frame-level QA evidence for AI agents via MCP.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"clipqa-mcp": "build/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "build/index.js",
|
|
10
|
+
"types": "build/index.d.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"build",
|
|
13
|
+
"scripts/install-wgc.ps1",
|
|
14
|
+
"docs/assets/banner.png",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=20"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -p tsconfig.json",
|
|
23
|
+
"check": "tsc -p tsconfig.json --noEmit",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:watch": "vitest",
|
|
26
|
+
"benchmark": "tsx benchmarks/temporal-coverage.ts",
|
|
27
|
+
"setup:wgc": "powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts/install-wgc.ps1",
|
|
28
|
+
"start": "node build/index.js",
|
|
29
|
+
"prepack": "npm run build && npm test"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"mcp",
|
|
33
|
+
"model-context-protocol",
|
|
34
|
+
"qa",
|
|
35
|
+
"screen-recording",
|
|
36
|
+
"roblox",
|
|
37
|
+
"computer-vision",
|
|
38
|
+
"testing"
|
|
39
|
+
],
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/WheresRace/clipqa-mcp.git"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/WheresRace/clipqa-mcp/issues"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/WheresRace/clipqa-mcp#readme",
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public",
|
|
51
|
+
"provenance": false
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@modelcontextprotocol/server": "^2.0.0",
|
|
55
|
+
"ffmpeg-static": "^5.3.0",
|
|
56
|
+
"ffprobe-static": "^3.1.0",
|
|
57
|
+
"sharp": "^0.35.4",
|
|
58
|
+
"zod": "^4.6.5"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@modelcontextprotocol/client": "^2.0.0",
|
|
62
|
+
"@types/node": "^24.0.0",
|
|
63
|
+
"tsx": "^4.20.0",
|
|
64
|
+
"typescript": "^7.0.2",
|
|
65
|
+
"vitest": "^5.0.0"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Optional Windows setup. Recording itself never downloads software.
|
|
2
|
+
$ErrorActionPreference = 'Stop'
|
|
3
|
+
$ProgressPreference = 'SilentlyContinue'
|
|
4
|
+
$clipInstall = Join-Path ([Environment]::GetFolderPath('UserProfile')) '.clipqa\bin'
|
|
5
|
+
$clipTemp = Join-Path ([System.IO.Path]::GetTempPath()) ('clipqa-ffmpeg-' + [guid]::NewGuid())
|
|
6
|
+
New-Item -ItemType Directory -Path $clipTemp | Out-Null
|
|
7
|
+
try {
|
|
8
|
+
$clipArchive = Join-Path $clipTemp 'ffmpeg.zip'
|
|
9
|
+
$clipUrl = 'https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip'
|
|
10
|
+
Invoke-WebRequest -UseBasicParsing -Uri $clipUrl -OutFile $clipArchive -TimeoutSec 120
|
|
11
|
+
$clipExpected = ((Invoke-WebRequest -UseBasicParsing -Uri ($clipUrl + '.sha256') -TimeoutSec 30).Content.Trim() -split '\s+')[0]
|
|
12
|
+
$clipHasher = [Security.Cryptography.SHA256]::Create()
|
|
13
|
+
$clipStream = [IO.File]::OpenRead($clipArchive)
|
|
14
|
+
try { $clipActual = [BitConverter]::ToString($clipHasher.ComputeHash($clipStream)).Replace('-', '') }
|
|
15
|
+
finally { $clipStream.Dispose(); $clipHasher.Dispose() }
|
|
16
|
+
if ($clipExpected -notmatch '^[0-9a-fA-F]{64}$' -or $clipActual -ne $clipExpected) {
|
|
17
|
+
throw 'FFmpeg checksum mismatch. No binaries installed; retry in case the release changed during download.'
|
|
18
|
+
}
|
|
19
|
+
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
|
20
|
+
[IO.Compression.ZipFile]::ExtractToDirectory($clipArchive, (Join-Path $clipTemp 'extracted'))
|
|
21
|
+
$clipExe = Get-ChildItem -LiteralPath (Join-Path $clipTemp 'extracted') -Recurse -Filter ffmpeg.exe | Select-Object -First 1
|
|
22
|
+
if (-not $clipExe) { throw 'FFmpeg executable missing from archive.' }
|
|
23
|
+
$ErrorActionPreference = 'Continue'
|
|
24
|
+
$clipHelp = & $clipExe.FullName -hide_banner -h filter=gfxcapture 2>&1 | Out-String
|
|
25
|
+
$clipExit = $LASTEXITCODE
|
|
26
|
+
$ErrorActionPreference = 'Stop'
|
|
27
|
+
if ($clipExit -ne 0) { throw 'Downloaded FFmpeg could not run.' }
|
|
28
|
+
if ($clipHelp -notmatch 'capture_cursor' -or $clipHelp -notmatch 'hwnd') { throw 'Downloaded build lacks the required gfxcapture filter.' }
|
|
29
|
+
New-Item -ItemType Directory -Path $clipInstall -Force | Out-Null
|
|
30
|
+
Copy-Item -LiteralPath $clipExe.FullName -Destination (Join-Path $clipInstall 'ffmpeg.exe') -Force
|
|
31
|
+
Copy-Item -LiteralPath (Join-Path $clipExe.DirectoryName 'ffprobe.exe') -Destination (Join-Path $clipInstall 'ffprobe.exe') -Force
|
|
32
|
+
Get-ChildItem -LiteralPath $clipExe.Directory.Parent.FullName -File | Where-Object { $_.Name -match 'LICENSE|README' } | Copy-Item -Destination $clipInstall -Force
|
|
33
|
+
Write-Output "Installed verified WGC-capable FFmpeg in $clipInstall. Restart your MCP client to use it. FFmpeg is separately licensed by its distributor."
|
|
34
|
+
} finally {
|
|
35
|
+
$clipResolved = [IO.Path]::GetFullPath($clipTemp)
|
|
36
|
+
$clipRoot = [IO.Path]::GetFullPath([IO.Path]::GetTempPath()).TrimEnd('\') + '\'
|
|
37
|
+
if ($clipResolved.StartsWith($clipRoot, [StringComparison]::OrdinalIgnoreCase) -and [IO.Path]::GetFileName($clipResolved).StartsWith('clipqa-ffmpeg-')) {
|
|
38
|
+
Remove-Item -LiteralPath $clipResolved -Recurse -Force
|
|
39
|
+
}
|
|
40
|
+
}
|