Agent Skills

notte-cli

Browser automation in your terminal

README.md

Notte CLI - Browser automation in your terminal

Control browser sessions and web scraping through intuitive resource-based commands
→ Read more at: Landing • Console • Docs • X • LinkedIn

GitHub stars License: MIT Go 1.25+ Homebrew


What is Notte CLI?

The Notte CLI brings the full power of notte.cc to your terminal — letting you drive browser sessions and web scraping pipelines from the command line. Pair it with shell scripts, CI/CD pipelines, or AI coding assistants for repeatable, scriptable web automation.

Features

  • Browser sessions - remote Chromium/Chrome with full control
  • Files - upload and download files to notte.cc
  • Output formats - human-readable text or JSON for scripting
  • Personas - create and manage digital identities with email, phone, and SMS
  • Secure credentials - system keyring for API keys, vaults for website passwords
  • Web scraping - structured data extraction with custom schemas
  • Functions - schedule and execute repeatable automation tasks

Installation

Install script (macOS and Linux)

curl -fsSL https://notte.cc/install-cli.sh | sh

Installs the latest release to /usr/local/bin. Set NOTTE_VERSION to install a specific version, or NOTTE_INSTALL_DIR to install somewhere else.

Homebrew

brew tap nottelabs/notte-cli https://github.com/nottelabs/notte-cli.git
brew install notte

Go Install

go install github.com/nottelabs/notte-cli/cmd/notte@latest

Build from Source

git clone https://github.com/nottelabs/notte-cli.git
cd notte-cli
make build

Quick Start

1. Authenticate

Specify the API key using one of three methods (checked in priority order):

# 1. Via environment variable (recommended for CI/CD)
export NOTTE_API_KEY="your-api-key"
# 2. Via system keyring (recommended for local development)
notte auth login
# 3. Via config file (~/.notte/cli/config.json)
# create ~/.notte/cli/config.json and add your API key
notte auth status

2. Start a Browser Session

notte sessions start

Watch the session live through the ViewerUrl in the output.

Commands

Authentication

notte auth login                     # Store API key in system keychain
notte auth logout                    # Remove API key from keychain
notte auth status                    # Show authentication status

Web Search

notte search <query>                                    # Search the web for a query
notte search <query> --depth fast|standard|deep         # Tune search depth (default: standard)
notte search <query> --output-type sourcedAnswer        # Get an LLM answer with sources

The query may be quoted (notte search "what is anthropic") or passed as separate words (notte search what is anthropic). Use --output json to get the raw API response for scripting.

Browser Sessions

notte sessions list [--page N] [--page-size N] [-a|--all]  # List running sessions (-a includes stopped)
notte sessions start [flags]          # Start a new session
notte sessions status                 # Get current session status
notte sessions status --auth          # Get managed authentication readiness and operation details
notte sessions stop                   # Stop current session
notte sessions cookies                # Get all cookies from current session
notte sessions cookies-set --file cookies.json  # Set cookies in current session
notte sessions network                # View network activity logs
notte sessions replay                 # Get session replay data
notte sessions workflow-code          # Export session steps as Python code
notte sessions viewer                 # Open session viewer in browser
notte sessions code                   # Get Python script for session steps

Note: When you start a session, it automatically becomes the "current" session. All subsequent commands use this session by default. Use --session-id <session-id> only when you need to manage multiple sessions simultaneously or reference a specific session.

Session Start Options

notte sessions start \
  --browser-type chromium|chrome  # Browser type (default: chromium)
  --idle-timeout-minutes <minutes>        # Idle timeout (default: 3)
  --max-duration-minutes <minutes>        # Maximum session lifetime (default: 15)
  --user-agent <string>                   # Custom user agent
  --viewport-width <pixels>               # Viewport width
  --viewport-height <pixels>              # Viewport height
  --proxy                                  # Use default proxy rotation
  --proxy-country <code>                  # Proxy with specific country (e.g. us, gb, fr)
  --no-solve-captchas                     # Turn OFF captcha solving (on by default)
  --no-file-storage                       # Detach FileStorage (attached by default).
                                          # Disables page download and files --from session
  --advanced-stealth                      # Highest-fidelity browser for sites with
                                          # sophisticated bot detection (approved workspaces)
  --cdp-url <url>                         # CDP URL of remote session provider
  --profile-id <id>                       # Profile ID to use for session
  --profile-persist                       # Save browser state to profile on close
  --vault-id <id>                         # Vault used to resolve credential fields
  --screenshot-type <type>                # Screenshot type (raw, full, last_action)
  --chrome-args <args>                    # Chrome instance arguments (repeatable)

Page Actions

Interact with pages using simplified commands (requires an active session). Start the session with --vault-id <id> before using page fill --vault-field:

notte page observe                    # Get page state and available actions
notte page scrape --instructions "..." # Scrape content from the page 
notte page click "@B3"            # Click an element by ID
notte page fill "@I1" "text"    # Fill an input field
notte page fill "#email" --vault-field email       # Fill from the session vault
notte page fill "#password" --vault-field password # Fill the stored password
notte page fill "#card-number" --vault-field card_number
notte page fill "#card-name" --vault-field card_holder_name
notte page fill "#card-expiry" --vault-field card_expiration
notte page fill "#card-cvc" --vault-field card_cvv
notte page goto "https://example.com" # Navigate to a URL
notte page back                       # Go back in history
notte page forward                    # Go forward in history
notte page scroll-down [amount]       # Scroll down the page
notte page scroll-up [amount]         # Scroll up
notte page press "Enter"              # Press a key
notte page screenshot                 # Take a screenshot
notte page select <id> "option"       # Select dropdown option
notte page check <id>                 # Check/uncheck checkbox
notte page upload <id> --file <name>  # Fill a file input. <name> is a file in your
                                      # uploads store, not a local path - send it with
                                      # `notte files upload` first
notte page download <id>              # Download by clicking. The file lands in the
                                      # session store; retrieve it with
                                      # `notte files download <name> --from session`
notte page new-tab <url>              # Open URL in new tab
notte page switch-tab <index>         # Switch to tab by index
notte page close-tab                  # Close current tab
notte page reload                     # Reload page
notte page wait <seconds>             # Wait for duration
notte page captcha-solve              # Solve captcha
notte page eval-js "document.title"   # Evaluate JavaScript in the page

Evaluating JavaScript

page eval-js prints the evaluated value alone on stdout — objects and arrays as JSON, a JS null as null — with the status line on stderr, so it captures and pipes without post-processing:

title=$(notte page eval-js "document.title")

notte page eval-js "JSON.stringify([...document.querySelectorAll('a')].map(a => a.href))" | jq length

Return JSON.stringify(...) when the answer is structured. console.log output is discarded — only the returned value comes back. A failing script exits non-zero and reports the actual JavaScript error; use -o json to get the full execution result instead of the bare value.

Functions

notte functions list [--page N] [--page-size N] [--include-deleted]  # List functions
notte functions create --file workflow.py  # Create a new function
notte functions show                  # View current function details
notte functions show --function-id <id>  # View specific function details (different from current function)
notte functions download workflow.py [--version <version>]  # Download current function code to disk
notte functions create --file workflow.py --response-format @schema.json  # ... with its response documented
notte functions update --file workflow.py  # Update current function code
notte functions update --file workflow.py --response-format @schema.json  # ... and re-document its response
notte functions configure --run-instructions "..." --self-healing  # Set usage notes and self-healing
notte functions configure --response-format @schema.json  # Document run()'s return schema without re-upload
notte functions rollback --version <version>  # Restore an earlier version (see `versions` in show)
notte functions health                # Runtime health: Python version, installed packages, reachability
notte functions delete                # Delete current function
notte functions fork                  # Fork current function to new version
notte functions run                   # Execute current function
notte functions run --no-stream       # Poll until complete, returning final run metadata without log output
notte functions run --no-wait          # Return the run ID after startup
notte functions run-metadata --wait --run-id <id> [--wait-timeout 30m]  # Wait for an existing run
notte functions runs [--page N] [--page-size N] [--running]  # List runs for current function (--running = in-flight only)
notte functions run-stop --run-id <id>  # Stop a running function execution
notte functions run-metadata --run-id <id>  # Get run logs and results
notte functions schedule --cron "0 12 ? * * *"  # Schedule current function (six-field cron: daily at noon UTC)
notte functions unschedule            # Remove schedule from current function

Function runs print their ID and a tracking command to stderr as soon as the server creates the run. After the runner accepts the request, the CLI closes the response stream and polls metadata. --timeout applies to each API request; --wait-timeout limits the total wait (unlimited by default). --no-wait returns after startup so another command can wait separately.

In JSON mode, stdout contains one object: the run ID and status with --no-wait, or final run metadata (including status, logs, and the stored result string) when waiting. Logs are printed to stderr as they become available in metadata; some runners persist logs only at completion. --no-stream suppresses those log messages. An interrupted or timed-out CLI does not cancel the server run: resume with run-metadata --wait, or explicitly stop it with run-stop.

Personas, Profiles and Usage

notte personas update --persona-id <id> --name "checkout tester"  # Rename a persona
notte profiles cookies --profile-id <id>                          # Read a profile's cookies
notte profiles cookies-set --profile-id <id> --file cookies.json  # Import cookies into a profile
notte usage logs [--endpoint /sessions/start] [--page N]          # List API requests made with your key

profiles cookies-set takes either a bare array of cookies — what Playwright's storageState and the browser extensions export — or an object with a cookies key. Add --source-format chrome if they came from Chrome, and --mode append to add to the profile's cookies rather than replace them.

--response-format takes a JSON Schema describing what run() returns, as inline JSON, @file.json, or - for stdin. The API never derives it, so a function created without it has no documented response — which is what the console reads to show callers the shape they will get back. From a pydantic return model:

python -c 'import json, typing, client; print(json.dumps(typing.get_type_hints(client.run)["return"].model_json_schema()))' > schema.json
notte functions create --file client.py --response-format @schema.json
# Or document it later without re-uploading the code:
notte functions configure --response-format @schema.json

--run-instructions is documentation for whoever calls the function — how long a run takes, what each variable is for, which sites it trips over:

notte functions configure --run-instructions "Takes ~3 min, so call it with --no-wait. \
Hits a captcha on the login page every few runs. \
\`query\` is the search term; \`max_items\` caps the results."

It is not input to the self-healing agent, which is the separate --self-healing flag.

configure sends only the flags you pass, so setting --run-instructions leaves self-healing untouched. Disable self-healing with --self-healing=false: the API treats an absent field as "leave it alone" rather than "off". Note that it can only be enabled on functions an agent built — a CLI-created function has no thread for the healer to resume, and the API rejects it.

Note: When you create a function, it automatically becomes the "current" function. All subsequent commands use this function by default. Use --function-id <function-id> only when you need to manage multiple functions simultaneously or reference a specific function.

Vaults

notte vaults list [--page N] [--page-size N] [--include-deleted]  # List all vaults
notte vaults create                                   # Create a new vault
notte vaults update --vault-id <id>                   # Update vault metadata
notte vaults delete --vault-id <id>                   # Delete a vault
notte vaults credentials list --vault-id <id>         # List all credentials
notte vaults credentials add --vault-id <id>          # Add credentials
notte vaults credentials get --vault-id <id>          # Get credentials for URL
notte vaults credentials delete --vault-id <id>       # Delete credentials

Personas

notte personas list [--page N] [--page-size N] [--include-deleted]  # List all personas
notte personas create                    # Create a new persona
notte personas show --persona-id <id>    # View persona details
notte personas delete --persona-id <id>  # Delete a persona
notte personas emails --persona-id <id>  # List emails
notte personas sms --persona-id <id>     # List SMS messages

Profiles

notte profiles list [--page N] [--page-size N] [--name "..."] [--include-deleted]  # List all profiles
notte profiles create                    # Create a new profile
notte profiles show --profile-id <id>    # View profile details
notte profiles delete --profile-id <id>  # Delete a profile
notte profiles sync                      # Copy your local browser login into a profile

profiles sync reads the cookies from a local Firefox, Chrome, Brave, Edge or Chromium profile and uploads them into a Notte profile, so remote sessions start already logged in. The cookies are read from a copy of the browser's own files on your machine (and, for the Chromium browsers, decrypted with the key from your OS keychain); nothing is sent until you confirm, and --domain limits the sync to the sites you name.

notte profiles sync                                        # pick a browser profile, create a Notte profile
notte profiles sync --domain github.com --domain mail.google.com   # only these sites (and subdomains)
notte profiles sync --profile-id <id>                      # refresh an existing profile after a re-login
notte profiles sync --browser brave --local-profile Work   # choose the source without the prompt

Then start a session with it: notte sessions start --profile-id <id>. Re-run sync whenever the local login changes. Supported on macOS and Linux; for the Chromium browsers the first run asks permission to read the browser key from your OS keychain (Firefox stores cookies in the clear and needs no such permission). Scoped syncs into an existing profile may want --mode append so other sites' cookies are kept.

Files

notte files upload <path>                                      # Upload a persistent input file
notte files list --from uploads                                # List persistent input files
notte files download <filename> --from uploads                 # Download a persistent input file
notte files list --from session [--session-id <id>]            # List files produced by a session
notte files download <filename> [--session-id <id>]            # Download a file produced by a session

Utilities

notte usage                          # View API usage statistics
notte health                         # Check API health status
notte version                        # Show CLI version

Output Formats

Text

Human-readable tables with colors and formatting:

$ notte sessions list
ID                        STATUS    BROWSER     CREATED
ses_abc123def456          ACTIVE    chromium    2024-01-15 10:30:00
ses_xyz789uvw012          STOPPED   chrome      2024-01-15 09:15:00

JSON

Machine-readable output:

$ notte sessions list --output json
{
  "sessions": [
    {
      "id": "ses_abc123def456",
      "status": "ACTIVE",
      "browser": "chromium",
      "created_at": "2024-01-15T10:30:00Z"
    }
  ]
}

Data goes to stdout, errors and progress to stderr for clean piping.

Examples

Automated Web Scraping Pipeline

# Start session (automatically becomes the current session)
notte sessions start

# Navigate to page
notte page goto "https://news.ycombinator.com"

# Extract structured data
notte page scrape --instructions "Extract top 10 stories with title and URL"

# Cleanup
notte sessions stop

Running a Workflow

# List functions to find ID
notte functions list

# Run workflow
notte functions run --function-id func_abc123

Managing Credentials Securely

# Create a vault for production credentials
VAULT_ID=$(notte vaults create --name "Production Sites" -o json | jq -r '.id')

# Add website credentials
notte vaults credentials add --vault-id $VAULT_ID \
  --username "admin@example.com" \
  --password "$SECURE_PASSWORD" \
  --url "https://app.example.com"

# List stored credentials
notte vaults credentials list --vault-id $VAULT_ID

Multi-Step Browser Automation

# Start browser with specific configuration
notte sessions start \
  --browser-type chrome \
  --viewport-width 1920 \
  --viewport-height 1080

# Navigate and interact
notte page goto "https://example.com"
notte page click "#login-button"
notte page fill "#username" "user@example.com"

# Get current page state with available actions
notte page observe

# Stop when done
notte sessions stop

JQ Filtering

# Get only active sessions (using built-in filter)
notte sessions list --all

# Paginate through results
notte sessions list --page 2 --page-size 5

# Extract session IDs with jq
notte sessions list --output json | jq -r '.sessions[].id'

Usage with AI Agents

Just Ask the Agent

The simplest approach - just tell your agent to use it:

Use notte to test the login flow. Run notte --help to see available commands.

The --help output is comprehensive and most agents can figure it out from there.

AI Coding Assistants

Add the skill to your AI coding assistant for richer context:

npx skills add nottelabs/notte-skills

This works with Claude Code, Cursor, Windsurf, and other MCP-compatible assistants.

AGENTS.md / CLAUDE.md

For more consistent results, add to your project or global instructions file:

## Browser Automation

Use `notte` for web automation. Run `notte --help` for all commands.

Core workflow:
1. `notte sessions start` - Start a browser session
2. `notte page goto <url>` - Navigate to a URL
3. `notte page observe` - Get interactive elements with IDs (@B1, @B2)
4. `notte page click "@B1"` / `notte page fill "@I1" "text"` - Interact using element IDs
5. `notte page scrape --instructions "..."` - Extract structured data
6. `notte sessions stop` - Clean up when done

Tips

  • Viewing sessions: When you start a session, the output includes a ViewerUrl - open it to watch the browser live
  • Session lifetime: sessions close after 3 minutes idle or 15 minutes total by default. Raise --idle-timeout-minutes/--max-duration-minutes for anything slow, or the next command fails with Session closed
  • Element selectors: If element IDs from observe (like @B1) don't work, use Playwright selectors: #id, .class, button:has-text('Submit')
  • Multiple matches: Use >> nth=0 suffix to select the first match: button:has-text('OK') >> nth=0
  • Closing modals: notte page press "Escape" reliably dismisses most dialogs

Skills Documentation

For comprehensive documentation including templates and reference guides, see the notte-skills/plugins/notte-cli/skills/notte-browser folder (vendored as a submodule from nottelabs/notte-skills).

Security

Credential Storage

API keys are stored securely in your system's keychain:

  • macOS: Keychain Access
  • Linux: Secret Service (GNOME Keyring, KWallet)
  • Windows: Credential Manager

Best Practices

  • Never pass API keys on the command line
  • Use vaults for website passwords and payment cards
  • Rotate API keys regularly from notte.cc dashboard
  • Use notte auth logout to remove stored keys

Shell Completions

Generate shell completions for your preferred shell:

Bash

# macOS (Homebrew):
notte completion bash > $(brew --prefix)/etc/bash_completion.d/notte

# Linux:
notte completion bash > /etc/bash_completion.d/notte

# Or source directly:
source <(notte completion bash)

Zsh

notte completion zsh > "${fpath[1]}/_notte"

Fish

notte completion fish > ~/.config/fish/completions/notte.fish

PowerShell

notte completion powershell | Out-String | Invoke-Expression

Development

After cloning, install git hooks:

make setup

This installs lefthook pre-commit and pre-push hooks for linting and testing.

License

This project is licensed under the MIT License.

Links

Copyright © 2025 Notte Labs, Inc.

Coverage guards

Two checks keep the CLI in step with what surrounds it. Both read a live source of truth, so both need the network; make runs them with -strict, where an unreachable source is a failure. The pre-commit hooks run them without it, so an offline commit warns and proceeds.

make check-endpoints   # every API endpoint is reachable or recorded as skipped
make check-skills      # every command is documented in nottelabs/notte-skills
make check-coverage    # both

check-endpoints compares the API's OpenAPI spec against the commands. An endpoint is covered when a command calls its generated client method; anything else has to be listed in scripts/endpoint-coverage.txt with a reason, as manual (a command builds the request itself) or skip (not exposed on purpose). A line whose endpoint the API no longer serves fails too, so the file cannot rot the way the flag generator's endpoint map did.

check-skills walks the cobra tree and requires every non-hidden command to be mentioned somewhere under plugins/notte/skills/ in the skills repository. Pass -skills-dir <path> to check against a working copy before pushing it:

go run ./scripts/checkcoverage -check skills -skills-dir ../notte-skills -strict

Session payments

Connect your wallet first. This returns a URL and phrase; authorize in your browser. Completion is recorded in the background. Re-run the same command to confirm connected or retrieve the pending link:

notte payment connect --mode test -o json

Then request spending for an existing session (amounts use currency units):

notte payment request --session-id "$SESSION_ID" --mode test --amount 1.00 --currency usd \
  --merchant-url https://example.com --merchant-name Example \
  --description "Buy one sandbox item for this browser session, with a maximum total of one US dollar including all applicable fees." \
  --idempotency-key "$REQUEST_KEY" -o json
notte payment status "$PAYMENT_ID" -o json
notte payment wait "$PAYMENT_ID" --wait-timeout 10m -o json

When --mode is omitted, both commands use the API default. Use --mode test on both commands for a development/test payment. Wallet connections are scoped to your authenticated account and mode. Requests fail with wallet_not_connected until connection completes; they do not start connection or reserve a card slot. Once connected, a request provides a spending approval URL. Send that URL to the user before waiting for the card.

To switch wallets, run notte payment disconnect --mode live, then notte payment connect --mode live. Disconnect affects only the selected mode and refuses while payments or card cleanup are still active. Use --mode test for the test wallet. Omitting the mode uses the API default.

If Link requires additional wallet verification, payment wait prints the action URL. Resumable verification keeps waiting on the same request. When Link requires a new spend request, the command exits with instructions to complete verification and request payment again with a new idempotency key. payment status -o json includes the structured next_action instructions.

wait displays approval and verification instructions on stderr as they become available and prints one final result on stdout when ready. It exits nonzero on failure, expiration, decline, session closure, or timeout. Stopping the CLI does not cancel provisioning; resume with the same payment ID. Requests print their idempotency key on stderr; reuse it with the same inputs to recover an uncertain request without creating another one.

ready means the temporary card is available for existing vault placeholders. It does not confirm a merchant purchase. Payment commands never return card numbers, security codes, or wallet tokens. Descriptions must contain 100 to 4000 characters. --amount 100.91 --currency usd requests USD 100.91. The backend validates currency precision without rounding and enforces a 50,000-minor-unit limit (USD 500.00).

The deployed sandbox lifecycle test runs real CLI subprocesses and creates a short-lived browser session and unapproved test spend request. It checks request replay, status, wait timeout, and session-close cleanup without approving a wallet request or submitting a merchant payment:

NOTTE_API_URL=https://YOUR_ENABLED_TEST_DEPLOYMENT \
NOTTE_PAYMENT_E2E_REQUIRED=1 \
go test -tags=integration ./tests/integration \
  -run '^TestPaymentSandboxLifecycle$' -count=1 -v

Supply NOTTE_API_KEY through the environment. The normal integration suite skips this case only when the API explicitly returns payments_disabled. NOTTE_PAYMENT_E2E_REQUIRED=1 makes that condition fail instead, so a release validation cannot count a disabled feature as a passed payment test. Auth and configuration errors always fail. Use a dedicated test principal without someone concurrently approving its payment requests.

Search skills and MCP servers

Fuzzy search across 23,137 skills and servers