opendevbrowser 0.0.11 → 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/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
- } from "../chunk-R5VUZEUU.js";
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
- const args = expandShortFlags(argv.slice(2));
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
- if (args.includes("--help") || args.includes("-h")) {
36
- return { command: "help", withConfig: false, noPrompt: false, skillsMode, fullInstall };
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 { command: "version", withConfig: false, noPrompt: false, skillsMode, fullInstall };
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 { command: "update", mode: mode2, withConfig: false, noPrompt: false, skillsMode, fullInstall };
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 { command: "uninstall", mode: mode2, withConfig: false, noPrompt: noPrompt2, skillsMode, fullInstall };
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 new Error(`Unknown flag: ${arg}`);
228
+ throw createUsageError(`Unknown flag: ${arg}`);
77
229
  }
78
230
  if (arg.startsWith("-") && !arg.startsWith("--") && !SHORT_FLAGS[arg]) {
79
- throw new Error(`Unknown flag: ${arg}`);
231
+ throw createUsageError(`Unknown flag: ${arg}`);
80
232
  }
81
233
  }
82
- return { command: "install", mode, withConfig, noPrompt, skillsMode, fullInstall };
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
- (default) Install the plugin (interactive if no mode specified)
93
- --update, -u Clear cached plugin to trigger reinstall
94
- --uninstall Remove plugin from config
95
- --help, -h Show this help message
96
- --version, -v Show version
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
- switch (args.command) {
716
- case "help":
717
- console.log(getHelpText());
718
- process.exit(0);
719
- break;
720
- case "version":
721
- console.log(`opendevbrowser v${VERSION}`);
722
- process.exit(0);
723
- break;
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
- case "uninstall": {
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
- console.log("Uninstall cancelled.");
736
- process.exit(0);
2152
+ return { success: true, message: "Uninstall cancelled." };
737
2153
  }
738
2154
  }
739
2155
  if (!mode) {
740
- console.error("Error: Please specify --global or --local for uninstall.");
741
- process.exit(1);
2156
+ return { success: false, message: "Error: Please specify --global or --local for uninstall.", exitCode: EXIT_USAGE };
742
2157
  }
743
- const result = runUninstall(mode);
744
- console.log(result.message);
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
- case "install":
749
- default: {
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 result = mode === "global" ? installGlobal(args.withConfig) : installLocal(args.withConfig);
755
- console.log(result.message);
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
- console.log("Skill installation skipped (--no-skills).");
758
- } else if (result.success) {
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
- console.log(skillsResult.message);
2203
+ log(skillsResult.message);
762
2204
  } else {
763
- console.warn(skillsResult.message);
2205
+ warn(skillsResult.message);
764
2206
  }
765
2207
  } else {
766
- console.warn("Skill installation skipped because plugin install failed.");
2208
+ warn("Skill installation skipped because plugin install failed.");
767
2209
  }
768
- if (args.fullInstall && result.success) {
2210
+ if (args.fullInstall && result2.success) {
769
2211
  try {
770
2212
  const extensionPath = extractExtension();
771
2213
  if (extensionPath) {
772
- console.log(`Extension assets extracted to ${extensionPath}`);
2214
+ log(`Extension assets extracted to ${extensionPath}`);
773
2215
  } else {
774
- console.warn("Extension assets not found; skipping extraction.");
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
- console.warn(`Extension pre-extraction failed: ${message}`);
2220
+ warn(`Extension pre-extraction failed: ${message}`);
779
2221
  }
780
2222
  }
781
- if (result.success && !result.alreadyInstalled) {
782
- console.log("\nNext steps:");
783
- console.log(" 1. Start or restart OpenCode");
784
- console.log(" 2. Use opendevbrowser_status to verify the plugin is loaded");
785
- console.log("\nFor help: npx opendevbrowser --help");
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
- process.exit(result.success ? 0 : 1);
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 message = error instanceof Error ? error.message : String(error);
793
- console.error(`Error: ${message}`);
794
- console.error("\nFor help: npx opendevbrowser --help");
795
- process.exit(1);
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
- console.error("Unexpected error:", error);
800
- process.exit(1);
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