--- name: kastell-ops description: 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. user-invocable: false allowed-tools: Bash, Read, Glob, Grep effort: medium memory: project --- # 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. ```typescript 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; createBackup(ip: string, serverName: string, provider: string): Promise; getStatus(ip: string): Promise; update(ip: string): Promise; restoreBackup?(ip, backupPath, manifest): Promise; // 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 1. `src/commands/.ts` — thin wrapper (parse + delegate, no logic) 2. `src/core/.ts` — all business logic here 3. `src/index.ts` — register with `program` 4. `src/__tests__/` — test core, not command ### New audit check 1. `src/core/audit//` — add to existing category 2. Update check catalog in `src/core/audit/catalog.ts` 3. No new command file needed — runs through `kastell audit` ### New provider 1. `src/providers/.ts` — implements base.ts contract 2. `src/constants.ts` — add to PROVIDER_REGISTRY 3. No adapter changes — providers handle cloud API only ### New MCP tool 1. `src/mcp/tools/server.ts` — Zod schema + handler 2. `src/mcp/server.ts` — import + `registerTool()` 3. Annotations: `readOnlyHint` / `destructiveHint` / `idempotentHint` ## Key Conventions - ESM project (`"type": "module"`) — `import`, not `require` - `KASTELL_SAFE_MODE` + `isSafeMode()` = destructive operation guard - `assertValidIp()` before every SSH operation - `sanitizedEnv` for subprocess calls - `sanitizeResponseData()` whitelist approach for API error responses - Config dir: user home `kastell/` directory (auto-migrated from legacy name) - `PROVIDER_REGISTRY` = single source of truth for providers - `withProviderErrorHandling` HOF for consistent provider error handling - `describe.each` with `jest.resetAllMocks()` (not `clearAllMocks()`) ## Scripts (Deterministic) Run without LLM — deterministic analysis scripts: ```bash # 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](references/commands.md) - 13 MCP tools — see [references/mcp-tools.md](references/mcp-tools.md) - Patterns and test templates — see [references/patterns.md](references/patterns.md) - Known pitfalls — see [references/pitfalls.md](references/pitfalls.md)