Skip to content
This repository was archived by the owner on Oct 6, 2025. It is now read-only.

Commit fd5ec6e

Browse files
authored
Merge pull request #150 from doringeman/ls-completion
feat(list): filter based on model arg
2 parents 89e512d + 9037652 commit fd5ec6e

File tree

2 files changed

+83
-3
lines changed

2 files changed

+83
-3
lines changed

commands/completion/functions.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package completion
22

33
import (
4+
"strings"
5+
46
"github.com/docker/model-cli/desktop"
57
"github.com/spf13/cobra"
68
)
@@ -31,3 +33,56 @@ func ModelNames(desktopClient func() *desktop.Client, limit int) cobra.Completio
3133
return names, cobra.ShellCompDirectiveNoFileComp
3234
}
3335
}
36+
37+
// ModelNamesAndTags offers completion that matches the base model name along with its tags.
38+
// If the model has multiple tags, match both the base model name and each tag.
39+
func ModelNamesAndTags(desktopClient func() *desktop.Client, limit int) cobra.CompletionFunc {
40+
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
41+
// HACK: Invoke rootCmd's PersistentPreRunE, which is needed for context
42+
// detection and client initialization. This function isn't invoked
43+
// automatically on autocompletion paths.
44+
cmd.Parent().PersistentPreRunE(cmd, args)
45+
46+
if limit > 0 && len(args) >= limit {
47+
return nil, cobra.ShellCompDirectiveNoFileComp
48+
}
49+
50+
models, err := desktopClient().List()
51+
if err != nil {
52+
return nil, cobra.ShellCompDirectiveError
53+
}
54+
55+
var names []string
56+
57+
modelNames := make(map[string]bool)
58+
modelTags := make(map[string][]string)
59+
60+
for _, m := range models {
61+
for _, tag := range m.Tags {
62+
// Extract model name (everything before the first colon or the full tag if no colon).
63+
modelName, _, _ := strings.Cut(tag, ":")
64+
modelNames[modelName] = true
65+
modelTags[modelName] = append(modelTags[modelName], tag)
66+
}
67+
}
68+
69+
for name := range modelNames {
70+
// If model has multiple tags, suggest the base model name and all specific tags.
71+
if len(modelTags[name]) > 1 {
72+
names = append(names, name)
73+
for _, tag := range modelTags[name] {
74+
names = append(names, tag)
75+
// If this model doesn't have a tag, also add the :latest variant.
76+
if tag == name {
77+
names = append(names, tag+":latest")
78+
}
79+
}
80+
} else {
81+
// If only one tag, just suggest that tag to avoid duplication.
82+
names = append(names, modelTags[name][0])
83+
}
84+
}
85+
86+
return names, cobra.ShellCompDirectiveNoSpace
87+
}
88+
}

commands/list.go

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"bytes"
55
"fmt"
66
"os"
7+
"slices"
8+
"strings"
79
"time"
810

911
"github.com/docker/go-units"
@@ -50,14 +52,18 @@ func newListCmd() *cobra.Command {
5052
if _, err := ensureStandaloneRunnerAvailable(cmd.Context(), standaloneInstallPrinter); err != nil {
5153
return fmt.Errorf("unable to initialize standalone model runner: %w", err)
5254
}
53-
models, err := listModels(openai, backend, desktopClient, quiet, jsonFormat, apiKey)
55+
var modelFilter string
56+
if len(args) > 0 {
57+
modelFilter = args[0]
58+
}
59+
models, err := listModels(openai, backend, desktopClient, quiet, jsonFormat, apiKey, modelFilter)
5460
if err != nil {
5561
return err
5662
}
5763
cmd.Print(models)
5864
return nil
5965
},
60-
ValidArgsFunction: completion.NoComplete,
66+
ValidArgsFunction: completion.ModelNamesAndTags(getDesktopClient, 1),
6167
}
6268
c.Flags().BoolVar(&jsonFormat, "json", false, "List models in a JSON format")
6369
c.Flags().BoolVar(&openai, "openai", false, "List models in an OpenAI format")
@@ -67,7 +73,7 @@ func newListCmd() *cobra.Command {
6773
return c
6874
}
6975

70-
func listModels(openai bool, backend string, desktopClient *desktop.Client, quiet bool, jsonFormat bool, apiKey string) (string, error) {
76+
func listModels(openai bool, backend string, desktopClient *desktop.Client, quiet bool, jsonFormat bool, apiKey string, modelFilter string) (string, error) {
7177
if openai || backend == "openai" {
7278
models, err := desktopClient.ListOpenAI(backend, apiKey)
7379
if err != nil {
@@ -81,6 +87,25 @@ func listModels(openai bool, backend string, desktopClient *desktop.Client, quie
8187
err = handleClientError(err, "Failed to list models")
8288
return "", handleNotRunningError(err)
8389
}
90+
91+
if modelFilter != "" {
92+
var filteredModels []dmrm.Model
93+
for _, m := range models {
94+
hasMatchingTag := false
95+
for _, tag := range m.Tags {
96+
modelName, _, _ := strings.Cut(tag, ":")
97+
if slices.Contains([]string{modelName, tag + ":latest", tag}, modelFilter) {
98+
hasMatchingTag = true
99+
break
100+
}
101+
}
102+
if hasMatchingTag {
103+
filteredModels = append(filteredModels, m)
104+
}
105+
}
106+
models = filteredModels
107+
}
108+
84109
if jsonFormat {
85110
return formatter.ToStandardJSON(models)
86111
}

0 commit comments

Comments
 (0)