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:
2026-07-18 22:55:08 -07:00
parent 25d7a522cc
commit 05b28cb584
190 changed files with 2428 additions and 3337 deletions

View File

@@ -0,0 +1,101 @@
"""wildpc run - run a program or service in the foreground.
Unified: if the target is a program with a declared `run` command, run that in
the foreground (dev-run). Otherwise fall back to the deployed-service run from
the registry.
"""
from __future__ import annotations
import argparse
import os
import subprocess
from pathlib import Path
from wildpc_core.generators.systemd import secret_env_path
from wildpc_core.registry import REGISTRY_PATH, load_registry
from wildpc_cli.config import load_config
def _load_secret_env(name: str) -> dict[str, str]:
"""Read a deployment's generated secret env file (KEY=value lines), if any.
Secrets are kept out of the registry, so a foreground run merges them from the
file systemd/docker would otherwise load, matching the deployed environment.
"""
path = secret_env_path(name)
if not path.exists():
return {}
out: dict[str, str] = {}
for line in path.read_text().splitlines():
if "=" in line and not line.startswith("#"):
key, val = line.split("=", 1)
out[key] = val
return out
def _run_program(name: str, extra: list[str]) -> int | None:
"""Run a program's declared `run` command in the foreground.
Returns the exit code, or None if the program has no declared run command
(so the caller can fall back to the deployed-service path).
"""
config = load_config()
prog = config.programs.get(name)
if prog is None or prog.commands is None or prog.commands.run is None:
return None
if not prog.source:
print(f"Error: program '{name}' has no source directory.")
return 1
cwd = Path(prog.source)
cmds = prog.commands.run
rc = 0
for i, argv in enumerate(cmds):
# Append extra args to the final command in the sequence.
full = list(argv) + (extra if i == len(cmds) - 1 else [])
print(f"Running {name}: {' '.join(full)}")
rc = subprocess.run(full, cwd=cwd).returncode
if rc != 0:
break
return rc
def run_run(args: argparse.Namespace) -> int:
"""Run a program's declared run, or a deployed service, in the foreground.
Scoped by `resource`: `program run` runs the program's declared `run`;
`service run` runs the deployed service's command from the registry.
"""
name = args.name
extra_args = getattr(args, "extra", []) or []
resource = getattr(args, "resource", None)
# Program: declared run command.
if resource in (None, "program"):
prog_rc = _run_program(name, extra_args)
if prog_rc is not None:
return prog_rc
if resource == "program":
print(f"Error: program '{name}' has no declared `run` command.")
return 1
# Service: deployed command from the registry.
if not REGISTRY_PATH.exists():
print("Error: no registry found. Run 'wildpc apply' first.")
return 1
registry = load_registry()
matches = registry.named(name)
if not matches:
print(f"Error: '{name}' is not a deployed service. Run 'wildpc apply' first.")
return 1
# A name may span kinds; run the one that has a run command (service/job).
deployed = next((d for d in matches if d.run_cmd), matches[0])
cmd = list(deployed.run_cmd) + extra_args
env = dict(os.environ)
env.update(deployed.env)
env.update(_load_secret_env(name))
print(f"Running {name}: {' '.join(cmd)}")
return subprocess.run(cmd, env=env).returncode