cmd.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950
  1. package cmd
  2. import (
  3. "context"
  4. "crypto/ed25519"
  5. "crypto/rand"
  6. "encoding/pem"
  7. "errors"
  8. "fmt"
  9. "io"
  10. "log"
  11. "net"
  12. "net/http"
  13. "os"
  14. "os/exec"
  15. "os/signal"
  16. "path/filepath"
  17. "runtime"
  18. "strings"
  19. "syscall"
  20. "time"
  21. "github.com/olekukonko/tablewriter"
  22. "github.com/spf13/cobra"
  23. "golang.org/x/crypto/ssh"
  24. "golang.org/x/term"
  25. "github.com/jmorganca/ollama/api"
  26. "github.com/jmorganca/ollama/format"
  27. "github.com/jmorganca/ollama/progressbar"
  28. "github.com/jmorganca/ollama/readline"
  29. "github.com/jmorganca/ollama/server"
  30. "github.com/jmorganca/ollama/version"
  31. )
  32. func CreateHandler(cmd *cobra.Command, args []string) error {
  33. filename, _ := cmd.Flags().GetString("file")
  34. filename, err := filepath.Abs(filename)
  35. if err != nil {
  36. return err
  37. }
  38. client, err := api.ClientFromEnvironment()
  39. if err != nil {
  40. return err
  41. }
  42. var spinner *Spinner
  43. var currentDigest string
  44. var bar *progressbar.ProgressBar
  45. request := api.CreateRequest{Name: args[0], Path: filename}
  46. fn := func(resp api.ProgressResponse) error {
  47. if resp.Digest != currentDigest && resp.Digest != "" {
  48. if spinner != nil {
  49. spinner.Stop()
  50. }
  51. currentDigest = resp.Digest
  52. // pulling
  53. bar = progressbar.DefaultBytes(
  54. resp.Total,
  55. resp.Status,
  56. )
  57. bar.Set64(resp.Completed)
  58. } else if resp.Digest == currentDigest && resp.Digest != "" {
  59. bar.Set64(resp.Completed)
  60. } else {
  61. currentDigest = ""
  62. if spinner != nil {
  63. spinner.Stop()
  64. }
  65. spinner = NewSpinner(resp.Status)
  66. go spinner.Spin(100 * time.Millisecond)
  67. }
  68. return nil
  69. }
  70. if err := client.Create(context.Background(), &request, fn); err != nil {
  71. return err
  72. }
  73. if spinner != nil {
  74. spinner.Stop()
  75. if spinner.description != "success" {
  76. return errors.New("unexpected end to create model")
  77. }
  78. }
  79. return nil
  80. }
  81. func RunHandler(cmd *cobra.Command, args []string) error {
  82. client, err := api.ClientFromEnvironment()
  83. if err != nil {
  84. return err
  85. }
  86. name := args[0]
  87. // check if the model exists on the server
  88. _, err = client.Show(context.Background(), &api.ShowRequest{Name: name})
  89. var statusError api.StatusError
  90. switch {
  91. case errors.As(err, &statusError) && statusError.StatusCode == http.StatusNotFound:
  92. if err := PullHandler(cmd, args); err != nil {
  93. return err
  94. }
  95. case err != nil:
  96. return err
  97. }
  98. return RunGenerate(cmd, args)
  99. }
  100. func PushHandler(cmd *cobra.Command, args []string) error {
  101. client, err := api.ClientFromEnvironment()
  102. if err != nil {
  103. return err
  104. }
  105. insecure, err := cmd.Flags().GetBool("insecure")
  106. if err != nil {
  107. return err
  108. }
  109. var currentDigest string
  110. var bar *progressbar.ProgressBar
  111. request := api.PushRequest{Name: args[0], Insecure: insecure}
  112. fn := func(resp api.ProgressResponse) error {
  113. if resp.Digest != currentDigest && resp.Digest != "" {
  114. currentDigest = resp.Digest
  115. bar = progressbar.DefaultBytes(
  116. resp.Total,
  117. fmt.Sprintf("pushing %s...", resp.Digest[7:19]),
  118. )
  119. bar.Set64(resp.Completed)
  120. } else if resp.Digest == currentDigest && resp.Digest != "" {
  121. bar.Set64(resp.Completed)
  122. } else {
  123. currentDigest = ""
  124. fmt.Println(resp.Status)
  125. }
  126. return nil
  127. }
  128. if err := client.Push(context.Background(), &request, fn); err != nil {
  129. return err
  130. }
  131. if bar != nil && !bar.IsFinished() {
  132. return errors.New("unexpected end to push model")
  133. }
  134. return nil
  135. }
  136. func ListHandler(cmd *cobra.Command, args []string) error {
  137. client, err := api.ClientFromEnvironment()
  138. if err != nil {
  139. return err
  140. }
  141. models, err := client.List(context.Background())
  142. if err != nil {
  143. return err
  144. }
  145. var data [][]string
  146. for _, m := range models.Models {
  147. if len(args) == 0 || strings.HasPrefix(m.Name, args[0]) {
  148. data = append(data, []string{m.Name, m.Digest[:12], format.HumanBytes(m.Size), format.HumanTime(m.ModifiedAt, "Never")})
  149. }
  150. }
  151. table := tablewriter.NewWriter(os.Stdout)
  152. table.SetHeader([]string{"NAME", "ID", "SIZE", "MODIFIED"})
  153. table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
  154. table.SetAlignment(tablewriter.ALIGN_LEFT)
  155. table.SetHeaderLine(false)
  156. table.SetBorder(false)
  157. table.SetNoWhiteSpace(true)
  158. table.SetTablePadding("\t")
  159. table.AppendBulk(data)
  160. table.Render()
  161. return nil
  162. }
  163. func DeleteHandler(cmd *cobra.Command, args []string) error {
  164. client, err := api.ClientFromEnvironment()
  165. if err != nil {
  166. return err
  167. }
  168. for _, name := range args {
  169. req := api.DeleteRequest{Name: name}
  170. if err := client.Delete(context.Background(), &req); err != nil {
  171. return err
  172. }
  173. fmt.Printf("deleted '%s'\n", name)
  174. }
  175. return nil
  176. }
  177. func ShowHandler(cmd *cobra.Command, args []string) error {
  178. client, err := api.ClientFromEnvironment()
  179. if err != nil {
  180. return err
  181. }
  182. if len(args) != 1 {
  183. return errors.New("missing model name")
  184. }
  185. license, errLicense := cmd.Flags().GetBool("license")
  186. modelfile, errModelfile := cmd.Flags().GetBool("modelfile")
  187. parameters, errParams := cmd.Flags().GetBool("parameters")
  188. system, errSystem := cmd.Flags().GetBool("system")
  189. template, errTemplate := cmd.Flags().GetBool("template")
  190. for _, boolErr := range []error{errLicense, errModelfile, errParams, errSystem, errTemplate} {
  191. if boolErr != nil {
  192. return errors.New("error retrieving flags")
  193. }
  194. }
  195. flagsSet := 0
  196. showType := ""
  197. if license {
  198. flagsSet++
  199. showType = "license"
  200. }
  201. if modelfile {
  202. flagsSet++
  203. showType = "modelfile"
  204. }
  205. if parameters {
  206. flagsSet++
  207. showType = "parameters"
  208. }
  209. if system {
  210. flagsSet++
  211. showType = "system"
  212. }
  213. if template {
  214. flagsSet++
  215. showType = "template"
  216. }
  217. if flagsSet > 1 {
  218. return errors.New("only one of '--license', '--modelfile', '--parameters', '--system', or '--template' can be specified")
  219. } else if flagsSet == 0 {
  220. return errors.New("one of '--license', '--modelfile', '--parameters', '--system', or '--template' must be specified")
  221. }
  222. req := api.ShowRequest{Name: args[0]}
  223. resp, err := client.Show(context.Background(), &req)
  224. if err != nil {
  225. return err
  226. }
  227. switch showType {
  228. case "license":
  229. fmt.Println(resp.License)
  230. case "modelfile":
  231. fmt.Println(resp.Modelfile)
  232. case "parameters":
  233. fmt.Println(resp.Parameters)
  234. case "system":
  235. fmt.Println(resp.System)
  236. case "template":
  237. fmt.Println(resp.Template)
  238. }
  239. return nil
  240. }
  241. func CopyHandler(cmd *cobra.Command, args []string) error {
  242. client, err := api.ClientFromEnvironment()
  243. if err != nil {
  244. return err
  245. }
  246. req := api.CopyRequest{Source: args[0], Destination: args[1]}
  247. if err := client.Copy(context.Background(), &req); err != nil {
  248. return err
  249. }
  250. fmt.Printf("copied '%s' to '%s'\n", args[0], args[1])
  251. return nil
  252. }
  253. func PullHandler(cmd *cobra.Command, args []string) error {
  254. insecure, err := cmd.Flags().GetBool("insecure")
  255. if err != nil {
  256. return err
  257. }
  258. return pull(args[0], insecure)
  259. }
  260. func pull(model string, insecure bool) error {
  261. client, err := api.ClientFromEnvironment()
  262. if err != nil {
  263. return err
  264. }
  265. var currentDigest string
  266. var bar *progressbar.ProgressBar
  267. request := api.PullRequest{Name: model, Insecure: insecure}
  268. fn := func(resp api.ProgressResponse) error {
  269. if resp.Digest != currentDigest && resp.Digest != "" {
  270. currentDigest = resp.Digest
  271. bar = progressbar.DefaultBytes(
  272. resp.Total,
  273. fmt.Sprintf("pulling %s...", resp.Digest[7:19]),
  274. )
  275. bar.Set64(resp.Completed)
  276. } else if resp.Digest == currentDigest && resp.Digest != "" {
  277. bar.Set64(resp.Completed)
  278. } else {
  279. currentDigest = ""
  280. fmt.Println(resp.Status)
  281. }
  282. return nil
  283. }
  284. if err := client.Pull(context.Background(), &request, fn); err != nil {
  285. return err
  286. }
  287. if bar != nil && !bar.IsFinished() {
  288. return errors.New("unexpected end to pull model")
  289. }
  290. return nil
  291. }
  292. func RunGenerate(cmd *cobra.Command, args []string) error {
  293. format, err := cmd.Flags().GetString("format")
  294. if err != nil {
  295. return err
  296. }
  297. prompts := args[1:]
  298. // prepend stdin to the prompt if provided
  299. if !term.IsTerminal(int(os.Stdin.Fd())) {
  300. in, err := io.ReadAll(os.Stdin)
  301. if err != nil {
  302. return err
  303. }
  304. prompts = append([]string{string(in)}, prompts...)
  305. }
  306. // output is being piped
  307. if !term.IsTerminal(int(os.Stdout.Fd())) {
  308. return generate(cmd, args[0], strings.Join(prompts, " "), false, format)
  309. }
  310. wordWrap := os.Getenv("TERM") == "xterm-256color"
  311. nowrap, err := cmd.Flags().GetBool("nowordwrap")
  312. if err != nil {
  313. return err
  314. }
  315. if nowrap {
  316. wordWrap = false
  317. }
  318. // prompts are provided via stdin or args so don't enter interactive mode
  319. if len(prompts) > 0 {
  320. return generate(cmd, args[0], strings.Join(prompts, " "), wordWrap, format)
  321. }
  322. return generateInteractive(cmd, args[0], wordWrap, format)
  323. }
  324. type generateContextKey string
  325. func generate(cmd *cobra.Command, model, prompt string, wordWrap bool, format string) error {
  326. client, err := api.ClientFromEnvironment()
  327. if err != nil {
  328. return err
  329. }
  330. spinner := NewSpinner("")
  331. go spinner.Spin(60 * time.Millisecond)
  332. var latest api.GenerateResponse
  333. generateContext, ok := cmd.Context().Value(generateContextKey("context")).([]int)
  334. if !ok {
  335. generateContext = []int{}
  336. }
  337. termWidth, _, err := term.GetSize(int(os.Stdout.Fd()))
  338. if err != nil {
  339. wordWrap = false
  340. }
  341. cancelCtx, cancel := context.WithCancel(context.Background())
  342. defer cancel()
  343. sigChan := make(chan os.Signal, 1)
  344. signal.Notify(sigChan, syscall.SIGINT)
  345. var abort bool
  346. go func() {
  347. <-sigChan
  348. cancel()
  349. abort = true
  350. }()
  351. var currentLineLength int
  352. var wordBuffer string
  353. request := api.GenerateRequest{Model: model, Prompt: prompt, Context: generateContext, Format: format}
  354. fn := func(response api.GenerateResponse) error {
  355. if !spinner.IsFinished() {
  356. spinner.Finish()
  357. }
  358. latest = response
  359. if wordWrap {
  360. for _, ch := range response.Response {
  361. if currentLineLength+1 > termWidth-5 {
  362. // backtrack the length of the last word and clear to the end of the line
  363. fmt.Printf("\x1b[%dD\x1b[K\n", len(wordBuffer))
  364. fmt.Printf("%s%c", wordBuffer, ch)
  365. currentLineLength = len(wordBuffer) + 1
  366. } else {
  367. fmt.Print(string(ch))
  368. currentLineLength += 1
  369. switch ch {
  370. case ' ':
  371. wordBuffer = ""
  372. case '\n':
  373. currentLineLength = 0
  374. default:
  375. wordBuffer += string(ch)
  376. }
  377. }
  378. }
  379. } else {
  380. fmt.Print(response.Response)
  381. }
  382. return nil
  383. }
  384. if err := client.Generate(cancelCtx, &request, fn); err != nil {
  385. if strings.Contains(err.Error(), "context canceled") && abort {
  386. spinner.Finish()
  387. return nil
  388. }
  389. return err
  390. }
  391. if prompt != "" {
  392. fmt.Println()
  393. fmt.Println()
  394. }
  395. if !latest.Done {
  396. if abort {
  397. return nil
  398. }
  399. return errors.New("unexpected end of response")
  400. }
  401. verbose, err := cmd.Flags().GetBool("verbose")
  402. if err != nil {
  403. return err
  404. }
  405. if verbose {
  406. latest.Summary()
  407. }
  408. ctx := cmd.Context()
  409. ctx = context.WithValue(ctx, generateContextKey("context"), latest.Context)
  410. cmd.SetContext(ctx)
  411. return nil
  412. }
  413. func generateInteractive(cmd *cobra.Command, model string, wordWrap bool, format string) error {
  414. // load the model
  415. if err := generate(cmd, model, "", false, ""); err != nil {
  416. return err
  417. }
  418. usage := func() {
  419. fmt.Fprintln(os.Stderr, "Available Commands:")
  420. fmt.Fprintln(os.Stderr, " /set Set session variables")
  421. fmt.Fprintln(os.Stderr, " /show Show model information")
  422. fmt.Fprintln(os.Stderr, " /bye Exit")
  423. fmt.Fprintln(os.Stderr, " /?, /help Help for a command")
  424. fmt.Fprintln(os.Stderr, "")
  425. fmt.Fprintln(os.Stderr, "Use \"\"\" to begin a multi-line message.")
  426. fmt.Fprintln(os.Stderr, "")
  427. }
  428. usageSet := func() {
  429. fmt.Fprintln(os.Stderr, "Available Commands:")
  430. fmt.Fprintln(os.Stderr, " /set history Enable history")
  431. fmt.Fprintln(os.Stderr, " /set nohistory Disable history")
  432. fmt.Fprintln(os.Stderr, " /set wordwrap Enable wordwrap")
  433. fmt.Fprintln(os.Stderr, " /set nowordwrap Disable wordwrap")
  434. fmt.Fprintln(os.Stderr, " /set format json Enable JSON mode")
  435. fmt.Fprintln(os.Stderr, " /set noformat Disable formatting")
  436. fmt.Fprintln(os.Stderr, " /set verbose Show LLM stats")
  437. fmt.Fprintln(os.Stderr, " /set quiet Disable LLM stats")
  438. fmt.Fprintln(os.Stderr, "")
  439. }
  440. usageShow := func() {
  441. fmt.Fprintln(os.Stderr, "Available Commands:")
  442. fmt.Fprintln(os.Stderr, " /show license Show model license")
  443. fmt.Fprintln(os.Stderr, " /show modelfile Show Modelfile for this model")
  444. fmt.Fprintln(os.Stderr, " /show parameters Show parameters for this model")
  445. fmt.Fprintln(os.Stderr, " /show system Show system prompt")
  446. fmt.Fprintln(os.Stderr, " /show template Show prompt template")
  447. fmt.Fprintln(os.Stderr, "")
  448. }
  449. prompt := readline.Prompt{
  450. Prompt: ">>> ",
  451. AltPrompt: "... ",
  452. Placeholder: "Send a message (/? for help)",
  453. AltPlaceholder: `Use """ to end multi-line input`,
  454. }
  455. scanner, err := readline.New(prompt)
  456. if err != nil {
  457. return err
  458. }
  459. fmt.Print(readline.StartBracketedPaste)
  460. defer fmt.Printf(readline.EndBracketedPaste)
  461. var multiLineBuffer string
  462. for {
  463. line, err := scanner.Readline()
  464. switch {
  465. case errors.Is(err, io.EOF):
  466. fmt.Println()
  467. return nil
  468. case errors.Is(err, readline.ErrInterrupt):
  469. if line == "" {
  470. fmt.Println("\nUse Ctrl-D or /bye to exit.")
  471. }
  472. continue
  473. case err != nil:
  474. return err
  475. }
  476. line = strings.TrimSpace(line)
  477. switch {
  478. case scanner.Prompt.UseAlt:
  479. if strings.HasSuffix(line, `"""`) {
  480. scanner.Prompt.UseAlt = false
  481. multiLineBuffer += strings.TrimSuffix(line, `"""`)
  482. line = multiLineBuffer
  483. multiLineBuffer = ""
  484. } else {
  485. multiLineBuffer += line + " "
  486. continue
  487. }
  488. case strings.HasPrefix(line, `"""`):
  489. scanner.Prompt.UseAlt = true
  490. multiLineBuffer = strings.TrimPrefix(line, `"""`) + " "
  491. continue
  492. case strings.HasPrefix(line, "/list"):
  493. args := strings.Fields(line)
  494. if err := ListHandler(cmd, args[1:]); err != nil {
  495. return err
  496. }
  497. case strings.HasPrefix(line, "/set"):
  498. args := strings.Fields(line)
  499. if len(args) > 1 {
  500. switch args[1] {
  501. case "history":
  502. scanner.HistoryEnable()
  503. case "nohistory":
  504. scanner.HistoryDisable()
  505. case "wordwrap":
  506. wordWrap = true
  507. fmt.Println("Set 'wordwrap' mode.")
  508. case "nowordwrap":
  509. wordWrap = false
  510. fmt.Println("Set 'nowordwrap' mode.")
  511. case "verbose":
  512. cmd.Flags().Set("verbose", "true")
  513. fmt.Println("Set 'verbose' mode.")
  514. case "quiet":
  515. cmd.Flags().Set("verbose", "false")
  516. fmt.Println("Set 'quiet' mode.")
  517. case "format":
  518. if len(args) < 3 || args[2] != "json" {
  519. fmt.Println("Invalid or missing format. For 'json' mode use '/set format json'")
  520. } else {
  521. format = args[2]
  522. fmt.Printf("Set format to '%s' mode.\n", args[2])
  523. }
  524. case "noformat":
  525. format = ""
  526. fmt.Println("Disabled format.")
  527. default:
  528. fmt.Printf("Unknown command '/set %s'. Type /? for help\n", args[1])
  529. }
  530. } else {
  531. usageSet()
  532. }
  533. case strings.HasPrefix(line, "/show"):
  534. args := strings.Fields(line)
  535. if len(args) > 1 {
  536. client, err := api.ClientFromEnvironment()
  537. if err != nil {
  538. fmt.Println("error: couldn't connect to ollama server")
  539. return err
  540. }
  541. resp, err := client.Show(cmd.Context(), &api.ShowRequest{Name: model})
  542. if err != nil {
  543. fmt.Println("error: couldn't get model")
  544. return err
  545. }
  546. switch args[1] {
  547. case "license":
  548. if resp.License == "" {
  549. fmt.Print("No license was specified for this model.\n\n")
  550. } else {
  551. fmt.Println(resp.License)
  552. }
  553. case "modelfile":
  554. fmt.Println(resp.Modelfile)
  555. case "parameters":
  556. if resp.Parameters == "" {
  557. fmt.Print("No parameters were specified for this model.\n\n")
  558. } else {
  559. fmt.Println(resp.Parameters)
  560. }
  561. case "system":
  562. if resp.System == "" {
  563. fmt.Print("No system prompt was specified for this model.\n\n")
  564. } else {
  565. fmt.Println(resp.System)
  566. }
  567. case "template":
  568. if resp.Template == "" {
  569. fmt.Print("No prompt template was specified for this model.\n\n")
  570. } else {
  571. fmt.Println(resp.Template)
  572. }
  573. default:
  574. fmt.Printf("Unknown command '/show %s'. Type /? for help\n", args[1])
  575. }
  576. } else {
  577. usageShow()
  578. }
  579. case strings.HasPrefix(line, "/help"), strings.HasPrefix(line, "/?"):
  580. args := strings.Fields(line)
  581. if len(args) > 1 {
  582. switch args[1] {
  583. case "set", "/set":
  584. usageSet()
  585. case "show", "/show":
  586. usageShow()
  587. }
  588. } else {
  589. usage()
  590. }
  591. case line == "/exit", line == "/bye":
  592. return nil
  593. case strings.HasPrefix(line, "/"):
  594. args := strings.Fields(line)
  595. fmt.Printf("Unknown command '%s'. Type /? for help\n", args[0])
  596. }
  597. if len(line) > 0 && line[0] != '/' {
  598. if err := generate(cmd, model, line, wordWrap, format); err != nil {
  599. return err
  600. }
  601. }
  602. }
  603. }
  604. func RunServer(cmd *cobra.Command, _ []string) error {
  605. host, port, err := net.SplitHostPort(os.Getenv("OLLAMA_HOST"))
  606. if err != nil {
  607. host, port = "127.0.0.1", "11434"
  608. if ip := net.ParseIP(strings.Trim(os.Getenv("OLLAMA_HOST"), "[]")); ip != nil {
  609. host = ip.String()
  610. }
  611. }
  612. if err := initializeKeypair(); err != nil {
  613. return err
  614. }
  615. ln, err := net.Listen("tcp", net.JoinHostPort(host, port))
  616. if err != nil {
  617. return err
  618. }
  619. var origins []string
  620. if o := os.Getenv("OLLAMA_ORIGINS"); o != "" {
  621. origins = strings.Split(o, ",")
  622. }
  623. return server.Serve(ln, origins)
  624. }
  625. func initializeKeypair() error {
  626. home, err := os.UserHomeDir()
  627. if err != nil {
  628. return err
  629. }
  630. privKeyPath := filepath.Join(home, ".ollama", "id_ed25519")
  631. pubKeyPath := filepath.Join(home, ".ollama", "id_ed25519.pub")
  632. _, err = os.Stat(privKeyPath)
  633. if os.IsNotExist(err) {
  634. fmt.Printf("Couldn't find '%s'. Generating new private key.\n", privKeyPath)
  635. _, privKey, err := ed25519.GenerateKey(rand.Reader)
  636. if err != nil {
  637. return err
  638. }
  639. privKeyBytes, err := format.OpenSSHPrivateKey(privKey, "")
  640. if err != nil {
  641. return err
  642. }
  643. err = os.MkdirAll(filepath.Dir(privKeyPath), 0o755)
  644. if err != nil {
  645. return fmt.Errorf("could not create directory %w", err)
  646. }
  647. err = os.WriteFile(privKeyPath, pem.EncodeToMemory(privKeyBytes), 0o600)
  648. if err != nil {
  649. return err
  650. }
  651. sshPrivateKey, err := ssh.NewSignerFromKey(privKey)
  652. if err != nil {
  653. return err
  654. }
  655. pubKeyData := ssh.MarshalAuthorizedKey(sshPrivateKey.PublicKey())
  656. err = os.WriteFile(pubKeyPath, pubKeyData, 0o644)
  657. if err != nil {
  658. return err
  659. }
  660. fmt.Printf("Your new public key is: \n\n%s\n", string(pubKeyData))
  661. }
  662. return nil
  663. }
  664. func startMacApp(client *api.Client) error {
  665. exe, err := os.Executable()
  666. if err != nil {
  667. return err
  668. }
  669. link, err := os.Readlink(exe)
  670. if err != nil {
  671. return err
  672. }
  673. if !strings.Contains(link, "Ollama.app") {
  674. return fmt.Errorf("could not find ollama app")
  675. }
  676. path := strings.Split(link, "Ollama.app")
  677. if err := exec.Command("/usr/bin/open", "-a", path[0]+"Ollama.app").Run(); err != nil {
  678. return err
  679. }
  680. // wait for the server to start
  681. timeout := time.After(5 * time.Second)
  682. tick := time.Tick(500 * time.Millisecond)
  683. for {
  684. select {
  685. case <-timeout:
  686. return errors.New("timed out waiting for server to start")
  687. case <-tick:
  688. if err := client.Heartbeat(context.Background()); err == nil {
  689. return nil // server has started
  690. }
  691. }
  692. }
  693. }
  694. func checkServerHeartbeat(_ *cobra.Command, _ []string) error {
  695. client, err := api.ClientFromEnvironment()
  696. if err != nil {
  697. return err
  698. }
  699. if err := client.Heartbeat(context.Background()); err != nil {
  700. if !strings.Contains(err.Error(), "connection refused") {
  701. return err
  702. }
  703. if runtime.GOOS == "darwin" {
  704. if err := startMacApp(client); err != nil {
  705. return fmt.Errorf("could not connect to ollama app, is it running?")
  706. }
  707. } else {
  708. return fmt.Errorf("could not connect to ollama server, run 'ollama serve' to start it")
  709. }
  710. }
  711. return nil
  712. }
  713. func NewCLI() *cobra.Command {
  714. log.SetFlags(log.LstdFlags | log.Lshortfile)
  715. rootCmd := &cobra.Command{
  716. Use: "ollama",
  717. Short: "Large language model runner",
  718. SilenceUsage: true,
  719. SilenceErrors: true,
  720. CompletionOptions: cobra.CompletionOptions{
  721. DisableDefaultCmd: true,
  722. },
  723. Version: version.Version,
  724. }
  725. cobra.EnableCommandSorting = false
  726. createCmd := &cobra.Command{
  727. Use: "create MODEL",
  728. Short: "Create a model from a Modelfile",
  729. Args: cobra.ExactArgs(1),
  730. PreRunE: checkServerHeartbeat,
  731. RunE: CreateHandler,
  732. }
  733. createCmd.Flags().StringP("file", "f", "Modelfile", "Name of the Modelfile (default \"Modelfile\")")
  734. showCmd := &cobra.Command{
  735. Use: "show MODEL",
  736. Short: "Show information for a model",
  737. Args: cobra.ExactArgs(1),
  738. PreRunE: checkServerHeartbeat,
  739. RunE: ShowHandler,
  740. }
  741. showCmd.Flags().Bool("license", false, "Show license of a model")
  742. showCmd.Flags().Bool("modelfile", false, "Show Modelfile of a model")
  743. showCmd.Flags().Bool("parameters", false, "Show parameters of a model")
  744. showCmd.Flags().Bool("template", false, "Show template of a model")
  745. showCmd.Flags().Bool("system", false, "Show system prompt of a model")
  746. runCmd := &cobra.Command{
  747. Use: "run MODEL [PROMPT]",
  748. Short: "Run a model",
  749. Args: cobra.MinimumNArgs(1),
  750. PreRunE: checkServerHeartbeat,
  751. RunE: RunHandler,
  752. }
  753. runCmd.Flags().Bool("verbose", false, "Show timings for response")
  754. runCmd.Flags().Bool("insecure", false, "Use an insecure registry")
  755. runCmd.Flags().Bool("nowordwrap", false, "Don't wrap words to the next line automatically")
  756. runCmd.Flags().String("format", "", "Response format (e.g. json)")
  757. serveCmd := &cobra.Command{
  758. Use: "serve",
  759. Aliases: []string{"start"},
  760. Short: "Start ollama",
  761. Args: cobra.ExactArgs(0),
  762. RunE: RunServer,
  763. }
  764. pullCmd := &cobra.Command{
  765. Use: "pull MODEL",
  766. Short: "Pull a model from a registry",
  767. Args: cobra.ExactArgs(1),
  768. PreRunE: checkServerHeartbeat,
  769. RunE: PullHandler,
  770. }
  771. pullCmd.Flags().Bool("insecure", false, "Use an insecure registry")
  772. pushCmd := &cobra.Command{
  773. Use: "push MODEL",
  774. Short: "Push a model to a registry",
  775. Args: cobra.ExactArgs(1),
  776. PreRunE: checkServerHeartbeat,
  777. RunE: PushHandler,
  778. }
  779. pushCmd.Flags().Bool("insecure", false, "Use an insecure registry")
  780. listCmd := &cobra.Command{
  781. Use: "list",
  782. Aliases: []string{"ls"},
  783. Short: "List models",
  784. PreRunE: checkServerHeartbeat,
  785. RunE: ListHandler,
  786. }
  787. copyCmd := &cobra.Command{
  788. Use: "cp SOURCE TARGET",
  789. Short: "Copy a model",
  790. Args: cobra.ExactArgs(2),
  791. PreRunE: checkServerHeartbeat,
  792. RunE: CopyHandler,
  793. }
  794. deleteCmd := &cobra.Command{
  795. Use: "rm MODEL [MODEL...]",
  796. Short: "Remove a model",
  797. Args: cobra.MinimumNArgs(1),
  798. PreRunE: checkServerHeartbeat,
  799. RunE: DeleteHandler,
  800. }
  801. rootCmd.AddCommand(
  802. serveCmd,
  803. createCmd,
  804. showCmd,
  805. runCmd,
  806. pullCmd,
  807. pushCmd,
  808. listCmd,
  809. copyCmd,
  810. deleteCmd,
  811. )
  812. return rootCmd
  813. }