@flrande/browserctl 0.5.0-dev.22.1 → 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.
Files changed (136) hide show
  1. package/dist/client.d.ts +34 -0
  2. package/dist/client.js +138 -0
  3. package/dist/commandRegistry.d.ts +16 -0
  4. package/dist/commandRegistry.js +21 -0
  5. package/dist/help.d.ts +4 -0
  6. package/dist/help.js +24 -0
  7. package/dist/index.d.ts +3 -0
  8. package/dist/index.js +23 -0
  9. package/dist/runCli.d.ts +5 -0
  10. package/dist/runCli.js +170 -0
  11. package/package.json +32 -59
  12. package/INSTALL-CN.md +0 -92
  13. package/INSTALL.md +0 -92
  14. package/LICENSE +0 -21
  15. package/README-CN.md +0 -69
  16. package/README.md +0 -69
  17. package/apps/browserctl/src/commands/a11y-snapshot.ts +0 -20
  18. package/apps/browserctl/src/commands/act.test.ts +0 -71
  19. package/apps/browserctl/src/commands/act.ts +0 -64
  20. package/apps/browserctl/src/commands/command-wrappers.test.ts +0 -688
  21. package/apps/browserctl/src/commands/common.test.ts +0 -87
  22. package/apps/browserctl/src/commands/common.ts +0 -191
  23. package/apps/browserctl/src/commands/console-list.test.ts +0 -102
  24. package/apps/browserctl/src/commands/console-list.ts +0 -108
  25. package/apps/browserctl/src/commands/cookie-clear.ts +0 -18
  26. package/apps/browserctl/src/commands/cookie-get.ts +0 -18
  27. package/apps/browserctl/src/commands/cookie-set.ts +0 -22
  28. package/apps/browserctl/src/commands/dialog-arm.ts +0 -20
  29. package/apps/browserctl/src/commands/dom-query-all.ts +0 -18
  30. package/apps/browserctl/src/commands/dom-query.ts +0 -18
  31. package/apps/browserctl/src/commands/download-trigger.ts +0 -22
  32. package/apps/browserctl/src/commands/download-wait.test.ts +0 -67
  33. package/apps/browserctl/src/commands/download-wait.ts +0 -27
  34. package/apps/browserctl/src/commands/element-screenshot.ts +0 -20
  35. package/apps/browserctl/src/commands/frame-list.ts +0 -16
  36. package/apps/browserctl/src/commands/frame-snapshot.ts +0 -18
  37. package/apps/browserctl/src/commands/har-export.test.ts +0 -112
  38. package/apps/browserctl/src/commands/har-export.ts +0 -120
  39. package/apps/browserctl/src/commands/memory-delete.ts +0 -20
  40. package/apps/browserctl/src/commands/memory-inspect.ts +0 -20
  41. package/apps/browserctl/src/commands/memory-list.ts +0 -90
  42. package/apps/browserctl/src/commands/memory-mode-set.ts +0 -29
  43. package/apps/browserctl/src/commands/memory-purge.ts +0 -16
  44. package/apps/browserctl/src/commands/memory-resolve.ts +0 -56
  45. package/apps/browserctl/src/commands/memory-status.ts +0 -16
  46. package/apps/browserctl/src/commands/memory-ttl-set.ts +0 -28
  47. package/apps/browserctl/src/commands/memory-upsert.ts +0 -142
  48. package/apps/browserctl/src/commands/network-list.test.ts +0 -110
  49. package/apps/browserctl/src/commands/network-list.ts +0 -112
  50. package/apps/browserctl/src/commands/network-wait-for.test.ts +0 -90
  51. package/apps/browserctl/src/commands/network-wait-for.ts +0 -100
  52. package/apps/browserctl/src/commands/profile-list.ts +0 -16
  53. package/apps/browserctl/src/commands/profile-use.ts +0 -18
  54. package/apps/browserctl/src/commands/response-body.ts +0 -24
  55. package/apps/browserctl/src/commands/screenshot.ts +0 -16
  56. package/apps/browserctl/src/commands/session-drop.test.ts +0 -36
  57. package/apps/browserctl/src/commands/session-drop.ts +0 -16
  58. package/apps/browserctl/src/commands/session-list.test.ts +0 -81
  59. package/apps/browserctl/src/commands/session-list.ts +0 -70
  60. package/apps/browserctl/src/commands/snapshot.ts +0 -16
  61. package/apps/browserctl/src/commands/status.ts +0 -10
  62. package/apps/browserctl/src/commands/storage-get.ts +0 -20
  63. package/apps/browserctl/src/commands/storage-set.ts +0 -22
  64. package/apps/browserctl/src/commands/tab-close.ts +0 -20
  65. package/apps/browserctl/src/commands/tab-focus.ts +0 -20
  66. package/apps/browserctl/src/commands/tab-open.ts +0 -19
  67. package/apps/browserctl/src/commands/tabs.ts +0 -13
  68. package/apps/browserctl/src/commands/trace-get.test.ts +0 -61
  69. package/apps/browserctl/src/commands/trace-get.ts +0 -62
  70. package/apps/browserctl/src/commands/upload-arm.ts +0 -26
  71. package/apps/browserctl/src/commands/wait-element.test.ts +0 -80
  72. package/apps/browserctl/src/commands/wait-element.ts +0 -76
  73. package/apps/browserctl/src/commands/wait-text.test.ts +0 -110
  74. package/apps/browserctl/src/commands/wait-text.ts +0 -93
  75. package/apps/browserctl/src/commands/wait-url.test.ts +0 -80
  76. package/apps/browserctl/src/commands/wait-url.ts +0 -76
  77. package/apps/browserctl/src/daemon-client.test.ts +0 -512
  78. package/apps/browserctl/src/daemon-client.ts +0 -632
  79. package/apps/browserctl/src/e2e.test.ts +0 -103
  80. package/apps/browserctl/src/main.dispatch.test.ts +0 -461
  81. package/apps/browserctl/src/main.test.ts +0 -334
  82. package/apps/browserctl/src/main.ts +0 -957
  83. package/apps/browserctl/src/smoke.e2e.test.ts +0 -97
  84. package/apps/browserctl/src/test-port.ts +0 -26
  85. package/apps/browserd/src/bootstrap.ts +0 -432
  86. package/apps/browserd/src/chrome-relay-extension-bridge.test.ts +0 -250
  87. package/apps/browserd/src/chrome-relay-extension-bridge.ts +0 -506
  88. package/apps/browserd/src/container.ts +0 -3088
  89. package/apps/browserd/src/main.test.ts +0 -1522
  90. package/apps/browserd/src/main.ts +0 -7
  91. package/apps/browserd/src/test-port.ts +0 -26
  92. package/apps/browserd/src/tool-matrix.test.ts +0 -887
  93. package/bin/browserctl.cjs +0 -21
  94. package/bin/browserd.cjs +0 -21
  95. package/extensions/chrome-relay/README-CN.md +0 -39
  96. package/extensions/chrome-relay/README.md +0 -39
  97. package/extensions/chrome-relay/background.js +0 -1687
  98. package/extensions/chrome-relay/manifest.json +0 -15
  99. package/extensions/chrome-relay/popup.html +0 -369
  100. package/extensions/chrome-relay/popup.js +0 -972
  101. package/packages/core/src/bootstrap.test.ts +0 -10
  102. package/packages/core/src/driver-registry.test.ts +0 -45
  103. package/packages/core/src/driver-registry.ts +0 -22
  104. package/packages/core/src/driver.ts +0 -47
  105. package/packages/core/src/index.ts +0 -6
  106. package/packages/core/src/navigation-memory.test.ts +0 -259
  107. package/packages/core/src/navigation-memory.ts +0 -360
  108. package/packages/core/src/ref-cache.test.ts +0 -61
  109. package/packages/core/src/ref-cache.ts +0 -28
  110. package/packages/core/src/session-store.test.ts +0 -82
  111. package/packages/core/src/session-store.ts +0 -138
  112. package/packages/core/src/types.ts +0 -9
  113. package/packages/driver-chrome-relay/src/chrome-relay-driver.test.ts +0 -744
  114. package/packages/driver-chrome-relay/src/chrome-relay-driver.ts +0 -2429
  115. package/packages/driver-chrome-relay/src/chrome-relay-extension-runtime.test.ts +0 -264
  116. package/packages/driver-chrome-relay/src/chrome-relay-extension-runtime.ts +0 -521
  117. package/packages/driver-chrome-relay/src/index.ts +0 -26
  118. package/packages/driver-managed/src/index.ts +0 -22
  119. package/packages/driver-managed/src/managed-driver.test.ts +0 -183
  120. package/packages/driver-managed/src/managed-driver.ts +0 -341
  121. package/packages/driver-managed/src/managed-local-driver.test.ts +0 -608
  122. package/packages/driver-managed/src/managed-local-driver.ts +0 -2243
  123. package/packages/driver-remote-cdp/src/index.ts +0 -19
  124. package/packages/driver-remote-cdp/src/remote-cdp-driver.test.ts +0 -727
  125. package/packages/driver-remote-cdp/src/remote-cdp-driver.ts +0 -2264
  126. package/packages/protocol/src/envelope.test.ts +0 -25
  127. package/packages/protocol/src/envelope.ts +0 -31
  128. package/packages/protocol/src/errors.test.ts +0 -17
  129. package/packages/protocol/src/errors.ts +0 -11
  130. package/packages/protocol/src/index.ts +0 -3
  131. package/packages/protocol/src/tools.ts +0 -3
  132. package/packages/transport-mcp-stdio/src/index.ts +0 -3
  133. package/packages/transport-mcp-stdio/src/sdk-server.ts +0 -139
  134. package/packages/transport-mcp-stdio/src/server.test.ts +0 -281
  135. package/packages/transport-mcp-stdio/src/server.ts +0 -183
  136. package/packages/transport-mcp-stdio/src/tool-map.ts +0 -84
@@ -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
@@ -0,0 +1,4 @@
1
+ import { type DomainName } from "./commandRegistry.js";
2
+ export declare function renderTextHelp(): string;
3
+ export declare function renderDomainHelp(domain: DomainName): string;
4
+ export declare function renderJsonHelp(): string;
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
+ }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export { runCli } from "./runCli.js";
3
+ export type { RunCliDependencies } from "./runCli.js";
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
+ }
@@ -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,59 +1,32 @@
1
- {
2
- "name": "@flrande/browserctl",
3
- "version": "0.5.0-dev.22.1",
4
- "private": false,
5
- "bin": {
6
- "browserctl": "bin/browserctl.cjs",
7
- "browserd": "bin/browserd.cjs"
8
- },
9
- "files": [
10
- "apps/browserctl/src",
11
- "apps/browserd/src",
12
- "packages/core/src",
13
- "packages/driver-chrome-relay/src",
14
- "packages/driver-managed/src",
15
- "packages/driver-remote-cdp/src",
16
- "packages/protocol/src",
17
- "packages/transport-mcp-stdio/src",
18
- "extensions/chrome-relay",
19
- "INSTALL.md",
20
- "INSTALL-CN.md",
21
- "bin",
22
- "README.md",
23
- "README-CN.md",
24
- "LICENSE",
25
- "!**/*.test.ts",
26
- "!**/vitest.config.ts",
27
- "!**/node_modules/**",
28
- "!**/.vite/**"
29
- ],
30
- "dependencies": {
31
- "@modelcontextprotocol/sdk": "^1.17.4",
32
- "playwright-core": "^1.52.0",
33
- "tsx": "^4.21.0",
34
- "ws": "^8.19.0",
35
- "yaml": "^2.8.1",
36
- "zod": "^4.3.6"
37
- },
38
- "devDependencies": {
39
- "@vitest/coverage-v8": "^2.1.8",
40
- "@types/node": "^22.15.30",
41
- "typescript": "^5.6.3",
42
- "vitest": "^2.1.8"
43
- },
44
- "scripts": {
45
- "test": "pnpm run test:unit",
46
- "test:unit": "vitest run --config vitest.config.ts",
47
- "test:contract": "vitest run --config vitest.contract.config.ts",
48
- "test:e2e": "vitest run --config vitest.e2e.config.ts",
49
- "test:e2e:smoke": "vitest run --config vitest.e2e.smoke.config.ts",
50
- "test:e2e:full": "vitest run --config vitest.e2e.full.config.ts",
51
- "test:e2e:stress": "vitest run --config vitest.e2e.stress.config.ts",
52
- "test:smoke": "vitest run --config vitest.smoke.config.ts",
53
- "test:coverage": "vitest run --config vitest.config.ts --coverage",
54
- "test:all": "pnpm run test:unit && pnpm run test:contract && pnpm run test:e2e && pnpm run test:smoke",
55
- "build": "npm pack --dry-run",
56
- "typecheck": "pnpm exec tsc --noEmit -p tsconfig.typecheck.json",
57
- "lint": "node ./scripts/lint.mjs"
58
- }
59
- }
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`