From da3a280a43c2997228d4fb19df8ba934a2e376a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Untersch=C3=BCtz?= Date: Sat, 23 May 2026 13:28:43 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20netssh=20completion=20install=20?= =?UTF-8?q?=E2=80=94=20user-space=20shell=20completion=20setup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- cmd/netssh/main.go | 76 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/cmd/netssh/main.go b/cmd/netssh/main.go index a222273..47adbc6 100644 --- a/cmd/netssh/main.go +++ b/cmd/netssh/main.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "os" + "path/filepath" "sort" "strings" "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()) + + for _, cmd := range root.Commands() { + if cmd.Name() == "completion" { + cmd.AddCommand(completionInstallCmd(root)) + break + } + } + 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 { return &cobra.Command{ Use: "configure",