refactor(cli): resource-first command surface

Names collide across resource types (a program and a service can share a name),
so the CLI no longer resolves bare names. Operations live under the resource
they act on; platform lifecycle and the cross-resource overview stay top-level.

  castle program  list|info|create|add|clone|delete|run|install|uninstall|
                  build|test|lint|format|type-check|check
  castle service  list|info|create|delete|deploy|enable|disable|start|stop|restart|logs
  castle job      <same verbs>   (create takes --schedule)
  castle gateway  start|stop|reload|status
  castle list | status | deploy [name]        # cross-cutting
  castle start | stop | restart               # whole platform (systemd verbs)

Key changes:
- info/delete/list/run are resource-scoped (no more cross-section name match).
- delete only removes the named resource's entry; program delete still blocks
  when a deployment references it.
- per-resource start/stop/restart (systemctl on the unit/timer); top-level
  start/stop/restart act on the whole platform.
- expose → 'service create' (more general: --program ref, --runner); gained
  'job create' (the CLI never had one).
- dropped up/down — bringing things online is the honest 'deploy && start'.

Docs (CLAUDE.md, registry.md, stack guides) and user-facing messages updated.
core 94 / cli 24 / api 52 green; ruff clean. Verified live: program/service/job
list+info scope correctly, service create+scoped-delete round-trips.
This commit is contained in:
2026-06-14 16:25:34 -07:00
parent 82f12c9d61
commit 5c6d8ec6f1
16 changed files with 556 additions and 511 deletions

View File

@@ -157,9 +157,9 @@ repo: https://github.com/me/widget.git
ref: v2.1.0 # optional branch/tag/commit
```
`repo` records a git URL so `castle clone` can provision the source on a fresh
`repo` records a git URL so `castle program clone` can provision the source on a fresh
machine. When `source:` points at an existing working copy, that takes
precedence. Use `castle add <path|url>` to register an existing repo as a program.
precedence. Use `castle program add <path|url>` to register an existing repo as a program.
### `system_dependencies` — Required system packages
@@ -240,7 +240,7 @@ manage:
systemd: {}
```
Enables `castle service enable/disable` and `castle logs`. An empty `{}`
Enables `castle service enable/disable` and `castle service logs`. An empty `{}`
uses defaults (enable=true, restart=on-failure, restart_sec=2).
Full options:
@@ -292,7 +292,7 @@ semantics as services.
Every program's source lives in one place — `~/.castle/code/<name>/`. It can
arrive there a few ways:
1. **Scaffold a new one** with `castle create` — writes the project into
1. **Scaffold a new one** with `castle program create` — writes the project into
`~/.castle/code/<name>/` and registers it in `castle.yaml` with
`source: code/<name>`.
2. **Clone an existing project**`git clone <url> ~/.castle/code/<name>`,
@@ -310,14 +310,14 @@ inside the castle git repo and are referenced with `source: repo:<name>`.
## Registering a new program
### Via `castle create` (recommended)
### Via `castle program create` (recommended)
```bash
# Service — scaffolds into ~/.castle/code/, assigns port, registers in castle.yaml
castle create my-service --stack python-fastapi --description "Does something"
castle program create my-service --stack python-fastapi --description "Does something"
# Tool — scaffolds into ~/.castle/code/
castle create my-tool --stack python-cli --description "Does something"
castle program create my-tool --stack python-cli --description "Does something"
```
### Manually
@@ -363,10 +363,10 @@ services:
### Service lifecycle
```bash
castle create my-service --stack python-fastapi # 1. Scaffold + register
castle program create my-service --stack python-fastapi # 1. Scaffold + register
cd ~/.castle/code/my-service && uv sync # 2. Install deps
# ... implement ...
castle test my-service # 3. Run tests
castle program test my-service # 3. Run tests
castle service enable my-service # 4. Generate systemd unit, start
castle gateway reload # 5. Update Caddy routes
```
@@ -376,17 +376,17 @@ on failure. Manage with:
```bash
castle logs my-service -f # Tail logs
castle run my-service # Run in foreground (for debugging)
castle service run my-service # Run in foreground (for debugging)
castle service disable my-service # Stop and remove systemd unit
```
### Tool lifecycle
```bash
castle create my-tool --stack python-cli # 1. Scaffold + register
castle program create my-tool --stack python-cli # 1. Scaffold + register
cd ~/.castle/code/my-tool && uv sync # 2. Install deps
# ... implement ...
castle test my-tool # 3. Run tests
castle program test my-tool # 3. Run tests
uv tool install --editable ~/.castle/code/my-tool/ # 4. Install to PATH
```
@@ -406,7 +406,7 @@ jobs:
systemd: {}
```
`castle service enable my-job` generates both a `.service` (Type=oneshot)
`castle job enable my-job` generates both a `.service` (Type=oneshot)
and a `.timer` file.
## Infrastructure paths

View File

@@ -1,9 +1,9 @@
# Python Tools in Castle
> **This is a stack — creation-time guidance for writing _new_ CLI tools.**
> A stack is a template + conventions, not a runtime requirement. `castle create
> A stack is a template + conventions, not a runtime requirement. `castle program create
> --stack python-cli` scaffolds from it and seeds the program's default dev-verb
> commands. An existing CLI adopted with `castle add` doesn't need this stack — it
> commands. An existing CLI adopted with `castle program add` doesn't need this stack — it
> declares its own `commands:`. See @docs/registry.md for `commands:`,
> `stack:` (optional), and `repo:`.
@@ -49,7 +49,7 @@ Examples: `code/pdf2md/`, `code/gpt/`, `code/protonmail/`
## Creating a new tool
```bash
castle create my-tool --stack python-cli --description "Does something"
castle program create my-tool --stack python-cli --description "Does something"
cd ~/.castle/code/my-tool && uv sync
```

View File

@@ -1,9 +1,9 @@
# Web APIs in Castle
> **This is a stack — creation-time guidance for writing _new_ FastAPI services.**
> A stack is a template + conventions, not a runtime requirement. `castle create
> A stack is a template + conventions, not a runtime requirement. `castle program create
> --stack python-fastapi` scaffolds from it and seeds the program's default
> dev-verb commands. An existing service adopted with `castle add` doesn't need
> dev-verb commands. An existing service adopted with `castle program add` doesn't need
> this stack — it declares its own `commands:`. See @docs/registry.md
> for `commands:`, `stack:` (optional), and `repo:`.
@@ -442,10 +442,10 @@ uv run ruff format . # Format
## Scaffolding
`castle create` generates all of this automatically:
`castle program create` generates all of this automatically:
```bash
castle create my-service --stack python-fastapi --description "Does something useful"
castle program create my-service --stack python-fastapi --description "Does something useful"
```
See @docs/registry.md for manifest fields, castle.yaml structure,

View File

@@ -1,9 +1,9 @@
# Web Frontends in Castle
> **This is a stack — creation-time guidance for writing _new_ frontends.**
> A stack is a template + conventions, not a runtime requirement. `castle create
> A stack is a template + conventions, not a runtime requirement. `castle program create
> --stack react-vite` scaffolds from it and seeds the program's default dev-verb
> commands. An existing frontend adopted with `castle add` doesn't need this
> commands. An existing frontend adopted with `castle program add` doesn't need this
> stack — it declares its own `commands:`. See @docs/registry.md for
> `commands:`, `stack:` (optional), and `repo:`.