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

import (
Michael Yang's avatar
Michael Yang committed
4
	"bytes"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
5
	"context"
6
7
	"crypto/ed25519"
	"crypto/rand"
Michael Yang's avatar
Michael Yang committed
8
	"crypto/sha256"
9
	"encoding/pem"
Michael Yang's avatar
Michael Yang committed
10
	"errors"
Bruce MacDonald's avatar
Bruce MacDonald committed
11
	"fmt"
Michael Yang's avatar
Michael Yang committed
12
	"io"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
13
14
	"log"
	"net"
15
	"net/http"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
16
	"os"
17
	"os/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
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)
Michael Yang's avatar
Michael Yang committed
721
		cryptoPublicKey, cryptoPrivateKey, err := ed25519.GenerateKey(rand.Reader)
722
723
724
725
		if err != nil {
			return err
		}

Michael Yang's avatar
Michael Yang committed
726
		privateKeyBytes, err := ssh.MarshalPrivateKey(cryptoPrivateKey, "")
727
728
729
730
		if err != nil {
			return err
		}

Michael Yang's avatar
Michael Yang committed
731
		if err := os.MkdirAll(filepath.Dir(privKeyPath), 0o755); err != nil {
732
733
734
			return fmt.Errorf("could not create directory %w", err)
		}

Michael Yang's avatar
Michael Yang committed
735
		if err := os.WriteFile(privKeyPath, pem.EncodeToMemory(privateKeyBytes), 0o600); err != nil {
736
737
738
			return err
		}

Michael Yang's avatar
Michael Yang committed
739
		sshPublicKey, err := ssh.NewPublicKey(cryptoPublicKey)
740
741
742
743
		if err != nil {
			return err
		}

Michael Yang's avatar
Michael Yang committed
744
		publicKeyBytes := ssh.MarshalAuthorizedKey(sshPublicKey)
745

Michael Yang's avatar
Michael Yang committed
746
		if err := os.WriteFile(pubKeyPath, publicKeyBytes, 0o644); err != nil {
747
748
749
			return err
		}

Michael Yang's avatar
Michael Yang committed
750
		fmt.Printf("Your new public key is: \n\n%s\n", publicKeyBytes)
751
752
753
754
	}
	return nil
}

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

Bruce MacDonald's avatar
Bruce MacDonald committed
771
772
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

891
892
	pullCmd.Flags().Bool("insecure", false, "Use an insecure registry")

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

901
902
	pushCmd.Flags().Bool("insecure", false, "Use an insecure registry")

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

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

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

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

	return rootCmd
}