@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/qp.js
CHANGED
|
@@ -147,6 +147,139 @@ var init_image_kind = __esm({
|
|
|
147
147
|
}
|
|
148
148
|
});
|
|
149
149
|
|
|
150
|
+
// src/browser/headless.ts
|
|
151
|
+
async function launchHeadlessChromium(options = {}) {
|
|
152
|
+
const opts = { ...options, headless: true };
|
|
153
|
+
try {
|
|
154
|
+
return await import_playwright.chromium.launch(opts);
|
|
155
|
+
} catch (bundledError) {
|
|
156
|
+
try {
|
|
157
|
+
return await import_playwright.chromium.launch({ ...opts, channel: "chrome" });
|
|
158
|
+
} catch {
|
|
159
|
+
const detail = bundledError instanceof Error ? bundledError.message : String(bundledError);
|
|
160
|
+
throw new Error(`${NO_BROWSER_MESSAGE}
|
|
161
|
+
|
|
162
|
+
Playwright reported: ${detail}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
var import_playwright, NO_BROWSER_MESSAGE;
|
|
167
|
+
var init_headless = __esm({
|
|
168
|
+
"src/browser/headless.ts"() {
|
|
169
|
+
"use strict";
|
|
170
|
+
import_playwright = require("playwright");
|
|
171
|
+
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";
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// src/lib/json-repair.ts
|
|
176
|
+
function repairUnicodeEscapes(content) {
|
|
177
|
+
return content.replace(/\\u\{([0-9a-fA-F]{1,6})\}/g, (match, hex) => {
|
|
178
|
+
const code = Number.parseInt(hex, 16);
|
|
179
|
+
return code <= 1114111 ? String.fromCodePoint(code) : match;
|
|
180
|
+
}).replace(/\\u(?![0-9a-fA-F]{4})/g, "\\\\u");
|
|
181
|
+
}
|
|
182
|
+
function salvageTruncatedJson(content) {
|
|
183
|
+
const stack = [];
|
|
184
|
+
const cuts = [];
|
|
185
|
+
let inString = false;
|
|
186
|
+
let escaped = false;
|
|
187
|
+
const record = (index, closesContainer) => cuts.push({ index, open: stack.join(""), closesContainer });
|
|
188
|
+
for (let i = 0; i < content.length; i++) {
|
|
189
|
+
const ch = content[i];
|
|
190
|
+
if (inString) {
|
|
191
|
+
if (escaped) escaped = false;
|
|
192
|
+
else if (ch === "\\") escaped = true;
|
|
193
|
+
else if (ch === '"') {
|
|
194
|
+
inString = false;
|
|
195
|
+
record(i + 1, false);
|
|
196
|
+
}
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (ch === '"') {
|
|
200
|
+
inString = true;
|
|
201
|
+
} else if (ch === "{" || ch === "[") {
|
|
202
|
+
stack.push(ch);
|
|
203
|
+
} else if (ch === "}" || ch === "]") {
|
|
204
|
+
stack.pop();
|
|
205
|
+
record(i + 1, true);
|
|
206
|
+
} else if (ch === ",") {
|
|
207
|
+
record(i, content[i - 1] === "}" || content[i - 1] === "]");
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const tryCut = (cut) => {
|
|
211
|
+
const head = content.slice(0, cut.index).replace(/[\s,]+$/, "");
|
|
212
|
+
if (head.endsWith(":")) return null;
|
|
213
|
+
let closed = head;
|
|
214
|
+
for (let d = cut.open.length - 1; d >= 0; d--) {
|
|
215
|
+
closed += CLOSERS[cut.open[d]];
|
|
216
|
+
}
|
|
217
|
+
try {
|
|
218
|
+
JSON.parse(closed);
|
|
219
|
+
return closed;
|
|
220
|
+
} catch {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
const attempts = Math.min(cuts.length, MAX_SALVAGE_ATTEMPTS);
|
|
225
|
+
for (const containerOnly of [true, false]) {
|
|
226
|
+
for (let n = 0; n < attempts; n++) {
|
|
227
|
+
const cut = cuts[cuts.length - 1 - n];
|
|
228
|
+
if (containerOnly && !cut.closesContainer) continue;
|
|
229
|
+
const salvaged = tryCut(cut);
|
|
230
|
+
if (salvaged !== null) return salvaged;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
function extractJsonCandidates(content) {
|
|
236
|
+
let text = content.trim();
|
|
237
|
+
const fence = text.match(/```(?:json)?\s*([\s\S]*?)(?:```|$)/);
|
|
238
|
+
if (fence?.[1]?.trim()) text = fence[1].trim();
|
|
239
|
+
const start = text.search(/[{[]/);
|
|
240
|
+
if (start === -1) return [];
|
|
241
|
+
const closer = text[start] === "{" ? "}" : "]";
|
|
242
|
+
const end = text.lastIndexOf(closer);
|
|
243
|
+
const candidates = [text.slice(start)];
|
|
244
|
+
if (end > start && end < text.length - 1) candidates.unshift(text.slice(start, end + 1));
|
|
245
|
+
return candidates;
|
|
246
|
+
}
|
|
247
|
+
function parseAIJson(content) {
|
|
248
|
+
const candidates = extractJsonCandidates(content);
|
|
249
|
+
if (candidates.length === 0) return null;
|
|
250
|
+
for (const text of candidates) {
|
|
251
|
+
try {
|
|
252
|
+
return { value: JSON.parse(text), repaired: false };
|
|
253
|
+
} catch {
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
for (const text of candidates) {
|
|
257
|
+
try {
|
|
258
|
+
return { value: JSON.parse(repairUnicodeEscapes(text)), repaired: true };
|
|
259
|
+
} catch {
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
for (const text of candidates) {
|
|
263
|
+
for (const variant of [repairUnicodeEscapes(text), text]) {
|
|
264
|
+
const salvaged = salvageTruncatedJson(variant);
|
|
265
|
+
if (salvaged === null) continue;
|
|
266
|
+
try {
|
|
267
|
+
return { value: JSON.parse(salvaged), repaired: true };
|
|
268
|
+
} catch {
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
var MAX_SALVAGE_ATTEMPTS, CLOSERS;
|
|
275
|
+
var init_json_repair = __esm({
|
|
276
|
+
"src/lib/json-repair.ts"() {
|
|
277
|
+
"use strict";
|
|
278
|
+
MAX_SALVAGE_ATTEMPTS = 500;
|
|
279
|
+
CLOSERS = { "{": "}", "[": "]" };
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
150
283
|
// src/lib/utils.ts
|
|
151
284
|
function getLanguageName(lang) {
|
|
152
285
|
if (!lang || lang === "en") return null;
|
|
@@ -468,24 +601,26 @@ async function callAIViaRelay(systemPrompt, userPrompt, email, tier = "free", op
|
|
|
468
601
|
}
|
|
469
602
|
}
|
|
470
603
|
function parseAIPlanResponse(content) {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
if (codeBlockMatch?.[1]) {
|
|
475
|
-
processedContent = codeBlockMatch[1].trim();
|
|
476
|
-
}
|
|
477
|
-
const jsonMatch = processedContent.match(/\{[\s\S]*\}/);
|
|
478
|
-
if (!jsonMatch) {
|
|
479
|
-
return { success: false, error: "No valid JSON found in AI response" };
|
|
480
|
-
}
|
|
481
|
-
const parsed = JSON.parse(jsonMatch[0]);
|
|
482
|
-
return { success: true, plan: parsed };
|
|
483
|
-
} catch (err) {
|
|
484
|
-
return {
|
|
485
|
-
success: false,
|
|
486
|
-
error: `Failed to parse AI response: ${err instanceof Error ? err.message : String(err)}`
|
|
487
|
-
};
|
|
604
|
+
const parsed = parseAIJson(content);
|
|
605
|
+
if (!parsed) {
|
|
606
|
+
return { success: false, error: "No valid JSON found in AI response" };
|
|
488
607
|
}
|
|
608
|
+
if (!Array.isArray(parsed.value.steps) || parsed.value.steps.length === 0) {
|
|
609
|
+
return { success: false, error: "AI response has no steps" };
|
|
610
|
+
}
|
|
611
|
+
return { success: true, plan: { ...parsed.value, steps: parsed.value.steps } };
|
|
612
|
+
}
|
|
613
|
+
async function callAIForPlan(systemPrompt, userPrompt, tier = "free", opts = {}) {
|
|
614
|
+
let lastParseError = "No valid JSON found in AI response";
|
|
615
|
+
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
616
|
+
const aiResult = await callAI(systemPrompt, userPrompt, tier, opts);
|
|
617
|
+
if (!aiResult.success) return aiResult;
|
|
618
|
+
const parsed = parseAIPlanResponse(aiResult.content);
|
|
619
|
+
if (parsed.success) return { success: true, plan: parsed.plan, limits: aiResult.limits };
|
|
620
|
+
lastParseError = parsed.error;
|
|
621
|
+
console.error(`Plan parse failed (attempt ${attempt}/2):`, parsed.error);
|
|
622
|
+
}
|
|
623
|
+
return { success: false, error: lastParseError, isParseError: true };
|
|
489
624
|
}
|
|
490
625
|
function normalizeUrl(input) {
|
|
491
626
|
let url = input.trim();
|
|
@@ -496,6 +631,7 @@ var PLAN_MAX_TOKENS, SERVER_BACKOFF_MS, CLI_BACKOFF_MS, SPARK_AI_BASE, SPARK_AI_
|
|
|
496
631
|
var init_api_client = __esm({
|
|
497
632
|
"src/lib/api-client.ts"() {
|
|
498
633
|
"use strict";
|
|
634
|
+
init_json_repair();
|
|
499
635
|
init_utils();
|
|
500
636
|
PLAN_MAX_TOKENS = 1500;
|
|
501
637
|
SERVER_BACKOFF_MS = [2e3, 4e3];
|
|
@@ -666,7 +802,7 @@ var VERSION, CONFIG_FILE, SIZE_PRESETS, RATE_FACTOR, NO_CLIP_OUTRO, DEFAULT_CONF
|
|
|
666
802
|
var init_config = __esm({
|
|
667
803
|
"src/lib/config.ts"() {
|
|
668
804
|
"use strict";
|
|
669
|
-
VERSION = "1.2.
|
|
805
|
+
VERSION = "1.2.6";
|
|
670
806
|
CONFIG_FILE = "quickpeek.config.json";
|
|
671
807
|
SIZE_PRESETS = {
|
|
672
808
|
wide: { width: 1920, height: 1080, zoom: 1 },
|
|
@@ -977,7 +1113,13 @@ async function crawlPage(url, task2 = noopTask, storageStatePath, viewport) {
|
|
|
977
1113
|
return { title: cached.title, elements: [...cached.elements], scrollable: cached.scrollable };
|
|
978
1114
|
}
|
|
979
1115
|
const browserTask = task2("Launching browser");
|
|
980
|
-
|
|
1116
|
+
let browser;
|
|
1117
|
+
try {
|
|
1118
|
+
browser = await launchHeadlessChromium();
|
|
1119
|
+
} catch (err) {
|
|
1120
|
+
browserTask.succeed("Could not launch a browser");
|
|
1121
|
+
throw err;
|
|
1122
|
+
}
|
|
981
1123
|
try {
|
|
982
1124
|
const context = await browser.newContext({
|
|
983
1125
|
...storageStatePath ? { storageState: storageStatePath } : {},
|
|
@@ -1153,11 +1295,11 @@ async function crawlPage(url, task2 = noopTask, storageStatePath, viewport) {
|
|
|
1153
1295
|
});
|
|
1154
1296
|
}
|
|
1155
1297
|
}
|
|
1156
|
-
var
|
|
1298
|
+
var SETTLE_POLL_MS, SETTLE_STABLE_POLLS, SETTLE_CAP_MS, PLACEHOLDER_POLL_MS, PLACEHOLDER_CAP_MS, PLACEHOLDER_STATIC_POLLS, PLACEHOLDER_CLEAR_POLLS, PLACEHOLDER_SELECTOR, noopTask, CRAWL_CACHE_TTL_MS, CRAWL_CACHE_MAX_ENTRIES, crawlCache;
|
|
1157
1299
|
var init_crawl = __esm({
|
|
1158
1300
|
"src/lib/crawl.ts"() {
|
|
1159
1301
|
"use strict";
|
|
1160
|
-
|
|
1302
|
+
init_headless();
|
|
1161
1303
|
init_scraping();
|
|
1162
1304
|
SETTLE_POLL_MS = 400;
|
|
1163
1305
|
SETTLE_STABLE_POLLS = 2;
|
|
@@ -2635,114 +2777,6 @@ var init_ffmpeg2 = __esm({
|
|
|
2635
2777
|
}
|
|
2636
2778
|
});
|
|
2637
2779
|
|
|
2638
|
-
// src/lib/json-repair.ts
|
|
2639
|
-
function repairUnicodeEscapes(content) {
|
|
2640
|
-
return content.replace(/\\u\{([0-9a-fA-F]{1,6})\}/g, (match, hex) => {
|
|
2641
|
-
const code = Number.parseInt(hex, 16);
|
|
2642
|
-
return code <= 1114111 ? String.fromCodePoint(code) : match;
|
|
2643
|
-
}).replace(/\\u(?![0-9a-fA-F]{4})/g, "\\\\u");
|
|
2644
|
-
}
|
|
2645
|
-
function salvageTruncatedJson(content) {
|
|
2646
|
-
const stack = [];
|
|
2647
|
-
const cuts = [];
|
|
2648
|
-
let inString = false;
|
|
2649
|
-
let escaped = false;
|
|
2650
|
-
const record = (index, closesContainer) => cuts.push({ index, open: stack.join(""), closesContainer });
|
|
2651
|
-
for (let i = 0; i < content.length; i++) {
|
|
2652
|
-
const ch = content[i];
|
|
2653
|
-
if (inString) {
|
|
2654
|
-
if (escaped) escaped = false;
|
|
2655
|
-
else if (ch === "\\") escaped = true;
|
|
2656
|
-
else if (ch === '"') {
|
|
2657
|
-
inString = false;
|
|
2658
|
-
record(i + 1, false);
|
|
2659
|
-
}
|
|
2660
|
-
continue;
|
|
2661
|
-
}
|
|
2662
|
-
if (ch === '"') {
|
|
2663
|
-
inString = true;
|
|
2664
|
-
} else if (ch === "{" || ch === "[") {
|
|
2665
|
-
stack.push(ch);
|
|
2666
|
-
} else if (ch === "}" || ch === "]") {
|
|
2667
|
-
stack.pop();
|
|
2668
|
-
record(i + 1, true);
|
|
2669
|
-
} else if (ch === ",") {
|
|
2670
|
-
record(i, content[i - 1] === "}" || content[i - 1] === "]");
|
|
2671
|
-
}
|
|
2672
|
-
}
|
|
2673
|
-
const tryCut = (cut) => {
|
|
2674
|
-
const head = content.slice(0, cut.index).replace(/[\s,]+$/, "");
|
|
2675
|
-
if (head.endsWith(":")) return null;
|
|
2676
|
-
let closed = head;
|
|
2677
|
-
for (let d = cut.open.length - 1; d >= 0; d--) {
|
|
2678
|
-
closed += CLOSERS[cut.open[d]];
|
|
2679
|
-
}
|
|
2680
|
-
try {
|
|
2681
|
-
JSON.parse(closed);
|
|
2682
|
-
return closed;
|
|
2683
|
-
} catch {
|
|
2684
|
-
return null;
|
|
2685
|
-
}
|
|
2686
|
-
};
|
|
2687
|
-
const attempts = Math.min(cuts.length, MAX_SALVAGE_ATTEMPTS);
|
|
2688
|
-
for (const containerOnly of [true, false]) {
|
|
2689
|
-
for (let n = 0; n < attempts; n++) {
|
|
2690
|
-
const cut = cuts[cuts.length - 1 - n];
|
|
2691
|
-
if (containerOnly && !cut.closesContainer) continue;
|
|
2692
|
-
const salvaged = tryCut(cut);
|
|
2693
|
-
if (salvaged !== null) return salvaged;
|
|
2694
|
-
}
|
|
2695
|
-
}
|
|
2696
|
-
return null;
|
|
2697
|
-
}
|
|
2698
|
-
function extractJsonCandidates(content) {
|
|
2699
|
-
let text = content.trim();
|
|
2700
|
-
const fence = text.match(/```(?:json)?\s*([\s\S]*?)(?:```|$)/);
|
|
2701
|
-
if (fence?.[1]?.trim()) text = fence[1].trim();
|
|
2702
|
-
const start = text.search(/[{[]/);
|
|
2703
|
-
if (start === -1) return [];
|
|
2704
|
-
const closer = text[start] === "{" ? "}" : "]";
|
|
2705
|
-
const end = text.lastIndexOf(closer);
|
|
2706
|
-
const candidates = [text.slice(start)];
|
|
2707
|
-
if (end > start && end < text.length - 1) candidates.unshift(text.slice(start, end + 1));
|
|
2708
|
-
return candidates;
|
|
2709
|
-
}
|
|
2710
|
-
function parseAIJson(content) {
|
|
2711
|
-
const candidates = extractJsonCandidates(content);
|
|
2712
|
-
if (candidates.length === 0) return null;
|
|
2713
|
-
for (const text of candidates) {
|
|
2714
|
-
try {
|
|
2715
|
-
return { value: JSON.parse(text), repaired: false };
|
|
2716
|
-
} catch {
|
|
2717
|
-
}
|
|
2718
|
-
}
|
|
2719
|
-
for (const text of candidates) {
|
|
2720
|
-
try {
|
|
2721
|
-
return { value: JSON.parse(repairUnicodeEscapes(text)), repaired: true };
|
|
2722
|
-
} catch {
|
|
2723
|
-
}
|
|
2724
|
-
}
|
|
2725
|
-
for (const text of candidates) {
|
|
2726
|
-
for (const variant of [repairUnicodeEscapes(text), text]) {
|
|
2727
|
-
const salvaged = salvageTruncatedJson(variant);
|
|
2728
|
-
if (salvaged === null) continue;
|
|
2729
|
-
try {
|
|
2730
|
-
return { value: JSON.parse(salvaged), repaired: true };
|
|
2731
|
-
} catch {
|
|
2732
|
-
}
|
|
2733
|
-
}
|
|
2734
|
-
}
|
|
2735
|
-
return null;
|
|
2736
|
-
}
|
|
2737
|
-
var MAX_SALVAGE_ATTEMPTS, CLOSERS;
|
|
2738
|
-
var init_json_repair = __esm({
|
|
2739
|
-
"src/lib/json-repair.ts"() {
|
|
2740
|
-
"use strict";
|
|
2741
|
-
MAX_SALVAGE_ATTEMPTS = 500;
|
|
2742
|
-
CLOSERS = { "{": "}", "[": "]" };
|
|
2743
|
-
}
|
|
2744
|
-
});
|
|
2745
|
-
|
|
2746
2780
|
// src/lib/prompts.ts
|
|
2747
2781
|
function buildSystemPrompt(maxSteps, profile) {
|
|
2748
2782
|
let scope;
|
|
@@ -4115,6 +4149,7 @@ __export(lib_exports, {
|
|
|
4115
4149
|
buildUserPrompt: () => buildUserPrompt,
|
|
4116
4150
|
buildWatermarkFilters: () => buildWatermarkFilters,
|
|
4117
4151
|
callAI: () => callAI,
|
|
4152
|
+
callAIForPlan: () => callAIForPlan,
|
|
4118
4153
|
callAIViaRelay: () => callAIViaRelay,
|
|
4119
4154
|
canReuseVoiceover: () => canReuseVoiceover,
|
|
4120
4155
|
captionCanvas: () => captionCanvas,
|
|
@@ -5037,7 +5072,7 @@ async function listAuthStates() {
|
|
|
5037
5072
|
|
|
5038
5073
|
// src/browser/launch.ts
|
|
5039
5074
|
var import_promises2 = require("fs/promises");
|
|
5040
|
-
var
|
|
5075
|
+
var import_playwright3 = require("playwright");
|
|
5041
5076
|
init_lifecycle();
|
|
5042
5077
|
|
|
5043
5078
|
// src/browser/brand-watermark.ts
|
|
@@ -5131,11 +5166,14 @@ function clampOpacity(value) {
|
|
|
5131
5166
|
return Math.min(1, Math.max(0.35, value));
|
|
5132
5167
|
}
|
|
5133
5168
|
|
|
5169
|
+
// src/browser/launch.ts
|
|
5170
|
+
init_headless();
|
|
5171
|
+
|
|
5134
5172
|
// src/browser/wsl.ts
|
|
5135
5173
|
var import_node_child_process = require("child_process");
|
|
5136
5174
|
var import_node_fs3 = require("fs");
|
|
5137
5175
|
var import_node_http = require("http");
|
|
5138
|
-
var
|
|
5176
|
+
var import_playwright2 = require("playwright");
|
|
5139
5177
|
function isWSL() {
|
|
5140
5178
|
if (process.platform !== "linux") return false;
|
|
5141
5179
|
try {
|
|
@@ -5254,7 +5292,7 @@ async function launchWindowsChrome(extensionPath, userDataDir, options = {}) {
|
|
|
5254
5292
|
const { proc: relay, port: proxyPort } = await startRelay(nodePath, windowsHost, debugPort);
|
|
5255
5293
|
try {
|
|
5256
5294
|
await waitForChrome(windowsHost, proxyPort);
|
|
5257
|
-
const browser = await
|
|
5295
|
+
const browser = await import_playwright2.chromium.connectOverCDP(`http://${windowsHost}:${proxyPort}`, { timeout: 5e3 });
|
|
5258
5296
|
return {
|
|
5259
5297
|
browser,
|
|
5260
5298
|
cleanup: async () => {
|
|
@@ -5311,7 +5349,7 @@ async function launchBrowserContext(config, userDataDir, recordVideo, storageSta
|
|
|
5311
5349
|
acceptDownloads: true
|
|
5312
5350
|
});
|
|
5313
5351
|
} catch {
|
|
5314
|
-
context = await
|
|
5352
|
+
context = await import_playwright3.chromium.launchPersistentContext(userDataDir, {
|
|
5315
5353
|
headless: false,
|
|
5316
5354
|
args: persistentArgs,
|
|
5317
5355
|
viewport,
|
|
@@ -5322,7 +5360,7 @@ async function launchBrowserContext(config, userDataDir, recordVideo, storageSta
|
|
|
5322
5360
|
});
|
|
5323
5361
|
}
|
|
5324
5362
|
} else {
|
|
5325
|
-
context = await
|
|
5363
|
+
context = await import_playwright3.chromium.launchPersistentContext(userDataDir, {
|
|
5326
5364
|
headless: false,
|
|
5327
5365
|
args: persistentArgs,
|
|
5328
5366
|
viewport,
|
|
@@ -5340,12 +5378,12 @@ async function launchBrowserContext(config, userDataDir, recordVideo, storageSta
|
|
|
5340
5378
|
ignoreDefaultArgs: ["--enable-automation"]
|
|
5341
5379
|
};
|
|
5342
5380
|
try {
|
|
5343
|
-
browser = await
|
|
5381
|
+
browser = await import_playwright3.chromium.launch({ ...headedOpts, channel: "chrome" });
|
|
5344
5382
|
} catch {
|
|
5345
|
-
browser = await
|
|
5383
|
+
browser = await import_playwright3.chromium.launch(headedOpts);
|
|
5346
5384
|
}
|
|
5347
5385
|
} else {
|
|
5348
|
-
browser = await
|
|
5386
|
+
browser = await launchHeadlessChromium();
|
|
5349
5387
|
}
|
|
5350
5388
|
context = await browser.newContext({
|
|
5351
5389
|
viewport,
|
|
@@ -5382,9 +5420,9 @@ async function launchSignInBrowser() {
|
|
|
5382
5420
|
ignoreDefaultArgs: ["--enable-automation"]
|
|
5383
5421
|
};
|
|
5384
5422
|
try {
|
|
5385
|
-
return await
|
|
5423
|
+
return await import_playwright3.chromium.launch({ ...opts, channel: "chrome" });
|
|
5386
5424
|
} catch {
|
|
5387
|
-
return await
|
|
5425
|
+
return await import_playwright3.chromium.launch(opts);
|
|
5388
5426
|
}
|
|
5389
5427
|
}
|
|
5390
5428
|
async function cleanupBrowserSession(session) {
|
|
@@ -5453,7 +5491,7 @@ async function cancelSignIn() {
|
|
|
5453
5491
|
var import_node_fs4 = require("fs");
|
|
5454
5492
|
var import_node_os2 = require("os");
|
|
5455
5493
|
var import_node_path2 = require("path");
|
|
5456
|
-
var
|
|
5494
|
+
var import_playwright4 = require("playwright");
|
|
5457
5495
|
var BROWSERS = [
|
|
5458
5496
|
{
|
|
5459
5497
|
key: "chrome",
|
|
@@ -5563,7 +5601,7 @@ async function readState(profileDir, channel, store2) {
|
|
|
5563
5601
|
];
|
|
5564
5602
|
if (store2) args.push(`--password-store=${store2}`);
|
|
5565
5603
|
try {
|
|
5566
|
-
const context = await
|
|
5604
|
+
const context = await import_playwright4.chromium.launchPersistentContext(profileDir, { headless: true, channel, args });
|
|
5567
5605
|
try {
|
|
5568
5606
|
return await context.storageState();
|
|
5569
5607
|
} finally {
|
|
@@ -6359,35 +6397,23 @@ async function setupUserEmail() {
|
|
|
6359
6397
|
console.log(import_chalk5.default.white(" needs an address to meter. No card, and no verification"));
|
|
6360
6398
|
console.log(import_chalk5.default.white(" needed for your first 5 videos."));
|
|
6361
6399
|
console.log(import_chalk5.default.dim(" After that, signing in gets you 10 videos a day."));
|
|
6362
|
-
const browserEmail = await browserLogin();
|
|
6363
|
-
if (browserEmail) {
|
|
6364
|
-
console.log();
|
|
6365
|
-
console.log(import_chalk5.default.green(` \u2713 Signed in as ${browserEmail}`));
|
|
6366
|
-
console.log();
|
|
6367
|
-
return browserEmail;
|
|
6368
|
-
}
|
|
6369
6400
|
console.log();
|
|
6370
|
-
console.log(import_chalk5.default.dim("
|
|
6401
|
+
console.log(import_chalk5.default.dim(" Type your email, or 'b' to sign in through a browser."));
|
|
6371
6402
|
const email = await prompt(import_chalk5.default.white(" Email: "));
|
|
6372
|
-
if (!email)
|
|
6403
|
+
if (!email) return null;
|
|
6404
|
+
if (email.toLowerCase() === "b" || email.toLowerCase() === "browser") {
|
|
6405
|
+
const browserEmail = await browserLogin();
|
|
6406
|
+
if (!browserEmail) return null;
|
|
6373
6407
|
console.log();
|
|
6374
|
-
console.log(import_chalk5.default.
|
|
6375
|
-
console.log(import_chalk5.default.dim(" Nothing has been recorded. Run qp again when you are ready."));
|
|
6376
|
-
console.log(import_chalk5.default.dim(" To record with no account at all, use the MCP server:"));
|
|
6377
|
-
console.log(import_chalk5.default.dim(" https://quickpeek.co/mcp"));
|
|
6408
|
+
console.log(import_chalk5.default.green(` \u2713 Signed in as ${browserEmail}`));
|
|
6378
6409
|
console.log();
|
|
6379
|
-
|
|
6410
|
+
return browserEmail;
|
|
6380
6411
|
}
|
|
6381
6412
|
if (!email.includes("@") || !email.includes(".")) {
|
|
6382
6413
|
console.log();
|
|
6383
6414
|
console.log(import_chalk5.default.yellow(" \u26A0 That doesn't look like a valid email address."));
|
|
6384
6415
|
const proceed = await prompt(import_chalk5.default.white(" Continue anyway? ") + import_chalk5.default.dim("[y/N] "));
|
|
6385
|
-
if (proceed.toLowerCase() !== "y")
|
|
6386
|
-
console.log();
|
|
6387
|
-
console.log(import_chalk5.default.dim(" Nothing has been recorded. Run qp again when you are ready."));
|
|
6388
|
-
console.log();
|
|
6389
|
-
process.exit(1);
|
|
6390
|
-
}
|
|
6416
|
+
if (proceed.toLowerCase() !== "y") return null;
|
|
6391
6417
|
}
|
|
6392
6418
|
console.log();
|
|
6393
6419
|
console.log(import_chalk5.default.green(" \u2713 Email registered"));
|
|
@@ -10548,10 +10574,11 @@ async function handleRegistrationGate() {
|
|
|
10548
10574
|
console.log(import_chalk15.default.dim(" \u2022 10 videos a day instead of 5 in total"));
|
|
10549
10575
|
console.log(import_chalk15.default.dim(" \u2022 Keep your video history"));
|
|
10550
10576
|
console.log(import_chalk15.default.dim(" \u2022 Upgrade to Pro anytime"));
|
|
10551
|
-
|
|
10552
|
-
|
|
10553
|
-
|
|
10554
|
-
|
|
10577
|
+
console.log();
|
|
10578
|
+
console.log(import_chalk15.default.dim(" Type your email, or 'b' to sign in through a browser."));
|
|
10579
|
+
let email = await promptInput("Email: ");
|
|
10580
|
+
if (email.toLowerCase() === "b" || email.toLowerCase() === "browser") {
|
|
10581
|
+
email = await browserLogin() ?? "";
|
|
10555
10582
|
}
|
|
10556
10583
|
if (!email || !email.includes("@")) {
|
|
10557
10584
|
console.log(import_chalk15.default.red(" Invalid email. Run qp again to try again."));
|
|
@@ -10626,7 +10653,7 @@ async function runAuthPhase(input) {
|
|
|
10626
10653
|
console.log();
|
|
10627
10654
|
const input2 = await promptInput(`New email or Enter to retry (${userEmail}): `);
|
|
10628
10655
|
if (input2.toLowerCase() === "exit") {
|
|
10629
|
-
gracefulExit();
|
|
10656
|
+
gracefulExit(1);
|
|
10630
10657
|
return null;
|
|
10631
10658
|
}
|
|
10632
10659
|
if (input2?.includes("@")) {
|
|
@@ -10635,7 +10662,7 @@ async function runAuthPhase(input) {
|
|
|
10635
10662
|
const result = await registerFreeUser(newEmail);
|
|
10636
10663
|
if (!result.success) {
|
|
10637
10664
|
registerTask.fail(result.error || "Registration failed");
|
|
10638
|
-
gracefulExit();
|
|
10665
|
+
gracefulExit(1);
|
|
10639
10666
|
return null;
|
|
10640
10667
|
}
|
|
10641
10668
|
if (result.alreadyVerified) {
|
|
@@ -10650,7 +10677,7 @@ async function runAuthPhase(input) {
|
|
|
10650
10677
|
console.log(import_chalk16.default.cyan(` \u{1F4E7} Verification sent to: ${newEmail}`));
|
|
10651
10678
|
console.log(import_chalk16.default.dim(" Click the link in the email, then run qp again."));
|
|
10652
10679
|
console.log();
|
|
10653
|
-
gracefulExit();
|
|
10680
|
+
gracefulExit(1);
|
|
10654
10681
|
return null;
|
|
10655
10682
|
}
|
|
10656
10683
|
} else {
|
|
@@ -10666,7 +10693,7 @@ async function runAuthPhase(input) {
|
|
|
10666
10693
|
console.log();
|
|
10667
10694
|
console.log(import_chalk16.default.cyan(` Register/upgrade at: ${getUpgradeUrl(userEmail)}`));
|
|
10668
10695
|
console.log();
|
|
10669
|
-
gracefulExit();
|
|
10696
|
+
gracefulExit(1);
|
|
10670
10697
|
return null;
|
|
10671
10698
|
}
|
|
10672
10699
|
}
|
|
@@ -10674,7 +10701,7 @@ async function runAuthPhase(input) {
|
|
|
10674
10701
|
} else if (localVideoCount >= FREE_VIDEO_LIMIT) {
|
|
10675
10702
|
const registration = await handleRegistrationGate();
|
|
10676
10703
|
if (!registration) {
|
|
10677
|
-
gracefulExit();
|
|
10704
|
+
gracefulExit(1);
|
|
10678
10705
|
return null;
|
|
10679
10706
|
}
|
|
10680
10707
|
userEmail = registration.email;
|
|
@@ -10684,7 +10711,7 @@ async function runAuthPhase(input) {
|
|
|
10684
10711
|
localVideoCount
|
|
10685
10712
|
});
|
|
10686
10713
|
if (!registration.verified) {
|
|
10687
|
-
gracefulExit();
|
|
10714
|
+
gracefulExit(1);
|
|
10688
10715
|
return null;
|
|
10689
10716
|
}
|
|
10690
10717
|
await displayUsageStatus(userEmail, userTier, localVideoCount);
|
|
@@ -10693,7 +10720,16 @@ async function runAuthPhase(input) {
|
|
|
10693
10720
|
console.log();
|
|
10694
10721
|
console.log(import_chalk16.default.cyan(` \u{1F3AC} Video ${localVideoCount + 1}/${FREE_VIDEO_LIMIT} free`));
|
|
10695
10722
|
console.log(import_chalk16.default.dim(` ${remaining2 - 1} more after this \u2022 Sign up for 10 a day`));
|
|
10696
|
-
|
|
10723
|
+
const enteredEmail = await setupUserEmail();
|
|
10724
|
+
if (!enteredEmail) {
|
|
10725
|
+
console.log();
|
|
10726
|
+
console.log(import_chalk16.default.yellow(" No email, so no video was recorded."));
|
|
10727
|
+
console.log(import_chalk16.default.white(" Next: run `qp login` to sign in, then run qp again."));
|
|
10728
|
+
console.log();
|
|
10729
|
+
gracefulExit(1);
|
|
10730
|
+
return null;
|
|
10731
|
+
}
|
|
10732
|
+
userEmail = enteredEmail;
|
|
10697
10733
|
let registeredVerified = false;
|
|
10698
10734
|
try {
|
|
10699
10735
|
const reg = await registerFreeUser(userEmail);
|
|
@@ -3,7 +3,7 @@ import { Page } from 'playwright';
|
|
|
3
3
|
/**
|
|
4
4
|
* Configuration types and defaults for QuickPeek
|
|
5
5
|
*/
|
|
6
|
-
declare const VERSION = "1.2.
|
|
6
|
+
declare const VERSION = "1.2.6";
|
|
7
7
|
declare const CONFIG_FILE = "quickpeek.config.json";
|
|
8
8
|
type UserTier = 'free' | 'pro' | 'elite';
|
|
9
9
|
/** The one dimension knob users see: wide (16:9) or short (9:16). */
|
|
@@ -280,6 +280,8 @@ interface AIError {
|
|
|
280
280
|
host?: string;
|
|
281
281
|
/** Raw response body (truncated), for the error log. */
|
|
282
282
|
detail?: string;
|
|
283
|
+
/** The call succeeded but its output was not a usable plan. */
|
|
284
|
+
isParseError?: boolean;
|
|
283
285
|
}
|
|
284
286
|
type AIResult = AIResponse | AIError;
|
|
285
287
|
/** Called before each backoff so the CLI can explain a long pause. */
|
|
@@ -338,7 +340,17 @@ declare function callAI(systemPrompt: string, userPrompt: string, tier?: UserTie
|
|
|
338
340
|
*/
|
|
339
341
|
declare function callAIViaRelay(systemPrompt: string, userPrompt: string, email: string, tier?: UserTier, opts?: ChatOptions): Promise<AIResult>;
|
|
340
342
|
/**
|
|
341
|
-
* Parse AI-generated plan JSON from response text
|
|
343
|
+
* Parse AI-generated plan JSON from response text.
|
|
344
|
+
*
|
|
345
|
+
* Delegates to parseAIJson, the same lenient path the CLI's plan step uses.
|
|
346
|
+
* This used to do its own `\{[\s\S]*\}` extraction, which fails on exactly the
|
|
347
|
+
* two things a model does under a token ceiling: stopping mid-array (the
|
|
348
|
+
* greedy match then ends at an inner brace and JSON.parse throws), and
|
|
349
|
+
* spending the whole budget on prose so no braces are emitted at all — the
|
|
350
|
+
* "No valid JSON found in AI response" the web wizard kept showing, where
|
|
351
|
+
* hitting Retry worked only because the next sample happened to be terser.
|
|
352
|
+
* The repair path recovers every complete step from a truncated plan, so a
|
|
353
|
+
* clipped response yields a usable plan instead of an error and a re-crawl.
|
|
342
354
|
*/
|
|
343
355
|
declare function parseAIPlanResponse(content: string): {
|
|
344
356
|
success: true;
|
|
@@ -357,6 +369,36 @@ declare function parseAIPlanResponse(content: string): {
|
|
|
357
369
|
success: false;
|
|
358
370
|
error: string;
|
|
359
371
|
};
|
|
372
|
+
/**
|
|
373
|
+
* Ask for a plan and parse it, resampling once if the answer is unusable.
|
|
374
|
+
*
|
|
375
|
+
* parseAIJson recovers a truncated plan, but it cannot recover the other
|
|
376
|
+
* failure mode: a model that spends its whole completion budget reasoning in
|
|
377
|
+
* prose and is cut off before writing a single brace. That is sampling luck,
|
|
378
|
+
* not a broken request — which is why the wizard's Retry button "fixed" it,
|
|
379
|
+
* and why the fix is to resample here rather than to hand the user a red box.
|
|
380
|
+
* Retrying in the route is strictly cheaper than the user retrying: their
|
|
381
|
+
* Retry re-runs the page crawl as well.
|
|
382
|
+
*
|
|
383
|
+
* Exactly one extra attempt. A model that produces prose twice is a prompt or
|
|
384
|
+
* backend problem, and a third round trip only spends the caller's serverless
|
|
385
|
+
* budget on the same answer.
|
|
386
|
+
*/
|
|
387
|
+
declare function callAIForPlan(systemPrompt: string, userPrompt: string, tier?: UserTier, opts?: ChatOptions): Promise<{
|
|
388
|
+
success: true;
|
|
389
|
+
plan: {
|
|
390
|
+
title?: string;
|
|
391
|
+
description?: string;
|
|
392
|
+
steps: Array<{
|
|
393
|
+
id?: number;
|
|
394
|
+
action?: string;
|
|
395
|
+
caption?: string;
|
|
396
|
+
target?: string;
|
|
397
|
+
value?: string;
|
|
398
|
+
}>;
|
|
399
|
+
};
|
|
400
|
+
limits: RateLimitInfo;
|
|
401
|
+
} | AIError>;
|
|
360
402
|
/**
|
|
361
403
|
* Normalize a URL by adding https:// and stripping www prefix
|
|
362
404
|
*/
|
|
@@ -644,4 +686,4 @@ declare function hasVoice(lang: string): boolean;
|
|
|
644
686
|
*/
|
|
645
687
|
declare function voiceFor(lang: string, gender?: VoiceGender): string | null;
|
|
646
688
|
|
|
647
|
-
export {
|
|
689
|
+
export { getOrRefreshRelayToken as $, type AIError as A, type VideoSize as B, type CaptionStyle as C, DEFAULT_CAPTIONS as D, EXCLUDED_LINK_PATTERNS as E, type VoiceGender as F, applyProfile as G, type HighlightMode as H, INTERACTIVE_SELECTORS as I, ariaLabelSelector as J, buildSystemPrompt as K, buildUserPrompt as L, callAI as M, NO_CLIP_OUTRO as N, callAIForPlan as O, PLAN_MAX_TOKENS as P, callAIViaRelay as Q, RATE_FACTOR as R, SERVER_BACKOFF_MS as S, type Tier as T, type UserTier as U, type VoiceRate as V, crawlPage as W, deStock as X, defaultCaptionsFor as Y, getErrorMessage as Z, getLanguageName as _, type CaptionPosition as a, getStatus as a0, getStatusCached as a1, getTierByEmail as a2, gotoSettled as a3, hasTrialRemaining as a4, hasVoice as a5, idSelector as a6, internationalVoiceFor as a7, isCanceling as a8, isExcludedLink as a9, isPaid as aa, isVerified as ab, normalizeUrl as ac, parseAIPlanResponse as ad, parsePartial as ae, pricingUrl as af, rgbaToHex as ag, runPooled as ah, shouldSkipLink as ai, showPaywall as aj, stripLongDashes as ak, toTitleCase as al, voiceFor as am, waitForPlaceholdersGone as an, getTier as ao, type Config as b, type AIResponse as c, type AIResult as d, CLI_BACKOFF_MS as e, CONFIG_FILE as f, type CaptionPreset as g, type CaptionWordStyle as h, type ChatOptions as i, DEFAULT_CONFIG as j, type ElementInfo as k, type ElementType as l, INTERNATIONAL_VOICE as m, type Plan as n, RELAY_BASE as o, type RateLimitInfo as p, type RetryNotice as q, SIZE_PRESETS as r, type SparkStatus as s, type SparkSubscription as t, type SparkTrial as u, type Step as v, type SuggestedAction as w, VERSION as x, VIDEO_PROFILES as y, type VideoProfileName as z };
|
package/dist/web.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as Plan } from './voices-
|
|
2
|
-
export { A as AIError, c as AIResponse, d as AIResult, i as ChatOptions, E as EXCLUDED_LINK_PATTERNS, k as ElementInfo, l as ElementType, H as HighlightMode, I as INTERACTIVE_SELECTORS, m as INTERNATIONAL_VOICE, P as PLAN_MAX_TOKENS, p as RateLimitInfo, s as SparkStatus, t as SparkSubscription, u as SparkTrial, v as Step, w as SuggestedAction, T as Tier, K as buildSystemPrompt, L as buildUserPrompt, M as callAI,
|
|
1
|
+
import { n as Plan } from './voices-5AFt1GDp.mjs';
|
|
2
|
+
export { A as AIError, c as AIResponse, d as AIResult, i as ChatOptions, E as EXCLUDED_LINK_PATTERNS, k as ElementInfo, l as ElementType, H as HighlightMode, I as INTERACTIVE_SELECTORS, m as INTERNATIONAL_VOICE, P as PLAN_MAX_TOKENS, p as RateLimitInfo, s as SparkStatus, t as SparkSubscription, u as SparkTrial, v as Step, w as SuggestedAction, T as Tier, K as buildSystemPrompt, L as buildUserPrompt, M as callAI, O as callAIForPlan, W as crawlPage, X as deStock, Z as getErrorMessage, _ as getLanguageName, a0 as getStatus, a1 as getStatusCached, ao as getTier, a2 as getTierByEmail, a4 as hasTrialRemaining, a5 as hasVoice, a7 as internationalVoiceFor, a8 as isCanceling, a9 as isExcludedLink, aa as isPaid, ab as isVerified, ac as normalizeUrl, ad as parseAIPlanResponse, ae as parsePartial, af as pricingUrl, ag as rgbaToHex, ah as runPooled, ai as shouldSkipLink, aj as showPaywall, ak as stripLongDashes, al as toTitleCase, am as voiceFor } from './voices-5AFt1GDp.mjs';
|
|
3
3
|
export { MALE_VOICES, MULTILINGUAL_VOICES, WIDE_VOICES, hexToAss as hexToASS, windowsDrivePathToWsl as windowsPathToWSL } from '@spark-apps/video-kit';
|
|
4
4
|
import 'playwright';
|
|
5
5
|
|