opendevbrowser 0.0.10

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.
@@ -0,0 +1,802 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ extractExtension,
4
+ generateSecureToken
5
+ } from "../chunk-R5VUZEUU.js";
6
+
7
+ // src/cli/args.ts
8
+ var SHORT_FLAGS = {
9
+ "-g": "--global",
10
+ "-l": "--local",
11
+ "-u": "--update",
12
+ "-h": "--help",
13
+ "-v": "--version",
14
+ "-f": "--full"
15
+ };
16
+ function expandShortFlags(args) {
17
+ return args.map((arg) => SHORT_FLAGS[arg] ?? arg);
18
+ }
19
+ function parseSkillsMode(args) {
20
+ if (args.includes("--no-skills")) {
21
+ return "none";
22
+ }
23
+ if (args.includes("--skills-local")) {
24
+ return "local";
25
+ }
26
+ if (args.includes("--skills-global")) {
27
+ return "global";
28
+ }
29
+ return "global";
30
+ }
31
+ function parseArgs(argv) {
32
+ const args = expandShortFlags(argv.slice(2));
33
+ const skillsMode = parseSkillsMode(args);
34
+ const fullInstall = args.includes("--full");
35
+ if (args.includes("--help") || args.includes("-h")) {
36
+ return { command: "help", withConfig: false, noPrompt: false, skillsMode, fullInstall };
37
+ }
38
+ if (args.includes("--version") || args.includes("-v")) {
39
+ return { command: "version", withConfig: false, noPrompt: false, skillsMode, fullInstall };
40
+ }
41
+ if (args.includes("--update")) {
42
+ const mode2 = args.includes("--global") ? "global" : args.includes("--local") ? "local" : void 0;
43
+ return { command: "update", mode: mode2, withConfig: false, noPrompt: false, skillsMode, fullInstall };
44
+ }
45
+ if (args.includes("--uninstall")) {
46
+ const mode2 = args.includes("--global") ? "global" : args.includes("--local") ? "local" : void 0;
47
+ const noPrompt2 = args.includes("--no-prompt");
48
+ return { command: "uninstall", mode: mode2, withConfig: false, noPrompt: noPrompt2, skillsMode, fullInstall };
49
+ }
50
+ const withConfig = args.includes("--with-config") || fullInstall;
51
+ const noPrompt = args.includes("--no-prompt");
52
+ let mode;
53
+ if (args.includes("--global")) {
54
+ mode = "global";
55
+ } else if (args.includes("--local")) {
56
+ mode = "local";
57
+ } else if (noPrompt) {
58
+ mode = "global";
59
+ }
60
+ const validFlags = /* @__PURE__ */ new Set([
61
+ "--global",
62
+ "--local",
63
+ "--update",
64
+ "--uninstall",
65
+ "--help",
66
+ "--version",
67
+ "--with-config",
68
+ "--no-prompt",
69
+ "--full",
70
+ "--skills-global",
71
+ "--skills-local",
72
+ "--no-skills"
73
+ ]);
74
+ for (const arg of args) {
75
+ if (arg.startsWith("--") && !validFlags.has(arg)) {
76
+ throw new Error(`Unknown flag: ${arg}`);
77
+ }
78
+ if (arg.startsWith("-") && !arg.startsWith("--") && !SHORT_FLAGS[arg]) {
79
+ throw new Error(`Unknown flag: ${arg}`);
80
+ }
81
+ }
82
+ return { command: "install", mode, withConfig, noPrompt, skillsMode, fullInstall };
83
+ }
84
+ function getHelpText() {
85
+ return `
86
+ OpenDevBrowser CLI - Install and manage the OpenDevBrowser plugin
87
+
88
+ USAGE:
89
+ npx opendevbrowser [options]
90
+
91
+ COMMANDS:
92
+ (default) Install the plugin (interactive if no mode specified)
93
+ --update, -u Clear cached plugin to trigger reinstall
94
+ --uninstall Remove plugin from config
95
+ --help, -h Show this help message
96
+ --version, -v Show version
97
+
98
+ INSTALL OPTIONS:
99
+ --global, -g Install to ~/.config/opencode/opencode.json
100
+ --local, -l Install to ./opencode.json (project-local)
101
+ --with-config Also create opendevbrowser.jsonc with defaults
102
+ --full, -f Create config and pre-extract extension assets
103
+ --no-prompt Skip prompts, use defaults (global install)
104
+ --skills-global Install bundled skills to ~/.config/opencode/skill (default)
105
+ --skills-local Install bundled skills to ./.opencode/skill
106
+ --no-skills Skip installing bundled skills
107
+
108
+ EXAMPLES:
109
+ npx opendevbrowser # Interactive install
110
+ npx opendevbrowser --global # Global install
111
+ npx opendevbrowser --local # Project install
112
+ npx opendevbrowser --full # Install + config + extension assets
113
+ npx opendevbrowser -g --with-config # Global + config file
114
+ npx opendevbrowser --skills-local # Install skills locally
115
+ npx opendevbrowser --no-skills # Skip skill installation
116
+ npx opendevbrowser --update # Update plugin
117
+ npx opendevbrowser --uninstall --global # Remove from global config
118
+ `.trim();
119
+ }
120
+
121
+ // src/cli/installers/global.ts
122
+ import * as fs4 from "fs";
123
+
124
+ // src/cli/utils/config.ts
125
+ import * as fs2 from "fs";
126
+ import * as path2 from "path";
127
+ import * as os from "os";
128
+ import { parse as parseJsonc, modify, applyEdits } from "jsonc-parser";
129
+
130
+ // src/utils/fs.ts
131
+ import * as fs from "fs";
132
+ import * as path from "path";
133
+ import * as crypto from "crypto";
134
+ function writeFileAtomic(filePath, content, options = {}) {
135
+ const { encoding = "utf-8", mode } = options;
136
+ const dir = path.dirname(filePath);
137
+ const hash = crypto.randomBytes(8).toString("hex");
138
+ const tempPath = path.join(dir, `.${path.basename(filePath)}.${process.pid}.${hash}.tmp`);
139
+ try {
140
+ if (!fs.existsSync(dir)) {
141
+ fs.mkdirSync(dir, { recursive: true });
142
+ }
143
+ const writeOptions = { encoding };
144
+ if (mode !== void 0) {
145
+ writeOptions.mode = mode;
146
+ }
147
+ fs.writeFileSync(tempPath, content, writeOptions);
148
+ fs.renameSync(tempPath, filePath);
149
+ } catch (error) {
150
+ try {
151
+ if (fs.existsSync(tempPath)) {
152
+ fs.unlinkSync(tempPath);
153
+ }
154
+ } catch {
155
+ }
156
+ throw error;
157
+ }
158
+ }
159
+
160
+ // src/cli/utils/config.ts
161
+ var PLUGIN_NAME = "opendevbrowser";
162
+ var SCHEMA_URL = "https://opencode.ai/config.json";
163
+ function getGlobalConfigPath() {
164
+ const configDir = process.env.OPENCODE_CONFIG_DIR || path2.join(os.homedir(), ".config", "opencode");
165
+ return path2.join(configDir, "opencode.json");
166
+ }
167
+ function getLocalConfigPath() {
168
+ return path2.join(process.cwd(), "opencode.json");
169
+ }
170
+ function ensureDir(dirPath) {
171
+ if (!fs2.existsSync(dirPath)) {
172
+ fs2.mkdirSync(dirPath, { recursive: true });
173
+ }
174
+ }
175
+ function readConfig(configPath) {
176
+ if (!fs2.existsSync(configPath)) {
177
+ return { content: "", config: {} };
178
+ }
179
+ const content = fs2.readFileSync(configPath, "utf-8");
180
+ const errors = [];
181
+ const parsed = parseJsonc(content, errors, { allowTrailingComma: true });
182
+ if (errors.length > 0) {
183
+ const firstError = errors[0];
184
+ throw new Error(`Invalid JSONC at ${configPath}: parse error at offset ${firstError?.offset ?? 0}`);
185
+ }
186
+ return { content, config: parsed ?? {} };
187
+ }
188
+ function hasPlugin(config, pluginName = PLUGIN_NAME) {
189
+ return config.plugin?.includes(pluginName) ?? false;
190
+ }
191
+ function createConfigWithPlugin(pluginName = PLUGIN_NAME) {
192
+ return {
193
+ $schema: SCHEMA_URL,
194
+ plugin: [pluginName]
195
+ };
196
+ }
197
+ function updateConfigContent(content, pluginName = PLUGIN_NAME) {
198
+ if (!content.trim()) {
199
+ return JSON.stringify(createConfigWithPlugin(pluginName), null, 2) + "\n";
200
+ }
201
+ const parsed = parseJsonc(content, [], { allowTrailingComma: true }) ?? {};
202
+ if (parsed.plugin?.includes(pluginName)) {
203
+ return content;
204
+ }
205
+ let result = content;
206
+ if (!parsed.$schema) {
207
+ const edits2 = modify(result, ["$schema"], SCHEMA_URL, { formattingOptions: { tabSize: 2, insertSpaces: true } });
208
+ result = applyEdits(result, edits2);
209
+ }
210
+ const newPlugins = parsed.plugin ? [...parsed.plugin, pluginName] : [pluginName];
211
+ const edits = modify(result, ["plugin"], newPlugins, { formattingOptions: { tabSize: 2, insertSpaces: true } });
212
+ result = applyEdits(result, edits);
213
+ return result;
214
+ }
215
+ function removePluginFromContent(content, pluginName = PLUGIN_NAME) {
216
+ if (!content.trim()) {
217
+ return content;
218
+ }
219
+ const parsed = parseJsonc(content, [], { allowTrailingComma: true }) ?? {};
220
+ if (!parsed.plugin?.includes(pluginName)) {
221
+ return content;
222
+ }
223
+ const newPlugins = parsed.plugin.filter((p) => p !== pluginName);
224
+ const edits = modify(content, ["plugin"], newPlugins.length > 0 ? newPlugins : void 0, {
225
+ formattingOptions: { tabSize: 2, insertSpaces: true }
226
+ });
227
+ return applyEdits(content, edits);
228
+ }
229
+
230
+ // src/cli/templates/config.ts
231
+ import * as fs3 from "fs";
232
+ import * as path3 from "path";
233
+ import * as os2 from "os";
234
+ function buildConfigTemplate(token) {
235
+ return `{
236
+ // OpenDevBrowser Plugin Configuration
237
+ // See: https://github.com/anthropics/opendevbrowser#configuration
238
+
239
+ "headless": false,
240
+ "profile": "default",
241
+ "persistProfile": true,
242
+
243
+ "snapshot": {
244
+ "maxChars": 16000,
245
+ "maxNodes": 1000
246
+ },
247
+
248
+ "export": {
249
+ "maxNodes": 1000,
250
+ "inlineStyles": true
251
+ },
252
+
253
+ "devtools": {
254
+ "showFullUrls": false,
255
+ "showFullConsole": false
256
+ },
257
+
258
+ "security": {
259
+ "allowRawCDP": false,
260
+ "allowNonLocalCdp": false,
261
+ "allowUnsafeExport": false
262
+ },
263
+
264
+ "skillPaths": [],
265
+ "skills": {
266
+ "nudge": {
267
+ "enabled": true,
268
+ "keywords": ["login", "form", "extract"],
269
+ "maxAgeMs": 60000
270
+ }
271
+ },
272
+
273
+ "continuity": {
274
+ "enabled": true,
275
+ "filePath": "opendevbrowser_continuity.md",
276
+ "nudge": {
277
+ "enabled": true,
278
+ "keywords": [
279
+ "plan",
280
+ "multi-step",
281
+ "multi step",
282
+ "long-running",
283
+ "long running",
284
+ "refactor",
285
+ "migration",
286
+ "rollout",
287
+ "release",
288
+ "upgrade",
289
+ "investigate",
290
+ "follow-up",
291
+ "continue"
292
+ ],
293
+ "maxAgeMs": 60000
294
+ }
295
+ },
296
+
297
+ "relayPort": 8787,
298
+ "relayToken": "${token}",
299
+
300
+ "flags": [],
301
+
302
+ "checkForUpdates": false
303
+ }
304
+ `;
305
+ }
306
+ function getPluginConfigPath(mode) {
307
+ if (mode === "global") {
308
+ const configDir = process.env.OPENCODE_CONFIG_DIR || path3.join(os2.homedir(), ".config", "opencode");
309
+ return path3.join(configDir, "opendevbrowser.jsonc");
310
+ }
311
+ return path3.join(process.cwd(), "opendevbrowser.jsonc");
312
+ }
313
+ function createPluginConfig(mode) {
314
+ const configPath = getPluginConfigPath(mode);
315
+ if (fs3.existsSync(configPath)) {
316
+ return { created: false, path: configPath };
317
+ }
318
+ const dir = path3.dirname(configPath);
319
+ if (!fs3.existsSync(dir)) {
320
+ fs3.mkdirSync(dir, { recursive: true });
321
+ }
322
+ const token = generateSecureToken();
323
+ writeFileAtomic(configPath, buildConfigTemplate(token));
324
+ return { created: true, path: configPath };
325
+ }
326
+
327
+ // src/cli/installers/global.ts
328
+ function installGlobal(withConfig = false) {
329
+ const configPath = getGlobalConfigPath();
330
+ try {
331
+ const { content, config } = readConfig(configPath);
332
+ if (hasPlugin(config)) {
333
+ return {
334
+ success: true,
335
+ message: `opendevbrowser is already installed in ${configPath}`,
336
+ configPath,
337
+ created: false,
338
+ alreadyInstalled: true
339
+ };
340
+ }
341
+ const newContent = updateConfigContent(content, "opendevbrowser");
342
+ ensureDir(configPath.replace(/[/\\][^/\\]+$/, ""));
343
+ fs4.writeFileSync(configPath, newContent, "utf-8");
344
+ if (withConfig) {
345
+ createPluginConfig("global");
346
+ }
347
+ return {
348
+ success: true,
349
+ message: `Added opendevbrowser to ${configPath}`,
350
+ configPath,
351
+ created: content.trim() === "",
352
+ alreadyInstalled: false
353
+ };
354
+ } catch (error) {
355
+ const message = error instanceof Error ? error.message : String(error);
356
+ return {
357
+ success: false,
358
+ message: `Failed to install globally: ${message}`,
359
+ configPath,
360
+ created: false,
361
+ alreadyInstalled: false
362
+ };
363
+ }
364
+ }
365
+
366
+ // src/cli/installers/local.ts
367
+ import * as fs5 from "fs";
368
+ function installLocal(withConfig = false) {
369
+ const configPath = getLocalConfigPath();
370
+ try {
371
+ const { content, config } = readConfig(configPath);
372
+ if (hasPlugin(config)) {
373
+ return {
374
+ success: true,
375
+ message: `opendevbrowser is already installed in ${configPath}`,
376
+ configPath,
377
+ created: false,
378
+ alreadyInstalled: true
379
+ };
380
+ }
381
+ const newContent = updateConfigContent(content, "opendevbrowser");
382
+ fs5.writeFileSync(configPath, newContent, "utf-8");
383
+ if (withConfig) {
384
+ createPluginConfig("local");
385
+ }
386
+ return {
387
+ success: true,
388
+ message: `Added opendevbrowser to ${configPath}`,
389
+ configPath,
390
+ created: content.trim() === "",
391
+ alreadyInstalled: false
392
+ };
393
+ } catch (error) {
394
+ const message = error instanceof Error ? error.message : String(error);
395
+ return {
396
+ success: false,
397
+ message: `Failed to install locally: ${message}`,
398
+ configPath,
399
+ created: false,
400
+ alreadyInstalled: false
401
+ };
402
+ }
403
+ }
404
+
405
+ // src/cli/installers/skills.ts
406
+ import * as fs7 from "fs";
407
+ import * as path5 from "path";
408
+
409
+ // src/cli/utils/skills.ts
410
+ import * as fs6 from "fs";
411
+ import * as path4 from "path";
412
+ import * as os3 from "os";
413
+ import { fileURLToPath } from "url";
414
+ var PACKAGE_NAME = "opendevbrowser";
415
+ var SKILL_DIR_NAME = "skill";
416
+ var cachedPackageRoot = null;
417
+ function findPackageRoot(startDir) {
418
+ let current = startDir;
419
+ while (true) {
420
+ const pkgPath = path4.join(current, "package.json");
421
+ if (fs6.existsSync(pkgPath)) {
422
+ try {
423
+ const parsed = JSON.parse(fs6.readFileSync(pkgPath, "utf-8"));
424
+ if (parsed.name === PACKAGE_NAME) {
425
+ return current;
426
+ }
427
+ } catch {
428
+ }
429
+ }
430
+ const parent = path4.dirname(current);
431
+ if (parent === current) {
432
+ break;
433
+ }
434
+ current = parent;
435
+ }
436
+ throw new Error("Unable to locate opendevbrowser package root for skill installation.");
437
+ }
438
+ function getPackageRoot() {
439
+ if (cachedPackageRoot) return cachedPackageRoot;
440
+ const moduleDir = path4.dirname(fileURLToPath(import.meta.url));
441
+ cachedPackageRoot = findPackageRoot(moduleDir);
442
+ return cachedPackageRoot;
443
+ }
444
+ function getBundledSkillsDir() {
445
+ const skillsDir = path4.join(getPackageRoot(), "skills");
446
+ if (!fs6.existsSync(skillsDir)) {
447
+ throw new Error(`Bundled skills directory not found at ${skillsDir}`);
448
+ }
449
+ return skillsDir;
450
+ }
451
+ function getGlobalSkillDir() {
452
+ const configDir = process.env.OPENCODE_CONFIG_DIR || path4.join(os3.homedir(), ".config", "opencode");
453
+ return path4.join(configDir, SKILL_DIR_NAME);
454
+ }
455
+ function getLocalSkillDir() {
456
+ return path4.join(process.cwd(), ".opencode", SKILL_DIR_NAME);
457
+ }
458
+
459
+ // src/cli/installers/skills.ts
460
+ function installSkills(mode) {
461
+ const targetDir = mode === "global" ? getGlobalSkillDir() : getLocalSkillDir();
462
+ const installed = [];
463
+ const skipped = [];
464
+ try {
465
+ const sourceDir = getBundledSkillsDir();
466
+ const entries = fs7.readdirSync(sourceDir, { withFileTypes: true });
467
+ ensureDir(targetDir);
468
+ for (const entry of entries) {
469
+ if (!entry.isDirectory()) continue;
470
+ const skillName = entry.name;
471
+ const sourcePath = path5.join(sourceDir, skillName);
472
+ const targetPath = path5.join(targetDir, skillName);
473
+ if (fs7.existsSync(targetPath)) {
474
+ skipped.push(skillName);
475
+ continue;
476
+ }
477
+ fs7.cpSync(sourcePath, targetPath, { recursive: true });
478
+ installed.push(skillName);
479
+ }
480
+ const summary = `Skills ${mode} install: ${installed.length} installed${skipped.length ? `, ${skipped.length} skipped` : ""} (${targetDir})`;
481
+ return {
482
+ success: true,
483
+ message: summary,
484
+ targetDir,
485
+ installed,
486
+ skipped
487
+ };
488
+ } catch (error) {
489
+ const message = error instanceof Error ? error.message : String(error);
490
+ return {
491
+ success: false,
492
+ message: `Failed to install skills (${mode}): ${message}`,
493
+ targetDir,
494
+ installed,
495
+ skipped
496
+ };
497
+ }
498
+ }
499
+
500
+ // src/cli/commands/update.ts
501
+ import * as fs8 from "fs";
502
+ import * as path6 from "path";
503
+ import * as os4 from "os";
504
+ var PLUGIN_NAME2 = "opendevbrowser";
505
+ function getCacheDir() {
506
+ return process.env.OPENCODE_CACHE_DIR || path6.join(os4.homedir(), ".cache", "opencode");
507
+ }
508
+ function rmdir(dirPath) {
509
+ const cacheDir = getCacheDir();
510
+ const resolvedCache = path6.resolve(cacheDir);
511
+ const resolvedPath = path6.resolve(dirPath);
512
+ if (!resolvedPath.startsWith(resolvedCache + path6.sep) || resolvedPath === resolvedCache) {
513
+ throw new Error(`Security: refusing to delete path outside cache directory: ${dirPath}`);
514
+ }
515
+ if (fs8.existsSync(dirPath)) {
516
+ fs8.rmSync(dirPath, { recursive: true, force: true });
517
+ }
518
+ }
519
+ function runUpdate() {
520
+ const cacheDir = getCacheDir();
521
+ const nodeModulesDir = path6.join(cacheDir, "node_modules");
522
+ const pluginCacheDir = path6.join(nodeModulesDir, PLUGIN_NAME2);
523
+ try {
524
+ if (!fs8.existsSync(pluginCacheDir)) {
525
+ if (fs8.existsSync(nodeModulesDir)) {
526
+ rmdir(nodeModulesDir);
527
+ return {
528
+ success: true,
529
+ message: "Cleared OpenCode plugin cache. The latest version will be installed on next run.",
530
+ cleared: true
531
+ };
532
+ }
533
+ return {
534
+ success: true,
535
+ message: "No cached plugin found. OpenCode will install the latest version on next run.",
536
+ cleared: false
537
+ };
538
+ }
539
+ rmdir(pluginCacheDir);
540
+ return {
541
+ success: true,
542
+ message: "Cache cleared. OpenCode will install the latest version on next run.",
543
+ cleared: true
544
+ };
545
+ } catch (error) {
546
+ const message = error instanceof Error ? error.message : String(error);
547
+ return {
548
+ success: false,
549
+ message: `Failed to clear cache: ${message}`,
550
+ cleared: false
551
+ };
552
+ }
553
+ }
554
+
555
+ // src/cli/commands/uninstall.ts
556
+ import * as fs9 from "fs";
557
+ import * as path7 from "path";
558
+ import * as os5 from "os";
559
+ function getPluginConfigPath2(mode) {
560
+ if (mode === "global") {
561
+ const configDir = process.env.OPENCODE_CONFIG_DIR || path7.join(os5.homedir(), ".config", "opencode");
562
+ return path7.join(configDir, "opendevbrowser.jsonc");
563
+ }
564
+ return path7.join(process.cwd(), "opendevbrowser.jsonc");
565
+ }
566
+ function removePluginConfigFile(mode) {
567
+ const configPath = getPluginConfigPath2(mode);
568
+ if (fs9.existsSync(configPath)) {
569
+ fs9.unlinkSync(configPath);
570
+ return true;
571
+ }
572
+ return false;
573
+ }
574
+ function runUninstall(mode, deleteConfigFile = false) {
575
+ const configPath = mode === "global" ? getGlobalConfigPath() : getLocalConfigPath();
576
+ try {
577
+ const { content, config } = readConfig(configPath);
578
+ if (!hasPlugin(config)) {
579
+ return {
580
+ success: true,
581
+ message: `opendevbrowser is not installed in ${configPath}`,
582
+ configPath,
583
+ removed: false,
584
+ configFileDeleted: false
585
+ };
586
+ }
587
+ const newContent = removePluginFromContent(content, "opendevbrowser");
588
+ fs9.writeFileSync(configPath, newContent, "utf-8");
589
+ let configFileDeleted = false;
590
+ if (deleteConfigFile) {
591
+ configFileDeleted = removePluginConfigFile(mode);
592
+ }
593
+ return {
594
+ success: true,
595
+ message: `Removed opendevbrowser from ${configPath}`,
596
+ configPath,
597
+ removed: true,
598
+ configFileDeleted
599
+ };
600
+ } catch (error) {
601
+ const message = error instanceof Error ? error.message : String(error);
602
+ return {
603
+ success: false,
604
+ message: `Failed to uninstall: ${message}`,
605
+ configPath,
606
+ removed: false,
607
+ configFileDeleted: false
608
+ };
609
+ }
610
+ }
611
+ function findInstalledConfigs() {
612
+ let global = false;
613
+ let local = false;
614
+ try {
615
+ const { config: globalConfig } = readConfig(getGlobalConfigPath());
616
+ global = hasPlugin(globalConfig);
617
+ } catch {
618
+ }
619
+ try {
620
+ const { config: localConfig } = readConfig(getLocalConfigPath());
621
+ local = hasPlugin(localConfig);
622
+ } catch {
623
+ }
624
+ return { global, local };
625
+ }
626
+
627
+ // src/cli/index.ts
628
+ var VERSION = "0.1.0";
629
+ async function promptInstallMode() {
630
+ if (!process.stdin.isTTY) {
631
+ console.log("Non-interactive mode detected. Using global install.");
632
+ return "global";
633
+ }
634
+ return new Promise((resolve2) => {
635
+ console.log("\nWhere would you like to install opendevbrowser?\n");
636
+ console.log(" 1. Global (~/.config/opencode/opencode.json)");
637
+ console.log(" 2. Local (./opencode.json in this project)\n");
638
+ process.stdout.write("Enter choice [1]: ");
639
+ process.stdin.setEncoding("utf8");
640
+ let resolved = false;
641
+ let timeoutId = null;
642
+ const cleanup = () => {
643
+ if (timeoutId !== null) {
644
+ clearTimeout(timeoutId);
645
+ timeoutId = null;
646
+ }
647
+ };
648
+ process.stdin.once("data", (data) => {
649
+ cleanup();
650
+ if (resolved) return;
651
+ resolved = true;
652
+ const input = data.toString().trim();
653
+ if (input === "2") {
654
+ resolve2("local");
655
+ } else {
656
+ resolve2("global");
657
+ }
658
+ });
659
+ process.stdin.once("close", () => {
660
+ cleanup();
661
+ if (resolved) return;
662
+ resolved = true;
663
+ resolve2("global");
664
+ });
665
+ timeoutId = setTimeout(() => {
666
+ timeoutId = null;
667
+ if (resolved) return;
668
+ resolved = true;
669
+ console.log("\nTimeout - using global install.");
670
+ resolve2("global");
671
+ }, 3e4);
672
+ });
673
+ }
674
+ async function promptUninstallMode() {
675
+ const installed = findInstalledConfigs();
676
+ if (!installed.global && !installed.local) {
677
+ console.log("opendevbrowser is not installed in any config.");
678
+ return null;
679
+ }
680
+ if (installed.global && !installed.local) {
681
+ return "global";
682
+ }
683
+ if (!installed.global && installed.local) {
684
+ return "local";
685
+ }
686
+ if (!process.stdin.isTTY) {
687
+ console.log("Plugin found in both global and local configs. Use --global or --local flag.");
688
+ return null;
689
+ }
690
+ return new Promise((resolve2) => {
691
+ console.log("\nopendevbrowser is installed in multiple locations:\n");
692
+ console.log(" 1. Global (~/.config/opencode/opencode.json)");
693
+ console.log(" 2. Local (./opencode.json)");
694
+ console.log(" 3. Cancel\n");
695
+ process.stdout.write("Which to uninstall? [3]: ");
696
+ process.stdin.setEncoding("utf8");
697
+ process.stdin.once("data", (data) => {
698
+ const input = data.toString().trim();
699
+ if (input === "1") {
700
+ resolve2("global");
701
+ } else if (input === "2") {
702
+ resolve2("local");
703
+ } else {
704
+ resolve2(null);
705
+ }
706
+ });
707
+ process.stdin.once("close", () => {
708
+ resolve2(null);
709
+ });
710
+ });
711
+ }
712
+ async function main() {
713
+ try {
714
+ const args = parseArgs(process.argv);
715
+ switch (args.command) {
716
+ case "help":
717
+ console.log(getHelpText());
718
+ process.exit(0);
719
+ break;
720
+ case "version":
721
+ console.log(`opendevbrowser v${VERSION}`);
722
+ process.exit(0);
723
+ break;
724
+ case "update": {
725
+ const result = runUpdate();
726
+ console.log(result.message);
727
+ process.exit(result.success ? 0 : 1);
728
+ break;
729
+ }
730
+ case "uninstall": {
731
+ let mode = args.mode;
732
+ if (!mode && !args.noPrompt) {
733
+ mode = await promptUninstallMode() ?? void 0;
734
+ if (!mode) {
735
+ console.log("Uninstall cancelled.");
736
+ process.exit(0);
737
+ }
738
+ }
739
+ if (!mode) {
740
+ console.error("Error: Please specify --global or --local for uninstall.");
741
+ process.exit(1);
742
+ }
743
+ const result = runUninstall(mode);
744
+ console.log(result.message);
745
+ process.exit(result.success ? 0 : 1);
746
+ break;
747
+ }
748
+ case "install":
749
+ default: {
750
+ let mode = args.mode;
751
+ if (!mode) {
752
+ mode = await promptInstallMode();
753
+ }
754
+ const result = mode === "global" ? installGlobal(args.withConfig) : installLocal(args.withConfig);
755
+ console.log(result.message);
756
+ if (args.skillsMode === "none") {
757
+ console.log("Skill installation skipped (--no-skills).");
758
+ } else if (result.success) {
759
+ const skillsResult = installSkills(args.skillsMode);
760
+ if (skillsResult.success) {
761
+ console.log(skillsResult.message);
762
+ } else {
763
+ console.warn(skillsResult.message);
764
+ }
765
+ } else {
766
+ console.warn("Skill installation skipped because plugin install failed.");
767
+ }
768
+ if (args.fullInstall && result.success) {
769
+ try {
770
+ const extensionPath = extractExtension();
771
+ if (extensionPath) {
772
+ console.log(`Extension assets extracted to ${extensionPath}`);
773
+ } else {
774
+ console.warn("Extension assets not found; skipping extraction.");
775
+ }
776
+ } catch (error) {
777
+ const message = error instanceof Error ? error.message : String(error);
778
+ console.warn(`Extension pre-extraction failed: ${message}`);
779
+ }
780
+ }
781
+ if (result.success && !result.alreadyInstalled) {
782
+ console.log("\nNext steps:");
783
+ console.log(" 1. Start or restart OpenCode");
784
+ console.log(" 2. Use opendevbrowser_status to verify the plugin is loaded");
785
+ console.log("\nFor help: npx opendevbrowser --help");
786
+ }
787
+ process.exit(result.success ? 0 : 1);
788
+ break;
789
+ }
790
+ }
791
+ } catch (error) {
792
+ const message = error instanceof Error ? error.message : String(error);
793
+ console.error(`Error: ${message}`);
794
+ console.error("\nFor help: npx opendevbrowser --help");
795
+ process.exit(1);
796
+ }
797
+ }
798
+ main().catch((error) => {
799
+ console.error("Unexpected error:", error);
800
+ process.exit(1);
801
+ });
802
+ //# sourceMappingURL=index.js.map