This repository is managed with swamp.
- Search before you build. When automating AWS, APIs, or any external service: (a) search local types with
swamp model type search <query>, (b) search community extensions withswamp extension search <query>, (c) if a community extension exists, install it withswamp extension pull <package>instead of building from scratch, (d) only create a custom extension model inextensions/models/if nothing exists. Use theswamp-extensionskill for guidance. Thecommand/shellmodel is ONLY for ad-hoc one-off shell commands, NEVER for wrapping CLI tools or building integrations. - Extend, don't be clever. When a model covers the domain but lacks the method you need, extend it with
export const extension— don't bypass it with shell scripts, CLI tools, or multi-step hacks. One method, one purpose. Useswamp model type describe <type> --jsonto check available methods. - Use the data model. Once data exists in a model (via
lookup,start,sync, etc.), reference it with CEL expressions. Don't re-fetch data that's already available. - CEL expressions everywhere. Wire models together with CEL expressions. Always prefer
data.latest("<name>", "<dataName>").attributes.<field>over the deprecatedmodel.<name>.resource.<spec>.<instance>.attributes.<field>pattern. - Verify before destructive operations. Always
swamp model get <name> --jsonand verify resource IDs before running delete/stop/destroy methods. - Prefer fan-out methods over loops. When operating on multiple targets, use a single method that handles all targets internally (factory pattern) rather than looping N separate
swamp model method runcalls against the same model. Multiple parallel calls against the same model contend on the per-model lock, causing timeouts. A single fan-out method acquires the lock once and produces all outputs in one execution. Checkswamp model type describefor methods that accept filters or produce multiple outputs. - Extension npm deps are bundled, not lockfile-tracked. Swamp's bundler inlines all npm packages (except zod) into extension bundles at bundle time.
deno.lockandpackage.jsondo NOT cover extension model dependencies — this is by design. Always pin explicit versions innpm:import specifiers (e.g.,npm:lodash-es@4.17.21). - Reports for reusable data pipelines. When the task involves building a repeatable pipeline to transform, aggregate, or analyze model output (security reports, cost analysis, compliance checks, summaries), create a report extension. Use the
swamp-reportskill for guidance.
IMPORTANT: Always load swamp skills, even when in plan mode. The skills provide essential context for working with this repository.
swamp-getting-started- Interactive onboarding for new swamp usersswamp-model- Work with swamp models (creating, editing, validating)swamp-workflow- Work with workflows (creating, editing, running)swamp-vault- Manage secrets and credentialsswamp-data- Manage model data lifecycle and query with CELswamp-report- Run and configure reports for models and workflowsswamp-repo- Repository managementswamp-extension- Create custom extensions (models, vaults, drivers, datastores, reports)swamp-extension-publish- Publish extensions to the registryswamp-issue- Submit bug reports and feature requestsswamp-troubleshooting- Diagnose swamp problems and verify swamp's health
IMPORTANT: At the start of every conversation, run
swamp model search --json. If no models are returned (empty result), you MUST
immediately invoke the swamp-getting-started skill before doing anything else.
This walks new users through an interactive onboarding tutorial.
If models already exist, start by using the swamp-model skill to work with
swamp models.
Use swamp --help to see available commands. For a machine-readable JSON
schema of the CLI (commands, options, arguments) intended for agent
consumption, run swamp help [<command>...] — e.g. swamp help returns
the full tree, and swamp help model method run scopes to a subtree.
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
# Install all dependencies (root + workspaces, rebuilds native modules)
npm run install:all
# Development
npm run dev:backend # Backend on port 3000 (tsx watch)
npm run dev:frontend # Frontend dev server on port 5173
npm run dev:fullstack # Build frontend + serve everything from backend
# Build
npm run build # Build both frontend and backend
# Testing
npm run test # Run all tests (backend + frontend, no-watch)
npm run test --workspace=backend # Backend tests only
npm run test --workspace=frontend # Frontend tests only
npm run test:watch --workspace=backend # Backend watch mode
npm run test:e2e # Playwright E2E tests
npm run test:e2e:ui # Playwright with UI mode
npm run test:e2e:headed # Playwright in headed (visible) browser
npm run test:e2e:debug # Playwright debug mode
# Pre-commit hooks
npm run setup:hooks # Install pre-commit hooks
npm run precommit # Run pre-commit checks manually
# Run a single test file
cd backend && npx vitest run test/unit/SomeService.test.ts
# Linting
npm run lint # Lint both workspaces (0 warnings allowed)
npm run lint:fix # Auto-fix lint issues
# Component type checking (eslint and tsc do not see .svelte files)
npm run check:components # svelte-check against the recorded baseline
npm run check:update --workspace=frontend # re-record it after fixing errors
# Secret scan over the whole tracked tree, not just the pre-commit subset
bash scripts/quality/secret-scan.shBackend uses tsx watch for hot-reload during development. The frontend dev server proxies API calls to the backend.
Pabawi is an infrastructure management web UI — a monorepo with a Node.js/Express/TypeScript backend and a Svelte 5 SPA frontend.
All infrastructure integrations (Bolt, PuppetDB, Puppetserver, Hiera, Ansible, SSH, AWS, Azure, Proxmox) are plugins declared in backend/src/plugins/registry.ts. Every plugin:
- Extends
BasePlugin(integrations/BasePlugin.ts) and implementsisEnabled(),initialize(),healthCheck() - Optionally implements
ExecutionToolPlugin(can run commands) orInformationSourcePlugin(provides inventory/facts) - Is registered with
IntegrationManager(integrations/IntegrationManager.ts), which handles lifecycle, health aggregation, and routes data requests to the correct plugin
The plugin registry (plugins/registry.ts) is a declarative array of PluginRegistryEntry objects. server.ts iterates this array in a single loop — no per-plugin init blocks. Each entry has resolveConfig() (returns null to skip) and create() (factory).
IntegrationManager is the central registry. When inventory or facts are requested, it fans out to all enabled InformationSourcePlugins and merges results using a priority system (SSH: 50, Bolt: 5, PuppetDB: 10, Puppetserver: 8, Ansible: 5, Hiera: 6, Proxmox/AWS/Azure: 7). When executing commands, it dispatches to the correct ExecutionToolPlugin.
server.ts— Express app init, DI container setup, plugin registry loop, middleware wiring, route factory mountingcontainer/—DIContainerclass with typedServiceRegistryinterface (logger, config, expertMode). Route factories receive the container and resolve services from itplugins/—registry.tsdeclares all integration plugins as aPluginRegistryEntry[]array withresolveConfig()andcreate()per entryconfig/—ConfigServicewraps all env vars with Zod validation; always use this, neverprocess.envdirectly. Secrets:JWT_SECRET(required),PABAWI_LIFECYCLE_TOKEN(optional, defaults to empty)integrations/<name>/— One directory per integration:<Name>Plugin.ts(lifecycle + routing) and<Name>Service.ts(business logic, CLI spawning, API calls)mcp/— MCP (Model Context Protocol) server: read-only infrastructure query interface for LLM clients.McpServer.ts(factory + RBAC gates),McpToolHandlers.ts(8 tools:inventory_list,facts_get,reports_query,catalogs_get,hiera_lookup,executions_list,integrations_list,journal_query),McpOutputSummariser.ts(strips verbose fields for LLM-friendly output),McpServiceUser.ts(idempotent provisioning of themcp-serviceuser). Enabled viaMCP_ENABLED=true; session-based HTTP transport atPOST/GET/DELETE /mcp.services/— Cross-cutting services:ExecutionQueue(concurrent limiting, FIFO),StreamingExecutionManager(SSE real-time output),CommandWhitelistService(security),DatabaseService,AuthenticationService,BatchExecutionService,JournalService(audit trail of infrastructure events),AuditLoggingService(user-action audit log),PuppetRunHistoryService(persists Puppet run history),NodeLinkingService(correlates the same node across integration sources),ExpertModeService(debug/verbose UI toggle), and RBAC services (UserService,RoleService,PermissionService,GroupService)routes/— Express route factories. All exportcreateXxxRouter(container)functions. All async handlers wrapped withasyncHandler()fromutils/middleware/— Auth (JWT), RBAC, error handler, rate limiting, security headers,deduplication.ts(request deduplication)database/—DatabaseService.ts(migration-first approach),ExecutionRepository.ts(CRUD for execution history). All schema inmigrations/*.sql. Multi-database support via adapter pattern:DatabaseAdapter.ts(interface),SQLiteAdapter.ts(default),PostgresAdapter.ts(optional),AdapterFactory.ts(selects adapter from config) — useDatabaseService, never instantiate adapters directly.types/— Shared type declarations (express.d.tsfor request augmentation,mcp-sdk.d.tsfor MCP SDK types)errors/— Typed error classes extending base classes; use these instead of genericErrorvalidation/— Zod schemas for request body validation
Bolt command output is parsed from JSON; both _output and _error fields must be extracted from failed task results. Inventory and facts are cached (30 s and 5 min TTL respectively) inside each plugin's service.
App.svelte— Root: initializes router, auth guard, and setup checkpages/— One Svelte component per page routecomponents/— Shared UI componentslib/— Core utilities and reactive state:router.svelte.ts— Client-side router using Svelte 5 runes ($state)auth.svelte.ts— JWT auth stateapi.ts— HTTP infrastructure (get, post, put, del) with retry logic and error handlingproxmoxApi.ts— Proxmox provisioning API functionsawsApi.ts— AWS EC2 API functionsazureApi.ts— Azure VM API functionsexecutionStream.svelte.ts— SSE client for real-time command output (SSE-first, single-fetch fallback)expertMode.svelte.ts— Debug info toggleintegrationColors.svelte.ts— Per-integration color constantstoast.svelte.ts— Notification system
The frontend uses Svelte 5 runes throughout ($state(), $effect(), $derived()). State that needs to persist across components lives in the lib/*.svelte.ts files as module-level rune state.
All configuration is via backend/.env. Run scripts/setup.sh for interactive setup. Key variable groups: PORT/HOST/LOG_LEVEL, JWT_SECRET (required), PABAWI_LIFECYCLE_TOKEN (optional), BOLT_*, PUPPETDB_*, PUPPETSERVER_*, HIERA_*, ANSIBLE_*, SSH_*, AWS_*, AZURE_*, PROXMOX_*, COMMAND_WHITELIST*, CACHE_*, CONCURRENT_EXECUTION_LIMIT, MCP_ENABLED.
See docs/configuration.md for the full reference. Other useful docs: docs/mcp.md (MCP setup and tools), docs/permissions-rbac.md (RBAC model), docs/architecture.md (system overview), docs/api.md (REST API reference), docs/integrations/ (per-integration guides).
- Backend tests live in
backend/test/(unit, integration, security, middleware, properties with fast-check) - Frontend tests co-located with source in
frontend/src/**/*.test.ts - E2E tests in
e2e/using Playwright - Database tests use in-memory SQLite
- Use
supertestfor HTTP route testing in backend
Use LoggerService everywhere — never console.log. Pass structured metadata: { component, integration, operation }.
@.kirograph/claude.md