@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/README.md +7 -14
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +147 -122
- package/dist/mcp-tools.mjs +207 -171
- package/dist/qp.js +208 -172
- package/dist/{voices-ufga0oOn.d.mts → voices-5AFt1GDp.d.mts} +45 -3
- package/dist/web.d.mts +2 -2
- package/dist/web.mjs +146 -19
- package/package.json +3 -6
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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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/
|
|
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
|
-
|
|
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.
|
|
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://
|
|
57
|
+
"url": "https://quickpeek.co/docs",
|
|
58
|
+
"email": "support@quickpeek.co"
|
|
62
59
|
},
|
|
63
60
|
"license": "MIT",
|
|
64
61
|
"dependencies": {
|