cmd.go 20.4 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/exp/slices"
29
	"golang.org/x/term"
Michael Yang's avatar
Michael Yang committed
30

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

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

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

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

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

Michael Yang's avatar
Michael Yang committed
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
	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
	}

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

Michael Yang's avatar
Michael Yang committed
75
76
77
78
79
80
81
82
83
84
	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:])
			}

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

Michael Yang's avatar
Michael Yang committed
89
90
			bin, err := os.Open(path)
			if errors.Is(err, os.ErrNotExist) && c.Name == "model" {
Michael Yang's avatar
Michael Yang committed
91
				continue
Michael Yang's avatar
Michael Yang committed
92
93
94
95
96
97
98
99
100
101
102
103
			} 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
104
			if err = client.CreateBlob(cmd.Context(), digest, bin); err != nil {
Michael Yang's avatar
Michael Yang committed
105
106
107
				return err
			}

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

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

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

132
133
134
		return nil
	}

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

	return nil
}

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

149
	name := args[0]
150

151
	// check if the model exists on the server
152
	show, 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
		}
159
160
161
162
163

		show, err = client.Show(cmd.Context(), &api.ShowRequest{Name: name})
		if err != nil {
			return err
		}
Michael Yang's avatar
Michael Yang committed
164
165
	case err != nil:
		return err
166
167
	}

168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
	interactive := true

	opts := runOptions{
		Model:       args[0],
		WordWrap:    os.Getenv("TERM") == "xterm-256color",
		Options:     map[string]interface{}{},
		MultiModal:  slices.Contains(show.Details.Families, "clip"),
		ParentModel: show.Details.ParentModel,
	}

	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)
Bruce MacDonald's avatar
Bruce MacDonald committed
212
213
}

214
func PushHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
215
	client, err := api.ClientFromEnvironment()
216
217
218
	if err != nil {
		return err
	}
219

220
221
222
223
224
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
225
226
227
228
	p := progress.NewProgress(os.Stderr)
	defer p.Stop()

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

232
	fn := func(resp api.ProgressResponse) error {
Michael Yang's avatar
Michael Yang committed
233
		if resp.Digest != "" {
234
235
236
			if spinner != nil {
				spinner.Stop()
			}
Michael Yang's avatar
Michael Yang committed
237
238
239

			bar, ok := bars[resp.Digest]
			if !ok {
240
				bar = progress.NewBar(fmt.Sprintf("pushing %s...", resp.Digest[7:19]), resp.Total, resp.Completed)
Michael Yang's avatar
Michael Yang committed
241
242
243
244
245
246
				bars[resp.Digest] = bar
				p.Add(resp.Digest, bar)
			}

			bar.Set(resp.Completed)
		} else if status != resp.Status {
247
248
249
			if spinner != nil {
				spinner.Stop()
			}
Michael Yang's avatar
Michael Yang committed
250
251
252
253
254
255

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

256
257
258
		return nil
	}

Michael Yang's avatar
Michael Yang committed
259
	request := api.PushRequest{Name: args[0], Insecure: insecure}
Michael Yang's avatar
Michael Yang committed
260
	if err := client.Push(cmd.Context(), &request, fn); err != nil {
Michael Yang's avatar
Michael Yang committed
261
262
263
		return err
	}

264
	spinner.Stop()
Michael Yang's avatar
Michael Yang committed
265
	return nil
266
267
}

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

Michael Yang's avatar
Michael Yang committed
274
	models, err := client.List(cmd.Context())
Patrick Devine's avatar
Patrick Devine committed
275
276
277
278
279
280
281
	if err != nil {
		return err
	}

	var data [][]string

	for _, m := range models.Models {
Michael Yang's avatar
Michael Yang committed
282
		if len(args) == 0 || strings.HasPrefix(m.Name, args[0]) {
283
			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
284
		}
Patrick Devine's avatar
Patrick Devine committed
285
286
287
	}

	table := tablewriter.NewWriter(os.Stdout)
Patrick Devine's avatar
Patrick Devine committed
288
	table.SetHeader([]string{"NAME", "ID", "SIZE", "MODIFIED"})
Patrick Devine's avatar
Patrick Devine committed
289
290
291
292
293
294
295
296
297
298
299
300
	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
}

301
func DeleteHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
302
	client, err := api.ClientFromEnvironment()
303
304
305
	if err != nil {
		return err
	}
306

307
308
	for _, name := range args {
		req := api.DeleteRequest{Name: name}
Michael Yang's avatar
Michael Yang committed
309
		if err := client.Delete(cmd.Context(), &req); err != nil {
310
311
312
			return err
		}
		fmt.Printf("deleted '%s'\n", name)
313
314
315
316
	}
	return nil
}

Patrick Devine's avatar
Patrick Devine committed
317
func ShowHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
318
	client, err := api.ClientFromEnvironment()
Patrick Devine's avatar
Patrick Devine committed
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
	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 {
368
		return errors.New("only one of '--license', '--modelfile', '--parameters', '--system', or '--template' can be specified")
Patrick Devine's avatar
Patrick Devine committed
369
	} else if flagsSet == 0 {
370
		return errors.New("one of '--license', '--modelfile', '--parameters', '--system', or '--template' must be specified")
Patrick Devine's avatar
Patrick Devine committed
371
372
	}

373
	req := api.ShowRequest{Name: args[0]}
Michael Yang's avatar
Michael Yang committed
374
	resp, err := client.Show(cmd.Context(), &req)
Patrick Devine's avatar
Patrick Devine committed
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
	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
395
func CopyHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
396
	client, err := api.ClientFromEnvironment()
397
398
399
	if err != nil {
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
400
401

	req := api.CopyRequest{Source: args[0], Destination: args[1]}
Michael Yang's avatar
Michael Yang committed
402
	if err := client.Copy(cmd.Context(), &req); err != nil {
Patrick Devine's avatar
Patrick Devine committed
403
404
405
406
407
408
		return err
	}
	fmt.Printf("copied '%s' to '%s'\n", args[0], args[1])
	return nil
}

409
func PullHandler(cmd *cobra.Command, args []string) error {
410
411
412
413
414
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
415
	client, err := api.ClientFromEnvironment()
416
417
418
	if err != nil {
		return err
	}
419

Michael Yang's avatar
Michael Yang committed
420
421
422
423
424
	p := progress.NewProgress(os.Stderr)
	defer p.Stop()

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

425
426
	var status string
	var spinner *progress.Spinner
Michael Yang's avatar
Michael Yang committed
427

428
	fn := func(resp api.ProgressResponse) error {
Michael Yang's avatar
Michael Yang committed
429
		if resp.Digest != "" {
430
431
432
			if spinner != nil {
				spinner.Stop()
			}
Michael Yang's avatar
Michael Yang committed
433
434
435

			bar, ok := bars[resp.Digest]
			if !ok {
436
				bar = progress.NewBar(fmt.Sprintf("pulling %s...", resp.Digest[7:19]), resp.Total, resp.Completed)
Michael Yang's avatar
Michael Yang committed
437
438
439
440
441
442
				bars[resp.Digest] = bar
				p.Add(resp.Digest, bar)
			}

			bar.Set(resp.Completed)
		} else if status != resp.Status {
443
444
445
			if spinner != nil {
				spinner.Stop()
			}
Michael Yang's avatar
Michael Yang committed
446
447
448
449
450
451

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

452
453
		return nil
	}
454

Michael Yang's avatar
Michael Yang committed
455
	request := api.PullRequest{Name: args[0], Insecure: insecure}
Michael Yang's avatar
Michael Yang committed
456
	if err := client.Pull(cmd.Context(), &request, fn); err != nil {
Michael Yang's avatar
Michael Yang committed
457
458
459
460
		return err
	}

	return nil
Michael Yang's avatar
Michael Yang committed
461
462
}

463
464
type generateContextKey string

465
type runOptions struct {
466
467
468
469
470
471
472
473
474
475
476
	Model       string
	ParentModel string
	Prompt      string
	Messages    []api.Message
	WordWrap    bool
	Format      string
	System      string
	Template    string
	Images      []api.ImageData
	Options     map[string]interface{}
	MultiModal  bool
477
478
}

479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
type displayResponseState struct {
	lineLength int
	wordBuffer string
}

func displayResponse(content string, wordWrap bool, state *displayResponseState) {
	termWidth, _, _ := term.GetSize(int(os.Stdout.Fd()))
	if wordWrap && termWidth >= 10 {
		for _, ch := range content {
			if state.lineLength+1 > termWidth-5 {
				if len(state.wordBuffer) > termWidth-10 {
					fmt.Printf("%s%c", state.wordBuffer, ch)
					state.wordBuffer = ""
					state.lineLength = 0
					continue
				}

				// backtrack the length of the last word and clear to the end of the line
				fmt.Printf("\x1b[%dD\x1b[K\n", len(state.wordBuffer))
				fmt.Printf("%s%c", state.wordBuffer, ch)
				state.lineLength = len(state.wordBuffer) + 1
			} else {
				fmt.Print(string(ch))
				state.lineLength += 1

				switch ch {
				case ' ':
					state.wordBuffer = ""
				case '\n':
					state.lineLength = 0
				default:
					state.wordBuffer += string(ch)
				}
			}
		}
	} else {
		fmt.Printf("%s%s", state.wordBuffer, content)
		if len(state.wordBuffer) > 0 {
			state.wordBuffer = ""
		}
	}
}

func chat(cmd *cobra.Command, opts runOptions) (*api.Message, error) {
	client, err := api.ClientFromEnvironment()
	if err != nil {
		return nil, err
	}

	p := progress.NewProgress(os.Stderr)
	defer p.StopAndClear()

	spinner := progress.NewSpinner("")
	p.Add("", spinner)

	cancelCtx, cancel := context.WithCancel(cmd.Context())
	defer cancel()

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

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

	var state *displayResponseState = &displayResponseState{}
	var latest api.ChatResponse
	var fullResponse strings.Builder
	var role string

	fn := func(response api.ChatResponse) error {
		p.StopAndClear()

		latest = response

		role = response.Message.Role
		content := response.Message.Content
		fullResponse.WriteString(content)

		displayResponse(content, opts.WordWrap, state)

		return nil
	}

	req := &api.ChatRequest{
		Model:    opts.Model,
		Messages: opts.Messages,
		Format:   opts.Format,
		Options:  opts.Options,
	}

	if err := client.Chat(cancelCtx, req, fn); err != nil {
		if errors.Is(err, context.Canceled) {
			return nil, nil
		}
		return nil, err
	}

	if len(opts.Messages) > 0 {
		fmt.Println()
		fmt.Println()
	}

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

	if verbose {
		latest.Summary()
	}

	return &api.Message{Role: role, Content: fullResponse.String()}, nil
}

func generate(cmd *cobra.Command, opts runOptions) error {
Michael Yang's avatar
Michael Yang committed
596
	client, err := api.ClientFromEnvironment()
Patrick Devine's avatar
Patrick Devine committed
597
	if err != nil {
598
		return err
Patrick Devine's avatar
Patrick Devine committed
599
	}
Michael Yang's avatar
Michael Yang committed
600

Michael Yang's avatar
Michael Yang committed
601
	p := progress.NewProgress(os.Stderr)
602
	defer p.StopAndClear()
603

Michael Yang's avatar
Michael Yang committed
604
605
606
	spinner := progress.NewSpinner("")
	p.Add("", spinner)

607
608
609
610
611
612
613
	var latest api.GenerateResponse

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

Michael Yang's avatar
Michael Yang committed
614
	ctx, cancel := context.WithCancel(cmd.Context())
615
616
617
618
619
620
621
622
623
624
	defer cancel()

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

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

625
	var state *displayResponseState = &displayResponseState{}
626

627
	fn := func(response api.GenerateResponse) error {
Michael Yang's avatar
Michael Yang committed
628
		p.StopAndClear()
629

Patrick Devine's avatar
Patrick Devine committed
630
		latest = response
631
		content := response.Response
632

633
		displayResponse(content, opts.WordWrap, state)
634

Patrick Devine's avatar
Patrick Devine committed
635
636
		return nil
	}
637

638
639
640
641
642
643
644
	if opts.MultiModal {
		opts.Prompt, opts.Images, err = extractFileData(opts.Prompt)
		if err != nil {
			return err
		}
	}

Michael Yang's avatar
Michael Yang committed
645
646
647
648
	request := api.GenerateRequest{
		Model:    opts.Model,
		Prompt:   opts.Prompt,
		Context:  generateContext,
649
		Images:   opts.Images,
Michael Yang's avatar
Michael Yang committed
650
651
652
653
654
655
656
		Format:   opts.Format,
		System:   opts.System,
		Template: opts.Template,
		Options:  opts.Options,
	}

	if err := client.Generate(ctx, &request, fn); err != nil {
657
		if errors.Is(err, context.Canceled) {
658
			return nil
659
		}
660
		return err
Patrick Devine's avatar
Patrick Devine committed
661
	}
662

663
	if opts.Prompt != "" {
Michael Yang's avatar
Michael Yang committed
664
665
		fmt.Println()
		fmt.Println()
Patrick Devine's avatar
Patrick Devine committed
666
	}
667

668
669
670
671
	if !latest.Done {
		return nil
	}

Patrick Devine's avatar
Patrick Devine committed
672
673
	verbose, err := cmd.Flags().GetBool("verbose")
	if err != nil {
674
		return err
Patrick Devine's avatar
Patrick Devine committed
675
	}
Michael Yang's avatar
Michael Yang committed
676

Patrick Devine's avatar
Patrick Devine committed
677
678
	if verbose {
		latest.Summary()
Michael Yang's avatar
Michael Yang committed
679
	}
Michael Yang's avatar
Michael Yang committed
680

Patrick Devine's avatar
Patrick Devine committed
681
682
683
	ctx = context.WithValue(cmd.Context(), generateContextKey("context"), latest.Context)
	cmd.SetContext(ctx)

684
	return nil
Michael Yang's avatar
Michael Yang committed
685
686
}

687
func RunServer(cmd *cobra.Command, _ []string) error {
Michael Yang's avatar
Michael Yang committed
688
689
690
	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
691
		if ip := net.ParseIP(strings.Trim(os.Getenv("OLLAMA_HOST"), "[]")); ip != nil {
Michael Yang's avatar
Michael Yang committed
692
693
			host = ip.String()
		}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
694
	}
695

Michael Yang's avatar
Michael Yang committed
696
	if err := initializeKeypair(); err != nil {
697
698
699
		return err
	}

Michael Yang's avatar
Michael Yang committed
700
	ln, err := net.Listen("tcp", net.JoinHostPort(host, port))
701
702
703
	if err != nil {
		return err
	}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
704

705
	return server.Serve(ln)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
706
707
}

708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
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
730
		err = os.MkdirAll(filepath.Dir(privKeyPath), 0o755)
731
732
733
734
		if err != nil {
			return fmt.Errorf("could not create directory %w", err)
		}

735
		err = os.WriteFile(privKeyPath, pem.EncodeToMemory(privKeyBytes), 0o600)
736
737
738
739
740
741
742
743
744
745
746
		if err != nil {
			return err
		}

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

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

747
		err = os.WriteFile(pubKeyPath, pubKeyData, 0o644)
748
749
750
751
752
753
754
755
756
		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
757
func startMacApp(ctx context.Context, client *api.Client) error {
Bruce MacDonald's avatar
Bruce MacDonald committed
758
759
760
761
762
	exe, err := os.Executable()
	if err != nil {
		return err
	}
	link, err := os.Readlink(exe)
Bruce MacDonald's avatar
Bruce MacDonald committed
763
764
765
	if err != nil {
		return err
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
766
767
768
	if !strings.Contains(link, "Ollama.app") {
		return fmt.Errorf("could not find ollama app")
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
769
770
771
772
773
774
775
776
777
778
779
780
	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
781
			if err := client.Heartbeat(ctx); err == nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
782
783
784
785
786
787
				return nil // server has started
			}
		}
	}
}

Michael Yang's avatar
Michael Yang committed
788
func checkServerHeartbeat(cmd *cobra.Command, _ []string) error {
Michael Yang's avatar
Michael Yang committed
789
	client, err := api.ClientFromEnvironment()
790
791
792
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
793
	if err := client.Heartbeat(cmd.Context()); err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
794
795
796
797
		if !strings.Contains(err.Error(), "connection refused") {
			return err
		}
		if runtime.GOOS == "darwin" {
Michael Yang's avatar
Michael Yang committed
798
			if err := startMacApp(cmd.Context(), client); err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
799
				return fmt.Errorf("could not connect to ollama app, is it running?")
800
			}
Bruce MacDonald's avatar
Bruce MacDonald committed
801
		} else {
802
803
804
805
806
807
			return fmt.Errorf("could not connect to ollama server, run 'ollama serve' to start it")
		}
	}
	return nil
}

Michael Yang's avatar
Michael Yang committed
808
809
810
811
812
813
814
815
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
816
817
818
819
820
		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
821
822
	}

823
	if serverVersion != version.Version {
Michael Yang's avatar
Michael Yang committed
824
		fmt.Printf("Warning: client version is %s\n", version.Version)
825
	}
Michael Yang's avatar
Michael Yang committed
826
827
}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
828
829
func NewCLI() *cobra.Command {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
Michael Yang's avatar
Michael Yang committed
830
	cobra.EnableCommandSorting = false
Jeffrey Morgan's avatar
Jeffrey Morgan committed
831
832

	rootCmd := &cobra.Command{
833
834
835
836
		Use:           "ollama",
		Short:         "Large language model runner",
		SilenceUsage:  true,
		SilenceErrors: true,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
837
838
839
		CompletionOptions: cobra.CompletionOptions{
			DisableDefaultCmd: true,
		},
Michael Yang's avatar
Michael Yang committed
840
841
842
843
844
845
846
847
		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
848
849
	}

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

852
	createCmd := &cobra.Command{
853
854
		Use:     "create MODEL",
		Short:   "Create a model from a Modelfile",
Michael Yang's avatar
Michael Yang committed
855
		Args:    cobra.ExactArgs(1),
856
857
		PreRunE: checkServerHeartbeat,
		RunE:    CreateHandler,
858
859
860
861
	}

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

Patrick Devine's avatar
Patrick Devine committed
862
863
864
	showCmd := &cobra.Command{
		Use:     "show MODEL",
		Short:   "Show information for a model",
Michael Yang's avatar
Michael Yang committed
865
		Args:    cobra.ExactArgs(1),
Patrick Devine's avatar
Patrick Devine committed
866
867
868
869
870
871
872
873
		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")
874
	showCmd.Flags().Bool("system", false, "Show system message of a model")
Patrick Devine's avatar
Patrick Devine committed
875

Jeffrey Morgan's avatar
Jeffrey Morgan committed
876
	runCmd := &cobra.Command{
877
878
879
880
881
		Use:     "run MODEL [PROMPT]",
		Short:   "Run a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    RunHandler,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
882
883
	}

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

Jeffrey Morgan's avatar
Jeffrey Morgan committed
889
890
891
892
	serveCmd := &cobra.Command{
		Use:     "serve",
		Aliases: []string{"start"},
		Short:   "Start ollama",
Michael Yang's avatar
Michael Yang committed
893
		Args:    cobra.ExactArgs(0),
Michael Yang's avatar
Michael Yang committed
894
		RunE:    RunServer,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
895
896
	}

897
	pullCmd := &cobra.Command{
898
899
		Use:     "pull MODEL",
		Short:   "Pull a model from a registry",
Michael Yang's avatar
Michael Yang committed
900
		Args:    cobra.ExactArgs(1),
901
902
		PreRunE: checkServerHeartbeat,
		RunE:    PullHandler,
903
904
	}

905
906
	pullCmd.Flags().Bool("insecure", false, "Use an insecure registry")

907
	pushCmd := &cobra.Command{
908
909
		Use:     "push MODEL",
		Short:   "Push a model to a registry",
Michael Yang's avatar
Michael Yang committed
910
		Args:    cobra.ExactArgs(1),
911
912
		PreRunE: checkServerHeartbeat,
		RunE:    PushHandler,
913
914
	}

915
916
	pushCmd.Flags().Bool("insecure", false, "Use an insecure registry")

Patrick Devine's avatar
Patrick Devine committed
917
	listCmd := &cobra.Command{
918
		Use:     "list",
Patrick Devine's avatar
Patrick Devine committed
919
		Aliases: []string{"ls"},
920
		Short:   "List models",
921
		PreRunE: checkServerHeartbeat,
922
		RunE:    ListHandler,
923
924
	}

Patrick Devine's avatar
Patrick Devine committed
925
	copyCmd := &cobra.Command{
Michael Yang's avatar
Michael Yang committed
926
		Use:     "cp SOURCE TARGET",
927
		Short:   "Copy a model",
Michael Yang's avatar
Michael Yang committed
928
		Args:    cobra.ExactArgs(2),
929
930
		PreRunE: checkServerHeartbeat,
		RunE:    CopyHandler,
Patrick Devine's avatar
Patrick Devine committed
931
932
	}

933
	deleteCmd := &cobra.Command{
Michael Yang's avatar
Michael Yang committed
934
		Use:     "rm MODEL [MODEL...]",
935
936
937
938
		Short:   "Remove a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    DeleteHandler,
Patrick Devine's avatar
Patrick Devine committed
939
940
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
941
942
	rootCmd.AddCommand(
		serveCmd,
943
		createCmd,
Patrick Devine's avatar
Patrick Devine committed
944
		showCmd,
945
		runCmd,
946
947
		pullCmd,
		pushCmd,
Patrick Devine's avatar
Patrick Devine committed
948
		listCmd,
Patrick Devine's avatar
Patrick Devine committed
949
		copyCmd,
950
		deleteCmd,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
951
952
953
954
	)

	return rootCmd
}