package cmd import ( "bufio" "context" "crypto/ed25519" "crypto/rand" "database/sql" "encoding/json" "encoding/pem" "errors" "fmt" "io" "log" "log/slog" "math" "net" "net/http" "os" "os/exec" "path" "path/filepath" "runtime" "slices" "sort" "strconv" "strings" "sync/atomic" "time" "github.com/containerd/console" "github.com/mattn/go-runewidth" "github.com/olekukonko/tablewriter" "github.com/pkg/browser" "github.com/spf13/cobra" "github.com/spf13/pflag" "golang.org/x/crypto/ssh" "golang.org/x/sync/errgroup" "golang.org/x/term" agentstore "github.com/ollama/ollama/agent/store" "github.com/ollama/ollama/api" "github.com/ollama/ollama/cmd/config" "github.com/ollama/ollama/cmd/launch" "github.com/ollama/ollama/cmd/tui" "github.com/ollama/ollama/discover" "github.com/ollama/ollama/envconfig" "github.com/ollama/ollama/format" "github.com/ollama/ollama/internal/modelref" "github.com/ollama/ollama/logutil" "github.com/ollama/ollama/parser" "github.com/ollama/ollama/progress" "github.com/ollama/ollama/runner" "github.com/ollama/ollama/server" "github.com/ollama/ollama/types/model" "github.com/ollama/ollama/types/syncmap" "github.com/ollama/ollama/version" xcreate "github.com/ollama/ollama/x/create" xcreateclient "github.com/ollama/ollama/x/create/client" "github.com/ollama/ollama/x/imagegen" ) func init() { // Override default selectors to use Bubbletea TUI instead of raw terminal I/O. launch.DefaultSingleSelector = func(title string, items []launch.SelectionItem, current string) (string, error) { return runTUISingleSelector(title, items, current, nil) } launch.DefaultSingleSelectorWithUpdates = func(title string, items []launch.SelectionItem, current string, updates <-chan []launch.SelectionItem) (string, error) { return runTUISingleSelector(title, items, current, updates) } launch.DefaultMultiSelector = func(title string, items []launch.SelectionItem, preChecked []string) ([]string, error) { return runTUIMultiSelector(title, items, preChecked, nil) } launch.DefaultMultiSelectorWithUpdates = func(title string, items []launch.SelectionItem, preChecked []string, updates <-chan []launch.SelectionItem) ([]string, error) { return runTUIMultiSelector(title, items, preChecked, updates) } launch.DefaultSignIn = func(modelName, signInURL string) (string, error) { userName, err := tui.RunSignIn(modelName, signInURL) if errors.Is(err, tui.ErrCancelled) { return "", launch.ErrCancelled } return userName, err } launch.DefaultUpgrade = func(modelName, requiredPlan string) (string, error) { plan, err := tui.RunUpgrade(modelName, requiredPlan) if errors.Is(err, tui.ErrCancelled) { return "", launch.ErrCancelled } return plan, err } launch.DefaultConfirmPrompt = tui.RunConfirmWithOptions } func runTUISingleSelector(title string, items []launch.SelectionItem, current string, updates <-chan []launch.SelectionItem) (string, error) { if !term.IsTerminal(int(os.Stdin.Fd())) || !term.IsTerminal(int(os.Stdout.Fd())) { return "", fmt.Errorf("model selection requires an interactive terminal; use --model to run in headless mode") } tuiItems := tui.ReorderItems(tui.ConvertItems(items)) result, err := tui.SelectSingleWithUpdates(title, tuiItems, current, convertSelectionItemUpdates(updates)) if errors.Is(err, tui.ErrCancelled) { return "", launch.ErrCancelled } return result, err } func runTUIMultiSelector(title string, items []launch.SelectionItem, preChecked []string, updates <-chan []launch.SelectionItem) ([]string, error) { if !term.IsTerminal(int(os.Stdin.Fd())) || !term.IsTerminal(int(os.Stdout.Fd())) { return nil, fmt.Errorf("model selection requires an interactive terminal; use --model to run in headless mode") } tuiItems := tui.ReorderItems(tui.ConvertItems(items)) result, err := tui.SelectMultipleWithUpdates(title, tuiItems, preChecked, convertSelectionItemUpdates(updates)) if errors.Is(err, tui.ErrCancelled) { return nil, launch.ErrCancelled } return result, err } func convertSelectionItemUpdates(updates <-chan []launch.SelectionItem) <-chan []tui.SelectItem { if updates == nil { return nil } out := make(chan []tui.SelectItem, 1) go func() { defer close(out) for items := range updates { out <- tui.ReorderItems(tui.ConvertItems(items)) } }() return out } const ConnectInstructions = "If your browser did not open, navigate to:\n %s\n\n" // ensureThinkingSupport emits a warning if the model does not advertise thinking support func ensureThinkingSupport(ctx context.Context, client *api.Client, name string) { if name == "" { return } resp, err := client.Show(ctx, &api.ShowRequest{Model: name}) if err != nil { return } if slices.Contains(resp.Capabilities, model.CapabilityThinking) { return } fmt.Fprintf(os.Stderr, "warning: model %q does not support thinking output\n", name) } var errModelfileNotFound = errors.New("specified Modelfile wasn't found") func getModelfileName(cmd *cobra.Command) (string, error) { filename, _ := cmd.Flags().GetString("file") if filename == "" { filename = "Modelfile" } absName, err := filepath.Abs(filename) if err != nil { return "", err } _, err = os.Stat(absName) if err != nil { return "", err } return absName, nil } // isLocalhost returns true if the configured Ollama host is a loopback or unspecified address. func isLocalhost() bool { host := envconfig.Host() h, _, _ := net.SplitHostPort(host.Host) if h == "localhost" { return true } ip := net.ParseIP(h) return ip != nil && (ip.IsLoopback() || ip.IsUnspecified()) } func resolveExperimentalLocalModelDir(ref, filename string) string { if ref == "" || filepath.IsAbs(ref) || filename == "" { return ref } candidate := filepath.Join(filepath.Dir(filename), ref) if xcreate.IsSafetensorsModelDir(candidate) || xcreate.IsTensorModelDir(candidate) { return candidate } return ref } func resolveExperimentalDraftDir(ref, filename string) (string, error) { if ref == "" { return "", nil } if filepath.IsAbs(ref) { if xcreate.IsSafetensorsModelDir(ref) { return ref, nil } return "", fmt.Errorf("draft %s is not a supported safetensors model directory", ref) } if filename != "" { candidate := filepath.Join(filepath.Dir(filename), ref) if xcreate.IsSafetensorsModelDir(candidate) { return candidate, nil } } return "", fmt.Errorf("DRAFT model references are not supported with --experimental yet: %s", ref) } func CreateHandler(cmd *cobra.Command, args []string) error { p := progress.NewProgress(os.Stderr) defer p.Stop() // Validate model name early to fail fast modelName := args[0] name := model.ParseName(modelName) if !name.IsValid() { return fmt.Errorf("invalid model name: %s", modelName) } // Check for --experimental flag for safetensors model creation // This gates both safetensors LLM and imagegen model creation experimental, _ := cmd.Flags().GetBool("experimental") draftQuantize, _ := cmd.Flags().GetString("draft-quantize") if experimental { if !isLocalhost() { return errors.New("remote safetensor model creation not yet supported") } // Get Modelfile content - either from -f flag or default to "FROM ." var reader io.Reader filename, err := getModelfileName(cmd) if os.IsNotExist(err) || filename == "" { // No Modelfile specified or found - use default reader = strings.NewReader("FROM .\n") } else if err != nil { return err } else { f, err := os.Open(filename) if err != nil { return err } defer f.Close() reader = f } // Parse the Modelfile modelfile, err := parser.ParseFile(reader) if err != nil { return fmt.Errorf("failed to parse Modelfile: %w", err) } modelDir, mfConfig, err := xcreateclient.ConfigFromModelfile(modelfile) if err != nil { return err } modelDir = resolveExperimentalLocalModelDir(modelDir, filename) if mfConfig.Draft != "" { draftDir, err := resolveExperimentalDraftDir(mfConfig.Draft, filename) if err != nil { return err } mfConfig.Draft = draftDir } quantize, _ := cmd.Flags().GetString("quantize") return xcreateclient.CreateModel(xcreateclient.CreateOptions{ ModelName: modelName, ModelDir: modelDir, Quantize: quantize, DraftQuantize: draftQuantize, Modelfile: mfConfig, }, p) } // Standard Modelfile + API path var reader io.Reader filename, err := getModelfileName(cmd) if os.IsNotExist(err) { if filename == "" { reader = strings.NewReader("FROM .\n") } else { return errModelfileNotFound } } else if err != nil { return err } else { f, err := os.Open(filename) if err != nil { return err } reader = f defer f.Close() } modelfile, err := parser.ParseFile(reader) if err != nil { return err } status := "gathering model components" spinner := progress.NewSpinner(status) p.Add(status, spinner) req, err := modelfile.CreateRequest(filepath.Dir(filename)) if err != nil { return err } spinner.Stop() req.Model = modelName quantize, _ := cmd.Flags().GetString("quantize") if quantize != "" { req.Quantize = quantize } if draftQuantize != "" { if len(req.DraftFiles) == 0 { return errors.New("--draft-quantize requires a DRAFT model") } req.DraftQuantize = draftQuantize } client, err := api.ClientFromEnvironment() if err != nil { return err } var g errgroup.Group g.SetLimit(max(runtime.GOMAXPROCS(0)-1, 1)) files := syncmap.NewSyncMap[string, string]() fileNames := createRequestFileNames(req.Files) for f, digest := range req.Files { g.Go(func() error { if _, err := createBlob(cmd, client, f, digest, p); err != nil { return err } files.Store(fileNames[f], digest) return nil }) } adapters := syncmap.NewSyncMap[string, string]() adapterNames := createRequestFileNames(req.Adapters) for f, digest := range req.Adapters { g.Go(func() error { if _, err := createBlob(cmd, client, f, digest, p); err != nil { return err } adapters.Store(adapterNames[f], digest) return nil }) } draftFiles := syncmap.NewSyncMap[string, string]() draftFileNames := createRequestFileNames(req.DraftFiles) for f, digest := range req.DraftFiles { g.Go(func() error { if _, err := createBlob(cmd, client, f, digest, p); err != nil { return err } draftFiles.Store(draftFileNames[f], digest) return nil }) } if err := g.Wait(); err != nil { return err } req.Files = files.Items() req.Adapters = adapters.Items() req.DraftFiles = draftFiles.Items() bars := make(map[string]*progress.Bar) fn := func(resp api.ProgressResponse) error { if resp.Digest != "" { bar, ok := bars[resp.Digest] if !ok { msg := resp.Status if msg == "" { msg = fmt.Sprintf("pulling %s...", resp.Digest[7:19]) } bar = progress.NewBar(msg, resp.Total, resp.Completed) bars[resp.Digest] = bar p.Add(resp.Digest, bar) } bar.Set(resp.Completed) } else if status != resp.Status { spinner.Stop() status = resp.Status spinner = progress.NewSpinner(status) p.Add(status, spinner) } return nil } if err := client.Create(cmd.Context(), req, fn); err != nil { if strings.Contains(err.Error(), "path or Modelfile are required") { return fmt.Errorf("the ollama server must be updated to use `ollama create` with this client") } return err } return nil } func createRequestFileNames(files map[string]string) map[string]string { names := make(map[string]string, len(files)) root, ok := commonFileRoot(files) for f := range files { name := filepath.Base(f) if ok { abs, err := filepath.Abs(f) if err == nil { if rel, err := filepath.Rel(root, abs); err == nil && rel != "." && rel != ".." && !strings.HasPrefix(rel, ".."+string(filepath.Separator)) { name = rel } } } names[f] = path.Clean(filepath.ToSlash(name)) } return names } func commonFileRoot(files map[string]string) (string, bool) { if len(files) < 2 { return "", false } var root string var volume string for f := range files { abs, err := filepath.Abs(f) if err != nil { return "", false } if nextVolume := filepath.VolumeName(abs); volume == "" { volume = nextVolume } else if !strings.EqualFold(volume, nextVolume) { return "", false } dir := filepath.Dir(abs) if root == "" { root = dir continue } for { rel, err := filepath.Rel(root, dir) if err == nil && (rel == "." || (rel != ".." && !strings.HasPrefix(rel, ".."+string(filepath.Separator)))) { break } parent := filepath.Dir(root) if parent == root { return "", false } root = parent } } return root, root != "" } func createBlob(cmd *cobra.Command, client *api.Client, path string, digest string, p *progress.Progress) (string, error) { realPath, err := filepath.EvalSymlinks(path) if err != nil { return "", err } bin, err := os.Open(realPath) if err != nil { return "", err } defer bin.Close() // Get file info to retrieve the size fileInfo, err := bin.Stat() if err != nil { return "", err } fileSize := fileInfo.Size() var pw progressWriter status := fmt.Sprintf("copying file %s 0%%", digest) spinner := progress.NewSpinner(status) p.Add(status, spinner) defer spinner.Stop() done := make(chan struct{}) defer close(done) go func() { ticker := time.NewTicker(60 * time.Millisecond) defer ticker.Stop() for { select { case <-ticker.C: spinner.SetMessage(fmt.Sprintf("copying file %s %d%%", digest, int(100*pw.n.Load()/fileSize))) case <-done: spinner.SetMessage(fmt.Sprintf("copying file %s 100%%", digest)) return } } }() if err := client.CreateBlob(cmd.Context(), digest, io.TeeReader(bin, &pw)); err != nil { return "", err } return digest, nil } type progressWriter struct { n atomic.Int64 } func (w *progressWriter) Write(p []byte) (n int, err error) { w.n.Add(int64(len(p))) return len(p), nil } func loadOrUnloadModel(cmd *cobra.Command, opts *runOptions) error { p := progress.NewProgress(os.Stderr) defer p.StopAndClear() spinner := progress.NewSpinner("") p.Add("", spinner) client, err := api.ClientFromEnvironment() if err != nil { return err } requestedCloud := modelref.HasExplicitCloudSource(opts.Model) if info, err := client.Show(cmd.Context(), &api.ShowRequest{Model: opts.Model}); err != nil { return err } else if info.RemoteHost != "" || requestedCloud { // Cloud model, no need to load/unload isCloud := requestedCloud || strings.HasPrefix(info.RemoteHost, "https://ollama.com") // Check if user is signed in for ollama.com cloud models if isCloud { if _, err := client.Whoami(cmd.Context()); err != nil { return err } } if opts.ShowConnect && !isCloud { p.StopAndClear() remoteModel := info.RemoteModel if remoteModel == "" { remoteModel = opts.Model } fmt.Fprintf(os.Stderr, "Connecting to '%s' on '%s'\n", remoteModel, info.RemoteHost) } return nil } return preloadLocalModel(cmd.Context(), client, *opts) } func preloadLocalModel(ctx context.Context, client *api.Client, opts runOptions) error { if client == nil { return errors.New("client is required") } req := &api.GenerateRequest{ Model: opts.Model, KeepAlive: opts.KeepAlive, // Pass Think here so unsupported thinking modes fail before the first chat request. Think: opts.Think, } return client.Generate(ctx, req, func(api.GenerateResponse) error { return nil }) } func StopHandler(cmd *cobra.Command, args []string) error { opts := &runOptions{ Model: args[0], KeepAlive: &api.Duration{Duration: 0}, } if err := loadOrUnloadModel(cmd, opts); err != nil { if strings.Contains(err.Error(), "not found") { return fmt.Errorf("couldn't find model \"%s\" to stop", args[0]) } return err } return nil } func generateEmbedding(cmd *cobra.Command, modelName, input string, keepAlive *api.Duration, truncate *bool, dimensions int) error { client, err := api.ClientFromEnvironment() if err != nil { return err } req := &api.EmbedRequest{ Model: modelName, Input: input, } if keepAlive != nil { req.KeepAlive = keepAlive } if truncate != nil { req.Truncate = truncate } if dimensions > 0 { req.Dimensions = dimensions } resp, err := client.Embed(cmd.Context(), req) if err != nil { return err } if len(resp.Embeddings) == 0 { return errors.New("no embeddings returned") } output, err := json.Marshal(resp.Embeddings[0]) if err != nil { return err } fmt.Println(string(output)) return nil } // TODO(parthsareen): consolidate with TUI signin flow func handleCloudAuthorizationError(err error) bool { var authErr api.AuthorizationError if errors.As(err, &authErr) && authErr.StatusCode == http.StatusUnauthorized { fmt.Printf("You need to be signed in to Ollama to run Cloud models.\n\n") if authErr.SigninURL != "" { fmt.Printf(ConnectInstructions, authErr.SigninURL) } return true } return false } // TEMP(drifkin): To match legacy `ollama run some-model:cloud` behavior, we // best-effort pull cloud stub files for any explicit cloud source models. // Remove this once `/api/tags` is cloud-aware. func ensureCloudStub(ctx context.Context, client *api.Client, modelName string) { if !modelref.HasExplicitCloudSource(modelName) { return } normalizedName, _, err := modelref.NormalizePullName(modelName) if err != nil { slog.Warn("failed to normalize pull name", "model", modelName, "error", err, "normalizedName", normalizedName) return } listResp, err := client.List(ctx) if err != nil { slog.Warn("failed to list models", "error", err) return } if hasListedModelName(listResp.Models, modelName) || hasListedModelName(listResp.Models, normalizedName) { return } logutil.Trace("pulling cloud stub", "model", modelName, "normalizedName", normalizedName) err = client.Pull(ctx, &api.PullRequest{ Model: normalizedName, }, func(api.ProgressResponse) error { return nil }) if err != nil { slog.Warn("failed to pull cloud stub", "model", modelName, "error", err) } } func hasListedModelName(models []api.ListModelResponse, name string) bool { for _, m := range models { if strings.EqualFold(m.Name, name) || strings.EqualFold(m.Model, name) { return true } } return false } func RunHandler(cmd *cobra.Command, args []string) error { interactive := true opts := runOptions{ Options: map[string]any{}, ShowConnect: true, } thinkExplicit, err := applyRunFlagsToOptions(cmd, &opts) if err != nil { return err } if len(args) == 0 { if !opts.Resume { return errors.New("model is required") } } else { opts.Model = args[0] } var prompts []string if len(args) > 1 { prompts = args[1:] } // prepend stdin to the prompt if provided if !term.IsTerminal(int(os.Stdin.Fd())) { in, err := io.ReadAll(os.Stdin) if err != nil { return err } // Only prepend stdin content if it's not empty stdinContent := string(in) if len(stdinContent) > 0 { prompts = append([]string{stdinContent}, prompts...) } opts.ShowConnect = false interactive = false } opts.Prompt = strings.Join(prompts, " ") if len(prompts) > 0 { interactive = false } // Be quiet if we're redirecting to a pipe or file if !term.IsTerminal(int(os.Stdout.Fd())) { interactive = false } if opts.Resume && opts.Model == "" { modelName, err := resumeModelFromLatestChat(cmd.Context()) if err != nil { return err } opts.Model = modelName } // Fill out the rest of the options based on information about the // model. client, err := api.ClientFromEnvironment() if err != nil { return err } name := opts.Model requestedCloud := modelref.HasExplicitCloudSource(name) info, err := func() (*api.ShowResponse, error) { showReq := &api.ShowRequest{Name: name} info, err := client.Show(cmd.Context(), showReq) var se api.StatusError if errors.As(err, &se) && se.StatusCode == http.StatusNotFound { if requestedCloud { return nil, err } if err := PullHandler(cmd, []string{name}); err != nil { return nil, err } return client.Show(cmd.Context(), &api.ShowRequest{Name: name}) } return info, err }() if err != nil { if handleCloudAuthorizationError(err) { return nil } return err } ensureCloudStub(cmd.Context(), client, name) opts.Think, err = inferThinkingOption(&info.Capabilities, &opts, thinkExplicit) if err != nil { return err } applyMultiModalCompat(&opts, info) applyShowResponseToRunOptions(&opts, info) // Check if this is an embedding model isEmbeddingModel := slices.Contains(info.Capabilities, model.CapabilityEmbedding) // If it's an embedding model, handle embedding generation if isEmbeddingModel { if opts.Prompt == "" { return errors.New("embedding models require input text. Usage: ollama run " + name + " \"your text here\"") } // Get embedding-specific flags var truncate *bool if truncateFlag, err := cmd.Flags().GetBool("truncate"); err == nil && cmd.Flags().Changed("truncate") { truncate = &truncateFlag } dimensions, err := cmd.Flags().GetInt("dimensions") if err != nil { return err } return generateEmbedding(cmd, name, opts.Prompt, opts.KeepAlive, truncate, dimensions) } // Check if this is an image generation model if slices.Contains(info.Capabilities, model.CapabilityImage) { if opts.Prompt == "" && !interactive { return errors.New("image generation models require a prompt. Usage: ollama run " + name + " \"your prompt here\"") } return imagegen.RunCLI(cmd, name, opts.Prompt, interactive, opts.KeepAlive) } if interactive { if info.RemoteHost != "" || requestedCloud { if err := loadOrUnloadModel(cmd, &opts); err != nil { var sErr api.AuthorizationError if errors.As(err, &sErr) && sErr.StatusCode == http.StatusUnauthorized { fmt.Printf("You need to be signed in to Ollama to run Cloud models.\n\n") if sErr.SigninURL != "" { fmt.Printf(ConnectInstructions, sErr.SigninURL) } return nil } return err } } contextWindowTokens := contextWindowTokensForRun(cmd.Context(), client, opts.Model, opts.ContextWindowTokens) agentOpts := agentOptionsFromRunOptions(opts) agentOpts.ContextWindowTokens = contextWindowTokens if err := config.SetLastModel(opts.Model); err != nil { return err } return GenerateAgentTUI(cmd, agentOpts) } if info.RemoteHost == "" && !requestedCloud { if err := loadOrUnloadModel(cmd, &opts); err != nil { var sErr api.AuthorizationError if errors.As(err, &sErr) && sErr.StatusCode == http.StatusUnauthorized { fmt.Printf("You need to be signed in to Ollama to run Cloud models.\n\n") if sErr.SigninURL != "" { fmt.Printf(ConnectInstructions, sErr.SigninURL) } return nil } return err } } opts.ContextWindowTokens = contextWindowTokensForRun(cmd.Context(), client, opts.Model, opts.ContextWindowTokens) return runAgentHeadless(cmd, opts) } func autoApproveToolsFromFlags(cmd *cobra.Command) (bool, error) { for _, name := range []string{"auto-approve-tools", "yolo", "experimental-yolo"} { if cmd.Flags().Lookup(name) == nil { continue } enabled, err := cmd.Flags().GetBool(name) if err != nil { return false, err } if enabled { return true, nil } } return false, nil } func applyRunFlagsToOptions(cmd *cobra.Command, opts *runOptions) (bool, error) { if cmd == nil || opts == nil { return false, nil } if cmd.Flags().Lookup("format") != nil { format, err := cmd.Flags().GetString("format") if err != nil { return false, err } opts.Format = format } thinkExplicit := false if thinkFlag := cmd.Flags().Lookup("think"); thinkFlag != nil && thinkFlag.Changed { thinkStr, err := cmd.Flags().GetString("think") if err != nil { return false, err } thinkExplicit = true switch thinkStr { case "", "true": opts.Think = &api.ThinkValue{Value: true} case "false": opts.Think = &api.ThinkValue{Value: false} case "high", "medium", "low", "max": opts.Think = &api.ThinkValue{Value: thinkStr} default: return false, fmt.Errorf("invalid value for --think: %q (must be true, false, high, medium, low, or max)", thinkStr) } } else { opts.Think = nil } if cmd.Flags().Lookup("resume") != nil { resume, err := cmd.Flags().GetBool("resume") if err != nil { return false, err } opts.Resume = resume } autoApproveTools, err := autoApproveToolsFromFlags(cmd) if err != nil { return false, err } opts.AutoApproveTools = autoApproveTools if cmd.Flags().Lookup("verbose") != nil { verbose, err := cmd.Flags().GetBool("verbose") if err != nil { return false, err } opts.Verbose = verbose } if cmd.Flags().Lookup("keepalive") != nil { keepAlive, err := cmd.Flags().GetString("keepalive") if err != nil { return false, err } if keepAlive != "" { d, err := time.ParseDuration(keepAlive) if err != nil { return false, err } opts.KeepAlive = &api.Duration{Duration: d} } } return thinkExplicit, nil } func runAgentHeadless(cmd *cobra.Command, opts runOptions) error { if err := GenerateAgentHeadless(cmd, agentOptionsFromRunOptions(opts)); err != nil { if handleCloudAuthorizationError(err) { return nil } return err } return nil } func resumeModelFromLatestChat(ctx context.Context) (string, error) { store, err := agentstore.New("") if err != nil { return "", fmt.Errorf("chat resume unavailable: %w", err) } defer store.Close() chat, err := store.LatestChat(ctx) if err != nil { if errors.Is(err, sql.ErrNoRows) { return "", errors.New("no saved chat to resume; pass a model to start a new chat") } return "", fmt.Errorf("could not resume chat: %w", err) } if strings.TrimSpace(chat.Model) == "" { return "", errors.New("latest saved chat has no model; pass a model to start a new chat") } return chat.Model, nil } func SigninHandler(cmd *cobra.Command, args []string) error { client, err := api.ClientFromEnvironment() if err != nil { return err } user, err := client.Whoami(cmd.Context()) if err != nil { var aErr api.AuthorizationError if errors.As(err, &aErr) && aErr.StatusCode == http.StatusUnauthorized { fmt.Println("You need to be signed in to Ollama to run Cloud models.") fmt.Println() if aErr.SigninURL != "" { _ = browser.OpenURL(aErr.SigninURL) fmt.Printf(ConnectInstructions, aErr.SigninURL) } return nil } return err } if user != nil && user.Name != "" { fmt.Printf("You are already signed in as user '%s'\n", user.Name) fmt.Println() return nil } return nil } func SignoutHandler(cmd *cobra.Command, args []string) error { client, err := api.ClientFromEnvironment() if err != nil { return err } err = client.Signout(cmd.Context()) if err != nil { var aErr api.AuthorizationError if errors.As(err, &aErr) && aErr.StatusCode == http.StatusUnauthorized { fmt.Println("You are not signed in to ollama.com") fmt.Println() return nil } else { return err } } fmt.Println("You have signed out of ollama.com") fmt.Println() return nil } func PushHandler(cmd *cobra.Command, args []string) error { client, err := api.ClientFromEnvironment() if err != nil { return err } insecure, err := cmd.Flags().GetBool("insecure") if err != nil { return err } n := model.ParseName(args[0]) if strings.HasSuffix(n.Host, ".ollama.ai") || strings.HasSuffix(n.Host, ".ollama.com") { _, err := client.Whoami(cmd.Context()) if err != nil { var aErr api.AuthorizationError if errors.As(err, &aErr) && aErr.StatusCode == http.StatusUnauthorized { fmt.Println("You need to be signed in to push models to ollama.com.") fmt.Println() if aErr.SigninURL != "" { fmt.Printf(ConnectInstructions, aErr.SigninURL) } return nil } return err } } p := progress.NewProgress(os.Stderr) defer p.Stop() bars := make(map[string]*progress.Bar) var status string var spinner *progress.Spinner fn := func(resp api.ProgressResponse) error { if resp.Digest != "" { if spinner != nil { spinner.Stop() } bar, ok := bars[resp.Digest] if !ok { msg := resp.Status if msg == "" { msg = fmt.Sprintf("pushing %s...", resp.Digest[7:19]) } bar = progress.NewBar(msg, resp.Total, resp.Completed) bars[resp.Digest] = bar p.Add(resp.Digest, bar) } bar.Set(resp.Completed) } else if status != resp.Status { if spinner != nil { spinner.Stop() } status = resp.Status spinner = progress.NewSpinner(status) p.Add(status, spinner) } return nil } request := api.PushRequest{Name: args[0], Insecure: insecure} if err := client.Push(cmd.Context(), &request, fn); err != nil { if spinner != nil { spinner.Stop() } errStr := strings.ToLower(err.Error()) if strings.Contains(errStr, "access denied") || strings.Contains(errStr, "unauthorized") { return errors.New("you are not authorized to push to this namespace, create the model under a namespace you own") } return err } p.Stop() spinner.Stop() destination := n.String() if strings.HasSuffix(n.Host, ".ollama.ai") || strings.HasSuffix(n.Host, ".ollama.com") { destination = "https://ollama.com/" + strings.TrimSuffix(n.DisplayShortest(), ":latest") } fmt.Printf("\nYou can find your model at:\n\n") fmt.Printf("\t%s\n", destination) return nil } func ListHandler(cmd *cobra.Command, args []string) error { client, err := api.ClientFromEnvironment() if err != nil { return err } models, err := client.List(cmd.Context()) if err != nil { return err } var data [][]string for _, m := range models.Models { if len(args) == 0 || strings.HasPrefix(strings.ToLower(m.Name), strings.ToLower(args[0])) { var size string if m.RemoteModel != "" { size = "-" } else { size = format.HumanBytes(m.Size) } data = append(data, []string{m.Name, m.Digest[:12], size, format.HumanTime(m.ModifiedAt, "Never")}) } } table := tablewriter.NewWriter(os.Stdout) table.SetHeader([]string{"NAME", "ID", "SIZE", "MODIFIED"}) table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) table.SetAlignment(tablewriter.ALIGN_LEFT) table.SetHeaderLine(false) table.SetBorder(false) table.SetNoWhiteSpace(true) table.SetTablePadding(" ") table.AppendBulk(data) table.Render() return nil } func ListRunningHandler(cmd *cobra.Command, args []string) error { client, err := api.ClientFromEnvironment() if err != nil { return err } models, err := client.ListRunning(cmd.Context()) if err != nil { return err } var data [][]string for _, m := range models.Models { if len(args) == 0 || strings.HasPrefix(m.Name, args[0]) { var procStr string switch { case m.SizeVRAM == 0: procStr = "100% CPU" case m.SizeVRAM == m.Size: procStr = "100% GPU" case m.SizeVRAM > m.Size || m.Size == 0: procStr = "Unknown" default: sizeCPU := m.Size - m.SizeVRAM cpuPercent := math.Round(float64(sizeCPU) / float64(m.Size) * 100) procStr = fmt.Sprintf("%d%%/%d%% CPU/GPU", int(cpuPercent), int(100-cpuPercent)) } var until string delta := time.Since(m.ExpiresAt) if delta > 0 { until = "Stopping..." } else { until = format.HumanTime(m.ExpiresAt, "Never") } ctxStr := strconv.Itoa(m.ContextLength) data = append(data, []string{m.Name, m.Digest[:12], format.HumanBytes(m.Size), procStr, ctxStr, until}) } } table := tablewriter.NewWriter(os.Stdout) table.SetHeader([]string{"NAME", "ID", "SIZE", "PROCESSOR", "CONTEXT", "UNTIL"}) table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) table.SetAlignment(tablewriter.ALIGN_LEFT) table.SetHeaderLine(false) table.SetBorder(false) table.SetNoWhiteSpace(true) table.SetTablePadding(" ") table.AppendBulk(data) table.Render() return nil } func DeleteHandler(cmd *cobra.Command, args []string) error { client, err := api.ClientFromEnvironment() if err != nil { return err } for _, arg := range args { // Unload the model if it's running before deletion if err := loadOrUnloadModel(cmd, &runOptions{ Model: arg, KeepAlive: &api.Duration{Duration: 0}, }); err != nil { if !strings.Contains(strings.ToLower(err.Error()), "not found") { fmt.Fprintf(os.Stderr, "Warning: unable to stop model '%s'\n", arg) } } if err := client.Delete(cmd.Context(), &api.DeleteRequest{Name: arg}); err != nil { return err } fmt.Printf("deleted '%s'\n", arg) } return nil } func ShowHandler(cmd *cobra.Command, args []string) error { client, err := api.ClientFromEnvironment() if err != nil { return err } license, errLicense := cmd.Flags().GetBool("license") modelfile, errModelfile := cmd.Flags().GetBool("modelfile") parameters, errParams := cmd.Flags().GetBool("parameters") system, errSystem := cmd.Flags().GetBool("system") template, errTemplate := cmd.Flags().GetBool("template") verbose, errVerbose := cmd.Flags().GetBool("verbose") for _, boolErr := range []error{errLicense, errModelfile, errParams, errSystem, errTemplate, errVerbose} { if boolErr != nil { return errors.New("error retrieving flags") } } flagsSet := 0 showType := "" if license { flagsSet++ showType = "license" } if modelfile { flagsSet++ showType = "modelfile" } if parameters { flagsSet++ showType = "parameters" } if system { flagsSet++ showType = "system" } if template { flagsSet++ showType = "template" } if flagsSet > 1 { return errors.New("only one of '--license', '--modelfile', '--parameters', '--system', or '--template' can be specified") } req := api.ShowRequest{Name: args[0], Verbose: verbose} resp, err := client.Show(cmd.Context(), &req) if err != nil { return err } if flagsSet == 1 { switch showType { case "license": fmt.Println(resp.License) case "modelfile": fmt.Println(resp.Modelfile) case "parameters": fmt.Println(resp.Parameters) case "system": fmt.Print(resp.System) case "template": fmt.Print(resp.Template) } return nil } return showInfo(resp, verbose, os.Stdout) } func showInfo(resp *api.ShowResponse, verbose bool, w io.Writer) error { tableRender := func(header string, rows func() [][]string) { fmt.Fprintln(w, " ", header) table := tablewriter.NewWriter(w) table.SetAlignment(tablewriter.ALIGN_LEFT) table.SetBorder(false) table.SetNoWhiteSpace(true) table.SetTablePadding(" ") switch header { case "Template", "System", "License": table.SetColWidth(100) } table.AppendBulk(rows()) table.Render() fmt.Fprintln(w) } tableRender("Model", func() (rows [][]string) { if resp.RemoteHost != "" { rows = append(rows, []string{"", "Remote model", resp.RemoteModel}) rows = append(rows, []string{"", "Remote URL", resp.RemoteHost}) } if resp.ModelInfo != nil { arch, _ := resp.ModelInfo["general.architecture"].(string) if arch != "" { rows = append(rows, []string{"", "architecture", arch}) } var paramStr string if resp.Details.ParameterSize != "" { paramStr = resp.Details.ParameterSize } else if v, ok := resp.ModelInfo["general.parameter_count"]; ok { if f, ok := v.(float64); ok { paramStr = format.HumanNumber(uint64(f)) } } if paramStr != "" { rows = append(rows, []string{"", "parameters", paramStr}) } if v, ok := resp.ModelInfo[fmt.Sprintf("%s.context_length", arch)]; ok { if f, ok := v.(float64); ok { rows = append(rows, []string{"", "context length", strconv.FormatFloat(f, 'f', -1, 64)}) } } if v, ok := resp.ModelInfo[fmt.Sprintf("%s.embedding_length", arch)]; ok { if f, ok := v.(float64); ok { rows = append(rows, []string{"", "embedding length", strconv.FormatFloat(f, 'f', -1, 64)}) } } } else { rows = append(rows, []string{"", "architecture", resp.Details.Family}) rows = append(rows, []string{"", "parameters", resp.Details.ParameterSize}) } rows = append(rows, []string{"", "quantization", resp.Details.QuantizationLevel}) if resp.Requires != "" { rows = append(rows, []string{"", "requires", resp.Requires}) } return }) if len(resp.Capabilities) > 0 { tableRender("Capabilities", func() (rows [][]string) { for _, capability := range resp.Capabilities { rows = append(rows, []string{"", capability.String()}) } return }) } if resp.ProjectorInfo != nil { tableRender("Projector", func() (rows [][]string) { arch, _ := resp.ProjectorInfo["general.architecture"].(string) if arch != "" { rows = append(rows, []string{"", "architecture", arch}) } if v, ok := resp.ProjectorInfo["general.parameter_count"].(float64); ok { rows = append(rows, []string{"", "parameters", format.HumanNumber(uint64(v))}) } projectorValue := func(suffix string) (float64, bool) { for _, modality := range []string{"vision", "audio"} { if v, ok := resp.ProjectorInfo[fmt.Sprintf("%s.%s.%s", arch, modality, suffix)].(float64); ok { return v, true } } return 0, false } if v, ok := projectorValue("embedding_length"); ok { rows = append(rows, []string{"", "embedding length", strconv.FormatFloat(v, 'f', -1, 64)}) } if v, ok := projectorValue("projection_dim"); ok { rows = append(rows, []string{"", "dimensions", strconv.FormatFloat(v, 'f', -1, 64)}) } return }) } if resp.Parameters != "" { tableRender("Parameters", func() (rows [][]string) { scanner := bufio.NewScanner(strings.NewReader(resp.Parameters)) for scanner.Scan() { if text := scanner.Text(); text != "" { rows = append(rows, append([]string{""}, strings.Fields(text)...)) } } return }) } if resp.ModelInfo != nil && verbose { tableRender("Metadata", func() (rows [][]string) { keys := make([]string, 0, len(resp.ModelInfo)) for k := range resp.ModelInfo { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { var v string switch vData := resp.ModelInfo[k].(type) { case bool: v = fmt.Sprintf("%t", vData) case string: v = vData case float64: v = fmt.Sprintf("%g", vData) case []any: targetWidth := 10 // Small width where we are displaying the data in a column var itemsToShow int totalWidth := 1 // Start with 1 for opening bracket // Find how many we can fit for i := range vData { itemStr := fmt.Sprintf("%v", vData[i]) width := runewidth.StringWidth(itemStr) // Add separator width (", ") for all items except the first if i > 0 { width += 2 } // Check if adding this item would exceed our width limit if totalWidth+width > targetWidth && i > 0 { break } totalWidth += width itemsToShow++ } // Format the output if itemsToShow < len(vData) { v = fmt.Sprintf("%v", vData[:itemsToShow]) v = strings.TrimSuffix(v, "]") v += fmt.Sprintf(" ...+%d more]", len(vData)-itemsToShow) } else { v = fmt.Sprintf("%v", vData) } default: v = fmt.Sprintf("%T", vData) } rows = append(rows, []string{"", k, v}) } return }) } if len(resp.Tensors) > 0 && verbose { tableRender("Tensors", func() (rows [][]string) { for _, t := range resp.Tensors { rows = append(rows, []string{"", t.Name, t.Type, fmt.Sprint(t.Shape)}) } return }) } head := func(s string, n int) (rows [][]string) { scanner := bufio.NewScanner(strings.NewReader(s)) count := 0 for scanner.Scan() { text := strings.TrimSpace(scanner.Text()) if text == "" { continue } count++ if n < 0 || count <= n { rows = append(rows, []string{"", text}) } } if n >= 0 && count > n { rows = append(rows, []string{"", "..."}) } return } if resp.System != "" { tableRender("System", func() [][]string { return head(resp.System, 2) }) } if resp.License != "" { tableRender("License", func() [][]string { return head(resp.License, 2) }) } return nil } func CopyHandler(cmd *cobra.Command, args []string) error { client, err := api.ClientFromEnvironment() if err != nil { return err } req := api.CopyRequest{Source: args[0], Destination: args[1]} if err := client.Copy(cmd.Context(), &req); err != nil { return err } fmt.Printf("copied '%s' to '%s'\n", args[0], args[1]) return nil } func PullHandler(cmd *cobra.Command, args []string) error { insecure, err := cmd.Flags().GetBool("insecure") if err != nil { return err } client, err := api.ClientFromEnvironment() if err != nil { return err } p := progress.NewProgress(os.Stderr) defer p.Stop() bars := make(map[string]*progress.Bar) var status string var spinner *progress.Spinner fn := func(resp api.ProgressResponse) error { if resp.Digest != "" { if resp.Completed == 0 { // This is the initial status update for the // layer, which the server sends before // beginning the download, for clients to // compute total size and prepare for // downloads, if needed. // // Skipping this here to avoid showing a 0% // progress bar, which *should* clue the user // into the fact that many things are being // downloaded and that the current active // download is not that last. However, in rare // cases it seems to be triggering to some, and // it isn't worth explaining, so just ignore // and regress to the old UI that keeps giving // you the "But wait, there is more!" after // each "100% done" bar, which is "better." return nil } if spinner != nil { spinner.Stop() } bar, ok := bars[resp.Digest] if !ok { name, isDigest := strings.CutPrefix(resp.Digest, "sha256:") name = strings.TrimSpace(name) if isDigest { name = name[:min(12, len(name))] } bar = progress.NewBar(fmt.Sprintf("pulling %s:", name), resp.Total, resp.Completed) bars[resp.Digest] = bar p.Add(resp.Digest, bar) } bar.Set(resp.Completed) } else if status != resp.Status { if spinner != nil { spinner.Stop() } status = resp.Status spinner = progress.NewSpinner(status) p.Add(status, spinner) } return nil } request := api.PullRequest{Name: args[0], Insecure: insecure} return client.Pull(cmd.Context(), &request, fn) } type runOptions struct { Model string ParentModel string LoadedMessages []api.Message Prompt string Messages []api.Message Format string System string Images []api.ImageData Options map[string]any MultiModal bool KeepAlive *api.Duration Think *api.ThinkValue ShowConnect bool ContextWindowTokens int Resume bool AutoApproveTools bool Verbose bool } func (r runOptions) Copy() runOptions { var loadedMessages []api.Message if r.LoadedMessages != nil { loadedMessages = make([]api.Message, len(r.LoadedMessages)) copy(loadedMessages, r.LoadedMessages) } var messages []api.Message if r.Messages != nil { messages = make([]api.Message, len(r.Messages)) copy(messages, r.Messages) } var images []api.ImageData if r.Images != nil { images = make([]api.ImageData, len(r.Images)) copy(images, r.Images) } var opts map[string]any if r.Options != nil { opts = make(map[string]any, len(r.Options)) for k, v := range r.Options { opts[k] = v } } var think *api.ThinkValue if r.Think != nil { cThink := *r.Think think = &cThink } return runOptions{ Model: r.Model, ParentModel: r.ParentModel, LoadedMessages: loadedMessages, Prompt: r.Prompt, Messages: messages, Format: r.Format, System: r.System, Images: images, Options: opts, MultiModal: r.MultiModal, KeepAlive: r.KeepAlive, Think: think, ShowConnect: r.ShowConnect, ContextWindowTokens: r.ContextWindowTokens, Resume: r.Resume, AutoApproveTools: r.AutoApproveTools, Verbose: r.Verbose, } } func applyShowResponseToRunOptions(opts *runOptions, info *api.ShowResponse) { opts.ParentModel = info.Details.ParentModel opts.LoadedMessages = slices.Clone(info.Messages) if strings.TrimSpace(opts.System) == "" { opts.System = info.System } opts.ContextWindowTokens = contextWindowTokensFromShowResponse(info) } func applyMultiModalCompat(opts *runOptions, info *api.ShowResponse) { audioCapable := slices.Contains(info.Capabilities, model.CapabilityAudio) opts.MultiModal = slices.Contains(info.Capabilities, model.CapabilityVision) || audioCapable // TODO: remove the projector info and vision info checks below, // these are left in for backwards compatibility with older servers // that don't have the capabilities field in the model info if len(info.ProjectorInfo) != 0 { opts.MultiModal = true } for k := range info.ModelInfo { if strings.Contains(k, ".vision.") { opts.MultiModal = true break } } } func contextWindowTokensFromShowResponse(info *api.ShowResponse) int { if info == nil { return 0 } if info.Details.ContextLength > 0 { return info.Details.ContextLength } if info.ModelInfo == nil { return 0 } arch, _ := info.ModelInfo["general.architecture"].(string) if arch == "" { return 0 } switch v := info.ModelInfo[fmt.Sprintf("%s.context_length", arch)].(type) { case int: return v case int64: return int(v) case float64: return int(v) case float32: return int(v) default: return 0 } } func contextWindowTokensForRun(ctx context.Context, client *api.Client, modelName string, fallback int) int { if client == nil || strings.TrimSpace(modelName) == "" { return fallback } if running, err := client.ListRunning(ctx); err == nil { if contextLength := contextWindowTokensFromRunningModels(running.Models, modelName); contextLength > 0 { return contextLength } } if modelref.HasExplicitCloudSource(modelName) { if info, err := client.Show(ctx, &api.ShowRequest{Model: modelName}); err == nil { if contextLength := contextWindowTokensFromShowResponse(info); contextLength > 0 { return contextLength } } } return fallback } func contextWindowTokensFromRunningModels(models []api.ProcessModelResponse, modelName string) int { for _, running := range models { if running.ContextLength > 0 && runningModelMatchesName(running, modelName) { return running.ContextLength } } return 0 } func runningModelMatchesName(running api.ProcessModelResponse, modelName string) bool { for _, candidate := range []string{running.Name, running.Model} { if modelNameMatches(candidate, modelName) { return true } } return false } func modelNameMatches(a, b string) bool { a = strings.TrimSpace(a) b = strings.TrimSpace(b) if a == "" || b == "" { return false } if a == b { return true } return model.ParseName(a).DisplayShortest() == model.ParseName(b).DisplayShortest() } func runCommandArgs(cmd *cobra.Command, args []string) error { if len(args) > 0 { return nil } if flag := cmd.Flags().Lookup("resume"); flag != nil { resume, err := cmd.Flags().GetBool("resume") if err != nil { return err } if resume { return nil } } return cobra.MinimumNArgs(1)(cmd, args) } func prepareRootResumeRunCommand(rootCmd, runCmd *cobra.Command) error { return prepareRootRunCommand(rootCmd, runCmd, true) } func prepareRootModelRunCommand(rootCmd, runCmd *cobra.Command) error { return prepareRootRunCommand(rootCmd, runCmd, false) } func prepareRootRunCommand(rootCmd, runCmd *cobra.Command, resume bool) error { if runCmd == nil { return errors.New("run command unavailable") } runCmd.SetContext(rootCmd.Context()) if resume { if err := runCmd.Flags().Set("resume", "true"); err != nil { return err } } var setErr error rootCmd.Flags().Visit(func(rootFlag *pflag.Flag) { if setErr != nil { return } switch rootFlag.Name { case "model", "resume", "version": return } runFlag := runCmd.Flags().Lookup(rootFlag.Name) if runFlag == nil { return } if err := runCmd.Flags().Set(rootFlag.Name, rootFlag.Value.String()); err != nil { setErr = err } }) if setErr != nil { return setErr } return nil } type runFlagOptions struct { includeResume bool includeVerbose bool includeFormat bool } func registerRunFlags(cmd *cobra.Command, includeResume bool) { registerRunFlagsWithOptions(cmd, runFlagOptions{ includeResume: includeResume, includeVerbose: true, includeFormat: true, }) } func registerRootRunFlags(cmd *cobra.Command) { registerRunFlagsWithOptions(cmd, runFlagOptions{ includeResume: true, }) } func registerRunFlagsWithOptions(cmd *cobra.Command, opts runFlagOptions) { cmd.Flags().String("keepalive", "", "Duration to keep a model loaded (e.g. 5m)") if opts.includeVerbose { cmd.Flags().Bool("verbose", false, "Show timings for response") } cmd.Flags().Bool("insecure", false, "Use an insecure registry") if opts.includeFormat { cmd.Flags().String("format", "", "Response format (e.g. json)") } cmd.Flags().String("think", "", "Enable thinking mode: true/false or high/medium/low for supported models") cmd.Flags().Lookup("think").NoOptDefVal = "true" if opts.includeResume { cmd.Flags().Bool("resume", false, "Resume the latest persisted chat") } cmd.Flags().Bool("auto-approve-tools", false, "Allow agent tools to run without prompting") cmd.Flags().Bool("yolo", false, "Alias for --auto-approve-tools") cmd.Flags().Bool("experimental-yolo", false, "Deprecated: use --auto-approve-tools") cmd.Flags().Bool("truncate", false, "For embedding models: truncate inputs exceeding context length (default: true). Set --truncate=false to error instead") cmd.Flags().Int("dimensions", 0, "Truncate output embeddings to specified dimension (embedding models only)") cmd.Flags().Bool("experimental", false, "Deprecated: agent chat is enabled by default") cmd.Flags().Bool("experimental-websearch", false, "Deprecated: web tools are enabled by default when available") _ = cmd.Flags().MarkHidden("experimental-yolo") _ = cmd.Flags().MarkHidden("experimental") _ = cmd.Flags().MarkHidden("experimental-websearch") imagegen.RegisterFlags(cmd) cmd.Flags().Bool("imagegen", false, "Use the imagegen runner for LLM inference") _ = cmd.Flags().MarkHidden("imagegen") } var ( runHandler = RunHandler rootAgentHandler = runAgentModelPicker agentOnboardingPrompt = tui.RunAgentSignInOnboarding agentOnboardingSignIn = runAgentOnboardingSignIn agentOnboardingSignedInStatus = runAgentOnboardingSignedInStatus errAgentOnboardingNotSignedIn = errors.New("not signed in") ) func runRootResume(rootCmd, runCmd *cobra.Command, args []string) error { if err := prepareRootResumeRunCommand(rootCmd, runCmd); err != nil { return err } if runCmd.PreRunE != nil { if err := runCmd.PreRunE(runCmd, args); err != nil { return err } } return runHandler(runCmd, args) } func runRootModel(rootCmd, runCmd *cobra.Command, modelName string, args []string) error { if modelName == "" { return errors.New("model is required") } if err := prepareRootModelRunCommand(rootCmd, runCmd); err != nil { return err } runArgs := append([]string{modelName}, args...) if runCmd.PreRunE != nil { if err := runCmd.PreRunE(runCmd, runArgs); err != nil { return err } } return runHandler(runCmd, runArgs) } func RunServer(_ *cobra.Command, _ []string) error { if err := initializeKeypair(); err != nil { return err } ln, err := net.Listen("tcp", envconfig.Host().Host) if err != nil { return err } err = server.Serve(ln) if errors.Is(err, http.ErrServerClosed) { return nil } return err } func initializeKeypair() error { home, err := os.UserHomeDir() if err != nil { return err } privKeyPath := filepath.Join(home, ".ollama", "id_ed25519") pubKeyPath := filepath.Join(home, ".ollama", "id_ed25519.pub") _, err = os.Stat(privKeyPath) if os.IsNotExist(err) { fmt.Printf("Couldn't find '%s'. Generating new private key.\n", privKeyPath) cryptoPublicKey, cryptoPrivateKey, err := ed25519.GenerateKey(rand.Reader) if err != nil { return err } privateKeyBytes, err := ssh.MarshalPrivateKey(cryptoPrivateKey, "") if err != nil { return err } if err := os.MkdirAll(filepath.Dir(privKeyPath), 0o755); err != nil { return fmt.Errorf("could not create directory %w", err) } if err := os.WriteFile(privKeyPath, pem.EncodeToMemory(privateKeyBytes), 0o600); err != nil { return err } sshPublicKey, err := ssh.NewPublicKey(cryptoPublicKey) if err != nil { return err } publicKeyBytes := ssh.MarshalAuthorizedKey(sshPublicKey) if err := os.WriteFile(pubKeyPath, publicKeyBytes, 0o644); err != nil { return err } fmt.Printf("Your new public key is: \n\n%s\n", publicKeyBytes) } return nil } func checkServerHeartbeat(cmd *cobra.Command, _ []string) error { client, err := api.ClientFromEnvironment() if err != nil { return err } if err := client.Heartbeat(cmd.Context()); err != nil { if !(strings.Contains(err.Error(), " refused") || strings.Contains(err.Error(), "could not connect")) { return err } if err := startApp(cmd.Context(), client); err != nil { return err } } return nil } func versionHandler(cmd *cobra.Command, _ []string) { client, err := api.ClientFromEnvironment() if err != nil { return } serverVersion, err := client.Version(cmd.Context()) if err != nil { fmt.Println("Warning: could not connect to a running Ollama instance") } if serverVersion != "" { fmt.Printf("ollama version is %s\n", serverVersion) } if serverVersion != version.Version { fmt.Printf("Warning: client version is %s\n", version.Version) } } func appendEnvDocs(cmd *cobra.Command, envs []envconfig.EnvVar) { if len(envs) == 0 { return } envUsage := ` Environment Variables: ` for _, e := range envs { envUsage += fmt.Sprintf(" %-27s %s\n", e.Name, e.Description) } cmd.SetUsageTemplate(cmd.UsageTemplate() + envUsage) } // ensureServerRunning checks if the ollama server is running and starts it in the background if not. func ensureServerRunning(ctx context.Context) error { client, err := api.ClientFromEnvironment() if err != nil { return err } // Check if server is already running if err := client.Heartbeat(ctx); err == nil { return nil // server is already running } // Server not running, start it in the background exe, err := os.Executable() if err != nil { return fmt.Errorf("could not find executable: %w", err) } serverCmd := exec.CommandContext(ctx, exe, "serve") serverCmd.Env = os.Environ() serverCmd.SysProcAttr = backgroundServerSysProcAttr() if err := serverCmd.Start(); err != nil { return fmt.Errorf("failed to start server: %w", err) } // Wait for the server to be ready for { time.Sleep(500 * time.Millisecond) if err := client.Heartbeat(ctx); err == nil { return nil // server has started } } } func launchInteractiveModel(cmd *cobra.Command, modelName string) error { opts := runOptions{ Model: modelName, Options: map[string]any{}, ShowConnect: true, } thinkExplicit, err := applyRunFlagsToOptions(cmd, &opts) if err != nil { return err } client, err := api.ClientFromEnvironment() if err != nil { return err } requestedCloud := modelref.HasExplicitCloudSource(modelName) info, err := func() (*api.ShowResponse, error) { showReq := &api.ShowRequest{Name: modelName} info, err := client.Show(cmd.Context(), showReq) var se api.StatusError if errors.As(err, &se) && se.StatusCode == http.StatusNotFound { if requestedCloud { return nil, err } if err := PullHandler(cmd, []string{modelName}); err != nil { return nil, err } return client.Show(cmd.Context(), &api.ShowRequest{Name: modelName}) } return info, err }() if err != nil { if handleCloudAuthorizationError(err) { return nil } return err } ensureCloudStub(cmd.Context(), client, modelName) opts.Think, err = inferThinkingOption(&info.Capabilities, &opts, thinkExplicit) if err != nil { return err } applyMultiModalCompat(&opts, info) applyShowResponseToRunOptions(&opts, info) if info.RemoteHost != "" || requestedCloud { if err := loadOrUnloadModel(cmd, &opts); err != nil { return fmt.Errorf("error loading model: %w", err) } } agentOpts := agentOptionsFromRunOptions(opts) agentOpts.ContextWindowTokens = contextWindowTokensForRun(cmd.Context(), client, opts.Model, opts.ContextWindowTokens) if err := config.SetLastModel(opts.Model); err != nil { return err } if err := GenerateAgentTUI(cmd, agentOpts); err != nil { if handleCloudAuthorizationError(err) { return nil } return fmt.Errorf("error running agent: %w", err) } return nil } func runAgentModelPicker(cmd *cobra.Command) { if err := ensureServerRunning(cmd.Context()); err != nil { fmt.Fprintf(os.Stderr, "Error starting server: %v\n", err) return } signIn, err := maybeRunAgentOnboarding(cmd.Context()) if errors.Is(err, launch.ErrCancelled) { return } if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) return } if signIn { if err := agentOnboardingSignIn(cmd.Context()); err != nil && !errors.Is(err, launch.ErrCancelled) { fmt.Fprintf(os.Stderr, "Warning: unable to sign in: %v\n\n", err) } } accountPrefetch := launch.StartAccountStatePrefetch(cmd.Context()) deps := agentModelPickerDeps{ resolveRunModel: launch.ResolveRunModel, runModel: launchInteractiveModel, accountState: accountPrefetch.StateIfReady, accountStateUpdates: accountPrefetch.StateUpdates, } if err := runAgentModelPickerWithDeps(cmd, deps); err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) } } func maybeRunAgentOnboarding(ctx context.Context) (bool, error) { if envconfig.NoCloud() || config.AgentSignInPromptSeen() { return false, nil } signedIn, known := agentOnboardingSignedInStatus(ctx) if signedIn { return false, config.SetAgentSignInPromptSeen(true) } if !known { return false, nil } signIn, err := agentOnboardingPrompt() if errors.Is(err, tui.ErrCancelled) { return false, launch.ErrCancelled } if err != nil { return false, err } if err := config.SetAgentSignInPromptSeen(true); err != nil { return false, err } return signIn, nil } func runAgentOnboardingSignedInStatus(ctx context.Context) (signedIn bool, known bool) { client, err := api.ClientFromEnvironment() if err != nil { return false, false } user, err := client.Whoami(ctx) if err == nil { return user != nil && user.Name != "", true } var authErr api.AuthorizationError if errors.As(err, &authErr) { return false, true } return false, false } func runAgentOnboardingSignIn(ctx context.Context) error { client, err := api.ClientFromEnvironment() if err != nil { return err } user, err := client.Whoami(ctx) if err == nil && user != nil && user.Name != "" { return nil } if err == nil { return errAgentOnboardingNotSignedIn } var authErr api.AuthorizationError if !errors.As(err, &authErr) || authErr.SigninURL == "" { return err } _, err = tui.RunSignIn("Ollama", authErr.SigninURL) if errors.Is(err, tui.ErrCancelled) { return launch.ErrCancelled } return err } type agentModelPickerDeps struct { resolveRunModel func(context.Context, launch.RunModelRequest) (string, error) runModel func(*cobra.Command, string) error accountState func() *launch.AccountState accountStateUpdates func(context.Context) <-chan *launch.AccountState } func runAgentModelPickerWithDeps(cmd *cobra.Command, deps agentModelPickerDeps) error { req := launch.RunModelRequest{} if deps.accountState != nil { req.AccountState = deps.accountState() req.AccountStateProvider = deps.accountState } req.AccountStateUpdates = deps.accountStateUpdates modelName, err := deps.resolveRunModel(cmd.Context(), req) if errors.Is(err, launch.ErrCancelled) { return nil } if errors.Is(err, launch.ErrPlanVerificationUnavailable) && !req.ForcePicker { req.ForcePicker = true req.AccountState = &launch.AccountState{} req.AccountStateProvider = nil modelName, err = deps.resolveRunModel(cmd.Context(), req) if errors.Is(err, launch.ErrCancelled) { return nil } } if err != nil { return fmt.Errorf("selecting model: %w", err) } return deps.runModel(cmd, modelName) } // runInteractiveTUI runs the main interactive TUI menu. func runInteractiveTUI(cmd *cobra.Command) { // Ensure the server is running before showing the TUI if err := ensureServerRunning(cmd.Context()); err != nil { fmt.Fprintf(os.Stderr, "Error starting server: %v\n", err) return } accountPrefetch := launch.StartAccountStatePrefetch(cmd.Context()) deps := launcherDeps{ buildState: launch.BuildLauncherState, runMenu: tui.RunMenu, resolveRunModel: launch.ResolveRunModel, launchIntegration: launch.LaunchIntegration, runModel: launchInteractiveModel, accountState: accountPrefetch.StateIfReady, accountStateUpdates: accountPrefetch.StateUpdates, } for { continueLoop, err := runInteractiveTUIStep(cmd, deps) if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) } if !continueLoop { return } } } type launcherDeps struct { buildState func(context.Context) (*launch.LauncherState, error) runMenu func(*launch.LauncherState) (tui.TUIAction, error) resolveRunModel func(context.Context, launch.RunModelRequest) (string, error) launchIntegration func(context.Context, launch.IntegrationLaunchRequest) error runModel func(*cobra.Command, string) error accountState func() *launch.AccountState accountStateUpdates func(context.Context) <-chan *launch.AccountState } func runInteractiveTUIStep(cmd *cobra.Command, deps launcherDeps) (bool, error) { state, err := deps.buildState(cmd.Context()) if err != nil { return false, fmt.Errorf("build launcher state: %w", err) } if state != nil && deps.accountState != nil { state.AccountState = deps.accountState() } action, err := deps.runMenu(state) if err != nil { return false, fmt.Errorf("run launcher menu: %w", err) } return runLauncherAction(cmd, action, deps) } func saveLauncherSelection(action tui.TUIAction) { // Best effort only: this affects menu recall, not launch correctness. _ = config.SetLastSelection(action.LastSelection()) } func runLauncherAction(cmd *cobra.Command, action tui.TUIAction, deps launcherDeps) (bool, error) { switch action.Kind { case tui.TUIActionNone: return false, nil case tui.TUIActionRunModel: saveLauncherSelection(action) req := action.RunModelRequest() if deps.accountState != nil { req.AccountState = deps.accountState() req.AccountStateProvider = deps.accountState } req.AccountStateUpdates = deps.accountStateUpdates modelName, err := deps.resolveRunModel(cmd.Context(), req) if errors.Is(err, launch.ErrCancelled) { return true, nil } if err != nil { return true, fmt.Errorf("selecting model: %w", err) } if err := deps.runModel(cmd, modelName); err != nil { return true, err } return true, nil case tui.TUIActionLaunchIntegration: saveLauncherSelection(action) req := action.IntegrationLaunchRequest() if deps.accountState != nil { req.AccountState = deps.accountState() req.AccountStateProvider = deps.accountState } req.AccountStateUpdates = deps.accountStateUpdates err := deps.launchIntegration(cmd.Context(), req) if errors.Is(err, launch.ErrCancelled) { return true, nil } if err != nil { return true, fmt.Errorf("launching %s: %w", action.Integration, err) } if launcherActionExitsLoop(action.Integration) { return false, nil } return true, nil default: return false, fmt.Errorf("unknown launcher action: %d", action.Kind) } } func launcherActionExitsLoop(integration string) bool { switch integration { case "codex-app", "vscode": return true default: return false } } func NewCLI() *cobra.Command { log.SetFlags(log.LstdFlags | log.Lshortfile) cobra.EnableCommandSorting = false if runtime.GOOS == "windows" && term.IsTerminal(int(os.Stdout.Fd())) { console.ConsoleFromFile(os.Stdin) //nolint:errcheck } var runCmd *cobra.Command rootCmd := &cobra.Command{ Use: "ollama", Short: "Run large language models and connect them to agents", SilenceUsage: true, SilenceErrors: true, CompletionOptions: cobra.CompletionOptions{ DisableDefaultCmd: true, }, RunE: func(cmd *cobra.Command, args []string) error { if version, _ := cmd.Flags().GetBool("version"); version { versionHandler(cmd, args) return nil } if resume, _ := cmd.Flags().GetBool("resume"); resume { modelName, _ := cmd.Flags().GetString("model") if modelName != "" { args = append([]string{modelName}, args...) } return runRootResume(cmd, runCmd, args) } if modelName, _ := cmd.Flags().GetString("model"); modelName != "" { return runRootModel(cmd, runCmd, modelName, args) } rootAgentHandler(cmd) return nil }, } rootCmd.Flags().BoolP("version", "v", false, "Show version information") rootCmd.Flags().String("model", "", "Run a model") registerRootRunFlags(rootCmd) createCmd := &cobra.Command{ Use: "create MODEL", Short: "Create a model", Args: cobra.ExactArgs(1), PreRunE: func(cmd *cobra.Command, args []string) error { // Skip server check for experimental mode (writes directly to disk) if experimental, _ := cmd.Flags().GetBool("experimental"); experimental { return nil } return checkServerHeartbeat(cmd, args) }, RunE: CreateHandler, } createCmd.Flags().StringP("file", "f", "", "Name of the Modelfile (default \"Modelfile\")") createCmd.Flags().StringP("quantize", "q", "", "Quantize model to this level (e.g. q4_K_M)") createCmd.Flags().String("draft-quantize", "", "Quantize draft model to this level") createCmd.Flags().Bool("experimental", false, "Enable experimental safetensors model creation") showCmd := &cobra.Command{ Use: "show MODEL", Short: "Show information for a model", Args: cobra.ExactArgs(1), PreRunE: checkServerHeartbeat, RunE: ShowHandler, } showCmd.Flags().Bool("license", false, "Show license of a model") showCmd.Flags().Bool("modelfile", false, "Show Modelfile of a model") showCmd.Flags().Bool("parameters", false, "Show parameters of a model") showCmd.Flags().Bool("template", false, "Show template of a model") showCmd.Flags().Bool("system", false, "Show system message of a model") showCmd.Flags().BoolP("verbose", "v", false, "Show detailed model information") runCmd = &cobra.Command{ Use: "run [MODEL] [PROMPT]", Short: "Run a model", Args: runCommandArgs, PreRunE: checkServerHeartbeat, RunE: RunHandler, } registerRunFlags(runCmd, true) stopCmd := &cobra.Command{ Use: "stop MODEL", Short: "Stop a running model", Args: cobra.ExactArgs(1), PreRunE: checkServerHeartbeat, RunE: StopHandler, } serveCmd := &cobra.Command{ Use: "serve", Aliases: []string{"start"}, Short: "Start Ollama", Args: cobra.ExactArgs(0), RunE: RunServer, } pullCmd := &cobra.Command{ Use: "pull MODEL", Short: "Pull a model from a registry", Args: cobra.ExactArgs(1), PreRunE: checkServerHeartbeat, RunE: PullHandler, } pullCmd.Flags().Bool("insecure", false, "Use an insecure registry") pushCmd := &cobra.Command{ Use: "push MODEL", Short: "Push a model to a registry", Args: cobra.ExactArgs(1), PreRunE: checkServerHeartbeat, RunE: PushHandler, } pushCmd.Flags().Bool("insecure", false, "Use an insecure registry") signinCmd := &cobra.Command{ Use: "signin", Short: "Sign in to ollama.com", Args: cobra.ExactArgs(0), PreRunE: checkServerHeartbeat, RunE: SigninHandler, } loginCmd := &cobra.Command{ Use: "login", Short: "Sign in to ollama.com", Hidden: true, Args: cobra.ExactArgs(0), PreRunE: checkServerHeartbeat, RunE: SigninHandler, } signoutCmd := &cobra.Command{ Use: "signout", Short: "Sign out from ollama.com", Args: cobra.ExactArgs(0), PreRunE: checkServerHeartbeat, RunE: SignoutHandler, } logoutCmd := &cobra.Command{ Use: "logout", Short: "Sign out from ollama.com", Hidden: true, Args: cobra.ExactArgs(0), PreRunE: checkServerHeartbeat, RunE: SignoutHandler, } listCmd := &cobra.Command{ Use: "list", Aliases: []string{"ls"}, Short: "List models", PreRunE: checkServerHeartbeat, RunE: ListHandler, } psCmd := &cobra.Command{ Use: "ps", Short: "List running models", PreRunE: checkServerHeartbeat, RunE: ListRunningHandler, } copyCmd := &cobra.Command{ Use: "cp SOURCE DESTINATION", Short: "Copy a model", Args: cobra.ExactArgs(2), PreRunE: checkServerHeartbeat, RunE: CopyHandler, } deleteCmd := &cobra.Command{ Use: "rm MODEL [MODEL...]", Short: "Remove a model", Args: cobra.MinimumNArgs(1), PreRunE: checkServerHeartbeat, RunE: DeleteHandler, } runnerCmd := &cobra.Command{ Use: "runner", Hidden: true, RunE: func(cmd *cobra.Command, args []string) error { return runner.Execute(os.Args[1:]) }, FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true}, } runnerCmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { _ = runner.Execute(args[1:]) }) var gpuDiscoverLibDirs []string gpuDiscoverCmd := &cobra.Command{ Use: "gpu-discover", Hidden: true, RunE: func(cmd *cobra.Command, _ []string) error { return discover.RunNativeProbeCommand(cmd.Context(), gpuDiscoverLibDirs, os.Stdout) }, } gpuDiscoverCmd.Flags().StringArrayVar(&gpuDiscoverLibDirs, "lib-dir", nil, "Ollama runtime library directory") envVars := envconfig.AsMap() envs := []envconfig.EnvVar{envVars["OLLAMA_HOST"]} for _, cmd := range []*cobra.Command{ createCmd, showCmd, runCmd, stopCmd, pullCmd, pushCmd, listCmd, psCmd, copyCmd, deleteCmd, serveCmd, } { switch cmd { case runCmd: imagegen.AppendFlagsDocs(cmd) appendEnvDocs(cmd, []envconfig.EnvVar{envVars["OLLAMA_EDITOR"], envVars["OLLAMA_HOST"], envVars["OLLAMA_NOHISTORY"]}) case serveCmd: appendEnvDocs(cmd, []envconfig.EnvVar{ envVars["OLLAMA_DEBUG"], envVars["OLLAMA_HOST"], envVars["OLLAMA_CONTEXT_LENGTH"], envVars["OLLAMA_KEEP_ALIVE"], envVars["OLLAMA_MAX_LOADED_MODELS"], envVars["OLLAMA_MAX_TRANSFER_STREAMS"], envVars["OLLAMA_MAX_QUEUE"], envVars["OLLAMA_MODELS"], envVars["OLLAMA_NUM_PARALLEL"], envVars["OLLAMA_NO_CLOUD"], envVars["OLLAMA_NOPRUNE"], envVars["OLLAMA_ORIGINS"], envVars["OLLAMA_SCHED_SPREAD"], envVars["OLLAMA_FLASH_ATTENTION"], envVars["OLLAMA_KV_CACHE_TYPE"], envVars["OLLAMA_LLM_LIBRARY"], envVars["OLLAMA_GPU_OVERHEAD"], envVars["OLLAMA_IGPU_ENABLE"], envVars["LLAMA_ARG_FIT"], envVars["LLAMA_ARG_FIT_TARGET"], envVars["OLLAMA_LOAD_TIMEOUT"], }) default: appendEnvDocs(cmd, envs) } } rootCmd.AddCommand( serveCmd, createCmd, showCmd, runCmd, stopCmd, pullCmd, pushCmd, signinCmd, loginCmd, signoutCmd, logoutCmd, listCmd, psCmd, copyCmd, deleteCmd, runnerCmd, gpuDiscoverCmd, launch.LaunchCmd(checkServerHeartbeat, runInteractiveTUI), ) return rootCmd } // If the user has explicitly set thinking options, either through the CLI or // through the `/set think` or `set nothink` interactive options, then we // respect them. Otherwise, we check model capabilities to see if the model // supports thinking. If the model does support thinking, we enable it. // Otherwise, we unset the thinking option (which is different than setting it // to false). // // If capabilities are not provided, we fetch them from the server. func inferThinkingOption(caps *[]model.Capability, runOpts *runOptions, explicitlySetByUser bool) (*api.ThinkValue, error) { if explicitlySetByUser { return runOpts.Think, nil } if caps == nil { client, err := api.ClientFromEnvironment() if err != nil { return nil, err } ret, err := client.Show(context.Background(), &api.ShowRequest{ Model: runOpts.Model, }) if err != nil { return nil, err } caps = &ret.Capabilities } thinkingSupported := false for _, cap := range *caps { if cap == model.CapabilityThinking { thinkingSupported = true } } if thinkingSupported { return &api.ThinkValue{Value: true}, nil } return nil, nil }