ecosyste-ms-cli 1.3.2__tar.gz → 1.4.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 (105) hide show
  1. ecosyste_ms_cli-1.4.1/CHANGELOG.md +11 -0
  2. {ecosyste_ms_cli-1.3.2/ecosyste_ms_cli.egg-info → ecosyste_ms_cli-1.4.1}/PKG-INFO +5 -5
  3. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1/ecosyste_ms_cli.egg-info}/PKG-INFO +5 -5
  4. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosyste_ms_cli.egg-info/requires.txt +4 -4
  5. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/__init__.py +1 -1
  6. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/cli.py +7 -0
  7. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/advisories.py +5 -3
  8. ecosyste_ms_cli-1.4.1/ecosystems_cli/commands/dependabot.py +39 -0
  9. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/packages.py +99 -9
  10. ecosyste_ms_cli-1.4.1/ecosystems_cli/commands/repos.py +56 -0
  11. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/pyproject.toml +5 -5
  12. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/tests/test_cli.py +11 -0
  13. ecosyste_ms_cli-1.3.2/CHANGELOG.md +0 -6
  14. ecosyste_ms_cli-1.3.2/ecosystems_cli/commands/dependabot.py +0 -5
  15. ecosyste_ms_cli-1.3.2/ecosystems_cli/commands/repos.py +0 -5
  16. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/LICENSE +0 -0
  17. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/MANIFEST.in +0 -0
  18. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/Readme.md +0 -0
  19. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosyste_ms_cli.egg-info/SOURCES.txt +0 -0
  20. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosyste_ms_cli.egg-info/dependency_links.txt +0 -0
  21. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosyste_ms_cli.egg-info/entry_points.txt +0 -0
  22. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosyste_ms_cli.egg-info/top_level.txt +0 -0
  23. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/__main__.py +0 -0
  24. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/apis/__init__.py +0 -0
  25. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/apis/advisories.openapi.yaml +0 -0
  26. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/apis/archives.openapi.yaml +0 -0
  27. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/apis/commits.openapi.yaml +0 -0
  28. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/apis/dependabot.openapi.yaml +0 -0
  29. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/apis/diff.openapi.yaml +0 -0
  30. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/apis/docker.openapi.yaml +0 -0
  31. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/apis/issues.openapi.yaml +0 -0
  32. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/apis/licenses.openapi.yaml +0 -0
  33. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/apis/opencollective.openapi.yaml +0 -0
  34. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/apis/packages.openapi.yaml +0 -0
  35. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/apis/parser.openapi.yaml +0 -0
  36. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/apis/registries.yaml +0 -0
  37. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/apis/repos.openapi.yaml +0 -0
  38. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/apis/resolve.openapi.yaml +0 -0
  39. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/apis/sbom.openapi.yaml +0 -0
  40. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/apis/sponsors.openapi.yaml +0 -0
  41. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/apis/summary.openapi.yaml +0 -0
  42. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/apis/timeline.openapi.yaml +0 -0
  43. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/__init__.py +0 -0
  44. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/archives.py +0 -0
  45. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/commits.py +0 -0
  46. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/decorators.py +0 -0
  47. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/diff.py +0 -0
  48. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/docker.py +0 -0
  49. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/execution.py +0 -0
  50. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/generator.py +0 -0
  51. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/handlers/__init__.py +0 -0
  52. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/handlers/advisories.py +0 -0
  53. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/handlers/archives.py +0 -0
  54. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/handlers/base.py +0 -0
  55. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/handlers/commits.py +0 -0
  56. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/handlers/default.py +0 -0
  57. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/handlers/dependabot.py +0 -0
  58. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/handlers/diff.py +0 -0
  59. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/handlers/docker.py +0 -0
  60. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/handlers/factory.py +0 -0
  61. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/handlers/issues.py +0 -0
  62. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/handlers/licenses.py +0 -0
  63. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/handlers/opencollective.py +0 -0
  64. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/handlers/packages.py +0 -0
  65. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/handlers/parser.py +0 -0
  66. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/handlers/repos.py +0 -0
  67. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/handlers/resolve.py +0 -0
  68. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/handlers/sbom.py +0 -0
  69. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/handlers/sponsors.py +0 -0
  70. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/handlers/summary.py +0 -0
  71. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/handlers/timeline.py +0 -0
  72. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/issues.py +0 -0
  73. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/licenses.py +0 -0
  74. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/mcp.py +0 -0
  75. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/opencollective.py +0 -0
  76. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/parser.py +0 -0
  77. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/resolve.py +0 -0
  78. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/sbom.py +0 -0
  79. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/sponsors.py +0 -0
  80. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/summary.py +0 -0
  81. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/commands/timeline.py +0 -0
  82. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/constants.py +0 -0
  83. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/exceptions.py +0 -0
  84. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/helpers/click_params.py +0 -0
  85. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/helpers/flatten_dict.py +0 -0
  86. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/helpers/format_value.py +0 -0
  87. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/helpers/get_domain.py +0 -0
  88. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/helpers/load_api_spec.py +0 -0
  89. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/helpers/print_error.py +0 -0
  90. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/helpers/print_operations.py +0 -0
  91. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/helpers/print_output.py +0 -0
  92. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/helpers/purl_parser.py +0 -0
  93. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/mcp_server.py +0 -0
  94. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/ecosystems_cli/openapi_client.py +0 -0
  95. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/setup.cfg +0 -0
  96. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/tests/test_cli_domain.py +0 -0
  97. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/tests/test_constants.py +0 -0
  98. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/tests/test_format_value.py +0 -0
  99. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/tests/test_help_output.py +0 -0
  100. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/tests/test_load_api_spec.py +0 -0
  101. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/tests/test_mcp_server.py +0 -0
  102. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/tests/test_openapi_client.py +0 -0
  103. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/tests/test_package_metadata.py +0 -0
  104. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/tests/test_print_operations.py +0 -0
  105. {ecosyste_ms_cli-1.3.2 → ecosyste_ms_cli-1.4.1}/tests/test_print_output.py +0 -0
@@ -0,0 +1,11 @@
1
+ # Changelog for v1.4.1
2
+
3
+ ## Changes since v1.4.0
4
+
5
+ - fix: corrections and docs for purl usage (07698f5)
6
+ - fix: more inconsitent apis (7028846)
7
+ - fix: parameter precendence consistent \ \ * check for invalid purls \ * small errors corrected (b112cff)
8
+ - feature: purl parameter for features (b9fd27c)
9
+ - feature: purl for all repos commands (8a064b5)
10
+ - feature: dependabot api receives --purl parameter (10f3feb)
11
+ - feature: --purl parameter (0beb032)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ecosyste_ms_cli
3
- Version: 1.3.2
3
+ Version: 1.4.1
4
4
  Summary: CLI for ecosyste.ms API
5
5
  Author-email: Sebastian Schürmann <sebs@2xs.org>
6
6
  License-Expression: MIT
@@ -22,10 +22,10 @@ Requires-Python: >=3.9
22
22
  Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
24
  Requires-Dist: click~=8.3.1
25
- Requires-Dist: requests~=2.32.5
25
+ Requires-Dist: requests~=2.33.1
26
26
  Requires-Dist: pyyaml~=6.0.3
27
- Requires-Dist: rich~=14.3.3
28
- Requires-Dist: mcp~=1.26.0
27
+ Requires-Dist: rich~=15.0.0
28
+ Requires-Dist: mcp~=1.27.0
29
29
  Requires-Dist: openapi-core~=0.23.0
30
30
  Requires-Dist: packageurl-python~=0.17.6
31
31
  Requires-Dist: importlib-resources; python_version < "3.9"
@@ -36,7 +36,7 @@ Requires-Dist: black~=26.3.1; extra == "dev"
36
36
  Requires-Dist: isort~=8.0.1; extra == "dev"
37
37
  Requires-Dist: flake8~=7.3.0; extra == "dev"
38
38
  Requires-Dist: bandit~=1.9.4; extra == "dev"
39
- Requires-Dist: complexipy~=5.2.0; extra == "dev"
39
+ Requires-Dist: complexipy~=5.3.0; extra == "dev"
40
40
  Requires-Dist: tomli~=2.3.0; python_version < "3.11" and extra == "dev"
41
41
  Dynamic: license-file
42
42
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ecosyste_ms_cli
3
- Version: 1.3.2
3
+ Version: 1.4.1
4
4
  Summary: CLI for ecosyste.ms API
5
5
  Author-email: Sebastian Schürmann <sebs@2xs.org>
6
6
  License-Expression: MIT
@@ -22,10 +22,10 @@ Requires-Python: >=3.9
22
22
  Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
24
  Requires-Dist: click~=8.3.1
25
- Requires-Dist: requests~=2.32.5
25
+ Requires-Dist: requests~=2.33.1
26
26
  Requires-Dist: pyyaml~=6.0.3
27
- Requires-Dist: rich~=14.3.3
28
- Requires-Dist: mcp~=1.26.0
27
+ Requires-Dist: rich~=15.0.0
28
+ Requires-Dist: mcp~=1.27.0
29
29
  Requires-Dist: openapi-core~=0.23.0
30
30
  Requires-Dist: packageurl-python~=0.17.6
31
31
  Requires-Dist: importlib-resources; python_version < "3.9"
@@ -36,7 +36,7 @@ Requires-Dist: black~=26.3.1; extra == "dev"
36
36
  Requires-Dist: isort~=8.0.1; extra == "dev"
37
37
  Requires-Dist: flake8~=7.3.0; extra == "dev"
38
38
  Requires-Dist: bandit~=1.9.4; extra == "dev"
39
- Requires-Dist: complexipy~=5.2.0; extra == "dev"
39
+ Requires-Dist: complexipy~=5.3.0; extra == "dev"
40
40
  Requires-Dist: tomli~=2.3.0; python_version < "3.11" and extra == "dev"
41
41
  Dynamic: license-file
42
42
 
@@ -1,8 +1,8 @@
1
1
  click~=8.3.1
2
- requests~=2.32.5
2
+ requests~=2.33.1
3
3
  pyyaml~=6.0.3
4
- rich~=14.3.3
5
- mcp~=1.26.0
4
+ rich~=15.0.0
5
+ mcp~=1.27.0
6
6
  openapi-core~=0.23.0
7
7
  packageurl-python~=0.17.6
8
8
 
@@ -16,7 +16,7 @@ black~=26.3.1
16
16
  isort~=8.0.1
17
17
  flake8~=7.3.0
18
18
  bandit~=1.9.4
19
- complexipy~=5.2.0
19
+ complexipy~=5.3.0
20
20
 
21
21
  [dev:python_version < "3.11"]
22
22
  tomli~=2.3.0
@@ -1,3 +1,3 @@
1
1
  """Ecosystems CLI tool for interacting with ecosyste.ms APIs."""
2
2
 
3
- __version__ = "1.3.2"
3
+ __version__ = "1.4.1"
@@ -7,6 +7,7 @@ import click
7
7
  from rich.console import Console
8
8
  from rich.panel import Panel
9
9
 
10
+ from ecosystems_cli import __version__
10
11
  from ecosystems_cli.commands.advisories import advisories
11
12
  from ecosystems_cli.commands.archives import archives
12
13
  from ecosystems_cli.commands.commits import commits
@@ -139,6 +140,12 @@ main.add_command(mcp)
139
140
  # Dynamic op commands have been removed
140
141
 
141
142
 
143
+ @main.command(name="version", help="Show the ecosystems CLI version and exit.")
144
+ def version():
145
+ """Print the installed ecosystems CLI version."""
146
+ click.echo(__version__)
147
+
148
+
142
149
  def _parse_json_param(param: Optional[str]) -> Optional[Dict]:
143
150
  """Parse JSON parameter if provided."""
144
151
  if not param:
@@ -73,12 +73,14 @@ def get_advisories(
73
73
  """
74
74
  update_context(ctx, timeout, format, domain, mailto)
75
75
 
76
- # If PURL is provided, parse it and override ecosystem/package_name
76
+ # If PURL is provided, decompose it; explicit flags win over PURL-derived values.
77
77
  if purl:
78
78
  parsed_ecosystem, parsed_package_name = parse_purl(purl)
79
- if parsed_ecosystem:
79
+ if not parsed_ecosystem and not parsed_package_name:
80
+ raise click.UsageError(f"Invalid PURL: {purl!r}. Expected format: pkg:type/name (e.g. pkg:npm/axios).")
81
+ if parsed_ecosystem and not ecosystem:
80
82
  ecosystem = parsed_ecosystem
81
- if parsed_package_name:
83
+ if parsed_package_name and not package_name:
82
84
  package_name = parsed_package_name
83
85
 
84
86
  # Build kwargs for the API call
@@ -0,0 +1,39 @@
1
+ """Commands for the dependabot API."""
2
+
3
+ import click
4
+
5
+ from ecosystems_cli.commands.generator import APICommandGenerator
6
+ from ecosystems_cli.helpers.purl_parser import parse_purl
7
+
8
+ dependabot = APICommandGenerator.create_api_group("dependabot")
9
+
10
+
11
+ # Attach --purl as an optional parameter to the auto-generated get_advisories
12
+ # command. When provided, its type/name decompose into --ecosystem/--package-name,
13
+ # letting users pass a single PURL instead of two flags.
14
+ if "get_advisories" in dependabot.commands:
15
+ _get_advisories_cmd = dependabot.commands["get_advisories"]
16
+ _get_advisories_cmd.params.insert(
17
+ 0,
18
+ click.Option(
19
+ ["--purl"],
20
+ type=str,
21
+ default=None,
22
+ help="Package URL (PURL). Example: pkg:npm/fsa. Decomposes into --ecosystem and --package-name.",
23
+ ),
24
+ )
25
+ _original_get_advisories_callback = _get_advisories_cmd.callback
26
+
27
+ def _get_advisories_with_purl(*args, **kwargs):
28
+ purl = kwargs.pop("purl", None)
29
+ if purl:
30
+ parsed_ecosystem, parsed_package_name = parse_purl(purl)
31
+ if not parsed_ecosystem and not parsed_package_name:
32
+ raise click.UsageError(f"Invalid PURL: {purl!r}. Expected format: pkg:type/name (e.g. pkg:npm/axios).")
33
+ if parsed_ecosystem and not kwargs.get("ecosystem"):
34
+ kwargs["ecosystem"] = parsed_ecosystem
35
+ if parsed_package_name and not kwargs.get("package_name"):
36
+ kwargs["package_name"] = parsed_package_name
37
+ return _original_get_advisories_callback(*args, **kwargs)
38
+
39
+ _get_advisories_cmd.callback = _get_advisories_with_purl
@@ -12,6 +12,92 @@ from ecosystems_cli.helpers.purl_parser import parse_purl_with_version, purl_typ
12
12
  packages = APICommandGenerator.create_api_group("packages")
13
13
 
14
14
 
15
+ # Attach --purl as an optional parameter to the auto-generated get_dependencies
16
+ # command. When provided, its type/name decompose into --ecosystem/--package-name,
17
+ # letting users pass a single PURL instead of multiple flags.
18
+ if "get_dependencies" in packages.commands:
19
+ _get_dependencies_cmd = packages.commands["get_dependencies"]
20
+ _get_dependencies_cmd.params.insert(
21
+ 0,
22
+ click.Option(
23
+ ["--purl"],
24
+ type=str,
25
+ default=None,
26
+ help="Package URL (PURL). Example: pkg:npm/axios@1.7.9. Decomposes into --ecosystem and --package-name.",
27
+ ),
28
+ )
29
+ _original_get_dependencies_callback = _get_dependencies_cmd.callback
30
+
31
+ def _get_dependencies_with_purl(*args, **kwargs):
32
+ purl = kwargs.pop("purl", None)
33
+ if purl:
34
+ parsed_ecosystem, parsed_package_name, _ = parse_purl_with_version(purl)
35
+ if not parsed_ecosystem and not parsed_package_name:
36
+ raise click.UsageError(f"Invalid PURL: {purl!r}. Expected format: pkg:type/name (e.g. pkg:npm/axios).")
37
+ if parsed_ecosystem and not kwargs.get("ecosystem"):
38
+ # getDependencies filters by ecosystem name (e.g. "npm"), not the
39
+ # registry name ("npmjs.org"), so pass the raw PURL type through.
40
+ kwargs["ecosystem"] = parsed_ecosystem
41
+ if parsed_package_name and not kwargs.get("package_name"):
42
+ kwargs["package_name"] = parsed_package_name
43
+ return _original_get_dependencies_callback(*args, **kwargs)
44
+
45
+ _get_dependencies_cmd.callback = _get_dependencies_with_purl
46
+
47
+
48
+ def _add_purl_to_registry_package_command(command_name: str) -> None:
49
+ """Attach --purl to a command whose path args are registryName + packageName.
50
+
51
+ Makes the positional REGISTRY_NAME and PACKAGE_NAME args optional, injects
52
+ --purl, and wraps the callback so a PURL decomposes into those two args.
53
+ Purl type is mapped to a registry name (e.g. 'npm' -> 'npmjs.org').
54
+ Positional args still win over a PURL when both are passed.
55
+ """
56
+ if command_name not in packages.commands:
57
+ return
58
+
59
+ cmd = packages.commands[command_name]
60
+
61
+ for param in cmd.params:
62
+ if isinstance(param, click.Argument) and param.name in ("registryname", "packagename"):
63
+ param.required = False
64
+
65
+ cmd.params.insert(
66
+ 0,
67
+ click.Option(
68
+ ["--purl"],
69
+ type=str,
70
+ default=None,
71
+ help="Package URL (PURL). Example: pkg:npm/lodash. Decomposes into REGISTRY_NAME and PACKAGE_NAME.",
72
+ ),
73
+ )
74
+ original_callback = cmd.callback
75
+
76
+ def wrapped_callback(*args, **kwargs):
77
+ purl = kwargs.pop("purl", None)
78
+ if purl:
79
+ parsed_ecosystem, parsed_package_name, _ = parse_purl_with_version(purl)
80
+ if not parsed_ecosystem and not parsed_package_name:
81
+ raise click.UsageError(f"Invalid PURL: {purl!r}. Expected format: pkg:type/name (e.g. pkg:npm/lodash).")
82
+ if parsed_ecosystem and not kwargs.get("registryname"):
83
+ kwargs["registryname"] = purl_type_to_registry(parsed_ecosystem)
84
+ if parsed_package_name and not kwargs.get("packagename"):
85
+ kwargs["packagename"] = parsed_package_name
86
+ if not kwargs.get("registryname") or not kwargs.get("packagename"):
87
+ raise click.UsageError("Either --purl or both REGISTRY_NAME and PACKAGE_NAME arguments are required")
88
+ return original_callback(*args, **kwargs)
89
+
90
+ cmd.callback = wrapped_callback
91
+
92
+
93
+ for _op in (
94
+ "get_registry_package_dependent_packages",
95
+ "get_registry_package_versions",
96
+ "get_registry_package_version_numbers",
97
+ ):
98
+ _add_purl_to_registry_package_command(_op)
99
+
100
+
15
101
  # Remove auto-generated commands to replace with custom implementations
16
102
  if "get_registry_package" in packages.commands:
17
103
  del packages.commands["get_registry_package"]
@@ -55,13 +141,14 @@ def get_registry_package(
55
141
  """
56
142
  update_context(ctx, timeout, format, domain, mailto)
57
143
 
58
- # If PURL is provided, parse it and override registry_name/package_name
144
+ # If PURL is provided, decompose it; explicit positional args win over PURL-derived values.
59
145
  if purl:
60
146
  parsed_ecosystem, parsed_package_name, _ = parse_purl_with_version(purl)
61
- if parsed_ecosystem:
62
- # Convert purl type to registry name (e.g., 'npm' -> 'npmjs.org')
147
+ if not parsed_ecosystem and not parsed_package_name:
148
+ raise click.UsageError(f"Invalid PURL: {purl!r}. Expected format: pkg:type/name (e.g. pkg:npm/lodash).")
149
+ if parsed_ecosystem and not registry_name:
63
150
  registry_name = purl_type_to_registry(parsed_ecosystem)
64
- if parsed_package_name:
151
+ if parsed_package_name and not package_name:
65
152
  package_name = parsed_package_name
66
153
 
67
154
  # Validate that we have the required parameters
@@ -120,15 +207,18 @@ def get_registry_package_version(
120
207
  """
121
208
  update_context(ctx, timeout, format, domain, mailto)
122
209
 
123
- # If PURL is provided, parse it and override registry_name/package_name/version_number
210
+ # If PURL is provided, decompose it; explicit positional args win over PURL-derived values.
124
211
  if purl:
125
212
  parsed_ecosystem, parsed_package_name, parsed_version = parse_purl_with_version(purl)
126
- if parsed_ecosystem:
127
- # Convert purl type to registry name (e.g., 'npm' -> 'npmjs.org')
213
+ if not parsed_ecosystem and not parsed_package_name:
214
+ raise click.UsageError(
215
+ f"Invalid PURL: {purl!r}. Expected format: pkg:type/name@version (e.g. pkg:npm/lodash@4.17.21)."
216
+ )
217
+ if parsed_ecosystem and not registry_name:
128
218
  registry_name = purl_type_to_registry(parsed_ecosystem)
129
- if parsed_package_name:
219
+ if parsed_package_name and not package_name:
130
220
  package_name = parsed_package_name
131
- if parsed_version:
221
+ if parsed_version and not version_number:
132
222
  version_number = parsed_version
133
223
 
134
224
  # Validate that we have the required parameters
@@ -0,0 +1,56 @@
1
+ """Commands for the repos API."""
2
+
3
+ import click
4
+
5
+ from ecosystems_cli.commands.generator import APICommandGenerator
6
+ from ecosystems_cli.helpers.purl_parser import parse_purl
7
+
8
+ repos = APICommandGenerator.create_api_group("repos")
9
+
10
+
11
+ def _add_purl_to_ecosystem_package_command(command_name: str) -> None:
12
+ """Attach --purl to a command whose first two path args are ecosystem + package.
13
+
14
+ Makes the positional ECOSYSTEM and PACKAGE args optional, injects --purl, and
15
+ wraps the callback so a PURL decomposes into those two args when provided.
16
+ Positional args still win over a PURL when both are passed.
17
+ """
18
+ if command_name not in repos.commands:
19
+ return
20
+
21
+ cmd = repos.commands[command_name]
22
+
23
+ for param in cmd.params:
24
+ if isinstance(param, click.Argument) and param.name in ("ecosystem", "package"):
25
+ param.required = False
26
+
27
+ cmd.params.insert(
28
+ 0,
29
+ click.Option(
30
+ ["--purl"],
31
+ type=str,
32
+ default=None,
33
+ help="Package URL (PURL). Example: pkg:npm/lodash. Decomposes into ECOSYSTEM and PACKAGE.",
34
+ ),
35
+ )
36
+ original_callback = cmd.callback
37
+
38
+ def wrapped_callback(*args, **kwargs):
39
+ purl = kwargs.pop("purl", None)
40
+ if purl:
41
+ parsed_ecosystem, parsed_package = parse_purl(purl)
42
+ if not parsed_ecosystem and not parsed_package:
43
+ raise click.UsageError(f"Invalid PURL: {purl!r}. Expected format: pkg:type/name (e.g. pkg:npm/lodash).")
44
+ if parsed_ecosystem and not kwargs.get("ecosystem"):
45
+ kwargs["ecosystem"] = parsed_ecosystem
46
+ if parsed_package and not kwargs.get("package"):
47
+ kwargs["package"] = parsed_package
48
+ if not kwargs.get("ecosystem") or not kwargs.get("package"):
49
+ raise click.UsageError("Either --purl or both ECOSYSTEM and PACKAGE arguments are required")
50
+ return original_callback(*args, **kwargs)
51
+
52
+ cmd.callback = wrapped_callback
53
+
54
+
55
+ for _op in ("usage_package", "usage_package_dependencies", "usage_package_dependent_repositories"):
56
+ _add_purl_to_ecosystem_package_command(_op)
@@ -52,7 +52,7 @@ exclude = ["tests*"]
52
52
 
53
53
  [project]
54
54
  name = "ecosyste_ms_cli"
55
- version = "1.3.2"
55
+ version = "1.4.1"
56
56
  description = "CLI for ecosyste.ms API"
57
57
  authors = [
58
58
  {name = "Sebastian Schürmann", email = "sebs@2xs.org"}
@@ -76,10 +76,10 @@ classifiers = [
76
76
  ]
77
77
  dependencies = [
78
78
  "click~=8.3.1",
79
- "requests~=2.32.5",
79
+ "requests~=2.33.1",
80
80
  "pyyaml~=6.0.3",
81
- "rich~=14.3.3",
82
- "mcp~=1.26.0",
81
+ "rich~=15.0.0",
82
+ "mcp~=1.27.0",
83
83
  "openapi-core~=0.23.0",
84
84
  "packageurl-python~=0.17.6",
85
85
  "importlib-resources; python_version < '3.9'",
@@ -93,7 +93,7 @@ dev = [
93
93
  "isort~=8.0.1",
94
94
  "flake8~=7.3.0",
95
95
  "bandit~=1.9.4",
96
- "complexipy~=5.2.0",
96
+ "complexipy~=5.3.0",
97
97
  "tomli~=2.3.0; python_version < '3.11'"
98
98
  ]
99
99
 
@@ -6,6 +6,7 @@ from unittest import mock
6
6
  import pytest
7
7
  from click.testing import CliRunner
8
8
 
9
+ from ecosystems_cli import __version__
9
10
  from ecosystems_cli.cli import main
10
11
 
11
12
 
@@ -24,6 +25,16 @@ def mock_api_client():
24
25
  yield mock_factory
25
26
 
26
27
 
28
+ class TestVersionCommand:
29
+ """Test the version subcommand."""
30
+
31
+ def test_version_prints_package_version(self, runner):
32
+ """Version output must match __version__ so semantic-release stays the source of truth."""
33
+ result = runner.invoke(main, ["version"])
34
+ assert result.exit_code == 0
35
+ assert result.output.strip() == __version__
36
+
37
+
27
38
  class TestAdvisoriesCommands:
28
39
  """Test the advisories commands."""
29
40
 
@@ -1,6 +0,0 @@
1
- # Changelog for v1.3.2
2
-
3
- ## Changes since v1.3.1
4
-
5
- - fix: name change for pypi (05932a1)
6
- - chore: set verbose (3197d06)
@@ -1,5 +0,0 @@
1
- """Commands for the dependabot API."""
2
-
3
- from ecosystems_cli.commands.generator import APICommandGenerator
4
-
5
- dependabot = APICommandGenerator.create_api_group("dependabot")
@@ -1,5 +0,0 @@
1
- """Commands for the repos API."""
2
-
3
- from ecosystems_cli.commands.generator import APICommandGenerator
4
-
5
- repos = APICommandGenerator.create_api_group("repos")
File without changes