@xbrowser/cli 0.14.3 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -1
- package/dist/cli.js +126 -99
- package/dist/daemon-main.js +67 -34
- package/dist/index.d.ts +2026 -5
- package/dist/index.js +124 -97
- package/package.json +19 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# xbrowser
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> **Browser automation CLI** for web scraping, headless browsing, SEO analysis, and AI agent workflows. 35+ commands, 67+ plugins. A command-line alternative to Playwright, Puppeteer, and Selenium — **no code required**.
|
|
4
4
|
|
|
5
5
|
[](https://github.com/dyyz1993/xbrowser/actions)
|
|
6
6
|
[](https://codecov.io/gh/dyyz1993/xbrowser)
|
|
@@ -18,6 +18,42 @@
|
|
|
18
18
|
- **会话管理** — 多会话并行,独立上下文
|
|
19
19
|
- **Daemon 模式** — 后台常驻,快速响应
|
|
20
20
|
|
|
21
|
+
## Use Cases
|
|
22
|
+
|
|
23
|
+
**Web Scraping & Data Extraction**
|
|
24
|
+
```bash
|
|
25
|
+
# Scrape any page to Markdown — no Playwright scripts needed
|
|
26
|
+
xbrowser scrape https://news.ycombinator.com --mode smart --output markdown
|
|
27
|
+
|
|
28
|
+
# Crawl an entire site
|
|
29
|
+
xbrowser crawl https://example.com --max-depth 3 --output json
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Search Engine Automation**
|
|
33
|
+
```bash
|
|
34
|
+
# Search Google, Bing, Baidu from the command line
|
|
35
|
+
xbrowser search "best headless browser CLI" --engine google --limit 10 --json
|
|
36
|
+
|
|
37
|
+
# Time-filtered search
|
|
38
|
+
xbrowser search "playwright alternative 2025" --engine bing --time-filter month
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Browser Automation for AI Agents**
|
|
42
|
+
```bash
|
|
43
|
+
# Record browser actions, replay in CI/CD
|
|
44
|
+
xbrowser record --output workflow.yaml
|
|
45
|
+
xbrowser replay workflow.yaml
|
|
46
|
+
|
|
47
|
+
# Chain commands for complex workflows
|
|
48
|
+
xbrowser "goto https://example.com && wait .content && text --selector .content"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**SEO & Backlink Automation**
|
|
52
|
+
```bash
|
|
53
|
+
# Auto-publish articles to 13+ SEO platforms
|
|
54
|
+
xbrowser publish --platform devto,juejin --file article.md
|
|
55
|
+
```
|
|
56
|
+
|
|
21
57
|
## 快速开始
|
|
22
58
|
|
|
23
59
|
### 安装
|
|
@@ -841,6 +877,32 @@ xbrowser/
|
|
|
841
877
|
└── docs/ # 文档
|
|
842
878
|
```
|
|
843
879
|
|
|
880
|
+
## xbrowser vs Other Browser Automation Tools
|
|
881
|
+
|
|
882
|
+
| Feature | xbrowser | Playwright | Puppeteer | Selenium |
|
|
883
|
+
|---------|----------|------------|-----------|----------|
|
|
884
|
+
| **Interface** | CLI (command line) | Node.js library | Node.js library | Multi-language library |
|
|
885
|
+
| **Setup** | `npm i -g` — 0 config | Install + browser download | Install + browser download | Install + WebDriver + drivers |
|
|
886
|
+
| **Web Scraping** | Built-in (`scrape`, `crawl`, `map`) | Write custom scripts | Write custom scripts | Write custom scripts |
|
|
887
|
+
| **Search** | Built-in multi-engine (`search`) | No | No | No |
|
|
888
|
+
| **Plugin Ecosystem** | 67+ plugins | Limited | Limited | No |
|
|
889
|
+
| **No Code Required** | ✅ CLI commands | ❌ Must write JS/TS | ❌ Must write JS/TS | ❌ Must write code |
|
|
890
|
+
| **Headless Mode** | ✅ Default | ✅ | ✅ | ✅ |
|
|
891
|
+
| **Record/Replay** | ✅ Built-in (`record`/`replay`) | ✅ Codegen | ❌ | ❌ |
|
|
892
|
+
| **Anti-Detection** | ✅ CDP firewall | ❌ | ❌ | ❌ |
|
|
893
|
+
| **AI Agent Integration** | ✅ Built-in plugins | Manual integration | Manual integration | Manual integration |
|
|
894
|
+
|
|
895
|
+
**When to use xbrowser:**
|
|
896
|
+
- You want to **automate browsers from the command line** without writing code
|
|
897
|
+
- You need **web scraping, SEO automation, or AI agent workflows**
|
|
898
|
+
- You want a **Playwright/Puppeteer alternative** that works with one-liners
|
|
899
|
+
- You're building **CLI tools that need browser automation**
|
|
900
|
+
|
|
901
|
+
**When to use Playwright/Puppeteer:**
|
|
902
|
+
- You're writing **test suites** for web applications
|
|
903
|
+
- You need **fine-grained programmatic control** of browser behavior
|
|
904
|
+
- You're building a **Node.js application** that embeds browser automation
|
|
905
|
+
|
|
844
906
|
## 与相关项目的关系
|
|
845
907
|
|
|
846
908
|
| 项目 | 定位 | 关系 |
|
package/dist/cli.js
CHANGED
|
@@ -132,7 +132,18 @@ function parsePluginParams(args, schema, base = {}) {
|
|
|
132
132
|
if (value === "true") params[key] = true;
|
|
133
133
|
else if (value === "false") params[key] = false;
|
|
134
134
|
else if (/^\d+$/.test(value)) params[key] = parseInt(value, 10);
|
|
135
|
-
else
|
|
135
|
+
else {
|
|
136
|
+
try {
|
|
137
|
+
const parsed = JSON.parse(value);
|
|
138
|
+
if (Array.isArray(parsed) || typeof parsed === "object" && parsed !== null) {
|
|
139
|
+
params[key] = parsed;
|
|
140
|
+
} else {
|
|
141
|
+
params[key] = value;
|
|
142
|
+
}
|
|
143
|
+
} catch {
|
|
144
|
+
params[key] = value;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
136
147
|
i++;
|
|
137
148
|
} else {
|
|
138
149
|
params[key] = true;
|
|
@@ -6225,8 +6236,48 @@ async function loadHooks() {
|
|
|
6225
6236
|
// src/executor.ts
|
|
6226
6237
|
import { homedir as homedir3 } from "os";
|
|
6227
6238
|
import { join as join3 } from "path";
|
|
6239
|
+
import { existsSync as existsSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
6228
6240
|
var NAVIGATION_COMMANDS = /* @__PURE__ */ new Set(["goto", "back", "forward", "refresh"]);
|
|
6229
6241
|
var snapshotHintShown = /* @__PURE__ */ new WeakSet();
|
|
6242
|
+
var STORAGE_DIR = join3(homedir3(), ".xbrowser", "storage");
|
|
6243
|
+
var storageCache = /* @__PURE__ */ new Map();
|
|
6244
|
+
function getPluginStorage(pluginName) {
|
|
6245
|
+
if (!storageCache.has(pluginName)) {
|
|
6246
|
+
const filePath = join3(STORAGE_DIR, `${pluginName}.json`);
|
|
6247
|
+
let data = {};
|
|
6248
|
+
const load3 = () => {
|
|
6249
|
+
if (existsSync6(filePath)) {
|
|
6250
|
+
try {
|
|
6251
|
+
data = JSON.parse(readFileSync10(filePath, "utf-8"));
|
|
6252
|
+
} catch {
|
|
6253
|
+
data = {};
|
|
6254
|
+
}
|
|
6255
|
+
}
|
|
6256
|
+
};
|
|
6257
|
+
const save = () => {
|
|
6258
|
+
mkdirSync3(STORAGE_DIR, { recursive: true });
|
|
6259
|
+
writeFileSync5(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
6260
|
+
};
|
|
6261
|
+
load3();
|
|
6262
|
+
storageCache.set(pluginName, {
|
|
6263
|
+
get: async (key) => data[key] ?? null,
|
|
6264
|
+
set: async (key, value) => {
|
|
6265
|
+
data[key] = value;
|
|
6266
|
+
save();
|
|
6267
|
+
},
|
|
6268
|
+
delete: async (key) => {
|
|
6269
|
+
delete data[key];
|
|
6270
|
+
save();
|
|
6271
|
+
},
|
|
6272
|
+
clear: async () => {
|
|
6273
|
+
data = {};
|
|
6274
|
+
save();
|
|
6275
|
+
},
|
|
6276
|
+
keys: async () => Object.keys(data)
|
|
6277
|
+
});
|
|
6278
|
+
}
|
|
6279
|
+
return storageCache.get(pluginName);
|
|
6280
|
+
}
|
|
6230
6281
|
var archiveInitialized = false;
|
|
6231
6282
|
function ensureArchiveInit() {
|
|
6232
6283
|
if (!archiveInitialized) {
|
|
@@ -6334,16 +6385,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6334
6385
|
args: [],
|
|
6335
6386
|
options: {},
|
|
6336
6387
|
cwd: process.cwd(),
|
|
6337
|
-
storage:
|
|
6338
|
-
get: async () => null,
|
|
6339
|
-
set: async () => {
|
|
6340
|
-
},
|
|
6341
|
-
delete: async () => {
|
|
6342
|
-
},
|
|
6343
|
-
clear: async () => {
|
|
6344
|
-
},
|
|
6345
|
-
keys: async () => []
|
|
6346
|
-
},
|
|
6388
|
+
storage: getPluginStorage(commandName),
|
|
6347
6389
|
output: {
|
|
6348
6390
|
mode: "text",
|
|
6349
6391
|
showTips: false,
|
|
@@ -6566,16 +6608,7 @@ async function executeChain(input, options) {
|
|
|
6566
6608
|
browser: session.context.browser(),
|
|
6567
6609
|
browserContext: session.context,
|
|
6568
6610
|
sessionId: session.id,
|
|
6569
|
-
storage:
|
|
6570
|
-
get: async (_key) => null,
|
|
6571
|
-
set: async (_key, _value) => {
|
|
6572
|
-
},
|
|
6573
|
-
delete: async (_key) => {
|
|
6574
|
-
},
|
|
6575
|
-
clear: async () => {
|
|
6576
|
-
},
|
|
6577
|
-
keys: async () => []
|
|
6578
|
-
},
|
|
6611
|
+
storage: getPluginStorage(cmdName),
|
|
6579
6612
|
output: { mode: "text", showTips: false, color: false, emoji: false },
|
|
6580
6613
|
error: (msg) => {
|
|
6581
6614
|
throw new Error(msg);
|
|
@@ -6597,7 +6630,7 @@ async function executeChain(input, options) {
|
|
|
6597
6630
|
const outputs = [];
|
|
6598
6631
|
for (const h of hooks) {
|
|
6599
6632
|
const output = await h.onAfterCommand?.({ page: session.page, command: `${cmdName} ${subCommand}`, params: pluginParams, result: raw, duration: duration2 });
|
|
6600
|
-
if (output) outputs.push(output);
|
|
6633
|
+
if (output) outputs.push({ _hook: h.name, ...output });
|
|
6601
6634
|
}
|
|
6602
6635
|
if (outputs.length > 0) hookOutputs = outputs;
|
|
6603
6636
|
}
|
|
@@ -6868,7 +6901,7 @@ var sessionKillBuiltin = {
|
|
|
6868
6901
|
};
|
|
6869
6902
|
|
|
6870
6903
|
// src/config.ts
|
|
6871
|
-
import { existsSync as
|
|
6904
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
6872
6905
|
import { join as join4 } from "path";
|
|
6873
6906
|
import { homedir as homedir4, tmpdir } from "os";
|
|
6874
6907
|
function getConfigFile() {
|
|
@@ -6876,14 +6909,14 @@ function getConfigFile() {
|
|
|
6876
6909
|
}
|
|
6877
6910
|
function loadConfig() {
|
|
6878
6911
|
const configFile = getConfigFile();
|
|
6879
|
-
if (!
|
|
6912
|
+
if (!existsSync7(configFile)) return {};
|
|
6880
6913
|
return readJsonFile(configFile, {});
|
|
6881
6914
|
}
|
|
6882
6915
|
function saveConfig(config) {
|
|
6883
6916
|
const dir = join4(homedir4() || tmpdir(), ".xbrowser");
|
|
6884
6917
|
const configFile = getConfigFile();
|
|
6885
|
-
if (!
|
|
6886
|
-
|
|
6918
|
+
if (!existsSync7(dir)) mkdirSync4(dir, { recursive: true });
|
|
6919
|
+
writeFileSync6(configFile, JSON.stringify(config, null, 2), "utf-8");
|
|
6887
6920
|
}
|
|
6888
6921
|
function getConfigValue(key) {
|
|
6889
6922
|
return loadConfig()[key];
|
|
@@ -6991,26 +7024,26 @@ var configBuiltin = {
|
|
|
6991
7024
|
|
|
6992
7025
|
// src/plugin/installer.ts
|
|
6993
7026
|
import {
|
|
6994
|
-
existsSync as
|
|
7027
|
+
existsSync as existsSync14,
|
|
6995
7028
|
readdirSync as readdirSync3,
|
|
6996
|
-
mkdirSync as
|
|
7029
|
+
mkdirSync as mkdirSync9,
|
|
6997
7030
|
rmSync as rmSync7
|
|
6998
7031
|
} from "fs";
|
|
6999
7032
|
import { resolve as resolve15, basename as basename2 } from "path";
|
|
7000
7033
|
import { homedir as homedir5 } from "os";
|
|
7001
7034
|
|
|
7002
7035
|
// src/plugin/install-sources/local.ts
|
|
7003
|
-
import { existsSync as
|
|
7036
|
+
import { existsSync as existsSync9, cpSync as cpSync2, rmSync as rmSync2 } from "fs";
|
|
7004
7037
|
import { resolve as resolve10 } from "path";
|
|
7005
7038
|
|
|
7006
7039
|
// src/plugin/install-utils.ts
|
|
7007
7040
|
import {
|
|
7008
|
-
existsSync as
|
|
7041
|
+
existsSync as existsSync8,
|
|
7009
7042
|
readdirSync as readdirSync2,
|
|
7010
7043
|
cpSync,
|
|
7011
7044
|
rmSync,
|
|
7012
|
-
mkdirSync as
|
|
7013
|
-
readFileSync as
|
|
7045
|
+
mkdirSync as mkdirSync5,
|
|
7046
|
+
readFileSync as readFileSync11,
|
|
7014
7047
|
createWriteStream
|
|
7015
7048
|
} from "fs";
|
|
7016
7049
|
import { resolve as resolve9 } from "path";
|
|
@@ -7081,7 +7114,7 @@ async function downloadToFile(url, destPath) {
|
|
|
7081
7114
|
await pipeline(nodeStream, createWriteStream(destPath));
|
|
7082
7115
|
}
|
|
7083
7116
|
function extractTarGz(tarballPath, targetDir) {
|
|
7084
|
-
|
|
7117
|
+
mkdirSync5(targetDir, { recursive: true });
|
|
7085
7118
|
execSync7(`tar -xzf "${tarballPath}" -C "${targetDir}"`, { stdio: "pipe" });
|
|
7086
7119
|
}
|
|
7087
7120
|
function flattenPackageRoot(targetDir) {
|
|
@@ -7102,18 +7135,18 @@ function flattenPackageRoot(targetDir) {
|
|
|
7102
7135
|
async function verifyPlugin(dir) {
|
|
7103
7136
|
const warnings = [];
|
|
7104
7137
|
const indexPath = resolve9(dir, "index.ts");
|
|
7105
|
-
if (!
|
|
7138
|
+
if (!existsSync8(indexPath)) {
|
|
7106
7139
|
const indexJs = resolve9(dir, "index.js");
|
|
7107
|
-
if (!
|
|
7140
|
+
if (!existsSync8(indexJs)) {
|
|
7108
7141
|
return { valid: false, error: "No index.ts or index.js entry point found", warnings };
|
|
7109
7142
|
}
|
|
7110
7143
|
}
|
|
7111
7144
|
const pkgPath = resolve9(dir, "package.json");
|
|
7112
|
-
if (!
|
|
7145
|
+
if (!existsSync8(pkgPath)) {
|
|
7113
7146
|
warnings.push("No package.json found");
|
|
7114
7147
|
} else {
|
|
7115
7148
|
try {
|
|
7116
|
-
const pkg2 = JSON.parse(
|
|
7149
|
+
const pkg2 = JSON.parse(readFileSync11(pkgPath, "utf-8"));
|
|
7117
7150
|
if (!pkg2.xbrowser) {
|
|
7118
7151
|
warnings.push("No xbrowser metadata in package.json");
|
|
7119
7152
|
}
|
|
@@ -7133,7 +7166,7 @@ function safeCleanup(dir) {
|
|
|
7133
7166
|
// src/plugin/install-sources/local.ts
|
|
7134
7167
|
async function installFromLocal(source, name, targetDir) {
|
|
7135
7168
|
const srcPath = resolve10(source);
|
|
7136
|
-
if (!
|
|
7169
|
+
if (!existsSync9(srcPath)) {
|
|
7137
7170
|
throw new Error(`Local path does not exist: ${srcPath}`);
|
|
7138
7171
|
}
|
|
7139
7172
|
const tmpTarget = `${targetDir}-tmp-${Date.now()}`;
|
|
@@ -7146,7 +7179,7 @@ async function installFromLocal(source, name, targetDir) {
|
|
|
7146
7179
|
safeCleanup(tmpTarget);
|
|
7147
7180
|
throw new Error(`Invalid plugin: ${verify.error}`);
|
|
7148
7181
|
}
|
|
7149
|
-
if (
|
|
7182
|
+
if (existsSync9(targetDir)) {
|
|
7150
7183
|
rmSync2(targetDir, { recursive: true, force: true });
|
|
7151
7184
|
}
|
|
7152
7185
|
cpSync2(tmpTarget, targetDir, { recursive: true, force: true });
|
|
@@ -7166,7 +7199,7 @@ async function installFromLocal(source, name, targetDir) {
|
|
|
7166
7199
|
}
|
|
7167
7200
|
|
|
7168
7201
|
// src/plugin/install-sources/npm.ts
|
|
7169
|
-
import { existsSync as
|
|
7202
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync7, rmSync as rmSync3, cpSync as cpSync3 } from "fs";
|
|
7170
7203
|
import { resolve as resolve11, join as join5 } from "path";
|
|
7171
7204
|
import { tmpdir as tmpdir2 } from "os";
|
|
7172
7205
|
async function installFromNpm(packageName, name, targetDir) {
|
|
@@ -7187,7 +7220,7 @@ async function installFromNpm(packageName, name, targetDir) {
|
|
|
7187
7220
|
}
|
|
7188
7221
|
const tarballUrl = versionMeta.dist.tarball;
|
|
7189
7222
|
const tmpDir = join5(tmpdir2(), `xbrowser-npm-${Date.now()}`);
|
|
7190
|
-
|
|
7223
|
+
mkdirSync6(tmpDir, { recursive: true });
|
|
7191
7224
|
let warnings = [];
|
|
7192
7225
|
try {
|
|
7193
7226
|
const tarballPath = join5(tmpDir, `${name}.tgz`);
|
|
@@ -7200,16 +7233,16 @@ async function installFromNpm(packageName, name, targetDir) {
|
|
|
7200
7233
|
if (!verify.valid) {
|
|
7201
7234
|
throw new Error(`Invalid npm plugin: ${verify.error}`);
|
|
7202
7235
|
}
|
|
7203
|
-
if (
|
|
7236
|
+
if (existsSync10(targetDir)) {
|
|
7204
7237
|
rmSync3(targetDir, { recursive: true, force: true });
|
|
7205
7238
|
}
|
|
7206
7239
|
cpSync3(extractDir, targetDir, { recursive: true, force: true });
|
|
7207
7240
|
const pkgPath = resolve11(targetDir, "package.json");
|
|
7208
|
-
if (
|
|
7209
|
-
const pkg2 = JSON.parse(
|
|
7241
|
+
if (existsSync10(pkgPath)) {
|
|
7242
|
+
const pkg2 = JSON.parse(readFileSync12(pkgPath, "utf-8"));
|
|
7210
7243
|
if (!pkg2._npmSource) {
|
|
7211
7244
|
pkg2._npmSource = { name: packageName, version: latestVersion };
|
|
7212
|
-
|
|
7245
|
+
writeFileSync7(pkgPath, JSON.stringify(pkg2, null, 2));
|
|
7213
7246
|
}
|
|
7214
7247
|
}
|
|
7215
7248
|
} finally {
|
|
@@ -7226,7 +7259,7 @@ async function installFromNpm(packageName, name, targetDir) {
|
|
|
7226
7259
|
}
|
|
7227
7260
|
|
|
7228
7261
|
// src/plugin/install-sources/git.ts
|
|
7229
|
-
import { existsSync as
|
|
7262
|
+
import { existsSync as existsSync11, readFileSync as readFileSync13, writeFileSync as writeFileSync8, rmSync as rmSync4, cpSync as cpSync4 } from "fs";
|
|
7230
7263
|
import { resolve as resolve12, join as join6 } from "path";
|
|
7231
7264
|
import { tmpdir as tmpdir3 } from "os";
|
|
7232
7265
|
import { execSync as execSync8 } from "child_process";
|
|
@@ -7240,17 +7273,17 @@ async function installFromGit(gitUrl, name, targetDir) {
|
|
|
7240
7273
|
if (!verify.valid) {
|
|
7241
7274
|
throw new Error(`Invalid git plugin: ${verify.error}`);
|
|
7242
7275
|
}
|
|
7243
|
-
if (
|
|
7276
|
+
if (existsSync11(targetDir)) {
|
|
7244
7277
|
rmSync4(targetDir, { recursive: true, force: true });
|
|
7245
7278
|
}
|
|
7246
7279
|
cpSync4(tmpDir, targetDir, { recursive: true, force: true });
|
|
7247
7280
|
rmSync4(resolve12(targetDir, ".git"), { recursive: true, force: true });
|
|
7248
7281
|
const pkgPath = resolve12(targetDir, "package.json");
|
|
7249
|
-
if (
|
|
7250
|
-
const pkg2 = JSON.parse(
|
|
7282
|
+
if (existsSync11(pkgPath)) {
|
|
7283
|
+
const pkg2 = JSON.parse(readFileSync13(pkgPath, "utf-8"));
|
|
7251
7284
|
if (!pkg2._gitSource) {
|
|
7252
7285
|
pkg2._gitSource = { url: gitUrl };
|
|
7253
|
-
|
|
7286
|
+
writeFileSync8(pkgPath, JSON.stringify(pkg2, null, 2));
|
|
7254
7287
|
}
|
|
7255
7288
|
}
|
|
7256
7289
|
} finally {
|
|
@@ -7267,12 +7300,12 @@ async function installFromGit(gitUrl, name, targetDir) {
|
|
|
7267
7300
|
}
|
|
7268
7301
|
|
|
7269
7302
|
// src/plugin/install-sources/url.ts
|
|
7270
|
-
import { existsSync as
|
|
7303
|
+
import { existsSync as existsSync12, readFileSync as readFileSync14, writeFileSync as writeFileSync9, mkdirSync as mkdirSync7, rmSync as rmSync5, cpSync as cpSync5 } from "fs";
|
|
7271
7304
|
import { resolve as resolve13, join as join7, basename } from "path";
|
|
7272
7305
|
import { tmpdir as tmpdir4 } from "os";
|
|
7273
7306
|
async function installFromUrl(url, name, targetDir) {
|
|
7274
7307
|
const tmpDir = join7(tmpdir4(), `xbrowser-url-${Date.now()}`);
|
|
7275
|
-
|
|
7308
|
+
mkdirSync7(tmpDir, { recursive: true });
|
|
7276
7309
|
let warnings = [];
|
|
7277
7310
|
try {
|
|
7278
7311
|
const fileName = basename(new URL(url).pathname) || "plugin.tar.gz";
|
|
@@ -7286,16 +7319,16 @@ async function installFromUrl(url, name, targetDir) {
|
|
|
7286
7319
|
if (!verify.valid) {
|
|
7287
7320
|
throw new Error(`Invalid plugin from URL: ${verify.error}`);
|
|
7288
7321
|
}
|
|
7289
|
-
if (
|
|
7322
|
+
if (existsSync12(targetDir)) {
|
|
7290
7323
|
rmSync5(targetDir, { recursive: true, force: true });
|
|
7291
7324
|
}
|
|
7292
7325
|
cpSync5(extractDir, targetDir, { recursive: true, force: true });
|
|
7293
7326
|
const pkgPath = resolve13(targetDir, "package.json");
|
|
7294
|
-
if (
|
|
7295
|
-
const pkg2 = JSON.parse(
|
|
7327
|
+
if (existsSync12(pkgPath)) {
|
|
7328
|
+
const pkg2 = JSON.parse(readFileSync14(pkgPath, "utf-8"));
|
|
7296
7329
|
if (!pkg2._urlSource) {
|
|
7297
7330
|
pkg2._urlSource = { url };
|
|
7298
|
-
|
|
7331
|
+
writeFileSync9(pkgPath, JSON.stringify(pkg2, null, 2));
|
|
7299
7332
|
}
|
|
7300
7333
|
}
|
|
7301
7334
|
} finally {
|
|
@@ -7313,10 +7346,10 @@ async function installFromUrl(url, name, targetDir) {
|
|
|
7313
7346
|
|
|
7314
7347
|
// src/plugin/install-sources/marketplace.ts
|
|
7315
7348
|
import {
|
|
7316
|
-
existsSync as
|
|
7317
|
-
mkdirSync as
|
|
7318
|
-
writeFileSync as
|
|
7319
|
-
readFileSync as
|
|
7349
|
+
existsSync as existsSync13,
|
|
7350
|
+
mkdirSync as mkdirSync8,
|
|
7351
|
+
writeFileSync as writeFileSync10,
|
|
7352
|
+
readFileSync as readFileSync15,
|
|
7320
7353
|
rmSync as rmSync6,
|
|
7321
7354
|
cpSync as cpSync6
|
|
7322
7355
|
} from "fs";
|
|
@@ -7338,12 +7371,12 @@ async function installFromMarketplace(pluginsDir, slug, options) {
|
|
|
7338
7371
|
const plugin = detailData.data;
|
|
7339
7372
|
const name = options?.name || String(plugin.slug || slug);
|
|
7340
7373
|
const targetDir = resolve14(pluginsDir, name);
|
|
7341
|
-
if (
|
|
7374
|
+
if (existsSync13(targetDir) && !options?.force) {
|
|
7342
7375
|
throw new Error(`Plugin "${name}" already exists. Use --force to overwrite.`);
|
|
7343
7376
|
}
|
|
7344
|
-
|
|
7377
|
+
mkdirSync8(targetDir, { recursive: true });
|
|
7345
7378
|
const tmpDir = join8(tmpdir5(), `xbrowser-marketplace-${Date.now()}`);
|
|
7346
|
-
|
|
7379
|
+
mkdirSync8(tmpDir, { recursive: true });
|
|
7347
7380
|
const realSlug = String(plugin.slug || slug);
|
|
7348
7381
|
try {
|
|
7349
7382
|
await downloadAndExtractMarketplaceTarball(baseUrl, realSlug, tmpDir, targetDir);
|
|
@@ -7373,14 +7406,14 @@ function isManifestArray(data) {
|
|
|
7373
7406
|
return Array.isArray(data) && data.length > 0 && typeof data[0].path === "string" && typeof data[0].content === "string";
|
|
7374
7407
|
}
|
|
7375
7408
|
function extractManifestToDir(manifest, targetDir) {
|
|
7376
|
-
if (
|
|
7409
|
+
if (existsSync13(targetDir)) {
|
|
7377
7410
|
rmSync6(targetDir, { recursive: true, force: true });
|
|
7378
7411
|
}
|
|
7379
|
-
|
|
7412
|
+
mkdirSync8(targetDir, { recursive: true });
|
|
7380
7413
|
for (const file of manifest) {
|
|
7381
7414
|
const filePath = resolve14(targetDir, file.path);
|
|
7382
|
-
|
|
7383
|
-
|
|
7415
|
+
mkdirSync8(dirname2(filePath), { recursive: true });
|
|
7416
|
+
writeFileSync10(filePath, Buffer.from(file.content, "base64"));
|
|
7384
7417
|
}
|
|
7385
7418
|
}
|
|
7386
7419
|
function tryParseAsGzippedManifest(buffer) {
|
|
@@ -7407,7 +7440,7 @@ async function downloadAndExtractMarketplaceTarball(baseUrl, slug, tmpDir, targe
|
|
|
7407
7440
|
const redirectUrl = tarballRes.headers.get("location");
|
|
7408
7441
|
const tarballPath = join8(tmpDir, `${slug}.tar.gz`);
|
|
7409
7442
|
await downloadToFile(redirectUrl, tarballPath);
|
|
7410
|
-
const buffer =
|
|
7443
|
+
const buffer = readFileSync15(tarballPath);
|
|
7411
7444
|
const manifest = tryParseAsGzippedManifest(buffer);
|
|
7412
7445
|
if (manifest) {
|
|
7413
7446
|
extractManifestToDir(manifest, targetDir);
|
|
@@ -7416,7 +7449,7 @@ async function downloadAndExtractMarketplaceTarball(baseUrl, slug, tmpDir, targe
|
|
|
7416
7449
|
const extractDir = join8(tmpDir, "extracted");
|
|
7417
7450
|
extractTarGz(tarballPath, extractDir);
|
|
7418
7451
|
flattenPackageRoot(extractDir);
|
|
7419
|
-
if (
|
|
7452
|
+
if (existsSync13(targetDir)) {
|
|
7420
7453
|
rmSync6(targetDir, { recursive: true, force: true });
|
|
7421
7454
|
}
|
|
7422
7455
|
cpSync6(extractDir, targetDir, { recursive: true, force: true });
|
|
@@ -7428,12 +7461,12 @@ async function downloadAndExtractMarketplaceTarball(baseUrl, slug, tmpDir, targe
|
|
|
7428
7461
|
return;
|
|
7429
7462
|
}
|
|
7430
7463
|
const tarballPath = join8(tmpDir, `${slug}.tar.gz`);
|
|
7431
|
-
|
|
7464
|
+
writeFileSync10(tarballPath, buffer);
|
|
7432
7465
|
try {
|
|
7433
7466
|
const extractDir = join8(tmpDir, "extracted");
|
|
7434
7467
|
extractTarGz(tarballPath, extractDir);
|
|
7435
7468
|
flattenPackageRoot(extractDir);
|
|
7436
|
-
if (
|
|
7469
|
+
if (existsSync13(targetDir)) {
|
|
7437
7470
|
rmSync6(targetDir, { recursive: true, force: true });
|
|
7438
7471
|
}
|
|
7439
7472
|
cpSync6(extractDir, targetDir, { recursive: true, force: true });
|
|
@@ -7469,24 +7502,24 @@ function writeMarketplacePackageJson(plugin, slug, name, baseUrl, targetDir) {
|
|
|
7469
7502
|
}
|
|
7470
7503
|
};
|
|
7471
7504
|
const pkgPath = resolve14(targetDir, "package.json");
|
|
7472
|
-
if (!
|
|
7473
|
-
|
|
7505
|
+
if (!existsSync13(pkgPath)) {
|
|
7506
|
+
writeFileSync10(pkgPath, JSON.stringify(packageJson, null, 2));
|
|
7474
7507
|
} else {
|
|
7475
7508
|
try {
|
|
7476
|
-
const existing = JSON.parse(
|
|
7509
|
+
const existing = JSON.parse(readFileSync15(pkgPath, "utf-8"));
|
|
7477
7510
|
const merged = {
|
|
7478
7511
|
...existing,
|
|
7479
7512
|
xbrowser: { ...existing.xbrowser, ...packageJson.xbrowser },
|
|
7480
7513
|
_marketplace: packageJson._marketplace
|
|
7481
7514
|
};
|
|
7482
|
-
|
|
7515
|
+
writeFileSync10(pkgPath, JSON.stringify(merged, null, 2));
|
|
7483
7516
|
} catch {
|
|
7484
|
-
|
|
7517
|
+
writeFileSync10(pkgPath, JSON.stringify(packageJson, null, 2));
|
|
7485
7518
|
}
|
|
7486
7519
|
}
|
|
7487
7520
|
}
|
|
7488
7521
|
function ensureIndexFile(plugin, name, targetDir) {
|
|
7489
|
-
if (!
|
|
7522
|
+
if (!existsSync13(resolve14(targetDir, "index.ts")) && !existsSync13(resolve14(targetDir, "index.js"))) {
|
|
7490
7523
|
const commands = plugin.commands || [];
|
|
7491
7524
|
const commandHandlers = commands.length > 0 ? commands.map((cmd) => {
|
|
7492
7525
|
return [
|
|
@@ -7501,7 +7534,7 @@ function ensureIndexFile(plugin, name, targetDir) {
|
|
|
7501
7534
|
` handler: async () => ({ data: { message: 'Hello from ${name}!' }, tips: [] }),`,
|
|
7502
7535
|
` });`
|
|
7503
7536
|
].join("\n");
|
|
7504
|
-
|
|
7537
|
+
writeFileSync10(
|
|
7505
7538
|
resolve14(targetDir, "index.ts"),
|
|
7506
7539
|
[
|
|
7507
7540
|
`import type { XCLIAPI } from '@dyyz1993/xcli-core';`,
|
|
@@ -7540,10 +7573,10 @@ var PluginInstaller = class {
|
|
|
7540
7573
|
const type = this.detectSourceType(source);
|
|
7541
7574
|
const name = options?.name || this.deriveName(source, type);
|
|
7542
7575
|
const targetDir = resolve15(this.pluginsDir, name);
|
|
7543
|
-
if (
|
|
7576
|
+
if (existsSync14(targetDir) && !options?.force) {
|
|
7544
7577
|
throw new Error(`Plugin "${name}" already exists. Use --force to overwrite.`);
|
|
7545
7578
|
}
|
|
7546
|
-
|
|
7579
|
+
mkdirSync9(targetDir, { recursive: true });
|
|
7547
7580
|
const resolvedSource = type === "npm" ? await resolveNpmPackageWithFallback(source) : source;
|
|
7548
7581
|
switch (type) {
|
|
7549
7582
|
case "local":
|
|
@@ -7601,7 +7634,7 @@ var PluginInstaller = class {
|
|
|
7601
7634
|
*/
|
|
7602
7635
|
async uninstall(name) {
|
|
7603
7636
|
const targetDir = resolve15(this.pluginsDir, name);
|
|
7604
|
-
if (!
|
|
7637
|
+
if (!existsSync14(targetDir)) {
|
|
7605
7638
|
throw new Error(`Plugin "${name}" not found`);
|
|
7606
7639
|
}
|
|
7607
7640
|
rmSync7(targetDir, { recursive: true, force: true });
|
|
@@ -7612,7 +7645,7 @@ var PluginInstaller = class {
|
|
|
7612
7645
|
* @returns Array of installed plugin information.
|
|
7613
7646
|
*/
|
|
7614
7647
|
async list(_options) {
|
|
7615
|
-
if (!
|
|
7648
|
+
if (!existsSync14(this.pluginsDir)) return [];
|
|
7616
7649
|
const entries = readdirSync3(this.pluginsDir, { withFileTypes: true });
|
|
7617
7650
|
const plugins = [];
|
|
7618
7651
|
for (const entry of entries) {
|
|
@@ -7620,7 +7653,7 @@ var PluginInstaller = class {
|
|
|
7620
7653
|
const pluginPath = resolve15(this.pluginsDir, entry.name);
|
|
7621
7654
|
const indexPath = resolve15(pluginPath, "index.ts");
|
|
7622
7655
|
const indexJsPath = resolve15(pluginPath, "index.js");
|
|
7623
|
-
if (!
|
|
7656
|
+
if (!existsSync14(indexPath) && !existsSync14(indexJsPath)) continue;
|
|
7624
7657
|
const metadata = PluginMetadataParser.parseFromPackageJson(pluginPath);
|
|
7625
7658
|
let source = "local";
|
|
7626
7659
|
const pkg2 = readJsonFile(resolve15(pluginPath, "package.json"), {});
|
|
@@ -7647,10 +7680,10 @@ var PluginInstaller = class {
|
|
|
7647
7680
|
}
|
|
7648
7681
|
if (source.startsWith("file://")) {
|
|
7649
7682
|
const filePath = decodeURIComponent(new URL(source).pathname);
|
|
7650
|
-
if (
|
|
7683
|
+
if (existsSync14(filePath)) return "url";
|
|
7651
7684
|
}
|
|
7652
7685
|
if (source.endsWith(".git") || source.includes("github.com/")) return "git";
|
|
7653
|
-
if (
|
|
7686
|
+
if (existsSync14(resolve15(source))) return "local";
|
|
7654
7687
|
return "npm";
|
|
7655
7688
|
}
|
|
7656
7689
|
deriveName(source, type) {
|
|
@@ -9674,7 +9707,7 @@ async function handleFilter(args, _mode) {
|
|
|
9674
9707
|
|
|
9675
9708
|
// src/stdin.ts
|
|
9676
9709
|
import { createInterface } from "readline";
|
|
9677
|
-
import { readFileSync as
|
|
9710
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
9678
9711
|
async function readStdin2() {
|
|
9679
9712
|
if (process.stdin.isTTY) return [];
|
|
9680
9713
|
const lines = [];
|
|
@@ -9688,7 +9721,7 @@ async function readStdin2() {
|
|
|
9688
9721
|
return lines;
|
|
9689
9722
|
}
|
|
9690
9723
|
function readCommandFile(filePath) {
|
|
9691
|
-
const content =
|
|
9724
|
+
const content = readFileSync16(filePath, "utf-8");
|
|
9692
9725
|
return content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
9693
9726
|
}
|
|
9694
9727
|
|
|
@@ -10956,11 +10989,14 @@ Run "xbrowser ${command} --help" to see available commands.`
|
|
|
10956
10989
|
showCommandHelp(command, cmdEntry, { description: site.config.description, name: site.name, url: site.url }, mode);
|
|
10957
10990
|
return;
|
|
10958
10991
|
}
|
|
10959
|
-
const
|
|
10992
|
+
const pluginNameIdx = argv.indexOf(command);
|
|
10993
|
+
const subCmdIdx = pluginNameIdx >= 0 ? argv.indexOf(subCommand, pluginNameIdx + 1) : -1;
|
|
10994
|
+
const rawPluginArgs = subCmdIdx >= 0 ? argv.slice(subCmdIdx + 1) : [];
|
|
10995
|
+
const params = parsePluginParams(rawPluginArgs, cmdEntry.parameters);
|
|
10960
10996
|
if (options.target && !params._target) {
|
|
10961
10997
|
params._target = options.target;
|
|
10962
10998
|
}
|
|
10963
|
-
const needsBrowser = cmdEntry.scope
|
|
10999
|
+
const needsBrowser = cmdEntry.scope === "page";
|
|
10964
11000
|
if (needsBrowser && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
10965
11001
|
const { forwardExec } = await import("./daemon-client-XWSSQBEA.js");
|
|
10966
11002
|
const userTimeout = typeof params.timeout === "number" && params.timeout > 0 ? params.timeout * 1e3 + 3e4 : void 0;
|
|
@@ -10997,16 +11033,7 @@ Run "xbrowser ${command} --help" to see available commands.`
|
|
|
10997
11033
|
browser: needsBrowser ? session.context.browser() : null,
|
|
10998
11034
|
browserContext: needsBrowser ? session.context : null,
|
|
10999
11035
|
sessionId: needsBrowser ? session.id : "",
|
|
11000
|
-
storage:
|
|
11001
|
-
get: async (_key) => null,
|
|
11002
|
-
set: async (_key, _value) => {
|
|
11003
|
-
},
|
|
11004
|
-
delete: async (_key) => {
|
|
11005
|
-
},
|
|
11006
|
-
clear: async () => {
|
|
11007
|
-
},
|
|
11008
|
-
keys: async () => []
|
|
11009
|
-
},
|
|
11036
|
+
storage: getPluginStorage(command),
|
|
11010
11037
|
output: { mode, showTips: true, color: true, emoji: true },
|
|
11011
11038
|
error: (msg) => {
|
|
11012
11039
|
outputError(msg);
|