cloudsmith-cli 1.17.0__tar.gz → 1.18.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 (163) hide show
  1. {cloudsmith_cli-1.17.0/cloudsmith_cli.egg-info → cloudsmith_cli-1.18.0}/PKG-INFO +31 -1
  2. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/README.md +24 -0
  3. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/mcp.py +126 -37
  4. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/metadata.py +4 -16
  5. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/whoami.py +15 -22
  6. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/config.py +72 -0
  7. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/decorators.py +149 -18
  8. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/tests/commands/test_mcp.py +226 -2
  9. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/tests/commands/test_metadata.py +14 -9
  10. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/tests/conftest.py +4 -1
  11. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/tests/test_webserver.py +5 -1
  12. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/webserver.py +10 -1
  13. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/api/init.py +10 -64
  14. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/api/metadata.py +9 -64
  15. cloudsmith_cli-1.18.0/cloudsmith_cli/core/credentials/chain.py +69 -0
  16. cloudsmith_cli-1.18.0/cloudsmith_cli/core/credentials/models.py +42 -0
  17. cloudsmith_cli-1.18.0/cloudsmith_cli/core/credentials/oidc/__init__.py +6 -0
  18. cloudsmith_cli-1.18.0/cloudsmith_cli/core/credentials/oidc/cache.py +219 -0
  19. cloudsmith_cli-1.18.0/cloudsmith_cli/core/credentials/oidc/detectors/__init__.py +42 -0
  20. cloudsmith_cli-1.18.0/cloudsmith_cli/core/credentials/oidc/detectors/aws.py +84 -0
  21. cloudsmith_cli-1.18.0/cloudsmith_cli/core/credentials/oidc/detectors/base.py +25 -0
  22. cloudsmith_cli-1.18.0/cloudsmith_cli/core/credentials/oidc/exchange.py +87 -0
  23. cloudsmith_cli-1.18.0/cloudsmith_cli/core/credentials/provider.py +17 -0
  24. cloudsmith_cli-1.18.0/cloudsmith_cli/core/credentials/providers/__init__.py +15 -0
  25. cloudsmith_cli-1.18.0/cloudsmith_cli/core/credentials/providers/cli_flag.py +24 -0
  26. cloudsmith_cli-1.18.0/cloudsmith_cli/core/credentials/providers/credentials_file.py +24 -0
  27. cloudsmith_cli-1.18.0/cloudsmith_cli/core/credentials/providers/env_var.py +24 -0
  28. cloudsmith_cli-1.18.0/cloudsmith_cli/core/credentials/providers/keyring_provider.py +59 -0
  29. cloudsmith_cli-1.18.0/cloudsmith_cli/core/credentials/providers/oidc_provider.py +115 -0
  30. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/keyring.py +31 -0
  31. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/rest.py +16 -12
  32. cloudsmith_cli-1.18.0/cloudsmith_cli/core/tests/__init__.py +0 -0
  33. cloudsmith_cli-1.18.0/cloudsmith_cli/core/tests/test_cli_flag_provider.py +46 -0
  34. cloudsmith_cli-1.18.0/cloudsmith_cli/core/tests/test_credential_chain_priority.py +135 -0
  35. cloudsmith_cli-1.18.0/cloudsmith_cli/core/tests/test_credential_context.py +30 -0
  36. cloudsmith_cli-1.18.0/cloudsmith_cli/core/tests/test_credential_provider_chain.py +80 -0
  37. cloudsmith_cli-1.18.0/cloudsmith_cli/core/tests/test_credentials_file_provider.py +47 -0
  38. cloudsmith_cli-1.18.0/cloudsmith_cli/core/tests/test_env_var_provider.py +45 -0
  39. cloudsmith_cli-1.18.0/cloudsmith_cli/core/tests/test_init.py +120 -0
  40. cloudsmith_cli-1.18.0/cloudsmith_cli/core/tests/test_keyring_provider.py +81 -0
  41. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/tests/test_metadata.py +44 -55
  42. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/tests/test_rest.py +24 -27
  43. cloudsmith_cli-1.18.0/cloudsmith_cli/data/VERSION +1 -0
  44. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0/cloudsmith_cli.egg-info}/PKG-INFO +31 -1
  45. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli.egg-info/SOURCES.txt +23 -0
  46. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli.egg-info/requires.txt +7 -0
  47. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/setup.py +9 -0
  48. cloudsmith_cli-1.17.0/cloudsmith_cli/core/tests/test_init.py +0 -371
  49. cloudsmith_cli-1.17.0/cloudsmith_cli/data/VERSION +0 -1
  50. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/LICENSE +0 -0
  51. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/MANIFEST.in +0 -0
  52. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/__init__.py +0 -0
  53. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/__main__.py +0 -0
  54. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/__init__.py +0 -0
  55. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/command.py +0 -0
  56. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/__init__.py +0 -0
  57. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/auth.py +0 -0
  58. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/check.py +0 -0
  59. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/copy.py +0 -0
  60. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/delete.py +0 -0
  61. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/dependencies.py +0 -0
  62. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/docs.py +0 -0
  63. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/download.py +0 -0
  64. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/entitlements.py +0 -0
  65. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/help_.py +0 -0
  66. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/list_.py +0 -0
  67. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/login.py +0 -0
  68. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/logout.py +0 -0
  69. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/main.py +0 -0
  70. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/metrics/__init__.py +0 -0
  71. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/metrics/command.py +0 -0
  72. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/metrics/entitlements.py +0 -0
  73. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/metrics/packages.py +0 -0
  74. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/move.py +0 -0
  75. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/policy/__init__.py +0 -0
  76. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/policy/command.py +0 -0
  77. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/policy/deny.py +0 -0
  78. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/policy/license.py +0 -0
  79. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/policy/vulnerability.py +0 -0
  80. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/push.py +0 -0
  81. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/quarantine.py +0 -0
  82. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/quota/__init__.py +0 -0
  83. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/quota/command.py +0 -0
  84. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/quota/history.py +0 -0
  85. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/quota/quota.py +0 -0
  86. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/repos.py +0 -0
  87. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/resync.py +0 -0
  88. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/status.py +0 -0
  89. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/tags.py +0 -0
  90. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/tokens.py +0 -0
  91. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/upstream.py +0 -0
  92. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/commands/vulnerabilities.py +0 -0
  93. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/exceptions.py +0 -0
  94. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/metadata_common.py +0 -0
  95. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/saml.py +0 -0
  96. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/table.py +0 -0
  97. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/tests/__init__.py +0 -0
  98. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/tests/commands/__init__.py +0 -0
  99. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/tests/commands/conftest.py +0 -0
  100. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/tests/commands/policy/__init__.py +0 -0
  101. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/tests/commands/policy/test_deny.py +0 -0
  102. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/tests/commands/policy/test_licence.py +0 -0
  103. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/tests/commands/policy/test_vulnerability.py +0 -0
  104. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/tests/commands/test_auth.py +0 -0
  105. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/tests/commands/test_check.py +0 -0
  106. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/tests/commands/test_download.py +0 -0
  107. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/tests/commands/test_entitlements.py +0 -0
  108. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/tests/commands/test_login.py +0 -0
  109. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/tests/commands/test_logout.py +0 -0
  110. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/tests/commands/test_main.py +0 -0
  111. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/tests/commands/test_package_commands.py +0 -0
  112. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/tests/commands/test_repos.py +0 -0
  113. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/tests/commands/test_tokens.py +0 -0
  114. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/tests/commands/test_upstream.py +0 -0
  115. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/tests/commands/test_vulnerabilities.py +0 -0
  116. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/tests/test_metadata_common.py +0 -0
  117. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/tests/test_push.py +0 -0
  118. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/tests/test_saml.py +0 -0
  119. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/tests/test_utils.py +0 -0
  120. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/tests/utils.py +0 -0
  121. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/types.py +0 -0
  122. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/utils.py +0 -0
  123. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/cli/validators.py +0 -0
  124. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/__init__.py +0 -0
  125. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/api/__init__.py +0 -0
  126. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/api/distros.py +0 -0
  127. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/api/entitlements.py +0 -0
  128. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/api/exceptions.py +0 -0
  129. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/api/files.py +0 -0
  130. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/api/metrics.py +0 -0
  131. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/api/orgs.py +0 -0
  132. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/api/packages.py +0 -0
  133. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/api/quota.py +0 -0
  134. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/api/rates.py +0 -0
  135. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/api/repos.py +0 -0
  136. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/api/status.py +0 -0
  137. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/api/upstreams.py +0 -0
  138. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/api/user.py +0 -0
  139. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/api/version.py +0 -0
  140. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/api/vulnerabilities.py +0 -0
  141. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/config.py +0 -0
  142. {cloudsmith_cli-1.17.0/cloudsmith_cli/core/mcp → cloudsmith_cli-1.18.0/cloudsmith_cli/core/credentials}/__init__.py +0 -0
  143. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/download.py +0 -0
  144. {cloudsmith_cli-1.17.0/cloudsmith_cli/core/tests → cloudsmith_cli-1.18.0/cloudsmith_cli/core/mcp}/__init__.py +0 -0
  145. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/mcp/data.py +0 -0
  146. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/mcp/server.py +0 -0
  147. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/pagination.py +0 -0
  148. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/ratelimits.py +0 -0
  149. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/tests/test_download.py +0 -0
  150. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/tests/test_keyring.py +0 -0
  151. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/tests/test_version.py +0 -0
  152. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/utils.py +0 -0
  153. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/core/version.py +0 -0
  154. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/data/config.ini +0 -0
  155. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/data/credentials.ini +0 -0
  156. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/templates/__init__.py +0 -0
  157. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/templates/auth_error.html +0 -0
  158. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli/templates/auth_success.html +0 -0
  159. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli.egg-info/dependency_links.txt +0 -0
  160. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli.egg-info/entry_points.txt +0 -0
  161. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli.egg-info/not-zip-safe +0 -0
  162. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/cloudsmith_cli.egg-info/top_level.txt +0 -0
  163. {cloudsmith_cli-1.17.0 → cloudsmith_cli-1.18.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudsmith-cli
3
- Version: 1.17.0
3
+ Version: 1.18.0
4
4
  Summary: Cloudsmith Command-Line Interface (CLI)
5
5
  Home-page: https://github.com/cloudsmith-io/cloudsmith-cli
6
6
  Author: Cloudsmith Ltd
@@ -34,12 +34,17 @@ Requires-Dist: json5>=0.9.0
34
34
  Requires-Dist: cloudsmith-api<3.0,>=2.0.25
35
35
  Requires-Dist: keyring>=25.4.1
36
36
  Requires-Dist: mcp==1.9.1
37
+ Requires-Dist: PyJWT>=2.0.0
37
38
  Requires-Dist: python-toon==0.1.2
38
39
  Requires-Dist: requests>=2.18.4
39
40
  Requires-Dist: requests_toolbelt>=1.0.0
40
41
  Requires-Dist: rich>=13.0.0
41
42
  Requires-Dist: semver>=2.7.9
42
43
  Requires-Dist: urllib3>=2.5
44
+ Provides-Extra: aws
45
+ Requires-Dist: boto3[crt]>=1.26.0; extra == "aws"
46
+ Provides-Extra: all
47
+ Requires-Dist: boto3[crt]>=1.26.0; extra == "all"
43
48
  Dynamic: author
44
49
  Dynamic: author-email
45
50
  Dynamic: classifier
@@ -50,6 +55,7 @@ Dynamic: keywords
50
55
  Dynamic: license
51
56
  Dynamic: license-file
52
57
  Dynamic: platform
58
+ Dynamic: provides-extra
53
59
  Dynamic: requires-dist
54
60
  Dynamic: requires-python
55
61
  Dynamic: summary
@@ -197,6 +203,30 @@ Or you can get the latest pre-release version from Cloudsmith:
197
203
  pip install --upgrade cloudsmith-cli --extra-index-url=https://dl.cloudsmith.io/public/cloudsmith/cli/python/index/
198
204
  ```
199
205
 
206
+ ### Optional Dependencies
207
+
208
+ The CLI supports optional extras for additional functionality:
209
+
210
+ #### AWS OIDC Support
211
+
212
+ For AWS environments (ECS, EKS, EC2), install with `aws` extra to enable automatic credential discovery:
213
+
214
+ ```
215
+ pip install cloudsmith-cli[aws]
216
+ ```
217
+
218
+ This installs `boto3[crt]` for AWS credential chain support, STS token generation, and AWS SSO compatibility.
219
+
220
+ #### All Optional Features
221
+
222
+ To install all optional dependencies:
223
+
224
+ ```
225
+ pip install cloudsmith-cli[all]
226
+ ```
227
+
228
+ **Note:** If you don't install the AWS extra, the AWS OIDC detector will gracefully skip itself with no errors.
229
+
200
230
  ## Configuration
201
231
 
202
232
  There are two configuration files used by the CLI:
@@ -141,6 +141,30 @@ Or you can get the latest pre-release version from Cloudsmith:
141
141
  pip install --upgrade cloudsmith-cli --extra-index-url=https://dl.cloudsmith.io/public/cloudsmith/cli/python/index/
142
142
  ```
143
143
 
144
+ ### Optional Dependencies
145
+
146
+ The CLI supports optional extras for additional functionality:
147
+
148
+ #### AWS OIDC Support
149
+
150
+ For AWS environments (ECS, EKS, EC2), install with `aws` extra to enable automatic credential discovery:
151
+
152
+ ```
153
+ pip install cloudsmith-cli[aws]
154
+ ```
155
+
156
+ This installs `boto3[crt]` for AWS credential chain support, STS token generation, and AWS SSO compatibility.
157
+
158
+ #### All Optional Features
159
+
160
+ To install all optional dependencies:
161
+
162
+ ```
163
+ pip install cloudsmith-cli[all]
164
+ ```
165
+
166
+ **Note:** If you don't install the AWS extra, the AWS OIDC detector will gracefully skip itself with no errors.
167
+
144
168
  ## Configuration
145
169
 
146
170
  There are two configuration files used by the CLI:
@@ -4,6 +4,7 @@ import json
4
4
  import os
5
5
  import shutil
6
6
  import sys
7
+ import tempfile
7
8
  from pathlib import Path
8
9
 
9
10
  import click
@@ -16,6 +17,7 @@ from .main import main
16
17
 
17
18
  SUPPORTED_MCP_CLIENTS = {
18
19
  "claude": "Claude Desktop",
20
+ "claude-code": "Claude Code",
19
21
  "cursor": "Cursor IDE",
20
22
  "vscode": "VS Code",
21
23
  "gemini-cli": "Gemini CLI",
@@ -196,12 +198,17 @@ def configure(ctx, opts, client, is_global): # pylint: disable=unused-argument
196
198
  This command automatically adds the Cloudsmith MCP server configuration
197
199
  to the specified client's configuration file. Supported clients are:
198
200
  - Claude Desktop
201
+ - Claude Code
199
202
  - Cursor IDE
200
203
  - VS Code (GitHub Copilot)
201
204
  - Gemini CLI
202
205
 
206
+ For Claude Code, --global edits ~/.claude.json (user scope) and
207
+ --local writes ./.mcp.json (project scope, intended to be committed).
208
+
203
209
  Examples:\n
204
210
  cloudsmith mcp configure --client claude\n
211
+ cloudsmith mcp configure --client claude-code\n
205
212
  cloudsmith mcp configure --client cursor --local\n
206
213
  cloudsmith mcp configure --client gemini-cli\n
207
214
  cloudsmith mcp configure # Auto-detect and configure all
@@ -315,10 +322,16 @@ def _get_server_config(profile=None):
315
322
  def detect_available_clients():
316
323
  """Detect which MCP clients are available on the system."""
317
324
  available = []
325
+ home = Path.home()
318
326
 
319
327
  for client in SUPPORTED_MCP_CLIENTS:
320
328
  config = get_config_path(client, is_global=True)
321
- if config and config.parent.exists():
329
+ if not config:
330
+ continue
331
+ # Parent-dir existence is the usual "app installed" marker, but a
332
+ # parent of $HOME (Claude Code) tells us nothing; require the file
333
+ # itself in that case.
334
+ if config.exists() or (config.parent.exists() and config.parent != home):
322
335
  available.append(client)
323
336
 
324
337
  return available
@@ -344,6 +357,10 @@ def get_config_path(client_name, is_global=True):
344
357
  ),
345
358
  "linux": home / ".config" / "Claude" / "claude_desktop_config.json",
346
359
  },
360
+ "claude-code": {
361
+ "global": home / ".claude.json",
362
+ "local": Path.cwd() / ".mcp.json",
363
+ },
347
364
  "cursor": {
348
365
  "global": home / ".cursor" / "mcp.json",
349
366
  "local": Path.cwd() / ".cursor" / "mcp.json",
@@ -369,8 +386,8 @@ def get_config_path(client_name, is_global=True):
369
386
 
370
387
  client_config = config_paths.get(client_name, {})
371
388
 
372
- # For Cursor and Gemini CLI, use global/local scope instead of platform
373
- if client_name in ("cursor", "gemini-cli"):
389
+ # For scope-keyed (not platform-keyed) clients, look up by global/local.
390
+ if client_name in ("claude-code", "cursor", "gemini-cli"):
374
391
  scope = "global" if is_global else "local"
375
392
  return client_config.get(scope)
376
393
 
@@ -385,47 +402,119 @@ def get_config_path(client_name, is_global=True):
385
402
 
386
403
  def configure_client(client_name, server_config, is_global=True, profile=None):
387
404
  """Configure a specific MCP client with the Cloudsmith server."""
388
- config_path = get_config_path(client_name, is_global)
405
+ server_name = f"cloudsmith-{profile}" if profile else "cloudsmith"
406
+
407
+ if client_name == "claude-code":
408
+ return _configure_claude_code(server_name, server_config, is_global)
389
409
 
410
+ config_path = get_config_path(client_name, is_global)
390
411
  if not config_path:
391
412
  return False
392
413
 
393
- # Ensure parent directory exists
394
- config_path.parent.mkdir(parents=True, exist_ok=True)
414
+ key = "chat.mcp.servers" if client_name == "vscode" else "mcpServers"
395
415
 
396
- # Read existing config or create new one
397
- config = {}
398
- if config_path.exists():
399
- with open(config_path) as f:
400
- content = f.read()
401
- try:
402
- # Use json5 first as it handles both standard JSON and JSONC
403
- # (comments, trailing commas) which VS Code settings.json may contain
404
- config = json5.loads(content)
405
- except (json.JSONDecodeError, ValueError) as e:
406
- raise ValueError(
407
- f"Cannot parse config file '{config_path}': {e}. "
408
- f"Please fix the JSON syntax or remove the file to create a new one."
409
- ) from e
410
-
411
- # Determine server name based on profile
412
- server_name = f"cloudsmith-{profile}" if profile else "cloudsmith"
416
+ def mutate(config):
417
+ config.setdefault(key, {})[server_name] = server_config
418
+
419
+ _safe_update_json(config_path, mutate)
420
+ return True
413
421
 
414
- # Add Cloudsmith MCP server based on client format
415
- if client_name in {"claude", "cursor", "gemini-cli"}:
416
- if "mcpServers" not in config:
417
- config["mcpServers"] = {}
418
- config["mcpServers"][server_name] = server_config
419
422
 
420
- elif client_name == "vscode":
421
- # VS Code uses a different format in settings.json
422
- if "chat.mcp.servers" not in config:
423
- config["chat.mcp.servers"] = {}
424
- config["chat.mcp.servers"][server_name] = server_config
423
+ def _configure_claude_code(server_name, server_config, is_global):
424
+ """Register the Cloudsmith MCP server with Claude Code.
425
+
426
+ Why direct edit (not `claude mcp add-json`): avoids requiring the Claude
427
+ Code CLI on PATH. _safe_update_json handles the race with a running
428
+ Claude Code session writing to ~/.claude.json.
429
+ """
430
+ path = get_config_path("claude-code", is_global=is_global)
431
+ if is_global and not path.exists():
432
+ raise ValueError(
433
+ f"{path} not found. Launch Claude Code at least once, "
434
+ "then re-run this command."
435
+ )
425
436
 
426
- # Write updated config
427
- with open(config_path, "w") as f:
428
- # As we are using json5 to read, we write standard JSON back, which removes existing user JSON comments in the files (currently only present in VS Code settings.json).
429
- json.dump(config, f, indent=2)
437
+ def mutate(config):
438
+ config.setdefault("mcpServers", {})[server_name] = server_config
430
439
 
440
+ _safe_update_json(path, mutate)
431
441
  return True
442
+
443
+
444
+ def _atomic_write_json(path: Path, data) -> None:
445
+ """Write JSON to ``path`` atomically via a tempfile + os.replace.
446
+
447
+ Preserves the destination's existing file mode when present.
448
+
449
+ Follows symlinks before writing so dotfile-managed configs (a symlinked
450
+ ~/.claude.json, settings.json, etc.) update through to the real file
451
+ rather than getting the link replaced.
452
+ """
453
+ if path.is_symlink():
454
+ path = Path(os.path.realpath(path))
455
+
456
+ path.parent.mkdir(parents=True, exist_ok=True)
457
+ existing_mode = path.stat().st_mode & 0o777 if path.exists() else None
458
+
459
+ tmp = tempfile.NamedTemporaryFile(
460
+ mode="w",
461
+ dir=path.parent,
462
+ prefix=f".{path.name}.",
463
+ suffix=".tmp",
464
+ delete=False,
465
+ )
466
+ tmp_path = Path(tmp.name)
467
+ try:
468
+ with tmp as f:
469
+ # json5 is used for reading; we write standard JSON, which drops
470
+ # any user comments (currently only relevant for VS Code's JSONC).
471
+ json.dump(data, f, indent=2)
472
+ f.flush()
473
+ os.fsync(f.fileno())
474
+ if existing_mode is not None:
475
+ os.chmod(tmp_path, existing_mode)
476
+ os.replace(tmp_path, path)
477
+ except BaseException:
478
+ try:
479
+ tmp_path.unlink()
480
+ except FileNotFoundError:
481
+ pass
482
+ raise
483
+
484
+
485
+ def _safe_update_json(path: Path, mutate, *, max_retries: int = 3) -> None:
486
+ """Read JSON at ``path``, apply ``mutate(dict)``, atomic-write back.
487
+
488
+ Retries on mtime change between read and replace -- guards against a
489
+ concurrent writer (e.g. a running Claude Code session updating
490
+ ~/.claude.json, or VS Code editing settings.json) clobbering deltas.
491
+ """
492
+ for _ in range(max_retries):
493
+ if path.exists():
494
+ mtime_before = path.stat().st_mtime_ns
495
+ with open(path) as f:
496
+ content = f.read()
497
+ try:
498
+ config = json5.loads(content)
499
+ except (json.JSONDecodeError, ValueError) as e:
500
+ raise ValueError(
501
+ f"Cannot parse config file '{path}': {e}. "
502
+ "Please fix the JSON syntax or remove the file to "
503
+ "create a new one."
504
+ ) from e
505
+ else:
506
+ mtime_before = None
507
+ config = {}
508
+
509
+ mutate(config)
510
+
511
+ if mtime_before is not None and path.stat().st_mtime_ns != mtime_before:
512
+ continue
513
+
514
+ _atomic_write_json(path, config)
515
+ return
516
+
517
+ raise ValueError(
518
+ f"Could not safely update {path}: another process keeps modifying "
519
+ "it. Close the consuming app and retry."
520
+ )
@@ -9,8 +9,6 @@ from ...core.api.metadata import (
9
9
  delete_metadata as api_delete_metadata,
10
10
  get_metadata as api_get_metadata,
11
11
  list_metadata as api_list_metadata,
12
- normalise_classification,
13
- normalise_source_kind,
14
12
  update_metadata as api_update_metadata,
15
13
  )
16
14
  from ...core.api.packages import get_package_slug_perm as api_get_package_slug_perm
@@ -121,9 +119,8 @@ def metadata_(ctx, opts): # pylint: disable=unused-argument
121
119
  "source_kind",
122
120
  default=None,
123
121
  help=(
124
- "Filter by source kind. Accepts an integer or name "
125
- "(for example, 'customer' or 'third_party'). Ignored when "
126
- "METADATA_SLUG_PERM is given."
122
+ "Filter by source kind name (one of: unknown, system, upstream, "
123
+ "custom, third_party). Ignored when METADATA_SLUG_PERM is given."
127
124
  ),
128
125
  )
129
126
  @click.option(
@@ -131,9 +128,8 @@ def metadata_(ctx, opts): # pylint: disable=unused-argument
131
128
  "classification",
132
129
  default=None,
133
130
  help=(
134
- "Filter by classification. Accepts an integer or name "
135
- "(for example, 'provenance' or 'sbom'). Ignored when "
136
- "METADATA_SLUG_PERM is given."
131
+ "Filter by classification name (one of: unknown, intrinsic, security, "
132
+ "provenance, sbom, generic). Ignored when METADATA_SLUG_PERM is given."
137
133
  ),
138
134
  )
139
135
  @click.pass_context
@@ -187,14 +183,6 @@ def list_metadata(
187
183
  _print_metadata_entry(opts, entry)
188
184
  return
189
185
 
190
- # Validate filter values up-front for a friendlier error than what the
191
- # API would return (the normalisers raise ValueError on invalid values).
192
- try:
193
- normalise_source_kind(source_kind)
194
- normalise_classification(classification)
195
- except ValueError as exc:
196
- raise click.UsageError(str(exc)) from exc
197
-
198
186
  _echo_action(
199
187
  "Listing metadata for %(package)s ... "
200
188
  % {"package": click.style(package, bold=True)},
@@ -1,14 +1,11 @@
1
1
  """CLI/Commands - Retrieve authentication status."""
2
2
 
3
- import os
4
-
5
3
  import click
6
4
 
7
5
  from ...core import keyring
8
6
  from ...core.api.exceptions import ApiException
9
7
  from ...core.api.user import get_token_metadata, get_user_brief
10
8
  from .. import decorators, utils
11
- from ..config import CredentialsReader
12
9
  from ..exceptions import handle_api_exceptions
13
10
  from .main import main
14
11
 
@@ -26,26 +23,17 @@ def _get_active_method(api_config):
26
23
  def _get_api_key_source(opts):
27
24
  """Determine where the API key was loaded from.
28
25
 
29
- Checks in priority order matching actual resolution:
30
- CLI --api-key flag > CLOUDSMITH_API_KEY env var > credentials.ini.
26
+ Uses the credential provider chain result attached by initialise_api.
31
27
  """
32
- if not opts.api_key:
33
- return {"configured": False, "source": None, "source_key": None}
34
-
35
- env_key = os.environ.get("CLOUDSMITH_API_KEY")
36
-
37
- # If env var is set but differs from the resolved key, CLI flag won
38
- if env_key and opts.api_key != env_key:
39
- source, key = "CLI --api-key flag", "cli_flag"
40
- elif env_key:
41
- suffix = env_key[-4:]
42
- source, key = f"CLOUDSMITH_API_KEY env var (ends with ...{suffix})", "env_var"
43
- elif creds := CredentialsReader.find_existing_files():
44
- source, key = f"credentials.ini ({creds[0]})", "credentials_file"
45
- else:
46
- source, key = "CLI --api-key flag", "cli_flag"
28
+ credential = getattr(opts, "credential", None)
29
+ if credential and credential.auth_type == "api_key":
30
+ return {
31
+ "configured": True,
32
+ "source": credential.source_detail or credential.source_name,
33
+ "source_key": credential.source_name,
34
+ }
47
35
 
48
- return {"configured": True, "source": source, "source_key": key}
36
+ return {"configured": False, "source": None, "source_key": None}
49
37
 
50
38
 
51
39
  def _get_sso_status(api_host):
@@ -120,7 +108,12 @@ def _print_verbose_text(data):
120
108
  click.echo(f" Source: {ak['source']}")
121
109
  click.echo(" Note: SSO token is being used instead")
122
110
  elif active == "api_key":
123
- click.secho("Authentication Method: API Key", fg="cyan", bold=True)
111
+ if ak.get("source_key") == "oidc":
112
+ click.secho(
113
+ "Authentication Method: OIDC Auto-Discovery", fg="cyan", bold=True
114
+ )
115
+ else:
116
+ click.secho("Authentication Method: API Key", fg="cyan", bold=True)
124
117
  for label, field in [
125
118
  ("Source", "source"),
126
119
  ("Token Slug", "slug"),
@@ -66,6 +66,9 @@ class ConfigSchema:
66
66
  api_user_agent = ConfigParam(name="api_user_agent", type=str)
67
67
  mcp_allowed_tools = ConfigParam(name="mcp_allowed_tools", type=str)
68
68
  mcp_allowed_tool_groups = ConfigParam(name="mcp_allowed_tool_groups", type=str)
69
+ oidc_audience = ConfigParam(name="oidc_audience", type=str)
70
+ oidc_org = ConfigParam(name="oidc_org", type=str)
71
+ oidc_service_slug = ConfigParam(name="oidc_service_slug", type=str)
69
72
  metadata_failure_mode = ConfigParam(name="metadata_failure_mode", type=str)
70
73
 
71
74
  @matches_section("profile:*")
@@ -319,6 +322,33 @@ class Options: # pylint: disable=too-many-public-methods
319
322
  """Set value for API key."""
320
323
  self._set_option("api_key", value)
321
324
 
325
+ @property
326
+ def api_key_from_flag(self):
327
+ """Get API key set explicitly via --api-key CLI flag."""
328
+ return self._get_option("api_key_from_flag")
329
+
330
+ @api_key_from_flag.setter
331
+ def api_key_from_flag(self, value):
332
+ self._set_option("api_key_from_flag", value, allow_clear=True)
333
+
334
+ @property
335
+ def api_key_from_env(self):
336
+ """Get API key from CLOUDSMITH_API_KEY environment variable."""
337
+ return self._get_option("api_key_from_env")
338
+
339
+ @api_key_from_env.setter
340
+ def api_key_from_env(self, value):
341
+ self._set_option("api_key_from_env", value, allow_clear=True)
342
+
343
+ @property
344
+ def api_key_from_file(self):
345
+ """Get API key loaded from credentials.ini."""
346
+ return self._get_option("api_key_from_file")
347
+
348
+ @api_key_from_file.setter
349
+ def api_key_from_file(self, value):
350
+ self._set_option("api_key_from_file", value, allow_clear=True)
351
+
322
352
  @property
323
353
  def api_proxy(self):
324
354
  """Get value for API proxy."""
@@ -417,6 +447,48 @@ class Options: # pylint: disable=too-many-public-methods
417
447
 
418
448
  self._set_option("mcp_allowed_tool_groups", tool_groups)
419
449
 
450
+ @property
451
+ def oidc_audience(self):
452
+ """Get value for OIDC audience."""
453
+ return self._get_option("oidc_audience")
454
+
455
+ @oidc_audience.setter
456
+ def oidc_audience(self, value):
457
+ """Set value for OIDC audience."""
458
+ self._set_option("oidc_audience", value)
459
+
460
+ @property
461
+ def oidc_org(self):
462
+ """Get value for OIDC organisation slug."""
463
+ return self._get_option("oidc_org")
464
+
465
+ @oidc_org.setter
466
+ def oidc_org(self, value):
467
+ """Set value for OIDC organisation slug."""
468
+ self._set_option("oidc_org", value)
469
+
470
+ @property
471
+ def oidc_service_slug(self):
472
+ """Get value for OIDC service slug."""
473
+ return self._get_option("oidc_service_slug")
474
+
475
+ @oidc_service_slug.setter
476
+ def oidc_service_slug(self, value):
477
+ """Set value for OIDC service slug."""
478
+ self._set_option("oidc_service_slug", value)
479
+
480
+ @property
481
+ def oidc_discovery_disabled(self):
482
+ """Get value for OIDC discovery disabled flag."""
483
+ return self._get_option("oidc_discovery_disabled", default=False)
484
+
485
+ @oidc_discovery_disabled.setter
486
+ def oidc_discovery_disabled(self, value):
487
+ """Set value for OIDC discovery disabled flag."""
488
+ self._set_option(
489
+ "oidc_discovery_disabled", bool(value) if value is not None else False
490
+ )
491
+
420
492
  @property
421
493
  def metadata_failure_mode(self):
422
494
  """Get value for push-time metadata failure mode."""