From e56ab3630540ce719d48e820c1eb9b04e3ec4df3 Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Sat, 25 Jul 2026 09:15:03 -0400 Subject: [PATCH 01/14] docs(skill): sync debug-browser-session with latest CLI --- .../skills/debug-browser-session/SKILL.md | 72 +++++++++++++------ 1 file changed, 49 insertions(+), 23 deletions(-) diff --git a/plugins/kernel-cli/skills/debug-browser-session/SKILL.md b/plugins/kernel-cli/skills/debug-browser-session/SKILL.md index 890670d..7354b07 100644 --- a/plugins/kernel-cli/skills/debug-browser-session/SKILL.md +++ b/plugins/kernel-cli/skills/debug-browser-session/SKILL.md @@ -26,9 +26,14 @@ Substitute your session ID for ``. ### Session status ```bash -kernel browsers get +kernel browsers get -o json + +# If the normal lookup returns not found, recover the soft-deleted record by session ID. +kernel browsers get --include-deleted -o json ``` +The deleted record provides static configuration and timestamps such as `created_at` and `deleted_at`; VM, Playwright, screenshot, curl, log, and live-view commands still require a live session. + ### Screenshot the current state ```bash kernel browsers computer screenshot --to screenshot.png @@ -39,20 +44,25 @@ kernel browsers computer screenshot --to screenshot.png kernel browsers playwright execute "return { url: page.url(), title: await page.title() }" ``` -### Read VM log files +### Read browser service logs ```bash -kernel browsers fs read-file --path /var/log/supervisord.log -kernel browsers fs read-file --path /var/log/supervisord/chromium -kernel browsers fs read-file --path /var/log/supervisord/neko +kernel browsers logs stream --source supervisor --supervisor-process chromium --follow=false +kernel browsers logs stream --source supervisor --supervisor-process neko --follow=false ``` -### List files in the VM +For other VM logs, list and read files directly: + ```bash kernel browsers fs list-files --path /var/log +kernel browsers fs read-file --path /var/log/supervisord.log ``` -### Run commands inside the VM +### Compare Chrome and VM network paths ```bash +# Uses Chrome's TLS fingerprint, cookies, headers, and proxy configuration. +kernel browsers curl https://example.com -I -w 'status=%{http_code} total=%{time_total}\n' + +# Uses curl inside the VM instead of Chrome's network stack. kernel browsers process exec -- curl -I https://example.com kernel browsers process exec -- cat /etc/resolv.conf ``` @@ -64,7 +74,7 @@ kernel browsers playwright execute "const cookies = await page.cont ## Browser telemetry events (works even after deletion) -Telemetry only helps if it was enabled before the failure — capture is off by default. When it was on, events captured in the VM stay readable after telemetry is disabled or the session is deleted; every other command in this skill needs a live session. +Telemetry only helps if it was enabled before the failure — capture is off by default. When it was on, captured events stay readable after telemetry is disabled or the session is deleted. A soft-deleted session's static record is also available with `kernel browsers get --include-deleted`; runtime inspection still needs a live session. The debug-critical categories are console (console output and uncaught exceptions), network (request/response metadata), and page (navigation and lifecycle). High-signal event types: console_error, network_loading_failed, network_response with non-2xx status, system_oom_kill, and monitor_disconnected (telemetry gap — treat following events as incomplete). For the full category and event-type catalog, see the [telemetry categories docs](https://docs.onkernel.com/browsers/telemetry/categories). @@ -75,7 +85,7 @@ kernel browsers telemetry events --since 24h --categories console,n kernel browsers telemetry events --since 24h --types console_error,network_loading_failed ``` -`--since` accepts an RFC-3339 timestamp or a duration like `5m`, and **defaults to the last 5 minutes** — a deleted session can't be `get`ed to check its lifetime, so when in doubt use a generous window like `--since 24h` (as in the examples), or the session's created_at if you know it. `--all` walks every page in the window (default is one page of 20 events, `--limit` up to 100, with an `--offset` cursor for manual paging); a `--types` filter walks the whole window automatically. Use `-o json` for full event payloads — the default table shows only sequence, time, category, and type, so anything that depends on event contents (a `network_response` status code, a console error message, a URL) requires json output. The same events are available via the API/SDK (`GET /browsers/{session_id}/telemetry/events`, which has the same 5-minute `since` default) and — if your Kernel MCP server build includes it — the `manage_browsers` tool's `get_telemetry` action (which defaults to the full session window). For a live session, `kernel browsers telemetry stream ` tails events as they happen. +`--since` accepts an RFC-3339 timestamp or a duration like `5m`, and **defaults to the last 5 minutes**. When in doubt, use a generous window like `--since 24h` (as in the examples); for a deleted session, use `browsers get --include-deleted` to recover its `created_at` and bound the window precisely. `--all` walks every page in the window (default is one page of 20 events, `--limit` up to 100, with an `--offset` cursor for manual paging); a `--types` filter walks the whole window automatically. Use `-o json` for full event payloads — the default table shows only sequence, time, category, and type, so anything that depends on event contents (a `network_response` status code, a console error message, a URL) requires json output. The same events are available via the API/SDK (`GET /browsers/{session_id}/telemetry/events`, which has the same 5-minute `since` default) and — if your Kernel MCP server build includes it — the `manage_browsers` tool's `get_telemetry` action (which defaults to the full session window). For a live session, `kernel browsers telemetry stream ` tails events as they happen. ### Enable capture ```bash @@ -97,11 +107,26 @@ Gotchas: Bot detection is a common cause. Many sites use CDNs like Cloudflare, Imperva, or Akamai that fingerprint browsers and block automation. Signs of bot detection: -- `curl` works from the VM but Chrome shows an error +- VM `curl` works but the page or `kernel browsers curl` fails - "Access Denied", captcha pages, or "Checking your browser…" messages -- `stealth: false` in the browser config (check with `kernel browsers get`) +- `stealth: false` in the browser config + +Check `kernel browsers get -o json` for `stealth` and an explicit `proxy_id`. If a stealth session is expected to use Kernel's default proxy, isolate that route only after capturing the screenshot, telemetry, and logs: -Solutions: stealth is set at creation and can't be toggled on a live session, so recreate it with `kernel browsers create --stealth`; use profiles with real auth; or try shorter session lifetimes. +```bash +# This mutates live routing. +kernel browsers update --disable-default-proxy +# Reproduce the failing request once and capture the result. +kernel browsers update --disable-default-proxy=false +``` + +Only run this A/B test when the session owner confirms the default proxy should currently be enabled, because `browsers get` does not expose the prior `disable_default_proxy` state. Do not substitute `--clear-proxy`; that removes an explicit proxy. Restore the default proxy regardless of the result; if direct routing fixed the failure, investigate the proxy path. + +Stealth is set at creation and can't be toggled on a live session. If you create a stealth replacement to reproduce, preserve the original session and delete only the diagnostic replacement when done: + +```bash +kernel browsers delete +``` ### Browser not responding Cause: Chrome process crashed or hung. @@ -110,12 +135,12 @@ Solutions: confirm the timeout wasn't reached, look for memory issues in the log ### Page not loading Cause: network, DNS, or proxy issues. -Check: `curl` from inside the VM, `/etc/resolv.conf` for DNS config, proxy settings if one is configured. +Check: compare `kernel browsers curl` with VM `curl`, inspect `/etc/resolv.conf`, then inspect the configured proxy. A Chrome-only failure points toward Chrome state, TLS fingerprinting, cookies, or its proxy route; failure in both paths points lower in the VM/network stack. ### Live view not working Cause: Neko/WebRTC issues. -Check: fetch the live view URL with `kernel browsers view ` and open it; check Neko logs for connection errors. -Solutions: check for a firewall blocking WebRTC, verify the browser isn't in headless mode. +Check: fetch the live view URL with `kernel browsers view ` and open it; read Neko logs with `browsers logs stream`; verify `headless` is false in `browsers get`. +Solutions: check for a firewall blocking WebRTC and compare whether Playwright and screenshots still work. If they do, the browser is healthy and the fault is isolated to live view. ## Expected log entries (normal operation) @@ -127,21 +152,22 @@ These are normal and don't indicate problems: ## Debugging checklist -- [ ] Session exists and is active +- [ ] Active session found, or deleted metadata recovered with `--include-deleted` - [ ] Screenshot shows expected content (or reveals the error) - [ ] Current URL is as expected - [ ] Supervisor logs show all services running -- [ ] Network connectivity works (curl test) +- [ ] Chrome-stack and VM curl results compared - [ ] No critical errors in chromium logs - [ ] Cookies/session state are correct - [ ] Telemetry events checked for console_error / network_loading_failed / system events (especially if the session is gone) +- [ ] Any temporary default-proxy change restored; any diagnostic replacement deleted ## Suggested order -1. Get browser info to confirm the session is active. -2. Take a screenshot to see the current state. -3. Check the page URL to see whether it's on an error page. -4. Test network connectivity if seeing connection errors. -5. Review logs for specific error patterns. +1. Get browser info; retry with `--include-deleted` if it is gone. +2. Preserve evidence: screenshot, URL/title, telemetry, then logs. +3. Compare Chrome-stack curl with VM curl for connection failures. +4. Reproduce once; only then run a temporary default-proxy A/B test if relevant. +5. Restore changed routing and delete any diagnostic replacement session. -If the session no longer exists, telemetry events are the only remaining signal — and only if telemetry was enabled while it ran. +If the session is deleted, recover its static metadata with `browsers get --include-deleted`; telemetry is the remaining runtime signal, and only if capture was enabled while it ran. From 62cd084949c7fe305de384f6790fcffc8c9b823e Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Sat, 25 Jul 2026 09:14:02 -0400 Subject: [PATCH 02/14] docs(skill): sync diff-profile-archives with latest CLI --- .../skills/diff-profile-archives/SKILL.md | 57 +++++++++++-------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/plugins/kernel-cli/skills/diff-profile-archives/SKILL.md b/plugins/kernel-cli/skills/diff-profile-archives/SKILL.md index 678abca..c0e4d87 100644 --- a/plugins/kernel-cli/skills/diff-profile-archives/SKILL.md +++ b/plugins/kernel-cli/skills/diff-profile-archives/SKILL.md @@ -1,11 +1,11 @@ --- name: diff-profile-archives -description: Compare two Kernel profile archives to investigate behavioral differences. Use when an issue (e.g. login failure, captcha, broken automation, vendor mismatch) reproduces on one profile but not another, or when a "good" vs "bad" profile needs to be diffed. Takes two profile IDs/names plus an issue description, downloads both archives via the Kernel CLI, and surfaces differences in cookies, storage, preferences, extensions, and login state that could explain the issue. +description: Compare two Kernel profile snapshots to investigate behavioral differences. Use when an issue (e.g. login failure, captcha, broken automation, vendor mismatch) reproduces on one profile but not another, or when a "good" vs "bad" profile needs to be diffed. Takes two profile IDs/names plus an issue description, downloads and extracts both profiles via the Kernel CLI, and surfaces differences in cookies, storage, preferences, extensions, and login state that could explain the issue. --- # Diff Kernel Profile Archives -Compare the actual contents of two Kernel browser profiles to identify state differences that could explain a reported issue. The skill downloads both profile archives via the Kernel CLI, decompresses them, and produces a structured diff focused on the surfaces most likely to drive divergent behavior: cookies, local/session storage, preferences, extension state, login data, and history. +Compare the actual contents of two Kernel browser profiles to identify state differences that could explain a reported issue. The skill downloads and extracts both profiles via the Kernel CLI, then produces a structured diff focused on the surfaces most likely to drive divergent behavior: cookies, local/session storage, preferences, extension state, login data, and history. ## Inputs @@ -19,9 +19,9 @@ If any of these are missing, prompt for them before proceeding. The issue descri ## Prerequisites -- Kernel CLI installed and authenticated (`kernel auth status`) -- `KERNEL_API_KEY` set, or the user is logged in via `kernel auth login` -- `zstd`, `tar`, `sqlite3`, and `jq` available (install via `apt-get install -y zstd sqlite3 jq` if needed) +- Kernel CLI installed and authenticated (`kernel auth`) +- `KERNEL_API_KEY` set, or the user is logged in via `kernel login` +- `sqlite3` and `jq` available (install via `apt-get install -y sqlite3 jq` if needed) - ~500MB free disk space per profile (profiles can be large) ## Workflow @@ -35,29 +35,38 @@ mkdir -p a b diff echo "Workspace: $WORKDIR" ``` -### Step 2: Download both archives +### Step 2: Resolve and download both profiles ```bash -# Replace and with the profile IDs or names from the user -kernel profiles download --pretty --to a/profile.zip -kernel profiles download --pretty --to b/profile.zip +# Replace these with the profile IDs or exact names from the user. +PROFILE_A='' +PROFILE_B='' + +# Record metadata and confirm the identifiers resolve to two distinct profile IDs. +kernel profiles get "$PROFILE_A" -o json | tee diff/profile-a.json +kernel profiles get "$PROFILE_B" -o json | tee diff/profile-b.json +A_ID=$(jq -r '.id' diff/profile-a.json) +B_ID=$(jq -r '.id' diff/profile-b.json) +[ "$A_ID" != "$B_ID" ] || { echo "Both inputs resolve to $A_ID" >&2; exit 1; } + +# --to is an extraction directory, not an archive filename. +kernel profiles download "$PROFILE_A" --to a +kernel profiles download "$PROFILE_B" --to b ``` -`--pretty` pretty-prints JSON files (e.g. `Preferences`) so they diff cleanly line-by-line. Always pass it. +Profile names and IDs are resolved in the CLI's active project scope. If the user specifies a project, use the same `--project ` on every `profiles get` and `profiles download` command. -### Step 3: Extract - -The archive is **Zstandard-compressed TAR despite the `.zip` extension**. Standard unzip will fail. +The CLI requests `tar.zst` by default, streams the response, decompresses it, and extracts it directly under `--to`. It does not leave an archive file. Keep the default for the smaller transfer. Use `--format tar` only to bypass zstd decompression while debugging a format/decompression problem; the CLI still extracts the response into `--to`: ```bash -for side in a b; do - zstd -d "$side/profile.zip" -o "$side/profile.tar" - tar -xf "$side/profile.tar" -C "$side" - rm "$side/profile.tar" -done +kernel profiles download "$PROFILE_A" --format tar --to a ``` -After extraction each side typically contains a Chrome user-data-dir layout (e.g. `Default/` with `Cookies`, `Local Storage/`, `Preferences`, etc.). If the layout differs, run `find a -maxdepth 3 -type d` to orient yourself before continuing. +If the CLI reports that a profile has no saved data yet, stop: the profile must first be used in a browser session so Kernel can capture state. + +### Step 3: Inspect the extracted layout + +Each side typically contains a Chrome user-data-dir layout (e.g. `Default/` with `Cookies`, `Local Storage/`, `Preferences`, etc.). Run `find a b -maxdepth 3 -type d | sort` to orient yourself before continuing. ### Step 4: Triage by issue description @@ -123,8 +132,8 @@ for side in a b; do echo "(no Preferences)" > "diff/preferences-$side.json" continue fi - # Already pretty-printed thanks to --pretty - cp "$PREF" "diff/preferences-$side.json" + # Normalize key ordering and indentation before diffing. + jq -S . "$PREF" > "diff/preferences-$side.json" done diff -u diff/preferences-a.json diff/preferences-b.json > diff/preferences.diff || true ``` @@ -327,7 +336,7 @@ Use this template: ### Step 8: Cleanup -Profile archives can contain credentials. Always remove the workspace when done: +Extracted profile data can contain credentials. Always remove the workspace when done: ```bash rm -rf "$WORKDIR" @@ -337,8 +346,8 @@ If the user wants to keep the artifacts for further investigation, tell them the ## Notes & gotchas -- **Archive format**: `.zip` extension is misleading — it's `zstd`-compressed TAR. Use `zstd -d` then `tar -xf`, not `unzip`. -- **`--pretty` is mandatory** for meaningful JSON diffs. Without it, `Preferences` is a single line and diffs are unreadable. +- **Download destination**: `--to` names an extraction directory. Do not give it a `.zip` filename and do not run `unzip`, `zstd`, or `tar`; the CLI extracts the requested `tar.zst` (default) or `tar` response itself. +- **Normalize JSON locally**: `profiles download` has no `--pretty` flag. Use `jq -S .` before diffing JSON files so formatting and key order do not create noise. - **Chrome profile path varies**: most data lives under `Default/`, but newer Chrome moved cookies to `Default/Network/Cookies`. Use `find` instead of hardcoding paths. - **SQLite locks**: profiles are downloaded as snapshots, so DBs aren't locked — but if a query errors with "database is locked", make sure you're querying the extracted copy, not a path inside an open archive. - **Don't compare `Cache/`, `Code Cache/`, `GPUCache/`, `Service Worker/CacheStorage/`** — these are noise and will produce huge meaningless diffs. From 91106fba47e6b8020d7bef95ca27789d1bbadab1 Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Sat, 25 Jul 2026 09:16:51 -0400 Subject: [PATCH 03/14] docs(skill): sync kernel-agent-browser with latest CLI --- .../skills/kernel-agent-browser/SKILL.md | 147 ++++++++---------- 1 file changed, 69 insertions(+), 78 deletions(-) diff --git a/plugins/kernel-cli/skills/kernel-agent-browser/SKILL.md b/plugins/kernel-cli/skills/kernel-agent-browser/SKILL.md index a9064c1..a0038db 100644 --- a/plugins/kernel-cli/skills/kernel-agent-browser/SKILL.md +++ b/plugins/kernel-cli/skills/kernel-agent-browser/SKILL.md @@ -1,6 +1,6 @@ --- name: kernel-agent-browser -description: Best practices for using agent-browser with Kernel cloud browsers. Use when automating websites with agent-browser -p kernel, dealing with bot detection, iframes, login persistence, or needing to find Kernel browser session IDs and live view URLs. +description: Best practices for using agent-browser with Kernel cloud browsers. Use when automating websites with agent-browser -p kernel, tuning stealth or proxy behavior, persisting profiles, handling iframes, discovering Kernel session IDs or live views, or cleaning up cloud sessions. --- # Agent-Browser with Kernel Cloud Browsers @@ -34,17 +34,20 @@ Set these before your first `agent-browser -p kernel` call. The CLI holds state | Variable | Description | Default | |----------|-------------|---------| | `KERNEL_API_KEY` | **Required.** Your Kernel API key for authentication | (none) | -| `KERNEL_HEADLESS` | Run browser in headless mode (`true`/`false`) | `false` | -| `KERNEL_STEALTH` | Enable stealth mode to avoid bot detection (`true`/`false`) | `true` | +| `KERNEL_HEADLESS` | Run browser in headless mode (`true`/`false`) | `true` | +| `KERNEL_STEALTH` | Launch a stealth browser (`true`/`false`) | `false` | | `KERNEL_TIMEOUT_SECONDS` | Session timeout in seconds | `300` | | `KERNEL_PROFILE_NAME` | Browser profile name for persistent cookies/logins | (none) | ### Recommended Configuration +Set options explicitly; agent-browser reads them when it creates the provider session. + ```bash export KERNEL_API_KEY="your-api-key" export KERNEL_TIMEOUT_SECONDS=600 # 10-minute timeout for complex workflows -export KERNEL_STEALTH=true # Avoid bot detection (default) +export KERNEL_HEADLESS=false # Required when you need a live view +export KERNEL_STEALTH=true # Opt in for bot-sensitive sites export KERNEL_PROFILE_NAME=mysite # Persist login sessions across runs ``` @@ -110,43 +113,50 @@ agent-browser -p kernel find nth 2 ".card" hover **Recommendation**: Use `find` for production automation. Use `@e` refs for exploration and quick prototyping, then convert to semantic selectors. -## Finding Session ID and Live View URL +## Find the Kernel Session and Live View -agent-browser creates a Kernel browser session under the hood. To get the session ID or live view URL: +Match agent-browser's CDP URL to the active Kernel session. This is more reliable than guessing from creation time when several sessions share a profile. This workflow requires `jq`. ```bash -# List all Kernel browsers (find yours by profile name or creation time) -kernel browsers list - -# Get live view URL for a specific session -kernel browsers view +CDP_URL="$(agent-browser -p kernel get cdp-url)" +SESSION_ID="$( + kernel browsers list --status active --limit 100 -o json | + jq -r --arg cdp "$CDP_URL" '.[] | select(.cdp_ws_url == $cdp) | .session_id' | + head -n 1 +)" +test -n "$SESSION_ID" + +kernel browsers view "$SESSION_ID" ``` -This is useful when: -- You need to execute Playwright scripts directly against the session -- You want to share a live view URL with the user for manual intervention -- You're debugging and want to watch the browser in real-time +Do not print or share the CDP URL; it grants browser access. Share a live view URL only with the intended user. A headless session has no live view. If you use `--session `, include it on every agent-browser command, including `get cdp-url`. ## Handling Bot Detection -### Stealth Mode +### Stealth and Proxy Routing -Stealth mode (`KERNEL_STEALTH=true`) is enabled by default and helps avoid detection. However, some sites have aggressive bot detection that still triggers. +Stealth is opt-in in current agent-browser releases. Set `KERNEL_STEALTH=true` before the first command for a session; changing it later does not reconfigure the running browser. -### Manual Login Fallback +A stealth browser can use Kernel's default stealth proxy. If that proxy causes a site-specific network or reputation failure and direct metro egress is acceptable, change the running session without disabling stealth: + +```bash +kernel browsers update "$SESSION_ID" --disable-default-proxy +# Retry the navigation and compare behavior. + +# Re-enable the default stealth proxy while continuing the same session. +kernel browsers update "$SESSION_ID" --disable-default-proxy=false +``` -If login automation fails due to bot detection: +Direct egress changes the public IP and can reduce anti-bot protection. Prefer the default proxy unless testing shows it is the problem. For a configured Kernel proxy, use `--proxy-id `; remove it with `--clear-proxy`. -1. Get the live view URL: - ```bash - kernel browsers list - # Find your session by profile name - kernel browsers view - ``` +### Manual Login Fallback -2. Share the live view URL with the user and ask them to complete the login manually +If automated login fails: -3. Once logged in, continue automation—the profile will save the authenticated state +1. Resolve `SESSION_ID` using the CDP-matching workflow above. +2. Run `kernel browsers view "$SESSION_ID"` and give the URL only to the intended user. +3. Ask the user to complete login, then continue in the same agent-browser session. +4. Close normally so the authenticated profile is saved. ### JavaScript Fallback for Tricky Elements @@ -195,23 +205,17 @@ agent-browser -p kernel frame main # Return to main frame ### Cross-Origin Iframes -Cross-origin iframes require executing a Playwright script directly against the Kernel session: +Try `agent-browser frame` first; current releases can switch into iframe context, including many cross-origin frames. If an out-of-process or payment iframe still fails, resolve `SESSION_ID` and use Kernel's Playwright executor: -1. Find the session ID: - ```bash - kernel browsers list - ``` - -2. Execute a Playwright script: - ```bash - kernel browsers exec --code " - const frame = page.frameLocator('#payment-iframe'); - await frame.locator('#card-number').fill('4111111111111111'); - await frame.locator('#submit').click(); - " - ``` +```bash +kernel browsers playwright execute "$SESSION_ID" ' + const frame = page.frameLocator("#payment-iframe"); + await frame.locator("#card-number").fill("4111111111111111"); + await frame.locator("#submit").click(); +' +``` -See the kernel-cli skill for more details on executing Playwright code. +Return to the main document with `agent-browser -p kernel frame main` after frame interactions. ## Waiting Strategies @@ -315,24 +319,6 @@ agent-browser -p kernel press Enter agent-browser -p kernel wait --url "**/home" ``` -### Comparison: Old vs Optimized - -```bash -# OLD (slow, fragile): -agent-browser -p kernel wait 2000 -agent-browser -p kernel fill @e1 "username" -agent-browser -p kernel wait 2000 -agent-browser -p kernel fill @e3 "password" -agent-browser -p kernel wait 5000 - -# OPTIMIZED (fast, stable): -agent-browser -p kernel wait --load domcontentloaded -agent-browser -p kernel find label "Username" fill "username" -agent-browser -p kernel wait --text "Password" -agent-browser -p kernel find label "Password" fill "password" -agent-browser -p kernel wait --url "**/dashboard" -``` - ### Modal Login Login form appears in a modal overlay: @@ -402,12 +388,21 @@ agent-browser -p kernel get url ### Cleanup -Always close the browser when done to save the profile: +Close the same named agent-browser session you opened. This saves profile changes and deletes its Kernel browser: ```bash agent-browser -p kernel close +# Named session: agent-browser -p kernel --session site1 close +``` + +If agent-browser is unavailable or close fails, delete the orphan explicitly: + +```bash +kernel browsers delete "$SESSION_ID" ``` +Do not use `close --all` when unrelated agent-browser sessions may be running. + ### Multiple Sessions Run parallel browser sessions with named sessions: @@ -420,19 +415,11 @@ agent-browser -p kernel session list ## Common Gotchas -1. **Refs change after navigation**: Always re-snapshot after clicking links or submitting forms. - -2. **Wait after actions**: Add waits after clicks/submits that trigger page loads or AJAX. - -3. **Profile not saving**: Make sure to run `agent-browser -p kernel close` to save the profile state. - -4. **Timeout too short**: Increase `KERNEL_TIMEOUT_SECONDS` for workflows with user pauses or slow pages. - -5. **Stealth not working**: Some sites detect bots despite stealth. Use manual login fallback. - -6. **eval for stubborn elements**: If `fill` or `click` don't work, try `eval` with direct DOM manipulation. - -7. **Cross-origin iframes**: Can't interact via agent-browser commands. Use Kernel's Playwright execution. +1. **Refs change after navigation**: Re-snapshot after links, submissions, or major DOM updates. +2. **Wait for outcomes**: Use URL, text, load-state, or JavaScript conditions after asynchronous actions. +3. **Provider settings are launch-time settings**: Close the current session before changing `KERNEL_HEADLESS`, `KERNEL_STEALTH`, timeout, or profile. +4. **Profiles need normal cleanup**: Run `close`; use `kernel browsers delete` only as the orphan fallback. +5. **Stealth is not sufficient for every site**: Compare proxy routing, use manual login, or fall back to direct Playwright for difficult frames. ## Quick Reference @@ -454,11 +441,15 @@ agent-browser -p kernel snapshot -i agent-browser -p kernel fill @eN "text" agent-browser -p kernel click @eM -# Get session info for manual intervention -kernel browsers list -kernel browsers view +# Resolve the underlying session before manual intervention +CDP_URL="$(agent-browser -p kernel get cdp-url)" +SESSION_ID="$(kernel browsers list --status active --limit 100 -o json | + jq -r --arg cdp "$CDP_URL" '.[] | select(.cdp_ws_url == $cdp) | .session_id' | + head -n 1)" +test -n "$SESSION_ID" +kernel browsers view "$SESSION_ID" -# Cleanup +# Cleanup (delete by ID only if close fails) agent-browser -p kernel close ``` From 34c79813e7416ade4ac0e1451d531d8c85e488e9 Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Sat, 25 Jul 2026 09:16:29 -0400 Subject: [PATCH 04/14] docs(skill): sync kernel-auth with latest CLI --- .../kernel-cli/skills/kernel-auth/SKILL.md | 305 +++++++++++------- 1 file changed, 188 insertions(+), 117 deletions(-) diff --git a/plugins/kernel-cli/skills/kernel-auth/SKILL.md b/plugins/kernel-cli/skills/kernel-auth/SKILL.md index 758c1b9..f7add69 100644 --- a/plugins/kernel-cli/skills/kernel-auth/SKILL.md +++ b/plugins/kernel-cli/skills/kernel-auth/SKILL.md @@ -1,6 +1,6 @@ --- name: kernel-auth -description: Setup and manage Kernel authentication connections for any website (Gmail, GitHub, Outlook, or custom domains). Handles managed auth flows with automatic reauthentication support. +description: Create, update, inspect, and troubleshoot Kernel managed auth connections, login and submit flows, profiles, telemetry, health checks, and automatic reauthentication for any website. metadata: { "openclaw": @@ -10,194 +10,265 @@ metadata: } --- -# Kernel Auth Skill +# Kernel Managed Auth -Setup and manage Kernel managed authentication connections for **any website** with safety checks and reauthentication support. +Use `kernel auth connections` to keep a named browser profile authenticated to a website. Use the same connection for initial login, later human-assisted login, and automatic reauthentication. -## Quick Start +## Safe defaults + +1. Scope every resource to the intended project. Set `KERNEL_PROJECT` once or pass global `--project ` to every command. +2. Reuse an existing connection for the same domain and profile instead of creating duplicates. +3. Prefer credential references over putting secrets in command arguments, logs, chat, or shell history. +4. Keep profile downloads outside repositories; they can contain cookies and authenticated browser state. +5. Delete temporary browsers, connections, profiles, and downloaded state after testing. + +Check for an existing connection: ```bash -kernel-auth setup gmail +kernel auth connections list \ + --domain example.com \ + --profile-name example-main \ + -o json ``` -Then visit the URL printed to complete login. +## Create a connection -> **Works for any website** — See [Using Custom Domains](#using-custom-domains) for any other site. +A connection requires a domain and profile name. Kernel creates the profile automatically if it does not exist. + +```bash +CONNECTION_ID=$( + kernel auth connections create \ + --domain example.com \ + --profile-name example-main \ + -o json | jq -r '.id' +) +``` -## Usage +Add only settings required by the site: ```bash -kernel-auth setup [--profile-name ] +kernel auth connections create \ + --domain example.com \ + --profile-name example-main \ + --login-url https://example.com/login \ + --allowed-domain auth.example.com \ + --credential-name example-credential \ + --proxy-id prx_123 \ + --health-check-interval 3600 \ + --telemetry=console,network \ + -o json ``` -### Built-in Services +Important creation behavior: + +- Repeat `--allowed-domain` for non-standard redirect domains. Common SSO provider domains are allowed automatically. +- Use either `--credential-name` or `--credential-provider`, not both. +- With `--credential-provider `, omit `--credential-path` to enable provider lookup by domain automatically. Use `--credential-path ` to select a specific external item. +- Prefer `--proxy-id` over `--proxy-name` for a stable, unambiguous reference. +- Credentials are saved after successful login by default; `--no-save-credentials` disables this and usually prevents unattended reauthentication. +- Health checks and automatic reauthentication default on server-side. The health-check interval defaults to one hour; accepted minimums depend on the organization's plan. -- `gmail` → gmail.com -- `github` → github.com -- `outlook` → outlook.com +A connection's domain and profile cannot be changed with `update`; create another connection when either must differ. -### Using Custom Domains +## Update a connection -For any other website, use the `--domain` flag: +Updates are partial except where noted: ```bash -kernel-auth setup --domain amazon.com --profile-name amazon-main -kernel-auth setup --domain linkedin.com -kernel-auth setup --domain example.com --profile-name custom-site +kernel auth connections update "$CONNECTION_ID" \ + --login-url https://example.com/sign-in \ + --health-check-interval 3600 \ + --save-credentials \ + -o json ``` -### Examples +Non-obvious update behavior: + +- `--allowed-domain` replaces the entire allowed-domain list; repeat it with every domain to retain. +- Pass `--login-url ''` to clear a custom login URL. +- When changing to an external provider's domain lookup, pass both `--credential-provider ` and `--credential-auto`. Unlike create, update does not infer auto lookup. +- Use either `--save-credentials` or `--no-save-credentials`. +- A telemetry category list merges those categories into the connection's existing config for future managed-auth sessions. + +Inspect the result after every update: ```bash -kernel-auth setup gmail -kernel-auth setup github --profile-name github-work -kernel-auth setup outlook +kernel auth connections get "$CONNECTION_ID" -o json | jq '{ + id, + domain, + profile_name, + status, + flow_status, + flow_step, + can_reauth, + can_reauth_reason, + health_checks, + health_check_interval, + browser_telemetry +}' ``` -## Authentication Flow +## Start and complete a login -1. **Create auth connection** — Sets up a managed auth profile (domain + profile name) -2. **Initiate login session** — Generates a hosted login URL -3. **You visit URL** — Complete the login flow on your device/browser -4. **Login state stored in profile** — Kernel saves your authenticated session -5. **Use authenticated browser** — Create browser sessions with that profile, automatically logged in +Start a login or reauthentication flow with the existing connection: -## Key Concepts +```bash +kernel auth connections login "$CONNECTION_ID" -o json +``` -### Auth Connections -- Each connection ties a service domain to a profile name -- Connections can be reused for multiple browser sessions -- Status: `AUTHENTICATED` (user completed login, state stored) or `NEEDS_AUTH` (never logged in or login session expired) +The response tells you whether Kernel started a `LOGIN` or `REAUTH` flow and includes `hosted_url`, `live_view_url`, and `flow_expires_at`. Treat these URLs as sensitive and open the hosted URL directly rather than sending it through link-previewing chat or email clients. -### Login Sessions -- Login sessions (the hosted URL) expire after a generous timeframe as cleanup -- If you don't complete login within that window, the session is deleted -- The connection itself stays — just initiate a new login session +Track the flow in another terminal: -Check connection status: ```bash -kernel auth connections list # Check status -kernel auth connections get # Get connection details +kernel auth connections follow "$CONNECTION_ID" ``` -If a connection shows `NEEDS_AUTH`: +Or poll the current state and canonical prompts: + ```bash -kernel-auth setup # Re-initiate login session with fresh URL +kernel auth connections get "$CONNECTION_ID" -o json | jq '{ + status, + flow_status, + flow_step, + flow_expires_at, + fields, + choices, + external_action_message, + website_error, + error_code, + error_message +}' ``` -### Why Manual URL Visit? -- **Login sessions are time-bound** — If you don't visit within the window, they expire (cleanup) -- **Prevent auto-opening** — Avoid Telegram/email clients accidentally consuming the link -- **Control is yours** — You visit the URL when you're ready - -## Checking Status +When `flow_step` is `AWAITING_INPUT`, prefer the canonical IDs returned in `fields` and `choices`: ```bash -# List all auth connections -kernel auth connections list -o json +# Submit one or more fields as one submit mode. +kernel auth connections submit "$CONNECTION_ID" \ + --field-value 'field_email=' \ + --field-value 'field_password=' + +# Or select exactly one returned choice. +kernel auth connections submit "$CONNECTION_ID" \ + --choice-id 'choice_sms' +``` + +Use exactly one submit mode per call. Do not combine canonical fields, a canonical choice, or any legacy selector in the same call. `--field` is legacy; use `--field-value` when `fields` is present. Legacy-only flows may still require one of: -# Check specific connection -kernel auth connections get -o json | jq '.status' +```bash +kernel auth connections submit "$CONNECTION_ID" --mfa-option-id sms +kernel auth connections submit "$CONNECTION_ID" --sign-in-option-id '' +kernel auth connections submit "$CONNECTION_ID" --sso-provider google +kernel auth connections submit "$CONNECTION_ID" --sso-button-selector '' ``` -## Using Authenticated Browsers +A successful `submit` means the input was accepted for processing, not that authentication has completed. Wait for `flow_status=SUCCESS` and `status=AUTHENTICATED`. + +If a stale flow is past `flow_expires_at`, call `login` again to supersede it. Do not delete the connection or profile just to refresh an expired flow. -Once auth is connected, create browser sessions with that profile: +## Telemetry + +Configure telemetry when creating a connection, updating defaults for future managed-auth sessions, or overriding one login: ```bash -# Create browser with Gmail auth already loaded -kernel browser create --profile-name gmail-main --stealth -o json +# Reset to the default telemetry categories. +kernel auth connections update "$CONNECTION_ID" --telemetry=all -# Browser will be logged into Gmail automatically -``` +# Disable telemetry. +kernel auth connections update "$CONNECTION_ID" --telemetry=off -## Important Notes +# Enable selected categories on future managed-auth sessions. +kernel auth connections update "$CONNECTION_ID" --telemetry=console,network,page -### ⚠️ Profile Deletion = Cascade Delete -Deleting a Kernel profile deletes ALL connections attached to it: -```bash -kernel profile delete gmail-main # Deletes ALL gmail-main connections +# Override only this login; the override merges onto the connection config. +kernel auth connections login "$CONNECTION_ID" --telemetry=screenshot,network ``` -Use sparingly. Better to refresh auth than delete and recreate. +Settable categories are `console`, `network`, `page`, `interaction`, `control`, `connection`, `system`, `screenshot`, and `captcha`. `all` means the default category set, not every category. A category list on create selects that list; on update it merges into the current selection. To remove selected categories, reset with `off`, then enable the desired set in a second update. + +This setting applies to managed-auth browser sessions. Configure telemetry separately on ordinary sessions created later with `kernel browsers create`. -### 🔗 Telegram & Link Previews -If you send auth URLs via Telegram, disable link previews in settings: -- Settings → Privacy & Security → Link Preview → Never show +## Diagnose reauthentication -Otherwise Telegram auto-opens the URL and consumes the code. +Do not infer reauthentication readiness from `status` alone: -### 🌐 Network Requirements -Kernel auth requires: -- Outbound HTTPS to Kernel's managed auth service -- Browser with JavaScript enabled -- Cookie/session storage support +```bash +kernel auth connections get "$CONNECTION_ID" -o json | jq '{ + status, + can_reauth, + can_reauth_reason, + auto_reauth, + health_checks, + health_check_interval, + credential, + save_credentials +}' +``` -## Scripts +Automatic reauthentication requires scheduled health checks, automatic reauth enabled, and a feasible credential or recorded login plan. `can_reauth=false` means a human action is currently required; inspect `can_reauth_reason` rather than repeatedly restarting the flow. Common blockers include no prior successful login, no credential, no viable plan, external action, or an unavailable TOTP/SMS/email code. -- `setup` — Create connection, generate login URL, display instructions -- No background watchers — You control when/if you visit the URL +For a connection in `NEEDS_AUTH`: -## Troubleshooting +1. Attach or correct its credential reference with `update` when one is available. +2. Call `login` on the existing connection. +3. Complete any canonical fields, choices, or external action. +4. Confirm `status=AUTHENTICATED` and `can_reauth=true`. +5. Check the timeline for subsequent health-check or reauth outcomes. -### "Code already used" -The auth code was consumed. This happens if: -- You visited the URL twice -- Telegram/email client auto-opened it -- Someone else completed the login first +## Inspect the timeline -Solution: Run `kernel-auth setup ` again to get a fresh code. +Timeline events are ordered most recent first: -### "Code expired" -Codes expire after ~40 minutes. Re-run setup to generate a new one. +```bash +kernel auth connections timeline "$CONNECTION_ID" +kernel auth connections timeline "$CONNECTION_ID" --type reauth +kernel auth connections timeline "$CONNECTION_ID" --type health_check --page 1 --per-page 50 +``` -### "Connection not found" -The connection may have been deleted. Run setup again to create it. +JSON output is an object containing `events`, `page`, `per_page`, and `has_more`: -### Auth Status is NEEDS_AUTH -You didn't complete the login within the session window, or you need to re-authenticate. Re-initiate login: ```bash -kernel-auth setup gmail +kernel auth connections timeline "$CONNECTION_ID" -o json | + jq '.events[] | {timestamp, type, status, step, browser_session_id, error_code, error_message, website_error}' ``` -## Integration with OpenClaw +Use `login` events for human/initial attempts, `reauth` for automatic or subsequent authentication attempts, and `health_check` to see when session validity changed between `AUTHENTICATED` and `NEEDS_AUTH`. -The auth skill integrates with OpenClaw cron jobs: -1. Cron job checks auth status before running -2. If `AUTHENTICATED`, proceeds with browser automation -3. If not, sends message requesting reauthentication -4. User confirms, system re-runs auth flow +## Use and protect the profile + +Create an ordinary browser from the authenticated profile: -Example from GMAIL_DAILY_WORKFLOW.md: ```bash -# Daily cron checks this before scraping -AUTH_STATUS=$(kernel auth connections list -o json | jq -r ".[] | select(.domain == \"gmail.com\") | .status") -if [ "$AUTH_STATUS" != "AUTHENTICATED" ]; then - echo "Reauthentication needed" - exit 1 -fi +kernel browsers create --profile-name example-main --stealth -o json ``` -## Advanced +Delete that browser when finished: -### Programmatic Auth Check ```bash -# Get auth status -kernel auth connections list -o json | jq '.[] | {id, status, domain}' - -# Delete and recreate -kernel profile delete gmail-main --yes -kernel-auth setup gmail +kernel browsers delete ``` -### Multiple Accounts -Create separate profiles for each account: +Rename or download profile state with the current profile commands: + ```bash -kernel-auth setup gmail --profile-name gmail-personal -kernel-auth setup gmail --profile-name gmail-work +kernel profiles update example-main --name example-primary -o json + +PROFILE_DIR=$(mktemp -d) +kernel profiles download example-primary --to "$PROFILE_DIR" --format tar.zst +# Inspect locally; never commit the extracted state. +rm -rf "${PROFILE_DIR:?}" ``` -Then use the appropriate profile when creating browsers: +`--format` accepts `tar.zst` (default) or `tar`; both are extracted into `--to`. If the profile has no saved data yet, download reports that it must first be used in a browser session. + +## Cleanup temporary setup + +Deleting a connection terminates its workflow and cancels any in-progress login. It is separate from deleting the profile: + ```bash -kernel browser create --profile-name gmail-work --stealth +kernel auth connections delete "$CONNECTION_ID" # confirm interactively +kernel profiles delete example-main # only if its saved state is disposable ``` + +Use `--yes` only in automation after verifying the exact connection ID or profile name. Never delete a reused authenticated profile as routine reauthentication recovery. From 9bf534b084615e4ce1b7962c7f4381afed6305ae Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Sat, 25 Jul 2026 09:19:19 -0400 Subject: [PATCH 05/14] docs(skill): sync kernel-browser-harness with latest CLI --- .../skills/kernel-browser-harness/SKILL.md | 118 ++++++++++++------ 1 file changed, 79 insertions(+), 39 deletions(-) diff --git a/plugins/kernel-cli/skills/kernel-browser-harness/SKILL.md b/plugins/kernel-cli/skills/kernel-browser-harness/SKILL.md index b5c1295..a8b4316 100644 --- a/plugins/kernel-cli/skills/kernel-browser-harness/SKILL.md +++ b/plugins/kernel-cli/skills/kernel-browser-harness/SKILL.md @@ -32,22 +32,34 @@ Use this skill when you need to: ## Basic Usage -Fast path: create **exactly one** Kernel browser, save the JSON, derive every value from that saved JSON, then pass the exact CDP URL to the first harness call. Do not re-run `kernel browsers create` just to recover variables or copy/paste values from terminal output. +Fast path: create **exactly one** Kernel browser, save its raw JSON response in a private, unique temporary file, and derive every value from that file. Treat `cdp_ws_url` as a credential: do not log it or copy it between tasks. + +Run this as one Bash script so the `EXIT` trap cleans up after success, failure, or interruption: ```bash -SESSION=$(kernel browsers create --stealth --timeout 1800 -o json) -printf '%s\n' "$SESSION" > /tmp/kernel-session.json -SESSION_ID=$(jq -r '.session_id' /tmp/kernel-session.json) -CDP_WS=$(jq -r '.cdp_ws_url' /tmp/kernel-session.json) -echo "live view: $(jq -r '.browser_live_view_url // empty' /tmp/kernel-session.json)" +set -euo pipefail + +SESSION_FILE=$(mktemp "${TMPDIR:-/tmp}/kernel-browser-harness.XXXXXX.json") +SESSION_ID= +cleanup() { + if [ -n "$SESSION_ID" ]; then + BU_NAME="$SESSION_ID" browser-harness --reload >/dev/null 2>&1 || true + kernel browsers delete "$SESSION_ID" || true + fi + rm -f "$SESSION_FILE" +} +trap cleanup EXIT + +kernel browsers create --stealth --timeout 1800 -o json >"$SESSION_FILE" +SESSION_ID=$(jq -er '.session_id' "$SESSION_FILE") +CDP_WS=$(jq -er '.cdp_ws_url' "$SESSION_FILE") +echo "live view: $(jq -r '.browser_live_view_url // empty' "$SESSION_FILE")" BU_NAME="$SESSION_ID" BU_CDP_WS="$CDP_WS" browser-harness <<'PY' new_tab("https://news.ycombinator.com") wait_for_load() print(page_info()) PY - -kernel browsers delete "$SESSION_ID" ``` Kernel browsers boot **headful by default** — the create response includes `browser_live_view_url`. Print it so the user can watch the agent work, or pass `--headless` to opt out (no live view, smaller image). @@ -68,9 +80,17 @@ Common choices when minting a session for harness use: See `kernel browsers create --help` for the full list. +Default stealth proxy control is a runtime update, not a create flag. If a stealth session must connect directly, run this **before** the first harness call: + +```bash +kernel browsers update "$SESSION_ID" --disable-default-proxy +``` + +Re-enable the default stealth proxy with `--disable-default-proxy=false`. Use `--proxy-id` or `--clear-proxy` instead when changing an explicitly configured proxy. + ## Multi-Step Usage -For tasks that span more than one shell call — mint, drive, inspect, drive more, tear down — the daemon does the work. `browser-harness` (addressed by `BU_NAME`) holds the CDP connection between invocations, so `BU_CDP_WS` only needs to be set on the **first** call. +For tasks that span more than one harness invocation — mint, drive, inspect, drive more, tear down — the daemon holds the CDP connection. Put every invocation before the owning script's `EXIT` trap runs; `BU_CDP_WS` is needed only on the **first** call for that `BU_NAME`. With `BU_NAME=$SESSION_ID` as the convention, every subsequent harness call is just: @@ -82,48 +102,68 @@ PY — and parallel sessions are automatic: each Kernel browser has a unique session ID, so two `BU_NAME=$SESSION_ID` invocations against different sessions never collide on the daemon socket. -If you lose `$SESSION_ID` across shell calls, recover it with `kernel browsers list -o json | jq`. +If separate shell processes are unavoidable, let the parent orchestrator own the response file and final cleanup; a trap in a short-lived mint subprocess would delete the session too early. Reload `session_id` and `cdp_ws_url` from the saved JSON when variables are lost. If the file is gone, inspect active sessions with `kernel browsers list --limit 100 -o json | jq`; use `--offset` for additional pages and identify the exact session before continuing or deleting anything. For replay recording around a harness session, see the `kernel-cli` skill's replays reference. -## Common Gotchas +## Parallel Sessions -1. **`BU_CDP_WS unreachable` mid-task**: the Kernel session probably hit its idle timeout. Default is 60s — pass `--timeout 1800` (or whatever fits the task) at create time. +Create one Kernel browser per worker and keep each worker's response file, session ID, CDP URL, and daemon name together. Never share a response file or `BU_NAME` between workers. Register each session with parent cleanup as soon as its create succeeds: -2. **CDP URL is a JWT-signed `wss://`** endpoint — paste it directly into `BU_CDP_WS`, no rewriting or stripping. - -3. **Daemon won't pick up a new session**: if you mint a new Kernel browser but reuse a stale `BU_NAME`, the daemon stays connected to the old CDP URL. Using `BU_NAME=$SESSION_ID` avoids this entirely (new session, new socket). If you do hit it manually, `browser-harness --reload` stops the daemon so the next call connects fresh. +```bash +set -euo pipefail +declare -a SESSION_IDS=() SESSION_FILES=() + +cleanup_parallel() { + for sid in "${SESSION_IDS[@]}"; do + BU_NAME="$sid" browser-harness --reload >/dev/null 2>&1 || true + kernel browsers delete "$sid" || true + done + rm -f "${SESSION_FILES[@]}" +} +trap cleanup_parallel EXIT + +mint() { + local sid_var=$1 cdp_var=$2 file_var=$3 file sid cdp + file=$(mktemp "${TMPDIR:-/tmp}/kernel-browser-harness.XXXXXX.json") + SESSION_FILES+=("$file") + kernel browsers create --stealth --timeout 1800 -o json >"$file" + sid=$(jq -er '.session_id' "$file") + SESSION_IDS+=("$sid") + cdp=$(jq -er '.cdp_ws_url' "$file") + printf -v "$sid_var" '%s' "$sid" + printf -v "$cdp_var" '%s' "$cdp" + printf -v "$file_var" '%s' "$file" +} + +mint SESSION_A CDP_A FILE_A +mint SESSION_B CDP_B FILE_B + +BU_NAME="$SESSION_A" BU_CDP_WS="$CDP_A" browser-harness <<'PY' & +new_tab("https://example.com"); wait_for_load(); print(page_info()) +PY +PID_A=$! -4. **Extra creates waste time and leak money**: one task should usually call `kernel browsers create` once. Save the `-o json` response immediately and reuse it; do not run a second create after seeing output, and do not hardcode IDs or CDP URLs from a previous command. +BU_NAME="$SESSION_B" BU_CDP_WS="$CDP_B" browser-harness <<'PY' & +new_tab("https://example.org"); wait_for_load(); print(page_info()) +PY +PID_B=$! -5. **Live view URL is for the human**: print `browser_live_view_url` from the create response so the user can watch. The agent only needs `cdp_ws_url`. +wait "$PID_A" "$PID_B" +``` -6. **Always tear down**: run `kernel browsers delete "$SESSION_ID"` when the task ends. Sessions bill until idle timeout. If you are wrapping several steps in a script, register a cleanup `trap` after `SESSION_ID` is known; for ad hoc command use, an explicit delete at the end is clearer. If you lost the SID, `kernel browsers list -o json | jq` recovers it. +The trap stops each local daemon, deletes each billed Kernel session, and removes both response files. Do not rely on `wait` or the idle timeout for cleanup. -7. **Skill responsibilities**: `browser-harness`'s `SKILL.md` owns helper usage (`new_tab`, `page_info`, `js`, …) and the heredoc form. The `kernel-cli` skill owns `kernel browsers create / list / get / delete` and `replays` lifecycle. This skill only owns the CLI-to-harness wiring. +## Common Gotchas -## Quick Reference +1. **`BU_CDP_WS unreachable` mid-task**: the Kernel session probably hit its idle timeout. Default is 60s — pass `--timeout 1800` (or whatever fits the task) at create time. -```bash -# Mint once -SESSION=$(kernel browsers create --stealth --timeout 1800 -o json) -printf '%s\n' "$SESSION" > /tmp/kernel-session.json -SESSION_ID=$(jq -r '.session_id' /tmp/kernel-session.json) -CDP_WS=$(jq -r '.cdp_ws_url' /tmp/kernel-session.json) +2. **Keep URLs separated**: treat `cdp_ws_url` as an opaque secret and pass it directly into `BU_CDP_WS` without rewriting it. `browser_live_view_url` is only for the human observer. -# Drive (first call seeds the daemon with the exact signed CDP URL) -BU_NAME="$SESSION_ID" BU_CDP_WS="$CDP_WS" browser-harness <<'PY' -new_tab("https://example.com"); print(page_info()) -PY +3. **Daemon won't pick up a new session**: if you mint a new Kernel browser but reuse a stale `BU_NAME`, the daemon stays connected to the old CDP URL. Using `BU_NAME=$SESSION_ID` avoids this. Otherwise, run `BU_NAME= browser-harness --reload` before connecting that name to a new endpoint. -# Later calls reuse BU_NAME only -BU_NAME="$SESSION_ID" browser-harness <<'PY' -print(js("document.title")) -PY +4. **Extra creates waste time and leak money**: one task should usually call `kernel browsers create` once. Save and reuse its `-o json` response; never hardcode IDs or CDP URLs from a previous task. -# Recovery if you lost the SID across shells -kernel browsers list -o json | jq +5. **Always tear down both layers**: stop the named local daemon with `BU_NAME="$SESSION_ID" browser-harness --reload`, then run `kernel browsers delete "$SESSION_ID"` and remove the saved JSON file. `--reload` does not delete the Kernel browser; Kernel sessions bill until deletion or idle timeout. Put all three operations in an `EXIT` trap for scripts. -# Teardown -kernel browsers delete "$SESSION_ID" -``` +`browser-harness`'s skill owns helper usage (`new_tab`, `page_info`, `js`, and so on). The `kernel-cli` skill owns browser and replay lifecycles. Keep this skill limited to CLI-to-harness wiring. From 7a0138dc15fe1c48b2eabfe29fc1e8ac5715edfb Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Sat, 25 Jul 2026 09:20:52 -0400 Subject: [PATCH 06/14] docs(kernel-cli): sync CLI skill with current commands --- plugins/kernel-cli/skills/kernel-cli/SKILL.md | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/plugins/kernel-cli/skills/kernel-cli/SKILL.md b/plugins/kernel-cli/skills/kernel-cli/SKILL.md index 51ed376..ce52e7b 100644 --- a/plugins/kernel-cli/skills/kernel-cli/SKILL.md +++ b/plugins/kernel-cli/skills/kernel-cli/SKILL.md @@ -1,6 +1,6 @@ --- name: kernel-cli -description: Complete guide to Kernel CLI - cloud browser platform with automation, deployment, and management +description: Use the Kernel CLI to manage cloud browsers, apps, profiles, proxies, managed auth, API keys, projects, and organization limits. Use when installing or authenticating the CLI, looking up current command syntax, or choosing the right command group. --- # Kernel CLI @@ -9,8 +9,10 @@ The Kernel CLI provides command-line access to Kernel's cloud browser platform f ## Installation -- Homebrew: `brew install kernel/tap/kernel` (>=v0.13.4) -- npm: `npm install -g @onkernel/cli` (>=v0.13.4) +- Homebrew: `brew install onkernel/tap/kernel` +- npm: `npm install -g @onkernel/cli` + +Verify with `kernel --version`. Use `kernel --help` as the source of truth for the installed version. ## Authentication @@ -39,6 +41,33 @@ kernel browsers computer screenshot --to screenshot.png kernel browsers delete ``` +## Safe Operation + +- Prefer `-o json` plus `jq` for scripts; deploy and invoke emit JSONL rather than one JSON object. +- Use `--project ` or `KERNEL_PROJECT` when an API key can access multiple projects. +- Keep confirmation prompts for destructive operations unless non-interactive execution is intentional. +- Delete created browser sessions and pools after testing. Never echo, log, commit, or share API keys, credentials, or proxy passwords. + +## Project, API Key, and Organization Administration + +```bash +# Rename, archive, or reactivate a project (provide at least one update) +kernel projects update --name -o json +kernel projects update --status archived -o json +kernel projects update --status active -o json + +# Look up a soft-deleted API key +kernel api-keys get --include-deleted -o json + +# Inspect limits before changing the default project cap +kernel org limits get -o json +kernel org limits set --default-project-max-concurrent-sessions -o json +``` + +For `org limits set`, `0` removes the default cap; the value cannot exceed the organization concurrency limit. + +Rotate an API key interactively with `kernel api-keys rotate `. Use `--days-to-expire <1-3650>` to set the replacement key lifetime and `--expire-in-days ` to set the old key's grace period (default 7 days; `0` revokes it immediately). The output contains the replacement plaintext key once: keep it out of logs, migrate callers during the grace period, and then verify the old key no longer works. + ## References - [Browser Management](./references/browser-management.md) - Create, list, view, and delete browser sessions From efc7fb93e7ba54754cc6e4aecac6d4a5cf65d2b5 Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Sat, 25 Jul 2026 09:16:47 -0400 Subject: [PATCH 07/14] docs(skill): sync profile-website-bot-detection with latest CLI --- .../profile-website-bot-detection/SKILL.md | 165 +++++++++--------- 1 file changed, 81 insertions(+), 84 deletions(-) diff --git a/plugins/kernel-cli/skills/profile-website-bot-detection/SKILL.md b/plugins/kernel-cli/skills/profile-website-bot-detection/SKILL.md index 312c0b7..6170c7b 100644 --- a/plugins/kernel-cli/skills/profile-website-bot-detection/SKILL.md +++ b/plugins/kernel-cli/skills/profile-website-bot-detection/SKILL.md @@ -10,73 +10,105 @@ Analyzes a target website to identify bot detection vendors, their specific prod ## Prerequisites - Kernel CLI installed and authenticated -- Node.js 22+ installed -- `jq` installed (`brew install jq` or `apt install jq`) -- `KERNEL_API_KEY` environment variable set. If it is not set, prompt the user to supply. +- Bash, Node.js 22+, and `jq` installed +- `KERNEL_API_KEY` exported for the analysis script; never print it or ask the user to paste it into chat ## Comparative Workflow (Recommended) -Compare bot detection behavior between stealth and non-stealth browsers to evaluate stealth effectiveness. +Compare bot detection behavior between stealth and non-stealth browsers while controlling the network path. Run Steps 1-7 in the same Bash process so the cleanup trap remains armed. Do not run Step 2 as a standalone non-interactive shell: its EXIT trap will delete the sessions when that shell returns. -### Step 1: Create Both Browser Types +### Step 1: Prepare the Analyzer -```bash -# Create stealth browser (with -s flag) -kernel browsers create -s --viewport 1920x1080@25 -t 300 -# Save session_id as STEALTH_ID - -# Create non-stealth headful browser (no -s flag) -kernel browsers create --viewport 1920x1080@25 -t 300 -# Save session_id as NORMAL_ID -``` - -### Step 2: Run Analysis on Both Browsers +Install dependencies before creating billable browser sessions: ```bash cd scripts npm install # first run only +export TARGET_URL='https://example.com' +test -n "${KERNEL_API_KEY:-}" || { echo "KERNEL_API_KEY is not set" >&2; exit 1; } +``` + +### Step 2: Create Both Browser Types and Arm Cleanup -# Test with stealth browser -KERNEL_API_KEY=$KERNEL_API_KEY KERNEL_BROWSER_ID=$STEALTH_ID TARGET_URL= BROWSER_MODE=stealth npm run analyze +Use JSON output to capture exact session IDs. The EXIT trap cleans up the first session if the second creation or a later analysis fails. -# Test with non-stealth browser -KERNEL_API_KEY=$KERNEL_API_KEY KERNEL_BROWSER_ID=$NORMAL_ID TARGET_URL= BROWSER_MODE=normal npm run analyze +```bash +set -euo pipefail +STEALTH_ID= +NORMAL_ID= + +cleanup() { + local ids=() + [[ -n "${STEALTH_ID:-}" ]] && ids+=("$STEALTH_ID") + [[ -n "${NORMAL_ID:-}" ]] && ids+=("$NORMAL_ID") + if ((${#ids[@]})); then + kernel browsers delete "${ids[@]}" || true + fi +} +trap cleanup EXIT +trap 'exit 130' INT +trap 'exit 143' TERM + +STEALTH_ID=$(kernel browsers create --stealth --viewport 1920x1080@25 --timeout 300 --output json | jq -er '.session_id') +NORMAL_ID=$(kernel browsers create --viewport 1920x1080@25 --timeout 300 --output json | jq -er '.session_id') + +# Isolate browser stealth behavior: route the stealth session directly, like the normal session. +kernel browsers update "$STEALTH_ID" --disable-default-proxy >/dev/null ``` -### Step 3: Compare Results +A stealth session otherwise uses Kernel's default stealth proxy, while a normal session connects directly. Without the update, the result measures **stealth plus proxy** versus **normal plus direct**, not stealth alone. Apply `--disable-default-proxy` only to the stealth session; the API rejects it for non-stealth sessions. -Compare the vendor detections and blocking behavior: +Choose one network design before navigating: + +- **Browser-mode comparison (recommended):** Keep the update above so both sessions connect directly. +- **Platform-default comparison:** Omit the update and report the stealth result as `stealth + default proxy`; do not attribute differences solely to stealth. +- **Explicit-proxy comparison:** Optionally validate a configured proxy with `kernel proxies check "$PROXY_ID" --url https://example.com --output json`, add `--proxy-id "$PROXY_ID"` to both create commands, and omit the update. This controls proxy configuration, though provider rotation can still produce different exit IPs. + +### Step 3: Run Analysis on Both Browsers ```bash -# Set the hostname folder (e.g., chase-com for chase.com) -HOST=chase-com +KERNEL_BROWSER_ID="$STEALTH_ID" TARGET_URL="$TARGET_URL" BROWSER_MODE=stealth npm run analyze +KERNEL_BROWSER_ID="$NORMAL_ID" TARGET_URL="$TARGET_URL" BROWSER_MODE=normal npm run analyze +``` -# Quick verdict comparison -echo "=== STEALTH VERDICT ===" && cat output/$HOST/stealth/report-*.json | jq '.summary.verdict' -echo "=== NORMAL VERDICT ===" && cat output/$HOST/normal/report-*.json | jq '.summary.verdict' +### Step 4: Compare Results -# Compare block status -echo "=== STEALTH BLOCKED ===" && cat output/$HOST/stealth/report-*.json | jq '.summary | {isBlocked, blockedPages, blockedVendors}' -echo "=== NORMAL BLOCKED ===" && cat output/$HOST/normal/report-*.json | jq '.summary | {isBlocked, blockedPages, blockedVendors}' +Compare the newest report from each mode rather than concatenating multiple JSON documents: -# Compare detected vendors -echo "=== STEALTH VENDORS ===" && cat output/$HOST/stealth/report-*.json | jq '.summary.vendorNames' -echo "=== NORMAL VENDORS ===" && cat output/$HOST/normal/report-*.json | jq '.summary.vendorNames' +```bash +# Set the hostname folder (e.g., chase-com for chase.com). +HOST=chase-com +latest_report() { + local reports=("$1"/report-*.json) + [[ -e "${reports[0]}" ]] || return 1 + printf '%s\n' "${reports[@]}" | sort | tail -n 1 +} +STEALTH_REPORT=$(latest_report "output/$HOST/stealth") +NORMAL_REPORT=$(latest_report "output/$HOST/normal") + +echo "=== STEALTH VERDICT ===" && jq '.summary.verdict' "$STEALTH_REPORT" +echo "=== NORMAL VERDICT ===" && jq '.summary.verdict' "$NORMAL_REPORT" + +echo "=== STEALTH BLOCKED ===" && jq '.summary | {isBlocked, blockedPages, blockedVendors}' "$STEALTH_REPORT" +echo "=== NORMAL BLOCKED ===" && jq '.summary | {isBlocked, blockedPages, blockedVendors}' "$NORMAL_REPORT" + +echo "=== STEALTH VENDORS ===" && jq '.summary.vendorNames' "$STEALTH_REPORT" +echo "=== NORMAL VENDORS ===" && jq '.summary.vendorNames' "$NORMAL_REPORT" ``` -### Step 4: Interpret Comparison +### Step 5: Interpret Comparison | Scenario | Stealth | Normal | Meaning | |----------|---------|--------|---------| -| No vendors detected | 0 | 0 | Site has no bot detection | +| No vendors detected | 0 | 0 | No signals observed; do not assume the site has no bot detection | | Same vendors, no blocks | N | N | Bot detection present, both pass | -| Normal blocked, stealth passes | 0 blocks | Blocked | Stealth mode is effective | -| Both blocked | Blocked | Blocked | Bot detection defeats stealth | -| Different challenge types | Lighter | Harder | Stealth reduces suspicion | +| Normal blocked, stealth passes | 0 blocks | Blocked | With a controlled network, stealth is effective | +| Both blocked | Blocked | Blocked | Both tested configurations are blocked | +| Different challenge types | Lighter | Harder | With a controlled network, stealth likely reduces suspicion | -### Step 5: Provide Summary +### Step 6: Provide Summary -After running the comparative analysis, provide a detailed summary report to the user that includes: +After running the comparative analysis, state the network design explicitly. Claim stealth effectiveness only when both sessions used the same network class. **Summary Report Template:** @@ -84,9 +116,10 @@ After running the comparative analysis, provide a detailed summary report to the ## Bot Detection Comparative Analysis: [TARGET_URL] ### Verdict +- **Network Design**: [both direct / same explicit proxy config / platform defaults] - **Stealth Browser**: [verdict from summary.verdict] - **Normal Browser**: [verdict from summary.verdict] -- **Stealth Effectiveness**: [Effective/Ineffective/Inconclusive] +- **Stealth Effectiveness**: [Effective/Ineffective/Inconclusive; use Inconclusive for mixed network paths] ### Block Status | Browser | Blocked | Block Type | Evidence | @@ -117,13 +150,17 @@ Use the JSON reports to populate this template: - `summary.vendorNames` - List of detected vendors - `vendorDetections` - Detailed vendor/product information -### Step 6: Cleanup +### Step 7: Cleanup + +Delete both sessions immediately after collecting reports. `browsers delete` is non-interactive and accepts multiple IDs; it has no `-y` flag. ```bash -kernel browsers delete -y $STEALTH_ID -kernel browsers delete -y $NORMAL_ID +cleanup +trap - EXIT INT TERM ``` +If the shell was interrupted or the variables were lost, use `kernel browsers list` to find the created sessions and delete each with `kernel browsers delete `. + --- ## Interpreting Results @@ -194,46 +231,6 @@ The JSON report includes: - `networkRequests/networkResponses`: All requests with vendor matching - `cookies`: All cookies with vendor attribution -## Example: Comparative Session - -```bash -# Create both browsers -STEALTH_ID=$(kernel browsers create -s --viewport 1920x1080@25 -t 300 -o json | jq -r '.session_id') -NORMAL_ID=$(kernel browsers create --viewport 1920x1080@25 -t 300 -o json | jq -r '.session_id') - -echo "Stealth: $STEALTH_ID" -echo "Normal: $NORMAL_ID" - -# Run analysis on both -cd scripts -KERNEL_API_KEY=$KERNEL_API_KEY KERNEL_BROWSER_ID=$STEALTH_ID TARGET_URL=chase.com BROWSER_MODE=stealth npm run analyze -KERNEL_API_KEY=$KERNEL_API_KEY KERNEL_BROWSER_ID=$NORMAL_ID TARGET_URL=chase.com BROWSER_MODE=normal npm run analyze - -# Output structure: -# ./output/chase-com/stealth/report-*.json -# ./output/chase-com/stealth/screenshot-*.png -# ./output/chase-com/normal/report-*.json -# ./output/chase-com/normal/screenshot-*.png - -# Quick comparison - check verdicts -echo "--- Stealth verdict ---" -cat output/chase-com/stealth/report-*.json | jq '.summary.verdict' - -echo "--- Normal verdict ---" -cat output/chase-com/normal/report-*.json | jq '.summary.verdict' - -# Detailed vendor comparison -echo "--- Stealth vendors ---" -cat output/chase-com/stealth/report-*.json | jq '.summary.vendorNames' - -echo "--- Normal vendors ---" -cat output/chase-com/normal/report-*.json | jq '.summary.vendorNames' - -# Cleanup -kernel browsers delete -y $STEALTH_ID -kernel browsers delete -y $NORMAL_ID -``` - ## Vendor-Specific Detection Notes ### Akamai From c11d8b3c6446e5c64dc6a70e47c8e533ffcad824 Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Sat, 25 Jul 2026 09:16:39 -0400 Subject: [PATCH 08/14] docs(skill): sync kernel-python-sdk with latest CLI --- .../skills/kernel-python-sdk/SKILL.md | 250 +++++++++++++----- 1 file changed, 180 insertions(+), 70 deletions(-) diff --git a/plugins/kernel-sdks/skills/kernel-python-sdk/SKILL.md b/plugins/kernel-sdks/skills/kernel-python-sdk/SKILL.md index 7269605..f60062e 100644 --- a/plugins/kernel-sdks/skills/kernel-python-sdk/SKILL.md +++ b/plugins/kernel-sdks/skills/kernel-python-sdk/SKILL.md @@ -1,109 +1,219 @@ --- name: kernel-python-sdk -description: Build browser automation scripts using the Kernel Python SDK with Playwright and remote browser management. +description: Build and debug Python browser automation with the Kernel SDK, including browser lifecycle, server-side Playwright, CDP, profiles, proxies, and reliable cleanup. context: fork --- ## When to Use This Skill -Use the Kernel Python SDK when you need to: +Use this skill to write Python that creates and controls Kernel browser sessions. Prefer it for: -- **Build browser automation scripts** - Create Python programs that control remote browsers -- **Execute server-side automation** - Run Playwright code directly in the browser VM without local dependencies -- **Manage browser sessions programmatically** - Create, configure, and control browsers from code -- **Build scalable scraping/testing tools** - Use browser pools and profiles for high-volume automation -- **Deploy automation as actions** - Package scripts as Kernel actions for invocation via API +- Server-side Playwright execution in the browser VM +- Client-side Playwright over CDP +- Persistent profiles and explicit proxy routing +- Browser pools or deployed Python actions -**When NOT to use:** -- For CLI commands (e.g., `kernel browsers create`), use the `kernel-cli` skill instead -- For quick one-off tasks, the CLI may be simpler than writing code +Use the `kernel-cli` skill for shell commands and quick one-off sessions. -## Core Concepts +## Setup -### SDK Architecture +```bash +uv pip install -U kernel +# Add this only for client-side CDP: +uv pip install playwright +``` + +Set `KERNEL_API_KEY` in the environment; do not put it in source. `Kernel()` and `AsyncKernel()` read it automatically. Use the async client consistently—do not call synchronous SDK methods from an async action. + +## Choose an Automation Path + +### Server-side Playwright (default) + +Run JavaScript/TypeScript next to Chrome. This avoids a local CDP round trip and does not require local Playwright: + +```python +from kernel import Kernel + +with Kernel() as client: + browser = client.browsers.create(stealth=True, timeout_seconds=300) + try: + response = client.browsers.playwright.execute( + browser.session_id, + code=""" +await page.goto("https://example.com"); +return { title: await page.title(), url: page.url() }; +""", + timeout_sec=60, + ) + if not response.success: + raise RuntimeError(response.error or response.stderr or "Playwright execution failed") + print(response.result) + finally: + client.browsers.delete_by_id(browser.session_id) +``` + +Pass the session ID as the first positional argument. The code receives `page`, `context`, and `browser`. Use `return`; otherwise `response.result` is empty. `timeout_sec` limits remote code execution, while the SDK's `timeout=` option controls the HTTP request. + +### Client-side Playwright over CDP + +Use CDP when local Playwright tooling or interactive debugging is required: + +```python +import asyncio + +from kernel import AsyncKernel +from playwright.async_api import async_playwright + + +async def main() -> None: + async with AsyncKernel() as client: + kernel_browser = await client.browsers.create( + stealth=True, + timeout_seconds=300, + ) + try: + async with async_playwright() as playwright: + remote = await playwright.chromium.connect_over_cdp( + kernel_browser.cdp_ws_url + ) + context = remote.contexts[0] + page = context.pages[0] if context.pages else await context.new_page() + await page.goto("https://example.com") + print(await page.title()) + finally: + await client.browsers.delete_by_id(kernel_browser.session_id) + + +asyncio.run(main()) +``` + +Reuse the existing default context so pages see the Kernel session's loaded profile state; a new incognito context does not share that state. The SDK client context closes HTTP connections, but it does **not** replace deleting the remote browser. + +## Browser Lifecycle and Cleanup + +- Wrap every created or acquired session in `try/finally` immediately after creation. +- Delete ordinary sessions with `client.browsers.delete_by_id(session_id)`; use `await` with `AsyncKernel`. +- Release pool-acquired sessions through the browser-pool release API instead of deleting them. +- Keep the browser inactivity timeout finite even when cleanup exists. Activity can extend that timeout, so it is not a substitute for explicit cleanup. +- Delete throwaway profiles and proxies only after all sessions using them have ended. Keep intentional persistent profiles. + +## Profiles + +Create profiles before attaching them. Set `save_changes=True` when cookies and local state must persist back to the profile: + +```python +profile = client.profiles.create(name="checkout-login") +browser = client.browsers.create( + profile={"id": profile.id, "save_changes": True}, + stealth=True, +) +try: + # Automate with server-side Playwright or CDP. + ... +finally: + client.browsers.delete_by_id(browser.session_id) +``` + +Deleting the browser ends the session and allows profile changes to finalize. Do not delete the profile first. Use either `{"id": ...}` or `{"name": ...}`, not both. + +The current Python SDK's `profiles.download(id_or_name)` returns a zstd-compressed tar archive. Save it with: + +```python +archive = client.profiles.download(profile.id) +archive.write_to_file("profile.tar.zst") +``` -The SDK is organized into resource-based modules: +Do not pass the CLI-only `--format` concept to the SDK method. Use `kernel profiles download --to ./profile --format tar` when server-side decompression and extraction are needed. The current SDK does not expose profile rename; use `kernel profiles update --name `. -- `kernel.browsers` - Browser session management (create, list, delete) -- `kernel.browsers.playwright` - Server-side Playwright execution -- `kernel.browsers.computer` - OS-level controls (mouse, keyboard, screenshots) -- `kernel.browser_pools` - Pre-warmed browser pool management -- `kernel.profiles` - Persistent browser profiles (auth state) -- `kernel.auth.connections` - Managed auth (create, login, submit, follow, retrieve, delete) -- `kernel.credential_providers` - External credential providers (1Password) -- `kernel.proxies` - Proxy configuration -- `kernel.extensions` - Chrome extension management -- `kernel.deployments` - App deployment -- `kernel.invocations` - Action invocation +## Proxies and Default Stealth Proxy -### Two Automation Approaches +Attach an explicit proxy at creation with `proxy_id`: -**1. Server-side Execution (RECOMMENDED)** -- Execute Playwright code directly in browser VM using `kernel.browsers.playwright.execute(session_id, code="...")` -- `session_id` must be passed as a positional argument (first parameter), not as `id=` keyword -- Response accessed via `response.result` - **MUST use `return` in code to get data back** -- Best for: Most use cases, production automation, parallel execution, actions +```python +proxy = client.proxies.create( + type="datacenter", + config={"country": "US"}, + name="automation-us", +) +try: + client.proxies.check(proxy.id, url="https://example.com") + browser = client.browsers.create(stealth=True, proxy_id=proxy.id) + try: + ... + finally: + client.browsers.delete_by_id(browser.session_id) +finally: + client.proxies.delete(proxy.id) # Only if this proxy was created for this run. +``` + +A target-specific proxy check validates that public HTTP/HTTPS URL. For residential and mobile proxies, it does not guarantee the later browser uses the same exit node; with a custom URL, the check also does not update general proxy health status. + +Proxy routing can be changed on a running browser: + +```python +client.browsers.update(browser.session_id, proxy_id=another_proxy_id) +client.browsers.update(browser.session_id, proxy_id="") # Remove explicit proxy. +``` -**2. CDP Connection (Client-side)** -- Connect Playwright to browser via CDP WebSocket URL -- Code runs locally, browser runs remotely; requires local Playwright installation -- Best for: Complex debugging, specific local development needs +Stealth browsers may use Kernel's default stealth proxy when no explicit proxy is attached. Control it independently: -## Patterns Reference +```python +client.browsers.update(browser.session_id, disable_default_proxy=True) # Direct connection +client.browsers.update(browser.session_id, disable_default_proxy=False) # Re-enable default +``` -**Import Patterns** -- Standard: `from kernel import Kernel` -- For actions: `import kernel` and `from kernel import Kernel` -- For typed payloads: `from typing import TypedDict` -- For CDP: `from playwright.async_api import async_playwright` +`disable_default_proxy` is an update parameter, not a browser-create parameter. The current SDK does not expose proxy rename; use `kernel proxies update --name `. Recreate the proxy to change anything besides its name. -**SDK Initialization** -- `client = Kernel()` reads `KERNEL_API_KEY` from environment automatically +## Deployed Action Pattern -**Action Handler Pattern** ```python from typing import TypedDict -from kernel import Kernel + +import kernel app = kernel.App("app-name") + class TaskInput(TypedDict): task: str -@app.action("action-name") -async def my_action(ctx: kernel.KernelContext, input_data: TaskInput): - # Access input: input_data["task"] or input_data.get("task") - ... -``` -**CDP Connection Pattern (Client-side)** -```python -async with async_playwright() as playwright: - browser = await playwright.chromium.connect_over_cdp(kernel_browser.cdp_ws_url) - context = browser.contexts[0] if browser.contexts else await browser.new_context() - page = context.pages[0] if context.pages else await context.new_page() +@app.action("action-name") +async def run(ctx: kernel.KernelContext, payload: TaskInput): + async with kernel.AsyncKernel() as client: + browser = await client.browsers.create( + invocation_id=ctx.invocation_id, + timeout_seconds=300, + ) + try: + response = await client.browsers.playwright.execute( + browser.session_id, + code="return await page.title();", + ) + if not response.success: + raise RuntimeError(response.error or response.stderr or "Playwright execution failed") + return response.result + finally: + await client.browsers.delete_by_id(browser.session_id) ``` -**Binary Data Handling** +## Binary Results -Binary data (screenshots, PDFs) returns as Node.js Buffer: `{'data': [byte_array], 'type': 'Buffer'}` +Server-side screenshots and PDFs can return a Node.js Buffer-shaped result: ```python -# Follow canonical pattern above, then: +response = client.browsers.playwright.execute( + browser.session_id, + code="return await page.screenshot({ fullPage: true });", +) if response.success and response.result: - data = bytes(response.result['data']) - with open("output.png", "wb") as f: - f.write(data) + with open("output.png", "wb") as file: + file.write(bytes(response.result["data"])) ``` -**Installation** -- `uv pip install kernel` or `pip install kernel` -- For CDP: `uv pip install playwright` - ## References -- **Kernel Documentation**: https://www.kernel.sh/docs -- **API Reference**: https://www.kernel.sh/docs/api-reference/ -- **Templates**: https://www.kernel.sh/docs/reference/cli/create#available-templates -- **Quickstart Guide**: https://www.kernel.sh/docs/quickstart -- **Examples**: [examples](./examples/examples.md) +- [Kernel documentation](https://www.kernel.sh/docs) +- [Python SDK API reference](https://github.com/kernel/kernel-python-sdk/blob/main/api.md) +- [Kernel quickstart](https://www.kernel.sh/docs/quickstart) +- [Additional examples](./examples/examples.md) From 6fed9bc8564198930f46949abd52cffcbd29852b Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Sat, 25 Jul 2026 09:14:27 -0400 Subject: [PATCH 09/14] docs(skill): sync kernel-typescript-sdk with latest CLI --- .../skills/kernel-typescript-sdk/SKILL.md | 201 +++++++++++++----- 1 file changed, 150 insertions(+), 51 deletions(-) diff --git a/plugins/kernel-sdks/skills/kernel-typescript-sdk/SKILL.md b/plugins/kernel-sdks/skills/kernel-typescript-sdk/SKILL.md index c98600b..865e196 100644 --- a/plugins/kernel-sdks/skills/kernel-typescript-sdk/SKILL.md +++ b/plugins/kernel-sdks/skills/kernel-typescript-sdk/SKILL.md @@ -1,84 +1,183 @@ --- name: kernel-typescript-sdk -description: Build browser automation scripts using the Kernel TypeScript SDK with Playwright, CDP, and remote browser management. +description: Build Kernel browser automation in TypeScript with server-side Playwright or CDP, including profiles, proxies, and reliable session cleanup. context: fork --- +# Kernel TypeScript SDK + ## When to Use This Skill -Use the Kernel TypeScript SDK when you need to: +Use this skill to create and automate Kernel browser sessions from TypeScript, persist browser state with profiles, route sessions through proxies, or package automation as Kernel actions. + +For one-off `kernel ...` commands, use the `kernel-cli` skill instead. + +## Install and Initialize + +```bash +npm install @onkernel/sdk +# Only CDP-based automation also needs a local Playwright client: +npm install playwright-core +``` + +```typescript +import { Kernel } from "@onkernel/sdk"; + +const kernel = new Kernel(); // Reads KERNEL_API_KEY from the environment. +``` + +Request and response fields use `snake_case` (`timeout_seconds`, `session_id`, `cdp_ws_url`). SDK methods use JavaScript casing (`deleteByID`, `captureScreenshot`). -- **Build browser automation scripts** - Create TypeScript programs that control remote browsers -- **Execute server-side automation** - Run Playwright code directly in the browser VM without local dependencies -- **Manage browser sessions programmatically** - Create, configure, and control browsers from code -- **Build scalable scraping/testing tools** - Use browser pools and profiles for high-volume automation -- **Deploy automation as actions** - Package scripts as Kernel actions for invocation via API +## Choose an Automation Path -**When NOT to use:** -- For CLI commands (e.g., `kernel browsers create`), use the `kernel-cli` skill instead -- For quick one-off tasks, the CLI may be simpler than writing code +Prefer **server-side Playwright** with `kernel.browsers.playwright.execute()` for most automation. Code runs beside the browser with low latency and does not require a local Playwright installation. -## Core Concepts +Use a **CDP connection** when a local library must control Playwright directly or when debugging interactively. Connect with `browser.cdp_ws_url` and reuse the browser's existing context. -### SDK Architecture +## Server-Side Playwright Lifecycle -The SDK is organized into resource-based modules: +Always delete a created session in `finally`. The idle timeout is a safety net, not the normal cleanup path. -- `kernel.browsers` - Browser session management (create, list, delete) -- `kernel.browsers.playwright` - Server-side Playwright execution -- `kernel.browsers.computer` - OS-level controls (mouse, keyboard, screenshots) -- `kernel.browserPools` - Pre-warmed browser pool management -- `kernel.profiles` - Persistent browser profiles (auth state) -- `kernel.auth.connections` - Managed auth (create, login, submit, follow, retrieve, delete) -- `kernel.credentialProviders` - External credential providers (1Password) -- `kernel.proxies` - Proxy configuration -- `kernel.extensions` - Chrome extension management -- `kernel.deployments` - App deployment -- `kernel.invocations` - Action invocation +```typescript +import { Kernel } from "@onkernel/sdk"; -### Two Automation Approaches +const kernel = new Kernel(); +const session = await kernel.browsers.create({ + stealth: true, + timeout_seconds: 300, +}); + +try { + const response = await kernel.browsers.playwright.execute(session.session_id, { + code: ` + await page.goto("https://example.com", { waitUntil: "domcontentloaded" }); + return { title: await page.title(), url: page.url() }; + `, + timeout_sec: 60, + }); + + if (!response.success) { + throw new Error(response.error ?? response.stderr ?? "Playwright execution failed"); + } + + console.log(response.result); +} finally { + await kernel.browsers.deleteByID(session.session_id); +} +``` -**1. Server-side Execution (RECOMMENDED)** -- Execute Playwright code directly in browser VM using `kernel.browsers.playwright.execute()` -- Response accessed via `response.result` - **MUST use `return` in code to get data back** -- Best for: Most use cases, production automation, parallel execution, actions +Use `return` inside `code` to populate `response.result`. Each call uses a fresh execution context, so keep dependent steps in one call or return the state needed by the next call. -**2. CDP Connection (Client-side)** -- Connect Playwright/Puppeteer to browser via CDP WebSocket URL (`browser.cdp_ws_url`) -- Code runs locally, browser runs remotely; requires local Playwright installation -- Best for: Complex debugging, specific local development needs +## CDP Lifecycle -## Patterns Reference +Close the local CDP client, then delete the Kernel session even if connection or automation fails. -**SDK Initialization** ```typescript import { Kernel } from "@onkernel/sdk"; -const kernel = new Kernel(); // Reads KERNEL_API_KEY from environment +import { chromium } from "playwright-core"; + +const kernel = new Kernel(); +const session = await kernel.browsers.create({ stealth: true, timeout_seconds: 300 }); + +try { + const browser = await chromium.connectOverCDP(session.cdp_ws_url); + try { + const context = browser.contexts()[0]; + if (!context) throw new Error("Kernel browser has no default context"); + + const page = context.pages()[0] ?? (await context.newPage()); + await page.goto("https://example.com", { waitUntil: "domcontentloaded" }); + console.log(await page.title()); + } finally { + await browser.close(); + } +} finally { + await kernel.browsers.deleteByID(session.session_id); +} ``` -**Attribute Access**: Use `snake_case` (e.g., `browser.session_id`, `browser.cdp_ws_url`) +Do not treat `browser.close()` as Kernel resource cleanup; always call `deleteByID()`. + +## Profiles -**Binary Data Handling** +Create profiles before attaching them. Set `save_changes: true` only when changes from this session should persist when the session ends. -Binary data does not serialize through `playwright.execute` (returns `undefined`). Use dedicated APIs: +```typescript +const profile = await kernel.profiles.create({ name: "example-login" }); +const session = await kernel.browsers.create({ + profile: { id: profile.id, save_changes: true }, + stealth: true, +}); + +try { + // Authenticate or automate with playwright.execute or CDP. +} finally { + await kernel.browsers.deleteByID(session.session_id); +} +``` + +Prefer profile IDs for long-running work. Do not rename a profile while a browser references it by name; that can prevent session changes from saving. Delete a profile only when it was created as temporary test data, and only after deleting its browser sessions: ```typescript -// For screenshots: -const response = await kernel.browsers.computer.captureScreenshot(browser.session_id); -// For files: -const response = await kernel.browsers.filesystem.readFile(browser.session_id, { path: '/path/to/file' }); - -// Convert to buffer: -const blob = await response.blob(); -const buffer = Buffer.from(await blob.arrayBuffer()); +await kernel.profiles.delete(profile.id); ``` -> **Note:** This differs from the Python SDK where binary data CAN be returned via `playwright.execute` as a Buffer object. In TypeScript, always use dedicated APIs. +## Proxies and Direct Connections + +Attach an existing proxy by ID at creation: + +```typescript +const session = await kernel.browsers.create({ + stealth: true, + proxy_id: process.env.KERNEL_PROXY_ID!, +}); +``` + +Check a configured proxy against a public HTTP(S) target before use when target reachability matters: + +```typescript +const checked = await kernel.proxies.check(process.env.KERNEL_PROXY_ID!, { + url: "https://example.com", +}); +if (checked.status === "unavailable") throw new Error("Proxy is unavailable"); +``` + +For a running stealth session, clear an explicit proxy and disable Kernel's default stealth proxy to connect directly: + +```typescript +await kernel.browsers.update(session.session_id, { + proxy_id: "", + disable_default_proxy: true, +}); +``` + +Set `disable_default_proxy: false` to re-enable the default stealth proxy. `kernel.proxies.update(id, { name })` only renames a proxy; recreate it to change its type or configuration. Delete temporary proxies only after all sessions using them are deleted. + +## Binary Data + +Do not return screenshots or file bytes from `playwright.execute`. Use the dedicated response endpoints: + +```typescript +const screenshot = await kernel.browsers.computer.captureScreenshot(session.session_id); +const screenshotBuffer = Buffer.from(await screenshot.arrayBuffer()); + +const file = await kernel.browsers.fs.readFile(session.session_id, { + path: "/tmp/output.pdf", +}); +const fileBuffer = Buffer.from(await file.arrayBuffer()); +``` + +## Cleanup Rules + +1. Wrap every created browser session in `try/finally` immediately after creation. +2. Delete sessions with `kernel.browsers.deleteByID(session_id)` on success and failure. +3. For CDP, close the local client before deleting the session. +4. Delete temporary profiles and proxies only after dependent sessions are gone. +5. Keep credentials and proxy passwords in environment variables; never embed them in source or logs. ## References - **Kernel Documentation**: https://www.kernel.sh/docs - **Quickstart Guide**: https://www.kernel.sh/docs/quickstart -- **Templates**: https://www.kernel.sh/docs/reference/cli/create#available-templates -- **TypeScript Types**: Available in `@onkernel/sdk` package -- **Examples**: [examples](./examples/examples.md) +- **TypeScript Types**: Use the declarations and hover docs shipped with `@onkernel/sdk` +- **Additional integration examples**: [examples](./examples/examples.md) From 349488a81184ad07a891aedca126d31f53ce0f57 Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Sat, 25 Jul 2026 09:44:11 -0400 Subject: [PATCH 10/14] fix(skills): address live QA findings --- .../skills/kernel-agent-browser/SKILL.md | 10 ++++++--- .../profile-website-bot-detection/SKILL.md | 2 ++ .../scripts/test-kernel-bot-detection.ts | 22 ++++++++----------- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/plugins/kernel-cli/skills/kernel-agent-browser/SKILL.md b/plugins/kernel-cli/skills/kernel-agent-browser/SKILL.md index a0038db..3177723 100644 --- a/plugins/kernel-cli/skills/kernel-agent-browser/SKILL.md +++ b/plugins/kernel-cli/skills/kernel-agent-browser/SKILL.md @@ -115,13 +115,15 @@ agent-browser -p kernel find nth 2 ".card" hover ## Find the Kernel Session and Live View -Match agent-browser's CDP URL to the active Kernel session. This is more reliable than guessing from creation time when several sessions share a profile. This workflow requires `jq`. +Match agent-browser's CDP endpoint to the active Kernel session. Compare the URL without its query string: the CLI and agent-browser can hold different short-lived `jwt` query values for the same session. The endpoint's scheme, host, and path remain stable. This is more reliable than guessing from creation time when several sessions share a profile. This workflow requires `jq`. ```bash CDP_URL="$(agent-browser -p kernel get cdp-url)" +CDP_ENDPOINT="${CDP_URL%%\?*}" SESSION_ID="$( kernel browsers list --status active --limit 100 -o json | - jq -r --arg cdp "$CDP_URL" '.[] | select(.cdp_ws_url == $cdp) | .session_id' | + jq -r --arg endpoint "$CDP_ENDPOINT" \ + '.[] | select((.cdp_ws_url | split("?")[0]) == $endpoint) | .session_id' | head -n 1 )" test -n "$SESSION_ID" @@ -443,8 +445,10 @@ agent-browser -p kernel click @eM # Resolve the underlying session before manual intervention CDP_URL="$(agent-browser -p kernel get cdp-url)" +CDP_ENDPOINT="${CDP_URL%%\?*}" SESSION_ID="$(kernel browsers list --status active --limit 100 -o json | - jq -r --arg cdp "$CDP_URL" '.[] | select(.cdp_ws_url == $cdp) | .session_id' | + jq -r --arg endpoint "$CDP_ENDPOINT" \ + '.[] | select((.cdp_ws_url | split("?")[0]) == $endpoint) | .session_id' | head -n 1)" test -n "$SESSION_ID" kernel browsers view "$SESSION_ID" diff --git a/plugins/kernel-cli/skills/profile-website-bot-detection/SKILL.md b/plugins/kernel-cli/skills/profile-website-bot-detection/SKILL.md index 6170c7b..85aaf0f 100644 --- a/plugins/kernel-cli/skills/profile-website-bot-detection/SKILL.md +++ b/plugins/kernel-cli/skills/profile-website-bot-detection/SKILL.md @@ -71,6 +71,8 @@ KERNEL_BROWSER_ID="$STEALTH_ID" TARGET_URL="$TARGET_URL" BROWSER_MODE=stealth np KERNEL_BROWSER_ID="$NORMAL_ID" TARGET_URL="$TARGET_URL" BROWSER_MODE=normal npm run analyze ``` +Each analyzer run disconnects its local CDP client after writing the report; it leaves the Kernel session active for comparison and cleanup. + ### Step 4: Compare Results Compare the newest report from each mode rather than concatenating multiple JSON documents: diff --git a/plugins/kernel-cli/skills/profile-website-bot-detection/scripts/test-kernel-bot-detection.ts b/plugins/kernel-cli/skills/profile-website-bot-detection/scripts/test-kernel-bot-detection.ts index d2a7ec4..8bc0e78 100644 --- a/plugins/kernel-cli/skills/profile-website-bot-detection/scripts/test-kernel-bot-detection.ts +++ b/plugins/kernel-cli/skills/profile-website-bot-detection/scripts/test-kernel-bot-detection.ts @@ -1659,19 +1659,15 @@ async function runTest(): Promise { log(`Test failed: ${(error as Error).message}`, 'ERROR'); console.error(error); process.exit(1); - } finally { - // Use disconnect() not close() — close() destroys remote contexts/pages, - // disconnect() drops the CDP client while leaving the Kernel session intact. - if (browser) { - try { - await browser.disconnect(); - log('Disconnected from browser (session still active)'); - } catch { - // Already disconnected - } - } } } -// Run the test -runTest().catch(console.error); +// Run the test. The SDK's HTTP dispatcher can keep Node's event loop alive after +// the report is written, so exit explicitly once cleanup has completed. +runTest().then( + () => process.exit(0), + (error) => { + console.error(error); + process.exit(1); + }, +); From 08561b441c84f493e65d2d9726764f347b82d18e Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Sat, 25 Jul 2026 09:49:20 -0400 Subject: [PATCH 11/14] fix(skill): use correct Homebrew tap --- plugins/kernel-cli/skills/kernel-cli/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/kernel-cli/skills/kernel-cli/SKILL.md b/plugins/kernel-cli/skills/kernel-cli/SKILL.md index ce52e7b..0516cb5 100644 --- a/plugins/kernel-cli/skills/kernel-cli/SKILL.md +++ b/plugins/kernel-cli/skills/kernel-cli/SKILL.md @@ -9,7 +9,7 @@ The Kernel CLI provides command-line access to Kernel's cloud browser platform f ## Installation -- Homebrew: `brew install onkernel/tap/kernel` +- Homebrew: `brew install kernel/tap/kernel` - npm: `npm install -g @onkernel/cli` Verify with `kernel --version`. Use `kernel --help` as the source of truth for the installed version. From 867a15900c51aa3e7676f98309d4462e0725ce7e Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Sat, 25 Jul 2026 09:50:23 -0400 Subject: [PATCH 12/14] docs(skill): sync kernel-cli command references --- .../references/browser-management.md | 12 +++++ .../kernel-cli/references/managed-auth.md | 48 +++++++++++++++---- .../references/process-execution.md | 14 +++++- .../skills/kernel-cli/references/profiles.md | 26 ++++++---- .../skills/kernel-cli/references/proxies.md | 19 +++++++- 5 files changed, 96 insertions(+), 23 deletions(-) diff --git a/plugins/kernel-cli/skills/kernel-cli/references/browser-management.md b/plugins/kernel-cli/skills/kernel-cli/references/browser-management.md index 43381fd..c338deb 100644 --- a/plugins/kernel-cli/skills/kernel-cli/references/browser-management.md +++ b/plugins/kernel-cli/skills/kernel-cli/references/browser-management.md @@ -51,6 +51,18 @@ kernel browsers view -o json # Get live view URL **MCP Tools:** Use `kernel:list_browsers`, `kernel:get_browser`. +## Update a Browser + +```bash +# Bypass the default stealth proxy and connect directly +kernel browsers update --disable-default-proxy -o json + +# Restore the default stealth proxy +kernel browsers update --disable-default-proxy=false -o json +``` + +This controls Kernel's default stealth proxy. Use `--proxy-id` or `--clear-proxy` to manage an explicitly assigned proxy instead. + ## Delete a Browser ```bash diff --git a/plugins/kernel-cli/skills/kernel-cli/references/managed-auth.md b/plugins/kernel-cli/skills/kernel-cli/references/managed-auth.md index 4069cb3..b9a7a65 100644 --- a/plugins/kernel-cli/skills/kernel-cli/references/managed-auth.md +++ b/plugins/kernel-cli/skills/kernel-cli/references/managed-auth.md @@ -24,6 +24,7 @@ kernel auth connections create --domain --profile-name [flags] | `--allowed-domain` | Additional allowed domains (repeatable) | | `--health-check-interval` | Seconds between health checks (300-86400) | | `--no-save-credentials` | Don't save credentials after login | +| `--telemetry=` | Default telemetry (`all`, `off`, or category list) for this connection's browser sessions | | `-o json` | JSON output | ### Credential source examples @@ -42,13 +43,26 @@ kernel auth connections create --domain github.com --profile-name gh \ --credential-provider my-1p --credential-auto ``` +Telemetry is opt-in. On create, `--telemetry=all` enables the default set, `--telemetry=off` disables capture, and `--telemetry=console,network` captures exactly those categories. + +## update + +Update settings used by future login sessions: + +```bash +kernel auth connections update --telemetry=console,network -o json +kernel auth connections update --telemetry=off -o json +``` + +Named categories merge into the current selection. `all` and `off` reset the complete telemetry config. Other update flags include login URL, health-check interval, credential source, proxy, allowed domains, and credential-saving behavior; run `kernel auth connections update --help` before changing them. + ## get ```bash kernel auth connections get [-o json] ``` -Shows: ID, domain, profile, status, flow status/step, credential, health check interval, hosted URL, live view URL, error message, last auth time, allowed domains. +Shows: ID, domain, profile, status, flow status/step, credential, health check interval, hosted URL, live view URL, error message, last auth time, allowed domains, fields, and choices. ## list @@ -64,27 +78,41 @@ kernel auth connections delete [-y] ## login -Start a login flow. Returns hosted URL, flow type, and expiry. +Start a login flow. Returns hosted URL, flow type, and expiry. A login telemetry override merges onto the connection default for this flow only. ```bash -kernel auth connections login [--proxy-id ] [--proxy-name ] [-o json] +kernel auth connections login --telemetry=network,page -o json ``` +Use `--proxy-id` or `--proxy-name` to override the proxy for this login. + ## submit -Submit field values, SSO button selection, or MFA option to an active login flow. +Submit field values or select a choice from the active flow. Prefer canonical IDs returned in the connection's `fields` and `choices` lists; names accepted by legacy `--field` can be ambiguous. ```bash -# Submit credentials -kernel auth connections submit --field username=myuser --field password=mypass +# Canonical field IDs; repeat for each requested field +kernel auth connections submit \ + --field-value 'field_username=myuser' \ + --field-value 'field_password=' + +# Canonical choice ID for SSO, MFA, or another discovered choice +kernel auth connections submit --choice-id +``` + +Legacy selectors remain available (`--field`, `--mfa-option-id`, `--sign-in-option-id`, `--sso-button-selector`, and `--sso-provider`). Do not put real credential values in logs or committed shell scripts. -# Select SSO button -kernel auth connections submit --sso-button-selector "//button[@id='google-sso']" +## timeline -# Select MFA method -kernel auth connections submit --mfa-option-id totp +Inspect login, reauth, and health-check history, newest first: + +```bash +kernel auth connections timeline --page 1 --per-page 20 -o json +kernel auth connections timeline --type reauth -o json ``` +`--type` accepts `login`, `reauth`, or `health_check`; pages are 1-based. + ## follow Stream login flow events via SSE in real time. diff --git a/plugins/kernel-cli/skills/kernel-cli/references/process-execution.md b/plugins/kernel-cli/skills/kernel-cli/references/process-execution.md index 3ad529a..73c8f65 100644 --- a/plugins/kernel-cli/skills/kernel-cli/references/process-execution.md +++ b/plugins/kernel-cli/skills/kernel-cli/references/process-execution.md @@ -40,6 +40,10 @@ kernel browsers process exec --cwd /tmp -- pwd # With specific user kernel browsers process exec --as-user chromium -- whoami + +# Set repeatable process-scoped environment variables +kernel browsers process exec \ + --env REGION=us-east --env MODE=debug -- printenv REGION MODE ``` ## Spawn Background Process (Asynchronous) @@ -51,10 +55,16 @@ kernel browsers process spawn -- long-running-command # With timeout kernel browsers process spawn --timeout 300 -- background-task -# Start web server -kernel browsers process spawn -- python3 -m http.server 8080 +# Start web server with a process-scoped environment variable +kernel browsers process spawn --env PORT=8080 -- \ + /bin/bash -c 'python3 -m http.server "$PORT"' + +# Allocate a PTY for a shell or another TTY-dependent program +kernel browsers process spawn --allocate-tty --cols 120 --rows 40 -- /bin/bash ``` +Repeat `--env KEY=VALUE` as needed. `--cols` and `--rows` are valid only with `--allocate-tty`. + ## Additional Process Commands ### Check Process Status diff --git a/plugins/kernel-cli/skills/kernel-cli/references/profiles.md b/plugins/kernel-cli/skills/kernel-cli/references/profiles.md index 8e50117..dd3422f 100644 --- a/plugins/kernel-cli/skills/kernel-cli/references/profiles.md +++ b/plugins/kernel-cli/skills/kernel-cli/references/profiles.md @@ -39,27 +39,33 @@ kernel profiles list -o json **MCP Tool:** Use `kernel:list_profiles`. -## Get Profile Details +## Get and Rename a Profile ```bash # Get by name -kernel profiles get my-profile +kernel profiles get my-profile -o json + +# Rename by ID or name; names must be unique in the project +kernel profiles update my-profile --name renamed-profile -o json ``` -## Delete Profile +## Download Profile ```bash -# Delete with confirmation prompt -kernel profiles delete my-profile +# Request the default compressed tar.zst archive and extract it into a directory +kernel profiles download renamed-profile --to ./profile-data + +# Ask the server to decompress the archive before transfer; output is still a directory +kernel profiles download renamed-profile --to ./profile-data-tar --format tar ``` -**MCP Tool:** Use `kernel:delete_profile`. +`--to` is an extraction directory, not an archive filename; the CLI creates it if needed. Supported request formats are `tar.zst` (default) and `tar`. -## Download Profile +## Delete Profile ```bash -# Download profile data as ZIP -kernel profiles download my-profile --to profile.zip +# Delete with confirmation prompt +kernel profiles delete renamed-profile ``` -The downloaded file contains the profile's cookies and local storage data. +**MCP Tool:** Use `kernel:delete_profile`. diff --git a/plugins/kernel-cli/skills/kernel-cli/references/proxies.md b/plugins/kernel-cli/skills/kernel-cli/references/proxies.md index d8a8f5f..a5ffae4 100644 --- a/plugins/kernel-cli/skills/kernel-cli/references/proxies.md +++ b/plugins/kernel-cli/skills/kernel-cli/references/proxies.md @@ -121,7 +121,24 @@ kernel proxies list ### Get Proxy Details ```bash -kernel proxies get proxy_abc123xyz +kernel proxies get proxy_abc123xyz -o json +``` + +### Rename a Proxy + +Only the name is mutable. Recreate the proxy to change its type, location, protocol, or credentials. + +```bash +kernel proxies update proxy_abc123xyz --name "Renamed Proxy" -o json +``` + +### Check Proxy Health + +A check verifies reachability and updates the stored proxy status. Omit `--url` for the default health target, or provide a public HTTP(S) URL. + +```bash +kernel proxies check proxy_abc123xyz -o json +kernel proxies check proxy_abc123xyz --url https://example.com -o json ``` ### Delete Proxy From 07f8d21572180e2a9ee34754ca80447b225dc3e0 Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Sat, 25 Jul 2026 10:05:28 -0400 Subject: [PATCH 13/14] fix(skill): derive bot report host from target URL --- .../kernel-cli/skills/profile-website-bot-detection/SKILL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/kernel-cli/skills/profile-website-bot-detection/SKILL.md b/plugins/kernel-cli/skills/profile-website-bot-detection/SKILL.md index 85aaf0f..1b1bd3f 100644 --- a/plugins/kernel-cli/skills/profile-website-bot-detection/SKILL.md +++ b/plugins/kernel-cli/skills/profile-website-bot-detection/SKILL.md @@ -78,8 +78,8 @@ Each analyzer run disconnects its local CDP client after writing the report; it Compare the newest report from each mode rather than concatenating multiple JSON documents: ```bash -# Set the hostname folder (e.g., chase-com for chase.com). -HOST=chase-com +# Derive the same hostname folder used by the analyzer (e.g., chase.com -> chase-com). +HOST=$(node -e 'const host = new URL(process.env.TARGET_URL).hostname; console.log(host.replace(/^www\./, "").replace(/\./g, "-"))') latest_report() { local reports=("$1"/report-*.json) [[ -e "${reports[0]}" ]] || return 1 From 3c32b77caa06b69a7852366e37a06e2d555ca0ef Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Mon, 27 Jul 2026 13:24:11 -0400 Subject: [PATCH 14/14] fix(skills): address profile workflow review feedback --- .../skills/kernel-agent-browser/SKILL.md | 42 ++++++++++++------- .../skills/kernel-python-sdk/SKILL.md | 9 ++-- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/plugins/kernel-cli/skills/kernel-agent-browser/SKILL.md b/plugins/kernel-cli/skills/kernel-agent-browser/SKILL.md index 3177723..22c1043 100644 --- a/plugins/kernel-cli/skills/kernel-agent-browser/SKILL.md +++ b/plugins/kernel-cli/skills/kernel-agent-browser/SKILL.md @@ -37,7 +37,7 @@ Set these before your first `agent-browser -p kernel` call. The CLI holds state | `KERNEL_HEADLESS` | Run browser in headless mode (`true`/`false`) | `true` | | `KERNEL_STEALTH` | Launch a stealth browser (`true`/`false`) | `false` | | `KERNEL_TIMEOUT_SECONDS` | Session timeout in seconds | `300` | -| `KERNEL_PROFILE_NAME` | Browser profile name for persistent cookies/logins | (none) | +| `KERNEL_PROFILE_NAME` | Currently unusable with `-p kernel` in agent-browser 0.33.0; use the CDP workaround below | (none) | ### Recommended Configuration @@ -48,17 +48,32 @@ export KERNEL_API_KEY="your-api-key" export KERNEL_TIMEOUT_SECONDS=600 # 10-minute timeout for complex workflows export KERNEL_HEADLESS=false # Required when you need a live view export KERNEL_STEALTH=true # Opt in for bot-sensitive sites -export KERNEL_PROFILE_NAME=mysite # Persist login sessions across runs +# Do not set KERNEL_PROFILE_NAME with -p kernel; use the workaround below. ``` ### Profile Persistence -When `KERNEL_PROFILE_NAME` is set: -- The profile is created if it doesn't exist -- Cookies, logins, and session data are automatically saved when the browser session ends -- Future sessions with the same profile name restore the saved state +> **Warning:** `KERNEL_PROFILE_NAME` doesn't work with `-p kernel` in agent-browser 0.33.0. It sends `profile` as a string instead of the object required by Kernel, so session creation fails with HTTP 400. It also doesn't set `save_changes`. Profiles must be pre-created. -This is especially useful for sites requiring login—authenticate once, reuse across sessions. +Until this is fixed upstream, create the browser with the Kernel CLI and attach agent-browser over CDP. Don't combine `-p kernel` with `--cdp`. + +```bash +PROFILE_NAME=mysite # Create once with: kernel profiles create --name mysite +kernel profiles get "$PROFILE_NAME" >/dev/null +BROWSER=$(kernel browsers create --profile-name "$PROFILE_NAME" --save-changes --timeout 600 -o json) +SESSION_ID=$(jq -er '.session_id' <<<"$BROWSER") +CDP_URL=$(jq -er '.cdp_ws_url' <<<"$BROWSER") +trap 'kernel browsers delete "$SESSION_ID" >/dev/null 2>&1 || true' EXIT + +agent-browser --session mysite --cdp "$CDP_URL" open https://example.com +agent-browser --session mysite snapshot -i +# Run additional commands with --session mysite, then disconnect and end the Kernel session. +agent-browser --session mysite close +kernel browsers delete "$SESSION_ID" +trap - EXIT +``` + +Don't print or share `CDP_URL`; it grants browser access. Deleting the CLI-created session finalizes `--save-changes`, so later sessions can reuse the authenticated profile. ## Basic Usage @@ -67,10 +82,10 @@ agent-browser -p kernel open # Navigate to page agent-browser -p kernel snapshot -i # Get interactive elements with refs agent-browser -p kernel click @e1 # Click element by ref agent-browser -p kernel fill @e2 "text" # Fill input by ref -agent-browser -p kernel close # Close browser and save profile +agent-browser -p kernel close # Close the provider session ``` -Always use the `-p kernel` flag with each command. +For provider-managed sessions, use `-p kernel` with each command. For the profile/CDP workaround, reuse the same `--session` name and don't add `-p kernel`. ## Semantic Selectors (Recommended) @@ -158,7 +173,7 @@ If automated login fails: 1. Resolve `SESSION_ID` using the CDP-matching workflow above. 2. Run `kernel browsers view "$SESSION_ID"` and give the URL only to the intended user. 3. Ask the user to complete login, then continue in the same agent-browser session. -4. Close normally so the authenticated profile is saved. +4. To persist the login, use the CLI-created profile/CDP workflow above. Close agent-browser, then delete the Kernel session so `--save-changes` finalizes. ### JavaScript Fallback for Tricky Elements @@ -419,15 +434,14 @@ agent-browser -p kernel session list 1. **Refs change after navigation**: Re-snapshot after links, submissions, or major DOM updates. 2. **Wait for outcomes**: Use URL, text, load-state, or JavaScript conditions after asynchronous actions. -3. **Provider settings are launch-time settings**: Close the current session before changing `KERNEL_HEADLESS`, `KERNEL_STEALTH`, timeout, or profile. -4. **Profiles need normal cleanup**: Run `close`; use `kernel browsers delete` only as the orphan fallback. +3. **Provider settings are launch-time settings**: Close the current session before changing `KERNEL_HEADLESS`, `KERNEL_STEALTH`, or timeout. +4. **Profile persistence needs the CDP workaround**: `KERNEL_PROFILE_NAME` is currently broken. Close agent-browser, then delete the CLI-created Kernel session to finalize `--save-changes`. 5. **Stealth is not sufficient for every site**: Compare proxy routing, use manual login, or fall back to direct Playwright for difficult frames. ## Quick Reference ```bash -# Start session with profile persistence -export KERNEL_PROFILE_NAME=mysite +# Start a standard provider session (for persistence, use the CDP workflow above) export KERNEL_TIMEOUT_SECONDS=600 agent-browser -p kernel open https://example.com diff --git a/plugins/kernel-sdks/skills/kernel-python-sdk/SKILL.md b/plugins/kernel-sdks/skills/kernel-python-sdk/SKILL.md index f60062e..e29b84b 100644 --- a/plugins/kernel-sdks/skills/kernel-python-sdk/SKILL.md +++ b/plugins/kernel-sdks/skills/kernel-python-sdk/SKILL.md @@ -116,14 +116,15 @@ finally: Deleting the browser ends the session and allows profile changes to finalize. Do not delete the profile first. Use either `{"id": ...}` or `{"name": ...}`, not both. -The current Python SDK's `profiles.download(id_or_name)` returns a zstd-compressed tar archive. Save it with: +The Python SDK can request either archive format and rename profiles natively: ```python -archive = client.profiles.download(profile.id) -archive.write_to_file("profile.tar.zst") +archive = client.profiles.download(profile.id, format="tar") +archive.write_to_file("profile.tar") +client.profiles.update(profile.id, name="checkout-login-v2") ``` -Do not pass the CLI-only `--format` concept to the SDK method. Use `kernel profiles download --to ./profile --format tar` when server-side decompression and extraction are needed. The current SDK does not expose profile rename; use `kernel profiles update --name `. +The SDK writes an archive for both `tar.zst` and `tar`; it never extracts the contents. Use `kernel profiles download --to ./profile` when the archive must be unpacked into a directory. ## Proxies and Default Stealth Proxy