mirror of
https://github.com/ollama/ollama.git
synced 2026-05-13 06:21:28 +00:00
launch: disable Claude Desktop launch (#16028)
This commit is contained in:
parent
bab59072fb
commit
f866e7608f
15 changed files with 179 additions and 214 deletions
|
|
@ -1201,16 +1201,15 @@ func (db *database) getSettings() (Settings, error) {
|
|||
func (db *database) setSettings(s Settings) error {
|
||||
lastHomeView := strings.ToLower(strings.TrimSpace(s.LastHomeView))
|
||||
validLaunchView := map[string]struct{}{
|
||||
"launch": {},
|
||||
"openclaw": {},
|
||||
"claude": {},
|
||||
"claude-desktop": {},
|
||||
"hermes": {},
|
||||
"codex": {},
|
||||
"copilot": {},
|
||||
"opencode": {},
|
||||
"droid": {},
|
||||
"pi": {},
|
||||
"launch": {},
|
||||
"openclaw": {},
|
||||
"claude": {},
|
||||
"hermes": {},
|
||||
"codex": {},
|
||||
"copilot": {},
|
||||
"opencode": {},
|
||||
"droid": {},
|
||||
"pi": {},
|
||||
}
|
||||
if lastHomeView != "chat" {
|
||||
if _, ok := validLaunchView[lastHomeView]; !ok {
|
||||
|
|
|
|||
|
|
@ -107,6 +107,21 @@ func TestStore(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
t.Run("settings disabled home view falls back to launch", func(t *testing.T) {
|
||||
if err := s.SetSettings(Settings{LastHomeView: "claude-desktop"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
loaded, err := s.Settings()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if loaded.LastHomeView != "launch" {
|
||||
t.Fatalf("expected disabled LastHomeView to fall back to launch, got %q", loaded.LastHomeView)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("window size", func(t *testing.T) {
|
||||
if err := s.SetWindowSize(1024, 768); err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
|
|||
|
|
@ -13,14 +13,6 @@ interface LaunchCommand {
|
|||
}
|
||||
|
||||
const LAUNCH_COMMANDS: LaunchCommand[] = [
|
||||
{
|
||||
id: "claude-desktop",
|
||||
name: "Claude Desktop",
|
||||
command: "ollama launch claude-desktop",
|
||||
description: "Claude Desktop with Ollama Cloud",
|
||||
icon: "/launch-icons/claude.svg",
|
||||
iconClassName: "h-7 w-7",
|
||||
},
|
||||
{
|
||||
id: "claude",
|
||||
name: "Claude Code",
|
||||
|
|
|
|||
|
|
@ -2231,7 +2231,7 @@ func runLauncherAction(cmd *cobra.Command, action tui.TUIAction, deps launcherDe
|
|||
|
||||
func launcherActionExitsLoop(integration string) bool {
|
||||
switch integration {
|
||||
case "claude-desktop", "vscode":
|
||||
case "vscode":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -249,7 +249,7 @@ func TestRunLauncherAction_GUIAppsExitTUILoop(t *testing.T) {
|
|||
cmd := &cobra.Command{}
|
||||
cmd.SetContext(context.Background())
|
||||
|
||||
for _, integration := range []string{"claude-desktop", "vscode"} {
|
||||
for _, integration := range []string{"vscode"} {
|
||||
continueLoop, err := runLauncherAction(cmd, tui.TUIAction{Kind: tui.TUIActionLaunchIntegration, Integration: integration}, launcherDeps{
|
||||
resolveRunModel: unexpectedRunModelResolution(t),
|
||||
launchIntegration: func(ctx context.Context, req launch.IntegrationLaunchRequest) error {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ const (
|
|||
claudeDesktopGatewayBaseURL = "https://ollama.com"
|
||||
claudeDesktopAPIKeyURL = "https://ollama.com/settings/keys"
|
||||
claudeDesktopModelLabel = "Ollama Cloud"
|
||||
claudeDesktopUnsupported = "Claude Desktop is no longer supported. Existing installations can be restored with 'ollama launch claude-desktop --restore'."
|
||||
claudeDesktopSuccessMessage = "Claude Desktop profile changed to Ollama Cloud."
|
||||
claudeDesktopRestoreMessage = "To restore the usual Claude profile, run: ollama launch claude-desktop --restore"
|
||||
claudeDesktopRestoredMessage = "Claude Desktop restored to the usual Claude profile."
|
||||
|
|
@ -129,14 +130,8 @@ func (c *ClaudeDesktop) SkipModelReadiness() bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (c *ClaudeDesktop) Run(_ string, args []string) error {
|
||||
if err := claudeDesktopSupported(); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(args) > 0 {
|
||||
return fmt.Errorf("claude-desktop does not accept extra arguments")
|
||||
}
|
||||
return claudeDesktopLaunchOrRestart("Restart Claude Desktop to use Ollama?")
|
||||
func (c *ClaudeDesktop) Run(_ string, _ []string) error {
|
||||
return errClaudeDesktopUnsupported()
|
||||
}
|
||||
|
||||
func (c *ClaudeDesktop) Restore() error {
|
||||
|
|
@ -167,6 +162,10 @@ func (c *ClaudeDesktop) Restore() error {
|
|||
return claudeDesktopLaunchOrRestart("Restart Claude Desktop to use the usual Claude profile?")
|
||||
}
|
||||
|
||||
func errClaudeDesktopUnsupported() error {
|
||||
return errors.New(claudeDesktopUnsupported)
|
||||
}
|
||||
|
||||
func claudeDesktopSupported() error {
|
||||
switch claudeDesktopGOOS {
|
||||
case "darwin", "windows":
|
||||
|
|
@ -838,7 +837,7 @@ func defaultClaudeDesktopOpenApp() error {
|
|||
if path := claudeDesktopRunningAppPath(); path != "" {
|
||||
return claudeDesktopOpenAppPath(path)
|
||||
}
|
||||
return fmt.Errorf("Claude Desktop executable was not found; open Claude Desktop manually once and re-run 'ollama launch claude-desktop'")
|
||||
return fmt.Errorf("Claude Desktop executable was not found; open Claude Desktop manually once and re-run 'ollama launch claude-desktop --restore'")
|
||||
case "darwin":
|
||||
return openClaudeDesktopDarwin()
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -4,10 +4,8 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
|
@ -129,62 +127,59 @@ func TestClaudeDesktopIntegration(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestLaunchIntegration_ClaudeDesktopDoesNotRequireLocalCloudSignIn(t *testing.T) {
|
||||
func TestLaunchIntegration_ClaudeDesktopLaunchReturnsUnsupported(t *testing.T) {
|
||||
for _, name := range []string{"claude-desktop", "claude-app"} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
err := LaunchIntegration(context.Background(), IntegrationLaunchRequest{Name: name})
|
||||
if err == nil {
|
||||
t.Fatal("expected Claude Desktop launch to fail")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "Claude Desktop is no longer supported") {
|
||||
t.Fatalf("expected unsupported guidance, got %v", err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "ollama launch claude-desktop --restore") {
|
||||
t.Fatalf("expected restore guidance, got %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLaunchIntegration_ClaudeDesktopRestoreStillWorks(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
setTestHome(t, tmpDir)
|
||||
withClaudeDesktopPlatform(t, "darwin")
|
||||
withInteractiveSession(t, true)
|
||||
withLauncherHooks(t)
|
||||
t.Setenv("OLLAMA_API_KEY", "test-api-key")
|
||||
withClaudeDesktopProcessHooks(t, func() bool { return false }, func() error { return nil }, func() error { return nil })
|
||||
|
||||
if err := os.MkdirAll(filepath.Join(tmpDir, "Applications", "Claude.app"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/api/status":
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
fmt.Fprint(w, `{"error":"not found"}`)
|
||||
case "/api/me":
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
fmt.Fprint(w, `{"error":"unauthorized","signin_url":"https://example.com/signin"}`)
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}))
|
||||
defer srv.Close()
|
||||
t.Setenv("OLLAMA_HOST", srv.URL)
|
||||
paths, err := claudeDesktopConfigPaths()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(paths.profile), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(paths.meta, []byte(`{"appliedId":"`+claudeDesktopProfileID+`","entries":[{"id":"`+claudeDesktopProfileID+`","name":"Ollama"}]}`), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(paths.profile, []byte(`{"disableDeploymentModeChooser":true,"inferenceGatewayApiKey":"keep","inferenceProvider":"gateway","inferenceGatewayBaseUrl":"https://ollama.com","inferenceGatewayAuthScheme":"bearer"}`), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var validatedKey string
|
||||
withClaudeDesktopValidation(t, func(_ context.Context, key string) error {
|
||||
validatedKey = key
|
||||
return nil
|
||||
stderr := captureStderr(t, func() {
|
||||
err = LaunchIntegration(context.Background(), IntegrationLaunchRequest{Name: "claude-desktop", Restore: true})
|
||||
})
|
||||
|
||||
DefaultSignIn = func(modelName, signInURL string) (string, error) {
|
||||
t.Fatalf("Claude Desktop launch should not require local Ollama Cloud sign-in, got %s at %s", modelName, signInURL)
|
||||
return "", nil
|
||||
if err != nil {
|
||||
t.Fatalf("LaunchIntegration restore returned error: %v", err)
|
||||
}
|
||||
|
||||
var openCalls int
|
||||
withClaudeDesktopProcessHooks(t,
|
||||
func() bool { return false },
|
||||
func() error { return nil },
|
||||
func() error {
|
||||
openCalls++
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err := LaunchIntegration(context.Background(), IntegrationLaunchRequest{Name: "claude-desktop"}); err != nil {
|
||||
t.Fatalf("LaunchIntegration returned error: %v", err)
|
||||
if !strings.Contains(stderr, claudeDesktopRestoredMessage) {
|
||||
t.Fatalf("expected restore success message, got stderr: %q", stderr)
|
||||
}
|
||||
if validatedKey != "test-api-key" {
|
||||
t.Fatalf("validated key = %q, want test API key", validatedKey)
|
||||
}
|
||||
if openCalls != 1 {
|
||||
t.Fatalf("open calls = %d, want 1", openCalls)
|
||||
desktopConfig := claudeDesktopReadJSON(t, paths.desktopConfig)
|
||||
if desktopConfig["deploymentMode"] != "1p" {
|
||||
t.Fatalf("deploymentMode = %v, want 1p", desktopConfig["deploymentMode"])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -472,11 +467,28 @@ func TestWaitForClaudeDesktopExitUsesRunningHook(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestClaudeDesktopWindowsRestartUsesCapturedDesktopPath(t *testing.T) {
|
||||
func TestClaudeDesktopWindowsRestoreRestartUsesCapturedDesktopPath(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
setTestHome(t, tmpDir)
|
||||
withClaudeDesktopPlatform(t, "windows")
|
||||
t.Setenv("LOCALAPPDATA", filepath.Join(tmpDir, "LocalAppData"))
|
||||
restoreConfirm := withLaunchConfirmPolicy(launchConfirmPolicy{yes: true})
|
||||
defer restoreConfirm()
|
||||
|
||||
paths, err := claudeDesktopConfigPaths()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(paths.profile), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(paths.meta, []byte(`{"appliedId":"`+claudeDesktopProfileID+`","entries":[{"id":"`+claudeDesktopProfileID+`","name":"Ollama"}]}`), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(paths.profile, []byte(`{"disableDeploymentModeChooser":true,"inferenceGatewayApiKey":"keep"}`), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
desktopPath := `C:\Users\parth\AppData\Local\AnthropicClaude\app-1.2.3\Claude.exe`
|
||||
running := true
|
||||
var openedPath string
|
||||
|
|
@ -497,8 +509,8 @@ func TestClaudeDesktopWindowsRestartUsesCapturedDesktopPath(t *testing.T) {
|
|||
return nil
|
||||
}
|
||||
|
||||
if err := (&ClaudeDesktop{}).Run("qwen3.5", nil); err != nil {
|
||||
t.Fatalf("Run returned error: %v", err)
|
||||
if err := (&ClaudeDesktop{}).Restore(); err != nil {
|
||||
t.Fatalf("Restore returned error: %v", err)
|
||||
}
|
||||
if openedPath != desktopPath {
|
||||
t.Fatalf("opened path = %q, want %q", openedPath, desktopPath)
|
||||
|
|
@ -901,38 +913,34 @@ func TestClaudeDesktopRestoreTouchesAllWindowsProfileCandidates(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestClaudeDesktopRunRestartsRunningAppWhenConfirmed(t *testing.T) {
|
||||
func TestClaudeDesktopRunReturnsUnsupported(t *testing.T) {
|
||||
withClaudeDesktopPlatform(t, "darwin")
|
||||
restoreConfirm := withLaunchConfirmPolicy(launchConfirmPolicy{yes: true})
|
||||
defer restoreConfirm()
|
||||
|
||||
running := true
|
||||
var quitCalls, openCalls int
|
||||
withClaudeDesktopProcessHooks(t,
|
||||
func() bool { return running },
|
||||
func() bool {
|
||||
t.Fatal("Run should not inspect Claude Desktop process state")
|
||||
return false
|
||||
},
|
||||
func() error {
|
||||
quitCalls++
|
||||
running = false
|
||||
t.Fatal("Run should not quit Claude Desktop")
|
||||
return nil
|
||||
},
|
||||
func() error {
|
||||
openCalls++
|
||||
t.Fatal("Run should not open Claude Desktop")
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err := (&ClaudeDesktop{}).Run("qwen3.5", nil); err != nil {
|
||||
t.Fatalf("Run returned error: %v", err)
|
||||
}
|
||||
if quitCalls != 1 || openCalls != 1 {
|
||||
t.Fatalf("quit/open calls = %d/%d, want 1/1", quitCalls, openCalls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClaudeDesktopRunRejectsExtraArgs(t *testing.T) {
|
||||
withClaudeDesktopPlatform(t, "darwin")
|
||||
err := (&ClaudeDesktop{}).Run("qwen3.5", []string{"--foo"})
|
||||
if err == nil || !strings.Contains(err.Error(), "does not accept extra arguments") {
|
||||
t.Fatalf("Run error = %v, want extra args rejection", err)
|
||||
for _, args := range [][]string{nil, {"--foo"}} {
|
||||
err := (&ClaudeDesktop{}).Run("qwen3.5", args)
|
||||
if err == nil {
|
||||
t.Fatal("expected Run to fail")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "Claude Desktop is no longer supported") {
|
||||
t.Fatalf("expected unsupported guidance, got %v", err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "ollama launch claude-desktop --restore") {
|
||||
t.Fatalf("expected restore guidance, got %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -233,6 +233,31 @@ func TestLaunchCmdTUICallback(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestLaunchCmdClaudeDesktopLaunchReturnsUnsupported(t *testing.T) {
|
||||
for _, name := range []string{"claude-desktop", "claude-app"} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
cmd := LaunchCmd(func(cmd *cobra.Command, args []string) error {
|
||||
t.Fatal("heartbeat check should not run before Claude Desktop unsupported error")
|
||||
return nil
|
||||
}, func(cmd *cobra.Command) {
|
||||
t.Fatal("TUI callback should not run for direct integration launch")
|
||||
})
|
||||
cmd.SetArgs([]string{name})
|
||||
|
||||
err := cmd.Execute()
|
||||
if err == nil {
|
||||
t.Fatal("expected Claude Desktop launch command to fail")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "Claude Desktop is no longer supported") {
|
||||
t.Fatalf("expected unsupported guidance, got %v", err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "ollama launch claude-desktop --restore") {
|
||||
t.Fatalf("expected restore guidance, got %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLaunchCmdNilHeartbeat(t *testing.T) {
|
||||
cmd := LaunchCmd(nil, nil)
|
||||
if cmd == nil {
|
||||
|
|
|
|||
|
|
@ -140,6 +140,23 @@ func TestLookupIntegration_UnknownIntegration(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestLookupIntegration_ClaudeDesktopResolvesForRestore(t *testing.T) {
|
||||
for _, name := range []string{"claude-desktop", "claude-app"} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
canonical, runner, err := LookupIntegration(name)
|
||||
if err != nil {
|
||||
t.Fatalf("expected Claude Desktop lookup to resolve, got: %v", err)
|
||||
}
|
||||
if canonical != "claude-desktop" {
|
||||
t.Fatalf("canonical name = %q, want claude-desktop", canonical)
|
||||
}
|
||||
if runner.String() != "Claude Desktop" {
|
||||
t.Fatalf("runner = %q, want Claude Desktop", runner.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsIntegrationInstalled_UnknownIntegrationReturnsFalse(t *testing.T) {
|
||||
stderr := captureStderr(t, func() {
|
||||
if IsIntegrationInstalled("unknown-integration") {
|
||||
|
|
@ -1715,11 +1732,6 @@ func TestIntegration_InstallHint(t *testing.T) {
|
|||
input: "claude",
|
||||
wantURL: "https://code.claude.com/docs/en/quickstart",
|
||||
},
|
||||
{
|
||||
name: "claude desktop has hint",
|
||||
input: "claude-desktop",
|
||||
wantURL: "https://claude.com/download",
|
||||
},
|
||||
{
|
||||
name: "codex has hint",
|
||||
input: "codex",
|
||||
|
|
@ -1801,16 +1813,6 @@ func TestListIntegrationInfos(t *testing.T) {
|
|||
}
|
||||
want = filtered
|
||||
}
|
||||
if claudeDesktopSupported() != nil {
|
||||
filtered := make([]string, 0, len(want))
|
||||
for _, name := range want {
|
||||
if name != "claude-desktop" {
|
||||
filtered = append(filtered, name)
|
||||
}
|
||||
}
|
||||
want = filtered
|
||||
}
|
||||
|
||||
if diff := compareStrings(got, want); diff != "" {
|
||||
t.Fatalf("launcher integration order mismatch: %s", diff)
|
||||
}
|
||||
|
|
@ -1829,9 +1831,6 @@ func TestListIntegrationInfos(t *testing.T) {
|
|||
|
||||
t.Run("includes known integrations", func(t *testing.T) {
|
||||
known := map[string]bool{"claude": false, "codex": false, "opencode": false}
|
||||
if claudeDesktopSupported() == nil {
|
||||
known["claude-desktop"] = false
|
||||
}
|
||||
if poolsideGOOS != "windows" {
|
||||
known["pool"] = false
|
||||
}
|
||||
|
|
@ -1882,14 +1881,10 @@ func TestListIntegrationInfos_HidesPoolsideOnWindows(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestListIntegrationInfos_HidesClaudeDesktopOnUnsupportedPlatform(t *testing.T) {
|
||||
prev := claudeDesktopGOOS
|
||||
claudeDesktopGOOS = "linux"
|
||||
t.Cleanup(func() { claudeDesktopGOOS = prev })
|
||||
|
||||
func TestListIntegrationInfos_HidesClaudeDesktop(t *testing.T) {
|
||||
for _, info := range ListIntegrationInfos() {
|
||||
if info.Name == "claude-desktop" {
|
||||
t.Fatal("expected claude-desktop to be hidden on unsupported platforms")
|
||||
t.Fatal("expected hidden claude-desktop to be absent")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -285,7 +285,6 @@ Flags and extra arguments require an integration name.
|
|||
|
||||
Supported integrations:
|
||||
claude Claude Code
|
||||
claude-desktop Claude Desktop (aliases: claude-app)
|
||||
cline Cline
|
||||
codex Codex
|
||||
copilot Copilot CLI (aliases: copilot-cli)
|
||||
|
|
@ -302,8 +301,6 @@ Examples:
|
|||
ollama launch
|
||||
ollama launch claude
|
||||
ollama launch claude --model <model>
|
||||
ollama launch claude-desktop
|
||||
ollama launch claude-desktop --restore
|
||||
ollama launch hermes
|
||||
ollama launch droid --config (does not auto-launch)
|
||||
ollama launch codex -- -p myprofile (pass extra args to integration)
|
||||
|
|
@ -350,6 +347,10 @@ Examples:
|
|||
return nil
|
||||
}
|
||||
|
||||
if !restoreFlag && launchCommandIsClaudeDesktop(name) {
|
||||
return errClaudeDesktopUnsupported()
|
||||
}
|
||||
|
||||
if modelFlag != "" && isCloudModelName(modelFlag) {
|
||||
if client, err := api.ClientFromEnvironment(); err == nil {
|
||||
if disabled, _ := cloudStatusDisabled(cmd.Context(), client); disabled {
|
||||
|
|
@ -395,8 +396,12 @@ func launchCommandCanSkipHeartbeat(args []string) bool {
|
|||
if len(args) == 0 {
|
||||
return false
|
||||
}
|
||||
name, _, err := LookupIntegration(args[0])
|
||||
return err == nil && name == "claude-desktop"
|
||||
return launchCommandIsClaudeDesktop(args[0])
|
||||
}
|
||||
|
||||
func launchCommandIsClaudeDesktop(name string) bool {
|
||||
canonical, _, err := LookupIntegration(name)
|
||||
return err == nil && canonical == claudeDesktopIntegrationName
|
||||
}
|
||||
|
||||
type launcherClient struct {
|
||||
|
|
@ -459,6 +464,10 @@ func LaunchIntegration(ctx context.Context, req IntegrationLaunchRequest) error
|
|||
return err
|
||||
}
|
||||
|
||||
if name == claudeDesktopIntegrationName && !req.Restore {
|
||||
return errClaudeDesktopUnsupported()
|
||||
}
|
||||
|
||||
policy := launchIntegrationPolicy(req)
|
||||
if req.Restore {
|
||||
return restoreIntegration(name, runner, req)
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ type IntegrationInfo struct {
|
|||
Description string
|
||||
}
|
||||
|
||||
var launcherIntegrationOrder = []string{"claude-desktop", "claude", "openclaw", "hermes", "opencode", "codex", "copilot", "droid", "pi", "pool"}
|
||||
var launcherIntegrationOrder = []string{"claude", "openclaw", "hermes", "opencode", "codex", "copilot", "droid", "pi", "pool"}
|
||||
|
||||
var integrationSpecs = []*IntegrationSpec{
|
||||
{
|
||||
|
|
@ -53,6 +53,7 @@ var integrationSpecs = []*IntegrationSpec{
|
|||
Runner: &ClaudeDesktop{},
|
||||
Aliases: []string{"claude-app"},
|
||||
Description: "Claude Desktop with Ollama Cloud",
|
||||
Hidden: true,
|
||||
Install: IntegrationInstallSpec{
|
||||
CheckInstalled: func() bool {
|
||||
return claudeDesktopInstalled()
|
||||
|
|
|
|||
|
|
@ -44,13 +44,6 @@ func launcherTestState() *launch.LauncherState {
|
|||
Selectable: true,
|
||||
Changeable: true,
|
||||
},
|
||||
"claude-desktop": {
|
||||
Name: "claude-desktop",
|
||||
DisplayName: "Claude Desktop",
|
||||
Description: "Claude Desktop with Ollama Cloud",
|
||||
Selectable: true,
|
||||
Changeable: true,
|
||||
},
|
||||
"hermes": {
|
||||
Name: "hermes",
|
||||
DisplayName: "Hermes Agent",
|
||||
|
|
@ -135,12 +128,8 @@ func TestMenuRendersPinnedItemsAndMore(t *testing.T) {
|
|||
t.Fatalf("expected menu view to contain %q\n%s", want, view)
|
||||
}
|
||||
}
|
||||
if findMenuCursorByIntegration(menu.items, "claude-desktop") == -1 {
|
||||
if strings.Contains(view, "Launch Claude Desktop") {
|
||||
t.Fatalf("expected Claude Desktop to be hidden on unsupported platforms\n%s", view)
|
||||
}
|
||||
} else if !strings.Contains(view, "Launch Claude Desktop") {
|
||||
t.Fatalf("expected menu view to contain Claude Desktop\n%s", view)
|
||||
if strings.Contains(view, "Launch Claude Desktop") {
|
||||
t.Fatalf("expected hidden Claude Desktop to be absent\n%s", view)
|
||||
}
|
||||
wantOrder := expectedCollapsedSequence(state)
|
||||
if diff := compareStrings(integrationSequence(menu.items), wantOrder); diff != "" {
|
||||
|
|
|
|||
|
|
@ -115,8 +115,7 @@
|
|||
"expanded": true,
|
||||
"pages": [
|
||||
"/integrations/openclaw",
|
||||
"/integrations/hermes",
|
||||
"/integrations/claude-desktop"
|
||||
"/integrations/hermes"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,77 +2,12 @@
|
|||
title: Claude Desktop
|
||||
---
|
||||
|
||||
Claude Desktop can use Ollama Cloud, including Claude Cowork and Claude Code inside the app.
|
||||
Claude Desktop is no longer supported by `ollama launch`.
|
||||
|
||||
<img
|
||||
src="/images/claude-cowork-kimi-k2-6.png"
|
||||
alt="Claude Cowork using kimi-k2.6 through Ollama Cloud"
|
||||
className="rounded-xl"
|
||||
/>
|
||||
|
||||
## Requirements
|
||||
|
||||
- Claude Desktop for macOS or Windows
|
||||
- An [Ollama API key](https://ollama.com/settings/keys)
|
||||
|
||||
Set the key in your shell before launching:
|
||||
|
||||
```shell
|
||||
export OLLAMA_API_KEY=your_api_key
|
||||
```
|
||||
|
||||
## Quick setup
|
||||
|
||||
```shell
|
||||
ollama launch claude-desktop
|
||||
```
|
||||
|
||||
To bring back the usual Anthropic Claude profile later, run:
|
||||
Existing installations can be restored to the usual Claude profile:
|
||||
|
||||
```shell
|
||||
ollama launch claude-desktop --restore
|
||||
```
|
||||
|
||||
## Using Ollama Cloud models
|
||||
|
||||
After setup, Claude Desktop discovers your available Ollama Cloud models automatically. For example, `kimi-k2.6` appears inside Claude Cowork.
|
||||
|
||||
The same models are also available to Claude Code inside Claude Desktop:
|
||||
|
||||

|
||||
|
||||
## Configure without launching
|
||||
|
||||
```shell
|
||||
ollama launch claude-desktop --config
|
||||
```
|
||||
|
||||
## Restore normal Claude
|
||||
|
||||
Switch Claude Desktop back to its usual profile:
|
||||
|
||||
```shell
|
||||
ollama launch claude-desktop --restore
|
||||
```
|
||||
|
||||
|
||||
If Claude Desktop is running, use `--yes` to approve the restart automatically:
|
||||
|
||||
```shell
|
||||
ollama launch claude-desktop --restore --yes
|
||||
```
|
||||
|
||||
## Supported with Ollama
|
||||
|
||||
Claude Desktop with Ollama currently supports:
|
||||
|
||||
- Ollama Cloud as the third-party inference gateway
|
||||
- Automatic model discovery from Ollama Cloud
|
||||
- Claude Cowork with Ollama Cloud models
|
||||
- Claude Code inside Claude Desktop with the same cloud models
|
||||
- subagents (tell Claude to have subagents inherit the current model)
|
||||
|
||||
Claude Desktop with Ollama does not support yet:
|
||||
|
||||
- Web search
|
||||
- Extensions
|
||||
Use [Claude Code](/integrations/claude-code) for Anthropic-compatible coding workflows with Ollama.
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ AI assistants that help with everyday tasks.
|
|||
|
||||
- [OpenClaw](/integrations/openclaw)
|
||||
- [Hermes Agent](/integrations/hermes)
|
||||
- [Claude Desktop](/integrations/claude-desktop)
|
||||
|
||||
## IDEs & Editors
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue