@spark-apps/quickpeek 1.2.5 → 1.2.6

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/web.mjs CHANGED
@@ -1,3 +1,105 @@
1
+ // src/lib/json-repair.ts
2
+ function repairUnicodeEscapes(content) {
3
+ return content.replace(/\\u\{([0-9a-fA-F]{1,6})\}/g, (match, hex) => {
4
+ const code = Number.parseInt(hex, 16);
5
+ return code <= 1114111 ? String.fromCodePoint(code) : match;
6
+ }).replace(/\\u(?![0-9a-fA-F]{4})/g, "\\\\u");
7
+ }
8
+ var MAX_SALVAGE_ATTEMPTS = 500;
9
+ var CLOSERS = { "{": "}", "[": "]" };
10
+ function salvageTruncatedJson(content) {
11
+ const stack = [];
12
+ const cuts = [];
13
+ let inString = false;
14
+ let escaped = false;
15
+ const record = (index, closesContainer) => cuts.push({ index, open: stack.join(""), closesContainer });
16
+ for (let i = 0; i < content.length; i++) {
17
+ const ch = content[i];
18
+ if (inString) {
19
+ if (escaped) escaped = false;
20
+ else if (ch === "\\") escaped = true;
21
+ else if (ch === '"') {
22
+ inString = false;
23
+ record(i + 1, false);
24
+ }
25
+ continue;
26
+ }
27
+ if (ch === '"') {
28
+ inString = true;
29
+ } else if (ch === "{" || ch === "[") {
30
+ stack.push(ch);
31
+ } else if (ch === "}" || ch === "]") {
32
+ stack.pop();
33
+ record(i + 1, true);
34
+ } else if (ch === ",") {
35
+ record(i, content[i - 1] === "}" || content[i - 1] === "]");
36
+ }
37
+ }
38
+ const tryCut = (cut) => {
39
+ const head = content.slice(0, cut.index).replace(/[\s,]+$/, "");
40
+ if (head.endsWith(":")) return null;
41
+ let closed = head;
42
+ for (let d = cut.open.length - 1; d >= 0; d--) {
43
+ closed += CLOSERS[cut.open[d]];
44
+ }
45
+ try {
46
+ JSON.parse(closed);
47
+ return closed;
48
+ } catch {
49
+ return null;
50
+ }
51
+ };
52
+ const attempts = Math.min(cuts.length, MAX_SALVAGE_ATTEMPTS);
53
+ for (const containerOnly of [true, false]) {
54
+ for (let n = 0; n < attempts; n++) {
55
+ const cut = cuts[cuts.length - 1 - n];
56
+ if (containerOnly && !cut.closesContainer) continue;
57
+ const salvaged = tryCut(cut);
58
+ if (salvaged !== null) return salvaged;
59
+ }
60
+ }
61
+ return null;
62
+ }
63
+ function extractJsonCandidates(content) {
64
+ let text = content.trim();
65
+ const fence = text.match(/```(?:json)?\s*([\s\S]*?)(?:```|$)/);
66
+ if (fence?.[1]?.trim()) text = fence[1].trim();
67
+ const start = text.search(/[{[]/);
68
+ if (start === -1) return [];
69
+ const closer = text[start] === "{" ? "}" : "]";
70
+ const end = text.lastIndexOf(closer);
71
+ const candidates = [text.slice(start)];
72
+ if (end > start && end < text.length - 1) candidates.unshift(text.slice(start, end + 1));
73
+ return candidates;
74
+ }
75
+ function parseAIJson(content) {
76
+ const candidates = extractJsonCandidates(content);
77
+ if (candidates.length === 0) return null;
78
+ for (const text of candidates) {
79
+ try {
80
+ return { value: JSON.parse(text), repaired: false };
81
+ } catch {
82
+ }
83
+ }
84
+ for (const text of candidates) {
85
+ try {
86
+ return { value: JSON.parse(repairUnicodeEscapes(text)), repaired: true };
87
+ } catch {
88
+ }
89
+ }
90
+ for (const text of candidates) {
91
+ for (const variant of [repairUnicodeEscapes(text), text]) {
92
+ const salvaged = salvageTruncatedJson(variant);
93
+ if (salvaged === null) continue;
94
+ try {
95
+ return { value: JSON.parse(salvaged), repaired: true };
96
+ } catch {
97
+ }
98
+ }
99
+ }
100
+ return null;
101
+ }
102
+
1
103
  // src/lib/utils.ts
2
104
  import { hexToAss } from "@spark-apps/video-kit";
3
105
  import { windowsDrivePathToWsl } from "@spark-apps/video-kit";
@@ -276,24 +378,26 @@ async function callAI(systemPrompt, userPrompt, tier = "free", opts = {}) {
276
378
  }
277
379
  }
278
380
  function parseAIPlanResponse(content) {
279
- try {
280
- let processedContent = content;
281
- const codeBlockMatch = processedContent.match(/```(?:json)?\s*([\s\S]*?)```/);
282
- if (codeBlockMatch?.[1]) {
283
- processedContent = codeBlockMatch[1].trim();
284
- }
285
- const jsonMatch = processedContent.match(/\{[\s\S]*\}/);
286
- if (!jsonMatch) {
287
- return { success: false, error: "No valid JSON found in AI response" };
288
- }
289
- const parsed = JSON.parse(jsonMatch[0]);
290
- return { success: true, plan: parsed };
291
- } catch (err) {
292
- return {
293
- success: false,
294
- error: `Failed to parse AI response: ${err instanceof Error ? err.message : String(err)}`
295
- };
381
+ const parsed = parseAIJson(content);
382
+ if (!parsed) {
383
+ return { success: false, error: "No valid JSON found in AI response" };
384
+ }
385
+ if (!Array.isArray(parsed.value.steps) || parsed.value.steps.length === 0) {
386
+ return { success: false, error: "AI response has no steps" };
296
387
  }
388
+ return { success: true, plan: { ...parsed.value, steps: parsed.value.steps } };
389
+ }
390
+ async function callAIForPlan(systemPrompt, userPrompt, tier = "free", opts = {}) {
391
+ let lastParseError = "No valid JSON found in AI response";
392
+ for (let attempt = 1; attempt <= 2; attempt++) {
393
+ const aiResult = await callAI(systemPrompt, userPrompt, tier, opts);
394
+ if (!aiResult.success) return aiResult;
395
+ const parsed = parseAIPlanResponse(aiResult.content);
396
+ if (parsed.success) return { success: true, plan: parsed.plan, limits: aiResult.limits };
397
+ lastParseError = parsed.error;
398
+ console.error(`Plan parse failed (attempt ${attempt}/2):`, parsed.error);
399
+ }
400
+ return { success: false, error: lastParseError, isParseError: true };
297
401
  }
298
402
  function normalizeUrl(input) {
299
403
  let url = input.trim();
@@ -301,8 +405,24 @@ function normalizeUrl(input) {
301
405
  return `https://${url}`;
302
406
  }
303
407
 
304
- // src/lib/crawl.ts
408
+ // src/browser/headless.ts
305
409
  import { chromium } from "playwright";
410
+ var NO_BROWSER_MESSAGE = "QuickPeek needs a browser to record and could not find one.\n Run this once, then run QuickPeek again:\n npx playwright install chromium";
411
+ async function launchHeadlessChromium(options = {}) {
412
+ const opts = { ...options, headless: true };
413
+ try {
414
+ return await chromium.launch(opts);
415
+ } catch (bundledError) {
416
+ try {
417
+ return await chromium.launch({ ...opts, channel: "chrome" });
418
+ } catch {
419
+ const detail = bundledError instanceof Error ? bundledError.message : String(bundledError);
420
+ throw new Error(`${NO_BROWSER_MESSAGE}
421
+
422
+ Playwright reported: ${detail}`);
423
+ }
424
+ }
425
+ }
306
426
 
307
427
  // src/lib/scraping.ts
308
428
  var EXCLUDED_LINK_PATTERNS = [
@@ -537,7 +657,13 @@ async function crawlPage(url, task = noopTask, storageStatePath, viewport) {
537
657
  return { title: cached.title, elements: [...cached.elements], scrollable: cached.scrollable };
538
658
  }
539
659
  const browserTask = task("Launching browser");
540
- const browser = await chromium.launch({ headless: true });
660
+ let browser;
661
+ try {
662
+ browser = await launchHeadlessChromium();
663
+ } catch (err) {
664
+ browserTask.succeed("Could not launch a browser");
665
+ throw err;
666
+ }
541
667
  try {
542
668
  const context = await browser.newContext({
543
669
  ...storageStatePath ? { storageState: storageStatePath } : {},
@@ -1314,6 +1440,7 @@ export {
1314
1440
  buildSystemPrompt,
1315
1441
  buildUserPrompt,
1316
1442
  callAI,
1443
+ callAIForPlan,
1317
1444
  collectPreservedValues,
1318
1445
  crawlPage,
1319
1446
  deStock,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spark-apps/quickpeek",
3
- "version": "1.2.5",
3
+ "version": "1.2.6",
4
4
  "description": "Auto-generate demo videos for your web app with AI",
5
5
  "mcpName": "co.quickpeek/quickpeek",
6
6
  "main": "dist/qp.js",
@@ -52,13 +52,10 @@
52
52
  "mcp",
53
53
  "model-context-protocol"
54
54
  ],
55
- "repository": {
56
- "type": "git",
57
- "url": "git+https://github.com/muammar-yacoob/QuickPeek.git"
58
- },
59
55
  "homepage": "https://quickpeek.co",
60
56
  "bugs": {
61
- "url": "https://discord.gg/mAjHWE3mSp"
57
+ "url": "https://quickpeek.co/docs",
58
+ "email": "support@quickpeek.co"
62
59
  },
63
60
  "license": "MIT",
64
61
  "dependencies": {