cmd.go 17.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
	"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
106
107
108
109
110
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

	mp, err := server.ParseModelPath(args[0], insecure)
	if err != nil {
		return err
	}

Patrick Devine's avatar
Patrick Devine committed
111
112
113
114
115
116
	fp, err := mp.GetManifestPath(false)
	if err != nil {
		return err
	}

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

	return RunGenerate(cmd, args)
Bruce MacDonald's avatar
Bruce MacDonald committed
134
135
}

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

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

147
148
149
	var currentDigest string
	var bar *progressbar.ProgressBar

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

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

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

177
178
179
	return nil
}

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

	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
194
195
196
		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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
	}

	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
}

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

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

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

	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
}

241
func PullHandler(cmd *cobra.Command, args []string) error {
242
243
244
245
246
247
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

	return pull(args[0], insecure)
248
249
}

250
func pull(model string, insecure bool) error {
251
252
253
254
	client, err := api.FromEnv()
	if err != nil {
		return err
	}
255

256
	var currentDigest string
Bruce MacDonald's avatar
Bruce MacDonald committed
257
	var bar *progressbar.ProgressBar
Michael Yang's avatar
Michael Yang committed
258

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

			bar.Set(resp.Completed)
		} else if resp.Digest == currentDigest && resp.Digest != "" {
270
271
			bar.Set(resp.Completed)
		} else {
272
			currentDigest = ""
273
274
			fmt.Println(resp.Status)
		}
275

276
277
		return nil
	}
278

279
280
281
	if err := client.Pull(context.Background(), &request, fn); err != nil {
		return err
	}
282
283
284
285
286

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

287
	return nil
Michael Yang's avatar
Michael Yang committed
288
289
}

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

Michael Yang's avatar
Michael Yang committed
296
	if readline.IsTerminal(int(os.Stdin.Fd())) {
297
		return generateInteractive(cmd, args[0])
Michael Yang's avatar
Michael Yang committed
298
299
	}

300
	return generateBatch(cmd, args[0])
Michael Yang's avatar
Michael Yang committed
301
302
}

Michael Yang's avatar
Michael Yang committed
303
type generateContextKey string
Michael Yang's avatar
Michael Yang committed
304

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

Michael Yang's avatar
Michael Yang committed
312
313
		spinner := NewSpinner("")
		go spinner.Spin(60 * time.Millisecond)
Michael Yang's avatar
Michael Yang committed
314

315
316
		var latest api.GenerateResponse

Michael Yang's avatar
Michael Yang committed
317
		generateContext, ok := cmd.Context().Value(generateContextKey("context")).([]int)
Michael Yang's avatar
Michael Yang committed
318
319
320
321
		if !ok {
			generateContext = []int{}
		}

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

Michael Yang's avatar
Michael Yang committed
328
			latest = response
329

Michael Yang's avatar
Michael Yang committed
330
			fmt.Print(response.Response)
Michael Yang's avatar
Michael Yang committed
331
			return nil
332
333
334
		}

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

Michael Yang's avatar
Michael Yang committed
350
351
		fmt.Println()
		fmt.Println()
352

353
354
355
356
		if !latest.Done {
			return errors.New("unexpected end of response")
		}

357
358
359
360
361
362
363
364
		verbose, err := cmd.Flags().GetBool("verbose")
		if err != nil {
			return err
		}

		if verbose {
			latest.Summary()
		}
Michael Yang's avatar
Michael Yang committed
365
366
367
368

		ctx := cmd.Context()
		ctx = context.WithValue(ctx, generateContextKey("context"), latest.Context)
		cmd.SetContext(ctx)
Michael Yang's avatar
Michael Yang committed
369
	}
Michael Yang's avatar
Michael Yang committed
370
371
372
373

	return nil
}

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

388
func generateInteractive(cmd *cobra.Command, model string) error {
Michael Yang's avatar
Michael Yang committed
389
390
391
392
393
394
395
396
397
398
399
	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
400
401
			readline.PcItem("verbose"),
			readline.PcItem("quiet"),
Michael Yang's avatar
Michael Yang committed
402
403
404
405
406
407
			readline.PcItem("mode",
				readline.PcItem("vim"),
				readline.PcItem("emacs"),
				readline.PcItem("default"),
			),
		),
408
409
410
411
412
		readline.PcItem("/show",
			readline.PcItem("license"),
			readline.PcItem("system"),
			readline.PcItem("template"),
		),
Michael Yang's avatar
Michael Yang committed
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
		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()

434
435
436
	var multiLineBuffer string
	var isMultiLine bool

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

Michael Yang's avatar
Michael Yang committed
447
448
			continue
		case err != nil:
Michael Yang's avatar
Michael Yang committed
449
450
451
			return err
		}

Michael Yang's avatar
Michael Yang committed
452
		line = strings.TrimSpace(line)
Michael Yang's avatar
Michael Yang committed
453

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

524
525
				manifest, err := server.GetManifest(mp)
				if err != nil {
526
					fmt.Println("error: couldn't get a manifest for this model")
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
					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
548
549
						}
					}
550
551
552
553
					continue
				default:
					usage()
					continue
Michael Yang's avatar
Michael Yang committed
554
				}
555
556
557
			} else {
				usage()
				continue
Michael Yang's avatar
Michael Yang committed
558
559
560
561
562
563
564
565
566
567
568
569
			}
		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
570
571
}

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

	return nil
}

585
func RunServer(cmd *cobra.Command, _ []string) error {
586
	host, port := "127.0.0.1", "11434"
587
588
589
590

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

593
594
595
596
597
598
599
	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
600
	}
601

602
603
604
605
606
	err := initializeKeypair()
	if err != nil {
		return err
	}

607
	ln, err := net.Listen("tcp", fmt.Sprintf("%s:%s", host, port))
608
609
610
	if err != nil {
		return err
	}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
611

612
613
614
615
616
	var origins []string
	if o := os.Getenv("OLLAMA_ORIGINS"); o != "" {
		origins = strings.Split(o, ",")
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
617
	return server.Serve(ln, origins)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
618
619
}

620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
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
		}

642
643
644
645
646
		err = os.MkdirAll(path.Dir(privKeyPath), 0o700)
		if err != nil {
			return fmt.Errorf("could not create directory %w", err)
		}

647
		err = os.WriteFile(privKeyPath, pem.EncodeToMemory(privKeyBytes), 0o600)
648
649
650
651
652
653
654
655
656
657
658
		if err != nil {
			return err
		}

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

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

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

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

Jeffrey Morgan's avatar
Jeffrey Morgan committed
720
721
722
723
func NewCLI() *cobra.Command {
	log.SetFlags(log.LstdFlags | log.Lshortfile)

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

	cobra.EnableCommandSorting = false

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

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

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

754
	runCmd.Flags().Bool("verbose", false, "Show timings for response")
755
	runCmd.Flags().Bool("insecure", false, "Use an insecure registry")
756

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

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

772
773
	pullCmd.Flags().Bool("insecure", false, "Use an insecure registry")

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

782
783
	pushCmd.Flags().Bool("insecure", false, "Use an insecure registry")

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

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

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

Jeffrey Morgan's avatar
Jeffrey Morgan committed
808
809
	rootCmd.AddCommand(
		serveCmd,
810
		createCmd,
811
		runCmd,
812
813
		pullCmd,
		pushCmd,
Patrick Devine's avatar
Patrick Devine committed
814
		listCmd,
Patrick Devine's avatar
Patrick Devine committed
815
		copyCmd,
816
		deleteCmd,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
817
818
819
820
	)

	return rootCmd
}