Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 33 additions & 0 deletions .github/workflows/preview.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Preview CLI binaries

on:
pull_request:
branches: [main]
workflow_dispatch:

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
persist-credentials: false
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
cache: true
- name: Download dependencies
run: go mod download
- name: Build preview archives
run: bash scripts/build-preview.sh
- uses: actions/upload-artifact@v4
with:
name: kernel-preview-${{ github.event.pull_request.head.sha || github.sha }}
path: dist/preview/*
if-no-files-found: error
retention-days: 14
40 changes: 40 additions & 0 deletions PREVIEW.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Preview binaries

The `Preview CLI binaries` workflow builds pull requests automatically.
It also supports `workflow_dispatch` after the workflow exists on the default branch.
It builds the PR head, not GitHub's synthetic merge commit. Archives cover Linux,
macOS, and Windows on amd64 and arm64, with `SHA256SUMS`. The embedded version is
`0.0.0-preview.g<commit>`; the full commit is embedded as well.

Download a run's artifact (GitHub authentication required):

```sh
gh run list --repo kernel/cli --workflow preview.yaml --branch <branch>
gh run download <run-id> --repo kernel/cli --name kernel-preview-<full-commit> --dir preview
cd preview
sha256sum -c SHA256SUMS
# macOS: shasum -a 256 -c SHA256SUMS
# Extract the archive matching your operating system and architecture.
tar -xzf kernel_0.0.0-preview.g<commit>_linux_amd64.tar.gz
./kernel --version
./kernel vaults credentials --help
./kernel vaults items invoke --help
```

Use the extracted binary explicitly rather than replacing the stable installation.
Set `KERNEL_BASE_URL` and `KERNEL_API_KEY` for your local/test environment before API
calls; do not assume the production API supports preview features.

Artifacts expire after 14 days. No GitHub release, stable tag, npm package, Homebrew
formula, or production deployment is created. macOS binaries are unsigned; Windows
archives contain `kernel.exe`. Download on trusted machines and verify checksums.

## Local builds

Preview builds use the public Go SDK release pinned in `go.mod`; private SDK
repository access is not required. For a local cross-platform build, run:

```sh
go mod download
bash scripts/build-preview.sh
```
99 changes: 85 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ Commands with JSON output support:
- **Proxies**: `create`, `list`, `get`, `update`, `check`
- **API Keys**: `create`, `list`, `get`, `update`, `rotate`
- **Auth Connections**: `timeline`
- **Vaults**: `create`, `list`, `get`, `items list/get/events/invoke`, `wallets create/payment-methods`, `cards create/update` (display-safe public fields only)
- **Vaults**: `create`, `list`, `get`, `credentials create/update`, `items list/get/events/invoke` (including `collect`, `fill`, and `prepare_checkout`), `wallets create/payment-methods`, `cards create/update` (display-safe public fields only)
- **Projects**: `update`
- **Org**: `limits get/set`
- **Apps**: `list`, `history`
Expand Down Expand Up @@ -270,7 +270,50 @@ Commands with JSON output support:

### Vaults

Vault commands **prepare and observe payment credentials; they do not submit merchant payments**.
Vault commands **collect user credentials and manage payment credentials; fill does not submit website forms**.

#### User credentials

Create a vault for the end user, attach it when creating a browser, then navigate to the
sensitive form. Define the observed fields without supplying values:

```sh
kernel vaults create --name user-vault
kernel browsers create --vault user-vault
kernel vaults credentials create user-vault login --spec-file - <<'JSON'
{"description":"Hacker News","fields":{"username":{"type":"text","required":true,"sensitive":false},"password":{"type":"password","required":true,"sensitive":true}}}
JSON
kernel vaults items get user-vault login --wait 60 -o json
kernel vaults items invoke user-vault login fill --spec-file - <<'JSON'
{"browser_id":"<browser-id>","fields":[{"field":"username","selector":"#username"},{"field":"password","selector":"#password"}]}
JSON
```

Present the returned collection URL to the user before waiting for `ready`. It is a
bearer credential: share it only with that user. Readiness means required values are
populated, not that login succeeded. `fill` requires an already-open page and never
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.

Use `credentials update <vault> <key> --version <version> --spec-file changes.json`
with a spec such as `{"fields":{"password":{"value":"replacement"}}}`. 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
clearing values; compare versions to observe edits to already-ready items. When an update
is bound to an earlier read, also pass `--expected-item-id <id>` to reject a replacement
item at the same key. Neither precondition is refreshed automatically.

Do not use credential items to store, collect, or fill credit card data, including card numbers (PANs), security codes (CVV/CVC), or expiration dates. Use wallet and card item types for credit cards and payment checkout instead.

Set `description` to the recognizable site name only, such as `Hacker News`, not `Hacker News sign-in credentials`. Set `sensitive: false` explicitly for ordinary usernames and email addresses. Reserve `sensitive: true` for passwords, API tokens, and TOTP seeds; the omitted default remains true for safety.

Types are `text`, `email`, `password`, and `totp`. TOTP seeds must be provided through
create/update, never the form; only generated codes enter the browser. Unrestricted
browser access can read filled values. CLI output omits all stored credential values,
including non-sensitive values, and retains definitions, version, and `has_value`.
Credential spec input is capped at 128 KiB; write errors are redacted.

Vault names, item keys, and project ownership are immutable. Optionally select a project with
`--project <id-or-name>` or `KERNEL_PROJECT`; otherwise, the API resolves the project from your
credentials and its defaults (the default project for org-wide credentials, not all projects).
Expand Down Expand Up @@ -474,7 +517,7 @@ card spec. Otherwise, the cardholder selects a card at approval. A reusable card
#### Invoking item operations

`items get` displays every `available_operations` entry's type and description, plus
an `items invoke` command retaining the selected project (replace `<json>` for fill).
an `items invoke` command retaining the selected project (replace `<json>` for fill or prepare_checkout).
Read the description and follow its approval requirements before invoking. Required user actions
(OAuth, enrollment, MFA, spend approval) appear separately; they are not operations to invoke
through this endpoint.
Expand All @@ -485,17 +528,45 @@ advertised. The API controls availability. The CLI additionally refuses invocati
actions in `recovery_required`, even if a stale action or operation was returned.

`authorize` sends `{"type":"authorize"}` without `--params` and returns the updated item,
possibly with a required user action. `--open` is supported only for authorize.
The [API spec](https://api.onkernel.com/spec.yaml) also accepts `fill`, with its inputs in
`--params`. The positional operation supplies `type`; including `type` in params is rejected.
possibly with a required user action. `collect` is also parameterless and returns a credential
collection URL. `--open` is supported for authorize, collect, and prepare_checkout.
The [API spec](https://api.onkernel.com/spec.yaml) also accepts `fill` and `prepare_checkout`, with their inputs in
`--params` or `--spec-file <path|->` (mutually exclusive, maximum 128 KiB). The positional
operation supplies `type`; including `type` in either input is rejected.
Parameters must be a JSON object without unknown or duplicate properties. There is no
operation `--spec` flag; wallet/card `--spec` flags remain unchanged. New parameterless
operations can still be invoked by name when advertised.

##### Fill checkout fields
##### Prepare an AgentCard checkout

For an unused AgentCard card, invoke `prepare_checkout` only when advertised:

```bash
kernel vaults items invoke user-123 order-1 prepare_checkout --params '{"checkout":{"browser_id":"browser-session-id","merchant_origin":"https://shop.example","environment":"production"}}' --open
kernel vaults items get user-123 order-1 --wait 60 -o json
```

`--spec-file <path|->` accepts the same JSON. `browser_id` is the active session with
this vault attached. `merchant_origin` is the canonical HTTPS origin of the top-level
merchant document, not the Square iframe; HTTP localhost is allowed for tests.
`environment` is `production` or `sandbox` and refers to Square, not the credential mode.

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,
browser, origin, environment, approval URL, and submission deadline.

Each preparation is single-use, including after failure or expiry. A preparation
marked `consumed` has been claimed; it does not prove the payment settled or succeeded.
Do not automatically retry or switch checkout paths after an uncertain result.

##### Fill browser fields

Fill is supported only when advertised by a ready Link card, not AgentCard. It writes stored
card data without returning the values or submitting checkout:
Fill supports credential items and ready Link cards when advertised by the API, not AgentCard.
Both use the same execution and outcome handling. Credential bindings use declared field names,
including TOTP fields, and must omit `format`. Credentials may omit `page_url` only when the API
can resolve a unique page. Card bindings require an exact HTTPS `page_url` and the card fields
listed below. It writes stored values without returning them or submitting the website form:

```bash
kernel vaults items get checkout order-1
Expand All @@ -504,16 +575,16 @@ kernel vaults items invoke checkout order-1 fill --params '{"browser_id":"browse

- `browser_id` is a browser **session ID**, not a reusable browser name. It is sent unchanged;
the CLI does not resolve names.
- `page_url` is the exact current top-level HTTPS URL, including path, query, and fragment,
- For cards, `page_url` is the exact current top-level HTTPS URL, including path, query, and fragment,
without embedded credentials. It must match exactly one open page; no prefix/glob matching.
- `fields` contains 1-32 bindings in write order. Each has `field` and a nonempty CSS
`selector` targeting an editable input/select or its container. The API searches the selected
page and descendants, including payment iframes. Do not supply frame IDs or literal values.
- Stored fields: `number`, `cvc`, `exp_month` (MM), `exp_year` (YYYY), `billing_name`,
- Stored card fields: `number`, `cvc`, `exp_month` (MM), `exp_year` (YYYY), `billing_name`,
`billing_line1`, `billing_line2`, `billing_city`, `billing_state`, `billing_postal_code`,
`billing_country`. Billing fields use the stored address without reformatting; request only
needed fields. Missing requested billing data fails validation before browser writes.
- Combined `expiration` requires `format: "MM/YY"` or `"MM/YYYY"`. Other fields reject `format`.
- Combined card `expiration` requires `format: "MM/YY"` or `"MM/YYYY"`. Other fields reject `format`.
- Optional `timeout_ms` is an integer from 1 to 30000 (default 10000), for the whole operation.

Fill returns an execution result, **not an updated item**. Normal output shows zero-based
Expand All @@ -530,8 +601,8 @@ errors are printed in fill results.

Fill is non-atomic: execution stops at the first failed/unknown field and earlier writes are
not rolled back. `filled` does not mean the site retained or accepted the value; `completed`
does not mean paid. Transport errors do not prove no writes occurred. Inspect the browser
before deciding what to do next. The CLI never retries, submits checkout, or falls back to
does not mean logged in or paid. Transport errors do not prove no writes occurred. Inspect the browser
before deciding what to do next. The CLI never retries, submits website forms, or falls back to
aliases. Returned `state.aliases` remain an alternative for explicitly chosen egress-substitution
integrations, not a recovery path after a failed or indeterminate fill.

Expand Down
2 changes: 1 addition & 1 deletion cmd/browsers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2974,7 +2974,7 @@ func init() {
browsersCreateCmd.Flags().StringSlice("extension", []string{}, "Extension IDs or names to load (repeatable; may be passed multiple times or comma-separated)")
browsersCreateCmd.Flags().String("viewport", "", "Browser viewport size (e.g., 1920x1080@25). Supported: 2560x1440@10, 1920x1080@25, 1920x1200@25, 1440x900@25, 1024x768@60, 1200x800@60, 1280x800@60")
browsersCreateCmd.Flags().Bool("viewport-interactive", false, "Interactively select viewport size from list")
browsersCreateCmd.Flags().StringArray("vault", nil, "Project-owned vault ID or name to attach at creation (repeatable, max 20; incompatible with pools)")
browsersCreateCmd.Flags().StringArray("vault", nil, "Vault ID or name to attach for credential/card fill at creation (repeatable, max 20; incompatible with pools; see vaults --help)")
browsersCreateCmd.Flags().String("pool-id", "", "Browser pool ID to acquire from (mutually exclusive with --pool-name)")
browsersCreateCmd.Flags().String("pool-name", "", "Browser pool name to acquire from (mutually exclusive with --pool-id)")
browsersCreateCmd.Flags().String("telemetry", "", "Configure telemetry (opt-in): --telemetry=all (default set), --telemetry=off (disable), or --telemetry=console,network (capture exactly those categories)")
Expand Down
36 changes: 27 additions & 9 deletions cmd/vaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"github.com/kernel/cli/pkg/util"
kernel "github.com/kernel/kernel-go-sdk"
"github.com/kernel/kernel-go-sdk/option"
"github.com/kernel/kernel-go-sdk/shared/constant"
"github.com/pterm/pterm"
)

Expand Down Expand Up @@ -193,7 +192,7 @@ func (c VaultsCmd) SaveCard(ctx context.Context, vault, key string, spec kernel.
var item *kernel.VaultItemUnion
var err error
if update {
item, err = c.vaults.Items.Update(ctx, key, kernel.VaultItemUpdateParams{IDOrName: vault, Spec: spec}, option.WithMaxRetries(0))
item, err = c.vaults.Items.Update(ctx, key, kernel.VaultItemUpdateParams{IDOrName: vault, OfCardVaultItemUpdateRequest: &kernel.VaultItemUpdateParamsBodyCardVaultItemUpdateRequest{Type: "card", Spec: spec}}, option.WithMaxRetries(0))
} else {
item, err = c.vaults.Items.Upsert(ctx, key, kernel.VaultItemUpsertParams{IDOrName: vault, OfCard: &kernel.VaultItemUpsertParamsBodyCard{Spec: spec}}, option.WithMaxRetries(0))
}
Expand All @@ -203,13 +202,16 @@ func (c VaultsCmd) SaveCard(ctx context.Context, vault, key string, spec kernel.
return c.showItem(item, output, false)
}

func (c VaultsCmd) Invoke(ctx context.Context, vault, key, operation string, params *vaultFillParams, output string, open bool) error {
func (c VaultsCmd) Invoke(ctx context.Context, vault, key, operation string, params *vaultOperationParams, output string, open bool) error {
if strings.TrimSpace(operation) == "" {
return fmt.Errorf("operation must not be empty")
}
if operation == "fill" && (params == nil || open) {
if operation == "fill" && (params == nil || params.Fill == nil || open) {
return fmt.Errorf("fill requires --params and does not support --open")
}
if operation == "prepare_checkout" && (params == nil || params.Checkout == nil) {
return fmt.Errorf("prepare_checkout requires checkout parameters")
}
item, err := c.vaults.Items.Get(ctx, key, kernel.VaultItemGetParams{IDOrName: vault}, option.WithMaxRetries(0))
if err != nil {
if operation == "fill" {
Expand Down Expand Up @@ -241,15 +243,31 @@ func (c VaultsCmd) Invoke(ctx context.Context, vault, key, operation string, par
return fmt.Errorf("operation %q is not advertised in available_operations; inspect the item", operation)
}
if operation == "fill" {
return c.fill(ctx, vault, key, params, output)
if err := validateVaultFillItem(params.Fill, item); err != nil {
return err
}
return c.fill(ctx, vault, key, params.Fill, output)
}
// Preserve support for other advertised parameterless operations.
authorize := kernel.VaultItemPerformOperationParamsBodyAuthorize{Type: constant.Authorize(operation)}
response, err := c.vaults.Items.PerformOperation(ctx, key, kernel.VaultItemPerformOperationParams{IDOrName: vault, OfAuthorize: &authorize}, option.WithMaxRetries(0))
request := kernel.VaultItemPerformOperationParams{IDOrName: vault}
if operation == "prepare_checkout" {
if item.Type != "card" || item.Spec.Provider != "agentcard" {
return fmt.Errorf("prepare_checkout requires an AgentCard card")
}
request.OfPrepareCheckout = &kernel.PrepareCheckoutVaultItemOperationRequestParam{Type: "prepare_checkout", Checkout: *params.Checkout}
} else if operation == "collect" {
request.OfCollect = &kernel.CollectVaultItemOperationRequestParam{Type: "collect"}
} else {
// Preserve support for other advertised parameterless operations.
request.OfAuthorize = &kernel.AuthorizeVaultItemOperationRequestParam{Type: kernel.AuthorizeVaultItemOperationRequestType(operation)}
}
response, err := c.vaults.Items.PerformOperation(ctx, key, request, option.WithMaxRetries(0))
if err != nil {
if item.Type == "credential" {
return vaultCredentialError(err)
}
return util.CleanedUpSdkError{Err: err}
}
if response == nil || (response.Type != "card" && response.Type != "wallet") {
if response == nil || (response.Type != "card" && response.Type != "wallet" && response.Type != "credential") {
return fmt.Errorf("unexpected vault operation response; inspect the item and do not retry")
}
var updated kernel.VaultItemUnion
Expand Down
Loading
Loading