@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/qp.js
CHANGED
|
@@ -59,7 +59,7 @@ function getErrorMessage(err) {
|
|
|
59
59
|
return err instanceof Error ? err.message : String(err);
|
|
60
60
|
}
|
|
61
61
|
function toTitleCase(text) {
|
|
62
|
-
return text.replace(/\w
|
|
62
|
+
return text.replace(/\w+(?:'\w+)*/g, (word, index) => {
|
|
63
63
|
if (index === 0) {
|
|
64
64
|
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
65
65
|
}
|
|
@@ -125,6 +125,9 @@ var init_utils = __esm({
|
|
|
125
125
|
});
|
|
126
126
|
|
|
127
127
|
// src/lib/api-client.ts
|
|
128
|
+
function apiKey() {
|
|
129
|
+
return process.env.SPARK_AI_KEY || "";
|
|
130
|
+
}
|
|
128
131
|
async function getOrRefreshDirectToken() {
|
|
129
132
|
const now = Date.now();
|
|
130
133
|
if (cachedDirectToken && cachedDirectToken.expiresAt > now + 3e5) {
|
|
@@ -133,7 +136,7 @@ async function getOrRefreshDirectToken() {
|
|
|
133
136
|
try {
|
|
134
137
|
const response = await fetch(SPARK_AI_TOKEN, {
|
|
135
138
|
method: "POST",
|
|
136
|
-
headers: { "X-Api-Key":
|
|
139
|
+
headers: { "X-Api-Key": apiKey() }
|
|
137
140
|
});
|
|
138
141
|
if (!response.ok) {
|
|
139
142
|
throw new Error(`Token exchange failed: ${response.status} ${response.statusText}`);
|
|
@@ -244,7 +247,7 @@ async function executeChat(chatUrl, bearerToken, systemPrompt, userPrompt, tier)
|
|
|
244
247
|
return lastRateLimitError || { success: false, error: "Request failed after retries" };
|
|
245
248
|
}
|
|
246
249
|
async function callAI(systemPrompt, userPrompt, tier = "free") {
|
|
247
|
-
if (!
|
|
250
|
+
if (!apiKey()) {
|
|
248
251
|
return {
|
|
249
252
|
success: false,
|
|
250
253
|
error: "AI service is not configured. Please report this issue.",
|
|
@@ -304,7 +307,7 @@ function normalizeUrl(input) {
|
|
|
304
307
|
url = url.replace(/^(https?:\/\/)?(www\.)?/i, "");
|
|
305
308
|
return `https://${url}`;
|
|
306
309
|
}
|
|
307
|
-
var SPARK_AI_BASE, SPARK_AI_CHAT, SPARK_AI_TOKEN, RELAY_BASE, RELAY_TOKEN_URL, RELAY_CHAT_URL,
|
|
310
|
+
var SPARK_AI_BASE, SPARK_AI_CHAT, SPARK_AI_TOKEN, RELAY_BASE, RELAY_TOKEN_URL, RELAY_CHAT_URL, cachedDirectToken, cachedRelayToken;
|
|
308
311
|
var init_api_client = __esm({
|
|
309
312
|
"src/lib/api-client.ts"() {
|
|
310
313
|
"use strict";
|
|
@@ -315,7 +318,6 @@ var init_api_client = __esm({
|
|
|
315
318
|
RELAY_BASE = process.env.QUICKPEEK_API_BASE || "https://quickpeek.co";
|
|
316
319
|
RELAY_TOKEN_URL = `${RELAY_BASE}/api/cli/token`;
|
|
317
320
|
RELAY_CHAT_URL = `${RELAY_BASE}/api/cli/ai`;
|
|
318
|
-
API_KEY = process.env.SPARK_AI_KEY || "";
|
|
319
321
|
cachedDirectToken = null;
|
|
320
322
|
cachedRelayToken = null;
|
|
321
323
|
}
|
|
@@ -419,7 +421,7 @@ var VERSION, CONFIG_FILE, DEFAULT_CONFIG, DEFAULT_CAPTIONS;
|
|
|
419
421
|
var init_config = __esm({
|
|
420
422
|
"src/lib/config.ts"() {
|
|
421
423
|
"use strict";
|
|
422
|
-
VERSION = "1.
|
|
424
|
+
VERSION = "1.2.1";
|
|
423
425
|
CONFIG_FILE = "quickpeek.config.json";
|
|
424
426
|
DEFAULT_CONFIG = {
|
|
425
427
|
plan: { maxSteps: 8 },
|
|
@@ -586,20 +588,29 @@ function getActionForType(type) {
|
|
|
586
588
|
return "type";
|
|
587
589
|
}
|
|
588
590
|
}
|
|
589
|
-
async function crawlPage(url, task2 = noopTask, storageStatePath) {
|
|
590
|
-
const
|
|
591
|
+
async function crawlPage(url, task2 = noopTask, storageStatePath, viewport) {
|
|
592
|
+
const cacheKey = viewport ? `${url}@${viewport.width}x${viewport.height}` : url;
|
|
593
|
+
const cached = crawlCache.get(cacheKey);
|
|
591
594
|
if (cached && Date.now() - cached.timestamp < CRAWL_CACHE_TTL_MS) {
|
|
592
595
|
task2("Using cached crawl").succeed(`Found ${cached.elements.length} interactive elements (cached)`);
|
|
593
|
-
return { title: cached.title, elements: [...cached.elements] };
|
|
596
|
+
return { title: cached.title, elements: [...cached.elements], scrollable: cached.scrollable };
|
|
594
597
|
}
|
|
595
598
|
const browserTask = task2("Launching browser");
|
|
596
599
|
const browser = await import_playwright.chromium.launch({ headless: true });
|
|
597
|
-
const context = await browser.newContext(
|
|
600
|
+
const context = await browser.newContext({
|
|
601
|
+
...storageStatePath ? { storageState: storageStatePath } : {},
|
|
602
|
+
// Crawl at the size we will record at, or the measurements below describe
|
|
603
|
+
// a page nobody will ever see.
|
|
604
|
+
...viewport ? { viewport } : {}
|
|
605
|
+
});
|
|
598
606
|
const page = await context.newPage();
|
|
599
607
|
browserTask.succeed(storageStatePath ? "Browser launched (signed in)" : "Browser launched");
|
|
600
608
|
const crawlTask = task2("Crawling page");
|
|
601
609
|
await page.goto(url, { waitUntil: "networkidle", timeout: 3e4 });
|
|
602
610
|
const title = await page.title();
|
|
611
|
+
const scrollable = await page.evaluate(
|
|
612
|
+
"Math.max(0, document.documentElement.scrollHeight - window.innerHeight)"
|
|
613
|
+
);
|
|
603
614
|
const rawElements = await page.evaluate(`
|
|
604
615
|
(() => {
|
|
605
616
|
const items = [];
|
|
@@ -729,12 +740,12 @@ async function crawlPage(url, task2 = noopTask, storageStatePath) {
|
|
|
729
740
|
});
|
|
730
741
|
await browser.close();
|
|
731
742
|
crawlTask.succeed(`Found ${elements.length} interactive elements`);
|
|
732
|
-
crawlCache.set(
|
|
743
|
+
crawlCache.set(cacheKey, { timestamp: Date.now(), title, elements: [...elements], scrollable });
|
|
733
744
|
if (crawlCache.size > CRAWL_CACHE_MAX_ENTRIES) {
|
|
734
745
|
const oldest = crawlCache.keys().next().value;
|
|
735
746
|
if (oldest !== void 0) crawlCache.delete(oldest);
|
|
736
747
|
}
|
|
737
|
-
return { title, elements };
|
|
748
|
+
return { title, elements, scrollable };
|
|
738
749
|
}
|
|
739
750
|
var import_playwright, noopTask, CRAWL_CACHE_TTL_MS, CRAWL_CACHE_MAX_ENTRIES, crawlCache;
|
|
740
751
|
var init_crawl = __esm({
|
|
@@ -1308,7 +1319,7 @@ EXAMPLES:
|
|
|
1308
1319
|
\u2022 File upload: "target": "input[type=\\"file\\"]"
|
|
1309
1320
|
\u2022 Slider: "target": "input[type=\\"range\\"]"`;
|
|
1310
1321
|
}
|
|
1311
|
-
function buildUserPrompt(url, title, elements, langName, description, maxSteps) {
|
|
1322
|
+
function buildUserPrompt(url, title, elements, langName, description, maxSteps, scrollable) {
|
|
1312
1323
|
const lang = langName ? `
|
|
1313
1324
|
Language: Write ALL captions in ${langName}.` : "";
|
|
1314
1325
|
const purpose = description ? `
|
|
@@ -1346,8 +1357,13 @@ ${selectables.map((e) => `\u2022 "${e.text}" \u2192 ${e.selector}`).join("\n")}`
|
|
|
1346
1357
|
${checkables.map((e) => `\u2022 "${e.text}" \u2192 ${e.selector}`).join("\n")}`);
|
|
1347
1358
|
}
|
|
1348
1359
|
const elementSummary = elementDict.length > 0 ? elementDict.join("\n\n") : "(no interactive elements found)";
|
|
1360
|
+
const cannotScroll = scrollable !== void 0 && scrollable < MIN_USEFUL_SCROLL_PX;
|
|
1361
|
+
const scrolling = cannotScroll ? `
|
|
1362
|
+
|
|
1363
|
+
SCROLLING: this page has only ${scrollable}px of scroll travel at the recording size, so it does not scroll.` : "";
|
|
1364
|
+
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" : "";
|
|
1349
1365
|
return `URL: ${url}
|
|
1350
|
-
Title: ${title}${purpose}
|
|
1366
|
+
Title: ${title}${purpose}${scrolling}
|
|
1351
1367
|
|
|
1352
1368
|
AVAILABLE ELEMENTS (grouped by action):
|
|
1353
1369
|
${elementSummary}
|
|
@@ -1357,7 +1373,7 @@ REQUIREMENTS:
|
|
|
1357
1373
|
\u2022 Complete ONE user journey: input \u2192 process \u2192 output
|
|
1358
1374
|
\u2022 For CLICK: use simplified text target like "extract frame" or "download"
|
|
1359
1375
|
\u2022 For UPLOAD/DRAG/TYPE: use the CSS selector shown above
|
|
1360
|
-
\u2022 For buttons that appear after actions: use partial text like "extract" or "download"${lang}
|
|
1376
|
+
\u2022 For buttons that appear after actions: use partial text like "extract" or "download"${scrollRule}${lang}
|
|
1361
1377
|
|
|
1362
1378
|
Return JSON:
|
|
1363
1379
|
{
|
|
@@ -1380,10 +1396,12 @@ TARGET SYNTAX:
|
|
|
1380
1396
|
\u2022 Sliders: input[type="range"]
|
|
1381
1397
|
\u2022 Text inputs: Use selector from list above`;
|
|
1382
1398
|
}
|
|
1399
|
+
var MIN_USEFUL_SCROLL_PX;
|
|
1383
1400
|
var init_prompts = __esm({
|
|
1384
1401
|
"src/lib/prompts.ts"() {
|
|
1385
1402
|
"use strict";
|
|
1386
1403
|
init_utils();
|
|
1404
|
+
MIN_USEFUL_SCROLL_PX = 200;
|
|
1387
1405
|
}
|
|
1388
1406
|
});
|
|
1389
1407
|
|
|
@@ -2011,7 +2029,7 @@ var init_lib = __esm({
|
|
|
2011
2029
|
|
|
2012
2030
|
// src/qp.ts
|
|
2013
2031
|
var import_dotenv = __toESM(require("dotenv"));
|
|
2014
|
-
var
|
|
2032
|
+
var import_chalk21 = __toESM(require("chalk"));
|
|
2015
2033
|
|
|
2016
2034
|
// src/commands/auth-cmd.ts
|
|
2017
2035
|
var import_node_readline2 = require("readline");
|
|
@@ -2324,6 +2342,7 @@ async function showHelp() {
|
|
|
2324
2342
|
"Use your own narration script"
|
|
2325
2343
|
],
|
|
2326
2344
|
[`${cmd("qp capture")} ${arg("<url>")}`, `qp capture <url>`, "Record interactions manually"],
|
|
2345
|
+
[`${cmd("qp edit")} ${arg("<url>")}`, `qp edit <url>`, "Edit the demo plan in the browser"],
|
|
2327
2346
|
[`${cmd("qp login")}`, `qp login`, "Sign in with your Google account"],
|
|
2328
2347
|
[`${cmd("qp logout")}`, `qp logout`, "Sign out"],
|
|
2329
2348
|
[`${cmd("qp auth")} ${arg("<url>")}`, `qp auth <url>`, "Save a signed-in session for a site you demo"],
|
|
@@ -2414,7 +2433,7 @@ function promptInput(prompt2) {
|
|
|
2414
2433
|
var _isWSL = process.platform === "linux" && process.env.WSL_DISTRO_NAME;
|
|
2415
2434
|
function formatClickablePath(filePath) {
|
|
2416
2435
|
const absolutePath = (0, import_node_path8.resolve)(filePath);
|
|
2417
|
-
if (_isWSL) {
|
|
2436
|
+
if (_isWSL || process.platform !== "win32") {
|
|
2418
2437
|
return absolutePath;
|
|
2419
2438
|
}
|
|
2420
2439
|
let formatted = absolutePath;
|
|
@@ -2513,16 +2532,23 @@ init_lib();
|
|
|
2513
2532
|
var import_node_http = require("http");
|
|
2514
2533
|
var import_node_url = require("url");
|
|
2515
2534
|
var import_chalk4 = __toESM(require("chalk"));
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2535
|
+
|
|
2536
|
+
// src/lib/browser-open.ts
|
|
2537
|
+
var import_node_child_process3 = require("child_process");
|
|
2538
|
+
function editorBaseUrl() {
|
|
2539
|
+
return (process.env.QUICKPEEK_API_BASE || "https://quickpeek.co").replace(/\/$/, "");
|
|
2540
|
+
}
|
|
2541
|
+
function openInBrowser(url) {
|
|
2520
2542
|
const isWSL2 = process.platform === "linux" && Boolean(process.env.WSL_DISTRO_NAME);
|
|
2521
2543
|
const command = isWSL2 ? `cmd.exe /c start "" "${url}"` : process.platform === "darwin" ? `open "${url}"` : `xdg-open "${url}"`;
|
|
2522
2544
|
return new Promise((resolve9) => {
|
|
2523
|
-
|
|
2545
|
+
(0, import_node_child_process3.exec)(command, () => resolve9());
|
|
2524
2546
|
});
|
|
2525
2547
|
}
|
|
2548
|
+
|
|
2549
|
+
// src/utils/browser-auth.ts
|
|
2550
|
+
init_SparkStripe_feature_gating();
|
|
2551
|
+
var TIMEOUT_MS = 12e4;
|
|
2526
2552
|
async function browserLogin() {
|
|
2527
2553
|
return new Promise((resolve9) => {
|
|
2528
2554
|
let server;
|
|
@@ -2576,7 +2602,7 @@ async function browserLogin() {
|
|
|
2576
2602
|
console.log(import_chalk4.default.dim(` ${url}`));
|
|
2577
2603
|
console.log();
|
|
2578
2604
|
console.log(import_chalk4.default.dim(" Waiting for sign-in (press Ctrl+C to cancel)..."));
|
|
2579
|
-
|
|
2605
|
+
void openInBrowser(url);
|
|
2580
2606
|
});
|
|
2581
2607
|
timer = setTimeout(() => {
|
|
2582
2608
|
console.log();
|
|
@@ -2858,20 +2884,20 @@ function normalizeUrl2(url) {
|
|
|
2858
2884
|
}
|
|
2859
2885
|
|
|
2860
2886
|
// src/capture.ts
|
|
2861
|
-
var
|
|
2887
|
+
var import_node_child_process5 = require("child_process");
|
|
2862
2888
|
var import_node_http3 = require("http");
|
|
2863
2889
|
var import_node_readline4 = require("readline");
|
|
2864
2890
|
var import_playwright4 = require("playwright");
|
|
2865
2891
|
|
|
2866
2892
|
// src/browser/wsl.ts
|
|
2867
|
-
var
|
|
2893
|
+
var import_node_child_process4 = require("child_process");
|
|
2868
2894
|
var import_node_fs5 = require("fs");
|
|
2869
2895
|
var import_node_http2 = require("http");
|
|
2870
2896
|
var import_playwright3 = require("playwright");
|
|
2871
2897
|
function isWSL() {
|
|
2872
2898
|
if (process.platform !== "linux") return false;
|
|
2873
2899
|
try {
|
|
2874
|
-
const r = (0,
|
|
2900
|
+
const r = (0, import_node_child_process4.execSync)("uname -r", { encoding: "utf-8" }).toLowerCase();
|
|
2875
2901
|
return r.includes("microsoft") || r.includes("wsl");
|
|
2876
2902
|
} catch {
|
|
2877
2903
|
return false;
|
|
@@ -2889,7 +2915,7 @@ function wslToWindowsPath(wslPath) {
|
|
|
2889
2915
|
function findExe(whereExeQuery, ...hardcoded) {
|
|
2890
2916
|
for (const p of hardcoded) if ((0, import_node_fs5.existsSync)(p)) return p;
|
|
2891
2917
|
try {
|
|
2892
|
-
const line = (0,
|
|
2918
|
+
const line = (0, import_node_child_process4.execSync)(`where.exe ${whereExeQuery}`, { encoding: "utf-8", timeout: 3e3 }).split("\n")[0]?.trim();
|
|
2893
2919
|
if (line) return line.replace(/^([A-Za-z]):\\/, (_, d) => `/mnt/${d.toLowerCase()}/`).replace(/\\/g, "/");
|
|
2894
2920
|
} catch {
|
|
2895
2921
|
}
|
|
@@ -2903,7 +2929,7 @@ var findWindowsChrome = () => findExe(
|
|
|
2903
2929
|
var findWindowsNode = () => findExe("node.exe");
|
|
2904
2930
|
function getWindowsHostIP() {
|
|
2905
2931
|
try {
|
|
2906
|
-
const m = (0,
|
|
2932
|
+
const m = (0, import_node_child_process4.execSync)("ip route show default", { encoding: "utf-8", timeout: 2e3 }).match(
|
|
2907
2933
|
/default via (\d+\.\d+\.\d+\.\d+)/
|
|
2908
2934
|
);
|
|
2909
2935
|
if (m?.[1]) return m[1];
|
|
@@ -2919,7 +2945,7 @@ function getWindowsHostIP() {
|
|
|
2919
2945
|
function startRelay(nodePath, windowsHost, targetPort) {
|
|
2920
2946
|
const script = `var n=require('net'),s=n.createServer(function(c){var r=n.createConnection(${targetPort},'127.0.0.1');c.pipe(r);r.pipe(c);r.on('error',function(){c.destroy()});c.on('error',function(){r.destroy()});});s.listen(0,'${windowsHost}',function(){process.stdout.write('PORT:'+s.address().port+'\\n')});`;
|
|
2921
2947
|
return new Promise((resolve9, reject) => {
|
|
2922
|
-
const proc = (0,
|
|
2948
|
+
const proc = (0, import_node_child_process4.spawn)(nodePath, ["-e", script], { stdio: ["ignore", "pipe", "ignore"], detached: true });
|
|
2923
2949
|
const timer = setTimeout(() => reject(new Error("relay startup timed out")), 6e3);
|
|
2924
2950
|
let buf = "";
|
|
2925
2951
|
proc.stdout.on("data", (d) => {
|
|
@@ -2966,7 +2992,7 @@ async function launchWindowsChrome(extensionPath, userDataDir, options = {}) {
|
|
|
2966
2992
|
if (!nodePath) throw new Error("Windows Node.js not found. Install Node.js for Windows.");
|
|
2967
2993
|
const winUserData = wslToWindowsPath(userDataDir);
|
|
2968
2994
|
const winExtPath = extensionPath ? wslToWindowsPath(extensionPath) : "";
|
|
2969
|
-
const chromeProc = (0,
|
|
2995
|
+
const chromeProc = (0, import_node_child_process4.spawn)(
|
|
2970
2996
|
chromePath,
|
|
2971
2997
|
[
|
|
2972
2998
|
`--remote-debugging-port=${debugPort}`,
|
|
@@ -3150,9 +3176,9 @@ async function launchCaptureChrome() {
|
|
|
3150
3176
|
return { browser, relay: probe.proc };
|
|
3151
3177
|
}
|
|
3152
3178
|
probe.proc.kill();
|
|
3153
|
-
const winTemp = (0,
|
|
3179
|
+
const winTemp = (0, import_node_child_process5.execSync)("cmd.exe /c echo %TEMP%", { encoding: "utf-8" }).trim().replace(/\r$/, "");
|
|
3154
3180
|
const userDataDir = `${winTemp}\\qp-capture`;
|
|
3155
|
-
(0,
|
|
3181
|
+
(0, import_node_child_process5.spawn)(
|
|
3156
3182
|
chromePath,
|
|
3157
3183
|
[
|
|
3158
3184
|
`--remote-debugging-port=${debugPort}`,
|
|
@@ -3649,7 +3675,14 @@ async function executeStep(page, step, plan, config, cursorInjected, baseDir) {
|
|
|
3649
3675
|
}
|
|
3650
3676
|
} else {
|
|
3651
3677
|
const amount = direction === "up" ? -PAGE_SCROLL_PX : PAGE_SCROLL_PX;
|
|
3652
|
-
await page.evaluate((y) =>
|
|
3678
|
+
const moved = await page.evaluate((y) => {
|
|
3679
|
+
const before = window.scrollY;
|
|
3680
|
+
window.scrollBy(0, y);
|
|
3681
|
+
return window.scrollY - before;
|
|
3682
|
+
}, amount);
|
|
3683
|
+
if (moved === 0) {
|
|
3684
|
+
throw new Error("scroll moved nothing: the page has no scroll headroom at this viewport");
|
|
3685
|
+
}
|
|
3653
3686
|
}
|
|
3654
3687
|
break;
|
|
3655
3688
|
}
|
|
@@ -4805,12 +4838,12 @@ async function parsePlanJson(content, task2, logError2, userEmail, tier = "free"
|
|
|
4805
4838
|
}
|
|
4806
4839
|
}
|
|
4807
4840
|
}
|
|
4808
|
-
async function crawlAndAnalyze(url, lang, description, maxSteps, task2, logError2, userEmail, extensionPath, workDir, tier = "free") {
|
|
4809
|
-
const { title, elements } = extensionPath && workDir ? await crawlExtension(url, extensionPath, workDir, task2) : await crawlPage(url, task2, findAuthState(url));
|
|
4841
|
+
async function crawlAndAnalyze(url, lang, description, maxSteps, task2, logError2, userEmail, extensionPath, workDir, tier = "free", viewport) {
|
|
4842
|
+
const { title, elements, scrollable } = extensionPath && workDir ? { ...await crawlExtension(url, extensionPath, workDir, task2), scrollable: void 0 } : await crawlPage(url, task2, findAuthState(url), viewport);
|
|
4810
4843
|
const aiTask = task2("Generating plan with AI");
|
|
4811
4844
|
const langName = getLanguageName(lang);
|
|
4812
4845
|
const systemPrompt = buildSystemPrompt(maxSteps);
|
|
4813
|
-
const userPrompt = buildUserPrompt(url, title, elements, langName, description, maxSteps);
|
|
4846
|
+
const userPrompt = buildUserPrompt(url, title, elements, langName, description, maxSteps, scrollable);
|
|
4814
4847
|
try {
|
|
4815
4848
|
const content = await callGroq(systemPrompt, userPrompt, logError2, userEmail, tier);
|
|
4816
4849
|
const jsonMatch = content.match(/\{[\s\S]*\}/);
|
|
@@ -4839,7 +4872,7 @@ async function validateAndFixPlan(planPath, task2, logError2, userEmail, tier =
|
|
|
4839
4872
|
}
|
|
4840
4873
|
const renumberTask = task2("Validating plan structure");
|
|
4841
4874
|
const originalCount = plan.steps.length;
|
|
4842
|
-
const
|
|
4875
|
+
const INVALID_TARGETS2 = [/\.png$/i, /\.jpg$/i, /\.gif$/i, /\.webp$/i, /\.svg$/i, /sprite_sheet/i, /downloaded/i];
|
|
4843
4876
|
const isValidStep = (s) => {
|
|
4844
4877
|
if (!s?.action) return false;
|
|
4845
4878
|
if (s.action === "navigate") {
|
|
@@ -4848,7 +4881,7 @@ async function validateAndFixPlan(planPath, task2, logError2, userEmail, tier =
|
|
|
4848
4881
|
}
|
|
4849
4882
|
if (["click", "type", "select", "check", "upload", "drag"].includes(s.action)) {
|
|
4850
4883
|
if (!s.target) return false;
|
|
4851
|
-
if (
|
|
4884
|
+
if (INVALID_TARGETS2.some((pattern) => pattern.test(s.target))) return false;
|
|
4852
4885
|
}
|
|
4853
4886
|
if (s.action === "upload") {
|
|
4854
4887
|
if (!s.target?.includes("file") && !s.target?.includes("input")) return false;
|
|
@@ -5314,16 +5347,172 @@ async function captureCommand(url, lang) {
|
|
|
5314
5347
|
console.log(import_chalk11.default.cyan(` ${finalPath}`));
|
|
5315
5348
|
}
|
|
5316
5349
|
|
|
5317
|
-
// src/commands/
|
|
5350
|
+
// src/commands/edit-cmd.ts
|
|
5318
5351
|
var import_node_fs14 = require("fs");
|
|
5319
|
-
var import_promises14 = require("fs/promises");
|
|
5320
5352
|
var import_node_path21 = require("path");
|
|
5321
5353
|
var import_chalk12 = __toESM(require("chalk"));
|
|
5354
|
+
|
|
5355
|
+
// src/lib/plan-validation.ts
|
|
5356
|
+
var INVALID_TARGETS = [
|
|
5357
|
+
/\.png$/i,
|
|
5358
|
+
/\.jpg$/i,
|
|
5359
|
+
/\.gif$/i,
|
|
5360
|
+
/\.webp$/i,
|
|
5361
|
+
/\.svg$/i,
|
|
5362
|
+
/sprite_sheet/i,
|
|
5363
|
+
/downloaded/i
|
|
5364
|
+
];
|
|
5365
|
+
var ACTIONS_REQUIRING_TARGET = ["click", "type", "select", "check", "drag"];
|
|
5366
|
+
function isFilePath(value) {
|
|
5367
|
+
if (!value) return false;
|
|
5368
|
+
return /\.\w{2,5}$/.test(value) && (/[/\\]/.test(value) || /^[a-zA-Z]:/.test(value));
|
|
5369
|
+
}
|
|
5370
|
+
function isInvalidTarget(target) {
|
|
5371
|
+
return INVALID_TARGETS.some((pattern) => pattern.test(target));
|
|
5372
|
+
}
|
|
5373
|
+
function validateStep(step) {
|
|
5374
|
+
if (!step?.action) {
|
|
5375
|
+
return "Missing action";
|
|
5376
|
+
}
|
|
5377
|
+
if (step.action === "navigate") {
|
|
5378
|
+
if (!step.target) return "Navigate requires a URL target";
|
|
5379
|
+
if (!step.target.startsWith("http://") && !step.target.startsWith("https://")) {
|
|
5380
|
+
return "Navigate target must be a valid URL";
|
|
5381
|
+
}
|
|
5382
|
+
}
|
|
5383
|
+
if (step.action === "media") {
|
|
5384
|
+
if (!step.mediaFile && !isFilePath(step.value)) {
|
|
5385
|
+
return "Media step requires a file (image or video)";
|
|
5386
|
+
}
|
|
5387
|
+
}
|
|
5388
|
+
if (step.action === "upload") {
|
|
5389
|
+
if (!step.mediaFile && !isFilePath(step.value)) {
|
|
5390
|
+
return "Upload step requires a file";
|
|
5391
|
+
}
|
|
5392
|
+
}
|
|
5393
|
+
if (ACTIONS_REQUIRING_TARGET.includes(step.action)) {
|
|
5394
|
+
if (!step.target) return `${step.action} requires a target`;
|
|
5395
|
+
if (isInvalidTarget(step.target)) return `Invalid target pattern: ${step.target}`;
|
|
5396
|
+
}
|
|
5397
|
+
return null;
|
|
5398
|
+
}
|
|
5399
|
+
|
|
5400
|
+
// src/lib/plan-file.ts
|
|
5401
|
+
var MAX_HANDOFF_URL_CHARS = 32e3;
|
|
5402
|
+
function serializePlan(plan) {
|
|
5403
|
+
return `${JSON.stringify(plan, null, 2)}
|
|
5404
|
+
`;
|
|
5405
|
+
}
|
|
5406
|
+
function parsePlanText(text) {
|
|
5407
|
+
let raw;
|
|
5408
|
+
try {
|
|
5409
|
+
raw = JSON.parse(text);
|
|
5410
|
+
} catch (e) {
|
|
5411
|
+
return { ok: false, error: `Not valid JSON: ${e instanceof Error ? e.message : String(e)}` };
|
|
5412
|
+
}
|
|
5413
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
5414
|
+
return { ok: false, error: "A plan must be a JSON object." };
|
|
5415
|
+
}
|
|
5416
|
+
const candidate = raw;
|
|
5417
|
+
if (typeof candidate.url !== "string" || !candidate.url.trim()) {
|
|
5418
|
+
return { ok: false, error: "A plan needs a `url` to record." };
|
|
5419
|
+
}
|
|
5420
|
+
if (!Array.isArray(candidate.steps) || candidate.steps.length === 0) {
|
|
5421
|
+
return { ok: false, error: "A plan needs at least one step." };
|
|
5422
|
+
}
|
|
5423
|
+
const steps = [];
|
|
5424
|
+
for (const [index, step] of candidate.steps.entries()) {
|
|
5425
|
+
if (!step || typeof step !== "object" || Array.isArray(step)) {
|
|
5426
|
+
return { ok: false, error: `Step ${index + 1} is not an object.` };
|
|
5427
|
+
}
|
|
5428
|
+
const typed = step;
|
|
5429
|
+
if (typeof typed.action !== "string" || !typed.action.trim()) {
|
|
5430
|
+
return { ok: false, error: `Step ${index + 1} has no action.` };
|
|
5431
|
+
}
|
|
5432
|
+
const failure = validateStep(typed);
|
|
5433
|
+
if (failure) return { ok: false, error: `Step ${index + 1}: ${failure}` };
|
|
5434
|
+
steps.push({ ...typed, id: typed.id ?? index + 1 });
|
|
5435
|
+
}
|
|
5436
|
+
return {
|
|
5437
|
+
ok: true,
|
|
5438
|
+
plan: {
|
|
5439
|
+
url: candidate.url,
|
|
5440
|
+
title: candidate.title ?? "",
|
|
5441
|
+
description: candidate.description ?? "",
|
|
5442
|
+
...candidate.lang ? { lang: candidate.lang } : {},
|
|
5443
|
+
steps
|
|
5444
|
+
}
|
|
5445
|
+
};
|
|
5446
|
+
}
|
|
5447
|
+
function toBase64Url(text) {
|
|
5448
|
+
const bytes = new TextEncoder().encode(text);
|
|
5449
|
+
const binary = Array.from(bytes, (b) => String.fromCharCode(b)).join("");
|
|
5450
|
+
const base64 = typeof btoa === "function" ? btoa(binary) : Buffer.from(text, "utf8").toString("base64");
|
|
5451
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
5452
|
+
}
|
|
5453
|
+
function encodePlan(plan) {
|
|
5454
|
+
return toBase64Url(serializePlan(plan));
|
|
5455
|
+
}
|
|
5456
|
+
function planEditorUrl(baseUrl, plan) {
|
|
5457
|
+
const url = `${baseUrl.replace(/\/$/, "")}/edit#plan=${encodePlan(plan)}`;
|
|
5458
|
+
return url.length <= MAX_HANDOFF_URL_CHARS ? url : null;
|
|
5459
|
+
}
|
|
5460
|
+
|
|
5461
|
+
// src/commands/edit-cmd.ts
|
|
5462
|
+
var PLAN_FILE = /^plan-([a-z]{2}(?:-[A-Z]{2})?)\.json$/;
|
|
5463
|
+
function findPlanFile(dir, lang) {
|
|
5464
|
+
if (lang) {
|
|
5465
|
+
const exact = (0, import_node_path21.join)(dir, `plan-${lang}.json`);
|
|
5466
|
+
return (0, import_node_fs14.existsSync)(exact) ? exact : null;
|
|
5467
|
+
}
|
|
5468
|
+
if (!(0, import_node_fs14.existsSync)(dir)) return null;
|
|
5469
|
+
const plans = (0, import_node_fs14.readdirSync)(dir).filter((f) => PLAN_FILE.test(f));
|
|
5470
|
+
const preferred = plans.find((f) => f === "plan-en.json") ?? plans[0];
|
|
5471
|
+
return preferred ? (0, import_node_path21.join)(dir, preferred) : null;
|
|
5472
|
+
}
|
|
5473
|
+
async function editCommand(url, lang) {
|
|
5474
|
+
console.log();
|
|
5475
|
+
showLogo();
|
|
5476
|
+
const dir = getOutputDirFromUrl(url);
|
|
5477
|
+
const planPath = findPlanFile(dir, lang);
|
|
5478
|
+
if (!planPath) {
|
|
5479
|
+
console.log(import_chalk12.default.red(` No plan found in ${dir}`));
|
|
5480
|
+
console.log(import_chalk12.default.dim(` Record the demo first: `) + import_chalk12.default.cyan(`qp ${url}`));
|
|
5481
|
+
console.log();
|
|
5482
|
+
return;
|
|
5483
|
+
}
|
|
5484
|
+
const parsed = parsePlanText((0, import_node_fs14.readFileSync)(planPath, "utf8"));
|
|
5485
|
+
if (!parsed.ok) {
|
|
5486
|
+
console.log(import_chalk12.default.red(` ${planPath} cannot be opened: ${parsed.error}`));
|
|
5487
|
+
console.log();
|
|
5488
|
+
return;
|
|
5489
|
+
}
|
|
5490
|
+
const base = editorBaseUrl();
|
|
5491
|
+
const editorUrl = planEditorUrl(base, parsed.plan);
|
|
5492
|
+
if (!editorUrl) {
|
|
5493
|
+
console.log(import_chalk12.default.yellow(" This plan is too long to carry in a link."));
|
|
5494
|
+
console.log(import_chalk12.default.dim(` Open ${base}/edit and load `) + import_chalk12.default.cyan(formatClickablePath(planPath)));
|
|
5495
|
+
console.log();
|
|
5496
|
+
await openInBrowser(`${base}/edit`);
|
|
5497
|
+
return;
|
|
5498
|
+
}
|
|
5499
|
+
await openInBrowser(editorUrl);
|
|
5500
|
+
console.log(import_chalk12.default.green(` \u2713 Opened ${parsed.plan.steps.length} steps in the editor`));
|
|
5501
|
+
console.log(import_chalk12.default.dim(` ${formatClickablePath(planPath)}`));
|
|
5502
|
+
console.log(import_chalk12.default.dim(" Save from the editor over this file, then record again."));
|
|
5503
|
+
console.log();
|
|
5504
|
+
}
|
|
5505
|
+
|
|
5506
|
+
// src/commands/init-cmd.ts
|
|
5507
|
+
var import_node_fs15 = require("fs");
|
|
5508
|
+
var import_promises14 = require("fs/promises");
|
|
5509
|
+
var import_node_path22 = require("path");
|
|
5510
|
+
var import_chalk13 = __toESM(require("chalk"));
|
|
5322
5511
|
init_lib();
|
|
5323
5512
|
async function initCommand() {
|
|
5324
|
-
const configPath = (0,
|
|
5325
|
-
if ((0,
|
|
5326
|
-
console.log(
|
|
5513
|
+
const configPath = (0, import_node_path22.join)(process.cwd(), CONFIG_FILE);
|
|
5514
|
+
if ((0, import_node_fs15.existsSync)(configPath)) {
|
|
5515
|
+
console.log(import_chalk13.default.yellow(` \u26A0 ${CONFIG_FILE} already exists`));
|
|
5327
5516
|
return;
|
|
5328
5517
|
}
|
|
5329
5518
|
const defaultConfig = {
|
|
@@ -5348,28 +5537,28 @@ async function initCommand() {
|
|
|
5348
5537
|
};
|
|
5349
5538
|
await (0, import_promises14.writeFile)(configPath, `${JSON.stringify(defaultConfig, null, 2)}
|
|
5350
5539
|
`);
|
|
5351
|
-
console.log(
|
|
5352
|
-
console.log(
|
|
5540
|
+
console.log(import_chalk13.default.green(` \u2714 Created ${CONFIG_FILE}`));
|
|
5541
|
+
console.log(import_chalk13.default.dim(` Edit it or use: qp config <key> <value>`));
|
|
5353
5542
|
}
|
|
5354
5543
|
async function configCommand(key, value) {
|
|
5355
|
-
const configPath = (0,
|
|
5544
|
+
const configPath = (0, import_node_path22.join)(process.cwd(), CONFIG_FILE);
|
|
5356
5545
|
if (!key) {
|
|
5357
|
-
if (!(0,
|
|
5358
|
-
console.log(
|
|
5546
|
+
if (!(0, import_node_fs15.existsSync)(configPath)) {
|
|
5547
|
+
console.log(import_chalk13.default.yellow(` No ${CONFIG_FILE} found. Run: qp init`));
|
|
5359
5548
|
return;
|
|
5360
5549
|
}
|
|
5361
5550
|
const content = await (0, import_promises14.readFile)(configPath, "utf-8");
|
|
5362
|
-
console.log(
|
|
5551
|
+
console.log(import_chalk13.default.cyan(` ${CONFIG_FILE}:`));
|
|
5363
5552
|
console.log(content);
|
|
5364
5553
|
return;
|
|
5365
5554
|
}
|
|
5366
5555
|
if (!value) {
|
|
5367
|
-
console.log(
|
|
5368
|
-
console.log(
|
|
5556
|
+
console.log(import_chalk13.default.red(` Missing value for ${key}`));
|
|
5557
|
+
console.log(import_chalk13.default.dim(` Usage: qp config ${key} <value>`));
|
|
5369
5558
|
return;
|
|
5370
5559
|
}
|
|
5371
5560
|
let config = {};
|
|
5372
|
-
if ((0,
|
|
5561
|
+
if ((0, import_node_fs15.existsSync)(configPath)) {
|
|
5373
5562
|
try {
|
|
5374
5563
|
config = JSON.parse(await (0, import_promises14.readFile)(configPath, "utf-8"));
|
|
5375
5564
|
} catch {
|
|
@@ -5395,40 +5584,40 @@ async function configCommand(key, value) {
|
|
|
5395
5584
|
}
|
|
5396
5585
|
await (0, import_promises14.writeFile)(configPath, `${JSON.stringify(config, null, 2)}
|
|
5397
5586
|
`);
|
|
5398
|
-
console.log(
|
|
5587
|
+
console.log(import_chalk13.default.green(` \u2714 Set ${key} = ${value}`));
|
|
5399
5588
|
}
|
|
5400
5589
|
|
|
5401
5590
|
// src/commands/login-cmd.ts
|
|
5402
|
-
var
|
|
5591
|
+
var import_chalk14 = __toESM(require("chalk"));
|
|
5403
5592
|
init_lib();
|
|
5404
5593
|
async function loginCommand() {
|
|
5405
5594
|
console.log();
|
|
5406
5595
|
showLogo();
|
|
5407
5596
|
const existing = await loadUserData();
|
|
5408
5597
|
if (existing.email && existing.verified) {
|
|
5409
|
-
console.log(
|
|
5410
|
-
console.log(
|
|
5598
|
+
console.log(import_chalk14.default.dim(` Already signed in as ${existing.email}`));
|
|
5599
|
+
console.log(import_chalk14.default.dim(" Run ") + import_chalk14.default.cyan("qp logout") + import_chalk14.default.dim(" first to switch accounts."));
|
|
5411
5600
|
console.log();
|
|
5412
5601
|
try {
|
|
5413
5602
|
const status = await getStatusCached(existing.email);
|
|
5414
5603
|
const tier2 = await getTierByEmail(existing.email);
|
|
5415
|
-
const tierLabel2 = tier2 === "pro" ?
|
|
5604
|
+
const tierLabel2 = tier2 === "pro" ? import_chalk14.default.green("Pro") : "Free";
|
|
5416
5605
|
console.log(` ${tierLabel2} plan`);
|
|
5417
5606
|
for (const line of formatUsageLines(status, tier2)) {
|
|
5418
|
-
console.log(
|
|
5607
|
+
console.log(import_chalk14.default.dim(` ${line}`));
|
|
5419
5608
|
}
|
|
5420
5609
|
} catch {
|
|
5421
5610
|
}
|
|
5422
5611
|
console.log();
|
|
5423
5612
|
return;
|
|
5424
5613
|
}
|
|
5425
|
-
console.log(
|
|
5426
|
-
console.log(
|
|
5614
|
+
console.log(import_chalk14.default.white(" Sign in with your Google account to track usage"));
|
|
5615
|
+
console.log(import_chalk14.default.white(" and unlock Pro features."));
|
|
5427
5616
|
const email = await browserLogin();
|
|
5428
5617
|
if (!email) {
|
|
5429
5618
|
console.log();
|
|
5430
|
-
console.log(
|
|
5431
|
-
console.log(
|
|
5619
|
+
console.log(import_chalk14.default.yellow(" Sign-in cancelled or timed out."));
|
|
5620
|
+
console.log(import_chalk14.default.dim(" Run ") + import_chalk14.default.cyan("qp login") + import_chalk14.default.dim(" to try again."));
|
|
5432
5621
|
console.log();
|
|
5433
5622
|
return;
|
|
5434
5623
|
}
|
|
@@ -5439,8 +5628,8 @@ async function loginCommand() {
|
|
|
5439
5628
|
}
|
|
5440
5629
|
await saveUserData({ email, verified: true, tier });
|
|
5441
5630
|
console.log();
|
|
5442
|
-
const tierLabel = tier === "pro" ?
|
|
5443
|
-
console.log(
|
|
5631
|
+
const tierLabel = tier === "pro" ? import_chalk14.default.green("Pro") : "Free";
|
|
5632
|
+
console.log(import_chalk14.default.green(` \u2713 Signed in as ${email}`));
|
|
5444
5633
|
console.log(` ${tierLabel} plan`);
|
|
5445
5634
|
console.log();
|
|
5446
5635
|
}
|
|
@@ -5448,46 +5637,46 @@ async function logoutCommand() {
|
|
|
5448
5637
|
console.log();
|
|
5449
5638
|
const userData = await loadUserData();
|
|
5450
5639
|
if (!userData.email) {
|
|
5451
|
-
console.log(
|
|
5640
|
+
console.log(import_chalk14.default.dim(" Not signed in."));
|
|
5452
5641
|
console.log();
|
|
5453
5642
|
return;
|
|
5454
5643
|
}
|
|
5455
5644
|
const email = userData.email;
|
|
5456
5645
|
await saveUserData({ email: "", verified: false, tier: "free" });
|
|
5457
|
-
console.log(
|
|
5646
|
+
console.log(import_chalk14.default.green(` \u2713 Signed out (${email})`));
|
|
5458
5647
|
console.log();
|
|
5459
5648
|
}
|
|
5460
5649
|
|
|
5461
5650
|
// src/commands/record-cmd.ts
|
|
5462
|
-
var
|
|
5651
|
+
var import_node_fs18 = require("fs");
|
|
5463
5652
|
var import_promises17 = require("fs/promises");
|
|
5464
|
-
var
|
|
5465
|
-
var
|
|
5653
|
+
var import_node_path25 = require("path");
|
|
5654
|
+
var import_chalk19 = __toESM(require("chalk"));
|
|
5466
5655
|
init_lib();
|
|
5467
5656
|
|
|
5468
5657
|
// src/commands/phases/auth-phase.ts
|
|
5469
|
-
var
|
|
5658
|
+
var import_chalk16 = __toESM(require("chalk"));
|
|
5470
5659
|
init_lib();
|
|
5471
5660
|
|
|
5472
5661
|
// src/commands/utils/auth-helpers.ts
|
|
5473
|
-
var
|
|
5662
|
+
var import_chalk15 = __toESM(require("chalk"));
|
|
5474
5663
|
init_lib();
|
|
5475
5664
|
async function handleRegistrationGate() {
|
|
5476
5665
|
console.log();
|
|
5477
|
-
console.log(
|
|
5478
|
-
console.log(
|
|
5666
|
+
console.log(import_chalk15.default.yellow(" \u{1F389} You've made 5 free videos!"));
|
|
5667
|
+
console.log(import_chalk15.default.white(" Sign in to continue creating videos."));
|
|
5479
5668
|
console.log();
|
|
5480
|
-
console.log(
|
|
5481
|
-
console.log(
|
|
5482
|
-
console.log(
|
|
5483
|
-
console.log(
|
|
5669
|
+
console.log(import_chalk15.default.dim(" Benefits of signing in:"));
|
|
5670
|
+
console.log(import_chalk15.default.dim(" \u2022 20 more videos per month"));
|
|
5671
|
+
console.log(import_chalk15.default.dim(" \u2022 Keep your video history"));
|
|
5672
|
+
console.log(import_chalk15.default.dim(" \u2022 Upgrade to Pro anytime"));
|
|
5484
5673
|
let email = await browserLogin();
|
|
5485
5674
|
if (!email) {
|
|
5486
5675
|
console.log();
|
|
5487
5676
|
email = await promptInput("Email: ");
|
|
5488
5677
|
}
|
|
5489
5678
|
if (!email || !email.includes("@")) {
|
|
5490
|
-
console.log(
|
|
5679
|
+
console.log(import_chalk15.default.red(" Invalid email. Run qp again to try again."));
|
|
5491
5680
|
return null;
|
|
5492
5681
|
}
|
|
5493
5682
|
const registerTask = task("Sending verification email");
|
|
@@ -5502,10 +5691,10 @@ async function handleRegistrationGate() {
|
|
|
5502
5691
|
}
|
|
5503
5692
|
registerTask.succeed("Check your email!");
|
|
5504
5693
|
console.log();
|
|
5505
|
-
console.log(
|
|
5506
|
-
console.log(
|
|
5694
|
+
console.log(import_chalk15.default.cyan(" \u{1F4E7} Verification email sent to:"));
|
|
5695
|
+
console.log(import_chalk15.default.white(` ${email}`));
|
|
5507
5696
|
console.log();
|
|
5508
|
-
console.log(
|
|
5697
|
+
console.log(import_chalk15.default.dim(" Click the link in the email, then press Enter to continue..."));
|
|
5509
5698
|
await promptInput("");
|
|
5510
5699
|
const verifyTask = task("Checking verification");
|
|
5511
5700
|
const verified = await isVerified(email);
|
|
@@ -5515,10 +5704,10 @@ async function handleRegistrationGate() {
|
|
|
5515
5704
|
}
|
|
5516
5705
|
verifyTask.fail("Not verified yet");
|
|
5517
5706
|
console.log();
|
|
5518
|
-
console.log(
|
|
5519
|
-
console.log(
|
|
5707
|
+
console.log(import_chalk15.default.yellow(" Email not verified. Check your inbox and spam folder."));
|
|
5708
|
+
console.log(import_chalk15.default.dim(" Run qp again after clicking the verification link."));
|
|
5520
5709
|
console.log();
|
|
5521
|
-
console.log(
|
|
5710
|
+
console.log(import_chalk15.default.cyan(` Or register/upgrade at: ${getUpgradeUrl(email)}`));
|
|
5522
5711
|
console.log();
|
|
5523
5712
|
return { email, verified: false };
|
|
5524
5713
|
}
|
|
@@ -5546,7 +5735,7 @@ async function runAuthPhase(input) {
|
|
|
5546
5735
|
} else {
|
|
5547
5736
|
verifyTask.fail("Email not verified");
|
|
5548
5737
|
console.log();
|
|
5549
|
-
console.log(
|
|
5738
|
+
console.log(import_chalk16.default.yellow(" Check your inbox for the verification email."));
|
|
5550
5739
|
console.log();
|
|
5551
5740
|
const input2 = await promptInput(`New email or Enter to retry (${userEmail}): `);
|
|
5552
5741
|
if (input2.toLowerCase() === "exit") {
|
|
@@ -5571,8 +5760,8 @@ async function runAuthPhase(input) {
|
|
|
5571
5760
|
registerTask.succeed("Check your email!");
|
|
5572
5761
|
await saveUserData({ email: newEmail, verified: false });
|
|
5573
5762
|
console.log();
|
|
5574
|
-
console.log(
|
|
5575
|
-
console.log(
|
|
5763
|
+
console.log(import_chalk16.default.cyan(` \u{1F4E7} Verification sent to: ${newEmail}`));
|
|
5764
|
+
console.log(import_chalk16.default.dim(" Click the link in the email, then run qp again."));
|
|
5576
5765
|
console.log();
|
|
5577
5766
|
gracefulExit();
|
|
5578
5767
|
return null;
|
|
@@ -5588,7 +5777,7 @@ async function runAuthPhase(input) {
|
|
|
5588
5777
|
} else {
|
|
5589
5778
|
recheckTask.fail("Still not verified");
|
|
5590
5779
|
console.log();
|
|
5591
|
-
console.log(
|
|
5780
|
+
console.log(import_chalk16.default.cyan(` Register/upgrade at: ${getUpgradeUrl(userEmail)}`));
|
|
5592
5781
|
console.log();
|
|
5593
5782
|
gracefulExit();
|
|
5594
5783
|
return null;
|
|
@@ -5615,8 +5804,8 @@ async function runAuthPhase(input) {
|
|
|
5615
5804
|
} else {
|
|
5616
5805
|
const remaining = FREE_VIDEO_LIMIT - localVideoCount;
|
|
5617
5806
|
console.log();
|
|
5618
|
-
console.log(
|
|
5619
|
-
console.log(
|
|
5807
|
+
console.log(import_chalk16.default.cyan(` \u{1F3AC} Video ${localVideoCount + 1}/${FREE_VIDEO_LIMIT} free`));
|
|
5808
|
+
console.log(import_chalk16.default.dim(` ${remaining - 1} more after this \u2022 Sign up for unlimited`));
|
|
5620
5809
|
userEmail = await setupUserEmail();
|
|
5621
5810
|
let registeredVerified = false;
|
|
5622
5811
|
try {
|
|
@@ -5635,10 +5824,10 @@ async function runAuthPhase(input) {
|
|
|
5635
5824
|
}
|
|
5636
5825
|
|
|
5637
5826
|
// src/commands/phases/plan-phase.ts
|
|
5638
|
-
var
|
|
5827
|
+
var import_node_fs16 = require("fs");
|
|
5639
5828
|
var import_promises15 = require("fs/promises");
|
|
5640
|
-
var
|
|
5641
|
-
var
|
|
5829
|
+
var import_node_path23 = require("path");
|
|
5830
|
+
var import_chalk17 = __toESM(require("chalk"));
|
|
5642
5831
|
|
|
5643
5832
|
// src/ai/fix.ts
|
|
5644
5833
|
init_lib();
|
|
@@ -5790,12 +5979,22 @@ Return ONLY JSON: {"description": "...", "captions": ["...", ...]}`;
|
|
|
5790
5979
|
// src/commands/phases/plan-phase.ts
|
|
5791
5980
|
var INDENT6 = " ";
|
|
5792
5981
|
async function runPlanPhase(params) {
|
|
5793
|
-
const {
|
|
5982
|
+
const {
|
|
5983
|
+
url,
|
|
5984
|
+
config,
|
|
5985
|
+
userEmail,
|
|
5986
|
+
description,
|
|
5987
|
+
planPath,
|
|
5988
|
+
legacyPlanPath,
|
|
5989
|
+
targetLang,
|
|
5990
|
+
userTier = "free",
|
|
5991
|
+
fresh = false
|
|
5992
|
+
} = params;
|
|
5794
5993
|
const maxSteps = config.plan?.maxSteps ?? 8;
|
|
5795
5994
|
const extensionPath = config.browser?.extension;
|
|
5796
5995
|
let existingPlan = null;
|
|
5797
5996
|
let existingPlanPath = null;
|
|
5798
|
-
if ((0,
|
|
5997
|
+
if ((0, import_node_fs16.existsSync)(planPath)) {
|
|
5799
5998
|
try {
|
|
5800
5999
|
const parsed = JSON.parse(await (0, import_promises15.readFile)(planPath, "utf-8"));
|
|
5801
6000
|
if (parsed.url && parsed.steps?.length > 0) {
|
|
@@ -5807,10 +6006,10 @@ async function runPlanPhase(params) {
|
|
|
5807
6006
|
}
|
|
5808
6007
|
if (!existingPlan) {
|
|
5809
6008
|
try {
|
|
5810
|
-
const files = (0,
|
|
5811
|
-
const planFiles = files.filter((f) => /^plan-[a-z]{2}(-[A-Z]{2})?\.json$/.test(f) && f !== (0,
|
|
6009
|
+
const files = (0, import_node_fs16.readdirSync)(config.output.dir);
|
|
6010
|
+
const planFiles = files.filter((f) => /^plan-[a-z]{2}(-[A-Z]{2})?\.json$/.test(f) && f !== (0, import_node_path23.basename)(planPath));
|
|
5812
6011
|
for (const file of planFiles) {
|
|
5813
|
-
const filePath = (0,
|
|
6012
|
+
const filePath = (0, import_node_path23.join)(config.output.dir, file);
|
|
5814
6013
|
try {
|
|
5815
6014
|
const parsed = JSON.parse(await (0, import_promises15.readFile)(filePath, "utf-8"));
|
|
5816
6015
|
if (parsed.url && parsed.steps?.length > 0) {
|
|
@@ -5824,7 +6023,7 @@ async function runPlanPhase(params) {
|
|
|
5824
6023
|
} catch {
|
|
5825
6024
|
}
|
|
5826
6025
|
}
|
|
5827
|
-
if (!existingPlan && (0,
|
|
6026
|
+
if (!existingPlan && (0, import_node_fs16.existsSync)(legacyPlanPath)) {
|
|
5828
6027
|
try {
|
|
5829
6028
|
const parsed = JSON.parse(await (0, import_promises15.readFile)(legacyPlanPath, "utf-8"));
|
|
5830
6029
|
if (parsed.url && parsed.steps?.length > 0) {
|
|
@@ -5850,25 +6049,25 @@ async function runPlanPhase(params) {
|
|
|
5850
6049
|
translateTask.fail(failMsg);
|
|
5851
6050
|
}
|
|
5852
6051
|
};
|
|
5853
|
-
if (existingPlan && existingPlanPath) {
|
|
6052
|
+
if (existingPlan && existingPlanPath && !fresh) {
|
|
5854
6053
|
console.log();
|
|
5855
|
-
console.log(
|
|
5856
|
-
console.log(
|
|
6054
|
+
console.log(import_chalk17.default.dim(` Found existing plan: `) + import_chalk17.default.cyan(formatClickablePath(existingPlanPath)));
|
|
6055
|
+
console.log(import_chalk17.default.dim(` ${existingPlan.steps.length} steps for ${existingPlan.url}`));
|
|
5857
6056
|
if (!urlMatches) {
|
|
5858
|
-
console.log(
|
|
5859
|
-
console.log(
|
|
5860
|
-
console.log(
|
|
6057
|
+
console.log(import_chalk17.default.yellow(` \u26A0 Plan URL differs from argument:`));
|
|
6058
|
+
console.log(import_chalk17.default.yellow(` Plan: ${existingPlan.url}`));
|
|
6059
|
+
console.log(import_chalk17.default.yellow(` Arg: ${url}`));
|
|
5861
6060
|
}
|
|
5862
6061
|
const existingLang = existingPlan.lang || "en";
|
|
5863
6062
|
if (existingLang !== targetLang) {
|
|
5864
|
-
console.log(
|
|
6063
|
+
console.log(import_chalk17.default.yellow(` \u26A0 Plan is in ${existingLang}, config is ${targetLang}`));
|
|
5865
6064
|
}
|
|
5866
6065
|
const reuse = await confirm(`${INDENT6}Use existing plan?`);
|
|
5867
6066
|
if (reuse) {
|
|
5868
6067
|
plan = await validateAndFixPlan(existingPlanPath, task, logError, userEmail, userTier);
|
|
5869
6068
|
if (existingPlanPath !== planPath) {
|
|
5870
6069
|
await savePlan(plan, planPath);
|
|
5871
|
-
console.log(
|
|
6070
|
+
console.log(import_chalk17.default.dim(` Migrated to: ${formatClickablePath(planPath)}`));
|
|
5872
6071
|
}
|
|
5873
6072
|
if (existingLang !== targetLang) {
|
|
5874
6073
|
const translate = await confirm(`${INDENT6}Translate to ${targetLang}?`);
|
|
@@ -5886,7 +6085,8 @@ async function runPlanPhase(params) {
|
|
|
5886
6085
|
userEmail,
|
|
5887
6086
|
extensionPath,
|
|
5888
6087
|
config.output.dir,
|
|
5889
|
-
userTier
|
|
6088
|
+
userTier,
|
|
6089
|
+
{ width: config.video.width, height: config.video.height }
|
|
5890
6090
|
);
|
|
5891
6091
|
displayRateLimitInfo();
|
|
5892
6092
|
if (urlMatches) {
|
|
@@ -5903,7 +6103,7 @@ async function runPlanPhase(params) {
|
|
|
5903
6103
|
uploadIdx++;
|
|
5904
6104
|
}
|
|
5905
6105
|
}
|
|
5906
|
-
console.log(
|
|
6106
|
+
console.log(import_chalk17.default.dim(` Preserved upload paths from previous plan`));
|
|
5907
6107
|
}
|
|
5908
6108
|
await savePlan(plan, planPath);
|
|
5909
6109
|
plan = await validateAndFixPlan(planPath, task, logError, userEmail, userTier);
|
|
@@ -5919,7 +6119,9 @@ async function runPlanPhase(params) {
|
|
|
5919
6119
|
logError,
|
|
5920
6120
|
userEmail,
|
|
5921
6121
|
extensionPath,
|
|
5922
|
-
config.output.dir
|
|
6122
|
+
config.output.dir,
|
|
6123
|
+
void 0,
|
|
6124
|
+
{ width: config.video.width, height: config.video.height }
|
|
5923
6125
|
);
|
|
5924
6126
|
displayRateLimitInfo();
|
|
5925
6127
|
await savePlan(plan, planPath);
|
|
@@ -5927,8 +6129,8 @@ async function runPlanPhase(params) {
|
|
|
5927
6129
|
}
|
|
5928
6130
|
displayPlanSummary(plan);
|
|
5929
6131
|
console.log();
|
|
5930
|
-
console.log(
|
|
5931
|
-
console.log(
|
|
6132
|
+
console.log(import_chalk17.default.dim(` Plan: ${formatClickablePath(planPath)}`));
|
|
6133
|
+
console.log(import_chalk17.default.dim(` Enter a prompt to refine, or press Enter to continue.`));
|
|
5932
6134
|
const MAX_REFINEMENTS = 10;
|
|
5933
6135
|
let refinementCount = 0;
|
|
5934
6136
|
while (true) {
|
|
@@ -5946,15 +6148,15 @@ async function runPlanPhase(params) {
|
|
|
5946
6148
|
refineTask.succeed("Plan refined");
|
|
5947
6149
|
displayRateLimitInfo();
|
|
5948
6150
|
if (plan.steps.length > maxSteps) {
|
|
5949
|
-
console.log(
|
|
6151
|
+
console.log(import_chalk17.default.yellow(` \u26A0 ${plan.steps.length} steps exceeds limit of ${maxSteps}`));
|
|
5950
6152
|
}
|
|
5951
6153
|
displayPlanSummary(plan);
|
|
5952
6154
|
console.log();
|
|
5953
|
-
console.log(
|
|
6155
|
+
console.log(import_chalk17.default.dim(` Plan: ${formatClickablePath(planPath)}`));
|
|
5954
6156
|
if (refinementCount >= MAX_REFINEMENTS) {
|
|
5955
6157
|
console.log();
|
|
5956
|
-
console.log(
|
|
5957
|
-
console.log(
|
|
6158
|
+
console.log(import_chalk17.default.yellow(` \u26A0 You've refined the plan ${MAX_REFINEMENTS} times.`));
|
|
6159
|
+
console.log(import_chalk17.default.dim(` Further refinements may incur additional costs.`));
|
|
5958
6160
|
const continueRefining = await confirm(`${INDENT6}Continue refining?`);
|
|
5959
6161
|
if (!continueRefining) break;
|
|
5960
6162
|
}
|
|
@@ -5966,12 +6168,12 @@ async function runPlanPhase(params) {
|
|
|
5966
6168
|
const planLang = plan.lang || "en";
|
|
5967
6169
|
if (planLang !== targetLang) {
|
|
5968
6170
|
let useExistingTranslation = false;
|
|
5969
|
-
if ((0,
|
|
6171
|
+
if ((0, import_node_fs16.existsSync)(planPath) && existingPlanPath !== planPath) {
|
|
5970
6172
|
try {
|
|
5971
6173
|
const translatedPlan = JSON.parse(await (0, import_promises15.readFile)(planPath, "utf-8"));
|
|
5972
6174
|
if (translatedPlan.steps?.length === plan.steps.length && translatedPlan.lang === targetLang) {
|
|
5973
6175
|
console.log();
|
|
5974
|
-
console.log(
|
|
6176
|
+
console.log(import_chalk17.default.dim(` Found translated plan: ${formatClickablePath(planPath)}`));
|
|
5975
6177
|
const useTranslated = await confirm(`${INDENT6}Use ${targetLang} translation?`);
|
|
5976
6178
|
if (useTranslated) {
|
|
5977
6179
|
plan = translatedPlan;
|
|
@@ -5983,7 +6185,7 @@ async function runPlanPhase(params) {
|
|
|
5983
6185
|
}
|
|
5984
6186
|
if (!useExistingTranslation) {
|
|
5985
6187
|
console.log();
|
|
5986
|
-
console.log(
|
|
6188
|
+
console.log(import_chalk17.default.yellow(` Plan is in ${planLang}, but voice is set to ${targetLang}`));
|
|
5987
6189
|
const translate = await confirm(`${INDENT6}Translate captions to ${targetLang}?`);
|
|
5988
6190
|
if (translate) await doTranslate("Translation failed, using original");
|
|
5989
6191
|
}
|
|
@@ -5993,7 +6195,7 @@ async function runPlanPhase(params) {
|
|
|
5993
6195
|
|
|
5994
6196
|
// src/commands/phases/validation-phase.ts
|
|
5995
6197
|
var import_promises16 = require("fs/promises");
|
|
5996
|
-
var
|
|
6198
|
+
var import_chalk18 = __toESM(require("chalk"));
|
|
5997
6199
|
async function runValidationPhase(params) {
|
|
5998
6200
|
let { plan } = params;
|
|
5999
6201
|
const { config, planPath, isExtensionUrl, userEmail, userTier = "free" } = params;
|
|
@@ -6044,21 +6246,21 @@ async function runValidationPhase(params) {
|
|
|
6044
6246
|
const stepNum = result.stepIndex + 1;
|
|
6045
6247
|
const step = plan.steps[result.stepIndex];
|
|
6046
6248
|
console.log();
|
|
6047
|
-
console.log(
|
|
6249
|
+
console.log(import_chalk18.default.red(` \u2717 Step ${stepNum}: ${step.action}`));
|
|
6048
6250
|
const caption = step.caption || "";
|
|
6049
|
-
if (caption) console.log(
|
|
6050
|
-
console.log(
|
|
6051
|
-
if (step.value) console.log(
|
|
6052
|
-
console.log(
|
|
6251
|
+
if (caption) console.log(import_chalk18.default.dim(` Caption: ${caption.slice(0, 50)}${caption.length > 50 ? "..." : ""}`));
|
|
6252
|
+
console.log(import_chalk18.default.dim(` Target: ${step.target || "none"}`));
|
|
6253
|
+
if (step.value) console.log(import_chalk18.default.dim(` Value: ${step.value}`));
|
|
6254
|
+
console.log(import_chalk18.default.dim(` Error: ${result.error}`));
|
|
6053
6255
|
if (result.suggestion) {
|
|
6054
|
-
console.log(
|
|
6256
|
+
console.log(import_chalk18.default.yellow(` Hint: ${result.suggestion}`));
|
|
6055
6257
|
}
|
|
6056
6258
|
console.log();
|
|
6057
6259
|
const isUploadMissingFile = step.action === "upload" && (!step.value || step.value.startsWith("[") || step.value.includes("example") || step.value.includes("/path/to"));
|
|
6058
6260
|
if (isUploadMissingFile) {
|
|
6059
6261
|
const filePath = await promptInput('Enter file path to upload (or "skip"): ');
|
|
6060
6262
|
if (filePath.toLowerCase() === "skip") {
|
|
6061
|
-
console.log(
|
|
6263
|
+
console.log(import_chalk18.default.yellow(` Skipping step ${stepNum}`));
|
|
6062
6264
|
continue;
|
|
6063
6265
|
}
|
|
6064
6266
|
if (filePath) {
|
|
@@ -6067,13 +6269,13 @@ async function runValidationPhase(params) {
|
|
|
6067
6269
|
const revalidation2 = await validateStepsOnPage(plan, config, logError, config.output.dir);
|
|
6068
6270
|
const newResult2 = revalidation2.results[result.stepIndex];
|
|
6069
6271
|
if (newResult2?.success) {
|
|
6070
|
-
console.log(
|
|
6272
|
+
console.log(import_chalk18.default.green(` \u2713 Step ${stepNum} now passes`));
|
|
6071
6273
|
validationResult = revalidation2;
|
|
6072
6274
|
continue;
|
|
6073
6275
|
}
|
|
6074
6276
|
result.error = newResult2?.error;
|
|
6075
6277
|
result.suggestion = newResult2?.suggestion;
|
|
6076
|
-
console.log(
|
|
6278
|
+
console.log(import_chalk18.default.red(` Still failing: ${result.error}`));
|
|
6077
6279
|
}
|
|
6078
6280
|
}
|
|
6079
6281
|
const fixTask = task(`Fixing step ${stepNum} with AI`);
|
|
@@ -6095,8 +6297,8 @@ async function runValidationPhase(params) {
|
|
|
6095
6297
|
await savePlan(plan, planPath);
|
|
6096
6298
|
fixTask.succeed("AI suggested a fix");
|
|
6097
6299
|
const fixedStep = plan.steps[result.stepIndex];
|
|
6098
|
-
console.log(
|
|
6099
|
-
if (fixedStep.value) console.log(
|
|
6300
|
+
console.log(import_chalk18.default.dim(` New target: ${fixedStep.target || "none"}`));
|
|
6301
|
+
if (fixedStep.value) console.log(import_chalk18.default.dim(` New value: ${fixedStep.value}`));
|
|
6100
6302
|
} catch {
|
|
6101
6303
|
fixTask.fail("AI could not fix automatically");
|
|
6102
6304
|
}
|
|
@@ -6112,11 +6314,11 @@ async function runValidationPhase(params) {
|
|
|
6112
6314
|
result.error = newResult?.error;
|
|
6113
6315
|
result.suggestion = newResult?.suggestion;
|
|
6114
6316
|
console.log();
|
|
6115
|
-
console.log(
|
|
6317
|
+
console.log(import_chalk18.default.yellow(` Edit step ${stepNum} in: `) + import_chalk18.default.cyan(formatClickablePath(planPath)));
|
|
6116
6318
|
console.log();
|
|
6117
6319
|
const choice = await promptInput('Press Enter after editing, or type "skip" to skip this step: ');
|
|
6118
6320
|
if (choice.toLowerCase() === "skip") {
|
|
6119
|
-
console.log(
|
|
6321
|
+
console.log(import_chalk18.default.yellow(` Skipping step ${stepNum} - may fail during recording`));
|
|
6120
6322
|
continue;
|
|
6121
6323
|
}
|
|
6122
6324
|
plan = JSON.parse(await (0, import_promises16.readFile)(planPath, "utf-8"));
|
|
@@ -6143,31 +6345,31 @@ async function runValidationPhase(params) {
|
|
|
6143
6345
|
}
|
|
6144
6346
|
|
|
6145
6347
|
// src/commands/utils/chrome-extension.ts
|
|
6146
|
-
var
|
|
6348
|
+
var import_node_fs17 = require("fs");
|
|
6147
6349
|
var import_node_os3 = require("os");
|
|
6148
|
-
var
|
|
6350
|
+
var import_node_path24 = require("path");
|
|
6149
6351
|
function resolveChromeExtension(extensionId) {
|
|
6150
6352
|
const platform = process.platform;
|
|
6151
6353
|
const possibleDirs = [];
|
|
6152
6354
|
const getChromeExtDirs = (userDataPath) => {
|
|
6153
6355
|
const dirs = [];
|
|
6154
6356
|
try {
|
|
6155
|
-
const profiles = (0,
|
|
6357
|
+
const profiles = (0, import_node_fs17.readdirSync)(userDataPath).filter((p) => p === "Default" || p.startsWith("Profile "));
|
|
6156
6358
|
for (const profile of profiles) {
|
|
6157
|
-
dirs.push((0,
|
|
6359
|
+
dirs.push((0, import_node_path24.join)(userDataPath, profile, "Extensions"));
|
|
6158
6360
|
}
|
|
6159
6361
|
} catch {
|
|
6160
|
-
dirs.push((0,
|
|
6362
|
+
dirs.push((0, import_node_path24.join)(userDataPath, "Default", "Extensions"));
|
|
6161
6363
|
}
|
|
6162
6364
|
return dirs;
|
|
6163
6365
|
};
|
|
6164
6366
|
if (platform === "win32") {
|
|
6165
|
-
possibleDirs.push(...getChromeExtDirs((0,
|
|
6367
|
+
possibleDirs.push(...getChromeExtDirs((0, import_node_path24.join)((0, import_node_os3.homedir)(), "AppData", "Local", "Google", "Chrome", "User Data")));
|
|
6166
6368
|
} else if (platform === "darwin") {
|
|
6167
|
-
possibleDirs.push(...getChromeExtDirs((0,
|
|
6369
|
+
possibleDirs.push(...getChromeExtDirs((0, import_node_path24.join)((0, import_node_os3.homedir)(), "Library", "Application Support", "Google", "Chrome")));
|
|
6168
6370
|
} else {
|
|
6169
6371
|
try {
|
|
6170
|
-
const windowsUsers = (0,
|
|
6372
|
+
const windowsUsers = (0, import_node_fs17.readdirSync)("/mnt/c/Users").filter(
|
|
6171
6373
|
(u) => !["All Users", "Default", "Default User", "Public", "desktop.ini"].includes(u)
|
|
6172
6374
|
);
|
|
6173
6375
|
for (const user of windowsUsers) {
|
|
@@ -6175,22 +6377,22 @@ function resolveChromeExtension(extensionId) {
|
|
|
6175
6377
|
}
|
|
6176
6378
|
} catch {
|
|
6177
6379
|
}
|
|
6178
|
-
possibleDirs.push(...getChromeExtDirs((0,
|
|
6380
|
+
possibleDirs.push(...getChromeExtDirs((0, import_node_path24.join)((0, import_node_os3.homedir)(), ".config", "google-chrome")));
|
|
6179
6381
|
}
|
|
6180
6382
|
for (const chromeExtDir of possibleDirs) {
|
|
6181
|
-
const extDir = (0,
|
|
6182
|
-
if (!(0,
|
|
6383
|
+
const extDir = (0, import_node_path24.join)(chromeExtDir, extensionId);
|
|
6384
|
+
if (!(0, import_node_fs17.existsSync)(extDir)) continue;
|
|
6183
6385
|
try {
|
|
6184
|
-
const versions = (0,
|
|
6386
|
+
const versions = (0, import_node_fs17.readdirSync)(extDir).filter((f) => !f.startsWith("."));
|
|
6185
6387
|
if (versions.length === 0) continue;
|
|
6186
6388
|
const latestVersion = versions.sort().pop();
|
|
6187
|
-
const extPath = (0,
|
|
6389
|
+
const extPath = (0, import_node_path24.join)(extDir, latestVersion);
|
|
6188
6390
|
let name = extensionId;
|
|
6189
6391
|
let popupPath = "popup.html";
|
|
6190
|
-
const manifestPath = (0,
|
|
6191
|
-
if ((0,
|
|
6392
|
+
const manifestPath = (0, import_node_path24.join)(extPath, "manifest.json");
|
|
6393
|
+
if ((0, import_node_fs17.existsSync)(manifestPath)) {
|
|
6192
6394
|
try {
|
|
6193
|
-
const manifest = JSON.parse((0,
|
|
6395
|
+
const manifest = JSON.parse((0, import_node_fs17.readFileSync)(manifestPath, "utf-8"));
|
|
6194
6396
|
if (manifest.name && !manifest.name.startsWith("__MSG_")) {
|
|
6195
6397
|
name = manifest.name;
|
|
6196
6398
|
}
|
|
@@ -6221,16 +6423,16 @@ async function recordCommand(url, options = {}) {
|
|
|
6221
6423
|
const extensionId = url.slice(7);
|
|
6222
6424
|
chromeExtension = resolveChromeExtension(extensionId);
|
|
6223
6425
|
if (!chromeExtension) {
|
|
6224
|
-
console.log(
|
|
6426
|
+
console.log(import_chalk19.default.red(`
|
|
6225
6427
|
\u2717 Chrome extension not found: ${extensionId}`));
|
|
6226
|
-
console.log(
|
|
6428
|
+
console.log(import_chalk19.default.dim(` Make sure the extension is installed in Chrome
|
|
6227
6429
|
`));
|
|
6228
6430
|
process.exit(1);
|
|
6229
6431
|
}
|
|
6230
6432
|
config.browser = { extension: chromeExtension.path };
|
|
6231
6433
|
url = chromeExtension.popupUrl;
|
|
6232
|
-
console.log(
|
|
6233
|
-
console.log(
|
|
6434
|
+
console.log(import_chalk19.default.dim(` Extension: ${chromeExtension.name}`));
|
|
6435
|
+
console.log(import_chalk19.default.dim(` Path: ${chromeExtension.path}`));
|
|
6234
6436
|
}
|
|
6235
6437
|
config.output.dir = chromeExtension ? `./${chromeExtension.name.toLowerCase().replace(/[^a-z0-9]+/g, "-")}-demo` : getOutputDirFromUrl(url);
|
|
6236
6438
|
const userData = await loadUserData();
|
|
@@ -6243,20 +6445,20 @@ async function recordCommand(url, options = {}) {
|
|
|
6243
6445
|
if (!authResult) return;
|
|
6244
6446
|
const { userEmail, userTier, localVideoCount } = authResult;
|
|
6245
6447
|
if (configCreated) {
|
|
6246
|
-
const configPath = (0,
|
|
6448
|
+
const configPath = (0, import_node_path25.join)(process.cwd(), CONFIG_FILE);
|
|
6247
6449
|
console.log();
|
|
6248
|
-
console.log(
|
|
6249
|
-
console.log(
|
|
6450
|
+
console.log(import_chalk19.default.green(` \u2714 Created ${CONFIG_FILE}`));
|
|
6451
|
+
console.log(import_chalk19.default.dim(` ${configPath}`));
|
|
6250
6452
|
console.log();
|
|
6251
|
-
console.log(
|
|
6252
|
-
console.log(
|
|
6253
|
-
console.log(
|
|
6254
|
-
console.log(
|
|
6255
|
-
console.log(
|
|
6453
|
+
console.log(import_chalk19.default.cyan(" Review the config file to customize:"));
|
|
6454
|
+
console.log(import_chalk19.default.dim(" \u2022 lang: Voice language (en, es, ar, zh...)"));
|
|
6455
|
+
console.log(import_chalk19.default.dim(" \u2022 video: Resolution, quality, effects"));
|
|
6456
|
+
console.log(import_chalk19.default.dim(" \u2022 music: Background music path and volume"));
|
|
6457
|
+
console.log(import_chalk19.default.dim(" \u2022 output: Output directory"));
|
|
6256
6458
|
console.log();
|
|
6257
6459
|
const proceed = await confirm(`${INDENT3}Proceed with default settings?`);
|
|
6258
6460
|
if (!proceed) {
|
|
6259
|
-
console.log(
|
|
6461
|
+
console.log(import_chalk19.default.yellow(`
|
|
6260
6462
|
Edit ${CONFIG_FILE} and run again.
|
|
6261
6463
|
`));
|
|
6262
6464
|
process.exit(0);
|
|
@@ -6264,17 +6466,17 @@ async function recordCommand(url, options = {}) {
|
|
|
6264
6466
|
}
|
|
6265
6467
|
if (langOverride) config.lang = langOverride;
|
|
6266
6468
|
if (config.music?.path) {
|
|
6267
|
-
const musicPath = (0,
|
|
6268
|
-
if (!(0,
|
|
6469
|
+
const musicPath = (0, import_node_path25.isAbsolute)(config.music.path) ? config.music.path : (0, import_node_path25.resolve)(config.output.dir, config.music.path);
|
|
6470
|
+
if (!(0, import_node_fs18.existsSync)(musicPath)) {
|
|
6269
6471
|
console.log();
|
|
6270
|
-
console.log(
|
|
6271
|
-
console.log(
|
|
6472
|
+
console.log(import_chalk19.default.yellow(` \u26A0 Music file not found: ${config.music.path}`));
|
|
6473
|
+
console.log(import_chalk19.default.dim(` Config: ${(0, import_node_path25.join)(process.cwd(), CONFIG_FILE)}`));
|
|
6272
6474
|
console.log();
|
|
6273
6475
|
const proceed = await confirm(`${INDENT3}Continue without background music?`);
|
|
6274
6476
|
if (!proceed) {
|
|
6275
|
-
console.log(
|
|
6477
|
+
console.log(import_chalk19.default.yellow(`
|
|
6276
6478
|
Edit music.path in config and run again.`));
|
|
6277
|
-
console.log(
|
|
6479
|
+
console.log(import_chalk19.default.dim(` ${(0, import_node_path25.join)(process.cwd(), CONFIG_FILE)}
|
|
6278
6480
|
`));
|
|
6279
6481
|
process.exit(0);
|
|
6280
6482
|
}
|
|
@@ -6284,8 +6486,8 @@ async function recordCommand(url, options = {}) {
|
|
|
6284
6486
|
const captions = await loadCaptions();
|
|
6285
6487
|
const workDir = await prepareWorkDir();
|
|
6286
6488
|
const targetLang = config.lang || "en";
|
|
6287
|
-
const planPath = (0,
|
|
6288
|
-
const legacyPlanPath = (0,
|
|
6489
|
+
const planPath = (0, import_node_path25.join)(config.output.dir, `plan-${targetLang}.json`);
|
|
6490
|
+
const legacyPlanPath = (0, import_node_path25.join)(config.output.dir, "plan.json");
|
|
6289
6491
|
const isExtensionUrl = url.startsWith("chrome-extension://");
|
|
6290
6492
|
await (0, import_promises17.mkdir)(config.output.dir, { recursive: true });
|
|
6291
6493
|
await setLogFilePath(config.output.dir);
|
|
@@ -6298,7 +6500,8 @@ async function recordCommand(url, options = {}) {
|
|
|
6298
6500
|
...description !== void 0 && { description },
|
|
6299
6501
|
planPath,
|
|
6300
6502
|
legacyPlanPath,
|
|
6301
|
-
targetLang
|
|
6503
|
+
targetLang,
|
|
6504
|
+
...fresh !== void 0 && { fresh }
|
|
6302
6505
|
});
|
|
6303
6506
|
const validatedPlan = await runValidationPhase({
|
|
6304
6507
|
plan,
|
|
@@ -6323,29 +6526,29 @@ async function recordCommand(url, options = {}) {
|
|
|
6323
6526
|
await saveUserData({ localVideoCount: newCount });
|
|
6324
6527
|
const remaining = FREE_VIDEO_LIMIT - newCount;
|
|
6325
6528
|
if (remaining > 0) {
|
|
6326
|
-
console.log(
|
|
6529
|
+
console.log(import_chalk19.default.dim(` ${remaining} free video${remaining === 1 ? "" : "s"} remaining`));
|
|
6327
6530
|
}
|
|
6328
6531
|
}
|
|
6329
6532
|
console.log();
|
|
6330
|
-
console.log(
|
|
6533
|
+
console.log(import_chalk19.default.bold.green(" \u2713 Done!"));
|
|
6331
6534
|
console.log();
|
|
6332
|
-
console.log(
|
|
6535
|
+
console.log(import_chalk19.default.cyan(` ${formatClickablePath(finalPath)}`));
|
|
6333
6536
|
console.log();
|
|
6334
6537
|
return finalPath;
|
|
6335
6538
|
} catch (err) {
|
|
6336
6539
|
await logError("Record Command", err);
|
|
6337
6540
|
if (rethrow) throw err;
|
|
6338
6541
|
console.log();
|
|
6339
|
-
console.log(
|
|
6340
|
-
console.log(
|
|
6542
|
+
console.log(import_chalk19.default.red(` Error: ${err instanceof Error ? err.message : "Unknown error"}`));
|
|
6543
|
+
console.log(import_chalk19.default.dim(` See ${logFilePath} for details`));
|
|
6341
6544
|
process.exit(1);
|
|
6342
6545
|
}
|
|
6343
6546
|
}
|
|
6344
6547
|
|
|
6345
6548
|
// src/commands/script-cmd.ts
|
|
6346
6549
|
var import_promises18 = require("fs/promises");
|
|
6347
|
-
var
|
|
6348
|
-
var
|
|
6550
|
+
var import_node_path26 = require("path");
|
|
6551
|
+
var import_chalk20 = __toESM(require("chalk"));
|
|
6349
6552
|
init_lib();
|
|
6350
6553
|
async function scriptCommand(url, scriptPath, lang) {
|
|
6351
6554
|
const { config, workDir, captions } = await initCommandEnv(url, lang);
|
|
@@ -6353,7 +6556,7 @@ async function scriptCommand(url, scriptPath, lang) {
|
|
|
6353
6556
|
const raw = await (0, import_promises18.readFile)(scriptPath, "utf-8");
|
|
6354
6557
|
const lines = raw.split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#"));
|
|
6355
6558
|
if (lines.length === 0) {
|
|
6356
|
-
console.log(
|
|
6559
|
+
console.log(import_chalk20.default.red(" Script file is empty or has no non-comment lines"));
|
|
6357
6560
|
process.exit(1);
|
|
6358
6561
|
}
|
|
6359
6562
|
info(`${lines.length} captions from script`);
|
|
@@ -6365,7 +6568,7 @@ async function scriptCommand(url, scriptPath, lang) {
|
|
|
6365
6568
|
const planTask = task("Mapping script to UI actions");
|
|
6366
6569
|
const appTitle = new URL(url).hostname.replace(/^www\./, "");
|
|
6367
6570
|
const plan = await generatePlanFromScript(lines, url, appTitle, elementsText, lang, logError);
|
|
6368
|
-
const planPath = (0,
|
|
6571
|
+
const planPath = (0, import_node_path26.join)(config.output.dir, `plan-${lang ?? "en"}.json`);
|
|
6369
6572
|
await savePlan(plan, planPath);
|
|
6370
6573
|
planTask.succeed(`${plan.steps.length} steps planned`);
|
|
6371
6574
|
header("Voiceover");
|
|
@@ -6385,8 +6588,8 @@ async function scriptCommand(url, scriptPath, lang) {
|
|
|
6385
6588
|
const assPath = await generateASS(plan, stepAudio, workDir, config, captions);
|
|
6386
6589
|
const finalPath = await composeAndFinish(videoPath, voiceoverPath, assPath, config, "free");
|
|
6387
6590
|
console.log();
|
|
6388
|
-
console.log(
|
|
6389
|
-
console.log(
|
|
6591
|
+
console.log(import_chalk20.default.bold.green(" Done!"));
|
|
6592
|
+
console.log(import_chalk20.default.cyan(` ${finalPath}`));
|
|
6390
6593
|
}
|
|
6391
6594
|
|
|
6392
6595
|
// src/qp.ts
|
|
@@ -6456,6 +6659,20 @@ function parseArgs(args) {
|
|
|
6456
6659
|
if (first === "record") {
|
|
6457
6660
|
args = args.slice(1);
|
|
6458
6661
|
}
|
|
6662
|
+
if (first === "edit") {
|
|
6663
|
+
const rest = args.slice(1);
|
|
6664
|
+
let url2;
|
|
6665
|
+
let lang2;
|
|
6666
|
+
for (const arg2 of rest) {
|
|
6667
|
+
if (arg2.startsWith("lang=")) {
|
|
6668
|
+
lang2 = arg2.slice(5);
|
|
6669
|
+
} else if (!arg2.startsWith("-") && !url2 && !arg2.includes("=")) {
|
|
6670
|
+
url2 = normalizeUrl3(arg2);
|
|
6671
|
+
}
|
|
6672
|
+
}
|
|
6673
|
+
if (!url2) return { ...base, command: "help" };
|
|
6674
|
+
return { ...base, command: "edit", url: url2, lang: lang2 };
|
|
6675
|
+
}
|
|
6459
6676
|
if (first === "capture") {
|
|
6460
6677
|
const rest = args.slice(1);
|
|
6461
6678
|
let url2;
|
|
@@ -6523,8 +6740,8 @@ async function main() {
|
|
|
6523
6740
|
break;
|
|
6524
6741
|
case "auth":
|
|
6525
6742
|
if (!parsed.url) {
|
|
6526
|
-
console.log(
|
|
6527
|
-
console.log(
|
|
6743
|
+
console.log(import_chalk21.default.red(" Missing URL"));
|
|
6744
|
+
console.log(import_chalk21.default.dim(" Run: qp auth <url>"));
|
|
6528
6745
|
process.exit(1);
|
|
6529
6746
|
}
|
|
6530
6747
|
await authCommand(parsed.url);
|
|
@@ -6535,6 +6752,14 @@ async function main() {
|
|
|
6535
6752
|
case "auth-list":
|
|
6536
6753
|
await authListCommand();
|
|
6537
6754
|
break;
|
|
6755
|
+
case "edit":
|
|
6756
|
+
if (!parsed.url) {
|
|
6757
|
+
console.log(import_chalk21.default.red(" Missing URL"));
|
|
6758
|
+
console.log(import_chalk21.default.dim(" Run: qp edit <url>"));
|
|
6759
|
+
process.exit(1);
|
|
6760
|
+
}
|
|
6761
|
+
await editCommand(parsed.url, parsed.lang);
|
|
6762
|
+
break;
|
|
6538
6763
|
case "init":
|
|
6539
6764
|
await initCommand();
|
|
6540
6765
|
break;
|
|
@@ -6543,8 +6768,8 @@ async function main() {
|
|
|
6543
6768
|
break;
|
|
6544
6769
|
case "record":
|
|
6545
6770
|
if (!parsed.url) {
|
|
6546
|
-
console.log(
|
|
6547
|
-
console.log(
|
|
6771
|
+
console.log(import_chalk21.default.red(" Missing URL"));
|
|
6772
|
+
console.log(import_chalk21.default.dim(" Run: qp --help"));
|
|
6548
6773
|
process.exit(1);
|
|
6549
6774
|
}
|
|
6550
6775
|
setAutoYes(parsed.yes);
|
|
@@ -6563,8 +6788,8 @@ async function main() {
|
|
|
6563
6788
|
break;
|
|
6564
6789
|
case "capture":
|
|
6565
6790
|
if (!parsed.url) {
|
|
6566
|
-
console.log(
|
|
6567
|
-
console.log(
|
|
6791
|
+
console.log(import_chalk21.default.red(" Missing URL"));
|
|
6792
|
+
console.log(import_chalk21.default.dim(" Run: qp --help"));
|
|
6568
6793
|
process.exit(1);
|
|
6569
6794
|
}
|
|
6570
6795
|
console.log();
|