Compare commits

..
4 Commits
Author SHA1 Message Date
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
18 changed files with 97 additions and 23 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
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
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
[MIT](LICENSE)
Licensed under the [MIT License](LICENSE) - Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)
+47 -20
View File
@@ -1,9 +1,13 @@
// Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)
// Licensed under the MIT License. See LICENSE file in project root for details.
package main
import (
"fmt"
"os"
"os/exec"
"runtime"
"sort"
tea "github.com/charmbracelet/bubbletea"
@@ -278,29 +282,52 @@ func (m *fullModel) startTmuxSession() tea.Cmd {
k9sCmdA := fmt.Sprintf("k9s --context %s -n %s", ctxA, m.selectedNamespace)
// Kill stale session first (ignore error if none exists).
exec.Command("tmux", "kill-session", "-t", "kctl").Run()
killErr := exec.Command("tmux", "kill-session", "-t", "kctl").Run()
fmt.Fprintf(os.Stderr, "[debug] selfPath=%s\n", selfPath)
fmt.Fprintf(os.Stderr, "[debug] panelCmd=%s\n", panelCmd)
fmt.Fprintf(os.Stderr, "[debug] kill-session err=%v\n", killErr)
args := []string{
"new-session", "-d", "-s", "kctl",
panelCmd, ";",
"set-option", "-t", "kctl", "remain-on-exit", "on", ";",
"split-window", "-v", "-t", "kctl:0.0", k9sCmdA, ";",
}
if len(m.cfg.Envs) > 1 {
envB := m.cfg.Envs[1]
ctxB := m.cfg.ResolveContext(envB, m.selectedContext)
k9sCmdB := fmt.Sprintf("k9s --context %s -n %s", ctxB, m.selectedNamespace)
args = append(args,
"split-window", "-v", "-t", "kctl:0.1", k9sCmdB, ";",
var c *exec.Cmd
if runtime.GOOS == "windows" {
// On Windows, psmux doesn't handle ; separators when args are
// passed individually via exec.Command. Build the full command
// string and run it through cmd.exe.
tmuxScript := fmt.Sprintf(
"tmux new-session -d -s kctl %q ; set-option -t kctl remain-on-exit on ; split-window -v -t kctl:0.0 %q",
panelCmd, k9sCmdA,
)
if len(m.cfg.Envs) > 1 {
envB := m.cfg.Envs[1]
ctxB := m.cfg.ResolveContext(envB, m.selectedContext)
k9sCmdB := fmt.Sprintf("k9s --context %s -n %s", ctxB, m.selectedNamespace)
tmuxScript += fmt.Sprintf(" ; split-window -v -t kctl:0.1 %q", k9sCmdB)
}
tmuxScript += " ; select-layout -t kctl even-vertical ; select-pane -t kctl:0.0 ; attach -t kctl"
fmt.Fprintf(os.Stderr, "[debug] windows cmd: %s\n", tmuxScript)
c = exec.Command("cmd.exe", "/c", tmuxScript)
} else {
args := []string{
"new-session", "-d", "-s", "kctl",
panelCmd, ";",
"set-option", "-t", "kctl", "remain-on-exit", "on", ";",
"split-window", "-v", "-t", "kctl:0.0", k9sCmdA, ";",
}
if len(m.cfg.Envs) > 1 {
envB := m.cfg.Envs[1]
ctxB := m.cfg.ResolveContext(envB, m.selectedContext)
k9sCmdB := fmt.Sprintf("k9s --context %s -n %s", ctxB, m.selectedNamespace)
args = append(args,
"split-window", "-v", "-t", "kctl:0.1", k9sCmdB, ";",
)
}
args = append(args,
"select-layout", "-t", "kctl", "even-vertical", ";",
"select-pane", "-t", "kctl:0.0", ";",
"attach", "-t", "kctl",
)
fmt.Fprintf(os.Stderr, "[debug] unix args: %v\n", args)
c = exec.Command("tmux", args...)
}
args = append(args,
"select-layout", "-t", "kctl", "even-vertical", ";",
"select-pane", "-t", "kctl:0.0", ";",
"attach", "-t", "kctl",
)
c := exec.Command("tmux", args...)
return tea.ExecProcess(c, func(err error) tea.Msg {
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
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
// 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
import (
@@ -37,10 +40,13 @@ func main() {
kubeexec.SetVerbose(true, os.Stderr)
}
if showHelp || len(filtered) == 0 {
if showHelp {
printUsage()
return
}
if len(filtered) == 0 {
// No subcommand — start the full TUI.
}
if len(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
import (
+2
View File
@@ -1,4 +1,6 @@
#!/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.
# Downloads the latest GitHub release binary matching the current OS/arch
# 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
// configuration (contexts, envs, templates) from a YAML file.
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
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
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
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
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
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
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
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.
// All functions here have side effects (they run external processes) and
// 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
import (