Skip to content

Commit 492c59f

Browse files
CLI: Update SDK to f737b63ffd168cc67bf0158b961ca3275c94bd1c and add new commands/flags (#291)
This PR updates the Go SDK to f737b63ffd168cc67bf0158b961ca3275c94bd1c and adds CLI commands/flags for new SDK methods. ## SDK Update - Updated kernel-go-sdk to f737b63ffd168cc67bf0158b961ca3275c94bd1c (v0.122.1-0.20261009181557-f737b63ffd16) - The latest SDK commit (f737b63) only changes documentation for the 1Password fill result: `fill_unknown` can now carry `error_code: autosubmitFailed`, and after an uncertain fill the caller should inspect the page. The CLI already prints `error_code` for every `1pw_fill` status, and its help text already gives this guidance, so no code changes were needed for it. - An earlier commit on this branch updated the SDK to 6f3ae15351013fd71e5d554b69d7e532e1e592db, which adds `video_memory` for the 4GiB VRAM GPU browser tier. ## Coverage Analysis This PR was generated by performing a full enumeration of SDK methods and CLI commands. After this PR, every method in api.md has a CLI command. The only exception is `ConfigRegistry.*`, which is skipped because those endpoints are marked `x-cli-skip`. ## New Commands - `kernel vaults get-encryption-key <vault>` for `client.Vaults.GetEncryptionKey()`. It shows a table by default, and `-o json` returns the raw key response. ## New Flags - `--video-memory` on `kernel browsers create` for `BrowserNewParams.VideoMemory`. It accepts `2GiB` (the default: 4 vCPU, 6GiB memory) or `4GiB` (8 vCPU, 12GiB memory), matched case-insensitively and checked before the request is sent. It requires `--gpu`. `browsers get` and `create` now show a "Video Memory" row when the API returns one. - The new `encrypted_value` field (`CredentialVaultFieldInputParam.EncryptedValue`, `CredentialVaultFieldUpdateParam.EncryptedValue`) goes through the existing `--spec-file` JSON on `vaults credentials create/update`. The help text and README now describe it. ## Testing - f737b63 bump: `go build ./...`, `go vet ./...` and `go test ./...` all pass. - `browsers create --gpu --video-memory 2GiB -t 30`: the session was created, and `browsers get` showed `GPU true`, `Memory 6GiB`, `Video Memory 2GiB`. The test browser was deleted afterwards. - `browsers create --gpu --video-memory 4GiB`: the API received the 4GiB tier but returned `No_gpu_browsers_available` (at capacity), so no 4GiB session was created. - `--video-memory 3GiB` is rejected on the client with a clear error. A new unit test, `TestBrowsersCreate_WithVideoMemory`, covers this. - `vaults get-encryption-key`: table output, `-o json` output, and the not-found error all checked. - Created a credential with a real ECDH-ES/A256GCM JWE `encrypted_value`. The item became ready, and the update bumped the version from 1 to 2. Triggered by: kernel/kernel-go-sdk@f737b63 Reviewer: @kernel-internal[bot] 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Mostly new CLI flags/commands and docs atop the SDK; credential encryption still flows through existing spec-file JSON without new local crypto logic. > > **Overview** > Updates **kernel-go-sdk** and exposes two new API surfaces in the CLI. > > Adds **`kernel vaults get-encryption-key`** so custom credential-collection apps can fetch a vault’s public JWK (`kid`, ECDH-ES / A256GCM) for browser-side JWE encryption, with table or `-o json` output. README and **`vaults credentials`** help now document **`encrypted_value`** as an alternative to **`value`** on create/update specs (mutually exclusive). > > Adds **`--video-memory`** on **`kernel browsers create`** (`2GiB` / `4GiB`, case-insensitive client validation; omitted leaves the API default). Create passes it through to **`BrowserNewParams`**, and **`browsers get`/create detail** show **Video Memory** when present. **`TestBrowsersCreate_WithVideoMemory`** covers mapping, omission, and invalid sizes. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 456553a. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: kernel-internal[bot] <260533166+kernel-internal[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent ddbe59f commit 492c59f

9 files changed

Lines changed: 107 additions & 5 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,7 @@ cannot switch projects.
410410
| `kernel vaults create --name <name>` | Create or retrieve the vault with that immutable name |
411411
| `kernel vaults list` | `--limit 1..100` (default 20), `--offset`, `--query` (name substring or exact ID); JSON includes `vaults` and optional `next_offset` |
412412
| `kernel vaults get <vault>` | Get by ID or name |
413+
| `kernel vaults get-encryption-key <vault>` | Get the public key custom collection apps use to send `encrypted_value` |
413414
| `kernel vaults delete <vault>` | Invalidate the vault and all its items; `--yes` skips confirmation |
414415
| `kernel vaults wallets create <vault> <key> --provider link\|agentcard --spec '<json>'` | Connect/enroll a wallet using its provider's spec; `--open` opens a returned HTTPS action URL |
415416
| `kernel vaults wallets payment-methods <vault> <key>` | Fetch advertised live payment methods; JSON is the item with `expanded.payment_methods` |

‎cmd/browsers.go‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,26 @@ func parseMemoryFlag(memory string) (kernel.BrowserMemoryRequest, error) {
201201
return "", fmt.Errorf("invalid --memory value: %s (must be one of %s)", memory, strings.Join(availableMemorySizes(), ", "))
202202
}
203203

204+
// availableVideoMemorySizes returns the video memory (VRAM) sizes the API
205+
// accepts for a GPU browser session.
206+
func availableVideoMemorySizes() []string {
207+
return []string{"2GiB", "4GiB"}
208+
}
209+
210+
// parseVideoMemoryFlag validates a --video-memory value. An empty value means the
211+
// flag was not set, which lets the API apply its default (2GiB).
212+
func parseVideoMemoryFlag(videoMemory string) (kernel.BrowserVideoMemory, error) {
213+
if videoMemory == "" {
214+
return "", nil
215+
}
216+
for _, m := range availableVideoMemorySizes() {
217+
if strings.EqualFold(videoMemory, m) {
218+
return kernel.BrowserVideoMemory(m), nil
219+
}
220+
}
221+
return "", fmt.Errorf("invalid --video-memory value: %s (must be one of %s)", videoMemory, strings.Join(availableVideoMemorySizes(), ", "))
222+
}
223+
204224
// maxPrivateHosts mirrors the API's cap on network.private_hosts entries.
205225
const maxPrivateHosts = 32
206226

@@ -468,6 +488,7 @@ type BrowsersCreateInput struct {
468488
Headless BoolFlag
469489
GPU BoolFlag
470490
Memory string
491+
VideoMemory string
471492
InvocationID string
472493
Kiosk BoolFlag
473494
ProfileID string
@@ -705,6 +726,13 @@ func (b BrowsersCmd) Create(ctx context.Context, in BrowsersCreateInput) error {
705726
if memory != "" {
706727
params.Memory = memory
707728
}
729+
videoMemory, err := parseVideoMemoryFlag(in.VideoMemory)
730+
if err != nil {
731+
return err
732+
}
733+
if videoMemory != "" {
734+
params.VideoMemory = videoMemory
735+
}
708736
if in.InvocationID != "" {
709737
params.InvocationID = kernel.Opt(in.InvocationID)
710738
}
@@ -972,6 +1000,9 @@ func (b BrowsersCmd) Get(ctx context.Context, in BrowsersGetInput) error {
9721000
tableData = append(tableData, []string{"Stealth", fmt.Sprintf("%t", browser.Stealth)})
9731001
tableData = append(tableData, []string{"GPU", fmt.Sprintf("%t", browser.GPU)})
9741002
tableData = append(tableData, []string{"Memory", util.OrDash(string(browser.Memory))})
1003+
if browser.VideoMemory != "" {
1004+
tableData = append(tableData, []string{"Video Memory", string(browser.VideoMemory)})
1005+
}
9751006
tableData = append(tableData, []string{"Kiosk Mode", fmt.Sprintf("%t", browser.KioskMode)})
9761007
if browser.Viewport.Width > 0 && browser.Viewport.Height > 0 {
9771008
viewportStr := fmt.Sprintf("%dx%d", browser.Viewport.Width, browser.Viewport.Height)
@@ -3247,6 +3278,7 @@ unrestricted code execution inside the browser VM and is not sandboxed.`,
32473278
browsersCreateCmd.Flags().BoolP("headless", "H", false, "Launch browser without GUI access")
32483279
browsersCreateCmd.Flags().Bool("gpu", false, "Launch browser with hardware-accelerated GPU rendering")
32493280
browsersCreateCmd.Flags().String("memory", "", "Memory for a headful, non-GPU browser session: '8GiB' (default) or '16GiB'")
3281+
browsersCreateCmd.Flags().String("video-memory", "", "Video memory (VRAM) for a GPU browser session (requires --gpu): '2GiB' (default, 4 vCPU/6GiB memory) or '4GiB' (8 vCPU/12GiB memory)")
32503282
browsersCreateCmd.Flags().String("invocation-id", "", "Associate the browser session with an invocation")
32513283
browsersCreateCmd.Flags().Bool("kiosk", false, "Launch browser in kiosk mode")
32523284
browsersCreateCmd.Flags().IntP("timeout", "t", 60, "Timeout in seconds for the browser session")
@@ -3387,6 +3419,7 @@ func runBrowsersCreate(cmd *cobra.Command, args []string) error {
33873419
headlessVal, _ := cmd.Flags().GetBool("headless")
33883420
gpuVal, _ := cmd.Flags().GetBool("gpu")
33893421
memory, _ := cmd.Flags().GetString("memory")
3422+
videoMemory, _ := cmd.Flags().GetString("video-memory")
33903423
invocationID, _ := cmd.Flags().GetString("invocation-id")
33913424
kioskVal, _ := cmd.Flags().GetBool("kiosk")
33923425
timeout, _ := cmd.Flags().GetInt("timeout")
@@ -3544,6 +3577,7 @@ func runBrowsersCreate(cmd *cobra.Command, args []string) error {
35443577
Headless: BoolFlag{Set: cmd.Flags().Changed("headless"), Value: headlessVal},
35453578
GPU: BoolFlag{Set: cmd.Flags().Changed("gpu"), Value: gpuVal},
35463579
Memory: memory,
3580+
VideoMemory: videoMemory,
35473581
InvocationID: invocationID,
35483582
Kiosk: BoolFlag{Set: cmd.Flags().Changed("kiosk"), Value: kioskVal},
35493583
ProfileID: profileID,

‎cmd/browsers_test.go‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -900,6 +900,35 @@ func TestBrowsersCreate_WithMemory(t *testing.T) {
900900
assert.Error(t, b.Create(context.Background(), BrowsersCreateInput{Memory: "4GiB"}))
901901
}
902902

903+
func TestBrowsersCreate_WithVideoMemory(t *testing.T) {
904+
setupStdoutCapture(t)
905+
906+
var captured kernel.BrowserNewParams
907+
fake := &FakeBrowsersService{
908+
NewFunc: func(ctx context.Context, body kernel.BrowserNewParams, opts ...option.RequestOption) (*kernel.BrowserNewResponse, error) {
909+
captured = body
910+
return &kernel.BrowserNewResponse{SessionID: "sess-video-memory"}, nil
911+
},
912+
}
913+
b := BrowsersCmd{browsers: fake}
914+
915+
err := b.Create(context.Background(), BrowsersCreateInput{GPU: BoolFlag{Set: true, Value: true}, VideoMemory: "4gib"})
916+
require.NoError(t, err)
917+
assert.Equal(t, kernel.BrowserVideoMemory4GiB, captured.VideoMemory)
918+
919+
raw, err := captured.MarshalJSON()
920+
require.NoError(t, err)
921+
assert.Contains(t, string(raw), `"video_memory":"4GiB"`)
922+
923+
// Omitting the flag sends nothing; the server defaults to 2GiB.
924+
err = b.Create(context.Background(), BrowsersCreateInput{GPU: BoolFlag{Set: true, Value: true}})
925+
require.NoError(t, err)
926+
assert.Empty(t, captured.VideoMemory)
927+
928+
// An unsupported size is rejected before the request is made.
929+
assert.Error(t, b.Create(context.Background(), BrowsersCreateInput{VideoMemory: "8GiB"}))
930+
}
931+
903932
func TestBrowsersCreate_WithChromePolicy(t *testing.T) {
904933
setupStdoutCapture(t)
905934

‎cmd/vaults.go‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ func (c VaultsCmd) Get(ctx context.Context, vault, output string) error {
5151
return printVault(v, output)
5252
}
5353

54+
func (c VaultsCmd) GetEncryptionKey(ctx context.Context, vault, output string) error {
55+
k, err := c.vaults.GetEncryptionKey(ctx, vault, option.WithMaxRetries(0))
56+
if err != nil {
57+
return util.CleanedUpSdkError{Err: err}
58+
}
59+
return printVaultEncryptionKey(k, output)
60+
}
61+
5462
func (c VaultsCmd) List(ctx context.Context, limit, offset int64, query, project, output string) error {
5563
if limit < 1 || limit > 100 || offset < 0 {
5664
return fmt.Errorf("--limit must be between 1 and 100; --offset must be non-negative")

‎cmd/vaults_commands.go‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,23 @@ JSON output preserves returned public fields but omits unknown/opaque provider d
132132
return getVaultsHandler(cmd).Get(cmd.Context(), args[0], vaultOutput(cmd))
133133
}}
134134
addVaultJSONOutputFlag(get)
135-
cmd.AddCommand(create, list, get, newVaultDeleteCommand(false))
135+
136+
encryptionKey := &cobra.Command{Use: "get-encryption-key <vault>", Short: "Get a vault's public key for browser-encrypted credential values", Args: cobra.ExactArgs(1), PreRunE: vaultPreRun,
137+
Long: `Get the public key that custom credential collection web apps use to encrypt values in the browser.
138+
139+
Use this only if you run your own credential collection web app and want values
140+
encrypted in the browser, sent to your backend still encrypted, and forwarded to
141+
Kernel's API still encrypted. In every other case, including server-side code that
142+
already holds the plaintext, use value. The page encrypts each value to this key as a
143+
compact JWE (alg ECDH-ES, enc A256GCM, kid in the protected header), and your backend
144+
forwards the ciphertext unchanged as encrypted_value in credentials create/update specs.
145+
Each vault has its own key; it is created on first request and may be cached.`,
146+
Example: " kernel vaults get-encryption-key user-vault -o json",
147+
RunE: func(cmd *cobra.Command, args []string) error {
148+
return getVaultsHandler(cmd).GetEncryptionKey(cmd.Context(), args[0], vaultOutput(cmd))
149+
}}
150+
addJSONOutputFlag(encryptionKey)
151+
cmd.AddCommand(create, list, get, encryptionKey, newVaultDeleteCommand(false))
136152

137153
items := &cobra.Command{Use: "items", Short: "Inspect readiness and collection URLs, or invoke collect/fill/webmcp_invoke", Long: "Use get --wait 60 to observe readiness and get -o json for schema/version/presence.\nUse invoke collect to obtain a collection URL, or invoke fill --spec-file to fill a browser.\nUse webmcp invoke to call a live WebMCP tool with item fields bound to null input slots.\n1Password credentials use the advertised 1pw_* operations instead of collect/fill/webmcp_invoke.\nCreate and edit credentials with vaults credentials; payment items use wallets/cards."}
138154
itemList := &cobra.Command{Use: "list <vault>", Short: "List items by vault ID or name", Args: cobra.ExactArgs(1), PreRunE: vaultPreRun,

‎cmd/vaults_credentials.go‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ user-facing collection form renders that order unchanged. Definitions accept a s
107107
name and an optional non-secret human-readable label; forms fall back to name. Updates,
108108
state, and browser fills always use name. Field types are text, email, password, and
109109
totp; definitions also accept required, sensitive, and value.
110+
Custom collection web apps that encrypt in the browser may send encrypted_value (a
111+
compact JWE to the key from vaults get-encryption-key) instead of value; never both.
110112
Set description to the recognizable site name only, e.g. "Hacker News", not
111113
"Hacker News sign-in credentials". This text is the user-facing form title.
112114
Set sensitive:false explicitly for ordinary usernames and email addresses.
@@ -155,7 +157,7 @@ func newVaultCredentialsCommand() *cobra.Command {
155157
},
156158
}
157159
if update {
158-
cmd.Long += "\nUpdate applies to Kernel-hosted credentials only. Update spec fields are an object keyed by field name, not the ordered array used on create.\nUpdate preserves omitted fields, replaces nonempty string values, and clears supported values with null or an empty string. Clearing a required text/email/password field returns pending_collection; form submissions still require a nonempty value.\nField definitions are immutable. Do not automatically retry version conflicts."
160+
cmd.Long += "\nUpdate applies to Kernel-hosted credentials only. Update spec fields are an object keyed by field name, not the ordered array used on create.\nUpdate preserves omitted fields, replaces nonempty string values (value, or encrypted_value from a browser-encrypting collection app), and clears supported values with null or an empty string. Clearing a required text/email/password field returns pending_collection; form submissions still require a nonempty value.\nField definitions are immutable. Do not automatically retry version conflicts."
159161
cmd.Flags().Int64("version", 0, "Expected version from items get (required; never auto-refreshed)")
160162
_ = cmd.MarkFlagRequired("version")
161163
cmd.Flags().String("expected-item-id", "", "Immutable item ID from the original read; reject an update if the key now refers to a replacement item")

‎cmd/vaults_output.go‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,18 @@ func printVault(v *kernel.Vault, output string) error {
235235
return nil
236236
}
237237

238+
// The encryption key is public, so JSON output passes the API response through unchanged.
239+
func printVaultEncryptionKey(k *kernel.VaultEncryptionKey, output string) error {
240+
if output == "json" {
241+
return printVaultJSON(json.RawMessage(k.RawJSON()))
242+
}
243+
PrintTableNoPad(pterm.TableData{
244+
{"Property", "Value"}, {"Key ID (kid)", k.Kid}, {"Algorithm (alg)", string(k.Alg)}, {"Encryption (enc)", string(k.Enc)},
245+
{"JWK kty", k.Jwk.Kty}, {"JWK crv", k.Jwk.Crv}, {"JWK x", k.Jwk.X}, {"JWK y", k.Jwk.Y},
246+
}, true)
247+
return nil
248+
}
249+
238250
func vaultDisplayURL(address string) bool {
239251
u, err := url.Parse(address)
240252
if err != nil || (u.Scheme != "https" && u.Scheme != "http") || u.Hostname() == "" || u.User != nil {

‎go.mod‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ require (
99
github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.1
1010
github.com/golang-jwt/jwt/v5 v5.2.2
1111
github.com/joho/godotenv v1.5.1
12-
github.com/kernel/kernel-go-sdk v0.121.1-0.20261009121157-454206ab83bc
12+
github.com/kernel/kernel-go-sdk v0.122.1-0.20261009181557-f737b63ffd16
1313
github.com/klauspost/compress v1.18.5
1414
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
1515
github.com/pterm/pterm v0.12.80

‎go.sum‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
6666
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
6767
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
6868
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
69-
github.com/kernel/kernel-go-sdk v0.121.1-0.20261009121157-454206ab83bc h1:iIVpTV5HZMbbDp74IB/0z1Qjz9uzL9wpKJRZXoiqPEk=
70-
github.com/kernel/kernel-go-sdk v0.121.1-0.20261009121157-454206ab83bc/go.mod h1:EeZzSuHZVeHKxKCPUzxou2bovNGhXaz0RXrSqKNf1AQ=
69+
github.com/kernel/kernel-go-sdk v0.122.1-0.20261009181557-f737b63ffd16 h1:/cyzSOIuZRlBmYJyXFBE0A62g+BZ+R+UNxA7VmZOJqE=
70+
github.com/kernel/kernel-go-sdk v0.122.1-0.20261009181557-f737b63ffd16/go.mod h1:EeZzSuHZVeHKxKCPUzxou2bovNGhXaz0RXrSqKNf1AQ=
7171
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
7272
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
7373
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=

0 commit comments

Comments
 (0)