@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.
Files changed (78) hide show
  1. package/README.md +91 -12
  2. package/claude-skills/screenshot/SKILL.md +121 -20
  3. package/dist/cli/outputSchemas.d.ts +16 -0
  4. package/dist/cli/outputSchemas.d.ts.map +1 -1
  5. package/dist/cli/outputSchemas.js +33 -0
  6. package/dist/cli/outputSchemas.js.map +1 -1
  7. package/dist/cli/sweetlink-dev.js +0 -0
  8. package/dist/cli/sweetlink.js +347 -11
  9. package/dist/cli/sweetlink.js.map +1 -1
  10. package/dist/daemon/browser.d.ts +51 -0
  11. package/dist/daemon/browser.d.ts.map +1 -0
  12. package/dist/daemon/browser.js +153 -0
  13. package/dist/daemon/browser.js.map +1 -0
  14. package/dist/daemon/client.d.ts +32 -0
  15. package/dist/daemon/client.d.ts.map +1 -0
  16. package/dist/daemon/client.js +133 -0
  17. package/dist/daemon/client.js.map +1 -0
  18. package/dist/daemon/cursor.d.ts +15 -0
  19. package/dist/daemon/cursor.d.ts.map +1 -0
  20. package/dist/daemon/cursor.js +76 -0
  21. package/dist/daemon/cursor.js.map +1 -0
  22. package/dist/daemon/devices.d.ts +39 -0
  23. package/dist/daemon/devices.d.ts.map +1 -0
  24. package/dist/daemon/devices.js +101 -0
  25. package/dist/daemon/devices.js.map +1 -0
  26. package/dist/daemon/diff.d.ts +20 -0
  27. package/dist/daemon/diff.d.ts.map +1 -0
  28. package/dist/daemon/diff.js +181 -0
  29. package/dist/daemon/diff.js.map +1 -0
  30. package/dist/daemon/evidence.d.ts +29 -0
  31. package/dist/daemon/evidence.d.ts.map +1 -0
  32. package/dist/daemon/evidence.js +130 -0
  33. package/dist/daemon/evidence.js.map +1 -0
  34. package/dist/daemon/index.d.ts +10 -0
  35. package/dist/daemon/index.d.ts.map +1 -0
  36. package/dist/daemon/index.js +90 -0
  37. package/dist/daemon/index.js.map +1 -0
  38. package/dist/daemon/listeners.d.ts +55 -0
  39. package/dist/daemon/listeners.d.ts.map +1 -0
  40. package/dist/daemon/listeners.js +129 -0
  41. package/dist/daemon/listeners.js.map +1 -0
  42. package/dist/daemon/recording.d.ts +44 -0
  43. package/dist/daemon/recording.d.ts.map +1 -0
  44. package/dist/daemon/recording.js +133 -0
  45. package/dist/daemon/recording.js.map +1 -0
  46. package/dist/daemon/refs.d.ts +70 -0
  47. package/dist/daemon/refs.d.ts.map +1 -0
  48. package/dist/daemon/refs.js +185 -0
  49. package/dist/daemon/refs.js.map +1 -0
  50. package/dist/daemon/ringBuffer.d.ts +26 -0
  51. package/dist/daemon/ringBuffer.d.ts.map +1 -0
  52. package/dist/daemon/ringBuffer.js +54 -0
  53. package/dist/daemon/ringBuffer.js.map +1 -0
  54. package/dist/daemon/server.d.ts +23 -0
  55. package/dist/daemon/server.d.ts.map +1 -0
  56. package/dist/daemon/server.js +508 -0
  57. package/dist/daemon/server.js.map +1 -0
  58. package/dist/daemon/session.d.ts +41 -0
  59. package/dist/daemon/session.d.ts.map +1 -0
  60. package/dist/daemon/session.js +8 -0
  61. package/dist/daemon/session.js.map +1 -0
  62. package/dist/daemon/stateFile.d.ts +49 -0
  63. package/dist/daemon/stateFile.d.ts.map +1 -0
  64. package/dist/daemon/stateFile.js +162 -0
  65. package/dist/daemon/stateFile.js.map +1 -0
  66. package/dist/daemon/types.d.ts +72 -0
  67. package/dist/daemon/types.d.ts.map +1 -0
  68. package/dist/daemon/types.js +28 -0
  69. package/dist/daemon/types.js.map +1 -0
  70. package/dist/daemon/viewer.d.ts +33 -0
  71. package/dist/daemon/viewer.d.ts.map +1 -0
  72. package/dist/daemon/viewer.js +226 -0
  73. package/dist/daemon/viewer.js.map +1 -0
  74. package/dist/daemon/visualDiff.d.ts +34 -0
  75. package/dist/daemon/visualDiff.d.ts.map +1 -0
  76. package/dist/daemon/visualDiff.js +80 -0
  77. package/dist/daemon/visualDiff.js.map +1 -0
  78. 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, '&amp;')
105
+ .replace(/</g, '&lt;')
106
+ .replace(/>/g, '&gt;');
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> &middot; ${duration.toFixed(1)}s &middot; ${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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"}