From 018901d977aea405ea47f00d637f5abfbc4b1280 Mon Sep 17 00:00:00 2001 From: sathiraumesh Date: Mon, 6 Apr 2026 10:32:50 +0200 Subject: [PATCH 1/7] adding llmfit container app intergration --- cmd/cli/commands/launch.go | 40 +++++++++++++++++++---- cmd/cli/commands/launch_test.go | 58 +++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 7 deletions(-) diff --git a/cmd/cli/commands/launch.go b/cmd/cli/commands/launch.go index 1570c31db..09d844304 100644 --- a/cmd/cli/commands/launch.go +++ b/cmd/cli/commands/launch.go @@ -48,7 +48,16 @@ var containerApps = map[string]containerApp{ envFn: anythingllmEnv, extraDockerArgs: []string{"-v", "anythingllm_storage:/app/server/storage"}, }, - "openwebui": {defaultImage: "ghcr.io/open-webui/open-webui:latest", defaultHostPort: 3000, containerPort: 8080, envFn: openwebuiEnv}, + "openwebui": { + defaultImage: "ghcr.io/open-webui/open-webui:latest", + defaultHostPort: 3000, + containerPort: 8080, + envFn: openwebuiEnv}, + + "llmfit": { + defaultImage: "ghcr.io/alexsjones/llmfit", + envFn: llmfitEnv, + }, } // hostApp describes a native CLI app launched on the host. @@ -86,6 +95,7 @@ var appDescriptions = map[string]string{ "openclaw": "Open Claw AI assistant", "opencode": "Open Code AI code editor", "openwebui": "Open WebUI for models", + "llmfit": "Recommend models that run on your system", } func newLaunchCmd() *cobra.Command { @@ -109,9 +119,11 @@ Supported apps: %s Examples: docker model launch docker model launch opencode + docker model launch llmfit docker model launch claude -- --help docker model launch openwebui --port 3000 - docker model launch claude --config`, strings.Join(supportedApps, ", ")), + docker model launch claude --config + docker model launch llmfit -- recommend -n 5`, strings.Join(supportedApps, ", ")), ValidArgs: supportedApps, RunE: func(cmd *cobra.Command, args []string) error { // No args - list supported apps @@ -205,8 +217,12 @@ func printAppConfig(cmd *cobra.Command, app string, ep engineEndpoints, imageOve } cmd.Printf("Configuration for %s (container app):\n", app) cmd.Printf(" Image: %s\n", img) - cmd.Printf(" Container port: %d\n", ca.containerPort) - cmd.Printf(" Host port: %d\n", hostPort) + if ca.containerPort > 0 { + cmd.Printf(" Container port: %d\n", ca.containerPort) + } + if ca.defaultHostPort > 0 { + cmd.Printf(" Host port: %d\n", hostPort) + } if ca.envFn != nil { cmd.Printf(" Environment:\n") for _, e := range ca.envFn(ep.container) { @@ -295,9 +311,13 @@ func launchContainerApp(cmd *cobra.Command, ca containerApp, baseURL string, ima if detach { dockerArgs = append(dockerArgs, "-d") } - dockerArgs = append(dockerArgs, - "-p", fmt.Sprintf("%d:%d", hostPort, ca.containerPort), - ) + + if ca.containerPort > 0 { + dockerArgs = append(dockerArgs, + "-p", fmt.Sprintf("%d:%d", hostPort, ca.containerPort), + ) + } + dockerArgs = append(dockerArgs, ca.extraDockerArgs...) if ca.envFn == nil { return fmt.Errorf("container app requires envFn to be set") @@ -419,6 +439,12 @@ func anthropicEnv(baseURL string) []string { } } +func llmfitEnv(baseURL string) []string { + return []string{ + "DOCKER_MODEL_RUNNER_HOST=" + baseURL, + } +} + // withEnv returns the current process environment extended with extra vars. func withEnv(extra ...string) []string { return append(os.Environ(), extra...) diff --git a/cmd/cli/commands/launch_test.go b/cmd/cli/commands/launch_test.go index 069c2426e..fa65f11b0 100644 --- a/cmd/cli/commands/launch_test.go +++ b/cmd/cli/commands/launch_test.go @@ -549,6 +549,7 @@ func TestListSupportedApps(t *testing.T) { require.Contains(t, output, "claude") require.Contains(t, output, "opencode") require.Contains(t, output, "openwebui") + require.Contains(t, output, "llmfit") } func TestOpenWebuiEnvIncludesWebuiAuth(t *testing.T) { @@ -570,6 +571,8 @@ func TestPrintAppConfigContainerApp(t *testing.T) { require.Contains(t, output, "Configuration for openwebui") require.Contains(t, output, "container app") require.Contains(t, output, "ghcr.io/open-webui/open-webui:latest") + require.Contains(t, output, "Container port") + require.Contains(t, output, "Host port") require.Contains(t, output, "OPENAI_API_BASE") require.Contains(t, output, "WEBUI_AUTH=false") } @@ -588,6 +591,22 @@ func TestPrintAppConfigContainerAppOverrides(t *testing.T) { require.Contains(t, output, "9999") } +func TestPrintAppConfigContainerAppNoPorts(t *testing.T) { + buf := new(bytes.Buffer) + cmd := newTestCmd(buf) + + ep := engineEndpoints{container: testBaseURL, host: testBaseURL} + err := printAppConfig(cmd, "llmfit", ep, "", 0) + require.NoError(t, err) + output := buf.String() + require.Contains(t, output, "Configuration for llmfit") + require.Contains(t, output, "container app") + require.Contains(t, output, "ghcr.io/alexsjones/llmfit") + require.Contains(t, output, "DOCKER_MODEL_RUNNER_HOST="+testBaseURL) + require.NotContains(t, output, "Container port") + require.NotContains(t, output, "Host port") +} + func TestPrintAppConfigHostApp(t *testing.T) { buf := new(bytes.Buffer) cmd := newTestCmd(buf) @@ -611,3 +630,42 @@ func TestPrintAppConfigUnsupported(t *testing.T) { require.Error(t, err) require.Contains(t, err.Error(), "unsupported app") } + +func TestLaunchContainerAppNoPort(t *testing.T) { + ca := containerApp{ + defaultImage: "ghcr.io/alexsjones/llmfit", + envFn: llmfitEnv, + } + buf := new(bytes.Buffer) + cmd := newTestCmd(buf) + + err := launchContainerApp(cmd, ca, testBaseURL, "", 0, false, nil, true) + require.NoError(t, err) + + output := buf.String() + require.Contains(t, output, "Would run: docker") + require.Contains(t, output, "run --rm") + require.NotContains(t, output, "-p") + require.Contains(t, output, "DOCKER_MODEL_RUNNER_HOST="+testBaseURL) + require.Contains(t, output, "ghcr.io/alexsjones/llmfit") +} + +func TestLaunchContainerAppNoPortWithArgs(t *testing.T) { + ca := containerApp{ + defaultImage: "ghcr.io/alexsjones/llmfit", + envFn: llmfitEnv, + } + buf := new(bytes.Buffer) + cmd := newTestCmd(buf) + + err := launchContainerApp(cmd, ca, testBaseURL, "", 0, false, []string{"recommend", "-n", "3"}, true) + require.NoError(t, err) + + output := buf.String() + require.Contains(t, output, "Would run: docker") + require.Contains(t, output, "run --rm") + require.NotContains(t, output, "-p") + require.Contains(t, output, "recommend -n 3") + require.Contains(t, output, "DOCKER_MODEL_RUNNER_HOST="+testBaseURL) + require.Contains(t, output, "ghcr.io/alexsjones/llmfit") +} From 9753b1e2453ca592bad15c69357b07848bc24694 Mon Sep 17 00:00:00 2001 From: sathiraumesh Date: Mon, 6 Apr 2026 10:45:09 +0200 Subject: [PATCH 2/7] Fix error by review comment --- cmd/cli/commands/launch.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/cli/commands/launch.go b/cmd/cli/commands/launch.go index 09d844304..12a807b40 100644 --- a/cmd/cli/commands/launch.go +++ b/cmd/cli/commands/launch.go @@ -220,7 +220,7 @@ func printAppConfig(cmd *cobra.Command, app string, ep engineEndpoints, imageOve if ca.containerPort > 0 { cmd.Printf(" Container port: %d\n", ca.containerPort) } - if ca.defaultHostPort > 0 { + if hostPort > 0 { cmd.Printf(" Host port: %d\n", hostPort) } if ca.envFn != nil { From 1d4fb3d4a6251fb5e8bbad06b1effcb8741bc4e1 Mon Sep 17 00:00:00 2001 From: sathiraumesh Date: Mon, 6 Apr 2026 10:47:22 +0200 Subject: [PATCH 3/7] Fix review comments --- cmd/cli/commands/launch.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cmd/cli/commands/launch.go b/cmd/cli/commands/launch.go index 12a807b40..ebcf7b784 100644 --- a/cmd/cli/commands/launch.go +++ b/cmd/cli/commands/launch.go @@ -52,7 +52,8 @@ var containerApps = map[string]containerApp{ defaultImage: "ghcr.io/open-webui/open-webui:latest", defaultHostPort: 3000, containerPort: 8080, - envFn: openwebuiEnv}, + envFn: openwebuiEnv, + }, "llmfit": { defaultImage: "ghcr.io/alexsjones/llmfit", @@ -219,8 +220,6 @@ func printAppConfig(cmd *cobra.Command, app string, ep engineEndpoints, imageOve cmd.Printf(" Image: %s\n", img) if ca.containerPort > 0 { cmd.Printf(" Container port: %d\n", ca.containerPort) - } - if hostPort > 0 { cmd.Printf(" Host port: %d\n", hostPort) } if ca.envFn != nil { From c9510dfed6e83d1966f384571e35ee7876be8fbe Mon Sep 17 00:00:00 2001 From: sathiraumesh Date: Mon, 6 Apr 2026 10:48:43 +0200 Subject: [PATCH 4/7] "removed extra spacing" --- cmd/cli/commands/launch.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/cli/commands/launch.go b/cmd/cli/commands/launch.go index ebcf7b784..9df4aa5e2 100644 --- a/cmd/cli/commands/launch.go +++ b/cmd/cli/commands/launch.go @@ -54,7 +54,6 @@ var containerApps = map[string]containerApp{ containerPort: 8080, envFn: openwebuiEnv, }, - "llmfit": { defaultImage: "ghcr.io/alexsjones/llmfit", envFn: llmfitEnv, From f3392a9cdb8d979b7f5c2b431a297f9c56c5aff9 Mon Sep 17 00:00:00 2001 From: sathiraumesh Date: Wed, 8 Apr 2026 10:12:06 +0200 Subject: [PATCH 5/7] trying to run in interactive mode --- cmd/cli/commands/launch.go | 19 ++++++++++++--- cmd/cli/commands/launch_test.go | 43 ++++++++++++++++++++++++++------- 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/cmd/cli/commands/launch.go b/cmd/cli/commands/launch.go index 9df4aa5e2..421a4750e 100644 --- a/cmd/cli/commands/launch.go +++ b/cmd/cli/commands/launch.go @@ -37,6 +37,7 @@ type containerApp struct { containerPort int envFn func(baseURL string) []string extraDockerArgs []string // additional docker run args (e.g., volume mounts) + interactive bool // attach TTY for interactive/TUI apps } // containerApps are launched via "docker run --rm". @@ -55,8 +56,10 @@ var containerApps = map[string]containerApp{ envFn: openwebuiEnv, }, "llmfit": { - defaultImage: "ghcr.io/alexsjones/llmfit", - envFn: llmfitEnv, + defaultImage: "ghcr.io/alexsjones/llmfit", + envFn: llmfitEnv, + interactive: true, + extraDockerArgs: []string{"--entrypoint", "llmfit"}, }, } @@ -165,7 +168,7 @@ Examples: } if ca, ok := containerApps[app]; ok { - return launchContainerApp(cmd, ca, ep.container, image, port, detach, appArgs, dryRun) + return launchContainerApp(cmd, app, ca, ep.container, image, port, detach, appArgs, dryRun) } if cli, ok := hostApps[app]; ok { return launchHostApp(cmd, app, ep.host, cli, model, runner, appArgs, dryRun) @@ -217,6 +220,7 @@ func printAppConfig(cmd *cobra.Command, app string, ep engineEndpoints, imageOve } cmd.Printf("Configuration for %s (container app):\n", app) cmd.Printf(" Image: %s\n", img) + cmd.Printf(" Interactive: %v\n", ca.interactive) if ca.containerPort > 0 { cmd.Printf(" Container port: %d\n", ca.containerPort) cmd.Printf(" Host port: %d\n", hostPort) @@ -295,7 +299,7 @@ func resolveBaseEndpoints(runner *standaloneRunner) (engineEndpoints, error) { } // launchContainerApp launches a container-based app via "docker run". -func launchContainerApp(cmd *cobra.Command, ca containerApp, baseURL string, imageOverride string, portOverride int, detach bool, appArgs []string, dryRun bool) error { +func launchContainerApp(cmd *cobra.Command, appName string, ca containerApp, baseURL string, imageOverride string, portOverride int, detach bool, appArgs []string, dryRun bool) error { img := imageOverride if img == "" { img = ca.defaultImage @@ -309,6 +313,13 @@ func launchContainerApp(cmd *cobra.Command, ca containerApp, baseURL string, ima if detach { dockerArgs = append(dockerArgs, "-d") } + if ca.interactive { + if detach { + cmd.Printf("Warning: %s runs in interactive mode, app may not work as expected in detached mode\n", appName) + } else { + dockerArgs = append(dockerArgs, "-it") + } + } if ca.containerPort > 0 { dockerArgs = append(dockerArgs, diff --git a/cmd/cli/commands/launch_test.go b/cmd/cli/commands/launch_test.go index fa65f11b0..0a5316f39 100644 --- a/cmd/cli/commands/launch_test.go +++ b/cmd/cli/commands/launch_test.go @@ -159,7 +159,7 @@ func TestLaunchContainerAppDryRun(t *testing.T) { buf := new(bytes.Buffer) cmd := newTestCmd(buf) - err := launchContainerApp(cmd, ca, testBaseURL, "", 0, false, nil, true) + err := launchContainerApp(cmd, "openapi", ca, testBaseURL, "", 0, false, nil, true) require.NoError(t, err) output := buf.String() @@ -177,7 +177,7 @@ func TestLaunchContainerAppOverrides(t *testing.T) { buf := new(bytes.Buffer) cmd := newTestCmd(buf) - err := launchContainerApp(cmd, ca, testBaseURL, overrideImage, overridePort, false, nil, true) + err := launchContainerApp(cmd, "openapi", ca, testBaseURL, overrideImage, overridePort, false, nil, true) require.NoError(t, err) output := buf.String() @@ -191,7 +191,7 @@ func TestLaunchContainerAppDetach(t *testing.T) { buf := new(bytes.Buffer) cmd := newTestCmd(buf) - err := launchContainerApp(cmd, ca, testBaseURL, "", 0, true, nil, true) + err := launchContainerApp(cmd, "openpai", ca, testBaseURL, "", 0, true, nil, true) require.NoError(t, err) output := buf.String() @@ -206,7 +206,7 @@ func TestLaunchContainerAppUsesEnvFn(t *testing.T) { buf := new(bytes.Buffer) cmd := newTestCmd(buf) - err := launchContainerApp(cmd, ca, testBaseURL, "", 0, false, nil, true) + err := launchContainerApp(cmd, "openapi", ca, testBaseURL, "", 0, false, nil, true) require.NoError(t, err) output := buf.String() @@ -219,7 +219,7 @@ func TestLaunchContainerAppNilEnvFn(t *testing.T) { buf := new(bytes.Buffer) cmd := newTestCmd(buf) - err := launchContainerApp(cmd, ca, testBaseURL, "", 0, false, nil, true) + err := launchContainerApp(cmd, "", ca, testBaseURL, "", 0, false, nil, true) require.Error(t, err) require.Contains(t, err.Error(), "container app requires envFn to be set") } @@ -571,6 +571,7 @@ func TestPrintAppConfigContainerApp(t *testing.T) { require.Contains(t, output, "Configuration for openwebui") require.Contains(t, output, "container app") require.Contains(t, output, "ghcr.io/open-webui/open-webui:latest") + require.Contains(t, output, "Interactive: false") require.Contains(t, output, "Container port") require.Contains(t, output, "Host port") require.Contains(t, output, "OPENAI_API_BASE") @@ -602,6 +603,7 @@ func TestPrintAppConfigContainerAppNoPorts(t *testing.T) { require.Contains(t, output, "Configuration for llmfit") require.Contains(t, output, "container app") require.Contains(t, output, "ghcr.io/alexsjones/llmfit") + require.Contains(t, output, "Interactive: true") require.Contains(t, output, "DOCKER_MODEL_RUNNER_HOST="+testBaseURL) require.NotContains(t, output, "Container port") require.NotContains(t, output, "Host port") @@ -635,16 +637,17 @@ func TestLaunchContainerAppNoPort(t *testing.T) { ca := containerApp{ defaultImage: "ghcr.io/alexsjones/llmfit", envFn: llmfitEnv, + interactive: true, } buf := new(bytes.Buffer) cmd := newTestCmd(buf) - err := launchContainerApp(cmd, ca, testBaseURL, "", 0, false, nil, true) + err := launchContainerApp(cmd, "llmfit", ca, testBaseURL, "", 0, false, nil, true) require.NoError(t, err) output := buf.String() require.Contains(t, output, "Would run: docker") - require.Contains(t, output, "run --rm") + require.Contains(t, output, "run --rm -it") require.NotContains(t, output, "-p") require.Contains(t, output, "DOCKER_MODEL_RUNNER_HOST="+testBaseURL) require.Contains(t, output, "ghcr.io/alexsjones/llmfit") @@ -654,18 +657,40 @@ func TestLaunchContainerAppNoPortWithArgs(t *testing.T) { ca := containerApp{ defaultImage: "ghcr.io/alexsjones/llmfit", envFn: llmfitEnv, + interactive: true, } buf := new(bytes.Buffer) cmd := newTestCmd(buf) - err := launchContainerApp(cmd, ca, testBaseURL, "", 0, false, []string{"recommend", "-n", "3"}, true) + err := launchContainerApp(cmd, "llmfit", ca, testBaseURL, "", 0, false, []string{"recommend", "-n", "3"}, true) require.NoError(t, err) output := buf.String() require.Contains(t, output, "Would run: docker") - require.Contains(t, output, "run --rm") + require.Contains(t, output, "run --rm -it") require.NotContains(t, output, "-p") require.Contains(t, output, "recommend -n 3") require.Contains(t, output, "DOCKER_MODEL_RUNNER_HOST="+testBaseURL) require.Contains(t, output, "ghcr.io/alexsjones/llmfit") } + +func TestLaunchContainerAppInteractiveSkippedOnDetach(t *testing.T) { + ca := containerApp{ + defaultImage: "ghcr.io/alexsjones/llmfit", + envFn: llmfitEnv, + interactive: true, + } + stdout := new(bytes.Buffer) + stderr := new(bytes.Buffer) + cmd := &cobra.Command{} + cmd.SetOut(stdout) + cmd.SetErr(stderr) + + err := launchContainerApp(cmd, "llmfit", ca, testBaseURL, "", 0, true, nil, true) + require.NoError(t, err) + + output := stdout.String() + require.Contains(t, output, "run --rm -d") + require.NotContains(t, output, "-it") + require.Contains(t, output, "Warning: llmfit runs in interactive mode, app may not work as expected in detached mode") +} From 646487ce652b09170e74c0f1ab03eb5558f5123f Mon Sep 17 00:00:00 2001 From: sathiraumesh Date: Wed, 8 Apr 2026 10:13:55 +0200 Subject: [PATCH 6/7] fix spacing in command usage --- cmd/cli/commands/launch.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/cli/commands/launch.go b/cmd/cli/commands/launch.go index 421a4750e..39eff4f4b 100644 --- a/cmd/cli/commands/launch.go +++ b/cmd/cli/commands/launch.go @@ -122,7 +122,7 @@ Supported apps: %s Examples: docker model launch docker model launch opencode - docker model launch llmfit + docker model launch llmfit docker model launch claude -- --help docker model launch openwebui --port 3000 docker model launch claude --config From 4bb0270d536e5b8885ef426acde283243ee20fa1 Mon Sep 17 00:00:00 2001 From: sathiraumesh Date: Wed, 8 Apr 2026 15:35:46 +0200 Subject: [PATCH 7/7] make docs --- cmd/cli/docs/reference/docker_model_launch.yaml | 4 +++- cmd/cli/docs/reference/model_launch.md | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/cli/docs/reference/docker_model_launch.yaml b/cmd/cli/docs/reference/docker_model_launch.yaml index 58f074ee0..f407a3c0f 100644 --- a/cmd/cli/docs/reference/docker_model_launch.yaml +++ b/cmd/cli/docs/reference/docker_model_launch.yaml @@ -5,14 +5,16 @@ long: |- Without arguments, lists all supported apps. - Supported apps: anythingllm, claude, codex, openclaw, opencode, openwebui + Supported apps: anythingllm, claude, codex, llmfit, openclaw, opencode, openwebui Examples: docker model launch docker model launch opencode + docker model launch llmfit docker model launch claude -- --help docker model launch openwebui --port 3000 docker model launch claude --config + docker model launch llmfit -- recommend -n 5 usage: docker model launch [APP] [-- APP_ARGS...] pname: docker model plink: docker_model.yaml diff --git a/cmd/cli/docs/reference/model_launch.md b/cmd/cli/docs/reference/model_launch.md index 161e2a3a3..426cdeb67 100644 --- a/cmd/cli/docs/reference/model_launch.md +++ b/cmd/cli/docs/reference/model_launch.md @@ -5,14 +5,16 @@ Launch an app configured to use Docker Model Runner. Without arguments, lists all supported apps. -Supported apps: anythingllm, claude, codex, openclaw, opencode, openwebui +Supported apps: anythingllm, claude, codex, llmfit, openclaw, opencode, openwebui Examples: docker model launch docker model launch opencode + docker model launch llmfit docker model launch claude -- --help docker model launch openwebui --port 3000 docker model launch claude --config + docker model launch llmfit -- recommend -n 5 ### Options