Skip to content

Add completion for plugins#1664

Open
oncilla wants to merge 1 commit into
smallstep:masterfrom
oncilla:plugin-completion
Open

Add completion for plugins#1664
oncilla wants to merge 1 commit into
smallstep:masterfrom
oncilla:plugin-completion

Conversation

@oncilla

@oncilla oncilla commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Name of feature:

Completion for plugins (step--plugin) that are discovered at runtime.

Pain or issue this feature alleviates:

Right now, step kms <TAB> does not have completion.

Is there documentation on how to use this feature? If so, where?

No documentation necessary. It will just work once shell completion is installed.

In what environments or workflows is this feature supported?

Linux, MacOS, Windows

In what environments or workflows is this feature explicitly NOT supported (if any)?

None

Description

The completion now also includes plugins. Completion requests are forwarded to the plugin. If the plugin reacts appropriately to --generate-bash-completion, completion is also available for the sub-commands. Otherwise, we fallback to default bash completion based on files in cwd.

Here is an example how a cobra-based plugin could provide completion in step:
(If interested, I could extend step-kms-plugin as well)

func main() {
	cmd := &cobra.Command{
		Use: "example",
	}
	cmd.AddCommand(func() *cobra.Command {
		var world string
		cmd := &cobra.Command{
			Use: "hello",
			Run: func(_ *cobra.Command, _ []string) {
				fmt.Println("world:", world)
			},
		}
		cmd.Flags().StringVar(&world, "world", "world", "World")
		return cmd
	}())

	maybeCompletion(cmd)
	if err := cmd.Execute(); err != nil {
		os.Exit(1)
	}
}

func maybeCompletion(cmd *cobra.Command) {
	args := os.Args[1:]
	if n := len(args); n > 0 && args[n-1] == "--generate-bash-completion" {
		args = args[:n-1]
		// step's scripts include the partial word only if it starts with "-"
		toComplete := ""
		if len(args) > 0 && strings.HasPrefix(args[len(args)-1], "-") {
			toComplete, args = args[len(args)-1], args[:len(args)-1]
		}
		var buf bytes.Buffer
		cmd.SetOut(&buf)
		cmd.SetArgs(append(append([]string{cobra.ShellCompRequestCmd}, args...), toComplete))
		_ = cmd.Execute()
		for _, line := range strings.Split(buf.String(), "\n") {
			if line == "" || strings.HasPrefix(line, ":") { // directive line, e.g. ":4"
				continue
			}
			// cobra emits "candidate\tdescription"; step's compgen wants bare words
			fmt.Println(strings.SplitN(line, "\t", 2)[0])
		}
		return
	}
}

Available plugins are now made available in the tab completion.

The completion request is forwarded to the plugin. If the plugin reacts
appropriately to `--generate-bash-completion`, completion is also
available for the sub-commands.

Example using cobra:

```
func main() {
	cmd := &cobra.Command{
		Use: "example",
	}
	cmd.AddCommand(func() *cobra.Command {
		var world string
		cmd := &cobra.Command{
			Use: "hello",
			Run: func(_ *cobra.Command, _ []string) {
				fmt.Println("world:", world)
			},
		}
		cmd.Flags().StringVar(&world, "world", "world", "World")
		return cmd
	}())

	maybeCompletion(cmd)
	if err := cmd.Execute(); err != nil {
		os.Exit(1)
	}
}

func maybeCompletion(cmd *cobra.Command) {
	args := os.Args[1:]
	if n := len(args); n > 0 && args[n-1] == "--generate-bash-completion" {
		args = args[:n-1]
		// step's scripts include the partial word only if it starts with "-"
		toComplete := ""
		if len(args) > 0 && strings.HasPrefix(args[len(args)-1], "-") {
			toComplete, args = args[len(args)-1], args[:len(args)-1]
		}
		var buf bytes.Buffer
		cmd.SetOut(&buf)
		cmd.SetArgs(append(append([]string{cobra.ShellCompRequestCmd}, args...), toComplete))
		_ = cmd.Execute()
		for _, line := range strings.Split(buf.String(), "\n") {
			if line == "" || strings.HasPrefix(line, ":") { // directive line, e.g. ":4"
				continue
			}
			// cobra emits "candidate\tdescription"; step's compgen wants bare words
			fmt.Println(strings.SplitN(line, "\t", 2)[0])
		}
		return
	}
}
````
@github-actions github-actions Bot added the needs triage Waiting for discussion / prioritization by team label Jul 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs triage Waiting for discussion / prioritization by team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants