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

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

Patrick Devine's avatar
Patrick Devine committed
23
24
	"github.com/dustin/go-humanize"
	"github.com/olekukonko/tablewriter"
Michael Yang's avatar
Michael Yang committed
25
	"github.com/spf13/cobra"
26
	"golang.org/x/crypto/ssh"
27
	"golang.org/x/term"
Michael Yang's avatar
Michael Yang committed
28

Jeffrey Morgan's avatar
Jeffrey Morgan committed
29
	"github.com/jmorganca/ollama/api"
Patrick Devine's avatar
Patrick Devine committed
30
	"github.com/jmorganca/ollama/format"
31
	"github.com/jmorganca/ollama/progressbar"
Patrick Devine's avatar
Patrick Devine committed
32
	"github.com/jmorganca/ollama/readline"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
33
	"github.com/jmorganca/ollama/server"
Michael Yang's avatar
Michael Yang committed
34
	"github.com/jmorganca/ollama/version"
Jeffrey Morgan's avatar
Jeffrey Morgan committed
35
36
)

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

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

Michael Yang's avatar
Michael Yang committed
49
50
	var spinner *Spinner

51
52
	var currentDigest string
	var bar *progressbar.ProgressBar
Michael Yang's avatar
Michael Yang committed
53

54
55
56
57
58
59
60
	request := api.CreateRequest{Name: args[0], Path: filename}
	fn := func(resp api.ProgressResponse) error {
		if resp.Digest != currentDigest && resp.Digest != "" {
			if spinner != nil {
				spinner.Stop()
			}
			currentDigest = resp.Digest
61
62
63
64
65
66
			// pulling
			bar = progressbar.DefaultBytes(
				resp.Total,
				resp.Status,
			)
			bar.Set64(resp.Completed)
67
		} else if resp.Digest == currentDigest && resp.Digest != "" {
Michael Yang's avatar
Michael Yang committed
68
			bar.Set64(resp.Completed)
69
70
71
72
73
74
75
76
		} else {
			currentDigest = ""
			if spinner != nil {
				spinner.Stop()
			}
			spinner = NewSpinner(resp.Status)
			go spinner.Spin(100 * time.Millisecond)
		}
77

78
79
80
81
82
83
84
		return nil
	}

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

Michael Yang's avatar
Michael Yang committed
85
86
	if spinner != nil {
		spinner.Stop()
87
88
89
		if spinner.description != "success" {
			return errors.New("unexpected end to create model")
		}
Michael Yang's avatar
Michael Yang committed
90
91
	}

92
93
94
	return nil
}

95
func RunHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
96
	client, err := api.ClientFromEnvironment()
97
98
99
100
	if err != nil {
		return err
	}

101
102
103
	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
104
105
106
107
	var statusError api.StatusError
	switch {
	case errors.As(err, &statusError) && statusError.StatusCode == http.StatusNotFound:
		if err := PullHandler(cmd, args); err != nil {
108
			return err
Michael Yang's avatar
Michael Yang committed
109
		}
Michael Yang's avatar
Michael Yang committed
110
111
	case err != nil:
		return err
112
113
	}

Michael Yang's avatar
Michael Yang committed
114
	return RunGenerate(cmd, args)
Bruce MacDonald's avatar
Bruce MacDonald committed
115
116
}

117
func PushHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
118
	client, err := api.ClientFromEnvironment()
119
120
121
	if err != nil {
		return err
	}
122

123
124
125
126
127
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

128
129
130
	var currentDigest string
	var bar *progressbar.ProgressBar

131
	request := api.PushRequest{Name: args[0], Insecure: insecure}
132
	fn := func(resp api.ProgressResponse) error {
133
134
135
		if resp.Digest != currentDigest && resp.Digest != "" {
			currentDigest = resp.Digest
			bar = progressbar.DefaultBytes(
Michael Yang's avatar
Michael Yang committed
136
				resp.Total,
137
138
139
				fmt.Sprintf("pushing %s...", resp.Digest[7:19]),
			)

Michael Yang's avatar
Michael Yang committed
140
			bar.Set64(resp.Completed)
141
		} else if resp.Digest == currentDigest && resp.Digest != "" {
Michael Yang's avatar
Michael Yang committed
142
			bar.Set64(resp.Completed)
143
144
145
146
		} else {
			currentDigest = ""
			fmt.Println(resp.Status)
		}
147
148
149
150
151
152
		return nil
	}

	if err := client.Push(context.Background(), &request, fn); err != nil {
		return err
	}
153
154
155
156
157

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

158
159
160
	return nil
}

161
func ListHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
162
	client, err := api.ClientFromEnvironment()
163
164
165
	if err != nil {
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
166
167
168
169
170
171
172
173
174

	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
175
		if len(args) == 0 || strings.HasPrefix(m.Name, args[0]) {
Patrick Devine's avatar
Patrick Devine committed
176
			data = append(data, []string{m.Name, m.Digest[:12], humanize.Bytes(uint64(m.Size)), format.HumanTime(m.ModifiedAt, "Never")})
Michael Yang's avatar
Michael Yang committed
177
		}
Patrick Devine's avatar
Patrick Devine committed
178
179
180
	}

	table := tablewriter.NewWriter(os.Stdout)
Patrick Devine's avatar
Patrick Devine committed
181
	table.SetHeader([]string{"NAME", "ID", "SIZE", "MODIFIED"})
Patrick Devine's avatar
Patrick Devine committed
182
183
184
185
186
187
188
189
190
191
192
193
	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
}

194
func DeleteHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
195
	client, err := api.ClientFromEnvironment()
196
197
198
	if err != nil {
		return err
	}
199

200
201
202
203
204
205
	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)
206
207
208
209
	}
	return nil
}

Patrick Devine's avatar
Patrick Devine committed
210
func ShowHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
211
	client, err := api.ClientFromEnvironment()
Patrick Devine's avatar
Patrick Devine committed
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
	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 {
261
		return errors.New("only one of '--license', '--modelfile', '--parameters', '--system', or '--template' can be specified")
Patrick Devine's avatar
Patrick Devine committed
262
	} else if flagsSet == 0 {
263
		return errors.New("one of '--license', '--modelfile', '--parameters', '--system', or '--template' must be specified")
Patrick Devine's avatar
Patrick Devine committed
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
	}

	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
288
func CopyHandler(cmd *cobra.Command, args []string) error {
Michael Yang's avatar
Michael Yang committed
289
	client, err := api.ClientFromEnvironment()
290
291
292
	if err != nil {
		return err
	}
Patrick Devine's avatar
Patrick Devine committed
293
294
295
296
297
298
299
300
301

	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
}

302
func PullHandler(cmd *cobra.Command, args []string) error {
303
304
305
306
307
308
	insecure, err := cmd.Flags().GetBool("insecure")
	if err != nil {
		return err
	}

	return pull(args[0], insecure)
309
310
}

311
func pull(model string, insecure bool) error {
Michael Yang's avatar
Michael Yang committed
312
	client, err := api.ClientFromEnvironment()
313
314
315
	if err != nil {
		return err
	}
316

317
	var currentDigest string
Bruce MacDonald's avatar
Bruce MacDonald committed
318
	var bar *progressbar.ProgressBar
Michael Yang's avatar
Michael Yang committed
319

320
	request := api.PullRequest{Name: model, Insecure: insecure}
321
322
323
	fn := func(resp api.ProgressResponse) error {
		if resp.Digest != currentDigest && resp.Digest != "" {
			currentDigest = resp.Digest
324
			bar = progressbar.DefaultBytes(
Michael Yang's avatar
Michael Yang committed
325
				resp.Total,
326
				fmt.Sprintf("pulling %s...", resp.Digest[7:19]),
327
			)
328

Michael Yang's avatar
Michael Yang committed
329
			bar.Set64(resp.Completed)
330
		} else if resp.Digest == currentDigest && resp.Digest != "" {
Michael Yang's avatar
Michael Yang committed
331
			bar.Set64(resp.Completed)
332
		} else {
333
			currentDigest = ""
334
335
			fmt.Println(resp.Status)
		}
336

337
338
		return nil
	}
339

340
341
342
	if err := client.Pull(context.Background(), &request, fn); err != nil {
		return err
	}
343
344
345
346
347

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

348
	return nil
Michael Yang's avatar
Michael Yang committed
349
350
}

351
func RunGenerate(cmd *cobra.Command, args []string) error {
352
353
354
355
	format, err := cmd.Flags().GetString("format")
	if err != nil {
		return err
	}
356

357
	prompts := args[1:]
358

359
360
361
	// 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
362
363
364
365
		if err != nil {
			return err
		}

366
		prompts = append([]string{string(in)}, prompts...)
Michael Yang's avatar
Michael Yang committed
367
368
	}

369
370
371
	// 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
372
373
	}

374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
	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
390
391
}

Michael Yang's avatar
Michael Yang committed
392
type generateContextKey string
Michael Yang's avatar
Michael Yang committed
393

Jeffrey Morgan's avatar
Jeffrey Morgan committed
394
func generate(cmd *cobra.Command, model, prompt string, wordWrap bool, format string) error {
Michael Yang's avatar
Michael Yang committed
395
	client, err := api.ClientFromEnvironment()
Patrick Devine's avatar
Patrick Devine committed
396
397
398
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
399

Patrick Devine's avatar
Patrick Devine committed
400
401
	spinner := NewSpinner("")
	go spinner.Spin(60 * time.Millisecond)
Michael Yang's avatar
Michael Yang committed
402

Patrick Devine's avatar
Patrick Devine committed
403
	var latest api.GenerateResponse
404

Patrick Devine's avatar
Patrick Devine committed
405
406
407
408
	generateContext, ok := cmd.Context().Value(generateContextKey("context")).([]int)
	if !ok {
		generateContext = []int{}
	}
Michael Yang's avatar
Michael Yang committed
409

410
	termWidth, _, err := term.GetSize(int(os.Stdout.Fd()))
411
	if err != nil {
412
		wordWrap = false
413
414
	}

415
416
417
418
419
420
421
422
423
424
425
426
427
	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
	}()

428
429
430
	var currentLineLength int
	var wordBuffer string

Jeffrey Morgan's avatar
Jeffrey Morgan committed
431
	request := api.GenerateRequest{Model: model, Prompt: prompt, Context: generateContext, Format: format}
Patrick Devine's avatar
Patrick Devine committed
432
433
434
435
	fn := func(response api.GenerateResponse) error {
		if !spinner.IsFinished() {
			spinner.Finish()
		}
Michael Yang's avatar
Michael Yang committed
436

Patrick Devine's avatar
Patrick Devine committed
437
		latest = response
438

439
		if wordWrap {
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
			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
464
465
		return nil
	}
466

467
	if err := client.Generate(cancelCtx, &request, fn); err != nil {
468
		if strings.Contains(err.Error(), "context canceled") && abort {
469
470
			spinner.Finish()
			return nil
471
		}
Patrick Devine's avatar
Patrick Devine committed
472
473
474
		return err
	}
	if prompt != "" {
Michael Yang's avatar
Michael Yang committed
475
476
		fmt.Println()
		fmt.Println()
Patrick Devine's avatar
Patrick Devine committed
477
	}
478

Patrick Devine's avatar
Patrick Devine committed
479
	if !latest.Done {
480
481
482
		if abort {
			return nil
		}
Patrick Devine's avatar
Patrick Devine committed
483
484
		return errors.New("unexpected end of response")
	}
485

Patrick Devine's avatar
Patrick Devine committed
486
487
488
489
	verbose, err := cmd.Flags().GetBool("verbose")
	if err != nil {
		return err
	}
Michael Yang's avatar
Michael Yang committed
490

Patrick Devine's avatar
Patrick Devine committed
491
492
	if verbose {
		latest.Summary()
Michael Yang's avatar
Michael Yang committed
493
	}
Michael Yang's avatar
Michael Yang committed
494

Patrick Devine's avatar
Patrick Devine committed
495
496
497
498
	ctx := cmd.Context()
	ctx = context.WithValue(ctx, generateContextKey("context"), latest.Context)
	cmd.SetContext(ctx)

Michael Yang's avatar
Michael Yang committed
499
500
501
	return nil
}

502
func generateInteractive(cmd *cobra.Command, model string, wordWrap bool, format string) error {
Patrick Devine's avatar
Patrick Devine committed
503
	// load the model
Jeffrey Morgan's avatar
Jeffrey Morgan committed
504
	if err := generate(cmd, model, "", false, ""); err != nil {
Patrick Devine's avatar
Patrick Devine committed
505
506
507
		return err
	}

Michael Yang's avatar
Michael Yang committed
508
	usage := func() {
Patrick Devine's avatar
Patrick Devine committed
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
		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
525
526
		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
527
528
529
530
531
532
533
534
535
536
537
538
539
		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
540
541
	}

Patrick Devine's avatar
Patrick Devine committed
542
543
544
545
546
	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
547
548
	}

Patrick Devine's avatar
Patrick Devine committed
549
	scanner, err := readline.New(prompt)
Michael Yang's avatar
Michael Yang committed
550
551
552
553
	if err != nil {
		return err
	}

554
555
556
	fmt.Print(readline.StartBracketedPaste)
	defer fmt.Printf(readline.EndBracketedPaste)

557
558
	var multiLineBuffer string

Michael Yang's avatar
Michael Yang committed
559
560
561
562
	for {
		line, err := scanner.Readline()
		switch {
		case errors.Is(err, io.EOF):
563
			fmt.Println()
Michael Yang's avatar
Michael Yang committed
564
565
			return nil
		case errors.Is(err, readline.ErrInterrupt):
566
			if line == "" {
Patrick Devine's avatar
Patrick Devine committed
567
				fmt.Println("\nUse Ctrl-D or /bye to exit.")
568
569
			}

Michael Yang's avatar
Michael Yang committed
570
571
			continue
		case err != nil:
Michael Yang's avatar
Michael Yang committed
572
573
574
			return err
		}

Michael Yang's avatar
Michael Yang committed
575
		line = strings.TrimSpace(line)
Michael Yang's avatar
Michael Yang committed
576

Michael Yang's avatar
Michael Yang committed
577
		switch {
Patrick Devine's avatar
Patrick Devine committed
578
		case scanner.Prompt.UseAlt:
579
			if strings.HasSuffix(line, `"""`) {
Patrick Devine's avatar
Patrick Devine committed
580
				scanner.Prompt.UseAlt = false
581
				multiLineBuffer += strings.TrimSuffix(line, `"""`)
582
				line = multiLineBuffer
583
584
585
586
587
588
				multiLineBuffer = ""
			} else {
				multiLineBuffer += line + " "
				continue
			}
		case strings.HasPrefix(line, `"""`):
Patrick Devine's avatar
Patrick Devine committed
589
			scanner.Prompt.UseAlt = true
590
591
			multiLineBuffer = strings.TrimPrefix(line, `"""`) + " "
			continue
Michael Yang's avatar
Michael Yang committed
592
593
		case strings.HasPrefix(line, "/list"):
			args := strings.Fields(line)
594
			if err := ListHandler(cmd, args[1:]); err != nil {
Michael Yang's avatar
Michael Yang committed
595
596
597
598
599
600
601
602
603
604
				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()
605
				case "wordwrap":
606
					wordWrap = true
607
608
					fmt.Println("Set 'wordwrap' mode.")
				case "nowordwrap":
609
					wordWrap = false
610
					fmt.Println("Set 'nowordwrap' mode.")
Michael Yang's avatar
Michael Yang committed
611
612
				case "verbose":
					cmd.Flags().Set("verbose", "true")
613
					fmt.Println("Set 'verbose' mode.")
Michael Yang's avatar
Michael Yang committed
614
615
				case "quiet":
					cmd.Flags().Set("verbose", "false")
616
					fmt.Println("Set 'quiet' mode.")
Jeffrey Morgan's avatar
Jeffrey Morgan committed
617
618
619
620
621
622
623
624
625
626
				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.")
627
628
				default:
					fmt.Printf("Unknown command '/set %s'. Type /? for help\n", args[1])
629
630
				}
			} else {
Patrick Devine's avatar
Patrick Devine committed
631
				usageSet()
632
633
634
635
			}
		case strings.HasPrefix(line, "/show"):
			args := strings.Fields(line)
			if len(args) > 1 {
636
637
638
639
640
641
				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})
642
				if err != nil {
Patrick Devine's avatar
Patrick Devine committed
643
					fmt.Println("error: couldn't get model")
644
					return err
645
				}
Patrick Devine's avatar
Patrick Devine committed
646

647
648
				switch args[1] {
				case "license":
649
					if resp.License == "" {
650
						fmt.Print("No license was specified for this model.\n\n")
651
652
653
					} else {
						fmt.Println(resp.License)
					}
Patrick Devine's avatar
Patrick Devine committed
654
655
656
				case "modelfile":
					fmt.Println(resp.Modelfile)
				case "parameters":
657
					if resp.Parameters == "" {
658
						fmt.Print("No parameters were specified for this model.\n\n")
659
660
661
					} else {
						fmt.Println(resp.Parameters)
					}
662
				case "system":
663
					if resp.System == "" {
664
						fmt.Print("No system prompt was specified for this model.\n\n")
665
666
667
					} else {
						fmt.Println(resp.System)
					}
668
				case "template":
669
					if resp.Template == "" {
670
						fmt.Print("No prompt template was specified for this model.\n\n")
671
672
673
					} else {
						fmt.Println(resp.Template)
					}
674
				default:
675
					fmt.Printf("Unknown command '/show %s'. Type /? for help\n", args[1])
Michael Yang's avatar
Michael Yang committed
676
				}
Patrick Devine's avatar
Patrick Devine committed
677
678
679
680
681
682
683
684
685
686
687
688
			} 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()
				}
689
690
			} else {
				usage()
Michael Yang's avatar
Michael Yang committed
691
692
693
			}
		case line == "/exit", line == "/bye":
			return nil
694
695
696
		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
697
698
		}

Patrick Devine's avatar
Patrick Devine committed
699
		if len(line) > 0 && line[0] != '/' {
Jeffrey Morgan's avatar
Jeffrey Morgan committed
700
			if err := generate(cmd, model, line, wordWrap, format); err != nil {
Patrick Devine's avatar
Patrick Devine committed
701
702
				return err
			}
Michael Yang's avatar
Michael Yang committed
703
704
		}
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
705
706
}

707
func RunServer(cmd *cobra.Command, _ []string) error {
Michael Yang's avatar
Michael Yang committed
708
709
710
	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
711
		if ip := net.ParseIP(strings.Trim(os.Getenv("OLLAMA_HOST"), "[]")); ip != nil {
Michael Yang's avatar
Michael Yang committed
712
713
			host = ip.String()
		}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
714
	}
715

Michael Yang's avatar
Michael Yang committed
716
	if err := initializeKeypair(); err != nil {
717
718
719
		return err
	}

Michael Yang's avatar
Michael Yang committed
720
	ln, err := net.Listen("tcp", net.JoinHostPort(host, port))
721
722
723
	if err != nil {
		return err
	}
Jeffrey Morgan's avatar
Jeffrey Morgan committed
724

725
726
727
728
729
	var origins []string
	if o := os.Getenv("OLLAMA_ORIGINS"); o != "" {
		origins = strings.Split(o, ",")
	}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
730
	return server.Serve(ln, origins)
Jeffrey Morgan's avatar
Jeffrey Morgan committed
731
732
}

733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
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
755
		err = os.MkdirAll(filepath.Dir(privKeyPath), 0o755)
756
757
758
759
		if err != nil {
			return fmt.Errorf("could not create directory %w", err)
		}

760
		err = os.WriteFile(privKeyPath, pem.EncodeToMemory(privKeyBytes), 0o600)
761
762
763
764
765
766
767
768
769
770
771
		if err != nil {
			return err
		}

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

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

772
		err = os.WriteFile(pubKeyPath, pubKeyData, 0o644)
773
774
775
776
777
778
779
780
781
		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
782
func startMacApp(client *api.Client) error {
Bruce MacDonald's avatar
Bruce MacDonald committed
783
784
785
786
787
	exe, err := os.Executable()
	if err != nil {
		return err
	}
	link, err := os.Readlink(exe)
Bruce MacDonald's avatar
Bruce MacDonald committed
788
789
790
	if err != nil {
		return err
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
791
792
793
	if !strings.Contains(link, "Ollama.app") {
		return fmt.Errorf("could not find ollama app")
	}
Bruce MacDonald's avatar
Bruce MacDonald committed
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
	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
			}
		}
	}
}

813
func checkServerHeartbeat(_ *cobra.Command, _ []string) error {
Michael Yang's avatar
Michael Yang committed
814
	client, err := api.ClientFromEnvironment()
815
816
817
	if err != nil {
		return err
	}
818
	if err := client.Heartbeat(context.Background()); err != nil {
Bruce MacDonald's avatar
Bruce MacDonald committed
819
820
821
822
823
		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
824
				return fmt.Errorf("could not connect to ollama app, is it running?")
825
			}
Bruce MacDonald's avatar
Bruce MacDonald committed
826
		} else {
827
828
829
830
831
832
			return fmt.Errorf("could not connect to ollama server, run 'ollama serve' to start it")
		}
	}
	return nil
}

Jeffrey Morgan's avatar
Jeffrey Morgan committed
833
834
835
836
func NewCLI() *cobra.Command {
	log.SetFlags(log.LstdFlags | log.Lshortfile)

	rootCmd := &cobra.Command{
837
838
839
840
		Use:           "ollama",
		Short:         "Large language model runner",
		SilenceUsage:  true,
		SilenceErrors: true,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
841
842
843
		CompletionOptions: cobra.CompletionOptions{
			DisableDefaultCmd: true,
		},
Michael Yang's avatar
Michael Yang committed
844
		Version: version.Version,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
845
846
847
848
	}

	cobra.EnableCommandSorting = false

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

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

Patrick Devine's avatar
Patrick Devine committed
859
860
861
	showCmd := &cobra.Command{
		Use:     "show MODEL",
		Short:   "Show information for a model",
Michael Yang's avatar
Michael Yang committed
862
		Args:    cobra.ExactArgs(1),
Patrick Devine's avatar
Patrick Devine committed
863
864
865
866
867
868
869
870
871
872
		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
873
	runCmd := &cobra.Command{
874
875
876
877
878
		Use:     "run MODEL [PROMPT]",
		Short:   "Run a model",
		Args:    cobra.MinimumNArgs(1),
		PreRunE: checkServerHeartbeat,
		RunE:    RunHandler,
Jeffrey Morgan's avatar
Jeffrey Morgan committed
879
880
	}

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

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

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

902
903
	pullCmd.Flags().Bool("insecure", false, "Use an insecure registry")

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

912
913
	pushCmd.Flags().Bool("insecure", false, "Use an insecure registry")

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

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

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

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

	return rootCmd
}