Files
autocoder/server/schemas.py
Auto 6c99e40408 feat: Add arbitrary directory project storage with registry system
This major update replaces the fixed `generations/` directory with support
for storing projects in any directory on the filesystem. Projects are now
tracked via a cross-platform registry system.

## New Features

### Project Registry (`registry.py`)
- Cross-platform registry storing project name-to-path mappings
- Platform-specific config locations:
  - Windows: %APPDATA%\autonomous-coder\projects.json
  - macOS: ~/Library/Application Support/autonomous-coder/projects.json
  - Linux: ~/.config/autonomous-coder/projects.json
- POSIX path format for cross-platform compatibility
- File locking for concurrent access safety (fcntl/msvcrt)
- Atomic writes via temp file + rename to prevent corruption
- Fixed Windows file locking issue with tempfile.mkstemp()

### Filesystem Browser API (`server/routers/filesystem.py`)
- REST endpoints for browsing directories server-side
- Cross-platform support with blocked system paths:
  - Windows: C:\Windows, Program Files, ProgramData, etc.
  - macOS: /System, /Library, /private, etc.
  - Linux: /etc, /var, /usr, /bin, etc.
- Universal blocked paths: .ssh, .aws, .gnupg, .docker, etc.
- Hidden file detection (Unix dot-prefix + Windows attributes)
- UNC path blocking for security
- Windows drive enumeration via ctypes
- Directory creation with validation
- Added `has_children` field to DirectoryEntry schema

### UI Folder Browser (`ui/src/components/FolderBrowser.tsx`)
- React component for selecting project directories
- Breadcrumb navigation with clickable segments
- Windows drive selector
- New folder creation inline
- Fixed text visibility with explicit color values

## Updated Components

### Server Routers
- `projects.py`: Uses registry instead of fixed generations/ directory
- `agent.py`: Uses registry for project path lookups
- `features.py`: Uses registry for database path resolution
- `spec_creation.py`: Uses registry for WebSocket project resolution

### Process Manager (`server/services/process_manager.py`)
- Fixed sandbox issue: subprocess now uses project_dir as cwd
- This allows the Claude SDK sandbox to access external project directories

### Schemas (`server/schemas.py`)
- Added `has_children` to DirectoryEntry
- Added `in_progress` to ProjectStats
- Added path field to ProjectSummary and ProjectDetail

### UI Components
- `NewProjectModal.tsx`: Multi-step wizard with folder selection
- Added clarifying text about subfolder creation
- Fixed text color visibility issues

### API Client (`ui/src/lib/api.ts`)
- Added filesystem API functions (listDirectory, createDirectory)
- Fixed Windows path splitting for directory creation

### Documentation
- Updated CLAUDE.md with registry system details
- Updated command examples for absolute paths

## Security Improvements
- Blocked `.` and `..` in directory names to prevent traversal
- Added path blocking check in project creation
- UNC path blocking throughout filesystem API

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 10:20:07 +02:00

203 lines
5.2 KiB
Python

"""
Pydantic Schemas
================
Request/Response models for the API endpoints.
"""
from datetime import datetime
from typing import Literal
from pydantic import BaseModel, Field
# ============================================================================
# Project Schemas
# ============================================================================
class ProjectCreate(BaseModel):
"""Request schema for creating a new project."""
name: str = Field(..., min_length=1, max_length=50, pattern=r'^[a-zA-Z0-9_-]+$')
path: str = Field(..., min_length=1, description="Absolute path to project directory")
spec_method: Literal["claude", "manual"] = "claude"
class ProjectStats(BaseModel):
"""Project statistics."""
passing: int = 0
in_progress: int = 0
total: int = 0
percentage: float = 0.0
class ProjectSummary(BaseModel):
"""Summary of a project for list view."""
name: str
path: str
has_spec: bool
stats: ProjectStats
class ProjectDetail(BaseModel):
"""Detailed project information."""
name: str
path: str
has_spec: bool
stats: ProjectStats
prompts_dir: str
class ProjectPrompts(BaseModel):
"""Project prompt files content."""
app_spec: str = ""
initializer_prompt: str = ""
coding_prompt: str = ""
class ProjectPromptsUpdate(BaseModel):
"""Request schema for updating project prompts."""
app_spec: str | None = None
initializer_prompt: str | None = None
coding_prompt: str | None = None
# ============================================================================
# Feature Schemas
# ============================================================================
class FeatureBase(BaseModel):
"""Base feature attributes."""
category: str
name: str
description: str
steps: list[str]
class FeatureCreate(FeatureBase):
"""Request schema for creating a new feature."""
priority: int | None = None
class FeatureResponse(FeatureBase):
"""Response schema for a feature."""
id: int
priority: int
passes: bool
in_progress: bool
class Config:
from_attributes = True
class FeatureListResponse(BaseModel):
"""Response containing list of features organized by status."""
pending: list[FeatureResponse]
in_progress: list[FeatureResponse]
done: list[FeatureResponse]
# ============================================================================
# Agent Schemas
# ============================================================================
class AgentStatus(BaseModel):
"""Current agent status."""
status: Literal["stopped", "running", "paused", "crashed"]
pid: int | None = None
started_at: datetime | None = None
class AgentActionResponse(BaseModel):
"""Response for agent control actions."""
success: bool
status: str
message: str = ""
# ============================================================================
# Setup Schemas
# ============================================================================
class SetupStatus(BaseModel):
"""System setup status."""
claude_cli: bool
credentials: bool
node: bool
npm: bool
# ============================================================================
# WebSocket Message Schemas
# ============================================================================
class WSProgressMessage(BaseModel):
"""WebSocket message for progress updates."""
type: Literal["progress"] = "progress"
passing: int
total: int
percentage: float
class WSFeatureUpdateMessage(BaseModel):
"""WebSocket message for feature status updates."""
type: Literal["feature_update"] = "feature_update"
feature_id: int
passes: bool
class WSLogMessage(BaseModel):
"""WebSocket message for agent log output."""
type: Literal["log"] = "log"
line: str
timestamp: datetime
class WSAgentStatusMessage(BaseModel):
"""WebSocket message for agent status changes."""
type: Literal["agent_status"] = "agent_status"
status: str
# ============================================================================
# Filesystem Schemas
# ============================================================================
class DriveInfo(BaseModel):
"""Information about a drive (Windows only)."""
letter: str
label: str
available: bool = True
class DirectoryEntry(BaseModel):
"""An entry in a directory listing."""
name: str
path: str # POSIX format
is_directory: bool
is_hidden: bool = False
size: int | None = None # Bytes, for files
has_children: bool = False # True if directory has subdirectories
class DirectoryListResponse(BaseModel):
"""Response for directory listing."""
current_path: str # POSIX format
parent_path: str | None
entries: list[DirectoryEntry]
drives: list[DriveInfo] | None = None # Windows only
class PathValidationResponse(BaseModel):
"""Response for path validation."""
valid: bool
exists: bool
is_directory: bool
can_read: bool
can_write: bool
message: str = ""
class CreateDirectoryRequest(BaseModel):
"""Request to create a new directory."""
parent_path: str
name: str = Field(..., min_length=1, max_length=255)