@spark-apps/quickpeek 1.2.3 → 1.2.5

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.
@@ -0,0 +1,647 @@
1
+ import { Page } from 'playwright';
2
+
3
+ /**
4
+ * Configuration types and defaults for QuickPeek
5
+ */
6
+ declare const VERSION = "1.2.5";
7
+ declare const CONFIG_FILE = "quickpeek.config.json";
8
+ type UserTier = 'free' | 'pro' | 'elite';
9
+ /** The one dimension knob users see: wide (16:9) or short (9:16). */
10
+ type VideoSize = 'wide' | 'short';
11
+ /** Narration pace, mapped to an Edge TTS rate. */
12
+ type VoiceRate = 'slow' | 'normal' | 'fast';
13
+ type VoiceGender = 'female' | 'male';
14
+ /**
15
+ * What each size means in pixels. `zoom` shrinks the CSS viewport so a
16
+ * portrait page hits its mobile breakpoints and text stays legible; wide
17
+ * records at desktop layout untouched.
18
+ */
19
+ declare const SIZE_PRESETS: Record<VideoSize, {
20
+ width: number;
21
+ height: number;
22
+ zoom: number;
23
+ }>;
24
+ /**
25
+ * The three named paces as multipliers of the voice's natural rate.
26
+ *
27
+ * A multiplier rather than the edge-tts "-20%" string it used to be, so the
28
+ * named pace can COMPOSE with the profile's numeric `audio.speed` instead of
29
+ * excluding it - see the note in tts/generate.ts, where excluding it made
30
+ * `audio.speed` dead code and took the profiles' narration pace with it.
31
+ */
32
+ declare const RATE_FACTOR: Record<VoiceRate, number>;
33
+ /**
34
+ * Set as video.outro when the closing card is already a plan step.
35
+ *
36
+ * Lives here rather than with the phase that sets it because compose reads it
37
+ * too: a run that ends on a credits card must not also stamp the free-tier
38
+ * end text over that card.
39
+ */
40
+ declare const NO_CLIP_OUTRO = "@card-step";
41
+ interface Config {
42
+ lang?: string;
43
+ plan?: {
44
+ maxSteps: number;
45
+ };
46
+ browser?: {
47
+ extension?: string;
48
+ };
49
+ profile?: 'wide' | 'short';
50
+ video: {
51
+ size: VideoSize;
52
+ width: number;
53
+ height: number;
54
+ format: string;
55
+ preset: 'ultrafast' | 'superfast' | 'veryfast' | 'faster' | 'fast' | 'medium' | 'slow' | 'slower' | 'veryslow';
56
+ quality: number;
57
+ fps: number;
58
+ cursor: boolean;
59
+ highlight: false | 'outline' | 'full';
60
+ fadeIn: number;
61
+ fadeOut: number;
62
+ transitions: 'none' | 'fade' | 'blend';
63
+ contrast: number;
64
+ tempo: number;
65
+ stepDelay: number;
66
+ watermark?: {
67
+ name: string;
68
+ iconPath?: string;
69
+ position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
70
+ opacity?: number;
71
+ };
72
+ outro?: string;
73
+ outroCard?: {
74
+ /** The app the video is about - the card's headline. */
75
+ subject: {
76
+ name: string;
77
+ tagline?: string;
78
+ domain?: string;
79
+ iconPath?: string;
80
+ /** Play the mark rather than show a still of it. See OutroSubject. */
81
+ animatedMark?: 'vidlet';
82
+ };
83
+ /** Tools that made the video, shown small in the corner. */
84
+ credits?: Array<{
85
+ name: string;
86
+ role: string;
87
+ iconPath?: string;
88
+ }>;
89
+ promo?: {
90
+ code: string;
91
+ percent: number;
92
+ site?: string;
93
+ };
94
+ /**
95
+ * Say "free to start" on the closing card. Default true.
96
+ *
97
+ * Almost every product this records is freemium, and the free tier is
98
+ * the one closing line that never expires the way a discount code does,
99
+ * so it is opt-out rather than opt-in. Set false for a paid-only product.
100
+ */
101
+ freeTier?: boolean;
102
+ seconds?: number;
103
+ };
104
+ zoom?: number;
105
+ maxSpeedup?: number;
106
+ autoPan?: boolean;
107
+ introCard?: boolean;
108
+ outroCardEnabled?: boolean;
109
+ introCardSeconds?: number;
110
+ maxSilenceSecs?: number;
111
+ fitLimitSecs?: number;
112
+ captions?: Partial<CaptionStyle>;
113
+ hwaccel?: 'auto' | 'off';
114
+ captionsBurn?: boolean;
115
+ };
116
+ audio?: {
117
+ voice?: string;
118
+ gender?: VoiceGender;
119
+ rate?: VoiceRate;
120
+ format: string;
121
+ bitrate: string;
122
+ speed?: number;
123
+ voiceover?: boolean;
124
+ voiceClipsDir?: string;
125
+ ambience?: boolean;
126
+ transcribe?: boolean;
127
+ };
128
+ music?: {
129
+ path: string;
130
+ volume: number;
131
+ /**
132
+ * Which bundled CC0 bed to score with when no `path` is given.
133
+ *
134
+ * 'corporate' | 'calm' | 'lofi' - see assets/music/manifest.json. An unknown
135
+ * mood falls back to the default rather than dropping the music.
136
+ */
137
+ mood?: string;
138
+ /**
139
+ * Hold the music back until the closing card.
140
+ *
141
+ * A track written FOR the product is not a bed: ducked under a narrator
142
+ * for a minute it is inaudible, and it is the thing people are meant to
143
+ * hear. With this on, the demo plays dry and the song fades in exactly as
144
+ * the closing card arrives, with the rest of the video silent underneath
145
+ * the voice.
146
+ *
147
+ * ON by default. Music under narration was the single most disliked thing
148
+ * about the output: ducked it is mud, unducked it fights the voice, and
149
+ * either way it makes a 17 second demo sound like a stock video. Set
150
+ * false in quickpeek.config.json to score the whole video.
151
+ */
152
+ outroOnly?: boolean;
153
+ };
154
+ timing: {
155
+ minStepDuration: number;
156
+ /**
157
+ * Ceiling on a step's hold, in ms. Undefined means none.
158
+ *
159
+ * A demo is filmed in real time, so its holds ARE the recording time: a
160
+ * 75s video costs 75s to shoot no matter what the encoder does. Capping
161
+ * them is the only thing that makes a draft quick, and it is the one
162
+ * trade a draft can afford, since the question a draft answers is whether
163
+ * the demo clicks the right things in the right order.
164
+ */
165
+ maxStepDuration?: number;
166
+ /**
167
+ * How long an action waits for the page to paint, in ms.
168
+ *
169
+ * The recording floor is not the holds, it is the page loads: eleven
170
+ * navigations each waiting on content is most of a draft's runtime. A
171
+ * draft is allowed to film a half-drawn page; a deliverable is not.
172
+ */
173
+ paintBudget?: number;
174
+ };
175
+ output: {
176
+ dir: string;
177
+ };
178
+ }
179
+ interface CaptionWordStyle {
180
+ color: string;
181
+ size: number;
182
+ bold: boolean;
183
+ }
184
+ type CaptionPreset = 'karaoke' | 'hormozi' | 'shorts' | 'minimal';
185
+ type CaptionPosition = 'bottom' | 'center' | 'top';
186
+ interface CaptionStyle {
187
+ font: string;
188
+ outline: string;
189
+ outlineWidth: number;
190
+ shadow: number;
191
+ marginBottom: number;
192
+ preset: CaptionPreset;
193
+ position: CaptionPosition;
194
+ regular: CaptionWordStyle;
195
+ highlight: CaptionWordStyle;
196
+ /**
197
+ * Characters a caption line may reach before it wraps.
198
+ *
199
+ * A ceiling, not a target: the renderer narrows it further to whatever the
200
+ * frame actually fits at this font size. It exists because "fits the frame"
201
+ * and "reads at a glance on a phone" are different questions - a 9:16 short
202
+ * wants two short lines where a 16:9 tutorial is happy with one long one.
203
+ */
204
+ maxChars: number;
205
+ }
206
+ declare const DEFAULT_CONFIG: Config;
207
+ /** The two standard deliverables. Every generated demo is one or the other. */
208
+ type VideoProfileName = 'wide' | 'short';
209
+ /**
210
+ * Standard settings per deliverable, applied over the loaded config.
211
+ *
212
+ * wide: a 16:9 educational tutorial. Comprehensive, natural pace - no
213
+ * over-limit squeeze (fitLimitSecs 0), desktop layout (zoom 1.5), narration
214
+ * slightly brisker than the voice's default.
215
+ *
216
+ * short: a 9:16 quick-paced short. Covers every interesting detail even past
217
+ * a minute, then is squeezed toward 60s - but never beyond 1.7x, because past
218
+ * that the fix is a tighter plan, not a faster player.
219
+ */
220
+ declare const VIDEO_PROFILES: Record<VideoProfileName, {
221
+ video: Partial<Config['video']>;
222
+ audioSpeed: number;
223
+ voice?: string;
224
+ }>;
225
+ /** Overlay a standard profile on a loaded config. Explicit knobs win where set. */
226
+ declare function applyProfile(config: Config, profile: VideoProfileName): Config;
227
+ declare const DEFAULT_CAPTIONS: CaptionStyle;
228
+ /**
229
+ * Caption geometry that suits the canvas, expressed as fractions of it.
230
+ *
231
+ * The fixed defaults above were authored once and applied to every shape, and
232
+ * on the 1080x1920 canvas QuickPeek defaults to they are wrong in three ways
233
+ * at once: 56px is 5% of the frame width, which is a desktop subtitle rather
234
+ * than a social caption; 80px of bottom margin puts the words 4% up the frame,
235
+ * underneath the title, channel row and action rail every phone player draws
236
+ * over the bottom of a vertical video; and 28 characters at that size is a
237
+ * single long line the eye has to track rather than a short stack it can take
238
+ * in at a glance.
239
+ *
240
+ * Fractions rather than pixels so the same reasoning holds at 720x1280 or
241
+ * 1440x2560. The portrait numbers: text at 21% of frame height clears the
242
+ * player chrome on YouTube Shorts, TikTok and Reels while staying in the lower
243
+ * third, and ~9% of frame width is the size those platforms' own captions use.
244
+ */
245
+ declare function defaultCaptionsFor(width: number, height: number): CaptionStyle;
246
+
247
+ /**
248
+ * AI API client — Spark AI communication
249
+ * Handles token management, rate limiting, and retries
250
+ *
251
+ * Two transports:
252
+ * - Relay (default for the published CLI): quickpeek-web mints a short-lived
253
+ * per-user token from a registered email and proxies the chat server-side.
254
+ * No credential ships in the npm bundle.
255
+ * - Direct (dev/server only): when SPARK_AI_KEY is present in the runtime
256
+ * environment (never baked at build time), call Spark AI directly. This is
257
+ * the path quickpeek-web itself uses server-side.
258
+ */
259
+
260
+ interface RateLimitInfo {
261
+ used: number;
262
+ limit: number;
263
+ tier: UserTier;
264
+ resetAt?: string;
265
+ }
266
+ interface AIResponse {
267
+ success: true;
268
+ content: string;
269
+ limits: RateLimitInfo;
270
+ }
271
+ interface AIError {
272
+ success: false;
273
+ error: string;
274
+ isRateLimit?: boolean;
275
+ isAuthError?: boolean;
276
+ retryAfterSeconds?: number;
277
+ /** Upstream HTTP status, when the failure was an HTTP response. */
278
+ status?: number;
279
+ /** Host that produced the failure, so relay vs backend is distinguishable. */
280
+ host?: string;
281
+ /** Raw response body (truncated), for the error log. */
282
+ detail?: string;
283
+ }
284
+ type AIResult = AIResponse | AIError;
285
+ /** Called before each backoff so the CLI can explain a long pause. */
286
+ type RetryNotice = (info: {
287
+ attempt: number;
288
+ status: number;
289
+ delayMs: number;
290
+ host: string;
291
+ }) => void;
292
+ interface ChatOptions {
293
+ onRetry?: RetryNotice;
294
+ /**
295
+ * Backoff before each 5xx retry. Defaults to SERVER_BACKOFF_MS, which is
296
+ * sized to fit inside a serverless function budget — callAI runs inside the
297
+ * quickpeek-web routes as well as the CLI. Interactive callers should pass
298
+ * CLI_BACKOFF_MS, which can afford to outwait the backend's closed window.
299
+ */
300
+ backoffMs?: number[];
301
+ /**
302
+ * Completion-token budget to ask the backend for. Omitted, the backend falls
303
+ * back to the app's own default, which is 300 — far too small for the JSON
304
+ * this CLI asks for. See PLAN_MAX_TOKENS.
305
+ */
306
+ maxTokens?: number;
307
+ }
308
+ /**
309
+ * Every AI call this CLI makes is structured generation: a JSON plan with one
310
+ * entry per script line, or one caption per step. The backend defaults to a
311
+ * 300-token completion budget sized for a two-sentence chat reply, and a
312
+ * 14-line plan measured ~300 tokens of JSON on its own, so the default
313
+ * truncated every real run mid-array (salvaged silently by the JSON repair
314
+ * path) and returned nothing at all once the model's own reasoning ate the
315
+ * budget first. Ask for a budget the output actually fits in.
316
+ */
317
+ declare const PLAN_MAX_TOKENS = 1500;
318
+ /** Short enough to stay well inside a serverless maxDuration. */
319
+ declare const SERVER_BACKOFF_MS: number[];
320
+ /**
321
+ * Long enough to outlast the AI backend's ~45-60s closed window (measured
322
+ * 16 Aug 2026). Only safe where a human is waiting on a spinner.
323
+ */
324
+ declare const CLI_BACKOFF_MS: number[];
325
+ declare const RELAY_BASE: string;
326
+ /**
327
+ * Relay mode: exchange the signed-in email for a short-lived relay token (cached)
328
+ */
329
+ declare function getOrRefreshRelayToken(email: string): Promise<string>;
330
+ /**
331
+ * Direct Spark AI call using the runtime SPARK_AI_KEY (dev + server-side use).
332
+ */
333
+ declare function callAI(systemPrompt: string, userPrompt: string, tier?: UserTier, opts?: ChatOptions): Promise<AIResult>;
334
+ /**
335
+ * Relayed Spark AI call for the published CLI: authenticates with the user's
336
+ * registered email via quickpeek-web, which holds the real key server-side
337
+ * and enforces per-user daily limits.
338
+ */
339
+ declare function callAIViaRelay(systemPrompt: string, userPrompt: string, email: string, tier?: UserTier, opts?: ChatOptions): Promise<AIResult>;
340
+ /**
341
+ * Parse AI-generated plan JSON from response text
342
+ */
343
+ declare function parseAIPlanResponse(content: string): {
344
+ success: true;
345
+ plan: {
346
+ title?: string;
347
+ description?: string;
348
+ steps: Array<{
349
+ id?: number;
350
+ action?: string;
351
+ caption?: string;
352
+ target?: string;
353
+ value?: string;
354
+ }>;
355
+ };
356
+ } | {
357
+ success: false;
358
+ error: string;
359
+ };
360
+ /**
361
+ * Normalize a URL by adding https:// and stripping www prefix
362
+ */
363
+ declare function normalizeUrl(input: string): string;
364
+
365
+ /**
366
+ * Shared types for QuickPeek core
367
+ */
368
+ /** Action type suggested for an element based on its type */
369
+ type SuggestedAction = 'click' | 'type' | 'upload' | 'drag' | 'select' | 'check';
370
+ /** Element type discovered on a page */
371
+ type ElementType = 'button' | 'input' | 'link' | 'text' | 'file' | 'select' | 'range' | 'checkbox';
372
+ /** Discovered element from page crawl */
373
+ interface ElementInfo {
374
+ /** Element type */
375
+ type: ElementType | string;
376
+ /** Display text/label for the element */
377
+ text: string;
378
+ /** CSS selector to target the element */
379
+ selector: string;
380
+ /** Suggested action for this element */
381
+ action?: SuggestedAction;
382
+ /** Simplified target name (just the text, for AI use) */
383
+ target?: string;
384
+ /** Same-origin destination, for links the planner may navigate to. */
385
+ href?: string | undefined;
386
+ }
387
+ /** Highlight mode for UI elements */
388
+ type HighlightMode = false | 'outline' | 'full';
389
+ /** Step in a demo plan */
390
+ interface Step {
391
+ id: number | string;
392
+ action: string;
393
+ target?: string;
394
+ value?: string;
395
+ caption?: string;
396
+ description?: string;
397
+ duration?: number;
398
+ zoom?: number | string;
399
+ partial?: string;
400
+ transition?: 'fade' | 'blend';
401
+ highlight?: HighlightMode;
402
+ mediaFile?: string;
403
+ mediaType?: 'image' | 'video';
404
+ /**
405
+ * A small JPEG data URL of the file `value` names, attached only when a plan
406
+ * is handed to the browser editor, which cannot read a path on disk. Never
407
+ * written to a plan file; see plan-thumbs.ts.
408
+ */
409
+ thumb?: string;
410
+ /**
411
+ * Speak this step's caption but do not burn it.
412
+ *
413
+ * For a step whose frame is already type - a title or closing card - where
414
+ * a burned caption lands on top of the words it is repeating.
415
+ */
416
+ spokenOnly?: boolean;
417
+ }
418
+ /** Demo plan structure */
419
+ interface Plan {
420
+ url: string;
421
+ title: string;
422
+ description: string;
423
+ lang?: string;
424
+ steps: Step[];
425
+ }
426
+
427
+ /**
428
+ * Page crawling and interactive element extraction
429
+ */
430
+
431
+ /**
432
+ * Wait until the loading placeholders are gone, or give up and film anyway.
433
+ *
434
+ * waitForDomStable counts elements, and a skeleton shell is exactly the state
435
+ * where that count has already stopped moving: the panels are on screen, they
436
+ * are just grey. So the recorder started step 1 over spinners and the opening
437
+ * seconds of the demo are the app pretending to be itself.
438
+ *
439
+ * Three ways out, and none of them can hang a run: no placeholders (the
440
+ * common case, one evaluate and no wait at all), a count that never budges -
441
+ * which is furniture, a permanent element whose class happens to say spinner,
442
+ * not loading state - or the cap.
443
+ */
444
+ declare function waitForPlaceholdersGone(page: Page, cap?: number): Promise<void>;
445
+ /**
446
+ * Navigate and give the page a bounded chance to finish rendering.
447
+ *
448
+ * `networkidle` as the goto condition strands on pages that poll or stream —
449
+ * a signed-in dashboard rarely goes idle, so goto times out with the page
450
+ * already painted. Land on domcontentloaded, which such pages do reach, then
451
+ * wait for idle only as a best effort, then for the DOM to hold still, and
452
+ * finally for the skeletons to clear - a shell whose element count has
453
+ * settled is still not a page anyone wants filmed.
454
+ */
455
+ declare function gotoSettled(page: Page, url: string, timeout?: number): Promise<void>;
456
+
457
+ /**
458
+ * A selector matching one id.
459
+ *
460
+ * HTML ids are far more permissive than CSS identifiers: `description-(optional)`
461
+ * is a perfectly legal id, but `#description-(optional)` is invalid CSS and
462
+ * Playwright rejects it, so the step silently finds nothing. Anything that is
463
+ * not identifier-safe gets the attribute form instead.
464
+ */
465
+ declare function idSelector(id: string): string;
466
+ /**
467
+ * A selector matching one aria-label.
468
+ *
469
+ * An icon-only button carries its label in `aria-label`, and Playwright's
470
+ * `text=` engine and `:has-text()` both match rendered text — never attributes.
471
+ * A label borrowed from the attribute therefore has to be emitted as one too,
472
+ * or the selector matches nothing at all.
473
+ */
474
+ declare function ariaLabelSelector(label: string): string;
475
+ /**
476
+ * Crawl a page and extract interactive elements with smart selectors
477
+ * Results are cached in-memory for a few minutes to avoid duplicate browser launches
478
+ * @param url - URL to crawl
479
+ * @param task - Optional progress reporter (CLI uses ora spinners, web can omit)
480
+ */
481
+ declare function crawlPage(url: string, task?: (text: string) => {
482
+ succeed: (text?: string) => void;
483
+ }, storageStatePath?: string, viewport?: {
484
+ width: number;
485
+ height: number;
486
+ }): Promise<{
487
+ title: string;
488
+ elements: ElementInfo[];
489
+ scrollable: number;
490
+ }>;
491
+
492
+ /**
493
+ * Utility functions for QuickPeek
494
+ */
495
+ /**
496
+ * Get full language name from ISO code
497
+ */
498
+ declare function getLanguageName(lang?: string): string | null;
499
+
500
+ /**
501
+ * Convert rgba() or rgb() color string to hex
502
+ */
503
+ declare function rgbaToHex(color: string): string;
504
+
505
+ /**
506
+ * Parse partial field "start-end" into trim values
507
+ * e.g., "0-3.5" -> { trimStart: 0, trimEnd: 3.5 }
508
+ * e.g., "2-" -> { trimStart: 2, trimEnd: undefined }
509
+ */
510
+ declare function parsePartial(partial: string | undefined): {
511
+ trimStart?: number;
512
+ trimEnd?: number;
513
+ };
514
+ /**
515
+ * Extract a message string from an unknown error value
516
+ */
517
+ declare function getErrorMessage(err: unknown): string;
518
+ declare function deStock(text: string): string;
519
+ /**
520
+ * Strip dashes a caption should never carry.
521
+ *
522
+ * House rule: no em or en dashes in anything QuickPeek burns or speaks. On
523
+ * screen a long dash at portrait sizes is hard to tell from a hyphen and
524
+ * eats width a short has none of; spoken, edge-tts reads it as a hard stop
525
+ * mid-sentence. A comma does the same job in both places.
526
+ *
527
+ * A dash BETWEEN digits is a range ("2-3x", "10-20%"), so it becomes a
528
+ * hyphen rather than a comma.
529
+ */
530
+ declare function stripLongDashes(text: string): string;
531
+ /**
532
+ * Convert text to Title Case, keeping prepositions/articles lowercase
533
+ * First word is always capitalized
534
+ */
535
+ declare function toTitleCase(text: string): string;
536
+ /**
537
+ * Run `task` for every index in [0, count) with at most `size` in flight.
538
+ *
539
+ * Workers pull from a shared cursor rather than being handed a fixed slice, so
540
+ * one slow item does not leave its worker's remaining share unstarted while
541
+ * others sit idle. Tasks are expected to record their own results by index;
542
+ * nothing is collected here, because the callers that need ordering want to
543
+ * assemble it themselves.
544
+ */
545
+ declare function runPooled(count: number, size: number, task: (index: number) => Promise<void>): Promise<void>;
546
+
547
+ /**
548
+ * AI prompts for demo plan generation
549
+ */
550
+
551
+ declare function buildSystemPrompt(maxSteps: number, profile?: 'wide' | 'short'): string;
552
+ declare function buildUserPrompt(url: string, title: string, elements: ElementInfo[], langName: string | null, description: string | undefined, maxSteps: number, scrollable?: number): string;
553
+
554
+ type Tier = 'free' | 'pro' | 'elite';
555
+ interface SparkSubscription {
556
+ payment_type: 'subscription' | 'one_time';
557
+ status: 'active' | 'trialing' | 'past_due' | 'canceled' | 'lifetime';
558
+ is_lifetime: boolean;
559
+ current_period_end: string;
560
+ cancel_at_period_end: boolean;
561
+ billing_interval: 'monthly' | 'yearly' | 'one_time';
562
+ price_id: string;
563
+ created_at: string;
564
+ }
565
+ interface SparkTrial {
566
+ is_expired: boolean;
567
+ days_remaining: number;
568
+ uses_remaining: number;
569
+ max_days: number;
570
+ max_uses: number;
571
+ }
572
+ interface SparkStatus {
573
+ subscription: SparkSubscription | null;
574
+ verified: boolean;
575
+ registered: boolean;
576
+ usage_count: number;
577
+ trial: SparkTrial | null;
578
+ message?: string;
579
+ }
580
+ declare const getStatus: (email: string) => Promise<SparkStatus>;
581
+ declare function getStatusCached(email: string): Promise<SparkStatus>;
582
+ declare function getTier(d: SparkStatus): Tier;
583
+ declare const isPaid: (d: SparkStatus) => boolean;
584
+ declare const hasTrialRemaining: (d: SparkStatus) => boolean;
585
+ declare const showPaywall: (d: SparkStatus) => boolean;
586
+ declare const isCanceling: (d: SparkStatus) => boolean;
587
+ /** Resolve tier directly from an email — the most common consumer call. Falls back to 'free' on error. */
588
+ declare function getTierByEmail(email: string): Promise<Tier>;
589
+ /** Check whether an email is verified. Falls back to false on error. */
590
+ declare function isVerified(email: string): Promise<boolean>;
591
+ declare function pricingUrl(opts?: {
592
+ email?: string;
593
+ returnUrl?: string;
594
+ }): string;
595
+
596
+ /**
597
+ * Shared constants for page scraping and element filtering
598
+ * Used by both CLI and web versions
599
+ */
600
+ declare const EXCLUDED_LINK_PATTERNS: string[];
601
+ /** CSS selectors for interactive page elements */
602
+ declare const INTERACTIVE_SELECTORS: string[];
603
+ /** Check if link text or href matches an excluded pattern */
604
+ declare function isExcludedLink(text: string, href?: string): boolean;
605
+ /** Check if a link should be skipped during crawling */
606
+ declare function shouldSkipLink(text: string, href: string, currentPath: string): boolean;
607
+
608
+ /**
609
+ * Edge TTS voice selection.
610
+ *
611
+ * QuickPeek uses the kit's WIDE table: 68 languages with standard neural
612
+ * voices and en-GB for English. Breadth matters more than polish here,
613
+ * because a demo gets captioned for whatever audience asked for it. Male
614
+ * voices exist for the 14 major languages of the kit's MALE table; the rest
615
+ * fall back to the standard voice for that language rather than switching
616
+ * language just to keep the gender.
617
+ */
618
+
619
+ /**
620
+ * Asking for the multilingual voices by name rather than by Edge id.
621
+ *
622
+ * `audio.voice` takes an exact voice, which is precise and useless for
623
+ * choosing a SET: the multilingual voice differs per language, so an exact id
624
+ * pins the config to one language. This word resolves per language instead,
625
+ * so the same config narrates a demo in any of them.
626
+ */
627
+ declare const INTERNATIONAL_VOICE = "international";
628
+ /**
629
+ * The multilingual neural voice for a language, e.g. en-US-AvaMultilingualNeural.
630
+ *
631
+ * These read noticeably better than the standard neural voices and carry an
632
+ * accent across languages instead of switching person. The WIDE table is
633
+ * still the fallback: multilingual voices do not exist for every language,
634
+ * and dropping to the standard voice for one is better than no narration.
635
+ */
636
+ declare function internationalVoiceFor(lang: string, gender?: VoiceGender): string | null;
637
+ /** Whether narration exists for this language at all. */
638
+ declare function hasVoice(lang: string): boolean;
639
+ /**
640
+ * The Edge voice for a language + gender, falling back through WIDE_VOICES —
641
+ * or null for a language with no voice at all. Null keeps the deliberate
642
+ * captions-only fallback: English narration over foreign captions would be
643
+ * worse than silence.
644
+ */
645
+ declare function voiceFor(lang: string, gender?: VoiceGender): string | null;
646
+
647
+ export { getStatus as $, type AIError as A, type VideoSize as B, type CaptionStyle as C, DEFAULT_CAPTIONS as D, EXCLUDED_LINK_PATTERNS as E, type VoiceGender as F, applyProfile as G, type HighlightMode as H, INTERACTIVE_SELECTORS as I, ariaLabelSelector as J, buildSystemPrompt as K, buildUserPrompt as L, callAI as M, NO_CLIP_OUTRO as N, callAIViaRelay as O, PLAN_MAX_TOKENS as P, crawlPage as Q, RATE_FACTOR as R, SERVER_BACKOFF_MS as S, type Tier as T, type UserTier as U, type VoiceRate as V, deStock as W, defaultCaptionsFor as X, getErrorMessage as Y, getLanguageName as Z, getOrRefreshRelayToken as _, type CaptionPosition as a, getStatusCached as a0, getTierByEmail as a1, gotoSettled as a2, hasTrialRemaining as a3, hasVoice as a4, idSelector as a5, internationalVoiceFor as a6, isCanceling as a7, isExcludedLink as a8, isPaid as a9, isVerified as aa, normalizeUrl as ab, parseAIPlanResponse as ac, parsePartial as ad, pricingUrl as ae, rgbaToHex as af, runPooled as ag, shouldSkipLink as ah, showPaywall as ai, stripLongDashes as aj, toTitleCase as ak, voiceFor as al, waitForPlaceholdersGone as am, getTier as an, type Config as b, type AIResponse as c, type AIResult as d, CLI_BACKOFF_MS as e, CONFIG_FILE as f, type CaptionPreset as g, type CaptionWordStyle as h, type ChatOptions as i, DEFAULT_CONFIG as j, type ElementInfo as k, type ElementType as l, INTERNATIONAL_VOICE as m, type Plan as n, RELAY_BASE as o, type RateLimitInfo as p, type RetryNotice as q, SIZE_PRESETS as r, type SparkStatus as s, type SparkSubscription as t, type SparkTrial as u, type Step as v, type SuggestedAction as w, VERSION as x, VIDEO_PROFILES as y, type VideoProfileName as z };
package/dist/web.d.mts CHANGED
@@ -1,6 +1,7 @@
1
- import { P as Plan } from './scraping-C334VxX5.mjs';
2
- export { A as AIError, a as AIResponse, b as AIResult, E as EXCLUDED_LINK_PATTERNS, i as ElementInfo, j as ElementType, F as FEATURES, H as HighlightMode, I as INTERACTIVE_SELECTORS, R as RateLimitInfo, l as SparkStatus, m as SparkSubscription, n as SparkTrial, o as Step, p as SuggestedAction, T as Tier, q as buildSystemPrompt, r as buildUserPrompt, s as callAI, u as crawlPage, v as getErrorMessage, w as getLanguageName, x as getStatus, y as getStatusCached, a0 as getTier, z as getTierByEmail, B as hasAccess, G as hasTrialRemaining, K as isCanceling, L as isExcludedLink, M as isPaid, N as isVerified, O as normalizeUrl, Q as parseAIPlanResponse, W as parsePartial, X as pricingUrl, Y as rgbaToHex, Z as shouldSkipLink, _ as showPaywall, $ as toTitleCase } from './scraping-C334VxX5.mjs';
3
- export { WIDE_VOICES as LANGUAGE_VOICES, hexToAss as hexToASS, windowsDrivePathToWsl as windowsPathToWSL } from '@spark-apps/video-kit';
1
+ import { n as Plan } from './voices-ufga0oOn.mjs';
2
+ export { A as AIError, c as AIResponse, d as AIResult, i as ChatOptions, E as EXCLUDED_LINK_PATTERNS, k as ElementInfo, l as ElementType, H as HighlightMode, I as INTERACTIVE_SELECTORS, m as INTERNATIONAL_VOICE, P as PLAN_MAX_TOKENS, p as RateLimitInfo, s as SparkStatus, t as SparkSubscription, u as SparkTrial, v as Step, w as SuggestedAction, T as Tier, K as buildSystemPrompt, L as buildUserPrompt, M as callAI, Q as crawlPage, W as deStock, Y as getErrorMessage, Z as getLanguageName, $ as getStatus, a0 as getStatusCached, an as getTier, a1 as getTierByEmail, a3 as hasTrialRemaining, a4 as hasVoice, a6 as internationalVoiceFor, a7 as isCanceling, a8 as isExcludedLink, a9 as isPaid, aa as isVerified, ab as normalizeUrl, ac as parseAIPlanResponse, ad as parsePartial, ae as pricingUrl, af as rgbaToHex, ag as runPooled, ah as shouldSkipLink, ai as showPaywall, aj as stripLongDashes, ak as toTitleCase, al as voiceFor } from './voices-ufga0oOn.mjs';
3
+ export { MALE_VOICES, MULTILINGUAL_VOICES, WIDE_VOICES, hexToAss as hexToASS, windowsDrivePathToWsl as windowsPathToWSL } from '@spark-apps/video-kit';
4
+ import 'playwright';
4
5
 
5
6
  /**
6
7
  * The plan file, and handing it to the browser editor.
@@ -112,6 +113,7 @@ interface PreservedValues {
112
113
  mediaType: string | undefined;
113
114
  }>;
114
115
  mediaSteps: Array<{
116
+ value: string | undefined;
115
117
  mediaFile: string | undefined;
116
118
  mediaType: string | undefined;
117
119
  target: string | undefined;