Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Fetch any URL via Chrome CDP and convert the rendered page to clean markdown with YouTube transcript support.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/lib/cli.ts
1#!/usr/bin/env bun23import {4runConvertCommand,5type ConvertCommandOptions,6type OutputFormat,7type WaitMode,8} from "./commands/convert";910export const HELP_TEXT = `11baoyu-fetch - Read a URL into Markdown or JSON with Chrome CDP1213Usage:14baoyu-fetch <url> [options]1516Options:17--output <file> Save output to file18--format <type> Output format: markdown | json19--json Alias for --format json20--adapter <name> Force an adapter (e.g. x, generic)21--download-media Download adapter-reported media into ./imgs and ./videos, then rewrite markdown links22--media-dir <dir> Base directory for downloaded media. Defaults to the output directory23--debug-dir <dir> Write debug artifacts24--cdp-url <url> Reuse an existing Chrome DevTools endpoint25--browser-path <path> Explicit Chrome binary path26--chrome-profile-dir <path>27Chrome user data dir. Defaults to BAOYU_CHROME_PROFILE_DIR28or baoyu-skills/chrome-profile.29--headless Launch a temporary headless Chrome if needed30--wait-for <mode> Wait mode: interaction | force31interaction: start visible Chrome and auto-wait only when login or verification is required32force: start visible Chrome, then auto-continue after it detects login/challenge progress33or continue immediately when you press Enter34--wait-for-interaction35Alias for --wait-for interaction36--wait-for-login Alias for --wait-for interaction37--interaction-timeout <ms>38How long to wait for manual interaction before failing (default: 600000)39--interaction-poll-interval <ms>40How often to poll interaction state while waiting (default: 1500)41--login-timeout <ms> Alias for --interaction-timeout42--login-poll-interval <ms>43Alias for --interaction-poll-interval44--timeout <ms> Page timeout in milliseconds (default: 30000)45--help Show help4647Examples:48baoyu-fetch https://example.com49baoyu-fetch https://example.com --format markdown --output article.md --download-media50baoyu-fetch https://example.com --format json --output article.json51baoyu-fetch https://x.com/lennysan/status/2036483059407810640 --wait-for interaction52baoyu-fetch https://x.com/lennysan/status/2036483059407810640 --wait-for force53`.trim();5455interface CliOptions extends ConvertCommandOptions {56url?: string;57help: boolean;58}5960function normalizeWaitMode(raw: string): WaitMode {61const value = raw.toLowerCase();62if (value === "interaction" || value === "auto") {63return "interaction";64}65if (value === "force" || value === "manual" || value === "always") {66return "force";67}68throw new Error(`Invalid wait mode: ${raw}. Expected interaction or force.`);69}7071function normalizeOutputFormat(raw: string): OutputFormat {72const value = raw.toLowerCase();73if (value === "markdown" || value === "json") {74return value;75}7677throw new Error(`Invalid output format: ${raw}. Expected markdown or json.`);78}7980export function parseArgs(argv: string[]): CliOptions {81const options: CliOptions = {82format: "markdown",83headless: false,84downloadMedia: false,85waitMode: "none",86interactionTimeoutMs: 600_000,87interactionPollIntervalMs: 1_500,88timeoutMs: 30_000,89help: false,90};9192const args = argv.slice(2);93for (let index = 0; index < args.length; index += 1) {94const value = args[index];9596if (value === "--help" || value === "-h") {97options.help = true;98continue;99}100if (value === "--format") {101const format = args[index + 1];102if (!format) {103throw new Error("--format requires a value");104}105options.format = normalizeOutputFormat(format);106index += 1;107continue;108}109if (value === "--json") {110options.format = "json";111continue;112}113if (value === "--download-media") {114options.downloadMedia = true;115continue;116}117if (value === "--headless") {118options.headless = true;119continue;120}121if (value === "--wait-for") {122const mode = args[index + 1];123if (!mode) {124throw new Error("--wait-for requires a mode");125}126options.waitMode = normalizeWaitMode(mode);127index += 1;128continue;129}130if (value === "--wait-for-interaction" || value === "--wait-for-login") {131options.waitMode = "interaction";132continue;133}134if (value === "--output") {135options.output = args[index + 1];136index += 1;137continue;138}139if (value === "--adapter") {140options.adapter = args[index + 1];141index += 1;142continue;143}144if (value === "--debug-dir") {145options.debugDir = args[index + 1];146index += 1;147continue;148}149if (value === "--media-dir") {150options.mediaDir = args[index + 1];151index += 1;152continue;153}154if (value === "--cdp-url") {155options.cdpUrl = args[index + 1];156index += 1;157continue;158}159if (value === "--browser-path") {160options.browserPath = args[index + 1];161index += 1;162continue;163}164if (value === "--chrome-profile-dir") {165options.chromeProfileDir = args[index + 1];166index += 1;167continue;168}169if (value === "--timeout") {170const parsed = Number(args[index + 1]);171if (!Number.isFinite(parsed) || parsed <= 0) {172throw new Error(`Invalid timeout: ${args[index + 1]}`);173}174options.timeoutMs = parsed;175index += 1;176continue;177}178if (value === "--interaction-timeout" || value === "--login-timeout") {179const parsed = Number(args[index + 1]);180if (!Number.isFinite(parsed) || parsed <= 0) {181throw new Error(`Invalid interaction timeout: ${args[index + 1]}`);182}183options.interactionTimeoutMs = parsed;184index += 1;185continue;186}187if (value === "--interaction-poll-interval" || value === "--login-poll-interval") {188const parsed = Number(args[index + 1]);189if (!Number.isFinite(parsed) || parsed <= 0) {190throw new Error(`Invalid interaction poll interval: ${args[index + 1]}`);191}192options.interactionPollIntervalMs = parsed;193index += 1;194continue;195}196if (value.startsWith("-")) {197throw new Error(`Unknown option: ${value}`);198}199if (!options.url) {200options.url = value;201continue;202}203throw new Error(`Unexpected argument: ${value}`);204}205206return options;207}208209async function main(): Promise<void> {210try {211const options = parseArgs(process.argv);212if (options.help || !options.url) {213console.log(HELP_TEXT);214return;215}216217await runConvertCommand(options);218} catch (error) {219const message = error instanceof Error ? error.message : String(error);220console.error(message);221process.exitCode = 1;222}223}224225if (import.meta.main) {226void main();227}228