Skip to content

feat: experimental k9s inspired TUI#1533

Draft
n9te9 wants to merge 5 commits intokagent-dev:mainfrom
n9te9:main
Draft

feat: experimental k9s inspired TUI#1533
n9te9 wants to merge 5 commits intokagent-dev:mainfrom
n9te9:main

Conversation

@n9te9
Copy link
Copy Markdown

@n9te9 n9te9 commented Mar 23, 2026

Context

As discussed in #1494, we are revamping the CLI TUI to provide a more intuitive, k9s-inspired experience for agent management.

Since the existing TUI relies heavily on a session-first architecture, modifying it directly carries a high risk of breaking current functionality. To ensure a safe and iterative development process, this PR lays the foundation for the new TUI by introducing a clean-slate charmbracelet/bubbletea architecture behind a hidden experimental-tui command.

This initial PR focuses strictly on establishing the UI framework, state routing, and layout components using mock data, serving as a Proof of Concept (PoC) for the new UX paradigm before we wire it up to the Kubernetes client.

Changes

CLI Entrypoint: Added a hidden experimental-tui command to cmd/kagent/main.go to safely test the new TUI.

New Architecture: Created the internal/experimental_tui package to completely isolate the new implementation from the existing TUI codebase.

State Router: Implemented a stack-based navigation router (screen/root.go) capable of seamlessly pushing and popping different screen models.

Domain Models: Added Agent and ModelProvider structs/interfaces in domain/ to standardize the data structures required for the views.

UI Components: Built reusable lipgloss components in component/, including a responsive data table, status badges, and a context usage gauge.

Agent List Screen: Implemented the initial agent_list.go screen featuring vim-like keybindings (j/k to navigate, q to quit), currently populated with a DemoProvider to demonstrate the layout and interaction mechanics.

Copilot AI review requested due to automatic review settings March 23, 2026 05:18
jmunozro and others added 4 commits March 23, 2026 14:22
…1525)

Fixes kagent-dev#1524

---------

Signed-off-by: Jesus Munoz <jesus.munoz@solo.io>
Signed-off-by: n9te9 <ai.kanaria.ai@gmail.com>
## Summary

- **PyJWT**: `>=2.8.0` → `>=2.12.0` — fixes CVE-2026-32597 (accepts
unknown `crit` header extensions)
- **pyOpenSSL**: `25.3.0` → `>=26.0.0` — fixes CVE-2026-27459
- **pyasn1**: `0.6.2` → `>=0.6.3` — fixes CVE-2026-30922
- **google.golang.org/grpc**: `v1.79.2` → `v1.79.3` — fixes
CVE-2026-33186 (authorization bypass via missing leading slash in :path)
- **kagent-tools** helm dep: `0.1.1` → `0.1.2`

## CVE Details

| Package | CVE | Severity | Fixed In |
|---------|-----|----------|----------|
| google.golang.org/grpc | CVE-2026-33186 | CRITICAL | 1.79.3 |
| PyJWT | CVE-2026-32597 | HIGH | 2.12.0 |
| pyOpenSSL | CVE-2026-27459 | HIGH | 26.0.0 |
| pyasn1 | CVE-2026-30922 | HIGH | 0.6.3 |

## Test plan

- [ ] `uv sync` in Python workspace resolves without conflicts
- [ ] `make -C python test` passes
- [ ] `go mod tidy` succeeds with no diff
- [ ] Trivy scan passes in CI

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: n9te9 <ai.kanaria.ai@gmail.com>
Signed-off-by: Dmytro Rashko <dmitriy.rashko@amdocs.com>
Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io>
Co-authored-by: Eitan Yarmush <eitan.yarmush@solo.io>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: n9te9 <ai.kanaria.ai@gmail.com>
Signed-off-by: n9te9 <ai.kanaria.ai@gmail.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces an isolated, experimental Bubble Tea–based TUI (k9s-inspired) behind a new CLI entrypoint to prototype a new navigation/layout architecture without impacting the existing session-first TUI.

Changes:

  • Added internal/experimental_tui with initial router (RootModel), an agent list screen, demo data/provider, and basic UI components (table, status, gauge).
  • Wired a new experimental-tui Cobra command into the CLI (currently gated to ENVIRONMENT=local).
  • Introduced initial domain models (Agent, ModelProvider) to standardize data needed by views.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
go/core/cli/internal/experimental_tui/screen/root.go Adds stack-based screen router for push/pop navigation.
go/core/cli/internal/experimental_tui/screen/agent_list.go Adds initial agent list screen with keybindings and table rendering.
go/core/cli/internal/experimental_tui/domain/provider.go Defines ModelProvider interface for model metadata/cost/context info.
go/core/cli/internal/experimental_tui/domain/agent.go Adds Agent domain model and helper methods for metrics/labels.
go/core/cli/internal/experimental_tui/demo.go Provides a demo ModelProvider implementation for mock rendering.
go/core/cli/internal/experimental_tui/component/table.go Implements table rendering for agents (ID/name/status/context gauge).
go/core/cli/internal/experimental_tui/component/status.go Adds (currently minimal) status badge rendering helper.
go/core/cli/internal/experimental_tui/component/guage.go Adds context usage gauge renderer (note: filename typo).
go/core/cli/internal/experimental_tui/app.go Adds Run() entrypoint to launch the experimental program with demo data.
go/core/cli/internal/cli/agent/run.go Adds RunExperimentalTUI wrapper called from Cobra.
go/core/cli/cmd/kagent/main.go Registers the new experimental-tui command (currently env-gated).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +19 to +26
func Run() error {
m := screen.NewRootModel(screen.NewAgentListModel(demoAgents))

p := tea.NewProgram(m)
if _, err := p.Run(); err != nil {
fmt.Printf("Error running program: %v", err)
os.Exit(1)
}
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

Run() returns an error but currently prints the error and calls os.Exit(1), which makes it hard for callers (and tests) to handle failures and also makes the return value misleading. Consider returning the p.Run() error instead of exiting, and let the Cobra command decide whether to os.Exit. Also consider using tea.WithAltScreen() (as the existing TUI does) to avoid leaving the UI rendered in the user’s terminal buffer.

Copilot uses AI. Check for mistakes.
Comment on lines +192 to +194
experimentaltui.Run()
return nil
}
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

RunExperimentalTUI ignores the error returned by experimentaltui.Run() and always returns nil. This will hide failures from the CLI command and makes the error handling in main.go ineffective; return the error from experimentaltui.Run() instead.

Suggested change
experimentaltui.Run()
return nil
}
return experimentaltui.Run()
}

Copilot uses AI. Check for mistakes.
Comment on lines +430 to +445
experimentalTuiCmd := &cobra.Command{
Use: "experimental-tui",
Short: "Run the experimental TUI (for development/testing)",
Long: `Run the experimental TUI (for development/testing)`,
Run: func(cmd *cobra.Command, args []string) {
if err := cli.RunExperimentalTUI(cmd.Context(), runCfg); err != nil {
fmt.Fprintf(os.Stderr, "Error running experimental TUI: %v\n", err)
os.Exit(1)
}
},
}

commands := []*cobra.Command{installCmd, uninstallCmd, invokeCmd, bugReportCmd, versionCmd, dashboardCmd, getCmd, initCmd, buildCmd, deployCmd, addMcpCmd, runCmd, mcp.NewMCPCmd(), envdoc.NewEnvCmd()}
if os.Getenv("ENVIRONMENT") == "local" {
commands = append(commands, experimentalTuiCmd)
}
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

The PR description says this command is a "hidden experimental-tui command", but the implementation conditionally registers it only when ENVIRONMENT == local. If the intent is "hidden but available", prefer always registering the command and marking it hidden via Cobra (e.g., Hidden: true) rather than gating it behind an environment variable.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +3
package component

import (
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

File name appears to be misspelled (guage vs gauge). Renaming it to gauge.go will improve discoverability/searchability and avoid propagating the typo in future references.

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +39
func (m RootModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case PushScreenMessage:
m.stack = append(m.stack, msg.Screen)
return m, msg.Screen.Init()
case PopScreenMessage:
if len(m.stack) > 1 {
m.stack = m.stack[:len(m.stack)-1]
}
return m, nil
}
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

The new stack-based router logic in RootModel.Update (push/pop behavior) is core navigation infrastructure but currently has no unit tests. Since this logic is easy to regress when new screens are added, add focused tests that assert push/pop ordering and that messages are forwarded to the top screen.

Copilot uses AI. Check for mistakes.
Comment on lines +56 to +64
func (a Agent) ContextUsageRate() float64 {
max := a.Provider.MaxContextTokens()
if max == 0 {
return 0
}

current := float64(a.Provider.InputTokens() + a.Provider.OutputTokens())
return current / float64(max)
}
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

Several methods (e.g., ContextUsageRate, TokenRatio, Model) dereference a.Provider without a nil check, so the zero-value Agent{} (or any agent built without setting Provider) will panic at runtime. Consider enforcing non-nil provider by construction (e.g., unexport Provider and require NewAgent), or make these methods nil-safe (return 0/"" when Provider is nil).

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +56
statusBadge := agent.Status
if w := lipgloss.Width(string(statusBadge)); w > columnWidth.Status {
columnWidth.Status = w
}
gauge := RenderUsageGauge(agent.ContextUsageRate(), colGaugeWidth)
if w := lipgloss.Width(gauge); w > columnWidth.Gauge {
columnWidth.Gauge = w
}
}
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

columnWidth.Gauge is being updated using the rendered gauge string width, but later it is passed back into RenderUsageGauge as the bar width. This causes the gauge to grow (since the rendered string includes brackets/percent), breaking column alignment. Keep a separate fixed bar width (e.g., colGaugeWidth) and compute the gauge column width deterministically, or store bar width in columnWidth.Gauge and never overwrite it with lipgloss.Width(gauge).

Copilot uses AI. Check for mistakes.
@n9te9 n9te9 requested a review from peterj as a code owner March 23, 2026 05:23
@n9te9 n9te9 marked this pull request as draft March 23, 2026 09:13
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.

5 participants