diff --git a/src/content/guides/_meta.ts b/src/content/guides/_meta.ts index 25f8c52..a9e1e23 100644 --- a/src/content/guides/_meta.ts +++ b/src/content/guides/_meta.ts @@ -1,6 +1,7 @@ import type { MetaRecord } from 'nextra' const meta: MetaRecord = { + 'lightpanda-agent-tutorial': 'Use Lightpanda Agent', 'retrieve-an-html-webpage': 'Retrieve an HTML webpage', 'configure-a-proxy': 'Configure a proxy', 'markdown-axtree': 'Get Markdown and AX Tree', diff --git a/src/content/guides/configure-a-proxy.mdx b/src/content/guides/configure-a-proxy.mdx index 8adc0c9..a5b5b27 100644 --- a/src/content/guides/configure-a-proxy.mdx +++ b/src/content/guides/configure-a-proxy.mdx @@ -15,11 +15,11 @@ proxy. Ensure your proxy address starts with `http://` or `https://`. Use a local proxy with the `fetch` command: ```sh copy -./lightpanda fetch --http-proxy http://127.0.0.1:3000 https://lightpanda.io +lightpanda fetch --http-proxy http://127.0.0.1:3000 https://lightpanda.io ``` Or configure the proxy with `serve` for the CDP server. All outgoing requests will use the proxy. ```sh copy -./lightpanda serve --http-proxy http://127.0.0.1:3000 +lightpanda serve --http-proxy http://127.0.0.1:3000 ``` ### HTTP proxy with basic auth @@ -30,7 +30,7 @@ for the proxy using the `username:password@` format in the proxy address. It works for both `fetch` and `serve` commands. ```sh copy -./lightpanda fetch --http-proxy 'http://me:my-password@127.0.0.1:3000' https://lightpanda.io +lightpanda fetch --http-proxy 'http://me:my-password@127.0.0.1:3000' https://lightpanda.io ``` ### HTTP proxy with bearer auth @@ -43,7 +43,7 @@ to authenticate with the `--proxy-bearer-token`. It works for both `fetch` and This option will add a `Proxy-Authorization` header all the outgoing requests. ```sh copy -./lightpanda fetch --http-proxy 'http://127.0.0.1:3000' --proxy-bearer-token 'MY-TOKEN' https://lightpanda.io +lightpanda fetch --http-proxy 'http://127.0.0.1:3000' --proxy-bearer-token 'MY-TOKEN' https://lightpanda.io ``` ## Configure a proxy from your Puppeteer/Playwright script @@ -57,7 +57,7 @@ With Puppeteer, you have to configure the proxy address when starting Lightpanda. ```sh copy -./lightpanda fetch --http-proxy 'http://127.0.0.1:3000' +lightpanda fetch --http-proxy 'http://127.0.0.1:3000' ``` Then you can call `page.authenticate` function to inject your authentication diff --git a/src/content/guides/lightpanda-agent-tutorial.mdx b/src/content/guides/lightpanda-agent-tutorial.mdx new file mode 100644 index 0000000..daec9e9 --- /dev/null +++ b/src/content/guides/lightpanda-agent-tutorial.mdx @@ -0,0 +1,303 @@ +--- +title: Use Lightpanda Agent +description: Use Lightpanda Agent to create a reproducible JavaScript browser script. +--- + +# Use Lightpanda Agent + +This tutorial will walk you through how to use Lightpanda Agent to create a +reproducible JavaScript browser script. + +See [reference](/run-locally/commands/agent) for flags and commands. + +## Prerequisites + +- Lightpanda installed and on your `PATH`. +- A Hacker News account. +- An LLM API key for the natural-language sections (Anthropic, OpenAI, + Gemini, Hugging Face or a local Ollama). Recorded `.js` PandaScripts need no key. + +Export your HN credentials as `LP_*` env vars. The `LP_` prefix +matters for security: Lightpanda only resolves these placeholders +inside its own subprocess (so your password never reaches the LLM), +and the `getEnv` tool refuses any variable that doesn't start with +`LP_` (so the LLM can't read your other secrets). + +```sh copy +export LP_HN_USERNAME="your-hn-handle" +export LP_HN_PASSWORD="your-hn-password" +``` + +Check the variables are set. If one is missing, `/fill` will silently +type the literal `$LP_HN_USERNAME` into the form rather than your +username: + +```sh copy +lightpanda agent --no-llm +``` +```console +> /getEnv LP_HN_USERNAME +``` + +## 1. Start the REPL + +```sh copy +lightpanda agent +``` + +The startup banner shows whether natural language is available; +`/model` prints the resolved model. REPL history lives in `.lp-history` +in the working directory. + +```console +> /help # list every browser tool +> /help goto # JSON schema for one tool +> /quit +``` + +No API key? `lightpanda agent --no-llm` runs the slash-commands-only +REPL. + +## 2. Run a single task + +Before doing anything complicated, run a one-shot task to check +everything works: + +```sh copy +lightpanda agent --task "what is the top story on news.ycombinator.com?" +``` + +`--task` runs one user turn, prints the answer on stdout, exits. +Tool calls and progress go to stderr, so redirecting gives you a +clean answer: + +```sh copy +lightpanda agent --task "top story on news.ycombinator.com?" > out.txt +``` + +Use `-a ` (repeatable) to attach local files. + +## 3. Log in to Hacker News + +Paste these into the REPL in order: + +```console +> /goto https://news.ycombinator.com/login +> /fill selector='form[action="login"] input[name="acct"]' value='$LP_HN_USERNAME' +> /fill selector='form[action="login"] input[name="pw"]' value='$LP_HN_PASSWORD' +> /click selector='form[action="login"] input[type="submit"][value="login"]' +> /waitForSelector '#logout' +``` + +A few things worth knowing: + +- **Slash commands only.** `click '#foo'` is forwarded to the LLM; + only `/click '#foo'` runs as a command. TAB completes tool names. +- **`/waitForSelector '#logout'` is both a sync point and an + assertion.** It blocks until HN's logged-in DOM renders. If the + credentials are wrong (or the website throws a captcha, or the layout + changes), the command times out at the line where the failure + actually happened. Use this pattern after every state-changing + action. +- **Selectors are CSS only.** The click-family tools (`/click`, + `/fill`, `/hover`, `/selectOption`, `/setChecked`) accept CSS + only. Backend node IDs are invalidated by any DOM mutation and + can't be serialized into recordings. + +Confirm the login worked: + +```console +> /extract '{"karma": "#karma"}' +{"karma":"42"} +``` + +`/extract` takes a JSON schema and prints one JSON object to stdout. +Schema grammar: + +- `""`: text of the first match. +- `[""]`: text of every match. +- `{"selector": "", "attr": ""}`: attribute of the first match. +- `[{"selector": "", "fields": {…}}]`: array of records, with + each `fields` entry resolved relative to the matched element. + +Now go to the front page and pull the story list: + +```console +> /goto https://news.ycombinator.com +> /extract ''' +{ + "topStories": [{ + "selector": ".athing", + "fields": { + "rank": ".rank", + "title": ".titleline > a", + "url": {"selector": ".titleline > a", "attr": "href"} + } + }] +} +''' +``` + +Triple-quoted values let a schema span multiple lines. + +### How we got those selectors + +Skip this if you're happy treating them as given. + +`/tree` prints the semantic tree. On the login page you'll see two +forms (login and signup) with unlabeled textboxes, which means +`/findElement role=textbox name=username` returns nothing. + +`/detectForms` reads the HTML directly and surfaces each form's +`action` plus each input's `name`. The first form has `action: "login"` +and fields named `acct` and `pw`, which gives us the form-scoped +selector (`form[action="login"] input[name="acct"]`) that won't +collide with signup. + +`/nodeDetails backendNodeId=` is the alternative: it returns a +ready-to-use CSS selector for any node ID from `/tree`. + +## 4. Save the session as a script + +Retype the login plus front-page sequence in a single REPL session, +then: + +```console +> /save hn_login.js +> /quit +``` + +In `--no-llm` mode, `/save` transcribes the session deterministically. +With an LLM, it synthesizes an idiomatic script. The result is +JavaScript: + +```js copy +const page = new Page(); +await page.goto("https://news.ycombinator.com/login"); +page.fill({ selector: "form[action=\"login\"] input[name=\"acct\"]", value: "$LP_HN_USERNAME" }); +page.fill({ selector: "form[action=\"login\"] input[name=\"pw\"]", value: "$LP_HN_PASSWORD" }); +page.click({ selector: "form[action=\"login\"] input[type=\"submit\"][value=\"login\"]" }); +page.waitForSelector("#logout"); +await page.goto("https://news.ycombinator.com"); +return page.extract({ topStories: [{ selector: ".athing", fields: { rank: ".rank", title: ".titleline > a", url: { selector: ".titleline > a", attr: "href" } } }] }); +``` + +Only state-mutating commands are recorded; read-only ones (`/tree`, +`/markdown`) are dropped. `/extract` is recorded because it shapes +what the script returns. + +## 5. Replay the script without an LLM + +```sh copy +lightpanda agent hn_login.js +``` + +No `--provider`, no API key, no token spend. Whatever the script +`return`s is printed automatically as JSON. Because the saved script +ends with `return page.extract(...)`, you get clean JSON on stdout: + +```sh copy +lightpanda agent hn_login.js > stories.json +``` + +From inside the REPL, `/load hn_login.js` runs the same script against +the current session. + +To reshape the output, assign the result and end with `return ` +(what you return is what prints): + +```js copy +const page = new Page(); +await page.goto("https://news.ycombinator.com"); + +const topStories = page.extract({ + topStories: [{ + selector: ".athing", + fields: { + rank: ".rank", + title: ".titleline > a", + url: { selector: ".titleline > a", attr: "href" } + } + }] +}); + +return topStories; +``` + +## 6. Add your own JavaScript logic + +Agent scripts run in a separate JavaScript context from the page. No +`window`, `document`, DOM API, `require`, or `process`. Browser +interaction happens through the `Page` object: `new Page()` makes a +page, `await page.goto(url)` navigates it, and every other primitive is +a synchronous method on it. + +Use `page.extract(...)` to move page data into local logic, then +process it with normal JavaScript: + +```js copy +const page = new Page(); +await page.goto("https://news.ycombinator.com"); + +const topStories = page.extract({ + topStories: [{ + selector: ".athing", + limit: 5, + fields: { + rank: ".rank", + title: ".titleline > a", + url: { selector: ".titleline > a", attr: "href" } + } + }] +}); + +return topStories.map((s) => ({ rank: s.rank, title: s.title, url: s.url })); +``` + +Use `page.evaluate(...)` only when you intentionally want a string to +run in the page's JavaScript context. Page evaluate cannot see agent +variables or call agent primitives. + +## 7. Use Lightpanda from another agent (MCP) + +If you're driving Lightpanda from a different agent (Claude Code, a +custom MCP client, your own harness), use `lightpanda mcp` instead. +The calling agent supplies the LLM, so Lightpanda needs no API key. + +```json copy +{ + "mcpServers": { + "lightpanda": { + "command": "/path/to/lightpanda", + "args": ["mcp"] + } + } +} +``` + +Drive the browser with the usual tools (`goto`, `fill`, `click`, +`waitForSelector`), then hand back a script with the `save` tool: + +```json +{ + "tool": "save", + "args": { + "path": "hn_login.js", + "script": "const page = new Page();\nawait page.goto(\"...\");\n..." + } +} +``` + +The path must be relative and free of `..`. Literal `LP_*` values are +scrubbed back to placeholders before the file is written. The output +runs unmodified: + +```sh copy +lightpanda agent hn_login.js +``` + +## Further reading + +- [Agent](/usage/agent): full documentation reference, +- [PandaScript](/usage/pandascript): runtime, primitives, + return values. diff --git a/src/content/guides/markdown-axtree.mdx b/src/content/guides/markdown-axtree.mdx index 4e66ddb..55260c1 100644 --- a/src/content/guides/markdown-axtree.mdx +++ b/src/content/guides/markdown-axtree.mdx @@ -54,7 +54,7 @@ lightpanda fetch --dump [options] ### Basic example ```sh copy -./lightpanda fetch --dump markdown https://example.com +lightpanda fetch --dump markdown https://example.com ```
@@ -76,10 +76,10 @@ Includes rendered iframe content in the output. Without it, iframe content is ex ```sh copy # Without iframe content (default) -./lightpanda fetch --dump markdown https://example.com +lightpanda fetch --dump markdown https://example.com # With iframe content included -./lightpanda fetch --dump markdown --with-frames https://example.com +lightpanda fetch --dump markdown --with-frames https://example.com ``` On pages without iframes, output is identical either way. @@ -90,10 +90,10 @@ Removes groups of tags from the output. Values can be combined with commas: ```sh copy # Remove JavaScript and CSS from output -./lightpanda fetch --dump html --strip-mode js,css https://example.com +lightpanda fetch --dump html --strip-mode js,css https://example.com # Remove all UI, scripts and styles -./lightpanda fetch --dump html --strip-mode full https://example.com +lightpanda fetch --dump html --strip-mode full https://example.com ``` | Value | Tags removed | @@ -108,7 +108,7 @@ Removes groups of tags from the output. Values can be combined with commas: The `semantic_tree` format returns the page's accessibility tree as structured JSON: ```sh copy -./lightpanda fetch --dump semantic_tree https://example.com +lightpanda fetch --dump semantic_tree https://example.com ``` Each node includes: @@ -156,7 +156,7 @@ Each node includes: The `semantic_tree_text` format outputs the same data in a compact readable form: ```sh copy -./lightpanda fetch --dump semantic_tree_text https://example.com +lightpanda fetch --dump semantic_tree_text https://example.com ```
@@ -187,10 +187,10 @@ Unlike `--dump markdown` which processes a URL from scratch, `LP.getMarkdown` co ```sh copy # Start CDP server (default: ws://127.0.0.1:9222) -./lightpanda serve +lightpanda serve # Custom host and port -./lightpanda serve --host 0.0.0.0 --port 9333 +lightpanda serve --host 0.0.0.0 --port 9333 # Verify it's running curl http://127.0.0.1:9222/json/version @@ -319,7 +319,7 @@ For `example.com`, the CDP accessibility tree contains **12 nodes**. Lightpanda includes a built-in MCP (Model Context Protocol) server for direct integration with AI tools and LLM frameworks. ```sh copy -./lightpanda mcp +lightpanda mcp ``` The MCP server exposes `markdown` and `semantic_tree` tools. diff --git a/src/content/index.mdx b/src/content/index.mdx index 1fb60d5..b500c90 100644 --- a/src/content/index.mdx +++ b/src/content/index.mdx @@ -9,6 +9,7 @@ Made for headless usage: - Javascript execution - Support of Web APIs - Compatible with [Playwright](https://playwright.dev/), [Puppeteer](https://pptr.dev/) through CDP + - Built-in automation: [agent](/usage/agent) with natural language and reproducible [script](/usage/pandascript) Fast web automation for AI agents, LLM training, scraping and testing: - Ultra-low memory footprint (16x less than Chrome) diff --git a/src/content/run-locally/commands/_meta.ts b/src/content/run-locally/commands/_meta.ts index ada85e5..82d7a2e 100644 --- a/src/content/run-locally/commands/_meta.ts +++ b/src/content/run-locally/commands/_meta.ts @@ -3,6 +3,7 @@ import type { MetaRecord } from 'nextra' const meta: MetaRecord = { fetch: 'Fetch', serve: 'CDP server', + agent: 'Agent', mcp: 'MCP', } diff --git a/src/content/run-locally/commands/agent.mdx b/src/content/run-locally/commands/agent.mdx new file mode 100644 index 0000000..44a54c3 --- /dev/null +++ b/src/content/run-locally/commands/agent.mdx @@ -0,0 +1,213 @@ +--- +title: Agent +description: Starts an MCP (Model Context Protocol) server over stdio +--- + +# Agent + +Start Lightpanda as an interactive AI agent that can browse the web. + +```sh copy +lightpanda agent +``` + +## Options + +```console +Arguments: +[SCRIPT] Optional path to a .js script. Runs the script + (no LLM calls) and exits. With no script and no + --task, the REPL starts; from there /load runs a + script and /save exports the session to a file. + Caution: .js files can contain evaluate(...) calls + that run arbitrary JavaScript in the page. Only run + scripts you trust, the same way you would a shell + script. + +Options: +--provider The AI provider. + When omitted, lightpanda auto-detects an API key + from your environment (ANTHROPIC_API_KEY, + OPENAI_API_KEY, GOOGLE_API_KEY/GEMINI_API_KEY, + HF_TOKEN). + With exactly one key set: that provider is used. + With multiple keys on a TTY: you'll be prompted + to pick; in non-interactive contexts, pass + --provider explicitly. With no keys set: falls + back to the basic REPL (slash commands only, no + natural-language input, no LOGIN / + ACCEPT_COOKIES keywords). + + ollama is never auto-detected (it needs no key); + select it explicitly with --provider ollama. + + Allowed values: + "anthropic", "openai", "gemini", + "huggingface", "ollama". + In the REPL, use /provider to list and change + providers. + +--no-llm Force the basic REPL even when an API key is + present or --provider is set. Useful for testing + slash commands without burning tokens, or for + disabling the LLM in a saved command without + editing the existing flags. Wins over --provider. + +--model The model name to use. + Defaults to a sensible default per provider. + In the REPL, use /model to list and change + models for the active provider. + +--base-url Override the API base URL for the provider. + Defaults to the provider's standard endpoint. + Ollama default: http://localhost:11434/v1. + Hugging Face default is the serverless router + (https://router.huggingface.co/v1); point this + at a dedicated Inference Endpoint to use one. + +--system-prompt Override the default system prompt. + +--task One-shot mode: run a single user turn, print the + final answer to stdout, and exit. Conflicts with + the positional script. + +-a, --attach Feed a local file to the model alongside --task. + Repeatable, one file per flag. Text files are + inlined (max 512 KiB each); images/audio/pdf are + base64-encoded (max 20 MiB each). Requires --task. + +--list-models Print the model IDs usable with `agent` for + --provider, one per line, sorted, and exit. + Auto-detects the provider from env when + --provider is omitted. + +--verbosity Stderr chatter level. + Default: high when --task captures stderr to a + pipe or file; low otherwise. low/medium also + raise --log-level to err (mutes page-side + console.error spam) unless --log-level is set + explicitly. + + Allowed values: + low Silent in --task mode (final answer to + stdout only); spinner + summary in REPL. + medium + one `● [tool: ...]` line per call. + high + the matching `[result: ...]` body + (required by the benchmarks harness). + +--effort Per-turn reasoning budget, mapped to each + provider's native thinking/reasoning knob. + Default: low in the REPL (snappy turns), + medium in one-shot --task mode. In the REPL, + use /effort to change it. + + Allowed values: + none, minimal, low, medium, high, xhigh. + +The provider, model, effort, and verbosity you choose in the REPL are +remembered per-directory in .lp-agent.zon and reused on the next run. + +API keys are read from the environment: ANTHROPIC_API_KEY, OPENAI_API_KEY, +GOOGLE_API_KEY/GEMINI_API_KEY, or HF_TOKEN. Ollama does not require an API +key. + +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