Skip to content
This repository was archived by the owner on Oct 6, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ link:
@ln -sf $(shell pwd)/$(BINARY_NAME) $(PLUGIN_DIR)/$(PLUGIN_NAME)
@echo "Link created: $(PLUGIN_DIR)/$(PLUGIN_NAME)"

install: build link

release:
@if [ -z "$(VERSION)" ]; then \
echo "Error: VERSION parameter is required. Use: make release VERSION=x.y.z"; \
Expand Down
8 changes: 1 addition & 7 deletions commands/completion/functions.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package completion

import (
"encoding/json"

"github.com/docker/model-cli/desktop"
"github.com/spf13/cobra"
)
Expand All @@ -17,14 +15,10 @@ func ModelNames(desktopClient *desktop.Client, limit int) cobra.CompletionFunc {
if limit > 0 && len(args) >= limit {
return nil, cobra.ShellCompDirectiveNoFileComp
}
modelsString, err := desktopClient.List(true, false, false, "")
models, err := desktopClient.List()
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
var models []desktop.Model
if err := json.Unmarshal([]byte(modelsString), &models); err != nil {
return nil, cobra.ShellCompDirectiveError
}
var names []string
for _, m := range models {
names = append(names, m.Tags...)
Expand Down
23 changes: 23 additions & 0 deletions commands/formatter/json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package formatter

import (
"bytes"
"encoding/json"
)

const standardIndentation = " "

// ToStandardJSON return a string with the JSON representation of the interface{}
func ToStandardJSON(i interface{}) (string, error) {
return ToJSON(i, "", standardIndentation)
}

// ToJSON return a string with the JSON representation of the interface{}
func ToJSON(i interface{}, prefix string, indentation string) (string, error) {
buffer := &bytes.Buffer{}
encoder := json.NewEncoder(buffer)
encoder.SetEscapeHTML(false)
encoder.SetIndent(prefix, indentation)
err := encoder.Encode(i)
return buffer.String(), err
}
28 changes: 22 additions & 6 deletions commands/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package commands

import (
"fmt"

"github.com/docker/model-cli/commands/completion"
"github.com/docker/model-cli/commands/formatter"
"github.com/docker/model-cli/desktop"
"github.com/spf13/cobra"
)
Expand All @@ -24,17 +24,33 @@ func newInspectCmd(desktopClient *desktop.Client) *cobra.Command {
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
model := args[0]
model, err := desktopClient.List(false, openai, false, model)
inspectedModel, err := inspectModel(args, openai, desktopClient)
if err != nil {
err = handleClientError(err, "Failed to list models")
return handleNotRunningError(err)
return err
}
cmd.Println(model)
cmd.Print(inspectedModel)
return nil
},
ValidArgsFunction: completion.ModelNames(desktopClient, 1),
}
c.Flags().BoolVar(&openai, "openai", false, "List model in an OpenAI format")
return c
}

func inspectModel(args []string, openai bool, desktopClient *desktop.Client) (string, error) {
modelName := args[0]
if openai {
model, err := desktopClient.InspectOpenAI(modelName)
if err != nil {
err = handleClientError(err, "Failed to get model "+modelName)
return "", handleNotRunningError(err)
}
return formatter.ToStandardJSON(model)
}
model, err := desktopClient.Inspect(modelName)
if err != nil {
err = handleClientError(err, "Failed to get model "+modelName)
return "", handleNotRunningError(err)
}
return formatter.ToStandardJSON(model)
}
99 changes: 96 additions & 3 deletions commands/list.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
package commands

import (
"bytes"
"fmt"
"github.com/docker/go-units"
"github.com/docker/model-cli/commands/completion"
"github.com/docker/model-cli/commands/formatter"
"github.com/docker/model-cli/desktop"
"github.com/olekukonko/tablewriter"
"github.com/spf13/cobra"
"os"
"time"
)

func newListCmd(desktopClient *desktop.Client) *cobra.Command {
Expand All @@ -13,10 +20,12 @@ func newListCmd(desktopClient *desktop.Client) *cobra.Command {
Aliases: []string{"ls"},
Short: "List the available models that can be run with the Docker Model Runner",
RunE: func(cmd *cobra.Command, args []string) error {
models, err := desktopClient.List(jsonFormat, openai, quiet, "")
if openai && quiet {
return fmt.Errorf("--quiet flag cannot be used with --openai flag")
}
models, err := listModels(openai, desktopClient, quiet, jsonFormat)
if err != nil {
err = handleClientError(err, "Failed to list models")
return handleNotRunningError(err)
return err
}
cmd.Print(models)
return nil
Expand All @@ -28,3 +37,87 @@ func newListCmd(desktopClient *desktop.Client) *cobra.Command {
c.Flags().BoolVarP(&quiet, "quiet", "q", false, "Only show model IDs")
return c
}

func listModels(openai bool, desktopClient *desktop.Client, quiet bool, jsonFormat bool) (string, error) {
if openai {
models, err := desktopClient.ListOpenAI()
if err != nil {
err = handleClientError(err, "Failed to list models")
return "", handleNotRunningError(err)
}
return formatter.ToStandardJSON(models)
}
models, err := desktopClient.List()
if err != nil {
err = handleClientError(err, "Failed to list models")
return "", handleNotRunningError(err)
}
if jsonFormat {
return formatter.ToStandardJSON(models)
}
if quiet {
var modelIDs string
for _, m := range models {
if len(m.ID) < 19 {
fmt.Fprintf(os.Stderr, "invalid image ID for model: %v\n", m)
continue
}
modelIDs += fmt.Sprintf("%s\n", m.ID[7:19])
}
return modelIDs, nil
}
return prettyPrintModels(models), nil
}

func prettyPrintModels(models []desktop.Model) string {
var buf bytes.Buffer
table := tablewriter.NewWriter(&buf)

table.SetHeader([]string{"MODEL NAME", "PARAMETERS", "QUANTIZATION", "ARCHITECTURE", "MODEL ID", "CREATED", "SIZE"})

table.SetBorder(false)
table.SetColumnSeparator("")
table.SetHeaderLine(false)
table.SetTablePadding(" ")
table.SetNoWhiteSpace(true)

table.SetColumnAlignment([]int{
tablewriter.ALIGN_LEFT, // MODEL
tablewriter.ALIGN_LEFT, // PARAMETERS
tablewriter.ALIGN_LEFT, // QUANTIZATION
tablewriter.ALIGN_LEFT, // ARCHITECTURE
tablewriter.ALIGN_LEFT, // MODEL ID
tablewriter.ALIGN_LEFT, // CREATED
tablewriter.ALIGN_LEFT, // SIZE
})
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)

for _, m := range models {
if len(m.Tags) == 0 {
appendRow(table, "<none>", m)
continue
}
for _, tag := range m.Tags {
appendRow(table, tag, m)
}
}

table.Render()
return buf.String()
}

func appendRow(table *tablewriter.Table, tag string, model desktop.Model) {
if len(model.ID) < 19 {
fmt.Fprintf(os.Stderr, "invalid model ID for model: %v\n", model)
return
}
table.Append([]string{
tag,
model.Config.Parameters,
model.Config.Quantization,
model.Config.Architecture,
model.ID[7:19],
units.HumanDuration(time.Since(time.Unix(model.Created, 0))) + " ago",
model.Config.Size,
})
}
6 changes: 5 additions & 1 deletion commands/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ func newRunCmd(desktopClient *desktop.Client) *cobra.Command {
}
}

if _, err := desktopClient.List(false, false, false, model); err != nil {
modelDetail, err := desktopClient.Inspect(model)
if err != nil {
if !errors.Is(err, desktop.ErrNotFound) {
return handleNotRunningError(handleClientError(err, "Failed to list models"))
}
Expand All @@ -42,6 +43,9 @@ func newRunCmd(desktopClient *desktop.Client) *cobra.Command {
return err
}
}
if model != modelDetail.Tags[0] {
model = modelDetail.Tags[0]
}

if prompt != "" {
if err := desktopClient.Chat(model, prompt); err != nil {
Expand Down
12 changes: 12 additions & 0 deletions desktop/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ type OpenAIChatResponse struct {
} `json:"choices"`
}

type OpenAIModel struct {
ID string `json:"id"`
Object string `json:"object"`
Created int64 `json:"created"`
OwnedBy string `json:"owned_by"`
}

type OpenAIModelList struct {
Object string `json:"object"`
Data []*OpenAIModel `json:"data"`
}

// TODO: To be replaced by the Model struct from pianta's common/pkg/inference/models/api.go.
// (https://github.com/docker/pinata/pull/33331)
type Format string
Expand Down
Loading