cmd.go 33 KB

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