ego-browser

When you need a browser, read this Skill by default. Use it to open and operate websites, fill forms, click buttons, take screenshots, extract page data, sign in, and perform other browser automation tasks, as well as web app testing, dogfooding, QA, bug investigation, and app-quality review. ego-br

By citrolabs · 7,464 installs

npx skills add citrolabs/ego-lite --skill ego-browser

Source repository · Upstream listing

ego browser For installation, connection, or runtime problems, read references/install.md . Use help() or references/api.md for signatures and uncommon options of APIs named below. Run browser scripts Run JavaScript through a heredoc: In some sandbox environments, heredoc input may not work; use e instead: In Bash/Zsh, use single quotes around the code and double quotes for JavaScript strings. Single quotes within the code require shell quoting. The script always runs in Node.js, not in the web Page. Browser helpers and Node.js APIs belong in the script; Page globals such as window , document , location , and DOM APIs do not. Put browser side JavaScript inside page.evaluate() . Do not import Playwright or launch another browser. The Node.js runtime uses ESM. When a script needs local files, load built ins with dynamic imports such as await import("node:fs/promises") . Ego browser deliberately exposes a small custom API. It is not Playwright, even where method names and options look similar. Use only the TaskSpace, Page, FileChooser, mouse, and keyboard APIs explicitly listed in this Skill. Do not infer Playwright methods such as locator() , getByRole() , context() , expect() , or route() . When the listed API does not cover an operation, use the documented page.evaluate() or page.cdp() escape hatches instead of guessing another method. Pointer actions accept an optional label with a concise 3 6 word description. Pass it with clicks, hovers, drags, or scrolling to keep the action text next to the visible agent cursor in sync with the action. When the user explicitly asks for ego browser, start with a real browser command and diagnose the CLI or installation only if it fails. Spaces, rounds, and pages Use exactly one TaskSpace for the entire user goal. Create it once, print its spaceId , and resume that same space in later rounds. Use multiple spaces only when the user explicitly requests them. Never use a new TaskSpace to recover from a stuck, blocked, timed out, or unexpected Page. Recover within the existing space; if it cannot continue, stop and ask the user. Every invocation starts a new Node.js process. Task spaces, tabs, and Page labels persist; JavaScript variables do not. A new task space starts with Page p1 ; navigate it instead of opening another Page. Reuse a Page with goto() instead of opening a new Page for every URL. All time values are milliseconds. Do not inspect or select profiles unless the user explicitly requests a particular Ego Lite profile. A profileId applies only when creating a space; use help("profiles") for the exact workflow. Supported TaskSpace API: State: spaceId , name , ownership , page(label) , userPage() Pages: await task.pages() , await task.tabs() , newPage() , adopt(page, { as? }) , release(label) Control: waitForControl(options) , handOff() , finish({ keep }) Advanced: cdp(method, params, options) Pages receive permanent labels such as p1 , p2 , and p3 . Prefer these labels to custom { as } values. Reuse or close Pages as the task proceeds; the runtime reports the configured Page budget when it is reached. task.newPage() creates another blank Page when multiple Pages must stay open. Navigate it separately with page.goto() . await task.pages() returns managed Pages. await task.tabs() returns every tab in the space as { label?, page, targetId, title, url, active, openedBy } . A tab without a label is unmanaged; adopt it before operating: release(label) returns an unknown origin Page to the user without closing its tab. Close Agent created Pages with page.close() . Treat openedBy: "unknown" as user owned when deciding whether a Page may be closed. Page operations ego browser provides the following Page API: State and observation: label , spaceId , openedBy , targetId , url() , title() , info() , snapshot() , screenshot() Navigation and waits: goto() , reload() , waitForURL() , waitForEvent() , waitForSelector() , waitForLoadState() , waitForFunction() , waitForTimeout() Elements: click() , dblclick() , hover() , dragAndDrop() , fill() , selectOption() , focus() , press() , setInputFiles() , waitForFileChooser() , close() Dialogs: acceptDialog(promptText?) , dismissDialog() Pointer: mouse.click() , move() , down() , up() , wheel() Keyboard: keyboard.down() , up() , press() , type() , insertText() , paste() Page code and protocols: evaluate(fnOrString, argument) , fetch(url, options) , cdp(method, params, options) page.evaluate() callbacks run only inside the Page; they cannot read variables or Node.js modules from the surrounding script. Define browser side helpers inside the callback or pass one JSON serializable value as its second argument. Work efficiently: Each time you observe, collect only the cheapest page state sufficient to choose the next action. Use a snapshot for semantic or locator ground truth and a screenshot for visual confirmation; do not request both by default. If an action does not produce the expected result, inspect the current page before deciding whether to retry. Do not blindly repeat it or immediately fall back to coordinates or raw CDP. Once the page clearly shows the requested result, stop; do not confirm the same result through multiple surfaces. Semantic pages: snapshot and selectors Prefer snapshots and semantic selectors for ordinary DOM pages. Use screenshots and coordinates only when useful DOM semantics are unavailable. Before choosing an unfamiliar target, take a snapshot. When the current state is sufficient to plan several actions on the same Page, complete them in one script invocation, then observe the result once. Observe between actions only when an intermediate result changes what should happen next. Keep the action sequence, the wait for its final expected state, and the next snapshot in the same script invocation. Print the snapshot last so the next round can act on it directly. The final snapshot is the next round's starting view of the changed page; without it, that round usually has to spend a separate browser call observing before it can choose the next target, which wastes compute. Wait for the expected result: use waitForURL() for navigation, waitForSelector() for element state, or waitForFunction() for application state. Avoid fixed delays when an observable condition exists. A snapshot captures the current moment; it does not wait for the page to become stable. page.snapshot() captures the current viewport. For content outside it, use page.snapshot({ scope: "full page" }) . The default viewport snapshot includes visible iframe content returned by the browser. To focus on a frame's subtree, reuse the ref printed on its iframe line: Use the refs returned by the subtree for actions inside the iframe. A subtree snapshot does not scope later locator actions; they still prefer actionable matches in the top document before searching frames. waitForLoadState() defaults to load . waitForFunction() follows the Playwright argument order; pass undefined before options when there is no Page argument: Element actions accept: snapshot refs such as @21 or ref=21 text=... for page content loc=css: , loc=role: , and loc=href: locators xpath=... raw CSS selectors Selector actions require exactly one match. Unquoted text normalizes whitespace, ignores case, and matches a substring; quoted text such as text="Save changes" is exact and case sensitive. A small Playwright compatible selector subset is also accepted: css=... , terminal :has text("...") and :text is("...") , nth=N after a CSS, text, or href selector ( N is 1 or non negative), plus loc=role:...[name ="..."] for accessible name substrings. Other Playwright selector syntax is not supported. When a selector identifies a wrapper, focus() and press() may use its interactive ancestor or unique editable descendant; fill() and setInputFiles() only continue to a unique compatible control. click() , fill() , hover() , and dragAndDrop() automatically bring their target into view with browser wheel input. Do not pre scroll solely to make a DOM target actionable. Snapshot node names are accessibility roles. Use a ref now or loc=... to find the element again. After the page changes, take a new snapshot. When a useful node has no ref, construct a selector from its role, text, or surrounding context. CSS searches nested open shadow roots. Actions use an actionable match in the top document first, then search frames when the top document has none. Multiple actionable matches in the selected document or frame are ambiguous. Select options by value, visible label, or zero based index. A string matches either value or label; pass an array for a multiple select: Pass null or [] to clear the current selection. Visual pages: screenshot, mouse, and keyboard Use a screenshot with mouse and keyboard operations for canvas, rich text, spreadsheets, maps, and other interfaces that lack useful DOM semantics: Inspect the screenshot with an image viewing tool. Coordinates use CSS pixels; keyboard names and + separated chords follow Playwright syntax. Use ControlOrMeta for portable shortcuts and verify the resulting page state. mouse.wheel() performs a short wheel input motion at the current mouse position and resolves when that motion completes. In each script invocation, move or click over the intended scrollable area before using it. On macOS, keyboard.paste() sends the native paste shortcut and then restores the user's clipboard. Pass { text, html } when a rich editor needs structured clipboard content; text is the plain text fallback. On other platforms, use keyboard.insertText() for plain text. For rich text editors and editable grids, validate a small edit before repeating it at scale. Canvas backed editors may not expose visible content through DOM text or selectors; verify those results with a screenshot or an application specific visible state. Page JavaScript and CDP Use page.evaluate() for bulk extraction or complex in page work. It accepts one JSON serializable argument and returns a JSON serializable value: page.evaluate() has no timeout option. Keep long work in bounded calls; on a safety timeout, use executionStopped and mayHaveLateEffects to decide whether an unsafe follow up requires reloading or closing the Page first. Use documented Page methods first. If a wrapper is missing or does not work reliably on the current page, use page.cdp() as a lower level path for diagnosis or control. It accepts Page, Runtime, DOM, Network, Input, and similar commands; use task.cdp() for Target and Browser commands. Raw CDP invalidates refs. Do not persist page.targetId across rounds. Action receipts, popups, and dialogs When an action is expected to open a new Page, start the wait before the action: High level actions also report immediately observed popups in receipt.popups as { label, targetId } . Resolve the Page with task.page(receipt.popups[0].label) and continue there; wait for its URL when the destination matters. For uncommon protocol event workflows, await page.events() returns and clears the buffered event array; it is not an EventEmitter. A synchronous JavaScript dialog may appear as receipt.dialog or in page.info() . Handle it before continuing: A receipt describes only the dispatched action and immediate popup or dialog observations; it does not verify the resulting application state. Files and requests Set an existing file input with absolute paths: If a click creates the file input, start waiting before the click: An upload triggered JavaScript dialog may be returned as result.dialog ; when present, handle it with the dialog metho