kctl-litellm 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.
@@ -0,0 +1,33 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ *.egg
6
+ dist/
7
+ build/
8
+ .eggs/
9
+
10
+ # Virtual environments
11
+ .venv/
12
+ venv/
13
+
14
+ # IDE
15
+ .idea/
16
+ .vscode/
17
+ *.swp
18
+ *.swo
19
+
20
+ # Testing
21
+ .pytest_cache/
22
+ .coverage
23
+ htmlcov/
24
+ .mypy_cache/
25
+ .ruff_cache/
26
+
27
+ # OS
28
+ .DS_Store
29
+ Thumbs.db
30
+
31
+ # Environment
32
+ .env
33
+ .env.local
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: kctl-litellm
3
+ Version: 0.2.0
4
+ Summary: Kodemeio LiteLLM CLI — manage LiteLLM proxy instances
5
+ Requires-Python: >=3.12
6
+ Requires-Dist: httpx>=0.27.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>=8.3.0; extra == 'dev'
15
+ Requires-Dist: ruff>=0.9.0; extra == 'dev'
16
+ Requires-Dist: types-pyyaml>=6.0.0; extra == 'dev'
@@ -0,0 +1,78 @@
1
+ # kctl-litellm
2
+
3
+ Kodemeio CLI for managing [LiteLLM](https://github.com/BerriAI/litellm) proxy instances. Part of the [kodemeio-platform](https://github.com/tgunawandev/kodemeio-platform) monorepo.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ uv pip install -e packages/kctl-litellm
9
+ ```
10
+
11
+ ## Configuration
12
+
13
+ Add a profile to `~/.config/kodemeio/config.yaml`:
14
+
15
+ ```yaml
16
+ profiles:
17
+ production:
18
+ litellm:
19
+ url: https://llm.terakidz.com
20
+ master_key: ${KCTL_LITELLM_MASTER_KEY}
21
+ container_name: litellm
22
+ ```
23
+
24
+ Or use the CLI:
25
+
26
+ ```bash
27
+ kctl-litellm config add production --url https://llm.terakidz.com --master-key sk-...
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ ```bash
33
+ # Health
34
+ kctl-litellm health check # Full health check with model status
35
+ kctl-litellm health ping # Quick connectivity check
36
+ kctl-litellm health liveliness # Liveness probe (no auth)
37
+
38
+ # Models
39
+ kctl-litellm models list # List available models
40
+ kctl-litellm models info # Detailed model info with pricing
41
+
42
+ # Key Management
43
+ kctl-litellm keys generate --key-alias my-app --max-budget 100
44
+ kctl-litellm keys list
45
+ kctl-litellm keys info <token>
46
+ kctl-litellm keys delete <token>
47
+
48
+ # Teams & Budgets
49
+ kctl-litellm teams create --team-alias engineering --max-budget 500
50
+ kctl-litellm teams list
51
+ kctl-litellm budgets create --budget-id dev-tier --max-budget 200
52
+ kctl-litellm budgets list
53
+
54
+ # Spend & Logs
55
+ kctl-litellm spend summary # Total spend by model
56
+ kctl-litellm spend daily # Daily activity (last 7 days)
57
+ kctl-litellm logs list --limit 50 # Recent request logs
58
+
59
+ # Config
60
+ kctl-litellm config profiles # List profiles
61
+ kctl-litellm config show # Show current config (secrets masked)
62
+ kctl-litellm config use staging # Switch default profile
63
+ ```
64
+
65
+ ## Global Options
66
+
67
+ ```
68
+ --json Output as JSON
69
+ --quiet, -q Suppress info messages
70
+ --profile, -p Config profile name
71
+ --url LiteLLM URL override
72
+ --version, -V Show version
73
+ ```
74
+
75
+ ## Dependencies
76
+
77
+ - Python >= 3.12
78
+ - [kctl-lib](../kctl-lib) >= 0.4.0
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "kctl-litellm"
7
+ version = "0.2.0"
8
+ description = "Kodemeio LiteLLM CLI — manage LiteLLM proxy instances"
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.27.0",
17
+ ]
18
+
19
+ [project.optional-dependencies]
20
+ dev = [
21
+ "pytest>=8.3.0",
22
+ "ruff>=0.9.0",
23
+ "mypy>=1.14.0",
24
+ "types-PyYAML>=6.0.0",
25
+ ]
26
+
27
+ [project.scripts]
28
+ kctl-litellm = "kctl_litellm.cli:_run"
29
+
30
+ [tool.uv.sources]
31
+ kctl-lib = { workspace = true }
32
+
33
+ [tool.hatch.build.targets.wheel]
34
+ packages = ["src/kctl_litellm"]
35
+
36
+ [tool.ruff]
37
+ target-version = "py312"
38
+ line-length = 120
39
+
40
+ [tool.mypy]
41
+ python_version = "3.12"
42
+ strict = true
@@ -0,0 +1,149 @@
1
+ ---
2
+ name: litellm-admin
3
+ description: >
4
+ LiteLLM proxy administration via kctl-litellm CLI (7 groups, ~20 commands).
5
+ MUST use for ANY kctl-litellm operation.
6
+ Triggers on: "budget", "config", "health", "kctl-litellm", "keys", "litellm", "llm",
7
+ "logs", "models", "profile", "proxy", "spend", "teams", "tokens", "virtual-key".
8
+ Auto-generated: 2026-05-03
9
+ ---
10
+
11
+ # litellm-admin -- kctl-litellm CLI Reference
12
+
13
+ > Auto-generated from `kctl-litellm` command registry. Do not edit manually.
14
+ > To regenerate: `kctl-litellm skill generate`
15
+ > To add custom content: edit `SKILL.extra.md` in the same directory.
16
+
17
+ ## Overview
18
+
19
+ **CLI:** `kctl-litellm`
20
+ **Command groups:** 7
21
+ **Total commands:** ~20
22
+ **Install:** `cd packages/kctl-litellm && uv tool install --editable .`
23
+
24
+ ## Global Options
25
+
26
+ | Flag | Description |
27
+ |------|-------------|
28
+ | `--json` | Output as JSON |
29
+ | `--quiet`, `-q` | Suppress info messages |
30
+ | `--profile`, `-p` | Config profile name |
31
+ | `--url` | LiteLLM URL override |
32
+ | `--version`, `-V` | Show version |
33
+
34
+ ## Command Reference
35
+
36
+ ### `kctl-litellm health` (Services)
37
+
38
+ Check LiteLLM proxy service health.
39
+
40
+ | Command | Description |
41
+ |---------|-------------|
42
+ | `health check` | Full health check via /health endpoint. Shows healthy and unhealthy models. |
43
+ | `health ping` | Quick connectivity check (GET /, status code and response time). |
44
+ | `health liveliness` | Quick liveness check via /health/liveliness (no auth required). |
45
+
46
+ ### `kctl-litellm models` (Services)
47
+
48
+ Manage and inspect LiteLLM models.
49
+
50
+ | Command | Description |
51
+ |---------|-------------|
52
+ | `models list` | List available models via /v1/models. |
53
+ | `models info [<model>]` | Show detailed model info with pricing via /model/info. |
54
+
55
+ ### `kctl-litellm keys` (Key Management)
56
+
57
+ Manage LiteLLM virtual keys.
58
+
59
+ | Command | Description |
60
+ |---------|-------------|
61
+ | `keys generate [--team-id] [--max-budget] [--duration] [--models] [--key-alias]` | Generate a new virtual key. |
62
+ | `keys list` | List all virtual keys. |
63
+ | `keys info <key>` | Show detailed key information (key value masked). |
64
+ | `keys update <key> [--max-budget] [--models] [--key-alias] [--duration]` | Update a virtual key's settings. |
65
+ | `keys delete <key> [--force]` | Delete a virtual key. |
66
+
67
+ ### `kctl-litellm teams` (Teams & Budgets)
68
+
69
+ Manage LiteLLM teams.
70
+
71
+ | Command | Description |
72
+ |---------|-------------|
73
+ | `teams create --team-alias <alias> [--max-budget] [--models]` | Create a new team. |
74
+ | `teams list` | List all teams. |
75
+ | `teams update <team_id> [--team-alias] [--max-budget] [--models]` | Update a team's settings. |
76
+ | `teams delete <team_id> [--force]` | Delete a team. |
77
+
78
+ ### `kctl-litellm budgets` (Teams & Budgets)
79
+
80
+ Manage LiteLLM budgets.
81
+
82
+ | Command | Description |
83
+ |---------|-------------|
84
+ | `budgets create [--budget-id] [--max-budget] [--soft-budget] [--tpm-limit] [--rpm-limit]` | Create a new budget with rate limits. |
85
+ | `budgets list` | List all budgets. |
86
+
87
+ ### `kctl-litellm spend` (Usage & Spend)
88
+
89
+ Track LiteLLM spend and usage.
90
+
91
+ | Command | Description |
92
+ |---------|-------------|
93
+ | `spend summary` | Show total spend summary aggregated by model. |
94
+ | `spend daily [--start-date] [--end-date]` | Show daily activity (requests, spend, tokens). |
95
+
96
+ ### `kctl-litellm logs` (Usage & Spend)
97
+
98
+ View LiteLLM request spend logs.
99
+
100
+ | Command | Description |
101
+ |---------|-------------|
102
+ | `logs list [--limit] [--start-date] [--end-date] [--api-key]` | List spend logs from /global/spend/logs. |
103
+
104
+ ### `kctl-litellm config` (Admin & Config)
105
+
106
+ Manage CLI configuration and profiles.
107
+
108
+ | Command | Description |
109
+ |---------|-------------|
110
+ | `config init` | Initialize CLI configuration. |
111
+ | `config show` | Show configuration (keys masked). |
112
+ | `config add` | Add a new profile. |
113
+ | `config use` | Switch default profile. |
114
+ | `config remove` | Remove a profile. |
115
+
116
+ ## Configuration
117
+
118
+ Shared config: `~/.config/kodemeio/config.yaml`
119
+
120
+ ```bash
121
+ kctl-litellm config init # Interactive setup
122
+ kctl-litellm config show # Show current config
123
+ kctl-litellm self-update # Check for updates
124
+ kctl-litellm completions zsh --install # Install shell completions
125
+ ```
126
+
127
+ **Examples:**
128
+ ```bash
129
+ # Check proxy health
130
+ kctl-litellm health check -p kodemeio
131
+
132
+ # List all available models with pricing
133
+ kctl-litellm models info -p kodemeio
134
+
135
+ # Generate a virtual key for an app
136
+ kctl-litellm keys generate --key-alias my-app --max-budget 100 --duration 30d -p kodemeio
137
+
138
+ # Create a team with a budget
139
+ kctl-litellm teams create --team-alias engineering --max-budget 500 -p kodemeio
140
+
141
+ # View spend breakdown by model
142
+ kctl-litellm spend summary -p kodemeio
143
+
144
+ # View daily activity for the last 7 days
145
+ kctl-litellm spend daily -p kodemeio
146
+
147
+ # Browse recent request logs
148
+ kctl-litellm logs list --limit 50 -p kodemeio
149
+ ```
@@ -0,0 +1,3 @@
1
+ """Kodemeio LiteLLM CLI."""
2
+
3
+ __version__ = "0.2.0"
@@ -0,0 +1,5 @@
1
+ """Allow running as: python -m kctl_litellm."""
2
+
3
+ from kctl_litellm.cli import _run
4
+
5
+ _run()
@@ -0,0 +1,126 @@
1
+ """Main CLI entry point for kctl-litellm."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Annotated
6
+
7
+ import typer
8
+ from kctl_lib import handle_cli_error
9
+
10
+ from kctl_litellm import __version__
11
+ from kctl_litellm.commands.budgets_cmd import app as budgets_app
12
+ from kctl_litellm.commands.config_cmd import app as config_app
13
+ from kctl_litellm.commands.health_cmd import app as health_app
14
+ from kctl_litellm.commands.keys_cmd import app as keys_app
15
+ from kctl_litellm.commands.logs_cmd import app as logs_app
16
+ from kctl_litellm.commands.models_cmd import app as models_app
17
+ from kctl_litellm.commands.spend_cmd import app as spend_app
18
+ from kctl_litellm.commands.teams_cmd import app as teams_app
19
+ from kctl_litellm.core.callbacks import AppContext
20
+ from kctl_litellm.core.exceptions import LiteLLMError
21
+ from kctl_lib.self_update import notify_if_outdated
22
+
23
+
24
+ def version_callback(value: bool) -> None:
25
+ if value:
26
+ typer.echo(f"kctl-litellm {__version__}")
27
+ raise typer.Exit()
28
+
29
+
30
+ app = typer.Typer(
31
+ name="kctl-litellm",
32
+ help="Kodemeio LiteLLM CLI - manage LiteLLM proxy instances.",
33
+ no_args_is_help=True,
34
+ rich_markup_mode="rich",
35
+ pretty_exceptions_enable=False,
36
+ )
37
+
38
+
39
+ @app.callback()
40
+ def main(
41
+ ctx: typer.Context,
42
+ json_output: Annotated[bool, typer.Option("--json", help="Output as JSON")] = False,
43
+ quiet: Annotated[bool, typer.Option("--quiet", "-q", help="Suppress info messages")] = False,
44
+ profile: Annotated[str | None, typer.Option("--profile", "-p", help="Config profile name")] = None,
45
+ url: Annotated[str | None, typer.Option("--url", help="LiteLLM URL override")] = None,
46
+ version: Annotated[
47
+ bool, typer.Option("--version", "-V", callback=version_callback, is_eager=True, help="Show version")
48
+ ] = False,
49
+ ) -> None:
50
+ """Kodemeio LiteLLM CLI."""
51
+ ctx.ensure_object(dict)
52
+ ctx.obj = AppContext(
53
+ json_mode=json_output,
54
+ quiet=quiet,
55
+ profile=profile,
56
+ url_override=url,
57
+ )
58
+ notify_if_outdated(ctx.obj.output, "kctl-litellm", __version__)
59
+
60
+
61
+ _P_ADMIN = "Admin & Config"
62
+ app.add_typer(config_app, name="config", rich_help_panel=_P_ADMIN)
63
+
64
+ _P_SERVICES = "Services"
65
+ app.add_typer(health_app, name="health", rich_help_panel=_P_SERVICES)
66
+ app.add_typer(models_app, name="models", rich_help_panel=_P_SERVICES)
67
+
68
+ _P_KEYS = "Key Management"
69
+ app.add_typer(keys_app, name="keys", rich_help_panel=_P_KEYS)
70
+
71
+ _P_TEAMS = "Teams & Budgets"
72
+ app.add_typer(teams_app, name="teams", rich_help_panel=_P_TEAMS)
73
+ app.add_typer(budgets_app, name="budgets", rich_help_panel=_P_TEAMS)
74
+
75
+ _P_USAGE = "Usage & Spend"
76
+ app.add_typer(spend_app, name="spend", rich_help_panel=_P_USAGE)
77
+ app.add_typer(logs_app, name="logs", rich_help_panel=_P_USAGE)
78
+
79
+
80
+ @app.command("self-update")
81
+ def self_update_cmd(ctx: typer.Context) -> None:
82
+ """Check for updates and upgrade kctl-litellm."""
83
+ actx = ctx.obj
84
+ out = actx.output
85
+ from kctl_lib.self_update import check_update
86
+ from kctl_lib.self_update import update as do_update
87
+
88
+ latest = check_update("kctl-litellm", __version__)
89
+ if latest:
90
+ out.info(f"Updating to {latest}...")
91
+ do_update("kctl-litellm")
92
+ out.success(f"Updated to {latest}")
93
+ else:
94
+ out.success("Already up to date")
95
+
96
+
97
+ @app.command()
98
+ def completions(
99
+ shell: Annotated[str, typer.Argument(help="Shell type: zsh, bash, fish")] = "zsh",
100
+ install: Annotated[bool, typer.Option("--install", help="Install completions")] = False,
101
+ ) -> None:
102
+ """Generate or install shell completions."""
103
+ from kctl_lib.completions import get_completion_script, install_completions
104
+
105
+ if install:
106
+ path = install_completions("kctl-litellm", shell)
107
+ if path:
108
+ typer.echo(f"Completions installed to {path}")
109
+ else:
110
+ typer.echo(f"Could not install completions for {shell}", err=True)
111
+ raise typer.Exit(code=1)
112
+ else:
113
+ script = get_completion_script("kctl-litellm", shell)
114
+ typer.echo(script)
115
+
116
+
117
+ def _run() -> None:
118
+ """Entry point with error handling."""
119
+ try:
120
+ app()
121
+ except LiteLLMError as e:
122
+ handle_cli_error(e)
123
+
124
+
125
+ if __name__ == "__main__":
126
+ _run()
@@ -0,0 +1 @@
1
+ """CLI command modules for kctl-litellm."""
@@ -0,0 +1,116 @@
1
+ """Budget management commands for kctl-litellm.
2
+
3
+ Create and list LiteLLM budgets.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import Annotated
9
+
10
+ import typer
11
+ from rich import print as rprint
12
+ from rich.table import Table
13
+
14
+ from kctl_litellm.core.callbacks import AppContext
15
+ from kctl_litellm.core.client import LiteLLMClient
16
+ from kctl_litellm.core.exceptions import LiteLLMError
17
+
18
+ app = typer.Typer(help="Manage LiteLLM budgets.")
19
+
20
+
21
+ def _get_client(actx: AppContext) -> LiteLLMClient:
22
+ cfg = actx.config
23
+ return LiteLLMClient(base_url=cfg.url, master_key=cfg.master_key)
24
+
25
+
26
+ @app.command()
27
+ def create(
28
+ ctx: typer.Context,
29
+ budget_id: Annotated[str | None, typer.Option("--budget-id", help="Budget identifier.")] = None,
30
+ max_budget: Annotated[float, typer.Option("--max-budget", help="Maximum budget in USD.")] = 100.0,
31
+ soft_budget: Annotated[
32
+ float | None, typer.Option("--soft-budget", help="Soft budget limit (warning threshold).")
33
+ ] = None,
34
+ max_parallel_requests: Annotated[
35
+ int | None, typer.Option("--max-parallel-requests", help="Max parallel requests.")
36
+ ] = None,
37
+ tpm_limit: Annotated[int | None, typer.Option("--tpm-limit", help="Tokens per minute limit.")] = None,
38
+ rpm_limit: Annotated[int | None, typer.Option("--rpm-limit", help="Requests per minute limit.")] = None,
39
+ ) -> None:
40
+ """Create a new budget.
41
+
42
+ Example: kctl-litellm budgets create --budget-id dev-budget --max-budget 200
43
+ """
44
+ actx: AppContext = ctx.obj
45
+ out = actx.output
46
+
47
+ kwargs: dict = {"max_budget": max_budget}
48
+ if budget_id:
49
+ kwargs["budget_id"] = budget_id
50
+ if soft_budget is not None:
51
+ kwargs["soft_budget"] = soft_budget
52
+ if max_parallel_requests is not None:
53
+ kwargs["max_parallel_requests"] = max_parallel_requests
54
+ if tpm_limit is not None:
55
+ kwargs["tpm_limit"] = tpm_limit
56
+ if rpm_limit is not None:
57
+ kwargs["rpm_limit"] = rpm_limit
58
+
59
+ try:
60
+ client = _get_client(actx)
61
+ result = client.create_budget(**kwargs)
62
+ client.close()
63
+ except LiteLLMError as exc:
64
+ out.error(str(exc))
65
+ raise typer.Exit(1) from exc
66
+
67
+ out.success("Budget created")
68
+ out.kv("Budget ID", result.get("budget_id", "[dim]auto-generated[/dim]"))
69
+ out.kv("Max Budget", f"${result.get('max_budget', max_budget)}")
70
+ if result.get("soft_budget") is not None:
71
+ out.kv("Soft Budget", f"${result['soft_budget']}")
72
+ if result.get("tpm_limit"):
73
+ out.kv("TPM Limit", str(result["tpm_limit"]))
74
+ if result.get("rpm_limit"):
75
+ out.kv("RPM Limit", str(result["rpm_limit"]))
76
+
77
+
78
+ @app.command("list")
79
+ def list_(ctx: typer.Context) -> None:
80
+ """List all budgets."""
81
+ actx: AppContext = ctx.obj
82
+ out = actx.output
83
+
84
+ try:
85
+ client = _get_client(actx)
86
+ budgets = client.list_budgets()
87
+ client.close()
88
+ except LiteLLMError as exc:
89
+ out.error(str(exc))
90
+ raise typer.Exit(1) from exc
91
+
92
+ if not budgets:
93
+ out.warn("No budgets found.")
94
+ return
95
+
96
+ table = Table(title="Budgets", show_header=True, header_style="bold cyan")
97
+ table.add_column("Budget ID", style="cyan")
98
+ table.add_column("Max Budget", justify="right")
99
+ table.add_column("Soft Budget", justify="right")
100
+ table.add_column("TPM Limit", justify="right")
101
+ table.add_column("RPM Limit", justify="right")
102
+ table.add_column("Max Parallel", justify="right")
103
+
104
+ for b in budgets:
105
+ if not isinstance(b, dict):
106
+ continue
107
+ budget_id = b.get("budget_id", "unknown")
108
+ max_b = f"${b['max_budget']:.2f}" if b.get("max_budget") is not None else "[dim]-[/dim]"
109
+ soft_b = f"${b['soft_budget']:.2f}" if b.get("soft_budget") is not None else "[dim]-[/dim]"
110
+ tpm = str(b.get("tpm_limit", "")) or "[dim]-[/dim]"
111
+ rpm = str(b.get("rpm_limit", "")) or "[dim]-[/dim]"
112
+ max_par = str(b.get("max_parallel_requests", "")) or "[dim]-[/dim]"
113
+ table.add_row(budget_id, max_b, soft_b, tpm, rpm, max_par)
114
+
115
+ rprint(table)
116
+ out.info(f"Total budgets: {len(budgets)}")