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/dist/config.js
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
//#region src/scripts/config.ts
|
|
6
|
+
const CLI_RUNTIME_DIR = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const HOME = process.env.HOME || process.env.USERPROFILE || "";
|
|
8
|
+
const BROWSER_PATHS = {
|
|
9
|
+
chrome: {
|
|
10
|
+
darwin: ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"],
|
|
11
|
+
linux: ["google-chrome", "google-chrome-stable"],
|
|
12
|
+
win32: [
|
|
13
|
+
`${process.env.PROGRAMFILES}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
14
|
+
`${process.env["PROGRAMFILES(X86)"]}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
15
|
+
`${process.env.LOCALAPPDATA}\\Google\\Chrome\\Application\\chrome.exe`
|
|
16
|
+
],
|
|
17
|
+
aix: [],
|
|
18
|
+
android: [],
|
|
19
|
+
freebsd: [],
|
|
20
|
+
haiku: [],
|
|
21
|
+
openbsd: [],
|
|
22
|
+
sunos: [],
|
|
23
|
+
cygwin: [],
|
|
24
|
+
netbsd: []
|
|
25
|
+
},
|
|
26
|
+
chromium: {
|
|
27
|
+
darwin: ["/Applications/Chromium.app/Contents/MacOS/Chromium"],
|
|
28
|
+
linux: ["chromium-browser", "chromium"],
|
|
29
|
+
win32: [`${process.env.LOCALAPPDATA}\\Chromium\\Application\\chrome.exe`],
|
|
30
|
+
aix: [],
|
|
31
|
+
android: [],
|
|
32
|
+
freebsd: [],
|
|
33
|
+
haiku: [],
|
|
34
|
+
openbsd: [],
|
|
35
|
+
sunos: [],
|
|
36
|
+
cygwin: [],
|
|
37
|
+
netbsd: []
|
|
38
|
+
},
|
|
39
|
+
edge: {
|
|
40
|
+
darwin: ["/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"],
|
|
41
|
+
linux: ["microsoft-edge", "microsoft-edge-stable"],
|
|
42
|
+
win32: [`${process.env.PROGRAMFILES}\\Microsoft\\Edge\\Application\\msedge.exe`, `${process.env["PROGRAMFILES(X86)"]}\\Microsoft\\Edge\\Application\\msedge.exe`],
|
|
43
|
+
aix: [],
|
|
44
|
+
android: [],
|
|
45
|
+
freebsd: [],
|
|
46
|
+
haiku: [],
|
|
47
|
+
openbsd: [],
|
|
48
|
+
sunos: [],
|
|
49
|
+
cygwin: [],
|
|
50
|
+
netbsd: []
|
|
51
|
+
},
|
|
52
|
+
brave: {
|
|
53
|
+
darwin: ["/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"],
|
|
54
|
+
linux: ["brave-browser", "brave"],
|
|
55
|
+
win32: [`${process.env.PROGRAMFILES}\\BraveSoftware\\Brave-Browser\\Application\\brave.exe`, `${process.env.LOCALAPPDATA}\\BraveSoftware\\Brave-Browser\\Application\\brave.exe`],
|
|
56
|
+
aix: [],
|
|
57
|
+
android: [],
|
|
58
|
+
freebsd: [],
|
|
59
|
+
haiku: [],
|
|
60
|
+
openbsd: [],
|
|
61
|
+
sunos: [],
|
|
62
|
+
cygwin: [],
|
|
63
|
+
netbsd: []
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const BASE_DIR = join(CLI_RUNTIME_DIR, ".browse-agent");
|
|
67
|
+
const DEFAULT_SERVICE_PORT = Number(process.env.BROWSE_AGENT_SERVICE_PORT || 9316);
|
|
68
|
+
async function importSdk() {
|
|
69
|
+
try {
|
|
70
|
+
return await import("browse-agent-sdk");
|
|
71
|
+
} catch {}
|
|
72
|
+
const globalSdk = join(dirname(CLI_RUNTIME_DIR), "node_modules", "browse-agent-sdk");
|
|
73
|
+
if (existsSync(globalSdk)) {
|
|
74
|
+
const pkg = JSON.parse(readFileSync(join(globalSdk, "package.json"), "utf8"));
|
|
75
|
+
return await import(pathToFileURL(join(globalSdk, pkg.module || pkg.main || "index.js")).href);
|
|
76
|
+
}
|
|
77
|
+
throw new Error("browse-agent-sdk not found. Run \"setup\" first.");
|
|
78
|
+
}
|
|
79
|
+
const DEFAULT_PROFILE_PATHS = {
|
|
80
|
+
chrome: {
|
|
81
|
+
darwin: join(HOME, "Library", "Application Support", "Google", "Chrome"),
|
|
82
|
+
linux: join(HOME, ".config", "google-chrome"),
|
|
83
|
+
win32: join(process.env.LOCALAPPDATA || "", "Google", "Chrome", "User Data"),
|
|
84
|
+
aix: "",
|
|
85
|
+
android: "",
|
|
86
|
+
freebsd: "",
|
|
87
|
+
haiku: "",
|
|
88
|
+
openbsd: "",
|
|
89
|
+
sunos: "",
|
|
90
|
+
cygwin: "",
|
|
91
|
+
netbsd: ""
|
|
92
|
+
},
|
|
93
|
+
chromium: {
|
|
94
|
+
darwin: join(HOME, "Library", "Application Support", "Chromium"),
|
|
95
|
+
linux: join(HOME, ".config", "chromium"),
|
|
96
|
+
win32: join(process.env.LOCALAPPDATA || "", "Chromium", "User Data"),
|
|
97
|
+
aix: "",
|
|
98
|
+
android: "",
|
|
99
|
+
freebsd: "",
|
|
100
|
+
haiku: "",
|
|
101
|
+
openbsd: "",
|
|
102
|
+
sunos: "",
|
|
103
|
+
cygwin: "",
|
|
104
|
+
netbsd: ""
|
|
105
|
+
},
|
|
106
|
+
edge: {
|
|
107
|
+
darwin: join(HOME, "Library", "Application Support", "Microsoft Edge"),
|
|
108
|
+
linux: join(HOME, ".config", "microsoft-edge"),
|
|
109
|
+
win32: join(process.env.LOCALAPPDATA || "", "Microsoft", "Edge", "User Data"),
|
|
110
|
+
aix: "",
|
|
111
|
+
android: "",
|
|
112
|
+
freebsd: "",
|
|
113
|
+
haiku: "",
|
|
114
|
+
openbsd: "",
|
|
115
|
+
sunos: "",
|
|
116
|
+
cygwin: "",
|
|
117
|
+
netbsd: ""
|
|
118
|
+
},
|
|
119
|
+
brave: {
|
|
120
|
+
darwin: join(HOME, "Library", "Application Support", "BraveSoftware", "Brave-Browser"),
|
|
121
|
+
linux: join(HOME, ".config", "BraveSoftware", "Brave-Browser"),
|
|
122
|
+
win32: join(process.env.LOCALAPPDATA || "", "BraveSoftware", "Brave-Browser", "User Data"),
|
|
123
|
+
aix: "",
|
|
124
|
+
android: "",
|
|
125
|
+
freebsd: "",
|
|
126
|
+
haiku: "",
|
|
127
|
+
openbsd: "",
|
|
128
|
+
sunos: "",
|
|
129
|
+
cygwin: "",
|
|
130
|
+
netbsd: ""
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
function saveSession(data) {
|
|
134
|
+
mkdirSync(BASE_DIR, { recursive: true });
|
|
135
|
+
writeFileSync(join(BASE_DIR, "_session.json"), JSON.stringify(data, null, 2));
|
|
136
|
+
}
|
|
137
|
+
function loadSession() {
|
|
138
|
+
const stateFile = join(BASE_DIR, "_session.json");
|
|
139
|
+
if (!existsSync(stateFile)) return null;
|
|
140
|
+
return JSON.parse(readFileSync(stateFile, "utf8"));
|
|
141
|
+
}
|
|
142
|
+
function clearSession() {
|
|
143
|
+
const stateFile = join(BASE_DIR, "_session.json");
|
|
144
|
+
if (existsSync(stateFile)) rmSync(stateFile);
|
|
145
|
+
}
|
|
146
|
+
function saveServiceSession(data) {
|
|
147
|
+
mkdirSync(BASE_DIR, { recursive: true });
|
|
148
|
+
writeFileSync(join(BASE_DIR, "_service.json"), JSON.stringify(data, null, 2));
|
|
149
|
+
}
|
|
150
|
+
function loadServiceSession() {
|
|
151
|
+
const stateFile = join(BASE_DIR, "_service.json");
|
|
152
|
+
if (!existsSync(stateFile)) return null;
|
|
153
|
+
return JSON.parse(readFileSync(stateFile, "utf8"));
|
|
154
|
+
}
|
|
155
|
+
function clearServiceSession() {
|
|
156
|
+
const stateFile = join(BASE_DIR, "_service.json");
|
|
157
|
+
if (existsSync(stateFile)) rmSync(stateFile);
|
|
158
|
+
}
|
|
159
|
+
function findBrowser(browser) {
|
|
160
|
+
if (process.env.CHROME_PATH) return process.env.CHROME_PATH;
|
|
161
|
+
const paths = BROWSER_PATHS[browser];
|
|
162
|
+
if (!paths) throw new Error(`Unknown browser: ${browser}. Use: chrome, chromium, edge, brave`);
|
|
163
|
+
const isAbsolute = (path) => path.startsWith("/") || /^[a-zA-Z]:\\/.test(path);
|
|
164
|
+
const whichCmd = process.platform === "win32" ? "where" : "which";
|
|
165
|
+
for (const path of paths[process.platform] || []) try {
|
|
166
|
+
if (isAbsolute(path)) {
|
|
167
|
+
if (existsSync(path)) return path;
|
|
168
|
+
} else {
|
|
169
|
+
const resolved = execSync(`${whichCmd} "${path}"`, { stdio: "pipe" }).toString().trim().split("\n")[0];
|
|
170
|
+
if (resolved) return resolved;
|
|
171
|
+
}
|
|
172
|
+
} catch {}
|
|
173
|
+
if (browser !== "edge") {
|
|
174
|
+
console.error(`[browse-agent] ${browser} not found, falling back to edge...`);
|
|
175
|
+
return findBrowser("edge");
|
|
176
|
+
}
|
|
177
|
+
throw new Error("No supported browser found. Set CHROME_PATH env variable or install Chrome/Edge.");
|
|
178
|
+
}
|
|
179
|
+
function getProfileDir(browser, useUserProfile) {
|
|
180
|
+
if (useUserProfile) {
|
|
181
|
+
const profilePath = DEFAULT_PROFILE_PATHS[browser]?.[process.platform];
|
|
182
|
+
if (profilePath && existsSync(profilePath)) return profilePath;
|
|
183
|
+
console.error(`[browse-agent] Default ${browser} profile not found at ${profilePath}`);
|
|
184
|
+
console.error("[browse-agent] Falling back to isolated profile.");
|
|
185
|
+
}
|
|
186
|
+
const dir = join(BASE_DIR, "chrome-profile");
|
|
187
|
+
mkdirSync(dir, { recursive: true });
|
|
188
|
+
return dir;
|
|
189
|
+
}
|
|
190
|
+
function patchExtension(port, secret) {
|
|
191
|
+
const extensionSrc = join(BASE_DIR, "extension");
|
|
192
|
+
const extensionWork = join(BASE_DIR, "_ext_work");
|
|
193
|
+
if (!existsSync(join(extensionSrc, "manifest.json"))) throw new Error(`Extension is not installed at ${extensionSrc}. Run "browse-agent setup" first.`);
|
|
194
|
+
if (existsSync(extensionWork)) rmSync(extensionWork, { recursive: true });
|
|
195
|
+
cpSync(extensionSrc, extensionWork, { recursive: true });
|
|
196
|
+
const swPath = join(extensionWork, "service-worker.js");
|
|
197
|
+
const originalSW = readFileSync(swPath, "utf8");
|
|
198
|
+
writeFileSync(swPath, [`chrome.storage.local.set({ wsUrl: 'ws://127.0.0.1:${port}', secret: '${secret}' });`, originalSW].join("\n"));
|
|
199
|
+
return extensionWork;
|
|
200
|
+
}
|
|
201
|
+
function cleanExtensionWork() {
|
|
202
|
+
const extensionWork = join(BASE_DIR, "_ext_work");
|
|
203
|
+
if (existsSync(extensionWork)) rmSync(extensionWork, { recursive: true });
|
|
204
|
+
}
|
|
205
|
+
function resolveOptions(options = {}) {
|
|
206
|
+
return {
|
|
207
|
+
browser: options.browser ?? process.env.BROWSER ?? "chrome",
|
|
208
|
+
headless: options.headless ?? process.env.HEADLESS === "true",
|
|
209
|
+
useUserProfile: options.useUserProfile ?? process.env.USE_USER_PROFILE === "true",
|
|
210
|
+
port: options.port ?? Number(process.env.BROWSE_AGENT_PORT || 9315),
|
|
211
|
+
timeout: options.timeout ?? Number(process.env.CONNECTION_TIMEOUT || 3e4),
|
|
212
|
+
secret: options.secret ?? process.env.SHARED_SECRET ?? ""
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
//#endregion
|
|
216
|
+
export { clearSession as a, importSdk as c, patchExtension as d, resolveOptions as f, clearServiceSession as i, loadServiceSession as l, saveSession as m, DEFAULT_SERVICE_PORT as n, findBrowser as o, saveServiceSession as p, cleanExtensionWork as r, getProfileDir as s, BASE_DIR as t, loadSession as u };
|
|
217
|
+
|
|
218
|
+
//# sourceMappingURL=config.js.map
|
package/dist/script.d.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import "browse-agent-sdk";
|
|
2
|
+
|
|
3
|
+
//#region src/scripts/config.d.ts
|
|
4
|
+
type BrowserName = 'chrome' | 'chromium' | 'edge' | 'brave';
|
|
5
|
+
interface SessionData {
|
|
6
|
+
pid?: number;
|
|
7
|
+
port: number;
|
|
8
|
+
secret: string;
|
|
9
|
+
browser: BrowserName;
|
|
10
|
+
profileDir: string;
|
|
11
|
+
extensionWork: string;
|
|
12
|
+
startedAt: string;
|
|
13
|
+
_agent?: any;
|
|
14
|
+
_proc?: any;
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/scripts/launch-browser.d.ts
|
|
18
|
+
interface LaunchOptions {
|
|
19
|
+
browser?: 'chrome' | 'chromium' | 'edge' | 'brave';
|
|
20
|
+
headless?: boolean;
|
|
21
|
+
useUserProfile?: boolean;
|
|
22
|
+
port?: number;
|
|
23
|
+
timeout?: number;
|
|
24
|
+
secret?: string;
|
|
25
|
+
}
|
|
26
|
+
declare function launchBrowser(options?: LaunchOptions): Promise<SessionData>;
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/scripts/connect.d.ts
|
|
29
|
+
interface ConnectOptions {
|
|
30
|
+
port?: number;
|
|
31
|
+
secret?: string;
|
|
32
|
+
timeout?: number;
|
|
33
|
+
}
|
|
34
|
+
declare function connect(options?: ConnectOptions): Promise<any>;
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/scripts/close-browser.d.ts
|
|
37
|
+
declare function closeBrowser(agent?: {
|
|
38
|
+
stop?: () => Promise<void>;
|
|
39
|
+
}): Promise<void>;
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/scripts/clear.d.ts
|
|
42
|
+
declare function clear(): Promise<void>;
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/scripts/navigate.d.ts
|
|
45
|
+
declare function navigate(agent: any, url: string, options?: Record<string, unknown>): Promise<unknown>;
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/scripts/get-content.d.ts
|
|
48
|
+
interface GetContentOptions {
|
|
49
|
+
format?: string;
|
|
50
|
+
tabId?: number;
|
|
51
|
+
}
|
|
52
|
+
declare function getContent(agent: any, options?: GetContentOptions): Promise<unknown>;
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/scripts/get-dom.d.ts
|
|
55
|
+
interface GetDomOptions {
|
|
56
|
+
property?: string;
|
|
57
|
+
all?: boolean;
|
|
58
|
+
tabId?: number;
|
|
59
|
+
}
|
|
60
|
+
declare function getDOM(agent: any, selector: string, options?: GetDomOptions): Promise<unknown>;
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/scripts/evaluate.d.ts
|
|
63
|
+
interface EvaluateOptions {
|
|
64
|
+
tabId?: number;
|
|
65
|
+
}
|
|
66
|
+
declare function evaluate(agent: any, expression: string, options?: EvaluateOptions): Promise<unknown>;
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/scripts/inject-script.d.ts
|
|
69
|
+
interface InjectScriptOptions {
|
|
70
|
+
tabId?: number;
|
|
71
|
+
}
|
|
72
|
+
declare function injectScript(agent: any, code: string, options?: InjectScriptOptions): Promise<unknown>;
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/scripts/inject-css.d.ts
|
|
75
|
+
interface InjectCssOptions {
|
|
76
|
+
tabId?: number;
|
|
77
|
+
}
|
|
78
|
+
declare function injectCSS(agent: any, code: string, options?: InjectCssOptions): Promise<unknown>;
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region src/scripts/screenshot.d.ts
|
|
81
|
+
interface ScreenshotClip {
|
|
82
|
+
x: number;
|
|
83
|
+
y: number;
|
|
84
|
+
width: number;
|
|
85
|
+
height: number;
|
|
86
|
+
}
|
|
87
|
+
interface ScreenshotOptions {
|
|
88
|
+
format?: string;
|
|
89
|
+
quality?: number;
|
|
90
|
+
tabId?: number;
|
|
91
|
+
clip?: ScreenshotClip;
|
|
92
|
+
}
|
|
93
|
+
declare function screenshot(agent: any, mode?: string, options?: ScreenshotOptions): Promise<unknown>;
|
|
94
|
+
//#endregion
|
|
95
|
+
//#region src/scripts/tabs.d.ts
|
|
96
|
+
declare function listTabs(agent: any): Promise<unknown>;
|
|
97
|
+
declare function closeTab(agent: any, tabId: number): Promise<void>;
|
|
98
|
+
declare function activateTab(agent: any, tabId: number): Promise<void>;
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/scripts/browse.d.ts
|
|
101
|
+
interface BrowseOptions {
|
|
102
|
+
browser?: 'chrome' | 'chromium' | 'edge' | 'brave';
|
|
103
|
+
headless?: boolean;
|
|
104
|
+
useUserProfile?: boolean;
|
|
105
|
+
port?: number;
|
|
106
|
+
timeout?: number;
|
|
107
|
+
secret?: string;
|
|
108
|
+
printResult?: boolean;
|
|
109
|
+
}
|
|
110
|
+
declare function browse(task: (agent: any) => Promise<unknown>, options?: BrowseOptions): Promise<unknown>;
|
|
111
|
+
//#endregion
|
|
112
|
+
export { activateTab, browse, clear, closeBrowser, closeTab, connect, evaluate, getContent, getDOM, injectCSS, injectScript, launchBrowser, listTabs, navigate, screenshot };
|
|
113
|
+
//# sourceMappingURL=script.d.ts.map
|
package/dist/script.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { c as importSdk, f as resolveOptions, r as cleanExtensionWork, u as loadSession } from "./config.js";
|
|
2
|
+
import { t as clear } from "./clear.js";
|
|
3
|
+
import { a as injectCSS, c as getDOM, d as closeBrowser, f as launchBrowser, i as screenshot, l as getContent, n as closeTab, o as injectScript, r as listTabs, s as evaluate, t as activateTab, u as navigate } from "./tabs.js";
|
|
4
|
+
//#region src/scripts/connect.ts
|
|
5
|
+
async function connect(options = {}) {
|
|
6
|
+
const { BrowserAgent } = await importSdk();
|
|
7
|
+
const session = loadSession();
|
|
8
|
+
const port = options.port ?? session?.port ?? resolveOptions().port;
|
|
9
|
+
const secret = options.secret ?? session?.secret ?? resolveOptions().secret;
|
|
10
|
+
const timeout = options.timeout ?? resolveOptions().timeout;
|
|
11
|
+
const agent = new BrowserAgent({
|
|
12
|
+
secret,
|
|
13
|
+
port
|
|
14
|
+
});
|
|
15
|
+
await agent.start();
|
|
16
|
+
console.error(`[browse-agent] Connecting on port ${port}...`);
|
|
17
|
+
await agent.waitForConnection(timeout);
|
|
18
|
+
console.error("[browse-agent] Extension connected");
|
|
19
|
+
return agent;
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/scripts/browse.ts
|
|
23
|
+
async function browse(task, options = {}) {
|
|
24
|
+
const opts = resolveOptions(options);
|
|
25
|
+
let session;
|
|
26
|
+
let agent;
|
|
27
|
+
let taskResult;
|
|
28
|
+
try {
|
|
29
|
+
session = await launchBrowser(opts);
|
|
30
|
+
agent = session._agent;
|
|
31
|
+
await agent.waitForConnection(opts.timeout);
|
|
32
|
+
console.error("[browse-agent] Extension connected");
|
|
33
|
+
taskResult = await task(agent);
|
|
34
|
+
if (options.printResult !== false && taskResult !== void 0) console.log(JSON.stringify(taskResult, null, 2));
|
|
35
|
+
} catch (err) {
|
|
36
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
37
|
+
console.error("[browse-agent] Error:", message);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
} finally {
|
|
40
|
+
if (session?._proc) try {
|
|
41
|
+
session._proc.kill();
|
|
42
|
+
} catch {}
|
|
43
|
+
if (agent) try {
|
|
44
|
+
await agent.stop();
|
|
45
|
+
} catch {}
|
|
46
|
+
cleanExtensionWork();
|
|
47
|
+
}
|
|
48
|
+
return taskResult;
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { activateTab, browse, clear, closeBrowser, closeTab, connect, evaluate, getContent, getDOM, injectCSS, injectScript, launchBrowser, listTabs, navigate, screenshot };
|
|
52
|
+
|
|
53
|
+
//# sourceMappingURL=script.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/service.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { f as resolveOptions, i as clearServiceSession, n as DEFAULT_SERVICE_PORT, p as saveServiceSession } from "./config.js";
|
|
2
|
+
import { a as injectCSS, c as getDOM, d as closeBrowser, f as launchBrowser, i as screenshot, l as getContent, n as closeTab, o as injectScript, r as listTabs, s as evaluate, t as activateTab, u as navigate } from "./tabs.js";
|
|
3
|
+
import { createServer } from "node:http";
|
|
4
|
+
//#region src/service.ts
|
|
5
|
+
function toCommandRequest(value) {
|
|
6
|
+
const name = value.name;
|
|
7
|
+
if (typeof name !== "string" || name.length === 0) throw new Error("Invalid command request: \"name\" must be a non-empty string.");
|
|
8
|
+
const payloadRaw = value.payload;
|
|
9
|
+
return {
|
|
10
|
+
name,
|
|
11
|
+
payload: payloadRaw !== void 0 && payloadRaw !== null && typeof payloadRaw === "object" ? payloadRaw : void 0
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
let currentSession = null;
|
|
15
|
+
let currentAgent = null;
|
|
16
|
+
function getServicePort() {
|
|
17
|
+
const index = process.argv.findIndex((arg) => arg === "--service-port");
|
|
18
|
+
if (index !== -1 && process.argv[index + 1]) {
|
|
19
|
+
const parsed = Number(process.argv[index + 1]);
|
|
20
|
+
if (!Number.isNaN(parsed)) return parsed;
|
|
21
|
+
}
|
|
22
|
+
return DEFAULT_SERVICE_PORT;
|
|
23
|
+
}
|
|
24
|
+
function writeJson(res, status, body) {
|
|
25
|
+
res.statusCode = status;
|
|
26
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
27
|
+
res.end(JSON.stringify(body));
|
|
28
|
+
}
|
|
29
|
+
async function readJson(req) {
|
|
30
|
+
const chunks = [];
|
|
31
|
+
for await (const chunk of req) chunks.push(Buffer.from(chunk));
|
|
32
|
+
if (chunks.length === 0) return {};
|
|
33
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
34
|
+
if (!raw) return {};
|
|
35
|
+
return JSON.parse(raw);
|
|
36
|
+
}
|
|
37
|
+
function sessionInfo(session) {
|
|
38
|
+
if (!session) return { running: false };
|
|
39
|
+
const { _agent, _proc, ...info } = session;
|
|
40
|
+
return {
|
|
41
|
+
running: true,
|
|
42
|
+
...info
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async function handleLaunch(body) {
|
|
46
|
+
if (currentSession && currentAgent) return sessionInfo(currentSession);
|
|
47
|
+
const options = {
|
|
48
|
+
browser: body.browser,
|
|
49
|
+
headless: body.headless,
|
|
50
|
+
port: body.port,
|
|
51
|
+
timeout: body.timeout,
|
|
52
|
+
secret: body.secret
|
|
53
|
+
};
|
|
54
|
+
const session = await launchBrowser(options);
|
|
55
|
+
currentSession = session;
|
|
56
|
+
currentAgent = session._agent;
|
|
57
|
+
const timeout = resolveOptions(options).timeout;
|
|
58
|
+
await currentAgent.waitForConnection(timeout);
|
|
59
|
+
return sessionInfo(session);
|
|
60
|
+
}
|
|
61
|
+
async function handleCommand(body) {
|
|
62
|
+
if (!currentAgent) throw new Error("Service is running but browser session is not started. Run \"browse-agent launch\" first.");
|
|
63
|
+
const payload = body.payload ?? {};
|
|
64
|
+
switch (body.name) {
|
|
65
|
+
case "navigate": return navigate(currentAgent, String(payload.url), payload.options ?? {});
|
|
66
|
+
case "get-content": return getContent(currentAgent, payload.options ?? {});
|
|
67
|
+
case "get-dom": return getDOM(currentAgent, String(payload.selector), payload.options ?? {});
|
|
68
|
+
case "evaluate": return evaluate(currentAgent, String(payload.expression), payload.options ?? {});
|
|
69
|
+
case "inject-script": return injectScript(currentAgent, String(payload.code), payload.options ?? {});
|
|
70
|
+
case "inject-css": return injectCSS(currentAgent, String(payload.code), payload.options ?? {});
|
|
71
|
+
case "screenshot": return screenshot(currentAgent, String(payload.mode ?? "visible"), payload.options ?? {});
|
|
72
|
+
case "tabs-list": return listTabs(currentAgent);
|
|
73
|
+
case "tabs-close":
|
|
74
|
+
await closeTab(currentAgent, Number(payload.tabId));
|
|
75
|
+
return { closed: Number(payload.tabId) };
|
|
76
|
+
case "tabs-activate":
|
|
77
|
+
await activateTab(currentAgent, Number(payload.tabId));
|
|
78
|
+
return { activated: Number(payload.tabId) };
|
|
79
|
+
default: throw new Error(`Unknown command: ${body.name}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function stopBrowserSession() {
|
|
83
|
+
if (!currentSession && !currentAgent) return;
|
|
84
|
+
await closeBrowser(currentAgent ?? void 0);
|
|
85
|
+
currentSession = null;
|
|
86
|
+
currentAgent = null;
|
|
87
|
+
}
|
|
88
|
+
const servicePort = getServicePort();
|
|
89
|
+
const server = createServer(async (req, res) => {
|
|
90
|
+
try {
|
|
91
|
+
const method = req.method ?? "GET";
|
|
92
|
+
const url = req.url ?? "/";
|
|
93
|
+
if (method === "GET" && url === "/health") {
|
|
94
|
+
writeJson(res, 200, {
|
|
95
|
+
ok: true,
|
|
96
|
+
servicePort,
|
|
97
|
+
pid: process.pid
|
|
98
|
+
});
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (method === "GET" && url === "/status") {
|
|
102
|
+
writeJson(res, 200, {
|
|
103
|
+
servicePort,
|
|
104
|
+
pid: process.pid,
|
|
105
|
+
...sessionInfo(currentSession)
|
|
106
|
+
});
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (method === "POST" && url === "/launch") {
|
|
110
|
+
writeJson(res, 200, await handleLaunch(await readJson(req)));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (method === "POST" && url === "/command") {
|
|
114
|
+
writeJson(res, 200, await handleCommand(toCommandRequest(await readJson(req))));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (method === "POST" && url === "/close") {
|
|
118
|
+
await stopBrowserSession();
|
|
119
|
+
writeJson(res, 200, { closed: true });
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (method === "POST" && url === "/shutdown") {
|
|
123
|
+
await stopBrowserSession();
|
|
124
|
+
clearServiceSession();
|
|
125
|
+
writeJson(res, 200, { stopped: true });
|
|
126
|
+
server.close(() => {
|
|
127
|
+
process.exit(0);
|
|
128
|
+
});
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
writeJson(res, 404, { error: `Unknown route: ${method} ${url}` });
|
|
132
|
+
} catch (err) {
|
|
133
|
+
writeJson(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
server.listen(servicePort, "127.0.0.1", () => {
|
|
137
|
+
saveServiceSession({
|
|
138
|
+
pid: process.pid,
|
|
139
|
+
servicePort,
|
|
140
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
process.on("SIGINT", async () => {
|
|
144
|
+
await stopBrowserSession();
|
|
145
|
+
clearServiceSession();
|
|
146
|
+
process.exit(0);
|
|
147
|
+
});
|
|
148
|
+
process.on("SIGTERM", async () => {
|
|
149
|
+
await stopBrowserSession();
|
|
150
|
+
clearServiceSession();
|
|
151
|
+
process.exit(0);
|
|
152
|
+
});
|
|
153
|
+
//#endregion
|
|
154
|
+
export {};
|
|
155
|
+
|
|
156
|
+
//# sourceMappingURL=service.js.map
|
package/dist/tabs.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { a as clearSession, c as importSdk, d as patchExtension, f as resolveOptions, m as saveSession, o as findBrowser, r as cleanExtensionWork, s as getProfileDir, u as loadSession } from "./config.js";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
//#region src/scripts/launch-browser.ts
|
|
4
|
+
async function launchBrowser(options = {}) {
|
|
5
|
+
const { BrowserAgent } = await importSdk();
|
|
6
|
+
const opts = resolveOptions(options);
|
|
7
|
+
const extensionWork = patchExtension(opts.port, opts.secret);
|
|
8
|
+
const profileDir = getProfileDir(opts.browser, opts.useUserProfile ?? true);
|
|
9
|
+
const agent = new BrowserAgent({
|
|
10
|
+
secret: opts.secret,
|
|
11
|
+
port: opts.port
|
|
12
|
+
});
|
|
13
|
+
await agent.start();
|
|
14
|
+
console.error(`[browse-agent] Server started on port ${opts.port}`);
|
|
15
|
+
const launchArgs = [
|
|
16
|
+
`--load-extension=${extensionWork}`,
|
|
17
|
+
"--no-first-run",
|
|
18
|
+
"--no-default-browser-check"
|
|
19
|
+
];
|
|
20
|
+
if (opts.headless) launchArgs.push("--headless=new");
|
|
21
|
+
const proc = spawn(findBrowser(opts.browser), launchArgs, {
|
|
22
|
+
stdio: "ignore",
|
|
23
|
+
detached: true
|
|
24
|
+
});
|
|
25
|
+
proc.on("error", (err) => {
|
|
26
|
+
console.error(`[browse-agent] ${opts.browser} launch failed:`, err.message);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
});
|
|
29
|
+
proc.unref();
|
|
30
|
+
const session = {
|
|
31
|
+
pid: proc.pid,
|
|
32
|
+
port: opts.port,
|
|
33
|
+
secret: opts.secret,
|
|
34
|
+
browser: opts.browser,
|
|
35
|
+
profileDir,
|
|
36
|
+
extensionWork,
|
|
37
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
38
|
+
_agent: agent,
|
|
39
|
+
_proc: proc
|
|
40
|
+
};
|
|
41
|
+
saveSession(session);
|
|
42
|
+
console.error(`[browse-agent] Browser launched (PID: ${proc.pid})`);
|
|
43
|
+
return session;
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/scripts/close-browser.ts
|
|
47
|
+
async function closeBrowser(agent) {
|
|
48
|
+
const session = loadSession();
|
|
49
|
+
if (session?.pid) try {
|
|
50
|
+
process.kill(session.pid);
|
|
51
|
+
console.error(`[browse-agent] Browser (PID: ${session.pid}) killed`);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
const killErr = err;
|
|
54
|
+
if (killErr.code === "ESRCH") console.error(`[browse-agent] Browser (PID: ${session.pid}) already exited`);
|
|
55
|
+
else console.error("[browse-agent] Failed to kill browser:", killErr.message);
|
|
56
|
+
}
|
|
57
|
+
if (agent?.stop) try {
|
|
58
|
+
await agent.stop();
|
|
59
|
+
} catch {}
|
|
60
|
+
cleanExtensionWork();
|
|
61
|
+
clearSession();
|
|
62
|
+
console.error("[browse-agent] Session cleaned up");
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/scripts/navigate.ts
|
|
66
|
+
async function navigate(agent, url, options = {}) {
|
|
67
|
+
return await agent.navigate(url, options);
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/scripts/get-content.ts
|
|
71
|
+
async function getContent(agent, options = {}) {
|
|
72
|
+
return await agent.getContent(options);
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/scripts/get-dom.ts
|
|
76
|
+
async function getDOM(agent, selector, options = {}) {
|
|
77
|
+
return await agent.getDOM(selector, options);
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region src/scripts/evaluate.ts
|
|
81
|
+
async function evaluate(agent, expression, options = {}) {
|
|
82
|
+
return await agent.evaluate(expression, options.tabId);
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region src/scripts/inject-script.ts
|
|
86
|
+
async function injectScript(agent, code, options = {}) {
|
|
87
|
+
return await agent.injectScript(code, options.tabId);
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region src/scripts/inject-css.ts
|
|
91
|
+
async function injectCSS(agent, code, options = {}) {
|
|
92
|
+
return await agent.injectCSS(code, options.tabId);
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
//#region src/scripts/screenshot.ts
|
|
96
|
+
async function screenshot(agent, mode = "visible", options = {}) {
|
|
97
|
+
let result;
|
|
98
|
+
switch (mode) {
|
|
99
|
+
case "fullPage":
|
|
100
|
+
result = await agent.screenshotFullPage(options);
|
|
101
|
+
break;
|
|
102
|
+
case "area":
|
|
103
|
+
if (!options.clip) throw new Error("clip option required for area screenshot");
|
|
104
|
+
result = await agent.screenshotArea(options.clip, options);
|
|
105
|
+
break;
|
|
106
|
+
default:
|
|
107
|
+
result = await agent.screenshotVisible(options);
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/scripts/tabs.ts
|
|
114
|
+
async function listTabs(agent) {
|
|
115
|
+
return await agent.listTabs();
|
|
116
|
+
}
|
|
117
|
+
async function closeTab(agent, tabId) {
|
|
118
|
+
await agent.closeTab(tabId);
|
|
119
|
+
}
|
|
120
|
+
async function activateTab(agent, tabId) {
|
|
121
|
+
await agent.activateTab(tabId);
|
|
122
|
+
}
|
|
123
|
+
//#endregion
|
|
124
|
+
export { injectCSS as a, getDOM as c, closeBrowser as d, launchBrowser as f, screenshot as i, getContent as l, closeTab as n, injectScript as o, listTabs as r, evaluate as s, activateTab as t, navigate as u };
|
|
125
|
+
|
|
126
|
+
//# sourceMappingURL=tabs.js.map
|