This commit is contained in:
Chris Tate
2026-01-10 11:43:55 -06:00
parent 2d0520b770
commit 9d253da8bd
8 changed files with 527 additions and 62 deletions
+134 -2
View File
@@ -1,7 +1,40 @@
#!/usr/bin/env node
import { send, setDebug } from './client.js';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { send, setDebug, setSession, getSession } from './client.js';
import type { Response } from './types.js';
/**
* List all active veb sessions
*/
function listSessions(): string[] {
const tmpDir = os.tmpdir();
try {
const files = fs.readdirSync(tmpDir);
const sessions: string[] = [];
for (const file of files) {
const match = file.match(/^veb-(.+)\.pid$/);
if (match) {
const pidFile = path.join(tmpDir, file);
try {
const pid = parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10);
// Check if process is still running
process.kill(pid, 0);
sessions.push(match[1]);
} catch {
// Process not running, ignore
}
}
}
return sessions;
} catch {
return [];
}
}
// ANSI colors
const colors = {
reset: '\x1b[0m',
@@ -39,7 +72,19 @@ ${c('yellow', 'Commands:')}
${c('cyan', 'select')} <selector> <value> Select dropdown option
${c('cyan', 'close')} Close browser and stop daemon
${c('yellow', 'Tab/Window Commands:')}
${c('cyan', 'tab new')} Open a new tab
${c('cyan', 'tab list')} List all open tabs
${c('cyan', 'tab')} <index> Switch to tab by index
${c('cyan', 'tab close')} [index] Close tab (current if no index)
${c('cyan', 'window new')} Open a new window
${c('yellow', 'Session Commands:')}
${c('cyan', 'session')} Show current session name
${c('cyan', 'session list')} List all active sessions
${c('yellow', 'Options:')}
--session <name> Use isolated browser session (or VEB_SESSION env)
--json Output raw JSON (for agents)
--selector, -s <sel> Target specific element
--text, -t Wait for text instead of selector
@@ -57,6 +102,9 @@ ${c('yellow', 'Examples:')}
veb extract "table" --json
veb eval "document.title"
veb scroll down 500
veb tab new
veb tab list
veb tab 0
`);
}
@@ -101,8 +149,24 @@ function printResponse(response: Response, jsonMode: boolean): void {
console.log(c('green', '✓'), 'Done');
} else if (data.launched) {
console.log(c('green', '✓'), 'Browser launched');
} else if (data.closed) {
} else if (data.closed === true) {
console.log(c('green', '✓'), 'Browser closed');
} else if (data.tabs) {
// Tab list
const tabs = data.tabs as Array<{ index: number; url: string; title: string; active: boolean }>;
tabs.forEach(tab => {
const marker = tab.active ? c('green', '→') : ' ';
const idx = c('cyan', `[${tab.index}]`);
const title = tab.title || c('dim', '(untitled)');
console.log(`${marker} ${idx} ${title}`);
if (tab.url) console.log(c('dim', ` ${tab.url}`));
});
} else if (data.index !== undefined && data.total !== undefined) {
// Tab new / window new
console.log(c('green', '✓'), `Tab ${data.index} created (${data.total} total)`);
} else if (data.remaining !== undefined) {
// Tab close
console.log(c('green', '✓'), `Tab closed (${data.remaining} remaining)`);
} else {
console.log(c('green', '✓'), JSON.stringify(data));
}
@@ -117,6 +181,13 @@ async function main(): Promise<void> {
setDebug(true);
}
// Handle session - check --session flag first, then env var
const sessionIdx = args.findIndex(a => a === '--session');
if (sessionIdx !== -1 && args[sessionIdx + 1]) {
setSession(args[sessionIdx + 1]);
}
// VEB_SESSION env var is already handled by daemon.ts default
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
printHelp();
process.exit(0);
@@ -132,6 +203,7 @@ async function main(): Promise<void> {
// Check if previous arg was a flag that takes a value
const prev = args[i - 1];
if (prev === '--selector' || prev === '-s') return false;
if (prev === '--session') return false;
return true;
});
const command = cleanArgs[0];
@@ -292,6 +364,66 @@ async function main(): Promise<void> {
break;
}
case 'tab': {
const subCmd = cleanArgs[1];
if (subCmd === 'new') {
cmd = { id, action: 'tab_new' };
} else if (subCmd === 'list' || subCmd === 'ls') {
cmd = { id, action: 'tab_list' };
} else if (subCmd === 'close') {
const tabIndex = cleanArgs[2] !== undefined ? parseInt(cleanArgs[2], 10) : undefined;
cmd = { id, action: 'tab_close', index: tabIndex };
} else if (subCmd !== undefined) {
// Assume it's a tab index to switch to
const tabIndex = parseInt(subCmd, 10);
if (isNaN(tabIndex)) {
console.error(c('red', 'Error:'), `Invalid tab command or index: ${subCmd}`);
process.exit(1);
}
cmd = { id, action: 'tab_switch', index: tabIndex };
} else {
// No subcommand - list tabs
cmd = { id, action: 'tab_list' };
}
break;
}
case 'window': {
const subCmd = cleanArgs[1];
if (subCmd === 'new') {
cmd = { id, action: 'window_new' };
} else {
console.error(c('red', 'Error:'), 'Usage: veb window new');
process.exit(1);
}
break;
}
case 'session': {
const subCmd = cleanArgs[1];
if (subCmd === 'list' || subCmd === 'ls') {
const sessions = listSessions();
const currentSession = getSession();
if (sessions.length === 0) {
console.log(c('dim', 'No active sessions'));
} else {
sessions.forEach(sess => {
const marker = sess === currentSession ? c('green', '→') : ' ';
console.log(`${marker} ${c('cyan', sess)}`);
});
}
process.exit(0);
} else {
// Show current session
console.log(c('cyan', getSession()));
process.exit(0);
}
}
default:
console.error(c('red', 'Error:'), `Unknown command: ${command}`);
console.error(c('dim', 'Run veb --help for usage'));