cmd.go 21.7 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
8
	"crypto/ed25519"
	"crypto/rand"
	"encoding/pem"
Michael Yang's avatar
Michael Yang committed
9
	"errors"
Bruce MacDonald's avatar
Bruce MacDonald committed
10
	"fmt"
Michael Yang's avatar
Michael Yang committed
11
	"io"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
12
13
14
	"log"
	"net"
	"os"
15
	"os/exec"
16
	"os/signal"
17
	"path/filepath"
18
	"runtime"
Michael Yang's avatar
Michael Yang committed
19
	"strings"
20
	"syscall"
Michael Yang's avatar
Michael Yang committed
21
	"time"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
22

Patrick Devine's avatar
Patrick Devine committed
23
24
	"github.com/dustin/go-humanize"
	"github.com/olekukonko/tablewriter"
25
	"github.com/pdevine/readline"
Michael Yang's avatar
Michael Yang committed
26
	"github.com/spf13/cobra"
27
	"golang.org/x/crypto/ssh"
28
	"golang.org/x/term"
Michael Yang's avatar
Michael Yang committed
29

Jeffrey Morgan's avatar
Jeffrey Morgan committed
30
	"github.com/jmorganca/ollama/api"
Patrick Devine's avatar
Patrick Devine committed
31
	"github.com/jmorganca/ollama/format"
32
	"github.com/jmorganca/ollama/progressbar"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
33
	"github.com/jmorganca/ollama/server"
Michael Yang's avatar
Michael Yang committed
34
	"github.com/jmorganca/ollama/version"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
35
36
)

37
type Painter struct {
38
	IsMultiLine bool
39
}
Patrick Devine's avatar
Patrick Devine committed
40

41
func (p Painter) Paint(line []rune, _ int) []rune {
Patrick Devine's avatar
Patrick Devine committed
42
	termType := os.Getenv("TERM")
43
44
45
46
47
	if termType == "xterm-256color" && len(line) == 0 {
		var prompt string
		if p.IsMultiLine {
			prompt = "Use \"\"\" to end multi-line input"
		} else {
48
			prompt = "Send a message (/? for help)"
49
		}
Patrick Devine's avatar
Patrick Devine committed
50
51
		return []rune(fmt.Sprintf("\033[38;5;245m%s\033[%dD\033[0m", prompt, len(prompt)))
	}
52
53
	// add a space and a backspace to prevent the cursor from walking up the screen
	line = append(line, []rune(" \b")...)
Patrick Devine's avatar
Patrick Devine committed
54
55
56
	return line
}

57
func CreateHandler(cmd *cobra.Command, args []string) error {
58
	filename, _ := cmd.Flags().GetString("file")
59
60
61
62
63
	filename, err := filepath.Abs(filename)
	if err != nil {
		return err
	}

64
65
66
67
	client, err := api.FromEnv()
	if err != nil {
		return err
	}
68

Michael Yang's avatar
Michael Yang committed
69
70
	var spinner *Spinner

71
72
	var currentDigest string
	var bar *progressbar.ProgressBar
Michael Yang's avatar
Michael Yang committed
73

74
75
76
77
78
79
80
	request := api.CreateRequest{Name: args[0], Path: filename}
	fn := func(resp api.ProgressResponse) error {
		if resp.Digest != currentDigest && resp.Digest != "" {
			if spinner != nil {
				spinner.Stop()
			}
			currentDigest = resp.Digest
81
82
			switch {
			case strings.Contains(resp.Status, "embeddings"):
Michael Yang's avatar
Michael Yang committed
83
84
				bar = progressbar.Default(resp.Total, resp.Status)
				bar.Set64(resp.Completed)
85
86
87
			default:
				// pulling
				bar = progressbar.DefaultBytes(
Michael Yang's avatar
Michael Yang committed
88
					resp.Total,
89
90
					resp.Status,
				)
Michael Yang's avatar
Michael Yang committed
91
				bar.Set64(resp.Completed)
92
			}
93
		} else if resp.Digest == currentDigest && resp.Digest != "" {
Michael Yang's avatar
Michael Yang committed
94
			bar.Set64(resp.Completed)
95
96
97
98
99
100
101
102
		} else {
			currentDigest = ""
			if spinner != nil {
				spinner.Stop()
			}
			spinner = NewSpinner(resp.Status)
			go spinner.Spin(100 * time.Millisecond)
		}
103

104
105
106
107
108
109
110
		return nil
	}

	if err := client.Create(context.Background(), &request, fn); err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
111
112
	if spinner != nil {
		spinner.Stop()
113
114
115
		if spinner.description != "success" {
			return errors.New("unexpected end to create model")
		}
Michael Yang's avatar
Michael Yang committed
116
117
	}

118
119
120
	return nil
}

121
func RunHandler(cmd *cobra.Command, args []string) error {
122
	client, err := api.FromEnv()
123
124
125
126
	if err != nil {
		return err
	}

127
	models, err := client.List(context.Background())
Patrick Devine's avatar
Patrick Devine committed
128
129
130
131
	if err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
132
	canonicalModelPath := server.ParseModelPath(args[0])
133
	for _, model := range models.Models {
Michael Yang's avatar
Michael Yang committed
134
		if model.Name == canonicalModelPath.GetShortTagname() {
135
			return RunGenerate(cmd, args)
Michael Yang's avatar
Michael Yang committed
136
		}
137
138
139
	}

	if err := PullHandler(cmd, args); err != nil {
Michael Yang's avatar
Michael Yang committed
140
141
142
143
		return err
	}

	return RunGenerate(cmd, args)
Bruce MacDonald's avatar
Bruce MacDonald committed
144
145
}

146
func PushHandler(cmd *cobra.Command, args []string) error {
147
148
149
150
	client, err := api.FromEnv()
	if err != nil {
		return err
	}
151

152
153
154
155
156
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

157
158
159
	var currentDigest string
	var bar *progressbar.ProgressBar

160
	request := api.PushRequest{Name: args[0], Insecure: insecure}
161
	fn := func(resp api.ProgressResponse) error {
162
163
164
		if resp.Digest != currentDigest && resp.Digest != "" {
			currentDigest = resp.Digest
			bar = progressbar.DefaultBytes(
Michael Yang's avatar
Michael Yang committed
165
				resp.Total,
166
167
168
				fmt.Sprintf("pushing %s...", resp.Digest[7:19]),
			)

Michael Yang's avatar
Michael Yang committed
169
			bar.Set64(resp.Completed)
170
		} else if resp.Digest == currentDigest && resp.Digest != "" {
Michael Yang's avatar
Michael Yang committed
171
			bar.Set64(resp.Completed)
172
173
174
175
		} else {
			currentDigest = ""
			fmt.Println(resp.Status)
		}
176
177
178
179
180
181
		return nil
	}

	if err := client.Push(context.Background(), &request, fn); err != nil {
		return err
	}
182
183
184
185
186

	if bar != nil && !bar.IsFinished() {
		return errors.New("unexpected end to push model")
	}

187
188
189
	return nil
}

190
func ListHandler(cmd *cobra.Command, args []string) error {
191
192
193
194
	client, err := api.FromEnv()
	if err != nil {
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
195
196
197
198
199
200
201
202
203

	models, err := client.List(context.Background())
	if err != nil {
		return err
	}

	var data [][]string

	for _, m := range models.Models {
Michael Yang's avatar
Michael Yang committed
204
		if len(args) == 0 || strings.HasPrefix(m.Name, args[0]) {
Patrick Devine's avatar
Patrick Devine committed
205
			data = append(data, []string{m.Name, m.Digest[:12], humanize.Bytes(uint64(m.Size)), format.HumanTime(m.ModifiedAt, "Never")})
Michael Yang's avatar
Michael Yang committed
206
		}
Patrick Devine's avatar
Patrick Devine committed
207
208
209
	}

	table := tablewriter.NewWriter(os.Stdout)
Patrick Devine's avatar
Patrick Devine committed
210
	table.SetHeader([]string{"NAME", "ID", "SIZE", "MODIFIED"})
Patrick Devine's avatar
Patrick Devine committed
211
212
213
214
215
216
217
218
219
220
221
222
	table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
	table.SetAlignment(tablewriter.ALIGN_LEFT)
	table.SetHeaderLine(false)
	table.SetBorder(false)
	table.SetNoWhiteSpace(true)
	table.SetTablePadding("\t")
	table.AppendBulk(data)
	table.Render()

	return nil
}

223
func DeleteHandler(cmd *cobra.Command, args []string) error {
224
225
226
227
	client, err := api.FromEnv()
	if err != nil {
		return err
	}
228

229
230
231
232
233
234
	for _, name := range args {
		req := api.DeleteRequest{Name: name}
		if err := client.Delete(context.Background(), &req); err != nil {
			return err
		}
		fmt.Printf("deleted '%s'\n", name)
235
236
237
238
	}
	return nil
}

Patrick Devine's avatar
Patrick Devine committed
239
240
241
242
243
244
245
246
247
248
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
278
279
280
281
282
283
284
285
286
287
288
289
func ShowHandler(cmd *cobra.Command, args []string) error {
	client, err := api.FromEnv()
	if err != nil {
		return err
	}

	if len(args) != 1 {
		return errors.New("missing model name")
	}

	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")

	for _, boolErr := range []error{errLicense, errModelfile, errParams, errSystem, errTemplate} {
		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 {
290
		return errors.New("only one of '--license', '--modelfile', '--parameters', '--system', or '--template' can be specified")
Patrick Devine's avatar
Patrick Devine committed
291
	} else if flagsSet == 0 {
292
		return errors.New("one of '--license', '--modelfile', '--parameters', '--system', or '--template' must be specified")
Patrick Devine's avatar
Patrick Devine committed
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
	}

	req := api.ShowRequest{Name: args[0]}
	resp, err := client.Show(context.Background(), &req)
	if err != nil {
		return err
	}

	switch showType {
	case "license":
		fmt.Println(resp.License)
	case "modelfile":
		fmt.Println(resp.Modelfile)
	case "parameters":
		fmt.Println(resp.Parameters)
	case "system":
		fmt.Println(resp.System)
	case "template":
		fmt.Println(resp.Template)
	}

	return nil
}

Patrick Devine's avatar
Patrick Devine committed
317
func CopyHandler(cmd *cobra.Command, args []string) error {
318
319
320
321
	client, err := api.FromEnv()
	if err != nil {
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
322
323
324
325
326
327
328
329
330

	req := api.CopyRequest{Source: args[0], Destination: args[1]}
	if err := client.Copy(context.Background(), &req); err != nil {
		return err
	}
	fmt.Printf("copied '%s' to '%s'\n", args[0], args[1])
	return nil
}

331
func PullHandler(cmd *cobra.Command, args []string) error {
332
333
334
335
336
337
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

	return pull(args[0], insecure)
338
339
}

340
func pull(model string, insecure bool) error {
341
342
343
344
	client, err := api.FromEnv()
	if err != nil {
		return err
	}
345

346
	var currentDigest string
Bruce MacDonald's avatar
Bruce MacDonald committed
347
	var bar *progressbar.ProgressBar
Michael Yang's avatar
Michael Yang committed
348

349
	request := api.PullRequest{Name: model, Insecure: insecure}
350
351
352
	fn := func(resp api.ProgressResponse) error {
		if resp.Digest != currentDigest && resp.Digest != "" {
			currentDigest = resp.Digest
353
			bar = progressbar.DefaultBytes(
Michael Yang's avatar
Michael Yang committed
354
				resp.Total,
355
				fmt.Sprintf("pulling %s...", resp.Digest[7:19]),
356
			)
357

Michael Yang's avatar
Michael Yang committed
358
			bar.Set64(resp.Completed)
359
		} else if resp.Digest == currentDigest && resp.Digest != "" {
Michael Yang's avatar
Michael Yang committed
360
			bar.Set64(resp.Completed)
361
		} else {
362
			currentDigest = ""
363
364
			fmt.Println(resp.Status)
		}
365

366
367
		return nil
	}
368

369
370
371
	if err := client.Pull(context.Background(), &request, fn); err != nil {
		return err
	}
372
373
374
375
376

	if bar != nil && !bar.IsFinished() {
		return errors.New("unexpected end to pull model")
	}

377
	return nil
Michael Yang's avatar
Michael Yang committed
378
379
}

380
func RunGenerate(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
381
	if len(args) > 1 {
Michael Yang's avatar
Michael Yang committed
382
		// join all args into a single prompt
383
		return generate(cmd, args[0], strings.Join(args[1:], " "))
Michael Yang's avatar
Michael Yang committed
384
385
	}

Michael Yang's avatar
Michael Yang committed
386
	if readline.IsTerminal(int(os.Stdin.Fd())) {
387
		return generateInteractive(cmd, args[0])
Michael Yang's avatar
Michael Yang committed
388
389
	}

390
	return generateBatch(cmd, args[0])
Michael Yang's avatar
Michael Yang committed
391
392
}

Michael Yang's avatar
Michael Yang committed
393
type generateContextKey string
Michael Yang's avatar
Michael Yang committed
394

395
func generate(cmd *cobra.Command, model, prompt string) error {
Patrick Devine's avatar
Patrick Devine committed
396
397
398
399
	client, err := api.FromEnv()
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
400

Patrick Devine's avatar
Patrick Devine committed
401
402
	spinner := NewSpinner("")
	go spinner.Spin(60 * time.Millisecond)
Michael Yang's avatar
Michael Yang committed
403

Patrick Devine's avatar
Patrick Devine committed
404
	var latest api.GenerateResponse
405

Patrick Devine's avatar
Patrick Devine committed
406
407
408
409
	generateContext, ok := cmd.Context().Value(generateContextKey("context")).([]int)
	if !ok {
		generateContext = []int{}
	}
Michael Yang's avatar
Michael Yang committed
410

411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
	var wrapTerm bool
	termType := os.Getenv("TERM")
	if termType == "xterm-256color" {
		wrapTerm = true
	}

	termWidth, _, err := term.GetSize(int(0))
	if err != nil {
		wrapTerm = false
	}

	// override wrapping if the user turned it off
	nowrap, err := cmd.Flags().GetBool("nowordwrap")
	if err != nil {
		return err
	}
	if nowrap {
		wrapTerm = false
	}

431
432
433
434
435
436
437
438
439
440
441
442
443
	cancelCtx, cancel := context.WithCancel(context.Background())
	defer cancel()

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

	go func() {
		<-sigChan
		cancel()
		abort = true
	}()

444
445
446
	var currentLineLength int
	var wordBuffer string

Patrick Devine's avatar
Patrick Devine committed
447
448
449
450
451
	request := api.GenerateRequest{Model: model, Prompt: prompt, Context: generateContext}
	fn := func(response api.GenerateResponse) error {
		if !spinner.IsFinished() {
			spinner.Finish()
		}
Michael Yang's avatar
Michael Yang committed
452

Patrick Devine's avatar
Patrick Devine committed
453
		latest = response
454

455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
		if wrapTerm {
			for _, ch := range response.Response {
				if currentLineLength+1 > termWidth-5 {
					// backtrack the length of the last word and clear to the end of the line
					fmt.Printf("\x1b[%dD\x1b[K\n", len(wordBuffer))
					fmt.Printf("%s%c", wordBuffer, ch)
					currentLineLength = len(wordBuffer) + 1
				} else {
					fmt.Print(string(ch))
					currentLineLength += 1

					switch ch {
					case ' ':
						wordBuffer = ""
					case '\n':
						currentLineLength = 0
					default:
						wordBuffer += string(ch)
					}
				}
			}
		} else {
			fmt.Print(response.Response)
		}

Patrick Devine's avatar
Patrick Devine committed
480
481
		return nil
	}
482

483
	if err := client.Generate(cancelCtx, &request, fn); err != nil {
Patrick Devine's avatar
Patrick Devine committed
484
485
486
487
488
489
490
491
492
493
		if strings.Contains(err.Error(), "failed to load model") {
			// tell the user to check the server log, if it exists locally
			home, nestedErr := os.UserHomeDir()
			if nestedErr != nil {
				// return the original error
				return err
			}
			logPath := filepath.Join(home, ".ollama", "logs", "server.log")
			if _, nestedErr := os.Stat(logPath); nestedErr == nil {
				err = fmt.Errorf("%w\nFor more details, check the error logs at %s", err, logPath)
494
			}
495
496
497
		} else if strings.Contains(err.Error(), "context canceled") && abort {
			spinner.Finish()
			return nil
498
		}
Patrick Devine's avatar
Patrick Devine committed
499
500
501
		return err
	}
	if prompt != "" {
Michael Yang's avatar
Michael Yang committed
502
503
		fmt.Println()
		fmt.Println()
Patrick Devine's avatar
Patrick Devine committed
504
	}
505

Patrick Devine's avatar
Patrick Devine committed
506
	if !latest.Done {
507
508
509
		if abort {
			return nil
		}
Patrick Devine's avatar
Patrick Devine committed
510
511
		return errors.New("unexpected end of response")
	}
512

Patrick Devine's avatar
Patrick Devine committed
513
514
515
516
	verbose, err := cmd.Flags().GetBool("verbose")
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
517

Patrick Devine's avatar
Patrick Devine committed
518
519
	if verbose {
		latest.Summary()
Michael Yang's avatar
Michael Yang committed
520
	}
Michael Yang's avatar
Michael Yang committed
521

Patrick Devine's avatar
Patrick Devine committed
522
523
524
525
	ctx := cmd.Context()
	ctx = context.WithValue(ctx, generateContextKey("context"), latest.Context)
	cmd.SetContext(ctx)

Michael Yang's avatar
Michael Yang committed
526
527
528
	return nil
}

529
func generateInteractive(cmd *cobra.Command, model string) error {
Michael Yang's avatar
Michael Yang committed
530
531
532
533
534
	home, err := os.UserHomeDir()
	if err != nil {
		return err
	}

Patrick Devine's avatar
Patrick Devine committed
535
536
537
538
539
	// load the model
	if err := generate(cmd, model, ""); err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
540
541
542
543
544
545
	completer := readline.NewPrefixCompleter(
		readline.PcItem("/help"),
		readline.PcItem("/list"),
		readline.PcItem("/set",
			readline.PcItem("history"),
			readline.PcItem("nohistory"),
546
547
			readline.PcItem("wordwrap"),
			readline.PcItem("nowordwrap"),
Michael Yang's avatar
Michael Yang committed
548
549
			readline.PcItem("verbose"),
			readline.PcItem("quiet"),
Michael Yang's avatar
Michael Yang committed
550
		),
551
552
		readline.PcItem("/show",
			readline.PcItem("license"),
Patrick Devine's avatar
Patrick Devine committed
553
554
			readline.PcItem("modelfile"),
			readline.PcItem("parameters"),
555
556
557
			readline.PcItem("system"),
			readline.PcItem("template"),
		),
Michael Yang's avatar
Michael Yang committed
558
559
560
561
562
563
564
565
566
		readline.PcItem("/exit"),
		readline.PcItem("/bye"),
	)

	usage := func() {
		fmt.Fprintln(os.Stderr, "commands:")
		fmt.Fprintln(os.Stderr, completer.Tree("  "))
	}

567
568
	var painter Painter

Michael Yang's avatar
Michael Yang committed
569
	config := readline.Config{
570
		Painter:      &painter,
Michael Yang's avatar
Michael Yang committed
571
572
573
574
575
576
577
578
579
580
581
		Prompt:       ">>> ",
		HistoryFile:  filepath.Join(home, ".ollama", "history"),
		AutoComplete: completer,
	}

	scanner, err := readline.NewEx(&config)
	if err != nil {
		return err
	}
	defer scanner.Close()

582
583
584
	var multiLineBuffer string
	var isMultiLine bool

Michael Yang's avatar
Michael Yang committed
585
586
587
588
589
590
	for {
		line, err := scanner.Readline()
		switch {
		case errors.Is(err, io.EOF):
			return nil
		case errors.Is(err, readline.ErrInterrupt):
591
			if line == "" {
592
				fmt.Println("Use Ctrl-D or /bye to exit.")
593
594
			}

Michael Yang's avatar
Michael Yang committed
595
596
			continue
		case err != nil:
Michael Yang's avatar
Michael Yang committed
597
598
599
			return err
		}

Michael Yang's avatar
Michael Yang committed
600
		line = strings.TrimSpace(line)
Michael Yang's avatar
Michael Yang committed
601

Michael Yang's avatar
Michael Yang committed
602
		switch {
603
604
605
		case isMultiLine:
			if strings.HasSuffix(line, `"""`) {
				isMultiLine = false
606
				painter.IsMultiLine = isMultiLine
607
				multiLineBuffer += strings.TrimSuffix(line, `"""`)
608
				line = multiLineBuffer
609
610
611
612
613
614
615
616
				multiLineBuffer = ""
				scanner.SetPrompt(">>> ")
			} else {
				multiLineBuffer += line + " "
				continue
			}
		case strings.HasPrefix(line, `"""`):
			isMultiLine = true
617
			painter.IsMultiLine = isMultiLine
618
619
620
			multiLineBuffer = strings.TrimPrefix(line, `"""`) + " "
			scanner.SetPrompt("... ")
			continue
Michael Yang's avatar
Michael Yang committed
621
622
		case strings.HasPrefix(line, "/list"):
			args := strings.Fields(line)
623
			if err := ListHandler(cmd, args[1:]); err != nil {
Michael Yang's avatar
Michael Yang committed
624
625
626
627
628
629
630
631
632
633
				return err
			}
		case strings.HasPrefix(line, "/set"):
			args := strings.Fields(line)
			if len(args) > 1 {
				switch args[1] {
				case "history":
					scanner.HistoryEnable()
				case "nohistory":
					scanner.HistoryDisable()
634
635
636
637
638
639
				case "wordwrap":
					cmd.Flags().Set("nowordwrap", "false")
					fmt.Println("Set 'wordwrap' mode.")
				case "nowordwrap":
					cmd.Flags().Set("nowordwrap", "true")
					fmt.Println("Set 'nowordwrap' mode.")
Michael Yang's avatar
Michael Yang committed
640
641
				case "verbose":
					cmd.Flags().Set("verbose", "true")
642
					fmt.Println("Set 'verbose' mode.")
Michael Yang's avatar
Michael Yang committed
643
644
				case "quiet":
					cmd.Flags().Set("verbose", "false")
645
					fmt.Println("Set 'quiet' mode.")
Michael Yang's avatar
Michael Yang committed
646
647
648
649
650
651
652
				case "mode":
					if len(args) > 2 {
						switch args[2] {
						case "vim":
							scanner.SetVimMode(true)
						case "emacs", "default":
							scanner.SetVimMode(false)
653
654
655
656
657
658
						default:
							usage()
						}
					} else {
						usage()
					}
659
660
				default:
					fmt.Printf("Unknown command '/set %s'. Type /? for help\n", args[1])
661
662
663
664
665
666
667
				}
			} else {
				usage()
			}
		case strings.HasPrefix(line, "/show"):
			args := strings.Fields(line)
			if len(args) > 1 {
Patrick Devine's avatar
Patrick Devine committed
668
				resp, err := server.GetModelInfo(model)
669
				if err != nil {
Patrick Devine's avatar
Patrick Devine committed
670
					fmt.Println("error: couldn't get model")
671
					return err
672
				}
Patrick Devine's avatar
Patrick Devine committed
673

674
675
				switch args[1] {
				case "license":
Patrick Devine's avatar
Patrick Devine committed
676
677
678
679
680
					fmt.Println(resp.License)
				case "modelfile":
					fmt.Println(resp.Modelfile)
				case "parameters":
					fmt.Println(resp.Parameters)
681
				case "system":
Patrick Devine's avatar
Patrick Devine committed
682
					fmt.Println(resp.System)
683
				case "template":
Patrick Devine's avatar
Patrick Devine committed
684
					fmt.Println(resp.Template)
685
				default:
686
					fmt.Printf("Unknown command '/show %s'. Type /? for help\n", args[1])
Michael Yang's avatar
Michael Yang committed
687
				}
688
689
			} else {
				usage()
Michael Yang's avatar
Michael Yang committed
690
691
692
693
694
			}
		case line == "/help", line == "/?":
			usage()
		case line == "/exit", line == "/bye":
			return nil
695
696
697
		case strings.HasPrefix(line, "/"):
			args := strings.Fields(line)
			fmt.Printf("Unknown command '%s'. Type /? for help\n", args[0])
Michael Yang's avatar
Michael Yang committed
698
699
		}

Patrick Devine's avatar
Patrick Devine committed
700
701
702
703
		if len(line) > 0 && line[0] != '/' {
			if err := generate(cmd, model, line); err != nil {
				return err
			}
Michael Yang's avatar
Michael Yang committed
704
705
		}
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
706
707
}

708
func generateBatch(cmd *cobra.Command, model string) error {
Michael Yang's avatar
Michael Yang committed
709
710
711
712
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		prompt := scanner.Text()
		fmt.Printf(">>> %s\n", prompt)
713
		if err := generate(cmd, model, prompt); err != nil {
Michael Yang's avatar
Michael Yang committed
714
715
716
717
718
719
720
			return err
		}
	}

	return nil
}

721
func RunServer(cmd *cobra.Command, _ []string) error {
Michael Yang's avatar
Michael Yang committed
722
723
724
	host, port, err := net.SplitHostPort(os.Getenv("OLLAMA_HOST"))
	if err != nil {
		host, port = "127.0.0.1", "11434"
Michael Yang's avatar
Michael Yang committed
725
		if ip := net.ParseIP(strings.Trim(os.Getenv("OLLAMA_HOST"), "[]")); ip != nil {
Michael Yang's avatar
Michael Yang committed
726
727
			host = ip.String()
		}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
728
	}
729

Michael Yang's avatar
Michael Yang committed
730
	if err := initializeKeypair(); err != nil {
731
732
733
		return err
	}

Michael Yang's avatar
Michael Yang committed
734
	ln, err := net.Listen("tcp", net.JoinHostPort(host, port))
735
736
737
	if err != nil {
		return err
	}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
738

739
740
741
742
743
	var origins []string
	if o := os.Getenv("OLLAMA_ORIGINS"); o != "" {
		origins = strings.Split(o, ",")
	}

744
745
746
747
	if noprune := os.Getenv("OLLAMA_NOPRUNE"); noprune == "" {
		if err := server.PruneLayers(); err != nil {
			return err
		}
Michael Yang's avatar
Michael Yang committed
748
749
750
751
752
753
754
755
756

		manifestsPath, err := server.GetManifestPath()
		if err != nil {
			return err
		}

		if err := server.PruneDirectory(manifestsPath); err != nil {
			return err
		}
757
758
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
759
	return server.Serve(ln, origins)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
760
761
}

762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
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)
		_, privKey, err := ed25519.GenerateKey(rand.Reader)
		if err != nil {
			return err
		}

		privKeyBytes, err := format.OpenSSHPrivateKey(privKey, "")
		if err != nil {
			return err
		}

Michael Yang's avatar
Michael Yang committed
784
		err = os.MkdirAll(filepath.Dir(privKeyPath), 0o755)
785
786
787
788
		if err != nil {
			return fmt.Errorf("could not create directory %w", err)
		}

789
		err = os.WriteFile(privKeyPath, pem.EncodeToMemory(privKeyBytes), 0o600)
790
791
792
793
794
795
796
797
798
799
800
		if err != nil {
			return err
		}

		sshPrivateKey, err := ssh.NewSignerFromKey(privKey)
		if err != nil {
			return err
		}

		pubKeyData := ssh.MarshalAuthorizedKey(sshPrivateKey.PublicKey())

801
		err = os.WriteFile(pubKeyPath, pubKeyData, 0o644)
802
803
804
805
806
807
808
809
810
		if err != nil {
			return err
		}

		fmt.Printf("Your new public key is: \n\n%s\n", string(pubKeyData))
	}
	return nil
}

Bruce MacDonald's avatar
Bruce MacDonald committed
811
func startMacApp(client *api.Client) error {
Bruce MacDonald's avatar
Bruce MacDonald committed
812
813
814
815
816
	exe, err := os.Executable()
	if err != nil {
		return err
	}
	link, err := os.Readlink(exe)
Bruce MacDonald's avatar
Bruce MacDonald committed
817
818
819
	if err != nil {
		return err
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
820
821
822
	if !strings.Contains(link, "Ollama.app") {
		return fmt.Errorf("could not find ollama app")
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
	path := strings.Split(link, "Ollama.app")
	if err := exec.Command("/usr/bin/open", "-a", path[0]+"Ollama.app").Run(); err != nil {
		return err
	}
	// wait for the server to start
	timeout := time.After(5 * time.Second)
	tick := time.Tick(500 * time.Millisecond)
	for {
		select {
		case <-timeout:
			return errors.New("timed out waiting for server to start")
		case <-tick:
			if err := client.Heartbeat(context.Background()); err == nil {
				return nil // server has started
			}
		}
	}
}

842
func checkServerHeartbeat(_ *cobra.Command, _ []string) error {
843
844
845
846
	client, err := api.FromEnv()
	if err != nil {
		return err
	}
847
	if err := client.Heartbeat(context.Background()); err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
848
849
850
851
852
		if !strings.Contains(err.Error(), "connection refused") {
			return err
		}
		if runtime.GOOS == "darwin" {
			if err := startMacApp(client); err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
853
				return fmt.Errorf("could not connect to ollama app, is it running?")
854
			}
Bruce MacDonald's avatar
Bruce MacDonald committed
855
		} else {
856
857
858
859
860
861
			return fmt.Errorf("could not connect to ollama server, run 'ollama serve' to start it")
		}
	}
	return nil
}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
862
863
864
865
func NewCLI() *cobra.Command {
	log.SetFlags(log.LstdFlags | log.Lshortfile)

	rootCmd := &cobra.Command{
866
867
868
869
		Use:           "ollama",
		Short:         "Large language model runner",
		SilenceUsage:  true,
		SilenceErrors: true,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
870
871
872
		CompletionOptions: cobra.CompletionOptions{
			DisableDefaultCmd: true,
		},
Michael Yang's avatar
Michael Yang committed
873
		Version: version.Version,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
874
875
876
877
	}

	cobra.EnableCommandSorting = false

878
	createCmd := &cobra.Command{
879
880
881
882
883
		Use:     "create MODEL",
		Short:   "Create a model from a Modelfile",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    CreateHandler,
884
885
886
887
	}

	createCmd.Flags().StringP("file", "f", "Modelfile", "Name of the Modelfile (default \"Modelfile\")")

Patrick Devine's avatar
Patrick Devine committed
888
889
890
891
892
893
894
895
896
897
898
899
900
901
	showCmd := &cobra.Command{
		Use:     "show MODEL",
		Short:   "Show information for a model",
		Args:    cobra.MinimumNArgs(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 prompt of a model")

Jeffrey Morgan's avatar
Jeffrey Morgan committed
902
	runCmd := &cobra.Command{
903
904
905
906
907
		Use:     "run MODEL [PROMPT]",
		Short:   "Run a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    RunHandler,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
908
909
	}

910
	runCmd.Flags().Bool("verbose", false, "Show timings for response")
911
	runCmd.Flags().Bool("insecure", false, "Use an insecure registry")
912
	runCmd.Flags().Bool("nowordwrap", false, "Don't wrap words to the next line automatically")
913

Jeffrey Morgan's avatar
Jeffrey Morgan committed
914
915
916
917
	serveCmd := &cobra.Command{
		Use:     "serve",
		Aliases: []string{"start"},
		Short:   "Start ollama",
Michael Yang's avatar
Michael Yang committed
918
		RunE:    RunServer,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
919
920
	}

921
	pullCmd := &cobra.Command{
922
923
924
925
926
		Use:     "pull MODEL",
		Short:   "Pull a model from a registry",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    PullHandler,
927
928
	}

929
930
	pullCmd.Flags().Bool("insecure", false, "Use an insecure registry")

931
	pushCmd := &cobra.Command{
932
933
934
935
936
		Use:     "push MODEL",
		Short:   "Push a model to a registry",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    PushHandler,
937
938
	}

939
940
	pushCmd.Flags().Bool("insecure", false, "Use an insecure registry")

Patrick Devine's avatar
Patrick Devine committed
941
	listCmd := &cobra.Command{
942
		Use:     "list",
Patrick Devine's avatar
Patrick Devine committed
943
		Aliases: []string{"ls"},
944
		Short:   "List models",
945
		PreRunE: checkServerHeartbeat,
946
		RunE:    ListHandler,
947
948
	}

Patrick Devine's avatar
Patrick Devine committed
949
	copyCmd := &cobra.Command{
950
951
952
953
954
		Use:     "cp",
		Short:   "Copy a model",
		Args:    cobra.MinimumNArgs(2),
		PreRunE: checkServerHeartbeat,
		RunE:    CopyHandler,
Patrick Devine's avatar
Patrick Devine committed
955
956
	}

957
	deleteCmd := &cobra.Command{
958
959
960
961
962
		Use:     "rm",
		Short:   "Remove a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    DeleteHandler,
Patrick Devine's avatar
Patrick Devine committed
963
964
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
965
966
	rootCmd.AddCommand(
		serveCmd,
967
		createCmd,
Patrick Devine's avatar
Patrick Devine committed
968
		showCmd,
969
		runCmd,
970
971
		pullCmd,
		pushCmd,
Patrick Devine's avatar
Patrick Devine committed
972
		listCmd,
Patrick Devine's avatar
Patrick Devine committed
973
		copyCmd,
974
		deleteCmd,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
975
976
977
978
	)

	return rootCmd
}