├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .golangci.yaml ├── LICENSE ├── Makefile ├── README.md ├── cmd └── replicate │ └── main.go ├── demo.gif ├── demo.tape ├── go.mod ├── go.sum ├── internal ├── client │ └── client.go ├── cmd │ ├── account.go │ ├── account │ │ ├── current.go │ │ └── root.go │ ├── auth │ │ ├── login.go │ │ └── root.go │ ├── deployment │ │ ├── create.go │ │ ├── list.go │ │ ├── root.go │ │ ├── schema.go │ │ ├── show.go │ │ └── update.go │ ├── hardware │ │ ├── list.go │ │ └── root.go │ ├── model │ │ ├── create.go │ │ ├── list.go │ │ ├── root.go │ │ ├── run.go │ │ ├── schema.go │ │ └── show.go │ ├── prediction │ │ ├── create.go │ │ ├── list.go │ │ ├── root.go │ │ └── show.go │ ├── run.go │ ├── scaffold.go │ ├── stream.go │ ├── train.go │ └── training │ │ ├── create.go │ │ ├── list.go │ │ ├── root.go │ │ └── show.go ├── config │ └── auth.go ├── identifier │ └── identifier.go ├── util │ ├── download.go │ ├── istty.go │ ├── optparse.go │ ├── schema.go │ ├── status.go │ └── util_test.go └── version.go └── main.go /.gitattributes: -------------------------------------------------------------------------------- 1 | Makefile -linguist-detectable 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["main"] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Set up Go 16 | uses: actions/setup-go@v4 17 | with: 18 | go-version-file: "go.mod" 19 | 20 | - name: Build 21 | run: make 22 | 23 | - name: Test 24 | run: make test 25 | 26 | - name: Lint 27 | run: make lint 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | /r8 3 | /replicate 4 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | linters: 2 | enable: 3 | # Note that these are *additional* linters beyond the defaults: 4 | # 5 | # https://golangci-lint.run/usage/linters/#enabled-by-default 6 | - exportloopref 7 | - gocritic 8 | - revive 9 | - misspell 10 | - unconvert 11 | - bodyclose 12 | 13 | linters-settings: 14 | misspell: 15 | locale: US 16 | issues: 17 | exclude-rules: 18 | - path: _test\.go$ 19 | linters: 20 | - errcheck 21 | - bodyclose 22 | - revive 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2022, Replicate, Inc. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | 3 | DESTDIR ?= 4 | PREFIX = /usr/local 5 | BINDIR = $(PREFIX)/bin 6 | 7 | INSTALL := install -m 0755 8 | INSTALL_PROGRAM := $(INSTALL) 9 | 10 | GO := go 11 | GOOS := $(shell $(GO) env GOOS) 12 | GOARCH := $(shell $(GO) env GOARCH) 13 | 14 | VHS := vhs 15 | 16 | default: all 17 | 18 | .PHONY: all 19 | all: replicate 20 | 21 | replicate: 22 | CGO_ENABLED=0 $(GO) build -o $@ \ 23 | -ldflags "-X github.com/replicate/cli/internal.version=$(REPLICATE_CLI_VERSION) -w" \ 24 | main.go 25 | 26 | demo.gif: replicate demo.tape 27 | PATH=$(PWD):$(PATH) $(VHS) demo.tape 28 | 29 | .PHONY: install 30 | install: replicate 31 | $(INSTALL_PROGRAM) -d $(DESTDIR)$(BINDIR) 32 | $(INSTALL_PROGRAM) replicate $(DESTDIR)$(BINDIR)/replicate 33 | 34 | .PHONY: uninstall 35 | uninstall: 36 | rm -f $(DESTDIR)$(BINDIR)/replicate 37 | 38 | .PHONY: clean 39 | clean: 40 | $(GO) clean 41 | rm -f replicate 42 | 43 | .PHONY: test 44 | test: 45 | $(GO) test -v ./... 46 | 47 | .PHONY: format 48 | format: 49 | $(GO) run golang.org/x/tools/cmd/goimports@latest -d -w -local $(shell $(GO) list -m) . 50 | 51 | .PHONY: lint 52 | lint: lint-golangci lint-nilaway 53 | 54 | .PHONY: lint-golangci 55 | lint-golangci: 56 | $(GO) run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.57.2 run ./... 57 | 58 | .PHONY: lint-nilaway 59 | lint-nilaway: 60 | $(GO) run go.uber.org/nilaway/cmd/nilaway@v0.0.0-20240403175823-755a685ab68b ./... 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Replicate CLI 2 | 3 | ![demo](demo.gif) 4 | 5 | ## Install 6 | 7 | If you're using macOS, you can install the Replicate CLI using Homebrew: 8 | 9 | ```console 10 | brew tap replicate/tap 11 | brew install replicate 12 | ``` 13 | 14 | Or you can build from source and install it with these commands 15 | (requires Go 1.20 or later): 16 | 17 | ```console 18 | make 19 | sudo make install 20 | ``` 21 | 22 | ## Upgrade 23 | 24 | If you previously installed the CLI with Homebrew, 25 | you can upgrade to the latest version by running the following command: 26 | 27 | ```console 28 | brew upgrade replicate 29 | ``` 30 | 31 | ## Usage 32 | 33 | Grab your API token from [replicate.com/account](https://replicate.com/account) 34 | and set the `REPLICATE_API_TOKEN` environment variable. 35 | 36 | ```console 37 | $ export REPLICATE_API_TOKEN= 38 | ``` 39 | 40 | --- 41 | 42 | ```console 43 | Usage: 44 | replicate [command] 45 | 46 | Core commands: 47 | hardware Interact with hardware 48 | model Interact with models 49 | prediction Interact with predictions 50 | scaffold Create a new local development environment from a prediction 51 | training Interact with trainings 52 | 53 | Alias commands: 54 | run Alias for "prediction create" 55 | stream Alias for "prediction create --stream" 56 | train Alias for "training create" 57 | 58 | Additional Commands: 59 | completion Generate the autocompletion script for the specified shell 60 | help Help about any command 61 | 62 | Flags: 63 | -h, --help help for replicate 64 | -v, --version version for replicate 65 | 66 | Use "replicate [command] --help" for more information about a command.``` 67 | ``` 68 | 69 | --- 70 | 71 | ### Create a prediction 72 | 73 | Generate an image with [SDXL]. 74 | 75 | ```console 76 | $ replicate run stability-ai/sdxl \ 77 | prompt="a studio photo of a rainbow colored corgi" 78 | Prediction created: https://replicate.com/p/jpgp263bdekvxileu2ppsy46v4 79 | ``` 80 | 81 | ### Stream prediction output 82 | 83 | Run [LLaMA 2] and stream output tokens to your terminal. 84 | 85 | ```console 86 | $ replicate run meta/llama-2-70b-chat --stream \ 87 | prompt="Tell me a joke about llamas" 88 | Sure, here's a joke about llamas for you: 89 | 90 | Why did the llama refuse to play poker? 91 | 92 | Because he always got fleeced! 93 | ``` 94 | 95 | ### Create a local development environment from a prediction 96 | 97 | Create a Node.js or Python project from a prediction. 98 | 99 | ```console 100 | $ replicate scaffold https://replicate.com/p/jpgp263bdekvxileu2ppsy46v4 --template=node 101 | Cloning starter repo and installing dependencies... 102 | Cloning into 'jpgp263bdekvxileu2ppsy46v4'... 103 | Writing new index.js... 104 | Running example prediction... 105 | [ 106 | 'https://replicate.delivery/pbxt/P79eJmjeJsql40QpRbWVDtGJSoTtLTdJ494kpQexSDhYGy0jA/out-0.png' 107 | ] 108 | Done! 109 | ``` 110 | 111 | ### Chain multiple predictions 112 | 113 | Generate an image with [SDXL] and upscale that image with [ESRGAN]. 114 | 115 | ```console 116 | $ replicate run stability-ai/sdxl \ 117 | prompt="a studio photo of a rainbow colored corgi" | \ 118 | replicate run nightmareai/real-esrgan --web \ 119 | image={{.output[0]}} 120 | # opens prediction in browser (https://replicate.com/p/jpgp263bdekvxileu2ppsy46v4) 121 | ``` 122 | 123 | ### Create a model 124 | 125 | Create a new model on Replicate. 126 | 127 | ```console 128 | $ replicate model create yourname/model --private --hardware gpu-a40-small 129 | ``` 130 | 131 | To list available hardware types: 132 | 133 | ```console 134 | $ replicate hardware list 135 | ``` 136 | 137 | After creating your model, you can [fine-tune an existing model](https://replicate.com/docs/fine-tuning) or [build and push a custom model using Cog](https://replicate.com/docs/guides/push-a-model). 138 | 139 | ### Fine-tune a model 140 | 141 | Fine-tune [SDXL] with your own images: 142 | 143 | ```console 144 | $ replicate train --destination mattt/sdxl-dreambooth --web \ 145 | stability-ai/sdxl \ 146 | input_images=@path/to/pictures.zip \ 147 | use_face_detection_instead=true 148 | # opens the training in browser 149 | ``` 150 | 151 | > [!NOTE] 152 | > Use the `@` prefix to upload a file from your local filesystem. 153 | > It works like curl's `--data-binary` option. 154 | 155 | For more information, 156 | see [our blog post about fine-tuning with SDXL](https://replicate.com/blog/fine-tune-sdxl). 157 | 158 | ### View a model's inputs and outputs 159 | 160 | Get the schema for [SunoAI Bark] 161 | 162 | ```console 163 | $ replicate model schema suno-ai/bark 164 | Inputs: 165 | - prompt: Input prompt (type: string) 166 | - history_prompt: history choice for audio cloning, choose from the list (type: ) 167 | - custom_history_prompt: Provide your own .npz file with history choice for audio cloning, this will override the previous history_prompt setting (type: string) 168 | - text_temp: generation temperature (1.0 more diverse, 0.0 more conservative) (type: number) 169 | - waveform_temp: generation temperature (1.0 more diverse, 0.0 more conservative) (type: number) 170 | - output_full: return full generation as a .npz file to be used as a history prompt (type: boolean) 171 | 172 | Output: 173 | - type: object 174 | ``` 175 | 176 | [api]: https://replicate.com/docs/reference/http 177 | [LLaMA 2]: https://replicate.com/replicate/llama-2-70b-chat 178 | [SDXL]: https://replicate.com/stability-ai/sdxl 179 | [ESRGAN]: https://replicate.com/nightmareai/real-esrgan 180 | [SunoAI Bark]: https://replicate.com/suno-ai/bark 181 | -------------------------------------------------------------------------------- /cmd/replicate/main.go: -------------------------------------------------------------------------------- 1 | package replicate 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/spf13/cobra" 7 | 8 | "github.com/replicate/cli/internal" 9 | "github.com/replicate/cli/internal/cmd" 10 | "github.com/replicate/cli/internal/cmd/account" 11 | "github.com/replicate/cli/internal/cmd/auth" 12 | "github.com/replicate/cli/internal/cmd/deployment" 13 | "github.com/replicate/cli/internal/cmd/hardware" 14 | "github.com/replicate/cli/internal/cmd/model" 15 | "github.com/replicate/cli/internal/cmd/prediction" 16 | "github.com/replicate/cli/internal/cmd/training" 17 | ) 18 | 19 | // rootCmd represents the base command when called without any subcommands 20 | var rootCmd = &cobra.Command{ 21 | Use: "replicate", 22 | Version: internal.Version(), 23 | } 24 | 25 | // Execute adds all child commands to the root command and sets flags appropriately. 26 | // This is called by main.main(). It only needs to happen once to the rootCmd. 27 | func Execute() { 28 | err := rootCmd.Execute() 29 | if err != nil { 30 | os.Exit(1) 31 | } 32 | } 33 | 34 | func init() { 35 | rootCmd.AddGroup(&cobra.Group{ 36 | ID: "core", 37 | Title: "Core commands:", 38 | }) 39 | for _, cmd := range []*cobra.Command{ 40 | account.RootCmd, 41 | auth.RootCmd, 42 | model.RootCmd, 43 | prediction.RootCmd, 44 | training.RootCmd, 45 | deployment.RootCmd, 46 | hardware.RootCmd, 47 | cmd.ScaffoldCmd, 48 | } { 49 | rootCmd.AddCommand(cmd) 50 | cmd.GroupID = "core" 51 | } 52 | 53 | rootCmd.AddGroup(&cobra.Group{ 54 | ID: "alias", 55 | Title: "Alias commands:", 56 | }) 57 | for _, cmd := range []*cobra.Command{ 58 | cmd.RunCmd, 59 | cmd.TrainCmd, 60 | cmd.StreamCmd, 61 | cmd.AccountCmd, 62 | } { 63 | rootCmd.AddCommand(cmd) 64 | cmd.GroupID = "alias" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/replicate/cli/a865c187d03798b5d2cf63837a27e43ec9b301d7/demo.gif -------------------------------------------------------------------------------- /demo.tape: -------------------------------------------------------------------------------- 1 | Output demo.gif 2 | 3 | Set Margin 20 4 | Set MarginFill "#009B77" 5 | Set BorderRadius 10 6 | 7 | Set FontSize 24 8 | Set Width 1200 9 | Set Height 600 10 | 11 | Type "echo " 12 | Sleep 100ms 13 | Hide 14 | Type "r8_•••••••••••••••••••••••••••••••••••••" 15 | Show 16 | Sleep 100ms 17 | Type " | replicate auth login" 18 | Sleep 100ms 19 | Ctrl+C # Don't actually set the API key 20 | Sleep 1s 21 | 22 | Type 'replicate run meta/llama-2-70b-chat \' 23 | Enter 24 | Type@50ms ' prompt="write a haiku about corgis"' 25 | Enter 26 | 27 | Sleep 2s 28 | 29 | Enter 30 | 31 | Enter 32 | Type 'replicate run stability-ai/sdxl \' 33 | Enter 34 | Type@50ms ' prompt="a studio photo of a rainbow colored corgi" \' 35 | Enter 36 | Type@50ms ' width=512 height=512 seed=42069' 37 | Enter 38 | 39 | Sleep 30s 40 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/replicate/cli 2 | 3 | go 1.21 4 | 5 | toolchain go1.21.1 6 | 7 | require ( 8 | github.com/PaesslerAG/jsonpath v0.1.1 9 | github.com/briandowns/spinner v1.23.0 10 | github.com/charmbracelet/bubbles v0.16.1 11 | github.com/charmbracelet/bubbletea v0.26.4 12 | github.com/charmbracelet/lipgloss v0.11.0 13 | github.com/cli/browser v1.3.0 14 | github.com/getkin/kin-openapi v0.125.0 15 | github.com/mattn/go-isatty v0.0.20 16 | github.com/replicate/replicate-go v0.21.0 17 | github.com/schollz/progressbar/v3 v3.14.4 18 | github.com/spf13/cobra v1.8.1 19 | github.com/stretchr/testify v1.9.0 20 | golang.org/x/sync v0.7.0 21 | gopkg.in/yaml.v3 v3.0.1 22 | ) 23 | 24 | require ( 25 | github.com/PaesslerAG/gval v1.2.2 // indirect 26 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 27 | github.com/charmbracelet/x/ansi v0.1.2 // indirect 28 | github.com/charmbracelet/x/input v0.1.0 // indirect 29 | github.com/charmbracelet/x/term v0.1.1 // indirect 30 | github.com/charmbracelet/x/windows v0.1.0 // indirect 31 | github.com/davecgh/go-spew v1.1.1 // indirect 32 | github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect 33 | github.com/fatih/color v1.16.0 // indirect 34 | github.com/go-openapi/jsonpointer v0.20.2 // indirect 35 | github.com/go-openapi/swag v0.22.8 // indirect 36 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 37 | github.com/invopop/yaml v0.2.0 // indirect 38 | github.com/josharian/intern v1.0.0 // indirect 39 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 40 | github.com/mailru/easyjson v0.7.7 // indirect 41 | github.com/mattn/go-colorable v0.1.13 // indirect 42 | github.com/mattn/go-localereader v0.0.1 // indirect 43 | github.com/mattn/go-runewidth v0.0.15 // indirect 44 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect 45 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect 46 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect 47 | github.com/muesli/cancelreader v0.2.2 // indirect 48 | github.com/muesli/termenv v0.15.2 // indirect 49 | github.com/perimeterx/marshmallow v1.1.5 // indirect 50 | github.com/pmezard/go-difflib v1.0.0 // indirect 51 | github.com/rivo/uniseg v0.4.7 // indirect 52 | github.com/shopspring/decimal v1.3.1 // indirect 53 | github.com/spf13/pflag v1.0.5 // indirect 54 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 55 | golang.org/x/sys v0.20.0 // indirect 56 | golang.org/x/term v0.20.0 // indirect 57 | golang.org/x/text v0.14.0 // indirect 58 | ) 59 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/PaesslerAG/gval v1.0.0/go.mod h1:y/nm5yEyTeX6av0OfKJNp9rBNj2XrGhAf5+v24IBN1I= 2 | github.com/PaesslerAG/gval v1.2.2 h1:Y7iBzhgE09IGTt5QgGQ2IdaYYYOU134YGHBThD+wm9E= 3 | github.com/PaesslerAG/gval v1.2.2/go.mod h1:XRFLwvmkTEdYziLdaCeCa5ImcGVrfQbeNUbVR+C6xac= 4 | github.com/PaesslerAG/jsonpath v0.1.0/go.mod h1:4BzmtoM/PI8fPO4aQGIusjGxGir2BzcV0grWtFzq1Y8= 5 | github.com/PaesslerAG/jsonpath v0.1.1 h1:c1/AToHQMVsduPAa4Vh6xp2U0evy4t8SWp8imEsylIk= 6 | github.com/PaesslerAG/jsonpath v0.1.1/go.mod h1:lVboNxFGal/VwW6d9JzIy56bUsYAP6tH/x80vjnCseY= 7 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 8 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 9 | github.com/briandowns/spinner v1.23.0 h1:alDF2guRWqa/FOZZYWjlMIx2L6H0wyewPxo/CH4Pt2A= 10 | github.com/briandowns/spinner v1.23.0/go.mod h1:rPG4gmXeN3wQV/TsAY4w8lPdIM6RX3yqeBQJSrbXjuE= 11 | github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY= 12 | github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc= 13 | github.com/charmbracelet/bubbletea v0.26.4 h1:2gDkkzLZaTjMl/dQBpNVtnvcCxsh/FCkimep7FC9c40= 14 | github.com/charmbracelet/bubbletea v0.26.4/go.mod h1:P+r+RRA5qtI1DOHNFn0otoNwB4rn+zNAzSj/EXz6xU0= 15 | github.com/charmbracelet/lipgloss v0.11.0 h1:UoAcbQ6Qml8hDwSWs0Y1cB5TEQuZkDPH/ZqwWWYTG4g= 16 | github.com/charmbracelet/lipgloss v0.11.0/go.mod h1:1UdRTH9gYgpcdNN5oBtjbu/IzNKtzVtb7sqN1t9LNn8= 17 | github.com/charmbracelet/x/ansi v0.1.2 h1:6+LR39uG8DE6zAmbu023YlqjJHkYXDF1z36ZwzO4xZY= 18 | github.com/charmbracelet/x/ansi v0.1.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= 19 | github.com/charmbracelet/x/input v0.1.0 h1:TEsGSfZYQyOtp+STIjyBq6tpRaorH0qpwZUj8DavAhQ= 20 | github.com/charmbracelet/x/input v0.1.0/go.mod h1:ZZwaBxPF7IG8gWWzPUVqHEtWhc1+HXJPNuerJGRGZ28= 21 | github.com/charmbracelet/x/term v0.1.1 h1:3cosVAiPOig+EV4X9U+3LDgtwwAoEzJjNdwbXDjF6yI= 22 | github.com/charmbracelet/x/term v0.1.1/go.mod h1:wB1fHt5ECsu3mXYusyzcngVWWlu1KKUmmLhfgr/Flxw= 23 | github.com/charmbracelet/x/windows v0.1.0 h1:gTaxdvzDM5oMa/I2ZNF7wN78X/atWemG9Wph7Ika2k4= 24 | github.com/charmbracelet/x/windows v0.1.0/go.mod h1:GLEO/l+lizvFDBPLIOk+49gdX49L9YWMB5t+DZd0jkQ= 25 | github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo= 26 | github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= 27 | github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 28 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 29 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 30 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 31 | github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= 32 | github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= 33 | github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= 34 | github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= 35 | github.com/getkin/kin-openapi v0.125.0 h1:jyQCyf2qXS1qvs2U00xQzkGCqYPhEhZDmSmVt65fXno= 36 | github.com/getkin/kin-openapi v0.125.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM= 37 | github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= 38 | github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= 39 | github.com/go-openapi/swag v0.22.8 h1:/9RjDSQ0vbFR+NyjGMkFTsA1IA0fmhKSThmfGZjicbw= 40 | github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI= 41 | github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= 42 | github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= 43 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 44 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 45 | github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY= 46 | github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= 47 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 48 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 49 | github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= 50 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 51 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 52 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 53 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 54 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 55 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 56 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 57 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 58 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 59 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 60 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 61 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 62 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 63 | github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= 64 | github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= 65 | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= 66 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 67 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= 68 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= 69 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= 70 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= 71 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= 72 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= 73 | github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= 74 | github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= 75 | github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= 76 | github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= 77 | github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= 78 | github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= 79 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 80 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 81 | github.com/replicate/replicate-go v0.21.0 h1:37eWInQH7rRr14Epj5axfh/Iko+vsOqsS1JKm9DEhr4= 82 | github.com/replicate/replicate-go v0.21.0/go.mod h1:D2x8SztjeUKcaYnSgVu3H2DechufLJWZJB4+TLA3Rag= 83 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 84 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 85 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 86 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 87 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 88 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 89 | github.com/schollz/progressbar/v3 v3.14.4 h1:W9ZrDSJk7eqmQhd3uxFNNcTr0QL+xuGNI9dEMrw0r74= 90 | github.com/schollz/progressbar/v3 v3.14.4/go.mod h1:aT3UQ7yGm+2ZjeXPqsjTenwL3ddUiuZ0kfQ/2tHlyNI= 91 | github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= 92 | github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 93 | github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= 94 | github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= 95 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 96 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 97 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 98 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 99 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 100 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 101 | github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= 102 | github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= 103 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= 104 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 105 | golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= 106 | golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= 107 | golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= 108 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 109 | golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 110 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 111 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 112 | golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= 113 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 114 | golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= 115 | golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= 116 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 117 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 118 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 119 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 120 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 121 | gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 122 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 123 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 124 | -------------------------------------------------------------------------------- /internal/client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/replicate/replicate-go" 9 | 10 | "github.com/replicate/cli/internal" 11 | "github.com/replicate/cli/internal/config" 12 | ) 13 | 14 | func NewClient(opts ...replicate.ClientOption) (*replicate.Client, error) { 15 | token, err := getToken() 16 | if err != nil { 17 | return nil, fmt.Errorf("failed to get API token: %w", err) 18 | } 19 | 20 | baseURL := getBaseURL() 21 | 22 | // Validate token when connecting to api.replicate.com. 23 | // Alternate API hosts proxying Replicate may not require a token. 24 | if token == "" && baseURL == config.DefaultBaseURL { 25 | return nil, fmt.Errorf("please authenticate with `replicate auth login`") 26 | } 27 | 28 | return NewClientWithAPIToken(token, opts...) 29 | } 30 | 31 | func NewClientWithAPIToken(token string, opts ...replicate.ClientOption) (*replicate.Client, error) { 32 | baseURL := getBaseURL() 33 | userAgent := fmt.Sprintf("replicate-cli/%s", internal.Version()) 34 | 35 | opts = append([]replicate.ClientOption{ 36 | replicate.WithBaseURL(baseURL), 37 | replicate.WithToken(token), 38 | replicate.WithUserAgent(userAgent), 39 | }, opts...) 40 | 41 | r8, err := replicate.NewClient(opts...) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | return r8, nil 47 | } 48 | 49 | func VerifyToken(ctx context.Context, token string) (bool, error) { 50 | r8, err := NewClientWithAPIToken(token) 51 | if err != nil { 52 | return false, err 53 | } 54 | 55 | // FIXME: Add better endpoint for verifying token 56 | _, err = r8.ListHardware(ctx) 57 | if err != nil { 58 | return false, nil 59 | } 60 | 61 | return true, nil 62 | } 63 | 64 | func getToken() (string, error) { 65 | token, exists := os.LookupEnv("REPLICATE_API_TOKEN") 66 | if !exists { 67 | return config.GetAPIToken() 68 | } 69 | return token, nil 70 | } 71 | 72 | func getBaseURL() string { 73 | baseURL, exists := os.LookupEnv("REPLICATE_BASE_URL") 74 | if !exists { 75 | baseURL = config.GetAPIBaseURL() 76 | } 77 | return baseURL 78 | } 79 | -------------------------------------------------------------------------------- /internal/cmd/account.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | 6 | "github.com/replicate/cli/internal/cmd/account" 7 | ) 8 | 9 | var AccountCmd = &cobra.Command{ 10 | Use: "account", 11 | Short: `Alias for "accounts current"`, 12 | Aliases: []string{"profile", "whoami"}, 13 | RunE: account.CurrentCmd.RunE, 14 | } 15 | 16 | func init() { 17 | account.AddCurrentAccountFlags(AccountCmd) 18 | } 19 | -------------------------------------------------------------------------------- /internal/cmd/account/current.go: -------------------------------------------------------------------------------- 1 | package account 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/cli/browser" 8 | "github.com/spf13/cobra" 9 | 10 | "github.com/replicate/cli/internal/client" 11 | "github.com/replicate/cli/internal/util" 12 | ) 13 | 14 | // CurrentCmd represents the get current account command 15 | var CurrentCmd = &cobra.Command{ 16 | Use: "current", 17 | Short: "Show the current account", 18 | RunE: func(cmd *cobra.Command, _ []string) error { 19 | ctx := cmd.Context() 20 | 21 | r8, err := client.NewClient() 22 | if err != nil { 23 | return err 24 | } 25 | 26 | account, err := r8.GetCurrentAccount(ctx) 27 | if err != nil { 28 | return fmt.Errorf("failed to get account: %w", err) 29 | } 30 | 31 | if cmd.Flags().Changed("web") { 32 | if util.IsTTY() { 33 | fmt.Println("Opening in browser...") 34 | } 35 | 36 | url := "https://replicate.com/" + account.Username 37 | err := browser.OpenURL(url) 38 | if err != nil { 39 | return fmt.Errorf("failed to open browser: %w", err) 40 | } 41 | 42 | return nil 43 | } 44 | 45 | if cmd.Flags().Changed("json") || !util.IsTTY() { 46 | bytes, err := json.MarshalIndent(account, "", " ") 47 | if err != nil { 48 | return fmt.Errorf("failed to marshal account: %w", err) 49 | } 50 | fmt.Println(string(bytes)) 51 | return nil 52 | } 53 | 54 | fmt.Printf("Type: %s\n", account.Type) 55 | fmt.Printf("Username: %s\n", account.Username) 56 | fmt.Printf("Name: %s\n", account.Name) 57 | fmt.Printf("GitHub URL: %s\n", account.GithubURL) 58 | 59 | return nil 60 | }, 61 | } 62 | 63 | func init() { 64 | AddCurrentAccountFlags(CurrentCmd) 65 | } 66 | 67 | func AddCurrentAccountFlags(cmd *cobra.Command) { 68 | cmd.Flags().Bool("json", false, "Emit JSON") 69 | cmd.Flags().Bool("web", false, "View on web") 70 | cmd.MarkFlagsMutuallyExclusive("json", "web") 71 | } 72 | -------------------------------------------------------------------------------- /internal/cmd/account/root.go: -------------------------------------------------------------------------------- 1 | package account 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | var RootCmd = &cobra.Command{ 8 | Use: "account [subcommand]", 9 | Short: "Interact with accounts", 10 | Aliases: []string{"accounts", "a"}, 11 | } 12 | 13 | func init() { 14 | RootCmd.AddGroup(&cobra.Group{ 15 | ID: "subcommand", 16 | Title: "Subcommands:", 17 | }) 18 | for _, cmd := range []*cobra.Command{ 19 | CurrentCmd, 20 | } { 21 | RootCmd.AddCommand(cmd) 22 | cmd.GroupID = "subcommand" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /internal/cmd/auth/login.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "strings" 8 | 9 | "github.com/spf13/cobra" 10 | 11 | "github.com/replicate/cli/internal/client" 12 | "github.com/replicate/cli/internal/config" 13 | ) 14 | 15 | // loginCmd represents the login command 16 | var loginCmd = &cobra.Command{ 17 | Use: "login --token-stdin", 18 | Short: "Log in to Replicate", 19 | Long: `Log in to Replicate 20 | 21 | You can find your Replicate API token at https://replicate.com/account`, 22 | Example: ` 23 | # Log in with environment variable 24 | $ echo $REPLICATE_API_TOKEN | replicate auth login --token-stdin 25 | 26 | # Log in with token file 27 | $ replicate auth login --token-stdin < path/to/token`, 28 | RunE: func(cmd *cobra.Command, _ []string) error { 29 | ctx := cmd.Context() 30 | 31 | tokenStdin, err := cmd.Flags().GetBool("token-stdin") 32 | if err != nil { 33 | return err 34 | } 35 | 36 | var token string 37 | if tokenStdin { 38 | token, err = readTokenFromStdin() 39 | if err != nil { 40 | return fmt.Errorf("failed to read token from stdin: %w", err) 41 | } 42 | if token == "" { 43 | return fmt.Errorf("no token provided (empty string)") 44 | } 45 | } else { 46 | return fmt.Errorf("token must be passed to stdin with --token-stdin flag") 47 | } 48 | token = strings.TrimSpace(token) 49 | 50 | ok, err := client.VerifyToken(ctx, token) 51 | if err != nil { 52 | return fmt.Errorf("error verifying token: %w", err) 53 | } 54 | if !ok { 55 | return fmt.Errorf("invalid token") 56 | } 57 | 58 | if err := config.SetAPIToken(token); err != nil { 59 | return fmt.Errorf("failed to set API token: %w", err) 60 | } 61 | 62 | fmt.Printf("Token saved to configuration file: %s\n", config.ConfigFilePath) 63 | 64 | return nil 65 | }, 66 | } 67 | 68 | func readTokenFromStdin() (string, error) { 69 | tokenBytes, err := io.ReadAll(os.Stdin) 70 | if err != nil { 71 | return "", fmt.Errorf("Failed to read token from stdin: %w", err) 72 | } 73 | return string(tokenBytes), nil 74 | } 75 | 76 | func init() { 77 | loginCmd.Flags().Bool("token-stdin", false, "Take the token from stdin.") 78 | _ = loginCmd.MarkFlagRequired("token-stdin") 79 | } 80 | -------------------------------------------------------------------------------- /internal/cmd/auth/root.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | var RootCmd = &cobra.Command{ 8 | Use: "auth [subcommand]", 9 | Short: "Authenticate with Replicate", 10 | } 11 | 12 | func init() { 13 | RootCmd.AddGroup(&cobra.Group{ 14 | ID: "subcommand", 15 | Title: "Subcommands:", 16 | }) 17 | for _, cmd := range []*cobra.Command{ 18 | loginCmd, 19 | } { 20 | RootCmd.AddCommand(cmd) 21 | cmd.GroupID = "subcommand" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /internal/cmd/deployment/create.go: -------------------------------------------------------------------------------- 1 | package deployment 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/cli/browser" 8 | "github.com/replicate/replicate-go" 9 | "github.com/spf13/cobra" 10 | 11 | "github.com/replicate/cli/internal/client" 12 | "github.com/replicate/cli/internal/identifier" 13 | "github.com/replicate/cli/internal/util" 14 | ) 15 | 16 | // createCmd represents the create command 17 | var createCmd = &cobra.Command{ 18 | Use: "create <[owner/]name> [flags]", 19 | Short: "Create a new deployment", 20 | Example: `replicate deployment create text-to-image --model=stability-ai/sdxl --hardware=gpu-a100-large`, 21 | Args: cobra.ExactArgs(1), 22 | RunE: func(cmd *cobra.Command, args []string) error { 23 | r8, err := client.NewClient() 24 | if err != nil { 25 | return err 26 | } 27 | 28 | opts := &replicate.CreateDeploymentOptions{} 29 | 30 | opts.Name = args[0] 31 | 32 | flags := cmd.Flags() 33 | 34 | modelFlag, _ := flags.GetString("model") 35 | id, err := identifier.ParseIdentifier(modelFlag) 36 | if err != nil { 37 | return fmt.Errorf("expected /[:version] but got %s", args[0]) 38 | } 39 | opts.Model = fmt.Sprintf("%s/%s", id.Owner, id.Name) 40 | if id.Version != "" { 41 | opts.Version = id.Version 42 | } else { 43 | model, err := r8.GetModel(cmd.Context(), id.Owner, id.Name) 44 | if err != nil { 45 | return fmt.Errorf("failed to get model: %w", err) 46 | } 47 | opts.Version = model.LatestVersion.ID 48 | } 49 | 50 | opts.Hardware, _ = flags.GetString("hardware") 51 | 52 | flagMap := map[string]*int{ 53 | "min-instances": &opts.MinInstances, 54 | "max-instances": &opts.MaxInstances, 55 | } 56 | for flagName, optPtr := range flagMap { 57 | if flags.Changed(flagName) { 58 | value, _ := flags.GetInt(flagName) 59 | *optPtr = value 60 | } 61 | } 62 | 63 | deployment, err := r8.CreateDeployment(cmd.Context(), *opts) 64 | if err != nil { 65 | return fmt.Errorf("failed to create deployment: %w", err) 66 | } 67 | 68 | if flags.Changed("json") || !util.IsTTY() { 69 | bytes, err := json.MarshalIndent(deployment, "", " ") 70 | if err != nil { 71 | return fmt.Errorf("failed to serialize model: %w", err) 72 | } 73 | fmt.Println(string(bytes)) 74 | return nil 75 | } 76 | 77 | url := fmt.Sprintf("https://replicate.com/deployments/%s/%s", deployment.Owner, deployment.Name) 78 | if flags.Changed("web") { 79 | if util.IsTTY() { 80 | fmt.Println("Opening in browser...") 81 | } 82 | 83 | err := browser.OpenURL(url) 84 | if err != nil { 85 | return fmt.Errorf("failed to open browser: %w", err) 86 | } 87 | 88 | return nil 89 | } 90 | 91 | fmt.Printf("Deployment created: %s\n", url) 92 | 93 | return nil 94 | }, 95 | } 96 | 97 | func init() { 98 | addCreateFlags(createCmd) 99 | } 100 | 101 | func addCreateFlags(cmd *cobra.Command) { 102 | cmd.Flags().String("model", "", "Model to deploy") 103 | _ = cmd.MarkFlagRequired("model") 104 | 105 | cmd.Flags().String("hardware", "", "SKU of the hardware to run the model") 106 | _ = cmd.MarkFlagRequired("hardware") 107 | 108 | cmd.Flags().Int("min-instances", 0, "Minimum number of instances to run the model") 109 | cmd.Flags().Int("max-instances", 0, "Maximum number of instances to run the model") 110 | 111 | cmd.Flags().Bool("json", false, "Emit JSON") 112 | cmd.Flags().Bool("web", false, "View on web") 113 | cmd.MarkFlagsMutuallyExclusive("json", "web") 114 | } 115 | -------------------------------------------------------------------------------- /internal/cmd/deployment/list.go: -------------------------------------------------------------------------------- 1 | package deployment 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os/exec" 7 | "strconv" 8 | 9 | "github.com/spf13/cobra" 10 | 11 | "github.com/replicate/cli/internal/client" 12 | "github.com/replicate/cli/internal/util" 13 | 14 | "github.com/charmbracelet/bubbles/table" 15 | tea "github.com/charmbracelet/bubbletea" 16 | "github.com/charmbracelet/lipgloss" 17 | ) 18 | 19 | var baseStyle = lipgloss.NewStyle(). 20 | BorderStyle(lipgloss.NormalBorder()). 21 | BorderForeground(lipgloss.Color("240")) 22 | 23 | type model struct { 24 | table table.Model 25 | } 26 | 27 | func (m model) Init() tea.Cmd { return nil } 28 | 29 | func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 30 | var cmd tea.Cmd 31 | switch msg := msg.(type) { //nolint:gocritic 32 | case tea.KeyMsg: 33 | switch msg.String() { 34 | case "esc": 35 | if m.table.Focused() { 36 | m.table.Blur() 37 | } else { 38 | m.table.Focus() 39 | } 40 | case "q", "ctrl+c": 41 | return m, tea.Quit 42 | case "enter": 43 | selected := m.table.SelectedRow() 44 | if len(selected) == 0 { 45 | return m, nil 46 | } 47 | url := fmt.Sprintf("https://replicate.com/deployments/%s", selected[0]) 48 | return m, tea.ExecProcess(exec.Command("open", url), nil) 49 | } 50 | } 51 | m.table, cmd = m.table.Update(msg) 52 | return m, cmd 53 | } 54 | 55 | func (m model) View() string { 56 | return baseStyle.Render(m.table.View()) + "\n" 57 | } 58 | 59 | var listCmd = &cobra.Command{ 60 | Use: "list", 61 | Short: "List deployments", 62 | Example: "replicate deployment list", 63 | RunE: func(cmd *cobra.Command, _ []string) error { 64 | ctx := cmd.Context() 65 | 66 | r8, err := client.NewClient() 67 | if err != nil { 68 | return err 69 | } 70 | 71 | deployments, err := r8.ListDeployments(ctx) 72 | if err != nil { 73 | return fmt.Errorf("failed to get deployments: %w", err) 74 | } 75 | 76 | if cmd.Flags().Changed("json") || !util.IsTTY() { 77 | bytes, err := json.MarshalIndent(deployments, "", " ") 78 | if err != nil { 79 | return fmt.Errorf("failed to marshal deployments: %w", err) 80 | } 81 | fmt.Println(string(bytes)) 82 | return nil 83 | } 84 | 85 | columns := []table.Column{ 86 | {Title: "Name", Width: 20}, 87 | {Title: "Release #", Width: 10}, 88 | {Title: "Model Version", Width: 60}, 89 | } 90 | 91 | rows := []table.Row{} 92 | 93 | for _, deployment := range deployments.Results { 94 | rows = append(rows, table.Row{ 95 | deployment.Owner + "/" + deployment.Name, 96 | strconv.Itoa(deployment.CurrentRelease.Number), 97 | fmt.Sprintf("%s:%s", deployment.CurrentRelease.Model, deployment.CurrentRelease.Version), 98 | }) 99 | } 100 | 101 | t := table.New( 102 | table.WithColumns(columns), 103 | table.WithRows(rows), 104 | table.WithFocused(true), 105 | table.WithHeight(30), 106 | ) 107 | 108 | s := table.DefaultStyles() 109 | s.Header = s.Header. 110 | BorderStyle(lipgloss.NormalBorder()). 111 | BorderForeground(lipgloss.Color("240")). 112 | BorderBottom(true). 113 | Bold(false) 114 | s.Selected = s.Selected. 115 | Foreground(lipgloss.Color("229")). 116 | Background(lipgloss.Color("57")). 117 | Bold(false) 118 | t.SetStyles(s) 119 | 120 | m := model{t} 121 | if _, err := tea.NewProgram(m).Run(); err != nil { 122 | return err 123 | } 124 | 125 | return nil 126 | }, 127 | } 128 | 129 | func init() { 130 | addListFlags(listCmd) 131 | } 132 | 133 | func addListFlags(cmd *cobra.Command) { 134 | cmd.Flags().Bool("json", false, "Emit JSON") 135 | } 136 | -------------------------------------------------------------------------------- /internal/cmd/deployment/root.go: -------------------------------------------------------------------------------- 1 | package deployment 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | var RootCmd = &cobra.Command{ 8 | Use: "deployments [subcommand]", 9 | Short: "Interact with deployments", 10 | Aliases: []string{"deployments", "d"}, 11 | } 12 | 13 | func init() { 14 | RootCmd.AddGroup(&cobra.Group{ 15 | ID: "subcommand", 16 | Title: "Subcommands:", 17 | }) 18 | for _, cmd := range []*cobra.Command{ 19 | listCmd, 20 | showCmd, 21 | schemaCmd, 22 | createCmd, 23 | updateCmd, 24 | } { 25 | RootCmd.AddCommand(cmd) 26 | cmd.GroupID = "subcommand" 27 | } 28 | 29 | // RootCmd.AddGroup(&cobra.Group{ 30 | // ID: "alias", 31 | // Title: "Alias commands:", 32 | // }) 33 | // for _, cmd := range []*cobra.Command{ 34 | // runCmd, 35 | // } { 36 | // RootCmd.AddCommand(cmd) 37 | // cmd.GroupID = "alias" 38 | // } 39 | } 40 | -------------------------------------------------------------------------------- /internal/cmd/deployment/schema.go: -------------------------------------------------------------------------------- 1 | package deployment 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/replicate/cli/internal/client" 8 | "github.com/replicate/cli/internal/identifier" 9 | "github.com/replicate/cli/internal/util" 10 | 11 | "github.com/replicate/replicate-go" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | var schemaCmd = &cobra.Command{ 16 | Use: "schema <[owner/]name>", 17 | Short: "Show the inputs and outputs of a deployment", 18 | Example: `replicate deployment schema acme/text-to-image`, 19 | Args: cobra.ExactArgs(1), 20 | RunE: func(cmd *cobra.Command, args []string) error { 21 | id, err := identifier.ParseIdentifier(args[0]) 22 | if err != nil { 23 | return fmt.Errorf("invalid model specified: %s", args[0]) 24 | } 25 | 26 | ctx := cmd.Context() 27 | 28 | r8, err := client.NewClient() 29 | if err != nil { 30 | return err 31 | } 32 | 33 | deployment, err := r8.GetDeployment(ctx, id.Owner, id.Name) 34 | if err != nil { 35 | return fmt.Errorf("failed to get deployment: %w", err) 36 | } 37 | 38 | if deployment.CurrentRelease.Version == "" { 39 | return fmt.Errorf("deployment %s has no current release", args[0]) 40 | } 41 | 42 | version, err := r8.GetModelVersion(ctx, id.Owner, id.Name, deployment.CurrentRelease.Version) 43 | if err != nil { 44 | return fmt.Errorf("failed to get model version of current release: %w", err) 45 | } 46 | 47 | if cmd.Flags().Changed("json") || !util.IsTTY() { 48 | bytes, err := json.MarshalIndent(version.OpenAPISchema, "", " ") 49 | if err != nil { 50 | return fmt.Errorf("failed to serialize schema: %w", err) 51 | } 52 | fmt.Println(string(bytes)) 53 | 54 | return nil 55 | } 56 | 57 | return printModelVersionSchema(version) 58 | }, 59 | } 60 | 61 | // TODO: move this to util package 62 | func printModelVersionSchema(version *replicate.ModelVersion) error { 63 | inputSchema, outputSchema, err := util.GetSchemas(*version) 64 | if err != nil { 65 | return fmt.Errorf("failed to get schemas: %w", err) 66 | } 67 | 68 | if inputSchema != nil { 69 | fmt.Println("Inputs:") 70 | 71 | for _, propName := range util.SortedKeys(inputSchema.Properties) { 72 | prop, ok := inputSchema.Properties[propName] 73 | if !ok { 74 | continue 75 | } 76 | 77 | description := prop.Value.Description 78 | if prop.Value.Enum != nil { 79 | for _, enum := range prop.Value.Enum { 80 | description += fmt.Sprintf("\n- %s", enum) 81 | } 82 | } 83 | 84 | fmt.Printf("- %s: %s (type: %s)\n", propName, description, prop.Value.Type) 85 | } 86 | fmt.Println() 87 | } 88 | 89 | if outputSchema != nil { 90 | fmt.Println("Output:") 91 | fmt.Printf("- type: %s\n", outputSchema.Type) 92 | if outputSchema.Type.Is("array") { 93 | fmt.Printf("- items: %s %s\n", outputSchema.Items.Value.Type, outputSchema.Items.Value.Format) 94 | } 95 | fmt.Println() 96 | } 97 | 98 | return nil 99 | } 100 | 101 | func init() { 102 | schemaCmd.Flags().Bool("json", false, "Emit JSON") 103 | } 104 | -------------------------------------------------------------------------------- /internal/cmd/deployment/show.go: -------------------------------------------------------------------------------- 1 | package deployment 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/cli/browser" 9 | "github.com/spf13/cobra" 10 | 11 | "github.com/replicate/cli/internal/client" 12 | "github.com/replicate/cli/internal/identifier" 13 | "github.com/replicate/cli/internal/util" 14 | ) 15 | 16 | var showCmd = &cobra.Command{ 17 | Use: "show <[owner/]name> [flags]", 18 | Short: "Show a deployment", 19 | Example: "replicate deployment show acme/text-to-image", 20 | Args: cobra.ExactArgs(1), 21 | Aliases: []string{"view"}, 22 | RunE: func(cmd *cobra.Command, args []string) error { 23 | ctx := cmd.Context() 24 | 25 | r8, err := client.NewClient() 26 | if err != nil { 27 | return err 28 | } 29 | 30 | name := args[0] 31 | if !strings.Contains(name, "/") { 32 | account, err := r8.GetCurrentAccount(ctx) 33 | if err != nil { 34 | return fmt.Errorf("failed to get current account: %w", err) 35 | } 36 | name = fmt.Sprintf("%s/%s", account.Username, name) 37 | } 38 | id, err := identifier.ParseIdentifier(name) 39 | if err != nil { 40 | return fmt.Errorf("invalid deployment specified: %s", name) 41 | } 42 | 43 | if cmd.Flags().Changed("web") { 44 | if util.IsTTY() { 45 | fmt.Println("Opening in browser...") 46 | } 47 | 48 | url := fmt.Sprintf("https://replicate.com/deployments/%s/%s", id.Owner, id.Name) 49 | err := browser.OpenURL(url) 50 | if err != nil { 51 | return fmt.Errorf("failed to open browser: %w", err) 52 | } 53 | 54 | return nil 55 | } 56 | 57 | deployment, err := r8.GetDeployment(ctx, id.Owner, id.Name) 58 | if err != nil { 59 | return fmt.Errorf("failed to get deployment: %w", err) 60 | } 61 | 62 | if cmd.Flags().Changed("json") || !util.IsTTY() { 63 | bytes, err := json.MarshalIndent(deployment, "", " ") 64 | if err != nil { 65 | return fmt.Errorf("failed to marshal model: %w", err) 66 | } 67 | fmt.Println(string(bytes)) 68 | return nil 69 | } 70 | 71 | if id.Version != "" { 72 | fmt.Println("Ignoring specified version", id.Version) 73 | } 74 | 75 | fmt.Printf("%s/%s\n", deployment.Owner, deployment.Name) 76 | fmt.Println() 77 | fmt.Printf("Release #%d\n", deployment.CurrentRelease.Number) 78 | fmt.Println("Model:", deployment.CurrentRelease.Model) 79 | fmt.Println("Version:", deployment.CurrentRelease.Version) 80 | fmt.Println("Hardware:", deployment.CurrentRelease.Configuration.Hardware) 81 | fmt.Println("Min instances:", deployment.CurrentRelease.Configuration.MinInstances) 82 | fmt.Println("Max instances:", deployment.CurrentRelease.Configuration.MaxInstances) 83 | 84 | return nil 85 | }, 86 | } 87 | 88 | func init() { 89 | showCmd.Flags().Bool("json", false, "Emit JSON") 90 | showCmd.Flags().Bool("web", false, "Open in web browser") 91 | 92 | showCmd.MarkFlagsMutuallyExclusive("json", "web") 93 | } 94 | -------------------------------------------------------------------------------- /internal/cmd/deployment/update.go: -------------------------------------------------------------------------------- 1 | package deployment 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/cli/browser" 9 | "github.com/replicate/replicate-go" 10 | "github.com/spf13/cobra" 11 | 12 | "github.com/replicate/cli/internal/client" 13 | "github.com/replicate/cli/internal/identifier" 14 | "github.com/replicate/cli/internal/util" 15 | ) 16 | 17 | // updateCmd represents the create command 18 | var updateCmd = &cobra.Command{ 19 | Use: "update <[owner/]name> [flags]", 20 | Short: "Update an existing deployment", 21 | Example: `replicate deployment update acme/text-to-image --max-instances=2`, 22 | Args: cobra.ExactArgs(1), 23 | RunE: func(cmd *cobra.Command, args []string) error { 24 | r8, err := client.NewClient() 25 | if err != nil { 26 | return err 27 | } 28 | 29 | name := args[0] 30 | if !strings.Contains(name, "/") { 31 | account, err := r8.GetCurrentAccount(cmd.Context()) 32 | if err != nil { 33 | return fmt.Errorf("failed to get current account: %w", err) 34 | } 35 | name = fmt.Sprintf("%s/%s", account.Username, name) 36 | } 37 | deploymentID, err := identifier.ParseIdentifier(name) 38 | if err != nil { 39 | return fmt.Errorf("invalid deployment specified: %s", name) 40 | } 41 | 42 | opts := &replicate.UpdateDeploymentOptions{} 43 | 44 | flags := cmd.Flags() 45 | 46 | if flags.Changed("version") { 47 | value, _ := flags.GetString("version") 48 | var version string 49 | if strings.Contains(value, ":") { 50 | modelID, err := identifier.ParseIdentifier(value) 51 | if err != nil { 52 | return fmt.Errorf("invalid model version specified: %s", value) 53 | } 54 | version = modelID.Version 55 | } else { 56 | version = value 57 | } 58 | opts.Version = &version 59 | } 60 | 61 | if flags.Changed("hardware") { 62 | value, _ := flags.GetString("hardware") 63 | opts.Hardware = &value 64 | } 65 | 66 | if flags.Changed("min-instances") { 67 | value, _ := flags.GetInt("min-instances") 68 | opts.MinInstances = &value 69 | } 70 | 71 | if flags.Changed("max-instances") { 72 | value, _ := flags.GetInt("max-instances") 73 | opts.MaxInstances = &value 74 | } 75 | 76 | deployment, err := r8.UpdateDeployment(cmd.Context(), deploymentID.Owner, deploymentID.Name, *opts) 77 | if err != nil { 78 | return fmt.Errorf("failed to update deployment: %w", err) 79 | } 80 | 81 | if flags.Changed("json") || !util.IsTTY() { 82 | bytes, err := json.MarshalIndent(deployment, "", " ") 83 | if err != nil { 84 | return fmt.Errorf("failed to serialize model: %w", err) 85 | } 86 | fmt.Println(string(bytes)) 87 | return nil 88 | } 89 | 90 | url := fmt.Sprintf("https://replicate.com/deployments/%s/%s", deployment.Owner, deployment.Name) 91 | if flags.Changed("web") { 92 | if util.IsTTY() { 93 | fmt.Println("Opening in browser...") 94 | } 95 | 96 | err := browser.OpenURL(url) 97 | if err != nil { 98 | return fmt.Errorf("failed to open browser: %w", err) 99 | } 100 | 101 | return nil 102 | } 103 | 104 | fmt.Printf("Deployment updated: %s\n", url) 105 | 106 | return nil 107 | }, 108 | } 109 | 110 | func init() { 111 | addUpdateFlags(updateCmd) 112 | } 113 | 114 | func addUpdateFlags(cmd *cobra.Command) { 115 | cmd.Flags().String("version", "", "Version of the model to deploy") 116 | cmd.Flags().String("hardware", "", "SKU of the hardware to run the model") 117 | cmd.Flags().Int("min-instances", 0, "Minimum number of instances to run the model") 118 | cmd.Flags().Int("max-instances", 0, "Maximum number of instances to run the model") 119 | 120 | cmd.Flags().Bool("json", false, "Emit JSON") 121 | cmd.Flags().Bool("web", false, "View on web") 122 | cmd.MarkFlagsMutuallyExclusive("json", "web") 123 | } 124 | -------------------------------------------------------------------------------- /internal/cmd/hardware/list.go: -------------------------------------------------------------------------------- 1 | package hardware 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/cli/browser" 8 | "github.com/spf13/cobra" 9 | 10 | "github.com/replicate/cli/internal/client" 11 | "github.com/replicate/cli/internal/util" 12 | ) 13 | 14 | // listCmd represents the list hardware command 15 | var listCmd = &cobra.Command{ 16 | Use: "list", 17 | Short: "List hardware", 18 | RunE: func(cmd *cobra.Command, _ []string) error { 19 | ctx := cmd.Context() 20 | 21 | if cmd.Flags().Changed("web") { 22 | if util.IsTTY() { 23 | fmt.Println("Opening in browser...") 24 | } 25 | 26 | url := "https://replicate.com/pricing#hardware" 27 | err := browser.OpenURL(url) 28 | if err != nil { 29 | return fmt.Errorf("failed to open browser: %w", err) 30 | } 31 | 32 | return nil 33 | } 34 | 35 | r8, err := client.NewClient() 36 | if err != nil { 37 | return err 38 | } 39 | 40 | hardware, err := r8.ListHardware(ctx) 41 | if err != nil { 42 | return fmt.Errorf("failed to list hardware: %w", err) 43 | } 44 | 45 | if cmd.Flags().Changed("json") || !util.IsTTY() { 46 | bytes, err := json.MarshalIndent(hardware, "", " ") 47 | if err != nil { 48 | return fmt.Errorf("failed to marshal hardware: %w", err) 49 | } 50 | fmt.Println(string(bytes)) 51 | return nil 52 | } 53 | 54 | for _, hw := range *hardware { 55 | fmt.Printf("- %s: %s\n", hw.SKU, hw.Name) 56 | } 57 | 58 | return nil 59 | }, 60 | } 61 | 62 | func init() { 63 | addListFlags(listCmd) 64 | } 65 | 66 | func addListFlags(cmd *cobra.Command) { 67 | cmd.Flags().Bool("json", false, "Emit JSON") 68 | cmd.Flags().Bool("web", false, "View on web") 69 | cmd.MarkFlagsMutuallyExclusive("json", "web") 70 | } 71 | -------------------------------------------------------------------------------- /internal/cmd/hardware/root.go: -------------------------------------------------------------------------------- 1 | package hardware 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | var RootCmd = &cobra.Command{ 8 | Use: "hardware [subcommand]", 9 | Short: "Interact with hardware", 10 | Aliases: []string{"hw"}, 11 | } 12 | 13 | func init() { 14 | RootCmd.AddGroup(&cobra.Group{ 15 | ID: "subcommand", 16 | Title: "Subcommands:", 17 | }) 18 | for _, cmd := range []*cobra.Command{ 19 | listCmd, 20 | } { 21 | RootCmd.AddCommand(cmd) 22 | cmd.GroupID = "subcommand" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /internal/cmd/model/create.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/cli/browser" 8 | "github.com/replicate/replicate-go" 9 | "github.com/spf13/cobra" 10 | 11 | "github.com/replicate/cli/internal/client" 12 | "github.com/replicate/cli/internal/identifier" 13 | "github.com/replicate/cli/internal/util" 14 | ) 15 | 16 | // createCmd represents the create command 17 | var createCmd = &cobra.Command{ 18 | Use: "create / [flags]", 19 | Short: "Create a new model", 20 | Args: cobra.ExactArgs(1), 21 | RunE: func(cmd *cobra.Command, args []string) error { 22 | id, err := identifier.ParseIdentifier(args[0]) 23 | if err != nil || id.Version != "" { 24 | return fmt.Errorf("expected / but got %s", args[0]) 25 | } 26 | 27 | opts := &replicate.CreateModelOptions{} 28 | flags := cmd.Flags() 29 | 30 | if flags.Changed("public") { 31 | opts.Visibility = "public" 32 | } else if flags.Changed("private") { 33 | opts.Visibility = "private" 34 | } 35 | 36 | opts.Hardware, _ = flags.GetString("hardware") 37 | 38 | flagMap := map[string]**string{ 39 | "description": &opts.Description, 40 | "github-url": &opts.GithubURL, 41 | "paper-url": &opts.PaperURL, 42 | "license-url": &opts.LicenseURL, 43 | "cover-image-url": &opts.CoverImageURL, 44 | } 45 | for flagName, optPtr := range flagMap { 46 | if flags.Changed(flagName) { 47 | value, _ := flags.GetString(flagName) 48 | *optPtr = &value 49 | } 50 | } 51 | 52 | r8, err := client.NewClient() 53 | if err != nil { 54 | return err 55 | } 56 | 57 | model, err := r8.CreateModel(cmd.Context(), id.Owner, id.Name, *opts) 58 | if err != nil { 59 | return fmt.Errorf("failed to create model: %w", err) 60 | } 61 | 62 | if flags.Changed("json") || !util.IsTTY() { 63 | bytes, err := json.MarshalIndent(model, "", " ") 64 | if err != nil { 65 | return fmt.Errorf("failed to serialize model: %w", err) 66 | } 67 | fmt.Println(string(bytes)) 68 | return nil 69 | } 70 | 71 | url := fmt.Sprintf("https://replicate.com/%s/%s", id.Owner, id.Name) 72 | if flags.Changed("web") { 73 | if util.IsTTY() { 74 | fmt.Println("Opening in browser...") 75 | } 76 | 77 | err := browser.OpenURL(url) 78 | if err != nil { 79 | return fmt.Errorf("failed to open browser: %w", err) 80 | } 81 | 82 | return nil 83 | } 84 | 85 | fmt.Printf("Model created: %s\n", url) 86 | 87 | return nil 88 | }, 89 | } 90 | 91 | func init() { 92 | addCreateFlags(createCmd) 93 | } 94 | 95 | func addCreateFlags(cmd *cobra.Command) { 96 | cmd.Flags().Bool("public", false, "Make the new model public") 97 | cmd.Flags().Bool("private", false, "Make the new model private") 98 | cmd.MarkFlagsOneRequired("public", "private") 99 | cmd.MarkFlagsMutuallyExclusive("public", "private") 100 | 101 | cmd.Flags().String("hardware", "", "SKU of the hardware to run the model") 102 | _ = cmd.MarkFlagRequired("hardware") 103 | 104 | cmd.Flags().String("description", "", "Description of the model") 105 | cmd.Flags().String("github-url", "", "URL of the GitHub repository") 106 | cmd.Flags().String("paper-url", "", "URL of the paper") 107 | cmd.Flags().String("license-url", "", "URL of the license") 108 | cmd.Flags().String("cover-image-url", "", "URL of the cover image") 109 | 110 | cmd.Flags().Bool("json", false, "Emit JSON") 111 | cmd.Flags().Bool("web", false, "View on web") 112 | cmd.MarkFlagsMutuallyExclusive("json", "web") 113 | } 114 | -------------------------------------------------------------------------------- /internal/cmd/model/list.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os/exec" 7 | 8 | "github.com/spf13/cobra" 9 | 10 | "github.com/replicate/cli/internal/client" 11 | "github.com/replicate/cli/internal/util" 12 | 13 | "github.com/charmbracelet/bubbles/table" 14 | tea "github.com/charmbracelet/bubbletea" 15 | "github.com/charmbracelet/lipgloss" 16 | ) 17 | 18 | var baseStyle = lipgloss.NewStyle(). 19 | BorderStyle(lipgloss.NormalBorder()). 20 | BorderForeground(lipgloss.Color("240")) 21 | 22 | type model struct { 23 | table table.Model 24 | } 25 | 26 | func (m model) Init() tea.Cmd { return nil } 27 | 28 | func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 29 | var cmd tea.Cmd 30 | switch msg := msg.(type) { //nolint:gocritic 31 | case tea.KeyMsg: 32 | switch msg.String() { 33 | case "esc": 34 | if m.table.Focused() { 35 | m.table.Blur() 36 | } else { 37 | m.table.Focus() 38 | } 39 | case "q", "ctrl+c": 40 | return m, tea.Quit 41 | case "enter": 42 | selected := m.table.SelectedRow() 43 | if len(selected) == 0 { 44 | return m, nil 45 | } 46 | url := fmt.Sprintf("https://replicate.com/%s", selected[0]) 47 | return m, tea.ExecProcess(exec.Command("open", url), nil) 48 | } 49 | } 50 | m.table, cmd = m.table.Update(msg) 51 | return m, cmd 52 | } 53 | 54 | func (m model) View() string { 55 | return baseStyle.Render(m.table.View()) + "\n" 56 | } 57 | 58 | var listCmd = &cobra.Command{ 59 | Use: "list", 60 | Short: "List models", 61 | RunE: func(cmd *cobra.Command, _ []string) error { 62 | ctx := cmd.Context() 63 | 64 | r8, err := client.NewClient() 65 | if err != nil { 66 | return err 67 | } 68 | 69 | models, err := r8.ListModels(ctx) 70 | if err != nil { 71 | return fmt.Errorf("failed to get predictions: %w", err) 72 | } 73 | 74 | if cmd.Flags().Changed("json") || !util.IsTTY() { 75 | bytes, err := json.MarshalIndent(models, "", " ") 76 | if err != nil { 77 | return fmt.Errorf("failed to marshal predictions: %w", err) 78 | } 79 | fmt.Println(string(bytes)) 80 | return nil 81 | } 82 | 83 | columns := []table.Column{ 84 | {Title: "Name", Width: 20}, 85 | {Title: "Description", Width: 60}, 86 | } 87 | 88 | rows := []table.Row{} 89 | 90 | for _, model := range models.Results { 91 | rows = append(rows, table.Row{ 92 | model.Owner + "/" + model.Name, 93 | model.Description, 94 | }) 95 | } 96 | 97 | t := table.New( 98 | table.WithColumns(columns), 99 | table.WithRows(rows), 100 | table.WithFocused(true), 101 | table.WithHeight(30), 102 | ) 103 | 104 | s := table.DefaultStyles() 105 | s.Header = s.Header. 106 | BorderStyle(lipgloss.NormalBorder()). 107 | BorderForeground(lipgloss.Color("240")). 108 | BorderBottom(true). 109 | Bold(false) 110 | s.Selected = s.Selected. 111 | Foreground(lipgloss.Color("229")). 112 | Background(lipgloss.Color("57")). 113 | Bold(false) 114 | t.SetStyles(s) 115 | 116 | m := model{t} 117 | if _, err := tea.NewProgram(m).Run(); err != nil { 118 | return err 119 | } 120 | 121 | return nil 122 | }, 123 | } 124 | 125 | func init() { 126 | addListFlags(listCmd) 127 | } 128 | 129 | func addListFlags(cmd *cobra.Command) { 130 | cmd.Flags().Bool("json", false, "Emit JSON") 131 | } 132 | -------------------------------------------------------------------------------- /internal/cmd/model/root.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | var RootCmd = &cobra.Command{ 8 | Use: "model [subcommand]", 9 | Short: "Interact with models", 10 | Aliases: []string{"models", "m"}, 11 | } 12 | 13 | func init() { 14 | RootCmd.AddGroup(&cobra.Group{ 15 | ID: "subcommand", 16 | Title: "Subcommands:", 17 | }) 18 | for _, cmd := range []*cobra.Command{ 19 | listCmd, 20 | showCmd, 21 | schemaCmd, 22 | createCmd, 23 | } { 24 | RootCmd.AddCommand(cmd) 25 | cmd.GroupID = "subcommand" 26 | } 27 | 28 | RootCmd.AddGroup(&cobra.Group{ 29 | ID: "alias", 30 | Title: "Alias commands:", 31 | }) 32 | for _, cmd := range []*cobra.Command{ 33 | runCmd, 34 | } { 35 | RootCmd.AddCommand(cmd) 36 | cmd.GroupID = "alias" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /internal/cmd/model/run.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | 6 | "github.com/replicate/cli/internal/cmd/prediction" 7 | ) 8 | 9 | var runCmd = &cobra.Command{ 10 | Use: "run [input=value] ... [flags]", 11 | Short: `Alias for "prediction create"`, 12 | Args: cobra.MinimumNArgs(1), 13 | RunE: prediction.CreateCmd.RunE, 14 | } 15 | 16 | func init() { 17 | prediction.AddCreateFlags(runCmd) 18 | } 19 | -------------------------------------------------------------------------------- /internal/cmd/model/schema.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/replicate/cli/internal/client" 8 | "github.com/replicate/cli/internal/identifier" 9 | "github.com/replicate/cli/internal/util" 10 | 11 | "github.com/replicate/replicate-go" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | var schemaCmd = &cobra.Command{ 16 | Use: "schema ", 17 | Short: "Show the inputs and outputs of a model", 18 | Args: cobra.ExactArgs(1), 19 | Example: ` replicate model schema stability-ai/sdxl`, 20 | RunE: func(cmd *cobra.Command, args []string) error { 21 | id, err := identifier.ParseIdentifier(args[0]) 22 | if err != nil { 23 | return fmt.Errorf("invalid model specified: %s", args[0]) 24 | } 25 | 26 | ctx := cmd.Context() 27 | 28 | r8, err := client.NewClient() 29 | if err != nil { 30 | return err 31 | } 32 | 33 | var version *replicate.ModelVersion 34 | if id.Version == "" { 35 | model, err := r8.GetModel(ctx, id.Owner, id.Name) 36 | if err != nil { 37 | return fmt.Errorf("failed to get model: %w", err) 38 | } 39 | 40 | if model.LatestVersion == nil { 41 | return fmt.Errorf("no versions found for model %s", args[0]) 42 | } 43 | 44 | version = model.LatestVersion 45 | } else { 46 | version, err = r8.GetModelVersion(ctx, id.Owner, id.Name, id.Version) 47 | if err != nil { 48 | return fmt.Errorf("failed to get model version: %w", err) 49 | } 50 | } 51 | 52 | if cmd.Flags().Changed("json") || !util.IsTTY() { 53 | bytes, err := json.MarshalIndent(version.OpenAPISchema, "", " ") 54 | if err != nil { 55 | return fmt.Errorf("failed to serialize schema: %w", err) 56 | } 57 | fmt.Println(string(bytes)) 58 | 59 | return nil 60 | } 61 | 62 | return printModelVersionSchema(version) 63 | }, 64 | } 65 | 66 | func printModelVersionSchema(version *replicate.ModelVersion) error { 67 | inputSchema, outputSchema, err := util.GetSchemas(*version) 68 | if err != nil { 69 | return fmt.Errorf("failed to get schemas: %w", err) 70 | } 71 | 72 | if inputSchema != nil { 73 | fmt.Println("Inputs:") 74 | 75 | for _, propName := range util.SortedKeys(inputSchema.Properties) { 76 | prop, ok := inputSchema.Properties[propName] 77 | if !ok { 78 | continue 79 | } 80 | 81 | description := prop.Value.Description 82 | if prop.Value.Enum != nil { 83 | for _, enum := range prop.Value.Enum { 84 | description += fmt.Sprintf("\n- %s", enum) 85 | } 86 | } 87 | 88 | fmt.Printf("- %s: %s (type: %s)\n", propName, description, prop.Value.Type) 89 | } 90 | fmt.Println() 91 | } 92 | 93 | if outputSchema != nil { 94 | fmt.Println("Output:") 95 | fmt.Printf("- type: %s\n", outputSchema.Type) 96 | if outputSchema.Type.Is("array") { 97 | fmt.Printf("- items: %s %s\n", outputSchema.Items.Value.Type, outputSchema.Items.Value.Format) 98 | } 99 | fmt.Println() 100 | } 101 | 102 | return nil 103 | } 104 | 105 | func init() { 106 | schemaCmd.Flags().Bool("json", false, "Emit JSON") 107 | } 108 | -------------------------------------------------------------------------------- /internal/cmd/model/show.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/cli/browser" 8 | "github.com/replicate/replicate-go" 9 | "github.com/spf13/cobra" 10 | 11 | "github.com/replicate/cli/internal/client" 12 | "github.com/replicate/cli/internal/identifier" 13 | "github.com/replicate/cli/internal/util" 14 | ) 15 | 16 | var showCmd = &cobra.Command{ 17 | Use: "show [flags]", 18 | Short: "Show a model", 19 | Args: cobra.ExactArgs(1), 20 | Aliases: []string{"view"}, 21 | RunE: func(cmd *cobra.Command, args []string) error { 22 | id, err := identifier.ParseIdentifier(args[0]) 23 | if err != nil { 24 | return fmt.Errorf("invalid model specified: %s", args[0]) 25 | } 26 | 27 | if cmd.Flags().Changed("web") { 28 | if util.IsTTY() { 29 | fmt.Println("Opening in browser...") 30 | } 31 | 32 | var url string 33 | if id.Version != "" { 34 | url = fmt.Sprintf("https://replicate.com/%s/%s/versions/%s", id.Owner, id.Name, id.Version) 35 | } else { 36 | url = fmt.Sprintf("https://replicate.com/%s/%s", id.Owner, id.Name) 37 | } 38 | 39 | err := browser.OpenURL(url) 40 | if err != nil { 41 | return fmt.Errorf("failed to open browser: %w", err) 42 | } 43 | 44 | return nil 45 | } 46 | 47 | ctx := cmd.Context() 48 | 49 | var model *replicate.Model 50 | // var version *replicate.ModelVersion 51 | 52 | r8, err := client.NewClient() 53 | if err != nil { 54 | return err 55 | } 56 | 57 | model, err = r8.GetModel(ctx, id.Owner, id.Name) 58 | if err != nil { 59 | return fmt.Errorf("failed to get model: %w", err) 60 | } 61 | 62 | if cmd.Flags().Changed("json") || !util.IsTTY() { 63 | bytes, err := json.MarshalIndent(model, "", " ") 64 | if err != nil { 65 | return fmt.Errorf("failed to marshal model: %w", err) 66 | } 67 | fmt.Println(string(bytes)) 68 | return nil 69 | } 70 | 71 | if id.Version != "" { 72 | fmt.Println("Ignoring specified version", id.Version) 73 | } 74 | 75 | fmt.Println(model.Name) 76 | fmt.Println(model.Description) 77 | if model.LatestVersion != nil { 78 | fmt.Println() 79 | fmt.Println("Latest version:", model.LatestVersion.ID) 80 | } 81 | 82 | return nil 83 | }, 84 | } 85 | 86 | func init() { 87 | showCmd.Flags().Bool("json", false, "Emit JSON") 88 | showCmd.Flags().Bool("web", false, "Open in web browser") 89 | 90 | showCmd.MarkFlagsMutuallyExclusive("json", "web") 91 | } 92 | -------------------------------------------------------------------------------- /internal/cmd/prediction/create.go: -------------------------------------------------------------------------------- 1 | package prediction 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | "time" 10 | 11 | "github.com/briandowns/spinner" 12 | "github.com/cli/browser" 13 | "github.com/getkin/kin-openapi/openapi3" 14 | "github.com/replicate/replicate-go" 15 | "github.com/schollz/progressbar/v3" 16 | "github.com/spf13/cobra" 17 | 18 | "github.com/replicate/cli/internal/client" 19 | "github.com/replicate/cli/internal/identifier" 20 | "github.com/replicate/cli/internal/util" 21 | ) 22 | 23 | var CreateCmd = &cobra.Command{ 24 | Use: "create [input=value] ... [flags]", 25 | Short: "Create a prediction", 26 | Args: cobra.MinimumNArgs(1), 27 | Aliases: []string{"new", "run"}, 28 | RunE: func(cmd *cobra.Command, args []string) error { 29 | // TODO support running interactively 30 | 31 | id, err := identifier.ParseIdentifier(args[0]) 32 | if err != nil { 33 | return fmt.Errorf("invalid model specified: %s", args[0]) 34 | } 35 | 36 | s := spinner.New(spinner.CharSets[21], 100*time.Millisecond) 37 | s.FinalMSG = "" 38 | 39 | ctx := cmd.Context() 40 | 41 | r8, err := client.NewClient() 42 | if err != nil { 43 | return err 44 | } 45 | 46 | var version *replicate.ModelVersion 47 | if id.Version == "" { 48 | if model, err := r8.GetModel(ctx, id.Owner, id.Name); err == nil { 49 | version = model.LatestVersion 50 | } 51 | } else { 52 | if v, err := r8.GetModelVersion(ctx, id.Owner, id.Name, id.Version); err == nil { 53 | version = v 54 | } 55 | } 56 | 57 | stdin, err := util.GetPipedArgs() 58 | if err != nil { 59 | return fmt.Errorf("failed to get stdin info: %w", err) 60 | } 61 | 62 | separator := cmd.Flag("separator").Value.String() 63 | inputs, err := util.ParseInputs(ctx, r8, args[1:], stdin, separator) 64 | if err != nil { 65 | return fmt.Errorf("failed to parse inputs: %w", err) 66 | } 67 | 68 | var inputSchema *openapi3.Schema 69 | var outputSchema *openapi3.Schema 70 | if version != nil { 71 | inputSchema, outputSchema, err = util.GetSchemas(*version) 72 | if err != nil { 73 | return fmt.Errorf("failed to get input schema for version: %w", err) 74 | } 75 | } 76 | 77 | coercedInputs, err := util.CoerceTypes(inputs, inputSchema) 78 | if err != nil { 79 | return fmt.Errorf("failed to coerce inputs: %w", err) 80 | } 81 | 82 | shouldWait := (cmd.Flags().Changed("wait") || !cmd.Flags().Changed("no-wait")) 83 | 84 | canStream := (outputSchema != nil && 85 | outputSchema.Type.Is("array") && 86 | outputSchema.Items.Value.Type.Is("string") && 87 | outputSchema.Extensions["x-cog-array-type"] == "iterator" && 88 | outputSchema.Extensions["x-cog-array-display"] == "concatenate") 89 | shouldStream := canStream && !cmd.Flags().Changed("wait") && 90 | (cmd.Flags().Changed("stream") || !cmd.Flags().Changed("no-stream")) 91 | 92 | s.Start() 93 | var prediction *replicate.Prediction 94 | if id.Version == "" { 95 | prediction, err = r8.CreatePredictionWithModel(ctx, id.Owner, id.Name, coercedInputs, nil, shouldStream) 96 | // TODO: check status code 97 | if err != nil { 98 | if version != nil { 99 | prediction, err = r8.CreatePrediction(ctx, version.ID, coercedInputs, nil, shouldStream) 100 | } 101 | } 102 | } else { 103 | prediction, err = r8.CreatePrediction(ctx, id.Version, coercedInputs, nil, shouldStream) 104 | } 105 | if err != nil { 106 | return fmt.Errorf("failed to create prediction: %w", err) 107 | } 108 | s.Stop() 109 | 110 | hasStream := prediction.URLs["stream"] != "" 111 | 112 | if !util.IsTTY() || cmd.Flags().Changed("json") { 113 | if hasStream { 114 | events, _ := r8.StreamPrediction(ctx, prediction) 115 | 116 | if cmd.Flags().Changed("json") { 117 | fmt.Print("[") 118 | defer fmt.Print("]") 119 | 120 | prefix := "" 121 | for event := range events { 122 | if event.Type != replicate.SSETypeOutput { 123 | continue 124 | } 125 | 126 | if event.Data == "" { 127 | continue 128 | } 129 | 130 | b, err := json.Marshal(event.Data) 131 | if err != nil { 132 | return fmt.Errorf("failed to marshal event: %w", err) 133 | } 134 | 135 | fmt.Printf("%s%s", prefix, string(b)) 136 | prefix = ", " 137 | } 138 | } else { 139 | for event := range events { 140 | if event.Type != replicate.SSETypeOutput { 141 | continue 142 | } 143 | 144 | fmt.Print(event.Data) 145 | } 146 | fmt.Println("") 147 | } 148 | 149 | return nil 150 | } 151 | 152 | if shouldWait { 153 | err = r8.Wait(ctx, prediction) 154 | if err != nil { 155 | return fmt.Errorf("failed to wait for prediction: %w", err) 156 | } 157 | } 158 | 159 | b, err := json.Marshal(prediction) 160 | if err != nil { 161 | return fmt.Errorf("failed to marshal prediction: %w", err) 162 | } 163 | fmt.Println(string(b)) 164 | 165 | return nil 166 | } 167 | 168 | url := fmt.Sprintf("https://replicate.com/p/%s", prediction.ID) 169 | if !hasStream { 170 | fmt.Printf("Prediction created: %s\n", url) 171 | } 172 | 173 | if cmd.Flags().Changed("web") { 174 | if util.IsTTY() { 175 | fmt.Println("Opening in browser...") 176 | } 177 | 178 | err = browser.OpenURL(url) 179 | if err != nil { 180 | return fmt.Errorf("failed to open browser: %w", err) 181 | } 182 | 183 | return nil 184 | } 185 | 186 | if hasStream { 187 | sseChan, errChan := r8.StreamPrediction(ctx, prediction) 188 | 189 | tokens := []string{} 190 | for { 191 | select { 192 | case event, ok := <-sseChan: 193 | if !ok { 194 | return nil 195 | } 196 | 197 | switch event.Type { 198 | case replicate.SSETypeOutput: 199 | token := event.Data 200 | tokens = append(tokens, token) 201 | fmt.Print(token) 202 | case replicate.SSETypeLogs: 203 | // TODO: print logs to stderr 204 | case replicate.SSETypeDone: 205 | return nil 206 | default: 207 | // ignore 208 | } 209 | case err, ok := <-errChan: 210 | if !ok { 211 | return nil 212 | } 213 | 214 | return fmt.Errorf("streaming error: %w", err) 215 | } 216 | 217 | if cmd.Flags().Changed("save") { 218 | var dirname string 219 | if cmd.Flags().Changed("output-directory") { 220 | dirname = cmd.Flag("output-directory").Value.String() 221 | } else { 222 | dirname = fmt.Sprintf("./%s", prediction.ID) 223 | } 224 | 225 | dir, err := filepath.Abs(dirname) 226 | if err != nil { 227 | return fmt.Errorf("failed to create output directory: %w", err) 228 | } 229 | 230 | err = os.MkdirAll(dir, 0o755) 231 | if err != nil { 232 | return fmt.Errorf("failed to create directory: %w", err) 233 | } 234 | 235 | err = os.WriteFile(filepath.Join(dir, "output.txt"), []byte(strings.Join(tokens, "")), 0o644) 236 | if err != nil { 237 | return fmt.Errorf("failed to write output: %w", err) 238 | } 239 | } 240 | } 241 | } else if shouldWait { 242 | bar := progressbar.Default(100) 243 | bar.Describe("processing") 244 | 245 | predChan, errChan := r8.WaitAsync(ctx, prediction) 246 | for pred := range predChan { 247 | progress := pred.Progress() 248 | if progress != nil { 249 | bar.ChangeMax(progress.Total) 250 | _ = bar.Set(progress.Current) 251 | } 252 | 253 | if pred.Status.Terminated() { 254 | _ = bar.Finish() 255 | break 256 | } 257 | } 258 | 259 | if err := <-errChan; err != nil { 260 | return fmt.Errorf("failed to wait for prediction: %w", err) 261 | } 262 | 263 | switch prediction.Status { 264 | case replicate.Succeeded: 265 | fmt.Println("✅ Succeeded") 266 | bytes, err := json.MarshalIndent(prediction.Output, "", " ") 267 | if err != nil { 268 | return fmt.Errorf("failed to marshal output: %w", err) 269 | } 270 | fmt.Println(string(bytes)) 271 | case replicate.Failed: 272 | fmt.Println("❌ Failed") 273 | fmt.Println(*prediction.Logs) 274 | bytes, err := json.MarshalIndent(prediction.Error, "", " ") 275 | if err != nil { 276 | return fmt.Errorf("error: %v", prediction.Error) 277 | } 278 | fmt.Println(string(bytes)) 279 | case replicate.Canceled: 280 | fmt.Println("🚫 Canceled") 281 | fmt.Println(prediction.Logs) 282 | } 283 | 284 | if cmd.Flags().Changed("save") && prediction.Status == replicate.Succeeded { 285 | var dirname string 286 | if cmd.Flags().Changed("output-directory") { 287 | dirname = cmd.Flag("output-directory").Value.String() 288 | } else { 289 | dirname = fmt.Sprintf("./%s", prediction.ID) 290 | } 291 | 292 | dir, err := filepath.Abs(dirname) 293 | if err != nil { 294 | return fmt.Errorf("failed to create output directory: %w", err) 295 | } 296 | 297 | err = util.DownloadPrediction(ctx, *prediction, dir) 298 | if err != nil { 299 | return fmt.Errorf("failed to save output: %w", err) 300 | } 301 | } 302 | } 303 | 304 | return nil 305 | 306 | }, 307 | } 308 | 309 | func init() { 310 | AddCreateFlags(CreateCmd) 311 | } 312 | 313 | func AddCreateFlags(cmd *cobra.Command) { 314 | cmd.Flags().Bool("json", false, "Emit JSON") 315 | cmd.Flags().Bool("web", false, "View on web") 316 | cmd.MarkFlagsMutuallyExclusive("json", "web") 317 | 318 | cmd.Flags().BoolP("wait", "w", true, "Wait for prediction to complete") 319 | cmd.Flags().Bool("no-wait", false, "Don't wait for prediction to complete") 320 | cmd.MarkFlagsMutuallyExclusive("wait", "no-wait") 321 | 322 | cmd.Flags().Bool("stream", false, "Stream prediction output") 323 | cmd.Flags().Bool("no-stream", false, "Don't stream prediction output") 324 | cmd.MarkFlagsMutuallyExclusive("stream", "no-stream") 325 | cmd.MarkFlagsMutuallyExclusive("stream", "wait") 326 | 327 | cmd.Flags().String("separator", "=", "Separator between input key and value") 328 | 329 | cmd.Flags().Bool("save", false, "Save prediction outputs to directory") 330 | cmd.Flags().String("output-directory", "", "Output directory, defaults to ./{prediction-id}") 331 | } 332 | -------------------------------------------------------------------------------- /internal/cmd/prediction/list.go: -------------------------------------------------------------------------------- 1 | package prediction 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os/exec" 7 | 8 | "github.com/spf13/cobra" 9 | 10 | "github.com/replicate/cli/internal/client" 11 | "github.com/replicate/cli/internal/util" 12 | 13 | "github.com/charmbracelet/bubbles/table" 14 | tea "github.com/charmbracelet/bubbletea" 15 | "github.com/charmbracelet/lipgloss" 16 | ) 17 | 18 | var baseStyle = lipgloss.NewStyle(). 19 | BorderStyle(lipgloss.NormalBorder()). 20 | BorderForeground(lipgloss.Color("240")) 21 | 22 | type model struct { 23 | table table.Model 24 | } 25 | 26 | func (m model) Init() tea.Cmd { return nil } 27 | 28 | func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 29 | var cmd tea.Cmd 30 | switch msg := msg.(type) { //nolint:gocritic 31 | case tea.KeyMsg: 32 | switch msg.String() { 33 | case "esc": 34 | if m.table.Focused() { 35 | m.table.Blur() 36 | } else { 37 | m.table.Focus() 38 | } 39 | case "q", "ctrl+c": 40 | return m, tea.Quit 41 | case "enter": 42 | selected := m.table.SelectedRow() 43 | if len(selected) == 0 { 44 | return m, nil 45 | } 46 | url := fmt.Sprintf("https://replicate.com/p/%s", selected[0]) 47 | return m, tea.ExecProcess(exec.Command("open", url), nil) 48 | } 49 | } 50 | m.table, cmd = m.table.Update(msg) 51 | return m, cmd 52 | } 53 | 54 | func (m model) View() string { 55 | return baseStyle.Render(m.table.View()) + "\n" 56 | } 57 | 58 | var listCmd = &cobra.Command{ 59 | Use: "list", 60 | Short: "List predictions", 61 | RunE: func(cmd *cobra.Command, _ []string) error { 62 | ctx := cmd.Context() 63 | 64 | r8, err := client.NewClient() 65 | if err != nil { 66 | return err 67 | } 68 | 69 | predictions, err := r8.ListPredictions(ctx) 70 | if err != nil { 71 | return fmt.Errorf("failed to get predictions: %w", err) 72 | } 73 | 74 | if cmd.Flags().Changed("json") || !util.IsTTY() { 75 | bytes, err := json.MarshalIndent(predictions, "", " ") 76 | if err != nil { 77 | return fmt.Errorf("failed to marshal predictions: %w", err) 78 | } 79 | fmt.Println(string(bytes)) 80 | return nil 81 | } 82 | 83 | columns := []table.Column{ 84 | {Title: "ID", Width: 20}, 85 | {Title: "Version", Width: 20}, 86 | {Title: "", Width: 3}, 87 | {Title: "Created", Width: 20}, 88 | } 89 | 90 | rows := []table.Row{} 91 | 92 | for _, prediction := range predictions.Results { 93 | rows = append(rows, table.Row{ 94 | prediction.ID, 95 | prediction.Version, 96 | util.StatusSymbol(prediction.Status), 97 | prediction.CreatedAt, 98 | }) 99 | } 100 | 101 | t := table.New( 102 | table.WithColumns(columns), 103 | table.WithRows(rows), 104 | table.WithFocused(true), 105 | table.WithHeight(30), 106 | ) 107 | 108 | s := table.DefaultStyles() 109 | s.Header = s.Header. 110 | BorderStyle(lipgloss.NormalBorder()). 111 | BorderForeground(lipgloss.Color("240")). 112 | BorderBottom(true). 113 | Bold(false) 114 | s.Selected = s.Selected. 115 | Foreground(lipgloss.Color("229")). 116 | Background(lipgloss.Color("57")). 117 | Bold(false) 118 | t.SetStyles(s) 119 | 120 | m := model{t} 121 | if _, err := tea.NewProgram(m).Run(); err != nil { 122 | return err 123 | } 124 | 125 | return nil 126 | }, 127 | } 128 | 129 | func init() { 130 | addListFlags(listCmd) 131 | } 132 | 133 | func addListFlags(cmd *cobra.Command) { 134 | cmd.Flags().Bool("json", false, "Emit JSON") 135 | } 136 | -------------------------------------------------------------------------------- /internal/cmd/prediction/root.go: -------------------------------------------------------------------------------- 1 | package prediction 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | var RootCmd = &cobra.Command{ 8 | Use: "prediction [subcommand]", 9 | Short: "Interact with predictions", 10 | Aliases: []string{"predictions", "p"}, 11 | } 12 | 13 | func init() { 14 | RootCmd.AddGroup(&cobra.Group{ 15 | ID: "subcommand", 16 | Title: "Subcommands:", 17 | }) 18 | for _, cmd := range []*cobra.Command{ 19 | CreateCmd, 20 | listCmd, 21 | showCmd, 22 | } { 23 | RootCmd.AddCommand(cmd) 24 | cmd.GroupID = "subcommand" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /internal/cmd/prediction/show.go: -------------------------------------------------------------------------------- 1 | package prediction 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/cli/browser" 8 | "github.com/spf13/cobra" 9 | 10 | "github.com/replicate/cli/internal/client" 11 | "github.com/replicate/cli/internal/util" 12 | ) 13 | 14 | var showCmd = &cobra.Command{ 15 | Use: "show ", 16 | Short: "Show a prediction", 17 | Args: cobra.ExactArgs(1), 18 | Aliases: []string{"view"}, 19 | RunE: func(cmd *cobra.Command, args []string) error { 20 | id := args[0] 21 | 22 | if cmd.Flags().Changed("web") { 23 | if util.IsTTY() { 24 | fmt.Println("Opening in browser...") 25 | } 26 | 27 | url := fmt.Sprintf("https://replicate.com/p/%s", id) 28 | err := browser.OpenURL(url) 29 | if err != nil { 30 | return fmt.Errorf("failed to open browser: %w", err) 31 | } 32 | 33 | return nil 34 | } 35 | 36 | ctx := cmd.Context() 37 | 38 | r8, err := client.NewClient() 39 | if err != nil { 40 | return err 41 | } 42 | 43 | prediction, err := r8.GetPrediction(ctx, id) 44 | if prediction == nil || err != nil { 45 | return fmt.Errorf("failed to get prediction: %w", err) 46 | } 47 | 48 | if cmd.Flags().Changed("json") || !util.IsTTY() { 49 | bytes, err := json.MarshalIndent(prediction, "", " ") 50 | if err != nil { 51 | return fmt.Errorf("failed to marshal prediction: %w", err) 52 | } 53 | fmt.Println(string(bytes)) 54 | return nil 55 | } 56 | 57 | // TODO: render prediction with TUI 58 | fmt.Println(prediction.ID) 59 | fmt.Println("Status: " + prediction.Status) 60 | 61 | if prediction.CompletedAt != nil { 62 | fmt.Println("Completed at: " + *prediction.CompletedAt) 63 | fmt.Println("Inputs:") 64 | for key, value := range prediction.Input { 65 | fmt.Printf(" %s: %s\n", key, value) 66 | } 67 | 68 | fmt.Println("Outputs:") 69 | bytes, err := json.MarshalIndent(prediction.Output, "", " ") 70 | if err != nil { 71 | return fmt.Errorf("failed to marshal prediction output: %w", err) 72 | } 73 | fmt.Println(string(bytes)) 74 | } 75 | 76 | return nil 77 | }, 78 | } 79 | 80 | func init() { 81 | showCmd.Flags().Bool("json", false, "Emit JSON") 82 | showCmd.Flags().Bool("web", false, "Open in web browser") 83 | 84 | showCmd.MarkFlagsMutuallyExclusive("json", "web") 85 | } 86 | -------------------------------------------------------------------------------- /internal/cmd/run.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | 6 | "github.com/replicate/cli/internal/cmd/prediction" 7 | ) 8 | 9 | var RunCmd = &cobra.Command{ 10 | Use: "run [input=value] ... [flags]", 11 | Short: `Alias for "prediction create"`, 12 | Args: cobra.MinimumNArgs(1), 13 | RunE: prediction.CreateCmd.RunE, 14 | } 15 | 16 | func init() { 17 | prediction.AddCreateFlags(RunCmd) 18 | } 19 | -------------------------------------------------------------------------------- /internal/cmd/scaffold.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "net/url" 8 | "os" 9 | "os/exec" 10 | "strings" 11 | 12 | "github.com/replicate/replicate-go" 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | var ScaffoldCmd = &cobra.Command{ 17 | Use: "scaffold [] [--template=