b843eea9ae
- Add auto-daemon.ts: ensureDaemonRunning() checks health, reads pidfile, spawns daemon if needed, polls health up to 3s - Rewrite index.ts: commander-based CLI with all subcommands (health, open, list, get, kill, navigate, click, type, scroll, eval, wait, screenshot, text, html, profiles) - Add daemon project reference to cli tsconfig
167 lines
5.0 KiB
JavaScript
167 lines
5.0 KiB
JavaScript
#!/usr/bin/env node
|
|
import { Command } from 'commander';
|
|
import { ensureDaemonRunning } from './auto-daemon.js';
|
|
import { safeStringify, VisionLClient } from '@visionl/core';
|
|
|
|
const DEFAULT_PORT = 9527;
|
|
|
|
const program = new Command();
|
|
program.name('visionl').version('0.1.0');
|
|
|
|
program.option('-p, --port <n>', 'daemon port', String(DEFAULT_PORT));
|
|
program.option('--pretty', 'human-readable output');
|
|
|
|
function getClient(): Promise<VisionLClient> {
|
|
const opts = program.opts<{ port: string }>();
|
|
const port = parseInt(opts.port, 10) || DEFAULT_PORT;
|
|
return ensureDaemonRunning(port);
|
|
}
|
|
|
|
function print(result: unknown): void {
|
|
const opts = program.opts<{ pretty: boolean }>();
|
|
if (opts.pretty) {
|
|
console.log(JSON.stringify(result, null, 2));
|
|
} else {
|
|
console.log(safeStringify(result));
|
|
}
|
|
}
|
|
|
|
// health — check daemon health
|
|
program.command('health')
|
|
.description('Check daemon health')
|
|
.action(async () => {
|
|
const client = await getClient();
|
|
const ok = await client.health();
|
|
print({ ok });
|
|
});
|
|
|
|
// pages — page management
|
|
program.command('open <url>')
|
|
.description('Open a URL in a new page')
|
|
.option('-a, --alias <alias>', 'Page alias')
|
|
.option('-p, --profile <profile>', 'Browser profile')
|
|
.action(async (url: string, options: { alias?: string; profile?: string }) => {
|
|
const client = await getClient();
|
|
const result = await client.openPage(url, options.alias, options.profile);
|
|
print(result);
|
|
});
|
|
|
|
program.command('list')
|
|
.description('List all open pages')
|
|
.action(async () => {
|
|
const client = await getClient();
|
|
const result = await client.listPages();
|
|
print(result);
|
|
});
|
|
|
|
program.command('get <id>')
|
|
.description('Get page info by ID or alias')
|
|
.action(async (id: string) => {
|
|
const client = await getClient();
|
|
const result = await client.getPage(id);
|
|
print(result);
|
|
});
|
|
|
|
program.command('kill <id>')
|
|
.description('Close a page by ID or alias')
|
|
.action(async (id: string) => {
|
|
const client = await getClient();
|
|
const result = await client.killPage(id);
|
|
print(result);
|
|
});
|
|
|
|
// navigation — page interactions
|
|
program.command('navigate <id> <url>')
|
|
.description('Navigate an existing page to a URL')
|
|
.action(async (id: string, url: string) => {
|
|
const client = await getClient();
|
|
const result = await client.navigate(id, url);
|
|
print(result);
|
|
});
|
|
|
|
program.command('click <id> <selector>')
|
|
.description('Click an element on a page')
|
|
.action(async (id: string, selector: string) => {
|
|
const client = await getClient();
|
|
const result = await client.click(id, selector);
|
|
print(result);
|
|
});
|
|
|
|
program.command('type <id> <selector> <text>')
|
|
.description('Type text into an element')
|
|
.action(async (id: string, selector: string, text: string) => {
|
|
const client = await getClient();
|
|
const result = await client.type(id, selector, text);
|
|
print(result);
|
|
});
|
|
|
|
program.command('scroll <id>')
|
|
.description('Scroll on a page')
|
|
.option('-y, --delta-y <n>', 'Vertical scroll delta', '0')
|
|
.option('-b, --to-bottom', 'Scroll to bottom')
|
|
.action(async (id: string, options: { deltaY: string; toBottom?: boolean }) => {
|
|
const client = await getClient();
|
|
const result = await client.scroll(id, {
|
|
deltaY: parseInt(options.deltaY, 10) || undefined,
|
|
toBottom: options.toBottom,
|
|
});
|
|
print(result);
|
|
});
|
|
|
|
program.command('eval <id> <code>')
|
|
.description('Evaluate JavaScript on a page')
|
|
.action(async (id: string, code: string) => {
|
|
const client = await getClient();
|
|
const result = await client.eval(id, code);
|
|
print(result);
|
|
});
|
|
|
|
program.command('wait <id>')
|
|
.description('Wait for selector or duration on a page')
|
|
.option('-s, --selector <selector>', 'Wait for CSS selector')
|
|
.option('-m, --ms <ms>', 'Wait time in milliseconds')
|
|
.action(async (id: string, options: { selector?: string; ms?: string }) => {
|
|
const client = await getClient();
|
|
const result = await client.wait(id, {
|
|
selector: options.selector,
|
|
ms: options.ms ? parseInt(options.ms, 10) : undefined,
|
|
});
|
|
print(result);
|
|
});
|
|
|
|
// content — page content retrieval
|
|
program.command('screenshot <id>')
|
|
.description('Take a screenshot of a page (returns base64)')
|
|
.action(async (id: string) => {
|
|
const client = await getClient();
|
|
const result = await client.screenshot(id);
|
|
print(result);
|
|
});
|
|
|
|
program.command('text <id>')
|
|
.description('Get the text content of a page')
|
|
.action(async (id: string) => {
|
|
const client = await getClient();
|
|
const result = await client.text(id);
|
|
print(result);
|
|
});
|
|
|
|
program.command('html <id>')
|
|
.description('Get the HTML source of a page')
|
|
.action(async (id: string) => {
|
|
const client = await getClient();
|
|
const result = await client.html(id);
|
|
print(result);
|
|
});
|
|
|
|
// profiles
|
|
program.command('profiles')
|
|
.description('List available fingerprint profiles')
|
|
.action(async () => {
|
|
const client = await getClient();
|
|
const result = await client.getProfiles();
|
|
print(result);
|
|
});
|
|
|
|
program.parse();
|