#!/usr/bin/env bash # # newproject - Interactive project scaffolding tool # Uses gum for beautiful terminal UI # set -e # Colors CYAN='\033[0;36m' GREEN='\033[0;32m' YELLOW='\033[1;33m' RED='\033[0;31m' NC='\033[0m' # No Color # Config PROJECTS_DIR="$HOME/Projects" TEMPLATE_REPO="git@git.htsn.io:hutson/template-project.git" # Source central config if available [ -f ~/.secrets ] && source ~/.secrets [ -f ~/.hosts ] && source ~/.hosts ####################################### # Header ####################################### clear gum style \ --foreground 212 --border-foreground 212 --border double \ --align center --width 60 --margin "1 2" --padding "1 2" \ '🚀 New Project Wizard' '' 'Create a new project with Claude Code' ####################################### # Project Name ####################################### echo "" gum style --foreground 245 "Enter your project name (lowercase, hyphens ok):" PROJECT_NAME=$(gum input --placeholder "my-awesome-project" --width 40) if [ -z "$PROJECT_NAME" ]; then gum style --foreground 196 "❌ Project name is required" exit 1 fi # Validate name if [[ ! "$PROJECT_NAME" =~ ^[a-z0-9-]+$ ]]; then gum style --foreground 196 "❌ Project name must be lowercase alphanumeric with hyphens only" exit 1 fi PROJECT_PATH="$PROJECTS_DIR/$PROJECT_NAME" if [ -d "$PROJECT_PATH" ]; then gum style --foreground 196 "❌ Project already exists at $PROJECT_PATH" exit 1 fi ####################################### # Project Type ####################################### echo "" gum style --foreground 245 "Select project type:" PROJECT_TYPE=$(gum choose \ "python-fastapi" \ "python-generic" \ "typescript-react" \ "typescript-node" \ "generic" \ --header "Project Type") ####################################### # Project Description ####################################### echo "" gum style --foreground 245 "Brief project description:" PROJECT_DESC=$(gum input --placeholder "A brief description of what this project does" --width 60) ####################################### # MCP Selection ####################################### echo "" gum style --foreground 245 "Select MCPs to enable (space to select, enter to confirm):" # Define available MCPs with descriptions MCPS=$(gum choose --no-limit \ "exa (web search, research)" \ "Ref (documentation lookup)" \ "ticktick (task management)" \ "beeper (messaging)" \ "airtable (database)" \ "claude-in-chrome (browser automation)" \ "shopping (Amazon)" \ "proton-mail (email)" \ "weather (forecasts)" \ --header "MCPs (space=select, enter=confirm)" \ --selected "exa (web search, research),Ref (documentation lookup)") ####################################### # Git Options ####################################### echo "" gum style --foreground 245 "Git repository options:" GIT_INIT=$(gum choose "Yes" "No" --header "Initialize git repository?") GITEA_CREATE="No" if [ "$GIT_INIT" = "Yes" ]; then GITEA_CREATE=$(gum choose "Yes" "No" --header "Create repo on Gitea (git.htsn.io)?") fi ####################################### # Additional Options ####################################### echo "" gum style --foreground 245 "Additional options:" ADDITIONAL=$(gum choose --no-limit \ "spec-kit (spec-driven development)" \ "pre-commit hooks" \ "GitHub Actions CI" \ "Docker support" \ --header "Additional features (space=select)") ####################################### # Confirm ####################################### echo "" gum style --foreground 212 --border-foreground 212 --border rounded \ --align left --width 60 --margin "1 0" --padding "1 2" \ "📋 Project Summary" \ "" \ "Name: $PROJECT_NAME" \ "Type: $PROJECT_TYPE" \ "Path: $PROJECT_PATH" \ "Description: $PROJECT_DESC" \ "" \ "MCPs: $(echo "$MCPS" | tr '\n' ', ' | sed 's/, $//')" \ "" \ "Git: $GIT_INIT | Gitea: $GITEA_CREATE" \ "Extras: $(echo "$ADDITIONAL" | tr '\n' ', ' | sed 's/, $//')" echo "" CONFIRM=$(gum choose "Create Project" "Cancel" --header "Proceed?") if [ "$CONFIRM" = "Cancel" ]; then gum style --foreground 196 "❌ Cancelled" exit 0 fi ####################################### # Create Project ####################################### echo "" gum spin --spinner dot --title "Creating project directory..." -- sleep 0.5 mkdir -p "$PROJECT_PATH" ####################################### # Generate .claude/settings.json (MCP config) ####################################### gum spin --spinner dot --title "Generating MCP configuration..." -- sleep 0.3 mkdir -p "$PROJECT_PATH/.claude" # Build disabled MCPs list ALL_MCPS=("exa" "Ref" "ticktick" "beeper" "airtable" "claude-in-chrome" "shopping" "proton-mail" "weather") ENABLED_MCPS="" # Parse selected MCPs for mcp in "${ALL_MCPS[@]}"; do if echo "$MCPS" | grep -qi "^$mcp "; then ENABLED_MCPS="$ENABLED_MCPS $mcp" fi done # Generate settings.json with disabled MCPs cat > "$PROJECT_PATH/.claude/settings.json" << 'SETTINGS_EOF' { "mcpServers": { SETTINGS_EOF first=true for mcp in "${ALL_MCPS[@]}"; do if ! echo "$MCPS" | grep -qi "^$mcp "; then if [ "$first" = true ]; then first=false else echo "," >> "$PROJECT_PATH/.claude/settings.json" fi printf ' "%s": { "disabled": true }' "$mcp" >> "$PROJECT_PATH/.claude/settings.json" fi done cat >> "$PROJECT_PATH/.claude/settings.json" << 'SETTINGS_EOF' } } SETTINGS_EOF ####################################### # Generate CLAUDE.md ####################################### gum spin --spinner dot --title "Generating CLAUDE.md..." -- sleep 0.3 # Determine type-specific content case "$PROJECT_TYPE" in python-fastapi) TECH_STACK="Python 3.12+, FastAPI, SQLModel, Pydantic, Alembic" TYPE_CHECK_CMD="mypy --strict src/" LINT_CMD="ruff check src/ && black src/" TEST_CMD="pytest" ;; python-generic) TECH_STACK="Python 3.12+" TYPE_CHECK_CMD="mypy --strict src/" LINT_CMD="ruff check src/ && black src/" TEST_CMD="pytest" ;; typescript-react) TECH_STACK="TypeScript, React 18, Bun, TailwindCSS" TYPE_CHECK_CMD="bun run tsc --noEmit" LINT_CMD="bun run lint" TEST_CMD="bun test" ;; typescript-node) TECH_STACK="TypeScript, Node.js/Bun" TYPE_CHECK_CMD="bun run tsc --noEmit" LINT_CMD="bun run lint" TEST_CMD="bun test" ;; generic) TECH_STACK="[Customize]" TYPE_CHECK_CMD="# Add type check command" LINT_CMD="# Add lint command" TEST_CMD="# Add test command" ;; esac # Check if spec-kit selected USE_SPECKIT="No" if echo "$ADDITIONAL" | grep -q "spec-kit"; then USE_SPECKIT="Yes" fi # Generate CLAUDE.md cat > "$PROJECT_PATH/CLAUDE.md" << CLAUDE_EOF # $PROJECT_NAME - Claude Code Guidelines ## Project Overview $PROJECT_DESC **Tech Stack**: $TECH_STACK **Key Directories**: - \`src/\` - Main source code - \`tests/\` - Test files - \`docs/\` - Documentation --- ## Type Safety (Strict Enforcement) **Type safety is non-negotiable. All code must pass strict type checking.** \`\`\`bash # Required before every commit $TYPE_CHECK_CMD \`\`\` - Complete type hints on ALL functions (params + return types) - No \`any\` types (TypeScript) or \`# type: ignore\` without justification - Money/prices: Always \`Decimal\`, never \`float\` --- ## Required Tool Usage ### 1. Documentation Lookup (Priority Order) \`\`\` 1. Ref Tools (private docs) → mcp__Ref__ref_search_documentation(query="topic ref_src=private") 2. Ref Tools (public docs) → mcp__Ref__ref_search_documentation(query="topic") 3. Exa Tools → mcp__exa__get_code_context_exa(query="...") (last resort) \`\`\` ### 2. Code Review Run after completing significant features: \`\`\` /code-review:code-review \`\`\` ### 3. Git Commits \`\`\` /commit-commands:commit /commit-commands:commit-push-pr # For complete workflow \`\`\` ### 4. Frontend Design (if applicable) \`\`\` /frontend-design:frontend-design \`\`\` --- ## Pre-Commit Checklist \`\`\`bash # Type safety $TYPE_CHECK_CMD # Linting $LINT_CMD # Tests $TEST_CMD # Code review (for significant changes) /code-review:code-review \`\`\` --- CLAUDE_EOF # Add spec-kit section if selected if [ "$USE_SPECKIT" = "Yes" ]; then cat >> "$PROJECT_PATH/CLAUDE.md" << 'SPECKIT_EOF' ## Spec-Driven Development **All features and significant changes should follow spec-driven development.** ``` 1. /speckit.specify → Define requirements (what & why) 2. /speckit.plan → Specify technology and architecture 3. /speckit.tasks → Break plan into actionable tasks 4. /speckit.implement → Build features according to plan 5. /speckit.clarify → Resolve ambiguities as needed ``` --- SPECKIT_EOF fi # Add MCP configuration section cat >> "$PROJECT_PATH/CLAUDE.md" << MCP_EOF ## MCP Configuration **Enabled MCPs for this project:** $(echo "$MCPS" | sed 's/^/- /') **Disabled MCPs** are configured in \`.claude/settings.json\`. To re-enable, remove from the disabled list or use \`/config\` → MCP Servers. --- ## Central Configuration Reference This project can access Hutson's central homelab configuration files. These are synced across all machines via Syncthing. | File | Purpose | Usage | |------|---------|-------| | \`~/.secrets\` | API keys, tokens, credentials | \`source ~/.secrets\` then use \`\$VAR_NAME\` | | \`~/.hosts\` | IPs, hostnames, service URLs | \`source ~/.hosts\` then use \`\$IP_*\` or \`\$HOST_*\` | | \`~/.ssh/config\` | SSH aliases for all homelab hosts | \`ssh pve\`, \`ssh truenas\`, \`ssh docker-host\`, etc. | **When adding new credentials or hosts:** 1. Add to the central files (\`~/.secrets\` or \`~/.hosts\`) 2. Files sync via Syncthing to all machines 3. Never commit secrets to git - use environment variables --- ## Project-Specific Notes [Add project-specific patterns, gotchas, and conventions here] MCP_EOF ####################################### # Generate README.md ####################################### gum spin --spinner dot --title "Generating README.md..." -- sleep 0.2 cat > "$PROJECT_PATH/README.md" << README_EOF # $PROJECT_NAME $PROJECT_DESC ## Quick Start \`\`\`bash # Install dependencies # [Add install command] # Run development server # [Add dev command] # Run tests $TEST_CMD \`\`\` ## Development See [CLAUDE.md](CLAUDE.md) for development guidelines and conventions. README_EOF ####################################### # Generate .gitignore ####################################### gum spin --spinner dot --title "Generating .gitignore..." -- sleep 0.2 cat > "$PROJECT_PATH/.gitignore" << 'GITIGNORE_EOF' # Dependencies node_modules/ .venv/ venv/ __pycache__/ *.pyc # Build outputs dist/ build/ .next/ *.egg-info/ # Environment .env .env.local .env.*.local # IDE .idea/ .vscode/ *.swp *.swo # OS .DS_Store Thumbs.db # Testing coverage/ .coverage htmlcov/ .pytest_cache/ # Logs *.log logs/ GITIGNORE_EOF ####################################### # Git Init ####################################### if [ "$GIT_INIT" = "Yes" ]; then gum spin --spinner dot --title "Initializing git repository..." -- \ git -C "$PROJECT_PATH" init -q gum spin --spinner dot --title "Creating initial commit..." -- \ bash -c "cd '$PROJECT_PATH' && git add -A && git commit -q -m 'Initial project setup Generated with newproject wizard. Co-Authored-By: Claude Opus 4.5 '" fi ####################################### # Gitea Repo ####################################### if [ "$GITEA_CREATE" = "Yes" ] && [ -n "$GITEA_TOKEN" ]; then gum spin --spinner dot --title "Creating Gitea repository..." -- sleep 0.5 # Create repo via API RESPONSE=$(curl -s -X POST "https://git.htsn.io/api/v1/user/repos" \ -H "Authorization: token $GITEA_TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"name\": \"$PROJECT_NAME\", \"description\": \"$PROJECT_DESC\", \"private\": false, \"auto_init\": false }" 2>/dev/null) if echo "$RESPONSE" | grep -q "\"name\":\"$PROJECT_NAME\""; then # Add remote and push git -C "$PROJECT_PATH" remote add origin "git@git.htsn.io:hutson/$PROJECT_NAME.git" 2>/dev/null || true gum spin --spinner dot --title "Pushing to Gitea..." -- \ git -C "$PROJECT_PATH" push -u origin main -q 2>/dev/null || true GITEA_URL="https://git.htsn.io/hutson/$PROJECT_NAME" else GITEA_URL="(failed to create)" fi elif [ "$GITEA_CREATE" = "Yes" ]; then GITEA_URL="(GITEA_TOKEN not set in ~/.secrets)" fi ####################################### # Spec-kit init ####################################### if [ "$USE_SPECKIT" = "Yes" ]; then gum spin --spinner dot --title "Initializing spec-kit structure..." -- sleep 0.3 mkdir -p "$PROJECT_PATH/specs" mkdir -p "$PROJECT_PATH/plans" mkdir -p "$PROJECT_PATH/tasks" touch "$PROJECT_PATH/specs/.gitkeep" touch "$PROJECT_PATH/plans/.gitkeep" touch "$PROJECT_PATH/tasks/.gitkeep" fi ####################################### # Docker support ####################################### if echo "$ADDITIONAL" | grep -q "Docker support"; then gum spin --spinner dot --title "Adding Docker support..." -- sleep 0.3 cat > "$PROJECT_PATH/Dockerfile" << 'DOCKER_EOF' # Customize this Dockerfile for your project FROM python:3.12-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD ["python", "-m", "src.main"] DOCKER_EOF cat > "$PROJECT_PATH/docker-compose.yml" << 'COMPOSE_EOF' version: '3.8' services: app: build: . ports: - "8000:8000" environment: - ENV=development volumes: - .:/app COMPOSE_EOF fi ####################################### # Success! ####################################### echo "" gum style --foreground 82 --border-foreground 82 --border double \ --align center --width 60 --margin "1 2" --padding "1 2" \ '✅ Project Created Successfully!' \ '' \ "Path: $PROJECT_PATH" echo "" gum style --foreground 245 "Next steps:" echo "" echo " cd $PROJECT_PATH" if [ "$USE_SPECKIT" = "Yes" ]; then echo " /speckit.specify # Define requirements" else echo " # Start coding!" fi if [ -n "$GITEA_URL" ] && [ "$GITEA_URL" != "(failed to create)" ] && [ "$GITEA_URL" != "(GITEA_TOKEN not set in ~/.secrets)" ]; then echo "" echo " Gitea: $GITEA_URL" fi echo ""