Commit Graph
290 Commits
Author SHA1 Message Date
Adrià Arrufat 10609b025a Upgrade to Zig 0.17
Dependencies:
- v8: zig-v8-fork 0.17 branch (lightpanda-io/zig-v8-fork#218).
- sqlite3: build the amalgamation directly; the allyourcodebase wrapper
  has no 0.17 support yet.
- pcre2: master commit with 0.17 support (no release yet).
- translate_c package (2.0.0) replaces the deprecated addTranslateC.
  default_init is set to keep 0.16's zero-initialized struct fields.

Build:
- b.pathFromRoot/build_root/args/sysroot are gone: resolve paths from
  b.root, use addPassthruArgs, declare configure-time file and directory
  dependencies, and poison the configure cache for the git version.
- Drop curl config values the template never used (now an error).
- Drop the 0.16 zip-fetch workaround from the Makefile and CI.
- -Doptimize=ReleaseFast -> -Doptimize=fast.

Language and std:
- @intFromEnum/@enumFromInt -> @backingInt/@fromBackingInt.
- Struct-of-arrays @typeInfo (field_names, field_types, decl_names,
  param_types, error_names).
- `a ** n` removed: @splat for arrays, string.repeat for strings.
- errdefer captures removed: split WebDriver.run and fulfillRedirect.
- @hasDecl only sees pub decls: make StyleManager's Spec.finish pub,
  otherwise it silently stops running.
- SafeAllocator replaces DebugAllocator; the test runner now has to
  initialize std.testing.allocator_instance itself.
- std.fmt.allocPrint/bufPrint -> Allocator.print/std.mem.print,
  dupeZ -> dupeSentinel, std.builtin -> std.lang, builtin.os ->
  builtin.target.os, zon.parse arena API, BufferFirstAllocator, and the
  remaining renames (getLastOrNull, bit_set, ascii.find*, meta.Int).
2026-10-03 16:09:42 +02:00
Pierre Tachoire 8d6ebf0635 replace --disable-cors by --disable-features cors 2026-09-28 12:03:18 +02:00
Pierre Tachoire 12d3880d20 enable cors by default
keep `--experimental-features cors` available for BC.
add `--disable-cors` option.
2026-09-28 12:03:18 +02:00
Adrià Arrufat 1188554515 cli: read a url-like bare word as the url, not a misspelt command
`lightpanda version.io` was rejected as a misspelt `version`, 3 edits
within the length-scaled limit. Like curl, it fetches now: the command check
shares --dump's isUrlLike.
2026-09-28 09:22:29 +02:00
Adrià Arrufat cafe31649a cli: read a url-like --dump argument as the url, keep --log-filter's sign
`fetch --dump markdown.com` was rejected as a misspelt `markdown`: the
length-scaled limit allows 4 edits for 12 characters. No format has a `.`,
`/` or `:`, so an argument with one is always the url.

`--log-filter -cdpp` suggested `cdp`; invalidChoice now takes the stripped
prefix and puts it back on the value and the suggestion.
2026-09-27 13:26:28 +02:00
Adrià Arrufat 497f0363e5 cli: share the "invalid option choice" error
`cli.invalidChoice` logs a bad value with its closest match, and the
generic enum path, --dump, --log-level, --log-format and now --log-filter
all use it. editDistance lowercases its inputs once, and closest strips
exactly a leading `--`, as its doc says.
2026-09-27 13:26:28 +02:00
Adrià Arrufat f667c140ab cli: scale the "did you mean" distance with the input's length
A flat limit of two edits was too loose for short words and too strict
for long ones: `--dump md` suggested `pdf`, and a bare word within two
edits of `run` or `mcp` was rejected as a mistyped command instead of
being fetched, while `--insecure-disable-tls-verification` got no
suggestion at all. `closest` now allows about one edit per three
characters, as rustc does, not counting a leading `--`, and swapping two
adjacent characters counts as one edit so `--dmup` still finds `--dump`.

`--log-level` and `--log-format` now suggest the closest value too.
2026-09-27 13:26:28 +02:00
Adrià Arrufat 1f582d4674 cli: report usage errors once, accept -h, show tips only on a terminal
A flag given without its value, or an extra positional, failed with
only `FATAL exit err=MissingArgument`, not naming the flag. The parser
now logs which flag is missing its value or which argument is extra,
with a hint pointing at the command's help, and a missing fetch URL is
caught while parsing, next to run's missing script. Since each of these
is logged where it's found, main exits without the generic `exit` line.

`-h` works wherever `--help` does, instead of being taken as a URL.

The --obey-robots tip is for a person at a terminal, so it's skipped
when stderr isn't one; scripts capturing stderr no longer get it on
every run.
2026-09-26 23:52:34 +02:00
Adrià Arrufat b5896c259b Merge branch 'main' into agent-fixes
# Conflicts:
#	build.zig.zon
#	src/SemanticTree.zig
2026-09-25 10:44:05 +02:00
Muki Kiboigo 4afdc412ee add cors-store-entry-limit option 2026-09-23 07:33:14 -07:00
Karl Seguin 4a7590691d Merge pull request #3546 from lightpanda-io/robots-limit-entries
Robot Limit Entries
2026-09-23 07:31:30 +08:00
Muki Kiboigo d16091a403 0 for robot entry limit means no limit 2026-09-22 12:55:26 -07:00
Adrià Arrufat 006af21a6d agent: --url opens a start page before the first turn
A `--task` run that knows where it is going still spent a model turn
navigating there, and the REPL had no way to start anywhere but blank.

`--url` opens the page first, through `browser_tools.call` rather than
around it, so a bad URL fails like any other tool call and the opening
navigation is recorded for `--save` -- which is the first line any
replayable script needs anyway.
2026-09-22 15:52:07 +02:00
Adrià Arrufat 92a6b698fc agent: --search-engine, and say when the engine has no key
The search engine could only be set from the REPL's `/searchEngine`, so a
one-shot run had no way to pin one -- and a benchmark that wants its
results to mean something has to record which API answered.

`/searchEngine` also carried the only warning about key state, which is
the half that matters more. A keyless engine is not an error and starts
fine, so a run that silently falls back to a rate-limited public endpoint
looks exactly like a working one until every search begins failing, and
then it looks like a bad agent. Both messages now fire wherever the
engine is resolved, not just from the command.

`resolveSearchEngine`'s doc comment said there was no CLI flag. There is
one now.
2026-09-22 15:50:15 +02:00
Karl Seguin ee4d54928d webdriver: increase http default / max limit
The http max default was 4K with a 16KB hard limit. The default limit is now 1MB
with an initial default of 4K. This is to accommodate larger WebDriver payloads.
2026-09-21 17:52:41 +08:00
Muki Kiboigo 7e4f1dedc1 force min of 1 on robot entry limit 2026-09-17 20:36:31 -07:00
Muki Kiboigo 9fa9e5ae0d add robot store entry limit option 2026-09-17 20:00:09 -07:00
Karl Seguin dc1ae129e3 webapi: experimental (and useless, for now) ServiceWorker
This adds the shell for ServiceWorker, behind
`--experimental-features serviceworker`.

It's pretty useless as-is. We don't have the CacheStorage API (next) and don't
have the fetch interceptor (next next). But as-is, the change is quite big but
thankfully largely isolated.
2026-09-16 17:49:07 +08:00
Halil Durak 901e5eaf4d Agent: add lightpanda run -
Arbitrary JS code can be passed directly through stdin thanks to this.
2026-09-15 16:21:01 +03:00
Karl Seguin bcf69ced9c address feedback
tighten socket ownership (on error paths)

allow reaper to be disabled

Handle window where link is being destroyed, worker is still alive, and client
attempts to re-link.
2026-09-11 05:11:36 +08:00
Karl Seguin 8ad9eaf48d webdriver: HTTP WebDriver session management
This is a small step towards WebDriver supports (non-bidi). It allows creating
and deleting a BiDi "Session" (e.g. a worker). It also allows attaching a BiDi
driver to an HTTP-created BiDi session (the typical selenium startup flow).

This change unblocks the most basic setup/teardown of Selenium, so it still
isn't enough to actually use a Selenium script as-is. But it's significant
because it models a worker (thread) that isn't tied to a WebSocket, something we
haven't had before.

A consequence of a pure HTTP Session is that we don't have a clear cleanup
signal. There is no "the socket is disconnected". There's a new HTTP reaper
which kills HTTP Sessions after --http-session-timeout. It's expected that
drivers properly DELETE /session/:id. I imagine we're going to run into
--cdp-max-connections limits and need to tweak this code. BUT, this entire flow
is only enabled with --protocol webdriver, so it won't impact exiting CDP users.
2026-09-11 05:11:26 +08:00
Karl Seguin 2e6999f20b chore: make declarations private if they don't need to be public
This change is 99%  s/pub//   + a handful of dead code removal.
2026-09-10 14:42:09 +08:00
Karl Seguin 58e1a547d8 fix help ordering, remove unnecessary pub 2026-09-10 07:41:34 +08:00
Adrià Arrufat 37bf7b68c1 cli: one Accept-Language parser for the header and navigator.languages
navigator.languages now lists the Accept-Language tags in order, which is
Chrome's contract, instead of a second derivation from the locale tag that
disagreed with the header (--locale de-DE sent de-DE,de,en but reported
["de-DE","de"]). HttpHeaders.AcceptLanguage owns both shapes and is also
the CDP override type.

ICU canonicalizes a BCP 47 tag read from LC_ALL itself, script subtag
included, so the POSIX id conversion is gone; it dropped the script and
turned zh-Hans-TW into Traditional Chinese.

Also: the CDP handler keeps validateUserAgent's verdict instead of scanning
for Mozilla twice, the override is cleared unconditionally on context
teardown instead of through a flag, and the flags are sentinel strings so
Platform passes them to setenv without copying.
2026-09-09 18:07:03 +02:00
Adrià Arrufat c55afc1df9 cli: add --locale and --timezone, derive language signals from one value
navigator.language was hard-coded to en-US and Accept-Language was a
constant, while Intl, toLocaleString and Date followed the host process
environment. On a de_DE host a page saw navigator.language === "en-US"
next to German number formatting, a mismatch fingerprinting scripts look
for, and the same page rendered differently across machines.

Follow Chrome's --lang rule: one configured tag drives navigator.language(s),
the Accept-Language header and ICU's default locale. --locale defaults to
en-US, so Intl is now en-US on every host instead of whatever LANG says.
--timezone sets the IANA zone Date and Intl use; absent, the host zone stays.

Both are applied by writing LC_ALL and TZ before V8 initializes ICU, which
reads them lazily. Platform.init is the first call in App.init, before any
thread exists, so setenv is safe there.

CDP Emulation.setUserAgentOverride.acceptLanguage, which Playwright sends
for its locale option, now overrides the header and navigator.languages
for the browser context's lifetime, mirroring the user agent override, and
applies even when the Mozilla user agent is refused.

Emulation.setLocaleOverride and setTimezoneOverride stay no-ops: changing
ICU's defaults at runtime needs new zig-v8-fork bindings.
2026-09-09 17:49:02 +02:00
Adrià Arrufat a32d30fa65 cli: suggest the closest enum value on a typo
A misspelt value for an enum-typed flag now names the nearest tag as
did_you_mean. --dump's peeking validator treats a word within two
edits of a format name as that mistake rather than as the url, so
--dump htmx no longer becomes a second url.

tagNames moves from Config into cli so both can use it.
2026-09-08 17:33:48 +02:00
Karl Seguin 82344c609f Merge pull request #3432 from lightpanda-io/tweak-http-timeouts
http: tweak http timeouts
2026-09-07 17:41:46 +08:00
Halil Durak 814a2ab549 update Config + help.zon 2026-09-07 10:57:35 +03:00
Karl Seguin ec61861b20 http: tweak http timeouts
change --http-timeout default to 15000 (up from 5000)
change --http-connect-timeout default to 8000 (down from 300000, curl's default)

5 second _total_ transfer time can be a little tight. I generally don't see a
good reason to overly limit this value.

Worth noting that help.zon said the default for --http-timeout was 10000, but
it was, in fact, 5000.

Hopefully this improve situations like https://github.com/lightpanda-io/browser/issues/3395
which I believe are due to slow proxies.
2026-09-07 13:19:52 +08:00
Muki Kiboigo 15d27902ba use experimental features flag instead of obey cors 2026-09-04 07:00:01 -07:00
Muki Kiboigo ec45dccacf add obey_cors option 2026-09-04 06:59:38 -07:00
Karl Seguin 731774ab67 ops: Allow serving both cdp and bidi on the same port
--protocol can now be specified multiple times. This just makes ops/dev easier
by only requiring 1 instance.
2026-08-31 22:45:15 +08:00
Karl Seguin 766c0d05d6 bidi: protocol selection CLI, start of [classic] WebDriver
In order to support Selenium the way people are used to, it looks  like we need
to support both WebDriver classic (WebDriver) and WebDriver BiDi (BiDi). Typical
scripts look like a mix of the two, e.g. using WebDriver to control the browser
and using BiDi  to receive notifications. This commit:

1 - adds a --protocol (cdp|webdriver) CLI argument to the `serve` command to
    enable one or the other protocol (defaulting to CDP)

2 - adds basic WebDriver endpoint to let a Selenium client connect. This
    implementation is hackish and sits on top of our simple Handshake handler.

The handshake handler is well past its original design. Serving /json/version
and /metrics from it was one thing. But Driving the entire browser session? This
will get a follow up PR.
2026-08-31 22:45:15 +08:00
Karl Seguin 7dd4496b0f cli: add --http-version 1.1 / auto flag
https://github.com/lightpanda-io/browser/issues/3348

When set to 1.1, libcurl is configured to only offer HTTP 1.1. By default, or
when set to "auto", it's up to libcurl to decide how to connect. This maps to
libcurl's CURL_HTTP_VERSION_1_1 and CURL_HTTP_VERSION_NONE.

LP.configureCDP now takes an `httpVersion` field which can be "1.1" or "auto"
to control that specific browser session. Ideally this is called prior to any
navigation.
2026-08-31 16:16:47 +08:00
Karl Seguin 67927b9bde Add PDF support
adds --dump screen.pdf and CDP's Page.printToPDF (along with IO.read and
IO.read). Builds ontop of https://github.com/lightpanda-io/browser/pull/3231

This generates a text-based PDF, not just our PNG placed into a PDF. The biggest
change to the existing (isolated) code was adding href information so that links
are actually clickable.

Like 3231 the hard parts are all Claude but isolated. The one place it diverged
from the plan was NOT using the subsetter rust crate for trimming the size of
the embedded font. It felt that the dependencies were too much (e.g. a second
version of skrifa, ...). One subsetter was removed, the pdf rendering, while
still a blackbox, was moved from Rust to Zig (pdf.zig), which is kind of nice.
2026-08-28 12:53:36 +08:00
Karl Seguin db258bfec4 breaking: disable worker and iframe loading by default
By default, iframes and workers no longer loaded. Use `--load-resources iframe`
and `--load-resources worker` to restore the previous behavior. The disabling
makes resource loading more consistent.

To further make things more consistent, Config seems the following changes:

1. remove  `--timeout` from `serve` which does nothing but has printed a
   deprecation warning for a long time

2. added .deprecated field to CLI config flags which now logs the specified
   deprecated warning when used

3. `--log-filter-scopes` is deprecated in favor of `--log-scopes`

4. `--disable_subframes` is deprecated. Iframe loading is disabled by default,
    use `--load-resources iframe` to enable iframe loading

5. `--disable_workers` is deprecated. Worker loading is disabled by default,
    use `--load-resources worker` to enable worker loading

6. `--enable_external_stylesheets`  is deprecated. Stylesheets remain disabled
    by default. Use `--load-resources stylesheet` to enable loading external
    stylesheets

CLI log parameters now alter the logger behavior on parse. This helps minimze
the window where default log settings are in-play. It also means things like
this work:

```
./lightpanda --log-level fatal --disable_subframes --log-level warn
```

More seriously, there's now an optional `beforeParse` fired once the mode is
known. This is used by mcp to set the default log level to logfmt. Previously
this was done much later and could easily result in a mix of pretty and logfmt
logs.
2026-08-27 17:25:42 +08:00
Adrià Arrufat 6811cc8226 fetch: per-page errors, an "error" field in --json, --fail-on-http-error
A wait failure on one URL aborted the whole multi-URL fetch with no
output, the wait_ms budget was computed once before the loop so N pages
could take N times the budget, navigation failures exited 0 with an empty
dump, and HTTP 4xx/5xx were indistinguishable from success by exit code.

Each page now has its own error slot: wait, navigation and dump failures
are recorded there instead of returned, every page is still written (the
JSON envelope carries the name under "error"), then the first failure is
returned so the process exits 1. The remaining budget is recomputed per
page. --fail-on-http-error turns a status >= 400 into exit 22, curl's
code for the same condition; the dump is written first either way.
2026-08-26 15:35:15 +02:00
Adrià Arrufat 1fb7a1b983 fetch: namespace the dump options in Config too 2026-08-26 15:34:15 +02:00
Adrià Arrufat d3bad960c3 fetch: add --selector and --max-bytes, honor --strip-mode for markdown
--strip-mode was silently ignored for --dump markdown, and fetch had no
way to scope a dump to one element or cap its size although the markdown
and html tools have both.

--selector dumps the first matching element in any dump mode, --max-bytes
caps html and markdown with the tools' truncation marker, and markdown
now honors strip ui (images; scripts, styles and hidden elements are
never rendered, so the other groups don't apply).
2026-08-26 13:23:18 +02:00
Halil Durak 3265d66052 change how --load-resources are provided from CLI 2026-08-25 14:18:47 +03:00
Halil Durak e58f99da18 add --load-resources CLI arg, supporting image param 2026-08-25 14:18:45 +03:00
Karl Seguin 289502e19a Merge pull request #3231 from lightpanda-io/screenshot
Screenshot
2026-08-25 18:12:00 +08:00
Adrià Arrufat e02c45888d mcp: make logfmt a Config default instead of an override
MCP mode unconditionally set log.opts.format = .logfmt after the
--log_format flag had already been applied, so `--log_format pretty`
was silently ignored. It also ran after the "starting server" line,
which came out in the pre-override format in debug builds.

Move it into Config.logFormat() as a mode-aware default, mirroring
what logLevel() does for agent mode: null -> logfmt for mcp, and an
explicit flag wins.
2026-08-24 19:35:07 +02:00
Karl Seguin 09edfd3ce5 http: Add optional rate limit
Add an optional per-host rate limit. This currently only applies to the top-
level navigation. This makes it simpler to implement and simpler to reason
about. The full load of a page is only ever delayed at its head, not
sporadically through the page loading.

The use-case where a RateLimiter is most useful is when the browser is crawling
multiple pages of the same site, and in that case, the top-level rate limit
still applies some degree of limit to any linked resources (e.g. a JS on a
different host).

`--http-nav-delay` is the delay, in milliseconds, to apply to top level
navigates per host. Currently defaults to 0 (disabled).

`--http-nav-burst` is the burst allowed per host. Defaults to 1 (has no impact
when `--http-nav-delay` is disabled).
2026-08-20 16:03:42 +08:00
Karl Seguin ff5b791b7e wip: screenshot 2026-08-19 21:49:02 +08:00
Karl Seguin d3c26f589e Merge pull request #3219 from lightpanda-io/sec-ch-ua-full-version
send Sec-Ch-Ua-Full-Version-List header
2026-08-18 20:00:44 +08:00
Pierre Tachoire 0a0164c972 remove useless null terminated version_z 2026-08-18 12:28:21 +02:00
Pierre Tachoire a961156283 send Sec-Ch-Ua-Full-Version-List header
Add Sec-Ch-Ua-Full-Version-List header with the full LP version

```
Sec-Ch-Ua-Full-Version-List: "Lightpanda";v="1.0.0-dev.8713+33867c727"
```
2026-08-18 09:38:00 +02:00
Karl Seguin 5c0aa90ea4 Merge pull request #3187 from lightpanda-io/http-header-option
add --http-header option to send headers on every HTTP requests
2026-08-18 15:24:10 +08:00
Pierre Tachoire 33867c7275 Merge pull request #3116 from lightpanda-io/nikneym/adblocker-init
Initial implementation for easy list parsing and AdBlocker
2026-08-18 08:50:42 +02:00