cmd.go 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452
  1. package cmd
  2. import (
  3. "archive/zip"
  4. "bytes"
  5. "context"
  6. "crypto/ed25519"
  7. "crypto/rand"
  8. "crypto/sha256"
  9. "encoding/pem"
  10. "errors"
  11. "fmt"
  12. "io"
  13. "log"
  14. "math"
  15. "net"
  16. "net/http"
  17. "os"
  18. "os/signal"
  19. "path/filepath"
  20. "regexp"
  21. "runtime"
  22. "slices"
  23. "strings"
  24. "sync/atomic"
  25. "syscall"
  26. "time"
  27. "github.com/containerd/console"
  28. "github.com/mattn/go-runewidth"
  29. "github.com/olekukonko/tablewriter"
  30. "github.com/spf13/cobra"
  31. "golang.org/x/crypto/ssh"
  32. "golang.org/x/term"
  33. "github.com/ollama/ollama/api"
  34. "github.com/ollama/ollama/auth"
  35. "github.com/ollama/ollama/envconfig"
  36. "github.com/ollama/ollama/format"
  37. "github.com/ollama/ollama/parser"
  38. "github.com/ollama/ollama/progress"
  39. "github.com/ollama/ollama/server"
  40. "github.com/ollama/ollama/types/errtypes"
  41. "github.com/ollama/ollama/types/model"
  42. "github.com/ollama/ollama/version"
  43. )
  44. func CreateHandler(cmd *cobra.Command, args []string) error {
  45. filename, _ := cmd.Flags().GetString("file")
  46. filename, err := filepath.Abs(filename)
  47. if err != nil {
  48. return err
  49. }
  50. client, err := api.ClientFromEnvironment()
  51. if err != nil {
  52. return err
  53. }
  54. p := progress.NewProgress(os.Stderr)
  55. defer p.Stop()
  56. f, err := os.Open(filename)
  57. if err != nil {
  58. return err
  59. }
  60. defer f.Close()
  61. modelfile, err := parser.ParseFile(f)
  62. if err != nil {
  63. return err
  64. }
  65. home, err := os.UserHomeDir()
  66. if err != nil {
  67. return err
  68. }
  69. status := "transferring model data"
  70. spinner := progress.NewSpinner(status)
  71. p.Add(status, spinner)
  72. defer p.Stop()
  73. for i := range modelfile.Commands {
  74. switch modelfile.Commands[i].Name {
  75. case "model", "adapter":
  76. path := modelfile.Commands[i].Args
  77. if path == "~" {
  78. path = home
  79. } else if strings.HasPrefix(path, "~/") {
  80. path = filepath.Join(home, path[2:])
  81. }
  82. if !filepath.IsAbs(path) {
  83. path = filepath.Join(filepath.Dir(filename), path)
  84. }
  85. fi, err := os.Stat(path)
  86. if errors.Is(err, os.ErrNotExist) && modelfile.Commands[i].Name == "model" {
  87. continue
  88. } else if err != nil {
  89. return err
  90. }
  91. if fi.IsDir() {
  92. // this is likely a safetensors or pytorch directory
  93. // TODO make this work w/ adapters
  94. tempfile, err := tempZipFiles(path)
  95. if err != nil {
  96. return err
  97. }
  98. defer os.RemoveAll(tempfile)
  99. path = tempfile
  100. }
  101. digest, err := createBlob(cmd, client, path, spinner)
  102. if err != nil {
  103. return err
  104. }
  105. modelfile.Commands[i].Args = "@" + digest
  106. }
  107. }
  108. bars := make(map[string]*progress.Bar)
  109. var convertSpin *progress.Spinner
  110. fn := func(resp api.ProgressResponse) error {
  111. if resp.Digest != "" {
  112. spinner.Stop()
  113. bar, ok := bars[resp.Digest]
  114. if !ok {
  115. bar = progress.NewBar(fmt.Sprintf("pulling %s...", resp.Digest[7:19]), resp.Total, resp.Completed)
  116. bars[resp.Digest] = bar
  117. p.Add(resp.Digest, bar)
  118. }
  119. bar.Set(resp.Completed)
  120. } else if strings.Contains(resp.Status, "converting") {
  121. spinner.Stop()
  122. if convertSpin != nil {
  123. convertSpin.SetMessage(resp.Status)
  124. } else {
  125. status = resp.Status
  126. convertSpin = progress.NewSpinner(resp.Status)
  127. p.Add("convert", convertSpin)
  128. }
  129. } else if status != resp.Status {
  130. spinner.Stop()
  131. status = resp.Status
  132. spinner = progress.NewSpinner(status)
  133. p.Add(status, spinner)
  134. }
  135. return nil
  136. }
  137. quantize, _ := cmd.Flags().GetString("quantize")
  138. request := api.CreateRequest{Name: args[0], Modelfile: modelfile.String(), Quantize: quantize}
  139. if err := client.Create(cmd.Context(), &request, fn); err != nil {
  140. return err
  141. }
  142. return nil
  143. }
  144. func tempZipFiles(path string) (string, error) {
  145. tempfile, err := os.CreateTemp("", "ollama-tf")
  146. if err != nil {
  147. return "", err
  148. }
  149. defer tempfile.Close()
  150. detectContentType := func(path string) (string, error) {
  151. f, err := os.Open(path)
  152. if err != nil {
  153. return "", err
  154. }
  155. defer f.Close()
  156. var b bytes.Buffer
  157. b.Grow(512)
  158. if _, err := io.CopyN(&b, f, 512); err != nil && !errors.Is(err, io.EOF) {
  159. return "", err
  160. }
  161. contentType, _, _ := strings.Cut(http.DetectContentType(b.Bytes()), ";")
  162. return contentType, nil
  163. }
  164. glob := func(pattern, contentType string) ([]string, error) {
  165. matches, err := filepath.Glob(pattern)
  166. if err != nil {
  167. return nil, err
  168. }
  169. for _, safetensor := range matches {
  170. if ct, err := detectContentType(safetensor); err != nil {
  171. return nil, err
  172. } else if ct != contentType {
  173. return nil, fmt.Errorf("invalid content type: expected %s for %s", ct, safetensor)
  174. }
  175. }
  176. return matches, nil
  177. }
  178. var files []string
  179. if st, _ := glob(filepath.Join(path, "model*.safetensors"), "application/octet-stream"); len(st) > 0 {
  180. // safetensors files might be unresolved git lfs references; skip if they are
  181. // covers model-x-of-y.safetensors, model.fp32-x-of-y.safetensors, model.safetensors
  182. files = append(files, st...)
  183. } else if st, _ := glob(filepath.Join(path, "adapters.safetensors"), "application/octet-stream"); len(st) > 0 {
  184. // covers adapters.safetensors
  185. files = append(files, st...)
  186. } else if st, _ := glob(filepath.Join(path, "adapter_model.safetensors"), "application/octet-stream"); len(st) > 0 {
  187. // covers adapter_model.safetensors
  188. files = append(files, st...)
  189. } else if pt, _ := glob(filepath.Join(path, "pytorch_model*.bin"), "application/zip"); len(pt) > 0 {
  190. // pytorch files might also be unresolved git lfs references; skip if they are
  191. // covers pytorch_model-x-of-y.bin, pytorch_model.fp32-x-of-y.bin, pytorch_model.bin
  192. files = append(files, pt...)
  193. } else if pt, _ := glob(filepath.Join(path, "consolidated*.pth"), "application/zip"); len(pt) > 0 {
  194. // pytorch files might also be unresolved git lfs references; skip if they are
  195. // covers consolidated.x.pth, consolidated.pth
  196. files = append(files, pt...)
  197. } else {
  198. return "", errors.New("no safetensors or torch files found")
  199. }
  200. // add configuration files, json files are detected as text/plain
  201. js, err := glob(filepath.Join(path, "*.json"), "text/plain")
  202. if err != nil {
  203. return "", err
  204. }
  205. files = append(files, js...)
  206. // bert models require a nested config.json
  207. // TODO(mxyng): merge this with the glob above
  208. js, err = glob(filepath.Join(path, "**/*.json"), "text/plain")
  209. if err != nil {
  210. return "", err
  211. }
  212. files = append(files, js...)
  213. if tks, _ := glob(filepath.Join(path, "tokenizer.model"), "application/octet-stream"); len(tks) > 0 {
  214. // add tokenizer.model if it exists, tokenizer.json is automatically picked up by the previous glob
  215. // tokenizer.model might be a unresolved git lfs reference; error if it is
  216. files = append(files, tks...)
  217. } else if tks, _ := glob(filepath.Join(path, "**/tokenizer.model"), "text/plain"); len(tks) > 0 {
  218. // some times tokenizer.model is in a subdirectory (e.g. meta-llama/Meta-Llama-3-8B)
  219. files = append(files, tks...)
  220. }
  221. zipfile := zip.NewWriter(tempfile)
  222. defer zipfile.Close()
  223. for _, file := range files {
  224. f, err := os.Open(file)
  225. if err != nil {
  226. return "", err
  227. }
  228. defer f.Close()
  229. fi, err := f.Stat()
  230. if err != nil {
  231. return "", err
  232. }
  233. zfi, err := zip.FileInfoHeader(fi)
  234. if err != nil {
  235. return "", err
  236. }
  237. zfi.Name, err = filepath.Rel(path, file)
  238. if err != nil {
  239. return "", err
  240. }
  241. zf, err := zipfile.CreateHeader(zfi)
  242. if err != nil {
  243. return "", err
  244. }
  245. if _, err := io.Copy(zf, f); err != nil {
  246. return "", err
  247. }
  248. }
  249. return tempfile.Name(), nil
  250. }
  251. func createBlob(cmd *cobra.Command, client *api.Client, path string, spinner *progress.Spinner) (string, error) {
  252. bin, err := os.Open(path)
  253. if err != nil {
  254. return "", err
  255. }
  256. defer bin.Close()
  257. // Get file info to retrieve the size
  258. fileInfo, err := bin.Stat()
  259. if err != nil {
  260. return "", err
  261. }
  262. fileSize := fileInfo.Size()
  263. hash := sha256.New()
  264. if _, err := io.Copy(hash, bin); err != nil {
  265. return "", err
  266. }
  267. if _, err := bin.Seek(0, io.SeekStart); err != nil {
  268. return "", err
  269. }
  270. var pw progressWriter
  271. status := "transferring model data 0%"
  272. spinner.SetMessage(status)
  273. done := make(chan struct{})
  274. defer close(done)
  275. go func() {
  276. ticker := time.NewTicker(60 * time.Millisecond)
  277. defer ticker.Stop()
  278. for {
  279. select {
  280. case <-ticker.C:
  281. spinner.SetMessage(fmt.Sprintf("transferring model data %d%%", int(100*pw.n.Load()/fileSize)))
  282. case <-done:
  283. spinner.SetMessage("transferring model data 100%")
  284. return
  285. }
  286. }
  287. }()
  288. digest := fmt.Sprintf("sha256:%x", hash.Sum(nil))
  289. if err = client.CreateBlob(cmd.Context(), digest, io.TeeReader(bin, &pw)); err != nil {
  290. return "", err
  291. }
  292. return digest, nil
  293. }
  294. type progressWriter struct {
  295. n atomic.Int64
  296. }
  297. func (w *progressWriter) Write(p []byte) (n int, err error) {
  298. w.n.Add(int64(len(p)))
  299. return len(p), nil
  300. }
  301. func RunHandler(cmd *cobra.Command, args []string) error {
  302. interactive := true
  303. opts := runOptions{
  304. Model: args[0],
  305. WordWrap: os.Getenv("TERM") == "xterm-256color",
  306. Options: map[string]interface{}{},
  307. }
  308. format, err := cmd.Flags().GetString("format")
  309. if err != nil {
  310. return err
  311. }
  312. opts.Format = format
  313. keepAlive, err := cmd.Flags().GetString("keepalive")
  314. if err != nil {
  315. return err
  316. }
  317. if keepAlive != "" {
  318. d, err := time.ParseDuration(keepAlive)
  319. if err != nil {
  320. return err
  321. }
  322. opts.KeepAlive = &api.Duration{Duration: d}
  323. }
  324. prompts := args[1:]
  325. // prepend stdin to the prompt if provided
  326. if !term.IsTerminal(int(os.Stdin.Fd())) {
  327. in, err := io.ReadAll(os.Stdin)
  328. if err != nil {
  329. return err
  330. }
  331. prompts = append([]string{string(in)}, prompts...)
  332. opts.WordWrap = false
  333. interactive = false
  334. }
  335. opts.Prompt = strings.Join(prompts, " ")
  336. if len(prompts) > 0 {
  337. interactive = false
  338. }
  339. nowrap, err := cmd.Flags().GetBool("nowordwrap")
  340. if err != nil {
  341. return err
  342. }
  343. opts.WordWrap = !nowrap
  344. // Fill out the rest of the options based on information about the
  345. // model.
  346. client, err := api.ClientFromEnvironment()
  347. if err != nil {
  348. return err
  349. }
  350. name := args[0]
  351. info, err := func() (*api.ShowResponse, error) {
  352. showReq := &api.ShowRequest{Name: name}
  353. info, err := client.Show(cmd.Context(), showReq)
  354. var se api.StatusError
  355. if errors.As(err, &se) && se.StatusCode == http.StatusNotFound {
  356. if err := PullHandler(cmd, []string{name}); err != nil {
  357. return nil, err
  358. }
  359. return client.Show(cmd.Context(), &api.ShowRequest{Name: name})
  360. }
  361. return info, err
  362. }()
  363. if err != nil {
  364. return err
  365. }
  366. opts.MultiModal = slices.Contains(info.Details.Families, "clip")
  367. opts.ParentModel = info.Details.ParentModel
  368. if interactive {
  369. if err := loadModel(cmd, &opts); err != nil {
  370. return err
  371. }
  372. for _, msg := range info.Messages {
  373. switch msg.Role {
  374. case "user":
  375. fmt.Printf(">>> %s\n", msg.Content)
  376. case "assistant":
  377. state := &displayResponseState{}
  378. displayResponse(msg.Content, opts.WordWrap, state)
  379. fmt.Println()
  380. fmt.Println()
  381. }
  382. }
  383. return generateInteractive(cmd, opts)
  384. }
  385. return generate(cmd, opts)
  386. }
  387. func errFromUnknownKey(unknownKeyErr error) error {
  388. // find SSH public key in the error message
  389. sshKeyPattern := `ssh-\w+ [^\s"]+`
  390. re := regexp.MustCompile(sshKeyPattern)
  391. matches := re.FindStringSubmatch(unknownKeyErr.Error())
  392. if len(matches) > 0 {
  393. serverPubKey := matches[0]
  394. localPubKey, err := auth.GetPublicKey()
  395. if err != nil {
  396. return unknownKeyErr
  397. }
  398. if runtime.GOOS == "linux" && serverPubKey != localPubKey {
  399. // try the ollama service public key
  400. svcPubKey, err := os.ReadFile("/usr/share/ollama/.ollama/id_ed25519.pub")
  401. if err != nil {
  402. return unknownKeyErr
  403. }
  404. localPubKey = strings.TrimSpace(string(svcPubKey))
  405. }
  406. // check if the returned public key matches the local public key, this prevents adding a remote key to the user's account
  407. if serverPubKey != localPubKey {
  408. return unknownKeyErr
  409. }
  410. var msg strings.Builder
  411. msg.WriteString(unknownKeyErr.Error())
  412. msg.WriteString("\n\nYour ollama key is:\n")
  413. msg.WriteString(localPubKey)
  414. msg.WriteString("\nAdd your key at:\n")
  415. msg.WriteString("https://ollama.com/settings/keys")
  416. return errors.New(msg.String())
  417. }
  418. return unknownKeyErr
  419. }
  420. func PushHandler(cmd *cobra.Command, args []string) error {
  421. client, err := api.ClientFromEnvironment()
  422. if err != nil {
  423. return err
  424. }
  425. insecure, err := cmd.Flags().GetBool("insecure")
  426. if err != nil {
  427. return err
  428. }
  429. p := progress.NewProgress(os.Stderr)
  430. defer p.Stop()
  431. bars := make(map[string]*progress.Bar)
  432. var status string
  433. var spinner *progress.Spinner
  434. fn := func(resp api.ProgressResponse) error {
  435. if resp.Digest != "" {
  436. if spinner != nil {
  437. spinner.Stop()
  438. }
  439. bar, ok := bars[resp.Digest]
  440. if !ok {
  441. bar = progress.NewBar(fmt.Sprintf("pushing %s...", resp.Digest[7:19]), resp.Total, resp.Completed)
  442. bars[resp.Digest] = bar
  443. p.Add(resp.Digest, bar)
  444. }
  445. bar.Set(resp.Completed)
  446. } else if status != resp.Status {
  447. if spinner != nil {
  448. spinner.Stop()
  449. }
  450. status = resp.Status
  451. spinner = progress.NewSpinner(status)
  452. p.Add(status, spinner)
  453. }
  454. return nil
  455. }
  456. request := api.PushRequest{Name: args[0], Insecure: insecure}
  457. if err := client.Push(cmd.Context(), &request, fn); err != nil {
  458. if spinner != nil {
  459. spinner.Stop()
  460. }
  461. if strings.Contains(err.Error(), "access denied") {
  462. return errors.New("you are not authorized to push to this namespace, create the model under a namespace you own")
  463. }
  464. host := model.ParseName(args[0]).Host
  465. isOllamaHost := strings.HasSuffix(host, ".ollama.ai") || strings.HasSuffix(host, ".ollama.com")
  466. if strings.Contains(err.Error(), errtypes.UnknownOllamaKeyErrMsg) && isOllamaHost {
  467. // the user has not added their ollama key to ollama.com
  468. // re-throw an error with a more user-friendly message
  469. return errFromUnknownKey(err)
  470. }
  471. return err
  472. }
  473. spinner.Stop()
  474. return nil
  475. }
  476. func ListHandler(cmd *cobra.Command, args []string) error {
  477. client, err := api.ClientFromEnvironment()
  478. if err != nil {
  479. return err
  480. }
  481. models, err := client.List(cmd.Context())
  482. if err != nil {
  483. return err
  484. }
  485. var data [][]string
  486. for _, m := range models.Models {
  487. if len(args) == 0 || strings.HasPrefix(m.Name, args[0]) {
  488. data = append(data, []string{m.Name, m.Digest[:12], format.HumanBytes(m.Size), format.HumanTime(m.ModifiedAt, "Never")})
  489. }
  490. }
  491. table := tablewriter.NewWriter(os.Stdout)
  492. table.SetHeader([]string{"NAME", "ID", "SIZE", "MODIFIED"})
  493. table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
  494. table.SetAlignment(tablewriter.ALIGN_LEFT)
  495. table.SetHeaderLine(false)
  496. table.SetBorder(false)
  497. table.SetNoWhiteSpace(true)
  498. table.SetTablePadding("\t")
  499. table.AppendBulk(data)
  500. table.Render()
  501. return nil
  502. }
  503. func ListRunningHandler(cmd *cobra.Command, args []string) error {
  504. client, err := api.ClientFromEnvironment()
  505. if err != nil {
  506. return err
  507. }
  508. models, err := client.ListRunning(cmd.Context())
  509. if err != nil {
  510. return err
  511. }
  512. var data [][]string
  513. for _, m := range models.Models {
  514. if len(args) == 0 || strings.HasPrefix(m.Name, args[0]) {
  515. var procStr string
  516. switch {
  517. case m.SizeVRAM == 0:
  518. procStr = "100% CPU"
  519. case m.SizeVRAM == m.Size:
  520. procStr = "100% GPU"
  521. case m.SizeVRAM > m.Size || m.Size == 0:
  522. procStr = "Unknown"
  523. default:
  524. sizeCPU := m.Size - m.SizeVRAM
  525. cpuPercent := math.Round(float64(sizeCPU) / float64(m.Size) * 100)
  526. procStr = fmt.Sprintf("%d%%/%d%% CPU/GPU", int(cpuPercent), int(100-cpuPercent))
  527. }
  528. data = append(data, []string{m.Name, m.Digest[:12], format.HumanBytes(m.Size), procStr, format.HumanTime(m.ExpiresAt, "Never")})
  529. }
  530. }
  531. table := tablewriter.NewWriter(os.Stdout)
  532. table.SetHeader([]string{"NAME", "ID", "SIZE", "PROCESSOR", "UNTIL"})
  533. table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
  534. table.SetAlignment(tablewriter.ALIGN_LEFT)
  535. table.SetHeaderLine(false)
  536. table.SetBorder(false)
  537. table.SetNoWhiteSpace(true)
  538. table.SetTablePadding("\t")
  539. table.AppendBulk(data)
  540. table.Render()
  541. return nil
  542. }
  543. func DeleteHandler(cmd *cobra.Command, args []string) error {
  544. client, err := api.ClientFromEnvironment()
  545. if err != nil {
  546. return err
  547. }
  548. for _, name := range args {
  549. req := api.DeleteRequest{Name: name}
  550. if err := client.Delete(cmd.Context(), &req); err != nil {
  551. return err
  552. }
  553. fmt.Printf("deleted '%s'\n", name)
  554. }
  555. return nil
  556. }
  557. func ShowHandler(cmd *cobra.Command, args []string) error {
  558. client, err := api.ClientFromEnvironment()
  559. if err != nil {
  560. return err
  561. }
  562. license, errLicense := cmd.Flags().GetBool("license")
  563. modelfile, errModelfile := cmd.Flags().GetBool("modelfile")
  564. parameters, errParams := cmd.Flags().GetBool("parameters")
  565. system, errSystem := cmd.Flags().GetBool("system")
  566. template, errTemplate := cmd.Flags().GetBool("template")
  567. for _, boolErr := range []error{errLicense, errModelfile, errParams, errSystem, errTemplate} {
  568. if boolErr != nil {
  569. return errors.New("error retrieving flags")
  570. }
  571. }
  572. flagsSet := 0
  573. showType := ""
  574. if license {
  575. flagsSet++
  576. showType = "license"
  577. }
  578. if modelfile {
  579. flagsSet++
  580. showType = "modelfile"
  581. }
  582. if parameters {
  583. flagsSet++
  584. showType = "parameters"
  585. }
  586. if system {
  587. flagsSet++
  588. showType = "system"
  589. }
  590. if template {
  591. flagsSet++
  592. showType = "template"
  593. }
  594. if flagsSet > 1 {
  595. return errors.New("only one of '--license', '--modelfile', '--parameters', '--system', or '--template' can be specified")
  596. }
  597. req := api.ShowRequest{Name: args[0]}
  598. resp, err := client.Show(cmd.Context(), &req)
  599. if err != nil {
  600. return err
  601. }
  602. if flagsSet == 1 {
  603. switch showType {
  604. case "license":
  605. fmt.Println(resp.License)
  606. case "modelfile":
  607. fmt.Println(resp.Modelfile)
  608. case "parameters":
  609. fmt.Println(resp.Parameters)
  610. case "system":
  611. fmt.Println(resp.System)
  612. case "template":
  613. fmt.Println(resp.Template)
  614. }
  615. return nil
  616. }
  617. showInfo(resp)
  618. return nil
  619. }
  620. func showInfo(resp *api.ShowResponse) {
  621. arch := resp.ModelInfo["general.architecture"].(string)
  622. modelData := [][]string{
  623. {"arch", arch},
  624. {"parameters", resp.Details.ParameterSize},
  625. {"quantization", resp.Details.QuantizationLevel},
  626. {"context length", fmt.Sprintf("%v", resp.ModelInfo[fmt.Sprintf("%s.context_length", arch)].(float64))},
  627. {"embedding length", fmt.Sprintf("%v", resp.ModelInfo[fmt.Sprintf("%s.embedding_length", arch)].(float64))},
  628. }
  629. mainTableData := [][]string{
  630. {"Model"},
  631. {renderSubTable(modelData, false)},
  632. }
  633. if resp.ProjectorInfo != nil {
  634. projectorData := [][]string{
  635. {"arch", "clip"},
  636. {"parameters", format.HumanNumber(uint64(resp.ProjectorInfo["general.parameter_count"].(float64)))},
  637. }
  638. if projectorType, ok := resp.ProjectorInfo["clip.projector_type"]; ok {
  639. projectorData = append(projectorData, []string{"projector type", projectorType.(string)})
  640. }
  641. projectorData = append(projectorData,
  642. []string{"embedding length", fmt.Sprintf("%v", resp.ProjectorInfo["clip.vision.embedding_length"].(float64))},
  643. []string{"projection dimensionality", fmt.Sprintf("%v", resp.ProjectorInfo["clip.vision.projection_dim"].(float64))},
  644. )
  645. mainTableData = append(mainTableData,
  646. []string{"Projector"},
  647. []string{renderSubTable(projectorData, false)},
  648. )
  649. }
  650. if resp.Parameters != "" {
  651. mainTableData = append(mainTableData, []string{"Parameters"}, []string{formatParams(resp.Parameters)})
  652. }
  653. if resp.System != "" {
  654. mainTableData = append(mainTableData, []string{"System"}, []string{renderSubTable(twoLines(resp.System), true)})
  655. }
  656. if resp.License != "" {
  657. mainTableData = append(mainTableData, []string{"License"}, []string{renderSubTable(twoLines(resp.License), true)})
  658. }
  659. table := tablewriter.NewWriter(os.Stdout)
  660. table.SetAutoWrapText(false)
  661. table.SetBorder(false)
  662. table.SetAlignment(tablewriter.ALIGN_LEFT)
  663. for _, v := range mainTableData {
  664. table.Append(v)
  665. }
  666. table.Render()
  667. }
  668. func renderSubTable(data [][]string, file bool) string {
  669. var buf bytes.Buffer
  670. table := tablewriter.NewWriter(&buf)
  671. table.SetAutoWrapText(!file)
  672. table.SetBorder(false)
  673. table.SetNoWhiteSpace(true)
  674. table.SetTablePadding("\t")
  675. table.SetAlignment(tablewriter.ALIGN_LEFT)
  676. for _, v := range data {
  677. table.Append(v)
  678. }
  679. table.Render()
  680. renderedTable := buf.String()
  681. lines := strings.Split(renderedTable, "\n")
  682. for i, line := range lines {
  683. lines[i] = "\t" + line
  684. }
  685. return strings.Join(lines, "\n")
  686. }
  687. func twoLines(s string) [][]string {
  688. lines := strings.Split(s, "\n")
  689. res := [][]string{}
  690. count := 0
  691. for _, line := range lines {
  692. line = strings.TrimSpace(line)
  693. if line != "" {
  694. count++
  695. res = append(res, []string{line})
  696. if count == 2 {
  697. return res
  698. }
  699. }
  700. }
  701. return res
  702. }
  703. func formatParams(s string) string {
  704. lines := strings.Split(s, "\n")
  705. table := [][]string{}
  706. for _, line := range lines {
  707. table = append(table, strings.Fields(line))
  708. }
  709. return renderSubTable(table, false)
  710. }
  711. func CopyHandler(cmd *cobra.Command, args []string) error {
  712. client, err := api.ClientFromEnvironment()
  713. if err != nil {
  714. return err
  715. }
  716. req := api.CopyRequest{Source: args[0], Destination: args[1]}
  717. if err := client.Copy(cmd.Context(), &req); err != nil {
  718. return err
  719. }
  720. fmt.Printf("copied '%s' to '%s'\n", args[0], args[1])
  721. return nil
  722. }
  723. func PullHandler(cmd *cobra.Command, args []string) error {
  724. insecure, err := cmd.Flags().GetBool("insecure")
  725. if err != nil {
  726. return err
  727. }
  728. client, err := api.ClientFromEnvironment()
  729. if err != nil {
  730. return err
  731. }
  732. p := progress.NewProgress(os.Stderr)
  733. defer p.Stop()
  734. bars := make(map[string]*progress.Bar)
  735. var status string
  736. var spinner *progress.Spinner
  737. fn := func(resp api.ProgressResponse) error {
  738. if resp.Digest != "" {
  739. if spinner != nil {
  740. spinner.Stop()
  741. }
  742. bar, ok := bars[resp.Digest]
  743. if !ok {
  744. bar = progress.NewBar(fmt.Sprintf("pulling %s...", resp.Digest[7:19]), resp.Total, resp.Completed)
  745. bars[resp.Digest] = bar
  746. p.Add(resp.Digest, bar)
  747. }
  748. bar.Set(resp.Completed)
  749. } else if status != resp.Status {
  750. if spinner != nil {
  751. spinner.Stop()
  752. }
  753. status = resp.Status
  754. spinner = progress.NewSpinner(status)
  755. p.Add(status, spinner)
  756. }
  757. return nil
  758. }
  759. request := api.PullRequest{Name: args[0], Insecure: insecure}
  760. if err := client.Pull(cmd.Context(), &request, fn); err != nil {
  761. return err
  762. }
  763. return nil
  764. }
  765. type generateContextKey string
  766. type runOptions struct {
  767. Model string
  768. ParentModel string
  769. Prompt string
  770. Messages []api.Message
  771. WordWrap bool
  772. Format string
  773. System string
  774. Images []api.ImageData
  775. Options map[string]interface{}
  776. MultiModal bool
  777. KeepAlive *api.Duration
  778. }
  779. type displayResponseState struct {
  780. lineLength int
  781. wordBuffer string
  782. }
  783. func displayResponse(content string, wordWrap bool, state *displayResponseState) {
  784. termWidth, _, _ := term.GetSize(int(os.Stdout.Fd()))
  785. if wordWrap && termWidth >= 10 {
  786. for _, ch := range content {
  787. if state.lineLength+1 > termWidth-5 {
  788. if runewidth.StringWidth(state.wordBuffer) > termWidth-10 {
  789. fmt.Printf("%s%c", state.wordBuffer, ch)
  790. state.wordBuffer = ""
  791. state.lineLength = 0
  792. continue
  793. }
  794. // backtrack the length of the last word and clear to the end of the line
  795. a := runewidth.StringWidth(state.wordBuffer)
  796. if a > 0 {
  797. fmt.Printf("\x1b[%dD", a)
  798. }
  799. fmt.Printf("\x1b[K\n")
  800. fmt.Printf("%s%c", state.wordBuffer, ch)
  801. chWidth := runewidth.RuneWidth(ch)
  802. state.lineLength = runewidth.StringWidth(state.wordBuffer) + chWidth
  803. } else {
  804. fmt.Print(string(ch))
  805. state.lineLength += runewidth.RuneWidth(ch)
  806. if runewidth.RuneWidth(ch) >= 2 {
  807. state.wordBuffer = ""
  808. continue
  809. }
  810. switch ch {
  811. case ' ':
  812. state.wordBuffer = ""
  813. case '\n':
  814. state.lineLength = 0
  815. default:
  816. state.wordBuffer += string(ch)
  817. }
  818. }
  819. }
  820. } else {
  821. fmt.Printf("%s%s", state.wordBuffer, content)
  822. if len(state.wordBuffer) > 0 {
  823. state.wordBuffer = ""
  824. }
  825. }
  826. }
  827. func chat(cmd *cobra.Command, opts runOptions) (*api.Message, error) {
  828. client, err := api.ClientFromEnvironment()
  829. if err != nil {
  830. return nil, err
  831. }
  832. p := progress.NewProgress(os.Stderr)
  833. defer p.StopAndClear()
  834. spinner := progress.NewSpinner("")
  835. p.Add("", spinner)
  836. cancelCtx, cancel := context.WithCancel(cmd.Context())
  837. defer cancel()
  838. sigChan := make(chan os.Signal, 1)
  839. signal.Notify(sigChan, syscall.SIGINT)
  840. go func() {
  841. <-sigChan
  842. cancel()
  843. }()
  844. var state *displayResponseState = &displayResponseState{}
  845. var latest api.ChatResponse
  846. var fullResponse strings.Builder
  847. var role string
  848. fn := func(response api.ChatResponse) error {
  849. p.StopAndClear()
  850. latest = response
  851. role = response.Message.Role
  852. content := response.Message.Content
  853. fullResponse.WriteString(content)
  854. displayResponse(content, opts.WordWrap, state)
  855. return nil
  856. }
  857. req := &api.ChatRequest{
  858. Model: opts.Model,
  859. Messages: opts.Messages,
  860. Format: opts.Format,
  861. Options: opts.Options,
  862. }
  863. if opts.KeepAlive != nil {
  864. req.KeepAlive = opts.KeepAlive
  865. }
  866. if err := client.Chat(cancelCtx, req, fn); err != nil {
  867. if errors.Is(err, context.Canceled) {
  868. return nil, nil
  869. }
  870. return nil, err
  871. }
  872. if len(opts.Messages) > 0 {
  873. fmt.Println()
  874. fmt.Println()
  875. }
  876. verbose, err := cmd.Flags().GetBool("verbose")
  877. if err != nil {
  878. return nil, err
  879. }
  880. if verbose {
  881. latest.Summary()
  882. }
  883. return &api.Message{Role: role, Content: fullResponse.String()}, nil
  884. }
  885. func generate(cmd *cobra.Command, opts runOptions) error {
  886. client, err := api.ClientFromEnvironment()
  887. if err != nil {
  888. return err
  889. }
  890. p := progress.NewProgress(os.Stderr)
  891. defer p.StopAndClear()
  892. spinner := progress.NewSpinner("")
  893. p.Add("", spinner)
  894. var latest api.GenerateResponse
  895. generateContext, ok := cmd.Context().Value(generateContextKey("context")).([]int)
  896. if !ok {
  897. generateContext = []int{}
  898. }
  899. ctx, cancel := context.WithCancel(cmd.Context())
  900. defer cancel()
  901. sigChan := make(chan os.Signal, 1)
  902. signal.Notify(sigChan, syscall.SIGINT)
  903. go func() {
  904. <-sigChan
  905. cancel()
  906. }()
  907. var state *displayResponseState = &displayResponseState{}
  908. fn := func(response api.GenerateResponse) error {
  909. p.StopAndClear()
  910. latest = response
  911. content := response.Response
  912. displayResponse(content, opts.WordWrap, state)
  913. return nil
  914. }
  915. if opts.MultiModal {
  916. opts.Prompt, opts.Images, err = extractFileData(opts.Prompt)
  917. if err != nil {
  918. return err
  919. }
  920. }
  921. request := api.GenerateRequest{
  922. Model: opts.Model,
  923. Prompt: opts.Prompt,
  924. Context: generateContext,
  925. Images: opts.Images,
  926. Format: opts.Format,
  927. System: opts.System,
  928. Options: opts.Options,
  929. KeepAlive: opts.KeepAlive,
  930. }
  931. if err := client.Generate(ctx, &request, fn); err != nil {
  932. if errors.Is(err, context.Canceled) {
  933. return nil
  934. }
  935. return err
  936. }
  937. if opts.Prompt != "" {
  938. fmt.Println()
  939. fmt.Println()
  940. }
  941. if !latest.Done {
  942. return nil
  943. }
  944. verbose, err := cmd.Flags().GetBool("verbose")
  945. if err != nil {
  946. return err
  947. }
  948. if verbose {
  949. latest.Summary()
  950. }
  951. ctx = context.WithValue(cmd.Context(), generateContextKey("context"), latest.Context)
  952. cmd.SetContext(ctx)
  953. return nil
  954. }
  955. func RunServer(_ *cobra.Command, _ []string) error {
  956. if err := initializeKeypair(); err != nil {
  957. return err
  958. }
  959. ln, err := net.Listen("tcp", envconfig.Host().Host)
  960. if err != nil {
  961. return err
  962. }
  963. err = server.Serve(ln)
  964. if errors.Is(err, http.ErrServerClosed) {
  965. return nil
  966. }
  967. return err
  968. }
  969. func initializeKeypair() error {
  970. home, err := os.UserHomeDir()
  971. if err != nil {
  972. return err
  973. }
  974. privKeyPath := filepath.Join(home, ".ollama", "id_ed25519")
  975. pubKeyPath := filepath.Join(home, ".ollama", "id_ed25519.pub")
  976. _, err = os.Stat(privKeyPath)
  977. if os.IsNotExist(err) {
  978. fmt.Printf("Couldn't find '%s'. Generating new private key.\n", privKeyPath)
  979. cryptoPublicKey, cryptoPrivateKey, err := ed25519.GenerateKey(rand.Reader)
  980. if err != nil {
  981. return err
  982. }
  983. privateKeyBytes, err := ssh.MarshalPrivateKey(cryptoPrivateKey, "")
  984. if err != nil {
  985. return err
  986. }
  987. if err := os.MkdirAll(filepath.Dir(privKeyPath), 0o755); err != nil {
  988. return fmt.Errorf("could not create directory %w", err)
  989. }
  990. if err := os.WriteFile(privKeyPath, pem.EncodeToMemory(privateKeyBytes), 0o600); err != nil {
  991. return err
  992. }
  993. sshPublicKey, err := ssh.NewPublicKey(cryptoPublicKey)
  994. if err != nil {
  995. return err
  996. }
  997. publicKeyBytes := ssh.MarshalAuthorizedKey(sshPublicKey)
  998. if err := os.WriteFile(pubKeyPath, publicKeyBytes, 0o644); err != nil {
  999. return err
  1000. }
  1001. fmt.Printf("Your new public key is: \n\n%s\n", publicKeyBytes)
  1002. }
  1003. return nil
  1004. }
  1005. func checkServerHeartbeat(cmd *cobra.Command, _ []string) error {
  1006. client, err := api.ClientFromEnvironment()
  1007. if err != nil {
  1008. return err
  1009. }
  1010. if err := client.Heartbeat(cmd.Context()); err != nil {
  1011. if !strings.Contains(err.Error(), " refused") {
  1012. return err
  1013. }
  1014. if err := startApp(cmd.Context(), client); err != nil {
  1015. return errors.New("could not connect to ollama app, is it running?")
  1016. }
  1017. }
  1018. return nil
  1019. }
  1020. func versionHandler(cmd *cobra.Command, _ []string) {
  1021. client, err := api.ClientFromEnvironment()
  1022. if err != nil {
  1023. return
  1024. }
  1025. serverVersion, err := client.Version(cmd.Context())
  1026. if err != nil {
  1027. fmt.Println("Warning: could not connect to a running Ollama instance")
  1028. }
  1029. if serverVersion != "" {
  1030. fmt.Printf("ollama version is %s\n", serverVersion)
  1031. }
  1032. if serverVersion != version.Version {
  1033. fmt.Printf("Warning: client version is %s\n", version.Version)
  1034. }
  1035. }
  1036. func appendEnvDocs(cmd *cobra.Command, envs []envconfig.EnvVar) {
  1037. if len(envs) == 0 {
  1038. return
  1039. }
  1040. envUsage := `
  1041. Environment Variables:
  1042. `
  1043. for _, e := range envs {
  1044. envUsage += fmt.Sprintf(" %-24s %s\n", e.Name, e.Description)
  1045. }
  1046. cmd.SetUsageTemplate(cmd.UsageTemplate() + envUsage)
  1047. }
  1048. func NewCLI() *cobra.Command {
  1049. log.SetFlags(log.LstdFlags | log.Lshortfile)
  1050. cobra.EnableCommandSorting = false
  1051. if runtime.GOOS == "windows" {
  1052. console.ConsoleFromFile(os.Stdin) //nolint:errcheck
  1053. }
  1054. rootCmd := &cobra.Command{
  1055. Use: "ollama",
  1056. Short: "Large language model runner",
  1057. SilenceUsage: true,
  1058. SilenceErrors: true,
  1059. CompletionOptions: cobra.CompletionOptions{
  1060. DisableDefaultCmd: true,
  1061. },
  1062. Run: func(cmd *cobra.Command, args []string) {
  1063. if version, _ := cmd.Flags().GetBool("version"); version {
  1064. versionHandler(cmd, args)
  1065. return
  1066. }
  1067. cmd.Print(cmd.UsageString())
  1068. },
  1069. }
  1070. rootCmd.Flags().BoolP("version", "v", false, "Show version information")
  1071. createCmd := &cobra.Command{
  1072. Use: "create MODEL",
  1073. Short: "Create a model from a Modelfile",
  1074. Args: cobra.ExactArgs(1),
  1075. PreRunE: checkServerHeartbeat,
  1076. RunE: CreateHandler,
  1077. }
  1078. createCmd.Flags().StringP("file", "f", "Modelfile", "Name of the Modelfile")
  1079. createCmd.Flags().StringP("quantize", "q", "", "Quantize model to this level (e.g. q4_0)")
  1080. showCmd := &cobra.Command{
  1081. Use: "show MODEL",
  1082. Short: "Show information for a model",
  1083. Args: cobra.ExactArgs(1),
  1084. PreRunE: checkServerHeartbeat,
  1085. RunE: ShowHandler,
  1086. }
  1087. showCmd.Flags().Bool("license", false, "Show license of a model")
  1088. showCmd.Flags().Bool("modelfile", false, "Show Modelfile of a model")
  1089. showCmd.Flags().Bool("parameters", false, "Show parameters of a model")
  1090. showCmd.Flags().Bool("template", false, "Show template of a model")
  1091. showCmd.Flags().Bool("system", false, "Show system message of a model")
  1092. runCmd := &cobra.Command{
  1093. Use: "run MODEL [PROMPT]",
  1094. Short: "Run a model",
  1095. Args: cobra.MinimumNArgs(1),
  1096. PreRunE: checkServerHeartbeat,
  1097. RunE: RunHandler,
  1098. }
  1099. runCmd.Flags().String("keepalive", "", "Duration to keep a model loaded (e.g. 5m)")
  1100. runCmd.Flags().Bool("verbose", false, "Show timings for response")
  1101. runCmd.Flags().Bool("insecure", false, "Use an insecure registry")
  1102. runCmd.Flags().Bool("nowordwrap", false, "Don't wrap words to the next line automatically")
  1103. runCmd.Flags().String("format", "", "Response format (e.g. json)")
  1104. serveCmd := &cobra.Command{
  1105. Use: "serve",
  1106. Aliases: []string{"start"},
  1107. Short: "Start ollama",
  1108. Args: cobra.ExactArgs(0),
  1109. RunE: RunServer,
  1110. }
  1111. pullCmd := &cobra.Command{
  1112. Use: "pull MODEL",
  1113. Short: "Pull a model from a registry",
  1114. Args: cobra.ExactArgs(1),
  1115. PreRunE: checkServerHeartbeat,
  1116. RunE: PullHandler,
  1117. }
  1118. pullCmd.Flags().Bool("insecure", false, "Use an insecure registry")
  1119. pushCmd := &cobra.Command{
  1120. Use: "push MODEL",
  1121. Short: "Push a model to a registry",
  1122. Args: cobra.ExactArgs(1),
  1123. PreRunE: checkServerHeartbeat,
  1124. RunE: PushHandler,
  1125. }
  1126. pushCmd.Flags().Bool("insecure", false, "Use an insecure registry")
  1127. listCmd := &cobra.Command{
  1128. Use: "list",
  1129. Aliases: []string{"ls"},
  1130. Short: "List models",
  1131. PreRunE: checkServerHeartbeat,
  1132. RunE: ListHandler,
  1133. }
  1134. psCmd := &cobra.Command{
  1135. Use: "ps",
  1136. Short: "List running models",
  1137. PreRunE: checkServerHeartbeat,
  1138. RunE: ListRunningHandler,
  1139. }
  1140. copyCmd := &cobra.Command{
  1141. Use: "cp SOURCE DESTINATION",
  1142. Short: "Copy a model",
  1143. Args: cobra.ExactArgs(2),
  1144. PreRunE: checkServerHeartbeat,
  1145. RunE: CopyHandler,
  1146. }
  1147. deleteCmd := &cobra.Command{
  1148. Use: "rm MODEL [MODEL...]",
  1149. Short: "Remove a model",
  1150. Args: cobra.MinimumNArgs(1),
  1151. PreRunE: checkServerHeartbeat,
  1152. RunE: DeleteHandler,
  1153. }
  1154. envVars := envconfig.AsMap()
  1155. envs := []envconfig.EnvVar{envVars["OLLAMA_HOST"]}
  1156. for _, cmd := range []*cobra.Command{
  1157. createCmd,
  1158. showCmd,
  1159. runCmd,
  1160. pullCmd,
  1161. pushCmd,
  1162. listCmd,
  1163. psCmd,
  1164. copyCmd,
  1165. deleteCmd,
  1166. serveCmd,
  1167. } {
  1168. switch cmd {
  1169. case runCmd:
  1170. appendEnvDocs(cmd, []envconfig.EnvVar{envVars["OLLAMA_HOST"], envVars["OLLAMA_NOHISTORY"]})
  1171. case serveCmd:
  1172. appendEnvDocs(cmd, []envconfig.EnvVar{
  1173. envVars["OLLAMA_DEBUG"],
  1174. envVars["OLLAMA_HOST"],
  1175. envVars["OLLAMA_KEEP_ALIVE"],
  1176. envVars["OLLAMA_MAX_LOADED_MODELS"],
  1177. envVars["OLLAMA_MAX_QUEUE"],
  1178. envVars["OLLAMA_MODELS"],
  1179. envVars["OLLAMA_NUM_PARALLEL"],
  1180. envVars["OLLAMA_NOPRUNE"],
  1181. envVars["OLLAMA_ORIGINS"],
  1182. envVars["OLLAMA_SCHED_SPREAD"],
  1183. envVars["OLLAMA_TMPDIR"],
  1184. envVars["OLLAMA_FLASH_ATTENTION"],
  1185. envVars["OLLAMA_LLM_LIBRARY"],
  1186. })
  1187. default:
  1188. appendEnvDocs(cmd, envs)
  1189. }
  1190. }
  1191. rootCmd.AddCommand(
  1192. serveCmd,
  1193. createCmd,
  1194. showCmd,
  1195. runCmd,
  1196. pullCmd,
  1197. pushCmd,
  1198. listCmd,
  1199. psCmd,
  1200. copyCmd,
  1201. deleteCmd,
  1202. )
  1203. return rootCmd
  1204. }