opendevbrowser 0.0.10 → 0.0.12
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 +224 -25
- package/dist/chunk-WTFSMBVH.js +2815 -0
- package/dist/chunk-WTFSMBVH.js.map +1 -0
- package/dist/cli/index.js +1589 -71
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +164 -2698
- package/dist/index.js.map +1 -1
- package/dist/opendevbrowser.js +164 -2698
- package/dist/opendevbrowser.js.map +1 -1
- package/extension/dist/background.js +121 -0
- package/extension/dist/popup.js +93 -14
- package/extension/dist/relay-settings.js +3 -1
- package/extension/icons/icon128.png +0 -0
- package/extension/icons/icon16.png +0 -0
- package/extension/icons/icon32.png +0 -0
- package/extension/icons/icon48.png +0 -0
- package/extension/manifest.json +1 -1
- package/extension/popup.html +326 -66
- package/package.json +2 -2
- package/skills/AGENTS.md +1 -0
- package/dist/chunk-R5VUZEUU.js +0 -128
- package/dist/chunk-R5VUZEUU.js.map +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -1,8 +1,52 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
createOpenDevBrowserCore,
|
|
3
4
|
extractExtension,
|
|
4
|
-
generateSecureToken
|
|
5
|
-
|
|
5
|
+
generateSecureToken,
|
|
6
|
+
loadGlobalConfig
|
|
7
|
+
} from "../chunk-WTFSMBVH.js";
|
|
8
|
+
|
|
9
|
+
// src/cli/errors.ts
|
|
10
|
+
var EXIT_SUCCESS = 0;
|
|
11
|
+
var EXIT_USAGE = 1;
|
|
12
|
+
var EXIT_EXECUTION = 2;
|
|
13
|
+
var EXIT_DISCONNECTED = 10;
|
|
14
|
+
var CliError = class extends Error {
|
|
15
|
+
exitCode;
|
|
16
|
+
constructor(message, exitCode) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.exitCode = exitCode;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
function createUsageError(message) {
|
|
22
|
+
return new CliError(message, EXIT_USAGE);
|
|
23
|
+
}
|
|
24
|
+
function createDisconnectedError(message) {
|
|
25
|
+
return new CliError(message, EXIT_DISCONNECTED);
|
|
26
|
+
}
|
|
27
|
+
function toCliError(error, fallbackExitCode = EXIT_EXECUTION) {
|
|
28
|
+
if (error instanceof CliError) {
|
|
29
|
+
return error;
|
|
30
|
+
}
|
|
31
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
32
|
+
return new CliError(message, fallbackExitCode);
|
|
33
|
+
}
|
|
34
|
+
function formatErrorPayload(error) {
|
|
35
|
+
return {
|
|
36
|
+
success: false,
|
|
37
|
+
error: error.message,
|
|
38
|
+
exitCode: error.exitCode
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function resolveExitCode(result) {
|
|
42
|
+
if (result.exitCode === null) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
if (typeof result.exitCode === "number") {
|
|
46
|
+
return result.exitCode;
|
|
47
|
+
}
|
|
48
|
+
return result.success ? EXIT_SUCCESS : EXIT_EXECUTION;
|
|
49
|
+
}
|
|
6
50
|
|
|
7
51
|
// src/cli/args.ts
|
|
8
52
|
var SHORT_FLAGS = {
|
|
@@ -28,27 +72,99 @@ function parseSkillsMode(args) {
|
|
|
28
72
|
}
|
|
29
73
|
return "global";
|
|
30
74
|
}
|
|
75
|
+
function parseOutputFormat(args) {
|
|
76
|
+
const outputFlag = args.find((arg) => arg.startsWith("--output-format"));
|
|
77
|
+
if (!outputFlag) {
|
|
78
|
+
return "text";
|
|
79
|
+
}
|
|
80
|
+
let value;
|
|
81
|
+
if (outputFlag.includes("=")) {
|
|
82
|
+
value = outputFlag.split("=", 2)[1];
|
|
83
|
+
} else {
|
|
84
|
+
const index = args.indexOf(outputFlag);
|
|
85
|
+
value = index >= 0 ? args[index + 1] : void 0;
|
|
86
|
+
}
|
|
87
|
+
if (value === "text" || value === "json" || value === "stream-json") {
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
throw createUsageError(`Invalid --output-format: ${value ?? "missing"}`);
|
|
91
|
+
}
|
|
31
92
|
function parseArgs(argv) {
|
|
32
|
-
|
|
93
|
+
let args = expandShortFlags(argv.slice(2));
|
|
94
|
+
let commandOverride = null;
|
|
95
|
+
if (args[0] && !args[0].startsWith("-")) {
|
|
96
|
+
const candidate = args[0];
|
|
97
|
+
if (candidate === "install" || candidate === "update" || candidate === "uninstall" || candidate === "help" || candidate === "version" || candidate === "serve" || candidate === "run" || candidate === "launch" || candidate === "connect" || candidate === "disconnect" || candidate === "status" || candidate === "goto" || candidate === "wait" || candidate === "snapshot" || candidate === "click" || candidate === "type" || candidate === "select" || candidate === "scroll") {
|
|
98
|
+
commandOverride = candidate;
|
|
99
|
+
args = args.slice(1);
|
|
100
|
+
} else {
|
|
101
|
+
throw createUsageError(`Unknown command: ${candidate}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
33
104
|
const skillsMode = parseSkillsMode(args);
|
|
34
105
|
const fullInstall = args.includes("--full");
|
|
35
|
-
|
|
36
|
-
|
|
106
|
+
const outputFormat = parseOutputFormat(args);
|
|
107
|
+
if (commandOverride === "help" || args.includes("--help") || args.includes("-h")) {
|
|
108
|
+
return {
|
|
109
|
+
command: "help",
|
|
110
|
+
withConfig: false,
|
|
111
|
+
noPrompt: false,
|
|
112
|
+
noInteractive: false,
|
|
113
|
+
quiet: false,
|
|
114
|
+
outputFormat,
|
|
115
|
+
skillsMode,
|
|
116
|
+
fullInstall,
|
|
117
|
+
rawArgs: args
|
|
118
|
+
};
|
|
37
119
|
}
|
|
38
|
-
if (args.includes("--version") || args.includes("-v")) {
|
|
39
|
-
return {
|
|
120
|
+
if (commandOverride === "version" || args.includes("--version") || args.includes("-v")) {
|
|
121
|
+
return {
|
|
122
|
+
command: "version",
|
|
123
|
+
withConfig: false,
|
|
124
|
+
noPrompt: false,
|
|
125
|
+
noInteractive: false,
|
|
126
|
+
quiet: false,
|
|
127
|
+
outputFormat,
|
|
128
|
+
skillsMode,
|
|
129
|
+
fullInstall,
|
|
130
|
+
rawArgs: args
|
|
131
|
+
};
|
|
40
132
|
}
|
|
41
|
-
if (args.includes("--update")) {
|
|
133
|
+
if (commandOverride === "update" || args.includes("--update")) {
|
|
42
134
|
const mode2 = args.includes("--global") ? "global" : args.includes("--local") ? "local" : void 0;
|
|
43
|
-
return {
|
|
135
|
+
return {
|
|
136
|
+
command: "update",
|
|
137
|
+
mode: mode2,
|
|
138
|
+
withConfig: false,
|
|
139
|
+
noPrompt: false,
|
|
140
|
+
noInteractive: false,
|
|
141
|
+
quiet: false,
|
|
142
|
+
outputFormat,
|
|
143
|
+
skillsMode,
|
|
144
|
+
fullInstall,
|
|
145
|
+
rawArgs: args
|
|
146
|
+
};
|
|
44
147
|
}
|
|
45
|
-
if (args.includes("--uninstall")) {
|
|
148
|
+
if (commandOverride === "uninstall" || args.includes("--uninstall")) {
|
|
46
149
|
const mode2 = args.includes("--global") ? "global" : args.includes("--local") ? "local" : void 0;
|
|
47
|
-
const noPrompt2 = args.includes("--no-prompt");
|
|
48
|
-
return {
|
|
150
|
+
const noPrompt2 = args.includes("--no-prompt") || args.includes("--no-interactive");
|
|
151
|
+
return {
|
|
152
|
+
command: "uninstall",
|
|
153
|
+
mode: mode2,
|
|
154
|
+
withConfig: false,
|
|
155
|
+
noPrompt: noPrompt2,
|
|
156
|
+
noInteractive: noPrompt2,
|
|
157
|
+
quiet: args.includes("--quiet"),
|
|
158
|
+
outputFormat,
|
|
159
|
+
skillsMode,
|
|
160
|
+
fullInstall,
|
|
161
|
+
rawArgs: args
|
|
162
|
+
};
|
|
49
163
|
}
|
|
50
164
|
const withConfig = args.includes("--with-config") || fullInstall;
|
|
51
|
-
const noPrompt = args.includes("--no-prompt");
|
|
165
|
+
const noPrompt = args.includes("--no-prompt") || args.includes("--no-interactive");
|
|
166
|
+
const noInteractive = args.includes("--no-interactive") || noPrompt;
|
|
167
|
+
const quiet = args.includes("--quiet");
|
|
52
168
|
let mode;
|
|
53
169
|
if (args.includes("--global")) {
|
|
54
170
|
mode = "global";
|
|
@@ -66,34 +182,100 @@ function parseArgs(argv) {
|
|
|
66
182
|
"--version",
|
|
67
183
|
"--with-config",
|
|
68
184
|
"--no-prompt",
|
|
185
|
+
"--no-interactive",
|
|
186
|
+
"--quiet",
|
|
187
|
+
"--output-format",
|
|
69
188
|
"--full",
|
|
189
|
+
"--port",
|
|
190
|
+
"--token",
|
|
191
|
+
"--stop",
|
|
192
|
+
"--script",
|
|
193
|
+
"--headless",
|
|
194
|
+
"--profile",
|
|
195
|
+
"--persist-profile",
|
|
196
|
+
"--chrome-path",
|
|
197
|
+
"--start-url",
|
|
198
|
+
"--flag",
|
|
199
|
+
"--session-id",
|
|
200
|
+
"--close-browser",
|
|
201
|
+
"--ws-endpoint",
|
|
202
|
+
"--host",
|
|
203
|
+
"--cdp-port",
|
|
204
|
+
"--url",
|
|
205
|
+
"--wait-until",
|
|
206
|
+
"--timeout-ms",
|
|
207
|
+
"--ref",
|
|
208
|
+
"--state",
|
|
209
|
+
"--until",
|
|
210
|
+
"--mode",
|
|
211
|
+
"--max-chars",
|
|
212
|
+
"--cursor",
|
|
213
|
+
"--text",
|
|
214
|
+
"--clear",
|
|
215
|
+
"--submit",
|
|
216
|
+
"--values",
|
|
217
|
+
"--dy",
|
|
218
|
+
"--no-extension",
|
|
219
|
+
"--extension-only",
|
|
220
|
+
"--wait-for-extension",
|
|
221
|
+
"--wait-timeout-ms",
|
|
70
222
|
"--skills-global",
|
|
71
223
|
"--skills-local",
|
|
72
224
|
"--no-skills"
|
|
73
225
|
]);
|
|
74
226
|
for (const arg of args) {
|
|
75
227
|
if (arg.startsWith("--") && !validFlags.has(arg)) {
|
|
76
|
-
throw
|
|
228
|
+
throw createUsageError(`Unknown flag: ${arg}`);
|
|
77
229
|
}
|
|
78
230
|
if (arg.startsWith("-") && !arg.startsWith("--") && !SHORT_FLAGS[arg]) {
|
|
79
|
-
throw
|
|
231
|
+
throw createUsageError(`Unknown flag: ${arg}`);
|
|
80
232
|
}
|
|
81
233
|
}
|
|
82
|
-
return {
|
|
234
|
+
return {
|
|
235
|
+
command: commandOverride ?? "install",
|
|
236
|
+
mode,
|
|
237
|
+
withConfig,
|
|
238
|
+
noPrompt,
|
|
239
|
+
noInteractive,
|
|
240
|
+
quiet,
|
|
241
|
+
outputFormat,
|
|
242
|
+
skillsMode,
|
|
243
|
+
fullInstall,
|
|
244
|
+
rawArgs: args
|
|
245
|
+
};
|
|
83
246
|
}
|
|
84
247
|
function getHelpText() {
|
|
85
248
|
return `
|
|
86
249
|
OpenDevBrowser CLI - Install and manage the OpenDevBrowser plugin
|
|
87
250
|
|
|
88
251
|
USAGE:
|
|
89
|
-
npx opendevbrowser [options]
|
|
252
|
+
npx opendevbrowser [command] [options]
|
|
90
253
|
|
|
91
254
|
COMMANDS:
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
255
|
+
install Install the plugin (default if no command specified)
|
|
256
|
+
update Clear cached plugin to trigger reinstall
|
|
257
|
+
uninstall Remove plugin from config
|
|
258
|
+
serve Start or stop the local daemon
|
|
259
|
+
run Execute a JSON script in a single process
|
|
260
|
+
launch Launch a managed browser session via daemon
|
|
261
|
+
connect Connect to an existing browser via daemon
|
|
262
|
+
disconnect Disconnect a daemon session
|
|
263
|
+
status Get daemon session status
|
|
264
|
+
goto Navigate current session to a URL
|
|
265
|
+
wait Wait for load or a ref to appear
|
|
266
|
+
snapshot Capture a snapshot of the active page
|
|
267
|
+
click Click an element by ref
|
|
268
|
+
type Type into an element by ref
|
|
269
|
+
select Select values in a select by ref
|
|
270
|
+
scroll Scroll the page or element by ref
|
|
271
|
+
help Show this help message
|
|
272
|
+
version Show version
|
|
273
|
+
|
|
274
|
+
ALIASES:
|
|
275
|
+
--update, -u Same as update
|
|
276
|
+
--uninstall Same as uninstall
|
|
277
|
+
--help, -h Same as help
|
|
278
|
+
--version, -v Same as version
|
|
97
279
|
|
|
98
280
|
INSTALL OPTIONS:
|
|
99
281
|
--global, -g Install to ~/.config/opencode/opencode.json
|
|
@@ -101,6 +283,9 @@ INSTALL OPTIONS:
|
|
|
101
283
|
--with-config Also create opendevbrowser.jsonc with defaults
|
|
102
284
|
--full, -f Create config and pre-extract extension assets
|
|
103
285
|
--no-prompt Skip prompts, use defaults (global install)
|
|
286
|
+
--no-interactive Alias of --no-prompt
|
|
287
|
+
--quiet Suppress non-error output
|
|
288
|
+
--output-format Output format: text (default), json, stream-json
|
|
104
289
|
--skills-global Install bundled skills to ~/.config/opencode/skill (default)
|
|
105
290
|
--skills-local Install bundled skills to ./.opencode/skill
|
|
106
291
|
--no-skills Skip installing bundled skills
|
|
@@ -117,6 +302,23 @@ EXAMPLES:
|
|
|
117
302
|
npx opendevbrowser --uninstall --global # Remove from global config
|
|
118
303
|
`.trim();
|
|
119
304
|
}
|
|
305
|
+
function detectOutputFormat(argv) {
|
|
306
|
+
const args = expandShortFlags(argv.slice(2));
|
|
307
|
+
try {
|
|
308
|
+
return parseOutputFormat(args);
|
|
309
|
+
} catch {
|
|
310
|
+
return "text";
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// src/cli/commands/registry.ts
|
|
315
|
+
var registry = /* @__PURE__ */ new Map();
|
|
316
|
+
function registerCommand(definition) {
|
|
317
|
+
registry.set(definition.name, definition);
|
|
318
|
+
}
|
|
319
|
+
function getCommand(name) {
|
|
320
|
+
return registry.get(name);
|
|
321
|
+
}
|
|
120
322
|
|
|
121
323
|
// src/cli/installers/global.ts
|
|
122
324
|
import * as fs4 from "fs";
|
|
@@ -624,6 +826,1178 @@ function findInstalledConfigs() {
|
|
|
624
826
|
return { global, local };
|
|
625
827
|
}
|
|
626
828
|
|
|
829
|
+
// src/cli/daemon.ts
|
|
830
|
+
import { createServer } from "http";
|
|
831
|
+
import { timingSafeEqual } from "crypto";
|
|
832
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3, existsSync as existsSync8 } from "fs";
|
|
833
|
+
import { homedir as homedir6 } from "os";
|
|
834
|
+
import { join as join8 } from "path";
|
|
835
|
+
|
|
836
|
+
// src/cli/daemon-commands.ts
|
|
837
|
+
async function handleDaemonCommand(core, request) {
|
|
838
|
+
const params = request.params ?? {};
|
|
839
|
+
switch (request.name) {
|
|
840
|
+
case "session.launch":
|
|
841
|
+
return launchWithRelay(core, params);
|
|
842
|
+
case "session.connect":
|
|
843
|
+
return core.manager.connect({
|
|
844
|
+
wsEndpoint: optionalString(params.wsEndpoint),
|
|
845
|
+
host: optionalString(params.host),
|
|
846
|
+
port: optionalNumber(params.port)
|
|
847
|
+
});
|
|
848
|
+
case "session.disconnect":
|
|
849
|
+
await core.manager.disconnect(requireString(params.sessionId, "sessionId"), optionalBoolean(params.closeBrowser) ?? false);
|
|
850
|
+
return { ok: true };
|
|
851
|
+
case "session.status":
|
|
852
|
+
return core.manager.status(requireString(params.sessionId, "sessionId"));
|
|
853
|
+
case "nav.goto":
|
|
854
|
+
return core.manager.goto(
|
|
855
|
+
requireString(params.sessionId, "sessionId"),
|
|
856
|
+
requireString(params.url, "url"),
|
|
857
|
+
requireWaitUntil(params.waitUntil),
|
|
858
|
+
optionalNumber(params.timeoutMs) ?? 3e4
|
|
859
|
+
);
|
|
860
|
+
case "nav.wait":
|
|
861
|
+
if (typeof params.ref === "string") {
|
|
862
|
+
return core.manager.waitForRef(
|
|
863
|
+
requireString(params.sessionId, "sessionId"),
|
|
864
|
+
requireString(params.ref, "ref"),
|
|
865
|
+
requireState(params.state),
|
|
866
|
+
optionalNumber(params.timeoutMs) ?? 3e4
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
return core.manager.waitForLoad(
|
|
870
|
+
requireString(params.sessionId, "sessionId"),
|
|
871
|
+
requireWaitUntil(params.until),
|
|
872
|
+
optionalNumber(params.timeoutMs) ?? 3e4
|
|
873
|
+
);
|
|
874
|
+
case "nav.snapshot":
|
|
875
|
+
return core.manager.snapshot(
|
|
876
|
+
requireString(params.sessionId, "sessionId"),
|
|
877
|
+
requireSnapshotMode(params.mode),
|
|
878
|
+
optionalNumber(params.maxChars) ?? 16e3,
|
|
879
|
+
optionalString(params.cursor)
|
|
880
|
+
);
|
|
881
|
+
case "interact.click":
|
|
882
|
+
return core.manager.click(
|
|
883
|
+
requireString(params.sessionId, "sessionId"),
|
|
884
|
+
requireString(params.ref, "ref")
|
|
885
|
+
);
|
|
886
|
+
case "interact.type":
|
|
887
|
+
return core.manager.type(
|
|
888
|
+
requireString(params.sessionId, "sessionId"),
|
|
889
|
+
requireString(params.ref, "ref"),
|
|
890
|
+
requireString(params.text, "text"),
|
|
891
|
+
optionalBoolean(params.clear) ?? false,
|
|
892
|
+
optionalBoolean(params.submit) ?? false
|
|
893
|
+
);
|
|
894
|
+
case "interact.select":
|
|
895
|
+
return core.manager.select(
|
|
896
|
+
requireString(params.sessionId, "sessionId"),
|
|
897
|
+
requireString(params.ref, "ref"),
|
|
898
|
+
requireStringArray(params.values, "values")
|
|
899
|
+
);
|
|
900
|
+
case "interact.scroll":
|
|
901
|
+
return core.manager.scroll(
|
|
902
|
+
requireString(params.sessionId, "sessionId"),
|
|
903
|
+
optionalNumber(params.dy) ?? 0,
|
|
904
|
+
optionalString(params.ref)
|
|
905
|
+
);
|
|
906
|
+
default:
|
|
907
|
+
throw new Error(`Unknown daemon command: ${request.name}`);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
async function launchWithRelay(core, params) {
|
|
911
|
+
let relayStatus = core.relay.status();
|
|
912
|
+
const relayUrl = core.relay.getCdpUrl();
|
|
913
|
+
const noExtension = optionalBoolean(params.noExtension) ?? false;
|
|
914
|
+
const extensionOnly = optionalBoolean(params.extensionOnly) ?? false;
|
|
915
|
+
const waitForExtension = optionalBoolean(params.waitForExtension) ?? false;
|
|
916
|
+
const waitTimeoutMs = optionalNumber(params.waitTimeoutMs) ?? 3e4;
|
|
917
|
+
if (waitForExtension) {
|
|
918
|
+
const connected = await waitForRelay(core.relay, waitTimeoutMs);
|
|
919
|
+
if (connected) {
|
|
920
|
+
relayStatus = core.relay.status();
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
const useRelay = Boolean(!noExtension && relayStatus.extensionConnected && relayUrl);
|
|
924
|
+
let relayWarning = null;
|
|
925
|
+
if (extensionOnly && !useRelay) {
|
|
926
|
+
throw new Error("Extension not connected; use --no-extension to launch a new browser.");
|
|
927
|
+
}
|
|
928
|
+
if (useRelay && relayUrl) {
|
|
929
|
+
try {
|
|
930
|
+
const result2 = await core.manager.connectRelay(relayUrl);
|
|
931
|
+
return { ...result2, warnings: result2.warnings ?? [] };
|
|
932
|
+
} catch (error) {
|
|
933
|
+
if (extensionOnly) {
|
|
934
|
+
throw error instanceof Error ? error : new Error("Extension relay connection failed.");
|
|
935
|
+
}
|
|
936
|
+
relayWarning = "Relay connection failed; falling back to managed Chrome.";
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
if (relayUrl && !noExtension) {
|
|
940
|
+
relayWarning ??= "Extension not connected; launching managed Chrome instead.";
|
|
941
|
+
}
|
|
942
|
+
const result = await core.manager.launch({
|
|
943
|
+
profile: optionalString(params.profile),
|
|
944
|
+
headless: optionalBoolean(params.headless),
|
|
945
|
+
startUrl: optionalString(params.startUrl),
|
|
946
|
+
chromePath: optionalString(params.chromePath),
|
|
947
|
+
flags: optionalStringArray(params.flags),
|
|
948
|
+
persistProfile: optionalBoolean(params.persistProfile)
|
|
949
|
+
});
|
|
950
|
+
const warnings = [
|
|
951
|
+
...result.warnings ?? [],
|
|
952
|
+
...relayWarning ? [relayWarning] : []
|
|
953
|
+
];
|
|
954
|
+
return { ...result, warnings };
|
|
955
|
+
}
|
|
956
|
+
function requireString(value, label) {
|
|
957
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
958
|
+
throw new Error(`Missing ${label}`);
|
|
959
|
+
}
|
|
960
|
+
return value;
|
|
961
|
+
}
|
|
962
|
+
function requireStringArray(value, label) {
|
|
963
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
964
|
+
throw new Error(`Invalid ${label}`);
|
|
965
|
+
}
|
|
966
|
+
return value;
|
|
967
|
+
}
|
|
968
|
+
function optionalString(value) {
|
|
969
|
+
return typeof value === "string" ? value : void 0;
|
|
970
|
+
}
|
|
971
|
+
function optionalStringArray(value) {
|
|
972
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : void 0;
|
|
973
|
+
}
|
|
974
|
+
function optionalNumber(value) {
|
|
975
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
976
|
+
}
|
|
977
|
+
function optionalBoolean(value) {
|
|
978
|
+
return typeof value === "boolean" ? value : void 0;
|
|
979
|
+
}
|
|
980
|
+
function requireWaitUntil(value) {
|
|
981
|
+
if (value === "domcontentloaded" || value === "load" || value === "networkidle") {
|
|
982
|
+
return value;
|
|
983
|
+
}
|
|
984
|
+
return "load";
|
|
985
|
+
}
|
|
986
|
+
function requireSnapshotMode(value) {
|
|
987
|
+
if (value === "actionables") return "actionables";
|
|
988
|
+
return "outline";
|
|
989
|
+
}
|
|
990
|
+
function requireState(value) {
|
|
991
|
+
if (value === "visible" || value === "hidden") return value;
|
|
992
|
+
return "attached";
|
|
993
|
+
}
|
|
994
|
+
async function waitForRelay(relay, timeoutMs) {
|
|
995
|
+
const start = Date.now();
|
|
996
|
+
while (Date.now() - start < timeoutMs) {
|
|
997
|
+
if (relay.status().extensionConnected) {
|
|
998
|
+
return true;
|
|
999
|
+
}
|
|
1000
|
+
await new Promise((resolve2) => setTimeout(resolve2, 500));
|
|
1001
|
+
}
|
|
1002
|
+
return false;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
// src/cli/daemon.ts
|
|
1006
|
+
var DEFAULT_DAEMON_PORT = 8788;
|
|
1007
|
+
function getCacheRoot() {
|
|
1008
|
+
const base = process.env.OPENCODE_CACHE_DIR ?? process.env.XDG_CACHE_HOME ?? join8(homedir6(), ".cache");
|
|
1009
|
+
return join8(base, "opendevbrowser");
|
|
1010
|
+
}
|
|
1011
|
+
function getDaemonMetadataPath() {
|
|
1012
|
+
return join8(getCacheRoot(), "daemon.json");
|
|
1013
|
+
}
|
|
1014
|
+
function readDaemonMetadata() {
|
|
1015
|
+
const metadataPath = getDaemonMetadataPath();
|
|
1016
|
+
if (!existsSync8(metadataPath)) {
|
|
1017
|
+
return null;
|
|
1018
|
+
}
|
|
1019
|
+
try {
|
|
1020
|
+
const content = readFileSync3(metadataPath, "utf-8");
|
|
1021
|
+
return JSON.parse(content);
|
|
1022
|
+
} catch {
|
|
1023
|
+
return null;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
function writeDaemonMetadata(state) {
|
|
1027
|
+
const metadataPath = getDaemonMetadataPath();
|
|
1028
|
+
mkdirSync4(join8(getCacheRoot()), { recursive: true });
|
|
1029
|
+
writeFileSync5(metadataPath, JSON.stringify(state, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1030
|
+
}
|
|
1031
|
+
function clearDaemonMetadata() {
|
|
1032
|
+
const metadataPath = getDaemonMetadataPath();
|
|
1033
|
+
try {
|
|
1034
|
+
unlinkSync3(metadataPath);
|
|
1035
|
+
} catch {
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
function isAuthorized(request, token) {
|
|
1039
|
+
const header = request.headers.authorization ?? "";
|
|
1040
|
+
if (!header.startsWith("Bearer ")) {
|
|
1041
|
+
return false;
|
|
1042
|
+
}
|
|
1043
|
+
const received = header.slice("Bearer ".length).trim();
|
|
1044
|
+
const expectedBuf = Buffer.from(token, "utf-8");
|
|
1045
|
+
const receivedBuf = Buffer.from(received, "utf-8");
|
|
1046
|
+
if (expectedBuf.length !== receivedBuf.length) {
|
|
1047
|
+
timingSafeEqual(expectedBuf, expectedBuf);
|
|
1048
|
+
return false;
|
|
1049
|
+
}
|
|
1050
|
+
return timingSafeEqual(expectedBuf, receivedBuf);
|
|
1051
|
+
}
|
|
1052
|
+
function sendJson(response, status, payload) {
|
|
1053
|
+
response.writeHead(status, {
|
|
1054
|
+
"Content-Type": "application/json",
|
|
1055
|
+
"Cache-Control": "no-store"
|
|
1056
|
+
});
|
|
1057
|
+
response.end(JSON.stringify(payload));
|
|
1058
|
+
}
|
|
1059
|
+
async function startDaemon(options = {}) {
|
|
1060
|
+
const config = options.config ?? loadGlobalConfig();
|
|
1061
|
+
const port = options.port ?? DEFAULT_DAEMON_PORT;
|
|
1062
|
+
const token = options.token ?? generateSecureToken();
|
|
1063
|
+
const core = createOpenDevBrowserCore({
|
|
1064
|
+
directory: options.directory ?? process.cwd(),
|
|
1065
|
+
worktree: options.worktree ?? null,
|
|
1066
|
+
config
|
|
1067
|
+
});
|
|
1068
|
+
await core.ensureRelay(config.relayPort);
|
|
1069
|
+
const server = createServer(async (request, response) => {
|
|
1070
|
+
if (!isAuthorized(request, token)) {
|
|
1071
|
+
sendJson(response, 401, { error: "Unauthorized" });
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
const url = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
1075
|
+
if (request.method === "GET" && url.pathname === "/status") {
|
|
1076
|
+
sendJson(response, 200, {
|
|
1077
|
+
ok: true,
|
|
1078
|
+
pid: process.pid,
|
|
1079
|
+
relay: core.relay.status()
|
|
1080
|
+
});
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
if (request.method === "POST" && url.pathname === "/stop") {
|
|
1084
|
+
sendJson(response, 200, { ok: true });
|
|
1085
|
+
await stop();
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
if (request.method === "POST" && url.pathname === "/command") {
|
|
1089
|
+
try {
|
|
1090
|
+
const body = await readJson(request);
|
|
1091
|
+
const data = await handleDaemonCommand(core, body);
|
|
1092
|
+
sendJson(response, 200, { ok: true, data });
|
|
1093
|
+
} catch (error) {
|
|
1094
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1095
|
+
sendJson(response, 400, { ok: false, error: message });
|
|
1096
|
+
}
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
sendJson(response, 404, { error: "Not found" });
|
|
1100
|
+
});
|
|
1101
|
+
await new Promise((resolve2, reject) => {
|
|
1102
|
+
server.once("error", reject);
|
|
1103
|
+
server.listen(port, "127.0.0.1", () => resolve2());
|
|
1104
|
+
});
|
|
1105
|
+
const state = {
|
|
1106
|
+
port,
|
|
1107
|
+
token,
|
|
1108
|
+
pid: process.pid,
|
|
1109
|
+
relayPort: config.relayPort,
|
|
1110
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1111
|
+
};
|
|
1112
|
+
writeDaemonMetadata(state);
|
|
1113
|
+
const stop = async () => {
|
|
1114
|
+
clearDaemonMetadata();
|
|
1115
|
+
core.cleanup();
|
|
1116
|
+
await new Promise((resolve2) => {
|
|
1117
|
+
server.close(() => resolve2());
|
|
1118
|
+
});
|
|
1119
|
+
};
|
|
1120
|
+
process.on("SIGINT", () => {
|
|
1121
|
+
stop().catch(() => {
|
|
1122
|
+
});
|
|
1123
|
+
});
|
|
1124
|
+
process.on("SIGTERM", () => {
|
|
1125
|
+
stop().catch(() => {
|
|
1126
|
+
});
|
|
1127
|
+
});
|
|
1128
|
+
return { state, stop };
|
|
1129
|
+
}
|
|
1130
|
+
function readJson(request) {
|
|
1131
|
+
return new Promise((resolve2, reject) => {
|
|
1132
|
+
let data = "";
|
|
1133
|
+
request.setEncoding("utf8");
|
|
1134
|
+
request.on("data", (chunk) => {
|
|
1135
|
+
data += chunk;
|
|
1136
|
+
});
|
|
1137
|
+
request.on("end", () => {
|
|
1138
|
+
try {
|
|
1139
|
+
const parsed = JSON.parse(data || "{}");
|
|
1140
|
+
if (!parsed || typeof parsed !== "object") {
|
|
1141
|
+
reject(new Error("Invalid JSON body"));
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
resolve2(parsed);
|
|
1145
|
+
} catch (error) {
|
|
1146
|
+
reject(error);
|
|
1147
|
+
}
|
|
1148
|
+
});
|
|
1149
|
+
request.on("error", reject);
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
// src/cli/commands/serve.ts
|
|
1154
|
+
function parseServeArgs(rawArgs) {
|
|
1155
|
+
const parsed = { stop: false };
|
|
1156
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
1157
|
+
const arg = rawArgs[i];
|
|
1158
|
+
if (arg === "--stop") {
|
|
1159
|
+
parsed.stop = true;
|
|
1160
|
+
continue;
|
|
1161
|
+
}
|
|
1162
|
+
if (arg === "--port") {
|
|
1163
|
+
const value = rawArgs[i + 1];
|
|
1164
|
+
if (!value) {
|
|
1165
|
+
throw createUsageError("Missing value for --port");
|
|
1166
|
+
}
|
|
1167
|
+
parsed.port = Number(value);
|
|
1168
|
+
i += 1;
|
|
1169
|
+
continue;
|
|
1170
|
+
}
|
|
1171
|
+
if (arg?.startsWith("--port=")) {
|
|
1172
|
+
parsed.port = Number(arg.split("=", 2)[1]);
|
|
1173
|
+
continue;
|
|
1174
|
+
}
|
|
1175
|
+
if (arg === "--token") {
|
|
1176
|
+
const value = rawArgs[i + 1];
|
|
1177
|
+
if (!value) {
|
|
1178
|
+
throw createUsageError("Missing value for --token");
|
|
1179
|
+
}
|
|
1180
|
+
parsed.token = value;
|
|
1181
|
+
i += 1;
|
|
1182
|
+
continue;
|
|
1183
|
+
}
|
|
1184
|
+
if (arg?.startsWith("--token=")) {
|
|
1185
|
+
parsed.token = arg.split("=", 2)[1];
|
|
1186
|
+
continue;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
return parsed;
|
|
1190
|
+
}
|
|
1191
|
+
async function runServe(args) {
|
|
1192
|
+
const serveArgs = parseServeArgs(args.rawArgs);
|
|
1193
|
+
if (serveArgs.stop) {
|
|
1194
|
+
const metadata = readDaemonMetadata();
|
|
1195
|
+
if (!metadata) {
|
|
1196
|
+
return { success: false, message: "Daemon not running.", exitCode: EXIT_DISCONNECTED };
|
|
1197
|
+
}
|
|
1198
|
+
try {
|
|
1199
|
+
const response = await fetch(`http://127.0.0.1:${metadata.port}/stop`, {
|
|
1200
|
+
method: "POST",
|
|
1201
|
+
headers: { Authorization: `Bearer ${metadata.token}` }
|
|
1202
|
+
});
|
|
1203
|
+
if (!response.ok) {
|
|
1204
|
+
throw new Error(`Stop failed (${response.status})`);
|
|
1205
|
+
}
|
|
1206
|
+
return { success: true, message: "Daemon stopped." };
|
|
1207
|
+
} catch (error) {
|
|
1208
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1209
|
+
return { success: false, message: `Failed to stop daemon: ${message}`, exitCode: EXIT_EXECUTION };
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
const { state } = await startDaemon({
|
|
1213
|
+
port: serveArgs.port,
|
|
1214
|
+
token: serveArgs.token
|
|
1215
|
+
});
|
|
1216
|
+
return {
|
|
1217
|
+
success: true,
|
|
1218
|
+
message: `Daemon running on 127.0.0.1:${state.port}`,
|
|
1219
|
+
data: { port: state.port, pid: state.pid, relayPort: state.relayPort },
|
|
1220
|
+
exitCode: null
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
// src/cli/commands/run.ts
|
|
1225
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
1226
|
+
|
|
1227
|
+
// src/cli/output.ts
|
|
1228
|
+
function writeOutput(payload, options) {
|
|
1229
|
+
if (options.quiet) {
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
if (options.format === "text") {
|
|
1233
|
+
if (typeof payload === "string") {
|
|
1234
|
+
console.log(payload);
|
|
1235
|
+
} else {
|
|
1236
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
1237
|
+
}
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
if (options.format === "stream-json") {
|
|
1241
|
+
if (Array.isArray(payload)) {
|
|
1242
|
+
for (const entry of payload) {
|
|
1243
|
+
console.log(JSON.stringify(entry));
|
|
1244
|
+
}
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
console.log(JSON.stringify(payload));
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
// src/cli/commands/run.ts
|
|
1252
|
+
function parseRunArgs(rawArgs) {
|
|
1253
|
+
const parsed = { flags: [] };
|
|
1254
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
1255
|
+
const arg = rawArgs[i];
|
|
1256
|
+
if (arg === "--script") {
|
|
1257
|
+
const value = rawArgs[i + 1];
|
|
1258
|
+
if (!value) throw createUsageError("Missing value for --script");
|
|
1259
|
+
parsed.scriptPath = value;
|
|
1260
|
+
i += 1;
|
|
1261
|
+
continue;
|
|
1262
|
+
}
|
|
1263
|
+
if (arg?.startsWith("--script=")) {
|
|
1264
|
+
parsed.scriptPath = arg.split("=", 2)[1];
|
|
1265
|
+
continue;
|
|
1266
|
+
}
|
|
1267
|
+
if (arg === "--headless") {
|
|
1268
|
+
parsed.headless = true;
|
|
1269
|
+
continue;
|
|
1270
|
+
}
|
|
1271
|
+
if (arg === "--profile") {
|
|
1272
|
+
const value = rawArgs[i + 1];
|
|
1273
|
+
if (!value) throw createUsageError("Missing value for --profile");
|
|
1274
|
+
parsed.profile = value;
|
|
1275
|
+
i += 1;
|
|
1276
|
+
continue;
|
|
1277
|
+
}
|
|
1278
|
+
if (arg?.startsWith("--profile=")) {
|
|
1279
|
+
parsed.profile = arg.split("=", 2)[1];
|
|
1280
|
+
continue;
|
|
1281
|
+
}
|
|
1282
|
+
if (arg === "--persist-profile") {
|
|
1283
|
+
parsed.persistProfile = true;
|
|
1284
|
+
continue;
|
|
1285
|
+
}
|
|
1286
|
+
if (arg === "--chrome-path") {
|
|
1287
|
+
const value = rawArgs[i + 1];
|
|
1288
|
+
if (!value) throw createUsageError("Missing value for --chrome-path");
|
|
1289
|
+
parsed.chromePath = value;
|
|
1290
|
+
i += 1;
|
|
1291
|
+
continue;
|
|
1292
|
+
}
|
|
1293
|
+
if (arg?.startsWith("--chrome-path=")) {
|
|
1294
|
+
parsed.chromePath = arg.split("=", 2)[1];
|
|
1295
|
+
continue;
|
|
1296
|
+
}
|
|
1297
|
+
if (arg === "--start-url") {
|
|
1298
|
+
const value = rawArgs[i + 1];
|
|
1299
|
+
if (!value) throw createUsageError("Missing value for --start-url");
|
|
1300
|
+
parsed.startUrl = value;
|
|
1301
|
+
i += 1;
|
|
1302
|
+
continue;
|
|
1303
|
+
}
|
|
1304
|
+
if (arg?.startsWith("--start-url=")) {
|
|
1305
|
+
parsed.startUrl = arg.split("=", 2)[1];
|
|
1306
|
+
continue;
|
|
1307
|
+
}
|
|
1308
|
+
if (arg === "--flag") {
|
|
1309
|
+
const value = rawArgs[i + 1];
|
|
1310
|
+
if (!value) throw createUsageError("Missing value for --flag");
|
|
1311
|
+
parsed.flags.push(value);
|
|
1312
|
+
i += 1;
|
|
1313
|
+
continue;
|
|
1314
|
+
}
|
|
1315
|
+
if (arg?.startsWith("--flag=")) {
|
|
1316
|
+
parsed.flags.push(arg.split("=", 2)[1]);
|
|
1317
|
+
continue;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
return parsed;
|
|
1321
|
+
}
|
|
1322
|
+
function readScriptFromStdin() {
|
|
1323
|
+
return new Promise((resolve2, reject) => {
|
|
1324
|
+
let data = "";
|
|
1325
|
+
process.stdin.setEncoding("utf8");
|
|
1326
|
+
process.stdin.on("data", (chunk) => {
|
|
1327
|
+
data += chunk;
|
|
1328
|
+
});
|
|
1329
|
+
process.stdin.on("end", () => resolve2(data));
|
|
1330
|
+
process.stdin.on("error", reject);
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
async function runScriptCommand(args) {
|
|
1334
|
+
const runArgs = parseRunArgs(args.rawArgs);
|
|
1335
|
+
const outputOptions = { format: args.outputFormat, quiet: args.quiet };
|
|
1336
|
+
let scriptRaw = "";
|
|
1337
|
+
if (runArgs.scriptPath) {
|
|
1338
|
+
scriptRaw = readFileSync4(runArgs.scriptPath, "utf-8");
|
|
1339
|
+
} else if (!process.stdin.isTTY) {
|
|
1340
|
+
scriptRaw = await readScriptFromStdin();
|
|
1341
|
+
} else {
|
|
1342
|
+
throw createUsageError("Provide --script <path> or pipe JSON to stdin.");
|
|
1343
|
+
}
|
|
1344
|
+
let steps = [];
|
|
1345
|
+
try {
|
|
1346
|
+
const parsed = JSON.parse(scriptRaw);
|
|
1347
|
+
if (Array.isArray(parsed)) {
|
|
1348
|
+
steps = parsed;
|
|
1349
|
+
} else if (parsed && typeof parsed === "object" && Array.isArray(parsed.steps)) {
|
|
1350
|
+
steps = parsed.steps;
|
|
1351
|
+
} else {
|
|
1352
|
+
throw new Error("Script must be a JSON array or an object with steps.");
|
|
1353
|
+
}
|
|
1354
|
+
} catch (error) {
|
|
1355
|
+
const message = error instanceof Error ? error.message : "Invalid JSON script.";
|
|
1356
|
+
writeOutput({ success: false, error: message, exitCode: EXIT_USAGE }, outputOptions);
|
|
1357
|
+
return { success: false, message, exitCode: EXIT_USAGE, data: { suppressOutput: true } };
|
|
1358
|
+
}
|
|
1359
|
+
const core = createOpenDevBrowserCore({ directory: process.cwd() });
|
|
1360
|
+
const launchResult = await core.manager.launch({
|
|
1361
|
+
profile: runArgs.profile,
|
|
1362
|
+
headless: runArgs.headless,
|
|
1363
|
+
startUrl: runArgs.startUrl,
|
|
1364
|
+
chromePath: runArgs.chromePath,
|
|
1365
|
+
flags: runArgs.flags.length ? runArgs.flags : void 0,
|
|
1366
|
+
persistProfile: runArgs.persistProfile
|
|
1367
|
+
});
|
|
1368
|
+
try {
|
|
1369
|
+
const result = await core.runner.run(launchResult.sessionId, steps, true);
|
|
1370
|
+
writeOutput({
|
|
1371
|
+
success: true,
|
|
1372
|
+
sessionId: launchResult.sessionId,
|
|
1373
|
+
warnings: launchResult.warnings.length ? launchResult.warnings : void 0,
|
|
1374
|
+
...result
|
|
1375
|
+
}, outputOptions);
|
|
1376
|
+
return { success: true, data: { suppressOutput: true } };
|
|
1377
|
+
} finally {
|
|
1378
|
+
await core.manager.disconnect(launchResult.sessionId, true);
|
|
1379
|
+
core.cleanup();
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
// src/cli/client.ts
|
|
1384
|
+
async function callDaemon(command, params) {
|
|
1385
|
+
const metadata = readDaemonMetadata();
|
|
1386
|
+
if (!metadata) {
|
|
1387
|
+
throw createDisconnectedError("Daemon not running. Start with `opendevbrowser serve`.");
|
|
1388
|
+
}
|
|
1389
|
+
let response;
|
|
1390
|
+
try {
|
|
1391
|
+
response = await fetch(`http://127.0.0.1:${metadata.port}/command`, {
|
|
1392
|
+
method: "POST",
|
|
1393
|
+
headers: {
|
|
1394
|
+
"Content-Type": "application/json",
|
|
1395
|
+
Authorization: `Bearer ${metadata.token}`
|
|
1396
|
+
},
|
|
1397
|
+
body: JSON.stringify({ name: command, params: params ?? {} })
|
|
1398
|
+
});
|
|
1399
|
+
} catch {
|
|
1400
|
+
throw createDisconnectedError("Daemon not running. Start with `opendevbrowser serve`.");
|
|
1401
|
+
}
|
|
1402
|
+
if (!response.ok) {
|
|
1403
|
+
const message = await response.text();
|
|
1404
|
+
throw new CliError(`Daemon error: ${message || response.status}`, EXIT_EXECUTION);
|
|
1405
|
+
}
|
|
1406
|
+
const payload = await response.json();
|
|
1407
|
+
if (!payload.ok) {
|
|
1408
|
+
throw new CliError(payload.error || "Daemon command failed.", EXIT_EXECUTION);
|
|
1409
|
+
}
|
|
1410
|
+
return payload.data;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
// src/cli/commands/session/launch.ts
|
|
1414
|
+
function parseLaunchArgs(rawArgs) {
|
|
1415
|
+
const parsed = { flags: [] };
|
|
1416
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
1417
|
+
const arg = rawArgs[i];
|
|
1418
|
+
if (arg === "--headless") {
|
|
1419
|
+
parsed.headless = true;
|
|
1420
|
+
continue;
|
|
1421
|
+
}
|
|
1422
|
+
if (arg === "--profile") {
|
|
1423
|
+
const value = rawArgs[i + 1];
|
|
1424
|
+
if (!value) throw createUsageError("Missing value for --profile");
|
|
1425
|
+
parsed.profile = value;
|
|
1426
|
+
i += 1;
|
|
1427
|
+
continue;
|
|
1428
|
+
}
|
|
1429
|
+
if (arg?.startsWith("--profile=")) {
|
|
1430
|
+
parsed.profile = arg.split("=", 2)[1];
|
|
1431
|
+
continue;
|
|
1432
|
+
}
|
|
1433
|
+
if (arg === "--start-url") {
|
|
1434
|
+
const value = rawArgs[i + 1];
|
|
1435
|
+
if (!value) throw createUsageError("Missing value for --start-url");
|
|
1436
|
+
parsed.startUrl = value;
|
|
1437
|
+
i += 1;
|
|
1438
|
+
continue;
|
|
1439
|
+
}
|
|
1440
|
+
if (arg?.startsWith("--start-url=")) {
|
|
1441
|
+
parsed.startUrl = arg.split("=", 2)[1];
|
|
1442
|
+
continue;
|
|
1443
|
+
}
|
|
1444
|
+
if (arg === "--chrome-path") {
|
|
1445
|
+
const value = rawArgs[i + 1];
|
|
1446
|
+
if (!value) throw createUsageError("Missing value for --chrome-path");
|
|
1447
|
+
parsed.chromePath = value;
|
|
1448
|
+
i += 1;
|
|
1449
|
+
continue;
|
|
1450
|
+
}
|
|
1451
|
+
if (arg?.startsWith("--chrome-path=")) {
|
|
1452
|
+
parsed.chromePath = arg.split("=", 2)[1];
|
|
1453
|
+
continue;
|
|
1454
|
+
}
|
|
1455
|
+
if (arg === "--persist-profile") {
|
|
1456
|
+
parsed.persistProfile = true;
|
|
1457
|
+
continue;
|
|
1458
|
+
}
|
|
1459
|
+
if (arg === "--no-extension") {
|
|
1460
|
+
parsed.noExtension = true;
|
|
1461
|
+
continue;
|
|
1462
|
+
}
|
|
1463
|
+
if (arg === "--extension-only") {
|
|
1464
|
+
parsed.extensionOnly = true;
|
|
1465
|
+
continue;
|
|
1466
|
+
}
|
|
1467
|
+
if (arg === "--wait-for-extension") {
|
|
1468
|
+
parsed.waitForExtension = true;
|
|
1469
|
+
continue;
|
|
1470
|
+
}
|
|
1471
|
+
if (arg === "--wait-timeout-ms") {
|
|
1472
|
+
const value = rawArgs[i + 1];
|
|
1473
|
+
if (!value) throw createUsageError("Missing value for --wait-timeout-ms");
|
|
1474
|
+
parsed.waitTimeoutMs = Number(value);
|
|
1475
|
+
i += 1;
|
|
1476
|
+
continue;
|
|
1477
|
+
}
|
|
1478
|
+
if (arg?.startsWith("--wait-timeout-ms=")) {
|
|
1479
|
+
parsed.waitTimeoutMs = Number(arg.split("=", 2)[1]);
|
|
1480
|
+
continue;
|
|
1481
|
+
}
|
|
1482
|
+
if (arg === "--flag") {
|
|
1483
|
+
const value = rawArgs[i + 1];
|
|
1484
|
+
if (!value) throw createUsageError("Missing value for --flag");
|
|
1485
|
+
parsed.flags.push(value);
|
|
1486
|
+
i += 1;
|
|
1487
|
+
continue;
|
|
1488
|
+
}
|
|
1489
|
+
if (arg?.startsWith("--flag=")) {
|
|
1490
|
+
parsed.flags.push(arg.split("=", 2)[1]);
|
|
1491
|
+
continue;
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
return parsed;
|
|
1495
|
+
}
|
|
1496
|
+
async function runSessionLaunch(args) {
|
|
1497
|
+
const launchArgs = parseLaunchArgs(args.rawArgs);
|
|
1498
|
+
const result = await callDaemon("session.launch", launchArgs);
|
|
1499
|
+
return {
|
|
1500
|
+
success: true,
|
|
1501
|
+
message: `Session launched: ${result.sessionId}`,
|
|
1502
|
+
data: result
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
// src/cli/commands/session/connect.ts
|
|
1507
|
+
function parseConnectArgs(rawArgs) {
|
|
1508
|
+
const parsed = {};
|
|
1509
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
1510
|
+
const arg = rawArgs[i];
|
|
1511
|
+
if (arg === "--ws-endpoint") {
|
|
1512
|
+
const value = rawArgs[i + 1];
|
|
1513
|
+
if (!value) throw createUsageError("Missing value for --ws-endpoint");
|
|
1514
|
+
parsed.wsEndpoint = value;
|
|
1515
|
+
i += 1;
|
|
1516
|
+
continue;
|
|
1517
|
+
}
|
|
1518
|
+
if (arg?.startsWith("--ws-endpoint=")) {
|
|
1519
|
+
parsed.wsEndpoint = arg.split("=", 2)[1];
|
|
1520
|
+
continue;
|
|
1521
|
+
}
|
|
1522
|
+
if (arg === "--host") {
|
|
1523
|
+
const value = rawArgs[i + 1];
|
|
1524
|
+
if (!value) throw createUsageError("Missing value for --host");
|
|
1525
|
+
parsed.host = value;
|
|
1526
|
+
i += 1;
|
|
1527
|
+
continue;
|
|
1528
|
+
}
|
|
1529
|
+
if (arg?.startsWith("--host=")) {
|
|
1530
|
+
parsed.host = arg.split("=", 2)[1];
|
|
1531
|
+
continue;
|
|
1532
|
+
}
|
|
1533
|
+
if (arg === "--cdp-port") {
|
|
1534
|
+
const value = rawArgs[i + 1];
|
|
1535
|
+
if (!value) throw createUsageError("Missing value for --cdp-port");
|
|
1536
|
+
parsed.port = Number(value);
|
|
1537
|
+
i += 1;
|
|
1538
|
+
continue;
|
|
1539
|
+
}
|
|
1540
|
+
if (arg?.startsWith("--cdp-port=")) {
|
|
1541
|
+
parsed.port = Number(arg.split("=", 2)[1]);
|
|
1542
|
+
continue;
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
return parsed;
|
|
1546
|
+
}
|
|
1547
|
+
async function runSessionConnect(args) {
|
|
1548
|
+
const connectArgs = parseConnectArgs(args.rawArgs);
|
|
1549
|
+
const result = await callDaemon("session.connect", connectArgs);
|
|
1550
|
+
return {
|
|
1551
|
+
success: true,
|
|
1552
|
+
message: `Session connected: ${result.sessionId}`,
|
|
1553
|
+
data: result
|
|
1554
|
+
};
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
// src/cli/commands/session/disconnect.ts
|
|
1558
|
+
function parseDisconnectArgs(rawArgs) {
|
|
1559
|
+
const parsed = {};
|
|
1560
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
1561
|
+
const arg = rawArgs[i];
|
|
1562
|
+
if (arg === "--session-id") {
|
|
1563
|
+
const value = rawArgs[i + 1];
|
|
1564
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
1565
|
+
parsed.sessionId = value;
|
|
1566
|
+
i += 1;
|
|
1567
|
+
continue;
|
|
1568
|
+
}
|
|
1569
|
+
if (arg?.startsWith("--session-id=")) {
|
|
1570
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
1571
|
+
continue;
|
|
1572
|
+
}
|
|
1573
|
+
if (arg === "--close-browser") {
|
|
1574
|
+
parsed.closeBrowser = true;
|
|
1575
|
+
continue;
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
return parsed;
|
|
1579
|
+
}
|
|
1580
|
+
async function runSessionDisconnect(args) {
|
|
1581
|
+
const { sessionId, closeBrowser } = parseDisconnectArgs(args.rawArgs);
|
|
1582
|
+
if (!sessionId) {
|
|
1583
|
+
throw createUsageError("Missing --session-id");
|
|
1584
|
+
}
|
|
1585
|
+
await callDaemon("session.disconnect", { sessionId, closeBrowser });
|
|
1586
|
+
return { success: true, message: `Session disconnected: ${sessionId}` };
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
// src/cli/commands/session/status.ts
|
|
1590
|
+
function parseStatusArgs(rawArgs) {
|
|
1591
|
+
const parsed = {};
|
|
1592
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
1593
|
+
const arg = rawArgs[i];
|
|
1594
|
+
if (arg === "--session-id") {
|
|
1595
|
+
const value = rawArgs[i + 1];
|
|
1596
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
1597
|
+
parsed.sessionId = value;
|
|
1598
|
+
i += 1;
|
|
1599
|
+
continue;
|
|
1600
|
+
}
|
|
1601
|
+
if (arg?.startsWith("--session-id=")) {
|
|
1602
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
1603
|
+
continue;
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
return parsed;
|
|
1607
|
+
}
|
|
1608
|
+
async function runSessionStatus(args) {
|
|
1609
|
+
const { sessionId } = parseStatusArgs(args.rawArgs);
|
|
1610
|
+
if (!sessionId) {
|
|
1611
|
+
throw createUsageError("Missing --session-id");
|
|
1612
|
+
}
|
|
1613
|
+
const result = await callDaemon("session.status", { sessionId });
|
|
1614
|
+
return { success: true, message: `Session status: ${sessionId}`, data: result };
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
// src/cli/commands/nav/goto.ts
|
|
1618
|
+
function parseGotoArgs(rawArgs) {
|
|
1619
|
+
const parsed = {};
|
|
1620
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
1621
|
+
const arg = rawArgs[i];
|
|
1622
|
+
if (arg === "--session-id") {
|
|
1623
|
+
const value = rawArgs[i + 1];
|
|
1624
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
1625
|
+
parsed.sessionId = value;
|
|
1626
|
+
i += 1;
|
|
1627
|
+
continue;
|
|
1628
|
+
}
|
|
1629
|
+
if (arg?.startsWith("--session-id=")) {
|
|
1630
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
1631
|
+
continue;
|
|
1632
|
+
}
|
|
1633
|
+
if (arg === "--url") {
|
|
1634
|
+
const value = rawArgs[i + 1];
|
|
1635
|
+
if (!value) throw createUsageError("Missing value for --url");
|
|
1636
|
+
parsed.url = value;
|
|
1637
|
+
i += 1;
|
|
1638
|
+
continue;
|
|
1639
|
+
}
|
|
1640
|
+
if (arg?.startsWith("--url=")) {
|
|
1641
|
+
parsed.url = arg.split("=", 2)[1];
|
|
1642
|
+
continue;
|
|
1643
|
+
}
|
|
1644
|
+
if (arg === "--wait-until") {
|
|
1645
|
+
const value = rawArgs[i + 1];
|
|
1646
|
+
if (!value) throw createUsageError("Missing value for --wait-until");
|
|
1647
|
+
parsed.waitUntil = value;
|
|
1648
|
+
i += 1;
|
|
1649
|
+
continue;
|
|
1650
|
+
}
|
|
1651
|
+
if (arg?.startsWith("--wait-until=")) {
|
|
1652
|
+
parsed.waitUntil = arg.split("=", 2)[1];
|
|
1653
|
+
continue;
|
|
1654
|
+
}
|
|
1655
|
+
if (arg === "--timeout-ms") {
|
|
1656
|
+
const value = rawArgs[i + 1];
|
|
1657
|
+
if (!value) throw createUsageError("Missing value for --timeout-ms");
|
|
1658
|
+
parsed.timeoutMs = Number(value);
|
|
1659
|
+
i += 1;
|
|
1660
|
+
continue;
|
|
1661
|
+
}
|
|
1662
|
+
if (arg?.startsWith("--timeout-ms=")) {
|
|
1663
|
+
parsed.timeoutMs = Number(arg.split("=", 2)[1]);
|
|
1664
|
+
continue;
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
return parsed;
|
|
1668
|
+
}
|
|
1669
|
+
async function runGoto(args) {
|
|
1670
|
+
const { sessionId, url, waitUntil, timeoutMs } = parseGotoArgs(args.rawArgs);
|
|
1671
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
1672
|
+
if (!url) throw createUsageError("Missing --url");
|
|
1673
|
+
const result = await callDaemon("nav.goto", { sessionId, url, waitUntil, timeoutMs });
|
|
1674
|
+
return { success: true, message: `Navigated: ${url}`, data: result };
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
// src/cli/commands/nav/wait.ts
|
|
1678
|
+
function parseWaitArgs(rawArgs) {
|
|
1679
|
+
const parsed = {};
|
|
1680
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
1681
|
+
const arg = rawArgs[i];
|
|
1682
|
+
if (arg === "--session-id") {
|
|
1683
|
+
const value = rawArgs[i + 1];
|
|
1684
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
1685
|
+
parsed.sessionId = value;
|
|
1686
|
+
i += 1;
|
|
1687
|
+
continue;
|
|
1688
|
+
}
|
|
1689
|
+
if (arg?.startsWith("--session-id=")) {
|
|
1690
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
1691
|
+
continue;
|
|
1692
|
+
}
|
|
1693
|
+
if (arg === "--ref") {
|
|
1694
|
+
const value = rawArgs[i + 1];
|
|
1695
|
+
if (!value) throw createUsageError("Missing value for --ref");
|
|
1696
|
+
parsed.ref = value;
|
|
1697
|
+
i += 1;
|
|
1698
|
+
continue;
|
|
1699
|
+
}
|
|
1700
|
+
if (arg?.startsWith("--ref=")) {
|
|
1701
|
+
parsed.ref = arg.split("=", 2)[1];
|
|
1702
|
+
continue;
|
|
1703
|
+
}
|
|
1704
|
+
if (arg === "--state") {
|
|
1705
|
+
const value = rawArgs[i + 1];
|
|
1706
|
+
if (!value) throw createUsageError("Missing value for --state");
|
|
1707
|
+
parsed.state = value;
|
|
1708
|
+
i += 1;
|
|
1709
|
+
continue;
|
|
1710
|
+
}
|
|
1711
|
+
if (arg?.startsWith("--state=")) {
|
|
1712
|
+
parsed.state = arg.split("=", 2)[1];
|
|
1713
|
+
continue;
|
|
1714
|
+
}
|
|
1715
|
+
if (arg === "--until") {
|
|
1716
|
+
const value = rawArgs[i + 1];
|
|
1717
|
+
if (!value) throw createUsageError("Missing value for --until");
|
|
1718
|
+
parsed.until = value;
|
|
1719
|
+
i += 1;
|
|
1720
|
+
continue;
|
|
1721
|
+
}
|
|
1722
|
+
if (arg?.startsWith("--until=")) {
|
|
1723
|
+
parsed.until = arg.split("=", 2)[1];
|
|
1724
|
+
continue;
|
|
1725
|
+
}
|
|
1726
|
+
if (arg === "--timeout-ms") {
|
|
1727
|
+
const value = rawArgs[i + 1];
|
|
1728
|
+
if (!value) throw createUsageError("Missing value for --timeout-ms");
|
|
1729
|
+
parsed.timeoutMs = Number(value);
|
|
1730
|
+
i += 1;
|
|
1731
|
+
continue;
|
|
1732
|
+
}
|
|
1733
|
+
if (arg?.startsWith("--timeout-ms=")) {
|
|
1734
|
+
parsed.timeoutMs = Number(arg.split("=", 2)[1]);
|
|
1735
|
+
continue;
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
return parsed;
|
|
1739
|
+
}
|
|
1740
|
+
async function runWait(args) {
|
|
1741
|
+
const { sessionId, ref, state, until, timeoutMs } = parseWaitArgs(args.rawArgs);
|
|
1742
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
1743
|
+
const result = await callDaemon("nav.wait", { sessionId, ref, state, until, timeoutMs });
|
|
1744
|
+
return { success: true, message: "Wait complete.", data: result };
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
// src/cli/commands/nav/snapshot.ts
|
|
1748
|
+
function parseSnapshotArgs(rawArgs) {
|
|
1749
|
+
const parsed = {};
|
|
1750
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
1751
|
+
const arg = rawArgs[i];
|
|
1752
|
+
if (arg === "--session-id") {
|
|
1753
|
+
const value = rawArgs[i + 1];
|
|
1754
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
1755
|
+
parsed.sessionId = value;
|
|
1756
|
+
i += 1;
|
|
1757
|
+
continue;
|
|
1758
|
+
}
|
|
1759
|
+
if (arg?.startsWith("--session-id=")) {
|
|
1760
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
1761
|
+
continue;
|
|
1762
|
+
}
|
|
1763
|
+
if (arg === "--mode") {
|
|
1764
|
+
const value = rawArgs[i + 1];
|
|
1765
|
+
if (!value) throw createUsageError("Missing value for --mode");
|
|
1766
|
+
parsed.mode = value;
|
|
1767
|
+
i += 1;
|
|
1768
|
+
continue;
|
|
1769
|
+
}
|
|
1770
|
+
if (arg?.startsWith("--mode=")) {
|
|
1771
|
+
parsed.mode = arg.split("=", 2)[1];
|
|
1772
|
+
continue;
|
|
1773
|
+
}
|
|
1774
|
+
if (arg === "--max-chars") {
|
|
1775
|
+
const value = rawArgs[i + 1];
|
|
1776
|
+
if (!value) throw createUsageError("Missing value for --max-chars");
|
|
1777
|
+
parsed.maxChars = Number(value);
|
|
1778
|
+
i += 1;
|
|
1779
|
+
continue;
|
|
1780
|
+
}
|
|
1781
|
+
if (arg?.startsWith("--max-chars=")) {
|
|
1782
|
+
parsed.maxChars = Number(arg.split("=", 2)[1]);
|
|
1783
|
+
continue;
|
|
1784
|
+
}
|
|
1785
|
+
if (arg === "--cursor") {
|
|
1786
|
+
const value = rawArgs[i + 1];
|
|
1787
|
+
if (!value) throw createUsageError("Missing value for --cursor");
|
|
1788
|
+
parsed.cursor = value;
|
|
1789
|
+
i += 1;
|
|
1790
|
+
continue;
|
|
1791
|
+
}
|
|
1792
|
+
if (arg?.startsWith("--cursor=")) {
|
|
1793
|
+
parsed.cursor = arg.split("=", 2)[1];
|
|
1794
|
+
continue;
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
return parsed;
|
|
1798
|
+
}
|
|
1799
|
+
async function runSnapshot(args) {
|
|
1800
|
+
const { sessionId, mode, maxChars, cursor } = parseSnapshotArgs(args.rawArgs);
|
|
1801
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
1802
|
+
const result = await callDaemon("nav.snapshot", { sessionId, mode, maxChars, cursor });
|
|
1803
|
+
return { success: true, message: "Snapshot captured.", data: result };
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
// src/cli/commands/interact/click.ts
|
|
1807
|
+
function parseClickArgs(rawArgs) {
|
|
1808
|
+
const parsed = {};
|
|
1809
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
1810
|
+
const arg = rawArgs[i];
|
|
1811
|
+
if (arg === "--session-id") {
|
|
1812
|
+
const value = rawArgs[i + 1];
|
|
1813
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
1814
|
+
parsed.sessionId = value;
|
|
1815
|
+
i += 1;
|
|
1816
|
+
continue;
|
|
1817
|
+
}
|
|
1818
|
+
if (arg?.startsWith("--session-id=")) {
|
|
1819
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
1820
|
+
continue;
|
|
1821
|
+
}
|
|
1822
|
+
if (arg === "--ref") {
|
|
1823
|
+
const value = rawArgs[i + 1];
|
|
1824
|
+
if (!value) throw createUsageError("Missing value for --ref");
|
|
1825
|
+
parsed.ref = value;
|
|
1826
|
+
i += 1;
|
|
1827
|
+
continue;
|
|
1828
|
+
}
|
|
1829
|
+
if (arg?.startsWith("--ref=")) {
|
|
1830
|
+
parsed.ref = arg.split("=", 2)[1];
|
|
1831
|
+
continue;
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
return parsed;
|
|
1835
|
+
}
|
|
1836
|
+
async function runClick(args) {
|
|
1837
|
+
const { sessionId, ref } = parseClickArgs(args.rawArgs);
|
|
1838
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
1839
|
+
if (!ref) throw createUsageError("Missing --ref");
|
|
1840
|
+
const result = await callDaemon("interact.click", { sessionId, ref });
|
|
1841
|
+
return { success: true, message: "Click complete.", data: result };
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
// src/cli/commands/interact/type.ts
|
|
1845
|
+
function parseTypeArgs(rawArgs) {
|
|
1846
|
+
const parsed = {};
|
|
1847
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
1848
|
+
const arg = rawArgs[i];
|
|
1849
|
+
if (arg === "--session-id") {
|
|
1850
|
+
const value = rawArgs[i + 1];
|
|
1851
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
1852
|
+
parsed.sessionId = value;
|
|
1853
|
+
i += 1;
|
|
1854
|
+
continue;
|
|
1855
|
+
}
|
|
1856
|
+
if (arg?.startsWith("--session-id=")) {
|
|
1857
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
1858
|
+
continue;
|
|
1859
|
+
}
|
|
1860
|
+
if (arg === "--ref") {
|
|
1861
|
+
const value = rawArgs[i + 1];
|
|
1862
|
+
if (!value) throw createUsageError("Missing value for --ref");
|
|
1863
|
+
parsed.ref = value;
|
|
1864
|
+
i += 1;
|
|
1865
|
+
continue;
|
|
1866
|
+
}
|
|
1867
|
+
if (arg?.startsWith("--ref=")) {
|
|
1868
|
+
parsed.ref = arg.split("=", 2)[1];
|
|
1869
|
+
continue;
|
|
1870
|
+
}
|
|
1871
|
+
if (arg === "--text") {
|
|
1872
|
+
const value = rawArgs[i + 1];
|
|
1873
|
+
if (!value) throw createUsageError("Missing value for --text");
|
|
1874
|
+
parsed.text = value;
|
|
1875
|
+
i += 1;
|
|
1876
|
+
continue;
|
|
1877
|
+
}
|
|
1878
|
+
if (arg?.startsWith("--text=")) {
|
|
1879
|
+
parsed.text = arg.split("=", 2)[1];
|
|
1880
|
+
continue;
|
|
1881
|
+
}
|
|
1882
|
+
if (arg === "--clear") {
|
|
1883
|
+
parsed.clear = true;
|
|
1884
|
+
continue;
|
|
1885
|
+
}
|
|
1886
|
+
if (arg === "--submit") {
|
|
1887
|
+
parsed.submit = true;
|
|
1888
|
+
continue;
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
return parsed;
|
|
1892
|
+
}
|
|
1893
|
+
async function runType(args) {
|
|
1894
|
+
const { sessionId, ref, text, clear, submit } = parseTypeArgs(args.rawArgs);
|
|
1895
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
1896
|
+
if (!ref) throw createUsageError("Missing --ref");
|
|
1897
|
+
if (!text) throw createUsageError("Missing --text");
|
|
1898
|
+
const result = await callDaemon("interact.type", { sessionId, ref, text, clear, submit });
|
|
1899
|
+
return { success: true, message: "Type complete.", data: result };
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
// src/cli/commands/interact/select.ts
|
|
1903
|
+
function parseSelectArgs(rawArgs) {
|
|
1904
|
+
const parsed = {};
|
|
1905
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
1906
|
+
const arg = rawArgs[i];
|
|
1907
|
+
if (arg === "--session-id") {
|
|
1908
|
+
const value = rawArgs[i + 1];
|
|
1909
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
1910
|
+
parsed.sessionId = value;
|
|
1911
|
+
i += 1;
|
|
1912
|
+
continue;
|
|
1913
|
+
}
|
|
1914
|
+
if (arg?.startsWith("--session-id=")) {
|
|
1915
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
1916
|
+
continue;
|
|
1917
|
+
}
|
|
1918
|
+
if (arg === "--ref") {
|
|
1919
|
+
const value = rawArgs[i + 1];
|
|
1920
|
+
if (!value) throw createUsageError("Missing value for --ref");
|
|
1921
|
+
parsed.ref = value;
|
|
1922
|
+
i += 1;
|
|
1923
|
+
continue;
|
|
1924
|
+
}
|
|
1925
|
+
if (arg?.startsWith("--ref=")) {
|
|
1926
|
+
parsed.ref = arg.split("=", 2)[1];
|
|
1927
|
+
continue;
|
|
1928
|
+
}
|
|
1929
|
+
if (arg === "--values") {
|
|
1930
|
+
const value = rawArgs[i + 1];
|
|
1931
|
+
if (!value) throw createUsageError("Missing value for --values");
|
|
1932
|
+
parsed.values = value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
1933
|
+
i += 1;
|
|
1934
|
+
continue;
|
|
1935
|
+
}
|
|
1936
|
+
if (arg?.startsWith("--values=")) {
|
|
1937
|
+
parsed.values = arg.split("=", 2)[1].split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
1938
|
+
continue;
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
return parsed;
|
|
1942
|
+
}
|
|
1943
|
+
async function runSelect(args) {
|
|
1944
|
+
const { sessionId, ref, values } = parseSelectArgs(args.rawArgs);
|
|
1945
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
1946
|
+
if (!ref) throw createUsageError("Missing --ref");
|
|
1947
|
+
if (!values || values.length === 0) throw createUsageError("Missing --values");
|
|
1948
|
+
const result = await callDaemon("interact.select", { sessionId, ref, values });
|
|
1949
|
+
return { success: true, message: "Select complete.", data: result };
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
// src/cli/commands/interact/scroll.ts
|
|
1953
|
+
function parseScrollArgs(rawArgs) {
|
|
1954
|
+
const parsed = {};
|
|
1955
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
1956
|
+
const arg = rawArgs[i];
|
|
1957
|
+
if (arg === "--session-id") {
|
|
1958
|
+
const value = rawArgs[i + 1];
|
|
1959
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
1960
|
+
parsed.sessionId = value;
|
|
1961
|
+
i += 1;
|
|
1962
|
+
continue;
|
|
1963
|
+
}
|
|
1964
|
+
if (arg?.startsWith("--session-id=")) {
|
|
1965
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
1966
|
+
continue;
|
|
1967
|
+
}
|
|
1968
|
+
if (arg === "--ref") {
|
|
1969
|
+
const value = rawArgs[i + 1];
|
|
1970
|
+
if (!value) throw createUsageError("Missing value for --ref");
|
|
1971
|
+
parsed.ref = value;
|
|
1972
|
+
i += 1;
|
|
1973
|
+
continue;
|
|
1974
|
+
}
|
|
1975
|
+
if (arg?.startsWith("--ref=")) {
|
|
1976
|
+
parsed.ref = arg.split("=", 2)[1];
|
|
1977
|
+
continue;
|
|
1978
|
+
}
|
|
1979
|
+
if (arg === "--dy") {
|
|
1980
|
+
const value = rawArgs[i + 1];
|
|
1981
|
+
if (!value) throw createUsageError("Missing value for --dy");
|
|
1982
|
+
parsed.dy = Number(value);
|
|
1983
|
+
i += 1;
|
|
1984
|
+
continue;
|
|
1985
|
+
}
|
|
1986
|
+
if (arg?.startsWith("--dy=")) {
|
|
1987
|
+
parsed.dy = Number(arg.split("=", 2)[1]);
|
|
1988
|
+
continue;
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
return parsed;
|
|
1992
|
+
}
|
|
1993
|
+
async function runScroll(args) {
|
|
1994
|
+
const { sessionId, ref, dy } = parseScrollArgs(args.rawArgs);
|
|
1995
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
1996
|
+
if (typeof dy !== "number" || Number.isNaN(dy)) throw createUsageError("Missing --dy");
|
|
1997
|
+
const result = await callDaemon("interact.scroll", { sessionId, ref, dy });
|
|
1998
|
+
return { success: true, message: "Scroll complete.", data: result };
|
|
1999
|
+
}
|
|
2000
|
+
|
|
627
2001
|
// src/cli/index.ts
|
|
628
2002
|
var VERSION = "0.1.0";
|
|
629
2003
|
async function promptInstallMode() {
|
|
@@ -709,94 +2083,238 @@ async function promptUninstallMode() {
|
|
|
709
2083
|
});
|
|
710
2084
|
});
|
|
711
2085
|
}
|
|
2086
|
+
function emitFatalError(error, outputFormat) {
|
|
2087
|
+
if (outputFormat === "text") {
|
|
2088
|
+
console.error(`Error: ${error.message}`);
|
|
2089
|
+
if (error.exitCode === EXIT_USAGE) {
|
|
2090
|
+
console.error("\nFor help: npx opendevbrowser --help");
|
|
2091
|
+
}
|
|
2092
|
+
return;
|
|
2093
|
+
}
|
|
2094
|
+
writeOutput(formatErrorPayload(error), { format: outputFormat });
|
|
2095
|
+
}
|
|
712
2096
|
async function main() {
|
|
2097
|
+
let outputFormat = null;
|
|
2098
|
+
let parseSucceeded = false;
|
|
713
2099
|
try {
|
|
714
2100
|
const args = parseArgs(process.argv);
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
case "update": {
|
|
725
|
-
const result = runUpdate();
|
|
726
|
-
console.log(result.message);
|
|
727
|
-
process.exit(result.success ? 0 : 1);
|
|
728
|
-
break;
|
|
2101
|
+
parseSucceeded = true;
|
|
2102
|
+
outputFormat = args.outputFormat;
|
|
2103
|
+
const outputOptions = { format: args.outputFormat, quiet: args.quiet };
|
|
2104
|
+
const emitResult = (result2, payload) => {
|
|
2105
|
+
const suppressOutput = Boolean(
|
|
2106
|
+
result2.data && typeof result2.data === "object" && "suppressOutput" in result2.data && result2.data.suppressOutput
|
|
2107
|
+
);
|
|
2108
|
+
if (suppressOutput) {
|
|
2109
|
+
return;
|
|
729
2110
|
}
|
|
730
|
-
|
|
2111
|
+
if (args.outputFormat === "text") {
|
|
2112
|
+
if (result2.message) {
|
|
2113
|
+
writeOutput(result2.message, outputOptions);
|
|
2114
|
+
}
|
|
2115
|
+
} else {
|
|
2116
|
+
const exitCode2 = resolveExitCode(result2);
|
|
2117
|
+
writeOutput({
|
|
2118
|
+
success: result2.success,
|
|
2119
|
+
message: result2.message,
|
|
2120
|
+
...result2.success || !result2.message ? {} : { error: result2.message },
|
|
2121
|
+
...result2.success || exitCode2 === null ? {} : { exitCode: exitCode2 },
|
|
2122
|
+
...payload
|
|
2123
|
+
}, outputOptions);
|
|
2124
|
+
}
|
|
2125
|
+
};
|
|
2126
|
+
registerCommand({
|
|
2127
|
+
name: "help",
|
|
2128
|
+
description: "Show help",
|
|
2129
|
+
run: () => ({ success: true, message: getHelpText() })
|
|
2130
|
+
});
|
|
2131
|
+
registerCommand({
|
|
2132
|
+
name: "version",
|
|
2133
|
+
description: "Show version",
|
|
2134
|
+
run: () => ({ success: true, message: `opendevbrowser v${VERSION}` })
|
|
2135
|
+
});
|
|
2136
|
+
registerCommand({
|
|
2137
|
+
name: "update",
|
|
2138
|
+
description: "Clear cached plugin to trigger reinstall",
|
|
2139
|
+
run: () => {
|
|
2140
|
+
const result2 = runUpdate();
|
|
2141
|
+
return { success: result2.success, message: result2.message };
|
|
2142
|
+
}
|
|
2143
|
+
});
|
|
2144
|
+
registerCommand({
|
|
2145
|
+
name: "uninstall",
|
|
2146
|
+
description: "Remove plugin from config",
|
|
2147
|
+
run: async () => {
|
|
731
2148
|
let mode = args.mode;
|
|
732
2149
|
if (!mode && !args.noPrompt) {
|
|
733
2150
|
mode = await promptUninstallMode() ?? void 0;
|
|
734
2151
|
if (!mode) {
|
|
735
|
-
|
|
736
|
-
process.exit(0);
|
|
2152
|
+
return { success: true, message: "Uninstall cancelled." };
|
|
737
2153
|
}
|
|
738
2154
|
}
|
|
739
2155
|
if (!mode) {
|
|
740
|
-
|
|
741
|
-
process.exit(1);
|
|
2156
|
+
return { success: false, message: "Error: Please specify --global or --local for uninstall.", exitCode: EXIT_USAGE };
|
|
742
2157
|
}
|
|
743
|
-
const
|
|
744
|
-
|
|
745
|
-
process.exit(result.success ? 0 : 1);
|
|
746
|
-
break;
|
|
2158
|
+
const result2 = runUninstall(mode);
|
|
2159
|
+
return { success: result2.success, message: result2.message };
|
|
747
2160
|
}
|
|
748
|
-
|
|
749
|
-
|
|
2161
|
+
});
|
|
2162
|
+
registerCommand({
|
|
2163
|
+
name: "install",
|
|
2164
|
+
description: "Install the plugin",
|
|
2165
|
+
run: async () => {
|
|
2166
|
+
const log = (...values) => {
|
|
2167
|
+
if (args.quiet) return;
|
|
2168
|
+
console.log(...values);
|
|
2169
|
+
};
|
|
2170
|
+
const warn = (...values) => {
|
|
2171
|
+
if (args.quiet) return;
|
|
2172
|
+
console.warn(...values);
|
|
2173
|
+
};
|
|
750
2174
|
let mode = args.mode;
|
|
751
2175
|
if (!mode) {
|
|
752
2176
|
mode = await promptInstallMode();
|
|
753
2177
|
}
|
|
754
|
-
const
|
|
755
|
-
|
|
2178
|
+
const result2 = mode === "global" ? installGlobal(args.withConfig) : installLocal(args.withConfig);
|
|
2179
|
+
if (args.outputFormat !== "text") {
|
|
2180
|
+
const payload = {
|
|
2181
|
+
alreadyInstalled: result2.alreadyInstalled
|
|
2182
|
+
};
|
|
2183
|
+
if (result2.success && args.skillsMode !== "none") {
|
|
2184
|
+
const skillsResult = installSkills(args.skillsMode);
|
|
2185
|
+
payload.skills = skillsResult;
|
|
2186
|
+
}
|
|
2187
|
+
if (args.fullInstall && result2.success) {
|
|
2188
|
+
try {
|
|
2189
|
+
const extensionPath = extractExtension();
|
|
2190
|
+
payload.extensionPath = extensionPath;
|
|
2191
|
+
} catch (error) {
|
|
2192
|
+
payload.extensionError = error instanceof Error ? error.message : String(error);
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
return { success: result2.success, message: result2.message, data: payload };
|
|
2196
|
+
}
|
|
2197
|
+
log(result2.message);
|
|
756
2198
|
if (args.skillsMode === "none") {
|
|
757
|
-
|
|
758
|
-
} else if (
|
|
2199
|
+
log("Skill installation skipped (--no-skills).");
|
|
2200
|
+
} else if (result2.success) {
|
|
759
2201
|
const skillsResult = installSkills(args.skillsMode);
|
|
760
2202
|
if (skillsResult.success) {
|
|
761
|
-
|
|
2203
|
+
log(skillsResult.message);
|
|
762
2204
|
} else {
|
|
763
|
-
|
|
2205
|
+
warn(skillsResult.message);
|
|
764
2206
|
}
|
|
765
2207
|
} else {
|
|
766
|
-
|
|
2208
|
+
warn("Skill installation skipped because plugin install failed.");
|
|
767
2209
|
}
|
|
768
|
-
if (args.fullInstall &&
|
|
2210
|
+
if (args.fullInstall && result2.success) {
|
|
769
2211
|
try {
|
|
770
2212
|
const extensionPath = extractExtension();
|
|
771
2213
|
if (extensionPath) {
|
|
772
|
-
|
|
2214
|
+
log(`Extension assets extracted to ${extensionPath}`);
|
|
773
2215
|
} else {
|
|
774
|
-
|
|
2216
|
+
warn("Extension assets not found; skipping extraction.");
|
|
775
2217
|
}
|
|
776
2218
|
} catch (error) {
|
|
777
2219
|
const message = error instanceof Error ? error.message : String(error);
|
|
778
|
-
|
|
2220
|
+
warn(`Extension pre-extraction failed: ${message}`);
|
|
779
2221
|
}
|
|
780
2222
|
}
|
|
781
|
-
if (
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
2223
|
+
if (result2.success && !result2.alreadyInstalled) {
|
|
2224
|
+
log("\nNext steps:");
|
|
2225
|
+
log(" 1. Start or restart OpenCode");
|
|
2226
|
+
log(" 2. Use opendevbrowser_status to verify the plugin is loaded");
|
|
2227
|
+
log("\nFor help: npx opendevbrowser --help");
|
|
786
2228
|
}
|
|
787
|
-
|
|
788
|
-
break;
|
|
2229
|
+
return { success: result2.success, message: result2.message };
|
|
789
2230
|
}
|
|
2231
|
+
});
|
|
2232
|
+
registerCommand({
|
|
2233
|
+
name: "serve",
|
|
2234
|
+
description: "Start or stop the local daemon",
|
|
2235
|
+
run: async () => runServe(args)
|
|
2236
|
+
});
|
|
2237
|
+
registerCommand({
|
|
2238
|
+
name: "run",
|
|
2239
|
+
description: "Execute a JSON script in a single process",
|
|
2240
|
+
run: async () => runScriptCommand(args)
|
|
2241
|
+
});
|
|
2242
|
+
registerCommand({
|
|
2243
|
+
name: "launch",
|
|
2244
|
+
description: "Launch a managed browser session via daemon",
|
|
2245
|
+
run: async () => runSessionLaunch(args)
|
|
2246
|
+
});
|
|
2247
|
+
registerCommand({
|
|
2248
|
+
name: "connect",
|
|
2249
|
+
description: "Connect to an existing browser via daemon",
|
|
2250
|
+
run: async () => runSessionConnect(args)
|
|
2251
|
+
});
|
|
2252
|
+
registerCommand({
|
|
2253
|
+
name: "disconnect",
|
|
2254
|
+
description: "Disconnect a daemon session",
|
|
2255
|
+
run: async () => runSessionDisconnect(args)
|
|
2256
|
+
});
|
|
2257
|
+
registerCommand({
|
|
2258
|
+
name: "status",
|
|
2259
|
+
description: "Get daemon session status",
|
|
2260
|
+
run: async () => runSessionStatus(args)
|
|
2261
|
+
});
|
|
2262
|
+
registerCommand({
|
|
2263
|
+
name: "goto",
|
|
2264
|
+
description: "Navigate current session to a URL",
|
|
2265
|
+
run: async () => runGoto(args)
|
|
2266
|
+
});
|
|
2267
|
+
registerCommand({
|
|
2268
|
+
name: "wait",
|
|
2269
|
+
description: "Wait for load or a ref to appear",
|
|
2270
|
+
run: async () => runWait(args)
|
|
2271
|
+
});
|
|
2272
|
+
registerCommand({
|
|
2273
|
+
name: "snapshot",
|
|
2274
|
+
description: "Capture a snapshot of the active page",
|
|
2275
|
+
run: async () => runSnapshot(args)
|
|
2276
|
+
});
|
|
2277
|
+
registerCommand({
|
|
2278
|
+
name: "click",
|
|
2279
|
+
description: "Click an element by ref",
|
|
2280
|
+
run: async () => runClick(args)
|
|
2281
|
+
});
|
|
2282
|
+
registerCommand({
|
|
2283
|
+
name: "type",
|
|
2284
|
+
description: "Type into an element by ref",
|
|
2285
|
+
run: async () => runType(args)
|
|
2286
|
+
});
|
|
2287
|
+
registerCommand({
|
|
2288
|
+
name: "select",
|
|
2289
|
+
description: "Select values in a select by ref",
|
|
2290
|
+
run: async () => runSelect(args)
|
|
2291
|
+
});
|
|
2292
|
+
registerCommand({
|
|
2293
|
+
name: "scroll",
|
|
2294
|
+
description: "Scroll the page or element by ref",
|
|
2295
|
+
run: async () => runScroll(args)
|
|
2296
|
+
});
|
|
2297
|
+
const command = getCommand(args.command);
|
|
2298
|
+
if (!command) {
|
|
2299
|
+
throw new Error(`Unknown command: ${args.command}`);
|
|
790
2300
|
}
|
|
2301
|
+
const result = await command.run(args);
|
|
2302
|
+
emitResult(result, result.data ? { data: result.data } : void 0);
|
|
2303
|
+
const exitCode = resolveExitCode(result);
|
|
2304
|
+
if (exitCode === null) {
|
|
2305
|
+
return;
|
|
2306
|
+
}
|
|
2307
|
+
process.exit(exitCode);
|
|
791
2308
|
} catch (error) {
|
|
792
|
-
const
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
process.exit(
|
|
2309
|
+
const format = outputFormat ?? detectOutputFormat(process.argv);
|
|
2310
|
+
const cliError = toCliError(error, parseSucceeded ? EXIT_EXECUTION : EXIT_USAGE);
|
|
2311
|
+
emitFatalError(cliError, format);
|
|
2312
|
+
process.exit(cliError.exitCode);
|
|
796
2313
|
}
|
|
797
2314
|
}
|
|
798
2315
|
main().catch((error) => {
|
|
799
|
-
|
|
800
|
-
process.
|
|
2316
|
+
const cliError = toCliError(error, EXIT_EXECUTION);
|
|
2317
|
+
emitFatalError(cliError, detectOutputFormat(process.argv));
|
|
2318
|
+
process.exit(cliError.exitCode);
|
|
801
2319
|
});
|
|
802
2320
|
//# sourceMappingURL=index.js.map
|