@ytspar/sweetlink 1.13.0 → 1.14.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/README.md +91 -12
- package/claude-skills/screenshot/SKILL.md +121 -20
- package/dist/cli/outputSchemas.d.ts +16 -0
- package/dist/cli/outputSchemas.d.ts.map +1 -1
- package/dist/cli/outputSchemas.js +33 -0
- package/dist/cli/outputSchemas.js.map +1 -1
- package/dist/cli/sweetlink-dev.js +0 -0
- package/dist/cli/sweetlink.js +347 -11
- package/dist/cli/sweetlink.js.map +1 -1
- package/dist/daemon/browser.d.ts +51 -0
- package/dist/daemon/browser.d.ts.map +1 -0
- package/dist/daemon/browser.js +153 -0
- package/dist/daemon/browser.js.map +1 -0
- package/dist/daemon/client.d.ts +32 -0
- package/dist/daemon/client.d.ts.map +1 -0
- package/dist/daemon/client.js +133 -0
- package/dist/daemon/client.js.map +1 -0
- package/dist/daemon/cursor.d.ts +15 -0
- package/dist/daemon/cursor.d.ts.map +1 -0
- package/dist/daemon/cursor.js +76 -0
- package/dist/daemon/cursor.js.map +1 -0
- package/dist/daemon/devices.d.ts +39 -0
- package/dist/daemon/devices.d.ts.map +1 -0
- package/dist/daemon/devices.js +101 -0
- package/dist/daemon/devices.js.map +1 -0
- package/dist/daemon/diff.d.ts +20 -0
- package/dist/daemon/diff.d.ts.map +1 -0
- package/dist/daemon/diff.js +181 -0
- package/dist/daemon/diff.js.map +1 -0
- package/dist/daemon/evidence.d.ts +29 -0
- package/dist/daemon/evidence.d.ts.map +1 -0
- package/dist/daemon/evidence.js +130 -0
- package/dist/daemon/evidence.js.map +1 -0
- package/dist/daemon/index.d.ts +10 -0
- package/dist/daemon/index.d.ts.map +1 -0
- package/dist/daemon/index.js +90 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/listeners.d.ts +55 -0
- package/dist/daemon/listeners.d.ts.map +1 -0
- package/dist/daemon/listeners.js +129 -0
- package/dist/daemon/listeners.js.map +1 -0
- package/dist/daemon/recording.d.ts +44 -0
- package/dist/daemon/recording.d.ts.map +1 -0
- package/dist/daemon/recording.js +133 -0
- package/dist/daemon/recording.js.map +1 -0
- package/dist/daemon/refs.d.ts +70 -0
- package/dist/daemon/refs.d.ts.map +1 -0
- package/dist/daemon/refs.js +185 -0
- package/dist/daemon/refs.js.map +1 -0
- package/dist/daemon/ringBuffer.d.ts +26 -0
- package/dist/daemon/ringBuffer.d.ts.map +1 -0
- package/dist/daemon/ringBuffer.js +54 -0
- package/dist/daemon/ringBuffer.js.map +1 -0
- package/dist/daemon/server.d.ts +23 -0
- package/dist/daemon/server.d.ts.map +1 -0
- package/dist/daemon/server.js +508 -0
- package/dist/daemon/server.js.map +1 -0
- package/dist/daemon/session.d.ts +41 -0
- package/dist/daemon/session.d.ts.map +1 -0
- package/dist/daemon/session.js +8 -0
- package/dist/daemon/session.js.map +1 -0
- package/dist/daemon/stateFile.d.ts +49 -0
- package/dist/daemon/stateFile.d.ts.map +1 -0
- package/dist/daemon/stateFile.js +162 -0
- package/dist/daemon/stateFile.js.map +1 -0
- package/dist/daemon/types.d.ts +72 -0
- package/dist/daemon/types.d.ts.map +1 -0
- package/dist/daemon/types.js +28 -0
- package/dist/daemon/types.js.map +1 -0
- package/dist/daemon/viewer.d.ts +33 -0
- package/dist/daemon/viewer.d.ts.map +1 -0
- package/dist/daemon/viewer.js +226 -0
- package/dist/daemon/viewer.js.map +1 -0
- package/dist/daemon/visualDiff.d.ts +34 -0
- package/dist/daemon/visualDiff.d.ts.map +1 -0
- package/dist/daemon/visualDiff.js +80 -0
- package/dist/daemon/visualDiff.js.map +1 -0
- package/package.json +20 -12
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evidence Upload & Terminal Capture
|
|
3
|
+
*
|
|
4
|
+
* - Upload session artifacts to GitHub PR as comments
|
|
5
|
+
* - Capture terminal output as asciicast + self-contained HTML player
|
|
6
|
+
*/
|
|
7
|
+
import { execFileSync } from 'child_process';
|
|
8
|
+
import { promises as fs } from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// PR Evidence Upload
|
|
12
|
+
// ============================================================================
|
|
13
|
+
/**
|
|
14
|
+
* Upload session evidence to a GitHub PR.
|
|
15
|
+
* Uses `gh` CLI for uploading and commenting.
|
|
16
|
+
*/
|
|
17
|
+
export async function uploadEvidence(manifest, sessionDir, prNumber, options) {
|
|
18
|
+
// Build comment body
|
|
19
|
+
const screenshotCount = manifest.screenshots.length;
|
|
20
|
+
const duration = manifest.duration.toFixed(1);
|
|
21
|
+
const actionCount = manifest.commands.length;
|
|
22
|
+
const errors = manifest.errors;
|
|
23
|
+
let body = `## Sweetlink QA Evidence\n\n`;
|
|
24
|
+
body += `**Session:** \`${manifest.sessionId}\`\n`;
|
|
25
|
+
body += `**Duration:** ${duration}s | **Actions:** ${actionCount} | **Screenshots:** ${screenshotCount}\n`;
|
|
26
|
+
body += `**Errors:** Console: ${errors.console} | Network: ${errors.network} | Server: ${errors.server}\n\n`;
|
|
27
|
+
// Add action timeline
|
|
28
|
+
if (manifest.commands.length > 0) {
|
|
29
|
+
body += `### Action Timeline\n\n`;
|
|
30
|
+
body += `| Time | Action |\n|------|--------|\n`;
|
|
31
|
+
for (const cmd of manifest.commands) {
|
|
32
|
+
body += `| ${cmd.timestamp.toFixed(1)}s | \`${cmd.action} ${cmd.args.join(' ')}\` |\n`;
|
|
33
|
+
}
|
|
34
|
+
body += `\n`;
|
|
35
|
+
}
|
|
36
|
+
// Check if viewer.html exists
|
|
37
|
+
const viewerPath = path.join(sessionDir, 'viewer.html');
|
|
38
|
+
const hasViewer = await fs.access(viewerPath).then(() => true).catch(() => false);
|
|
39
|
+
if (hasViewer) {
|
|
40
|
+
body += `> Interactive viewer: \`${viewerPath}\`\n`;
|
|
41
|
+
}
|
|
42
|
+
// Post comment via gh CLI
|
|
43
|
+
const repoFlag = options?.repo ? ['--repo', options.repo] : [];
|
|
44
|
+
try {
|
|
45
|
+
const output = execFileSync('gh', ['pr', 'comment', String(prNumber), '--body', body, ...repoFlag], { encoding: 'utf-8', timeout: 30_000 });
|
|
46
|
+
const commentUrl = output.trim();
|
|
47
|
+
return { commentUrl };
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
throw new Error(`Failed to post PR comment. Ensure \`gh\` CLI is installed and authenticated.\n` +
|
|
51
|
+
(error instanceof Error ? error.message : String(error)));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Run a command, capture its output with timing, and produce:
|
|
56
|
+
* - .cast file (asciicast v2 format)
|
|
57
|
+
* - Self-contained HTML player
|
|
58
|
+
*/
|
|
59
|
+
export async function captureTerminal(command, args, outputDir) {
|
|
60
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
61
|
+
const startTime = Date.now();
|
|
62
|
+
const events = [];
|
|
63
|
+
let output = '';
|
|
64
|
+
try {
|
|
65
|
+
output = execFileSync(command, args, {
|
|
66
|
+
encoding: 'utf-8',
|
|
67
|
+
timeout: 120_000,
|
|
68
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
// Capture output even on failure
|
|
73
|
+
if (error && typeof error === 'object' && 'stdout' in error) {
|
|
74
|
+
output = String(error.stdout);
|
|
75
|
+
}
|
|
76
|
+
if (error && typeof error === 'object' && 'stderr' in error) {
|
|
77
|
+
output += String(error.stderr);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const duration = (Date.now() - startTime) / 1000;
|
|
81
|
+
// Split into lines and create timed events
|
|
82
|
+
const lines = output.split('\n');
|
|
83
|
+
const timePerLine = duration / Math.max(lines.length, 1);
|
|
84
|
+
for (let i = 0; i < lines.length; i++) {
|
|
85
|
+
events.push([i * timePerLine, 'o', lines[i] + '\n']);
|
|
86
|
+
}
|
|
87
|
+
// Write asciicast v2 format
|
|
88
|
+
const castFilename = `terminal-${Date.now()}.cast`;
|
|
89
|
+
const castPath = path.join(outputDir, castFilename);
|
|
90
|
+
const header = JSON.stringify({
|
|
91
|
+
version: 2,
|
|
92
|
+
width: 120,
|
|
93
|
+
height: 40,
|
|
94
|
+
timestamp: Math.floor(startTime / 1000),
|
|
95
|
+
title: `${command} ${args.join(' ')}`,
|
|
96
|
+
env: { SHELL: '/bin/bash', TERM: 'xterm-256color' },
|
|
97
|
+
});
|
|
98
|
+
const castContent = [header, ...events.map((e) => JSON.stringify(e))].join('\n');
|
|
99
|
+
await fs.writeFile(castPath, castContent, 'utf-8');
|
|
100
|
+
// Generate self-contained HTML player
|
|
101
|
+
const htmlFilename = `terminal-${Date.now()}.html`;
|
|
102
|
+
const htmlPath = path.join(outputDir, htmlFilename);
|
|
103
|
+
const escapedOutput = output
|
|
104
|
+
.replace(/&/g, '&')
|
|
105
|
+
.replace(/</g, '<')
|
|
106
|
+
.replace(/>/g, '>');
|
|
107
|
+
const html = `<!DOCTYPE html>
|
|
108
|
+
<html><head>
|
|
109
|
+
<meta charset="UTF-8">
|
|
110
|
+
<title>Terminal: ${command} ${args.join(' ')}</title>
|
|
111
|
+
<style>
|
|
112
|
+
body { margin: 0; background: #1a1a2e; color: #e0e0e0; font-family: monospace; }
|
|
113
|
+
.header { padding: 12px 20px; background: #16213e; border-bottom: 1px solid #333; font-size: 13px; }
|
|
114
|
+
.header span { color: #888; }
|
|
115
|
+
pre { padding: 20px; font-size: 13px; line-height: 1.5; white-space: pre-wrap; word-wrap: break-word; overflow-x: auto; }
|
|
116
|
+
.ansi-red { color: #ff6b6b; }
|
|
117
|
+
.ansi-green { color: #51cf66; }
|
|
118
|
+
.ansi-yellow { color: #ffd43b; }
|
|
119
|
+
</style>
|
|
120
|
+
</head><body>
|
|
121
|
+
<div class="header">
|
|
122
|
+
<strong>$ ${command} ${args.join(' ')}</strong>
|
|
123
|
+
<span> · ${duration.toFixed(1)}s · ${lines.length} lines</span>
|
|
124
|
+
</div>
|
|
125
|
+
<pre>${escapedOutput}</pre>
|
|
126
|
+
</body></html>`;
|
|
127
|
+
await fs.writeFile(htmlPath, html, 'utf-8');
|
|
128
|
+
return { castPath, htmlPath, lines: lines.length, duration };
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=evidence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evidence.js","sourceRoot":"","sources":["../../src/daemon/evidence.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Daemon Entry Point
|
|
4
|
+
*
|
|
5
|
+
* This is the process that gets forked by the CLI.
|
|
6
|
+
* It generates a token, picks a random port, starts the HTTP server,
|
|
7
|
+
* writes the state file, and waits for commands.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/daemon/index.ts"],"names":[],"mappings":";AAEA;;;;;;GAMG"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Daemon Entry Point
|
|
4
|
+
*
|
|
5
|
+
* This is the process that gets forked by the CLI.
|
|
6
|
+
* It generates a token, picks a random port, starts the HTTP server,
|
|
7
|
+
* writes the state file, and waits for commands.
|
|
8
|
+
*/
|
|
9
|
+
import * as crypto from 'crypto';
|
|
10
|
+
import { startServer } from './server.js';
|
|
11
|
+
import { removeDaemonState, releaseLock, writeDaemonState } from './stateFile.js';
|
|
12
|
+
import { DAEMON_PORT_MAX, DAEMON_PORT_MIN } from './types.js';
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Parse CLI Arguments
|
|
15
|
+
// ============================================================================
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
function getArg(name) {
|
|
18
|
+
const idx = args.indexOf(name);
|
|
19
|
+
return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : undefined;
|
|
20
|
+
}
|
|
21
|
+
const url = getArg('--url') ?? 'http://localhost:3000';
|
|
22
|
+
const projectRoot = getArg('--project-root') ?? process.cwd();
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Startup
|
|
25
|
+
// ============================================================================
|
|
26
|
+
function randomPort() {
|
|
27
|
+
return DAEMON_PORT_MIN + Math.floor(Math.random() * (DAEMON_PORT_MAX - DAEMON_PORT_MIN));
|
|
28
|
+
}
|
|
29
|
+
async function main() {
|
|
30
|
+
const token = crypto.randomBytes(16).toString('hex');
|
|
31
|
+
const maxRetries = 5;
|
|
32
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
33
|
+
const port = randomPort();
|
|
34
|
+
try {
|
|
35
|
+
await startServer({
|
|
36
|
+
port,
|
|
37
|
+
token,
|
|
38
|
+
url,
|
|
39
|
+
onShutdown: () => {
|
|
40
|
+
console.error('[Daemon] Cleaning up state...');
|
|
41
|
+
removeDaemonState(projectRoot);
|
|
42
|
+
releaseLock(projectRoot);
|
|
43
|
+
process.exit(0);
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
// Write state file so the CLI can find us
|
|
47
|
+
writeDaemonState(projectRoot, {
|
|
48
|
+
pid: process.pid,
|
|
49
|
+
port,
|
|
50
|
+
token,
|
|
51
|
+
startedAt: new Date().toISOString(),
|
|
52
|
+
url,
|
|
53
|
+
lastActivity: new Date().toISOString(),
|
|
54
|
+
});
|
|
55
|
+
console.error(`[Daemon] Started on port ${port} (PID: ${process.pid})`);
|
|
56
|
+
console.error(`[Daemon] Target URL: ${url}`);
|
|
57
|
+
console.error(`[Daemon] State file: ${projectRoot}/.sweetlink/daemon.json`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
62
|
+
if (message.includes('in use') && attempt < maxRetries - 1) {
|
|
63
|
+
console.error(`[Daemon] Port ${port} in use, retrying...`);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
throw new Error(`Failed to find available port after ${maxRetries} attempts`);
|
|
70
|
+
}
|
|
71
|
+
// Handle graceful shutdown signals
|
|
72
|
+
process.on('SIGTERM', () => {
|
|
73
|
+
console.error('[Daemon] Received SIGTERM');
|
|
74
|
+
removeDaemonState(projectRoot);
|
|
75
|
+
releaseLock(projectRoot);
|
|
76
|
+
process.exit(0);
|
|
77
|
+
});
|
|
78
|
+
process.on('SIGINT', () => {
|
|
79
|
+
console.error('[Daemon] Received SIGINT');
|
|
80
|
+
removeDaemonState(projectRoot);
|
|
81
|
+
releaseLock(projectRoot);
|
|
82
|
+
process.exit(0);
|
|
83
|
+
});
|
|
84
|
+
main().catch((error) => {
|
|
85
|
+
console.error('[Daemon] Fatal error:', error);
|
|
86
|
+
removeDaemonState(projectRoot);
|
|
87
|
+
releaseLock(projectRoot);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
});
|
|
90
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/daemon/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page Event Listeners
|
|
3
|
+
*
|
|
4
|
+
* Sets up always-on capture of console, network, and dialog events
|
|
5
|
+
* from the daemon's persistent Playwright page into ring buffers.
|
|
6
|
+
*/
|
|
7
|
+
type Page = import('playwright').Page;
|
|
8
|
+
import { RingBuffer } from './ringBuffer.js';
|
|
9
|
+
export interface ConsoleEntry {
|
|
10
|
+
timestamp: number;
|
|
11
|
+
level: string;
|
|
12
|
+
message: string;
|
|
13
|
+
location?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface NetworkEntry {
|
|
16
|
+
timestamp: number;
|
|
17
|
+
method: string;
|
|
18
|
+
url: string;
|
|
19
|
+
status: number;
|
|
20
|
+
duration: number;
|
|
21
|
+
contentType?: string;
|
|
22
|
+
size?: number;
|
|
23
|
+
}
|
|
24
|
+
export interface DialogEntry {
|
|
25
|
+
timestamp: number;
|
|
26
|
+
type: string;
|
|
27
|
+
message: string;
|
|
28
|
+
defaultValue?: string;
|
|
29
|
+
}
|
|
30
|
+
export declare const consoleBuffer: RingBuffer<ConsoleEntry>;
|
|
31
|
+
export declare const networkBuffer: RingBuffer<NetworkEntry>;
|
|
32
|
+
export declare const dialogBuffer: RingBuffer<DialogEntry>;
|
|
33
|
+
/**
|
|
34
|
+
* Install event listeners on the daemon page.
|
|
35
|
+
* Safe to call multiple times — only installs once.
|
|
36
|
+
*/
|
|
37
|
+
export declare function installListeners(page: Page): void;
|
|
38
|
+
/**
|
|
39
|
+
* Get error count from console buffer.
|
|
40
|
+
*/
|
|
41
|
+
export declare function getErrorCount(): number;
|
|
42
|
+
/**
|
|
43
|
+
* Get warning count from console buffer.
|
|
44
|
+
*/
|
|
45
|
+
export declare function getWarningCount(): number;
|
|
46
|
+
/**
|
|
47
|
+
* Format console entries as human-readable text.
|
|
48
|
+
*/
|
|
49
|
+
export declare function formatConsoleEntries(entries: ConsoleEntry[]): string;
|
|
50
|
+
/**
|
|
51
|
+
* Format network entries as human-readable text.
|
|
52
|
+
*/
|
|
53
|
+
export declare function formatNetworkEntries(entries: NetworkEntry[]): string;
|
|
54
|
+
export {};
|
|
55
|
+
//# sourceMappingURL=listeners.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listeners.d.ts","sourceRoot":"","sources":["../../src/daemon/listeners.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,KAAK,IAAI,GAAG,OAAO,YAAY,EAAE,IAAI,CAAC;AAEtC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAM7C,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAMD,eAAO,MAAM,aAAa,0BAAuC,CAAC;AAClE,eAAO,MAAM,aAAa,0BAAuC,CAAC;AAClE,eAAO,MAAM,YAAY,yBAAsC,CAAC;AAWhE;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAmEjD;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CAWpE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CAapE"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page Event Listeners
|
|
3
|
+
*
|
|
4
|
+
* Sets up always-on capture of console, network, and dialog events
|
|
5
|
+
* from the daemon's persistent Playwright page into ring buffers.
|
|
6
|
+
*/
|
|
7
|
+
import { RingBuffer } from './ringBuffer.js';
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Buffers (50K entries each)
|
|
10
|
+
// ============================================================================
|
|
11
|
+
export const consoleBuffer = new RingBuffer(50_000);
|
|
12
|
+
export const networkBuffer = new RingBuffer(50_000);
|
|
13
|
+
export const dialogBuffer = new RingBuffer(50_000);
|
|
14
|
+
// Track pending requests for duration calculation
|
|
15
|
+
const pendingRequests = new Map();
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Setup
|
|
18
|
+
// ============================================================================
|
|
19
|
+
let listenersInstalled = false;
|
|
20
|
+
/**
|
|
21
|
+
* Install event listeners on the daemon page.
|
|
22
|
+
* Safe to call multiple times — only installs once.
|
|
23
|
+
*/
|
|
24
|
+
export function installListeners(page) {
|
|
25
|
+
if (listenersInstalled)
|
|
26
|
+
return;
|
|
27
|
+
listenersInstalled = true;
|
|
28
|
+
// Console events
|
|
29
|
+
page.on('console', (msg) => {
|
|
30
|
+
consoleBuffer.push({
|
|
31
|
+
timestamp: Date.now(),
|
|
32
|
+
level: msg.type(),
|
|
33
|
+
message: msg.text(),
|
|
34
|
+
location: msg.location()
|
|
35
|
+
? `${msg.location().url}:${msg.location().lineNumber}`
|
|
36
|
+
: undefined,
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
// Network events
|
|
40
|
+
page.on('request', (request) => {
|
|
41
|
+
pendingRequests.set(request.url(), {
|
|
42
|
+
startTime: Date.now(),
|
|
43
|
+
method: request.method(),
|
|
44
|
+
url: request.url(),
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
page.on('response', (response) => {
|
|
48
|
+
const pending = pendingRequests.get(response.url());
|
|
49
|
+
const startTime = pending?.startTime ?? Date.now();
|
|
50
|
+
pendingRequests.delete(response.url());
|
|
51
|
+
networkBuffer.push({
|
|
52
|
+
timestamp: Date.now(),
|
|
53
|
+
method: pending?.method ?? 'GET',
|
|
54
|
+
url: response.url(),
|
|
55
|
+
status: response.status(),
|
|
56
|
+
duration: Date.now() - startTime,
|
|
57
|
+
contentType: response.headers()['content-type'],
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
page.on('requestfailed', (request) => {
|
|
61
|
+
const pending = pendingRequests.get(request.url());
|
|
62
|
+
const startTime = pending?.startTime ?? Date.now();
|
|
63
|
+
pendingRequests.delete(request.url());
|
|
64
|
+
networkBuffer.push({
|
|
65
|
+
timestamp: Date.now(),
|
|
66
|
+
method: pending?.method ?? request.method(),
|
|
67
|
+
url: request.url(),
|
|
68
|
+
status: 0,
|
|
69
|
+
duration: Date.now() - startTime,
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
// Dialog events (auto-dismiss)
|
|
73
|
+
page.on('dialog', async (dialog) => {
|
|
74
|
+
dialogBuffer.push({
|
|
75
|
+
timestamp: Date.now(),
|
|
76
|
+
type: dialog.type(),
|
|
77
|
+
message: dialog.message(),
|
|
78
|
+
defaultValue: dialog.defaultValue() || undefined,
|
|
79
|
+
});
|
|
80
|
+
// Auto-dismiss to prevent blocking
|
|
81
|
+
await dialog.dismiss().catch(() => { });
|
|
82
|
+
});
|
|
83
|
+
console.error('[Daemon] Event listeners installed (console, network, dialog)');
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get error count from console buffer.
|
|
87
|
+
*/
|
|
88
|
+
export function getErrorCount() {
|
|
89
|
+
return consoleBuffer.filter((e) => e.level === 'error').length;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Get warning count from console buffer.
|
|
93
|
+
*/
|
|
94
|
+
export function getWarningCount() {
|
|
95
|
+
return consoleBuffer.filter((e) => e.level === 'warning').length;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Format console entries as human-readable text.
|
|
99
|
+
*/
|
|
100
|
+
export function formatConsoleEntries(entries) {
|
|
101
|
+
if (entries.length === 0)
|
|
102
|
+
return '(no console messages)';
|
|
103
|
+
return entries
|
|
104
|
+
.map((e) => {
|
|
105
|
+
const time = new Date(e.timestamp).toISOString().slice(11, 19);
|
|
106
|
+
const levelTag = e.level.toUpperCase().padEnd(7);
|
|
107
|
+
const location = e.location ? ` (${e.location})` : '';
|
|
108
|
+
return `[${time}] ${levelTag} ${e.message}${location}`;
|
|
109
|
+
})
|
|
110
|
+
.join('\n');
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Format network entries as human-readable text.
|
|
114
|
+
*/
|
|
115
|
+
export function formatNetworkEntries(entries) {
|
|
116
|
+
if (entries.length === 0)
|
|
117
|
+
return '(no network requests)';
|
|
118
|
+
return entries
|
|
119
|
+
.map((e) => {
|
|
120
|
+
const time = new Date(e.timestamp).toISOString().slice(11, 19);
|
|
121
|
+
const status = e.status === 0 ? 'FAIL' : String(e.status);
|
|
122
|
+
const duration = `${e.duration}ms`;
|
|
123
|
+
// Truncate long URLs
|
|
124
|
+
const url = e.url.length > 80 ? e.url.substring(0, 77) + '...' : e.url;
|
|
125
|
+
return `[${time}] ${status} ${e.method} ${url} ${duration}`;
|
|
126
|
+
})
|
|
127
|
+
.join('\n');
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=listeners.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listeners.js","sourceRoot":"","sources":["../../src/daemon/listeners.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Recording
|
|
3
|
+
*
|
|
4
|
+
* Records browser sessions as video with synchronized action timeline.
|
|
5
|
+
* Uses Playwright's built-in video recording (Chromium screencast).
|
|
6
|
+
*/
|
|
7
|
+
import type { SessionManifest } from './session.js';
|
|
8
|
+
type Page = import('playwright').Page;
|
|
9
|
+
/**
|
|
10
|
+
* Start recording a session.
|
|
11
|
+
* Creates a new browser context with video recording enabled.
|
|
12
|
+
*/
|
|
13
|
+
export declare function startRecording(page: Page, outputDir: string): Promise<{
|
|
14
|
+
sessionId: string;
|
|
15
|
+
}>;
|
|
16
|
+
/**
|
|
17
|
+
* Log an action during recording.
|
|
18
|
+
* Captures a screenshot at the moment of the action.
|
|
19
|
+
*/
|
|
20
|
+
export declare function logAction(action: string, args: string[], page: Page, boundingBox?: {
|
|
21
|
+
x: number;
|
|
22
|
+
y: number;
|
|
23
|
+
width: number;
|
|
24
|
+
height: number;
|
|
25
|
+
}): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Stop recording and generate session manifest.
|
|
28
|
+
*/
|
|
29
|
+
export declare function stopRecording(): Promise<SessionManifest | null>;
|
|
30
|
+
/**
|
|
31
|
+
* Check if recording is in progress.
|
|
32
|
+
*/
|
|
33
|
+
export declare function isRecording(): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Get recording status info.
|
|
36
|
+
*/
|
|
37
|
+
export declare function getRecordingStatus(): {
|
|
38
|
+
recording: boolean;
|
|
39
|
+
sessionId: string | null;
|
|
40
|
+
duration: number | null;
|
|
41
|
+
actionCount: number;
|
|
42
|
+
};
|
|
43
|
+
export {};
|
|
44
|
+
//# sourceMappingURL=recording.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recording.d.ts","sourceRoot":"","sources":["../../src/daemon/recording.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAe,MAAM,cAAc,CAAC;AAGjE,KAAK,IAAI,GAAG,OAAO,YAAY,EAAE,IAAI,CAAC;AAmBtC;;;GAGG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAsBhC;AAED;;;GAGG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,EAAE,IAAI,EACV,WAAW,CAAC,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACpE,OAAO,CAAC,IAAI,CAAC,CAuBf;AAED;;GAEG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CA2CrE;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,OAAO,CAErC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI;IACpC,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;CACrB,CAOA"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Recording
|
|
3
|
+
*
|
|
4
|
+
* Records browser sessions as video with synchronized action timeline.
|
|
5
|
+
* Uses Playwright's built-in video recording (Chromium screencast).
|
|
6
|
+
*/
|
|
7
|
+
import { promises as fs } from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// State
|
|
11
|
+
// ============================================================================
|
|
12
|
+
let recording = false;
|
|
13
|
+
let sessionId = null;
|
|
14
|
+
let startedAt = null;
|
|
15
|
+
let actions = [];
|
|
16
|
+
let screenshotPaths = [];
|
|
17
|
+
let videoPath = null;
|
|
18
|
+
let recordingContext = null;
|
|
19
|
+
let recordingPage = null;
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// Public API
|
|
22
|
+
// ============================================================================
|
|
23
|
+
/**
|
|
24
|
+
* Start recording a session.
|
|
25
|
+
* Creates a new browser context with video recording enabled.
|
|
26
|
+
*/
|
|
27
|
+
export async function startRecording(page, outputDir) {
|
|
28
|
+
if (recording) {
|
|
29
|
+
throw new Error('Recording already in progress. Stop it first.');
|
|
30
|
+
}
|
|
31
|
+
sessionId = `session-${Date.now()}`;
|
|
32
|
+
const videoDir = path.join(outputDir, sessionId);
|
|
33
|
+
await fs.mkdir(videoDir, { recursive: true });
|
|
34
|
+
// We can't add video to an existing context, so we use the existing page
|
|
35
|
+
// and capture screenshots at each action instead of true video.
|
|
36
|
+
// True video requires creating context with recordVideo option.
|
|
37
|
+
recording = true;
|
|
38
|
+
startedAt = Date.now();
|
|
39
|
+
actions = [];
|
|
40
|
+
screenshotPaths = [];
|
|
41
|
+
recordingContext = null;
|
|
42
|
+
recordingPage = page;
|
|
43
|
+
videoPath = videoDir;
|
|
44
|
+
console.error(`[Daemon] Recording started: ${sessionId}`);
|
|
45
|
+
return { sessionId };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Log an action during recording.
|
|
49
|
+
* Captures a screenshot at the moment of the action.
|
|
50
|
+
*/
|
|
51
|
+
export async function logAction(action, args, page, boundingBox) {
|
|
52
|
+
if (!recording || !startedAt || !videoPath)
|
|
53
|
+
return;
|
|
54
|
+
const timestamp = (Date.now() - startedAt) / 1000;
|
|
55
|
+
const screenshotName = `action-${actions.length}.png`;
|
|
56
|
+
const screenshotPath = path.join(videoPath, screenshotName);
|
|
57
|
+
try {
|
|
58
|
+
const buffer = await page.screenshot();
|
|
59
|
+
await fs.writeFile(screenshotPath, buffer);
|
|
60
|
+
screenshotPaths.push(screenshotPath);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// Screenshot may fail if page is navigating
|
|
64
|
+
}
|
|
65
|
+
actions.push({
|
|
66
|
+
timestamp,
|
|
67
|
+
action,
|
|
68
|
+
args,
|
|
69
|
+
duration: 0,
|
|
70
|
+
boundingBox,
|
|
71
|
+
screenshot: screenshotName,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Stop recording and generate session manifest.
|
|
76
|
+
*/
|
|
77
|
+
export async function stopRecording() {
|
|
78
|
+
if (!recording || !sessionId || !startedAt || !videoPath) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const endedAt = Date.now();
|
|
82
|
+
const duration = (endedAt - startedAt) / 1000;
|
|
83
|
+
// Close recording context if we created one
|
|
84
|
+
if (recordingContext) {
|
|
85
|
+
try {
|
|
86
|
+
await recordingContext.close();
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Context may already be closed
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const manifest = {
|
|
93
|
+
sessionId,
|
|
94
|
+
startedAt: new Date(startedAt).toISOString(),
|
|
95
|
+
endedAt: new Date(endedAt).toISOString(),
|
|
96
|
+
duration,
|
|
97
|
+
commands: actions,
|
|
98
|
+
screenshots: screenshotPaths.map((p) => path.basename(p)),
|
|
99
|
+
errors: { console: 0, network: 0, server: 0 },
|
|
100
|
+
};
|
|
101
|
+
// Write manifest
|
|
102
|
+
const manifestPath = path.join(videoPath, 'sweetlink-session.json');
|
|
103
|
+
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
104
|
+
console.error(`[Daemon] Recording stopped: ${manifestPath}`);
|
|
105
|
+
// Reset state
|
|
106
|
+
recording = false;
|
|
107
|
+
sessionId = null;
|
|
108
|
+
startedAt = null;
|
|
109
|
+
actions = [];
|
|
110
|
+
screenshotPaths = [];
|
|
111
|
+
videoPath = null;
|
|
112
|
+
recordingContext = null;
|
|
113
|
+
recordingPage = null;
|
|
114
|
+
return manifest;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Check if recording is in progress.
|
|
118
|
+
*/
|
|
119
|
+
export function isRecording() {
|
|
120
|
+
return recording;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Get recording status info.
|
|
124
|
+
*/
|
|
125
|
+
export function getRecordingStatus() {
|
|
126
|
+
return {
|
|
127
|
+
recording,
|
|
128
|
+
sessionId,
|
|
129
|
+
duration: startedAt ? (Date.now() - startedAt) / 1000 : null,
|
|
130
|
+
actionCount: actions.length,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=recording.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recording.js","sourceRoot":"","sources":["../../src/daemon/recording.ts"],"names":[],"mappings":"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"}
|