quickscale-cli 0.87.0__tar.gz → 0.89.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 (85) hide show
  1. {quickscale_cli-0.87.0 → quickscale_cli-0.89.0}/PKG-INFO +2 -2
  2. {quickscale_cli-0.87.0 → quickscale_cli-0.89.0}/pyproject.toml +10 -4
  3. {quickscale_cli-0.87.0 → quickscale_cli-0.89.0}/src/quickscale_cli/_version.py +1 -1
  4. quickscale_cli-0.89.0/src/quickscale_cli/cli_help.py +292 -0
  5. quickscale_cli-0.89.0/src/quickscale_cli/commands/_config.py +419 -0
  6. quickscale_cli-0.89.0/src/quickscale_cli/commands/_context.py +637 -0
  7. quickscale_cli-0.89.0/src/quickscale_cli/commands/_deploy_service_config.py +320 -0
  8. quickscale_cli-0.89.0/src/quickscale_cli/commands/_deploy_steps.py +383 -0
  9. quickscale_cli-0.89.0/src/quickscale_cli/commands/_dr_adapter.py +192 -0
  10. quickscale_cli-0.89.0/src/quickscale_cli/commands/_dr_context.py +201 -0
  11. quickscale_cli-0.89.0/src/quickscale_cli/commands/_dr_env_sync.py +157 -0
  12. quickscale_cli-0.89.0/src/quickscale_cli/commands/_dr_report.py +211 -0
  13. quickscale_cli-0.89.0/src/quickscale_cli/commands/_embedding.py +144 -0
  14. quickscale_cli-0.89.0/src/quickscale_cli/commands/_finalize.py +527 -0
  15. quickscale_cli-0.89.0/src/quickscale_cli/commands/_generation.py +258 -0
  16. quickscale_cli-0.89.0/src/quickscale_cli/commands/_git_ops.py +329 -0
  17. quickscale_cli-0.89.0/src/quickscale_cli/commands/_module_embed.py +542 -0
  18. quickscale_cli-0.89.0/src/quickscale_cli/commands/_module_push.py +140 -0
  19. quickscale_cli-0.89.0/src/quickscale_cli/commands/_module_shared.py +383 -0
  20. quickscale_cli-0.89.0/src/quickscale_cli/commands/_module_update.py +1463 -0
  21. quickscale_cli-0.89.0/src/quickscale_cli/commands/_module_update_guard.py +250 -0
  22. quickscale_cli-0.89.0/src/quickscale_cli/commands/_module_update_identity.py +105 -0
  23. quickscale_cli-0.89.0/src/quickscale_cli/commands/_module_update_io.py +646 -0
  24. quickscale_cli-0.89.0/src/quickscale_cli/commands/_module_update_journal.py +238 -0
  25. quickscale_cli-0.89.0/src/quickscale_cli/commands/_module_update_seal.py +167 -0
  26. quickscale_cli-0.89.0/src/quickscale_cli/commands/_plan_add.py +353 -0
  27. quickscale_cli-0.89.0/src/quickscale_cli/commands/_plan_new_project.py +252 -0
  28. quickscale_cli-0.89.0/src/quickscale_cli/commands/_plan_reconfigure.py +268 -0
  29. quickscale_cli-0.89.0/src/quickscale_cli/commands/_plan_shared.py +182 -0
  30. quickscale_cli-0.89.0/src/quickscale_cli/commands/_plan_validation.py +122 -0
  31. quickscale_cli-0.89.0/src/quickscale_cli/commands/_runtime.py +521 -0
  32. quickscale_cli-0.89.0/src/quickscale_cli/commands/_split_refs.py +114 -0
  33. quickscale_cli-0.89.0/src/quickscale_cli/commands/_state.py +535 -0
  34. quickscale_cli-0.89.0/src/quickscale_cli/commands/_status_context.py +247 -0
  35. quickscale_cli-0.89.0/src/quickscale_cli/commands/_status_diagnostics.py +282 -0
  36. quickscale_cli-0.89.0/src/quickscale_cli/commands/_status_display.py +539 -0
  37. quickscale_cli-0.89.0/src/quickscale_cli/commands/_steps.py +751 -0
  38. quickscale_cli-0.89.0/src/quickscale_cli/commands/_types.py +115 -0
  39. quickscale_cli-0.89.0/src/quickscale_cli/commands/apply_command.py +365 -0
  40. quickscale_cli-0.89.0/src/quickscale_cli/commands/apply_support.py +200 -0
  41. quickscale_cli-0.89.0/src/quickscale_cli/commands/contract_vintage.py +363 -0
  42. quickscale_cli-0.89.0/src/quickscale_cli/commands/deployment_commands.py +200 -0
  43. {quickscale_cli-0.87.0 → quickscale_cli-0.89.0}/src/quickscale_cli/commands/development_commands.py +206 -101
  44. quickscale_cli-0.89.0/src/quickscale_cli/commands/dr_commands.py +544 -0
  45. {quickscale_cli-0.87.0 → quickscale_cli-0.89.0}/src/quickscale_cli/commands/dr_output.py +18 -0
  46. quickscale_cli-0.89.0/src/quickscale_cli/commands/module_commands.py +95 -0
  47. quickscale_cli-0.89.0/src/quickscale_cli/commands/module_config.py +288 -0
  48. {quickscale_cli-0.87.0 → quickscale_cli-0.89.0}/src/quickscale_cli/commands/module_output.py +5 -1
  49. quickscale_cli-0.89.0/src/quickscale_cli/commands/plan_command.py +171 -0
  50. {quickscale_cli-0.87.0 → quickscale_cli-0.89.0}/src/quickscale_cli/commands/plan_selection.py +1 -6
  51. {quickscale_cli-0.87.0 → quickscale_cli-0.89.0}/src/quickscale_cli/commands/remove_command.py +93 -170
  52. {quickscale_cli-0.87.0 → quickscale_cli-0.89.0}/src/quickscale_cli/commands/remove_output.py +1 -2
  53. quickscale_cli-0.89.0/src/quickscale_cli/commands/status_command.py +161 -0
  54. {quickscale_cli-0.87.0 → quickscale_cli-0.89.0}/src/quickscale_cli/main.py +2 -1
  55. quickscale_cli-0.89.0/src/quickscale_cli/utils/_dependency_io.py +299 -0
  56. quickscale_cli-0.89.0/src/quickscale_cli/utils/_dependency_sync_ops.py +403 -0
  57. quickscale_cli-0.89.0/src/quickscale_cli/utils/_dependency_write.py +368 -0
  58. quickscale_cli-0.89.0/src/quickscale_cli/utils/_railway_cli.py +484 -0
  59. quickscale_cli-0.87.0/src/quickscale_cli/utils/railway_utils.py → quickscale_cli-0.89.0/src/quickscale_cli/utils/_railway_project.py +63 -472
  60. quickscale_cli-0.89.0/src/quickscale_cli/utils/auth_migration.py +169 -0
  61. {quickscale_cli-0.87.0 → quickscale_cli-0.89.0}/src/quickscale_cli/utils/dependency_utils.py +5 -5
  62. quickscale_cli-0.89.0/src/quickscale_cli/utils/docker_utils.py +691 -0
  63. quickscale_cli-0.89.0/src/quickscale_cli/utils/module_dependency_sync.py +25 -0
  64. quickscale_cli-0.89.0/src/quickscale_cli/utils/module_provenance.py +437 -0
  65. quickscale_cli-0.89.0/src/quickscale_cli/utils/module_wiring_manager.py +514 -0
  66. {quickscale_cli-0.87.0 → quickscale_cli-0.89.0}/src/quickscale_cli/utils/project_manager.py +38 -22
  67. quickscale_cli-0.89.0/src/quickscale_cli/utils/railway_utils.py +45 -0
  68. quickscale_cli-0.87.0/src/quickscale_cli/commands/apply_command.py +0 -4074
  69. quickscale_cli-0.87.0/src/quickscale_cli/commands/apply_support.py +0 -102
  70. quickscale_cli-0.87.0/src/quickscale_cli/commands/deployment_commands.py +0 -583
  71. quickscale_cli-0.87.0/src/quickscale_cli/commands/dr_commands.py +0 -1066
  72. quickscale_cli-0.87.0/src/quickscale_cli/commands/module_commands.py +0 -1666
  73. quickscale_cli-0.87.0/src/quickscale_cli/commands/module_config.py +0 -2154
  74. quickscale_cli-0.87.0/src/quickscale_cli/commands/plan_command.py +0 -1231
  75. quickscale_cli-0.87.0/src/quickscale_cli/commands/status_command.py +0 -892
  76. quickscale_cli-0.87.0/src/quickscale_cli/utils/docker_utils.py +0 -340
  77. quickscale_cli-0.87.0/src/quickscale_cli/utils/module_dependency_sync.py +0 -768
  78. quickscale_cli-0.87.0/src/quickscale_cli/utils/module_wiring_manager.py +0 -324
  79. {quickscale_cli-0.87.0 → quickscale_cli-0.89.0}/README.md +0 -0
  80. {quickscale_cli-0.87.0 → quickscale_cli-0.89.0}/src/quickscale_cli/__init__.py +0 -0
  81. {quickscale_cli-0.87.0 → quickscale_cli-0.89.0}/src/quickscale_cli/commands/__init__.py +0 -0
  82. {quickscale_cli-0.87.0 → quickscale_cli-0.89.0}/src/quickscale_cli/py.typed +0 -0
  83. {quickscale_cli-0.87.0 → quickscale_cli-0.89.0}/src/quickscale_cli/utils/__init__.py +0 -0
  84. {quickscale_cli-0.87.0 → quickscale_cli-0.89.0}/src/quickscale_cli/utils/development_state.py +0 -0
  85. {quickscale_cli-0.87.0 → quickscale_cli-0.89.0}/src/quickscale_cli/utils/project_identity.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quickscale-cli
3
- Version: 0.87.0
3
+ Version: 0.89.0
4
4
  Summary: Command-line interface for QuickScale Django project generator
5
5
  License-Expression: Apache-2.0
6
6
  Keywords: django,cli,project-generator,saas
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.14
14
14
  Classifier: Framework :: Django
15
15
  Classifier: Framework :: Django :: 6.0
16
16
  Requires-Dist: click (>=8.4.2,<9.0.0)
17
- Requires-Dist: quickscale-core (>=0.87.0,<0.88.0)
17
+ Requires-Dist: quickscale-core (>=0.89.0,<0.90.0)
18
18
  Project-URL: Documentation, https://github.com/Experto-AI/quickscale/tree/main/docs
19
19
  Project-URL: Homepage, https://github.com/Experto-AI/quickscale
20
20
  Project-URL: Repository, https://github.com/Experto-AI/quickscale
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "quickscale-cli"
3
- version = "0.87.0"
3
+ version = "0.89.0"
4
4
  description = "Command-line interface for QuickScale Django project generator"
5
5
  requires-python = ">=3.14,<3.15"
6
6
  readme = "README.md"
@@ -31,14 +31,14 @@ packages = [{include = "quickscale_cli", from = "src"}]
31
31
  [tool.poetry.dependencies]
32
32
  python = ">=3.14,<3.15"
33
33
  click = "^8.4.2"
34
- quickscale-core = "^0.87.0"
34
+ quickscale-core = "^0.89.0"
35
35
 
36
36
  [tool.poetry.group.dev.dependencies]
37
37
  # Dev dependencies centralized in root pyproject.toml
38
38
  # Use: poetry install from repository root
39
39
 
40
40
  [build-system]
41
- requires = ["poetry-core"]
41
+ requires = ["poetry-core>=2.0,<3.0"]
42
42
  build-backend = "poetry.core.masonry.api"
43
43
 
44
44
  [tool.pytest.ini_options]
@@ -76,13 +76,19 @@ target-version = "py314"
76
76
  # findings (0.16 widened them by ~486 across these two packages).
77
77
  # Adopting the shared select here is worthwhile but is its own cleanup.
78
78
  [tool.ruff.lint]
79
- select = ["E4", "E7", "E9", "F"]
79
+ select = ["E4", "E7", "E9", "F", "S"]
80
80
 
81
81
  # NOTE: `I` (isort) is not in the select above, so the [tool.ruff.lint.isort]
82
82
  # override below is currently inert — it configures a rule that is not
83
83
  # enabled. It is kept because it becomes correct the moment this package
84
84
  # adopts the shared select (which does enable `I`); see implementation_contract.md.
85
85
 
86
+ # The S (flake8-bandit) rules govern shipped package source. The Bandit
87
+ # static-security gate already scopes its scan to maintained source roots,
88
+ # excluding test trees; this table keeps the Ruff selection in step.
89
+ [tool.ruff.lint.per-file-ignores]
90
+ "tests/**" = ["S"]
91
+
86
92
  # Package-specific isort override
87
93
  [tool.ruff.lint.isort]
88
94
  known-first-party = ["quickscale_cli", "quickscale_core"]
@@ -1,2 +1,2 @@
1
1
  # Auto-generated by scripts/version_tool.sh
2
- __version__ = "0.87.0"
2
+ __version__ = "0.89.0"
@@ -0,0 +1,292 @@
1
+ """Help-tree machinery shared by the QuickScale CLI commands.
2
+
3
+ Click's default group help lists only each subcommand's name and one-line
4
+ summary. The CLI contract asks for the full tree, so :class:`QuickScaleGroup`
5
+ writes every reachable command's own help beneath its own. This module also
6
+ owns the help that Click cannot infer: per-command examples, positional
7
+ argument descriptions (Click 8.4 arguments carry none), and the ``--tail``
8
+ value type used by ``quickscale logs``.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import subprocess
14
+ from collections.abc import Callable
15
+ from pathlib import Path
16
+ from typing import Any
17
+
18
+ import click
19
+ import yaml
20
+
21
+ from quickscale_core.contracts.module_catalog import MODULE_CATALOG
22
+
23
+
24
+ _MODULE_NAMES = ", ".join(sorted(entry.name for entry in MODULE_CATALOG))
25
+ _DEFAULT_COMPOSE_SERVICES = ("backend", "frontend", "db")
26
+
27
+ EXAMPLES: dict[str, str] = {
28
+ "quickscale": (
29
+ "quickscale --help\nquickscale plan my-app\nquickscale apply\nquickscale up"
30
+ ),
31
+ "quickscale apply": (
32
+ "quickscale apply\n"
33
+ "quickscale apply myapp/quickscale.yml\n"
34
+ "quickscale apply --force --no-docker"
35
+ ),
36
+ "quickscale deploy": (
37
+ "quickscale deploy railway\nquickscale deploy railway --project-name my-app"
38
+ ),
39
+ "quickscale deploy railway": (
40
+ "quickscale deploy railway\nquickscale deploy railway --project-name my-app"
41
+ ),
42
+ "quickscale down": "quickscale down\nquickscale down --volumes",
43
+ "quickscale dr": (
44
+ "quickscale dr capture --route local-to-railway-develop\n"
45
+ "quickscale dr report --route local-to-railway-develop --snapshot-id <id>"
46
+ ),
47
+ "quickscale dr capture": (
48
+ "quickscale dr capture --route local-to-railway-develop\n"
49
+ "quickscale dr capture --route railway-develop-to-railway-production "
50
+ "--source-service my-app --json"
51
+ ),
52
+ "quickscale dr execute": (
53
+ "quickscale dr execute --route local-to-railway-develop --snapshot-id <id> "
54
+ "--target-service my-app --database\n"
55
+ "quickscale dr execute --route railway-develop-to-railway-production "
56
+ "--snapshot-id <id> --source-service my-app --target-service my-app --resume"
57
+ ),
58
+ "quickscale dr plan": (
59
+ "quickscale dr plan --route local-to-railway-develop --snapshot-id <id> "
60
+ "--target-service my-app\n"
61
+ "quickscale dr plan --route railway-develop-to-railway-production "
62
+ "--snapshot-id <id> --source-service my-app --target-service my-app --json"
63
+ ),
64
+ "quickscale dr report": (
65
+ "quickscale dr report --route local-to-railway-develop --snapshot-id <id>"
66
+ ),
67
+ "quickscale logs": (
68
+ "quickscale logs\n"
69
+ "quickscale logs backend --follow\n"
70
+ "quickscale logs db --tail 100"
71
+ ),
72
+ "quickscale manage": (
73
+ "quickscale manage\n"
74
+ "quickscale manage migrate\n"
75
+ "quickscale manage createsuperuser"
76
+ ),
77
+ "quickscale plan": (
78
+ "quickscale plan my-app\n"
79
+ "quickscale plan my-app --package my_app\n"
80
+ "quickscale plan --add\n"
81
+ "quickscale plan --reconfigure"
82
+ ),
83
+ "quickscale ps": "quickscale ps",
84
+ "quickscale push": (
85
+ "quickscale push --module auth\n"
86
+ "quickscale push --module auth --branch feature/fix-email-validation"
87
+ ),
88
+ "quickscale remove": "quickscale remove auth\nquickscale remove billing --keep-data",
89
+ "quickscale shell": (
90
+ 'quickscale shell\nquickscale shell -c "python manage.py check"'
91
+ ),
92
+ "quickscale status": "quickscale status\nquickscale status --json",
93
+ "quickscale up": "quickscale up\nquickscale up --build\nquickscale up --no-cache",
94
+ "quickscale update": "quickscale update\nquickscale update --no-preview",
95
+ "quickscale version": "quickscale version",
96
+ }
97
+
98
+ ARGUMENT_HELP: dict[str, dict[str, str]] = {
99
+ "quickscale apply": {
100
+ "config": "Path to the configuration file (default: quickscale.yml in the current directory).",
101
+ },
102
+ "quickscale logs": {
103
+ "service": (
104
+ "Compose service to show logs for: backend, frontend, or db in "
105
+ "generated projects (read from the project's docker-compose.yml)."
106
+ ),
107
+ },
108
+ "quickscale manage": {
109
+ "args": (
110
+ "Django management command and its arguments, for example: migrate, "
111
+ "createsuperuser. Omit it, or pass --help, to list the project's commands."
112
+ ),
113
+ },
114
+ "quickscale plan": {
115
+ "project_slug": "Project slug to configure; omit it with --add or --reconfigure.",
116
+ },
117
+ "quickscale remove": {
118
+ "module_name": (
119
+ f"Embedded module to remove. Valid shipped modules: {_MODULE_NAMES}."
120
+ ),
121
+ },
122
+ }
123
+
124
+
125
+ class TailLines(click.ParamType):
126
+ """Accept a non-negative line count or the literal ``all``."""
127
+
128
+ name = "LINES|all"
129
+
130
+ def convert(
131
+ self,
132
+ value: Any,
133
+ param: click.Parameter | None,
134
+ ctx: click.Context | None,
135
+ ) -> Any:
136
+ if value is None:
137
+ return None
138
+ text = str(value).strip()
139
+ if text.lower() == "all":
140
+ return "all"
141
+ try:
142
+ lines = int(text)
143
+ except ValueError:
144
+ self.fail("must be a non-negative integer or 'all'", param, ctx)
145
+ if lines < 0:
146
+ self.fail("must be a non-negative integer or 'all'", param, ctx)
147
+ return str(lines)
148
+
149
+
150
+ TAIL_LINES = TailLines()
151
+
152
+
153
+ def _declared_compose_services() -> tuple[str, ...] | None:
154
+ """Return the Compose services declared in the current project, if any."""
155
+ compose_file = Path("docker-compose.yml")
156
+ try:
157
+ data = yaml.safe_load(compose_file.read_text(encoding="utf-8")) or {}
158
+ except OSError, yaml.YAMLError:
159
+ return None
160
+ services = data.get("services") if isinstance(data, dict) else None
161
+ if not isinstance(services, dict) or not services:
162
+ return None
163
+ return tuple(sorted(str(name) for name in services))
164
+
165
+
166
+ def _compose_service_help(default: str) -> str:
167
+ """Name the project's declared services in the logs argument help."""
168
+ services = _declared_compose_services()
169
+ if services is None:
170
+ return default
171
+ return (
172
+ f"Compose service to show logs for: {', '.join(services)} "
173
+ "(from the project's docker-compose.yml)."
174
+ )
175
+
176
+
177
+ def validate_compose_service(service: str | None) -> None:
178
+ """Reject a service name the project's Compose file does not declare."""
179
+ if service is None:
180
+ return
181
+ services = _declared_compose_services()
182
+ if services is None:
183
+ return
184
+ if service not in services:
185
+ choices = ", ".join(services)
186
+ raise click.BadParameter(
187
+ f"{service!r} is not declared in docker-compose.yml; choose one of: {choices}",
188
+ param_hint="SERVICE",
189
+ )
190
+
191
+
192
+ def list_django_commands(run_exec: Callable[..., None], container_name: str) -> None:
193
+ """List the project's Django commands through manage.py help."""
194
+ click.echo("Django management commands, grouped by app:")
195
+ try:
196
+ run_exec(container_name, ["python", "manage.py", "help"], capture=True)
197
+ except subprocess.CalledProcessError as error:
198
+ click.secho(
199
+ "❌ Error: Backend container is not running or manage.py help failed",
200
+ fg="red",
201
+ err=True,
202
+ )
203
+ click.echo("💡 Start the stack with 'quickscale up' and try again.", err=True)
204
+ raise SystemExit(error.returncode) from error
205
+
206
+
207
+ def _help_key(ctx: click.Context) -> str:
208
+ """Return the registry key for a context, independent of the program name."""
209
+ root = ctx.find_root()
210
+ if ctx is root:
211
+ return "quickscale"
212
+ suffix = ctx.command_path[len(root.command_path) :].strip()
213
+ return f"quickscale {suffix}"
214
+
215
+
216
+ def _write_argument_help(
217
+ ctx: click.Context, formatter: click.HelpFormatter, command: click.Command
218
+ ) -> None:
219
+ """Write the documented positional arguments, if this command has any."""
220
+ key = _help_key(ctx)
221
+ documented = ARGUMENT_HELP.get(key, {})
222
+ rows = []
223
+ for param in command.get_params(ctx):
224
+ if isinstance(param, click.Argument):
225
+ help_text = documented.get(param.name)
226
+ if key == "quickscale logs" and param.name == "service":
227
+ help_text = _compose_service_help(help_text or "")
228
+ if help_text:
229
+ rows.append((param.make_metavar(ctx), help_text))
230
+ if rows:
231
+ with formatter.section("Arguments"):
232
+ formatter.write_dl(rows)
233
+
234
+
235
+ def _write_examples(ctx: click.Context, formatter: click.HelpFormatter) -> None:
236
+ """Write the registered examples for this command, if it has any."""
237
+ examples = EXAMPLES.get(_help_key(ctx))
238
+ if not examples:
239
+ return
240
+ formatter.write_paragraph()
241
+ formatter.write("Examples:\n")
242
+ for line in examples.splitlines():
243
+ formatter.write(f" {line}\n")
244
+
245
+
246
+ class QuickScaleCommand(click.Command):
247
+ """A command whose help carries examples and argument descriptions."""
248
+
249
+ def format_help_text(
250
+ self, ctx: click.Context, formatter: click.HelpFormatter
251
+ ) -> None:
252
+ super().format_help_text(ctx, formatter)
253
+ _write_argument_help(ctx, formatter, self)
254
+ _write_examples(ctx, formatter)
255
+
256
+
257
+ class QuickScaleGroup(click.Group):
258
+ """A group that prints its full command tree beneath its own help."""
259
+
260
+ command_class = QuickScaleCommand
261
+
262
+ def format_help_text(
263
+ self, ctx: click.Context, formatter: click.HelpFormatter
264
+ ) -> None:
265
+ super().format_help_text(ctx, formatter)
266
+ _write_argument_help(ctx, formatter, self)
267
+ _write_examples(ctx, formatter)
268
+
269
+ def format_commands(
270
+ self, ctx: click.Context, formatter: click.HelpFormatter
271
+ ) -> None:
272
+ commands = []
273
+ for name in self.list_commands(ctx):
274
+ command = self.get_command(ctx, name)
275
+ if command is not None and not command.hidden:
276
+ commands.append((name, command))
277
+ if not commands:
278
+ return
279
+ formatter.write_paragraph()
280
+ formatter.write_heading("Commands")
281
+ for name, command in commands:
282
+ sub_ctx = click.Context(command, info_name=name, parent=ctx)
283
+ formatter.write_paragraph()
284
+ for line in command.get_help(sub_ctx).splitlines():
285
+ formatter.write(f"{line}\n")
286
+
287
+
288
+ class PassThroughCommand(QuickScaleCommand):
289
+ """A command that forwards unknown options, including ``--help``."""
290
+
291
+ def get_help_option(self, ctx: click.Context) -> click.Option | None:
292
+ return None