cmd.go 16.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"
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
		return nil
	}

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

Michael Yang's avatar
Michael Yang committed
85
86
87
88
	if spinner != nil {
		spinner.Stop()
	}

89
90
91
	return nil
}

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

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

	return RunGenerate(cmd, args)
Bruce MacDonald's avatar
Bruce MacDonald committed
117
118
}

119
func PushHandler(cmd *cobra.Command, args []string) error {
120
121
	client := api.NewClient()

122
123
124
125
126
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

127
128
129
	var currentDigest string
	var bar *progressbar.ProgressBar

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

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

155
func ListHandler(cmd *cobra.Command, args []string) error {
Patrick Devine's avatar
Patrick Devine committed
156
157
158
159
160
161
162
163
164
165
	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
166
167
168
		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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
	}

	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
}

185
186
187
func DeleteHandler(cmd *cobra.Command, args []string) error {
	client := api.NewClient()

Patrick Devine's avatar
Patrick Devine committed
188
189
	req := api.DeleteRequest{Name: args[0]}
	if err := client.Delete(context.Background(), &req); err != nil {
190
191
		return err
	}
192
	fmt.Printf("deleted '%s'\n", args[0])
193
194
195
	return nil
}

Patrick Devine's avatar
Patrick Devine committed
196
197
198
199
200
201
202
203
204
205
206
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
}

207
func PullHandler(cmd *cobra.Command, args []string) error {
208
209
210
211
212
213
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

	return pull(args[0], insecure)
214
215
}

216
func pull(model string, insecure bool) error {
Bruce MacDonald's avatar
Bruce MacDonald committed
217
	client := api.NewClient()
218

219
	var currentDigest string
Bruce MacDonald's avatar
Bruce MacDonald committed
220
	var bar *progressbar.ProgressBar
Michael Yang's avatar
Michael Yang committed
221

222
	request := api.PullRequest{Name: model, Insecure: insecure}
223
224
225
	fn := func(resp api.ProgressResponse) error {
		if resp.Digest != currentDigest && resp.Digest != "" {
			currentDigest = resp.Digest
226
227
			bar = progressbar.DefaultBytes(
				int64(resp.Total),
228
				fmt.Sprintf("pulling %s...", resp.Digest[7:19]),
229
			)
230
231
232

			bar.Set(resp.Completed)
		} else if resp.Digest == currentDigest && resp.Digest != "" {
233
234
			bar.Set(resp.Completed)
		} else {
235
			currentDigest = ""
236
237
238
239
			fmt.Println(resp.Status)
		}
		return nil
	}
240

241
242
243
244
	if err := client.Pull(context.Background(), &request, fn); err != nil {
		return err
	}
	return nil
Michael Yang's avatar
Michael Yang committed
245
246
}

247
func RunGenerate(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
248
	if len(args) > 1 {
Michael Yang's avatar
Michael Yang committed
249
		// join all args into a single prompt
250
		return generate(cmd, args[0], strings.Join(args[1:], " "))
Michael Yang's avatar
Michael Yang committed
251
252
	}

Michael Yang's avatar
Michael Yang committed
253
	if readline.IsTerminal(int(os.Stdin.Fd())) {
254
		return generateInteractive(cmd, args[0])
Michael Yang's avatar
Michael Yang committed
255
256
	}

257
	return generateBatch(cmd, args[0])
Michael Yang's avatar
Michael Yang committed
258
259
}

Michael Yang's avatar
Michael Yang committed
260
type generateContextKey string
Michael Yang's avatar
Michael Yang committed
261

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

Michael Yang's avatar
Michael Yang committed
266
267
		spinner := NewSpinner("")
		go spinner.Spin(60 * time.Millisecond)
Michael Yang's avatar
Michael Yang committed
268

269
270
		var latest api.GenerateResponse

Michael Yang's avatar
Michael Yang committed
271
		generateContext, ok := cmd.Context().Value(generateContextKey("context")).([]int)
Michael Yang's avatar
Michael Yang committed
272
273
274
275
		if !ok {
			generateContext = []int{}
		}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
276
		request := api.GenerateRequest{Model: model, Prompt: prompt, Context: generateContext}
Michael Yang's avatar
Michael Yang committed
277
		fn := func(response api.GenerateResponse) error {
Michael Yang's avatar
Michael Yang committed
278
279
280
			if !spinner.IsFinished() {
				spinner.Finish()
			}
Michael Yang's avatar
Michael Yang committed
281

Michael Yang's avatar
Michael Yang committed
282
			latest = response
283

Michael Yang's avatar
Michael Yang committed
284
			fmt.Print(response.Response)
Michael Yang's avatar
Michael Yang committed
285
			return nil
286
287
288
		}

		if err := client.Generate(context.Background(), &request, fn); err != nil {
289
290
291
292
293
294
295
296
297
298
299
300
			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)
				}
			}
301
302
			return err
		}
Michael Yang's avatar
Michael Yang committed
303

Michael Yang's avatar
Michael Yang committed
304
305
		fmt.Println()
		fmt.Println()
306
307
308
309
310
311
312
313
314

		verbose, err := cmd.Flags().GetBool("verbose")
		if err != nil {
			return err
		}

		if verbose {
			latest.Summary()
		}
Michael Yang's avatar
Michael Yang committed
315
316
317
318

		ctx := cmd.Context()
		ctx = context.WithValue(ctx, generateContextKey("context"), latest.Context)
		cmd.SetContext(ctx)
Michael Yang's avatar
Michael Yang committed
319
	}
Michael Yang's avatar
Michael Yang committed
320
321
322
323

	return nil
}

324
325
func showLayer(l *server.Layer) {
	filename, err := server.GetBlobsPath(l.Digest)
326
327
328
329
	if err != nil {
		fmt.Println("Couldn't get layer's path")
		return
	}
330
331
	bts, err := os.ReadFile(filename)
	if err != nil {
332
		fmt.Println("Couldn't read layer")
333
334
		return
	}
335
	fmt.Println(string(bts))
336
337
}

338
func generateInteractive(cmd *cobra.Command, model string) error {
Michael Yang's avatar
Michael Yang committed
339
340
341
342
343
344
345
346
347
348
349
	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
350
351
			readline.PcItem("verbose"),
			readline.PcItem("quiet"),
Michael Yang's avatar
Michael Yang committed
352
353
354
355
356
357
			readline.PcItem("mode",
				readline.PcItem("vim"),
				readline.PcItem("emacs"),
				readline.PcItem("default"),
			),
		),
358
359
360
361
362
		readline.PcItem("/show",
			readline.PcItem("license"),
			readline.PcItem("system"),
			readline.PcItem("template"),
		),
Michael Yang's avatar
Michael Yang committed
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
		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()

384
385
386
	var multiLineBuffer string
	var isMultiLine bool

Michael Yang's avatar
Michael Yang committed
387
388
389
390
391
392
	for {
		line, err := scanner.Readline()
		switch {
		case errors.Is(err, io.EOF):
			return nil
		case errors.Is(err, readline.ErrInterrupt):
393
394
395
396
			if line == "" {
				return nil
			}

Michael Yang's avatar
Michael Yang committed
397
398
			continue
		case err != nil:
Michael Yang's avatar
Michael Yang committed
399
400
401
			return err
		}

Michael Yang's avatar
Michael Yang committed
402
		line = strings.TrimSpace(line)
Michael Yang's avatar
Michael Yang committed
403

Michael Yang's avatar
Michael Yang committed
404
		switch {
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
		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
421
422
		case strings.HasPrefix(line, "/list"):
			args := strings.Fields(line)
423
			if err := ListHandler(cmd, args[1:]); err != nil {
Michael Yang's avatar
Michael Yang committed
424
425
426
427
428
429
430
431
432
433
434
435
436
437
				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
438
439
440
441
442
443
				case "verbose":
					cmd.Flags().Set("verbose", "true")
					continue
				case "quiet":
					cmd.Flags().Set("verbose", "false")
					continue
Michael Yang's avatar
Michael Yang committed
444
445
446
447
448
449
450
451
452
				case "mode":
					if len(args) > 2 {
						switch args[2] {
						case "vim":
							scanner.SetVimMode(true)
							continue
						case "emacs", "default":
							scanner.SetVimMode(false)
							continue
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
						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 {
472
					fmt.Println("error: couldn't get a manifest for this model")
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
					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
494
495
						}
					}
496
497
498
499
					continue
				default:
					usage()
					continue
Michael Yang's avatar
Michael Yang committed
500
				}
501
502
503
			} else {
				usage()
				continue
Michael Yang's avatar
Michael Yang committed
504
505
506
507
508
509
510
511
512
513
514
515
			}
		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
516
517
}

518
func generateBatch(cmd *cobra.Command, model string) error {
Michael Yang's avatar
Michael Yang committed
519
520
521
522
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		prompt := scanner.Text()
		fmt.Printf(">>> %s\n", prompt)
523
		if err := generate(cmd, model, prompt); err != nil {
Michael Yang's avatar
Michael Yang committed
524
525
526
527
528
529
530
			return err
		}
	}

	return nil
}

531
532
533
534
535
536
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()
537
538
	}

539
540
541
542
543
544
545
	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
546
	}
547

548
549
550
551
552
	err := initializeKeypair()
	if err != nil {
		return err
	}

553
	ln, err := net.Listen("tcp", fmt.Sprintf("%s:%s", host, port))
554
555
556
	if err != nil {
		return err
	}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
557

558
559
560
561
562
	var origins []string
	if o := os.Getenv("OLLAMA_ORIGINS"); o != "" {
		origins = strings.Split(o, ",")
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
563
	return server.Serve(ln, origins)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
564
565
}

566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
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
		}

588
589
590
591
592
593
		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)
594
595
596
597
598
599
600
601
602
603
604
		if err != nil {
			return err
		}

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

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

605
		err = os.WriteFile(pubKeyPath, pubKeyData, 0644)
606
607
608
609
610
611
612
613
614
		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
615
func startMacApp(client *api.Client) error {
Bruce MacDonald's avatar
Bruce MacDonald committed
616
617
618
619
620
	exe, err := os.Executable()
	if err != nil {
		return err
	}
	link, err := os.Readlink(exe)
Bruce MacDonald's avatar
Bruce MacDonald committed
621
622
623
	if err != nil {
		return err
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
624
625
626
	if !strings.Contains(link, "Ollama.app") {
		return fmt.Errorf("could not find ollama app")
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
	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
			}
		}
	}
}

646
647
648
func checkServerHeartbeat(_ *cobra.Command, _ []string) error {
	client := api.NewClient()
	if err := client.Heartbeat(context.Background()); err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
649
650
651
652
653
		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
654
				return fmt.Errorf("could not connect to ollama app, is it running?")
655
			}
Bruce MacDonald's avatar
Bruce MacDonald committed
656
		} else {
657
658
659
660
661
662
			return fmt.Errorf("could not connect to ollama server, run 'ollama serve' to start it")
		}
	}
	return nil
}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
663
664
665
666
func NewCLI() *cobra.Command {
	log.SetFlags(log.LstdFlags | log.Lshortfile)

	rootCmd := &cobra.Command{
Michael Yang's avatar
Michael Yang committed
667
668
669
		Use:          "ollama",
		Short:        "Large language model runner",
		SilenceUsage: true,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
670
671
672
673
674
675
676
		CompletionOptions: cobra.CompletionOptions{
			DisableDefaultCmd: true,
		},
	}

	cobra.EnableCommandSorting = false

677
	createCmd := &cobra.Command{
678
679
680
681
682
		Use:     "create MODEL",
		Short:   "Create a model from a Modelfile",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    CreateHandler,
683
684
685
686
	}

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

Jeffrey Morgan's avatar
Jeffrey Morgan committed
687
	runCmd := &cobra.Command{
688
689
690
691
692
		Use:     "run MODEL [PROMPT]",
		Short:   "Run a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    RunHandler,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
693
694
	}

695
696
	runCmd.Flags().Bool("verbose", false, "Show timings for response")

Jeffrey Morgan's avatar
Jeffrey Morgan committed
697
698
699
700
	serveCmd := &cobra.Command{
		Use:     "serve",
		Aliases: []string{"start"},
		Short:   "Start ollama",
Michael Yang's avatar
Michael Yang committed
701
		RunE:    RunServer,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
702
703
	}

704
	pullCmd := &cobra.Command{
705
706
707
708
709
		Use:     "pull MODEL",
		Short:   "Pull a model from a registry",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    PullHandler,
710
711
	}

712
713
	pullCmd.Flags().Bool("insecure", false, "Use an insecure registry")

714
	pushCmd := &cobra.Command{
715
716
717
718
719
		Use:     "push MODEL",
		Short:   "Push a model to a registry",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    PushHandler,
720
721
	}

722
723
	pushCmd.Flags().Bool("insecure", false, "Use an insecure registry")

Patrick Devine's avatar
Patrick Devine committed
724
	listCmd := &cobra.Command{
725
		Use:     "list",
Patrick Devine's avatar
Patrick Devine committed
726
		Aliases: []string{"ls"},
727
		Short:   "List models",
728
		PreRunE: checkServerHeartbeat,
729
		RunE:    ListHandler,
730
731
	}

Patrick Devine's avatar
Patrick Devine committed
732
	copyCmd := &cobra.Command{
733
734
735
736
737
		Use:     "cp",
		Short:   "Copy a model",
		Args:    cobra.MinimumNArgs(2),
		PreRunE: checkServerHeartbeat,
		RunE:    CopyHandler,
Patrick Devine's avatar
Patrick Devine committed
738
739
	}

740
	deleteCmd := &cobra.Command{
741
742
743
744
745
		Use:     "rm",
		Short:   "Remove a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    DeleteHandler,
Patrick Devine's avatar
Patrick Devine committed
746
747
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
748
749
	rootCmd.AddCommand(
		serveCmd,
750
		createCmd,
751
		runCmd,
752
753
		pullCmd,
		pushCmd,
Patrick Devine's avatar
Patrick Devine committed
754
		listCmd,
Patrick Devine's avatar
Patrick Devine committed
755
		copyCmd,
756
		deleteCmd,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
757
758
759
760
	)

	return rootCmd
}