Skip to content

Commit 322acab

Browse files
committed
Support Kernel-managed vault wallets and cards
1 parent 4aff0a9 commit 322acab

10 files changed

Lines changed: 346 additions & 42 deletions

‎README.md‎

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -397,9 +397,10 @@ cannot switch projects.
397397
| `kernel vaults list` | `--limit 1..100` (default 20), `--offset`; JSON includes `vaults` and optional `next_offset` |
398398
| `kernel vaults get <vault>` | Get by ID or name |
399399
| `kernel vaults delete <vault>` | Invalidate the vault and all its items; `--yes` skips confirmation |
400-
| `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 |
400+
| `kernel vaults wallets create <vault> <key> --provider link\|agentcard\|kernel --spec '<json>'` | Connect/enroll a wallet using its provider's spec; `--open` opens a returned HTTPS action URL |
401+
| `kernel vaults wallets get <vault> <key>` | Observe wallet state and its hosted action; `--wait 0..60`, `--open` |
401402
| `kernel vaults wallets payment-methods <vault> <key>` | Fetch advertised live payment methods; JSON is the item with `expanded.payment_methods` |
402-
| `kernel vaults cards create <vault> <key> --provider link\|agentcard --spec '<json>'` | Create a card request; never implicitly authorize Link |
403+
| `kernel vaults cards create <vault> <key> --provider link\|agentcard\|kernel --spec '<json>'` | Create a card request without authorizing it |
403404
| `kernel vaults cards update <vault> <key> --provider link\|agentcard --spec '<json>'` | Update a card spec; pending issuance preserves omitted optional fields, and the API enforces state/provider constraints |
404405
| `kernel vaults items list <vault>` | List item keys, types, providers, status, and required actions |
405406
| `kernel vaults items get <vault> <key>` | Inspect state/actions/returned AgentCard aliases and copyable operation commands; `--wait 0..60`, `--expand payment_methods`, `--open` |
@@ -414,7 +415,8 @@ its generated item ID. Names and keys use letters, digits, dots, underscores, an
414415
JSON preserves field presence and API-returned AgentCard aliases, while omitting unknown fields,
415416
opaque metadata, and unrecognized event data. Human output labels aliases as non-secret
416417
checkout values and distinguishes card readiness from checkout authorization/payment outcomes.
417-
Link cards do not expose aliases or support egress substitution; browser checkout uses only `fill`.
418+
Link and Kernel cards do not expose aliases or support egress substitution; browser checkout
419+
uses only advertised `fill`.
418420
Action and approval URLs print in full on separate lines, without table truncation.
419421
Most API failures use the CLI's standard error formatter. Wallet creation and provider config
420422
commands withhold response/transport details to prevent credential echoes; HTTP status remains visible.
@@ -425,23 +427,29 @@ Other API errors still return a nonzero exit status.
425427
**Provider specifications:** wallet creation and card creation/update require `--provider`
426428
and `--spec '<json>'`. Supply only the spec object, not a `{type, spec}` envelope. The command
427429
sets the item type and injects `provider`; if JSON also contains `provider`, it must match.
428-
Other values are forwarded unchanged, including optional fields, without defaults or normalization.
429-
The API validates the provider-specific schema. Each command's `--help` includes its raw
430+
Other non-secret values are forwarded unchanged, including optional fields, without defaults
431+
or normalization. The API validates the provider-specific schema. Each command's `--help` includes its raw
430432
TypeScript-style types, which must stay in sync with the [API spec](https://api.onkernel.com/spec.yaml).
431433

434+
- **Kernel wallet:** use `{}`; no provider configuration or token file is accepted. The hosted
435+
`card_enrollment` action collects card details from the cardholder, never from the CLI.
432436
- **Link wallet:** supply `authorization: {method: "oauth", client: {type: "kernel_managed"}}`.
433437
- **AgentCard wallet:** use `{}` to enroll, or supply `user_id` for a user enrolled in the same organization and provider configuration.
438+
- **Kernel card:** supply `wallet`, `amount` (minor units, at most 50000), `currency`,
439+
`merchant_name`, and HTTPS `merchant_url`. Supply two-letter `merchant_country` for Visa.
440+
Kernel card updates are unsupported.
434441
- **Link card:** include the required fields shown in help. Optional `line_items`, `totals`,
435442
`metadata`, and `expires_at` are supported through JSON.
436443
- **AgentCard card:** uses `merchant`, not Link's `merchant_name`. Its optional `card_id` selects
437444
a vaulted card; otherwise the cardholder selects one at approval.
438445

439-
`cards update` replaces the spec for requested cards. Pending issuance updates preserve omitted
440-
optional fields; explicit empty lists clear them. Wallet/provider bindings and unsupported fields
446+
`cards update` replaces the spec for supported providers' requested cards; Kernel cards
447+
cannot be updated. Pending issuance updates preserve omitted optional fields; explicit empty lists clear them. Wallet/provider bindings and unsupported fields
441448
cannot change after authorization starts. Checkout cards can be edited between authorizations.
442449
An uncertain update enters `recovery_required` and must not be retried. Identical card creation
443-
returns its existing state without polling, reauthorizing, or resetting recovery. Permitted
444-
checkout domains remain provider-assigned. Neither command submits a merchant payment.
450+
returns its existing state without polling, reauthorizing, or resetting recovery. Link and
451+
AgentCard checkout domains remain provider-assigned; Kernel fill is locked to the exact origin
452+
of `merchant_url`. Neither command submits a merchant payment.
445453

446454
#### Provider configurations and imported grants
447455

@@ -507,6 +515,40 @@ Do not retry, delete, or replace the original operation. Reconcile with the prov
507515
there is no reset or caller-asserted reconciliation endpoint. Unresolved child cards can block
508516
wallet and vault deletion. Time passing or deletion is not evidence of non-execution.
509517

518+
#### Kernel-managed card checkout
519+
520+
```bash
521+
kernel vaults create --name checkout
522+
kernel vaults wallets create checkout cardholder --provider kernel --spec '{}' --open
523+
kernel vaults wallets get checkout cardholder --wait 60
524+
kernel vaults wallets payment-methods checkout cardholder
525+
```
526+
527+
Share the returned `card_enrollment` URL with the cardholder. A connected wallet may still
528+
report `single_use_card.eligible: false` with a `network_token_*` reason while network token
529+
enrollment is pending or unsupported. Inspect the expansion before requesting a card; this
530+
wallet uses its enrolled card, not a `payment_method_id` in the card spec.
531+
532+
```bash
533+
kernel vaults cards create checkout order-1 --provider kernel --spec '{
534+
"wallet":"cardholder", "amount":1200, "currency":"usd",
535+
"merchant_name":"Example Shop", "merchant_url":"https://shop.example/checkout",
536+
"merchant_country":"US"
537+
}'
538+
kernel vaults items get checkout order-1
539+
kernel vaults items invoke checkout order-1 authorize --open
540+
kernel vaults items get checkout order-1 --wait 60
541+
```
542+
543+
Only invoke `authorize` when advertised. Visa may return a `spend_approval` URL for the
544+
cardholder; Mastercard may become ready without one. An unresolved `recovery_required` item
545+
must not be retried, deleted, or replaced. When ready, create a browser with `--vault checkout`
546+
and invoke the advertised `fill` operation with the browser ID, an HTTPS `page_url` on the
547+
exact origin of `merchant_url`, and the checkout field selectors (see `items invoke --help`).
548+
Fill types the one-time network token and code but does not submit the merchant form.
549+
Submit before `expires_at`; neither ready nor fill proves that the merchant charged the card.
550+
Never put PAN, CVC, or enrollment credentials in CLI arguments or logs.
551+
510552
#### Link checkout preparation
511553

512554
Link OAuth, user approval, and provider-issued single-use card issuance precede browser checkout.

‎cmd/vaults_commands.go‎

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -83,18 +83,22 @@ Otherwise, the API resolves the project from your credentials and its defaults.
8383
Vault names, item keys, and project ownership are immutable.
8484
8585
1. Create/select a vault, then create a provider wallet and follow its returned action.
86-
2. For Link, list wallet payment methods and select an ID explicitly.
86+
For Kernel, share the hosted card_enrollment URL; card details stay on that page.
87+
2. Inspect wallets payment-methods for eligibility and reasons before creating a card.
88+
For Link, select a payment method ID explicitly.
8789
3. Create a card request with --provider and --spec JSON.
8890
4. Inspect items get, then use items invoke <vault> <key> <operation> only when advertised.
8991
Follow the operation description and any returned provider action.
9092
5. Attach the vault with browsers create --vault <id-or-name>; attachment is required for fill.
91-
Ready Link cards use only advertised fill with --params for browser checkout.
92-
Link cards do not expose aliases or support egress substitution.
93+
Ready Link and Kernel cards use only advertised fill with --params for browser checkout.
94+
Neither exposes aliases or supports egress substitution. Kernel fill is locked to
95+
merchant_url's origin; submit the merchant checkout before the card expires.
9396
AgentCard-only checkout aliases support egress substitution with checkout hold,
9497
approval, and replay; they are not a fallback after fill.
95-
Inspect items get/events for payment outcomes.
98+
Inspect items get/events for vault outcomes; Kernel does not observe merchant charges.
9699
97-
Permitted checkout domains are provider-assigned and displayed when returned;
100+
Permitted checkout domains are provider-assigned and displayed when returned; Kernel
101+
card fill instead uses the exact origin of merchant_url.
98102
there is no domain-setting API.
99103
Never supply card data, OAuth codes, ciphertext, or secrets in shell arguments.
100104
Use vault-provider-configs for client credentials and wallets create --tokens-file
@@ -175,8 +179,9 @@ exp_year (YYYY), billing_name, billing_line1, billing_line2, billing_city,
175179
billing_state, billing_postal_code, billing_country. expiration requires format MM/YY
176180
or MM/YYYY. Optional timeout_ms is 1-30000 (default 10000).
177181
The API searches the page and descendant frames, including payment iframes.
178-
Fill is available for credential items and ready Link cards when advertised, not AgentCard.
179-
Link cards do not expose aliases or support egress substitution.
182+
Fill is available for credential items and ready Link or Kernel cards when advertised, not AgentCard.
183+
Link and Kernel cards do not expose aliases or support egress substitution. Kernel card fill
184+
is locked to merchant_url's origin and its one-time code expires; submit checkout before expiry.
180185
Fill writes real values into the browser; unrestricted browser/CDP access can read them.
181186
Fill never explicitly submits forms or clicks buttons, but input/change events may trigger site behavior.
182187
completed means fields were filled, not website acceptance, login, or payment success.
@@ -237,6 +242,7 @@ JSON
237242
kernel vaults items invoke user-vault login webmcp_invoke --spec-file - <<'JSON'
238243
{"browser_id":"<browser-id>","tool_ref":"<tool-ref>","page_url":"https://example.com/login","input":{"email":null,"password":null},"bindings":[{"field":"email","input_path":"/email"},{"field":"password","input_path":"/password"}]}
239244
JSON
245+
kernel vaults items invoke checkout order-1 authorize --open
240246
kernel vaults items invoke user-vault github 1pw_create_access_request --params '{"browser_id":"<browser-id>","reason":"Sign in to GitHub"}'
241247
kernel vaults items invoke user-vault github 1pw_access_request_status --params '{"browser_id":"<browser-id>","timeout_seconds":60}'
242248
kernel vaults items invoke user-vault github 1pw_fill --params '{"browser_id":"<browser-id>","page_url":"https://github.com/login"}'
@@ -273,7 +279,7 @@ JSON
273279
items.AddCommand(itemList, itemGet, itemEvents, invoke, newVaultWebMCPCommand(), newVaultDeleteCommand(true))
274280

275281
wallets := &cobra.Command{Use: "wallets", Short: "Connect provider wallets and inspect funding methods"}
276-
walletCreate := &cobra.Command{Use: "create <vault> <key> --provider <link|agentcard> --spec '<json>'", Short: "Create a wallet and display its connection or enrollment action", Args: cobra.ExactArgs(2), PreRunE: vaultPreRun,
282+
walletCreate := &cobra.Command{Use: "create <vault> <key> --provider <link|agentcard|kernel> --spec '<json>'", Short: "Create a wallet and display its connection or enrollment action", Args: cobra.ExactArgs(2), PreRunE: vaultPreRun,
277283
Long: "Create a wallet at an immutable key and follow the returned provider action.\n" + vaultSpecHelp + vaultWalletSpecHelp,
278284
Example: ` kernel vaults wallets create checkout wallet-1 \
279285
--provider link --spec '{
@@ -284,7 +290,9 @@ JSON
284290
}' --open
285291
286292
kernel vaults wallets create checkout wallet-1 \
287-
--provider agentcard --spec '{}'`,
293+
--provider agentcard --spec '{}'
294+
295+
kernel vaults wallets create checkout cardholder --provider kernel --spec '{}' --open`,
288296
RunE: func(cmd *cobra.Command, args []string) error {
289297
spec, err := vaultWalletSpecFromFlags(cmd)
290298
if err != nil {
@@ -307,7 +315,17 @@ JSON
307315
return getVaultsHandler(cmd).GetItem(cmd.Context(), args[0], args[1], 0, []string{"payment_methods"}, resolveProjectSelection(project), vaultOutput(cmd), false)
308316
}}
309317
addVaultJSONOutputFlag(methods)
310-
wallets.AddCommand(walletCreate, methods)
318+
walletGet := &cobra.Command{Use: "get <vault> <key>", Short: "Get a wallet's state and hosted enrollment action", Args: cobra.ExactArgs(2), PreRunE: vaultPreRun,
319+
RunE: func(cmd *cobra.Command, args []string) error {
320+
project, _ := cmd.Flags().GetString("project")
321+
wait, _ := cmd.Flags().GetInt64("wait")
322+
open, _ := cmd.Flags().GetBool("open")
323+
return getVaultsHandler(cmd).GetItem(cmd.Context(), args[0], args[1], wait, nil, resolveProjectSelection(project), vaultOutput(cmd), open)
324+
}}
325+
walletGet.Flags().Int64("wait", 0, "Hold while pending for up to this many seconds (0-60); observe only")
326+
walletGet.Flags().Bool("open", false, "Open a returned HTTPS enrollment URL")
327+
addVaultJSONOutputFlag(walletGet)
328+
wallets.AddCommand(walletCreate, walletGet, methods)
311329

312330
cards := &cobra.Command{Use: "cards", Short: "Configure card requests"}
313331
cards.AddCommand(newVaultCardCommand(false), newVaultCardCommand(true))
@@ -338,10 +356,14 @@ func newVaultCardCommand(update bool) *cobra.Command {
338356
if update {
339357
use, short = "update", "Update a card spec when the API permits configuration"
340358
}
341-
cmd := &cobra.Command{Use: use + " <vault> <key> --provider <link|agentcard> --spec '<json>'", Short: short, Args: cobra.ExactArgs(2), PreRunE: vaultPreRun,
342-
Long: short + `. Neither create nor update authorizes a Link card.
343-
Requested cards accept a replacement spec. Pending issuance updates preserve omitted
344-
optional fields; explicit empty lists clear them. The API restricts fields after
359+
providers := "link|agentcard|kernel"
360+
if update {
361+
providers = "link|agentcard"
362+
}
363+
cmd := &cobra.Command{Use: use + " <vault> <key> --provider <" + providers + "> --spec '<json>'", Short: short, Args: cobra.ExactArgs(2), PreRunE: vaultPreRun,
364+
Long: short + `. Neither create nor update authorizes a card.
365+
Kernel cards cannot be updated. Other requested cards accept a replacement spec.
366+
Pending issuance updates preserve omitted optional fields; explicit empty lists clear them. The API restricts fields after
345367
authorization starts; wallet/provider bindings cannot change. An uncertain update
346368
enters recovery_required and must not be retried. Checkout cards can be edited
347369
between authorizations. Identical creates return existing state without resetting it.
@@ -360,6 +382,9 @@ A recovery item that permits abandonment must be deleted after explicit user con
360382
if err != nil {
361383
return err
362384
}
385+
if update && string(spec["provider"]) == `"kernel"` {
386+
return fmt.Errorf("Kernel card updates are not supported; create a new card item for a new purchase")
387+
}
363388
return getVaultsHandler(cmd).SaveCard(cmd.Context(), args[0], args[1], param.Override[kernel.CardVaultItemSpecUnionParam](spec), update, vaultOutput(cmd))
364389
}}
365390
addVaultSpecFlags(cmd)
@@ -368,16 +393,16 @@ A recovery item that permits abandonment must be deleted after explicit user con
368393
}
369394

370395
func addVaultSpecFlags(cmd *cobra.Command) {
371-
cmd.Flags().String("provider", "", "Provider: link or agentcard (required)")
396+
cmd.Flags().String("provider", "", "Provider: link, agentcard, or kernel (required)")
372397
cmd.Flags().String("spec", "", "Raw JSON specification object (required); see types and examples above")
373398
_ = cmd.MarkFlagRequired("provider")
374399
_ = cmd.MarkFlagRequired("spec")
375400
}
376401

377402
func vaultSpecFromFlags(cmd *cobra.Command) (map[string]json.RawMessage, error) {
378403
provider, _ := cmd.Flags().GetString("provider")
379-
if provider != "link" && provider != "agentcard" {
380-
return nil, fmt.Errorf("--provider must be link or agentcard")
404+
if provider != "link" && provider != "agentcard" && provider != "kernel" {
405+
return nil, fmt.Errorf("--provider must be link, agentcard, or kernel")
381406
}
382407
raw, _ := cmd.Flags().GetString("spec")
383408
var spec map[string]json.RawMessage
@@ -393,6 +418,9 @@ func vaultSpecFromFlags(cmd *cobra.Command) (map[string]json.RawMessage, error)
393418
if vaultSpecHasSecrets(json.RawMessage(raw)) {
394419
return nil, fmt.Errorf("--spec must not contain credentials or tokens; use the dedicated file/stdin inputs")
395420
}
421+
if provider == "kernel" && vaultSpecHasCardData(json.RawMessage(raw)) {
422+
return nil, fmt.Errorf("--spec must not contain card details; use hosted enrollment")
423+
}
396424
spec["provider"], _ = json.Marshal(provider)
397425
return spec, nil
398426
}

‎cmd/vaults_help.go‎

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ or normalization. Never include card data, OAuth tokens, or provider secrets in
1313
`
1414

1515
const vaultWalletSpecHelp = `
16-
Omit config selection to preserve Kernel-managed defaults.
16+
For --provider kernel, use --spec '{}'. Share the returned card_enrollment URL
17+
with the cardholder; never enter card details in the CLI. Use wallets get --wait 60
18+
to observe connection, then wallets payment-methods to inspect token eligibility.
19+
A connected wallet may still be ineligible while network token enrollment runs.
20+
Kernel wallets accept neither provider configuration flags nor --tokens-file.
21+
For Link and AgentCard, omit config selection to preserve managed defaults.
1722
--provider-config-id and --provider-config-name are mutually exclusive.
1823
For Link with selection flags, use --spec '{}' and --tokens-file <path|->.
1924
Alternatively, set the customer_managed client and provider_config in --spec,
@@ -31,6 +36,8 @@ an uncertain payment through the new wallet. There is no in-place reauthorizatio
3136
3237
type ProviderConfigReference = { id: string } | { name: string };
3338
39+
type KernelWalletSpec = { provider: "kernel" };
40+
3441
type LinkWalletSpec = {
3542
provider: "link";
3643
authorization: {
@@ -48,6 +55,16 @@ type AgentCardWalletSpec = {
4855
`
4956

5057
const vaultCardSpecHelp = `
58+
type KernelCardSpec = {
59+
provider: "kernel";
60+
wallet: string; // enrolled Kernel wallet item key
61+
amount: number; // integer minor units; 1..50000
62+
currency: string; // ISO 4217 three-letter code
63+
merchant_name: string; // 1..255 characters
64+
merchant_url: string; // HTTPS checkout URL; fill locked to its exact origin
65+
merchant_country?: string; // ISO 3166-1 alpha-2; required for Visa
66+
};
67+
5168
type LinkCardSpec = {
5269
provider: "link";
5370
wallet: string; // wallet item key
@@ -91,6 +108,13 @@ type LinkTotal = {
91108
amount: number; // integer minor units
92109
};
93110
111+
Kernel cards cannot be updated. Create a new card item for a new purchase, but
112+
never retry an uncertain authorization or replace an item in recovery_required.
113+
Invoke authorize only when advertised. For Visa, present the returned
114+
spend_approval URL to the cardholder and poll items get --wait 60 until ready.
115+
Mastercard may become ready without hosted approval. Neither ready nor fill
116+
confirms merchant payment. Fill and submit before expires_at; never pass PAN to CLI.
117+
94118
Card updates replace the whole spec, so omitting checkout_origin from an update removes
95119
its existing value. For non-prepared authorization, checkout_origin is forwarded to
96120
AgentCard for eligible autopilot rule matching. Use a canonical HTTPS origin (lowercase

0 commit comments

Comments
 (0)