cmd.go 21.1 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
	"path/filepath"
17
	"runtime"
Michael Yang's avatar
Michael Yang committed
18
	"strings"
Michael Yang's avatar
Michael Yang committed
19
	"time"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
20

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

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

35
type Painter struct {
36
	IsMultiLine bool
37
}
Patrick Devine's avatar
Patrick Devine committed
38

39
func (p Painter) Paint(line []rune, _ int) []rune {
Patrick Devine's avatar
Patrick Devine committed
40
	termType := os.Getenv("TERM")
41
42
43
44
45
	if termType == "xterm-256color" && len(line) == 0 {
		var prompt string
		if p.IsMultiLine {
			prompt = "Use \"\"\" to end multi-line input"
		} else {
Michael Yang's avatar
Michael Yang committed
46
			prompt = "Send a message (/? for help, /bye to exit)"
47
		}
Patrick Devine's avatar
Patrick Devine committed
48
49
		return []rune(fmt.Sprintf("\033[38;5;245m%s\033[%dD\033[0m", prompt, len(prompt)))
	}
50
51
	// 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
52
53
54
	return line
}

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

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

Michael Yang's avatar
Michael Yang committed
67
68
	var spinner *Spinner

69
70
	var currentDigest string
	var bar *progressbar.ProgressBar
Michael Yang's avatar
Michael Yang committed
71

72
73
74
75
76
77
78
	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
79
80
81
82
83
84
85
86
87
88
89
90
			switch {
			case strings.Contains(resp.Status, "embeddings"):
				bar = progressbar.Default(int64(resp.Total), resp.Status)
				bar.Set(resp.Completed)
			default:
				// pulling
				bar = progressbar.DefaultBytes(
					int64(resp.Total),
					resp.Status,
				)
				bar.Set(resp.Completed)
			}
91
92
93
94
95
96
97
98
99
100
		} else if resp.Digest == currentDigest && resp.Digest != "" {
			bar.Set(resp.Completed)
		} else {
			currentDigest = ""
			if spinner != nil {
				spinner.Stop()
			}
			spinner = NewSpinner(resp.Status)
			go spinner.Spin(100 * time.Millisecond)
		}
101

102
103
104
105
106
107
108
		return nil
	}

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

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

116
117
118
	return nil
}

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

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

130
131
132
133
	modelName, modelTag, ok := strings.Cut(args[0], ":")
	if !ok {
		modelTag = "latest"
	}
Michael Yang's avatar
Michael Yang committed
134

135
136
137
	for _, model := range models.Models {
		if model.Name == strings.Join([]string{modelName, modelTag}, ":") {
			return RunGenerate(cmd, args)
Michael Yang's avatar
Michael Yang committed
138
		}
139
140
141
	}

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

	return RunGenerate(cmd, args)
Bruce MacDonald's avatar
Bruce MacDonald committed
146
147
}

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

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

159
160
161
	var currentDigest string
	var bar *progressbar.ProgressBar

162
	request := api.PushRequest{Name: args[0], Insecure: insecure}
163
	fn := func(resp api.ProgressResponse) error {
164
165
166
167
168
169
170
171
172
173
174
175
176
177
		if resp.Digest != currentDigest && resp.Digest != "" {
			currentDigest = resp.Digest
			bar = progressbar.DefaultBytes(
				int64(resp.Total),
				fmt.Sprintf("pushing %s...", resp.Digest[7:19]),
			)

			bar.Set(resp.Completed)
		} else if resp.Digest == currentDigest && resp.Digest != "" {
			bar.Set(resp.Completed)
		} else {
			currentDigest = ""
			fmt.Println(resp.Status)
		}
178
179
180
181
182
183
		return nil
	}

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

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

189
190
191
	return nil
}

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

	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
206
		if len(args) == 0 || strings.HasPrefix(m.Name, args[0]) {
Patrick Devine's avatar
Patrick Devine committed
207
			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
208
		}
Patrick Devine's avatar
Patrick Devine committed
209
210
211
	}

	table := tablewriter.NewWriter(os.Stdout)
Patrick Devine's avatar
Patrick Devine committed
212
	table.SetHeader([]string{"NAME", "ID", "SIZE", "MODIFIED"})
Patrick Devine's avatar
Patrick Devine committed
213
214
215
216
217
218
219
220
221
222
223
224
	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
}

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

231
232
233
234
235
236
	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)
237
238
239
240
	}
	return nil
}

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

	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
319
func CopyHandler(cmd *cobra.Command, args []string) error {
320
321
322
323
	client, err := api.FromEnv()
	if err != nil {
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
324
325
326
327
328
329
330
331
332

	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
}

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

	return pull(args[0], insecure)
340
341
}

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

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

351
	request := api.PullRequest{Name: model, Insecure: insecure}
352
353
354
	fn := func(resp api.ProgressResponse) error {
		if resp.Digest != currentDigest && resp.Digest != "" {
			currentDigest = resp.Digest
355
356
			bar = progressbar.DefaultBytes(
				int64(resp.Total),
357
				fmt.Sprintf("pulling %s...", resp.Digest[7:19]),
358
			)
359
360
361

			bar.Set(resp.Completed)
		} else if resp.Digest == currentDigest && resp.Digest != "" {
362
363
			bar.Set(resp.Completed)
		} else {
364
			currentDigest = ""
365
366
			fmt.Println(resp.Status)
		}
367

368
369
		return nil
	}
370

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

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

379
	return nil
Michael Yang's avatar
Michael Yang committed
380
381
}

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

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

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

Michael Yang's avatar
Michael Yang committed
395
type generateContextKey string
Michael Yang's avatar
Michael Yang committed
396

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

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

Patrick Devine's avatar
Patrick Devine committed
406
	var latest api.GenerateResponse
407

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

413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
	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
	}

	var currentLineLength int
	var wordBuffer string

Patrick Devine's avatar
Patrick Devine committed
436
437
438
439
440
	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
441

Patrick Devine's avatar
Patrick Devine committed
442
		latest = response
443

444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
		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
469
470
		return nil
	}
471

Patrick Devine's avatar
Patrick Devine committed
472
473
474
475
476
477
478
479
480
481
482
	if err := client.Generate(context.Background(), &request, fn); err != nil {
		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)
483
			}
484
		}
Patrick Devine's avatar
Patrick Devine committed
485
486
487
		return err
	}
	if prompt != "" {
Michael Yang's avatar
Michael Yang committed
488
489
		fmt.Println()
		fmt.Println()
Patrick Devine's avatar
Patrick Devine committed
490
	}
491

Patrick Devine's avatar
Patrick Devine committed
492
493
494
	if !latest.Done {
		return errors.New("unexpected end of response")
	}
495

Patrick Devine's avatar
Patrick Devine committed
496
497
498
499
	verbose, err := cmd.Flags().GetBool("verbose")
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
500

Patrick Devine's avatar
Patrick Devine committed
501
502
	if verbose {
		latest.Summary()
Michael Yang's avatar
Michael Yang committed
503
	}
Michael Yang's avatar
Michael Yang committed
504

Patrick Devine's avatar
Patrick Devine committed
505
506
507
508
	ctx := cmd.Context()
	ctx = context.WithValue(ctx, generateContextKey("context"), latest.Context)
	cmd.SetContext(ctx)

Michael Yang's avatar
Michael Yang committed
509
510
511
	return nil
}

512
func generateInteractive(cmd *cobra.Command, model string) error {
Michael Yang's avatar
Michael Yang committed
513
514
515
516
517
	home, err := os.UserHomeDir()
	if err != nil {
		return err
	}

Patrick Devine's avatar
Patrick Devine committed
518
519
520
521
522
	// load the model
	if err := generate(cmd, model, ""); err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
523
524
525
526
527
528
	completer := readline.NewPrefixCompleter(
		readline.PcItem("/help"),
		readline.PcItem("/list"),
		readline.PcItem("/set",
			readline.PcItem("history"),
			readline.PcItem("nohistory"),
529
530
			readline.PcItem("wordwrap"),
			readline.PcItem("nowordwrap"),
Michael Yang's avatar
Michael Yang committed
531
532
			readline.PcItem("verbose"),
			readline.PcItem("quiet"),
Michael Yang's avatar
Michael Yang committed
533
		),
534
535
		readline.PcItem("/show",
			readline.PcItem("license"),
Patrick Devine's avatar
Patrick Devine committed
536
537
			readline.PcItem("modelfile"),
			readline.PcItem("parameters"),
538
539
540
			readline.PcItem("system"),
			readline.PcItem("template"),
		),
Michael Yang's avatar
Michael Yang committed
541
542
543
544
545
546
547
548
549
		readline.PcItem("/exit"),
		readline.PcItem("/bye"),
	)

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

550
551
	var painter Painter

Michael Yang's avatar
Michael Yang committed
552
	config := readline.Config{
553
		Painter:      &painter,
Michael Yang's avatar
Michael Yang committed
554
555
556
557
558
559
560
561
562
563
564
		Prompt:       ">>> ",
		HistoryFile:  filepath.Join(home, ".ollama", "history"),
		AutoComplete: completer,
	}

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

565
566
567
	var multiLineBuffer string
	var isMultiLine bool

Michael Yang's avatar
Michael Yang committed
568
569
570
571
572
573
	for {
		line, err := scanner.Readline()
		switch {
		case errors.Is(err, io.EOF):
			return nil
		case errors.Is(err, readline.ErrInterrupt):
574
575
576
577
			if line == "" {
				return nil
			}

Michael Yang's avatar
Michael Yang committed
578
579
			continue
		case err != nil:
Michael Yang's avatar
Michael Yang committed
580
581
582
			return err
		}

Michael Yang's avatar
Michael Yang committed
583
		line = strings.TrimSpace(line)
Michael Yang's avatar
Michael Yang committed
584

Michael Yang's avatar
Michael Yang committed
585
		switch {
586
587
588
		case isMultiLine:
			if strings.HasSuffix(line, `"""`) {
				isMultiLine = false
589
				painter.IsMultiLine = isMultiLine
590
				multiLineBuffer += strings.TrimSuffix(line, `"""`)
591
				line = multiLineBuffer
592
593
594
595
596
597
598
599
				multiLineBuffer = ""
				scanner.SetPrompt(">>> ")
			} else {
				multiLineBuffer += line + " "
				continue
			}
		case strings.HasPrefix(line, `"""`):
			isMultiLine = true
600
			painter.IsMultiLine = isMultiLine
601
602
603
			multiLineBuffer = strings.TrimPrefix(line, `"""`) + " "
			scanner.SetPrompt("... ")
			continue
Michael Yang's avatar
Michael Yang committed
604
605
		case strings.HasPrefix(line, "/list"):
			args := strings.Fields(line)
606
			if err := ListHandler(cmd, args[1:]); err != nil {
Michael Yang's avatar
Michael Yang committed
607
608
609
610
611
612
613
614
615
616
				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()
617
618
619
620
621
622
				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
623
624
				case "verbose":
					cmd.Flags().Set("verbose", "true")
625
					fmt.Println("Set 'verbose' mode.")
Michael Yang's avatar
Michael Yang committed
626
627
				case "quiet":
					cmd.Flags().Set("verbose", "false")
628
					fmt.Println("Set 'quiet' mode.")
Michael Yang's avatar
Michael Yang committed
629
630
631
632
633
634
635
				case "mode":
					if len(args) > 2 {
						switch args[2] {
						case "vim":
							scanner.SetVimMode(true)
						case "emacs", "default":
							scanner.SetVimMode(false)
636
637
638
639
640
641
642
643
644
645
646
647
648
						default:
							usage()
						}
					} else {
						usage()
					}
				}
			} else {
				usage()
			}
		case strings.HasPrefix(line, "/show"):
			args := strings.Fields(line)
			if len(args) > 1 {
Patrick Devine's avatar
Patrick Devine committed
649
				resp, err := server.GetModelInfo(model)
650
				if err != nil {
Patrick Devine's avatar
Patrick Devine committed
651
					fmt.Println("error: couldn't get model")
652
				}
Patrick Devine's avatar
Patrick Devine committed
653

654
655
				switch args[1] {
				case "license":
Patrick Devine's avatar
Patrick Devine committed
656
657
658
659
660
					fmt.Println(resp.License)
				case "modelfile":
					fmt.Println(resp.Modelfile)
				case "parameters":
					fmt.Println(resp.Parameters)
661
				case "system":
Patrick Devine's avatar
Patrick Devine committed
662
					fmt.Println(resp.System)
663
				case "template":
Patrick Devine's avatar
Patrick Devine committed
664
					fmt.Println(resp.Template)
665
				default:
Patrick Devine's avatar
Patrick Devine committed
666
					fmt.Println("error: unknown command")
Michael Yang's avatar
Michael Yang committed
667
				}
668
669
			} else {
				usage()
Michael Yang's avatar
Michael Yang committed
670
671
672
673
674
			}
		case line == "/help", line == "/?":
			usage()
		case line == "/exit", line == "/bye":
			return nil
675
676
677
		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
678
679
		}

Patrick Devine's avatar
Patrick Devine committed
680
681
682
683
		if len(line) > 0 && line[0] != '/' {
			if err := generate(cmd, model, line); err != nil {
				return err
			}
Michael Yang's avatar
Michael Yang committed
684
685
		}
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
686
687
}

688
func generateBatch(cmd *cobra.Command, model string) error {
Michael Yang's avatar
Michael Yang committed
689
690
691
692
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		prompt := scanner.Text()
		fmt.Printf(">>> %s\n", prompt)
693
		if err := generate(cmd, model, prompt); err != nil {
Michael Yang's avatar
Michael Yang committed
694
695
696
697
698
699
700
			return err
		}
	}

	return nil
}

701
func RunServer(cmd *cobra.Command, _ []string) error {
Michael Yang's avatar
Michael Yang committed
702
703
704
	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
705
		if ip := net.ParseIP(strings.Trim(os.Getenv("OLLAMA_HOST"), "[]")); ip != nil {
Michael Yang's avatar
Michael Yang committed
706
707
			host = ip.String()
		}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
708
	}
709

Michael Yang's avatar
Michael Yang committed
710
	if err := initializeKeypair(); err != nil {
711
712
713
		return err
	}

Michael Yang's avatar
Michael Yang committed
714
	ln, err := net.Listen("tcp", net.JoinHostPort(host, port))
715
716
717
	if err != nil {
		return err
	}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
718

719
720
721
722
723
	var origins []string
	if o := os.Getenv("OLLAMA_ORIGINS"); o != "" {
		origins = strings.Split(o, ",")
	}

724
725
726
727
728
729
	if noprune := os.Getenv("OLLAMA_NOPRUNE"); noprune == "" {
		if err := server.PruneLayers(); err != nil {
			return err
		}
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
730
	return server.Serve(ln, origins)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
731
732
}

733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
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
755
		err = os.MkdirAll(filepath.Dir(privKeyPath), 0o755)
756
757
758
759
		if err != nil {
			return fmt.Errorf("could not create directory %w", err)
		}

760
		err = os.WriteFile(privKeyPath, pem.EncodeToMemory(privKeyBytes), 0o600)
761
762
763
764
765
766
767
768
769
770
771
		if err != nil {
			return err
		}

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

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

772
		err = os.WriteFile(pubKeyPath, pubKeyData, 0o644)
773
774
775
776
777
778
779
780
781
		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
782
func startMacApp(client *api.Client) error {
Bruce MacDonald's avatar
Bruce MacDonald committed
783
784
785
786
787
	exe, err := os.Executable()
	if err != nil {
		return err
	}
	link, err := os.Readlink(exe)
Bruce MacDonald's avatar
Bruce MacDonald committed
788
789
790
	if err != nil {
		return err
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
791
792
793
	if !strings.Contains(link, "Ollama.app") {
		return fmt.Errorf("could not find ollama app")
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
	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
			}
		}
	}
}

813
func checkServerHeartbeat(_ *cobra.Command, _ []string) error {
814
815
816
817
	client, err := api.FromEnv()
	if err != nil {
		return err
	}
818
	if err := client.Heartbeat(context.Background()); err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
819
820
821
822
823
		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
824
				return fmt.Errorf("could not connect to ollama app, is it running?")
825
			}
Bruce MacDonald's avatar
Bruce MacDonald committed
826
		} else {
827
828
829
830
831
832
			return fmt.Errorf("could not connect to ollama server, run 'ollama serve' to start it")
		}
	}
	return nil
}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
833
834
835
836
func NewCLI() *cobra.Command {
	log.SetFlags(log.LstdFlags | log.Lshortfile)

	rootCmd := &cobra.Command{
837
838
839
840
		Use:           "ollama",
		Short:         "Large language model runner",
		SilenceUsage:  true,
		SilenceErrors: true,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
841
842
843
		CompletionOptions: cobra.CompletionOptions{
			DisableDefaultCmd: true,
		},
Michael Yang's avatar
Michael Yang committed
844
		Version: version.Version,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
845
846
847
848
	}

	cobra.EnableCommandSorting = false

849
	createCmd := &cobra.Command{
850
851
852
853
854
		Use:     "create MODEL",
		Short:   "Create a model from a Modelfile",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    CreateHandler,
855
856
857
858
	}

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

Patrick Devine's avatar
Patrick Devine committed
859
860
861
862
863
864
865
866
867
868
869
870
871
872
	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
873
	runCmd := &cobra.Command{
874
875
876
877
878
		Use:     "run MODEL [PROMPT]",
		Short:   "Run a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    RunHandler,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
879
880
	}

881
	runCmd.Flags().Bool("verbose", false, "Show timings for response")
882
	runCmd.Flags().Bool("insecure", false, "Use an insecure registry")
883
	runCmd.Flags().Bool("nowordwrap", false, "Don't wrap words to the next line automatically")
884

Jeffrey Morgan's avatar
Jeffrey Morgan committed
885
886
887
888
	serveCmd := &cobra.Command{
		Use:     "serve",
		Aliases: []string{"start"},
		Short:   "Start ollama",
Michael Yang's avatar
Michael Yang committed
889
		RunE:    RunServer,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
890
891
	}

892
	pullCmd := &cobra.Command{
893
894
895
896
897
		Use:     "pull MODEL",
		Short:   "Pull a model from a registry",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    PullHandler,
898
899
	}

900
901
	pullCmd.Flags().Bool("insecure", false, "Use an insecure registry")

902
	pushCmd := &cobra.Command{
903
904
905
906
907
		Use:     "push MODEL",
		Short:   "Push a model to a registry",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    PushHandler,
908
909
	}

910
911
	pushCmd.Flags().Bool("insecure", false, "Use an insecure registry")

Patrick Devine's avatar
Patrick Devine committed
912
	listCmd := &cobra.Command{
913
		Use:     "list",
Patrick Devine's avatar
Patrick Devine committed
914
		Aliases: []string{"ls"},
915
		Short:   "List models",
916
		PreRunE: checkServerHeartbeat,
917
		RunE:    ListHandler,
918
919
	}

Patrick Devine's avatar
Patrick Devine committed
920
	copyCmd := &cobra.Command{
921
922
923
924
925
		Use:     "cp",
		Short:   "Copy a model",
		Args:    cobra.MinimumNArgs(2),
		PreRunE: checkServerHeartbeat,
		RunE:    CopyHandler,
Patrick Devine's avatar
Patrick Devine committed
926
927
	}

928
	deleteCmd := &cobra.Command{
929
930
931
932
933
		Use:     "rm",
		Short:   "Remove a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    DeleteHandler,
Patrick Devine's avatar
Patrick Devine committed
934
935
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
936
937
	rootCmd.AddCommand(
		serveCmd,
938
		createCmd,
Patrick Devine's avatar
Patrick Devine committed
939
		showCmd,
940
		runCmd,
941
942
		pullCmd,
		pushCmd,
Patrick Devine's avatar
Patrick Devine committed
943
		listCmd,
Patrick Devine's avatar
Patrick Devine committed
944
		copyCmd,
945
		deleteCmd,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
946
947
948
949
	)

	return rootCmd
}