Files
wild-pc/cli/src/castle_cli/commands/info.py

165 lines
5.4 KiB
Python

"""castle info - show detailed component information."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from castle_cli.config import load_config
# Terminal colors
BOLD = "\033[1m"
RESET = "\033[0m"
CYAN = "\033[96m"
DIM = "\033[2m"
GREEN = "\033[92m"
RED = "\033[91m"
def _load_deployed_component(name: str) -> object | None:
"""Try to load a specific deployed component from registry."""
try:
from castle_core.registry import load_registry
registry = load_registry()
return registry.deployed.get(name)
except (FileNotFoundError, ValueError):
return None
def run_info(args: argparse.Namespace) -> int:
"""Show detailed info for a component."""
config = load_config()
name = args.project
if name not in config.components:
print(f"Error: component '{name}' not found in castle.yaml")
return 1
manifest = config.components[name]
deployed = _load_deployed_component(name)
if getattr(args, "json", False):
data = manifest.model_dump(exclude_none=True)
# Include CLAUDE.md content if it exists
claude_md = _find_claude_md(config.root, manifest.source_dir or name)
if claude_md:
data["claude_md"] = claude_md
if deployed:
data["deployed"] = {
"runner": deployed.runner,
"run_cmd": deployed.run_cmd,
"env": deployed.env,
"port": deployed.port,
"managed": deployed.managed,
}
print(json.dumps(data, indent=2))
return 0
# Human-readable output
print(f"\n{BOLD}{name}{RESET}")
print(f"{'' * 40}")
print(f" {BOLD}roles{RESET}: {', '.join(r.value for r in manifest.roles)}")
if manifest.description:
print(f" {BOLD}description{RESET}: {manifest.description}")
# Source
if manifest.source:
print(f" {BOLD}source{RESET}: {manifest.source}")
# Run spec
if manifest.run:
print(f" {BOLD}runner{RESET}: {manifest.run.runner}")
if hasattr(manifest.run, "tool"):
print(f" {BOLD}tool{RESET}: {manifest.run.tool}")
elif hasattr(manifest.run, "argv"):
print(f" {BOLD}argv{RESET}: {manifest.run.argv}")
elif hasattr(manifest.run, "image"):
print(f" {BOLD}image{RESET}: {manifest.run.image}")
# Defaults env
if manifest.defaults and manifest.defaults.env:
print(f" {BOLD}defaults.env{RESET}:")
for key, val in manifest.defaults.env.items():
print(f" {key}: {val}")
# Expose
if manifest.expose and manifest.expose.http:
http = manifest.expose.http
print(f" {BOLD}port{RESET}: {http.internal.port}")
if http.health_path:
print(f" {BOLD}health{RESET}: {http.health_path}")
# Proxy
if manifest.proxy and manifest.proxy.caddy:
caddy = manifest.proxy.caddy
if caddy.path_prefix:
print(f" {BOLD}path{RESET}: {caddy.path_prefix}")
# Manage
if manifest.manage and manifest.manage.systemd:
sd = manifest.manage.systemd
print(f" {BOLD}systemd{RESET}: enabled={sd.enable}, restart={sd.restart.value}")
# Install
if manifest.install and manifest.install.path:
pi = manifest.install.path
print(f" {BOLD}install{RESET}: path" + (f" (alias: {pi.alias})" if pi.alias else ""))
# Tool
if manifest.tool:
t = manifest.tool
if t.source:
print(f" {BOLD}tool.source{RESET}: {t.source}")
if t.system_dependencies:
print(f" {BOLD}requires{RESET}: {', '.join(t.system_dependencies)}")
# Tags
if manifest.tags:
print(f" {BOLD}tags{RESET}: {', '.join(manifest.tags)}")
# Capabilities
if manifest.provides:
print(f" {BOLD}provides{RESET}:")
for cap in manifest.provides:
print(f" - {cap.type}" + (f" ({cap.name})" if cap.name else ""))
if manifest.consumes:
print(f" {BOLD}consumes{RESET}:")
for cap in manifest.consumes:
print(f" - {cap.type}" + (f" ({cap.name})" if cap.name else ""))
# Deployed state from registry
if deployed:
print(f"\n {GREEN}{BOLD}deployed{RESET}")
print(f" {'' * 36}")
print(f" {BOLD}run_cmd{RESET}: {' '.join(deployed.run_cmd)}")
if deployed.port is not None:
print(f" {BOLD}port{RESET}: {deployed.port}")
print(f" {BOLD}managed{RESET}: {deployed.managed}")
if deployed.env:
print(f" {BOLD}env{RESET}:")
for key, val in deployed.env.items():
print(f" {key}={val}")
else:
print(f"\n {DIM}not deployed (run 'castle deploy {name}'){RESET}")
# Show CLAUDE.md if it exists
source_dir = manifest.source_dir or name
claude_md = _find_claude_md(config.root, source_dir)
if claude_md:
print(f"\n{BOLD}{CYAN}CLAUDE.md{RESET}")
print(f"{CYAN}{'' * 40}{RESET}")
print(f"{DIM}{claude_md}{RESET}")
print()
return 0
def _find_claude_md(root: Path, source_dir: str) -> str | None:
"""Read CLAUDE.md from project directory if it exists."""
claude_path = root / source_dir / "CLAUDE.md"
if claude_path.exists():
return claude_path.read_text()
return None