cloudsmith-cli 1.20.0__py3-none-any.whl

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 (130) hide show
  1. cloudsmith_cli/__init__.py +10 -0
  2. cloudsmith_cli/__main__.py +8 -0
  3. cloudsmith_cli/cli/__init__.py +1 -0
  4. cloudsmith_cli/cli/command.py +160 -0
  5. cloudsmith_cli/cli/commands/__init__.py +33 -0
  6. cloudsmith_cli/cli/commands/auth.py +173 -0
  7. cloudsmith_cli/cli/commands/check.py +129 -0
  8. cloudsmith_cli/cli/commands/copy.py +98 -0
  9. cloudsmith_cli/cli/commands/credential_helper/__init__.py +39 -0
  10. cloudsmith_cli/cli/commands/credential_helper/docker.py +66 -0
  11. cloudsmith_cli/cli/commands/credential_helper/manage.py +299 -0
  12. cloudsmith_cli/cli/commands/delete.py +67 -0
  13. cloudsmith_cli/cli/commands/dependencies.py +108 -0
  14. cloudsmith_cli/cli/commands/docs.py +16 -0
  15. cloudsmith_cli/cli/commands/download.py +620 -0
  16. cloudsmith_cli/cli/commands/entitlements.py +819 -0
  17. cloudsmith_cli/cli/commands/help_.py +12 -0
  18. cloudsmith_cli/cli/commands/list_.py +317 -0
  19. cloudsmith_cli/cli/commands/login.py +99 -0
  20. cloudsmith_cli/cli/commands/logout.py +151 -0
  21. cloudsmith_cli/cli/commands/main.py +73 -0
  22. cloudsmith_cli/cli/commands/mcp.py +523 -0
  23. cloudsmith_cli/cli/commands/metadata.py +503 -0
  24. cloudsmith_cli/cli/commands/metrics/__init__.py +2 -0
  25. cloudsmith_cli/cli/commands/metrics/command.py +20 -0
  26. cloudsmith_cli/cli/commands/metrics/entitlements.py +148 -0
  27. cloudsmith_cli/cli/commands/metrics/packages.py +134 -0
  28. cloudsmith_cli/cli/commands/move.py +111 -0
  29. cloudsmith_cli/cli/commands/policy/__init__.py +3 -0
  30. cloudsmith_cli/cli/commands/policy/command.py +20 -0
  31. cloudsmith_cli/cli/commands/policy/deny.py +248 -0
  32. cloudsmith_cli/cli/commands/policy/license.py +335 -0
  33. cloudsmith_cli/cli/commands/policy/vulnerability.py +322 -0
  34. cloudsmith_cli/cli/commands/push.py +1323 -0
  35. cloudsmith_cli/cli/commands/quarantine.py +148 -0
  36. cloudsmith_cli/cli/commands/quota/__init__.py +2 -0
  37. cloudsmith_cli/cli/commands/quota/command.py +20 -0
  38. cloudsmith_cli/cli/commands/quota/history.py +122 -0
  39. cloudsmith_cli/cli/commands/quota/quota.py +107 -0
  40. cloudsmith_cli/cli/commands/repos.py +330 -0
  41. cloudsmith_cli/cli/commands/resync.py +90 -0
  42. cloudsmith_cli/cli/commands/status.py +92 -0
  43. cloudsmith_cli/cli/commands/tags.py +375 -0
  44. cloudsmith_cli/cli/commands/tokens.py +318 -0
  45. cloudsmith_cli/cli/commands/upstream.py +479 -0
  46. cloudsmith_cli/cli/commands/vulnerabilities.py +141 -0
  47. cloudsmith_cli/cli/commands/whoami.py +188 -0
  48. cloudsmith_cli/cli/config.py +635 -0
  49. cloudsmith_cli/cli/decorators.py +624 -0
  50. cloudsmith_cli/cli/exceptions.py +215 -0
  51. cloudsmith_cli/cli/metadata_common.py +146 -0
  52. cloudsmith_cli/cli/saml.py +109 -0
  53. cloudsmith_cli/cli/table.py +59 -0
  54. cloudsmith_cli/cli/types.py +14 -0
  55. cloudsmith_cli/cli/utils.py +267 -0
  56. cloudsmith_cli/cli/validators.py +378 -0
  57. cloudsmith_cli/cli/webserver.py +263 -0
  58. cloudsmith_cli/core/__init__.py +1 -0
  59. cloudsmith_cli/core/api/__init__.py +1 -0
  60. cloudsmith_cli/core/api/distros.py +31 -0
  61. cloudsmith_cli/core/api/entitlements.py +130 -0
  62. cloudsmith_cli/core/api/exceptions.py +57 -0
  63. cloudsmith_cli/core/api/files.py +131 -0
  64. cloudsmith_cli/core/api/init.py +109 -0
  65. cloudsmith_cli/core/api/metadata.py +217 -0
  66. cloudsmith_cli/core/api/metrics.py +78 -0
  67. cloudsmith_cli/core/api/orgs.py +201 -0
  68. cloudsmith_cli/core/api/packages.py +309 -0
  69. cloudsmith_cli/core/api/quota.py +64 -0
  70. cloudsmith_cli/core/api/rates.py +28 -0
  71. cloudsmith_cli/core/api/repos.py +81 -0
  72. cloudsmith_cli/core/api/status.py +27 -0
  73. cloudsmith_cli/core/api/upstreams.py +72 -0
  74. cloudsmith_cli/core/api/user.py +109 -0
  75. cloudsmith_cli/core/api/version.py +15 -0
  76. cloudsmith_cli/core/api/vulnerabilities.py +230 -0
  77. cloudsmith_cli/core/cache_utils.py +160 -0
  78. cloudsmith_cli/core/config.py +140 -0
  79. cloudsmith_cli/core/credentials/__init__.py +0 -0
  80. cloudsmith_cli/core/credentials/chain.py +69 -0
  81. cloudsmith_cli/core/credentials/models.py +44 -0
  82. cloudsmith_cli/core/credentials/oidc/__init__.py +6 -0
  83. cloudsmith_cli/core/credentials/oidc/cache.py +220 -0
  84. cloudsmith_cli/core/credentials/oidc/detectors/__init__.py +122 -0
  85. cloudsmith_cli/core/credentials/oidc/detectors/aws.py +85 -0
  86. cloudsmith_cli/core/credentials/oidc/detectors/azure_devops.py +70 -0
  87. cloudsmith_cli/core/credentials/oidc/detectors/base.py +26 -0
  88. cloudsmith_cli/core/credentials/oidc/detectors/bitbucket_pipelines.py +35 -0
  89. cloudsmith_cli/core/credentials/oidc/detectors/circleci.py +40 -0
  90. cloudsmith_cli/core/credentials/oidc/detectors/generic.py +42 -0
  91. cloudsmith_cli/core/credentials/oidc/detectors/github_actions.py +64 -0
  92. cloudsmith_cli/core/credentials/oidc/detectors/gitlab_ci.py +49 -0
  93. cloudsmith_cli/core/credentials/oidc/exchange.py +87 -0
  94. cloudsmith_cli/core/credentials/provider.py +17 -0
  95. cloudsmith_cli/core/credentials/providers/__init__.py +15 -0
  96. cloudsmith_cli/core/credentials/providers/cli_flag.py +24 -0
  97. cloudsmith_cli/core/credentials/providers/credentials_file.py +24 -0
  98. cloudsmith_cli/core/credentials/providers/env_var.py +24 -0
  99. cloudsmith_cli/core/credentials/providers/keyring_provider.py +59 -0
  100. cloudsmith_cli/core/credentials/providers/oidc_provider.py +115 -0
  101. cloudsmith_cli/core/download.py +594 -0
  102. cloudsmith_cli/core/keyring.py +171 -0
  103. cloudsmith_cli/core/mcp/__init__.py +0 -0
  104. cloudsmith_cli/core/mcp/data.py +17 -0
  105. cloudsmith_cli/core/mcp/server.py +786 -0
  106. cloudsmith_cli/core/pagination.py +131 -0
  107. cloudsmith_cli/core/ratelimits.py +88 -0
  108. cloudsmith_cli/core/rest.py +255 -0
  109. cloudsmith_cli/core/utils.py +95 -0
  110. cloudsmith_cli/core/version.py +20 -0
  111. cloudsmith_cli/credential_helpers/__init__.py +7 -0
  112. cloudsmith_cli/credential_helpers/backends.py +41 -0
  113. cloudsmith_cli/credential_helpers/common.py +111 -0
  114. cloudsmith_cli/credential_helpers/custom_domains.py +281 -0
  115. cloudsmith_cli/credential_helpers/docker/__init__.py +4 -0
  116. cloudsmith_cli/credential_helpers/docker/installer.py +349 -0
  117. cloudsmith_cli/credential_helpers/docker/runtime.py +117 -0
  118. cloudsmith_cli/credential_helpers/launchers.py +175 -0
  119. cloudsmith_cli/data/VERSION +1 -0
  120. cloudsmith_cli/data/config.ini +23 -0
  121. cloudsmith_cli/data/credentials.ini +14 -0
  122. cloudsmith_cli/templates/__init__.py +3 -0
  123. cloudsmith_cli/templates/auth_error.html +45 -0
  124. cloudsmith_cli/templates/auth_success.html +37 -0
  125. cloudsmith_cli-1.20.0.dist-info/METADATA +610 -0
  126. cloudsmith_cli-1.20.0.dist-info/RECORD +130 -0
  127. cloudsmith_cli-1.20.0.dist-info/WHEEL +5 -0
  128. cloudsmith_cli-1.20.0.dist-info/entry_points.txt +2 -0
  129. cloudsmith_cli-1.20.0.dist-info/licenses/LICENSE +201 -0
  130. cloudsmith_cli-1.20.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,10 @@
1
+ """Cloudsmith CLI."""
2
+
3
+ import warnings
4
+
5
+ import click
6
+ import urllib3
7
+
8
+ click.disable_unicode_literals_warning = True
9
+ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
10
+ warnings.filterwarnings("ignore", category=ResourceWarning)
@@ -0,0 +1,8 @@
1
+ """Cloudsmith CLI - Main script."""
2
+
3
+ from .cli.commands.main import main
4
+
5
+ if __name__ == "__main__":
6
+ # Disable false positive for parameters handled by click.
7
+ # pylint: disable=no-value-for-parameter
8
+ main()
@@ -0,0 +1 @@
1
+ """CLI-specific functionality."""
@@ -0,0 +1,160 @@
1
+ """CLI - Group/Command classes."""
2
+
3
+ from collections import OrderedDict
4
+
5
+ import click.exceptions
6
+ from click_didyoumean import DYMGroup
7
+
8
+
9
+ def _is_json_output_requested(exception):
10
+ """Determine if JSON output was requested, checking context and argv."""
11
+ # Check context if available
12
+ ctx = getattr(exception, "ctx", None)
13
+ if ctx and ctx.params:
14
+ fmt = ctx.params.get("output")
15
+ if fmt in ("json", "pretty_json"):
16
+ return True
17
+
18
+ # Fallback: check sys.argv for output format flags
19
+ import sys
20
+
21
+ argv = sys.argv
22
+
23
+ if "--output-format=json" in argv or "--output-format=pretty_json" in argv:
24
+ return True
25
+
26
+ for idx, arg in enumerate(argv):
27
+ if arg in ("-F", "--output-format") and idx + 1 < len(argv):
28
+ if argv[idx + 1] in ("json", "pretty_json"):
29
+ return True
30
+
31
+ return False
32
+
33
+
34
+ def _format_click_exception_as_json(exception):
35
+ """Format a ClickException as a JSON error dict."""
36
+ return {
37
+ "detail": exception.format_message(),
38
+ "meta": {
39
+ "code": exception.exit_code,
40
+ "description": "Usage Error",
41
+ },
42
+ "help": {
43
+ "context": "Invalid usage",
44
+ "hint": "Check your command arguments/flags.",
45
+ },
46
+ }
47
+
48
+
49
+ class AliasGroup(DYMGroup):
50
+ """A command group with DYM and alias support."""
51
+
52
+ def __init__(self, *args, **kwargs):
53
+ super().__init__(*args, **kwargs)
54
+ self.aliases = OrderedDict()
55
+ self.inverse = {}
56
+
57
+ def resolve_command(self, ctx, args):
58
+ try:
59
+ return super().resolve_command(ctx, args)
60
+ except click.exceptions.UsageError:
61
+ # Before DYM kicks in, check to see if the command prefix matches
62
+ # exactly one command, then use that instead.
63
+ if args:
64
+ cmd_name = args[0]
65
+ cmds = self.list_commands(ctx)
66
+ matched = [cmd for cmd in cmds if cmd.startswith(cmd_name)]
67
+ if len(matched) == 1 and len(cmd_name) > 1:
68
+ args[0] = matched[0]
69
+ return super().resolve_command(ctx, args)
70
+
71
+ raise
72
+
73
+ def list_commands(self, ctx):
74
+ commands = super().list_commands(ctx)
75
+
76
+ if getattr(ctx, "showing_help", False):
77
+ for k, v in enumerate(commands):
78
+ try:
79
+ commands[k] = f"{v}|{'|'.join(self.aliases[v])}"
80
+ except KeyError:
81
+ pass
82
+
83
+ return commands
84
+
85
+ for k in self.inverse:
86
+ commands.append(k)
87
+
88
+ return commands
89
+
90
+ def get_command(self, ctx, cmd_name):
91
+ if getattr(ctx, "showing_help", False):
92
+ if "|" in cmd_name:
93
+ cmd_name = cmd_name.split("|")[0]
94
+
95
+ try:
96
+ cmd_name = self.inverse[cmd_name]
97
+ except KeyError:
98
+ pass
99
+
100
+ return super().get_command(ctx, cmd_name)
101
+
102
+ def command(self, *args, **kwargs):
103
+ def decorator(f):
104
+ # pylint: disable=missing-docstring
105
+ aliases = kwargs.pop("aliases", [])
106
+ cmd = super(AliasGroup, self).command(*args, **kwargs)(f)
107
+
108
+ if aliases:
109
+ self.aliases[cmd.name] = aliases
110
+ for alias in aliases:
111
+ self.inverse[alias] = cmd.name
112
+
113
+ return cmd
114
+
115
+ return decorator
116
+
117
+ def group(self, *args, **kwargs):
118
+ def decorator(f):
119
+ # pylint: disable=missing-docstring
120
+ aliases = kwargs.pop("aliases", [])
121
+ cmd = super(AliasGroup, self).group(*args, **kwargs)(f)
122
+
123
+ if aliases:
124
+ self.aliases[cmd.name] = aliases
125
+ for alias in aliases:
126
+ self.inverse[alias] = cmd.name
127
+
128
+ return cmd
129
+
130
+ return decorator
131
+
132
+ def format_commands(self, ctx, formatter):
133
+ ctx.showing_help = True
134
+ return super().format_commands(ctx, formatter)
135
+
136
+ def main(self, *args, **kwargs):
137
+ """Override main to intercept exceptions and format as JSON if requested."""
138
+ import sys
139
+
140
+ original_standalone_mode = kwargs.get("standalone_mode", True)
141
+ kwargs["standalone_mode"] = False
142
+
143
+ try:
144
+ return super().main(*args, **kwargs)
145
+ except click.exceptions.Abort:
146
+ if not original_standalone_mode:
147
+ raise
148
+ click.echo("Aborted!", err=True)
149
+ sys.exit(1)
150
+ except click.exceptions.ClickException as e:
151
+ if _is_json_output_requested(e):
152
+ import json
153
+
154
+ click.echo(json.dumps(_format_click_exception_as_json(e), indent=4))
155
+ sys.exit(e.exit_code)
156
+
157
+ if not original_standalone_mode:
158
+ raise
159
+ e.show()
160
+ sys.exit(e.exit_code)
@@ -0,0 +1,33 @@
1
+ """CLI/Commands - Import all commands."""
2
+
3
+ from . import (
4
+ auth,
5
+ check,
6
+ copy,
7
+ credential_helper,
8
+ delete,
9
+ dependencies,
10
+ docs,
11
+ download,
12
+ entitlements,
13
+ help_,
14
+ list_,
15
+ login,
16
+ logout,
17
+ mcp,
18
+ metadata,
19
+ metrics,
20
+ move,
21
+ policy,
22
+ push,
23
+ quarantine,
24
+ quota,
25
+ repos,
26
+ resync,
27
+ status,
28
+ tags,
29
+ tokens,
30
+ upstream,
31
+ vulnerabilities,
32
+ whoami,
33
+ )
@@ -0,0 +1,173 @@
1
+ """CLI/Commands - Authenticate the user."""
2
+
3
+ import webbrowser
4
+
5
+ import click
6
+
7
+ from .. import decorators, utils, validators
8
+ from ..exceptions import handle_api_exceptions
9
+ from ..saml import create_configured_session, get_idp_url
10
+ from ..webserver import AuthenticationWebRequestHandler, AuthenticationWebServer
11
+ from .main import main
12
+ from .tokens import create, request_api_key
13
+
14
+ # Authentication server configuration
15
+ AUTH_SERVER_HOST = "127.0.0.1"
16
+ AUTH_SERVER_PORT = 12400
17
+
18
+
19
+ def _perform_saml_authentication(
20
+ opts, owner, enable_token_creation=False, use_stderr=False
21
+ ):
22
+ """Perform SAML authentication via web browser and local web server."""
23
+ session = create_configured_session(opts)
24
+ api_host = opts.api_config.host
25
+
26
+ idp_url = get_idp_url(api_host, owner, session=session)
27
+
28
+ click.echo(
29
+ f"Opening your organization's SAML IDP URL in your browser: {click.style(idp_url, bold=True)}",
30
+ err=use_stderr,
31
+ )
32
+ click.echo(err=use_stderr)
33
+ webbrowser.open(idp_url)
34
+
35
+ click.echo("Starting webserver to begin authentication ... ", err=use_stderr)
36
+
37
+ auth_server = AuthenticationWebServer(
38
+ (AUTH_SERVER_HOST, AUTH_SERVER_PORT),
39
+ AuthenticationWebRequestHandler,
40
+ owner=owner,
41
+ session=session,
42
+ debug=opts.debug,
43
+ refresh_api_on_success=enable_token_creation,
44
+ api_opts=opts.api_config,
45
+ )
46
+
47
+ auth_server.handle_request()
48
+
49
+
50
+ @main.command(aliases=["auth"])
51
+ @click.option(
52
+ "-o",
53
+ "--owner",
54
+ metavar="OWNER",
55
+ required=True,
56
+ callback=validators.validate_owner,
57
+ prompt=True,
58
+ help="The name of the Cloudsmith organization to authenticate with.",
59
+ )
60
+ @click.option(
61
+ "-t",
62
+ "--token",
63
+ default=False,
64
+ is_flag=True,
65
+ help="[DEPRECATED: Use --request-api-key] Retrieve a user API token after successful authentication.",
66
+ )
67
+ @click.option(
68
+ "-f",
69
+ "--force",
70
+ default=False,
71
+ is_flag=True,
72
+ help="[DEPRECATED: Use --request-api-key] Force refresh of user API token without prompts.",
73
+ )
74
+ @click.option(
75
+ "--save-config",
76
+ default=False,
77
+ is_flag=True,
78
+ help="Save the new API key to your configuration files.",
79
+ )
80
+ @click.option(
81
+ "--json",
82
+ default=False,
83
+ is_flag=True,
84
+ help="[DEPRECATED: Use --output-format json] Output token details in JSON format.",
85
+ )
86
+ @click.option(
87
+ "--request-api-key",
88
+ "request_api_key_flag",
89
+ default=False,
90
+ is_flag=True,
91
+ help="Retrieve API token (auto-creates or auto-rotates, no prompts). "
92
+ "Warning: If token exists, this will rotate it and invalidate the old key.",
93
+ )
94
+ @decorators.common_cli_config_options
95
+ @decorators.common_cli_output_options
96
+ @decorators.initialise_api
97
+ @click.pass_context
98
+ def authenticate(
99
+ ctx, opts, owner, token, force, save_config, json, request_api_key_flag
100
+ ):
101
+ """Authenticate to Cloudsmith using the org's SAML setup."""
102
+ # Validate mutual exclusivity
103
+ if request_api_key_flag and (token or force):
104
+ raise click.UsageError(
105
+ "--request-api-key cannot be used with --token or --force. "
106
+ "Use --request-api-key alone for fully automated token retrieval."
107
+ )
108
+
109
+ # Determine if we should redirect info messages to stderr
110
+ use_stderr = request_api_key_flag or json or utils.should_use_stderr(opts)
111
+
112
+ if token:
113
+ click.secho(
114
+ "DEPRECATION WARNING: The `--token` flag is deprecated and will be removed in a future release. "
115
+ "Please use `--request-api-key` instead.",
116
+ fg="yellow",
117
+ err=True,
118
+ )
119
+
120
+ if force:
121
+ click.secho(
122
+ "DEPRECATION WARNING: The `--force` flag is deprecated and will be removed in a future release. "
123
+ "Please use `--request-api-key` instead (force is implied).",
124
+ fg="yellow",
125
+ err=True,
126
+ )
127
+
128
+ if json and not utils.should_use_stderr(opts):
129
+ click.secho(
130
+ "DEPRECATION WARNING: The `--json` flag is deprecated and will be removed in a future release. "
131
+ "Please use `--output-format json` instead.",
132
+ fg="yellow",
133
+ err=True,
134
+ )
135
+
136
+ owner = owner[0].strip("'[]'")
137
+
138
+ click.echo(
139
+ f"Beginning authentication for the {click.style(owner, bold=True)} org ... ",
140
+ err=use_stderr,
141
+ )
142
+
143
+ # Determine if we need to refresh API after SSO (required for token operations)
144
+ enable_token_creation = token or request_api_key_flag
145
+
146
+ context_message = "Failed to authenticate via SSO!"
147
+ with handle_api_exceptions(ctx, opts=opts, context_msg=context_message):
148
+ _perform_saml_authentication(
149
+ opts,
150
+ owner,
151
+ enable_token_creation=enable_token_creation,
152
+ use_stderr=use_stderr,
153
+ )
154
+
155
+ if request_api_key_flag:
156
+ # Non-interactive token retrieval
157
+ new_token = request_api_key(ctx, opts, save_config=save_config)
158
+
159
+ if not new_token:
160
+ raise click.ClickException(
161
+ "Failed to retrieve API token. No token was returned."
162
+ )
163
+
164
+ # Check if JSON output is requested
165
+ if utils.maybe_print_as_json(opts, new_token):
166
+ return
167
+
168
+ # Default: output only the raw token value to stdout
169
+ click.echo(new_token.key)
170
+ return
171
+
172
+ if token:
173
+ ctx.invoke(create, opts=opts, save_config=save_config, force=force, json=json)
@@ -0,0 +1,129 @@
1
+ """CLI/Commands - Get an API token."""
2
+
3
+ import click
4
+ import cloudsmith_api
5
+ import semver
6
+
7
+ from ...core.api.rates import get_rate_limits
8
+ from ...core.api.status import get_status
9
+ from ...core.api.version import get_version as get_api_version_info
10
+ from .. import command, decorators, utils
11
+ from ..exceptions import handle_api_exceptions
12
+ from ..utils import maybe_spinner
13
+ from .main import main
14
+
15
+
16
+ @main.group(cls=command.AliasGroup)
17
+ @decorators.common_cli_config_options
18
+ @decorators.common_cli_output_options
19
+ @decorators.common_api_auth_options
20
+ @click.pass_context
21
+ def check(ctx, opts): # pylint: disable=unused-argument
22
+ """Check rate limits and service status."""
23
+
24
+
25
+ @check.command(aliases=["limits"])
26
+ @decorators.common_cli_config_options
27
+ @decorators.common_cli_output_options
28
+ @decorators.initialise_api
29
+ @click.pass_context
30
+ def rates(ctx, opts):
31
+ """Check current API rate limits."""
32
+ use_stderr = utils.should_use_stderr(opts)
33
+ click.echo("Retrieving rate limits ... ", nl=False, err=use_stderr)
34
+
35
+ context_msg = "Failed to retrieve status!"
36
+ with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
37
+ with maybe_spinner(opts):
38
+ resources_limits = get_rate_limits()
39
+
40
+ click.secho("OK", fg="green", err=use_stderr)
41
+
42
+ if utils.maybe_print_as_json(opts, resources_limits):
43
+ return
44
+
45
+ headers = ["Resource", "Throttled", "Remaining", "Interval (Seconds)", "Reset"]
46
+
47
+ rows = []
48
+ for resource, limits in resources_limits.items():
49
+ rows.append(
50
+ [
51
+ click.style(resource, fg="cyan"),
52
+ click.style(
53
+ "Yes" if limits.throttled else "No",
54
+ fg="red" if limits.throttled else "green",
55
+ ),
56
+ "%(remaining)s/%(limit)s"
57
+ % {
58
+ "remaining": click.style(str(limits.remaining), fg="yellow"),
59
+ "limit": click.style(str(limits.limit), fg="yellow"),
60
+ },
61
+ click.style(str(limits.interval), fg="blue"),
62
+ click.style(str(limits.reset), fg="magenta"),
63
+ ]
64
+ )
65
+
66
+ if resources_limits:
67
+ click.echo()
68
+ utils.pretty_print_table(headers, rows)
69
+
70
+ click.echo()
71
+
72
+ num_results = len(resources_limits)
73
+ list_suffix = "resource%s" % ("s" if num_results != 1 else "")
74
+ utils.pretty_print_list_info(num_results=num_results, suffix=list_suffix)
75
+
76
+
77
+ @check.command()
78
+ @decorators.common_cli_config_options
79
+ @decorators.common_cli_output_options
80
+ @decorators.initialise_api
81
+ @click.pass_context
82
+ def service(ctx, opts):
83
+ """Check the status of the Cloudsmith service."""
84
+ use_stderr = utils.should_use_stderr(opts)
85
+ click.echo("Retrieving service status ... ", nl=False, err=use_stderr)
86
+
87
+ context_msg = "Failed to retrieve status!"
88
+ with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg):
89
+ with maybe_spinner(opts):
90
+ status, version = get_status(with_version=True)
91
+
92
+ click.secho("OK", fg="green", err=use_stderr)
93
+
94
+ config = cloudsmith_api.Configuration()
95
+
96
+ data = {
97
+ "endpoint": config.host,
98
+ "status": status,
99
+ "version": version,
100
+ }
101
+
102
+ if utils.maybe_print_as_json(opts, data):
103
+ return
104
+
105
+ click.echo()
106
+ click.echo(f"The service endpoint is: {click.style(config.host, bold=True)}")
107
+ click.echo(f"The service status is: {click.style(status, bold=True)}")
108
+ click.echo(
109
+ f"The service version is: {click.style(version, bold=True)} ",
110
+ nl=False,
111
+ )
112
+
113
+ api_version = get_api_version_info()
114
+
115
+ if semver.Version.parse(version).compare(api_version) > 0:
116
+ click.secho("(maybe out-of-date)", fg="yellow")
117
+
118
+ click.echo()
119
+ click.secho(
120
+ f"The API library used by this CLI tool is built against service version: {click.style(api_version, bold=True)}",
121
+ fg="yellow",
122
+ )
123
+ else:
124
+ click.secho("(up-to-date)", fg="green")
125
+
126
+ click.echo()
127
+ click.secho(
128
+ "The API library used by this CLI tool seems to be up-to-date.", fg="green"
129
+ )
@@ -0,0 +1,98 @@
1
+ """CLI/Commands - List objects."""
2
+
3
+ import click
4
+
5
+ from ...core.api.packages import copy_package
6
+ from .. import decorators, utils, validators
7
+ from ..exceptions import handle_api_exceptions
8
+ from ..utils import maybe_spinner
9
+ from .main import main
10
+ from .push import wait_for_package_sync
11
+
12
+
13
+ @main.command(aliases=["cp"])
14
+ @decorators.common_cli_config_options
15
+ @decorators.common_cli_output_options
16
+ @decorators.common_package_action_options
17
+ @decorators.common_api_auth_options
18
+ @decorators.initialise_api
19
+ @click.argument(
20
+ "owner_repo_package",
21
+ metavar="OWNER/REPO/PACKAGE",
22
+ callback=validators.validate_owner_repo_package,
23
+ )
24
+ @click.argument("destination", metavar="DEST")
25
+ @click.pass_context
26
+ def copy(
27
+ ctx,
28
+ opts,
29
+ owner_repo_package,
30
+ destination,
31
+ skip_errors,
32
+ wait_interval,
33
+ no_wait_for_sync,
34
+ sync_attempts,
35
+ ):
36
+ """
37
+ Copy a package to another repository.
38
+
39
+ This requires appropriate permissions for both the source
40
+ repository/package and the destination repository.
41
+
42
+ - OWNER/REPO/PACKAGE: Specify the OWNER namespace (i.e. user or org), the
43
+ REPO name where the package is stored, and the PACKAGE name (slug) of the
44
+ package itself. All separated by a slash.
45
+
46
+ Example: 'your-org/awesome-repo/better-pkg'.
47
+
48
+ - DEST: Specify the DEST (destination) repository to copy the package to.
49
+ This *must* be in the same namespace as the source repository.
50
+
51
+ Example: 'other-repo'
52
+
53
+ Full CLI example:
54
+
55
+ $ cloudsmith cp your-org/awesome-repo/better-pkg other-repo
56
+ """
57
+ owner, source, slug = owner_repo_package
58
+
59
+ use_stderr = utils.should_use_stderr(opts)
60
+
61
+ click.echo(
62
+ "Copying %(slug)s package from %(source)s to %(dest)s ... "
63
+ % {
64
+ "slug": click.style(slug, bold=True),
65
+ "source": click.style(source, bold=True),
66
+ "dest": click.style(destination, bold=True),
67
+ },
68
+ nl=False,
69
+ err=use_stderr,
70
+ )
71
+
72
+ context_msg = "Failed to copy package!"
73
+ with handle_api_exceptions(
74
+ ctx, opts=opts, context_msg=context_msg, reraise_on_error=skip_errors
75
+ ):
76
+ with maybe_spinner(opts):
77
+ _, new_slug = copy_package(
78
+ owner=owner, repo=source, identifier=slug, destination=destination
79
+ )
80
+
81
+ click.secho("OK", fg="green", err=use_stderr)
82
+
83
+ if no_wait_for_sync:
84
+ utils.maybe_print_status_json(opts, {"slug": new_slug, "status": "OK"})
85
+ return
86
+
87
+ wait_for_package_sync(
88
+ ctx=ctx,
89
+ opts=opts,
90
+ owner=owner,
91
+ repo=destination,
92
+ slug=new_slug,
93
+ wait_interval=wait_interval,
94
+ skip_errors=skip_errors,
95
+ attempts=sync_attempts,
96
+ )
97
+
98
+ utils.maybe_print_status_json(opts, {"slug": new_slug, "status": "OK"})
@@ -0,0 +1,39 @@
1
+ # Copyright 2026 Cloudsmith Ltd
2
+ """
3
+ Credential helper commands for Cloudsmith.
4
+
5
+ This module provides credential helper commands for package managers
6
+ that follow their respective credential helper protocols.
7
+ """
8
+
9
+ import click
10
+
11
+ from ..main import main
12
+ from .docker import docker as docker_cmd
13
+ from .manage import install_cmd, list_cmd, uninstall_cmd
14
+
15
+
16
+ @click.group()
17
+ def credential_helper():
18
+ """
19
+ Credential helpers for package managers.
20
+
21
+ These commands provide credentials for package managers like Docker.
22
+ Use ``install`` to set up the on-PATH launcher and configure the package
23
+ manager automatically, or run the runtime command directly for debugging.
24
+
25
+ Examples:
26
+ # Install Docker credential helper
27
+ $ cloudsmith credential-helper install docker
28
+
29
+ # Test Docker credential helper directly
30
+ $ echo "docker.cloudsmith.io" | cloudsmith credential-helper docker
31
+ """
32
+
33
+
34
+ credential_helper.add_command(docker_cmd, name="docker")
35
+ credential_helper.add_command(install_cmd, name="install")
36
+ credential_helper.add_command(uninstall_cmd, name="uninstall")
37
+ credential_helper.add_command(list_cmd, name="list")
38
+
39
+ main.add_command(credential_helper, name="credential-helper")