@kintsugi-ai/core 0.1.0
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.d.ts +57 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +151 -0
- package/dist/auth.js.map +1 -0
- package/dist/baseline.d.ts +8 -0
- package/dist/baseline.d.ts.map +1 -0
- package/dist/baseline.js +114 -0
- package/dist/baseline.js.map +1 -0
- package/dist/benchmark.d.ts +65 -0
- package/dist/benchmark.d.ts.map +1 -0
- package/dist/benchmark.js +232 -0
- package/dist/benchmark.js.map +1 -0
- package/dist/capturer.d.ts +14 -0
- package/dist/capturer.d.ts.map +1 -0
- package/dist/capturer.js +92 -0
- package/dist/capturer.js.map +1 -0
- package/dist/classifier.d.ts +62 -0
- package/dist/classifier.d.ts.map +1 -0
- package/dist/classifier.js +204 -0
- package/dist/classifier.js.map +1 -0
- package/dist/comparator.d.ts +28 -0
- package/dist/comparator.d.ts.map +1 -0
- package/dist/comparator.js +184 -0
- package/dist/comparator.js.map +1 -0
- package/dist/comparator.test.d.ts +2 -0
- package/dist/comparator.test.d.ts.map +1 -0
- package/dist/comparator.test.js +73 -0
- package/dist/comparator.test.js.map +1 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +79 -0
- package/dist/config.js.map +1 -0
- package/dist/config.test.d.ts +2 -0
- package/dist/config.test.d.ts.map +1 -0
- package/dist/config.test.js +67 -0
- package/dist/config.test.js.map +1 -0
- package/dist/domdiff.d.ts +6 -0
- package/dist/domdiff.d.ts.map +1 -0
- package/dist/domdiff.js +71 -0
- package/dist/domdiff.js.map +1 -0
- package/dist/feedback.d.ts +9 -0
- package/dist/feedback.d.ts.map +1 -0
- package/dist/feedback.js +57 -0
- package/dist/feedback.js.map +1 -0
- package/dist/flowid.d.ts +3 -0
- package/dist/flowid.d.ts.map +1 -0
- package/dist/flowid.js +11 -0
- package/dist/flowid.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/recorder.d.ts +12 -0
- package/dist/recorder.d.ts.map +1 -0
- package/dist/recorder.js +86 -0
- package/dist/recorder.js.map +1 -0
- package/dist/setupPrompt.d.ts +10 -0
- package/dist/setupPrompt.d.ts.map +1 -0
- package/dist/setupPrompt.js +27 -0
- package/dist/setupPrompt.js.map +1 -0
- package/dist/types.d.ts +144 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/dist/vision.d.ts +34 -0
- package/dist/vision.d.ts.map +1 -0
- package/dist/vision.js +139 -0
- package/dist/vision.js.map +1 -0
- package/package.json +37 -0
- package/src/auth.ts +195 -0
- package/src/baseline.ts +125 -0
- package/src/benchmark.ts +307 -0
- package/src/capturer.ts +105 -0
- package/src/classifier.ts +258 -0
- package/src/comparator.test.ts +97 -0
- package/src/comparator.ts +217 -0
- package/src/config.test.ts +80 -0
- package/src/config.ts +83 -0
- package/src/domdiff.ts +62 -0
- package/src/feedback.ts +64 -0
- package/src/flowid.ts +14 -0
- package/src/index.ts +13 -0
- package/src/recorder.ts +96 -0
- package/src/setupPrompt.ts +26 -0
- package/src/types.ts +144 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comparator.js","sourceRoot":"","sources":["../src/comparator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comparator.test.d.ts","sourceRoot":"","sources":["../src/comparator.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { test, describe } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createSyntheticUiPng, computePixelDiff, compareScreenshots, diffAriaSnapshots, parseVerdict, DiffResult } from './index.js';
|
|
4
|
+
describe('Kintsugi Visual Comparator Engine', () => {
|
|
5
|
+
test('identical images return 0% diff and IDENTICAL status', async () => {
|
|
6
|
+
const img1 = createSyntheticUiPng();
|
|
7
|
+
const img2 = createSyntheticUiPng();
|
|
8
|
+
const pixelResult = await computePixelDiff(img1, img2);
|
|
9
|
+
assert.equal(pixelResult.diffPercent, 0);
|
|
10
|
+
const comp = await compareScreenshots(img1, img2);
|
|
11
|
+
assert.equal(comp.result, DiffResult.IDENTICAL);
|
|
12
|
+
assert.equal(comp.pixelDiffPercent, 0);
|
|
13
|
+
});
|
|
14
|
+
test('large pixel diff without a classifier escalates as CHANGED', async () => {
|
|
15
|
+
const baseline = createSyntheticUiPng({ removeButton: false });
|
|
16
|
+
const regressed = createSyntheticUiPng({ removeButton: true });
|
|
17
|
+
const pixelResult = await computePixelDiff(baseline, regressed);
|
|
18
|
+
assert.ok(pixelResult.diffPercent > 0.5, 'Pixel diff should exceed 0.5%');
|
|
19
|
+
const comp = await compareScreenshots(baseline, regressed);
|
|
20
|
+
assert.equal(comp.result, DiffResult.CHANGED, 'Missing button should escalate as CHANGED');
|
|
21
|
+
});
|
|
22
|
+
test('sub-threshold noise is classified as MINOR', async () => {
|
|
23
|
+
const baseline = createSyntheticUiPng();
|
|
24
|
+
const withNoise = createSyntheticUiPng({ addNoise: true, noiseAmount: 0.0002 });
|
|
25
|
+
const comp = await compareScreenshots(baseline, withNoise);
|
|
26
|
+
assert.ok(comp.result === DiffResult.IDENTICAL || comp.result === DiffResult.MINOR, 'Subpixel noise should be classified as IDENTICAL or MINOR, not BROKEN');
|
|
27
|
+
});
|
|
28
|
+
test('aria snapshot diff reports removed and added lines', () => {
|
|
29
|
+
const baseline = [
|
|
30
|
+
'- button "Complete Purchase ($89.00)"',
|
|
31
|
+
'- text "Free shipping"',
|
|
32
|
+
].join('\n');
|
|
33
|
+
const current = [
|
|
34
|
+
'- text "Free shipping"',
|
|
35
|
+
'- textbox "Email"',
|
|
36
|
+
].join('\n');
|
|
37
|
+
const diff = diffAriaSnapshots(baseline, current);
|
|
38
|
+
assert.ok(diff.includes('REMOVED: - button "Complete Purchase ($89.00)"'), 'Removed button should appear as removed');
|
|
39
|
+
assert.ok(diff.includes('textbox "Email"') && diff.includes('ADDED:'), 'Added textbox should appear as added');
|
|
40
|
+
assert.ok(!diff.includes('no line-level differences'));
|
|
41
|
+
});
|
|
42
|
+
test('aria snapshot diff of identical snapshots reports no differences', () => {
|
|
43
|
+
const snap = '- button "Buy"\n- text "Hello"';
|
|
44
|
+
assert.ok(diffAriaSnapshots(snap, snap).includes('no line-level differences'));
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
describe('Classifier verdict parsing', () => {
|
|
48
|
+
test('parses the JSON verdict format', () => {
|
|
49
|
+
const v = parseVerdict('{"intentional": true, "confidence": 0.92, "reasoning": "Matches the requested recolor."}');
|
|
50
|
+
assert.equal(v.intentional, true);
|
|
51
|
+
assert.equal(v.confidence, 0.92);
|
|
52
|
+
assert.ok(v.reasoning.includes('recolor'));
|
|
53
|
+
});
|
|
54
|
+
test('parses JSON embedded in chatter', () => {
|
|
55
|
+
const v = parseVerdict('Sure! {"intentional": false, "confidence": 0.85, "reasoning": "The checkout button was removed; re-add it."} hope that helps');
|
|
56
|
+
assert.equal(v.intentional, false);
|
|
57
|
+
assert.equal(v.confidence, 0.85);
|
|
58
|
+
assert.ok(v.reasoning.includes('re-add'));
|
|
59
|
+
});
|
|
60
|
+
test('defaults missing confidence and reasoning conservatively', () => {
|
|
61
|
+
const v = parseVerdict('{"intentional": true}');
|
|
62
|
+
assert.equal(v.intentional, true);
|
|
63
|
+
assert.equal(v.confidence, 0.5);
|
|
64
|
+
assert.ok(v.reasoning.length > 0);
|
|
65
|
+
});
|
|
66
|
+
test('fails safe to not-intentional on unparseable output', () => {
|
|
67
|
+
const v = parseVerdict('I cannot tell what changed here, sorry.');
|
|
68
|
+
assert.equal(v.intentional, false);
|
|
69
|
+
assert.ok(v.confidence <= 0.7);
|
|
70
|
+
assert.ok(v.reasoning.length > 0);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
//# sourceMappingURL=comparator.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comparator.test.js","sourceRoot":"","sources":["../src/comparator.test.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { KintsugiConfig } from './types.js';
|
|
2
|
+
export declare function getDefaultConfig(): KintsugiConfig;
|
|
3
|
+
export declare function ensureKintsugiDir(projectDir: string): Promise<void>;
|
|
4
|
+
export declare function loadConfig(projectDir: string): Promise<KintsugiConfig>;
|
|
5
|
+
export declare function saveConfig(projectDir: string, config: KintsugiConfig): Promise<void>;
|
|
6
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,wBAAgB,gBAAgB,IAAI,cAAc,CAmCjD;AAED,wBAAsB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQzE;AAED,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAyB5E;AAED,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAI1F"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
export function getDefaultConfig() {
|
|
4
|
+
return {
|
|
5
|
+
version: 1,
|
|
6
|
+
devServerUrl: 'http://localhost:3000',
|
|
7
|
+
viewport: { width: 1280, height: 720 },
|
|
8
|
+
thresholds: {
|
|
9
|
+
pixelDiffPercent: 0.1,
|
|
10
|
+
ssimScore: 0.98,
|
|
11
|
+
maxCheckLatencyMs: 5000,
|
|
12
|
+
},
|
|
13
|
+
llm: {
|
|
14
|
+
provider: 'claude',
|
|
15
|
+
model: 'claude-3-5-sonnet-20241022',
|
|
16
|
+
apiKeyEnvVar: 'ANTHROPIC_API_KEY',
|
|
17
|
+
},
|
|
18
|
+
classifier: {
|
|
19
|
+
enabled: true,
|
|
20
|
+
provider: 'kintsugi',
|
|
21
|
+
endpoint: process.env.KINTSUGI_API_URL ?? 'https://api.kintsugi.dev/v1',
|
|
22
|
+
model: 'kintsugi-hosted',
|
|
23
|
+
tokenEnvVar: 'KINTSUGI_API_KEY',
|
|
24
|
+
pixelDiffThreshold: 0.5,
|
|
25
|
+
confidenceThreshold: 0.7,
|
|
26
|
+
timeoutMs: 20000,
|
|
27
|
+
},
|
|
28
|
+
agent: {
|
|
29
|
+
type: 'auto',
|
|
30
|
+
maxRetries: 3,
|
|
31
|
+
},
|
|
32
|
+
recording: {
|
|
33
|
+
passive: true,
|
|
34
|
+
collectorPort: 4321,
|
|
35
|
+
},
|
|
36
|
+
uiFilePatterns: ['src/**/*.tsx', 'src/**/*.ts', 'src/**/*.jsx', 'src/**/*.js'],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export async function ensureKintsugiDir(projectDir) {
|
|
40
|
+
const kintsugiDir = path.join(projectDir, '.kintsugi');
|
|
41
|
+
const flowsDir = path.join(kintsugiDir, 'flows');
|
|
42
|
+
const recordingsDir = path.join(kintsugiDir, 'recordings');
|
|
43
|
+
await fs.mkdir(kintsugiDir, { recursive: true });
|
|
44
|
+
await fs.mkdir(flowsDir, { recursive: true });
|
|
45
|
+
await fs.mkdir(recordingsDir, { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
export async function loadConfig(projectDir) {
|
|
48
|
+
const configPath = path.join(projectDir, '.kintsugi', 'config.json');
|
|
49
|
+
const defaultConfig = getDefaultConfig();
|
|
50
|
+
try {
|
|
51
|
+
const data = await fs.readFile(configPath, 'utf-8');
|
|
52
|
+
const userConfig = JSON.parse(data);
|
|
53
|
+
const classifier = {
|
|
54
|
+
...defaultConfig.classifier,
|
|
55
|
+
...(userConfig.classifier ?? {}),
|
|
56
|
+
};
|
|
57
|
+
// Migration: configs written before the provider field pointed at
|
|
58
|
+
// OpenAI-compatible endpoints (HF router). Keep them working unchanged.
|
|
59
|
+
if (userConfig.classifier && userConfig.classifier.provider === undefined) {
|
|
60
|
+
if (!classifier.endpoint.includes('kintsugi')) {
|
|
61
|
+
classifier.provider = 'openai-compatible';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
...defaultConfig,
|
|
66
|
+
...userConfig,
|
|
67
|
+
classifier,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
return defaultConfig;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export async function saveConfig(projectDir, config) {
|
|
75
|
+
await ensureKintsugiDir(projectDir);
|
|
76
|
+
const configPath = path.join(projectDir, '.kintsugi', 'config.json');
|
|
77
|
+
await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,IAAI,MAAM,MAAM,CAAC;AAGxB,MAAM,UAAU,gBAAgB;IAC9B,OAAO;QACL,OAAO,EAAE,CAAC;QACV,YAAY,EAAE,uBAAuB;QACrC,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;QACtC,UAAU,EAAE;YACV,gBAAgB,EAAE,GAAG;YACrB,SAAS,EAAE,IAAI;YACf,iBAAiB,EAAE,IAAI;SACxB;QACD,GAAG,EAAE;YACH,QAAQ,EAAE,QAAQ;YAClB,KAAK,EAAE,4BAA4B;YACnC,YAAY,EAAE,mBAAmB;SAClC;QACD,UAAU,EAAE;YACV,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,UAAU;YACpB,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,6BAA6B;YACvE,KAAK,EAAE,iBAAiB;YACxB,WAAW,EAAE,kBAAkB;YAC/B,kBAAkB,EAAE,GAAG;YACvB,mBAAmB,EAAE,GAAG;YACxB,SAAS,EAAE,KAAK;SACjB;QACD,KAAK,EAAE;YACL,IAAI,EAAE,MAAM;YACZ,UAAU,EAAE,CAAC;SACd;QACD,SAAS,EAAE;YACT,OAAO,EAAE,IAAI;YACb,aAAa,EAAE,IAAI;SACpB;QACD,cAAc,EAAE,CAAC,cAAc,EAAE,aAAa,EAAE,cAAc,EAAE,aAAa,CAAC;KAC/E,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,UAAkB;IACxD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IACvD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IACjD,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;IAE3D,MAAM,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACjD,MAAM,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,MAAM,EAAE,CAAC,KAAK,CAAC,aAAa,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,UAAkB;IACjD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,EAAE,aAAa,CAAC,CAAC;IACrE,MAAM,aAAa,GAAG,gBAAgB,EAAE,CAAC;IACzC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACpD,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAA4B,CAAC;QAC/D,MAAM,UAAU,GAAG;YACjB,GAAG,aAAa,CAAC,UAAU;YAC3B,GAAG,CAAC,UAAU,CAAC,UAAU,IAAI,EAAE,CAAC;SACD,CAAC;QAClC,kEAAkE;QAClE,wEAAwE;QACxE,IAAI,UAAU,CAAC,UAAU,IAAI,UAAU,CAAC,UAAU,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC1E,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC9C,UAAU,CAAC,QAAQ,GAAG,mBAAmB,CAAC;YAC5C,CAAC;QACH,CAAC;QACD,OAAO;YACL,GAAG,aAAa;YAChB,GAAG,UAAU;YACb,UAAU;SACO,CAAC;IACtB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,aAAa,CAAC;IACvB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,UAAkB,EAAE,MAAsB;IACzE,MAAM,iBAAiB,CAAC,UAAU,CAAC,CAAC;IACpC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,EAAE,aAAa,CAAC,CAAC;IACrE,MAAM,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AAC3E,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../src/config.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { test, describe } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import fs from 'node:fs/promises';
|
|
6
|
+
import { getDefaultConfig, saveConfig, loadConfig, ensureKintsugiDir, formatAgentFeedback, formatUserSummary, DiffResult } from './index.js';
|
|
7
|
+
describe('Kintsugi Config & Feedback Formatter', () => {
|
|
8
|
+
test('generates valid default config', () => {
|
|
9
|
+
const config = getDefaultConfig();
|
|
10
|
+
assert.equal(config.version, 1);
|
|
11
|
+
assert.equal(config.viewport.width, 1280);
|
|
12
|
+
assert.equal(config.viewport.height, 720);
|
|
13
|
+
assert.ok(config.thresholds.pixelDiffPercent > 0);
|
|
14
|
+
});
|
|
15
|
+
test('saves and loads config from disk', async () => {
|
|
16
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'kintsugi-test-'));
|
|
17
|
+
try {
|
|
18
|
+
await ensureKintsugiDir(tmpDir);
|
|
19
|
+
const config = getDefaultConfig();
|
|
20
|
+
config.devServerUrl = 'http://localhost:5173';
|
|
21
|
+
await saveConfig(tmpDir, config);
|
|
22
|
+
const loaded = await loadConfig(tmpDir);
|
|
23
|
+
assert.equal(loaded.devServerUrl, 'http://localhost:5173');
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
test('formats actionable agent feedback on regression', () => {
|
|
30
|
+
const mockResult = {
|
|
31
|
+
result: DiffResult.BROKEN,
|
|
32
|
+
pixelDiffPercent: 3.42,
|
|
33
|
+
ssimScore: 1,
|
|
34
|
+
failedStep: 2,
|
|
35
|
+
stepResults: [
|
|
36
|
+
{
|
|
37
|
+
step: 2,
|
|
38
|
+
pixelDiffPercent: 3.42,
|
|
39
|
+
ssimScore: 1,
|
|
40
|
+
result: DiffResult.BROKEN,
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
classifications: [
|
|
44
|
+
{
|
|
45
|
+
step: 2,
|
|
46
|
+
intentional: false,
|
|
47
|
+
confidence: 0.9,
|
|
48
|
+
reasoning: 'The checkout button was removed; re-add it to the order summary.',
|
|
49
|
+
ariaDiff: 'REMOVED: - button "Complete Purchase ($89.00)"',
|
|
50
|
+
oldImagePath: '/baseline/step_2.png',
|
|
51
|
+
newImagePath: '/tmp/step_2.png',
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
};
|
|
55
|
+
const feedback = formatAgentFeedback(mockResult, 'checkout-flow');
|
|
56
|
+
assert.ok(feedback.includes('checkout-flow is broken'), 'Should state the flow is broken');
|
|
57
|
+
assert.ok(feedback.includes('aria diffs:'), 'Should include aria diffs');
|
|
58
|
+
assert.ok(feedback.includes('REMOVED: - button'), 'Should include the aria diff content');
|
|
59
|
+
assert.ok(feedback.includes('image pairs (old image | new image):'), 'Should include image pairs');
|
|
60
|
+
assert.ok(feedback.includes('/baseline/step_2.png | /tmp/step_2.png'), 'Should include both image paths');
|
|
61
|
+
assert.ok(feedback.includes('suggested fixes:'), 'Should include suggested fixes');
|
|
62
|
+
assert.ok(feedback.includes('re-add it to the order summary'), 'Should include the classifier fix');
|
|
63
|
+
const summary = formatUserSummary(mockResult, 'checkout-flow');
|
|
64
|
+
assert.ok(summary.includes('checkout-flow'), 'Summary should include flow name');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
//# sourceMappingURL=config.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.test.js","sourceRoot":"","sources":["../src/config.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domdiff.d.ts","sourceRoot":"","sources":["../src/domdiff.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAsD3E"}
|
package/dist/domdiff.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Line diff for Playwright aria snapshots (YAML). Produces a compact
|
|
3
|
+
* removed/added summary suitable as classifier input.
|
|
4
|
+
*/
|
|
5
|
+
const MAX_DIFF_LINES = 80;
|
|
6
|
+
export function diffAriaSnapshots(baseline, current) {
|
|
7
|
+
const a = baseline.split('\n');
|
|
8
|
+
const b = current.split('\n');
|
|
9
|
+
// LCS table (snapshots are small — a few hundred lines max)
|
|
10
|
+
const m = a.length, n = b.length;
|
|
11
|
+
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
12
|
+
for (let i = m - 1; i >= 0; i--) {
|
|
13
|
+
for (let j = n - 1; j >= 0; j--) {
|
|
14
|
+
dp[i][j] = a[i] === b[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const ops = [];
|
|
18
|
+
let i = 0, j = 0;
|
|
19
|
+
while (i < m && j < n) {
|
|
20
|
+
if (a[i] === b[j]) {
|
|
21
|
+
ops.push({ type: ' ', line: a[i] });
|
|
22
|
+
i++;
|
|
23
|
+
j++;
|
|
24
|
+
}
|
|
25
|
+
else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
|
26
|
+
ops.push({ type: '-', line: a[i] });
|
|
27
|
+
i++;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
ops.push({ type: '+', line: b[j] });
|
|
31
|
+
j++;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
while (i < m) {
|
|
35
|
+
ops.push({ type: '-', line: a[i++] });
|
|
36
|
+
}
|
|
37
|
+
while (j < n) {
|
|
38
|
+
ops.push({ type: '+', line: b[j++] });
|
|
39
|
+
}
|
|
40
|
+
// Emit only changed hunks (with 1 line of context), trimmed.
|
|
41
|
+
// Prefixes are words, not +/-, because aria snapshot lines themselves
|
|
42
|
+
// start with YAML list markers ("- ").
|
|
43
|
+
const out = [];
|
|
44
|
+
let changes = 0;
|
|
45
|
+
let lastContextIndex = -1;
|
|
46
|
+
for (let k = 0; k < ops.length; k++) {
|
|
47
|
+
if (ops[k].type === ' ')
|
|
48
|
+
continue;
|
|
49
|
+
if (out.length > 0 && out[out.length - 1] !== '…' && ops[k - 1]?.type === ' ' && (k - 1) !== lastContextIndex) {
|
|
50
|
+
out.push(' ctx: ' + ops[k - 1].line);
|
|
51
|
+
}
|
|
52
|
+
while (k < ops.length && ops[k].type !== ' ') {
|
|
53
|
+
out.push((ops[k].type === '-' ? 'REMOVED: ' : 'ADDED: ') + ops[k].line);
|
|
54
|
+
changes++;
|
|
55
|
+
k++;
|
|
56
|
+
}
|
|
57
|
+
if (k < ops.length) {
|
|
58
|
+
out.push(' ctx: ' + ops[k].line);
|
|
59
|
+
lastContextIndex = k;
|
|
60
|
+
}
|
|
61
|
+
out.push('');
|
|
62
|
+
if (out.length >= MAX_DIFF_LINES) {
|
|
63
|
+
out.push('… (diff truncated, more changes below)');
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (changes === 0)
|
|
68
|
+
return '(no line-level differences — change is purely visual/rendered)';
|
|
69
|
+
return out.join('\n').trim();
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=domdiff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domdiff.js","sourceRoot":"","sources":["../src/domdiff.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ComparisonResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Fix-it feedback in the agent-facing format:
|
|
4
|
+
* "{flow name} is broken, aria diffs: [...], [sets of (new image and old image)],
|
|
5
|
+
* suggested fixes: [fixes from the classifier per image pair]"
|
|
6
|
+
*/
|
|
7
|
+
export declare function formatAgentFeedback(result: ComparisonResult, flowName: string): string;
|
|
8
|
+
export declare function formatUserSummary(result: ComparisonResult, flowName: string): string;
|
|
9
|
+
//# sourceMappingURL=feedback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feedback.d.ts","sourceRoot":"","sources":["../src/feedback.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE/D;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CA2CtF;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAWpF"}
|
package/dist/feedback.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { DiffResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Fix-it feedback in the agent-facing format:
|
|
4
|
+
* "{flow name} is broken, aria diffs: [...], [sets of (new image and old image)],
|
|
5
|
+
* suggested fixes: [fixes from the classifier per image pair]"
|
|
6
|
+
*/
|
|
7
|
+
export function formatAgentFeedback(result, flowName) {
|
|
8
|
+
const lines = [];
|
|
9
|
+
lines.push(`${flowName} is broken.`);
|
|
10
|
+
// aria diffs per classified pair
|
|
11
|
+
lines.push('aria diffs:');
|
|
12
|
+
const classifications = result.classifications ?? [];
|
|
13
|
+
if (classifications.length === 0) {
|
|
14
|
+
lines.push(' (none captured — flow failed during replay; check selectors and page load)');
|
|
15
|
+
}
|
|
16
|
+
for (const c of classifications) {
|
|
17
|
+
lines.push(` step ${c.step}:`);
|
|
18
|
+
for (const line of c.ariaDiff.split('\n')) {
|
|
19
|
+
lines.push(` ${line}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// sets of (old image, new image)
|
|
23
|
+
lines.push('image pairs (old image | new image):');
|
|
24
|
+
if (classifications.length === 0) {
|
|
25
|
+
lines.push(' (none — flow failed during replay)');
|
|
26
|
+
}
|
|
27
|
+
for (const c of classifications) {
|
|
28
|
+
lines.push(` step ${c.step}: ${c.oldImagePath ?? '(unavailable)'} | ${c.newImagePath ?? '(unavailable)'}`);
|
|
29
|
+
}
|
|
30
|
+
// suggested fixes — the classifier's reasoning per non-intentional pair
|
|
31
|
+
lines.push('suggested fixes:');
|
|
32
|
+
const regressions = classifications.filter(c => !c.intentional || c.confidence <= 0.7);
|
|
33
|
+
if (classifications.length === 0) {
|
|
34
|
+
lines.push(' - re-run the flow and verify the page loads; an element selector likely changed');
|
|
35
|
+
}
|
|
36
|
+
for (const c of regressions) {
|
|
37
|
+
lines.push(` step ${c.step}: ${c.reasoning}`);
|
|
38
|
+
}
|
|
39
|
+
if (classifications.length > 0 && regressions.length === 0) {
|
|
40
|
+
lines.push(' - (classifier found no intentional-only pairs, but the flow still failed — see replay errors)');
|
|
41
|
+
}
|
|
42
|
+
if (result.diffImagePath) {
|
|
43
|
+
lines.push(`diff heatmap: ${result.diffImagePath}`);
|
|
44
|
+
}
|
|
45
|
+
return lines.join('\n');
|
|
46
|
+
}
|
|
47
|
+
export function formatUserSummary(result, flowName) {
|
|
48
|
+
if (result.result === DiffResult.IDENTICAL) {
|
|
49
|
+
return `✅ Flow "${flowName}" passed. No visual differences detected.`;
|
|
50
|
+
}
|
|
51
|
+
let summary = `⚠️ Flow "${flowName}" resulted in ${result.result.toUpperCase()}.\n`;
|
|
52
|
+
if (result.failedStep !== undefined) {
|
|
53
|
+
summary += `Step ${result.failedStep} failed with a pixel difference of ${result.pixelDiffPercent.toFixed(2)}%.`;
|
|
54
|
+
}
|
|
55
|
+
return summary;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=feedback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feedback.js","sourceRoot":"","sources":["../src/feedback.ts"],"names":[],"mappings":"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"}
|
package/dist/flowid.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flowid.d.ts","sourceRoot":"","sources":["../src/flowid.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,CAKvE"}
|
package/dist/flowid.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable per-flow fingerprint used by the hosted API's flow gate: hashed from
|
|
3
|
+
* the flow name + step actions/selectors/urls — NOT from screenshots or
|
|
4
|
+
* timestamps — so replays count once and renames can't evade the limit.
|
|
5
|
+
*/
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
7
|
+
export function flowFingerprint(name, steps) {
|
|
8
|
+
const canonical = JSON.stringify(steps.map(s => [s.action, s.selector ?? null, s.url ?? null, s.value ?? null]));
|
|
9
|
+
return createHash('sha256').update(`${name}\n${canonical}`).digest('hex').slice(0, 40);
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=flowid.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flowid.js","sourceRoot":"","sources":["../src/flowid.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,KAAiB;IAC7D,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAC9B,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,IAAI,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,IAAI,EAAE,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAC/E,CAAC;IACF,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,IAAI,KAAK,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACzF,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export * from './config.js';
|
|
3
|
+
export * from './setupPrompt.js';
|
|
4
|
+
export * from './baseline.js';
|
|
5
|
+
export * from './capturer.js';
|
|
6
|
+
export * from './comparator.js';
|
|
7
|
+
export * from './feedback.js';
|
|
8
|
+
export * from './recorder.js';
|
|
9
|
+
export * from './benchmark.js';
|
|
10
|
+
export * from './classifier.js';
|
|
11
|
+
export * from './auth.js';
|
|
12
|
+
export * from './flowid.js';
|
|
13
|
+
export * from './domdiff.js';
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export * from './config.js';
|
|
3
|
+
export * from './setupPrompt.js';
|
|
4
|
+
export * from './baseline.js';
|
|
5
|
+
export * from './capturer.js';
|
|
6
|
+
export * from './comparator.js';
|
|
7
|
+
export * from './feedback.js';
|
|
8
|
+
export * from './recorder.js';
|
|
9
|
+
export * from './benchmark.js';
|
|
10
|
+
export * from './classifier.js';
|
|
11
|
+
export * from './auth.js';
|
|
12
|
+
export * from './flowid.js';
|
|
13
|
+
export * from './domdiff.js';
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FlowRecording, FlowStep } from './types.js';
|
|
2
|
+
export declare function recordFlow(options: {
|
|
3
|
+
name: string;
|
|
4
|
+
url: string;
|
|
5
|
+
outputDir: string;
|
|
6
|
+
viewport?: {
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
};
|
|
10
|
+
}): Promise<FlowRecording>;
|
|
11
|
+
export declare function generateStepsFromScript(scriptPath: string): FlowStep[];
|
|
12
|
+
//# sourceMappingURL=recorder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../src/recorder.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAgB,MAAM,YAAY,CAAC;AAExE,wBAAsB,UAAU,CAAC,OAAO,EAAE;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9C,GAAG,OAAO,CAAC,aAAa,CAAC,CAiFzB;AAED,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,QAAQ,EAAE,CAEtE"}
|
package/dist/recorder.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { chromium } from 'playwright';
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
export async function recordFlow(options) {
|
|
5
|
+
const browser = await chromium.launch({ headless: false });
|
|
6
|
+
const context = await browser.newContext({ viewport: options.viewport || { width: 1280, height: 720 } });
|
|
7
|
+
const page = await context.newPage();
|
|
8
|
+
const steps = [];
|
|
9
|
+
const startTime = Date.now();
|
|
10
|
+
steps.push({ action: 'navigate', url: options.url });
|
|
11
|
+
await page.goto(options.url);
|
|
12
|
+
await page.exposeFunction('trackAction', (action) => {
|
|
13
|
+
steps.push(action);
|
|
14
|
+
});
|
|
15
|
+
await page.addInitScript(() => {
|
|
16
|
+
document.addEventListener('click', (e) => {
|
|
17
|
+
const target = e.target;
|
|
18
|
+
let selector;
|
|
19
|
+
if (target.id) {
|
|
20
|
+
selector = `#${target.id}`;
|
|
21
|
+
}
|
|
22
|
+
else if (target.getAttribute('data-testid')) {
|
|
23
|
+
selector = `[data-testid="${target.getAttribute('data-testid')}"]`;
|
|
24
|
+
}
|
|
25
|
+
else if (target.name) {
|
|
26
|
+
selector = `${target.tagName.toLowerCase()}[name="${target.name}"]`;
|
|
27
|
+
}
|
|
28
|
+
else if (target.textContent && target.textContent.trim().length > 0 && target.textContent.trim().length < 80 && target.children.length === 0) {
|
|
29
|
+
selector = `text=${target.textContent.trim()}`;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
const parent = target.parentElement;
|
|
33
|
+
const siblings = parent ? Array.from(parent.children).filter(c => c.tagName === target.tagName) : [];
|
|
34
|
+
const idx = siblings.indexOf(target) + 1;
|
|
35
|
+
selector = siblings.length > 1 ? `${target.tagName.toLowerCase()}:nth-of-type(${idx})` : target.tagName.toLowerCase();
|
|
36
|
+
}
|
|
37
|
+
window.trackAction({ action: 'click', selector });
|
|
38
|
+
}, true);
|
|
39
|
+
document.addEventListener('change', (e) => {
|
|
40
|
+
const target = e.target;
|
|
41
|
+
let selector;
|
|
42
|
+
if (target.id) {
|
|
43
|
+
selector = `#${target.id}`;
|
|
44
|
+
}
|
|
45
|
+
else if (target.getAttribute('data-testid')) {
|
|
46
|
+
selector = `[data-testid="${target.getAttribute('data-testid')}"]`;
|
|
47
|
+
}
|
|
48
|
+
else if (target.name) {
|
|
49
|
+
selector = `${target.tagName.toLowerCase()}[name="${target.name}"]`;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
selector = target.tagName.toLowerCase();
|
|
53
|
+
}
|
|
54
|
+
if (target.type === 'checkbox' || target.type === 'radio') {
|
|
55
|
+
window.trackAction({ action: 'click', selector });
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
window.trackAction({ action: 'type', selector, value: target.value });
|
|
59
|
+
}
|
|
60
|
+
}, true);
|
|
61
|
+
});
|
|
62
|
+
await page.waitForEvent('close', { timeout: 0 });
|
|
63
|
+
await browser.close();
|
|
64
|
+
const duration = Date.now() - startTime;
|
|
65
|
+
const metadata = {
|
|
66
|
+
name: options.name,
|
|
67
|
+
url: options.url,
|
|
68
|
+
viewport: options.viewport || { width: 1280, height: 720 },
|
|
69
|
+
recordedAt: new Date().toISOString(),
|
|
70
|
+
duration,
|
|
71
|
+
stepCount: steps.length,
|
|
72
|
+
};
|
|
73
|
+
const recording = {
|
|
74
|
+
metadata,
|
|
75
|
+
steps,
|
|
76
|
+
screenshotPaths: [],
|
|
77
|
+
};
|
|
78
|
+
await fs.mkdir(options.outputDir, { recursive: true });
|
|
79
|
+
await fs.writeFile(path.join(options.outputDir, 'metadata.json'), JSON.stringify(metadata, null, 2));
|
|
80
|
+
await fs.writeFile(path.join(options.outputDir, 'steps.json'), JSON.stringify(steps, null, 2));
|
|
81
|
+
return recording;
|
|
82
|
+
}
|
|
83
|
+
export function generateStepsFromScript(scriptPath) {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=recorder.js.map
|