browse-agent-cli 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +125 -0
- package/dist/clear.js +24 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +501 -0
- package/dist/config.js +218 -0
- package/dist/script.d.ts +113 -0
- package/dist/script.js +53 -0
- package/dist/service.d.ts +1 -0
- package/dist/service.js +156 -0
- package/dist/tabs.js +126 -0
- package/package.json +40 -0
- package/skill/SKILL.md +126 -0
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# browse-agent-cli
|
|
2
|
+
|
|
3
|
+
`browse-agent-cli` is an npm package that provides:
|
|
4
|
+
|
|
5
|
+
- A CLI command: `browse-agent`
|
|
6
|
+
- Script-style exports for Node.js: `browse-agent-cli/script`
|
|
7
|
+
|
|
8
|
+
It is designed for browser automation workflows powered by `browse-agent-sdk`.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
### Global install (recommended for CLI)
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install -g browse-agent-cli
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
After installation:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
browse-agent --help
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Project install (for script imports)
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install browse-agent-cli
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick Start (CLI)
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# One-time setup
|
|
34
|
+
browse-agent setup
|
|
35
|
+
|
|
36
|
+
# Launch browser session
|
|
37
|
+
browse-agent launch
|
|
38
|
+
|
|
39
|
+
# Navigate and extract
|
|
40
|
+
browse-agent navigate "https://example.com"
|
|
41
|
+
browse-agent get-content --format text
|
|
42
|
+
|
|
43
|
+
# Close session
|
|
44
|
+
browse-agent close
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## CLI Commands
|
|
48
|
+
|
|
49
|
+
### Lifecycle
|
|
50
|
+
|
|
51
|
+
- `browse-agent setup`
|
|
52
|
+
- `browse-agent launch`
|
|
53
|
+
- `browse-agent connect`
|
|
54
|
+
- `browse-agent close`
|
|
55
|
+
- `browse-agent clear`
|
|
56
|
+
|
|
57
|
+
### Feature commands
|
|
58
|
+
|
|
59
|
+
- `browse-agent navigate <url>`
|
|
60
|
+
- `browse-agent get-content [--format text|html] [--tabId <id>]`
|
|
61
|
+
- `browse-agent get-dom <selector> [--property outerHTML|innerHTML|innerText] [--all] [--tabId <id>]`
|
|
62
|
+
- `browse-agent evaluate <expression> [--tabId <id>]`
|
|
63
|
+
- `browse-agent inject-script <code> [--tabId <id>]`
|
|
64
|
+
- `browse-agent inject-css <code> [--tabId <id>]`
|
|
65
|
+
- `browse-agent screenshot [visible|fullPage|area] [--format png|jpeg] [--quality <1-100>] [--tabId <id>]`
|
|
66
|
+
- `browse-agent tabs [list|close|activate] [tabId]`
|
|
67
|
+
|
|
68
|
+
### Skill command
|
|
69
|
+
|
|
70
|
+
- `browse-agent skill <directory>`
|
|
71
|
+
|
|
72
|
+
This command copies the package `skill` folder into:
|
|
73
|
+
|
|
74
|
+
- `<directory>/browse-agent`
|
|
75
|
+
|
|
76
|
+
Conflict behavior when target exists and is not empty:
|
|
77
|
+
|
|
78
|
+
- `overwrite`: remove and replace target folder
|
|
79
|
+
- `rename`: enter a new directory name, then retry
|
|
80
|
+
- `cancel`: abort operation
|
|
81
|
+
|
|
82
|
+
## Script Import
|
|
83
|
+
|
|
84
|
+
Import helper functions from `browse-agent-cli/script`:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { browse } from 'browse-agent-cli/script';
|
|
88
|
+
|
|
89
|
+
await browse(async (agent) => {
|
|
90
|
+
await agent.navigate('https://example.com');
|
|
91
|
+
const page = await agent.getContent({ format: 'text' });
|
|
92
|
+
return { title: page.title, content: page.content };
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
You can also import modular helpers:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import {
|
|
100
|
+
launchBrowser,
|
|
101
|
+
navigate,
|
|
102
|
+
getContent,
|
|
103
|
+
screenshot,
|
|
104
|
+
closeBrowser,
|
|
105
|
+
} from 'browse-agent-cli/script';
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Common Options
|
|
109
|
+
|
|
110
|
+
- `--browser <name>`: `chrome | chromium | edge | brave`
|
|
111
|
+
- `--headless`
|
|
112
|
+
- `--port <number>`
|
|
113
|
+
- `--servicePort <number>`
|
|
114
|
+
- `--timeout <ms>`
|
|
115
|
+
- `--tabId <id>`
|
|
116
|
+
|
|
117
|
+
## Notes
|
|
118
|
+
|
|
119
|
+
- Node.js 18+ is required.
|
|
120
|
+
- One of Chrome/Chromium/Edge/Brave is required.
|
|
121
|
+
- For logged-in pages, ensure your browser profile/session strategy matches your environment.
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
MIT
|
package/dist/clear.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { t as BASE_DIR, u as loadSession } from "./config.js";
|
|
2
|
+
import { existsSync, rmSync } from "node:fs";
|
|
3
|
+
//#region src/scripts/clear.ts
|
|
4
|
+
async function clear() {
|
|
5
|
+
const baseDir = BASE_DIR;
|
|
6
|
+
console.log("Clearing browse-agent...");
|
|
7
|
+
const session = loadSession();
|
|
8
|
+
if (session?.pid) try {
|
|
9
|
+
process.kill(session.pid);
|
|
10
|
+
console.log(` Killed browser (PID: ${session.pid})`);
|
|
11
|
+
} catch (err) {
|
|
12
|
+
const killErr = err;
|
|
13
|
+
if (killErr.code !== "ESRCH") console.error(` Failed to kill browser (PID: ${session.pid}):`, killErr.message);
|
|
14
|
+
}
|
|
15
|
+
if (existsSync(baseDir)) {
|
|
16
|
+
rmSync(baseDir, { recursive: true });
|
|
17
|
+
console.log(` Removed ${baseDir}`);
|
|
18
|
+
} else console.log(` ${baseDir} does not exist, skipping.`);
|
|
19
|
+
console.log("Done.\n");
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { clear as t };
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=clear.js.map
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { l as loadServiceSession, n as DEFAULT_SERVICE_PORT, t as BASE_DIR } from "./config.js";
|
|
3
|
+
import { t as clear } from "./clear.js";
|
|
4
|
+
import { execSync, spawn } from "node:child_process";
|
|
5
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, rmSync, statSync, unlinkSync } from "node:fs";
|
|
6
|
+
import { dirname, join, resolve } from "node:path";
|
|
7
|
+
import { createInterface } from "node:readline/promises";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
//#region src/scripts/setup.ts
|
|
10
|
+
async function setup() {
|
|
11
|
+
const baseDir = BASE_DIR;
|
|
12
|
+
const extensionDir = join(baseDir, "extension");
|
|
13
|
+
const zipPath = join(baseDir, "extension.zip");
|
|
14
|
+
const extensionInstalled = existsSync(join(extensionDir, "manifest.json"));
|
|
15
|
+
if (extensionInstalled) {
|
|
16
|
+
console.log("browse-agent is already set up.");
|
|
17
|
+
console.log(` Extension path: ${extensionDir}`);
|
|
18
|
+
console.log(" Extension: installed\n");
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
console.log("Setting up browse-agent...\n");
|
|
22
|
+
mkdirSync(baseDir, { recursive: true });
|
|
23
|
+
if (extensionInstalled) {
|
|
24
|
+
console.log("\n[1/2] Downloading Chrome extension from latest release...");
|
|
25
|
+
console.log(" Skipped: extension is already installed.");
|
|
26
|
+
console.log("\n[2/2] Extracting extension...");
|
|
27
|
+
console.log(" Skipped: extension is already installed.");
|
|
28
|
+
} else {
|
|
29
|
+
console.log("\n[1/2] Downloading Chrome extension from latest release...");
|
|
30
|
+
const releaseJson = execSync(`curl -s "https://api.github.com/repos/imlinhanchao/browse-agent/releases/latest"`).toString();
|
|
31
|
+
const asset = JSON.parse(releaseJson).assets?.find((item) => item.name.endsWith(".zip"));
|
|
32
|
+
if (!asset) {
|
|
33
|
+
console.error("Error: No extension zip found in latest release.");
|
|
34
|
+
console.error("Visit https://github.com/imlinhanchao/browse-agent/releases to check.");
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
console.log(` Downloading ${asset.name} (${(asset.size / 1024).toFixed(1)} KB)...`);
|
|
38
|
+
execSync(`curl -sL -o "${zipPath}" "${asset.browser_download_url}"`);
|
|
39
|
+
console.log("\n[2/2] Extracting extension...");
|
|
40
|
+
if (existsSync(extensionDir)) execSync(`rm -rf "${extensionDir}"`);
|
|
41
|
+
mkdirSync(extensionDir, { recursive: true });
|
|
42
|
+
execSync(`unzip -o "${zipPath}" -d "${extensionDir}"`, { stdio: "pipe" });
|
|
43
|
+
unlinkSync(zipPath);
|
|
44
|
+
const files = readdirSync(extensionDir);
|
|
45
|
+
if (!files.includes("manifest.json")) {
|
|
46
|
+
const subdirs = files.filter((file) => {
|
|
47
|
+
try {
|
|
48
|
+
return readdirSync(join(extensionDir, file)).includes("manifest.json");
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
if (subdirs.length > 0) {
|
|
54
|
+
const subdir = join(extensionDir, subdirs[0]);
|
|
55
|
+
execSync(`mv "${subdir}"/* "${extensionDir}"/`);
|
|
56
|
+
execSync(`rmdir "${subdir}"`);
|
|
57
|
+
} else {
|
|
58
|
+
console.error("Error: Extension extraction failed - manifest.json not found.");
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
console.log("\nSetup complete!");
|
|
64
|
+
console.log(` Extension path: ${extensionDir}`);
|
|
65
|
+
console.log(" Extension: installed\n");
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/cli.ts
|
|
69
|
+
function parseArgs(argv) {
|
|
70
|
+
const args = argv.slice(2);
|
|
71
|
+
const command = args.find((a) => !a.startsWith("-"));
|
|
72
|
+
const positional = args.filter((a) => !a.startsWith("-"));
|
|
73
|
+
const flags = {};
|
|
74
|
+
for (let i = 0; i < args.length; i++) {
|
|
75
|
+
const arg = args[i];
|
|
76
|
+
if (arg === "--headless") {
|
|
77
|
+
flags.headless = true;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (arg === "--all") {
|
|
81
|
+
flags.all = true;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (arg.startsWith("--") && i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
|
85
|
+
flags[arg.slice(2)] = args[++i];
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
command,
|
|
91
|
+
positional: positional.slice(1),
|
|
92
|
+
flags
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function getStringFlag(flags, key) {
|
|
96
|
+
const value = flags[key];
|
|
97
|
+
return typeof value === "string" ? value : void 0;
|
|
98
|
+
}
|
|
99
|
+
function getNumberFlag(flags, key) {
|
|
100
|
+
const value = getStringFlag(flags, key);
|
|
101
|
+
if (!value) return void 0;
|
|
102
|
+
const parsed = Number(value);
|
|
103
|
+
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
104
|
+
}
|
|
105
|
+
function showHelp() {
|
|
106
|
+
console.log(`
|
|
107
|
+
browse-agent - Browser automation skill CLI
|
|
108
|
+
|
|
109
|
+
Usage: browse-agent <command> [options]
|
|
110
|
+
|
|
111
|
+
Lifecycle commands:
|
|
112
|
+
setup Install Chrome extension
|
|
113
|
+
launch Start background service and launch browser
|
|
114
|
+
connect Verify connection to running service/browser
|
|
115
|
+
close Stop browser and background service
|
|
116
|
+
clear Remove local installation data
|
|
117
|
+
skill <dir> Copy skill bundle into <dir>/browse-agent
|
|
118
|
+
|
|
119
|
+
Feature commands (require launch first):
|
|
120
|
+
navigate <url> Navigate to URL
|
|
121
|
+
get-content Get page content (HTML or text)
|
|
122
|
+
get-dom <selector> Query DOM elements
|
|
123
|
+
evaluate <expr> Evaluate JavaScript expression
|
|
124
|
+
inject-script <code> Inject and execute JavaScript
|
|
125
|
+
inject-css <code> Inject CSS stylesheet
|
|
126
|
+
screenshot [mode] Capture screenshot (visible|fullPage|area)
|
|
127
|
+
tabs [action] [id] Manage tabs (list|close|activate)
|
|
128
|
+
|
|
129
|
+
Options:
|
|
130
|
+
--browser <name> Browser: chrome | chromium | edge | brave (default: chrome)
|
|
131
|
+
--headless Run in headless mode
|
|
132
|
+
--port <number> WebSocket port for browser-agent (default: 9315)
|
|
133
|
+
--servicePort <number> Local service port (default: 9316)
|
|
134
|
+
--tabId <id> Target tab ID (from navigate or tabs list output)
|
|
135
|
+
--format <type> Content/screenshot format (text|html / png|jpeg)
|
|
136
|
+
--property <prop> DOM property: outerHTML | innerHTML | innerText
|
|
137
|
+
--all Return all DOM matches instead of first only
|
|
138
|
+
--quality <num> Screenshot quality (1-100, jpeg only)
|
|
139
|
+
--timeout <ms> Connection timeout in ms
|
|
140
|
+
--help, -h Show this help
|
|
141
|
+
|
|
142
|
+
Examples:
|
|
143
|
+
browse-agent setup
|
|
144
|
+
browse-agent launch --browser edge --headless
|
|
145
|
+
browse-agent navigate https://example.com
|
|
146
|
+
browse-agent get-content --format text
|
|
147
|
+
browse-agent get-dom "h1" --property innerText
|
|
148
|
+
browse-agent evaluate "document.title"
|
|
149
|
+
browse-agent screenshot fullPage --format png
|
|
150
|
+
browse-agent tabs list
|
|
151
|
+
browse-agent close
|
|
152
|
+
browse-agent clear
|
|
153
|
+
browse-agent skill ~/my-skills
|
|
154
|
+
`.trim());
|
|
155
|
+
}
|
|
156
|
+
function resolveSkillSourceDir() {
|
|
157
|
+
return join(dirname(fileURLToPath(import.meta.url)), "..", "skill");
|
|
158
|
+
}
|
|
159
|
+
function isNonEmptyDirectory(path) {
|
|
160
|
+
if (!existsSync(path)) return false;
|
|
161
|
+
if (!statSync(path).isDirectory()) return true;
|
|
162
|
+
return readdirSync(path).length > 0;
|
|
163
|
+
}
|
|
164
|
+
async function chooseSkillDestination(baseDir) {
|
|
165
|
+
let targetName = "browse-agent";
|
|
166
|
+
while (true) {
|
|
167
|
+
const targetDir = join(baseDir, targetName);
|
|
168
|
+
if (!isNonEmptyDirectory(targetDir)) return targetDir;
|
|
169
|
+
const rl = createInterface({
|
|
170
|
+
input: process.stdin,
|
|
171
|
+
output: process.stdout
|
|
172
|
+
});
|
|
173
|
+
const action = (await rl.question(`Target directory "${targetDir}" already exists and is not empty. Choose [o]verwrite, [r]ename, or [c]ancel: `)).trim().toLowerCase();
|
|
174
|
+
if (action === "o" || action === "overwrite") {
|
|
175
|
+
rmSync(targetDir, {
|
|
176
|
+
recursive: true,
|
|
177
|
+
force: true
|
|
178
|
+
});
|
|
179
|
+
rl.close();
|
|
180
|
+
return targetDir;
|
|
181
|
+
}
|
|
182
|
+
if (action === "r" || action === "rename") {
|
|
183
|
+
const nextName = (await rl.question("Enter a new directory name: ")).trim();
|
|
184
|
+
rl.close();
|
|
185
|
+
if (!nextName) {
|
|
186
|
+
console.error("Directory name cannot be empty.");
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
targetName = nextName;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
rl.close();
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
async function installSkill(targetBaseDir) {
|
|
197
|
+
const sourceDir = resolveSkillSourceDir();
|
|
198
|
+
if (!existsSync(sourceDir) || !statSync(sourceDir).isDirectory()) throw new Error(`Skill source directory not found: ${sourceDir}`);
|
|
199
|
+
const targetDir = await chooseSkillDestination(targetBaseDir);
|
|
200
|
+
if (!targetDir) {
|
|
201
|
+
console.log("Skill installation canceled.");
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
mkdirSync(targetDir, { recursive: true });
|
|
205
|
+
cpSync(sourceDir, targetDir, { recursive: true });
|
|
206
|
+
console.log(JSON.stringify({
|
|
207
|
+
success: true,
|
|
208
|
+
source: sourceDir,
|
|
209
|
+
target: targetDir
|
|
210
|
+
}, null, 2));
|
|
211
|
+
}
|
|
212
|
+
function sleep(ms) {
|
|
213
|
+
return new Promise((resolve) => {
|
|
214
|
+
setTimeout(resolve, ms);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
async function requestService(servicePort, path, method = "GET", body) {
|
|
218
|
+
const response = await fetch(`http://127.0.0.1:${servicePort}${path}`, {
|
|
219
|
+
method,
|
|
220
|
+
headers: { "Content-Type": "application/json" },
|
|
221
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
222
|
+
});
|
|
223
|
+
const payload = await response.json();
|
|
224
|
+
if (!response.ok) throw new Error(payload.error || `Service request failed: ${method} ${path}`);
|
|
225
|
+
return payload;
|
|
226
|
+
}
|
|
227
|
+
async function isServiceHealthy(servicePort) {
|
|
228
|
+
try {
|
|
229
|
+
await requestService(servicePort, "/health", "GET");
|
|
230
|
+
return true;
|
|
231
|
+
} catch {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function getRequestedServicePort(flags) {
|
|
236
|
+
return getNumberFlag(flags, "servicePort") ?? DEFAULT_SERVICE_PORT;
|
|
237
|
+
}
|
|
238
|
+
async function ensureServiceRunning(flags) {
|
|
239
|
+
const requestedPort = getRequestedServicePort(flags);
|
|
240
|
+
if (await isServiceHealthy(requestedPort)) return {
|
|
241
|
+
servicePort: requestedPort,
|
|
242
|
+
newlyStarted: false
|
|
243
|
+
};
|
|
244
|
+
const existing = loadServiceSession();
|
|
245
|
+
if (existing && await isServiceHealthy(existing.servicePort)) return {
|
|
246
|
+
servicePort: existing.servicePort,
|
|
247
|
+
newlyStarted: false
|
|
248
|
+
};
|
|
249
|
+
const serviceScript = join(dirname(fileURLToPath(import.meta.url)), "service.js");
|
|
250
|
+
spawn(process.execPath, [
|
|
251
|
+
serviceScript,
|
|
252
|
+
"--service-port",
|
|
253
|
+
String(requestedPort)
|
|
254
|
+
], {
|
|
255
|
+
stdio: "ignore",
|
|
256
|
+
detached: true
|
|
257
|
+
}).unref();
|
|
258
|
+
const deadline = Date.now() + 8e3;
|
|
259
|
+
while (Date.now() < deadline) {
|
|
260
|
+
if (await isServiceHealthy(requestedPort)) return {
|
|
261
|
+
servicePort: requestedPort,
|
|
262
|
+
newlyStarted: true
|
|
263
|
+
};
|
|
264
|
+
await sleep(200);
|
|
265
|
+
}
|
|
266
|
+
throw new Error("Failed to start background service.");
|
|
267
|
+
}
|
|
268
|
+
async function getRunningServicePort(flags) {
|
|
269
|
+
const requestedPort = getRequestedServicePort(flags);
|
|
270
|
+
if (await isServiceHealthy(requestedPort)) return requestedPort;
|
|
271
|
+
const existing = loadServiceSession();
|
|
272
|
+
if (existing && await isServiceHealthy(existing.servicePort)) return existing.servicePort;
|
|
273
|
+
throw new Error("Service is not running. Run \"browse-agent launch\" first.");
|
|
274
|
+
}
|
|
275
|
+
async function runServiceCommand(flags, name, payload = {}) {
|
|
276
|
+
const result = await requestService(await getRunningServicePort(flags), "/command", "POST", {
|
|
277
|
+
name,
|
|
278
|
+
payload
|
|
279
|
+
});
|
|
280
|
+
if (result !== void 0) console.log(JSON.stringify(result, null, 2));
|
|
281
|
+
}
|
|
282
|
+
const { command, positional, flags } = parseArgs(process.argv);
|
|
283
|
+
if (!command || flags.help || flags.h || command === "help") {
|
|
284
|
+
showHelp();
|
|
285
|
+
process.exit(0);
|
|
286
|
+
}
|
|
287
|
+
const browser = getStringFlag(flags, "browser");
|
|
288
|
+
const port = getStringFlag(flags, "port");
|
|
289
|
+
const timeout = getStringFlag(flags, "timeout");
|
|
290
|
+
if (browser) process.env.BROWSER = browser;
|
|
291
|
+
if (flags.headless) process.env.HEADLESS = "true";
|
|
292
|
+
if (port) process.env.BROWSE_AGENT_PORT = port;
|
|
293
|
+
if (timeout) process.env.CONNECTION_TIMEOUT = timeout;
|
|
294
|
+
try {
|
|
295
|
+
switch (command) {
|
|
296
|
+
case "setup":
|
|
297
|
+
await setup();
|
|
298
|
+
break;
|
|
299
|
+
case "launch": {
|
|
300
|
+
const service = await ensureServiceRunning(flags);
|
|
301
|
+
const servicePort = service.servicePort;
|
|
302
|
+
if (!service.newlyStarted) {
|
|
303
|
+
const status = await requestService(servicePort, "/status", "GET");
|
|
304
|
+
if (status.running === true) {
|
|
305
|
+
console.log(JSON.stringify({
|
|
306
|
+
servicePort,
|
|
307
|
+
skipped: "service-and-browser-already-running",
|
|
308
|
+
...status
|
|
309
|
+
}, null, 2));
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
const opts = {};
|
|
314
|
+
const launchBrowserFlag = getStringFlag(flags, "browser") || "chrome";
|
|
315
|
+
const launchPort = getNumberFlag(flags, "port");
|
|
316
|
+
const launchTimeout = getNumberFlag(flags, "timeout");
|
|
317
|
+
if (launchBrowserFlag) opts.browser = launchBrowserFlag;
|
|
318
|
+
if (flags.headless) opts.headless = true;
|
|
319
|
+
if (launchPort !== void 0) opts.port = launchPort;
|
|
320
|
+
if (launchTimeout !== void 0) opts.timeout = launchTimeout;
|
|
321
|
+
const result = await requestService(servicePort, "/launch", "POST", opts);
|
|
322
|
+
console.log(JSON.stringify({
|
|
323
|
+
servicePort,
|
|
324
|
+
...result
|
|
325
|
+
}, null, 2));
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
case "connect": {
|
|
329
|
+
const servicePort = await getRunningServicePort(flags);
|
|
330
|
+
const status = await requestService(servicePort, "/status", "GET");
|
|
331
|
+
console.log(JSON.stringify({
|
|
332
|
+
servicePort,
|
|
333
|
+
...status
|
|
334
|
+
}, null, 2));
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
case "close": {
|
|
338
|
+
const servicePort = await getRunningServicePort(flags);
|
|
339
|
+
const closeResult = await requestService(servicePort, "/close", "POST");
|
|
340
|
+
const shutdownResult = await requestService(servicePort, "/shutdown", "POST");
|
|
341
|
+
console.log(JSON.stringify({
|
|
342
|
+
servicePort,
|
|
343
|
+
...closeResult,
|
|
344
|
+
...shutdownResult
|
|
345
|
+
}, null, 2));
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
case "clear":
|
|
349
|
+
try {
|
|
350
|
+
await requestService(await getRunningServicePort(flags), "/shutdown", "POST");
|
|
351
|
+
} catch {}
|
|
352
|
+
await clear();
|
|
353
|
+
break;
|
|
354
|
+
case "skill": {
|
|
355
|
+
const directory = positional[0];
|
|
356
|
+
if (!directory) {
|
|
357
|
+
console.error("Usage: browse-agent skill <directory>");
|
|
358
|
+
process.exit(1);
|
|
359
|
+
}
|
|
360
|
+
const targetBaseDir = directory.startsWith("~") ? join(process.env.HOME || "", directory.replace(/^~[\\/]?/, "")) : resolve(directory);
|
|
361
|
+
mkdirSync(targetBaseDir, { recursive: true });
|
|
362
|
+
if (!statSync(targetBaseDir).isDirectory()) throw new Error(`Target path is not a directory: ${targetBaseDir}`);
|
|
363
|
+
await installSkill(targetBaseDir);
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
case "navigate": {
|
|
367
|
+
const url = positional[0];
|
|
368
|
+
if (!url) {
|
|
369
|
+
console.error("Usage: browse-agent navigate <url>");
|
|
370
|
+
process.exit(1);
|
|
371
|
+
}
|
|
372
|
+
await runServiceCommand(flags, "navigate", { url });
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
case "get-content": {
|
|
376
|
+
const opts = {};
|
|
377
|
+
const format = getStringFlag(flags, "format");
|
|
378
|
+
const tabId = getNumberFlag(flags, "tabId");
|
|
379
|
+
if (format) opts.format = format;
|
|
380
|
+
if (tabId !== void 0) opts.tabId = tabId;
|
|
381
|
+
await runServiceCommand(flags, "get-content", { options: opts });
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
case "get-dom": {
|
|
385
|
+
const selector = positional[0];
|
|
386
|
+
if (!selector) {
|
|
387
|
+
console.error("Usage: browse-agent get-dom <selector>");
|
|
388
|
+
process.exit(1);
|
|
389
|
+
}
|
|
390
|
+
const opts = {};
|
|
391
|
+
const property = getStringFlag(flags, "property");
|
|
392
|
+
const tabId = getNumberFlag(flags, "tabId");
|
|
393
|
+
if (property) opts.property = property;
|
|
394
|
+
if (flags.all) opts.all = true;
|
|
395
|
+
if (tabId !== void 0) opts.tabId = tabId;
|
|
396
|
+
await runServiceCommand(flags, "get-dom", {
|
|
397
|
+
selector,
|
|
398
|
+
options: opts
|
|
399
|
+
});
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
case "evaluate": {
|
|
403
|
+
const expression = positional[0];
|
|
404
|
+
if (!expression) {
|
|
405
|
+
console.error("Usage: browse-agent evaluate <expression>");
|
|
406
|
+
process.exit(1);
|
|
407
|
+
}
|
|
408
|
+
const opts = {};
|
|
409
|
+
const tabId = getNumberFlag(flags, "tabId");
|
|
410
|
+
if (tabId !== void 0) opts.tabId = tabId;
|
|
411
|
+
await runServiceCommand(flags, "evaluate", {
|
|
412
|
+
expression,
|
|
413
|
+
options: opts
|
|
414
|
+
});
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
case "inject-script": {
|
|
418
|
+
const code = positional[0];
|
|
419
|
+
if (!code) {
|
|
420
|
+
console.error("Usage: browse-agent inject-script <code>");
|
|
421
|
+
process.exit(1);
|
|
422
|
+
}
|
|
423
|
+
const opts = {};
|
|
424
|
+
const tabId = getNumberFlag(flags, "tabId");
|
|
425
|
+
if (tabId !== void 0) opts.tabId = tabId;
|
|
426
|
+
await runServiceCommand(flags, "inject-script", {
|
|
427
|
+
code,
|
|
428
|
+
options: opts
|
|
429
|
+
});
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
case "inject-css": {
|
|
433
|
+
const code = positional[0];
|
|
434
|
+
if (!code) {
|
|
435
|
+
console.error("Usage: browse-agent inject-css <code>");
|
|
436
|
+
process.exit(1);
|
|
437
|
+
}
|
|
438
|
+
const opts = {};
|
|
439
|
+
const tabId = getNumberFlag(flags, "tabId");
|
|
440
|
+
if (tabId !== void 0) opts.tabId = tabId;
|
|
441
|
+
await runServiceCommand(flags, "inject-css", {
|
|
442
|
+
code,
|
|
443
|
+
options: opts
|
|
444
|
+
});
|
|
445
|
+
break;
|
|
446
|
+
}
|
|
447
|
+
case "screenshot": {
|
|
448
|
+
const mode = positional[0] || "visible";
|
|
449
|
+
const opts = {};
|
|
450
|
+
const format = getStringFlag(flags, "format");
|
|
451
|
+
const quality = getNumberFlag(flags, "quality");
|
|
452
|
+
const tabId = getNumberFlag(flags, "tabId");
|
|
453
|
+
if (format) opts.format = format;
|
|
454
|
+
if (quality !== void 0) opts.quality = quality;
|
|
455
|
+
if (tabId !== void 0) opts.tabId = tabId;
|
|
456
|
+
await runServiceCommand(flags, "screenshot", {
|
|
457
|
+
mode,
|
|
458
|
+
options: opts
|
|
459
|
+
});
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
case "tabs": {
|
|
463
|
+
const action = positional[0] || "list";
|
|
464
|
+
const tabId = positional[1] ? Number(positional[1]) : void 0;
|
|
465
|
+
switch (action) {
|
|
466
|
+
case "list":
|
|
467
|
+
await runServiceCommand(flags, "tabs-list");
|
|
468
|
+
break;
|
|
469
|
+
case "close":
|
|
470
|
+
if (tabId === void 0) {
|
|
471
|
+
console.error("Usage: browse-agent tabs close <tabId>");
|
|
472
|
+
process.exit(1);
|
|
473
|
+
}
|
|
474
|
+
await runServiceCommand(flags, "tabs-close", { tabId });
|
|
475
|
+
break;
|
|
476
|
+
case "activate":
|
|
477
|
+
if (tabId === void 0) {
|
|
478
|
+
console.error("Usage: browse-agent tabs activate <tabId>");
|
|
479
|
+
process.exit(1);
|
|
480
|
+
}
|
|
481
|
+
await runServiceCommand(flags, "tabs-activate", { tabId });
|
|
482
|
+
break;
|
|
483
|
+
default:
|
|
484
|
+
console.error(`Unknown tabs action: ${action}. Use: list, close, activate`);
|
|
485
|
+
process.exit(1);
|
|
486
|
+
}
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
default:
|
|
490
|
+
console.error(`Unknown command: ${command}\\nRun "browse-agent --help" for usage.`);
|
|
491
|
+
process.exit(1);
|
|
492
|
+
}
|
|
493
|
+
} catch (err) {
|
|
494
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
495
|
+
console.error(`[browse-agent] Error: ${message}`);
|
|
496
|
+
process.exit(1);
|
|
497
|
+
}
|
|
498
|
+
//#endregion
|
|
499
|
+
export {};
|
|
500
|
+
|
|
501
|
+
//# sourceMappingURL=cli.js.map
|