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