Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
40 changes: 33 additions & 7 deletions cmd/cli/commands/launch.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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...)
Expand Down
58 changes: 58 additions & 0 deletions cmd/cli/commands/launch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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")
}
Expand All @@ -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)
Expand All @@ -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")
}