cmd.go 19.2 KB
Newer Older
Jeffrey Morgan's avatar
Jeffrey Morgan committed
1
2
3
package cmd

import (
Michael Yang's avatar
Michael Yang committed
4
	"bytes"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
5
	"context"
6
7
	"crypto/ed25519"
	"crypto/rand"
Michael Yang's avatar
Michael Yang committed
8
	"crypto/sha256"
9
	"encoding/pem"
Michael Yang's avatar
Michael Yang committed
10
	"errors"
Bruce MacDonald's avatar
Bruce MacDonald committed
11
	"fmt"
Michael Yang's avatar
Michael Yang committed
12
	"io"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
13
14
	"log"
	"net"
15
	"net/http"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
16
	"os"
17
	"os/exec"
18
	"os/signal"
19
	"path/filepath"
20
	"runtime"
Michael Yang's avatar
Michael Yang committed
21
	"strings"
22
	"syscall"
Michael Yang's avatar
Michael Yang committed
23
	"time"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
24

Patrick Devine's avatar
Patrick Devine committed
25
	"github.com/olekukonko/tablewriter"
Michael Yang's avatar
Michael Yang committed
26
	"github.com/spf13/cobra"
27
	"golang.org/x/crypto/ssh"
28
	"golang.org/x/term"
Michael Yang's avatar
Michael Yang committed
29

Jeffrey Morgan's avatar
Jeffrey Morgan committed
30
	"github.com/jmorganca/ollama/api"
Patrick Devine's avatar
Patrick Devine committed
31
	"github.com/jmorganca/ollama/format"
Michael Yang's avatar
Michael Yang committed
32
	"github.com/jmorganca/ollama/parser"
Michael Yang's avatar
Michael Yang committed
33
	"github.com/jmorganca/ollama/progress"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
34
	"github.com/jmorganca/ollama/server"
Michael Yang's avatar
Michael Yang committed
35
	"github.com/jmorganca/ollama/version"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
36
37
)

Patrick Devine's avatar
Patrick Devine committed
38
39
type ImageData []byte

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

Michael Yang's avatar
Michael Yang committed
47
	client, err := api.ClientFromEnvironment()
48
49
50
	if err != nil {
		return err
	}
51

Michael Yang's avatar
Michael Yang committed
52
53
54
55
56
	p := progress.NewProgress(os.Stderr)
	defer p.Stop()

	bars := make(map[string]*progress.Bar)

Michael Yang's avatar
Michael Yang committed
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
	modelfile, err := os.ReadFile(filename)
	if err != nil {
		return err
	}

	commands, err := parser.Parse(bytes.NewReader(modelfile))
	if err != nil {
		return err
	}

	home, err := os.UserHomeDir()
	if err != nil {
		return err
	}

72
73
	status := "transferring model data"
	spinner := progress.NewSpinner(status)
74
75
	p.Add(status, spinner)

Michael Yang's avatar
Michael Yang committed
76
77
78
79
80
81
82
83
84
85
	for _, c := range commands {
		switch c.Name {
		case "model", "adapter":
			path := c.Args
			if path == "~" {
				path = home
			} else if strings.HasPrefix(path, "~/") {
				path = filepath.Join(home, path[2:])
			}

86
87
88
89
			if !filepath.IsAbs(path) {
				path = filepath.Join(filepath.Dir(filename), path)
			}

Michael Yang's avatar
Michael Yang committed
90
91
			bin, err := os.Open(path)
			if errors.Is(err, os.ErrNotExist) && c.Name == "model" {
Michael Yang's avatar
Michael Yang committed
92
				continue
Michael Yang's avatar
Michael Yang committed
93
94
95
96
97
98
99
100
101
102
103
104
			} else if err != nil {
				return err
			}
			defer bin.Close()

			hash := sha256.New()
			if _, err := io.Copy(hash, bin); err != nil {
				return err
			}
			bin.Seek(0, io.SeekStart)

			digest := fmt.Sprintf("sha256:%x", hash.Sum(nil))
Michael Yang's avatar
Michael Yang committed
105
			if err = client.CreateBlob(cmd.Context(), digest, bin); err != nil {
Michael Yang's avatar
Michael Yang committed
106
107
108
				return err
			}

Michael Yang's avatar
Michael Yang committed
109
			modelfile = bytes.ReplaceAll(modelfile, []byte(c.Args), []byte("@"+digest))
Michael Yang's avatar
Michael Yang committed
110
111
		}
	}
Michael Yang's avatar
Michael Yang committed
112

113
	fn := func(resp api.ProgressResponse) error {
Michael Yang's avatar
Michael Yang committed
114
115
116
117
118
		if resp.Digest != "" {
			spinner.Stop()

			bar, ok := bars[resp.Digest]
			if !ok {
119
				bar = progress.NewBar(fmt.Sprintf("pulling %s...", resp.Digest[7:19]), resp.Total, resp.Completed)
Michael Yang's avatar
Michael Yang committed
120
121
122
123
124
125
126
127
128
129
130
131
132
				bars[resp.Digest] = bar
				p.Add(resp.Digest, bar)
			}

			bar.Set(resp.Completed)
		} else if status != resp.Status {
			spinner.Stop()

			status = resp.Status
			spinner = progress.NewSpinner(status)
			p.Add(status, spinner)
		}

133
134
135
		return nil
	}

136
	request := api.CreateRequest{Name: args[0], Modelfile: string(modelfile)}
Michael Yang's avatar
Michael Yang committed
137
	if err := client.Create(cmd.Context(), &request, fn); err != nil {
138
139
140
141
142
143
		return err
	}

	return nil
}

144
func RunHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
145
	client, err := api.ClientFromEnvironment()
146
147
148
149
	if err != nil {
		return err
	}

150
151
	name := args[0]
	// check if the model exists on the server
152
	model, err := client.Show(cmd.Context(), &api.ShowRequest{Model: name})
Michael Yang's avatar
Michael Yang committed
153
154
155
	var statusError api.StatusError
	switch {
	case errors.As(err, &statusError) && statusError.StatusCode == http.StatusNotFound:
156
		if err := PullHandler(cmd, []string{name}); err != nil {
157
			return err
Michael Yang's avatar
Michael Yang committed
158
		}
Michael Yang's avatar
Michael Yang committed
159
160
	case err != nil:
		return err
161
162
	}

163
164
165
166
167
168
169
170
171
172
173
174
175
	if model.Details.Format != "gguf" {
		// pull and retry to see if the model has been updated
		parts := strings.Split(name, string(os.PathSeparator))
		if len(parts) == 1 {
			// this is a library model, log some info
			fmt.Fprintln(os.Stderr, "This model is no longer compatible with Ollama. Pulling a new version...")
		}
		if err := PullHandler(cmd, []string{name}); err != nil {
			fmt.Printf("Error: %s\n", err)
			return fmt.Errorf("unsupported model, please update this model to gguf format") // relay the original error
		}
	}

176
	return RunGenerate(cmd, args)
Bruce MacDonald's avatar
Bruce MacDonald committed
177
178
}

179
func PushHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
180
	client, err := api.ClientFromEnvironment()
181
182
183
	if err != nil {
		return err
	}
184

185
186
187
188
189
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
190
191
192
193
	p := progress.NewProgress(os.Stderr)
	defer p.Stop()

	bars := make(map[string]*progress.Bar)
194
195
	var status string
	var spinner *progress.Spinner
Michael Yang's avatar
Michael Yang committed
196

197
	fn := func(resp api.ProgressResponse) error {
Michael Yang's avatar
Michael Yang committed
198
		if resp.Digest != "" {
199
200
201
			if spinner != nil {
				spinner.Stop()
			}
Michael Yang's avatar
Michael Yang committed
202
203
204

			bar, ok := bars[resp.Digest]
			if !ok {
205
				bar = progress.NewBar(fmt.Sprintf("pushing %s...", resp.Digest[7:19]), resp.Total, resp.Completed)
Michael Yang's avatar
Michael Yang committed
206
207
208
209
210
211
				bars[resp.Digest] = bar
				p.Add(resp.Digest, bar)
			}

			bar.Set(resp.Completed)
		} else if status != resp.Status {
212
213
214
			if spinner != nil {
				spinner.Stop()
			}
Michael Yang's avatar
Michael Yang committed
215
216
217
218
219
220

			status = resp.Status
			spinner = progress.NewSpinner(status)
			p.Add(status, spinner)
		}

221
222
223
		return nil
	}

Michael Yang's avatar
Michael Yang committed
224
	request := api.PushRequest{Name: args[0], Insecure: insecure}
Michael Yang's avatar
Michael Yang committed
225
	if err := client.Push(cmd.Context(), &request, fn); err != nil {
Michael Yang's avatar
Michael Yang committed
226
227
228
		return err
	}

229
	spinner.Stop()
Michael Yang's avatar
Michael Yang committed
230
	return nil
231
232
}

233
func ListHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
234
	client, err := api.ClientFromEnvironment()
235
236
237
	if err != nil {
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
238

Michael Yang's avatar
Michael Yang committed
239
	models, err := client.List(cmd.Context())
Patrick Devine's avatar
Patrick Devine committed
240
241
242
243
244
245
246
	if err != nil {
		return err
	}

	var data [][]string

	for _, m := range models.Models {
Michael Yang's avatar
Michael Yang committed
247
		if len(args) == 0 || strings.HasPrefix(m.Name, args[0]) {
248
			data = append(data, []string{m.Name, m.Digest[:12], format.HumanBytes(m.Size), format.HumanTime(m.ModifiedAt, "Never")})
Michael Yang's avatar
Michael Yang committed
249
		}
Patrick Devine's avatar
Patrick Devine committed
250
251
252
	}

	table := tablewriter.NewWriter(os.Stdout)
Patrick Devine's avatar
Patrick Devine committed
253
	table.SetHeader([]string{"NAME", "ID", "SIZE", "MODIFIED"})
Patrick Devine's avatar
Patrick Devine committed
254
255
256
257
258
259
260
261
262
263
264
265
	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
}

266
func DeleteHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
267
	client, err := api.ClientFromEnvironment()
268
269
270
	if err != nil {
		return err
	}
271

272
273
	for _, name := range args {
		req := api.DeleteRequest{Name: name}
Michael Yang's avatar
Michael Yang committed
274
		if err := client.Delete(cmd.Context(), &req); err != nil {
275
276
277
			return err
		}
		fmt.Printf("deleted '%s'\n", name)
278
279
280
281
	}
	return nil
}

Patrick Devine's avatar
Patrick Devine committed
282
func ShowHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
283
	client, err := api.ClientFromEnvironment()
Patrick Devine's avatar
Patrick Devine committed
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
	if err != nil {
		return err
	}

	if len(args) != 1 {
		return errors.New("missing model name")
	}

	license, errLicense := cmd.Flags().GetBool("license")
	modelfile, errModelfile := cmd.Flags().GetBool("modelfile")
	parameters, errParams := cmd.Flags().GetBool("parameters")
	system, errSystem := cmd.Flags().GetBool("system")
	template, errTemplate := cmd.Flags().GetBool("template")

	for _, boolErr := range []error{errLicense, errModelfile, errParams, errSystem, errTemplate} {
		if boolErr != nil {
			return errors.New("error retrieving flags")
		}
	}

	flagsSet := 0
	showType := ""

	if license {
		flagsSet++
		showType = "license"
	}

	if modelfile {
		flagsSet++
		showType = "modelfile"
	}

	if parameters {
		flagsSet++
		showType = "parameters"
	}

	if system {
		flagsSet++
		showType = "system"
	}

	if template {
		flagsSet++
		showType = "template"
	}

	if flagsSet > 1 {
333
		return errors.New("only one of '--license', '--modelfile', '--parameters', '--system', or '--template' can be specified")
Patrick Devine's avatar
Patrick Devine committed
334
	} else if flagsSet == 0 {
335
		return errors.New("one of '--license', '--modelfile', '--parameters', '--system', or '--template' must be specified")
Patrick Devine's avatar
Patrick Devine committed
336
337
	}

338
	req := api.ShowRequest{Model: args[0]}
Michael Yang's avatar
Michael Yang committed
339
	resp, err := client.Show(cmd.Context(), &req)
Patrick Devine's avatar
Patrick Devine committed
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
	if err != nil {
		return err
	}

	switch showType {
	case "license":
		fmt.Println(resp.License)
	case "modelfile":
		fmt.Println(resp.Modelfile)
	case "parameters":
		fmt.Println(resp.Parameters)
	case "system":
		fmt.Println(resp.System)
	case "template":
		fmt.Println(resp.Template)
	}

	return nil
}

Patrick Devine's avatar
Patrick Devine committed
360
func CopyHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
361
	client, err := api.ClientFromEnvironment()
362
363
364
	if err != nil {
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
365
366

	req := api.CopyRequest{Source: args[0], Destination: args[1]}
Michael Yang's avatar
Michael Yang committed
367
	if err := client.Copy(cmd.Context(), &req); err != nil {
Patrick Devine's avatar
Patrick Devine committed
368
369
370
371
372
373
		return err
	}
	fmt.Printf("copied '%s' to '%s'\n", args[0], args[1])
	return nil
}

374
func PullHandler(cmd *cobra.Command, args []string) error {
375
376
377
378
379
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
380
	client, err := api.ClientFromEnvironment()
381
382
383
	if err != nil {
		return err
	}
384

Michael Yang's avatar
Michael Yang committed
385
386
387
388
389
	p := progress.NewProgress(os.Stderr)
	defer p.Stop()

	bars := make(map[string]*progress.Bar)

390
391
	var status string
	var spinner *progress.Spinner
Michael Yang's avatar
Michael Yang committed
392

393
	fn := func(resp api.ProgressResponse) error {
Michael Yang's avatar
Michael Yang committed
394
		if resp.Digest != "" {
395
396
397
			if spinner != nil {
				spinner.Stop()
			}
Michael Yang's avatar
Michael Yang committed
398
399
400

			bar, ok := bars[resp.Digest]
			if !ok {
401
				bar = progress.NewBar(fmt.Sprintf("pulling %s...", resp.Digest[7:19]), resp.Total, resp.Completed)
Michael Yang's avatar
Michael Yang committed
402
403
404
405
406
407
				bars[resp.Digest] = bar
				p.Add(resp.Digest, bar)
			}

			bar.Set(resp.Completed)
		} else if status != resp.Status {
408
409
410
			if spinner != nil {
				spinner.Stop()
			}
Michael Yang's avatar
Michael Yang committed
411
412
413
414
415
416

			status = resp.Status
			spinner = progress.NewSpinner(status)
			p.Add(status, spinner)
		}

417
418
		return nil
	}
419

Michael Yang's avatar
Michael Yang committed
420
	request := api.PullRequest{Name: args[0], Insecure: insecure}
Michael Yang's avatar
Michael Yang committed
421
	if err := client.Pull(cmd.Context(), &request, fn); err != nil {
Michael Yang's avatar
Michael Yang committed
422
423
424
425
		return err
	}

	return nil
Michael Yang's avatar
Michael Yang committed
426
427
}

428
429
430
431
432
433
434
func RunGenerate(cmd *cobra.Command, args []string) error {
	interactive := true

	opts := generateOptions{
		Model:    args[0],
		WordWrap: os.Getenv("TERM") == "xterm-256color",
		Options:  map[string]interface{}{},
Patrick Devine's avatar
Patrick Devine committed
435
		Images:   []ImageData{},
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
	}

	format, err := cmd.Flags().GetString("format")
	if err != nil {
		return err
	}
	opts.Format = format

	prompts := args[1:]
	// prepend stdin to the prompt if provided
	if !term.IsTerminal(int(os.Stdin.Fd())) {
		in, err := io.ReadAll(os.Stdin)
		if err != nil {
			return err
		}

		prompts = append([]string{string(in)}, prompts...)
		opts.WordWrap = false
		interactive = false
	}
	opts.Prompt = strings.Join(prompts, " ")
	if len(prompts) > 0 {
		interactive = false
	}

	nowrap, err := cmd.Flags().GetBool("nowordwrap")
	if err != nil {
		return err
	}
	opts.WordWrap = !nowrap

	if !interactive {
		return generate(cmd, opts)
	}

	return generateInteractive(cmd, opts)
}

type generateContextKey string

type generateOptions struct {
477
	Model    string
478
	Prompt   string
479
480
	WordWrap bool
	Format   string
481
	System   string
482
	Template string
Patrick Devine's avatar
Patrick Devine committed
483
	Images   []ImageData
484
485
486
	Options  map[string]interface{}
}

487
func generate(cmd *cobra.Command, opts generateOptions) error {
Michael Yang's avatar
Michael Yang committed
488
	client, err := api.ClientFromEnvironment()
Patrick Devine's avatar
Patrick Devine committed
489
	if err != nil {
490
		return err
Patrick Devine's avatar
Patrick Devine committed
491
	}
Michael Yang's avatar
Michael Yang committed
492

Michael Yang's avatar
Michael Yang committed
493
	p := progress.NewProgress(os.Stderr)
494
	defer p.StopAndClear()
495

Michael Yang's avatar
Michael Yang committed
496
497
498
	spinner := progress.NewSpinner("")
	p.Add("", spinner)

499
500
501
502
503
504
505
	var latest api.GenerateResponse

	generateContext, ok := cmd.Context().Value(generateContextKey("context")).([]int)
	if !ok {
		generateContext = []int{}
	}

506
	termWidth, _, err := term.GetSize(int(os.Stdout.Fd()))
507
	if err != nil {
508
		opts.WordWrap = false
509
510
	}

Michael Yang's avatar
Michael Yang committed
511
	ctx, cancel := context.WithCancel(cmd.Context())
512
513
514
515
516
517
518
519
520
521
	defer cancel()

	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT)

	go func() {
		<-sigChan
		cancel()
	}()

522
523
524
	var currentLineLength int
	var wordBuffer string

525
	fn := func(response api.GenerateResponse) error {
Michael Yang's avatar
Michael Yang committed
526
		p.StopAndClear()
527

Patrick Devine's avatar
Patrick Devine committed
528
		latest = response
529

530
531
		termWidth, _, _ = term.GetSize(int(os.Stdout.Fd()))
		if opts.WordWrap && termWidth >= 10 {
532
			for _, ch := range response.Response {
533
				if currentLineLength+1 > termWidth-5 {
534
535
536
537
538
539
540
					if len(wordBuffer) > termWidth-10 {
						fmt.Printf("%s%c", wordBuffer, ch)
						wordBuffer = ""
						currentLineLength = 0
						continue
					}

541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
					// backtrack the length of the last word and clear to the end of the line
					fmt.Printf("\x1b[%dD\x1b[K\n", len(wordBuffer))
					fmt.Printf("%s%c", wordBuffer, ch)
					currentLineLength = len(wordBuffer) + 1
				} else {
					fmt.Print(string(ch))
					currentLineLength += 1

					switch ch {
					case ' ':
						wordBuffer = ""
					case '\n':
						currentLineLength = 0
					default:
						wordBuffer += string(ch)
					}
				}
			}
		} else {
560
			fmt.Printf("%s%s", wordBuffer, response.Response)
561
562
563
			if len(wordBuffer) > 0 {
				wordBuffer = ""
			}
564
565
		}

Patrick Devine's avatar
Patrick Devine committed
566
567
		return nil
	}
568

Patrick Devine's avatar
Patrick Devine committed
569
570
571
572
	images := make([]api.ImageData, 0)
	for _, i := range opts.Images {
		images = append(images, api.ImageData(i))
	}
Michael Yang's avatar
Michael Yang committed
573
574
575
576
577
578
579
580
	request := api.GenerateRequest{
		Model:    opts.Model,
		Prompt:   opts.Prompt,
		Context:  generateContext,
		Format:   opts.Format,
		System:   opts.System,
		Template: opts.Template,
		Options:  opts.Options,
Patrick Devine's avatar
Patrick Devine committed
581
		Images:   images,
Michael Yang's avatar
Michael Yang committed
582
583
584
	}

	if err := client.Generate(ctx, &request, fn); err != nil {
585
		if errors.Is(err, context.Canceled) {
586
			return nil
587
		}
588
		return err
Patrick Devine's avatar
Patrick Devine committed
589
	}
590

591
	if opts.Prompt != "" {
Michael Yang's avatar
Michael Yang committed
592
593
		fmt.Println()
		fmt.Println()
Patrick Devine's avatar
Patrick Devine committed
594
	}
595

596
597
598
599
	if !latest.Done {
		return nil
	}

Patrick Devine's avatar
Patrick Devine committed
600
601
	verbose, err := cmd.Flags().GetBool("verbose")
	if err != nil {
602
		return err
Patrick Devine's avatar
Patrick Devine committed
603
	}
Michael Yang's avatar
Michael Yang committed
604

Patrick Devine's avatar
Patrick Devine committed
605
606
	if verbose {
		latest.Summary()
Michael Yang's avatar
Michael Yang committed
607
	}
Michael Yang's avatar
Michael Yang committed
608

Patrick Devine's avatar
Patrick Devine committed
609
610
611
	ctx = context.WithValue(cmd.Context(), generateContextKey("context"), latest.Context)
	cmd.SetContext(ctx)

612
	return nil
Michael Yang's avatar
Michael Yang committed
613
614
}

615
func RunServer(cmd *cobra.Command, _ []string) error {
Michael Yang's avatar
Michael Yang committed
616
617
618
	host, port, err := net.SplitHostPort(os.Getenv("OLLAMA_HOST"))
	if err != nil {
		host, port = "127.0.0.1", "11434"
Michael Yang's avatar
Michael Yang committed
619
		if ip := net.ParseIP(strings.Trim(os.Getenv("OLLAMA_HOST"), "[]")); ip != nil {
Michael Yang's avatar
Michael Yang committed
620
621
			host = ip.String()
		}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
622
	}
623

Michael Yang's avatar
Michael Yang committed
624
	if err := initializeKeypair(); err != nil {
625
626
627
		return err
	}

Michael Yang's avatar
Michael Yang committed
628
	ln, err := net.Listen("tcp", net.JoinHostPort(host, port))
629
630
631
	if err != nil {
		return err
	}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
632

633
	return server.Serve(ln)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
634
635
}

636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
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
		}

Michael Yang's avatar
Michael Yang committed
658
		err = os.MkdirAll(filepath.Dir(privKeyPath), 0o755)
659
660
661
662
		if err != nil {
			return fmt.Errorf("could not create directory %w", err)
		}

663
		err = os.WriteFile(privKeyPath, pem.EncodeToMemory(privKeyBytes), 0o600)
664
665
666
667
668
669
670
671
672
673
674
		if err != nil {
			return err
		}

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

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

675
		err = os.WriteFile(pubKeyPath, pubKeyData, 0o644)
676
677
678
679
680
681
682
683
684
		if err != nil {
			return err
		}

		fmt.Printf("Your new public key is: \n\n%s\n", string(pubKeyData))
	}
	return nil
}

Michael Yang's avatar
Michael Yang committed
685
func startMacApp(ctx context.Context, client *api.Client) error {
Bruce MacDonald's avatar
Bruce MacDonald committed
686
687
688
689
690
	exe, err := os.Executable()
	if err != nil {
		return err
	}
	link, err := os.Readlink(exe)
Bruce MacDonald's avatar
Bruce MacDonald committed
691
692
693
	if err != nil {
		return err
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
694
695
696
	if !strings.Contains(link, "Ollama.app") {
		return fmt.Errorf("could not find ollama app")
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
697
698
699
700
701
702
703
704
705
706
707
708
	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:
Michael Yang's avatar
Michael Yang committed
709
			if err := client.Heartbeat(ctx); err == nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
710
711
712
713
714
715
				return nil // server has started
			}
		}
	}
}

Michael Yang's avatar
Michael Yang committed
716
func checkServerHeartbeat(cmd *cobra.Command, _ []string) error {
Michael Yang's avatar
Michael Yang committed
717
	client, err := api.ClientFromEnvironment()
718
719
720
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
721
	if err := client.Heartbeat(cmd.Context()); err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
722
723
724
725
		if !strings.Contains(err.Error(), "connection refused") {
			return err
		}
		if runtime.GOOS == "darwin" {
Michael Yang's avatar
Michael Yang committed
726
			if err := startMacApp(cmd.Context(), client); err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
727
				return fmt.Errorf("could not connect to ollama app, is it running?")
728
			}
Bruce MacDonald's avatar
Bruce MacDonald committed
729
		} else {
730
731
732
733
734
735
			return fmt.Errorf("could not connect to ollama server, run 'ollama serve' to start it")
		}
	}
	return nil
}

Michael Yang's avatar
Michael Yang committed
736
737
738
739
740
741
742
743
func versionHandler(cmd *cobra.Command, _ []string) {
	client, err := api.ClientFromEnvironment()
	if err != nil {
		return
	}

	serverVersion, err := client.Version(cmd.Context())
	if err != nil {
Michael Yang's avatar
Michael Yang committed
744
745
746
747
748
		fmt.Println("Warning: could not connect to a running Ollama instance")
	}

	if serverVersion != "" {
		fmt.Printf("ollama version is %s\n", serverVersion)
Michael Yang's avatar
Michael Yang committed
749
750
	}

751
	if serverVersion != version.Version {
Michael Yang's avatar
Michael Yang committed
752
		fmt.Printf("Warning: client version is %s\n", version.Version)
753
	}
Michael Yang's avatar
Michael Yang committed
754
755
}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
756
757
func NewCLI() *cobra.Command {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
Michael Yang's avatar
Michael Yang committed
758
	cobra.EnableCommandSorting = false
Jeffrey Morgan's avatar
Jeffrey Morgan committed
759
760

	rootCmd := &cobra.Command{
761
762
763
764
		Use:           "ollama",
		Short:         "Large language model runner",
		SilenceUsage:  true,
		SilenceErrors: true,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
765
766
767
		CompletionOptions: cobra.CompletionOptions{
			DisableDefaultCmd: true,
		},
Michael Yang's avatar
Michael Yang committed
768
769
770
771
772
773
774
775
		Run: func(cmd *cobra.Command, args []string) {
			if version, _ := cmd.Flags().GetBool("version"); version {
				versionHandler(cmd, args)
				return
			}

			cmd.Print(cmd.UsageString())
		},
Jeffrey Morgan's avatar
Jeffrey Morgan committed
776
777
	}

Michael Yang's avatar
Michael Yang committed
778
	rootCmd.Flags().BoolP("version", "v", false, "Show version information")
Jeffrey Morgan's avatar
Jeffrey Morgan committed
779

780
	createCmd := &cobra.Command{
781
782
		Use:     "create MODEL",
		Short:   "Create a model from a Modelfile",
Michael Yang's avatar
Michael Yang committed
783
		Args:    cobra.ExactArgs(1),
784
785
		PreRunE: checkServerHeartbeat,
		RunE:    CreateHandler,
786
787
788
789
	}

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

Patrick Devine's avatar
Patrick Devine committed
790
791
792
	showCmd := &cobra.Command{
		Use:     "show MODEL",
		Short:   "Show information for a model",
Michael Yang's avatar
Michael Yang committed
793
		Args:    cobra.ExactArgs(1),
Patrick Devine's avatar
Patrick Devine committed
794
795
796
797
798
799
800
801
		PreRunE: checkServerHeartbeat,
		RunE:    ShowHandler,
	}

	showCmd.Flags().Bool("license", false, "Show license of a model")
	showCmd.Flags().Bool("modelfile", false, "Show Modelfile of a model")
	showCmd.Flags().Bool("parameters", false, "Show parameters of a model")
	showCmd.Flags().Bool("template", false, "Show template of a model")
802
	showCmd.Flags().Bool("system", false, "Show system message of a model")
Patrick Devine's avatar
Patrick Devine committed
803

Jeffrey Morgan's avatar
Jeffrey Morgan committed
804
	runCmd := &cobra.Command{
805
806
807
808
809
		Use:     "run MODEL [PROMPT]",
		Short:   "Run a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    RunHandler,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
810
811
	}

812
	runCmd.Flags().Bool("verbose", false, "Show timings for response")
813
	runCmd.Flags().Bool("insecure", false, "Use an insecure registry")
814
	runCmd.Flags().Bool("nowordwrap", false, "Don't wrap words to the next line automatically")
Jeffrey Morgan's avatar
Jeffrey Morgan committed
815
	runCmd.Flags().String("format", "", "Response format (e.g. json)")
816

Jeffrey Morgan's avatar
Jeffrey Morgan committed
817
818
819
820
	serveCmd := &cobra.Command{
		Use:     "serve",
		Aliases: []string{"start"},
		Short:   "Start ollama",
Michael Yang's avatar
Michael Yang committed
821
		Args:    cobra.ExactArgs(0),
Michael Yang's avatar
Michael Yang committed
822
		RunE:    RunServer,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
823
824
	}

825
	pullCmd := &cobra.Command{
826
827
		Use:     "pull MODEL",
		Short:   "Pull a model from a registry",
Michael Yang's avatar
Michael Yang committed
828
		Args:    cobra.ExactArgs(1),
829
830
		PreRunE: checkServerHeartbeat,
		RunE:    PullHandler,
831
832
	}

833
834
	pullCmd.Flags().Bool("insecure", false, "Use an insecure registry")

835
	pushCmd := &cobra.Command{
836
837
		Use:     "push MODEL",
		Short:   "Push a model to a registry",
Michael Yang's avatar
Michael Yang committed
838
		Args:    cobra.ExactArgs(1),
839
840
		PreRunE: checkServerHeartbeat,
		RunE:    PushHandler,
841
842
	}

843
844
	pushCmd.Flags().Bool("insecure", false, "Use an insecure registry")

Patrick Devine's avatar
Patrick Devine committed
845
	listCmd := &cobra.Command{
846
		Use:     "list",
Patrick Devine's avatar
Patrick Devine committed
847
		Aliases: []string{"ls"},
848
		Short:   "List models",
849
		PreRunE: checkServerHeartbeat,
850
		RunE:    ListHandler,
851
852
	}

Patrick Devine's avatar
Patrick Devine committed
853
	copyCmd := &cobra.Command{
Michael Yang's avatar
Michael Yang committed
854
		Use:     "cp SOURCE TARGET",
855
		Short:   "Copy a model",
Michael Yang's avatar
Michael Yang committed
856
		Args:    cobra.ExactArgs(2),
857
858
		PreRunE: checkServerHeartbeat,
		RunE:    CopyHandler,
Patrick Devine's avatar
Patrick Devine committed
859
860
	}

861
	deleteCmd := &cobra.Command{
Michael Yang's avatar
Michael Yang committed
862
		Use:     "rm MODEL [MODEL...]",
863
864
865
866
		Short:   "Remove a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    DeleteHandler,
Patrick Devine's avatar
Patrick Devine committed
867
868
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
869
870
	rootCmd.AddCommand(
		serveCmd,
871
		createCmd,
Patrick Devine's avatar
Patrick Devine committed
872
		showCmd,
873
		runCmd,
874
875
		pullCmd,
		pushCmd,
Patrick Devine's avatar
Patrick Devine committed
876
		listCmd,
Patrick Devine's avatar
Patrick Devine committed
877
		copyCmd,
878
		deleteCmd,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
879
880
881
882
	)

	return rootCmd
}