From 899caeef617027445e1edc88b8350ba77b19c5ec Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 30 Jan 2026 05:09:37 +0000 Subject: [PATCH 1/6] CLI: Update SDK to v0.29.0 and add 1280x800@60 viewport support - Updated kernel-go-sdk from v0.28.0 to v0.29.0 - Added 1280x800@60 to available viewport configurations to match SDK update - Updated viewport test to reflect the new viewport option SDK release notes: - Add support for 1280x800@60 viewport - Add convenient param.SetJSON helper --- cmd/browsers.go | 1 + cmd/browsers_test.go | 3 ++- go.mod | 2 +- go.sum | 4 ++-- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cmd/browsers.go b/cmd/browsers.go index 7555d91..782579f 100644 --- a/cmd/browsers.go +++ b/cmd/browsers.go @@ -124,6 +124,7 @@ func getAvailableViewports() []string { "1920x1080@25", "1920x1200@25", "1440x900@25", + "1280x800@60", "1024x768@60", "1200x800@60", } diff --git a/cmd/browsers_test.go b/cmd/browsers_test.go index 447b6bd..e5c3a3e 100644 --- a/cmd/browsers_test.go +++ b/cmd/browsers_test.go @@ -1147,11 +1147,12 @@ func TestParseViewport_InvalidFormats(t *testing.T) { func TestGetAvailableViewports_ReturnsExpectedOptions(t *testing.T) { viewports := getAvailableViewports() - assert.Len(t, viewports, 6) + assert.Len(t, viewports, 7) assert.Contains(t, viewports, "2560x1440@10") assert.Contains(t, viewports, "1920x1080@25") assert.Contains(t, viewports, "1920x1200@25") assert.Contains(t, viewports, "1440x900@25") + assert.Contains(t, viewports, "1280x800@60") assert.Contains(t, viewports, "1200x800@60") assert.Contains(t, viewports, "1024x768@60") } diff --git a/go.mod b/go.mod index 09d225a..8e0b3ae 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.1 github.com/golang-jwt/jwt/v5 v5.2.2 github.com/joho/godotenv v1.5.1 - github.com/kernel/kernel-go-sdk v0.28.0 + github.com/kernel/kernel-go-sdk v0.29.0 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/pquerna/otp v1.5.0 github.com/pterm/pterm v0.12.80 diff --git a/go.sum b/go.sum index f3de7bd..dea7a9e 100644 --- a/go.sum +++ b/go.sum @@ -66,8 +66,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/kernel/kernel-go-sdk v0.28.0 h1:cvaCWP25UIB5w6oOdQ5J+rVboNGq3VaWYhtmshlPrhg= -github.com/kernel/kernel-go-sdk v0.28.0/go.mod h1:EeZzSuHZVeHKxKCPUzxou2bovNGhXaz0RXrSqKNf1AQ= +github.com/kernel/kernel-go-sdk v0.29.0 h1:YExAB/fvwTV05pwYCf+BhvSWXRYgETAJH4pH7T8IdzE= +github.com/kernel/kernel-go-sdk v0.29.0/go.mod h1:EeZzSuHZVeHKxKCPUzxou2bovNGhXaz0RXrSqKNf1AQ= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= From 440c557d3b62050cd946d7a65cef7d7d45b9e67e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 3 Feb 2026 17:08:52 +0000 Subject: [PATCH 2/6] CLI: Update SDK to v0.30.0 and add new flags - Update kernel-go-sdk from v0.29.0 to v0.30.0 - Add --status flag for browser list (active, deleted, all) - Add --async-timeout flag for invoke command SDK bump triggered by: kernel/kernel-go-sdk@6ca29d21e5610db982caf74297cf481996793170 --- cmd/browsers.go | 25 +++++++++++++++++++++---- cmd/invoke.go | 5 +++++ go.mod | 2 +- go.sum | 4 ++-- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/cmd/browsers.go b/cmd/browsers.go index 3e5f350..c140cf4 100644 --- a/cmd/browsers.go +++ b/cmd/browsers.go @@ -223,6 +223,7 @@ type BrowsersCmd struct { type BrowsersListInput struct { Output string IncludeDeleted bool + Status string Limit int Offset int } @@ -233,7 +234,19 @@ func (b BrowsersCmd) List(ctx context.Context, in BrowsersListInput) error { } params := kernel.BrowserListParams{} - if in.IncludeDeleted { + // Use new Status parameter if provided, otherwise fall back to deprecated IncludeDeleted + if in.Status != "" { + switch in.Status { + case "active": + params.Status = kernel.BrowserListParamsStatusActive + case "deleted": + params.Status = kernel.BrowserListParamsStatusDeleted + case "all": + params.Status = kernel.BrowserListParamsStatusAll + default: + return fmt.Errorf("invalid --status value: %s (must be 'active', 'deleted', or 'all')", in.Status) + } + } else if in.IncludeDeleted { params.IncludeDeleted = kernel.Opt(true) } if in.Limit > 0 { @@ -264,7 +277,8 @@ func (b BrowsersCmd) List(ctx context.Context, in BrowsersListInput) error { // Prepare table data headers := []string{"Browser ID", "Created At", "Persistent ID", "Profile", "CDP WS URL", "Live View URL"} - if in.IncludeDeleted { + showDeletedAt := in.IncludeDeleted || in.Status == "deleted" || in.Status == "all" + if showDeletedAt { headers = append(headers, "Deleted At") } tableData := pterm.TableData{headers} @@ -291,7 +305,7 @@ func (b BrowsersCmd) List(ctx context.Context, in BrowsersListInput) error { truncateURL(browser.BrowserLiveViewURL, 50), } - if in.IncludeDeleted { + if showDeletedAt { deletedAt := "-" if !browser.DeletedAt.IsZero() { deletedAt = util.FormatLocal(browser.DeletedAt) @@ -2054,7 +2068,8 @@ Note: Profiles can only be loaded into sessions that don't already have a profil func init() { // list flags browsersListCmd.Flags().StringP("output", "o", "", "Output format: json for raw API response") - browsersListCmd.Flags().Bool("include-deleted", false, "Include soft-deleted browser sessions in the results") + browsersListCmd.Flags().Bool("include-deleted", false, "DEPRECATED: Use --status instead. Include soft-deleted browser sessions in the results") + browsersListCmd.Flags().String("status", "", "Filter by status: 'active' (default), 'deleted', or 'all'") browsersListCmd.Flags().Int("limit", 0, "Maximum number of results to return (default 20, max 100)") browsersListCmd.Flags().Int("offset", 0, "Number of results to skip (for pagination)") @@ -2323,11 +2338,13 @@ func runBrowsersList(cmd *cobra.Command, args []string) error { b := BrowsersCmd{browsers: &svc} out, _ := cmd.Flags().GetString("output") includeDeleted, _ := cmd.Flags().GetBool("include-deleted") + status, _ := cmd.Flags().GetString("status") limit, _ := cmd.Flags().GetInt("limit") offset, _ := cmd.Flags().GetInt("offset") return b.List(cmd.Context(), BrowsersListInput{ Output: out, IncludeDeleted: includeDeleted, + Status: status, Limit: limit, Offset: offset, }) diff --git a/cmd/invoke.go b/cmd/invoke.go index 4b80b67..d8e22e2 100644 --- a/cmd/invoke.go +++ b/cmd/invoke.go @@ -40,6 +40,7 @@ func init() { invokeCmd.Flags().StringP("payload", "p", "", "JSON payload for the invocation (optional)") invokeCmd.Flags().StringP("payload-file", "f", "", "Path to a JSON file containing the payload (use '-' for stdin)") invokeCmd.Flags().BoolP("sync", "s", false, "Invoke synchronously (default false). A synchronous invocation will open a long-lived HTTP POST to the Kernel API to wait for the invocation to complete. This will time out after 60 seconds, so only use this option if you expect your invocation to complete in less than 60 seconds. The default is to invoke asynchronously, in which case the CLI will open an SSE connection to the Kernel API after submitting the invocation and wait for the invocation to complete.") + invokeCmd.Flags().Int64("async-timeout", 0, "Timeout in seconds for async invocations (min 10, max 3600). Only applies when async mode is used.") invokeCmd.Flags().StringP("output", "o", "", "Output format: json for JSONL streaming output") invokeCmd.MarkFlagsMutuallyExclusive("payload", "payload-file") @@ -70,12 +71,16 @@ func runInvoke(cmd *cobra.Command, args []string) error { return fmt.Errorf("version cannot be an empty string") } isSync, _ := cmd.Flags().GetBool("sync") + asyncTimeout, _ := cmd.Flags().GetInt64("async-timeout") params := kernel.InvocationNewParams{ AppName: appName, ActionName: actionName, Version: version, Async: kernel.Opt(!isSync), } + if asyncTimeout > 0 { + params.AsyncTimeoutSeconds = kernel.Opt(asyncTimeout) + } payloadStr, hasPayload, err := getPayload(cmd) if err != nil { diff --git a/go.mod b/go.mod index 140eab8..15d5dbd 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.1 github.com/golang-jwt/jwt/v5 v5.2.2 github.com/joho/godotenv v1.5.1 - github.com/kernel/kernel-go-sdk v0.29.0 + github.com/kernel/kernel-go-sdk v0.30.0 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/pquerna/otp v1.5.0 github.com/pterm/pterm v0.12.80 diff --git a/go.sum b/go.sum index 65a17b9..1082c5b 100644 --- a/go.sum +++ b/go.sum @@ -66,8 +66,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/kernel/kernel-go-sdk v0.29.0 h1:YExAB/fvwTV05pwYCf+BhvSWXRYgETAJH4pH7T8IdzE= -github.com/kernel/kernel-go-sdk v0.29.0/go.mod h1:EeZzSuHZVeHKxKCPUzxou2bovNGhXaz0RXrSqKNf1AQ= +github.com/kernel/kernel-go-sdk v0.30.0 h1:FN9G84mbqqTETSBRHRTuG4rBoUVu3xRhDIaWG3AyYNI= +github.com/kernel/kernel-go-sdk v0.30.0/go.mod h1:EeZzSuHZVeHKxKCPUzxou2bovNGhXaz0RXrSqKNf1AQ= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= From 91545de614a27efb9ce27228bd82d7bc8ce2d8af Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 3 Feb 2026 18:34:46 +0000 Subject: [PATCH 3/6] fix: remove duplicate 1280x800@60 viewport entry The viewport '1280x800@60' was added twice: - Once in commit 899caee (SDK v0.29.0 update) - Again in commit 4d9565b (feat: add 1280x800 viewport support #97) This resulted in 8 items in getAvailableViewports() while the test expected 7 items, causing test failures. Also removes the duplicate assertion in the test file. Co-authored-by: mason --- cmd/browsers.go | 1 - cmd/browsers_test.go | 1 - 2 files changed, 2 deletions(-) diff --git a/cmd/browsers.go b/cmd/browsers.go index c140cf4..e96eb12 100644 --- a/cmd/browsers.go +++ b/cmd/browsers.go @@ -127,7 +127,6 @@ func getAvailableViewports() []string { "1280x800@60", "1024x768@60", "1200x800@60", - "1280x800@60", } } diff --git a/cmd/browsers_test.go b/cmd/browsers_test.go index f5f13e1..696e29a 100644 --- a/cmd/browsers_test.go +++ b/cmd/browsers_test.go @@ -1153,7 +1153,6 @@ func TestGetAvailableViewports_ReturnsExpectedOptions(t *testing.T) { assert.Contains(t, viewports, "1440x900@25") assert.Contains(t, viewports, "1280x800@60") assert.Contains(t, viewports, "1200x800@60") - assert.Contains(t, viewports, "1280x800@60") assert.Contains(t, viewports, "1024x768@60") } From 26580149a654dd557ee0ca5a917e2862324ba175 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 3 Feb 2026 18:38:39 +0000 Subject: [PATCH 4/6] feat: add 'invoke browsers' command to list browsers for an invocation Adds CLI support for the SDK's InvocationService.ListBrowsers() method which returns all active browser sessions created within a specific invocation. Usage: kernel invoke browsers [--output json] Co-authored-by: mason --- cmd/invoke.go | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/cmd/invoke.go b/cmd/invoke.go index d8e22e2..3b5091c 100644 --- a/cmd/invoke.go +++ b/cmd/invoke.go @@ -35,6 +35,14 @@ var invocationHistoryCmd = &cobra.Command{ RunE: runInvocationHistory, } +var invocationBrowsersCmd = &cobra.Command{ + Use: "browsers ", + Short: "List browser sessions for an invocation", + Long: "List all active browser sessions created within a specific invocation.", + Args: cobra.ExactArgs(1), + RunE: runInvocationBrowsers, +} + func init() { invokeCmd.Flags().StringP("version", "v", "latest", "Specify a version of the app to invoke (optional, defaults to 'latest')") invokeCmd.Flags().StringP("payload", "p", "", "JSON payload for the invocation (optional)") @@ -49,6 +57,9 @@ func init() { invocationHistoryCmd.Flags().String("version", "", "Filter by invocation version") invocationHistoryCmd.Flags().StringP("output", "o", "", "Output format: json for raw API response") invokeCmd.AddCommand(invocationHistoryCmd) + + invocationBrowsersCmd.Flags().StringP("output", "o", "", "Output format: json for raw API response") + invokeCmd.AddCommand(invocationBrowsersCmd) } func runInvoke(cmd *cobra.Command, args []string) error { @@ -433,3 +444,56 @@ func runInvocationHistory(cmd *cobra.Command, args []string) error { } return nil } + +func runInvocationBrowsers(cmd *cobra.Command, args []string) error { + client := getKernelClient(cmd) + invocationID := args[0] + output, _ := cmd.Flags().GetString("output") + + if output != "" && output != "json" { + return fmt.Errorf("unsupported --output value: use 'json'") + } + + resp, err := client.Invocations.ListBrowsers(cmd.Context(), invocationID) + if err != nil { + pterm.Error.Printf("Failed to list browsers for invocation: %v\n", err) + return nil + } + + if output == "json" { + if len(resp.Browsers) == 0 { + fmt.Println("[]") + return nil + } + return util.PrintPrettyJSONSlice(resp.Browsers) + } + + if len(resp.Browsers) == 0 { + pterm.Info.Printf("No active browsers found for invocation %s\n", invocationID) + return nil + } + + table := pterm.TableData{{"Session ID", "Created At", "Headless", "Stealth", "Timeout", "CDP WS URL", "Live View URL"}} + + for _, browser := range resp.Browsers { + created := util.FormatLocal(browser.CreatedAt) + liveView := browser.BrowserLiveViewURL + if liveView == "" { + liveView = "-" + } + + table = append(table, []string{ + browser.SessionID, + created, + fmt.Sprintf("%v", browser.Headless), + fmt.Sprintf("%v", browser.Stealth), + fmt.Sprintf("%d", browser.TimeoutSeconds), + truncateURL(browser.CdpWsURL, 40), + truncateURL(liveView, 40), + }) + } + + pterm.Info.Printf("Browsers for invocation %s:\n", invocationID) + pterm.DefaultTable.WithHasHeader().WithData(table).Render() + return nil +} From 89d90ca49a0c06fa0740223f9f11940ba4d32fe0 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 7 Feb 2026 18:39:26 +0000 Subject: [PATCH 5/6] feat: bump SDK to v0.32.0 and add credential-providers list-items command - Update github.com/kernel/kernel-go-sdk from v0.30.0 to v0.32.0 - Add `kernel credential-providers list-items ` command to list all credential items from an external provider - Fix breaking change: use Credential.Name instead of removed CredentialName field in AuthAgent display output Co-authored-by: Cursor --- cmd/agents.go | 14 +++++-- cmd/credential_providers.go | 75 +++++++++++++++++++++++++++++++++++++ go.mod | 2 +- go.sum | 4 +- 4 files changed, 88 insertions(+), 7 deletions(-) diff --git a/cmd/agents.go b/cmd/agents.go index 718306f..73095b3 100644 --- a/cmd/agents.go +++ b/cmd/agents.go @@ -189,8 +189,11 @@ func (c AgentAuthCmd) Create(ctx context.Context, in AgentAuthCreateInput) error {"Status", string(agent.Status)}, {"Can Reauth", fmt.Sprintf("%t", agent.CanReauth)}, } - if agent.CredentialName != "" { - tableData = append(tableData, []string{"Credential Name", agent.CredentialName}) + if agent.Credential.Name != "" { + tableData = append(tableData, []string{"Credential Name", agent.Credential.Name}) + } + if agent.Credential.Provider != "" { + tableData = append(tableData, []string{"Credential Provider", agent.Credential.Provider}) } PrintTableNoPad(tableData, true) @@ -223,8 +226,11 @@ func (c AgentAuthCmd) Get(ctx context.Context, in AgentAuthGetInput) error { if agent.CredentialID != "" { tableData = append(tableData, []string{"Credential ID", agent.CredentialID}) } - if agent.CredentialName != "" { - tableData = append(tableData, []string{"Credential Name", agent.CredentialName}) + if agent.Credential.Name != "" { + tableData = append(tableData, []string{"Credential Name", agent.Credential.Name}) + } + if agent.Credential.Provider != "" { + tableData = append(tableData, []string{"Credential Provider", agent.Credential.Provider}) } if agent.PostLoginURL != "" { tableData = append(tableData, []string{"Post-Login URL", agent.PostLoginURL}) diff --git a/cmd/credential_providers.go b/cmd/credential_providers.go index 1da25f0..0ed7133 100644 --- a/cmd/credential_providers.go +++ b/cmd/credential_providers.go @@ -20,6 +20,7 @@ type CredentialProvidersService interface { List(ctx context.Context, opts ...option.RequestOption) (res *[]kernel.CredentialProvider, err error) Delete(ctx context.Context, id string, opts ...option.RequestOption) (err error) Test(ctx context.Context, id string, opts ...option.RequestOption) (res *kernel.CredentialProviderTestResult, err error) + ListItems(ctx context.Context, id string, opts ...option.RequestOption) (res *kernel.CredentialProviderListItemsResponse, err error) } // CredentialProvidersCmd handles credential provider operations independent of cobra. @@ -62,6 +63,11 @@ type CredentialProvidersTestInput struct { Output string } +type CredentialProvidersListItemsInput struct { + ID string + Output string +} + func (c CredentialProvidersCmd) List(ctx context.Context, in CredentialProvidersListInput) error { if in.Output != "" && in.Output != "json" { return fmt.Errorf("unsupported --output value: use 'json'") @@ -281,6 +287,51 @@ func (c CredentialProvidersCmd) Test(ctx context.Context, in CredentialProviders return nil } +func (c CredentialProvidersCmd) ListItems(ctx context.Context, in CredentialProvidersListItemsInput) error { + if in.Output != "" && in.Output != "json" { + return fmt.Errorf("unsupported --output value: use 'json'") + } + + if in.Output != "json" { + pterm.Info.Printf("Listing items for credential provider '%s'...\n", in.ID) + } + + result, err := c.providers.ListItems(ctx, in.ID) + if err != nil { + return util.CleanedUpSdkError{Err: err} + } + + if in.Output == "json" { + if len(result.Items) == 0 { + fmt.Println("[]") + return nil + } + return util.PrintPrettyJSONSlice(result.Items) + } + + if len(result.Items) == 0 { + pterm.Info.Println("No items found") + return nil + } + + tableData := pterm.TableData{{"Path", "Title", "Vault", "URLs"}} + for _, item := range result.Items { + urls := "" + if len(item.URLs) > 0 { + urls = strings.Join(item.URLs, ", ") + } + tableData = append(tableData, []string{ + item.Path, + item.Title, + item.VaultName, + urls, + }) + } + + PrintTableNoPad(tableData, true) + return nil +} + // --- Cobra wiring --- var credentialProvidersCmd = &cobra.Command{ @@ -345,6 +396,14 @@ var credentialProvidersTestCmd = &cobra.Command{ RunE: runCredentialProvidersTest, } +var credentialProvidersListItemsCmd = &cobra.Command{ + Use: "list-items ", + Short: "List items from a credential provider", + Long: `List all credential items available from the specified external credential provider.`, + Args: cobra.ExactArgs(1), + RunE: runCredentialProvidersListItems, +} + func init() { credentialProvidersCmd.AddCommand(credentialProvidersListCmd) credentialProvidersCmd.AddCommand(credentialProvidersGetCmd) @@ -352,6 +411,7 @@ func init() { credentialProvidersCmd.AddCommand(credentialProvidersUpdateCmd) credentialProvidersCmd.AddCommand(credentialProvidersDeleteCmd) credentialProvidersCmd.AddCommand(credentialProvidersTestCmd) + credentialProvidersCmd.AddCommand(credentialProvidersListItemsCmd) // List flags credentialProvidersListCmd.Flags().StringP("output", "o", "", "Output format: json for raw API response") @@ -379,6 +439,9 @@ func init() { // Test flags credentialProvidersTestCmd.Flags().StringP("output", "o", "", "Output format: json for raw API response") + + // ListItems flags + credentialProvidersListItemsCmd.Flags().StringP("output", "o", "", "Output format: json for raw API response") } func runCredentialProvidersList(cmd *cobra.Command, args []string) error { @@ -464,3 +527,15 @@ func runCredentialProvidersTest(cmd *cobra.Command, args []string) error { Output: output, }) } + +func runCredentialProvidersListItems(cmd *cobra.Command, args []string) error { + client := getKernelClient(cmd) + output, _ := cmd.Flags().GetString("output") + + svc := client.CredentialProviders + c := CredentialProvidersCmd{providers: &svc} + return c.ListItems(cmd.Context(), CredentialProvidersListItemsInput{ + ID: args[0], + Output: output, + }) +} diff --git a/go.mod b/go.mod index 15d5dbd..d65b9f4 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.1 github.com/golang-jwt/jwt/v5 v5.2.2 github.com/joho/godotenv v1.5.1 - github.com/kernel/kernel-go-sdk v0.30.0 + github.com/kernel/kernel-go-sdk v0.32.0 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/pquerna/otp v1.5.0 github.com/pterm/pterm v0.12.80 diff --git a/go.sum b/go.sum index 1082c5b..9653f0d 100644 --- a/go.sum +++ b/go.sum @@ -66,8 +66,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/kernel/kernel-go-sdk v0.30.0 h1:FN9G84mbqqTETSBRHRTuG4rBoUVu3xRhDIaWG3AyYNI= -github.com/kernel/kernel-go-sdk v0.30.0/go.mod h1:EeZzSuHZVeHKxKCPUzxou2bovNGhXaz0RXrSqKNf1AQ= +github.com/kernel/kernel-go-sdk v0.32.0 h1:xdypUWiHvZlivIZ4eoBUE2jxZr2h9ZGl9IdWLW6P3fc= +github.com/kernel/kernel-go-sdk v0.32.0/go.mod h1:EeZzSuHZVeHKxKCPUzxou2bovNGhXaz0RXrSqKNf1AQ= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= From d1c35a49467856ce121ba4f39abfdf01a05f537c Mon Sep 17 00:00:00 2001 From: "kernel-internal[bot]" <260533166+kernel-internal[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 17:47:20 +0000 Subject: [PATCH 6/6] feat: add auth connections commands for managed auth API Add CLI commands for the new Auth.Connections SDK service: - kernel auth connections create: Create managed auth for profile/domain - kernel auth connections get: Get managed auth by ID - kernel auth connections list: List managed auths - kernel auth connections delete: Delete managed auth - kernel auth connections login: Start login flow - kernel auth connections submit: Submit field values to login flow - kernel auth connections follow: Follow login flow events via SSE Also bump SDK to latest version (c90e1da19efb). Co-authored-by: Cursor --- cmd/auth_connections.go | 711 ++++++++++++++++++++++++++++++++++++++++ go.mod | 2 +- go.sum | 4 +- 3 files changed, 714 insertions(+), 3 deletions(-) create mode 100644 cmd/auth_connections.go diff --git a/cmd/auth_connections.go b/cmd/auth_connections.go new file mode 100644 index 0000000..affa011 --- /dev/null +++ b/cmd/auth_connections.go @@ -0,0 +1,711 @@ +package cmd + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/kernel/cli/pkg/util" + "github.com/kernel/kernel-go-sdk" + "github.com/kernel/kernel-go-sdk/option" + "github.com/kernel/kernel-go-sdk/packages/pagination" + "github.com/kernel/kernel-go-sdk/packages/ssestream" + "github.com/pterm/pterm" + "github.com/spf13/cobra" +) + +// AuthConnectionService defines the subset of the Kernel SDK auth connection client that we use. +type AuthConnectionService interface { + New(ctx context.Context, body kernel.AuthConnectionNewParams, opts ...option.RequestOption) (res *kernel.ManagedAuth, err error) + Get(ctx context.Context, id string, opts ...option.RequestOption) (res *kernel.ManagedAuth, err error) + List(ctx context.Context, query kernel.AuthConnectionListParams, opts ...option.RequestOption) (res *pagination.OffsetPagination[kernel.ManagedAuth], err error) + Delete(ctx context.Context, id string, opts ...option.RequestOption) (err error) + Login(ctx context.Context, id string, body kernel.AuthConnectionLoginParams, opts ...option.RequestOption) (res *kernel.LoginResponse, err error) + Submit(ctx context.Context, id string, body kernel.AuthConnectionSubmitParams, opts ...option.RequestOption) (res *kernel.SubmitFieldsResponse, err error) + FollowStreaming(ctx context.Context, id string, opts ...option.RequestOption) (stream *ssestream.Stream[kernel.AuthConnectionFollowResponseUnion]) +} + +// AuthConnectionCmd handles auth connection operations independent of cobra. +type AuthConnectionCmd struct { + svc AuthConnectionService +} + +type AuthConnectionCreateInput struct { + Domain string + ProfileName string + LoginURL string + AllowedDomains []string + CredentialName string + CredentialProvider string + CredentialPath string + CredentialAuto bool + ProxyID string + HealthCheckInterval int + Output string +} + +type AuthConnectionGetInput struct { + ID string + Output string +} + +type AuthConnectionListInput struct { + Domain string + ProfileName string + Limit int + Offset int + Output string +} + +type AuthConnectionDeleteInput struct { + ID string + SkipConfirm bool +} + +type AuthConnectionLoginInput struct { + ID string + SaveCredentialAs string + Output string +} + +type AuthConnectionSubmitInput struct { + ID string + FieldValues map[string]string + MfaOptionID string + SSOButtonSelector string + Output string +} + +type AuthConnectionFollowInput struct { + ID string + Output string +} + +func (c AuthConnectionCmd) Create(ctx context.Context, in AuthConnectionCreateInput) error { + if in.Output != "" && in.Output != "json" { + return fmt.Errorf("unsupported --output value: use 'json'") + } + + if in.Domain == "" { + return fmt.Errorf("--domain is required") + } + if in.ProfileName == "" { + return fmt.Errorf("--profile-name is required") + } + + params := kernel.AuthConnectionNewParams{ + ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{ + Domain: in.Domain, + ProfileName: in.ProfileName, + }, + } + if in.LoginURL != "" { + params.ManagedAuthCreateRequest.LoginURL = kernel.Opt(in.LoginURL) + } + if len(in.AllowedDomains) > 0 { + params.ManagedAuthCreateRequest.AllowedDomains = in.AllowedDomains + } + if in.HealthCheckInterval > 0 { + params.ManagedAuthCreateRequest.HealthCheckInterval = kernel.Opt(int64(in.HealthCheckInterval)) + } + + // Handle credential reference + if in.CredentialName != "" { + params.ManagedAuthCreateRequest.Credential = kernel.ManagedAuthCreateRequestCredentialParam{ + Name: kernel.Opt(in.CredentialName), + } + } else if in.CredentialProvider != "" { + params.ManagedAuthCreateRequest.Credential = kernel.ManagedAuthCreateRequestCredentialParam{ + Provider: kernel.Opt(in.CredentialProvider), + } + if in.CredentialPath != "" { + params.ManagedAuthCreateRequest.Credential.Path = kernel.Opt(in.CredentialPath) + } + if in.CredentialAuto { + params.ManagedAuthCreateRequest.Credential.Auto = kernel.Opt(true) + } + } + + if in.ProxyID != "" { + params.ManagedAuthCreateRequest.Proxy = kernel.ManagedAuthCreateRequestProxyParam{ + ProxyID: kernel.Opt(in.ProxyID), + } + } + + if in.Output != "json" { + pterm.Info.Printf("Creating managed auth for %s...\n", in.Domain) + } + + auth, err := c.svc.New(ctx, params) + if err != nil { + return util.CleanedUpSdkError{Err: err} + } + + if in.Output == "json" { + return util.PrintPrettyJSON(auth) + } + + pterm.Success.Printf("Created managed auth: %s\n", auth.ID) + + tableData := pterm.TableData{ + {"Property", "Value"}, + {"ID", auth.ID}, + {"Domain", auth.Domain}, + {"Profile Name", auth.ProfileName}, + {"Status", string(auth.Status)}, + {"Can Reauth", fmt.Sprintf("%t", auth.CanReauth)}, + } + if auth.CanReauthReason != "" { + tableData = append(tableData, []string{"Can Reauth Reason", auth.CanReauthReason}) + } + if auth.Credential.Name != "" { + tableData = append(tableData, []string{"Credential Name", auth.Credential.Name}) + } + if auth.Credential.Provider != "" { + tableData = append(tableData, []string{"Credential Provider", auth.Credential.Provider}) + } + + PrintTableNoPad(tableData, true) + return nil +} + +func (c AuthConnectionCmd) Get(ctx context.Context, in AuthConnectionGetInput) error { + if in.Output != "" && in.Output != "json" { + return fmt.Errorf("unsupported --output value: use 'json'") + } + + auth, err := c.svc.Get(ctx, in.ID) + if err != nil { + return util.CleanedUpSdkError{Err: err} + } + + if in.Output == "json" { + return util.PrintPrettyJSON(auth) + } + + tableData := pterm.TableData{ + {"Property", "Value"}, + {"ID", auth.ID}, + {"Domain", auth.Domain}, + {"Profile Name", auth.ProfileName}, + {"Status", string(auth.Status)}, + {"Can Reauth", fmt.Sprintf("%t", auth.CanReauth)}, + } + if auth.CanReauthReason != "" { + tableData = append(tableData, []string{"Can Reauth Reason", auth.CanReauthReason}) + } + if auth.Credential.Name != "" { + tableData = append(tableData, []string{"Credential Name", auth.Credential.Name}) + } + if auth.Credential.Provider != "" { + tableData = append(tableData, []string{"Credential Provider", auth.Credential.Provider}) + } + if auth.FlowStatus != "" { + tableData = append(tableData, []string{"Flow Status", string(auth.FlowStatus)}) + } + if auth.FlowStep != "" { + tableData = append(tableData, []string{"Flow Step", string(auth.FlowStep)}) + } + if auth.HostedURL != "" { + tableData = append(tableData, []string{"Hosted URL", auth.HostedURL}) + } + if auth.LiveViewURL != "" { + tableData = append(tableData, []string{"Live View URL", auth.LiveViewURL}) + } + if auth.ErrorMessage != "" { + tableData = append(tableData, []string{"Error Message", auth.ErrorMessage}) + } + if !auth.LastAuthAt.IsZero() { + tableData = append(tableData, []string{"Last Auth At", util.FormatLocal(auth.LastAuthAt)}) + } + if len(auth.AllowedDomains) > 0 { + tableData = append(tableData, []string{"Allowed Domains", strings.Join(auth.AllowedDomains, ", ")}) + } + if auth.HealthCheckInterval > 0 { + tableData = append(tableData, []string{"Health Check Interval", fmt.Sprintf("%d seconds", auth.HealthCheckInterval)}) + } + + PrintTableNoPad(tableData, true) + return nil +} + +func (c AuthConnectionCmd) List(ctx context.Context, in AuthConnectionListInput) error { + if in.Output != "" && in.Output != "json" { + return fmt.Errorf("unsupported --output value: use 'json'") + } + + params := kernel.AuthConnectionListParams{} + if in.Domain != "" { + params.Domain = kernel.Opt(in.Domain) + } + if in.ProfileName != "" { + params.ProfileName = kernel.Opt(in.ProfileName) + } + if in.Limit > 0 { + params.Limit = kernel.Opt(int64(in.Limit)) + } + if in.Offset > 0 { + params.Offset = kernel.Opt(int64(in.Offset)) + } + + page, err := c.svc.List(ctx, params) + if err != nil { + return util.CleanedUpSdkError{Err: err} + } + + var auths []kernel.ManagedAuth + if page != nil { + auths = page.Items + } + + if in.Output == "json" { + if len(auths) == 0 { + fmt.Println("[]") + return nil + } + return util.PrintPrettyJSONSlice(auths) + } + + if len(auths) == 0 { + pterm.Info.Println("No managed auths found") + return nil + } + + tableData := pterm.TableData{{"ID", "Domain", "Profile Name", "Status", "Can Reauth"}} + for _, auth := range auths { + tableData = append(tableData, []string{ + auth.ID, + auth.Domain, + auth.ProfileName, + string(auth.Status), + fmt.Sprintf("%t", auth.CanReauth), + }) + } + + PrintTableNoPad(tableData, true) + return nil +} + +func (c AuthConnectionCmd) Delete(ctx context.Context, in AuthConnectionDeleteInput) error { + if !in.SkipConfirm { + msg := fmt.Sprintf("Are you sure you want to delete managed auth '%s'?", in.ID) + pterm.DefaultInteractiveConfirm.DefaultText = msg + ok, _ := pterm.DefaultInteractiveConfirm.Show() + if !ok { + pterm.Info.Println("Deletion cancelled") + return nil + } + } + + if err := c.svc.Delete(ctx, in.ID); err != nil { + if util.IsNotFound(err) { + pterm.Info.Printf("Managed auth '%s' not found\n", in.ID) + return nil + } + return util.CleanedUpSdkError{Err: err} + } + pterm.Success.Printf("Deleted managed auth: %s\n", in.ID) + return nil +} + +func (c AuthConnectionCmd) Login(ctx context.Context, in AuthConnectionLoginInput) error { + if in.Output != "" && in.Output != "json" { + return fmt.Errorf("unsupported --output value: use 'json'") + } + + params := kernel.AuthConnectionLoginParams{} + if in.SaveCredentialAs != "" { + params.LoginRequest.SaveCredentialAs = kernel.Opt(in.SaveCredentialAs) + } + + if in.Output != "json" { + pterm.Info.Println("Starting login flow...") + } + + resp, err := c.svc.Login(ctx, in.ID, params) + if err != nil { + return util.CleanedUpSdkError{Err: err} + } + + if in.Output == "json" { + return util.PrintPrettyJSON(resp) + } + + pterm.Success.Printf("Login flow started: %s\n", resp.FlowType) + + tableData := pterm.TableData{ + {"Property", "Value"}, + {"ID", resp.ID}, + {"Flow Type", string(resp.FlowType)}, + {"Hosted URL", resp.HostedURL}, + {"Flow Expires At", util.FormatLocal(resp.FlowExpiresAt)}, + } + if resp.LiveViewURL != "" { + tableData = append(tableData, []string{"Live View URL", resp.LiveViewURL}) + } + + PrintTableNoPad(tableData, true) + return nil +} + +func (c AuthConnectionCmd) Submit(ctx context.Context, in AuthConnectionSubmitInput) error { + if in.Output != "" && in.Output != "json" { + return fmt.Errorf("unsupported --output value: use 'json'") + } + + // Validate that we have some input to submit + hasFields := len(in.FieldValues) > 0 + hasMfaOption := in.MfaOptionID != "" + hasSSOButton := in.SSOButtonSelector != "" + + if !hasFields && !hasMfaOption && !hasSSOButton { + return fmt.Errorf("must provide at least one of: --field, --mfa-option-id, or --sso-button-selector") + } + + params := kernel.AuthConnectionSubmitParams{ + SubmitFieldsRequest: kernel.SubmitFieldsRequestParam{ + Fields: in.FieldValues, + }, + } + if hasMfaOption { + params.SubmitFieldsRequest.MfaOptionID = kernel.Opt(in.MfaOptionID) + } + if hasSSOButton { + params.SubmitFieldsRequest.SSOButtonSelector = kernel.Opt(in.SSOButtonSelector) + } + + if in.Output != "json" { + pterm.Info.Println("Submitting to managed auth...") + } + + resp, err := c.svc.Submit(ctx, in.ID, params) + if err != nil { + return util.CleanedUpSdkError{Err: err} + } + + if in.Output == "json" { + return util.PrintPrettyJSON(resp) + } + + if resp.Accepted { + pterm.Success.Println("Submission accepted") + } else { + pterm.Warning.Println("Submission not accepted") + } + return nil +} + +func (c AuthConnectionCmd) Follow(ctx context.Context, in AuthConnectionFollowInput) error { + if in.Output != "" && in.Output != "json" { + return fmt.Errorf("unsupported --output value: use 'json'") + } + + stream := c.svc.FollowStreaming(ctx, in.ID) + defer stream.Close() + + if in.Output != "json" { + pterm.Info.Println("Following managed auth events (Ctrl+C to stop)...") + } + + for stream.Next() { + event := stream.Current() + + if in.Output == "json" { + if err := util.PrintPrettyJSON(event); err != nil { + return err + } + continue + } + + // Human-readable output + switch event.Event { + case "managed_auth_state": + state := event.AsManagedAuthState() + pterm.Info.Printf("[%s] Status: %s, Step: %s\n", + state.Timestamp.Local().Format(time.RFC3339), + state.FlowStatus, + state.FlowStep) + if len(state.DiscoveredFields) > 0 { + var fieldNames []string + for _, f := range state.DiscoveredFields { + fieldNames = append(fieldNames, f.Name) + } + pterm.Info.Printf(" Discovered fields: %s\n", strings.Join(fieldNames, ", ")) + } + if state.ErrorMessage != "" { + pterm.Error.Printf(" Error: %s\n", state.ErrorMessage) + } + if state.WebsiteError != "" { + pterm.Warning.Printf(" Website error: %s\n", state.WebsiteError) + } + case "error": + errEvent := event.AsError() + pterm.Error.Printf("Error: %s\n", errEvent.Error.Message) + case "sse_heartbeat": + // Silently ignore heartbeats for human-readable output + } + } + + if err := stream.Err(); err != nil { + return util.CleanedUpSdkError{Err: err} + } + + if in.Output != "json" { + pterm.Success.Println("Stream ended") + } + return nil +} + +// --- Cobra wiring --- + +var authConnectionsCmd = &cobra.Command{ + Use: "connections", + Short: "Manage auth connections (managed auth)", + Long: "Commands for managing authentication connections that keep profiles logged into domains", +} + +var authConnectionsCreateCmd = &cobra.Command{ + Use: "create", + Short: "Create a managed auth connection", + Long: "Create managed authentication for a profile and domain combination", + Args: cobra.NoArgs, + RunE: runAuthConnectionsCreate, +} + +var authConnectionsGetCmd = &cobra.Command{ + Use: "get ", + Short: "Get a managed auth by ID", + Args: cobra.ExactArgs(1), + RunE: runAuthConnectionsGet, +} + +var authConnectionsListCmd = &cobra.Command{ + Use: "list", + Short: "List managed auths", + Args: cobra.NoArgs, + RunE: runAuthConnectionsList, +} + +var authConnectionsDeleteCmd = &cobra.Command{ + Use: "delete ", + Short: "Delete a managed auth", + Args: cobra.ExactArgs(1), + RunE: runAuthConnectionsDelete, +} + +var authConnectionsLoginCmd = &cobra.Command{ + Use: "login ", + Short: "Start a login flow", + Long: "Start a login flow for the managed auth, returns a hosted URL for authentication", + Args: cobra.ExactArgs(1), + RunE: runAuthConnectionsLogin, +} + +var authConnectionsSubmitCmd = &cobra.Command{ + Use: "submit ", + Short: "Submit field values to a login flow", + Long: `Submit field values for the login form. Poll the managed auth to track progress. + +Examples: + # Submit field values + kernel auth connections submit --field username=myuser --field password=mypass + + # Select an MFA option + kernel auth connections submit --mfa-option-id + + # Click an SSO button + kernel auth connections submit --sso-button-selector "//button[@id='google-sso']"`, + Args: cobra.ExactArgs(1), + RunE: runAuthConnectionsSubmit, +} + +var authConnectionsFollowCmd = &cobra.Command{ + Use: "follow ", + Short: "Follow login flow events", + Long: "Establish an SSE stream to receive real-time login flow state updates", + Args: cobra.ExactArgs(1), + RunE: runAuthConnectionsFollow, +} + +func init() { + // Create flags + authConnectionsCreateCmd.Flags().StringP("output", "o", "", "Output format: json for raw API response") + authConnectionsCreateCmd.Flags().String("domain", "", "Target domain for authentication (required)") + authConnectionsCreateCmd.Flags().String("profile-name", "", "Name of the profile to manage (required)") + authConnectionsCreateCmd.Flags().String("login-url", "", "Optional login page URL to skip discovery") + authConnectionsCreateCmd.Flags().StringSlice("allowed-domain", []string{}, "Additional allowed domains (repeatable)") + authConnectionsCreateCmd.Flags().String("credential-name", "", "Kernel credential name to use") + authConnectionsCreateCmd.Flags().String("credential-provider", "", "External credential provider name") + authConnectionsCreateCmd.Flags().String("credential-path", "", "Provider-specific path (e.g., VaultName/ItemName)") + authConnectionsCreateCmd.Flags().Bool("credential-auto", false, "Lookup by domain from the specified provider") + authConnectionsCreateCmd.Flags().String("proxy-id", "", "Optional proxy ID to use") + authConnectionsCreateCmd.Flags().Int("health-check-interval", 0, "Interval in seconds between health checks (300-86400)") + _ = authConnectionsCreateCmd.MarkFlagRequired("domain") + _ = authConnectionsCreateCmd.MarkFlagRequired("profile-name") + + // Get flags + authConnectionsGetCmd.Flags().StringP("output", "o", "", "Output format: json for raw API response") + + // List flags + authConnectionsListCmd.Flags().StringP("output", "o", "", "Output format: json for raw API response") + authConnectionsListCmd.Flags().String("domain", "", "Filter by domain") + authConnectionsListCmd.Flags().String("profile-name", "", "Filter by profile name") + authConnectionsListCmd.Flags().Int("limit", 0, "Maximum number of results to return") + authConnectionsListCmd.Flags().Int("offset", 0, "Number of results to skip") + + // Delete flags + authConnectionsDeleteCmd.Flags().BoolP("yes", "y", false, "Skip confirmation prompt") + + // Login flags + authConnectionsLoginCmd.Flags().StringP("output", "o", "", "Output format: json for raw API response") + authConnectionsLoginCmd.Flags().String("save-credential-as", "", "Save credentials under this name on success") + + // Submit flags + authConnectionsSubmitCmd.Flags().StringP("output", "o", "", "Output format: json for raw API response") + authConnectionsSubmitCmd.Flags().StringArray("field", []string{}, "Field name=value pair (repeatable)") + authConnectionsSubmitCmd.Flags().String("mfa-option-id", "", "MFA option ID if user selected an MFA method") + authConnectionsSubmitCmd.Flags().String("sso-button-selector", "", "XPath selector if user chose an SSO button") + + // Follow flags + authConnectionsFollowCmd.Flags().StringP("output", "o", "", "Output format: json for raw API response") + + // Wire up commands + authConnectionsCmd.AddCommand(authConnectionsCreateCmd) + authConnectionsCmd.AddCommand(authConnectionsGetCmd) + authConnectionsCmd.AddCommand(authConnectionsListCmd) + authConnectionsCmd.AddCommand(authConnectionsDeleteCmd) + authConnectionsCmd.AddCommand(authConnectionsLoginCmd) + authConnectionsCmd.AddCommand(authConnectionsSubmitCmd) + authConnectionsCmd.AddCommand(authConnectionsFollowCmd) + + authCmd.AddCommand(authConnectionsCmd) +} + +func runAuthConnectionsCreate(cmd *cobra.Command, args []string) error { + client := getKernelClient(cmd) + output, _ := cmd.Flags().GetString("output") + domain, _ := cmd.Flags().GetString("domain") + profileName, _ := cmd.Flags().GetString("profile-name") + loginURL, _ := cmd.Flags().GetString("login-url") + allowedDomains, _ := cmd.Flags().GetStringSlice("allowed-domain") + credentialName, _ := cmd.Flags().GetString("credential-name") + credentialProvider, _ := cmd.Flags().GetString("credential-provider") + credentialPath, _ := cmd.Flags().GetString("credential-path") + credentialAuto, _ := cmd.Flags().GetBool("credential-auto") + proxyID, _ := cmd.Flags().GetString("proxy-id") + healthCheckInterval, _ := cmd.Flags().GetInt("health-check-interval") + + svc := client.Auth.Connections + c := AuthConnectionCmd{svc: &svc} + return c.Create(cmd.Context(), AuthConnectionCreateInput{ + Domain: domain, + ProfileName: profileName, + LoginURL: loginURL, + AllowedDomains: allowedDomains, + CredentialName: credentialName, + CredentialProvider: credentialProvider, + CredentialPath: credentialPath, + CredentialAuto: credentialAuto, + ProxyID: proxyID, + HealthCheckInterval: healthCheckInterval, + Output: output, + }) +} + +func runAuthConnectionsGet(cmd *cobra.Command, args []string) error { + client := getKernelClient(cmd) + output, _ := cmd.Flags().GetString("output") + + svc := client.Auth.Connections + c := AuthConnectionCmd{svc: &svc} + return c.Get(cmd.Context(), AuthConnectionGetInput{ + ID: args[0], + Output: output, + }) +} + +func runAuthConnectionsList(cmd *cobra.Command, args []string) error { + client := getKernelClient(cmd) + output, _ := cmd.Flags().GetString("output") + domain, _ := cmd.Flags().GetString("domain") + profileName, _ := cmd.Flags().GetString("profile-name") + limit, _ := cmd.Flags().GetInt("limit") + offset, _ := cmd.Flags().GetInt("offset") + + svc := client.Auth.Connections + c := AuthConnectionCmd{svc: &svc} + return c.List(cmd.Context(), AuthConnectionListInput{ + Domain: domain, + ProfileName: profileName, + Limit: limit, + Offset: offset, + Output: output, + }) +} + +func runAuthConnectionsDelete(cmd *cobra.Command, args []string) error { + client := getKernelClient(cmd) + skip, _ := cmd.Flags().GetBool("yes") + + svc := client.Auth.Connections + c := AuthConnectionCmd{svc: &svc} + return c.Delete(cmd.Context(), AuthConnectionDeleteInput{ + ID: args[0], + SkipConfirm: skip, + }) +} + +func runAuthConnectionsLogin(cmd *cobra.Command, args []string) error { + client := getKernelClient(cmd) + output, _ := cmd.Flags().GetString("output") + saveCredentialAs, _ := cmd.Flags().GetString("save-credential-as") + + svc := client.Auth.Connections + c := AuthConnectionCmd{svc: &svc} + return c.Login(cmd.Context(), AuthConnectionLoginInput{ + ID: args[0], + SaveCredentialAs: saveCredentialAs, + Output: output, + }) +} + +func runAuthConnectionsSubmit(cmd *cobra.Command, args []string) error { + client := getKernelClient(cmd) + output, _ := cmd.Flags().GetString("output") + fieldPairs, _ := cmd.Flags().GetStringArray("field") + mfaOptionID, _ := cmd.Flags().GetString("mfa-option-id") + ssoButtonSelector, _ := cmd.Flags().GetString("sso-button-selector") + + // Parse field pairs into map + fieldValues := make(map[string]string) + for _, pair := range fieldPairs { + parts := strings.SplitN(pair, "=", 2) + if len(parts) != 2 { + return fmt.Errorf("invalid field format: %s (expected key=value)", pair) + } + fieldValues[parts[0]] = parts[1] + } + + svc := client.Auth.Connections + c := AuthConnectionCmd{svc: &svc} + return c.Submit(cmd.Context(), AuthConnectionSubmitInput{ + ID: args[0], + FieldValues: fieldValues, + MfaOptionID: mfaOptionID, + SSOButtonSelector: ssoButtonSelector, + Output: output, + }) +} + +func runAuthConnectionsFollow(cmd *cobra.Command, args []string) error { + client := getKernelClient(cmd) + output, _ := cmd.Flags().GetString("output") + + svc := client.Auth.Connections + c := AuthConnectionCmd{svc: &svc} + return c.Follow(cmd.Context(), AuthConnectionFollowInput{ + ID: args[0], + Output: output, + }) +} diff --git a/go.mod b/go.mod index d65b9f4..4a547e5 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.1 github.com/golang-jwt/jwt/v5 v5.2.2 github.com/joho/godotenv v1.5.1 - github.com/kernel/kernel-go-sdk v0.32.0 + github.com/kernel/kernel-go-sdk v0.32.1-0.20260210174239-c90e1da19efb github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/pquerna/otp v1.5.0 github.com/pterm/pterm v0.12.80 diff --git a/go.sum b/go.sum index 9653f0d..799bf0e 100644 --- a/go.sum +++ b/go.sum @@ -66,8 +66,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/kernel/kernel-go-sdk v0.32.0 h1:xdypUWiHvZlivIZ4eoBUE2jxZr2h9ZGl9IdWLW6P3fc= -github.com/kernel/kernel-go-sdk v0.32.0/go.mod h1:EeZzSuHZVeHKxKCPUzxou2bovNGhXaz0RXrSqKNf1AQ= +github.com/kernel/kernel-go-sdk v0.32.1-0.20260210174239-c90e1da19efb h1:yu5PSECVvcZ7Erb5lvwbc8wLa4ncrZjT+EF6R9g4zaU= +github.com/kernel/kernel-go-sdk v0.32.1-0.20260210174239-c90e1da19efb/go.mod h1:EeZzSuHZVeHKxKCPUzxou2bovNGhXaz0RXrSqKNf1AQ= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=