Commit Graph

215 Commits

Author SHA1 Message Date
Paul Payne
82f12c9d61 Remove buttons: confirm + block program removal with active deployments
The Stage 2 editor split dropped the old confirm() — Remove program/service/job
deleted immediately on click. Restored confirmation on all three (FormFooter
window.confirm with a per-type message).

Removing a program that still has services/jobs referencing it would orphan
their program: ref. Now blocked:
- API: DELETE /config/programs/{name} → 409 'Programs with active jobs or
  services cannot be removed. Delete these first: …' (authoritative).
- App: ProgramFields shows the blocked reason instead of a Remove button when
  the program has deployments.
- CLI: castle delete refuses a program with referencing deployments not named
  the same (which would survive the delete dangling).

Verified live: DELETE program lakehouse → 409. api 52 / cli 24 green; build clean.
2026-06-14 15:52:57 -07:00
Paul Payne
2a073fc0b4 Stage 4: App — create service/job forms (UI twin of castle expose)
CreateDeploymentForm builds a service or job in castle.yaml (PUT /config/...),
then deploys it (POST /deploy {name} → unit + Caddyfile + reload) and, for a
service, starts it (POST /services/{name}/start), then navigates to the new
detail page.

Reachable two ways:
- Program page Deployments section: 'Create service' / 'Create job' buttons,
  prefilled from the program (name, program ref, run target, runner).
- Dashboard: standalone '+ Add service' / '+ Add job' (deployments can run
  anything, not just castle programs).

Service form covers port / port_env / health / proxy path / proxy host; job
form covers schedule. Name validated against existing services/jobs.

Verified: the configs the form builds validate (PUT 200) and round-trip clean.
App builds; api 52 / core 94 green from prior stages.
2026-06-14 15:41:13 -07:00
Paul Payne
dea585b15b Stage 3: App — program/deployment model & controls
- ProgramActions: install/uninstall (activate) shown only for tools and
  no-service frontends. Daemons activate via a service/job, so the program
  page no longer offers a misleading Install/Uninstall (which secretly
  enabled/disabled the service).
- Program page: new Deployments section listing the services/jobs that run
  the program, as links (a program → 0-N services/jobs).
- Service page: 'Runs' row from the run_target summary field (the old code
  dug into manifest.run, which was empty pre-Stage-1), a Host row for
  proxy_host, and a convenience link to the referenced program.
- lifecycle.activate on a daemon with no service now errors toward
  'castle expose' instead of silently installing its binary to PATH.

core 94 green; ruff + app build clean.
2026-06-14 15:37:20 -07:00
Paul Payne
73698fafe7 Stage 2: App — split the one editor into typed program/service/job forms
The single service-shaped DeploymentFields was reused for all three sections,
so the program page bled service fields (port/proxy/health) and couldn't edit
program config, and the job page couldn't edit its schedule. Replaced it with:

- ProgramFields  — description, source, stack, behavior, version, repo/ref,
  system deps (+ read-only commands). Edits the program catalog entry.
- ServiceFields  — run target, port, port_env, health, proxy path + host,
  env/secrets. Prefills correctly now (Stage 1) and round-trips through PUT.
- JobFields      — schedule, run target, env/secrets.

ConfigPanel selects the form by section. Shared Field/TextField/useEnvSecrets/
FormFooter helpers in detail/fields.tsx. Retired DeploymentFields. run editing
only touches python/command specs (container/node/remote run blocks preserved).

Verified: GET service manifest → PUT back validates 200. App type-checks/builds.
2026-06-14 15:34:07 -07:00
Paul Payne
1c37380cec Stage 1: API — correct editable shape + program/deployment links
The config editor read service/job detail 'manifest' as the castle.yaml spec
but the endpoint returned the flat *runtime registry* shape for deployed
entries — so service fields (port/health/proxy) never prefilled and a save
would 422/corrupt. Detail 'manifest' is now always the editable castle.yaml
ServiceSpec/JobSpec when the entry is declared there; the flat registry shape
is a display-only fallback for entries that exist only at runtime.

Also:
- ServiceSummary/JobSummary gain run_target (what it runs) + program ref;
  ServiceSummary gains proxy_host.
- ProgramSummary gains services[]/jobs[] — the deployments referencing a
  program (a program → 0-N services/jobs), for linking + create prefill.
- App types updated to match; deleted orphaned DeploymentEditor/AddDeployment
  (leftovers from the removed ConfigEditor).

Verified live: service manifest nested (expose.http.internal.port, proxy.caddy
.host prefill), run_target=lakehoused, program lakehouse → services:[lakehouse].
api 52 green; ruff + app build clean.
2026-06-14 15:30:32 -07:00
Paul Payne
4840cbe72a feat: castle UX fixes from the lakehouse adoption test
Driven by adopting lakehouse (an existing daemon that bundles its own SPA) —
see docs/findings-lakehouse.md.

#3 deploy reloads the gateway. castle deploy regenerated the Caddyfile but
   left the running Caddy on the old config, so new proxy routes were silently
   dead. Deploy now reloads the gateway when it's running.

#1 castle expose <program>. Turns an adopted program into a service
   (run/port/health/proxy/systemd) in one command — the missing
   daemon-to-service step. Flags: --port --health --path --run --port-env
   --host --no-proxy.

#2 port_env mapping. A service can declare expose.http.internal.port_env so
   castle sets the env var the program actually reads (e.g. lakehoused reads
   LAKEHOUSED_DAEMON_PORT, not castle's convention LAKEHOUSE_PORT). Castle now
   genuinely drives an adopted daemon's bind port.

#4a auto-base for react-vite. The build passes VITE_BASE = the gateway serve
   prefix (/<name>/, or / for castle-app); vite.config reads it. A castle-built
   frontend now works at its subpath with no hand-tuned base. castle-app's
   vite.config updated as the reference.

#4b host-based routing. proxy.caddy.host routes a whole hostname to the backend
   root via a host matcher inside the :9000 site, so a root-based SPA (base="/")
   serves unchanged — the fix for proxying an app castle can't rebuild. Caddyfile
   now emits 'auto_https off' (HTTP-only gateway on a non-standard port).

Nit: activate skips the editable reinstall when the tool is already on PATH.

Tests: core 94, cli 24, api 52; ruff + app build clean. Verified live: lakehouse
runs under systemd, API at /lakehouse, full UI (SPA boots) via host routing.
2026-06-14 13:16:34 -07:00
Paul Payne
203f5212e6 fix(app): drive program activate toggle off active, not installed
The Programs table's install/uninstall icon keyed off `installed`
(shutil.which(name)) — a PATH lookup that only makes sense for python CLI
tools. Static frontends have no PATH binary so they were always
`installed=false` → a green 'Install' icon even while actively served;
container daemons were `installed=null` → same. And for tools the value
reflected the castle-api service's PATH, which isn't guaranteed to include
~/.local/bin across reboots, so they could all flip to 'not installed'.

Switch ProgramActions/ProgramTable/ProgramDetail to the uniform `active`
state (Phase 2's lifecycle.is_active: PATH-independent ~/.local/bin check for
tools, systemctl for services/jobs, served-assets check for static
frontends). Now every program reflects real state: served frontends show
Uninstall, container daemons (no install verb) show no icon.

App builds clean.
2026-06-14 12:26:29 -07:00
Paul Payne
432bf2c57f chore(app): remove orphaned ConfigEditor page
Imported nowhere and absent from the router — an unreachable bulk-editor
superseded by the typed detail pages (ServiceDetail/ProgramDetail/
ScheduledDetail), which edit via ConfigPanel. It also called endpoints that
no longer exist (GET /components, PUT/DELETE /config/components/{name}; the
API uses /deployments and typed /config/{programs,services,jobs}/{name}).

App builds clean.
2026-06-14 12:17:41 -07:00
Paul Payne
0515faadfd refactor(app): rename Component* UI widgets to accurate domain names
The dashboard widgets were named after the old 'component' concept. Renamed
each to what it actually renders:

- ComponentCard  → ServiceCard      (ServiceSummary, /services/, service actions)
- ComponentTable → ProgramTable     (ProgramSummary[], the program catalog)
- ComponentEditor→ DeploymentEditor (DeploymentDetail, the unified type)
- ComponentFields→ DeploymentFields (AnyDetail)
- AddComponent   → AddDeployment

Also purged the domain term 'component' from the rest of the app: the
useComponent hook → useDeployment, ConfigPanel + the three detail pages'
`component` prop/var → `deployment`, and the gateway/node table headers
(Component → Program / Deployment). Props renamed to match (component →
service / program / deployment). The React UI directory app/src/components/
keeps its conventional name.

App type-checks and builds clean.
2026-06-14 12:13:41 -07:00
Paul Payne
a5dcb6b346 feat: CLI consistency pass — unified args, verbs, and status
#1 Positional dest unified to `name` across info + dev verbs (was `project`).
#2 install/uninstall help now states the activate semantics (tool→PATH,
   service/job→systemd, frontend→served) rather than 'to PATH'.
#4 `service status` (a plural op) moved to `services status`; the singular
   `service` group is enable/disable only.
#5 `list --behavior` now filters the program *catalog* by its real behavior
   field. behavior (what a program is) and service/job (how it's deployed) are
   separate axes: `list` shows a Programs catalog plus Services/Jobs deployment
   views; a behavior filter scopes the catalog only.
#6 `list` uses lifecycle.is_active uniformly for the status dot (tool on PATH /
   service running / job timer / static frontend served) instead of registry
   presence.
#7 New `castle restart <name>` — restart a service (unit) or job (timer).
#8 New `castle status` — unified services + jobs + programs activation view.
#9 New `castle format [name]` dev verb (ruff format / pnpm format); wired into
   stacks DEV_ACTIONS + handlers.
#10 New `castle up` — deploy + start everything in one shot.

Docs: CLAUDE.md command reference refreshed. Tests updated to the two-axis
list model (+ a daemon-behavior program fixture). core 92 · cli 24 · api 52
green; ruff clean.
2026-06-14 11:57:58 -07:00
Paul Payne
3f3b88f17b refactor: Align vocabulary — component → program / deployment
Canonical terms (see docs/registry.md glossary):
- program: any catalog entry (tool/daemon/frontend) — the software
- service / job: how a program is deployed (systemd .service / .timer)
- deployment: umbrella for the unified service+job+program view

Changes:
- core: ServiceSpec/JobSpec component: → program: (component accepted as
  back-compat validation_alias); DeployedComponent → Deployment.
- api: ComponentSummary/Detail → DeploymentSummary/Detail; GET /components
  → /deployments; GatewayRoute.component → program; GatewayInfo
  .component_count → deployment_count; ServiceActionResponse/action keys
  component → program.
- app: matching type renames, /components → /deployments hook, route
  /component/:name → /deployment/:name, GatewayRoute.program.
- docs: component-registry.md → registry.md (+ canonical glossary);
  CLAUDE.md endpoint list refreshed (drop removed /tools, add typed
  program/service/job + config routes).

Tests: core 92, cli 24, api 52 green; app build clean; ruff clean.
2026-06-14 11:05:22 -07:00
Paul Payne
482524bfe5 Phase 2b: serve static frontends in place from repo dist
The Caddyfile generator now emits static-frontend routes that root directly at
<source>/<build.outputs[0]> (e.g. /data/repos/castle/app/dist) instead of a copy
under ~/.castle/artifacts/content. Removes _copy_app_static and the central
content-dir staging entirely; rebuilds are live without a copy step.

- caddyfile.py: manifest-driven static routes (castle-app at /, others at /<name>)
- ReactViteHandler.install just builds in place (no copy); uninstall is a no-op
- is_active(static frontend) = repo dist exists
- test isolation updated for the generator's config use

Dashboard + power-graph-app verified serving from repo dist; 168 tests pass.
2026-06-13 18:16:09 -07:00
Paul Payne
2953aea548 Runtime half: convergent deploy + unified active lifecycle
Convergent deploy (prefix-based prune): castle deploy now removes orphaned
castle-* systemd units/timers no longer in the registry. Full deploy only;
partial (--name) deploys preserve siblings. Fixes the orphaned-timer / stale-
path drift class.

Container runner: derive --name from the service name (castle-<name>) instead
of the image name; skip the <PREFIX>_DATA_DIR env injection for containers
(avoids colliding with image env namespaces like NEO4J_*).

Unified active lifecycle (core/lifecycle.py): install/uninstall keep their
names but become activate/deactivate, dispatching by behavior — tool→PATH,
daemon/self-serving-frontend/job→systemd, static frontend→served. is_active
reports a uniform state (PATH-independent for tools). The CLI install/uninstall
and service enable/disable route through the extracted core.

API + dashboard: surface a uniform `active` state on programs alongside
`installed`; ProgramDetail shows active/inactive.

Tests: test_deploy_prune (6), test_lifecycle (5). 168 tests pass; lint clean.
2026-06-13 18:08:54 -07:00
Paul Payne
400e0b253b Less stack-centric and location-centric model. 2026-06-13 17:26:49 -07:00
Paul Payne
7bf98c17b7 Ignored working dir. 2026-06-13 13:27:20 -07:00
Paul Payne
74a902ee62 Clean up. 2026-06-13 12:24:41 -07:00
payneio
9fe95f6d1e fix: Update import from castle_api.tools to castle_api.programs
The tools.py file was renamed to programs.py but the import in
main.py was not updated in the previous commit.
2026-04-27 22:06:54 -07:00
payneio
691216b04e refactor: Extract deploy logic to castle-core and add deploy API endpoint
- Extract all deploy logic from CLI into castle-core/deploy.py as a reusable deploy() function
  * Resolves services/jobs to DeployedComponents
  * Generates systemd units and Caddyfile
  * Copies frontend assets and reloads systemd
  * Returns DeployResult with deployed count and messages
- Simplify CLI deploy command to a thin wrapper calling castle_core.deploy()
- Add POST /deploy endpoint to castle-api for remote deployment management
  * Supports optional {name} body param to deploy one or all services
  * Returns deployment result as JSON
- Register deploy router in castle-api main.py
- Update README with new deploy endpoint documentation

This enables full remote management: build, test, lint, and deploy any castle
node over HTTP (e.g. POST http://node:9000/api/deploy)
2026-04-27 22:01:39 -07:00
payneio
e1a9386177 refactor: Remove castle sync command
Sync was a bootstrap command that ran `git submodule update` and `uv tool install` for each program. Neither machine uses git submodules anymore, and the install functionality is already available as a program action (`POST /programs/{name}/install`).

Removed files:
- cli/src/castle_cli/commands/sync.py

Updated files:
- cli/src/castle_cli/main.py (removed sync subparser and dispatch)
- README.md (removed sync from Quick Start and CLI reference)
2026-04-27 21:57:08 -07:00
payneio
9fd2221e05 refactor: Rename component pages to program pages and remove tools concept
The 'tools' concept was just a filtered view of programs and added unnecessary
complexity to the frontend. This commit:

- Removes ToolCard.tsx and Tools.tsx (pages/components never used in router)
- Renames ComponentDetail.tsx → ProgramDetail.tsx (exports ProgramDetailPage)
- Renames ComponentRedirect.tsx → ProgramRedirect.tsx (exports ProgramRedirect)
- Updates imports in routes.tsx to match new file names
- Fixes stale comment in hooks.ts

Frontend naming now aligns with the unified programs model.
2026-04-27 21:10:31 -07:00
payneio
2069e30353 refactor: Remove dedicated /tools endpoints, use /programs?behavior=tool instead
Eliminates inconsistency where tools had their own filtered view but daemons and frontends didn't.

Changes:
- API: Removed tools router and ToolSummary/ToolDetail models. Added optional behavior query parameter to GET /programs for filtering by program type (tool, daemon, frontend).
- Frontend: Updated hooks to pass behavior param to usePrograms instead of separate useTools. Updated components to use ProgramSummary type. Removed useToolDetail hook.
- Docs: Updated API documentation to reflect program behavior filtering.
2026-04-27 21:06:57 -07:00
payneio
36d2d6073c refactor: Move program endpoints to separate programs router
Relocates the POST /programs/{name}/{action} endpoint from the tools
router to a new programs_router for better API documentation
organization. Program lifecycle actions (build, test, lint, etc.) now
appear under 'programs' in the API docs instead of 'tools'.
2026-04-27 20:58:51 -07:00
payneio
22122b86f4 docs: Update README to reflect current project state
- Add castle.yaml creation step to Quick Start (was missing from bootstrap)
- Add `castle build` to CLI reference
- Standardize terminology: components → programs throughout
- Fix YAML runner example (tool → program)
- Update architecture diagram to show ~/.castle/ runtime layout
- Correct data directory path (~/.castle/data/<name>/)
- Rewrite API reference table with all current endpoints
- Remove instance-specific component deployment tables
2026-04-27 20:52:55 -07:00
payneio
b720a96c6e feat: Add castle build command
Added castle build [project] command following the same pattern as castle test and castle lint. Stack handlers already had build methods — this just wires up the CLI to expose them.

Supports building a single project or all projects, providing feature parity with existing project-level commands.
2026-04-27 20:48:35 -07:00
payneio
529ca53242 refactor: Remove component install-by-path from sync
Simplified castle sync by removing the _try_install() function and the loop that installed components via install.path. Only python-runner service installs remain.

This removes dead complexity — the install.path feature was unused and added unnecessary code paths to maintain.
2026-04-27 20:48:29 -07:00
payneio
a553525c71 feat: Add support for remote services
- Added base_url field to DeployedComponent for external URL proxying
- Updated Caddyfile generator to proxy to base_url when present instead of requiring local port
- Added runner: remote handling in deploy command (returns empty run_cmd, marks as unmanaged)
- Reformatted long ternary expression in registry.py for readability

This enables castle to manage services that proxy to external endpoints without requiring a local process.
2026-04-27 20:48:25 -07:00
Paul Payne
a28d40c49e Fixes 2026-04-27 20:19:16 -07:00
Paul Payne
01a09d6a61 feat: Implement unit specification and expansion logic in configuration 2026-04-08 16:57:19 -07:00
Paul Payne
8be129259c refactor: Restructure ~/.castle/ as the instance directory
Move all instance state into ~/.castle/ with clear separation:
- code/        — user project source (was components/)
- artifacts/   — generated specs, built content (was generated/, static/)
- data/        — service runtime data (was /data/castle/)
- secrets/     — credentials

castle.yaml moves to ~/.castle/castle.yaml as the canonical location.
Source paths use repo: prefix for git repo programs (castle-api, app)
and relative paths for user projects (code/central-context).

Add idempotent install.sh for bootstrapping infrastructure (Docker,
Caddy, MQTT, Postgres, Neo4j) with interactive service detection.

Remove components/ from repo (now in ~/.castle/code/), deinit
submodules, remove .gitmodules.
2026-03-09 22:03:37 -07:00
Paul Payne
b7628c590b refactor: Remove unnecessary HTTP expose configuration from MQTT service 2026-03-05 10:16:25 -08:00
Paul Payne
0c70559b9d refactor: Enhance health check logic to separate HTTP and systemd checks for clarity and maintainability 2026-03-05 10:16:17 -08:00
Paul Payne
99e70f8543 install_extras 2026-03-05 10:01:26 -08:00
Paul Payne
5446fbb950 feat: Add favicon.svg and link it in index.html for improved branding 2026-03-05 10:00:45 -08:00
Paul Payne
ca2d780018 Full source paths. 2026-02-23 23:15:19 -08:00
Paul Payne
88e7d9ff17 Fix test. 2026-02-23 22:59:10 -08:00
Paul Payne
efab2a7893 refactor: Update component specifications to use 'program' and 'behavior' attributes, enhancing clarity and consistency across the application 2026-02-23 22:56:18 -08:00
Paul Payne
0d36e4f72a Refactor component terminology to programs in config and manifest
- Updated the terminology from "components" to "programs" across the codebase, including in config loading, saving, and manifest specifications.
- Introduced a new `stacks.py` file to handle lifecycle actions for development stacks, implementing handlers for Python and React Vite stacks.
- Adjusted tests to reflect the new program structure and ensure proper functionality.
- Revised documentation to align with the new terminology and structure, ensuring clarity on the purpose and configuration of programs, services, and jobs.
2026-02-23 22:09:41 -08:00
Paul Payne
f559fba143 feat: Introduce job and service categorization for components, enhancing routing and display logic across the application 2026-02-23 17:02:55 -08:00
Paul Payne
56b9c8ddf1 refactor: Update component structure to use behavior and stack attributes, enhancing clarity and functionality across services, tools, and frontends 2026-02-23 16:32:55 -08:00
Paul Payne
3343e955fd feat: Implement multi-node support with MQTT and mDNS for service discovery and coordination 2026-02-23 02:30:12 -08:00
Paul Payne
eeaa5045d0 refactor: Decouple roles. 2026-02-23 01:49:24 -08:00
Paul Payne
72d35f2641 refactor: Enhance component info and list commands with deployed state from registry 2026-02-23 00:07:42 -08:00
Paul Payne
5e3e01a5b6 refactor: Update runner specifications from 'python_uv_tool' to 'python' across components and documentation 2026-02-22 23:56:37 -08:00
Paul Payne
73e4a2ba00 fix: Improve command resolution in deploy and sync processes, adding warnings for missing tools 2026-02-22 23:36:51 -08:00
Paul Payne
d52d8829ba refactor: Separate runtime from build. Enhance configuration and registry management, migrate to registry-based component handling 2026-02-22 23:19:16 -08:00
Paul Payne
033a76ccfd refactor: Update project structure to place tools and frontends under components/ 2026-02-22 22:56:54 -08:00
Paul Payne
006cafa1e8 fix: Correct project directory paths in create command tests 2026-02-22 22:55:10 -08:00
Paul Payne
41d4c574cb refactor: Migrate CLI and API components to use castle-core for configuration and manifest handling 2026-02-22 22:54:38 -08:00
Paul Payne
6d0332e32b feat: Add various tools for document processing and management
- Introduced `docx-extractor` for extracting content and metadata from Word .docx files.
- Added `docx2md` for converting Word .docx files to Markdown format.
- Implemented `gpt` for generating text using OpenAI's GPT models.
- Created `html2text` for converting HTML content to plain text.
- Developed `mbox2eml` for converting MBOX mailbox files to individual .eml files.
- Added `md2pdf` for converting Markdown files to PDF format.
- Introduced `mdscraper` for combining text files into a single markdown document.
- Created `pdf-extractor` for extracting content and metadata from PDF files.
- Developed `pdf2md` for converting PDF files to Markdown format.
- Implemented `protonmail` for managing ProtonMail emails via Bridge.
- Added `schedule` for managing systemd timers and services.
- Introduced `search` for managing searchable collections of files.
- Added `text-extractor` for extracting content and metadata from text files.
- Removed outdated recommendations document.
2026-02-22 22:18:41 -08:00
Paul Payne
eab6f8b535 refactor: update tools handling and improve CLI documentation 2026-02-22 21:34:00 -08:00