diff --git a/.gitignore b/.gitignore index 097ad84..175b5b4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,6 @@ __pycache__ .pytest_cache .claude node_modules -castle.yaml -!bootstrap/**/castle.yaml +wildpc.yaml +!bootstrap/**/wildpc.yaml .working diff --git a/AGENTS.md b/AGENTS.md index 75d37e0..7744128 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,7 @@ -# AGENTS.md — Castle +# AGENTS.md — Wild PC -You are working in **Castle**, a personal software platform. Castle is a -monorepo of independent programs managed by the `castle` CLI. From this directory +You are working in **Wild PC**, a personal software platform. Wild PC is a +monorepo of independent programs managed by the `wildpc` CLI. From this directory you can **manage all the software on this box from source** — create programs, deploy them as services, jobs, tools, or static frontends, route them through the gateway, expose them over TLS or a public tunnel, and coordinate across nodes. @@ -13,7 +13,7 @@ section links to a doc under `docs/`. Read those before non-trivial changes. ## 1. Mental model — two layers -Castle splits every piece of software into **what it is** and **how it runs here**: +Wild PC splits every piece of software into **what it is** and **how it runs here**: - **`programs/.yaml`** — the software *catalog*: source, stack, build, system dependencies. "What software exists." @@ -32,70 +32,70 @@ stored: | `none` | — | **reference** | an external service on another node | A program may have **no** deployment (just source you develop), or one/more -deployments. Global settings live in **`castle.yaml`** (`gateway`, `repo`, -`agents`). Config root defaults to `~/.castle/`. +deployments. Global settings live in **`wildpc.yaml`** (`gateway`, `repo`, +`agents`). Config root defaults to `~/.wildpc/`. -**Prime directive:** regular programs must **never depend on castle**. They take -standard config (data dir, port, URLs) via **env vars**; only castle's own -programs (CLI, gateway, api) know castle internals. When you scaffold or adopt a -program, wire it with env vars — not castle imports. +**Prime directive:** regular programs must **never depend on wildpc**. They take +standard config (data dir, port, URLs) via **env vars**; only wildpc's own +programs (CLI, gateway, api) know wildpc internals. When you scaffold or adopt a +program, wire it with env vars — not wildpc imports. -→ Full reference: **`docs/registry.md`** (castle.yaml structure, every field, +→ Full reference: **`docs/registry.md`** (wildpc.yaml structure, every field, manifest models, lifecycle). Architecture rationale: **`docs/design.md`**. --- -## 2. The `castle` CLI +## 2. The `wildpc` CLI Resource-first: operations live under the resource they act on. Names can collide across resource types, so the resource is explicit. ```bash # Programs — the software catalog -castle program list [--kind service] [--stack python-cli] [--json] -castle program info [--json] -castle program create [--stack ...] [--description ...] # scaffold NEW code -castle program add [--name ...] # adopt EXISTING repo -castle program clone [name] # provision repo: source -castle program delete [--source] [-y] -castle program run [args...] # declared run command -castle program build|test|lint|format|type-check|check [name] # dev verbs +wildpc program list [--kind service] [--stack python-cli] [--json] +wildpc program info [--json] +wildpc program create [--stack ...] [--description ...] # scaffold NEW code +wildpc program add [--name ...] # adopt EXISTING repo +wildpc program clone [name] # provision repo: source +wildpc program delete [--source] [-y] +wildpc program run [args...] # declared run command +wildpc program build|test|lint|format|type-check|check [name] # dev verbs # Services — daemons (manager: systemd, no schedule) -castle service list|info [--json] -castle service create [--program P] [--port N] [--health ...] [--launcher ...] -castle service restart # imperative bounce -castle service logs [-f] [-n 50] +wildpc service list|info [--json] +wildpc service create [--program P] [--port N] [--health ...] [--launcher ...] +wildpc service restart # imperative bounce +wildpc service logs [-f] [-n 50] # Jobs — scheduled tasks (manager: systemd + schedule). create takes --schedule -castle job create [--program P] --schedule "0 2 * * *" [--launcher ...] -castle job ... +wildpc job create [--program P] --schedule "0 2 * * *" [--launcher ...] +wildpc job ... # Tools — CLIs on PATH (manager: path) -castle tool list [--json] # each tool's executable + description + install state -castle tool info [--json] +wildpc tool list [--json] # each tool's executable + description + install state +wildpc tool info [--json] # Secrets — read/write the ACTIVE backend (file or openbao); never hand-write the store -castle secret list # names in the active backend -castle secret set [VALUE] # VALUE omitted → hidden prompt / stdin -castle secret get # print a value -castle secret rm [-y] # delete +wildpc secret list # names in the active backend +wildpc secret set [VALUE] # VALUE omitted → hidden prompt / stdin +wildpc secret get # print a value +wildpc secret rm [-y] # delete # Platform-wide -castle apply [name] [--plan] # converge runtime to config — the workhorse -castle list [--kind ...] [--stack ...] [--json] # all deployments -castle status # unified health/status -castle doctor # diagnose setup + runtime, with fix hints -castle restart [name] # imperative bounce (one or all) -castle gateway # gateway status + route table (inspection) +wildpc apply [name] [--plan] # converge runtime to config — the workhorse +wildpc list [--kind ...] [--stack ...] [--json] # all deployments +wildpc status # unified health/status +wildpc doctor # diagnose setup + runtime, with fix hints +wildpc restart [name] # imperative bounce (one or all) +wildpc gateway # gateway status + route table (inspection) ``` -`castle service`/`job`/`tool` are **views** over the one deployment set, filtered -by derived kind. Lifecycle is **convergence**: edit config, then **`castle apply`** +`wildpc service`/`job`/`tool` are **views** over the one deployment set, filtered +by derived kind. Lifecycle is **convergence**: edit config, then **`wildpc apply`** renders units + the Caddyfile and reconciles the runtime (activate what's enabled, restart what changed, deactivate what's disabled). To durably turn a deployment off, set `enabled: false` and apply — there is no separate start/stop/enable. -`castle restart` is the one imperative bounce. +`wildpc restart` is the one imperative bounce. **Dev verbs resolve per-program:** a declared `commands:` entry (or `build:`) overrides the program's stack default, else the stack handler, else the verb is @@ -109,11 +109,11 @@ commands. All projects use **uv** (Python) / **pnpm** (frontends). ### Create a new service (HTTP daemon) ```bash -castle program create my-service --stack python-fastapi --description "Does X" +wildpc program create my-service --stack python-fastapi --description "Does X" cd /data/repos/my-service && uv sync # implement it -castle program test my-service -castle service create my-service --program my-service --port 9001 -castle apply my-service # renders the unit + gateway route and starts it +wildpc program test my-service +wildpc service create my-service --program my-service --port 9001 +wildpc apply my-service # renders the unit + gateway route and starts it ``` The service reads its port/data dir from env vars that `deployments/my-service.yaml` @@ -122,12 +122,12 @@ maps via placeholders (see §6). Stack guide: **`docs/stacks/python-fastapi.md`* ### Create a CLI tool ```bash -castle program create my-tool --stack python-cli --description "Does Y" +wildpc program create my-tool --stack python-cli --description "Does Y" cd /data/repos/my-tool && uv sync -castle apply my-tool # installs the path deployment on PATH +wildpc apply my-tool # installs the path deployment on PATH ``` -`castle tool list --json` is the machine-readable tool catalog (each tool's real +`wildpc tool list --json` is the machine-readable tool catalog (each tool's real **executable**, which may differ from the program name). Stack: **`docs/stacks/python-cli.md`**. @@ -137,17 +137,17 @@ A job is a `manager: systemd` deployment with a `schedule` (cron). Generates a `.service` (Type=oneshot) + a `.timer`. ```bash -castle job create nightly --program my-tool --schedule "0 2 * * *" --launcher command -castle apply nightly +wildpc job create nightly --program my-tool --schedule "0 2 * * *" --launcher command +wildpc apply nightly ``` ### Create a static frontend ```bash # scaffold a Vite/React app under /data/repos/my-frontend (see docs/stacks/react-vite.md) -castle program build my-frontend # produces dist/ +wildpc program build my-frontend # produces dist/ # deployments/my-frontend.yaml → manager: caddy, root: dist -castle apply # served at my-frontend. +wildpc apply # served at my-frontend. ``` The gateway serves the build **in place** from `/` — no copy, no @@ -158,11 +158,11 @@ Supabase substrate: **`docs/stacks/supabase.md`**. ### Adopt an existing repo (no stack needed) ```bash -castle program add ~/projects/some-rust-tool # local path -castle program add https://github.com/me/widget.git --name widget +wildpc program add ~/projects/some-rust-tool # local path +wildpc program add https://github.com/me/widget.git --name widget ``` -Castle detects dev-verb commands (pyproject→uv/ruff/pytest, Cargo.toml→cargo, …) +Wild PC detects dev-verb commands (pyproject→uv/ruff/pytest, Cargo.toml→cargo, …) or you declare them under `commands:` in `programs/.yaml`. --- @@ -191,19 +191,19 @@ public: true # ALSO expose to the internet via Cloudflare tunnel (requires pro - There are **no path-prefix routes** — a whole subdomain maps to the backend root, so root-relative assets and `window.location` WebSocket URLs just work. -Inspect routes: `castle gateway` / `GET /gateway`. Regenerate routes + reload the -gateway: `castle apply` (converge). → Field-level detail: **`docs/registry.md`**. +Inspect routes: `wildpc gateway` / `GET /gateway`. Regenerate routes + reload the +gateway: `wildpc apply` (converge). → Field-level detail: **`docs/registry.md`**. --- ## 5. DNS & TLS — making names resolve and be trusted Two orthogonal questions for `https://foo./` to work from a LAN browser: -**resolve** (DNS) and **trust** (TLS). Castle doesn't run DNS — you add one +**resolve** (DNS) and **trust** (TLS). Wild PC doesn't run DNS — you add one **wildcard** record on the LAN's DNS server (usually the router): `address=//` (dnsmasq) pinned with a DHCP reservation. -`gateway.tls` in `castle.yaml` picks the trust mode: +`gateway.tls` in `wildpc.yaml` picks the trust mode: | `gateway.tls` | serves | client setup | when | |---------------|--------|--------------|------| @@ -215,83 +215,83 @@ Two orthogonal questions for `https://foo./` to work from a LAN browser: the names — the public zone has no A records). HTTPS also unlocks **secure context** (`crypto.subtle`, service workers), which plain-HTTP LAN hosts lack. -`acme` operational prerequisites (castle can't do these for you): +`acme` operational prerequisites (wildpc can't do these for you): - **DNS-plugin Caddy** at `/usr/local/bin/caddy` — `./install.sh --with-dns-plugin=cloudflare`. - **Provider token** stored as a secret and mapped into the gateway service env - (`CLOUDFLARE_API_TOKEN`); `castle apply` warns if missing. + (`CLOUDFLARE_API_TOKEN`); `wildpc apply` warns if missing. - **Bind :443/:80** — lower the floor once: `net.ipv4.ip_unprivileged_port_start=80` in `/etc/sysctl.d/` (beats `setcap`, which `NoNewPrivileges` would void). -- **Stage first**: `CASTLE_ACME_STAGING=1` at deploy, verify issuance, then unset +- **Stage first**: `WILDPC_ACME_STAGING=1` at deploy, verify issuance, then unset and redeploy for a production cert. → Full conceptual + step-by-step guide: **`docs/dns-and-tls.md`**. Read the actual -values for *this* node in `~/.castle/castle.yaml` (`gateway.domain`, `tls`, etc.). +values for *this* node in `~/.wildpc/wildpc.yaml` (`gateway.domain`, `tls`, etc.). --- ## 6. Environment, secrets, data, placeholders `defaults.env` in a deployment is the **single explicit source** of the env a -service/job runs with — castle injects nothing implicitly. Map the vars your -program reads to castle's computed values with placeholders: +service/job runs with — wildpc injects nothing implicitly. Map the vars your +program reads to wildpc's computed values with placeholders: ```yaml expose: { http: { internal: { port: 9001 }, health_path: /health } } defaults: env: MY_SERVICE_PORT: ${port} # = expose.http.internal.port - MY_SERVICE_DATA_DIR: ${data_dir} # = $CASTLE_DATA_DIR/ + MY_SERVICE_DATA_DIR: ${data_dir} # = $WILDPC_DATA_DIR/ PUBLIC_URL: ${public_url} # gateway origin (CORS/allowlists) - API_KEY: ${secret:MY_API_KEY} # reads ~/.castle/secrets/MY_API_KEY + API_KEY: ${secret:MY_API_KEY} # reads ~/.wildpc/secrets/MY_API_KEY ``` | placeholder | expands to | |-------------|-----------| | `${port}` | the service's `expose.http.internal.port` | -| `${data_dir}` | `$CASTLE_DATA_DIR/` (default `/data/castle/`) | +| `${data_dir}` | `$WILDPC_DATA_DIR/` (default `/data/wildpc/`) | | `${name}` | the deployment name | | `${public_url}` | `https://.` under acme, else `http://localhost:` | | `${secret:NAME}` | the secret `NAME` from the active backend | **Secret backend.** `${secret:NAME}` (and `read_secret()` for direct-read code) -resolve through a backend selected by the **`secrets:` block in `castle.yaml`** +resolve through a backend selected by the **`secrets:` block in `wildpc.yaml`** (`core/secret_backends.py`): ```yaml secrets: backend: openbao # or `file` (default) - addr: https://castle-openbao.:8200 - mount: castle # KV-v2 mount + addr: https://wildpc-openbao.:8200 + mount: wildpc # KV-v2 mount token_secret: OPENBAO_ROOT_TOKEN # vault token, read from a file node_prefix: nodes/ # optional: per-node overrides ``` -- **file** (default) → `~/.castle/secrets/NAME`. +- **file** (default) → `~/.wildpc/secrets/NAME`. - **openbao** → vault KV-v2 `mount/NAME`, no file fallback. The vault **token** is still a file (`token_secret`, the bootstrap root of trust — it can't live in the vault it opens). With `node_prefix`, a read tries `mount//NAME` then shared `mount/NAME`, so one vault serves shared secrets + per-node overrides - (e.g. each node's postgres password). Env vars (`CASTLE_SECRET_BACKEND`, - `CASTLE_OPENBAO_*`) override the block (tests/CI force `file`). + (e.g. each node's postgres password). Env vars (`WILDPC_SECRET_BACKEND`, + `WILDPC_OPENBAO_*`) override the block (tests/CI force `file`). - **This fleet runs `openbao`**: civil is the authority (root token), primer reads - via a least-privilege `castle-read` token. Only the bootstrap tier stays as + via a least-privilege `wildpc-read` token. Only the bootstrap tier stays as files (`OPENBAO_ROOT_TOKEN`/`OPENBAO_UNSEAL_KEY`, the `cloudflared` creds dir). -**Write secrets with `castle secret set NAME`, never by hand.** It targets the +**Write secrets with `wildpc secret set NAME`, never by hand.** It targets the *active* backend, so a value can't land in a store the resolver never reads — the failure mode where a file-written secret is silently ignored on an openbao fleet and `${secret:NAME}` degrades to a literal `` that a service -then uses as its credential. `castle apply` **refuses to converge** (exits +then uses as its credential. `wildpc apply` **refuses to converge** (exits non-zero, writes nothing) any deployment whose `${secret:…}` refs don't resolve in the active backend, naming the fix. -**Never** put secret *values* in `castle.yaml` or project dirs — use `${secret:…}`. -Roots: **`CASTLE_HOME`** (config/code/artifacts/secrets, default `~/.castle`, -env-only — it *contains* castle.yaml) and **program data** (base of `${data_dir}`, -default `/data/castle`) + **repos** (default `/data/repos`). The latter two resolve -**env > `castle.yaml` > default** — set `data_dir:` / `repos_dir:` in `castle.yaml` +**Never** put secret *values* in `wildpc.yaml` or project dirs — use `${secret:…}`. +Roots: **`WILDPC_HOME`** (config/code/artifacts/secrets, default `~/.wildpc`, +env-only — it *contains* wildpc.yaml) and **program data** (base of `${data_dir}`, +default `/data/wildpc`) + **repos** (default `/data/repos`). The latter two resolve +**env > `wildpc.yaml` > default** — set `data_dir:` / `repos_dir:` in `wildpc.yaml` (the single source of truth both the CLI and the api read), not a per-shell env var -that only one of them sees. → **`docs/registry.md`** (castle.yaml globals). +that only one of them sees. → **`docs/registry.md`** (wildpc.yaml globals). --- @@ -299,9 +299,9 @@ that only one of them sees. → **`docs/registry.md`** (castle.yaml globals). `public: true` (requires `proxy: true`) projects a service to the internet at `.` (a **separate** zone, so internal subdomain names -stay out of public DNS). `castle apply` generates the cloudflared ingress from the +stay out of public DNS). `wildpc apply` generates the cloudflared ingress from the set of public services. Needs `gateway.public_domain` + `gateway.tunnel_id` set and -the `castle-tunnel` service running. → One-time setup: **`docs/tunnel-setup.md`**. +the `wildpc-tunnel` service running. → One-time setup: **`docs/tunnel-setup.md`**. Routing only moves bytes — it does **not** supply a backend's own auth. Do not make a service public unless it authenticates or is meant to be open. @@ -310,19 +310,19 @@ a service public unless it authenticates or is meant to be open. ## 8. Mesh — multi-node coordination (opt-in) -Runs on **NATS JetStream** (`castle-nats`, TLS + token). Enable via env on -`castle-api`: `CASTLE_API_NATS_ENABLED=true`, `CASTLE_API_NATS_URL=tls://castle-nats.:4222`, -`CASTLE_API_NATS_TOKEN=${secret:NATS_TOKEN}`. Each node publishes its +Runs on **NATS JetStream** (`wildpc-nats`, TLS + token). Enable via env on +`wildpc-api`: `WILDPC_API_NATS_ENABLED=true`, `WILDPC_API_NATS_URL=tls://wildpc-nats.:4222`, +`WILDPC_API_NATS_TOKEN=${secret:NATS_TOKEN}`. Each node publishes its (secret-stripped) registry to a JetStream **KV** bucket, renews a **presence** key, and watches for peers; remote deployments surface as `manager: none` -**reference** kinds. A static **`role`** (`authority`|`follower`, in `castle.yaml`) +**reference** kinds. A static **`role`** (`authority`|`follower`, in `wildpc.yaml`) gates who may write the shared-config bucket. A consumed cross-node service (`requires: - ref: X` satisfied by a peer) is routed by the gateway with a presence-gated circuit-breaker. -Inspect + drive from the CLI: **`castle mesh status`** / **`castle mesh nodes`** / -**`castle mesh config list|get|set`** (or `GET /mesh/status`, `/nodes`, -`/mesh/config`). Modules: `castle_api.nats_client`, `.mesh`, `.mesh_gateway`, +Inspect + drive from the CLI: **`wildpc mesh status`** / **`wildpc mesh nodes`** / +**`wildpc mesh config list|get|set`** (or `GET /mesh/status`, `/nodes`, +`/mesh/config`). Modules: `wildpc_api.nats_client`, `.mesh`, `.mesh_gateway`, `.mdns`; secrets via `core` `secret_backends` (file default, OpenBao opt-in). → Full history + operations: **`docs/fleet-mesh-plan.md`**. @@ -332,8 +332,8 @@ Inspect + drive from the CLI: **`castle mesh status`** / **`castle mesh nodes`** | Topic | Doc | |-------|-----| -| Registry model, `castle.yaml`, every field, lifecycle | **`docs/registry.md`** | -| Why castle is shaped this way | **`docs/design.md`** | +| Registry model, `wildpc.yaml`, every field, lifecycle | **`docs/registry.md`** | +| Why wildpc is shaped this way | **`docs/design.md`** | | DNS resolution + the two TLS modes, acme recipe | **`docs/dns-and-tls.md`** | | Public exposure (cloudflared) one-time setup | **`docs/tunnel-setup.md`** | | Writing FastAPI services | **`docs/stacks/python-fastapi.md`** | @@ -341,9 +341,9 @@ Inspect + drive from the CLI: **`castle mesh status`** / **`castle mesh nodes`** | Writing React/Vite frontends | **`docs/stacks/react-vite.md`** | | Writing Hugo static sites | **`docs/stacks/hugo.md`** | | Database-backed apps (shared Supabase) | **`docs/stacks/supabase.md`** | -| **Developing Castle itself** (CLI/core/api/app, key files, endpoints) | **`docs/developing-castle.md`** | +| **Developing Wild PC itself** (CLI/core/api/app, key files, endpoints) | **`docs/developing-wildpc.md`** | -Castle's own programs live in this repo (`source: repo:` → cli, core, -castle-api, app). Your programs live under `/data/repos//` with an absolute +Wild PC's own programs live in this repo (`source: repo:` → cli, core, +wildpc-api, app). Your programs live under `/data/repos//` with an absolute `source:`. When in doubt about *this* node's actual config, read -`~/.castle/castle.yaml` and `castle status`. +`~/.wildpc/wildpc.yaml` and `wildpc status`. diff --git a/CLAUDE.md b/CLAUDE.md index c22dce2..cdfea35 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,5 +1,5 @@ # CLAUDE.md See @AGENTS.md — the single, agent-agnostic guide for this repo: how to use -Castle (create/deploy/expose/manage software) and how to develop Castle's own +Wild PC (create/deploy/expose/manage software) and how to develop Wild PC's own code. Everything lives there. diff --git a/README.md b/README.md index 0cc97a8..efb83b3 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# Castle +# Wild PC > Standing to author, run, govern, and maintain your own software. -A personal software platform. Castle manages independent services, tools, and +A personal software platform. Wild PC manages independent services, tools, and frontends — and launches your coding agents — from a single CLI, with a unified gateway, systemd integration, and a web dashboard. @@ -18,27 +18,27 @@ and configurations of existing tools. Many of these programs do not need a publi release process, an app store listing, or a conventional distribution channel. They need a reliable place to run. -Castle provides that place. +Wild PC provides that place. -Castle gives simple applications a consistent local environment for development, +Wild PC gives simple applications a consistent local environment for development, registration, deployment, discovery, and operation. Programs remain independent, but -Castle provides the surrounding structure: process management, routing, metadata, +Wild PC provides the surrounding structure: process management, routing, metadata, service lifecycle, logs, and a common interface for running and inspecting the software in your domain. -In this sense, Castle is a **personal software estate**: a practical way to organize +In this sense, Wild PC is a **personal software estate**: a practical way to organize the software you create and run yourself, without requiring every tool to become a fully packaged product. ## How it works -Castle separates *what software exists* from *how it runs*: +Wild PC separates *what software exists* from *how it runs*: -- **Programs** — the catalog. A program is a source repo Castle knows how to work +- **Programs** — the catalog. A program is a source repo Wild PC knows how to work with (dev verbs, build) and where it lives. One file per program under - `~/.castle/programs/.yaml`. + `~/.wildpc/programs/.yaml`. - **Deployments** — how a program is realized on this node. One file per deployment - under `~/.castle/deployments/.yaml`, discriminated by its **manager**: + under `~/.wildpc/deployments/.yaml`, discriminated by its **manager**: | manager | what it is | derived **kind** | |---------|------------|------------------| @@ -55,33 +55,33 @@ A program can have several deployments — a CLI that is both a `tool` on PATH a scheduled `job` — so a program has no single kind of its own; it *has deployments*, each with its own. -Standing everything up is one honest step: `castle apply` renders systemd units and +Standing everything up is one honest step: `wildpc apply` renders systemd units and gateway config from your config, then reconciles the runtime to match — activating what's enabled, restarting what changed, deactivating what's disabled. Edit config, -`castle apply`; `castle apply --plan` shows the diff first. +`wildpc apply`; `wildpc apply --plan` shows the diff first. ## Stacks -Castle **stacks** are pre-configured development environments that provide starting -points for building Castle programs. A stack can define the language, framework, -dependencies, tools, conventions, and Castle integration needed for a particular +Wild PC **stacks** are pre-configured development environments that provide starting +points for building Wild PC programs. A stack can define the language, framework, +dependencies, tools, conventions, and Wild PC integration needed for a particular kind of application — `python-fastapi`, `python-cli`, `react-vite`, `supabase`. Stacks are designed to work well with coding assistants. They give assistants a -consistent target when generating Castle programs, making it easier to produce +consistent target when generating Wild PC programs, making it easier to produce applications that are correctly structured, configured, and ready to run under -Castle. If your coding assistant understands Castle, it can help you create, +Wild PC. If your coding assistant understands Wild PC, it can help you create, register, manage, and evolve custom applications more efficiently. A stack seeds a new program's scaffold and default dev-verb commands, but it's optional at runtime: a program stands on its own via its declared `commands:` and -`source:`, so you can adopt any existing repo with `castle program add` — no stack +`source:`, so you can adopt any existing repo with `wildpc program add` — no stack required. ## Agents -Castle can launch your coding agents. Declare them in `castle.yaml` under `agents:` -— each entry is just a command Castle runs in a terminal: +Wild PC can launch your coding agents. Declare them in `wildpc.yaml` under `agents:` +— each entry is just a command Wild PC runs in a terminal: ```yaml agents: @@ -96,7 +96,7 @@ agents: The dashboard has a terminal dock that launches any declared agent in a pseudo-terminal (over a WebSocket) and manages live sessions (list, resume, kill). -Castle is **assistant-agnostic** — it only ever runs `command args` in a pty and +Wild PC is **assistant-agnostic** — it only ever runs `command args` in a pty and never parses the agent's output, so any interactive CLI works. With no `agents:` block set, a sensible default set (`claude`, `opencode`, `amplifier`, …) is offered. @@ -104,28 +104,28 @@ block set, a sensible default set (`claude`, `opencode`, `amplifier`, …) is of **Prerequisites:** a Debian/Ubuntu-family Linux with `apt` and `sudo`, `systemd` (user services), and `git`. `install.sh` sets up everything else — Docker, Caddy, -`uv`, and the `castle` CLI itself. +`uv`, and the `wildpc` CLI itself. ```bash -git clone ~/castle && cd ~/castle +git clone ~/wildpc && cd ~/wildpc -# One command: installs uv + the castle CLI, sets up infra (Docker, Caddy, MQTT, -# Postgres), creates ~/.castle, registers Castle's own control plane, builds the UI. +# One command: installs uv + the wildpc CLI, sets up infra (Docker, Caddy, MQTT, +# Postgres), creates ~/.wildpc, registers Wild PC's own control plane, builds the UI. ./install.sh -castle apply # converge the runtime to config (units, routes, run) -castle doctor # verify — every check should be green +wildpc apply # converge the runtime to config (units, routes, run) +wildpc doctor # verify — every check should be green open http://localhost:9000 # the dashboard ``` -`castle doctor` is your friend at every step: it inspects setup *and* runtime and, +`wildpc doctor` is your friend at every step: it inspects setup *and* runtime and, for anything not green, prints the exact next command. Run it any time something looks off — after an install, a deploy, or a config change. ### Exposure: from localhost to your own HTTPS domain Localhost is the first rung; you climb only as far as you need. Each rung is a small -config change plus `castle apply`, and `castle doctor` tells you what a rung still +config change plus `wildpc apply`, and `wildpc doctor` tells you what a rung still needs. | Rung | You get | What it takes | @@ -135,86 +135,86 @@ needs. | **Public** | a chosen service reachable from the internet | `public: true` on the service + a Cloudflare tunnel. → [docs/tunnel-setup.md](docs/tunnel-setup.md) | The jump to LAN HTTPS is the involved one (DNS + a token + binding `:443`). Set -`gateway.tls: acme` and run `castle doctor` — it enumerates exactly the pieces that +`gateway.tls: acme` and run `wildpc doctor` — it enumerates exactly the pieces that are still missing, each with its fix. ## Creating programs -`castle program create` scaffolds the source **and** its deployment from a stack: +`wildpc program create` scaffolds the source **and** its deployment from a stack: ```bash # A service — FastAPI app + a systemd service deployment (health, unit, route) -castle program create my-api --stack python-fastapi --description "Does something" -castle program test my-api -castle apply my-api # render unit + route, then start it +wildpc program create my-api --stack python-fastapi --description "Does something" +wildpc program test my-api +wildpc apply my-api # render unit + route, then start it # A tool — a CLI installed on your PATH -castle program create my-tool --stack python-cli --description "Does something" -castle apply my-tool # installs its path deployment on PATH +wildpc program create my-tool --stack python-cli --description "Does something" +wildpc apply my-tool # installs its path deployment on PATH # A static frontend — built once, served by the gateway -castle program create my-app --stack react-vite --description "Web interface" -castle program build my-app && castle apply +wildpc program create my-app --stack react-vite --description "Web interface" +wildpc program build my-app && wildpc apply # Adopt an existing repo (no stack needed — dev verbs detected or declared) -castle program add ~/projects/some-rust-tool +wildpc program add ~/projects/some-rust-tool ``` ## CLI Operations live under the resource they act on. `program` is the catalog; `service`, `job`, and `tool` are **views** over the one deployment set. Lifecycle -is convergence — `castle apply` — not a pile of per-kind verbs. +is convergence — `wildpc apply` — not a pile of per-kind verbs. ``` # Programs — the software catalog -castle program list|info|create|add|clone|delete|run -castle program build|test|lint|type-check|check [name] # dev verbs +wildpc program list|info|create|add|clone|delete|run +wildpc program build|test|lint|type-check|check [name] # dev verbs # Deployment lenses (service = systemd, job = systemd + schedule, tool = path) -castle service list|info|create|delete|restart|logs -castle job …same verbs; create takes --schedule -castle tool list|info # CLIs on your PATH +wildpc service list|info|create|delete|restart|logs +wildpc job …same verbs; create takes --schedule +wildpc tool list|info # CLIs on your PATH # Platform-wide -castle apply [name] [--plan] # converge runtime to config — the workhorse -castle list [--kind K] [--stack S] [--json] # catalog + every deployment view -castle status # unified runtime status -castle doctor # diagnose setup + health, with fix hints -castle restart [name] # imperative bounce (one or all) -castle gateway # status + route table (inspection) +wildpc apply [name] [--plan] # converge runtime to config — the workhorse +wildpc list [--kind K] [--stack S] [--json] # catalog + every deployment view +wildpc status # unified runtime status +wildpc doctor # diagnose setup + health, with fix hints +wildpc restart [name] # imperative bounce (one or all) +wildpc gateway # status + route table (inspection) ``` -To turn a deployment off, set `enabled: false` in its config and `castle apply` — +To turn a deployment off, set `enabled: false` in its config and `wildpc apply` — there's no start/stop/enable/install verb; the manager decides the mechanism (systemd unit, PATH install, gateway route), the verb is always `apply`. -`castle tool list --json` is the machine-readable tool catalog assistants use to +`wildpc tool list --json` is the machine-readable tool catalog assistants use to build context — it surfaces each tool's actual **executable** (which can differ from the program name, e.g. `litellm-intent-router` installs `intent-router`), its description, and whether it's installed. ## Configuration -The registry lives under `~/.castle/`: a global `castle.yaml` plus one file per +The registry lives under `~/.wildpc/`: a global `wildpc.yaml` plus one file per resource under `programs/` and `deployments/`. ```yaml -# ~/.castle/castle.yaml — globals +# ~/.wildpc/wildpc.yaml — globals gateway: port: 9000 -repo: /data/repos/castle # resolves `source: repo:` for castle's own programs +repo: /data/repos/wildpc # resolves `source: repo:` for wildpc's own programs ``` ```yaml -# ~/.castle/programs/my-api.yaml — the catalog entry +# ~/.wildpc/programs/my-api.yaml — the catalog entry description: Does something useful source: /data/repos/my-api stack: python-fastapi ``` ```yaml -# ~/.castle/deployments/my-api.yaml — a systemd service (derived kind: service) +# ~/.wildpc/deployments/my-api.yaml — a systemd service (derived kind: service) program: my-api manager: systemd run: { launcher: python, program: my-api } @@ -224,21 +224,21 @@ manage: { systemd: {} } defaults: env: MY_API_PORT: ${port} # = expose.http.internal.port - MY_API_DATA_DIR: ${data_dir} # = $CASTLE_DATA_DIR/my-api + MY_API_DATA_DIR: ${data_dir} # = $WILDPC_DATA_DIR/my-api API_KEY: ${secret:MY_API_KEY} ``` `defaults.env` is the **single, explicit source** of a deployment's environment — -Castle injects nothing implicitly. The placeholders `${port}`, `${data_dir}`, +Wild PC injects nothing implicitly. The placeholders `${port}`, `${data_dir}`, `${name}`, `${public_url}`, and `${secret:NAME}` map your program's own env var names -to Castle's computed values. Secrets live in `~/.castle/secrets/` (never in a repo). +to Wild PC's computed values. Secrets live in `~/.wildpc/secrets/` (never in a repo). ## Gateway, DNS & TLS Every gateway-exposed deployment gets its own subdomain — `.` — routed to it by the Caddy gateway (there are no path-prefix routes). Exposure is a single checkbox: `proxy: true` on a service, while a static deployment is inherently -served. The dashboard is `castle.` and the API `castle-api.`; on a +served. The dashboard is `wildpc.` and the API `wildpc-api.`; on a node with no domain, `:9000` serves the dashboard plus a `/api` proxy. `gateway.tls` is a per-node choice: `off` (plain HTTP on `:9000`) or `acme` (a real @@ -250,42 +250,42 @@ public internet is separately opt-in via a Cloudflare tunnel (`public: true`). S ## Layout ``` -~/.castle/ # instance: config, artifacts, secrets - castle.yaml # globals (gateway, repo, agents) +~/.wildpc/ # instance: config, artifacts, secrets + wildpc.yaml # globals (gateway, repo, agents) programs/ deployments/ # one file per program / deployment secrets/ # secret files (mode 700) artifacts/specs/ # generated Caddyfile, registry.yaml /data/repos// # your program source (absolute source:) -/data/castle// # per-deployment data volume -/ # castle itself: cli/ core/ castle-api/ app/ docs/ +/data/wildpc// # per-deployment data volume +/ # wildpc itself: cli/ core/ wildpc-api/ app/ docs/ ``` -**Independence principle:** your programs never depend on Castle. They accept -configuration (data dir, port, URLs) via environment variables; only Castle's own -programs (CLI, API, gateway) know Castle internals. +**Independence principle:** your programs never depend on Wild PC. They accept +configuration (data dir, port, URLs) via environment variables; only Wild PC's own +programs (CLI, API, gateway) know Wild PC internals. ## Dashboard & API -The **dashboard** (`app/`, served at `castle.` or `http://localhost:9000`) +The **dashboard** (`app/`, served at `wildpc.` or `http://localhost:9000`) lists programs and deployments, edits their config, drives lifecycle, shows the gateway route table and logs, and hosts the agent terminal dock. -**`castle-api`** (port 9020, proxied at `castle-api.`) is the control plane: +**`wildpc-api`** (port 9020, proxied at `wildpc-api.`) is the control plane: `/deployments`, `/programs`, `/services`, `/jobs`, `/gateway`, `/status`, an SSE `/stream`, config editing under `/config/…`, and the agent session endpoints under `/agents`. The full endpoint reference is in [CLAUDE.md](CLAUDE.md). ## Mesh (opt-in) -Castle nodes can discover each other via MQTT + mDNS to form a personal +Wild PC nodes can discover each other via MQTT + mDNS to form a personal infrastructure mesh — the gateway can route to services on other nodes and the dashboard shows discovered nodes and cross-node routes. It's all off by default; -single-node needs none of it. Enable on `castle-api` via `CASTLE_API_MQTT_ENABLED` -and `CASTLE_API_MDNS_ENABLED`. +single-node needs none of it. Enable on `wildpc-api` via `WILDPC_API_MQTT_ENABLED` +and `WILDPC_API_MDNS_ENABLED`. ## Docs -- [docs/registry.md](docs/registry.md) — the registry model, `castle.yaml`, deployment fields, lifecycle +- [docs/registry.md](docs/registry.md) — the registry model, `wildpc.yaml`, deployment fields, lifecycle - [docs/dns-and-tls.md](docs/dns-and-tls.md) — gateway routing, DNS, the `off` / `acme` TLS modes - [docs/stacks/](docs/stacks/) — per-stack guides (python-fastapi, python-cli, react-vite, supabase) - [AGENTS.md](AGENTS.md) — the canonical, assistant-agnostic operator guide (recipes, gateway, tunnel, mesh) diff --git a/app/.env b/app/.env index d88544d..49ee1bb 100644 --- a/app/.env +++ b/app/.env @@ -1,4 +1,4 @@ # The API base URL is derived at runtime (see src/services/api/client.ts): -# - dashboard at castle-app. -> castle-api. (cross-origin, CORS) +# - dashboard at wildpc-app. -> wildpc-api. (cross-origin, CORS) # - dev / off-mode :9000 (bare host) -> /api (same-origin) # Set VITE_API_BASE_URL only to force a specific base (rarely needed). diff --git a/app/index.html b/app/index.html index e1ab651..9b8cf79 100644 --- a/app/index.html +++ b/app/index.html @@ -4,7 +4,7 @@ - Castle + Wild PC
diff --git a/app/src/components/AddProgramForm.tsx b/app/src/components/AddProgramForm.tsx index 8f3427a..ce5aa87 100644 --- a/app/src/components/AddProgramForm.tsx +++ b/app/src/components/AddProgramForm.tsx @@ -21,7 +21,7 @@ function splitPath(raw: string): { dir: string | null; filter: string } { return { dir: raw.slice(0, idx), filter: raw.slice(idx + 1) } } -/** Adopt an existing repo as a program (the web `castle program add`). Programs +/** Adopt an existing repo as a program (the web `wildpc program add`). Programs * live on the *server's* filesystem, so the picker browses the server's dirs: type * a path (autocompletes as you go), navigate the listing, or paste a git URL. */ export function AddProgramForm({ @@ -181,7 +181,7 @@ export function AddProgramForm({ {target && (

Registering {target} - {isGit && " (cloned later via castle clone)"} + {isGit && " (cloned later via wildpc clone)"}

)} diff --git a/app/src/components/AssistantDock.tsx b/app/src/components/AssistantDock.tsx index ac56103..19be366 100644 --- a/app/src/components/AssistantDock.tsx +++ b/app/src/components/AssistantDock.tsx @@ -133,7 +133,7 @@ export function AssistantDock() { ? "border-[var(--primary)] text-[var(--foreground)]" : pillIdle, )} - title="Plain login shell in the castle repo" + title="Plain login shell in the wildpc repo" > Shell diff --git a/app/src/components/CommandPalette.tsx b/app/src/components/CommandPalette.tsx index fe3f72d..c73b51e 100644 --- a/app/src/components/CommandPalette.tsx +++ b/app/src/components/CommandPalette.tsx @@ -240,7 +240,7 @@ function PaletteBody({ onClose }: { onClose: () => void }) { )} - Run castle apply (or the Apply button) to converge. + Run wildpc apply (or the Apply button) to converge. ) diff --git a/app/src/components/HostSwitcher.tsx b/app/src/components/HostSwitcher.tsx index 030eeb3..1902aa8 100644 --- a/app/src/components/HostSwitcher.tsx +++ b/app/src/components/HostSwitcher.tsx @@ -4,12 +4,12 @@ import { useNodes } from "@/services/api/hooks" import type { NodeSummary } from "@/types" import { cn } from "@/lib/utils" -// Each machine's castle is served at its own origin (castle.), so hopping +// Each machine's wildpc is served at its own origin (wildpc.), so hopping // between hosts is a cross-origin navigation. This shows which host you're driving // (the is_local node) and lets you jump to a peer's dashboard, preserving the view. function dashboardUrl(n: NodeSummary): string { const base = n.gateway_domain - ? `https://castle.${n.gateway_domain}` + ? `https://wildpc.${n.gateway_domain}` : `http://${n.hostname}:${n.gateway_port}` return `${base}${window.location.pathname}` } @@ -58,7 +58,7 @@ export function HostSwitcher({ collapsed }: { collapsed: boolean }) { {open && (
- Castle hosts + Wild PC hosts
{list.map((n) => { const isCur = n.is_local diff --git a/app/src/components/Layout.tsx b/app/src/components/Layout.tsx index 84310db..2b4588e 100644 --- a/app/src/components/Layout.tsx +++ b/app/src/components/Layout.tsx @@ -50,7 +50,7 @@ const NAV: (NavLeaf | NavGroup)[] = [ { to: "/secrets", label: "Secrets", icon: KeyRound }, ] -const COLLAPSE_KEY = "castle-nav-collapsed" +const COLLAPSE_KEY = "wildpc-nav-collapsed" function NavLeafLink({ leaf, @@ -162,7 +162,7 @@ function NavItems({ collapsed, onNavigate }: { collapsed: boolean; onNavigate?: function Brand({ collapsed, onClick }: { collapsed?: boolean; onClick?: () => void }) { return ( - {collapsed ? "C" : "Castle"} + {collapsed ? "W" : "Wild PC"} ) } diff --git a/app/src/components/detail/ConfigPanel.tsx b/app/src/components/detail/ConfigPanel.tsx index 0602e03..348fad0 100644 --- a/app/src/components/detail/ConfigPanel.tsx +++ b/app/src/components/detail/ConfigPanel.tsx @@ -26,7 +26,7 @@ export function ConfigPanel({ deployment, configSection, onRefetch }: ConfigPane const navigate = useNavigate() const qc = useQueryClient() const [message, setMessage] = useState<{ type: "ok" | "error"; text: string } | null>(null) - // A deployment edit only persists to castle.yaml; the unit/process still need + // A deployment edit only persists to wildpc.yaml; the unit/process still need // a deploy (regenerate) + restart to actually take effect. const [pendingApply, setPendingApply] = useState(false) const [applying, setApplying] = useState(false) @@ -43,8 +43,8 @@ export function ConfigPanel({ deployment, configSection, onRefetch }: ConfigPane setMessage({ type: "ok", text: isDeployment - ? "Saved to castle.yaml — not live yet; apply to converge." - : "Saved to castle.yaml", + ? "Saved to wildpc.yaml — not live yet; apply to converge." + : "Saved to wildpc.yaml", }) if (isDeployment) setPendingApply(true) onRefetch() @@ -67,7 +67,7 @@ export function ConfigPanel({ deployment, configSection, onRefetch }: ConfigPane qc.invalidateQueries() onRefetch() } catch (e: unknown) { - // A self-apply of castle-api restarts it, killing the connection — expected. + // A self-apply of wildpc-api restarts it, killing the connection — expected. if (e instanceof TypeError) { setPendingApply(false) setMessage({ type: "ok", text: "Applied — the service is restarting." }) diff --git a/app/src/components/detail/CreateDeploymentForm.tsx b/app/src/components/detail/CreateDeploymentForm.tsx index f1dad1e..7aa280f 100644 --- a/app/src/components/detail/CreateDeploymentForm.tsx +++ b/app/src/components/detail/CreateDeploymentForm.tsx @@ -26,7 +26,7 @@ const KIND_INFO: Record = { static: { label: "Static", hint: "Static site served by the gateway" }, } -/** Create a deployment in castle.yaml, then deploy (and start, for a service). +/** Create a deployment in wildpc.yaml, then deploy (and start, for a service). * A pick-a-kind wizard: the chosen kind sets the manager and shows only its * relevant fields. Reachable standalone or prefilled from a program page. */ export function CreateDeploymentForm({ diff --git a/app/src/components/detail/JobFields.tsx b/app/src/components/detail/JobFields.tsx index b0006af..8113bca 100644 --- a/app/src/components/detail/JobFields.tsx +++ b/app/src/components/detail/JobFields.tsx @@ -93,7 +93,7 @@ export function JobFields({ job, onSave, onDelete }: Props) { width="w-48" mono placeholder="0 2 * * *" - hint="Cron expression — castle generates a systemd timer that runs the job on this schedule." + hint="Cron expression — wildpc generates a systemd timer that runs the job on this schedule." />
@@ -124,7 +124,7 @@ export function JobFields({ job, onSave, onDelete }: Props) { onSave={handleSave} onDelete={onDelete ? () => onDelete(job.id) : undefined} deleteLabel="Remove job" - confirmMessage={`Remove job "${job.id}" from castle.yaml? Run a deploy afterward to tear down its timer.`} + confirmMessage={`Remove job "${job.id}" from wildpc.yaml? Run a deploy afterward to tear down its timer.`} />
) diff --git a/app/src/components/detail/ProgramFields.tsx b/app/src/components/detail/ProgramFields.tsx index 61b939f..b0ecdb2 100644 --- a/app/src/components/detail/ProgramFields.tsx +++ b/app/src/components/detail/ProgramFields.tsx @@ -95,7 +95,7 @@ export function ProgramFields({ program, onSave, onDelete }: Props) { onChange={setSource} mono placeholder="/data/repos/my-prog" - hint="The working copy on disk. Castle runs dev verbs and builds here. Absolute path, or repo: for castle's own programs." + hint="The working copy on disk. Wild PC runs dev verbs and builds here. Absolute path, or repo: for wildpc's own programs." /> onDelete(service.id) : undefined} deleteLabel="Remove service" - confirmMessage={`Remove service "${service.id}" from castle.yaml? Run a deploy afterward to tear down its unit.`} + confirmMessage={`Remove service "${service.id}" from wildpc.yaml? Run a deploy afterward to tear down its unit.`} />
) diff --git a/app/src/components/detail/ToolFields.tsx b/app/src/components/detail/ToolFields.tsx index eb8ba38..f05a625 100644 --- a/app/src/components/detail/ToolFields.tsx +++ b/app/src/components/detail/ToolFields.tsx @@ -15,7 +15,7 @@ interface Props { /** Edit a tool's (path) deployment config. A path deployment has no launcher, * port, or schedule — only a description and its `tool_schema` (the neutral * tool-call definition handed to agents), plus its manager. It has no run env: - * a tool is a CLI on PATH, invoked from a shell with castle out of the loop, so + * a tool is a CLI on PATH, invoked from a shell with wildpc out of the loop, so * `defaults.env` is never applied (deploy.py only wires env for systemd units). */ export function ToolFields({ tool, onSave, onDelete }: Props) { const m = tool.manifest diff --git a/app/src/components/detail/fields.tsx b/app/src/components/detail/fields.tsx index 848dada..6af3eb5 100644 --- a/app/src/components/detail/fields.tsx +++ b/app/src/components/detail/fields.tsx @@ -170,7 +170,7 @@ export function useEnvSecrets(initial: Record) { Use ${"{port}"},{" "} ${"{data_dir}"},{" "} ${"{name}"},{" "} - ${"{public_url}"} for castle's computed values, + ${"{public_url}"} for wildpc's computed values, and ${"{secret:NAME}"} for secrets.

{Object.entries(env).map(([key, val]) => ( diff --git a/app/src/lib/labels.ts b/app/src/lib/labels.ts index 24feb2c..6bd14b0 100644 --- a/app/src/lib/labels.ts +++ b/app/src/lib/labels.ts @@ -82,7 +82,7 @@ export function detailPath(name: string, kind: string): string { /** * Full URL for a service exposed at .. The domain is - * derived from the dashboard's own host (it is served at castle.), so + * derived from the dashboard's own host (it is served at wildpc.), so * this returns null when the dashboard is on a bare host (off mode, no subdomains). */ export function subdomainUrl(subdomain: string): string | null { diff --git a/app/src/pages/Overview.tsx b/app/src/pages/Overview.tsx index 4a3c7c5..c8a2c00 100644 --- a/app/src/pages/Overview.tsx +++ b/app/src/pages/Overview.tsx @@ -78,7 +78,7 @@ export function Overview() { return (
- + {nodes && } diff --git a/app/src/pages/ProgramDetail.tsx b/app/src/pages/ProgramDetail.tsx index f70ffa0..f67387d 100644 --- a/app/src/pages/ProgramDetail.tsx +++ b/app/src/pages/ProgramDetail.tsx @@ -93,7 +93,7 @@ export function ProgramDetailPage() { Program Info

- Where the source lives and how castle works with it. + Where the source lives and how wildpc works with it.

{deployment.source && ( diff --git a/app/src/pages/ServiceDetail.tsx b/app/src/pages/ServiceDetail.tsx index 53557fc..88b6135 100644 --- a/app/src/pages/ServiceDetail.tsx +++ b/app/src/pages/ServiceDetail.tsx @@ -15,7 +15,7 @@ export function ServiceDetailPage() { const { data: deployment, isLoading, error, refetch } = useService(name ?? "") const { data: statusResp } = useStatus() const health = statusResp?.statuses.find((s) => s.id === name) - const isGateway = name === "castle-gateway" + const isGateway = name === "wildpc-gateway" const { data: caddyfile } = useCaddyfile(isGateway) if (isLoading) { diff --git a/app/src/pages/SystemMap.tsx b/app/src/pages/SystemMap.tsx index f5cec6f..e79524b 100644 --- a/app/src/pages/SystemMap.tsx +++ b/app/src/pages/SystemMap.tsx @@ -84,7 +84,7 @@ const KIND_COLOR: Record = { // Dependency edges are colored by the protocol of the thing being consumed (the // target's endpoint). "system" = a socket-less dep (a tool/package) or a target -// castle doesn't model an endpoint for. +// wildpc doesn't model an endpoint for. const PROTO_COLOR: Record = { http: "#58a6ff", pg: "#3b82f6", @@ -100,7 +100,7 @@ const HANDLE = { opacity: 0, width: 8, height: 8 } as const // User's manual node arrangement. Positions default to the computed lane layout; // any node the user drags is remembered here and survives refetches + reloads. -const POS_KEY = "castle-map-positions" +const POS_KEY = "wildpc-map-positions" type PosMap = Record function loadPositions(): PosMap { try { @@ -295,7 +295,7 @@ function protoOf(url: string | null): string { } } -// An external resource — a `reference` (manager: none) castle doesn't run. Sits in +// An external resource — a `reference` (manager: none) wildpc doesn't run. Sits in // the External zone as a registry entry + a drop target for consumes edges. function ExternalNode({ data }: NodeProps) { const d = data as { label: string; host: string; focusDim?: boolean; focused?: boolean } @@ -325,7 +325,7 @@ function ExternalNode({ data }: NodeProps) { ) } -// A deployment on another castle node (mesh-discovered). Read-only — you manage +// A deployment on another wildpc node (mesh-discovered). Read-only — you manage // remote deployments from that node. function RemoteNode({ data }: NodeProps) { const d = data as { @@ -1336,7 +1336,7 @@ export function SystemMapPage() { open={!!confirmDel} danger title={`Delete ${confirmDel?.name}?`} - body={`This removes the ${confirmDel?.kind} deployment from castle.yaml and tears it down. The program's source is kept.`} + body={`This removes the ${confirmDel?.kind} deployment from wildpc.yaml and tears it down. The program's source is kept.`} confirmLabel="Delete" onConfirm={doDelete} onCancel={() => setConfirmDel(null)} @@ -1345,7 +1345,7 @@ export function SystemMapPage() { ) } -const LEGEND_KEY = "castle-map-legend-open" +const LEGEND_KEY = "wildpc-map-legend-open" function Legend() { const [open, setOpen] = useState(() => { @@ -1461,7 +1461,7 @@ function InspectPanel({ )} -