@flrande/browserctl 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +34 -0
- package/dist/client.js +138 -0
- package/dist/commandRegistry.d.ts +16 -0
- package/dist/commandRegistry.js +21 -0
- package/dist/help.d.ts +4 -0
- package/dist/help.js +24 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +23 -0
- package/dist/runCli.d.ts +5 -0
- package/dist/runCli.js +170 -0
- package/package.json +32 -57
- package/INSTALL-CN.md +0 -92
- package/INSTALL.md +0 -92
- package/LICENSE +0 -21
- package/README-CN.md +0 -69
- package/README.md +0 -69
- package/apps/browserctl/src/commands/a11y-snapshot.ts +0 -20
- package/apps/browserctl/src/commands/act.test.ts +0 -71
- package/apps/browserctl/src/commands/act.ts +0 -64
- package/apps/browserctl/src/commands/command-wrappers.test.ts +0 -688
- package/apps/browserctl/src/commands/common.test.ts +0 -87
- package/apps/browserctl/src/commands/common.ts +0 -191
- package/apps/browserctl/src/commands/console-list.test.ts +0 -102
- package/apps/browserctl/src/commands/console-list.ts +0 -108
- package/apps/browserctl/src/commands/cookie-clear.ts +0 -18
- package/apps/browserctl/src/commands/cookie-get.ts +0 -18
- package/apps/browserctl/src/commands/cookie-set.ts +0 -22
- package/apps/browserctl/src/commands/dialog-arm.ts +0 -20
- package/apps/browserctl/src/commands/dom-query-all.ts +0 -18
- package/apps/browserctl/src/commands/dom-query.ts +0 -18
- package/apps/browserctl/src/commands/download-trigger.ts +0 -22
- package/apps/browserctl/src/commands/download-wait.test.ts +0 -67
- package/apps/browserctl/src/commands/download-wait.ts +0 -27
- package/apps/browserctl/src/commands/element-screenshot.ts +0 -20
- package/apps/browserctl/src/commands/frame-list.ts +0 -16
- package/apps/browserctl/src/commands/frame-snapshot.ts +0 -18
- package/apps/browserctl/src/commands/har-export.test.ts +0 -112
- package/apps/browserctl/src/commands/har-export.ts +0 -120
- package/apps/browserctl/src/commands/memory-delete.ts +0 -20
- package/apps/browserctl/src/commands/memory-inspect.ts +0 -20
- package/apps/browserctl/src/commands/memory-list.ts +0 -90
- package/apps/browserctl/src/commands/memory-mode-set.ts +0 -29
- package/apps/browserctl/src/commands/memory-purge.ts +0 -16
- package/apps/browserctl/src/commands/memory-resolve.ts +0 -56
- package/apps/browserctl/src/commands/memory-status.ts +0 -16
- package/apps/browserctl/src/commands/memory-ttl-set.ts +0 -28
- package/apps/browserctl/src/commands/memory-upsert.ts +0 -142
- package/apps/browserctl/src/commands/network-list.test.ts +0 -110
- package/apps/browserctl/src/commands/network-list.ts +0 -112
- package/apps/browserctl/src/commands/network-wait-for.test.ts +0 -90
- package/apps/browserctl/src/commands/network-wait-for.ts +0 -100
- package/apps/browserctl/src/commands/profile-list.ts +0 -16
- package/apps/browserctl/src/commands/profile-use.ts +0 -18
- package/apps/browserctl/src/commands/response-body.ts +0 -24
- package/apps/browserctl/src/commands/screenshot.ts +0 -16
- package/apps/browserctl/src/commands/session-drop.test.ts +0 -36
- package/apps/browserctl/src/commands/session-drop.ts +0 -16
- package/apps/browserctl/src/commands/session-list.test.ts +0 -81
- package/apps/browserctl/src/commands/session-list.ts +0 -70
- package/apps/browserctl/src/commands/snapshot.ts +0 -16
- package/apps/browserctl/src/commands/status.ts +0 -10
- package/apps/browserctl/src/commands/storage-get.ts +0 -20
- package/apps/browserctl/src/commands/storage-set.ts +0 -22
- package/apps/browserctl/src/commands/tab-close.ts +0 -20
- package/apps/browserctl/src/commands/tab-focus.ts +0 -20
- package/apps/browserctl/src/commands/tab-open.ts +0 -19
- package/apps/browserctl/src/commands/tabs.ts +0 -13
- package/apps/browserctl/src/commands/trace-get.test.ts +0 -61
- package/apps/browserctl/src/commands/trace-get.ts +0 -62
- package/apps/browserctl/src/commands/upload-arm.ts +0 -26
- package/apps/browserctl/src/commands/wait-element.test.ts +0 -80
- package/apps/browserctl/src/commands/wait-element.ts +0 -76
- package/apps/browserctl/src/commands/wait-text.test.ts +0 -110
- package/apps/browserctl/src/commands/wait-text.ts +0 -93
- package/apps/browserctl/src/commands/wait-url.test.ts +0 -80
- package/apps/browserctl/src/commands/wait-url.ts +0 -76
- package/apps/browserctl/src/daemon-client.test.ts +0 -253
- package/apps/browserctl/src/daemon-client.ts +0 -632
- package/apps/browserctl/src/e2e.test.ts +0 -103
- package/apps/browserctl/src/main.dispatch.test.ts +0 -461
- package/apps/browserctl/src/main.test.ts +0 -334
- package/apps/browserctl/src/main.ts +0 -957
- package/apps/browserctl/src/smoke.e2e.test.ts +0 -97
- package/apps/browserctl/src/test-port.ts +0 -26
- package/apps/browserd/src/bootstrap.ts +0 -432
- package/apps/browserd/src/chrome-relay-extension-bridge.test.ts +0 -250
- package/apps/browserd/src/chrome-relay-extension-bridge.ts +0 -506
- package/apps/browserd/src/container.ts +0 -3088
- package/apps/browserd/src/main.test.ts +0 -1436
- package/apps/browserd/src/main.ts +0 -7
- package/apps/browserd/src/test-port.ts +0 -26
- package/apps/browserd/src/tool-matrix.test.ts +0 -887
- package/bin/browserctl.cjs +0 -21
- package/bin/browserd.cjs +0 -21
- package/extensions/chrome-relay/README-CN.md +0 -39
- package/extensions/chrome-relay/README.md +0 -39
- package/extensions/chrome-relay/background.js +0 -1687
- package/extensions/chrome-relay/manifest.json +0 -15
- package/extensions/chrome-relay/popup.html +0 -369
- package/extensions/chrome-relay/popup.js +0 -972
- package/packages/core/src/bootstrap.test.ts +0 -10
- package/packages/core/src/driver-registry.test.ts +0 -45
- package/packages/core/src/driver-registry.ts +0 -22
- package/packages/core/src/driver.ts +0 -47
- package/packages/core/src/index.ts +0 -6
- package/packages/core/src/navigation-memory.test.ts +0 -259
- package/packages/core/src/navigation-memory.ts +0 -360
- package/packages/core/src/ref-cache.test.ts +0 -61
- package/packages/core/src/ref-cache.ts +0 -28
- package/packages/core/src/session-store.test.ts +0 -82
- package/packages/core/src/session-store.ts +0 -138
- package/packages/core/src/types.ts +0 -9
- package/packages/driver-chrome-relay/src/chrome-relay-driver.test.ts +0 -744
- package/packages/driver-chrome-relay/src/chrome-relay-driver.ts +0 -2429
- package/packages/driver-chrome-relay/src/chrome-relay-extension-runtime.test.ts +0 -264
- package/packages/driver-chrome-relay/src/chrome-relay-extension-runtime.ts +0 -521
- package/packages/driver-chrome-relay/src/index.ts +0 -26
- package/packages/driver-managed/src/index.ts +0 -22
- package/packages/driver-managed/src/managed-driver.test.ts +0 -183
- package/packages/driver-managed/src/managed-driver.ts +0 -341
- package/packages/driver-managed/src/managed-local-driver.test.ts +0 -608
- package/packages/driver-managed/src/managed-local-driver.ts +0 -2243
- package/packages/driver-remote-cdp/src/index.ts +0 -19
- package/packages/driver-remote-cdp/src/remote-cdp-driver.test.ts +0 -727
- package/packages/driver-remote-cdp/src/remote-cdp-driver.ts +0 -2264
- package/packages/protocol/src/envelope.test.ts +0 -25
- package/packages/protocol/src/envelope.ts +0 -31
- package/packages/protocol/src/errors.test.ts +0 -17
- package/packages/protocol/src/errors.ts +0 -11
- package/packages/protocol/src/index.ts +0 -3
- package/packages/protocol/src/tools.ts +0 -3
- package/packages/transport-mcp-stdio/src/index.ts +0 -3
- package/packages/transport-mcp-stdio/src/sdk-server.ts +0 -139
- package/packages/transport-mcp-stdio/src/server.test.ts +0 -281
- package/packages/transport-mcp-stdio/src/server.ts +0 -183
- package/packages/transport-mcp-stdio/src/tool-map.ts +0 -84
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
declare const JSON_RPC_VERSION = "2.0";
|
|
2
|
+
export declare const RPC_CLIENT_CONFIG_ERROR = -32001;
|
|
3
|
+
export declare const RPC_CLIENT_TRANSPORT_ERROR = -32002;
|
|
4
|
+
export declare const RPC_CLIENT_PROTOCOL_ERROR = -32003;
|
|
5
|
+
export declare const RPC_CLIENT_INTERNAL_ERROR = -32004;
|
|
6
|
+
export type JsonRpcId = number | string | null;
|
|
7
|
+
export type JsonRpcRequest = {
|
|
8
|
+
jsonrpc: typeof JSON_RPC_VERSION;
|
|
9
|
+
id: JsonRpcId;
|
|
10
|
+
method: string;
|
|
11
|
+
params: unknown;
|
|
12
|
+
};
|
|
13
|
+
export type JsonRpcError = {
|
|
14
|
+
code: number;
|
|
15
|
+
message: string;
|
|
16
|
+
data?: unknown;
|
|
17
|
+
};
|
|
18
|
+
export type JsonRpcSuccessResponse = {
|
|
19
|
+
jsonrpc: typeof JSON_RPC_VERSION;
|
|
20
|
+
id: JsonRpcId;
|
|
21
|
+
result: unknown;
|
|
22
|
+
};
|
|
23
|
+
export type JsonRpcErrorResponse = {
|
|
24
|
+
jsonrpc: typeof JSON_RPC_VERSION;
|
|
25
|
+
id: JsonRpcId;
|
|
26
|
+
error: JsonRpcError;
|
|
27
|
+
};
|
|
28
|
+
export type JsonRpcResponse = JsonRpcSuccessResponse | JsonRpcErrorResponse;
|
|
29
|
+
export type RpcClientOptions = {
|
|
30
|
+
pipeName?: string;
|
|
31
|
+
timeoutMs?: number;
|
|
32
|
+
};
|
|
33
|
+
export declare function callDaemon(request: JsonRpcRequest, options?: RpcClientOptions): Promise<JsonRpcResponse>;
|
|
34
|
+
export {};
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { connect } from "node:net";
|
|
2
|
+
const JSON_RPC_VERSION = "2.0";
|
|
3
|
+
const DEFAULT_TIMEOUT_MS = 2000;
|
|
4
|
+
export const RPC_CLIENT_CONFIG_ERROR = -32001;
|
|
5
|
+
export const RPC_CLIENT_TRANSPORT_ERROR = -32002;
|
|
6
|
+
export const RPC_CLIENT_PROTOCOL_ERROR = -32003;
|
|
7
|
+
export const RPC_CLIENT_INTERNAL_ERROR = -32004;
|
|
8
|
+
export async function callDaemon(request, options = {}) {
|
|
9
|
+
const pipeName = options.pipeName ?? process.env.BROWSERCTL_DAEMON_PIPE_NAME;
|
|
10
|
+
if (!pipeName) {
|
|
11
|
+
return createErrorResponse(request.id, RPC_CLIENT_CONFIG_ERROR, "Daemon pipe name is not configured. Pass options.pipeName or set BROWSERCTL_DAEMON_PIPE_NAME.");
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const rawResponse = await sendRequest(request, pipeName, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
15
|
+
return parseAndValidateResponse(rawResponse, request.id);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
return createErrorResponse(request.id, RPC_CLIENT_TRANSPORT_ERROR, toErrorMessage(error));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function connectToPipe(pipeName) {
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
const socket = connect(pipeName);
|
|
24
|
+
socket.once("connect", () => resolve(socket));
|
|
25
|
+
socket.once("error", reject);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
async function sendRequest(request, pipeName, timeoutMs) {
|
|
29
|
+
const socket = await connectToPipe(pipeName);
|
|
30
|
+
try {
|
|
31
|
+
socket.write(`${JSON.stringify(request)}\n`, "utf8");
|
|
32
|
+
return readLine(socket, timeoutMs);
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
socket.end();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function parseAndValidateResponse(rawResponse, requestId) {
|
|
39
|
+
let parsed;
|
|
40
|
+
try {
|
|
41
|
+
parsed = JSON.parse(rawResponse);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return createErrorResponse(requestId, RPC_CLIENT_PROTOCOL_ERROR, "Daemon response was not valid JSON.");
|
|
45
|
+
}
|
|
46
|
+
if (!isRecord(parsed) || parsed.jsonrpc !== JSON_RPC_VERSION) {
|
|
47
|
+
return createErrorResponse(requestId, RPC_CLIENT_PROTOCOL_ERROR, "Daemon response must include jsonrpc: \"2.0\".");
|
|
48
|
+
}
|
|
49
|
+
if (!Object.prototype.hasOwnProperty.call(parsed, "id") || !isJsonRpcId(parsed.id)) {
|
|
50
|
+
return createErrorResponse(requestId, RPC_CLIENT_PROTOCOL_ERROR, "Daemon response id is missing or invalid.");
|
|
51
|
+
}
|
|
52
|
+
if (parsed.id !== requestId) {
|
|
53
|
+
return createErrorResponse(requestId, RPC_CLIENT_PROTOCOL_ERROR, `Daemon response id mismatch. Expected ${String(requestId)}.`);
|
|
54
|
+
}
|
|
55
|
+
const hasResult = Object.prototype.hasOwnProperty.call(parsed, "result");
|
|
56
|
+
const hasError = Object.prototype.hasOwnProperty.call(parsed, "error");
|
|
57
|
+
if (hasResult === hasError) {
|
|
58
|
+
return createErrorResponse(requestId, RPC_CLIENT_PROTOCOL_ERROR, "Daemon response must include exactly one of result or error.");
|
|
59
|
+
}
|
|
60
|
+
if (hasError) {
|
|
61
|
+
if (!isJsonRpcError(parsed.error)) {
|
|
62
|
+
return createErrorResponse(requestId, RPC_CLIENT_PROTOCOL_ERROR, "Daemon error response is invalid.");
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
jsonrpc: JSON_RPC_VERSION,
|
|
66
|
+
id: parsed.id,
|
|
67
|
+
error: parsed.error
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
jsonrpc: JSON_RPC_VERSION,
|
|
72
|
+
id: parsed.id,
|
|
73
|
+
result: parsed.result
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function readLine(socket, timeoutMs) {
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
let buffer = "";
|
|
79
|
+
const timeoutId = setTimeout(() => {
|
|
80
|
+
cleanup();
|
|
81
|
+
reject(new Error("Timed out waiting for daemon response."));
|
|
82
|
+
}, timeoutMs);
|
|
83
|
+
const onData = (chunk) => {
|
|
84
|
+
buffer += chunk.toString("utf8");
|
|
85
|
+
const separatorIndex = buffer.indexOf("\n");
|
|
86
|
+
if (separatorIndex < 0) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
cleanup();
|
|
90
|
+
resolve(buffer.slice(0, separatorIndex));
|
|
91
|
+
};
|
|
92
|
+
const onError = (error) => {
|
|
93
|
+
cleanup();
|
|
94
|
+
reject(error);
|
|
95
|
+
};
|
|
96
|
+
const onClose = () => {
|
|
97
|
+
cleanup();
|
|
98
|
+
reject(new Error("Socket closed before daemon responded."));
|
|
99
|
+
};
|
|
100
|
+
const cleanup = () => {
|
|
101
|
+
clearTimeout(timeoutId);
|
|
102
|
+
socket.off("data", onData);
|
|
103
|
+
socket.off("error", onError);
|
|
104
|
+
socket.off("close", onClose);
|
|
105
|
+
};
|
|
106
|
+
socket.on("data", onData);
|
|
107
|
+
socket.on("error", onError);
|
|
108
|
+
socket.on("close", onClose);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function createErrorResponse(id, code, message) {
|
|
112
|
+
return {
|
|
113
|
+
jsonrpc: JSON_RPC_VERSION,
|
|
114
|
+
id,
|
|
115
|
+
error: {
|
|
116
|
+
code,
|
|
117
|
+
message
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function toErrorMessage(error) {
|
|
122
|
+
if (error instanceof Error) {
|
|
123
|
+
return error.message;
|
|
124
|
+
}
|
|
125
|
+
return `Unexpected client failure: ${String(error)}`;
|
|
126
|
+
}
|
|
127
|
+
function isRecord(value) {
|
|
128
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
129
|
+
}
|
|
130
|
+
function isJsonRpcId(value) {
|
|
131
|
+
return value === null || typeof value === "number" || typeof value === "string";
|
|
132
|
+
}
|
|
133
|
+
function isJsonRpcError(value) {
|
|
134
|
+
if (!isRecord(value)) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
return typeof value.code === "number" && typeof value.message === "string";
|
|
138
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const DOMAIN_HELP: {
|
|
2
|
+
readonly session: readonly ["create", "close"];
|
|
3
|
+
readonly page: readonly ["navigate", "reload"];
|
|
4
|
+
readonly dom: readonly ["click", "type"];
|
|
5
|
+
readonly view: readonly ["snapshot", "dom"];
|
|
6
|
+
readonly network: readonly ["capture.start", "capture.stop"];
|
|
7
|
+
readonly runtime: readonly ["console.tail", "eval"];
|
|
8
|
+
readonly workflow: readonly ["run", "replay"];
|
|
9
|
+
readonly memory: readonly ["suggest", "explain", "pin", "block"];
|
|
10
|
+
readonly policy: readonly ["preview", "confirm", "whitelist.add", "whitelist.remove"];
|
|
11
|
+
readonly artifact: readonly ["list", "read", "delete"];
|
|
12
|
+
};
|
|
13
|
+
export type DomainName = keyof typeof DOMAIN_HELP;
|
|
14
|
+
export declare function isDomainName(value: string): value is DomainName;
|
|
15
|
+
export declare function hasDomainCommand(domain: DomainName, command: string): boolean;
|
|
16
|
+
export declare function listDomains(): DomainName[];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const DOMAIN_HELP = {
|
|
2
|
+
session: ["create", "close"],
|
|
3
|
+
page: ["navigate", "reload"],
|
|
4
|
+
dom: ["click", "type"],
|
|
5
|
+
view: ["snapshot", "dom"],
|
|
6
|
+
network: ["capture.start", "capture.stop"],
|
|
7
|
+
runtime: ["console.tail", "eval"],
|
|
8
|
+
workflow: ["run", "replay"],
|
|
9
|
+
memory: ["suggest", "explain", "pin", "block"],
|
|
10
|
+
policy: ["preview", "confirm", "whitelist.add", "whitelist.remove"],
|
|
11
|
+
artifact: ["list", "read", "delete"]
|
|
12
|
+
};
|
|
13
|
+
export function isDomainName(value) {
|
|
14
|
+
return Object.prototype.hasOwnProperty.call(DOMAIN_HELP, value);
|
|
15
|
+
}
|
|
16
|
+
export function hasDomainCommand(domain, command) {
|
|
17
|
+
return DOMAIN_HELP[domain].includes(command);
|
|
18
|
+
}
|
|
19
|
+
export function listDomains() {
|
|
20
|
+
return Object.keys(DOMAIN_HELP);
|
|
21
|
+
}
|
package/dist/help.d.ts
ADDED
package/dist/help.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { DOMAIN_HELP, listDomains } from "./commandRegistry.js";
|
|
2
|
+
export function renderTextHelp() {
|
|
3
|
+
const lines = ["browserctl command reference", "", "domains:"];
|
|
4
|
+
for (const domain of listDomains()) {
|
|
5
|
+
lines.push(` ${domain}: ${DOMAIN_HELP[domain].join(", ")}`);
|
|
6
|
+
}
|
|
7
|
+
lines.push("");
|
|
8
|
+
lines.push("use `browserctl help <domain>` for more details.");
|
|
9
|
+
lines.push("use `browserctl --json-help` for machine-readable help.");
|
|
10
|
+
return lines.join("\n");
|
|
11
|
+
}
|
|
12
|
+
export function renderDomainHelp(domain) {
|
|
13
|
+
const lines = [`browserctl ${domain}`, "", "commands:"];
|
|
14
|
+
for (const command of DOMAIN_HELP[domain]) {
|
|
15
|
+
lines.push(` ${domain} ${command}`);
|
|
16
|
+
}
|
|
17
|
+
return lines.join("\n");
|
|
18
|
+
}
|
|
19
|
+
export function renderJsonHelp() {
|
|
20
|
+
return JSON.stringify({
|
|
21
|
+
domains: listDomains(),
|
|
22
|
+
commands: DOMAIN_HELP
|
|
23
|
+
});
|
|
24
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
import { runCli } from "./runCli.js";
|
|
4
|
+
export { runCli } from "./runCli.js";
|
|
5
|
+
async function main() {
|
|
6
|
+
const output = await runCli(process.argv.slice(2));
|
|
7
|
+
process.stdout.write(`${output}\n`);
|
|
8
|
+
if (hasErrorEnvelope(output)) {
|
|
9
|
+
process.exitCode = 1;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
13
|
+
void main();
|
|
14
|
+
}
|
|
15
|
+
function hasErrorEnvelope(output) {
|
|
16
|
+
try {
|
|
17
|
+
const parsed = JSON.parse(output);
|
|
18
|
+
return typeof parsed === "object" && parsed !== null && "error" in parsed;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
package/dist/runCli.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type JsonRpcRequest, type JsonRpcResponse, type RpcClientOptions } from "./client.js";
|
|
2
|
+
export type RunCliDependencies = {
|
|
3
|
+
callDaemon?: (request: JsonRpcRequest, options?: RpcClientOptions) => Promise<JsonRpcResponse>;
|
|
4
|
+
};
|
|
5
|
+
export declare function runCli(args: string[], options?: RpcClientOptions, dependencies?: RunCliDependencies): Promise<string>;
|
package/dist/runCli.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { callDaemon, RPC_CLIENT_INTERNAL_ERROR } from "./client.js";
|
|
2
|
+
import { hasDomainCommand, isDomainName } from "./commandRegistry.js";
|
|
3
|
+
import { renderDomainHelp, renderJsonHelp, renderTextHelp } from "./help.js";
|
|
4
|
+
const JSON_RPC_VERSION = "2.0";
|
|
5
|
+
const CLI_REQUEST_ID = 1;
|
|
6
|
+
const JSON_RPC_INVALID_REQUEST = -32600;
|
|
7
|
+
export async function runCli(args, options = {}, dependencies = {}) {
|
|
8
|
+
if (args[0] === "--help" || (args[0] === "help" && args[1] === undefined)) {
|
|
9
|
+
return renderTextHelp();
|
|
10
|
+
}
|
|
11
|
+
if (args[0] === "--json-help") {
|
|
12
|
+
return renderJsonHelp();
|
|
13
|
+
}
|
|
14
|
+
if (args[0] && isDomainName(args[0]) && (args[1] === "--help" || args[1] === "-h")) {
|
|
15
|
+
return renderDomainHelp(args[0]);
|
|
16
|
+
}
|
|
17
|
+
if (args[0] === "help") {
|
|
18
|
+
const helpTarget = args[1];
|
|
19
|
+
if (helpTarget && isDomainName(helpTarget)) {
|
|
20
|
+
return renderDomainHelp(helpTarget);
|
|
21
|
+
}
|
|
22
|
+
return createInvalidRequestError(`Unknown help topic: ${helpTarget ?? "<empty>"}`);
|
|
23
|
+
}
|
|
24
|
+
const resolvedCommand = resolveCommand(args);
|
|
25
|
+
if (resolvedCommand === null) {
|
|
26
|
+
return createInvalidRequestError("Missing RPC method.");
|
|
27
|
+
}
|
|
28
|
+
if ("error" in resolvedCommand) {
|
|
29
|
+
return createInvalidRequestError(resolvedCommand.error);
|
|
30
|
+
}
|
|
31
|
+
const request = {
|
|
32
|
+
jsonrpc: JSON_RPC_VERSION,
|
|
33
|
+
id: CLI_REQUEST_ID,
|
|
34
|
+
method: resolvedCommand.method,
|
|
35
|
+
params: resolvedCommand.params
|
|
36
|
+
};
|
|
37
|
+
const invokeDaemon = dependencies.callDaemon ?? callDaemon;
|
|
38
|
+
try {
|
|
39
|
+
const response = await invokeDaemon(request, options);
|
|
40
|
+
return JSON.stringify(response);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
return JSON.stringify({
|
|
44
|
+
jsonrpc: JSON_RPC_VERSION,
|
|
45
|
+
id: request.id,
|
|
46
|
+
error: {
|
|
47
|
+
code: RPC_CLIENT_INTERNAL_ERROR,
|
|
48
|
+
message: error instanceof Error ? error.message : String(error)
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function resolveCommand(args) {
|
|
54
|
+
const first = args[0];
|
|
55
|
+
if (!first) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
if (first.includes(".")) {
|
|
59
|
+
const parsed = parseParams(args.slice(1));
|
|
60
|
+
if ("error" in parsed) {
|
|
61
|
+
return parsed;
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
method: first,
|
|
65
|
+
params: parsed.params
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (!isDomainName(first)) {
|
|
69
|
+
const parsed = parseParams(args.slice(1));
|
|
70
|
+
if ("error" in parsed) {
|
|
71
|
+
return parsed;
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
method: first,
|
|
75
|
+
params: parsed.params
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const onePartCommand = args[1];
|
|
79
|
+
const twoPartCommand = args[1] !== undefined && args[2] !== undefined ? `${args[1]}.${args[2]}` : undefined;
|
|
80
|
+
if (twoPartCommand && hasDomainCommand(first, twoPartCommand)) {
|
|
81
|
+
const parsed = parseParams(args.slice(3));
|
|
82
|
+
if ("error" in parsed) {
|
|
83
|
+
return parsed;
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
method: `${first}.${twoPartCommand}`,
|
|
87
|
+
params: parsed.params
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (onePartCommand && hasDomainCommand(first, onePartCommand)) {
|
|
91
|
+
const parsed = parseParams(args.slice(2));
|
|
92
|
+
if ("error" in parsed) {
|
|
93
|
+
return parsed;
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
method: `${first}.${onePartCommand}`,
|
|
97
|
+
params: parsed.params
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
error: `Unknown command: ${args.join(" ")}`
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function parseParams(tokens) {
|
|
105
|
+
const params = {};
|
|
106
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
107
|
+
const token = tokens[index];
|
|
108
|
+
if (!token.startsWith("--")) {
|
|
109
|
+
return {
|
|
110
|
+
error: `Unexpected positional argument: ${token}`
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const key = token.slice(2);
|
|
114
|
+
if (!key) {
|
|
115
|
+
return {
|
|
116
|
+
error: "Argument flag must include a key after --"
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const next = tokens[index + 1];
|
|
120
|
+
if (next === undefined || next.startsWith("--")) {
|
|
121
|
+
appendParam(params, key, true);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
appendParam(params, key, coerceParamValue(next));
|
|
125
|
+
index += 1;
|
|
126
|
+
}
|
|
127
|
+
return { params };
|
|
128
|
+
}
|
|
129
|
+
function coerceParamValue(input) {
|
|
130
|
+
if ((input.startsWith("{") && input.endsWith("}")) || (input.startsWith("[") && input.endsWith("]"))) {
|
|
131
|
+
try {
|
|
132
|
+
return JSON.parse(input);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return input;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (input === "true") {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
if (input === "false") {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
if (/^-?\d+(\.\d+)?$/.test(input)) {
|
|
145
|
+
return Number(input);
|
|
146
|
+
}
|
|
147
|
+
return input;
|
|
148
|
+
}
|
|
149
|
+
function appendParam(params, key, value) {
|
|
150
|
+
const current = params[key];
|
|
151
|
+
if (current === undefined) {
|
|
152
|
+
params[key] = value;
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (Array.isArray(current)) {
|
|
156
|
+
current.push(value);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
params[key] = [current, value];
|
|
160
|
+
}
|
|
161
|
+
function createInvalidRequestError(message) {
|
|
162
|
+
return JSON.stringify({
|
|
163
|
+
jsonrpc: JSON_RPC_VERSION,
|
|
164
|
+
id: null,
|
|
165
|
+
error: {
|
|
166
|
+
code: JSON_RPC_INVALID_REQUEST,
|
|
167
|
+
message
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
package/package.json
CHANGED
|
@@ -1,57 +1,32 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@flrande/browserctl",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"tsx": "^4.21.0",
|
|
34
|
-
"ws": "^8.19.0",
|
|
35
|
-
"yaml": "^2.8.1",
|
|
36
|
-
"zod": "^4.3.6"
|
|
37
|
-
},
|
|
38
|
-
"devDependencies": {
|
|
39
|
-
"@types/node": "^22.15.30",
|
|
40
|
-
"typescript": "^5.6.3",
|
|
41
|
-
"vitest": "^2.1.8"
|
|
42
|
-
},
|
|
43
|
-
"scripts": {
|
|
44
|
-
"test": "pnpm run test:unit",
|
|
45
|
-
"test:unit": "vitest run --config vitest.config.ts",
|
|
46
|
-
"test:contract": "vitest run --config vitest.contract.config.ts",
|
|
47
|
-
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
48
|
-
"test:e2e:smoke": "vitest run --config vitest.e2e.smoke.config.ts",
|
|
49
|
-
"test:e2e:full": "vitest run --config vitest.e2e.full.config.ts",
|
|
50
|
-
"test:e2e:stress": "vitest run --config vitest.e2e.stress.config.ts",
|
|
51
|
-
"test:smoke": "vitest run --config vitest.smoke.config.ts",
|
|
52
|
-
"test:all": "pnpm run test:unit && pnpm run test:contract && pnpm run test:e2e && pnpm run test:smoke",
|
|
53
|
-
"build": "npm pack --dry-run",
|
|
54
|
-
"typecheck": "pnpm exec tsc --noEmit -p tsconfig.typecheck.json",
|
|
55
|
-
"lint": "node ./scripts/lint.mjs"
|
|
56
|
-
}
|
|
57
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@flrande/browserctl",
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"browserctl": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/Flrande/browserctl"
|
|
22
|
+
},
|
|
23
|
+
"private": false,
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public",
|
|
26
|
+
"provenance": true
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc src/index.ts --outDir dist --rootDir src --target ES2022 --module NodeNext --moduleResolution NodeNext --strict --esModuleInterop --skipLibCheck --declaration --pretty false",
|
|
30
|
+
"test": "vitest run"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/INSTALL-CN.md
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
# 安装 browserctl Skills
|
|
2
|
-
|
|
3
|
-
[English](INSTALL.md) | 简体中文
|
|
4
|
-
|
|
5
|
-
本仓库发布一个默认可安装 skill:
|
|
6
|
-
|
|
7
|
-
- `skills/browserctl`
|
|
8
|
-
|
|
9
|
-
## 给 Agent 的主提示词
|
|
10
|
-
|
|
11
|
-
把下面文本贴给 agent:
|
|
12
|
-
|
|
13
|
-
```text
|
|
14
|
-
请抓取并遵循 https://raw.githubusercontent.com/Flrande/browserctl/refs/heads/master/INSTALL.md 中的安装说明。
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## 平台安装入口
|
|
18
|
-
|
|
19
|
-
- Codex:`https://raw.githubusercontent.com/Flrande/browserctl/refs/heads/master/.codex/INSTALL.md`
|
|
20
|
-
- Claude Code:`https://raw.githubusercontent.com/Flrande/browserctl/refs/heads/master/.claude-plugin/INSTALL.md`
|
|
21
|
-
- Cursor:`https://raw.githubusercontent.com/Flrande/browserctl/refs/heads/master/.cursor-plugin/INSTALL.md`
|
|
22
|
-
- OpenCode:`https://raw.githubusercontent.com/Flrande/browserctl/refs/heads/master/.opencode/INSTALL.md`
|
|
23
|
-
|
|
24
|
-
## 安装后更新
|
|
25
|
-
|
|
26
|
-
默认推荐使用“仓库同源更新”,保证 skill、扩展和 CLI 版本一致。
|
|
27
|
-
|
|
28
|
-
PowerShell 7:
|
|
29
|
-
|
|
30
|
-
```powershell
|
|
31
|
-
$repoPath = Join-Path $env:USERPROFILE ".codex\\browserctl" # 按你的本地克隆路径调整
|
|
32
|
-
|
|
33
|
-
git -C $repoPath pull --ff-only
|
|
34
|
-
npm install --global "$repoPath"
|
|
35
|
-
|
|
36
|
-
browserctl daemon-stop --json
|
|
37
|
-
browserctl daemon-start --json
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
Bash:
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
repoPath="$HOME/.codex/browserctl" # 按你的本地克隆路径调整
|
|
44
|
-
|
|
45
|
-
git -C "$repoPath" pull --ff-only
|
|
46
|
-
npm install --global "$repoPath"
|
|
47
|
-
|
|
48
|
-
browserctl daemon-stop --json
|
|
49
|
-
browserctl daemon-start --json
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
拉取后需要在 `edge://extensions` 或 `chrome://extensions` 里刷新扩展:
|
|
53
|
-
|
|
54
|
-
1. 打开扩展管理页。
|
|
55
|
-
2. 找到 `BrowserCtl Relay`。
|
|
56
|
-
3. 点击 `Reload`。
|
|
57
|
-
|
|
58
|
-
验证命令:
|
|
59
|
-
|
|
60
|
-
PowerShell 7:
|
|
61
|
-
|
|
62
|
-
```powershell
|
|
63
|
-
browserctl status --json --session relay --profile chrome-relay
|
|
64
|
-
Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:9223/browserctl/relay/status" -TimeoutSec 3
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
Bash:
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
browserctl status --json --session relay --profile chrome-relay
|
|
71
|
-
curl --fail --max-time 3 "http://127.0.0.1:9223/browserctl/relay/status"
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
可选兜底(非默认):仅从 npm 仓库更新 CLI。
|
|
75
|
-
|
|
76
|
-
PowerShell 7:
|
|
77
|
-
|
|
78
|
-
```powershell
|
|
79
|
-
npm install --global @flrande/browserctl@latest
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
Bash:
|
|
83
|
-
|
|
84
|
-
```bash
|
|
85
|
-
npm install --global @flrande/browserctl@latest
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
## 兜底约定
|
|
89
|
-
|
|
90
|
-
如果平台安装入口不可用,agent 应直接抓取并遵循:
|
|
91
|
-
|
|
92
|
-
- `https://raw.githubusercontent.com/Flrande/browserctl/refs/heads/master/skills/browserctl/SKILL.md`
|