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

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

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

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

	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
}

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

	return pull(args[0], insecure)
254
255
}

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

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

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

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

282
283
		return nil
	}
284

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

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

293
	return nil
Michael Yang's avatar
Michael Yang committed
294
295
}

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

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

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

Michael Yang's avatar
Michael Yang committed
309
type generateContextKey string
Michael Yang's avatar
Michael Yang committed
310

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

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

321
322
		var latest api.GenerateResponse

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

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

Michael Yang's avatar
Michael Yang committed
334
			latest = response
335

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

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

Michael Yang's avatar
Michael Yang committed
356
357
		fmt.Println()
		fmt.Println()
358

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

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

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

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

	return nil
}

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

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

440
441
442
	var multiLineBuffer string
	var isMultiLine bool

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

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

Michael Yang's avatar
Michael Yang committed
458
		line = strings.TrimSpace(line)
Michael Yang's avatar
Michael Yang committed
459

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

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

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

	return nil
}

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

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

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

608
609
610
611
612
	err := initializeKeypair()
	if err != nil {
		return err
	}

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

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

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

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

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

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

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

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

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

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

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

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

	cobra.EnableCommandSorting = false

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

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

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

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

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

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

778
779
	pullCmd.Flags().Bool("insecure", false, "Use an insecure registry")

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

788
789
	pushCmd.Flags().Bool("insecure", false, "Use an insecure registry")

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

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

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

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

	return rootCmd
}