Added castle build [project] command following the same pattern as castle test and castle lint. Stack handlers already had build methods — this just wires up the CLI to expose them. Supports building a single project or all projects, providing feature parity with existing project-level commands.
218 lines
7.5 KiB
Python
218 lines
7.5 KiB
Python
"""Castle CLI entry point."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import sys
|
|
|
|
from castle_cli import __version__
|
|
|
|
|
|
def build_parser() -> argparse.ArgumentParser:
|
|
"""Build the argument parser."""
|
|
parser = argparse.ArgumentParser(
|
|
prog="castle",
|
|
description="Castle platform CLI - manage projects, services, and infrastructure",
|
|
)
|
|
parser.add_argument("--version", action="version", version=f"castle {__version__}")
|
|
|
|
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
|
|
# castle list
|
|
list_parser = subparsers.add_parser("list", help="List all programs, services, and jobs")
|
|
list_parser.add_argument(
|
|
"--behavior",
|
|
choices=["daemon", "tool", "frontend"],
|
|
help="Filter by behavior",
|
|
)
|
|
list_parser.add_argument(
|
|
"--stack",
|
|
help="Filter by stack (e.g. python-cli, python-fastapi, react-vite)",
|
|
)
|
|
list_parser.add_argument("--json", action="store_true", help="Output as JSON")
|
|
|
|
# castle create
|
|
create_parser = subparsers.add_parser("create", help="Create a new project")
|
|
create_parser.add_argument("name", help="Project name")
|
|
create_parser.add_argument(
|
|
"--stack",
|
|
choices=["python-cli", "python-fastapi", "react-vite"],
|
|
required=True,
|
|
help="Development stack (determines scaffold template and default behavior)",
|
|
)
|
|
create_parser.add_argument("--description", default="", help="Project description")
|
|
create_parser.add_argument("--port", type=int, help="Port number (daemons only)")
|
|
# castle info
|
|
info_parser = subparsers.add_parser("info", help="Show program details")
|
|
info_parser.add_argument("project", help="Program, service, or job name")
|
|
info_parser.add_argument("--json", action="store_true", help="Output as JSON")
|
|
|
|
# castle test
|
|
test_parser = subparsers.add_parser("test", help="Run tests")
|
|
test_parser.add_argument("project", nargs="?", help="Project to test (default: all)")
|
|
|
|
# castle lint
|
|
lint_parser = subparsers.add_parser("lint", help="Run linter")
|
|
lint_parser.add_argument("project", nargs="?", help="Project to lint (default: all)")
|
|
|
|
# castle build
|
|
build_parser = subparsers.add_parser("build", help="Build projects")
|
|
build_parser.add_argument("project", nargs="?", help="Project to build (default: all)")
|
|
|
|
# castle sync
|
|
subparsers.add_parser("sync", help="Sync submodules and install dependencies")
|
|
|
|
# castle gateway
|
|
gateway_parser = subparsers.add_parser("gateway", help="Manage the Caddy gateway")
|
|
gateway_sub = gateway_parser.add_subparsers(dest="gateway_command")
|
|
gw_start = gateway_sub.add_parser("start", help="Start the gateway")
|
|
gw_start.add_argument(
|
|
"--dry-run", action="store_true", help="Print generated config without applying"
|
|
)
|
|
gateway_sub.add_parser("stop", help="Stop the gateway")
|
|
gw_reload = gateway_sub.add_parser("reload", help="Reload gateway configuration")
|
|
gw_reload.add_argument(
|
|
"--dry-run", action="store_true", help="Print generated config without applying"
|
|
)
|
|
gateway_sub.add_parser("status", help="Show gateway status")
|
|
|
|
# castle service (singular - manage individual services)
|
|
service_parser = subparsers.add_parser("service", help="Manage a service")
|
|
service_sub = service_parser.add_subparsers(dest="service_command")
|
|
enable_parser = service_sub.add_parser("enable", help="Enable and start a service")
|
|
enable_parser.add_argument("name", help="Service name")
|
|
enable_parser.add_argument(
|
|
"--dry-run", action="store_true", help="Print generated unit without installing"
|
|
)
|
|
disable_parser = service_sub.add_parser("disable", help="Stop and disable a service")
|
|
disable_parser.add_argument("name", help="Service name")
|
|
service_sub.add_parser("status", help="Show status of all services")
|
|
|
|
# castle services (plural - manage all services)
|
|
services_parser = subparsers.add_parser("services", help="Manage all services together")
|
|
services_sub = services_parser.add_subparsers(dest="services_command")
|
|
services_sub.add_parser("start", help="Start all services and gateway")
|
|
services_sub.add_parser("stop", help="Stop all services and gateway")
|
|
|
|
# castle logs
|
|
logs_parser = subparsers.add_parser("logs", help="View service/job logs")
|
|
logs_parser.add_argument("name", help="Service or job name")
|
|
logs_parser.add_argument("-f", "--follow", action="store_true", help="Follow log output")
|
|
logs_parser.add_argument(
|
|
"-n", "--lines", type=int, default=50, help="Number of lines to show (default: 50)"
|
|
)
|
|
|
|
# castle run
|
|
run_parser = subparsers.add_parser("run", help="Run a service in the foreground")
|
|
run_parser.add_argument("name", help="Service name")
|
|
run_parser.add_argument(
|
|
"extra", nargs=argparse.REMAINDER, help="Extra arguments passed to the service"
|
|
)
|
|
|
|
# castle deploy
|
|
deploy_parser = subparsers.add_parser("deploy", help="Deploy to ~/.castle/ (spec → runtime)")
|
|
deploy_parser.add_argument("name", nargs="?", help="Service or job to deploy (default: all)")
|
|
|
|
# castle tool
|
|
tool_parser = subparsers.add_parser("tool", help="Manage tools")
|
|
tool_sub = tool_parser.add_subparsers(dest="tool_command")
|
|
tool_sub.add_parser("list", help="List all tools")
|
|
tool_info_parser = tool_sub.add_parser("info", help="Show tool details")
|
|
tool_info_parser.add_argument("name", help="Tool name")
|
|
|
|
return parser
|
|
|
|
|
|
def main() -> int:
|
|
"""Main entry point."""
|
|
parser = build_parser()
|
|
args = parser.parse_args()
|
|
|
|
if not args.command:
|
|
parser.print_help()
|
|
return 0
|
|
|
|
# Import command handlers lazily to keep startup fast
|
|
if args.command == "list":
|
|
from castle_cli.commands.list_cmd import run_list
|
|
|
|
return run_list(args)
|
|
|
|
elif args.command == "info":
|
|
from castle_cli.commands.info import run_info
|
|
|
|
return run_info(args)
|
|
|
|
elif args.command == "create":
|
|
from castle_cli.commands.create import run_create
|
|
|
|
return run_create(args)
|
|
|
|
elif args.command == "test":
|
|
from castle_cli.commands.dev import run_test
|
|
|
|
return run_test(args)
|
|
|
|
elif args.command == "lint":
|
|
from castle_cli.commands.dev import run_lint
|
|
|
|
return run_lint(args)
|
|
|
|
elif args.command == "build":
|
|
from castle_cli.commands.dev import run_build
|
|
|
|
return run_build(args)
|
|
|
|
elif args.command == "sync":
|
|
from castle_cli.commands.sync import run_sync
|
|
|
|
return run_sync(args)
|
|
|
|
elif args.command == "gateway":
|
|
from castle_cli.commands.gateway import run_gateway
|
|
|
|
return run_gateway(args)
|
|
|
|
elif args.command == "service":
|
|
from castle_cli.commands.service import run_service
|
|
|
|
return run_service(args)
|
|
|
|
elif args.command == "services":
|
|
from castle_cli.commands.service import run_services
|
|
|
|
return run_services(args)
|
|
|
|
elif args.command == "logs":
|
|
from castle_cli.commands.logs import run_logs
|
|
|
|
return run_logs(args)
|
|
|
|
elif args.command == "deploy":
|
|
from castle_cli.commands.deploy import run_deploy
|
|
|
|
return run_deploy(args)
|
|
|
|
elif args.command == "run":
|
|
from castle_cli.commands.run_cmd import run_run
|
|
|
|
return run_run(args)
|
|
|
|
elif args.command == "tool":
|
|
from castle_cli.commands.tool import run_tool
|
|
|
|
return run_tool(args)
|
|
|
|
else:
|
|
parser.print_help()
|
|
return 1
|
|
|
|
|
|
def cli() -> None:
|
|
"""Entry point for the CLI."""
|
|
sys.exit(main())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
cli()
|