## Context The tool system currently supports: - ToolTypes with compose templates and basic metadata - ToolConfigs as simple key-value pairs (env vars or files) - Instance creation via compose rendering - Basic flat-list UI at `/tool-configs` Users need a much richer system for defining, configuring, and running development tools. ## Goals / Non-Goals **Goals:** - Support both Docker Compose and Dockerfile for tool definitions - Add readiness probes with configurable commands and timeouts - Create reusable config file collections ("folders") mountable as volumes - Add rich tool config fields (port, start_command, working_directory, volumes, env vars) - Build a unified "Tool Workshop" UI for all tool management - Support per-project overrides on config folders - Maintain backward compatibility with existing built-in tool types **Non-Goals:** - Docker image registry management (assume local builds or public images) - Real-time collaborative tool editing - Tool marketplace/sharing between users - Advanced orchestration (Kubernetes, Swarm) - Config folder versioning/Git integration ## Architecture ### Data Model ``` ┌─────────────────────────────────────────────────────────────────┐ │ TOOL WORKSHOP │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ ToolType │────▶│ ToolConfig │◀────│ ConfigFolder │ │ │ │ (Blueprint) │ │ (Settings) │ │ (Files) │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ ToolInstance │ │ │ │ (Runtime + Volumes) │ │ │ └──────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` ### ToolType Model ```python class ToolType: # Existing fields name: str # unique identifier display_name: str description: str | None category: str interfaces: list[str] # ["web", "terminal"] default_port: int required_variables: list[str] is_builtin: bool # New fields definition_type: str # "compose" | "dockerfile" compose_template: str | None # YAML template (if definition_type == "compose") dockerfile_template: str | None # Dockerfile content (if definition_type == "dockerfile") build_context: dict | None # {"files": {"path": "content"}} for dockerfile builds readiness_probe: dict | None # {"command": "...", "timeout": 30, "interval": 2} ``` **Decision**: Store both compose and dockerfile, use `definition_type` to determine which to use. This allows easy switching and migration. ### ToolConfig Model ```python class ToolConfig: # Existing fields user_id: UUID tool_type_id: UUID project_id: UUID | None # null = global config key: str value: str config_type: str # "env" | "file" file_path: str | None # New fields port_override: int | None # Override tool type default port start_command: str | None # Override container start command working_directory: str | None # Working directory inside container environment_variables: dict | None # JSON {"KEY": "value", ...} volumes: list[dict] | None # JSON [{"source": "...", "target": "...", "type": "..."}] ``` **Decision**: Store env vars and volumes as JSONB for flexibility. Port as integer with validation. ### ConfigFolder Model (NEW) ```python class ConfigFolder: id: UUID user_id: UUID name: str # e.g., "my-dotfiles", "vscode-settings" description: str | None mount_path: str # Default mount path in container (e.g., "/home/user/.config") files: dict # JSON {"relative/path": "content", ...} project_overrides: dict | None # JSON {project_id: {"mount_path": "...", "files": {...}}} is_active: bool # Quick toggle created_at, updated_at ``` **Decision**: Files stored as JSONB with relative paths as keys. This is simple and sufficient for config files (not binary assets). ### Volume Mount Resolution When creating an instance, volumes are resolved in this priority order: ``` 1. ToolConfig.volumes (explicit per-config mounts) 2. ConfigFolder mounts (user's active config folders) 3. ToolType default volumes (from compose/dockerfile) ``` Config folder files are written to the instance directory under `volumes//` and mounted from there. ### Readiness Probe System ```python class ReadinessProbe: command: str # e.g., "curl -f http://localhost:8080/health" timeout: int # seconds (default: 30) interval: int # seconds between checks (default: 2) retries: int # max attempts (default: timeout/interval) ``` **Execution Flow**: 1. Start container 2. Wait for container to be running 3. Execute probe command inside container via `docker exec` 4. If success → mark instance as "running" 5. If timeout → mark instance as "failed" with probe output in logs **Decision**: Probes run inside the container using `docker exec`. This works for both network-based probes (curl) and command-based probes (binary version checks). ### Instance Creation Flow ``` 1. Generate instance ID and directory 2. Resolve ToolConfig (global + project-specific) 3. Write config files: a. .env file (from env-type ToolConfigs) b. Config files (from file-type ToolConfigs) c. Config folder files (to volumes//) 4. IF ToolType.definition_type == "dockerfile": a. Write Dockerfile + build context files b. Build image: docker build -t . c. Generate compose from template using built image 5. IF ToolType.definition_type == "compose": a. Render compose template with variables 6. Write docker-compose.yml 7. docker compose up -d 8. Connect to backend network 9. IF readiness_probe defined: a. Execute probe with timeout b. Update status based on result 10. IF web interface: a. Create Cloudflare tunnel b. Update URL ``` ## UI Design ### Tool Workshop Page (`/tool-workshop`) ``` ┌─────────────────────────────────────────────────────────────────┐ │ Tool Workshop [+ New] │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────┐ ┌──────────────────────────────────────┐ │ │ │ │ │ │ │ │ │ MY TOOLS │ │ [Tool Type Builder] │ │ │ │ │ │ │ │ │ │ ▼ Code Editor│ │ Name: [____________] │ │ │ │ □ VS Code │ │ Type: (•) Compose ( ) Dockerfile │ │ │ │ □ Cursor │ │ │ │ │ │ │ │ [Compose Template / Dockerfile] │ │ │ │ ▼ AI Tools │ │ ┌────────────────────────────────┐ │ │ │ │ □ OpenCode │ │ │ version: '3.8' │ │ │ │ │ □ Continue │ │ │ services: │ │ │ │ │ │ │ │ app: │ │ │ │ │ CONFIGS │ │ │ image: ... │ │ │ │ │ │ │ │ ports: │ │ │ │ │ ▼ Global │ │ │ - "{{PORT}}:8080" │ │ │ │ │ □ dotfiles │ │ │ volumes: │ │ │ │ │ □ api-keys │ │ │ - ... │ │ │ │ │ │ │ └────────────────────────────────┘ │ │ │ │ ▼ Project X │ │ │ │ │ │ □ overrides│ │ Readiness Probe: │ │ │ │ │ │ Command: [curl -f localhost:8080] │ │ │ │ │ │ Timeout: [30] seconds │ │ │ │ │ │ │ │ │ │ │ │ [Save Tool Type] │ │ │ │ │ │ │ │ │ └──────────────┘ └──────────────────────────────────────┘ │ │ │ │ Tabs: [Tool Types] [Configs] [Config Folders] │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` **Navigation Structure**: - Left sidebar: Hierarchical tree - Tool Types (expandable, shows instances count) - Config Folders (grouped by global/project) - Right panel: Context-aware editor based on selection - Tab bar: Switch between Tool Types / Configs / Config Folders views ### Config Editor ``` ┌─────────────────────────────────────────────────────────────────┐ │ Edit Config: OpenCode API Keys │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ Basic Settings │ Advanced Settings │ │ ────────────────────────────┼──────────────────────────────── │ │ Key: [OPENAI_API_KEY] │ Port Override: [_____] │ │ Value: [sk-... ] │ Start Command: [_____] │ │ Type: (•) Env ( ) File │ Working Dir: [/workspace] │ │ File Path: [__________] │ │ │ │ Environment Variables: │ │ │ ┌──────────────────────────┐ │ │ │ │ KEY │ VALUE │ │ │ │ │ OPENAI_KEY │ sk-... │ │ │ │ │ MODEL │ gpt-4 │ │ │ │ └──────────────────────────┘ │ │ │ │ │ │ Volume Mounts: │ │ │ ┌──────────────────────────┐ │ │ │ │ SOURCE │ TARGET │ │ │ │ │ dotfiles │ ~/.config │ │ │ │ │ vscode-set │ ~/.vscode │ │ │ │ └──────────────────────────┘ │ │ │ │ │ [Delete] [Cancel] [Save] │ │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` ### Config Folder Manager ``` ┌─────────────────────────────────────────────────────────────────┐ │ Config Folder: my-dotfiles │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ Name: [my-dotfiles] │ │ Description: [My personal dotfiles] │ │ Default Mount Path: [/home/user] │ │ │ │ Files: │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ Path │ Size │ Actions │ │ │ │ .zshrc │ 2.1KB │ [Edit] [Delete] │ │ │ │ .gitconfig │ 412B │ [Edit] [Delete] │ │ │ │ .config/starship.toml │ 1.8KB │ [Edit] [Delete] │ │ │ └──────────────────────────────────────────────────────┘ │ │ │ │ [+ Add File] │ │ │ │ Project Overrides: │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ Project │ Mount Path │ Files Override │ │ │ │ Project Alpha │ /home/dev │ [3 files] │ │ │ │ Project Beta │ /workspace │ [1 file] │ │ │ └──────────────────────────────────────────────────────┘ │ │ │ │ [Add Override] [Delete Folder] [Save] │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` ## Decisions 1. **Dockerfile vs Compose**: Support both. `definition_type` field determines which path to use. Compose is the default for backward compatibility. 2. **Config Folder Storage**: Store files as JSONB keyed by relative path. This avoids file system complexity and works well for text-based config files. Limit: 10MB per folder. 3. **Readiness Probe Execution**: Use `docker exec` to run commands inside the container. This is the most flexible approach (works for HTTP checks, binary checks, file checks). 4. **Volume Resolution Order**: Config-level volumes override config-folder volumes, which override tool-type defaults. Last-write-wins for conflicts. 5. **UI Organization**: Single page with three tabs (Tool Types, Configs, Config Folders) and a left sidebar for navigation. This consolidates the current `/tool-configs` and `/tool-types` pages. 6. **Project Overrides**: ConfigFolders support per-project overrides for mount_path and files. This allows project-specific customizations while keeping the base collection reusable. ## Risks / Trade-offs - **Dockerfile build times**: Building images on-demand is slow. Mitigation: Document that users should use pre-built images in compose for faster startup; dockerfile is for custom tools. - **Config folder size limits**: JSONB has practical limits. Mitigation: 10MB limit per folder, enforced in API. - **Readiness probe complexity**: Commands might hang or fail in unexpected ways. Mitigation: Strict timeout, clear error messages, probe logs stored on instance. - **Migration complexity**: Existing tool types need `definition_type` set to "compose". Mitigation: Database default, seed function update. - **UI complexity**: Three tabs with different editors could feel overwhelming. Mitigation: Progressive disclosure (hide advanced fields, collapsible sections). ## API Endpoints ### Tool Types - `GET /tool-types` - List all (existing) - `POST /tool-types` - Create with new fields - `PUT /tool-types/{id}` - Update with new fields - `GET /tool-types/{id}/validate` - Validate compose/dockerfile syntax ### Tool Configs - `GET /tool-configs` - List with new fields - `POST /tool-configs` - Create with new fields - `PUT /tool-configs/{id}` - Update with new fields - `GET /tool-configs/defaults/{tool_type_id}` - Get suggested defaults ### Config Folders (NEW) - `GET /config-folders` - List user's folders - `POST /config-folders` - Create folder - `PUT /config-folders/{id}` - Update folder (files, mount_path) - `DELETE /config-folders/{id}` - Delete folder - `POST /config-folders/{id}/overrides` - Add project override - `PUT /config-folders/{id}/overrides/{project_id}` - Update override - `DELETE /config-folders/{id}/overrides/{project_id}` - Remove override ## Database Schema ### Migration: tool_types ```sql ALTER TABLE tool_types ADD COLUMN definition_type VARCHAR(20) NOT NULL DEFAULT 'compose', ADD COLUMN dockerfile_template TEXT, ADD COLUMN build_context JSONB DEFAULT '{}', ADD COLUMN readiness_probe JSONB; -- Ensure consistency ALTER TABLE tool_types ADD CONSTRAINT chk_definition_type CHECK (definition_type IN ('compose', 'dockerfile')); ``` ### Migration: tool_configs ```sql ALTER TABLE tool_configs ADD COLUMN port_override INTEGER, ADD COLUMN start_command TEXT, ADD COLUMN working_directory TEXT, ADD COLUMN environment_variables JSONB DEFAULT '{}', ADD COLUMN volumes JSONB DEFAULT '[]'; ALTER TABLE tool_configs ADD CONSTRAINT chk_port_range CHECK (port_override IS NULL OR (port_override >= 1 AND port_override <= 65535)); ``` ### New Table: config_folders ```sql CREATE TABLE config_folders ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, description TEXT, mount_path VARCHAR(1024) NOT NULL, files JSONB NOT NULL DEFAULT '{}', project_overrides JSONB DEFAULT '{}', is_active BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE(user_id, name) ); CREATE INDEX idx_config_folders_user ON config_folders(user_id); ```