cmd.go 22 KB

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