@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 +2 -2
- package/dist/index.mjs +31 -14
- package/dist/mcp-tools.mjs +634 -337
- package/dist/qp.js +413 -188
- package/dist/{scraping-BK8pIJhm.d.mts → scraping-CHN4ClyT.d.mts} +7 -3
- package/dist/web.d.mts +42 -2
- package/dist/web.mjs +110 -13
- package/mcp.mjs +24 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { U as UserTier, C as Config } from './scraping-
|
|
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-
|
|
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
|
|
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
|
-
|
|
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":
|
|
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 (!
|
|
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.
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
{
|