@xbrowser/cli 1.2.0 → 1.2.2
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/{chunk-XYXCS7JW.js → chunk-JPSFUFPG.js} +4 -2
- package/dist/chunk-PHBK3TRN.js +436 -0
- package/dist/cli.js +52 -37
- package/dist/{daemon-client-R4QWHD7V.js → daemon-client-COJQESU2.js} +4 -2
- package/dist/{daemon-client-ZHO6NG36.js → daemon-client-XXKMJZZ7.js} +1 -1
- package/dist/daemon-main.js +132 -524
- package/dist/index.js +52 -37
- package/dist/plugin-singleton-ZBVTWEYK.js +9 -0
- package/package.json +1 -1
package/dist/daemon-main.js
CHANGED
|
@@ -24,6 +24,9 @@ import {
|
|
|
24
24
|
} from "./chunk-SLQR57XZ.js";
|
|
25
25
|
import "./chunk-QFROODUU.js";
|
|
26
26
|
import "./chunk-TNEN6VQ2.js";
|
|
27
|
+
import {
|
|
28
|
+
getPluginLoader
|
|
29
|
+
} from "./chunk-PHBK3TRN.js";
|
|
27
30
|
import {
|
|
28
31
|
getDaemonConfig,
|
|
29
32
|
getDaemonProcessStatus
|
|
@@ -34,15 +37,15 @@ import {
|
|
|
34
37
|
import "./chunk-KFQGP6VL.js";
|
|
35
38
|
|
|
36
39
|
// src/daemon/daemon-main.ts
|
|
37
|
-
import { writeFileSync as
|
|
38
|
-
import { join as
|
|
39
|
-
import { homedir as
|
|
40
|
+
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, appendFileSync, unlinkSync } from "fs";
|
|
41
|
+
import { join as join8 } from "path";
|
|
42
|
+
import { homedir as homedir8 } from "os";
|
|
40
43
|
import { startHttpServer } from "@dyyz1993/xcli-core";
|
|
41
44
|
|
|
42
45
|
// src/daemon/rpc-handlers.ts
|
|
43
|
-
import { writeFileSync as
|
|
44
|
-
import { join as
|
|
45
|
-
import { homedir as
|
|
46
|
+
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync5 } from "fs";
|
|
47
|
+
import { join as join7 } from "path";
|
|
48
|
+
import { homedir as homedir7 } from "os";
|
|
46
49
|
import {
|
|
47
50
|
createSessionMeta,
|
|
48
51
|
removeSession
|
|
@@ -449,15 +452,15 @@ var clickCommand = registerCommand({
|
|
|
449
452
|
let detectedNewPage;
|
|
450
453
|
let cleanup;
|
|
451
454
|
if (ctx.browserContext?.on) {
|
|
452
|
-
const pagePromise = new Promise((
|
|
455
|
+
const pagePromise = new Promise((resolve) => {
|
|
453
456
|
const timer = setTimeout(() => {
|
|
454
457
|
ctx.browserContext.off("page", handler);
|
|
455
|
-
|
|
458
|
+
resolve(void 0);
|
|
456
459
|
}, 3e3);
|
|
457
460
|
const handler = (page2) => {
|
|
458
461
|
clearTimeout(timer);
|
|
459
462
|
ctx.browserContext.off("page", handler);
|
|
460
|
-
|
|
463
|
+
resolve(page2);
|
|
461
464
|
};
|
|
462
465
|
ctx.browserContext.on("page", handler);
|
|
463
466
|
});
|
|
@@ -907,10 +910,10 @@ var setCookieCommand = registerCommand({
|
|
|
907
910
|
description: "Set a cookie",
|
|
908
911
|
scope: "page",
|
|
909
912
|
parameters: z8.object({
|
|
910
|
-
name: z8.string(),
|
|
911
|
-
value: z8.string(),
|
|
912
|
-
domain: z8.string().optional(),
|
|
913
|
-
path: z8.string().optional(),
|
|
913
|
+
name: z8.coerce.string(),
|
|
914
|
+
value: z8.coerce.string(),
|
|
915
|
+
domain: z8.coerce.string().optional(),
|
|
916
|
+
path: z8.coerce.string().optional(),
|
|
914
917
|
expires: z8.number().optional(),
|
|
915
918
|
httpOnly: z8.boolean().optional(),
|
|
916
919
|
secure: z8.boolean().optional(),
|
|
@@ -1255,7 +1258,7 @@ var consoleCheckCommand = registerCommand({
|
|
|
1255
1258
|
await page.goto(p.url, { waitUntil: "domcontentloaded" });
|
|
1256
1259
|
}
|
|
1257
1260
|
const messages = await page.evaluate((args) => {
|
|
1258
|
-
return new Promise((
|
|
1261
|
+
return new Promise((resolve) => {
|
|
1259
1262
|
const collected = [];
|
|
1260
1263
|
const originalConsole = {
|
|
1261
1264
|
log: console.log,
|
|
@@ -1322,7 +1325,7 @@ ${a.stack || ""}`;
|
|
|
1322
1325
|
console.warn = originalConsole.warn;
|
|
1323
1326
|
console.error = originalConsole.error;
|
|
1324
1327
|
console.info = originalConsole.info;
|
|
1325
|
-
|
|
1328
|
+
resolve(collected);
|
|
1326
1329
|
}, args.duration);
|
|
1327
1330
|
});
|
|
1328
1331
|
}, { duration: p.duration });
|
|
@@ -1748,7 +1751,7 @@ async function executeAction(page, action) {
|
|
|
1748
1751
|
if (action.selector) {
|
|
1749
1752
|
await page.waitForSelector(action.selector, { timeout: 3e4 });
|
|
1750
1753
|
} else if (action.milliseconds) {
|
|
1751
|
-
await new Promise((
|
|
1754
|
+
await new Promise((resolve) => setTimeout(resolve, action.milliseconds));
|
|
1752
1755
|
} else {
|
|
1753
1756
|
throw new Error("wait action requires either milliseconds or selector");
|
|
1754
1757
|
}
|
|
@@ -1837,8 +1840,8 @@ var actionsCommand = registerCommand({
|
|
|
1837
1840
|
results.push(result);
|
|
1838
1841
|
}
|
|
1839
1842
|
})();
|
|
1840
|
-
const timeoutPromise = new Promise((
|
|
1841
|
-
setTimeout(
|
|
1843
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
1844
|
+
setTimeout(resolve, timeoutMs);
|
|
1842
1845
|
});
|
|
1843
1846
|
await Promise.race([executionPromise, timeoutPromise]);
|
|
1844
1847
|
const title = await ctx.page.title();
|
|
@@ -2316,44 +2319,45 @@ var scrapeCommand = registerCommand({
|
|
|
2316
2319
|
switch (p.format) {
|
|
2317
2320
|
case "markdown": {
|
|
2318
2321
|
const tablesMd = await page.evaluate(() => {
|
|
2319
|
-
|
|
2320
|
-
"
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2322
|
+
document.querySelectorAll(
|
|
2323
|
+
'.el-table__fixed, .el-table__fixed-right, [class*="fixed-left"], [class*="fixed-right"], .ant-table-fixed-left, .ant-table-fixed-right'
|
|
2324
|
+
).forEach((el) => el.remove());
|
|
2325
|
+
document.querySelectorAll("table").forEach((t) => {
|
|
2326
|
+
if (t.closest(".el-table__fixed, .el-table__fixed-right")) t.remove();
|
|
2327
|
+
});
|
|
2328
|
+
const tables = document.querySelectorAll("table");
|
|
2329
|
+
if (tables.length === 0) {
|
|
2330
|
+
const altTables = document.querySelectorAll(
|
|
2331
|
+
'[role="table"], [role="grid"], .el-table__body, .ant-table-tbody'
|
|
2332
|
+
);
|
|
2333
|
+
if (altTables.length === 0) return "";
|
|
2334
|
+
return Array.from(altTables).map((table) => {
|
|
2335
|
+
return extractRowsFromContainer(table);
|
|
2336
|
+
}).filter((md) => md).join("\n\n");
|
|
2337
|
+
}
|
|
2334
2338
|
return Array.from(tables).map((table) => {
|
|
2335
|
-
|
|
2339
|
+
return extractRowsFromContainer(table);
|
|
2340
|
+
}).filter((md) => md).join("\n\n");
|
|
2341
|
+
function extractRowsFromContainer(container) {
|
|
2342
|
+
const rows = container.querySelectorAll(':scope > tr, :scope > thead > tr, :scope > tbody > tr, :scope > tfoot > tr, [role="row"]');
|
|
2336
2343
|
if (rows.length === 0) return "";
|
|
2337
2344
|
const mdRows = Array.from(rows).map((row) => {
|
|
2338
|
-
const cells = row.querySelectorAll('th, td, [role="columnheader"], [role="cell"]
|
|
2345
|
+
const cells = row.querySelectorAll(':scope > th, :scope > td, :scope > [role="columnheader"], :scope > [role="cell"]');
|
|
2346
|
+
if (cells.length === 0) return "";
|
|
2339
2347
|
return "| " + Array.from(cells).map((c) => {
|
|
2340
2348
|
const cellText = c.innerText?.trim().replace(/\n/g, " ") || "";
|
|
2341
2349
|
return cellText.replace(/\|/g, "\\|") || "";
|
|
2342
2350
|
}).join(" | ") + " |";
|
|
2343
|
-
}).
|
|
2351
|
+
}).filter((r) => r);
|
|
2352
|
+
if (mdRows.length === 0) return "";
|
|
2344
2353
|
const headerRow = rows[0];
|
|
2345
|
-
const headerCells = headerRow.querySelectorAll('th, [role="columnheader"]
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
const sep = "| " + Array(headerCount).fill("---").join(" | ") + " |";
|
|
2350
|
-
return mdRows.split("\n").map((line, i) => {
|
|
2351
|
-
if (i === 0) return line + "\n" + sep;
|
|
2352
|
-
return line;
|
|
2353
|
-
}).join("\n");
|
|
2354
|
+
const headerCells = headerRow.querySelectorAll(':scope > th, :scope > [role="columnheader"]');
|
|
2355
|
+
if (headerCells.length > 0) {
|
|
2356
|
+
const sep = "| " + Array(headerCells.length).fill("---").join(" | ") + " |";
|
|
2357
|
+
return mdRows[0] + "\n" + sep + "\n" + mdRows.slice(1).join("\n");
|
|
2354
2358
|
}
|
|
2355
|
-
return mdRows;
|
|
2356
|
-
}
|
|
2359
|
+
return mdRows.join("\n");
|
|
2360
|
+
}
|
|
2357
2361
|
});
|
|
2358
2362
|
content = htmlToMarkdown(html, { onlyMainContent: p.onlyMainContent });
|
|
2359
2363
|
if (tablesMd) {
|
|
@@ -2557,11 +2561,11 @@ async function navigateForMap(page, url, timeout = 15e3) {
|
|
|
2557
2561
|
}
|
|
2558
2562
|
async function extractPageLinks(page, baseUrl) {
|
|
2559
2563
|
await navigateForMap(page, baseUrl);
|
|
2560
|
-
await new Promise((
|
|
2564
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
2561
2565
|
await page.evaluate(() => {
|
|
2562
2566
|
window.scrollTo(0, document.body.scrollHeight);
|
|
2563
2567
|
});
|
|
2564
|
-
await new Promise((
|
|
2568
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
2565
2569
|
const origin = new URL(baseUrl).origin;
|
|
2566
2570
|
const rawLinks = await page.evaluate((evalOrigin) => {
|
|
2567
2571
|
return Array.from(document.querySelectorAll("a[href]")).map((a) => {
|
|
@@ -4419,25 +4423,25 @@ aria snapshot\uFF1A
|
|
|
4419
4423
|
async function analyzeWithLLM(ariaSnapshot) {
|
|
4420
4424
|
const piBin = process.env.PI_CLI_PATH || "pi";
|
|
4421
4425
|
const prompt = LLM_PROMPT.replace("{snapshot}", ariaSnapshot.slice(0, 4e3));
|
|
4422
|
-
return new Promise((
|
|
4426
|
+
return new Promise((resolve) => {
|
|
4423
4427
|
execFile(
|
|
4424
4428
|
piBin,
|
|
4425
4429
|
["--provider", LLM_PROVIDER, "--model", LLM_MODEL, prompt],
|
|
4426
4430
|
{ timeout: LLM_TIMEOUT_MS, maxBuffer: 1024 * 1024 },
|
|
4427
4431
|
(err, stdout, _stderr) => {
|
|
4428
4432
|
if (err) {
|
|
4429
|
-
|
|
4433
|
+
resolve(null);
|
|
4430
4434
|
return;
|
|
4431
4435
|
}
|
|
4432
4436
|
const output = (stdout || "").trim();
|
|
4433
4437
|
if (!output) {
|
|
4434
|
-
|
|
4438
|
+
resolve(null);
|
|
4435
4439
|
return;
|
|
4436
4440
|
}
|
|
4437
4441
|
try {
|
|
4438
4442
|
const parsed = parse(output);
|
|
4439
4443
|
if (!parsed || typeof parsed !== "object") {
|
|
4440
|
-
|
|
4444
|
+
resolve(null);
|
|
4441
4445
|
return;
|
|
4442
4446
|
}
|
|
4443
4447
|
const elements = {};
|
|
@@ -4452,9 +4456,9 @@ async function analyzeWithLLM(ariaSnapshot) {
|
|
|
4452
4456
|
};
|
|
4453
4457
|
}
|
|
4454
4458
|
}
|
|
4455
|
-
|
|
4459
|
+
resolve(Object.keys(elements).length > 0 ? elements : null);
|
|
4456
4460
|
} catch {
|
|
4457
|
-
|
|
4461
|
+
resolve(null);
|
|
4458
4462
|
}
|
|
4459
4463
|
}
|
|
4460
4464
|
);
|
|
@@ -4809,7 +4813,7 @@ async function pollUntil(timeout, pollInterval, predicate) {
|
|
|
4809
4813
|
const startedAt = Date.now();
|
|
4810
4814
|
while (Date.now() - startedAt <= timeout) {
|
|
4811
4815
|
if (await predicate()) return true;
|
|
4812
|
-
await new Promise((
|
|
4816
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
4813
4817
|
}
|
|
4814
4818
|
return false;
|
|
4815
4819
|
}
|
|
@@ -5415,11 +5419,11 @@ function resolveScriptContent(params) {
|
|
|
5415
5419
|
async function readStdin() {
|
|
5416
5420
|
const { createReadStream } = await import("fs");
|
|
5417
5421
|
const { createInterface } = await import("readline");
|
|
5418
|
-
return new Promise((
|
|
5422
|
+
return new Promise((resolve, reject) => {
|
|
5419
5423
|
const lines = [];
|
|
5420
5424
|
const rl = createInterface({ input: createReadStream("/dev/stdin") });
|
|
5421
5425
|
rl.on("line", (line) => lines.push(line));
|
|
5422
|
-
rl.on("close", () =>
|
|
5426
|
+
rl.on("close", () => resolve(lines.join("\n")));
|
|
5423
5427
|
rl.on("error", reject);
|
|
5424
5428
|
});
|
|
5425
5429
|
}
|
|
@@ -5887,434 +5891,6 @@ function formatDetectionMessage(result) {
|
|
|
5887
5891
|
Action: ${action}`;
|
|
5888
5892
|
}
|
|
5889
5893
|
|
|
5890
|
-
// src/plugin/loader.ts
|
|
5891
|
-
import {
|
|
5892
|
-
Core
|
|
5893
|
-
} from "@dyyz1993/xcli-core";
|
|
5894
|
-
import { resolve as resolve2 } from "path";
|
|
5895
|
-
import { existsSync as existsSync4, readdirSync } from "fs";
|
|
5896
|
-
import { homedir as homedir5 } from "os";
|
|
5897
|
-
|
|
5898
|
-
// src/plugin/metadata-parser.ts
|
|
5899
|
-
import { existsSync as existsSync2 } from "fs";
|
|
5900
|
-
import { resolve } from "path";
|
|
5901
|
-
|
|
5902
|
-
// src/utils/json-file.ts
|
|
5903
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
5904
|
-
function readJsonFile(filePath, defaultValue) {
|
|
5905
|
-
try {
|
|
5906
|
-
const content = readFileSync3(filePath, "utf-8");
|
|
5907
|
-
return JSON.parse(content);
|
|
5908
|
-
} catch {
|
|
5909
|
-
return defaultValue;
|
|
5910
|
-
}
|
|
5911
|
-
}
|
|
5912
|
-
|
|
5913
|
-
// src/plugin/metadata-parser.ts
|
|
5914
|
-
var PluginMetadataParser = class {
|
|
5915
|
-
static XBROWSER_KEYWORDS = ["xbrowser", "xbrowser-plugin"];
|
|
5916
|
-
static parseFromPackageJson(pluginPath) {
|
|
5917
|
-
const packageJsonPath = resolve(pluginPath, "package.json");
|
|
5918
|
-
if (!existsSync2(packageJsonPath)) {
|
|
5919
|
-
return null;
|
|
5920
|
-
}
|
|
5921
|
-
const packageJson = readJsonFile(packageJsonPath, null);
|
|
5922
|
-
if (!packageJson) return null;
|
|
5923
|
-
if (!packageJson.xbrowser) {
|
|
5924
|
-
return null;
|
|
5925
|
-
}
|
|
5926
|
-
const xbrowser = packageJson.xbrowser;
|
|
5927
|
-
const metadata = {
|
|
5928
|
-
id: xbrowser.id || packageJson.name,
|
|
5929
|
-
name: xbrowser.name || packageJson.name,
|
|
5930
|
-
description: xbrowser.description || packageJson.description || "",
|
|
5931
|
-
version: xbrowser.version || packageJson.version || "1.0.0",
|
|
5932
|
-
author: xbrowser.author || this.extractAuthor(packageJson.author),
|
|
5933
|
-
homepage: xbrowser.homepage || packageJson.homepage,
|
|
5934
|
-
commands: xbrowser.commands,
|
|
5935
|
-
sites: xbrowser.sites,
|
|
5936
|
-
tags: xbrowser.tags,
|
|
5937
|
-
screenshot: xbrowser.screenshot,
|
|
5938
|
-
license: xbrowser.license || packageJson.license
|
|
5939
|
-
};
|
|
5940
|
-
return metadata;
|
|
5941
|
-
}
|
|
5942
|
-
static isXBrowserPlugin(packageJson) {
|
|
5943
|
-
if (packageJson.xbrowser) {
|
|
5944
|
-
return true;
|
|
5945
|
-
}
|
|
5946
|
-
const keywords = packageJson.keywords;
|
|
5947
|
-
if (!keywords) return false;
|
|
5948
|
-
return this.XBROWSER_KEYWORDS.some((kw) => keywords.includes(kw));
|
|
5949
|
-
}
|
|
5950
|
-
static fromNPMResult(result) {
|
|
5951
|
-
const author = typeof result.author === "string" ? result.author : result.author?.name || "Unknown";
|
|
5952
|
-
return {
|
|
5953
|
-
id: result.name,
|
|
5954
|
-
name: result.name.replace(/^xbrowser-plugin-/, "").replace(/^@[^/]+\//, ""),
|
|
5955
|
-
description: result.description || "",
|
|
5956
|
-
version: result.version,
|
|
5957
|
-
author,
|
|
5958
|
-
homepage: result.homepage || result.links?.homepage,
|
|
5959
|
-
tags: result.keywords,
|
|
5960
|
-
license: ""
|
|
5961
|
-
};
|
|
5962
|
-
}
|
|
5963
|
-
static extractAuthor(author) {
|
|
5964
|
-
if (typeof author === "string") return author;
|
|
5965
|
-
if (typeof author === "object" && author !== null) {
|
|
5966
|
-
const authorObj = author;
|
|
5967
|
-
return authorObj.name || "Unknown";
|
|
5968
|
-
}
|
|
5969
|
-
return "Unknown";
|
|
5970
|
-
}
|
|
5971
|
-
static validateMetadata(metadata) {
|
|
5972
|
-
const errors = [];
|
|
5973
|
-
if (!metadata.id) errors.push("id is required");
|
|
5974
|
-
if (!metadata.name) errors.push("name is required");
|
|
5975
|
-
if (!metadata.description) errors.push("description is required");
|
|
5976
|
-
if (!metadata.version) errors.push("version is required");
|
|
5977
|
-
return errors;
|
|
5978
|
-
}
|
|
5979
|
-
};
|
|
5980
|
-
|
|
5981
|
-
// src/plugin/ensure-deps.ts
|
|
5982
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
5983
|
-
import { join as join5 } from "path";
|
|
5984
|
-
import { execSync } from "child_process";
|
|
5985
|
-
var SHARED_PLUGIN_DEPENDENCIES = {
|
|
5986
|
-
"zod": "^3.24.0",
|
|
5987
|
-
"@dyyz1993/xcli-core": "^0.12.1"
|
|
5988
|
-
};
|
|
5989
|
-
function ensurePluginDependencies(pluginsDir) {
|
|
5990
|
-
const zodPath = join5(pluginsDir, "node_modules", "zod");
|
|
5991
|
-
if (existsSync3(zodPath)) return;
|
|
5992
|
-
mkdirSync4(pluginsDir, { recursive: true });
|
|
5993
|
-
const pkgPath = join5(pluginsDir, "package.json");
|
|
5994
|
-
let pkg = {};
|
|
5995
|
-
if (existsSync3(pkgPath)) {
|
|
5996
|
-
try {
|
|
5997
|
-
pkg = readJsonFile(pkgPath, {});
|
|
5998
|
-
} catch {
|
|
5999
|
-
}
|
|
6000
|
-
}
|
|
6001
|
-
const existingDeps = pkg.dependencies || {};
|
|
6002
|
-
let needsInstall = false;
|
|
6003
|
-
for (const [dep, version] of Object.entries(SHARED_PLUGIN_DEPENDENCIES)) {
|
|
6004
|
-
if (!existingDeps[dep]) {
|
|
6005
|
-
existingDeps[dep] = version;
|
|
6006
|
-
needsInstall = true;
|
|
6007
|
-
}
|
|
6008
|
-
}
|
|
6009
|
-
if (!needsInstall && existsSync3(join5(pluginsDir, "node_modules"))) return;
|
|
6010
|
-
pkg.dependencies = existingDeps;
|
|
6011
|
-
pkg.private = true;
|
|
6012
|
-
pkg.description = pkg.description || "xbrowser plugins \u2014 shared dependencies";
|
|
6013
|
-
writeFileSync5(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
6014
|
-
try {
|
|
6015
|
-
execSync("npm install --production --no-package-lock --no-fund --no-audit", {
|
|
6016
|
-
cwd: pluginsDir,
|
|
6017
|
-
stdio: "pipe",
|
|
6018
|
-
timeout: 6e4,
|
|
6019
|
-
env: { ...process.env, NODE_ENV: "production" }
|
|
6020
|
-
});
|
|
6021
|
-
} catch (err) {
|
|
6022
|
-
console.warn(`\u26A0\uFE0F Failed to install shared plugin dependencies: ${err instanceof Error ? err.message : String(err)}`);
|
|
6023
|
-
}
|
|
6024
|
-
}
|
|
6025
|
-
|
|
6026
|
-
// src/plugin/contract.ts
|
|
6027
|
-
import {
|
|
6028
|
-
unwrapZod,
|
|
6029
|
-
fieldsFromZodObjectReflected,
|
|
6030
|
-
zodTypeToContractType
|
|
6031
|
-
} from "@dyyz1993/xcli-core";
|
|
6032
|
-
function buildPluginContract(site) {
|
|
6033
|
-
const commands = site.getAllCommands().map((command) => buildCommandContract(site.getCommand?.(command.name) || command, {
|
|
6034
|
-
siteRequiresLogin: site.config?.requiresLogin
|
|
6035
|
-
}));
|
|
6036
|
-
return {
|
|
6037
|
-
version: 2,
|
|
6038
|
-
plugin: {
|
|
6039
|
-
name: site.name,
|
|
6040
|
-
url: site.url,
|
|
6041
|
-
description: site.config?.description,
|
|
6042
|
-
requiresLogin: site.config?.requiresLogin
|
|
6043
|
-
},
|
|
6044
|
-
commands
|
|
6045
|
-
};
|
|
6046
|
-
}
|
|
6047
|
-
function buildCommandContract(command, options = {}) {
|
|
6048
|
-
const extension = command.xbrowser || {};
|
|
6049
|
-
const inferredFields = fieldsFromZodObject(command.parameters);
|
|
6050
|
-
const fields = mergeFields(inferredFields, extension.form?.fields || []);
|
|
6051
|
-
const positional = extension.positional || fields.filter((field) => field.positional).map((field) => field.name);
|
|
6052
|
-
const requiresLogin = command.requiresLogin === true || options.siteRequiresLogin === true && command.name !== "login" && command.name !== "logout";
|
|
6053
|
-
const capabilities = extension.capabilities || inferCapabilities(command.scope || "project", requiresLogin);
|
|
6054
|
-
const outputSchema = command.result ? summarizeZod(command.result) : void 0;
|
|
6055
|
-
return {
|
|
6056
|
-
name: command.name,
|
|
6057
|
-
description: command.description || "",
|
|
6058
|
-
scope: command.scope || "project",
|
|
6059
|
-
requiresLogin,
|
|
6060
|
-
category: extension.category,
|
|
6061
|
-
capabilities,
|
|
6062
|
-
positional,
|
|
6063
|
-
form: {
|
|
6064
|
-
title: extension.form?.title || command.description || command.name,
|
|
6065
|
-
description: extension.form?.description,
|
|
6066
|
-
submitLabel: extension.form?.submitLabel || "Run",
|
|
6067
|
-
fields
|
|
6068
|
-
},
|
|
6069
|
-
output: extension.output || (outputSchema ? { schema: outputSchema } : void 0)
|
|
6070
|
-
};
|
|
6071
|
-
}
|
|
6072
|
-
function fieldsFromZodObject(schema) {
|
|
6073
|
-
const reflected = fieldsFromZodObjectReflected(schema);
|
|
6074
|
-
return reflected.map((field) => {
|
|
6075
|
-
const widget = widgetFor(field.type, field.enum);
|
|
6076
|
-
return {
|
|
6077
|
-
name: field.name,
|
|
6078
|
-
label: toLabel(field.name),
|
|
6079
|
-
type: field.type,
|
|
6080
|
-
widget,
|
|
6081
|
-
required: field.required,
|
|
6082
|
-
...field.description ? { description: field.description } : {},
|
|
6083
|
-
...field.default !== void 0 ? { default: field.default } : {},
|
|
6084
|
-
...field.enum ? { enum: field.enum } : {},
|
|
6085
|
-
...field.type === "array" ? { multiple: true } : {}
|
|
6086
|
-
};
|
|
6087
|
-
});
|
|
6088
|
-
}
|
|
6089
|
-
function mergeFields(inferred, overrides) {
|
|
6090
|
-
if (overrides.length === 0) return inferred;
|
|
6091
|
-
const byName = new Map(inferred.map((field) => [field.name, field]));
|
|
6092
|
-
const seen = /* @__PURE__ */ new Set();
|
|
6093
|
-
const merged = [];
|
|
6094
|
-
for (const override of overrides) {
|
|
6095
|
-
if (!override.name) continue;
|
|
6096
|
-
const base = byName.get(override.name) || {
|
|
6097
|
-
name: override.name,
|
|
6098
|
-
label: toLabel(override.name),
|
|
6099
|
-
type: "string",
|
|
6100
|
-
widget: "text",
|
|
6101
|
-
required: false
|
|
6102
|
-
};
|
|
6103
|
-
merged.push({ ...base, ...override, name: override.name });
|
|
6104
|
-
seen.add(override.name);
|
|
6105
|
-
}
|
|
6106
|
-
for (const field of inferred) {
|
|
6107
|
-
if (!seen.has(field.name)) merged.push(field);
|
|
6108
|
-
}
|
|
6109
|
-
return merged;
|
|
6110
|
-
}
|
|
6111
|
-
function inferCapabilities(scope, requiresLogin) {
|
|
6112
|
-
const caps = [];
|
|
6113
|
-
if (scope === "page") caps.push("browser.page");
|
|
6114
|
-
if (scope === "browser") caps.push("browser.context");
|
|
6115
|
-
if (requiresLogin) caps.push("auth.login");
|
|
6116
|
-
return caps;
|
|
6117
|
-
}
|
|
6118
|
-
function widgetFor(type, enumValues) {
|
|
6119
|
-
if (enumValues) return "select";
|
|
6120
|
-
if (type === "boolean") return "checkbox";
|
|
6121
|
-
if (type === "number") return "number";
|
|
6122
|
-
if (type === "array") return "multi-select";
|
|
6123
|
-
if (type === "object") return "json";
|
|
6124
|
-
return "text";
|
|
6125
|
-
}
|
|
6126
|
-
function summarizeZod(schema) {
|
|
6127
|
-
const unwrapped = unwrapZod(schema);
|
|
6128
|
-
if (unwrapped.typeName === "ZodArray") {
|
|
6129
|
-
const def = unwrapped.schema?._def;
|
|
6130
|
-
return {
|
|
6131
|
-
type: "array",
|
|
6132
|
-
items: summarizeZod(def?.type || def?.innerType)
|
|
6133
|
-
};
|
|
6134
|
-
}
|
|
6135
|
-
const shape = getObjectShape(schema);
|
|
6136
|
-
if (!shape) {
|
|
6137
|
-
return {
|
|
6138
|
-
type: zodTypeToContractType(unwrapped.typeName),
|
|
6139
|
-
required: !unwrapped.optional,
|
|
6140
|
-
...unwrapped.description ? { description: unwrapped.description } : {}
|
|
6141
|
-
};
|
|
6142
|
-
}
|
|
6143
|
-
return Object.fromEntries(
|
|
6144
|
-
Object.entries(shape).map(([name, field]) => {
|
|
6145
|
-
const inner = unwrapZod(field);
|
|
6146
|
-
return [name, {
|
|
6147
|
-
type: zodTypeToContractType(inner.typeName),
|
|
6148
|
-
required: !inner.optional,
|
|
6149
|
-
...inner.description ? { description: inner.description } : {}
|
|
6150
|
-
}];
|
|
6151
|
-
})
|
|
6152
|
-
);
|
|
6153
|
-
}
|
|
6154
|
-
function getObjectShape(schema) {
|
|
6155
|
-
const zod = schema;
|
|
6156
|
-
const shapeOrFn = zod?.shape ?? zod?._def?.shape;
|
|
6157
|
-
if (!shapeOrFn) return void 0;
|
|
6158
|
-
return typeof shapeOrFn === "function" ? shapeOrFn() : shapeOrFn;
|
|
6159
|
-
}
|
|
6160
|
-
function toLabel(name) {
|
|
6161
|
-
return name.replace(/([A-Z])/g, " $1").replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim().replace(/^./, (char) => char.toUpperCase());
|
|
6162
|
-
}
|
|
6163
|
-
|
|
6164
|
-
// src/plugin/login-required-patch.ts
|
|
6165
|
-
import { SiteInstanceImpl } from "@dyyz1993/xcli-core";
|
|
6166
|
-
var patched = false;
|
|
6167
|
-
function patchLoginRequired() {
|
|
6168
|
-
if (patched) return;
|
|
6169
|
-
patched = true;
|
|
6170
|
-
const target = SiteInstanceImpl.prototype;
|
|
6171
|
-
const originalCommand = target.command;
|
|
6172
|
-
const wrapped = function(...args) {
|
|
6173
|
-
const result = originalCommand.apply(this, args);
|
|
6174
|
-
const [name, cmd] = args;
|
|
6175
|
-
const loginRequired = cmd.loginRequired;
|
|
6176
|
-
if (loginRequired) {
|
|
6177
|
-
const commands = this.commands;
|
|
6178
|
-
const entry = commands?.get(name);
|
|
6179
|
-
if (entry) {
|
|
6180
|
-
entry.loginRequired = loginRequired;
|
|
6181
|
-
}
|
|
6182
|
-
}
|
|
6183
|
-
return result;
|
|
6184
|
-
};
|
|
6185
|
-
Object.defineProperty(target, "command", {
|
|
6186
|
-
value: wrapped,
|
|
6187
|
-
writable: true,
|
|
6188
|
-
configurable: true
|
|
6189
|
-
});
|
|
6190
|
-
}
|
|
6191
|
-
|
|
6192
|
-
// src/plugin/loader.ts
|
|
6193
|
-
var DEFAULT_PLUGIN_DIRS = [".xcli/plugins", "../.xcli/plugins"];
|
|
6194
|
-
var XBrowserPluginLoader = class {
|
|
6195
|
-
core;
|
|
6196
|
-
loader;
|
|
6197
|
-
options;
|
|
6198
|
-
constructor(options) {
|
|
6199
|
-
patchLoginRequired();
|
|
6200
|
-
this.options = options ?? {};
|
|
6201
|
-
const cwd = this.options.cwd || process.cwd();
|
|
6202
|
-
const coreConfig = {
|
|
6203
|
-
name: "xbrowser",
|
|
6204
|
-
version: "0.1.0",
|
|
6205
|
-
description: "Browser automation CLI",
|
|
6206
|
-
configDirName: ".xbrowser",
|
|
6207
|
-
envPrefix: "XBROWSER",
|
|
6208
|
-
pluginDirs: [
|
|
6209
|
-
...DEFAULT_PLUGIN_DIRS,
|
|
6210
|
-
resolve2(cwd, ".xcli/plugins")
|
|
6211
|
-
]
|
|
6212
|
-
};
|
|
6213
|
-
this.core = new Core(coreConfig);
|
|
6214
|
-
this.loader = this.core.loader;
|
|
6215
|
-
}
|
|
6216
|
-
getAPI() {
|
|
6217
|
-
return this.loader.getAPI();
|
|
6218
|
-
}
|
|
6219
|
-
/**
|
|
6220
|
-
* Get the core instance for external use.
|
|
6221
|
-
* @returns The xcli-core Core instance.
|
|
6222
|
-
*/
|
|
6223
|
-
getCore() {
|
|
6224
|
-
return this.core;
|
|
6225
|
-
}
|
|
6226
|
-
getPlugin(id) {
|
|
6227
|
-
return this.loader.getPlugin(id);
|
|
6228
|
-
}
|
|
6229
|
-
getPluginStatus(id) {
|
|
6230
|
-
return this.loader.getPluginStatus(id);
|
|
6231
|
-
}
|
|
6232
|
-
getLoadedPlugins() {
|
|
6233
|
-
return this.loader.getLoadedPlugins();
|
|
6234
|
-
}
|
|
6235
|
-
getPluginContract(siteName, commandName) {
|
|
6236
|
-
const site = this.core.loader.getSite(siteName);
|
|
6237
|
-
if (!site) return void 0;
|
|
6238
|
-
const contract = buildPluginContract(site);
|
|
6239
|
-
if (!commandName) return contract;
|
|
6240
|
-
return contract.commands.find((command) => command.name === commandName);
|
|
6241
|
-
}
|
|
6242
|
-
async loadPlugin(pluginPath, id) {
|
|
6243
|
-
return this.loader.loadPlugin(pluginPath, id);
|
|
6244
|
-
}
|
|
6245
|
-
async unloadPlugin(id) {
|
|
6246
|
-
return this.loader.unloadPlugin(id);
|
|
6247
|
-
}
|
|
6248
|
-
async reloadPlugin(id) {
|
|
6249
|
-
return this.loader.reloadPlugin(id);
|
|
6250
|
-
}
|
|
6251
|
-
async loadFromFunction(setup) {
|
|
6252
|
-
return this.loader.loadFromFunction(setup);
|
|
6253
|
-
}
|
|
6254
|
-
async scanAndLoad() {
|
|
6255
|
-
const cwd = this.options.cwd || process.cwd();
|
|
6256
|
-
const globalDir = this.options.globalDir || resolve2(homedir5(), ".xbrowser/plugins");
|
|
6257
|
-
ensurePluginDependencies(globalDir);
|
|
6258
|
-
const dirs = [
|
|
6259
|
-
resolve2(cwd, ".xcli/plugins"),
|
|
6260
|
-
resolve2(cwd, "../.xcli/plugins"),
|
|
6261
|
-
this.options.userDir || resolve2(homedir5(), ".xcli/plugins"),
|
|
6262
|
-
globalDir
|
|
6263
|
-
];
|
|
6264
|
-
const loaded = [];
|
|
6265
|
-
const seen = /* @__PURE__ */ new Set();
|
|
6266
|
-
for (const dir of dirs) {
|
|
6267
|
-
if (!existsSync4(dir)) continue;
|
|
6268
|
-
const entries = readdirSync(dir, { withFileTypes: true });
|
|
6269
|
-
for (const entry of entries) {
|
|
6270
|
-
if (!entry.isDirectory()) continue;
|
|
6271
|
-
if (seen.has(entry.name)) continue;
|
|
6272
|
-
seen.add(entry.name);
|
|
6273
|
-
const pluginDir = resolve2(dir, entry.name);
|
|
6274
|
-
let indexPath = resolve2(pluginDir, "index.js");
|
|
6275
|
-
if (!existsSync4(indexPath)) {
|
|
6276
|
-
indexPath = resolve2(pluginDir, "index.ts");
|
|
6277
|
-
}
|
|
6278
|
-
if (!existsSync4(indexPath)) continue;
|
|
6279
|
-
try {
|
|
6280
|
-
if (!existsSync4(resolve2(pluginDir, "package.json"))) {
|
|
6281
|
-
console.warn(`\u26A0\uFE0F Plugin "${entry.name}" has no package.json. Use "xbrowser create ${entry.name} --template static" for proper structure.`);
|
|
6282
|
-
} else {
|
|
6283
|
-
const metadata = PluginMetadataParser.parseFromPackageJson(pluginDir);
|
|
6284
|
-
if (!metadata) {
|
|
6285
|
-
console.warn(`\u26A0\uFE0F Plugin "${entry.name}" has package.json but no xbrowser metadata. Add { "xbrowser": { "description": "..." } } to package.json.`);
|
|
6286
|
-
}
|
|
6287
|
-
}
|
|
6288
|
-
const instance = await this.loadPlugin(indexPath, entry.name);
|
|
6289
|
-
loaded.push(instance);
|
|
6290
|
-
} catch (err) {
|
|
6291
|
-
if (process.env.XBROWSER_DEBUG) {
|
|
6292
|
-
console.warn(`\u26A0\uFE0F Plugin "${entry.name}" load failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
6293
|
-
}
|
|
6294
|
-
}
|
|
6295
|
-
}
|
|
6296
|
-
}
|
|
6297
|
-
return loaded;
|
|
6298
|
-
}
|
|
6299
|
-
async unload() {
|
|
6300
|
-
return this.loader.unload();
|
|
6301
|
-
}
|
|
6302
|
-
};
|
|
6303
|
-
|
|
6304
|
-
// src/utils/plugin-singleton.ts
|
|
6305
|
-
var pluginLoader = null;
|
|
6306
|
-
var pluginsScanned = false;
|
|
6307
|
-
async function getPluginLoader() {
|
|
6308
|
-
if (!pluginLoader) {
|
|
6309
|
-
pluginLoader = new XBrowserPluginLoader();
|
|
6310
|
-
}
|
|
6311
|
-
if (!pluginsScanned) {
|
|
6312
|
-
await pluginLoader.scanAndLoad();
|
|
6313
|
-
pluginsScanned = true;
|
|
6314
|
-
}
|
|
6315
|
-
return pluginLoader;
|
|
6316
|
-
}
|
|
6317
|
-
|
|
6318
5894
|
// src/utils/viewer-url.ts
|
|
6319
5895
|
function buildViewerUrl(sessionName = "default") {
|
|
6320
5896
|
try {
|
|
@@ -6798,7 +6374,7 @@ var TipsManager = class {
|
|
|
6798
6374
|
}
|
|
6799
6375
|
}
|
|
6800
6376
|
debounce() {
|
|
6801
|
-
return new Promise((
|
|
6377
|
+
return new Promise((resolve) => setTimeout(resolve, DEBOUNCE_MS));
|
|
6802
6378
|
}
|
|
6803
6379
|
formatTips(tips) {
|
|
6804
6380
|
return tips.map((tip) => {
|
|
@@ -6915,11 +6491,11 @@ async function loadHooks() {
|
|
|
6915
6491
|
}
|
|
6916
6492
|
|
|
6917
6493
|
// src/executor.ts
|
|
6918
|
-
import { homedir as
|
|
6919
|
-
import { join as
|
|
6494
|
+
import { homedir as homedir5 } from "os";
|
|
6495
|
+
import { join as join5 } from "path";
|
|
6920
6496
|
var NAVIGATION_COMMANDS = /* @__PURE__ */ new Set(["goto", "back", "forward", "refresh"]);
|
|
6921
6497
|
var snapshotHintShown = /* @__PURE__ */ new WeakSet();
|
|
6922
|
-
var CONFIG_DIR2 =
|
|
6498
|
+
var CONFIG_DIR2 = join5(homedir5(), ".xbrowser");
|
|
6923
6499
|
var storageCache = /* @__PURE__ */ new Map();
|
|
6924
6500
|
function getPluginStorage(pluginName) {
|
|
6925
6501
|
if (!storageCache.has(pluginName)) {
|
|
@@ -6931,7 +6507,7 @@ var archiveInitialized = false;
|
|
|
6931
6507
|
function ensureArchiveInit() {
|
|
6932
6508
|
if (!archiveInitialized) {
|
|
6933
6509
|
try {
|
|
6934
|
-
configureArchiveStore({ archiveDir:
|
|
6510
|
+
configureArchiveStore({ archiveDir: join5(homedir5(), ".xbrowser", "archives") });
|
|
6935
6511
|
} catch {
|
|
6936
6512
|
}
|
|
6937
6513
|
archiveInitialized = true;
|
|
@@ -7005,7 +6581,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7005
6581
|
params = result.data;
|
|
7006
6582
|
}
|
|
7007
6583
|
if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
7008
|
-
const { forwardExec } = await import("./daemon-client-
|
|
6584
|
+
const { forwardExec } = await import("./daemon-client-COJQESU2.js");
|
|
7009
6585
|
const result = await forwardExec(commandName, params, sessionName, extraOpts?.cdpEndpoint);
|
|
7010
6586
|
if (result) return result;
|
|
7011
6587
|
}
|
|
@@ -7688,10 +7264,10 @@ async function replayEntry(entry, options = {}) {
|
|
|
7688
7264
|
}
|
|
7689
7265
|
|
|
7690
7266
|
// src/daemon/feedback-store.ts
|
|
7691
|
-
import { readFileSync as
|
|
7692
|
-
import { join as
|
|
7693
|
-
import { homedir as
|
|
7694
|
-
var FEEDBACK_FILE =
|
|
7267
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
|
|
7268
|
+
import { join as join6 } from "path";
|
|
7269
|
+
import { homedir as homedir6 } from "os";
|
|
7270
|
+
var FEEDBACK_FILE = join6(homedir6(), ".xbrowser", "feedback.json");
|
|
7695
7271
|
var FeedbackStore = class {
|
|
7696
7272
|
entries = [];
|
|
7697
7273
|
constructor() {
|
|
@@ -7699,7 +7275,7 @@ var FeedbackStore = class {
|
|
|
7699
7275
|
}
|
|
7700
7276
|
load() {
|
|
7701
7277
|
try {
|
|
7702
|
-
const data =
|
|
7278
|
+
const data = readFileSync3(FEEDBACK_FILE, "utf8");
|
|
7703
7279
|
this.entries = JSON.parse(data);
|
|
7704
7280
|
} catch {
|
|
7705
7281
|
this.entries = [];
|
|
@@ -7707,8 +7283,8 @@ var FeedbackStore = class {
|
|
|
7707
7283
|
}
|
|
7708
7284
|
save() {
|
|
7709
7285
|
try {
|
|
7710
|
-
|
|
7711
|
-
|
|
7286
|
+
mkdirSync4(join6(homedir6(), ".xbrowser"), { recursive: true });
|
|
7287
|
+
writeFileSync4(FEEDBACK_FILE, JSON.stringify(this.entries, null, 2));
|
|
7712
7288
|
} catch {
|
|
7713
7289
|
}
|
|
7714
7290
|
}
|
|
@@ -8006,7 +7582,7 @@ var PlaybackEngine = class _PlaybackEngine {
|
|
|
8006
7582
|
// src/daemon/rpc-handlers.ts
|
|
8007
7583
|
var activeRecorders = /* @__PURE__ */ new Map();
|
|
8008
7584
|
var replayResumeResolvers = /* @__PURE__ */ new Map();
|
|
8009
|
-
var CONFIG_DIR3 =
|
|
7585
|
+
var CONFIG_DIR3 = join7(homedir7(), ".xbrowser");
|
|
8010
7586
|
var RECORDING_INJECT_JS = `
|
|
8011
7587
|
(function(){
|
|
8012
7588
|
if(window.__xb_rec) return;
|
|
@@ -8098,6 +7674,8 @@ function createRPCHandler() {
|
|
|
8098
7674
|
// ── Utility ──
|
|
8099
7675
|
case "ping":
|
|
8100
7676
|
return { ok: true, pid: process.pid };
|
|
7677
|
+
case "plugins:reload":
|
|
7678
|
+
return handlePluginsReload();
|
|
8101
7679
|
// ── Network analysis ──
|
|
8102
7680
|
case "network:list":
|
|
8103
7681
|
return handleNetworkList(params);
|
|
@@ -8284,6 +7862,13 @@ function createRPCHandler() {
|
|
|
8284
7862
|
registerSessionIfNew(sessionName);
|
|
8285
7863
|
return result;
|
|
8286
7864
|
}
|
|
7865
|
+
async function handlePluginsReload() {
|
|
7866
|
+
const { resetPluginLoader } = await import("./plugin-singleton-ZBVTWEYK.js");
|
|
7867
|
+
resetPluginLoader();
|
|
7868
|
+
const loader = await import("./plugin-singleton-ZBVTWEYK.js").then((m) => m.getPluginLoader());
|
|
7869
|
+
const sites = loader.getCore().loader.getSites();
|
|
7870
|
+
return { ok: true, plugins: sites.length };
|
|
7871
|
+
}
|
|
8287
7872
|
async function handleAgentObserve(params) {
|
|
8288
7873
|
const sessionName = params.session || "default";
|
|
8289
7874
|
const commandParams = {
|
|
@@ -8454,10 +8039,10 @@ function createRPCHandler() {
|
|
|
8454
8039
|
if (!sess) return { ok: false, error: "No session" };
|
|
8455
8040
|
try {
|
|
8456
8041
|
const events = await sess.page.evaluate(() => window.__xb_evts || []);
|
|
8457
|
-
const recordingsDir =
|
|
8458
|
-
|
|
8459
|
-
const outPath = params.path ||
|
|
8460
|
-
|
|
8042
|
+
const recordingsDir = join7(CONFIG_DIR3, "recordings");
|
|
8043
|
+
mkdirSync5(recordingsDir, { recursive: true });
|
|
8044
|
+
const outPath = params.path || join7(recordingsDir, `recording-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.json`);
|
|
8045
|
+
writeFileSync5(outPath, JSON.stringify({
|
|
8461
8046
|
startUrl: sess.page.url(),
|
|
8462
8047
|
recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8463
8048
|
events
|
|
@@ -8557,13 +8142,19 @@ function createRPCHandler() {
|
|
|
8557
8142
|
}
|
|
8558
8143
|
async function handleRecordStop(params) {
|
|
8559
8144
|
const sessionName = params.session || "default";
|
|
8145
|
+
const outputPath = params.output;
|
|
8560
8146
|
const recorder = activeRecorders.get(sessionName);
|
|
8561
8147
|
if (!recorder) {
|
|
8562
8148
|
const existingData = SessionRecorder.readData(sessionName);
|
|
8563
8149
|
if (existingData) {
|
|
8150
|
+
if (outputPath) {
|
|
8151
|
+
const { writeFileSync: writeFileSync7 } = await import("fs");
|
|
8152
|
+
writeFileSync7(outputPath, JSON.stringify(existingData, null, 2), "utf-8");
|
|
8153
|
+
}
|
|
8564
8154
|
return {
|
|
8565
8155
|
ok: true,
|
|
8566
8156
|
message: "Recorder process already exited. Recording data found on disk.",
|
|
8157
|
+
output: outputPath,
|
|
8567
8158
|
session: sessionName,
|
|
8568
8159
|
actions: existingData.actions.length,
|
|
8569
8160
|
network: existingData.network.length
|
|
@@ -8574,8 +8165,20 @@ function createRPCHandler() {
|
|
|
8574
8165
|
try {
|
|
8575
8166
|
const { data, summary } = await recorder.stop();
|
|
8576
8167
|
activeRecorders.delete(sessionName);
|
|
8168
|
+
if (outputPath) {
|
|
8169
|
+
const { writeFileSync: writeFileSync7, mkdirSync: mkdirSync7 } = await import("fs");
|
|
8170
|
+
const { dirname: dirname2 } = await import("path");
|
|
8171
|
+
mkdirSync7(dirname2(outputPath), { recursive: true });
|
|
8172
|
+
if (outputPath.endsWith(".yaml") || outputPath.endsWith(".yml")) {
|
|
8173
|
+
const yaml2 = (await import("yaml")).default;
|
|
8174
|
+
writeFileSync7(outputPath, yaml2.stringify(data), "utf-8");
|
|
8175
|
+
} else {
|
|
8176
|
+
writeFileSync7(outputPath, JSON.stringify(data, null, 2), "utf-8");
|
|
8177
|
+
}
|
|
8178
|
+
}
|
|
8577
8179
|
return {
|
|
8578
8180
|
ok: true,
|
|
8181
|
+
output: outputPath,
|
|
8579
8182
|
session: sessionName,
|
|
8580
8183
|
actions: data.actions.length,
|
|
8581
8184
|
network: data.network.length,
|
|
@@ -8664,8 +8267,13 @@ function createRPCHandler() {
|
|
|
8664
8267
|
let rawContent;
|
|
8665
8268
|
let parsed;
|
|
8666
8269
|
try {
|
|
8667
|
-
rawContent =
|
|
8668
|
-
|
|
8270
|
+
rawContent = readFileSync5(file, "utf8");
|
|
8271
|
+
try {
|
|
8272
|
+
parsed = JSON.parse(rawContent);
|
|
8273
|
+
} catch {
|
|
8274
|
+
const yaml2 = (await import("yaml")).default;
|
|
8275
|
+
parsed = yaml2.parse(rawContent);
|
|
8276
|
+
}
|
|
8669
8277
|
} catch (e) {
|
|
8670
8278
|
return { ok: false, success: false, duration: 0, eventsPlayed: 0, totalEvents: 0, errors: [{ eventIndex: -1, error: "Failed to read/parse file: " + String(e) }] };
|
|
8671
8279
|
}
|
|
@@ -8708,8 +8316,8 @@ function createRPCHandler() {
|
|
|
8708
8316
|
const result = await engine.play({
|
|
8709
8317
|
slowMo,
|
|
8710
8318
|
onCheckpoint: async (checkpoint) => {
|
|
8711
|
-
return new Promise((
|
|
8712
|
-
replayResumeResolvers.set(sessionName, () =>
|
|
8319
|
+
return new Promise((resolve) => {
|
|
8320
|
+
replayResumeResolvers.set(sessionName, () => resolve(true));
|
|
8713
8321
|
console.log(`[replay] Checkpoint reached: [${checkpoint.type}] ${checkpoint.hint}`);
|
|
8714
8322
|
console.log('[replay] Send "replay:resume" RPC to continue.');
|
|
8715
8323
|
});
|
|
@@ -9773,13 +9381,13 @@ var FileListHandler = class {
|
|
|
9773
9381
|
async handle(ctx) {
|
|
9774
9382
|
const msg = ctx.message;
|
|
9775
9383
|
try {
|
|
9776
|
-
const { readdirSync
|
|
9777
|
-
const { join:
|
|
9778
|
-
const targetPath =
|
|
9779
|
-
const entries =
|
|
9384
|
+
const { readdirSync, statSync } = await import("fs");
|
|
9385
|
+
const { join: join9, resolve } = await import("path");
|
|
9386
|
+
const targetPath = resolve(msg.path);
|
|
9387
|
+
const entries = readdirSync(targetPath);
|
|
9780
9388
|
const files = entries.map((name) => {
|
|
9781
9389
|
try {
|
|
9782
|
-
const stat = statSync(
|
|
9390
|
+
const stat = statSync(join9(targetPath, name));
|
|
9783
9391
|
return { name, isDir: stat.isDirectory(), size: stat.size, modified: stat.mtime.toISOString() };
|
|
9784
9392
|
} catch {
|
|
9785
9393
|
return { name, isDir: false, size: 0, modified: "" };
|
|
@@ -9796,10 +9404,10 @@ var FileDownloadHandler = class {
|
|
|
9796
9404
|
async handle(ctx) {
|
|
9797
9405
|
const msg = ctx.message;
|
|
9798
9406
|
try {
|
|
9799
|
-
const { readFileSync:
|
|
9800
|
-
const { resolve
|
|
9801
|
-
const targetPath =
|
|
9802
|
-
const data =
|
|
9407
|
+
const { readFileSync: readFileSync6 } = await import("fs");
|
|
9408
|
+
const { resolve, basename } = await import("path");
|
|
9409
|
+
const targetPath = resolve(msg.path);
|
|
9410
|
+
const data = readFileSync6(targetPath);
|
|
9803
9411
|
const base64 = data.toString("base64");
|
|
9804
9412
|
const ext = targetPath.split(".").pop()?.toLowerCase() || "";
|
|
9805
9413
|
const mimeMap = {
|
|
@@ -10103,7 +9711,7 @@ var WSServer = class extends EventEmitter2 {
|
|
|
10103
9711
|
this.isRunning = false;
|
|
10104
9712
|
return;
|
|
10105
9713
|
}
|
|
10106
|
-
return new Promise((
|
|
9714
|
+
return new Promise((resolve, reject) => {
|
|
10107
9715
|
this.wsServer.close((err) => {
|
|
10108
9716
|
if (err) {
|
|
10109
9717
|
reject(err);
|
|
@@ -10111,7 +9719,7 @@ var WSServer = class extends EventEmitter2 {
|
|
|
10111
9719
|
this.wsServer = null;
|
|
10112
9720
|
this.isRunning = false;
|
|
10113
9721
|
this.emit("stopped");
|
|
10114
|
-
|
|
9722
|
+
resolve();
|
|
10115
9723
|
}
|
|
10116
9724
|
});
|
|
10117
9725
|
});
|
|
@@ -11440,8 +11048,8 @@ connectWS();
|
|
|
11440
11048
|
}
|
|
11441
11049
|
|
|
11442
11050
|
// src/daemon/daemon-main.ts
|
|
11443
|
-
var CONFIG_DIR4 =
|
|
11444
|
-
var LOG_FILE =
|
|
11051
|
+
var CONFIG_DIR4 = join8(homedir8(), ".xbrowser");
|
|
11052
|
+
var LOG_FILE = join8(CONFIG_DIR4, "daemon.log");
|
|
11445
11053
|
function log(msg) {
|
|
11446
11054
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19);
|
|
11447
11055
|
const line = `[DAEMON ${ts}] ${msg}
|
|
@@ -11473,7 +11081,7 @@ async function main() {
|
|
|
11473
11081
|
if (err.code === "EADDRINUSE") {
|
|
11474
11082
|
log(`Port ${daemonPort} already in use \u2014 another daemon instance likely won the startup race. Exiting gracefully.`);
|
|
11475
11083
|
try {
|
|
11476
|
-
unlinkSync(
|
|
11084
|
+
unlinkSync(join8(CONFIG_DIR4, "daemon.json"));
|
|
11477
11085
|
} catch {
|
|
11478
11086
|
}
|
|
11479
11087
|
process.exit(0);
|
|
@@ -11507,8 +11115,8 @@ async function main() {
|
|
|
11507
11115
|
rpcHandler.setPreviewWS(previewWS);
|
|
11508
11116
|
previewWS.on("screencast-started", (sid) => log(`Preview screencast started: ${sid}`));
|
|
11509
11117
|
previewWS.on("screencast-stopped", (sid) => log(`Preview screencast stopped: ${sid}`));
|
|
11510
|
-
|
|
11511
|
-
|
|
11118
|
+
mkdirSync6(CONFIG_DIR4, { recursive: true });
|
|
11119
|
+
writeFileSync6(join8(CONFIG_DIR4, "daemon.json"), JSON.stringify({
|
|
11512
11120
|
port: daemonPort,
|
|
11513
11121
|
pid: process.pid,
|
|
11514
11122
|
startedAt: Date.now()
|