pkgmap-py 0.1.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.
@@ -0,0 +1,155 @@
1
+ Metadata-Version: 2.3
2
+ Name: pkgmap-py
3
+ Version: 0.1.0
4
+ Summary: Cross-distribution Linux package name resolution
5
+ Author: Levizor
6
+ Author-email: Levizor <levizor@disroot.org>
7
+ License: MIT
8
+ Requires-Dist: typer>=0.12.0
9
+ Requires-Dist: zstandard>=0.22.0
10
+ Requires-Python: >=3.12
11
+ Project-URL: Homepage, https://github.com/Levizor/pkgmap
12
+ Project-URL: Repository, https://github.com/Levizor/pkgmap
13
+ Project-URL: Issues, https://github.com/Levizor/pkgmap/issues
14
+ Description-Content-Type: text/markdown
15
+
16
+ # pkgmap
17
+
18
+ Cross-distribution Linux package name resolution.
19
+
20
+ Different distributions often package the exact same software under different names (for example, `ninja` on Arch is `ninja-build` on Debian and Alpine; `bat` on Arch is `rust-bat` on Debian and Fedora; `fd` on Arch is `rust-fd-find` on Debian; Nixpkgs uses specific attribute paths). `pkgmap` provides an offline SQLite database and a companion Python library/CLI to translate package names between any pair of Linux distributions, resolve packages across all repositories, or look up native packages by upstream project name.
21
+
22
+ Data is updated daily from [Repology](https://repology.org) and indexed for instant local lookups. On first run, `pkgmap` automatically downloads and decompresses the latest daily database release (`~/.cache/pkgmap/pkgmap.db`).
23
+
24
+ ---
25
+
26
+ ## CLI Usage
27
+
28
+ ### Translate between distributions
29
+
30
+ Use `--from` (`-f`) and `--to` (`-t`):
31
+
32
+ ```bash
33
+ # What is Arch's 'bat' called in Debian?
34
+ pkgmap resolve bat --from arch --to debian
35
+ # Output: rust-bat (0.22.1) [category: misc]
36
+
37
+ # What is Arch's 'fd' in Nixpkgs?
38
+ pkgmap resolve fd -f arch -t nixpkgs
39
+ # Output: fd (10.5.0)
40
+ ```
41
+
42
+ ### Resolve across all distributions
43
+
44
+ Omit `--to` to see equivalents across every indexed distribution:
45
+
46
+ ```bash
47
+ pkgmap resolve bat --from arch
48
+ ```
49
+
50
+ Output:
51
+ ```text
52
+ alpine_3_20: bat (0.24.0)
53
+ debian_12: rust-bat (0.22.1) [category: misc]
54
+ fedora_rawhide: rust-bat (0.26.1)
55
+ nix_unstable: bat (0.26.1)
56
+ ubuntu_24_04: rust-bat (0.24.0)
57
+ ...
58
+ ```
59
+
60
+ ### Look up by upstream project name
61
+
62
+ Omit `--from` (or pass `--project` / `-p`) to resolve packages directly from an upstream project name:
63
+
64
+ ```bash
65
+ pkgmap resolve ripgrep --to debian
66
+ # Output: rust-ripgrep (13.0.0) [category: misc]
67
+ ```
68
+
69
+ ### JSON output
70
+
71
+ To output machine-readable JSON (great for automation and scripts), add `--json` / `-j`:
72
+
73
+ ```bash
74
+ pkgmap resolve bat -f arch -t debian --json
75
+ ```
76
+
77
+ ```json
78
+ [
79
+ {
80
+ "name": "rust-bat",
81
+ "version": "0.22.1",
82
+ "category": "misc",
83
+ "repo": "debian_12",
84
+ "repo_family": "debuntu",
85
+ "install_name": "rust-bat"
86
+ }
87
+ ]
88
+ ```
89
+
90
+ ### Other commands
91
+
92
+ ```bash
93
+ # List available distributions and repository slugs
94
+ pkgmap repos
95
+
96
+ # Manually refresh the local database snapshot
97
+ pkgmap update
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Python API
103
+
104
+ You can also use `pkgmap` as a Python library:
105
+
106
+ ```python
107
+ from pkgmap import resolve, resolve_all, resolve_batch
108
+
109
+ # 1. Single package resolution
110
+ res = resolve("bat", from_repo="arch", to_repo="debian")
111
+ if res:
112
+ print(res.install_name, res.version)
113
+ # Output: rust-bat 0.22.1
114
+
115
+ # 2. Query all repositories for equivalents
116
+ matches = resolve_all("bat", from_repo="arch")
117
+ for r in matches:
118
+ print(f"{r.repo}: {r.install_name}")
119
+
120
+ # 3. High-performance batch resolution (executed in a single SQL query)
121
+ packages = ["bat", "fd", "ripgrep"]
122
+ results = resolve_batch(packages, from_repo="arch", to_repo="nixpkgs")
123
+
124
+ for pkg, match in results.items():
125
+ if match:
126
+ print(f"{pkg} -> {match.install_name}")
127
+ ```
128
+
129
+ ### API Methods
130
+
131
+ - `resolve(package, from_repo=None, to_repo=None, db_path=None)`: Resolves a package to a single match.
132
+ - `resolve_all(package, from_repo=None, to_repo=None, db_path=None)`: Returns all matching packages.
133
+ - `resolve_batch(packages, from_repo=None, to_repo=None, db_path=None)`: Resolves a sequence of packages in a single batch query, returning `{package: PackageResolution | None}`.
134
+ - `resolve_batch_all(packages, from_repo=None, to_repo=None, db_path=None)`: Batch resolution returning `{package: list[PackageResolution]}`.
135
+ - `list_repositories(db_path=None)`: Returns all repositories with package counts and families.
136
+
137
+ ---
138
+
139
+ ## Configuration
140
+
141
+ By default, the database is stored at:
142
+ - `~/.cache/pkgmap/pkgmap.db`
143
+
144
+ You can customize the location using:
145
+ - The `PKGMAP_DB_PATH` environment variable:
146
+ ```bash
147
+ export PKGMAP_DB_PATH="/path/to/custom/pkgmap.db"
148
+ ```
149
+ - Or the `--db-path` argument on the CLI / `db_path` parameter in Python.
150
+
151
+ ---
152
+
153
+ ## Attribution
154
+
155
+ Data is sourced daily from [Repology](https://repology.org). Repology's data is published under the [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) license.
@@ -0,0 +1,140 @@
1
+ # pkgmap
2
+
3
+ Cross-distribution Linux package name resolution.
4
+
5
+ Different distributions often package the exact same software under different names (for example, `ninja` on Arch is `ninja-build` on Debian and Alpine; `bat` on Arch is `rust-bat` on Debian and Fedora; `fd` on Arch is `rust-fd-find` on Debian; Nixpkgs uses specific attribute paths). `pkgmap` provides an offline SQLite database and a companion Python library/CLI to translate package names between any pair of Linux distributions, resolve packages across all repositories, or look up native packages by upstream project name.
6
+
7
+ Data is updated daily from [Repology](https://repology.org) and indexed for instant local lookups. On first run, `pkgmap` automatically downloads and decompresses the latest daily database release (`~/.cache/pkgmap/pkgmap.db`).
8
+
9
+ ---
10
+
11
+ ## CLI Usage
12
+
13
+ ### Translate between distributions
14
+
15
+ Use `--from` (`-f`) and `--to` (`-t`):
16
+
17
+ ```bash
18
+ # What is Arch's 'bat' called in Debian?
19
+ pkgmap resolve bat --from arch --to debian
20
+ # Output: rust-bat (0.22.1) [category: misc]
21
+
22
+ # What is Arch's 'fd' in Nixpkgs?
23
+ pkgmap resolve fd -f arch -t nixpkgs
24
+ # Output: fd (10.5.0)
25
+ ```
26
+
27
+ ### Resolve across all distributions
28
+
29
+ Omit `--to` to see equivalents across every indexed distribution:
30
+
31
+ ```bash
32
+ pkgmap resolve bat --from arch
33
+ ```
34
+
35
+ Output:
36
+ ```text
37
+ alpine_3_20: bat (0.24.0)
38
+ debian_12: rust-bat (0.22.1) [category: misc]
39
+ fedora_rawhide: rust-bat (0.26.1)
40
+ nix_unstable: bat (0.26.1)
41
+ ubuntu_24_04: rust-bat (0.24.0)
42
+ ...
43
+ ```
44
+
45
+ ### Look up by upstream project name
46
+
47
+ Omit `--from` (or pass `--project` / `-p`) to resolve packages directly from an upstream project name:
48
+
49
+ ```bash
50
+ pkgmap resolve ripgrep --to debian
51
+ # Output: rust-ripgrep (13.0.0) [category: misc]
52
+ ```
53
+
54
+ ### JSON output
55
+
56
+ To output machine-readable JSON (great for automation and scripts), add `--json` / `-j`:
57
+
58
+ ```bash
59
+ pkgmap resolve bat -f arch -t debian --json
60
+ ```
61
+
62
+ ```json
63
+ [
64
+ {
65
+ "name": "rust-bat",
66
+ "version": "0.22.1",
67
+ "category": "misc",
68
+ "repo": "debian_12",
69
+ "repo_family": "debuntu",
70
+ "install_name": "rust-bat"
71
+ }
72
+ ]
73
+ ```
74
+
75
+ ### Other commands
76
+
77
+ ```bash
78
+ # List available distributions and repository slugs
79
+ pkgmap repos
80
+
81
+ # Manually refresh the local database snapshot
82
+ pkgmap update
83
+ ```
84
+
85
+ ---
86
+
87
+ ## Python API
88
+
89
+ You can also use `pkgmap` as a Python library:
90
+
91
+ ```python
92
+ from pkgmap import resolve, resolve_all, resolve_batch
93
+
94
+ # 1. Single package resolution
95
+ res = resolve("bat", from_repo="arch", to_repo="debian")
96
+ if res:
97
+ print(res.install_name, res.version)
98
+ # Output: rust-bat 0.22.1
99
+
100
+ # 2. Query all repositories for equivalents
101
+ matches = resolve_all("bat", from_repo="arch")
102
+ for r in matches:
103
+ print(f"{r.repo}: {r.install_name}")
104
+
105
+ # 3. High-performance batch resolution (executed in a single SQL query)
106
+ packages = ["bat", "fd", "ripgrep"]
107
+ results = resolve_batch(packages, from_repo="arch", to_repo="nixpkgs")
108
+
109
+ for pkg, match in results.items():
110
+ if match:
111
+ print(f"{pkg} -> {match.install_name}")
112
+ ```
113
+
114
+ ### API Methods
115
+
116
+ - `resolve(package, from_repo=None, to_repo=None, db_path=None)`: Resolves a package to a single match.
117
+ - `resolve_all(package, from_repo=None, to_repo=None, db_path=None)`: Returns all matching packages.
118
+ - `resolve_batch(packages, from_repo=None, to_repo=None, db_path=None)`: Resolves a sequence of packages in a single batch query, returning `{package: PackageResolution | None}`.
119
+ - `resolve_batch_all(packages, from_repo=None, to_repo=None, db_path=None)`: Batch resolution returning `{package: list[PackageResolution]}`.
120
+ - `list_repositories(db_path=None)`: Returns all repositories with package counts and families.
121
+
122
+ ---
123
+
124
+ ## Configuration
125
+
126
+ By default, the database is stored at:
127
+ - `~/.cache/pkgmap/pkgmap.db`
128
+
129
+ You can customize the location using:
130
+ - The `PKGMAP_DB_PATH` environment variable:
131
+ ```bash
132
+ export PKGMAP_DB_PATH="/path/to/custom/pkgmap.db"
133
+ ```
134
+ - Or the `--db-path` argument on the CLI / `db_path` parameter in Python.
135
+
136
+ ---
137
+
138
+ ## Attribution
139
+
140
+ Data is sourced daily from [Repology](https://repology.org). Repology's data is published under the [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) license.
@@ -0,0 +1,56 @@
1
+ [project]
2
+ name = "pkgmap-py"
3
+ version = "0.1.0"
4
+ description = "Cross-distribution Linux package name resolution"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ "typer>=0.12.0",
9
+ "zstandard>=0.22.0",
10
+ ]
11
+
12
+ [[project.authors]]
13
+ name = "Levizor"
14
+ email = "levizor@disroot.org"
15
+
16
+ [project.license]
17
+ text = "MIT"
18
+
19
+ [project.urls]
20
+ Homepage = "https://github.com/Levizor/pkgmap"
21
+ Repository = "https://github.com/Levizor/pkgmap"
22
+ Issues = "https://github.com/Levizor/pkgmap/issues"
23
+
24
+ [project.scripts]
25
+ pkgmap = "pkgmap.apps:cli_app"
26
+ pkgmap-pipeline = "pkgmap.apps:pipeline_app"
27
+
28
+ [dependency-groups]
29
+ dev = [
30
+ "pytest>=8.0.0",
31
+ "ruff>=0.4.0",
32
+ "pre-commit",
33
+ ]
34
+
35
+ [build-system]
36
+ requires = ["uv_build>=0.9.0,<0.10.0"]
37
+ build-backend = "uv_build"
38
+
39
+ [tool.uv.build-backend]
40
+ module-name = "pkgmap"
41
+
42
+ [tool.ruff.lint]
43
+ select = [
44
+ "E4",
45
+ "E7",
46
+ "E9",
47
+ "F",
48
+ "I",
49
+ "B",
50
+ ]
51
+
52
+ [tool.ruff.lint.isort]
53
+ combine-as-imports = true
54
+
55
+ [tool.pytest.ini_options]
56
+ testpaths = ["tests"]
@@ -0,0 +1,44 @@
1
+ [project]
2
+ name = "pkgmap-py"
3
+ version = "0.1.0"
4
+ description = "Cross-distribution Linux package name resolution"
5
+ authors = [{ name = "Levizor", email = "levizor@disroot.org" }]
6
+ license = { text = "MIT" }
7
+ readme = "README.md"
8
+ requires-python = ">=3.12"
9
+ dependencies = [
10
+ "typer>=0.12.0",
11
+ "zstandard>=0.22.0",
12
+ ]
13
+
14
+ [project.urls]
15
+ Homepage = "https://github.com/Levizor/pkgmap"
16
+ Repository = "https://github.com/Levizor/pkgmap"
17
+ Issues = "https://github.com/Levizor/pkgmap/issues"
18
+
19
+ [project.scripts]
20
+ pkgmap = "pkgmap.apps:cli_app"
21
+ pkgmap-pipeline = "pkgmap.apps:pipeline_app"
22
+
23
+ [dependency-groups]
24
+ dev = [
25
+ "pytest>=8.0.0",
26
+ "ruff>=0.4.0",
27
+ "pre-commit",
28
+ ]
29
+
30
+ [build-system]
31
+ requires = ["uv_build>=0.9.0,<0.10.0"]
32
+ build-backend = "uv_build"
33
+
34
+ [tool.uv.build-backend]
35
+ module-name = "pkgmap"
36
+
37
+ [tool.ruff.lint]
38
+ select = ["E4", "E7", "E9", "F", "I", "B"]
39
+
40
+ [tool.ruff.lint.isort]
41
+ combine-as-imports = true
42
+
43
+ [tool.pytest.ini_options]
44
+ testpaths = ["tests"]
@@ -0,0 +1,26 @@
1
+ """Cross-distribution Linux package name resolution library."""
2
+
3
+ from pkgmap.db import ensure_database, get_database_path, update_database
4
+ from pkgmap.resolver import (
5
+ PackageResolution,
6
+ list_repositories,
7
+ resolve,
8
+ resolve_all,
9
+ resolve_batch,
10
+ resolve_batch_all,
11
+ resolve_project,
12
+ )
13
+
14
+ __version__ = "0.1.0"
15
+ __all__ = [
16
+ "PackageResolution",
17
+ "ensure_database",
18
+ "get_database_path",
19
+ "list_repositories",
20
+ "resolve",
21
+ "resolve_all",
22
+ "resolve_batch",
23
+ "resolve_batch_all",
24
+ "resolve_project",
25
+ "update_database",
26
+ ]
@@ -0,0 +1,83 @@
1
+ from pathlib import Path
2
+ from typing import Annotated
3
+
4
+ import typer
5
+
6
+ from pkgmap import pipeline
7
+ from pkgmap.cli import cli_app
8
+ from pkgmap.pipeline import download_dump_file, set_github_output
9
+
10
+ pipeline_app = typer.Typer(
11
+ name="pkgmap-pipeline",
12
+ help="Download dump, transform, and build package map database",
13
+ add_completion=False,
14
+ )
15
+
16
+
17
+ @pipeline_app.command(name="download")
18
+ def pipeline_download(
19
+ link: Annotated[
20
+ str | None,
21
+ typer.Option("--link", "-l", help="Dump link to download from"),
22
+ ] = None,
23
+ dest_dir: Annotated[
24
+ Path | None,
25
+ typer.Option("--dest-dir", "-d", help="Directory where to save the dump file"),
26
+ ] = None,
27
+ ) -> None:
28
+ """Download the latest Repology database dump."""
29
+ dump_file, dump_date = download_dump_file(dump_link=link, dest_dir=dest_dir)
30
+ set_github_output(
31
+ date=dump_date,
32
+ tag=f"db-{dump_date}",
33
+ dump_path=str(dump_file.resolve()),
34
+ )
35
+ typer.echo(f"Downloaded Repology dump ({dump_date}) to {dump_file}")
36
+
37
+
38
+ @pipeline_app.command(name="build")
39
+ def pipeline_build(
40
+ input_dump: Annotated[
41
+ Path | None,
42
+ typer.Option(
43
+ "--input-dump",
44
+ "-i",
45
+ help="Dump file to take as an input to the processing pipeline",
46
+ ),
47
+ ] = None,
48
+ output_file: Annotated[
49
+ Path,
50
+ typer.Option(
51
+ "-o",
52
+ "--output-path",
53
+ help="Path for the output SQLite database",
54
+ ),
55
+ ] = Path("pkgmap.db"),
56
+ batch_size: Annotated[
57
+ int,
58
+ typer.Option(
59
+ "--batch-size",
60
+ help="Number of records per batch in the processing pipeline",
61
+ ),
62
+ ] = 50_000,
63
+ release_notes: Annotated[
64
+ Path | None,
65
+ typer.Option("--release-notes", help="Path to generate release notes markdown"),
66
+ ] = None,
67
+ dump_date: Annotated[
68
+ str | None,
69
+ typer.Option("--dump-date", help="Snapshot date (YYYY-MM-DD)"),
70
+ ] = None,
71
+ ) -> None:
72
+ """Process Repology dump and generate SQLite package map database."""
73
+ pipeline.main(
74
+ dump_file=input_dump,
75
+ output_file=output_file,
76
+ batch_size=batch_size,
77
+ release_notes=release_notes,
78
+ dump_date=dump_date,
79
+ )
80
+
81
+
82
+ if __name__ == "__main__":
83
+ cli_app()
@@ -0,0 +1,171 @@
1
+ import json
2
+ from pathlib import Path
3
+ from typing import Annotated
4
+
5
+ import typer
6
+
7
+ from pkgmap.db import get_database_path, update_database
8
+ from pkgmap.resolver import list_repositories, resolve_all, resolve_project
9
+
10
+ cli_app = typer.Typer(
11
+ name="pkgmap",
12
+ help="Cross-distribution Linux package name resolution CLI",
13
+ add_completion=False,
14
+ )
15
+
16
+
17
+ @cli_app.callback()
18
+ def cli_callback(
19
+ verbose: Annotated[
20
+ bool,
21
+ typer.Option("--verbose", "-v", help="Enable verbose debug logging"),
22
+ ] = False,
23
+ ) -> None:
24
+ if verbose:
25
+ import logging
26
+
27
+ for name in ("pkgmap", "pkgmap-db", "pkgmap-downloader", "pkgmap-consumer"):
28
+ log = logging.getLogger(name)
29
+ log.setLevel(logging.DEBUG)
30
+ for handler in log.handlers:
31
+ handler.setLevel(logging.DEBUG)
32
+
33
+
34
+ @cli_app.command(name="resolve")
35
+ def resolve_cmd(
36
+ package: Annotated[
37
+ str, typer.Argument(help="Package or upstream project name to resolve")
38
+ ],
39
+ to_repo: Annotated[
40
+ str | None,
41
+ typer.Option(
42
+ "--to",
43
+ "-t",
44
+ help="Target repository/distribution (e.g. nixpkgs, alpine). If omitted, matches across all repositories.",
45
+ ),
46
+ ] = None,
47
+ from_repo: Annotated[
48
+ str | None,
49
+ typer.Option(
50
+ "--from",
51
+ "-f",
52
+ help="Source repository/distribution (e.g. debian, arch). If omitted, package is resolved by upstream project name.",
53
+ ),
54
+ ] = None,
55
+ project: Annotated[
56
+ bool,
57
+ typer.Option(
58
+ "--project",
59
+ "-p",
60
+ help="Resolve package directly as an upstream project name",
61
+ ),
62
+ ] = False,
63
+ all_matches: Annotated[
64
+ bool,
65
+ typer.Option("--all", "-a", help="Show all matching target packages"),
66
+ ] = False,
67
+ json_output: Annotated[
68
+ bool,
69
+ typer.Option("--json", "-j", help="Output results in JSON format"),
70
+ ] = False,
71
+ db_path: Annotated[
72
+ Path | None,
73
+ typer.Option("--db-path", help="Custom path to pkgmap.db database"),
74
+ ] = None,
75
+ ) -> None:
76
+ """Resolve a package name across Linux distributions."""
77
+ if project and from_repo:
78
+ typer.secho(
79
+ "Error: Cannot specify both --from and --project.",
80
+ fg=typer.colors.RED,
81
+ )
82
+ raise typer.Exit(code=2)
83
+
84
+ if from_repo and not project:
85
+ results = resolve_all(
86
+ package, from_repo=from_repo, to_repo=to_repo, db_path=db_path
87
+ )
88
+ else:
89
+ results = resolve_project(package, to_repo=to_repo, db_path=db_path)
90
+
91
+ if not results:
92
+ if json_output:
93
+ typer.echo(json.dumps([]))
94
+ else:
95
+ if from_repo and not project:
96
+ if to_repo:
97
+ msg = f"No equivalent package found for '{package}' from '{from_repo}' to '{to_repo}'."
98
+ else:
99
+ msg = f"No equivalent package found for '{package}' from '{from_repo}'."
100
+ else:
101
+ if to_repo:
102
+ msg = f"No equivalent package found for project '{package}' in '{to_repo}'."
103
+ else:
104
+ msg = f"No equivalent package found for project '{package}'."
105
+ typer.secho(msg, fg=typer.colors.YELLOW)
106
+ raise typer.Exit(code=1)
107
+
108
+ items_to_show = results if (all_matches or to_repo is None) else results[:1]
109
+
110
+ if json_output:
111
+ data = [
112
+ {
113
+ "name": r.name,
114
+ "version": r.version,
115
+ "category": r.category,
116
+ "repo": r.repo,
117
+ "repo_family": r.repo_family,
118
+ "install_name": r.install_name,
119
+ }
120
+ for r in items_to_show
121
+ ]
122
+ typer.echo(json.dumps(data, indent=2))
123
+ return
124
+
125
+ for res in items_to_show:
126
+ version_str = f" ({res.version})" if res.version else ""
127
+ meta_parts = []
128
+ if res.category and res.repo_family != "nix":
129
+ meta_parts.append(f"category: {res.category}")
130
+ meta_str = f" [{', '.join(meta_parts)}]" if meta_parts else ""
131
+ repo_prefix = f"{res.repo}: " if to_repo is None else ""
132
+ typer.echo(f"{repo_prefix}{res.install_name}{version_str}{meta_str}")
133
+
134
+
135
+ @cli_app.command(name="update")
136
+ def update_cmd(
137
+ db_path: Annotated[
138
+ Path | None,
139
+ typer.Option("--db-path", help="Custom path to save pkgmap.db database"),
140
+ ] = None,
141
+ ) -> None:
142
+ """Explicitly download and update the local package map database."""
143
+ target_path = get_database_path(db_path)
144
+ typer.echo(f"Updating package database at {target_path}...")
145
+ update_database(db_path=target_path, show_progress=True)
146
+ typer.secho(
147
+ f"Successfully updated database at {target_path}", fg=typer.colors.GREEN
148
+ )
149
+
150
+
151
+ @cli_app.command(name="repos")
152
+ def repos_cmd(
153
+ db_path: Annotated[
154
+ Path | None,
155
+ typer.Option("--db-path", help="Custom path to pkgmap.db database"),
156
+ ] = None,
157
+ ) -> None:
158
+ """List all available distributions and repositories."""
159
+ repos = list_repositories(db_path=db_path)
160
+ typer.echo(f"{'Repository':<25} {'Family':<15} {'Packages':>10}")
161
+ typer.echo("-" * 52)
162
+ for r in sorted(repos, key=lambda x: x.name):
163
+ typer.echo(f"{r.name:<25} {r.family:<15} {r.package_count:>10,}")
164
+
165
+
166
+ def main() -> None:
167
+ cli_app()
168
+
169
+
170
+ if __name__ == "__main__":
171
+ main()