@spark-apps/quickpeek 1.1.3 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/qp.js CHANGED
@@ -35,14 +35,6 @@ function getLanguageName(lang) {
35
35
  if (!lang || lang === "en") return null;
36
36
  return LANGUAGE_NAMES[lang] || lang.toUpperCase();
37
37
  }
38
- function hexToASS(hex, alpha = 0) {
39
- const clean = hex.replace("#", "");
40
- const r = clean.slice(0, 2);
41
- const g = clean.slice(2, 4);
42
- const b = clean.slice(4, 6);
43
- const a = alpha.toString(16).padStart(2, "0").toUpperCase();
44
- return `&H${a}${b}${g}${r}`.toUpperCase();
45
- }
46
38
  function rgbaToHex(color) {
47
39
  if (color.startsWith("#")) return color;
48
40
  const match = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
@@ -52,12 +44,6 @@ function rgbaToHex(color) {
52
44
  const b = parseInt(match[3], 10).toString(16).padStart(2, "0");
53
45
  return `#${r}${g}${b}`.toUpperCase();
54
46
  }
55
- function windowsPathToWSL(path) {
56
- const driveMatch = /^([A-Z]):[/\\]/i.exec(path);
57
- if (!driveMatch) return null;
58
- const drive = driveMatch[1].toLowerCase();
59
- return `/mnt/${drive}/${path.slice(3).replace(/\\/g, "/")}`;
60
- }
61
47
  function parsePartial(partial) {
62
48
  if (!partial) return {};
63
49
  const parts = partial.split("-");
@@ -83,10 +69,12 @@ function toTitleCase(text) {
83
69
  return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
84
70
  });
85
71
  }
86
- var LANGUAGE_NAMES, LOWERCASE_WORDS;
72
+ var import_video_kit, import_video_kit2, LANGUAGE_NAMES, LOWERCASE_WORDS;
87
73
  var init_utils = __esm({
88
74
  "src/lib/utils.ts"() {
89
75
  "use strict";
76
+ import_video_kit = require("@spark-apps/video-kit");
77
+ import_video_kit2 = require("@spark-apps/video-kit");
90
78
  LANGUAGE_NAMES = {
91
79
  ar: "Arabic",
92
80
  es: "Spanish",
@@ -137,21 +125,24 @@ var init_utils = __esm({
137
125
  });
138
126
 
139
127
  // src/lib/api-client.ts
140
- async function getOrRefreshToken() {
128
+ async function getOrRefreshDirectToken() {
141
129
  const now = Date.now();
142
- if (cachedToken && cachedToken.expiresAt > now + 3e5) {
143
- return cachedToken.token;
130
+ if (cachedDirectToken && cachedDirectToken.expiresAt > now + 3e5) {
131
+ return cachedDirectToken.token;
144
132
  }
145
133
  try {
146
134
  const response = await fetch(SPARK_AI_TOKEN, {
147
135
  method: "POST",
148
136
  headers: { "X-Api-Key": API_KEY }
149
137
  });
138
+ if (!response.ok) {
139
+ throw new Error(`Token exchange failed: ${response.status} ${response.statusText}`);
140
+ }
150
141
  const data = await response.json();
151
- if (!response.ok || !data.success || !data.token) {
152
- throw new Error(data.error || `Token exchange failed: ${response.status}`);
142
+ if (!data.success || !data.token) {
143
+ throw new Error(data.error || `Token exchange failed: unexpected response`);
153
144
  }
154
- cachedToken = {
145
+ cachedDirectToken = {
155
146
  token: data.token,
156
147
  expiresAt: data.expiresAt || now + 36e5
157
148
  };
@@ -160,77 +151,130 @@ async function getOrRefreshToken() {
160
151
  throw new Error(`Failed to get Spark AI token: ${getErrorMessage(err)}`);
161
152
  }
162
153
  }
163
- async function callAI(systemPrompt, userPrompt, tier = "free") {
164
- if (!API_KEY) {
165
- return {
166
- success: false,
167
- error: "AI service is not configured. Please report this issue.",
168
- isAuthError: true
169
- };
154
+ async function getOrRefreshRelayToken(email) {
155
+ const now = Date.now();
156
+ if (cachedRelayToken && cachedRelayToken.email === email && cachedRelayToken.expiresAt > now + 3e5) {
157
+ return cachedRelayToken.token;
170
158
  }
171
159
  try {
172
- const token = await getOrRefreshToken();
173
- let lastRateLimitError = null;
174
- for (let attempt = 1; attempt <= 3; attempt++) {
175
- try {
176
- const response = await fetch(SPARK_AI_CHAT, {
177
- method: "POST",
178
- headers: {
179
- "Content-Type": "application/json",
180
- Authorization: `Bearer ${token}`
181
- },
182
- body: JSON.stringify({
183
- messages: [
184
- { role: "system", content: systemPrompt },
185
- { role: "user", content: userPrompt }
186
- ]
187
- })
188
- });
189
- const data = await response.json();
190
- if (!response.ok) {
191
- const error = data.error || `Spark AI error: ${response.status}`;
192
- if (response.status === 429 && attempt < 3) {
193
- const retryAfter = response.headers.get("Retry-After");
194
- const delayMs = retryAfter ? parseInt(retryAfter, 10) * 1e3 : 1e3 * Math.pow(2, attempt - 1);
195
- lastRateLimitError = {
196
- success: false,
197
- error,
198
- isRateLimit: true,
199
- ...retryAfter ? { retryAfterSeconds: parseInt(retryAfter, 10) } : {}
200
- };
201
- await new Promise((resolve9) => setTimeout(resolve9, Math.min(delayMs, 3e4)));
202
- continue;
203
- }
204
- return {
160
+ const response = await fetch(RELAY_TOKEN_URL, {
161
+ method: "POST",
162
+ headers: { "Content-Type": "application/json" },
163
+ body: JSON.stringify({ email })
164
+ });
165
+ const data = await response.json().catch(() => ({}));
166
+ if (!response.ok || !data.success || !data.token) {
167
+ throw new Error(data.error || `Token request failed: ${response.status} ${response.statusText}`);
168
+ }
169
+ cachedRelayToken = {
170
+ token: data.token,
171
+ expiresAt: data.expiresAt || now + 36e5,
172
+ email
173
+ };
174
+ return data.token;
175
+ } catch (err) {
176
+ throw new Error(`Failed to get QuickPeek token: ${getErrorMessage(err)}`);
177
+ }
178
+ }
179
+ async function executeChat(chatUrl, bearerToken, systemPrompt, userPrompt, tier) {
180
+ let lastRateLimitError = null;
181
+ for (let attempt = 1; attempt <= 3; attempt++) {
182
+ try {
183
+ const response = await fetch(chatUrl, {
184
+ method: "POST",
185
+ headers: {
186
+ "Content-Type": "application/json",
187
+ Authorization: `Bearer ${bearerToken}`
188
+ },
189
+ body: JSON.stringify({
190
+ messages: [
191
+ { role: "system", content: systemPrompt },
192
+ { role: "user", content: userPrompt }
193
+ ]
194
+ })
195
+ });
196
+ let data = {};
197
+ const contentType = response.headers.get("content-type") ?? "";
198
+ if (contentType.includes("application/json")) {
199
+ data = await response.json();
200
+ }
201
+ if (!response.ok) {
202
+ const error = data.error || `Spark AI error: ${response.status} ${response.statusText}`;
203
+ if (response.status === 429 && attempt < 3) {
204
+ const retryAfter = response.headers.get("Retry-After");
205
+ const delayMs = retryAfter ? parseInt(retryAfter, 10) * 1e3 : 1e3 * 2 ** (attempt - 1);
206
+ lastRateLimitError = {
205
207
  success: false,
206
208
  error,
207
- isRateLimit: response.status === 429,
208
- isAuthError: response.status === 401 || response.status === 403
209
- };
210
- }
211
- if (!data.success || !data.response) {
212
- return {
213
- success: false,
214
- error: data.error || "No response from Spark AI"
209
+ isRateLimit: true,
210
+ ...retryAfter ? { retryAfterSeconds: parseInt(retryAfter, 10) } : {}
215
211
  };
212
+ await new Promise((resolve9) => setTimeout(resolve9, Math.min(delayMs, 3e4)));
213
+ continue;
216
214
  }
217
215
  return {
218
- success: true,
219
- content: data.response,
220
- limits: {
221
- used: data.limits?.used ?? 0,
222
- limit: data.limits?.limit ?? 100,
223
- tier,
224
- ...data.limits?.resetAt ? { resetAt: data.limits.resetAt } : {}
225
- }
216
+ success: false,
217
+ error,
218
+ isRateLimit: response.status === 429,
219
+ isAuthError: response.status === 401 || response.status === 403
220
+ };
221
+ }
222
+ if (!data.success || !data.response) {
223
+ return {
224
+ success: false,
225
+ error: data.error || "No response from Spark AI"
226
226
  };
227
- } catch (err) {
228
- if (attempt === 3) throw err;
229
- const delayMs = 1e3 * Math.pow(2, attempt - 1);
230
- await new Promise((resolve9) => setTimeout(resolve9, delayMs));
231
227
  }
228
+ return {
229
+ success: true,
230
+ content: data.response,
231
+ limits: {
232
+ used: data.limits?.used ?? 0,
233
+ limit: data.limits?.limit ?? 100,
234
+ tier,
235
+ ...data.limits?.resetAt ? { resetAt: data.limits.resetAt } : {}
236
+ }
237
+ };
238
+ } catch (err) {
239
+ if (attempt === 3) throw err;
240
+ const delayMs = 1e3 * 2 ** (attempt - 1);
241
+ await new Promise((resolve9) => setTimeout(resolve9, delayMs));
232
242
  }
233
- return lastRateLimitError || { success: false, error: "Request failed after retries" };
243
+ }
244
+ return lastRateLimitError || { success: false, error: "Request failed after retries" };
245
+ }
246
+ async function callAI(systemPrompt, userPrompt, tier = "free") {
247
+ if (!API_KEY) {
248
+ return {
249
+ success: false,
250
+ error: "AI service is not configured. Please report this issue.",
251
+ isAuthError: true
252
+ };
253
+ }
254
+ try {
255
+ const token = await getOrRefreshDirectToken();
256
+ return await executeChat(SPARK_AI_CHAT, token, systemPrompt, userPrompt, tier);
257
+ } catch (err) {
258
+ return { success: false, error: `Spark AI error: ${getErrorMessage(err)}` };
259
+ }
260
+ }
261
+ async function callAIViaRelay(systemPrompt, userPrompt, email, tier = "free") {
262
+ if (!email) {
263
+ return {
264
+ success: false,
265
+ error: "Sign in required. Run qp login to connect your account.",
266
+ isAuthError: true
267
+ };
268
+ }
269
+ try {
270
+ let token = await getOrRefreshRelayToken(email);
271
+ let result = await executeChat(RELAY_CHAT_URL, token, systemPrompt, userPrompt, tier);
272
+ if (!result.success && result.isAuthError) {
273
+ cachedRelayToken = null;
274
+ token = await getOrRefreshRelayToken(email);
275
+ result = await executeChat(RELAY_CHAT_URL, token, systemPrompt, userPrompt, tier);
276
+ }
277
+ return result;
234
278
  } catch (err) {
235
279
  return { success: false, error: `Spark AI error: ${getErrorMessage(err)}` };
236
280
  }
@@ -260,7 +304,7 @@ function normalizeUrl(input) {
260
304
  url = url.replace(/^(https?:\/\/)?(www\.)?/i, "");
261
305
  return `https://${url}`;
262
306
  }
263
- var SPARK_AI_BASE, SPARK_AI_CHAT, SPARK_AI_TOKEN, API_KEY, cachedToken;
307
+ var SPARK_AI_BASE, SPARK_AI_CHAT, SPARK_AI_TOKEN, RELAY_BASE, RELAY_TOKEN_URL, RELAY_CHAT_URL, API_KEY, cachedDirectToken, cachedRelayToken;
264
308
  var init_api_client = __esm({
265
309
  "src/lib/api-client.ts"() {
266
310
  "use strict";
@@ -268,14 +312,21 @@ var init_api_client = __esm({
268
312
  SPARK_AI_BASE = "https://sparkbrain.app";
269
313
  SPARK_AI_CHAT = `${SPARK_AI_BASE}/api/chat`;
270
314
  SPARK_AI_TOKEN = `${SPARK_AI_BASE}/api/models/token`;
271
- API_KEY = "spark_ai-8eefdbe9be3069cad2566cb5753dcff9";
272
- cachedToken = null;
315
+ RELAY_BASE = process.env.QUICKPEEK_API_BASE || "https://quickpeek.co";
316
+ RELAY_TOKEN_URL = `${RELAY_BASE}/api/cli/token`;
317
+ RELAY_CHAT_URL = `${RELAY_BASE}/api/cli/ai`;
318
+ API_KEY = process.env.SPARK_AI_KEY || "";
319
+ cachedDirectToken = null;
320
+ cachedRelayToken = null;
273
321
  }
274
322
  });
275
323
 
276
324
  // src/lib/SparkStripe-feature-gating.ts
277
325
  async function _fetch(email, init) {
278
- const r = await fetch(`${BASE}/api/public/subscription/status-public?email=${encodeURIComponent(email)}&app_id=${APP_ID}`, init);
326
+ const r = await fetch(
327
+ `${BASE}/api/public/subscription/status-public?email=${encodeURIComponent(email)}&app_id=${APP_ID}`,
328
+ init
329
+ );
279
330
  if (!r.ok) throw new Error(`spark-stripe: ${r.status}`);
280
331
  return r.json();
281
332
  }
@@ -318,24 +369,27 @@ var init_SparkStripe_feature_gating = __esm({
318
369
  APP_ID = process.env.SPARK_APP_ID ?? "quickpeek";
319
370
  BASE = "https://sparkstripe.com";
320
371
  TIERS = {
321
- "price_1TADnPEf9opkuWid9NtqOYON": "pro",
322
- "price_1TADnPEf9opkuWidgbvvX9ma": "pro",
323
- "price_1TADm7Ef9opkuWid3I9tquIB": "pro",
324
- "price_1TADm8Ef9opkuWid1RSA2wCa": "pro",
325
- "price_1TCREmEf9opkuWidvhuqzqDM": "pro",
326
- "price_1TCREmEf9opkuWidacf1tiC7": "pro"
372
+ price_1TADnPEf9opkuWid9NtqOYON: "pro",
373
+ price_1TADnPEf9opkuWidgbvvX9ma: "pro",
374
+ price_1TADm7Ef9opkuWid3I9tquIB: "pro",
375
+ price_1TADm8Ef9opkuWid1RSA2wCa: "pro",
376
+ price_1TCREmEf9opkuWidvhuqzqDM: "pro",
377
+ price_1TCREmEf9opkuWidacf1tiC7: "pro"
327
378
  };
328
379
  RANK = {
329
- "free": 0,
330
- "pro": 1
380
+ free: 0,
381
+ pro: 1
382
+ };
383
+ FEATURES = {
384
+ free: [],
385
+ pro: [
386
+ "Everything in Free",
387
+ "No watermark",
388
+ "Full video composition (voiceover + captions + music + transitions)",
389
+ "Unlimited videos",
390
+ "Priority support"
391
+ ]
331
392
  };
332
- FEATURES = { "free": [], "pro": [
333
- "Everything in Free",
334
- "No watermark",
335
- "Full video composition (voiceover + captions + music + transitions)",
336
- "Unlimited videos",
337
- "Priority support"
338
- ] };
339
393
  getStatus = (email) => _fetch(email);
340
394
  _cache = /* @__PURE__ */ new Map();
341
395
  isPaid = (d) => getTier(d) !== "free";
@@ -532,11 +586,17 @@ function getActionForType(type) {
532
586
  return "type";
533
587
  }
534
588
  }
535
- async function crawlPage(url, task2 = noopTask) {
589
+ async function crawlPage(url, task2 = noopTask, storageStatePath) {
590
+ const cached = crawlCache.get(url);
591
+ if (cached && Date.now() - cached.timestamp < CRAWL_CACHE_TTL_MS) {
592
+ task2("Using cached crawl").succeed(`Found ${cached.elements.length} interactive elements (cached)`);
593
+ return { title: cached.title, elements: [...cached.elements] };
594
+ }
536
595
  const browserTask = task2("Launching browser");
537
596
  const browser = await import_playwright.chromium.launch({ headless: true });
538
- const page = await browser.newPage();
539
- browserTask.succeed("Browser launched");
597
+ const context = await browser.newContext(storageStatePath ? { storageState: storageStatePath } : {});
598
+ const page = await context.newPage();
599
+ browserTask.succeed(storageStatePath ? "Browser launched (signed in)" : "Browser launched");
540
600
  const crawlTask = task2("Crawling page");
541
601
  await page.goto(url, { waitUntil: "networkidle", timeout: 3e4 });
542
602
  const title = await page.title();
@@ -669,9 +729,14 @@ async function crawlPage(url, task2 = noopTask) {
669
729
  });
670
730
  await browser.close();
671
731
  crawlTask.succeed(`Found ${elements.length} interactive elements`);
732
+ crawlCache.set(url, { timestamp: Date.now(), title, elements: [...elements] });
733
+ if (crawlCache.size > CRAWL_CACHE_MAX_ENTRIES) {
734
+ const oldest = crawlCache.keys().next().value;
735
+ if (oldest !== void 0) crawlCache.delete(oldest);
736
+ }
672
737
  return { title, elements };
673
738
  }
674
- var import_playwright, noopTask;
739
+ var import_playwright, noopTask, CRAWL_CACHE_TTL_MS, CRAWL_CACHE_MAX_ENTRIES, crawlCache;
675
740
  var init_crawl = __esm({
676
741
  "src/lib/crawl.ts"() {
677
742
  "use strict";
@@ -679,6 +744,9 @@ var init_crawl = __esm({
679
744
  init_scraping();
680
745
  noopTask = () => ({ succeed: () => {
681
746
  } });
747
+ CRAWL_CACHE_TTL_MS = 5 * 60 * 1e3;
748
+ CRAWL_CACHE_MAX_ENTRIES = 50;
749
+ crawlCache = /* @__PURE__ */ new Map();
682
750
  }
683
751
  });
684
752
 
@@ -723,10 +791,10 @@ var init_watermark = __esm({
723
791
  // src/lib/ffmpeg/spawn.ts
724
792
  function toWSLPath(path) {
725
793
  if (!isWindowsRuntime) return path;
726
- return windowsPathToWSL(path) ?? path.replace(/\\/g, "/");
794
+ return (0, import_video_kit2.windowsDrivePathToWsl)(path) ?? path.replace(/\\/g, "/");
727
795
  }
728
796
  function convertArgToWSL(arg2) {
729
- const wslPath = windowsPathToWSL(arg2);
797
+ const wslPath = (0, import_video_kit2.windowsDrivePathToWsl)(arg2);
730
798
  if (wslPath) return wslPath;
731
799
  if (arg2.includes("\\")) return arg2.replace(/\\/g, "/");
732
800
  return arg2;
@@ -823,20 +891,24 @@ function composeVideo(opts) {
823
891
  `[1:a]apad=whole_dur=${voiceoverDuration},afade=t=in:st=0:d=${fadeIn}[voice]`,
824
892
  // Music: fade in and fade out
825
893
  `[2:a]aloop=loop=-1:size=2e+09,atrim=0:${effectiveDuration},volume=${musicVolume},afade=t=in:st=0:d=${musicFadeIn},afade=t=out:st=${musicFadeOutStart}:d=${musicFadeOut}[music]`,
826
- `[voice][music]amix=inputs=2:duration=longest[aout]`
894
+ // Normalise the MIX and hard-limit its true peak - ported from
895
+ // VidLet, where single-pass loudnorm alone measured +0.9 dBTP on
896
+ // sharp TTS consonants. -14 LUFS is the YouTube/IG/TikTok playback
897
+ // target, so the platform leaves the audio untouched.
898
+ `[voice][music]amix=inputs=2:duration=longest,${import_video_kit3.MASTER_CHAIN}[aout]`
827
899
  ].join(";");
828
900
  args.push("-vf", vfStr);
829
901
  args.push("-filter_complex", complexFilter, "-map", "0:v", "-map", "[aout]");
830
902
  args.push("-c:v", "libx264", "-preset", preset, "-crf", String(crf), "-pix_fmt", "yuv420p");
831
903
  args.push("-c:a", "aac", "-b:a", audioBitrate);
832
904
  } else if (hasVoiceover) {
833
- const af = `apad=whole_dur=${voiceoverDuration},afade=t=in:st=0:d=${fadeIn}`;
905
+ const af = `apad=whole_dur=${voiceoverDuration},afade=t=in:st=0:d=${fadeIn},${import_video_kit3.MASTER_CHAIN}`;
834
906
  args.push("-vf", vfStr);
835
907
  args.push("-map", "0:v", "-map", "1:a", "-af", af);
836
908
  args.push("-c:v", "libx264", "-preset", preset, "-crf", String(crf), "-pix_fmt", "yuv420p");
837
909
  args.push("-c:a", "aac", "-b:a", audioBitrate);
838
910
  } else if (hasMusic) {
839
- const musicFilter = `aloop=loop=-1:size=2e+09,atrim=0:${effectiveDuration},volume=${musicVolume},afade=t=in:st=0:d=${musicFadeIn},afade=t=out:st=${musicFadeOutStart}:d=${musicFadeOut}`;
911
+ const musicFilter = `aloop=loop=-1:size=2e+09,atrim=0:${effectiveDuration},volume=${musicVolume},afade=t=in:st=0:d=${musicFadeIn},afade=t=out:st=${musicFadeOutStart}:d=${musicFadeOut},${import_video_kit3.MASTER_CHAIN}`;
840
912
  args.push("-vf", vfStr);
841
913
  args.push("-map", "0:v", "-map", "1:a", "-af", musicFilter);
842
914
  args.push("-c:v", "libx264", "-preset", preset, "-crf", String(crf), "-pix_fmt", "yuv420p");
@@ -849,9 +921,11 @@ function composeVideo(opts) {
849
921
  runFFmpeg(args, "ffmpeg compose failed").then(resolve9, reject);
850
922
  });
851
923
  }
924
+ var import_video_kit3;
852
925
  var init_compose = __esm({
853
926
  "src/lib/ffmpeg/compose.ts"() {
854
927
  "use strict";
928
+ import_video_kit3 = require("@spark-apps/video-kit");
855
929
  init_watermark();
856
930
  init_spawn();
857
931
  }
@@ -1108,14 +1182,13 @@ function applyTransitions(opts) {
1108
1182
  filterParts.push(`[0:v]trim=start=${start.toFixed(3)}:end=${end.toFixed(3)},setpts=PTS-STARTPTS[v${i}]`);
1109
1183
  }
1110
1184
  let currentV = "v0";
1185
+ let chainDuration = boundaries[1] - boundaries[0];
1111
1186
  for (let i = 0; i < sorted.length; i++) {
1112
1187
  const trans = sorted[i];
1113
1188
  const duration = trans.duration ?? 0.3;
1114
1189
  const nextIdx = i + 1;
1115
- const segStart = boundaries[i];
1116
- const segEnd = boundaries[i + 1];
1117
- const segDuration = segEnd - segStart;
1118
- const offset = Math.max(0, segDuration - duration);
1190
+ const offset = Math.max(0, chainDuration - duration);
1191
+ chainDuration += boundaries[nextIdx + 1] - boundaries[nextIdx] - duration;
1119
1192
  const xfadeType = trans.type === "fade" ? "fadeblack" : "fade";
1120
1193
  const outV = i === sorted.length - 1 ? "outv" : `xv${i}`;
1121
1194
  filterParts.push(
@@ -1316,24 +1389,20 @@ var init_prompts = __esm({
1316
1389
 
1317
1390
  // src/lib/styles.ts
1318
1391
  function generateASSHeader(width, height, font, size, primaryColor, secondaryColor, outlineColor, backColor, bold, outlineWidth, shadow, marginBottom) {
1319
- return `[Script Info]
1320
- Title: QuickPeek Demo
1321
- ScriptType: v4.00+
1322
- PlayResX: ${width}
1323
- PlayResY: ${height}
1324
-
1325
- [V4+ Styles]
1326
- Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
1327
- Style: Karaoke,${font},${size},${primaryColor},${secondaryColor},${outlineColor},${backColor},${bold ? 1 : 0},0,0,0,100,100,0,0,1,${outlineWidth},${shadow},2,40,40,${marginBottom},1
1328
-
1329
- [Events]
1330
- Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
1331
- `;
1392
+ return (0, import_video_kit4.buildAssHeader)({
1393
+ title: "QuickPeek Demo",
1394
+ width,
1395
+ height,
1396
+ styles: [
1397
+ `Style: Karaoke,${font},${size},${primaryColor},${secondaryColor},${outlineColor},${backColor},${bold ? 1 : 0},0,0,0,100,100,0,0,1,${outlineWidth},${shadow},2,40,40,${marginBottom},1`
1398
+ ]
1399
+ });
1332
1400
  }
1333
- var CURSOR_MOVE_DURATION, CURSOR_CSS, CURSOR_SCRIPT;
1401
+ var import_video_kit4, CURSOR_MOVE_DURATION, CURSOR_CSS, CURSOR_SCRIPT;
1334
1402
  var init_styles = __esm({
1335
1403
  "src/lib/styles.ts"() {
1336
1404
  "use strict";
1405
+ import_video_kit4 = require("@spark-apps/video-kit");
1337
1406
  init_cursor();
1338
1407
  CURSOR_MOVE_DURATION = 800;
1339
1408
  CURSOR_CSS = `
@@ -1416,7 +1485,7 @@ var init_subscription_client = __esm({
1416
1485
 
1417
1486
  // src/lib/tts/helpers.ts
1418
1487
  function toWSLPathForTTS(path) {
1419
- return windowsPathToWSL(path) ?? path;
1488
+ return (0, import_video_kit2.windowsDrivePathToWsl)(path) ?? path;
1420
1489
  }
1421
1490
  function estimateActionDuration(step) {
1422
1491
  if (step.action === "drag" && step.value === "around") {
@@ -1446,8 +1515,8 @@ async function getMediaStepDuration(step, baseDir) {
1446
1515
  if (!["mp4", "webm", "mov"].includes(ext)) {
1447
1516
  return 0;
1448
1517
  }
1449
- const mediaPath = (0, import_node_path.isAbsolute)(step.value) ? step.value : (0, import_node_path.resolve)(baseDir, step.value);
1450
- if (!(0, import_node_fs.existsSync)(mediaPath)) {
1518
+ const mediaPath = (0, import_node_path2.isAbsolute)(step.value) ? step.value : (0, import_node_path2.resolve)(baseDir, step.value);
1519
+ if (!(0, import_node_fs2.existsSync)(mediaPath)) {
1451
1520
  return 0;
1452
1521
  }
1453
1522
  try {
@@ -1467,38 +1536,38 @@ async function getMediaStepDuration(step, baseDir) {
1467
1536
  }
1468
1537
  }
1469
1538
  async function loadVoiceoverMeta(outputDir) {
1470
- const metaPath = (0, import_node_path.join)(outputDir, "voiceover.json");
1539
+ const metaPath = (0, import_node_path2.join)(outputDir, "voiceover.json");
1471
1540
  try {
1472
- if (!(0, import_node_fs.existsSync)(metaPath)) return null;
1473
- const content = await (0, import_promises.readFile)(metaPath, "utf-8");
1541
+ if (!(0, import_node_fs2.existsSync)(metaPath)) return null;
1542
+ const content = await (0, import_promises2.readFile)(metaPath, "utf-8");
1474
1543
  return JSON.parse(content);
1475
1544
  } catch {
1476
1545
  return null;
1477
1546
  }
1478
1547
  }
1479
1548
  async function saveVoiceoverMeta(outputDir, meta) {
1480
- const metaPath = (0, import_node_path.join)(outputDir, "voiceover.json");
1481
- await (0, import_promises.writeFile)(metaPath, JSON.stringify(meta, null, 2));
1549
+ const metaPath = (0, import_node_path2.join)(outputDir, "voiceover.json");
1550
+ await (0, import_promises2.writeFile)(metaPath, JSON.stringify(meta, null, 2));
1482
1551
  }
1483
1552
  async function concatenateAudio(inputs, output, workDir) {
1484
1553
  const formatPath = (f) => {
1485
- const resolved = (0, import_node_path.resolve)(f);
1486
- return isWindows ? windowsPathToWSL(resolved) ?? resolved : resolved;
1554
+ const resolved = (0, import_node_path2.resolve)(f);
1555
+ return isWindows ? (0, import_video_kit2.windowsDrivePathToWsl)(resolved) ?? resolved : resolved;
1487
1556
  };
1488
1557
  const listContent = inputs.map((f) => `file '${formatPath(f)}'`).join("\n");
1489
- const listPath = (0, import_node_path.join)(workDir, "concat.txt");
1490
- await (0, import_promises.writeFile)(listPath, listContent);
1558
+ const listPath = (0, import_node_path2.join)(workDir, "concat.txt");
1559
+ await (0, import_promises2.writeFile)(listPath, listContent);
1491
1560
  await concatMedia(listPath, output);
1492
- await (0, import_promises.unlink)(listPath).catch(() => {
1561
+ await (0, import_promises2.unlink)(listPath).catch(() => {
1493
1562
  });
1494
1563
  }
1495
- var import_node_fs, import_promises, import_node_path, isWindows;
1564
+ var import_node_fs2, import_promises2, import_node_path2, isWindows;
1496
1565
  var init_helpers = __esm({
1497
1566
  "src/lib/tts/helpers.ts"() {
1498
1567
  "use strict";
1499
- import_node_fs = require("fs");
1500
- import_promises = require("fs/promises");
1501
- import_node_path = require("path");
1568
+ import_node_fs2 = require("fs");
1569
+ import_promises2 = require("fs/promises");
1570
+ import_node_path2 = require("path");
1502
1571
  init_ffmpeg2();
1503
1572
  init_utils();
1504
1573
  isWindows = process.platform === "win32";
@@ -1534,7 +1603,7 @@ async function alignVoiceoverToRecording(stepAudio, actualRecordingDurations, wo
1534
1603
  if (!needsRealignment) {
1535
1604
  return stepAudio;
1536
1605
  }
1537
- const audioDir = (0, import_node_path2.join)(workDir, "audio");
1606
+ const audioDir = (0, import_node_path3.join)(workDir, "audio");
1538
1607
  const segments = [];
1539
1608
  const alignedStepAudio = /* @__PURE__ */ new Map();
1540
1609
  for (let i = 0; i < actualRecordingDurations.length; i++) {
@@ -1543,20 +1612,20 @@ async function alignVoiceoverToRecording(stepAudio, actualRecordingDurations, wo
1543
1612
  const voiceDuration = voiceData?.duration ?? 0;
1544
1613
  const recordingDuration = actualRecordingDurations[i] ?? 0;
1545
1614
  const targetDuration = Math.max(voiceDuration, recordingDuration);
1546
- const audioPath = (0, import_node_path2.join)(audioDir, `step-${stepNum}.mp3`);
1547
- const originalPaddingPath = (0, import_node_path2.join)(audioDir, `step-${stepNum}-padding.mp3`);
1548
- const silencePath = (0, import_node_path2.join)(audioDir, `step-${stepNum}-silence.mp3`);
1549
- if ((0, import_node_fs2.existsSync)(audioPath)) {
1615
+ const audioPath = (0, import_node_path3.join)(audioDir, `step-${stepNum}.mp3`);
1616
+ const originalPaddingPath = (0, import_node_path3.join)(audioDir, `step-${stepNum}-padding.mp3`);
1617
+ const silencePath = (0, import_node_path3.join)(audioDir, `step-${stepNum}-silence.mp3`);
1618
+ if ((0, import_node_fs3.existsSync)(audioPath)) {
1550
1619
  segments.push(audioPath);
1551
- if ((0, import_node_fs2.existsSync)(originalPaddingPath)) {
1620
+ if ((0, import_node_fs3.existsSync)(originalPaddingPath)) {
1552
1621
  segments.push(originalPaddingPath);
1553
1622
  }
1554
1623
  if (recordingDuration > voiceDuration) {
1555
- const alignPaddingPath = (0, import_node_path2.join)(audioDir, `step-${stepNum}-align-padding.mp3`);
1624
+ const alignPaddingPath = (0, import_node_path3.join)(audioDir, `step-${stepNum}-align-padding.mp3`);
1556
1625
  await generateSilence(alignPaddingPath, recordingDuration - voiceDuration);
1557
1626
  segments.push(alignPaddingPath);
1558
1627
  }
1559
- } else if ((0, import_node_fs2.existsSync)(silencePath)) {
1628
+ } else if ((0, import_node_fs3.existsSync)(silencePath)) {
1560
1629
  if (targetDuration !== voiceDuration) {
1561
1630
  await generateSilence(silencePath, targetDuration);
1562
1631
  }
@@ -1568,7 +1637,7 @@ async function alignVoiceoverToRecording(stepAudio, actualRecordingDurations, wo
1568
1637
  });
1569
1638
  }
1570
1639
  if (segments.length > 0) {
1571
- const voiceoverPath = (0, import_node_path2.join)(outputDir, "voiceover.wav");
1640
+ const voiceoverPath = (0, import_node_path3.join)(outputDir, "voiceover.wav");
1572
1641
  await concatenateAudio(segments, voiceoverPath, workDir);
1573
1642
  }
1574
1643
  const existingMeta = await loadVoiceoverMeta(outputDir);
@@ -1580,12 +1649,12 @@ async function alignVoiceoverToRecording(stepAudio, actualRecordingDurations, wo
1580
1649
  }
1581
1650
  return alignedStepAudio;
1582
1651
  }
1583
- var import_node_fs2, import_node_path2;
1652
+ var import_node_fs3, import_node_path3;
1584
1653
  var init_timing = __esm({
1585
1654
  "src/lib/tts/timing.ts"() {
1586
1655
  "use strict";
1587
- import_node_fs2 = require("fs");
1588
- import_node_path2 = require("path");
1656
+ import_node_fs3 = require("fs");
1657
+ import_node_path3 = require("path");
1589
1658
  init_ffmpeg2();
1590
1659
  init_helpers();
1591
1660
  }
@@ -1616,7 +1685,7 @@ function canReuseVoiceover(steps, voice, existingMeta) {
1616
1685
  }
1617
1686
  async function loadExistingVoiceover(steps, outputDir, meta, config) {
1618
1687
  const { estimateWordTimings: estimateWordTimings2 } = await Promise.resolve().then(() => (init_timing(), timing_exports));
1619
- const voiceoverPath = (0, import_node_path3.join)(outputDir, "voiceover.wav");
1688
+ const voiceoverPath = (0, import_node_path4.join)(outputDir, "voiceover.wav");
1620
1689
  const stepAudio = /* @__PURE__ */ new Map();
1621
1690
  for (let i = 0; i < steps.length; i++) {
1622
1691
  const step = steps[i];
@@ -1629,97 +1698,29 @@ async function loadExistingVoiceover(steps, outputDir, meta, config) {
1629
1698
  }
1630
1699
  return { voiceoverPath, stepAudio };
1631
1700
  }
1632
- var import_node_path3;
1701
+ var import_node_path4;
1633
1702
  var init_cache = __esm({
1634
1703
  "src/lib/tts/cache.ts"() {
1635
1704
  "use strict";
1636
- import_node_path3 = require("path");
1705
+ import_node_path4 = require("path");
1637
1706
  init_helpers();
1638
1707
  }
1639
1708
  });
1640
1709
 
1641
1710
  // src/lib/voices.ts
1642
- var LANGUAGE_VOICES;
1711
+ var import_video_kit5;
1643
1712
  var init_voices = __esm({
1644
1713
  "src/lib/voices.ts"() {
1645
1714
  "use strict";
1646
- LANGUAGE_VOICES = {
1647
- en: "en-GB-SoniaNeural",
1648
- ar: "ar-LB-LaylaNeural",
1649
- es: "es-ES-ElviraNeural",
1650
- fr: "fr-FR-DeniseNeural",
1651
- de: "de-DE-KatjaNeural",
1652
- it: "it-IT-ElsaNeural",
1653
- pt: "pt-BR-FranciscaNeural",
1654
- ru: "ru-RU-SvetlanaNeural",
1655
- zh: "zh-CN-XiaoxiaoNeural",
1656
- ja: "ja-JP-NanamiNeural",
1657
- ko: "ko-KR-SunHiNeural",
1658
- hi: "hi-IN-SwaraNeural",
1659
- tr: "tr-TR-EmelNeural",
1660
- nl: "nl-NL-ColetteNeural",
1661
- pl: "pl-PL-AgnieszkaNeural",
1662
- sv: "sv-SE-SofieNeural",
1663
- he: "he-IL-HilaNeural",
1664
- th: "th-TH-PremwadeeNeural",
1665
- vi: "vi-VN-HoaiMyNeural",
1666
- id: "id-ID-GadisNeural",
1667
- ms: "ms-MY-YasminNeural",
1668
- fil: "fil-PH-BlessicaNeural",
1669
- uk: "uk-UA-PolinaNeural",
1670
- cs: "cs-CZ-VlastaNeural",
1671
- el: "el-GR-AthinaNeural",
1672
- ro: "ro-RO-AlinaNeural",
1673
- hu: "hu-HU-NoemiNeural",
1674
- da: "da-DK-ChristelNeural",
1675
- fi: "fi-FI-NooraNeural",
1676
- no: "nb-NO-PernilleNeural",
1677
- sk: "sk-SK-ViktoriaNeural",
1678
- bg: "bg-BG-KalinaNeural",
1679
- hr: "hr-HR-GabrijelaNeural",
1680
- sl: "sl-SI-PetraNeural",
1681
- et: "et-EE-AnuNeural",
1682
- lv: "lv-LV-EveritaNeural",
1683
- lt: "lt-LT-OnaNeural",
1684
- bn: "bn-IN-TanishaaNeural",
1685
- ta: "ta-IN-PallaviNeural",
1686
- te: "te-IN-ShrutiNeural",
1687
- mr: "mr-IN-AarohiNeural",
1688
- gu: "gu-IN-DhwaniNeural",
1689
- kn: "kn-IN-SapnaNeural",
1690
- ml: "ml-IN-SobhanaNeural",
1691
- ur: "ur-PK-UzmaNeural",
1692
- fa: "fa-IR-DilaraNeural",
1693
- sw: "sw-KE-ZuriNeural",
1694
- af: "af-ZA-AdriNeural",
1695
- ca: "ca-ES-JoanaNeural",
1696
- cy: "cy-GB-NiaNeural",
1697
- ga: "ga-IE-OrlaNeural",
1698
- mt: "mt-MT-GraceNeural",
1699
- is: "is-IS-GudrunNeural",
1700
- mk: "mk-MK-MarijaNeural",
1701
- sr: "sr-RS-SophieNeural",
1702
- bs: "bs-BA-VesnaNeural",
1703
- sq: "sq-AL-AnilaNeural",
1704
- am: "am-ET-MekdesNeural",
1705
- az: "az-AZ-BabekNeural",
1706
- ka: "ka-GE-EkaNeural",
1707
- kk: "kk-KZ-AigulNeural",
1708
- mn: "mn-MN-YesUINeural",
1709
- ne: "ne-NP-HemkalaNeural",
1710
- ps: "ps-AF-LatifaNeural",
1711
- si: "si-LK-ThiliniNeural",
1712
- so: "so-SO-UbaxNeural",
1713
- zu: "zu-ZA-ThandoNeural"
1714
- };
1715
+ import_video_kit5 = require("@spark-apps/video-kit");
1715
1716
  }
1716
1717
  });
1717
1718
 
1718
1719
  // src/lib/tts/synthesize.ts
1719
1720
  async function synthesizeSpeech(text, outputPath, voice) {
1720
- const tempScriptPath = (0, import_node_path4.join)((0, import_node_path4.resolve)(outputPath, ".."), `tts-${Date.now()}.py`);
1721
- const wslScriptPath = isWindows ? toWSLPathForTTS((0, import_node_path4.resolve)(tempScriptPath)) : tempScriptPath;
1722
- const wslOutputPath = isWindows ? toWSLPathForTTS((0, import_node_path4.resolve)(outputPath)) : outputPath;
1721
+ const tempScriptPath = (0, import_node_path5.join)((0, import_node_path5.resolve)(outputPath, ".."), `tts-${Date.now()}.py`);
1722
+ const wslScriptPath = isWindows ? toWSLPathForTTS((0, import_node_path5.resolve)(tempScriptPath)) : tempScriptPath;
1723
+ const wslOutputPath = isWindows ? toWSLPathForTTS((0, import_node_path5.resolve)(outputPath)) : outputPath;
1723
1724
  const pythonScript = `
1724
1725
  import asyncio
1725
1726
  from edge_tts import Communicate
@@ -1734,7 +1735,7 @@ async def main():
1734
1735
  asyncio.run(main())
1735
1736
  `;
1736
1737
  try {
1737
- await (0, import_promises2.writeFile)(tempScriptPath, pythonScript);
1738
+ await (0, import_promises3.writeFile)(tempScriptPath, pythonScript);
1738
1739
  const pythonCmd = isWindows ? `wsl python3 "${wslScriptPath}"` : `python3 "${wslScriptPath}"`;
1739
1740
  await execAsync(pythonCmd, { timeout: 3e4 });
1740
1741
  await import("fs/promises").then((fs) => fs.unlink(tempScriptPath).catch(() => {
@@ -1762,13 +1763,13 @@ asyncio.run(main())
1762
1763
  }
1763
1764
  }
1764
1765
  }
1765
- var import_node_child_process2, import_promises2, import_node_path4, import_node_util, execAsync;
1766
+ var import_node_child_process2, import_promises3, import_node_path5, import_node_util, execAsync;
1766
1767
  var init_synthesize = __esm({
1767
1768
  "src/lib/tts/synthesize.ts"() {
1768
1769
  "use strict";
1769
1770
  import_node_child_process2 = require("child_process");
1770
- import_promises2 = require("fs/promises");
1771
- import_node_path4 = require("path");
1771
+ import_promises3 = require("fs/promises");
1772
+ import_node_path5 = require("path");
1772
1773
  import_node_util = require("util");
1773
1774
  init_helpers();
1774
1775
  execAsync = (0, import_node_util.promisify)(import_node_child_process2.exec);
@@ -1782,7 +1783,7 @@ function estimateDurationFromText(text, wordsPerMinute = 150) {
1782
1783
  }
1783
1784
  function resolveVoice(config) {
1784
1785
  if (config.audio?.voice) return config.audio.voice;
1785
- if (config.lang) return LANGUAGE_VOICES[config.lang] || null;
1786
+ if (config.lang) return import_video_kit5.WIDE_VOICES[config.lang] || null;
1786
1787
  return null;
1787
1788
  }
1788
1789
  async function generateVoiceover(steps, config, workDir, outputDir, logError2, task2, info2) {
@@ -1807,8 +1808,8 @@ async function generateVoiceover(steps, config, workDir, outputDir, logError2, t
1807
1808
  info2("No voice configured");
1808
1809
  return { voiceoverPath: "", stepAudio: stepAudio2 };
1809
1810
  }
1810
- const audioDir = (0, import_node_path5.join)(workDir, "audio");
1811
- await (0, import_promises3.mkdir)(audioDir, { recursive: true });
1811
+ const audioDir = (0, import_node_path6.join)(workDir, "audio");
1812
+ await (0, import_promises4.mkdir)(audioDir, { recursive: true });
1812
1813
  const segments = [];
1813
1814
  const stepAudio = /* @__PURE__ */ new Map();
1814
1815
  const ttsTask = task2("Generating voiceover");
@@ -1820,13 +1821,13 @@ async function generateVoiceover(steps, config, workDir, outputDir, logError2, t
1820
1821
  const manualDuration = (step.duration ?? 0) * 1e3;
1821
1822
  const mediaDuration = await getMediaStepDuration(step, outputDir);
1822
1823
  const silenceDuration = Math.max(actionDuration, manualDuration, mediaDuration, config.timing.minStepDuration);
1823
- const silencePath = (0, import_node_path5.join)(audioDir, `step-${stepNum}-silence.mp3`);
1824
+ const silencePath = (0, import_node_path6.join)(audioDir, `step-${stepNum}-silence.mp3`);
1824
1825
  await generateSilence(silencePath, silenceDuration);
1825
1826
  segments.push(silencePath);
1826
1827
  stepAudio.set(stepNum, { duration: silenceDuration, words: [] });
1827
1828
  continue;
1828
1829
  }
1829
- const audioPath = (0, import_node_path5.join)(audioDir, `step-${stepNum}.mp3`);
1830
+ const audioPath = (0, import_node_path6.join)(audioDir, `step-${stepNum}.mp3`);
1830
1831
  if (segments.length > 0) await new Promise((r) => setTimeout(r, 200));
1831
1832
  try {
1832
1833
  await synthesizeSpeech(step.caption, audioPath, voice);
@@ -1843,7 +1844,7 @@ async function generateVoiceover(steps, config, workDir, outputDir, logError2, t
1843
1844
  mediaDuration
1844
1845
  );
1845
1846
  if (stepDuration > audioDuration) {
1846
- const paddingPath = (0, import_node_path5.join)(audioDir, `step-${stepNum}-padding.mp3`);
1847
+ const paddingPath = (0, import_node_path6.join)(audioDir, `step-${stepNum}-padding.mp3`);
1847
1848
  await generateSilence(paddingPath, stepDuration - audioDuration);
1848
1849
  segments.push(paddingPath);
1849
1850
  }
@@ -1859,8 +1860,8 @@ async function generateVoiceover(steps, config, workDir, outputDir, logError2, t
1859
1860
  return { voiceoverPath: "", stepAudio };
1860
1861
  }
1861
1862
  const concatTask = task2("Combining audio");
1862
- await (0, import_promises3.mkdir)(outputDir, { recursive: true });
1863
- const voiceoverPath = (0, import_node_path5.join)(outputDir, "voiceover.wav");
1863
+ await (0, import_promises4.mkdir)(outputDir, { recursive: true });
1864
+ const voiceoverPath = (0, import_node_path6.join)(outputDir, "voiceover.wav");
1864
1865
  await concatenateAudio(segments, voiceoverPath, workDir);
1865
1866
  concatTask.succeed("Done");
1866
1867
  const meta = {
@@ -1873,12 +1874,12 @@ async function generateVoiceover(steps, config, workDir, outputDir, logError2, t
1873
1874
  await saveVoiceoverMeta(outputDir, meta);
1874
1875
  return { voiceoverPath, stepAudio };
1875
1876
  }
1876
- var import_promises3, import_node_path5;
1877
+ var import_promises4, import_node_path6;
1877
1878
  var init_generate = __esm({
1878
1879
  "src/lib/tts/generate.ts"() {
1879
1880
  "use strict";
1880
- import_promises3 = require("fs/promises");
1881
- import_node_path5 = require("path");
1881
+ import_promises4 = require("fs/promises");
1882
+ import_node_path6 = require("path");
1882
1883
  init_ffmpeg2();
1883
1884
  init_voices();
1884
1885
  init_helpers();
@@ -1933,7 +1934,7 @@ __export(lib_exports, {
1933
1934
  EXCLUDED_LINK_PATTERNS: () => EXCLUDED_LINK_PATTERNS,
1934
1935
  FEATURES: () => FEATURES,
1935
1936
  INTERACTIVE_SELECTORS: () => INTERACTIVE_SELECTORS,
1936
- LANGUAGE_VOICES: () => LANGUAGE_VOICES,
1937
+ LANGUAGE_VOICES: () => import_video_kit5.WIDE_VOICES,
1937
1938
  VERSION: () => VERSION,
1938
1939
  alignVoiceoverToRecording: () => alignVoiceoverToRecording,
1939
1940
  applyTransitions: () => applyTransitions,
@@ -1943,6 +1944,7 @@ __export(lib_exports, {
1943
1944
  buildUserPrompt: () => buildUserPrompt,
1944
1945
  buildWatermarkFilters: () => buildWatermarkFilters,
1945
1946
  callAI: () => callAI,
1947
+ callAIViaRelay: () => callAIViaRelay,
1946
1948
  canReuseVoiceover: () => canReuseVoiceover,
1947
1949
  composeVideo: () => composeVideo,
1948
1950
  concatMedia: () => concatMedia,
@@ -1961,7 +1963,7 @@ __export(lib_exports, {
1961
1963
  getUpgradeUrl: () => getUpgradeUrl,
1962
1964
  hasAccess: () => hasAccess,
1963
1965
  hasTrialRemaining: () => hasTrialRemaining,
1964
- hexToASS: () => hexToASS,
1966
+ hexToASS: () => import_video_kit.hexToAss,
1965
1967
  isCanceling: () => isCanceling,
1966
1968
  isExcludedLink: () => isExcludedLink,
1967
1969
  isPaid: () => isPaid,
@@ -1983,7 +1985,7 @@ __export(lib_exports, {
1983
1985
  spliceVideos: () => spliceVideos,
1984
1986
  synthesizeSpeech: () => synthesizeSpeech,
1985
1987
  toTitleCase: () => toTitleCase,
1986
- windowsPathToWSL: () => windowsPathToWSL
1988
+ windowsPathToWSL: () => import_video_kit2.windowsDrivePathToWsl
1987
1989
  });
1988
1990
  var init_lib = __esm({
1989
1991
  "src/lib/index.ts"() {
@@ -1995,9 +1997,9 @@ var init_lib = __esm({
1995
1997
  init_cursor();
1996
1998
  init_ffmpeg2();
1997
1999
  init_prompts();
2000
+ init_SparkStripe_feature_gating();
1998
2001
  init_scraping();
1999
2002
  init_styles();
2000
- init_SparkStripe_feature_gating();
2001
2003
  init_subscription_client();
2002
2004
  init_tts2();
2003
2005
  init_types3();
@@ -2009,198 +2011,682 @@ var init_lib = __esm({
2009
2011
 
2010
2012
  // src/qp.ts
2011
2013
  var import_dotenv = __toESM(require("dotenv"));
2012
- var import_chalk18 = __toESM(require("chalk"));
2013
-
2014
- // src/commands/capture-cmd.ts
2015
- var import_node_path19 = require("path");
2016
- var import_chalk9 = __toESM(require("chalk"));
2017
-
2018
- // src/ai/narrate.ts
2019
- init_lib();
2014
+ var import_chalk20 = __toESM(require("chalk"));
2020
2015
 
2021
- // src/ai/client.ts
2022
- var import_node_readline = require("readline");
2023
- var import_chalk2 = __toESM(require("chalk"));
2024
- init_lib();
2016
+ // src/commands/auth-cmd.ts
2017
+ var import_node_readline2 = require("readline");
2018
+ var import_chalk3 = __toESM(require("chalk"));
2019
+ var import_playwright2 = require("playwright");
2025
2020
 
2026
- // src/utils/browser-auth.ts
2027
- var import_node_http = require("http");
2028
- var import_node_url = require("url");
2029
- var import_chalk = __toESM(require("chalk"));
2030
- init_SparkStripe_feature_gating();
2031
- var TIMEOUT_MS = 12e4;
2032
- async function openBrowser(url) {
2033
- const { exec: exec2 } = await import("child_process");
2034
- const isWSL2 = process.platform === "linux" && Boolean(process.env.WSL_DISTRO_NAME);
2035
- const command = isWSL2 ? `cmd.exe /c start "" "${url}"` : process.platform === "darwin" ? `open "${url}"` : `xdg-open "${url}"`;
2036
- return new Promise((resolve9) => {
2037
- exec2(command, () => resolve9());
2038
- });
2021
+ // src/browser/auth-state.ts
2022
+ var import_node_fs = require("fs");
2023
+ var import_promises = require("fs/promises");
2024
+ var import_node_os = require("os");
2025
+ var import_node_path = require("path");
2026
+ var AUTH_DIR = (0, import_node_path.join)((0, import_node_os.homedir)(), ".quickpeek", "auth");
2027
+ function hostOf(url) {
2028
+ try {
2029
+ return new URL(url).host;
2030
+ } catch {
2031
+ return null;
2032
+ }
2039
2033
  }
2040
- async function browserLogin() {
2041
- return new Promise((resolve9) => {
2042
- let server;
2043
- let timer;
2044
- const cleanup = (email) => {
2045
- clearTimeout(timer);
2046
- server?.close();
2047
- resolve9(email);
2048
- };
2049
- server = (0, import_node_http.createServer)((req, res) => {
2050
- const url = new import_node_url.URL(req.url || "/", `http://localhost`);
2051
- const path = url.pathname;
2052
- if (path === "/callback" || path === "/") {
2053
- const email = url.searchParams.get("email");
2054
- res.writeHead(200, { "Content-Type": "text/html" });
2055
- res.end(`
2056
- <!DOCTYPE html>
2057
- <html>
2058
- <head><title>QuickPeek</title><style>
2059
- body { font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #0a0a0a; color: #fff; }
2060
- .card { text-align: center; padding: 2rem; }
2061
- h1 { font-size: 1.5rem; margin-bottom: 0.5rem; }
2062
- p { color: #888; }
2063
- </style></head>
2064
- <body><div class="card">
2065
- <h1>${email ? "Logged in!" : "Return to your terminal"}</h1>
2066
- <p>${email ? `Signed in as ${email}` : "Complete setup in your terminal."}</p>
2067
- <p style="margin-top: 1rem; font-size: 0.875rem;">You can close this tab.</p>
2068
- </div></body>
2069
- </html>
2070
- `);
2071
- if (email) {
2072
- cleanup(email);
2073
- }
2074
- } else {
2075
- res.writeHead(404);
2076
- res.end();
2077
- }
2078
- });
2079
- server.listen(0, "127.0.0.1", () => {
2080
- const addr = server.address();
2081
- if (!addr || typeof addr === "string") {
2082
- cleanup(null);
2083
- return;
2084
- }
2085
- const port = addr.port;
2086
- const returnUrl = `http://localhost:${port}/callback`;
2087
- const url = pricingUrl({ returnUrl });
2088
- console.log();
2089
- console.log(import_chalk.default.cyan(" Opening browser to sign in..."));
2090
- console.log(import_chalk.default.dim(` ${url}`));
2091
- console.log();
2092
- console.log(import_chalk.default.dim(" Waiting for sign-in (press Ctrl+C to cancel)..."));
2093
- openBrowser(url);
2094
- });
2095
- timer = setTimeout(() => {
2096
- console.log();
2097
- console.log(import_chalk.default.yellow(" Sign-in timed out."));
2098
- cleanup(null);
2099
- }, TIMEOUT_MS);
2100
- });
2034
+ function authStatePath(url) {
2035
+ const host = hostOf(url);
2036
+ return host ? (0, import_node_path.join)(AUTH_DIR, `${host}.json`) : null;
2101
2037
  }
2102
-
2103
- // src/ai/client.ts
2104
- var lastRateLimitInfo = null;
2105
- function getLastRateLimitInfo() {
2106
- return lastRateLimitInfo;
2038
+ function findAuthState(url) {
2039
+ const path = authStatePath(url);
2040
+ return path && (0, import_node_fs.existsSync)(path) ? path : void 0;
2107
2041
  }
2108
- function prompt(question) {
2109
- const rl = (0, import_node_readline.createInterface)({ input: process.stdin, output: process.stdout });
2110
- return new Promise((resolve9) => {
2111
- rl.question(question, (answer) => {
2112
- rl.close();
2113
- resolve9(answer.trim());
2114
- });
2115
- });
2042
+ async function saveAuthState(url, state) {
2043
+ const path = authStatePath(url);
2044
+ if (!path) throw new Error(`Not a valid URL: ${url}`);
2045
+ await (0, import_promises.mkdir)(AUTH_DIR, { recursive: true });
2046
+ await (0, import_promises.writeFile)(path, JSON.stringify(state, null, 2));
2047
+ await (0, import_promises.chmod)(path, 384);
2048
+ return path;
2116
2049
  }
2117
- async function setupUserEmail() {
2118
- console.log();
2119
- console.log(import_chalk2.default.yellow(" \u2709 Account Setup"));
2120
- console.log(import_chalk2.default.dim(" \u2500".repeat(20)));
2121
- console.log();
2122
- console.log(import_chalk2.default.white(" Sign in with your Google account to track usage"));
2123
- console.log(import_chalk2.default.white(" and unlock Pro features."));
2124
- const browserEmail = await browserLogin();
2125
- if (browserEmail) {
2126
- console.log();
2127
- console.log(import_chalk2.default.green(` \u2713 Signed in as ${browserEmail}`));
2128
- console.log();
2129
- return browserEmail;
2130
- }
2131
- console.log();
2132
- console.log(import_chalk2.default.dim(" Or enter your email manually:"));
2133
- const email = await prompt(import_chalk2.default.white(" Email: "));
2134
- if (!email) {
2135
- console.log();
2136
- console.log(import_chalk2.default.red(" No email provided. Exiting."));
2137
- process.exit(1);
2138
- }
2139
- if (!email.includes("@") || !email.includes(".")) {
2140
- console.log();
2141
- console.log(import_chalk2.default.yellow(" \u26A0 That doesn't look like a valid email address."));
2142
- const proceed = await prompt(import_chalk2.default.white(" Continue anyway? ") + import_chalk2.default.dim("[y/N] "));
2143
- if (proceed.toLowerCase() !== "y") {
2144
- process.exit(1);
2145
- }
2050
+ async function clearAuthState(url) {
2051
+ if (url) {
2052
+ const path = authStatePath(url);
2053
+ if (!path || !(0, import_node_fs.existsSync)(path)) return [];
2054
+ await (0, import_promises.unlink)(path);
2055
+ return [hostOf(url)];
2146
2056
  }
2147
- console.log();
2148
- console.log(import_chalk2.default.green(" \u2713 Email registered"));
2149
- console.log();
2150
- return email;
2057
+ if (!(0, import_node_fs.existsSync)(AUTH_DIR)) return [];
2058
+ const files = (await (0, import_promises.readdir)(AUTH_DIR)).filter((f) => f.endsWith(".json"));
2059
+ await Promise.all(files.map((f) => (0, import_promises.unlink)((0, import_node_path.join)(AUTH_DIR, f))));
2060
+ return files.map((f) => f.slice(0, -".json".length));
2151
2061
  }
2152
- async function callGroq(systemPrompt, userPrompt, logError2, _userEmail, tier = "free") {
2153
- const result = await callAI(systemPrompt, userPrompt, tier);
2154
- if (!result.success) {
2155
- await logError2("AI API", new Error(result.error));
2156
- if (result.isRateLimit) {
2157
- console.log();
2158
- console.log(import_chalk2.default.red(" \u26A0 Rate Limit Reached (after 3 retries)"));
2159
- console.log();
2160
- console.log(import_chalk2.default.dim(" Spark AI daily limit exceeded. You may have hit your quota."));
2161
- console.log(import_chalk2.default.dim(" Options:"));
2162
- if (result.retryAfterSeconds) {
2163
- const minutes = Math.ceil(result.retryAfterSeconds / 60);
2164
- console.log(import_chalk2.default.dim(` \u2022 Wait ${minutes} minutes until quota resets`));
2165
- } else {
2166
- console.log(import_chalk2.default.dim(" \u2022 Wait until tomorrow (limits reset at midnight UTC)"));
2167
- }
2168
- console.log(import_chalk2.default.dim(" \u2022 Check your usage at: https://sparkbrain.app/dashboard"));
2169
- console.log(import_chalk2.default.dim(" \u2022 Upgrade to a higher tier for more requests"));
2170
- console.log();
2171
- process.exit(1);
2172
- }
2173
- if (result.isAuthError) {
2174
- console.log();
2175
- console.log(import_chalk2.default.red(" \u26A0 Authentication Error"));
2176
- console.log();
2177
- console.log(import_chalk2.default.dim(` ${result.error}`));
2178
- console.log();
2179
- process.exit(1);
2180
- }
2181
- console.log();
2182
- console.log(import_chalk2.default.red(" \u26A0 AI Error"));
2183
- console.log();
2184
- const firstLine = result.error.split("\n")[0] ?? result.error;
2185
- console.log(import_chalk2.default.dim(` ${firstLine.slice(0, 80)}`));
2186
- console.log();
2187
- process.exit(1);
2188
- }
2189
- lastRateLimitInfo = result.limits;
2190
- return result.content;
2062
+ async function listAuthStates() {
2063
+ if (!(0, import_node_fs.existsSync)(AUTH_DIR)) return [];
2064
+ return (await (0, import_promises.readdir)(AUTH_DIR)).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -".json".length));
2191
2065
  }
2192
2066
 
2193
- // src/ai/narrate.ts
2194
- async function generateCaptionsForSteps(steps, url, title, lang, logError2, userEmail, pageContext, tier = "free") {
2195
- const langName = getLanguageName(lang);
2196
- const stepList = steps.map((s, i) => {
2197
- const parts = [`${i + 1}. ${s.action}`];
2198
- if (s.target) parts.push(`on "${s.target}"`);
2199
- if (s.value) parts.push(`= "${s.value}"`);
2200
- return parts.join(" ");
2201
- }).join("\n");
2202
- const systemPrompt = `You write professional demo video captions in ${langName}.
2203
- Given UI steps, return a JSON array of captions \u2014 one per step, in order.
2067
+ // src/help.ts
2068
+ var import_chalk = __toESM(require("chalk"));
2069
+ init_lib();
2070
+
2071
+ // src/logo.ts
2072
+ var import_figlet = __toESM(require("figlet"));
2073
+ var import_gradient_string = __toESM(require("gradient-string"));
2074
+ var DEFAULT_LOGO_CONFIG = {
2075
+ text: "QuickPeek",
2076
+ font: "Slant",
2077
+ horizontalLayout: "default",
2078
+ gradientColors: ["#00ffff", "#00d4ff", "#a855f7", "#ec4899"],
2079
+ suffix: " \u{1F440}"
2080
+ };
2081
+ function renderLogo(config = {}) {
2082
+ const resolved = { ...DEFAULT_LOGO_CONFIG, ...config };
2083
+ const ascii = import_figlet.default.textSync(resolved.text, {
2084
+ font: resolved.font,
2085
+ horizontalLayout: resolved.horizontalLayout
2086
+ });
2087
+ const colored = (0, import_gradient_string.default)(resolved.gradientColors)(ascii);
2088
+ return resolved.suffix ? `${colored}${resolved.suffix}` : colored;
2089
+ }
2090
+
2091
+ // src/utils/config-loader.ts
2092
+ var import_node_fs4 = require("fs");
2093
+ var import_promises5 = require("fs/promises");
2094
+ var import_node_os2 = require("os");
2095
+ var import_node_path7 = require("path");
2096
+ init_lib();
2097
+ async function loadConfig(_url, langOverride) {
2098
+ const configPath = (0, import_node_path7.join)(process.cwd(), CONFIG_FILE);
2099
+ if ((0, import_node_fs4.existsSync)(configPath)) {
2100
+ try {
2101
+ const content = await (0, import_promises5.readFile)(configPath, "utf-8");
2102
+ const userConfig = JSON.parse(content);
2103
+ const config = {
2104
+ lang: langOverride || userConfig.lang || "en",
2105
+ plan: { ...DEFAULT_CONFIG.plan, ...userConfig.plan },
2106
+ video: { ...DEFAULT_CONFIG.video, ...userConfig.video },
2107
+ audio: { ...DEFAULT_CONFIG.audio, ...userConfig.audio },
2108
+ music: userConfig.music,
2109
+ timing: { ...DEFAULT_CONFIG.timing, ...userConfig.timing },
2110
+ output: { ...DEFAULT_CONFIG.output, ...userConfig.output }
2111
+ };
2112
+ return { config, created: false };
2113
+ } catch {
2114
+ }
2115
+ }
2116
+ const fullDefaultConfig = {
2117
+ lang: "en",
2118
+ plan: { maxSteps: 8 },
2119
+ video: {
2120
+ width: 1080,
2121
+ height: 1920,
2122
+ fps: 30,
2123
+ quality: 0.5,
2124
+ preset: "fast",
2125
+ cursor: true,
2126
+ highlight: false,
2127
+ fadeIn: 500,
2128
+ // ms
2129
+ fadeOut: 500,
2130
+ // ms
2131
+ contrast: 0.25
2132
+ },
2133
+ audio: {
2134
+ // Voice is derived from lang setting via LANGUAGE_VOICES mapping
2135
+ // To override, use Edge TTS voice name like 'en-GB-SoniaNeural'
2136
+ speed: 1
2137
+ },
2138
+ music: {
2139
+ path: "",
2140
+ volume: 0.3
2141
+ },
2142
+ output: { dir: "./demo" }
2143
+ };
2144
+ await (0, import_promises5.writeFile)(configPath, JSON.stringify(fullDefaultConfig, null, 2));
2145
+ return { config: { ...DEFAULT_CONFIG, lang: langOverride || "en" }, created: true };
2146
+ }
2147
+ var WORK_DIR = ".quickpeek";
2148
+ async function prepareWorkDir() {
2149
+ const workDir = (0, import_node_path7.join)(process.cwd(), WORK_DIR);
2150
+ if ((0, import_node_fs4.existsSync)(workDir)) {
2151
+ await (0, import_promises5.rm)(workDir, { recursive: true });
2152
+ }
2153
+ await (0, import_promises5.mkdir)(workDir, { recursive: true });
2154
+ return workDir;
2155
+ }
2156
+ var USER_DATA_DIR = (0, import_node_path7.join)((0, import_node_os2.homedir)(), ".quickpeek");
2157
+ var USER_DATA_FILE = (0, import_node_path7.join)(USER_DATA_DIR, "user.json");
2158
+ async function loadUserData() {
2159
+ try {
2160
+ if ((0, import_node_fs4.existsSync)(USER_DATA_FILE)) {
2161
+ return JSON.parse(await (0, import_promises5.readFile)(USER_DATA_FILE, "utf-8"));
2162
+ }
2163
+ } catch {
2164
+ }
2165
+ return {};
2166
+ }
2167
+ async function saveUserData(userData) {
2168
+ if (!(0, import_node_fs4.existsSync)(USER_DATA_DIR)) {
2169
+ await (0, import_promises5.mkdir)(USER_DATA_DIR, { recursive: true });
2170
+ }
2171
+ const existing = await loadUserData();
2172
+ const merged = { ...existing, ...userData };
2173
+ await (0, import_promises5.writeFile)(USER_DATA_FILE, JSON.stringify(merged, null, 2));
2174
+ }
2175
+ var DEFAULT_CAPTIONS_CSS = `.caption {
2176
+ font-family: Arial, sans-serif;
2177
+ -webkit-text-stroke: 3px #000000;
2178
+ }
2179
+ .caption .regular {
2180
+ font-size: 56px;
2181
+ color: #FFFFFF;
2182
+ }
2183
+ .caption .highlight {
2184
+ font-size: 64px;
2185
+ color: #FFFF00;
2186
+ }
2187
+ `;
2188
+ function parseCaptionCSS(css) {
2189
+ const style = JSON.parse(JSON.stringify(DEFAULT_CAPTIONS));
2190
+ const captionBlock = css.match(/\.caption\s*\{([^}]+)\}/)?.[1] || "";
2191
+ const regularBlock = css.match(/\.caption\s+\.regular\s*\{([^}]+)\}/)?.[1] || "";
2192
+ const highlightBlock = css.match(/\.caption\s+\.highlight\s*\{([^}]+)\}/)?.[1] || "";
2193
+ const fontMatch = captionBlock.match(/font-family:\s*"?([^";,]+)/);
2194
+ if (fontMatch) style.font = fontMatch[1].trim();
2195
+ const strokeMatch = captionBlock.match(/-webkit-text-stroke:\s*(\d+)px\s*(rgba?\([^)]+\)|#[A-Fa-f0-9]{6})/);
2196
+ if (strokeMatch) {
2197
+ style.outlineWidth = parseInt(strokeMatch[1], 10);
2198
+ style.outline = rgbaToHex(strokeMatch[2]);
2199
+ }
2200
+ const shadowMatch = captionBlock.match(/text-shadow:\s*(\d+)px/);
2201
+ if (shadowMatch) style.shadow = parseInt(shadowMatch[1], 10);
2202
+ const marginMatch = captionBlock.match(/margin-bottom:\s*(\d+)/);
2203
+ if (marginMatch) style.marginBottom = parseInt(marginMatch[1], 10);
2204
+ const parseWordStyle = (block, target) => {
2205
+ const colorMatch = block.match(/[^-]color:\s*(#[A-Fa-f0-9]{6})/);
2206
+ if (colorMatch) target.color = colorMatch[1];
2207
+ const sizeMatch = block.match(/font-size:\s*(\d+)/);
2208
+ if (sizeMatch) target.size = parseInt(sizeMatch[1], 10);
2209
+ const weightMatch = block.match(/font-weight:\s*(bold|normal|\d+)/);
2210
+ if (weightMatch) target.bold = weightMatch[1] === "bold" || parseInt(weightMatch[1], 10) >= 700;
2211
+ };
2212
+ parseWordStyle(regularBlock, style.regular);
2213
+ parseWordStyle(highlightBlock, style.highlight);
2214
+ if (!regularBlock && !highlightBlock) {
2215
+ const colorMatch = captionBlock.match(/[^-]color:\s*(#[A-Fa-f0-9]{6})/);
2216
+ if (colorMatch) style.regular.color = colorMatch[1];
2217
+ const sizeMatch = captionBlock.match(/font-size:\s*(\d+)/);
2218
+ if (sizeMatch) {
2219
+ style.regular.size = parseInt(sizeMatch[1], 10);
2220
+ style.highlight.size = Math.round(style.regular.size * 1.15);
2221
+ }
2222
+ const highlightMatch = captionBlock.match(/-qp-highlight:\s*(#[A-Fa-f0-9]{6})/);
2223
+ if (highlightMatch) style.highlight.color = highlightMatch[1];
2224
+ const weightMatch = captionBlock.match(/font-weight:\s*(bold|normal|\d+)/);
2225
+ if (weightMatch) {
2226
+ const isBold = weightMatch[1] === "bold" || parseInt(weightMatch[1], 10) >= 700;
2227
+ style.regular.bold = isBold;
2228
+ style.highlight.bold = isBold;
2229
+ }
2230
+ }
2231
+ return style;
2232
+ }
2233
+ async function loadCaptions() {
2234
+ const captionsPath = (0, import_node_path7.join)(process.cwd(), "captions.css");
2235
+ if (!(0, import_node_fs4.existsSync)(captionsPath)) {
2236
+ await (0, import_promises5.writeFile)(captionsPath, DEFAULT_CAPTIONS_CSS);
2237
+ return DEFAULT_CAPTIONS;
2238
+ }
2239
+ try {
2240
+ const css = await (0, import_promises5.readFile)(captionsPath, "utf-8");
2241
+ return parseCaptionCSS(css);
2242
+ } catch {
2243
+ return DEFAULT_CAPTIONS;
2244
+ }
2245
+ }
2246
+ async function savePlan(plan, planPath) {
2247
+ const cleanPlan = {
2248
+ ...plan,
2249
+ steps: plan.steps.map(({ duration, ...step }) => step)
2250
+ };
2251
+ await (0, import_promises5.mkdir)((0, import_node_path7.dirname)(planPath), { recursive: true });
2252
+ await (0, import_promises5.writeFile)(planPath, JSON.stringify(cleanPlan, null, 2));
2253
+ }
2254
+
2255
+ // src/help.ts
2256
+ var cmd = import_chalk.default.cyan;
2257
+ var arg = import_chalk.default.green;
2258
+ var opt = import_chalk.default.magenta;
2259
+ var desc = import_chalk.default.white;
2260
+ var ex = import_chalk.default.dim;
2261
+ function pad(colored, visible, width) {
2262
+ return colored + " ".repeat(Math.max(0, width - visible.length));
2263
+ }
2264
+ function showLogo() {
2265
+ const logo = renderLogo();
2266
+ console.log(logo);
2267
+ console.log(import_chalk.default.dim(` v${VERSION} - Auto-generate demo videos with AI`));
2268
+ console.log(import_chalk.default.italic.dim(" Because Tutorial Videos are Overrated!\n"));
2269
+ }
2270
+ function formatUsageLines(status, tier) {
2271
+ const lines = [];
2272
+ lines.push(`Videos generated: ${status.usage_count}`);
2273
+ if (tier !== "pro" && status.trial) {
2274
+ const { uses_remaining, max_uses, days_remaining, is_expired } = status.trial;
2275
+ const used = max_uses - uses_remaining;
2276
+ lines.push(`Trial: ${used}/${max_uses} uses${is_expired ? " (expired)" : ` (${uses_remaining} remaining)`}`);
2277
+ if (!is_expired && days_remaining > 0) {
2278
+ lines.push(`Trial ends in ${days_remaining} day${days_remaining === 1 ? "" : "s"}`);
2279
+ }
2280
+ }
2281
+ if (tier === "pro" && status.subscription) {
2282
+ const end = status.subscription.current_period_end;
2283
+ if (status.subscription.is_lifetime) {
2284
+ lines.push("Lifetime access");
2285
+ } else if (isCanceling(status)) {
2286
+ const date = new Date(end).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
2287
+ lines.push(`Cancels: ${date}`);
2288
+ } else if (end) {
2289
+ const date = new Date(end).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
2290
+ lines.push(`Renews: ${date}`);
2291
+ }
2292
+ }
2293
+ return lines;
2294
+ }
2295
+ async function showAccount() {
2296
+ const userData = await loadUserData();
2297
+ if (!userData.email) {
2298
+ console.log(import_chalk.default.dim(" Not signed in. Run ") + cmd("qp login") + import_chalk.default.dim(" to sign in."));
2299
+ console.log();
2300
+ return;
2301
+ }
2302
+ const email = userData.email;
2303
+ const tier = userData.tier === "pro" ? "pro" : "free";
2304
+ const tierLabel = tier === "pro" ? import_chalk.default.green("Pro") : "Free";
2305
+ console.log(` ${tierLabel} plan ${import_chalk.default.dim("\xB7")} ${import_chalk.default.dim(email)}`);
2306
+ try {
2307
+ const status = await getStatusCached(email);
2308
+ for (const line of formatUsageLines(status, tier)) {
2309
+ console.log(import_chalk.default.dim(` ${line}`));
2310
+ }
2311
+ } catch {
2312
+ }
2313
+ console.log();
2314
+ }
2315
+ async function showHelp() {
2316
+ showLogo();
2317
+ await showAccount();
2318
+ const W = 28;
2319
+ const usage = [
2320
+ [`${cmd("qp")} ${arg("<url>")}`, `qp <url>`, "AI-generated demo video"],
2321
+ [
2322
+ `${cmd("qp")} ${arg("<url>")} ${opt("--script")} ${arg("<file>")}`,
2323
+ `qp <url> --script <file>`,
2324
+ "Use your own narration script"
2325
+ ],
2326
+ [`${cmd("qp capture")} ${arg("<url>")}`, `qp capture <url>`, "Record interactions manually"],
2327
+ [`${cmd("qp login")}`, `qp login`, "Sign in with your Google account"],
2328
+ [`${cmd("qp logout")}`, `qp logout`, "Sign out"],
2329
+ [`${cmd("qp auth")} ${arg("<url>")}`, `qp auth <url>`, "Save a signed-in session for a site you demo"],
2330
+ [`${cmd("qp auth clear")} ${arg("[url]")}`, `qp auth clear [url]`, "Forget saved sessions"],
2331
+ [`${cmd("qp init")}`, `qp init`, "Create config file"],
2332
+ [`${cmd("qp config")} ${arg("<key>")} ${arg("<value>")}`, `qp config <key> <value>`, "Update a config option"]
2333
+ ];
2334
+ const options = [
2335
+ [`${opt("lang=")}${arg("xx")}`, `lang=xx`, "Voice language (en, es, ar, zh, fr, de, ja...)"],
2336
+ [
2337
+ `${opt("-d")}, ${opt("--description")} ${arg('"..."')}`,
2338
+ `-d, --description "..."`,
2339
+ "Guide AI with an app description"
2340
+ ],
2341
+ [
2342
+ `${opt("-s")}, ${opt("--script")} ${arg("<file>")}`,
2343
+ `-s, --script <file>`,
2344
+ "Narration script (one caption per line)"
2345
+ ],
2346
+ [`${opt("-y")}, ${opt("--yes")}`, `-y, --yes`, "Skip confirmation prompts"],
2347
+ [`${opt("--skip-voice")}, ${opt("-sv")}`, `--skip-voice, -sv`, "Reuse existing voiceover"],
2348
+ [`${opt("-f")}, ${opt("--fresh")}`, `-f, --fresh`, "Clear cached voiceover and recording"]
2349
+ ];
2350
+ const examples = [
2351
+ `${ex("$")} ${cmd("qp")} ${arg("localhost:3000")}`,
2352
+ `${ex("$")} ${cmd("qp")} ${arg("myapp.com")} ${opt("lang=")}${arg("es")} ${opt("-d")} ${arg('"E-commerce checkout flow"')}`,
2353
+ `${ex("$")} ${cmd("qp")} ${arg("myapp.com")} ${opt("--script")} ${arg("narration.txt")}`,
2354
+ `${ex("$")} ${cmd("qp capture")} ${arg("localhost:3000")}`
2355
+ ];
2356
+ console.log();
2357
+ console.log(import_chalk.default.yellow.bold("USAGE"));
2358
+ for (const [colored, plain, d] of usage) {
2359
+ console.log(` ${pad(colored, plain, W)}${desc(d)}`);
2360
+ }
2361
+ console.log();
2362
+ console.log(import_chalk.default.yellow.bold("OPTIONS"));
2363
+ for (const [colored, plain, d] of options) {
2364
+ console.log(` ${pad(colored, plain, W)}${desc(d)}`);
2365
+ }
2366
+ console.log();
2367
+ console.log(import_chalk.default.yellow.bold("EXAMPLES"));
2368
+ for (const e of examples) {
2369
+ console.log(` ${e}`);
2370
+ }
2371
+ console.log();
2372
+ }
2373
+
2374
+ // src/utils/ui.ts
2375
+ var import_node_path8 = require("path");
2376
+ var import_node_readline = require("readline");
2377
+ var import_chalk2 = __toESM(require("chalk"));
2378
+ var import_ora = __toESM(require("ora"));
2379
+ var INDENT = " ";
2380
+ var autoYes = false;
2381
+ function setAutoYes(value) {
2382
+ autoYes = value;
2383
+ }
2384
+ function task(text) {
2385
+ return (0, import_ora.default)({ text, prefixText: INDENT, spinner: "dots" }).start();
2386
+ }
2387
+ function header(text) {
2388
+ console.log();
2389
+ console.log(import_chalk2.default.bold.cyan(`\u25A0 ${text}`));
2390
+ }
2391
+ function info(text) {
2392
+ console.log(`${INDENT}${import_chalk2.default.dim("\u2502")} ${import_chalk2.default.dim(text)}`);
2393
+ }
2394
+ function confirm(prompt2) {
2395
+ if (autoYes) return Promise.resolve(true);
2396
+ const rl = (0, import_node_readline.createInterface)({ input: process.stdin, output: process.stdout });
2397
+ return new Promise((res) => {
2398
+ rl.question(import_chalk2.default.yellow(`${prompt2} [Y/n] `), (answer) => {
2399
+ rl.close();
2400
+ res(answer.toLowerCase() !== "n");
2401
+ });
2402
+ });
2403
+ }
2404
+ function promptInput(prompt2) {
2405
+ if (autoYes) return Promise.resolve("");
2406
+ const rl = (0, import_node_readline.createInterface)({ input: process.stdin, output: process.stdout });
2407
+ return new Promise((res) => {
2408
+ rl.question(import_chalk2.default.cyan(`${INDENT}${prompt2}`), (answer) => {
2409
+ rl.close();
2410
+ res(answer.trim());
2411
+ });
2412
+ });
2413
+ }
2414
+ var _isWSL = process.platform === "linux" && process.env.WSL_DISTRO_NAME;
2415
+ function formatClickablePath(filePath) {
2416
+ const absolutePath = (0, import_node_path8.resolve)(filePath);
2417
+ if (_isWSL) {
2418
+ return absolutePath;
2419
+ }
2420
+ let formatted = absolutePath;
2421
+ formatted = formatted.replace(/\//g, "\\");
2422
+ if (formatted.includes(" ") && !formatted.startsWith('"')) {
2423
+ formatted = `"${formatted}"`;
2424
+ }
2425
+ return formatted;
2426
+ }
2427
+ function formatDuration(ms) {
2428
+ const mins = Math.floor(ms / 6e4);
2429
+ const secs = Math.floor(ms % 6e4 / 1e3);
2430
+ return `${mins}:${secs.toString().padStart(2, "0")}`;
2431
+ }
2432
+
2433
+ // src/commands/auth-cmd.ts
2434
+ function waitForEnter(prompt2) {
2435
+ return new Promise((resolve9) => {
2436
+ const rl = (0, import_node_readline2.createInterface)({ input: process.stdin, output: process.stdout });
2437
+ rl.question(prompt2, () => {
2438
+ rl.close();
2439
+ resolve9();
2440
+ });
2441
+ });
2442
+ }
2443
+ async function authCommand(url) {
2444
+ console.log();
2445
+ showLogo();
2446
+ if (!authStatePath(url)) {
2447
+ console.log(import_chalk3.default.red(` Not a valid URL: ${url}`));
2448
+ return;
2449
+ }
2450
+ console.log(import_chalk3.default.white(" A browser will open. Sign in to the site, then come back here."));
2451
+ console.log();
2452
+ const browser = await import_playwright2.chromium.launch({ headless: false });
2453
+ const context = await browser.newContext();
2454
+ const page = await context.newPage();
2455
+ try {
2456
+ await page.goto(url, { waitUntil: "domcontentloaded", timeout: 3e4 });
2457
+ } catch {
2458
+ }
2459
+ await waitForEnter(import_chalk3.default.cyan(" Press Enter when you are signed in... "));
2460
+ try {
2461
+ const state = await context.storageState();
2462
+ const path = await saveAuthState(url, state);
2463
+ console.log();
2464
+ console.log(import_chalk3.default.green(` \u2713 Session saved for ${new URL(url).host}`));
2465
+ console.log(import_chalk3.default.dim(` ${formatClickablePath(path)}`));
2466
+ console.log(import_chalk3.default.dim(" Crawls and recordings of this site now start signed in."));
2467
+ console.log(import_chalk3.default.dim(" Run ") + import_chalk3.default.cyan(`qp auth clear ${new URL(url).host}`) + import_chalk3.default.dim(" to forget it."));
2468
+ } finally {
2469
+ await browser.close().catch(() => {
2470
+ });
2471
+ }
2472
+ console.log();
2473
+ }
2474
+ async function authClearCommand(url) {
2475
+ console.log();
2476
+ const removed = await clearAuthState(url);
2477
+ if (removed.length === 0) {
2478
+ console.log(import_chalk3.default.dim(url ? ` No saved session for ${url}` : " No saved sessions."));
2479
+ } else {
2480
+ for (const host of removed) {
2481
+ console.log(import_chalk3.default.green(` \u2713 Forgot session for ${host}`));
2482
+ }
2483
+ }
2484
+ console.log();
2485
+ }
2486
+ async function authListCommand() {
2487
+ console.log();
2488
+ const hosts = await listAuthStates();
2489
+ if (hosts.length === 0) {
2490
+ console.log(import_chalk3.default.dim(" No saved sessions. Run ") + import_chalk3.default.cyan("qp auth <url>") + import_chalk3.default.dim(" to add one."));
2491
+ } else {
2492
+ console.log(import_chalk3.default.white(" Saved sessions:"));
2493
+ for (const host of hosts) {
2494
+ console.log(` ${import_chalk3.default.green("\u25CF")} ${host}`);
2495
+ }
2496
+ }
2497
+ console.log();
2498
+ }
2499
+
2500
+ // src/commands/capture-cmd.ts
2501
+ var import_node_path20 = require("path");
2502
+ var import_chalk11 = __toESM(require("chalk"));
2503
+
2504
+ // src/ai/narrate.ts
2505
+ init_lib();
2506
+
2507
+ // src/ai/client.ts
2508
+ var import_node_readline3 = require("readline");
2509
+ var import_chalk5 = __toESM(require("chalk"));
2510
+ init_lib();
2511
+
2512
+ // src/utils/browser-auth.ts
2513
+ var import_node_http = require("http");
2514
+ var import_node_url = require("url");
2515
+ var import_chalk4 = __toESM(require("chalk"));
2516
+ init_SparkStripe_feature_gating();
2517
+ var TIMEOUT_MS = 12e4;
2518
+ async function openBrowser(url) {
2519
+ const { exec: exec2 } = await import("child_process");
2520
+ const isWSL2 = process.platform === "linux" && Boolean(process.env.WSL_DISTRO_NAME);
2521
+ const command = isWSL2 ? `cmd.exe /c start "" "${url}"` : process.platform === "darwin" ? `open "${url}"` : `xdg-open "${url}"`;
2522
+ return new Promise((resolve9) => {
2523
+ exec2(command, () => resolve9());
2524
+ });
2525
+ }
2526
+ async function browserLogin() {
2527
+ return new Promise((resolve9) => {
2528
+ let server;
2529
+ let timer;
2530
+ const cleanup = (email) => {
2531
+ clearTimeout(timer);
2532
+ server?.close();
2533
+ resolve9(email);
2534
+ };
2535
+ server = (0, import_node_http.createServer)((req, res) => {
2536
+ const url = new import_node_url.URL(req.url || "/", `http://localhost`);
2537
+ const path = url.pathname;
2538
+ if (path === "/callback" || path === "/") {
2539
+ const email = url.searchParams.get("email");
2540
+ res.writeHead(200, { "Content-Type": "text/html" });
2541
+ res.end(`
2542
+ <!DOCTYPE html>
2543
+ <html>
2544
+ <head><title>QuickPeek</title><style>
2545
+ body { font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #0a0a0a; color: #fff; }
2546
+ .card { text-align: center; padding: 2rem; }
2547
+ h1 { font-size: 1.5rem; margin-bottom: 0.5rem; }
2548
+ p { color: #888; }
2549
+ </style></head>
2550
+ <body><div class="card">
2551
+ <h1>${email ? "Logged in!" : "Return to your terminal"}</h1>
2552
+ <p>${email ? `Signed in as ${email}` : "Complete setup in your terminal."}</p>
2553
+ <p style="margin-top: 1rem; font-size: 0.875rem;">You can close this tab.</p>
2554
+ </div></body>
2555
+ </html>
2556
+ `);
2557
+ if (email) {
2558
+ cleanup(email);
2559
+ }
2560
+ } else {
2561
+ res.writeHead(404);
2562
+ res.end();
2563
+ }
2564
+ });
2565
+ server.listen(0, "127.0.0.1", () => {
2566
+ const addr = server.address();
2567
+ if (!addr || typeof addr === "string") {
2568
+ cleanup(null);
2569
+ return;
2570
+ }
2571
+ const port = addr.port;
2572
+ const returnUrl = `http://localhost:${port}/callback`;
2573
+ const url = pricingUrl({ returnUrl });
2574
+ console.log();
2575
+ console.log(import_chalk4.default.cyan(" Opening browser to sign in..."));
2576
+ console.log(import_chalk4.default.dim(` ${url}`));
2577
+ console.log();
2578
+ console.log(import_chalk4.default.dim(" Waiting for sign-in (press Ctrl+C to cancel)..."));
2579
+ openBrowser(url);
2580
+ });
2581
+ timer = setTimeout(() => {
2582
+ console.log();
2583
+ console.log(import_chalk4.default.yellow(" Sign-in timed out."));
2584
+ cleanup(null);
2585
+ }, TIMEOUT_MS);
2586
+ });
2587
+ }
2588
+
2589
+ // src/ai/client.ts
2590
+ var lastRateLimitInfo = null;
2591
+ function getLastRateLimitInfo() {
2592
+ return lastRateLimitInfo;
2593
+ }
2594
+ function prompt(question) {
2595
+ const rl = (0, import_node_readline3.createInterface)({ input: process.stdin, output: process.stdout });
2596
+ return new Promise((resolve9) => {
2597
+ rl.question(question, (answer) => {
2598
+ rl.close();
2599
+ resolve9(answer.trim());
2600
+ });
2601
+ });
2602
+ }
2603
+ async function setupUserEmail() {
2604
+ console.log();
2605
+ console.log(import_chalk5.default.yellow(" \u2709 Account Setup"));
2606
+ console.log(import_chalk5.default.dim(" \u2500".repeat(20)));
2607
+ console.log();
2608
+ console.log(import_chalk5.default.white(" Sign in with your Google account to track usage"));
2609
+ console.log(import_chalk5.default.white(" and unlock Pro features."));
2610
+ const browserEmail = await browserLogin();
2611
+ if (browserEmail) {
2612
+ console.log();
2613
+ console.log(import_chalk5.default.green(` \u2713 Signed in as ${browserEmail}`));
2614
+ console.log();
2615
+ return browserEmail;
2616
+ }
2617
+ console.log();
2618
+ console.log(import_chalk5.default.dim(" Or enter your email manually:"));
2619
+ const email = await prompt(import_chalk5.default.white(" Email: "));
2620
+ if (!email) {
2621
+ console.log();
2622
+ console.log(import_chalk5.default.red(" No email provided. Exiting."));
2623
+ process.exit(1);
2624
+ }
2625
+ if (!email.includes("@") || !email.includes(".")) {
2626
+ console.log();
2627
+ console.log(import_chalk5.default.yellow(" \u26A0 That doesn't look like a valid email address."));
2628
+ const proceed = await prompt(import_chalk5.default.white(" Continue anyway? ") + import_chalk5.default.dim("[y/N] "));
2629
+ if (proceed.toLowerCase() !== "y") {
2630
+ process.exit(1);
2631
+ }
2632
+ }
2633
+ console.log();
2634
+ console.log(import_chalk5.default.green(" \u2713 Email registered"));
2635
+ console.log();
2636
+ return email;
2637
+ }
2638
+ async function callGroq(systemPrompt, userPrompt, logError2, userEmail, tier = "free") {
2639
+ const result = process.env.SPARK_AI_KEY ? await callAI(systemPrompt, userPrompt, tier) : await callAIViaRelay(systemPrompt, userPrompt, userEmail ?? "", tier);
2640
+ if (!result.success) {
2641
+ await logError2("AI API", new Error(result.error));
2642
+ if (result.isRateLimit) {
2643
+ console.log();
2644
+ console.log(import_chalk5.default.red(" \u26A0 Rate Limit Reached (after 3 retries)"));
2645
+ console.log();
2646
+ console.log(import_chalk5.default.dim(" Spark AI daily limit exceeded. You may have hit your quota."));
2647
+ console.log(import_chalk5.default.dim(" Options:"));
2648
+ if (result.retryAfterSeconds) {
2649
+ const minutes = Math.ceil(result.retryAfterSeconds / 60);
2650
+ console.log(import_chalk5.default.dim(` \u2022 Wait ${minutes} minutes until quota resets`));
2651
+ } else {
2652
+ console.log(import_chalk5.default.dim(" \u2022 Wait until tomorrow (limits reset at midnight UTC)"));
2653
+ }
2654
+ console.log(import_chalk5.default.dim(" \u2022 Check your usage at: https://sparkbrain.app/dashboard"));
2655
+ console.log(import_chalk5.default.dim(" \u2022 Upgrade to a higher tier for more requests"));
2656
+ console.log();
2657
+ process.exit(1);
2658
+ }
2659
+ if (result.isAuthError) {
2660
+ console.log();
2661
+ console.log(import_chalk5.default.red(" \u26A0 Authentication Error"));
2662
+ console.log();
2663
+ console.log(import_chalk5.default.dim(` ${result.error}`));
2664
+ console.log();
2665
+ process.exit(1);
2666
+ }
2667
+ console.log();
2668
+ console.log(import_chalk5.default.red(" \u26A0 AI Error"));
2669
+ console.log();
2670
+ const firstLine = result.error.split("\n")[0] ?? result.error;
2671
+ console.log(import_chalk5.default.dim(` ${firstLine.slice(0, 80)}`));
2672
+ console.log();
2673
+ process.exit(1);
2674
+ }
2675
+ lastRateLimitInfo = result.limits;
2676
+ return result.content;
2677
+ }
2678
+
2679
+ // src/ai/narrate.ts
2680
+ async function generateCaptionsForSteps(steps, url, title, lang, logError2, userEmail, pageContext, tier = "free") {
2681
+ const langName = getLanguageName(lang);
2682
+ const stepList = steps.map((s, i) => {
2683
+ const parts = [`${i + 1}. ${s.action}`];
2684
+ if (s.target) parts.push(`on "${s.target}"`);
2685
+ if (s.value) parts.push(`= "${s.value}"`);
2686
+ return parts.join(" ");
2687
+ }).join("\n");
2688
+ const systemPrompt = `You write professional demo video captions in ${langName}.
2689
+ Given UI steps, return a JSON array of captions \u2014 one per step, in order.
2204
2690
 
2205
2691
  RULES:
2206
2692
  - Return ONLY a valid JSON array of strings, no explanation
@@ -2374,14 +2860,14 @@ function normalizeUrl2(url) {
2374
2860
  // src/capture.ts
2375
2861
  var import_node_child_process4 = require("child_process");
2376
2862
  var import_node_http3 = require("http");
2377
- var import_node_readline2 = require("readline");
2378
- var import_playwright3 = require("playwright");
2863
+ var import_node_readline4 = require("readline");
2864
+ var import_playwright4 = require("playwright");
2379
2865
 
2380
2866
  // src/browser/wsl.ts
2381
2867
  var import_node_child_process3 = require("child_process");
2382
- var import_node_fs3 = require("fs");
2868
+ var import_node_fs5 = require("fs");
2383
2869
  var import_node_http2 = require("http");
2384
- var import_playwright2 = require("playwright");
2870
+ var import_playwright3 = require("playwright");
2385
2871
  function isWSL() {
2386
2872
  if (process.platform !== "linux") return false;
2387
2873
  try {
@@ -2392,7 +2878,7 @@ function isWSL() {
2392
2878
  }
2393
2879
  }
2394
2880
  function tryEnableWSLInterop() {
2395
- return (0, import_node_fs3.existsSync)("/proc/sys/fs/binfmt_misc/WSLInterop");
2881
+ return (0, import_node_fs5.existsSync)("/proc/sys/fs/binfmt_misc/WSLInterop");
2396
2882
  }
2397
2883
  function wslToWindowsPath(wslPath) {
2398
2884
  if (!wslPath) return wslPath;
@@ -2401,7 +2887,7 @@ function wslToWindowsPath(wslPath) {
2401
2887
  return `${m[1].toUpperCase()}:\\${m[2].replace(/\//g, "\\")}`;
2402
2888
  }
2403
2889
  function findExe(whereExeQuery, ...hardcoded) {
2404
- for (const p of hardcoded) if ((0, import_node_fs3.existsSync)(p)) return p;
2890
+ for (const p of hardcoded) if ((0, import_node_fs5.existsSync)(p)) return p;
2405
2891
  try {
2406
2892
  const line = (0, import_node_child_process3.execSync)(`where.exe ${whereExeQuery}`, { encoding: "utf-8", timeout: 3e3 }).split("\n")[0]?.trim();
2407
2893
  if (line) return line.replace(/^([A-Za-z]):\\/, (_, d) => `/mnt/${d.toLowerCase()}/`).replace(/\\/g, "/");
@@ -2424,7 +2910,7 @@ function getWindowsHostIP() {
2424
2910
  } catch {
2425
2911
  }
2426
2912
  try {
2427
- const m = (0, import_node_fs3.readFileSync)("/etc/resolv.conf", "utf-8").match(/nameserver\s+(\d+\.\d+\.\d+\.\d+)/);
2913
+ const m = (0, import_node_fs5.readFileSync)("/etc/resolv.conf", "utf-8").match(/nameserver\s+(\d+\.\d+\.\d+\.\d+)/);
2428
2914
  if (m?.[1]) return m[1];
2429
2915
  } catch {
2430
2916
  }
@@ -2500,7 +2986,7 @@ async function launchWindowsChrome(extensionPath, userDataDir, options = {}) {
2500
2986
  const { proc: relay, port: proxyPort } = await startRelay(nodePath, windowsHost, debugPort);
2501
2987
  try {
2502
2988
  await waitForChrome(windowsHost, proxyPort);
2503
- const browser = await import_playwright2.chromium.connectOverCDP(`http://${windowsHost}:${proxyPort}`, { timeout: 5e3 });
2989
+ const browser = await import_playwright3.chromium.connectOverCDP(`http://${windowsHost}:${proxyPort}`, { timeout: 5e3 });
2504
2990
  return {
2505
2991
  browser,
2506
2992
  cleanup: async () => {
@@ -2629,9 +3115,9 @@ var EVENT_TRACKER_SCRIPT = `
2629
3115
  }, { passive: true, capture: true });
2630
3116
  }());
2631
3117
  `;
2632
- function waitForEnter() {
3118
+ function waitForEnter2() {
2633
3119
  return new Promise((resolve9) => {
2634
- const rl = (0, import_node_readline2.createInterface)({ input: process.stdin, output: process.stdout });
3120
+ const rl = (0, import_node_readline4.createInterface)({ input: process.stdin, output: process.stdout });
2635
3121
  process.stdout.write(" Press Enter when done...\n");
2636
3122
  rl.once("line", () => {
2637
3123
  rl.close();
@@ -2660,7 +3146,7 @@ async function launchCaptureChrome() {
2660
3146
  req.end();
2661
3147
  });
2662
3148
  if (alreadyUp) {
2663
- const browser = await import_playwright3.chromium.connectOverCDP(`http://${windowsHost}:${probe.port}`, { timeout: 5e3 });
3149
+ const browser = await import_playwright4.chromium.connectOverCDP(`http://${windowsHost}:${probe.port}`, { timeout: 5e3 });
2664
3150
  return { browser, relay: probe.proc };
2665
3151
  }
2666
3152
  probe.proc.kill();
@@ -2680,7 +3166,7 @@ async function launchCaptureChrome() {
2680
3166
  const relay = await startRelay(nodePath, windowsHost, debugPort);
2681
3167
  try {
2682
3168
  await waitForChrome(windowsHost, relay.port);
2683
- const browser = await import_playwright3.chromium.connectOverCDP(`http://${windowsHost}:${relay.port}`, { timeout: 5e3 });
3169
+ const browser = await import_playwright4.chromium.connectOverCDP(`http://${windowsHost}:${relay.port}`, { timeout: 5e3 });
2684
3170
  return { browser, relay: relay.proc };
2685
3171
  } catch (err) {
2686
3172
  relay.proc.kill();
@@ -2695,7 +3181,7 @@ async function captureUserSession(url, _workDir) {
2695
3181
  if (isWSL()) {
2696
3182
  ({ browser, relay } = await launchCaptureChrome());
2697
3183
  } else {
2698
- browser = await import_playwright3.chromium.launch({
3184
+ browser = await import_playwright4.chromium.launch({
2699
3185
  headless: false,
2700
3186
  args: ["--start-maximized", "--no-first-run"]
2701
3187
  });
@@ -2737,7 +3223,7 @@ async function captureUserSession(url, _workDir) {
2737
3223
  return [title, parts.join(" | ")];
2738
3224
  }).catch(() => ["", ""]);
2739
3225
  startTime = Date.now();
2740
- await waitForEnter();
3226
+ await waitForEnter2();
2741
3227
  const durationMs = Date.now() - startTime;
2742
3228
  if (relay) {
2743
3229
  try {
@@ -2752,195 +3238,31 @@ async function captureUserSession(url, _workDir) {
2752
3238
  return { events, durationMs, pageTitle, pageContext };
2753
3239
  }
2754
3240
 
2755
- // src/utils/config-loader.ts
2756
- var import_node_fs4 = require("fs");
2757
- var import_promises4 = require("fs/promises");
2758
- var import_node_os = require("os");
2759
- var import_node_path6 = require("path");
2760
- init_lib();
2761
- async function loadConfig(_url, langOverride) {
2762
- const configPath = (0, import_node_path6.join)(process.cwd(), CONFIG_FILE);
2763
- if ((0, import_node_fs4.existsSync)(configPath)) {
2764
- try {
2765
- const content = await (0, import_promises4.readFile)(configPath, "utf-8");
2766
- const userConfig = JSON.parse(content);
2767
- const config = {
2768
- lang: langOverride || userConfig.lang || "en",
2769
- plan: { ...DEFAULT_CONFIG.plan, ...userConfig.plan },
2770
- video: { ...DEFAULT_CONFIG.video, ...userConfig.video },
2771
- audio: { ...DEFAULT_CONFIG.audio, ...userConfig.audio },
2772
- music: userConfig.music,
2773
- timing: { ...DEFAULT_CONFIG.timing, ...userConfig.timing },
2774
- output: { ...DEFAULT_CONFIG.output, ...userConfig.output }
2775
- };
2776
- return { config, created: false };
2777
- } catch {
2778
- }
2779
- }
2780
- const fullDefaultConfig = {
2781
- lang: "en",
2782
- plan: { maxSteps: 8 },
2783
- video: {
2784
- width: 1080,
2785
- height: 1920,
2786
- fps: 30,
2787
- quality: 0.5,
2788
- preset: "fast",
2789
- cursor: true,
2790
- highlight: false,
2791
- fadeIn: 500,
2792
- // ms
2793
- fadeOut: 500,
2794
- // ms
2795
- contrast: 0.25
2796
- },
2797
- audio: {
2798
- // Voice is derived from lang setting via LANGUAGE_VOICES mapping
2799
- // To override, use Edge TTS voice name like 'en-GB-SoniaNeural'
2800
- speed: 1
2801
- },
2802
- music: {
2803
- path: "",
2804
- volume: 0.3
2805
- },
2806
- output: { dir: "./demo" }
2807
- };
2808
- await (0, import_promises4.writeFile)(configPath, JSON.stringify(fullDefaultConfig, null, 2));
2809
- return { config: { ...DEFAULT_CONFIG, lang: langOverride || "en" }, created: true };
2810
- }
2811
- var WORK_DIR = ".quickpeek";
2812
- async function prepareWorkDir() {
2813
- const workDir = (0, import_node_path6.join)(process.cwd(), WORK_DIR);
2814
- if ((0, import_node_fs4.existsSync)(workDir)) {
2815
- await (0, import_promises4.rm)(workDir, { recursive: true });
2816
- }
2817
- await (0, import_promises4.mkdir)(workDir, { recursive: true });
2818
- return workDir;
2819
- }
2820
- var USER_DATA_DIR = (0, import_node_path6.join)((0, import_node_os.homedir)(), ".quickpeek");
2821
- var USER_DATA_FILE = (0, import_node_path6.join)(USER_DATA_DIR, "user.json");
2822
- async function loadUserData() {
2823
- try {
2824
- if ((0, import_node_fs4.existsSync)(USER_DATA_FILE)) {
2825
- return JSON.parse(await (0, import_promises4.readFile)(USER_DATA_FILE, "utf-8"));
2826
- }
2827
- } catch {
2828
- }
2829
- return {};
2830
- }
2831
- async function saveUserData(userData) {
2832
- if (!(0, import_node_fs4.existsSync)(USER_DATA_DIR)) {
2833
- await (0, import_promises4.mkdir)(USER_DATA_DIR, { recursive: true });
2834
- }
2835
- const existing = await loadUserData();
2836
- const merged = { ...existing, ...userData };
2837
- await (0, import_promises4.writeFile)(USER_DATA_FILE, JSON.stringify(merged, null, 2));
2838
- }
2839
- var DEFAULT_CAPTIONS_CSS = `.caption {
2840
- font-family: Arial, sans-serif;
2841
- -webkit-text-stroke: 3px #000000;
2842
- }
2843
- .caption .regular {
2844
- font-size: 56px;
2845
- color: #FFFFFF;
2846
- }
2847
- .caption .highlight {
2848
- font-size: 64px;
2849
- color: #FFFF00;
2850
- }
2851
- `;
2852
- function parseCaptionCSS(css) {
2853
- const style = JSON.parse(JSON.stringify(DEFAULT_CAPTIONS));
2854
- const captionBlock = css.match(/\.caption\s*\{([^}]+)\}/)?.[1] || "";
2855
- const regularBlock = css.match(/\.caption\s+\.regular\s*\{([^}]+)\}/)?.[1] || "";
2856
- const highlightBlock = css.match(/\.caption\s+\.highlight\s*\{([^}]+)\}/)?.[1] || "";
2857
- const fontMatch = captionBlock.match(/font-family:\s*"?([^";,]+)/);
2858
- if (fontMatch) style.font = fontMatch[1].trim();
2859
- const strokeMatch = captionBlock.match(/-webkit-text-stroke:\s*(\d+)px\s*(rgba?\([^)]+\)|#[A-Fa-f0-9]{6})/);
2860
- if (strokeMatch) {
2861
- style.outlineWidth = parseInt(strokeMatch[1], 10);
2862
- style.outline = rgbaToHex(strokeMatch[2]);
2863
- }
2864
- const shadowMatch = captionBlock.match(/text-shadow:\s*(\d+)px/);
2865
- if (shadowMatch) style.shadow = parseInt(shadowMatch[1], 10);
2866
- const marginMatch = captionBlock.match(/margin-bottom:\s*(\d+)/);
2867
- if (marginMatch) style.marginBottom = parseInt(marginMatch[1], 10);
2868
- const parseWordStyle = (block, target) => {
2869
- const colorMatch = block.match(/[^-]color:\s*(#[A-Fa-f0-9]{6})/);
2870
- if (colorMatch) target.color = colorMatch[1];
2871
- const sizeMatch = block.match(/font-size:\s*(\d+)/);
2872
- if (sizeMatch) target.size = parseInt(sizeMatch[1], 10);
2873
- const weightMatch = block.match(/font-weight:\s*(bold|normal|\d+)/);
2874
- if (weightMatch) target.bold = weightMatch[1] === "bold" || parseInt(weightMatch[1], 10) >= 700;
2875
- };
2876
- parseWordStyle(regularBlock, style.regular);
2877
- parseWordStyle(highlightBlock, style.highlight);
2878
- if (!regularBlock && !highlightBlock) {
2879
- const colorMatch = captionBlock.match(/[^-]color:\s*(#[A-Fa-f0-9]{6})/);
2880
- if (colorMatch) style.regular.color = colorMatch[1];
2881
- const sizeMatch = captionBlock.match(/font-size:\s*(\d+)/);
2882
- if (sizeMatch) {
2883
- style.regular.size = parseInt(sizeMatch[1], 10);
2884
- style.highlight.size = Math.round(style.regular.size * 1.15);
2885
- }
2886
- const highlightMatch = captionBlock.match(/-qp-highlight:\s*(#[A-Fa-f0-9]{6})/);
2887
- if (highlightMatch) style.highlight.color = highlightMatch[1];
2888
- const weightMatch = captionBlock.match(/font-weight:\s*(bold|normal|\d+)/);
2889
- if (weightMatch) {
2890
- const isBold = weightMatch[1] === "bold" || parseInt(weightMatch[1], 10) >= 700;
2891
- style.regular.bold = isBold;
2892
- style.highlight.bold = isBold;
2893
- }
2894
- }
2895
- return style;
2896
- }
2897
- async function loadCaptions() {
2898
- const captionsPath = (0, import_node_path6.join)(process.cwd(), "captions.css");
2899
- if (!(0, import_node_fs4.existsSync)(captionsPath)) {
2900
- await (0, import_promises4.writeFile)(captionsPath, DEFAULT_CAPTIONS_CSS);
2901
- return DEFAULT_CAPTIONS;
2902
- }
2903
- try {
2904
- const css = await (0, import_promises4.readFile)(captionsPath, "utf-8");
2905
- return parseCaptionCSS(css);
2906
- } catch {
2907
- return DEFAULT_CAPTIONS;
2908
- }
2909
- }
2910
- async function savePlan(plan, planPath) {
2911
- const cleanPlan = {
2912
- ...plan,
2913
- steps: plan.steps.map(({ duration, ...step }) => step)
2914
- };
2915
- await (0, import_promises4.mkdir)((0, import_node_path6.dirname)(planPath), { recursive: true });
2916
- await (0, import_promises4.writeFile)(planPath, JSON.stringify(cleanPlan, null, 2));
2917
- }
2918
-
2919
3241
  // src/utils/logging.ts
2920
- var import_promises5 = require("fs/promises");
2921
- var import_node_path7 = require("path");
3242
+ var import_promises6 = require("fs/promises");
3243
+ var import_node_path9 = require("path");
2922
3244
  var LOG_FILE_NAME = "quickpeek.log";
2923
3245
  var logFileInitialized = false;
2924
3246
  var logFilePath = LOG_FILE_NAME;
2925
3247
  async function writeLogEntry(entry) {
2926
3248
  try {
2927
3249
  if (!logFileInitialized) {
2928
- await (0, import_promises5.writeFile)(logFilePath, entry);
3250
+ await (0, import_promises6.writeFile)(logFilePath, entry);
2929
3251
  logFileInitialized = true;
2930
3252
  } else {
2931
- await (0, import_promises5.appendFile)(logFilePath, entry);
3253
+ await (0, import_promises6.appendFile)(logFilePath, entry);
2932
3254
  }
2933
3255
  } catch {
2934
3256
  }
2935
3257
  }
2936
3258
  async function setLogFilePath(outputDir) {
2937
- logFilePath = (0, import_node_path7.join)(outputDir, LOG_FILE_NAME);
3259
+ logFilePath = (0, import_node_path9.join)(outputDir, LOG_FILE_NAME);
2938
3260
  const header2 = `QuickPeek Log - ${(/* @__PURE__ */ new Date()).toISOString()}
2939
3261
  ${"=".repeat(60)}
2940
3262
 
2941
3263
  `;
2942
3264
  try {
2943
- await (0, import_promises5.writeFile)(logFilePath, header2);
3265
+ await (0, import_promises6.writeFile)(logFilePath, header2);
2944
3266
  logFileInitialized = true;
2945
3267
  } catch {
2946
3268
  }
@@ -2974,95 +3296,36 @@ async function logStep(stepNum, action, details) {
2974
3296
  `);
2975
3297
  }
2976
3298
 
2977
- // src/utils/ui.ts
2978
- var import_node_path8 = require("path");
2979
- var import_node_readline3 = require("readline");
2980
- var import_chalk3 = __toESM(require("chalk"));
2981
- var import_ora = __toESM(require("ora"));
2982
- var INDENT = " ";
2983
- var autoYes = false;
2984
- function setAutoYes(value) {
2985
- autoYes = value;
2986
- }
2987
- function task(text) {
2988
- return (0, import_ora.default)({ text, prefixText: INDENT, spinner: "dots" }).start();
2989
- }
2990
- function header(text) {
2991
- console.log();
2992
- console.log(import_chalk3.default.bold.cyan(`\u25A0 ${text}`));
2993
- }
2994
- function info(text) {
2995
- console.log(`${INDENT}${import_chalk3.default.dim("\u2502")} ${import_chalk3.default.dim(text)}`);
2996
- }
2997
- function confirm(prompt2) {
2998
- if (autoYes) return Promise.resolve(true);
2999
- const rl = (0, import_node_readline3.createInterface)({ input: process.stdin, output: process.stdout });
3000
- return new Promise((res) => {
3001
- rl.question(import_chalk3.default.yellow(`${prompt2} [Y/n] `), (answer) => {
3002
- rl.close();
3003
- res(answer.toLowerCase() !== "n");
3004
- });
3005
- });
3006
- }
3007
- function promptInput(prompt2) {
3008
- if (autoYes) return Promise.resolve("");
3009
- const rl = (0, import_node_readline3.createInterface)({ input: process.stdin, output: process.stdout });
3010
- return new Promise((res) => {
3011
- rl.question(import_chalk3.default.cyan(`${INDENT}${prompt2}`), (answer) => {
3012
- rl.close();
3013
- res(answer.trim());
3014
- });
3015
- });
3016
- }
3017
- var _isWSL = process.platform === "linux" && process.env.WSL_DISTRO_NAME;
3018
- function formatClickablePath(filePath) {
3019
- const absolutePath = (0, import_node_path8.resolve)(filePath);
3020
- if (_isWSL) {
3021
- return absolutePath;
3022
- }
3023
- let formatted = absolutePath;
3024
- formatted = formatted.replace(/\//g, "\\");
3025
- if (formatted.includes(" ") && !formatted.startsWith('"')) {
3026
- formatted = `"${formatted}"`;
3027
- }
3028
- return formatted;
3029
- }
3030
- function formatDuration(ms) {
3031
- const mins = Math.floor(ms / 6e4);
3032
- const secs = Math.floor(ms % 6e4 / 1e3);
3033
- return `${mins}:${secs.toString().padStart(2, "0")}`;
3034
- }
3035
-
3036
3299
  // src/commands/phases/video-phase.ts
3037
- var import_node_fs11 = require("fs");
3038
- var import_promises12 = require("fs/promises");
3039
- var import_node_path17 = require("path");
3300
+ var import_node_fs12 = require("fs");
3301
+ var import_promises13 = require("fs/promises");
3302
+ var import_node_path18 = require("path");
3040
3303
  init_lib();
3041
3304
 
3042
3305
  // src/record.ts
3043
- var import_node_fs9 = require("fs");
3044
- var import_promises9 = require("fs/promises");
3045
- var import_node_path14 = require("path");
3046
- var import_chalk4 = __toESM(require("chalk"));
3306
+ var import_node_fs10 = require("fs");
3307
+ var import_promises10 = require("fs/promises");
3308
+ var import_node_path15 = require("path");
3309
+ var import_chalk6 = __toESM(require("chalk"));
3047
3310
 
3048
3311
  // src/browser/actions.ts
3049
- var import_node_fs6 = require("fs");
3050
- var import_promises6 = require("fs/promises");
3051
- var import_node_path10 = require("path");
3312
+ var import_node_fs7 = require("fs");
3313
+ var import_promises7 = require("fs/promises");
3314
+ var import_node_path11 = require("path");
3052
3315
 
3053
3316
  // src/browser/effects.ts
3054
- var import_node_fs5 = require("fs");
3055
- var import_node_path9 = require("path");
3317
+ var import_node_fs6 = require("fs");
3318
+ var import_node_path10 = require("path");
3056
3319
  init_lib();
3057
3320
  function loadHighlightCSS() {
3058
3321
  const candidates = [
3059
- (0, import_node_path9.join)(__dirname, "highlight.css"),
3322
+ (0, import_node_path10.join)(__dirname, "highlight.css"),
3060
3323
  // dist/ build
3061
- (0, import_node_path9.join)(__dirname, "..", "highlight.css")
3324
+ (0, import_node_path10.join)(__dirname, "..", "highlight.css")
3062
3325
  // src/browser/ → src/
3063
3326
  ];
3064
3327
  for (const p of candidates) {
3065
- if ((0, import_node_fs5.existsSync)(p)) return (0, import_node_fs5.readFileSync)(p, "utf-8");
3328
+ if ((0, import_node_fs6.existsSync)(p)) return (0, import_node_fs6.readFileSync)(p, "utf-8");
3066
3329
  }
3067
3330
  return "";
3068
3331
  }
@@ -3403,8 +3666,8 @@ async function executeStep(page, step, plan, config, cursorInjected, baseDir) {
3403
3666
  const isPlaceholder = !rawPath || rawPath.startsWith("[") || rawPath.includes("example");
3404
3667
  if (!isPlaceholder) {
3405
3668
  const normalizedPath = rawPath.startsWith("mnt/") ? `/${rawPath}` : rawPath;
3406
- const filePath = (0, import_node_path10.isAbsolute)(normalizedPath) ? normalizedPath : (0, import_node_path10.resolve)(baseDir, normalizedPath);
3407
- if ((0, import_node_fs6.existsSync)(filePath)) {
3669
+ const filePath = (0, import_node_path11.isAbsolute)(normalizedPath) ? normalizedPath : (0, import_node_path11.resolve)(baseDir, normalizedPath);
3670
+ if ((0, import_node_fs7.existsSync)(filePath)) {
3408
3671
  await fileInput.setInputFiles(filePath);
3409
3672
  await page.waitForTimeout(2e3);
3410
3673
  break;
@@ -3520,8 +3783,8 @@ async function executeStep(page, step, plan, config, cursorInjected, baseDir) {
3520
3783
  case "wait":
3521
3784
  break;
3522
3785
  case "image": {
3523
- const imagePath = step.value ? (0, import_node_path10.isAbsolute)(step.value) ? step.value : (0, import_node_path10.resolve)(baseDir, step.value) : null;
3524
- if (imagePath && (0, import_node_fs6.existsSync)(imagePath)) {
3786
+ const imagePath = step.value ? (0, import_node_path11.isAbsolute)(step.value) ? step.value : (0, import_node_path11.resolve)(baseDir, step.value) : null;
3787
+ if (imagePath && (0, import_node_fs7.existsSync)(imagePath)) {
3525
3788
  const imgStyle = step.target === "stretch" ? `width: ${config.video.width}px; height: ${config.video.height}px; object-fit: cover;` : "max-width: 90%; max-height: 90%;";
3526
3789
  if (await showImagePage(page, imagePath, imgStyle, config, cursorInjected)) injected = true;
3527
3790
  }
@@ -3551,7 +3814,7 @@ async function executeStep(page, step, plan, config, cursorInjected, baseDir) {
3551
3814
  await pendingDownload;
3552
3815
  pendingDownload = null;
3553
3816
  }
3554
- if (lastDownloadPath && (0, import_node_fs6.existsSync)(lastDownloadPath)) {
3817
+ if (lastDownloadPath && (0, import_node_fs7.existsSync)(lastDownloadPath)) {
3555
3818
  const imgStyle = step.target === "stretch" ? "width: 100%; height: 100%; object-fit: contain;" : "max-width: 90%; max-height: 90%; object-fit: contain;";
3556
3819
  if (await showImagePage(page, lastDownloadPath, imgStyle, config, cursorInjected)) injected = true;
3557
3820
  }
@@ -3562,7 +3825,7 @@ async function executeStep(page, step, plan, config, cursorInjected, baseDir) {
3562
3825
  async function showImagePage(page, filePath, imgStyle, config, cursorInjected) {
3563
3826
  const { width, height } = config.video;
3564
3827
  const ext = filePath.split(".").pop()?.toLowerCase() || "";
3565
- const fileBuffer = await (0, import_promises6.readFile)(filePath);
3828
+ const fileBuffer = await (0, import_promises7.readFile)(filePath);
3566
3829
  const base64 = fileBuffer.toString("base64");
3567
3830
  const mimeTypes = {
3568
3831
  png: "image/png",
@@ -3592,17 +3855,18 @@ async function showImagePage(page, filePath, imgStyle, config, cursorInjected) {
3592
3855
  }
3593
3856
 
3594
3857
  // src/browser/launch.ts
3595
- var import_promises7 = require("fs/promises");
3596
- var import_playwright4 = require("playwright");
3597
- async function launchBrowserContext(config, userDataDir, recordVideo) {
3858
+ var import_promises8 = require("fs/promises");
3859
+ var import_playwright5 = require("playwright");
3860
+ async function launchBrowserContext(config, userDataDir, recordVideo, storageStatePath) {
3598
3861
  const extensionPath = config.browser?.extension;
3599
3862
  const viewport = { width: config.video.width, height: config.video.height };
3600
3863
  const videoOpts = recordVideo ? { recordVideo } : {};
3864
+ const authOpts = storageStatePath ? { storageState: storageStatePath } : {};
3601
3865
  let context;
3602
3866
  let browser = null;
3603
3867
  let chromeSession = null;
3604
3868
  if (extensionPath) {
3605
- await (0, import_promises7.mkdir)(userDataDir, { recursive: true });
3869
+ await (0, import_promises8.mkdir)(userDataDir, { recursive: true });
3606
3870
  const persistentArgs = [
3607
3871
  `--disable-extensions-except=${extensionPath}`,
3608
3872
  `--load-extension=${extensionPath}`,
@@ -3616,7 +3880,7 @@ async function launchBrowserContext(config, userDataDir, recordVideo) {
3616
3880
  const contexts = chromeSession.browser.contexts();
3617
3881
  context = contexts[0] || await chromeSession.browser.newContext({ viewport, ...videoOpts, acceptDownloads: true });
3618
3882
  } catch {
3619
- context = await import_playwright4.chromium.launchPersistentContext(userDataDir, {
3883
+ context = await import_playwright5.chromium.launchPersistentContext(userDataDir, {
3620
3884
  headless: false,
3621
3885
  args: persistentArgs,
3622
3886
  viewport,
@@ -3625,7 +3889,7 @@ async function launchBrowserContext(config, userDataDir, recordVideo) {
3625
3889
  });
3626
3890
  }
3627
3891
  } else {
3628
- context = await import_playwright4.chromium.launchPersistentContext(userDataDir, {
3892
+ context = await import_playwright5.chromium.launchPersistentContext(userDataDir, {
3629
3893
  headless: false,
3630
3894
  args: persistentArgs,
3631
3895
  viewport,
@@ -3634,8 +3898,8 @@ async function launchBrowserContext(config, userDataDir, recordVideo) {
3634
3898
  });
3635
3899
  }
3636
3900
  } else {
3637
- browser = await import_playwright4.chromium.launch({ headless: true });
3638
- context = await browser.newContext({ viewport, ...videoOpts, acceptDownloads: true });
3901
+ browser = await import_playwright5.chromium.launch({ headless: true });
3902
+ context = await browser.newContext({ viewport, ...videoOpts, ...authOpts, acceptDownloads: true });
3639
3903
  }
3640
3904
  return { context, browser, chromeSession };
3641
3905
  }
@@ -3652,23 +3916,23 @@ async function cleanupBrowserSession(session) {
3652
3916
  init_lib();
3653
3917
 
3654
3918
  // src/browser/dry-run.ts
3655
- var import_node_path12 = require("path");
3919
+ var import_node_path13 = require("path");
3656
3920
  init_utils();
3657
3921
 
3658
3922
  // src/browser/step-validator.ts
3659
- var import_node_fs7 = require("fs");
3660
- var import_node_path11 = require("path");
3923
+ var import_node_fs8 = require("fs");
3924
+ var import_node_path12 = require("path");
3661
3925
  init_utils();
3662
3926
  function findMediaFile(filename, baseDir) {
3663
- const name = (0, import_node_path11.basename)(filename);
3664
- const searchDirs = [baseDir, (0, import_node_path11.join)(baseDir, "res"), (0, import_node_path11.join)(baseDir, "assets"), (0, import_node_path11.join)(baseDir, "media")];
3927
+ const name = (0, import_node_path12.basename)(filename);
3928
+ const searchDirs = [baseDir, (0, import_node_path12.join)(baseDir, "res"), (0, import_node_path12.join)(baseDir, "assets"), (0, import_node_path12.join)(baseDir, "media")];
3665
3929
  for (const dir of searchDirs) {
3666
- if (!(0, import_node_fs7.existsSync)(dir)) continue;
3930
+ if (!(0, import_node_fs8.existsSync)(dir)) continue;
3667
3931
  try {
3668
- const files = (0, import_node_fs7.readdirSync)(dir);
3669
- if (files.includes(name)) return (0, import_node_path11.join)(dir, name);
3932
+ const files = (0, import_node_fs8.readdirSync)(dir);
3933
+ if (files.includes(name)) return (0, import_node_path12.join)(dir, name);
3670
3934
  const match = files.find((f) => f.toLowerCase() === name.toLowerCase());
3671
- if (match) return (0, import_node_path11.join)(dir, match);
3935
+ if (match) return (0, import_node_path12.join)(dir, match);
3672
3936
  } catch {
3673
3937
  }
3674
3938
  }
@@ -3740,8 +4004,8 @@ function resolveMediaStep(step, baseDir, label, startTime) {
3740
4004
  if (!step.value) {
3741
4005
  return { success: false, error: `Missing ${label} path`, executionTimeMs: Date.now() - startTime };
3742
4006
  }
3743
- const specifiedPath = (0, import_node_path11.isAbsolute)(step.value) ? step.value : (0, import_node_path11.resolve)(baseDir, step.value);
3744
- if ((0, import_node_fs7.existsSync)(specifiedPath)) return { success: true, executionTimeMs: Date.now() - startTime };
4007
+ const specifiedPath = (0, import_node_path12.isAbsolute)(step.value) ? step.value : (0, import_node_path12.resolve)(baseDir, step.value);
4008
+ if ((0, import_node_fs8.existsSync)(specifiedPath)) return { success: true, executionTimeMs: Date.now() - startTime };
3745
4009
  const foundPath = findMediaFile(step.value, baseDir);
3746
4010
  if (foundPath) {
3747
4011
  step.value = `./${foundPath.replace(`${baseDir}/`, "").replace(`${baseDir}\\`, "")}`;
@@ -3862,8 +4126,8 @@ async function validateAndExecuteStep(page, step, plan, baseDir) {
3862
4126
  const isPlaceholder = !rawPath || rawPath.startsWith("[") || rawPath.includes("example");
3863
4127
  if (!isPlaceholder) {
3864
4128
  const normalizedPath = rawPath.startsWith("mnt/") ? `/${rawPath}` : rawPath;
3865
- let filePath = (0, import_node_path11.isAbsolute)(normalizedPath) ? normalizedPath : (0, import_node_path11.resolve)(baseDir, normalizedPath);
3866
- if (!(0, import_node_fs7.existsSync)(filePath)) {
4129
+ let filePath = (0, import_node_path12.isAbsolute)(normalizedPath) ? normalizedPath : (0, import_node_path12.resolve)(baseDir, normalizedPath);
4130
+ if (!(0, import_node_fs8.existsSync)(filePath)) {
3867
4131
  const foundPath = findMediaFile(rawPath, baseDir);
3868
4132
  if (foundPath) {
3869
4133
  const relativePath = `./${foundPath.replace(`${baseDir}/`, "").replace(`${baseDir}\\`, "")}`;
@@ -3962,8 +4226,13 @@ async function getPageElements(page) {
3962
4226
  });
3963
4227
  }
3964
4228
  async function validateStepsOnPage(plan, config, logError2, baseDir, onStepStart, onStepEnd) {
3965
- const userDataDir = (0, import_node_path12.join)(baseDir, ".browser-data");
3966
- const { context, browser, chromeSession } = await launchBrowserContext(config, userDataDir);
4229
+ const userDataDir = (0, import_node_path13.join)(baseDir, ".browser-data");
4230
+ const { context, browser, chromeSession } = await launchBrowserContext(
4231
+ config,
4232
+ userDataDir,
4233
+ void 0,
4234
+ findAuthState(plan.url)
4235
+ );
3967
4236
  const page = context.pages()[0] || await context.newPage();
3968
4237
  setupDownloadTracking(page);
3969
4238
  const results = [];
@@ -4022,27 +4291,27 @@ async function validateStepsOnPage(plan, config, logError2, baseDir, onStepStart
4022
4291
  }
4023
4292
 
4024
4293
  // src/browser/meta.ts
4025
- var import_node_fs8 = require("fs");
4026
- var import_promises8 = require("fs/promises");
4027
- var import_node_path13 = require("path");
4294
+ var import_node_fs9 = require("fs");
4295
+ var import_promises9 = require("fs/promises");
4296
+ var import_node_path14 = require("path");
4028
4297
  var RECORDING_FILE = "recording.mp4";
4029
4298
  var RECORDING_META_FILE = "recording.meta.json";
4030
4299
  function getRecordingPath(outputDir) {
4031
- return (0, import_node_path13.join)(outputDir, RECORDING_FILE);
4300
+ return (0, import_node_path14.join)(outputDir, RECORDING_FILE);
4032
4301
  }
4033
4302
  async function loadRecordingMeta(outputDir) {
4034
- const metaPath = (0, import_node_path13.join)(outputDir, RECORDING_META_FILE);
4035
- if (!(0, import_node_fs8.existsSync)(metaPath)) return null;
4303
+ const metaPath = (0, import_node_path14.join)(outputDir, RECORDING_META_FILE);
4304
+ if (!(0, import_node_fs9.existsSync)(metaPath)) return null;
4036
4305
  try {
4037
- const content = await (0, import_promises8.readFile)(metaPath, "utf-8");
4306
+ const content = await (0, import_promises9.readFile)(metaPath, "utf-8");
4038
4307
  return JSON.parse(content);
4039
4308
  } catch {
4040
4309
  return null;
4041
4310
  }
4042
4311
  }
4043
4312
  async function saveRecordingMeta(outputDir, meta) {
4044
- const metaPath = (0, import_node_path13.join)(outputDir, RECORDING_META_FILE);
4045
- await (0, import_promises8.writeFile)(metaPath, JSON.stringify(meta, null, 2));
4313
+ const metaPath = (0, import_node_path14.join)(outputDir, RECORDING_META_FILE);
4314
+ await (0, import_promises9.writeFile)(metaPath, JSON.stringify(meta, null, 2));
4046
4315
  }
4047
4316
  function canReuseRecording(plan, config, stepDurations, existingMeta) {
4048
4317
  if (!existingMeta) {
@@ -4111,16 +4380,17 @@ function formatStepLabel(step) {
4111
4380
  return `${caption.slice(0, 47)}...`;
4112
4381
  }
4113
4382
  async function recordSession(plan, config, stepDurations, workDir, baseDir, task2, logError2) {
4114
- const videoDir = (0, import_node_path14.join)(workDir, "video");
4115
- await (0, import_promises9.mkdir)(videoDir, { recursive: true });
4383
+ const videoDir = (0, import_node_path15.join)(workDir, "video");
4384
+ await (0, import_promises10.mkdir)(videoDir, { recursive: true });
4116
4385
  const getStepDuration = (stepNum) => {
4117
4386
  return stepDurations[stepNum - 1] ?? config.timing.minStepDuration;
4118
4387
  };
4119
4388
  const browserTask = task2("Starting browser recording");
4120
4389
  const recordingStartTime = Date.now();
4121
- const userDataDir = (0, import_node_path14.join)(workDir, "browser-data");
4390
+ const userDataDir = (0, import_node_path15.join)(workDir, "browser-data");
4122
4391
  const recordVideo = { dir: videoDir, size: { width: config.video.width, height: config.video.height } };
4123
- const { context, browser, chromeSession } = await launchBrowserContext(config, userDataDir, recordVideo);
4392
+ const authState = findAuthState(plan.url);
4393
+ const { context, browser, chromeSession } = await launchBrowserContext(config, userDataDir, recordVideo, authState);
4124
4394
  const page = context.pages()[0] || await context.newPage();
4125
4395
  setupDownloadTracking(page);
4126
4396
  context.on("page", (p) => setupDownloadTracking(p));
@@ -4218,9 +4488,9 @@ async function recordSession(plan, config, stepDurations, workDir, baseDir, task
4218
4488
  actualStepDurations.push(actualDuration);
4219
4489
  }
4220
4490
  if (failures.length > 0) {
4221
- console.log(import_chalk4.default.yellow(` \u26A0 ${failures.length} step(s) skipped:`));
4491
+ console.log(import_chalk6.default.yellow(` \u26A0 ${failures.length} step(s) skipped:`));
4222
4492
  for (const f of failures) {
4223
- console.log(import_chalk4.default.yellow(` - ${f}`));
4493
+ console.log(import_chalk6.default.yellow(` - ${f}`));
4224
4494
  }
4225
4495
  }
4226
4496
  const saveTask = task2("Saving video");
@@ -4229,7 +4499,7 @@ async function recordSession(plan, config, stepDurations, workDir, baseDir, task
4229
4499
  await page.close();
4230
4500
  const videoPath = await video.path();
4231
4501
  await cleanupBrowserSession({ context, browser, chromeSession });
4232
- if (!videoPath || !(0, import_node_fs9.existsSync)(videoPath)) {
4502
+ if (!videoPath || !(0, import_node_fs10.existsSync)(videoPath)) {
4233
4503
  throw new Error("Video file was not created");
4234
4504
  }
4235
4505
  saveTask.succeed("Video saved");
@@ -4237,33 +4507,18 @@ async function recordSession(plan, config, stepDurations, workDir, baseDir, task
4237
4507
  }
4238
4508
 
4239
4509
  // src/utils/video-pipeline.ts
4240
- var import_node_fs10 = require("fs");
4241
- var import_promises10 = require("fs/promises");
4242
- var import_node_path15 = require("path");
4510
+ var import_video_kit6 = require("@spark-apps/video-kit");
4511
+ var import_node_fs11 = require("fs");
4512
+ var import_promises11 = require("fs/promises");
4513
+ var import_node_path16 = require("path");
4514
+ var import_chalk7 = __toESM(require("chalk"));
4243
4515
  init_lib();
4244
4516
  var INDENT2 = " ";
4245
- function chunkWords(words, maxWords) {
4246
- const chunks = [];
4247
- for (let i = 0; i < words.length; i += maxWords) {
4248
- chunks.push(words.slice(i, i + maxWords));
4249
- }
4250
- return chunks;
4251
- }
4252
- function formatASSTime(ms) {
4253
- const hours = Math.floor(ms / 36e5);
4254
- const minutes = Math.floor(ms % 36e5 / 6e4);
4255
- const seconds = Math.floor(ms % 6e4 / 1e3);
4256
- const centis = Math.floor(ms % 1e3 / 10);
4257
- return `${hours}:${pad(minutes)}:${pad(seconds)}.${pad(centis)}`;
4258
- }
4259
- function pad(n, len = 2) {
4260
- return n.toString().padStart(len, "0");
4261
- }
4262
4517
  async function generateASS(plan, stepAudio, workDir, config, captions) {
4263
- const regularColor = hexToASS(captions.regular.color);
4264
- const highlightColor = hexToASS(captions.highlight.color);
4265
- const outline = hexToASS(captions.outline);
4266
- const back = hexToASS(captions.outline, 128);
4518
+ const regularColor = (0, import_video_kit.hexToAss)(captions.regular.color);
4519
+ const highlightColor = (0, import_video_kit.hexToAss)(captions.highlight.color);
4520
+ const outline = (0, import_video_kit.hexToAss)(captions.outline);
4521
+ const back = (0, import_video_kit.hexToAss)(captions.outline, 128);
4267
4522
  const highlightScale = Math.round(captions.highlight.size / captions.regular.size * 100);
4268
4523
  let ass = generateASSHeader(
4269
4524
  config.video.width,
@@ -4288,7 +4543,7 @@ async function generateASS(plan, stepAudio, workDir, config, captions) {
4288
4543
  continue;
4289
4544
  }
4290
4545
  const words = audio.words.length > 0 ? audio.words : estimateWordTimings(step.caption || "", audio.duration);
4291
- const chunks = chunkWords(words, 3);
4546
+ const chunks = (0, import_video_kit6.chunkWordsByCount)(words, 3);
4292
4547
  for (const chunk of chunks) {
4293
4548
  if (chunk.length === 0) continue;
4294
4549
  const chunkStart = currentTime + chunk[0].start;
@@ -4299,13 +4554,13 @@ async function generateASS(plan, stepAudio, workDir, config, captions) {
4299
4554
  const duration = Math.round((word.end - word.start) / 10);
4300
4555
  karaokeText += `{\\kf${duration}\\fscx${highlightScale}\\fscy${highlightScale}}${word.word}{\\fscx100\\fscy100} `;
4301
4556
  }
4302
- ass += `Dialogue: 0,${formatASSTime(chunkStart)},${formatASSTime(chunkEnd)},Karaoke,,0,0,0,,${karaokeText.trim()}
4557
+ ass += `Dialogue: 0,${(0, import_video_kit6.assTimeFromMs)(chunkStart)},${(0, import_video_kit6.assTimeFromMs)(chunkEnd)},Karaoke,,0,0,0,,${karaokeText.trim()}
4303
4558
  `;
4304
4559
  }
4305
4560
  currentTime += audio.duration;
4306
4561
  }
4307
- const assPath = (0, import_node_path15.join)(workDir, "subtitles.ass");
4308
- await (0, import_promises10.writeFile)(assPath, ass);
4562
+ const assPath = (0, import_node_path16.join)(workDir, "subtitles.ass");
4563
+ await (0, import_promises11.writeFile)(assPath, ass);
4309
4564
  return assPath;
4310
4565
  }
4311
4566
  function buildVideoOverlays(steps, stepDurations, baseDir) {
@@ -4318,8 +4573,8 @@ function buildVideoOverlays(steps, stepDurations, baseDir) {
4318
4573
  if ((step.action === "video" || step.action === "media") && step.value) {
4319
4574
  const ext = step.value.split(".").pop()?.toLowerCase() || "";
4320
4575
  if (["mp4", "webm", "mov"].includes(ext)) {
4321
- const mediaPath = (0, import_node_path15.isAbsolute)(step.value) ? step.value : (0, import_node_path15.resolve)(baseDir, step.value);
4322
- if ((0, import_node_fs10.existsSync)(mediaPath)) {
4576
+ const mediaPath = (0, import_node_path16.isAbsolute)(step.value) ? step.value : (0, import_node_path16.resolve)(baseDir, step.value);
4577
+ if ((0, import_node_fs11.existsSync)(mediaPath)) {
4323
4578
  const partial = parsePartial(step.partial);
4324
4579
  overlays.push({
4325
4580
  path: mediaPath,
@@ -4338,23 +4593,23 @@ function buildVideoOverlays(steps, stepDurations, baseDir) {
4338
4593
  }
4339
4594
  async function composeAndFinish(videoPath, audioPath, assPath, config, userTier = "free", mainContentDuration, trimStart = 0.5, stepTransitions, _stepSync) {
4340
4595
  header("Compose");
4341
- const configPath = (0, import_node_path15.join)(process.cwd(), CONFIG_FILE);
4342
- console.log(`${INDENT2}${require("chalk").dim(`Config: ${formatClickablePath(configPath)}`)}`);
4596
+ const configPath = (0, import_node_path16.join)(process.cwd(), CONFIG_FILE);
4597
+ console.log(`${INDENT2}${import_chalk7.default.dim(`Config: ${formatClickablePath(configPath)}`)}`);
4343
4598
  const proceed = await confirm(`${INDENT2}Review config or continue?`);
4344
4599
  if (!proceed) {
4345
- console.log(require("chalk").yellow(`
4600
+ console.log(import_chalk7.default.yellow(`
4346
4601
  Edit ${CONFIG_FILE} and run again.
4347
4602
  `));
4348
4603
  process.exit(0);
4349
4604
  }
4350
4605
  const composeTask = task("Building final video");
4351
- await (0, import_promises10.mkdir)(config.output.dir, { recursive: true });
4606
+ await (0, import_promises11.mkdir)(config.output.dir, { recursive: true });
4352
4607
  const suffix = config.lang ? `-${config.lang}` : "";
4353
- const finalPath = (0, import_node_path15.join)(config.output.dir, `demo${suffix}.${config.video.format}`);
4608
+ const finalPath = (0, import_node_path16.join)(config.output.dir, `demo${suffix}.${config.video.format}`);
4354
4609
  const videoDuration = await getMediaDuration(videoPath) / 1e3;
4355
- const hasVoiceover = audioPath && (0, import_node_fs10.existsSync)(audioPath);
4356
- const musicPath = config.music?.path ? (0, import_node_path15.isAbsolute)(config.music.path) ? config.music.path : (0, import_node_path15.resolve)(config.output.dir, config.music.path) : "";
4357
- const hasMusic = musicPath && (0, import_node_fs10.existsSync)(musicPath);
4610
+ const hasVoiceover = audioPath && (0, import_node_fs11.existsSync)(audioPath);
4611
+ const musicPath = config.music?.path ? (0, import_node_path16.isAbsolute)(config.music.path) ? config.music.path : (0, import_node_path16.resolve)(config.output.dir, config.music.path) : "";
4612
+ const hasMusic = musicPath && (0, import_node_fs11.existsSync)(musicPath);
4358
4613
  try {
4359
4614
  await composeVideo({
4360
4615
  videoPath,
@@ -4391,10 +4646,9 @@ async function composeAndFinish(videoPath, audioPath, assPath, config, userTier
4391
4646
  async function speedUpToFitLimit(finalPath, config, limitSecs = 60) {
4392
4647
  const duration = await getMediaDuration(finalPath);
4393
4648
  if (duration <= limitSecs * 1e3) return finalPath;
4394
- const chalk19 = require("chalk");
4395
4649
  const speedFactor = Math.ceil(duration / (limitSecs * 1e3) * 10) / 10;
4396
4650
  console.log();
4397
- console.log(chalk19.yellow(` Video is ${(duration / 1e3).toFixed(0)}s - over ${limitSecs}s`));
4651
+ console.log(import_chalk7.default.yellow(` Video is ${(duration / 1e3).toFixed(0)}s - over ${limitSecs}s`));
4398
4652
  const speedUp = await confirm(` Speed up ${speedFactor}x to fit under ${limitSecs}s?`);
4399
4653
  if (!speedUp) return finalPath;
4400
4654
  const speedTask = task(`Speeding up ${speedFactor}x`);
@@ -4416,152 +4670,23 @@ async function speedUpToFitLimit(finalPath, config, limitSecs = 60) {
4416
4670
  }
4417
4671
 
4418
4672
  // src/commands/utils/record-helpers.ts
4419
- var import_chalk7 = __toESM(require("chalk"));
4420
- init_lib();
4421
-
4422
- // src/help.ts
4423
- var import_chalk5 = __toESM(require("chalk"));
4673
+ var import_chalk9 = __toESM(require("chalk"));
4424
4674
  init_lib();
4425
4675
 
4426
- // src/logo.ts
4427
- var import_figlet = __toESM(require("figlet"));
4428
- var import_gradient_string = __toESM(require("gradient-string"));
4429
- var DEFAULT_LOGO_CONFIG = {
4430
- text: "QuickPeek",
4431
- font: "Slant",
4432
- horizontalLayout: "default",
4433
- gradientColors: ["#00ffff", "#00d4ff", "#a855f7", "#ec4899"],
4434
- suffix: " \u{1F440}"
4435
- };
4436
- function renderLogo(config = {}) {
4437
- const resolved = { ...DEFAULT_LOGO_CONFIG, ...config };
4438
- const ascii = import_figlet.default.textSync(resolved.text, {
4439
- font: resolved.font,
4440
- horizontalLayout: resolved.horizontalLayout
4441
- });
4442
- const colored = (0, import_gradient_string.default)(resolved.gradientColors)(ascii);
4443
- return resolved.suffix ? `${colored}${resolved.suffix}` : colored;
4444
- }
4445
-
4446
- // src/help.ts
4447
- var cmd = import_chalk5.default.cyan;
4448
- var arg = import_chalk5.default.green;
4449
- var opt = import_chalk5.default.magenta;
4450
- var desc = import_chalk5.default.white;
4451
- var ex = import_chalk5.default.dim;
4452
- function pad2(colored, visible, width) {
4453
- return colored + " ".repeat(Math.max(0, width - visible.length));
4454
- }
4455
- function showLogo() {
4456
- const logo = renderLogo();
4457
- console.log(logo);
4458
- console.log(import_chalk5.default.dim(` v${VERSION} - Auto-generate demo videos with AI`));
4459
- console.log(import_chalk5.default.italic.dim(" Because Tutorial Videos are Overrated!\n"));
4460
- }
4461
- function formatUsageLines(status, tier) {
4462
- const lines = [];
4463
- lines.push(`Videos generated: ${status.usage_count}`);
4464
- if (tier !== "pro" && status.trial) {
4465
- const { uses_remaining, max_uses, days_remaining, is_expired } = status.trial;
4466
- const used = max_uses - uses_remaining;
4467
- lines.push(`Trial: ${used}/${max_uses} uses${is_expired ? " (expired)" : ` (${uses_remaining} remaining)`}`);
4468
- if (!is_expired && days_remaining > 0) {
4469
- lines.push(`Trial ends in ${days_remaining} day${days_remaining === 1 ? "" : "s"}`);
4470
- }
4471
- }
4472
- if (tier === "pro" && status.subscription) {
4473
- const end = status.subscription.current_period_end;
4474
- if (status.subscription.is_lifetime) {
4475
- lines.push("Lifetime access");
4476
- } else if (isCanceling(status)) {
4477
- const date = new Date(end).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
4478
- lines.push(`Cancels: ${date}`);
4479
- } else if (end) {
4480
- const date = new Date(end).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
4481
- lines.push(`Renews: ${date}`);
4482
- }
4483
- }
4484
- return lines;
4485
- }
4486
- async function showAccount() {
4487
- const userData = await loadUserData();
4488
- if (!userData.email) {
4489
- console.log(import_chalk5.default.dim(" Not signed in. Run ") + cmd("qp login") + import_chalk5.default.dim(" to sign in."));
4490
- console.log();
4491
- return;
4492
- }
4493
- const email = userData.email;
4494
- const tier = userData.tier === "pro" ? "pro" : "free";
4495
- const tierLabel = tier === "pro" ? import_chalk5.default.green("Pro") : "Free";
4496
- console.log(` ${tierLabel} plan ${import_chalk5.default.dim("\xB7")} ${import_chalk5.default.dim(email)}`);
4497
- try {
4498
- const status = await getStatusCached(email);
4499
- for (const line of formatUsageLines(status, tier)) {
4500
- console.log(import_chalk5.default.dim(` ${line}`));
4501
- }
4502
- } catch {
4503
- }
4504
- console.log();
4505
- }
4506
- async function showHelp() {
4507
- showLogo();
4508
- await showAccount();
4509
- const W = 28;
4510
- const usage = [
4511
- [`${cmd("qp")} ${arg("<url>")}`, `qp <url>`, "AI-generated demo video"],
4512
- [`${cmd("qp")} ${arg("<url>")} ${opt("--script")} ${arg("<file>")}`, `qp <url> --script <file>`, "Use your own narration script"],
4513
- [`${cmd("qp capture")} ${arg("<url>")}`, `qp capture <url>`, "Record interactions manually"],
4514
- [`${cmd("qp login")}`, `qp login`, "Sign in with your Google account"],
4515
- [`${cmd("qp logout")}`, `qp logout`, "Sign out"],
4516
- [`${cmd("qp init")}`, `qp init`, "Create config file"],
4517
- [`${cmd("qp config")} ${arg("<key>")} ${arg("<value>")}`, `qp config <key> <value>`, "Update a config option"]
4518
- ];
4519
- const options = [
4520
- [`${opt("lang=")}${arg("xx")}`, `lang=xx`, "Voice language (en, es, ar, zh, fr, de, ja...)"],
4521
- [`${opt("-d")}, ${opt("--description")} ${arg('"..."')}`, `-d, --description "..."`, "Guide AI with an app description"],
4522
- [`${opt("-s")}, ${opt("--script")} ${arg("<file>")}`, `-s, --script <file>`, "Narration script (one caption per line)"],
4523
- [`${opt("-y")}, ${opt("--yes")}`, `-y, --yes`, "Skip confirmation prompts"],
4524
- [`${opt("--skip-voice")}, ${opt("-sv")}`, `--skip-voice, -sv`, "Reuse existing voiceover"],
4525
- [`${opt("-f")}, ${opt("--fresh")}`, `-f, --fresh`, "Clear cached voiceover and recording"]
4526
- ];
4527
- const examples = [
4528
- `${ex("$")} ${cmd("qp")} ${arg("localhost:3000")}`,
4529
- `${ex("$")} ${cmd("qp")} ${arg("myapp.com")} ${opt("lang=")}${arg("es")} ${opt("-d")} ${arg('"E-commerce checkout flow"')}`,
4530
- `${ex("$")} ${cmd("qp")} ${arg("myapp.com")} ${opt("--script")} ${arg("narration.txt")}`,
4531
- `${ex("$")} ${cmd("qp capture")} ${arg("localhost:3000")}`
4532
- ];
4533
- console.log();
4534
- console.log(import_chalk5.default.yellow.bold("USAGE"));
4535
- for (const [colored, plain, d] of usage) {
4536
- console.log(` ${pad2(colored, plain, W)}${desc(d)}`);
4537
- }
4538
- console.log();
4539
- console.log(import_chalk5.default.yellow.bold("OPTIONS"));
4540
- for (const [colored, plain, d] of options) {
4541
- console.log(` ${pad2(colored, plain, W)}${desc(d)}`);
4542
- }
4543
- console.log();
4544
- console.log(import_chalk5.default.yellow.bold("EXAMPLES"));
4545
- for (const e of examples) {
4546
- console.log(` ${e}`);
4547
- }
4548
- console.log();
4549
- }
4550
-
4551
4676
  // src/plan.ts
4552
- var import_promises11 = require("fs/promises");
4553
- var import_node_path16 = require("path");
4554
- var import_chalk6 = __toESM(require("chalk"));
4555
- var import_playwright5 = require("playwright");
4677
+ var import_promises12 = require("fs/promises");
4678
+ var import_node_path17 = require("path");
4679
+ var import_chalk8 = __toESM(require("chalk"));
4680
+ var import_playwright6 = require("playwright");
4556
4681
  init_lib();
4557
4682
  async function crawlExtension(url, extensionPath, workDir, task2) {
4558
4683
  const browserTask = task2("Launching browser with extension");
4559
- const userDataDir = (0, import_node_path16.join)(workDir, ".browser-data");
4560
- await (0, import_promises11.mkdir)(userDataDir, { recursive: true });
4684
+ const userDataDir = (0, import_node_path17.join)(workDir, ".browser-data");
4685
+ await (0, import_promises12.mkdir)(userDataDir, { recursive: true });
4561
4686
  let context;
4562
4687
  let chromeSession = null;
4563
4688
  const useWindowsChrome = isWSL() && tryEnableWSLInterop();
4564
- const launchPersistent = () => import_playwright5.chromium.launchPersistentContext(userDataDir, {
4689
+ const launchPersistent = () => import_playwright6.chromium.launchPersistentContext(userDataDir, {
4565
4690
  headless: false,
4566
4691
  args: [
4567
4692
  `--disable-extensions-except=${extensionPath}`,
@@ -4577,7 +4702,7 @@ async function crawlExtension(url, extensionPath, workDir, task2) {
4577
4702
  const contexts = chromeSession.browser.contexts();
4578
4703
  context = contexts[0] || await chromeSession.browser.newContext({ viewport: { width: 400, height: 600 } });
4579
4704
  } catch (_err) {
4580
- console.log(import_chalk6.default.yellow(" \u26A0 Windows Chrome unavailable, using Chromium"));
4705
+ console.log(import_chalk8.default.yellow(" \u26A0 Windows Chrome unavailable, using Chromium"));
4581
4706
  context = await launchPersistent();
4582
4707
  }
4583
4708
  } else {
@@ -4654,8 +4779,34 @@ async function crawlExtension(url, extensionPath, workDir, task2) {
4654
4779
  crawlTask.succeed(`Found ${elements.length} interactive elements`);
4655
4780
  return { title, elements };
4656
4781
  }
4782
+ var JSON_FIX_SYSTEM_PROMPT = "You fix malformed JSON. Return ONLY valid JSON, no explanation.";
4783
+ var jsonFixUserPrompt = (content) => `Fix this malformed JSON plan. Return ONLY valid JSON, no explanation.
4784
+ The JSON should have: url, title, description, steps array.
4785
+ Each step has: id (number), action, caption, and optionally target, value.
4786
+
4787
+ Malformed content:
4788
+ ${content}`;
4789
+ async function parsePlanJson(content, task2, logError2, userEmail, tier = "free") {
4790
+ try {
4791
+ return JSON.parse(content);
4792
+ } catch {
4793
+ const fixTask = task2("Fixing malformed JSON");
4794
+ try {
4795
+ const fixed = await callGroq(JSON_FIX_SYSTEM_PROMPT, jsonFixUserPrompt(content), logError2, userEmail, tier);
4796
+ const jsonMatch = fixed.match(/\{[\s\S]*\}/);
4797
+ if (!jsonMatch) throw new Error("No JSON in fix");
4798
+ const plan = JSON.parse(jsonMatch[0]);
4799
+ fixTask.succeed("JSON fixed by AI");
4800
+ return plan;
4801
+ } catch (err) {
4802
+ await logError2("JSON Fix", err);
4803
+ fixTask.fail("Could not fix JSON");
4804
+ throw new Error(`Malformed JSON: ${getErrorMessage(err)}`);
4805
+ }
4806
+ }
4807
+ }
4657
4808
  async function crawlAndAnalyze(url, lang, description, maxSteps, task2, logError2, userEmail, extensionPath, workDir, tier = "free") {
4658
- const { title, elements } = extensionPath && workDir ? await crawlExtension(url, extensionPath, workDir, task2) : await crawlPage(url, task2);
4809
+ const { title, elements } = extensionPath && workDir ? await crawlExtension(url, extensionPath, workDir, task2) : await crawlPage(url, task2, findAuthState(url));
4659
4810
  const aiTask = task2("Generating plan with AI");
4660
4811
  const langName = getLanguageName(lang);
4661
4812
  const systemPrompt = buildSystemPrompt(maxSteps);
@@ -4664,7 +4815,7 @@ async function crawlAndAnalyze(url, lang, description, maxSteps, task2, logError
4664
4815
  const content = await callGroq(systemPrompt, userPrompt, logError2, userEmail, tier);
4665
4816
  const jsonMatch = content.match(/\{[\s\S]*\}/);
4666
4817
  if (!jsonMatch) throw new Error("No JSON found in response");
4667
- const plan = JSON.parse(jsonMatch[0]);
4818
+ const plan = await parsePlanJson(jsonMatch[0], task2, logError2, userEmail, tier);
4668
4819
  if (lang !== void 0) plan.lang = lang;
4669
4820
  plan.steps = plan.steps.map(({ duration, ...s }, i) => ({
4670
4821
  ...s,
@@ -4681,35 +4832,8 @@ async function crawlAndAnalyze(url, lang, description, maxSteps, task2, logError
4681
4832
  }
4682
4833
  }
4683
4834
  async function validateAndFixPlan(planPath, task2, logError2, userEmail, tier = "free") {
4684
- const content = await (0, import_promises11.readFile)(planPath, "utf-8");
4685
- let plan;
4686
- try {
4687
- plan = JSON.parse(content);
4688
- } catch (_parseError) {
4689
- const fixTask = task2("Fixing malformed JSON");
4690
- try {
4691
- const fixed = await callGroq(
4692
- "You fix malformed JSON. Return ONLY valid JSON, no explanation.",
4693
- `Fix this malformed JSON plan. Return ONLY valid JSON, no explanation.
4694
- The JSON should have: url, title, description, steps array.
4695
- Each step has: id (number), action, caption, and optionally target, value.
4696
-
4697
- Malformed content:
4698
- ${content}`,
4699
- logError2,
4700
- userEmail,
4701
- tier
4702
- );
4703
- const jsonMatch = fixed.match(/\{[\s\S]*\}/);
4704
- if (!jsonMatch) throw new Error("No JSON in fix");
4705
- plan = JSON.parse(jsonMatch[0]);
4706
- fixTask.succeed("JSON fixed by AI");
4707
- } catch (err) {
4708
- await logError2("JSON Fix", err);
4709
- fixTask.fail("Could not fix JSON");
4710
- throw new Error(`Malformed JSON: ${getErrorMessage(err)}`);
4711
- }
4712
- }
4835
+ const content = await (0, import_promises12.readFile)(planPath, "utf-8");
4836
+ const plan = await parsePlanJson(content, task2, logError2, userEmail, tier);
4713
4837
  if (!plan.url || !plan.steps || !Array.isArray(plan.steps)) {
4714
4838
  throw new Error("Invalid plan: missing url or steps");
4715
4839
  }
@@ -4741,7 +4865,7 @@ ${content}`,
4741
4865
  renumberTask.fail("No valid steps");
4742
4866
  throw new Error("Plan has no valid steps");
4743
4867
  }
4744
- await (0, import_promises11.writeFile)(planPath, JSON.stringify(plan, null, 2));
4868
+ await (0, import_promises12.writeFile)(planPath, JSON.stringify(plan, null, 2));
4745
4869
  const removed = originalCount - plan.steps.length;
4746
4870
  renumberTask.succeed(
4747
4871
  removed > 0 ? `${plan.steps.length} steps (${removed} removed)` : `${plan.steps.length} steps validated`
@@ -4773,41 +4897,41 @@ function displayRateLimitInfo() {
4773
4897
  const percentage = Math.round(limits.used / limits.limit * 100);
4774
4898
  if (percentage >= 90) {
4775
4899
  console.log();
4776
- console.log(import_chalk7.default.yellow(` \u26A0 ${remaining} requests remaining (${limits.tier} tier)`));
4900
+ console.log(import_chalk9.default.yellow(` \u26A0 ${remaining} requests remaining (${limits.tier} tier)`));
4777
4901
  if (limits.tier === "free") {
4778
- console.log(import_chalk7.default.dim(` Upgrade: ${getUpgradeUrl()}`));
4902
+ console.log(import_chalk9.default.dim(` Upgrade: ${getUpgradeUrl()}`));
4779
4903
  }
4780
4904
  } else if (percentage >= 50) {
4781
- console.log(import_chalk7.default.dim(` ${remaining}/${limits.limit} requests remaining`));
4905
+ console.log(import_chalk9.default.dim(` ${remaining}/${limits.limit} requests remaining`));
4782
4906
  }
4783
4907
  }
4784
4908
  async function displayUsageStatus(userEmail, userTier, _localVideoCount = 0) {
4785
4909
  const tier = userTier === "pro" ? "pro" : "free";
4786
- const tierLabel = tier === "pro" ? import_chalk7.default.green("Pro") : "Free";
4787
- console.log(import_chalk7.default.cyan(` \u2714 ${tierLabel} plan`));
4788
- console.log(import_chalk7.default.dim(` ${userEmail}`));
4910
+ const tierLabel = tier === "pro" ? import_chalk9.default.green("Pro") : "Free";
4911
+ console.log(import_chalk9.default.cyan(` \u2714 ${tierLabel} plan`));
4912
+ console.log(import_chalk9.default.dim(` ${userEmail}`));
4789
4913
  if (userEmail && userEmail !== "anonymous") {
4790
4914
  try {
4791
4915
  const status = await getStatusCached(userEmail);
4792
4916
  for (const line of formatUsageLines(status, tier)) {
4793
- console.log(import_chalk7.default.dim(` ${line}`));
4917
+ console.log(import_chalk9.default.dim(` ${line}`));
4794
4918
  }
4795
4919
  } catch {
4796
4920
  }
4797
4921
  }
4798
4922
  if (userTier === "free") {
4799
4923
  const upgradeUrl = getUpgradeUrl(userEmail !== "anonymous" ? userEmail : void 0);
4800
- console.log(import_chalk7.default.dim(` Upgrade: ${upgradeUrl}`));
4924
+ console.log(import_chalk9.default.dim(` Upgrade: ${upgradeUrl}`));
4801
4925
  }
4802
4926
  }
4803
4927
  function displayPlanSummary(plan) {
4804
4928
  console.log();
4805
- console.log(import_chalk7.default.dim(` ${plan.title || "Untitled"}`));
4806
- console.log(import_chalk7.default.dim(` ${plan.steps?.length || 0} steps`));
4929
+ console.log(import_chalk9.default.dim(` ${plan.title || "Untitled"}`));
4930
+ console.log(import_chalk9.default.dim(` ${plan.steps?.length || 0} steps`));
4807
4931
  for (const step of plan.steps || []) {
4808
4932
  const text = step.caption || step.action || "";
4809
4933
  const caption = text.length > 50 ? `${text.slice(0, 47)}...` : text;
4810
- console.log(import_chalk7.default.dim(` ${step.id}. ${caption}`));
4934
+ console.log(import_chalk9.default.dim(` ${step.id}. ${caption}`));
4811
4935
  }
4812
4936
  }
4813
4937
  function getOutputDirFromUrl(url) {
@@ -4873,8 +4997,8 @@ async function runVideoPhase(params) {
4873
4997
  if (trimStart2 !== void 0 && trimEnd !== void 0) {
4874
4998
  mediaDuration = (trimEnd - trimStart2) * 1e3;
4875
4999
  } else {
4876
- const mediaPath = (0, import_node_path17.resolve)(s.value);
4877
- if ((0, import_node_fs11.existsSync)(mediaPath)) {
5000
+ const mediaPath = (0, import_node_path18.resolve)(s.value);
5001
+ if ((0, import_node_fs12.existsSync)(mediaPath)) {
4878
5002
  try {
4879
5003
  const fullDuration = await getMediaDuration(mediaPath);
4880
5004
  const start = (trimStart2 ?? 0) * 1e3;
@@ -4885,8 +5009,8 @@ async function runVideoPhase(params) {
4885
5009
  }
4886
5010
  }
4887
5011
  } else {
4888
- const mediaPath = (0, import_node_path17.resolve)(s.value);
4889
- if ((0, import_node_fs11.existsSync)(mediaPath)) {
5012
+ const mediaPath = (0, import_node_path18.resolve)(s.value);
5013
+ if ((0, import_node_fs12.existsSync)(mediaPath)) {
4890
5014
  try {
4891
5015
  mediaDuration = await getMediaDuration(mediaPath);
4892
5016
  } catch {
@@ -4899,7 +5023,7 @@ async function runVideoPhase(params) {
4899
5023
  })
4900
5024
  );
4901
5025
  const recordingPath = getRecordingPath(config.output.dir);
4902
- const hasExistingRecording = existingRecordingMeta && (0, import_node_fs11.existsSync)(recordingPath);
5026
+ const hasExistingRecording = existingRecordingMeta && (0, import_node_fs12.existsSync)(recordingPath);
4903
5027
  let videoPath = recordingPath;
4904
5028
  const firstStep = plan.steps[0];
4905
5029
  const firstStepIsVideo = firstStep && (firstStep.action === "video" || firstStep.action === "media");
@@ -4908,7 +5032,7 @@ async function runVideoPhase(params) {
4908
5032
  header("Record");
4909
5033
  const result = await recordSession(plan, config, stepDurations, workDir, config.output.dir, task, logError);
4910
5034
  stepAudio = await alignVoiceoverToRecording(stepAudio, result.actualStepDurations, workDir, config.output.dir);
4911
- await (0, import_promises12.copyFile)(result.videoPath, recordingPath);
5035
+ await (0, import_promises13.copyFile)(result.videoPath, recordingPath);
4912
5036
  const meta = buildRecordingMeta(plan, config, result.actualStepDurations);
4913
5037
  meta.pageLoadTime = result.pageLoadTime;
4914
5038
  await saveRecordingMeta(config.output.dir, meta);
@@ -4939,7 +5063,7 @@ async function runVideoPhase(params) {
4939
5063
  if (videoOverlays.length > 0) {
4940
5064
  const spliceTask = task("Splicing videos into recording");
4941
5065
  try {
4942
- const splicedPath = (0, import_node_path17.join)(workDir, "spliced.mp4");
5066
+ const splicedPath = (0, import_node_path18.join)(workDir, "spliced.mp4");
4943
5067
  await spliceVideos({
4944
5068
  inputPath: videoPath,
4945
5069
  outputPath: splicedPath,
@@ -4959,7 +5083,7 @@ async function runVideoPhase(params) {
4959
5083
  if (stepTransitions.length > 0) {
4960
5084
  const transitionTask = task("Applying transitions");
4961
5085
  try {
4962
- const transitionedPath = (0, import_node_path17.join)(workDir, "transitioned.mp4");
5086
+ const transitionedPath = (0, import_node_path18.join)(workDir, "transitioned.mp4");
4963
5087
  const videoDuration = alignedStepDurations.reduce((a, b) => a + b, 0) / 1e3;
4964
5088
  await applyTransitions({
4965
5089
  inputVideo: videoPath,
@@ -4980,15 +5104,15 @@ async function runVideoPhase(params) {
4980
5104
  let mainContentDuration;
4981
5105
  if (userTier === "free") {
4982
5106
  const scriptDir = getScriptDir();
4983
- let outroPath = (0, import_node_path17.join)(scriptDir, "assets", "qp.wm");
4984
- if (!(0, import_node_fs11.existsSync)(outroPath)) {
4985
- outroPath = (0, import_node_path17.join)(scriptDir, "..", "dist", "assets", "qp.wm");
5107
+ let outroPath = (0, import_node_path18.join)(scriptDir, "assets", "qp.wm");
5108
+ if (!(0, import_node_fs12.existsSync)(outroPath)) {
5109
+ outroPath = (0, import_node_path18.join)(scriptDir, "..", "dist", "assets", "qp.wm");
4986
5110
  }
4987
- if ((0, import_node_fs11.existsSync)(outroPath)) {
5111
+ if ((0, import_node_fs12.existsSync)(outroPath)) {
4988
5112
  try {
4989
5113
  const mainDuration = await getMediaDuration(videoPath);
4990
5114
  mainContentDuration = mainDuration / 1e3 - trimStart;
4991
- const combinedPath = (0, import_node_path17.join)(workDir, "combined-with-outro.mp4");
5115
+ const combinedPath = (0, import_node_path18.join)(workDir, "combined-with-outro.mp4");
4992
5116
  await concatVideoWithOutro({
4993
5117
  inputPath: videoPath,
4994
5118
  outroPath,
@@ -5035,19 +5159,19 @@ async function runVideoPhase(params) {
5035
5159
  }
5036
5160
 
5037
5161
  // src/commands/phases/voiceover-phase.ts
5038
- var import_node_fs12 = require("fs");
5039
- var import_node_path18 = require("path");
5040
- var import_chalk8 = __toESM(require("chalk"));
5162
+ var import_node_fs13 = require("fs");
5163
+ var import_node_path19 = require("path");
5164
+ var import_chalk10 = __toESM(require("chalk"));
5041
5165
  init_lib();
5042
5166
  var INDENT5 = " ";
5043
5167
  async function runVoiceoverPhase(plan, config, workDir, skipVoice, fresh) {
5044
5168
  if (fresh) {
5045
5169
  const filesToDelete = ["voiceover.json", "voiceover.wav", "recording.meta.json"];
5046
5170
  for (const file of filesToDelete) {
5047
- const filePath = (0, import_node_path18.join)(config.output.dir, file);
5048
- if ((0, import_node_fs12.existsSync)(filePath)) {
5049
- const { unlink: unlink2 } = await import("fs/promises");
5050
- await unlink2(filePath).catch(() => {
5171
+ const filePath = (0, import_node_path19.join)(config.output.dir, file);
5172
+ if ((0, import_node_fs13.existsSync)(filePath)) {
5173
+ const { unlink: unlink3 } = await import("fs/promises");
5174
+ await unlink3(filePath).catch(() => {
5051
5175
  });
5052
5176
  }
5053
5177
  }
@@ -5055,8 +5179,8 @@ async function runVoiceoverPhase(plan, config, workDir, skipVoice, fresh) {
5055
5179
  }
5056
5180
  const voice = resolveVoice(config);
5057
5181
  const existingMeta = await loadVoiceoverMeta(config.output.dir);
5058
- const voiceoverFile = (0, import_node_path18.join)(config.output.dir, "voiceover.wav");
5059
- const hasExistingVoiceover = existingMeta && (0, import_node_fs12.existsSync)(voiceoverFile);
5182
+ const voiceoverFile = (0, import_node_path19.join)(config.output.dir, "voiceover.wav");
5183
+ const hasExistingVoiceover = existingMeta && (0, import_node_fs13.existsSync)(voiceoverFile);
5060
5184
  const voiceMatches = !existingMeta || !voice || existingMeta.voice === voice;
5061
5185
  let voiceoverPath;
5062
5186
  let stepAudio;
@@ -5079,9 +5203,9 @@ async function runVoiceoverPhase(plan, config, workDir, skipVoice, fresh) {
5079
5203
  const reuseCheck = canReuseVoiceover(plan.steps, voice, existingMeta);
5080
5204
  if (reuseCheck.canReuse) {
5081
5205
  console.log();
5082
- console.log(import_chalk8.default.dim(` Found existing voiceover in ${config.output.dir}`));
5206
+ console.log(import_chalk10.default.dim(` Found existing voiceover in ${config.output.dir}`));
5083
5207
  if (reuseCheck.reason) {
5084
- console.log(import_chalk8.default.dim(` ${reuseCheck.reason}`));
5208
+ console.log(import_chalk10.default.dim(` ${reuseCheck.reason}`));
5085
5209
  }
5086
5210
  const reuse = await confirm(`${INDENT5}Reuse existing voiceover?`);
5087
5211
  if (reuse) {
@@ -5121,7 +5245,7 @@ async function captureCommand(url, lang) {
5121
5245
  const captureResult = await captureUserSession(url, workDir);
5122
5246
  info(`Captured ${captureResult.events.length} interactions over ${(captureResult.durationMs / 1e3).toFixed(0)}s`);
5123
5247
  if (captureResult.events.length === 0) {
5124
- console.log(import_chalk9.default.yellow(" No interactions captured. Exiting."));
5248
+ console.log(import_chalk11.default.yellow(" No interactions captured. Exiting."));
5125
5249
  return;
5126
5250
  }
5127
5251
  header("Convert");
@@ -5129,7 +5253,7 @@ async function captureCommand(url, lang) {
5129
5253
  const rawSteps = eventsToSteps(captureResult.events, url);
5130
5254
  if (rawSteps.length === 0) {
5131
5255
  convertTask.fail("No actionable steps found");
5132
- console.log(import_chalk9.default.yellow(" No actionable steps could be derived from the captured events."));
5256
+ console.log(import_chalk11.default.yellow(" No actionable steps could be derived from the captured events."));
5133
5257
  return;
5134
5258
  }
5135
5259
  convertTask.succeed(`${rawSteps.length} steps extracted`);
@@ -5161,16 +5285,16 @@ async function captureCommand(url, lang) {
5161
5285
  ...lang !== void 0 ? { lang } : {},
5162
5286
  steps
5163
5287
  };
5164
- const planPath = (0, import_node_path19.join)(config.output.dir, `plan-${lang ?? "en"}.json`);
5288
+ const planPath = (0, import_node_path20.join)(config.output.dir, `plan-${lang ?? "en"}.json`);
5165
5289
  await savePlan(plan, planPath);
5166
5290
  console.log();
5167
- console.log(import_chalk9.default.bold.green(` Plan saved: ${planPath}`));
5168
- console.log(import_chalk9.default.dim(` Run: qp ${url} to replay and create a polished demo video`));
5291
+ console.log(import_chalk11.default.bold.green(` Plan saved: ${planPath}`));
5292
+ console.log(import_chalk11.default.dim(` Run: qp ${url} to replay and create a polished demo video`));
5169
5293
  console.log();
5170
5294
  const makeVideo = await confirm(" Create polished demo video now?");
5171
5295
  if (!makeVideo) {
5172
5296
  console.log();
5173
- console.log(import_chalk9.default.dim(` Run: qp ${url} anytime to create a video from this plan.`));
5297
+ console.log(import_chalk11.default.dim(` Run: qp ${url} anytime to create a video from this plan.`));
5174
5298
  return;
5175
5299
  }
5176
5300
  header("Voiceover");
@@ -5186,20 +5310,20 @@ async function captureCommand(url, lang) {
5186
5310
  userTier
5187
5311
  });
5188
5312
  console.log();
5189
- console.log(import_chalk9.default.bold.green(" Done!"));
5190
- console.log(import_chalk9.default.cyan(` ${finalPath}`));
5313
+ console.log(import_chalk11.default.bold.green(" Done!"));
5314
+ console.log(import_chalk11.default.cyan(` ${finalPath}`));
5191
5315
  }
5192
5316
 
5193
5317
  // src/commands/init-cmd.ts
5194
- var import_node_fs13 = require("fs");
5195
- var import_promises13 = require("fs/promises");
5196
- var import_node_path20 = require("path");
5197
- var import_chalk10 = __toESM(require("chalk"));
5318
+ var import_node_fs14 = require("fs");
5319
+ var import_promises14 = require("fs/promises");
5320
+ var import_node_path21 = require("path");
5321
+ var import_chalk12 = __toESM(require("chalk"));
5198
5322
  init_lib();
5199
5323
  async function initCommand() {
5200
- const configPath = (0, import_node_path20.join)(process.cwd(), CONFIG_FILE);
5201
- if ((0, import_node_fs13.existsSync)(configPath)) {
5202
- console.log(import_chalk10.default.yellow(` \u26A0 ${CONFIG_FILE} already exists`));
5324
+ const configPath = (0, import_node_path21.join)(process.cwd(), CONFIG_FILE);
5325
+ if ((0, import_node_fs14.existsSync)(configPath)) {
5326
+ console.log(import_chalk12.default.yellow(` \u26A0 ${CONFIG_FILE} already exists`));
5203
5327
  return;
5204
5328
  }
5205
5329
  const defaultConfig = {
@@ -5222,32 +5346,32 @@ async function initCommand() {
5222
5346
  volume: 0.3
5223
5347
  }
5224
5348
  };
5225
- await (0, import_promises13.writeFile)(configPath, `${JSON.stringify(defaultConfig, null, 2)}
5349
+ await (0, import_promises14.writeFile)(configPath, `${JSON.stringify(defaultConfig, null, 2)}
5226
5350
  `);
5227
- console.log(import_chalk10.default.green(` \u2714 Created ${CONFIG_FILE}`));
5228
- console.log(import_chalk10.default.dim(` Edit it or use: qp config <key> <value>`));
5351
+ console.log(import_chalk12.default.green(` \u2714 Created ${CONFIG_FILE}`));
5352
+ console.log(import_chalk12.default.dim(` Edit it or use: qp config <key> <value>`));
5229
5353
  }
5230
5354
  async function configCommand(key, value) {
5231
- const configPath = (0, import_node_path20.join)(process.cwd(), CONFIG_FILE);
5355
+ const configPath = (0, import_node_path21.join)(process.cwd(), CONFIG_FILE);
5232
5356
  if (!key) {
5233
- if (!(0, import_node_fs13.existsSync)(configPath)) {
5234
- console.log(import_chalk10.default.yellow(` No ${CONFIG_FILE} found. Run: qp init`));
5357
+ if (!(0, import_node_fs14.existsSync)(configPath)) {
5358
+ console.log(import_chalk12.default.yellow(` No ${CONFIG_FILE} found. Run: qp init`));
5235
5359
  return;
5236
5360
  }
5237
- const content = await (0, import_promises13.readFile)(configPath, "utf-8");
5238
- console.log(import_chalk10.default.cyan(` ${CONFIG_FILE}:`));
5361
+ const content = await (0, import_promises14.readFile)(configPath, "utf-8");
5362
+ console.log(import_chalk12.default.cyan(` ${CONFIG_FILE}:`));
5239
5363
  console.log(content);
5240
5364
  return;
5241
5365
  }
5242
5366
  if (!value) {
5243
- console.log(import_chalk10.default.red(` Missing value for ${key}`));
5244
- console.log(import_chalk10.default.dim(` Usage: qp config ${key} <value>`));
5367
+ console.log(import_chalk12.default.red(` Missing value for ${key}`));
5368
+ console.log(import_chalk12.default.dim(` Usage: qp config ${key} <value>`));
5245
5369
  return;
5246
5370
  }
5247
5371
  let config = {};
5248
- if ((0, import_node_fs13.existsSync)(configPath)) {
5372
+ if ((0, import_node_fs14.existsSync)(configPath)) {
5249
5373
  try {
5250
- config = JSON.parse(await (0, import_promises13.readFile)(configPath, "utf-8"));
5374
+ config = JSON.parse(await (0, import_promises14.readFile)(configPath, "utf-8"));
5251
5375
  } catch {
5252
5376
  }
5253
5377
  }
@@ -5269,42 +5393,42 @@ async function configCommand(key, value) {
5269
5393
  }
5270
5394
  obj[keys[keys.length - 1]] = parsedValue;
5271
5395
  }
5272
- await (0, import_promises13.writeFile)(configPath, `${JSON.stringify(config, null, 2)}
5396
+ await (0, import_promises14.writeFile)(configPath, `${JSON.stringify(config, null, 2)}
5273
5397
  `);
5274
- console.log(import_chalk10.default.green(` \u2714 Set ${key} = ${value}`));
5398
+ console.log(import_chalk12.default.green(` \u2714 Set ${key} = ${value}`));
5275
5399
  }
5276
5400
 
5277
5401
  // src/commands/login-cmd.ts
5278
- var import_chalk11 = __toESM(require("chalk"));
5402
+ var import_chalk13 = __toESM(require("chalk"));
5279
5403
  init_lib();
5280
5404
  async function loginCommand() {
5281
5405
  console.log();
5282
5406
  showLogo();
5283
5407
  const existing = await loadUserData();
5284
5408
  if (existing.email && existing.verified) {
5285
- console.log(import_chalk11.default.dim(` Already signed in as ${existing.email}`));
5286
- console.log(import_chalk11.default.dim(" Run ") + import_chalk11.default.cyan("qp logout") + import_chalk11.default.dim(" first to switch accounts."));
5409
+ console.log(import_chalk13.default.dim(` Already signed in as ${existing.email}`));
5410
+ console.log(import_chalk13.default.dim(" Run ") + import_chalk13.default.cyan("qp logout") + import_chalk13.default.dim(" first to switch accounts."));
5287
5411
  console.log();
5288
5412
  try {
5289
5413
  const status = await getStatusCached(existing.email);
5290
5414
  const tier2 = await getTierByEmail(existing.email);
5291
- const tierLabel2 = tier2 === "pro" ? import_chalk11.default.green("Pro") : "Free";
5415
+ const tierLabel2 = tier2 === "pro" ? import_chalk13.default.green("Pro") : "Free";
5292
5416
  console.log(` ${tierLabel2} plan`);
5293
5417
  for (const line of formatUsageLines(status, tier2)) {
5294
- console.log(import_chalk11.default.dim(` ${line}`));
5418
+ console.log(import_chalk13.default.dim(` ${line}`));
5295
5419
  }
5296
5420
  } catch {
5297
5421
  }
5298
5422
  console.log();
5299
5423
  return;
5300
5424
  }
5301
- console.log(import_chalk11.default.white(" Sign in with your Google account to track usage"));
5302
- console.log(import_chalk11.default.white(" and unlock Pro features."));
5425
+ console.log(import_chalk13.default.white(" Sign in with your Google account to track usage"));
5426
+ console.log(import_chalk13.default.white(" and unlock Pro features."));
5303
5427
  const email = await browserLogin();
5304
5428
  if (!email) {
5305
5429
  console.log();
5306
- console.log(import_chalk11.default.yellow(" Sign-in cancelled or timed out."));
5307
- console.log(import_chalk11.default.dim(" Run ") + import_chalk11.default.cyan("qp login") + import_chalk11.default.dim(" to try again."));
5430
+ console.log(import_chalk13.default.yellow(" Sign-in cancelled or timed out."));
5431
+ console.log(import_chalk13.default.dim(" Run ") + import_chalk13.default.cyan("qp login") + import_chalk13.default.dim(" to try again."));
5308
5432
  console.log();
5309
5433
  return;
5310
5434
  }
@@ -5315,8 +5439,8 @@ async function loginCommand() {
5315
5439
  }
5316
5440
  await saveUserData({ email, verified: true, tier });
5317
5441
  console.log();
5318
- const tierLabel = tier === "pro" ? import_chalk11.default.green("Pro") : "Free";
5319
- console.log(import_chalk11.default.green(` \u2713 Signed in as ${email}`));
5442
+ const tierLabel = tier === "pro" ? import_chalk13.default.green("Pro") : "Free";
5443
+ console.log(import_chalk13.default.green(` \u2713 Signed in as ${email}`));
5320
5444
  console.log(` ${tierLabel} plan`);
5321
5445
  console.log();
5322
5446
  }
@@ -5324,46 +5448,46 @@ async function logoutCommand() {
5324
5448
  console.log();
5325
5449
  const userData = await loadUserData();
5326
5450
  if (!userData.email) {
5327
- console.log(import_chalk11.default.dim(" Not signed in."));
5451
+ console.log(import_chalk13.default.dim(" Not signed in."));
5328
5452
  console.log();
5329
5453
  return;
5330
5454
  }
5331
5455
  const email = userData.email;
5332
5456
  await saveUserData({ email: "", verified: false, tier: "free" });
5333
- console.log(import_chalk11.default.green(` \u2713 Signed out (${email})`));
5457
+ console.log(import_chalk13.default.green(` \u2713 Signed out (${email})`));
5334
5458
  console.log();
5335
5459
  }
5336
5460
 
5337
5461
  // src/commands/record-cmd.ts
5338
- var import_node_fs16 = require("fs");
5339
- var import_promises16 = require("fs/promises");
5340
- var import_node_path23 = require("path");
5341
- var import_chalk16 = __toESM(require("chalk"));
5462
+ var import_node_fs17 = require("fs");
5463
+ var import_promises17 = require("fs/promises");
5464
+ var import_node_path24 = require("path");
5465
+ var import_chalk18 = __toESM(require("chalk"));
5342
5466
  init_lib();
5343
5467
 
5344
5468
  // src/commands/phases/auth-phase.ts
5345
- var import_chalk13 = __toESM(require("chalk"));
5469
+ var import_chalk15 = __toESM(require("chalk"));
5346
5470
  init_lib();
5347
5471
 
5348
5472
  // src/commands/utils/auth-helpers.ts
5349
- var import_chalk12 = __toESM(require("chalk"));
5473
+ var import_chalk14 = __toESM(require("chalk"));
5350
5474
  init_lib();
5351
5475
  async function handleRegistrationGate() {
5352
5476
  console.log();
5353
- console.log(import_chalk12.default.yellow(" \u{1F389} You've made 5 free videos!"));
5354
- console.log(import_chalk12.default.white(" Sign in to continue creating videos."));
5477
+ console.log(import_chalk14.default.yellow(" \u{1F389} You've made 5 free videos!"));
5478
+ console.log(import_chalk14.default.white(" Sign in to continue creating videos."));
5355
5479
  console.log();
5356
- console.log(import_chalk12.default.dim(" Benefits of signing in:"));
5357
- console.log(import_chalk12.default.dim(" \u2022 20 more videos per month"));
5358
- console.log(import_chalk12.default.dim(" \u2022 Keep your video history"));
5359
- console.log(import_chalk12.default.dim(" \u2022 Upgrade to Pro anytime"));
5480
+ console.log(import_chalk14.default.dim(" Benefits of signing in:"));
5481
+ console.log(import_chalk14.default.dim(" \u2022 20 more videos per month"));
5482
+ console.log(import_chalk14.default.dim(" \u2022 Keep your video history"));
5483
+ console.log(import_chalk14.default.dim(" \u2022 Upgrade to Pro anytime"));
5360
5484
  let email = await browserLogin();
5361
5485
  if (!email) {
5362
5486
  console.log();
5363
5487
  email = await promptInput("Email: ");
5364
5488
  }
5365
5489
  if (!email || !email.includes("@")) {
5366
- console.log(import_chalk12.default.red(" Invalid email. Run qp again to try again."));
5490
+ console.log(import_chalk14.default.red(" Invalid email. Run qp again to try again."));
5367
5491
  return null;
5368
5492
  }
5369
5493
  const registerTask = task("Sending verification email");
@@ -5378,10 +5502,10 @@ async function handleRegistrationGate() {
5378
5502
  }
5379
5503
  registerTask.succeed("Check your email!");
5380
5504
  console.log();
5381
- console.log(import_chalk12.default.cyan(" \u{1F4E7} Verification email sent to:"));
5382
- console.log(import_chalk12.default.white(` ${email}`));
5505
+ console.log(import_chalk14.default.cyan(" \u{1F4E7} Verification email sent to:"));
5506
+ console.log(import_chalk14.default.white(` ${email}`));
5383
5507
  console.log();
5384
- console.log(import_chalk12.default.dim(" Click the link in the email, then press Enter to continue..."));
5508
+ console.log(import_chalk14.default.dim(" Click the link in the email, then press Enter to continue..."));
5385
5509
  await promptInput("");
5386
5510
  const verifyTask = task("Checking verification");
5387
5511
  const verified = await isVerified(email);
@@ -5391,10 +5515,10 @@ async function handleRegistrationGate() {
5391
5515
  }
5392
5516
  verifyTask.fail("Not verified yet");
5393
5517
  console.log();
5394
- console.log(import_chalk12.default.yellow(" Email not verified. Check your inbox and spam folder."));
5395
- console.log(import_chalk12.default.dim(" Run qp again after clicking the verification link."));
5518
+ console.log(import_chalk14.default.yellow(" Email not verified. Check your inbox and spam folder."));
5519
+ console.log(import_chalk14.default.dim(" Run qp again after clicking the verification link."));
5396
5520
  console.log();
5397
- console.log(import_chalk12.default.cyan(` Or register/upgrade at: ${getUpgradeUrl(email)}`));
5521
+ console.log(import_chalk14.default.cyan(` Or register/upgrade at: ${getUpgradeUrl(email)}`));
5398
5522
  console.log();
5399
5523
  return { email, verified: false };
5400
5524
  }
@@ -5422,7 +5546,7 @@ async function runAuthPhase(input) {
5422
5546
  } else {
5423
5547
  verifyTask.fail("Email not verified");
5424
5548
  console.log();
5425
- console.log(import_chalk13.default.yellow(" Check your inbox for the verification email."));
5549
+ console.log(import_chalk15.default.yellow(" Check your inbox for the verification email."));
5426
5550
  console.log();
5427
5551
  const input2 = await promptInput(`New email or Enter to retry (${userEmail}): `);
5428
5552
  if (input2.toLowerCase() === "exit") {
@@ -5447,8 +5571,8 @@ async function runAuthPhase(input) {
5447
5571
  registerTask.succeed("Check your email!");
5448
5572
  await saveUserData({ email: newEmail, verified: false });
5449
5573
  console.log();
5450
- console.log(import_chalk13.default.cyan(` \u{1F4E7} Verification sent to: ${newEmail}`));
5451
- console.log(import_chalk13.default.dim(" Click the link in the email, then run qp again."));
5574
+ console.log(import_chalk15.default.cyan(` \u{1F4E7} Verification sent to: ${newEmail}`));
5575
+ console.log(import_chalk15.default.dim(" Click the link in the email, then run qp again."));
5452
5576
  console.log();
5453
5577
  gracefulExit();
5454
5578
  return null;
@@ -5464,7 +5588,7 @@ async function runAuthPhase(input) {
5464
5588
  } else {
5465
5589
  recheckTask.fail("Still not verified");
5466
5590
  console.log();
5467
- console.log(import_chalk13.default.cyan(` Register/upgrade at: ${getUpgradeUrl(userEmail)}`));
5591
+ console.log(import_chalk15.default.cyan(` Register/upgrade at: ${getUpgradeUrl(userEmail)}`));
5468
5592
  console.log();
5469
5593
  gracefulExit();
5470
5594
  return null;
@@ -5491,10 +5615,16 @@ async function runAuthPhase(input) {
5491
5615
  } else {
5492
5616
  const remaining = FREE_VIDEO_LIMIT - localVideoCount;
5493
5617
  console.log();
5494
- console.log(import_chalk13.default.cyan(` \u{1F3AC} Video ${localVideoCount + 1}/${FREE_VIDEO_LIMIT} free`));
5495
- console.log(import_chalk13.default.dim(` ${remaining - 1} more after this \u2022 Sign up for unlimited`));
5618
+ console.log(import_chalk15.default.cyan(` \u{1F3AC} Video ${localVideoCount + 1}/${FREE_VIDEO_LIMIT} free`));
5619
+ console.log(import_chalk15.default.dim(` ${remaining - 1} more after this \u2022 Sign up for unlimited`));
5496
5620
  userEmail = await setupUserEmail();
5497
- await saveUserData({ email: userEmail, verified: false, localVideoCount });
5621
+ let registeredVerified = false;
5622
+ try {
5623
+ const reg = await registerFreeUser(userEmail);
5624
+ registeredVerified = Boolean(reg.success && reg.alreadyVerified);
5625
+ } catch {
5626
+ }
5627
+ await saveUserData({ email: userEmail, verified: registeredVerified, localVideoCount });
5498
5628
  }
5499
5629
  return {
5500
5630
  userEmail: userEmail ?? "anonymous",
@@ -5505,10 +5635,10 @@ async function runAuthPhase(input) {
5505
5635
  }
5506
5636
 
5507
5637
  // src/commands/phases/plan-phase.ts
5508
- var import_node_fs14 = require("fs");
5509
- var import_promises14 = require("fs/promises");
5510
- var import_node_path21 = require("path");
5511
- var import_chalk14 = __toESM(require("chalk"));
5638
+ var import_node_fs15 = require("fs");
5639
+ var import_promises15 = require("fs/promises");
5640
+ var import_node_path22 = require("path");
5641
+ var import_chalk16 = __toESM(require("chalk"));
5512
5642
 
5513
5643
  // src/ai/fix.ts
5514
5644
  init_lib();
@@ -5665,9 +5795,9 @@ async function runPlanPhase(params) {
5665
5795
  const extensionPath = config.browser?.extension;
5666
5796
  let existingPlan = null;
5667
5797
  let existingPlanPath = null;
5668
- if ((0, import_node_fs14.existsSync)(planPath)) {
5798
+ if ((0, import_node_fs15.existsSync)(planPath)) {
5669
5799
  try {
5670
- const parsed = JSON.parse(await (0, import_promises14.readFile)(planPath, "utf-8"));
5800
+ const parsed = JSON.parse(await (0, import_promises15.readFile)(planPath, "utf-8"));
5671
5801
  if (parsed.url && parsed.steps?.length > 0) {
5672
5802
  existingPlan = parsed;
5673
5803
  existingPlanPath = planPath;
@@ -5677,12 +5807,12 @@ async function runPlanPhase(params) {
5677
5807
  }
5678
5808
  if (!existingPlan) {
5679
5809
  try {
5680
- const files = (0, import_node_fs14.readdirSync)(config.output.dir);
5681
- const planFiles = files.filter((f) => /^plan-[a-z]{2}(-[A-Z]{2})?\.json$/.test(f) && f !== (0, import_node_path21.basename)(planPath));
5810
+ const files = (0, import_node_fs15.readdirSync)(config.output.dir);
5811
+ const planFiles = files.filter((f) => /^plan-[a-z]{2}(-[A-Z]{2})?\.json$/.test(f) && f !== (0, import_node_path22.basename)(planPath));
5682
5812
  for (const file of planFiles) {
5683
- const filePath = (0, import_node_path21.join)(config.output.dir, file);
5813
+ const filePath = (0, import_node_path22.join)(config.output.dir, file);
5684
5814
  try {
5685
- const parsed = JSON.parse(await (0, import_promises14.readFile)(filePath, "utf-8"));
5815
+ const parsed = JSON.parse(await (0, import_promises15.readFile)(filePath, "utf-8"));
5686
5816
  if (parsed.url && parsed.steps?.length > 0) {
5687
5817
  existingPlan = parsed;
5688
5818
  existingPlanPath = filePath;
@@ -5694,9 +5824,9 @@ async function runPlanPhase(params) {
5694
5824
  } catch {
5695
5825
  }
5696
5826
  }
5697
- if (!existingPlan && (0, import_node_fs14.existsSync)(legacyPlanPath)) {
5827
+ if (!existingPlan && (0, import_node_fs15.existsSync)(legacyPlanPath)) {
5698
5828
  try {
5699
- const parsed = JSON.parse(await (0, import_promises14.readFile)(legacyPlanPath, "utf-8"));
5829
+ const parsed = JSON.parse(await (0, import_promises15.readFile)(legacyPlanPath, "utf-8"));
5700
5830
  if (parsed.url && parsed.steps?.length > 0) {
5701
5831
  existingPlan = parsed;
5702
5832
  existingPlanPath = legacyPlanPath;
@@ -5722,23 +5852,23 @@ async function runPlanPhase(params) {
5722
5852
  };
5723
5853
  if (existingPlan && existingPlanPath) {
5724
5854
  console.log();
5725
- console.log(import_chalk14.default.dim(` Found existing plan: `) + import_chalk14.default.cyan(formatClickablePath(existingPlanPath)));
5726
- console.log(import_chalk14.default.dim(` ${existingPlan.steps.length} steps for ${existingPlan.url}`));
5855
+ console.log(import_chalk16.default.dim(` Found existing plan: `) + import_chalk16.default.cyan(formatClickablePath(existingPlanPath)));
5856
+ console.log(import_chalk16.default.dim(` ${existingPlan.steps.length} steps for ${existingPlan.url}`));
5727
5857
  if (!urlMatches) {
5728
- console.log(import_chalk14.default.yellow(` \u26A0 Plan URL differs from argument:`));
5729
- console.log(import_chalk14.default.yellow(` Plan: ${existingPlan.url}`));
5730
- console.log(import_chalk14.default.yellow(` Arg: ${url}`));
5858
+ console.log(import_chalk16.default.yellow(` \u26A0 Plan URL differs from argument:`));
5859
+ console.log(import_chalk16.default.yellow(` Plan: ${existingPlan.url}`));
5860
+ console.log(import_chalk16.default.yellow(` Arg: ${url}`));
5731
5861
  }
5732
5862
  const existingLang = existingPlan.lang || "en";
5733
5863
  if (existingLang !== targetLang) {
5734
- console.log(import_chalk14.default.yellow(` \u26A0 Plan is in ${existingLang}, config is ${targetLang}`));
5864
+ console.log(import_chalk16.default.yellow(` \u26A0 Plan is in ${existingLang}, config is ${targetLang}`));
5735
5865
  }
5736
5866
  const reuse = await confirm(`${INDENT6}Use existing plan?`);
5737
5867
  if (reuse) {
5738
5868
  plan = await validateAndFixPlan(existingPlanPath, task, logError, userEmail, userTier);
5739
5869
  if (existingPlanPath !== planPath) {
5740
5870
  await savePlan(plan, planPath);
5741
- console.log(import_chalk14.default.dim(` Migrated to: ${formatClickablePath(planPath)}`));
5871
+ console.log(import_chalk16.default.dim(` Migrated to: ${formatClickablePath(planPath)}`));
5742
5872
  }
5743
5873
  if (existingLang !== targetLang) {
5744
5874
  const translate = await confirm(`${INDENT6}Translate to ${targetLang}?`);
@@ -5773,7 +5903,7 @@ async function runPlanPhase(params) {
5773
5903
  uploadIdx++;
5774
5904
  }
5775
5905
  }
5776
- console.log(import_chalk14.default.dim(` Preserved upload paths from previous plan`));
5906
+ console.log(import_chalk16.default.dim(` Preserved upload paths from previous plan`));
5777
5907
  }
5778
5908
  await savePlan(plan, planPath);
5779
5909
  plan = await validateAndFixPlan(planPath, task, logError, userEmail, userTier);
@@ -5797,8 +5927,8 @@ async function runPlanPhase(params) {
5797
5927
  }
5798
5928
  displayPlanSummary(plan);
5799
5929
  console.log();
5800
- console.log(import_chalk14.default.dim(` Plan: ${formatClickablePath(planPath)}`));
5801
- console.log(import_chalk14.default.dim(` Enter a prompt to refine, or press Enter to continue.`));
5930
+ console.log(import_chalk16.default.dim(` Plan: ${formatClickablePath(planPath)}`));
5931
+ console.log(import_chalk16.default.dim(` Enter a prompt to refine, or press Enter to continue.`));
5802
5932
  const MAX_REFINEMENTS = 10;
5803
5933
  let refinementCount = 0;
5804
5934
  while (true) {
@@ -5816,15 +5946,15 @@ async function runPlanPhase(params) {
5816
5946
  refineTask.succeed("Plan refined");
5817
5947
  displayRateLimitInfo();
5818
5948
  if (plan.steps.length > maxSteps) {
5819
- console.log(import_chalk14.default.yellow(` \u26A0 ${plan.steps.length} steps exceeds limit of ${maxSteps}`));
5949
+ console.log(import_chalk16.default.yellow(` \u26A0 ${plan.steps.length} steps exceeds limit of ${maxSteps}`));
5820
5950
  }
5821
5951
  displayPlanSummary(plan);
5822
5952
  console.log();
5823
- console.log(import_chalk14.default.dim(` Plan: ${formatClickablePath(planPath)}`));
5953
+ console.log(import_chalk16.default.dim(` Plan: ${formatClickablePath(planPath)}`));
5824
5954
  if (refinementCount >= MAX_REFINEMENTS) {
5825
5955
  console.log();
5826
- console.log(import_chalk14.default.yellow(` \u26A0 You've refined the plan ${MAX_REFINEMENTS} times.`));
5827
- console.log(import_chalk14.default.dim(` Further refinements may incur additional costs.`));
5956
+ console.log(import_chalk16.default.yellow(` \u26A0 You've refined the plan ${MAX_REFINEMENTS} times.`));
5957
+ console.log(import_chalk16.default.dim(` Further refinements may incur additional costs.`));
5828
5958
  const continueRefining = await confirm(`${INDENT6}Continue refining?`);
5829
5959
  if (!continueRefining) break;
5830
5960
  }
@@ -5836,12 +5966,12 @@ async function runPlanPhase(params) {
5836
5966
  const planLang = plan.lang || "en";
5837
5967
  if (planLang !== targetLang) {
5838
5968
  let useExistingTranslation = false;
5839
- if ((0, import_node_fs14.existsSync)(planPath) && existingPlanPath !== planPath) {
5969
+ if ((0, import_node_fs15.existsSync)(planPath) && existingPlanPath !== planPath) {
5840
5970
  try {
5841
- const translatedPlan = JSON.parse(await (0, import_promises14.readFile)(planPath, "utf-8"));
5971
+ const translatedPlan = JSON.parse(await (0, import_promises15.readFile)(planPath, "utf-8"));
5842
5972
  if (translatedPlan.steps?.length === plan.steps.length && translatedPlan.lang === targetLang) {
5843
5973
  console.log();
5844
- console.log(import_chalk14.default.dim(` Found translated plan: ${formatClickablePath(planPath)}`));
5974
+ console.log(import_chalk16.default.dim(` Found translated plan: ${formatClickablePath(planPath)}`));
5845
5975
  const useTranslated = await confirm(`${INDENT6}Use ${targetLang} translation?`);
5846
5976
  if (useTranslated) {
5847
5977
  plan = translatedPlan;
@@ -5853,7 +5983,7 @@ async function runPlanPhase(params) {
5853
5983
  }
5854
5984
  if (!useExistingTranslation) {
5855
5985
  console.log();
5856
- console.log(import_chalk14.default.yellow(` Plan is in ${planLang}, but voice is set to ${targetLang}`));
5986
+ console.log(import_chalk16.default.yellow(` Plan is in ${planLang}, but voice is set to ${targetLang}`));
5857
5987
  const translate = await confirm(`${INDENT6}Translate captions to ${targetLang}?`);
5858
5988
  if (translate) await doTranslate("Translation failed, using original");
5859
5989
  }
@@ -5862,8 +5992,8 @@ async function runPlanPhase(params) {
5862
5992
  }
5863
5993
 
5864
5994
  // src/commands/phases/validation-phase.ts
5865
- var import_promises15 = require("fs/promises");
5866
- var import_chalk15 = __toESM(require("chalk"));
5995
+ var import_promises16 = require("fs/promises");
5996
+ var import_chalk17 = __toESM(require("chalk"));
5867
5997
  async function runValidationPhase(params) {
5868
5998
  let { plan } = params;
5869
5999
  const { config, planPath, isExtensionUrl, userEmail, userTier = "free" } = params;
@@ -5914,21 +6044,21 @@ async function runValidationPhase(params) {
5914
6044
  const stepNum = result.stepIndex + 1;
5915
6045
  const step = plan.steps[result.stepIndex];
5916
6046
  console.log();
5917
- console.log(import_chalk15.default.red(` \u2717 Step ${stepNum}: ${step.action}`));
6047
+ console.log(import_chalk17.default.red(` \u2717 Step ${stepNum}: ${step.action}`));
5918
6048
  const caption = step.caption || "";
5919
- if (caption) console.log(import_chalk15.default.dim(` Caption: ${caption.slice(0, 50)}${caption.length > 50 ? "..." : ""}`));
5920
- console.log(import_chalk15.default.dim(` Target: ${step.target || "none"}`));
5921
- if (step.value) console.log(import_chalk15.default.dim(` Value: ${step.value}`));
5922
- console.log(import_chalk15.default.dim(` Error: ${result.error}`));
6049
+ if (caption) console.log(import_chalk17.default.dim(` Caption: ${caption.slice(0, 50)}${caption.length > 50 ? "..." : ""}`));
6050
+ console.log(import_chalk17.default.dim(` Target: ${step.target || "none"}`));
6051
+ if (step.value) console.log(import_chalk17.default.dim(` Value: ${step.value}`));
6052
+ console.log(import_chalk17.default.dim(` Error: ${result.error}`));
5923
6053
  if (result.suggestion) {
5924
- console.log(import_chalk15.default.yellow(` Hint: ${result.suggestion}`));
6054
+ console.log(import_chalk17.default.yellow(` Hint: ${result.suggestion}`));
5925
6055
  }
5926
6056
  console.log();
5927
6057
  const isUploadMissingFile = step.action === "upload" && (!step.value || step.value.startsWith("[") || step.value.includes("example") || step.value.includes("/path/to"));
5928
6058
  if (isUploadMissingFile) {
5929
6059
  const filePath = await promptInput('Enter file path to upload (or "skip"): ');
5930
6060
  if (filePath.toLowerCase() === "skip") {
5931
- console.log(import_chalk15.default.yellow(` Skipping step ${stepNum}`));
6061
+ console.log(import_chalk17.default.yellow(` Skipping step ${stepNum}`));
5932
6062
  continue;
5933
6063
  }
5934
6064
  if (filePath) {
@@ -5937,13 +6067,13 @@ async function runValidationPhase(params) {
5937
6067
  const revalidation2 = await validateStepsOnPage(plan, config, logError, config.output.dir);
5938
6068
  const newResult2 = revalidation2.results[result.stepIndex];
5939
6069
  if (newResult2?.success) {
5940
- console.log(import_chalk15.default.green(` \u2713 Step ${stepNum} now passes`));
6070
+ console.log(import_chalk17.default.green(` \u2713 Step ${stepNum} now passes`));
5941
6071
  validationResult = revalidation2;
5942
6072
  continue;
5943
6073
  }
5944
6074
  result.error = newResult2?.error;
5945
6075
  result.suggestion = newResult2?.suggestion;
5946
- console.log(import_chalk15.default.red(` Still failing: ${result.error}`));
6076
+ console.log(import_chalk17.default.red(` Still failing: ${result.error}`));
5947
6077
  }
5948
6078
  }
5949
6079
  const fixTask = task(`Fixing step ${stepNum} with AI`);
@@ -5965,8 +6095,8 @@ async function runValidationPhase(params) {
5965
6095
  await savePlan(plan, planPath);
5966
6096
  fixTask.succeed("AI suggested a fix");
5967
6097
  const fixedStep = plan.steps[result.stepIndex];
5968
- console.log(import_chalk15.default.dim(` New target: ${fixedStep.target || "none"}`));
5969
- if (fixedStep.value) console.log(import_chalk15.default.dim(` New value: ${fixedStep.value}`));
6098
+ console.log(import_chalk17.default.dim(` New target: ${fixedStep.target || "none"}`));
6099
+ if (fixedStep.value) console.log(import_chalk17.default.dim(` New value: ${fixedStep.value}`));
5970
6100
  } catch {
5971
6101
  fixTask.fail("AI could not fix automatically");
5972
6102
  }
@@ -5982,14 +6112,14 @@ async function runValidationPhase(params) {
5982
6112
  result.error = newResult?.error;
5983
6113
  result.suggestion = newResult?.suggestion;
5984
6114
  console.log();
5985
- console.log(import_chalk15.default.yellow(` Edit step ${stepNum} in: `) + import_chalk15.default.cyan(formatClickablePath(planPath)));
6115
+ console.log(import_chalk17.default.yellow(` Edit step ${stepNum} in: `) + import_chalk17.default.cyan(formatClickablePath(planPath)));
5986
6116
  console.log();
5987
6117
  const choice = await promptInput('Press Enter after editing, or type "skip" to skip this step: ');
5988
6118
  if (choice.toLowerCase() === "skip") {
5989
- console.log(import_chalk15.default.yellow(` Skipping step ${stepNum} - may fail during recording`));
6119
+ console.log(import_chalk17.default.yellow(` Skipping step ${stepNum} - may fail during recording`));
5990
6120
  continue;
5991
6121
  }
5992
- plan = JSON.parse(await (0, import_promises15.readFile)(planPath, "utf-8"));
6122
+ plan = JSON.parse(await (0, import_promises16.readFile)(planPath, "utf-8"));
5993
6123
  const finalRevalidateTask = task(`Final validation of step ${stepNum}`);
5994
6124
  const finalRevalidation = await validateStepsOnPage(plan, config, logError, config.output.dir);
5995
6125
  const finalResult = finalRevalidation.results[result.stepIndex];
@@ -6013,31 +6143,31 @@ async function runValidationPhase(params) {
6013
6143
  }
6014
6144
 
6015
6145
  // src/commands/utils/chrome-extension.ts
6016
- var import_node_fs15 = require("fs");
6017
- var import_node_os2 = require("os");
6018
- var import_node_path22 = require("path");
6146
+ var import_node_fs16 = require("fs");
6147
+ var import_node_os3 = require("os");
6148
+ var import_node_path23 = require("path");
6019
6149
  function resolveChromeExtension(extensionId) {
6020
6150
  const platform = process.platform;
6021
6151
  const possibleDirs = [];
6022
6152
  const getChromeExtDirs = (userDataPath) => {
6023
6153
  const dirs = [];
6024
6154
  try {
6025
- const profiles = (0, import_node_fs15.readdirSync)(userDataPath).filter((p) => p === "Default" || p.startsWith("Profile "));
6155
+ const profiles = (0, import_node_fs16.readdirSync)(userDataPath).filter((p) => p === "Default" || p.startsWith("Profile "));
6026
6156
  for (const profile of profiles) {
6027
- dirs.push((0, import_node_path22.join)(userDataPath, profile, "Extensions"));
6157
+ dirs.push((0, import_node_path23.join)(userDataPath, profile, "Extensions"));
6028
6158
  }
6029
6159
  } catch {
6030
- dirs.push((0, import_node_path22.join)(userDataPath, "Default", "Extensions"));
6160
+ dirs.push((0, import_node_path23.join)(userDataPath, "Default", "Extensions"));
6031
6161
  }
6032
6162
  return dirs;
6033
6163
  };
6034
6164
  if (platform === "win32") {
6035
- possibleDirs.push(...getChromeExtDirs((0, import_node_path22.join)((0, import_node_os2.homedir)(), "AppData", "Local", "Google", "Chrome", "User Data")));
6165
+ possibleDirs.push(...getChromeExtDirs((0, import_node_path23.join)((0, import_node_os3.homedir)(), "AppData", "Local", "Google", "Chrome", "User Data")));
6036
6166
  } else if (platform === "darwin") {
6037
- possibleDirs.push(...getChromeExtDirs((0, import_node_path22.join)((0, import_node_os2.homedir)(), "Library", "Application Support", "Google", "Chrome")));
6167
+ possibleDirs.push(...getChromeExtDirs((0, import_node_path23.join)((0, import_node_os3.homedir)(), "Library", "Application Support", "Google", "Chrome")));
6038
6168
  } else {
6039
6169
  try {
6040
- const windowsUsers = (0, import_node_fs15.readdirSync)("/mnt/c/Users").filter(
6170
+ const windowsUsers = (0, import_node_fs16.readdirSync)("/mnt/c/Users").filter(
6041
6171
  (u) => !["All Users", "Default", "Default User", "Public", "desktop.ini"].includes(u)
6042
6172
  );
6043
6173
  for (const user of windowsUsers) {
@@ -6045,22 +6175,22 @@ function resolveChromeExtension(extensionId) {
6045
6175
  }
6046
6176
  } catch {
6047
6177
  }
6048
- possibleDirs.push(...getChromeExtDirs((0, import_node_path22.join)((0, import_node_os2.homedir)(), ".config", "google-chrome")));
6178
+ possibleDirs.push(...getChromeExtDirs((0, import_node_path23.join)((0, import_node_os3.homedir)(), ".config", "google-chrome")));
6049
6179
  }
6050
6180
  for (const chromeExtDir of possibleDirs) {
6051
- const extDir = (0, import_node_path22.join)(chromeExtDir, extensionId);
6052
- if (!(0, import_node_fs15.existsSync)(extDir)) continue;
6181
+ const extDir = (0, import_node_path23.join)(chromeExtDir, extensionId);
6182
+ if (!(0, import_node_fs16.existsSync)(extDir)) continue;
6053
6183
  try {
6054
- const versions = (0, import_node_fs15.readdirSync)(extDir).filter((f) => !f.startsWith("."));
6184
+ const versions = (0, import_node_fs16.readdirSync)(extDir).filter((f) => !f.startsWith("."));
6055
6185
  if (versions.length === 0) continue;
6056
6186
  const latestVersion = versions.sort().pop();
6057
- const extPath = (0, import_node_path22.join)(extDir, latestVersion);
6187
+ const extPath = (0, import_node_path23.join)(extDir, latestVersion);
6058
6188
  let name = extensionId;
6059
6189
  let popupPath = "popup.html";
6060
- const manifestPath = (0, import_node_path22.join)(extPath, "manifest.json");
6061
- if ((0, import_node_fs15.existsSync)(manifestPath)) {
6190
+ const manifestPath = (0, import_node_path23.join)(extPath, "manifest.json");
6191
+ if ((0, import_node_fs16.existsSync)(manifestPath)) {
6062
6192
  try {
6063
- const manifest = JSON.parse((0, import_node_fs15.readFileSync)(manifestPath, "utf-8"));
6193
+ const manifest = JSON.parse((0, import_node_fs16.readFileSync)(manifestPath, "utf-8"));
6064
6194
  if (manifest.name && !manifest.name.startsWith("__MSG_")) {
6065
6195
  name = manifest.name;
6066
6196
  }
@@ -6080,7 +6210,8 @@ function resolveChromeExtension(extensionId) {
6080
6210
  }
6081
6211
 
6082
6212
  // src/commands/record-cmd.ts
6083
- async function recordCommand(url, langOverride, description, skipVoice, fresh, autoYes2) {
6213
+ async function recordCommand(url, options = {}) {
6214
+ const { langOverride, description, skipVoice, fresh, autoYes: autoYes2, rethrow } = options;
6084
6215
  if (autoYes2) setAutoYes(true);
6085
6216
  console.log();
6086
6217
  showLogo();
@@ -6090,16 +6221,16 @@ async function recordCommand(url, langOverride, description, skipVoice, fresh, a
6090
6221
  const extensionId = url.slice(7);
6091
6222
  chromeExtension = resolveChromeExtension(extensionId);
6092
6223
  if (!chromeExtension) {
6093
- console.log(import_chalk16.default.red(`
6224
+ console.log(import_chalk18.default.red(`
6094
6225
  \u2717 Chrome extension not found: ${extensionId}`));
6095
- console.log(import_chalk16.default.dim(` Make sure the extension is installed in Chrome
6226
+ console.log(import_chalk18.default.dim(` Make sure the extension is installed in Chrome
6096
6227
  `));
6097
6228
  process.exit(1);
6098
6229
  }
6099
6230
  config.browser = { extension: chromeExtension.path };
6100
6231
  url = chromeExtension.popupUrl;
6101
- console.log(import_chalk16.default.dim(` Extension: ${chromeExtension.name}`));
6102
- console.log(import_chalk16.default.dim(` Path: ${chromeExtension.path}`));
6232
+ console.log(import_chalk18.default.dim(` Extension: ${chromeExtension.name}`));
6233
+ console.log(import_chalk18.default.dim(` Path: ${chromeExtension.path}`));
6103
6234
  }
6104
6235
  config.output.dir = chromeExtension ? `./${chromeExtension.name.toLowerCase().replace(/[^a-z0-9]+/g, "-")}-demo` : getOutputDirFromUrl(url);
6105
6236
  const userData = await loadUserData();
@@ -6112,20 +6243,20 @@ async function recordCommand(url, langOverride, description, skipVoice, fresh, a
6112
6243
  if (!authResult) return;
6113
6244
  const { userEmail, userTier, localVideoCount } = authResult;
6114
6245
  if (configCreated) {
6115
- const configPath = (0, import_node_path23.join)(process.cwd(), CONFIG_FILE);
6246
+ const configPath = (0, import_node_path24.join)(process.cwd(), CONFIG_FILE);
6116
6247
  console.log();
6117
- console.log(import_chalk16.default.green(` \u2714 Created ${CONFIG_FILE}`));
6118
- console.log(import_chalk16.default.dim(` ${configPath}`));
6248
+ console.log(import_chalk18.default.green(` \u2714 Created ${CONFIG_FILE}`));
6249
+ console.log(import_chalk18.default.dim(` ${configPath}`));
6119
6250
  console.log();
6120
- console.log(import_chalk16.default.cyan(" Review the config file to customize:"));
6121
- console.log(import_chalk16.default.dim(" \u2022 lang: Voice language (en, es, ar, zh...)"));
6122
- console.log(import_chalk16.default.dim(" \u2022 video: Resolution, quality, effects"));
6123
- console.log(import_chalk16.default.dim(" \u2022 music: Background music path and volume"));
6124
- console.log(import_chalk16.default.dim(" \u2022 output: Output directory"));
6251
+ console.log(import_chalk18.default.cyan(" Review the config file to customize:"));
6252
+ console.log(import_chalk18.default.dim(" \u2022 lang: Voice language (en, es, ar, zh...)"));
6253
+ console.log(import_chalk18.default.dim(" \u2022 video: Resolution, quality, effects"));
6254
+ console.log(import_chalk18.default.dim(" \u2022 music: Background music path and volume"));
6255
+ console.log(import_chalk18.default.dim(" \u2022 output: Output directory"));
6125
6256
  console.log();
6126
6257
  const proceed = await confirm(`${INDENT3}Proceed with default settings?`);
6127
6258
  if (!proceed) {
6128
- console.log(import_chalk16.default.yellow(`
6259
+ console.log(import_chalk18.default.yellow(`
6129
6260
  Edit ${CONFIG_FILE} and run again.
6130
6261
  `));
6131
6262
  process.exit(0);
@@ -6133,17 +6264,17 @@ async function recordCommand(url, langOverride, description, skipVoice, fresh, a
6133
6264
  }
6134
6265
  if (langOverride) config.lang = langOverride;
6135
6266
  if (config.music?.path) {
6136
- const musicPath = (0, import_node_path23.isAbsolute)(config.music.path) ? config.music.path : (0, import_node_path23.resolve)(config.output.dir, config.music.path);
6137
- if (!(0, import_node_fs16.existsSync)(musicPath)) {
6267
+ const musicPath = (0, import_node_path24.isAbsolute)(config.music.path) ? config.music.path : (0, import_node_path24.resolve)(config.output.dir, config.music.path);
6268
+ if (!(0, import_node_fs17.existsSync)(musicPath)) {
6138
6269
  console.log();
6139
- console.log(import_chalk16.default.yellow(` \u26A0 Music file not found: ${config.music.path}`));
6140
- console.log(import_chalk16.default.dim(` Config: ${(0, import_node_path23.join)(process.cwd(), CONFIG_FILE)}`));
6270
+ console.log(import_chalk18.default.yellow(` \u26A0 Music file not found: ${config.music.path}`));
6271
+ console.log(import_chalk18.default.dim(` Config: ${(0, import_node_path24.join)(process.cwd(), CONFIG_FILE)}`));
6141
6272
  console.log();
6142
6273
  const proceed = await confirm(`${INDENT3}Continue without background music?`);
6143
6274
  if (!proceed) {
6144
- console.log(import_chalk16.default.yellow(`
6275
+ console.log(import_chalk18.default.yellow(`
6145
6276
  Edit music.path in config and run again.`));
6146
- console.log(import_chalk16.default.dim(` ${(0, import_node_path23.join)(process.cwd(), CONFIG_FILE)}
6277
+ console.log(import_chalk18.default.dim(` ${(0, import_node_path24.join)(process.cwd(), CONFIG_FILE)}
6147
6278
  `));
6148
6279
  process.exit(0);
6149
6280
  }
@@ -6153,10 +6284,10 @@ async function recordCommand(url, langOverride, description, skipVoice, fresh, a
6153
6284
  const captions = await loadCaptions();
6154
6285
  const workDir = await prepareWorkDir();
6155
6286
  const targetLang = config.lang || "en";
6156
- const planPath = (0, import_node_path23.join)(config.output.dir, `plan-${targetLang}.json`);
6157
- const legacyPlanPath = (0, import_node_path23.join)(config.output.dir, "plan.json");
6287
+ const planPath = (0, import_node_path24.join)(config.output.dir, `plan-${targetLang}.json`);
6288
+ const legacyPlanPath = (0, import_node_path24.join)(config.output.dir, "plan.json");
6158
6289
  const isExtensionUrl = url.startsWith("chrome-extension://");
6159
- await (0, import_promises16.mkdir)(config.output.dir, { recursive: true });
6290
+ await (0, import_promises17.mkdir)(config.output.dir, { recursive: true });
6160
6291
  await setLogFilePath(config.output.dir);
6161
6292
  try {
6162
6293
  const plan = await runPlanPhase({
@@ -6192,35 +6323,37 @@ async function recordCommand(url, langOverride, description, skipVoice, fresh, a
6192
6323
  await saveUserData({ localVideoCount: newCount });
6193
6324
  const remaining = FREE_VIDEO_LIMIT - newCount;
6194
6325
  if (remaining > 0) {
6195
- console.log(import_chalk16.default.dim(` ${remaining} free video${remaining === 1 ? "" : "s"} remaining`));
6326
+ console.log(import_chalk18.default.dim(` ${remaining} free video${remaining === 1 ? "" : "s"} remaining`));
6196
6327
  }
6197
6328
  }
6198
6329
  console.log();
6199
- console.log(import_chalk16.default.bold.green(" \u2713 Done!"));
6330
+ console.log(import_chalk18.default.bold.green(" \u2713 Done!"));
6200
6331
  console.log();
6201
- console.log(import_chalk16.default.cyan(` ${formatClickablePath(finalPath)}`));
6332
+ console.log(import_chalk18.default.cyan(` ${formatClickablePath(finalPath)}`));
6202
6333
  console.log();
6334
+ return finalPath;
6203
6335
  } catch (err) {
6204
6336
  await logError("Record Command", err);
6337
+ if (rethrow) throw err;
6205
6338
  console.log();
6206
- console.log(import_chalk16.default.red(` Error: ${err instanceof Error ? err.message : "Unknown error"}`));
6207
- console.log(import_chalk16.default.dim(` See ${logFilePath} for details`));
6339
+ console.log(import_chalk18.default.red(` Error: ${err instanceof Error ? err.message : "Unknown error"}`));
6340
+ console.log(import_chalk18.default.dim(` See ${logFilePath} for details`));
6208
6341
  process.exit(1);
6209
6342
  }
6210
6343
  }
6211
6344
 
6212
6345
  // src/commands/script-cmd.ts
6213
- var import_promises17 = require("fs/promises");
6214
- var import_node_path24 = require("path");
6215
- var import_chalk17 = __toESM(require("chalk"));
6346
+ var import_promises18 = require("fs/promises");
6347
+ var import_node_path25 = require("path");
6348
+ var import_chalk19 = __toESM(require("chalk"));
6216
6349
  init_lib();
6217
6350
  async function scriptCommand(url, scriptPath, lang) {
6218
6351
  const { config, workDir, captions } = await initCommandEnv(url, lang);
6219
6352
  header("Script");
6220
- const raw = await (0, import_promises17.readFile)(scriptPath, "utf-8");
6353
+ const raw = await (0, import_promises18.readFile)(scriptPath, "utf-8");
6221
6354
  const lines = raw.split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#"));
6222
6355
  if (lines.length === 0) {
6223
- console.log(import_chalk17.default.red(" Script file is empty or has no non-comment lines"));
6356
+ console.log(import_chalk19.default.red(" Script file is empty or has no non-comment lines"));
6224
6357
  process.exit(1);
6225
6358
  }
6226
6359
  info(`${lines.length} captions from script`);
@@ -6232,7 +6365,7 @@ async function scriptCommand(url, scriptPath, lang) {
6232
6365
  const planTask = task("Mapping script to UI actions");
6233
6366
  const appTitle = new URL(url).hostname.replace(/^www\./, "");
6234
6367
  const plan = await generatePlanFromScript(lines, url, appTitle, elementsText, lang, logError);
6235
- const planPath = (0, import_node_path24.join)(config.output.dir, `plan-${lang ?? "en"}.json`);
6368
+ const planPath = (0, import_node_path25.join)(config.output.dir, `plan-${lang ?? "en"}.json`);
6236
6369
  await savePlan(plan, planPath);
6237
6370
  planTask.succeed(`${plan.steps.length} steps planned`);
6238
6371
  header("Voiceover");
@@ -6252,8 +6385,8 @@ async function scriptCommand(url, scriptPath, lang) {
6252
6385
  const assPath = await generateASS(plan, stepAudio, workDir, config, captions);
6253
6386
  const finalPath = await composeAndFinish(videoPath, voiceoverPath, assPath, config, "free");
6254
6387
  console.log();
6255
- console.log(import_chalk17.default.bold.green(" Done!"));
6256
- console.log(import_chalk17.default.cyan(` ${finalPath}`));
6388
+ console.log(import_chalk19.default.bold.green(" Done!"));
6389
+ console.log(import_chalk19.default.cyan(` ${finalPath}`));
6257
6390
  }
6258
6391
 
6259
6392
  // src/qp.ts
@@ -6298,6 +6431,16 @@ function parseArgs(args) {
6298
6431
  if (first === "logout") {
6299
6432
  return { ...base, command: "logout" };
6300
6433
  }
6434
+ if (first === "auth") {
6435
+ const sub = args[1];
6436
+ if (sub === "list" || sub === void 0) {
6437
+ return { ...base, command: "auth-list" };
6438
+ }
6439
+ if (sub === "clear") {
6440
+ return { ...base, command: "auth-clear", url: args[2] ? normalizeUrl3(args[2]) : void 0 };
6441
+ }
6442
+ return { ...base, command: "auth", url: normalizeUrl3(sub) };
6443
+ }
6301
6444
  if (first === "init") {
6302
6445
  return { ...base, command: "init" };
6303
6446
  }
@@ -6378,6 +6521,20 @@ async function main() {
6378
6521
  case "logout":
6379
6522
  await logoutCommand();
6380
6523
  break;
6524
+ case "auth":
6525
+ if (!parsed.url) {
6526
+ console.log(import_chalk20.default.red(" Missing URL"));
6527
+ console.log(import_chalk20.default.dim(" Run: qp auth <url>"));
6528
+ process.exit(1);
6529
+ }
6530
+ await authCommand(parsed.url);
6531
+ break;
6532
+ case "auth-clear":
6533
+ await authClearCommand(parsed.url);
6534
+ break;
6535
+ case "auth-list":
6536
+ await authListCommand();
6537
+ break;
6381
6538
  case "init":
6382
6539
  await initCommand();
6383
6540
  break;
@@ -6386,8 +6543,8 @@ async function main() {
6386
6543
  break;
6387
6544
  case "record":
6388
6545
  if (!parsed.url) {
6389
- console.log(import_chalk18.default.red(" Missing URL"));
6390
- console.log(import_chalk18.default.dim(" Run: qp --help"));
6546
+ console.log(import_chalk20.default.red(" Missing URL"));
6547
+ console.log(import_chalk20.default.dim(" Run: qp --help"));
6391
6548
  process.exit(1);
6392
6549
  }
6393
6550
  setAutoYes(parsed.yes);
@@ -6396,13 +6553,18 @@ async function main() {
6396
6553
  showLogo();
6397
6554
  await scriptCommand(parsed.url, parsed.scriptPath, parsed.lang);
6398
6555
  } else {
6399
- await recordCommand(parsed.url, parsed.lang, parsed.description, parsed.skipVoice, parsed.fresh);
6556
+ await recordCommand(parsed.url, {
6557
+ langOverride: parsed.lang,
6558
+ description: parsed.description,
6559
+ skipVoice: parsed.skipVoice,
6560
+ fresh: parsed.fresh
6561
+ });
6400
6562
  }
6401
6563
  break;
6402
6564
  case "capture":
6403
6565
  if (!parsed.url) {
6404
- console.log(import_chalk18.default.red(" Missing URL"));
6405
- console.log(import_chalk18.default.dim(" Run: qp --help"));
6566
+ console.log(import_chalk20.default.red(" Missing URL"));
6567
+ console.log(import_chalk20.default.dim(" Run: qp --help"));
6406
6568
  process.exit(1);
6407
6569
  }
6408
6570
  console.log();