cmd.go 20.1 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/signal"
18
	"path/filepath"
19
	"runtime"
Michael Yang's avatar
Michael Yang committed
20
	"strings"
21
	"syscall"
Michael Yang's avatar
Michael Yang committed
22
	"time"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
23

24
25
	"github.com/containerd/console"

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

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

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
	name := args[0]
151

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

		show, err = client.Show(cmd.Context(), &api.ShowRequest{Name: name})
		if err != nil {
			return err
		}
Michael Yang's avatar
Michael Yang committed
165
166
	case err != nil:
		return err
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
212
	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
213
214
}

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

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

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

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

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

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

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

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

257
258
259
		return nil
	}

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

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

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

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

	var data [][]string

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

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

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

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

Patrick Devine's avatar
Patrick Devine committed
318
func ShowHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
319
	client, err := api.ClientFromEnvironment()
Patrick Devine's avatar
Patrick Devine committed
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
368
	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 {
369
		return errors.New("only one of '--license', '--modelfile', '--parameters', '--system', or '--template' can be specified")
Patrick Devine's avatar
Patrick Devine committed
370
	} else if flagsSet == 0 {
371
		return errors.New("one of '--license', '--modelfile', '--parameters', '--system', or '--template' must be specified")
Patrick Devine's avatar
Patrick Devine committed
372
373
	}

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

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

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

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

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

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

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

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

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

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

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

453
454
		return nil
	}
455

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

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

464
465
type generateContextKey string

466
type runOptions struct {
467
468
469
470
471
472
473
474
475
476
477
	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
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
596
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
597
	client, err := api.ClientFromEnvironment()
Patrick Devine's avatar
Patrick Devine committed
598
	if err != nil {
599
		return err
Patrick Devine's avatar
Patrick Devine committed
600
	}
Michael Yang's avatar
Michael Yang committed
601

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

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

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

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

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

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

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

626
	var state *displayResponseState = &displayResponseState{}
627

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

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

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

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

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

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

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

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

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

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

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

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

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

688
func RunServer(cmd *cobra.Command, _ []string) error {
689
	host, port, err := net.SplitHostPort(strings.Trim(os.Getenv("OLLAMA_HOST"), "\"'"))
Michael Yang's avatar
Michael Yang committed
690
691
	if err != nil {
		host, port = "127.0.0.1", "11434"
Michael Yang's avatar
Michael Yang committed
692
		if ip := net.ParseIP(strings.Trim(os.Getenv("OLLAMA_HOST"), "[]")); ip != nil {
Michael Yang's avatar
Michael Yang committed
693
694
			host = ip.String()
		}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
695
	}
696

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

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

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

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

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

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

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

748
		err = os.WriteFile(pubKeyPath, pubKeyData, 0o644)
749
750
751
752
753
754
755
756
757
		if err != nil {
			return err
		}

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

758
759
//nolint:unused
func waitForServer(ctx context.Context, client *api.Client) error {
Bruce MacDonald's avatar
Bruce MacDonald committed
760
761
762
763
764
765
766
767
	// 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
768
			if err := client.Heartbeat(ctx); err == nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
769
770
771
772
				return nil // server has started
			}
		}
	}
773

Bruce MacDonald's avatar
Bruce MacDonald committed
774
775
}

Michael Yang's avatar
Michael Yang committed
776
func checkServerHeartbeat(cmd *cobra.Command, _ []string) error {
Michael Yang's avatar
Michael Yang committed
777
	client, err := api.ClientFromEnvironment()
778
779
780
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
781
	if err := client.Heartbeat(cmd.Context()); err != nil {
782
		if !strings.Contains(err.Error(), " refused") {
Bruce MacDonald's avatar
Bruce MacDonald committed
783
784
			return err
		}
785
786
		if err := startApp(cmd.Context(), client); err != nil {
			return fmt.Errorf("could not connect to ollama app, is it running?")
787
788
789
790
791
		}
	}
	return nil
}

Michael Yang's avatar
Michael Yang committed
792
793
794
795
796
797
798
799
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
800
801
802
803
804
		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
805
806
	}

807
	if serverVersion != version.Version {
Michael Yang's avatar
Michael Yang committed
808
		fmt.Printf("Warning: client version is %s\n", version.Version)
809
	}
Michael Yang's avatar
Michael Yang committed
810
811
}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
812
813
func NewCLI() *cobra.Command {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
Michael Yang's avatar
Michael Yang committed
814
	cobra.EnableCommandSorting = false
Jeffrey Morgan's avatar
Jeffrey Morgan committed
815

816
817
	if runtime.GOOS == "windows" {
		// Enable colorful ANSI escape code in Windows terminal (disabled by default)
818
		console.ConsoleFromFile(os.Stdout) //nolint:errcheck
819
820
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
821
	rootCmd := &cobra.Command{
822
823
824
825
		Use:           "ollama",
		Short:         "Large language model runner",
		SilenceUsage:  true,
		SilenceErrors: true,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
826
827
828
		CompletionOptions: cobra.CompletionOptions{
			DisableDefaultCmd: true,
		},
Michael Yang's avatar
Michael Yang committed
829
830
831
832
833
834
835
836
		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
837
838
	}

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

841
	createCmd := &cobra.Command{
842
843
		Use:     "create MODEL",
		Short:   "Create a model from a Modelfile",
Michael Yang's avatar
Michael Yang committed
844
		Args:    cobra.ExactArgs(1),
845
846
		PreRunE: checkServerHeartbeat,
		RunE:    CreateHandler,
847
848
849
850
	}

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

Patrick Devine's avatar
Patrick Devine committed
851
852
853
	showCmd := &cobra.Command{
		Use:     "show MODEL",
		Short:   "Show information for a model",
Michael Yang's avatar
Michael Yang committed
854
		Args:    cobra.ExactArgs(1),
Patrick Devine's avatar
Patrick Devine committed
855
856
857
858
859
860
861
862
		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")
863
	showCmd.Flags().Bool("system", false, "Show system message of a model")
Patrick Devine's avatar
Patrick Devine committed
864

Jeffrey Morgan's avatar
Jeffrey Morgan committed
865
	runCmd := &cobra.Command{
866
867
868
869
870
		Use:     "run MODEL [PROMPT]",
		Short:   "Run a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    RunHandler,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
871
872
	}

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

Jeffrey Morgan's avatar
Jeffrey Morgan committed
878
879
880
881
	serveCmd := &cobra.Command{
		Use:     "serve",
		Aliases: []string{"start"},
		Short:   "Start ollama",
Michael Yang's avatar
Michael Yang committed
882
		Args:    cobra.ExactArgs(0),
Michael Yang's avatar
Michael Yang committed
883
		RunE:    RunServer,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
884
885
	}

886
	pullCmd := &cobra.Command{
887
888
		Use:     "pull MODEL",
		Short:   "Pull a model from a registry",
Michael Yang's avatar
Michael Yang committed
889
		Args:    cobra.ExactArgs(1),
890
891
		PreRunE: checkServerHeartbeat,
		RunE:    PullHandler,
892
893
	}

894
895
	pullCmd.Flags().Bool("insecure", false, "Use an insecure registry")

896
	pushCmd := &cobra.Command{
897
898
		Use:     "push MODEL",
		Short:   "Push a model to a registry",
Michael Yang's avatar
Michael Yang committed
899
		Args:    cobra.ExactArgs(1),
900
901
		PreRunE: checkServerHeartbeat,
		RunE:    PushHandler,
902
903
	}

904
905
	pushCmd.Flags().Bool("insecure", false, "Use an insecure registry")

Patrick Devine's avatar
Patrick Devine committed
906
	listCmd := &cobra.Command{
907
		Use:     "list",
Patrick Devine's avatar
Patrick Devine committed
908
		Aliases: []string{"ls"},
909
		Short:   "List models",
910
		PreRunE: checkServerHeartbeat,
911
		RunE:    ListHandler,
912
913
	}

Patrick Devine's avatar
Patrick Devine committed
914
	copyCmd := &cobra.Command{
Michael Yang's avatar
Michael Yang committed
915
		Use:     "cp SOURCE TARGET",
916
		Short:   "Copy a model",
Michael Yang's avatar
Michael Yang committed
917
		Args:    cobra.ExactArgs(2),
918
919
		PreRunE: checkServerHeartbeat,
		RunE:    CopyHandler,
Patrick Devine's avatar
Patrick Devine committed
920
921
	}

922
	deleteCmd := &cobra.Command{
Michael Yang's avatar
Michael Yang committed
923
		Use:     "rm MODEL [MODEL...]",
924
925
926
927
		Short:   "Remove a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    DeleteHandler,
Patrick Devine's avatar
Patrick Devine committed
928
929
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
930
931
	rootCmd.AddCommand(
		serveCmd,
932
		createCmd,
Patrick Devine's avatar
Patrick Devine committed
933
		showCmd,
934
		runCmd,
935
936
		pullCmd,
		pushCmd,
Patrick Devine's avatar
Patrick Devine committed
937
		listCmd,
Patrick Devine's avatar
Patrick Devine committed
938
		copyCmd,
939
		deleteCmd,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
940
941
942
943
	)

	return rootCmd
}