cmd.go 19.8 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
	"log"
	"net"
Michael Yang's avatar
Michael Yang committed
14
	"net/http"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
15
	"os"
16
	"os/exec"
17
	"path"
18
	"path/filepath"
19
	"runtime"
Michael Yang's avatar
Michael Yang committed
20
	"strings"
Michael Yang's avatar
Michael Yang committed
21
	"time"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
22

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

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

Patrick Devine's avatar
Patrick Devine committed
36
37
38
39
40
41
42
43
44
45
46
type Painter struct{}

func (p Painter) Paint(line []rune, l int) []rune {
	termType := os.Getenv("TERM")
	if termType == "xterm-256color" && len(line) == 0 {
		prompt := "Send a message (/? for help)"
		return []rune(fmt.Sprintf("\033[38;5;245m%s\033[%dD\033[0m", prompt, len(prompt)))
	}
	return line
}

47
func CreateHandler(cmd *cobra.Command, args []string) error {
48
	filename, _ := cmd.Flags().GetString("file")
49
50
51
52
53
	filename, err := filepath.Abs(filename)
	if err != nil {
		return err
	}

54
55
56
57
	client, err := api.FromEnv()
	if err != nil {
		return err
	}
58

Michael Yang's avatar
Michael Yang committed
59
60
	var spinner *Spinner

61
62
	var currentDigest string
	var bar *progressbar.ProgressBar
Michael Yang's avatar
Michael Yang committed
63

64
65
66
67
68
69
70
	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
71
72
73
74
75
76
77
78
79
80
81
82
			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)
			}
83
84
85
86
87
88
89
90
91
92
		} 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)
		}
93

94
95
96
97
98
99
100
		return nil
	}

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

Michael Yang's avatar
Michael Yang committed
101
102
	if spinner != nil {
		spinner.Stop()
103
104
105
		if spinner.description != "success" {
			return errors.New("unexpected end to create model")
		}
Michael Yang's avatar
Michael Yang committed
106
107
	}

108
109
110
	return nil
}

111
func RunHandler(cmd *cobra.Command, args []string) error {
112
113
114
115
116
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

117
	mp := server.ParseModelPath(args[0])
118
119
120
121
	if err != nil {
		return err
	}

122
123
124
125
	if mp.ProtocolScheme == "http" && !insecure {
		return fmt.Errorf("insecure protocol http")
	}

Patrick Devine's avatar
Patrick Devine committed
126
127
128
129
130
131
	fp, err := mp.GetManifestPath(false)
	if err != nil {
		return err
	}

	_, err = os.Stat(fp)
Michael Yang's avatar
Michael Yang committed
132
133
	switch {
	case errors.Is(err, os.ErrNotExist):
134
		if err := pull(args[0], insecure); err != nil {
Michael Yang's avatar
Michael Yang committed
135
136
137
138
139
140
141
142
			var apiStatusError api.StatusError
			if !errors.As(err, &apiStatusError) {
				return err
			}

			if apiStatusError.StatusCode != http.StatusBadGateway {
				return err
			}
Michael Yang's avatar
Michael Yang committed
143
144
		}
	case err != nil:
Michael Yang's avatar
Michael Yang committed
145
146
147
148
		return err
	}

	return RunGenerate(cmd, args)
Bruce MacDonald's avatar
Bruce MacDonald committed
149
150
}

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

157
158
159
160
161
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

162
163
164
	var currentDigest string
	var bar *progressbar.ProgressBar

165
	request := api.PushRequest{Name: args[0], Insecure: insecure}
166
	fn := func(resp api.ProgressResponse) error {
167
168
169
170
171
172
173
174
175
176
177
178
179
180
		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)
		}
181
182
183
184
185
186
		return nil
	}

	if err := client.Push(context.Background(), &request, fn); err != nil {
		return err
	}
187
188
189
190
191

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

192
193
194
	return nil
}

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

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

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

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

234
235
236
237
238
239
	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)
240
241
242
243
	}
	return nil
}

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

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

	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
}

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

	return pull(args[0], insecure)
343
344
}

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

351
	var currentDigest string
Bruce MacDonald's avatar
Bruce MacDonald committed
352
	var bar *progressbar.ProgressBar
Michael Yang's avatar
Michael Yang committed
353

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

			bar.Set(resp.Completed)
		} else if resp.Digest == currentDigest && resp.Digest != "" {
365
366
			bar.Set(resp.Completed)
		} else {
367
			currentDigest = ""
368
369
			fmt.Println(resp.Status)
		}
370

371
372
		return nil
	}
373

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

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

382
	return nil
Michael Yang's avatar
Michael Yang committed
383
384
}

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

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

395
	return generateBatch(cmd, args[0])
Michael Yang's avatar
Michael Yang committed
396
397
}

Michael Yang's avatar
Michael Yang committed
398
type generateContextKey string
Michael Yang's avatar
Michael Yang committed
399

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

Patrick Devine's avatar
Patrick Devine committed
406
407
	spinner := NewSpinner("")
	go spinner.Spin(60 * time.Millisecond)
Michael Yang's avatar
Michael Yang committed
408

Patrick Devine's avatar
Patrick Devine committed
409
	var latest api.GenerateResponse
410

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

Patrick Devine's avatar
Patrick Devine committed
416
417
418
419
420
	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
421

Patrick Devine's avatar
Patrick Devine committed
422
		latest = response
423

Patrick Devine's avatar
Patrick Devine committed
424
425
426
		fmt.Print(response.Response)
		return nil
	}
427

Patrick Devine's avatar
Patrick Devine committed
428
429
430
431
432
433
434
435
436
437
438
	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)
439
			}
440
		}
Patrick Devine's avatar
Patrick Devine committed
441
442
		return err
	}
Michael Yang's avatar
Michael Yang committed
443

Patrick Devine's avatar
Patrick Devine committed
444
	if prompt != "" {
Michael Yang's avatar
Michael Yang committed
445
446
		fmt.Println()
		fmt.Println()
Patrick Devine's avatar
Patrick Devine committed
447
	}
448

Patrick Devine's avatar
Patrick Devine committed
449
450
451
	if !latest.Done {
		return errors.New("unexpected end of response")
	}
452

Patrick Devine's avatar
Patrick Devine committed
453
454
455
456
	verbose, err := cmd.Flags().GetBool("verbose")
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
457

Patrick Devine's avatar
Patrick Devine committed
458
459
	if verbose {
		latest.Summary()
Michael Yang's avatar
Michael Yang committed
460
	}
Michael Yang's avatar
Michael Yang committed
461

Patrick Devine's avatar
Patrick Devine committed
462
463
464
465
	ctx := cmd.Context()
	ctx = context.WithValue(ctx, generateContextKey("context"), latest.Context)
	cmd.SetContext(ctx)

Michael Yang's avatar
Michael Yang committed
466
467
468
	return nil
}

469
func generateInteractive(cmd *cobra.Command, model string) error {
Michael Yang's avatar
Michael Yang committed
470
471
472
473
474
	home, err := os.UserHomeDir()
	if err != nil {
		return err
	}

Patrick Devine's avatar
Patrick Devine committed
475
476
477
478
479
	// load the model
	if err := generate(cmd, model, ""); err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
480
481
482
483
484
485
	completer := readline.NewPrefixCompleter(
		readline.PcItem("/help"),
		readline.PcItem("/list"),
		readline.PcItem("/set",
			readline.PcItem("history"),
			readline.PcItem("nohistory"),
Michael Yang's avatar
Michael Yang committed
486
487
			readline.PcItem("verbose"),
			readline.PcItem("quiet"),
Michael Yang's avatar
Michael Yang committed
488
489
490
491
492
493
			readline.PcItem("mode",
				readline.PcItem("vim"),
				readline.PcItem("emacs"),
				readline.PcItem("default"),
			),
		),
494
495
		readline.PcItem("/show",
			readline.PcItem("license"),
Patrick Devine's avatar
Patrick Devine committed
496
497
			readline.PcItem("modelfile"),
			readline.PcItem("parameters"),
498
499
500
			readline.PcItem("system"),
			readline.PcItem("template"),
		),
Michael Yang's avatar
Michael Yang committed
501
502
503
504
505
506
507
508
509
510
		readline.PcItem("/exit"),
		readline.PcItem("/bye"),
	)

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

	config := readline.Config{
Patrick Devine's avatar
Patrick Devine committed
511
		Painter:      Painter{},
Michael Yang's avatar
Michael Yang committed
512
513
514
515
516
517
518
519
520
521
522
		Prompt:       ">>> ",
		HistoryFile:  filepath.Join(home, ".ollama", "history"),
		AutoComplete: completer,
	}

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

523
524
525
	var multiLineBuffer string
	var isMultiLine bool

Michael Yang's avatar
Michael Yang committed
526
527
528
529
530
531
	for {
		line, err := scanner.Readline()
		switch {
		case errors.Is(err, io.EOF):
			return nil
		case errors.Is(err, readline.ErrInterrupt):
532
533
534
535
			if line == "" {
				return nil
			}

Michael Yang's avatar
Michael Yang committed
536
537
			continue
		case err != nil:
Michael Yang's avatar
Michael Yang committed
538
539
540
			return err
		}

Michael Yang's avatar
Michael Yang committed
541
		line = strings.TrimSpace(line)
Michael Yang's avatar
Michael Yang committed
542

Michael Yang's avatar
Michael Yang committed
543
		switch {
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
		case isMultiLine:
			if strings.HasSuffix(line, `"""`) {
				isMultiLine = false
				multiLineBuffer += strings.TrimSuffix(line, `"""`)
				line = multiLineBuffer
				multiLineBuffer = ""
				scanner.SetPrompt(">>> ")
			} else {
				multiLineBuffer += line + " "
				continue
			}
		case strings.HasPrefix(line, `"""`):
			isMultiLine = true
			multiLineBuffer = strings.TrimPrefix(line, `"""`) + " "
			scanner.SetPrompt("... ")
			continue
Michael Yang's avatar
Michael Yang committed
560
561
		case strings.HasPrefix(line, "/list"):
			args := strings.Fields(line)
562
			if err := ListHandler(cmd, args[1:]); err != nil {
Michael Yang's avatar
Michael Yang committed
563
564
565
566
567
568
569
570
571
572
573
574
575
576
				return err
			}

			continue
		case strings.HasPrefix(line, "/set"):
			args := strings.Fields(line)
			if len(args) > 1 {
				switch args[1] {
				case "history":
					scanner.HistoryEnable()
					continue
				case "nohistory":
					scanner.HistoryDisable()
					continue
Michael Yang's avatar
Michael Yang committed
577
578
579
580
581
582
				case "verbose":
					cmd.Flags().Set("verbose", "true")
					continue
				case "quiet":
					cmd.Flags().Set("verbose", "false")
					continue
Michael Yang's avatar
Michael Yang committed
583
584
585
586
587
588
589
590
591
				case "mode":
					if len(args) > 2 {
						switch args[2] {
						case "vim":
							scanner.SetVimMode(true)
							continue
						case "emacs", "default":
							scanner.SetVimMode(false)
							continue
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
						default:
							usage()
							continue
						}
					} else {
						usage()
						continue
					}
				}
			} else {
				usage()
				continue
			}
		case strings.HasPrefix(line, "/show"):
			args := strings.Fields(line)
			if len(args) > 1 {
Patrick Devine's avatar
Patrick Devine committed
608
				resp, err := server.GetModelInfo(model)
609
				if err != nil {
Patrick Devine's avatar
Patrick Devine committed
610
					fmt.Println("error: couldn't get model")
611
612
					continue
				}
Patrick Devine's avatar
Patrick Devine committed
613

614
615
				switch args[1] {
				case "license":
Patrick Devine's avatar
Patrick Devine committed
616
617
618
619
620
					fmt.Println(resp.License)
				case "modelfile":
					fmt.Println(resp.Modelfile)
				case "parameters":
					fmt.Println(resp.Parameters)
621
				case "system":
Patrick Devine's avatar
Patrick Devine committed
622
					fmt.Println(resp.System)
623
				case "template":
Patrick Devine's avatar
Patrick Devine committed
624
					fmt.Println(resp.Template)
625
				default:
Patrick Devine's avatar
Patrick Devine committed
626
					fmt.Println("error: unknown command")
Michael Yang's avatar
Michael Yang committed
627
				}
Patrick Devine's avatar
Patrick Devine committed
628
629

				continue
630
631
632
			} else {
				usage()
				continue
Michael Yang's avatar
Michael Yang committed
633
634
635
636
637
638
639
640
			}
		case line == "/help", line == "/?":
			usage()
			continue
		case line == "/exit", line == "/bye":
			return nil
		}

Patrick Devine's avatar
Patrick Devine committed
641
642
643
644
		if len(line) > 0 && line[0] != '/' {
			if err := generate(cmd, model, line); err != nil {
				return err
			}
Michael Yang's avatar
Michael Yang committed
645
646
		}
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
647
648
}

649
func generateBatch(cmd *cobra.Command, model string) error {
Michael Yang's avatar
Michael Yang committed
650
651
652
653
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		prompt := scanner.Text()
		fmt.Printf(">>> %s\n", prompt)
654
		if err := generate(cmd, model, prompt); err != nil {
Michael Yang's avatar
Michael Yang committed
655
656
657
658
659
660
661
			return err
		}
	}

	return nil
}

662
func RunServer(cmd *cobra.Command, _ []string) error {
663
	host, port := "127.0.0.1", "11434"
664
665
666
667

	parts := strings.Split(os.Getenv("OLLAMA_HOST"), ":")
	if ip := net.ParseIP(parts[0]); ip != nil {
		host = ip.String()
668
669
	}

670
671
672
673
674
675
676
	if len(parts) > 1 {
		port = parts[1]
	}

	// deprecated: include port in OLLAMA_HOST
	if p := os.Getenv("OLLAMA_PORT"); p != "" {
		port = p
Jeffrey Morgan's avatar
Jeffrey Morgan committed
677
	}
678

679
680
681
682
683
	err := initializeKeypair()
	if err != nil {
		return err
	}

684
	ln, err := net.Listen("tcp", fmt.Sprintf("%s:%s", host, port))
685
686
687
	if err != nil {
		return err
	}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
688

689
690
691
692
693
	var origins []string
	if o := os.Getenv("OLLAMA_ORIGINS"); o != "" {
		origins = strings.Split(o, ",")
	}

694
695
696
697
698
699
	if noprune := os.Getenv("OLLAMA_NOPRUNE"); noprune == "" {
		if err := server.PruneLayers(); err != nil {
			return err
		}
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
700
	return server.Serve(ln, origins)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
701
702
}

703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
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
		}

725
726
727
728
729
		err = os.MkdirAll(path.Dir(privKeyPath), 0o700)
		if err != nil {
			return fmt.Errorf("could not create directory %w", err)
		}

730
		err = os.WriteFile(privKeyPath, pem.EncodeToMemory(privKeyBytes), 0o600)
731
732
733
734
735
736
737
738
739
740
741
		if err != nil {
			return err
		}

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

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

742
		err = os.WriteFile(pubKeyPath, pubKeyData, 0o644)
743
744
745
746
747
748
749
750
751
		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
752
func startMacApp(client *api.Client) error {
Bruce MacDonald's avatar
Bruce MacDonald committed
753
754
755
756
757
	exe, err := os.Executable()
	if err != nil {
		return err
	}
	link, err := os.Readlink(exe)
Bruce MacDonald's avatar
Bruce MacDonald committed
758
759
760
	if err != nil {
		return err
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
761
762
763
	if !strings.Contains(link, "Ollama.app") {
		return fmt.Errorf("could not find ollama app")
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
	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
			}
		}
	}
}

783
func checkServerHeartbeat(_ *cobra.Command, _ []string) error {
784
785
786
787
	client, err := api.FromEnv()
	if err != nil {
		return err
	}
788
	if err := client.Heartbeat(context.Background()); err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
789
790
791
792
793
		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
794
				return fmt.Errorf("could not connect to ollama app, is it running?")
795
			}
Bruce MacDonald's avatar
Bruce MacDonald committed
796
		} else {
797
798
799
800
801
802
			return fmt.Errorf("could not connect to ollama server, run 'ollama serve' to start it")
		}
	}
	return nil
}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
803
804
805
806
func NewCLI() *cobra.Command {
	log.SetFlags(log.LstdFlags | log.Lshortfile)

	rootCmd := &cobra.Command{
807
808
809
810
		Use:           "ollama",
		Short:         "Large language model runner",
		SilenceUsage:  true,
		SilenceErrors: true,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
811
812
813
		CompletionOptions: cobra.CompletionOptions{
			DisableDefaultCmd: true,
		},
Michael Yang's avatar
Michael Yang committed
814
		Version: version.Version,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
815
816
817
818
	}

	cobra.EnableCommandSorting = false

819
	createCmd := &cobra.Command{
820
821
822
823
824
		Use:     "create MODEL",
		Short:   "Create a model from a Modelfile",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    CreateHandler,
825
826
827
828
	}

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

Patrick Devine's avatar
Patrick Devine committed
829
830
831
832
833
834
835
836
837
838
839
840
841
842
	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
843
	runCmd := &cobra.Command{
844
845
846
847
848
		Use:     "run MODEL [PROMPT]",
		Short:   "Run a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    RunHandler,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
849
850
	}

851
	runCmd.Flags().Bool("verbose", false, "Show timings for response")
852
	runCmd.Flags().Bool("insecure", false, "Use an insecure registry")
853

Jeffrey Morgan's avatar
Jeffrey Morgan committed
854
855
856
857
	serveCmd := &cobra.Command{
		Use:     "serve",
		Aliases: []string{"start"},
		Short:   "Start ollama",
Michael Yang's avatar
Michael Yang committed
858
		RunE:    RunServer,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
859
860
	}

861
	pullCmd := &cobra.Command{
862
863
864
865
866
		Use:     "pull MODEL",
		Short:   "Pull a model from a registry",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    PullHandler,
867
868
	}

869
870
	pullCmd.Flags().Bool("insecure", false, "Use an insecure registry")

871
	pushCmd := &cobra.Command{
872
873
874
875
876
		Use:     "push MODEL",
		Short:   "Push a model to a registry",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    PushHandler,
877
878
	}

879
880
	pushCmd.Flags().Bool("insecure", false, "Use an insecure registry")

Patrick Devine's avatar
Patrick Devine committed
881
	listCmd := &cobra.Command{
882
		Use:     "list",
Patrick Devine's avatar
Patrick Devine committed
883
		Aliases: []string{"ls"},
884
		Short:   "List models",
885
		PreRunE: checkServerHeartbeat,
886
		RunE:    ListHandler,
887
888
	}

Patrick Devine's avatar
Patrick Devine committed
889
	copyCmd := &cobra.Command{
890
891
892
893
894
		Use:     "cp",
		Short:   "Copy a model",
		Args:    cobra.MinimumNArgs(2),
		PreRunE: checkServerHeartbeat,
		RunE:    CopyHandler,
Patrick Devine's avatar
Patrick Devine committed
895
896
	}

897
	deleteCmd := &cobra.Command{
898
899
900
901
902
		Use:     "rm",
		Short:   "Remove a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    DeleteHandler,
Patrick Devine's avatar
Patrick Devine committed
903
904
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
905
906
	rootCmd.AddCommand(
		serveCmd,
907
		createCmd,
Patrick Devine's avatar
Patrick Devine committed
908
		showCmd,
909
		runCmd,
910
911
		pullCmd,
		pushCmd,
Patrick Devine's avatar
Patrick Devine committed
912
		listCmd,
Patrick Devine's avatar
Patrick Devine committed
913
		copyCmd,
914
		deleteCmd,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
915
916
917
918
	)

	return rootCmd
}