aironin-browse-cli
Version:
aiRonin Browse CLI tool with headed Chrome support
582 lines โข 26.4 kB
JavaScript
import { Command } from "commander";
import chalk from "chalk";
import { BrowserSession } from "./browser/BrowserSession.js";
import { discoverChromeHostUrl } from "./browser/browserDiscovery.js";
import { createInterface } from "node:readline";
const program = new Command();
// Helper function to detect and configure remote browser
async function detectRemoteBrowser(forceRemote = false) {
// Try remote browser detection first (unless explicitly disabled)
if (forceRemote || process.env.REMOTE_BROWSER_ENABLED !== "false") {
console.log(chalk.blue("๐ Attempting remote browser detection..."));
const remoteHostUrl = await discoverChromeHostUrl(9222);
if (remoteHostUrl) {
console.log(chalk.green(`โ
Found remote browser at: ${remoteHostUrl}`));
process.env.REMOTE_BROWSER_ENABLED = "true";
process.env.REMOTE_BROWSER_HOST = remoteHostUrl;
}
else {
console.log(chalk.yellow("โ No remote browser found, using local browser"));
process.env.REMOTE_BROWSER_ENABLED = "false";
}
}
}
program
.name("aironin-browse")
.description("aiRonin Browse CLI with headed Chrome support")
.version("1.0.0");
// Global options
program
.option("-v, --viewport <size>", "Browser viewport size (e.g., 900x600)", "900x600")
.option("-q, --quality <number>", "Screenshot quality (1-100)", "75")
.option("-r, --remote", "Enable remote browser connection")
.option("-h, --host <url>", "Remote browser host URL");
// Test command
program
.command("test")
.description("Test browser connection and functionality")
.option("-u, --url <url>", "Test URL to navigate to", "https://example.com")
.action(async (options) => {
try {
console.log(chalk.yellow("๐งช Testing browser automation..."));
// Set environment variables from options
process.env.BROWSER_VIEWPORT_SIZE = options.viewport || "900x600";
process.env.SCREENSHOT_QUALITY = (options.quality || 75).toString();
// Detect remote browser (unless explicitly disabled)
await detectRemoteBrowser(options.remote);
const browser = new BrowserSession();
// Test 1: Launch browser
console.log(chalk.blue("1. Testing browser launch..."));
await browser.launchBrowser();
console.log(chalk.green("โ
Browser launched successfully"));
// Test 2: Navigate to URL
console.log(chalk.blue("2. Testing navigation..."));
const navResult = await browser.navigateToUrl(options.url);
console.log(chalk.green("โ
Navigation successful"));
console.log(chalk.gray(" Current URL:"), navResult.currentUrl);
// Test 3: Take screenshot
console.log(chalk.blue("3. Testing screenshot capture..."));
if (navResult.screenshot) {
console.log(chalk.green("โ
Screenshot captured successfully"));
console.log(chalk.gray(" Screenshot size:"), `${Math.round(navResult.screenshot.length / 1024)}KB`);
}
else {
console.log(chalk.red("โ Screenshot capture failed"));
}
// Test 4: Console logs
console.log(chalk.blue("4. Testing console log capture..."));
if (navResult.logs) {
console.log(chalk.green("โ
Console logs captured"));
console.log(chalk.gray(" Log count:"), navResult.logs.split('\n').filter(line => line.trim()).length);
}
else {
console.log(chalk.yellow("โ ๏ธ No console logs captured"));
}
// Test 5: Mouse interaction
console.log(chalk.blue("5. Testing mouse interaction..."));
const clickResult = await browser.click("100,100");
console.log(chalk.green("โ
Mouse click successful"));
// Test 6: Keyboard input
console.log(chalk.blue("6. Testing keyboard input..."));
const typeResult = await browser.type("test");
console.log(chalk.green("โ
Keyboard input successful"));
// Test 7: Scroll
console.log(chalk.blue("7. Testing scroll..."));
const scrollResult = await browser.scrollDown();
console.log(chalk.green("โ
Scroll successful"));
// Test 8: Close browser
console.log(chalk.blue("8. Testing browser close..."));
await browser.closeBrowser();
console.log(chalk.green("โ
Browser closed successfully"));
// Exit the process explicitly
process.exit(0);
console.log(chalk.green("\n๐ All tests passed! Browser automation is working correctly."));
console.log(chalk.gray("\nYou can now use the browser automation tool with your AI agent."));
}
catch (error) {
console.error(chalk.red("โ Test failed:"), error);
console.log(chalk.yellow("\nTroubleshooting tips:"));
console.log(chalk.gray("1. Ensure you have sufficient disk space for Chromium download"));
console.log(chalk.gray("2. Check your internet connection for Chromium download"));
console.log(chalk.gray("3. Verify Chrome/Chromium is not already running in debug mode"));
console.log(chalk.gray("4. Try running with --remote flag if you have Chrome running with --remote-debugging-port=9222"));
process.exit(1);
}
});
// Launch command
program
.command("launch")
.description("Launch browser and navigate to URL")
.argument("<url>", "URL to navigate to")
.option("-r, --remote <boolean>", "Connect to remote Chrome browser", "true")
.option("--host <host>", "Remote Chrome host URL")
.action(async (url, options = {}) => {
try {
// Set environment variables from options with defaults
process.env.BROWSER_VIEWPORT_SIZE = options.viewport || "900x600";
process.env.SCREENSHOT_QUALITY = (options.quality || 75).toString();
// Detect remote browser (unless explicitly disabled)
await detectRemoteBrowser(options.remote);
const browser = new BrowserSession();
await browser.launchBrowser();
const result = await browser.navigateToUrl(url);
console.log(chalk.green("โ
Browser launched successfully"));
console.log(chalk.blue("Current URL:"), result.currentUrl);
console.log(chalk.blue("Console logs:"), result.logs || "No logs");
if (result.screenshot) {
console.log(chalk.blue("Screenshot captured"));
// You could save the screenshot to a file here
}
// Close the browser to prevent hanging
await browser.closeBrowser();
console.log(chalk.gray("Browser closed"));
// Exit the process explicitly
process.exit(0);
}
catch (error) {
console.error(chalk.red("โ Error launching browser:"), error);
process.exit(1);
}
});
// Click command
program
.command("click")
.description("Click at coordinates")
.argument("<coordinates>", "Coordinates in format x,y")
.action(async (coordinates, options = {}) => {
try {
const browser = new BrowserSession();
const result = await browser.click(coordinates);
console.log(chalk.green("โ
Clicked successfully"));
console.log(chalk.blue("Console logs:"), result.logs || "No logs");
if (result.screenshot) {
console.log(chalk.blue("Screenshot captured"));
}
// Close the browser to prevent hanging
await browser.closeBrowser();
console.log(chalk.gray("Browser closed"));
// Exit the process explicitly
process.exit(0);
}
catch (error) {
console.error(chalk.red("โ Error clicking:"), error);
process.exit(1);
}
});
// Type command
program
.command("type")
.description("Type text")
.argument("<text>", "Text to type")
.action(async (text, options = {}) => {
try {
const browser = new BrowserSession();
const result = await browser.type(text);
console.log(chalk.green("โ
Typed successfully"));
console.log(chalk.blue("Console logs:"), result.logs || "No logs");
if (result.screenshot) {
console.log(chalk.blue("Screenshot captured"));
}
// Close the browser to prevent hanging
await browser.closeBrowser();
console.log(chalk.gray("Browser closed"));
// Exit the process explicitly
process.exit(0);
}
catch (error) {
console.error(chalk.red("โ Error typing:"), error);
process.exit(1);
}
});
// Scroll command
program
.command("scroll")
.description("Scroll page")
.argument("<direction>", "Scroll direction (up or down)")
.action(async (direction, options = {}) => {
try {
const browser = new BrowserSession();
const result = direction === "down" ? await browser.scrollDown() : await browser.scrollUp();
console.log(chalk.green(`โ
Scrolled ${direction} successfully`));
console.log(chalk.blue("Console logs:"), result.logs || "No logs");
if (result.screenshot) {
console.log(chalk.blue("Screenshot captured"));
}
// Close the browser to prevent hanging
await browser.closeBrowser();
console.log(chalk.gray("Browser closed"));
// Exit the process explicitly
process.exit(0);
}
catch (error) {
console.error(chalk.red("โ Error scrolling:"), error);
process.exit(1);
}
});
// Hover command
program
.command("hover")
.description("Hover at coordinates")
.argument("<coordinates>", "Coordinates in format x,y")
.action(async (coordinates, options = {}) => {
try {
const browser = new BrowserSession();
const result = await browser.hover(coordinates);
console.log(chalk.green("โ
Hovered successfully"));
console.log(chalk.blue("Console logs:"), result.logs || "No logs");
if (result.screenshot) {
console.log(chalk.blue("Screenshot captured"));
}
// Close the browser to prevent hanging
await browser.closeBrowser();
console.log(chalk.gray("Browser closed"));
// Exit the process explicitly
process.exit(0);
}
catch (error) {
console.error(chalk.red("โ Error hovering:"), error);
process.exit(1);
}
});
// Resize command
program
.command("resize")
.description("Resize browser window")
.argument("<size>", "Size in format width,height")
.action(async (size, options = {}) => {
try {
const browser = new BrowserSession();
const result = await browser.resize(size);
console.log(chalk.green("โ
Resized successfully"));
console.log(chalk.blue("Console logs:"), result.logs || "No logs");
if (result.screenshot) {
console.log(chalk.blue("Screenshot captured"));
}
// Close the browser to prevent hanging
await browser.closeBrowser();
console.log(chalk.gray("Browser closed"));
// Exit the process explicitly
process.exit(0);
}
catch (error) {
console.error(chalk.red("โ Error resizing:"), error);
process.exit(1);
}
});
// Console logs command
program
.command("logs")
.description("Get JavaScript console logs from the current page")
.action(async (options) => {
try {
// Detect remote browser (unless explicitly disabled)
await detectRemoteBrowser(false);
const browser = new BrowserSession();
await browser.launchBrowser();
const result = await browser.getConsoleLogs();
console.log(chalk.green("โ
Console logs retrieved successfully"));
console.log(chalk.blue("Current URL:"), result.currentUrl);
if (result.logs && result.logs.trim()) {
const logLines = result.logs.split('\n').filter((line) => line.trim());
console.log(chalk.blue(`Total log entries: ${logLines.length}`));
console.log(chalk.yellow("\n๐ Console Logs:"));
logLines.forEach((line, index) => {
console.log(chalk.gray(`${index + 1}.`), line);
});
}
else {
console.log(chalk.yellow("๐ญ No console logs captured"));
console.log(chalk.gray("This could mean:"));
console.log(chalk.gray("- No JavaScript errors or logs occurred"));
console.log(chalk.gray("- Page is static or has minimal JavaScript"));
console.log(chalk.gray("- Logs were cleared recently"));
}
// Close the browser to prevent hanging
await browser.closeBrowser();
console.log(chalk.gray("Browser closed"));
// Exit the process explicitly
process.exit(0);
}
catch (error) {
console.error(chalk.red("โ Error getting console logs:"), error);
process.exit(1);
}
});
// Close command
program
.command("close")
.description("Close browser")
.action(async (options) => {
try {
const browser = new BrowserSession();
await browser.closeBrowser();
console.log(chalk.green("โ
Browser closed successfully"));
}
catch (error) {
console.error(chalk.red("โ Error closing browser:"), error);
process.exit(1);
}
});
// Interactive mode command
program
.command("interactive")
.description("Start interactive mode")
.action(async (options) => {
console.log(chalk.yellow("๐ Starting interactive browser automation mode"));
console.log(chalk.gray("Type 'help' for available commands"));
console.log(chalk.gray("Type 'exit' to quit"));
const browser = new BrowserSession();
let isLaunched = false;
const rl = createInterface({
input: process.stdin,
output: process.stdout
});
const question = (query) => {
return new Promise((resolve) => {
rl.question(query, resolve);
});
};
while (true) {
try {
const input = await question(chalk.blue("browser> "));
const [command, ...args] = input.trim().split(" ");
switch (command?.toLowerCase()) {
case "help":
console.log(chalk.cyan("Available commands:"));
console.log(" launch <url> - Launch browser and navigate to URL");
console.log(" click <x,y> - Click at coordinates");
console.log(" type <text> - Type text");
console.log(" scroll <up|down> - Scroll page");
console.log(" hover <x,y> - Hover at coordinates");
console.log(" resize <w,h> - Resize browser window");
console.log(" logs - Get JavaScript console logs");
console.log(" close - Close browser");
console.log(" test - Run connection test");
console.log(" exit - Exit interactive mode");
break;
case "test":
console.log(chalk.yellow("๐งช Running connection test..."));
if (!isLaunched) {
await browser.launchBrowser();
isLaunched = true;
}
const testResult = await browser.navigateToUrl("https://example.com");
console.log(chalk.green("โ
Test successful"));
console.log(chalk.blue("Current URL:"), testResult.currentUrl);
break;
case "launch":
if (args.length === 0) {
console.log(chalk.red("โ URL required"));
break;
}
if (!isLaunched) {
await browser.launchBrowser();
isLaunched = true;
}
const result = await browser.navigateToUrl(args[0]);
console.log(chalk.green("โ
Navigated successfully"));
console.log(chalk.blue("Current URL:"), result.currentUrl);
break;
case "click":
if (args.length === 0) {
console.log(chalk.red("โ Coordinates required (x,y format)"));
break;
}
await browser.click(args[0]);
console.log(chalk.green("โ
Clicked successfully"));
break;
case "type":
if (args.length === 0) {
console.log(chalk.red("โ Text required"));
break;
}
await browser.type(args.join(" "));
console.log(chalk.green("โ
Typed successfully"));
break;
case "scroll":
if (args.length === 0 || !["up", "down"].includes(args[0])) {
console.log(chalk.red("โ Direction required (up or down)"));
break;
}
if (args[0] === "down") {
await browser.scrollDown();
}
else {
await browser.scrollUp();
}
console.log(chalk.green(`โ
Scrolled ${args[0]} successfully`));
break;
case "hover":
if (args.length === 0) {
console.log(chalk.red("โ Coordinates required (x,y format)"));
break;
}
await browser.hover(args[0]);
console.log(chalk.green("โ
Hovered successfully"));
break;
case "resize":
if (args.length === 0) {
console.log(chalk.red("โ Size required (width,height format)"));
break;
}
await browser.resize(args[0]);
console.log(chalk.green("โ
Resized successfully"));
break;
case "logs":
const logsResult = await browser.getConsoleLogs();
console.log(chalk.green("โ
Console logs retrieved successfully"));
console.log(chalk.blue("Current URL:"), logsResult.currentUrl);
if (logsResult.logs && logsResult.logs.trim()) {
const logLines = logsResult.logs.split('\n').filter((line) => line.trim());
console.log(chalk.blue(`Total log entries: ${logLines.length}`));
console.log(chalk.yellow("\n๐ Console Logs:"));
logLines.forEach((line, index) => {
console.log(chalk.gray(`${index + 1}.`), line);
});
}
else {
console.log(chalk.yellow("๐ญ No console logs captured"));
console.log(chalk.gray("This could mean:"));
console.log(chalk.gray("- No JavaScript errors or logs occurred"));
console.log(chalk.gray("- Page is static or has minimal JavaScript"));
console.log(chalk.gray("- Logs were cleared recently"));
}
break;
case "close":
await browser.closeBrowser();
isLaunched = false;
console.log(chalk.green("โ
Browser closed"));
break;
case "exit":
if (isLaunched) {
await browser.closeBrowser();
}
rl.close();
console.log(chalk.yellow("๐ Goodbye!"));
process.exit(0);
break;
default:
console.log(chalk.red(`โ Unknown command: ${command}`));
console.log(chalk.gray("Type 'help' for available commands"));
}
}
catch (error) {
console.error(chalk.red("โ Error:"), error);
}
}
});
// Test connection command
program
.command("test-connection")
.description("Test connection to Chrome browser (useful for dev containers)")
.option("-p, --port <port>", "Chrome debugging port", "9222")
.option("-h, --host <host>", "Specific host to test")
.action(async (options = {}) => {
try {
const port = parseInt(options.port || "9222");
const specificHost = options.host;
console.log(chalk.blue("๐ Testing Chrome browser connection..."));
console.log(chalk.gray(`Port: ${port}`));
if (specificHost) {
console.log(chalk.gray(`Testing specific host: ${specificHost}`));
const { tryChromeHostUrl } = await import("./browser/browserDiscovery.js");
const isValid = await tryChromeHostUrl(specificHost);
if (isValid) {
console.log(chalk.green(`โ
Successfully connected to ${specificHost}`));
}
else {
console.log(chalk.red(`โ Failed to connect to ${specificHost}`));
}
return;
}
console.log(chalk.gray("Auto-discovering Chrome instances..."));
const { discoverChromeHostUrl } = await import("./browser/browserDiscovery.js");
const chromeHostUrl = await discoverChromeHostUrl(port);
if (chromeHostUrl) {
console.log(chalk.green(`โ
Auto-discovered and tested connection to Chrome: ${chromeHostUrl}`));
console.log(chalk.blue("You can now use this host with:"));
console.log(chalk.gray(`export REMOTE_BROWSER_HOST="${chromeHostUrl}"`));
console.log(chalk.gray(`export REMOTE_BROWSER_ENABLED=true`));
}
else {
console.log(chalk.red("โ No Chrome instances found with remote debugging enabled"));
console.log(chalk.yellow("\nTo start Chrome with remote debugging:"));
console.log(chalk.gray("chrome --remote-debugging-port=9222"));
console.log(chalk.gray("chromium --remote-debugging-port=9222"));
console.log(chalk.gray("google-chrome --remote-debugging-port=9222"));
}
}
catch (error) {
console.error(chalk.red("โ Error testing connection:"), error);
process.exit(1);
}
});
// Dev containers command
program
.command("dev-containers")
.description("Setup and test browser automation for dev containers")
.option("-p, --port <port>", "Chrome debugging port", "9222")
.option("-h, --host <host>", "Host machine IP address")
.action(async (options = {}) => {
try {
const port = parseInt(options.port || "9222");
const hostIP = options.host;
console.log(chalk.blue("๐ณ Dev Containers Browser Automation Setup"));
console.log(chalk.gray("This will help you connect to Chrome running on your host machine"));
// Test current connection
console.log(chalk.yellow("\n1. Testing current connection..."));
const { discoverChromeHostUrl } = await import("./browser/browserDiscovery.js");
const chromeHostUrl = await discoverChromeHostUrl(port);
if (chromeHostUrl) {
console.log(chalk.green(`โ
Found Chrome at: ${chromeHostUrl}`));
}
else {
console.log(chalk.red("โ No Chrome found"));
if (hostIP) {
console.log(chalk.yellow(`\n2. Testing provided host: ${hostIP}:${port}`));
const { tryChromeHostUrl } = await import("./browser/browserDiscovery.js");
const testUrl = `http://${hostIP}:${port}`;
const isValid = await tryChromeHostUrl(testUrl);
if (isValid) {
console.log(chalk.green(`โ
Successfully connected to ${testUrl}`));
console.log(chalk.blue("\n3. Environment variables to set:"));
console.log(chalk.gray(`export REMOTE_BROWSER_HOST="${testUrl}"`));
console.log(chalk.gray(`export REMOTE_BROWSER_ENABLED=true`));
}
else {
console.log(chalk.red(`โ Failed to connect to ${testUrl}`));
console.log(chalk.yellow("\nTroubleshooting:"));
console.log(chalk.gray("1. Make sure Chrome is running on host with --remote-debugging-port=9222"));
console.log(chalk.gray("2. Check if the host IP is correct"));
console.log(chalk.gray("3. Verify network connectivity between container and host"));
}
}
else {
console.log(chalk.yellow("\n2. To connect to host Chrome:"));
console.log(chalk.gray("a) Start Chrome on host with remote debugging:"));
console.log(chalk.gray(" chrome --remote-debugging-port=9222"));
console.log(chalk.gray("b) Find your host IP address:"));
console.log(chalk.gray(" ip addr show | grep inet"));
console.log(chalk.gray("c) Run this command with your host IP:"));
console.log(chalk.gray(` browser-automation dev-containers --host YOUR_HOST_IP`));
}
}
console.log(chalk.blue("\n4. Usage in dev containers:"));
console.log(chalk.gray("# Set environment variables"));
console.log(chalk.gray("export REMOTE_BROWSER_ENABLED=true"));
console.log(chalk.gray("export REMOTE_BROWSER_HOST=http://HOST_IP:9222"));
console.log(chalk.gray(""));
console.log(chalk.gray("# Test the connection"));
console.log(chalk.gray("browser-automation test-connection"));
console.log(chalk.gray(""));
console.log(chalk.gray("# Use browser automation"));
console.log(chalk.gray("browser-automation launch https://example.com"));
}
catch (error) {
console.error(chalk.red("โ Error in dev containers setup:"), error);
process.exit(1);
}
});
program.parse();
//# sourceMappingURL=cli.js.map