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/filepath"
18
	"runtime"
Michael Yang's avatar
Michael Yang committed
19
	"strings"
Michael Yang's avatar
Michael Yang committed
20
	"time"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
21

Michael Yang's avatar
Michael Yang committed
22
	"github.com/chzyer/readline"
Patrick Devine's avatar
Patrick Devine committed
23
24
	"github.com/dustin/go-humanize"
	"github.com/olekukonko/tablewriter"
Michael Yang's avatar
Michael Yang committed
25
	"github.com/spf13/cobra"
26
	"golang.org/x/crypto/ssh"
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
)

Patrick Devine's avatar
Patrick Devine committed
35
36
37
38
39
40
41
42
43
44
45
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
}

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

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

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

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

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

93
94
95
96
97
98
99
		return nil
	}

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

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

107
108
109
	return nil
}

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

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

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

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

	_, err = os.Stat(fp)
Michael Yang's avatar
Michael Yang committed
131
132
	switch {
	case errors.Is(err, os.ErrNotExist):
133
		if err := pull(args[0], insecure); err != nil {
Michael Yang's avatar
Michael Yang committed
134
135
136
137
138
139
140
141
			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
142
143
		}
	case err != nil:
Michael Yang's avatar
Michael Yang committed
144
145
146
147
		return err
	}

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

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

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

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

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

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

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

191
192
193
	return nil
}

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

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

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

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

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

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

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

	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
}

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

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

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

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

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

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

370
371
		return nil
	}
372

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Michael Yang's avatar
Michael Yang committed
479
480
481
482
483
484
	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
485
486
			readline.PcItem("verbose"),
			readline.PcItem("quiet"),
Michael Yang's avatar
Michael Yang committed
487
488
489
490
491
492
			readline.PcItem("mode",
				readline.PcItem("vim"),
				readline.PcItem("emacs"),
				readline.PcItem("default"),
			),
		),
493
494
		readline.PcItem("/show",
			readline.PcItem("license"),
Patrick Devine's avatar
Patrick Devine committed
495
496
			readline.PcItem("modelfile"),
			readline.PcItem("parameters"),
497
498
499
			readline.PcItem("system"),
			readline.PcItem("template"),
		),
Michael Yang's avatar
Michael Yang committed
500
501
502
503
504
505
506
507
508
509
		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
510
		Painter:      Painter{},
Michael Yang's avatar
Michael Yang committed
511
512
513
514
515
516
517
518
519
520
521
		Prompt:       ">>> ",
		HistoryFile:  filepath.Join(home, ".ollama", "history"),
		AutoComplete: completer,
	}

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

522
523
524
	var multiLineBuffer string
	var isMultiLine bool

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

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

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

Michael Yang's avatar
Michael Yang committed
542
		switch {
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
		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
559
560
		case strings.HasPrefix(line, "/list"):
			args := strings.Fields(line)
561
			if err := ListHandler(cmd, args[1:]); err != nil {
Michael Yang's avatar
Michael Yang committed
562
563
564
565
566
567
568
569
570
571
572
573
574
575
				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
576
577
578
579
580
581
				case "verbose":
					cmd.Flags().Set("verbose", "true")
					continue
				case "quiet":
					cmd.Flags().Set("verbose", "false")
					continue
Michael Yang's avatar
Michael Yang committed
582
583
584
585
586
587
588
589
590
				case "mode":
					if len(args) > 2 {
						switch args[2] {
						case "vim":
							scanner.SetVimMode(true)
							continue
						case "emacs", "default":
							scanner.SetVimMode(false)
							continue
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
						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
607
				resp, err := server.GetModelInfo(model)
608
				if err != nil {
Patrick Devine's avatar
Patrick Devine committed
609
					fmt.Println("error: couldn't get model")
610
611
					continue
				}
Patrick Devine's avatar
Patrick Devine committed
612

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

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

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

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

	return nil
}

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

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

669
670
671
672
673
674
675
	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
676
	}
677

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

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

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

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

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

702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
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
724
		err = os.MkdirAll(filepath.Dir(privKeyPath), 0o755)
725
726
727
728
		if err != nil {
			return fmt.Errorf("could not create directory %w", err)
		}

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

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

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

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

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

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

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

	cobra.EnableCommandSorting = false

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

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

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

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

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

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

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

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

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

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

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

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

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

	return rootCmd
}