@spark-apps/quickpeek 1.1.1 → 1.1.3

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
@@ -137,16 +137,7 @@ var init_utils = __esm({
137
137
  });
138
138
 
139
139
  // src/lib/api-client.ts
140
- function hasCredentials() {
141
- return Boolean(process.env[ENV_SPARK_API_KEY]);
142
- }
143
- function getGroqApiKey() {
144
- return process.env[ENV_SPARK_API_KEY] || null;
145
- }
146
- function getStripeUrl() {
147
- return process.env[ENV_STRIPE_BACKEND] || DEFAULT_STRIPE_BACKEND;
148
- }
149
- async function getOrRefreshToken(apiKey) {
140
+ async function getOrRefreshToken() {
150
141
  const now = Date.now();
151
142
  if (cachedToken && cachedToken.expiresAt > now + 3e5) {
152
143
  return cachedToken.token;
@@ -154,7 +145,7 @@ async function getOrRefreshToken(apiKey) {
154
145
  try {
155
146
  const response = await fetch(SPARK_AI_TOKEN, {
156
147
  method: "POST",
157
- headers: { "X-Api-Key": apiKey }
148
+ headers: { "X-Api-Key": API_KEY }
158
149
  });
159
150
  const data = await response.json();
160
151
  if (!response.ok || !data.success || !data.token) {
@@ -163,7 +154,6 @@ async function getOrRefreshToken(apiKey) {
163
154
  cachedToken = {
164
155
  token: data.token,
165
156
  expiresAt: data.expiresAt || now + 36e5
166
- // Default 1 hour
167
157
  };
168
158
  return data.token;
169
159
  } catch (err) {
@@ -171,16 +161,15 @@ async function getOrRefreshToken(apiKey) {
171
161
  }
172
162
  }
173
163
  async function callAI(systemPrompt, userPrompt, tier = "free") {
174
- const apiKey = getGroqApiKey();
175
- if (!apiKey) {
164
+ if (!API_KEY) {
176
165
  return {
177
166
  success: false,
178
- error: `Missing API key. Set ${ENV_SPARK_API_KEY} environment variable.`,
167
+ error: "AI service is not configured. Please report this issue.",
179
168
  isAuthError: true
180
169
  };
181
170
  }
182
171
  try {
183
- const token = await getOrRefreshToken(apiKey);
172
+ const token = await getOrRefreshToken();
184
173
  let lastRateLimitError = null;
185
174
  for (let attempt = 1; attempt <= 3; attempt++) {
186
175
  try {
@@ -207,7 +196,7 @@ async function callAI(systemPrompt, userPrompt, tier = "free") {
207
196
  success: false,
208
197
  error,
209
198
  isRateLimit: true,
210
- retryAfterSeconds: retryAfter ? parseInt(retryAfter, 10) : void 0
199
+ ...retryAfter ? { retryAfterSeconds: parseInt(retryAfter, 10) } : {}
211
200
  };
212
201
  await new Promise((resolve9) => setTimeout(resolve9, Math.min(delayMs, 3e4)));
213
202
  continue;
@@ -232,7 +221,7 @@ async function callAI(systemPrompt, userPrompt, tier = "free") {
232
221
  used: data.limits?.used ?? 0,
233
222
  limit: data.limits?.limit ?? 100,
234
223
  tier,
235
- resetAt: data.limits?.resetAt
224
+ ...data.limits?.resetAt ? { resetAt: data.limits.resetAt } : {}
236
225
  }
237
226
  };
238
227
  } catch (err) {
@@ -246,14 +235,32 @@ async function callAI(systemPrompt, userPrompt, tier = "free") {
246
235
  return { success: false, error: `Spark AI error: ${getErrorMessage(err)}` };
247
236
  }
248
237
  }
249
- function getBackendUrls() {
250
- return { stripe: getStripeUrl() };
238
+ function parseAIPlanResponse(content) {
239
+ try {
240
+ let processedContent = content;
241
+ const codeBlockMatch = processedContent.match(/```(?:json)?\s*([\s\S]*?)```/);
242
+ if (codeBlockMatch?.[1]) {
243
+ processedContent = codeBlockMatch[1].trim();
244
+ }
245
+ const jsonMatch = processedContent.match(/\{[\s\S]*\}/);
246
+ if (!jsonMatch) {
247
+ return { success: false, error: "No valid JSON found in AI response" };
248
+ }
249
+ const parsed = JSON.parse(jsonMatch[0]);
250
+ return { success: true, plan: parsed };
251
+ } catch (err) {
252
+ return {
253
+ success: false,
254
+ error: `Failed to parse AI response: ${err instanceof Error ? err.message : String(err)}`
255
+ };
256
+ }
251
257
  }
252
- function getCredentials() {
253
- const key = getGroqApiKey();
254
- return key ? { apiKey: key } : null;
258
+ function normalizeUrl(input) {
259
+ let url = input.trim();
260
+ url = url.replace(/^(https?:\/\/)?(www\.)?/i, "");
261
+ return `https://${url}`;
255
262
  }
256
- var SPARK_AI_BASE, SPARK_AI_CHAT, SPARK_AI_TOKEN, ENV_SPARK_API_KEY, ENV_STRIPE_BACKEND, DEFAULT_STRIPE_BACKEND, cachedToken;
263
+ var SPARK_AI_BASE, SPARK_AI_CHAT, SPARK_AI_TOKEN, API_KEY, cachedToken;
257
264
  var init_api_client = __esm({
258
265
  "src/lib/api-client.ts"() {
259
266
  "use strict";
@@ -261,29 +268,95 @@ var init_api_client = __esm({
261
268
  SPARK_AI_BASE = "https://sparkbrain.app";
262
269
  SPARK_AI_CHAT = `${SPARK_AI_BASE}/api/chat`;
263
270
  SPARK_AI_TOKEN = `${SPARK_AI_BASE}/api/models/token`;
264
- ENV_SPARK_API_KEY = "SPARK_API_KEY";
265
- ENV_STRIPE_BACKEND = "QP_STRIPE_BACKEND";
266
- DEFAULT_STRIPE_BACKEND = "https://sparkstripe.com";
271
+ API_KEY = "spark_ai-8eefdbe9be3069cad2566cb5753dcff9";
267
272
  cachedToken = null;
268
273
  }
269
274
  });
270
275
 
276
+ // src/lib/SparkStripe-feature-gating.ts
277
+ async function _fetch(email, init) {
278
+ const r = await fetch(`${BASE}/api/public/subscription/status-public?email=${encodeURIComponent(email)}&app_id=${APP_ID}`, init);
279
+ if (!r.ok) throw new Error(`spark-stripe: ${r.status}`);
280
+ return r.json();
281
+ }
282
+ async function getStatusCached(email) {
283
+ const k = email.toLowerCase(), hit = _cache.get(k);
284
+ if (hit && Date.now() < hit.exp) return hit.data;
285
+ const data = await getStatus(email);
286
+ _cache.set(k, { data, exp: Date.now() + 6e4 });
287
+ return data;
288
+ }
289
+ function getTier(d) {
290
+ if (!d.subscription || !d.registered) return "free";
291
+ if (d.subscription.status === "canceled" || d.subscription.status === "past_due") return "free";
292
+ return TIERS[d.subscription.price_id] ?? "free";
293
+ }
294
+ async function getTierByEmail(email) {
295
+ try {
296
+ return getTier(await getStatusCached(email));
297
+ } catch {
298
+ return "free";
299
+ }
300
+ }
301
+ async function isVerified(email) {
302
+ try {
303
+ return (await getStatusCached(email)).verified;
304
+ } catch {
305
+ return false;
306
+ }
307
+ }
308
+ function pricingUrl(opts) {
309
+ const u = new URL(`${BASE}/pricing/${APP_ID}`);
310
+ if (opts?.email) u.searchParams.set("email", opts.email);
311
+ if (opts?.returnUrl) u.searchParams.set("return_url", opts.returnUrl);
312
+ return u.toString();
313
+ }
314
+ var APP_ID, BASE, TIERS, RANK, FEATURES, getStatus, _cache, isPaid, hasAccess, hasTrialRemaining, showPaywall, isCanceling;
315
+ var init_SparkStripe_feature_gating = __esm({
316
+ "src/lib/SparkStripe-feature-gating.ts"() {
317
+ "use strict";
318
+ APP_ID = process.env.SPARK_APP_ID ?? "quickpeek";
319
+ BASE = "https://sparkstripe.com";
320
+ 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"
327
+ };
328
+ RANK = {
329
+ "free": 0,
330
+ "pro": 1
331
+ };
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
+ getStatus = (email) => _fetch(email);
340
+ _cache = /* @__PURE__ */ new Map();
341
+ isPaid = (d) => getTier(d) !== "free";
342
+ hasAccess = (d, min) => (RANK[getTier(d)] ?? 0) >= (RANK[min] ?? 0);
343
+ hasTrialRemaining = (d) => isPaid(d) || !!d.trial && !d.trial.is_expired && d.trial.uses_remaining > 0;
344
+ showPaywall = (d) => !isPaid(d) && !hasTrialRemaining(d);
345
+ isCanceling = (d) => !!d.subscription && d.subscription.cancel_at_period_end && d.subscription.status !== "canceled";
346
+ }
347
+ });
348
+
271
349
  // src/lib/billing.ts
272
350
  function shouldWatermark(tier, videoDurationSeconds) {
273
351
  return tier === "free" && videoDurationSeconds > 20;
274
352
  }
275
353
  function getUpgradeUrl(userEmail) {
276
- const stripe = getStripeUrl();
277
- let url = `${stripe}/pricing/quickpeek`;
278
- if (userEmail) {
279
- url += `?email=${encodeURIComponent(userEmail)}`;
280
- }
281
- return url;
354
+ return pricingUrl(userEmail ? { email: userEmail } : void 0);
282
355
  }
283
356
  var init_billing = __esm({
284
357
  "src/lib/billing.ts"() {
285
358
  "use strict";
286
- init_api_client();
359
+ init_SparkStripe_feature_gating();
287
360
  }
288
361
  });
289
362
 
@@ -340,7 +413,22 @@ var init_config = __esm({
340
413
  });
341
414
 
342
415
  // src/lib/scraping.ts
343
- var EXCLUDED_LINK_PATTERNS;
416
+ function isExcludedLink(text, href = "") {
417
+ const lowerText = text.toLowerCase();
418
+ const lowerHref = href.toLowerCase();
419
+ return EXCLUDED_LINK_PATTERNS.some((p) => lowerText.includes(p) || lowerHref.includes(p));
420
+ }
421
+ function shouldSkipLink(text, href, currentPath) {
422
+ if (!href || href === "#") return true;
423
+ try {
424
+ const linkPath = new URL(href).pathname;
425
+ if (linkPath === "/" || linkPath === currentPath) return true;
426
+ } catch {
427
+ return true;
428
+ }
429
+ return isExcludedLink(text, href);
430
+ }
431
+ var EXCLUDED_LINK_PATTERNS, INTERACTIVE_SELECTORS;
344
432
  var init_scraping = __esm({
345
433
  "src/lib/scraping.ts"() {
346
434
  "use strict";
@@ -407,6 +495,22 @@ var init_scraping = __esm({
407
495
  "feed",
408
496
  "home"
409
497
  ];
498
+ INTERACTIVE_SELECTORS = [
499
+ "button",
500
+ "input",
501
+ "select",
502
+ "textarea",
503
+ "a[href]",
504
+ '[role="button"]',
505
+ '[role="checkbox"]',
506
+ '[role="radio"]',
507
+ '[role="switch"]',
508
+ '[role="slider"]',
509
+ '[role="combobox"]',
510
+ '[role="menuitem"]',
511
+ '[type="submit"]',
512
+ "[onclick]"
513
+ ];
410
514
  }
411
515
  });
412
516
 
@@ -1273,49 +1377,13 @@ var init_styles = __esm({
1273
1377
  });
1274
1378
 
1275
1379
  // src/lib/subscription-client.ts
1276
- async function fetchStatus(email) {
1277
- const cacheKey = `${email}:${APP_ID}`;
1278
- const cached = responseCache.get(cacheKey);
1279
- if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) return cached.data;
1280
- const stripe = getStripeUrl();
1281
- try {
1282
- const params = new URLSearchParams({ email, app_id: APP_ID });
1283
- const response = await fetch(`${stripe}/api/subscription/status-public?${params}`);
1284
- if (!response.ok) return null;
1285
- const data = await response.json();
1286
- responseCache.set(cacheKey, { data, timestamp: Date.now() });
1287
- return data;
1288
- } catch {
1289
- return null;
1290
- }
1291
- }
1292
- function resolveTier(status) {
1293
- return status && PRO_STATUSES.includes(status) ? "pro" : "free";
1294
- }
1295
- async function getTier(userEmail) {
1296
- const data = await fetchStatus(userEmail);
1297
- if (!data) return "free";
1298
- const status = data.subscription?.status;
1299
- const tier = resolveTier(status);
1300
- if (tier === "pro" && status === "trialing" && data.subscription?.current_period_end) {
1301
- if (new Date(data.subscription.current_period_end) <= /* @__PURE__ */ new Date()) return "free";
1302
- }
1303
- return tier;
1304
- }
1305
- async function checkVerificationStatus(email) {
1306
- const data = await fetchStatus(email);
1307
- if (!data) return { verified: false, tier: "free" };
1308
- const verified = data.verified === true || data.user?.verified === true || data.user?.email_verified === true;
1309
- const tier = resolveTier(data.subscription?.status);
1310
- return { verified, tier };
1311
- }
1312
1380
  async function registerFreeUser(email) {
1313
- const stripe = getStripeUrl();
1381
+ const APP_ID2 = process.env.SPARK_APP_ID ?? "quickpeek";
1314
1382
  try {
1315
- const response = await fetch(`${stripe}/api/register-free`, {
1383
+ const response = await fetch(`https://sparkstripe.com/api/register-free`, {
1316
1384
  method: "POST",
1317
1385
  headers: { "Content-Type": "application/json" },
1318
- body: JSON.stringify({ email, app_id: APP_ID, app_name: "QuickPeek" })
1386
+ body: JSON.stringify({ email, app_id: APP_ID2, app_name: "QuickPeek" })
1319
1387
  });
1320
1388
  const text = await response.text();
1321
1389
  let data = {};
@@ -1340,15 +1408,9 @@ async function registerFreeUser(email) {
1340
1408
  return { success: false, error: err instanceof Error ? err.message : "Network error" };
1341
1409
  }
1342
1410
  }
1343
- var APP_ID, PRO_STATUSES, CACHE_TTL_MS, responseCache;
1344
1411
  var init_subscription_client = __esm({
1345
1412
  "src/lib/subscription-client.ts"() {
1346
1413
  "use strict";
1347
- init_api_client();
1348
- APP_ID = "quickpeek";
1349
- PRO_STATUSES = ["active", "trialing", "lifetime"];
1350
- CACHE_TTL_MS = 6e4;
1351
- responseCache = /* @__PURE__ */ new Map();
1352
1414
  }
1353
1415
  });
1354
1416
 
@@ -1869,6 +1931,8 @@ __export(lib_exports, {
1869
1931
  DEFAULT_CAPTIONS: () => DEFAULT_CAPTIONS,
1870
1932
  DEFAULT_CONFIG: () => DEFAULT_CONFIG,
1871
1933
  EXCLUDED_LINK_PATTERNS: () => EXCLUDED_LINK_PATTERNS,
1934
+ FEATURES: () => FEATURES,
1935
+ INTERACTIVE_SELECTORS: () => INTERACTIVE_SELECTORS,
1872
1936
  LANGUAGE_VOICES: () => LANGUAGE_VOICES,
1873
1937
  VERSION: () => VERSION,
1874
1938
  alignVoiceoverToRecording: () => alignVoiceoverToRecording,
@@ -1880,7 +1944,6 @@ __export(lib_exports, {
1880
1944
  buildWatermarkFilters: () => buildWatermarkFilters,
1881
1945
  callAI: () => callAI,
1882
1946
  canReuseVoiceover: () => canReuseVoiceover,
1883
- checkVerificationStatus: () => checkVerificationStatus,
1884
1947
  composeVideo: () => composeVideo,
1885
1948
  concatMedia: () => concatMedia,
1886
1949
  concatVideoWithOutro: () => concatVideoWithOutro,
@@ -1889,25 +1952,33 @@ __export(lib_exports, {
1889
1952
  generateASSHeader: () => generateASSHeader,
1890
1953
  generateSilence: () => generateSilence,
1891
1954
  generateVoiceover: () => generateVoiceover,
1892
- getBackendUrls: () => getBackendUrls,
1893
- getCredentials: () => getCredentials,
1894
1955
  getErrorMessage: () => getErrorMessage,
1895
- getGroqApiKey: () => getGroqApiKey,
1896
1956
  getLanguageName: () => getLanguageName,
1897
1957
  getMediaDuration: () => getMediaDuration,
1898
- getStripeUrl: () => getStripeUrl,
1899
- getTier: () => getTier,
1958
+ getStatus: () => getStatus,
1959
+ getStatusCached: () => getStatusCached,
1960
+ getTierByEmail: () => getTierByEmail,
1900
1961
  getUpgradeUrl: () => getUpgradeUrl,
1901
- hasCredentials: () => hasCredentials,
1962
+ hasAccess: () => hasAccess,
1963
+ hasTrialRemaining: () => hasTrialRemaining,
1902
1964
  hexToASS: () => hexToASS,
1965
+ isCanceling: () => isCanceling,
1966
+ isExcludedLink: () => isExcludedLink,
1967
+ isPaid: () => isPaid,
1968
+ isVerified: () => isVerified,
1903
1969
  loadExistingVoiceover: () => loadExistingVoiceover,
1904
1970
  loadVoiceoverMeta: () => loadVoiceoverMeta,
1905
1971
  mapTimestampsAfterCompression: () => mapTimestampsAfterCompression,
1972
+ normalizeUrl: () => normalizeUrl,
1973
+ parseAIPlanResponse: () => parseAIPlanResponse,
1906
1974
  parsePartial: () => parsePartial,
1975
+ pricingUrl: () => pricingUrl,
1907
1976
  registerFreeUser: () => registerFreeUser,
1908
1977
  resolveVoice: () => resolveVoice,
1909
1978
  rgbaToHex: () => rgbaToHex,
1979
+ shouldSkipLink: () => shouldSkipLink,
1910
1980
  shouldWatermark: () => shouldWatermark,
1981
+ showPaywall: () => showPaywall,
1911
1982
  speedUpVideo: () => speedUpVideo,
1912
1983
  spliceVideos: () => spliceVideos,
1913
1984
  synthesizeSpeech: () => synthesizeSpeech,
@@ -1926,6 +1997,7 @@ var init_lib = __esm({
1926
1997
  init_prompts();
1927
1998
  init_scraping();
1928
1999
  init_styles();
2000
+ init_SparkStripe_feature_gating();
1929
2001
  init_subscription_client();
1930
2002
  init_tts2();
1931
2003
  init_types3();
@@ -1937,19 +2009,98 @@ var init_lib = __esm({
1937
2009
 
1938
2010
  // src/qp.ts
1939
2011
  var import_dotenv = __toESM(require("dotenv"));
1940
- var import_chalk16 = __toESM(require("chalk"));
2012
+ var import_chalk18 = __toESM(require("chalk"));
1941
2013
 
1942
2014
  // src/commands/capture-cmd.ts
1943
2015
  var import_node_path19 = require("path");
1944
- var import_chalk7 = __toESM(require("chalk"));
2016
+ var import_chalk9 = __toESM(require("chalk"));
1945
2017
 
1946
2018
  // src/ai/narrate.ts
1947
2019
  init_lib();
1948
2020
 
1949
2021
  // src/ai/client.ts
1950
2022
  var import_node_readline = require("readline");
1951
- var import_chalk = __toESM(require("chalk"));
2023
+ var import_chalk2 = __toESM(require("chalk"));
1952
2024
  init_lib();
2025
+
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
+ });
2039
+ }
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
+ });
2101
+ }
2102
+
2103
+ // src/ai/client.ts
1953
2104
  var lastRateLimitInfo = null;
1954
2105
  function getLastRateLimitInfo() {
1955
2106
  return lastRateLimitInfo;
@@ -1965,78 +2116,73 @@ function prompt(question) {
1965
2116
  }
1966
2117
  async function setupUserEmail() {
1967
2118
  console.log();
1968
- console.log(import_chalk.default.yellow(" \u2709 Account Setup"));
1969
- console.log(import_chalk.default.dim(" \u2500".repeat(20)));
2119
+ console.log(import_chalk2.default.yellow(" \u2709 Account Setup"));
2120
+ console.log(import_chalk2.default.dim(" \u2500".repeat(20)));
1970
2121
  console.log();
1971
- console.log(import_chalk.default.white(" QuickPeek uses your email to track usage and"));
1972
- console.log(import_chalk.default.white(" unlock Pro features when you subscribe."));
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
+ }
1973
2131
  console.log();
1974
- const email = await prompt(import_chalk.default.white(" Email: "));
2132
+ console.log(import_chalk2.default.dim(" Or enter your email manually:"));
2133
+ const email = await prompt(import_chalk2.default.white(" Email: "));
1975
2134
  if (!email) {
1976
2135
  console.log();
1977
- console.log(import_chalk.default.red(" No email provided. Exiting."));
2136
+ console.log(import_chalk2.default.red(" No email provided. Exiting."));
1978
2137
  process.exit(1);
1979
2138
  }
1980
2139
  if (!email.includes("@") || !email.includes(".")) {
1981
2140
  console.log();
1982
- console.log(import_chalk.default.yellow(" \u26A0 That doesn't look like a valid email address."));
1983
- const proceed = await prompt(import_chalk.default.white(" Continue anyway? ") + import_chalk.default.dim("[y/N] "));
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] "));
1984
2143
  if (proceed.toLowerCase() !== "y") {
1985
2144
  process.exit(1);
1986
2145
  }
1987
2146
  }
1988
2147
  console.log();
1989
- console.log(import_chalk.default.green(" \u2713 Email registered"));
2148
+ console.log(import_chalk2.default.green(" \u2713 Email registered"));
1990
2149
  console.log();
1991
2150
  return email;
1992
2151
  }
1993
- function checkCredentials() {
1994
- if (!hasCredentials()) {
1995
- console.log();
1996
- console.log(import_chalk.default.red(" \u26A0 Spark AI API key not configured"));
1997
- console.log();
1998
- console.log(import_chalk.default.dim(" Set the following environment variable:"));
1999
- console.log(import_chalk.default.white(" SPARK_API_KEY=your-spark-api-key"));
2000
- console.log();
2001
- console.log(import_chalk.default.dim(" Get a key at: https://sparkbrain.app/dashboard"));
2002
- console.log();
2003
- process.exit(1);
2004
- }
2005
- }
2006
2152
  async function callGroq(systemPrompt, userPrompt, logError2, _userEmail, tier = "free") {
2007
2153
  const result = await callAI(systemPrompt, userPrompt, tier);
2008
2154
  if (!result.success) {
2009
2155
  await logError2("AI API", new Error(result.error));
2010
2156
  if (result.isRateLimit) {
2011
2157
  console.log();
2012
- console.log(import_chalk.default.red(" \u26A0 Rate Limit Reached (after 3 retries)"));
2158
+ console.log(import_chalk2.default.red(" \u26A0 Rate Limit Reached (after 3 retries)"));
2013
2159
  console.log();
2014
- console.log(import_chalk.default.dim(" Spark AI daily limit exceeded. You may have hit your quota."));
2015
- console.log(import_chalk.default.dim(" Options:"));
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:"));
2016
2162
  if (result.retryAfterSeconds) {
2017
2163
  const minutes = Math.ceil(result.retryAfterSeconds / 60);
2018
- console.log(import_chalk.default.dim(` \u2022 Wait ${minutes} minutes until quota resets`));
2164
+ console.log(import_chalk2.default.dim(` \u2022 Wait ${minutes} minutes until quota resets`));
2019
2165
  } else {
2020
- console.log(import_chalk.default.dim(" \u2022 Wait until tomorrow (limits reset at midnight UTC)"));
2166
+ console.log(import_chalk2.default.dim(" \u2022 Wait until tomorrow (limits reset at midnight UTC)"));
2021
2167
  }
2022
- console.log(import_chalk.default.dim(" \u2022 Check your usage at: https://sparkbrain.app/dashboard"));
2023
- console.log(import_chalk.default.dim(" \u2022 Upgrade to a higher tier for more requests"));
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"));
2024
2170
  console.log();
2025
2171
  process.exit(1);
2026
2172
  }
2027
2173
  if (result.isAuthError) {
2028
2174
  console.log();
2029
- console.log(import_chalk.default.red(" \u26A0 Authentication Error"));
2175
+ console.log(import_chalk2.default.red(" \u26A0 Authentication Error"));
2030
2176
  console.log();
2031
- console.log(import_chalk.default.dim(` ${result.error}`));
2177
+ console.log(import_chalk2.default.dim(` ${result.error}`));
2032
2178
  console.log();
2033
2179
  process.exit(1);
2034
2180
  }
2035
2181
  console.log();
2036
- console.log(import_chalk.default.red(" \u26A0 AI Error"));
2182
+ console.log(import_chalk2.default.red(" \u26A0 AI Error"));
2037
2183
  console.log();
2038
2184
  const firstLine = result.error.split("\n")[0] ?? result.error;
2039
- console.log(import_chalk.default.dim(` ${firstLine.slice(0, 80)}`));
2185
+ console.log(import_chalk2.default.dim(` ${firstLine.slice(0, 80)}`));
2040
2186
  console.log();
2041
2187
  process.exit(1);
2042
2188
  }
@@ -2162,11 +2308,11 @@ function eventsToSteps(events, url) {
2162
2308
  let prevClickSelector;
2163
2309
  let prevClickTime = 0;
2164
2310
  let lastNavigatedUrl;
2165
- const normalizedOriginUrl = normalizeUrl(url);
2311
+ const normalizedOriginUrl = normalizeUrl2(url);
2166
2312
  for (const event of events) {
2167
2313
  if (event.type === "navigate") {
2168
2314
  if (!event.url) continue;
2169
- const normalized = normalizeUrl(event.url);
2315
+ const normalized = normalizeUrl2(event.url);
2170
2316
  if (normalized === normalizedOriginUrl) continue;
2171
2317
  if (lastNavigatedUrl !== void 0 && normalized === lastNavigatedUrl) continue;
2172
2318
  lastNavigatedUrl = normalized;
@@ -2216,7 +2362,7 @@ function eventsToSteps(events, url) {
2216
2362
  }
2217
2363
  return steps;
2218
2364
  }
2219
- function normalizeUrl(url) {
2365
+ function normalizeUrl2(url) {
2220
2366
  try {
2221
2367
  const u = new URL(url);
2222
2368
  return u.origin + u.pathname.replace(/\/$/, "");
@@ -2227,14 +2373,14 @@ function normalizeUrl(url) {
2227
2373
 
2228
2374
  // src/capture.ts
2229
2375
  var import_node_child_process4 = require("child_process");
2230
- var import_node_http2 = require("http");
2376
+ var import_node_http3 = require("http");
2231
2377
  var import_node_readline2 = require("readline");
2232
2378
  var import_playwright3 = require("playwright");
2233
2379
 
2234
2380
  // src/browser/wsl.ts
2235
2381
  var import_node_child_process3 = require("child_process");
2236
2382
  var import_node_fs3 = require("fs");
2237
- var import_node_http = require("http");
2383
+ var import_node_http2 = require("http");
2238
2384
  var import_playwright2 = require("playwright");
2239
2385
  function isWSL() {
2240
2386
  if (process.platform !== "linux") return false;
@@ -2312,7 +2458,7 @@ function waitForChrome(host, port) {
2312
2458
  reject(new Error("Chrome debug port did not open within 20 s"));
2313
2459
  return;
2314
2460
  }
2315
- const req = (0, import_node_http.request)({ host, port, path: "/json/version", method: "GET" }, (res) => {
2461
+ const req = (0, import_node_http2.request)({ host, port, path: "/json/version", method: "GET" }, (res) => {
2316
2462
  res.resume();
2317
2463
  resolve9();
2318
2464
  });
@@ -2502,7 +2648,7 @@ async function launchCaptureChrome() {
2502
2648
  const windowsHost = getWindowsHostIP();
2503
2649
  const probe = await startRelay(nodePath, windowsHost, debugPort);
2504
2650
  const alreadyUp = await new Promise((resolve9) => {
2505
- const req = (0, import_node_http2.request)({ host: windowsHost, port: probe.port, path: "/json/version", method: "GET" }, (res) => {
2651
+ const req = (0, import_node_http3.request)({ host: windowsHost, port: probe.port, path: "/json/version", method: "GET" }, (res) => {
2506
2652
  res.resume();
2507
2653
  resolve9(true);
2508
2654
  });
@@ -2831,7 +2977,7 @@ async function logStep(stepNum, action, details) {
2831
2977
  // src/utils/ui.ts
2832
2978
  var import_node_path8 = require("path");
2833
2979
  var import_node_readline3 = require("readline");
2834
- var import_chalk2 = __toESM(require("chalk"));
2980
+ var import_chalk3 = __toESM(require("chalk"));
2835
2981
  var import_ora = __toESM(require("ora"));
2836
2982
  var INDENT = " ";
2837
2983
  var autoYes = false;
@@ -2843,16 +2989,16 @@ function task(text) {
2843
2989
  }
2844
2990
  function header(text) {
2845
2991
  console.log();
2846
- console.log(import_chalk2.default.bold.cyan(`\u25A0 ${text}`));
2992
+ console.log(import_chalk3.default.bold.cyan(`\u25A0 ${text}`));
2847
2993
  }
2848
2994
  function info(text) {
2849
- console.log(`${INDENT}${import_chalk2.default.dim("\u2502")} ${import_chalk2.default.dim(text)}`);
2995
+ console.log(`${INDENT}${import_chalk3.default.dim("\u2502")} ${import_chalk3.default.dim(text)}`);
2850
2996
  }
2851
2997
  function confirm(prompt2) {
2852
2998
  if (autoYes) return Promise.resolve(true);
2853
2999
  const rl = (0, import_node_readline3.createInterface)({ input: process.stdin, output: process.stdout });
2854
3000
  return new Promise((res) => {
2855
- rl.question(import_chalk2.default.yellow(`${prompt2} [Y/n] `), (answer) => {
3001
+ rl.question(import_chalk3.default.yellow(`${prompt2} [Y/n] `), (answer) => {
2856
3002
  rl.close();
2857
3003
  res(answer.toLowerCase() !== "n");
2858
3004
  });
@@ -2862,7 +3008,7 @@ function promptInput(prompt2) {
2862
3008
  if (autoYes) return Promise.resolve("");
2863
3009
  const rl = (0, import_node_readline3.createInterface)({ input: process.stdin, output: process.stdout });
2864
3010
  return new Promise((res) => {
2865
- rl.question(import_chalk2.default.cyan(`${INDENT}${prompt2}`), (answer) => {
3011
+ rl.question(import_chalk3.default.cyan(`${INDENT}${prompt2}`), (answer) => {
2866
3012
  rl.close();
2867
3013
  res(answer.trim());
2868
3014
  });
@@ -2897,7 +3043,7 @@ init_lib();
2897
3043
  var import_node_fs9 = require("fs");
2898
3044
  var import_promises9 = require("fs/promises");
2899
3045
  var import_node_path14 = require("path");
2900
- var import_chalk3 = __toESM(require("chalk"));
3046
+ var import_chalk4 = __toESM(require("chalk"));
2901
3047
 
2902
3048
  // src/browser/actions.ts
2903
3049
  var import_node_fs6 = require("fs");
@@ -4072,9 +4218,9 @@ async function recordSession(plan, config, stepDurations, workDir, baseDir, task
4072
4218
  actualStepDurations.push(actualDuration);
4073
4219
  }
4074
4220
  if (failures.length > 0) {
4075
- console.log(import_chalk3.default.yellow(` \u26A0 ${failures.length} step(s) skipped:`));
4221
+ console.log(import_chalk4.default.yellow(` \u26A0 ${failures.length} step(s) skipped:`));
4076
4222
  for (const f of failures) {
4077
- console.log(import_chalk3.default.yellow(` - ${f}`));
4223
+ console.log(import_chalk4.default.yellow(` - ${f}`));
4078
4224
  }
4079
4225
  }
4080
4226
  const saveTask = task2("Saving video");
@@ -4245,10 +4391,10 @@ async function composeAndFinish(videoPath, audioPath, assPath, config, userTier
4245
4391
  async function speedUpToFitLimit(finalPath, config, limitSecs = 60) {
4246
4392
  const duration = await getMediaDuration(finalPath);
4247
4393
  if (duration <= limitSecs * 1e3) return finalPath;
4248
- const chalk17 = require("chalk");
4394
+ const chalk19 = require("chalk");
4249
4395
  const speedFactor = Math.ceil(duration / (limitSecs * 1e3) * 10) / 10;
4250
4396
  console.log();
4251
- console.log(chalk17.yellow(` Video is ${(duration / 1e3).toFixed(0)}s - over ${limitSecs}s`));
4397
+ console.log(chalk19.yellow(` Video is ${(duration / 1e3).toFixed(0)}s - over ${limitSecs}s`));
4252
4398
  const speedUp = await confirm(` Speed up ${speedFactor}x to fit under ${limitSecs}s?`);
4253
4399
  if (!speedUp) return finalPath;
4254
4400
  const speedTask = task(`Speeding up ${speedFactor}x`);
@@ -4270,13 +4416,142 @@ async function speedUpToFitLimit(finalPath, config, limitSecs = 60) {
4270
4416
  }
4271
4417
 
4272
4418
  // src/commands/utils/record-helpers.ts
4419
+ var import_chalk7 = __toESM(require("chalk"));
4420
+ init_lib();
4421
+
4422
+ // src/help.ts
4273
4423
  var import_chalk5 = __toESM(require("chalk"));
4274
4424
  init_lib();
4275
4425
 
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
+
4276
4551
  // src/plan.ts
4277
4552
  var import_promises11 = require("fs/promises");
4278
4553
  var import_node_path16 = require("path");
4279
- var import_chalk4 = __toESM(require("chalk"));
4554
+ var import_chalk6 = __toESM(require("chalk"));
4280
4555
  var import_playwright5 = require("playwright");
4281
4556
  init_lib();
4282
4557
  async function crawlExtension(url, extensionPath, workDir, task2) {
@@ -4302,7 +4577,7 @@ async function crawlExtension(url, extensionPath, workDir, task2) {
4302
4577
  const contexts = chromeSession.browser.contexts();
4303
4578
  context = contexts[0] || await chromeSession.browser.newContext({ viewport: { width: 400, height: 600 } });
4304
4579
  } catch (_err) {
4305
- console.log(import_chalk4.default.yellow(" \u26A0 Windows Chrome unavailable, using Chromium"));
4580
+ console.log(import_chalk6.default.yellow(" \u26A0 Windows Chrome unavailable, using Chromium"));
4306
4581
  context = await launchPersistent();
4307
4582
  }
4308
4583
  } else {
@@ -4412,7 +4687,6 @@ async function validateAndFixPlan(planPath, task2, logError2, userEmail, tier =
4412
4687
  plan = JSON.parse(content);
4413
4688
  } catch (_parseError) {
4414
4689
  const fixTask = task2("Fixing malformed JSON");
4415
- checkCredentials();
4416
4690
  try {
4417
4691
  const fixed = await callGroq(
4418
4692
  "You fix malformed JSON. Return ONLY valid JSON, no explanation.",
@@ -4499,31 +4773,41 @@ function displayRateLimitInfo() {
4499
4773
  const percentage = Math.round(limits.used / limits.limit * 100);
4500
4774
  if (percentage >= 90) {
4501
4775
  console.log();
4502
- console.log(import_chalk5.default.yellow(` \u26A0 ${remaining} requests remaining (${limits.tier} tier)`));
4776
+ console.log(import_chalk7.default.yellow(` \u26A0 ${remaining} requests remaining (${limits.tier} tier)`));
4503
4777
  if (limits.tier === "free") {
4504
- console.log(import_chalk5.default.dim(` Upgrade: ${getUpgradeUrl()}`));
4778
+ console.log(import_chalk7.default.dim(` Upgrade: ${getUpgradeUrl()}`));
4505
4779
  }
4506
4780
  } else if (percentage >= 50) {
4507
- console.log(import_chalk5.default.dim(` ${remaining}/${limits.limit} requests remaining`));
4781
+ console.log(import_chalk7.default.dim(` ${remaining}/${limits.limit} requests remaining`));
4508
4782
  }
4509
4783
  }
4510
- function displayUsageStatus(userEmail, userTier, localVideoCount = 0) {
4511
- const tierLabel = userTier === "pro" ? "Pro" : "Free";
4512
- const used = localVideoCount + 1;
4513
- console.log(import_chalk5.default.cyan(` \u2714 ${tierLabel} tier (${used}/${FREE_VIDEO_LIMIT})`));
4784
+ async function displayUsageStatus(userEmail, userTier, _localVideoCount = 0) {
4785
+ 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}`));
4789
+ if (userEmail && userEmail !== "anonymous") {
4790
+ try {
4791
+ const status = await getStatusCached(userEmail);
4792
+ for (const line of formatUsageLines(status, tier)) {
4793
+ console.log(import_chalk7.default.dim(` ${line}`));
4794
+ }
4795
+ } catch {
4796
+ }
4797
+ }
4514
4798
  if (userTier === "free") {
4515
4799
  const upgradeUrl = getUpgradeUrl(userEmail !== "anonymous" ? userEmail : void 0);
4516
- console.log(import_chalk5.default.dim(` Upgrade: ${upgradeUrl}`));
4800
+ console.log(import_chalk7.default.dim(` Upgrade: ${upgradeUrl}`));
4517
4801
  }
4518
4802
  }
4519
4803
  function displayPlanSummary(plan) {
4520
4804
  console.log();
4521
- console.log(import_chalk5.default.dim(` ${plan.title || "Untitled"}`));
4522
- console.log(import_chalk5.default.dim(` ${plan.steps?.length || 0} steps`));
4805
+ console.log(import_chalk7.default.dim(` ${plan.title || "Untitled"}`));
4806
+ console.log(import_chalk7.default.dim(` ${plan.steps?.length || 0} steps`));
4523
4807
  for (const step of plan.steps || []) {
4524
4808
  const text = step.caption || step.action || "";
4525
4809
  const caption = text.length > 50 ? `${text.slice(0, 47)}...` : text;
4526
- console.log(import_chalk5.default.dim(` ${step.id}. ${caption}`));
4810
+ console.log(import_chalk7.default.dim(` ${step.id}. ${caption}`));
4527
4811
  }
4528
4812
  }
4529
4813
  function getOutputDirFromUrl(url) {
@@ -4753,7 +5037,7 @@ async function runVideoPhase(params) {
4753
5037
  // src/commands/phases/voiceover-phase.ts
4754
5038
  var import_node_fs12 = require("fs");
4755
5039
  var import_node_path18 = require("path");
4756
- var import_chalk6 = __toESM(require("chalk"));
5040
+ var import_chalk8 = __toESM(require("chalk"));
4757
5041
  init_lib();
4758
5042
  var INDENT5 = " ";
4759
5043
  async function runVoiceoverPhase(plan, config, workDir, skipVoice, fresh) {
@@ -4795,9 +5079,9 @@ async function runVoiceoverPhase(plan, config, workDir, skipVoice, fresh) {
4795
5079
  const reuseCheck = canReuseVoiceover(plan.steps, voice, existingMeta);
4796
5080
  if (reuseCheck.canReuse) {
4797
5081
  console.log();
4798
- console.log(import_chalk6.default.dim(` Found existing voiceover in ${config.output.dir}`));
5082
+ console.log(import_chalk8.default.dim(` Found existing voiceover in ${config.output.dir}`));
4799
5083
  if (reuseCheck.reason) {
4800
- console.log(import_chalk6.default.dim(` ${reuseCheck.reason}`));
5084
+ console.log(import_chalk8.default.dim(` ${reuseCheck.reason}`));
4801
5085
  }
4802
5086
  const reuse = await confirm(`${INDENT5}Reuse existing voiceover?`);
4803
5087
  if (reuse) {
@@ -4837,7 +5121,7 @@ async function captureCommand(url, lang) {
4837
5121
  const captureResult = await captureUserSession(url, workDir);
4838
5122
  info(`Captured ${captureResult.events.length} interactions over ${(captureResult.durationMs / 1e3).toFixed(0)}s`);
4839
5123
  if (captureResult.events.length === 0) {
4840
- console.log(import_chalk7.default.yellow(" No interactions captured. Exiting."));
5124
+ console.log(import_chalk9.default.yellow(" No interactions captured. Exiting."));
4841
5125
  return;
4842
5126
  }
4843
5127
  header("Convert");
@@ -4845,7 +5129,7 @@ async function captureCommand(url, lang) {
4845
5129
  const rawSteps = eventsToSteps(captureResult.events, url);
4846
5130
  if (rawSteps.length === 0) {
4847
5131
  convertTask.fail("No actionable steps found");
4848
- console.log(import_chalk7.default.yellow(" No actionable steps could be derived from the captured events."));
5132
+ console.log(import_chalk9.default.yellow(" No actionable steps could be derived from the captured events."));
4849
5133
  return;
4850
5134
  }
4851
5135
  convertTask.succeed(`${rawSteps.length} steps extracted`);
@@ -4880,13 +5164,13 @@ async function captureCommand(url, lang) {
4880
5164
  const planPath = (0, import_node_path19.join)(config.output.dir, `plan-${lang ?? "en"}.json`);
4881
5165
  await savePlan(plan, planPath);
4882
5166
  console.log();
4883
- console.log(import_chalk7.default.bold.green(` Plan saved: ${planPath}`));
4884
- console.log(import_chalk7.default.dim(` Run: qp ${url} to replay and create a polished demo video`));
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`));
4885
5169
  console.log();
4886
5170
  const makeVideo = await confirm(" Create polished demo video now?");
4887
5171
  if (!makeVideo) {
4888
5172
  console.log();
4889
- console.log(import_chalk7.default.dim(` Run: qp ${url} anytime to create a video from this plan.`));
5173
+ console.log(import_chalk9.default.dim(` Run: qp ${url} anytime to create a video from this plan.`));
4890
5174
  return;
4891
5175
  }
4892
5176
  header("Voiceover");
@@ -4902,20 +5186,20 @@ async function captureCommand(url, lang) {
4902
5186
  userTier
4903
5187
  });
4904
5188
  console.log();
4905
- console.log(import_chalk7.default.bold.green(" Done!"));
4906
- console.log(import_chalk7.default.cyan(` ${finalPath}`));
5189
+ console.log(import_chalk9.default.bold.green(" Done!"));
5190
+ console.log(import_chalk9.default.cyan(` ${finalPath}`));
4907
5191
  }
4908
5192
 
4909
5193
  // src/commands/init-cmd.ts
4910
5194
  var import_node_fs13 = require("fs");
4911
5195
  var import_promises13 = require("fs/promises");
4912
5196
  var import_node_path20 = require("path");
4913
- var import_chalk8 = __toESM(require("chalk"));
5197
+ var import_chalk10 = __toESM(require("chalk"));
4914
5198
  init_lib();
4915
5199
  async function initCommand() {
4916
5200
  const configPath = (0, import_node_path20.join)(process.cwd(), CONFIG_FILE);
4917
5201
  if ((0, import_node_fs13.existsSync)(configPath)) {
4918
- console.log(import_chalk8.default.yellow(` \u26A0 ${CONFIG_FILE} already exists`));
5202
+ console.log(import_chalk10.default.yellow(` \u26A0 ${CONFIG_FILE} already exists`));
4919
5203
  return;
4920
5204
  }
4921
5205
  const defaultConfig = {
@@ -4940,24 +5224,24 @@ async function initCommand() {
4940
5224
  };
4941
5225
  await (0, import_promises13.writeFile)(configPath, `${JSON.stringify(defaultConfig, null, 2)}
4942
5226
  `);
4943
- console.log(import_chalk8.default.green(` \u2714 Created ${CONFIG_FILE}`));
4944
- console.log(import_chalk8.default.dim(` Edit it or use: qp config <key> <value>`));
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>`));
4945
5229
  }
4946
5230
  async function configCommand(key, value) {
4947
5231
  const configPath = (0, import_node_path20.join)(process.cwd(), CONFIG_FILE);
4948
5232
  if (!key) {
4949
5233
  if (!(0, import_node_fs13.existsSync)(configPath)) {
4950
- console.log(import_chalk8.default.yellow(` No ${CONFIG_FILE} found. Run: qp init`));
5234
+ console.log(import_chalk10.default.yellow(` No ${CONFIG_FILE} found. Run: qp init`));
4951
5235
  return;
4952
5236
  }
4953
5237
  const content = await (0, import_promises13.readFile)(configPath, "utf-8");
4954
- console.log(import_chalk8.default.cyan(` ${CONFIG_FILE}:`));
5238
+ console.log(import_chalk10.default.cyan(` ${CONFIG_FILE}:`));
4955
5239
  console.log(content);
4956
5240
  return;
4957
5241
  }
4958
5242
  if (!value) {
4959
- console.log(import_chalk8.default.red(` Missing value for ${key}`));
4960
- console.log(import_chalk8.default.dim(` Usage: qp config ${key} <value>`));
5243
+ console.log(import_chalk10.default.red(` Missing value for ${key}`));
5244
+ console.log(import_chalk10.default.dim(` Usage: qp config ${key} <value>`));
4961
5245
  return;
4962
5246
  }
4963
5247
  let config = {};
@@ -4987,122 +5271,99 @@ async function configCommand(key, value) {
4987
5271
  }
4988
5272
  await (0, import_promises13.writeFile)(configPath, `${JSON.stringify(config, null, 2)}
4989
5273
  `);
4990
- console.log(import_chalk8.default.green(` \u2714 Set ${key} = ${value}`));
5274
+ console.log(import_chalk10.default.green(` \u2714 Set ${key} = ${value}`));
4991
5275
  }
4992
5276
 
4993
- // src/commands/record-cmd.ts
4994
- var import_node_fs16 = require("fs");
4995
- var import_promises16 = require("fs/promises");
4996
- var import_node_path23 = require("path");
4997
- var import_chalk14 = __toESM(require("chalk"));
4998
-
4999
- // src/help.ts
5000
- var import_chalk9 = __toESM(require("chalk"));
5277
+ // src/commands/login-cmd.ts
5278
+ var import_chalk11 = __toESM(require("chalk"));
5001
5279
  init_lib();
5002
-
5003
- // src/logo.ts
5004
- var import_figlet = __toESM(require("figlet"));
5005
- var import_gradient_string = __toESM(require("gradient-string"));
5006
- var DEFAULT_LOGO_CONFIG = {
5007
- text: "QuickPeek",
5008
- font: "Slant",
5009
- horizontalLayout: "default",
5010
- gradientColors: ["#00ffff", "#00d4ff", "#a855f7", "#ec4899"],
5011
- suffix: " \u{1F440}"
5012
- };
5013
- function renderLogo(config = {}) {
5014
- const resolved = { ...DEFAULT_LOGO_CONFIG, ...config };
5015
- const ascii = import_figlet.default.textSync(resolved.text, {
5016
- font: resolved.font,
5017
- horizontalLayout: resolved.horizontalLayout
5018
- });
5019
- const colored = (0, import_gradient_string.default)(resolved.gradientColors)(ascii);
5020
- return resolved.suffix ? `${colored}${resolved.suffix}` : colored;
5021
- }
5022
-
5023
- // src/help.ts
5024
- var cmd = import_chalk9.default.cyan;
5025
- var arg = import_chalk9.default.green;
5026
- var opt = import_chalk9.default.magenta;
5027
- var desc = import_chalk9.default.white;
5028
- var ex = import_chalk9.default.dim;
5029
- function pad2(colored, visible, width) {
5030
- return colored + " ".repeat(Math.max(0, width - visible.length));
5031
- }
5032
- function showLogo() {
5033
- const logo = renderLogo();
5034
- console.log(logo);
5035
- console.log(import_chalk9.default.dim(` v${VERSION} - Auto-generate demo videos with AI`));
5036
- console.log(import_chalk9.default.italic.dim(" Because Tutorial Videos are Overrated!\n"));
5037
- }
5038
- function showHelp() {
5039
- showLogo();
5040
- const W = 28;
5041
- const usage = [
5042
- [`${cmd("qp")} ${arg("<url>")}`, `qp <url>`, "AI-generated demo video"],
5043
- [`${cmd("qp")} ${arg("<url>")} ${opt("--script")} ${arg("<file>")}`, `qp <url> --script <file>`, "Use your own narration script"],
5044
- [`${cmd("qp capture")} ${arg("<url>")}`, `qp capture <url>`, "Record interactions manually"],
5045
- [`${cmd("qp init")}`, `qp init`, "Create config file"],
5046
- [`${cmd("qp config")} ${arg("<key>")} ${arg("<value>")}`, `qp config <key> <value>`, "Update a config option"]
5047
- ];
5048
- const options = [
5049
- [`${opt("lang=")}${arg("xx")}`, `lang=xx`, "Voice language (en, es, ar, zh, fr, de, ja...)"],
5050
- [`${opt("-d")}, ${opt("--description")} ${arg('"..."')}`, `-d, --description "..."`, "Guide AI with an app description"],
5051
- [`${opt("-s")}, ${opt("--script")} ${arg("<file>")}`, `-s, --script <file>`, "Narration script (one caption per line)"],
5052
- [`${opt("-y")}, ${opt("--yes")}`, `-y, --yes`, "Skip confirmation prompts"],
5053
- [`${opt("--skip-voice")}, ${opt("-sv")}`, `--skip-voice, -sv`, "Reuse existing voiceover"],
5054
- [`${opt("-f")}, ${opt("--fresh")}`, `-f, --fresh`, "Clear cached voiceover and recording"]
5055
- ];
5056
- const examples = [
5057
- `${ex("$")} ${cmd("qp")} ${arg("localhost:3000")}`,
5058
- `${ex("$")} ${cmd("qp")} ${arg("myapp.com")} ${opt("lang=")}${arg("es")} ${opt("-d")} ${arg('"E-commerce checkout flow"')}`,
5059
- `${ex("$")} ${cmd("qp")} ${arg("myapp.com")} ${opt("--script")} ${arg("narration.txt")}`,
5060
- `${ex("$")} ${cmd("qp capture")} ${arg("localhost:3000")}`
5061
- ];
5280
+ async function loginCommand() {
5062
5281
  console.log();
5063
- console.log(import_chalk9.default.yellow.bold("USAGE"));
5064
- for (const [colored, plain, d] of usage) {
5065
- console.log(` ${pad2(colored, plain, W)}${desc(d)}`);
5282
+ showLogo();
5283
+ const existing = await loadUserData();
5284
+ 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."));
5287
+ console.log();
5288
+ try {
5289
+ const status = await getStatusCached(existing.email);
5290
+ const tier2 = await getTierByEmail(existing.email);
5291
+ const tierLabel2 = tier2 === "pro" ? import_chalk11.default.green("Pro") : "Free";
5292
+ console.log(` ${tierLabel2} plan`);
5293
+ for (const line of formatUsageLines(status, tier2)) {
5294
+ console.log(import_chalk11.default.dim(` ${line}`));
5295
+ }
5296
+ } catch {
5297
+ }
5298
+ console.log();
5299
+ return;
5066
5300
  }
5067
- console.log();
5068
- console.log(import_chalk9.default.yellow.bold("OPTIONS"));
5069
- for (const [colored, plain, d] of options) {
5070
- console.log(` ${pad2(colored, plain, W)}${desc(d)}`);
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."));
5303
+ const email = await browserLogin();
5304
+ if (!email) {
5305
+ 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."));
5308
+ console.log();
5309
+ return;
5071
5310
  }
5072
- console.log();
5073
- console.log(import_chalk9.default.yellow.bold("EXAMPLES"));
5074
- for (const e of examples) {
5075
- console.log(` ${e}`);
5311
+ let tier = "free";
5312
+ try {
5313
+ tier = await getTierByEmail(email);
5314
+ } catch {
5076
5315
  }
5316
+ await saveUserData({ email, verified: true, tier });
5317
+ 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}`));
5320
+ console.log(` ${tierLabel} plan`);
5077
5321
  console.log();
5078
- console.log(import_chalk9.default.yellow.bold("ENVIRONMENT"));
5079
- console.log(` ${pad2(opt("SPARK_API_KEY"), "SPARK_API_KEY", W)}${desc("Spark AI API key")} ${ex("(get at sparkbrain.app/dashboard)")}`);
5322
+ }
5323
+ async function logoutCommand() {
5324
+ console.log();
5325
+ const userData = await loadUserData();
5326
+ if (!userData.email) {
5327
+ console.log(import_chalk11.default.dim(" Not signed in."));
5328
+ console.log();
5329
+ return;
5330
+ }
5331
+ const email = userData.email;
5332
+ await saveUserData({ email: "", verified: false, tier: "free" });
5333
+ console.log(import_chalk11.default.green(` \u2713 Signed out (${email})`));
5080
5334
  console.log();
5081
5335
  }
5082
5336
 
5083
5337
  // 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"));
5084
5342
  init_lib();
5085
5343
 
5086
5344
  // src/commands/phases/auth-phase.ts
5087
- var import_chalk11 = __toESM(require("chalk"));
5345
+ var import_chalk13 = __toESM(require("chalk"));
5088
5346
  init_lib();
5089
5347
 
5090
5348
  // src/commands/utils/auth-helpers.ts
5091
- var import_chalk10 = __toESM(require("chalk"));
5349
+ var import_chalk12 = __toESM(require("chalk"));
5092
5350
  init_lib();
5093
5351
  async function handleRegistrationGate() {
5094
5352
  console.log();
5095
- console.log(import_chalk10.default.yellow(" \u{1F389} You've made 5 free videos!"));
5096
- console.log(import_chalk10.default.white(" Register to continue creating videos."));
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."));
5097
5355
  console.log();
5098
- console.log(import_chalk10.default.dim(" Benefits of registering:"));
5099
- console.log(import_chalk10.default.dim(" \u2022 20 more videos per month"));
5100
- console.log(import_chalk10.default.dim(" \u2022 Keep your video history"));
5101
- console.log(import_chalk10.default.dim(" \u2022 Upgrade to Pro anytime"));
5102
- console.log();
5103
- const email = await promptInput("Email: ");
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"));
5360
+ let email = await browserLogin();
5361
+ if (!email) {
5362
+ console.log();
5363
+ email = await promptInput("Email: ");
5364
+ }
5104
5365
  if (!email || !email.includes("@")) {
5105
- console.log(import_chalk10.default.red(" Invalid email. Run qp again to try again."));
5366
+ console.log(import_chalk12.default.red(" Invalid email. Run qp again to try again."));
5106
5367
  return null;
5107
5368
  }
5108
5369
  const registerTask = task("Sending verification email");
@@ -5117,23 +5378,23 @@ async function handleRegistrationGate() {
5117
5378
  }
5118
5379
  registerTask.succeed("Check your email!");
5119
5380
  console.log();
5120
- console.log(import_chalk10.default.cyan(" \u{1F4E7} Verification email sent to:"));
5121
- console.log(import_chalk10.default.white(` ${email}`));
5381
+ console.log(import_chalk12.default.cyan(" \u{1F4E7} Verification email sent to:"));
5382
+ console.log(import_chalk12.default.white(` ${email}`));
5122
5383
  console.log();
5123
- console.log(import_chalk10.default.dim(" Click the link in the email, then press Enter to continue..."));
5384
+ console.log(import_chalk12.default.dim(" Click the link in the email, then press Enter to continue..."));
5124
5385
  await promptInput("");
5125
5386
  const verifyTask = task("Checking verification");
5126
- const status = await checkVerificationStatus(email);
5127
- if (status.verified) {
5387
+ const verified = await isVerified(email);
5388
+ if (verified) {
5128
5389
  verifyTask.succeed("Email verified!");
5129
5390
  return { email, verified: true };
5130
5391
  }
5131
5392
  verifyTask.fail("Not verified yet");
5132
5393
  console.log();
5133
- console.log(import_chalk10.default.yellow(" Email not verified. Check your inbox and spam folder."));
5134
- console.log(import_chalk10.default.dim(" Run qp again after clicking the verification link."));
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."));
5135
5396
  console.log();
5136
- console.log(import_chalk10.default.cyan(` Or register/upgrade at: ${getUpgradeUrl(email)}`));
5397
+ console.log(import_chalk12.default.cyan(` Or register/upgrade at: ${getUpgradeUrl(email)}`));
5137
5398
  console.log();
5138
5399
  return { email, verified: false };
5139
5400
  }
@@ -5141,27 +5402,27 @@ async function handleRegistrationGate() {
5141
5402
  // src/commands/phases/auth-phase.ts
5142
5403
  async function runAuthPhase(input) {
5143
5404
  let { userEmail, userTier, localVideoCount } = input;
5144
- const { isVerified } = input;
5145
- if (userEmail && isVerified) {
5405
+ const { isVerified: isVerified2 } = input;
5406
+ if (userEmail && isVerified2) {
5146
5407
  const tierTask = task("Checking subscription");
5147
- userTier = await getTier(userEmail);
5408
+ userTier = await getTierByEmail(userEmail);
5148
5409
  tierTask.stop();
5149
- displayUsageStatus(userEmail, userTier, localVideoCount);
5410
+ await displayUsageStatus(userEmail, userTier, localVideoCount);
5150
5411
  if (userTier !== input.userTier) {
5151
5412
  await saveUserData({ tier: userTier });
5152
5413
  }
5153
- } else if (userEmail && !isVerified) {
5414
+ } else if (userEmail && !isVerified2) {
5154
5415
  const verifyTask = task("Checking verification");
5155
- const status = await checkVerificationStatus(userEmail);
5156
- if (status.verified) {
5416
+ const verified = await isVerified(userEmail);
5417
+ if (verified) {
5157
5418
  verifyTask.stop();
5158
- await saveUserData({ verified: true, tier: status.tier });
5159
- userTier = status.tier;
5160
- displayUsageStatus(userEmail, userTier, localVideoCount);
5419
+ userTier = await getTierByEmail(userEmail);
5420
+ await saveUserData({ verified: true, tier: userTier });
5421
+ await displayUsageStatus(userEmail, userTier, localVideoCount);
5161
5422
  } else {
5162
5423
  verifyTask.fail("Email not verified");
5163
5424
  console.log();
5164
- console.log(import_chalk11.default.yellow(" Check your inbox for the verification email."));
5425
+ console.log(import_chalk13.default.yellow(" Check your inbox for the verification email."));
5165
5426
  console.log();
5166
5427
  const input2 = await promptInput(`New email or Enter to retry (${userEmail}): `);
5167
5428
  if (input2.toLowerCase() === "exit") {
@@ -5181,29 +5442,29 @@ async function runAuthPhase(input) {
5181
5442
  registerTask.stop();
5182
5443
  userEmail = newEmail;
5183
5444
  await saveUserData({ email: newEmail, verified: true });
5184
- displayUsageStatus(userEmail, userTier, localVideoCount);
5445
+ await displayUsageStatus(userEmail, userTier, localVideoCount);
5185
5446
  } else {
5186
5447
  registerTask.succeed("Check your email!");
5187
5448
  await saveUserData({ email: newEmail, verified: false });
5188
5449
  console.log();
5189
- console.log(import_chalk11.default.cyan(` \u{1F4E7} Verification sent to: ${newEmail}`));
5190
- console.log(import_chalk11.default.dim(" Click the link in the email, then run qp again."));
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."));
5191
5452
  console.log();
5192
5453
  gracefulExit();
5193
5454
  return null;
5194
5455
  }
5195
5456
  } else {
5196
5457
  const recheckTask = task("Rechecking verification");
5197
- const recheckStatus = await checkVerificationStatus(userEmail);
5198
- if (recheckStatus.verified) {
5458
+ const recheckVerified = await isVerified(userEmail);
5459
+ if (recheckVerified) {
5199
5460
  recheckTask.stop();
5200
- await saveUserData({ verified: true, tier: recheckStatus.tier });
5201
- userTier = recheckStatus.tier;
5202
- displayUsageStatus(userEmail, userTier, localVideoCount);
5461
+ userTier = await getTierByEmail(userEmail);
5462
+ await saveUserData({ verified: true, tier: userTier });
5463
+ await displayUsageStatus(userEmail, userTier, localVideoCount);
5203
5464
  } else {
5204
5465
  recheckTask.fail("Still not verified");
5205
5466
  console.log();
5206
- console.log(import_chalk11.default.cyan(` Register/upgrade at: ${getUpgradeUrl(userEmail)}`));
5467
+ console.log(import_chalk13.default.cyan(` Register/upgrade at: ${getUpgradeUrl(userEmail)}`));
5207
5468
  console.log();
5208
5469
  gracefulExit();
5209
5470
  return null;
@@ -5226,12 +5487,12 @@ async function runAuthPhase(input) {
5226
5487
  gracefulExit();
5227
5488
  return null;
5228
5489
  }
5229
- displayUsageStatus(userEmail, userTier, localVideoCount);
5490
+ await displayUsageStatus(userEmail, userTier, localVideoCount);
5230
5491
  } else {
5231
5492
  const remaining = FREE_VIDEO_LIMIT - localVideoCount;
5232
5493
  console.log();
5233
- console.log(import_chalk11.default.cyan(` \u{1F3AC} Video ${localVideoCount + 1}/${FREE_VIDEO_LIMIT} free`));
5234
- console.log(import_chalk11.default.dim(` ${remaining - 1} more after this \u2022 Sign up for unlimited`));
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`));
5235
5496
  userEmail = await setupUserEmail();
5236
5497
  await saveUserData({ email: userEmail, verified: false, localVideoCount });
5237
5498
  }
@@ -5239,7 +5500,7 @@ async function runAuthPhase(input) {
5239
5500
  userEmail: userEmail ?? "anonymous",
5240
5501
  userTier,
5241
5502
  localVideoCount,
5242
- isVerified
5503
+ isVerified: isVerified2
5243
5504
  };
5244
5505
  }
5245
5506
 
@@ -5247,7 +5508,7 @@ async function runAuthPhase(input) {
5247
5508
  var import_node_fs14 = require("fs");
5248
5509
  var import_promises14 = require("fs/promises");
5249
5510
  var import_node_path21 = require("path");
5250
- var import_chalk12 = __toESM(require("chalk"));
5511
+ var import_chalk14 = __toESM(require("chalk"));
5251
5512
 
5252
5513
  // src/ai/fix.ts
5253
5514
  init_lib();
@@ -5443,8 +5704,8 @@ async function runPlanPhase(params) {
5443
5704
  } catch {
5444
5705
  }
5445
5706
  }
5446
- const normalizeUrl3 = (u) => u.replace(/\/+$/, "").toLowerCase();
5447
- const urlMatches = existingPlan && normalizeUrl3(existingPlan.url) === normalizeUrl3(url);
5707
+ const normalizeUrl4 = (u) => u.replace(/\/+$/, "").toLowerCase();
5708
+ const urlMatches = existingPlan && normalizeUrl4(existingPlan.url) === normalizeUrl4(url);
5448
5709
  let plan;
5449
5710
  const doTranslate = async (failMsg) => {
5450
5711
  const translateTask = task(`Translating to ${targetLang}`);
@@ -5461,23 +5722,23 @@ async function runPlanPhase(params) {
5461
5722
  };
5462
5723
  if (existingPlan && existingPlanPath) {
5463
5724
  console.log();
5464
- console.log(import_chalk12.default.dim(` Found existing plan: `) + import_chalk12.default.cyan(formatClickablePath(existingPlanPath)));
5465
- console.log(import_chalk12.default.dim(` ${existingPlan.steps.length} steps for ${existingPlan.url}`));
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}`));
5466
5727
  if (!urlMatches) {
5467
- console.log(import_chalk12.default.yellow(` \u26A0 Plan URL differs from argument:`));
5468
- console.log(import_chalk12.default.yellow(` Plan: ${existingPlan.url}`));
5469
- console.log(import_chalk12.default.yellow(` Arg: ${url}`));
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}`));
5470
5731
  }
5471
5732
  const existingLang = existingPlan.lang || "en";
5472
5733
  if (existingLang !== targetLang) {
5473
- console.log(import_chalk12.default.yellow(` \u26A0 Plan is in ${existingLang}, config is ${targetLang}`));
5734
+ console.log(import_chalk14.default.yellow(` \u26A0 Plan is in ${existingLang}, config is ${targetLang}`));
5474
5735
  }
5475
5736
  const reuse = await confirm(`${INDENT6}Use existing plan?`);
5476
5737
  if (reuse) {
5477
5738
  plan = await validateAndFixPlan(existingPlanPath, task, logError, userEmail, userTier);
5478
5739
  if (existingPlanPath !== planPath) {
5479
5740
  await savePlan(plan, planPath);
5480
- console.log(import_chalk12.default.dim(` Migrated to: ${formatClickablePath(planPath)}`));
5741
+ console.log(import_chalk14.default.dim(` Migrated to: ${formatClickablePath(planPath)}`));
5481
5742
  }
5482
5743
  if (existingLang !== targetLang) {
5483
5744
  const translate = await confirm(`${INDENT6}Translate to ${targetLang}?`);
@@ -5512,7 +5773,7 @@ async function runPlanPhase(params) {
5512
5773
  uploadIdx++;
5513
5774
  }
5514
5775
  }
5515
- console.log(import_chalk12.default.dim(` Preserved upload paths from previous plan`));
5776
+ console.log(import_chalk14.default.dim(` Preserved upload paths from previous plan`));
5516
5777
  }
5517
5778
  await savePlan(plan, planPath);
5518
5779
  plan = await validateAndFixPlan(planPath, task, logError, userEmail, userTier);
@@ -5536,8 +5797,8 @@ async function runPlanPhase(params) {
5536
5797
  }
5537
5798
  displayPlanSummary(plan);
5538
5799
  console.log();
5539
- console.log(import_chalk12.default.dim(` Plan: ${formatClickablePath(planPath)}`));
5540
- console.log(import_chalk12.default.dim(` Enter a prompt to refine, or press Enter to continue.`));
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.`));
5541
5802
  const MAX_REFINEMENTS = 10;
5542
5803
  let refinementCount = 0;
5543
5804
  while (true) {
@@ -5555,15 +5816,15 @@ async function runPlanPhase(params) {
5555
5816
  refineTask.succeed("Plan refined");
5556
5817
  displayRateLimitInfo();
5557
5818
  if (plan.steps.length > maxSteps) {
5558
- console.log(import_chalk12.default.yellow(` \u26A0 ${plan.steps.length} steps exceeds limit of ${maxSteps}`));
5819
+ console.log(import_chalk14.default.yellow(` \u26A0 ${plan.steps.length} steps exceeds limit of ${maxSteps}`));
5559
5820
  }
5560
5821
  displayPlanSummary(plan);
5561
5822
  console.log();
5562
- console.log(import_chalk12.default.dim(` Plan: ${formatClickablePath(planPath)}`));
5823
+ console.log(import_chalk14.default.dim(` Plan: ${formatClickablePath(planPath)}`));
5563
5824
  if (refinementCount >= MAX_REFINEMENTS) {
5564
5825
  console.log();
5565
- console.log(import_chalk12.default.yellow(` \u26A0 You've refined the plan ${MAX_REFINEMENTS} times.`));
5566
- console.log(import_chalk12.default.dim(` Further refinements may incur additional costs.`));
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.`));
5567
5828
  const continueRefining = await confirm(`${INDENT6}Continue refining?`);
5568
5829
  if (!continueRefining) break;
5569
5830
  }
@@ -5580,7 +5841,7 @@ async function runPlanPhase(params) {
5580
5841
  const translatedPlan = JSON.parse(await (0, import_promises14.readFile)(planPath, "utf-8"));
5581
5842
  if (translatedPlan.steps?.length === plan.steps.length && translatedPlan.lang === targetLang) {
5582
5843
  console.log();
5583
- console.log(import_chalk12.default.dim(` Found translated plan: ${formatClickablePath(planPath)}`));
5844
+ console.log(import_chalk14.default.dim(` Found translated plan: ${formatClickablePath(planPath)}`));
5584
5845
  const useTranslated = await confirm(`${INDENT6}Use ${targetLang} translation?`);
5585
5846
  if (useTranslated) {
5586
5847
  plan = translatedPlan;
@@ -5592,7 +5853,7 @@ async function runPlanPhase(params) {
5592
5853
  }
5593
5854
  if (!useExistingTranslation) {
5594
5855
  console.log();
5595
- console.log(import_chalk12.default.yellow(` Plan is in ${planLang}, but voice is set to ${targetLang}`));
5856
+ console.log(import_chalk14.default.yellow(` Plan is in ${planLang}, but voice is set to ${targetLang}`));
5596
5857
  const translate = await confirm(`${INDENT6}Translate captions to ${targetLang}?`);
5597
5858
  if (translate) await doTranslate("Translation failed, using original");
5598
5859
  }
@@ -5602,7 +5863,7 @@ async function runPlanPhase(params) {
5602
5863
 
5603
5864
  // src/commands/phases/validation-phase.ts
5604
5865
  var import_promises15 = require("fs/promises");
5605
- var import_chalk13 = __toESM(require("chalk"));
5866
+ var import_chalk15 = __toESM(require("chalk"));
5606
5867
  async function runValidationPhase(params) {
5607
5868
  let { plan } = params;
5608
5869
  const { config, planPath, isExtensionUrl, userEmail, userTier = "free" } = params;
@@ -5653,21 +5914,21 @@ async function runValidationPhase(params) {
5653
5914
  const stepNum = result.stepIndex + 1;
5654
5915
  const step = plan.steps[result.stepIndex];
5655
5916
  console.log();
5656
- console.log(import_chalk13.default.red(` \u2717 Step ${stepNum}: ${step.action}`));
5917
+ console.log(import_chalk15.default.red(` \u2717 Step ${stepNum}: ${step.action}`));
5657
5918
  const caption = step.caption || "";
5658
- if (caption) console.log(import_chalk13.default.dim(` Caption: ${caption.slice(0, 50)}${caption.length > 50 ? "..." : ""}`));
5659
- console.log(import_chalk13.default.dim(` Target: ${step.target || "none"}`));
5660
- if (step.value) console.log(import_chalk13.default.dim(` Value: ${step.value}`));
5661
- console.log(import_chalk13.default.dim(` Error: ${result.error}`));
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}`));
5662
5923
  if (result.suggestion) {
5663
- console.log(import_chalk13.default.yellow(` Hint: ${result.suggestion}`));
5924
+ console.log(import_chalk15.default.yellow(` Hint: ${result.suggestion}`));
5664
5925
  }
5665
5926
  console.log();
5666
5927
  const isUploadMissingFile = step.action === "upload" && (!step.value || step.value.startsWith("[") || step.value.includes("example") || step.value.includes("/path/to"));
5667
5928
  if (isUploadMissingFile) {
5668
5929
  const filePath = await promptInput('Enter file path to upload (or "skip"): ');
5669
5930
  if (filePath.toLowerCase() === "skip") {
5670
- console.log(import_chalk13.default.yellow(` Skipping step ${stepNum}`));
5931
+ console.log(import_chalk15.default.yellow(` Skipping step ${stepNum}`));
5671
5932
  continue;
5672
5933
  }
5673
5934
  if (filePath) {
@@ -5676,13 +5937,13 @@ async function runValidationPhase(params) {
5676
5937
  const revalidation2 = await validateStepsOnPage(plan, config, logError, config.output.dir);
5677
5938
  const newResult2 = revalidation2.results[result.stepIndex];
5678
5939
  if (newResult2?.success) {
5679
- console.log(import_chalk13.default.green(` \u2713 Step ${stepNum} now passes`));
5940
+ console.log(import_chalk15.default.green(` \u2713 Step ${stepNum} now passes`));
5680
5941
  validationResult = revalidation2;
5681
5942
  continue;
5682
5943
  }
5683
5944
  result.error = newResult2?.error;
5684
5945
  result.suggestion = newResult2?.suggestion;
5685
- console.log(import_chalk13.default.red(` Still failing: ${result.error}`));
5946
+ console.log(import_chalk15.default.red(` Still failing: ${result.error}`));
5686
5947
  }
5687
5948
  }
5688
5949
  const fixTask = task(`Fixing step ${stepNum} with AI`);
@@ -5704,8 +5965,8 @@ async function runValidationPhase(params) {
5704
5965
  await savePlan(plan, planPath);
5705
5966
  fixTask.succeed("AI suggested a fix");
5706
5967
  const fixedStep = plan.steps[result.stepIndex];
5707
- console.log(import_chalk13.default.dim(` New target: ${fixedStep.target || "none"}`));
5708
- if (fixedStep.value) console.log(import_chalk13.default.dim(` New value: ${fixedStep.value}`));
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}`));
5709
5970
  } catch {
5710
5971
  fixTask.fail("AI could not fix automatically");
5711
5972
  }
@@ -5721,11 +5982,11 @@ async function runValidationPhase(params) {
5721
5982
  result.error = newResult?.error;
5722
5983
  result.suggestion = newResult?.suggestion;
5723
5984
  console.log();
5724
- console.log(import_chalk13.default.yellow(` Edit step ${stepNum} in: `) + import_chalk13.default.cyan(formatClickablePath(planPath)));
5985
+ console.log(import_chalk15.default.yellow(` Edit step ${stepNum} in: `) + import_chalk15.default.cyan(formatClickablePath(planPath)));
5725
5986
  console.log();
5726
5987
  const choice = await promptInput('Press Enter after editing, or type "skip" to skip this step: ');
5727
5988
  if (choice.toLowerCase() === "skip") {
5728
- console.log(import_chalk13.default.yellow(` Skipping step ${stepNum} - may fail during recording`));
5989
+ console.log(import_chalk15.default.yellow(` Skipping step ${stepNum} - may fail during recording`));
5729
5990
  continue;
5730
5991
  }
5731
5992
  plan = JSON.parse(await (0, import_promises15.readFile)(planPath, "utf-8"));
@@ -5823,23 +6084,22 @@ async function recordCommand(url, langOverride, description, skipVoice, fresh, a
5823
6084
  if (autoYes2) setAutoYes(true);
5824
6085
  console.log();
5825
6086
  showLogo();
5826
- checkCredentials();
5827
6087
  const { config, created: configCreated } = await loadConfig(url, langOverride);
5828
6088
  let chromeExtension = null;
5829
6089
  if (url.startsWith("chrome:")) {
5830
6090
  const extensionId = url.slice(7);
5831
6091
  chromeExtension = resolveChromeExtension(extensionId);
5832
6092
  if (!chromeExtension) {
5833
- console.log(import_chalk14.default.red(`
6093
+ console.log(import_chalk16.default.red(`
5834
6094
  \u2717 Chrome extension not found: ${extensionId}`));
5835
- console.log(import_chalk14.default.dim(` Make sure the extension is installed in Chrome
6095
+ console.log(import_chalk16.default.dim(` Make sure the extension is installed in Chrome
5836
6096
  `));
5837
6097
  process.exit(1);
5838
6098
  }
5839
6099
  config.browser = { extension: chromeExtension.path };
5840
6100
  url = chromeExtension.popupUrl;
5841
- console.log(import_chalk14.default.dim(` Extension: ${chromeExtension.name}`));
5842
- console.log(import_chalk14.default.dim(` Path: ${chromeExtension.path}`));
6101
+ console.log(import_chalk16.default.dim(` Extension: ${chromeExtension.name}`));
6102
+ console.log(import_chalk16.default.dim(` Path: ${chromeExtension.path}`));
5843
6103
  }
5844
6104
  config.output.dir = chromeExtension ? `./${chromeExtension.name.toLowerCase().replace(/[^a-z0-9]+/g, "-")}-demo` : getOutputDirFromUrl(url);
5845
6105
  const userData = await loadUserData();
@@ -5854,18 +6114,18 @@ async function recordCommand(url, langOverride, description, skipVoice, fresh, a
5854
6114
  if (configCreated) {
5855
6115
  const configPath = (0, import_node_path23.join)(process.cwd(), CONFIG_FILE);
5856
6116
  console.log();
5857
- console.log(import_chalk14.default.green(` \u2714 Created ${CONFIG_FILE}`));
5858
- console.log(import_chalk14.default.dim(` ${configPath}`));
6117
+ console.log(import_chalk16.default.green(` \u2714 Created ${CONFIG_FILE}`));
6118
+ console.log(import_chalk16.default.dim(` ${configPath}`));
5859
6119
  console.log();
5860
- console.log(import_chalk14.default.cyan(" Review the config file to customize:"));
5861
- console.log(import_chalk14.default.dim(" \u2022 lang: Voice language (en, es, ar, zh...)"));
5862
- console.log(import_chalk14.default.dim(" \u2022 video: Resolution, quality, effects"));
5863
- console.log(import_chalk14.default.dim(" \u2022 music: Background music path and volume"));
5864
- console.log(import_chalk14.default.dim(" \u2022 output: Output directory"));
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"));
5865
6125
  console.log();
5866
6126
  const proceed = await confirm(`${INDENT3}Proceed with default settings?`);
5867
6127
  if (!proceed) {
5868
- console.log(import_chalk14.default.yellow(`
6128
+ console.log(import_chalk16.default.yellow(`
5869
6129
  Edit ${CONFIG_FILE} and run again.
5870
6130
  `));
5871
6131
  process.exit(0);
@@ -5876,14 +6136,14 @@ async function recordCommand(url, langOverride, description, skipVoice, fresh, a
5876
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);
5877
6137
  if (!(0, import_node_fs16.existsSync)(musicPath)) {
5878
6138
  console.log();
5879
- console.log(import_chalk14.default.yellow(` \u26A0 Music file not found: ${config.music.path}`));
5880
- console.log(import_chalk14.default.dim(` Config: ${(0, import_node_path23.join)(process.cwd(), CONFIG_FILE)}`));
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)}`));
5881
6141
  console.log();
5882
6142
  const proceed = await confirm(`${INDENT3}Continue without background music?`);
5883
6143
  if (!proceed) {
5884
- console.log(import_chalk14.default.yellow(`
6144
+ console.log(import_chalk16.default.yellow(`
5885
6145
  Edit music.path in config and run again.`));
5886
- console.log(import_chalk14.default.dim(` ${(0, import_node_path23.join)(process.cwd(), CONFIG_FILE)}
6146
+ console.log(import_chalk16.default.dim(` ${(0, import_node_path23.join)(process.cwd(), CONFIG_FILE)}
5887
6147
  `));
5888
6148
  process.exit(0);
5889
6149
  }
@@ -5932,19 +6192,19 @@ async function recordCommand(url, langOverride, description, skipVoice, fresh, a
5932
6192
  await saveUserData({ localVideoCount: newCount });
5933
6193
  const remaining = FREE_VIDEO_LIMIT - newCount;
5934
6194
  if (remaining > 0) {
5935
- console.log(import_chalk14.default.dim(` ${remaining} free video${remaining === 1 ? "" : "s"} remaining`));
6195
+ console.log(import_chalk16.default.dim(` ${remaining} free video${remaining === 1 ? "" : "s"} remaining`));
5936
6196
  }
5937
6197
  }
5938
6198
  console.log();
5939
- console.log(import_chalk14.default.bold.green(" \u2713 Done!"));
6199
+ console.log(import_chalk16.default.bold.green(" \u2713 Done!"));
5940
6200
  console.log();
5941
- console.log(import_chalk14.default.cyan(` ${formatClickablePath(finalPath)}`));
6201
+ console.log(import_chalk16.default.cyan(` ${formatClickablePath(finalPath)}`));
5942
6202
  console.log();
5943
6203
  } catch (err) {
5944
6204
  await logError("Record Command", err);
5945
6205
  console.log();
5946
- console.log(import_chalk14.default.red(` Error: ${err instanceof Error ? err.message : "Unknown error"}`));
5947
- console.log(import_chalk14.default.dim(` See ${logFilePath} for details`));
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`));
5948
6208
  process.exit(1);
5949
6209
  }
5950
6210
  }
@@ -5952,7 +6212,7 @@ async function recordCommand(url, langOverride, description, skipVoice, fresh, a
5952
6212
  // src/commands/script-cmd.ts
5953
6213
  var import_promises17 = require("fs/promises");
5954
6214
  var import_node_path24 = require("path");
5955
- var import_chalk15 = __toESM(require("chalk"));
6215
+ var import_chalk17 = __toESM(require("chalk"));
5956
6216
  init_lib();
5957
6217
  async function scriptCommand(url, scriptPath, lang) {
5958
6218
  const { config, workDir, captions } = await initCommandEnv(url, lang);
@@ -5960,7 +6220,7 @@ async function scriptCommand(url, scriptPath, lang) {
5960
6220
  const raw = await (0, import_promises17.readFile)(scriptPath, "utf-8");
5961
6221
  const lines = raw.split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#"));
5962
6222
  if (lines.length === 0) {
5963
- console.log(import_chalk15.default.red(" Script file is empty or has no non-comment lines"));
6223
+ console.log(import_chalk17.default.red(" Script file is empty or has no non-comment lines"));
5964
6224
  process.exit(1);
5965
6225
  }
5966
6226
  info(`${lines.length} captions from script`);
@@ -5992,15 +6252,15 @@ async function scriptCommand(url, scriptPath, lang) {
5992
6252
  const assPath = await generateASS(plan, stepAudio, workDir, config, captions);
5993
6253
  const finalPath = await composeAndFinish(videoPath, voiceoverPath, assPath, config, "free");
5994
6254
  console.log();
5995
- console.log(import_chalk15.default.bold.green(" Done!"));
5996
- console.log(import_chalk15.default.cyan(` ${finalPath}`));
6255
+ console.log(import_chalk17.default.bold.green(" Done!"));
6256
+ console.log(import_chalk17.default.cyan(` ${finalPath}`));
5997
6257
  }
5998
6258
 
5999
6259
  // src/qp.ts
6000
6260
  init_lib();
6001
6261
  import_dotenv.default.config({ path: ".env.local", quiet: true });
6002
6262
  import_dotenv.default.config({ quiet: true });
6003
- function normalizeUrl2(input) {
6263
+ function normalizeUrl3(input) {
6004
6264
  if (input.startsWith("chrome-extension://") || input.startsWith("chrome:")) {
6005
6265
  return input;
6006
6266
  }
@@ -6032,6 +6292,12 @@ function parseArgs(args) {
6032
6292
  return { ...base, command: "help" };
6033
6293
  }
6034
6294
  const first = args[0];
6295
+ if (first === "login") {
6296
+ return { ...base, command: "login" };
6297
+ }
6298
+ if (first === "logout") {
6299
+ return { ...base, command: "logout" };
6300
+ }
6035
6301
  if (first === "init") {
6036
6302
  return { ...base, command: "init" };
6037
6303
  }
@@ -6055,7 +6321,7 @@ function parseArgs(args) {
6055
6321
  if (arg2.startsWith("lang=")) {
6056
6322
  lang2 = arg2.slice(5);
6057
6323
  } else if (!arg2.startsWith("-") && !url2 && !arg2.includes("=")) {
6058
- url2 = normalizeUrl2(arg2);
6324
+ url2 = normalizeUrl3(arg2);
6059
6325
  }
6060
6326
  }
6061
6327
  if (!url2) return { ...base, command: "help" };
@@ -6088,7 +6354,7 @@ function parseArgs(args) {
6088
6354
  if (arg2.endsWith(".js") || arg2.endsWith(".ts") || arg2.startsWith("./") || arg2.startsWith("/")) {
6089
6355
  continue;
6090
6356
  }
6091
- url = normalizeUrl2(arg2);
6357
+ url = normalizeUrl3(arg2);
6092
6358
  }
6093
6359
  }
6094
6360
  if (!url) {
@@ -6104,7 +6370,13 @@ async function main() {
6104
6370
  console.log(`quickpeek v${VERSION}`);
6105
6371
  break;
6106
6372
  case "help":
6107
- showHelp();
6373
+ await showHelp();
6374
+ break;
6375
+ case "login":
6376
+ await loginCommand();
6377
+ break;
6378
+ case "logout":
6379
+ await logoutCommand();
6108
6380
  break;
6109
6381
  case "init":
6110
6382
  await initCommand();
@@ -6114,8 +6386,8 @@ async function main() {
6114
6386
  break;
6115
6387
  case "record":
6116
6388
  if (!parsed.url) {
6117
- console.log(import_chalk16.default.red(" Missing URL"));
6118
- console.log(import_chalk16.default.dim(" Run: qp --help"));
6389
+ console.log(import_chalk18.default.red(" Missing URL"));
6390
+ console.log(import_chalk18.default.dim(" Run: qp --help"));
6119
6391
  process.exit(1);
6120
6392
  }
6121
6393
  setAutoYes(parsed.yes);
@@ -6129,8 +6401,8 @@ async function main() {
6129
6401
  break;
6130
6402
  case "capture":
6131
6403
  if (!parsed.url) {
6132
- console.log(import_chalk16.default.red(" Missing URL"));
6133
- console.log(import_chalk16.default.dim(" Run: qp --help"));
6404
+ console.log(import_chalk18.default.red(" Missing URL"));
6405
+ console.log(import_chalk18.default.dim(" Run: qp --help"));
6134
6406
  process.exit(1);
6135
6407
  }
6136
6408
  console.log();