kctl-api 0.7.4__tar.gz → 0.9.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 (80) hide show
  1. {kctl_api-0.7.4 → kctl_api-0.9.0}/.gitignore +3 -0
  2. {kctl_api-0.7.4 → kctl_api-0.9.0}/PKG-INFO +2 -2
  3. {kctl_api-0.7.4 → kctl_api-0.9.0}/pyproject.toml +2 -2
  4. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/__init__.py +1 -1
  5. kctl_api-0.9.0/src/kctl_api/_register.py +127 -0
  6. kctl_api-0.9.0/src/kctl_api/cli.py +130 -0
  7. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/doctor_cmd.py +76 -0
  8. {kctl_api-0.7.4 → kctl_api-0.9.0}/tests/test_config_cmd.py +1 -3
  9. kctl_api-0.9.0/tests/test_doctor_ai_summary.py +16 -0
  10. {kctl_api-0.7.4 → kctl_api-0.9.0}/tests/test_users.py +0 -1
  11. kctl_api-0.7.4/src/kctl_api/cli.py +0 -245
  12. {kctl_api-0.7.4 → kctl_api-0.9.0}/README.md +0 -0
  13. {kctl_api-0.7.4 → kctl_api-0.9.0}/skills/api-admin/SKILL.md +0 -0
  14. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/__main__.py +0 -0
  15. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/__init__.py +0 -0
  16. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/ai.py +0 -0
  17. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/aliases.py +0 -0
  18. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/apps.py +0 -0
  19. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/auth.py +0 -0
  20. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/automation.py +0 -0
  21. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/build_cmd.py +0 -0
  22. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/clean.py +0 -0
  23. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/config_cmd.py +0 -0
  24. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/dashboard.py +0 -0
  25. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/db.py +0 -0
  26. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/deploy.py +0 -0
  27. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/deps.py +0 -0
  28. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/dev.py +0 -0
  29. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/docker_cmd.py +0 -0
  30. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/env.py +0 -0
  31. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/files.py +0 -0
  32. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/fmt_cmd.py +0 -0
  33. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/health.py +0 -0
  34. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/jobs.py +0 -0
  35. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/lint_cmd.py +0 -0
  36. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/logs.py +0 -0
  37. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/marketplace.py +0 -0
  38. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/monitor_cmd.py +0 -0
  39. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/notifications.py +0 -0
  40. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/odoo_proxy.py +0 -0
  41. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/openapi.py +0 -0
  42. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/perf.py +0 -0
  43. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/rate_limit.py +0 -0
  44. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/realtime.py +0 -0
  45. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/redis_cmd.py +0 -0
  46. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/routes_cmd.py +0 -0
  47. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/saas.py +0 -0
  48. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/scaffold.py +0 -0
  49. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/security_cmd.py +0 -0
  50. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/services.py +0 -0
  51. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/shell.py +0 -0
  52. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/skill_cmd.py +0 -0
  53. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/streams.py +0 -0
  54. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/stripe_cmd.py +0 -0
  55. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/tenant_ai.py +0 -0
  56. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/test_cmd.py +0 -0
  57. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/users.py +0 -0
  58. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/webhooks.py +0 -0
  59. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/workflows.py +0 -0
  60. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/commands/ws.py +0 -0
  61. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/core/__init__.py +0 -0
  62. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/core/async_client.py +0 -0
  63. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/core/callbacks.py +0 -0
  64. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/core/client.py +0 -0
  65. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/core/config.py +0 -0
  66. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/core/db.py +0 -0
  67. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/core/exceptions.py +0 -0
  68. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/core/output.py +0 -0
  69. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/core/plugins.py +0 -0
  70. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/core/redis.py +0 -0
  71. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/core/resolve.py +0 -0
  72. {kctl_api-0.7.4 → kctl_api-0.9.0}/src/kctl_api/core/utils.py +0 -0
  73. {kctl_api-0.7.4 → kctl_api-0.9.0}/tests/conftest.py +0 -0
  74. {kctl_api-0.7.4 → kctl_api-0.9.0}/tests/test_auth.py +0 -0
  75. {kctl_api-0.7.4 → kctl_api-0.9.0}/tests/test_cli.py +0 -0
  76. {kctl_api-0.7.4 → kctl_api-0.9.0}/tests/test_core.py +0 -0
  77. {kctl_api-0.7.4 → kctl_api-0.9.0}/tests/test_deploy.py +0 -0
  78. {kctl_api-0.7.4 → kctl_api-0.9.0}/tests/test_logs.py +0 -0
  79. {kctl_api-0.7.4 → kctl_api-0.9.0}/tests/test_smoke.py +0 -0
  80. {kctl_api-0.7.4 → kctl_api-0.9.0}/tests/test_standard.py +0 -0
@@ -31,3 +31,6 @@ Thumbs.db
31
31
  # Environment
32
32
  .env
33
33
  .env.local
34
+
35
+ # Agent memory (claude-mem regenerates AGENTS.md locally; not a committed guide)
36
+ AGENTS.md
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kctl-api
3
- Version: 0.7.4
3
+ Version: 0.9.0
4
4
  Summary: Kodemeio API CLI - manage your FastAPI platform
5
5
  Author-email: Kodemeio <dev@kodeme.io>
6
6
  License-Expression: MIT
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.13
14
14
  Classifier: Topic :: System :: Systems Administration
15
15
  Requires-Python: >=3.12
16
16
  Requires-Dist: httpx>=0.28.0
17
- Requires-Dist: kctl-lib>=0.12.0
17
+ Requires-Dist: kctl-lib>=0.13.0
18
18
  Provides-Extra: all
19
19
  Requires-Dist: asyncpg>=0.30.0; extra == 'all'
20
20
  Requires-Dist: redis>=5.0.0; extra == 'all'
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "kctl-api"
7
- version = "0.7.4"
7
+ version = "0.9.0"
8
8
  description = "Kodemeio API CLI - manage your FastAPI platform"
9
9
  license = "MIT"
10
10
  requires-python = ">=3.12"
@@ -20,7 +20,7 @@ classifiers = [
20
20
  "Topic :: System :: Systems Administration",
21
21
  ]
22
22
  dependencies = [
23
- "kctl-lib>=0.12.0",
23
+ "kctl-lib>=0.13.0",
24
24
  "httpx>=0.28.0",
25
25
  ]
26
26
 
@@ -1,3 +1,3 @@
1
1
  """kctl-api: Kodemeio API CLI."""
2
2
 
3
- __version__ = "0.7.4"
3
+ __version__ = "0.9.0"
@@ -0,0 +1,127 @@
1
+ """Command-group registration for kctl-api (extracted from cli.py to keep it thin)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import typer
6
+ from kctl_lib.ai_summary import add_ai_summary_command
7
+
8
+ from kctl_api import __version__
9
+ from kctl_api.commands.ai import app as ai_app
10
+ from kctl_api.commands.aliases import register_aliases
11
+ from kctl_api.commands.apps import app as apps_app
12
+ from kctl_api.commands.auth import app as auth_app
13
+ from kctl_api.commands.automation import app as automation_app
14
+ from kctl_api.commands.build_cmd import app as build_app
15
+ from kctl_api.commands.clean import app as clean_app
16
+ from kctl_api.commands.config_cmd import app as config_app
17
+ from kctl_api.commands.dashboard import app as dashboard_app
18
+ from kctl_api.commands.db import app as db_app
19
+ from kctl_api.commands.deploy import app as deploy_app
20
+ from kctl_api.commands.deps import app as deps_app
21
+ from kctl_api.commands.dev import app as dev_app
22
+ from kctl_api.commands.docker_cmd import app as docker_app
23
+ from kctl_api.commands.doctor_cmd import app as doctor_app
24
+ from kctl_api.commands.doctor_cmd import build_checks as _doctor_build_checks
25
+ from kctl_api.commands.env import app as env_app
26
+ from kctl_api.commands.files import app as files_app
27
+ from kctl_api.commands.fmt_cmd import app as fmt_app
28
+ from kctl_api.commands.health import app as health_app
29
+ from kctl_api.commands.jobs import app as jobs_app
30
+ from kctl_api.commands.lint_cmd import app as lint_app
31
+ from kctl_api.commands.logs import app as logs_app
32
+ from kctl_api.commands.marketplace import app as marketplace_app
33
+ from kctl_api.commands.monitor_cmd import app as monitor_app
34
+ from kctl_api.commands.notifications import app as notifications_app
35
+ from kctl_api.commands.odoo_proxy import app as odoo_app
36
+ from kctl_api.commands.openapi import app as openapi_app
37
+ from kctl_api.commands.perf import app as perf_app
38
+ from kctl_api.commands.rate_limit import app as rate_limit_app
39
+ from kctl_api.commands.realtime import app as realtime_app
40
+ from kctl_api.commands.redis_cmd import app as redis_app
41
+ from kctl_api.commands.routes_cmd import app as routes_app
42
+ from kctl_api.commands.saas import app as saas_app
43
+ from kctl_api.commands.scaffold import app as scaffold_app
44
+ from kctl_api.commands.security_cmd import app as security_app
45
+ from kctl_api.commands.services import app as services_app
46
+ from kctl_api.commands.shell import app as shell_app
47
+ from kctl_api.commands.skill_cmd import app as skill_app
48
+ from kctl_api.commands.streams import app as streams_app
49
+ from kctl_api.commands.stripe_cmd import app as stripe_app
50
+ from kctl_api.commands.tenant_ai import app as tenant_ai_app
51
+ from kctl_api.commands.test_cmd import app as test_app
52
+ from kctl_api.commands.users import app as users_app
53
+ from kctl_api.commands.webhooks import app as webhooks_app
54
+ from kctl_api.commands.workflows import app as workflows_app
55
+ from kctl_api.commands.ws import app as ws_app
56
+
57
+
58
+ def register_all(app: typer.Typer) -> None:
59
+ """Register all command groups on *app* in the canonical order."""
60
+ # Authentication & access
61
+ app.add_typer(config_app, name="config")
62
+ app.add_typer(auth_app, name="auth")
63
+ app.add_typer(health_app, name="health")
64
+
65
+ # API resources
66
+ app.add_typer(users_app, name="users")
67
+ app.add_typer(files_app, name="files")
68
+ app.add_typer(jobs_app, name="jobs")
69
+ app.add_typer(workflows_app, name="workflows")
70
+ app.add_typer(automation_app, name="automation")
71
+ app.add_typer(notifications_app, name="notifications")
72
+ app.add_typer(webhooks_app, name="webhooks")
73
+ app.add_typer(marketplace_app, name="marketplace")
74
+ app.add_typer(saas_app, name="saas")
75
+ app.add_typer(stripe_app, name="stripe")
76
+ app.add_typer(odoo_app, name="odoo")
77
+ app.add_typer(realtime_app, name="realtime")
78
+
79
+ # AI platform
80
+ app.add_typer(ai_app, name="ai")
81
+ app.add_typer(tenant_ai_app, name="tenant-ai")
82
+
83
+ # Infrastructure
84
+ app.add_typer(db_app, name="db")
85
+ app.add_typer(redis_app, name="redis")
86
+ app.add_typer(streams_app, name="streams")
87
+ app.add_typer(services_app, name="services")
88
+ app.add_typer(deploy_app, name="deploy")
89
+ app.add_typer(apps_app, name="apps")
90
+ app.add_typer(docker_app, name="docker")
91
+
92
+ # Development tools
93
+ app.add_typer(dev_app, name="dev")
94
+ app.add_typer(test_app, name="test")
95
+ app.add_typer(lint_app, name="lint")
96
+ app.add_typer(fmt_app, name="fmt")
97
+ app.add_typer(build_app, name="build")
98
+ app.add_typer(scaffold_app, name="scaffold")
99
+ app.add_typer(shell_app, name="shell")
100
+
101
+ # API analysis & testing
102
+ app.add_typer(openapi_app, name="openapi")
103
+ app.add_typer(routes_app, name="routes")
104
+ app.add_typer(rate_limit_app, name="rate-limit")
105
+ app.add_typer(ws_app, name="ws")
106
+ app.add_typer(perf_app, name="perf")
107
+
108
+ # Environment & security
109
+ app.add_typer(doctor_app, name="doctor")
110
+ add_ai_summary_command(
111
+ doctor_app, cli_name="kctl-api", version=__version__, app=app, get_checks=_doctor_build_checks
112
+ )
113
+ app.add_typer(env_app, name="env")
114
+ app.add_typer(security_app, name="security")
115
+ app.add_typer(deps_app, name="deps")
116
+ app.add_typer(clean_app, name="clean")
117
+
118
+ # Observability & monitoring
119
+ app.add_typer(logs_app, name="logs")
120
+ app.add_typer(dashboard_app, name="dashboard")
121
+ app.add_typer(monitor_app, name="monitor")
122
+
123
+ # Skill generation
124
+ app.add_typer(skill_app, name="skill")
125
+
126
+ # Short aliases
127
+ register_aliases(app)
@@ -0,0 +1,130 @@
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, cli_entrypoint, handle_cli_error, register_introspection_commands
9
+ from kctl_lib.self_update import notify_if_outdated
10
+ from kctl_lib.tui import add_tui_command
11
+
12
+ from kctl_api import __version__
13
+ from kctl_api._register import register_all
14
+ from kctl_api.core.callbacks import AppContext
15
+
16
+
17
+ def version_callback(value: bool) -> None:
18
+ if value:
19
+ typer.echo(f"kctl-api {__version__}")
20
+ raise typer.Exit()
21
+
22
+
23
+ app = typer.Typer(
24
+ name="kctl-api",
25
+ help="Kodemeio API CLI - manage your FastAPI platform.",
26
+ no_args_is_help=True,
27
+ rich_markup_mode="rich",
28
+ pretty_exceptions_enable=False,
29
+ )
30
+
31
+
32
+ @app.callback()
33
+ def main(
34
+ ctx: typer.Context,
35
+ json_output: Annotated[bool, typer.Option("--json", help="Output as JSON")] = False,
36
+ quiet: Annotated[bool, typer.Option("--quiet", "-q", help="Suppress info messages")] = False,
37
+ output_format: Annotated[
38
+ str, typer.Option("--format", "-f", help="Output format: pretty, json, csv, yaml")
39
+ ] = "pretty",
40
+ no_header: Annotated[bool, typer.Option("--no-header", help="Omit table headers (for scripting)")] = False,
41
+ profile: Annotated[str | None, typer.Option("--profile", "-p", help="Config profile name")] = None,
42
+ url: Annotated[str | None, typer.Option("--url", help="API URL override")] = None,
43
+ ai_url: Annotated[str | None, typer.Option("--ai-url", help="AI API URL override")] = None,
44
+ api_key: Annotated[str | None, typer.Option("--api-key", help="API key override")] = None,
45
+ database_url: Annotated[str | None, typer.Option("--database-url", help="Database URL override")] = None,
46
+ redis_url: Annotated[str | None, typer.Option("--redis-url", help="Redis URL override")] = None,
47
+ version: Annotated[
48
+ bool, typer.Option("--version", "-V", callback=version_callback, is_eager=True, help="Show version")
49
+ ] = False,
50
+ ) -> None:
51
+ """Kodemeio API CLI."""
52
+ ctx.ensure_object(dict)
53
+ ctx.obj = AppContext(
54
+ json_mode=json_output or output_format == "json",
55
+ quiet=quiet,
56
+ format=output_format,
57
+ no_header=no_header,
58
+ profile=profile,
59
+ url_override=url,
60
+ ai_url_override=ai_url,
61
+ api_key_override=api_key,
62
+ database_url_override=database_url,
63
+ redis_url_override=redis_url,
64
+ )
65
+ notify_if_outdated(ctx.obj.output, "kctl-api", __version__)
66
+
67
+
68
+ register_all(app)
69
+
70
+ # Load plugins via entry points
71
+ from kctl_api.core.plugins import discover_and_load_plugins # noqa: E402
72
+
73
+ discover_and_load_plugins(app)
74
+
75
+ add_tui_command(app, service_key="api", version=__version__)
76
+
77
+
78
+ @app.command("self-update")
79
+ def self_update_cmd(ctx: typer.Context) -> None:
80
+ """Check for updates and upgrade kctl-api."""
81
+ actx = ctx.obj
82
+ out = actx.output
83
+
84
+ from kctl_lib.self_update import check_update
85
+ from kctl_lib.self_update import update as do_update
86
+
87
+ latest = check_update("kctl-api", __version__)
88
+ if latest:
89
+ out.info(f"Updating to {latest}...")
90
+ do_update("kctl-api")
91
+ out.success(f"Updated to {latest}")
92
+ else:
93
+ out.success("Already up to date")
94
+
95
+
96
+ @app.command()
97
+ def completions(
98
+ shell: Annotated[str, typer.Argument(help="Shell type: zsh, bash, fish")] = "zsh",
99
+ install: Annotated[bool, typer.Option("--install", help="Install completions")] = False,
100
+ ) -> None:
101
+ """Generate or install shell completions."""
102
+ from kctl_lib.completions import get_completion_script, install_completions
103
+
104
+ if install:
105
+ path = install_completions("kctl-api", shell)
106
+ if path:
107
+ typer.echo(f"Completions installed to {path}")
108
+ else:
109
+ typer.echo(f"Could not install completions for {shell}", err=True)
110
+ raise typer.Exit(code=1)
111
+ else:
112
+ script = get_completion_script("kctl-api", shell)
113
+ typer.echo(script)
114
+
115
+
116
+ # Wrap app so KctlError subclasses surface as clean user-facing messages.
117
+ app = cli_entrypoint(app)
118
+ register_introspection_commands(app)
119
+
120
+
121
+ def _run() -> None:
122
+ """Entry point with error handling."""
123
+ try:
124
+ app()
125
+ except KctlError as e:
126
+ handle_cli_error(e)
127
+
128
+
129
+ if __name__ == "__main__":
130
+ _run()
@@ -8,8 +8,10 @@ from __future__ import annotations
8
8
  import asyncio
9
9
  import shutil
10
10
  import subprocess
11
+ from dataclasses import dataclass
11
12
 
12
13
  import typer
14
+ from kctl_lib.doctor_base import CheckResult, DoctorCheck
13
15
 
14
16
  from kctl_api.core.callbacks import AppContext
15
17
  from kctl_api.core.utils import find_project_root
@@ -136,6 +138,80 @@ def _run_all_checks(actx: AppContext) -> list[dict]:
136
138
  return checks
137
139
 
138
140
 
141
+ @dataclass
142
+ class _DictCheck:
143
+ """Adapter: wraps a no-arg callable returning a dict into DoctorCheck."""
144
+
145
+ _fn: object # callable[[], dict]
146
+ name: str = ""
147
+
148
+ def __post_init__(self) -> None:
149
+ if not self.name:
150
+
151
+ self.name = getattr(self._fn, "__name__", "check").replace("_check_", "").replace("_", " ").title()
152
+
153
+ def run(self) -> CheckResult:
154
+ try:
155
+ result = self._fn() # type: ignore[operator]
156
+ status = "ok" if result.get("ok") else "fail"
157
+ msg_parts = []
158
+ if result.get("version"):
159
+ msg_parts.append(result["version"])
160
+ if result.get("issue"):
161
+ msg_parts.append(result["issue"])
162
+ message = "; ".join(msg_parts) or ("OK" if status == "ok" else "failed")
163
+ return CheckResult(name=self.name, status=status, message=message)
164
+ except Exception as exc:
165
+ return CheckResult(name=self.name, status="warn", message=str(exc))
166
+
167
+
168
+ @dataclass
169
+ class _AsyncDictCheck:
170
+ """Adapter: wraps an async callable returning a dict into DoctorCheck."""
171
+
172
+ _fn: object # async callable[[AppContext], dict]
173
+ _actx: AppContext
174
+ name: str = ""
175
+
176
+ def __post_init__(self) -> None:
177
+ if not self.name:
178
+ self.name = (
179
+ getattr(self._fn, "__name__", "check")
180
+ .replace("_check_", "")
181
+ .replace("_async", "")
182
+ .replace("_", " ")
183
+ .title()
184
+ )
185
+
186
+ def run(self) -> CheckResult:
187
+ try:
188
+ result = asyncio.run(self._fn(self._actx)) # type: ignore[operator]
189
+ status = "ok" if result.get("ok") else "fail"
190
+ msg_parts = []
191
+ if result.get("version"):
192
+ msg_parts.append(result["version"])
193
+ if result.get("issue"):
194
+ msg_parts.append(result["issue"])
195
+ message = "; ".join(msg_parts) or ("OK" if status == "ok" else "failed")
196
+ return CheckResult(name=self.name, status=status, message=message)
197
+ except Exception as exc:
198
+ return CheckResult(name=self.name, status="warn", message=str(exc))
199
+
200
+
201
+ def build_checks(ctx: typer.Context) -> list[DoctorCheck]:
202
+ """Return the standard list of doctor checks (no side effects)."""
203
+ actx: AppContext = ctx.obj
204
+ return [
205
+ _DictCheck(_fn=_check_python, name="Python"),
206
+ _DictCheck(_fn=lambda: _check_binary("uv", "--version"), name="uv"),
207
+ _DictCheck(_fn=lambda: _check_binary("docker", "--version"), name="Docker"),
208
+ _DictCheck(_fn=lambda: _check_binary("git", "--version"), name="git"),
209
+ _DictCheck(_fn=_check_env_file, name=".env"),
210
+ _AsyncDictCheck(_fn=_check_postgres_async, _actx=actx, name="PostgreSQL"),
211
+ _AsyncDictCheck(_fn=_check_redis_async, _actx=actx, name="Redis"),
212
+ ]
213
+
214
+
139
215
  # ---------------------------------------------------------------------------
140
216
  # check
141
217
  # ---------------------------------------------------------------------------
@@ -3,13 +3,11 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from pathlib import Path
6
- from unittest.mock import MagicMock, patch
6
+ from unittest.mock import patch
7
7
 
8
- import pytest
9
8
  from typer.testing import CliRunner
10
9
 
11
10
  from kctl_api.cli import app
12
- from kctl_api.core.config import ServiceConfig
13
11
 
14
12
  runner = CliRunner()
15
13
 
@@ -0,0 +1,16 @@
1
+ import json
2
+
3
+ from typer.testing import CliRunner
4
+
5
+ from kctl_api.cli import app
6
+
7
+ runner = CliRunner()
8
+
9
+
10
+ def test_doctor_ai_summary_json_runs():
11
+ result = runner.invoke(app, ["--json", "doctor", "ai-summary"])
12
+ assert result.exit_code == 0
13
+ out = result.output
14
+ data = json.loads(out[out.index("{"):]) # tolerate a leading notify/update line
15
+ assert data["cli"] == "kctl-api"
16
+ assert "health" in data and "checks" in data
@@ -4,7 +4,6 @@ from __future__ import annotations
4
4
 
5
5
  from unittest.mock import MagicMock, patch
6
6
 
7
- import pytest
8
7
  from typer.testing import CliRunner
9
8
 
10
9
  from kctl_api.cli import app
@@ -1,245 +0,0 @@
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, cli_entrypoint, handle_cli_error, register_introspection_commands
9
- from kctl_lib.self_update import notify_if_outdated
10
- from kctl_lib.tui import add_tui_command
11
-
12
- from kctl_api import __version__
13
- from kctl_api.core.callbacks import AppContext
14
-
15
-
16
- def version_callback(value: bool) -> None:
17
- if value:
18
- typer.echo(f"kctl-api {__version__}")
19
- raise typer.Exit()
20
-
21
-
22
- app = typer.Typer(
23
- name="kctl-api",
24
- help="Kodemeio API CLI - manage your FastAPI platform.",
25
- no_args_is_help=True,
26
- rich_markup_mode="rich",
27
- pretty_exceptions_enable=False,
28
- )
29
-
30
-
31
- @app.callback()
32
- def main(
33
- ctx: typer.Context,
34
- json_output: Annotated[bool, typer.Option("--json", help="Output as JSON")] = False,
35
- quiet: Annotated[bool, typer.Option("--quiet", "-q", help="Suppress info messages")] = False,
36
- output_format: Annotated[
37
- str, typer.Option("--format", "-f", help="Output format: pretty, json, csv, yaml")
38
- ] = "pretty",
39
- no_header: Annotated[bool, typer.Option("--no-header", help="Omit table headers (for scripting)")] = False,
40
- profile: Annotated[str | None, typer.Option("--profile", "-p", help="Config profile name")] = None,
41
- url: Annotated[str | None, typer.Option("--url", help="API URL override")] = None,
42
- ai_url: Annotated[str | None, typer.Option("--ai-url", help="AI API URL override")] = None,
43
- api_key: Annotated[str | None, typer.Option("--api-key", help="API key override")] = None,
44
- database_url: Annotated[str | None, typer.Option("--database-url", help="Database URL override")] = None,
45
- redis_url: Annotated[str | None, typer.Option("--redis-url", help="Redis 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 API CLI."""
51
- ctx.ensure_object(dict)
52
- ctx.obj = AppContext(
53
- json_mode=json_output or output_format == "json",
54
- quiet=quiet,
55
- format=output_format,
56
- no_header=no_header,
57
- profile=profile,
58
- url_override=url,
59
- ai_url_override=ai_url,
60
- api_key_override=api_key,
61
- database_url_override=database_url,
62
- redis_url_override=redis_url,
63
- )
64
- notify_if_outdated(ctx.obj.output, "kctl-api", __version__)
65
-
66
-
67
- def _register_commands() -> None:
68
- """Register all command groups (lazy imports for fast startup)."""
69
- from kctl_api.commands.ai import app as ai_app
70
- from kctl_api.commands.aliases import register_aliases
71
- from kctl_api.commands.apps import app as apps_app
72
- from kctl_api.commands.auth import app as auth_app
73
- from kctl_api.commands.automation import app as automation_app
74
- from kctl_api.commands.build_cmd import app as build_app
75
- from kctl_api.commands.clean import app as clean_app
76
- from kctl_api.commands.config_cmd import app as config_app
77
- from kctl_api.commands.dashboard import app as dashboard_app
78
- from kctl_api.commands.db import app as db_app
79
- from kctl_api.commands.deploy import app as deploy_app
80
- from kctl_api.commands.deps import app as deps_app
81
- from kctl_api.commands.dev import app as dev_app
82
- from kctl_api.commands.docker_cmd import app as docker_app
83
- from kctl_api.commands.doctor_cmd import app as doctor_app
84
- from kctl_api.commands.env import app as env_app
85
- from kctl_api.commands.files import app as files_app
86
- from kctl_api.commands.fmt_cmd import app as fmt_app
87
- from kctl_api.commands.health import app as health_app
88
- from kctl_api.commands.jobs import app as jobs_app
89
- from kctl_api.commands.lint_cmd import app as lint_app
90
- from kctl_api.commands.logs import app as logs_app
91
- from kctl_api.commands.marketplace import app as marketplace_app
92
- from kctl_api.commands.monitor_cmd import app as monitor_app
93
- from kctl_api.commands.notifications import app as notifications_app
94
- from kctl_api.commands.odoo_proxy import app as odoo_app
95
- from kctl_api.commands.openapi import app as openapi_app
96
- from kctl_api.commands.perf import app as perf_app
97
- from kctl_api.commands.rate_limit import app as rate_limit_app
98
- from kctl_api.commands.realtime import app as realtime_app
99
- from kctl_api.commands.redis_cmd import app as redis_app
100
- from kctl_api.commands.routes_cmd import app as routes_app
101
- from kctl_api.commands.saas import app as saas_app
102
- from kctl_api.commands.scaffold import app as scaffold_app
103
- from kctl_api.commands.security_cmd import app as security_app
104
- from kctl_api.commands.services import app as services_app
105
- from kctl_api.commands.shell import app as shell_app
106
- from kctl_api.commands.streams import app as streams_app
107
- from kctl_api.commands.stripe_cmd import app as stripe_app
108
- from kctl_api.commands.tenant_ai import app as tenant_ai_app
109
- from kctl_api.commands.test_cmd import app as test_app
110
- from kctl_api.commands.users import app as users_app
111
- from kctl_api.commands.webhooks import app as webhooks_app
112
- from kctl_api.commands.workflows import app as workflows_app
113
- from kctl_api.commands.ws import app as ws_app
114
-
115
- # Authentication & access
116
- app.add_typer(config_app, name="config")
117
- app.add_typer(auth_app, name="auth")
118
- app.add_typer(health_app, name="health")
119
-
120
- # API resources
121
- app.add_typer(users_app, name="users")
122
- app.add_typer(files_app, name="files")
123
- app.add_typer(jobs_app, name="jobs")
124
- app.add_typer(workflows_app, name="workflows")
125
- app.add_typer(automation_app, name="automation")
126
- app.add_typer(notifications_app, name="notifications")
127
- app.add_typer(webhooks_app, name="webhooks")
128
- app.add_typer(marketplace_app, name="marketplace")
129
- app.add_typer(saas_app, name="saas")
130
- app.add_typer(stripe_app, name="stripe")
131
- app.add_typer(odoo_app, name="odoo")
132
- app.add_typer(realtime_app, name="realtime")
133
-
134
- # AI platform
135
- app.add_typer(ai_app, name="ai")
136
- app.add_typer(tenant_ai_app, name="tenant-ai")
137
-
138
- # Infrastructure
139
- app.add_typer(db_app, name="db")
140
- app.add_typer(redis_app, name="redis")
141
- app.add_typer(streams_app, name="streams")
142
- app.add_typer(services_app, name="services")
143
- app.add_typer(deploy_app, name="deploy")
144
- app.add_typer(apps_app, name="apps")
145
- app.add_typer(docker_app, name="docker")
146
-
147
- # Development tools
148
- app.add_typer(dev_app, name="dev")
149
- app.add_typer(test_app, name="test")
150
- app.add_typer(lint_app, name="lint")
151
- app.add_typer(fmt_app, name="fmt")
152
- app.add_typer(build_app, name="build")
153
- app.add_typer(scaffold_app, name="scaffold")
154
- app.add_typer(shell_app, name="shell")
155
-
156
- # API analysis & testing
157
- app.add_typer(openapi_app, name="openapi")
158
- app.add_typer(routes_app, name="routes")
159
- app.add_typer(rate_limit_app, name="rate-limit")
160
- app.add_typer(ws_app, name="ws")
161
- app.add_typer(perf_app, name="perf")
162
-
163
- # Environment & security
164
- app.add_typer(doctor_app, name="doctor")
165
- app.add_typer(env_app, name="env")
166
- app.add_typer(security_app, name="security")
167
- app.add_typer(deps_app, name="deps")
168
- app.add_typer(clean_app, name="clean")
169
-
170
- # Observability & monitoring
171
- app.add_typer(logs_app, name="logs")
172
- app.add_typer(dashboard_app, name="dashboard")
173
- app.add_typer(monitor_app, name="monitor")
174
-
175
- # Skill generation
176
- from kctl_api.commands.skill_cmd import app as skill_app
177
-
178
- app.add_typer(skill_app, name="skill")
179
-
180
- # Short aliases
181
- register_aliases(app)
182
-
183
-
184
- _register_commands()
185
-
186
- # Load plugins via entry points
187
- from kctl_api.core.plugins import discover_and_load_plugins # noqa: E402
188
-
189
- discover_and_load_plugins(app)
190
-
191
- add_tui_command(app, service_key="api", version=__version__)
192
-
193
-
194
- @app.command("self-update")
195
- def self_update_cmd(ctx: typer.Context) -> None:
196
- """Check for updates and upgrade kctl-api."""
197
- actx = ctx.obj
198
- out = actx.output
199
-
200
- from kctl_lib.self_update import check_update
201
- from kctl_lib.self_update import update as do_update
202
-
203
- latest = check_update("kctl-api", __version__)
204
- if latest:
205
- out.info(f"Updating to {latest}...")
206
- do_update("kctl-api")
207
- out.success(f"Updated to {latest}")
208
- else:
209
- out.success("Already up to date")
210
-
211
-
212
- @app.command()
213
- def completions(
214
- shell: Annotated[str, typer.Argument(help="Shell type: zsh, bash, fish")] = "zsh",
215
- install: Annotated[bool, typer.Option("--install", help="Install completions")] = False,
216
- ) -> None:
217
- """Generate or install shell completions."""
218
- from kctl_lib.completions import get_completion_script, install_completions
219
-
220
- if install:
221
- path = install_completions("kctl-api", shell)
222
- if path:
223
- typer.echo(f"Completions installed to {path}")
224
- else:
225
- typer.echo(f"Could not install completions for {shell}", err=True)
226
- raise typer.Exit(code=1)
227
- else:
228
- script = get_completion_script("kctl-api", shell)
229
- typer.echo(script)
230
-
231
-
232
- # Wrap app so KctlError subclasses surface as clean user-facing messages.
233
- app = cli_entrypoint(app)
234
- register_introspection_commands(app)
235
-
236
- def _run() -> None:
237
- """Entry point with error handling."""
238
- try:
239
- app()
240
- except KctlError as e:
241
- handle_cli_error(e)
242
-
243
-
244
- if __name__ == "__main__":
245
- _run()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes