cmd.go 16.9 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"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
33
34
)

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

42
43
	client := api.NewClient()

Michael Yang's avatar
Michael Yang committed
44
45
	var spinner *Spinner

46
47
	var currentDigest string
	var bar *progressbar.ProgressBar
Michael Yang's avatar
Michael Yang committed
48

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

79
80
81
82
83
84
85
		return nil
	}

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

Michael Yang's avatar
Michael Yang committed
86
87
	if spinner != nil {
		spinner.Stop()
88
89
90
		if spinner.description != "success" {
			return errors.New("unexpected end to create model")
		}
Michael Yang's avatar
Michael Yang committed
91
92
	}

93
94
95
	return nil
}

96
97
func RunHandler(cmd *cobra.Command, args []string) error {
	mp := server.ParseModelPath(args[0])
Patrick Devine's avatar
Patrick Devine committed
98
99
100
101
102
103
	fp, err := mp.GetManifestPath(false)
	if err != nil {
		return err
	}

	_, err = os.Stat(fp)
Michael Yang's avatar
Michael Yang committed
104
105
	switch {
	case errors.Is(err, os.ErrNotExist):
106
		if err := pull(args[0], false); err != nil {
Michael Yang's avatar
Michael Yang committed
107
108
109
110
111
112
113
114
			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
115
116
		}
	case err != nil:
Michael Yang's avatar
Michael Yang committed
117
118
119
120
		return err
	}

	return RunGenerate(cmd, args)
Bruce MacDonald's avatar
Bruce MacDonald committed
121
122
}

123
func PushHandler(cmd *cobra.Command, args []string) error {
124
125
	client := api.NewClient()

126
127
128
129
130
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

131
132
133
	var currentDigest string
	var bar *progressbar.ProgressBar

134
	request := api.PushRequest{Name: args[0], Insecure: insecure}
135
	fn := func(resp api.ProgressResponse) error {
136
137
138
139
140
141
142
143
144
145
146
147
148
149
		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)
		}
150
151
152
153
154
155
		return nil
	}

	if err := client.Push(context.Background(), &request, fn); err != nil {
		return err
	}
156
157
158
159
160

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

161
162
163
	return nil
}

164
func ListHandler(cmd *cobra.Command, args []string) error {
Patrick Devine's avatar
Patrick Devine committed
165
166
167
168
169
170
171
172
173
174
	client := api.NewClient()

	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
175
176
177
		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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
	}

	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
}

194
195
196
func DeleteHandler(cmd *cobra.Command, args []string) error {
	client := api.NewClient()

Patrick Devine's avatar
Patrick Devine committed
197
198
	req := api.DeleteRequest{Name: args[0]}
	if err := client.Delete(context.Background(), &req); err != nil {
199
200
		return err
	}
201
	fmt.Printf("deleted '%s'\n", args[0])
202
203
204
	return nil
}

Patrick Devine's avatar
Patrick Devine committed
205
206
207
208
209
210
211
212
213
214
215
func CopyHandler(cmd *cobra.Command, args []string) error {
	client := api.NewClient()

	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
}

216
func PullHandler(cmd *cobra.Command, args []string) error {
217
218
219
220
221
222
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

	return pull(args[0], insecure)
223
224
}

225
func pull(model string, insecure bool) error {
Bruce MacDonald's avatar
Bruce MacDonald committed
226
	client := api.NewClient()
227

228
	var currentDigest string
Bruce MacDonald's avatar
Bruce MacDonald committed
229
	var bar *progressbar.ProgressBar
Michael Yang's avatar
Michael Yang committed
230

231
	request := api.PullRequest{Name: model, Insecure: insecure}
232
233
234
	fn := func(resp api.ProgressResponse) error {
		if resp.Digest != currentDigest && resp.Digest != "" {
			currentDigest = resp.Digest
235
236
			bar = progressbar.DefaultBytes(
				int64(resp.Total),
237
				fmt.Sprintf("pulling %s...", resp.Digest[7:19]),
238
			)
239
240
241

			bar.Set(resp.Completed)
		} else if resp.Digest == currentDigest && resp.Digest != "" {
242
243
			bar.Set(resp.Completed)
		} else {
244
			currentDigest = ""
245
246
			fmt.Println(resp.Status)
		}
247

248
249
		return nil
	}
250

251
252
253
	if err := client.Pull(context.Background(), &request, fn); err != nil {
		return err
	}
254
255
256
257
258

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

259
	return nil
Michael Yang's avatar
Michael Yang committed
260
261
}

262
func RunGenerate(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
263
	if len(args) > 1 {
Michael Yang's avatar
Michael Yang committed
264
		// join all args into a single prompt
265
		return generate(cmd, args[0], strings.Join(args[1:], " "))
Michael Yang's avatar
Michael Yang committed
266
267
	}

Michael Yang's avatar
Michael Yang committed
268
	if readline.IsTerminal(int(os.Stdin.Fd())) {
269
		return generateInteractive(cmd, args[0])
Michael Yang's avatar
Michael Yang committed
270
271
	}

272
	return generateBatch(cmd, args[0])
Michael Yang's avatar
Michael Yang committed
273
274
}

Michael Yang's avatar
Michael Yang committed
275
type generateContextKey string
Michael Yang's avatar
Michael Yang committed
276

277
func generate(cmd *cobra.Command, model, prompt string) error {
Michael Yang's avatar
Michael Yang committed
278
279
280
	if len(strings.TrimSpace(prompt)) > 0 {
		client := api.NewClient()

Michael Yang's avatar
Michael Yang committed
281
282
		spinner := NewSpinner("")
		go spinner.Spin(60 * time.Millisecond)
Michael Yang's avatar
Michael Yang committed
283

284
285
		var latest api.GenerateResponse

Michael Yang's avatar
Michael Yang committed
286
		generateContext, ok := cmd.Context().Value(generateContextKey("context")).([]int)
Michael Yang's avatar
Michael Yang committed
287
288
289
290
		if !ok {
			generateContext = []int{}
		}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
291
		request := api.GenerateRequest{Model: model, Prompt: prompt, Context: generateContext}
Michael Yang's avatar
Michael Yang committed
292
		fn := func(response api.GenerateResponse) error {
Michael Yang's avatar
Michael Yang committed
293
294
295
			if !spinner.IsFinished() {
				spinner.Finish()
			}
Michael Yang's avatar
Michael Yang committed
296

Michael Yang's avatar
Michael Yang committed
297
			latest = response
298

Michael Yang's avatar
Michael Yang committed
299
			fmt.Print(response.Response)
Michael Yang's avatar
Michael Yang committed
300
			return nil
301
302
303
		}

		if err := client.Generate(context.Background(), &request, fn); err != nil {
304
305
306
307
308
309
310
311
312
313
314
315
			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)
				}
			}
316
317
			return err
		}
Michael Yang's avatar
Michael Yang committed
318

Michael Yang's avatar
Michael Yang committed
319
320
		fmt.Println()
		fmt.Println()
321

322
323
324
325
		if !latest.Done {
			return errors.New("unexpected end of response")
		}

326
327
328
329
330
331
332
333
		verbose, err := cmd.Flags().GetBool("verbose")
		if err != nil {
			return err
		}

		if verbose {
			latest.Summary()
		}
Michael Yang's avatar
Michael Yang committed
334
335
336
337

		ctx := cmd.Context()
		ctx = context.WithValue(ctx, generateContextKey("context"), latest.Context)
		cmd.SetContext(ctx)
Michael Yang's avatar
Michael Yang committed
338
	}
Michael Yang's avatar
Michael Yang committed
339
340
341
342

	return nil
}

343
344
func showLayer(l *server.Layer) {
	filename, err := server.GetBlobsPath(l.Digest)
345
346
347
348
	if err != nil {
		fmt.Println("Couldn't get layer's path")
		return
	}
349
350
	bts, err := os.ReadFile(filename)
	if err != nil {
351
		fmt.Println("Couldn't read layer")
352
353
		return
	}
354
	fmt.Println(string(bts))
355
356
}

357
func generateInteractive(cmd *cobra.Command, model string) error {
Michael Yang's avatar
Michael Yang committed
358
359
360
361
362
363
364
365
366
367
368
	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
369
370
			readline.PcItem("verbose"),
			readline.PcItem("quiet"),
Michael Yang's avatar
Michael Yang committed
371
372
373
374
375
376
			readline.PcItem("mode",
				readline.PcItem("vim"),
				readline.PcItem("emacs"),
				readline.PcItem("default"),
			),
		),
377
378
379
380
381
		readline.PcItem("/show",
			readline.PcItem("license"),
			readline.PcItem("system"),
			readline.PcItem("template"),
		),
Michael Yang's avatar
Michael Yang committed
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
		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()

403
404
405
	var multiLineBuffer string
	var isMultiLine bool

Michael Yang's avatar
Michael Yang committed
406
407
408
409
410
411
	for {
		line, err := scanner.Readline()
		switch {
		case errors.Is(err, io.EOF):
			return nil
		case errors.Is(err, readline.ErrInterrupt):
412
413
414
415
			if line == "" {
				return nil
			}

Michael Yang's avatar
Michael Yang committed
416
417
			continue
		case err != nil:
Michael Yang's avatar
Michael Yang committed
418
419
420
			return err
		}

Michael Yang's avatar
Michael Yang committed
421
		line = strings.TrimSpace(line)
Michael Yang's avatar
Michael Yang committed
422

Michael Yang's avatar
Michael Yang committed
423
		switch {
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
		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
440
441
		case strings.HasPrefix(line, "/list"):
			args := strings.Fields(line)
442
			if err := ListHandler(cmd, args[1:]); err != nil {
Michael Yang's avatar
Michael Yang committed
443
444
445
446
447
448
449
450
451
452
453
454
455
456
				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
457
458
459
460
461
462
				case "verbose":
					cmd.Flags().Set("verbose", "true")
					continue
				case "quiet":
					cmd.Flags().Set("verbose", "false")
					continue
Michael Yang's avatar
Michael Yang committed
463
464
465
466
467
468
469
470
471
				case "mode":
					if len(args) > 2 {
						switch args[2] {
						case "vim":
							scanner.SetVimMode(true)
							continue
						case "emacs", "default":
							scanner.SetVimMode(false)
							continue
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
						default:
							usage()
							continue
						}
					} else {
						usage()
						continue
					}
				}
			} else {
				usage()
				continue
			}
		case strings.HasPrefix(line, "/show"):
			args := strings.Fields(line)
			if len(args) > 1 {
				mp := server.ParseModelPath(model)
				manifest, err := server.GetManifest(mp)
				if err != nil {
491
					fmt.Println("error: couldn't get a manifest for this model")
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
					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
513
514
						}
					}
515
516
517
518
					continue
				default:
					usage()
					continue
Michael Yang's avatar
Michael Yang committed
519
				}
520
521
522
			} else {
				usage()
				continue
Michael Yang's avatar
Michael Yang committed
523
524
525
526
527
528
529
530
531
532
533
534
			}
		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
535
536
}

537
func generateBatch(cmd *cobra.Command, model string) error {
Michael Yang's avatar
Michael Yang committed
538
539
540
541
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		prompt := scanner.Text()
		fmt.Printf(">>> %s\n", prompt)
542
		if err := generate(cmd, model, prompt); err != nil {
Michael Yang's avatar
Michael Yang committed
543
544
545
546
547
548
549
			return err
		}
	}

	return nil
}

550
551
552
553
554
555
func RunServer(cmd *cobra.Command, _ []string) error {
	var host, port = "127.0.0.1", "11434"

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

558
559
560
561
562
563
564
	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
565
	}
566

567
568
569
570
571
	err := initializeKeypair()
	if err != nil {
		return err
	}

572
	ln, err := net.Listen("tcp", fmt.Sprintf("%s:%s", host, port))
573
574
575
	if err != nil {
		return err
	}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
576

577
578
579
580
581
	var origins []string
	if o := os.Getenv("OLLAMA_ORIGINS"); o != "" {
		origins = strings.Split(o, ",")
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
582
	return server.Serve(ln, origins)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
583
584
}

585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
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
		}

607
608
609
610
611
612
		err = os.MkdirAll(path.Dir(privKeyPath), 0o700)
		if err != nil {
			return fmt.Errorf("could not create directory %w", err)
		}

		err = os.WriteFile(privKeyPath, pem.EncodeToMemory(privKeyBytes), 0600)
613
614
615
616
617
618
619
620
621
622
623
		if err != nil {
			return err
		}

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

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

624
		err = os.WriteFile(pubKeyPath, pubKeyData, 0644)
625
626
627
628
629
630
631
632
633
		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
634
func startMacApp(client *api.Client) error {
Bruce MacDonald's avatar
Bruce MacDonald committed
635
636
637
638
639
	exe, err := os.Executable()
	if err != nil {
		return err
	}
	link, err := os.Readlink(exe)
Bruce MacDonald's avatar
Bruce MacDonald committed
640
641
642
	if err != nil {
		return err
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
643
644
645
	if !strings.Contains(link, "Ollama.app") {
		return fmt.Errorf("could not find ollama app")
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
	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
			}
		}
	}
}

665
666
667
func checkServerHeartbeat(_ *cobra.Command, _ []string) error {
	client := api.NewClient()
	if err := client.Heartbeat(context.Background()); err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
668
669
670
671
672
		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
673
				return fmt.Errorf("could not connect to ollama app, is it running?")
674
			}
Bruce MacDonald's avatar
Bruce MacDonald committed
675
		} else {
676
677
678
679
680
681
			return fmt.Errorf("could not connect to ollama server, run 'ollama serve' to start it")
		}
	}
	return nil
}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
682
683
684
685
func NewCLI() *cobra.Command {
	log.SetFlags(log.LstdFlags | log.Lshortfile)

	rootCmd := &cobra.Command{
686
687
688
689
		Use:           "ollama",
		Short:         "Large language model runner",
		SilenceUsage:  true,
		SilenceErrors: true,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
690
691
692
693
694
695
696
		CompletionOptions: cobra.CompletionOptions{
			DisableDefaultCmd: true,
		},
	}

	cobra.EnableCommandSorting = false

697
	createCmd := &cobra.Command{
698
699
700
701
702
		Use:     "create MODEL",
		Short:   "Create a model from a Modelfile",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    CreateHandler,
703
704
705
706
	}

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

Jeffrey Morgan's avatar
Jeffrey Morgan committed
707
	runCmd := &cobra.Command{
708
709
710
711
712
		Use:     "run MODEL [PROMPT]",
		Short:   "Run a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    RunHandler,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
713
714
	}

715
716
	runCmd.Flags().Bool("verbose", false, "Show timings for response")

Jeffrey Morgan's avatar
Jeffrey Morgan committed
717
718
719
720
	serveCmd := &cobra.Command{
		Use:     "serve",
		Aliases: []string{"start"},
		Short:   "Start ollama",
Michael Yang's avatar
Michael Yang committed
721
		RunE:    RunServer,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
722
723
	}

724
	pullCmd := &cobra.Command{
725
726
727
728
729
		Use:     "pull MODEL",
		Short:   "Pull a model from a registry",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    PullHandler,
730
731
	}

732
733
	pullCmd.Flags().Bool("insecure", false, "Use an insecure registry")

734
	pushCmd := &cobra.Command{
735
736
737
738
739
		Use:     "push MODEL",
		Short:   "Push a model to a registry",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    PushHandler,
740
741
	}

742
743
	pushCmd.Flags().Bool("insecure", false, "Use an insecure registry")

Patrick Devine's avatar
Patrick Devine committed
744
	listCmd := &cobra.Command{
745
		Use:     "list",
Patrick Devine's avatar
Patrick Devine committed
746
		Aliases: []string{"ls"},
747
		Short:   "List models",
748
		PreRunE: checkServerHeartbeat,
749
		RunE:    ListHandler,
750
751
	}

Patrick Devine's avatar
Patrick Devine committed
752
	copyCmd := &cobra.Command{
753
754
755
756
757
		Use:     "cp",
		Short:   "Copy a model",
		Args:    cobra.MinimumNArgs(2),
		PreRunE: checkServerHeartbeat,
		RunE:    CopyHandler,
Patrick Devine's avatar
Patrick Devine committed
758
759
	}

760
	deleteCmd := &cobra.Command{
761
762
763
764
765
		Use:     "rm",
		Short:   "Remove a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    DeleteHandler,
Patrick Devine's avatar
Patrick Devine committed
766
767
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
768
769
	rootCmd.AddCommand(
		serveCmd,
770
		createCmd,
771
		runCmd,
772
773
		pullCmd,
		pushCmd,
Patrick Devine's avatar
Patrick Devine committed
774
		listCmd,
Patrick Devine's avatar
Patrick Devine committed
775
		copyCmd,
776
		deleteCmd,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
777
778
779
780
	)

	return rootCmd
}