cloudsmith-cli 1.30.0__tar.gz → 1.30.1__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 (172) hide show
  1. {cloudsmith_cli-1.30.0/cloudsmith_cli.egg-info → cloudsmith_cli-1.30.1}/PKG-INFO +2 -2
  2. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/auth.py +22 -1
  3. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/credential_helper/manage.py +2 -2
  4. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/main.py +1 -0
  5. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/mcp.py +10 -7
  6. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/update.py +2 -2
  7. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/vulnerabilities.py +27 -8
  8. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/config.py +68 -0
  9. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/decorators.py +61 -1
  10. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/validators.py +11 -5
  11. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/conftest.py +7 -0
  12. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/exceptions.py +21 -5
  13. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/init.py +15 -4
  14. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/user.py +3 -1
  15. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/vulnerabilities.py +31 -17
  16. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/oidc/cache.py +11 -9
  17. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/download.py +3 -3
  18. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/keyring.py +37 -13
  19. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/pagination.py +16 -13
  20. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/sso.py +2 -2
  21. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/update_check.py +1 -1
  22. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/docker/installer.py +7 -3
  23. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/pnpm/installer.py +5 -7
  24. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/pnpm/rc.py +7 -4
  25. cloudsmith_cli-1.30.1/cloudsmith_cli/data/VERSION +1 -0
  26. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1/cloudsmith_cli.egg-info}/PKG-INFO +2 -2
  27. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli.egg-info/requires.txt +1 -1
  28. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/pyproject.toml +5 -4
  29. cloudsmith_cli-1.30.0/cloudsmith_cli/data/VERSION +0 -1
  30. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/LICENSE +0 -0
  31. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/MANIFEST.in +0 -0
  32. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/README.md +0 -0
  33. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/__init__.py +0 -0
  34. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/__main__.py +0 -0
  35. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/__init__.py +0 -0
  36. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/command.py +0 -0
  37. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/__init__.py +0 -0
  38. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/check.py +0 -0
  39. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/copy.py +0 -0
  40. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/credential_helper/__init__.py +0 -0
  41. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/credential_helper/cargo.py +0 -0
  42. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/credential_helper/docker.py +0 -0
  43. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/credential_helper/generic.py +0 -0
  44. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/credential_helper/pnpm.py +0 -0
  45. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/credential_helper/terraform.py +0 -0
  46. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/delete.py +0 -0
  47. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/dependencies.py +0 -0
  48. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/docs.py +0 -0
  49. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/domains.py +0 -0
  50. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/download.py +0 -0
  51. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/entitlements.py +0 -0
  52. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/help_.py +0 -0
  53. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/list_.py +0 -0
  54. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/login.py +0 -0
  55. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/logout.py +0 -0
  56. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/metadata.py +0 -0
  57. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/metrics/__init__.py +0 -0
  58. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/metrics/command.py +0 -0
  59. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/metrics/entitlements.py +0 -0
  60. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/metrics/packages.py +0 -0
  61. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/move.py +0 -0
  62. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/policy/__init__.py +0 -0
  63. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/policy/command.py +0 -0
  64. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/policy/deny.py +0 -0
  65. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/policy/license.py +0 -0
  66. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/policy/vulnerability.py +0 -0
  67. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/push.py +0 -0
  68. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/quarantine.py +0 -0
  69. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/quota/__init__.py +0 -0
  70. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/quota/command.py +0 -0
  71. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/quota/history.py +0 -0
  72. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/quota/quota.py +0 -0
  73. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/registry.py +0 -0
  74. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/repos.py +0 -0
  75. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/resync.py +0 -0
  76. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/status.py +0 -0
  77. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/tags.py +0 -0
  78. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/tokens.py +0 -0
  79. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/upstream.py +0 -0
  80. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/commands/whoami.py +0 -0
  81. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/dsc_parser.py +0 -0
  82. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/exceptions.py +0 -0
  83. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/metadata_common.py +0 -0
  84. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/saml.py +0 -0
  85. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/table.py +0 -0
  86. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/types.py +0 -0
  87. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/utils.py +0 -0
  88. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/cli/webserver.py +0 -0
  89. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/__init__.py +0 -0
  90. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/__init__.py +0 -0
  91. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/distros.py +0 -0
  92. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/entitlements.py +0 -0
  93. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/files.py +0 -0
  94. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/metadata.py +0 -0
  95. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/metrics.py +0 -0
  96. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/orgs.py +0 -0
  97. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/packages.py +0 -0
  98. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/quota.py +0 -0
  99. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/rates.py +0 -0
  100. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/repos.py +0 -0
  101. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/status.py +0 -0
  102. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/upstreams.py +0 -0
  103. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/v2.py +0 -0
  104. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/version.py +0 -0
  105. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/api/workspaces.py +0 -0
  106. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/cache_utils.py +0 -0
  107. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/certificate_errors.py +0 -0
  108. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/config.py +0 -0
  109. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/__init__.py +0 -0
  110. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/chain.py +0 -0
  111. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/models.py +0 -0
  112. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/oidc/__init__.py +0 -0
  113. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/oidc/detectors/__init__.py +0 -0
  114. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/oidc/detectors/aws.py +0 -0
  115. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/oidc/detectors/azure_devops.py +0 -0
  116. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/oidc/detectors/base.py +0 -0
  117. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/oidc/detectors/bitbucket_pipelines.py +0 -0
  118. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/oidc/detectors/buildkite.py +0 -0
  119. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/oidc/detectors/circleci.py +0 -0
  120. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/oidc/detectors/gcp.py +0 -0
  121. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/oidc/detectors/generic.py +0 -0
  122. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/oidc/detectors/github_actions.py +0 -0
  123. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/oidc/detectors/gitlab_ci.py +0 -0
  124. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/oidc/exchange.py +0 -0
  125. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/provider.py +0 -0
  126. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/providers/__init__.py +0 -0
  127. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/providers/cli_flag.py +0 -0
  128. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/providers/credentials_file.py +0 -0
  129. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/providers/env_var.py +0 -0
  130. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/providers/keyring_provider.py +0 -0
  131. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/credentials/providers/oidc_provider.py +0 -0
  132. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/installation.py +0 -0
  133. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/macos_keychain.py +0 -0
  134. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/mcp/__init__.py +0 -0
  135. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/mcp/data.py +0 -0
  136. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/mcp/server.py +0 -0
  137. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/ratelimits.py +0 -0
  138. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/rest.py +0 -0
  139. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/self_update.py +0 -0
  140. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/session.py +0 -0
  141. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/telemetry.py +0 -0
  142. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/utils.py +0 -0
  143. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/core/version.py +0 -0
  144. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/__init__.py +0 -0
  145. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/backends.py +0 -0
  146. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/cargo/__init__.py +0 -0
  147. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/cargo/installer.py +0 -0
  148. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/cargo/runtime.py +0 -0
  149. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/common.py +0 -0
  150. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/docker/__init__.py +0 -0
  151. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/docker/runtime.py +0 -0
  152. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/domains.py +0 -0
  153. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/generic.py +0 -0
  154. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/launchers.py +0 -0
  155. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/pnpm/__init__.py +0 -0
  156. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/pnpm/runtime.py +0 -0
  157. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/terraform/__init__.py +0 -0
  158. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/terraform/installer.py +0 -0
  159. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/terraform/runtime.py +0 -0
  160. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/credential_helpers/terraform/terraformrc.py +0 -0
  161. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/data/config.ini +0 -0
  162. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/data/credentials.ini +0 -0
  163. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/templates/__init__.py +0 -0
  164. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/templates/auth_error.html +0 -0
  165. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/templates/auth_success.html +0 -0
  166. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/templates/auth_two_factor.html +0 -0
  167. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli/wrapper.py +0 -0
  168. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli.egg-info/SOURCES.txt +0 -0
  169. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli.egg-info/dependency_links.txt +0 -0
  170. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli.egg-info/entry_points.txt +0 -0
  171. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/cloudsmith_cli.egg-info/top_level.txt +0 -0
  172. {cloudsmith_cli-1.30.0 → cloudsmith_cli-1.30.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudsmith-cli
3
- Version: 1.30.0
3
+ Version: 1.30.1
4
4
  Summary: Cloudsmith Command-Line Interface (CLI)
5
5
  Author-email: Cloudsmith Ltd <support@cloudsmith.io>
6
6
  License-Expression: Apache-2.0
@@ -22,7 +22,7 @@ Classifier: Topic :: Utilities
22
22
  Requires-Python: >=3.11.0
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
- Requires-Dist: certifi>=2024.7.4
25
+ Requires-Dist: certifi>=2026.10.23
26
26
  Requires-Dist: click!=8.3.0,>=8.2
27
27
  Requires-Dist: click-configfile>=0.2.3
28
28
  Requires-Dist: click-didyoumean>=0.0.3
@@ -158,10 +158,28 @@ def _perform_saml_authentication(
158
158
  )
159
159
 
160
160
  auth_server.handle_request()
161
+ return auth_server.sso_access_token is not None
161
162
  finally:
162
163
  auth_server.server_close()
163
164
 
164
165
 
166
+ def _save_workspace(ctx, opts, workspace, use_stderr):
167
+ """Save the Workspace to config.ini, or warn when the CLI cannot write it."""
168
+ try:
169
+ saved_path = opts.get_config_reader().save_workspace(
170
+ workspace, profile=ctx.meta.get("profile"), path=ctx.meta.get("config_file")
171
+ )
172
+ except (OSError, ValueError) as exc:
173
+ click.secho(
174
+ f"Could not save the {workspace} Workspace to config.ini: {exc}",
175
+ fg="yellow",
176
+ err=True,
177
+ )
178
+ return
179
+ if saved_path:
180
+ click.echo(f"Saved the {workspace} Workspace to {saved_path}.", err=use_stderr)
181
+
182
+
165
183
  @main.command(aliases=["auth"])
166
184
  @click.option(
167
185
  "-t",
@@ -276,7 +294,7 @@ def authenticate(
276
294
 
277
295
  context_message = "Failed to authenticate via SAML!"
278
296
  with handle_api_exceptions(ctx, opts=opts, context_msg=context_message):
279
- _perform_saml_authentication(
297
+ authenticated = _perform_saml_authentication(
280
298
  opts,
281
299
  workspace,
282
300
  enable_token_creation=enable_token_creation,
@@ -285,6 +303,9 @@ def authenticate(
285
303
  profile=ctx.meta.get("profile"),
286
304
  )
287
305
 
306
+ if authenticated:
307
+ _save_workspace(ctx, opts, workspace, use_stderr)
308
+
288
309
  if request_api_key_flag:
289
310
  # Non-interactive token retrieval
290
311
  new_token = request_api_key(ctx, opts, save_config=save_config)
@@ -351,7 +351,7 @@ def install_cmd(
351
351
  sys.exit(ec)
352
352
 
353
353
  if dry_run:
354
- click.echo("Dry run — no changes will be made:", err=use_stderr)
354
+ click.echo("Dry run - no changes will be made:", err=use_stderr)
355
355
  for action in normal:
356
356
  click.echo(f" {action}" if dry_run else action, err=use_stderr)
357
357
  for warning in warnings:
@@ -419,7 +419,7 @@ def uninstall_cmd(ctx, opts, helper: str, bin_dir: str | None, dry_run: bool) ->
419
419
  return
420
420
 
421
421
  if dry_run:
422
- click.echo("Dry run — no changes will be made:", err=use_stderr)
422
+ click.echo("Dry run - no changes will be made:", err=use_stderr)
423
423
  for action in normal:
424
424
  click.echo(f" {action}" if dry_run else action, err=use_stderr)
425
425
  for warning in warnings:
@@ -64,6 +64,7 @@ For issues/contributing: {get_github_website()}
64
64
  envvar="CLOUDSMITH_NO_UPDATE_CHECK",
65
65
  help="Disable the once-a-day check for a newer version of the CLI.",
66
66
  )
67
+ @decorators.keyring_options
67
68
  @decorators.common_cli_config_options
68
69
  @decorators.common_cli_output_options
69
70
  @click.pass_context
@@ -16,6 +16,7 @@ import json5
16
16
  if TYPE_CHECKING:
17
17
  from ...core.mcp import server
18
18
  from ...core.mcp.data import OpenAPITool
19
+ from ..config import Options
19
20
 
20
21
  from .. import command, decorators, utils
21
22
  from .main import main
@@ -196,7 +197,9 @@ def print_groups(group_list: dict[str, list[str]]):
196
197
  )
197
198
  @decorators.initialise_api
198
199
  @click.pass_context
199
- def configure(ctx, opts, client, is_global): # pylint: disable=unused-argument
200
+ def configure( # pylint: disable=unused-argument
201
+ ctx: click.Context, opts: Options, client: str | None, is_global: bool
202
+ ) -> None:
200
203
  """
201
204
  Configure the Cloudsmith MCP server for supported clients.
202
205
 
@@ -253,7 +256,7 @@ def configure(ctx, opts, client, is_global): # pylint: disable=unused-argument
253
256
  if configure_client(client_name, server_config, is_global, profile):
254
257
  if not use_stderr:
255
258
  click.echo(
256
- click.style(f"✓ Configured {client_name.title()}", fg="green")
259
+ click.style(f"Configured {client_name.title()}", fg="green")
257
260
  )
258
261
  success_count += 1
259
262
  results.append({"client": client_name, "success": True})
@@ -261,7 +264,7 @@ def configure(ctx, opts, client, is_global): # pylint: disable=unused-argument
261
264
  if not use_stderr:
262
265
  click.echo(
263
266
  click.style(
264
- f"✗ Failed to configure {client_name.title()}", fg="red"
267
+ f"Failed to configure {client_name.title()}", fg="red"
265
268
  )
266
269
  )
267
270
  results.append(
@@ -275,7 +278,7 @@ def configure(ctx, opts, client, is_global): # pylint: disable=unused-argument
275
278
  if not use_stderr:
276
279
  click.echo(
277
280
  click.style(
278
- f"✗ Error configuring {client_name.title()}: {e!s}", fg="red"
281
+ f"Error configuring {client_name.title()}: {e!s}", fg="red"
279
282
  )
280
283
  )
281
284
  results.append({"client": client_name, "success": False, "error": str(e)})
@@ -286,14 +289,14 @@ def configure(ctx, opts, client, is_global): # pylint: disable=unused-argument
286
289
  if success_count > 0:
287
290
  click.echo(
288
291
  click.style(
289
- f"\n✓ Successfully configured {success_count} client(s)", fg="green"
292
+ f"\nSuccessfully configured {success_count} client(s)", fg="green"
290
293
  )
291
294
  )
292
295
  click.echo(
293
296
  "\nNote: You may need to restart the client application for changes to take effect."
294
297
  )
295
298
  else:
296
- click.echo(click.style("\n✗ No clients were configured successfully", fg="red"))
299
+ click.echo(click.style("\nNo clients were configured successfully", fg="red"))
297
300
 
298
301
 
299
302
  def _get_server_config(profile=None):
@@ -502,7 +505,7 @@ def _safe_update_json(path: Path, mutate, *, max_retries: int = 3) -> None:
502
505
  for _ in range(max_retries):
503
506
  if path.exists():
504
507
  mtime_before = path.stat().st_mtime_ns
505
- with open(path) as f:
508
+ with open(path, encoding="utf-8") as f:
506
509
  content = f.read()
507
510
  try:
508
511
  config = json5.loads(content)
@@ -85,7 +85,7 @@ def _print_manual_update(opts, current, latest, manifest, data):
85
85
  if utils.maybe_print_as_json(opts, data):
86
86
  return
87
87
  click.echo(
88
- f"A new version of the Cloudsmith CLI is available: {current} \u2192 {latest}"
88
+ f"A new version of the Cloudsmith CLI is available: {current} -> {latest}"
89
89
  )
90
90
  click.echo(f"See what's changed: {changelog}")
91
91
  click.echo("Self-update is not supported for the standalone binary on Windows.")
@@ -150,7 +150,7 @@ def update(ctx, opts, assume_yes):
150
150
  if utils.maybe_print_as_json(opts, data):
151
151
  return
152
152
  click.echo(
153
- f"A new version of the Cloudsmith CLI is available: {current} \u2192 {latest}"
153
+ f"A new version of the Cloudsmith CLI is available: {current} -> {latest}"
154
154
  )
155
155
  click.echo(f"See what's changed: {changelog}")
156
156
  click.echo(f"The CLI was installed via {channel}. To update, run:")
@@ -2,16 +2,27 @@
2
2
 
3
3
  import click
4
4
 
5
+ from ...core.api.exceptions import ApiException
5
6
  from ...core.api.vulnerabilities import (
7
+ NO_SCAN_RESULTS_MSG,
6
8
  _print_vulnerabilities_assessment_table,
7
9
  _print_vulnerabilities_summary_table,
10
+ _severity_of,
8
11
  get_package_scan_result,
9
12
  )
10
13
  from .. import decorators, utils, validators
14
+ from ..config import Options
11
15
  from ..exceptions import handle_api_exceptions
12
16
  from .main import main
13
17
 
14
18
 
19
+ def _summarise_no_scan_results(
20
+ _exc: ApiException, detail: str | None, _fields: dict
21
+ ) -> str | None:
22
+ """Show only the detail when the package has no scan results."""
23
+ return detail if detail == NO_SCAN_RESULTS_MSG else None
24
+
25
+
15
26
  @main.command()
16
27
  @decorators.common_cli_config_options
17
28
  @decorators.common_cli_output_options
@@ -41,8 +52,13 @@ from .main import main
41
52
  )
42
53
  @click.pass_context
43
54
  def vulnerabilities(
44
- ctx, opts, owner_repo_package, show_assessment, fixable, severity_filter
45
- ):
55
+ ctx: click.Context,
56
+ opts: Options,
57
+ owner_repo_package: tuple[str, str, str],
58
+ show_assessment: bool,
59
+ fixable: bool | None,
60
+ severity_filter: str | None,
61
+ ) -> None:
46
62
  """
47
63
  Retrieve vulnerability scan results for a package.
48
64
 
@@ -82,7 +98,12 @@ def vulnerabilities(
82
98
 
83
99
  context_msg = "Failed to retrieve vulnerability report!"
84
100
  with (
85
- handle_api_exceptions(ctx, opts=opts, context_msg=context_msg),
101
+ handle_api_exceptions(
102
+ ctx,
103
+ opts=opts,
104
+ context_msg=context_msg,
105
+ summarise_error=_summarise_no_scan_results,
106
+ ),
86
107
  utils.maybe_spinner(opts),
87
108
  ):
88
109
  data = get_package_scan_result(
@@ -99,7 +120,7 @@ def vulnerabilities(
99
120
 
100
121
  # Filter results if severity or fixable flags are active
101
122
  if severity_filter or fixable is not None:
102
- scans = getattr(data, "scans", [])
123
+ scans = getattr(data, "scans", None) or []
103
124
 
104
125
  allowed_severities = (
105
126
  [s.strip().lower() for s in severity_filter.split(",")]
@@ -108,14 +129,12 @@ def vulnerabilities(
108
129
  )
109
130
 
110
131
  for scan in scans:
111
- results = getattr(scan, "results", [])
132
+ results = getattr(scan, "results", None) or []
112
133
 
113
134
  # 1. Filter by Severity
114
135
  if allowed_severities:
115
136
  results = [
116
- res
117
- for res in results
118
- if getattr(res, "severity", "unknown").lower() in allowed_severities
137
+ res for res in results if _severity_of(res) in allowed_severities
119
138
  ]
120
139
 
121
140
  # 2. Filter by Fixable Status
@@ -155,6 +155,44 @@ class ConfigReader(ConfigFileReader):
155
155
 
156
156
  return True
157
157
 
158
+ @classmethod
159
+ def get_user_filepath(cls):
160
+ """Get the user-level config file with the highest precedence, existing or not."""
161
+ user_paths = [
162
+ os.path.join(searchpath, filename)
163
+ for searchpath in cls.config_searchpath
164
+ for filename in cls.config_files
165
+ if os.path.isabs(os.path.join(searchpath, filename))
166
+ ]
167
+ existing_paths = [path for path in user_paths if os.path.isfile(path)]
168
+ return (existing_paths or user_paths)[0]
169
+
170
+ @classmethod
171
+ def save_workspace(cls, workspace, profile=None, path=None):
172
+ """Write the Workspace to the profile section; return the path if it changed."""
173
+ if path and os.path.isdir(path):
174
+ path = os.path.join(path, "config.ini")
175
+ path = path or cls.get_user_filepath()
176
+ if os.path.exists(path):
177
+ with open(path) as config_file:
178
+ content = config_file.read()
179
+ else:
180
+ content = read_file(get_data_path(), "config.ini")
181
+
182
+ section = (
183
+ f"profile:{profile}" if profile and profile != "default" else "default"
184
+ )
185
+ if _get_section_value(content, section, "workspace") == workspace:
186
+ return None
187
+ updated = _set_section_value(content, section, "workspace", workspace)
188
+ if _get_section_value(updated, section, "workspace") != workspace:
189
+ raise ValueError(f"the CLI cannot change the [{section}] section of {path}")
190
+
191
+ os.makedirs(os.path.dirname(path), exist_ok=True)
192
+ with open(path, "w") as config_file:
193
+ config_file.write(updated)
194
+ return path
195
+
158
196
  @classmethod
159
197
  def has_default_file(cls):
160
198
  """Check if a configuration file exists."""
@@ -249,6 +287,36 @@ class ConfigReader(ConfigFileReader):
249
287
  setattr(opts, k, v)
250
288
 
251
289
 
290
+ def _get_section_value(content, section, key):
291
+ """Get `key` from the INI `section` of `content`, or None if it does not parse."""
292
+ parser = configparser.ConfigParser(interpolation=None)
293
+ parser.optionxform = str
294
+ try:
295
+ parser.read_string(content)
296
+ except configparser.Error:
297
+ return None
298
+ return parser.get(section, key, fallback=None)
299
+
300
+
301
+ def _set_section_value(content, section, key, value):
302
+ """Set `key` in the INI `section` of `content`, preserving comments and layout."""
303
+ line = f"{key} = {value}"
304
+ header = re.search(rf"^\[{re.escape(section)}\].*$", content, re.MULTILINE)
305
+ if not header:
306
+ separator = "\n\n" if content.strip() else ""
307
+ return f"{content.rstrip()}{separator}[{section}]\n{line}\n"
308
+
309
+ next_header = re.compile(r"^\[", re.MULTILINE).search(content, header.end())
310
+ body_end = next_header.start() if next_header else len(content)
311
+ body = content[header.end() : body_end]
312
+ entry = re.compile(rf"^{re.escape(key)}[ \t]*[=:].*$", re.MULTILINE)
313
+ if entry.search(body):
314
+ body = entry.sub(lambda _: line, body, count=1)
315
+ else:
316
+ body = f"\n{line}{body}"
317
+ return content[: header.end()] + body + content[body_end:]
318
+
319
+
252
320
  class CredentialsSchema:
253
321
  """Schema for credentials configuration."""
254
322
 
@@ -11,7 +11,7 @@ from click.core import ParameterSource
11
11
  from cloudsmith_cli.cli import validators
12
12
  from cloudsmith_cli.core.utils import ColorMode, TTYMode, color_enabled
13
13
 
14
- from ..core import update_check
14
+ from ..core import keyring, update_check
15
15
  from ..core.certificate_errors import describe_certificate_error
16
16
  from ..core.credentials.chain import CredentialProviderChain
17
17
  from ..core.credentials.models import CredentialContext
@@ -118,6 +118,66 @@ def common_package_action_options(f):
118
118
  return wrapper
119
119
 
120
120
 
121
+ def _absolute_path(ctx, param, value):
122
+ """Expand ``~`` and environment variables in *value* and make it absolute."""
123
+ if not value:
124
+ return value
125
+ return os.path.abspath(os.path.expanduser(os.path.expandvars(value)))
126
+
127
+
128
+ def keyring_options(f):
129
+ """Add hidden options that read the keyring settings from the environment."""
130
+
131
+ @click.option(
132
+ "--no-keyring-env",
133
+ "no_keyring",
134
+ envvar="CLOUDSMITH_NO_KEYRING",
135
+ hidden=True,
136
+ )
137
+ @click.option(
138
+ "--keyring-backend-env",
139
+ "keyring_backend",
140
+ envvar="CLOUDSMITH_KEYRING_BACKEND",
141
+ hidden=True,
142
+ )
143
+ @click.option(
144
+ "--keyring-key-env",
145
+ "keyring_key",
146
+ envvar="CLOUDSMITH_KEYRING_KEY",
147
+ hidden=True,
148
+ )
149
+ @click.option(
150
+ "--keyring-file-path-env",
151
+ "keyring_file_path",
152
+ envvar="CLOUDSMITH_KEYRING_FILE_PATH",
153
+ callback=_absolute_path,
154
+ hidden=True,
155
+ )
156
+ @click.option(
157
+ "--keyring-dir-env",
158
+ "keyring_dir",
159
+ envvar="CLOUDSMITH_KEYRING_DIR",
160
+ callback=_absolute_path,
161
+ hidden=True,
162
+ )
163
+ @click.pass_context
164
+ @functools.wraps(f)
165
+ def wrapper(ctx, *args, **kwargs):
166
+ keyring.configure(
167
+ keyring.KeyringSettings(
168
+ no_keyring=(kwargs.pop("no_keyring") or "").strip().lower()
169
+ in ("1", "true", "yes"),
170
+ backend=kwargs.pop("keyring_backend"),
171
+ key=kwargs.pop("keyring_key"),
172
+ file_path=kwargs.pop("keyring_file_path"),
173
+ directory=kwargs.pop("keyring_dir"),
174
+ )
175
+ )
176
+ return ctx.invoke(f, *args, **kwargs)
177
+
178
+ return wrapper
179
+
180
+
121
181
  def common_cli_config_options(f):
122
182
  """Add common CLI config options to commands."""
123
183
 
@@ -39,14 +39,19 @@ class IntOrWildcard(click.ParamType):
39
39
  self.fail(f"{value!r} is not a valid integer or '*'", param, ctx)
40
40
 
41
41
 
42
- def transform_api_header_authorization(param, value):
42
+ def _option_hint(param: str) -> str:
43
+ """Return the Click error hint for the option that has the param name."""
44
+ return f"'--{param.replace('_', '-')}'"
45
+
46
+
47
+ def transform_api_header_authorization(param: str, value: str) -> str:
43
48
  """Transform a username:password value into a base64 string."""
44
49
  try:
45
50
  username, password = value.split(":", 1)
46
51
  except ValueError:
47
52
  raise click.BadParameter(
48
53
  "Authorization header needs to be Authorization=username:password",
49
- param=param,
54
+ param_hint=_option_hint(param),
50
55
  )
51
56
 
52
57
  value = f"{username.strip()}:{password}"
@@ -57,7 +62,7 @@ def transform_api_header_authorization(param, value):
57
62
  API_HEADER_TRANSFORMS["Authorization"] = transform_api_header_authorization
58
63
 
59
64
 
60
- def validate_api_headers(param, value):
65
+ def validate_api_headers(param: str, value: str | None) -> dict[str, str] | None:
61
66
  """Validate that API headers is a CSV of k=v pairs."""
62
67
  # pylint: disable=unused-argument
63
68
  if not value:
@@ -73,7 +78,7 @@ def validate_api_headers(param, value):
73
78
  if bad_header == k:
74
79
  raise click.BadParameter(
75
80
  f"{bad_header} is not an allowed header",
76
- param=param,
81
+ param_hint=_option_hint(param),
77
82
  )
78
83
 
79
84
  if k in API_HEADER_TRANSFORMS:
@@ -81,7 +86,8 @@ def validate_api_headers(param, value):
81
86
  v = transform_func(param, v)
82
87
  except ValueError:
83
88
  raise click.BadParameter(
84
- "Values need to be a CSV of key=value pairs", param=param
89
+ "Values need to be a CSV of key=value pairs",
90
+ param_hint=_option_hint(param),
85
91
  )
86
92
 
87
93
  headers[k] = v
@@ -1,6 +1,7 @@
1
1
  import cloudsmith_api
2
2
  import pytest
3
3
 
4
+ from cloudsmith_cli.core import keyring as core_keyring
4
5
  from cloudsmith_cli.core import session, telemetry
5
6
 
6
7
  #: A syntactically valid DSN that is never contacted: tests that report use
@@ -19,6 +20,12 @@ def no_real_error_reports(monkeypatch):
19
20
  monkeypatch.setattr(telemetry, "_transport", None)
20
21
 
21
22
 
23
+ @pytest.fixture(autouse=True)
24
+ def default_keyring_settings():
25
+ """Start each test with the default keyring settings."""
26
+ core_keyring.configure(core_keyring.KeyringSettings())
27
+
28
+
22
29
  @pytest.fixture()
23
30
  def telemetry_config_dir(monkeypatch, tmp_path_factory):
24
31
  """An empty config search path, isolated from the developer's config.ini.
@@ -3,6 +3,7 @@
3
3
  import contextlib
4
4
  import http.client
5
5
  import json
6
+ from collections.abc import Iterator
6
7
 
7
8
  from cloudsmith_api.rest import ApiException as _ApiException
8
9
 
@@ -37,8 +38,26 @@ class ApiException(Exception):
37
38
  return f"{self.status} - {self.detail or self.status_description}"
38
39
 
39
40
 
41
+ def error_detail_and_fields(
42
+ data: object,
43
+ ) -> tuple[str | None, dict[str, object] | None]:
44
+ """Get the detail and the fields from a parsed API error body of any JSON shape."""
45
+ if isinstance(data, dict):
46
+ detail = data.get("detail")
47
+ fields = data.get("fields")
48
+ return (
49
+ None if detail is None else str(detail),
50
+ fields if isinstance(fields, dict) else None,
51
+ )
52
+ if isinstance(data, list):
53
+ return " ".join(item for item in data if isinstance(item, str)) or None, None
54
+ if isinstance(data, str):
55
+ return data or None, None
56
+ return None, None
57
+
58
+
40
59
  @contextlib.contextmanager
41
- def catch_raise_api_exception():
60
+ def catch_raise_api_exception() -> Iterator[None]:
42
61
  """Context manager that translates upstream API exceptions."""
43
62
  try:
44
63
  yield
@@ -48,10 +67,7 @@ def catch_raise_api_exception():
48
67
 
49
68
  if exc.body:
50
69
  try:
51
- # pylint: disable=no-member
52
- data = json.loads(exc.body)
53
- detail = data.get("detail", None)
54
- fields = data.get("fields", None)
70
+ detail, fields = error_detail_and_fields(json.loads(exc.body))
55
71
  except ValueError:
56
72
  pass
57
73
 
@@ -9,6 +9,19 @@ import cloudsmith_api
9
9
  from ..rest import RestClient
10
10
 
11
11
 
12
+ def _basic_auth_credentials(encoded: str) -> tuple[str, str]:
13
+ """Decode a Basic auth token into a username and password."""
14
+ try:
15
+ decoded = base64.b64decode(encoded, validate=True).decode("utf-8")
16
+ username, password = decoded.split(":", 1)
17
+ except ValueError as exc:
18
+ raise click.BadParameter(
19
+ "Authorization header needs to be Authorization=username:password",
20
+ param_hint="'--api-headers'",
21
+ ) from exc
22
+ return username, password
23
+
24
+
12
25
  def initialise_api(
13
26
  debug=False,
14
27
  host=None,
@@ -24,7 +37,7 @@ def initialise_api(
24
37
  error_retry_backoff=None,
25
38
  error_retry_codes=None,
26
39
  error_retry_cb=None,
27
- ):
40
+ ) -> cloudsmith_api.Configuration:
28
41
  """Initialise the cloudsmith_api.Configuration."""
29
42
  # FIXME: pylint: disable=too-many-arguments
30
43
  config = cloudsmith_api.Configuration()
@@ -60,9 +73,7 @@ def initialise_api(
60
73
  if auth_header and " " in auth_header:
61
74
  auth_type, encoded = auth_header.split(" ", 1)
62
75
  if auth_type == "Basic":
63
- decoded = base64.b64decode(encoded)
64
- values = decoded.decode("utf-8")
65
- config.username, config.password = values.split(":")
76
+ config.username, config.password = _basic_auth_credentials(encoded)
66
77
 
67
78
  if config.debug:
68
79
  click.echo("Username and password config values set")
@@ -43,7 +43,9 @@ def get_user_token(login, password, totp_token=None, two_factor_token=None):
43
43
  if e.status == 422 and e.body:
44
44
  try:
45
45
  response_data = json.loads(e.body)
46
- if response_data.get("two_factor_required"):
46
+ if isinstance(response_data, dict) and response_data.get(
47
+ "two_factor_required"
48
+ ):
47
49
  two_factor_token = response_data.get("two_factor_token")
48
50
  # Raise custom exception for 2FA requirement
49
51
  raise TwoFactorRequiredException(two_factor_token)