cmd.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822
  1. package cmd
  2. import (
  3. "bufio"
  4. "context"
  5. "crypto/ed25519"
  6. "crypto/rand"
  7. "encoding/pem"
  8. "errors"
  9. "fmt"
  10. "io"
  11. "log"
  12. "net"
  13. "net/http"
  14. "os"
  15. "os/exec"
  16. "path"
  17. "path/filepath"
  18. "runtime"
  19. "strings"
  20. "time"
  21. "github.com/chzyer/readline"
  22. "github.com/dustin/go-humanize"
  23. "github.com/olekukonko/tablewriter"
  24. "github.com/spf13/cobra"
  25. "golang.org/x/crypto/ssh"
  26. "github.com/jmorganca/ollama/api"
  27. "github.com/jmorganca/ollama/format"
  28. "github.com/jmorganca/ollama/progressbar"
  29. "github.com/jmorganca/ollama/server"
  30. )
  31. func CreateHandler(cmd *cobra.Command, args []string) error {
  32. filename, _ := cmd.Flags().GetString("file")
  33. filename, err := filepath.Abs(filename)
  34. if err != nil {
  35. return err
  36. }
  37. client, err := api.FromEnv()
  38. if err != nil {
  39. return err
  40. }
  41. var spinner *Spinner
  42. var currentDigest string
  43. var bar *progressbar.ProgressBar
  44. request := api.CreateRequest{Name: args[0], Path: filename}
  45. fn := func(resp api.ProgressResponse) error {
  46. if resp.Digest != currentDigest && resp.Digest != "" {
  47. if spinner != nil {
  48. spinner.Stop()
  49. }
  50. currentDigest = resp.Digest
  51. switch {
  52. case strings.Contains(resp.Status, "embeddings"):
  53. bar = progressbar.Default(int64(resp.Total), resp.Status)
  54. bar.Set(resp.Completed)
  55. default:
  56. // pulling
  57. bar = progressbar.DefaultBytes(
  58. int64(resp.Total),
  59. resp.Status,
  60. )
  61. bar.Set(resp.Completed)
  62. }
  63. } else if resp.Digest == currentDigest && resp.Digest != "" {
  64. bar.Set(resp.Completed)
  65. } else {
  66. currentDigest = ""
  67. if spinner != nil {
  68. spinner.Stop()
  69. }
  70. spinner = NewSpinner(resp.Status)
  71. go spinner.Spin(100 * time.Millisecond)
  72. }
  73. return nil
  74. }
  75. if err := client.Create(context.Background(), &request, fn); err != nil {
  76. return err
  77. }
  78. if spinner != nil {
  79. spinner.Stop()
  80. if spinner.description != "success" {
  81. return errors.New("unexpected end to create model")
  82. }
  83. }
  84. return nil
  85. }
  86. func RunHandler(cmd *cobra.Command, args []string) error {
  87. insecure, err := cmd.Flags().GetBool("insecure")
  88. if err != nil {
  89. return err
  90. }
  91. mp := server.ParseModelPath(args[0])
  92. if err != nil {
  93. return err
  94. }
  95. if mp.ProtocolScheme == "http" && !insecure {
  96. return fmt.Errorf("insecure protocol http")
  97. }
  98. fp, err := mp.GetManifestPath(false)
  99. if err != nil {
  100. return err
  101. }
  102. _, err = os.Stat(fp)
  103. switch {
  104. case errors.Is(err, os.ErrNotExist):
  105. if err := pull(args[0], insecure); err != nil {
  106. var apiStatusError api.StatusError
  107. if !errors.As(err, &apiStatusError) {
  108. return err
  109. }
  110. if apiStatusError.StatusCode != http.StatusBadGateway {
  111. return err
  112. }
  113. }
  114. case err != nil:
  115. return err
  116. }
  117. return RunGenerate(cmd, args)
  118. }
  119. func PushHandler(cmd *cobra.Command, args []string) error {
  120. client, err := api.FromEnv()
  121. if err != nil {
  122. return err
  123. }
  124. insecure, err := cmd.Flags().GetBool("insecure")
  125. if err != nil {
  126. return err
  127. }
  128. var currentDigest string
  129. var bar *progressbar.ProgressBar
  130. request := api.PushRequest{Name: args[0], Insecure: insecure}
  131. fn := func(resp api.ProgressResponse) error {
  132. if resp.Digest != currentDigest && resp.Digest != "" {
  133. currentDigest = resp.Digest
  134. bar = progressbar.DefaultBytes(
  135. int64(resp.Total),
  136. fmt.Sprintf("pushing %s...", resp.Digest[7:19]),
  137. )
  138. bar.Set(resp.Completed)
  139. } else if resp.Digest == currentDigest && resp.Digest != "" {
  140. bar.Set(resp.Completed)
  141. } else {
  142. currentDigest = ""
  143. fmt.Println(resp.Status)
  144. }
  145. return nil
  146. }
  147. if err := client.Push(context.Background(), &request, fn); err != nil {
  148. return err
  149. }
  150. if bar != nil && !bar.IsFinished() {
  151. return errors.New("unexpected end to push model")
  152. }
  153. return nil
  154. }
  155. func ListHandler(cmd *cobra.Command, args []string) error {
  156. client, err := api.FromEnv()
  157. if err != nil {
  158. return err
  159. }
  160. models, err := client.List(context.Background())
  161. if err != nil {
  162. return err
  163. }
  164. var data [][]string
  165. for _, m := range models.Models {
  166. if len(args) == 0 || strings.HasPrefix(m.Name, args[0]) {
  167. data = append(data, []string{m.Name, humanize.Bytes(uint64(m.Size)), format.HumanTime(m.ModifiedAt, "Never")})
  168. }
  169. }
  170. table := tablewriter.NewWriter(os.Stdout)
  171. table.SetHeader([]string{"NAME", "SIZE", "MODIFIED"})
  172. table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
  173. table.SetAlignment(tablewriter.ALIGN_LEFT)
  174. table.SetHeaderLine(false)
  175. table.SetBorder(false)
  176. table.SetNoWhiteSpace(true)
  177. table.SetTablePadding("\t")
  178. table.AppendBulk(data)
  179. table.Render()
  180. return nil
  181. }
  182. func DeleteHandler(cmd *cobra.Command, args []string) error {
  183. client, err := api.FromEnv()
  184. if err != nil {
  185. return err
  186. }
  187. req := api.DeleteRequest{Name: args[0]}
  188. if err := client.Delete(context.Background(), &req); err != nil {
  189. return err
  190. }
  191. fmt.Printf("deleted '%s'\n", args[0])
  192. return nil
  193. }
  194. func CopyHandler(cmd *cobra.Command, args []string) error {
  195. client, err := api.FromEnv()
  196. if err != nil {
  197. return err
  198. }
  199. req := api.CopyRequest{Source: args[0], Destination: args[1]}
  200. if err := client.Copy(context.Background(), &req); err != nil {
  201. return err
  202. }
  203. fmt.Printf("copied '%s' to '%s'\n", args[0], args[1])
  204. return nil
  205. }
  206. func PullHandler(cmd *cobra.Command, args []string) error {
  207. insecure, err := cmd.Flags().GetBool("insecure")
  208. if err != nil {
  209. return err
  210. }
  211. return pull(args[0], insecure)
  212. }
  213. func pull(model string, insecure bool) error {
  214. client, err := api.FromEnv()
  215. if err != nil {
  216. return err
  217. }
  218. var currentDigest string
  219. var bar *progressbar.ProgressBar
  220. request := api.PullRequest{Name: model, Insecure: insecure}
  221. fn := func(resp api.ProgressResponse) error {
  222. if resp.Digest != currentDigest && resp.Digest != "" {
  223. currentDigest = resp.Digest
  224. bar = progressbar.DefaultBytes(
  225. int64(resp.Total),
  226. fmt.Sprintf("pulling %s...", resp.Digest[7:19]),
  227. )
  228. bar.Set(resp.Completed)
  229. } else if resp.Digest == currentDigest && resp.Digest != "" {
  230. bar.Set(resp.Completed)
  231. } else {
  232. currentDigest = ""
  233. fmt.Println(resp.Status)
  234. }
  235. return nil
  236. }
  237. if err := client.Pull(context.Background(), &request, fn); err != nil {
  238. return err
  239. }
  240. if bar != nil && !bar.IsFinished() {
  241. return errors.New("unexpected end to pull model")
  242. }
  243. return nil
  244. }
  245. func RunGenerate(cmd *cobra.Command, args []string) error {
  246. if len(args) > 1 {
  247. // join all args into a single prompt
  248. return generate(cmd, args[0], strings.Join(args[1:], " "))
  249. }
  250. if readline.IsTerminal(int(os.Stdin.Fd())) {
  251. return generateInteractive(cmd, args[0])
  252. }
  253. return generateBatch(cmd, args[0])
  254. }
  255. type generateContextKey string
  256. func generate(cmd *cobra.Command, model, prompt string) error {
  257. if len(strings.TrimSpace(prompt)) > 0 {
  258. client, err := api.FromEnv()
  259. if err != nil {
  260. return err
  261. }
  262. spinner := NewSpinner("")
  263. go spinner.Spin(60 * time.Millisecond)
  264. var latest api.GenerateResponse
  265. generateContext, ok := cmd.Context().Value(generateContextKey("context")).([]int)
  266. if !ok {
  267. generateContext = []int{}
  268. }
  269. request := api.GenerateRequest{Model: model, Prompt: prompt, Context: generateContext}
  270. fn := func(response api.GenerateResponse) error {
  271. if !spinner.IsFinished() {
  272. spinner.Finish()
  273. }
  274. latest = response
  275. fmt.Print(response.Response)
  276. return nil
  277. }
  278. if err := client.Generate(context.Background(), &request, fn); err != nil {
  279. if strings.Contains(err.Error(), "failed to load model") {
  280. // tell the user to check the server log, if it exists locally
  281. home, nestedErr := os.UserHomeDir()
  282. if nestedErr != nil {
  283. // return the original error
  284. return err
  285. }
  286. logPath := filepath.Join(home, ".ollama", "logs", "server.log")
  287. if _, nestedErr := os.Stat(logPath); nestedErr == nil {
  288. err = fmt.Errorf("%w\nFor more details, check the error logs at %s", err, logPath)
  289. }
  290. }
  291. return err
  292. }
  293. fmt.Println()
  294. fmt.Println()
  295. if !latest.Done {
  296. return errors.New("unexpected end of response")
  297. }
  298. verbose, err := cmd.Flags().GetBool("verbose")
  299. if err != nil {
  300. return err
  301. }
  302. if verbose {
  303. latest.Summary()
  304. }
  305. ctx := cmd.Context()
  306. ctx = context.WithValue(ctx, generateContextKey("context"), latest.Context)
  307. cmd.SetContext(ctx)
  308. }
  309. return nil
  310. }
  311. func showLayer(l *server.Layer) {
  312. filename, err := server.GetBlobsPath(l.Digest)
  313. if err != nil {
  314. fmt.Println("Couldn't get layer's path")
  315. return
  316. }
  317. bts, err := os.ReadFile(filename)
  318. if err != nil {
  319. fmt.Println("Couldn't read layer")
  320. return
  321. }
  322. fmt.Println(string(bts))
  323. }
  324. func generateInteractive(cmd *cobra.Command, model string) error {
  325. home, err := os.UserHomeDir()
  326. if err != nil {
  327. return err
  328. }
  329. completer := readline.NewPrefixCompleter(
  330. readline.PcItem("/help"),
  331. readline.PcItem("/list"),
  332. readline.PcItem("/set",
  333. readline.PcItem("history"),
  334. readline.PcItem("nohistory"),
  335. readline.PcItem("verbose"),
  336. readline.PcItem("quiet"),
  337. readline.PcItem("mode",
  338. readline.PcItem("vim"),
  339. readline.PcItem("emacs"),
  340. readline.PcItem("default"),
  341. ),
  342. ),
  343. readline.PcItem("/show",
  344. readline.PcItem("license"),
  345. readline.PcItem("system"),
  346. readline.PcItem("template"),
  347. ),
  348. readline.PcItem("/exit"),
  349. readline.PcItem("/bye"),
  350. )
  351. usage := func() {
  352. fmt.Fprintln(os.Stderr, "commands:")
  353. fmt.Fprintln(os.Stderr, completer.Tree(" "))
  354. }
  355. config := readline.Config{
  356. Prompt: ">>> ",
  357. HistoryFile: filepath.Join(home, ".ollama", "history"),
  358. AutoComplete: completer,
  359. }
  360. scanner, err := readline.NewEx(&config)
  361. if err != nil {
  362. return err
  363. }
  364. defer scanner.Close()
  365. var multiLineBuffer string
  366. var isMultiLine bool
  367. for {
  368. line, err := scanner.Readline()
  369. switch {
  370. case errors.Is(err, io.EOF):
  371. return nil
  372. case errors.Is(err, readline.ErrInterrupt):
  373. if line == "" {
  374. return nil
  375. }
  376. continue
  377. case err != nil:
  378. return err
  379. }
  380. line = strings.TrimSpace(line)
  381. switch {
  382. case isMultiLine:
  383. if strings.HasSuffix(line, `"""`) {
  384. isMultiLine = false
  385. multiLineBuffer += strings.TrimSuffix(line, `"""`)
  386. line = multiLineBuffer
  387. multiLineBuffer = ""
  388. scanner.SetPrompt(">>> ")
  389. } else {
  390. multiLineBuffer += line + " "
  391. continue
  392. }
  393. case strings.HasPrefix(line, `"""`):
  394. isMultiLine = true
  395. multiLineBuffer = strings.TrimPrefix(line, `"""`) + " "
  396. scanner.SetPrompt("... ")
  397. continue
  398. case strings.HasPrefix(line, "/list"):
  399. args := strings.Fields(line)
  400. if err := ListHandler(cmd, args[1:]); err != nil {
  401. return err
  402. }
  403. continue
  404. case strings.HasPrefix(line, "/set"):
  405. args := strings.Fields(line)
  406. if len(args) > 1 {
  407. switch args[1] {
  408. case "history":
  409. scanner.HistoryEnable()
  410. continue
  411. case "nohistory":
  412. scanner.HistoryDisable()
  413. continue
  414. case "verbose":
  415. cmd.Flags().Set("verbose", "true")
  416. continue
  417. case "quiet":
  418. cmd.Flags().Set("verbose", "false")
  419. continue
  420. case "mode":
  421. if len(args) > 2 {
  422. switch args[2] {
  423. case "vim":
  424. scanner.SetVimMode(true)
  425. continue
  426. case "emacs", "default":
  427. scanner.SetVimMode(false)
  428. continue
  429. default:
  430. usage()
  431. continue
  432. }
  433. } else {
  434. usage()
  435. continue
  436. }
  437. }
  438. } else {
  439. usage()
  440. continue
  441. }
  442. case strings.HasPrefix(line, "/show"):
  443. args := strings.Fields(line)
  444. if len(args) > 1 {
  445. mp := server.ParseModelPath(model)
  446. if err != nil {
  447. return err
  448. }
  449. manifest, err := server.GetManifest(mp)
  450. if err != nil {
  451. fmt.Println("error: couldn't get a manifest for this model")
  452. continue
  453. }
  454. switch args[1] {
  455. case "license":
  456. for _, l := range manifest.Layers {
  457. if l.MediaType == "application/vnd.ollama.image.license" {
  458. showLayer(l)
  459. }
  460. }
  461. continue
  462. case "system":
  463. for _, l := range manifest.Layers {
  464. if l.MediaType == "application/vnd.ollama.image.system" {
  465. showLayer(l)
  466. }
  467. }
  468. continue
  469. case "template":
  470. for _, l := range manifest.Layers {
  471. if l.MediaType == "application/vnd.ollama.image.template" {
  472. showLayer(l)
  473. }
  474. }
  475. continue
  476. default:
  477. usage()
  478. continue
  479. }
  480. } else {
  481. usage()
  482. continue
  483. }
  484. case line == "/help", line == "/?":
  485. usage()
  486. continue
  487. case line == "/exit", line == "/bye":
  488. return nil
  489. }
  490. if err := generate(cmd, model, line); err != nil {
  491. return err
  492. }
  493. }
  494. }
  495. func generateBatch(cmd *cobra.Command, model string) error {
  496. scanner := bufio.NewScanner(os.Stdin)
  497. for scanner.Scan() {
  498. prompt := scanner.Text()
  499. fmt.Printf(">>> %s\n", prompt)
  500. if err := generate(cmd, model, prompt); err != nil {
  501. return err
  502. }
  503. }
  504. return nil
  505. }
  506. func RunServer(cmd *cobra.Command, _ []string) error {
  507. host, port := "127.0.0.1", "11434"
  508. parts := strings.Split(os.Getenv("OLLAMA_HOST"), ":")
  509. if ip := net.ParseIP(parts[0]); ip != nil {
  510. host = ip.String()
  511. }
  512. if len(parts) > 1 {
  513. port = parts[1]
  514. }
  515. // deprecated: include port in OLLAMA_HOST
  516. if p := os.Getenv("OLLAMA_PORT"); p != "" {
  517. port = p
  518. }
  519. err := initializeKeypair()
  520. if err != nil {
  521. return err
  522. }
  523. ln, err := net.Listen("tcp", fmt.Sprintf("%s:%s", host, port))
  524. if err != nil {
  525. return err
  526. }
  527. var origins []string
  528. if o := os.Getenv("OLLAMA_ORIGINS"); o != "" {
  529. origins = strings.Split(o, ",")
  530. }
  531. return server.Serve(ln, origins)
  532. }
  533. func initializeKeypair() error {
  534. home, err := os.UserHomeDir()
  535. if err != nil {
  536. return err
  537. }
  538. privKeyPath := filepath.Join(home, ".ollama", "id_ed25519")
  539. pubKeyPath := filepath.Join(home, ".ollama", "id_ed25519.pub")
  540. _, err = os.Stat(privKeyPath)
  541. if os.IsNotExist(err) {
  542. fmt.Printf("Couldn't find '%s'. Generating new private key.\n", privKeyPath)
  543. _, privKey, err := ed25519.GenerateKey(rand.Reader)
  544. if err != nil {
  545. return err
  546. }
  547. privKeyBytes, err := format.OpenSSHPrivateKey(privKey, "")
  548. if err != nil {
  549. return err
  550. }
  551. err = os.MkdirAll(path.Dir(privKeyPath), 0o700)
  552. if err != nil {
  553. return fmt.Errorf("could not create directory %w", err)
  554. }
  555. err = os.WriteFile(privKeyPath, pem.EncodeToMemory(privKeyBytes), 0o600)
  556. if err != nil {
  557. return err
  558. }
  559. sshPrivateKey, err := ssh.NewSignerFromKey(privKey)
  560. if err != nil {
  561. return err
  562. }
  563. pubKeyData := ssh.MarshalAuthorizedKey(sshPrivateKey.PublicKey())
  564. err = os.WriteFile(pubKeyPath, pubKeyData, 0o644)
  565. if err != nil {
  566. return err
  567. }
  568. fmt.Printf("Your new public key is: \n\n%s\n", string(pubKeyData))
  569. }
  570. return nil
  571. }
  572. func startMacApp(client *api.Client) error {
  573. exe, err := os.Executable()
  574. if err != nil {
  575. return err
  576. }
  577. link, err := os.Readlink(exe)
  578. if err != nil {
  579. return err
  580. }
  581. if !strings.Contains(link, "Ollama.app") {
  582. return fmt.Errorf("could not find ollama app")
  583. }
  584. path := strings.Split(link, "Ollama.app")
  585. if err := exec.Command("/usr/bin/open", "-a", path[0]+"Ollama.app").Run(); err != nil {
  586. return err
  587. }
  588. // wait for the server to start
  589. timeout := time.After(5 * time.Second)
  590. tick := time.Tick(500 * time.Millisecond)
  591. for {
  592. select {
  593. case <-timeout:
  594. return errors.New("timed out waiting for server to start")
  595. case <-tick:
  596. if err := client.Heartbeat(context.Background()); err == nil {
  597. return nil // server has started
  598. }
  599. }
  600. }
  601. }
  602. func checkServerHeartbeat(_ *cobra.Command, _ []string) error {
  603. client, err := api.FromEnv()
  604. if err != nil {
  605. return err
  606. }
  607. if err := client.Heartbeat(context.Background()); err != nil {
  608. if !strings.Contains(err.Error(), "connection refused") {
  609. return err
  610. }
  611. if runtime.GOOS == "darwin" {
  612. if err := startMacApp(client); err != nil {
  613. return fmt.Errorf("could not connect to ollama app, is it running?")
  614. }
  615. } else {
  616. return fmt.Errorf("could not connect to ollama server, run 'ollama serve' to start it")
  617. }
  618. }
  619. return nil
  620. }
  621. func NewCLI() *cobra.Command {
  622. log.SetFlags(log.LstdFlags | log.Lshortfile)
  623. rootCmd := &cobra.Command{
  624. Use: "ollama",
  625. Short: "Large language model runner",
  626. SilenceUsage: true,
  627. SilenceErrors: true,
  628. CompletionOptions: cobra.CompletionOptions{
  629. DisableDefaultCmd: true,
  630. },
  631. }
  632. cobra.EnableCommandSorting = false
  633. createCmd := &cobra.Command{
  634. Use: "create MODEL",
  635. Short: "Create a model from a Modelfile",
  636. Args: cobra.MinimumNArgs(1),
  637. PreRunE: checkServerHeartbeat,
  638. RunE: CreateHandler,
  639. }
  640. createCmd.Flags().StringP("file", "f", "Modelfile", "Name of the Modelfile (default \"Modelfile\")")
  641. runCmd := &cobra.Command{
  642. Use: "run MODEL [PROMPT]",
  643. Short: "Run a model",
  644. Args: cobra.MinimumNArgs(1),
  645. PreRunE: checkServerHeartbeat,
  646. RunE: RunHandler,
  647. }
  648. runCmd.Flags().Bool("verbose", false, "Show timings for response")
  649. runCmd.Flags().Bool("insecure", false, "Use an insecure registry")
  650. serveCmd := &cobra.Command{
  651. Use: "serve",
  652. Aliases: []string{"start"},
  653. Short: "Start ollama",
  654. RunE: RunServer,
  655. }
  656. pullCmd := &cobra.Command{
  657. Use: "pull MODEL",
  658. Short: "Pull a model from a registry",
  659. Args: cobra.MinimumNArgs(1),
  660. PreRunE: checkServerHeartbeat,
  661. RunE: PullHandler,
  662. }
  663. pullCmd.Flags().Bool("insecure", false, "Use an insecure registry")
  664. pushCmd := &cobra.Command{
  665. Use: "push MODEL",
  666. Short: "Push a model to a registry",
  667. Args: cobra.MinimumNArgs(1),
  668. PreRunE: checkServerHeartbeat,
  669. RunE: PushHandler,
  670. }
  671. pushCmd.Flags().Bool("insecure", false, "Use an insecure registry")
  672. listCmd := &cobra.Command{
  673. Use: "list",
  674. Aliases: []string{"ls"},
  675. Short: "List models",
  676. PreRunE: checkServerHeartbeat,
  677. RunE: ListHandler,
  678. }
  679. copyCmd := &cobra.Command{
  680. Use: "cp",
  681. Short: "Copy a model",
  682. Args: cobra.MinimumNArgs(2),
  683. PreRunE: checkServerHeartbeat,
  684. RunE: CopyHandler,
  685. }
  686. deleteCmd := &cobra.Command{
  687. Use: "rm",
  688. Short: "Remove a model",
  689. Args: cobra.MinimumNArgs(1),
  690. PreRunE: checkServerHeartbeat,
  691. RunE: DeleteHandler,
  692. }
  693. rootCmd.AddCommand(
  694. serveCmd,
  695. createCmd,
  696. runCmd,
  697. pullCmd,
  698. pushCmd,
  699. listCmd,
  700. copyCmd,
  701. deleteCmd,
  702. )
  703. return rootCmd
  704. }