From 27c14de257b4a8f662426a52b068855860385357 Mon Sep 17 00:00:00 2001 From: jbogusz-billtech Date: Fri, 3 Nov 2023 00:30:27 +0100 Subject: [PATCH 1/4] #925: Tab Completion --- .gitignore | 1 + README.md | 6 +++ cmd/cmd.go | 118 +++++++++++++++++++++++++++++++++++++++-------------- 3 files changed, 95 insertions(+), 30 deletions(-) diff --git a/.gitignore b/.gitignore index 1e9ab3f4..2f14cbf6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .DS_Store .vscode +.idea .env .venv .swp diff --git a/README.md b/README.md index 84996372..ba64e3cb 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,12 @@ curl https://ollama.ai/install.sh | sh The official [Ollama Docker image `ollama/ollama`](https://hub.docker.com/r/ollama/ollama) is available on Docker Hub. +### Autocompletion +To enable autocompletion generate autocompletion script for your shell and `source` generated file in your shell config (`~/.bash_profile`, `~/.zshrc`, `~/.profile`, `~/.bashrc`, etc.): +``` +ollama completions [bash|zsh|fish] /path/to/completion/file +``` + ## Quickstart To run and chat with [Llama 2](https://ollama.ai/library/llama2): diff --git a/cmd/cmd.go b/cmd/cmd.go index 10e6b6a9..58760330 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -832,6 +832,48 @@ func checkServerHeartbeat(_ *cobra.Command, _ []string) error { return nil } +func GenerateCompletionsHandler(cmd *cobra.Command, args []string) error { + file, err := os.OpenFile(args[1], os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return err + } + switch args[0] { + case "bash": + err = cmd.Root().GenBashCompletion(file) + case "zsh": + err = cmd.Root().GenZshCompletion(file) + case "fish": + err = cmd.Root().GenFishCompletion(file, true) + } + if err != nil { + fmt.Fprintf(os.Stderr, "error: %s\n", err) + } + return nil +} + +func autocompleteModelName(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) { + client, err := api.ClientFromEnvironment() + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + models, err := client.List(context.Background()) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + var data []string + + for _, m := range models.Models { + log.Printf("model: %s", m.Name) + if strings.HasPrefix(m.Name, toComplete) { + data = append(data, m.Name[:strings.IndexByte(m.Name, ':')]) + } + } + + return data, cobra.ShellCompDirectiveNoFileComp +} + func NewCLI() *cobra.Command { log.SetFlags(log.LstdFlags | log.Lshortfile) @@ -859,11 +901,12 @@ func NewCLI() *cobra.Command { createCmd.Flags().StringP("file", "f", "Modelfile", "Name of the Modelfile (default \"Modelfile\")") showCmd := &cobra.Command{ - Use: "show MODEL", - Short: "Show information for a model", - Args: cobra.ExactArgs(1), - PreRunE: checkServerHeartbeat, - RunE: ShowHandler, + Use: "show MODEL", + Short: "Show information for a model", + Args: cobra.ExactArgs(1), + PreRunE: checkServerHeartbeat, + RunE: ShowHandler, + ValidArgsFunction: autocompleteModelName, } showCmd.Flags().Bool("license", false, "Show license of a model") @@ -873,11 +916,12 @@ func NewCLI() *cobra.Command { showCmd.Flags().Bool("system", false, "Show system prompt of a model") runCmd := &cobra.Command{ - Use: "run MODEL [PROMPT]", - Short: "Run a model", - Args: cobra.MinimumNArgs(1), - PreRunE: checkServerHeartbeat, - RunE: RunHandler, + Use: "run MODEL [PROMPT]", + Short: "Run a model", + Args: cobra.MinimumNArgs(1), + PreRunE: checkServerHeartbeat, + RunE: RunHandler, + ValidArgsFunction: autocompleteModelName, } runCmd.Flags().Bool("verbose", false, "Show timings for response") @@ -893,21 +937,23 @@ func NewCLI() *cobra.Command { } pullCmd := &cobra.Command{ - Use: "pull MODEL", - Short: "Pull a model from a registry", - Args: cobra.ExactArgs(1), - PreRunE: checkServerHeartbeat, - RunE: PullHandler, + Use: "pull MODEL", + Short: "Pull a model from a registry", + Args: cobra.ExactArgs(1), + PreRunE: checkServerHeartbeat, + RunE: PullHandler, + ValidArgsFunction: autocompleteModelName, } pullCmd.Flags().Bool("insecure", false, "Use an insecure registry") pushCmd := &cobra.Command{ - Use: "push MODEL", - Short: "Push a model to a registry", - Args: cobra.ExactArgs(1), - PreRunE: checkServerHeartbeat, - RunE: PushHandler, + Use: "push MODEL", + Short: "Push a model to a registry", + Args: cobra.ExactArgs(1), + PreRunE: checkServerHeartbeat, + RunE: PushHandler, + ValidArgsFunction: autocompleteModelName, } pushCmd.Flags().Bool("insecure", false, "Use an insecure registry") @@ -921,19 +967,30 @@ func NewCLI() *cobra.Command { } copyCmd := &cobra.Command{ - Use: "cp SOURCE TARGET", - Short: "Copy a model", - Args: cobra.ExactArgs(2), - PreRunE: checkServerHeartbeat, - RunE: CopyHandler, + Use: "cp SOURCE TARGET", + Short: "Copy a model", + Args: cobra.ExactArgs(2), + PreRunE: checkServerHeartbeat, + RunE: CopyHandler, + ValidArgsFunction: autocompleteModelName, } deleteCmd := &cobra.Command{ - Use: "rm MODEL [MODEL...]", - Short: "Remove a model", - Args: cobra.MinimumNArgs(1), - PreRunE: checkServerHeartbeat, - RunE: DeleteHandler, + Use: "rm MODEL [MODEL...]", + Short: "Remove a model", + Args: cobra.MinimumNArgs(1), + PreRunE: checkServerHeartbeat, + RunE: DeleteHandler, + ValidArgsFunction: autocompleteModelName, + } + + generateCompletionsCmd := &cobra.Command{ + Use: "completions [bash|zsh|fish] [PATH]", + Short: "Generate completion scripts", + DisableFlagsInUseLine: true, + Hidden: true, + Args: cobra.ExactArgs(2), + RunE: GenerateCompletionsHandler, } rootCmd.AddCommand( @@ -946,6 +1003,7 @@ func NewCLI() *cobra.Command { listCmd, copyCmd, deleteCmd, + generateCompletionsCmd, ) return rootCmd From a47733d4de85eae5360bcd4f92971e7b1d75e638 Mon Sep 17 00:00:00 2001 From: jbogusz-billtech Date: Fri, 10 Nov 2023 19:16:34 +0100 Subject: [PATCH 2/4] Rename 'generateCompletions' -> 'completion' & write scripts to stdout instead of a file --- README.md | 4 ++-- cmd/cmd.go | 26 +++++++++++++------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index ba64e3cb..09254baf 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,9 @@ The official [Ollama Docker image `ollama/ollama`](https://hub.docker.com/r/olla is available on Docker Hub. ### Autocompletion -To enable autocompletion generate autocompletion script for your shell and `source` generated file in your shell config (`~/.bash_profile`, `~/.zshrc`, `~/.profile`, `~/.bashrc`, etc.): +To enable autocompletion generate completion script for your shell (`bash`, `zsh`, `fish`) and `source` it in your shell config (`~/.bash_profile`, `~/.zshrc`, `~/.profile`, `~/.bashrc`, etc.): ``` -ollama completions [bash|zsh|fish] /path/to/completion/file +echo "source <(./ollama completion bash)" >> ~/.bashrc ``` ## Quickstart diff --git a/cmd/cmd.go b/cmd/cmd.go index 58760330..5ce29173 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -832,18 +832,17 @@ func checkServerHeartbeat(_ *cobra.Command, _ []string) error { return nil } -func GenerateCompletionsHandler(cmd *cobra.Command, args []string) error { - file, err := os.OpenFile(args[1], os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) - if err != nil { - return err - } +func completionHandler(cmd *cobra.Command, args []string) error { + var err error switch args[0] { case "bash": - err = cmd.Root().GenBashCompletion(file) + err = cmd.Root().GenBashCompletion(os.Stdout) case "zsh": - err = cmd.Root().GenZshCompletion(file) + err = cmd.Root().GenZshCompletion(os.Stdout) case "fish": - err = cmd.Root().GenFishCompletion(file, true) + err = cmd.Root().GenFishCompletion(os.Stdout, true) + default: + err = errors.New("unsupported shell") } if err != nil { fmt.Fprintf(os.Stderr, "error: %s\n", err) @@ -852,6 +851,7 @@ func GenerateCompletionsHandler(cmd *cobra.Command, args []string) error { } func autocompleteModelName(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) { + log.Printf("autocomplete: %s", toComplete) client, err := api.ClientFromEnvironment() if err != nil { return nil, cobra.ShellCompDirectiveError @@ -984,13 +984,13 @@ func NewCLI() *cobra.Command { ValidArgsFunction: autocompleteModelName, } - generateCompletionsCmd := &cobra.Command{ - Use: "completions [bash|zsh|fish] [PATH]", + completionCmd := &cobra.Command{ + Use: "completion [bash|zsh|fish]", Short: "Generate completion scripts", DisableFlagsInUseLine: true, Hidden: true, - Args: cobra.ExactArgs(2), - RunE: GenerateCompletionsHandler, + Args: cobra.ExactArgs(1), + RunE: completionHandler, } rootCmd.AddCommand( @@ -1003,7 +1003,7 @@ func NewCLI() *cobra.Command { listCmd, copyCmd, deleteCmd, - generateCompletionsCmd, + completionCmd, ) return rootCmd From 8c279b784a8e2dccafb424c0ea9aea0dd2651248 Mon Sep 17 00:00:00 2001 From: jbogusz-billtech Date: Fri, 10 Nov 2023 19:34:29 +0100 Subject: [PATCH 3/4] Mention bash-completion package in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 09254baf..be4e66d0 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ is available on Docker Hub. ### Autocompletion To enable autocompletion generate completion script for your shell (`bash`, `zsh`, `fish`) and `source` it in your shell config (`~/.bash_profile`, `~/.zshrc`, `~/.profile`, `~/.bashrc`, etc.): ``` -echo "source <(./ollama completion bash)" >> ~/.bashrc +echo "source <(./ollama completion bash)" >> ~/.bashrc # Required bash-completion package installed ``` ## Quickstart From e9196732014eef2414f844af8c484785bb84ca03 Mon Sep 17 00:00:00 2001 From: jbogusz-billtech Date: Thu, 28 Dec 2023 20:51:33 +0100 Subject: [PATCH 4/4] Disable autocompletion entirely when its not required --- cmd/cmd.go | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/cmd/cmd.go b/cmd/cmd.go index 7004476a..46d00a3d 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -1231,7 +1231,7 @@ func completionHandler(cmd *cobra.Command, args []string) error { case "fish": err = cmd.Root().GenFishCompletion(os.Stdout, true) default: - err = errors.New("unsupported shell") + err = errors.New("unsupported shell. Supported shells: zsh, fish, bash") } if err != nil { fmt.Fprintf(os.Stderr, "error: %s\n", err) @@ -1312,11 +1312,12 @@ func NewCLI() *cobra.Command { rootCmd.Flags().BoolP("version", "v", false, "Show version information") createCmd := &cobra.Command{ - Use: "create MODEL", - Short: "Create a model from a Modelfile", - Args: cobra.ExactArgs(1), - PreRunE: checkServerHeartbeat, - RunE: CreateHandler, + Use: "create MODEL", + Short: "Create a model from a Modelfile", + Args: cobra.ExactArgs(1), + PreRunE: checkServerHeartbeat, + RunE: CreateHandler, + ValidArgsFunction: doNotAutocomplete, } createCmd.Flags().StringP("file", "f", "Modelfile", "Name of the Modelfile (default \"Modelfile\")") @@ -1351,11 +1352,12 @@ func NewCLI() *cobra.Command { runCmd.Flags().String("format", "", "Response format (e.g. json)") serveCmd := &cobra.Command{ - Use: "serve", - Aliases: []string{"start"}, - Short: "Start ollama", - Args: cobra.ExactArgs(0), - RunE: RunServer, + Use: "serve", + Aliases: []string{"start"}, + Short: "Start ollama", + Args: cobra.ExactArgs(0), + RunE: RunServer, + ValidArgsFunction: doNotAutocomplete, } pullCmd := &cobra.Command{ @@ -1381,11 +1383,12 @@ func NewCLI() *cobra.Command { pushCmd.Flags().Bool("insecure", false, "Use an insecure registry") listCmd := &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List models", - PreRunE: checkServerHeartbeat, - RunE: ListHandler, + Use: "list", + Aliases: []string{"ls"}, + Short: "List models", + PreRunE: checkServerHeartbeat, + RunE: ListHandler, + ValidArgsFunction: doNotAutocomplete, } copyCmd := &cobra.Command{ @@ -1413,6 +1416,9 @@ func NewCLI() *cobra.Command { Hidden: true, Args: cobra.ExactArgs(1), RunE: completionHandler, + ValidArgsFunction: func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{"bash", "zsh", "fish"}, cobra.ShellCompDirectiveNoFileComp + }, } rootCmd.AddCommand(