diff --git a/cmd/cli/commands/bench.go b/cmd/cli/commands/bench.go index f5a90c23..c8b9a546 100644 --- a/cmd/cli/commands/bench.go +++ b/cmd/cli/commands/bench.go @@ -83,11 +83,6 @@ measuring the tokens per second (TPS) that the model can generate.`, return handleClientError(err, "Failed to inspect model") } - // Ensure model runner is available - if _, err := ensureStandaloneRunnerAvailable(cmd.Context(), asPrinter(cmd), false); err != nil { - return fmt.Errorf("unable to initialize standalone model runner: %w", err) - } - if !jsonOutput { fmt.Printf("Prompt: %s\n", prompt) fmt.Printf("Duration: %v per concurrency level\n", duration) diff --git a/cmd/cli/commands/compose.go b/cmd/cli/commands/compose.go index d71ebef4..fc2a44e4 100644 --- a/cmd/cli/commands/compose.go +++ b/cmd/cli/commands/compose.go @@ -53,13 +53,13 @@ func newUpCommand() *cobra.Command { sendInfo("Initializing model runner...") kind := modelRunner.EngineKind() - standalone, err := ensureStandaloneRunnerAvailable(cmd.Context(), nil, false) + runner, err := getStandaloneRunner(cmd.Context()) if err != nil { - _ = sendErrorf("Failed to initialize standalone model runner: %v", err) - return fmt.Errorf("Failed to initialize standalone model runner: %w", err) + _ = sendErrorf("Failed to get standalone model runner info: %v", err) + return fmt.Errorf("Failed to get standalone model runner info: %w", err) } else if ((kind == types.ModelRunnerEngineKindMoby || kind == types.ModelRunnerEngineKindCloud) && - standalone == nil) || - (standalone != nil && (standalone.gatewayIP == "" || standalone.gatewayPort == 0)) { + runner == nil) || + (runner != nil && (runner.gatewayIP == "" || runner.gatewayPort == 0)) { return errors.New("unable to determine standalone runner endpoint") } @@ -110,7 +110,7 @@ func newUpCommand() *cobra.Command { case types.ModelRunnerEngineKindCloud: fallthrough case types.ModelRunnerEngineKindMoby: - _ = setenv("URL", "http://"+net.JoinHostPort(standalone.gatewayIP, strconv.Itoa(int(standalone.gatewayPort)))+"/engines/v1/") + _ = setenv("URL", "http://"+net.JoinHostPort(runner.gatewayIP, strconv.Itoa(int(runner.gatewayPort)))+"/engines/v1/") default: return fmt.Errorf("unhandled engine kind: %v", kind) } diff --git a/cmd/cli/commands/inspect.go b/cmd/cli/commands/inspect.go index 3d715c84..ff877398 100644 --- a/cmd/cli/commands/inspect.go +++ b/cmd/cli/commands/inspect.go @@ -18,9 +18,6 @@ func newInspectCmd() *cobra.Command { Short: "Display detailed information on one model", Args: requireExactArgs(1, "inspect", "MODEL"), RunE: func(cmd *cobra.Command, args []string) error { - if _, err := ensureStandaloneRunnerAvailable(cmd.Context(), asPrinter(cmd), false); err != nil { - return fmt.Errorf("unable to initialize standalone model runner: %w", err) - } if openai && remote { return fmt.Errorf("--remote flag cannot be used with --openai flag") } diff --git a/cmd/cli/commands/install-runner.go b/cmd/cli/commands/install-runner.go index ef9a1363..597ffd39 100644 --- a/cmd/cli/commands/install-runner.go +++ b/cmd/cli/commands/install-runner.go @@ -163,6 +163,53 @@ func ensureStandaloneRunnerAvailable(ctx context.Context, printer standalone.Sta return inspectStandaloneRunner(container), nil } +// withStandaloneRunner wraps a command's RunE to ensure the standalone runner +// is available before executing the command. This is a no-op in unsupported +// contexts (e.g., Docker Desktop) or if automatic installations have been disabled. +func withStandaloneRunner(cmd *cobra.Command) *cobra.Command { + if cmd.RunE == nil { + return cmd + } + originalRunE := cmd.RunE + cmd.RunE = func(cmd *cobra.Command, args []string) error { + if _, err := ensureStandaloneRunnerAvailable(cmd.Context(), asPrinter(cmd), false); err != nil { + return fmt.Errorf("unable to initialize standalone model runner: %w", err) + } + return originalRunE(cmd, args) + } + return cmd +} + +// getStandaloneRunner returns the standalone runner info by finding the controller container. +// This is useful for commands that need runner details after withStandaloneRunner has run. +// Returns nil for non-standalone contexts (e.g., Docker Desktop). +func getStandaloneRunner(ctx context.Context) (*standaloneRunner, error) { + // Only standalone contexts have a runner container to inspect. + engineKind := modelRunner.EngineKind() + standaloneSupported := engineKind == types.ModelRunnerEngineKindMoby || + engineKind == types.ModelRunnerEngineKindCloud + if !standaloneSupported { + return nil, nil + } + + if dockerCLI == nil { + return nil, nil + } + + dockerClient, err := desktop.DockerClientForContext(dockerCLI, dockerCLI.CurrentContext()) + if err != nil { + return nil, fmt.Errorf("failed to create Docker client: %w", err) + } + containerID, _, ctr, err := standalone.FindControllerContainer(ctx, dockerClient) + if err != nil { + return nil, fmt.Errorf("unable to find standalone model runner: %w", err) + } + if containerID == "" { + return nil, nil + } + return inspectStandaloneRunner(ctr), nil +} + // runnerOptions holds common configuration for install/start/reinstall commands type runnerOptions struct { port uint16 diff --git a/cmd/cli/commands/pull.go b/cmd/cli/commands/pull.go index 09d8474b..6de1676f 100644 --- a/cmd/cli/commands/pull.go +++ b/cmd/cli/commands/pull.go @@ -1,11 +1,8 @@ package commands import ( - "fmt" - "github.com/docker/model-runner/cmd/cli/commands/completion" "github.com/docker/model-runner/cmd/cli/desktop" - "github.com/spf13/cobra" ) @@ -15,9 +12,6 @@ func newPullCmd() *cobra.Command { Short: "Pull a model from Docker Hub or HuggingFace to your local environment", Args: requireExactArgs(1, "pull", "MODEL"), RunE: func(cmd *cobra.Command, args []string) error { - if _, err := ensureStandaloneRunnerAvailable(cmd.Context(), asPrinter(cmd), false); err != nil { - return fmt.Errorf("unable to initialize standalone model runner: %w", err) - } return pullModel(cmd, desktopClient, args[0]) }, ValidArgsFunction: completion.NoComplete, diff --git a/cmd/cli/commands/push.go b/cmd/cli/commands/push.go index 86452d6c..a2c5bcff 100644 --- a/cmd/cli/commands/push.go +++ b/cmd/cli/commands/push.go @@ -1,11 +1,8 @@ package commands import ( - "fmt" - "github.com/docker/model-runner/cmd/cli/commands/completion" "github.com/docker/model-runner/cmd/cli/desktop" - "github.com/spf13/cobra" ) @@ -15,9 +12,6 @@ func newPushCmd() *cobra.Command { Short: "Push a model to Docker Hub", Args: requireExactArgs(1, "push", "MODEL"), RunE: func(cmd *cobra.Command, args []string) error { - if _, err := ensureStandaloneRunnerAvailable(cmd.Context(), asPrinter(cmd), false); err != nil { - return fmt.Errorf("unable to initialize standalone model runner: %w", err) - } return pushModel(cmd, desktopClient, args[0]) }, ValidArgsFunction: completion.NoComplete, diff --git a/cmd/cli/commands/requests.go b/cmd/cli/commands/requests.go index 76175c3b..f5632ca9 100644 --- a/cmd/cli/commands/requests.go +++ b/cmd/cli/commands/requests.go @@ -25,10 +25,6 @@ func newRequestsCmd() *cobra.Command { return nil }, RunE: func(cmd *cobra.Command, args []string) error { - if _, err := ensureStandaloneRunnerAvailable(cmd.Context(), asPrinter(cmd), false); err != nil { - return fmt.Errorf("unable to initialize standalone model runner: %w", err) - } - responseBody, cancel, err := desktopClient.Requests(model, follow, includeExisting) if err != nil { errMsg := "Failed to get requests" diff --git a/cmd/cli/commands/rm.go b/cmd/cli/commands/rm.go index 752b7445..bbcf0d37 100644 --- a/cmd/cli/commands/rm.go +++ b/cmd/cli/commands/rm.go @@ -1,10 +1,7 @@ package commands import ( - "fmt" - "github.com/docker/model-runner/cmd/cli/commands/completion" - "github.com/spf13/cobra" ) @@ -16,9 +13,6 @@ func newRemoveCmd() *cobra.Command { Short: "Remove local models downloaded from Docker Hub", Args: requireMinArgs(1, "rm", "[MODEL...]"), RunE: func(cmd *cobra.Command, args []string) error { - if _, err := ensureStandaloneRunnerAvailable(cmd.Context(), asPrinter(cmd), false); err != nil { - return fmt.Errorf("unable to initialize standalone model runner: %w", err) - } response, err := desktopClient.Remove(args, force) if response != "" { cmd.Print(response) diff --git a/cmd/cli/commands/root.go b/cmd/cli/commands/root.go index 37e691e3..72c2e7d6 100644 --- a/cmd/cli/commands/root.go +++ b/cmd/cli/commands/root.go @@ -84,26 +84,29 @@ func NewRootCmd(cli *command.DockerCli) *cobra.Command { globalOptions.InstallFlags(rootCmd.Flags()) } - // Add subcommands. + // Runner management commands - these manage the runner itself and don't need automatic runner initialization. rootCmd.AddCommand( newVersionCmd(), + newInstallRunner(), + newUninstallRunner(), + newStartRunner(), + newStopRunner(), + newRestartRunner(), + newReinstallRunner(), + ) + + // Commands that require a running model runner. These are wrapped to ensure the standalone runner is available. + for _, cmd := range []*cobra.Command{ newStatusCmd(), newPullCmd(), newPushCmd(), newPackagedCmd(), newListCmd(), newLogsCmd(), - newRunCmd(), newRemoveCmd(), newInspectCmd(), newComposeCmd(), newTagCmd(), - newInstallRunner(), - newUninstallRunner(), - newStartRunner(), - newStopRunner(), - newRestartRunner(), - newReinstallRunner(), newConfigureCmd(), newPSCmd(), newDFCmd(), @@ -111,6 +114,12 @@ func NewRootCmd(cli *command.DockerCli) *cobra.Command { newRequestsCmd(), newPurgeCmd(), newBenchCmd(), - ) + } { + rootCmd.AddCommand(withStandaloneRunner(cmd)) + } + + // run command handles standalone runner initialization itself (needs debug flag) + rootCmd.AddCommand(newRunCmd()) + return rootCmd } diff --git a/cmd/cli/commands/run.go b/cmd/cli/commands/run.go index 125d982a..a3bb764b 100644 --- a/cmd/cli/commands/run.go +++ b/cmd/cli/commands/run.go @@ -586,6 +586,10 @@ func newRunCmd() *cobra.Command { } }, RunE: func(cmd *cobra.Command, args []string) error { + if _, err := ensureStandaloneRunnerAvailable(cmd.Context(), asPrinter(cmd), debug); err != nil { + return fmt.Errorf("unable to initialize standalone model runner: %w", err) + } + model := args[0] prompt := "" argsLen := len(args) @@ -675,10 +679,6 @@ func newRunCmd() *cobra.Command { return nil } - if _, err := ensureStandaloneRunnerAvailable(cmd.Context(), asPrinter(cmd), debug); err != nil { - return fmt.Errorf("unable to initialize standalone model runner: %w", err) - } - _, err := desktopClient.Inspect(model, false) if err != nil { if !errors.Is(err, desktop.ErrNotFound) { diff --git a/cmd/cli/commands/status.go b/cmd/cli/commands/status.go index 20ff5f9d..8f994fb6 100644 --- a/cmd/cli/commands/status.go +++ b/cmd/cli/commands/status.go @@ -21,9 +21,9 @@ func newStatusCmd() *cobra.Command { Use: "status", Short: "Check if the Docker Model Runner is running", RunE: func(cmd *cobra.Command, args []string) error { - runner, err := ensureStandaloneRunnerAvailable(cmd.Context(), asPrinter(cmd), false) + runner, err := getStandaloneRunner(cmd.Context()) if err != nil { - return fmt.Errorf("unable to initialize standalone model runner: %w", err) + return fmt.Errorf("unable to get standalone model runner info: %w", err) } status := desktopClient.Status() if status.Error != nil { diff --git a/cmd/cli/commands/tag.go b/cmd/cli/commands/tag.go index da321b1e..b8c6e0ea 100644 --- a/cmd/cli/commands/tag.go +++ b/cmd/cli/commands/tag.go @@ -17,9 +17,6 @@ func newTagCmd() *cobra.Command { Short: "Tag a model", Args: requireExactArgs(2, "tag", "SOURCE TARGET"), RunE: func(cmd *cobra.Command, args []string) error { - if _, err := ensureStandaloneRunnerAvailable(cmd.Context(), asPrinter(cmd), false); err != nil { - return fmt.Errorf("unable to initialize standalone model runner: %w", err) - } return tagModel(cmd, desktopClient, args[0], args[1]) }, ValidArgsFunction: completion.ModelNames(getDesktopClient, 1),