# Agent tutorial — Hacker News, end-to-end This walks you from "I just built `./lightpanda`" to a recorded, replayable JavaScript browser script, then captures the same flow from an external MCP client. Every section ends with a command you can run; nothing references later sections. For the flag/command/tool tables, see [agent.md](agent.md). This document is the tutorial; that one is the reference. For the JavaScript runtime contract, see [agent-script.md](agent-script.md). ## What you'll build One session against Hacker News: 1. Log in with your account. 2. Confirm the login by reading the username out of the header. 3. Save the whole flow to a `.js` file. 4. Run it offline, with no LLM. 5. Add local JavaScript logic around `extract(...)` results. 6. Save the same flow as a script from an external agent over MCP. ## Prerequisites - `./lightpanda` on your PATH (build with `zig build`). - A Hacker News account. - One LLM API key for sections that use natural language — Anthropic, OpenAI, Gemini, or a local Ollama. Recorded `.js` scripts run with no key at all. Export your HN credentials as `LP_*` env vars. The convention is `LP__` — a short site identifier (`HN` for Hacker News, `GH` for GitHub, …) lets you keep credentials for multiple sites in your environment without collisions. The unprefixed `LP_USERNAME` / `LP_PASSWORD` form is the generic fallback when you only have one site. In **bash** or **zsh**: ```console export LP_HN_USERNAME="your-hn-handle" export LP_HN_PASSWORD="your-hn-password" ``` In **fish**: ```fish set -gx LP_HN_USERNAME "your-hn-handle" set -gx LP_HN_PASSWORD "your-hn-password" ``` The `LP_` prefix matters. The agent resolves `$LP_*` references *inside* the Lightpanda subprocess, so the literal secret never enters the LLM context; and the `getEnv` tool refuses to read anything that doesn't start with `LP_`, so the model can't probe your other env vars. Verify they're set before continuing — substitution fails silently if a variable is missing (the literal `$LP_HN_USERNAME` ends up typed into the form), and the `/fill` confirmation message intentionally echoes the placeholder name rather than the resolved value, so the response text won't tell you. Confirm directly: ```console ./lightpanda agent --no-llm > /getEnv LP_HN_USERNAME ``` `/getEnv` returns the literal value if set, or "not set" if missing. Only `$LP_*` references in fill values are substituted; other `$` characters in your password (`my$ecret`, `$5.99`) are passed through verbatim. ## 1. First contact: the REPL ```console ./lightpanda agent ``` On startup the agent prints a one-line notice telling you which mode it landed in — which provider it chose (a detected cloud key, or a local Ollama server if that's all it finds), or "basic REPL (no LLM)" when no provider is available. The REPL writes its history to `.lp-history` in the working directory, so up-arrow works across runs. Try the meta commands: ``` > /help > /help goto > /quit ``` `/help` lists every browser tool. `/help ` prints its JSON schema. `/quit` exits cleanly. If you have no API key yet and want to poke around without an LLM, `./lightpanda agent --no-llm` forces the basic REPL. ## 2. The shortest possible win: `--task` Before doing anything complicated, prove the LLM + browser stack works end-to-end: ```console ./lightpanda agent --task "what is the top story on news.ycombinator.com?" ``` `--task` runs a single user turn, prints the final answer on stdout, and exits. Tool calls, progress, and errors all go to stderr, so redirecting stdout gives you a clean answer: ```console ./lightpanda agent --task "top story on news.ycombinator.com?" > out.txt ``` If you need to feed the model a local file, repeat `-a ` (or `--attach `) for each one. ## 3. Driving the browser by hand Now back to the REPL. We'll write the HN login flow one command at a time so you can see how each step depends on what the previous one showed. ``` > /goto https://news.ycombinator.com/login ``` `/goto` takes a single URL argument (positional, optionally quoted). The page is now loaded. > The REPL scripting surface is slash commands. `click '#foo'` (no leading slash) is > forwarded to the LLM as a natural-language prompt; only `/click '#foo'` > runs as a command. TAB completion in the REPL helps you find the right > tool name. Inspect it before clicking anything: ``` > /tree ``` `/tree` prints the semantic tree to stdout. Two forms are visible — the login form and the create-account form below it — and each one contains two unlabeled textboxes: ``` 8 form 13 'username:' 15 [i] textbox 18 'password:' 20 [i] 22 [i] button 'login' value='login' 30 form 35 'username:' 37 [i] textbox … ``` Notice the textboxes have no accessible name — "username:" is a sibling text node, not a `