Kastell Architecture
kastell-ops
Kastell CLI patterns, architecture, anti-patterns, and decision trees. Use automatically when working in Kastell codebase or when asked about Kastell server infrastructure, security audit, hardening, lock, provision, or provider management.
SKILL.md
Full skill instructions
Kastell Architecture
Kastell is a CLI toolkit for provisioning, securing, and managing self-hosted servers. TypeScript, ESM, strict mode. 31 CLI commands, 13 MCP tools, 4 cloud providers (hetzner, digitalocean, vultr, linode), 2 platform adapters (coolify, dokploy).
Live Context
Version: !node -e "import('fs').then(f=>console.log(JSON.parse(f.readFileSync('package.json','utf8')).version)).catch(()=>console.log('unknown'))"
Registered servers:
!kastell list 2>/dev/null || echo "No servers registered"
Architecture File Map
src/
commands/ # 31 thin CLI wrappers (parse args + delegate only)
core/ # Business logic (ALL computation here)
providers/ # Cloud API: hetzner, digitalocean, vultr, linode
adapters/ # Platform abstraction: coolify, dokploy
interface.ts # PlatformAdapter contract
factory.ts # getAdapter(), detectPlatform(), resolvePlatform()
mcp/
server.ts # 13 tool registrations
tools/ # Handler files (Zod schema + handler per tool)
utils/ # ssh, config, cloudInit, modeGuard, migration
types/ # ServerMode, ServerRecord, Platform
constants.ts # PROVIDER_REGISTRY (single source of truth)
index.ts # CLI entry point
Layer Rules
| Layer | Path | Responsibility | Rule |
|---|---|---|---|
| Commands | src/commands/ | Parse CLI args, call core, display output | ZERO business logic |
| Core | src/core/ | All business logic, orchestration | No UI/chalk/ora imports |
| Providers | src/providers/ | Cloud API calls per provider | Implements cloud CRUD |
| Adapters | src/adapters/ | Platform-specific ops (Coolify/Dokploy) | Via PlatformAdapter interface |
| MCP | src/mcp/ | MCP server + tool handlers | Delegates to core |
| Utils | src/utils/ | SSH, config, modeGuard, errorMapper | Shared infrastructure |
Adapter Contract
Access adapters via getAdapter(platform) from src/adapters/factory.ts. Never import CoolifyAdapter or DokployAdapter directly in commands.
interface PlatformAdapter {
readonly name: string; // "coolify" | "dokploy"
readonly port: number; // 8000 (Coolify) | 3000 (Dokploy)
readonly defaultLogService: string; // matches platform name
readonly platformPorts: readonly number[]; // ports protected from firewall removal
getCloudInit(serverName: string): string;
healthCheck(ip: string, domain?: string): Promise<HealthResult>;
createBackup(ip: string, serverName: string, provider: string): Promise<PlatformBackupResult>;
getStatus(ip: string): Promise<PlatformStatusResult>;
update(ip: string): Promise<UpdateResult>;
restoreBackup?(ip, backupPath, manifest): Promise<PlatformRestoreResult>; // optional
}
Factory exports: getAdapter(platform), detectPlatform(ip), resolvePlatform(server)
Provider Registry
| Provider | Env Key | Display Name |
|---|---|---|
| hetzner | HETZNER_TOKEN | Hetzner Cloud |
| digitalocean | DIGITALOCEAN_TOKEN | DigitalOcean |
| vultr | VULTR_TOKEN | Vultr |
| linode | LINODE_TOKEN | Linode (Akamai) |
PROVIDER_REGISTRY in src/constants.ts is the single source of truth for providers.
What Do I Want to Add?
New CLI command
src/commands/<name>.ts— thin wrapper (parse + delegate, no logic)src/core/<name>.ts— all business logic heresrc/index.ts— register withprogramsrc/__tests__/— test core, not command
New audit check
src/core/audit/<category>/— add to existing category- Update check catalog in
src/core/audit/catalog.ts - No new command file needed — runs through
kastell audit
New provider
src/providers/<name>.ts— implements base.ts contractsrc/constants.ts— add to PROVIDER_REGISTRY- No adapter changes — providers handle cloud API only
New MCP tool
src/mcp/tools/server<Name>.ts— Zod schema + handlersrc/mcp/server.ts— import +registerTool()- Annotations:
readOnlyHint/destructiveHint/idempotentHint
Key Conventions
- ESM project (
"type": "module") —import, notrequire KASTELL_SAFE_MODE+isSafeMode()= destructive operation guardassertValidIp()before every SSH operationsanitizedEnvfor subprocess callssanitizeResponseData()whitelist approach for API error responses- Config dir: user home
kastell/directory (auto-migrated from legacy name) PROVIDER_REGISTRY= single source of truth for providerswithProviderErrorHandlingHOF for consistent provider error handlingdescribe.eachwithjest.resetAllMocks()(notclearAllMocks())
Scripts (Deterministic)
Run without LLM — deterministic analysis scripts:
# Parse audit JSON into 5 security domain summaries
kastell audit --server myserver --json > /tmp/audit.json
scripts/parse_audit.sh /tmp/audit.json
# Generate fleet-wide server score table
kastell fleet --json > /tmp/fleet.json
scripts/fleet_report.sh /tmp/fleet.json
# Compare audit check count vs test coverage
scripts/check_coverage.sh
Reference Files
- 31 CLI commands — see references/commands.md
- 13 MCP tools — see references/mcp-tools.md
- Patterns and test templates — see references/patterns.md
- Known pitfalls — see references/pitfalls.md
