opendevbrowser 0.0.11 → 0.0.15
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/LICENSE +21 -0
- package/README.md +289 -28
- package/dist/chunk-JVBMT2O5.js +7173 -0
- package/dist/chunk-JVBMT2O5.js.map +1 -0
- package/dist/cli/index.js +3690 -275
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +1080 -2857
- package/dist/index.js.map +1 -1
- package/dist/opendevbrowser.js +1080 -2857
- package/dist/opendevbrowser.js.map +1 -1
- package/extension/dist/annotate-content.css +237 -0
- package/extension/dist/annotate-content.js +934 -0
- package/extension/dist/background.js +1291 -8
- package/extension/dist/logging.js +50 -0
- package/extension/dist/ops/dom-bridge.js +355 -0
- package/extension/dist/ops/ops-runtime.js +1249 -0
- package/extension/dist/ops/ops-session-store.js +189 -0
- package/extension/dist/ops/redaction.js +52 -0
- package/extension/dist/ops/snapshot-builder.js +4 -0
- package/extension/dist/ops/snapshot-shared.js +220 -0
- package/extension/dist/popup.js +398 -21
- package/extension/dist/relay-settings.js +3 -1
- package/extension/dist/services/CDPRouter.js +501 -103
- package/extension/dist/services/ConnectionManager.js +464 -57
- package/extension/dist/services/NativePortManager.js +182 -0
- package/extension/dist/services/RelayClient.js +227 -26
- package/extension/dist/services/TabManager.js +81 -0
- package/extension/dist/services/TargetSessionMap.js +146 -0
- package/extension/dist/services/cdp-router-commands.js +203 -0
- package/extension/dist/services/url-restrictions.js +41 -0
- package/extension/dist/types.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 +17 -3
- package/extension/popup.html +469 -65
- package/package.json +2 -2
- package/skills/AGENTS.md +34 -61
- package/skills/data-extraction/SKILL.md +95 -103
- package/skills/form-testing/SKILL.md +75 -82
- package/skills/login-automation/SKILL.md +76 -66
- package/skills/opendevbrowser-best-practices/SKILL.md +90 -49
- package/skills/opendevbrowser-continuity-ledger/SKILL.md +57 -23
- package/dist/chunk-R5VUZEUU.js +0 -128
- package/dist/chunk-R5VUZEUU.js.map +0 -1
- package/extension/dist/popup.jsx +0 -150
package/dist/cli/index.js
CHANGED
|
@@ -1,8 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
DaemonClient,
|
|
4
|
+
EXIT_DISCONNECTED,
|
|
5
|
+
EXIT_EXECUTION,
|
|
6
|
+
EXIT_USAGE,
|
|
7
|
+
buildAnnotateResult,
|
|
8
|
+
callDaemon,
|
|
9
|
+
createOpenDevBrowserCore,
|
|
10
|
+
createUsageError,
|
|
3
11
|
extractExtension,
|
|
4
|
-
|
|
5
|
-
|
|
12
|
+
fetchDaemonStatusFromMetadata,
|
|
13
|
+
fetchWithTimeout,
|
|
14
|
+
formatErrorPayload,
|
|
15
|
+
generateSecureToken,
|
|
16
|
+
getExtensionPath,
|
|
17
|
+
loadGlobalConfig,
|
|
18
|
+
readDaemonMetadata,
|
|
19
|
+
resolveExitCode,
|
|
20
|
+
startDaemon,
|
|
21
|
+
toCliError,
|
|
22
|
+
writeFileAtomic
|
|
23
|
+
} from "../chunk-JVBMT2O5.js";
|
|
6
24
|
|
|
7
25
|
// src/cli/args.ts
|
|
8
26
|
var SHORT_FLAGS = {
|
|
@@ -17,38 +35,142 @@ function expandShortFlags(args) {
|
|
|
17
35
|
return args.map((arg) => SHORT_FLAGS[arg] ?? arg);
|
|
18
36
|
}
|
|
19
37
|
function parseSkillsMode(args) {
|
|
38
|
+
const hasLocal = args.includes("--skills-local");
|
|
39
|
+
const hasGlobal = args.includes("--skills-global");
|
|
40
|
+
if (hasLocal && hasGlobal) {
|
|
41
|
+
throw createUsageError("Choose either --skills-local or --skills-global.");
|
|
42
|
+
}
|
|
20
43
|
if (args.includes("--no-skills")) {
|
|
21
44
|
return "none";
|
|
22
45
|
}
|
|
23
|
-
if (
|
|
46
|
+
if (hasLocal) {
|
|
24
47
|
return "local";
|
|
25
48
|
}
|
|
26
|
-
if (
|
|
49
|
+
if (hasGlobal) {
|
|
27
50
|
return "global";
|
|
28
51
|
}
|
|
29
52
|
return "global";
|
|
30
53
|
}
|
|
54
|
+
function parseOutputFormat(args) {
|
|
55
|
+
const outputFlag = args.find((arg) => arg.startsWith("--output-format"));
|
|
56
|
+
if (!outputFlag) {
|
|
57
|
+
return "text";
|
|
58
|
+
}
|
|
59
|
+
let value;
|
|
60
|
+
if (outputFlag.includes("=")) {
|
|
61
|
+
value = outputFlag.split("=", 2)[1];
|
|
62
|
+
} else {
|
|
63
|
+
const index = args.indexOf(outputFlag);
|
|
64
|
+
value = index >= 0 ? args[index + 1] : void 0;
|
|
65
|
+
}
|
|
66
|
+
if (value === "text" || value === "json" || value === "stream-json") {
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
throw createUsageError(`Invalid --output-format: ${value ?? "missing"}`);
|
|
70
|
+
}
|
|
71
|
+
function parseTransport(args) {
|
|
72
|
+
const transportFlag = args.find((arg) => arg.startsWith("--transport"));
|
|
73
|
+
if (!transportFlag) {
|
|
74
|
+
return "relay";
|
|
75
|
+
}
|
|
76
|
+
let value;
|
|
77
|
+
if (transportFlag.includes("=")) {
|
|
78
|
+
value = transportFlag.split("=", 2)[1];
|
|
79
|
+
} else {
|
|
80
|
+
const index = args.indexOf(transportFlag);
|
|
81
|
+
value = index >= 0 ? args[index + 1] : void 0;
|
|
82
|
+
}
|
|
83
|
+
if (value === "relay" || value === "native") {
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
throw createUsageError(`Invalid --transport: ${value ?? "missing"}`);
|
|
87
|
+
}
|
|
31
88
|
function parseArgs(argv) {
|
|
32
|
-
|
|
89
|
+
let args = expandShortFlags(argv.slice(2));
|
|
90
|
+
let commandOverride = null;
|
|
91
|
+
if (args[0] && !args[0].startsWith("-")) {
|
|
92
|
+
const candidate = args[0];
|
|
93
|
+
if (candidate === "install" || candidate === "update" || candidate === "uninstall" || candidate === "help" || candidate === "version" || candidate === "serve" || candidate === "daemon" || candidate === "native" || candidate === "run" || candidate === "launch" || candidate === "connect" || candidate === "disconnect" || candidate === "status" || candidate === "goto" || candidate === "wait" || candidate === "snapshot" || candidate === "click" || candidate === "hover" || candidate === "press" || candidate === "check" || candidate === "uncheck" || candidate === "type" || candidate === "select" || candidate === "scroll" || candidate === "scroll-into-view" || candidate === "targets-list" || candidate === "target-use" || candidate === "target-new" || candidate === "target-close" || candidate === "page" || candidate === "pages" || candidate === "page-close" || candidate === "dom-html" || candidate === "dom-text" || candidate === "dom-attr" || candidate === "dom-value" || candidate === "dom-visible" || candidate === "dom-enabled" || candidate === "dom-checked" || candidate === "clone-page" || candidate === "clone-component" || candidate === "perf" || candidate === "screenshot" || candidate === "console-poll" || candidate === "network-poll" || candidate === "annotate") {
|
|
94
|
+
commandOverride = candidate;
|
|
95
|
+
args = args.slice(1);
|
|
96
|
+
} else {
|
|
97
|
+
throw createUsageError(`Unknown command: ${candidate}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const hasGlobal = args.includes("--global");
|
|
101
|
+
const hasLocal = args.includes("--local");
|
|
102
|
+
if (hasGlobal && hasLocal) {
|
|
103
|
+
throw createUsageError("Choose either --global or --local.");
|
|
104
|
+
}
|
|
33
105
|
const skillsMode = parseSkillsMode(args);
|
|
34
106
|
const fullInstall = args.includes("--full");
|
|
35
|
-
|
|
36
|
-
|
|
107
|
+
const outputFormat = parseOutputFormat(args);
|
|
108
|
+
const transport = commandOverride === "annotate" ? "relay" : parseTransport(args);
|
|
109
|
+
if (commandOverride === "help" || args.includes("--help") || args.includes("-h")) {
|
|
110
|
+
return {
|
|
111
|
+
command: "help",
|
|
112
|
+
withConfig: false,
|
|
113
|
+
noPrompt: false,
|
|
114
|
+
noInteractive: false,
|
|
115
|
+
quiet: false,
|
|
116
|
+
outputFormat,
|
|
117
|
+
transport,
|
|
118
|
+
skillsMode,
|
|
119
|
+
fullInstall,
|
|
120
|
+
rawArgs: args
|
|
121
|
+
};
|
|
37
122
|
}
|
|
38
|
-
if (args.includes("--version") || args.includes("-v")) {
|
|
39
|
-
return {
|
|
123
|
+
if (commandOverride === "version" || args.includes("--version") || args.includes("-v")) {
|
|
124
|
+
return {
|
|
125
|
+
command: "version",
|
|
126
|
+
withConfig: false,
|
|
127
|
+
noPrompt: false,
|
|
128
|
+
noInteractive: false,
|
|
129
|
+
quiet: false,
|
|
130
|
+
outputFormat,
|
|
131
|
+
transport,
|
|
132
|
+
skillsMode,
|
|
133
|
+
fullInstall,
|
|
134
|
+
rawArgs: args
|
|
135
|
+
};
|
|
40
136
|
}
|
|
41
|
-
if (args.includes("--update")) {
|
|
137
|
+
if (commandOverride === "update" || args.includes("--update")) {
|
|
42
138
|
const mode2 = args.includes("--global") ? "global" : args.includes("--local") ? "local" : void 0;
|
|
43
|
-
return {
|
|
139
|
+
return {
|
|
140
|
+
command: "update",
|
|
141
|
+
mode: mode2,
|
|
142
|
+
withConfig: false,
|
|
143
|
+
noPrompt: false,
|
|
144
|
+
noInteractive: false,
|
|
145
|
+
quiet: false,
|
|
146
|
+
outputFormat,
|
|
147
|
+
transport,
|
|
148
|
+
skillsMode,
|
|
149
|
+
fullInstall,
|
|
150
|
+
rawArgs: args
|
|
151
|
+
};
|
|
44
152
|
}
|
|
45
|
-
if (args.includes("--uninstall")) {
|
|
153
|
+
if (commandOverride === "uninstall" || args.includes("--uninstall")) {
|
|
46
154
|
const mode2 = args.includes("--global") ? "global" : args.includes("--local") ? "local" : void 0;
|
|
47
|
-
const noPrompt2 = args.includes("--no-prompt");
|
|
48
|
-
return {
|
|
155
|
+
const noPrompt2 = args.includes("--no-prompt") || args.includes("--no-interactive");
|
|
156
|
+
return {
|
|
157
|
+
command: "uninstall",
|
|
158
|
+
mode: mode2,
|
|
159
|
+
withConfig: false,
|
|
160
|
+
noPrompt: noPrompt2,
|
|
161
|
+
noInteractive: noPrompt2,
|
|
162
|
+
quiet: args.includes("--quiet"),
|
|
163
|
+
outputFormat,
|
|
164
|
+
transport,
|
|
165
|
+
skillsMode,
|
|
166
|
+
fullInstall,
|
|
167
|
+
rawArgs: args
|
|
168
|
+
};
|
|
49
169
|
}
|
|
50
170
|
const withConfig = args.includes("--with-config") || fullInstall;
|
|
51
|
-
const noPrompt = args.includes("--no-prompt");
|
|
171
|
+
const noPrompt = args.includes("--no-prompt") || args.includes("--no-interactive");
|
|
172
|
+
const noInteractive = args.includes("--no-interactive") || noPrompt;
|
|
173
|
+
const quiet = args.includes("--quiet");
|
|
52
174
|
let mode;
|
|
53
175
|
if (args.includes("--global")) {
|
|
54
176
|
mode = "global";
|
|
@@ -66,34 +188,161 @@ function parseArgs(argv) {
|
|
|
66
188
|
"--version",
|
|
67
189
|
"--with-config",
|
|
68
190
|
"--no-prompt",
|
|
191
|
+
"--no-interactive",
|
|
192
|
+
"--quiet",
|
|
193
|
+
"--output-format",
|
|
69
194
|
"--full",
|
|
195
|
+
"--port",
|
|
196
|
+
"--token",
|
|
197
|
+
"--stop",
|
|
198
|
+
"--script",
|
|
199
|
+
"--headless",
|
|
200
|
+
"--profile",
|
|
201
|
+
"--persist-profile",
|
|
202
|
+
"--chrome-path",
|
|
203
|
+
"--start-url",
|
|
204
|
+
"--flag",
|
|
205
|
+
"--session-id",
|
|
206
|
+
"--close-browser",
|
|
207
|
+
"--ws-endpoint",
|
|
208
|
+
"--host",
|
|
209
|
+
"--cdp-port",
|
|
210
|
+
"--url",
|
|
211
|
+
"--wait-until",
|
|
212
|
+
"--timeout-ms",
|
|
213
|
+
"--ref",
|
|
214
|
+
"--state",
|
|
215
|
+
"--until",
|
|
216
|
+
"--mode",
|
|
217
|
+
"--max-chars",
|
|
218
|
+
"--cursor",
|
|
219
|
+
"--text",
|
|
220
|
+
"--clear",
|
|
221
|
+
"--submit",
|
|
222
|
+
"--values",
|
|
223
|
+
"--dy",
|
|
224
|
+
"--key",
|
|
225
|
+
"--attr",
|
|
226
|
+
"--name",
|
|
227
|
+
"--target-id",
|
|
228
|
+
"--tab-id",
|
|
229
|
+
"--include-urls",
|
|
230
|
+
"--path",
|
|
231
|
+
"--since-seq",
|
|
232
|
+
"--max",
|
|
233
|
+
"--daemon",
|
|
234
|
+
"--transport",
|
|
235
|
+
"--no-extension",
|
|
236
|
+
"--extension-only",
|
|
237
|
+
"--extension-legacy",
|
|
238
|
+
"--wait-for-extension",
|
|
239
|
+
"--wait-timeout-ms",
|
|
70
240
|
"--skills-global",
|
|
71
241
|
"--skills-local",
|
|
72
|
-
"--no-skills"
|
|
242
|
+
"--no-skills",
|
|
243
|
+
"--screenshot-mode",
|
|
244
|
+
"--debug",
|
|
245
|
+
"--context"
|
|
246
|
+
]);
|
|
247
|
+
const validEqualsFlags = /* @__PURE__ */ new Set([
|
|
248
|
+
"--output-format",
|
|
249
|
+
"--transport",
|
|
250
|
+
"--session-id",
|
|
251
|
+
"--url",
|
|
252
|
+
"--screenshot-mode",
|
|
253
|
+
"--context",
|
|
254
|
+
"--timeout-ms",
|
|
255
|
+
"--target-id",
|
|
256
|
+
"--tab-id"
|
|
73
257
|
]);
|
|
74
258
|
for (const arg of args) {
|
|
75
259
|
if (arg.startsWith("--") && !validFlags.has(arg)) {
|
|
76
|
-
|
|
260
|
+
if (arg.includes("=")) {
|
|
261
|
+
const baseFlag = arg.split("=", 2)[0] ?? "";
|
|
262
|
+
if (validEqualsFlags.has(baseFlag)) {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
throw createUsageError(`Unknown flag: ${arg}`);
|
|
77
267
|
}
|
|
78
268
|
if (arg.startsWith("-") && !arg.startsWith("--") && !SHORT_FLAGS[arg]) {
|
|
79
|
-
throw
|
|
269
|
+
throw createUsageError(`Unknown flag: ${arg}`);
|
|
80
270
|
}
|
|
81
271
|
}
|
|
82
|
-
return {
|
|
272
|
+
return {
|
|
273
|
+
command: commandOverride ?? "install",
|
|
274
|
+
mode,
|
|
275
|
+
withConfig,
|
|
276
|
+
noPrompt,
|
|
277
|
+
noInteractive,
|
|
278
|
+
quiet,
|
|
279
|
+
outputFormat,
|
|
280
|
+
transport,
|
|
281
|
+
skillsMode,
|
|
282
|
+
fullInstall,
|
|
283
|
+
rawArgs: args
|
|
284
|
+
};
|
|
83
285
|
}
|
|
84
286
|
function getHelpText() {
|
|
85
287
|
return `
|
|
86
288
|
OpenDevBrowser CLI - Install and manage the OpenDevBrowser plugin
|
|
87
289
|
|
|
88
290
|
USAGE:
|
|
89
|
-
npx opendevbrowser [options]
|
|
291
|
+
npx opendevbrowser [command] [options]
|
|
90
292
|
|
|
91
293
|
COMMANDS:
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
294
|
+
install Install the plugin (default if no command specified)
|
|
295
|
+
update Clear cached plugin to trigger reinstall
|
|
296
|
+
uninstall Remove plugin from config
|
|
297
|
+
serve Start or stop the local daemon
|
|
298
|
+
daemon Install/uninstall/status daemon auto-start
|
|
299
|
+
native Install/uninstall/status native messaging host
|
|
300
|
+
run Execute a JSON script in a single process
|
|
301
|
+
launch Launch a managed browser session via daemon
|
|
302
|
+
connect Connect to an existing browser via daemon
|
|
303
|
+
disconnect Disconnect a daemon session
|
|
304
|
+
status Get daemon status (or session status with --session-id)
|
|
305
|
+
goto Navigate current session to a URL
|
|
306
|
+
wait Wait for load or a ref to appear
|
|
307
|
+
snapshot Capture a snapshot of the active page
|
|
308
|
+
click Click an element by ref
|
|
309
|
+
hover Hover an element by ref
|
|
310
|
+
press Press a keyboard key
|
|
311
|
+
check Check a checkbox by ref
|
|
312
|
+
uncheck Uncheck a checkbox by ref
|
|
313
|
+
type Type into an element by ref
|
|
314
|
+
select Select values in a select by ref
|
|
315
|
+
scroll Scroll the page or element by ref
|
|
316
|
+
scroll-into-view Scroll an element into view by ref
|
|
317
|
+
targets-list List page targets
|
|
318
|
+
target-use Focus a target by id
|
|
319
|
+
target-new Open a new target
|
|
320
|
+
target-close Close a target by id
|
|
321
|
+
page Open or focus a named page
|
|
322
|
+
pages List named pages
|
|
323
|
+
page-close Close a named page
|
|
324
|
+
dom-html Capture HTML for a ref
|
|
325
|
+
dom-text Capture text for a ref
|
|
326
|
+
dom-attr Capture attribute value for a ref
|
|
327
|
+
dom-value Capture input value for a ref
|
|
328
|
+
dom-visible Check visibility for a ref
|
|
329
|
+
dom-enabled Check enabled state for a ref
|
|
330
|
+
dom-checked Check checked state for a ref
|
|
331
|
+
clone-page Clone the active page to React
|
|
332
|
+
clone-component Clone a component by ref
|
|
333
|
+
perf Capture performance metrics
|
|
334
|
+
screenshot Capture a screenshot
|
|
335
|
+
console-poll Poll console events
|
|
336
|
+
network-poll Poll network events
|
|
337
|
+
annotate Request interactive annotations (direct or relay)
|
|
338
|
+
help Show this help message
|
|
339
|
+
version Show version
|
|
340
|
+
|
|
341
|
+
ALIASES:
|
|
342
|
+
--update, -u Same as update
|
|
343
|
+
--uninstall Same as uninstall
|
|
344
|
+
--help, -h Same as help
|
|
345
|
+
--version, -v Same as version
|
|
97
346
|
|
|
98
347
|
INSTALL OPTIONS:
|
|
99
348
|
--global, -g Install to ~/.config/opencode/opencode.json
|
|
@@ -101,6 +350,10 @@ INSTALL OPTIONS:
|
|
|
101
350
|
--with-config Also create opendevbrowser.jsonc with defaults
|
|
102
351
|
--full, -f Create config and pre-extract extension assets
|
|
103
352
|
--no-prompt Skip prompts, use defaults (global install)
|
|
353
|
+
--no-interactive Alias of --no-prompt
|
|
354
|
+
--quiet Suppress non-error output
|
|
355
|
+
--output-format Output format: text (default), json, stream-json
|
|
356
|
+
--transport Transport: relay (default) or native
|
|
104
357
|
--skills-global Install bundled skills to ~/.config/opencode/skill (default)
|
|
105
358
|
--skills-local Install bundled skills to ./.opencode/skill
|
|
106
359
|
--no-skills Skip installing bundled skills
|
|
@@ -115,68 +368,54 @@ EXAMPLES:
|
|
|
115
368
|
npx opendevbrowser --no-skills # Skip skill installation
|
|
116
369
|
npx opendevbrowser --update # Update plugin
|
|
117
370
|
npx opendevbrowser --uninstall --global # Remove from global config
|
|
371
|
+
npx opendevbrowser native install <extension-id> # Install native host
|
|
118
372
|
`.trim();
|
|
119
373
|
}
|
|
374
|
+
function detectOutputFormat(argv) {
|
|
375
|
+
const args = expandShortFlags(argv.slice(2));
|
|
376
|
+
try {
|
|
377
|
+
return parseOutputFormat(args);
|
|
378
|
+
} catch {
|
|
379
|
+
return "text";
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// src/cli/commands/registry.ts
|
|
384
|
+
var registry = /* @__PURE__ */ new Map();
|
|
385
|
+
function registerCommand(definition) {
|
|
386
|
+
registry.set(definition.name, definition);
|
|
387
|
+
}
|
|
388
|
+
function getCommand(name) {
|
|
389
|
+
return registry.get(name);
|
|
390
|
+
}
|
|
120
391
|
|
|
121
392
|
// src/cli/installers/global.ts
|
|
122
|
-
import * as
|
|
393
|
+
import * as fs3 from "fs";
|
|
123
394
|
|
|
124
395
|
// src/cli/utils/config.ts
|
|
125
|
-
import * as fs2 from "fs";
|
|
126
|
-
import * as path2 from "path";
|
|
127
|
-
import * as os from "os";
|
|
128
|
-
import { parse as parseJsonc, modify, applyEdits } from "jsonc-parser";
|
|
129
|
-
|
|
130
|
-
// src/utils/fs.ts
|
|
131
396
|
import * as fs from "fs";
|
|
132
397
|
import * as path from "path";
|
|
133
|
-
import * as
|
|
134
|
-
|
|
135
|
-
const { encoding = "utf-8", mode } = options;
|
|
136
|
-
const dir = path.dirname(filePath);
|
|
137
|
-
const hash = crypto.randomBytes(8).toString("hex");
|
|
138
|
-
const tempPath = path.join(dir, `.${path.basename(filePath)}.${process.pid}.${hash}.tmp`);
|
|
139
|
-
try {
|
|
140
|
-
if (!fs.existsSync(dir)) {
|
|
141
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
142
|
-
}
|
|
143
|
-
const writeOptions = { encoding };
|
|
144
|
-
if (mode !== void 0) {
|
|
145
|
-
writeOptions.mode = mode;
|
|
146
|
-
}
|
|
147
|
-
fs.writeFileSync(tempPath, content, writeOptions);
|
|
148
|
-
fs.renameSync(tempPath, filePath);
|
|
149
|
-
} catch (error) {
|
|
150
|
-
try {
|
|
151
|
-
if (fs.existsSync(tempPath)) {
|
|
152
|
-
fs.unlinkSync(tempPath);
|
|
153
|
-
}
|
|
154
|
-
} catch {
|
|
155
|
-
}
|
|
156
|
-
throw error;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// src/cli/utils/config.ts
|
|
398
|
+
import * as os from "os";
|
|
399
|
+
import { parse as parseJsonc, modify, applyEdits } from "jsonc-parser";
|
|
161
400
|
var PLUGIN_NAME = "opendevbrowser";
|
|
162
401
|
var SCHEMA_URL = "https://opencode.ai/config.json";
|
|
163
402
|
function getGlobalConfigPath() {
|
|
164
|
-
const configDir = process.env.OPENCODE_CONFIG_DIR ||
|
|
165
|
-
return
|
|
403
|
+
const configDir = process.env.OPENCODE_CONFIG_DIR || path.join(os.homedir(), ".config", "opencode");
|
|
404
|
+
return path.join(configDir, "opencode.json");
|
|
166
405
|
}
|
|
167
406
|
function getLocalConfigPath() {
|
|
168
|
-
return
|
|
407
|
+
return path.join(process.cwd(), "opencode.json");
|
|
169
408
|
}
|
|
170
409
|
function ensureDir(dirPath) {
|
|
171
|
-
if (!
|
|
172
|
-
|
|
410
|
+
if (!fs.existsSync(dirPath)) {
|
|
411
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
173
412
|
}
|
|
174
413
|
}
|
|
175
414
|
function readConfig(configPath) {
|
|
176
|
-
if (!
|
|
415
|
+
if (!fs.existsSync(configPath)) {
|
|
177
416
|
return { content: "", config: {} };
|
|
178
417
|
}
|
|
179
|
-
const content =
|
|
418
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
180
419
|
const errors = [];
|
|
181
420
|
const parsed = parseJsonc(content, errors, { allowTrailingComma: true });
|
|
182
421
|
if (errors.length > 0) {
|
|
@@ -228,10 +467,10 @@ function removePluginFromContent(content, pluginName = PLUGIN_NAME) {
|
|
|
228
467
|
}
|
|
229
468
|
|
|
230
469
|
// src/cli/templates/config.ts
|
|
231
|
-
import * as
|
|
232
|
-
import * as
|
|
470
|
+
import * as fs2 from "fs";
|
|
471
|
+
import * as path2 from "path";
|
|
233
472
|
import * as os2 from "os";
|
|
234
|
-
function buildConfigTemplate(
|
|
473
|
+
function buildConfigTemplate(relayToken, daemonToken) {
|
|
235
474
|
return `{
|
|
236
475
|
// OpenDevBrowser Plugin Configuration
|
|
237
476
|
// See: https://github.com/anthropics/opendevbrowser#configuration
|
|
@@ -295,7 +534,9 @@ function buildConfigTemplate(token) {
|
|
|
295
534
|
},
|
|
296
535
|
|
|
297
536
|
"relayPort": 8787,
|
|
298
|
-
"relayToken": "${
|
|
537
|
+
"relayToken": "${relayToken}",
|
|
538
|
+
"daemonPort": 8788,
|
|
539
|
+
"daemonToken": "${daemonToken}",
|
|
299
540
|
|
|
300
541
|
"flags": [],
|
|
301
542
|
|
|
@@ -305,22 +546,23 @@ function buildConfigTemplate(token) {
|
|
|
305
546
|
}
|
|
306
547
|
function getPluginConfigPath(mode) {
|
|
307
548
|
if (mode === "global") {
|
|
308
|
-
const configDir = process.env.OPENCODE_CONFIG_DIR ||
|
|
309
|
-
return
|
|
549
|
+
const configDir = process.env.OPENCODE_CONFIG_DIR || path2.join(os2.homedir(), ".config", "opencode");
|
|
550
|
+
return path2.join(configDir, "opendevbrowser.jsonc");
|
|
310
551
|
}
|
|
311
|
-
return
|
|
552
|
+
return path2.join(process.cwd(), "opendevbrowser.jsonc");
|
|
312
553
|
}
|
|
313
554
|
function createPluginConfig(mode) {
|
|
314
555
|
const configPath = getPluginConfigPath(mode);
|
|
315
|
-
if (
|
|
556
|
+
if (fs2.existsSync(configPath)) {
|
|
316
557
|
return { created: false, path: configPath };
|
|
317
558
|
}
|
|
318
|
-
const dir =
|
|
319
|
-
if (!
|
|
320
|
-
|
|
559
|
+
const dir = path2.dirname(configPath);
|
|
560
|
+
if (!fs2.existsSync(dir)) {
|
|
561
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
321
562
|
}
|
|
322
|
-
const
|
|
323
|
-
|
|
563
|
+
const relayToken = generateSecureToken();
|
|
564
|
+
const daemonToken = generateSecureToken();
|
|
565
|
+
writeFileAtomic(configPath, buildConfigTemplate(relayToken, daemonToken));
|
|
324
566
|
return { created: true, path: configPath };
|
|
325
567
|
}
|
|
326
568
|
|
|
@@ -340,7 +582,7 @@ function installGlobal(withConfig = false) {
|
|
|
340
582
|
}
|
|
341
583
|
const newContent = updateConfigContent(content, "opendevbrowser");
|
|
342
584
|
ensureDir(configPath.replace(/[/\\][^/\\]+$/, ""));
|
|
343
|
-
|
|
585
|
+
fs3.writeFileSync(configPath, newContent, "utf-8");
|
|
344
586
|
if (withConfig) {
|
|
345
587
|
createPluginConfig("global");
|
|
346
588
|
}
|
|
@@ -364,7 +606,7 @@ function installGlobal(withConfig = false) {
|
|
|
364
606
|
}
|
|
365
607
|
|
|
366
608
|
// src/cli/installers/local.ts
|
|
367
|
-
import * as
|
|
609
|
+
import * as fs4 from "fs";
|
|
368
610
|
function installLocal(withConfig = false) {
|
|
369
611
|
const configPath = getLocalConfigPath();
|
|
370
612
|
try {
|
|
@@ -379,7 +621,7 @@ function installLocal(withConfig = false) {
|
|
|
379
621
|
};
|
|
380
622
|
}
|
|
381
623
|
const newContent = updateConfigContent(content, "opendevbrowser");
|
|
382
|
-
|
|
624
|
+
fs4.writeFileSync(configPath, newContent, "utf-8");
|
|
383
625
|
if (withConfig) {
|
|
384
626
|
createPluginConfig("local");
|
|
385
627
|
}
|
|
@@ -403,12 +645,12 @@ function installLocal(withConfig = false) {
|
|
|
403
645
|
}
|
|
404
646
|
|
|
405
647
|
// src/cli/installers/skills.ts
|
|
406
|
-
import * as fs7 from "fs";
|
|
407
|
-
import * as path5 from "path";
|
|
408
|
-
|
|
409
|
-
// src/cli/utils/skills.ts
|
|
410
648
|
import * as fs6 from "fs";
|
|
411
649
|
import * as path4 from "path";
|
|
650
|
+
|
|
651
|
+
// src/cli/utils/skills.ts
|
|
652
|
+
import * as fs5 from "fs";
|
|
653
|
+
import * as path3 from "path";
|
|
412
654
|
import * as os3 from "os";
|
|
413
655
|
import { fileURLToPath } from "url";
|
|
414
656
|
var PACKAGE_NAME = "opendevbrowser";
|
|
@@ -417,17 +659,17 @@ var cachedPackageRoot = null;
|
|
|
417
659
|
function findPackageRoot(startDir) {
|
|
418
660
|
let current = startDir;
|
|
419
661
|
while (true) {
|
|
420
|
-
const pkgPath =
|
|
421
|
-
if (
|
|
662
|
+
const pkgPath = path3.join(current, "package.json");
|
|
663
|
+
if (fs5.existsSync(pkgPath)) {
|
|
422
664
|
try {
|
|
423
|
-
const parsed = JSON.parse(
|
|
665
|
+
const parsed = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
|
|
424
666
|
if (parsed.name === PACKAGE_NAME) {
|
|
425
667
|
return current;
|
|
426
668
|
}
|
|
427
669
|
} catch {
|
|
428
670
|
}
|
|
429
671
|
}
|
|
430
|
-
const parent =
|
|
672
|
+
const parent = path3.dirname(current);
|
|
431
673
|
if (parent === current) {
|
|
432
674
|
break;
|
|
433
675
|
}
|
|
@@ -437,23 +679,23 @@ function findPackageRoot(startDir) {
|
|
|
437
679
|
}
|
|
438
680
|
function getPackageRoot() {
|
|
439
681
|
if (cachedPackageRoot) return cachedPackageRoot;
|
|
440
|
-
const moduleDir =
|
|
682
|
+
const moduleDir = path3.dirname(fileURLToPath(import.meta.url));
|
|
441
683
|
cachedPackageRoot = findPackageRoot(moduleDir);
|
|
442
684
|
return cachedPackageRoot;
|
|
443
685
|
}
|
|
444
686
|
function getBundledSkillsDir() {
|
|
445
|
-
const skillsDir =
|
|
446
|
-
if (!
|
|
687
|
+
const skillsDir = path3.join(getPackageRoot(), "skills");
|
|
688
|
+
if (!fs5.existsSync(skillsDir)) {
|
|
447
689
|
throw new Error(`Bundled skills directory not found at ${skillsDir}`);
|
|
448
690
|
}
|
|
449
691
|
return skillsDir;
|
|
450
692
|
}
|
|
451
693
|
function getGlobalSkillDir() {
|
|
452
|
-
const configDir = process.env.OPENCODE_CONFIG_DIR ||
|
|
453
|
-
return
|
|
694
|
+
const configDir = process.env.OPENCODE_CONFIG_DIR || path3.join(os3.homedir(), ".config", "opencode");
|
|
695
|
+
return path3.join(configDir, SKILL_DIR_NAME);
|
|
454
696
|
}
|
|
455
697
|
function getLocalSkillDir() {
|
|
456
|
-
return
|
|
698
|
+
return path3.join(process.cwd(), ".opencode", SKILL_DIR_NAME);
|
|
457
699
|
}
|
|
458
700
|
|
|
459
701
|
// src/cli/installers/skills.ts
|
|
@@ -463,18 +705,18 @@ function installSkills(mode) {
|
|
|
463
705
|
const skipped = [];
|
|
464
706
|
try {
|
|
465
707
|
const sourceDir = getBundledSkillsDir();
|
|
466
|
-
const entries =
|
|
708
|
+
const entries = fs6.readdirSync(sourceDir, { withFileTypes: true });
|
|
467
709
|
ensureDir(targetDir);
|
|
468
710
|
for (const entry of entries) {
|
|
469
711
|
if (!entry.isDirectory()) continue;
|
|
470
712
|
const skillName = entry.name;
|
|
471
|
-
const sourcePath =
|
|
472
|
-
const targetPath =
|
|
473
|
-
if (
|
|
713
|
+
const sourcePath = path4.join(sourceDir, skillName);
|
|
714
|
+
const targetPath = path4.join(targetDir, skillName);
|
|
715
|
+
if (fs6.existsSync(targetPath)) {
|
|
474
716
|
skipped.push(skillName);
|
|
475
717
|
continue;
|
|
476
718
|
}
|
|
477
|
-
|
|
719
|
+
fs6.cpSync(sourcePath, targetPath, { recursive: true });
|
|
478
720
|
installed.push(skillName);
|
|
479
721
|
}
|
|
480
722
|
const summary = `Skills ${mode} install: ${installed.length} installed${skipped.length ? `, ${skipped.length} skipped` : ""} (${targetDir})`;
|
|
@@ -498,31 +740,31 @@ function installSkills(mode) {
|
|
|
498
740
|
}
|
|
499
741
|
|
|
500
742
|
// src/cli/commands/update.ts
|
|
501
|
-
import * as
|
|
502
|
-
import * as
|
|
743
|
+
import * as fs7 from "fs";
|
|
744
|
+
import * as path5 from "path";
|
|
503
745
|
import * as os4 from "os";
|
|
504
746
|
var PLUGIN_NAME2 = "opendevbrowser";
|
|
505
747
|
function getCacheDir() {
|
|
506
|
-
return process.env.OPENCODE_CACHE_DIR ||
|
|
748
|
+
return process.env.OPENCODE_CACHE_DIR || path5.join(os4.homedir(), ".cache", "opencode");
|
|
507
749
|
}
|
|
508
750
|
function rmdir(dirPath) {
|
|
509
751
|
const cacheDir = getCacheDir();
|
|
510
|
-
const resolvedCache =
|
|
511
|
-
const resolvedPath =
|
|
512
|
-
if (!resolvedPath.startsWith(resolvedCache +
|
|
752
|
+
const resolvedCache = path5.resolve(cacheDir);
|
|
753
|
+
const resolvedPath = path5.resolve(dirPath);
|
|
754
|
+
if (!resolvedPath.startsWith(resolvedCache + path5.sep) || resolvedPath === resolvedCache) {
|
|
513
755
|
throw new Error(`Security: refusing to delete path outside cache directory: ${dirPath}`);
|
|
514
756
|
}
|
|
515
|
-
if (
|
|
516
|
-
|
|
757
|
+
if (fs7.existsSync(dirPath)) {
|
|
758
|
+
fs7.rmSync(dirPath, { recursive: true, force: true });
|
|
517
759
|
}
|
|
518
760
|
}
|
|
519
761
|
function runUpdate() {
|
|
520
762
|
const cacheDir = getCacheDir();
|
|
521
|
-
const nodeModulesDir =
|
|
522
|
-
const pluginCacheDir =
|
|
763
|
+
const nodeModulesDir = path5.join(cacheDir, "node_modules");
|
|
764
|
+
const pluginCacheDir = path5.join(nodeModulesDir, PLUGIN_NAME2);
|
|
523
765
|
try {
|
|
524
|
-
if (!
|
|
525
|
-
if (
|
|
766
|
+
if (!fs7.existsSync(pluginCacheDir)) {
|
|
767
|
+
if (fs7.existsSync(nodeModulesDir)) {
|
|
526
768
|
rmdir(nodeModulesDir);
|
|
527
769
|
return {
|
|
528
770
|
success: true,
|
|
@@ -553,20 +795,20 @@ function runUpdate() {
|
|
|
553
795
|
}
|
|
554
796
|
|
|
555
797
|
// src/cli/commands/uninstall.ts
|
|
556
|
-
import * as
|
|
557
|
-
import * as
|
|
798
|
+
import * as fs8 from "fs";
|
|
799
|
+
import * as path6 from "path";
|
|
558
800
|
import * as os5 from "os";
|
|
559
801
|
function getPluginConfigPath2(mode) {
|
|
560
802
|
if (mode === "global") {
|
|
561
|
-
const configDir = process.env.OPENCODE_CONFIG_DIR ||
|
|
562
|
-
return
|
|
803
|
+
const configDir = process.env.OPENCODE_CONFIG_DIR || path6.join(os5.homedir(), ".config", "opencode");
|
|
804
|
+
return path6.join(configDir, "opendevbrowser.jsonc");
|
|
563
805
|
}
|
|
564
|
-
return
|
|
806
|
+
return path6.join(process.cwd(), "opendevbrowser.jsonc");
|
|
565
807
|
}
|
|
566
808
|
function removePluginConfigFile(mode) {
|
|
567
809
|
const configPath = getPluginConfigPath2(mode);
|
|
568
|
-
if (
|
|
569
|
-
|
|
810
|
+
if (fs8.existsSync(configPath)) {
|
|
811
|
+
fs8.unlinkSync(configPath);
|
|
570
812
|
return true;
|
|
571
813
|
}
|
|
572
814
|
return false;
|
|
@@ -585,7 +827,7 @@ function runUninstall(mode, deleteConfigFile = false) {
|
|
|
585
827
|
};
|
|
586
828
|
}
|
|
587
829
|
const newContent = removePluginFromContent(content, "opendevbrowser");
|
|
588
|
-
|
|
830
|
+
fs8.writeFileSync(configPath, newContent, "utf-8");
|
|
589
831
|
let configFileDeleted = false;
|
|
590
832
|
if (deleteConfigFile) {
|
|
591
833
|
configFileDeleted = removePluginConfigFile(mode);
|
|
@@ -624,179 +866,3352 @@ function findInstalledConfigs() {
|
|
|
624
866
|
return { global, local };
|
|
625
867
|
}
|
|
626
868
|
|
|
627
|
-
// src/cli/
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
if (!
|
|
631
|
-
|
|
632
|
-
return "global";
|
|
869
|
+
// src/cli/utils/parse.ts
|
|
870
|
+
function parseNumberFlag(value, flag, options = {}) {
|
|
871
|
+
const parsed = Number(value);
|
|
872
|
+
if (!Number.isFinite(parsed)) {
|
|
873
|
+
throw createUsageError(`Invalid ${flag}: ${value}`);
|
|
633
874
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
875
|
+
const requireInteger = options.integer ?? true;
|
|
876
|
+
if (requireInteger && !Number.isInteger(parsed)) {
|
|
877
|
+
throw createUsageError(`Invalid ${flag}: ${value}`);
|
|
878
|
+
}
|
|
879
|
+
if (typeof options.min === "number" && parsed < options.min) {
|
|
880
|
+
throw createUsageError(`Invalid ${flag}: ${value}`);
|
|
881
|
+
}
|
|
882
|
+
if (typeof options.max === "number" && parsed > options.max) {
|
|
883
|
+
throw createUsageError(`Invalid ${flag}: ${value}`);
|
|
884
|
+
}
|
|
885
|
+
return parsed;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// src/cli/commands/native.ts
|
|
889
|
+
import * as fs9 from "fs";
|
|
890
|
+
import * as path7 from "path";
|
|
891
|
+
import { homedir as homedir6 } from "os";
|
|
892
|
+
import { execFileSync } from "child_process";
|
|
893
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
894
|
+
var EXTENSION_ID_RE = /^[a-p]{32}$/;
|
|
895
|
+
var EXTENSION_NAME = "OpenDevBrowser Relay";
|
|
896
|
+
var ANNOTATION_COMMAND_NAME = "toggle-annotation";
|
|
897
|
+
var normalizeExtensionId = (value) => {
|
|
898
|
+
if (!value) return null;
|
|
899
|
+
const trimmed = value.trim();
|
|
900
|
+
if (!trimmed) return null;
|
|
901
|
+
return EXTENSION_ID_RE.test(trimmed) ? trimmed : null;
|
|
902
|
+
};
|
|
903
|
+
var requireExtensionId = (value) => {
|
|
904
|
+
if (!value) {
|
|
905
|
+
throw createUsageError("Missing extension ID. Usage: opendevbrowser native install <extension-id>");
|
|
906
|
+
}
|
|
907
|
+
const normalized = normalizeExtensionId(value);
|
|
908
|
+
if (!normalized) {
|
|
909
|
+
throw createUsageError("Invalid extension ID format. Expected 32 characters (a-p).");
|
|
910
|
+
}
|
|
911
|
+
return normalized;
|
|
912
|
+
};
|
|
913
|
+
var parseNativeArgs = (rawArgs) => {
|
|
914
|
+
const subcommand = rawArgs[0];
|
|
915
|
+
if (subcommand !== "install" && subcommand !== "uninstall" && subcommand !== "status") {
|
|
916
|
+
throw createUsageError("Usage: opendevbrowser native <install|uninstall|status> [extension-id]");
|
|
917
|
+
}
|
|
918
|
+
if (subcommand === "install") {
|
|
919
|
+
const extensionId = requireExtensionId(rawArgs[1]);
|
|
920
|
+
return { subcommand, extensionId };
|
|
921
|
+
}
|
|
922
|
+
return { subcommand };
|
|
923
|
+
};
|
|
924
|
+
var getManifestDir = () => {
|
|
925
|
+
if (process.platform === "darwin") {
|
|
926
|
+
return path7.join(process.env.HOME || "", "Library", "Application Support", "Google", "Chrome", "NativeMessagingHosts");
|
|
927
|
+
}
|
|
928
|
+
if (process.platform === "linux") {
|
|
929
|
+
return path7.join(process.env.HOME || "", ".config", "google-chrome", "NativeMessagingHosts");
|
|
930
|
+
}
|
|
931
|
+
if (process.platform === "win32") {
|
|
932
|
+
const base = process.env.LOCALAPPDATA || (process.env.USERPROFILE ? path7.join(process.env.USERPROFILE, "AppData", "Local") : "");
|
|
933
|
+
if (!base) {
|
|
934
|
+
throw createUsageError("LOCALAPPDATA is not set. Unable to locate NativeMessagingHosts directory.");
|
|
935
|
+
}
|
|
936
|
+
return path7.join(base, "Google", "Chrome", "User Data", "NativeMessagingHosts");
|
|
937
|
+
}
|
|
938
|
+
throw createUsageError(`Native messaging is not supported on ${process.platform}.`);
|
|
939
|
+
};
|
|
940
|
+
var getScriptsDir = () => {
|
|
941
|
+
const __filename = fileURLToPath2(import.meta.url);
|
|
942
|
+
const startDir = path7.dirname(__filename);
|
|
943
|
+
const rootsToScan = [startDir, process.cwd()];
|
|
944
|
+
for (const root of rootsToScan) {
|
|
945
|
+
let current = path7.resolve(root);
|
|
946
|
+
while (true) {
|
|
947
|
+
const scriptsDir = path7.join(current, "scripts", "native");
|
|
948
|
+
const packageJsonPath = path7.join(current, "package.json");
|
|
949
|
+
if (fs9.existsSync(scriptsDir) && fs9.existsSync(packageJsonPath)) {
|
|
950
|
+
return scriptsDir;
|
|
646
951
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
if (resolved) return;
|
|
651
|
-
resolved = true;
|
|
652
|
-
const input = data.toString().trim();
|
|
653
|
-
if (input === "2") {
|
|
654
|
-
resolve2("local");
|
|
655
|
-
} else {
|
|
656
|
-
resolve2("global");
|
|
952
|
+
const parent = path7.dirname(current);
|
|
953
|
+
if (parent === current) {
|
|
954
|
+
break;
|
|
657
955
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
cleanup();
|
|
661
|
-
if (resolved) return;
|
|
662
|
-
resolved = true;
|
|
663
|
-
resolve2("global");
|
|
664
|
-
});
|
|
665
|
-
timeoutId = setTimeout(() => {
|
|
666
|
-
timeoutId = null;
|
|
667
|
-
if (resolved) return;
|
|
668
|
-
resolved = true;
|
|
669
|
-
console.log("\nTimeout - using global install.");
|
|
670
|
-
resolve2("global");
|
|
671
|
-
}, 3e4);
|
|
672
|
-
});
|
|
673
|
-
}
|
|
674
|
-
async function promptUninstallMode() {
|
|
675
|
-
const installed = findInstalledConfigs();
|
|
676
|
-
if (!installed.global && !installed.local) {
|
|
677
|
-
console.log("opendevbrowser is not installed in any config.");
|
|
678
|
-
return null;
|
|
956
|
+
current = parent;
|
|
957
|
+
}
|
|
679
958
|
}
|
|
680
|
-
|
|
681
|
-
|
|
959
|
+
throw createUsageError("Unable to locate scripts/native directory.");
|
|
960
|
+
};
|
|
961
|
+
var getHostScriptPath = () => {
|
|
962
|
+
return path7.join(getScriptsDir(), "host.cjs");
|
|
963
|
+
};
|
|
964
|
+
var getManifestPath = () => {
|
|
965
|
+
return path7.join(getManifestDir(), "com.opendevbrowser.native.json");
|
|
966
|
+
};
|
|
967
|
+
var getWrapperPath = () => {
|
|
968
|
+
const wrapperName = process.platform === "win32" ? "com.opendevbrowser.native.cmd" : "com.opendevbrowser.native.sh";
|
|
969
|
+
return path7.join(getManifestDir(), wrapperName);
|
|
970
|
+
};
|
|
971
|
+
var readManifest = (manifestPath) => {
|
|
972
|
+
try {
|
|
973
|
+
const raw = fs9.readFileSync(manifestPath, "utf8");
|
|
974
|
+
const data = JSON.parse(raw);
|
|
975
|
+
const origins = Array.isArray(data.allowed_origins) ? data.allowed_origins : [];
|
|
976
|
+
const match = origins.find((origin) => origin.startsWith("chrome-extension://"));
|
|
977
|
+
if (!match) return { extensionId: null };
|
|
978
|
+
const id = match.replace("chrome-extension://", "").replace("/", "");
|
|
979
|
+
return { extensionId: EXTENSION_ID_RE.test(id) ? id : null };
|
|
980
|
+
} catch {
|
|
981
|
+
return { extensionId: null };
|
|
682
982
|
}
|
|
683
|
-
|
|
684
|
-
|
|
983
|
+
};
|
|
984
|
+
var runScript = (script, args) => {
|
|
985
|
+
if (process.platform === "win32") {
|
|
986
|
+
execFileSync("powershell", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", script, ...args], { stdio: "pipe" });
|
|
987
|
+
return;
|
|
685
988
|
}
|
|
686
|
-
|
|
687
|
-
|
|
989
|
+
execFileSync("bash", [script, ...args], { stdio: "pipe" });
|
|
990
|
+
};
|
|
991
|
+
var readRegistryPath = () => {
|
|
992
|
+
if (process.platform !== "win32") return null;
|
|
993
|
+
const key = "HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\com.opendevbrowser.native";
|
|
994
|
+
try {
|
|
995
|
+
const output = execFileSync("reg", ["query", key, "/ve"], { encoding: "utf8" });
|
|
996
|
+
const lines = output.split(/\r?\n/);
|
|
997
|
+
for (const line of lines) {
|
|
998
|
+
if (line.includes("REG_SZ")) {
|
|
999
|
+
const parts = line.trim().split(/\s{2,}/);
|
|
1000
|
+
return parts[parts.length - 1] || null;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
return null;
|
|
1004
|
+
} catch {
|
|
688
1005
|
return null;
|
|
689
1006
|
}
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
console.log(" 1. Global (~/.config/opencode/opencode.json)");
|
|
693
|
-
console.log(" 2. Local (./opencode.json)");
|
|
694
|
-
console.log(" 3. Cancel\n");
|
|
695
|
-
process.stdout.write("Which to uninstall? [3]: ");
|
|
696
|
-
process.stdin.setEncoding("utf8");
|
|
697
|
-
process.stdin.once("data", (data) => {
|
|
698
|
-
const input = data.toString().trim();
|
|
699
|
-
if (input === "1") {
|
|
700
|
-
resolve2("global");
|
|
701
|
-
} else if (input === "2") {
|
|
702
|
-
resolve2("local");
|
|
703
|
-
} else {
|
|
704
|
-
resolve2(null);
|
|
705
|
-
}
|
|
706
|
-
});
|
|
707
|
-
process.stdin.once("close", () => {
|
|
708
|
-
resolve2(null);
|
|
709
|
-
});
|
|
710
|
-
});
|
|
711
|
-
}
|
|
712
|
-
async function main() {
|
|
1007
|
+
};
|
|
1008
|
+
var normalizePath = (value) => {
|
|
713
1009
|
try {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
1010
|
+
return fs9.realpathSync(value);
|
|
1011
|
+
} catch {
|
|
1012
|
+
return path7.resolve(value);
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
var getChromeUserDataRoots = () => {
|
|
1016
|
+
if (process.platform === "darwin") {
|
|
1017
|
+
return [
|
|
1018
|
+
path7.join(homedir6(), "Library", "Application Support", "Google", "Chrome"),
|
|
1019
|
+
path7.join(homedir6(), "Library", "Application Support", "Chromium"),
|
|
1020
|
+
path7.join(homedir6(), "Library", "Application Support", "BraveSoftware", "Brave-Browser")
|
|
1021
|
+
];
|
|
1022
|
+
}
|
|
1023
|
+
if (process.platform === "linux") {
|
|
1024
|
+
return [
|
|
1025
|
+
path7.join(homedir6(), ".config", "google-chrome"),
|
|
1026
|
+
path7.join(homedir6(), ".config", "chromium"),
|
|
1027
|
+
path7.join(homedir6(), ".config", "BraveSoftware", "Brave-Browser")
|
|
1028
|
+
];
|
|
1029
|
+
}
|
|
1030
|
+
if (process.platform === "win32") {
|
|
1031
|
+
const base = process.env.LOCALAPPDATA || (process.env.USERPROFILE ? path7.join(process.env.USERPROFILE, "AppData", "Local") : "");
|
|
1032
|
+
if (!base) return [];
|
|
1033
|
+
return [
|
|
1034
|
+
path7.join(base, "Google", "Chrome", "User Data"),
|
|
1035
|
+
path7.join(base, "Chromium", "User Data"),
|
|
1036
|
+
path7.join(base, "BraveSoftware", "Brave-Browser", "User Data")
|
|
1037
|
+
];
|
|
1038
|
+
}
|
|
1039
|
+
return [];
|
|
1040
|
+
};
|
|
1041
|
+
var PROFILE_PREFERENCES_FILES = ["Preferences", "Secure Preferences"];
|
|
1042
|
+
var getProfileDirs = (root) => {
|
|
1043
|
+
try {
|
|
1044
|
+
const entries = fs9.readdirSync(root, { withFileTypes: true });
|
|
1045
|
+
return entries.filter((entry) => entry.isDirectory() && (entry.name === "Default" || entry.name.startsWith("Profile "))).map((entry) => path7.join(root, entry.name)).filter((dir) => PROFILE_PREFERENCES_FILES.some((filename) => fs9.existsSync(path7.join(dir, filename))));
|
|
1046
|
+
} catch {
|
|
1047
|
+
return [];
|
|
1048
|
+
}
|
|
1049
|
+
};
|
|
1050
|
+
var readProfilePreferences = (profileDir) => {
|
|
1051
|
+
const records = [];
|
|
1052
|
+
for (const filename of PROFILE_PREFERENCES_FILES) {
|
|
1053
|
+
try {
|
|
1054
|
+
const raw = fs9.readFileSync(path7.join(profileDir, filename), "utf8");
|
|
1055
|
+
records.push(JSON.parse(raw));
|
|
1056
|
+
} catch {
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
return records;
|
|
1060
|
+
};
|
|
1061
|
+
var findExtensionIdInCommands = (preferences) => {
|
|
1062
|
+
const extensionCommands = preferences.extensions;
|
|
1063
|
+
const commandMaps = [
|
|
1064
|
+
extensionCommands?.commands,
|
|
1065
|
+
preferences.account_values?.extensions?.commands
|
|
1066
|
+
];
|
|
1067
|
+
for (const commandMap of commandMaps) {
|
|
1068
|
+
if (!commandMap) {
|
|
1069
|
+
continue;
|
|
1070
|
+
}
|
|
1071
|
+
for (const value of Object.values(commandMap)) {
|
|
1072
|
+
if (typeof value !== "object" || value === null) {
|
|
1073
|
+
continue;
|
|
729
1074
|
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
console.log("Uninstall cancelled.");
|
|
736
|
-
process.exit(0);
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
if (!mode) {
|
|
740
|
-
console.error("Error: Please specify --global or --local for uninstall.");
|
|
741
|
-
process.exit(1);
|
|
742
|
-
}
|
|
743
|
-
const result = runUninstall(mode);
|
|
744
|
-
console.log(result.message);
|
|
745
|
-
process.exit(result.success ? 0 : 1);
|
|
746
|
-
break;
|
|
1075
|
+
const entry = value;
|
|
1076
|
+
const commandName = typeof entry.command_name === "string" ? entry.command_name : null;
|
|
1077
|
+
const extensionId = typeof entry.extension === "string" ? entry.extension : null;
|
|
1078
|
+
if (commandName === ANNOTATION_COMMAND_NAME && extensionId && EXTENSION_ID_RE.test(extensionId)) {
|
|
1079
|
+
return extensionId;
|
|
747
1080
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
return null;
|
|
1084
|
+
};
|
|
1085
|
+
var findExtensionIdInPreferences = (preferences, extensionPath) => {
|
|
1086
|
+
const extensions = preferences.extensions;
|
|
1087
|
+
const settings = extensions?.settings;
|
|
1088
|
+
if (!settings) return null;
|
|
1089
|
+
const normalizedTargetPath = extensionPath ? normalizePath(extensionPath) : null;
|
|
1090
|
+
let nameMatch = null;
|
|
1091
|
+
for (const [id, entry] of Object.entries(settings)) {
|
|
1092
|
+
if (!EXTENSION_ID_RE.test(id) || typeof entry !== "object" || entry === null) {
|
|
1093
|
+
continue;
|
|
1094
|
+
}
|
|
1095
|
+
const record = entry;
|
|
1096
|
+
const recordPath = typeof record.path === "string" ? record.path : null;
|
|
1097
|
+
if (recordPath && normalizedTargetPath) {
|
|
1098
|
+
if (normalizePath(recordPath) === normalizedTargetPath) {
|
|
1099
|
+
return { id, matchedBy: "path" };
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
const manifest = record.manifest;
|
|
1103
|
+
const name = typeof manifest?.name === "string" ? manifest.name : null;
|
|
1104
|
+
if (!nameMatch && name === EXTENSION_NAME) {
|
|
1105
|
+
nameMatch = id;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
if (nameMatch) {
|
|
1109
|
+
return { id: nameMatch, matchedBy: "name" };
|
|
1110
|
+
}
|
|
1111
|
+
return null;
|
|
1112
|
+
};
|
|
1113
|
+
var getExtensionPathCandidates = () => {
|
|
1114
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
1115
|
+
const primary = getExtensionPath();
|
|
1116
|
+
if (primary) {
|
|
1117
|
+
candidates.add(normalizePath(primary));
|
|
1118
|
+
}
|
|
1119
|
+
const cwdExtension = path7.join(process.cwd(), "extension");
|
|
1120
|
+
if (fs9.existsSync(path7.join(cwdExtension, "manifest.json"))) {
|
|
1121
|
+
candidates.add(normalizePath(cwdExtension));
|
|
1122
|
+
}
|
|
1123
|
+
if (candidates.size === 0) {
|
|
1124
|
+
return [null];
|
|
1125
|
+
}
|
|
1126
|
+
return [...candidates];
|
|
1127
|
+
};
|
|
1128
|
+
var getNativeStatusSnapshot = () => {
|
|
1129
|
+
const hostScript = getHostScriptPath();
|
|
1130
|
+
const manifestPath = getManifestPath();
|
|
1131
|
+
const wrapperPath = getWrapperPath();
|
|
1132
|
+
const registryPath = readRegistryPath();
|
|
1133
|
+
let installed = false;
|
|
1134
|
+
let manifestExists = false;
|
|
1135
|
+
let wrapperExists = false;
|
|
1136
|
+
let extensionIdValue = null;
|
|
1137
|
+
if (fs9.existsSync(manifestPath)) {
|
|
1138
|
+
manifestExists = true;
|
|
1139
|
+
installed = true;
|
|
1140
|
+
const manifest = readManifest(manifestPath);
|
|
1141
|
+
extensionIdValue = manifest.extensionId;
|
|
1142
|
+
}
|
|
1143
|
+
if (fs9.existsSync(wrapperPath)) {
|
|
1144
|
+
wrapperExists = true;
|
|
1145
|
+
}
|
|
1146
|
+
if (!manifestExists || !wrapperExists) {
|
|
1147
|
+
installed = false;
|
|
1148
|
+
}
|
|
1149
|
+
if (process.platform === "win32" && !registryPath) {
|
|
1150
|
+
installed = false;
|
|
1151
|
+
}
|
|
1152
|
+
return {
|
|
1153
|
+
installed,
|
|
1154
|
+
manifestPath: manifestExists ? manifestPath : null,
|
|
1155
|
+
wrapperPath: wrapperExists ? wrapperPath : null,
|
|
1156
|
+
hostScriptPath: hostScript,
|
|
1157
|
+
extensionId: extensionIdValue,
|
|
1158
|
+
registryPath
|
|
1159
|
+
};
|
|
1160
|
+
};
|
|
1161
|
+
function discoverExtensionId() {
|
|
1162
|
+
const extensionPaths = getExtensionPathCandidates();
|
|
1163
|
+
const roots = getChromeUserDataRoots();
|
|
1164
|
+
for (const root of roots) {
|
|
1165
|
+
for (const profileDir of getProfileDirs(root)) {
|
|
1166
|
+
let nameFallback = null;
|
|
1167
|
+
let commandFallback = null;
|
|
1168
|
+
for (const preferences of readProfilePreferences(profileDir)) {
|
|
1169
|
+
for (const extensionPath of extensionPaths) {
|
|
1170
|
+
const match = findExtensionIdInPreferences(preferences, extensionPath);
|
|
1171
|
+
if (!match) {
|
|
1172
|
+
continue;
|
|
764
1173
|
}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
const extensionPath = extractExtension();
|
|
771
|
-
if (extensionPath) {
|
|
772
|
-
console.log(`Extension assets extracted to ${extensionPath}`);
|
|
773
|
-
} else {
|
|
774
|
-
console.warn("Extension assets not found; skipping extraction.");
|
|
775
|
-
}
|
|
776
|
-
} catch (error) {
|
|
777
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
778
|
-
console.warn(`Extension pre-extraction failed: ${message}`);
|
|
1174
|
+
if (match.matchedBy === "path") {
|
|
1175
|
+
return { extensionId: match.id, matchedBy: match.matchedBy };
|
|
1176
|
+
}
|
|
1177
|
+
if (!nameFallback) {
|
|
1178
|
+
nameFallback = match;
|
|
779
1179
|
}
|
|
780
1180
|
}
|
|
781
|
-
if (
|
|
782
|
-
|
|
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");
|
|
1181
|
+
if (!commandFallback) {
|
|
1182
|
+
commandFallback = findExtensionIdInCommands(preferences);
|
|
786
1183
|
}
|
|
787
|
-
process.exit(result.success ? 0 : 1);
|
|
788
|
-
break;
|
|
789
1184
|
}
|
|
790
|
-
|
|
1185
|
+
if (nameFallback) {
|
|
1186
|
+
return { extensionId: nameFallback.id, matchedBy: nameFallback.matchedBy };
|
|
1187
|
+
}
|
|
1188
|
+
if (commandFallback) {
|
|
1189
|
+
return { extensionId: commandFallback, matchedBy: "command" };
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
return { extensionId: null };
|
|
1194
|
+
}
|
|
1195
|
+
function installNativeHost(extensionId) {
|
|
1196
|
+
const normalized = normalizeExtensionId(extensionId);
|
|
1197
|
+
if (!normalized) {
|
|
1198
|
+
return {
|
|
1199
|
+
success: false,
|
|
1200
|
+
message: "Invalid extension ID format. Expected 32 characters (a-p).",
|
|
1201
|
+
exitCode: EXIT_EXECUTION
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
const hostScript = getHostScriptPath();
|
|
1205
|
+
if (!fs9.existsSync(hostScript)) {
|
|
1206
|
+
return {
|
|
1207
|
+
success: false,
|
|
1208
|
+
message: `Native host not found at ${hostScript}.`,
|
|
1209
|
+
exitCode: EXIT_EXECUTION
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
const scriptsDir = getScriptsDir();
|
|
1213
|
+
const manifestPath = getManifestPath();
|
|
1214
|
+
const installScript = process.platform === "win32" ? path7.join(scriptsDir, "install.ps1") : path7.join(scriptsDir, "install.sh");
|
|
1215
|
+
try {
|
|
1216
|
+
runScript(installScript, [normalized]);
|
|
1217
|
+
return {
|
|
1218
|
+
success: true,
|
|
1219
|
+
message: `Native host installed for extension ${normalized}.`,
|
|
1220
|
+
data: { manifestPath }
|
|
1221
|
+
};
|
|
1222
|
+
} catch (error) {
|
|
1223
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1224
|
+
return {
|
|
1225
|
+
success: false,
|
|
1226
|
+
message: `Native install failed: ${message}`,
|
|
1227
|
+
exitCode: EXIT_EXECUTION
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
async function runNativeCommand(args) {
|
|
1232
|
+
const { subcommand, extensionId } = parseNativeArgs(args.rawArgs);
|
|
1233
|
+
const scriptsDir = getScriptsDir();
|
|
1234
|
+
const uninstallScript = process.platform === "win32" ? path7.join(scriptsDir, "uninstall.ps1") : path7.join(scriptsDir, "uninstall.sh");
|
|
1235
|
+
if (subcommand === "install") {
|
|
1236
|
+
return installNativeHost(extensionId);
|
|
1237
|
+
}
|
|
1238
|
+
if (subcommand === "uninstall") {
|
|
1239
|
+
try {
|
|
1240
|
+
runScript(uninstallScript, []);
|
|
1241
|
+
return { success: true, message: "Native host uninstalled." };
|
|
1242
|
+
} catch (error) {
|
|
1243
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
1244
|
+
return { success: false, message: `Native uninstall failed: ${message2}`, exitCode: EXIT_EXECUTION };
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
const data = getNativeStatusSnapshot();
|
|
1248
|
+
if (!data.installed) {
|
|
1249
|
+
return {
|
|
1250
|
+
success: false,
|
|
1251
|
+
message: "Native host not installed.",
|
|
1252
|
+
data,
|
|
1253
|
+
exitCode: EXIT_DISCONNECTED
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
const message = data.extensionId ? `Native host installed for extension ${data.extensionId}.` : "Native host installed (extension id missing).";
|
|
1257
|
+
return { success: true, message, data };
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// src/cli/commands/serve.ts
|
|
1261
|
+
var daemonHandle = null;
|
|
1262
|
+
function parseServeArgs(rawArgs) {
|
|
1263
|
+
const parsed = { stop: false };
|
|
1264
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
1265
|
+
const arg = rawArgs[i];
|
|
1266
|
+
if (arg === "--stop") {
|
|
1267
|
+
parsed.stop = true;
|
|
1268
|
+
continue;
|
|
1269
|
+
}
|
|
1270
|
+
if (arg === "--port") {
|
|
1271
|
+
const value = rawArgs[i + 1];
|
|
1272
|
+
if (!value) {
|
|
1273
|
+
throw createUsageError("Missing value for --port");
|
|
1274
|
+
}
|
|
1275
|
+
parsed.port = parseNumberFlag(value, "--port", { min: 1, max: 65535 });
|
|
1276
|
+
i += 1;
|
|
1277
|
+
continue;
|
|
1278
|
+
}
|
|
1279
|
+
if (arg?.startsWith("--port=")) {
|
|
1280
|
+
const value = arg.split("=", 2)[1];
|
|
1281
|
+
if (!value) {
|
|
1282
|
+
throw createUsageError("Missing value for --port");
|
|
1283
|
+
}
|
|
1284
|
+
parsed.port = parseNumberFlag(value, "--port", { min: 1, max: 65535 });
|
|
1285
|
+
continue;
|
|
1286
|
+
}
|
|
1287
|
+
if (arg === "--token") {
|
|
1288
|
+
const value = rawArgs[i + 1];
|
|
1289
|
+
if (!value) {
|
|
1290
|
+
throw createUsageError("Missing value for --token");
|
|
1291
|
+
}
|
|
1292
|
+
parsed.token = value;
|
|
1293
|
+
i += 1;
|
|
1294
|
+
continue;
|
|
1295
|
+
}
|
|
1296
|
+
if (arg?.startsWith("--token=")) {
|
|
1297
|
+
const value = arg.split("=", 2)[1];
|
|
1298
|
+
if (!value) {
|
|
1299
|
+
throw createUsageError("Missing value for --token");
|
|
1300
|
+
}
|
|
1301
|
+
parsed.token = value;
|
|
1302
|
+
continue;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
return parsed;
|
|
1306
|
+
}
|
|
1307
|
+
async function runServe(args) {
|
|
1308
|
+
const serveArgs = parseServeArgs(args.rawArgs);
|
|
1309
|
+
if (serveArgs.stop) {
|
|
1310
|
+
const metadata = readDaemonMetadata();
|
|
1311
|
+
if (!metadata) {
|
|
1312
|
+
if (daemonHandle) {
|
|
1313
|
+
await daemonHandle.stop();
|
|
1314
|
+
daemonHandle = null;
|
|
1315
|
+
return { success: true, message: "Daemon stopped." };
|
|
1316
|
+
}
|
|
1317
|
+
return { success: false, message: "Daemon not running.", exitCode: EXIT_DISCONNECTED };
|
|
1318
|
+
}
|
|
1319
|
+
try {
|
|
1320
|
+
const response = await fetchWithTimeout(`http://127.0.0.1:${metadata.port}/stop`, {
|
|
1321
|
+
method: "POST",
|
|
1322
|
+
headers: { Authorization: `Bearer ${metadata.token}` }
|
|
1323
|
+
});
|
|
1324
|
+
if (!response.ok) {
|
|
1325
|
+
throw new Error(`Stop failed (${response.status})`);
|
|
1326
|
+
}
|
|
1327
|
+
return { success: true, message: "Daemon stopped." };
|
|
1328
|
+
} catch (error) {
|
|
1329
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
1330
|
+
return { success: false, message: `Failed to stop daemon: ${message2}`, exitCode: EXIT_EXECUTION };
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
const config = loadGlobalConfig();
|
|
1334
|
+
let nativeStatus = getNativeStatusSnapshot();
|
|
1335
|
+
let nativeMessage = null;
|
|
1336
|
+
if (!nativeStatus.installed) {
|
|
1337
|
+
const discovered = discoverExtensionId();
|
|
1338
|
+
const extensionId = config.nativeExtensionId ?? discovered.extensionId ?? null;
|
|
1339
|
+
const usedDiscovery = !config.nativeExtensionId && Boolean(discovered.extensionId);
|
|
1340
|
+
if (extensionId) {
|
|
1341
|
+
const installResult = installNativeHost(extensionId);
|
|
1342
|
+
if (installResult.success) {
|
|
1343
|
+
const suffix = usedDiscovery && discovered.matchedBy ? ` (auto-detected by ${discovered.matchedBy})` : "";
|
|
1344
|
+
nativeMessage = `${installResult.message ?? "Native host installed."}${suffix}`;
|
|
1345
|
+
nativeStatus = getNativeStatusSnapshot();
|
|
1346
|
+
} else {
|
|
1347
|
+
nativeMessage = `Native host install skipped: ${installResult.message ?? "unknown error"}`;
|
|
1348
|
+
}
|
|
1349
|
+
} else {
|
|
1350
|
+
nativeMessage = "Native host not installed. Set nativeExtensionId in opendevbrowser.jsonc to auto-install.";
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
const handle = await startDaemon({
|
|
1354
|
+
port: serveArgs.port,
|
|
1355
|
+
token: serveArgs.token,
|
|
1356
|
+
config
|
|
1357
|
+
});
|
|
1358
|
+
daemonHandle = handle;
|
|
1359
|
+
const { state } = handle;
|
|
1360
|
+
const baseMessage = `Daemon running on 127.0.0.1:${state.port} (relay ${state.relayPort})`;
|
|
1361
|
+
const message = nativeMessage ? `${baseMessage}
|
|
1362
|
+
${nativeMessage}` : baseMessage;
|
|
1363
|
+
return {
|
|
1364
|
+
success: true,
|
|
1365
|
+
message,
|
|
1366
|
+
data: { port: state.port, pid: state.pid, relayPort: state.relayPort, native: nativeStatus },
|
|
1367
|
+
exitCode: null
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
// src/cli/daemon-autostart.ts
|
|
1372
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
1373
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
1374
|
+
import { homedir as homedir7 } from "os";
|
|
1375
|
+
import { dirname as dirname5, join as join8, resolve as resolve3 } from "path";
|
|
1376
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1377
|
+
var MAC_LABEL = "com.opendevbrowser.daemon";
|
|
1378
|
+
var WIN_TASK_NAME = "OpenDevBrowser Daemon";
|
|
1379
|
+
var defaultDeps = () => ({
|
|
1380
|
+
platform: process.platform,
|
|
1381
|
+
argv1: process.argv[1] ?? "",
|
|
1382
|
+
moduleUrl: import.meta.url,
|
|
1383
|
+
uid: typeof process.getuid === "function" ? process.getuid() : 0,
|
|
1384
|
+
homedir: homedir7,
|
|
1385
|
+
existsSync: existsSync8,
|
|
1386
|
+
mkdirSync: mkdirSync3,
|
|
1387
|
+
writeFileSync: writeFileSync4,
|
|
1388
|
+
unlinkSync: unlinkSync2,
|
|
1389
|
+
execFileSync: execFileSync2
|
|
1390
|
+
});
|
|
1391
|
+
var resolveCliPathFromModule = (moduleUrl, exists) => {
|
|
1392
|
+
const modulePath = fileURLToPath3(moduleUrl);
|
|
1393
|
+
const candidate = resolve3(dirname5(modulePath), "..", "index.js");
|
|
1394
|
+
if (!exists(candidate)) {
|
|
1395
|
+
throw new Error(`CLI entrypoint not found at ${candidate}`);
|
|
1396
|
+
}
|
|
1397
|
+
return candidate;
|
|
1398
|
+
};
|
|
1399
|
+
var resolveCliEntrypoint = (deps = {}) => {
|
|
1400
|
+
const resolved = { ...defaultDeps(), ...deps };
|
|
1401
|
+
const exists = resolved.existsSync;
|
|
1402
|
+
let cliPath = null;
|
|
1403
|
+
if (resolved.argv1) {
|
|
1404
|
+
const candidate = resolve3(resolved.argv1);
|
|
1405
|
+
if (exists(candidate)) {
|
|
1406
|
+
cliPath = candidate;
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
if (!cliPath) {
|
|
1410
|
+
cliPath = resolveCliPathFromModule(resolved.moduleUrl, exists);
|
|
1411
|
+
}
|
|
1412
|
+
const nodePath = process.execPath;
|
|
1413
|
+
const args = [cliPath, "serve"];
|
|
1414
|
+
const command = `"${nodePath}" "${cliPath}" serve`;
|
|
1415
|
+
return { nodePath, cliPath, args, command };
|
|
1416
|
+
};
|
|
1417
|
+
var getLaunchAgentPath = (home = homedir7()) => {
|
|
1418
|
+
return join8(home, "Library", "LaunchAgents", `${MAC_LABEL}.plist`);
|
|
1419
|
+
};
|
|
1420
|
+
var buildLaunchAgentPlist = (entrypoint, options = {}) => {
|
|
1421
|
+
const label = options.label ?? MAC_LABEL;
|
|
1422
|
+
const stdoutPath = options.stdoutPath ?? join8(homedir7(), "Library", "Logs", "opendevbrowser-daemon.log");
|
|
1423
|
+
const stderrPath = options.stderrPath ?? join8(homedir7(), "Library", "Logs", "opendevbrowser-daemon.err.log");
|
|
1424
|
+
const programArgs = [entrypoint.nodePath, ...entrypoint.args].map((value) => ` <string>${value}</string>`).join("\n");
|
|
1425
|
+
return [
|
|
1426
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
1427
|
+
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
1428
|
+
'<plist version="1.0">',
|
|
1429
|
+
"<dict>",
|
|
1430
|
+
` <key>Label</key>`,
|
|
1431
|
+
` <string>${label}</string>`,
|
|
1432
|
+
" <key>ProgramArguments</key>",
|
|
1433
|
+
" <array>",
|
|
1434
|
+
programArgs,
|
|
1435
|
+
" </array>",
|
|
1436
|
+
" <key>RunAtLoad</key>",
|
|
1437
|
+
" <true/>",
|
|
1438
|
+
" <key>KeepAlive</key>",
|
|
1439
|
+
" <true/>",
|
|
1440
|
+
" <key>StandardOutPath</key>",
|
|
1441
|
+
` <string>${stdoutPath}</string>`,
|
|
1442
|
+
" <key>StandardErrorPath</key>",
|
|
1443
|
+
` <string>${stderrPath}</string>`,
|
|
1444
|
+
"</dict>",
|
|
1445
|
+
"</plist>",
|
|
1446
|
+
""
|
|
1447
|
+
].join("\n");
|
|
1448
|
+
};
|
|
1449
|
+
var buildWindowsTaskArgs = (entrypoint, taskName = WIN_TASK_NAME) => {
|
|
1450
|
+
const command = `"${entrypoint.nodePath}" "${entrypoint.cliPath}" serve`;
|
|
1451
|
+
const args = [
|
|
1452
|
+
"/Create",
|
|
1453
|
+
"/TN",
|
|
1454
|
+
taskName,
|
|
1455
|
+
"/TR",
|
|
1456
|
+
command,
|
|
1457
|
+
"/SC",
|
|
1458
|
+
"ONLOGON",
|
|
1459
|
+
"/RL",
|
|
1460
|
+
"LIMITED",
|
|
1461
|
+
"/F"
|
|
1462
|
+
];
|
|
1463
|
+
return { taskName, command, args };
|
|
1464
|
+
};
|
|
1465
|
+
var runCommand = (exec, command, args, ignoreFailure = false) => {
|
|
1466
|
+
try {
|
|
1467
|
+
exec(command, args, { stdio: "ignore" });
|
|
791
1468
|
} catch (error) {
|
|
1469
|
+
if (ignoreFailure) return;
|
|
792
1470
|
const message = error instanceof Error ? error.message : String(error);
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
1471
|
+
throw new Error(`${command} ${args.join(" ")} failed: ${message}`);
|
|
1472
|
+
}
|
|
1473
|
+
};
|
|
1474
|
+
var installMacAutostart = (deps = {}) => {
|
|
1475
|
+
const resolved = { ...defaultDeps(), ...deps };
|
|
1476
|
+
const entrypoint = resolveCliEntrypoint(resolved);
|
|
1477
|
+
const plistPath = getLaunchAgentPath(resolved.homedir());
|
|
1478
|
+
resolved.mkdirSync(dirname5(plistPath), { recursive: true });
|
|
1479
|
+
resolved.writeFileSync(plistPath, buildLaunchAgentPlist(entrypoint), { encoding: "utf-8" });
|
|
1480
|
+
const uid = resolved.uid;
|
|
1481
|
+
runCommand(resolved.execFileSync, "launchctl", ["bootout", `gui/${uid}`, plistPath], true);
|
|
1482
|
+
runCommand(resolved.execFileSync, "launchctl", ["bootstrap", `gui/${uid}`, plistPath]);
|
|
1483
|
+
runCommand(resolved.execFileSync, "launchctl", ["enable", `gui/${uid}/${MAC_LABEL}`], true);
|
|
1484
|
+
runCommand(resolved.execFileSync, "launchctl", ["kickstart", "-k", `gui/${uid}/${MAC_LABEL}`], true);
|
|
1485
|
+
return {
|
|
1486
|
+
platform: "darwin",
|
|
1487
|
+
supported: true,
|
|
1488
|
+
installed: true,
|
|
1489
|
+
location: plistPath,
|
|
1490
|
+
label: MAC_LABEL,
|
|
1491
|
+
command: entrypoint.command
|
|
1492
|
+
};
|
|
1493
|
+
};
|
|
1494
|
+
var uninstallMacAutostart = (deps = {}) => {
|
|
1495
|
+
const resolved = { ...defaultDeps(), ...deps };
|
|
1496
|
+
const plistPath = getLaunchAgentPath(resolved.homedir());
|
|
1497
|
+
const uid = resolved.uid;
|
|
1498
|
+
runCommand(resolved.execFileSync, "launchctl", ["bootout", `gui/${uid}`, plistPath], true);
|
|
1499
|
+
if (resolved.existsSync(plistPath)) {
|
|
1500
|
+
resolved.unlinkSync(plistPath);
|
|
1501
|
+
}
|
|
1502
|
+
return {
|
|
1503
|
+
platform: "darwin",
|
|
1504
|
+
supported: true,
|
|
1505
|
+
installed: false,
|
|
1506
|
+
location: plistPath,
|
|
1507
|
+
label: MAC_LABEL
|
|
1508
|
+
};
|
|
1509
|
+
};
|
|
1510
|
+
var isWindowsTaskInstalled = (deps = {}) => {
|
|
1511
|
+
const resolved = { ...defaultDeps(), ...deps };
|
|
1512
|
+
try {
|
|
1513
|
+
resolved.execFileSync("schtasks", ["/Query", "/TN", WIN_TASK_NAME], { stdio: "ignore" });
|
|
1514
|
+
return true;
|
|
1515
|
+
} catch {
|
|
1516
|
+
return false;
|
|
1517
|
+
}
|
|
1518
|
+
};
|
|
1519
|
+
var installWindowsAutostart = (deps = {}) => {
|
|
1520
|
+
const resolved = { ...defaultDeps(), ...deps };
|
|
1521
|
+
const entrypoint = resolveCliEntrypoint(resolved);
|
|
1522
|
+
const { args } = buildWindowsTaskArgs(entrypoint, WIN_TASK_NAME);
|
|
1523
|
+
runCommand(resolved.execFileSync, "schtasks", args);
|
|
1524
|
+
return {
|
|
1525
|
+
platform: "win32",
|
|
1526
|
+
supported: true,
|
|
1527
|
+
installed: true,
|
|
1528
|
+
taskName: WIN_TASK_NAME,
|
|
1529
|
+
command: entrypoint.command
|
|
1530
|
+
};
|
|
1531
|
+
};
|
|
1532
|
+
var uninstallWindowsAutostart = (deps = {}) => {
|
|
1533
|
+
const resolved = { ...defaultDeps(), ...deps };
|
|
1534
|
+
runCommand(resolved.execFileSync, "schtasks", ["/Delete", "/TN", WIN_TASK_NAME, "/F"], true);
|
|
1535
|
+
return {
|
|
1536
|
+
platform: "win32",
|
|
1537
|
+
supported: true,
|
|
1538
|
+
installed: false,
|
|
1539
|
+
taskName: WIN_TASK_NAME
|
|
1540
|
+
};
|
|
1541
|
+
};
|
|
1542
|
+
var getAutostartStatus = (deps = {}) => {
|
|
1543
|
+
const resolved = { ...defaultDeps(), ...deps };
|
|
1544
|
+
const platform = resolved.platform;
|
|
1545
|
+
if (platform === "darwin") {
|
|
1546
|
+
const location = getLaunchAgentPath(resolved.homedir());
|
|
1547
|
+
return {
|
|
1548
|
+
platform,
|
|
1549
|
+
supported: true,
|
|
1550
|
+
installed: resolved.existsSync(location),
|
|
1551
|
+
location,
|
|
1552
|
+
label: MAC_LABEL
|
|
1553
|
+
};
|
|
1554
|
+
}
|
|
1555
|
+
if (platform === "win32") {
|
|
1556
|
+
return {
|
|
1557
|
+
platform,
|
|
1558
|
+
supported: true,
|
|
1559
|
+
installed: isWindowsTaskInstalled(resolved),
|
|
1560
|
+
taskName: WIN_TASK_NAME
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
return {
|
|
1564
|
+
platform,
|
|
1565
|
+
supported: false,
|
|
1566
|
+
installed: false
|
|
1567
|
+
};
|
|
1568
|
+
};
|
|
1569
|
+
var installAutostart = (deps = {}) => {
|
|
1570
|
+
const platform = deps.platform ?? process.platform;
|
|
1571
|
+
if (platform === "darwin") {
|
|
1572
|
+
return installMacAutostart(deps);
|
|
1573
|
+
}
|
|
1574
|
+
if (platform === "win32") {
|
|
1575
|
+
return installWindowsAutostart(deps);
|
|
1576
|
+
}
|
|
1577
|
+
return {
|
|
1578
|
+
platform,
|
|
1579
|
+
supported: false,
|
|
1580
|
+
installed: false
|
|
1581
|
+
};
|
|
1582
|
+
};
|
|
1583
|
+
var uninstallAutostart = (deps = {}) => {
|
|
1584
|
+
const platform = deps.platform ?? process.platform;
|
|
1585
|
+
if (platform === "darwin") {
|
|
1586
|
+
return uninstallMacAutostart(deps);
|
|
1587
|
+
}
|
|
1588
|
+
if (platform === "win32") {
|
|
1589
|
+
return uninstallWindowsAutostart(deps);
|
|
1590
|
+
}
|
|
1591
|
+
return {
|
|
1592
|
+
platform,
|
|
1593
|
+
supported: false,
|
|
1594
|
+
installed: false
|
|
1595
|
+
};
|
|
1596
|
+
};
|
|
1597
|
+
|
|
1598
|
+
// src/cli/commands/daemon.ts
|
|
1599
|
+
var parseDaemonArgs = (rawArgs) => {
|
|
1600
|
+
const subcommand = rawArgs[0];
|
|
1601
|
+
if (subcommand === "install" || subcommand === "uninstall" || subcommand === "status") {
|
|
1602
|
+
return { subcommand };
|
|
1603
|
+
}
|
|
1604
|
+
throw createUsageError("Usage: opendevbrowser daemon <install|uninstall|status>");
|
|
1605
|
+
};
|
|
1606
|
+
var stopDaemonIfRunning = async () => {
|
|
1607
|
+
const metadata = readDaemonMetadata();
|
|
1608
|
+
if (!metadata) {
|
|
1609
|
+
return false;
|
|
1610
|
+
}
|
|
1611
|
+
try {
|
|
1612
|
+
const response = await fetchWithTimeout(`http://127.0.0.1:${metadata.port}/stop`, {
|
|
1613
|
+
method: "POST",
|
|
1614
|
+
headers: { Authorization: `Bearer ${metadata.token}` }
|
|
1615
|
+
});
|
|
1616
|
+
return response.ok;
|
|
1617
|
+
} catch {
|
|
1618
|
+
return false;
|
|
1619
|
+
}
|
|
1620
|
+
};
|
|
1621
|
+
var buildStatusMessage = (autostart, running) => {
|
|
1622
|
+
if (!autostart.supported) {
|
|
1623
|
+
return `Daemon autostart is not supported on ${autostart.platform}.`;
|
|
1624
|
+
}
|
|
1625
|
+
const installed = autostart.installed ? "installed" : "not installed";
|
|
1626
|
+
const runningText = running ? "running" : "not running";
|
|
1627
|
+
const location = autostart.location ? ` at ${autostart.location}` : "";
|
|
1628
|
+
const task = autostart.taskName ? ` (${autostart.taskName})` : "";
|
|
1629
|
+
return `Autostart ${installed}${location}${task}. Daemon is ${runningText}.`;
|
|
1630
|
+
};
|
|
1631
|
+
async function runDaemonCommand(args) {
|
|
1632
|
+
const { subcommand } = parseDaemonArgs(args.rawArgs);
|
|
1633
|
+
if (subcommand === "install") {
|
|
1634
|
+
const result = installAutostart();
|
|
1635
|
+
if (!result.supported) {
|
|
1636
|
+
return {
|
|
1637
|
+
success: false,
|
|
1638
|
+
message: `Daemon autostart is not supported on ${result.platform}.`,
|
|
1639
|
+
data: result,
|
|
1640
|
+
exitCode: EXIT_EXECUTION
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
return {
|
|
1644
|
+
success: true,
|
|
1645
|
+
message: `Daemon autostart installed (${result.platform}).`,
|
|
1646
|
+
data: result
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
if (subcommand === "uninstall") {
|
|
1650
|
+
const result = uninstallAutostart();
|
|
1651
|
+
if (!result.supported) {
|
|
1652
|
+
return {
|
|
1653
|
+
success: false,
|
|
1654
|
+
message: `Daemon autostart is not supported on ${result.platform}.`,
|
|
1655
|
+
data: result,
|
|
1656
|
+
exitCode: EXIT_EXECUTION
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
await stopDaemonIfRunning();
|
|
1660
|
+
return {
|
|
1661
|
+
success: true,
|
|
1662
|
+
message: `Daemon autostart removed (${result.platform}).`,
|
|
1663
|
+
data: result
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
const autostart = getAutostartStatus();
|
|
1667
|
+
const daemonStatus = await fetchDaemonStatusFromMetadata();
|
|
1668
|
+
const running = Boolean(daemonStatus);
|
|
1669
|
+
const message = buildStatusMessage(autostart, running);
|
|
1670
|
+
const data = {
|
|
1671
|
+
installed: autostart.installed,
|
|
1672
|
+
running,
|
|
1673
|
+
autostart: autostart.supported ? autostart : void 0
|
|
1674
|
+
};
|
|
1675
|
+
if (!running) {
|
|
1676
|
+
return {
|
|
1677
|
+
success: false,
|
|
1678
|
+
message,
|
|
1679
|
+
data,
|
|
1680
|
+
exitCode: EXIT_DISCONNECTED
|
|
1681
|
+
};
|
|
1682
|
+
}
|
|
1683
|
+
return {
|
|
1684
|
+
success: true,
|
|
1685
|
+
message,
|
|
1686
|
+
data: { ...data, status: daemonStatus }
|
|
1687
|
+
};
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
// src/cli/commands/run.ts
|
|
1691
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
1692
|
+
|
|
1693
|
+
// src/cli/output.ts
|
|
1694
|
+
function writeOutput(payload, options) {
|
|
1695
|
+
if (options.quiet) {
|
|
1696
|
+
return;
|
|
1697
|
+
}
|
|
1698
|
+
if (options.format === "text") {
|
|
1699
|
+
if (typeof payload === "string") {
|
|
1700
|
+
console.log(payload);
|
|
1701
|
+
} else {
|
|
1702
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
1703
|
+
}
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
if (options.format === "stream-json") {
|
|
1707
|
+
if (Array.isArray(payload)) {
|
|
1708
|
+
for (const entry of payload) {
|
|
1709
|
+
console.log(JSON.stringify(entry));
|
|
1710
|
+
}
|
|
1711
|
+
return;
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
console.log(JSON.stringify(payload));
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
// src/cli/commands/run.ts
|
|
1718
|
+
function parseRunArgs(rawArgs) {
|
|
1719
|
+
const parsed = { flags: [] };
|
|
1720
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
1721
|
+
const arg = rawArgs[i];
|
|
1722
|
+
if (arg === "--script") {
|
|
1723
|
+
const value = rawArgs[i + 1];
|
|
1724
|
+
if (!value) throw createUsageError("Missing value for --script");
|
|
1725
|
+
parsed.scriptPath = value;
|
|
1726
|
+
i += 1;
|
|
1727
|
+
continue;
|
|
1728
|
+
}
|
|
1729
|
+
if (arg?.startsWith("--script=")) {
|
|
1730
|
+
parsed.scriptPath = arg.split("=", 2)[1];
|
|
1731
|
+
continue;
|
|
1732
|
+
}
|
|
1733
|
+
if (arg === "--headless") {
|
|
1734
|
+
parsed.headless = true;
|
|
1735
|
+
continue;
|
|
1736
|
+
}
|
|
1737
|
+
if (arg === "--profile") {
|
|
1738
|
+
const value = rawArgs[i + 1];
|
|
1739
|
+
if (!value) throw createUsageError("Missing value for --profile");
|
|
1740
|
+
parsed.profile = value;
|
|
1741
|
+
i += 1;
|
|
1742
|
+
continue;
|
|
1743
|
+
}
|
|
1744
|
+
if (arg?.startsWith("--profile=")) {
|
|
1745
|
+
parsed.profile = arg.split("=", 2)[1];
|
|
1746
|
+
continue;
|
|
1747
|
+
}
|
|
1748
|
+
if (arg === "--persist-profile") {
|
|
1749
|
+
parsed.persistProfile = true;
|
|
1750
|
+
continue;
|
|
1751
|
+
}
|
|
1752
|
+
if (arg === "--chrome-path") {
|
|
1753
|
+
const value = rawArgs[i + 1];
|
|
1754
|
+
if (!value) throw createUsageError("Missing value for --chrome-path");
|
|
1755
|
+
parsed.chromePath = value;
|
|
1756
|
+
i += 1;
|
|
1757
|
+
continue;
|
|
1758
|
+
}
|
|
1759
|
+
if (arg?.startsWith("--chrome-path=")) {
|
|
1760
|
+
parsed.chromePath = arg.split("=", 2)[1];
|
|
1761
|
+
continue;
|
|
1762
|
+
}
|
|
1763
|
+
if (arg === "--start-url") {
|
|
1764
|
+
const value = rawArgs[i + 1];
|
|
1765
|
+
if (!value) throw createUsageError("Missing value for --start-url");
|
|
1766
|
+
parsed.startUrl = value;
|
|
1767
|
+
i += 1;
|
|
1768
|
+
continue;
|
|
1769
|
+
}
|
|
1770
|
+
if (arg?.startsWith("--start-url=")) {
|
|
1771
|
+
const value = arg.split("=", 2)[1];
|
|
1772
|
+
if (!value) throw createUsageError("Missing value for --start-url");
|
|
1773
|
+
parsed.startUrl = value;
|
|
1774
|
+
continue;
|
|
1775
|
+
}
|
|
1776
|
+
if (arg === "--flag") {
|
|
1777
|
+
const value = rawArgs[i + 1];
|
|
1778
|
+
if (!value) throw createUsageError("Missing value for --flag");
|
|
1779
|
+
parsed.flags.push(value);
|
|
1780
|
+
i += 1;
|
|
1781
|
+
continue;
|
|
1782
|
+
}
|
|
1783
|
+
if (arg?.startsWith("--flag=")) {
|
|
1784
|
+
const value = arg.split("=", 2)[1];
|
|
1785
|
+
if (!value) throw createUsageError("Missing value for --flag");
|
|
1786
|
+
parsed.flags.push(value);
|
|
1787
|
+
continue;
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
return parsed;
|
|
1791
|
+
}
|
|
1792
|
+
function readScriptFromStdin() {
|
|
1793
|
+
return new Promise((resolve4, reject) => {
|
|
1794
|
+
let data = "";
|
|
1795
|
+
process.stdin.setEncoding("utf8");
|
|
1796
|
+
process.stdin.on("data", (chunk) => {
|
|
1797
|
+
data += chunk;
|
|
1798
|
+
});
|
|
1799
|
+
process.stdin.on("end", () => resolve4(data));
|
|
1800
|
+
process.stdin.on("error", reject);
|
|
1801
|
+
});
|
|
1802
|
+
}
|
|
1803
|
+
async function runScriptCommand(args) {
|
|
1804
|
+
const runArgs = parseRunArgs(args.rawArgs);
|
|
1805
|
+
const outputOptions = { format: args.outputFormat, quiet: args.quiet };
|
|
1806
|
+
let scriptRaw = "";
|
|
1807
|
+
if (runArgs.scriptPath) {
|
|
1808
|
+
scriptRaw = readFileSync4(runArgs.scriptPath, "utf-8");
|
|
1809
|
+
} else if (!process.stdin.isTTY) {
|
|
1810
|
+
scriptRaw = await readScriptFromStdin();
|
|
1811
|
+
} else {
|
|
1812
|
+
throw createUsageError("Provide --script <path> or pipe JSON to stdin.");
|
|
1813
|
+
}
|
|
1814
|
+
let steps = [];
|
|
1815
|
+
try {
|
|
1816
|
+
const parsed = JSON.parse(scriptRaw);
|
|
1817
|
+
if (Array.isArray(parsed)) {
|
|
1818
|
+
steps = parsed;
|
|
1819
|
+
} else if (parsed && typeof parsed === "object" && Array.isArray(parsed.steps)) {
|
|
1820
|
+
steps = parsed.steps;
|
|
1821
|
+
} else {
|
|
1822
|
+
throw new Error("Script must be a JSON array or an object with steps.");
|
|
1823
|
+
}
|
|
1824
|
+
} catch (error) {
|
|
1825
|
+
const message = error instanceof Error ? error.message : "Invalid JSON script.";
|
|
1826
|
+
writeOutput({ success: false, error: message, exitCode: EXIT_USAGE }, outputOptions);
|
|
1827
|
+
return { success: false, message, exitCode: EXIT_USAGE, data: { suppressOutput: true } };
|
|
1828
|
+
}
|
|
1829
|
+
const core = createOpenDevBrowserCore({ directory: process.cwd() });
|
|
1830
|
+
const launchResult = await core.manager.launch({
|
|
1831
|
+
profile: runArgs.profile,
|
|
1832
|
+
headless: runArgs.headless,
|
|
1833
|
+
startUrl: runArgs.startUrl,
|
|
1834
|
+
chromePath: runArgs.chromePath,
|
|
1835
|
+
flags: runArgs.flags.length ? runArgs.flags : void 0,
|
|
1836
|
+
persistProfile: runArgs.persistProfile
|
|
1837
|
+
});
|
|
1838
|
+
try {
|
|
1839
|
+
const result = await core.runner.run(launchResult.sessionId, steps, true);
|
|
1840
|
+
writeOutput({
|
|
1841
|
+
success: true,
|
|
1842
|
+
sessionId: launchResult.sessionId,
|
|
1843
|
+
warnings: launchResult.warnings.length ? launchResult.warnings : void 0,
|
|
1844
|
+
...result
|
|
1845
|
+
}, outputOptions);
|
|
1846
|
+
return { success: true, data: { suppressOutput: true } };
|
|
1847
|
+
} finally {
|
|
1848
|
+
await core.manager.disconnect(launchResult.sessionId, true);
|
|
1849
|
+
core.cleanup();
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
// src/cli/commands/session/launch.ts
|
|
1854
|
+
function parseLaunchArgs(rawArgs) {
|
|
1855
|
+
const parsed = { flags: [] };
|
|
1856
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
1857
|
+
const arg = rawArgs[i];
|
|
1858
|
+
if (arg === "--headless") {
|
|
1859
|
+
parsed.headless = true;
|
|
1860
|
+
continue;
|
|
1861
|
+
}
|
|
1862
|
+
if (arg === "--profile") {
|
|
1863
|
+
const value = rawArgs[i + 1];
|
|
1864
|
+
if (!value) throw createUsageError("Missing value for --profile");
|
|
1865
|
+
parsed.profile = value;
|
|
1866
|
+
i += 1;
|
|
1867
|
+
continue;
|
|
1868
|
+
}
|
|
1869
|
+
if (arg?.startsWith("--profile=")) {
|
|
1870
|
+
parsed.profile = arg.split("=", 2)[1];
|
|
1871
|
+
continue;
|
|
1872
|
+
}
|
|
1873
|
+
if (arg === "--start-url") {
|
|
1874
|
+
const value = rawArgs[i + 1];
|
|
1875
|
+
if (!value) throw createUsageError("Missing value for --start-url");
|
|
1876
|
+
parsed.startUrl = value;
|
|
1877
|
+
i += 1;
|
|
1878
|
+
continue;
|
|
1879
|
+
}
|
|
1880
|
+
if (arg?.startsWith("--start-url=")) {
|
|
1881
|
+
parsed.startUrl = arg.split("=", 2)[1];
|
|
1882
|
+
continue;
|
|
1883
|
+
}
|
|
1884
|
+
if (arg === "--chrome-path") {
|
|
1885
|
+
const value = rawArgs[i + 1];
|
|
1886
|
+
if (!value) throw createUsageError("Missing value for --chrome-path");
|
|
1887
|
+
parsed.chromePath = value;
|
|
1888
|
+
i += 1;
|
|
1889
|
+
continue;
|
|
1890
|
+
}
|
|
1891
|
+
if (arg?.startsWith("--chrome-path=")) {
|
|
1892
|
+
parsed.chromePath = arg.split("=", 2)[1];
|
|
1893
|
+
continue;
|
|
1894
|
+
}
|
|
1895
|
+
if (arg === "--persist-profile") {
|
|
1896
|
+
parsed.persistProfile = true;
|
|
1897
|
+
continue;
|
|
1898
|
+
}
|
|
1899
|
+
if (arg === "--no-extension") {
|
|
1900
|
+
parsed.noExtension = true;
|
|
1901
|
+
continue;
|
|
1902
|
+
}
|
|
1903
|
+
if (arg === "--extension-only") {
|
|
1904
|
+
parsed.extensionOnly = true;
|
|
1905
|
+
continue;
|
|
1906
|
+
}
|
|
1907
|
+
if (arg === "--extension-legacy") {
|
|
1908
|
+
parsed.extensionLegacy = true;
|
|
1909
|
+
continue;
|
|
1910
|
+
}
|
|
1911
|
+
if (arg === "--wait-for-extension") {
|
|
1912
|
+
parsed.waitForExtension = true;
|
|
1913
|
+
continue;
|
|
1914
|
+
}
|
|
1915
|
+
if (arg === "--wait-timeout-ms") {
|
|
1916
|
+
const value = rawArgs[i + 1];
|
|
1917
|
+
if (!value) throw createUsageError("Missing value for --wait-timeout-ms");
|
|
1918
|
+
parsed.waitTimeoutMs = parseNumberFlag(value, "--wait-timeout-ms", { min: 1 });
|
|
1919
|
+
i += 1;
|
|
1920
|
+
continue;
|
|
1921
|
+
}
|
|
1922
|
+
if (arg?.startsWith("--wait-timeout-ms=")) {
|
|
1923
|
+
const value = arg.split("=", 2)[1];
|
|
1924
|
+
if (!value) throw createUsageError("Missing value for --wait-timeout-ms");
|
|
1925
|
+
parsed.waitTimeoutMs = parseNumberFlag(value, "--wait-timeout-ms", { min: 1 });
|
|
1926
|
+
continue;
|
|
1927
|
+
}
|
|
1928
|
+
if (arg === "--flag") {
|
|
1929
|
+
const value = rawArgs[i + 1];
|
|
1930
|
+
if (!value) throw createUsageError("Missing value for --flag");
|
|
1931
|
+
parsed.flags.push(value);
|
|
1932
|
+
i += 1;
|
|
1933
|
+
continue;
|
|
1934
|
+
}
|
|
1935
|
+
if (arg?.startsWith("--flag=")) {
|
|
1936
|
+
const value = arg.split("=", 2)[1];
|
|
1937
|
+
if (!value) throw createUsageError("Missing value for --flag");
|
|
1938
|
+
parsed.flags.push(value);
|
|
1939
|
+
continue;
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
return parsed;
|
|
1943
|
+
}
|
|
1944
|
+
async function runSessionLaunch(args) {
|
|
1945
|
+
const launchArgs = parseLaunchArgs(args.rawArgs);
|
|
1946
|
+
try {
|
|
1947
|
+
const result = await callDaemon("session.launch", launchArgs);
|
|
1948
|
+
return {
|
|
1949
|
+
success: true,
|
|
1950
|
+
message: `Session launched: ${result.sessionId}`,
|
|
1951
|
+
data: result
|
|
1952
|
+
};
|
|
1953
|
+
} catch (error) {
|
|
1954
|
+
if (args.noInteractive) {
|
|
1955
|
+
throw error;
|
|
1956
|
+
}
|
|
1957
|
+
const message = error instanceof Error ? error.message : "";
|
|
1958
|
+
const lower = message.toLowerCase();
|
|
1959
|
+
const isExtensionFailure = message.includes("Extension not connected") || message.includes("Extension relay connection failed") || lower.includes("unauthorized");
|
|
1960
|
+
if (!isExtensionFailure) {
|
|
1961
|
+
throw error;
|
|
1962
|
+
}
|
|
1963
|
+
const retry = await promptYesNo(
|
|
1964
|
+
lower.includes("unauthorized") ? "Relay token mismatch detected. Open the extension popup and click Connect to refresh pairing, then retry now?" : "Extension not connected. Open the extension popup and click Connect, then retry now?",
|
|
1965
|
+
false
|
|
1966
|
+
);
|
|
1967
|
+
if (retry) {
|
|
1968
|
+
try {
|
|
1969
|
+
const result = await callDaemon("session.launch", { ...launchArgs, waitForExtension: true });
|
|
1970
|
+
return {
|
|
1971
|
+
success: true,
|
|
1972
|
+
message: `Session launched: ${result.sessionId}`,
|
|
1973
|
+
data: result
|
|
1974
|
+
};
|
|
1975
|
+
} catch (retryError) {
|
|
1976
|
+
error = retryError;
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
const proceedManaged = await promptYesNo("Proceed with a managed session (headed)?", false);
|
|
1980
|
+
if (proceedManaged) {
|
|
1981
|
+
const useHeadless = await promptYesNo("Run headless instead?", false);
|
|
1982
|
+
const result = await callDaemon("session.launch", {
|
|
1983
|
+
...launchArgs,
|
|
1984
|
+
noExtension: true,
|
|
1985
|
+
headless: useHeadless ? true : false
|
|
1986
|
+
});
|
|
1987
|
+
return {
|
|
1988
|
+
success: true,
|
|
1989
|
+
message: `Session launched: ${result.sessionId}`,
|
|
1990
|
+
data: result
|
|
1991
|
+
};
|
|
1992
|
+
}
|
|
1993
|
+
const proceedCdp = await promptYesNo("Proceed with CDPConnect (requires Chrome --remote-debugging-port=9222)?", false);
|
|
1994
|
+
if (proceedCdp) {
|
|
1995
|
+
const result = await callDaemon("session.connect", {});
|
|
1996
|
+
return {
|
|
1997
|
+
success: true,
|
|
1998
|
+
message: `Session connected: ${result.sessionId}`,
|
|
1999
|
+
data: result
|
|
2000
|
+
};
|
|
2001
|
+
}
|
|
2002
|
+
throw error;
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
function promptYesNo(question, defaultYes) {
|
|
2006
|
+
if (!process.stdin.isTTY) {
|
|
2007
|
+
return Promise.resolve(false);
|
|
2008
|
+
}
|
|
2009
|
+
const suffix = defaultYes ? " [Y/n] " : " [y/N] ";
|
|
2010
|
+
return new Promise((resolve4) => {
|
|
2011
|
+
process.stdout.write(`${question}${suffix}`);
|
|
2012
|
+
process.stdin.setEncoding("utf8");
|
|
2013
|
+
process.stdin.once("data", (data) => {
|
|
2014
|
+
const input = data.toString().trim().toLowerCase();
|
|
2015
|
+
if (!input) {
|
|
2016
|
+
resolve4(defaultYes);
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
2019
|
+
resolve4(input === "y" || input === "yes");
|
|
2020
|
+
});
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
// src/cli/commands/session/connect.ts
|
|
2025
|
+
function parseConnectArgs(rawArgs) {
|
|
2026
|
+
const parsed = {};
|
|
2027
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
2028
|
+
const arg = rawArgs[i];
|
|
2029
|
+
if (arg === "--ws-endpoint") {
|
|
2030
|
+
const value = rawArgs[i + 1];
|
|
2031
|
+
if (!value) throw createUsageError("Missing value for --ws-endpoint");
|
|
2032
|
+
parsed.wsEndpoint = value;
|
|
2033
|
+
i += 1;
|
|
2034
|
+
continue;
|
|
2035
|
+
}
|
|
2036
|
+
if (arg?.startsWith("--ws-endpoint=")) {
|
|
2037
|
+
parsed.wsEndpoint = arg.split("=", 2)[1];
|
|
2038
|
+
continue;
|
|
2039
|
+
}
|
|
2040
|
+
if (arg === "--host") {
|
|
2041
|
+
const value = rawArgs[i + 1];
|
|
2042
|
+
if (!value) throw createUsageError("Missing value for --host");
|
|
2043
|
+
parsed.host = value;
|
|
2044
|
+
i += 1;
|
|
2045
|
+
continue;
|
|
2046
|
+
}
|
|
2047
|
+
if (arg?.startsWith("--host=")) {
|
|
2048
|
+
const value = arg.split("=", 2)[1];
|
|
2049
|
+
if (!value) throw createUsageError("Missing value for --host");
|
|
2050
|
+
parsed.host = value;
|
|
2051
|
+
continue;
|
|
2052
|
+
}
|
|
2053
|
+
if (arg === "--cdp-port") {
|
|
2054
|
+
const value = rawArgs[i + 1];
|
|
2055
|
+
if (!value) throw createUsageError("Missing value for --cdp-port");
|
|
2056
|
+
parsed.port = parseNumberFlag(value, "--cdp-port", { min: 1, max: 65535 });
|
|
2057
|
+
i += 1;
|
|
2058
|
+
continue;
|
|
2059
|
+
}
|
|
2060
|
+
if (arg?.startsWith("--cdp-port=")) {
|
|
2061
|
+
const value = arg.split("=", 2)[1];
|
|
2062
|
+
if (!value) throw createUsageError("Missing value for --cdp-port");
|
|
2063
|
+
parsed.port = parseNumberFlag(value, "--cdp-port", { min: 1, max: 65535 });
|
|
2064
|
+
continue;
|
|
2065
|
+
}
|
|
2066
|
+
if (arg === "--extension-legacy") {
|
|
2067
|
+
parsed.extensionLegacy = true;
|
|
2068
|
+
continue;
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
return parsed;
|
|
2072
|
+
}
|
|
2073
|
+
async function runSessionConnect(args) {
|
|
2074
|
+
const connectArgs = parseConnectArgs(args.rawArgs);
|
|
2075
|
+
const result = await callDaemon("session.connect", connectArgs);
|
|
2076
|
+
return {
|
|
2077
|
+
success: true,
|
|
2078
|
+
message: `Session connected: ${result.sessionId}`,
|
|
2079
|
+
data: result
|
|
2080
|
+
};
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
// src/cli/commands/session/disconnect.ts
|
|
2084
|
+
function parseDisconnectArgs(rawArgs) {
|
|
2085
|
+
const parsed = {};
|
|
2086
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
2087
|
+
const arg = rawArgs[i];
|
|
2088
|
+
if (arg === "--session-id") {
|
|
2089
|
+
const value = rawArgs[i + 1];
|
|
2090
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
2091
|
+
parsed.sessionId = value;
|
|
2092
|
+
i += 1;
|
|
2093
|
+
continue;
|
|
2094
|
+
}
|
|
2095
|
+
if (arg?.startsWith("--session-id=")) {
|
|
2096
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
2097
|
+
continue;
|
|
2098
|
+
}
|
|
2099
|
+
if (arg === "--close-browser") {
|
|
2100
|
+
parsed.closeBrowser = true;
|
|
2101
|
+
continue;
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
return parsed;
|
|
2105
|
+
}
|
|
2106
|
+
async function runSessionDisconnect(args) {
|
|
2107
|
+
const { sessionId, closeBrowser } = parseDisconnectArgs(args.rawArgs);
|
|
2108
|
+
if (!sessionId) {
|
|
2109
|
+
throw createUsageError("Missing --session-id");
|
|
2110
|
+
}
|
|
2111
|
+
await callDaemon("session.disconnect", { sessionId, closeBrowser }, { timeoutMs: 2e4 });
|
|
2112
|
+
return { success: true, message: `Session disconnected: ${sessionId}` };
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
// src/cli/commands/session/status.ts
|
|
2116
|
+
function parseStatusArgs(rawArgs) {
|
|
2117
|
+
const parsed = {};
|
|
2118
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
2119
|
+
const arg = rawArgs[i];
|
|
2120
|
+
if (arg === "--session-id") {
|
|
2121
|
+
const value = rawArgs[i + 1];
|
|
2122
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
2123
|
+
parsed.sessionId = value;
|
|
2124
|
+
i += 1;
|
|
2125
|
+
continue;
|
|
2126
|
+
}
|
|
2127
|
+
if (arg?.startsWith("--session-id=")) {
|
|
2128
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
2129
|
+
continue;
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
return parsed;
|
|
2133
|
+
}
|
|
2134
|
+
async function runSessionStatus(args) {
|
|
2135
|
+
const { sessionId } = parseStatusArgs(args.rawArgs);
|
|
2136
|
+
if (!sessionId) {
|
|
2137
|
+
throw createUsageError("Missing --session-id");
|
|
2138
|
+
}
|
|
2139
|
+
const result = await callDaemon("session.status", { sessionId });
|
|
2140
|
+
return { success: true, message: `Session status: ${sessionId}`, data: result };
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
// src/cli/commands/status.ts
|
|
2144
|
+
var parseStatusArgs2 = (rawArgs) => {
|
|
2145
|
+
const parsed = { daemon: false };
|
|
2146
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
2147
|
+
const arg = rawArgs[i];
|
|
2148
|
+
if (arg === "--daemon") {
|
|
2149
|
+
parsed.daemon = true;
|
|
2150
|
+
continue;
|
|
2151
|
+
}
|
|
2152
|
+
if (arg === "--session-id") {
|
|
2153
|
+
const value = rawArgs[i + 1];
|
|
2154
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
2155
|
+
parsed.sessionId = value;
|
|
2156
|
+
i += 1;
|
|
2157
|
+
continue;
|
|
2158
|
+
}
|
|
2159
|
+
if (arg?.startsWith("--session-id=")) {
|
|
2160
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
2161
|
+
continue;
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
return parsed;
|
|
2165
|
+
};
|
|
2166
|
+
async function runStatus(args) {
|
|
2167
|
+
const { sessionId, daemon } = parseStatusArgs2(args.rawArgs);
|
|
2168
|
+
if (sessionId && daemon) {
|
|
2169
|
+
throw createUsageError("Use --session-id or --daemon, not both.");
|
|
2170
|
+
}
|
|
2171
|
+
if (sessionId) {
|
|
2172
|
+
return runSessionStatus(args);
|
|
2173
|
+
}
|
|
2174
|
+
if (!daemon && args.transport === "native") {
|
|
2175
|
+
const nativeStatus2 = getNativeStatusSnapshot();
|
|
2176
|
+
if (!nativeStatus2.installed) {
|
|
2177
|
+
return {
|
|
2178
|
+
success: false,
|
|
2179
|
+
message: "Native host not installed.",
|
|
2180
|
+
data: nativeStatus2,
|
|
2181
|
+
exitCode: EXIT_DISCONNECTED
|
|
2182
|
+
};
|
|
2183
|
+
}
|
|
2184
|
+
return {
|
|
2185
|
+
success: true,
|
|
2186
|
+
message: nativeStatus2.extensionId ? `Native host installed for extension ${nativeStatus2.extensionId}.` : "Native host installed.",
|
|
2187
|
+
data: nativeStatus2
|
|
2188
|
+
};
|
|
2189
|
+
}
|
|
2190
|
+
const daemonStatus = await fetchDaemonStatusFromMetadata();
|
|
2191
|
+
if (!daemonStatus) {
|
|
2192
|
+
throw createUsageError("Daemon not running. Start with `opendevbrowser serve`.");
|
|
2193
|
+
}
|
|
2194
|
+
const nativeStatus = getNativeStatusSnapshot();
|
|
2195
|
+
const baseMessage = [
|
|
2196
|
+
`Daemon OK (pid=${daemonStatus.pid})`,
|
|
2197
|
+
`Relay: port=${daemonStatus.relay.port ?? "n/a"} ext=${daemonStatus.relay.extensionConnected ? "on" : "off"} handshake=${daemonStatus.relay.extensionHandshakeComplete ? "on" : "off"} cdp=${daemonStatus.relay.cdpConnected ? "on" : "off"} annotate=${daemonStatus.relay.annotationConnected ? "on" : "off"} ops=${daemonStatus.relay.opsConnected ? "on" : "off"} pairing=${daemonStatus.relay.pairingRequired ? "on" : "off"} health=${daemonStatus.relay.health?.reason ?? "n/a"}`,
|
|
2198
|
+
`Native: ${nativeStatus.installed ? "installed" : "not installed"}${nativeStatus.extensionId ? ` (${nativeStatus.extensionId})` : ""}`,
|
|
2199
|
+
daemonStatus.relay.lastHandshakeError ? `Relay last handshake error: ${daemonStatus.relay.lastHandshakeError.code} (${daemonStatus.relay.lastHandshakeError.message})` : "Relay last handshake error: none",
|
|
2200
|
+
"Legend: ext=extension websocket, handshake=extension handshake, cdp=active /cdp client, annotate=annotation channel, ops=ops clients, pairing=token required, health=relay status"
|
|
2201
|
+
].join("\n");
|
|
2202
|
+
const message = daemon || args.outputFormat !== "text" ? baseMessage : [
|
|
2203
|
+
"Warning: `status` defaults to daemon status. Use --daemon explicitly or --session-id for session status.",
|
|
2204
|
+
baseMessage
|
|
2205
|
+
].join("\n");
|
|
2206
|
+
return {
|
|
2207
|
+
success: true,
|
|
2208
|
+
message,
|
|
2209
|
+
data: { ...daemonStatus, native: nativeStatus }
|
|
2210
|
+
};
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
// src/cli/commands/nav/goto.ts
|
|
2214
|
+
function parseGotoArgs(rawArgs) {
|
|
2215
|
+
const parsed = {};
|
|
2216
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
2217
|
+
const arg = rawArgs[i];
|
|
2218
|
+
if (arg === "--session-id") {
|
|
2219
|
+
const value = rawArgs[i + 1];
|
|
2220
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
2221
|
+
parsed.sessionId = value;
|
|
2222
|
+
i += 1;
|
|
2223
|
+
continue;
|
|
2224
|
+
}
|
|
2225
|
+
if (arg?.startsWith("--session-id=")) {
|
|
2226
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
2227
|
+
continue;
|
|
2228
|
+
}
|
|
2229
|
+
if (arg === "--url") {
|
|
2230
|
+
const value = rawArgs[i + 1];
|
|
2231
|
+
if (!value) throw createUsageError("Missing value for --url");
|
|
2232
|
+
parsed.url = value;
|
|
2233
|
+
i += 1;
|
|
2234
|
+
continue;
|
|
2235
|
+
}
|
|
2236
|
+
if (arg?.startsWith("--url=")) {
|
|
2237
|
+
parsed.url = arg.split("=", 2)[1];
|
|
2238
|
+
continue;
|
|
2239
|
+
}
|
|
2240
|
+
if (arg === "--wait-until") {
|
|
2241
|
+
const value = rawArgs[i + 1];
|
|
2242
|
+
if (!value) throw createUsageError("Missing value for --wait-until");
|
|
2243
|
+
parsed.waitUntil = value;
|
|
2244
|
+
i += 1;
|
|
2245
|
+
continue;
|
|
2246
|
+
}
|
|
2247
|
+
if (arg?.startsWith("--wait-until=")) {
|
|
2248
|
+
parsed.waitUntil = arg.split("=", 2)[1];
|
|
2249
|
+
continue;
|
|
2250
|
+
}
|
|
2251
|
+
if (arg === "--timeout-ms") {
|
|
2252
|
+
const value = rawArgs[i + 1];
|
|
2253
|
+
if (!value) throw createUsageError("Missing value for --timeout-ms");
|
|
2254
|
+
parsed.timeoutMs = parseNumberFlag(value, "--timeout-ms", { min: 1 });
|
|
2255
|
+
i += 1;
|
|
2256
|
+
continue;
|
|
2257
|
+
}
|
|
2258
|
+
if (arg?.startsWith("--timeout-ms=")) {
|
|
2259
|
+
const value = arg.split("=", 2)[1];
|
|
2260
|
+
if (!value) throw createUsageError("Missing value for --timeout-ms");
|
|
2261
|
+
parsed.timeoutMs = parseNumberFlag(value, "--timeout-ms", { min: 1 });
|
|
2262
|
+
continue;
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
return parsed;
|
|
2266
|
+
}
|
|
2267
|
+
async function runGoto(args) {
|
|
2268
|
+
const { sessionId, url, waitUntil, timeoutMs } = parseGotoArgs(args.rawArgs);
|
|
2269
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
2270
|
+
if (!url) throw createUsageError("Missing --url");
|
|
2271
|
+
const result = await callDaemon("nav.goto", { sessionId, url, waitUntil, timeoutMs });
|
|
2272
|
+
return { success: true, message: `Navigated: ${url}`, data: result };
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
// src/cli/commands/nav/wait.ts
|
|
2276
|
+
function parseWaitArgs(rawArgs) {
|
|
2277
|
+
const parsed = {};
|
|
2278
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
2279
|
+
const arg = rawArgs[i];
|
|
2280
|
+
if (arg === "--session-id") {
|
|
2281
|
+
const value = rawArgs[i + 1];
|
|
2282
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
2283
|
+
parsed.sessionId = value;
|
|
2284
|
+
i += 1;
|
|
2285
|
+
continue;
|
|
2286
|
+
}
|
|
2287
|
+
if (arg?.startsWith("--session-id=")) {
|
|
2288
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
2289
|
+
continue;
|
|
2290
|
+
}
|
|
2291
|
+
if (arg === "--ref") {
|
|
2292
|
+
const value = rawArgs[i + 1];
|
|
2293
|
+
if (!value) throw createUsageError("Missing value for --ref");
|
|
2294
|
+
parsed.ref = value;
|
|
2295
|
+
i += 1;
|
|
2296
|
+
continue;
|
|
2297
|
+
}
|
|
2298
|
+
if (arg?.startsWith("--ref=")) {
|
|
2299
|
+
parsed.ref = arg.split("=", 2)[1];
|
|
2300
|
+
continue;
|
|
2301
|
+
}
|
|
2302
|
+
if (arg === "--state") {
|
|
2303
|
+
const value = rawArgs[i + 1];
|
|
2304
|
+
if (!value) throw createUsageError("Missing value for --state");
|
|
2305
|
+
parsed.state = value;
|
|
2306
|
+
i += 1;
|
|
2307
|
+
continue;
|
|
2308
|
+
}
|
|
2309
|
+
if (arg?.startsWith("--state=")) {
|
|
2310
|
+
parsed.state = arg.split("=", 2)[1];
|
|
2311
|
+
continue;
|
|
2312
|
+
}
|
|
2313
|
+
if (arg === "--until") {
|
|
2314
|
+
const value = rawArgs[i + 1];
|
|
2315
|
+
if (!value) throw createUsageError("Missing value for --until");
|
|
2316
|
+
parsed.until = value;
|
|
2317
|
+
i += 1;
|
|
2318
|
+
continue;
|
|
2319
|
+
}
|
|
2320
|
+
if (arg?.startsWith("--until=")) {
|
|
2321
|
+
parsed.until = arg.split("=", 2)[1];
|
|
2322
|
+
continue;
|
|
2323
|
+
}
|
|
2324
|
+
if (arg === "--timeout-ms") {
|
|
2325
|
+
const value = rawArgs[i + 1];
|
|
2326
|
+
if (!value) throw createUsageError("Missing value for --timeout-ms");
|
|
2327
|
+
parsed.timeoutMs = parseNumberFlag(value, "--timeout-ms", { min: 1 });
|
|
2328
|
+
i += 1;
|
|
2329
|
+
continue;
|
|
2330
|
+
}
|
|
2331
|
+
if (arg?.startsWith("--timeout-ms=")) {
|
|
2332
|
+
const value = arg.split("=", 2)[1];
|
|
2333
|
+
if (!value) throw createUsageError("Missing value for --timeout-ms");
|
|
2334
|
+
parsed.timeoutMs = parseNumberFlag(value, "--timeout-ms", { min: 1 });
|
|
2335
|
+
continue;
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
return parsed;
|
|
2339
|
+
}
|
|
2340
|
+
async function runWait(args) {
|
|
2341
|
+
const { sessionId, ref, state, until, timeoutMs } = parseWaitArgs(args.rawArgs);
|
|
2342
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
2343
|
+
const result = await callDaemon("nav.wait", { sessionId, ref, state, until, timeoutMs });
|
|
2344
|
+
return { success: true, message: "Wait complete.", data: result };
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
// src/cli/commands/nav/snapshot.ts
|
|
2348
|
+
function parseSnapshotArgs(rawArgs) {
|
|
2349
|
+
const parsed = {};
|
|
2350
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
2351
|
+
const arg = rawArgs[i];
|
|
2352
|
+
if (arg === "--session-id") {
|
|
2353
|
+
const value = rawArgs[i + 1];
|
|
2354
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
2355
|
+
parsed.sessionId = value;
|
|
2356
|
+
i += 1;
|
|
2357
|
+
continue;
|
|
2358
|
+
}
|
|
2359
|
+
if (arg?.startsWith("--session-id=")) {
|
|
2360
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
2361
|
+
continue;
|
|
2362
|
+
}
|
|
2363
|
+
if (arg === "--mode") {
|
|
2364
|
+
const value = rawArgs[i + 1];
|
|
2365
|
+
if (!value) throw createUsageError("Missing value for --mode");
|
|
2366
|
+
parsed.mode = value;
|
|
2367
|
+
i += 1;
|
|
2368
|
+
continue;
|
|
2369
|
+
}
|
|
2370
|
+
if (arg?.startsWith("--mode=")) {
|
|
2371
|
+
parsed.mode = arg.split("=", 2)[1];
|
|
2372
|
+
continue;
|
|
2373
|
+
}
|
|
2374
|
+
if (arg === "--max-chars") {
|
|
2375
|
+
const value = rawArgs[i + 1];
|
|
2376
|
+
if (!value) throw createUsageError("Missing value for --max-chars");
|
|
2377
|
+
parsed.maxChars = Number(value);
|
|
2378
|
+
i += 1;
|
|
2379
|
+
continue;
|
|
2380
|
+
}
|
|
2381
|
+
if (arg?.startsWith("--max-chars=")) {
|
|
2382
|
+
parsed.maxChars = Number(arg.split("=", 2)[1]);
|
|
2383
|
+
continue;
|
|
2384
|
+
}
|
|
2385
|
+
if (arg === "--cursor") {
|
|
2386
|
+
const value = rawArgs[i + 1];
|
|
2387
|
+
if (!value) throw createUsageError("Missing value for --cursor");
|
|
2388
|
+
parsed.cursor = value;
|
|
2389
|
+
i += 1;
|
|
2390
|
+
continue;
|
|
2391
|
+
}
|
|
2392
|
+
if (arg?.startsWith("--cursor=")) {
|
|
2393
|
+
parsed.cursor = arg.split("=", 2)[1];
|
|
2394
|
+
continue;
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
return parsed;
|
|
2398
|
+
}
|
|
2399
|
+
async function runSnapshot(args) {
|
|
2400
|
+
const { sessionId, mode, maxChars, cursor } = parseSnapshotArgs(args.rawArgs);
|
|
2401
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
2402
|
+
const result = await callDaemon("nav.snapshot", { sessionId, mode, maxChars, cursor });
|
|
2403
|
+
return { success: true, message: "Snapshot captured.", data: result };
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
// src/cli/commands/annotate.ts
|
|
2407
|
+
var requireValue = (value, flag) => {
|
|
2408
|
+
if (!value) throw createUsageError(`Missing value for ${flag}`);
|
|
2409
|
+
return value;
|
|
2410
|
+
};
|
|
2411
|
+
var requireScreenshotMode = (value) => {
|
|
2412
|
+
if (value === "visible" || value === "full" || value === "none") {
|
|
2413
|
+
return value;
|
|
2414
|
+
}
|
|
2415
|
+
throw createUsageError(`Invalid --screenshot-mode: ${value}`);
|
|
2416
|
+
};
|
|
2417
|
+
var requireTransport = (value) => {
|
|
2418
|
+
if (value === "auto" || value === "direct" || value === "relay") {
|
|
2419
|
+
return value;
|
|
2420
|
+
}
|
|
2421
|
+
throw createUsageError(`Invalid --transport: ${value}`);
|
|
2422
|
+
};
|
|
2423
|
+
var parseAnnotateArgs = (rawArgs) => {
|
|
2424
|
+
const parsed = {};
|
|
2425
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
2426
|
+
const arg = rawArgs[i];
|
|
2427
|
+
if (arg === "--session-id") {
|
|
2428
|
+
const value = requireValue(rawArgs[i + 1], "--session-id");
|
|
2429
|
+
parsed.sessionId = value;
|
|
2430
|
+
i += 1;
|
|
2431
|
+
continue;
|
|
2432
|
+
}
|
|
2433
|
+
if (arg?.startsWith("--session-id=")) {
|
|
2434
|
+
const value = requireValue(arg.split("=", 2)[1], "--session-id");
|
|
2435
|
+
parsed.sessionId = value;
|
|
2436
|
+
continue;
|
|
2437
|
+
}
|
|
2438
|
+
if (arg === "--url") {
|
|
2439
|
+
const value = requireValue(rawArgs[i + 1], "--url");
|
|
2440
|
+
parsed.url = value;
|
|
2441
|
+
i += 1;
|
|
2442
|
+
continue;
|
|
2443
|
+
}
|
|
2444
|
+
if (arg?.startsWith("--url=")) {
|
|
2445
|
+
const value = requireValue(arg.split("=", 2)[1], "--url");
|
|
2446
|
+
parsed.url = value;
|
|
2447
|
+
continue;
|
|
2448
|
+
}
|
|
2449
|
+
if (arg === "--screenshot-mode") {
|
|
2450
|
+
const value = requireValue(rawArgs[i + 1], "--screenshot-mode");
|
|
2451
|
+
parsed.screenshotMode = requireScreenshotMode(value);
|
|
2452
|
+
i += 1;
|
|
2453
|
+
continue;
|
|
2454
|
+
}
|
|
2455
|
+
if (arg?.startsWith("--screenshot-mode=")) {
|
|
2456
|
+
const value = requireValue(arg.split("=", 2)[1], "--screenshot-mode");
|
|
2457
|
+
parsed.screenshotMode = requireScreenshotMode(value);
|
|
2458
|
+
continue;
|
|
2459
|
+
}
|
|
2460
|
+
if (arg === "--transport") {
|
|
2461
|
+
const value = requireValue(rawArgs[i + 1], "--transport");
|
|
2462
|
+
parsed.transport = requireTransport(value);
|
|
2463
|
+
i += 1;
|
|
2464
|
+
continue;
|
|
2465
|
+
}
|
|
2466
|
+
if (arg?.startsWith("--transport=")) {
|
|
2467
|
+
const value = requireValue(arg.split("=", 2)[1], "--transport");
|
|
2468
|
+
parsed.transport = requireTransport(value);
|
|
2469
|
+
continue;
|
|
2470
|
+
}
|
|
2471
|
+
if (arg === "--target-id") {
|
|
2472
|
+
const value = requireValue(rawArgs[i + 1], "--target-id");
|
|
2473
|
+
parsed.targetId = value;
|
|
2474
|
+
i += 1;
|
|
2475
|
+
continue;
|
|
2476
|
+
}
|
|
2477
|
+
if (arg?.startsWith("--target-id=")) {
|
|
2478
|
+
const value = requireValue(arg.split("=", 2)[1], "--target-id");
|
|
2479
|
+
parsed.targetId = value;
|
|
2480
|
+
continue;
|
|
2481
|
+
}
|
|
2482
|
+
if (arg === "--tab-id") {
|
|
2483
|
+
const value = requireValue(rawArgs[i + 1], "--tab-id");
|
|
2484
|
+
parsed.tabId = parseNumberFlag(value, "--tab-id", { min: 1 });
|
|
2485
|
+
i += 1;
|
|
2486
|
+
continue;
|
|
2487
|
+
}
|
|
2488
|
+
if (arg?.startsWith("--tab-id=")) {
|
|
2489
|
+
const value = requireValue(arg.split("=", 2)[1], "--tab-id");
|
|
2490
|
+
parsed.tabId = parseNumberFlag(value, "--tab-id", { min: 1 });
|
|
2491
|
+
continue;
|
|
2492
|
+
}
|
|
2493
|
+
if (arg === "--debug") {
|
|
2494
|
+
parsed.debug = true;
|
|
2495
|
+
continue;
|
|
2496
|
+
}
|
|
2497
|
+
if (arg === "--context") {
|
|
2498
|
+
const value = requireValue(rawArgs[i + 1], "--context");
|
|
2499
|
+
parsed.context = value;
|
|
2500
|
+
i += 1;
|
|
2501
|
+
continue;
|
|
2502
|
+
}
|
|
2503
|
+
if (arg?.startsWith("--context=")) {
|
|
2504
|
+
const value = requireValue(arg.split("=", 2)[1], "--context");
|
|
2505
|
+
parsed.context = value;
|
|
2506
|
+
continue;
|
|
2507
|
+
}
|
|
2508
|
+
if (arg === "--timeout-ms") {
|
|
2509
|
+
const value = requireValue(rawArgs[i + 1], "--timeout-ms");
|
|
2510
|
+
parsed.timeoutMs = parseNumberFlag(value, "--timeout-ms", { min: 1 });
|
|
2511
|
+
i += 1;
|
|
2512
|
+
continue;
|
|
2513
|
+
}
|
|
2514
|
+
if (arg?.startsWith("--timeout-ms=")) {
|
|
2515
|
+
const value = requireValue(arg.split("=", 2)[1], "--timeout-ms");
|
|
2516
|
+
parsed.timeoutMs = parseNumberFlag(value, "--timeout-ms", { min: 1 });
|
|
2517
|
+
continue;
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
return parsed;
|
|
2521
|
+
};
|
|
2522
|
+
async function runAnnotate(args) {
|
|
2523
|
+
const { sessionId, url, screenshotMode, debug, context, timeoutMs, transport, targetId, tabId } = parseAnnotateArgs(args.rawArgs);
|
|
2524
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
2525
|
+
const client = new DaemonClient({ autoRenew: true });
|
|
2526
|
+
const callTimeoutMs = typeof timeoutMs === "number" ? timeoutMs + 1e4 : void 0;
|
|
2527
|
+
try {
|
|
2528
|
+
const response = await client.call("annotate", {
|
|
2529
|
+
sessionId,
|
|
2530
|
+
transport,
|
|
2531
|
+
targetId,
|
|
2532
|
+
tabId,
|
|
2533
|
+
url,
|
|
2534
|
+
screenshotMode,
|
|
2535
|
+
debug,
|
|
2536
|
+
context,
|
|
2537
|
+
timeoutMs
|
|
2538
|
+
}, { timeoutMs: callTimeoutMs });
|
|
2539
|
+
if (response.status !== "ok" || !response.payload) {
|
|
2540
|
+
const message2 = response.error?.message ?? "Annotation failed.";
|
|
2541
|
+
throw new Error(message2);
|
|
2542
|
+
}
|
|
2543
|
+
const { message, details, screenshots } = await buildAnnotateResult(response.payload);
|
|
2544
|
+
return { success: true, message, data: { details, screenshots } };
|
|
2545
|
+
} finally {
|
|
2546
|
+
await client.releaseBinding().catch(() => {
|
|
2547
|
+
});
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
// src/cli/commands/interact/click.ts
|
|
2552
|
+
function parseClickArgs(rawArgs) {
|
|
2553
|
+
const parsed = {};
|
|
2554
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
2555
|
+
const arg = rawArgs[i];
|
|
2556
|
+
if (arg === "--session-id") {
|
|
2557
|
+
const value = rawArgs[i + 1];
|
|
2558
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
2559
|
+
parsed.sessionId = value;
|
|
2560
|
+
i += 1;
|
|
2561
|
+
continue;
|
|
2562
|
+
}
|
|
2563
|
+
if (arg?.startsWith("--session-id=")) {
|
|
2564
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
2565
|
+
continue;
|
|
2566
|
+
}
|
|
2567
|
+
if (arg === "--ref") {
|
|
2568
|
+
const value = rawArgs[i + 1];
|
|
2569
|
+
if (!value) throw createUsageError("Missing value for --ref");
|
|
2570
|
+
parsed.ref = value;
|
|
2571
|
+
i += 1;
|
|
2572
|
+
continue;
|
|
2573
|
+
}
|
|
2574
|
+
if (arg?.startsWith("--ref=")) {
|
|
2575
|
+
parsed.ref = arg.split("=", 2)[1];
|
|
2576
|
+
continue;
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
return parsed;
|
|
2580
|
+
}
|
|
2581
|
+
async function runClick(args) {
|
|
2582
|
+
const { sessionId, ref } = parseClickArgs(args.rawArgs);
|
|
2583
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
2584
|
+
if (!ref) throw createUsageError("Missing --ref");
|
|
2585
|
+
const result = await callDaemon("interact.click", { sessionId, ref });
|
|
2586
|
+
return { success: true, message: "Click complete.", data: result };
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
// src/cli/commands/interact/hover.ts
|
|
2590
|
+
function parseHoverArgs(rawArgs) {
|
|
2591
|
+
const parsed = {};
|
|
2592
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
2593
|
+
const arg = rawArgs[i];
|
|
2594
|
+
if (arg === "--session-id") {
|
|
2595
|
+
const value = rawArgs[i + 1];
|
|
2596
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
2597
|
+
parsed.sessionId = value;
|
|
2598
|
+
i += 1;
|
|
2599
|
+
continue;
|
|
2600
|
+
}
|
|
2601
|
+
if (arg?.startsWith("--session-id=")) {
|
|
2602
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
2603
|
+
continue;
|
|
2604
|
+
}
|
|
2605
|
+
if (arg === "--ref") {
|
|
2606
|
+
const value = rawArgs[i + 1];
|
|
2607
|
+
if (!value) throw createUsageError("Missing value for --ref");
|
|
2608
|
+
parsed.ref = value;
|
|
2609
|
+
i += 1;
|
|
2610
|
+
continue;
|
|
2611
|
+
}
|
|
2612
|
+
if (arg?.startsWith("--ref=")) {
|
|
2613
|
+
parsed.ref = arg.split("=", 2)[1];
|
|
2614
|
+
continue;
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
return parsed;
|
|
2618
|
+
}
|
|
2619
|
+
async function runHover(args) {
|
|
2620
|
+
const { sessionId, ref } = parseHoverArgs(args.rawArgs);
|
|
2621
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
2622
|
+
if (!ref) throw createUsageError("Missing --ref");
|
|
2623
|
+
const result = await callDaemon("interact.hover", { sessionId, ref });
|
|
2624
|
+
return { success: true, message: "Hover complete.", data: result };
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2627
|
+
// src/cli/commands/interact/press.ts
|
|
2628
|
+
function parsePressArgs(rawArgs) {
|
|
2629
|
+
const parsed = {};
|
|
2630
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
2631
|
+
const arg = rawArgs[i];
|
|
2632
|
+
if (arg === "--session-id") {
|
|
2633
|
+
const value = rawArgs[i + 1];
|
|
2634
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
2635
|
+
parsed.sessionId = value;
|
|
2636
|
+
i += 1;
|
|
2637
|
+
continue;
|
|
2638
|
+
}
|
|
2639
|
+
if (arg?.startsWith("--session-id=")) {
|
|
2640
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
2641
|
+
continue;
|
|
2642
|
+
}
|
|
2643
|
+
if (arg === "--key") {
|
|
2644
|
+
const value = rawArgs[i + 1];
|
|
2645
|
+
if (!value) throw createUsageError("Missing value for --key");
|
|
2646
|
+
parsed.key = value;
|
|
2647
|
+
i += 1;
|
|
2648
|
+
continue;
|
|
2649
|
+
}
|
|
2650
|
+
if (arg?.startsWith("--key=")) {
|
|
2651
|
+
parsed.key = arg.split("=", 2)[1];
|
|
2652
|
+
continue;
|
|
2653
|
+
}
|
|
2654
|
+
if (arg === "--ref") {
|
|
2655
|
+
const value = rawArgs[i + 1];
|
|
2656
|
+
if (!value) throw createUsageError("Missing value for --ref");
|
|
2657
|
+
parsed.ref = value;
|
|
2658
|
+
i += 1;
|
|
2659
|
+
continue;
|
|
2660
|
+
}
|
|
2661
|
+
if (arg?.startsWith("--ref=")) {
|
|
2662
|
+
parsed.ref = arg.split("=", 2)[1];
|
|
2663
|
+
continue;
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
return parsed;
|
|
2667
|
+
}
|
|
2668
|
+
async function runPress(args) {
|
|
2669
|
+
const { sessionId, key, ref } = parsePressArgs(args.rawArgs);
|
|
2670
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
2671
|
+
if (!key) throw createUsageError("Missing --key");
|
|
2672
|
+
const result = await callDaemon("interact.press", { sessionId, key, ref });
|
|
2673
|
+
return { success: true, message: "Key press complete.", data: result };
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
// src/cli/commands/interact/check.ts
|
|
2677
|
+
function parseCheckArgs(rawArgs) {
|
|
2678
|
+
const parsed = {};
|
|
2679
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
2680
|
+
const arg = rawArgs[i];
|
|
2681
|
+
if (arg === "--session-id") {
|
|
2682
|
+
const value = rawArgs[i + 1];
|
|
2683
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
2684
|
+
parsed.sessionId = value;
|
|
2685
|
+
i += 1;
|
|
2686
|
+
continue;
|
|
2687
|
+
}
|
|
2688
|
+
if (arg?.startsWith("--session-id=")) {
|
|
2689
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
2690
|
+
continue;
|
|
2691
|
+
}
|
|
2692
|
+
if (arg === "--ref") {
|
|
2693
|
+
const value = rawArgs[i + 1];
|
|
2694
|
+
if (!value) throw createUsageError("Missing value for --ref");
|
|
2695
|
+
parsed.ref = value;
|
|
2696
|
+
i += 1;
|
|
2697
|
+
continue;
|
|
2698
|
+
}
|
|
2699
|
+
if (arg?.startsWith("--ref=")) {
|
|
2700
|
+
parsed.ref = arg.split("=", 2)[1];
|
|
2701
|
+
continue;
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
return parsed;
|
|
2705
|
+
}
|
|
2706
|
+
async function runCheck(args) {
|
|
2707
|
+
const { sessionId, ref } = parseCheckArgs(args.rawArgs);
|
|
2708
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
2709
|
+
if (!ref) throw createUsageError("Missing --ref");
|
|
2710
|
+
const result = await callDaemon("interact.check", { sessionId, ref });
|
|
2711
|
+
return { success: true, message: "Check complete.", data: result };
|
|
2712
|
+
}
|
|
2713
|
+
|
|
2714
|
+
// src/cli/commands/interact/uncheck.ts
|
|
2715
|
+
function parseUncheckArgs(rawArgs) {
|
|
2716
|
+
const parsed = {};
|
|
2717
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
2718
|
+
const arg = rawArgs[i];
|
|
2719
|
+
if (arg === "--session-id") {
|
|
2720
|
+
const value = rawArgs[i + 1];
|
|
2721
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
2722
|
+
parsed.sessionId = value;
|
|
2723
|
+
i += 1;
|
|
2724
|
+
continue;
|
|
2725
|
+
}
|
|
2726
|
+
if (arg?.startsWith("--session-id=")) {
|
|
2727
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
2728
|
+
continue;
|
|
2729
|
+
}
|
|
2730
|
+
if (arg === "--ref") {
|
|
2731
|
+
const value = rawArgs[i + 1];
|
|
2732
|
+
if (!value) throw createUsageError("Missing value for --ref");
|
|
2733
|
+
parsed.ref = value;
|
|
2734
|
+
i += 1;
|
|
2735
|
+
continue;
|
|
2736
|
+
}
|
|
2737
|
+
if (arg?.startsWith("--ref=")) {
|
|
2738
|
+
parsed.ref = arg.split("=", 2)[1];
|
|
2739
|
+
continue;
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
return parsed;
|
|
2743
|
+
}
|
|
2744
|
+
async function runUncheck(args) {
|
|
2745
|
+
const { sessionId, ref } = parseUncheckArgs(args.rawArgs);
|
|
2746
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
2747
|
+
if (!ref) throw createUsageError("Missing --ref");
|
|
2748
|
+
const result = await callDaemon("interact.uncheck", { sessionId, ref });
|
|
2749
|
+
return { success: true, message: "Uncheck complete.", data: result };
|
|
2750
|
+
}
|
|
2751
|
+
|
|
2752
|
+
// src/cli/commands/interact/type.ts
|
|
2753
|
+
function parseTypeArgs(rawArgs) {
|
|
2754
|
+
const parsed = {};
|
|
2755
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
2756
|
+
const arg = rawArgs[i];
|
|
2757
|
+
if (arg === "--session-id") {
|
|
2758
|
+
const value = rawArgs[i + 1];
|
|
2759
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
2760
|
+
parsed.sessionId = value;
|
|
2761
|
+
i += 1;
|
|
2762
|
+
continue;
|
|
2763
|
+
}
|
|
2764
|
+
if (arg?.startsWith("--session-id=")) {
|
|
2765
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
2766
|
+
continue;
|
|
2767
|
+
}
|
|
2768
|
+
if (arg === "--ref") {
|
|
2769
|
+
const value = rawArgs[i + 1];
|
|
2770
|
+
if (!value) throw createUsageError("Missing value for --ref");
|
|
2771
|
+
parsed.ref = value;
|
|
2772
|
+
i += 1;
|
|
2773
|
+
continue;
|
|
2774
|
+
}
|
|
2775
|
+
if (arg?.startsWith("--ref=")) {
|
|
2776
|
+
parsed.ref = arg.split("=", 2)[1];
|
|
2777
|
+
continue;
|
|
2778
|
+
}
|
|
2779
|
+
if (arg === "--text") {
|
|
2780
|
+
const value = rawArgs[i + 1];
|
|
2781
|
+
if (!value) throw createUsageError("Missing value for --text");
|
|
2782
|
+
parsed.text = value;
|
|
2783
|
+
i += 1;
|
|
2784
|
+
continue;
|
|
2785
|
+
}
|
|
2786
|
+
if (arg?.startsWith("--text=")) {
|
|
2787
|
+
parsed.text = arg.split("=", 2)[1];
|
|
2788
|
+
continue;
|
|
2789
|
+
}
|
|
2790
|
+
if (arg === "--clear") {
|
|
2791
|
+
parsed.clear = true;
|
|
2792
|
+
continue;
|
|
2793
|
+
}
|
|
2794
|
+
if (arg === "--submit") {
|
|
2795
|
+
parsed.submit = true;
|
|
2796
|
+
continue;
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
return parsed;
|
|
2800
|
+
}
|
|
2801
|
+
async function runType(args) {
|
|
2802
|
+
const { sessionId, ref, text, clear, submit } = parseTypeArgs(args.rawArgs);
|
|
2803
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
2804
|
+
if (!ref) throw createUsageError("Missing --ref");
|
|
2805
|
+
if (!text) throw createUsageError("Missing --text");
|
|
2806
|
+
const result = await callDaemon("interact.type", { sessionId, ref, text, clear, submit });
|
|
2807
|
+
return { success: true, message: "Type complete.", data: result };
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
// src/cli/commands/interact/select.ts
|
|
2811
|
+
function parseSelectArgs(rawArgs) {
|
|
2812
|
+
const parsed = {};
|
|
2813
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
2814
|
+
const arg = rawArgs[i];
|
|
2815
|
+
if (arg === "--session-id") {
|
|
2816
|
+
const value = rawArgs[i + 1];
|
|
2817
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
2818
|
+
parsed.sessionId = value;
|
|
2819
|
+
i += 1;
|
|
2820
|
+
continue;
|
|
2821
|
+
}
|
|
2822
|
+
if (arg?.startsWith("--session-id=")) {
|
|
2823
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
2824
|
+
continue;
|
|
2825
|
+
}
|
|
2826
|
+
if (arg === "--ref") {
|
|
2827
|
+
const value = rawArgs[i + 1];
|
|
2828
|
+
if (!value) throw createUsageError("Missing value for --ref");
|
|
2829
|
+
parsed.ref = value;
|
|
2830
|
+
i += 1;
|
|
2831
|
+
continue;
|
|
2832
|
+
}
|
|
2833
|
+
if (arg?.startsWith("--ref=")) {
|
|
2834
|
+
parsed.ref = arg.split("=", 2)[1];
|
|
2835
|
+
continue;
|
|
2836
|
+
}
|
|
2837
|
+
if (arg === "--values") {
|
|
2838
|
+
const value = rawArgs[i + 1];
|
|
2839
|
+
if (!value) throw createUsageError("Missing value for --values");
|
|
2840
|
+
parsed.values = value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
2841
|
+
i += 1;
|
|
2842
|
+
continue;
|
|
2843
|
+
}
|
|
2844
|
+
if (arg?.startsWith("--values=")) {
|
|
2845
|
+
const value = arg.split("=", 2)[1];
|
|
2846
|
+
if (!value) throw createUsageError("Missing value for --values");
|
|
2847
|
+
parsed.values = value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
2848
|
+
continue;
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
return parsed;
|
|
2852
|
+
}
|
|
2853
|
+
async function runSelect(args) {
|
|
2854
|
+
const { sessionId, ref, values } = parseSelectArgs(args.rawArgs);
|
|
2855
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
2856
|
+
if (!ref) throw createUsageError("Missing --ref");
|
|
2857
|
+
if (!values || values.length === 0) throw createUsageError("Missing --values");
|
|
2858
|
+
const result = await callDaemon("interact.select", { sessionId, ref, values });
|
|
2859
|
+
return { success: true, message: "Select complete.", data: result };
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2862
|
+
// src/cli/commands/interact/scroll.ts
|
|
2863
|
+
function parseScrollArgs(rawArgs) {
|
|
2864
|
+
const parsed = {};
|
|
2865
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
2866
|
+
const arg = rawArgs[i];
|
|
2867
|
+
if (arg === "--session-id") {
|
|
2868
|
+
const value = rawArgs[i + 1];
|
|
2869
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
2870
|
+
parsed.sessionId = value;
|
|
2871
|
+
i += 1;
|
|
2872
|
+
continue;
|
|
2873
|
+
}
|
|
2874
|
+
if (arg?.startsWith("--session-id=")) {
|
|
2875
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
2876
|
+
continue;
|
|
2877
|
+
}
|
|
2878
|
+
if (arg === "--ref") {
|
|
2879
|
+
const value = rawArgs[i + 1];
|
|
2880
|
+
if (!value) throw createUsageError("Missing value for --ref");
|
|
2881
|
+
parsed.ref = value;
|
|
2882
|
+
i += 1;
|
|
2883
|
+
continue;
|
|
2884
|
+
}
|
|
2885
|
+
if (arg?.startsWith("--ref=")) {
|
|
2886
|
+
parsed.ref = arg.split("=", 2)[1];
|
|
2887
|
+
continue;
|
|
2888
|
+
}
|
|
2889
|
+
if (arg === "--dy") {
|
|
2890
|
+
const value = rawArgs[i + 1];
|
|
2891
|
+
if (!value) throw createUsageError("Missing value for --dy");
|
|
2892
|
+
parsed.dy = Number(value);
|
|
2893
|
+
i += 1;
|
|
2894
|
+
continue;
|
|
2895
|
+
}
|
|
2896
|
+
if (arg?.startsWith("--dy=")) {
|
|
2897
|
+
parsed.dy = Number(arg.split("=", 2)[1]);
|
|
2898
|
+
continue;
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
return parsed;
|
|
2902
|
+
}
|
|
2903
|
+
async function runScroll(args) {
|
|
2904
|
+
const { sessionId, ref, dy } = parseScrollArgs(args.rawArgs);
|
|
2905
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
2906
|
+
if (typeof dy !== "number" || Number.isNaN(dy)) throw createUsageError("Missing --dy");
|
|
2907
|
+
const result = await callDaemon("interact.scroll", { sessionId, ref, dy });
|
|
2908
|
+
return { success: true, message: "Scroll complete.", data: result };
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2911
|
+
// src/cli/commands/interact/scroll-into-view.ts
|
|
2912
|
+
function parseScrollIntoViewArgs(rawArgs) {
|
|
2913
|
+
const parsed = {};
|
|
2914
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
2915
|
+
const arg = rawArgs[i];
|
|
2916
|
+
if (arg === "--session-id") {
|
|
2917
|
+
const value = rawArgs[i + 1];
|
|
2918
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
2919
|
+
parsed.sessionId = value;
|
|
2920
|
+
i += 1;
|
|
2921
|
+
continue;
|
|
2922
|
+
}
|
|
2923
|
+
if (arg?.startsWith("--session-id=")) {
|
|
2924
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
2925
|
+
continue;
|
|
2926
|
+
}
|
|
2927
|
+
if (arg === "--ref") {
|
|
2928
|
+
const value = rawArgs[i + 1];
|
|
2929
|
+
if (!value) throw createUsageError("Missing value for --ref");
|
|
2930
|
+
parsed.ref = value;
|
|
2931
|
+
i += 1;
|
|
2932
|
+
continue;
|
|
2933
|
+
}
|
|
2934
|
+
if (arg?.startsWith("--ref=")) {
|
|
2935
|
+
parsed.ref = arg.split("=", 2)[1];
|
|
2936
|
+
continue;
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
return parsed;
|
|
2940
|
+
}
|
|
2941
|
+
async function runScrollIntoView(args) {
|
|
2942
|
+
const { sessionId, ref } = parseScrollIntoViewArgs(args.rawArgs);
|
|
2943
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
2944
|
+
if (!ref) throw createUsageError("Missing --ref");
|
|
2945
|
+
const result = await callDaemon("interact.scrollIntoView", { sessionId, ref });
|
|
2946
|
+
return { success: true, message: "Scroll into view complete.", data: result };
|
|
2947
|
+
}
|
|
2948
|
+
|
|
2949
|
+
// src/cli/commands/targets/list.ts
|
|
2950
|
+
function parseTargetsListArgs(rawArgs) {
|
|
2951
|
+
const parsed = {};
|
|
2952
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
2953
|
+
const arg = rawArgs[i];
|
|
2954
|
+
if (arg === "--session-id") {
|
|
2955
|
+
const value = rawArgs[i + 1];
|
|
2956
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
2957
|
+
parsed.sessionId = value;
|
|
2958
|
+
i += 1;
|
|
2959
|
+
continue;
|
|
2960
|
+
}
|
|
2961
|
+
if (arg?.startsWith("--session-id=")) {
|
|
2962
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
2963
|
+
continue;
|
|
2964
|
+
}
|
|
2965
|
+
if (arg === "--include-urls") {
|
|
2966
|
+
parsed.includeUrls = true;
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
return parsed;
|
|
2970
|
+
}
|
|
2971
|
+
async function runTargetsList(args) {
|
|
2972
|
+
const { sessionId, includeUrls } = parseTargetsListArgs(args.rawArgs);
|
|
2973
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
2974
|
+
const result = await callDaemon("targets.list", { sessionId, includeUrls });
|
|
2975
|
+
return { success: true, message: `Targets listed for session: ${sessionId}`, data: result };
|
|
2976
|
+
}
|
|
2977
|
+
|
|
2978
|
+
// src/cli/commands/targets/use.ts
|
|
2979
|
+
function parseTargetUseArgs(rawArgs) {
|
|
2980
|
+
const parsed = {};
|
|
2981
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
2982
|
+
const arg = rawArgs[i];
|
|
2983
|
+
if (arg === "--session-id") {
|
|
2984
|
+
const value = rawArgs[i + 1];
|
|
2985
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
2986
|
+
parsed.sessionId = value;
|
|
2987
|
+
i += 1;
|
|
2988
|
+
continue;
|
|
2989
|
+
}
|
|
2990
|
+
if (arg?.startsWith("--session-id=")) {
|
|
2991
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
2992
|
+
continue;
|
|
2993
|
+
}
|
|
2994
|
+
if (arg === "--target-id") {
|
|
2995
|
+
const value = rawArgs[i + 1];
|
|
2996
|
+
if (!value) throw createUsageError("Missing value for --target-id");
|
|
2997
|
+
parsed.targetId = value;
|
|
2998
|
+
i += 1;
|
|
2999
|
+
continue;
|
|
3000
|
+
}
|
|
3001
|
+
if (arg?.startsWith("--target-id=")) {
|
|
3002
|
+
parsed.targetId = arg.split("=", 2)[1];
|
|
3003
|
+
continue;
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
return parsed;
|
|
3007
|
+
}
|
|
3008
|
+
async function runTargetUse(args) {
|
|
3009
|
+
const { sessionId, targetId } = parseTargetUseArgs(args.rawArgs);
|
|
3010
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
3011
|
+
if (!targetId) throw createUsageError("Missing --target-id");
|
|
3012
|
+
const result = await callDaemon("targets.use", { sessionId, targetId });
|
|
3013
|
+
return { success: true, message: `Target selected: ${targetId}`, data: result };
|
|
3014
|
+
}
|
|
3015
|
+
|
|
3016
|
+
// src/cli/commands/targets/new.ts
|
|
3017
|
+
function parseTargetNewArgs(rawArgs) {
|
|
3018
|
+
const parsed = {};
|
|
3019
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
3020
|
+
const arg = rawArgs[i];
|
|
3021
|
+
if (arg === "--session-id") {
|
|
3022
|
+
const value = rawArgs[i + 1];
|
|
3023
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
3024
|
+
parsed.sessionId = value;
|
|
3025
|
+
i += 1;
|
|
3026
|
+
continue;
|
|
3027
|
+
}
|
|
3028
|
+
if (arg?.startsWith("--session-id=")) {
|
|
3029
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
3030
|
+
continue;
|
|
3031
|
+
}
|
|
3032
|
+
if (arg === "--url") {
|
|
3033
|
+
const value = rawArgs[i + 1];
|
|
3034
|
+
if (!value) throw createUsageError("Missing value for --url");
|
|
3035
|
+
parsed.url = value;
|
|
3036
|
+
i += 1;
|
|
3037
|
+
continue;
|
|
3038
|
+
}
|
|
3039
|
+
if (arg?.startsWith("--url=")) {
|
|
3040
|
+
parsed.url = arg.split("=", 2)[1];
|
|
3041
|
+
continue;
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
return parsed;
|
|
3045
|
+
}
|
|
3046
|
+
async function runTargetNew(args) {
|
|
3047
|
+
const { sessionId, url } = parseTargetNewArgs(args.rawArgs);
|
|
3048
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
3049
|
+
const result = await callDaemon("targets.new", { sessionId, url });
|
|
3050
|
+
return { success: true, message: "Target created.", data: result };
|
|
3051
|
+
}
|
|
3052
|
+
|
|
3053
|
+
// src/cli/commands/targets/close.ts
|
|
3054
|
+
function parseTargetCloseArgs(rawArgs) {
|
|
3055
|
+
const parsed = {};
|
|
3056
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
3057
|
+
const arg = rawArgs[i];
|
|
3058
|
+
if (arg === "--session-id") {
|
|
3059
|
+
const value = rawArgs[i + 1];
|
|
3060
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
3061
|
+
parsed.sessionId = value;
|
|
3062
|
+
i += 1;
|
|
3063
|
+
continue;
|
|
3064
|
+
}
|
|
3065
|
+
if (arg?.startsWith("--session-id=")) {
|
|
3066
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
3067
|
+
continue;
|
|
3068
|
+
}
|
|
3069
|
+
if (arg === "--target-id") {
|
|
3070
|
+
const value = rawArgs[i + 1];
|
|
3071
|
+
if (!value) throw createUsageError("Missing value for --target-id");
|
|
3072
|
+
parsed.targetId = value;
|
|
3073
|
+
i += 1;
|
|
3074
|
+
continue;
|
|
3075
|
+
}
|
|
3076
|
+
if (arg?.startsWith("--target-id=")) {
|
|
3077
|
+
parsed.targetId = arg.split("=", 2)[1];
|
|
3078
|
+
continue;
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
return parsed;
|
|
3082
|
+
}
|
|
3083
|
+
async function runTargetClose(args) {
|
|
3084
|
+
const { sessionId, targetId } = parseTargetCloseArgs(args.rawArgs);
|
|
3085
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
3086
|
+
if (!targetId) throw createUsageError("Missing --target-id");
|
|
3087
|
+
await callDaemon("targets.close", { sessionId, targetId });
|
|
3088
|
+
return { success: true, message: `Target closed: ${targetId}` };
|
|
3089
|
+
}
|
|
3090
|
+
|
|
3091
|
+
// src/cli/commands/pages/open.ts
|
|
3092
|
+
function parsePageOpenArgs(rawArgs) {
|
|
3093
|
+
const parsed = {};
|
|
3094
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
3095
|
+
const arg = rawArgs[i];
|
|
3096
|
+
if (arg === "--session-id") {
|
|
3097
|
+
const value = rawArgs[i + 1];
|
|
3098
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
3099
|
+
parsed.sessionId = value;
|
|
3100
|
+
i += 1;
|
|
3101
|
+
continue;
|
|
3102
|
+
}
|
|
3103
|
+
if (arg?.startsWith("--session-id=")) {
|
|
3104
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
3105
|
+
continue;
|
|
3106
|
+
}
|
|
3107
|
+
if (arg === "--name") {
|
|
3108
|
+
const value = rawArgs[i + 1];
|
|
3109
|
+
if (!value) throw createUsageError("Missing value for --name");
|
|
3110
|
+
parsed.name = value;
|
|
3111
|
+
i += 1;
|
|
3112
|
+
continue;
|
|
3113
|
+
}
|
|
3114
|
+
if (arg?.startsWith("--name=")) {
|
|
3115
|
+
parsed.name = arg.split("=", 2)[1];
|
|
3116
|
+
continue;
|
|
3117
|
+
}
|
|
3118
|
+
if (arg === "--url") {
|
|
3119
|
+
const value = rawArgs[i + 1];
|
|
3120
|
+
if (!value) throw createUsageError("Missing value for --url");
|
|
3121
|
+
parsed.url = value;
|
|
3122
|
+
i += 1;
|
|
3123
|
+
continue;
|
|
3124
|
+
}
|
|
3125
|
+
if (arg?.startsWith("--url=")) {
|
|
3126
|
+
parsed.url = arg.split("=", 2)[1];
|
|
3127
|
+
continue;
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
return parsed;
|
|
3131
|
+
}
|
|
3132
|
+
async function runPageOpen(args) {
|
|
3133
|
+
const { sessionId, name, url } = parsePageOpenArgs(args.rawArgs);
|
|
3134
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
3135
|
+
if (!name) throw createUsageError("Missing --name");
|
|
3136
|
+
const result = await callDaemon("page.open", { sessionId, name, url });
|
|
3137
|
+
return { success: true, message: `Page ready: ${name}`, data: result };
|
|
3138
|
+
}
|
|
3139
|
+
|
|
3140
|
+
// src/cli/commands/pages/list.ts
|
|
3141
|
+
function parsePagesListArgs(rawArgs) {
|
|
3142
|
+
const parsed = {};
|
|
3143
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
3144
|
+
const arg = rawArgs[i];
|
|
3145
|
+
if (arg === "--session-id") {
|
|
3146
|
+
const value = rawArgs[i + 1];
|
|
3147
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
3148
|
+
parsed.sessionId = value;
|
|
3149
|
+
i += 1;
|
|
3150
|
+
continue;
|
|
3151
|
+
}
|
|
3152
|
+
if (arg?.startsWith("--session-id=")) {
|
|
3153
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
3154
|
+
continue;
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
return parsed;
|
|
3158
|
+
}
|
|
3159
|
+
async function runPagesList(args) {
|
|
3160
|
+
const { sessionId } = parsePagesListArgs(args.rawArgs);
|
|
3161
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
3162
|
+
const result = await callDaemon("page.list", { sessionId });
|
|
3163
|
+
return { success: true, message: `Pages listed for session: ${sessionId}`, data: result };
|
|
3164
|
+
}
|
|
3165
|
+
|
|
3166
|
+
// src/cli/commands/pages/close.ts
|
|
3167
|
+
function parsePageCloseArgs(rawArgs) {
|
|
3168
|
+
const parsed = {};
|
|
3169
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
3170
|
+
const arg = rawArgs[i];
|
|
3171
|
+
if (arg === "--session-id") {
|
|
3172
|
+
const value = rawArgs[i + 1];
|
|
3173
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
3174
|
+
parsed.sessionId = value;
|
|
3175
|
+
i += 1;
|
|
3176
|
+
continue;
|
|
3177
|
+
}
|
|
3178
|
+
if (arg?.startsWith("--session-id=")) {
|
|
3179
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
3180
|
+
continue;
|
|
3181
|
+
}
|
|
3182
|
+
if (arg === "--name") {
|
|
3183
|
+
const value = rawArgs[i + 1];
|
|
3184
|
+
if (!value) throw createUsageError("Missing value for --name");
|
|
3185
|
+
parsed.name = value;
|
|
3186
|
+
i += 1;
|
|
3187
|
+
continue;
|
|
3188
|
+
}
|
|
3189
|
+
if (arg?.startsWith("--name=")) {
|
|
3190
|
+
parsed.name = arg.split("=", 2)[1];
|
|
3191
|
+
continue;
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
return parsed;
|
|
3195
|
+
}
|
|
3196
|
+
async function runPageClose(args) {
|
|
3197
|
+
const { sessionId, name } = parsePageCloseArgs(args.rawArgs);
|
|
3198
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
3199
|
+
if (!name) throw createUsageError("Missing --name");
|
|
3200
|
+
await callDaemon("page.close", { sessionId, name });
|
|
3201
|
+
return { success: true, message: `Page closed: ${name}` };
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
// src/cli/commands/dom/html.ts
|
|
3205
|
+
function parseDomHtmlArgs(rawArgs) {
|
|
3206
|
+
const parsed = {};
|
|
3207
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
3208
|
+
const arg = rawArgs[i];
|
|
3209
|
+
if (arg === "--session-id") {
|
|
3210
|
+
const value = rawArgs[i + 1];
|
|
3211
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
3212
|
+
parsed.sessionId = value;
|
|
3213
|
+
i += 1;
|
|
3214
|
+
continue;
|
|
3215
|
+
}
|
|
3216
|
+
if (arg?.startsWith("--session-id=")) {
|
|
3217
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
3218
|
+
continue;
|
|
3219
|
+
}
|
|
3220
|
+
if (arg === "--ref") {
|
|
3221
|
+
const value = rawArgs[i + 1];
|
|
3222
|
+
if (!value) throw createUsageError("Missing value for --ref");
|
|
3223
|
+
parsed.ref = value;
|
|
3224
|
+
i += 1;
|
|
3225
|
+
continue;
|
|
3226
|
+
}
|
|
3227
|
+
if (arg?.startsWith("--ref=")) {
|
|
3228
|
+
parsed.ref = arg.split("=", 2)[1];
|
|
3229
|
+
continue;
|
|
3230
|
+
}
|
|
3231
|
+
if (arg === "--max-chars") {
|
|
3232
|
+
const value = rawArgs[i + 1];
|
|
3233
|
+
if (!value) throw createUsageError("Missing value for --max-chars");
|
|
3234
|
+
parsed.maxChars = Number(value);
|
|
3235
|
+
i += 1;
|
|
3236
|
+
continue;
|
|
3237
|
+
}
|
|
3238
|
+
if (arg?.startsWith("--max-chars=")) {
|
|
3239
|
+
parsed.maxChars = Number(arg.split("=", 2)[1]);
|
|
3240
|
+
continue;
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
return parsed;
|
|
3244
|
+
}
|
|
3245
|
+
async function runDomHtml(args) {
|
|
3246
|
+
const { sessionId, ref, maxChars } = parseDomHtmlArgs(args.rawArgs);
|
|
3247
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
3248
|
+
if (!ref) throw createUsageError("Missing --ref");
|
|
3249
|
+
const result = await callDaemon("dom.getHtml", { sessionId, ref, maxChars });
|
|
3250
|
+
return { success: true, message: "DOM HTML captured.", data: result };
|
|
3251
|
+
}
|
|
3252
|
+
|
|
3253
|
+
// src/cli/commands/dom/text.ts
|
|
3254
|
+
function parseDomTextArgs(rawArgs) {
|
|
3255
|
+
const parsed = {};
|
|
3256
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
3257
|
+
const arg = rawArgs[i];
|
|
3258
|
+
if (arg === "--session-id") {
|
|
3259
|
+
const value = rawArgs[i + 1];
|
|
3260
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
3261
|
+
parsed.sessionId = value;
|
|
3262
|
+
i += 1;
|
|
3263
|
+
continue;
|
|
3264
|
+
}
|
|
3265
|
+
if (arg?.startsWith("--session-id=")) {
|
|
3266
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
3267
|
+
continue;
|
|
3268
|
+
}
|
|
3269
|
+
if (arg === "--ref") {
|
|
3270
|
+
const value = rawArgs[i + 1];
|
|
3271
|
+
if (!value) throw createUsageError("Missing value for --ref");
|
|
3272
|
+
parsed.ref = value;
|
|
3273
|
+
i += 1;
|
|
3274
|
+
continue;
|
|
3275
|
+
}
|
|
3276
|
+
if (arg?.startsWith("--ref=")) {
|
|
3277
|
+
parsed.ref = arg.split("=", 2)[1];
|
|
3278
|
+
continue;
|
|
3279
|
+
}
|
|
3280
|
+
if (arg === "--max-chars") {
|
|
3281
|
+
const value = rawArgs[i + 1];
|
|
3282
|
+
if (!value) throw createUsageError("Missing value for --max-chars");
|
|
3283
|
+
parsed.maxChars = Number(value);
|
|
3284
|
+
i += 1;
|
|
3285
|
+
continue;
|
|
3286
|
+
}
|
|
3287
|
+
if (arg?.startsWith("--max-chars=")) {
|
|
3288
|
+
parsed.maxChars = Number(arg.split("=", 2)[1]);
|
|
3289
|
+
continue;
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
return parsed;
|
|
3293
|
+
}
|
|
3294
|
+
async function runDomText(args) {
|
|
3295
|
+
const { sessionId, ref, maxChars } = parseDomTextArgs(args.rawArgs);
|
|
3296
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
3297
|
+
if (!ref) throw createUsageError("Missing --ref");
|
|
3298
|
+
const result = await callDaemon("dom.getText", { sessionId, ref, maxChars });
|
|
3299
|
+
return { success: true, message: "DOM text captured.", data: result };
|
|
3300
|
+
}
|
|
3301
|
+
|
|
3302
|
+
// src/cli/commands/dom/attr.ts
|
|
3303
|
+
function parseDomAttrArgs(rawArgs) {
|
|
3304
|
+
const parsed = {};
|
|
3305
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
3306
|
+
const arg = rawArgs[i];
|
|
3307
|
+
if (arg === "--session-id") {
|
|
3308
|
+
const value = rawArgs[i + 1];
|
|
3309
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
3310
|
+
parsed.sessionId = value;
|
|
3311
|
+
i += 1;
|
|
3312
|
+
continue;
|
|
3313
|
+
}
|
|
3314
|
+
if (arg?.startsWith("--session-id=")) {
|
|
3315
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
3316
|
+
continue;
|
|
3317
|
+
}
|
|
3318
|
+
if (arg === "--ref") {
|
|
3319
|
+
const value = rawArgs[i + 1];
|
|
3320
|
+
if (!value) throw createUsageError("Missing value for --ref");
|
|
3321
|
+
parsed.ref = value;
|
|
3322
|
+
i += 1;
|
|
3323
|
+
continue;
|
|
3324
|
+
}
|
|
3325
|
+
if (arg?.startsWith("--ref=")) {
|
|
3326
|
+
parsed.ref = arg.split("=", 2)[1];
|
|
3327
|
+
continue;
|
|
3328
|
+
}
|
|
3329
|
+
if (arg === "--attr") {
|
|
3330
|
+
const value = rawArgs[i + 1];
|
|
3331
|
+
if (!value) throw createUsageError("Missing value for --attr");
|
|
3332
|
+
parsed.attr = value;
|
|
3333
|
+
i += 1;
|
|
3334
|
+
continue;
|
|
3335
|
+
}
|
|
3336
|
+
if (arg?.startsWith("--attr=")) {
|
|
3337
|
+
parsed.attr = arg.split("=", 2)[1];
|
|
3338
|
+
continue;
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
return parsed;
|
|
3342
|
+
}
|
|
3343
|
+
async function runDomAttr(args) {
|
|
3344
|
+
const { sessionId, ref, attr } = parseDomAttrArgs(args.rawArgs);
|
|
3345
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
3346
|
+
if (!ref) throw createUsageError("Missing --ref");
|
|
3347
|
+
if (!attr) throw createUsageError("Missing --attr");
|
|
3348
|
+
const result = await callDaemon("dom.getAttr", { sessionId, ref, name: attr });
|
|
3349
|
+
return { success: true, message: "DOM attribute captured.", data: result };
|
|
3350
|
+
}
|
|
3351
|
+
|
|
3352
|
+
// src/cli/commands/dom/value.ts
|
|
3353
|
+
function parseDomValueArgs(rawArgs) {
|
|
3354
|
+
const parsed = {};
|
|
3355
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
3356
|
+
const arg = rawArgs[i];
|
|
3357
|
+
if (arg === "--session-id") {
|
|
3358
|
+
const value = rawArgs[i + 1];
|
|
3359
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
3360
|
+
parsed.sessionId = value;
|
|
3361
|
+
i += 1;
|
|
3362
|
+
continue;
|
|
3363
|
+
}
|
|
3364
|
+
if (arg?.startsWith("--session-id=")) {
|
|
3365
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
3366
|
+
continue;
|
|
3367
|
+
}
|
|
3368
|
+
if (arg === "--ref") {
|
|
3369
|
+
const value = rawArgs[i + 1];
|
|
3370
|
+
if (!value) throw createUsageError("Missing value for --ref");
|
|
3371
|
+
parsed.ref = value;
|
|
3372
|
+
i += 1;
|
|
3373
|
+
continue;
|
|
3374
|
+
}
|
|
3375
|
+
if (arg?.startsWith("--ref=")) {
|
|
3376
|
+
parsed.ref = arg.split("=", 2)[1];
|
|
3377
|
+
continue;
|
|
3378
|
+
}
|
|
3379
|
+
}
|
|
3380
|
+
return parsed;
|
|
3381
|
+
}
|
|
3382
|
+
async function runDomValue(args) {
|
|
3383
|
+
const { sessionId, ref } = parseDomValueArgs(args.rawArgs);
|
|
3384
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
3385
|
+
if (!ref) throw createUsageError("Missing --ref");
|
|
3386
|
+
const result = await callDaemon("dom.getValue", { sessionId, ref });
|
|
3387
|
+
return { success: true, message: "DOM value captured.", data: result };
|
|
3388
|
+
}
|
|
3389
|
+
|
|
3390
|
+
// src/cli/commands/dom/visible.ts
|
|
3391
|
+
function parseDomVisibleArgs(rawArgs) {
|
|
3392
|
+
const parsed = {};
|
|
3393
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
3394
|
+
const arg = rawArgs[i];
|
|
3395
|
+
if (arg === "--session-id") {
|
|
3396
|
+
const value = rawArgs[i + 1];
|
|
3397
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
3398
|
+
parsed.sessionId = value;
|
|
3399
|
+
i += 1;
|
|
3400
|
+
continue;
|
|
3401
|
+
}
|
|
3402
|
+
if (arg?.startsWith("--session-id=")) {
|
|
3403
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
3404
|
+
continue;
|
|
3405
|
+
}
|
|
3406
|
+
if (arg === "--ref") {
|
|
3407
|
+
const value = rawArgs[i + 1];
|
|
3408
|
+
if (!value) throw createUsageError("Missing value for --ref");
|
|
3409
|
+
parsed.ref = value;
|
|
3410
|
+
i += 1;
|
|
3411
|
+
continue;
|
|
3412
|
+
}
|
|
3413
|
+
if (arg?.startsWith("--ref=")) {
|
|
3414
|
+
parsed.ref = arg.split("=", 2)[1];
|
|
3415
|
+
continue;
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
return parsed;
|
|
3419
|
+
}
|
|
3420
|
+
async function runDomVisible(args) {
|
|
3421
|
+
const { sessionId, ref } = parseDomVisibleArgs(args.rawArgs);
|
|
3422
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
3423
|
+
if (!ref) throw createUsageError("Missing --ref");
|
|
3424
|
+
const result = await callDaemon("dom.isVisible", { sessionId, ref });
|
|
3425
|
+
return { success: true, message: "Visibility checked.", data: result };
|
|
3426
|
+
}
|
|
3427
|
+
|
|
3428
|
+
// src/cli/commands/dom/enabled.ts
|
|
3429
|
+
function parseDomEnabledArgs(rawArgs) {
|
|
3430
|
+
const parsed = {};
|
|
3431
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
3432
|
+
const arg = rawArgs[i];
|
|
3433
|
+
if (arg === "--session-id") {
|
|
3434
|
+
const value = rawArgs[i + 1];
|
|
3435
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
3436
|
+
parsed.sessionId = value;
|
|
3437
|
+
i += 1;
|
|
3438
|
+
continue;
|
|
3439
|
+
}
|
|
3440
|
+
if (arg?.startsWith("--session-id=")) {
|
|
3441
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
3442
|
+
continue;
|
|
3443
|
+
}
|
|
3444
|
+
if (arg === "--ref") {
|
|
3445
|
+
const value = rawArgs[i + 1];
|
|
3446
|
+
if (!value) throw createUsageError("Missing value for --ref");
|
|
3447
|
+
parsed.ref = value;
|
|
3448
|
+
i += 1;
|
|
3449
|
+
continue;
|
|
3450
|
+
}
|
|
3451
|
+
if (arg?.startsWith("--ref=")) {
|
|
3452
|
+
parsed.ref = arg.split("=", 2)[1];
|
|
3453
|
+
continue;
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
return parsed;
|
|
3457
|
+
}
|
|
3458
|
+
async function runDomEnabled(args) {
|
|
3459
|
+
const { sessionId, ref } = parseDomEnabledArgs(args.rawArgs);
|
|
3460
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
3461
|
+
if (!ref) throw createUsageError("Missing --ref");
|
|
3462
|
+
const result = await callDaemon("dom.isEnabled", { sessionId, ref });
|
|
3463
|
+
return { success: true, message: "Enabled state checked.", data: result };
|
|
3464
|
+
}
|
|
3465
|
+
|
|
3466
|
+
// src/cli/commands/dom/checked.ts
|
|
3467
|
+
function parseDomCheckedArgs(rawArgs) {
|
|
3468
|
+
const parsed = {};
|
|
3469
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
3470
|
+
const arg = rawArgs[i];
|
|
3471
|
+
if (arg === "--session-id") {
|
|
3472
|
+
const value = rawArgs[i + 1];
|
|
3473
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
3474
|
+
parsed.sessionId = value;
|
|
3475
|
+
i += 1;
|
|
3476
|
+
continue;
|
|
3477
|
+
}
|
|
3478
|
+
if (arg?.startsWith("--session-id=")) {
|
|
3479
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
3480
|
+
continue;
|
|
3481
|
+
}
|
|
3482
|
+
if (arg === "--ref") {
|
|
3483
|
+
const value = rawArgs[i + 1];
|
|
3484
|
+
if (!value) throw createUsageError("Missing value for --ref");
|
|
3485
|
+
parsed.ref = value;
|
|
3486
|
+
i += 1;
|
|
3487
|
+
continue;
|
|
3488
|
+
}
|
|
3489
|
+
if (arg?.startsWith("--ref=")) {
|
|
3490
|
+
parsed.ref = arg.split("=", 2)[1];
|
|
3491
|
+
continue;
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
return parsed;
|
|
3495
|
+
}
|
|
3496
|
+
async function runDomChecked(args) {
|
|
3497
|
+
const { sessionId, ref } = parseDomCheckedArgs(args.rawArgs);
|
|
3498
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
3499
|
+
if (!ref) throw createUsageError("Missing --ref");
|
|
3500
|
+
const result = await callDaemon("dom.isChecked", { sessionId, ref });
|
|
3501
|
+
return { success: true, message: "Checked state reported.", data: result };
|
|
3502
|
+
}
|
|
3503
|
+
|
|
3504
|
+
// src/cli/commands/export/clone-page.ts
|
|
3505
|
+
function parseClonePageArgs(rawArgs) {
|
|
3506
|
+
const parsed = {};
|
|
3507
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
3508
|
+
const arg = rawArgs[i];
|
|
3509
|
+
if (arg === "--session-id") {
|
|
3510
|
+
const value = rawArgs[i + 1];
|
|
3511
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
3512
|
+
parsed.sessionId = value;
|
|
3513
|
+
i += 1;
|
|
3514
|
+
continue;
|
|
3515
|
+
}
|
|
3516
|
+
if (arg?.startsWith("--session-id=")) {
|
|
3517
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
3518
|
+
continue;
|
|
3519
|
+
}
|
|
3520
|
+
}
|
|
3521
|
+
return parsed;
|
|
3522
|
+
}
|
|
3523
|
+
async function runClonePage(args) {
|
|
3524
|
+
const { sessionId } = parseClonePageArgs(args.rawArgs);
|
|
3525
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
3526
|
+
const result = await callDaemon("export.clonePage", { sessionId });
|
|
3527
|
+
return { success: true, message: "Page cloned.", data: result };
|
|
3528
|
+
}
|
|
3529
|
+
|
|
3530
|
+
// src/cli/commands/export/clone-component.ts
|
|
3531
|
+
function parseCloneComponentArgs(rawArgs) {
|
|
3532
|
+
const parsed = {};
|
|
3533
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
3534
|
+
const arg = rawArgs[i];
|
|
3535
|
+
if (arg === "--session-id") {
|
|
3536
|
+
const value = rawArgs[i + 1];
|
|
3537
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
3538
|
+
parsed.sessionId = value;
|
|
3539
|
+
i += 1;
|
|
3540
|
+
continue;
|
|
3541
|
+
}
|
|
3542
|
+
if (arg?.startsWith("--session-id=")) {
|
|
3543
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
3544
|
+
continue;
|
|
3545
|
+
}
|
|
3546
|
+
if (arg === "--ref") {
|
|
3547
|
+
const value = rawArgs[i + 1];
|
|
3548
|
+
if (!value) throw createUsageError("Missing value for --ref");
|
|
3549
|
+
parsed.ref = value;
|
|
3550
|
+
i += 1;
|
|
3551
|
+
continue;
|
|
3552
|
+
}
|
|
3553
|
+
if (arg?.startsWith("--ref=")) {
|
|
3554
|
+
parsed.ref = arg.split("=", 2)[1];
|
|
3555
|
+
continue;
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
return parsed;
|
|
3559
|
+
}
|
|
3560
|
+
async function runCloneComponent(args) {
|
|
3561
|
+
const { sessionId, ref } = parseCloneComponentArgs(args.rawArgs);
|
|
3562
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
3563
|
+
if (!ref) throw createUsageError("Missing --ref");
|
|
3564
|
+
const result = await callDaemon("export.cloneComponent", { sessionId, ref });
|
|
3565
|
+
return { success: true, message: "Component cloned.", data: result };
|
|
3566
|
+
}
|
|
3567
|
+
|
|
3568
|
+
// src/cli/commands/devtools/perf.ts
|
|
3569
|
+
function parsePerfArgs(rawArgs) {
|
|
3570
|
+
const parsed = {};
|
|
3571
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
3572
|
+
const arg = rawArgs[i];
|
|
3573
|
+
if (arg === "--session-id") {
|
|
3574
|
+
const value = rawArgs[i + 1];
|
|
3575
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
3576
|
+
parsed.sessionId = value;
|
|
3577
|
+
i += 1;
|
|
3578
|
+
continue;
|
|
3579
|
+
}
|
|
3580
|
+
if (arg?.startsWith("--session-id=")) {
|
|
3581
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
3582
|
+
continue;
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
return parsed;
|
|
3586
|
+
}
|
|
3587
|
+
async function runPerf(args) {
|
|
3588
|
+
const { sessionId } = parsePerfArgs(args.rawArgs);
|
|
3589
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
3590
|
+
const result = await callDaemon("devtools.perf", { sessionId });
|
|
3591
|
+
return { success: true, message: "Performance metrics captured.", data: result };
|
|
3592
|
+
}
|
|
3593
|
+
|
|
3594
|
+
// src/cli/commands/devtools/screenshot.ts
|
|
3595
|
+
function parseScreenshotArgs(rawArgs) {
|
|
3596
|
+
const parsed = {};
|
|
3597
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
3598
|
+
const arg = rawArgs[i];
|
|
3599
|
+
if (arg === "--session-id") {
|
|
3600
|
+
const value = rawArgs[i + 1];
|
|
3601
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
3602
|
+
parsed.sessionId = value;
|
|
3603
|
+
i += 1;
|
|
3604
|
+
continue;
|
|
3605
|
+
}
|
|
3606
|
+
if (arg?.startsWith("--session-id=")) {
|
|
3607
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
3608
|
+
continue;
|
|
3609
|
+
}
|
|
3610
|
+
if (arg === "--path") {
|
|
3611
|
+
const value = rawArgs[i + 1];
|
|
3612
|
+
if (!value) throw createUsageError("Missing value for --path");
|
|
3613
|
+
parsed.path = value;
|
|
3614
|
+
i += 1;
|
|
3615
|
+
continue;
|
|
3616
|
+
}
|
|
3617
|
+
if (arg?.startsWith("--path=")) {
|
|
3618
|
+
parsed.path = arg.split("=", 2)[1];
|
|
3619
|
+
continue;
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
return parsed;
|
|
3623
|
+
}
|
|
3624
|
+
async function runScreenshot(args) {
|
|
3625
|
+
const { sessionId, path: path8 } = parseScreenshotArgs(args.rawArgs);
|
|
3626
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
3627
|
+
const result = await callDaemon("page.screenshot", { sessionId, path: path8 });
|
|
3628
|
+
return { success: true, message: "Screenshot captured.", data: result };
|
|
3629
|
+
}
|
|
3630
|
+
|
|
3631
|
+
// src/cli/commands/devtools/console-poll.ts
|
|
3632
|
+
function parseConsolePollArgs(rawArgs) {
|
|
3633
|
+
const parsed = {};
|
|
3634
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
3635
|
+
const arg = rawArgs[i];
|
|
3636
|
+
if (arg === "--session-id") {
|
|
3637
|
+
const value = rawArgs[i + 1];
|
|
3638
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
3639
|
+
parsed.sessionId = value;
|
|
3640
|
+
i += 1;
|
|
3641
|
+
continue;
|
|
3642
|
+
}
|
|
3643
|
+
if (arg?.startsWith("--session-id=")) {
|
|
3644
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
3645
|
+
continue;
|
|
3646
|
+
}
|
|
3647
|
+
if (arg === "--since-seq") {
|
|
3648
|
+
const value = rawArgs[i + 1];
|
|
3649
|
+
if (!value) throw createUsageError("Missing value for --since-seq");
|
|
3650
|
+
parsed.sinceSeq = Number(value);
|
|
3651
|
+
i += 1;
|
|
3652
|
+
continue;
|
|
3653
|
+
}
|
|
3654
|
+
if (arg?.startsWith("--since-seq=")) {
|
|
3655
|
+
parsed.sinceSeq = Number(arg.split("=", 2)[1]);
|
|
3656
|
+
continue;
|
|
3657
|
+
}
|
|
3658
|
+
if (arg === "--max") {
|
|
3659
|
+
const value = rawArgs[i + 1];
|
|
3660
|
+
if (!value) throw createUsageError("Missing value for --max");
|
|
3661
|
+
parsed.max = Number(value);
|
|
3662
|
+
i += 1;
|
|
3663
|
+
continue;
|
|
3664
|
+
}
|
|
3665
|
+
if (arg?.startsWith("--max=")) {
|
|
3666
|
+
parsed.max = Number(arg.split("=", 2)[1]);
|
|
3667
|
+
continue;
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
return parsed;
|
|
3671
|
+
}
|
|
3672
|
+
async function runConsolePoll(args) {
|
|
3673
|
+
const { sessionId, sinceSeq, max } = parseConsolePollArgs(args.rawArgs);
|
|
3674
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
3675
|
+
const result = await callDaemon("devtools.consolePoll", { sessionId, sinceSeq, max });
|
|
3676
|
+
return { success: true, message: "Console events polled.", data: result };
|
|
3677
|
+
}
|
|
3678
|
+
|
|
3679
|
+
// src/cli/commands/devtools/network-poll.ts
|
|
3680
|
+
function parseNetworkPollArgs(rawArgs) {
|
|
3681
|
+
const parsed = {};
|
|
3682
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
3683
|
+
const arg = rawArgs[i];
|
|
3684
|
+
if (arg === "--session-id") {
|
|
3685
|
+
const value = rawArgs[i + 1];
|
|
3686
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
3687
|
+
parsed.sessionId = value;
|
|
3688
|
+
i += 1;
|
|
3689
|
+
continue;
|
|
3690
|
+
}
|
|
3691
|
+
if (arg?.startsWith("--session-id=")) {
|
|
3692
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
3693
|
+
continue;
|
|
3694
|
+
}
|
|
3695
|
+
if (arg === "--since-seq") {
|
|
3696
|
+
const value = rawArgs[i + 1];
|
|
3697
|
+
if (!value) throw createUsageError("Missing value for --since-seq");
|
|
3698
|
+
parsed.sinceSeq = Number(value);
|
|
3699
|
+
i += 1;
|
|
3700
|
+
continue;
|
|
3701
|
+
}
|
|
3702
|
+
if (arg?.startsWith("--since-seq=")) {
|
|
3703
|
+
parsed.sinceSeq = Number(arg.split("=", 2)[1]);
|
|
3704
|
+
continue;
|
|
3705
|
+
}
|
|
3706
|
+
if (arg === "--max") {
|
|
3707
|
+
const value = rawArgs[i + 1];
|
|
3708
|
+
if (!value) throw createUsageError("Missing value for --max");
|
|
3709
|
+
parsed.max = Number(value);
|
|
3710
|
+
i += 1;
|
|
3711
|
+
continue;
|
|
3712
|
+
}
|
|
3713
|
+
if (arg?.startsWith("--max=")) {
|
|
3714
|
+
parsed.max = Number(arg.split("=", 2)[1]);
|
|
3715
|
+
continue;
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
return parsed;
|
|
3719
|
+
}
|
|
3720
|
+
async function runNetworkPoll(args) {
|
|
3721
|
+
const { sessionId, sinceSeq, max } = parseNetworkPollArgs(args.rawArgs);
|
|
3722
|
+
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
3723
|
+
const result = await callDaemon("devtools.networkPoll", { sessionId, sinceSeq, max });
|
|
3724
|
+
return { success: true, message: "Network events polled.", data: result };
|
|
3725
|
+
}
|
|
3726
|
+
|
|
3727
|
+
// src/cli/index.ts
|
|
3728
|
+
var VERSION = "0.1.0";
|
|
3729
|
+
async function promptInstallMode() {
|
|
3730
|
+
if (!process.stdin.isTTY) {
|
|
3731
|
+
console.log("Non-interactive mode detected. Using global install.");
|
|
3732
|
+
return "global";
|
|
3733
|
+
}
|
|
3734
|
+
return new Promise((resolve4) => {
|
|
3735
|
+
console.log("\nWhere would you like to install opendevbrowser?\n");
|
|
3736
|
+
console.log(" 1. Global (~/.config/opencode/opencode.json)");
|
|
3737
|
+
console.log(" 2. Local (./opencode.json in this project)\n");
|
|
3738
|
+
process.stdout.write("Enter choice [1]: ");
|
|
3739
|
+
process.stdin.setEncoding("utf8");
|
|
3740
|
+
let resolved = false;
|
|
3741
|
+
let timeoutId = null;
|
|
3742
|
+
const cleanup = () => {
|
|
3743
|
+
if (timeoutId !== null) {
|
|
3744
|
+
clearTimeout(timeoutId);
|
|
3745
|
+
timeoutId = null;
|
|
3746
|
+
}
|
|
3747
|
+
};
|
|
3748
|
+
process.stdin.once("data", (data) => {
|
|
3749
|
+
cleanup();
|
|
3750
|
+
if (resolved) return;
|
|
3751
|
+
resolved = true;
|
|
3752
|
+
const input = data.toString().trim();
|
|
3753
|
+
if (input === "2") {
|
|
3754
|
+
resolve4("local");
|
|
3755
|
+
} else {
|
|
3756
|
+
resolve4("global");
|
|
3757
|
+
}
|
|
3758
|
+
});
|
|
3759
|
+
process.stdin.once("close", () => {
|
|
3760
|
+
cleanup();
|
|
3761
|
+
if (resolved) return;
|
|
3762
|
+
resolved = true;
|
|
3763
|
+
resolve4("global");
|
|
3764
|
+
});
|
|
3765
|
+
timeoutId = setTimeout(() => {
|
|
3766
|
+
timeoutId = null;
|
|
3767
|
+
if (resolved) return;
|
|
3768
|
+
resolved = true;
|
|
3769
|
+
console.log("\nTimeout - using global install.");
|
|
3770
|
+
resolve4("global");
|
|
3771
|
+
}, 3e4);
|
|
3772
|
+
});
|
|
3773
|
+
}
|
|
3774
|
+
async function promptUninstallMode() {
|
|
3775
|
+
const installed = findInstalledConfigs();
|
|
3776
|
+
if (!installed.global && !installed.local) {
|
|
3777
|
+
console.log("opendevbrowser is not installed in any config.");
|
|
3778
|
+
return null;
|
|
3779
|
+
}
|
|
3780
|
+
if (installed.global && !installed.local) {
|
|
3781
|
+
return "global";
|
|
3782
|
+
}
|
|
3783
|
+
if (!installed.global && installed.local) {
|
|
3784
|
+
return "local";
|
|
3785
|
+
}
|
|
3786
|
+
if (!process.stdin.isTTY) {
|
|
3787
|
+
console.log("Plugin found in both global and local configs. Use --global or --local flag.");
|
|
3788
|
+
return null;
|
|
3789
|
+
}
|
|
3790
|
+
return new Promise((resolve4) => {
|
|
3791
|
+
console.log("\nopendevbrowser is installed in multiple locations:\n");
|
|
3792
|
+
console.log(" 1. Global (~/.config/opencode/opencode.json)");
|
|
3793
|
+
console.log(" 2. Local (./opencode.json)");
|
|
3794
|
+
console.log(" 3. Cancel\n");
|
|
3795
|
+
process.stdout.write("Which to uninstall? [3]: ");
|
|
3796
|
+
process.stdin.setEncoding("utf8");
|
|
3797
|
+
process.stdin.once("data", (data) => {
|
|
3798
|
+
const input = data.toString().trim();
|
|
3799
|
+
if (input === "1") {
|
|
3800
|
+
resolve4("global");
|
|
3801
|
+
} else if (input === "2") {
|
|
3802
|
+
resolve4("local");
|
|
3803
|
+
} else {
|
|
3804
|
+
resolve4(null);
|
|
3805
|
+
}
|
|
3806
|
+
});
|
|
3807
|
+
process.stdin.once("close", () => {
|
|
3808
|
+
resolve4(null);
|
|
3809
|
+
});
|
|
3810
|
+
});
|
|
3811
|
+
}
|
|
3812
|
+
function emitFatalError(error, outputFormat) {
|
|
3813
|
+
if (outputFormat === "text") {
|
|
3814
|
+
console.error(`Error: ${error.message}`);
|
|
3815
|
+
if (error.exitCode === EXIT_USAGE) {
|
|
3816
|
+
console.error("\nFor help: npx opendevbrowser --help");
|
|
3817
|
+
}
|
|
3818
|
+
return;
|
|
3819
|
+
}
|
|
3820
|
+
writeOutput(formatErrorPayload(error), { format: outputFormat });
|
|
3821
|
+
}
|
|
3822
|
+
async function main() {
|
|
3823
|
+
let outputFormat = null;
|
|
3824
|
+
let parseSucceeded = false;
|
|
3825
|
+
try {
|
|
3826
|
+
const args = parseArgs(process.argv);
|
|
3827
|
+
parseSucceeded = true;
|
|
3828
|
+
outputFormat = args.outputFormat;
|
|
3829
|
+
const outputOptions = { format: args.outputFormat, quiet: args.quiet };
|
|
3830
|
+
const emitResult = (result2, payload) => {
|
|
3831
|
+
const suppressOutput = Boolean(
|
|
3832
|
+
result2.data && typeof result2.data === "object" && "suppressOutput" in result2.data && result2.data.suppressOutput
|
|
3833
|
+
);
|
|
3834
|
+
if (suppressOutput) {
|
|
3835
|
+
return;
|
|
3836
|
+
}
|
|
3837
|
+
if (args.outputFormat === "text") {
|
|
3838
|
+
if (result2.message) {
|
|
3839
|
+
writeOutput(result2.message, outputOptions);
|
|
3840
|
+
}
|
|
3841
|
+
} else {
|
|
3842
|
+
const exitCode2 = resolveExitCode(result2);
|
|
3843
|
+
writeOutput({
|
|
3844
|
+
success: result2.success,
|
|
3845
|
+
message: result2.message,
|
|
3846
|
+
...result2.success || !result2.message ? {} : { error: result2.message },
|
|
3847
|
+
...result2.success || exitCode2 === null ? {} : { exitCode: exitCode2 },
|
|
3848
|
+
...payload
|
|
3849
|
+
}, outputOptions);
|
|
3850
|
+
}
|
|
3851
|
+
};
|
|
3852
|
+
registerCommand({
|
|
3853
|
+
name: "help",
|
|
3854
|
+
description: "Show help",
|
|
3855
|
+
run: () => ({ success: true, message: getHelpText() })
|
|
3856
|
+
});
|
|
3857
|
+
registerCommand({
|
|
3858
|
+
name: "version",
|
|
3859
|
+
description: "Show version",
|
|
3860
|
+
run: () => ({ success: true, message: `opendevbrowser v${VERSION}` })
|
|
3861
|
+
});
|
|
3862
|
+
registerCommand({
|
|
3863
|
+
name: "update",
|
|
3864
|
+
description: "Clear cached plugin to trigger reinstall",
|
|
3865
|
+
run: () => {
|
|
3866
|
+
const result2 = runUpdate();
|
|
3867
|
+
return { success: result2.success, message: result2.message };
|
|
3868
|
+
}
|
|
3869
|
+
});
|
|
3870
|
+
registerCommand({
|
|
3871
|
+
name: "uninstall",
|
|
3872
|
+
description: "Remove plugin from config",
|
|
3873
|
+
run: async () => {
|
|
3874
|
+
let mode = args.mode;
|
|
3875
|
+
if (!mode && !args.noPrompt) {
|
|
3876
|
+
mode = await promptUninstallMode() ?? void 0;
|
|
3877
|
+
if (!mode) {
|
|
3878
|
+
return { success: true, message: "Uninstall cancelled." };
|
|
3879
|
+
}
|
|
3880
|
+
}
|
|
3881
|
+
if (!mode) {
|
|
3882
|
+
return { success: false, message: "Error: Please specify --global or --local for uninstall.", exitCode: EXIT_USAGE };
|
|
3883
|
+
}
|
|
3884
|
+
const result2 = runUninstall(mode);
|
|
3885
|
+
return { success: result2.success, message: result2.message };
|
|
3886
|
+
}
|
|
3887
|
+
});
|
|
3888
|
+
registerCommand({
|
|
3889
|
+
name: "install",
|
|
3890
|
+
description: "Install the plugin",
|
|
3891
|
+
run: async () => {
|
|
3892
|
+
const log = (...values) => {
|
|
3893
|
+
if (args.quiet) return;
|
|
3894
|
+
console.log(...values);
|
|
3895
|
+
};
|
|
3896
|
+
const warn = (...values) => {
|
|
3897
|
+
if (args.quiet) return;
|
|
3898
|
+
console.warn(...values);
|
|
3899
|
+
};
|
|
3900
|
+
let mode = args.mode;
|
|
3901
|
+
if (!mode) {
|
|
3902
|
+
mode = await promptInstallMode();
|
|
3903
|
+
}
|
|
3904
|
+
const result2 = mode === "global" ? installGlobal(args.withConfig) : installLocal(args.withConfig);
|
|
3905
|
+
const maybeInstallAutostart = () => {
|
|
3906
|
+
const status = getAutostartStatus();
|
|
3907
|
+
if (!status.supported) {
|
|
3908
|
+
return { status, installed: false, message: `Autostart not supported on ${status.platform}.` };
|
|
3909
|
+
}
|
|
3910
|
+
if (status.installed) {
|
|
3911
|
+
return { status, installed: true, message: "Autostart already installed." };
|
|
3912
|
+
}
|
|
3913
|
+
try {
|
|
3914
|
+
const result3 = installAutostart();
|
|
3915
|
+
return { status: result3, installed: result3.installed, message: `Autostart installed (${result3.platform}).` };
|
|
3916
|
+
} catch (error) {
|
|
3917
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3918
|
+
return { status, installed: false, message };
|
|
3919
|
+
}
|
|
3920
|
+
};
|
|
3921
|
+
if (args.outputFormat !== "text") {
|
|
3922
|
+
const payload = {
|
|
3923
|
+
alreadyInstalled: result2.alreadyInstalled
|
|
3924
|
+
};
|
|
3925
|
+
if (result2.success && args.skillsMode !== "none") {
|
|
3926
|
+
const skillsResult = installSkills(args.skillsMode);
|
|
3927
|
+
payload.skills = skillsResult;
|
|
3928
|
+
}
|
|
3929
|
+
if (args.fullInstall && result2.success) {
|
|
3930
|
+
try {
|
|
3931
|
+
const extensionPath = extractExtension();
|
|
3932
|
+
payload.extensionPath = extensionPath;
|
|
3933
|
+
} catch (error) {
|
|
3934
|
+
payload.extensionError = error instanceof Error ? error.message : String(error);
|
|
3935
|
+
}
|
|
3936
|
+
}
|
|
3937
|
+
if (result2.success && !result2.alreadyInstalled) {
|
|
3938
|
+
const autostart = maybeInstallAutostart();
|
|
3939
|
+
payload.autostart = autostart.status;
|
|
3940
|
+
if (!autostart.installed) {
|
|
3941
|
+
payload.autostartError = autostart.message;
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3944
|
+
return { success: result2.success, message: result2.message, data: payload };
|
|
3945
|
+
}
|
|
3946
|
+
log(result2.message);
|
|
3947
|
+
if (args.skillsMode === "none") {
|
|
3948
|
+
log("Skill installation skipped (--no-skills).");
|
|
3949
|
+
} else if (result2.success) {
|
|
3950
|
+
const skillsResult = installSkills(args.skillsMode);
|
|
3951
|
+
if (skillsResult.success) {
|
|
3952
|
+
log(skillsResult.message);
|
|
3953
|
+
} else {
|
|
3954
|
+
warn(skillsResult.message);
|
|
3955
|
+
}
|
|
3956
|
+
} else {
|
|
3957
|
+
warn("Skill installation skipped because plugin install failed.");
|
|
3958
|
+
}
|
|
3959
|
+
if (args.fullInstall && result2.success) {
|
|
3960
|
+
try {
|
|
3961
|
+
const extensionPath = extractExtension();
|
|
3962
|
+
if (extensionPath) {
|
|
3963
|
+
log(`Extension assets extracted to ${extensionPath}`);
|
|
3964
|
+
} else {
|
|
3965
|
+
warn("Extension assets not found; skipping extraction.");
|
|
3966
|
+
}
|
|
3967
|
+
} catch (error) {
|
|
3968
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3969
|
+
warn(`Extension pre-extraction failed: ${message}`);
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
if (result2.success && !result2.alreadyInstalled) {
|
|
3973
|
+
const autostart = maybeInstallAutostart();
|
|
3974
|
+
if (autostart.installed) {
|
|
3975
|
+
log(autostart.message);
|
|
3976
|
+
} else {
|
|
3977
|
+
warn(`Autostart install skipped: ${autostart.message}`);
|
|
3978
|
+
}
|
|
3979
|
+
}
|
|
3980
|
+
if (result2.success && !result2.alreadyInstalled) {
|
|
3981
|
+
log("\nNext steps:");
|
|
3982
|
+
log(" 1. Start or restart OpenCode");
|
|
3983
|
+
log(" 2. Use opendevbrowser_status to verify the plugin is loaded");
|
|
3984
|
+
log("\nFor help: npx opendevbrowser --help");
|
|
3985
|
+
}
|
|
3986
|
+
return { success: result2.success, message: result2.message };
|
|
3987
|
+
}
|
|
3988
|
+
});
|
|
3989
|
+
registerCommand({
|
|
3990
|
+
name: "serve",
|
|
3991
|
+
description: "Start or stop the local daemon",
|
|
3992
|
+
run: async () => runServe(args)
|
|
3993
|
+
});
|
|
3994
|
+
registerCommand({
|
|
3995
|
+
name: "daemon",
|
|
3996
|
+
description: "Install/uninstall/status daemon auto-start",
|
|
3997
|
+
run: async () => runDaemonCommand(args)
|
|
3998
|
+
});
|
|
3999
|
+
registerCommand({
|
|
4000
|
+
name: "native",
|
|
4001
|
+
description: "Install/uninstall/status native messaging host",
|
|
4002
|
+
run: async () => runNativeCommand(args)
|
|
4003
|
+
});
|
|
4004
|
+
registerCommand({
|
|
4005
|
+
name: "run",
|
|
4006
|
+
description: "Execute a JSON script in a single process",
|
|
4007
|
+
run: async () => runScriptCommand(args)
|
|
4008
|
+
});
|
|
4009
|
+
registerCommand({
|
|
4010
|
+
name: "launch",
|
|
4011
|
+
description: "Launch a managed browser session via daemon",
|
|
4012
|
+
run: async () => runSessionLaunch(args)
|
|
4013
|
+
});
|
|
4014
|
+
registerCommand({
|
|
4015
|
+
name: "connect",
|
|
4016
|
+
description: "Connect to an existing browser via daemon",
|
|
4017
|
+
run: async () => runSessionConnect(args)
|
|
4018
|
+
});
|
|
4019
|
+
registerCommand({
|
|
4020
|
+
name: "disconnect",
|
|
4021
|
+
description: "Disconnect a daemon session",
|
|
4022
|
+
run: async () => runSessionDisconnect(args)
|
|
4023
|
+
});
|
|
4024
|
+
registerCommand({
|
|
4025
|
+
name: "status",
|
|
4026
|
+
description: "Get daemon or session status",
|
|
4027
|
+
run: async () => runStatus(args)
|
|
4028
|
+
});
|
|
4029
|
+
registerCommand({
|
|
4030
|
+
name: "goto",
|
|
4031
|
+
description: "Navigate current session to a URL",
|
|
4032
|
+
run: async () => runGoto(args)
|
|
4033
|
+
});
|
|
4034
|
+
registerCommand({
|
|
4035
|
+
name: "wait",
|
|
4036
|
+
description: "Wait for load or a ref to appear",
|
|
4037
|
+
run: async () => runWait(args)
|
|
4038
|
+
});
|
|
4039
|
+
registerCommand({
|
|
4040
|
+
name: "snapshot",
|
|
4041
|
+
description: "Capture a snapshot of the active page",
|
|
4042
|
+
run: async () => runSnapshot(args)
|
|
4043
|
+
});
|
|
4044
|
+
registerCommand({
|
|
4045
|
+
name: "annotate",
|
|
4046
|
+
description: "Request interactive annotations (extension relay)",
|
|
4047
|
+
run: async () => runAnnotate(args)
|
|
4048
|
+
});
|
|
4049
|
+
registerCommand({
|
|
4050
|
+
name: "click",
|
|
4051
|
+
description: "Click an element by ref",
|
|
4052
|
+
run: async () => runClick(args)
|
|
4053
|
+
});
|
|
4054
|
+
registerCommand({
|
|
4055
|
+
name: "hover",
|
|
4056
|
+
description: "Hover an element by ref",
|
|
4057
|
+
run: async () => runHover(args)
|
|
4058
|
+
});
|
|
4059
|
+
registerCommand({
|
|
4060
|
+
name: "press",
|
|
4061
|
+
description: "Press a keyboard key",
|
|
4062
|
+
run: async () => runPress(args)
|
|
4063
|
+
});
|
|
4064
|
+
registerCommand({
|
|
4065
|
+
name: "check",
|
|
4066
|
+
description: "Check a checkbox by ref",
|
|
4067
|
+
run: async () => runCheck(args)
|
|
4068
|
+
});
|
|
4069
|
+
registerCommand({
|
|
4070
|
+
name: "uncheck",
|
|
4071
|
+
description: "Uncheck a checkbox by ref",
|
|
4072
|
+
run: async () => runUncheck(args)
|
|
4073
|
+
});
|
|
4074
|
+
registerCommand({
|
|
4075
|
+
name: "type",
|
|
4076
|
+
description: "Type into an element by ref",
|
|
4077
|
+
run: async () => runType(args)
|
|
4078
|
+
});
|
|
4079
|
+
registerCommand({
|
|
4080
|
+
name: "select",
|
|
4081
|
+
description: "Select values in a select by ref",
|
|
4082
|
+
run: async () => runSelect(args)
|
|
4083
|
+
});
|
|
4084
|
+
registerCommand({
|
|
4085
|
+
name: "scroll",
|
|
4086
|
+
description: "Scroll the page or element by ref",
|
|
4087
|
+
run: async () => runScroll(args)
|
|
4088
|
+
});
|
|
4089
|
+
registerCommand({
|
|
4090
|
+
name: "scroll-into-view",
|
|
4091
|
+
description: "Scroll an element into view by ref",
|
|
4092
|
+
run: async () => runScrollIntoView(args)
|
|
4093
|
+
});
|
|
4094
|
+
registerCommand({
|
|
4095
|
+
name: "targets-list",
|
|
4096
|
+
description: "List page targets",
|
|
4097
|
+
run: async () => runTargetsList(args)
|
|
4098
|
+
});
|
|
4099
|
+
registerCommand({
|
|
4100
|
+
name: "target-use",
|
|
4101
|
+
description: "Focus a target by id",
|
|
4102
|
+
run: async () => runTargetUse(args)
|
|
4103
|
+
});
|
|
4104
|
+
registerCommand({
|
|
4105
|
+
name: "target-new",
|
|
4106
|
+
description: "Open a new target",
|
|
4107
|
+
run: async () => runTargetNew(args)
|
|
4108
|
+
});
|
|
4109
|
+
registerCommand({
|
|
4110
|
+
name: "target-close",
|
|
4111
|
+
description: "Close a target by id",
|
|
4112
|
+
run: async () => runTargetClose(args)
|
|
4113
|
+
});
|
|
4114
|
+
registerCommand({
|
|
4115
|
+
name: "page",
|
|
4116
|
+
description: "Open or focus a named page",
|
|
4117
|
+
run: async () => runPageOpen(args)
|
|
4118
|
+
});
|
|
4119
|
+
registerCommand({
|
|
4120
|
+
name: "pages",
|
|
4121
|
+
description: "List named pages",
|
|
4122
|
+
run: async () => runPagesList(args)
|
|
4123
|
+
});
|
|
4124
|
+
registerCommand({
|
|
4125
|
+
name: "page-close",
|
|
4126
|
+
description: "Close a named page",
|
|
4127
|
+
run: async () => runPageClose(args)
|
|
4128
|
+
});
|
|
4129
|
+
registerCommand({
|
|
4130
|
+
name: "dom-html",
|
|
4131
|
+
description: "Capture HTML for a ref",
|
|
4132
|
+
run: async () => runDomHtml(args)
|
|
4133
|
+
});
|
|
4134
|
+
registerCommand({
|
|
4135
|
+
name: "dom-text",
|
|
4136
|
+
description: "Capture text for a ref",
|
|
4137
|
+
run: async () => runDomText(args)
|
|
4138
|
+
});
|
|
4139
|
+
registerCommand({
|
|
4140
|
+
name: "dom-attr",
|
|
4141
|
+
description: "Capture attribute value for a ref",
|
|
4142
|
+
run: async () => runDomAttr(args)
|
|
4143
|
+
});
|
|
4144
|
+
registerCommand({
|
|
4145
|
+
name: "dom-value",
|
|
4146
|
+
description: "Capture input value for a ref",
|
|
4147
|
+
run: async () => runDomValue(args)
|
|
4148
|
+
});
|
|
4149
|
+
registerCommand({
|
|
4150
|
+
name: "dom-visible",
|
|
4151
|
+
description: "Check visibility for a ref",
|
|
4152
|
+
run: async () => runDomVisible(args)
|
|
4153
|
+
});
|
|
4154
|
+
registerCommand({
|
|
4155
|
+
name: "dom-enabled",
|
|
4156
|
+
description: "Check enabled state for a ref",
|
|
4157
|
+
run: async () => runDomEnabled(args)
|
|
4158
|
+
});
|
|
4159
|
+
registerCommand({
|
|
4160
|
+
name: "dom-checked",
|
|
4161
|
+
description: "Check checked state for a ref",
|
|
4162
|
+
run: async () => runDomChecked(args)
|
|
4163
|
+
});
|
|
4164
|
+
registerCommand({
|
|
4165
|
+
name: "clone-page",
|
|
4166
|
+
description: "Clone the active page to React",
|
|
4167
|
+
run: async () => runClonePage(args)
|
|
4168
|
+
});
|
|
4169
|
+
registerCommand({
|
|
4170
|
+
name: "clone-component",
|
|
4171
|
+
description: "Clone a component by ref",
|
|
4172
|
+
run: async () => runCloneComponent(args)
|
|
4173
|
+
});
|
|
4174
|
+
registerCommand({
|
|
4175
|
+
name: "perf",
|
|
4176
|
+
description: "Capture performance metrics",
|
|
4177
|
+
run: async () => runPerf(args)
|
|
4178
|
+
});
|
|
4179
|
+
registerCommand({
|
|
4180
|
+
name: "screenshot",
|
|
4181
|
+
description: "Capture a screenshot",
|
|
4182
|
+
run: async () => runScreenshot(args)
|
|
4183
|
+
});
|
|
4184
|
+
registerCommand({
|
|
4185
|
+
name: "console-poll",
|
|
4186
|
+
description: "Poll console events",
|
|
4187
|
+
run: async () => runConsolePoll(args)
|
|
4188
|
+
});
|
|
4189
|
+
registerCommand({
|
|
4190
|
+
name: "network-poll",
|
|
4191
|
+
description: "Poll network events",
|
|
4192
|
+
run: async () => runNetworkPoll(args)
|
|
4193
|
+
});
|
|
4194
|
+
const command = getCommand(args.command);
|
|
4195
|
+
if (!command) {
|
|
4196
|
+
throw new Error(`Unknown command: ${args.command}`);
|
|
4197
|
+
}
|
|
4198
|
+
const result = await command.run(args);
|
|
4199
|
+
emitResult(result, result.data ? { data: result.data } : void 0);
|
|
4200
|
+
const exitCode = resolveExitCode(result);
|
|
4201
|
+
if (exitCode === null) {
|
|
4202
|
+
return;
|
|
4203
|
+
}
|
|
4204
|
+
process.exit(exitCode);
|
|
4205
|
+
} catch (error) {
|
|
4206
|
+
const format = outputFormat ?? detectOutputFormat(process.argv);
|
|
4207
|
+
const cliError = toCliError(error, parseSucceeded ? EXIT_EXECUTION : EXIT_USAGE);
|
|
4208
|
+
emitFatalError(cliError, format);
|
|
4209
|
+
process.exit(cliError.exitCode);
|
|
796
4210
|
}
|
|
797
4211
|
}
|
|
798
4212
|
main().catch((error) => {
|
|
799
|
-
|
|
800
|
-
process.
|
|
4213
|
+
const cliError = toCliError(error, EXIT_EXECUTION);
|
|
4214
|
+
emitFatalError(cliError, detectOutputFormat(process.argv));
|
|
4215
|
+
process.exit(cliError.exitCode);
|
|
801
4216
|
});
|
|
802
4217
|
//# sourceMappingURL=index.js.map
|