Compare commits

...
7 Commits
Author SHA1 Message Date
stefankoelle 201168e8cd Fix k9s on Windows: --namespace, --command pods
- -n flag not supported on Windows k9s, use --namespace instead
- Start k9s directly in pods view with --command pods
2026-08-09 21:57:24 +02:00
stefankoelle ffbf4341fa Remove debug output from TUI, use verbose-only logging for tmux setup
- Debug fmt.Fprintf calls were always visible in TUI
- Added VerboseLog() to kubeexec package for use by full.go
- All tmux debug output now only shows with --verbose flag
2026-08-09 21:46:25 +02:00
stefankoelle 37be0707ad Fix Windows: run tmux setup commands individually, attach via ExecProcess
psmux on Windows doesn't handle ; separators when args are passed
individually via exec.Command. Instead of chaining commands with ;,
run each setup command (new-session, set-option, split-window, etc.)
as individual exec.Command calls. Only tmux attach uses ExecProcess
so it properly takes over the terminal.
2026-08-09 21:34:46 +02:00
stefankoelle 9e6acdedc2 Fix Windows: use cmd.exe /c for tmux command chain
psmux on Windows doesn't handle ; separators when args are passed
individually via exec.Command. On Windows, build the full tmux
command string and run it through cmd.exe /c instead.
2026-08-09 21:27:37 +02:00
stefankoelle 07662dc9b7 Add debug logging to startTmuxSession for Windows troubleshooting 2026-08-09 21:20:27 +02:00
stefankoelle 38b28f64fb Fix: no-args should start TUI, not print help
The condition showHelp||len(filtered)==0 caused the tool to always
print usage and exit. Now only --help triggers usage+exit.
2026-08-09 21:00:45 +02:00
stefankoelle 6a749811c7 LICENSE 2026-08-09 20:59:04 +02:00
19 changed files with 92 additions and 22 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2026 Stefan Koelle Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+1 -1
View File
@@ -227,4 +227,4 @@ and untested, since it has no meaningful behavior without a live cluster.
## License ## License
[MIT](LICENSE) Licensed under the [MIT License](LICENSE) - Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)
+4 -4
View File
@@ -75,9 +75,9 @@ Once start navigation is complete, the tool opens a tmux session with
| -> runs the kctl-tui binary in "panel" mode | | -> runs the kctl-tui binary in "panel" mode |
| -> menu: Redeploy, secrets diff | | -> menu: Redeploy, secrets diff |
+--------------------------------------------------+ +--------------------------------------------------+
| Pane 1 (middle): k9s --context <context-a> -n <ns>| | Pane 1 (middle): k9s --context <context-a> --namespace <ns> --command pods|
+--------------------------------------------------+ +--------------------------------------------------+
| Pane 2 (bottom): k9s --context <context-b> -n <ns>| | Pane 2 (bottom): k9s --context <context-b> --namespace <ns> --command pods|
+--------------------------------------------------+ +--------------------------------------------------+
``` ```
@@ -94,8 +94,8 @@ tmux kill-session -t kctl
tmux new-session -d -s kctl \ tmux new-session -d -s kctl \
"kctl-tui panel --context=$CTX_A --ns=$NS --team=$TEAM" \; \ "kctl-tui panel --context=$CTX_A --ns=$NS --team=$TEAM" \; \
set-option -t kctl remain-on-exit on \; \ set-option -t kctl remain-on-exit on \; \
split-window -v -t kctl:0.0 "k9s --context $CTX_A -n $NS" \; \ split-window -v -t kctl:0.0 "k9s --context $CTX_A --namespace $NS --command pods" \; \
split-window -v -t kctl:0.1 "k9s --context $CTX_B -n $NS" \; \ split-window -v -t kctl:0.1 "k9s --context $CTX_B --namespace $NS --command pods" \; \
select-layout -t kctl even-vertical \; \ select-layout -t kctl even-vertical \; \
select-pane -t kctl:0.0 \; \ select-pane -t kctl:0.0 \; \
attach -t kctl attach -t kctl
+28 -15
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)
// Licensed under the MIT License. See LICENSE file in project root for details.
package main package main
import ( import (
@@ -275,33 +278,43 @@ func (m *fullModel) startTmuxSession() tea.Cmd {
envA := m.cfg.Envs[0] envA := m.cfg.Envs[0]
ctxA := m.cfg.ResolveContext(envA, m.selectedContext) ctxA := m.cfg.ResolveContext(envA, m.selectedContext)
k9sCmdA := fmt.Sprintf("k9s --context %s -n %s", ctxA, m.selectedNamespace) k9sCmdA := fmt.Sprintf("k9s --context %s --namespace %s --command pods", ctxA, m.selectedNamespace)
kubeexec.VerboseLog("[debug] selfPath=%s\n", selfPath)
kubeexec.VerboseLog("[debug] panelCmd=%s\n", panelCmd)
kubeexec.VerboseLog("[debug] k9sCmdA=%s\n", k9sCmdA)
// Kill stale session first (ignore error if none exists). // Kill stale session first (ignore error if none exists).
exec.Command("tmux", "kill-session", "-t", "kctl").Run() exec.Command("tmux", "kill-session", "-t", "kctl").Run()
args := []string{ // Run setup commands individually — this avoids the ; separator
"new-session", "-d", "-s", "kctl", // issue on Windows where psmux doesn't handle chained args.
panelCmd, ";", setup := [][]string{
"set-option", "-t", "kctl", "remain-on-exit", "on", ";", {"new-session", "-d", "-s", "kctl", panelCmd},
"split-window", "-v", "-t", "kctl:0.0", k9sCmdA, ";", {"set-option", "-t", "kctl", "remain-on-exit", "on"},
{"split-window", "-v", "-t", "kctl:0.0", k9sCmdA},
} }
if len(m.cfg.Envs) > 1 { if len(m.cfg.Envs) > 1 {
envB := m.cfg.Envs[1] envB := m.cfg.Envs[1]
ctxB := m.cfg.ResolveContext(envB, m.selectedContext) ctxB := m.cfg.ResolveContext(envB, m.selectedContext)
k9sCmdB := fmt.Sprintf("k9s --context %s -n %s", ctxB, m.selectedNamespace) k9sCmdB := fmt.Sprintf("k9s --context %s --namespace %s --command pods", ctxB, m.selectedNamespace)
args = append(args, kubeexec.VerboseLog("[debug] k9sCmdB=%s\n", k9sCmdB)
"split-window", "-v", "-t", "kctl:0.1", k9sCmdB, ";", setup = append(setup, []string{"split-window", "-v", "-t", "kctl:0.1", k9sCmdB})
)
} }
args = append(args, setup = append(setup,
"select-layout", "-t", "kctl", "even-vertical", ";", []string{"select-layout", "-t", "kctl", "even-vertical"},
"select-pane", "-t", "kctl:0.0", ";", []string{"select-pane", "-t", "kctl:0.0"},
"attach", "-t", "kctl",
) )
c := exec.Command("tmux", args...) for _, args := range setup {
if out, err := exec.Command("tmux", args...).CombinedOutput(); err != nil {
kubeexec.VerboseLog("[debug] tmux %s failed: %v\n%s\n", args[0], err, out)
return func() tea.Msg { return tmuxDoneMsg{err: fmt.Errorf("tmux %s: %w", args[0], err)} }
}
}
// Only attach uses tea.ExecProcess so it takes over the terminal.
c := exec.Command("tmux", "attach", "-t", "kctl")
return tea.ExecProcess(c, func(err error) tea.Msg { return tea.ExecProcess(c, func(err error) tea.Msg {
return tmuxDoneMsg{err: err} return tmuxDoneMsg{err: err}
}) })
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)
// Licensed under the MIT License. See LICENSE file in project root for details.
package main package main
import "github.com/skoelle/kctl-tui/internal/kctl" import "github.com/skoelle/kctl-tui/internal/kctl"
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)
// Licensed under the MIT License. See LICENSE file in project root for details.
package main package main
// simpleItem is a minimal implementation of list.Item used for all // simpleItem is a minimal implementation of list.Item used for all
+7 -1
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)
// Licensed under the MIT License. See LICENSE file in project root for details.
package main package main
import ( import (
@@ -37,10 +40,13 @@ func main() {
kubeexec.SetVerbose(true, os.Stderr) kubeexec.SetVerbose(true, os.Stderr)
} }
if showHelp || len(filtered) == 0 { if showHelp {
printUsage() printUsage()
return return
} }
if len(filtered) == 0 {
// No subcommand — start the full TUI.
}
if len(filtered) > 0 { if len(filtered) > 0 {
switch filtered[0] { switch filtered[0] {
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)
// Licensed under the MIT License. See LICENSE file in project root for details.
package main package main
import ( import (
+2
View File
@@ -1,4 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)
# Licensed under the MIT License. See LICENSE file in project root for details.
# Install script for kctl-tui. # Install script for kctl-tui.
# Downloads the latest GitHub release binary matching the current OS/arch # Downloads the latest GitHub release binary matching the current OS/arch
# and installs it to /usr/local/bin (or $INSTALL_DIR if set). # and installs it to /usr/local/bin (or $INSTALL_DIR if set).
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)
// Licensed under the MIT License. See LICENSE file in project root for details.
// Package config loads the user-specific, non-versioned kctl-tui // Package config loads the user-specific, non-versioned kctl-tui
// configuration (contexts, envs, templates) from a YAML file. // configuration (contexts, envs, templates) from a YAML file.
package config package config
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)
// Licensed under the MIT License. See LICENSE file in project root for details.
package config package config
import ( import (
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)
// Licensed under the MIT License. See LICENSE file in project root for details.
package kctl package kctl
import "sort" import "sort"
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)
// Licensed under the MIT License. See LICENSE file in project root for details.
package kctl package kctl
import "testing" import "testing"
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)
// Licensed under the MIT License. See LICENSE file in project root for details.
package kctl package kctl
import "sort" import "sort"
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)
// Licensed under the MIT License. See LICENSE file in project root for details.
package kctl package kctl
import ( import (
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)
// Licensed under the MIT License. See LICENSE file in project root for details.
package kctl package kctl
import "strings" import "strings"
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)
// Licensed under the MIT License. See LICENSE file in project root for details.
package kctl package kctl
import "testing" import "testing"
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)
// Licensed under the MIT License. See LICENSE file in project root for details.
// Package kubeexec wraps kubectl/aws-cli invocations used by kctl-tui. // Package kubeexec wraps kubectl/aws-cli invocations used by kctl-tui.
// All functions here have side effects (they run external processes) and // All functions here have side effects (they run external processes) and
// are therefore not covered by unit tests; the pure logic they depend on // are therefore not covered by unit tests; the pure logic they depend on
+13
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)
// Licensed under the MIT License. See LICENSE file in project root for details.
package kubeexec package kubeexec
import ( import (
@@ -26,6 +29,16 @@ func SetVerbose(enabled bool, w io.Writer) {
} }
} }
// VerboseLog writes a message to the verbose log if enabled.
func VerboseLog(format string, args ...interface{}) {
mu.Lock()
defer mu.Unlock()
if !verbose {
return
}
fmt.Fprintf(logOut, format, args...)
}
func logCmd(name string, args ...string) { func logCmd(name string, args ...string) {
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()