@xbrowser/cli 1.0.5 → 1.0.7
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 +19 -16
- package/dist/{browser-ITLZZDHJ.js → browser-AXCKBSWS.js} +2 -2
- package/dist/{browser-5CTOA2WS.js → browser-U4VWPTS2.js} +1 -1
- package/dist/{browser-IUJXXNBT.js → browser-X7OVRKJH.js} +2 -2
- package/dist/{cdp-driver-4X3DK6PS.js → cdp-driver-CWNZVWHX.js} +18 -4
- package/dist/{cdp-driver-D6WMSMWX.js → cdp-driver-ZAVN7GRB.js} +1 -1
- package/dist/{chunk-TWWOIJM7.js → chunk-5QAYN5EZ.js} +10 -1
- package/dist/{chunk-DKWR54XQ.js → chunk-7POCCXIB.js} +27 -4
- package/dist/{chunk-LRBSUKUZ.js → chunk-MXG2H3HJ.js} +10 -1
- package/dist/{chunk-N2JFPWMI.js → chunk-SEFIJY2M.js} +18 -4
- package/dist/cli.js +122 -57
- package/dist/daemon-main.js +16 -9
- package/dist/index.js +123 -58
- package/dist/{session-replayer-MY27H4DX.js → session-replayer-GCGY6KFK.js} +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# xbrowser
|
|
2
2
|
|
|
3
|
-
> **Browser automation CLI** for web scraping, headless browsing, SEO analysis, and AI agent workflows. 49 commands,
|
|
3
|
+
> **Browser automation CLI** for web scraping, headless browsing, SEO analysis, and AI agent workflows. 49 commands, 20+ 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)
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
- **命令链** — 用 `&&`、`,`、`+`、`->`、`;` 串联多个命令,一行搞定复杂流程
|
|
14
14
|
- **管道 & Heredoc** — 支持 stdin 管道和 heredoc 批量执行
|
|
15
15
|
- **录制 / 回放** — 录制浏览器操作为 YAML,随时回放,可转换为 JS/Python/Bash 脚本
|
|
16
|
-
- **插件系统** — 基于 `@dyyz1993/xcli-core`,用 TypeScript
|
|
16
|
+
- **插件系统** — 基于 `@dyyz1993/xcli-core`,用 TypeScript 编写站点插件。快速开始:`xbrowser create my-plugin --template static`
|
|
17
17
|
- **CDP 连接** — 连接已运行的 Chrome/Chromium,无需重新启动浏览器
|
|
18
18
|
- **会话管理** — 多会话并行,独立上下文
|
|
19
19
|
- **Daemon 模式** — 后台常驻,快速响应
|
|
@@ -256,7 +256,7 @@ xbrowser --cdp auto "goto https://example.com , title"
|
|
|
256
256
|
| `html --selector "#main"` | 获取指定元素 HTML | `xbrowser html --selector "#main"` |
|
|
257
257
|
| `text` | 获取页面文本 | `xbrowser text` |
|
|
258
258
|
| `text --selector "#article"` | 获取指定元素文本 | `xbrowser text --selector "#article"` |
|
|
259
|
-
| `
|
|
259
|
+
| `eval "expression"` | 执行 JS 获取元素属性 | `xbrowser eval "document.querySelector('#link').href"` |
|
|
260
260
|
|
|
261
261
|
### 截图与快照
|
|
262
262
|
|
|
@@ -282,20 +282,20 @@ xbrowser --cdp auto "goto https://example.com , title"
|
|
|
282
282
|
|------|------|------|
|
|
283
283
|
| `eval <expression>` | 执行 JS 表达式 | `xbrowser eval "document.title"` |
|
|
284
284
|
| `eval "1 + 2"` | 计算表达式 | `xbrowser eval "1 + 2"` |
|
|
285
|
-
| `
|
|
285
|
+
| `eval "(a, b) => a + b" --args 1 2` | 执行带参数的函数 | `xbrowser eval "(a, b) => a + b" --args 1 2` |
|
|
286
286
|
|
|
287
287
|
### 存储
|
|
288
288
|
|
|
289
289
|
| 命令 | 说明 | 示例 |
|
|
290
290
|
|------|------|------|
|
|
291
|
-
| `
|
|
292
|
-
| `
|
|
293
|
-
| `
|
|
294
|
-
| `
|
|
295
|
-
| `
|
|
296
|
-
| `
|
|
297
|
-
| `
|
|
298
|
-
| `
|
|
291
|
+
| `get-cookies` | 获取所有 Cookie | `xbrowser get-cookies` |
|
|
292
|
+
| `set-cookie <name> <value>` | 设置 Cookie | `xbrowser set-cookie session abc123` |
|
|
293
|
+
| `set-cookie <name> <value> --domain .example.com` | 指定域名 | `xbrowser set-cookie session abc123 --domain .example.com` |
|
|
294
|
+
| `clear-cookies` | 清除所有 Cookie | `xbrowser clear-cookies` |
|
|
295
|
+
| `get-local-storage` | 获取所有 localStorage | `xbrowser get-local-storage` |
|
|
296
|
+
| `get-local-storage --key token` | 获取指定 key | `xbrowser get-local-storage --key token` |
|
|
297
|
+
| `set-local-storage <key> <value>` | 设置 localStorage | `xbrowser set-local-storage token "abc"` |
|
|
298
|
+
| `clear-local-storage` | 清除 localStorage | `xbrowser clear-local-storage` |
|
|
299
299
|
|
|
300
300
|
### 帧操作
|
|
301
301
|
|
|
@@ -309,8 +309,8 @@ xbrowser --cdp auto "goto https://example.com , title"
|
|
|
309
309
|
|
|
310
310
|
| 命令 | 说明 | 示例 |
|
|
311
311
|
|------|------|------|
|
|
312
|
-
| `
|
|
313
|
-
| `
|
|
312
|
+
| `set-viewport <width> <height>` | 设置视口大小 | `xbrowser set-viewport 1920 1080` |
|
|
313
|
+
| `set-viewport 375 812 --isMobile true` | 移动设备模式 | `xbrowser set-viewport 375 812 --isMobile true` |
|
|
314
314
|
|
|
315
315
|
### 配置管理
|
|
316
316
|
|
|
@@ -416,7 +416,10 @@ xbrowser create my-plugin --template static --force
|
|
|
416
416
|
| `;` | 分割管线(前一个完成后开始下一个) | `goto https://example.com ; goto https://other.com` |
|
|
417
417
|
| `\|\|` | 前一步成功则跳过后续 | `goto https://example.com \|\| goto https://fallback.com` |
|
|
418
418
|
|
|
419
|
-
|
|
419
|
+
**注意**:
|
|
420
|
+
- 命令链中如果包含特殊字符(如 `#`、`>`),需要用引号包裹整个命令链或对单个参数加引号。
|
|
421
|
+
- `->` 和 `+` 分隔符要求两边有空格(`goto url -> title` ✅,`goto url->title` ❌)。
|
|
422
|
+
- `,` 和 `;` 不要求空格。
|
|
420
423
|
|
|
421
424
|
```bash
|
|
422
425
|
# 正确 — 整体引号
|
|
@@ -440,7 +443,7 @@ project > browser > page > element
|
|
|
440
443
|
| Scope | 说明 | 需要的条件 | 典型命令 |
|
|
441
444
|
|-------|------|------------|----------|
|
|
442
445
|
| **project** | 项目级别 | 无 | config, daemon, plugin |
|
|
443
|
-
| **browser** | 浏览器级别 | 浏览器实例 |
|
|
446
|
+
| **browser** | 浏览器级别 | 浏览器实例 | set-viewport, session |
|
|
444
447
|
| **page** | 页面级别 | 活跃页面 | goto, wait, scroll, screenshot |
|
|
445
448
|
| **element** | 元素级别 | 页面中的元素 | click, fill, type, hover |
|
|
446
449
|
|
|
@@ -20,8 +20,8 @@ import {
|
|
|
20
20
|
saveSessionDiskMeta,
|
|
21
21
|
setActivePage,
|
|
22
22
|
touchSession
|
|
23
|
-
} from "./chunk-
|
|
24
|
-
import "./chunk-
|
|
23
|
+
} from "./chunk-MXG2H3HJ.js";
|
|
24
|
+
import "./chunk-SEFIJY2M.js";
|
|
25
25
|
import "./chunk-TNEN6VQ2.js";
|
|
26
26
|
import "./chunk-GDKLH7ZY.js";
|
|
27
27
|
import "./chunk-KFQGP6VL.js";
|
|
@@ -20,8 +20,8 @@ import {
|
|
|
20
20
|
saveSessionDiskMeta,
|
|
21
21
|
setActivePage,
|
|
22
22
|
touchSession
|
|
23
|
-
} from "./chunk-
|
|
24
|
-
import "./chunk-
|
|
23
|
+
} from "./chunk-5QAYN5EZ.js";
|
|
24
|
+
import "./chunk-SEFIJY2M.js";
|
|
25
25
|
import "./chunk-TNEN6VQ2.js";
|
|
26
26
|
import "./chunk-GDKLH7ZY.js";
|
|
27
27
|
import "./chunk-ABXMBNQ6.js";
|
|
@@ -2179,8 +2179,14 @@ var XBContextImpl = class {
|
|
|
2179
2179
|
// ── Cookies ─────────────────────────────────────────────────
|
|
2180
2180
|
async cookies(urls) {
|
|
2181
2181
|
const urlList = typeof urls === "string" ? [urls] : urls;
|
|
2182
|
-
const
|
|
2183
|
-
|
|
2182
|
+
const params = urlList ? { urls: urlList } : void 0;
|
|
2183
|
+
try {
|
|
2184
|
+
const result = await this.conn.send("Storage.getCookies", params);
|
|
2185
|
+
return result.cookies;
|
|
2186
|
+
} catch {
|
|
2187
|
+
const result = await this.conn.send("Network.getCookies", params);
|
|
2188
|
+
return result.cookies;
|
|
2189
|
+
}
|
|
2184
2190
|
}
|
|
2185
2191
|
async addCookies(cookies) {
|
|
2186
2192
|
const cdpCookies = cookies.map((c) => ({
|
|
@@ -2193,10 +2199,18 @@ var XBContextImpl = class {
|
|
|
2193
2199
|
secure: c.secure,
|
|
2194
2200
|
sameSite: c.sameSite
|
|
2195
2201
|
}));
|
|
2196
|
-
|
|
2202
|
+
try {
|
|
2203
|
+
await this.conn.send("Storage.setCookies", { cookies: cdpCookies });
|
|
2204
|
+
} catch {
|
|
2205
|
+
await this.conn.send("Network.setCookies", { cookies: cdpCookies });
|
|
2206
|
+
}
|
|
2197
2207
|
}
|
|
2198
2208
|
async clearCookies() {
|
|
2199
|
-
|
|
2209
|
+
try {
|
|
2210
|
+
await this.conn.send("Storage.clearCookies");
|
|
2211
|
+
} catch {
|
|
2212
|
+
await this.conn.send("Network.clearBrowserCookies");
|
|
2213
|
+
}
|
|
2200
2214
|
}
|
|
2201
2215
|
on(event, handler) {
|
|
2202
2216
|
this._emitter.on(event, handler);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
launch
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-SEFIJY2M.js";
|
|
4
4
|
import {
|
|
5
5
|
errMsg
|
|
6
6
|
} from "./chunk-GDKLH7ZY.js";
|
|
@@ -36,6 +36,15 @@ async function fetchNoProxy(url) {
|
|
|
36
36
|
async function resolveCDPEndpoint(raw) {
|
|
37
37
|
if (raw === "auto") {
|
|
38
38
|
const httpResp = await fetchNoProxy("http://localhost:9222/json/version");
|
|
39
|
+
if (!httpResp.ok) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`CDP port 9222 responded with ${httpResp.status} ${httpResp.statusText}. \u53EF\u80FD\u539F\u56E0\uFF1A9222 \u88AB\u50F5\u6B7B\u7684 Chrome \u5360\u7528\uFF0C\u6216\u6CA1\u6709 Chrome \u4EE5 --remote-debugging-port \u542F\u52A8\u3002
|
|
42
|
+
\u89E3\u51B3\u65B9\u6CD5\uFF1A
|
|
43
|
+
1. \u6740\u6389\u6B8B\u7559 Chrome: pkill -f "remote-debugging-port"
|
|
44
|
+
2. \u91CD\u542F Chrome: npx cdp-tunnel setup
|
|
45
|
+
3. \u6216\u6307\u5B9A\u7AEF\u53E3: --cdp <port>`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
39
48
|
const data = await httpResp.json();
|
|
40
49
|
if (!data.webSocketDebuggerUrl) {
|
|
41
50
|
throw new Error("Could not auto-discover CDP endpoint from localhost:9222");
|
|
@@ -2184,8 +2184,14 @@ var XBContextImpl = class {
|
|
|
2184
2184
|
// ── Cookies ─────────────────────────────────────────────────
|
|
2185
2185
|
async cookies(urls) {
|
|
2186
2186
|
const urlList = typeof urls === "string" ? [urls] : urls;
|
|
2187
|
-
const
|
|
2188
|
-
|
|
2187
|
+
const params = urlList ? { urls: urlList } : void 0;
|
|
2188
|
+
try {
|
|
2189
|
+
const result = await this.conn.send("Storage.getCookies", params);
|
|
2190
|
+
return result.cookies;
|
|
2191
|
+
} catch {
|
|
2192
|
+
const result = await this.conn.send("Network.getCookies", params);
|
|
2193
|
+
return result.cookies;
|
|
2194
|
+
}
|
|
2189
2195
|
}
|
|
2190
2196
|
async addCookies(cookies) {
|
|
2191
2197
|
const cdpCookies = cookies.map((c) => ({
|
|
@@ -2198,10 +2204,18 @@ var XBContextImpl = class {
|
|
|
2198
2204
|
secure: c.secure,
|
|
2199
2205
|
sameSite: c.sameSite
|
|
2200
2206
|
}));
|
|
2201
|
-
|
|
2207
|
+
try {
|
|
2208
|
+
await this.conn.send("Storage.setCookies", { cookies: cdpCookies });
|
|
2209
|
+
} catch {
|
|
2210
|
+
await this.conn.send("Network.setCookies", { cookies: cdpCookies });
|
|
2211
|
+
}
|
|
2202
2212
|
}
|
|
2203
2213
|
async clearCookies() {
|
|
2204
|
-
|
|
2214
|
+
try {
|
|
2215
|
+
await this.conn.send("Storage.clearCookies");
|
|
2216
|
+
} catch {
|
|
2217
|
+
await this.conn.send("Network.clearBrowserCookies");
|
|
2218
|
+
}
|
|
2205
2219
|
}
|
|
2206
2220
|
on(event, handler) {
|
|
2207
2221
|
this._emitter.on(event, handler);
|
|
@@ -4129,6 +4143,15 @@ async function fetchNoProxy(url) {
|
|
|
4129
4143
|
async function resolveCDPEndpoint(raw) {
|
|
4130
4144
|
if (raw === "auto") {
|
|
4131
4145
|
const httpResp = await fetchNoProxy("http://localhost:9222/json/version");
|
|
4146
|
+
if (!httpResp.ok) {
|
|
4147
|
+
throw new Error(
|
|
4148
|
+
`CDP port 9222 responded with ${httpResp.status} ${httpResp.statusText}. \u53EF\u80FD\u539F\u56E0\uFF1A9222 \u88AB\u50F5\u6B7B\u7684 Chrome \u5360\u7528\uFF0C\u6216\u6CA1\u6709 Chrome \u4EE5 --remote-debugging-port \u542F\u52A8\u3002
|
|
4149
|
+
\u89E3\u51B3\u65B9\u6CD5\uFF1A
|
|
4150
|
+
1. \u6740\u6389\u6B8B\u7559 Chrome: pkill -f "remote-debugging-port"
|
|
4151
|
+
2. \u91CD\u542F Chrome: npx cdp-tunnel setup
|
|
4152
|
+
3. \u6216\u6307\u5B9A\u7AEF\u53E3: --cdp <port>`
|
|
4153
|
+
);
|
|
4154
|
+
}
|
|
4132
4155
|
const data = await httpResp.json();
|
|
4133
4156
|
if (!data.webSocketDebuggerUrl) {
|
|
4134
4157
|
throw new Error("Could not auto-discover CDP endpoint from localhost:9222");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
launch
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-SEFIJY2M.js";
|
|
4
4
|
import {
|
|
5
5
|
errMsg
|
|
6
6
|
} from "./chunk-GDKLH7ZY.js";
|
|
@@ -1401,6 +1401,15 @@ async function fetchNoProxy(url) {
|
|
|
1401
1401
|
async function resolveCDPEndpoint(raw) {
|
|
1402
1402
|
if (raw === "auto") {
|
|
1403
1403
|
const httpResp = await fetchNoProxy("http://localhost:9222/json/version");
|
|
1404
|
+
if (!httpResp.ok) {
|
|
1405
|
+
throw new Error(
|
|
1406
|
+
`CDP port 9222 responded with ${httpResp.status} ${httpResp.statusText}. \u53EF\u80FD\u539F\u56E0\uFF1A9222 \u88AB\u50F5\u6B7B\u7684 Chrome \u5360\u7528\uFF0C\u6216\u6CA1\u6709 Chrome \u4EE5 --remote-debugging-port \u542F\u52A8\u3002
|
|
1407
|
+
\u89E3\u51B3\u65B9\u6CD5\uFF1A
|
|
1408
|
+
1. \u6740\u6389\u6B8B\u7559 Chrome: pkill -f "remote-debugging-port"
|
|
1409
|
+
2. \u91CD\u542F Chrome: npx cdp-tunnel setup
|
|
1410
|
+
3. \u6216\u6307\u5B9A\u7AEF\u53E3: --cdp <port>`
|
|
1411
|
+
);
|
|
1412
|
+
}
|
|
1404
1413
|
const data = await httpResp.json();
|
|
1405
1414
|
if (!data.webSocketDebuggerUrl) {
|
|
1406
1415
|
throw new Error("Could not auto-discover CDP endpoint from localhost:9222");
|
|
@@ -2178,8 +2178,14 @@ var XBContextImpl = class {
|
|
|
2178
2178
|
// ── Cookies ─────────────────────────────────────────────────
|
|
2179
2179
|
async cookies(urls) {
|
|
2180
2180
|
const urlList = typeof urls === "string" ? [urls] : urls;
|
|
2181
|
-
const
|
|
2182
|
-
|
|
2181
|
+
const params = urlList ? { urls: urlList } : void 0;
|
|
2182
|
+
try {
|
|
2183
|
+
const result = await this.conn.send("Storage.getCookies", params);
|
|
2184
|
+
return result.cookies;
|
|
2185
|
+
} catch {
|
|
2186
|
+
const result = await this.conn.send("Network.getCookies", params);
|
|
2187
|
+
return result.cookies;
|
|
2188
|
+
}
|
|
2183
2189
|
}
|
|
2184
2190
|
async addCookies(cookies) {
|
|
2185
2191
|
const cdpCookies = cookies.map((c) => ({
|
|
@@ -2192,10 +2198,18 @@ var XBContextImpl = class {
|
|
|
2192
2198
|
secure: c.secure,
|
|
2193
2199
|
sameSite: c.sameSite
|
|
2194
2200
|
}));
|
|
2195
|
-
|
|
2201
|
+
try {
|
|
2202
|
+
await this.conn.send("Storage.setCookies", { cookies: cdpCookies });
|
|
2203
|
+
} catch {
|
|
2204
|
+
await this.conn.send("Network.setCookies", { cookies: cdpCookies });
|
|
2205
|
+
}
|
|
2196
2206
|
}
|
|
2197
2207
|
async clearCookies() {
|
|
2198
|
-
|
|
2208
|
+
try {
|
|
2209
|
+
await this.conn.send("Storage.clearCookies");
|
|
2210
|
+
} catch {
|
|
2211
|
+
await this.conn.send("Network.clearBrowserCookies");
|
|
2212
|
+
}
|
|
2199
2213
|
}
|
|
2200
2214
|
on(event, handler) {
|
|
2201
2215
|
this._emitter.on(event, handler);
|
package/dist/cli.js
CHANGED
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
resolveLaunchOpts,
|
|
26
26
|
saveSessionDiskMeta,
|
|
27
27
|
setActivePage
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-7POCCXIB.js";
|
|
29
29
|
import "./chunk-TNEN6VQ2.js";
|
|
30
30
|
import {
|
|
31
31
|
forwardCommandLog,
|
|
@@ -346,8 +346,13 @@ var gotoCommand = registerCommand({
|
|
|
346
346
|
}),
|
|
347
347
|
handler: async (p, ctx) => {
|
|
348
348
|
let url = p.url;
|
|
349
|
-
|
|
350
|
-
|
|
349
|
+
const hasScheme = /^(https?|wss?|file|about|data|chrome|blob):/i.test(url);
|
|
350
|
+
if (!hasScheme) {
|
|
351
|
+
if (/^[\w-]+(\.[\w-]+)+/.test(url) || url.startsWith("localhost")) {
|
|
352
|
+
url = "https://" + url;
|
|
353
|
+
} else {
|
|
354
|
+
throw new Error(`Invalid URL: "${url}". Expected http(s)://, file://, about:, data:, or a domain name.`);
|
|
355
|
+
}
|
|
351
356
|
}
|
|
352
357
|
const response = await ctx.page.goto(url, {
|
|
353
358
|
waitUntil: p.waitUntil || "domcontentloaded"
|
|
@@ -402,7 +407,7 @@ var urlCommand = registerCommand({
|
|
|
402
407
|
scope: "page",
|
|
403
408
|
result: z.object({ url: z.string() }),
|
|
404
409
|
handler: async (_p, ctx) => {
|
|
405
|
-
return ok({ url: ctx.page.url() });
|
|
410
|
+
return ok({ url: ctx.page.url() || "about:blank" });
|
|
406
411
|
}
|
|
407
412
|
});
|
|
408
413
|
registerCommand({
|
|
@@ -565,7 +570,8 @@ var fillCommand = registerCommand({
|
|
|
565
570
|
}),
|
|
566
571
|
handler: async (p, ctx) => {
|
|
567
572
|
const page = ctx.page;
|
|
568
|
-
|
|
573
|
+
const shouldClear = p.clear !== false;
|
|
574
|
+
if (shouldClear) {
|
|
569
575
|
await page.fill(p.selector, "", { force: true, timeout: 1e4 });
|
|
570
576
|
}
|
|
571
577
|
const isReact = await page.evaluate(() => {
|
|
@@ -591,7 +597,7 @@ var fillCommand = registerCommand({
|
|
|
591
597
|
} else {
|
|
592
598
|
await page.fill(p.selector, p.value, { force: true, timeout: 1e4 });
|
|
593
599
|
}
|
|
594
|
-
return ok2({ selector: p.selector, value: p.value, cleared:
|
|
600
|
+
return ok2({ selector: p.selector, value: p.value, cleared: shouldClear, reactMode: !!isReact });
|
|
595
601
|
}
|
|
596
602
|
});
|
|
597
603
|
var typeCommand = registerCommand({
|
|
@@ -2125,6 +2131,7 @@ function createTurndown() {
|
|
|
2125
2131
|
return turndown;
|
|
2126
2132
|
}
|
|
2127
2133
|
function postClean(md) {
|
|
2134
|
+
md = md.replace(/<(?:table|div|tbody|thead|tr|td|th|span|colgroup|col)\b[^>]*(?:>[\s\S]{200,}?<\/(?:table|div|tbody|thead|tr|td|th|span|colgroup|col)>)/g, "\n[\u26A0\uFE0F HTML block removed \u2014 complex table/layout not converted to Markdown]\n");
|
|
2128
2135
|
md = md.replace(/\n{3,}/g, "\n\n");
|
|
2129
2136
|
md = md.replace(/!\[[^\]]*\]\(\s*\)/g, "");
|
|
2130
2137
|
md = md.replace(/\[([^\]]*)\]\(\s*\)/g, "$1");
|
|
@@ -6960,7 +6967,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6960
6967
|
}
|
|
6961
6968
|
let targetPageOverride = null;
|
|
6962
6969
|
if (_target && extraOpts?.cdpEndpoint) {
|
|
6963
|
-
const { findTargetPage } = await import("./browser-
|
|
6970
|
+
const { findTargetPage } = await import("./browser-U4VWPTS2.js");
|
|
6964
6971
|
targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
|
|
6965
6972
|
if (!targetPageOverride) {
|
|
6966
6973
|
return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
|
|
@@ -8804,6 +8811,8 @@ var TEMPLATES = {
|
|
|
8804
8811
|
{
|
|
8805
8812
|
path: "index.ts",
|
|
8806
8813
|
content: [
|
|
8814
|
+
`import { z } from 'zod';`,
|
|
8815
|
+
`import { ok } from '@dyyz1993/xcli-core';`,
|
|
8807
8816
|
`import type { XCLIAPI } from '@dyyz1993/xcli-core';`,
|
|
8808
8817
|
``,
|
|
8809
8818
|
`export default function (xcli: XCLIAPI): void {`,
|
|
@@ -8821,7 +8830,7 @@ var TEMPLATES = {
|
|
|
8821
8830
|
` (sel: string) => document.querySelector(sel)?.textContent ?? '',`,
|
|
8822
8831
|
` params.selector || 'body'`,
|
|
8823
8832
|
` );`,
|
|
8824
|
-
` return {
|
|
8833
|
+
` return ok({ text });`,
|
|
8825
8834
|
` },`,
|
|
8826
8835
|
` });`,
|
|
8827
8836
|
`}`
|
|
@@ -8829,14 +8838,21 @@ var TEMPLATES = {
|
|
|
8829
8838
|
},
|
|
8830
8839
|
{
|
|
8831
8840
|
path: "package.json",
|
|
8832
|
-
content:
|
|
8833
|
-
|
|
8834
|
-
"
|
|
8835
|
-
"
|
|
8836
|
-
|
|
8837
|
-
|
|
8838
|
-
}
|
|
8839
|
-
`
|
|
8841
|
+
content: [
|
|
8842
|
+
`{`,
|
|
8843
|
+
` "name": "xbrowser-plugin-{{projectName}}",`,
|
|
8844
|
+
` "version": "1.0.0",`,
|
|
8845
|
+
` "main": "index.ts",`,
|
|
8846
|
+
` "type": "module",`,
|
|
8847
|
+
` "dependencies": { "zod": "^3.24.0" },`,
|
|
8848
|
+
` "peerDependencies": { "@dyyz1993/xcli-core": ">=1.0.0" },`,
|
|
8849
|
+
` "xbrowser": {`,
|
|
8850
|
+
` "name": "{{projectName}}",`,
|
|
8851
|
+
` "description": "A static page plugin",`,
|
|
8852
|
+
` "commands": ["scrape"]`,
|
|
8853
|
+
` }`,
|
|
8854
|
+
`}`
|
|
8855
|
+
].join("\n")
|
|
8840
8856
|
}
|
|
8841
8857
|
]
|
|
8842
8858
|
},
|
|
@@ -8847,6 +8863,8 @@ var TEMPLATES = {
|
|
|
8847
8863
|
{
|
|
8848
8864
|
path: "index.ts",
|
|
8849
8865
|
content: [
|
|
8866
|
+
`import { z } from 'zod';`,
|
|
8867
|
+
`import { ok } from '@dyyz1993/xcli-core';`,
|
|
8850
8868
|
`import type { XCLIAPI } from '@dyyz1993/xcli-core';`,
|
|
8851
8869
|
``,
|
|
8852
8870
|
`export default function (xcli: XCLIAPI): void {`,
|
|
@@ -8861,7 +8879,7 @@ var TEMPLATES = {
|
|
|
8861
8879
|
` parameters: z.object({ url: z.string() }),`,
|
|
8862
8880
|
` handler: async (params, ctx) => {`,
|
|
8863
8881
|
` await ctx.page.goto(params.url);`,
|
|
8864
|
-
` return {
|
|
8882
|
+
` return ok({ url: params.url });`,
|
|
8865
8883
|
` },`,
|
|
8866
8884
|
` });`,
|
|
8867
8885
|
``,
|
|
@@ -8876,7 +8894,7 @@ var TEMPLATES = {
|
|
|
8876
8894
|
` case 'fill': await page.fill(params.selector, ''); break;`,
|
|
8877
8895
|
` case 'hover': await page.hover(params.selector); break;`,
|
|
8878
8896
|
` }`,
|
|
8879
|
-
` return {
|
|
8897
|
+
` return ok({ action: params.action, selector: params.selector });`,
|
|
8880
8898
|
` },`,
|
|
8881
8899
|
` });`,
|
|
8882
8900
|
`}`
|
|
@@ -8884,14 +8902,21 @@ var TEMPLATES = {
|
|
|
8884
8902
|
},
|
|
8885
8903
|
{
|
|
8886
8904
|
path: "package.json",
|
|
8887
|
-
content:
|
|
8888
|
-
|
|
8889
|
-
"
|
|
8890
|
-
"
|
|
8891
|
-
|
|
8892
|
-
|
|
8893
|
-
}
|
|
8894
|
-
`
|
|
8905
|
+
content: [
|
|
8906
|
+
`{`,
|
|
8907
|
+
` "name": "xbrowser-plugin-{{projectName}}",`,
|
|
8908
|
+
` "version": "1.0.0",`,
|
|
8909
|
+
` "main": "index.ts",`,
|
|
8910
|
+
` "type": "module",`,
|
|
8911
|
+
` "dependencies": { "zod": "^3.24.0" },`,
|
|
8912
|
+
` "peerDependencies": { "@dyyz1993/xcli-core": ">=1.0.0" },`,
|
|
8913
|
+
` "xbrowser": {`,
|
|
8914
|
+
` "name": "{{projectName}}",`,
|
|
8915
|
+
` "description": "A dynamic page plugin",`,
|
|
8916
|
+
` "commands": ["navigate", "interact"]`,
|
|
8917
|
+
` }`,
|
|
8918
|
+
`}`
|
|
8919
|
+
].join("\n")
|
|
8895
8920
|
}
|
|
8896
8921
|
]
|
|
8897
8922
|
},
|
|
@@ -8902,6 +8927,8 @@ var TEMPLATES = {
|
|
|
8902
8927
|
{
|
|
8903
8928
|
path: "index.ts",
|
|
8904
8929
|
content: [
|
|
8930
|
+
`import { z } from 'zod';`,
|
|
8931
|
+
`import { ok } from '@dyyz1993/xcli-core';`,
|
|
8905
8932
|
`import type { XCLIAPI } from '@dyyz1993/xcli-core';`,
|
|
8906
8933
|
``,
|
|
8907
8934
|
`export default function (xcli: XCLIAPI): void {`,
|
|
@@ -8915,14 +8942,13 @@ var TEMPLATES = {
|
|
|
8915
8942
|
` description: 'Check login status',`,
|
|
8916
8943
|
` scope: 'project',`,
|
|
8917
8944
|
` parameters: z.object({}),`,
|
|
8918
|
-
` handler: async (
|
|
8945
|
+
` handler: async () => {`,
|
|
8919
8946
|
` const loggedIn = await site.isLoggedIn();`,
|
|
8920
|
-
` return {
|
|
8947
|
+
` return ok({ loggedIn });`,
|
|
8921
8948
|
` },`,
|
|
8922
8949
|
` });`,
|
|
8923
8950
|
``,
|
|
8924
8951
|
` site.login(async (ctx) => {`,
|
|
8925
|
-
` console.log('Login handler for {{projectName}}');`,
|
|
8926
8952
|
` await ctx.storage.set('auth_token', 'dummy');`,
|
|
8927
8953
|
` });`,
|
|
8928
8954
|
``,
|
|
@@ -8934,14 +8960,22 @@ var TEMPLATES = {
|
|
|
8934
8960
|
},
|
|
8935
8961
|
{
|
|
8936
8962
|
path: "package.json",
|
|
8937
|
-
content:
|
|
8938
|
-
|
|
8939
|
-
"
|
|
8940
|
-
"
|
|
8941
|
-
|
|
8942
|
-
|
|
8943
|
-
}
|
|
8944
|
-
`
|
|
8963
|
+
content: [
|
|
8964
|
+
`{`,
|
|
8965
|
+
` "name": "xbrowser-plugin-{{projectName}}",`,
|
|
8966
|
+
` "version": "1.0.0",`,
|
|
8967
|
+
` "main": "index.ts",`,
|
|
8968
|
+
` "type": "module",`,
|
|
8969
|
+
` "dependencies": { "zod": "^3.24.0" },`,
|
|
8970
|
+
` "peerDependencies": { "@dyyz1993/xcli-core": ">=1.0.0" },`,
|
|
8971
|
+
` "xbrowser": {`,
|
|
8972
|
+
` "name": "{{projectName}}",`,
|
|
8973
|
+
` "description": "A plugin with login/logout support",`,
|
|
8974
|
+
` "requiresLogin": true,`,
|
|
8975
|
+
` "commands": ["check"]`,
|
|
8976
|
+
` }`,
|
|
8977
|
+
`}`
|
|
8978
|
+
].join("\n")
|
|
8945
8979
|
}
|
|
8946
8980
|
]
|
|
8947
8981
|
},
|
|
@@ -8952,6 +8986,8 @@ var TEMPLATES = {
|
|
|
8952
8986
|
{
|
|
8953
8987
|
path: "index.ts",
|
|
8954
8988
|
content: [
|
|
8989
|
+
`import { z } from 'zod';`,
|
|
8990
|
+
`import { ok } from '@dyyz1993/xcli-core';`,
|
|
8955
8991
|
`import type { XCLIAPI } from '@dyyz1993/xcli-core';`,
|
|
8956
8992
|
``,
|
|
8957
8993
|
`export default function (xcli: XCLIAPI): void {`,
|
|
@@ -8964,9 +9000,9 @@ var TEMPLATES = {
|
|
|
8964
9000
|
` description: 'Fetch data from API',`,
|
|
8965
9001
|
` scope: 'project',`,
|
|
8966
9002
|
` parameters: z.object({ endpoint: z.string(), method: z.enum(['GET', 'POST']).optional() }),`,
|
|
8967
|
-
` handler: async (params
|
|
9003
|
+
` handler: async (params) => {`,
|
|
8968
9004
|
` const method = params.method || 'GET';`,
|
|
8969
|
-
` return {
|
|
9005
|
+
` return ok({ endpoint: params.endpoint, method });`,
|
|
8970
9006
|
` },`,
|
|
8971
9007
|
` });`,
|
|
8972
9008
|
``,
|
|
@@ -8974,8 +9010,8 @@ var TEMPLATES = {
|
|
|
8974
9010
|
` description: 'List available endpoints',`,
|
|
8975
9011
|
` scope: 'project',`,
|
|
8976
9012
|
` parameters: z.object({}),`,
|
|
8977
|
-
` handler: async (
|
|
8978
|
-
` return {
|
|
9013
|
+
` handler: async () => {`,
|
|
9014
|
+
` return ok({ endpoints: ['/api/data', '/api/status'] });`,
|
|
8979
9015
|
` },`,
|
|
8980
9016
|
` });`,
|
|
8981
9017
|
`}`
|
|
@@ -8983,14 +9019,21 @@ var TEMPLATES = {
|
|
|
8983
9019
|
},
|
|
8984
9020
|
{
|
|
8985
9021
|
path: "package.json",
|
|
8986
|
-
content:
|
|
8987
|
-
|
|
8988
|
-
"
|
|
8989
|
-
"
|
|
8990
|
-
|
|
8991
|
-
|
|
8992
|
-
}
|
|
8993
|
-
`
|
|
9022
|
+
content: [
|
|
9023
|
+
`{`,
|
|
9024
|
+
` "name": "xbrowser-plugin-{{projectName}}",`,
|
|
9025
|
+
` "version": "1.0.0",`,
|
|
9026
|
+
` "main": "index.ts",`,
|
|
9027
|
+
` "type": "module",`,
|
|
9028
|
+
` "dependencies": { "zod": "^3.24.0" },`,
|
|
9029
|
+
` "peerDependencies": { "@dyyz1993/xcli-core": ">=1.0.0" },`,
|
|
9030
|
+
` "xbrowser": {`,
|
|
9031
|
+
` "name": "{{projectName}}",`,
|
|
9032
|
+
` "description": "An API integration plugin",`,
|
|
9033
|
+
` "commands": ["fetch", "list-endpoints"]`,
|
|
9034
|
+
` }`,
|
|
9035
|
+
`}`
|
|
9036
|
+
].join("\n")
|
|
8994
9037
|
}
|
|
8995
9038
|
]
|
|
8996
9039
|
}
|
|
@@ -9497,10 +9540,10 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
|
|
|
9497
9540
|
break;
|
|
9498
9541
|
}
|
|
9499
9542
|
case "press": {
|
|
9500
|
-
const key = parsed.value || parsed.remaining[1];
|
|
9501
|
-
if (!
|
|
9543
|
+
const key = parsed.value || (sel ? parsed.remaining[1] : parsed.remaining[0]);
|
|
9544
|
+
if (!key) outputError("Usage: xbrowser press [selector] <key>");
|
|
9502
9545
|
cmdName = "press";
|
|
9503
|
-
params = { ...sel ? { selector: sel } : {}, key
|
|
9546
|
+
params = { ...sel ? { selector: sel } : {}, key };
|
|
9504
9547
|
break;
|
|
9505
9548
|
}
|
|
9506
9549
|
case "select": {
|
|
@@ -9569,13 +9612,22 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
|
|
|
9569
9612
|
params = { expression: args.join(" ") };
|
|
9570
9613
|
break;
|
|
9571
9614
|
case "scroll": {
|
|
9572
|
-
|
|
9573
|
-
|
|
9574
|
-
|
|
9615
|
+
let direction = "down";
|
|
9616
|
+
let distance;
|
|
9617
|
+
const firstArg = args[0];
|
|
9618
|
+
if (firstArg && ["up", "down", "left", "right"].includes(firstArg)) {
|
|
9619
|
+
direction = firstArg;
|
|
9620
|
+
if (args[1] && /^\d+$/.test(args[1])) distance = Number(args[1]);
|
|
9621
|
+
} else if (firstArg && /^\d+$/.test(firstArg)) {
|
|
9622
|
+
distance = Number(firstArg);
|
|
9623
|
+
} else if (firstArg) {
|
|
9624
|
+
outputError(`Invalid scroll argument: "${firstArg}". Use up/down/left/right or a pixel number.`);
|
|
9625
|
+
}
|
|
9626
|
+
distance = distance ?? (options.distance ? Number(options.distance) : options.amount ? Number(options.amount) : void 0);
|
|
9575
9627
|
cmdName = "scroll";
|
|
9576
9628
|
params = {
|
|
9577
9629
|
direction,
|
|
9578
|
-
distance
|
|
9630
|
+
distance,
|
|
9579
9631
|
selector: options.selector || options.s
|
|
9580
9632
|
};
|
|
9581
9633
|
break;
|
|
@@ -9588,6 +9640,14 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
|
|
|
9588
9640
|
cmdName = "url";
|
|
9589
9641
|
params = {};
|
|
9590
9642
|
break;
|
|
9643
|
+
case "set-viewport": {
|
|
9644
|
+
const width = options.width ? Number(options.width) : args[0] ? Number(args[0]) : void 0;
|
|
9645
|
+
const height = options.height ? Number(options.height) : args[1] ? Number(args[1]) : void 0;
|
|
9646
|
+
if (!width || !height) outputError("Usage: xbrowser set-viewport <width> <height>");
|
|
9647
|
+
cmdName = "set-viewport";
|
|
9648
|
+
params = { width, height };
|
|
9649
|
+
break;
|
|
9650
|
+
}
|
|
9591
9651
|
case "html":
|
|
9592
9652
|
cmdName = "html";
|
|
9593
9653
|
params = { selector: options.selector || options.s };
|
|
@@ -10124,6 +10184,11 @@ async function handlePlugin(args, options, mode) {
|
|
|
10124
10184
|
case "uninstall": {
|
|
10125
10185
|
const name = subArgs[0];
|
|
10126
10186
|
if (!name) outputError("Usage: xbrowser plugin uninstall <name>");
|
|
10187
|
+
const installed = await installer.list();
|
|
10188
|
+
const exists = installed.some((p) => p.name === name || p.metadata?.name === name);
|
|
10189
|
+
if (!exists) {
|
|
10190
|
+
outputError(`Plugin "${name}" is not installed. Use 'xbrowser plugin list' to see installed plugins.`);
|
|
10191
|
+
}
|
|
10127
10192
|
await installer.uninstall(name);
|
|
10128
10193
|
outputResult({ ok: true, name }, mode);
|
|
10129
10194
|
break;
|
|
@@ -12650,7 +12715,7 @@ async function main() {
|
|
|
12650
12715
|
const command = process.argv[2];
|
|
12651
12716
|
const isLongRunning = command === "preview" || command === "serve";
|
|
12652
12717
|
if (!isLongRunning) {
|
|
12653
|
-
const { ensureProcessCanExit } = await import("./browser-
|
|
12718
|
+
const { ensureProcessCanExit } = await import("./browser-U4VWPTS2.js");
|
|
12654
12719
|
await ensureProcessCanExit().catch(() => {
|
|
12655
12720
|
});
|
|
12656
12721
|
process.exit(exitCode);
|
package/dist/daemon-main.js
CHANGED
|
@@ -21,8 +21,8 @@ import {
|
|
|
21
21
|
resolveLaunchOpts,
|
|
22
22
|
saveSessionDiskMeta,
|
|
23
23
|
setActivePage
|
|
24
|
-
} from "./chunk-
|
|
25
|
-
import "./chunk-
|
|
24
|
+
} from "./chunk-MXG2H3HJ.js";
|
|
25
|
+
import "./chunk-SEFIJY2M.js";
|
|
26
26
|
import "./chunk-TNEN6VQ2.js";
|
|
27
27
|
import {
|
|
28
28
|
getDaemonConfig,
|
|
@@ -304,8 +304,13 @@ var gotoCommand = registerCommand({
|
|
|
304
304
|
}),
|
|
305
305
|
handler: async (p, ctx) => {
|
|
306
306
|
let url = p.url;
|
|
307
|
-
|
|
308
|
-
|
|
307
|
+
const hasScheme = /^(https?|wss?|file|about|data|chrome|blob):/i.test(url);
|
|
308
|
+
if (!hasScheme) {
|
|
309
|
+
if (/^[\w-]+(\.[\w-]+)+/.test(url) || url.startsWith("localhost")) {
|
|
310
|
+
url = "https://" + url;
|
|
311
|
+
} else {
|
|
312
|
+
throw new Error(`Invalid URL: "${url}". Expected http(s)://, file://, about:, data:, or a domain name.`);
|
|
313
|
+
}
|
|
309
314
|
}
|
|
310
315
|
const response = await ctx.page.goto(url, {
|
|
311
316
|
waitUntil: p.waitUntil || "domcontentloaded"
|
|
@@ -360,7 +365,7 @@ var urlCommand = registerCommand({
|
|
|
360
365
|
scope: "page",
|
|
361
366
|
result: z.object({ url: z.string() }),
|
|
362
367
|
handler: async (_p, ctx) => {
|
|
363
|
-
return ok({ url: ctx.page.url() });
|
|
368
|
+
return ok({ url: ctx.page.url() || "about:blank" });
|
|
364
369
|
}
|
|
365
370
|
});
|
|
366
371
|
registerCommand({
|
|
@@ -523,7 +528,8 @@ var fillCommand = registerCommand({
|
|
|
523
528
|
}),
|
|
524
529
|
handler: async (p, ctx) => {
|
|
525
530
|
const page = ctx.page;
|
|
526
|
-
|
|
531
|
+
const shouldClear = p.clear !== false;
|
|
532
|
+
if (shouldClear) {
|
|
527
533
|
await page.fill(p.selector, "", { force: true, timeout: 1e4 });
|
|
528
534
|
}
|
|
529
535
|
const isReact = await page.evaluate(() => {
|
|
@@ -549,7 +555,7 @@ var fillCommand = registerCommand({
|
|
|
549
555
|
} else {
|
|
550
556
|
await page.fill(p.selector, p.value, { force: true, timeout: 1e4 });
|
|
551
557
|
}
|
|
552
|
-
return ok2({ selector: p.selector, value: p.value, cleared:
|
|
558
|
+
return ok2({ selector: p.selector, value: p.value, cleared: shouldClear, reactMode: !!isReact });
|
|
553
559
|
}
|
|
554
560
|
});
|
|
555
561
|
var typeCommand = registerCommand({
|
|
@@ -2083,6 +2089,7 @@ function createTurndown() {
|
|
|
2083
2089
|
return turndown;
|
|
2084
2090
|
}
|
|
2085
2091
|
function postClean(md) {
|
|
2092
|
+
md = md.replace(/<(?:table|div|tbody|thead|tr|td|th|span|colgroup|col)\b[^>]*(?:>[\s\S]{200,}?<\/(?:table|div|tbody|thead|tr|td|th|span|colgroup|col)>)/g, "\n[\u26A0\uFE0F HTML block removed \u2014 complex table/layout not converted to Markdown]\n");
|
|
2086
2093
|
md = md.replace(/\n{3,}/g, "\n\n");
|
|
2087
2094
|
md = md.replace(/!\[[^\]]*\]\(\s*\)/g, "");
|
|
2088
2095
|
md = md.replace(/\[([^\]]*)\]\(\s*\)/g, "$1");
|
|
@@ -6918,7 +6925,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6918
6925
|
}
|
|
6919
6926
|
let targetPageOverride = null;
|
|
6920
6927
|
if (_target && extraOpts?.cdpEndpoint) {
|
|
6921
|
-
const { findTargetPage } = await import("./browser-
|
|
6928
|
+
const { findTargetPage } = await import("./browser-AXCKBSWS.js");
|
|
6922
6929
|
targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
|
|
6923
6930
|
if (!targetPageOverride) {
|
|
6924
6931
|
return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
|
|
@@ -8602,7 +8609,7 @@ function createRPCHandler() {
|
|
|
8602
8609
|
const isNewFormat = Array.isArray(parsed.actions);
|
|
8603
8610
|
if (isNewFormat) {
|
|
8604
8611
|
try {
|
|
8605
|
-
const { SessionReplayer } = await import("./session-replayer-
|
|
8612
|
+
const { SessionReplayer } = await import("./session-replayer-GCGY6KFK.js");
|
|
8606
8613
|
const replayer = new SessionReplayer({
|
|
8607
8614
|
page: session.page,
|
|
8608
8615
|
stepDelay: slowMo * 500,
|
package/dist/index.js
CHANGED
|
@@ -81,8 +81,8 @@ import {
|
|
|
81
81
|
resolveLaunchOpts,
|
|
82
82
|
saveSessionDiskMeta,
|
|
83
83
|
setActivePage
|
|
84
|
-
} from "./chunk-
|
|
85
|
-
import "./chunk-
|
|
84
|
+
} from "./chunk-5QAYN5EZ.js";
|
|
85
|
+
import "./chunk-SEFIJY2M.js";
|
|
86
86
|
import "./chunk-TNEN6VQ2.js";
|
|
87
87
|
import {
|
|
88
88
|
errMsg
|
|
@@ -386,8 +386,13 @@ var gotoCommand = registerCommand({
|
|
|
386
386
|
}),
|
|
387
387
|
handler: async (p, ctx) => {
|
|
388
388
|
let url = p.url;
|
|
389
|
-
|
|
390
|
-
|
|
389
|
+
const hasScheme = /^(https?|wss?|file|about|data|chrome|blob):/i.test(url);
|
|
390
|
+
if (!hasScheme) {
|
|
391
|
+
if (/^[\w-]+(\.[\w-]+)+/.test(url) || url.startsWith("localhost")) {
|
|
392
|
+
url = "https://" + url;
|
|
393
|
+
} else {
|
|
394
|
+
throw new Error(`Invalid URL: "${url}". Expected http(s)://, file://, about:, data:, or a domain name.`);
|
|
395
|
+
}
|
|
391
396
|
}
|
|
392
397
|
const response = await ctx.page.goto(url, {
|
|
393
398
|
waitUntil: p.waitUntil || "domcontentloaded"
|
|
@@ -442,7 +447,7 @@ var urlCommand = registerCommand({
|
|
|
442
447
|
scope: "page",
|
|
443
448
|
result: z.object({ url: z.string() }),
|
|
444
449
|
handler: async (_p, ctx) => {
|
|
445
|
-
return ok({ url: ctx.page.url() });
|
|
450
|
+
return ok({ url: ctx.page.url() || "about:blank" });
|
|
446
451
|
}
|
|
447
452
|
});
|
|
448
453
|
registerCommand({
|
|
@@ -605,7 +610,8 @@ var fillCommand = registerCommand({
|
|
|
605
610
|
}),
|
|
606
611
|
handler: async (p, ctx) => {
|
|
607
612
|
const page = ctx.page;
|
|
608
|
-
|
|
613
|
+
const shouldClear = p.clear !== false;
|
|
614
|
+
if (shouldClear) {
|
|
609
615
|
await page.fill(p.selector, "", { force: true, timeout: 1e4 });
|
|
610
616
|
}
|
|
611
617
|
const isReact = await page.evaluate(() => {
|
|
@@ -631,7 +637,7 @@ var fillCommand = registerCommand({
|
|
|
631
637
|
} else {
|
|
632
638
|
await page.fill(p.selector, p.value, { force: true, timeout: 1e4 });
|
|
633
639
|
}
|
|
634
|
-
return ok2({ selector: p.selector, value: p.value, cleared:
|
|
640
|
+
return ok2({ selector: p.selector, value: p.value, cleared: shouldClear, reactMode: !!isReact });
|
|
635
641
|
}
|
|
636
642
|
});
|
|
637
643
|
var typeCommand = registerCommand({
|
|
@@ -2165,6 +2171,7 @@ function createTurndown() {
|
|
|
2165
2171
|
return turndown;
|
|
2166
2172
|
}
|
|
2167
2173
|
function postClean(md) {
|
|
2174
|
+
md = md.replace(/<(?:table|div|tbody|thead|tr|td|th|span|colgroup|col)\b[^>]*(?:>[\s\S]{200,}?<\/(?:table|div|tbody|thead|tr|td|th|span|colgroup|col)>)/g, "\n[\u26A0\uFE0F HTML block removed \u2014 complex table/layout not converted to Markdown]\n");
|
|
2168
2175
|
md = md.replace(/\n{3,}/g, "\n\n");
|
|
2169
2176
|
md = md.replace(/!\[[^\]]*\]\(\s*\)/g, "");
|
|
2170
2177
|
md = md.replace(/\[([^\]]*)\]\(\s*\)/g, "$1");
|
|
@@ -7280,7 +7287,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7280
7287
|
}
|
|
7281
7288
|
let targetPageOverride = null;
|
|
7282
7289
|
if (_target && extraOpts?.cdpEndpoint) {
|
|
7283
|
-
const { findTargetPage } = await import("./browser-
|
|
7290
|
+
const { findTargetPage } = await import("./browser-X7OVRKJH.js");
|
|
7284
7291
|
targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
|
|
7285
7292
|
if (!targetPageOverride) {
|
|
7286
7293
|
return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
|
|
@@ -9139,6 +9146,8 @@ var TEMPLATES = {
|
|
|
9139
9146
|
{
|
|
9140
9147
|
path: "index.ts",
|
|
9141
9148
|
content: [
|
|
9149
|
+
`import { z } from 'zod';`,
|
|
9150
|
+
`import { ok } from '@dyyz1993/xcli-core';`,
|
|
9142
9151
|
`import type { XCLIAPI } from '@dyyz1993/xcli-core';`,
|
|
9143
9152
|
``,
|
|
9144
9153
|
`export default function (xcli: XCLIAPI): void {`,
|
|
@@ -9156,7 +9165,7 @@ var TEMPLATES = {
|
|
|
9156
9165
|
` (sel: string) => document.querySelector(sel)?.textContent ?? '',`,
|
|
9157
9166
|
` params.selector || 'body'`,
|
|
9158
9167
|
` );`,
|
|
9159
|
-
` return {
|
|
9168
|
+
` return ok({ text });`,
|
|
9160
9169
|
` },`,
|
|
9161
9170
|
` });`,
|
|
9162
9171
|
`}`
|
|
@@ -9164,14 +9173,21 @@ var TEMPLATES = {
|
|
|
9164
9173
|
},
|
|
9165
9174
|
{
|
|
9166
9175
|
path: "package.json",
|
|
9167
|
-
content:
|
|
9168
|
-
|
|
9169
|
-
"
|
|
9170
|
-
"
|
|
9171
|
-
|
|
9172
|
-
|
|
9173
|
-
}
|
|
9174
|
-
`
|
|
9176
|
+
content: [
|
|
9177
|
+
`{`,
|
|
9178
|
+
` "name": "xbrowser-plugin-{{projectName}}",`,
|
|
9179
|
+
` "version": "1.0.0",`,
|
|
9180
|
+
` "main": "index.ts",`,
|
|
9181
|
+
` "type": "module",`,
|
|
9182
|
+
` "dependencies": { "zod": "^3.24.0" },`,
|
|
9183
|
+
` "peerDependencies": { "@dyyz1993/xcli-core": ">=1.0.0" },`,
|
|
9184
|
+
` "xbrowser": {`,
|
|
9185
|
+
` "name": "{{projectName}}",`,
|
|
9186
|
+
` "description": "A static page plugin",`,
|
|
9187
|
+
` "commands": ["scrape"]`,
|
|
9188
|
+
` }`,
|
|
9189
|
+
`}`
|
|
9190
|
+
].join("\n")
|
|
9175
9191
|
}
|
|
9176
9192
|
]
|
|
9177
9193
|
},
|
|
@@ -9182,6 +9198,8 @@ var TEMPLATES = {
|
|
|
9182
9198
|
{
|
|
9183
9199
|
path: "index.ts",
|
|
9184
9200
|
content: [
|
|
9201
|
+
`import { z } from 'zod';`,
|
|
9202
|
+
`import { ok } from '@dyyz1993/xcli-core';`,
|
|
9185
9203
|
`import type { XCLIAPI } from '@dyyz1993/xcli-core';`,
|
|
9186
9204
|
``,
|
|
9187
9205
|
`export default function (xcli: XCLIAPI): void {`,
|
|
@@ -9196,7 +9214,7 @@ var TEMPLATES = {
|
|
|
9196
9214
|
` parameters: z.object({ url: z.string() }),`,
|
|
9197
9215
|
` handler: async (params, ctx) => {`,
|
|
9198
9216
|
` await ctx.page.goto(params.url);`,
|
|
9199
|
-
` return {
|
|
9217
|
+
` return ok({ url: params.url });`,
|
|
9200
9218
|
` },`,
|
|
9201
9219
|
` });`,
|
|
9202
9220
|
``,
|
|
@@ -9211,7 +9229,7 @@ var TEMPLATES = {
|
|
|
9211
9229
|
` case 'fill': await page.fill(params.selector, ''); break;`,
|
|
9212
9230
|
` case 'hover': await page.hover(params.selector); break;`,
|
|
9213
9231
|
` }`,
|
|
9214
|
-
` return {
|
|
9232
|
+
` return ok({ action: params.action, selector: params.selector });`,
|
|
9215
9233
|
` },`,
|
|
9216
9234
|
` });`,
|
|
9217
9235
|
`}`
|
|
@@ -9219,14 +9237,21 @@ var TEMPLATES = {
|
|
|
9219
9237
|
},
|
|
9220
9238
|
{
|
|
9221
9239
|
path: "package.json",
|
|
9222
|
-
content:
|
|
9223
|
-
|
|
9224
|
-
"
|
|
9225
|
-
"
|
|
9226
|
-
|
|
9227
|
-
|
|
9228
|
-
}
|
|
9229
|
-
`
|
|
9240
|
+
content: [
|
|
9241
|
+
`{`,
|
|
9242
|
+
` "name": "xbrowser-plugin-{{projectName}}",`,
|
|
9243
|
+
` "version": "1.0.0",`,
|
|
9244
|
+
` "main": "index.ts",`,
|
|
9245
|
+
` "type": "module",`,
|
|
9246
|
+
` "dependencies": { "zod": "^3.24.0" },`,
|
|
9247
|
+
` "peerDependencies": { "@dyyz1993/xcli-core": ">=1.0.0" },`,
|
|
9248
|
+
` "xbrowser": {`,
|
|
9249
|
+
` "name": "{{projectName}}",`,
|
|
9250
|
+
` "description": "A dynamic page plugin",`,
|
|
9251
|
+
` "commands": ["navigate", "interact"]`,
|
|
9252
|
+
` }`,
|
|
9253
|
+
`}`
|
|
9254
|
+
].join("\n")
|
|
9230
9255
|
}
|
|
9231
9256
|
]
|
|
9232
9257
|
},
|
|
@@ -9237,6 +9262,8 @@ var TEMPLATES = {
|
|
|
9237
9262
|
{
|
|
9238
9263
|
path: "index.ts",
|
|
9239
9264
|
content: [
|
|
9265
|
+
`import { z } from 'zod';`,
|
|
9266
|
+
`import { ok } from '@dyyz1993/xcli-core';`,
|
|
9240
9267
|
`import type { XCLIAPI } from '@dyyz1993/xcli-core';`,
|
|
9241
9268
|
``,
|
|
9242
9269
|
`export default function (xcli: XCLIAPI): void {`,
|
|
@@ -9250,14 +9277,13 @@ var TEMPLATES = {
|
|
|
9250
9277
|
` description: 'Check login status',`,
|
|
9251
9278
|
` scope: 'project',`,
|
|
9252
9279
|
` parameters: z.object({}),`,
|
|
9253
|
-
` handler: async (
|
|
9280
|
+
` handler: async () => {`,
|
|
9254
9281
|
` const loggedIn = await site.isLoggedIn();`,
|
|
9255
|
-
` return {
|
|
9282
|
+
` return ok({ loggedIn });`,
|
|
9256
9283
|
` },`,
|
|
9257
9284
|
` });`,
|
|
9258
9285
|
``,
|
|
9259
9286
|
` site.login(async (ctx) => {`,
|
|
9260
|
-
` console.log('Login handler for {{projectName}}');`,
|
|
9261
9287
|
` await ctx.storage.set('auth_token', 'dummy');`,
|
|
9262
9288
|
` });`,
|
|
9263
9289
|
``,
|
|
@@ -9269,14 +9295,22 @@ var TEMPLATES = {
|
|
|
9269
9295
|
},
|
|
9270
9296
|
{
|
|
9271
9297
|
path: "package.json",
|
|
9272
|
-
content:
|
|
9273
|
-
|
|
9274
|
-
"
|
|
9275
|
-
"
|
|
9276
|
-
|
|
9277
|
-
|
|
9278
|
-
}
|
|
9279
|
-
`
|
|
9298
|
+
content: [
|
|
9299
|
+
`{`,
|
|
9300
|
+
` "name": "xbrowser-plugin-{{projectName}}",`,
|
|
9301
|
+
` "version": "1.0.0",`,
|
|
9302
|
+
` "main": "index.ts",`,
|
|
9303
|
+
` "type": "module",`,
|
|
9304
|
+
` "dependencies": { "zod": "^3.24.0" },`,
|
|
9305
|
+
` "peerDependencies": { "@dyyz1993/xcli-core": ">=1.0.0" },`,
|
|
9306
|
+
` "xbrowser": {`,
|
|
9307
|
+
` "name": "{{projectName}}",`,
|
|
9308
|
+
` "description": "A plugin with login/logout support",`,
|
|
9309
|
+
` "requiresLogin": true,`,
|
|
9310
|
+
` "commands": ["check"]`,
|
|
9311
|
+
` }`,
|
|
9312
|
+
`}`
|
|
9313
|
+
].join("\n")
|
|
9280
9314
|
}
|
|
9281
9315
|
]
|
|
9282
9316
|
},
|
|
@@ -9287,6 +9321,8 @@ var TEMPLATES = {
|
|
|
9287
9321
|
{
|
|
9288
9322
|
path: "index.ts",
|
|
9289
9323
|
content: [
|
|
9324
|
+
`import { z } from 'zod';`,
|
|
9325
|
+
`import { ok } from '@dyyz1993/xcli-core';`,
|
|
9290
9326
|
`import type { XCLIAPI } from '@dyyz1993/xcli-core';`,
|
|
9291
9327
|
``,
|
|
9292
9328
|
`export default function (xcli: XCLIAPI): void {`,
|
|
@@ -9299,9 +9335,9 @@ var TEMPLATES = {
|
|
|
9299
9335
|
` description: 'Fetch data from API',`,
|
|
9300
9336
|
` scope: 'project',`,
|
|
9301
9337
|
` parameters: z.object({ endpoint: z.string(), method: z.enum(['GET', 'POST']).optional() }),`,
|
|
9302
|
-
` handler: async (params
|
|
9338
|
+
` handler: async (params) => {`,
|
|
9303
9339
|
` const method = params.method || 'GET';`,
|
|
9304
|
-
` return {
|
|
9340
|
+
` return ok({ endpoint: params.endpoint, method });`,
|
|
9305
9341
|
` },`,
|
|
9306
9342
|
` });`,
|
|
9307
9343
|
``,
|
|
@@ -9309,8 +9345,8 @@ var TEMPLATES = {
|
|
|
9309
9345
|
` description: 'List available endpoints',`,
|
|
9310
9346
|
` scope: 'project',`,
|
|
9311
9347
|
` parameters: z.object({}),`,
|
|
9312
|
-
` handler: async (
|
|
9313
|
-
` return {
|
|
9348
|
+
` handler: async () => {`,
|
|
9349
|
+
` return ok({ endpoints: ['/api/data', '/api/status'] });`,
|
|
9314
9350
|
` },`,
|
|
9315
9351
|
` });`,
|
|
9316
9352
|
`}`
|
|
@@ -9318,14 +9354,21 @@ var TEMPLATES = {
|
|
|
9318
9354
|
},
|
|
9319
9355
|
{
|
|
9320
9356
|
path: "package.json",
|
|
9321
|
-
content:
|
|
9322
|
-
|
|
9323
|
-
"
|
|
9324
|
-
"
|
|
9325
|
-
|
|
9326
|
-
|
|
9327
|
-
}
|
|
9328
|
-
`
|
|
9357
|
+
content: [
|
|
9358
|
+
`{`,
|
|
9359
|
+
` "name": "xbrowser-plugin-{{projectName}}",`,
|
|
9360
|
+
` "version": "1.0.0",`,
|
|
9361
|
+
` "main": "index.ts",`,
|
|
9362
|
+
` "type": "module",`,
|
|
9363
|
+
` "dependencies": { "zod": "^3.24.0" },`,
|
|
9364
|
+
` "peerDependencies": { "@dyyz1993/xcli-core": ">=1.0.0" },`,
|
|
9365
|
+
` "xbrowser": {`,
|
|
9366
|
+
` "name": "{{projectName}}",`,
|
|
9367
|
+
` "description": "An API integration plugin",`,
|
|
9368
|
+
` "commands": ["fetch", "list-endpoints"]`,
|
|
9369
|
+
` }`,
|
|
9370
|
+
`}`
|
|
9371
|
+
].join("\n")
|
|
9329
9372
|
}
|
|
9330
9373
|
]
|
|
9331
9374
|
}
|
|
@@ -9837,10 +9880,10 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
|
|
|
9837
9880
|
break;
|
|
9838
9881
|
}
|
|
9839
9882
|
case "press": {
|
|
9840
|
-
const key = parsed.value || parsed.remaining[1];
|
|
9841
|
-
if (!
|
|
9883
|
+
const key = parsed.value || (sel ? parsed.remaining[1] : parsed.remaining[0]);
|
|
9884
|
+
if (!key) outputError("Usage: xbrowser press [selector] <key>");
|
|
9842
9885
|
cmdName = "press";
|
|
9843
|
-
params = { ...sel ? { selector: sel } : {}, key
|
|
9886
|
+
params = { ...sel ? { selector: sel } : {}, key };
|
|
9844
9887
|
break;
|
|
9845
9888
|
}
|
|
9846
9889
|
case "select": {
|
|
@@ -9909,13 +9952,22 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
|
|
|
9909
9952
|
params = { expression: args.join(" ") };
|
|
9910
9953
|
break;
|
|
9911
9954
|
case "scroll": {
|
|
9912
|
-
|
|
9913
|
-
|
|
9914
|
-
|
|
9955
|
+
let direction = "down";
|
|
9956
|
+
let distance;
|
|
9957
|
+
const firstArg = args[0];
|
|
9958
|
+
if (firstArg && ["up", "down", "left", "right"].includes(firstArg)) {
|
|
9959
|
+
direction = firstArg;
|
|
9960
|
+
if (args[1] && /^\d+$/.test(args[1])) distance = Number(args[1]);
|
|
9961
|
+
} else if (firstArg && /^\d+$/.test(firstArg)) {
|
|
9962
|
+
distance = Number(firstArg);
|
|
9963
|
+
} else if (firstArg) {
|
|
9964
|
+
outputError(`Invalid scroll argument: "${firstArg}". Use up/down/left/right or a pixel number.`);
|
|
9965
|
+
}
|
|
9966
|
+
distance = distance ?? (options.distance ? Number(options.distance) : options.amount ? Number(options.amount) : void 0);
|
|
9915
9967
|
cmdName = "scroll";
|
|
9916
9968
|
params = {
|
|
9917
9969
|
direction,
|
|
9918
|
-
distance
|
|
9970
|
+
distance,
|
|
9919
9971
|
selector: options.selector || options.s
|
|
9920
9972
|
};
|
|
9921
9973
|
break;
|
|
@@ -9928,6 +9980,14 @@ async function handleBrowserCommand(command, args, options, sessionName, mode, c
|
|
|
9928
9980
|
cmdName = "url";
|
|
9929
9981
|
params = {};
|
|
9930
9982
|
break;
|
|
9983
|
+
case "set-viewport": {
|
|
9984
|
+
const width = options.width ? Number(options.width) : args[0] ? Number(args[0]) : void 0;
|
|
9985
|
+
const height = options.height ? Number(options.height) : args[1] ? Number(args[1]) : void 0;
|
|
9986
|
+
if (!width || !height) outputError("Usage: xbrowser set-viewport <width> <height>");
|
|
9987
|
+
cmdName = "set-viewport";
|
|
9988
|
+
params = { width, height };
|
|
9989
|
+
break;
|
|
9990
|
+
}
|
|
9931
9991
|
case "html":
|
|
9932
9992
|
cmdName = "html";
|
|
9933
9993
|
params = { selector: options.selector || options.s };
|
|
@@ -10464,6 +10524,11 @@ async function handlePlugin(args, options, mode) {
|
|
|
10464
10524
|
case "uninstall": {
|
|
10465
10525
|
const name = subArgs[0];
|
|
10466
10526
|
if (!name) outputError("Usage: xbrowser plugin uninstall <name>");
|
|
10527
|
+
const installed = await installer.list();
|
|
10528
|
+
const exists = installed.some((p) => p.name === name || p.metadata?.name === name);
|
|
10529
|
+
if (!exists) {
|
|
10530
|
+
outputError(`Plugin "${name}" is not installed. Use 'xbrowser plugin list' to see installed plugins.`);
|
|
10531
|
+
}
|
|
10467
10532
|
await installer.uninstall(name);
|
|
10468
10533
|
outputResult({ ok: true, name }, mode);
|
|
10469
10534
|
break;
|
|
@@ -15777,7 +15842,7 @@ var DataCollector = class {
|
|
|
15777
15842
|
return results;
|
|
15778
15843
|
}
|
|
15779
15844
|
async createBrowserContext() {
|
|
15780
|
-
const { launch } = await import("./cdp-driver-
|
|
15845
|
+
const { launch } = await import("./cdp-driver-ZAVN7GRB.js");
|
|
15781
15846
|
const { browser } = await launch({
|
|
15782
15847
|
headless: true,
|
|
15783
15848
|
args: ["--no-sandbox", "--disable-setuid-sandbox"]
|
|
@@ -31,7 +31,7 @@ var SessionReplayer = class {
|
|
|
31
31
|
if (this.opts.page) {
|
|
32
32
|
this.page = this.opts.page;
|
|
33
33
|
} else if (this.opts.cdpUrl) {
|
|
34
|
-
const { launch } = await import("./cdp-driver-
|
|
34
|
+
const { launch } = await import("./cdp-driver-ZAVN7GRB.js");
|
|
35
35
|
const { browser } = await launch({ cdpEndpoint: this.opts.cdpUrl });
|
|
36
36
|
let contexts = browser.contexts();
|
|
37
37
|
for (let i = 0; i < 10 && contexts.length === 0; i++) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xbrowser/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Browser automation CLI for web scraping, headless browsing, SEO analysis, and AI agent workflows. A command-line alternative to Playwright, Puppeteer, and Selenium.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|