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

import (
Michael Yang's avatar
Michael Yang committed
4
	"bufio"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
5
	"context"
6
7
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

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

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

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

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

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

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

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

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

92
93
94
95
96
97
98
		return nil
	}

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

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

106
107
108
	return nil
}

109
func RunHandler(cmd *cobra.Command, args []string) error {
110
	client, err := api.FromEnv()
111
112
113
114
	if err != nil {
		return err
	}

115
	models, err := client.List(context.Background())
Patrick Devine's avatar
Patrick Devine committed
116
117
118
119
	if err != nil {
		return err
	}

120
121
122
123
	modelName, modelTag, ok := strings.Cut(args[0], ":")
	if !ok {
		modelTag = "latest"
	}
Michael Yang's avatar
Michael Yang committed
124

125
126
127
	for _, model := range models.Models {
		if model.Name == strings.Join([]string{modelName, modelTag}, ":") {
			return RunGenerate(cmd, args)
Michael Yang's avatar
Michael Yang committed
128
		}
129
130
131
	}

	if err := PullHandler(cmd, args); err != nil {
Michael Yang's avatar
Michael Yang committed
132
133
134
135
		return err
	}

	return RunGenerate(cmd, args)
Bruce MacDonald's avatar
Bruce MacDonald committed
136
137
}

138
func PushHandler(cmd *cobra.Command, args []string) error {
139
140
141
142
	client, err := api.FromEnv()
	if err != nil {
		return err
	}
143

144
145
146
147
148
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

149
150
151
	var currentDigest string
	var bar *progressbar.ProgressBar

152
	request := api.PushRequest{Name: args[0], Insecure: insecure}
153
	fn := func(resp api.ProgressResponse) error {
154
155
156
157
158
159
160
161
162
163
164
165
166
167
		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)
		}
168
169
170
171
172
173
		return nil
	}

	if err := client.Push(context.Background(), &request, fn); err != nil {
		return err
	}
174
175
176
177
178

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

179
180
181
	return nil
}

182
func ListHandler(cmd *cobra.Command, args []string) error {
183
184
185
186
	client, err := api.FromEnv()
	if err != nil {
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
187
188
189
190
191
192
193
194
195

	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
196
		if len(args) == 0 || strings.HasPrefix(m.Name, args[0]) {
Patrick Devine's avatar
Patrick Devine committed
197
			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
198
		}
Patrick Devine's avatar
Patrick Devine committed
199
200
201
	}

	table := tablewriter.NewWriter(os.Stdout)
Patrick Devine's avatar
Patrick Devine committed
202
	table.SetHeader([]string{"NAME", "ID", "SIZE", "MODIFIED"})
Patrick Devine's avatar
Patrick Devine committed
203
204
205
206
207
208
209
210
211
212
213
214
	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
}

215
func DeleteHandler(cmd *cobra.Command, args []string) error {
216
217
218
219
	client, err := api.FromEnv()
	if err != nil {
		return err
	}
220

221
222
223
224
225
226
	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)
227
228
229
230
	}
	return nil
}

Patrick Devine's avatar
Patrick Devine committed
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
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 {
282
		return errors.New("only one of '--license', '--modelfile', '--parameters', '--system', or '--template' can be specified")
Patrick Devine's avatar
Patrick Devine committed
283
	} else if flagsSet == 0 {
284
		return errors.New("one of '--license', '--modelfile', '--parameters', '--system', or '--template' must be specified")
Patrick Devine's avatar
Patrick Devine committed
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
	}

	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
309
func CopyHandler(cmd *cobra.Command, args []string) error {
310
311
312
313
	client, err := api.FromEnv()
	if err != nil {
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
314
315
316
317
318
319
320
321
322

	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
}

323
func PullHandler(cmd *cobra.Command, args []string) error {
324
325
326
327
328
329
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

	return pull(args[0], insecure)
330
331
}

332
func pull(model string, insecure bool) error {
333
334
335
336
	client, err := api.FromEnv()
	if err != nil {
		return err
	}
337

338
	var currentDigest string
Bruce MacDonald's avatar
Bruce MacDonald committed
339
	var bar *progressbar.ProgressBar
Michael Yang's avatar
Michael Yang committed
340

341
	request := api.PullRequest{Name: model, Insecure: insecure}
342
343
344
	fn := func(resp api.ProgressResponse) error {
		if resp.Digest != currentDigest && resp.Digest != "" {
			currentDigest = resp.Digest
345
346
			bar = progressbar.DefaultBytes(
				int64(resp.Total),
347
				fmt.Sprintf("pulling %s...", resp.Digest[7:19]),
348
			)
349
350
351

			bar.Set(resp.Completed)
		} else if resp.Digest == currentDigest && resp.Digest != "" {
352
353
			bar.Set(resp.Completed)
		} else {
354
			currentDigest = ""
355
356
			fmt.Println(resp.Status)
		}
357

358
359
		return nil
	}
360

361
362
363
	if err := client.Pull(context.Background(), &request, fn); err != nil {
		return err
	}
364
365
366
367
368

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

369
	return nil
Michael Yang's avatar
Michael Yang committed
370
371
}

372
func RunGenerate(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
373
	if len(args) > 1 {
Michael Yang's avatar
Michael Yang committed
374
		// join all args into a single prompt
375
		return generate(cmd, args[0], strings.Join(args[1:], " "))
Michael Yang's avatar
Michael Yang committed
376
377
	}

Michael Yang's avatar
Michael Yang committed
378
	if readline.IsTerminal(int(os.Stdin.Fd())) {
379
		return generateInteractive(cmd, args[0])
Michael Yang's avatar
Michael Yang committed
380
381
	}

382
	return generateBatch(cmd, args[0])
Michael Yang's avatar
Michael Yang committed
383
384
}

Michael Yang's avatar
Michael Yang committed
385
type generateContextKey string
Michael Yang's avatar
Michael Yang committed
386

387
func generate(cmd *cobra.Command, model, prompt string) error {
Patrick Devine's avatar
Patrick Devine committed
388
389
390
391
	client, err := api.FromEnv()
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
392

Patrick Devine's avatar
Patrick Devine committed
393
394
	spinner := NewSpinner("")
	go spinner.Spin(60 * time.Millisecond)
Michael Yang's avatar
Michael Yang committed
395

Patrick Devine's avatar
Patrick Devine committed
396
	var latest api.GenerateResponse
397

Patrick Devine's avatar
Patrick Devine committed
398
399
400
401
	generateContext, ok := cmd.Context().Value(generateContextKey("context")).([]int)
	if !ok {
		generateContext = []int{}
	}
Michael Yang's avatar
Michael Yang committed
402

Patrick Devine's avatar
Patrick Devine committed
403
404
405
406
407
	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
408

Patrick Devine's avatar
Patrick Devine committed
409
		latest = response
410

Patrick Devine's avatar
Patrick Devine committed
411
412
413
		fmt.Print(response.Response)
		return nil
	}
414

Patrick Devine's avatar
Patrick Devine committed
415
416
417
418
419
420
421
422
423
424
425
	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)
426
			}
427
		}
Patrick Devine's avatar
Patrick Devine committed
428
429
		return err
	}
Michael Yang's avatar
Michael Yang committed
430

Patrick Devine's avatar
Patrick Devine committed
431
	if prompt != "" {
Michael Yang's avatar
Michael Yang committed
432
433
		fmt.Println()
		fmt.Println()
Patrick Devine's avatar
Patrick Devine committed
434
	}
435

Patrick Devine's avatar
Patrick Devine committed
436
437
438
	if !latest.Done {
		return errors.New("unexpected end of response")
	}
439

Patrick Devine's avatar
Patrick Devine committed
440
441
442
443
	verbose, err := cmd.Flags().GetBool("verbose")
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
444

Patrick Devine's avatar
Patrick Devine committed
445
446
	if verbose {
		latest.Summary()
Michael Yang's avatar
Michael Yang committed
447
	}
Michael Yang's avatar
Michael Yang committed
448

Patrick Devine's avatar
Patrick Devine committed
449
450
451
452
	ctx := cmd.Context()
	ctx = context.WithValue(ctx, generateContextKey("context"), latest.Context)
	cmd.SetContext(ctx)

Michael Yang's avatar
Michael Yang committed
453
454
455
	return nil
}

456
func generateInteractive(cmd *cobra.Command, model string) error {
Michael Yang's avatar
Michael Yang committed
457
458
459
460
461
	home, err := os.UserHomeDir()
	if err != nil {
		return err
	}

Patrick Devine's avatar
Patrick Devine committed
462
463
464
465
466
	// load the model
	if err := generate(cmd, model, ""); err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
467
468
469
470
471
472
	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
473
474
			readline.PcItem("verbose"),
			readline.PcItem("quiet"),
Michael Yang's avatar
Michael Yang committed
475
476
477
478
479
480
			readline.PcItem("mode",
				readline.PcItem("vim"),
				readline.PcItem("emacs"),
				readline.PcItem("default"),
			),
		),
481
482
		readline.PcItem("/show",
			readline.PcItem("license"),
Patrick Devine's avatar
Patrick Devine committed
483
484
			readline.PcItem("modelfile"),
			readline.PcItem("parameters"),
485
486
487
			readline.PcItem("system"),
			readline.PcItem("template"),
		),
Michael Yang's avatar
Michael Yang committed
488
489
490
491
492
493
494
495
496
497
		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
498
		Painter:      Painter{},
Michael Yang's avatar
Michael Yang committed
499
500
501
502
503
504
505
506
507
508
509
		Prompt:       ">>> ",
		HistoryFile:  filepath.Join(home, ".ollama", "history"),
		AutoComplete: completer,
	}

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

510
511
512
	var multiLineBuffer string
	var isMultiLine bool

Michael Yang's avatar
Michael Yang committed
513
514
515
516
517
518
	for {
		line, err := scanner.Readline()
		switch {
		case errors.Is(err, io.EOF):
			return nil
		case errors.Is(err, readline.ErrInterrupt):
519
520
521
522
			if line == "" {
				return nil
			}

Michael Yang's avatar
Michael Yang committed
523
524
			continue
		case err != nil:
Michael Yang's avatar
Michael Yang committed
525
526
527
			return err
		}

Michael Yang's avatar
Michael Yang committed
528
		line = strings.TrimSpace(line)
Michael Yang's avatar
Michael Yang committed
529

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

601
602
				switch args[1] {
				case "license":
Patrick Devine's avatar
Patrick Devine committed
603
604
605
606
607
					fmt.Println(resp.License)
				case "modelfile":
					fmt.Println(resp.Modelfile)
				case "parameters":
					fmt.Println(resp.Parameters)
608
				case "system":
Patrick Devine's avatar
Patrick Devine committed
609
					fmt.Println(resp.System)
610
				case "template":
Patrick Devine's avatar
Patrick Devine committed
611
					fmt.Println(resp.Template)
612
				default:
Patrick Devine's avatar
Patrick Devine committed
613
					fmt.Println("error: unknown command")
Michael Yang's avatar
Michael Yang committed
614
				}
Patrick Devine's avatar
Patrick Devine committed
615
616

				continue
617
618
619
			} else {
				usage()
				continue
Michael Yang's avatar
Michael Yang committed
620
621
622
623
624
625
626
627
			}
		case line == "/help", line == "/?":
			usage()
			continue
		case line == "/exit", line == "/bye":
			return nil
		}

Patrick Devine's avatar
Patrick Devine committed
628
629
630
631
		if len(line) > 0 && line[0] != '/' {
			if err := generate(cmd, model, line); err != nil {
				return err
			}
Michael Yang's avatar
Michael Yang committed
632
633
		}
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
634
635
}

636
func generateBatch(cmd *cobra.Command, model string) error {
Michael Yang's avatar
Michael Yang committed
637
638
639
640
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		prompt := scanner.Text()
		fmt.Printf(">>> %s\n", prompt)
641
		if err := generate(cmd, model, prompt); err != nil {
Michael Yang's avatar
Michael Yang committed
642
643
644
645
646
647
648
			return err
		}
	}

	return nil
}

649
func RunServer(cmd *cobra.Command, _ []string) error {
Michael Yang's avatar
Michael Yang committed
650
651
652
653
654
655
	host, port, err := net.SplitHostPort(os.Getenv("OLLAMA_HOST"))
	if err != nil {
		host, port = "127.0.0.1", "11434"
		if ip := net.ParseIP(os.Getenv("OLLAMA_HOST")); ip != nil {
			host = ip.String()
		}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
656
	}
657

Michael Yang's avatar
Michael Yang committed
658
	if err := initializeKeypair(); err != nil {
659
660
661
		return err
	}

Michael Yang's avatar
Michael Yang committed
662
	ln, err := net.Listen("tcp", net.JoinHostPort(host, port))
663
664
665
	if err != nil {
		return err
	}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
666

667
668
669
670
671
	var origins []string
	if o := os.Getenv("OLLAMA_ORIGINS"); o != "" {
		origins = strings.Split(o, ",")
	}

672
673
674
675
676
677
	if noprune := os.Getenv("OLLAMA_NOPRUNE"); noprune == "" {
		if err := server.PruneLayers(); err != nil {
			return err
		}
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
678
	return server.Serve(ln, origins)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
679
680
}

681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
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
703
		err = os.MkdirAll(filepath.Dir(privKeyPath), 0o755)
704
705
706
707
		if err != nil {
			return fmt.Errorf("could not create directory %w", err)
		}

708
		err = os.WriteFile(privKeyPath, pem.EncodeToMemory(privKeyBytes), 0o600)
709
710
711
712
713
714
715
716
717
718
719
		if err != nil {
			return err
		}

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

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

720
		err = os.WriteFile(pubKeyPath, pubKeyData, 0o644)
721
722
723
724
725
726
727
728
729
		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
730
func startMacApp(client *api.Client) error {
Bruce MacDonald's avatar
Bruce MacDonald committed
731
732
733
734
735
	exe, err := os.Executable()
	if err != nil {
		return err
	}
	link, err := os.Readlink(exe)
Bruce MacDonald's avatar
Bruce MacDonald committed
736
737
738
	if err != nil {
		return err
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
739
740
741
	if !strings.Contains(link, "Ollama.app") {
		return fmt.Errorf("could not find ollama app")
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
	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
			}
		}
	}
}

761
func checkServerHeartbeat(_ *cobra.Command, _ []string) error {
762
763
764
765
	client, err := api.FromEnv()
	if err != nil {
		return err
	}
766
	if err := client.Heartbeat(context.Background()); err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
767
768
769
770
771
		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
772
				return fmt.Errorf("could not connect to ollama app, is it running?")
773
			}
Bruce MacDonald's avatar
Bruce MacDonald committed
774
		} else {
775
776
777
778
779
780
			return fmt.Errorf("could not connect to ollama server, run 'ollama serve' to start it")
		}
	}
	return nil
}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
781
782
783
784
func NewCLI() *cobra.Command {
	log.SetFlags(log.LstdFlags | log.Lshortfile)

	rootCmd := &cobra.Command{
785
786
787
788
		Use:           "ollama",
		Short:         "Large language model runner",
		SilenceUsage:  true,
		SilenceErrors: true,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
789
790
791
		CompletionOptions: cobra.CompletionOptions{
			DisableDefaultCmd: true,
		},
Michael Yang's avatar
Michael Yang committed
792
		Version: version.Version,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
793
794
795
796
	}

	cobra.EnableCommandSorting = false

797
	createCmd := &cobra.Command{
798
799
800
801
802
		Use:     "create MODEL",
		Short:   "Create a model from a Modelfile",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    CreateHandler,
803
804
805
806
	}

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

Patrick Devine's avatar
Patrick Devine committed
807
808
809
810
811
812
813
814
815
816
817
818
819
820
	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
821
	runCmd := &cobra.Command{
822
823
824
825
826
		Use:     "run MODEL [PROMPT]",
		Short:   "Run a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    RunHandler,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
827
828
	}

829
	runCmd.Flags().Bool("verbose", false, "Show timings for response")
830
	runCmd.Flags().Bool("insecure", false, "Use an insecure registry")
831

Jeffrey Morgan's avatar
Jeffrey Morgan committed
832
833
834
835
	serveCmd := &cobra.Command{
		Use:     "serve",
		Aliases: []string{"start"},
		Short:   "Start ollama",
Michael Yang's avatar
Michael Yang committed
836
		RunE:    RunServer,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
837
838
	}

839
	pullCmd := &cobra.Command{
840
841
842
843
844
		Use:     "pull MODEL",
		Short:   "Pull a model from a registry",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    PullHandler,
845
846
	}

847
848
	pullCmd.Flags().Bool("insecure", false, "Use an insecure registry")

849
	pushCmd := &cobra.Command{
850
851
852
853
854
		Use:     "push MODEL",
		Short:   "Push a model to a registry",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    PushHandler,
855
856
	}

857
858
	pushCmd.Flags().Bool("insecure", false, "Use an insecure registry")

Patrick Devine's avatar
Patrick Devine committed
859
	listCmd := &cobra.Command{
860
		Use:     "list",
Patrick Devine's avatar
Patrick Devine committed
861
		Aliases: []string{"ls"},
862
		Short:   "List models",
863
		PreRunE: checkServerHeartbeat,
864
		RunE:    ListHandler,
865
866
	}

Patrick Devine's avatar
Patrick Devine committed
867
	copyCmd := &cobra.Command{
868
869
870
871
872
		Use:     "cp",
		Short:   "Copy a model",
		Args:    cobra.MinimumNArgs(2),
		PreRunE: checkServerHeartbeat,
		RunE:    CopyHandler,
Patrick Devine's avatar
Patrick Devine committed
873
874
	}

875
	deleteCmd := &cobra.Command{
876
877
878
879
880
		Use:     "rm",
		Short:   "Remove a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    DeleteHandler,
Patrick Devine's avatar
Patrick Devine committed
881
882
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
883
884
	rootCmd.AddCommand(
		serveCmd,
885
		createCmd,
Patrick Devine's avatar
Patrick Devine committed
886
		showCmd,
887
		runCmd,
888
889
		pullCmd,
		pushCmd,
Patrick Devine's avatar
Patrick Devine committed
890
		listCmd,
Patrick Devine's avatar
Patrick Devine committed
891
		copyCmd,
892
		deleteCmd,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
893
894
895
896
	)

	return rootCmd
}