Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
39d0464
chore: update kernel-go-sdk to v0.107.0
kernel-internal[bot] Sep 16, 2026
a63dd37
Merge main into cli-coverage-update
kernel-internal[bot] Sep 17, 2026
74e93b3
chore: bump Go SDK to 51d7fb8 (v0.107.1-0.20260917184535)
kernel-internal[bot] Sep 17, 2026
627c950
chore: bump Go SDK to v0.108.0 (3556e00)
kernel-internal[bot] Sep 17, 2026
7a8a71e
chore: bump Go SDK to 6367486 and show org concurrency usage
kernel-internal[bot] Sep 17, 2026
6643d5d
Update Go SDK to 3038f83 and adopt ordered credential fields
kernel-internal[bot] Sep 17, 2026
7176b47
chore: bump Go SDK to 5cfcedd and document opaque AgentCard card IDs
kernel-internal[bot] Sep 17, 2026
0601cdf
chore: bump Go SDK to v0.109.0 (5c91a27)
kernel-internal[bot] Sep 17, 2026
086f25e
Merge main into cli-coverage-update
kernel-internal[bot] Sep 18, 2026
51d6810
Update SDK to c4dc2a2 and surface credential field labels
kernel-internal[bot] Sep 18, 2026
5c5dc62
chore: bump Go SDK to released v0.110.0 (a414a68)
kernel-internal[bot] Sep 18, 2026
d53e6ed
Merge main into cli-coverage-update
kernel-internal[bot] Sep 18, 2026
863ed39
chore: bump Go SDK to 6e379e6 (proxy_error raw_code)
kernel-internal[bot] Sep 18, 2026
46ca7ed
feat: Update Go SDK to dd9a4a4 and accept adyen checkout processor
kernel-internal[bot] Sep 21, 2026
345a60d
chore: bump Go SDK to 68c5100 (optimistic managed auth reauth)
kernel-internal[bot] Sep 21, 2026
b6f9f2a
chore: bump Go SDK to ca8d465 and surface managed auth completion times
kernel-internal[bot] Sep 21, 2026
e7efb10
feat: add --skill-mode to auth connections login, bump SDK
kernel-internal[bot] Sep 21, 2026
f3bd1bb
feat: add kernel search commands, bump Go SDK to 0554699
kernel-internal[bot] Sep 22, 2026
f50d037
chore: bump Go SDK to v0.111.0 (df8b441)
kernel-internal[bot] Sep 22, 2026
2806722
chore: bump Go SDK to 1ef7b7c
kernel-internal[bot] Sep 22, 2026
846f841
chore: bump Go SDK to 0584ea1
kernel-internal[bot] Sep 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 66 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Kernel provides sandboxed, ready-to-use Chrome browsers for browser automations
- Invoke app actions (sync or async) and stream logs
- Create, list, view, and delete managed browser sessions
- Get a live view URL for visual monitoring and remote control
- Search the web across providers and retrieve page content for results

## Installation

Expand Down Expand Up @@ -298,8 +299,17 @@ populated, not that login succeeded. `fill` requires an already-open page and ne
navigates or submits it. Optional `page_url` selects the exact page; cards require it.
Do not automatically retry failed/unknown fills or fall back to aliases.

Create specs list `fields` as an ordered array. Each entry carries a stable `name`
(letters, digits, and underscores, starting with a letter) that keys values, updates,
and fills. Order is preserved: list fields in the same top-to-bottom order as the
website, because the collection form renders that order unchanged. An optional `label`
supplies non-secret display text for that field on the collection form; it never
affects value keys, updates, or fills. Use a single trimmed line of at most 128 UTF-8
bytes, and it is returned as metadata in `get`/`list` output.

Use `credentials update <vault> <key> --version <version> --spec-file changes.json`
with a spec such as `{"fields":{"password":{"value":"replacement"}}}`. Keep actual
with a spec such as `{"fields":{"password":{"value":"replacement"}}}`; update specs key
`fields` by name rather than using the ordered array. Keep actual
secrets in protected files or stdin, never shell arguments. Omission preserves values;
null or an empty string clears supported fields, including required text/email/password fields (returning them to pending collection). The form still requires nonempty required inputs. Field definitions cannot change. Stale versions fail,
without retries. `items invoke <vault> <key> collect` reopens the full form without
Expand Down Expand Up @@ -560,14 +570,21 @@ kernel vaults items get user-123 order-1 --wait 60 -o json
this vault attached. `merchant_origin` is the canonical HTTPS origin of the top-level
merchant document, not a processor iframe; HTTP localhost is allowed for tests.

Optional `psp` selects the tokenization processor: `square`, `braintree`, `worldpay`,
`bambora`, or `mercado_pago`. Omit it for Square; non-Square processors require
Optional `psp` selects the checkout processor: `square`, `braintree`, `worldpay`,
`bambora`, `mercado_pago`, or `adyen`. Omit it for Square; non-Square processors require
multi-processor preparation enablement. `environment` is `production`, `sandbox`, or
`shared`: use `production` or `sandbox` for Square, Braintree and Worldpay, and `shared`
for Bambora and Mercado Pago. Shared endpoints do not establish test mode; merchant
`shared`: use `production` or `sandbox` for Square, Braintree, Worldpay and Adyen, and
`shared` for Bambora and Mercado Pago. Shared endpoints do not establish test mode; merchant
credentials and configuration determine processor test mode, independently of the
AgentCard credential mode.

`adyen` supports fresh-card Sessions requests on Adyen hosts only. Fill the public dummy
card fields rather than vault aliases, and keep the approval page open through device
handoff, including Adyen encryption. The unique armed preparation is associated with the
next eligible request from the declared browser and merchant origin; competing preparations
are rejected. Adyen device approval and browser `Authorised` responses are not capture or
fulfillment evidence.

Keep the approval page open. Poll until the item's status is `ready_to_submit`, then
submit native Pay before `state.preparation.expires_at`. Readiness lasts at most 30
seconds, and polling does not extend it. The CLI displays the preparation ID, status,
Expand Down Expand Up @@ -1153,6 +1170,50 @@ Automated authentication for web services. The `run` command orchestrates the fu
- `--default-project-max-concurrent-sessions <n>` - Default maximum concurrent browsers for projects without an explicit override (`0` to remove the default)
- `--output json`, `-o json` - Output raw JSON object

### Search

- `kernel search <query>` - Search the web through Kernel's search providers
- `--country <code>` - ISO 3166-1 alpha-2 search locale preference
- `--language <tag>` - BCP 47 search language preference
- `--max-results <n>` - Requested result count, 1-100 (clamped to the serving provider's cap)
- `--recency <window>` - Relative search window: `hour`, `day`, `week`, `month`, or `year`
- `--safe-search <level>` - Safety preference: `off`, `moderate`, or `strict`
- `--start-date <YYYY-MM-DD>` / `--end-date <YYYY-MM-DD>` - Inclusive publication-date bounds (`--recency` takes precedence)
- `--include-domains <hosts>` / `--exclude-domains <hosts>` - Hostname preferences, matching a hostname and its subdomains
- `--strict-params` - Require every supplied portable parameter to be honored exactly instead of approximated
- `--include-raw` - Include untouched provider payloads in the response's raw fields
- `--timeout-ms <ms>` - Overall deadline across search attempts and inline retrieval
- `--content` - Retrieve page content for each result using portable defaults
- `--show-content` - Print the extracted content text for each result (implies `--content`)
- `--content-source <source>` - Retrieval source: `auto`, `provider`, or `browser`
- `--content-format <format>` - Extracted content format: `markdown` or `text`
- `--content-max-chars <n>` - Per-result Unicode character limit after extraction
- `--content-max-age-hours <n>` - Maximum acceptable age of cached page content; `0` forces a live fetch
- `--content-timeout-ms <ms>` - Per-result retrieval deadline
- `--content-browser-id <id>` - Retrieve through an existing browser session (requires `--content-source browser`)
- `--content-browser-mode <mode>` - Browser retrieval mode: `curl` or `render`
- `--provider <slug>` - Pin a single provider (`brave`, `exa`, `perplexity`, `context`, `parallel`, `valyu`, `octen`, `you`, `tavily`, `serpapi`)
- `--fallback-providers <slugs>` - Ordered provider chain to try in turn
- `--fallback-on <outcomes>` - Outcomes that advance to the next provider: `error`, `timeout`, `empty`
- `--provider-options <json>` - Provider-native options as a JSON object keyed by provider slug
- `--output json`, `-o json` - Output raw JSON object
- `kernel search get <id>` - Re-read a retained search without calling a provider or incurring cost
- `--show-content` - Print the extracted content text for each result
- `--output json`, `-o json` - Output raw JSON object
- `kernel search providers` - List providers, result caps, and content capabilities
- `--slug <slug>` - Filter to a single provider; also prints its portable-parameter support matrix and notes
- `--output json`, `-o json` - Output raw JSON array
- `kernel search contents <id>` - Deferred content retrieval for a retained search
- `--result-ids <ids>` - Result IDs from the retained search, in the desired response order
- `--limit <n>` - Number of results to fetch starting from rank 1 (mutually exclusive with `--result-ids`)
- `--timeout-ms <ms>` - Overall deadline across all selected results
- Accepts the same `--content-*` flags as `kernel search`
- This endpoint is reserved and returns 404 until deferred retrieval ships; use `kernel search --content` for inline retrieval

Searches are retained for 24 hours. Omitting the strategy flags lets Kernel pick an
eligible provider; portable filters a provider cannot honor are approximated or
dropped and reported as warnings unless `--strict-params` is set.

## Examples

### Create a new app
Expand Down
31 changes: 30 additions & 1 deletion cmd/auth_connections.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ type AuthConnectionLoginInput struct {
Region string
Stealth BoolFlag
RecordSession BoolFlag
SkillMode string
Telemetry string
TelemetryCdpExclude string
TelemetryExport string
Expand Down Expand Up @@ -794,6 +795,20 @@ func (c AuthConnectionCmd) Delete(ctx context.Context, in AuthConnectionDeleteIn
return nil
}

// parseSkillModeFlag validates the --skill-mode value against the modes the API
// accepts for a login, so a typo fails locally instead of starting a flow with
// the wrong skill behavior.
func parseSkillModeFlag(mode string) (kernel.AuthConnectionLoginParamsSkillMode, error) {
switch kernel.AuthConnectionLoginParamsSkillMode(mode) {
case kernel.AuthConnectionLoginParamsSkillModeEnabled:
return kernel.AuthConnectionLoginParamsSkillModeEnabled, nil
case kernel.AuthConnectionLoginParamsSkillModeDisabled:
return kernel.AuthConnectionLoginParamsSkillModeDisabled, nil
default:
return "", fmt.Errorf("invalid --skill-mode value: %s (must be one of enabled, disabled)", mode)
}
}

func (c AuthConnectionCmd) Login(ctx context.Context, in AuthConnectionLoginInput) error {
if err := validateJSONOutput(in.Output); err != nil {
return err
Expand Down Expand Up @@ -825,6 +840,14 @@ func (c AuthConnectionCmd) Login(ctx context.Context, in AuthConnectionLoginInpu
params.RecordSession = kernel.Opt(in.RecordSession.Value)
}

if in.SkillMode != "" {
mode, err := parseSkillModeFlag(in.SkillMode)
if err != nil {
return err
}
params.SkillMode = mode
}

if in.Telemetry != "" || in.TelemetryCdpExclude != "" || in.TelemetryExport != "" {
t, err := buildManagedAuthTelemetryParam(in.Telemetry, in.TelemetryCdpExclude, in.TelemetryExport, false)
if err != nil {
Expand Down Expand Up @@ -1069,7 +1092,7 @@ func (c AuthConnectionCmd) Timeline(ctx context.Context, in AuthConnectionTimeli
return nil
}

tableData := pterm.TableData{{"Timestamp", "Type", "Status", "Step", "Browser Session", "Telemetry", "Details"}}
tableData := pterm.TableData{{"Timestamp", "Completed", "Type", "Status", "Step", "Browser Session", "Telemetry", "Details"}}
for _, e := range events {
details := e.ErrorMessage
if details == "" {
Expand All @@ -1087,6 +1110,9 @@ func (c AuthConnectionCmd) Timeline(ctx context.Context, in AuthConnectionTimeli
}
tableData = append(tableData, []string{
util.FormatLocal(e.Timestamp),
// Absent (dashed out) for in-progress attempts, health checks, and
// older attempts recorded before completion times were persisted.
util.FormatLocal(e.CompletedAt),
string(e.Type),
string(e.Status),
string(e.Step),
Expand Down Expand Up @@ -1381,6 +1407,7 @@ func init() {
authConnectionsLoginCmd.Flags().String("region", "", "Geographic region override for this login: 'us-east', 'eu-west', or 'ap-southeast'")
authConnectionsLoginCmd.Flags().Bool("stealth", true, "Override stealth mode for this login's browser session; use --stealth=false to disable")
authConnectionsLoginCmd.Flags().Bool("record-session", false, "Override whether this login's browser session is recorded; use --record-session=false to disable")
authConnectionsLoginCmd.Flags().String("skill-mode", "", "Whether this login reads and writes learned domain skills: 'enabled' (default) or 'disabled'. Automatic reauths inherit the selected mode until a later accepted login sets enabled or omits the flag")
authConnectionsLoginCmd.Flags().String("telemetry", "", "Telemetry override for this login only, merged onto the connection's config: --telemetry=all, --telemetry=off, or --telemetry=console,network")
authConnectionsLoginCmd.Flags().String("telemetry-export-otlp", "", "Export override for this login only: an OTLP destination ID or name; --telemetry-export-otlp=off disables export for this login. Naming a destination requires passing --telemetry in the same command, since export and capture are validated together")
authConnectionsLoginCmd.Flags().String("telemetry-cdp-exclude", "", "Leave the named CDP methods out of control telemetry's cdp_command events, comma-separated (e.g. Input.dispatchMouseEvent,Page.captureScreenshot); --telemetry-cdp-exclude=none clears the list. Excluded commands are still relayed to the browser, they just produce no event")
Expand Down Expand Up @@ -1600,6 +1627,7 @@ func runAuthConnectionsLogin(cmd *cobra.Command, args []string) error {
proxyName, _ := cmd.Flags().GetString("proxy-name")
proxyMode, _ := cmd.Flags().GetString("proxy-mode")
region, _ := cmd.Flags().GetString("region")
skillMode, _ := cmd.Flags().GetString("skill-mode")
telemetry, _ := cmd.Flags().GetString("telemetry")
telemetryCdpExclude, _ := cmd.Flags().GetString("telemetry-cdp-exclude")
telemetryExport, _ := cmd.Flags().GetString("telemetry-export-otlp")
Expand All @@ -1614,6 +1642,7 @@ func runAuthConnectionsLogin(cmd *cobra.Command, args []string) error {
Region: region,
Stealth: readBoolFlag(cmd.Flags(), "stealth"),
RecordSession: readBoolFlag(cmd.Flags(), "record-session"),
SkillMode: skillMode,
Telemetry: telemetry,
TelemetryCdpExclude: telemetryCdpExclude,
TelemetryExport: telemetryExport,
Expand Down
47 changes: 46 additions & 1 deletion cmd/auth_connections_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"net/http/httptest"
"os"
"testing"
"time"

"github.com/kernel/cli/pkg/util"
"github.com/kernel/kernel-go-sdk"
Expand Down Expand Up @@ -1223,7 +1224,8 @@ func TestTimeline_RendersEventsAndPagination(t *testing.T) {
"type": "login",
"status": "SUCCESS",
"browser_session_id": "browser_1",
"telemetry_captured": true
"telemetry_captured": true,
"completed_at": "2026-09-21T12:00:00Z"
}`), &loginEvent))
fake := &FakeAuthConnectionService{
TimelineFunc: func(ctx context.Context, id string, query kernel.AuthConnectionTimelineParams, opts ...option.RequestOption) (*pagination.OffsetPagination[kernel.ManagedAuthTimelineEvent], error) {
Expand Down Expand Up @@ -1252,6 +1254,9 @@ func TestTimeline_RendersEventsAndPagination(t *testing.T) {
// Telemetry capture is reported for events that have a browser session.
assert.Contains(t, out, "Telemetry")
assert.Regexp(t, `browser_1.*yes`, out)
// completed_at is shown for terminal attempts and dashed out otherwise.
assert.Contains(t, out, "Completed")
assert.Contains(t, out, util.FormatLocal(time.Date(2026, 9, 21, 12, 0, 0, 0, time.UTC)))
// The third event is truncated off the page.
assert.NotContains(t, out, "health_check")
assert.Contains(t, out, "Has more: yes")
Expand Down Expand Up @@ -1390,3 +1395,43 @@ func TestAuthConnectionsGet_TelemetryRowOmittedWhenOff(t *testing.T) {
require.NoError(t, c.Get(context.Background(), AuthConnectionGetInput{ID: "conn-1"}))
assert.NotContains(t, outBuf.String(), "Browser Telemetry")
}

func TestLogin_SkillMode(t *testing.T) {
capturePtermOutput(t)
var captured kernel.AuthConnectionLoginParams
fake := &FakeAuthConnectionService{
LoginFunc: func(ctx context.Context, id string, body kernel.AuthConnectionLoginParams, opts ...option.RequestOption) (*kernel.LoginResponse, error) {
captured = body
return &kernel.LoginResponse{ID: id}, nil
},
}
c := AuthConnectionCmd{svc: fake}
require.NoError(t, c.Login(context.Background(), AuthConnectionLoginInput{ID: "auth_1", SkillMode: "disabled"}))
assert.Equal(t, kernel.AuthConnectionLoginParamsSkillModeDisabled, captured.SkillMode)
}

// Omitting --skill-mode leaves the field unset, so the API keeps its default of
// enabled rather than the CLI pinning a mode the user never asked for.
func TestLogin_SkillModeOmitted(t *testing.T) {
capturePtermOutput(t)
var captured kernel.AuthConnectionLoginParams
fake := &FakeAuthConnectionService{
LoginFunc: func(ctx context.Context, id string, body kernel.AuthConnectionLoginParams, opts ...option.RequestOption) (*kernel.LoginResponse, error) {
captured = body
return &kernel.LoginResponse{ID: id}, nil
},
}
c := AuthConnectionCmd{svc: fake}
require.NoError(t, c.Login(context.Background(), AuthConnectionLoginInput{ID: "auth_1"}))
assert.Empty(t, string(captured.SkillMode))
}

func TestLogin_InvalidSkillModeErrors(t *testing.T) {
capturePtermOutput(t)
c := AuthConnectionCmd{svc: &FakeAuthConnectionService{}}

err := c.Login(context.Background(), AuthConnectionLoginInput{ID: "auth_1", SkillMode: "mars"})

require.Error(t, err)
assert.Contains(t, err.Error(), "invalid --skill-mode value")
}
21 changes: 20 additions & 1 deletion cmd/org.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,16 @@ func renderOrgLimits(limits *kernel.OrgLimits) {
{"Default Project Max Concurrent Sessions", formatProjectLimitValue(limits.DefaultProjectMaxConcurrentSessions, limits.JSON.DefaultProjectMaxConcurrentSessions)},
}

// Concurrency usage is measured live and only returned by newer API
// versions. Unlike the limit rows, a null here means usage could not be
// read rather than "unlimited", so render it as unknown.
if orgLimitFieldPresent(limits.JSON.ConcurrentSessionsUsed) {
rows = append(rows, []string{"Concurrent Sessions Used", formatOrgUsageValue(limits.ConcurrentSessionsUsed, limits.JSON.ConcurrentSessionsUsed)})
}
if orgLimitFieldPresent(limits.JSON.ConcurrentSessionsAvailable) {
rows = append(rows, []string{"Concurrent Sessions Available", formatOrgUsageValue(limits.ConcurrentSessionsAvailable, limits.JSON.ConcurrentSessionsAvailable)})
}

// Managed auth limits are plan-derived and only returned by newer API
// versions, so render each row only when the field is present. A null
// max_auth_connections means unlimited, so presence — not validity — is the
Expand Down Expand Up @@ -167,6 +177,15 @@ func orgLimitFieldPresent(field respjson.Field) bool {
return field.Raw() != respjson.Omitted
}

// formatOrgUsageValue renders a live usage counter, where a null means the API
// could not read current usage rather than "unlimited".
func formatOrgUsageValue(value int64, field respjson.Field) string {
if !field.Valid() {
return "unknown"
}
return fmt.Sprintf("%d", value)
}

func renderOrgEntitlements(entitlements *kernel.OrgEntitlements) {
if entitlements == nil {
pterm.Info.Println("No organization entitlements found")
Expand Down Expand Up @@ -266,7 +285,7 @@ var orgLimitsCmd = &cobra.Command{
var orgLimitsGetCmd = &cobra.Command{
Use: "get",
Short: "Get organization limits",
Long: "Show the organization's effective limits: the concurrency limit, the default per-project cap applied to projects without an explicit override, and the plan-derived managed auth and vault limits along with current auth connection and vault usage.",
Long: "Show the organization's effective limits: the concurrency limit, current organization-wide concurrent browser usage and remaining capacity, the default per-project cap applied to projects without an explicit override, and the plan-derived managed auth and vault limits along with current auth connection and vault usage.",
Args: cobra.NoArgs,
RunE: runOrgLimitsGet,
}
Expand Down
Loading
Loading