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.
- kctl_github-0.2.0/.gitignore +10 -0
- kctl_github-0.2.0/PKG-INFO +17 -0
- kctl_github-0.2.0/README.md +102 -0
- kctl_github-0.2.0/pyproject.toml +45 -0
- kctl_github-0.2.0/skills/github-admin/SKILL.md +162 -0
- kctl_github-0.2.0/src/kctl_github/__init__.py +3 -0
- kctl_github-0.2.0/src/kctl_github/__main__.py +5 -0
- kctl_github-0.2.0/src/kctl_github/cli.py +133 -0
- kctl_github-0.2.0/src/kctl_github/commands/__init__.py +0 -0
- kctl_github-0.2.0/src/kctl_github/commands/billing.py +182 -0
- kctl_github-0.2.0/src/kctl_github/commands/ci.py +271 -0
- kctl_github-0.2.0/src/kctl_github/commands/config_cmd.py +196 -0
- kctl_github-0.2.0/src/kctl_github/commands/dashboard.py +89 -0
- kctl_github-0.2.0/src/kctl_github/commands/doctor_cmd.py +82 -0
- kctl_github-0.2.0/src/kctl_github/commands/health.py +63 -0
- kctl_github-0.2.0/src/kctl_github/commands/labels.py +131 -0
- kctl_github-0.2.0/src/kctl_github/commands/prs.py +161 -0
- kctl_github-0.2.0/src/kctl_github/commands/repos.py +179 -0
- kctl_github-0.2.0/src/kctl_github/commands/secrets.py +132 -0
- kctl_github-0.2.0/src/kctl_github/commands/skill_cmd.py +76 -0
- kctl_github-0.2.0/src/kctl_github/commands/stats.py +208 -0
- kctl_github-0.2.0/src/kctl_github/core/__init__.py +0 -0
- kctl_github-0.2.0/src/kctl_github/core/callbacks.py +40 -0
- kctl_github-0.2.0/src/kctl_github/core/client.py +124 -0
- kctl_github-0.2.0/src/kctl_github/core/config.py +55 -0
- kctl_github-0.2.0/src/kctl_github/core/exceptions.py +21 -0
- kctl_github-0.2.0/src/kctl_github/core/plugins.py +13 -0
- kctl_github-0.2.0/tests/__init__.py +0 -0
- kctl_github-0.2.0/tests/conftest.py +59 -0
- kctl_github-0.2.0/tests/test_client.py +48 -0
- kctl_github-0.2.0/tests/test_dashboard.py +9 -0
- kctl_github-0.2.0/tests/test_smoke.py +57 -0
|
@@ -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,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)
|