@ulpi/browse 0.2.4 → 0.2.5
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/package.json +1 -1
- package/skill/SKILL.md +5 -2
- package/src/cli.ts +67 -26
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -361,6 +361,7 @@ browse har stop [path] Stop and save HAR file
|
|
|
361
361
|
### Server management
|
|
362
362
|
```
|
|
363
363
|
browse status Server health, uptime, session count
|
|
364
|
+
browse instances List all running browse servers (instance, PID, port, status)
|
|
364
365
|
browse stop Shutdown server
|
|
365
366
|
browse restart Kill + restart server
|
|
366
367
|
```
|
|
@@ -425,12 +426,14 @@ browse restart Kill + restart server
|
|
|
425
426
|
|
|
426
427
|
- Persistent Chromium daemon on localhost (port 9400-10400)
|
|
427
428
|
- Bearer token auth per session
|
|
428
|
-
-
|
|
429
|
+
- One server per project directory — `--session` handles agent isolation
|
|
429
430
|
- Session multiplexing: multiple agents share one Chromium via isolated BrowserContexts
|
|
431
|
+
- For separate servers: set `BROWSE_INSTANCE` env var (e.g., fault isolation between teams)
|
|
432
|
+
- `browse instances` — discover all running servers (PID, port, status, session count)
|
|
430
433
|
- Project-local state: `.browse/` directory at project root (auto-created, self-gitignored)
|
|
431
434
|
- `sessions/{id}/` — per-session screenshots, logs, PDFs
|
|
432
435
|
- `states/{name}.json` — saved browser state (cookies + localStorage)
|
|
433
|
-
- `browse-server
|
|
436
|
+
- `browse-server.json` — server PID, port, auth token
|
|
434
437
|
- Auto-shutdown when all sessions idle past 30 min
|
|
435
438
|
- Chromium crash → server exits → auto-restarts on next command
|
|
436
439
|
- AI-friendly error messages: Playwright errors rewritten to actionable hints
|
package/src/cli.ts
CHANGED
|
@@ -22,11 +22,9 @@ const cliFlags = {
|
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
const BROWSE_PORT = parseInt(process.env.BROWSE_PORT || '0', 10);
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
const IS_COMPILED = import.meta.dir.includes('$bunfs');
|
|
29
|
-
const BROWSE_INSTANCE = process.env.BROWSE_INSTANCE || (BROWSE_PORT || IS_COMPILED ? '' : String(process.ppid));
|
|
25
|
+
// One server per project directory by default. Sessions handle agent isolation.
|
|
26
|
+
// For multiple servers on the same project: set BROWSE_INSTANCE or BROWSE_PORT.
|
|
27
|
+
const BROWSE_INSTANCE = process.env.BROWSE_INSTANCE || '';
|
|
30
28
|
const INSTANCE_SUFFIX = BROWSE_PORT ? `-${BROWSE_PORT}` : (BROWSE_INSTANCE ? `-${BROWSE_INSTANCE}` : '');
|
|
31
29
|
|
|
32
30
|
/**
|
|
@@ -124,6 +122,50 @@ function isProcessAlive(pid: number): boolean {
|
|
|
124
122
|
}
|
|
125
123
|
}
|
|
126
124
|
|
|
125
|
+
async function listInstances(): Promise<void> {
|
|
126
|
+
try {
|
|
127
|
+
const files = fs.readdirSync(LOCAL_DIR).filter(
|
|
128
|
+
f => f.startsWith('browse-server') && f.endsWith('.json') && !f.endsWith('.lock')
|
|
129
|
+
);
|
|
130
|
+
if (files.length === 0) { console.log('(no running instances)'); return; }
|
|
131
|
+
|
|
132
|
+
let found = false;
|
|
133
|
+
for (const file of files) {
|
|
134
|
+
try {
|
|
135
|
+
const data = JSON.parse(fs.readFileSync(path.join(LOCAL_DIR, file), 'utf-8'));
|
|
136
|
+
if (!data.pid || !data.port) continue;
|
|
137
|
+
|
|
138
|
+
const alive = isProcessAlive(data.pid);
|
|
139
|
+
let status = 'dead';
|
|
140
|
+
let sessions = 0;
|
|
141
|
+
if (alive) {
|
|
142
|
+
try {
|
|
143
|
+
const resp = await fetch(`http://127.0.0.1:${data.port}/health`, { signal: AbortSignal.timeout(1000) });
|
|
144
|
+
if (resp.ok) {
|
|
145
|
+
const health = await resp.json() as any;
|
|
146
|
+
status = health.status === 'healthy' ? 'healthy' : 'unhealthy';
|
|
147
|
+
sessions = health.sessions || 0;
|
|
148
|
+
}
|
|
149
|
+
} catch { status = 'unreachable'; }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Derive instance name from filename
|
|
153
|
+
const match = file.match(/^browse-server-?(.*)\.json$/);
|
|
154
|
+
const instance = match?.[1] || 'default';
|
|
155
|
+
|
|
156
|
+
console.log(` ${instance.padEnd(15)} PID ${String(data.pid).padEnd(8)} port ${data.port} ${status}${sessions ? ` ${sessions} session(s)` : ''}`);
|
|
157
|
+
found = true;
|
|
158
|
+
|
|
159
|
+
// Clean up dead entries
|
|
160
|
+
if (!alive) {
|
|
161
|
+
try { fs.unlinkSync(path.join(LOCAL_DIR, file)); } catch {}
|
|
162
|
+
}
|
|
163
|
+
} catch {}
|
|
164
|
+
}
|
|
165
|
+
if (!found) console.log('(no running instances)');
|
|
166
|
+
} catch { console.log('(no running instances)'); }
|
|
167
|
+
}
|
|
168
|
+
|
|
127
169
|
function isBrowseProcess(pid: number): boolean {
|
|
128
170
|
try {
|
|
129
171
|
const { execSync } = require('child_process');
|
|
@@ -291,9 +333,10 @@ async function ensureServer(): Promise<ServerState> {
|
|
|
291
333
|
}
|
|
292
334
|
|
|
293
335
|
/**
|
|
294
|
-
* Clean up orphaned browse
|
|
295
|
-
*
|
|
296
|
-
*
|
|
336
|
+
* Clean up orphaned browse server state files.
|
|
337
|
+
* Removes any browse-server*.json whose PID is dead.
|
|
338
|
+
* Kills live orphans (legacy PPID-suffixed files from pre-v0.2.4) if they're browse processes.
|
|
339
|
+
* Preserves intentional BROWSE_PORT instances (suffix matches port inside the file).
|
|
297
340
|
*/
|
|
298
341
|
function cleanOrphanedServers(): void {
|
|
299
342
|
try {
|
|
@@ -301,27 +344,20 @@ function cleanOrphanedServers(): void {
|
|
|
301
344
|
for (const file of files) {
|
|
302
345
|
if (!file.startsWith('browse-server') || !file.endsWith('.json') || file.endsWith('.lock')) continue;
|
|
303
346
|
const filePath = path.join(LOCAL_DIR, file);
|
|
304
|
-
if (filePath === STATE_FILE) continue;
|
|
305
|
-
// Only clean files with PID-based suffixes. Skip port-based and non-numeric.
|
|
306
|
-
// Port-based files have a port number from a BROWSE_PORT env var.
|
|
307
|
-
// PID-based files have a process ID (typically >10000, never <1000).
|
|
308
|
-
// To distinguish: read the state file and check if the suffix matches the PID inside.
|
|
309
|
-
const suffixMatch = file.match(/browse-server-(\d+)\.json$/);
|
|
310
|
-
if (!suffixMatch) continue;
|
|
311
|
-
const suffix = parseInt(suffixMatch[1], 10);
|
|
347
|
+
if (filePath === STATE_FILE) continue;
|
|
312
348
|
try {
|
|
313
349
|
const data = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
314
|
-
if (!data.pid) continue;
|
|
315
|
-
//
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
350
|
+
if (!data.pid) { fs.unlinkSync(filePath); continue; }
|
|
351
|
+
// Preserve intentional BROWSE_PORT instances (suffix = port number)
|
|
352
|
+
const suffixMatch = file.match(/browse-server-(\d+)\.json$/);
|
|
353
|
+
if (suffixMatch && data.port === parseInt(suffixMatch[1], 10) && isProcessAlive(data.pid)) continue;
|
|
354
|
+
// Dead process → remove state file
|
|
355
|
+
if (!isProcessAlive(data.pid)) { fs.unlinkSync(filePath); continue; }
|
|
356
|
+
// Live orphan (legacy PPID file) → kill if it's a browse process
|
|
357
|
+
if (isBrowseProcess(data.pid)) {
|
|
319
358
|
try { process.kill(data.pid, 'SIGTERM'); } catch {}
|
|
320
359
|
}
|
|
321
|
-
|
|
322
|
-
fs.unlinkSync(filePath);
|
|
323
|
-
}
|
|
324
|
-
} catch {}
|
|
360
|
+
} catch { try { fs.unlinkSync(filePath); } catch {} }
|
|
325
361
|
}
|
|
326
362
|
} catch {}
|
|
327
363
|
}
|
|
@@ -529,6 +565,11 @@ export async function main() {
|
|
|
529
565
|
cliFlags.allowedDomains = allowedDomains || '';
|
|
530
566
|
|
|
531
567
|
// ─── Local commands (no server needed) ─────────────────────
|
|
568
|
+
if (args[0] === 'instances') {
|
|
569
|
+
await listInstances();
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
|
|
532
573
|
if (args[0] === 'install-skill') {
|
|
533
574
|
const { installSkill } = await import('./install-skill');
|
|
534
575
|
installSkill(args[1]);
|
|
@@ -566,7 +607,7 @@ Sessions: sessions | session-close <id>
|
|
|
566
607
|
Auth: auth save <name> <url> <user> <pass|--password-stdin>
|
|
567
608
|
auth login <name> | auth list | auth delete <name>
|
|
568
609
|
State: state save|load|list|show [name]
|
|
569
|
-
Server: status | cookie <n>=<v> | header <n>:<v>
|
|
610
|
+
Server: status | instances | cookie <n>=<v> | header <n>:<v>
|
|
570
611
|
useragent <str> | stop | restart
|
|
571
612
|
Setup: install-skill [path]
|
|
572
613
|
|