Skip to content

feat(cli): add CLI entrypoint and Cobra root#127

Open
rogirun wants to merge 4 commits into
mainfrom
feat/cli-cobra-root
Open

feat(cli): add CLI entrypoint and Cobra root#127
rogirun wants to merge 4 commits into
mainfrom
feat/cli-cobra-root

Conversation

@rogirun

@rogirun rogirun commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds the foundation for the Karta CLI: a Cobra root command for the karta binary, built as its own Go module (github.com/run-ai/karta/cli) that mirrors the existing operator module. Keeping it a separate module means cobra and future CLI dependencies stay out of the embeddable github.com/run-ai/karta library module (it references the library via replace => ../, same as operator/).

  • Global persistent flags: --kubeconfig, -n/--namespace, -o/--output (validated against table|wide|json|yaml).
  • Two-noun command structure: karta workload and karta definition (parent commands only; their subcommands land in later tasks of the CLI epic).
  • workload commands require -n/--namespace (single-namespace MVP; no all-namespaces mode).
  • New dependency: github.com/spf13/cobra v1.10.2 (Apache-2.0; pulls github.com/spf13/pflag BSD-3 and github.com/inconshreveable/mousetrap BSD-3).

Acceptance: karta --help works and lists both nouns plus the global flags; karta workload errors with a clear message and non-zero exit when -n is omitted; with -n it renders help.

Related issue(s)

Part of #94

Checklist

  • All commits are signed off with DCO (git commit -s)
  • New/modified files have SPDX license and copyright headers
  • Documentation updated (if applicable)
  • Tests pass (make check)
  • No proprietary or internal information included

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a Cobra-based karta CLI with definition and workload command groups.
    • Added --kubeconfig and -o/--output flags, plus required -n/--namespace for workload commands.
    • Added output-format options with validation and shell completion: table, wide, json, yaml.
  • Bug Fixes
    • Workload commands now clearly require --namespace, and the parent command shows help when no subcommand is provided.
  • Tests
    • Added unit tests for root help text, output validation, and workload namespace enforcement.

Add the karta CLI as its own Go module (github.com/run-ai/karta/cli),
mirroring the operator module, so cobra and future CLI dependencies stay
out of the embeddable github.com/run-ai/karta library module.

Wire the Cobra root with global persistent flags (--kubeconfig,
-n/--namespace, -o/--output) and the two-noun command structure
(workload, definition). Workload commands require -n/--namespace
(single-namespace MVP); the output format is validated against
table|wide|json|yaml.

New dependency: github.com/spf13/cobra v1.10.2 (Apache-2.0; pulls
github.com/spf13/pflag BSD-3 and github.com/inconshreveable/mousetrap
BSD-3).

Part of #94.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Roee Gil <roee.gil@run.ai>
@rogirun rogirun force-pushed the feat/cli-cobra-root branch from 92e2c9c to 4884711 Compare June 30, 2026 06:56
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds the initial karta CLI module, entrypoint, root command wiring, shared flag and output types, workload and definition subcommands, and tests covering help output, output validation, and namespace enforcement.

Changes

Karta CLI initial scaffolding

Layer / File(s) Summary
Go module and entrypoint
cli/go.mod, cli/main.go
Declares the github.com/run-ai/karta/cli module with Cobra dependency and a local replace directive; main.go runs the root command and exits with code 1 on error.
Output flag type
cli/internal/flagtypes/*
Adds the generic enum flag implementation, the Output format type, allowed values, validation, and tests for valid, invalid, and type behavior.
Shared CLI flag registration
cli/cmd/flags/flags.go
Registers persistent --kubeconfig, -o/--output, and -n/--namespace flags, and wires output completion.
Root command and validation tests
cli/cmd/root.go, cli/cmd/root_test.go
Builds the root Cobra command, adds shared flags, mounts workload and definition, and tests help output plus supported and unsupported output formats.
Workload subcommand and tests
cli/cmd/workload/*
Adds the workload command with help output, applies the namespace flag to its subtree, and tests execution with and without -n.
Definition subcommand stub
cli/cmd/definition/definition.go
Adds the definition command with a help-printing RunE handler.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: ronlv10

Poem

🐇 A bunny hops with flags in tow,
table, json, yaml all glow.
workload asks for a namespace name,
definition whispers help just the same,
And karta springs up bright to show.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding the CLI entrypoint and Cobra root command.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cli-cobra-root

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
cli/cmd/root_test.go (1)

51-53: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the invalid -o error text, not just that an error happened.

The acceptance criteria call for a clear failure when the format is invalid. This test still passes if Cobra returns some unrelated error.

Proposed test tightening
 	if _, err := execute(t, "-o", "bogus", "noop"); err == nil {
 		t.Error("expected error for invalid output format, got nil")
+	} else if !strings.Contains(err.Error(), "must be one of table, wide, json, yaml") {
+		t.Fatalf("unexpected error message: %v", err)
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/cmd/root_test.go` around lines 51 - 53, The test around execute currently
only checks that an error occurs for an invalid -o value, which is too loose.
Tighten the assertion in root_test.go by checking the actual error text returned
from execute when passing "-o bogus noop", using the existing execute helper and
the command setup in rootCmd so the test fails unless the invalid output-format
message is specifically returned.
cli/cmd/workload/workload_test.go (1)

44-49: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add the parent-command help-path test from the acceptance criteria.

These tests only cover workload dummy. They do not lock in the promised karta workload -n <ns> behavior, where the parent command should print help instead of erroring.

Possible follow-up diff
 import (
 	"bytes"
 	"errors"
+	"strings"
 	"testing"

 	"github.com/spf13/cobra"
 )
@@
-func newTestTree() *cobra.Command {
+func newTestTree() (*cobra.Command, *bytes.Buffer) {
 	cobra.EnableTraverseRunHooks = true

 	root := &cobra.Command{Use: "root", SilenceUsage: true, SilenceErrors: true}
 	root.PersistentFlags().StringP("namespace", "n", "", "")
@@
-	var out bytes.Buffer
-	root.SetOut(&out)
-	root.SetErr(&out)
-	return root
+	out := &bytes.Buffer{}
+	root.SetOut(out)
+	root.SetErr(out)
+	return root, out
 }
 
 func TestWorkloadRequiresNamespace(t *testing.T) {
-	root := newTestTree()
+	root, _ := newTestTree()
 	root.SetArgs([]string{"workload", "dummy"})
 	if err := root.Execute(); !errors.Is(err, ErrNamespaceRequired) {
 		t.Fatalf("expected ErrNamespaceRequired, got %v", err)
 	}
 }
 
 func TestWorkloadWithNamespace(t *testing.T) {
-	root := newTestTree()
+	root, _ := newTestTree()
 	root.SetArgs([]string{"-n", "ml-team", "workload", "dummy"})
 	if err := root.Execute(); err != nil {
 		t.Fatalf("unexpected error with namespace set: %v", err)
 	}
 }
+
+func TestWorkloadParentShowsHelpWithNamespace(t *testing.T) {
+	root, out := newTestTree()
+	root.SetArgs([]string{"-n", "ml-team", "workload"})
+	if err := root.Execute(); err != nil {
+		t.Fatalf("unexpected error with namespace set: %v", err)
+	}
+	if !strings.Contains(out.String(), "Inspect workloads running in a namespace") {
+		t.Fatalf("expected workload help output, got %q", out.String())
+	}
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/cmd/workload/workload_test.go` around lines 44 - 49, Add the missing
parent-command help-path test in TestWorkloadWithNamespace so it covers the
accepted `karta workload -n <ns>` behavior, not just `workload dummy`. Use the
existing `newTestTree()` setup and `root.Execute()` flow to assert that invoking
the workload parent command with `-n` and no subcommand prints help instead of
returning an error, alongside the current dummy subcommand coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@cli/cmd/root_test.go`:
- Around line 51-53: The test around execute currently only checks that an error
occurs for an invalid -o value, which is too loose. Tighten the assertion in
root_test.go by checking the actual error text returned from execute when
passing "-o bogus noop", using the existing execute helper and the command setup
in rootCmd so the test fails unless the invalid output-format message is
specifically returned.

In `@cli/cmd/workload/workload_test.go`:
- Around line 44-49: Add the missing parent-command help-path test in
TestWorkloadWithNamespace so it covers the accepted `karta workload -n <ns>`
behavior, not just `workload dummy`. Use the existing `newTestTree()` setup and
`root.Execute()` flow to assert that invoking the workload parent command with
`-n` and no subcommand prints help instead of returning an error, alongside the
current dummy subcommand coverage.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: dac27c04-7f70-40f1-82a1-6ecbc744c899

📥 Commits

Reviewing files that changed from the base of the PR and between 19f7fc2 and 4884711.

⛔ Files ignored due to path filters (1)
  • cli/go.sum is excluded by !**/*.sum
📒 Files selected for processing (7)
  • cli/cmd/definition/definition.go
  • cli/cmd/root.go
  • cli/cmd/root_test.go
  • cli/cmd/workload/workload.go
  • cli/cmd/workload/workload_test.go
  • cli/go.mod
  • cli/main.go

Comment thread cli/cmd/root.go
Comment thread cli/cmd/root.go
Comment thread cli/cmd/workload/workload.go Outdated
Comment thread cli/cmd/root.go Outdated
Move -n/--namespace onto the workload command as a required flag (was a
persistent root flag guarded by a pre-run hook) and turn -o/--output into a
proper enum validated at parse time.

Introduce two domains: cli/internal/flagtypes holds the flag value structures
(Output enum implementing pflag.Value), and cli/cmd/flags registers the CLI
flags on cobra commands using those types. This removes the root
PersistentPreRunE output check and the now-unused EnableTraverseRunHooks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Roee Gil <roee.gil@run.ai>
@rogirun rogirun requested a review from Isan-Rivkin July 1, 2026 14:11

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
cli/internal/flagtypes/output.go (1)

29-37: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Derive the error message from OutputValues to avoid drift.

The error string in Set hardcodes the same list already defined in OutputValues (lines 23-25). If a new output format is added later, it's easy to update one and forget the other.

♻️ Proposed fix
-import "fmt"
+import (
+	"fmt"
+	"strings"
+)
 	default:
-		return fmt.Errorf("must be one of table, wide, json, yaml")
+		return fmt.Errorf("must be one of %s", strings.Join(OutputValues, ", "))
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/internal/flagtypes/output.go` around lines 29 - 37, The error message in
Output.Set is hardcoded and can drift from the canonical list in OutputValues.
Update Set to build the validation error from OutputValues instead of repeating
the format names, so any future output type changes are reflected automatically.
Use the existing OutputValues symbol as the source of truth and keep the
assignment logic in Set unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@cli/internal/flagtypes/output.go`:
- Around line 29-37: The error message in Output.Set is hardcoded and can drift
from the canonical list in OutputValues. Update Set to build the validation
error from OutputValues instead of repeating the format names, so any future
output type changes are reflected automatically. Use the existing OutputValues
symbol as the source of truth and keep the assignment logic in Set unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: 6fc103e5-6f2f-4261-93e1-43b3776e61aa

📥 Commits

Reviewing files that changed from the base of the PR and between 4884711 and c4ebcae.

📒 Files selected for processing (7)
  • cli/cmd/flags/flags.go
  • cli/cmd/root.go
  • cli/cmd/root_test.go
  • cli/cmd/workload/workload.go
  • cli/cmd/workload/workload_test.go
  • cli/internal/flagtypes/output.go
  • cli/internal/flagtypes/output_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • cli/cmd/root_test.go

Comment thread cli/cmd/flags/flags.go Outdated
Comment thread cli/cmd/flags/flags.go Outdated
Comment thread cli/cmd/workload/workload.go Outdated
Comment thread cli/cmd/workload/workload.go
// Package definition holds the "karta definition" command tree: inspection of
// the Karta definitions the CLI understands. Definitions are cluster-scoped, so
// these commands do not require a namespace.
package definition

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aiming for simplicity - why do we need there a dedicated package here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each subcommand has its own package.
for start we will need
karta workload describe ...
and
karta desribe ...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That doesn't explain, why do we need a dedicated package for commands?
Why can't the commands be:

cli/cmd/
    workload.go
    workload_describe.go // optional if its really big...
    describe.go

Comment thread cli/cmd/root.go Outdated
Comment thread cli/internal/flagtypes/output.go
Comment thread cli/internal/flagtypes/output.go Outdated
*o = Output(v)
return nil
default:
return fmt.Errorf("must be one of table, wide, json, yaml")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

convert to sentinel error

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it's a dynamic error based on the enum.

rogirun and others added 2 commits July 2, 2026 10:39
Replace flags.AddGlobals/AddNamespace with one WithKubeconfig, WithOutput, and
WithNamespace function per flag, adopting the With* naming convention.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Roee Gil <roee.gil@run.ai>
Address review feedback on PR #127:
- Introduce a reusable generic Enum[T ~string] implementing pflag.Value; define
  Output via it. Validation error wraps a sentinel (ErrInvalidValue) and derives
  the allowed-values list from the enum, removing the hardcoded message.
- Trim verbose comments on the workload command.
- Tighten tests: assert the invalid -o error text, cover the workload parent
  help path, and check errors.Is(ErrInvalidValue).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Roee Gil <roee.gil@run.ai>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
cli/cmd/workload/workload_test.go (1)

53-64: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider testing workload alone with no -n.

TestWorkloadRequiresNamespace covers workload dummy without -n, and this test covers workload -n ml-team (parent, no subcommand). The remaining combination — invoking workload itself with no subcommand and no -n — is untested, though it's a reasonable acceptance-criteria case since required-flag validation runs before the parent's own RunE.

🧪 Optional test to add
func TestWorkloadParentRequiresNamespace(t *testing.T) {
	root, _ := newTestTree()
	root.SetArgs([]string{"workload"})
	if err := root.Execute(); err == nil {
		t.Fatal("expected an error when namespace is omitted, got nil")
	}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/cmd/workload/workload_test.go` around lines 53 - 64, Add coverage for the
missing `workload` parent-command case in `TestWorkloadParentShowsHelp`/a new
test alongside `TestWorkloadRequiresNamespace`: invoke `root.Execute()` with
just `workload` and no `-n`, and assert it fails with the required-namespace
validation instead of reaching the parent help path. Use the existing
`newTestTree()` setup and the `workload` command registration to verify the
behavior before `RunE` on the parent command is considered.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@cli/cmd/workload/workload_test.go`:
- Around line 53-64: Add coverage for the missing `workload` parent-command case
in `TestWorkloadParentShowsHelp`/a new test alongside
`TestWorkloadRequiresNamespace`: invoke `root.Execute()` with just `workload`
and no `-n`, and assert it fails with the required-namespace validation instead
of reaching the parent help path. Use the existing `newTestTree()` setup and the
`workload` command registration to verify the behavior before `RunE` on the
parent command is considered.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: 738accf9-ef81-454c-92af-d35edd3f522b

📥 Commits

Reviewing files that changed from the base of the PR and between c4ebcae and d0af7d9.

📒 Files selected for processing (8)
  • cli/cmd/flags/flags.go
  • cli/cmd/root.go
  • cli/cmd/root_test.go
  • cli/cmd/workload/workload.go
  • cli/cmd/workload/workload_test.go
  • cli/internal/flagtypes/enum.go
  • cli/internal/flagtypes/output.go
  • cli/internal/flagtypes/output_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • cli/cmd/workload/workload.go
  • cli/cmd/root_test.go
  • cli/internal/flagtypes/output_test.go

@rogirun rogirun requested a review from Isan-Rivkin July 2, 2026 08:00
// Package definition holds the "karta definition" command tree: inspection of
// the Karta definitions the CLI understands. Definitions are cluster-scoped, so
// these commands do not require a namespace.
package definition

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That doesn't explain, why do we need a dedicated package for commands?
Why can't the commands be:

cli/cmd/
    workload.go
    workload_describe.go // optional if its really big...
    describe.go

Comment thread cli/cmd/flags/flags.go

// Package flags defines and registers the karta CLI's flags on cobra commands,
// backing them with the value structures in the flagtypes package.
package flags

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's simplify this, keep package cmd.
Simply put it in root.go or a dedicated flags.go file - Whatever
Packages heres just bloat the code

Comment thread cli/cmd/workload/workload.go
return root, out
}

func TestWorkloadRequiresNamespace(t *testing.T) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this test redundant just testing the build-in framework requiredflags?
(not a blocker but not sure it provides value or confident)

// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2026 NVIDIA Corporation

package flagtypes

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Path complexity cli/internal/flagtypes - This can simply be a file under cli/cmd/enumflag.go
Don't see why we need to introduce internal + a dedicated go package for flagtypes

// Package flagtypes holds the flag value structures for the karta CLI: types
// that implement pflag.Value and define a flag's shape and behavior (parse,
// validate, print). It starts with the output-format enum.
package flagtypes

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bloat, Path complexity cli/internal/flagtypes - This can simply be a file under cli/cmd/enumflag.go
Don't see why we need to introduce internal + a dedicated go package for flagtypes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants