mirror of
https://github.com/leonvanzyl/autocoder.git
synced 2026-02-02 07:23:35 +00:00
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>
This commit is contained in:
@@ -3,20 +3,22 @@
|
||||
*
|
||||
* Multi-step modal for creating new projects:
|
||||
* 1. Enter project name
|
||||
* 2. Choose spec method (Claude or manual)
|
||||
* 3a. If Claude: Show SpecCreationChat
|
||||
* 3b. If manual: Create project and close
|
||||
* 2. Select project folder
|
||||
* 3. Choose spec method (Claude or manual)
|
||||
* 4a. If Claude: Show SpecCreationChat
|
||||
* 4b. If manual: Create project and close
|
||||
*/
|
||||
|
||||
import { useState } from 'react'
|
||||
import { X, Bot, FileEdit, ArrowRight, ArrowLeft, Loader2, CheckCircle2 } from 'lucide-react'
|
||||
import { X, Bot, FileEdit, ArrowRight, ArrowLeft, Loader2, CheckCircle2, Folder } from 'lucide-react'
|
||||
import { useCreateProject } from '../hooks/useProjects'
|
||||
import { SpecCreationChat } from './SpecCreationChat'
|
||||
import { FolderBrowser } from './FolderBrowser'
|
||||
import { startAgent } from '../lib/api'
|
||||
|
||||
type InitializerStatus = 'idle' | 'starting' | 'error'
|
||||
|
||||
type Step = 'name' | 'method' | 'chat' | 'complete'
|
||||
type Step = 'name' | 'folder' | 'method' | 'chat' | 'complete'
|
||||
type SpecMethod = 'claude' | 'manual'
|
||||
|
||||
interface NewProjectModalProps {
|
||||
@@ -32,6 +34,7 @@ export function NewProjectModal({
|
||||
}: NewProjectModalProps) {
|
||||
const [step, setStep] = useState<Step>('name')
|
||||
const [projectName, setProjectName] = useState('')
|
||||
const [projectPath, setProjectPath] = useState<string | null>(null)
|
||||
const [_specMethod, setSpecMethod] = useState<SpecMethod | null>(null)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [initializerStatus, setInitializerStatus] = useState<InitializerStatus>('idle')
|
||||
@@ -59,17 +62,35 @@ export function NewProjectModal({
|
||||
}
|
||||
|
||||
setError(null)
|
||||
setStep('folder')
|
||||
}
|
||||
|
||||
const handleFolderSelect = (path: string) => {
|
||||
// Append project name to the selected path
|
||||
const fullPath = path.endsWith('/') ? `${path}${projectName.trim()}` : `${path}/${projectName.trim()}`
|
||||
setProjectPath(fullPath)
|
||||
setStep('method')
|
||||
}
|
||||
|
||||
const handleFolderCancel = () => {
|
||||
setStep('name')
|
||||
}
|
||||
|
||||
const handleMethodSelect = async (method: SpecMethod) => {
|
||||
setSpecMethod(method)
|
||||
|
||||
if (!projectPath) {
|
||||
setError('Please select a project folder first')
|
||||
setStep('folder')
|
||||
return
|
||||
}
|
||||
|
||||
if (method === 'manual') {
|
||||
// Create project immediately with manual method
|
||||
try {
|
||||
const project = await createProject.mutateAsync({
|
||||
name: projectName.trim(),
|
||||
path: projectPath,
|
||||
specMethod: 'manual',
|
||||
})
|
||||
setStep('complete')
|
||||
@@ -85,6 +106,7 @@ export function NewProjectModal({
|
||||
try {
|
||||
await createProject.mutateAsync({
|
||||
name: projectName.trim(),
|
||||
path: projectPath,
|
||||
specMethod: 'claude',
|
||||
})
|
||||
setStep('chat')
|
||||
@@ -126,6 +148,7 @@ export function NewProjectModal({
|
||||
const handleClose = () => {
|
||||
setStep('name')
|
||||
setProjectName('')
|
||||
setProjectPath(null)
|
||||
setSpecMethod(null)
|
||||
setError(null)
|
||||
setInitializerStatus('idle')
|
||||
@@ -135,8 +158,11 @@ export function NewProjectModal({
|
||||
|
||||
const handleBack = () => {
|
||||
if (step === 'method') {
|
||||
setStep('name')
|
||||
setStep('folder')
|
||||
setSpecMethod(null)
|
||||
} else if (step === 'folder') {
|
||||
setStep('name')
|
||||
setProjectPath(null)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,6 +182,47 @@ export function NewProjectModal({
|
||||
)
|
||||
}
|
||||
|
||||
// Folder step uses larger modal
|
||||
if (step === 'folder') {
|
||||
return (
|
||||
<div className="neo-modal-backdrop" onClick={handleClose}>
|
||||
<div
|
||||
className="neo-modal w-full max-w-3xl max-h-[85vh] flex flex-col"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-4 border-b-3 border-[var(--color-neo-border)]">
|
||||
<div className="flex items-center gap-3">
|
||||
<Folder size={24} className="text-[var(--color-neo-progress)]" />
|
||||
<div>
|
||||
<h2 className="font-display font-bold text-xl text-[#1a1a1a]">
|
||||
Select Project Location
|
||||
</h2>
|
||||
<p className="text-sm text-[#4a4a4a]">
|
||||
A folder named <span className="font-bold font-mono">{projectName}</span> will be created inside the selected directory
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="neo-btn neo-btn-ghost p-2"
|
||||
>
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Folder Browser */}
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<FolderBrowser
|
||||
onSelect={handleFolderSelect}
|
||||
onCancel={handleFolderCancel}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="neo-modal-backdrop" onClick={handleClose}>
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user