diff --git a/scripts/newproject b/scripts/newproject new file mode 100755 index 0000000..8273def --- /dev/null +++ b/scripts/newproject @@ -0,0 +1,572 @@ +#!/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 ""