Rename Castle -> Wild PC across the repo
Repo-side rename only (Phases 1-3 of the migration plan); the live box (~/.castle, systemd units, /data/castle, domains) is a separate cutover. - Slug `castle` -> `wildpc`: CLI command, module names (wildpc_core/cli/api), dist names, entry point `wildpc = wildpc_cli.main:main`. - Identifiers: CastleConfig/NATSClient/DirError/MDNS -> Wildpc*. - Env/constants: CASTLE_* -> WILDPC_*; ~/.castle -> ~/.wildpc, castle.yaml -> wildpc.yaml, /data/castle -> /data/wildpc. - Systemd UNIT_PREFIX castle- -> wildpc-; own programs castle-api/gateway/etc. - Display prose "Castle" -> "Wild PC" in docs, agent-guide files, README, frontend. - Package dirs and bootstrap yaml renamed via git mv; lockfiles regenerated; redundant nested uv.lock files dropped (workspace root lock is authoritative). Tests: core 273, cli 47, wildpc-api 120 all pass. Frontend type-checks + builds. Fixed a stale test fixture (secret_env_path kind arg) broken pre-rename.
This commit is contained in:
110
cli/src/wildpc_cli/commands/stack.py
Normal file
110
cli/src/wildpc_cli/commands/stack.py
Normal file
@@ -0,0 +1,110 @@
|
||||
"""wildpc stack — the stacks lens (toolchains a program's stack needs).
|
||||
|
||||
A *stack* (python-fastapi, react-vite, hugo, …) is creation-time guidance that
|
||||
also carries the **host toolchains** its programs need to build and run (`uv`,
|
||||
`pnpm`, `hugo`, …). This lens makes those dependencies visible and tells you
|
||||
whether they're present *where the running service needs them* — the drift a bare
|
||||
`which` in your shell misses — with a copyable fix when one is absent.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from dataclasses import asdict
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from wildpc_cli.config import load_config
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from wildpc_core.stack_status import StackStatus
|
||||
|
||||
BOLD = "\033[1m"
|
||||
DIM = "\033[2m"
|
||||
RESET = "\033[0m"
|
||||
GREEN = "\033[92m"
|
||||
RED = "\033[91m"
|
||||
GREY = "\033[90m"
|
||||
CYAN = "\033[96m"
|
||||
|
||||
|
||||
def _record(st: StackStatus) -> dict:
|
||||
d = asdict(st)
|
||||
d["in_use"] = st.in_use
|
||||
d["ok"] = st.ok
|
||||
return d
|
||||
|
||||
|
||||
def run_stack_list(args: argparse.Namespace) -> int:
|
||||
"""List stacks with their toolchain health, program count, and dev verbs."""
|
||||
from wildpc_core.stack_status import all_stack_status
|
||||
|
||||
config = load_config()
|
||||
# Skip per-tool version probes for the list (a subprocess per tool) — keep it snappy.
|
||||
stacks = all_stack_status(config, with_version=False)
|
||||
|
||||
if getattr(args, "json", False):
|
||||
print(json.dumps([_record(s) for s in stacks], indent=2))
|
||||
return 0
|
||||
|
||||
print(f"\n{BOLD}Stacks{RESET}")
|
||||
print("─" * 64)
|
||||
width = max((len(s.name) for s in stacks), default=0)
|
||||
for s in stacks:
|
||||
if not s.tools:
|
||||
dot = f"{GREY}○{RESET}"
|
||||
else:
|
||||
dot = f"{GREEN}●{RESET}" if s.ok else f"{RED}●{RESET}"
|
||||
missing = [t.command for t in s.tools if not t.present]
|
||||
tools = ", ".join(
|
||||
(f"{RED}{t.command}{RESET}" if not t.present else t.command) for t in s.tools
|
||||
)
|
||||
used = (
|
||||
f"{len(s.programs)} program{'s' if len(s.programs) != 1 else ''}"
|
||||
if s.in_use
|
||||
else f"{GREY}unused{RESET}"
|
||||
)
|
||||
tail = f" {DIM}{used}{RESET}"
|
||||
tools_str = f" {DIM}tools:{RESET} {tools}" if tools else ""
|
||||
print(f" {dot} {BOLD}{s.name:<{width}}{RESET}{tools_str}{tail}")
|
||||
if missing:
|
||||
print(f" {RED}missing:{RESET} {', '.join(missing)}")
|
||||
print()
|
||||
return 0
|
||||
|
||||
|
||||
def run_stack_info(args: argparse.Namespace) -> int:
|
||||
"""Show one stack: each tool's presence + version + fix, and who uses it."""
|
||||
from wildpc_core.stack_status import stack_status
|
||||
|
||||
config = load_config()
|
||||
name = args.name
|
||||
st = stack_status(config, name)
|
||||
if st is None:
|
||||
from wildpc_core.stacks import available_stacks
|
||||
|
||||
print(f"Error: no stack '{name}'. Known: {', '.join(available_stacks())}")
|
||||
return 1
|
||||
|
||||
if getattr(args, "json", False):
|
||||
print(json.dumps(_record(st), indent=2))
|
||||
return 0
|
||||
|
||||
print(f"\n{BOLD}{st.name}{RESET}")
|
||||
print("─" * 48)
|
||||
if st.verbs:
|
||||
print(f" {BOLD}verbs{RESET}: {', '.join(st.verbs)}")
|
||||
print(f" {BOLD}used by{RESET}: ", end="")
|
||||
print(", ".join(st.programs) if st.programs else f"{GREY}nothing{RESET}")
|
||||
|
||||
print(f"\n {BOLD}toolchain{RESET}")
|
||||
if not st.tools:
|
||||
print(f" {GREY}no host tools required{RESET}")
|
||||
for t in st.tools:
|
||||
mark = f"{GREEN}✓{RESET}" if t.present else f"{RED}✗{RESET}"
|
||||
ver = f" {DIM}{t.version}{RESET}" if t.version else ""
|
||||
print(f" {mark} {BOLD}{t.command}{RESET} {DIM}{t.purpose} ({t.phase}){RESET}{ver}")
|
||||
if not t.present:
|
||||
print(f" {CYAN}{t.install_hint}{RESET}")
|
||||
print()
|
||||
return 0
|
||||
Reference in New Issue
Block a user