cmd.go 17.6 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
)

36
func CreateHandler(cmd *cobra.Command, args []string) error {
37
	filename, _ := cmd.Flags().GetString("file")
38
39
40
41
42
	filename, err := filepath.Abs(filename)
	if err != nil {
		return err
	}

43
44
45
46
	client, err := api.FromEnv()
	if err != nil {
		return err
	}
47

Michael Yang's avatar
Michael Yang committed
48
49
	var spinner *Spinner

50
51
	var currentDigest string
	var bar *progressbar.ProgressBar
Michael Yang's avatar
Michael Yang committed
52

53
54
55
56
57
58
59
	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
60
61
62
63
64
65
66
67
68
69
70
71
			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)
			}
72
73
74
75
76
77
78
79
80
81
		} 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)
		}
82

83
84
85
86
87
88
89
		return nil
	}

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

Michael Yang's avatar
Michael Yang committed
90
91
	if spinner != nil {
		spinner.Stop()
92
93
94
		if spinner.description != "success" {
			return errors.New("unexpected end to create model")
		}
Michael Yang's avatar
Michael Yang committed
95
96
	}

97
98
99
	return nil
}

100
func RunHandler(cmd *cobra.Command, args []string) error {
101
102
103
104
105
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

106
	mp := server.ParseModelPath(args[0])
107
108
109
110
	if err != nil {
		return err
	}

111
112
113
114
	if mp.ProtocolScheme == "http" && !insecure {
		return fmt.Errorf("insecure protocol http")
	}

Patrick Devine's avatar
Patrick Devine committed
115
116
117
118
119
120
	fp, err := mp.GetManifestPath(false)
	if err != nil {
		return err
	}

	_, err = os.Stat(fp)
Michael Yang's avatar
Michael Yang committed
121
122
	switch {
	case errors.Is(err, os.ErrNotExist):
123
		if err := pull(args[0], insecure); err != nil {
Michael Yang's avatar
Michael Yang committed
124
125
126
127
128
129
130
131
			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
132
133
		}
	case err != nil:
Michael Yang's avatar
Michael Yang committed
134
135
136
137
		return err
	}

	return RunGenerate(cmd, args)
Bruce MacDonald's avatar
Bruce MacDonald committed
138
139
}

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

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

151
152
153
	var currentDigest string
	var bar *progressbar.ProgressBar

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

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

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

181
182
183
	return nil
}

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

	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
198
199
200
		if len(args) == 0 || strings.HasPrefix(m.Name, args[0]) {
			data = append(data, []string{m.Name, humanize.Bytes(uint64(m.Size)), format.HumanTime(m.ModifiedAt, "Never")})
		}
Patrick Devine's avatar
Patrick Devine committed
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
	}

	table := tablewriter.NewWriter(os.Stdout)
	table.SetHeader([]string{"NAME", "SIZE", "MODIFIED"})
	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
}

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

Patrick Devine's avatar
Patrick Devine committed
223
224
	req := api.DeleteRequest{Name: args[0]}
	if err := client.Delete(context.Background(), &req); err != nil {
225
226
		return err
	}
227
	fmt.Printf("deleted '%s'\n", args[0])
228
229
230
	return nil
}

Patrick Devine's avatar
Patrick Devine committed
231
func CopyHandler(cmd *cobra.Command, args []string) error {
232
233
234
235
	client, err := api.FromEnv()
	if err != nil {
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
236
237
238
239
240
241
242
243
244

	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
}

245
func PullHandler(cmd *cobra.Command, args []string) error {
246
247
248
249
250
251
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

	return pull(args[0], insecure)
252
253
}

254
func pull(model string, insecure bool) error {
255
256
257
258
	client, err := api.FromEnv()
	if err != nil {
		return err
	}
259

260
	var currentDigest string
Bruce MacDonald's avatar
Bruce MacDonald committed
261
	var bar *progressbar.ProgressBar
Michael Yang's avatar
Michael Yang committed
262

263
	request := api.PullRequest{Name: model, Insecure: insecure}
264
265
266
	fn := func(resp api.ProgressResponse) error {
		if resp.Digest != currentDigest && resp.Digest != "" {
			currentDigest = resp.Digest
267
268
			bar = progressbar.DefaultBytes(
				int64(resp.Total),
269
				fmt.Sprintf("pulling %s...", resp.Digest[7:19]),
270
			)
271
272
273

			bar.Set(resp.Completed)
		} else if resp.Digest == currentDigest && resp.Digest != "" {
274
275
			bar.Set(resp.Completed)
		} else {
276
			currentDigest = ""
277
278
			fmt.Println(resp.Status)
		}
279

280
281
		return nil
	}
282

283
284
285
	if err := client.Pull(context.Background(), &request, fn); err != nil {
		return err
	}
286
287
288
289
290

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

291
	return nil
Michael Yang's avatar
Michael Yang committed
292
293
}

294
func RunGenerate(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
295
	if len(args) > 1 {
Michael Yang's avatar
Michael Yang committed
296
		// join all args into a single prompt
297
		return generate(cmd, args[0], strings.Join(args[1:], " "))
Michael Yang's avatar
Michael Yang committed
298
299
	}

Michael Yang's avatar
Michael Yang committed
300
	if readline.IsTerminal(int(os.Stdin.Fd())) {
301
		return generateInteractive(cmd, args[0])
Michael Yang's avatar
Michael Yang committed
302
303
	}

304
	return generateBatch(cmd, args[0])
Michael Yang's avatar
Michael Yang committed
305
306
}

Michael Yang's avatar
Michael Yang committed
307
type generateContextKey string
Michael Yang's avatar
Michael Yang committed
308

309
func generate(cmd *cobra.Command, model, prompt string) error {
Michael Yang's avatar
Michael Yang committed
310
	if len(strings.TrimSpace(prompt)) > 0 {
311
312
313
314
		client, err := api.FromEnv()
		if err != nil {
			return err
		}
Michael Yang's avatar
Michael Yang committed
315

Michael Yang's avatar
Michael Yang committed
316
317
		spinner := NewSpinner("")
		go spinner.Spin(60 * time.Millisecond)
Michael Yang's avatar
Michael Yang committed
318

319
320
		var latest api.GenerateResponse

Michael Yang's avatar
Michael Yang committed
321
		generateContext, ok := cmd.Context().Value(generateContextKey("context")).([]int)
Michael Yang's avatar
Michael Yang committed
322
323
324
325
		if !ok {
			generateContext = []int{}
		}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
326
		request := api.GenerateRequest{Model: model, Prompt: prompt, Context: generateContext}
Michael Yang's avatar
Michael Yang committed
327
		fn := func(response api.GenerateResponse) error {
Michael Yang's avatar
Michael Yang committed
328
329
330
			if !spinner.IsFinished() {
				spinner.Finish()
			}
Michael Yang's avatar
Michael Yang committed
331

Michael Yang's avatar
Michael Yang committed
332
			latest = response
333

Michael Yang's avatar
Michael Yang committed
334
			fmt.Print(response.Response)
Michael Yang's avatar
Michael Yang committed
335
			return nil
336
337
338
		}

		if err := client.Generate(context.Background(), &request, fn); err != nil {
339
340
341
342
343
344
345
346
347
348
349
350
			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)
				}
			}
351
352
			return err
		}
Michael Yang's avatar
Michael Yang committed
353

Michael Yang's avatar
Michael Yang committed
354
355
		fmt.Println()
		fmt.Println()
356

357
358
359
360
		if !latest.Done {
			return errors.New("unexpected end of response")
		}

361
362
363
364
365
366
367
368
		verbose, err := cmd.Flags().GetBool("verbose")
		if err != nil {
			return err
		}

		if verbose {
			latest.Summary()
		}
Michael Yang's avatar
Michael Yang committed
369
370
371
372

		ctx := cmd.Context()
		ctx = context.WithValue(ctx, generateContextKey("context"), latest.Context)
		cmd.SetContext(ctx)
Michael Yang's avatar
Michael Yang committed
373
	}
Michael Yang's avatar
Michael Yang committed
374
375
376
377

	return nil
}

378
379
func showLayer(l *server.Layer) {
	filename, err := server.GetBlobsPath(l.Digest)
380
381
382
383
	if err != nil {
		fmt.Println("Couldn't get layer's path")
		return
	}
384
385
	bts, err := os.ReadFile(filename)
	if err != nil {
386
		fmt.Println("Couldn't read layer")
387
388
		return
	}
389
	fmt.Println(string(bts))
390
391
}

392
func generateInteractive(cmd *cobra.Command, model string) error {
Michael Yang's avatar
Michael Yang committed
393
394
395
396
397
398
399
400
401
402
403
	home, err := os.UserHomeDir()
	if err != nil {
		return err
	}

	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
404
405
			readline.PcItem("verbose"),
			readline.PcItem("quiet"),
Michael Yang's avatar
Michael Yang committed
406
407
408
409
410
411
			readline.PcItem("mode",
				readline.PcItem("vim"),
				readline.PcItem("emacs"),
				readline.PcItem("default"),
			),
		),
412
413
414
415
416
		readline.PcItem("/show",
			readline.PcItem("license"),
			readline.PcItem("system"),
			readline.PcItem("template"),
		),
Michael Yang's avatar
Michael Yang committed
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
		readline.PcItem("/exit"),
		readline.PcItem("/bye"),
	)

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

	config := readline.Config{
		Prompt:       ">>> ",
		HistoryFile:  filepath.Join(home, ".ollama", "history"),
		AutoComplete: completer,
	}

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

438
439
440
	var multiLineBuffer string
	var isMultiLine bool

Michael Yang's avatar
Michael Yang committed
441
442
443
444
445
446
	for {
		line, err := scanner.Readline()
		switch {
		case errors.Is(err, io.EOF):
			return nil
		case errors.Is(err, readline.ErrInterrupt):
447
448
449
450
			if line == "" {
				return nil
			}

Michael Yang's avatar
Michael Yang committed
451
452
			continue
		case err != nil:
Michael Yang's avatar
Michael Yang committed
453
454
455
			return err
		}

Michael Yang's avatar
Michael Yang committed
456
		line = strings.TrimSpace(line)
Michael Yang's avatar
Michael Yang committed
457

Michael Yang's avatar
Michael Yang committed
458
		switch {
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
		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
475
476
		case strings.HasPrefix(line, "/list"):
			args := strings.Fields(line)
477
			if err := ListHandler(cmd, args[1:]); err != nil {
Michael Yang's avatar
Michael Yang committed
478
479
480
481
482
483
484
485
486
487
488
489
490
491
				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
492
493
494
495
496
497
				case "verbose":
					cmd.Flags().Set("verbose", "true")
					continue
				case "quiet":
					cmd.Flags().Set("verbose", "false")
					continue
Michael Yang's avatar
Michael Yang committed
498
499
500
501
502
503
504
505
506
				case "mode":
					if len(args) > 2 {
						switch args[2] {
						case "vim":
							scanner.SetVimMode(true)
							continue
						case "emacs", "default":
							scanner.SetVimMode(false)
							continue
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
						default:
							usage()
							continue
						}
					} else {
						usage()
						continue
					}
				}
			} else {
				usage()
				continue
			}
		case strings.HasPrefix(line, "/show"):
			args := strings.Fields(line)
			if len(args) > 1 {
523
				mp := server.ParseModelPath(model)
524
525
526
527
				if err != nil {
					return err
				}

528
529
				manifest, err := server.GetManifest(mp)
				if err != nil {
530
					fmt.Println("error: couldn't get a manifest for this model")
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
					continue
				}
				switch args[1] {
				case "license":
					for _, l := range manifest.Layers {
						if l.MediaType == "application/vnd.ollama.image.license" {
							showLayer(l)
						}
					}
					continue
				case "system":
					for _, l := range manifest.Layers {
						if l.MediaType == "application/vnd.ollama.image.system" {
							showLayer(l)
						}
					}
					continue
				case "template":
					for _, l := range manifest.Layers {
						if l.MediaType == "application/vnd.ollama.image.template" {
							showLayer(l)
Michael Yang's avatar
Michael Yang committed
552
553
						}
					}
554
555
556
557
					continue
				default:
					usage()
					continue
Michael Yang's avatar
Michael Yang committed
558
				}
559
560
561
			} else {
				usage()
				continue
Michael Yang's avatar
Michael Yang committed
562
563
564
565
566
567
568
569
570
571
572
573
			}
		case line == "/help", line == "/?":
			usage()
			continue
		case line == "/exit", line == "/bye":
			return nil
		}

		if err := generate(cmd, model, line); err != nil {
			return err
		}
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
574
575
}

576
func generateBatch(cmd *cobra.Command, model string) error {
Michael Yang's avatar
Michael Yang committed
577
578
579
580
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		prompt := scanner.Text()
		fmt.Printf(">>> %s\n", prompt)
581
		if err := generate(cmd, model, prompt); err != nil {
Michael Yang's avatar
Michael Yang committed
582
583
584
585
586
587
588
			return err
		}
	}

	return nil
}

589
func RunServer(cmd *cobra.Command, _ []string) error {
590
	host, port := "127.0.0.1", "11434"
591
592
593
594

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

597
598
599
600
601
602
603
	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
604
	}
605

606
607
608
609
610
	err := initializeKeypair()
	if err != nil {
		return err
	}

611
	ln, err := net.Listen("tcp", fmt.Sprintf("%s:%s", host, port))
612
613
614
	if err != nil {
		return err
	}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
615

616
617
618
619
620
	var origins []string
	if o := os.Getenv("OLLAMA_ORIGINS"); o != "" {
		origins = strings.Split(o, ",")
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
621
	return server.Serve(ln, origins)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
622
623
}

624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
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
		}

646
647
648
649
650
		err = os.MkdirAll(path.Dir(privKeyPath), 0o700)
		if err != nil {
			return fmt.Errorf("could not create directory %w", err)
		}

651
		err = os.WriteFile(privKeyPath, pem.EncodeToMemory(privKeyBytes), 0o600)
652
653
654
655
656
657
658
659
660
661
662
		if err != nil {
			return err
		}

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

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

663
		err = os.WriteFile(pubKeyPath, pubKeyData, 0o644)
664
665
666
667
668
669
670
671
672
		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
673
func startMacApp(client *api.Client) error {
Bruce MacDonald's avatar
Bruce MacDonald committed
674
675
676
677
678
	exe, err := os.Executable()
	if err != nil {
		return err
	}
	link, err := os.Readlink(exe)
Bruce MacDonald's avatar
Bruce MacDonald committed
679
680
681
	if err != nil {
		return err
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
682
683
684
	if !strings.Contains(link, "Ollama.app") {
		return fmt.Errorf("could not find ollama app")
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
	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
			}
		}
	}
}

704
func checkServerHeartbeat(_ *cobra.Command, _ []string) error {
705
706
707
708
	client, err := api.FromEnv()
	if err != nil {
		return err
	}
709
	if err := client.Heartbeat(context.Background()); err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
710
711
712
713
714
		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
715
				return fmt.Errorf("could not connect to ollama app, is it running?")
716
			}
Bruce MacDonald's avatar
Bruce MacDonald committed
717
		} else {
718
719
720
721
722
723
			return fmt.Errorf("could not connect to ollama server, run 'ollama serve' to start it")
		}
	}
	return nil
}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
724
725
726
727
func NewCLI() *cobra.Command {
	log.SetFlags(log.LstdFlags | log.Lshortfile)

	rootCmd := &cobra.Command{
728
729
730
731
		Use:           "ollama",
		Short:         "Large language model runner",
		SilenceUsage:  true,
		SilenceErrors: true,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
732
733
734
		CompletionOptions: cobra.CompletionOptions{
			DisableDefaultCmd: true,
		},
Michael Yang's avatar
Michael Yang committed
735
		Version: version.Version,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
736
737
738
739
	}

	cobra.EnableCommandSorting = false

740
	createCmd := &cobra.Command{
741
742
743
744
745
		Use:     "create MODEL",
		Short:   "Create a model from a Modelfile",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    CreateHandler,
746
747
748
749
	}

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

Jeffrey Morgan's avatar
Jeffrey Morgan committed
750
	runCmd := &cobra.Command{
751
752
753
754
755
		Use:     "run MODEL [PROMPT]",
		Short:   "Run a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    RunHandler,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
756
757
	}

758
	runCmd.Flags().Bool("verbose", false, "Show timings for response")
759
	runCmd.Flags().Bool("insecure", false, "Use an insecure registry")
760

Jeffrey Morgan's avatar
Jeffrey Morgan committed
761
762
763
764
	serveCmd := &cobra.Command{
		Use:     "serve",
		Aliases: []string{"start"},
		Short:   "Start ollama",
Michael Yang's avatar
Michael Yang committed
765
		RunE:    RunServer,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
766
767
	}

768
	pullCmd := &cobra.Command{
769
770
771
772
773
		Use:     "pull MODEL",
		Short:   "Pull a model from a registry",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    PullHandler,
774
775
	}

776
777
	pullCmd.Flags().Bool("insecure", false, "Use an insecure registry")

778
	pushCmd := &cobra.Command{
779
780
781
782
783
		Use:     "push MODEL",
		Short:   "Push a model to a registry",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    PushHandler,
784
785
	}

786
787
	pushCmd.Flags().Bool("insecure", false, "Use an insecure registry")

Patrick Devine's avatar
Patrick Devine committed
788
	listCmd := &cobra.Command{
789
		Use:     "list",
Patrick Devine's avatar
Patrick Devine committed
790
		Aliases: []string{"ls"},
791
		Short:   "List models",
792
		PreRunE: checkServerHeartbeat,
793
		RunE:    ListHandler,
794
795
	}

Patrick Devine's avatar
Patrick Devine committed
796
	copyCmd := &cobra.Command{
797
798
799
800
801
		Use:     "cp",
		Short:   "Copy a model",
		Args:    cobra.MinimumNArgs(2),
		PreRunE: checkServerHeartbeat,
		RunE:    CopyHandler,
Patrick Devine's avatar
Patrick Devine committed
802
803
	}

804
	deleteCmd := &cobra.Command{
805
806
807
808
809
		Use:     "rm",
		Short:   "Remove a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    DeleteHandler,
Patrick Devine's avatar
Patrick Devine committed
810
811
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
812
813
	rootCmd.AddCommand(
		serveCmd,
814
		createCmd,
815
		runCmd,
816
817
		pullCmd,
		pushCmd,
Patrick Devine's avatar
Patrick Devine committed
818
		listCmd,
Patrick Devine's avatar
Patrick Devine committed
819
		copyCmd,
820
		deleteCmd,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
821
822
823
824
	)

	return rootCmd
}