/** * API Client for the Autonomous Coding UI */ import type { ProjectSummary, ProjectDetail, ProjectPrompts, FeatureListResponse, Feature, FeatureCreate, AgentStatusResponse, AgentActionResponse, SetupStatus, DirectoryListResponse, PathValidationResponse, AssistantConversation, AssistantConversationDetail, } from './types' const API_BASE = '/api' async function fetchJSON(url: string, options?: RequestInit): Promise { const response = await fetch(`${API_BASE}${url}`, { ...options, headers: { 'Content-Type': 'application/json', ...options?.headers, }, }) if (!response.ok) { const error = await response.json().catch(() => ({ detail: 'Unknown error' })) throw new Error(error.detail || `HTTP ${response.status}`) } return response.json() } // ============================================================================ // Projects API // ============================================================================ export async function listProjects(): Promise { return fetchJSON('/projects') } export async function createProject( name: string, path: string, specMethod: 'claude' | 'manual' = 'manual' ): Promise { return fetchJSON('/projects', { method: 'POST', body: JSON.stringify({ name, path, spec_method: specMethod }), }) } export async function getProject(name: string): Promise { return fetchJSON(`/projects/${encodeURIComponent(name)}`) } export async function deleteProject(name: string): Promise { await fetchJSON(`/projects/${encodeURIComponent(name)}`, { method: 'DELETE', }) } export async function getProjectPrompts(name: string): Promise { return fetchJSON(`/projects/${encodeURIComponent(name)}/prompts`) } export async function updateProjectPrompts( name: string, prompts: Partial ): Promise { await fetchJSON(`/projects/${encodeURIComponent(name)}/prompts`, { method: 'PUT', body: JSON.stringify(prompts), }) } // ============================================================================ // Features API // ============================================================================ export async function listFeatures(projectName: string): Promise { return fetchJSON(`/projects/${encodeURIComponent(projectName)}/features`) } export async function createFeature(projectName: string, feature: FeatureCreate): Promise { return fetchJSON(`/projects/${encodeURIComponent(projectName)}/features`, { method: 'POST', body: JSON.stringify(feature), }) } export async function getFeature(projectName: string, featureId: number): Promise { return fetchJSON(`/projects/${encodeURIComponent(projectName)}/features/${featureId}`) } export async function deleteFeature(projectName: string, featureId: number): Promise { await fetchJSON(`/projects/${encodeURIComponent(projectName)}/features/${featureId}`, { method: 'DELETE', }) } export async function skipFeature(projectName: string, featureId: number): Promise { await fetchJSON(`/projects/${encodeURIComponent(projectName)}/features/${featureId}/skip`, { method: 'PATCH', }) } // ============================================================================ // Agent API // ============================================================================ export async function getAgentStatus(projectName: string): Promise { return fetchJSON(`/projects/${encodeURIComponent(projectName)}/agent/status`) } export async function startAgent( projectName: string, yoloMode: boolean = false ): Promise { return fetchJSON(`/projects/${encodeURIComponent(projectName)}/agent/start`, { method: 'POST', body: JSON.stringify({ yolo_mode: yoloMode }), }) } export async function stopAgent(projectName: string): Promise { return fetchJSON(`/projects/${encodeURIComponent(projectName)}/agent/stop`, { method: 'POST', }) } export async function pauseAgent(projectName: string): Promise { return fetchJSON(`/projects/${encodeURIComponent(projectName)}/agent/pause`, { method: 'POST', }) } export async function resumeAgent(projectName: string): Promise { return fetchJSON(`/projects/${encodeURIComponent(projectName)}/agent/resume`, { method: 'POST', }) } // ============================================================================ // Spec Creation API // ============================================================================ export interface SpecFileStatus { exists: boolean status: 'complete' | 'in_progress' | 'not_started' | 'error' | 'unknown' feature_count: number | null timestamp: string | null files_written: string[] } export async function getSpecStatus(projectName: string): Promise { return fetchJSON(`/spec/status/${encodeURIComponent(projectName)}`) } // ============================================================================ // Setup API // ============================================================================ export async function getSetupStatus(): Promise { return fetchJSON('/setup/status') } export async function healthCheck(): Promise<{ status: string }> { return fetchJSON('/health') } // ============================================================================ // Filesystem API // ============================================================================ export async function listDirectory(path?: string): Promise { const params = path ? `?path=${encodeURIComponent(path)}` : '' return fetchJSON(`/filesystem/list${params}`) } export async function createDirectory(fullPath: string): Promise<{ success: boolean; path: string }> { // Backend expects { parent_path, name }, not { path } // Split the full path into parent directory and folder name // Remove trailing slash if present const normalizedPath = fullPath.endsWith('/') ? fullPath.slice(0, -1) : fullPath // Find the last path separator const lastSlash = normalizedPath.lastIndexOf('/') let parentPath: string let name: string // Handle Windows drive root (e.g., "C:/newfolder") if (lastSlash === 2 && /^[A-Za-z]:/.test(normalizedPath)) { // Path like "C:/newfolder" - parent is "C:/" parentPath = normalizedPath.substring(0, 3) // "C:/" name = normalizedPath.substring(3) } else if (lastSlash > 0) { parentPath = normalizedPath.substring(0, lastSlash) name = normalizedPath.substring(lastSlash + 1) } else if (lastSlash === 0) { // Unix root path like "/newfolder" parentPath = '/' name = normalizedPath.substring(1) } else { // No slash - invalid path throw new Error('Invalid path: must be an absolute path') } if (!name) { throw new Error('Invalid path: directory name is empty') } return fetchJSON('/filesystem/create-directory', { method: 'POST', body: JSON.stringify({ parent_path: parentPath, name }), }) } export async function validatePath(path: string): Promise { return fetchJSON('/filesystem/validate', { method: 'POST', body: JSON.stringify({ path }), }) } // ============================================================================ // Assistant Chat API // ============================================================================ export async function listAssistantConversations( projectName: string ): Promise { return fetchJSON(`/assistant/conversations/${encodeURIComponent(projectName)}`) } export async function getAssistantConversation( projectName: string, conversationId: number ): Promise { return fetchJSON( `/assistant/conversations/${encodeURIComponent(projectName)}/${conversationId}` ) } export async function createAssistantConversation( projectName: string ): Promise { return fetchJSON(`/assistant/conversations/${encodeURIComponent(projectName)}`, { method: 'POST', }) } export async function deleteAssistantConversation( projectName: string, conversationId: number ): Promise { await fetchJSON( `/assistant/conversations/${encodeURIComponent(projectName)}/${conversationId}`, { method: 'DELETE' } ) }