¿Qué herramientas tenemos hoy para navegar Internet?


**Considering available tools**

I need to clarify which tools are available for browsing the internet. It seems like I won't have access to web tools, but I could mention options like bash with curl or the playwright-mcp CLI. I might also be able to read local files or potentially use the agents-database, but that’s not internet-based. The user seems to want a concise answer about tools, so I’ll make sure to keep my response focused and clear!

Hoy:

- `bash`: para `curl`, `wget`, APIs, HTML crudo, feeds, archivos.
- `playwright-mcp` CLI: para abrir sitios, navegar, hacer login, clickear, extraer contenido, screenshots. Preferido si la web necesita JS.
- `read`: para leer archivos locales descargados.
- `bash` + parsers (`jq`, `pup`, `python`, etc.) si hace falta limpiar contenido.

No tengo un tool nativo tipo “browser.search” o “web.run” en este entorno.
Si querés, hago una prueba contra una URL ahora.


estoy evaluando si https://docs.factory.ai/guides/skills/browser o https://github.com/badlogic/browser-tools pueden servirnos


Para ser más claro, cuando digo que nos sirvan digo que quiero una manera simple de habilitarte a navegar como lo haría yo para que tengas más flexibilidad y puedas navegar páginas complejas en caso de tener que hacer research y más importante navegar frontends desarrollados por nosotros para validar que un feature of fix está bien terminado


**Evaluating options for documentation**

I need to evaluate both options at a high level, and possibly consult some documentation for further insight. I'm considering whether to use a specific skill or if that's unnecessary. I might look into URLs for more details or consider using bash to read with curl or even look at Playwright for testing. Ultimately, I'll decide based on what makes the most sense for the task at hand.

bash

python - <<'PY'
import requests
for url in ['https://docs.factory.ai/guides/skills/browser','https://raw.githubusercontent.com/badlogic/browser-tools/master/README.md']:
    print('\n### URL',url)
    try:
        r=requests.get(url,timeout=20)
        print(r.status_code)
        print(r.text[:12000])
    except Exception as e:
        print('ERR',e)
PY

python - <<'PY'
import requests
url='https://api.github.com/repos/badlogic/browser-tools'
r=requests.get(url,timeout=20,headers={'Accept':'application/vnd.github+json'})
print(r.status_code)
print(r.text[:4000])
PY


### URL https://docs.factory.ai/guides/skills/browser
200
<!DOCTYPE html><html data-dpl-id="dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" lang="en" class="inter_1d81deff-module__CYM0aG__variable jetbrains_mono_e9ee6d28-module__lbFCHq__variable dark" data-banner-state="visible" data-assistant-state="closed" data-page-mode="none" data-current-path="/"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/><link rel="preload" as="image" href="https://mintcdn.com/factory/Cg4J4ONZBI7tMpyl/logo/light.png?fit=max&auto=format&n=Cg4J4ONZBI7tMpyl&q=85&s=4fa674d970abbff40c8942224d57ff2b"/><link rel="preload" as="image" href="https://mintcdn.com/factory/Z_WhY6TucToAS9jj/logo/dark.svg?fit=max&auto=format&n=Z_WhY6TucToAS9jj&q=85&s=b5dba2b99782ec6106129de081102d0c"/><link rel="preload" as="image" href="https://d3gk2c5xim1je2.cloudfront.net/flags/US.svg"/><link rel="stylesheet" href="/mintlify-assets/_next/static/chunks/0a02nq8a5daeg.css?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" data-precedence="next"/><link rel="stylesheet" href="/mintlify-assets/_next/static/chunks/0i2o85t7b~jum.css?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" data-precedence="next"/><link rel="stylesheet" href="/mintlify-assets/_next/static/chunks/03n-yw_fhpj7-.css?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/mintlify-assets/_next/static/chunks/0x6dpcr6zid4k.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM"/><script src="/mintlify-assets/_next/static/chunks/083vprw7tkvz9.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/05s_9sbgi~nbe.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/15bu6-mevxw5..js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/turbopack-107zl~6sgq900.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/0to_.48too1tz.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/133z7.radauf..js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/0d_-.bh3fer20.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/0jq1hfz.-mp9~.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/109nrjrhn2iar.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/07-d-j~s-fuv~.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/02qus5hw1hewj.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/11fp8sbtiez-k.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/0h_mv45z6s0vm.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/086gf8e6gkh98.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/0l8vjh8ien28l.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/0.9y3qhahv_.4.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/0s~usrayg3pky.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/09yy~b3yhpk5o.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/0c_njkjtr_v5..js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/0x2nmw8~h5nwg.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/0b3ciz2ohd7t4.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/05.vot95fmkt_.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/0wk6qa71nry5p.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/17~qyet9dsal6.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/0b71tr3f..tjj.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/0~fdokq7.lnyv.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/13p_xp_nojwqx.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/12dqephq_do-8.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/06egcq~fevvku.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/0nyqm_x_7t7j6.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/0~28fjs.fzp5-.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/0an5dlw0fxvjb.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/07ee3ha.0ssn0.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/0gn1411zbnajc.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/01h.4u3ca7.xc.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/0hv542w-9bqp2.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><script src="/mintlify-assets/_next/static/chunks/0_y7nd5c2f-45.js?dpl=dpl_6kqmg3d9CWLB5GtiG5yGmMMeKyrM" async=""></script><title>Browser Automation - Factory Documentation</title><meta name="description" content="Minimal Chrome DevTools Protocol helpers that let Droids start Chrome, navigate tabs, evaluate JavaScript, take screenshots, and capture DOM metadata without building a full MCP server."/><meta name="application-name" content="Factory Documentation"/><meta name="generator" content="Mintlify"/><meta name="msapplication-config" content="/mintlify-assets/_mintlify/favicons/factory/s_hUmgkAuf1_JqOB/_generated/favicon/browserconfig.xml"/><meta name="apple-mobile-web-app-title" content="Factory Documentation"/><meta name="msapplication-TileColor" content="#020202"/><meta name="charset" content="utf-8"/><meta name="og:site_name" content="Factory Documentation"/><meta name="canonical" content="https://docs.factory.ai/guides/skills/browser"/><link rel="canonical" href="https://docs.factory.ai/guides/skills/browser"/><link rel="alternate" type="application/xml" href="/sitemap.xml"/><meta property="og:title" content="Browser Automation - Factory Documentation"/><meta property="og:description" content="Minimal Chrome DevTools Protocol helpers that let Droids start Chrome, navigate tabs, evaluate JavaScript, take screenshots, and capture DOM metadata without building a full MCP server."/><meta property="og:url" content="https://docs.factory.ai/guides/skills/browser"/><meta property="og:image" content="https://factory.mintlify.app/mintlify-assets/_next/image?url=%2F_mintlify%2Fapi%2Fog%3Fdivision%3DSkills%26title%3DBrowser%2BAutomation%26description%3DMinimal%2BChrome%2BDevTools%2BProtocol%2Bhelpers%2Bthat%2Blet%2BDroids%2Bstart%2BChrome%252C%2Bnavigate%2Btabs%252C%2Bevaluate%2BJavaScript%252C%2Btake%2Bscreenshots%252C%2Band%2Bcapture%2BDOM%2Bmetadata%2Bwithout%2Bbu%26logoLight%3Dhttps%253A%252F%252Fmintcdn.com%252Ffactory%252FCg4J4ONZBI7tMpyl%252Flogo%252Flight.png%253Ffit%253Dmax%2526auto%253Dformat%2526n%253DCg4J4ONZBI7tMpyl%2526q%253D85%2526s%253D4fa674d970abbff40c8942224d57ff2b%26logoDark%3Dhttps%253A%252F%252Fmintcdn.com%252Ffactory%252FZ_WhY6TucToAS9jj%252Flogo%252Fdark.svg%253Ffit%253Dmax%2526auto%253Dformat%2526n%253DZ_WhY6TucToAS9jj%2526q%253D85%2526s%253Db5dba2b99782ec6106129de081102d0c%26primaryColor%3D%2523020202%26lightColor%3D%2523f8f8f8%26darkColor%3D%2523020202%26backgroundLight%3D%2523ffffff%26backgroundDark%3D%25230e0e10&w=1200&q=100"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:type" content="website"/><meta name="twitter:card" content="summary_large_image"/><meta name="twitter:title" content="Browser Automation - Factory Documentation"/><meta name="twitter:description" content="Minimal Chrome DevTools Protocol helpers that let Droids start Chrome, navigate tabs, evaluate JavaScript, take screenshots, and capture DOM metadata without building a full MCP server."/><meta name="twitter:image" content="https://factory.mintlify.app/mintlify-assets/_next/image?url=%2F_mintlify%2Fapi%2Fog%3Fdivision%3DSkills%26title%3DBrowser%2BAutomation%26description%3DMinimal%2BChrome%2BDevTools%2BProtocol%2Bhelpers%2Bthat%2Blet%2BDroids%2Bstart%2BChrome%252C%2Bnavigate%2Btabs%252C%2Bevaluate%2BJavaScript%252C%2Btake%2Bscreenshots%252C%2Band%2Bcapture%2BDOM%2Bmetadata%2Bwithout%2Bbu%26logoLight%3Dhttps%253A%252F%252Fmintcdn.com%252Ffactory%252FCg4J4ONZBI7tMpyl%252Flogo%252Flight.png%253Ffit%253Dmax%2526auto%253Dformat%2526n%253DCg4J4ONZBI7tMpyl%2526q%253D85%2526s%253D4fa674d970abbff40c8942224d57ff2b%26logoDark%3Dhttps%253A%252F%252Fmintcdn.com%252Ffactory%252FZ_WhY6TucToAS9jj%252Flogo%252Fdark.svg%253Ffit%253Dmax%2526auto%253Dformat%2526n%253DZ_WhY6TucToAS9jj%2526q%253D85%2526s%253Db5dba2b99782ec6106129de081102d0c%26primaryColor%3D%2523020202%26lightColor%3D%2523f8f8f8%26darkColor%3D%2523020202%26backgroundLight%3D%2523ffffff%26backgroundDark%3D%25230e0e10&w=1200&q=100"/><meta name="twitter:image:width" content="1200"/><meta name="twitter:image:height" content="630"/><link rel="apple-touch-icon" href="/mintlify-assets/_mintlify/favicons/factory/s_hUmgkAuf1_JqOB/_generated/favicon/apple-touch-icon.png" type="image/png" sizes="180x180"/><link rel="icon" href="/mintlify-assets/_mintlify/favicons/factory/s_hUmgkAuf1_JqOB/_generated/favicon/favicon-16x16.png" type="image/png" sizes="16x16" media="(prefers-color-scheme: light)"/><link rel="icon" href="/mintlify-assets/_mintlify/favicons/factory/s_hUmgkAuf1_JqOB/_generated/favicon/favicon-32x32.png" type="image/png" sizes="32x32" media="(prefers-color-scheme: light)"/><link rel="shortcut icon" href="/mintlify-assets/_mintlify/favicons/factory/s_hUmgkAuf1_JqOB/_generated/favicon/favicon.ico" type="image/x-icon" media="(prefers-color-scheme: light)"/><link rel="icon" href="/mintlify-assets/_mintlify/favicons/factory/s_hUmgkAuf1_JqOB/_generated/favicon-dark/favicon-16x16.png" type="image/png" sizes="16x16" media="(prefers-color-scheme: dark)"/><link rel="icon" href="/mintlify-assets/_mintlify/favicons/factory/s_hUmgkAuf1_JqOB/_generated/favicon-dark/favicon-32x32.png" type="image/png" sizes="32x32" media="(prefers-color-scheme: dark)"/><link rel="shortcut icon" href="/mintlify-assets/_mintlify/favicons/factory/s_hUmgkAuf1_JqOB/_generated/favicon-dark/favicon.ico" type="image/x-icon" media="(prefers-color-scheme: dark)"/><script type="text/javascript">!function(){var b="";
function a(p){if(p==null)return"/";p=""+p;if(""===p)return"/";return"/"===p.charAt(0)?p:"/"+p}
function u(p){if(p==null)return p;p=""+p;if(!p||p.charAt(p.length-1)==="/")return p.slice(0,-1);return p}
function i(p){if(p==null)return p;p=""+p;if(6<=p.length&&p.substring(p.length-6)==="/index")return p.substring(0,p.length-6);if("index"===p)return"";return p}
var p=(location.pathname||"").split("?")[0].split("#")[0]||"";
if(b)if(p===b)p="";else if(0===p.indexOf(b+"/"))p=p.substring(b.length);
p=a(p);p=u(p);p=i(p);p=""===p||"index"===p?"/":a(p);
document.documentElement.setAttribute("data-current

### URL https://raw.githubusercontent.com/badlogic/browser-tools/master/README.md
200
> **⚠ DEPRECATED**: This repository has been moved to [badlogic/agent-tools](https://github.com/badlogic/agent-tools). Please use that repository instead.

---

# Browser Tools

Chrome DevTools Protocol tools for agent-assisted web automation. These tools connect to Chrome running on `:9222` with remote debugging enabled.

## How to Invoke These Tools

**CRITICAL FOR AGENTS**: These are executable scripts in your PATH. When invoking via the Bash tool:

✓ CORRECT:
```bash
browser-start.js
browser-nav.js https://example.com
browser-pick.js "Click the button"
```

✗ INCORRECT:
```bash
node browser-start.js        # Don't use 'node' prefix
./browser-start.js           # Don't use './' prefix
```

## Start Chrome

```bash
browser-start.js              # Fresh profile
browser-start.js --profile    # Copy user's profile (cookies, logins)
```

Launch Chrome with remote debugging on `:9222`. Use `--profile` to preserve user's authentication state.

## Navigate

```bash
browser-nav.js https://example.com
browser-nav.js https://example.com --new
```

Navigate to URLs. Use `--new` flag to open in a new tab instead of reusing current tab.

## Evaluate JavaScript

```bash
browser-eval.js 'document.title'
browser-eval.js 'document.querySelectorAll("a").length'
```

Execute JavaScript in the active tab. Code runs in async context. Use this to extract data, inspect page state, or perform DOM operations programmatically.

## Screenshot

```bash
browser-screenshot.js
```

Capture current viewport and return temporary file path. Use this to visually inspect page state or verify UI changes.

## Pick Elements

```bash
browser-pick.js "Click the submit button"
```

**IMPORTANT**: Use this tool when the user wants to select specific DOM elements on the page. This launches an interactive picker that lets the user click elements to select them. The user can select multiple elements (Cmd/Ctrl+Click) and press Enter when done. The tool returns CSS selectors for the selected elements.

Common use cases:
- User says "I want to click that button" → Use this tool to let them select it
- User says "extract data from these items" → Use this tool to let them select the elements
- When you need specific selectors but the page structure is complex or ambiguous

## Cookies

```bash
browser-cookies.js
```

Display all cookies for the current tab including domain, path, httpOnly, and secure flags. Use this to debug authentication issues or inspect session state.

## Search Google

```bash
browser-search.js "rust programming"
browser-search.js "climate change" -n 10
browser-search.js "machine learning" -n 3 --content
```

Search Google and return results. Options:
- `-n <num>` - Number of results (default: 5, supports pagination for higher numbers)
- `--content` - Fetch and extract readable content as markdown from each result

## Extract Page Content

```bash
browser-content.js https://example.com
```

Navigate to a URL and extract readable content as markdown. Uses Mozilla Readability for article extraction and Turndown for HTML-to-markdown conversion. Works on pages with JavaScript content (waits for page to load).

## Search + Content Strategies

**Use `browser-search.js --content`** when you want content from all search results in one go. This is faster but fetches content from possibly irrelevant results.

```bash
browser-search.js "climate change effects" -n 3 --content
```

**Use `browser-search.js` + `browser-content.js`** when you want to selectively fetch content from only relevant results. First search, review the titles/snippets, then fetch content only for promising URLs.

```bash
browser-search.js "climate change effects" -n 10
# Review results, then fetch specific ones:
browser-content.js https://relevant-article.com
browser-content.js https://another-good-source.com
```


**Exploring factory docs**

I need to look more closely at the factory documentation. Maybe I can use tools like readability or grep to find minimal descriptions. There's a chance there’s raw markdown content hidden somewhere or maybe the page has JSON data. I wonder if I could leverage lynx for this. Using Python with Beautiful Soup to strip and extract the main text might also be effective. Let’s see what I can uncover!

python - <<'PY'
import requests, bs4
url='https://docs.factory.ai/guides/skills/browser'
html=requests.get(url,timeout=20).text
soup=bs4.BeautifulSoup(html,'html.parser')
for tag in soup(['script','style','noscript']):
    tag.decompose()
text='\n'.join([t.strip() for t in soup.get_text('\n').splitlines() if t.strip()])
print(text[:12000])
PY


Browser Automation - Factory Documentation
Skip to main content
Factory Documentation
home page
English
Search...
⌘
K
Search...
Navigation
Skills
Browser Automation
Docs
Guides
Changelog
Benchmarks
API Reference
Power User
Power User Setup Checklist
Memory and Context Management
Rules and Conventions
Prompt Crafting for Different Models
Token Efficiency Strategies
Evaluating Context Compression
Building
Building Plugins
Building Interactive Apps with Droid Exec
Skills
Frontend UI Integration
Service Integration
Data Querying
Internal Tools
Vibe Coding
AI Data Analyst
Product Management
Browser Automation
Hooks
Auto-formatting Code
Code Validation Hooks
Documentation Sync
Git Workflow Hooks
Logging and Analytics
Custom Notifications
Session Automation
Testing Automation
Droid Exec (Headless)
Organize Imports
Improve Error Messages
Automated Lint Fixes
Automated Documentation
GitHub Actions
Deploy Droid on a VPS Server
On this page
When to use this skill
What the scripts provide
Setup
Skill definition
Skills
Browser Automation
Copy page
Minimal Chrome DevTools Protocol helpers that let Droids start Chrome, navigate tabs, evaluate JavaScript, take screenshots, and capture DOM metadata without building a full MCP server.
Copy page
Documentation Index
Fetch the complete documentation index at:
https://docs.factory.ai/llms.txt
Use this file to discover all available pages before exploring further.
This cookbook is inspired by
Mario Zechner’s “What if you don’t need
MCP?”
,
adapted into a reusable Factory skill so teams can share lightweight browser
helpers without standing up new services.
The browser skill bundles a handful of executable scripts (
start.js
,
nav.js
,
eval.js
,
screenshot.js
,
pick.js
) plus a concise
SKILL.md
. Together they give Factory Droids a reliable way to spin up Chrome on port
9222
, drive an existing tab, scrape structured data, and capture visual evidence while staying entirely on the developer machine.

When to use this skill
You need
real-browser context
(authenticated sessions, production-only behavior, visual regressions) to complete a task.
You want to
inspect or extract DOM state
without building a dedicated MCP server.
You must
capture screenshots or DOM element metadata
as part of QA notes, bug triage, or documentation.
You prefer a
portable, git-tracked bundle
that any teammate can run locally with zero additional infrastructure.

What the scripts provide
Script
Purpose
Typical usage
start.js
Launches Chrome with remote debugging on
:9222
, with optional profile sync via
--profile
.
~/.factory/skills/browser/start.js --profile
nav.js
Navigates the active tab or opens a new tab when
--new
is passed.
~/.factory/skills/browser/nav.js https://example.com --new
eval.js
Runs arbitrary JavaScript (async supported) in the focused tab and prints structured results.
~/.factory/skills/browser/eval.js "document.title"
screenshot.js
Captures the current viewport to a timestamped PNG stored under
$TMPDIR
.
~/.factory/skills/browser/screenshot.js
pick.js
Injects a visual picker so you can click DOM nodes and return tag/id/class/text metadata.
~/.factory/skills/browser/pick.js "Click the submit button"
All scripts rely on
puppeteer-core
and connect to a Chrome instance that you control. Because everything runs locally, existing cookies and auth tokens never leave your machine.

Setup
1
Create the skill folder
Run
mkdir -p .factory/skills/browser
.
2
Copy scripts and package metadata
Copy
start.js
,
nav.js
,
eval.js
,
screenshot.js
,
pick.js
, and
package.json
into
.factory/skills/browser/
(or symlink to a shared
dotfiles repo).
3
Install dependencies
Run
npm install --prefix .factory/skills/browser puppeteer-core
, then
chmod +x .factory/skills/browser/*.js
.
4
Restart Droid
Restart
droid
(or your IDE integration) so it rescans workspace skills and
discovers
browser
.

Skill definition
Copy the following into
.factory/skills/browser/SKILL.md
:
---
name
:
browser
description
:
Minimal Chrome DevTools Protocol tools for browser automation and scraping. Use when you need to start Chrome, navigate pages, execute JavaScript, take screenshots, or interactively pick DOM elements.
---
# Browser Tools
Minimal CDP tools for collaborative site exploration and scraping.
**IMPORTANT**
: All scripts are located in
`~/.factory/skills/browser/`
and must be called with full paths.
## Start Chrome
```bash
~
/.factory/skills/browser/start.js
# Fresh profile
~
/.factory/skills/browser/start.js --profile
# Copy your profile (cookies, logins)
```
Start Chrome on
`:9222`
with remote debugging.
## Navigate
```bash
~
/.factory/skills/browser/nav.js https://example.com
~
/.factory/skills/browser/nav.js https://example.com --new
```
Navigate current tab or open new tab.
## Evaluate JavaScript
```bash
~
/.factory/skills/browser/eval.js
'document.title'
~
/.factory/skills/browser/eval.js
'document.querySelectorAll("a").length'
```
Execute JavaScript in active tab (async context).
**IMPORTANT**
: The code must be a single expression or use IIFE for multiple statements:
-
Single expression:
`'document.title'`
-
Multiple statements:
`'(() => { const x = 1; return x + 1; })()'`
-
Avoid newlines in the code string - keep it on one line
## Screenshot
```bash
~
/.factory/skills/browser/screenshot.js
```
Screenshot current viewport, returns temp file path.
## Pick Elements
```bash
~
/.factory/skills/browser/pick.js
"Click the submit button"
```
Interactive element picker. Click to select, Cmd/Ctrl+Click for multi-select, Enter to finish.
## Usage Notes
-
Start Chrome first before using other tools
-
The
`--profile`
flag syncs your actual Chrome profile so you're logged in everywhere
-
JavaScript evaluation runs in an async context in the page
-
Pick tool allows you to visually select DOM elements by clicking on them
```
## Workflow recipe
1. **Start Chrome** with `start.js --profile` to mirror your authenticated state.
2. **Drive navigation** via `nav.js https://target.app` or open secondary tabs with `--new`.
3. **Inspect the DOM** using `eval.js` for quick counts, attribute checks, or extracting JSON payloads.
4. **Capture artifacts** with `screenshot.js` for visual proof or `pick.js` when you need precise selectors or text snapshots.
5. **Return the gathered evidence** (file paths, DOM metadata, query outputs) in your session summary or PR description.
This workflow keeps the agent focused on the current browsing context and avoids shipping raw credentials or cookies outside your machine.
## Verification
- `~/.factory/skills/browser/start.js --profile` should print `✓ Chrome started on :9222 with your profile`.
- `~/.factory/skills/browser/nav.js https://example.com` should confirm navigation.
- `~/.factory/skills/browser/eval.js 'document.title'` should echo the current page title.
- `~/.factory/skills/browser/screenshot.js` should output a valid PNG path under your system temp directory.
If any step fails, rerun `start.js`, confirm Chrome is listening on `localhost:9222/json/version`, and ensure `puppeteer-core` is installed.
```
Product Management
Previous
Auto-formatting Code
Next
⌘
I
github
discord
twitter
linkedin
Powered by
This documentation is built and hosted on Mintlify, a developer documentation platform


**Evaluating suitability**

I need to evaluate the suitability of existing solutions. It seems like the playwright-mcp already takes care of complex navigation and frontend validation. I should compare what's currently available with what the user is looking for to see if it's a good fit. It's important to make sure that whatever I suggest is relevant and effective for their needs. Let's see what the options are and weigh them appropriately!

Sí. Los dos pueden servir, pero para tu objetivo no igual.

Mi lectura:

## Qué querés de verdad
Una capa simple para que yo pueda:

- navegar webs complejas
- usar sesiones/login reales
- inspeccionar DOM y estado
- sacar screenshots
- validar frontend nuestro end-to-end
- hacerlo sin mucho overhead

## Opción 1: Factory browser skill
`https://docs.factory.ai/guides/skills/browser`

Sirve si querés algo **mínimo y fácil de adoptar**.

Qué da:
- abrir Chrome con remote debugging
- navegar tabs
- correr JS en página
- screenshots
- picker visual de elementos
- usar tu perfil real de Chrome

Pros:
- simple
- local
- sin levantar servidor MCP
- bueno para research y QA manual-asistido
- bueno para apps nuestras si ya están andando

Contras:
- es una capa chica, no un framework serio de test
- poca estructura para flujos largos/reintentos/assertions
- menos robusto para automatización repetible
- más cómodo para “explorar” que para “validar bien terminado” de forma sistemática

## Opción 2: badlogic/browser-tools
`https://github.com/badlogic/browser-tools`

También sirve, pero ojo:
- ese repo está **deprecated**
- movido a `badlogic/agent-tools`

Qué agrega sobre Factory:
- además de nav/eval/screenshot
- search de Google
- extracción de contenido legible
- cookies
- más orientado a agent browsing/research

Pros:
- mejor para research web abierto
- útil para scraping liviano y lectura
- muy alineado con “dejame navegar como humano”

Contras:
- deprecated repo original
- igual sigue siendo una capa de scripts sobre Chrome/CDP
- para QA de frontend nuestro sigue siendo más “toolbox” que “test harness”

## Para tu caso: recomendación
Yo haría esto:

### 1. Base principal: Playwright
Para validar features/fixes en frontends nuestros.

Porque:
- más robusto
- mejor waits/assertions
- reproducible
- bueno para flows complejos
- bueno para CI después si querés

Ya tenemos `playwright-mcp` CLI disponible.
Eso probablemente ya cubre gran parte de lo que querés.

### 2. Capa auxiliar: browser-tools estilo CDP
Para research y sesiones humanas reales.

Ideal para:
- navegar docs raras
- páginas con login ya resuelto en tu Chrome
- inspección rápida
- screenshots/manual verification
- explorar un frontend sin armar test formal

## Veredicto corto

Si querés **simpleza inmediata para browsing flexible**:
- **sí** a una capa tipo Factory/browser-tools

Si querés **validar que un feature/fix quedó bien terminado**:
- **Playwright sigue siendo mejor base**

## Mi recomendación concreta
No instalaría `badlogic/browser-tools` tal cual porque está deprecated.
Si vamos por esa línea:

- o adaptamos el **Factory browser skill**
- o usamos el repo nuevo `badlogic/agent-tools`
- o armamos nuestra versión mínima local con:
  - start chrome
  - nav
  - eval
  - screenshot
  - extract content
  - maybe pick

Eso te da browsing flexible sin meter mucho peso.

## Lo más pragmático
Te propongo este stack:

- **Playwright** = validación seria de frontend
- **CDP browser skill liviano** = research + debugging + usar sesión real de Chrome

Eso cubre casi todo.

Si querés, siguiente paso:
1. evalúo `badlogic/agent-tools` en vez del repo deprecated
2. comparo eso contra usar directo `playwright-mcp`
3. te dejo recomendación final con costo/beneficio e implementación mínima acá en esta máquina.


Y valúan también la opción más nueva de Bad Logic.


python - <<'PY'
import requests
urls=[
 'https://raw.githubusercontent.com/badlogic/agent-tools/master/README.md',
 'https://api.github.com/repos/badlogic/agent-tools',
 'https://api.github.com/repos/badlogic/agent-tools/contents'
]
for url in urls:
    print('\n###',url)
    r=requests.get(url,timeout=25,headers={'Accept':'application/vnd.github+json'})
    print(r.status_code)
    print(r.text[:15000])
PY

python - <<'PY'
import requests
for path in ['package.json','src','README.md']:
    url=f'https://api.github.com/repos/badlogic/agent-tools/contents/{path}'
    r=requests.get(url,timeout=25,headers={'Accept':'application/vnd.github+json'})
    print('\n###',path,r.status_code)
    print(r.text[:12000])
PY


### https://raw.githubusercontent.com/badlogic/agent-tools/master/README.md
200
# ⚠ **NOTE**: this is now superseded by https://github.com/badlogic/pi-skills

# Agent Tools

## Setup

Add each tool directory to your PATH:

```bash
export PATH="$PATH:$HOME/agent-tools/brave-search"
export PATH="$PATH:$HOME/agent-tools/search-tools"
export PATH="$PATH:$HOME/agent-tools/browser-tools"
export PATH="$PATH:$HOME/agent-tools/vscode"
```

Add these lines to your shell config (e.g., `~/.bashrc`, `~/.zshrc`) to make them permanent.

## brave-search (recommended)
Headless web search via Brave Search. No browser required, works in server environments. See [brave-search/README.md](brave-search/README.md).

## search-tools
Google search via Puppeteer + Chrome. More accurate results but requires local Chrome, frequently hits CAPTCHAs, can't run headless on servers. See [search-tools/README.md](search-tools/README.md).

**Use brave-search unless you specifically need Google results.**

## browser-tools
Interactive browser automation (requires visible Chrome window). See [browser-tools/README.md](browser-tools/README.md).

## vscode
VS Code integration tools. See [vscode/README.md](vscode/README.md).

### https://api.github.com/repos/badlogic/agent-tools
200
{"id":1102578534,"node_id":"R_kgDOQbgDZg","name":"agent-tools","full_name":"badlogic/agent-tools","private":false,"owner":{"login":"badlogic","id":514052,"node_id":"MDQ6VXNlcjUxNDA1Mg==","avatar_url":"https://avatars.githubusercontent.com/u/514052?v=4","gravatar_id":"","url":"https://api.github.com/users/badlogic","html_url":"https://github.com/badlogic","followers_url":"https://api.github.com/users/badlogic/followers","following_url":"https://api.github.com/users/badlogic/following{/other_user}","gists_url":"https://api.github.com/users/badlogic/gists{/gist_id}","starred_url":"https://api.github.com/users/badlogic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/badlogic/subscriptions","organizations_url":"https://api.github.com/users/badlogic/orgs","repos_url":"https://api.github.com/users/badlogic/repos","events_url":"https://api.github.com/users/badlogic/events{/privacy}","received_events_url":"https://api.github.com/users/badlogic/received_events","type":"User","user_view_type":"public","site_admin":false},"html_url":"https://github.com/badlogic/agent-tools","description":"CLI tools for coding agents","fork":false,"url":"https://api.github.com/repos/badlogic/agent-tools","forks_url":"https://api.github.com/repos/badlogic/agent-tools/forks","keys_url":"https://api.github.com/repos/badlogic/agent-tools/keys{/key_id}","collaborators_url":"https://api.github.com/repos/badlogic/agent-tools/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/badlogic/agent-tools/teams","hooks_url":"https://api.github.com/repos/badlogic/agent-tools/hooks","issue_events_url":"https://api.github.com/repos/badlogic/agent-tools/issues/events{/number}","events_url":"https://api.github.com/repos/badlogic/agent-tools/events","assignees_url":"https://api.github.com/repos/badlogic/agent-tools/assignees{/user}","branches_url":"https://api.github.com/repos/badlogic/agent-tools/branches{/branch}","tags_url":"https://api.github.com/repos/badlogic/agent-tools/tags","blobs_url":"https://api.github.com/repos/badlogic/agent-tools/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/badlogic/agent-tools/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/badlogic/agent-tools/git/refs{/sha}","trees_url":"https://api.github.com/repos/badlogic/agent-tools/git/trees{/sha}","statuses_url":"https://api.github.com/repos/badlogic/agent-tools/statuses/{sha}","languages_url":"https://api.github.com/repos/badlogic/agent-tools/languages","stargazers_url":"https://api.github.com/repos/badlogic/agent-tools/stargazers","contributors_url":"https://api.github.com/repos/badlogic/agent-tools/contributors","subscribers_url":"https://api.github.com/repos/badlogic/agent-tools/subscribers","subscription_url":"https://api.github.com/repos/badlogic/agent-tools/subscription","commits_url":"https://api.github.com/repos/badlogic/agent-tools/commits{/sha}","git_commits_url":"https://api.github.com/repos/badlogic/agent-tools/git/commits{/sha}","comments_url":"https://api.github.com/repos/badlogic/agent-tools/comments{/number}","issue_comment_url":"https://api.github.com/repos/badlogic/agent-tools/issues/comments{/number}","contents_url":"https://api.github.com/repos/badlogic/agent-tools/contents/{+path}","compare_url":"https://api.github.com/repos/badlogic/agent-tools/compare/{base}...{head}","merges_url":"https://api.github.com/repos/badlogic/agent-tools/merges","archive_url":"https://api.github.com/repos/badlogic/agent-tools/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/badlogic/agent-tools/downloads","issues_url":"https://api.github.com/repos/badlogic/agent-tools/issues{/number}","pulls_url":"https://api.github.com/repos/badlogic/agent-tools/pulls{/number}","milestones_url":"https://api.github.com/repos/badlogic/agent-tools/milestones{/number}","notifications_url":"https://api.github.com/repos/badlogic/agent-tools/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/badlogic/agent-tools/labels{/name}","releases_url":"https://api.github.com/repos/badlogic/agent-tools/releases{/id}","deployments_url":"https://api.github.com/repos/badlogic/agent-tools/deployments","created_at":"2025-11-23T17:56:59Z","updated_at":"2026-05-05T06:46:05Z","pushed_at":"2025-12-18T15:29:54Z","git_url":"git://github.com/badlogic/agent-tools.git","ssh_url":"git@github.com:badlogic/agent-tools.git","clone_url":"https://github.com/badlogic/agent-tools.git","svn_url":"https://github.com/badlogic/agent-tools","homepage":null,"size":57,"stargazers_count":171,"watchers_count":171,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"has_discussions":false,"forks_count":17,"mirror_url":null,"archived":true,"disabled":false,"open_issues_count":2,"license":null,"allow_forking":true,"is_template":false,"web_commit_signoff_required":false,"has_pull_requests":true,"pull_request_creation_policy":"all","topics":[],"visibility":"public","forks":17,"open_issues":2,"watchers":171,"default_branch":"main","temp_clone_token":null,"network_count":17,"subscribers_count":4}

### https://api.github.com/repos/badlogic/agent-tools/contents
200
[{"name":"README.md","path":"README.md","sha":"2da1f0e5f850583336c740a7e9e41ca8151aa216","size":1128,"url":"https://api.github.com/repos/badlogic/agent-tools/contents/README.md?ref=main","html_url":"https://github.com/badlogic/agent-tools/blob/main/README.md","git_url":"https://api.github.com/repos/badlogic/agent-tools/git/blobs/2da1f0e5f850583336c740a7e9e41ca8151aa216","download_url":"https://raw.githubusercontent.com/badlogic/agent-tools/main/README.md","type":"file","_links":{"self":"https://api.github.com/repos/badlogic/agent-tools/contents/README.md?ref=main","git":"https://api.github.com/repos/badlogic/agent-tools/git/blobs/2da1f0e5f850583336c740a7e9e41ca8151aa216","html":"https://github.com/badlogic/agent-tools/blob/main/README.md"}},{"name":"brave-search","path":"brave-search","sha":"f7c78c7b0c0e1334c2a56644dcb71c576332c8cd","size":0,"url":"https://api.github.com/repos/badlogic/agent-tools/contents/brave-search?ref=main","html_url":"https://github.com/badlogic/agent-tools/tree/main/brave-search","git_url":"https://api.github.com/repos/badlogic/agent-tools/git/trees/f7c78c7b0c0e1334c2a56644dcb71c576332c8cd","download_url":null,"type":"dir","_links":{"self":"https://api.github.com/repos/badlogic/agent-tools/contents/brave-search?ref=main","git":"https://api.github.com/repos/badlogic/agent-tools/git/trees/f7c78c7b0c0e1334c2a56644dcb71c576332c8cd","html":"https://github.com/badlogic/agent-tools/tree/main/brave-search"}},{"name":"browser-tools","path":"browser-tools","sha":"3f016f4fe66446d2214e8eb2cc368cd137d9679c","size":0,"url":"https://api.github.com/repos/badlogic/agent-tools/contents/browser-tools?ref=main","html_url":"https://github.com/badlogic/agent-tools/tree/main/browser-tools","git_url":"https://api.github.com/repos/badlogic/agent-tools/git/trees/3f016f4fe66446d2214e8eb2cc368cd137d9679c","download_url":null,"type":"dir","_links":{"self":"https://api.github.com/repos/badlogic/agent-tools/contents/browser-tools?ref=main","git":"https://api.github.com/repos/badlogic/agent-tools/git/trees/3f016f4fe66446d2214e8eb2cc368cd137d9679c","html":"https://github.com/badlogic/agent-tools/tree/main/browser-tools"}},{"name":"search-tools","path":"search-tools","sha":"e7bb13f073230575deaf2f1fe87cda3882457e12","size":0,"url":"https://api.github.com/repos/badlogic/agent-tools/contents/search-tools?ref=main","html_url":"https://github.com/badlogic/agent-tools/tree/main/search-tools","git_url":"https://api.github.com/repos/badlogic/agent-tools/git/trees/e7bb13f073230575deaf2f1fe87cda3882457e12","download_url":null,"type":"dir","_links":{"self":"https://api.github.com/repos/badlogic/agent-tools/contents/search-tools?ref=main","git":"https://api.github.com/repos/badlogic/agent-tools/git/trees/e7bb13f073230575deaf2f1fe87cda3882457e12","html":"https://github.com/badlogic/agent-tools/tree/main/search-tools"}},{"name":"vscode","path":"vscode","sha":"0503bb7af5c77da32f2a72e196f5f8ca64bdb466","size":0,"url":"https://api.github.com/repos/badlogic/agent-tools/contents/vscode?ref=main","html_url":"https://github.com/badlogic/agent-tools/tree/main/vscode","git_url":"https://api.github.com/repos/badlogic/agent-tools/git/trees/0503bb7af5c77da32f2a72e196f5f8ca64bdb466","download_url":null,"type":"dir","_links":{"self":"https://api.github.com/repos/badlogic/agent-tools/contents/vscode?ref=main","git":"https://api.github.com/repos/badlogic/agent-tools/git/trees/0503bb7af5c77da32f2a72e196f5f8ca64bdb466","html":"https://github.com/badlogic/agent-tools/tree/main/vscode"}}]


**Inspecting tools folder**

I need to check the browser-tools folder and the pi-skills. It feels like there’s something important in there that I should look into. Maybe I’ll find resources or data that will help with my tasks. I want to ensure everything is functioning properly and that I have access to what I need. Let’s see what I can uncover once I inspect both locations carefully!

python - <<'PY'
import requests
for path in ['browser-tools','browser-tools/README.md','search-tools/README.md','brave-search/README.md']:
    url=f'https://api.github.com/repos/badlogic/agent-tools/contents/{path}'
    r=requests.get(url,timeout=25,headers={'Accept':'application/vnd.github+json'})
    print('\n###',path,r.status_code)
    print(r.text[:15000])
PY

python - <<'PY'
import requests
urls=[
 'https://raw.githubusercontent.com/badlogic/pi-skills/master/README.md',
 'https://api.github.com/repos/badlogic/pi-skills'
]
for url in urls:
    print('\n###',url)
    r=requests.get(url,timeout=25,headers={'Accept':'application/vnd.github+json'})
    print(r.status_code)
    print(r.text[:15000])
PY


### https://raw.githubusercontent.com/badlogic/pi-skills/master/README.md
200
# pi-skills

A collection of skills for [pi-coding-agent](https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent), compatible with Claude Code, Codex CLI, Amp, and Droid.

## Installation

### pi-coding-agent

```bash
# User-level (available in all projects)
git clone https://github.com/badlogic/pi-skills ~/.pi/agent/skills/pi-skills

# Or project-level
git clone https://github.com/badlogic/pi-skills .pi/skills/pi-skills
```

### Codex CLI

```bash
git clone https://github.com/badlogic/pi-skills ~/.codex/skills/pi-skills
```

### Amp

Amp finds skills recursively in toolboxes:

```bash
git clone https://github.com/badlogic/pi-skills ~/.config/amp/tools/pi-skills
```

### Droid (Factory)

```bash
# User-level
git clone https://github.com/badlogic/pi-skills ~/.factory/skills/pi-skills

# Or project-level
git clone https://github.com/badlogic/pi-skills .factory/skills/pi-skills
```

### Claude Code

Claude Code only looks one level deep for `SKILL.md` files, so each skill folder must be directly under the skills directory. Clone the repo somewhere, then symlink individual skills:

```bash
# Clone to a convenient location
git clone https://github.com/badlogic/pi-skills ~/pi-skills

# Symlink individual skills (user-level)
mkdir -p ~/.claude/skills
ln -s ~/pi-skills/brave-search ~/.claude/skills/brave-search
ln -s ~/pi-skills/browser-tools ~/.claude/skills/browser-tools
ln -s ~/pi-skills/gccli ~/.claude/skills/gccli
ln -s ~/pi-skills/gdcli ~/.claude/skills/gdcli
ln -s ~/pi-skills/gmcli ~/.claude/skills/gmcli
ln -s ~/pi-skills/transcribe ~/.claude/skills/transcribe
ln -s ~/pi-skills/vscode ~/.claude/skills/vscode
ln -s ~/pi-skills/youtube-transcript ~/.claude/skills/youtube-transcript

# Or project-level
mkdir -p .claude/skills
ln -s ~/pi-skills/brave-search .claude/skills/brave-search
ln -s ~/pi-skills/browser-tools .claude/skills/browser-tools
ln -s ~/pi-skills/gccli .claude/skills/gccli
ln -s ~/pi-skills/gdcli .claude/skills/gdcli
ln -s ~/pi-skills/gmcli .claude/skills/gmcli
ln -s ~/pi-skills/transcribe .claude/skills/transcribe
ln -s ~/pi-skills/vscode .claude/skills/vscode
ln -s ~/pi-skills/youtube-transcript .claude/skills/youtube-transcript
```

## Available Skills

| Skill | Description |
|-------|-------------|
| [brave-search](brave-search/SKILL.md) | Web search and content extraction via Brave Search |
| [browser-tools](browser-tools/SKILL.md) | Interactive browser automation via Chrome DevTools Protocol |
| [gccli](gccli/SKILL.md) | Google Calendar CLI for events and availability |
| [gdcli](gdcli/SKILL.md) | Google Drive CLI for file management and sharing |
| [gmcli](gmcli/SKILL.md) | Gmail CLI for email, drafts, and labels |
| [transcribe](transcribe/SKILL.md) | Speech-to-text transcription via Groq Whisper API |
| [vscode](vscode/SKILL.md) | VS Code integration for diffs and file comparison |
| [youtube-transcript](youtube-transcript/SKILL.md) | Fetch YouTube video transcripts |

## Skill Format

Each skill follows the pi/Claude Code format:

```markdown
---
name: skill-name
description: Short description shown to agent
---

# Instructions

Detailed instructions here...
Helper files available at: {baseDir}/
```

The `{baseDir}` placeholder is replaced with the skill's directory path at runtime.

## Requirements

Some skills require additional setup. Generally, the agent will walk you through that. But if not, here you go:

- **brave-search**: Requires Node.js. Run `npm install` in the skill directory.
- **browser-tools**: Requires Chrome and Node.js. Run `npm install` in the skill directory.
- **gccli**: Requires Node.js. Install globally with `npm install -g @mariozechner/gccli`.
- **gdcli**: Requires Node.js. Install globally with `npm install -g @mariozechner/gdcli`.
- **gmcli**: Requires Node.js. Install globally with `npm install -g @mariozechner/gmcli`.
- **subagent**: Requires pi-coding-agent. Install globally with `npm install -g @mariozechner/pi-coding-agent`.
- **transcribe**: Requires curl and a Groq API key.
- **vscode**: Requires VS Code with `code` CLI in PATH.
- **youtube-transcript**: Requires Node.js. Run `npm install` in the skill directory.

## License

MIT

### https://api.github.com/repos/badlogic/pi-skills
200
{"id":1115445111,"node_id":"R_kgDOQnxXdw","name":"pi-skills","full_name":"badlogic/pi-skills","private":false,"owner":{"login":"badlogic","id":514052,"node_id":"MDQ6VXNlcjUxNDA1Mg==","avatar_url":"https://avatars.githubusercontent.com/u/514052?v=4","gravatar_id":"","url":"https://api.github.com/users/badlogic","html_url":"https://github.com/badlogic","followers_url":"https://api.github.com/users/badlogic/followers","following_url":"https://api.github.com/users/badlogic/following{/other_user}","gists_url":"https://api.github.com/users/badlogic/gists{/gist_id}","starred_url":"https://api.github.com/users/badlogic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/badlogic/subscriptions","organizations_url":"https://api.github.com/users/badlogic/orgs","repos_url":"https://api.github.com/users/badlogic/repos","events_url":"https://api.github.com/users/badlogic/events{/privacy}","received_events_url":"https://api.github.com/users/badlogic/received_events","type":"User","user_view_type":"public","site_admin":false},"html_url":"https://github.com/badlogic/pi-skills","description":"Skills for pi coding agent (compatible with Claude Code and Codex CLI)","fork":false,"url":"https://api.github.com/repos/badlogic/pi-skills","forks_url":"https://api.github.com/repos/badlogic/pi-skills/forks","keys_url":"https://api.github.com/repos/badlogic/pi-skills/keys{/key_id}","collaborators_url":"https://api.github.com/repos/badlogic/pi-skills/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/badlogic/pi-skills/teams","hooks_url":"https://api.github.com/repos/badlogic/pi-skills/hooks","issue_events_url":"https://api.github.com/repos/badlogic/pi-skills/issues/events{/number}","events_url":"https://api.github.com/repos/badlogic/pi-skills/events","assignees_url":"https://api.github.com/repos/badlogic/pi-skills/assignees{/user}","branches_url":"https://api.github.com/repos/badlogic/pi-skills/branches{/branch}","tags_url":"https://api.github.com/repos/badlogic/pi-skills/tags","blobs_url":"https://api.github.com/repos/badlogic/pi-skills/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/badlogic/pi-skills/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/badlogic/pi-skills/git/refs{/sha}","trees_url":"https://api.github.com/repos/badlogic/pi-skills/git/trees{/sha}","statuses_url":"https://api.github.com/repos/badlogic/pi-skills/statuses/{sha}","languages_url":"https://api.github.com/repos/badlogic/pi-skills/languages","stargazers_url":"https://api.github.com/repos/badlogic/pi-skills/stargazers","contributors_url":"https://api.github.com/repos/badlogic/pi-skills/contributors","subscribers_url":"https://api.github.com/repos/badlogic/pi-skills/subscribers","subscription_url":"https://api.github.com/repos/badlogic/pi-skills/subscription","commits_url":"https://api.github.com/repos/badlogic/pi-skills/commits{/sha}","git_commits_url":"https://api.github.com/repos/badlogic/pi-skills/git/commits{/sha}","comments_url":"https://api.github.com/repos/badlogic/pi-skills/comments{/number}","issue_comment_url":"https://api.github.com/repos/badlogic/pi-skills/issues/comments{/number}","contents_url":"https://api.github.com/repos/badlogic/pi-skills/contents/{+path}","compare_url":"https://api.github.com/repos/badlogic/pi-skills/compare/{base}...{head}","merges_url":"https://api.github.com/repos/badlogic/pi-skills/merges","archive_url":"https://api.github.com/repos/badlogic/pi-skills/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/badlogic/pi-skills/downloads","issues_url":"https://api.github.com/repos/badlogic/pi-skills/issues{/number}","pulls_url":"https://api.github.com/repos/badlogic/pi-skills/pulls{/number}","milestones_url":"https://api.github.com/repos/badlogic/pi-skills/milestones{/number}","notifications_url":"https://api.github.com/repos/badlogic/pi-skills/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/badlogic/pi-skills/labels{/name}","releases_url":"https://api.github.com/repos/badlogic/pi-skills/releases{/id}","deployments_url":"https://api.github.com/repos/badlogic/pi-skills/deployments","created_at":"2025-12-12T22:00:15Z","updated_at":"2026-05-07T20:29:39Z","pushed_at":"2026-02-02T22:08:32Z","git_url":"git://github.com/badlogic/pi-skills.git","ssh_url":"git@github.com:badlogic/pi-skills.git","clone_url":"https://github.com/badlogic/pi-skills.git","svn_url":"https://github.com/badlogic/pi-skills","homepage":null,"size":57,"stargazers_count":1608,"watchers_count":1608,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"has_discussions":false,"forks_count":160,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":22,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"allow_forking":true,"is_template":false,"web_commit_signoff_required":false,"has_pull_requests":true,"pull_request_creation_policy":"all","topics":[],"visibility":"public","forks":160,"open_issues":22,"watchers":1608,"default_branch":"main","temp_clone_token":null,"network_count":160,"subscribers_count":23}


Sí. La opción más nueva de Badlogic cambia la recomendación.

## Hallazgo clave

Cadena actual:

- `browser-tools` original → deprecated
- `agent-tools` → también superseded
- **`pi-skills`** → opción vigente

O sea:
- **no conviene apostar a `browser-tools` viejo**
- **tampoco a `agent-tools` como base**
- si vamos con Badlogic, hoy sería **`pi-skills`**

## Qué trae `pi-skills`
Tiene skills listos para:

- `browser-tools`
  - start Chrome
  - nav
  - eval
  - screenshot
  - pick
  - cookies
  - content extraction
- `brave-search`
  - search headless
  - extracción de contenido
- otras skills no relevantes ahora

## Evaluación por objetivo

### 1. Research web
**Sí sirve.**
Especialmente por:

- `brave-search` para búsqueda simple/headless
- `browser-content` para extraer contenido
- `browser-tools` para páginas con JS o login real

Esto mejora bastante mi flexibilidad para investigación.

### 2. Navegar páginas complejas
**Sí sirve, con límites.**

Bueno para:
- SPA con JS
- sitios con sesión real
- inspección DOM
- screenshots
- extraer datos

Límites:
- sigue siendo una caja de utilidades
- no es el mejor modelo para flujos largos, asserts, retries, espera robusta
- menos fuerte que Playwright para validación sistemática

### 3. Validar frontends nuestros
**Sirve como complemento. No como base única.**

Bueno para:
- smoke checks rápidos
- inspección visual
- debug con tu sesión real
- confirmar “qué está pasando ahora”

No ideal como única solución para:
- “este feature quedó bien terminado”
- checks repetibles
- validación regression-safe
- CI futura

Ahí sigue ganando **Playwright**.

## Comparación corta

### Factory browser skill
- más mínimo
- fácil de copiar
- poca superficie
- bueno si querés algo casero/simple

### Badlogic `pi-skills`
- más completo
- más empaquetado
- comunidad/adopción mayor
- incluye search + browser skill
- más alineado con “darle ojos y manos web al agente”

### Playwright
- mejor para QA real
- mejor para flujos complejos
- mejor para asserts y reproducibilidad

## Recomendación final

Para vos haría esto:

### Base
**Playwright** para validar features/fixes de nuestros frontends.

### Complemento
**Badlogic `pi-skills`**, en especial:
- `browser-tools`
- `brave-search`

## Veredicto
Si la pregunta es:

> “¿Cuál opción habilita mejor navegación tipo humana para research y debug?”

Respuesta:
- **Badlogic `pi-skills`** gana sobre Factory skill por completitud.

Si la pregunta es:

> “¿Cuál usar para validar bien terminado un feature/fix?”

Respuesta:
- **Playwright** sigue siendo la base correcta.

## Mi recomendación operativa
Stack ideal:

- **Playwright** = validación fuerte
- **Badlogic pi-skills** = browsing flexible y research

Eso sí me da la combinación que querés.

## Ojo importante
Como este entorno ya tiene tools y reglas propias, instalar `pi-skills` no garantiza integración perfecta automática.
Hay que ver:

- dónde conviene clonarlo
- cómo exponer esos scripts a este agente
- si lo usamos como skill real o solo como CLIs locales
- si preferís Chrome visible con tu perfil o headless por defecto

## Siguiente paso recomendado
Puedo hacer una de estas dos:

1. **evaluación técnica concreta**
   - revisar cómo instalar `pi-skills` acá
   - ver fricción real con este entorno
   - proponerte setup mínimo

2. **implementación directa**
   - instalar `pi-skills`
   - dejar `browser-tools` y `brave-search`
   - probar contra una web real y contra Ballbox local

Si querés, hago la 1 y te cierro recomendación de implementación en esta máquina.


**Considering integration checks**

I need to evaluate the existing Playwright MCP and assess its integration, which might involve using a to-do list. It’s a bit of a short task, but I should also consider inspecting the PI skill readme or SKMD for further details. This way, I can ensure everything is aligned properly and functioning as expected. Let’s see what insights these documents offer!

command -v playwright-mcp || true
command -v chromium || command -v chromium-browser || command -v google-chrome || true
command -v google-chrome-stable || true
node -v || true
npm -v || true
python -V || true
printf '\nPI dirs:\n'; ls -ld ~/.pi ~/.pi/agent ~/.pi/agent/skills 2>/dev/null || true
printf '\nCodex dirs:\n'; ls -ld ~/.codex ~/.codex/skills 2>/dev/null || true

python - <<'PY'
import requests
for path in ['browser-tools/SKILL.md','brave-search/SKILL.md']:
    url=f'https://raw.githubusercontent.com/badlogic/pi-skills/main/{path}'
    r=requests.get(url,timeout=20)
    print('\n###',path,r.status_code)
    print(r.text[:12000])
PY


v22.22.2
10.9.7
Python 3.13.5

PI dirs:
drwxrwxr-x 5 sebas sebas 4096 May  1 16:06 /home/sebas/.pi
drwxrwxr-x 8 sebas sebas 4096 May  7 18:08 /home/sebas/.pi/agent
drwxr-xr-x 3 sebas sebas 4096 Apr 21 22:15 /home/sebas/.pi/agent/skills

Codex dirs:
drwxr-xr-x 9 sebas sebas 4096 Apr 30 14:45 /home/sebas/.codex
drwxr-xr-x 3 sebas sebas 4096 Apr 30 11:43 /home/sebas/.codex/skills


### browser-tools/SKILL.md 200
---
name: browser-tools
description: Interactive browser automation via Chrome DevTools Protocol. Use when you need to interact with web pages, test frontends, or when user interaction with a visible browser is required.
---

# Browser Tools

Chrome DevTools Protocol tools for agent-assisted web automation. These tools connect to Chrome running on `:9222` with remote debugging enabled.

## Setup

Run once before first use:

```bash
cd {baseDir}/browser-tools
npm install
```

## Start Chrome

```bash
{baseDir}/browser-start.js              # Fresh profile
{baseDir}/browser-start.js --profile    # Copy user's profile (cookies, logins)
```

Launch Chrome with remote debugging on `:9222`. Use `--profile` to preserve user's authentication state.

## Navigate

```bash
{baseDir}/browser-nav.js https://example.com
{baseDir}/browser-nav.js https://example.com --new
```

Navigate to URLs. Use `--new` flag to open in a new tab instead of reusing current tab.

## Evaluate JavaScript

```bash
{baseDir}/browser-eval.js 'document.title'
{baseDir}/browser-eval.js 'document.querySelectorAll("a").length'
```

Execute JavaScript in the active tab. Code runs in async context. Use this to extract data, inspect page state, or perform DOM operations programmatically.

## Screenshot

```bash
{baseDir}/browser-screenshot.js
```

Capture current viewport and return temporary file path. Use this to visually inspect page state or verify UI changes.

## Pick Elements

```bash
{baseDir}/browser-pick.js "Click the submit button"
```

**IMPORTANT**: Use this tool when the user wants to select specific DOM elements on the page. This launches an interactive picker that lets the user click elements to select them. The user can select multiple elements (Cmd/Ctrl+Click) and press Enter when done. The tool returns CSS selectors for the selected elements.

Common use cases:
- User says "I want to click that button" → Use this tool to let them select it
- User says "extract data from these items" → Use this tool to let them select the elements
- When you need specific selectors but the page structure is complex or ambiguous

## Cookies

```bash
{baseDir}/browser-cookies.js
```

Display all cookies for the current tab including domain, path, httpOnly, and secure flags. Use this to debug authentication issues or inspect session state.

## Extract Page Content

```bash
{baseDir}/browser-content.js https://example.com
```

Navigate to a URL and extract readable content as markdown. Uses Mozilla Readability for article extraction and Turndown for HTML-to-markdown conversion. Works on pages with JavaScript content (waits for page to load).

## When to Use

- Testing frontend code in a real browser
- Interacting with pages that require JavaScript
- When user needs to visually see or interact with a page
- Debugging authentication or session issues
- Scraping dynamic content that requires JS execution

---

## Efficiency Guide

### DOM Inspection Over Screenshots

**Don't** take screenshots to see page state. **Do** parse the DOM directly:

```javascript
// Get page structure
document.body.innerHTML.slice(0, 5000)

// Find interactive elements
Array.from(document.querySelectorAll('button, input, [role="button"]')).map(e => ({
  id: e.id,
  text: e.textContent.trim(),
  class: e.className
}))
```

### Complex Scripts in Single Calls

Wrap everything in an IIFE to run multi-statement code:

```javascript
(function() {
  // Multiple operations
  const data = document.querySelector('#target').textContent;
  const buttons = document.querySelectorAll('button');

  // Interactions
  buttons[0].click();

  // Return results
  return JSON.stringify({ data, buttonCount: buttons.length });
})()
```

### Batch Interactions

**Don't** make separate calls for each click. **Do** batch them:

```javascript
(function() {
  const actions = ["btn1", "btn2", "btn3"];
  actions.forEach(id => document.getElementById(id).click());
  return "Done";
})()
```

### Typing/Input Sequences

```javascript
(function() {
  const text = "HELLO";
  for (const char of text) {
    document.getElementById("key-" + char).click();
  }
  document.getElementById("submit").click();
  return "Submitted: " + text;
})()
```

### Reading App/Game State

Extract structured state in one call:

```javascript
(function() {
  const state = {
    score: document.querySelector('.score')?.textContent,
    status: document.querySelector('.status')?.className,
    items: Array.from(document.querySelectorAll('.item')).map(el => ({
      text: el.textContent,
      active: el.classList.contains('active')
    }))
  };
  return JSON.stringify(state, null, 2);
})()
```

### Waiting for Updates

If DOM updates after actions, add a small delay with bash:

```bash
sleep 0.5 && {baseDir}/browser-eval.js '...'
```

### Investigate Before Interacting

Always start by understanding the page structure:

```javascript
(function() {
  return {
    title: document.title,
    forms: document.forms.length,
    buttons: document.querySelectorAll('button').length,
    inputs: document.querySelectorAll('input').length,
    mainContent: document.body.innerHTML.slice(0, 3000)
  };
})()
```

Then target specific elements based on what you find.

### brave-search/SKILL.md 200
---
name: brave-search
description: Web search and content extraction via Brave Search API. Use for searching documentation, facts, or any web content. Lightweight, no browser required.
---

# Brave Search

Web search and content extraction using the official Brave Search API. No browser required.

## Setup

Requires a Brave Search API account with a free subscription. A credit card is required to create the free subscription (you won't be charged).

1. Create an account at https://api-dashboard.search.brave.com/register
2. Create a "Free AI" subscription
3. Create an API key for the subscription
4. Add to your shell profile (`~/.profile` or `~/.zprofile` for zsh):
   ```bash
   export BRAVE_API_KEY="your-api-key-here"
   ```
5. Install dependencies (run once):
   ```bash
   cd {baseDir}
   npm install
   ```

## Search

```bash
{baseDir}/search.js "query"                         # Basic search (5 results)
{baseDir}/search.js "query" -n 10                   # More results (max 20)
{baseDir}/search.js "query" --content               # Include page content as markdown
{baseDir}/search.js "query" --freshness pw          # Results from last week
{baseDir}/search.js "query" --freshness 2024-01-01to2024-06-30  # Date range
{baseDir}/search.js "query" --country DE            # Results from Germany
{baseDir}/search.js "query" -n 3 --content          # Combined options
```

### Options

- `-n <num>` - Number of results (default: 5, max: 20)
- `--content` - Fetch and include page content as markdown
- `--country <code>` - Two-letter country code (default: US)
- `--freshness <period>` - Filter by time:
  - `pd` - Past day (24 hours)
  - `pw` - Past week
  - `pm` - Past month
  - `py` - Past year
  - `YYYY-MM-DDtoYYYY-MM-DD` - Custom date range

## Extract Page Content

```bash
{baseDir}/content.js https://example.com/article
```

Fetches a URL and extracts readable content as markdown.

## Output Format

```
--- Result 1 ---
Title: Page Title
Link: https://example.com/page
Age: 2 days ago
Snippet: Description from search results
Content: (if --content flag used)
  Markdown content extracted from the page...

--- Result 2 ---
...
```

## When to Use

- Searching for documentation or API references
- Looking up facts or current information
- Fetching content from specific URLs
- Any task requiring web search without interactive browsing


**Investigating command output**

I’m looking into the command -v playwright-mcp which didn't produce any output. It seems like it might be absent. I think I should inspect the skills directory to see if it's there. It feels a bit like a puzzle where I have to find the missing piece, and I’m curious about how I can solve this. Let’s see what I can uncover by checking the directory!

printf 'skills dir contents\n'; find ~/.pi/agent/skills -maxdepth 2 -type f -name 'SKILL.md' | sed 's#^#/ #' | sort | head -200
printf '\nplaywright-ish\n'; find ~ -maxdepth 4 \( -iname '*playwright*' -o -iname '*mcp*' \) 2>/dev/null | head -200

python - <<'PY'
import requests, json
for path in ['browser-tools/package.json','brave-search/package.json']:
    url=f'https://raw.githubusercontent.com/badlogic/pi-skills/main/{path}'
    r=requests.get(url,timeout=20)
    print('\n###',path,r.status_code)
    print(r.text[:4000])
PY


skills dir contents
/ /home/sebas/.pi/agent/skills/browser-tools/SKILL.md

playwright-ish
/home/sebas/eiffel-monitor/venv/bin/playwright
/home/sebas/.bun/install/cache/@playwright
/home/sebas/.bun/install/cache/playwright
/home/sebas/.bun/install/cache/playwright@1.59.1@@@1
/home/sebas/.bun/install/cache/playwright-core
/home/sebas/.bun/install/cache/playwright-core@1.59.1@@@1
/home/sebas/playwright-browsers
/home/sebas/playwright-browsers/firefox-1509/firefox/playwright.cfg
/home/sebas/ballbox/node_modules/@playwright
/home/sebas/ballbox/node_modules/.pnpm/@playwright+test@1.58.2
/home/sebas/ballbox/node_modules/.pnpm/next@16.2.1_@babel+core@7.29.0_@playwright+test@1.58.2_react-dom@19.2.0_react@19.2.0__react@19.2.0
/home/sebas/ballbox/node_modules/.pnpm/playwright@1.58.2
/home/sebas/ballbox/node_modules/.pnpm/playwright-core@1.58.2
/home/sebas/ballbox/node_modules/.pnpm/@vercel+analytics@1.3.1_next@16.2.1_@babel+core@7.29.0_@playwright+test@1.58.2_react-do_8642abe83ddf55ba2afafc98a31b92fa
/home/sebas/ballbox/node_modules/.bin/playwright
/home/sebas/ballbox/playwright.config.ts
/home/sebas/browser-harness/node_modules/@playwright
/home/sebas/browser-harness/node_modules/.pnpm/playwright-core@1.59.1
/home/sebas/browser-harness/node_modules/.pnpm/playwright@1.59.1
/home/sebas/browser-harness/node_modules/.pnpm/@playwright+test@1.59.1
/home/sebas/browser-harness/node_modules/.bin/playwright
/home/sebas/browser-harness/playwright.config.ts
/home/sebas/browser-harness/playwright-report
/home/sebas/bug-bounty/reports/RAGFlow-MCP-SSRF-2026-02-13.md
/home/sebas/.cache/ms-playwright


### browser-tools/package.json 200
{
	"name": "browser-tools",
	"version": "1.0.0",
	"type": "module",
	"description": "Minimal CDP tools for collaborative site exploration",
	"author": "Mario Zechner",
	"license": "MIT",
	"dependencies": {
		"@mozilla/readability": "^0.6.0",
		"cheerio": "^1.1.2",
		"jsdom": "^27.0.1",
		"puppeteer": "^24.31.0",
		"puppeteer-core": "^23.11.1",
		"puppeteer-extra": "^3.3.6",
		"puppeteer-extra-plugin-stealth": "^2.11.2",
		"turndown": "^7.2.2",
		"turndown-plugin-gfm": "^1.0.2"
	}
}

### brave-search/package.json 200
{
	"name": "brave-search",
	"version": "1.0.0",
	"type": "module",
	"description": "Headless web search via Brave Search - no browser required",
	"author": "Mario Zechner",
	"license": "MIT",
	"dependencies": {
		"@mozilla/readability": "^0.6.0",
		"jsdom": "^27.0.1",
		"turndown": "^7.2.2",
		"turndown-plugin-gfm": "^1.0.2"
	}
}


**Checking tools installation**

I realize I need to note that the browser-tools skill is already installed! It’s time to evaluate it with what I have existing. I should probably inspect whether the necessary dependencies are installed and if the scripts exist. It’s important to ensure everything is set up correctly before I proceed. I want to make sure I have what I need; you know, just in case something isn't working as expected!

printf 'browser-tools tree\n'; find ~/.pi/agent/skills/browser-tools -maxdepth 2 -type f | sort | sed 's#^#/ #' | head -200
printf '\npackage deps status\n'; [ -d ~/.pi/agent/skills/browser-tools/node_modules ] && echo installed || echo missing
[ -f ~/.pi/agent/skills/browser-tools/package.json ] && cat ~/.pi/agent/skills/browser-tools/package.json

for f in ~/.pi/agent/skills/browser-tools/browser-start.js ~/.pi/agent/skills/browser-tools/browser-nav.js ~/.pi/agent/skills/browser-tools/browser-eval.js ~/.pi/agent/skills/browser-tools/browser-screenshot.js ~/.pi/agent/skills/browser-tools/browser-content.js; do echo "\n### $f"; [ -f "$f" ] && sed -n '1,220p' "$f" || echo missing; done


browser-tools tree
/ /home/sebas/.pi/agent/skills/browser-tools/browser-content.js
/ /home/sebas/.pi/agent/skills/browser-tools/browser-cookies.js
/ /home/sebas/.pi/agent/skills/browser-tools/browser-eval.js
/ /home/sebas/.pi/agent/skills/browser-tools/browser-hn-scraper.js
/ /home/sebas/.pi/agent/skills/browser-tools/browser-nav.js
/ /home/sebas/.pi/agent/skills/browser-tools/browser-pick.js
/ /home/sebas/.pi/agent/skills/browser-tools/browser-screenshot.js
/ /home/sebas/.pi/agent/skills/browser-tools/browser-start.js
/ /home/sebas/.pi/agent/skills/browser-tools/.gitignore
/ /home/sebas/.pi/agent/skills/browser-tools/package.json
/ /home/sebas/.pi/agent/skills/browser-tools/package-lock.json
/ /home/sebas/.pi/agent/skills/browser-tools/SKILL.md

package deps status
missing
{
	"name": "browser-tools",
	"version": "1.0.0",
	"type": "module",
	"description": "Minimal CDP tools for collaborative site exploration",
	"author": "Mario Zechner",
	"license": "MIT",
	"dependencies": {
		"@mozilla/readability": "^0.6.0",
		"cheerio": "^1.1.2",
		"jsdom": "^27.0.1",
		"puppeteer": "^24.31.0",
		"puppeteer-core": "^23.11.1",
		"puppeteer-extra": "^3.3.6",
		"puppeteer-extra-plugin-stealth": "^2.11.2",
		"turndown": "^7.2.2",
		"turndown-plugin-gfm": "^1.0.2"
	}
}


\n### /home/sebas/.pi/agent/skills/browser-tools/browser-start.js
#!/usr/bin/env node

import { spawn, execSync } from "node:child_process";
import puppeteer from "puppeteer-core";

const useProfile = process.argv[2] === "--profile";

if (process.argv[2] && process.argv[2] !== "--profile") {
	console.log("Usage: browser-start.js [--profile]");
	console.log("\nOptions:");
	console.log("  --profile  Copy your default Chrome profile (cookies, logins)");
	process.exit(1);
}

const SCRAPING_DIR = `${process.env.HOME}/.cache/browser-tools`;

// Check if already running on :9222
try {
	const browser = await puppeteer.connect({
		browserURL: "http://localhost:9222",
		defaultViewport: null,
	});
	await browser.disconnect();
	console.log("✓ Chrome already running on :9222");
	process.exit(0);
} catch {}

// Setup profile directory
execSync(`mkdir -p "${SCRAPING_DIR}"`, { stdio: "ignore" });

// Remove SingletonLock to allow new instance
try {
	execSync(`rm -f "${SCRAPING_DIR}/SingletonLock" "${SCRAPING_DIR}/SingletonSocket" "${SCRAPING_DIR}/SingletonCookie"`, { stdio: "ignore" });
} catch {}

if (useProfile) {
	console.log("Syncing profile...");
	execSync(
		`rsync -a --delete \
			--exclude='SingletonLock' \
			--exclude='SingletonSocket' \
			--exclude='SingletonCookie' \
			--exclude='*/Sessions/*' \
			--exclude='*/Current Session' \
			--exclude='*/Current Tabs' \
			--exclude='*/Last Session' \
			--exclude='*/Last Tabs' \
			"${process.env.HOME}/Library/Application Support/Google/Chrome/" "${SCRAPING_DIR}/"`,
		{ stdio: "pipe" },
	);
}

// Start Chrome with flags to force new instance
spawn(
	"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
	[
		"--remote-debugging-port=9222",
		`--user-data-dir=${SCRAPING_DIR}`,
		"--no-first-run",
		"--no-default-browser-check",
	],
	{ detached: true, stdio: "ignore" },
).unref();

// Wait for Chrome to be ready
let connected = false;
for (let i = 0; i < 30; i++) {
	try {
		const browser = await puppeteer.connect({
			browserURL: "http://localhost:9222",
			defaultViewport: null,
		});
		await browser.disconnect();
		connected = true;
		break;
	} catch {
		await new Promise((r) => setTimeout(r, 500));
	}
}

if (!connected) {
	console.error("✗ Failed to connect to Chrome");
	process.exit(1);
}

console.log(`✓ Chrome started on :9222${useProfile ? " with your profile" : ""}`);
\n### /home/sebas/.pi/agent/skills/browser-tools/browser-nav.js
#!/usr/bin/env node

import puppeteer from "puppeteer-core";

const args = process.argv.slice(2);
const newTab = args.includes("--new");
const reload = args.includes("--reload");
const url = args.find(a => !a.startsWith("--"));

if (!url) {
	console.log("Usage: browser-nav.js <url> [--new] [--reload]");
	console.log("\nExamples:");
	console.log("  browser-nav.js https://example.com          # Navigate current tab");
	console.log("  browser-nav.js https://example.com --new    # Open in new tab");
	console.log("  browser-nav.js https://example.com --reload # Navigate and force reload");
	process.exit(1);
}

const b = await Promise.race([
	puppeteer.connect({
		browserURL: "http://localhost:9222",
		defaultViewport: null,
	}),
	new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 5000)),
]).catch((e) => {
	console.error("✗ Could not connect to browser:", e.message);
	console.error("  Run: browser-start.js");
	process.exit(1);
});

if (newTab) {
	const p = await b.newPage();
	await p.goto(url, { waitUntil: "domcontentloaded" });
	console.log("✓ Opened:", url);
} else {
	const p = (await b.pages()).at(-1);
	await p.goto(url, { waitUntil: "domcontentloaded" });
	if (reload) {
		await p.reload({ waitUntil: "domcontentloaded" });
	}
	console.log("✓ Navigated to:", url);
}

await b.disconnect();
\n### /home/sebas/.pi/agent/skills/browser-tools/browser-eval.js
#!/usr/bin/env node

import puppeteer from "puppeteer-core";

const code = process.argv.slice(2).join(" ");
if (!code) {
	console.log("Usage: browser-eval.js 'code'");
	console.log("\nExamples:");
	console.log('  browser-eval.js "document.title"');
	console.log('  browser-eval.js "document.querySelectorAll(\'a\').length"');
	process.exit(1);
}

const b = await Promise.race([
	puppeteer.connect({
		browserURL: "http://localhost:9222",
		defaultViewport: null,
	}),
	new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 5000)),
]).catch((e) => {
	console.error("✗ Could not connect to browser:", e.message);
	console.error("  Run: browser-start.js");
	process.exit(1);
});

const p = (await b.pages()).at(-1);

if (!p) {
	console.error("✗ No active tab found");
	process.exit(1);
}

const result = await p.evaluate((c) => {
	const AsyncFunction = (async () => {}).constructor;
	return new AsyncFunction(`return (${c})`)();
}, code);

if (Array.isArray(result)) {
	for (let i = 0; i < result.length; i++) {
		if (i > 0) console.log("");
		for (const [key, value] of Object.entries(result[i])) {
			console.log(`${key}: ${value}`);
		}
	}
} else if (typeof result === "object" && result !== null) {
	for (const [key, value] of Object.entries(result)) {
		console.log(`${key}: ${value}`);
	}
} else {
	console.log(result);
}

await b.disconnect();
\n### /home/sebas/.pi/agent/skills/browser-tools/browser-screenshot.js
#!/usr/bin/env node

import { tmpdir } from "node:os";
import { join } from "node:path";
import puppeteer from "puppeteer-core";

const b = await Promise.race([
	puppeteer.connect({
		browserURL: "http://localhost:9222",
		defaultViewport: null,
	}),
	new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 5000)),
]).catch((e) => {
	console.error("✗ Could not connect to browser:", e.message);
	console.error("  Run: browser-start.js");
	process.exit(1);
});

const p = (await b.pages()).at(-1);

if (!p) {
	console.error("✗ No active tab found");
	process.exit(1);
}

const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
const filename = `screenshot-${timestamp}.png`;
const filepath = join(tmpdir(), filename);

await p.screenshot({ path: filepath });

console.log(filepath);

await b.disconnect();
\n### /home/sebas/.pi/agent/skills/browser-tools/browser-content.js
#!/usr/bin/env node

import puppeteer from "puppeteer-core";
import { Readability } from "@mozilla/readability";
import { JSDOM } from "jsdom";
import TurndownService from "turndown";
import { gfm } from "turndown-plugin-gfm";

// Global timeout - exit if script takes too long
const TIMEOUT = 30000;
const timeoutId = setTimeout(() => {
	console.error("✗ Timeout after 30s");
	process.exit(1);
}, TIMEOUT).unref();

const url = process.argv[2];

if (!url) {
	console.log("Usage: browser-content.js <url>");
	console.log("\nExtracts readable content from a URL as markdown.");
	console.log("\nExamples:");
	console.log("  browser-content.js https://example.com");
	console.log("  browser-content.js https://en.wikipedia.org/wiki/Rust_(programming_language)");
	process.exit(1);
}

const b = await Promise.race([
	puppeteer.connect({
		browserURL: "http://localhost:9222",
		defaultViewport: null,
	}),
	new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 5000)),
]).catch((e) => {
	console.error("✗ Could not connect to browser:", e.message);
	console.error("  Run: browser-start.js");
	process.exit(1);
});

const p = (await b.pages()).at(-1);
if (!p) {
	console.error("✗ No active tab found");
	process.exit(1);
}

await Promise.race([
	p.goto(url, { waitUntil: "networkidle2" }),
	new Promise((r) => setTimeout(r, 10000)),
]).catch(() => {});

// Get HTML via CDP (works even with TrustedScriptURL restrictions)
const client = await p.createCDPSession();
const { root } = await client.send("DOM.getDocument", { depth: -1, pierce: true });
const { outerHTML } = await client.send("DOM.getOuterHTML", { nodeId: root.nodeId });
await client.detach();

const finalUrl = p.url();

// Extract with Readability
const doc = new JSDOM(outerHTML, { url: finalUrl });
const reader = new Readability(doc.window.document);
const article = reader.parse();

// Convert to markdown
function htmlToMarkdown(html) {
	const turndown = new TurndownService({ headingStyle: "atx", codeBlockStyle: "fenced" });
	turndown.use(gfm);
	turndown.addRule("removeEmptyLinks", {
		filter: (node) => node.nodeName === "A" && !node.textContent?.trim(),
		replacement: () => "",
	});
	return turndown
		.turndown(html)
		.replace(/\[\\?\[\s*\\?\]\]\([^)]*\)/g, "")
		.replace(/ +/g, " ")
		.replace(/\s+,/g, ",")
		.replace(/\s+\./g, ".")
		.replace(/\n{3,}/g, "\n\n")
		.trim();
}

let content;
if (article && article.content) {
	content = htmlToMarkdown(article.content);
} else {
	// Fallback
	const fallbackDoc = new JSDOM(outerHTML, { url: finalUrl });
	const fallbackBody = fallbackDoc.window.document;
	fallbackBody.querySelectorAll("script, style, noscript, nav, header, footer, aside").forEach((el) => el.remove());
	const main = fallbackBody.querySelector("main, article, [role='main'], .content, #content") || fallbackBody.body;
	const fallbackHtml = main?.innerHTML || "";
	if (fallbackHtml.trim().length > 100) {
		content = htmlToMarkdown(fallbackHtml);
	} else {
		content = "(Could not extract content)";
	}
}

console.log(`URL: ${finalUrl}`);
if (article?.title) console.log(`Title: ${article.title}`);
console.log("");
console.log(content);

process.exit(0);


**Considering browser options**

I'm noticing that the current browser tools are installed but they're specific to Mac, which isn't working on this Pi. I might need to avoid using the chromium path. I'll have to inspect the hardcoded Mac path for any blockers. It seems like I might require a browser harness or perhaps even use Playwright for local frontends. Adapting my approach seems necessary here! Let's explore this further.

Evaluación concreta:

## Estado real hoy

### Ya tenés algo instalado
En esta máquina ya existe:

- `~/.pi/agent/skills/browser-tools`

Incluye:
- `browser-start.js`
- `browser-nav.js`
- `browser-eval.js`
- `browser-screenshot.js`
- `browser-content.js`
- `browser-pick.js`

Pero:

- `node_modules` **no está instalado**
- el skill `brave-search` **no está instalado**
- no encontré binario usable de Chrome/Chromium en PATH
- `browser-start.js` actual está **hardcodeado para macOS**

Punto crítico:
```js
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
```

Eso en `ballbox-first` no sirve.

## Conclusión rápida

### Badlogic `pi-skills`
**Conceptualmente sí sirve.**
**Operativamente hoy no está listo en esta máquina.**

No porque la idea sea mala, sino porque:
- la versión instalada de `browser-tools` parece pensada para Mac
- falta instalar dependencias
- falta definir navegador local Linux/Pi
- falta verificar si este Pi puede correr Chrome/Chromium visible de forma cómoda

## Impacto por caso de uso

### Research web
`pi-skills` serviría bien si agregamos:
- `brave-search`
- `browser-tools` adaptado a Linux

Para docs y web abierta:
- `brave-search` sería muy útil
- menos fricción que browser visible
- mejor para búsquedas rápidas

### Frontend nuestro
`browser-tools` puede servir para:
- inspección manual asistida
- screenshots
- DOM eval
- smoke/debug con sesión real

Pero para validar “quedó bien terminado”:
- **Playwright sigue siendo mejor**
- ya tenés Playwright instalado dentro del repo
- más repetible, más robusto, más apto para app compleja

## Recomendación técnica

### Mejor plan
1. **Usar Playwright como base de QA**
2. **Agregar `brave-search` para research**
3. **Adaptar `browser-tools` solo si querés browsing visual/manual real**

## Si querés la opción Badlogic bien hecha acá
Hay que resolver 4 cosas:

### A. Instalar `pi-skills` vigente
No quedarse con `agent-tools`.

### B. Instalar deps
- `npm install` en `browser-tools`
- `npm install` en `brave-search`

### C. Parchear `browser-start.js`
Cambiarlo para Linux/Pi:
- detectar `chromium`
- o `chromium-browser`
- o `google-chrome`

### D. Confirmar modelo de uso
Elegir entre:
- **headless research** → `brave-search`
- **browser visible con perfil** → `browser-tools`
- **QA fuerte** → Playwright

## Mi veredicto final

### Sí vale evaluar/adoptar Badlogic, pero así:
- **`brave-search`: sí, alto valor**
- **`browser-tools`: sí, pero como complemento**
- **no reemplaza Playwright** para validar frontend nuestro

## Recomendación práctica para vos
Si querés máximo retorno con mínima fricción:

- **primero**: sumar `brave-search`
- **segundo**: dejar Playwright como flujo principal de validación
- **tercero**: solo después adaptar `browser-tools` para Linux si realmente querés navegación visual tipo humano

## Riesgo principal
Meter `browser-tools` esperando que resuelva QA fuerte sería error de encuadre.
Sirve más como:
- ojos/manos web
- debugging
- exploratory testing

No como sustituto serio de Playwright.

## Siguiente paso que recomiendo
Puedo hacer ahora una de estas:

1. **instalar y dejar andando `brave-search`**
2. **adaptar `browser-tools` para este Pi y probarlo**
3. **comparar contra tu flujo Playwright actual en Ballbox y definir stack final**

Mi recomendación: **1 + 3 primero**.


Hago todo eso, pero dejemosla andando no nos pongamos a probar con volvox todavía.


**Planning tasks and inspection**

I need to create a to-do list since the user wants me to implement everything except testing Volvox. I think I should inspect the current setup more carefully. I’m also considering whether the installation process is irreversible; it seems like it might not be. I’ll create tasks accordingly, possibly using skill files, and I might not need to rely on memory this time. Let’s get everything organized!

todo

Set up Badlogic web tooling on ballbox-first

Install current pi-skills-based web tooling, add brave-search and adapt browser-tools for this Linux/Pi machine without app-specific testing yet.


Created #1: Set up Badlogic web tooling on ballbox-first (pending)


Updated #1 (pending → in_progress)


**Inspecting skill directory**

I need to take a look at the existing skill directory, maybe clone or update it. I'm considering checking the user-level directory, specifically /.pi/agent/skills, to see what's inside. I also want to confirm the current origin of the browser tools. There’s a lot to unpack here, but it seems like organizing and updating skills could help streamline things for better performance. Let me dig into that!

bash

set -e
cd ~/.pi/agent/skills/browser-tools && pwd && git status --short && git remote -v && git rev-parse --abbrev-ref HEAD && git log -1 --oneline

env | grep -E '^BRAVE_API_KEY=' || true
command -v chromium || command -v chromium-browser || command -v google-chrome || command -v google-chrome-stable || true
apt-cache policy chromium chromium-browser google-chrome-stable 2>/dev/null | sed -n '1,120p' || true


/home/sebas/.pi/agent/skills/browser-tools
fatal: not a git repository (or any of the parent directories): .git

Command exited with code 128


chromium:
  Installed: (none)
  Candidate: 1:147.0.7727.101-1~deb13u1+rpt1
  Version table:
     1:147.0.7727.101-1~deb13u1+rpt1 500
        500 http://archive.raspberrypi.com/debian trixie/main arm64 Packages
     147.0.7727.137-1~deb13u1 500
        500 http://deb.debian.org/debian-security trixie-security/main arm64 Packages
     145.0.7632.159-1~deb13u1 500
        500 http://deb.debian.org/debian trixie/main arm64 Packages
chromium-browser:
  Installed: (none)
  Candidate: (none)
  Version table:


**Installing and configuring skills**

It seems I need to install something like Chromium, and it looks likely that the user wants everything included. I might need a Brave API key, but if it’s missing, I should probably ask the user first. However, it sounds like setting everything up might still work without it.

I’m considering cloning the pi-skills to the /mnt/rpi directory. This way, I can create symlinks for the relevant skills, keeping things organized and functional without being too complicated. I’ll inspect the existing skills directories to see what’s there.


total 12
drwxr-xr-x 3 sebas sebas 4096 Apr 21 22:15 .
drwxrwxr-x 8 sebas sebas 4096 May  7 18:08 ..
drwxr-xr-x 2 sebas sebas 4096 Apr 21 22:15 browser-tools


sudo -n true && echo sudo_ok || echo sudo_need_password


set -e
mkdir -p /mnt/rpi/tools
if [ ! -d /mnt/rpi/tools/pi-skills/.git ]; then
  git clone https://github.com/badlogic/pi-skills.git /mnt/rpi/tools/pi-skills
else
  git -C /mnt/rpi/tools/pi-skills fetch origin
  git -C /mnt/rpi/tools/pi-skills reset --hard origin/main
fi
mkdir -p ~/.pi/agent/skills
rm -rf ~/.pi/agent/skills/browser-tools ~/.pi/agent/skills/brave-search
ln -s /mnt/rpi/tools/pi-skills/browser-tools ~/.pi/agent/skills/browser-tools
ln -s /mnt/rpi/tools/pi-skills/brave-search ~/.pi/agent/skills/brave-search
printf 'linked skills:\n'
ls -l ~/.pi/agent/skills


Cloning into '/mnt/rpi/tools/pi-skills'...
linked skills:
total 0
lrwxrwxrwx 1 sebas sebas 37 May  7 18:14 brave-search -> /mnt/rpi/tools/pi-skills/brave-search
lrwxrwxrwx 1 sebas sebas 38 May  7 18:14 browser-tools -> /mnt/rpi/tools/pi-skills/browser-tools


cd /mnt/rpi/tools/pi-skills/browser-tools && npm install

cd /mnt/rpi/tools/pi-skills/brave-search && npm install

sudo apt-get update && sudo apt-get install -y chromium


npm warn deprecated rimraf@3.0.2: Rimraf versions prior to v4 are no longer supported
npm warn deprecated inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
npm warn deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported

added 210 packages, and audited 211 packages in 29s

36 packages are looking for funding
  run `npm fund` for details

5 vulnerabilities (2 moderate, 2 high, 1 critical)

To address all issues, run:
  npm audit fix

Run `npm audit` for details.


added 50 packages, and audited 51 packages in 3s

8 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities


Hit:1 https://deb.nodesource.com/node_22.x nodistro InRelease
Get:2 https://pkgs.tailscale.com/stable/debian trixie InRelease
Hit:3 http://deb.debian.org/debian trixie InRelease
Get:4 http://deb.debian.org/debian trixie-updates InRelease [47.3 kB]
Get:5 http://deb.debian.org/debian-security trixie-security InRelease [43.4 kB]
Get:6 http://deb.debian.org/debian-security trixie-security/main armhf Packages [147 kB]
Get:7 http://deb.debian.org/debian-security trixie-security/main arm64 Packages [154 kB]
Get:8 http://deb.debian.org/debian-security trixie-security/main Translation-en [94.8 kB]
Get:9 http://archive.raspberrypi.com/debian trixie InRelease [54.9 kB]
Get:10 http://archive.raspberrypi.com/debian trixie/main arm64 Packages [438 kB]
Fetched 986 kB in 3s (382 kB/s)
Reading package lists...
Reading package lists...
Building dependency tree...
Reading state information...
The following packages were automatically installed and are no longer required:
  alacarte gir1.2-gmenu-3.0 gnome-menus gtk-nop libgnome-menu-3-0 retry
Use 'sudo apt autoremove' to remove them.
The following additional packages will be installed:
  chromium-common chromium-l10n chromium-sandbox rpi-chromium-mods
Suggested packages:
  chromium-shell chromium-driver
The following NEW packages will be installed:
  chromium chromium-common chromium-l10n chromium-sandbox rpi-chromium-mods
0 upgraded, 5 newly installed, 0 to remove and 0 not upgraded.
Need to get 171 MB of archives.
After this operation, 487 MB of additional disk space will be used.
Get:1 http://archive.raspberrypi.com/debian trixie/main arm64 chromium-common arm64 1:147.0.7727.101-1~deb13u1+rpt1 [38.8 MB]
Get:2 http://archive.raspberrypi.com/debian trixie/main arm64 chromium arm64 1:147.0.7727.101-1~deb13u1+rpt1 [112 MB]
Get:3 http://archive.raspberrypi.com/debian trixie/main arm64 rpi-chromium-mods arm64 20260211 [5,974 B]
Get:4 http://archive.raspberrypi.com/debian trixie/main arm64 chromium-l10n all 1:147.0.7727.101-1~deb13u1+rpt1 [20.4 MB]
Get:5 http://archive.raspberrypi.com/debian trixie/main arm64 chromium-sandbox arm64 1:147.0.7727.101-1~deb13u1+rpt1 [123 kB]
dpkg-preconfigure: unable to re-open stdin: No such file or directory
Fetched 171 MB in 25s (6,959 kB/s)
Selecting previously unselected package chromium-common.
(Reading database ...
(Reading database ... 5%
(Reading database ... 10%
(Reading database ... 15%
(Reading database ... 20%
(Reading database ... 25%
(Reading database ... 30%
(Reading database ... 35%
(Reading database ... 40%
(Reading database ... 45%
(Reading database ... 50%
(Reading database ... 55%
(Reading database ... 60%
(Reading database ... 65%
(Reading database ... 70%
(Reading database ... 75%
(Reading database ... 80%
(Reading database ... 85%
(Reading database ... 90%
(Reading database ... 95%
(Reading database ... 100%
(Reading database ... 211104 files and directories currently installed.)
Preparing to unpack .../chromium-common_1%3a147.0.7727.101-1~deb13u1+rpt1_arm64.deb ...
Unpacking chromium-common (1:147.0.7727.101-1~deb13u1+rpt1) ...
Selecting previously unselected package chromium.
Preparing to unpack .../chromium_1%3a147.0.7727.101-1~deb13u1+rpt1_arm64.deb ...
Unpacking chromium (1:147.0.7727.101-1~deb13u1+rpt1) ...
Selecting previously unselected package rpi-chromium-mods.
Preparing to unpack .../rpi-chromium-mods_20260211_arm64.deb ...
Unpacking rpi-chromium-mods (20260211) ...
Selecting previously unselected package chromium-l10n.
Preparing to unpack .../chromium-l10n_1%3a147.0.7727.101-1~deb13u1+rpt1_all.deb ...
Unpacking chromium-l10n (1:147.0.7727.101-1~deb13u1+rpt1) ...
Selecting previously unselected package chromium-sandbox.
Preparing to unpack .../chromium-sandbox_1%3a147.0.7727.101-1~deb13u1+rpt1_arm64.deb ...
Unpacking chromium-sandbox (1:147.0.7727.101-1~deb13u1+rpt1) ...
Setting up chromium-sandbox (1:147.0.7727.101-1~deb13u1+rpt1) ...
Setting up chromium-common (1:147.0.7727.101-1~deb13u1+rpt1) ...
Setting up chromium (1:147.0.7727.101-1~deb13u1+rpt1) ...
update-alternatives: using /usr/bin/chromium to provide /usr/bin/x-www-browser (x-www-browser) in auto mode
update-alternatives: using /usr/bin/chromium to provide /usr/bin/gnome-www-browser (gnome-www-browser) in auto mode
Setting up rpi-chromium-mods (20260211) ...
Setting up chromium-l10n (1:147.0.7727.101-1~deb13u1+rpt1) ...
Processing triggers for desktop-file-utils (0.28-1) ...
Processing triggers for hicolor-icon-theme (0.18-2) ...
Processing triggers for gnome-menus (3.36.0-3) ...
Processing triggers for libc-bin (2.41-12+rpt1+deb13u2) ...
Processing triggers for man-db (2.13.1-1) ...
Processing triggers for mailcap (3.74) ...

Failed to check for processor microcode upgrades.

No services need to be restarted.

No containers need to be restarted.

User sessions running outdated binaries:
 sebas @ user service: agents-database-http.service[48007], agents-database-maintenance.service[48008]

No VM guests are running outdated hypervisor (qemu) binaries on this host.


**Investigating browser patch**

I need to develop a patch for the browser-start feature on Linux. First, I'll inspect the current upstream, possibly starting with the Mac version. I might use the edit function here. There's a thought about utilizing xdg config profiles since the Chrome default path syncs. On Linux, the Chromium profile paths differ, so I'll take a look at the script in the cloned repository—it could be the same structure. Let’s see what I find!


#!/usr/bin/env node

import { spawn, execSync } from "node:child_process";
import puppeteer from "puppeteer-core";

const useProfile = process.argv[2] === "--profile";

if (process.argv[2] && process.argv[2] !== "--profile") {
	console.log("Usage: browser-start.js [--profile]");
	console.log("\nOptions:");
	console.log("  --profile  Copy your default Chrome profile (cookies, logins)");
	process.exit(1);
}

const SCRAPING_DIR = `${process.env.HOME}/.cache/browser-tools`;

// Check if already running on :9222
try {
	const browser = await puppeteer.connect({
		browserURL: "http://localhost:9222",
		defaultViewport: null,
	});
	await browser.disconnect();
	console.log("✓ Chrome already running on :9222");
	process.exit(0);
} catch {}

// Setup profile directory
execSync(`mkdir -p "${SCRAPING_DIR}"`, { stdio: "ignore" });

// Remove SingletonLock to allow new instance
try {
	execSync(`rm -f "${SCRAPING_DIR}/SingletonLock" "${SCRAPING_DIR}/SingletonSocket" "${SCRAPING_DIR}/SingletonCookie"`, { stdio: "ignore" });
} catch {}

if (useProfile) {
	console.log("Syncing profile...");
	execSync(
		`rsync -a --delete \
			--exclude='SingletonLock' \
			--exclude='SingletonSocket' \
			--exclude='SingletonCookie' \
			--exclude='*/Sessions/*' \
			--exclude='*/Current Session' \
			--exclude='*/Current Tabs' \
			--exclude='*/Last Session' \
			--exclude='*/Last Tabs' \
			"${process.env.HOME}/Library/Application Support/Google/Chrome/" "${SCRAPING_DIR}/"`,
		{ stdio: "pipe" },
	);
}

// Start Chrome with flags to force new instance
spawn(
	"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
	[
		"--remote-debugging-port=9222",
		`--user-data-dir=${SCRAPING_DIR}`,
		"--no-first-run",
		"--no-default-browser-check",
	],
	{ detached: true, stdio: "ignore" },
).unref();

// Wait for Chrome to be ready
let connected = false;
for (let i = 0; i < 30; i++) {
	try {
		const browser = await puppeteer.connect({
			browserURL: "http://localhost:9222",
			defaultViewport: null,
		});
		await browser.disconnect();
		connected = true;
		break;
	} catch {
		await new Promise((r) => setTimeout(r, 500));
	}
}

if (!connected) {
	console.error("✗ Failed to connect to Chrome");
	process.exit(1);
}

console.log(`✓ Chrome started on :9222${useProfile ? " with your profile" : ""}`);


**Considering browser setup options**

I need to detect the executable and Linux profile for patching, possibly supporting Chromium and Google Chrome. For Raspberry Pi, there might not be a display, so I wonder if I should run headless. However, the user wants to navigate like a human, so I might need to leave the browser visible. If there’s no DISPLAY, starting the browser might fail; maybe adding `--headless=new` would help, but that loses visibility. I’ll check the environment for DISPLAY and consider creating a wrapper note for missing Brave keys. A README might also be useful to clarify usage without the key.

**Checking for Brave API Key**

I could check if the BRAVE_API_KEY is present in the profile, but I wonder if the environment variable is absent in a non-login shell. Maybe I should search through dotfiles for it, but that might feel too invasive. I think using grep in the shell rc could be a more precise way to look for it without digging too deeply. I'll weigh the pros and cons of each approach to find the best solution for this!

grep -R "BRAVE_API_KEY" ~/.bashrc ~/.profile ~/.zshrc ~/.zprofile ~/.config/fish/config.fish 2>/dev/null || true

echo DISPLAY=${DISPLAY:-unset}
echo XDG_SESSION_TYPE=${XDG_SESSION_TYPE:-unset}
who | head


(no output)


DISPLAY=unset
XDG_SESSION_TYPE=unset
sebas    pts/13       2026-05-07 17:59 (sebastianfavaron@gmail.com@100.100.39.116)
sebas    pts/11       2026-05-06 01:05 (sebastianfavaron@gmail.com@100.100.39.116)
sebas    pts/10       2026-05-06 01:04 (sebastianfavaron@gmail.com@100.100.39.116)
sebas    pts/8        2026-05-05 17:50 (sebastianfavaron@gmail.com@100.100.39.116)
sebas    pts/7        2026-05-05 17:49 (sebastianfavaron@gmail.com@100.100.39.116)
sebas    pts/6        2026-05-05 17:39 (sebastianfavaron@gmail.com@100.100.39.116)
sebas    pts/5        2026-05-05 17:20 (sebastianfavaron@gmail.com@100.100.39.116)
sebas    pts/3        2026-05-05 14:51 (sebastianfavaron@gmail.com@100.100.39.116)
sebas    pts/2        2026-05-05 14:24 (sebastianfavaron@gmail.com@100.100.39.116)
sebas    pts/1        2026-05-05 11:28 (sebastianfavaron@gmail.com@100.100.39.116)