cloudinary-cli 1.14.1__tar.gz → 1.16.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. {cloudinary_cli-1.14.1/cloudinary_cli.egg-info → cloudinary_cli-1.16.0}/PKG-INFO +174 -19
  2. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/README.md +170 -16
  3. cloudinary_cli-1.16.0/cloudinary_cli/auth/__init__.py +143 -0
  4. cloudinary_cli-1.16.0/cloudinary_cli/auth/callback_page.py +94 -0
  5. cloudinary_cli-1.16.0/cloudinary_cli/auth/flow.py +102 -0
  6. cloudinary_cli-1.16.0/cloudinary_cli/auth/loopback_server.py +76 -0
  7. cloudinary_cli-1.16.0/cloudinary_cli/auth/oauth_config.py +138 -0
  8. cloudinary_cli-1.16.0/cloudinary_cli/auth/refresh.py +123 -0
  9. cloudinary_cli-1.16.0/cloudinary_cli/auth/session.py +118 -0
  10. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/cli_group.py +4 -14
  11. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/core/__init__.py +7 -2
  12. cloudinary_cli-1.16.0/cloudinary_cli/core/agent.py +203 -0
  13. cloudinary_cli-1.16.0/cloudinary_cli/core/auth.py +94 -0
  14. cloudinary_cli-1.16.0/cloudinary_cli/core/config.py +199 -0
  15. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/core/search.py +2 -1
  16. cloudinary_cli-1.16.0/cloudinary_cli/defaults.py +127 -0
  17. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/modules/clone.py +2 -2
  18. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/modules/migrate.py +2 -1
  19. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/modules/sync.py +9 -6
  20. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/utils/api_utils.py +55 -19
  21. cloudinary_cli-1.16.0/cloudinary_cli/utils/config_listing.py +134 -0
  22. cloudinary_cli-1.16.0/cloudinary_cli/utils/config_resolver.py +145 -0
  23. cloudinary_cli-1.16.0/cloudinary_cli/utils/config_utils.py +452 -0
  24. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/utils/file_utils.py +44 -0
  25. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/utils/json_utils.py +16 -6
  26. cloudinary_cli-1.16.0/cloudinary_cli/utils/url_utils.py +29 -0
  27. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/utils/utils.py +55 -3
  28. cloudinary_cli-1.16.0/cloudinary_cli/version.py +1 -0
  29. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0/cloudinary_cli.egg-info}/PKG-INFO +174 -19
  30. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli.egg-info/SOURCES.txt +28 -0
  31. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli.egg-info/requires.txt +2 -1
  32. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/requirements.txt +2 -1
  33. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/setup.py +1 -1
  34. cloudinary_cli-1.16.0/test/conftest.py +16 -0
  35. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/test/helper_test.py +6 -0
  36. cloudinary_cli-1.16.0/test/oauth_helpers.py +30 -0
  37. cloudinary_cli-1.16.0/test/test_auth_flow.py +136 -0
  38. cloudinary_cli-1.16.0/test/test_auth_loopback.py +107 -0
  39. cloudinary_cli-1.16.0/test/test_auth_region.py +65 -0
  40. cloudinary_cli-1.16.0/test/test_auth_session.py +401 -0
  41. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/test/test_cli.py +3 -0
  42. cloudinary_cli-1.16.0/test/test_cli_agent.py +402 -0
  43. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/test/test_cli_api.py +10 -1
  44. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/test/test_cli_config.py +21 -7
  45. cloudinary_cli-1.16.0/test/test_cli_config_oauth.py +924 -0
  46. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/test/test_cli_search_api.py +5 -1
  47. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/test/test_cli_url.py +4 -0
  48. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/test/test_cli_utils.py +2 -0
  49. cloudinary_cli-1.16.0/test/test_config_cache.py +62 -0
  50. cloudinary_cli-1.16.0/test/test_config_concurrency.py +46 -0
  51. cloudinary_cli-1.16.0/test/test_config_permissions.py +45 -0
  52. cloudinary_cli-1.16.0/test/test_default_config.py +213 -0
  53. cloudinary_cli-1.16.0/test/test_file_utils.py +184 -0
  54. cloudinary_cli-1.16.0/test/test_json_utils.py +145 -0
  55. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/test/test_modules/test_cli_clone.py +33 -0
  56. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/test/test_modules/test_cli_sync.py +3 -0
  57. cloudinary_cli-1.16.0/test/test_oauth_multiprocess.py +211 -0
  58. cloudinary_cli-1.16.0/test/test_oauth_retry.py +295 -0
  59. cloudinary_cli-1.16.0/test/test_oauth_token_seam.py +149 -0
  60. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/test/test_utils.py +51 -1
  61. cloudinary_cli-1.14.1/cloudinary_cli/core/config.py +0 -50
  62. cloudinary_cli-1.14.1/cloudinary_cli/defaults.py +0 -32
  63. cloudinary_cli-1.14.1/cloudinary_cli/utils/config_utils.py +0 -124
  64. cloudinary_cli-1.14.1/cloudinary_cli/version.py +0 -1
  65. cloudinary_cli-1.14.1/test/test_file_utils.py +0 -35
  66. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/LICENSE +0 -0
  67. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/MANIFEST.in +0 -0
  68. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/__init__.py +0 -0
  69. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/cli.py +0 -0
  70. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/core/admin.py +0 -0
  71. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/core/overrides.py +0 -0
  72. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/core/provisioning.py +0 -0
  73. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/core/uploader.py +0 -0
  74. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/core/utils.py +0 -0
  75. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/modules/__init__.py +0 -0
  76. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/modules/make.py +0 -0
  77. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/modules/regen_derived.py +0 -0
  78. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/modules/upload_dir.py +0 -0
  79. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/samples/__init__.py +0 -0
  80. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/templates/html/media_library_widget +0 -0
  81. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/templates/html/product_gallery +0 -0
  82. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/templates/html/upload_widget +0 -0
  83. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/templates/html/video_player +0 -0
  84. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/templates/node/upload +0 -0
  85. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/templates/python/base +0 -0
  86. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/templates/python/explicit +0 -0
  87. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/templates/python/find_all_empty_folders +0 -0
  88. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/templates/python/upload +0 -0
  89. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/templates/ruby/upload +0 -0
  90. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/utils/__init__.py +0 -0
  91. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli/utils/search_utils.py +0 -0
  92. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli.egg-info/dependency_links.txt +0 -0
  93. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli.egg-info/entry_points.txt +0 -0
  94. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli.egg-info/not-zip-safe +0 -0
  95. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/cloudinary_cli.egg-info/top_level.txt +0 -0
  96. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/setup.cfg +0 -0
  97. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/test/__init__.py +0 -0
  98. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/test/test_cli_samples.py +0 -0
  99. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/test/test_modules/__init__.py +0 -0
  100. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/test/test_modules/test_cli_make.py +0 -0
  101. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/test/test_modules/test_cli_upload_dir.py +0 -0
  102. {cloudinary_cli-1.14.1 → cloudinary_cli-1.16.0}/test/test_search_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cloudinary-cli
3
- Version: 1.14.1
3
+ Version: 1.16.0
4
4
  Summary: A command line interface for Cloudinary with full API support
5
5
  Home-page: https://github.com/cloudinary/cloudinary-cli
6
6
  Author: Cloudinary, Brian Luk
@@ -10,14 +10,15 @@ Keywords: cloudinary cli pycloudinary image video digital asset management comma
10
10
  Classifier: Programming Language :: Python :: 3 :: Only
11
11
  Classifier: License :: OSI Approved :: MIT License
12
12
  Classifier: Operating System :: OS Independent
13
- Requires-Python: >=3.6.0
13
+ Requires-Python: >=3.8.0
14
14
  Description-Content-Type: text/markdown
15
15
  License-File: LICENSE
16
- Requires-Dist: cloudinary>=1.42.2
16
+ Requires-Dist: cloudinary>=1.45.0
17
17
  Requires-Dist: pygments
18
18
  Requires-Dist: jinja2
19
19
  Requires-Dist: click
20
20
  Requires-Dist: click-log
21
+ Requires-Dist: filelock
21
22
  Requires-Dist: requests
22
23
  Requires-Dist: docstring-parser
23
24
  Requires-Dist: urllib3>=2.2.2
@@ -37,28 +38,118 @@ It is fully documented at [https://cloudinary.com/documentation/cloudinary_cli](
37
38
  ## Requirements
38
39
  Your own Cloudinary account. If you don't already have one, sign up at [https://cloudinary.com/users/register/free](https://cloudinary.com/users/register/free).
39
40
 
40
- Python 3.6 or later. You can install Python from [https://www.python.org/](https://www.python.org/). Note that the Python Package Installer (pip) is installed with it.
41
+ Python 3.8 or later. You can install Python from [https://www.python.org/](https://www.python.org/). Note that the Python Package Installer (pip) is installed with it.
41
42
 
42
- ## Setup and Installation
43
+ ## Installation
43
44
 
44
- 1. To install this package, run: `pip3 install cloudinary-cli`
45
- 2. To make all your `cld` commands point to your Cloudinary account, set up your CLOUDINARY\_URL environment variable. For example:
46
- * On Mac or Linux:<br>`export CLOUDINARY_URL=cloudinary://123456789012345:abcdefghijklmnopqrstuvwxyzA@cloud_name`
47
- * On Windows (cmd.exe):<br>`set CLOUDINARY_URL=cloudinary://123456789012345:abcdefghijklmnopqrstuvwxyzA@cloud_name`
48
- * On Windows (PowerShell):<br>`$Env:CLOUDINARY_URL="cloudinary://123456789012345:abcdefghijklmnopqrstuvwxyzA@cloud_name"`
45
+ The CLI is published on PyPI as [`cloudinary-cli`](https://pypi.org/project/cloudinary-cli/). The package name (`cloudinary-cli`) is what you install; the command it provides is **`cld`** (it also installs a `cloudinary` alias). Pick the method that fits your setup. If you just want a working `cld` command and aren't sure, use **pipx** or **uv** — they install the CLI in its own isolated environment, so it won't conflict with other Python packages and you don't need to manage a virtual environment yourself.
46
+
47
+ ### Option 1 — pipx (recommended)
48
+
49
+ [pipx](https://pipx.pypa.io) installs Python CLI tools into isolated environments and puts the `cld` command on your `PATH` automatically.
50
+
51
+ ```sh
52
+ # Install pipx if you don't have it:
53
+ # macOS: brew install pipx && pipx ensurepath
54
+ # Debian/Ubuntu: sudo apt install pipx && pipx ensurepath
55
+ # Any platform: python3 -m pip install --user pipx && python3 -m pipx ensurepath
56
+
57
+ pipx install cloudinary-cli
58
+
59
+ # Upgrade later with:
60
+ pipx upgrade cloudinary-cli
61
+ ```
62
+
63
+ After `pipx ensurepath`, open a new terminal so the updated `PATH` takes effect.
64
+
65
+ ### Option 2 — uv
66
+
67
+ [uv](https://docs.astral.sh/uv/) is a fast Python package manager. Its `uv tool` command installs CLIs in isolation, like pipx:
68
+
69
+ ```sh
70
+ uv tool install cloudinary-cli
71
+
72
+ # Upgrade later with:
73
+ uv tool upgrade cloudinary-cli
74
+ ```
75
+
76
+ Or run it once without installing. The package's command is `cld`, so name it with `--from`:
77
+
78
+ ```sh
79
+ uvx --from cloudinary-cli cld --help # uvx is shorthand for `uv tool run`
80
+ ```
81
+
82
+ ### Option 3 — pip
83
+
84
+ A plain `pip` install also works. Prefer a virtual environment so the CLI and its dependencies don't collide with your system or other projects:
85
+
86
+ ```sh
87
+ python3 -m venv ~/.venvs/cloudinary-cli
88
+ source ~/.venvs/cloudinary-cli/bin/activate # Windows: .\.venvs\cloudinary-cli\Scripts\activate
89
+ pip install cloudinary-cli
90
+ ```
91
+
92
+ To install without a virtual environment, use a per-user install (avoids needing `sudo` and keeps it out of system Python):
93
+
94
+ ```sh
95
+ python3 -m pip install --user cloudinary-cli
96
+ ```
97
+
98
+ If `cld` is not found afterwards, the user scripts directory is not on your `PATH`. See [Troubleshooting](#troubleshooting-the-cld-command).
99
+
100
+ ### Option 4 — Docker (no Python needed)
101
+
102
+ If you'd rather not install Python at all, run the CLI from the official Docker image. See [Docker Usage](#docker-usage) below.
103
+
104
+ ### Verify the installation
105
+
106
+ ```sh
107
+ cld --version
108
+ ```
109
+
110
+ ### Troubleshooting the `cld` command
111
+
112
+ If your shell reports `cld: command not found` after installing:
113
+
114
+ - **pipx / uv:** run `pipx ensurepath` (or `uv tool update-shell`), then open a new terminal.
115
+ - **pip `--user` install:** the user scripts directory is not on your `PATH`. Find it with `python3 -m site --user-base` (the scripts live in its `bin` subdirectory on macOS/Linux, or `Scripts` on Windows) and add that to your `PATH`. For example, on macOS/Linux add this to `~/.zshrc` or `~/.bash_profile`:
116
+
117
+ ```sh
118
+ export PATH="$PATH:$(python3 -m site --user-base)/bin"
119
+ ```
120
+
121
+ - As a fallback, you can always invoke the CLI through Python: `python3 -m cloudinary_cli.cli <command>`.
122
+
123
+ ## Configuration
124
+
125
+ Once installed, point your `cld` commands at a Cloudinary account using **either** of the following.
126
+
127
+ **Option A — Log in with OAuth (recommended).** Run:
128
+
129
+ ```sh
130
+ cld login
131
+ ```
132
+
133
+ This opens your browser to authorize the CLI, then saves the login as a configuration (named after the cloud) and sets it as the default. The CLI refreshes the token automatically, and you can remove the login at any time with `cld logout`.
134
+
135
+ **Option B — Set your `CLOUDINARY_URL` environment variable.** For example:
136
+
137
+ * On Mac or Linux:<br>`export CLOUDINARY_URL=cloudinary://123456789012345:abcdefghijklmnopqrstuvwxyzA@cloud_name`
138
+ * On Windows (cmd.exe):<br>`set CLOUDINARY_URL=cloudinary://123456789012345:abcdefghijklmnopqrstuvwxyzA@cloud_name`
139
+ * On Windows (PowerShell):<br>`$Env:CLOUDINARY_URL="cloudinary://123456789012345:abcdefghijklmnopqrstuvwxyzA@cloud_name"`
49
140
 
50
141
  _**Note:** you can copy and paste your account environment variable from the Account Details section of the Dashboard page in the Cloudinary console._
51
142
 
52
- 3. Check your configuration by running `cld config`. A response of the following form is returned:
143
+ Then check your configuration by running `cld config`. A response of the following form is returned:
53
144
 
54
- ```
55
- cloud_name: <CLOUD_NAME>
56
- api_key: <API_KEY>
57
- api_secret: ***************<LAST_4_DIGITS>
58
- private_cdn: <True|False>
59
- ```
145
+ ```
146
+ cloud_name: <CLOUD_NAME>
147
+ api_key: <API_KEY>
148
+ api_secret: ***************<LAST_4_DIGITS>
149
+ private_cdn: <True|False>
150
+ ```
60
151
 
61
- If you get an error message when running `cld config`, you may need to add your Python installation to your $PATH. To do so, you can run `PATH="$PATH:/Library/Python/Versions/3.8/bin"` in your terminal, and add `export PATH="$PATH:/Library/Python/Versions/3.8/bin"` to your `/.bash_profile` or `~/.zshrc`.
152
+ If `cld` itself is not found, see [Troubleshooting the `cld` command](#troubleshooting-the-cld-command).
62
153
 
63
154
  ## Quickstart
64
155
 
@@ -72,9 +163,12 @@ Usage: cld [cli options] [command] [command options] [method] [method parameters
72
163
 
73
164
  ```
74
165
  cld --help # Lists available commands.
166
+ cld login # Logs in to a Cloudinary account via OAuth in your browser.
167
+ cld logout # Revokes and removes a saved OAuth login.
75
168
  cld search --help # Shows usage for the Search API.
76
169
  cld admin # Lists Admin API methods.
77
170
  cld uploader # Lists Upload API methods.
171
+ cld agent signup # For AI agents: creates a Cloudinary account on behalf of a human.
78
172
  ```
79
173
 
80
174
  ## Docker Usage
@@ -248,6 +342,28 @@ cld [cli options] migrate [command options] upload_mapping file
248
342
 
249
343
  For details, see the [Cloudinary CLI documentation](https://cloudinary.com/documentation/cloudinary_cli#migrate).
250
344
 
345
+ ### `agent signup`
346
+
347
+ **For AI agents only.** Creates a Free-plan Cloudinary account on behalf of a human. No existing configuration is required to run it. A verification email is sent to the address, and the returned credentials are **inert until the human completes that verification**. The new product environment is saved as a named configuration (named after the cloud) so it is ready to use once activated.
348
+
349
+ ```
350
+ cld agent signup [command options] <email> <agent_framework> <agent_llm_model> <agent_goal>
351
+ ```
352
+
353
+ Example:
354
+
355
+ ```
356
+ cld agent signup you@example.com claude-code claude-fable-5 "test the agent account flow"
357
+ ```
358
+
359
+ Options:
360
+
361
+ * `--name <name>` — name for the saved configuration (default: the returned cloud name).
362
+ * `--set-default` — set the saved configuration as the default.
363
+ * `--no-save` — show the credentials but do not save them as a configuration.
364
+ * `--sdk-framework <name>` — the Cloudinary SDK framework the agent intends to use.
365
+ * `--json` — output the full raw JSON response (the agent contract) instead of the human-readable summary.
366
+
251
367
  ## Additional configurations
252
368
 
253
369
  A configuration is a reference to a specified Cloudinary account or cloud name via its environment variable. You set the default configuration during setup and installation. Using different configurations allows you to access different Cloudinary cloud names, such as sub-accounts of your main Cloudinary account, or any additional Cloudinary accounts you may have.
@@ -268,7 +384,7 @@ Whereas using the saved configuration "accountx":
268
384
  cld -C accountx admin usage
269
385
  ```
270
386
 
271
- _**Caution:** Creating a saved configuration may put your API secret at risk as it is stored in a local plain text file._
387
+ _**Caution:** Creating a saved configuration may put your credentials at risk as they are stored in a local plain text file. This applies to both API-key configurations and OAuth logins._
272
388
 
273
389
  You can create, delete and list saved configurations using the `config` command.
274
390
 
@@ -277,3 +393,42 @@ cld config [options]
277
393
  ```
278
394
 
279
395
  For details, see the [Cloudinary CLI documentation](https://cloudinary.com/documentation/cloudinary_cli#config).
396
+
397
+ ### Logging in with OAuth
398
+
399
+ Instead of saving an API key and secret, you can log in to a Cloudinary account through your browser. The CLI saves the resulting session as a named configuration and refreshes its token automatically.
400
+
401
+ ```
402
+ cld login # Log in and save the configuration (named after the cloud).
403
+ cld login my-account # Save the login under a specific name.
404
+ cld logout # Choose a saved OAuth login to log out of.
405
+ cld logout my-account # Log out of a specific saved OAuth login.
406
+ ```
407
+
408
+ The first login becomes the default automatically. When other configurations already exist, the new login is saved but not made the default; `cld login` tells you so and prints the command to make it the default. Once saved, an OAuth login is selected with `-C <name>` just like any other saved configuration.
409
+
410
+ `cld logout` revokes the login's token at the server and removes the saved configuration. If the token cannot be revoked (for example, you are offline), the saved configuration is still removed.
411
+
412
+ ### Choosing a default configuration
413
+
414
+ The default configuration is used when no `-c`/`-C` option is given and no `CLOUDINARY_URL` environment variable is set. The first OAuth login becomes the default automatically; you can change it at any time.
415
+
416
+ ```
417
+ cld config -d <name> # Set an existing saved configuration as the default.
418
+ cld config --unset-default # Clear the stored default.
419
+ cld config -ls # List saved configurations, marking the default and the active one.
420
+ ```
421
+
422
+ When creating a configuration with `-n` or `--from_url`, add `--set-default` to make it the default in the same step. Resolution precedence is: `-c` (inline URL) > `-C` (saved name) > stored default > `CLOUDINARY_URL` environment variable.
423
+
424
+ ### Refreshing OAuth tokens
425
+
426
+ OAuth tokens are refreshed automatically as needed, but you can refresh them manually.
427
+
428
+ ```
429
+ cld config --refresh <name> # Refresh a saved OAuth configuration's token.
430
+ cld config --refresh-all # Refresh every saved OAuth configuration whose token is stale.
431
+ cld config --refresh <name> --force # Refresh even if the token is still fresh.
432
+ ```
433
+
434
+ If a token can no longer be refreshed (for example, the login was revoked), the CLI reports the configuration and the `cld login` command to use to log in again.
@@ -12,28 +12,118 @@ It is fully documented at [https://cloudinary.com/documentation/cloudinary_cli](
12
12
  ## Requirements
13
13
  Your own Cloudinary account. If you don't already have one, sign up at [https://cloudinary.com/users/register/free](https://cloudinary.com/users/register/free).
14
14
 
15
- Python 3.6 or later. You can install Python from [https://www.python.org/](https://www.python.org/). Note that the Python Package Installer (pip) is installed with it.
15
+ Python 3.8 or later. You can install Python from [https://www.python.org/](https://www.python.org/). Note that the Python Package Installer (pip) is installed with it.
16
16
 
17
- ## Setup and Installation
17
+ ## Installation
18
18
 
19
- 1. To install this package, run: `pip3 install cloudinary-cli`
20
- 2. To make all your `cld` commands point to your Cloudinary account, set up your CLOUDINARY\_URL environment variable. For example:
21
- * On Mac or Linux:<br>`export CLOUDINARY_URL=cloudinary://123456789012345:abcdefghijklmnopqrstuvwxyzA@cloud_name`
22
- * On Windows (cmd.exe):<br>`set CLOUDINARY_URL=cloudinary://123456789012345:abcdefghijklmnopqrstuvwxyzA@cloud_name`
23
- * On Windows (PowerShell):<br>`$Env:CLOUDINARY_URL="cloudinary://123456789012345:abcdefghijklmnopqrstuvwxyzA@cloud_name"`
19
+ The CLI is published on PyPI as [`cloudinary-cli`](https://pypi.org/project/cloudinary-cli/). The package name (`cloudinary-cli`) is what you install; the command it provides is **`cld`** (it also installs a `cloudinary` alias). Pick the method that fits your setup. If you just want a working `cld` command and aren't sure, use **pipx** or **uv** — they install the CLI in its own isolated environment, so it won't conflict with other Python packages and you don't need to manage a virtual environment yourself.
20
+
21
+ ### Option 1 — pipx (recommended)
22
+
23
+ [pipx](https://pipx.pypa.io) installs Python CLI tools into isolated environments and puts the `cld` command on your `PATH` automatically.
24
+
25
+ ```sh
26
+ # Install pipx if you don't have it:
27
+ # macOS: brew install pipx && pipx ensurepath
28
+ # Debian/Ubuntu: sudo apt install pipx && pipx ensurepath
29
+ # Any platform: python3 -m pip install --user pipx && python3 -m pipx ensurepath
30
+
31
+ pipx install cloudinary-cli
32
+
33
+ # Upgrade later with:
34
+ pipx upgrade cloudinary-cli
35
+ ```
36
+
37
+ After `pipx ensurepath`, open a new terminal so the updated `PATH` takes effect.
38
+
39
+ ### Option 2 — uv
40
+
41
+ [uv](https://docs.astral.sh/uv/) is a fast Python package manager. Its `uv tool` command installs CLIs in isolation, like pipx:
42
+
43
+ ```sh
44
+ uv tool install cloudinary-cli
45
+
46
+ # Upgrade later with:
47
+ uv tool upgrade cloudinary-cli
48
+ ```
49
+
50
+ Or run it once without installing. The package's command is `cld`, so name it with `--from`:
51
+
52
+ ```sh
53
+ uvx --from cloudinary-cli cld --help # uvx is shorthand for `uv tool run`
54
+ ```
55
+
56
+ ### Option 3 — pip
57
+
58
+ A plain `pip` install also works. Prefer a virtual environment so the CLI and its dependencies don't collide with your system or other projects:
59
+
60
+ ```sh
61
+ python3 -m venv ~/.venvs/cloudinary-cli
62
+ source ~/.venvs/cloudinary-cli/bin/activate # Windows: .\.venvs\cloudinary-cli\Scripts\activate
63
+ pip install cloudinary-cli
64
+ ```
65
+
66
+ To install without a virtual environment, use a per-user install (avoids needing `sudo` and keeps it out of system Python):
67
+
68
+ ```sh
69
+ python3 -m pip install --user cloudinary-cli
70
+ ```
71
+
72
+ If `cld` is not found afterwards, the user scripts directory is not on your `PATH`. See [Troubleshooting](#troubleshooting-the-cld-command).
73
+
74
+ ### Option 4 — Docker (no Python needed)
75
+
76
+ If you'd rather not install Python at all, run the CLI from the official Docker image. See [Docker Usage](#docker-usage) below.
77
+
78
+ ### Verify the installation
79
+
80
+ ```sh
81
+ cld --version
82
+ ```
83
+
84
+ ### Troubleshooting the `cld` command
85
+
86
+ If your shell reports `cld: command not found` after installing:
87
+
88
+ - **pipx / uv:** run `pipx ensurepath` (or `uv tool update-shell`), then open a new terminal.
89
+ - **pip `--user` install:** the user scripts directory is not on your `PATH`. Find it with `python3 -m site --user-base` (the scripts live in its `bin` subdirectory on macOS/Linux, or `Scripts` on Windows) and add that to your `PATH`. For example, on macOS/Linux add this to `~/.zshrc` or `~/.bash_profile`:
90
+
91
+ ```sh
92
+ export PATH="$PATH:$(python3 -m site --user-base)/bin"
93
+ ```
94
+
95
+ - As a fallback, you can always invoke the CLI through Python: `python3 -m cloudinary_cli.cli <command>`.
96
+
97
+ ## Configuration
98
+
99
+ Once installed, point your `cld` commands at a Cloudinary account using **either** of the following.
100
+
101
+ **Option A — Log in with OAuth (recommended).** Run:
102
+
103
+ ```sh
104
+ cld login
105
+ ```
106
+
107
+ This opens your browser to authorize the CLI, then saves the login as a configuration (named after the cloud) and sets it as the default. The CLI refreshes the token automatically, and you can remove the login at any time with `cld logout`.
108
+
109
+ **Option B — Set your `CLOUDINARY_URL` environment variable.** For example:
110
+
111
+ * On Mac or Linux:<br>`export CLOUDINARY_URL=cloudinary://123456789012345:abcdefghijklmnopqrstuvwxyzA@cloud_name`
112
+ * On Windows (cmd.exe):<br>`set CLOUDINARY_URL=cloudinary://123456789012345:abcdefghijklmnopqrstuvwxyzA@cloud_name`
113
+ * On Windows (PowerShell):<br>`$Env:CLOUDINARY_URL="cloudinary://123456789012345:abcdefghijklmnopqrstuvwxyzA@cloud_name"`
24
114
 
25
115
  _**Note:** you can copy and paste your account environment variable from the Account Details section of the Dashboard page in the Cloudinary console._
26
116
 
27
- 3. Check your configuration by running `cld config`. A response of the following form is returned:
117
+ Then check your configuration by running `cld config`. A response of the following form is returned:
28
118
 
29
- ```
30
- cloud_name: <CLOUD_NAME>
31
- api_key: <API_KEY>
32
- api_secret: ***************<LAST_4_DIGITS>
33
- private_cdn: <True|False>
34
- ```
119
+ ```
120
+ cloud_name: <CLOUD_NAME>
121
+ api_key: <API_KEY>
122
+ api_secret: ***************<LAST_4_DIGITS>
123
+ private_cdn: <True|False>
124
+ ```
35
125
 
36
- If you get an error message when running `cld config`, you may need to add your Python installation to your $PATH. To do so, you can run `PATH="$PATH:/Library/Python/Versions/3.8/bin"` in your terminal, and add `export PATH="$PATH:/Library/Python/Versions/3.8/bin"` to your `/.bash_profile` or `~/.zshrc`.
126
+ If `cld` itself is not found, see [Troubleshooting the `cld` command](#troubleshooting-the-cld-command).
37
127
 
38
128
  ## Quickstart
39
129
 
@@ -47,9 +137,12 @@ Usage: cld [cli options] [command] [command options] [method] [method parameters
47
137
 
48
138
  ```
49
139
  cld --help # Lists available commands.
140
+ cld login # Logs in to a Cloudinary account via OAuth in your browser.
141
+ cld logout # Revokes and removes a saved OAuth login.
50
142
  cld search --help # Shows usage for the Search API.
51
143
  cld admin # Lists Admin API methods.
52
144
  cld uploader # Lists Upload API methods.
145
+ cld agent signup # For AI agents: creates a Cloudinary account on behalf of a human.
53
146
  ```
54
147
 
55
148
  ## Docker Usage
@@ -223,6 +316,28 @@ cld [cli options] migrate [command options] upload_mapping file
223
316
 
224
317
  For details, see the [Cloudinary CLI documentation](https://cloudinary.com/documentation/cloudinary_cli#migrate).
225
318
 
319
+ ### `agent signup`
320
+
321
+ **For AI agents only.** Creates a Free-plan Cloudinary account on behalf of a human. No existing configuration is required to run it. A verification email is sent to the address, and the returned credentials are **inert until the human completes that verification**. The new product environment is saved as a named configuration (named after the cloud) so it is ready to use once activated.
322
+
323
+ ```
324
+ cld agent signup [command options] <email> <agent_framework> <agent_llm_model> <agent_goal>
325
+ ```
326
+
327
+ Example:
328
+
329
+ ```
330
+ cld agent signup you@example.com claude-code claude-fable-5 "test the agent account flow"
331
+ ```
332
+
333
+ Options:
334
+
335
+ * `--name <name>` — name for the saved configuration (default: the returned cloud name).
336
+ * `--set-default` — set the saved configuration as the default.
337
+ * `--no-save` — show the credentials but do not save them as a configuration.
338
+ * `--sdk-framework <name>` — the Cloudinary SDK framework the agent intends to use.
339
+ * `--json` — output the full raw JSON response (the agent contract) instead of the human-readable summary.
340
+
226
341
  ## Additional configurations
227
342
 
228
343
  A configuration is a reference to a specified Cloudinary account or cloud name via its environment variable. You set the default configuration during setup and installation. Using different configurations allows you to access different Cloudinary cloud names, such as sub-accounts of your main Cloudinary account, or any additional Cloudinary accounts you may have.
@@ -243,7 +358,7 @@ Whereas using the saved configuration "accountx":
243
358
  cld -C accountx admin usage
244
359
  ```
245
360
 
246
- _**Caution:** Creating a saved configuration may put your API secret at risk as it is stored in a local plain text file._
361
+ _**Caution:** Creating a saved configuration may put your credentials at risk as they are stored in a local plain text file. This applies to both API-key configurations and OAuth logins._
247
362
 
248
363
  You can create, delete and list saved configurations using the `config` command.
249
364
 
@@ -252,3 +367,42 @@ cld config [options]
252
367
  ```
253
368
 
254
369
  For details, see the [Cloudinary CLI documentation](https://cloudinary.com/documentation/cloudinary_cli#config).
370
+
371
+ ### Logging in with OAuth
372
+
373
+ Instead of saving an API key and secret, you can log in to a Cloudinary account through your browser. The CLI saves the resulting session as a named configuration and refreshes its token automatically.
374
+
375
+ ```
376
+ cld login # Log in and save the configuration (named after the cloud).
377
+ cld login my-account # Save the login under a specific name.
378
+ cld logout # Choose a saved OAuth login to log out of.
379
+ cld logout my-account # Log out of a specific saved OAuth login.
380
+ ```
381
+
382
+ The first login becomes the default automatically. When other configurations already exist, the new login is saved but not made the default; `cld login` tells you so and prints the command to make it the default. Once saved, an OAuth login is selected with `-C <name>` just like any other saved configuration.
383
+
384
+ `cld logout` revokes the login's token at the server and removes the saved configuration. If the token cannot be revoked (for example, you are offline), the saved configuration is still removed.
385
+
386
+ ### Choosing a default configuration
387
+
388
+ The default configuration is used when no `-c`/`-C` option is given and no `CLOUDINARY_URL` environment variable is set. The first OAuth login becomes the default automatically; you can change it at any time.
389
+
390
+ ```
391
+ cld config -d <name> # Set an existing saved configuration as the default.
392
+ cld config --unset-default # Clear the stored default.
393
+ cld config -ls # List saved configurations, marking the default and the active one.
394
+ ```
395
+
396
+ When creating a configuration with `-n` or `--from_url`, add `--set-default` to make it the default in the same step. Resolution precedence is: `-c` (inline URL) > `-C` (saved name) > stored default > `CLOUDINARY_URL` environment variable.
397
+
398
+ ### Refreshing OAuth tokens
399
+
400
+ OAuth tokens are refreshed automatically as needed, but you can refresh them manually.
401
+
402
+ ```
403
+ cld config --refresh <name> # Refresh a saved OAuth configuration's token.
404
+ cld config --refresh-all # Refresh every saved OAuth configuration whose token is stale.
405
+ cld config --refresh <name> --force # Refresh even if the token is still fresh.
406
+ ```
407
+
408
+ If a token can no longer be refreshed (for example, the login was revoked), the CLI reports the configuration and the `cld login` command to use to log in again.
@@ -0,0 +1,143 @@
1
+ """OAuth login façade: runs the PKCE loopback flow and persists each login as a named
2
+ `cloudinary://` entry in `config.json`. Token refresh lives in `auth.refresh`, re-exported here."""
3
+ import secrets
4
+ import webbrowser
5
+
6
+ import requests
7
+
8
+ from cloudinary_cli.auth import flow
9
+ from cloudinary_cli.auth.loopback_server import start_callback_server, wait_for_callback
10
+ from cloudinary_cli.auth.session import (
11
+ Session,
12
+ to_cloudinary_url,
13
+ from_cloudinary_url,
14
+ is_oauth_url,
15
+ )
16
+ from cloudinary_cli.auth.refresh import (
17
+ refresh_url_if_stale,
18
+ refresh_config,
19
+ refresh_configs,
20
+ relogin_command,
21
+ list_oauth_login_names,
22
+ )
23
+ from cloudinary_cli.defaults import logger, normalize_region, DEFAULT_REGION, CLOUDINARY_REGION
24
+ from cloudinary_cli.utils.config_utils import (
25
+ load_config,
26
+ remove_config_keys,
27
+ save_named_config,
28
+ is_reserved_config_name,
29
+ )
30
+ from cloudinary_cli.utils.utils import log_exception, is_interactive
31
+
32
+ __all__ = [
33
+ "login",
34
+ "logout",
35
+ "refresh_url_if_stale",
36
+ "refresh_config",
37
+ "refresh_configs",
38
+ "relogin_command",
39
+ "list_oauth_login_names",
40
+ ]
41
+
42
+
43
+ def login(region=None, name=None, set_default=False):
44
+ """
45
+ Run the interactive browser login and persist the resulting session as a named config entry.
46
+
47
+ Returns (config_name, default_status), where default_status is:
48
+ "made" - this login just became the default (explicit --set-default, or auto-defaulted as
49
+ the sole login),
50
+ "already" - the re-logged-into config was already the stored default,
51
+ "no" - it is not the default.
52
+ """
53
+ if name and is_reserved_config_name(name):
54
+ raise RuntimeError(f"'{name}' is a reserved configuration name.")
55
+ region = normalize_region(region or CLOUDINARY_REGION)
56
+ session = _run_browser_flow(region)
57
+ if not session.cloud_name:
58
+ raise RuntimeError("Login token did not include a cloud name; cannot save this login.")
59
+ config_name = name or _derive_config_name(session.cloud_name, region)
60
+
61
+ default_status = save_named_config(config_name, to_cloudinary_url(session), set_default=set_default)
62
+ return config_name, default_status
63
+
64
+
65
+ def logout(name):
66
+ """
67
+ Log out of a saved OAuth login by name: revoke its refresh token at the authorization server,
68
+ then remove the saved configuration. The local entry is always removed even if revocation fails
69
+ (offline, server error), so logout never leaves a stale entry behind.
70
+
71
+ Returns "removed" (revoked and removed), "revoke_failed" (removed locally but the token could not
72
+ be revoked), "not_found", or "not_oauth".
73
+ """
74
+ saved = load_config()
75
+ if name not in saved:
76
+ return "not_found"
77
+ if not is_oauth_url(saved[name]):
78
+ return "not_oauth"
79
+
80
+ revoked = _revoke_login(name, saved[name])
81
+ remove_config_keys(name)
82
+ return "removed" if revoked else "revoke_failed"
83
+
84
+
85
+ def _revoke_login(name, url):
86
+ """Best-effort revocation of a saved login's refresh token. Returns True on success (or when
87
+ there is nothing to revoke), False if the revoke request failed."""
88
+ session = from_cloudinary_url(url)
89
+ if not session.refresh_token:
90
+ return True
91
+ try:
92
+ flow.revoke(session.refresh_token, session.region)
93
+ return True
94
+ except requests.RequestException as e:
95
+ log_exception(e, debug_message=f"Could not revoke the OAuth token for '{name}'")
96
+ return False
97
+
98
+
99
+ def _run_browser_flow(region):
100
+ verifier, challenge = flow.generate_pkce_pair()
101
+ state = secrets.token_urlsafe(16)
102
+ httpd, redirect_uri = start_callback_server()
103
+
104
+ authorize_url = flow.build_authorize_url(challenge, state, redirect_uri, region)
105
+ logger.info("Opening browser to log in to Cloudinary...")
106
+ opened = webbrowser.open(authorize_url)
107
+ if not opened and not is_interactive():
108
+ # No browser and no interactive terminal: nobody can complete the redirect, so fail fast
109
+ # instead of blocking until the callback times out. Headless runs use a pre-set config.
110
+ httpd.server_close()
111
+ raise RuntimeError(
112
+ "cld login needs an interactive browser session, but no browser could be opened and "
113
+ "this is not an interactive terminal. For headless/CI use, configure credentials with "
114
+ "an API-key URL instead: `cld -c cloudinary://<key>:<secret>@<cloud> <command>` or save "
115
+ "one with `cld config -n <name> <url>` and select it via `-C <name>`."
116
+ )
117
+ if not opened:
118
+ logger.info(f"Could not open a browser. Visit this URL to log in:\n{authorize_url}")
119
+ else:
120
+ logger.info(f"If it doesn't open automatically, visit:\n{authorize_url}")
121
+
122
+ auth_code, returned_state = wait_for_callback(httpd)
123
+ if returned_state != state:
124
+ raise RuntimeError("State mismatch - possible CSRF, aborting.")
125
+
126
+ token_response = flow.exchange_code(auth_code, verifier, redirect_uri, region)
127
+ return Session.from_token_response(token_response, region=region)
128
+
129
+
130
+ def _derive_config_name(cloud_name, region):
131
+ """
132
+ Build the saved name: cloud_name + region geo suffix (when not default) + auth-type suffix
133
+ only when the base name collides with a DIFFERENT auth type (re-login overwrites in place).
134
+ """
135
+ base = cloud_name
136
+ if region != DEFAULT_REGION:
137
+ base = f"{base}-{region[len('api-'):]}" # api-eu -> "<cloud>-eu"
138
+
139
+ config = load_config()
140
+ existing = config.get(base)
141
+ if existing is None or is_oauth_url(existing):
142
+ return base # free, or same (oauth) type -> overwrite in place
143
+ return f"{base}-oauth" # taken by an api-key config -> suffix the new oauth entry