cmd.go 33.5 KB
Newer Older
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1
2
3
package cmd

import (
Michael Yang's avatar
Michael Yang committed
4
	"bufio"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
5
	"context"
6
7
	"crypto/ed25519"
	"crypto/rand"
8
	"encoding/json"
9
	"encoding/pem"
Michael Yang's avatar
Michael Yang committed
10
	"errors"
Bruce MacDonald's avatar
Bruce MacDonald committed
11
	"fmt"
Michael Yang's avatar
Michael Yang committed
12
	"io"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
13
	"log"
14
	"math"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
15
	"net"
16
	"net/http"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
17
	"os"
18
	"os/signal"
19
	"path/filepath"
20
	"runtime"
21
	"slices"
22
	"sort"
Michael Yang's avatar
Michael Yang committed
23
	"strconv"
Michael Yang's avatar
Michael Yang committed
24
	"strings"
25
	"sync/atomic"
26
	"syscall"
Michael Yang's avatar
Michael Yang committed
27
	"time"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
28

29
	"github.com/containerd/console"
30
	"github.com/mattn/go-runewidth"
Patrick Devine's avatar
Patrick Devine committed
31
	"github.com/olekukonko/tablewriter"
Michael Yang's avatar
Michael Yang committed
32
	"github.com/spf13/cobra"
33
	"golang.org/x/crypto/ssh"
34
	"golang.org/x/sync/errgroup"
35
	"golang.org/x/term"
Michael Yang's avatar
Michael Yang committed
36

37
	"github.com/ollama/ollama/api"
38
	"github.com/ollama/ollama/envconfig"
39
	"github.com/ollama/ollama/format"
40
	"github.com/ollama/ollama/parser"
41
	"github.com/ollama/ollama/progress"
Jesse Gross's avatar
Jesse Gross committed
42
	"github.com/ollama/ollama/runner"
43
	"github.com/ollama/ollama/server"
44
	"github.com/ollama/ollama/types/model"
45
	"github.com/ollama/ollama/types/syncmap"
46
	"github.com/ollama/ollama/version"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
47
48
)

49
var errModelfileNotFound = errors.New("specified Modelfile wasn't found")
50
51

func getModelfileName(cmd *cobra.Command) (string, error) {
52
	filename, _ := cmd.Flags().GetString("file")
53
54
55
56
57
58

	if filename == "" {
		filename = "Modelfile"
	}

	absName, err := filepath.Abs(filename)
59
	if err != nil {
60
		return "", err
61
62
	}

63
	_, err = os.Stat(absName)
64
	if err != nil {
65
		return "", err
66
	}
67

68
69
70
71
	return absName, nil
}

func CreateHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
72
73
74
	p := progress.NewProgress(os.Stderr)
	defer p.Stop()

75
76
77
78
79
80
81
82
83
84
	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 {
Michael Yang's avatar
Michael Yang committed
85
		return err
86
87
88
89
90
91
92
93
	} else {
		f, err := os.Open(filename)
		if err != nil {
			return err
		}

		reader = f
		defer f.Close()
Michael Yang's avatar
Michael Yang committed
94
95
	}

96
	modelfile, err := parser.ParseFile(reader)
Michael Yang's avatar
Michael Yang committed
97
98
99
100
	if err != nil {
		return err
	}

101
102
103
104
	status := "gathering model components"
	spinner := progress.NewSpinner(status)
	p.Add(status, spinner)

105
	req, err := modelfile.CreateRequest(filepath.Dir(filename))
Michael Yang's avatar
Michael Yang committed
106
107
108
	if err != nil {
		return err
	}
109
	spinner.Stop()
Michael Yang's avatar
Michael Yang committed
110

111
	req.Model = args[0]
112
113
114
115
	quantize, _ := cmd.Flags().GetString("quantize")
	if quantize != "" {
		req.Quantize = quantize
	}
116

117
118
119
120
121
	client, err := api.ClientFromEnvironment()
	if err != nil {
		return err
	}

122
123
124
125
126
127
	var g errgroup.Group
	g.SetLimit(max(runtime.GOMAXPROCS(0)-1, 1))

	files := syncmap.NewSyncMap[string, string]()
	for f, digest := range req.Files {
		g.Go(func() error {
128
			if _, err := createBlob(cmd, client, f, digest, p); err != nil {
Michael Yang's avatar
Michael Yang committed
129
130
				return err
			}
131
132
133
134
135
136
137

			// TODO: this is incorrect since the file might be in a subdirectory
			//       instead this should take the path relative to the model directory
			//       but the current implementation does not allow this
			files.Store(filepath.Base(f), digest)
			return nil
		})
138
	}
Michael Yang's avatar
Michael Yang committed
139

140
141
142
	adapters := syncmap.NewSyncMap[string, string]()
	for f, digest := range req.Adapters {
		g.Go(func() error {
143
			if _, err := createBlob(cmd, client, f, digest, p); err != nil {
Michael Yang's avatar
Michael Yang committed
144
145
				return err
			}
146
147
148
149
150

			// TODO: same here
			adapters.Store(filepath.Base(f), digest)
			return nil
		})
Michael Yang's avatar
Michael Yang committed
151
	}
Michael Yang's avatar
Michael Yang committed
152

153
154
155
156
157
158
159
	if err := g.Wait(); err != nil {
		return err
	}

	req.Files = files.Items()
	req.Adapters = adapters.Items()

Michael Yang's avatar
Michael Yang committed
160
	bars := make(map[string]*progress.Bar)
161
	fn := func(resp api.ProgressResponse) error {
Michael Yang's avatar
Michael Yang committed
162
163
164
		if resp.Digest != "" {
			bar, ok := bars[resp.Digest]
			if !ok {
165
				bar = progress.NewBar(fmt.Sprintf("pulling %s...", resp.Digest[7:19]), resp.Total, resp.Completed)
Michael Yang's avatar
Michael Yang committed
166
167
168
169
170
171
172
173
174
175
176
177
178
				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)
		}

179
180
181
		return nil
	}

182
	if err := client.Create(cmd.Context(), req, fn); err != nil {
183
184
185
		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")
		}
186
187
188
189
190
191
		return err
	}

	return nil
}

192
193
func createBlob(cmd *cobra.Command, client *api.Client, path string, digest string, p *progress.Progress) (string, error) {
	realPath, err := filepath.EvalSymlinks(path)
Michael Yang's avatar
Michael Yang committed
194
195
196
197
	if err != nil {
		return "", err
	}

198
	bin, err := os.Open(realPath)
199
200
201
202
203
	if err != nil {
		return "", err
	}
	defer bin.Close()

204
205
206
207
208
209
210
211
	// Get file info to retrieve the size
	fileInfo, err := bin.Stat()
	if err != nil {
		return "", err
	}
	fileSize := fileInfo.Size()

	var pw progressWriter
212
213
214
215
	status := fmt.Sprintf("copying file %s 0%%", digest)
	spinner := progress.NewSpinner(status)
	p.Add(status, spinner)
	defer spinner.Stop()
216
217
218
219
220
221
222
223
224
225

	done := make(chan struct{})
	defer close(done)

	go func() {
		ticker := time.NewTicker(60 * time.Millisecond)
		defer ticker.Stop()
		for {
			select {
			case <-ticker.C:
226
				spinner.SetMessage(fmt.Sprintf("copying file %s %d%%", digest, int(100*pw.n.Load()/fileSize)))
227
			case <-done:
228
				spinner.SetMessage(fmt.Sprintf("copying file %s 100%%", digest))
229
230
231
232
233
				return
			}
		}
	}()

234
	if err := client.CreateBlob(cmd.Context(), digest, io.TeeReader(bin, &pw)); err != nil {
235
236
237
238
239
		return "", err
	}
	return digest, nil
}

240
241
242
243
244
245
246
247
248
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
}

Patrick Devine's avatar
Patrick Devine committed
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
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
	}

	req := &api.GenerateRequest{
		Model:     opts.Model,
		KeepAlive: opts.KeepAlive,
	}

	return client.Generate(cmd.Context(), 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])
		}
278
		return err
Patrick Devine's avatar
Patrick Devine committed
279
280
281
282
	}
	return nil
}

283
func RunHandler(cmd *cobra.Command, args []string) error {
284
285
286
	interactive := true

	opts := runOptions{
287
288
		Model:    args[0],
		WordWrap: os.Getenv("TERM") == "xterm-256color",
289
		Options:  map[string]any{},
290
291
292
293
294
295
296
297
	}

	format, err := cmd.Flags().GetString("format")
	if err != nil {
		return err
	}
	opts.Format = format

298
299
300
301
302
303
304
305
306
307
308
309
	keepAlive, err := cmd.Flags().GetString("keepalive")
	if err != nil {
		return err
	}
	if keepAlive != "" {
		d, err := time.ParseDuration(keepAlive)
		if err != nil {
			return err
		}
		opts.KeepAlive = &api.Duration{Duration: d}
	}

310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
	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
		}

		prompts = append([]string{string(in)}, prompts...)
		opts.WordWrap = false
		interactive = false
	}
	opts.Prompt = strings.Join(prompts, " ")
	if len(prompts) > 0 {
		interactive = false
	}
326
327
328
329
	// Be quiet if we're redirecting to a pipe or file
	if !term.IsTerminal(int(os.Stdout.Fd())) {
		interactive = false
	}
330
331
332
333
334
335
336

	nowrap, err := cmd.Flags().GetBool("nowordwrap")
	if err != nil {
		return err
	}
	opts.WordWrap = !nowrap

337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
	// Fill out the rest of the options based on information about the
	// model.
	client, err := api.ClientFromEnvironment()
	if err != nil {
		return err
	}

	name := args[0]
	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 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 {
		return err
359
360
	}

361
362
363
364
365
	opts.MultiModal = slices.Contains(info.Capabilities, model.CapabilityVision)

	// 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
366
367
368
369
370
371
372
373
374
375
	if len(info.ProjectorInfo) != 0 {
		opts.MultiModal = true
	}
	for k := range info.ModelInfo {
		if strings.Contains(k, ".vision.") {
			opts.MultiModal = true
			break
		}
	}

376
377
378
	opts.ParentModel = info.Details.ParentModel

	if interactive {
Patrick Devine's avatar
Patrick Devine committed
379
		if err := loadOrUnloadModel(cmd, &opts); err != nil {
Michael Yang's avatar
Michael Yang committed
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
			return err
		}

		for _, msg := range info.Messages {
			switch msg.Role {
			case "user":
				fmt.Printf(">>> %s\n", msg.Content)
			case "assistant":
				state := &displayResponseState{}
				displayResponse(msg.Content, opts.WordWrap, state)
				fmt.Println()
				fmt.Println()
			}
		}

395
396
397
		return generateInteractive(cmd, opts)
	}
	return generate(cmd, opts)
Bruce MacDonald's avatar
Bruce MacDonald committed
398
399
}

400
func PushHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
401
	client, err := api.ClientFromEnvironment()
402
403
404
	if err != nil {
		return err
	}
405

406
407
408
409
410
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
411
412
413
414
	p := progress.NewProgress(os.Stderr)
	defer p.Stop()

	bars := make(map[string]*progress.Bar)
415
416
	var status string
	var spinner *progress.Spinner
Michael Yang's avatar
Michael Yang committed
417

418
	fn := func(resp api.ProgressResponse) error {
Michael Yang's avatar
Michael Yang committed
419
		if resp.Digest != "" {
420
421
422
			if spinner != nil {
				spinner.Stop()
			}
Michael Yang's avatar
Michael Yang committed
423
424
425

			bar, ok := bars[resp.Digest]
			if !ok {
426
				bar = progress.NewBar(fmt.Sprintf("pushing %s...", resp.Digest[7:19]), resp.Total, resp.Completed)
Michael Yang's avatar
Michael Yang committed
427
428
429
430
431
432
				bars[resp.Digest] = bar
				p.Add(resp.Digest, bar)
			}

			bar.Set(resp.Completed)
		} else if status != resp.Status {
433
434
435
			if spinner != nil {
				spinner.Stop()
			}
Michael Yang's avatar
Michael Yang committed
436
437
438
439
440
441

			status = resp.Status
			spinner = progress.NewSpinner(status)
			p.Add(status, spinner)
		}

442
443
444
		return nil
	}

Michael Yang's avatar
Michael Yang committed
445
	request := api.PushRequest{Name: args[0], Insecure: insecure}
446
447

	n := model.ParseName(args[0])
Michael Yang's avatar
Michael Yang committed
448
	if err := client.Push(cmd.Context(), &request, fn); err != nil {
449
450
451
452
453
454
		if spinner != nil {
			spinner.Stop()
		}
		if strings.Contains(err.Error(), "access denied") {
			return errors.New("you are not authorized to push to this namespace, create the model under a namespace you own")
		}
Michael Yang's avatar
Michael Yang committed
455
456
457
		return err
	}

458
	p.Stop()
459
	spinner.Stop()
460
461
462
463
464
465
466
467

	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)

Michael Yang's avatar
Michael Yang committed
468
	return nil
469
470
}

471
func ListHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
472
	client, err := api.ClientFromEnvironment()
473
474
475
	if err != nil {
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
476

Michael Yang's avatar
Michael Yang committed
477
	models, err := client.List(cmd.Context())
Patrick Devine's avatar
Patrick Devine committed
478
479
480
481
482
483
484
	if err != nil {
		return err
	}

	var data [][]string

	for _, m := range models.Models {
485
		if len(args) == 0 || strings.HasPrefix(strings.ToLower(m.Name), strings.ToLower(args[0])) {
486
			data = append(data, []string{m.Name, m.Digest[:12], format.HumanBytes(m.Size), format.HumanTime(m.ModifiedAt, "Never")})
Michael Yang's avatar
Michael Yang committed
487
		}
Patrick Devine's avatar
Patrick Devine committed
488
489
490
	}

	table := tablewriter.NewWriter(os.Stdout)
Patrick Devine's avatar
Patrick Devine committed
491
	table.SetHeader([]string{"NAME", "ID", "SIZE", "MODIFIED"})
Patrick Devine's avatar
Patrick Devine committed
492
493
494
495
496
	table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
	table.SetAlignment(tablewriter.ALIGN_LEFT)
	table.SetHeaderLine(false)
	table.SetBorder(false)
	table.SetNoWhiteSpace(true)
Michael Yang's avatar
Michael Yang committed
497
	table.SetTablePadding("    ")
Patrick Devine's avatar
Patrick Devine committed
498
499
500
501
502
503
	table.AppendBulk(data)
	table.Render()

	return nil
}

504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
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))
			}
Patrick Devine's avatar
Patrick Devine committed
532
533
534
535
536
537
538
539
540

			var until string
			delta := time.Since(m.ExpiresAt)
			if delta > 0 {
				until = "Stopping..."
			} else {
				until = format.HumanTime(m.ExpiresAt, "Never")
			}
			data = append(data, []string{m.Name, m.Digest[:12], format.HumanBytes(m.Size), procStr, until})
541
542
543
544
545
546
547
548
549
550
		}
	}

	table := tablewriter.NewWriter(os.Stdout)
	table.SetHeader([]string{"NAME", "ID", "SIZE", "PROCESSOR", "UNTIL"})
	table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
	table.SetAlignment(tablewriter.ALIGN_LEFT)
	table.SetHeaderLine(false)
	table.SetBorder(false)
	table.SetNoWhiteSpace(true)
Michael Yang's avatar
Michael Yang committed
551
	table.SetTablePadding("    ")
552
553
554
555
556
557
	table.AppendBulk(data)
	table.Render()

	return nil
}

558
func DeleteHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
559
	client, err := api.ClientFromEnvironment()
560
561
562
	if err != nil {
		return err
	}
563

564
565
566
567
568
569
570
571
572
573
574
	// Unload the model if it's running before deletion
	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("unable to stop existing running model \"%s\": %s", args[0], err)
		}
	}

575
576
	for _, name := range args {
		req := api.DeleteRequest{Name: name}
Michael Yang's avatar
Michael Yang committed
577
		if err := client.Delete(cmd.Context(), &req); err != nil {
578
579
580
			return err
		}
		fmt.Printf("deleted '%s'\n", name)
581
582
583
584
	}
	return nil
}

Patrick Devine's avatar
Patrick Devine committed
585
func ShowHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
586
	client, err := api.ClientFromEnvironment()
Patrick Devine's avatar
Patrick Devine committed
587
588
589
590
591
592
593
594
595
	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")
596
	verbose, errVerbose := cmd.Flags().GetBool("verbose")
Patrick Devine's avatar
Patrick Devine committed
597

598
	for _, boolErr := range []error{errLicense, errModelfile, errParams, errSystem, errTemplate, errVerbose} {
Patrick Devine's avatar
Patrick Devine committed
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
		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 {
633
		return errors.New("only one of '--license', '--modelfile', '--parameters', '--system', or '--template' can be specified")
634
635
	}

636
	req := api.ShowRequest{Name: args[0], Verbose: verbose}
637
638
639
640
	resp, err := client.Show(cmd.Context(), &req)
	if err != nil {
		return err
	}
641

642
	if flagsSet == 1 {
643
644
645
646
647
648
649
650
		switch showType {
		case "license":
			fmt.Println(resp.License)
		case "modelfile":
			fmt.Println(resp.Modelfile)
		case "parameters":
			fmt.Println(resp.Parameters)
		case "system":
651
			fmt.Print(resp.System)
652
		case "template":
653
			fmt.Print(resp.Template)
654
655
656
		}

		return nil
Patrick Devine's avatar
Patrick Devine committed
657
658
	}

659
	return showInfo(resp, verbose, os.Stdout)
660
661
}

662
func showInfo(resp *api.ShowResponse, verbose bool, w io.Writer) error {
Michael Yang's avatar
Michael Yang committed
663
664
665
666
667
668
669
	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("    ")
670

Michael Yang's avatar
Michael Yang committed
671
672
673
		switch header {
		case "Template", "System", "License":
			table.SetColWidth(100)
674
675
		}

Michael Yang's avatar
Michael Yang committed
676
677
678
		table.AppendBulk(rows())
		table.Render()
		fmt.Fprintln(w)
Patrick Devine's avatar
Patrick Devine committed
679
680
	}

Michael Yang's avatar
Michael Yang committed
681
682
683
684
685
686
687
688
689
690
691
692
693
694
	tableRender("Model", func() (rows [][]string) {
		if resp.ModelInfo != nil {
			arch := resp.ModelInfo["general.architecture"].(string)
			rows = append(rows, []string{"", "architecture", arch})
			rows = append(rows, []string{"", "parameters", format.HumanNumber(uint64(resp.ModelInfo["general.parameter_count"].(float64)))})
			rows = append(rows, []string{"", "context length", strconv.FormatFloat(resp.ModelInfo[fmt.Sprintf("%s.context_length", arch)].(float64), 'f', -1, 64)})
			rows = append(rows, []string{"", "embedding length", strconv.FormatFloat(resp.ModelInfo[fmt.Sprintf("%s.embedding_length", arch)].(float64), '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})
		return
	})
695

696
697
698
699
700
701
702
703
704
	if len(resp.Capabilities) > 0 {
		tableRender("Capabilities", func() (rows [][]string) {
			for _, capability := range resp.Capabilities {
				rows = append(rows, []string{"", capability.String()})
			}
			return
		})
	}

Michael Yang's avatar
Michael Yang committed
705
706
707
708
709
710
711
712
713
	if resp.ProjectorInfo != nil {
		tableRender("Projector", func() (rows [][]string) {
			arch := resp.ProjectorInfo["general.architecture"].(string)
			rows = append(rows, []string{"", "architecture", arch})
			rows = append(rows, []string{"", "parameters", format.HumanNumber(uint64(resp.ProjectorInfo["general.parameter_count"].(float64)))})
			rows = append(rows, []string{"", "embedding length", strconv.FormatFloat(resp.ProjectorInfo[fmt.Sprintf("%s.vision.embedding_length", arch)].(float64), 'f', -1, 64)})
			rows = append(rows, []string{"", "dimensions", strconv.FormatFloat(resp.ProjectorInfo[fmt.Sprintf("%s.vision.projection_dim", arch)].(float64), 'f', -1, 64)})
			return
		})
714
715
	}

Michael Yang's avatar
Michael Yang committed
716
717
718
719
720
721
722
723
724
725
	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
		})
726
727
	}

728
729
730
731
732
733
734
735
736
737
738
	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) {
739
740
				case bool:
					v = fmt.Sprintf("%t", vData)
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
				case string:
					v = vData
				case float64:
					v = fmt.Sprintf("%g", vData)
				case []any:
					n := 3
					if len(vData) < n {
						n = len(vData)
					}
					v = fmt.Sprintf("%v", vData[:n])
				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
		})
	}

Michael Yang's avatar
Michael Yang committed
769
770
771
772
773
	head := func(s string, n int) (rows [][]string) {
		scanner := bufio.NewScanner(strings.NewReader(s))
		for scanner.Scan() && (len(rows) < n || n < 0) {
			if text := scanner.Text(); text != "" {
				rows = append(rows, []string{"", strings.TrimSpace(text)})
774
775
			}
		}
Michael Yang's avatar
Michael Yang committed
776
		return
777
778
	}

Michael Yang's avatar
Michael Yang committed
779
780
781
782
783
	if resp.System != "" {
		tableRender("System", func() [][]string {
			return head(resp.System, 2)
		})
	}
784

Michael Yang's avatar
Michael Yang committed
785
786
787
788
	if resp.License != "" {
		tableRender("License", func() [][]string {
			return head(resp.License, 2)
		})
789
	}
Michael Yang's avatar
Michael Yang committed
790
791

	return nil
792
793
}

Patrick Devine's avatar
Patrick Devine committed
794
func CopyHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
795
	client, err := api.ClientFromEnvironment()
796
797
798
	if err != nil {
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
799
800

	req := api.CopyRequest{Source: args[0], Destination: args[1]}
Michael Yang's avatar
Michael Yang committed
801
	if err := client.Copy(cmd.Context(), &req); err != nil {
Patrick Devine's avatar
Patrick Devine committed
802
803
804
805
806
807
		return err
	}
	fmt.Printf("copied '%s' to '%s'\n", args[0], args[1])
	return nil
}

808
func PullHandler(cmd *cobra.Command, args []string) error {
809
810
811
812
813
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
814
	client, err := api.ClientFromEnvironment()
815
816
817
	if err != nil {
		return err
	}
818

Michael Yang's avatar
Michael Yang committed
819
820
821
822
823
	p := progress.NewProgress(os.Stderr)
	defer p.Stop()

	bars := make(map[string]*progress.Bar)

824
825
	var status string
	var spinner *progress.Spinner
Michael Yang's avatar
Michael Yang committed
826

827
	fn := func(resp api.ProgressResponse) error {
Michael Yang's avatar
Michael Yang committed
828
		if resp.Digest != "" {
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
			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
			}

849
850
851
			if spinner != nil {
				spinner.Stop()
			}
Michael Yang's avatar
Michael Yang committed
852
853
854

			bar, ok := bars[resp.Digest]
			if !ok {
855
856
857
858
859
860
				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)
Michael Yang's avatar
Michael Yang committed
861
862
863
864
865
866
				bars[resp.Digest] = bar
				p.Add(resp.Digest, bar)
			}

			bar.Set(resp.Completed)
		} else if status != resp.Status {
867
868
869
			if spinner != nil {
				spinner.Stop()
			}
Michael Yang's avatar
Michael Yang committed
870
871
872
873
874
875

			status = resp.Status
			spinner = progress.NewSpinner(status)
			p.Add(status, spinner)
		}

876
877
		return nil
	}
878

Michael Yang's avatar
Michael Yang committed
879
	request := api.PullRequest{Name: args[0], Insecure: insecure}
880
	return client.Pull(cmd.Context(), &request, fn)
Michael Yang's avatar
Michael Yang committed
881
882
}

883
884
type generateContextKey string

885
type runOptions struct {
886
887
888
889
890
891
892
893
	Model       string
	ParentModel string
	Prompt      string
	Messages    []api.Message
	WordWrap    bool
	Format      string
	System      string
	Images      []api.ImageData
894
	Options     map[string]any
895
	MultiModal  bool
896
	KeepAlive   *api.Duration
897
898
}

899
900
901
902
903
904
905
906
907
type displayResponseState struct {
	lineLength int
	wordBuffer string
}

func displayResponse(content string, wordWrap bool, state *displayResponseState) {
	termWidth, _, _ := term.GetSize(int(os.Stdout.Fd()))
	if wordWrap && termWidth >= 10 {
		for _, ch := range content {
Josh Yan's avatar
Josh Yan committed
908
909
			if state.lineLength+1 > termWidth-5 {
				if runewidth.StringWidth(state.wordBuffer) > termWidth-10 {
910
911
912
913
914
915
916
					fmt.Printf("%s%c", state.wordBuffer, ch)
					state.wordBuffer = ""
					state.lineLength = 0
					continue
				}

				// backtrack the length of the last word and clear to the end of the line
917
918
				a := runewidth.StringWidth(state.wordBuffer)
				if a > 0 {
919
					fmt.Printf("\x1b[%dD", a)
920
921
				}
				fmt.Printf("\x1b[K\n")
922
				fmt.Printf("%s%c", state.wordBuffer, ch)
923
924
925
				chWidth := runewidth.RuneWidth(ch)

				state.lineLength = runewidth.StringWidth(state.wordBuffer) + chWidth
926
927
			} else {
				fmt.Print(string(ch))
928
929
930
931
				state.lineLength += runewidth.RuneWidth(ch)
				if runewidth.RuneWidth(ch) >= 2 {
					state.wordBuffer = ""
					continue
Josh Yan's avatar
Josh Yan committed
932
				}
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993

				switch ch {
				case ' ':
					state.wordBuffer = ""
				case '\n':
					state.lineLength = 0
				default:
					state.wordBuffer += string(ch)
				}
			}
		}
	} else {
		fmt.Printf("%s%s", state.wordBuffer, content)
		if len(state.wordBuffer) > 0 {
			state.wordBuffer = ""
		}
	}
}

func chat(cmd *cobra.Command, opts runOptions) (*api.Message, error) {
	client, err := api.ClientFromEnvironment()
	if err != nil {
		return nil, err
	}

	p := progress.NewProgress(os.Stderr)
	defer p.StopAndClear()

	spinner := progress.NewSpinner("")
	p.Add("", spinner)

	cancelCtx, cancel := context.WithCancel(cmd.Context())
	defer cancel()

	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT)

	go func() {
		<-sigChan
		cancel()
	}()

	var state *displayResponseState = &displayResponseState{}
	var latest api.ChatResponse
	var fullResponse strings.Builder
	var role string

	fn := func(response api.ChatResponse) error {
		p.StopAndClear()

		latest = response

		role = response.Message.Role
		content := response.Message.Content
		fullResponse.WriteString(content)

		displayResponse(content, opts.WordWrap, state)

		return nil
	}

994
995
996
997
	if opts.Format == "json" {
		opts.Format = `"` + opts.Format + `"`
	}

998
999
1000
	req := &api.ChatRequest{
		Model:    opts.Model,
		Messages: opts.Messages,
1001
		Format:   json.RawMessage(opts.Format),
1002
1003
1004
		Options:  opts.Options,
	}

1005
1006
1007
1008
	if opts.KeepAlive != nil {
		req.KeepAlive = opts.KeepAlive
	}

1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
	if err := client.Chat(cancelCtx, req, fn); err != nil {
		if errors.Is(err, context.Canceled) {
			return nil, nil
		}
		return nil, err
	}

	if len(opts.Messages) > 0 {
		fmt.Println()
		fmt.Println()
	}

	verbose, err := cmd.Flags().GetBool("verbose")
	if err != nil {
		return nil, err
	}

	if verbose {
		latest.Summary()
	}

	return &api.Message{Role: role, Content: fullResponse.String()}, nil
}

func generate(cmd *cobra.Command, opts runOptions) error {
Michael Yang's avatar
Michael Yang committed
1034
	client, err := api.ClientFromEnvironment()
Patrick Devine's avatar
Patrick Devine committed
1035
	if err != nil {
1036
		return err
Patrick Devine's avatar
Patrick Devine committed
1037
	}
Michael Yang's avatar
Michael Yang committed
1038

Michael Yang's avatar
Michael Yang committed
1039
	p := progress.NewProgress(os.Stderr)
1040
	defer p.StopAndClear()
1041

Michael Yang's avatar
Michael Yang committed
1042
1043
1044
	spinner := progress.NewSpinner("")
	p.Add("", spinner)

1045
1046
1047
1048
1049
1050
1051
	var latest api.GenerateResponse

	generateContext, ok := cmd.Context().Value(generateContextKey("context")).([]int)
	if !ok {
		generateContext = []int{}
	}

Michael Yang's avatar
Michael Yang committed
1052
	ctx, cancel := context.WithCancel(cmd.Context())
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
	defer cancel()

	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT)

	go func() {
		<-sigChan
		cancel()
	}()

1063
	var state *displayResponseState = &displayResponseState{}
1064

1065
	fn := func(response api.GenerateResponse) error {
Michael Yang's avatar
Michael Yang committed
1066
		p.StopAndClear()
1067

Patrick Devine's avatar
Patrick Devine committed
1068
		latest = response
1069
		content := response.Response
1070

1071
		displayResponse(content, opts.WordWrap, state)
1072

Patrick Devine's avatar
Patrick Devine committed
1073
1074
		return nil
	}
1075

1076
1077
1078
1079
1080
1081
1082
	if opts.MultiModal {
		opts.Prompt, opts.Images, err = extractFileData(opts.Prompt)
		if err != nil {
			return err
		}
	}

1083
1084
1085
1086
	if opts.Format == "json" {
		opts.Format = `"` + opts.Format + `"`
	}

Michael Yang's avatar
Michael Yang committed
1087
	request := api.GenerateRequest{
1088
1089
1090
1091
		Model:     opts.Model,
		Prompt:    opts.Prompt,
		Context:   generateContext,
		Images:    opts.Images,
1092
		Format:    json.RawMessage(opts.Format),
1093
1094
1095
		System:    opts.System,
		Options:   opts.Options,
		KeepAlive: opts.KeepAlive,
Michael Yang's avatar
Michael Yang committed
1096
1097
1098
	}

	if err := client.Generate(ctx, &request, fn); err != nil {
1099
		if errors.Is(err, context.Canceled) {
1100
			return nil
1101
		}
1102
		return err
Patrick Devine's avatar
Patrick Devine committed
1103
	}
1104

1105
	if opts.Prompt != "" {
Michael Yang's avatar
Michael Yang committed
1106
1107
		fmt.Println()
		fmt.Println()
Patrick Devine's avatar
Patrick Devine committed
1108
	}
1109

1110
1111
1112
1113
	if !latest.Done {
		return nil
	}

Patrick Devine's avatar
Patrick Devine committed
1114
1115
	verbose, err := cmd.Flags().GetBool("verbose")
	if err != nil {
1116
		return err
Patrick Devine's avatar
Patrick Devine committed
1117
	}
Michael Yang's avatar
Michael Yang committed
1118

Patrick Devine's avatar
Patrick Devine committed
1119
1120
	if verbose {
		latest.Summary()
Michael Yang's avatar
Michael Yang committed
1121
	}
Michael Yang's avatar
Michael Yang committed
1122

Patrick Devine's avatar
Patrick Devine committed
1123
1124
1125
	ctx = context.WithValue(cmd.Context(), generateContextKey("context"), latest.Context)
	cmd.SetContext(ctx)

1126
	return nil
Michael Yang's avatar
Michael Yang committed
1127
1128
}

1129
func RunServer(_ *cobra.Command, _ []string) error {
Michael Yang's avatar
Michael Yang committed
1130
	if err := initializeKeypair(); err != nil {
1131
1132
1133
		return err
	}

Michael Yang's avatar
host  
Michael Yang committed
1134
	ln, err := net.Listen("tcp", envconfig.Host().Host)
1135
1136
1137
	if err != nil {
		return err
	}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1138

1139
1140
1141
1142
1143
1144
	err = server.Serve(ln)
	if errors.Is(err, http.ErrServerClosed) {
		return nil
	}

	return err
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1145
1146
}

1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
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)
Michael Yang's avatar
Michael Yang committed
1159
		cryptoPublicKey, cryptoPrivateKey, err := ed25519.GenerateKey(rand.Reader)
1160
1161
1162
1163
		if err != nil {
			return err
		}

Michael Yang's avatar
Michael Yang committed
1164
		privateKeyBytes, err := ssh.MarshalPrivateKey(cryptoPrivateKey, "")
1165
1166
1167
1168
		if err != nil {
			return err
		}

Michael Yang's avatar
Michael Yang committed
1169
		if err := os.MkdirAll(filepath.Dir(privKeyPath), 0o755); err != nil {
1170
1171
1172
			return fmt.Errorf("could not create directory %w", err)
		}

Michael Yang's avatar
Michael Yang committed
1173
		if err := os.WriteFile(privKeyPath, pem.EncodeToMemory(privateKeyBytes), 0o600); err != nil {
1174
1175
1176
			return err
		}

Michael Yang's avatar
Michael Yang committed
1177
		sshPublicKey, err := ssh.NewPublicKey(cryptoPublicKey)
1178
1179
1180
1181
		if err != nil {
			return err
		}

Michael Yang's avatar
Michael Yang committed
1182
		publicKeyBytes := ssh.MarshalAuthorizedKey(sshPublicKey)
1183

Michael Yang's avatar
Michael Yang committed
1184
		if err := os.WriteFile(pubKeyPath, publicKeyBytes, 0o644); err != nil {
1185
1186
1187
			return err
		}

Michael Yang's avatar
Michael Yang committed
1188
		fmt.Printf("Your new public key is: \n\n%s\n", publicKeyBytes)
1189
1190
1191
1192
	}
	return nil
}

Michael Yang's avatar
Michael Yang committed
1193
func checkServerHeartbeat(cmd *cobra.Command, _ []string) error {
Michael Yang's avatar
Michael Yang committed
1194
	client, err := api.ClientFromEnvironment()
1195
1196
1197
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
1198
	if err := client.Heartbeat(cmd.Context()); err != nil {
1199
		if !strings.Contains(err.Error(), " refused") {
Bruce MacDonald's avatar
Bruce MacDonald committed
1200
1201
			return err
		}
1202
		if err := startApp(cmd.Context(), client); err != nil {
Michael Yang's avatar
lint  
Michael Yang committed
1203
			return errors.New("could not connect to ollama app, is it running?")
1204
1205
1206
1207
1208
		}
	}
	return nil
}

Michael Yang's avatar
Michael Yang committed
1209
1210
1211
1212
1213
1214
1215
1216
func versionHandler(cmd *cobra.Command, _ []string) {
	client, err := api.ClientFromEnvironment()
	if err != nil {
		return
	}

	serverVersion, err := client.Version(cmd.Context())
	if err != nil {
Michael Yang's avatar
Michael Yang committed
1217
1218
1219
1220
1221
		fmt.Println("Warning: could not connect to a running Ollama instance")
	}

	if serverVersion != "" {
		fmt.Printf("ollama version is %s\n", serverVersion)
Michael Yang's avatar
Michael Yang committed
1222
1223
	}

1224
	if serverVersion != version.Version {
Michael Yang's avatar
Michael Yang committed
1225
		fmt.Printf("Warning: client version is %s\n", version.Version)
1226
	}
Michael Yang's avatar
Michael Yang committed
1227
1228
}

1229
func appendEnvDocs(cmd *cobra.Command, envs []envconfig.EnvVar) {
1230
1231
1232
1233
1234
	if len(envs) == 0 {
		return
	}

	envUsage := `
1235
1236
Environment Variables:
`
1237
	for _, e := range envs {
1238
		envUsage += fmt.Sprintf("      %-24s   %s\n", e.Name, e.Description)
1239
1240
1241
	}

	cmd.SetUsageTemplate(cmd.UsageTemplate() + envUsage)
1242
1243
}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
1244
1245
func NewCLI() *cobra.Command {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
Michael Yang's avatar
Michael Yang committed
1246
	cobra.EnableCommandSorting = false
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1247

1248
	if runtime.GOOS == "windows" && term.IsTerminal(int(os.Stdout.Fd())) {
1249
		console.ConsoleFromFile(os.Stdin) //nolint:errcheck
1250
1251
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
1252
	rootCmd := &cobra.Command{
1253
1254
1255
1256
		Use:           "ollama",
		Short:         "Large language model runner",
		SilenceUsage:  true,
		SilenceErrors: true,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1257
1258
1259
		CompletionOptions: cobra.CompletionOptions{
			DisableDefaultCmd: true,
		},
Michael Yang's avatar
Michael Yang committed
1260
1261
1262
1263
1264
1265
1266
1267
		Run: func(cmd *cobra.Command, args []string) {
			if version, _ := cmd.Flags().GetBool("version"); version {
				versionHandler(cmd, args)
				return
			}

			cmd.Print(cmd.UsageString())
		},
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1268
1269
	}

Michael Yang's avatar
Michael Yang committed
1270
	rootCmd.Flags().BoolP("version", "v", false, "Show version information")
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1271

1272
	createCmd := &cobra.Command{
1273
1274
		Use:     "create MODEL",
		Short:   "Create a model from a Modelfile",
Michael Yang's avatar
Michael Yang committed
1275
		Args:    cobra.ExactArgs(1),
1276
1277
		PreRunE: checkServerHeartbeat,
		RunE:    CreateHandler,
1278
1279
	}

1280
	createCmd.Flags().StringP("file", "f", "", "Name of the Modelfile (default \"Modelfile\"")
1281
	createCmd.Flags().StringP("quantize", "q", "", "Quantize model to this level (e.g. q4_0)")
1282

Patrick Devine's avatar
Patrick Devine committed
1283
1284
1285
	showCmd := &cobra.Command{
		Use:     "show MODEL",
		Short:   "Show information for a model",
Michael Yang's avatar
Michael Yang committed
1286
		Args:    cobra.ExactArgs(1),
Patrick Devine's avatar
Patrick Devine committed
1287
1288
1289
1290
1291
1292
1293
1294
		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")
1295
	showCmd.Flags().Bool("system", false, "Show system message of a model")
1296
	showCmd.Flags().BoolP("verbose", "v", false, "Show detailed model information")
Patrick Devine's avatar
Patrick Devine committed
1297

Jeffrey Morgan's avatar
Jeffrey Morgan committed
1298
	runCmd := &cobra.Command{
1299
1300
1301
1302
1303
		Use:     "run MODEL [PROMPT]",
		Short:   "Run a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    RunHandler,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1304
1305
	}

1306
	runCmd.Flags().String("keepalive", "", "Duration to keep a model loaded (e.g. 5m)")
1307
	runCmd.Flags().Bool("verbose", false, "Show timings for response")
1308
	runCmd.Flags().Bool("insecure", false, "Use an insecure registry")
1309
	runCmd.Flags().Bool("nowordwrap", false, "Don't wrap words to the next line automatically")
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1310
	runCmd.Flags().String("format", "", "Response format (e.g. json)")
Patrick Devine's avatar
Patrick Devine committed
1311
1312
1313
1314
1315
1316
1317
1318
1319

	stopCmd := &cobra.Command{
		Use:     "stop MODEL",
		Short:   "Stop a running model",
		Args:    cobra.ExactArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    StopHandler,
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
1320
1321
1322
1323
	serveCmd := &cobra.Command{
		Use:     "serve",
		Aliases: []string{"start"},
		Short:   "Start ollama",
Michael Yang's avatar
Michael Yang committed
1324
		Args:    cobra.ExactArgs(0),
Michael Yang's avatar
Michael Yang committed
1325
		RunE:    RunServer,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1326
1327
	}

1328
	pullCmd := &cobra.Command{
1329
1330
		Use:     "pull MODEL",
		Short:   "Pull a model from a registry",
Michael Yang's avatar
Michael Yang committed
1331
		Args:    cobra.ExactArgs(1),
1332
1333
		PreRunE: checkServerHeartbeat,
		RunE:    PullHandler,
1334
1335
	}

1336
1337
	pullCmd.Flags().Bool("insecure", false, "Use an insecure registry")

1338
	pushCmd := &cobra.Command{
1339
1340
		Use:     "push MODEL",
		Short:   "Push a model to a registry",
Michael Yang's avatar
Michael Yang committed
1341
		Args:    cobra.ExactArgs(1),
1342
1343
		PreRunE: checkServerHeartbeat,
		RunE:    PushHandler,
1344
1345
	}

1346
1347
	pushCmd.Flags().Bool("insecure", false, "Use an insecure registry")

Patrick Devine's avatar
Patrick Devine committed
1348
	listCmd := &cobra.Command{
1349
		Use:     "list",
Patrick Devine's avatar
Patrick Devine committed
1350
		Aliases: []string{"ls"},
1351
		Short:   "List models",
1352
		PreRunE: checkServerHeartbeat,
1353
		RunE:    ListHandler,
1354
	}
1355
1356
1357
1358
1359
1360
1361
1362

	psCmd := &cobra.Command{
		Use:     "ps",
		Short:   "List running models",
		PreRunE: checkServerHeartbeat,
		RunE:    ListRunningHandler,
	}

Patrick Devine's avatar
Patrick Devine committed
1363
	copyCmd := &cobra.Command{
Michael Yang's avatar
Michael Yang committed
1364
		Use:     "cp SOURCE DESTINATION",
1365
		Short:   "Copy a model",
Michael Yang's avatar
Michael Yang committed
1366
		Args:    cobra.ExactArgs(2),
1367
1368
		PreRunE: checkServerHeartbeat,
		RunE:    CopyHandler,
Patrick Devine's avatar
Patrick Devine committed
1369
1370
	}

1371
	deleteCmd := &cobra.Command{
Michael Yang's avatar
Michael Yang committed
1372
		Use:     "rm MODEL [MODEL...]",
1373
1374
1375
1376
		Short:   "Remove a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    DeleteHandler,
Patrick Devine's avatar
Patrick Devine committed
1377
1378
	}

1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
	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:])
	})

1391
1392
1393
	envVars := envconfig.AsMap()

	envs := []envconfig.EnvVar{envVars["OLLAMA_HOST"]}
1394

1395
1396
1397
1398
	for _, cmd := range []*cobra.Command{
		createCmd,
		showCmd,
		runCmd,
Patrick Devine's avatar
Patrick Devine committed
1399
		stopCmd,
1400
1401
1402
		pullCmd,
		pushCmd,
		listCmd,
1403
		psCmd,
1404
1405
		copyCmd,
		deleteCmd,
1406
		serveCmd,
1407
	} {
1408
1409
		switch cmd {
		case runCmd:
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
			appendEnvDocs(cmd, []envconfig.EnvVar{envVars["OLLAMA_HOST"], envVars["OLLAMA_NOHISTORY"]})
		case serveCmd:
			appendEnvDocs(cmd, []envconfig.EnvVar{
				envVars["OLLAMA_DEBUG"],
				envVars["OLLAMA_HOST"],
				envVars["OLLAMA_KEEP_ALIVE"],
				envVars["OLLAMA_MAX_LOADED_MODELS"],
				envVars["OLLAMA_MAX_QUEUE"],
				envVars["OLLAMA_MODELS"],
				envVars["OLLAMA_NUM_PARALLEL"],
				envVars["OLLAMA_NOPRUNE"],
				envVars["OLLAMA_ORIGINS"],
1422
				envVars["OLLAMA_SCHED_SPREAD"],
1423
				envVars["OLLAMA_FLASH_ATTENTION"],
1424
				envVars["OLLAMA_KV_CACHE_TYPE"],
1425
				envVars["OLLAMA_LLM_LIBRARY"],
1426
				envVars["OLLAMA_GPU_OVERHEAD"],
1427
				envVars["OLLAMA_LOAD_TIMEOUT"],
1428
			})
1429
1430
1431
		default:
			appendEnvDocs(cmd, envs)
		}
1432
1433
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
1434
1435
	rootCmd.AddCommand(
		serveCmd,
1436
		createCmd,
Patrick Devine's avatar
Patrick Devine committed
1437
		showCmd,
1438
		runCmd,
Patrick Devine's avatar
Patrick Devine committed
1439
		stopCmd,
1440
1441
		pullCmd,
		pushCmd,
Patrick Devine's avatar
Patrick Devine committed
1442
		listCmd,
1443
		psCmd,
Patrick Devine's avatar
Patrick Devine committed
1444
		copyCmd,
1445
		deleteCmd,
1446
		runnerCmd,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1447
1448
1449
1450
	)

	return rootCmd
}