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.
- package/README.md +241 -0
- package/dist/chunk-R5VUZEUU.js +128 -0
- package/dist/chunk-R5VUZEUU.js.map +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +802 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +3615 -0
- package/dist/index.js.map +1 -0
- package/dist/opendevbrowser.d.ts +5 -0
- package/dist/opendevbrowser.js +3615 -0
- package/dist/opendevbrowser.js.map +1 -0
- package/extension/dist/background.js +32 -0
- package/extension/dist/popup.js +150 -0
- package/extension/dist/popup.jsx +150 -0
- package/extension/dist/relay-settings.js +4 -0
- package/extension/dist/services/CDPRouter.js +176 -0
- package/extension/dist/services/ConnectionManager.js +301 -0
- package/extension/dist/services/RelayClient.js +73 -0
- package/extension/dist/services/TabManager.js +18 -0
- package/extension/dist/types.js +1 -0
- package/extension/icons/icon128.png +0 -0
- package/extension/icons/icon16.png +0 -0
- package/extension/icons/icon32.png +0 -0
- package/extension/icons/icon48.png +0 -0
- package/extension/manifest.json +34 -0
- package/extension/popup.html +108 -0
- package/package.json +71 -0
- package/skills/AGENTS.md +80 -0
- package/skills/data-extraction/SKILL.md +136 -0
- package/skills/form-testing/SKILL.md +113 -0
- package/skills/login-automation/SKILL.md +98 -0
- package/skills/opendevbrowser-best-practices/SKILL.md +81 -0
- package/skills/opendevbrowser-continuity-ledger/SKILL.md +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# OpenDevBrowser
|
|
2
|
+
|
|
3
|
+
[](https://registry.npmjs.org/opendevbrowser)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
[](https://opencode.ai)
|
|
7
|
+
[](https://github.com/freshtechbro/opendevbrowser)
|
|
8
|
+
|
|
9
|
+
> **Script-first browser automation for AI agents.** Snapshot → Refs → Actions.
|
|
10
|
+
|
|
11
|
+
OpenDevBrowser is an [OpenCode](https://opencode.ai) plugin that gives AI agents direct browser control via Chrome DevTools Protocol. Launch browsers, capture page snapshots, and interact with elements using stable refs.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
### For Humans
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Interactive installer (recommended)
|
|
21
|
+
npx opendevbrowser
|
|
22
|
+
|
|
23
|
+
# Or specify location
|
|
24
|
+
npx opendevbrowser --global # ~/.config/opencode/opencode.json
|
|
25
|
+
npx opendevbrowser --local # ./opencode.json
|
|
26
|
+
|
|
27
|
+
# Full install (config + extension assets)
|
|
28
|
+
npx opendevbrowser --full
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Restart OpenCode after installation.
|
|
32
|
+
|
|
33
|
+
OpenCode discovers skills in `.opencode/skill` (project) and `~/.config/opencode/skill` (global) first; `.claude/skills` is compatibility-only. The CLI installs bundled skills into the OpenCode-native locations by default.
|
|
34
|
+
|
|
35
|
+
### Agent Installation (OpenCode)
|
|
36
|
+
|
|
37
|
+
Recommended (CLI, installs plugin + config + bundled skills + extension assets):
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx opendevbrowser --full --global --no-prompt
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Manual fallback (edit OpenCode config):
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"$schema": "https://opencode.ai/config.json",
|
|
48
|
+
"plugin": ["opendevbrowser"]
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Config location: `~/.config/opencode/opencode.json`
|
|
53
|
+
|
|
54
|
+
Restart OpenCode, then run `opendevbrowser_status` to verify the plugin is loaded.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Quick Start
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
1. Launch a browser session
|
|
62
|
+
2. Navigate to a URL
|
|
63
|
+
3. Take a snapshot to get element refs
|
|
64
|
+
4. Interact using refs (click, type, select)
|
|
65
|
+
5. Re-snapshot after navigation
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Core Workflow
|
|
69
|
+
|
|
70
|
+
| Step | Tool | Purpose |
|
|
71
|
+
|------|------|---------|
|
|
72
|
+
| 1 | `opendevbrowser_launch` | Start managed Chrome session |
|
|
73
|
+
| 2 | `opendevbrowser_goto` | Navigate to URL |
|
|
74
|
+
| 3 | `opendevbrowser_snapshot` | Get page structure with refs |
|
|
75
|
+
| 4 | `opendevbrowser_click` / `opendevbrowser_type` | Interact with elements |
|
|
76
|
+
| 5 | `opendevbrowser_close` | Clean up session |
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Features
|
|
81
|
+
|
|
82
|
+
### Browser Control
|
|
83
|
+
- **Launch & Connect** - Start managed Chrome or connect to existing browsers
|
|
84
|
+
- **Multi-Tab Support** - Create, switch, and manage browser tabs
|
|
85
|
+
- **Profile Persistence** - Maintain login sessions across runs
|
|
86
|
+
- **Headless Mode** - Run without visible browser window
|
|
87
|
+
|
|
88
|
+
### Page Interaction
|
|
89
|
+
- **Snapshot** - Accessibility-tree based page capture (token-efficient)
|
|
90
|
+
- **Click** - Click elements by ref
|
|
91
|
+
- **Type** - Enter text into inputs
|
|
92
|
+
- **Select** - Choose dropdown options
|
|
93
|
+
- **Scroll** - Scroll page or elements
|
|
94
|
+
- **Wait** - Wait for selectors or navigation
|
|
95
|
+
|
|
96
|
+
### DevTools Integration
|
|
97
|
+
- **Console Capture** - Monitor console.log, errors, warnings
|
|
98
|
+
- **Network Tracking** - Capture XHR/fetch requests and responses
|
|
99
|
+
- **Screenshot** - Full page or element screenshots
|
|
100
|
+
- **Performance** - Page load metrics
|
|
101
|
+
|
|
102
|
+
### Export & Clone
|
|
103
|
+
- **DOM Capture** - Extract sanitized HTML with inline styles
|
|
104
|
+
- **React Emitter** - Generate React component code from pages
|
|
105
|
+
- **CSS Extraction** - Pull computed styles
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Chrome Extension (Optional)
|
|
110
|
+
|
|
111
|
+
The extension enables **Mode C** - attach to existing logged-in browser tabs without launching a new browser.
|
|
112
|
+
|
|
113
|
+
### Auto-Pair Feature
|
|
114
|
+
|
|
115
|
+
The plugin and extension can automatically pair:
|
|
116
|
+
|
|
117
|
+
1. **Plugin side**: Auto-generates secure token on first run (saved to config)
|
|
118
|
+
2. **Extension side**: Enable "Auto-Pair" toggle and click Connect
|
|
119
|
+
3. Extension fetches token from plugin's relay server
|
|
120
|
+
4. Connection established with color indicator (green = connected)
|
|
121
|
+
|
|
122
|
+
### Manual Setup
|
|
123
|
+
|
|
124
|
+
1. Install extension from Chrome Web Store or load unpacked from `~/.cache/opencode/opendevbrowser-extension/`
|
|
125
|
+
2. Open extension popup
|
|
126
|
+
3. Enter same port/token as plugin config
|
|
127
|
+
4. Click Connect
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Configuration
|
|
132
|
+
|
|
133
|
+
Optional config file: `~/.config/opencode/opendevbrowser.jsonc`
|
|
134
|
+
|
|
135
|
+
```jsonc
|
|
136
|
+
{
|
|
137
|
+
"headless": false,
|
|
138
|
+
"profile": "default",
|
|
139
|
+
"persistProfile": true,
|
|
140
|
+
"snapshot": { "maxChars": 16000, "maxNodes": 1000 },
|
|
141
|
+
"export": { "maxNodes": 1000, "inlineStyles": true },
|
|
142
|
+
"devtools": { "showFullUrls": false, "showFullConsole": false },
|
|
143
|
+
"security": {
|
|
144
|
+
"allowRawCDP": false,
|
|
145
|
+
"allowNonLocalCdp": false,
|
|
146
|
+
"allowUnsafeExport": false
|
|
147
|
+
},
|
|
148
|
+
"continuity": {
|
|
149
|
+
"enabled": true,
|
|
150
|
+
"filePath": "opendevbrowser_continuity.md",
|
|
151
|
+
"nudge": {
|
|
152
|
+
"enabled": true,
|
|
153
|
+
"keywords": [
|
|
154
|
+
"plan",
|
|
155
|
+
"multi-step",
|
|
156
|
+
"multi step",
|
|
157
|
+
"long-running",
|
|
158
|
+
"long running",
|
|
159
|
+
"refactor",
|
|
160
|
+
"migration",
|
|
161
|
+
"rollout",
|
|
162
|
+
"release",
|
|
163
|
+
"upgrade",
|
|
164
|
+
"investigate",
|
|
165
|
+
"follow-up",
|
|
166
|
+
"continue"
|
|
167
|
+
],
|
|
168
|
+
"maxAgeMs": 60000
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
"relayPort": 8787,
|
|
172
|
+
"relayToken": "auto-generated-on-first-run"
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
All fields optional. Plugin works with sensible defaults.
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## CLI Commands
|
|
181
|
+
|
|
182
|
+
| Command | Description |
|
|
183
|
+
|---------|-------------|
|
|
184
|
+
| `npx opendevbrowser` | Interactive install |
|
|
185
|
+
| `npx opendevbrowser --global` | Install to global config |
|
|
186
|
+
| `npx opendevbrowser --local` | Install to project config |
|
|
187
|
+
| `npx opendevbrowser --with-config` | Also create opendevbrowser.jsonc |
|
|
188
|
+
| `npx opendevbrowser --full` | Full install (config + extension assets) |
|
|
189
|
+
| `npx opendevbrowser --update` | Clear cache, trigger reinstall |
|
|
190
|
+
| `npx opendevbrowser --uninstall` | Remove from config |
|
|
191
|
+
| `npx opendevbrowser --version` | Show version |
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Security
|
|
196
|
+
|
|
197
|
+
- **Relay Authentication** - Cryptographically secure tokens, timing-safe comparison
|
|
198
|
+
- **Origin Validation** - Only localhost and Chrome extensions can pair
|
|
199
|
+
- **CDP Localhost-Only** - Remote CDP endpoints blocked by default
|
|
200
|
+
- **Data Redaction** - Console/network output redacts tokens and API keys
|
|
201
|
+
- **Export Sanitization** - Scripts and event handlers stripped from exports
|
|
202
|
+
- **Atomic Config Writes** - Prevents config corruption on crash
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Updating
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
# Option 1: Clear cache (recommended)
|
|
210
|
+
rm -rf ~/.cache/opencode/node_modules/opendevbrowser
|
|
211
|
+
# Then restart OpenCode
|
|
212
|
+
|
|
213
|
+
# Option 2: Use CLI
|
|
214
|
+
npx opendevbrowser --update
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Release checklist: `docs/DISTRIBUTION_PLAN.md`
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Development
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
npm install
|
|
225
|
+
npm run build # Compile to dist/
|
|
226
|
+
npm run test # Run tests with coverage
|
|
227
|
+
npm run lint # ESLint checks
|
|
228
|
+
npm run extension:build # Compile extension
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Privacy
|
|
234
|
+
|
|
235
|
+
See [Privacy Policy](docs/privacy.md) for data handling details.
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## License
|
|
240
|
+
|
|
241
|
+
MIT
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// src/extension-extractor.ts
|
|
2
|
+
import { existsSync, mkdirSync, cpSync, readFileSync, writeFileSync, rmSync, renameSync } from "fs";
|
|
3
|
+
import { dirname, join } from "path";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
var EXTENSION_DIR_NAME = "opendevbrowser";
|
|
7
|
+
var VERSION_FILE = ".version";
|
|
8
|
+
function getConfigDir() {
|
|
9
|
+
return join(homedir(), ".config", "opencode", EXTENSION_DIR_NAME, "extension");
|
|
10
|
+
}
|
|
11
|
+
function getPackageVersion() {
|
|
12
|
+
try {
|
|
13
|
+
const pkgPath = join(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
14
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
15
|
+
return pkg.version || "0.0.0";
|
|
16
|
+
} catch (error) {
|
|
17
|
+
console.warn("[opendevbrowser] Failed to read package.json for extension version:", error);
|
|
18
|
+
return "0.0.0";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function getInstalledVersion(destDir) {
|
|
22
|
+
try {
|
|
23
|
+
const versionPath = join(destDir, VERSION_FILE);
|
|
24
|
+
if (existsSync(versionPath)) {
|
|
25
|
+
return readFileSync(versionPath, "utf-8").trim();
|
|
26
|
+
}
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.warn("[opendevbrowser] Failed to read installed extension version:", error);
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
function getBundledExtensionPath() {
|
|
33
|
+
const candidates = [
|
|
34
|
+
join(dirname(fileURLToPath(import.meta.url)), "..", "extension"),
|
|
35
|
+
join(dirname(fileURLToPath(import.meta.url)), "..", "..", "extension")
|
|
36
|
+
];
|
|
37
|
+
for (const candidate of candidates) {
|
|
38
|
+
if (existsSync(join(candidate, "manifest.json"))) {
|
|
39
|
+
return candidate;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
function isCompleteInstall(dir) {
|
|
45
|
+
const required = ["manifest.json", VERSION_FILE];
|
|
46
|
+
return required.every((file) => existsSync(join(dir, file)));
|
|
47
|
+
}
|
|
48
|
+
function extractExtension() {
|
|
49
|
+
const bundledPath = getBundledExtensionPath();
|
|
50
|
+
if (!bundledPath) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const destDir = getConfigDir();
|
|
54
|
+
const currentVersion = getPackageVersion();
|
|
55
|
+
const installedVersion = getInstalledVersion(destDir);
|
|
56
|
+
if (installedVersion === currentVersion && isCompleteInstall(destDir)) {
|
|
57
|
+
return destDir;
|
|
58
|
+
}
|
|
59
|
+
const parentDir = dirname(destDir);
|
|
60
|
+
const stagingDir = join(parentDir, `.opendevbrowser-staging-${process.pid}-${Date.now()}`);
|
|
61
|
+
const backupDir = join(parentDir, `.opendevbrowser-backup-${process.pid}-${Date.now()}`);
|
|
62
|
+
try {
|
|
63
|
+
mkdirSync(stagingDir, { recursive: true });
|
|
64
|
+
const itemsToCopy = ["manifest.json", "popup.html", "dist", "icons"];
|
|
65
|
+
for (const item of itemsToCopy) {
|
|
66
|
+
const src = join(bundledPath, item);
|
|
67
|
+
const dest = join(stagingDir, item);
|
|
68
|
+
if (existsSync(src)) {
|
|
69
|
+
cpSync(src, dest, { recursive: true, force: true });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
writeFileSync(join(stagingDir, VERSION_FILE), currentVersion, "utf-8");
|
|
73
|
+
if (!isCompleteInstall(stagingDir)) {
|
|
74
|
+
throw new Error("Staging directory incomplete after copy");
|
|
75
|
+
}
|
|
76
|
+
if (existsSync(destDir)) {
|
|
77
|
+
renameSync(destDir, backupDir);
|
|
78
|
+
}
|
|
79
|
+
renameSync(stagingDir, destDir);
|
|
80
|
+
if (existsSync(backupDir)) {
|
|
81
|
+
rmSync(backupDir, { recursive: true, force: true });
|
|
82
|
+
}
|
|
83
|
+
return destDir;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (existsSync(backupDir) && !existsSync(destDir)) {
|
|
86
|
+
try {
|
|
87
|
+
renameSync(backupDir, destDir);
|
|
88
|
+
} catch (rollbackError) {
|
|
89
|
+
console.warn(`[opendevbrowser] Warning: Rollback failed for ${backupDir}:`, rollbackError);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (existsSync(stagingDir)) {
|
|
93
|
+
try {
|
|
94
|
+
rmSync(stagingDir, { recursive: true, force: true });
|
|
95
|
+
} catch (stagingCleanupError) {
|
|
96
|
+
console.warn(`[opendevbrowser] Warning: Failed to clean up staging directory ${stagingDir}:`, stagingCleanupError);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (existsSync(backupDir)) {
|
|
100
|
+
try {
|
|
101
|
+
rmSync(backupDir, { recursive: true, force: true });
|
|
102
|
+
} catch (backupCleanupError) {
|
|
103
|
+
console.warn(`[opendevbrowser] Warning: Failed to clean up backup directory ${backupDir}:`, backupCleanupError);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function getExtensionPath() {
|
|
110
|
+
const destDir = getConfigDir();
|
|
111
|
+
if (isCompleteInstall(destDir)) {
|
|
112
|
+
return destDir;
|
|
113
|
+
}
|
|
114
|
+
return getBundledExtensionPath();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/utils/crypto.ts
|
|
118
|
+
import { randomBytes } from "crypto";
|
|
119
|
+
function generateSecureToken() {
|
|
120
|
+
return randomBytes(32).toString("hex");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export {
|
|
124
|
+
generateSecureToken,
|
|
125
|
+
extractExtension,
|
|
126
|
+
getExtensionPath
|
|
127
|
+
};
|
|
128
|
+
//# sourceMappingURL=chunk-R5VUZEUU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/extension-extractor.ts","../src/utils/crypto.ts"],"sourcesContent":["import { existsSync, mkdirSync, cpSync, readFileSync, writeFileSync, rmSync, renameSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport { homedir } from \"os\";\nimport { fileURLToPath } from \"url\";\n\nconst EXTENSION_DIR_NAME = \"opendevbrowser\";\nconst VERSION_FILE = \".version\";\n\nfunction getConfigDir(): string {\n return join(homedir(), \".config\", \"opencode\", EXTENSION_DIR_NAME, \"extension\");\n}\n\nfunction getPackageVersion(): string {\n try {\n const pkgPath = join(dirname(fileURLToPath(import.meta.url)), \"..\", \"package.json\");\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\"));\n return pkg.version || \"0.0.0\";\n } catch (error) {\n console.warn(\"[opendevbrowser] Failed to read package.json for extension version:\", error);\n return \"0.0.0\";\n }\n}\n\nfunction getInstalledVersion(destDir: string): string | null {\n try {\n const versionPath = join(destDir, VERSION_FILE);\n if (existsSync(versionPath)) {\n return readFileSync(versionPath, \"utf-8\").trim();\n }\n } catch (error) {\n console.warn(\"[opendevbrowser] Failed to read installed extension version:\", error);\n }\n return null;\n}\n\nfunction getBundledExtensionPath(): string | null {\n const candidates = [\n join(dirname(fileURLToPath(import.meta.url)), \"..\", \"extension\"),\n join(dirname(fileURLToPath(import.meta.url)), \"..\", \"..\", \"extension\")\n ];\n for (const candidate of candidates) {\n if (existsSync(join(candidate, \"manifest.json\"))) {\n return candidate;\n }\n }\n return null;\n}\n\nfunction isCompleteInstall(dir: string): boolean {\n const required = [\"manifest.json\", VERSION_FILE];\n return required.every(file => existsSync(join(dir, file)));\n}\n\nexport function extractExtension(): string | null {\n const bundledPath = getBundledExtensionPath();\n if (!bundledPath) {\n return null;\n }\n\n const destDir = getConfigDir();\n const currentVersion = getPackageVersion();\n const installedVersion = getInstalledVersion(destDir);\n\n // Early return if version matches and installation is complete\n if (installedVersion === currentVersion && isCompleteInstall(destDir)) {\n return destDir;\n }\n\n // Create staging directory (sibling to destDir for same-device rename)\n const parentDir = dirname(destDir);\n const stagingDir = join(parentDir, `.opendevbrowser-staging-${process.pid}-${Date.now()}`);\n const backupDir = join(parentDir, `.opendevbrowser-backup-${process.pid}-${Date.now()}`);\n\n try {\n // Step 1: Copy to staging\n mkdirSync(stagingDir, { recursive: true });\n const itemsToCopy = [\"manifest.json\", \"popup.html\", \"dist\", \"icons\"];\n for (const item of itemsToCopy) {\n const src = join(bundledPath, item);\n const dest = join(stagingDir, item);\n if (existsSync(src)) {\n cpSync(src, dest, { recursive: true, force: true });\n }\n }\n writeFileSync(join(stagingDir, VERSION_FILE), currentVersion, \"utf-8\");\n\n // Step 2: Validate staging is complete\n if (!isCompleteInstall(stagingDir)) {\n throw new Error(\"Staging directory incomplete after copy\");\n }\n\n // Step 3: Atomic swap\n if (existsSync(destDir)) {\n renameSync(destDir, backupDir);\n }\n renameSync(stagingDir, destDir);\n\n // Step 4: Cleanup backup\n if (existsSync(backupDir)) {\n rmSync(backupDir, { recursive: true, force: true });\n }\n\n return destDir;\n } catch (error) {\n // Rollback: restore backup if it exists\n if (existsSync(backupDir) && !existsSync(destDir)) {\n try {\n renameSync(backupDir, destDir);\n } catch (rollbackError) {\n console.warn(`[opendevbrowser] Warning: Rollback failed for ${backupDir}:`, rollbackError);\n }\n }\n // Cleanup staging\n if (existsSync(stagingDir)) {\n try {\n rmSync(stagingDir, { recursive: true, force: true });\n } catch (stagingCleanupError) {\n console.warn(`[opendevbrowser] Warning: Failed to clean up staging directory ${stagingDir}:`, stagingCleanupError);\n }\n }\n // Cleanup backup\n if (existsSync(backupDir)) {\n try {\n rmSync(backupDir, { recursive: true, force: true });\n } catch (backupCleanupError) {\n console.warn(`[opendevbrowser] Warning: Failed to clean up backup directory ${backupDir}:`, backupCleanupError);\n }\n }\n throw error;\n }\n}\n\nexport function getExtensionPath(): string | null {\n const destDir = getConfigDir();\n if (isCompleteInstall(destDir)) {\n return destDir;\n }\n return getBundledExtensionPath();\n}\n","import { randomBytes } from \"crypto\";\n\nexport function generateSecureToken(): string {\n return randomBytes(32).toString(\"hex\");\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|