| 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. CLI commands, first-party 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;
readonly port: number;
readonly defaultLogService: string;
readonly platformPorts: readonly number[];
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>;
}
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 here
src/index.ts — register with program
src/__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 contract
src/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 + handler
src/mcp/server.ts — import + registerTool()
- 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:
kastell audit --server myserver --json > /tmp/audit.json
scripts/parse_audit.sh /tmp/audit.json
kastell fleet --json > /tmp/fleet.json
scripts/fleet_report.sh /tmp/fleet.json
scripts/check_coverage.sh
Reference Files