kctl-api 0.2.0__py3-none-any.whl

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 (66) hide show
  1. kctl_api/__init__.py +3 -0
  2. kctl_api/__main__.py +5 -0
  3. kctl_api/cli.py +238 -0
  4. kctl_api/commands/__init__.py +1 -0
  5. kctl_api/commands/ai.py +250 -0
  6. kctl_api/commands/aliases.py +84 -0
  7. kctl_api/commands/apps.py +172 -0
  8. kctl_api/commands/auth.py +313 -0
  9. kctl_api/commands/automation.py +242 -0
  10. kctl_api/commands/build_cmd.py +87 -0
  11. kctl_api/commands/clean.py +182 -0
  12. kctl_api/commands/config_cmd.py +443 -0
  13. kctl_api/commands/dashboard.py +139 -0
  14. kctl_api/commands/db.py +599 -0
  15. kctl_api/commands/deploy.py +84 -0
  16. kctl_api/commands/deps.py +289 -0
  17. kctl_api/commands/dev.py +136 -0
  18. kctl_api/commands/docker_cmd.py +252 -0
  19. kctl_api/commands/doctor_cmd.py +286 -0
  20. kctl_api/commands/env.py +289 -0
  21. kctl_api/commands/files.py +250 -0
  22. kctl_api/commands/fmt_cmd.py +58 -0
  23. kctl_api/commands/health.py +479 -0
  24. kctl_api/commands/jobs.py +169 -0
  25. kctl_api/commands/lint_cmd.py +81 -0
  26. kctl_api/commands/logs.py +258 -0
  27. kctl_api/commands/marketplace.py +316 -0
  28. kctl_api/commands/monitor_cmd.py +243 -0
  29. kctl_api/commands/notifications.py +132 -0
  30. kctl_api/commands/odoo_proxy.py +182 -0
  31. kctl_api/commands/openapi.py +299 -0
  32. kctl_api/commands/perf.py +307 -0
  33. kctl_api/commands/rate_limit.py +223 -0
  34. kctl_api/commands/realtime.py +100 -0
  35. kctl_api/commands/redis_cmd.py +609 -0
  36. kctl_api/commands/routes_cmd.py +277 -0
  37. kctl_api/commands/saas.py +145 -0
  38. kctl_api/commands/scaffold.py +362 -0
  39. kctl_api/commands/security_cmd.py +350 -0
  40. kctl_api/commands/services.py +191 -0
  41. kctl_api/commands/shell.py +197 -0
  42. kctl_api/commands/skill_cmd.py +58 -0
  43. kctl_api/commands/streams.py +309 -0
  44. kctl_api/commands/stripe_cmd.py +105 -0
  45. kctl_api/commands/tenant_ai.py +169 -0
  46. kctl_api/commands/test_cmd.py +95 -0
  47. kctl_api/commands/users.py +302 -0
  48. kctl_api/commands/webhooks.py +56 -0
  49. kctl_api/commands/workflows.py +127 -0
  50. kctl_api/commands/ws.py +323 -0
  51. kctl_api/core/__init__.py +1 -0
  52. kctl_api/core/async_client.py +120 -0
  53. kctl_api/core/callbacks.py +88 -0
  54. kctl_api/core/client.py +190 -0
  55. kctl_api/core/config.py +260 -0
  56. kctl_api/core/db.py +65 -0
  57. kctl_api/core/exceptions.py +43 -0
  58. kctl_api/core/output.py +5 -0
  59. kctl_api/core/plugins.py +26 -0
  60. kctl_api/core/redis.py +35 -0
  61. kctl_api/core/resolve.py +47 -0
  62. kctl_api/core/utils.py +109 -0
  63. kctl_api-0.2.0.dist-info/METADATA +34 -0
  64. kctl_api-0.2.0.dist-info/RECORD +66 -0
  65. kctl_api-0.2.0.dist-info/WHEEL +4 -0
  66. kctl_api-0.2.0.dist-info/entry_points.txt +2 -0
kctl_api/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """kctl-api: Kodemeio API CLI."""
2
+
3
+ __version__ = "0.2.0"
kctl_api/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+ """Allow running as python -m kctl_api."""
2
+
3
+ from kctl_api.cli import _run
4
+
5
+ _run()
kctl_api/cli.py ADDED
@@ -0,0 +1,238 @@
1
+ """Main CLI entry point for kctl-api."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Annotated
6
+
7
+ import typer
8
+ from kctl_lib import KctlError, handle_cli_error
9
+ from kctl_lib.self_update import notify_if_outdated
10
+
11
+ from kctl_api import __version__
12
+ from kctl_api.core.callbacks import AppContext
13
+
14
+
15
+ def version_callback(value: bool) -> None:
16
+ if value:
17
+ typer.echo(f"kctl-api {__version__}")
18
+ raise typer.Exit()
19
+
20
+
21
+ app = typer.Typer(
22
+ name="kctl-api",
23
+ help="Kodemeio API CLI - manage your FastAPI platform.",
24
+ no_args_is_help=True,
25
+ rich_markup_mode="rich",
26
+ pretty_exceptions_enable=False,
27
+ )
28
+
29
+
30
+ @app.callback()
31
+ def main(
32
+ ctx: typer.Context,
33
+ json_output: Annotated[bool, typer.Option("--json", help="Output as JSON")] = False,
34
+ quiet: Annotated[bool, typer.Option("--quiet", "-q", help="Suppress info messages")] = False,
35
+ output_format: Annotated[
36
+ str, typer.Option("--format", "-f", help="Output format: pretty, json, csv, yaml")
37
+ ] = "pretty",
38
+ no_header: Annotated[bool, typer.Option("--no-header", help="Omit table headers (for scripting)")] = False,
39
+ profile: Annotated[str | None, typer.Option("--profile", "-p", help="Config profile name")] = None,
40
+ url: Annotated[str | None, typer.Option("--url", help="API URL override")] = None,
41
+ ai_url: Annotated[str | None, typer.Option("--ai-url", help="AI API URL override")] = None,
42
+ api_key: Annotated[str | None, typer.Option("--api-key", help="API key override")] = None,
43
+ database_url: Annotated[str | None, typer.Option("--database-url", help="Database URL override")] = None,
44
+ redis_url: Annotated[str | None, typer.Option("--redis-url", help="Redis URL override")] = None,
45
+ version: Annotated[
46
+ bool, typer.Option("--version", "-V", callback=version_callback, is_eager=True, help="Show version")
47
+ ] = False,
48
+ ) -> None:
49
+ """Kodemeio API CLI."""
50
+ ctx.ensure_object(dict)
51
+ ctx.obj = AppContext(
52
+ json_mode=json_output or output_format == "json",
53
+ quiet=quiet,
54
+ format=output_format,
55
+ no_header=no_header,
56
+ profile=profile,
57
+ url_override=url,
58
+ ai_url_override=ai_url,
59
+ api_key_override=api_key,
60
+ database_url_override=database_url,
61
+ redis_url_override=redis_url,
62
+ )
63
+ notify_if_outdated(ctx.obj.output, "kctl-api", __version__)
64
+
65
+
66
+ def _register_commands() -> None:
67
+ """Register all command groups (lazy imports for fast startup)."""
68
+ from kctl_api.commands.ai import app as ai_app
69
+ from kctl_api.commands.aliases import register_aliases
70
+ from kctl_api.commands.apps import app as apps_app
71
+ from kctl_api.commands.auth import app as auth_app
72
+ from kctl_api.commands.automation import app as automation_app
73
+ from kctl_api.commands.build_cmd import app as build_app
74
+ from kctl_api.commands.clean import app as clean_app
75
+ from kctl_api.commands.config_cmd import app as config_app
76
+ from kctl_api.commands.dashboard import app as dashboard_app
77
+ from kctl_api.commands.db import app as db_app
78
+ from kctl_api.commands.deploy import app as deploy_app
79
+ from kctl_api.commands.deps import app as deps_app
80
+ from kctl_api.commands.dev import app as dev_app
81
+ from kctl_api.commands.docker_cmd import app as docker_app
82
+ from kctl_api.commands.doctor_cmd import app as doctor_app
83
+ from kctl_api.commands.env import app as env_app
84
+ from kctl_api.commands.files import app as files_app
85
+ from kctl_api.commands.fmt_cmd import app as fmt_app
86
+ from kctl_api.commands.health import app as health_app
87
+ from kctl_api.commands.jobs import app as jobs_app
88
+ from kctl_api.commands.lint_cmd import app as lint_app
89
+ from kctl_api.commands.logs import app as logs_app
90
+ from kctl_api.commands.marketplace import app as marketplace_app
91
+ from kctl_api.commands.monitor_cmd import app as monitor_app
92
+ from kctl_api.commands.notifications import app as notifications_app
93
+ from kctl_api.commands.odoo_proxy import app as odoo_app
94
+ from kctl_api.commands.openapi import app as openapi_app
95
+ from kctl_api.commands.perf import app as perf_app
96
+ from kctl_api.commands.rate_limit import app as rate_limit_app
97
+ from kctl_api.commands.realtime import app as realtime_app
98
+ from kctl_api.commands.redis_cmd import app as redis_app
99
+ from kctl_api.commands.routes_cmd import app as routes_app
100
+ from kctl_api.commands.saas import app as saas_app
101
+ from kctl_api.commands.scaffold import app as scaffold_app
102
+ from kctl_api.commands.security_cmd import app as security_app
103
+ from kctl_api.commands.services import app as services_app
104
+ from kctl_api.commands.shell import app as shell_app
105
+ from kctl_api.commands.streams import app as streams_app
106
+ from kctl_api.commands.stripe_cmd import app as stripe_app
107
+ from kctl_api.commands.tenant_ai import app as tenant_ai_app
108
+ from kctl_api.commands.test_cmd import app as test_app
109
+ from kctl_api.commands.users import app as users_app
110
+ from kctl_api.commands.webhooks import app as webhooks_app
111
+ from kctl_api.commands.workflows import app as workflows_app
112
+ from kctl_api.commands.ws import app as ws_app
113
+
114
+ # Authentication & access
115
+ app.add_typer(config_app, name="config")
116
+ app.add_typer(auth_app, name="auth")
117
+ app.add_typer(health_app, name="health")
118
+
119
+ # API resources
120
+ app.add_typer(users_app, name="users")
121
+ app.add_typer(files_app, name="files")
122
+ app.add_typer(jobs_app, name="jobs")
123
+ app.add_typer(workflows_app, name="workflows")
124
+ app.add_typer(automation_app, name="automation")
125
+ app.add_typer(notifications_app, name="notifications")
126
+ app.add_typer(webhooks_app, name="webhooks")
127
+ app.add_typer(marketplace_app, name="marketplace")
128
+ app.add_typer(saas_app, name="saas")
129
+ app.add_typer(stripe_app, name="stripe")
130
+ app.add_typer(odoo_app, name="odoo")
131
+ app.add_typer(realtime_app, name="realtime")
132
+
133
+ # AI platform
134
+ app.add_typer(ai_app, name="ai")
135
+ app.add_typer(tenant_ai_app, name="tenant-ai")
136
+
137
+ # Infrastructure
138
+ app.add_typer(db_app, name="db")
139
+ app.add_typer(redis_app, name="redis")
140
+ app.add_typer(streams_app, name="streams")
141
+ app.add_typer(services_app, name="services")
142
+ app.add_typer(deploy_app, name="deploy")
143
+ app.add_typer(apps_app, name="apps")
144
+ app.add_typer(docker_app, name="docker")
145
+
146
+ # Development tools
147
+ app.add_typer(dev_app, name="dev")
148
+ app.add_typer(test_app, name="test")
149
+ app.add_typer(lint_app, name="lint")
150
+ app.add_typer(fmt_app, name="fmt")
151
+ app.add_typer(build_app, name="build")
152
+ app.add_typer(scaffold_app, name="scaffold")
153
+ app.add_typer(shell_app, name="shell")
154
+
155
+ # API analysis & testing
156
+ app.add_typer(openapi_app, name="openapi")
157
+ app.add_typer(routes_app, name="routes")
158
+ app.add_typer(rate_limit_app, name="rate-limit")
159
+ app.add_typer(ws_app, name="ws")
160
+ app.add_typer(perf_app, name="perf")
161
+
162
+ # Environment & security
163
+ app.add_typer(doctor_app, name="doctor")
164
+ app.add_typer(env_app, name="env")
165
+ app.add_typer(security_app, name="security")
166
+ app.add_typer(deps_app, name="deps")
167
+ app.add_typer(clean_app, name="clean")
168
+
169
+ # Observability & monitoring
170
+ app.add_typer(logs_app, name="logs")
171
+ app.add_typer(dashboard_app, name="dashboard")
172
+ app.add_typer(monitor_app, name="monitor")
173
+
174
+ # Skill generation
175
+ from kctl_api.commands.skill_cmd import app as skill_app
176
+
177
+ app.add_typer(skill_app, name="skill")
178
+
179
+ # Short aliases
180
+ register_aliases(app)
181
+
182
+
183
+ _register_commands()
184
+
185
+ # Load plugins via entry points
186
+ from kctl_api.core.plugins import discover_and_load_plugins # noqa: E402
187
+
188
+ discover_and_load_plugins(app)
189
+
190
+
191
+ @app.command("self-update")
192
+ def self_update_cmd(ctx: typer.Context) -> None:
193
+ """Check for updates and upgrade kctl-api."""
194
+ actx = ctx.obj
195
+ out = actx.output
196
+
197
+ from kctl_lib.self_update import check_update
198
+ from kctl_lib.self_update import update as do_update
199
+
200
+ latest = check_update("kctl-api", __version__)
201
+ if latest:
202
+ out.info(f"Updating to {latest}...")
203
+ do_update("kctl-api")
204
+ out.success(f"Updated to {latest}")
205
+ else:
206
+ out.success("Already up to date")
207
+
208
+
209
+ @app.command()
210
+ def completions(
211
+ shell: Annotated[str, typer.Argument(help="Shell type: zsh, bash, fish")] = "zsh",
212
+ install: Annotated[bool, typer.Option("--install", help="Install completions")] = False,
213
+ ) -> None:
214
+ """Generate or install shell completions."""
215
+ from kctl_lib.completions import get_completion_script, install_completions
216
+
217
+ if install:
218
+ path = install_completions("kctl-api", shell)
219
+ if path:
220
+ typer.echo(f"Completions installed to {path}")
221
+ else:
222
+ typer.echo(f"Could not install completions for {shell}", err=True)
223
+ raise typer.Exit(code=1)
224
+ else:
225
+ script = get_completion_script("kctl-api", shell)
226
+ typer.echo(script)
227
+
228
+
229
+ def _run() -> None:
230
+ """Entry point with error handling."""
231
+ try:
232
+ app()
233
+ except KctlError as e:
234
+ handle_cli_error(e)
235
+
236
+
237
+ if __name__ == "__main__":
238
+ _run()
@@ -0,0 +1 @@
1
+ """Command groups for kctl-api."""
@@ -0,0 +1,250 @@
1
+ """AI platform commands for kctl-api.
2
+
3
+ Manage copilots, chat, conversations, and AI service health.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import Annotated
9
+
10
+ import typer
11
+
12
+ from kctl_api.core.callbacks import AppContext
13
+ from kctl_api.core.exceptions import APIError, AuthenticationError
14
+ from kctl_api.core.exceptions import ConnectionError as KctlConnectionError
15
+
16
+ app = typer.Typer(name="ai", help="AI platform — copilots, chat, conversations.", no_args_is_help=True)
17
+
18
+ _BASE = "/api/v1/ai"
19
+
20
+
21
+ # ---------------------------------------------------------------------------
22
+ # copilots
23
+ # ---------------------------------------------------------------------------
24
+ @app.command()
25
+ def copilots(ctx: typer.Context) -> None:
26
+ """List available AI copilots via GET /api/v1/ai/copilots."""
27
+ actx: AppContext = ctx.obj
28
+ out = actx.output
29
+
30
+ try:
31
+ data = actx.ai_client.get(f"{_BASE}/copilots")
32
+ except (AuthenticationError, KctlConnectionError, APIError) as e:
33
+ out.error(str(e))
34
+ raise typer.Exit(1) from None
35
+
36
+ items = data if isinstance(data, list) else data.get("items", []) if isinstance(data, dict) else []
37
+
38
+ rows: list[list[str]] = []
39
+ for cp in items:
40
+ rows.append(
41
+ [
42
+ str(cp.get("id", "")),
43
+ cp.get("name", ""),
44
+ cp.get("model", ""),
45
+ cp.get("description", "")[:50],
46
+ "[green]active[/green]" if cp.get("active", True) else "[dim]inactive[/dim]",
47
+ ]
48
+ )
49
+
50
+ out.table(
51
+ title="AI Copilots",
52
+ columns=[
53
+ ("ID", "bold"),
54
+ ("Name", ""),
55
+ ("Model", ""),
56
+ ("Description", "dim"),
57
+ ("Status", ""),
58
+ ],
59
+ rows=rows,
60
+ data_for_json=items,
61
+ )
62
+
63
+
64
+ # ---------------------------------------------------------------------------
65
+ # health
66
+ # ---------------------------------------------------------------------------
67
+ @app.command()
68
+ def health(ctx: typer.Context) -> None:
69
+ """Check AI service health via GET /api/v1/ai/health."""
70
+ actx: AppContext = ctx.obj
71
+ out = actx.output
72
+
73
+ try:
74
+ data = actx.ai_client.get(f"{_BASE}/health")
75
+ except (AuthenticationError, KctlConnectionError, APIError) as e:
76
+ out.error(str(e))
77
+ raise typer.Exit(1) from None
78
+
79
+ if not data:
80
+ out.error("No health data from ai-main.")
81
+ raise typer.Exit(1)
82
+
83
+ status = data.get("status", "unknown")
84
+ healthy = status == "ok"
85
+
86
+ out.detail(
87
+ title="AI Service Health",
88
+ sections=[
89
+ (
90
+ "Status",
91
+ [
92
+ ("Status", "[green]ok[/green]" if healthy else f"[red]{status}[/red]"),
93
+ *[(k, str(v)) for k, v in data.items() if k != "status"],
94
+ ],
95
+ ),
96
+ ],
97
+ data_for_json=data,
98
+ )
99
+
100
+ if not healthy:
101
+ raise typer.Exit(1)
102
+
103
+
104
+ # ---------------------------------------------------------------------------
105
+ # chat
106
+ # ---------------------------------------------------------------------------
107
+ @app.command()
108
+ def chat(
109
+ ctx: typer.Context,
110
+ message: Annotated[str, typer.Argument(help="Message to send to the copilot.")],
111
+ copilot: Annotated[str, typer.Option("--copilot", "-c", help="Copilot ID or name.")] = "default",
112
+ conversation_id: Annotated[
113
+ str | None, typer.Option("--conversation", help="Existing conversation ID to continue.")
114
+ ] = None,
115
+ ) -> None:
116
+ """Send a chat message to an AI copilot via POST /api/v1/ai/chat."""
117
+ actx: AppContext = ctx.obj
118
+ out = actx.output
119
+
120
+ payload: dict = {"message": message, "copilot": copilot}
121
+ if conversation_id:
122
+ payload["conversation_id"] = conversation_id
123
+
124
+ try:
125
+ result = actx.ai_client.post(f"{_BASE}/chat", json=payload)
126
+ except (AuthenticationError, KctlConnectionError, APIError) as e:
127
+ out.error(str(e))
128
+ raise typer.Exit(1) from None
129
+
130
+ if actx.json_mode:
131
+ out.raw_json(result)
132
+ else:
133
+ reply = result.get("reply", result.get("message", "")) if isinstance(result, dict) else str(result)
134
+ out.text(reply)
135
+
136
+
137
+ # ---------------------------------------------------------------------------
138
+ # conversations
139
+ # ---------------------------------------------------------------------------
140
+ @app.command()
141
+ def conversations(
142
+ ctx: typer.Context,
143
+ page: Annotated[int, typer.Option("--page", "-p", help="Page number.")] = 1,
144
+ per_page: Annotated[int, typer.Option("--per-page", "-n", help="Items per page.")] = 20,
145
+ ) -> None:
146
+ """List AI conversations via GET /api/v1/ai/conversations."""
147
+ actx: AppContext = ctx.obj
148
+ out = actx.output
149
+
150
+ try:
151
+ data = actx.ai_client.get(f"{_BASE}/conversations", params={"page": page, "per_page": per_page})
152
+ except (AuthenticationError, KctlConnectionError, APIError) as e:
153
+ out.error(str(e))
154
+ raise typer.Exit(1) from None
155
+
156
+ items = data.get("items", []) if isinstance(data, dict) else []
157
+ total = data.get("total", len(items)) if isinstance(data, dict) else len(items)
158
+
159
+ rows: list[list[str]] = []
160
+ for c in items:
161
+ rows.append(
162
+ [
163
+ str(c.get("id", "")),
164
+ c.get("copilot", ""),
165
+ str(c.get("message_count", "")),
166
+ str(c.get("created_at", "")),
167
+ ]
168
+ )
169
+
170
+ out.table(
171
+ title=f"AI Conversations (page {page}, {total} total)",
172
+ columns=[
173
+ ("ID", "bold"),
174
+ ("Copilot", ""),
175
+ ("Messages", ""),
176
+ ("Created", "dim"),
177
+ ],
178
+ rows=rows,
179
+ data_for_json=items,
180
+ )
181
+
182
+
183
+ # ---------------------------------------------------------------------------
184
+ # conversation
185
+ # ---------------------------------------------------------------------------
186
+ @app.command()
187
+ def conversation(
188
+ ctx: typer.Context,
189
+ conversation_id: Annotated[str, typer.Argument(help="Conversation ID.")],
190
+ ) -> None:
191
+ """Get conversation details and messages via GET /api/v1/ai/conversations/{id}."""
192
+ actx: AppContext = ctx.obj
193
+ out = actx.output
194
+
195
+ try:
196
+ data = actx.ai_client.get(f"{_BASE}/conversations/{conversation_id}")
197
+ except (AuthenticationError, KctlConnectionError, APIError) as e:
198
+ out.error(str(e))
199
+ raise typer.Exit(1) from None
200
+
201
+ if not data:
202
+ out.error(f"Conversation not found: {conversation_id}")
203
+ raise typer.Exit(1)
204
+
205
+ out.detail(
206
+ title=f"Conversation: {conversation_id}",
207
+ sections=[
208
+ ("Details", [(k, str(v)) for k, v in data.items() if k != "messages"]),
209
+ ],
210
+ data_for_json=data,
211
+ )
212
+
213
+ # Print messages if present
214
+ messages = data.get("messages", [])
215
+ if messages and not actx.json_mode:
216
+ out.header("Messages")
217
+ for msg in messages:
218
+ role = msg.get("role", "unknown")
219
+ content = msg.get("content", "")
220
+ out.text(f" [{role}] {content}")
221
+
222
+
223
+ # ---------------------------------------------------------------------------
224
+ # delete-conversation
225
+ # ---------------------------------------------------------------------------
226
+ @app.command(name="delete-conversation")
227
+ def delete_conversation(
228
+ ctx: typer.Context,
229
+ conversation_id: Annotated[str, typer.Argument(help="Conversation ID to delete.")],
230
+ force: Annotated[bool, typer.Option("--force", "-f", help="Skip confirmation.")] = False,
231
+ ) -> None:
232
+ """Delete an AI conversation via DELETE /api/v1/ai/conversations/{id}."""
233
+ actx: AppContext = ctx.obj
234
+ out = actx.output
235
+
236
+ if not force:
237
+ confirm = typer.confirm(f"Delete conversation {conversation_id}?", default=False)
238
+ if not confirm:
239
+ out.info("Cancelled.")
240
+ raise typer.Exit(0)
241
+
242
+ try:
243
+ result = actx.ai_client.delete(f"{_BASE}/conversations/{conversation_id}")
244
+ except (AuthenticationError, KctlConnectionError, APIError) as e:
245
+ out.error(str(e))
246
+ raise typer.Exit(1) from None
247
+
248
+ out.success(f"Conversation {conversation_id} deleted.")
249
+ if actx.json_mode:
250
+ out.raw_json(result)
@@ -0,0 +1,84 @@
1
+ """Short alias commands for kctl-api.
2
+
3
+ Hidden commands that delegate to longer command paths.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import subprocess
9
+
10
+ import typer
11
+
12
+ from kctl_api.core.callbacks import AppContext
13
+
14
+
15
+ def register_aliases(app: typer.Typer) -> None:
16
+ """Register hidden short-alias commands on the main app."""
17
+
18
+ def _base_cmd(ctx: typer.Context) -> list[str]:
19
+ cmd = ["kctl-api"]
20
+ actx: AppContext = ctx.obj
21
+ if actx.profile:
22
+ cmd.extend(["-p", actx.profile])
23
+ if actx.json_mode:
24
+ cmd.append("--json")
25
+ if actx.quiet:
26
+ cmd.append("-q")
27
+ if actx.format != "pretty" and not actx.json_mode:
28
+ cmd.extend(["-f", actx.format])
29
+ if actx.no_header:
30
+ cmd.append("--no-header")
31
+ if actx.url_override:
32
+ cmd.extend(["--url", actx.url_override])
33
+ if actx.ai_url_override:
34
+ cmd.extend(["--ai-url", actx.ai_url_override])
35
+ if actx.api_key_override:
36
+ cmd.extend(["--api-key", actx.api_key_override])
37
+ if actx.database_url_override:
38
+ cmd.extend(["--database-url", actx.database_url_override])
39
+ if actx.redis_url_override:
40
+ cmd.extend(["--redis-url", actx.redis_url_override])
41
+ return cmd
42
+
43
+ @app.command("hc", hidden=True, help="Alias: health all")
44
+ def hc(ctx: typer.Context) -> None:
45
+ subprocess.run([*_base_cmd(ctx), "health", "all"])
46
+
47
+ @app.command("dl", hidden=True, help="Alias: deploy logs <app>")
48
+ def dl(
49
+ ctx: typer.Context,
50
+ app_name: str = typer.Argument("api-main", help="App name."),
51
+ ) -> None:
52
+ subprocess.run([*_base_cmd(ctx), "deploy", "logs", app_name])
53
+
54
+ @app.command("ds", hidden=True, help="Alias: deploy status")
55
+ def ds(ctx: typer.Context) -> None:
56
+ subprocess.run([*_base_cmd(ctx), "deploy", "status"])
57
+
58
+ @app.command("ul", hidden=True, help="Alias: users list")
59
+ def ul(ctx: typer.Context) -> None:
60
+ subprocess.run([*_base_cmd(ctx), "users", "list"])
61
+
62
+ @app.command("fl", hidden=True, help="Alias: files list")
63
+ def fl(ctx: typer.Context) -> None:
64
+ subprocess.run([*_base_cmd(ctx), "files", "list"])
65
+
66
+ @app.command("jo", hidden=True, help="Alias: jobs overview")
67
+ def jo(ctx: typer.Context) -> None:
68
+ subprocess.run([*_base_cmd(ctx), "jobs", "overview"])
69
+
70
+ @app.command("du", hidden=True, help="Alias: dev up")
71
+ def du(ctx: typer.Context) -> None:
72
+ subprocess.run([*_base_cmd(ctx), "dev", "up"])
73
+
74
+ @app.command("dd", hidden=True, help="Alias: dev down")
75
+ def dd(ctx: typer.Context) -> None:
76
+ subprocess.run([*_base_cmd(ctx), "dev", "down"])
77
+
78
+ @app.command("dr", hidden=True, help="Alias: dev rebuild")
79
+ def dr(ctx: typer.Context) -> None:
80
+ subprocess.run([*_base_cmd(ctx), "dev", "rebuild"])
81
+
82
+ @app.command("tr", hidden=True, help="Alias: test run all")
83
+ def tr(ctx: typer.Context) -> None:
84
+ subprocess.run([*_base_cmd(ctx), "test", "run", "all"])