feat: netssh completion install — user-space shell completion setup

Adds `netssh completion install` subcommand:
- Auto-detects shell from $SHELL, override with --shell bash|zsh|fish
- bash  → ~/.local/share/bash-completion/completions/netssh
- zsh   → ~/.zfunc/_netssh  (prints fpath hint)
- fish  → ~/.config/fish/completions/netssh.fish
- No sudo required

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Sebastian Unterschütz
2026-05-23 13:28:43 +02:00
parent a4fa33d224
commit da3a280a43
+75 -1
View File
@@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"os" "os"
"path/filepath"
"sort" "sort"
"strings" "strings"
"text/tabwriter" "text/tabwriter"
@@ -207,11 +208,84 @@ func rootCmd() *cobra.Command {
}, },
} }
// cobra automatically adds a "completion" subcommand // cobra automatically adds a "completion" subcommand; we extend it with "install".
root.AddCommand(configureCmd(), searchCmd(), cacheCmd()) root.AddCommand(configureCmd(), searchCmd(), cacheCmd())
for _, cmd := range root.Commands() {
if cmd.Name() == "completion" {
cmd.AddCommand(completionInstallCmd(root))
break
}
}
return root return root
} }
func completionInstallCmd(root *cobra.Command) *cobra.Command {
var shell string
cmd := &cobra.Command{
Use: "install",
Short: "Install shell completion for the current user (no sudo required)",
RunE: func(cmd *cobra.Command, args []string) error {
if shell == "" {
shell = filepath.Base(os.Getenv("SHELL"))
}
var (
dir string
file string
gen func() ([]byte, error)
note string
)
switch shell {
case "bash":
dir = filepath.Join(os.Getenv("HOME"), ".local", "share", "bash-completion", "completions")
file = filepath.Join(dir, "netssh")
gen = func() ([]byte, error) {
var buf strings.Builder
return []byte(buf.String()), root.GenBashCompletionV2(&buf, true)
}
note = "Reload your shell or run: source " + file
case "zsh":
dir = filepath.Join(os.Getenv("HOME"), ".zfunc")
file = filepath.Join(dir, "_netssh")
gen = func() ([]byte, error) {
var buf strings.Builder
return []byte(buf.String()), root.GenZshCompletion(&buf)
}
note = "Make sure ~/.zfunc is in your fpath:\n fpath=(~/.zfunc $fpath)\n autoload -Uz compinit && compinit"
case "fish":
configDir, _ := os.UserConfigDir()
dir = filepath.Join(configDir, "fish", "completions")
file = filepath.Join(dir, "netssh.fish")
gen = func() ([]byte, error) {
var buf strings.Builder
return []byte(buf.String()), root.GenFishCompletion(&buf, true)
}
note = "Reload your shell or start a new fish session."
default:
return fmt.Errorf("unsupported shell %q — use --shell bash|zsh|fish", shell)
}
script, err := gen()
if err != nil {
return fmt.Errorf("generating completion: %w", err)
}
if err := os.MkdirAll(dir, 0o755); err != nil {
return fmt.Errorf("creating %s: %w", dir, err)
}
if err := os.WriteFile(file, script, 0o644); err != nil {
return fmt.Errorf("writing %s: %w", file, err)
}
fmt.Printf("Completion installed → %s\n%s\n", file, note)
return nil
},
}
cmd.Flags().StringVar(&shell, "shell", "", "Shell to install for (default: $SHELL). Supported: bash, zsh, fish")
return cmd
}
func configureCmd() *cobra.Command { func configureCmd() *cobra.Command {
return &cobra.Command{ return &cobra.Command{
Use: "configure", Use: "configure",