feat: implement tool workshop - comprehensive tool system enhancement
- Add Docker Compose and Dockerfile support for tool definitions - Implement readiness probes with configurable command, timeout, interval - Create ConfigFolder model for reusable file collections with project overrides - Add rich tool config fields: port_override, start_command, working_directory, env vars, volumes - Build unified Tool Workshop UI at /tool-workshop replacing /tool-configs and /tool-types - Update instance creation to support dockerfile builds, config folder mounting, readiness probes - Add 3 database migrations for tool_types, tool_configs, and new config_folders table - Create docker_build.py and readiness_probe.py services - Add config_folders API with CRUD and project override endpoints Quality gates: frontend build passes, Python syntax valid, all phases complete Addresses tool-workshop OpenSpec change
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
# Capability: Config Folders
|
||||
|
||||
## Overview
|
||||
|
||||
Config Folders are reusable collections of configuration files that can be mounted into tool instances as volumes. They enable users to maintain their preferred settings (dotfiles, IDE configs, etc.) and apply them across all their tool instances.
|
||||
|
||||
## Functional Requirements
|
||||
|
||||
### FR-1: Folder Creation
|
||||
- Users can create named config folders
|
||||
- Each folder has: name, description, default mount path, collection of files
|
||||
- Folder names must be unique per user
|
||||
- Files are stored with relative paths (e.g., `.zshrc`, `.config/nvim/init.vim`)
|
||||
|
||||
### FR-2: File Management
|
||||
- Users can add, edit, and delete files within a folder
|
||||
- File paths are relative to the mount path
|
||||
- File content is stored as text (UTF-8)
|
||||
- Maximum total folder size: 10MB
|
||||
- File paths are sanitized to prevent directory traversal attacks
|
||||
|
||||
### FR-3: Activation
|
||||
- Folders can be toggled active/inactive
|
||||
- Only active folders are mounted into new instances
|
||||
- Activation state is persisted
|
||||
- Changing activation does not affect running instances
|
||||
|
||||
### FR-4: Project Overrides
|
||||
- Users can define per-project overrides for any folder
|
||||
- Overrides can modify: mount path, add/remove/replace files
|
||||
- When an instance is created for a project, overrides are applied
|
||||
- Global settings serve as defaults; overrides are merged
|
||||
- Deleting an override reverts to global settings
|
||||
|
||||
### FR-5: Instance Mounting
|
||||
- When creating an instance, active folders are resolved
|
||||
- For each folder: global files + project overrides (if any)
|
||||
- Files are written to `instance_dir/volumes/<folder_name>/`
|
||||
- Compose file includes volume mounts from these directories
|
||||
- Mount target is the folder's mount path (or override)
|
||||
|
||||
## Data Model
|
||||
|
||||
```python
|
||||
class ConfigFolder:
|
||||
id: UUID
|
||||
user_id: UUID
|
||||
name: str # Unique per user
|
||||
description: str | None
|
||||
mount_path: str # e.g., "/home/user"
|
||||
files: dict[str, str] # {"relative/path": "content", ...}
|
||||
project_overrides: dict # {"project_id": {"mount_path": "...", "files": {...}}}
|
||||
is_active: bool
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
- `GET /config-folders` - List user's folders
|
||||
- `POST /config-folders` - Create folder
|
||||
- `PUT /config-folders/{id}` - Update folder
|
||||
- `DELETE /config-folders/{id}` - Delete folder
|
||||
- `POST /config-folders/{id}/overrides` - Add override
|
||||
- `PUT /config-folders/{id}/overrides/{project_id}` - Update override
|
||||
- `DELETE /config-folders/{id}/overrides/{project_id}` - Remove override
|
||||
|
||||
## Validation Rules
|
||||
|
||||
1. **Name uniqueness**: `(user_id, name)` must be unique
|
||||
2. **Path sanitization**: File paths cannot contain `..` or start with `/`
|
||||
3. **Size limit**: Total folder size (sum of all file contents) ≤ 10MB
|
||||
4. **Mount path**: Must be absolute path (starts with `/`)
|
||||
5. **Project existence**: Overrides can only reference existing projects
|
||||
|
||||
## Example Usage
|
||||
|
||||
### Global Config Folder
|
||||
```json
|
||||
{
|
||||
"name": "my-dotfiles",
|
||||
"description": "Personal shell and git configuration",
|
||||
"mount_path": "/home/user",
|
||||
"files": {
|
||||
".zshrc": "export ZSH=\"$HOME/.oh-my-zsh\"\n...",
|
||||
".gitconfig": "[user]\nname = John Doe\n...",
|
||||
".config/starship.toml": "[character]\n..."
|
||||
},
|
||||
"is_active": true
|
||||
}
|
||||
```
|
||||
|
||||
### Project Override
|
||||
```json
|
||||
{
|
||||
"project_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"mount_path": "/workspace",
|
||||
"files": {
|
||||
".gitconfig": "[user]\nname = Work Account\n..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] User can create a config folder with multiple files
|
||||
- [ ] Files are correctly mounted into new instances
|
||||
- [ ] Project overrides apply correctly
|
||||
- [ ] 10MB size limit is enforced
|
||||
- [ ] Path traversal attacks are prevented
|
||||
- [ ] Only active folders are mounted
|
||||
- [ ] Changing folder contents updates future instances
|
||||
- [ ] UI shows folder size and file count
|
||||
@@ -0,0 +1,163 @@
|
||||
# Capability: Readiness Probes
|
||||
|
||||
## Overview
|
||||
|
||||
Readiness probes ensure tool instances are fully initialized before being marked as "running". They execute a configurable command inside the container and wait for it to succeed, with configurable timeout and retry interval.
|
||||
|
||||
## Functional Requirements
|
||||
|
||||
### FR-1: Probe Definition
|
||||
- Tool types can define an optional readiness probe
|
||||
- Probe configuration: command, timeout, interval, retries
|
||||
- If no probe is defined, instance is marked running immediately after container start
|
||||
- Probe can be any shell command that returns exit code 0 for success
|
||||
|
||||
### FR-2: Probe Execution
|
||||
- Probe runs inside the container via `docker exec`
|
||||
- Probe starts after container is in "running" state
|
||||
- Probe executes periodically (interval) until success or timeout
|
||||
- Each execution has a separate timeout (not the total timeout)
|
||||
- Probe output is captured and stored
|
||||
|
||||
### FR-3: Status Management
|
||||
- While probing: instance status is "starting"
|
||||
- On success: instance status changes to "running"
|
||||
- On timeout: instance status changes to "failed"
|
||||
- Failed instances include probe logs in error details
|
||||
- Users can view probe execution history
|
||||
|
||||
### FR-4: Probe Types
|
||||
Support common probe patterns:
|
||||
- **HTTP probe**: `curl -f http://localhost:8080/health`
|
||||
- **Command probe**: `opencode --version`
|
||||
- **File probe**: `[ -f /app/ready ]`
|
||||
- **Port probe**: `nc -z localhost 8080`
|
||||
|
||||
## Configuration
|
||||
|
||||
```python
|
||||
class ReadinessProbe(BaseModel):
|
||||
command: str # Command to execute
|
||||
timeout: int = 30 # Total timeout in seconds
|
||||
interval: int = 2 # Seconds between checks
|
||||
|
||||
@property
|
||||
def max_retries(self) -> int:
|
||||
return self.timeout // self.interval
|
||||
```
|
||||
|
||||
## Execution Flow
|
||||
|
||||
```
|
||||
Container Start
|
||||
│
|
||||
▼
|
||||
Container Running?
|
||||
│
|
||||
├── No ──▶ Wait 1s ──▶ Retry (max 30s)
|
||||
│
|
||||
▼ Yes
|
||||
Execute Probe Command
|
||||
│
|
||||
├── Exit 0 ──▶ Status: "running" ✓
|
||||
│
|
||||
├── Exit !=0 ──▶ Wait interval ──▶ Retry
|
||||
│ │
|
||||
│ └── Max retries reached?
|
||||
│ ├── No ──▶ Execute again
|
||||
│ │
|
||||
│ ▼ Yes
|
||||
│ Status: "failed" ✗
|
||||
│ Store logs
|
||||
│
|
||||
└── Timeout ──▶ Status: "failed" ✗
|
||||
Store logs
|
||||
```
|
||||
|
||||
## Probe Examples
|
||||
|
||||
### Web Tool (VS Code Server)
|
||||
```json
|
||||
{
|
||||
"command": "curl -sf http://localhost:8080/health || curl -sf http://localhost:8080",
|
||||
"timeout": 60,
|
||||
"interval": 3
|
||||
}
|
||||
```
|
||||
|
||||
### Terminal Tool (OpenCode)
|
||||
```json
|
||||
{
|
||||
"command": "which opencode && opencode --version",
|
||||
"timeout": 30,
|
||||
"interval": 2
|
||||
}
|
||||
```
|
||||
|
||||
### Database Tool
|
||||
```json
|
||||
{
|
||||
"command": "pg_isready -U postgres",
|
||||
"timeout": 30,
|
||||
"interval": 2
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Probe Command Not Found
|
||||
- Exit code: 127
|
||||
- Behavior: Retry (command might not be in PATH yet)
|
||||
- Log: "Command not found, retrying..."
|
||||
|
||||
### Probe Times Out
|
||||
- Mark instance as "failed"
|
||||
- Store last probe output
|
||||
- Include timeout details in error message
|
||||
- Allow user to view full probe logs
|
||||
|
||||
### Container Exits During Probe
|
||||
- Stop probing immediately
|
||||
- Mark instance as "failed"
|
||||
- Include container exit code and logs
|
||||
|
||||
## API Integration
|
||||
|
||||
### Tool Type Response
|
||||
```json
|
||||
{
|
||||
"id": "...",
|
||||
"name": "code-server",
|
||||
"readiness_probe": {
|
||||
"command": "curl -sf http://localhost:8080",
|
||||
"timeout": 60,
|
||||
"interval": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Instance Response (Failed Probe)
|
||||
```json
|
||||
{
|
||||
"id": "...",
|
||||
"status": "failed",
|
||||
"error": "Readiness probe failed after 60s",
|
||||
"probe_logs": [
|
||||
"Attempt 1/20: Connection refused",
|
||||
"Attempt 2/20: Connection refused",
|
||||
"...",
|
||||
"Attempt 20/20: Timeout"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] Probe executes inside container and waits for success
|
||||
- [ ] Successful probe marks instance as "running"
|
||||
- [ ] Failed probe (timeout) marks instance as "failed"
|
||||
- [ ] Probe logs are stored and retrievable
|
||||
- [ ] Probe respects timeout and interval settings
|
||||
- [ ] No probe defined = immediate "running" status
|
||||
- [ ] Container exit during probe is handled gracefully
|
||||
- [ ] Common probe patterns work (HTTP, command, file, port)
|
||||
@@ -0,0 +1,96 @@
|
||||
# Capability: Tool Workshop
|
||||
|
||||
## Overview
|
||||
|
||||
The Tool Workshop is the unified interface for defining, configuring, and managing development tools. It consolidates tool type management, tool configuration, and config folder management into a single powerful interface.
|
||||
|
||||
## Functional Requirements
|
||||
|
||||
### FR-1: Tool Type Definition
|
||||
- Users can create new tool types with either Docker Compose or Dockerfile
|
||||
- Tool types specify: name, display name, description, category, interfaces, port, definition type, template
|
||||
- Built-in tool types can be viewed but not edited
|
||||
- Tool types can be deleted (with cascade deletion of associated configs)
|
||||
|
||||
### FR-2: Tool Configuration
|
||||
- Users can create tool configurations per tool type
|
||||
- Configs can be global (all projects) or project-scoped
|
||||
- Configs support: key-value pairs (env/file), port override, start command, working directory, environment variables, volumes
|
||||
- Configs are mounted into containers when instances are created
|
||||
|
||||
### FR-3: Config Folder Management
|
||||
- Users can create named collections of configuration files
|
||||
- Each folder has a default mount path in containers
|
||||
- Folders can be activated/deactivated
|
||||
- Folders support per-project overrides
|
||||
- Active folders are automatically mounted into new instances
|
||||
|
||||
### FR-4: Readiness Probes
|
||||
- Tool types can define a readiness probe command
|
||||
- Instance creation waits for the probe to succeed
|
||||
- Probes have configurable timeout and check interval
|
||||
- Failed probes mark instances as "failed" with logs
|
||||
|
||||
### FR-5: Instance Integration
|
||||
- Instance creation uses tool type definition (compose or dockerfile)
|
||||
- Instance creation applies tool configs (env vars, files, volumes)
|
||||
- Instance creation mounts active config folders
|
||||
- Instance creation executes readiness probe
|
||||
- Instance status reflects probe result
|
||||
|
||||
## Non-Functional Requirements
|
||||
|
||||
### NFR-1: Performance
|
||||
- Tool Workshop page loads in < 2 seconds
|
||||
- Config folder operations complete in < 500ms
|
||||
- Instance creation with dockerfile build completes in < 5 minutes
|
||||
|
||||
### NFR-2: Usability
|
||||
- UI is intuitive for both technical and non-technical users
|
||||
- Clear validation messages for all fields
|
||||
- Progressive disclosure of advanced options
|
||||
- Responsive design for mobile devices
|
||||
|
||||
### NFR-3: Security
|
||||
- Users can only access their own tool types, configs, and folders
|
||||
- File paths in config folders are sanitized (no path traversal)
|
||||
- Dockerfile builds run in isolated context
|
||||
- Config values are never logged or exposed
|
||||
|
||||
## State Diagram
|
||||
|
||||
```
|
||||
┌─────────────┐
|
||||
│ DRAFT │
|
||||
└──────┬──────┘
|
||||
│ Create
|
||||
▼
|
||||
┌─────────────┐ Edit ┌─────────────┐
|
||||
│ ACTIVE │◀────────────▶│ UPDATED │
|
||||
└──────┬──────┘ └─────────────┘
|
||||
│
|
||||
│ Delete
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ DELETED │
|
||||
└─────────────┘
|
||||
```
|
||||
|
||||
## API Specification
|
||||
|
||||
See `design.md` for complete endpoint list.
|
||||
|
||||
## UI Specification
|
||||
|
||||
See `design.md` for complete UI mockups.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] User can create a tool type with dockerfile and start an instance
|
||||
- [ ] User can create a tool type with compose and start an instance
|
||||
- [ ] User can create config folders and mount them into instances
|
||||
- [ ] User can set project overrides on config folders
|
||||
- [ ] Readiness probes wait for tools to be ready before marking running
|
||||
- [ ] Failed readiness probes show clear error messages
|
||||
- [ ] All new fields are persisted and retrieved correctly
|
||||
- [ ] UI is responsive and intuitive
|
||||
Reference in New Issue
Block a user