From ba1640cc81ecad30951fb45be9844a1959fe3eee Mon Sep 17 00:00:00 2001 From: "kernel-internal[bot]" <260533166+kernel-internal[bot]@users.noreply.github.com> Date: Sat, 21 Feb 2026 21:32:29 +0000 Subject: [PATCH 1/5] Update kernel-go-sdk to v0.36.2-0.20260221213031-b6a60bbf00a2 Update Go SDK dependency to latest version. Full SDK method enumeration performed - all SDK methods have corresponding CLI commands (no coverage gaps found). Triggered by: kernel/kernel-go-sdk@b6a60bbf00a2ee46bd2c13f4f068de462b46de47 Co-authored-by: Cursor --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1e7285d..fbda755 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.35.0 + github.com/kernel/kernel-go-sdk v0.36.2-0.20260221213031-b6a60bbf00a2 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/pterm/pterm v0.12.80 github.com/samber/lo v1.51.0 diff --git a/go.sum b/go.sum index b50a470..f3d74e6 100644 --- a/go.sum +++ b/go.sum @@ -64,8 +64,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.35.0 h1:zQcDPxq7N1njnNVoFmxvi3XMKoqemOVlnkVYuYPqAE0= -github.com/kernel/kernel-go-sdk v0.35.0/go.mod h1:EeZzSuHZVeHKxKCPUzxou2bovNGhXaz0RXrSqKNf1AQ= +github.com/kernel/kernel-go-sdk v0.36.2-0.20260221213031-b6a60bbf00a2 h1:LM9sL6VUfCYPdixsbvc+BYMOjFFPyqZ4JggeMEvIrnA= +github.com/kernel/kernel-go-sdk v0.36.2-0.20260221213031-b6a60bbf00a2/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 d36bb6c1fc26fd01db7734749234b9f8f0f1e5e2 Mon Sep 17 00:00:00 2001 From: "kernel-internal[bot]" <260533166+kernel-internal[bot]@users.noreply.github.com> Date: Sat, 21 Feb 2026 21:47:37 +0000 Subject: [PATCH 2/5] Update kernel-go-sdk to 0bf19a19dfd789f8a73e02513360d019fcb9d37d Updates the Go SDK dependency to the latest version. Coverage Analysis: Full enumeration of SDK methods and CLI commands was performed. No coverage gaps were found - all SDK methods have corresponding CLI commands. Co-authored-by: Cursor --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index fbda755..242874f 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.36.2-0.20260221213031-b6a60bbf00a2 + github.com/kernel/kernel-go-sdk v0.36.2-0.20260221214548-0bf19a19dfd7 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/pterm/pterm v0.12.80 github.com/samber/lo v1.51.0 diff --git a/go.sum b/go.sum index f3d74e6..ccd67fc 100644 --- a/go.sum +++ b/go.sum @@ -64,8 +64,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.36.2-0.20260221213031-b6a60bbf00a2 h1:LM9sL6VUfCYPdixsbvc+BYMOjFFPyqZ4JggeMEvIrnA= -github.com/kernel/kernel-go-sdk v0.36.2-0.20260221213031-b6a60bbf00a2/go.mod h1:EeZzSuHZVeHKxKCPUzxou2bovNGhXaz0RXrSqKNf1AQ= +github.com/kernel/kernel-go-sdk v0.36.2-0.20260221214548-0bf19a19dfd7 h1:0evHNgffqBgtPMqbYopZ0Y7RYTvCvIrt3zfmKBpOmaY= +github.com/kernel/kernel-go-sdk v0.36.2-0.20260221214548-0bf19a19dfd7/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 256b8e32539e1e4581359f9dcdfaf9371b8f25c4 Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Sun, 22 Feb 2026 16:06:26 -0500 Subject: [PATCH 3/5] Add deploy delete and app delete commands - `kernel deploy delete `: deletes a single deployment by ID with confirmation prompt (skippable via --yes) - `kernel app delete `: deletes all deployments for an app, optionally scoped to a specific --version, with parallel deletion and a progress spinner Both commands use the new Deployments.Delete SDK method added in the go-sdk bump. Co-authored-by: Cursor --- cmd/app.go | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++- cmd/deploy.go | 38 +++++++++++++++++++++++ go.mod | 2 +- 3 files changed, 123 insertions(+), 2 deletions(-) diff --git a/cmd/app.go b/cmd/app.go index 98793bc..9f770ff 100644 --- a/cmd/app.go +++ b/cmd/app.go @@ -9,6 +9,7 @@ import ( "github.com/pterm/pterm" "github.com/samber/lo" "github.com/spf13/cobra" + "golang.org/x/sync/errgroup" ) var appCmd = &cobra.Command{ @@ -25,7 +26,14 @@ var appListCmd = &cobra.Command{ RunE: runAppList, } -// --- app history subcommand (scaffold) +var appDeleteCmd = &cobra.Command{ + Use: "delete ", + Short: "Delete an app and all its deployments", + Long: "Deletes all deployments for an application. Use --version to scope deletion to a specific version.", + Args: cobra.ExactArgs(1), + RunE: runAppDelete, +} + var appHistoryCmd = &cobra.Command{ Use: "history ", Short: "Show deployment history for an application", @@ -36,8 +44,13 @@ var appHistoryCmd = &cobra.Command{ func init() { // register subcommands under app appCmd.AddCommand(appListCmd) + appCmd.AddCommand(appDeleteCmd) appCmd.AddCommand(appHistoryCmd) + // Flags for delete + appDeleteCmd.Flags().String("version", "", "Only delete deployments for this version (default: all versions)") + appDeleteCmd.Flags().BoolP("yes", "y", false, "Skip confirmation prompt") + // Add optional filters for list appListCmd.Flags().String("name", "", "Filter by application name") appListCmd.Flags().String("version", "", "Filter by version label") @@ -206,6 +219,76 @@ func runAppList(cmd *cobra.Command, args []string) error { return nil } +func runAppDelete(cmd *cobra.Command, args []string) error { + client := getKernelClient(cmd) + appName := args[0] + version, _ := cmd.Flags().GetString("version") + skipConfirm, _ := cmd.Flags().GetBool("yes") + + params := kernel.DeploymentListParams{ + AppName: kernel.Opt(appName), + Limit: kernel.Opt(int64(100)), + Offset: kernel.Opt(int64(0)), + } + if version != "" { + params.AppVersion = kernel.Opt(version) + } + + initial, err := client.Deployments.List(cmd.Context(), params) + if err != nil { + return util.CleanedUpSdkError{Err: err} + } + if initial == nil || len(initial.Items) == 0 { + pterm.Info.Printf("No deployments found for app '%s'\n", appName) + return nil + } + + if !skipConfirm { + scope := "all versions" + if version != "" { + scope = fmt.Sprintf("version '%s'", version) + } + msg := fmt.Sprintf("Delete all deployments for app '%s' (%s)? This cannot be undone.", appName, scope) + pterm.DefaultInteractiveConfirm.DefaultText = msg + ok, _ := pterm.DefaultInteractiveConfirm.Show() + if !ok { + pterm.Info.Println("Deletion cancelled") + return nil + } + } + + spinner, _ := pterm.DefaultSpinner.Start(fmt.Sprintf("Deleting deployments for app '%s'...", appName)) + deleted := 0 + + for { + page, err := client.Deployments.List(cmd.Context(), params) + if err != nil { + spinner.Fail("Failed to list deployments") + return util.CleanedUpSdkError{Err: err} + } + items := page.Items + if len(items) == 0 { + break + } + + g, gctx := errgroup.WithContext(cmd.Context()) + for _, dep := range items { + g.Go(func() error { + return client.Deployments.Delete(gctx, dep.ID) + }) + } + if err := g.Wait(); err != nil { + spinner.Fail("Failed to delete deployments") + return util.CleanedUpSdkError{Err: err} + } + deleted += len(items) + spinner.UpdateText(fmt.Sprintf("Deleted %d deployment(s) so far...", deleted)) + } + + spinner.Success(fmt.Sprintf("Deleted %d deployment(s) for app '%s'", deleted, appName)) + return nil +} + func runAppHistory(cmd *cobra.Command, args []string) error { client := getKernelClient(cmd) appName := args[0] diff --git a/cmd/deploy.go b/cmd/deploy.go index e826eb0..d82d7ea 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -23,6 +23,14 @@ import ( "github.com/spf13/cobra" ) +var deployDeleteCmd = &cobra.Command{ + Use: "delete ", + Short: "Delete a deployment", + Long: "Stops a running deployment and marks it for deletion. If already stopped or failed, deletes immediately.", + Args: cobra.ExactArgs(1), + RunE: runDeployDelete, +} + var deployLogsCmd = &cobra.Command{ Use: "logs ", Short: "Stream logs for a deployment", @@ -65,6 +73,9 @@ func init() { deployLogsCmd.Flags().BoolP("with-timestamps", "t", false, "Include timestamps in each log line") deployCmd.AddCommand(deployLogsCmd) + deployDeleteCmd.Flags().BoolP("yes", "y", false, "Skip confirmation prompt") + deployCmd.AddCommand(deployDeleteCmd) + deployHistoryCmd.Flags().Int("limit", 20, "Max deployments to return (default 20)") deployHistoryCmd.Flags().Int("per-page", 20, "Items per page (alias of --limit)") deployHistoryCmd.Flags().Int("page", 1, "Page number (1-based)") @@ -306,6 +317,33 @@ func quoteIfNeeded(s string) string { return s } +func runDeployDelete(cmd *cobra.Command, args []string) error { + client := getKernelClient(cmd) + deploymentID := args[0] + skipConfirm, _ := cmd.Flags().GetBool("yes") + + if !skipConfirm { + msg := fmt.Sprintf("Are you sure you want to delete deployment '%s'? This cannot be undone.", deploymentID) + pterm.DefaultInteractiveConfirm.DefaultText = msg + ok, _ := pterm.DefaultInteractiveConfirm.Show() + if !ok { + pterm.Info.Println("Deletion cancelled") + return nil + } + } + + if err := client.Deployments.Delete(cmd.Context(), deploymentID); err != nil { + if util.IsNotFound(err) { + pterm.Warning.Printf("Deployment '%s' not found\n", deploymentID) + return nil + } + return util.CleanedUpSdkError{Err: err} + } + + pterm.Success.Printf("Deleted deployment %s\n", deploymentID) + return nil +} + func runDeployLogs(cmd *cobra.Command, args []string) error { client := getKernelClient(cmd) diff --git a/go.mod b/go.mod index 242874f..e534a10 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/zalando/go-keyring v0.2.6 golang.org/x/crypto v0.47.0 golang.org/x/oauth2 v0.30.0 + golang.org/x/sync v0.19.0 ) require ( @@ -53,7 +54,6 @@ require ( github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/term v0.39.0 // indirect golang.org/x/text v0.33.0 // indirect From 039431227f32f4809a3d6b7beee148b40783fb95 Mon Sep 17 00:00:00 2001 From: "kernel-internal[bot]" <260533166+kernel-internal[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:18:58 +0000 Subject: [PATCH 4/5] Update kernel-go-sdk to v0.37.0 - Updated kernel-go-sdk from v0.36.2-0.20260221214548-0bf19a19dfd7 to v0.37.0 - Updated ProfilesService.List to accept ProfileListParams (breaking change) - Added --limit, --offset, --query flags to profiles list command - Updated tests for new ProfilesService interface Co-authored-by: Cursor --- cmd/profiles.go | 46 +++++++++++++++++++++++++++++++++++++------- cmd/profiles_test.go | 14 ++++++++------ go.mod | 2 +- go.sum | 4 ++-- 4 files changed, 50 insertions(+), 16 deletions(-) diff --git a/cmd/profiles.go b/cmd/profiles.go index 03b8c41..a400bbc 100644 --- a/cmd/profiles.go +++ b/cmd/profiles.go @@ -12,6 +12,7 @@ import ( "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/pterm/pterm" "github.com/spf13/cobra" ) @@ -19,7 +20,7 @@ import ( // ProfilesService defines the subset of the Kernel SDK profile client that we use. type ProfilesService interface { Get(ctx context.Context, idOrName string, opts ...option.RequestOption) (res *kernel.Profile, err error) - List(ctx context.Context, opts ...option.RequestOption) (res *[]kernel.Profile, err error) + List(ctx context.Context, query kernel.ProfileListParams, opts ...option.RequestOption) (res *pagination.OffsetPagination[kernel.Profile], err error) Delete(ctx context.Context, idOrName string, opts ...option.RequestOption) (err error) New(ctx context.Context, body kernel.ProfileNewParams, opts ...option.RequestOption) (res *kernel.Profile, err error) Download(ctx context.Context, idOrName string, opts ...option.RequestOption) (res *http.Response, err error) @@ -32,6 +33,9 @@ type ProfilesGetInput struct { type ProfilesListInput struct { Output string + Limit int + Offset int + Query string } type ProfilesCreateInput struct { @@ -63,25 +67,42 @@ func (p ProfilesCmd) List(ctx context.Context, in ProfilesListInput) error { if in.Output != "json" { pterm.Info.Println("Fetching profiles...") } - items, err := p.profiles.List(ctx) + + params := kernel.ProfileListParams{} + if in.Limit > 0 { + params.Limit = kernel.Opt(int64(in.Limit)) + } + if in.Offset > 0 { + params.Offset = kernel.Opt(int64(in.Offset)) + } + if in.Query != "" { + params.Query = kernel.Opt(in.Query) + } + + page, err := p.profiles.List(ctx, params) if err != nil { return util.CleanedUpSdkError{Err: err} } + var items []kernel.Profile + if page != nil { + items = page.Items + } + if in.Output == "json" { - if items == nil || len(*items) == 0 { + if len(items) == 0 { fmt.Println("[]") return nil } - return util.PrintPrettyJSONSlice(*items) + return util.PrintPrettyJSONSlice(items) } - if items == nil || len(*items) == 0 { + if len(items) == 0 { pterm.Info.Println("No profiles found") return nil } rows := pterm.TableData{{"Profile ID", "Name", "Created At", "Updated At", "Last Used At"}} - for _, prof := range *items { + for _, prof := range items { name := prof.Name if name == "" { name = "-" @@ -299,6 +320,9 @@ func init() { profilesCmd.AddCommand(profilesDownloadCmd) profilesListCmd.Flags().StringP("output", "o", "", "Output format: json for raw API response") + profilesListCmd.Flags().Int("limit", 0, "Maximum number of results to return") + profilesListCmd.Flags().Int("offset", 0, "Number of results to skip") + profilesListCmd.Flags().String("query", "", "Search profiles by name or ID") profilesGetCmd.Flags().StringP("output", "o", "", "Output format: json for raw API response") profilesCreateCmd.Flags().StringP("output", "o", "", "Output format: json for raw API response") profilesCreateCmd.Flags().String("name", "", "Optional unique profile name") @@ -310,9 +334,17 @@ func init() { func runProfilesList(cmd *cobra.Command, args []string) error { client := getKernelClient(cmd) output, _ := cmd.Flags().GetString("output") + limit, _ := cmd.Flags().GetInt("limit") + offset, _ := cmd.Flags().GetInt("offset") + query, _ := cmd.Flags().GetString("query") svc := client.Profiles p := ProfilesCmd{profiles: &svc} - return p.List(cmd.Context(), ProfilesListInput{Output: output}) + return p.List(cmd.Context(), ProfilesListInput{ + Output: output, + Limit: limit, + Offset: offset, + Query: query, + }) } func runProfilesGet(cmd *cobra.Command, args []string) error { diff --git a/cmd/profiles_test.go b/cmd/profiles_test.go index cf9b9fa..df55c50 100644 --- a/cmd/profiles_test.go +++ b/cmd/profiles_test.go @@ -13,6 +13,7 @@ import ( "github.com/kernel/kernel-go-sdk" "github.com/kernel/kernel-go-sdk/option" + "github.com/kernel/kernel-go-sdk/packages/pagination" "github.com/pterm/pterm" "github.com/stretchr/testify/assert" ) @@ -44,7 +45,7 @@ func captureProfilesOutput(t *testing.T) *bytes.Buffer { // FakeProfilesService implements ProfilesService type FakeProfilesService struct { GetFunc func(ctx context.Context, idOrName string, opts ...option.RequestOption) (*kernel.Profile, error) - ListFunc func(ctx context.Context, opts ...option.RequestOption) (*[]kernel.Profile, error) + ListFunc func(ctx context.Context, query kernel.ProfileListParams, opts ...option.RequestOption) (*pagination.OffsetPagination[kernel.Profile], error) DeleteFunc func(ctx context.Context, idOrName string, opts ...option.RequestOption) error NewFunc func(ctx context.Context, body kernel.ProfileNewParams, opts ...option.RequestOption) (*kernel.Profile, error) DownloadFunc func(ctx context.Context, idOrName string, opts ...option.RequestOption) (*http.Response, error) @@ -56,12 +57,11 @@ func (f *FakeProfilesService) Get(ctx context.Context, idOrName string, opts ... } return &kernel.Profile{ID: idOrName, CreatedAt: time.Unix(0, 0), UpdatedAt: time.Unix(0, 0)}, nil } -func (f *FakeProfilesService) List(ctx context.Context, opts ...option.RequestOption) (*[]kernel.Profile, error) { +func (f *FakeProfilesService) List(ctx context.Context, query kernel.ProfileListParams, opts ...option.RequestOption) (*pagination.OffsetPagination[kernel.Profile], error) { if f.ListFunc != nil { - return f.ListFunc(ctx, opts...) + return f.ListFunc(ctx, query, opts...) } - empty := []kernel.Profile{} - return &empty, nil + return &pagination.OffsetPagination[kernel.Profile]{Items: []kernel.Profile{}}, nil } func (f *FakeProfilesService) Delete(ctx context.Context, idOrName string, opts ...option.RequestOption) error { if f.DeleteFunc != nil { @@ -94,7 +94,9 @@ func TestProfilesList_WithRows(t *testing.T) { buf := captureProfilesOutput(t) created := time.Unix(0, 0) rows := []kernel.Profile{{ID: "p1", Name: "alpha", CreatedAt: created, UpdatedAt: created}, {ID: "p2", Name: "", CreatedAt: created, UpdatedAt: created}} - fake := &FakeProfilesService{ListFunc: func(ctx context.Context, opts ...option.RequestOption) (*[]kernel.Profile, error) { return &rows, nil }} + fake := &FakeProfilesService{ListFunc: func(ctx context.Context, query kernel.ProfileListParams, opts ...option.RequestOption) (*pagination.OffsetPagination[kernel.Profile], error) { + return &pagination.OffsetPagination[kernel.Profile]{Items: rows}, nil + }} p := ProfilesCmd{profiles: fake} _ = p.List(context.Background(), ProfilesListInput{}) out := buf.String() diff --git a/go.mod b/go.mod index e534a10..2eae5da 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.36.2-0.20260221214548-0bf19a19dfd7 + github.com/kernel/kernel-go-sdk v0.37.0 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/pterm/pterm v0.12.80 github.com/samber/lo v1.51.0 diff --git a/go.sum b/go.sum index ccd67fc..067be8d 100644 --- a/go.sum +++ b/go.sum @@ -64,8 +64,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.36.2-0.20260221214548-0bf19a19dfd7 h1:0evHNgffqBgtPMqbYopZ0Y7RYTvCvIrt3zfmKBpOmaY= -github.com/kernel/kernel-go-sdk v0.36.2-0.20260221214548-0bf19a19dfd7/go.mod h1:EeZzSuHZVeHKxKCPUzxou2bovNGhXaz0RXrSqKNf1AQ= +github.com/kernel/kernel-go-sdk v0.37.0 h1:90/AJUSSY0P09S2qO9GLP3xPr0qS8z0Fb7frDbVnJGQ= +github.com/kernel/kernel-go-sdk v0.37.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 74ca98515a2ebaf082c7e6fadb74489a571b88f2 Mon Sep 17 00:00:00 2001 From: Neil Dcruze Date: Mon, 23 Feb 2026 15:14:52 -0500 Subject: [PATCH 5/5] updated the CLI to use the kernel app list style format for profiles --- cmd/profiles.go | 66 ++++++++++++++++++++++++++++++-------------- cmd/profiles_test.go | 59 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 102 insertions(+), 23 deletions(-) diff --git a/cmd/profiles.go b/cmd/profiles.go index a400bbc..046364c 100644 --- a/cmd/profiles.go +++ b/cmd/profiles.go @@ -14,6 +14,7 @@ import ( "github.com/kernel/kernel-go-sdk/option" "github.com/kernel/kernel-go-sdk/packages/pagination" "github.com/pterm/pterm" + "github.com/samber/lo" "github.com/spf13/cobra" ) @@ -32,10 +33,10 @@ type ProfilesGetInput struct { } type ProfilesListInput struct { - Output string - Limit int - Offset int - Query string + Output string + Page int + PerPage int + Query string } type ProfilesCreateInput struct { @@ -64,31 +65,42 @@ func (p ProfilesCmd) List(ctx context.Context, in ProfilesListInput) error { return fmt.Errorf("unsupported --output value: use 'json'") } + page := in.Page + perPage := in.PerPage + if page <= 0 { + page = 1 + } + if perPage <= 0 { + perPage = 20 + } + if in.Output != "json" { pterm.Info.Println("Fetching profiles...") } params := kernel.ProfileListParams{} - if in.Limit > 0 { - params.Limit = kernel.Opt(int64(in.Limit)) - } - if in.Offset > 0 { - params.Offset = kernel.Opt(int64(in.Offset)) - } if in.Query != "" { params.Query = kernel.Opt(in.Query) } + params.Limit = kernel.Opt(int64(perPage + 1)) + params.Offset = kernel.Opt(int64((page - 1) * perPage)) - page, err := p.profiles.List(ctx, params) + result, err := p.profiles.List(ctx, params) if err != nil { return util.CleanedUpSdkError{Err: err} } var items []kernel.Profile - if page != nil { - items = page.Items + if result != nil { + items = result.Items } + hasMore := len(items) > perPage + if hasMore { + items = items[:perPage] + } + itemsThisPage := len(items) + if in.Output == "json" { if len(items) == 0 { fmt.Println("[]") @@ -116,6 +128,17 @@ func (p ProfilesCmd) List(ctx context.Context, in ProfilesListInput) error { }) } PrintTableNoPad(rows, true) + + pterm.Printf("\nPage: %d Per-page: %d Items this page: %d Has more: %s\n", page, perPage, itemsThisPage, lo.Ternary(hasMore, "yes", "no")) + if hasMore { + nextPage := page + 1 + nextCmd := fmt.Sprintf("kernel profile list --page %d --per-page %d", nextPage, perPage) + if in.Query != "" { + nextCmd += fmt.Sprintf(" --query \"%s\"", in.Query) + } + pterm.Printf("Next: %s\n", nextCmd) + } + return nil } @@ -320,8 +343,8 @@ func init() { profilesCmd.AddCommand(profilesDownloadCmd) profilesListCmd.Flags().StringP("output", "o", "", "Output format: json for raw API response") - profilesListCmd.Flags().Int("limit", 0, "Maximum number of results to return") - profilesListCmd.Flags().Int("offset", 0, "Number of results to skip") + profilesListCmd.Flags().Int("per-page", 20, "Items per page (default 20)") + profilesListCmd.Flags().Int("page", 1, "Page number (1-based)") profilesListCmd.Flags().String("query", "", "Search profiles by name or ID") profilesGetCmd.Flags().StringP("output", "o", "", "Output format: json for raw API response") profilesCreateCmd.Flags().StringP("output", "o", "", "Output format: json for raw API response") @@ -334,16 +357,17 @@ func init() { func runProfilesList(cmd *cobra.Command, args []string) error { client := getKernelClient(cmd) output, _ := cmd.Flags().GetString("output") - limit, _ := cmd.Flags().GetInt("limit") - offset, _ := cmd.Flags().GetInt("offset") + perPage, _ := cmd.Flags().GetInt("per-page") + page, _ := cmd.Flags().GetInt("page") query, _ := cmd.Flags().GetString("query") + svc := client.Profiles p := ProfilesCmd{profiles: &svc} return p.List(cmd.Context(), ProfilesListInput{ - Output: output, - Limit: limit, - Offset: offset, - Query: query, + Output: output, + Page: page, + PerPage: perPage, + Query: query, }) } diff --git a/cmd/profiles_test.go b/cmd/profiles_test.go index df55c50..8995b75 100644 --- a/cmd/profiles_test.go +++ b/cmd/profiles_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "errors" + "fmt" "io" "net/http" "os" @@ -86,7 +87,7 @@ func TestProfilesList_Empty(t *testing.T) { buf := captureProfilesOutput(t) fake := &FakeProfilesService{} p := ProfilesCmd{profiles: fake} - _ = p.List(context.Background(), ProfilesListInput{}) + _ = p.List(context.Background(), ProfilesListInput{Page: 1, PerPage: 20}) assert.Contains(t, buf.String(), "No profiles found") } @@ -98,11 +99,65 @@ func TestProfilesList_WithRows(t *testing.T) { return &pagination.OffsetPagination[kernel.Profile]{Items: rows}, nil }} p := ProfilesCmd{profiles: fake} - _ = p.List(context.Background(), ProfilesListInput{}) + _ = p.List(context.Background(), ProfilesListInput{Page: 1, PerPage: 20}) out := buf.String() assert.Contains(t, out, "p1") assert.Contains(t, out, "alpha") assert.Contains(t, out, "p2") + assert.Contains(t, out, "Has more: no") +} + +func TestProfilesList_HasMore(t *testing.T) { + buf := captureProfilesOutput(t) + created := time.Unix(0, 0) + perPage := 2 + items := make([]kernel.Profile, perPage+1) + for i := range items { + items[i] = kernel.Profile{ID: fmt.Sprintf("p%d", i), CreatedAt: created, UpdatedAt: created} + } + fake := &FakeProfilesService{ListFunc: func(ctx context.Context, query kernel.ProfileListParams, opts ...option.RequestOption) (*pagination.OffsetPagination[kernel.Profile], error) { + return &pagination.OffsetPagination[kernel.Profile]{Items: items}, nil + }} + p := ProfilesCmd{profiles: fake} + _ = p.List(context.Background(), ProfilesListInput{Page: 1, PerPage: perPage}) + out := buf.String() + assert.Contains(t, out, "Has more: yes") + assert.Contains(t, out, "Next: kernel profile list --page 2 --per-page 2") + assert.Contains(t, out, "p0") + assert.Contains(t, out, "p1") + assert.NotContains(t, out, "p2") +} + +func TestProfilesList_QueryInNextHint(t *testing.T) { + buf := captureProfilesOutput(t) + created := time.Unix(0, 0) + items := make([]kernel.Profile, 3) + for i := range items { + items[i] = kernel.Profile{ID: fmt.Sprintf("p%d", i), CreatedAt: created, UpdatedAt: created} + } + fake := &FakeProfilesService{ListFunc: func(ctx context.Context, query kernel.ProfileListParams, opts ...option.RequestOption) (*pagination.OffsetPagination[kernel.Profile], error) { + return &pagination.OffsetPagination[kernel.Profile]{Items: items}, nil + }} + p := ProfilesCmd{profiles: fake} + _ = p.List(context.Background(), ProfilesListInput{Page: 1, PerPage: 2, Query: "my-bot"}) + out := buf.String() + assert.Contains(t, out, `--query "my-bot"`) +} + +func TestProfilesList_QueryWithSpacesQuoted(t *testing.T) { + buf := captureProfilesOutput(t) + created := time.Unix(0, 0) + items := make([]kernel.Profile, 3) + for i := range items { + items[i] = kernel.Profile{ID: fmt.Sprintf("p%d", i), CreatedAt: created, UpdatedAt: created} + } + fake := &FakeProfilesService{ListFunc: func(ctx context.Context, query kernel.ProfileListParams, opts ...option.RequestOption) (*pagination.OffsetPagination[kernel.Profile], error) { + return &pagination.OffsetPagination[kernel.Profile]{Items: items}, nil + }} + p := ProfilesCmd{profiles: fake} + _ = p.List(context.Background(), ProfilesListInput{Page: 1, PerPage: 2, Query: "my bot"}) + out := buf.String() + assert.Contains(t, out, `--query "my bot"`) } func TestProfilesGet_Success(t *testing.T) {