kctl-github 0.2.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 (32) hide show
  1. kctl_github-0.2.0/.gitignore +10 -0
  2. kctl_github-0.2.0/PKG-INFO +17 -0
  3. kctl_github-0.2.0/README.md +102 -0
  4. kctl_github-0.2.0/pyproject.toml +45 -0
  5. kctl_github-0.2.0/skills/github-admin/SKILL.md +162 -0
  6. kctl_github-0.2.0/src/kctl_github/__init__.py +3 -0
  7. kctl_github-0.2.0/src/kctl_github/__main__.py +5 -0
  8. kctl_github-0.2.0/src/kctl_github/cli.py +133 -0
  9. kctl_github-0.2.0/src/kctl_github/commands/__init__.py +0 -0
  10. kctl_github-0.2.0/src/kctl_github/commands/billing.py +182 -0
  11. kctl_github-0.2.0/src/kctl_github/commands/ci.py +271 -0
  12. kctl_github-0.2.0/src/kctl_github/commands/config_cmd.py +196 -0
  13. kctl_github-0.2.0/src/kctl_github/commands/dashboard.py +89 -0
  14. kctl_github-0.2.0/src/kctl_github/commands/doctor_cmd.py +82 -0
  15. kctl_github-0.2.0/src/kctl_github/commands/health.py +63 -0
  16. kctl_github-0.2.0/src/kctl_github/commands/labels.py +131 -0
  17. kctl_github-0.2.0/src/kctl_github/commands/prs.py +161 -0
  18. kctl_github-0.2.0/src/kctl_github/commands/repos.py +179 -0
  19. kctl_github-0.2.0/src/kctl_github/commands/secrets.py +132 -0
  20. kctl_github-0.2.0/src/kctl_github/commands/skill_cmd.py +76 -0
  21. kctl_github-0.2.0/src/kctl_github/commands/stats.py +208 -0
  22. kctl_github-0.2.0/src/kctl_github/core/__init__.py +0 -0
  23. kctl_github-0.2.0/src/kctl_github/core/callbacks.py +40 -0
  24. kctl_github-0.2.0/src/kctl_github/core/client.py +124 -0
  25. kctl_github-0.2.0/src/kctl_github/core/config.py +55 -0
  26. kctl_github-0.2.0/src/kctl_github/core/exceptions.py +21 -0
  27. kctl_github-0.2.0/src/kctl_github/core/plugins.py +13 -0
  28. kctl_github-0.2.0/tests/__init__.py +0 -0
  29. kctl_github-0.2.0/tests/conftest.py +59 -0
  30. kctl_github-0.2.0/tests/test_client.py +48 -0
  31. kctl_github-0.2.0/tests/test_dashboard.py +9 -0
  32. kctl_github-0.2.0/tests/test_smoke.py +57 -0
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .ruff_cache/
4
+ .mypy_cache/
5
+ .pytest_cache/
6
+ dist/
7
+ build/
8
+ *.egg-info/
9
+ .venv/
10
+ .env
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.4
2
+ Name: kctl-github
3
+ Version: 0.2.0
4
+ Summary: Kodemeio GitHub CLI — cross-repo GitHub management
5
+ Requires-Python: >=3.12
6
+ Requires-Dist: httpx>=0.28.0
7
+ Requires-Dist: kctl-lib>=0.4.0
8
+ Requires-Dist: pydantic>=2.10.0
9
+ Requires-Dist: pyyaml>=6.0.2
10
+ Requires-Dist: rich>=13.9.0
11
+ Requires-Dist: typer>=0.15.0
12
+ Provides-Extra: dev
13
+ Requires-Dist: mypy>=1.14.0; extra == 'dev'
14
+ Requires-Dist: pytest-httpx>=0.35.0; extra == 'dev'
15
+ Requires-Dist: pytest>=8.3.0; extra == 'dev'
16
+ Requires-Dist: ruff>=0.9.0; extra == 'dev'
17
+ Requires-Dist: types-pyyaml>=6.0.0; extra == 'dev'
@@ -0,0 +1,102 @@
1
+ # kctl-github
2
+
3
+ Cross-repo GitHub management CLI for kodemeio-* repositories.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ # From workspace root
9
+ uv tool install --editable packages/kctl-github
10
+
11
+ # Verify
12
+ kctl-github --version
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ```bash
18
+ # Initialize config with your GitHub token
19
+ kctl-github config init
20
+
21
+ # Check API connectivity and rate limits
22
+ kctl-github health
23
+
24
+ # View dashboard overview of all repos
25
+ kctl-github dashboard
26
+
27
+ # List all kodemeio-* repositories
28
+ kctl-github repos list
29
+
30
+ # Check CI status across all repos
31
+ kctl-github ci status
32
+ ```
33
+
34
+ ## Command Groups
35
+
36
+ | Group | Commands | Description |
37
+ |-------------|--------------------------------------------|--------------------------------------------------|
38
+ | `config` | init, add, use, show, validate, remove, set, profiles, current | Profile management |
39
+ | `health` | (default) | API connectivity check and rate limit status |
40
+ | `dashboard` | (default) | Quick overview of repos, PRs, and CI status |
41
+ | `repos` | list, status, show | Cross-repo overview for kodemeio-* repositories |
42
+ | `ci` | status, show, stats, rerun, bulk-status | CI/CD monitoring across repositories |
43
+ | `prs` | list, show, stale | Cross-repo pull request management |
44
+ | `secrets` | list, audit, set, rotate | Cross-repo Actions secret management |
45
+ | `labels` | list, sync, diff | Cross-repo label standardization |
46
+ | `stats` | overview, activity, languages, contributors | Repository statistics and insights |
47
+ | `billing` | actions, storage, packages, overview | GitHub Actions billing and usage |
48
+
49
+ ## Global Options
50
+
51
+ All commands support these flags:
52
+
53
+ | Option | Description |
54
+ |---------------------|------------------------------------------|
55
+ | `--json` | Output as JSON |
56
+ | `--quiet`, `-q` | Suppress info messages |
57
+ | `--format`, `-f` | Output format: pretty, json, csv, yaml |
58
+ | `--no-header` | Omit header row in CSV output |
59
+ | `--profile`, `-p` | Use a named config profile |
60
+ | `--version`, `-V` | Show version and exit |
61
+
62
+ ## Configuration
63
+
64
+ Config lives in `~/.config/kodemeio/config.yaml` under the `github` service key.
65
+
66
+ ```bash
67
+ # Interactive setup
68
+ kctl-github config init
69
+
70
+ # Add a named profile
71
+ kctl-github config add --profile work
72
+
73
+ # Switch active profile
74
+ kctl-github config use work
75
+
76
+ # Show current config (tokens masked)
77
+ kctl-github config show
78
+ ```
79
+
80
+ Required config fields:
81
+
82
+ | Field | Description |
83
+ |-------------|------------------------------------|
84
+ | `token` | GitHub personal access token |
85
+ | `org` | GitHub organization (e.g. kodemeio)|
86
+
87
+ ## Development
88
+
89
+ ```bash
90
+ # Install with dev extras
91
+ cd packages/kctl-github
92
+ uv sync --all-extras
93
+
94
+ # Run tests
95
+ uv run pytest tests/ -v
96
+
97
+ # Lint
98
+ uv run ruff check src/
99
+
100
+ # Type check
101
+ uv run mypy src/
102
+ ```
@@ -0,0 +1,45 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "kctl-github"
7
+ version = "0.2.0"
8
+ description = "Kodemeio GitHub CLI — cross-repo GitHub management"
9
+ requires-python = ">=3.12"
10
+ dependencies = [
11
+ "kctl-lib>=0.4.0",
12
+ "typer>=0.15.0",
13
+ "rich>=13.9.0",
14
+ "pydantic>=2.10.0",
15
+ "pyyaml>=6.0.2",
16
+ "httpx>=0.28.0",
17
+ ]
18
+
19
+ [project.optional-dependencies]
20
+ dev = [
21
+ "pytest>=8.3.0",
22
+ "pytest-httpx>=0.35.0",
23
+ "ruff>=0.9.0",
24
+ "mypy>=1.14.0",
25
+ "types-PyYAML>=6.0.0",
26
+ ]
27
+
28
+ [project.scripts]
29
+ kctl-github = "kctl_github.cli:_run"
30
+
31
+ [tool.uv.sources]
32
+ kctl-lib = { workspace = true }
33
+
34
+ [project.entry-points."kctl_github.plugins"]
35
+
36
+ [tool.hatch.build.targets.wheel]
37
+ packages = ["src/kctl_github"]
38
+
39
+ [tool.ruff]
40
+ target-version = "py312"
41
+ line-length = 120
42
+
43
+ [tool.mypy]
44
+ python_version = "3.12"
45
+ strict = true
@@ -0,0 +1,162 @@
1
+ ---
2
+ name: github-admin
3
+ description: >
4
+ GitHub cross-repo management via kctl-github CLI (11 groups, ~39 commands).
5
+ MUST use for ANY kctl-github operation.
6
+ Triggers on: "actions", "activity", "audit", "billing", "bulk-status", "ci", "config", "contributors", "current", "dashboard", "diff", "generate", "health", "init", "kctl-github", "labels", "languages", "monitor", "overview", "packages", "profile", "profiles", "prs", "remove", "repos", "rerun", "rotate", "secrets", "skill", "stale", "stats", "storage", "sync", "test", "validate".
7
+ Auto-generated: 2026-04-05
8
+ registry_hash: 226d58bc8834
9
+ ---
10
+
11
+ # github-admin — kctl-github CLI Reference
12
+
13
+ > Auto-generated from `kctl-github` command registry. Do not edit manually.
14
+ > To regenerate: `kctl-github skill generate`
15
+ > To add custom content: edit `SKILL.extra.md` in the same directory.
16
+
17
+ ## Overview
18
+
19
+ **CLI:** `kctl-github`
20
+ **Command groups:** 11
21
+ **Total commands:** ~39
22
+ **Install:** `cd cli && uv tool install --editable .`
23
+
24
+ ## Global Options
25
+
26
+ | Flag | Description |
27
+ |------|-------------|
28
+ | `--json` | JSON output |
29
+ | `--quiet`, `-q` | Suppress info messages |
30
+ | `--format`, `-f` | Output format: pretty/json/csv/yaml |
31
+ | `--no-header` | Omit CSV header row |
32
+ | `--profile`, `-p` | Config profile name |
33
+ | `--version`, `-V` | Show version |
34
+
35
+ ## Command Reference
36
+
37
+ ### `kctl-github billing`
38
+
39
+ GitHub Actions billing and usage.
40
+
41
+ | Command | Description |
42
+ |---------|-------------|
43
+ | `billing actions` | Actions minutes used this billing cycle. |
44
+ | `billing overview` | Combined billing summary. |
45
+ | `billing packages` | Packages data transfer. |
46
+ | `billing storage` | Git LFS + Packages storage usage. |
47
+
48
+ ### `kctl-github ci`
49
+
50
+ CI/CD monitoring across kodemeio-* repositories.
51
+
52
+ | Command | Description |
53
+ |---------|-------------|
54
+ | `ci bulk-status` | Table of all repos x workflows with pass/fail matrix. |
55
+ | `ci rerun <repo> [--workflow]` | Re-trigger the latest failed workflow run. |
56
+ | `ci show <repo> [--limit]` | Show workflow runs for a specific repo. |
57
+ | `ci stats [--period]` | CI statistics: success rate, avg duration, failure trends. |
58
+ | `ci status` | Latest workflow run status across ALL repos (pass/fail/running). |
59
+
60
+ ### `kctl-github config`
61
+
62
+ Profile and configuration management.
63
+
64
+ | Command | Description |
65
+ |---------|-------------|
66
+ | `config add <name>` | Add a new config profile. |
67
+ | `config current` | Show active profile and resolved context. |
68
+ | `config init` | Interactive config setup. |
69
+ | `config profiles` | List all config profiles. |
70
+ | `config remove <name>` | Remove a config profile. |
71
+ | `config set <key> <value>` | Set a single config value. |
72
+ | `config show` | Show current configuration. |
73
+ | `config test` | Test API connection with current configuration. |
74
+ | `config use <name>` | Switch active config profile. |
75
+ | `config validate` | Validate current config completeness. |
76
+
77
+ ### `kctl-github dashboard`
78
+
79
+ Quick overview dashboard.
80
+
81
+ ### `kctl-github health`
82
+
83
+ API connectivity and rate limits.
84
+
85
+ ### `kctl-github labels`
86
+
87
+ Cross-repo label management.
88
+
89
+ | Command | Description |
90
+ |---------|-------------|
91
+ | `labels diff` | Show label differences across repos. |
92
+ | `labels list <repo>` | List labels for a repo. |
93
+ | `labels sync <source>` | Copy labels from source repo to all other kodemeio-* repos. |
94
+
95
+ ### `kctl-github prs`
96
+
97
+ Cross-repo PR management.
98
+
99
+ | Command | Description |
100
+ |---------|-------------|
101
+ | `prs list` | Open PRs across all kodemeio-* repos. |
102
+ | `prs show <repo> <number>` | Show PR details (delegates to gh pr view). |
103
+ | `prs stale [--days]` | Find PRs with no activity for N days. |
104
+
105
+ ### `kctl-github repos`
106
+
107
+ Cross-repo overview for kodemeio-* repositories.
108
+
109
+ | Command | Description |
110
+ |---------|-------------|
111
+ | `repos list` | List all kodemeio-* repos with visibility, default branch, last push. |
112
+ | `repos show <name>` | Show single repo details (size, languages, contributors). |
113
+ | `repos status` | Aggregated status: open PRs, failing CI, stale branches per repo. |
114
+
115
+ ### `kctl-github secrets`
116
+
117
+ Cross-repo Actions secret management.
118
+
119
+ | Command | Description |
120
+ |---------|-------------|
121
+ | `secrets audit` | Check which repos have which secrets (matrix view). |
122
+ | `secrets list <repo>` | List Actions secrets for a repo. |
123
+ | `secrets rotate <name>` | Update a secret across all repos that have it. |
124
+ | `secrets set <name> <repos>` | Set a secret across multiple repos (prompts for value). |
125
+
126
+ ### `kctl-github skill`
127
+
128
+ Claude Code skill management.
129
+
130
+ | Command | Description |
131
+ |---------|-------------|
132
+ | `skill generate [--output] [--install] [--check]` | Auto-generate SKILL.md from CLI command registry. |
133
+
134
+ **Examples:**
135
+ ```bash
136
+ kctl-github skill generate
137
+ kctl-github skill generate --install
138
+ kctl-github skill generate --check
139
+ ```
140
+
141
+ ### `kctl-github stats`
142
+
143
+ Repository statistics across kodemeio-* repos.
144
+
145
+ | Command | Description |
146
+ |---------|-------------|
147
+ | `stats activity [--period]` | Commit activity, PR merge rate, issue velocity. |
148
+ | `stats contributors` | Contributor activity across all repos. |
149
+ | `stats languages` | Language breakdown across all repos. |
150
+ | `stats overview` | Total repos, total stars, total issues, total PRs. |
151
+
152
+ ## Configuration
153
+
154
+ Shared config: `~/.config/kodemeio/config.yaml`
155
+
156
+ ```bash
157
+ kctl-github config init # Interactive setup
158
+ kctl-github config show # Show current config
159
+ kctl-github config profiles # List profiles
160
+ kctl-github config current # Show active profile
161
+ kctl-github config validate # Verify config
162
+ ```
@@ -0,0 +1,3 @@
1
+ """kctl-github: GitHub integration management."""
2
+
3
+ __version__ = "0.2.0"
@@ -0,0 +1,5 @@
1
+ """Allow running as python -m kctl_github."""
2
+
3
+ from kctl_github.cli import _run
4
+
5
+ _run()
@@ -0,0 +1,133 @@
1
+ """Main CLI entry point for kctl-github."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ from typing import Annotated
7
+
8
+ import typer
9
+ from kctl_lib import KctlError, handle_cli_error
10
+
11
+ from kctl_github import __version__
12
+ from kctl_github.commands.billing import app as billing_app
13
+ from kctl_github.commands.ci import app as ci_app
14
+ from kctl_github.commands.config_cmd import app as config_app
15
+ from kctl_github.commands.dashboard import app as dashboard_app
16
+ from kctl_github.commands.health import app as health_app
17
+ from kctl_github.commands.labels import app as labels_app
18
+ from kctl_github.commands.prs import app as prs_app
19
+ from kctl_github.commands.repos import app as repos_app
20
+ from kctl_github.commands.secrets import app as secrets_app
21
+ from kctl_github.commands.stats import app as stats_app
22
+ from kctl_github.core.callbacks import AppContext
23
+ from kctl_github.core.plugins import discover_and_load_plugins
24
+ from kctl_github.commands.doctor_cmd import app as doctor_app
25
+ from kctl_github.commands.skill_cmd import app as skill_app
26
+ from kctl_lib.self_update import notify_if_outdated
27
+
28
+
29
+ def version_callback(value: bool) -> None:
30
+ if value:
31
+ typer.echo(f"kctl-github {__version__}")
32
+ raise typer.Exit()
33
+
34
+
35
+ app = typer.Typer(
36
+ name="kctl-github",
37
+ help="GitHub cross-repo management for kodemeio-* repositories",
38
+ no_args_is_help=True,
39
+ rich_markup_mode="rich",
40
+ pretty_exceptions_enable=False,
41
+ )
42
+
43
+
44
+ @app.callback()
45
+ def main(
46
+ ctx: typer.Context,
47
+ json_output: Annotated[bool, typer.Option("--json", help="Output as JSON")] = False,
48
+ quiet: Annotated[bool, typer.Option("--quiet", "-q", help="Suppress info messages")] = False,
49
+ profile: Annotated[str | None, typer.Option("--profile", "-p", help="Config profile name")] = None,
50
+ format: Annotated[str, typer.Option("--format", "-f", help="Output format: pretty, json, csv, yaml")] = "pretty",
51
+ no_header: Annotated[bool, typer.Option("--no-header", help="Omit header row in CSV output")] = False,
52
+ version: Annotated[
53
+ bool, typer.Option("--version", "-V", callback=version_callback, is_eager=True, help="Show version")
54
+ ] = False,
55
+ ) -> None:
56
+ """GitHub cross-repo management for kodemeio-* repositories."""
57
+ ctx.ensure_object(dict)
58
+ ctx.obj = AppContext(
59
+ json_mode=json_output,
60
+ quiet=quiet,
61
+ profile=profile,
62
+ format=format,
63
+ no_header=no_header,
64
+ )
65
+ notify_if_outdated(ctx.obj.output, "kctl-github", __version__)
66
+
67
+
68
+ # Register command groups
69
+ app.add_typer(config_app, name="config")
70
+ app.add_typer(health_app, name="health")
71
+ app.add_typer(dashboard_app, name="dashboard")
72
+ app.add_typer(repos_app, name="repos")
73
+ app.add_typer(ci_app, name="ci")
74
+ app.add_typer(prs_app, name="prs")
75
+ app.add_typer(secrets_app, name="secrets")
76
+ app.add_typer(labels_app, name="labels")
77
+ app.add_typer(stats_app, name="stats")
78
+ app.add_typer(billing_app, name="billing")
79
+ app.add_typer(doctor_app, name="doctor")
80
+ app.add_typer(skill_app, name="skill", hidden=True)
81
+
82
+ # Load third-party plugins via entry points
83
+ discover_and_load_plugins(app)
84
+
85
+
86
+ @app.command("self-update")
87
+ def self_update_cmd(ctx: typer.Context) -> None:
88
+ """Check for updates and upgrade kctl-github."""
89
+ actx = ctx.obj
90
+ out = actx.output
91
+
92
+ from kctl_lib.self_update import check_update
93
+ from kctl_lib.self_update import update as do_update
94
+
95
+ latest = check_update("kctl-github", __version__)
96
+ if latest:
97
+ out.info(f"Updating to {latest}...")
98
+ do_update("kctl-github")
99
+ out.success(f"Updated to {latest}")
100
+ else:
101
+ out.success("Already up to date")
102
+
103
+
104
+ @app.command()
105
+ def completions(
106
+ shell: Annotated[str, typer.Argument(help="Shell type: zsh, bash, fish")] = "zsh",
107
+ install: Annotated[bool, typer.Option("--install", help="Install completions")] = False,
108
+ ) -> None:
109
+ """Generate or install shell completions."""
110
+ from kctl_lib.completions import get_completion_script, install_completions
111
+
112
+ if install:
113
+ path = install_completions("kctl-github", shell)
114
+ if path:
115
+ typer.echo(f"Completions installed to {path}")
116
+ else:
117
+ typer.echo(f"Could not install completions for {shell}", err=True)
118
+ raise typer.Exit(code=1)
119
+ else:
120
+ script = get_completion_script("kctl-github", shell)
121
+ typer.echo(script)
122
+
123
+
124
+ def _run() -> None:
125
+ """Entry point with error handling."""
126
+ try:
127
+ app()
128
+ except KctlError as e:
129
+ handle_cli_error(e)
130
+
131
+
132
+ if __name__ == "__main__":
133
+ _run()
File without changes
@@ -0,0 +1,182 @@
1
+ """GitHub billing and usage commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import typer
6
+
7
+ from kctl_github.core.callbacks import AppContext
8
+
9
+ app = typer.Typer(help="GitHub Actions billing and usage.")
10
+
11
+
12
+ @app.command()
13
+ def actions(ctx: typer.Context) -> None:
14
+ """Actions minutes used this billing cycle."""
15
+ actx: AppContext = ctx.obj
16
+ out = actx.output
17
+ client = actx.client
18
+ org = client.organization
19
+
20
+ # Try org endpoint first, fall back to user
21
+ try:
22
+ data = client.get(f"/orgs/{org}/settings/billing/actions")
23
+ except Exception: # noqa: BLE001
24
+ try:
25
+ data = client.get(f"/users/{org}/settings/billing/actions")
26
+ except Exception: # noqa: BLE001
27
+ out.error("Unable to fetch billing data. Token may lack billing scope.")
28
+ raise typer.Exit(1) from None
29
+
30
+ if out.json_mode:
31
+ out.raw_json(data)
32
+ return
33
+
34
+ total_min = data.get("total_minutes_used", 0)
35
+ included_min = data.get("included_minutes", 0)
36
+ paid_min = data.get("total_paid_minutes_used", 0)
37
+
38
+ sections = [
39
+ (
40
+ "Actions Minutes",
41
+ [
42
+ ("Total Used", f"{total_min} min"),
43
+ ("Included", f"{included_min} min"),
44
+ ("Paid Overage", f"{paid_min} min"),
45
+ ],
46
+ ),
47
+ ]
48
+
49
+ # Per-OS breakdown if available
50
+ breakdown = data.get("minutes_used_breakdown", {})
51
+ if breakdown:
52
+ os_lines = [(os_name, f"{minutes} min") for os_name, minutes in breakdown.items() if minutes > 0]
53
+ if os_lines:
54
+ sections.append(("By OS", os_lines))
55
+
56
+ out.detail("Actions Billing", sections)
57
+
58
+
59
+ @app.command()
60
+ def storage(ctx: typer.Context) -> None:
61
+ """Git LFS + Packages storage usage."""
62
+ actx: AppContext = ctx.obj
63
+ out = actx.output
64
+ client = actx.client
65
+ org = client.organization
66
+
67
+ try:
68
+ data = client.get(f"/orgs/{org}/settings/billing/shared-storage")
69
+ except Exception: # noqa: BLE001
70
+ try:
71
+ data = client.get(f"/users/{org}/settings/billing/shared-storage")
72
+ except Exception: # noqa: BLE001
73
+ out.error("Unable to fetch storage billing data.")
74
+ raise typer.Exit(1) from None
75
+
76
+ if out.json_mode:
77
+ out.raw_json(data)
78
+ return
79
+
80
+ sections = [
81
+ (
82
+ "Storage",
83
+ [
84
+ ("Days Left in Cycle", str(data.get("days_left_in_billing_cycle", "?"))),
85
+ ("Estimated Paid Storage (GB)", f"{data.get('estimated_paid_storage_for_month', 0):.2f}"),
86
+ ("Estimated Storage (GB)", f"{data.get('estimated_storage_for_month', 0):.2f}"),
87
+ ],
88
+ ),
89
+ ]
90
+ out.detail("Storage Billing", sections)
91
+
92
+
93
+ @app.command()
94
+ def packages(ctx: typer.Context) -> None:
95
+ """Packages data transfer."""
96
+ actx: AppContext = ctx.obj
97
+ out = actx.output
98
+ client = actx.client
99
+ org = client.organization
100
+
101
+ try:
102
+ data = client.get(f"/orgs/{org}/settings/billing/packages")
103
+ except Exception: # noqa: BLE001
104
+ try:
105
+ data = client.get(f"/users/{org}/settings/billing/packages")
106
+ except Exception: # noqa: BLE001
107
+ out.error("Unable to fetch packages billing data.")
108
+ raise typer.Exit(1) from None
109
+
110
+ if out.json_mode:
111
+ out.raw_json(data)
112
+ return
113
+
114
+ sections = [
115
+ (
116
+ "Packages",
117
+ [
118
+ ("Total Bandwidth (GB)", f"{data.get('total_gigabytes_bandwidth_used', 0):.2f}"),
119
+ ("Included Bandwidth (GB)", f"{data.get('included_gigabytes_bandwidth', 0)}"),
120
+ ("Paid Bandwidth (GB)", f"{data.get('total_paid_gigabytes_bandwidth_used', 0):.2f}"),
121
+ ],
122
+ ),
123
+ ]
124
+ out.detail("Packages Billing", sections)
125
+
126
+
127
+ @app.command()
128
+ def overview(ctx: typer.Context) -> None:
129
+ """Combined billing summary."""
130
+ actx: AppContext = ctx.obj
131
+ out = actx.output
132
+ client = actx.client
133
+ org = client.organization
134
+
135
+ results: dict[str, dict] = {}
136
+
137
+ # Fetch all billing endpoints
138
+ for endpoint_name, path_suffix in [
139
+ ("actions", "actions"),
140
+ ("storage", "shared-storage"),
141
+ ("packages", "packages"),
142
+ ]:
143
+ try:
144
+ data = client.get(f"/orgs/{org}/settings/billing/{path_suffix}")
145
+ except Exception: # noqa: BLE001
146
+ try:
147
+ data = client.get(f"/users/{org}/settings/billing/{path_suffix}")
148
+ except Exception: # noqa: BLE001
149
+ data = {}
150
+ results[endpoint_name] = data
151
+
152
+ if out.json_mode:
153
+ out.raw_json(results)
154
+ return
155
+
156
+ actions_data = results.get("actions", {})
157
+ storage_data = results.get("storage", {})
158
+ packages_data = results.get("packages", {})
159
+
160
+ sections = [
161
+ (
162
+ "Actions",
163
+ [
164
+ ("Minutes Used", f"{actions_data.get('total_minutes_used', 0)}"),
165
+ ("Minutes Included", f"{actions_data.get('included_minutes', 0)}"),
166
+ ],
167
+ ),
168
+ (
169
+ "Storage",
170
+ [
171
+ ("Estimated (GB)", f"{storage_data.get('estimated_storage_for_month', 0):.2f}"),
172
+ ],
173
+ ),
174
+ (
175
+ "Packages",
176
+ [
177
+ ("Bandwidth Used (GB)", f"{packages_data.get('total_gigabytes_bandwidth_used', 0):.2f}"),
178
+ ("Bandwidth Included (GB)", f"{packages_data.get('included_gigabytes_bandwidth', 0)}"),
179
+ ],
180
+ ),
181
+ ]
182
+ out.detail("Billing Overview", sections)