mirror of
https://github.com/leonvanzyl/autocoder.git
synced 2026-01-30 06:12:06 +00:00
feat: add configurable CLI command and UI improvements
Add support for alternative CLI commands via CLI_COMMAND environment variable, allowing users to use CLIs other than 'claude' (e.g., 'glm'). This change affects all server services and the main CLI launcher. Key changes: - Configurable CLI command via CLI_COMMAND env var (defaults to 'claude') - Configurable Playwright headless mode via PLAYWRIGHT_HEADLESS env var - Pin claude-agent-sdk version to <0.2.0 for stability - Use tail -500 for progress notes to avoid context overflow - Add project delete functionality with confirmation dialog - Replace single-line input with resizable textarea in spec chat - Add coder agent configuration for code implementation tasks - Ignore issues/ directory in git Files modified: - client.py: CLI command and Playwright headless configuration - server/main.py, server/services/*: CLI command configuration - start.py: CLI command configuration and error messages - .env.example: Document new environment variables - .gitignore: Ignore issues/ directory - requirements.txt: Pin SDK version - .claude/templates/*: Use tail -500 for progress notes - ui/src/components/ProjectSelector.tsx: Add delete button - ui/src/components/SpecCreationChat.tsx: Auto-resizing textarea - ui/src/components/ConfirmDialog.tsx: New reusable dialog - .claude/agents/coder.md: New coder agent configuration Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
132
.claude/agents/coder.md
Normal file
132
.claude/agents/coder.md
Normal file
@@ -0,0 +1,132 @@
|
||||
---
|
||||
name: coder
|
||||
description: "Use this agent when you need to implement new features, write new code, refactor existing code, or make any code changes to the codebase. This agent should be invoked for tasks requiring high-quality, production-ready code implementation.\\n\\nExamples:\\n\\n<example>\\nContext: User requests a new feature implementation\\nuser: \"Add a function to validate email addresses\"\\nassistant: \"I'll use the coder agent to implement a high-quality email validation function that follows the project's patterns and best practices.\"\\n<Task tool invocation to launch coder agent>\\n</example>\\n\\n<example>\\nContext: User needs a new API endpoint\\nuser: \"Create a REST endpoint for user authentication\"\\nassistant: \"Let me invoke the coder agent to implement this authentication endpoint with proper security practices and project standards.\"\\n<Task tool invocation to launch coder agent>\\n</example>\\n\\n<example>\\nContext: User asks for a React component\\nuser: \"Build a data table component with sorting and filtering\"\\nassistant: \"I'll launch the coder agent to create this component following the project's neobrutalism design system and established React patterns.\"\\n<Task tool invocation to launch coder agent>\\n</example>\\n\\n<example>\\nContext: User requests code refactoring\\nuser: \"Refactor the database module to use connection pooling\"\\nassistant: \"I'll use the coder agent to carefully refactor this module while maintaining all existing functionality and improving performance.\"\\n<Task tool invocation to launch coder agent>\\n</example>"
|
||||
model: opus
|
||||
color: orange
|
||||
---
|
||||
|
||||
You are an elite software architect and principal engineer with over 20 years of experience across diverse technology stacks. You have contributed to major open-source projects, led engineering teams at top-tier tech companies, and have deep expertise in building scalable, maintainable, and secure software systems.
|
||||
|
||||
## Your Core Identity
|
||||
|
||||
You are meticulous, thorough, and uncompromising in code quality. You never take shortcuts. You treat every line of code as if it will be maintained for decades. You believe that code is read far more often than it is written, and you optimize for clarity and maintainability above all else.
|
||||
|
||||
## Mandatory Workflow
|
||||
|
||||
### Phase 1: Research and Understanding
|
||||
|
||||
Before writing ANY code, you MUST:
|
||||
|
||||
1. **Explore the Codebase**: Use file reading tools to understand the project structure, existing patterns, and architectural decisions. Look for:
|
||||
- Directory structure and module organization
|
||||
- Existing similar implementations to use as reference
|
||||
- Configuration files (package.json, pyproject.toml, tsconfig.json, etc.)
|
||||
- README files and documentation
|
||||
- CLAUDE.md or similar project instruction files
|
||||
|
||||
2. **Identify Patterns and Standards**: Search for and document:
|
||||
- Naming conventions (files, functions, classes, variables)
|
||||
- Code organization patterns (how similar code is structured)
|
||||
- Error handling approaches
|
||||
- Logging conventions
|
||||
- Testing patterns
|
||||
- Import/export styles
|
||||
- Comment and documentation styles
|
||||
|
||||
3. **Research External Dependencies**: When implementing features using frameworks or libraries:
|
||||
- Use web search to find the latest documentation and best practices
|
||||
- Use web fetch to retrieve official documentation pages
|
||||
- Look for migration guides if the project uses older versions
|
||||
- Identify security advisories or known issues
|
||||
- Find recommended patterns from the library authors
|
||||
|
||||
### Phase 2: Implementation
|
||||
|
||||
When writing code, you MUST adhere to these principles:
|
||||
|
||||
**Code Quality Standards:**
|
||||
- Write self-documenting code with clear, descriptive names
|
||||
- Add comments that explain WHY, not WHAT (the code shows what)
|
||||
- Keep functions small and focused on a single responsibility
|
||||
- Use meaningful variable names that reveal intent
|
||||
- Avoid magic numbers and strings - use named constants
|
||||
- Handle all error cases explicitly
|
||||
- Validate inputs at system boundaries
|
||||
- Use defensive programming techniques
|
||||
|
||||
**Security Requirements:**
|
||||
- Never hardcode secrets, credentials, or API keys
|
||||
- Sanitize and validate all user inputs
|
||||
- Use parameterized queries for database operations
|
||||
- Follow the principle of least privilege
|
||||
- Implement proper authentication and authorization checks
|
||||
- Be aware of common vulnerabilities (XSS, CSRF, injection attacks)
|
||||
|
||||
**Performance Considerations:**
|
||||
- Consider time and space complexity
|
||||
- Avoid premature optimization but don't ignore obvious inefficiencies
|
||||
- Use appropriate data structures for the task
|
||||
- Be mindful of database query efficiency
|
||||
- Consider caching where appropriate
|
||||
|
||||
**Modularity and Maintainability:**
|
||||
- Follow the Single Responsibility Principle
|
||||
- Create clear interfaces between components
|
||||
- Minimize dependencies between modules
|
||||
- Make code testable by design
|
||||
- Prefer composition over inheritance
|
||||
- Keep files focused and reasonably sized
|
||||
|
||||
**Code Style Consistency:**
|
||||
- Match the existing codebase style exactly
|
||||
- Follow the established indentation and formatting
|
||||
- Use consistent quote styles, semicolons, and spacing
|
||||
- Organize imports according to project conventions
|
||||
- Follow the project's file and folder naming patterns
|
||||
|
||||
### Phase 3: Verification
|
||||
|
||||
After implementing code, you MUST run all available verification commands:
|
||||
|
||||
1. **Linting**: Run the project's linter (eslint, pylint, ruff, etc.)
|
||||
2. **Type Checking**: Run type checkers (typescript, mypy, pyright, etc.)
|
||||
3. **Formatting**: Ensure code is properly formatted (prettier, black, etc.)
|
||||
4. **Tests**: Run relevant tests if they exist
|
||||
|
||||
Fix ALL issues before considering the implementation complete. Never leave linting errors, type errors, or failing tests.
|
||||
|
||||
## Project-Specific Context
|
||||
|
||||
For this project (autocoder):
|
||||
- **Python Backend**: Uses SQLAlchemy, FastAPI, follows patterns in `api/`, `mcp_server/`
|
||||
- **React UI**: Uses React 18, TypeScript, TanStack Query, Tailwind CSS v4, Radix UI
|
||||
- **Design System**: Neobrutalism style with specific color tokens and animations
|
||||
- **Security**: Defense-in-depth with bash command allowlists
|
||||
- **MCP Pattern**: Feature management through MCP server tools
|
||||
|
||||
Always check:
|
||||
- `requirements.txt` for Python dependencies
|
||||
- `ui/package.json` for React dependencies
|
||||
- `ui/src/styles/globals.css` for design tokens
|
||||
- `security.py` for allowed commands
|
||||
- Existing components in `ui/src/components/` for UI patterns
|
||||
- Existing routers in `server/routers/` for API patterns
|
||||
|
||||
## Communication Style
|
||||
|
||||
- Explain your reasoning and decisions
|
||||
- Document what patterns you found and are following
|
||||
- Note any concerns or tradeoffs you considered
|
||||
- Be explicit about what verification steps you ran and their results
|
||||
- If you encounter issues, explain how you resolved them
|
||||
|
||||
## Non-Negotiable Rules
|
||||
|
||||
1. NEVER skip the research phase - always understand before implementing
|
||||
2. NEVER leave code that doesn't pass lint and type checks
|
||||
3. NEVER introduce code that doesn't match existing patterns without explicit justification
|
||||
4. NEVER ignore error cases or edge conditions
|
||||
5. NEVER write code without comments explaining complex logic
|
||||
6. ALWAYS verify your implementation compiles and passes checks before finishing
|
||||
7. ALWAYS use web search and fetch to get up-to-date information about libraries
|
||||
8. ALWAYS explore the codebase first to understand existing patterns
|
||||
@@ -17,8 +17,8 @@ ls -la
|
||||
# 3. Read the project specification to understand what you're building
|
||||
cat app_spec.txt
|
||||
|
||||
# 4. Read progress notes from previous sessions
|
||||
cat claude-progress.txt
|
||||
# 4. Read progress notes from previous sessions (last 500 lines to avoid context overflow)
|
||||
tail -500 claude-progress.txt
|
||||
|
||||
# 5. Check recent git history
|
||||
git log --oneline -20
|
||||
|
||||
@@ -28,8 +28,8 @@ ls -la
|
||||
# 3. Read the project specification to understand what you're building
|
||||
cat app_spec.txt
|
||||
|
||||
# 4. Read progress notes from previous sessions
|
||||
cat claude-progress.txt
|
||||
# 4. Read progress notes from previous sessions (last 500 lines to avoid context overflow)
|
||||
tail -500 claude-progress.txt
|
||||
|
||||
# 5. Check recent git history
|
||||
git log --oneline -20
|
||||
|
||||
14
.env.example
14
.env.example
@@ -1,2 +1,16 @@
|
||||
# Optional: N8N webhook for progress notifications
|
||||
# PROGRESS_N8N_WEBHOOK_URL=https://your-n8n-instance.com/webhook/...
|
||||
|
||||
# CLI Command Selection
|
||||
# Choose which CLI command to use for the agent.
|
||||
# - claude: Uses Anthropic's official Claude Code CLI (default)
|
||||
# - glm: Uses GLM CLI (or any other compatible CLI)
|
||||
# Defaults to 'claude' if not specified
|
||||
# CLI_COMMAND=claude
|
||||
|
||||
# Playwright Browser Mode
|
||||
# Controls whether Playwright runs Chrome in headless mode (no visible browser window).
|
||||
# - true: Browser runs in background, invisible (recommended for using PC while agent works)
|
||||
# - false: Browser opens a visible window (useful for debugging)
|
||||
# Defaults to 'false' if not specified
|
||||
# PLAYWRIGHT_HEADLESS=false
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,6 +2,7 @@
|
||||
generations/
|
||||
|
||||
nul
|
||||
issues/
|
||||
|
||||
# Log files
|
||||
logs/
|
||||
|
||||
50
client.py
50
client.py
@@ -13,9 +13,45 @@ from pathlib import Path
|
||||
|
||||
from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient
|
||||
from claude_agent_sdk.types import HookMatcher
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from security import bash_security_hook
|
||||
|
||||
# Load environment variables from .env file if present
|
||||
load_dotenv()
|
||||
|
||||
# Default CLI command - can be overridden via CLI_COMMAND environment variable
|
||||
# Common values: "claude" (default), "glm"
|
||||
DEFAULT_CLI_COMMAND = "claude"
|
||||
|
||||
# Default Playwright headless mode - can be overridden via PLAYWRIGHT_HEADLESS env var
|
||||
# When True, browser runs invisibly in background
|
||||
# When False, browser window is visible (default - useful for monitoring agent progress)
|
||||
DEFAULT_PLAYWRIGHT_HEADLESS = False
|
||||
|
||||
|
||||
def get_cli_command() -> str:
|
||||
"""
|
||||
Get the CLI command to use for the agent.
|
||||
|
||||
Reads from CLI_COMMAND environment variable, defaults to 'claude'.
|
||||
This allows users to use alternative CLIs like 'glm'.
|
||||
"""
|
||||
return os.getenv("CLI_COMMAND", DEFAULT_CLI_COMMAND)
|
||||
|
||||
|
||||
def get_playwright_headless() -> bool:
|
||||
"""
|
||||
Get the Playwright headless mode setting.
|
||||
|
||||
Reads from PLAYWRIGHT_HEADLESS environment variable, defaults to False.
|
||||
Returns True for headless mode (invisible browser), False for visible browser.
|
||||
"""
|
||||
value = os.getenv("PLAYWRIGHT_HEADLESS", "false").lower()
|
||||
# Accept various truthy/falsy values
|
||||
return value in ("true", "1", "yes", "on")
|
||||
|
||||
|
||||
# Feature MCP tools for feature/test management
|
||||
FEATURE_MCP_TOOLS = [
|
||||
"mcp__features__feature_get_stats",
|
||||
@@ -151,12 +187,14 @@ def create_client(project_dir: Path, model: str, yolo_mode: bool = False):
|
||||
print(" - Project settings enabled (skills, commands, CLAUDE.md)")
|
||||
print()
|
||||
|
||||
# Use system Claude CLI instead of bundled one (avoids Bun runtime crash on Windows)
|
||||
system_cli = shutil.which("claude")
|
||||
# Use system CLI instead of bundled one (avoids Bun runtime crash on Windows)
|
||||
# CLI command is configurable via CLI_COMMAND environment variable
|
||||
cli_command = get_cli_command()
|
||||
system_cli = shutil.which(cli_command)
|
||||
if system_cli:
|
||||
print(f" - Using system CLI: {system_cli}")
|
||||
else:
|
||||
print(" - Warning: System Claude CLI not found, using bundled CLI")
|
||||
print(f" - Warning: System CLI '{cli_command}' not found, using bundled CLI")
|
||||
|
||||
# Build MCP servers config - features is always included, playwright only in standard mode
|
||||
mcp_servers = {
|
||||
@@ -174,9 +212,13 @@ def create_client(project_dir: Path, model: str, yolo_mode: bool = False):
|
||||
}
|
||||
if not yolo_mode:
|
||||
# Include Playwright MCP server for browser automation (standard mode only)
|
||||
# Headless mode is configurable via PLAYWRIGHT_HEADLESS environment variable
|
||||
playwright_args = ["@playwright/mcp@latest", "--viewport-size", "1280x720"]
|
||||
if get_playwright_headless():
|
||||
playwright_args.append("--headless")
|
||||
mcp_servers["playwright"] = {
|
||||
"command": "npx",
|
||||
"args": ["@playwright/mcp@latest", "--viewport-size", "1280x720"],
|
||||
"args": playwright_args,
|
||||
}
|
||||
|
||||
return ClaudeSDKClient(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
claude-agent-sdk>=0.1.0
|
||||
claude-agent-sdk>=0.1.0,<0.2.0
|
||||
python-dotenv>=1.0.0
|
||||
sqlalchemy>=2.0.0
|
||||
fastapi>=0.115.0
|
||||
|
||||
@@ -6,10 +6,26 @@ Main entry point for the Autonomous Coding UI server.
|
||||
Provides REST API, WebSocket, and static file serving.
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables from .env file if present
|
||||
load_dotenv()
|
||||
|
||||
|
||||
def get_cli_command() -> str:
|
||||
"""
|
||||
Get the CLI command to use for the agent.
|
||||
|
||||
Reads from CLI_COMMAND environment variable, defaults to 'claude'.
|
||||
This allows users to use alternative CLIs like 'glm'.
|
||||
"""
|
||||
return os.getenv("CLI_COMMAND", "claude")
|
||||
|
||||
from fastapi import FastAPI, HTTPException, Request, WebSocket
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import FileResponse
|
||||
@@ -124,11 +140,12 @@ async def health_check():
|
||||
@app.get("/api/setup/status", response_model=SetupStatus)
|
||||
async def setup_status():
|
||||
"""Check system setup status."""
|
||||
# Check for Claude CLI
|
||||
claude_cli = shutil.which("claude") is not None
|
||||
# Check for CLI (configurable via CLI_COMMAND environment variable)
|
||||
cli_command = get_cli_command()
|
||||
claude_cli = shutil.which(cli_command) is not None
|
||||
|
||||
# Check for Claude CLI configuration directory
|
||||
# Note: Claude CLI no longer stores credentials in ~/.claude/.credentials.json
|
||||
# Check for CLI configuration directory
|
||||
# Note: CLI no longer stores credentials in ~/.claude/.credentials.json
|
||||
# The existence of ~/.claude indicates the CLI has been configured
|
||||
claude_dir = Path.home() / ".claude"
|
||||
credentials = claude_dir.exists() and claude_dir.is_dir()
|
||||
|
||||
@@ -18,12 +18,27 @@ from pathlib import Path
|
||||
from typing import AsyncGenerator, Optional
|
||||
|
||||
from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from .assistant_database import (
|
||||
add_message,
|
||||
create_conversation,
|
||||
)
|
||||
|
||||
# Load environment variables from .env file if present
|
||||
load_dotenv()
|
||||
|
||||
|
||||
def get_cli_command() -> str:
|
||||
"""
|
||||
Get the CLI command to use for the agent.
|
||||
|
||||
Reads from CLI_COMMAND environment variable, defaults to 'claude'.
|
||||
This allows users to use alternative CLIs like 'glm'.
|
||||
"""
|
||||
return os.getenv("CLI_COMMAND", "claude")
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Root directory of the project
|
||||
@@ -227,8 +242,9 @@ class AssistantChatSession:
|
||||
# Get system prompt with project context
|
||||
system_prompt = get_system_prompt(self.project_name, self.project_dir)
|
||||
|
||||
# Use system Claude CLI
|
||||
system_cli = shutil.which("claude")
|
||||
# Use system CLI (configurable via CLI_COMMAND environment variable)
|
||||
cli_command = get_cli_command()
|
||||
system_cli = shutil.which(cli_command)
|
||||
|
||||
try:
|
||||
self.client = ClaudeSDKClient(
|
||||
|
||||
@@ -9,6 +9,7 @@ Uses the expand-project.md skill to help users add features to existing projects
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import threading
|
||||
@@ -18,9 +19,23 @@ from pathlib import Path
|
||||
from typing import AsyncGenerator, Optional
|
||||
|
||||
from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from ..schemas import ImageAttachment
|
||||
|
||||
# Load environment variables from .env file if present
|
||||
load_dotenv()
|
||||
|
||||
|
||||
def get_cli_command() -> str:
|
||||
"""
|
||||
Get the CLI command to use for the agent.
|
||||
|
||||
Reads from CLI_COMMAND environment variable, defaults to 'claude'.
|
||||
This allows users to use alternative CLIs like 'glm'.
|
||||
"""
|
||||
return os.getenv("CLI_COMMAND", "claude")
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -120,12 +135,14 @@ class ExpandChatSession:
|
||||
except UnicodeDecodeError:
|
||||
skill_content = skill_path.read_text(encoding="utf-8", errors="replace")
|
||||
|
||||
# Find and validate Claude CLI before creating temp files
|
||||
system_cli = shutil.which("claude")
|
||||
# Find and validate CLI before creating temp files
|
||||
# CLI command is configurable via CLI_COMMAND environment variable
|
||||
cli_command = get_cli_command()
|
||||
system_cli = shutil.which(cli_command)
|
||||
if not system_cli:
|
||||
yield {
|
||||
"type": "error",
|
||||
"content": "Claude CLI not found. Please install Claude Code."
|
||||
"content": f"CLI '{cli_command}' not found. Please install it or check your CLI_COMMAND setting."
|
||||
}
|
||||
return
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ Uses the create-spec.md skill to guide users through app spec creation.
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import threading
|
||||
from datetime import datetime
|
||||
@@ -15,9 +16,23 @@ from pathlib import Path
|
||||
from typing import AsyncGenerator, Optional
|
||||
|
||||
from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from ..schemas import ImageAttachment
|
||||
|
||||
# Load environment variables from .env file if present
|
||||
load_dotenv()
|
||||
|
||||
|
||||
def get_cli_command() -> str:
|
||||
"""
|
||||
Get the CLI command to use for the agent.
|
||||
|
||||
Reads from CLI_COMMAND environment variable, defaults to 'claude'.
|
||||
This allows users to use alternative CLIs like 'glm'.
|
||||
"""
|
||||
return os.getenv("CLI_COMMAND", "claude")
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -142,7 +157,9 @@ class SpecChatSession:
|
||||
# Create Claude SDK client with limited tools for spec creation
|
||||
# Use Opus for best quality spec generation
|
||||
# Use system CLI to avoid bundled Bun runtime crash (exit code 3) on Windows
|
||||
system_cli = shutil.which("claude")
|
||||
# CLI command is configurable via CLI_COMMAND environment variable
|
||||
cli_command = get_cli_command()
|
||||
system_cli = shutil.which(cli_command)
|
||||
try:
|
||||
self.client = ClaudeSDKClient(
|
||||
options=ClaudeAgentOptions(
|
||||
|
||||
33
start.py
33
start.py
@@ -13,7 +13,24 @@ import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from auth import is_auth_error, print_auth_error_help
|
||||
|
||||
# Load environment variables from .env file if present
|
||||
load_dotenv()
|
||||
|
||||
|
||||
def get_cli_command() -> str:
|
||||
"""
|
||||
Get the CLI command to use for the agent.
|
||||
|
||||
Reads from CLI_COMMAND environment variable, defaults to 'claude'.
|
||||
This allows users to use alternative CLIs like 'glm'.
|
||||
"""
|
||||
return os.getenv("CLI_COMMAND", "claude")
|
||||
|
||||
|
||||
from prompts import (
|
||||
get_project_prompts_dir,
|
||||
has_project_prompts,
|
||||
@@ -217,11 +234,12 @@ def run_spec_creation(project_dir: Path) -> bool:
|
||||
print("Exit Claude Code (Ctrl+C or /exit) when finished.\n")
|
||||
|
||||
try:
|
||||
# Launch Claude Code with /create-spec command
|
||||
# Launch CLI with /create-spec command
|
||||
# Project path included in command string so it populates $ARGUMENTS
|
||||
# Capture stderr to detect auth errors while letting stdout flow to terminal
|
||||
cli_command = get_cli_command()
|
||||
result = subprocess.run(
|
||||
["claude", f"/create-spec {project_dir}"],
|
||||
[cli_command, f"/create-spec {project_dir}"],
|
||||
check=False, # Don't raise on non-zero exit
|
||||
cwd=str(Path(__file__).parent), # Run from project root
|
||||
stderr=subprocess.PIPE,
|
||||
@@ -249,13 +267,17 @@ def run_spec_creation(project_dir: Path) -> bool:
|
||||
print(f"Please ensure app_spec.txt exists in: {get_project_prompts_dir(project_dir)}")
|
||||
# If failed with non-zero exit and no spec, might be auth issue
|
||||
if result.returncode != 0:
|
||||
print("\nIf you're having authentication issues, try running: claude login")
|
||||
print(f"\nIf you're having authentication issues, try running: {cli_command} login")
|
||||
return False
|
||||
|
||||
except FileNotFoundError:
|
||||
print("\nError: 'claude' command not found.")
|
||||
cli_command = get_cli_command()
|
||||
print(f"\nError: '{cli_command}' command not found.")
|
||||
if cli_command == "claude":
|
||||
print("Make sure Claude Code CLI is installed:")
|
||||
print(" npm install -g @anthropic-ai/claude-code")
|
||||
else:
|
||||
print(f"Make sure the '{cli_command}' CLI is installed and in your PATH.")
|
||||
return False
|
||||
except KeyboardInterrupt:
|
||||
print("\n\nSpec creation cancelled.")
|
||||
@@ -407,7 +429,8 @@ def run_agent(project_name: str, project_dir: Path) -> None:
|
||||
print(f"\nAgent error:\n{stderr_output.strip()}")
|
||||
# Still hint about auth if exit was unexpected
|
||||
if "error" in stderr_output.lower() or "exception" in stderr_output.lower():
|
||||
print("\nIf this is an authentication issue, try running: claude login")
|
||||
cli_command = get_cli_command()
|
||||
print(f"\nIf this is an authentication issue, try running: {cli_command} login")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\nAgent interrupted. Run again to resume.")
|
||||
|
||||
103
ui/src/components/ConfirmDialog.tsx
Normal file
103
ui/src/components/ConfirmDialog.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* ConfirmDialog Component
|
||||
*
|
||||
* A reusable confirmation dialog following the neobrutalism design system.
|
||||
* Used to confirm destructive actions like deleting projects.
|
||||
*/
|
||||
|
||||
import { AlertTriangle, X } from 'lucide-react'
|
||||
|
||||
interface ConfirmDialogProps {
|
||||
isOpen: boolean
|
||||
title: string
|
||||
message: string
|
||||
confirmLabel?: string
|
||||
cancelLabel?: string
|
||||
variant?: 'danger' | 'warning'
|
||||
isLoading?: boolean
|
||||
onConfirm: () => void
|
||||
onCancel: () => void
|
||||
}
|
||||
|
||||
export function ConfirmDialog({
|
||||
isOpen,
|
||||
title,
|
||||
message,
|
||||
confirmLabel = 'Confirm',
|
||||
cancelLabel = 'Cancel',
|
||||
variant = 'danger',
|
||||
isLoading = false,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
}: ConfirmDialogProps) {
|
||||
if (!isOpen) return null
|
||||
|
||||
const variantColors = {
|
||||
danger: {
|
||||
icon: 'var(--color-neo-danger)',
|
||||
button: 'neo-btn-danger',
|
||||
},
|
||||
warning: {
|
||||
icon: 'var(--color-neo-pending)',
|
||||
button: 'neo-btn-warning',
|
||||
},
|
||||
}
|
||||
|
||||
const colors = variantColors[variant]
|
||||
|
||||
return (
|
||||
<div className="neo-modal-backdrop" onClick={onCancel}>
|
||||
<div
|
||||
className="neo-modal w-full max-w-md"
|
||||
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">
|
||||
<div
|
||||
className="p-2 border-2 border-[var(--color-neo-border)] shadow-[2px_2px_0px_rgba(0,0,0,1)]"
|
||||
style={{ backgroundColor: colors.icon }}
|
||||
>
|
||||
<AlertTriangle size={20} className="text-white" />
|
||||
</div>
|
||||
<h2 className="font-display font-bold text-lg text-[#1a1a1a]">
|
||||
{title}
|
||||
</h2>
|
||||
</div>
|
||||
<button
|
||||
onClick={onCancel}
|
||||
className="neo-btn neo-btn-ghost p-2"
|
||||
disabled={isLoading}
|
||||
>
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="p-6">
|
||||
<p className="text-[var(--color-neo-text-secondary)] mb-6">
|
||||
{message}
|
||||
</p>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex justify-end gap-3">
|
||||
<button
|
||||
onClick={onCancel}
|
||||
className="neo-btn"
|
||||
disabled={isLoading}
|
||||
>
|
||||
{cancelLabel}
|
||||
</button>
|
||||
<button
|
||||
onClick={onConfirm}
|
||||
className={`neo-btn ${colors.button}`}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? 'Deleting...' : confirmLabel}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import { useState } from 'react'
|
||||
import { ChevronDown, Plus, FolderOpen, Loader2 } from 'lucide-react'
|
||||
import { ChevronDown, Plus, FolderOpen, Loader2, Trash2 } from 'lucide-react'
|
||||
import type { ProjectSummary } from '../lib/types'
|
||||
import { NewProjectModal } from './NewProjectModal'
|
||||
import { ConfirmDialog } from './ConfirmDialog'
|
||||
import { useDeleteProject } from '../hooks/useProjects'
|
||||
|
||||
interface ProjectSelectorProps {
|
||||
projects: ProjectSummary[]
|
||||
@@ -18,12 +20,42 @@ export function ProjectSelector({
|
||||
}: ProjectSelectorProps) {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [showNewProjectModal, setShowNewProjectModal] = useState(false)
|
||||
const [projectToDelete, setProjectToDelete] = useState<string | null>(null)
|
||||
|
||||
const deleteProject = useDeleteProject()
|
||||
|
||||
const handleProjectCreated = (projectName: string) => {
|
||||
onSelectProject(projectName)
|
||||
setIsOpen(false)
|
||||
}
|
||||
|
||||
const handleDeleteClick = (e: React.MouseEvent, projectName: string) => {
|
||||
// Prevent the click from selecting the project
|
||||
e.stopPropagation()
|
||||
setProjectToDelete(projectName)
|
||||
}
|
||||
|
||||
const handleConfirmDelete = async () => {
|
||||
if (!projectToDelete) return
|
||||
|
||||
try {
|
||||
await deleteProject.mutateAsync(projectToDelete)
|
||||
// If the deleted project was selected, clear the selection
|
||||
if (selectedProject === projectToDelete) {
|
||||
onSelectProject(null)
|
||||
}
|
||||
setProjectToDelete(null)
|
||||
} catch (error) {
|
||||
// Error is handled by the mutation, just close the dialog
|
||||
console.error('Failed to delete project:', error)
|
||||
setProjectToDelete(null)
|
||||
}
|
||||
}
|
||||
|
||||
const handleCancelDelete = () => {
|
||||
setProjectToDelete(null)
|
||||
}
|
||||
|
||||
const selectedProjectData = projects.find(p => p.name === selectedProject)
|
||||
|
||||
return (
|
||||
@@ -70,17 +102,20 @@ export function ProjectSelector({
|
||||
{projects.length > 0 ? (
|
||||
<div className="max-h-[300px] overflow-auto">
|
||||
{projects.map(project => (
|
||||
<button
|
||||
<div
|
||||
key={project.name}
|
||||
onClick={() => {
|
||||
onSelectProject(project.name)
|
||||
setIsOpen(false)
|
||||
}}
|
||||
className={`w-full neo-dropdown-item flex items-center justify-between ${
|
||||
className={`flex items-center ${
|
||||
project.name === selectedProject
|
||||
? 'bg-[var(--color-neo-pending)]'
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
onClick={() => {
|
||||
onSelectProject(project.name)
|
||||
setIsOpen(false)
|
||||
}}
|
||||
className="flex-1 neo-dropdown-item flex items-center justify-between"
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<FolderOpen size={16} />
|
||||
@@ -92,6 +127,14 @@ export function ProjectSelector({
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => handleDeleteClick(e, project.name)}
|
||||
className="p-2 mr-2 text-[var(--color-neo-text-secondary)] hover:text-[var(--color-neo-danger)] hover:bg-[var(--color-neo-danger)]/10 transition-colors rounded"
|
||||
title={`Delete ${project.name}`}
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
@@ -124,6 +167,19 @@ export function ProjectSelector({
|
||||
onClose={() => setShowNewProjectModal(false)}
|
||||
onProjectCreated={handleProjectCreated}
|
||||
/>
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
<ConfirmDialog
|
||||
isOpen={projectToDelete !== null}
|
||||
title="Delete Project"
|
||||
message={`Are you sure you want to remove "${projectToDelete}" from the registry? This will unregister the project but preserve its files on disk.`}
|
||||
confirmLabel="Delete"
|
||||
cancelLabel="Cancel"
|
||||
variant="danger"
|
||||
isLoading={deleteProject.isPending}
|
||||
onConfirm={handleConfirmDelete}
|
||||
onCancel={handleCancelDelete}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ export function SpecCreationChat({
|
||||
const [yoloEnabled, setYoloEnabled] = useState(false)
|
||||
const [pendingAttachments, setPendingAttachments] = useState<ImageAttachment[]>([])
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null)
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const {
|
||||
@@ -98,6 +98,10 @@ export function SpecCreationChat({
|
||||
sendMessage(trimmed, pendingAttachments.length > 0 ? pendingAttachments : undefined)
|
||||
setInput('')
|
||||
setPendingAttachments([]) // Clear attachments after sending
|
||||
// Reset textarea height after sending
|
||||
if (inputRef.current) {
|
||||
inputRef.current.style.height = 'auto'
|
||||
}
|
||||
}
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
@@ -355,11 +359,15 @@ export function SpecCreationChat({
|
||||
<Paperclip size={18} />
|
||||
</button>
|
||||
|
||||
<input
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
onChange={(e) => {
|
||||
setInput(e.target.value)
|
||||
// Auto-resize the textarea
|
||||
e.target.style.height = 'auto'
|
||||
e.target.style.height = `${Math.min(e.target.scrollHeight, 200)}px`
|
||||
}}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={
|
||||
currentQuestions
|
||||
@@ -368,8 +376,9 @@ export function SpecCreationChat({
|
||||
? 'Add a message with your image(s)...'
|
||||
: 'Type your response... (or /exit to go to project)'
|
||||
}
|
||||
className="neo-input flex-1"
|
||||
className="neo-input flex-1 resize-none min-h-[46px] max-h-[200px] overflow-y-auto"
|
||||
disabled={(isLoading && !currentQuestions) || connectionStatus !== 'connected'}
|
||||
rows={1}
|
||||
/>
|
||||
<button
|
||||
onClick={handleSendMessage}
|
||||
@@ -386,7 +395,7 @@ export function SpecCreationChat({
|
||||
|
||||
{/* Help text */}
|
||||
<p className="text-xs text-[var(--color-neo-text-secondary)] mt-2">
|
||||
Press Enter to send. Drag & drop or click <Paperclip size={12} className="inline" /> to attach images (JPEG/PNG, max 5MB).
|
||||
Press Enter to send, Shift+Enter for new line. Drag & drop or click <Paperclip size={12} className="inline" /> to attach images (JPEG/PNG, max 5MB).
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/addfeatureform.tsx","./src/components/agentcontrol.tsx","./src/components/agentthought.tsx","./src/components/assistantchat.tsx","./src/components/assistantfab.tsx","./src/components/assistantpanel.tsx","./src/components/chatmessage.tsx","./src/components/debuglogviewer.tsx","./src/components/expandprojectchat.tsx","./src/components/expandprojectmodal.tsx","./src/components/featurecard.tsx","./src/components/featuremodal.tsx","./src/components/folderbrowser.tsx","./src/components/kanbanboard.tsx","./src/components/kanbancolumn.tsx","./src/components/newprojectmodal.tsx","./src/components/progressdashboard.tsx","./src/components/projectselector.tsx","./src/components/questionoptions.tsx","./src/components/settingsmodal.tsx","./src/components/setupwizard.tsx","./src/components/speccreationchat.tsx","./src/components/typingindicator.tsx","./src/hooks/useassistantchat.ts","./src/hooks/usecelebration.ts","./src/hooks/useexpandchat.ts","./src/hooks/usefeaturesound.ts","./src/hooks/useprojects.ts","./src/hooks/usespecchat.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/types.ts"],"version":"5.6.3"}
|
||||
{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/addfeatureform.tsx","./src/components/agentcontrol.tsx","./src/components/agentthought.tsx","./src/components/assistantchat.tsx","./src/components/assistantfab.tsx","./src/components/assistantpanel.tsx","./src/components/chatmessage.tsx","./src/components/confirmdialog.tsx","./src/components/debuglogviewer.tsx","./src/components/expandprojectchat.tsx","./src/components/expandprojectmodal.tsx","./src/components/featurecard.tsx","./src/components/featuremodal.tsx","./src/components/folderbrowser.tsx","./src/components/kanbanboard.tsx","./src/components/kanbancolumn.tsx","./src/components/newprojectmodal.tsx","./src/components/progressdashboard.tsx","./src/components/projectselector.tsx","./src/components/questionoptions.tsx","./src/components/settingsmodal.tsx","./src/components/setupwizard.tsx","./src/components/speccreationchat.tsx","./src/components/typingindicator.tsx","./src/hooks/useassistantchat.ts","./src/hooks/usecelebration.ts","./src/hooks/useexpandchat.ts","./src/hooks/usefeaturesound.ts","./src/hooks/useprojects.ts","./src/hooks/usespecchat.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/types.ts"],"version":"5.6.3"}
|
||||
Reference in New Issue
Block a user