autokap 1.0.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 (93) hide show
  1. package/assets/chrome/ios-statusbar-comparison-reference.jpg +0 -0
  2. package/assets/chrome/ios-statusbar-dark-reference.jpg +0 -0
  3. package/assets/chrome/ios-statusbar-light-reference.jpg +0 -0
  4. package/assets/devices/ipad-pro-11-m4.json +52 -0
  5. package/assets/devices/iphone-16-pro.json +53 -0
  6. package/assets/devices/macbook-air-13.json +45 -0
  7. package/assets/frames/MacBook Air 13.svg +242 -0
  8. package/assets/frames/Status bar - iPhone.png +0 -0
  9. Menu bar- iPad.png +0 -0
  10. package/assets/frames/iPad Pro M4 11_.png +0 -0
  11. package/assets/frames/iPhone 16 Pro.png +0 -0
  12. package/assets/icons/Cellular Connection.svg +3 -0
  13. package/assets/icons/Union.svg +6 -0
  14. package/assets/icons/Wifi.svg +3 -0
  15. package/assets/icons/battery.svg +5 -0
  16. package/assets/icons/battery_charging.svg +8 -0
  17. package/assets/skill/SKILL.md +575 -0
  18. package/dist/abort.d.ts +5 -0
  19. package/dist/abort.js +44 -0
  20. package/dist/agent.d.ts +142 -0
  21. package/dist/agent.js +4504 -0
  22. package/dist/browser-bar.d.ts +40 -0
  23. package/dist/browser-bar.js +147 -0
  24. package/dist/browser-pool.d.ts +34 -0
  25. package/dist/browser-pool.js +122 -0
  26. package/dist/browser.d.ts +279 -0
  27. package/dist/browser.js +2902 -0
  28. package/dist/cli-utils.d.ts +25 -0
  29. package/dist/cli-utils.js +80 -0
  30. package/dist/cli.d.ts +4 -0
  31. package/dist/cli.js +365 -0
  32. package/dist/clip-orchestrator.d.ts +148 -0
  33. package/dist/clip-orchestrator.js +950 -0
  34. package/dist/clip-postprocess.d.ts +42 -0
  35. package/dist/clip-postprocess.js +192 -0
  36. package/dist/cookie-dismiss.d.ts +5 -0
  37. package/dist/cookie-dismiss.js +172 -0
  38. package/dist/credential-templates.d.ts +5 -0
  39. package/dist/credential-templates.js +60 -0
  40. package/dist/element-capture.d.ts +53 -0
  41. package/dist/element-capture.js +766 -0
  42. package/dist/hybrid-navigator.d.ts +138 -0
  43. package/dist/hybrid-navigator.js +468 -0
  44. package/dist/index.d.ts +15 -0
  45. package/dist/index.js +11 -0
  46. package/dist/llm-usage.d.ts +17 -0
  47. package/dist/llm-usage.js +45 -0
  48. package/dist/logger.d.ts +46 -0
  49. package/dist/logger.js +79 -0
  50. package/dist/mockup-html.d.ts +119 -0
  51. package/dist/mockup-html.js +253 -0
  52. package/dist/mockup.d.ts +94 -0
  53. package/dist/mockup.js +604 -0
  54. package/dist/mouse-animation.d.ts +46 -0
  55. package/dist/mouse-animation.js +100 -0
  56. package/dist/overlay-utils.d.ts +14 -0
  57. package/dist/overlay-utils.js +13 -0
  58. package/dist/posthog.d.ts +4 -0
  59. package/dist/posthog.js +26 -0
  60. package/dist/prompt-cache.d.ts +10 -0
  61. package/dist/prompt-cache.js +24 -0
  62. package/dist/prompts.d.ts +167 -0
  63. package/dist/prompts.js +1165 -0
  64. package/dist/security.d.ts +20 -0
  65. package/dist/security.js +569 -0
  66. package/dist/session-profile.d.ts +86 -0
  67. package/dist/session-profile.js +1471 -0
  68. package/dist/sf-pro-fonts.d.ts +4 -0
  69. package/dist/sf-pro-fonts.js +7 -0
  70. package/dist/status-bar-l10n.d.ts +14 -0
  71. package/dist/status-bar-l10n.js +177 -0
  72. package/dist/status-bar.d.ts +44 -0
  73. package/dist/status-bar.js +336 -0
  74. package/dist/tools.d.ts +4 -0
  75. package/dist/tools.js +578 -0
  76. package/dist/types.d.ts +796 -0
  77. package/dist/types.js +2 -0
  78. package/dist/video-agent.d.ts +143 -0
  79. package/dist/video-agent.js +4783 -0
  80. package/dist/video-observation.d.ts +36 -0
  81. package/dist/video-observation.js +192 -0
  82. package/dist/video-planner.d.ts +12 -0
  83. package/dist/video-planner.js +500 -0
  84. package/dist/video-prompts.d.ts +37 -0
  85. package/dist/video-prompts.js +554 -0
  86. package/dist/video-tools.d.ts +3 -0
  87. package/dist/video-tools.js +59 -0
  88. package/dist/video-variant-state.d.ts +29 -0
  89. package/dist/video-variant-state.js +80 -0
  90. package/dist/vision-model.d.ts +17 -0
  91. package/dist/vision-model.js +74 -0
  92. package/package.json +165 -0
  93. package/readme.md +61 -0
@@ -0,0 +1,42 @@
1
+ import type { ClipOptions } from './types.js';
2
+ /**
3
+ * Resolve the system ffmpeg binary path. Throws if not found.
4
+ */
5
+ export declare function ensureFfmpegAvailable(): Promise<string>;
6
+ /**
7
+ * Convert a WebM recording to an optimized GIF using a 2-pass palette approach.
8
+ * Pass 1: generate an optimal palette. Pass 2: encode with that palette.
9
+ */
10
+ export declare function convertToGif(webmPath: string, outputPath: string, opts?: {
11
+ fps?: number;
12
+ maxWidth?: number;
13
+ loop?: boolean;
14
+ }): Promise<void>;
15
+ /**
16
+ * Convert a WebM recording to an MP4 with web-optimized settings.
17
+ */
18
+ export declare function convertToMp4(webmPath: string, outputPath: string): Promise<void>;
19
+ /**
20
+ * Extract the first frame of a WebM as a PNG thumbnail.
21
+ */
22
+ export declare function extractThumbnail(webmPath: string, outputPath: string): Promise<void>;
23
+ /**
24
+ * Trim a recording: skip dead frames at the start and cap duration.
25
+ */
26
+ export declare function trimRecording(inputPath: string, outputPath: string, startSec?: number, maxDurationSec?: number): Promise<void>;
27
+ /**
28
+ * Get the duration of a media file in milliseconds.
29
+ */
30
+ export declare function getMediaDurationMs(filePath: string): Promise<number>;
31
+ export interface ClipPostProcessResult {
32
+ gifPath?: string;
33
+ mp4Path?: string;
34
+ thumbnailPath?: string;
35
+ durationMs: number;
36
+ fileSizeBytes?: number;
37
+ }
38
+ /**
39
+ * Full post-processing pipeline for a single clip recording:
40
+ * trim → GIF and/or MP4 → thumbnail → measure.
41
+ */
42
+ export declare function postProcessClipRecording(webmPath: string, outputDir: string, clipId: string, options?: ClipOptions): Promise<ClipPostProcessResult>;
@@ -0,0 +1,192 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { stat } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { promisify } from 'node:util';
5
+ const execFileAsync = promisify(execFile);
6
+ // ── Default values ──────────────────────────────────────────────────
7
+ const DEFAULT_GIF_FPS = 15;
8
+ const DEFAULT_GIF_MAX_WIDTH = 800;
9
+ const DEFAULT_MAX_DURATION_SEC = 8;
10
+ const DEFAULT_TRIM_START_SEC = 0.3;
11
+ // ── ffmpeg detection ────────────────────────────────────────────────
12
+ let cachedFfmpegPath = null;
13
+ /**
14
+ * Resolve the system ffmpeg binary path. Throws if not found.
15
+ */
16
+ export async function ensureFfmpegAvailable() {
17
+ if (cachedFfmpegPath)
18
+ return cachedFfmpegPath;
19
+ try {
20
+ const { stdout } = await execFileAsync('which', ['ffmpeg']);
21
+ const ffmpegPath = stdout.trim();
22
+ if (!ffmpegPath)
23
+ throw new Error('ffmpeg not found');
24
+ cachedFfmpegPath = ffmpegPath;
25
+ return ffmpegPath;
26
+ }
27
+ catch {
28
+ throw new Error('ffmpeg is required for clip post-processing but was not found on your system. ' +
29
+ 'Install it via: brew install ffmpeg (macOS) or apt install ffmpeg (Linux).');
30
+ }
31
+ }
32
+ // ── Conversion functions ────────────────────────────────────────────
33
+ /**
34
+ * Convert a WebM recording to an optimized GIF using a 2-pass palette approach.
35
+ * Pass 1: generate an optimal palette. Pass 2: encode with that palette.
36
+ */
37
+ export async function convertToGif(webmPath, outputPath, opts = {}) {
38
+ const ffmpeg = await ensureFfmpegAvailable();
39
+ const fps = opts.fps ?? DEFAULT_GIF_FPS;
40
+ const maxWidth = opts.maxWidth ?? DEFAULT_GIF_MAX_WIDTH;
41
+ const loopFlag = (opts.loop ?? true) ? '0' : '-1';
42
+ const paletteDir = path.dirname(outputPath);
43
+ const palettePath = path.join(paletteDir, `_palette_${Date.now()}.png`);
44
+ const scaleFilter = `fps=${fps},scale=${maxWidth}:-1:flags=lanczos`;
45
+ // Pass 1: Generate palette
46
+ await execFileAsync(ffmpeg, [
47
+ '-i', webmPath,
48
+ '-vf', `${scaleFilter},palettegen=stats_mode=diff`,
49
+ '-y', palettePath,
50
+ ]);
51
+ // Pass 2: Encode GIF with palette
52
+ try {
53
+ await execFileAsync(ffmpeg, [
54
+ '-i', webmPath,
55
+ '-i', palettePath,
56
+ '-lavfi', `${scaleFilter}[x];[x][1:v]paletteuse=dither=bayer:bayer_scale=3`,
57
+ '-loop', loopFlag,
58
+ '-y', outputPath,
59
+ ]);
60
+ }
61
+ finally {
62
+ // Clean up palette file
63
+ try {
64
+ await stat(palettePath).then(() => execFileAsync('rm', [palettePath]));
65
+ }
66
+ catch { /* ignore */ }
67
+ }
68
+ }
69
+ /**
70
+ * Convert a WebM recording to an MP4 with web-optimized settings.
71
+ */
72
+ export async function convertToMp4(webmPath, outputPath) {
73
+ const ffmpeg = await ensureFfmpegAvailable();
74
+ await execFileAsync(ffmpeg, [
75
+ '-i', webmPath,
76
+ '-c:v', 'libx264',
77
+ '-preset', 'slow',
78
+ '-crf', '22',
79
+ '-pix_fmt', 'yuv420p',
80
+ '-movflags', '+faststart',
81
+ '-an',
82
+ '-y', outputPath,
83
+ ]);
84
+ }
85
+ /**
86
+ * Extract the first frame of a WebM as a PNG thumbnail.
87
+ */
88
+ export async function extractThumbnail(webmPath, outputPath) {
89
+ const ffmpeg = await ensureFfmpegAvailable();
90
+ await execFileAsync(ffmpeg, [
91
+ '-i', webmPath,
92
+ '-frames:v', '1',
93
+ '-y', outputPath,
94
+ ]);
95
+ }
96
+ /**
97
+ * Trim a recording: skip dead frames at the start and cap duration.
98
+ */
99
+ export async function trimRecording(inputPath, outputPath, startSec = DEFAULT_TRIM_START_SEC, maxDurationSec = DEFAULT_MAX_DURATION_SEC) {
100
+ const ffmpeg = await ensureFfmpegAvailable();
101
+ await execFileAsync(ffmpeg, [
102
+ '-ss', String(startSec),
103
+ '-i', inputPath,
104
+ '-t', String(maxDurationSec),
105
+ '-c', 'copy',
106
+ '-y', outputPath,
107
+ ]);
108
+ }
109
+ /**
110
+ * Get the duration of a media file in milliseconds.
111
+ */
112
+ export async function getMediaDurationMs(filePath) {
113
+ const ffmpeg = await ensureFfmpegAvailable();
114
+ const ffprobe = ffmpeg.replace(/ffmpeg$/, 'ffprobe');
115
+ const { stdout } = await execFileAsync(ffprobe, [
116
+ '-v', 'error',
117
+ '-show_entries', 'format=duration',
118
+ '-of', 'default=noprint_wrappers=1:nokey=1',
119
+ filePath,
120
+ ]);
121
+ return Math.round(parseFloat(stdout.trim()) * 1000);
122
+ }
123
+ /**
124
+ * Freeze the last frame of a video for `durationSec` additional seconds.
125
+ * Uses ffmpeg's tpad filter to hold the final frame in place.
126
+ */
127
+ async function freezeLastFrame(inputPath, outputPath, durationSec) {
128
+ const ffmpeg = await ensureFfmpegAvailable();
129
+ const stopDurationMs = Math.round(durationSec * 1000);
130
+ await execFileAsync(ffmpeg, [
131
+ '-i', inputPath,
132
+ '-vf', `tpad=stop_mode=clone:stop_duration=${stopDurationMs}ms`,
133
+ '-c:v', 'libvpx-vp9',
134
+ '-crf', '30',
135
+ '-b:v', '0',
136
+ '-an',
137
+ '-y', outputPath,
138
+ ]);
139
+ }
140
+ /**
141
+ * Full post-processing pipeline for a single clip recording:
142
+ * trim → GIF and/or MP4 → thumbnail → measure.
143
+ */
144
+ export async function postProcessClipRecording(webmPath, outputDir, clipId, options = {}) {
145
+ const maxDuration = options.maxDurationSec ?? DEFAULT_MAX_DURATION_SEC;
146
+ // Step 1: Trim the recording — cut the setup phase (page load, overlay dismiss)
147
+ const trimStart = options.trimStartSec ?? DEFAULT_TRIM_START_SEC;
148
+ const trimmedPath = path.join(outputDir, `${clipId}_trimmed.webm`);
149
+ await trimRecording(webmPath, trimmedPath, trimStart, maxDuration);
150
+ // Step 1b: Optionally freeze the last frame for a pause before looping
151
+ const holdSec = Math.min(Math.max(options.holdLastFrameSec ?? 0, 0), 10);
152
+ let sourcePath = trimmedPath;
153
+ if (holdSec > 0) {
154
+ const paddedPath = path.join(outputDir, `${clipId}_padded.webm`);
155
+ await freezeLastFrame(trimmedPath, paddedPath, holdSec);
156
+ sourcePath = paddedPath;
157
+ }
158
+ const durationMs = await getMediaDurationMs(sourcePath);
159
+ const result = { durationMs };
160
+ // Step 2: Always persist both a GIF and an MP4.
161
+ // The GIF remains useful for delivery and embeds, while the MP4 is the
162
+ // high-fidelity source for Studio playback and downstream re-renders.
163
+ const gifPath = path.join(outputDir, `${clipId}.gif`);
164
+ await convertToGif(sourcePath, gifPath, {
165
+ fps: options.gifFps,
166
+ maxWidth: options.gifMaxWidth,
167
+ loop: options.loop,
168
+ });
169
+ result.gifPath = gifPath;
170
+ const gifStat = await stat(gifPath);
171
+ result.fileSizeBytes = gifStat.size;
172
+ const mp4Path = path.join(outputDir, `${clipId}.mp4`);
173
+ await convertToMp4(sourcePath, mp4Path);
174
+ result.mp4Path = mp4Path;
175
+ // Step 3: Extract thumbnail
176
+ const thumbnailPath = path.join(outputDir, `${clipId}_thumb.png`);
177
+ await extractThumbnail(sourcePath, thumbnailPath);
178
+ result.thumbnailPath = thumbnailPath;
179
+ // Clean up intermediate files
180
+ try {
181
+ await execFileAsync('rm', [trimmedPath]);
182
+ }
183
+ catch { /* ignore */ }
184
+ if (sourcePath !== trimmedPath) {
185
+ try {
186
+ await execFileAsync('rm', [sourcePath]);
187
+ }
188
+ catch { /* ignore */ }
189
+ }
190
+ return result;
191
+ }
192
+ //# sourceMappingURL=clip-postprocess.js.map
@@ -0,0 +1,5 @@
1
+ import type { Page } from 'playwright';
2
+ export declare function dismissCookiesAndWidgets(page: Page): Promise<{
3
+ dismissed: boolean;
4
+ method: string | null;
5
+ }>;
@@ -0,0 +1,172 @@
1
+ import { logger } from './logger.js';
2
+ // Known CMP (Consent Management Platform) accept buttons
3
+ const CMP_SELECTORS = [
4
+ // OneTrust
5
+ '#onetrust-accept-btn-handler',
6
+ '.onetrust-close-btn-handler',
7
+ // Cookiebot
8
+ '#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll',
9
+ '#CybotCookiebotDialogBodyButtonAccept',
10
+ // Quantcast
11
+ '.qc-cmp2-summary-buttons button[mode="primary"]',
12
+ // Didomi
13
+ '#didomi-notice-agree-button',
14
+ // Axeptio
15
+ '.axeptio_btn_acceptAll',
16
+ // Iubenda
17
+ '.iubenda-cs-accept-btn',
18
+ // HubSpot
19
+ '#hs-eu-confirmation-button',
20
+ // Cookie Notice plugin
21
+ '.cookie-notice-container .cn-set-cookie',
22
+ // Cookie Consent (Osano)
23
+ '.cc-compliance .cc-btn.cc-allow',
24
+ '.cc-btn.cc-dismiss',
25
+ // Klaro
26
+ '.klaro .cm-btn-accept',
27
+ // Complianz
28
+ '#cmplz-cookiebanner-container .cmplz-btn.cmplz-accept',
29
+ // Generic patterns
30
+ '[data-cookiefirst-action="accept"]',
31
+ '[data-testid="cookie-accept"]',
32
+ '[data-testid="accept-cookies"]',
33
+ ];
34
+ // Multilingual accept button text patterns
35
+ const ACCEPT_PATTERNS = [
36
+ /^accept\s*(all|cookies)?$/i,
37
+ /^i\s*agree$/i,
38
+ /^agree(\s*(&|and)\s*close)?$/i,
39
+ /^allow\s*(all|cookies)?$/i,
40
+ /^got\s*it$/i,
41
+ /^ok$/i,
42
+ /^continue$/i,
43
+ // French
44
+ /^accepter\s*(tout|les cookies)?$/i,
45
+ /^tout\s*accepter$/i,
46
+ /^j.accepte$/i,
47
+ /^continuer$/i,
48
+ // German
49
+ /^(alle\s*)?akzeptieren$/i,
50
+ /^alle\s*zulassen$/i,
51
+ /^zustimmen$/i,
52
+ // Spanish
53
+ /^aceptar\s*(todo|cookies)?$/i,
54
+ // Portuguese
55
+ /^aceitar\s*(tudo|cookies)?$/i,
56
+ // Italian
57
+ /^accetta\s*(tutto|i cookie)?$/i,
58
+ // Dutch
59
+ /^(alles\s*)?accepteren$/i,
60
+ ];
61
+ // Elements to always hide via CSS injection
62
+ const HIDE_SELECTORS = [
63
+ // Chat widgets
64
+ '#intercom-container', '.intercom-lightweight-app', '#intercom-frame',
65
+ '#drift-widget', '#drift-frame-controller',
66
+ '.crisp-client', '#crisp-chatbox',
67
+ '#launcher', '#webWidget', // Zendesk
68
+ '#hubspot-messages-iframe-container',
69
+ '#tidio-chat', '#tidio-chat-iframe',
70
+ '.fb-customerchat', '.fb_dialog', // Facebook Messenger
71
+ '#chat-widget-container',
72
+ // Feedback widgets
73
+ '#_hj_feedback_container', '._hj-widget-container', // Hotjar
74
+ '#usersnap-button', '#usersnap-panel',
75
+ // Generic
76
+ '[class*="chat-widget"]', '[class*="chatWidget"]',
77
+ '[id*="chat-widget"]', '[id*="chatWidget"]',
78
+ // Cookie banners (CSS fallback)
79
+ '[class*="cookie-banner"]', '[class*="cookie-consent"]',
80
+ '[id*="cookie-banner"]', '[id*="cookie-consent"]',
81
+ '[class*="cookieBanner"]', '[class*="cookieConsent"]',
82
+ '[id*="cookieBanner"]', '[id*="cookieConsent"]',
83
+ '[class*="gdpr"]', '[id*="gdpr"]',
84
+ ];
85
+ export async function dismissCookiesAndWidgets(page) {
86
+ // Strategy 1: Click known CMP buttons
87
+ for (const selector of CMP_SELECTORS) {
88
+ try {
89
+ const button = page.locator(selector).first();
90
+ if (await button.isVisible({ timeout: 500 })) {
91
+ await button.click({ timeout: 2000 });
92
+ logger.info(`Cookie dismissed via CMP selector: ${selector}`);
93
+ await page.waitForTimeout(500);
94
+ await injectHideCSS(page);
95
+ return { dismissed: true, method: `cmp:${selector}` };
96
+ }
97
+ }
98
+ catch {
99
+ // Selector not found or not clickable, try next
100
+ }
101
+ }
102
+ // Strategy 2: Find accept buttons by text in cookie-like containers
103
+ try {
104
+ const dismissed = await page.evaluate((patterns) => {
105
+ const regexPatterns = patterns.map(p => {
106
+ const match = p.match(/^\/(.*)\/([gimsuy]*)$/);
107
+ return match ? new RegExp(match[1], match[2]) : new RegExp(p, 'i');
108
+ });
109
+ // Find all visible buttons and links
110
+ const candidates = document.querySelectorAll('button, a, [role="button"], input[type="submit"]');
111
+ for (const el of candidates) {
112
+ const htmlEl = el;
113
+ const text = (htmlEl.textContent || htmlEl.getAttribute('value') || '').trim();
114
+ if (!text || text.length > 40)
115
+ continue;
116
+ // Check if text matches an accept pattern
117
+ const matches = regexPatterns.some(rx => rx.test(text));
118
+ if (!matches)
119
+ continue;
120
+ // Check if it's inside a cookie/consent container.
121
+ // Only match containers that explicitly identify as cookie/consent/GDPR
122
+ // via ID, class, role, or aria-label. Do NOT match on position:fixed/sticky
123
+ // alone — that's too broad and can match unrelated sticky CTAs.
124
+ let parent = htmlEl;
125
+ let isCookieContainer = false;
126
+ for (let depth = 0; depth < 8 && parent; depth++) {
127
+ const id = (parent.id || '').toLowerCase();
128
+ const className = (parent.className || '').toString().toLowerCase();
129
+ const role = (parent.getAttribute('role') || '').toLowerCase();
130
+ const ariaLabel = (parent.getAttribute('aria-label') || '').toLowerCase();
131
+ const combinedAttrs = `${id} ${className} ${ariaLabel}`;
132
+ if (combinedAttrs.includes('cookie') || combinedAttrs.includes('consent') ||
133
+ combinedAttrs.includes('gdpr') || combinedAttrs.includes('privacy') ||
134
+ combinedAttrs.includes('banner') ||
135
+ ((role === 'dialog' || role === 'alertdialog') && combinedAttrs.includes('co'))) {
136
+ isCookieContainer = true;
137
+ break;
138
+ }
139
+ parent = parent.parentElement;
140
+ }
141
+ if (isCookieContainer) {
142
+ htmlEl.click();
143
+ return text;
144
+ }
145
+ }
146
+ return null;
147
+ }, ACCEPT_PATTERNS.map(rx => rx.toString()));
148
+ if (dismissed) {
149
+ logger.info(`Cookie dismissed via text match: "${dismissed}"`);
150
+ await page.waitForTimeout(500);
151
+ await injectHideCSS(page);
152
+ return { dismissed: true, method: `text:${dismissed}` };
153
+ }
154
+ }
155
+ catch {
156
+ // Text-based search failed
157
+ }
158
+ // Strategy 3: Always inject CSS to hide known overlays and widgets
159
+ await injectHideCSS(page);
160
+ return { dismissed: false, method: null };
161
+ }
162
+ async function injectHideCSS(page) {
163
+ try {
164
+ await page.addStyleTag({
165
+ content: HIDE_SELECTORS.map(s => `${s} { display: none !important; visibility: hidden !important; pointer-events: none !important; }`).join('\n'),
166
+ });
167
+ }
168
+ catch {
169
+ // Page might have navigated away
170
+ }
171
+ }
172
+ //# sourceMappingURL=cookie-dismiss.js.map
@@ -0,0 +1,5 @@
1
+ import type { LoginCredentials } from "./types.js";
2
+ export declare function resolveCredentialTemplates(value: string | undefined, credentials?: LoginCredentials): string | undefined;
3
+ export declare function ensureNoCredentialTemplate(field: string, value: string | undefined): void;
4
+ export declare function sanitizeCredentialParams(params: Record<string, unknown>, credentials?: LoginCredentials): Record<string, unknown>;
5
+ export declare function resolveActionCredentialArgs(action: string, args: Record<string, unknown>, credentials?: LoginCredentials): Record<string, unknown>;
@@ -0,0 +1,60 @@
1
+ const TEMPLATE_RE = /\{\{credential\.(loginUrl|email|password)\}\}/g;
2
+ export function resolveCredentialTemplates(value, credentials) {
3
+ if (!value)
4
+ return value;
5
+ const replacements = {
6
+ loginUrl: credentials?.loginUrl,
7
+ email: credentials?.email,
8
+ password: credentials?.password,
9
+ };
10
+ return value.replace(TEMPLATE_RE, (match, key) => {
11
+ const replacement = replacements[key];
12
+ return typeof replacement === "string" ? replacement : match;
13
+ });
14
+ }
15
+ export function ensureNoCredentialTemplate(field, value) {
16
+ if (value && TEMPLATE_RE.test(value)) {
17
+ TEMPLATE_RE.lastIndex = 0;
18
+ throw new Error(`Missing credential value for ${field}`);
19
+ }
20
+ TEMPLATE_RE.lastIndex = 0;
21
+ }
22
+ function sanitizeCredentialString(value, credentials) {
23
+ if (!credentials)
24
+ return value;
25
+ if (credentials.password && value === credentials.password) {
26
+ return "{{credential.password}}";
27
+ }
28
+ if (credentials.email && value === credentials.email) {
29
+ return "{{credential.email}}";
30
+ }
31
+ if (credentials.loginUrl && value === credentials.loginUrl) {
32
+ return "{{credential.loginUrl}}";
33
+ }
34
+ return value;
35
+ }
36
+ export function sanitizeCredentialParams(params, credentials) {
37
+ const sanitized = {};
38
+ for (const [key, value] of Object.entries(params)) {
39
+ sanitized[key] =
40
+ typeof value === "string"
41
+ ? sanitizeCredentialString(value, credentials)
42
+ : value;
43
+ }
44
+ return sanitized;
45
+ }
46
+ export function resolveActionCredentialArgs(action, args, credentials) {
47
+ if (!credentials)
48
+ return args;
49
+ const resolved = { ...args };
50
+ if (action === "type_text" && typeof resolved.text === "string") {
51
+ resolved.text = resolveCredentialTemplates(resolved.text, credentials);
52
+ ensureNoCredentialTemplate("type_text.text", resolved.text);
53
+ }
54
+ if (action === "navigate_to" && typeof resolved.url === "string") {
55
+ resolved.url = resolveCredentialTemplates(resolved.url, credentials);
56
+ ensureNoCredentialTemplate("navigate_to.url", resolved.url);
57
+ }
58
+ return resolved;
59
+ }
60
+ //# sourceMappingURL=credential-templates.js.map
@@ -0,0 +1,53 @@
1
+ import { Browser } from './browser.js';
2
+ import type { IsolatedElement, ElementCaptureResult, OutscaleConfig, SelectorValidationResult } from './types.js';
3
+ interface SearchQueryHistoryEntry {
4
+ candidateLines: string[];
5
+ domSignature: string;
6
+ selectors: string[];
7
+ hasTransientSelectors: boolean;
8
+ /** Best candidate bounding box for coordinate-based fallback capture. */
9
+ bestRegion?: {
10
+ x: number;
11
+ y: number;
12
+ width: number;
13
+ height: number;
14
+ } | null;
15
+ }
16
+ export declare function isLooseElementCaptureRejectionReason(reason: string | null | undefined): boolean;
17
+ export declare function isTagOnlyStructuralSelector(selector: string): boolean;
18
+ export declare function shouldBlockUngroundedStructuralSelector(params: {
19
+ selector: string;
20
+ groundedSelectors: Iterable<string>;
21
+ verifierRejectedAsTooLoose: boolean;
22
+ }): boolean;
23
+ export declare function outscaleAddsPadding(outscale: OutscaleConfig | undefined): boolean;
24
+ export declare function buildVerificationOutscale(outscale: OutscaleConfig | undefined): OutscaleConfig;
25
+ export declare function shouldAcceptDomCorroboratedSelector(params: {
26
+ looseFailureCount: number;
27
+ verifierRejectedAsTooLoose: boolean;
28
+ validation: SelectorValidationResult;
29
+ viewport: {
30
+ width: number;
31
+ height: number;
32
+ } | null;
33
+ observedAsInteractive: boolean;
34
+ directQueryCount: number;
35
+ containerQueryCount: number;
36
+ }): boolean;
37
+ export declare function computeElementCaptureDomSignature(params: {
38
+ currentUrl: string;
39
+ interactiveElements: Array<Pick<Awaited<ReturnType<Browser['getInteractiveElements']>>[number], 'index' | 'tag' | 'role' | 'text' | 'selector' | 'visibilityState'>>;
40
+ }): string;
41
+ export declare function shouldAllowSearchRefresh(params: {
42
+ cached: SearchQueryHistoryEntry | undefined;
43
+ domSignature: string;
44
+ lastFailedTransientSelector: string | null;
45
+ }): boolean;
46
+ interface ElementCaptureOptions {
47
+ abortSignal?: AbortSignal;
48
+ distinctId?: string;
49
+ fallbackModel?: string;
50
+ uploadImage?: (buffer: Buffer, mimeType: 'image/jpeg' | 'image/png') => Promise<string>;
51
+ }
52
+ export declare function captureIsolatedElement(browser: Browser, element: IsolatedElement, apiKey: string, model: string, options?: ElementCaptureOptions): Promise<ElementCaptureResult>;
53
+ export {};