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

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

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

Jeffrey Morgan's avatar
Jeffrey Morgan committed
30
	"github.com/jmorganca/ollama/api"
Patrick Devine's avatar
Patrick Devine committed
31
	"github.com/jmorganca/ollama/format"
Michael Yang's avatar
Michael Yang committed
32
	"github.com/jmorganca/ollama/parser"
33
	"github.com/jmorganca/ollama/progressbar"
Patrick Devine's avatar
Patrick Devine committed
34
	"github.com/jmorganca/ollama/readline"
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
	modelfile, err := os.ReadFile(filename)
	if err != nil {
		return err
	}

	spinner := NewSpinner("transferring context")
	go spinner.Spin(100 * time.Millisecond)

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

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

	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:])
			}

			bin, err := os.Open(path)
			if errors.Is(err, os.ErrNotExist) && c.Name == "model" {
Michael Yang's avatar
Michael Yang committed
81
				continue
Michael Yang's avatar
Michael Yang committed
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
			} 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))
			path, err = client.CreateBlob(cmd.Context(), digest, bin)
			if err != nil {
				return err
			}

			modelfile = bytes.ReplaceAll(modelfile, []byte(c.Args), []byte(path))
		}
	}
Michael Yang's avatar
Michael Yang committed
102

103
104
	var currentDigest string
	var bar *progressbar.ProgressBar
Michael Yang's avatar
Michael Yang committed
105

Michael Yang's avatar
Michael Yang committed
106
	request := api.CreateRequest{Name: args[0], Path: filename, Modelfile: string(modelfile)}
107
108
	fn := func(resp api.ProgressResponse) error {
		if resp.Digest != currentDigest && resp.Digest != "" {
Michael Yang's avatar
Michael Yang committed
109
			spinner.Stop()
110
			currentDigest = resp.Digest
111
112
113
114
115
116
			// pulling
			bar = progressbar.DefaultBytes(
				resp.Total,
				resp.Status,
			)
			bar.Set64(resp.Completed)
117
		} else if resp.Digest == currentDigest && resp.Digest != "" {
Michael Yang's avatar
Michael Yang committed
118
			bar.Set64(resp.Completed)
119
120
		} else {
			currentDigest = ""
Michael Yang's avatar
Michael Yang committed
121
			spinner.Stop()
122
123
124
			spinner = NewSpinner(resp.Status)
			go spinner.Spin(100 * time.Millisecond)
		}
125

126
127
128
129
130
131
132
		return nil
	}

	if err := client.Create(context.Background(), &request, fn); err != nil {
		return err
	}

Michael Yang's avatar
Michael Yang committed
133
134
135
	spinner.Stop()
	if spinner.description != "success" {
		return errors.New("unexpected end to create model")
Michael Yang's avatar
Michael Yang committed
136
137
	}

138
139
140
	return nil
}

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

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

Michael Yang's avatar
Michael Yang committed
160
	return RunGenerate(cmd, args)
Bruce MacDonald's avatar
Bruce MacDonald committed
161
162
}

163
func PushHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
164
	client, err := api.ClientFromEnvironment()
165
166
167
	if err != nil {
		return err
	}
168

169
170
171
172
173
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

174
175
176
	var currentDigest string
	var bar *progressbar.ProgressBar

177
	request := api.PushRequest{Name: args[0], Insecure: insecure}
178
	fn := func(resp api.ProgressResponse) error {
179
180
181
		if resp.Digest != currentDigest && resp.Digest != "" {
			currentDigest = resp.Digest
			bar = progressbar.DefaultBytes(
Michael Yang's avatar
Michael Yang committed
182
				resp.Total,
183
184
185
				fmt.Sprintf("pushing %s...", resp.Digest[7:19]),
			)

Michael Yang's avatar
Michael Yang committed
186
			bar.Set64(resp.Completed)
187
		} else if resp.Digest == currentDigest && resp.Digest != "" {
Michael Yang's avatar
Michael Yang committed
188
			bar.Set64(resp.Completed)
189
190
191
192
		} else {
			currentDigest = ""
			fmt.Println(resp.Status)
		}
193
194
195
196
197
198
		return nil
	}

	if err := client.Push(context.Background(), &request, fn); err != nil {
		return err
	}
199
200
201
202
203

	if bar != nil && !bar.IsFinished() {
		return errors.New("unexpected end to push model")
	}

204
205
206
	return nil
}

207
func ListHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
208
	client, err := api.ClientFromEnvironment()
209
210
211
	if err != nil {
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
212
213
214
215
216
217
218
219
220

	models, err := client.List(context.Background())
	if err != nil {
		return err
	}

	var data [][]string

	for _, m := range models.Models {
Michael Yang's avatar
Michael Yang committed
221
		if len(args) == 0 || strings.HasPrefix(m.Name, args[0]) {
222
			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
223
		}
Patrick Devine's avatar
Patrick Devine committed
224
225
226
	}

	table := tablewriter.NewWriter(os.Stdout)
Patrick Devine's avatar
Patrick Devine committed
227
	table.SetHeader([]string{"NAME", "ID", "SIZE", "MODIFIED"})
Patrick Devine's avatar
Patrick Devine committed
228
229
230
231
232
233
234
235
236
237
238
239
	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
}

240
func DeleteHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
241
	client, err := api.ClientFromEnvironment()
242
243
244
	if err != nil {
		return err
	}
245

246
247
248
249
250
251
	for _, name := range args {
		req := api.DeleteRequest{Name: name}
		if err := client.Delete(context.Background(), &req); err != nil {
			return err
		}
		fmt.Printf("deleted '%s'\n", name)
252
253
254
255
	}
	return nil
}

Patrick Devine's avatar
Patrick Devine committed
256
func ShowHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
257
	client, err := api.ClientFromEnvironment()
Patrick Devine's avatar
Patrick Devine committed
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
	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 {
307
		return errors.New("only one of '--license', '--modelfile', '--parameters', '--system', or '--template' can be specified")
Patrick Devine's avatar
Patrick Devine committed
308
	} else if flagsSet == 0 {
309
		return errors.New("one of '--license', '--modelfile', '--parameters', '--system', or '--template' must be specified")
Patrick Devine's avatar
Patrick Devine committed
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
	}

	req := api.ShowRequest{Name: args[0]}
	resp, err := client.Show(context.Background(), &req)
	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
334
func CopyHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
335
	client, err := api.ClientFromEnvironment()
336
337
338
	if err != nil {
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
339
340
341
342
343
344
345
346
347

	req := api.CopyRequest{Source: args[0], Destination: args[1]}
	if err := client.Copy(context.Background(), &req); err != nil {
		return err
	}
	fmt.Printf("copied '%s' to '%s'\n", args[0], args[1])
	return nil
}

348
func PullHandler(cmd *cobra.Command, args []string) error {
349
350
351
352
353
354
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

	return pull(args[0], insecure)
355
356
}

357
func pull(model string, insecure bool) error {
Michael Yang's avatar
Michael Yang committed
358
	client, err := api.ClientFromEnvironment()
359
360
361
	if err != nil {
		return err
	}
362

363
	var currentDigest string
Bruce MacDonald's avatar
Bruce MacDonald committed
364
	var bar *progressbar.ProgressBar
Michael Yang's avatar
Michael Yang committed
365

366
	request := api.PullRequest{Name: model, Insecure: insecure}
367
368
369
	fn := func(resp api.ProgressResponse) error {
		if resp.Digest != currentDigest && resp.Digest != "" {
			currentDigest = resp.Digest
370
			bar = progressbar.DefaultBytes(
Michael Yang's avatar
Michael Yang committed
371
				resp.Total,
372
				fmt.Sprintf("pulling %s...", resp.Digest[7:19]),
373
			)
374

Michael Yang's avatar
Michael Yang committed
375
			bar.Set64(resp.Completed)
376
		} else if resp.Digest == currentDigest && resp.Digest != "" {
Michael Yang's avatar
Michael Yang committed
377
			bar.Set64(resp.Completed)
378
		} else {
379
			currentDigest = ""
380
381
			fmt.Println(resp.Status)
		}
382

383
384
		return nil
	}
385

386
387
388
	if err := client.Pull(context.Background(), &request, fn); err != nil {
		return err
	}
389
390
391
392
393

	if bar != nil && !bar.IsFinished() {
		return errors.New("unexpected end to pull model")
	}

394
	return nil
Michael Yang's avatar
Michael Yang committed
395
396
}

397
func RunGenerate(cmd *cobra.Command, args []string) error {
398
399
400
401
	format, err := cmd.Flags().GetString("format")
	if err != nil {
		return err
	}
402

403
	prompts := args[1:]
404

405
406
407
	// prepend stdin to the prompt if provided
	if !term.IsTerminal(int(os.Stdin.Fd())) {
		in, err := io.ReadAll(os.Stdin)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
408
409
410
411
		if err != nil {
			return err
		}

412
		prompts = append([]string{string(in)}, prompts...)
Michael Yang's avatar
Michael Yang committed
413
414
	}

415
416
417
	// output is being piped
	if !term.IsTerminal(int(os.Stdout.Fd())) {
		return generate(cmd, args[0], strings.Join(prompts, " "), false, format)
Michael Yang's avatar
Michael Yang committed
418
419
	}

420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
	wordWrap := os.Getenv("TERM") == "xterm-256color"

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

	// prompts are provided via stdin or args so don't enter interactive mode
	if len(prompts) > 0 {
		return generate(cmd, args[0], strings.Join(prompts, " "), wordWrap, format)
	}

	return generateInteractive(cmd, args[0], wordWrap, format)
Michael Yang's avatar
Michael Yang committed
436
437
}

Michael Yang's avatar
Michael Yang committed
438
type generateContextKey string
Michael Yang's avatar
Michael Yang committed
439

Jeffrey Morgan's avatar
Jeffrey Morgan committed
440
func generate(cmd *cobra.Command, model, prompt string, wordWrap bool, format string) error {
Michael Yang's avatar
Michael Yang committed
441
	client, err := api.ClientFromEnvironment()
Patrick Devine's avatar
Patrick Devine committed
442
443
444
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
445

Patrick Devine's avatar
Patrick Devine committed
446
447
	spinner := NewSpinner("")
	go spinner.Spin(60 * time.Millisecond)
Michael Yang's avatar
Michael Yang committed
448

Patrick Devine's avatar
Patrick Devine committed
449
	var latest api.GenerateResponse
450

Patrick Devine's avatar
Patrick Devine committed
451
452
453
454
	generateContext, ok := cmd.Context().Value(generateContextKey("context")).([]int)
	if !ok {
		generateContext = []int{}
	}
Michael Yang's avatar
Michael Yang committed
455

456
	termWidth, _, err := term.GetSize(int(os.Stdout.Fd()))
457
	if err != nil {
458
		wordWrap = false
459
460
	}

461
462
463
464
465
466
467
468
469
470
471
472
473
	cancelCtx, cancel := context.WithCancel(context.Background())
	defer cancel()

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

	go func() {
		<-sigChan
		cancel()
		abort = true
	}()

474
475
476
	var currentLineLength int
	var wordBuffer string

Jeffrey Morgan's avatar
Jeffrey Morgan committed
477
	request := api.GenerateRequest{Model: model, Prompt: prompt, Context: generateContext, Format: format}
Patrick Devine's avatar
Patrick Devine committed
478
479
480
481
	fn := func(response api.GenerateResponse) error {
		if !spinner.IsFinished() {
			spinner.Finish()
		}
Michael Yang's avatar
Michael Yang committed
482

Patrick Devine's avatar
Patrick Devine committed
483
		latest = response
484

485
		if wordWrap {
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
			for _, ch := range response.Response {
				if currentLineLength+1 > termWidth-5 {
					// backtrack the length of the last word and clear to the end of the line
					fmt.Printf("\x1b[%dD\x1b[K\n", len(wordBuffer))
					fmt.Printf("%s%c", wordBuffer, ch)
					currentLineLength = len(wordBuffer) + 1
				} else {
					fmt.Print(string(ch))
					currentLineLength += 1

					switch ch {
					case ' ':
						wordBuffer = ""
					case '\n':
						currentLineLength = 0
					default:
						wordBuffer += string(ch)
					}
				}
			}
		} else {
			fmt.Print(response.Response)
		}

Patrick Devine's avatar
Patrick Devine committed
510
511
		return nil
	}
512

513
	if err := client.Generate(cancelCtx, &request, fn); err != nil {
514
		if strings.Contains(err.Error(), "context canceled") && abort {
515
516
			spinner.Finish()
			return nil
517
		}
Patrick Devine's avatar
Patrick Devine committed
518
519
520
		return err
	}
	if prompt != "" {
Michael Yang's avatar
Michael Yang committed
521
522
		fmt.Println()
		fmt.Println()
Patrick Devine's avatar
Patrick Devine committed
523
	}
524

Patrick Devine's avatar
Patrick Devine committed
525
	if !latest.Done {
526
527
528
		if abort {
			return nil
		}
Patrick Devine's avatar
Patrick Devine committed
529
530
		return errors.New("unexpected end of response")
	}
531

Patrick Devine's avatar
Patrick Devine committed
532
533
534
535
	verbose, err := cmd.Flags().GetBool("verbose")
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
536

Patrick Devine's avatar
Patrick Devine committed
537
538
	if verbose {
		latest.Summary()
Michael Yang's avatar
Michael Yang committed
539
	}
Michael Yang's avatar
Michael Yang committed
540

Patrick Devine's avatar
Patrick Devine committed
541
542
543
544
	ctx := cmd.Context()
	ctx = context.WithValue(ctx, generateContextKey("context"), latest.Context)
	cmd.SetContext(ctx)

Michael Yang's avatar
Michael Yang committed
545
546
547
	return nil
}

548
func generateInteractive(cmd *cobra.Command, model string, wordWrap bool, format string) error {
Patrick Devine's avatar
Patrick Devine committed
549
	// load the model
Jeffrey Morgan's avatar
Jeffrey Morgan committed
550
	if err := generate(cmd, model, "", false, ""); err != nil {
Patrick Devine's avatar
Patrick Devine committed
551
552
553
		return err
	}

Michael Yang's avatar
Michael Yang committed
554
	usage := func() {
Patrick Devine's avatar
Patrick Devine committed
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
		fmt.Fprintln(os.Stderr, "Available Commands:")
		fmt.Fprintln(os.Stderr, "  /set         Set session variables")
		fmt.Fprintln(os.Stderr, "  /show        Show model information")
		fmt.Fprintln(os.Stderr, "  /bye         Exit")
		fmt.Fprintln(os.Stderr, "  /?, /help    Help for a command")
		fmt.Fprintln(os.Stderr, "")
		fmt.Fprintln(os.Stderr, "Use \"\"\" to begin a multi-line message.")
		fmt.Fprintln(os.Stderr, "")
	}

	usageSet := func() {
		fmt.Fprintln(os.Stderr, "Available Commands:")
		fmt.Fprintln(os.Stderr, "  /set history      Enable history")
		fmt.Fprintln(os.Stderr, "  /set nohistory    Disable history")
		fmt.Fprintln(os.Stderr, "  /set wordwrap     Enable wordwrap")
		fmt.Fprintln(os.Stderr, "  /set nowordwrap   Disable wordwrap")
Jeffrey Morgan's avatar
Jeffrey Morgan committed
571
572
		fmt.Fprintln(os.Stderr, "  /set format json  Enable JSON mode")
		fmt.Fprintln(os.Stderr, "  /set noformat     Disable formatting")
Patrick Devine's avatar
Patrick Devine committed
573
574
575
576
577
578
579
580
581
582
583
584
585
		fmt.Fprintln(os.Stderr, "  /set verbose      Show LLM stats")
		fmt.Fprintln(os.Stderr, "  /set quiet        Disable LLM stats")
		fmt.Fprintln(os.Stderr, "")
	}

	usageShow := func() {
		fmt.Fprintln(os.Stderr, "Available Commands:")
		fmt.Fprintln(os.Stderr, "  /show license      Show model license")
		fmt.Fprintln(os.Stderr, "  /show modelfile    Show Modelfile for this model")
		fmt.Fprintln(os.Stderr, "  /show parameters   Show parameters for this model")
		fmt.Fprintln(os.Stderr, "  /show system       Show system prompt")
		fmt.Fprintln(os.Stderr, "  /show template     Show prompt template")
		fmt.Fprintln(os.Stderr, "")
Michael Yang's avatar
Michael Yang committed
586
587
	}

Patrick Devine's avatar
Patrick Devine committed
588
589
590
591
592
	prompt := readline.Prompt{
		Prompt:         ">>> ",
		AltPrompt:      "... ",
		Placeholder:    "Send a message (/? for help)",
		AltPlaceholder: `Use """ to end multi-line input`,
Michael Yang's avatar
Michael Yang committed
593
594
	}

Patrick Devine's avatar
Patrick Devine committed
595
	scanner, err := readline.New(prompt)
Michael Yang's avatar
Michael Yang committed
596
597
598
599
	if err != nil {
		return err
	}

600
601
602
	fmt.Print(readline.StartBracketedPaste)
	defer fmt.Printf(readline.EndBracketedPaste)

603
604
	var multiLineBuffer string

Michael Yang's avatar
Michael Yang committed
605
606
607
608
	for {
		line, err := scanner.Readline()
		switch {
		case errors.Is(err, io.EOF):
609
			fmt.Println()
Michael Yang's avatar
Michael Yang committed
610
611
			return nil
		case errors.Is(err, readline.ErrInterrupt):
612
			if line == "" {
Patrick Devine's avatar
Patrick Devine committed
613
				fmt.Println("\nUse Ctrl-D or /bye to exit.")
614
615
			}

Michael Yang's avatar
Michael Yang committed
616
617
			continue
		case err != nil:
Michael Yang's avatar
Michael Yang committed
618
619
620
			return err
		}

Michael Yang's avatar
Michael Yang committed
621
		line = strings.TrimSpace(line)
Michael Yang's avatar
Michael Yang committed
622

Michael Yang's avatar
Michael Yang committed
623
		switch {
Patrick Devine's avatar
Patrick Devine committed
624
		case scanner.Prompt.UseAlt:
625
			if strings.HasSuffix(line, `"""`) {
Patrick Devine's avatar
Patrick Devine committed
626
				scanner.Prompt.UseAlt = false
627
				multiLineBuffer += strings.TrimSuffix(line, `"""`)
628
				line = multiLineBuffer
629
630
631
632
633
634
				multiLineBuffer = ""
			} else {
				multiLineBuffer += line + " "
				continue
			}
		case strings.HasPrefix(line, `"""`):
Patrick Devine's avatar
Patrick Devine committed
635
			scanner.Prompt.UseAlt = true
636
637
			multiLineBuffer = strings.TrimPrefix(line, `"""`) + " "
			continue
Michael Yang's avatar
Michael Yang committed
638
639
		case strings.HasPrefix(line, "/list"):
			args := strings.Fields(line)
640
			if err := ListHandler(cmd, args[1:]); err != nil {
Michael Yang's avatar
Michael Yang committed
641
642
643
644
645
646
647
648
649
650
				return err
			}
		case strings.HasPrefix(line, "/set"):
			args := strings.Fields(line)
			if len(args) > 1 {
				switch args[1] {
				case "history":
					scanner.HistoryEnable()
				case "nohistory":
					scanner.HistoryDisable()
651
				case "wordwrap":
652
					wordWrap = true
653
654
					fmt.Println("Set 'wordwrap' mode.")
				case "nowordwrap":
655
					wordWrap = false
656
					fmt.Println("Set 'nowordwrap' mode.")
Michael Yang's avatar
Michael Yang committed
657
658
				case "verbose":
					cmd.Flags().Set("verbose", "true")
659
					fmt.Println("Set 'verbose' mode.")
Michael Yang's avatar
Michael Yang committed
660
661
				case "quiet":
					cmd.Flags().Set("verbose", "false")
662
					fmt.Println("Set 'quiet' mode.")
Jeffrey Morgan's avatar
Jeffrey Morgan committed
663
664
665
666
667
668
669
670
671
672
				case "format":
					if len(args) < 3 || args[2] != "json" {
						fmt.Println("Invalid or missing format. For 'json' mode use '/set format json'")
					} else {
						format = args[2]
						fmt.Printf("Set format to '%s' mode.\n", args[2])
					}
				case "noformat":
					format = ""
					fmt.Println("Disabled format.")
673
674
				default:
					fmt.Printf("Unknown command '/set %s'. Type /? for help\n", args[1])
675
676
				}
			} else {
Patrick Devine's avatar
Patrick Devine committed
677
				usageSet()
678
679
680
681
			}
		case strings.HasPrefix(line, "/show"):
			args := strings.Fields(line)
			if len(args) > 1 {
682
683
684
685
686
687
				client, err := api.ClientFromEnvironment()
				if err != nil {
					fmt.Println("error: couldn't connect to ollama server")
					return err
				}
				resp, err := client.Show(cmd.Context(), &api.ShowRequest{Name: model})
688
				if err != nil {
Patrick Devine's avatar
Patrick Devine committed
689
					fmt.Println("error: couldn't get model")
690
					return err
691
				}
Patrick Devine's avatar
Patrick Devine committed
692

693
694
				switch args[1] {
				case "license":
695
					if resp.License == "" {
696
						fmt.Print("No license was specified for this model.\n\n")
697
698
699
					} else {
						fmt.Println(resp.License)
					}
Patrick Devine's avatar
Patrick Devine committed
700
701
702
				case "modelfile":
					fmt.Println(resp.Modelfile)
				case "parameters":
703
					if resp.Parameters == "" {
704
						fmt.Print("No parameters were specified for this model.\n\n")
705
706
707
					} else {
						fmt.Println(resp.Parameters)
					}
708
				case "system":
709
					if resp.System == "" {
710
						fmt.Print("No system prompt was specified for this model.\n\n")
711
712
713
					} else {
						fmt.Println(resp.System)
					}
714
				case "template":
715
					if resp.Template == "" {
716
						fmt.Print("No prompt template was specified for this model.\n\n")
717
718
719
					} else {
						fmt.Println(resp.Template)
					}
720
				default:
721
					fmt.Printf("Unknown command '/show %s'. Type /? for help\n", args[1])
Michael Yang's avatar
Michael Yang committed
722
				}
Patrick Devine's avatar
Patrick Devine committed
723
724
725
726
727
728
729
730
731
732
733
734
			} else {
				usageShow()
			}
		case strings.HasPrefix(line, "/help"), strings.HasPrefix(line, "/?"):
			args := strings.Fields(line)
			if len(args) > 1 {
				switch args[1] {
				case "set", "/set":
					usageSet()
				case "show", "/show":
					usageShow()
				}
735
736
			} else {
				usage()
Michael Yang's avatar
Michael Yang committed
737
738
739
			}
		case line == "/exit", line == "/bye":
			return nil
740
741
742
		case strings.HasPrefix(line, "/"):
			args := strings.Fields(line)
			fmt.Printf("Unknown command '%s'. Type /? for help\n", args[0])
Michael Yang's avatar
Michael Yang committed
743
744
		}

Patrick Devine's avatar
Patrick Devine committed
745
		if len(line) > 0 && line[0] != '/' {
Jeffrey Morgan's avatar
Jeffrey Morgan committed
746
			if err := generate(cmd, model, line, wordWrap, format); err != nil {
Patrick Devine's avatar
Patrick Devine committed
747
748
				return err
			}
Michael Yang's avatar
Michael Yang committed
749
750
		}
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
751
752
}

753
func RunServer(cmd *cobra.Command, _ []string) error {
Michael Yang's avatar
Michael Yang committed
754
755
756
	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
757
		if ip := net.ParseIP(strings.Trim(os.Getenv("OLLAMA_HOST"), "[]")); ip != nil {
Michael Yang's avatar
Michael Yang committed
758
759
			host = ip.String()
		}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
760
	}
761

Michael Yang's avatar
Michael Yang committed
762
	if err := initializeKeypair(); err != nil {
763
764
765
		return err
	}

Michael Yang's avatar
Michael Yang committed
766
	ln, err := net.Listen("tcp", net.JoinHostPort(host, port))
767
768
769
	if err != nil {
		return err
	}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
770

771
772
773
774
775
	var origins []string
	if o := os.Getenv("OLLAMA_ORIGINS"); o != "" {
		origins = strings.Split(o, ",")
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
776
	return server.Serve(ln, origins)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
777
778
}

779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
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
801
		err = os.MkdirAll(filepath.Dir(privKeyPath), 0o755)
802
803
804
805
		if err != nil {
			return fmt.Errorf("could not create directory %w", err)
		}

806
		err = os.WriteFile(privKeyPath, pem.EncodeToMemory(privKeyBytes), 0o600)
807
808
809
810
811
812
813
814
815
816
817
		if err != nil {
			return err
		}

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

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

818
		err = os.WriteFile(pubKeyPath, pubKeyData, 0o644)
819
820
821
822
823
824
825
826
827
		if err != nil {
			return err
		}

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

Bruce MacDonald's avatar
Bruce MacDonald committed
828
func startMacApp(client *api.Client) error {
Bruce MacDonald's avatar
Bruce MacDonald committed
829
830
831
832
833
	exe, err := os.Executable()
	if err != nil {
		return err
	}
	link, err := os.Readlink(exe)
Bruce MacDonald's avatar
Bruce MacDonald committed
834
835
836
	if err != nil {
		return err
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
837
838
839
	if !strings.Contains(link, "Ollama.app") {
		return fmt.Errorf("could not find ollama app")
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
	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:
			if err := client.Heartbeat(context.Background()); err == nil {
				return nil // server has started
			}
		}
	}
}

859
func checkServerHeartbeat(_ *cobra.Command, _ []string) error {
Michael Yang's avatar
Michael Yang committed
860
	client, err := api.ClientFromEnvironment()
861
862
863
	if err != nil {
		return err
	}
864
	if err := client.Heartbeat(context.Background()); err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
865
866
867
868
869
		if !strings.Contains(err.Error(), "connection refused") {
			return err
		}
		if runtime.GOOS == "darwin" {
			if err := startMacApp(client); err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
870
				return fmt.Errorf("could not connect to ollama app, is it running?")
871
			}
Bruce MacDonald's avatar
Bruce MacDonald committed
872
		} else {
873
874
875
876
877
878
			return fmt.Errorf("could not connect to ollama server, run 'ollama serve' to start it")
		}
	}
	return nil
}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
879
880
881
882
func NewCLI() *cobra.Command {
	log.SetFlags(log.LstdFlags | log.Lshortfile)

	rootCmd := &cobra.Command{
883
884
885
886
		Use:           "ollama",
		Short:         "Large language model runner",
		SilenceUsage:  true,
		SilenceErrors: true,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
887
888
889
		CompletionOptions: cobra.CompletionOptions{
			DisableDefaultCmd: true,
		},
Michael Yang's avatar
Michael Yang committed
890
		Version: version.Version,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
891
892
893
894
	}

	cobra.EnableCommandSorting = false

895
	createCmd := &cobra.Command{
896
897
		Use:     "create MODEL",
		Short:   "Create a model from a Modelfile",
Michael Yang's avatar
Michael Yang committed
898
		Args:    cobra.ExactArgs(1),
899
900
		PreRunE: checkServerHeartbeat,
		RunE:    CreateHandler,
901
902
903
904
	}

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

Patrick Devine's avatar
Patrick Devine committed
905
906
907
	showCmd := &cobra.Command{
		Use:     "show MODEL",
		Short:   "Show information for a model",
Michael Yang's avatar
Michael Yang committed
908
		Args:    cobra.ExactArgs(1),
Patrick Devine's avatar
Patrick Devine committed
909
910
911
912
913
914
915
916
917
918
		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")
	showCmd.Flags().Bool("system", false, "Show system prompt of a model")

Jeffrey Morgan's avatar
Jeffrey Morgan committed
919
	runCmd := &cobra.Command{
920
921
922
923
924
		Use:     "run MODEL [PROMPT]",
		Short:   "Run a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    RunHandler,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
925
926
	}

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

Jeffrey Morgan's avatar
Jeffrey Morgan committed
932
933
934
935
	serveCmd := &cobra.Command{
		Use:     "serve",
		Aliases: []string{"start"},
		Short:   "Start ollama",
Michael Yang's avatar
Michael Yang committed
936
		Args:    cobra.ExactArgs(0),
Michael Yang's avatar
Michael Yang committed
937
		RunE:    RunServer,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
938
939
	}

940
	pullCmd := &cobra.Command{
941
942
		Use:     "pull MODEL",
		Short:   "Pull a model from a registry",
Michael Yang's avatar
Michael Yang committed
943
		Args:    cobra.ExactArgs(1),
944
945
		PreRunE: checkServerHeartbeat,
		RunE:    PullHandler,
946
947
	}

948
949
	pullCmd.Flags().Bool("insecure", false, "Use an insecure registry")

950
	pushCmd := &cobra.Command{
951
952
		Use:     "push MODEL",
		Short:   "Push a model to a registry",
Michael Yang's avatar
Michael Yang committed
953
		Args:    cobra.ExactArgs(1),
954
955
		PreRunE: checkServerHeartbeat,
		RunE:    PushHandler,
956
957
	}

958
959
	pushCmd.Flags().Bool("insecure", false, "Use an insecure registry")

Patrick Devine's avatar
Patrick Devine committed
960
	listCmd := &cobra.Command{
961
		Use:     "list",
Patrick Devine's avatar
Patrick Devine committed
962
		Aliases: []string{"ls"},
963
		Short:   "List models",
964
		PreRunE: checkServerHeartbeat,
965
		RunE:    ListHandler,
966
967
	}

Patrick Devine's avatar
Patrick Devine committed
968
	copyCmd := &cobra.Command{
Michael Yang's avatar
Michael Yang committed
969
		Use:     "cp SOURCE TARGET",
970
		Short:   "Copy a model",
Michael Yang's avatar
Michael Yang committed
971
		Args:    cobra.ExactArgs(2),
972
973
		PreRunE: checkServerHeartbeat,
		RunE:    CopyHandler,
Patrick Devine's avatar
Patrick Devine committed
974
975
	}

976
	deleteCmd := &cobra.Command{
Michael Yang's avatar
Michael Yang committed
977
		Use:     "rm MODEL [MODEL...]",
978
979
980
981
		Short:   "Remove a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    DeleteHandler,
Patrick Devine's avatar
Patrick Devine committed
982
983
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
984
985
	rootCmd.AddCommand(
		serveCmd,
986
		createCmd,
Patrick Devine's avatar
Patrick Devine committed
987
		showCmd,
988
		runCmd,
989
990
		pullCmd,
		pushCmd,
Patrick Devine's avatar
Patrick Devine committed
991
		listCmd,
Patrick Devine's avatar
Patrick Devine committed
992
		copyCmd,
993
		deleteCmd,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
994
995
996
997
	)

	return rootCmd
}