Compare commits

..
6 Commits
Author SHA1 Message Date
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
stefankoelle 313dffcbbf Fix Windows: remove -- separator (psmux incompatible), add unknown command error
- Remove -- separator from tmux args (psmux on Windows doesn't support it)
- Unknown subcommands now show error + usage (exit 1) instead of starting TUI
- --help now always shows usage and exits (no fallthrough to TUI)
2026-08-09 20:56:39 +02:00
19 changed files with 83 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)
+3 -3
View File
@@ -92,10 +92,10 @@ Example startup command (generic placeholders):
# Kill stale session first (separate command — tmux aborts on kill-session error). # Kill stale session first (separate command — tmux aborts on kill-session error).
tmux kill-session -t kctl 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 -n $NS" \; \
split-window -v -t kctl:0.1 -- "k9s --context $CTX_B -n $NS" \; \ split-window -v -t kctl:0.1 "k9s --context $CTX_B -n $NS" \; \
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
+25 -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 main package main
import ( import (
@@ -277,31 +280,40 @@ func (m *fullModel) startTmuxSession() tea.Cmd {
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 -n %s", ctxA, m.selectedNamespace)
fmt.Fprintf(os.Stderr, "[debug] selfPath=%s\n", selfPath)
fmt.Fprintf(os.Stderr, "[debug] panelCmd=%s\n", panelCmd)
// 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 -n %s", ctxB, m.selectedNamespace)
args = append(args, setup = append(setup, []string{"split-window", "-v", "-t", "kctl:0.1", k9sCmdB})
"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 {
fmt.Fprintf(os.Stderr, "[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.
fmt.Fprintf(os.Stderr, "[debug] tmux attach -t kctl\n")
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
+12 -4
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,11 +40,12 @@ func main() {
kubeexec.SetVerbose(true, os.Stderr) kubeexec.SetVerbose(true, os.Stderr)
} }
if showHelp || len(filtered) == 0 { if showHelp {
printUsage() printUsage()
if showHelp && len(filtered) == 0 { return
return }
} if len(filtered) == 0 {
// No subcommand — start the full TUI.
} }
if len(filtered) > 0 { if len(filtered) > 0 {
@@ -64,6 +68,10 @@ func main() {
os.Exit(1) os.Exit(1)
} }
return return
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n\n", filtered[0])
printUsage()
os.Exit(1)
} }
} }
+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
+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 package kubeexec
import ( import (