oc-browserless
v2.0.0
Published
Browserless plugin for OpenCode using puppeteer-core
Maintainers
Readme
oc-browserless
Browserless plugin for OpenCode using puppeteer-core.
Features
- Web Browsing (
web_browse): Navigate and browse web pages, returning clean Markdown with boilerplate (nav, footer, scripts, hidden elements) stripped, plus security certificate information - Web Search (
web_search): Search the web using SearXNG (if configured) or DuckDuckGo fallback (results page returned as Markdown) - Screenshots (
web_screenshot): Capture screenshots in PNG, JPEG, and WebP formats - PDF Generation (
web_pdf): Convert HTML or URLs to PDF documents - Browser Lifecycle Management: Automatic browser connection management
Installation
Prerequisites
Install Bun if you haven't already:
curl -fsSL https://bun.sh/install | bashAfter installation, restart your terminal or source your shell profile:
# For bash
source ~/.bashrc
# For zsh
source ~/.zshrc
# For fish
source ~/.config/fish/config.fishVerify Bun installation:
bun --versionGlobal Installation
npm install -g oc-browserlessProject Installation
npm install oc-browserlessConfiguration
Add to your opencode.json:
OpenCode V1 (requires >= 1.18.29):
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["oc-browserless"]
}OpenCode V2:
{
"$schema": "https://opencode.ai/config.json",
"plugins": ["oc-browserless"]
}One package serves both versions: the package declares engines.opencode >= 1.18.29 (enforced by V1's plugin loader and checked by V2), so V1 users must be on at least 1.18.29.
Behavioral note (V2 only): under V1, an invalid
urlargument (e.g. not http/https) is rejected at the schema level; under V2 the same argument surfaces at runtime as the tool's standard JSON failure result ({"success": false, "error": "Invalid URL format"}).
Browserless Setup
Local Browserless
Using Docker:
docker run -p 3000:3000 -e "CONCURRENT=10" browserless/chrome:latestOr install globally:
npm install -g browserless
browserless --port=3000Then set environment variable:
export BROWSERLESS_URL=ws://localhost:3000Remote Browserless
Sign up for browserless.io and get your API key:
export BROWSERLESS_URL=wss://your-browserless-instance.com
export BROWSERLESS_API_KEY=your-api-keyIf BROWSERLESS_URL already contains a token query parameter, it takes precedence over BROWSERLESS_API_KEY.
Environment Configuration
Copy the example environment file:
cp .env.example .envEdit .env with your browserless configuration:
BROWSERLESS_URL=ws://localhost:3000
BROWSERLESS_API_KEY=your-api-key-if-using-remote
# Optional: Operation timeout in milliseconds (default 30000)
BROWSERLESS_TIMEOUT=30000
# Optional: Max characters of Markdown content returned by browse/search (default 100000, 0 = unlimited)
BROWSERLESS_MAX_CONTENT=100000
# Optional: SearXNG instance URL (takes priority over DuckDuckGo)
SEARXNG_URL=http://localhost:8888
# Optional: Basic auth credentials for SearXNG (leave empty if no auth)
SEARXNG_BASIC_USER=
SEARXNG_BASIC_PASSWORD=SearXNG Search (Optional)
If you have a SearXNG instance, you can use it for search instead of DuckDuckGo. SearXNG returns structured JSON results directly (no browser needed).
Using Docker:
docker run -p 8888:8080 searxng/searxng:latestThen set environment variables:
export SEARXNG_URL=http://localhost:8888If your SearXNG instance requires authentication:
export SEARXNG_URL=http://localhost:8888
export SEARXNG_BASIC_USER=myuser
export SEARXNG_BASIC_PASSWORD=mypasswordWhen SEARXNG_URL is set, the web_search tool uses SearXNG's JSON API directly without needing a browserless instance. When not set, it falls back to DuckDuckGo via browserless.
Development Setup
1. Clone Repository
git clone https://github.com/rh-id/oc-browserless.git
cd oc-browserless2. Install Dependencies
bun installThis will install:
- Runtime dependencies (
puppeteer-core,@opencode-ai/plugin,@opencode/plugin,node-html-markdown) - Development dependencies (ESLint, Prettier, TypeScript)
3. Build Project
bun run buildThis will:
- Compile TypeScript files to JavaScript in
dist/directory - Generate type declarations
- Copy compiled files to
.opencode/for local OpenCode testing
Note: The .opencode/ directory is used for local development with OpenCode. When the package is published to npm, only the dist/ directory is included.
4. Test with OpenCode
Run opencode in the project directory:
opencodeThe plugin is automatically loaded from .opencode/plugin/ — this single directory is discovered by both OpenCode V1 and V2. Do not also place plugin files in .opencode/plugins/ (plural): V1 would register the plugin twice.
Project Structure
oc-browserless/
├── .github/workflows/ # CI/CD workflows
│ ├── build.yml # Build project
│ ├── lint.yml # Linting and formatting
│ └── release.yml # Automated releases
├── .husky/ # Git hooks
│ └── pre-commit # Pre-commit lint-staged
├── .opencode/ # OpenCode plugin files (compiled for local dev)
│ ├── plugin/ # Compiled plugin (discovered by BOTH V1 and V2)
│ │ ├── browserless.js # Plugin entry point (dual V1/V2 export)
│ │ ├── guidelines.js # System prompt guidelines module
│ │ └── schemas.js # Tool JSON Schemas + arg normalizers
│ └── package.json # Dependencies for local dev
├── dist/ # Compiled output (published to npm)
│ └── plugin/
│ ├── browserless.js # Compiled plugin
│ ├── guidelines.js # Compiled guidelines module
│ ├── schemas.js # Compiled schemas module
│ ├── browserless.d.ts # Type declarations
│ ├── browserless.js.map # Source map
│ └── browserless.d.ts.map # Type source map
├── scripts/ # Build scripts
│ ├── build-copy.js # Build copy script
│ └── smoke.ts # Post-build smoke test (bun scripts/smoke.ts)
├── src/ # Source code (TypeScript)
│ └── plugin/
│ ├── browserless.ts # Plugin entry (V1 tools + V2 setup, shared logic)
│ ├── guidelines.ts # System prompt text
│ └── schemas.ts # V2 JSON Schemas, descriptions, arg normalizers
├── Configuration Files
│ ├── .gitignore
│ ├── .gitattributes
│ ├── eslint.config.cjs
│ ├── .prettierrc
│ ├── .env.example
│ ├── .release-please-manifest.json
│ ├── package.json
│ └── tsconfig.json
└── Documentation
├── README.md
├── CONTRIBUTING.md
└── LICENSEPlugin Development
For contributors working on the plugin locally:
Local Testing Workflow
Make your changes under
src/plugin/(browserless.ts,guidelines.ts,schemas.ts)Build the project:
bun run buildThis compiles TypeScript and copies the output to
.opencode/plugin/directoryThe
.opencode/directory is used for local development with OpenCode:- It contains the compiled plugin code
.opencode/plugin/is the only plugin directory: it is discovered by BOTH OpenCode V1 and V2 from this single location- Do not also copy plugin files to
.opencode/plugins/(plural) — V1 would register the plugin twice (the build script deletes any stale.opencode/plugins/directory automatically) - When the package is published to npm, only the
dist/directory is included - Running
opencodein the project root automatically loads the plugin from.opencode/plugin/
Test your changes:
opencodeThe plugin is automatically loaded from
.opencode/plugin/directoryIterate:
- Make changes to
src/plugin/ - Run
bun run buildto update.opencode/plugin/ - Test with
opencode
- Make changes to
Project Structure for Development
src/plugin/
├── browserless.ts # Plugin entry point (edit this)
├── guidelines.ts # System prompt guidelines text
└── schemas.ts # V2 JSON Schemas + shared arg normalizers
dist/plugin/ # Compiled output (published to npm)
├── browserless.js
├── guidelines.js
├── schemas.js
├── browserless.d.ts
└── browserless.js.map
.opencode/plugin/ # Local dev copy (for testing with opencode)
├── browserless.js # Same compiled code as dist/plugin/ (V1 AND V2 load from here)
├── guidelines.js
└── schemas.jsBuild Process
The bun run build command:
- Compiles TypeScript files to JavaScript in
dist/directory - Generates type declarations (
.d.ts) - Copies compiled files to
.opencode/for local OpenCode testing
Note: Always run bun run build after making changes to test them locally.
Security Notes
The web_browse, web_search, web_screenshot, and web_pdf tools instruct the browserless instance to fetch any http(s) URL and return its content or rendering to the agent - they act as a proxy from the browserless host.
- Avoid exposing browserless to untrusted users
- Be aware that internal/private network URLs reachable from the browserless host can be requested by name
Usage
The plugin provides the following tools for OpenCode:
Browse Web Pages
// Navigate to a URL and get content
{
"tool": "web_browse",
"args": {
"url": "https://example.com"
}
}Web Search
// Search the web
{
"tool": "web_search",
"args": {
"query": "TypeScript best practices"
}
}With SearXNG (when SEARXNG_URL is set):
{
"success": true,
"query": "TypeScript best practices",
"results": [
{
"url": "https://example.com/typescript-tips",
"title": "TypeScript Best Practices",
"content": "Learn the best practices for writing TypeScript...",
"engine": "google",
"score": 1.0,
"category": "general"
}
],
"suggestions": ["typescript tutorial", "typescript handbook"],
"number_of_results": 1250000,
"engine": "searxng"
}With DuckDuckGo (fallback when SearXNG is not configured):
{
"success": true,
"query": "TypeScript best practices",
"content": "# TypeScript Best Practices\n\n...",
"engine": "duckduckgo"
}Take Screenshot
// Capture screenshot
{
"tool": "web_screenshot",
"args": {
"url": "https://example.com",
"path": "./screenshot.png",
"format": "png",
"fullPage": true
}
}Generate PDF
// Generate PDF from URL
{
"tool": "web_pdf",
"args": {
"url": "https://example.com",
"path": "./output.pdf",
"format": "A4",
"printBackground": true
}
}
// Generate PDF from HTML
{
"tool": "web_pdf",
"args": {
"html": "<html><body><h1>Hello World</h1></body></html>",
"path": "./output.pdf"
}
}Browser Lifecycle
All browser operations automatically manage their own connections:
- No manual start/stop required - tools handle this internally
- Each tool execution creates an isolated browser instance
- Browser sessions are NOT persistent across tool calls
- Browserless supports multiple concurrent connections automatically
- Each tool operates in isolation with no shared state
- No connection reuse - each operation creates fresh browser instance
API Reference
web_browse
Navigate to and browse web pages.
| Argument | Type | Required | Default | Description | | -------- | ------ | -------- | ------- | ------------------ | | url | string | Yes | - | URL to navigate to |
Returns:
{
"success": true,
"url": "https://example.com",
"title": "Example Domain",
"content": "# Example Domain\n\n...",
"certificate": {
"issuer": "CN=DigiCert Inc",
"protocol": "TLS 1.3",
"subjectName": "CN=example.com",
"subjectAlternativeNames": ["example.com", "www.example.com"],
"validFrom": 1234567890,
"validTo": 1234567890
}
}The certificate field is null for HTTP connections or when security details are unavailable. The content field contains Markdown of the page with boilerplate (nav, header, footer, scripts, hidden elements) stripped.
web_search
Search the web using SearXNG (if configured) or DuckDuckGo (fallback).
When SEARXNG_URL is set, uses the SearXNG JSON API directly (no browserless instance needed for search). Otherwise falls back to DuckDuckGo via browserless.
| Argument | Type | Required | Default | Description | | -------- | ------ | -------- | ------- | ------------ | | query | string | Yes | - | Search query |
web_screenshot
Capture page screenshots.
| Argument | Type | Required | Default | Description | | -------------- | ------- | -------- | ------- | ----------------------------- | | url | string | Yes | - | URL to screenshot | | path | string | No | - | Output file path | | format | enum | No | png | Format: png, jpeg, webp | | fullPage | boolean | No | false | Full page screenshot | | quality | number | No | - | Quality for jpeg/webp (0-100) | | viewportWidth | number | No | - | Viewport width | | viewportHeight | number | No | - | Viewport height |
web_pdf
Generate PDF from HTML or URL.
| Argument | Type | Required | Default | Description | | --------------- | ------- | -------- | ------- | ----------------- | | html | string | No* | - | HTML content | | url | string | No* | - | URL to convert | | path | string | No | - | Output file path | | format | enum | No | A4 | Paper format | | printBackground | boolean | No | true | Print backgrounds | | landscape | boolean | No | false | Landscape mode | | marginTop | string | No | 0cm | Top margin | | marginBottom | string | No | 0cm | Bottom margin | | marginLeft | string | No | 0cm | Left margin | | marginRight | string | No | 0cm | Right margin |
*Either html or url is required.
Development
# Install dependencies
bun install
# Build
bun run build
# Test
bun test
# Lint
bun run lint
# Format
bun run formatDevelopment Workflow
Linting:
bun run lintAuto-fix linting issues:
bun run lint:fixCheck formatting:
bun run format:checkAuto-fix formatting:
bun run formatType checking:
bunx tsc --noEmitTesting:
bun testCI/CD
Build Workflow
- Runs on push and PR
- Installs dependencies
- Builds project
- Uploads artifacts
Lint Workflow
- Runs on push and PR
- Lints code with ESLint
- Checks formatting
Release Workflow
- Runs on main branch
- Uses release-please for automated versioning
- Publishes to npm on release
Versioning
Uses automated versioning with release-please:
feat:→ Minor version bump (1.x.0)fix:→ Patch version bump (x.x.1)BREAKING CHANGE:→ Major version bump (2.0.0)
Troubleshooting
SearXNG Connection Issues
Error: "SearXNG request failed: 401 Unauthorized"
- Check
SEARXNG_BASIC_USERandSEARXNG_BASIC_PASSWORDif your instance requires auth - If your instance has no auth, make sure
SEARXNG_BASIC_USERis empty
Verify SearXNG is running:
curl http://localhost:8888/search?q=test&format=jsonConnection Issues
Error: "Failed to connect to browserless"
- Check
BROWSERLESS_URLenvironment variable - Ensure browserless instance is running
- Verify WebSocket URL is correct (use
ws://orwss://) - Verify firewall/network settings
Verify browserless is running:
curl http://localhost:3000/healthCheck WebSocket URL format:
- Local:
ws://localhost:3000 - Remote:
wss://your-browserless.com
Timeout Issues
Error: "Operation timed out"
- Increase timeout via the
BROWSERLESS_TIMEOUTenvironment variable (milliseconds, default 30000) - Check network connectivity
- Verify URL is accessible
Browser Disconnected
Error: "Browser not connected" or connection failures
- Check
BROWSERLESS_URLenvironment variable - Ensure browserless instance is running
- Each tool creates its own connection - no manual management needed
- Try running the tool again if connection fails
TypeScript Errors
If you see TypeScript errors about missing types:
Before dependencies are installed (expected):
Cannot find module 'puppeteer-core'→ Runbun installCannot find module '@opencode-ai/plugin'→ Runbun installCannot find name 'setTimeout'/URL/process/fetch→ Will resolve afterbun install
These are not actual errors - they're just TypeScript not being able to resolve types until dependencies are installed.
After dependencies are installed:
- Restart your TypeScript server in your IDE
- Clear Bun cache:
rm -rf node_modules
bun installBuild Errors
If build fails:
- Check TypeScript version:
bun --version- Update dependencies:
bun updateClean Scripts
The project includes clean scripts for easy cleanup:
# Clean build output only
bun run clean
# Full reset: clean everything, reinstall dependencies, and rebuild
bun run resetUse these when:
clean- After making changes to TypeScript filesreset- When troubleshooting build or cache issues
Troubleshooting Build Issues
If you encounter build errors:
# Option 1: Full reset
bun run reset
# Option 2: Manual clean and rebuild
rm -rf dist
bun run buildDonate / Sponsor
If you find this project useful and would like to support its continued development, consider making a donation or becoming a sponsor:
Your support helps maintain and improve the project. Thank you! ❤️
License
MIT © Ruby Hartono
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Support
For issues and questions, please use the GitHub Issues page.
