cmd.go 19.3 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{Name: 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
	default:
		// the model was found, check if it is in the correct format
		if model.Details.Format != "" && 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
			}
174
175
176
		}
	}

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

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

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

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

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

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

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

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

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

222
223
224
		return nil
	}

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

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

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

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

	var data [][]string

	for _, m := range models.Models {
Michael Yang's avatar
Michael Yang committed
248
		if len(args) == 0 || strings.HasPrefix(m.Name, args[0]) {
249
			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
250
		}
Patrick Devine's avatar
Patrick Devine committed
251
252
253
	}

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

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

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

Patrick Devine's avatar
Patrick Devine committed
283
func ShowHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
284
	client, err := api.ClientFromEnvironment()
Patrick Devine's avatar
Patrick Devine committed
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
333
	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 {
334
		return errors.New("only one of '--license', '--modelfile', '--parameters', '--system', or '--template' can be specified")
Patrick Devine's avatar
Patrick Devine committed
335
	} else if flagsSet == 0 {
336
		return errors.New("one of '--license', '--modelfile', '--parameters', '--system', or '--template' must be specified")
Patrick Devine's avatar
Patrick Devine committed
337
338
	}

339
	req := api.ShowRequest{Name: args[0]}
Michael Yang's avatar
Michael Yang committed
340
	resp, err := client.Show(cmd.Context(), &req)
Patrick Devine's avatar
Patrick Devine committed
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
	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
361
func CopyHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
362
	client, err := api.ClientFromEnvironment()
363
364
365
	if err != nil {
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
366
367

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

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

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

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

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

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

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

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

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

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

418
419
		return nil
	}
420

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

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

429
430
431
432
433
434
435
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
436
		Images:   []ImageData{},
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
477
	}

	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 {
478
	Model    string
479
	Prompt   string
480
481
	WordWrap bool
	Format   string
482
	System   string
483
	Template string
Patrick Devine's avatar
Patrick Devine committed
484
	Images   []ImageData
485
486
487
	Options  map[string]interface{}
}

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

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

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

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

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

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

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

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

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

523
524
525
	var currentLineLength int
	var wordBuffer string

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

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

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

542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
					// 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 {
561
			fmt.Printf("%s%s", wordBuffer, response.Response)
562
563
564
			if len(wordBuffer) > 0 {
				wordBuffer = ""
			}
565
566
		}

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

Patrick Devine's avatar
Patrick Devine committed
570
571
572
573
	images := make([]api.ImageData, 0)
	for _, i := range opts.Images {
		images = append(images, api.ImageData(i))
	}
Michael Yang's avatar
Michael Yang committed
574
575
576
577
578
579
580
581
	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
582
		Images:   images,
Michael Yang's avatar
Michael Yang committed
583
584
585
	}

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

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

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

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

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

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

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

616
func RunServer(cmd *cobra.Command, _ []string) error {
Michael Yang's avatar
Michael Yang committed
617
618
619
	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
620
		if ip := net.ParseIP(strings.Trim(os.Getenv("OLLAMA_HOST"), "[]")); ip != nil {
Michael Yang's avatar
Michael Yang committed
621
622
			host = ip.String()
		}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
623
	}
624

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

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

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

637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
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
659
		err = os.MkdirAll(filepath.Dir(privKeyPath), 0o755)
660
661
662
663
		if err != nil {
			return fmt.Errorf("could not create directory %w", err)
		}

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

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

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

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

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

Michael Yang's avatar
Michael Yang committed
737
738
739
740
741
742
743
744
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
745
746
747
748
749
		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
750
751
	}

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

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

	rootCmd := &cobra.Command{
762
763
764
765
		Use:           "ollama",
		Short:         "Large language model runner",
		SilenceUsage:  true,
		SilenceErrors: true,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
766
767
768
		CompletionOptions: cobra.CompletionOptions{
			DisableDefaultCmd: true,
		},
Michael Yang's avatar
Michael Yang committed
769
770
771
772
773
774
775
776
		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
777
778
	}

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

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

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

Patrick Devine's avatar
Patrick Devine committed
791
792
793
	showCmd := &cobra.Command{
		Use:     "show MODEL",
		Short:   "Show information for a model",
Michael Yang's avatar
Michael Yang committed
794
		Args:    cobra.ExactArgs(1),
Patrick Devine's avatar
Patrick Devine committed
795
796
797
798
799
800
801
802
		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")
803
	showCmd.Flags().Bool("system", false, "Show system message of a model")
Patrick Devine's avatar
Patrick Devine committed
804

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

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

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

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

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

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

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

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

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

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

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

	return rootCmd
}