opendevbrowser 0.0.12 → 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 +216 -28
- package/dist/chunk-JVBMT2O5.js +7173 -0
- package/dist/chunk-JVBMT2O5.js.map +1 -0
- package/dist/cli/index.js +2486 -589
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +1057 -194
- package/dist/index.js.map +1 -1
- package/dist/opendevbrowser.js +1057 -194
- 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 +1194 -32
- 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 +370 -25
- package/extension/dist/relay-settings.js +1 -0
- 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/manifest.json +17 -3
- package/extension/popup.html +144 -0
- package/package.json +2 -2
- package/skills/AGENTS.md +34 -62
- 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-WTFSMBVH.js +0 -2815
- package/dist/chunk-WTFSMBVH.js.map +0 -1
- package/extension/dist/popup.jsx +0 -150
package/dist/cli/index.js
CHANGED
|
@@ -1,52 +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,
|
|
3
9
|
createOpenDevBrowserCore,
|
|
10
|
+
createUsageError,
|
|
4
11
|
extractExtension,
|
|
12
|
+
fetchDaemonStatusFromMetadata,
|
|
13
|
+
fetchWithTimeout,
|
|
14
|
+
formatErrorPayload,
|
|
5
15
|
generateSecureToken,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
var CliError = class extends Error {
|
|
15
|
-
exitCode;
|
|
16
|
-
constructor(message, exitCode) {
|
|
17
|
-
super(message);
|
|
18
|
-
this.exitCode = exitCode;
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
function createUsageError(message) {
|
|
22
|
-
return new CliError(message, EXIT_USAGE);
|
|
23
|
-
}
|
|
24
|
-
function createDisconnectedError(message) {
|
|
25
|
-
return new CliError(message, EXIT_DISCONNECTED);
|
|
26
|
-
}
|
|
27
|
-
function toCliError(error, fallbackExitCode = EXIT_EXECUTION) {
|
|
28
|
-
if (error instanceof CliError) {
|
|
29
|
-
return error;
|
|
30
|
-
}
|
|
31
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
32
|
-
return new CliError(message, fallbackExitCode);
|
|
33
|
-
}
|
|
34
|
-
function formatErrorPayload(error) {
|
|
35
|
-
return {
|
|
36
|
-
success: false,
|
|
37
|
-
error: error.message,
|
|
38
|
-
exitCode: error.exitCode
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
function resolveExitCode(result) {
|
|
42
|
-
if (result.exitCode === null) {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
if (typeof result.exitCode === "number") {
|
|
46
|
-
return result.exitCode;
|
|
47
|
-
}
|
|
48
|
-
return result.success ? EXIT_SUCCESS : EXIT_EXECUTION;
|
|
49
|
-
}
|
|
16
|
+
getExtensionPath,
|
|
17
|
+
loadGlobalConfig,
|
|
18
|
+
readDaemonMetadata,
|
|
19
|
+
resolveExitCode,
|
|
20
|
+
startDaemon,
|
|
21
|
+
toCliError,
|
|
22
|
+
writeFileAtomic
|
|
23
|
+
} from "../chunk-JVBMT2O5.js";
|
|
50
24
|
|
|
51
25
|
// src/cli/args.ts
|
|
52
26
|
var SHORT_FLAGS = {
|
|
@@ -61,13 +35,18 @@ function expandShortFlags(args) {
|
|
|
61
35
|
return args.map((arg) => SHORT_FLAGS[arg] ?? arg);
|
|
62
36
|
}
|
|
63
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
|
+
}
|
|
64
43
|
if (args.includes("--no-skills")) {
|
|
65
44
|
return "none";
|
|
66
45
|
}
|
|
67
|
-
if (
|
|
46
|
+
if (hasLocal) {
|
|
68
47
|
return "local";
|
|
69
48
|
}
|
|
70
|
-
if (
|
|
49
|
+
if (hasGlobal) {
|
|
71
50
|
return "global";
|
|
72
51
|
}
|
|
73
52
|
return "global";
|
|
@@ -89,21 +68,44 @@ function parseOutputFormat(args) {
|
|
|
89
68
|
}
|
|
90
69
|
throw createUsageError(`Invalid --output-format: ${value ?? "missing"}`);
|
|
91
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
|
+
}
|
|
92
88
|
function parseArgs(argv) {
|
|
93
89
|
let args = expandShortFlags(argv.slice(2));
|
|
94
90
|
let commandOverride = null;
|
|
95
91
|
if (args[0] && !args[0].startsWith("-")) {
|
|
96
92
|
const candidate = args[0];
|
|
97
|
-
if (candidate === "install" || candidate === "update" || candidate === "uninstall" || candidate === "help" || candidate === "version" || candidate === "serve" || candidate === "run" || candidate === "launch" || candidate === "connect" || candidate === "disconnect" || candidate === "status" || candidate === "goto" || candidate === "wait" || candidate === "snapshot" || candidate === "click" || candidate === "type" || candidate === "select" || candidate === "scroll") {
|
|
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") {
|
|
98
94
|
commandOverride = candidate;
|
|
99
95
|
args = args.slice(1);
|
|
100
96
|
} else {
|
|
101
97
|
throw createUsageError(`Unknown command: ${candidate}`);
|
|
102
98
|
}
|
|
103
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
|
+
}
|
|
104
105
|
const skillsMode = parseSkillsMode(args);
|
|
105
106
|
const fullInstall = args.includes("--full");
|
|
106
107
|
const outputFormat = parseOutputFormat(args);
|
|
108
|
+
const transport = commandOverride === "annotate" ? "relay" : parseTransport(args);
|
|
107
109
|
if (commandOverride === "help" || args.includes("--help") || args.includes("-h")) {
|
|
108
110
|
return {
|
|
109
111
|
command: "help",
|
|
@@ -112,6 +114,7 @@ function parseArgs(argv) {
|
|
|
112
114
|
noInteractive: false,
|
|
113
115
|
quiet: false,
|
|
114
116
|
outputFormat,
|
|
117
|
+
transport,
|
|
115
118
|
skillsMode,
|
|
116
119
|
fullInstall,
|
|
117
120
|
rawArgs: args
|
|
@@ -125,6 +128,7 @@ function parseArgs(argv) {
|
|
|
125
128
|
noInteractive: false,
|
|
126
129
|
quiet: false,
|
|
127
130
|
outputFormat,
|
|
131
|
+
transport,
|
|
128
132
|
skillsMode,
|
|
129
133
|
fullInstall,
|
|
130
134
|
rawArgs: args
|
|
@@ -140,6 +144,7 @@ function parseArgs(argv) {
|
|
|
140
144
|
noInteractive: false,
|
|
141
145
|
quiet: false,
|
|
142
146
|
outputFormat,
|
|
147
|
+
transport,
|
|
143
148
|
skillsMode,
|
|
144
149
|
fullInstall,
|
|
145
150
|
rawArgs: args
|
|
@@ -156,6 +161,7 @@ function parseArgs(argv) {
|
|
|
156
161
|
noInteractive: noPrompt2,
|
|
157
162
|
quiet: args.includes("--quiet"),
|
|
158
163
|
outputFormat,
|
|
164
|
+
transport,
|
|
159
165
|
skillsMode,
|
|
160
166
|
fullInstall,
|
|
161
167
|
rawArgs: args
|
|
@@ -215,16 +221,48 @@ function parseArgs(argv) {
|
|
|
215
221
|
"--submit",
|
|
216
222
|
"--values",
|
|
217
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",
|
|
218
235
|
"--no-extension",
|
|
219
236
|
"--extension-only",
|
|
237
|
+
"--extension-legacy",
|
|
220
238
|
"--wait-for-extension",
|
|
221
239
|
"--wait-timeout-ms",
|
|
222
240
|
"--skills-global",
|
|
223
241
|
"--skills-local",
|
|
224
|
-
"--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"
|
|
225
257
|
]);
|
|
226
258
|
for (const arg of args) {
|
|
227
259
|
if (arg.startsWith("--") && !validFlags.has(arg)) {
|
|
260
|
+
if (arg.includes("=")) {
|
|
261
|
+
const baseFlag = arg.split("=", 2)[0] ?? "";
|
|
262
|
+
if (validEqualsFlags.has(baseFlag)) {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
228
266
|
throw createUsageError(`Unknown flag: ${arg}`);
|
|
229
267
|
}
|
|
230
268
|
if (arg.startsWith("-") && !arg.startsWith("--") && !SHORT_FLAGS[arg]) {
|
|
@@ -239,6 +277,7 @@ function parseArgs(argv) {
|
|
|
239
277
|
noInteractive,
|
|
240
278
|
quiet,
|
|
241
279
|
outputFormat,
|
|
280
|
+
transport,
|
|
242
281
|
skillsMode,
|
|
243
282
|
fullInstall,
|
|
244
283
|
rawArgs: args
|
|
@@ -256,18 +295,46 @@ COMMANDS:
|
|
|
256
295
|
update Clear cached plugin to trigger reinstall
|
|
257
296
|
uninstall Remove plugin from config
|
|
258
297
|
serve Start or stop the local daemon
|
|
298
|
+
daemon Install/uninstall/status daemon auto-start
|
|
299
|
+
native Install/uninstall/status native messaging host
|
|
259
300
|
run Execute a JSON script in a single process
|
|
260
301
|
launch Launch a managed browser session via daemon
|
|
261
302
|
connect Connect to an existing browser via daemon
|
|
262
303
|
disconnect Disconnect a daemon session
|
|
263
|
-
status Get daemon session status
|
|
304
|
+
status Get daemon status (or session status with --session-id)
|
|
264
305
|
goto Navigate current session to a URL
|
|
265
306
|
wait Wait for load or a ref to appear
|
|
266
307
|
snapshot Capture a snapshot of the active page
|
|
267
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
|
|
268
313
|
type Type into an element by ref
|
|
269
314
|
select Select values in a select by ref
|
|
270
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)
|
|
271
338
|
help Show this help message
|
|
272
339
|
version Show version
|
|
273
340
|
|
|
@@ -286,6 +353,7 @@ INSTALL OPTIONS:
|
|
|
286
353
|
--no-interactive Alias of --no-prompt
|
|
287
354
|
--quiet Suppress non-error output
|
|
288
355
|
--output-format Output format: text (default), json, stream-json
|
|
356
|
+
--transport Transport: relay (default) or native
|
|
289
357
|
--skills-global Install bundled skills to ~/.config/opencode/skill (default)
|
|
290
358
|
--skills-local Install bundled skills to ./.opencode/skill
|
|
291
359
|
--no-skills Skip installing bundled skills
|
|
@@ -300,6 +368,7 @@ EXAMPLES:
|
|
|
300
368
|
npx opendevbrowser --no-skills # Skip skill installation
|
|
301
369
|
npx opendevbrowser --update # Update plugin
|
|
302
370
|
npx opendevbrowser --uninstall --global # Remove from global config
|
|
371
|
+
npx opendevbrowser native install <extension-id> # Install native host
|
|
303
372
|
`.trim();
|
|
304
373
|
}
|
|
305
374
|
function detectOutputFormat(argv) {
|
|
@@ -321,64 +390,32 @@ function getCommand(name) {
|
|
|
321
390
|
}
|
|
322
391
|
|
|
323
392
|
// src/cli/installers/global.ts
|
|
324
|
-
import * as
|
|
393
|
+
import * as fs3 from "fs";
|
|
325
394
|
|
|
326
395
|
// src/cli/utils/config.ts
|
|
327
|
-
import * as fs2 from "fs";
|
|
328
|
-
import * as path2 from "path";
|
|
329
|
-
import * as os from "os";
|
|
330
|
-
import { parse as parseJsonc, modify, applyEdits } from "jsonc-parser";
|
|
331
|
-
|
|
332
|
-
// src/utils/fs.ts
|
|
333
396
|
import * as fs from "fs";
|
|
334
397
|
import * as path from "path";
|
|
335
|
-
import * as
|
|
336
|
-
|
|
337
|
-
const { encoding = "utf-8", mode } = options;
|
|
338
|
-
const dir = path.dirname(filePath);
|
|
339
|
-
const hash = crypto.randomBytes(8).toString("hex");
|
|
340
|
-
const tempPath = path.join(dir, `.${path.basename(filePath)}.${process.pid}.${hash}.tmp`);
|
|
341
|
-
try {
|
|
342
|
-
if (!fs.existsSync(dir)) {
|
|
343
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
344
|
-
}
|
|
345
|
-
const writeOptions = { encoding };
|
|
346
|
-
if (mode !== void 0) {
|
|
347
|
-
writeOptions.mode = mode;
|
|
348
|
-
}
|
|
349
|
-
fs.writeFileSync(tempPath, content, writeOptions);
|
|
350
|
-
fs.renameSync(tempPath, filePath);
|
|
351
|
-
} catch (error) {
|
|
352
|
-
try {
|
|
353
|
-
if (fs.existsSync(tempPath)) {
|
|
354
|
-
fs.unlinkSync(tempPath);
|
|
355
|
-
}
|
|
356
|
-
} catch {
|
|
357
|
-
}
|
|
358
|
-
throw error;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// src/cli/utils/config.ts
|
|
398
|
+
import * as os from "os";
|
|
399
|
+
import { parse as parseJsonc, modify, applyEdits } from "jsonc-parser";
|
|
363
400
|
var PLUGIN_NAME = "opendevbrowser";
|
|
364
401
|
var SCHEMA_URL = "https://opencode.ai/config.json";
|
|
365
402
|
function getGlobalConfigPath() {
|
|
366
|
-
const configDir = process.env.OPENCODE_CONFIG_DIR ||
|
|
367
|
-
return
|
|
403
|
+
const configDir = process.env.OPENCODE_CONFIG_DIR || path.join(os.homedir(), ".config", "opencode");
|
|
404
|
+
return path.join(configDir, "opencode.json");
|
|
368
405
|
}
|
|
369
406
|
function getLocalConfigPath() {
|
|
370
|
-
return
|
|
407
|
+
return path.join(process.cwd(), "opencode.json");
|
|
371
408
|
}
|
|
372
409
|
function ensureDir(dirPath) {
|
|
373
|
-
if (!
|
|
374
|
-
|
|
410
|
+
if (!fs.existsSync(dirPath)) {
|
|
411
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
375
412
|
}
|
|
376
413
|
}
|
|
377
414
|
function readConfig(configPath) {
|
|
378
|
-
if (!
|
|
415
|
+
if (!fs.existsSync(configPath)) {
|
|
379
416
|
return { content: "", config: {} };
|
|
380
417
|
}
|
|
381
|
-
const content =
|
|
418
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
382
419
|
const errors = [];
|
|
383
420
|
const parsed = parseJsonc(content, errors, { allowTrailingComma: true });
|
|
384
421
|
if (errors.length > 0) {
|
|
@@ -430,10 +467,10 @@ function removePluginFromContent(content, pluginName = PLUGIN_NAME) {
|
|
|
430
467
|
}
|
|
431
468
|
|
|
432
469
|
// src/cli/templates/config.ts
|
|
433
|
-
import * as
|
|
434
|
-
import * as
|
|
470
|
+
import * as fs2 from "fs";
|
|
471
|
+
import * as path2 from "path";
|
|
435
472
|
import * as os2 from "os";
|
|
436
|
-
function buildConfigTemplate(
|
|
473
|
+
function buildConfigTemplate(relayToken, daemonToken) {
|
|
437
474
|
return `{
|
|
438
475
|
// OpenDevBrowser Plugin Configuration
|
|
439
476
|
// See: https://github.com/anthropics/opendevbrowser#configuration
|
|
@@ -497,7 +534,9 @@ function buildConfigTemplate(token) {
|
|
|
497
534
|
},
|
|
498
535
|
|
|
499
536
|
"relayPort": 8787,
|
|
500
|
-
"relayToken": "${
|
|
537
|
+
"relayToken": "${relayToken}",
|
|
538
|
+
"daemonPort": 8788,
|
|
539
|
+
"daemonToken": "${daemonToken}",
|
|
501
540
|
|
|
502
541
|
"flags": [],
|
|
503
542
|
|
|
@@ -507,22 +546,23 @@ function buildConfigTemplate(token) {
|
|
|
507
546
|
}
|
|
508
547
|
function getPluginConfigPath(mode) {
|
|
509
548
|
if (mode === "global") {
|
|
510
|
-
const configDir = process.env.OPENCODE_CONFIG_DIR ||
|
|
511
|
-
return
|
|
549
|
+
const configDir = process.env.OPENCODE_CONFIG_DIR || path2.join(os2.homedir(), ".config", "opencode");
|
|
550
|
+
return path2.join(configDir, "opendevbrowser.jsonc");
|
|
512
551
|
}
|
|
513
|
-
return
|
|
552
|
+
return path2.join(process.cwd(), "opendevbrowser.jsonc");
|
|
514
553
|
}
|
|
515
554
|
function createPluginConfig(mode) {
|
|
516
555
|
const configPath = getPluginConfigPath(mode);
|
|
517
|
-
if (
|
|
556
|
+
if (fs2.existsSync(configPath)) {
|
|
518
557
|
return { created: false, path: configPath };
|
|
519
558
|
}
|
|
520
|
-
const dir =
|
|
521
|
-
if (!
|
|
522
|
-
|
|
559
|
+
const dir = path2.dirname(configPath);
|
|
560
|
+
if (!fs2.existsSync(dir)) {
|
|
561
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
523
562
|
}
|
|
524
|
-
const
|
|
525
|
-
|
|
563
|
+
const relayToken = generateSecureToken();
|
|
564
|
+
const daemonToken = generateSecureToken();
|
|
565
|
+
writeFileAtomic(configPath, buildConfigTemplate(relayToken, daemonToken));
|
|
526
566
|
return { created: true, path: configPath };
|
|
527
567
|
}
|
|
528
568
|
|
|
@@ -542,7 +582,7 @@ function installGlobal(withConfig = false) {
|
|
|
542
582
|
}
|
|
543
583
|
const newContent = updateConfigContent(content, "opendevbrowser");
|
|
544
584
|
ensureDir(configPath.replace(/[/\\][^/\\]+$/, ""));
|
|
545
|
-
|
|
585
|
+
fs3.writeFileSync(configPath, newContent, "utf-8");
|
|
546
586
|
if (withConfig) {
|
|
547
587
|
createPluginConfig("global");
|
|
548
588
|
}
|
|
@@ -566,7 +606,7 @@ function installGlobal(withConfig = false) {
|
|
|
566
606
|
}
|
|
567
607
|
|
|
568
608
|
// src/cli/installers/local.ts
|
|
569
|
-
import * as
|
|
609
|
+
import * as fs4 from "fs";
|
|
570
610
|
function installLocal(withConfig = false) {
|
|
571
611
|
const configPath = getLocalConfigPath();
|
|
572
612
|
try {
|
|
@@ -581,7 +621,7 @@ function installLocal(withConfig = false) {
|
|
|
581
621
|
};
|
|
582
622
|
}
|
|
583
623
|
const newContent = updateConfigContent(content, "opendevbrowser");
|
|
584
|
-
|
|
624
|
+
fs4.writeFileSync(configPath, newContent, "utf-8");
|
|
585
625
|
if (withConfig) {
|
|
586
626
|
createPluginConfig("local");
|
|
587
627
|
}
|
|
@@ -605,12 +645,12 @@ function installLocal(withConfig = false) {
|
|
|
605
645
|
}
|
|
606
646
|
|
|
607
647
|
// src/cli/installers/skills.ts
|
|
608
|
-
import * as fs7 from "fs";
|
|
609
|
-
import * as path5 from "path";
|
|
610
|
-
|
|
611
|
-
// src/cli/utils/skills.ts
|
|
612
648
|
import * as fs6 from "fs";
|
|
613
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";
|
|
614
654
|
import * as os3 from "os";
|
|
615
655
|
import { fileURLToPath } from "url";
|
|
616
656
|
var PACKAGE_NAME = "opendevbrowser";
|
|
@@ -619,17 +659,17 @@ var cachedPackageRoot = null;
|
|
|
619
659
|
function findPackageRoot(startDir) {
|
|
620
660
|
let current = startDir;
|
|
621
661
|
while (true) {
|
|
622
|
-
const pkgPath =
|
|
623
|
-
if (
|
|
662
|
+
const pkgPath = path3.join(current, "package.json");
|
|
663
|
+
if (fs5.existsSync(pkgPath)) {
|
|
624
664
|
try {
|
|
625
|
-
const parsed = JSON.parse(
|
|
665
|
+
const parsed = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
|
|
626
666
|
if (parsed.name === PACKAGE_NAME) {
|
|
627
667
|
return current;
|
|
628
668
|
}
|
|
629
669
|
} catch {
|
|
630
670
|
}
|
|
631
671
|
}
|
|
632
|
-
const parent =
|
|
672
|
+
const parent = path3.dirname(current);
|
|
633
673
|
if (parent === current) {
|
|
634
674
|
break;
|
|
635
675
|
}
|
|
@@ -639,23 +679,23 @@ function findPackageRoot(startDir) {
|
|
|
639
679
|
}
|
|
640
680
|
function getPackageRoot() {
|
|
641
681
|
if (cachedPackageRoot) return cachedPackageRoot;
|
|
642
|
-
const moduleDir =
|
|
682
|
+
const moduleDir = path3.dirname(fileURLToPath(import.meta.url));
|
|
643
683
|
cachedPackageRoot = findPackageRoot(moduleDir);
|
|
644
684
|
return cachedPackageRoot;
|
|
645
685
|
}
|
|
646
686
|
function getBundledSkillsDir() {
|
|
647
|
-
const skillsDir =
|
|
648
|
-
if (!
|
|
687
|
+
const skillsDir = path3.join(getPackageRoot(), "skills");
|
|
688
|
+
if (!fs5.existsSync(skillsDir)) {
|
|
649
689
|
throw new Error(`Bundled skills directory not found at ${skillsDir}`);
|
|
650
690
|
}
|
|
651
691
|
return skillsDir;
|
|
652
692
|
}
|
|
653
693
|
function getGlobalSkillDir() {
|
|
654
|
-
const configDir = process.env.OPENCODE_CONFIG_DIR ||
|
|
655
|
-
return
|
|
694
|
+
const configDir = process.env.OPENCODE_CONFIG_DIR || path3.join(os3.homedir(), ".config", "opencode");
|
|
695
|
+
return path3.join(configDir, SKILL_DIR_NAME);
|
|
656
696
|
}
|
|
657
697
|
function getLocalSkillDir() {
|
|
658
|
-
return
|
|
698
|
+
return path3.join(process.cwd(), ".opencode", SKILL_DIR_NAME);
|
|
659
699
|
}
|
|
660
700
|
|
|
661
701
|
// src/cli/installers/skills.ts
|
|
@@ -665,18 +705,18 @@ function installSkills(mode) {
|
|
|
665
705
|
const skipped = [];
|
|
666
706
|
try {
|
|
667
707
|
const sourceDir = getBundledSkillsDir();
|
|
668
|
-
const entries =
|
|
708
|
+
const entries = fs6.readdirSync(sourceDir, { withFileTypes: true });
|
|
669
709
|
ensureDir(targetDir);
|
|
670
710
|
for (const entry of entries) {
|
|
671
711
|
if (!entry.isDirectory()) continue;
|
|
672
712
|
const skillName = entry.name;
|
|
673
|
-
const sourcePath =
|
|
674
|
-
const targetPath =
|
|
675
|
-
if (
|
|
713
|
+
const sourcePath = path4.join(sourceDir, skillName);
|
|
714
|
+
const targetPath = path4.join(targetDir, skillName);
|
|
715
|
+
if (fs6.existsSync(targetPath)) {
|
|
676
716
|
skipped.push(skillName);
|
|
677
717
|
continue;
|
|
678
718
|
}
|
|
679
|
-
|
|
719
|
+
fs6.cpSync(sourcePath, targetPath, { recursive: true });
|
|
680
720
|
installed.push(skillName);
|
|
681
721
|
}
|
|
682
722
|
const summary = `Skills ${mode} install: ${installed.length} installed${skipped.length ? `, ${skipped.length} skipped` : ""} (${targetDir})`;
|
|
@@ -700,31 +740,31 @@ function installSkills(mode) {
|
|
|
700
740
|
}
|
|
701
741
|
|
|
702
742
|
// src/cli/commands/update.ts
|
|
703
|
-
import * as
|
|
704
|
-
import * as
|
|
743
|
+
import * as fs7 from "fs";
|
|
744
|
+
import * as path5 from "path";
|
|
705
745
|
import * as os4 from "os";
|
|
706
746
|
var PLUGIN_NAME2 = "opendevbrowser";
|
|
707
747
|
function getCacheDir() {
|
|
708
|
-
return process.env.OPENCODE_CACHE_DIR ||
|
|
748
|
+
return process.env.OPENCODE_CACHE_DIR || path5.join(os4.homedir(), ".cache", "opencode");
|
|
709
749
|
}
|
|
710
750
|
function rmdir(dirPath) {
|
|
711
751
|
const cacheDir = getCacheDir();
|
|
712
|
-
const resolvedCache =
|
|
713
|
-
const resolvedPath =
|
|
714
|
-
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) {
|
|
715
755
|
throw new Error(`Security: refusing to delete path outside cache directory: ${dirPath}`);
|
|
716
756
|
}
|
|
717
|
-
if (
|
|
718
|
-
|
|
757
|
+
if (fs7.existsSync(dirPath)) {
|
|
758
|
+
fs7.rmSync(dirPath, { recursive: true, force: true });
|
|
719
759
|
}
|
|
720
760
|
}
|
|
721
761
|
function runUpdate() {
|
|
722
762
|
const cacheDir = getCacheDir();
|
|
723
|
-
const nodeModulesDir =
|
|
724
|
-
const pluginCacheDir =
|
|
763
|
+
const nodeModulesDir = path5.join(cacheDir, "node_modules");
|
|
764
|
+
const pluginCacheDir = path5.join(nodeModulesDir, PLUGIN_NAME2);
|
|
725
765
|
try {
|
|
726
|
-
if (!
|
|
727
|
-
if (
|
|
766
|
+
if (!fs7.existsSync(pluginCacheDir)) {
|
|
767
|
+
if (fs7.existsSync(nodeModulesDir)) {
|
|
728
768
|
rmdir(nodeModulesDir);
|
|
729
769
|
return {
|
|
730
770
|
success: true,
|
|
@@ -755,20 +795,20 @@ function runUpdate() {
|
|
|
755
795
|
}
|
|
756
796
|
|
|
757
797
|
// src/cli/commands/uninstall.ts
|
|
758
|
-
import * as
|
|
759
|
-
import * as
|
|
798
|
+
import * as fs8 from "fs";
|
|
799
|
+
import * as path6 from "path";
|
|
760
800
|
import * as os5 from "os";
|
|
761
801
|
function getPluginConfigPath2(mode) {
|
|
762
802
|
if (mode === "global") {
|
|
763
|
-
const configDir = process.env.OPENCODE_CONFIG_DIR ||
|
|
764
|
-
return
|
|
803
|
+
const configDir = process.env.OPENCODE_CONFIG_DIR || path6.join(os5.homedir(), ".config", "opencode");
|
|
804
|
+
return path6.join(configDir, "opendevbrowser.jsonc");
|
|
765
805
|
}
|
|
766
|
-
return
|
|
806
|
+
return path6.join(process.cwd(), "opendevbrowser.jsonc");
|
|
767
807
|
}
|
|
768
808
|
function removePluginConfigFile(mode) {
|
|
769
809
|
const configPath = getPluginConfigPath2(mode);
|
|
770
|
-
if (
|
|
771
|
-
|
|
810
|
+
if (fs8.existsSync(configPath)) {
|
|
811
|
+
fs8.unlinkSync(configPath);
|
|
772
812
|
return true;
|
|
773
813
|
}
|
|
774
814
|
return false;
|
|
@@ -787,7 +827,7 @@ function runUninstall(mode, deleteConfigFile = false) {
|
|
|
787
827
|
};
|
|
788
828
|
}
|
|
789
829
|
const newContent = removePluginFromContent(content, "opendevbrowser");
|
|
790
|
-
|
|
830
|
+
fs8.writeFileSync(configPath, newContent, "utf-8");
|
|
791
831
|
let configFileDeleted = false;
|
|
792
832
|
if (deleteConfigFile) {
|
|
793
833
|
configFileDeleted = removePluginConfigFile(mode);
|
|
@@ -826,331 +866,399 @@ function findInstalledConfigs() {
|
|
|
826
866
|
return { global, local };
|
|
827
867
|
}
|
|
828
868
|
|
|
829
|
-
// src/cli/
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
return launchWithRelay(core, params);
|
|
842
|
-
case "session.connect":
|
|
843
|
-
return core.manager.connect({
|
|
844
|
-
wsEndpoint: optionalString(params.wsEndpoint),
|
|
845
|
-
host: optionalString(params.host),
|
|
846
|
-
port: optionalNumber(params.port)
|
|
847
|
-
});
|
|
848
|
-
case "session.disconnect":
|
|
849
|
-
await core.manager.disconnect(requireString(params.sessionId, "sessionId"), optionalBoolean(params.closeBrowser) ?? false);
|
|
850
|
-
return { ok: true };
|
|
851
|
-
case "session.status":
|
|
852
|
-
return core.manager.status(requireString(params.sessionId, "sessionId"));
|
|
853
|
-
case "nav.goto":
|
|
854
|
-
return core.manager.goto(
|
|
855
|
-
requireString(params.sessionId, "sessionId"),
|
|
856
|
-
requireString(params.url, "url"),
|
|
857
|
-
requireWaitUntil(params.waitUntil),
|
|
858
|
-
optionalNumber(params.timeoutMs) ?? 3e4
|
|
859
|
-
);
|
|
860
|
-
case "nav.wait":
|
|
861
|
-
if (typeof params.ref === "string") {
|
|
862
|
-
return core.manager.waitForRef(
|
|
863
|
-
requireString(params.sessionId, "sessionId"),
|
|
864
|
-
requireString(params.ref, "ref"),
|
|
865
|
-
requireState(params.state),
|
|
866
|
-
optionalNumber(params.timeoutMs) ?? 3e4
|
|
867
|
-
);
|
|
868
|
-
}
|
|
869
|
-
return core.manager.waitForLoad(
|
|
870
|
-
requireString(params.sessionId, "sessionId"),
|
|
871
|
-
requireWaitUntil(params.until),
|
|
872
|
-
optionalNumber(params.timeoutMs) ?? 3e4
|
|
873
|
-
);
|
|
874
|
-
case "nav.snapshot":
|
|
875
|
-
return core.manager.snapshot(
|
|
876
|
-
requireString(params.sessionId, "sessionId"),
|
|
877
|
-
requireSnapshotMode(params.mode),
|
|
878
|
-
optionalNumber(params.maxChars) ?? 16e3,
|
|
879
|
-
optionalString(params.cursor)
|
|
880
|
-
);
|
|
881
|
-
case "interact.click":
|
|
882
|
-
return core.manager.click(
|
|
883
|
-
requireString(params.sessionId, "sessionId"),
|
|
884
|
-
requireString(params.ref, "ref")
|
|
885
|
-
);
|
|
886
|
-
case "interact.type":
|
|
887
|
-
return core.manager.type(
|
|
888
|
-
requireString(params.sessionId, "sessionId"),
|
|
889
|
-
requireString(params.ref, "ref"),
|
|
890
|
-
requireString(params.text, "text"),
|
|
891
|
-
optionalBoolean(params.clear) ?? false,
|
|
892
|
-
optionalBoolean(params.submit) ?? false
|
|
893
|
-
);
|
|
894
|
-
case "interact.select":
|
|
895
|
-
return core.manager.select(
|
|
896
|
-
requireString(params.sessionId, "sessionId"),
|
|
897
|
-
requireString(params.ref, "ref"),
|
|
898
|
-
requireStringArray(params.values, "values")
|
|
899
|
-
);
|
|
900
|
-
case "interact.scroll":
|
|
901
|
-
return core.manager.scroll(
|
|
902
|
-
requireString(params.sessionId, "sessionId"),
|
|
903
|
-
optionalNumber(params.dy) ?? 0,
|
|
904
|
-
optionalString(params.ref)
|
|
905
|
-
);
|
|
906
|
-
default:
|
|
907
|
-
throw new Error(`Unknown daemon command: ${request.name}`);
|
|
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}`);
|
|
874
|
+
}
|
|
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}`);
|
|
908
881
|
}
|
|
882
|
+
if (typeof options.max === "number" && parsed > options.max) {
|
|
883
|
+
throw createUsageError(`Invalid ${flag}: ${value}`);
|
|
884
|
+
}
|
|
885
|
+
return parsed;
|
|
909
886
|
}
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
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>");
|
|
922
906
|
}
|
|
923
|
-
const
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
throw new Error("Extension not connected; use --no-extension to launch a new browser.");
|
|
907
|
+
const normalized = normalizeExtensionId(value);
|
|
908
|
+
if (!normalized) {
|
|
909
|
+
throw createUsageError("Invalid extension ID format. Expected 32 characters (a-p).");
|
|
927
910
|
}
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
throw error instanceof Error ? error : new Error("Extension relay connection failed.");
|
|
935
|
-
}
|
|
936
|
-
relayWarning = "Relay connection failed; falling back to managed Chrome.";
|
|
937
|
-
}
|
|
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]");
|
|
938
917
|
}
|
|
939
|
-
if (
|
|
940
|
-
|
|
918
|
+
if (subcommand === "install") {
|
|
919
|
+
const extensionId = requireExtensionId(rawArgs[1]);
|
|
920
|
+
return { subcommand, extensionId };
|
|
941
921
|
}
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
flags: optionalStringArray(params.flags),
|
|
948
|
-
persistProfile: optionalBoolean(params.persistProfile)
|
|
949
|
-
});
|
|
950
|
-
const warnings = [
|
|
951
|
-
...result.warnings ?? [],
|
|
952
|
-
...relayWarning ? [relayWarning] : []
|
|
953
|
-
];
|
|
954
|
-
return { ...result, warnings };
|
|
955
|
-
}
|
|
956
|
-
function requireString(value, label) {
|
|
957
|
-
if (typeof value !== "string" || !value.trim()) {
|
|
958
|
-
throw new Error(`Missing ${label}`);
|
|
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");
|
|
959
927
|
}
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
function requireStringArray(value, label) {
|
|
963
|
-
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
964
|
-
throw new Error(`Invalid ${label}`);
|
|
928
|
+
if (process.platform === "linux") {
|
|
929
|
+
return path7.join(process.env.HOME || "", ".config", "google-chrome", "NativeMessagingHosts");
|
|
965
930
|
}
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : void 0;
|
|
973
|
-
}
|
|
974
|
-
function optionalNumber(value) {
|
|
975
|
-
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
976
|
-
}
|
|
977
|
-
function optionalBoolean(value) {
|
|
978
|
-
return typeof value === "boolean" ? value : void 0;
|
|
979
|
-
}
|
|
980
|
-
function requireWaitUntil(value) {
|
|
981
|
-
if (value === "domcontentloaded" || value === "load" || value === "networkidle") {
|
|
982
|
-
return value;
|
|
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");
|
|
983
937
|
}
|
|
984
|
-
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
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;
|
|
951
|
+
}
|
|
952
|
+
const parent = path7.dirname(current);
|
|
953
|
+
if (parent === current) {
|
|
954
|
+
break;
|
|
955
|
+
}
|
|
956
|
+
current = parent;
|
|
999
957
|
}
|
|
1000
|
-
await new Promise((resolve2) => setTimeout(resolve2, 500));
|
|
1001
958
|
}
|
|
1002
|
-
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
return
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
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 };
|
|
982
|
+
}
|
|
983
|
+
};
|
|
984
|
+
var runScript = (script, args) => {
|
|
985
|
+
if (process.platform === "win32") {
|
|
986
|
+
execFileSync("powershell", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", script, ...args], { stdio: "pipe" });
|
|
987
|
+
return;
|
|
1018
988
|
}
|
|
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";
|
|
1019
994
|
try {
|
|
1020
|
-
const
|
|
1021
|
-
|
|
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;
|
|
1022
1004
|
} catch {
|
|
1023
1005
|
return null;
|
|
1024
1006
|
}
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
const metadataPath = getDaemonMetadataPath();
|
|
1028
|
-
mkdirSync4(join8(getCacheRoot()), { recursive: true });
|
|
1029
|
-
writeFileSync5(metadataPath, JSON.stringify(state, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1030
|
-
}
|
|
1031
|
-
function clearDaemonMetadata() {
|
|
1032
|
-
const metadataPath = getDaemonMetadataPath();
|
|
1007
|
+
};
|
|
1008
|
+
var normalizePath = (value) => {
|
|
1033
1009
|
try {
|
|
1034
|
-
|
|
1010
|
+
return fs9.realpathSync(value);
|
|
1035
1011
|
} catch {
|
|
1012
|
+
return path7.resolve(value);
|
|
1036
1013
|
}
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
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
|
+
];
|
|
1042
1022
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
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
|
+
];
|
|
1049
1029
|
}
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
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 {
|
|
1073
1057
|
}
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
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;
|
|
1082
1070
|
}
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1071
|
+
for (const value of Object.values(commandMap)) {
|
|
1072
|
+
if (typeof value !== "object" || value === null) {
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
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;
|
|
1080
|
+
}
|
|
1087
1081
|
}
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
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" };
|
|
1096
1100
|
}
|
|
1097
|
-
return;
|
|
1098
1101
|
}
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
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
|
|
1119
1159
|
};
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
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;
|
|
1173
|
+
}
|
|
1174
|
+
if (match.matchedBy === "path") {
|
|
1175
|
+
return { extensionId: match.id, matchedBy: match.matchedBy };
|
|
1176
|
+
}
|
|
1177
|
+
if (!nameFallback) {
|
|
1178
|
+
nameFallback = match;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
if (!commandFallback) {
|
|
1182
|
+
commandFallback = findExtensionIdInCommands(preferences);
|
|
1143
1183
|
}
|
|
1144
|
-
resolve2(parsed);
|
|
1145
|
-
} catch (error) {
|
|
1146
|
-
reject(error);
|
|
1147
1184
|
}
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
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 };
|
|
1151
1258
|
}
|
|
1152
1259
|
|
|
1153
1260
|
// src/cli/commands/serve.ts
|
|
1261
|
+
var daemonHandle = null;
|
|
1154
1262
|
function parseServeArgs(rawArgs) {
|
|
1155
1263
|
const parsed = { stop: false };
|
|
1156
1264
|
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
@@ -1164,12 +1272,16 @@ function parseServeArgs(rawArgs) {
|
|
|
1164
1272
|
if (!value) {
|
|
1165
1273
|
throw createUsageError("Missing value for --port");
|
|
1166
1274
|
}
|
|
1167
|
-
parsed.port =
|
|
1275
|
+
parsed.port = parseNumberFlag(value, "--port", { min: 1, max: 65535 });
|
|
1168
1276
|
i += 1;
|
|
1169
1277
|
continue;
|
|
1170
1278
|
}
|
|
1171
1279
|
if (arg?.startsWith("--port=")) {
|
|
1172
|
-
|
|
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 });
|
|
1173
1285
|
continue;
|
|
1174
1286
|
}
|
|
1175
1287
|
if (arg === "--token") {
|
|
@@ -1182,7 +1294,11 @@ function parseServeArgs(rawArgs) {
|
|
|
1182
1294
|
continue;
|
|
1183
1295
|
}
|
|
1184
1296
|
if (arg?.startsWith("--token=")) {
|
|
1185
|
-
|
|
1297
|
+
const value = arg.split("=", 2)[1];
|
|
1298
|
+
if (!value) {
|
|
1299
|
+
throw createUsageError("Missing value for --token");
|
|
1300
|
+
}
|
|
1301
|
+
parsed.token = value;
|
|
1186
1302
|
continue;
|
|
1187
1303
|
}
|
|
1188
1304
|
}
|
|
@@ -1193,10 +1309,15 @@ async function runServe(args) {
|
|
|
1193
1309
|
if (serveArgs.stop) {
|
|
1194
1310
|
const metadata = readDaemonMetadata();
|
|
1195
1311
|
if (!metadata) {
|
|
1312
|
+
if (daemonHandle) {
|
|
1313
|
+
await daemonHandle.stop();
|
|
1314
|
+
daemonHandle = null;
|
|
1315
|
+
return { success: true, message: "Daemon stopped." };
|
|
1316
|
+
}
|
|
1196
1317
|
return { success: false, message: "Daemon not running.", exitCode: EXIT_DISCONNECTED };
|
|
1197
1318
|
}
|
|
1198
1319
|
try {
|
|
1199
|
-
const response = await
|
|
1320
|
+
const response = await fetchWithTimeout(`http://127.0.0.1:${metadata.port}/stop`, {
|
|
1200
1321
|
method: "POST",
|
|
1201
1322
|
headers: { Authorization: `Bearer ${metadata.token}` }
|
|
1202
1323
|
});
|
|
@@ -1205,22 +1326,367 @@ async function runServe(args) {
|
|
|
1205
1326
|
}
|
|
1206
1327
|
return { success: true, message: "Daemon stopped." };
|
|
1207
1328
|
} catch (error) {
|
|
1208
|
-
const
|
|
1209
|
-
return { success: false, message: `Failed to stop daemon: ${
|
|
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.";
|
|
1210
1351
|
}
|
|
1211
1352
|
}
|
|
1212
|
-
const
|
|
1353
|
+
const handle = await startDaemon({
|
|
1213
1354
|
port: serveArgs.port,
|
|
1214
|
-
token: serveArgs.token
|
|
1355
|
+
token: serveArgs.token,
|
|
1356
|
+
config
|
|
1215
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;
|
|
1216
1363
|
return {
|
|
1217
1364
|
success: true,
|
|
1218
|
-
message
|
|
1219
|
-
data: { port: state.port, pid: state.pid, relayPort: state.relayPort },
|
|
1365
|
+
message,
|
|
1366
|
+
data: { port: state.port, pid: state.pid, relayPort: state.relayPort, native: nativeStatus },
|
|
1220
1367
|
exitCode: null
|
|
1221
1368
|
};
|
|
1222
1369
|
}
|
|
1223
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" });
|
|
1468
|
+
} catch (error) {
|
|
1469
|
+
if (ignoreFailure) return;
|
|
1470
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
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
|
+
|
|
1224
1690
|
// src/cli/commands/run.ts
|
|
1225
1691
|
import { readFileSync as readFileSync4 } from "fs";
|
|
1226
1692
|
|
|
@@ -1302,7 +1768,9 @@ function parseRunArgs(rawArgs) {
|
|
|
1302
1768
|
continue;
|
|
1303
1769
|
}
|
|
1304
1770
|
if (arg?.startsWith("--start-url=")) {
|
|
1305
|
-
|
|
1771
|
+
const value = arg.split("=", 2)[1];
|
|
1772
|
+
if (!value) throw createUsageError("Missing value for --start-url");
|
|
1773
|
+
parsed.startUrl = value;
|
|
1306
1774
|
continue;
|
|
1307
1775
|
}
|
|
1308
1776
|
if (arg === "--flag") {
|
|
@@ -1313,20 +1781,22 @@ function parseRunArgs(rawArgs) {
|
|
|
1313
1781
|
continue;
|
|
1314
1782
|
}
|
|
1315
1783
|
if (arg?.startsWith("--flag=")) {
|
|
1316
|
-
|
|
1784
|
+
const value = arg.split("=", 2)[1];
|
|
1785
|
+
if (!value) throw createUsageError("Missing value for --flag");
|
|
1786
|
+
parsed.flags.push(value);
|
|
1317
1787
|
continue;
|
|
1318
1788
|
}
|
|
1319
1789
|
}
|
|
1320
1790
|
return parsed;
|
|
1321
1791
|
}
|
|
1322
1792
|
function readScriptFromStdin() {
|
|
1323
|
-
return new Promise((
|
|
1793
|
+
return new Promise((resolve4, reject) => {
|
|
1324
1794
|
let data = "";
|
|
1325
1795
|
process.stdin.setEncoding("utf8");
|
|
1326
1796
|
process.stdin.on("data", (chunk) => {
|
|
1327
1797
|
data += chunk;
|
|
1328
1798
|
});
|
|
1329
|
-
process.stdin.on("end", () =>
|
|
1799
|
+
process.stdin.on("end", () => resolve4(data));
|
|
1330
1800
|
process.stdin.on("error", reject);
|
|
1331
1801
|
});
|
|
1332
1802
|
}
|
|
@@ -1380,36 +1850,6 @@ async function runScriptCommand(args) {
|
|
|
1380
1850
|
}
|
|
1381
1851
|
}
|
|
1382
1852
|
|
|
1383
|
-
// src/cli/client.ts
|
|
1384
|
-
async function callDaemon(command, params) {
|
|
1385
|
-
const metadata = readDaemonMetadata();
|
|
1386
|
-
if (!metadata) {
|
|
1387
|
-
throw createDisconnectedError("Daemon not running. Start with `opendevbrowser serve`.");
|
|
1388
|
-
}
|
|
1389
|
-
let response;
|
|
1390
|
-
try {
|
|
1391
|
-
response = await fetch(`http://127.0.0.1:${metadata.port}/command`, {
|
|
1392
|
-
method: "POST",
|
|
1393
|
-
headers: {
|
|
1394
|
-
"Content-Type": "application/json",
|
|
1395
|
-
Authorization: `Bearer ${metadata.token}`
|
|
1396
|
-
},
|
|
1397
|
-
body: JSON.stringify({ name: command, params: params ?? {} })
|
|
1398
|
-
});
|
|
1399
|
-
} catch {
|
|
1400
|
-
throw createDisconnectedError("Daemon not running. Start with `opendevbrowser serve`.");
|
|
1401
|
-
}
|
|
1402
|
-
if (!response.ok) {
|
|
1403
|
-
const message = await response.text();
|
|
1404
|
-
throw new CliError(`Daemon error: ${message || response.status}`, EXIT_EXECUTION);
|
|
1405
|
-
}
|
|
1406
|
-
const payload = await response.json();
|
|
1407
|
-
if (!payload.ok) {
|
|
1408
|
-
throw new CliError(payload.error || "Daemon command failed.", EXIT_EXECUTION);
|
|
1409
|
-
}
|
|
1410
|
-
return payload.data;
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
1853
|
// src/cli/commands/session/launch.ts
|
|
1414
1854
|
function parseLaunchArgs(rawArgs) {
|
|
1415
1855
|
const parsed = { flags: [] };
|
|
@@ -1464,6 +1904,10 @@ function parseLaunchArgs(rawArgs) {
|
|
|
1464
1904
|
parsed.extensionOnly = true;
|
|
1465
1905
|
continue;
|
|
1466
1906
|
}
|
|
1907
|
+
if (arg === "--extension-legacy") {
|
|
1908
|
+
parsed.extensionLegacy = true;
|
|
1909
|
+
continue;
|
|
1910
|
+
}
|
|
1467
1911
|
if (arg === "--wait-for-extension") {
|
|
1468
1912
|
parsed.waitForExtension = true;
|
|
1469
1913
|
continue;
|
|
@@ -1471,12 +1915,14 @@ function parseLaunchArgs(rawArgs) {
|
|
|
1471
1915
|
if (arg === "--wait-timeout-ms") {
|
|
1472
1916
|
const value = rawArgs[i + 1];
|
|
1473
1917
|
if (!value) throw createUsageError("Missing value for --wait-timeout-ms");
|
|
1474
|
-
parsed.waitTimeoutMs =
|
|
1918
|
+
parsed.waitTimeoutMs = parseNumberFlag(value, "--wait-timeout-ms", { min: 1 });
|
|
1475
1919
|
i += 1;
|
|
1476
1920
|
continue;
|
|
1477
1921
|
}
|
|
1478
1922
|
if (arg?.startsWith("--wait-timeout-ms=")) {
|
|
1479
|
-
|
|
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 });
|
|
1480
1926
|
continue;
|
|
1481
1927
|
}
|
|
1482
1928
|
if (arg === "--flag") {
|
|
@@ -1487,7 +1933,9 @@ function parseLaunchArgs(rawArgs) {
|
|
|
1487
1933
|
continue;
|
|
1488
1934
|
}
|
|
1489
1935
|
if (arg?.startsWith("--flag=")) {
|
|
1490
|
-
|
|
1936
|
+
const value = arg.split("=", 2)[1];
|
|
1937
|
+
if (!value) throw createUsageError("Missing value for --flag");
|
|
1938
|
+
parsed.flags.push(value);
|
|
1491
1939
|
continue;
|
|
1492
1940
|
}
|
|
1493
1941
|
}
|
|
@@ -1495,12 +1943,82 @@ function parseLaunchArgs(rawArgs) {
|
|
|
1495
1943
|
}
|
|
1496
1944
|
async function runSessionLaunch(args) {
|
|
1497
1945
|
const launchArgs = parseLaunchArgs(args.rawArgs);
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
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
|
+
});
|
|
1504
2022
|
}
|
|
1505
2023
|
|
|
1506
2024
|
// src/cli/commands/session/connect.ts
|
|
@@ -1527,18 +2045,26 @@ function parseConnectArgs(rawArgs) {
|
|
|
1527
2045
|
continue;
|
|
1528
2046
|
}
|
|
1529
2047
|
if (arg?.startsWith("--host=")) {
|
|
1530
|
-
|
|
2048
|
+
const value = arg.split("=", 2)[1];
|
|
2049
|
+
if (!value) throw createUsageError("Missing value for --host");
|
|
2050
|
+
parsed.host = value;
|
|
1531
2051
|
continue;
|
|
1532
2052
|
}
|
|
1533
2053
|
if (arg === "--cdp-port") {
|
|
1534
2054
|
const value = rawArgs[i + 1];
|
|
1535
2055
|
if (!value) throw createUsageError("Missing value for --cdp-port");
|
|
1536
|
-
parsed.port =
|
|
2056
|
+
parsed.port = parseNumberFlag(value, "--cdp-port", { min: 1, max: 65535 });
|
|
1537
2057
|
i += 1;
|
|
1538
2058
|
continue;
|
|
1539
2059
|
}
|
|
1540
2060
|
if (arg?.startsWith("--cdp-port=")) {
|
|
1541
|
-
|
|
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;
|
|
1542
2068
|
continue;
|
|
1543
2069
|
}
|
|
1544
2070
|
}
|
|
@@ -1582,7 +2108,7 @@ async function runSessionDisconnect(args) {
|
|
|
1582
2108
|
if (!sessionId) {
|
|
1583
2109
|
throw createUsageError("Missing --session-id");
|
|
1584
2110
|
}
|
|
1585
|
-
await callDaemon("session.disconnect", { sessionId, closeBrowser });
|
|
2111
|
+
await callDaemon("session.disconnect", { sessionId, closeBrowser }, { timeoutMs: 2e4 });
|
|
1586
2112
|
return { success: true, message: `Session disconnected: ${sessionId}` };
|
|
1587
2113
|
}
|
|
1588
2114
|
|
|
@@ -1614,6 +2140,76 @@ async function runSessionStatus(args) {
|
|
|
1614
2140
|
return { success: true, message: `Session status: ${sessionId}`, data: result };
|
|
1615
2141
|
}
|
|
1616
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
|
+
|
|
1617
2213
|
// src/cli/commands/nav/goto.ts
|
|
1618
2214
|
function parseGotoArgs(rawArgs) {
|
|
1619
2215
|
const parsed = {};
|
|
@@ -1655,12 +2251,14 @@ function parseGotoArgs(rawArgs) {
|
|
|
1655
2251
|
if (arg === "--timeout-ms") {
|
|
1656
2252
|
const value = rawArgs[i + 1];
|
|
1657
2253
|
if (!value) throw createUsageError("Missing value for --timeout-ms");
|
|
1658
|
-
parsed.timeoutMs =
|
|
2254
|
+
parsed.timeoutMs = parseNumberFlag(value, "--timeout-ms", { min: 1 });
|
|
1659
2255
|
i += 1;
|
|
1660
2256
|
continue;
|
|
1661
2257
|
}
|
|
1662
2258
|
if (arg?.startsWith("--timeout-ms=")) {
|
|
1663
|
-
|
|
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 });
|
|
1664
2262
|
continue;
|
|
1665
2263
|
}
|
|
1666
2264
|
}
|
|
@@ -1726,12 +2324,14 @@ function parseWaitArgs(rawArgs) {
|
|
|
1726
2324
|
if (arg === "--timeout-ms") {
|
|
1727
2325
|
const value = rawArgs[i + 1];
|
|
1728
2326
|
if (!value) throw createUsageError("Missing value for --timeout-ms");
|
|
1729
|
-
parsed.timeoutMs =
|
|
2327
|
+
parsed.timeoutMs = parseNumberFlag(value, "--timeout-ms", { min: 1 });
|
|
1730
2328
|
i += 1;
|
|
1731
2329
|
continue;
|
|
1732
2330
|
}
|
|
1733
2331
|
if (arg?.startsWith("--timeout-ms=")) {
|
|
1734
|
-
|
|
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 });
|
|
1735
2335
|
continue;
|
|
1736
2336
|
}
|
|
1737
2337
|
}
|
|
@@ -1803,104 +2403,1030 @@ async function runSnapshot(args) {
|
|
|
1803
2403
|
return { success: true, message: "Snapshot captured.", data: result };
|
|
1804
2404
|
}
|
|
1805
2405
|
|
|
1806
|
-
// src/cli/commands/
|
|
1807
|
-
|
|
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) => {
|
|
1808
2424
|
const parsed = {};
|
|
1809
2425
|
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
1810
2426
|
const arg = rawArgs[i];
|
|
1811
2427
|
if (arg === "--session-id") {
|
|
1812
|
-
const value = rawArgs[i + 1];
|
|
1813
|
-
if (!value) throw createUsageError("Missing value for --session-id");
|
|
2428
|
+
const value = requireValue(rawArgs[i + 1], "--session-id");
|
|
1814
2429
|
parsed.sessionId = value;
|
|
1815
2430
|
i += 1;
|
|
1816
2431
|
continue;
|
|
1817
2432
|
}
|
|
1818
2433
|
if (arg?.startsWith("--session-id=")) {
|
|
1819
|
-
|
|
2434
|
+
const value = requireValue(arg.split("=", 2)[1], "--session-id");
|
|
2435
|
+
parsed.sessionId = value;
|
|
1820
2436
|
continue;
|
|
1821
2437
|
}
|
|
1822
|
-
if (arg === "--
|
|
1823
|
-
const value = rawArgs[i + 1];
|
|
1824
|
-
|
|
1825
|
-
parsed.ref = value;
|
|
2438
|
+
if (arg === "--url") {
|
|
2439
|
+
const value = requireValue(rawArgs[i + 1], "--url");
|
|
2440
|
+
parsed.url = value;
|
|
1826
2441
|
i += 1;
|
|
1827
2442
|
continue;
|
|
1828
2443
|
}
|
|
1829
|
-
if (arg?.startsWith("--
|
|
1830
|
-
|
|
2444
|
+
if (arg?.startsWith("--url=")) {
|
|
2445
|
+
const value = requireValue(arg.split("=", 2)[1], "--url");
|
|
2446
|
+
parsed.url = value;
|
|
1831
2447
|
continue;
|
|
1832
2448
|
}
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
async function runClick(args) {
|
|
1837
|
-
const { sessionId, ref } = parseClickArgs(args.rawArgs);
|
|
1838
|
-
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
1839
|
-
if (!ref) throw createUsageError("Missing --ref");
|
|
1840
|
-
const result = await callDaemon("interact.click", { sessionId, ref });
|
|
1841
|
-
return { success: true, message: "Click complete.", data: result };
|
|
1842
|
-
}
|
|
1843
|
-
|
|
1844
|
-
// src/cli/commands/interact/type.ts
|
|
1845
|
-
function parseTypeArgs(rawArgs) {
|
|
1846
|
-
const parsed = {};
|
|
1847
|
-
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
1848
|
-
const arg = rawArgs[i];
|
|
1849
|
-
if (arg === "--session-id") {
|
|
1850
|
-
const value = rawArgs[i + 1];
|
|
1851
|
-
if (!value) throw createUsageError("Missing value for --session-id");
|
|
1852
|
-
parsed.sessionId = value;
|
|
2449
|
+
if (arg === "--screenshot-mode") {
|
|
2450
|
+
const value = requireValue(rawArgs[i + 1], "--screenshot-mode");
|
|
2451
|
+
parsed.screenshotMode = requireScreenshotMode(value);
|
|
1853
2452
|
i += 1;
|
|
1854
2453
|
continue;
|
|
1855
2454
|
}
|
|
1856
|
-
if (arg?.startsWith("--
|
|
1857
|
-
|
|
2455
|
+
if (arg?.startsWith("--screenshot-mode=")) {
|
|
2456
|
+
const value = requireValue(arg.split("=", 2)[1], "--screenshot-mode");
|
|
2457
|
+
parsed.screenshotMode = requireScreenshotMode(value);
|
|
1858
2458
|
continue;
|
|
1859
2459
|
}
|
|
1860
|
-
if (arg === "--
|
|
1861
|
-
const value = rawArgs[i + 1];
|
|
1862
|
-
|
|
1863
|
-
parsed.ref = value;
|
|
2460
|
+
if (arg === "--transport") {
|
|
2461
|
+
const value = requireValue(rawArgs[i + 1], "--transport");
|
|
2462
|
+
parsed.transport = requireTransport(value);
|
|
1864
2463
|
i += 1;
|
|
1865
2464
|
continue;
|
|
1866
2465
|
}
|
|
1867
|
-
if (arg?.startsWith("--
|
|
1868
|
-
|
|
2466
|
+
if (arg?.startsWith("--transport=")) {
|
|
2467
|
+
const value = requireValue(arg.split("=", 2)[1], "--transport");
|
|
2468
|
+
parsed.transport = requireTransport(value);
|
|
1869
2469
|
continue;
|
|
1870
2470
|
}
|
|
1871
|
-
if (arg === "--
|
|
1872
|
-
const value = rawArgs[i + 1];
|
|
1873
|
-
|
|
1874
|
-
parsed.text = value;
|
|
2471
|
+
if (arg === "--target-id") {
|
|
2472
|
+
const value = requireValue(rawArgs[i + 1], "--target-id");
|
|
2473
|
+
parsed.targetId = value;
|
|
1875
2474
|
i += 1;
|
|
1876
2475
|
continue;
|
|
1877
2476
|
}
|
|
1878
|
-
if (arg?.startsWith("--
|
|
1879
|
-
|
|
2477
|
+
if (arg?.startsWith("--target-id=")) {
|
|
2478
|
+
const value = requireValue(arg.split("=", 2)[1], "--target-id");
|
|
2479
|
+
parsed.targetId = value;
|
|
1880
2480
|
continue;
|
|
1881
2481
|
}
|
|
1882
|
-
if (arg === "--
|
|
1883
|
-
|
|
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;
|
|
1884
2486
|
continue;
|
|
1885
2487
|
}
|
|
1886
|
-
if (arg
|
|
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") {
|
|
1887
2795
|
parsed.submit = true;
|
|
1888
2796
|
continue;
|
|
1889
2797
|
}
|
|
1890
2798
|
}
|
|
1891
2799
|
return parsed;
|
|
1892
2800
|
}
|
|
1893
|
-
async function runType(args) {
|
|
1894
|
-
const { sessionId, ref, text, clear, submit } = parseTypeArgs(args.rawArgs);
|
|
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);
|
|
1895
3422
|
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
1896
3423
|
if (!ref) throw createUsageError("Missing --ref");
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
return { success: true, message: "Type complete.", data: result };
|
|
3424
|
+
const result = await callDaemon("dom.isVisible", { sessionId, ref });
|
|
3425
|
+
return { success: true, message: "Visibility checked.", data: result };
|
|
1900
3426
|
}
|
|
1901
3427
|
|
|
1902
|
-
// src/cli/commands/
|
|
1903
|
-
function
|
|
3428
|
+
// src/cli/commands/dom/enabled.ts
|
|
3429
|
+
function parseDomEnabledArgs(rawArgs) {
|
|
1904
3430
|
const parsed = {};
|
|
1905
3431
|
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
1906
3432
|
const arg = rawArgs[i];
|
|
@@ -1926,31 +3452,83 @@ function parseSelectArgs(rawArgs) {
|
|
|
1926
3452
|
parsed.ref = arg.split("=", 2)[1];
|
|
1927
3453
|
continue;
|
|
1928
3454
|
}
|
|
1929
|
-
|
|
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") {
|
|
1930
3472
|
const value = rawArgs[i + 1];
|
|
1931
|
-
if (!value) throw createUsageError("Missing value for --
|
|
1932
|
-
parsed.
|
|
3473
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
3474
|
+
parsed.sessionId = value;
|
|
1933
3475
|
i += 1;
|
|
1934
3476
|
continue;
|
|
1935
3477
|
}
|
|
1936
|
-
if (arg?.startsWith("--
|
|
1937
|
-
parsed.
|
|
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];
|
|
1938
3491
|
continue;
|
|
1939
3492
|
}
|
|
1940
3493
|
}
|
|
1941
3494
|
return parsed;
|
|
1942
3495
|
}
|
|
1943
|
-
async function
|
|
1944
|
-
const { sessionId, ref
|
|
3496
|
+
async function runDomChecked(args) {
|
|
3497
|
+
const { sessionId, ref } = parseDomCheckedArgs(args.rawArgs);
|
|
1945
3498
|
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
1946
3499
|
if (!ref) throw createUsageError("Missing --ref");
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
return { success: true, message: "Select complete.", data: result };
|
|
3500
|
+
const result = await callDaemon("dom.isChecked", { sessionId, ref });
|
|
3501
|
+
return { success: true, message: "Checked state reported.", data: result };
|
|
1950
3502
|
}
|
|
1951
3503
|
|
|
1952
|
-
// src/cli/commands/
|
|
1953
|
-
function
|
|
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) {
|
|
1954
3532
|
const parsed = {};
|
|
1955
3533
|
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
1956
3534
|
const arg = rawArgs[i];
|
|
@@ -1976,26 +3554,174 @@ function parseScrollArgs(rawArgs) {
|
|
|
1976
3554
|
parsed.ref = arg.split("=", 2)[1];
|
|
1977
3555
|
continue;
|
|
1978
3556
|
}
|
|
1979
|
-
|
|
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") {
|
|
1980
3574
|
const value = rawArgs[i + 1];
|
|
1981
|
-
if (!value) throw createUsageError("Missing value for --
|
|
1982
|
-
parsed.
|
|
3575
|
+
if (!value) throw createUsageError("Missing value for --session-id");
|
|
3576
|
+
parsed.sessionId = value;
|
|
1983
3577
|
i += 1;
|
|
1984
3578
|
continue;
|
|
1985
3579
|
}
|
|
1986
|
-
if (arg?.startsWith("--
|
|
1987
|
-
parsed.
|
|
3580
|
+
if (arg?.startsWith("--session-id=")) {
|
|
3581
|
+
parsed.sessionId = arg.split("=", 2)[1];
|
|
1988
3582
|
continue;
|
|
1989
3583
|
}
|
|
1990
3584
|
}
|
|
1991
3585
|
return parsed;
|
|
1992
3586
|
}
|
|
1993
|
-
async function
|
|
1994
|
-
const { sessionId
|
|
3587
|
+
async function runPerf(args) {
|
|
3588
|
+
const { sessionId } = parsePerfArgs(args.rawArgs);
|
|
1995
3589
|
if (!sessionId) throw createUsageError("Missing --session-id");
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
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 };
|
|
1999
3725
|
}
|
|
2000
3726
|
|
|
2001
3727
|
// src/cli/index.ts
|
|
@@ -2005,7 +3731,7 @@ async function promptInstallMode() {
|
|
|
2005
3731
|
console.log("Non-interactive mode detected. Using global install.");
|
|
2006
3732
|
return "global";
|
|
2007
3733
|
}
|
|
2008
|
-
return new Promise((
|
|
3734
|
+
return new Promise((resolve4) => {
|
|
2009
3735
|
console.log("\nWhere would you like to install opendevbrowser?\n");
|
|
2010
3736
|
console.log(" 1. Global (~/.config/opencode/opencode.json)");
|
|
2011
3737
|
console.log(" 2. Local (./opencode.json in this project)\n");
|
|
@@ -2025,23 +3751,23 @@ async function promptInstallMode() {
|
|
|
2025
3751
|
resolved = true;
|
|
2026
3752
|
const input = data.toString().trim();
|
|
2027
3753
|
if (input === "2") {
|
|
2028
|
-
|
|
3754
|
+
resolve4("local");
|
|
2029
3755
|
} else {
|
|
2030
|
-
|
|
3756
|
+
resolve4("global");
|
|
2031
3757
|
}
|
|
2032
3758
|
});
|
|
2033
3759
|
process.stdin.once("close", () => {
|
|
2034
3760
|
cleanup();
|
|
2035
3761
|
if (resolved) return;
|
|
2036
3762
|
resolved = true;
|
|
2037
|
-
|
|
3763
|
+
resolve4("global");
|
|
2038
3764
|
});
|
|
2039
3765
|
timeoutId = setTimeout(() => {
|
|
2040
3766
|
timeoutId = null;
|
|
2041
3767
|
if (resolved) return;
|
|
2042
3768
|
resolved = true;
|
|
2043
3769
|
console.log("\nTimeout - using global install.");
|
|
2044
|
-
|
|
3770
|
+
resolve4("global");
|
|
2045
3771
|
}, 3e4);
|
|
2046
3772
|
});
|
|
2047
3773
|
}
|
|
@@ -2061,7 +3787,7 @@ async function promptUninstallMode() {
|
|
|
2061
3787
|
console.log("Plugin found in both global and local configs. Use --global or --local flag.");
|
|
2062
3788
|
return null;
|
|
2063
3789
|
}
|
|
2064
|
-
return new Promise((
|
|
3790
|
+
return new Promise((resolve4) => {
|
|
2065
3791
|
console.log("\nopendevbrowser is installed in multiple locations:\n");
|
|
2066
3792
|
console.log(" 1. Global (~/.config/opencode/opencode.json)");
|
|
2067
3793
|
console.log(" 2. Local (./opencode.json)");
|
|
@@ -2071,15 +3797,15 @@ async function promptUninstallMode() {
|
|
|
2071
3797
|
process.stdin.once("data", (data) => {
|
|
2072
3798
|
const input = data.toString().trim();
|
|
2073
3799
|
if (input === "1") {
|
|
2074
|
-
|
|
3800
|
+
resolve4("global");
|
|
2075
3801
|
} else if (input === "2") {
|
|
2076
|
-
|
|
3802
|
+
resolve4("local");
|
|
2077
3803
|
} else {
|
|
2078
|
-
|
|
3804
|
+
resolve4(null);
|
|
2079
3805
|
}
|
|
2080
3806
|
});
|
|
2081
3807
|
process.stdin.once("close", () => {
|
|
2082
|
-
|
|
3808
|
+
resolve4(null);
|
|
2083
3809
|
});
|
|
2084
3810
|
});
|
|
2085
3811
|
}
|
|
@@ -2176,6 +3902,22 @@ async function main() {
|
|
|
2176
3902
|
mode = await promptInstallMode();
|
|
2177
3903
|
}
|
|
2178
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
|
+
};
|
|
2179
3921
|
if (args.outputFormat !== "text") {
|
|
2180
3922
|
const payload = {
|
|
2181
3923
|
alreadyInstalled: result2.alreadyInstalled
|
|
@@ -2192,6 +3934,13 @@ async function main() {
|
|
|
2192
3934
|
payload.extensionError = error instanceof Error ? error.message : String(error);
|
|
2193
3935
|
}
|
|
2194
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
|
+
}
|
|
2195
3944
|
return { success: result2.success, message: result2.message, data: payload };
|
|
2196
3945
|
}
|
|
2197
3946
|
log(result2.message);
|
|
@@ -2220,6 +3969,14 @@ async function main() {
|
|
|
2220
3969
|
warn(`Extension pre-extraction failed: ${message}`);
|
|
2221
3970
|
}
|
|
2222
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
|
+
}
|
|
2223
3980
|
if (result2.success && !result2.alreadyInstalled) {
|
|
2224
3981
|
log("\nNext steps:");
|
|
2225
3982
|
log(" 1. Start or restart OpenCode");
|
|
@@ -2234,6 +3991,16 @@ async function main() {
|
|
|
2234
3991
|
description: "Start or stop the local daemon",
|
|
2235
3992
|
run: async () => runServe(args)
|
|
2236
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
|
+
});
|
|
2237
4004
|
registerCommand({
|
|
2238
4005
|
name: "run",
|
|
2239
4006
|
description: "Execute a JSON script in a single process",
|
|
@@ -2256,8 +4023,8 @@ async function main() {
|
|
|
2256
4023
|
});
|
|
2257
4024
|
registerCommand({
|
|
2258
4025
|
name: "status",
|
|
2259
|
-
description: "Get daemon session status",
|
|
2260
|
-
run: async () =>
|
|
4026
|
+
description: "Get daemon or session status",
|
|
4027
|
+
run: async () => runStatus(args)
|
|
2261
4028
|
});
|
|
2262
4029
|
registerCommand({
|
|
2263
4030
|
name: "goto",
|
|
@@ -2274,11 +4041,36 @@ async function main() {
|
|
|
2274
4041
|
description: "Capture a snapshot of the active page",
|
|
2275
4042
|
run: async () => runSnapshot(args)
|
|
2276
4043
|
});
|
|
4044
|
+
registerCommand({
|
|
4045
|
+
name: "annotate",
|
|
4046
|
+
description: "Request interactive annotations (extension relay)",
|
|
4047
|
+
run: async () => runAnnotate(args)
|
|
4048
|
+
});
|
|
2277
4049
|
registerCommand({
|
|
2278
4050
|
name: "click",
|
|
2279
4051
|
description: "Click an element by ref",
|
|
2280
4052
|
run: async () => runClick(args)
|
|
2281
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
|
+
});
|
|
2282
4074
|
registerCommand({
|
|
2283
4075
|
name: "type",
|
|
2284
4076
|
description: "Type into an element by ref",
|
|
@@ -2294,6 +4086,111 @@ async function main() {
|
|
|
2294
4086
|
description: "Scroll the page or element by ref",
|
|
2295
4087
|
run: async () => runScroll(args)
|
|
2296
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
|
+
});
|
|
2297
4194
|
const command = getCommand(args.command);
|
|
2298
4195
|
if (!command) {
|
|
2299
4196
|
throw new Error(`Unknown command: ${args.command}`);
|