@spark-apps/quickpeek 1.2.0 → 1.2.1

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/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { U as UserTier, C as Config } from './scraping-BK8pIJhm.mjs';
2
- export { A as AIError, a as AIResponse, b as AIResult, c as CONFIG_FILE, d as CaptionStyle, e as CaptionWordStyle, D as DEFAULT_CAPTIONS, f as DEFAULT_CONFIG, E as EXCLUDED_LINK_PATTERNS, g as ElementInfo, h as ElementType, F as FEATURES, H as HighlightMode, I as INTERACTIVE_SELECTORS, P as Plan, R as RateLimitInfo, S as SparkStatus, i as SparkSubscription, j as SparkTrial, k as Step, l as SuggestedAction, T as Tier, V as VERSION, m as buildSystemPrompt, n as buildUserPrompt, o as callAI, p as callAIViaRelay, q as crawlPage, r as getErrorMessage, s as getLanguageName, t as getStatus, u as getStatusCached, v as getTierByEmail, w as hasAccess, x as hasTrialRemaining, y as isCanceling, z as isExcludedLink, B as isPaid, G as isVerified, J as normalizeUrl, K as parseAIPlanResponse, L as parsePartial, M as pricingUrl, N as rgbaToHex, O as shouldSkipLink, Q as showPaywall, W as toTitleCase } from './scraping-BK8pIJhm.mjs';
1
+ import { U as UserTier, C as Config } from './scraping-CHN4ClyT.mjs';
2
+ export { A as AIError, a as AIResponse, b as AIResult, c as CONFIG_FILE, d as CaptionStyle, e as CaptionWordStyle, D as DEFAULT_CAPTIONS, f as DEFAULT_CONFIG, E as EXCLUDED_LINK_PATTERNS, g as ElementInfo, h as ElementType, F as FEATURES, H as HighlightMode, I as INTERACTIVE_SELECTORS, P as Plan, R as RateLimitInfo, S as SparkStatus, i as SparkSubscription, j as SparkTrial, k as Step, l as SuggestedAction, T as Tier, V as VERSION, m as buildSystemPrompt, n as buildUserPrompt, o as callAI, p as callAIViaRelay, q as crawlPage, r as getErrorMessage, s as getLanguageName, t as getStatus, u as getStatusCached, v as getTierByEmail, w as hasAccess, x as hasTrialRemaining, y as isCanceling, z as isExcludedLink, B as isPaid, G as isVerified, J as normalizeUrl, K as parseAIPlanResponse, L as parsePartial, M as pricingUrl, N as rgbaToHex, O as shouldSkipLink, Q as showPaywall, W as toTitleCase } from './scraping-CHN4ClyT.mjs';
3
3
  export { WIDE_VOICES as LANGUAGE_VOICES, hexToAss as hexToASS, windowsDrivePathToWsl as windowsPathToWSL } from '@spark-apps/video-kit';
4
4
 
5
5
  /**
package/dist/index.mjs CHANGED
@@ -39,7 +39,7 @@ function getErrorMessage(err) {
39
39
  return err instanceof Error ? err.message : String(err);
40
40
  }
41
41
  function toTitleCase(text) {
42
- return text.replace(/\w+/g, (word, index) => {
42
+ return text.replace(/\w+(?:'\w+)*/g, (word, index) => {
43
43
  if (index === 0) {
44
44
  return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
45
45
  }
@@ -781,7 +781,9 @@ var SPARK_AI_TOKEN = `${SPARK_AI_BASE}/api/models/token`;
781
781
  var RELAY_BASE = process.env.QUICKPEEK_API_BASE || "https://quickpeek.co";
782
782
  var RELAY_TOKEN_URL = `${RELAY_BASE}/api/cli/token`;
783
783
  var RELAY_CHAT_URL = `${RELAY_BASE}/api/cli/ai`;
784
- var API_KEY = process.env.SPARK_AI_KEY || "";
784
+ function apiKey() {
785
+ return process.env.SPARK_AI_KEY || "";
786
+ }
785
787
  var cachedDirectToken = null;
786
788
  var cachedRelayToken = null;
787
789
  async function getOrRefreshDirectToken() {
@@ -792,7 +794,7 @@ async function getOrRefreshDirectToken() {
792
794
  try {
793
795
  const response = await fetch(SPARK_AI_TOKEN, {
794
796
  method: "POST",
795
- headers: { "X-Api-Key": API_KEY }
797
+ headers: { "X-Api-Key": apiKey() }
796
798
  });
797
799
  if (!response.ok) {
798
800
  throw new Error(`Token exchange failed: ${response.status} ${response.statusText}`);
@@ -903,7 +905,7 @@ async function executeChat(chatUrl, bearerToken, systemPrompt, userPrompt, tier)
903
905
  return lastRateLimitError || { success: false, error: "Request failed after retries" };
904
906
  }
905
907
  async function callAI(systemPrompt, userPrompt, tier = "free") {
906
- if (!API_KEY) {
908
+ if (!apiKey()) {
907
909
  return {
908
910
  success: false,
909
911
  error: "AI service is not configured. Please report this issue.",
@@ -1046,7 +1048,7 @@ function getUpgradeUrl(userEmail) {
1046
1048
  }
1047
1049
 
1048
1050
  // src/lib/config.ts
1049
- var VERSION = "1.1.1";
1051
+ var VERSION = "1.2.1";
1050
1052
  var CONFIG_FILE = "quickpeek.config.json";
1051
1053
  var DEFAULT_CONFIG = {
1052
1054
  plan: { maxSteps: 8 },
@@ -1213,20 +1215,29 @@ var noopTask = () => ({ succeed: () => {
1213
1215
  var CRAWL_CACHE_TTL_MS = 5 * 60 * 1e3;
1214
1216
  var CRAWL_CACHE_MAX_ENTRIES = 50;
1215
1217
  var crawlCache = /* @__PURE__ */ new Map();
1216
- async function crawlPage(url, task = noopTask, storageStatePath) {
1217
- const cached = crawlCache.get(url);
1218
+ async function crawlPage(url, task = noopTask, storageStatePath, viewport) {
1219
+ const cacheKey = viewport ? `${url}@${viewport.width}x${viewport.height}` : url;
1220
+ const cached = crawlCache.get(cacheKey);
1218
1221
  if (cached && Date.now() - cached.timestamp < CRAWL_CACHE_TTL_MS) {
1219
1222
  task("Using cached crawl").succeed(`Found ${cached.elements.length} interactive elements (cached)`);
1220
- return { title: cached.title, elements: [...cached.elements] };
1223
+ return { title: cached.title, elements: [...cached.elements], scrollable: cached.scrollable };
1221
1224
  }
1222
1225
  const browserTask = task("Launching browser");
1223
1226
  const browser = await chromium.launch({ headless: true });
1224
- const context = await browser.newContext(storageStatePath ? { storageState: storageStatePath } : {});
1227
+ const context = await browser.newContext({
1228
+ ...storageStatePath ? { storageState: storageStatePath } : {},
1229
+ // Crawl at the size we will record at, or the measurements below describe
1230
+ // a page nobody will ever see.
1231
+ ...viewport ? { viewport } : {}
1232
+ });
1225
1233
  const page = await context.newPage();
1226
1234
  browserTask.succeed(storageStatePath ? "Browser launched (signed in)" : "Browser launched");
1227
1235
  const crawlTask = task("Crawling page");
1228
1236
  await page.goto(url, { waitUntil: "networkidle", timeout: 3e4 });
1229
1237
  const title = await page.title();
1238
+ const scrollable = await page.evaluate(
1239
+ "Math.max(0, document.documentElement.scrollHeight - window.innerHeight)"
1240
+ );
1230
1241
  const rawElements = await page.evaluate(`
1231
1242
  (() => {
1232
1243
  const items = [];
@@ -1356,12 +1367,12 @@ async function crawlPage(url, task = noopTask, storageStatePath) {
1356
1367
  });
1357
1368
  await browser.close();
1358
1369
  crawlTask.succeed(`Found ${elements.length} interactive elements`);
1359
- crawlCache.set(url, { timestamp: Date.now(), title, elements: [...elements] });
1370
+ crawlCache.set(cacheKey, { timestamp: Date.now(), title, elements: [...elements], scrollable });
1360
1371
  if (crawlCache.size > CRAWL_CACHE_MAX_ENTRIES) {
1361
1372
  const oldest = crawlCache.keys().next().value;
1362
1373
  if (oldest !== void 0) crawlCache.delete(oldest);
1363
1374
  }
1364
- return { title, elements };
1375
+ return { title, elements, scrollable };
1365
1376
  }
1366
1377
 
1367
1378
  // src/lib/cursor.ts
@@ -1424,7 +1435,8 @@ EXAMPLES:
1424
1435
  \u2022 File upload: "target": "input[type=\\"file\\"]"
1425
1436
  \u2022 Slider: "target": "input[type=\\"range\\"]"`;
1426
1437
  }
1427
- function buildUserPrompt(url, title, elements, langName, description, maxSteps) {
1438
+ var MIN_USEFUL_SCROLL_PX = 200;
1439
+ function buildUserPrompt(url, title, elements, langName, description, maxSteps, scrollable) {
1428
1440
  const lang = langName ? `
1429
1441
  Language: Write ALL captions in ${langName}.` : "";
1430
1442
  const purpose = description ? `
@@ -1462,8 +1474,13 @@ ${selectables.map((e) => `\u2022 "${e.text}" \u2192 ${e.selector}`).join("\n")}`
1462
1474
  ${checkables.map((e) => `\u2022 "${e.text}" \u2192 ${e.selector}`).join("\n")}`);
1463
1475
  }
1464
1476
  const elementSummary = elementDict.length > 0 ? elementDict.join("\n\n") : "(no interactive elements found)";
1477
+ const cannotScroll = scrollable !== void 0 && scrollable < MIN_USEFUL_SCROLL_PX;
1478
+ const scrolling = cannotScroll ? `
1479
+
1480
+ SCROLLING: this page has only ${scrollable}px of scroll travel at the recording size, so it does not scroll.` : "";
1481
+ const scrollRule = cannotScroll ? "\n\u2022 NEVER use the scroll action: this page does not scroll, so the video would freeze. Use wait and click steps only" : "";
1465
1482
  return `URL: ${url}
1466
- Title: ${title}${purpose}
1483
+ Title: ${title}${purpose}${scrolling}
1467
1484
 
1468
1485
  AVAILABLE ELEMENTS (grouped by action):
1469
1486
  ${elementSummary}
@@ -1473,7 +1490,7 @@ REQUIREMENTS:
1473
1490
  \u2022 Complete ONE user journey: input \u2192 process \u2192 output
1474
1491
  \u2022 For CLICK: use simplified text target like "extract frame" or "download"
1475
1492
  \u2022 For UPLOAD/DRAG/TYPE: use the CSS selector shown above
1476
- \u2022 For buttons that appear after actions: use partial text like "extract" or "download"${lang}
1493
+ \u2022 For buttons that appear after actions: use partial text like "extract" or "download"${scrollRule}${lang}
1477
1494
 
1478
1495
  Return JSON:
1479
1496
  {