Merge pull request #2458 from lightpanda-io/nikneym/cli-help-rework

`help`: rework `help` command
This commit is contained in:
Pierre Tachoire
2026-05-18 11:54:29 +02:00
committed by GitHub
4 changed files with 387 additions and 326 deletions

View File

@@ -203,7 +203,6 @@ const Commands = cli.Builder(.{
.shared_options = CommonOptions,
},
.{ .name = "version", .options = .{} },
.{ .name = "help", .positional = .{ .name = "subcommand", .type = ?[]const u8 }, .options = .{} },
});
pub const RunMode = Commands.Enum;
@@ -531,292 +530,51 @@ pub const HttpHeaders = struct {
}
};
pub fn printUsageAndExit(self: *const Config, success: bool) void {
// MAX_HELP_LEN|
const common_options =
\\
\\--insecure-disable-tls-host-verification
\\ Disables host verification on all HTTP requests. This is an
\\ advanced option which should only be set if you understand
\\ and accept the risk of disabling host verification.
\\
\\--obey-robots
\\ Fetches and obeys the robots.txt (if available) of the web pages
\\ we make requests towards.
\\ Defaults to false.
\\
\\--disable-subframes
\\ Skip loading <iframe> elements. The HTML parser registers them
\\ in the DOM but no child frame, document fetch, or
\\ Page.frameAttached / Runtime.executionContextCreated events are
\\ produced. Useful for pages that load many analytics / pixel
\\ iframes where each subframe navigation invalidates driver-side
\\ executionContextIds (lightpanda-io/browser#2400). On the CDP
\\ serve path, drivers can also toggle this per-session via the
\\ LP.configureLoading method.
\\ Defaults to false.
\\
\\--disable-workers
\\ Skip loading dedicated Web Workers. The Worker constructor
\\ still returns a Worker object so calling pages do not throw,
\\ but no script fetch is initiated and the worker scope's
\\ eval never runs (postMessage from the page to the worker is
\\ queued indefinitely). Sidesteps a v8 entered-context
\\ corruption that crashes the process when an in-page Worker
\\ completes its script fetch under specific HTTP-proxy timing
\\ conditions on Shopify storefront pages. Drivers can also
\\ toggle this per-session via the LP.configureLoading method.
\\ Defaults to false.
\\
\\--block-private-networks
\\ Blocks HTTP requests to private/internal IP addresses
\\ after DNS resolution. Useful for sandboxing, multi-tenant
\\ deployments, and preventing access to internal infrastructure
\\ regardless of what triggers the request (JavaScript, HTML
\\ resources, redirects, etc.).
\\ Defaults to false.
\\
\\--block-cidrs
\\ Additional CIDR ranges to block, comma-separated.
\\ Prefix with '-' to allow (exempt from blocking).
\\ e.g. --block-cidrs 169.254.169.254/32,fd00:ec2::254/128
\\ e.g. --block-cidrs 10.0.0.0/8,-10.0.0.42/32
\\ Can be used standalone or combined with --block-private-networks.
\\
\\--http-proxy The HTTP proxy to use for all HTTP requests.
\\ A username:password can be included for basic authentication.
\\ Defaults to none.
\\
\\--proxy-bearer-token
\\ The <token> to send for bearer authentication with the proxy
\\ Proxy-Authorization: Bearer <token>
\\
\\--http-max-concurrent
\\ The maximum number of concurrent HTTP requests.
\\ Defaults to 10.
\\
\\--http-max-host-open
\\ The maximum number of open connection to a given host:port.
\\ Defaults to 4.
\\
\\--http-connect-timeout
\\ The time, in milliseconds, for establishing an HTTP connection
\\ before timing out. 0 means it never times out.
\\ Defaults to 0.
\\
\\--http-timeout
\\ The maximum time, in milliseconds, the transfer is allowed
\\ to complete. 0 means it never times out.
\\ Defaults to 10000.
\\
\\--http-max-response-size
\\ Limits the acceptable response size for any request
\\ (e.g. XHR, fetch, script loading, ...).
\\ Defaults to no limit.
\\
\\--ws-max-concurrent
\\ The maximum number of concurrent WebSocket connections.
\\ Defaults to 8.
\\
\\--log-level The log level: debug, info, warn, error or fatal.
\\ Defaults to
++ (if (builtin.mode == .Debug) " info." else "warn.") ++
\\
\\
\\--log-format The log format: pretty or logfmt.
\\ Defaults to
++ (if (builtin.mode == .Debug) " pretty." else " logfmt.") ++
\\
\\
\\--log-filter-scopes
\\ Filter out too verbose logs per scope:
\\ http, unknown_prop, event, ...
\\
\\--user-agent Override the User-Agent header entirely
\\ User-Agent mustn't impersonate other browser.
\\ Any value containing "Mozilla" is forbidden.
\\ The browser will continue to send Sec-Ch-Ua header.
\\ Incompatible with --user-agent-suffix
\\
\\--user-agent-suffix
\\ Suffix to append to the Lightpanda/X.Y User-Agent
\\
\\--web-bot-auth-key-file
\\ Path to the Ed25519 private key PEM file.
\\
\\--web-bot-auth-keyid
\\ The JWK thumbprint of your public key.
\\
\\--web-bot-auth-domain
\\ Your domain e.g. yourdomain.com
\\
\\--http-cache-dir
\\ Path to a directory to use as a Filesystem Cache for network resources.
\\ Omitting this will result is no caching.
\\ Defaults to no caching.
\\
\\--storage-engine
\\ The storage engine to use. Choices are: none, sqlite.
\\ Default to none.
\\
\\--storage-sqlite-path
\\ Path to SQLite database file for persistent storage.
\\ Use ":memory:" for in-memory storage.
;
pub fn printUsageAndExit(self: *const Config, help_for: RunMode, success: bool) void {
const exec_name = self.exec_name;
const Help = @import("help.zon");
const is_debug = builtin.mode == .Debug;
const info_or_warn = if (comptime is_debug) "info" else "warn";
const pretty_or_logfmt = if (comptime is_debug) "pretty" else "logfmt";
const comptimePrint = std.fmt.comptimePrint;
// MAX_HELP_LEN|
const fetch_options =
\\fetch command
\\Fetches the specified URL
\\Example: {0s} fetch --dump html https://lightpanda.io/
\\
\\Options:
\\--dump Dumps document to stdout.
\\ Argument must be 'html', 'markdown', 'semantic_tree', or 'semantic_tree_text'.
\\ Defaults to no dump.
\\
\\--strip-mode Comma separated list of tag groups to remove from dump
\\ the dump. e.g. --strip-mode js,css
\\ - "js" script and link[as=script, rel=preload]
\\ - "ui" includes img, picture, video, css and svg
\\ - "css" includes style and link[rel=stylesheet]
\\ - "full" includes js, ui and css
\\
\\--with-base Add a <base> tag in dump. Defaults to false.
\\
\\--with-frames Includes the contents of iframes. Defaults to false.
\\
\\--wait-ms Wait time in milliseconds. Supersedes all other --wait
\\ parameters.
\\ Defaults to 5000.
\\
\\--wait-until Wait until the specified event. Checked before the other
\\ --wait- options. Supported events: load, domcontentloaded,
\\ networkidle, done.
\\ Defaults to 'done'. If --wait-selector, --wait-script or
\\ --wait-script-file are specified, defaults to none.
\\
\\--wait-selector Wait for an element matching the CSS selector to appear.
\\ Checked after --wait-until condition is met.
\\
\\--wait-script Wait for a JavaScript expression to return truthy.
\\ Checked after --wait-until condition is met.
\\
\\--wait-script-file
\\ Like --wait-script, but reads the script from a file.
\\
\\--inject-script JavaScript to execute as the document's <head> is
\\ parsed, before any other scripts in the page run.
\\ Can be passed multiple times; scripts run in order.
\\
\\--inject-script-file
\\ Like --inject-script, but reads the script from a file.
\\ Can be passed multiple times; can be mixed with
\\ --inject-script and runs in CLI order.
\\
\\--terminate-ms Hard deadline in milliseconds. After this time elapses,
\\ JavaScript execution is forcibly terminated (e.g. for
\\ pages with endless scripts). Unlike --wait-ms, which
\\ only stops waiting, --terminate-ms aborts the page.
\\ Defaults to no terminate.
\\
\\--cookie Path to a JSON file to load cookies from (read-only).
\\ Defaults to no cookie loading.
\\
\\--cookie-jar Path to a JSON file to save cookies to on exit (write-only).
\\ Defaults to no cookie saving.
\\
++ common_options;
// MAX_HELP_LEN|
const serve_options =
\\serve command
\\Starts a websocket CDP server
\\Example: {0s} serve --host 127.0.0.1 --port 9222
\\
\\Options:
\\--host Host of the CDP server
\\ Defaults to "127.0.0.1"
\\
\\--port Port of the CDP server
\\ Defaults to 9222
\\
\\--advertise-host
\\ The host to advertise, e.g. in the /json/version response.
\\ Useful, for example, when --host is 0.0.0.0.
\\ Defaults to --host value
\\
\\--cdp-max-connections
\\ Maximum number of simultaneous CDP connections.
\\ Defaults to 16.
\\
\\--cdp-max-pending-connections
\\ Maximum pending connections in the accept queue.
\\ Defaults to 128.
\\
\\--cookie Path to a JSON file to load cookies from (read-only).
\\ Defaults to no cookie loading.
\\
++ common_options;
// MAX_HELP_LEN|
const mcp_options =
\\mcp command
\\Starts an MCP (Model Context Protocol) server over stdio
\\Example: {0s} mcp
\\
\\Options:
\\--cookie Path to a JSON file to load cookies from (read-only).
\\ Defaults to no cookie loading.
\\
\\--cookie-jar Path to a JSON file to save cookies to on exit (write-only).
\\ Defaults to no cookie saving.
\\
++ common_options;
// MAX_HELP_LEN|
const usage =
\\usage: {0s} command [options] [URL]
\\
\\Command can be either 'fetch', 'serve', 'mcp' or 'help'
\\
++ fetch_options ++
\\
\\
++ serve_options ++
\\
\\
++ mcp_options ++
\\
\\
\\version command
\\Displays the version of {0s}
\\
\\help command
\\Displays this message
\\
;
// When called with a subcommand argument,
// print only the relevant subcommand section instead of the full help.
switch (self.mode) {
.help => |h| if (h.subcommand) |sub| {
if (std.mem.eql(u8, sub, "fetch")) {
std.debug.print(fetch_options ++ "\n", .{self.exec_name});
} else if (std.mem.eql(u8, sub, "serve")) {
std.debug.print(serve_options ++ "\n", .{self.exec_name});
} else if (std.mem.eql(u8, sub, "mcp")) {
std.debug.print(mcp_options ++ "\n", .{self.exec_name});
} else {
std.debug.print(usage, .{self.exec_name});
}
if (success) return std.process.cleanExit();
std.process.exit(1);
switch (help_for) {
// Requested help for everything.
.help => {
const template = comptimePrint(
\\
\\Command can be either "fetch", "serve", "mcp" or "help".
\\
\\{s}
\\
\\{s}
\\
\\{s}
\\
\\{s}
\\
\\{s}
\\
\\{s}
\\
, .{ Help.fetch, Help.serve, Help.mcp, Help.common, Help.version, Help.help });
std.debug.print(template, .{ exec_name, info_or_warn, pretty_or_logfmt });
},
inline .fetch, .serve, .mcp => |tag| {
const template = comptimePrint(
\\{s}
\\
\\{s}
\\
, .{ @field(Help, @tagName(tag)), Help.common });
std.debug.print(template, .{ exec_name, info_or_warn, pretty_or_logfmt });
},
.version => {
const template = Help.version ++ "\n";
std.debug.print(template, .{exec_name});
},
else => {},
}
std.debug.print(usage, .{self.exec_name});
if (success) {
return std.process.cleanExit();
}

View File

@@ -23,7 +23,24 @@ const log = lp.log;
/// Comptime CLI builder that generates a tagged union parser from a
/// declarative command recipe. Each command becomes a union variant whose
/// payload is a struct with one field per option.
/// payload is a struct with one field per option. A `help` variant is added
/// automatically; do not include it in the recipe.
///
/// ## Parsing behavior
///
/// `parse` reads `std.process.args`, picks a command by the first non-exec
/// argument, then walks the rest as `--flag value` pairs. Quirks:
///
/// - When no command is given, the parser defaults to `serve`.
/// - `help`, `help <command>`, `<command> help`, and `<command> --help` all
/// yield the `help` union variant. When a command is named (in either
/// position), the variant carries that command's enum tag so callers can
/// print command-specific help; bare `help` and `help help` carry the
/// `.help` tag. An unknown name after `help` returns
/// `error.UnknownCommand`.
/// - Legacy fallback: if the first argument starts with `--` and matches a
/// known fetch/serve flag, the parser sniffs the command from it and
/// re-parses argv. Only exists for backwards compatibility.
///
/// ## Command descriptor fields
///
@@ -34,8 +51,9 @@ const log = lp.log;
/// command. Useful for common flags shared across commands.
/// - `positional: struct` (optional) — a single positional argument with
/// `.name` and `.type`. Type must be an optional pointer-to-u8 slice
/// (e.g. `?[:0]const u8`). Positionals can appear anywhere in argv and
/// must be provided; a missing positional returns `error.MissingArgument`.
/// (e.g. `?[:0]const u8`); it defaults to `null` and may appear anywhere
/// in argv. Passing it more than once returns
/// `error.TooManyPositionalArguments`.
///
/// ## Option descriptor fields
///
@@ -49,6 +67,8 @@ const log = lp.log;
/// `bool` or packed-struct options.
/// - `validator: fn` (optional) — custom parse function that replaces the
/// built-in type switch. See the validator section below.
/// - `variants: tuple` (optional) — alternate flag names that write into
/// the same field. See the variants section below.
///
/// ## Supported types and their defaults
///
@@ -60,10 +80,10 @@ const log = lp.log;
/// - `[]const u8`, `[:0]const u8` (and mutable variants) — string slices
/// duped from argv. Sentinel is preserved. Requires `default` unless `?`.
/// - Enums — parsed via `std.meta.stringToEnum`. Returns
/// `error.UnknownArgument` on a bad value. Requires `default` unless `?`.
/// `error.InvalidArgument` on a bad value. Requires `default` unless `?`.
/// - Packed structs of `bool` fields — parsed from a comma-separated list
/// (e.g. `--strip-mode js,css`). The literal `"full"` sets every field.
/// Unknown names return `error.UnknownArgument`. Requires `default`.
/// Unknown names return `error.InvalidArgument`. Requires `default`.
/// `multiple` is not supported.
/// - Optional types default to `null` when `default` is omitted.
///
@@ -80,6 +100,15 @@ const log = lp.log;
/// When a validator is present, the built-in type switch is skipped entirely.
/// The validator owns advancing the iterator and is free to peek ahead.
///
/// ## Variants
///
/// A `variants` tuple lets multiple flag names write into the same field
/// using different parse logic. Each variant has its own `.name` and an
/// optional `.validator` (with the same signatures as above); the option's
/// `type` and `multiple` are inherited. Useful for "value or file" pairs:
/// e.g. `--wait-script "code"` vs `--wait-script-file path/to/script.js`,
/// both populating the same `wait_script` field.
///
/// ## Example
///
/// ```zig
@@ -113,19 +142,25 @@ const log = lp.log;
/// .{ .name = "strip_mode", .type = StripMode, .default = .{} },
/// .{ .name = "wait_until", .type = ?WaitUntil },
/// .{ .name = "extra_header", .type = []const u8, .multiple = true },
/// .{
/// .name = "wait_script",
/// .type = ?[:0]const u8,
/// .variants = .{
/// .{ .name = "wait_script_file", .validator = readScriptFile },
/// },
/// },
/// },
/// .shared_options = CommonOptions,
/// },
/// .{ .name = "version", .options = .{} },
/// .{ .name = "help", .options = .{} },
/// });
///
/// const _, const cmd = try Cli.parse(arena);
/// switch (cmd) {
/// .serve => |opts| listen(opts.host, opts.port),
/// .fetch => |opts| fetch(opts.url.?, opts.dump),
/// .fetch => |opts| fetch(opts.url orelse return error.UrlRequired, opts.dump),
/// .version => printVersion(),
/// .help => printHelp(),
/// .help => |tag| printHelp(tag),
/// }
/// ```
pub fn Builder(comptime commands: anytype) type {
@@ -134,17 +169,24 @@ pub fn Builder(comptime commands: anytype) type {
/// Enum type for provided commands.
pub const Enum = blk: {
var enum_fields: [commands.len]std.builtin.Type.EnumField = undefined;
for (commands, 0..) |command, i| {
const len = commands.len + 1;
var enum_fields: [len]std.builtin.Type.EnumField = undefined;
var i: usize = 0;
while (i < commands.len) : (i += 1) {
const command = commands[i];
enum_fields[i] = .{ .name = command.name, .value = i };
}
// Entry for help.
enum_fields[i] = .{ .name = "help", .value = i };
break :blk @Type(.{
.@"enum" = .{
.decls = &.{},
.fields = &enum_fields,
.is_exhaustive = true,
.tag_type = std.math.IntFittingRange(0, commands.len),
.tag_type = std.math.IntFittingRange(0, len),
},
});
};
@@ -212,8 +254,12 @@ pub fn Builder(comptime commands: anytype) type {
/// Union type for provided commands.
pub const Union = blk: {
var union_fields: [commands.len]std.builtin.Type.UnionField = undefined;
for (commands, 0..) |command, i| {
const len = commands.len + 1;
var union_fields: [len]std.builtin.Type.UnionField = undefined;
var i: usize = 0;
while (i < commands.len) : (i += 1) {
const command = commands[i];
const Command = @TypeOf(command);
const options = command.options;
@@ -247,6 +293,10 @@ pub fn Builder(comptime commands: anytype) type {
union_fields[i] = .{ .name = command.name, .type = T, .alignment = @alignOf(T) };
}
// Entry for help; just takes `Enum` itself.
const Help = Enum;
union_fields[i] = .{ .name = "help", .type = Help, .alignment = @alignOf(Help) };
break :blk @Type(.{
.@"union" = .{
.decls = &.{},
@@ -268,27 +318,43 @@ pub fn Builder(comptime commands: anytype) type {
inline for (commands) |command| {
// Match a command.
if (std.mem.eql(u8, cmd_str, command.name)) {
const cmd_parsed = parseCommand(allocator, command, &args) catch |err| {
if (err == error.HelpRequested) {
// <subcommand> help requested, return help <subcommand>
var h = @FieldType(Union, "help"){};
if (@hasField(@FieldType(Union, "help"), "subcommand")) {
h.subcommand = command.name;
}
return .{ exec_name, @unionInit(Union, "help", h) };
} else return err;
};
const cmd_parsed = try parseCommand(allocator, command, &args);
return .{ exec_name, cmd_parsed };
}
}
// Help is not in `commands`; so, we have to special case it.
if (std.mem.eql(u8, cmd_str, "help")) {
// Check if we're followed by a command name.
const command_name: []const u8 = args.next() orelse {
// "lightpanda help"; short-circuit.
return .{ exec_name, @unionInit(Union, "help", .help) };
};
inline for (commands) |command| {
if (std.mem.eql(u8, command_name, command.name)) {
return .{
exec_name,
@unionInit(Union, "help", std.meta.stringToEnum(Enum, command.name).?),
};
}
}
// Treat `help help` as the full help.
if (std.mem.eql(u8, command_name, "help")) {
return .{ exec_name, @unionInit(Union, "help", .help) };
}
log.fatal(.app, "unknown command", .{ .arg = command_name });
return error.UnknownCommand;
}
// Last resort, try sniffing.
const command_enum = try sniffCommand(cmd_str);
// `help` takes no arguments; short-circuit so the sniffed flag
// isn't re-parsed as an unknown option.
// Legacy `--help` situation.
if (command_enum == .help) {
return .{ exec_name, .{ .help = .{} } };
return .{ exec_name, @unionInit(Union, "help", .help) };
}
// "cmd_str" wasn't a command but an option. We can't reset args, but
@@ -301,16 +367,7 @@ pub fn Builder(comptime commands: anytype) type {
inline for (commands) |command| {
if (std.mem.eql(u8, @tagName(command_enum), command.name)) {
const cmd_parsed = parseCommand(allocator, command, &args) catch |err| {
if (err == error.HelpRequested) {
// <subcommand> help requested, return help <subcommand>
var h = @FieldType(Union, "help"){};
if (@hasField(@FieldType(Union, "help"), "subcommand")) {
h.subcommand = command.name;
}
return .{ exec_name, @unionInit(Union, "help", h) };
} else return err;
};
const cmd_parsed = try parseCommand(allocator, command, &args);
return .{ exec_name, cmd_parsed };
}
}
@@ -607,9 +664,9 @@ pub fn Builder(comptime commands: anytype) type {
}
}
// Subcommand help: `lightpanda fetch help` or `lightpanda fetch --help`
// Subcommand help: `lightpanda fetch help` or `lightpanda fetch --help`.
if (std.mem.eql(u8, option_name, "help") or std.mem.eql(u8, option_name, "--help")) {
return error.HelpRequested;
return @unionInit(Union, "help", std.meta.stringToEnum(Enum, command.name).?);
}
// Encountered an option we don't know of.

249
src/help.zon Normal file
View File

@@ -0,0 +1,249 @@
.{
// MAX_HELP_LEN|
.serve =
\\serve command
\\Starts a WebSocket CDP server.
\\
\\Usage:
\\ {0s} serve [OPTIONS] [COMMON_OPTIONS]
\\
\\Options:
\\--host <HOST> Host of the CDP server.
\\ Defaults to "127.0.0.1".
\\
\\--port <INT> Port of the CDP server.
\\ Defaults to 9222.
\\
\\--advertise-host <HOST>
\\ The host to advertise, e.g. in the /json/version
\\ response. Useful, for example, when --host is 0.0.0.0.
\\ Defaults to --host value.
\\
\\--cdp-max-connections <INT>
\\ Maximum number of simultaneous CDP connections.
\\ Defaults to 16.
\\
\\--cdp-max-pending-connections <INT>
\\ Maximum pending connections in the accept queue.
\\ Defaults to 128.
\\
\\--cookie <PATH> Path to a JSON file to load cookies from (read-only).
\\ Defaults to no cookie loading.
,
.fetch =
\\fetch command
\\Fetches the specified URL.
\\
\\Usage:
\\ {0s} fetch <url> [OPTIONS] [COMMON_OPTIONS]
\\
\\Options:
\\--dump <DUMP> Dumps the document to stdout.
\\ Defaults to no dump.
\\
\\ Allowed values:
\\ html Serialized HTML of the DOM.
\\ markdown Converts content to Markdown.
\\ semantic_tree JSON-serialized semantic tree.
\\ semantic_tree_text Pruned plain-text semantic tree.
\\
\\--strip-mode <STRIP> Comma-separated list of tag groups to remove from dump.
\\ Defaults to no-strip.
\\
\\ Allowed values:
\\ js script and link[as=script, rel=preload].
\\ ui Includes img, picture, video, CSS and SVG.
\\ css Includes style and link[rel=stylesheet].
\\ full Strip everything.
\\
\\--with-base Add a <base> tag in dump.
\\ Defaults to false.
\\
\\--with-frames Includes the contents of iframes.
\\ Defaults to false.
\\
\\--wait-ms <INT> Wait time in milliseconds. Supersedes all other --wait
\\ parameters.
\\ Defaults to 5000.
\\
\\--wait-until <UNTIL> Wait until the specified event. Checked before other
\\ --wait-* options.
\\ Defaults to 'done'. If --wait-selector, --wait-script
\\ or --wait-script-file specified, defaults to none.
\\
\\ Allowed values:
\\ "load", "domcontentloaded", "networkidle", "done".
\\
\\--wait-selector <QUERY> Wait for an element matching the CSS selector to
\\ appear. Checked after --wait-until condition is met.
\\
\\--wait-script <EXPR> Wait for a JavaScript expression to return truthy.
\\ Checked after --wait-until condition is met.
\\
\\--wait-script-file <PATH> Like --wait-script, but reads the script from a file.
\\
\\--inject-script <EXPR> JavaScript to execute as the document's <head> is
\\ parsed, before any other scripts in the page run.
\\ Can be passed multiple times; scripts run in order.
\\
\\--inject-script-file <PATH>
\\ Like --inject-script, but reads the script from a file.
\\ Can be passed multiple times; can be mixed with
\\ --inject-script and runs in CLI order.
\\
\\--terminate-ms <INT> Hard deadline in milliseconds. After this time elapses,
\\ JavaScript execution is forcibly terminated (e.g. for
\\ pages with endless scripts). Unlike --wait-ms, which
\\ only stops waiting, --terminate-ms aborts the page.
\\ Defaults to no terminate.
\\
\\--cookie <PATH> Path to a JSON file to load cookies from (read-only).
\\ Defaults to no cookie loading.
\\
\\--cookie-jar <PATH> Path to a JSON file to save cookies to on exit.
\\ (write-only).
\\ Defaults to no cookie saving.
,
.mcp =
\\mcp command
\\Starts an MCP (Model Context Protocol) server over stdio.
\\
\\Usage:
\\ {0s} mcp [OPTIONS] [COMMON_OPTIONS]
\\
\\Options:
\\--cookie <PATH> Path to a JSON file to load cookies from (read-only).
\\ Defaults to no cookie loading.
\\
\\--cookie-jar <PATH> Path to a JSON file to save cookies to on exit.
\\ (write-only).
\\ Defaults to no cookie saving.
,
.version =
\\version command
\\Displays the version of {0s}.
\\
\\Usage:
\\ {0s} version
,
.help =
\\help command
\\Displays this message.
\\
\\Usage:
\\ {0s} help
,
.common =
\\Common Options:
\\--insecure-disable-tls-host-verification
\\ Disables host verification on all HTTP requests.
\\ Only set this if you understand and accept the risk.
\\
\\--obey-robots Fetches and obeys robots.txt of the target page.
\\ Defaults to false.
\\
\\--disable-subframes Skip loading <iframe> elements. The parser still
\\ registers them in the DOM, but no child frame or
\\ Page.frameAttached events are produced.
\\ Defaults to false.
\\
\\--disable-workers Skip loading dedicated Web Workers. The Worker
\\ constructor still returns a Worker object, but no
\\ script fetch is initiated and its scope never runs.
\\ Defaults to false.
\\
\\--block-private-networks Block HTTP requests to private/internal IP
\\ addresses after DNS resolution.
\\ Defaults to false.
\\
\\--block-cidrs <LIST> Additional CIDR ranges to block, comma-separated.
\\ Prefix with '-' to allow (exempt from blocking).
\\ e.g. --block-cidrs 10.0.0.0/8,-10.0.0.42/32
\\ Can be combined with --block-private-networks.
\\
\\--http-proxy <URL> HTTP proxy for all HTTP requests.
\\ username:password may be included for basic auth.
\\ Defaults to none.
\\
\\--proxy-bearer-token <TOKEN>
\\ Token sent for bearer authentication with the
\\ proxy: Proxy-Authorization: Bearer <token>.
\\
\\--http-max-concurrent <INT>
\\ Maximum number of concurrent HTTP requests.
\\ Defaults to 10.
\\
\\--http-max-host-open <INT> Maximum open connections to a given host:port.
\\ Defaults to 4.
\\
\\--http-connect-timeout <INT>
\\ Time in ms to establish an HTTP connection before
\\ timing out. 0 means never.
\\ Defaults to 0.
\\
\\--http-timeout <INT> Maximum time in ms the transfer is allowed to
\\ complete. 0 means never.
\\ Defaults to 10000.
\\
\\--http-max-response-size <INT>
\\ Limits the acceptable response size for any
\\ request (e.g. XHR, fetch, script loading).
\\ Defaults to no limit.
\\
\\--ws-max-concurrent <INT> Maximum number of concurrent WebSocket connections.
\\ Defaults to 8.
\\
\\--log-level <LEVEL> The log level.
\\ Defaults to {1s}.
\\
\\ Allowed values:
\\ "debug", "info", "warn", "error", "fatal".
\\
\\--log-format <FORMAT> The log format.
\\ Defaults to {2s}.
\\
\\ Allowed values: "pretty", "logfmt".
\\
\\--log-filter-scopes <SCOPES>
\\ Filter out too-verbose logs per scope,
\\ comma-separated. e.g. http, unknown_prop, event.
\\
\\--user-agent <STRING> Override the User-Agent header entirely.
\\ Must not impersonate other browsers; any value
\\ containing "Mozilla" is forbidden. The browser
\\ still sends Sec-Ch-Ua. Incompatible with
\\ --user-agent-suffix.
\\
\\--user-agent-suffix <STRING>
\\ Suffix appended to the Lightpanda/X.Y User-Agent.
\\
\\--web-bot-auth-key-file <PATH>
\\ Path to the Ed25519 private key PEM file.
\\
\\--web-bot-auth-keyid <STRING>
\\ The JWK thumbprint of your public key.
\\
\\--web-bot-auth-domain <DOMAIN>
\\ Your domain, e.g. yourdomain.com.
\\
\\--http-cache-dir <PATH> Directory used as a filesystem cache for network
\\ resources. Omitting this disables caching.
\\ Defaults to no caching.
\\
\\--cookie <PATH> Path to a JSON file to load cookies from (read-only).
\\ Defaults to no cookie loading.
\\
\\--cookie-jar <PATH> Path to a JSON file to save cookies to on exit
\\ (write-only).
\\ Defaults to no cookie saving.
\\
\\--storage-engine <ENGINE> The storage engine to use.
\\ Defaults to none.
\\
\\ Allowed values: "none", "sqlite".
\\
\\--storage-sqlite-path <PATH>
\\ Path to the SQLite database file for persistent
\\ storage. Use ":memory:" for in-memory storage.
,
}

View File

@@ -54,10 +54,7 @@ fn run(allocator: Allocator, main_arena: Allocator) !void {
defer args.deinit(main_arena);
switch (args.mode) {
.help => {
args.printUsageAndExit(true);
return std.process.cleanExit();
},
.help => |tag| return args.printUsageAndExit(tag, true),
.version => {
var stdout = std.fs.File.stdout().writer(&.{});
try stdout.interface.print("{s}\n", .{lp.build_config.version});
@@ -94,7 +91,7 @@ fn run(allocator: Allocator, main_arena: Allocator) !void {
log.debug(.app, "startup", .{ .mode = "serve", .snapshot = app.snapshot.fromEmbedded() });
const address = std.net.Address.parseIp(opts.host, opts.port) catch |err| {
log.fatal(.app, "invalid server address", .{ .err = err, .host = opts.host, .port = opts.port });
return args.printUsageAndExit(false);
return args.printUsageAndExit(.serve, false);
};
var server = lp.Server.init(app, address) catch |err| {