My agentic slop goes here. Not intended for anyone else!
at main 34 kB view raw
1#!/bin/bash 2set -e 3 4# slopper - Unified Claude Code devcontainer workflow manager 5# Handles update, execution, and interactive modes for Claude in devcontainers 6 7# Colors for output (disabled in non-TTY or quiet mode) 8if [ -t 1 ] && [ "${QUIET:-false}" != "true" ]; then 9 RED='\033[0;31m' 10 GREEN='\033[0;32m' 11 YELLOW='\033[1;33m' 12 BLUE='\033[0;34m' 13 NC='\033[0m' # No Color 14else 15 RED='' 16 GREEN='' 17 YELLOW='' 18 BLUE='' 19 NC='' 20fi 21 22# Configuration 23SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 24LOG_DIR="${HOME}/.slopper/logs" 25LOG_FILE="${LOG_DIR}/slopper-$(date +%Y%m%d).log" 26WORKSPACE="${SLOPPER_WORKSPACE:-$(pwd)}" 27# For OCaml mode, use workspace-local .slopper directory to work with container mount 28HISTORY_DIR="${WORKSPACE}/.slopper/history" 29TODO_FILE="${WORKSPACE}/.slopper/TODO.md" 30FORCE_REBUILD=false 31QUIET=false 32MODE="" 33EXEC_PROMPT="" 34TODO_LIMIT="" 35 36# Ensure log and history directories exist 37mkdir -p "${LOG_DIR}" 38mkdir -p "${HISTORY_DIR}" 39mkdir -p "$(dirname "${TODO_FILE}")" 40 41# Logging function 42log() { 43 local level="$1" 44 shift 45 local message="$*" 46 local timestamp="$(date '+%Y-%m-%d %H:%M:%S')" 47 48 # Always log to file 49 echo "[$timestamp] [$level] $message" >> "${LOG_FILE}" 50 51 # Output to console based on quiet mode and level 52 if [ "$QUIET" != "true" ]; then 53 case "$level" in 54 ERROR) 55 echo -e "${RED}ERROR: $message${NC}" >&2 56 ;; 57 INFO) 58 echo -e "${GREEN}$message${NC}" 59 ;; 60 WARN) 61 echo -e "${YELLOW}$message${NC}" 62 ;; 63 DEBUG) 64 if [ "${DEBUG:-false}" = "true" ]; then 65 echo -e "${BLUE}DEBUG: $message${NC}" 66 fi 67 ;; 68 esac 69 elif [ "$level" = "ERROR" ]; then 70 # Even in quiet mode, show errors 71 echo "ERROR: $message" >&2 72 fi 73} 74 75# Log rotation (keep last 30 days) 76rotate_logs() { 77 find "${LOG_DIR}" -name "slopper-*.log" -type f -mtime +30 -delete 2>/dev/null || true 78 log DEBUG "Rotated old log files" 79} 80 81# Initialize TODO.md file if it doesn't exist 82init_todo_file() { 83 if [ ! -f "$TODO_FILE" ]; then 84 cat > "$TODO_FILE" << 'EOF' 85# Slopper Development TODOs 86 87## Pending Setup Improvements 88 89## Pending Code Fixes 90 91## Pending Features 92 93## Completed 94EOF 95 log DEBUG "Initialized TODO file: $TODO_FILE" 96 fi 97} 98 99# Add a TODO item to the file 100add_todo_item() { 101 local category="$1" 102 local description="$2" 103 local session_id="$3" 104 local timestamp="$(date '+%Y-%m-%d')" 105 106 init_todo_file 107 108 # Find the appropriate section and add the item 109 local section_line 110 case "$category" in 111 "setup"|"Setup") 112 section_line="## Pending Setup Improvements" 113 ;; 114 "fix"|"Fix"|"fixes"|"Fixes") 115 section_line="## Pending Code Fixes" 116 ;; 117 "feature"|"Feature"|"features"|"Features") 118 section_line="## Pending Features" 119 ;; 120 *) 121 section_line="## Pending Setup Improvements" 122 ;; 123 esac 124 125 # Use awk to insert the item after the section header 126 awk -v section="$section_line" -v item="- [ ] [$timestamp $session_id] $description" ' 127 $0 == section { 128 print $0 129 print item 130 next 131 } 132 { print } 133 ' "$TODO_FILE" > "$TODO_FILE.tmp" && mv "$TODO_FILE.tmp" "$TODO_FILE" 134 135 log INFO "Added TODO: $description" 136} 137 138# Mark a TODO item as completed 139complete_todo_item() { 140 local description="$1" 141 local timestamp="$(date '+%Y-%m-%d %H:%M')" 142 143 if [ ! -f "$TODO_FILE" ]; then 144 log WARN "TODO file does not exist" 145 return 1 146 fi 147 148 # Move completed item to Completed section 149 awk -v desc="$description" -v timestamp="$timestamp" ' 150 BEGIN { in_completed = 0; moved = 0 } 151 /^## Completed/ { in_completed = 1; print; next } 152 /^##/ && !/^## Completed/ { in_completed = 0; print; next } 153 /^- \[ \].*/ && index($0, desc) > 0 && !moved { 154 # Add to completed section 155 if (!in_completed) { 156 completed_items[++completed_count] = "- [x] " substr($0, 7) " (completed " timestamp ")" 157 } 158 moved = 1 159 next 160 } 161 in_completed && !moved && completed_count > 0 { 162 for (i = 1; i <= completed_count; i++) { 163 print completed_items[i] 164 } 165 completed_count = 0 166 } 167 { print } 168 END { 169 if (completed_count > 0) { 170 for (i = 1; i <= completed_count; i++) { 171 print completed_items[i] 172 } 173 } 174 } 175 ' "$TODO_FILE" > "$TODO_FILE.tmp" && mv "$TODO_FILE.tmp" "$TODO_FILE" 176 177 log INFO "Marked TODO as completed: $description" 178} 179 180# Get pending TODO items 181get_pending_todos() { 182 if [ ! -f "$TODO_FILE" ]; then 183 return 0 184 fi 185 186 grep "^- \[ \]" "$TODO_FILE" || true 187} 188 189# Parse Claude suggestions and add them to TODO.md 190process_claude_suggestions_to_todos() { 191 local suggestions_file="$1" 192 local session_id="$2" 193 194 if [ ! -f "$suggestions_file" ] || [ ! -s "$suggestions_file" ]; then 195 log WARN "No suggestions file to process" 196 return 0 197 fi 198 199 log INFO "Processing Claude suggestions and adding to TODO.md..." 200 201 # Simple parsing - look for bullet points or numbered lists 202 # This is a basic implementation - could be enhanced with better parsing 203 while IFS= read -r line; do 204 # Skip empty lines and headers 205 if [[ -z "$line" || "$line" =~ ^[[:space:]]*$ || "$line" =~ ^[[:space:]]*[=#-] ]]; then 206 continue 207 fi 208 209 # Look for lines that seem like actionable items 210 if [[ "$line" =~ ^[[:space:]]*[-*][[:space:]]+(.+)$ ]] || 211 [[ "$line" =~ ^[[:space:]]*[0-9]+\.[[:space:]]+(.+)$ ]]; then 212 213 local item="${BASH_REMATCH[1]}" 214 215 # Categorize the item based on keywords 216 local category="setup" 217 if [[ "$item" =~ (fix|bug|error|issue) ]]; then 218 category="fix" 219 elif [[ "$item" =~ (add|implement|feature|new) ]]; then 220 category="feature" 221 elif [[ "$item" =~ (install|setup|configure|environment) ]]; then 222 category="setup" 223 fi 224 225 add_todo_item "$category" "$item" "$session_id" 226 fi 227 done < "$suggestions_file" 228} 229 230# Function to check if devcontainer CLI is available 231check_devcontainer_cli() { 232 if ! command -v npx &> /dev/null; then 233 log ERROR "npx not found. Please install Node.js and npm" 234 exit 1 235 fi 236 237 if ! npx @devcontainers/cli --version &> /dev/null; then 238 log WARN "Installing @devcontainers/cli..." 239 npm install -g @devcontainers/cli 240 fi 241} 242 243# Function to check if Claude CLI is available in container 244check_claude_cli() { 245 local result 246 result=$(npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- which claude 2>/dev/null || echo "") 247 if [ -z "$result" ]; then 248 log ERROR "Claude CLI not found in devcontainer" 249 return 1 250 fi 251 return 0 252} 253 254# Function to ensure devcontainer is up 255ensure_devcontainer() { 256 log INFO "Ensuring devcontainer is ready..." 257 258 cd "${WORKSPACE}" 259 260 if [ ! -f ".devcontainer/devcontainer.json" ]; then 261 log ERROR ".devcontainer/devcontainer.json not found in ${WORKSPACE}" 262 exit 1 263 fi 264 265 # Check if we need to force rebuild 266 if [ "$FORCE_REBUILD" = "true" ]; then 267 log INFO "Force rebuilding devcontainer..." 268 npx @devcontainers/cli down --workspace-folder "${WORKSPACE}" 2>/dev/null || true 269 npx @devcontainers/cli up --workspace-folder "${WORKSPACE}" --remove-existing-container --build-no-cache 270 else 271 log INFO "Starting devcontainer..." 272 npx @devcontainers/cli up --workspace-folder "${WORKSPACE}" 273 fi 274 275 # Verify container is running 276 if ! npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- echo "Container ready" &>/dev/null; then 277 log ERROR "Failed to start devcontainer" 278 exit 1 279 fi 280 281 log INFO "Devcontainer is ready" 282} 283 284# Function to stop devcontainer 285stop_devcontainer() { 286 log DEBUG "Stopping devcontainer..." 287 npx @devcontainers/cli down --workspace-folder "${WORKSPACE}" 2>/dev/null || true 288} 289 290# Update mode - suitable for cron 291mode_update() { 292 log INFO "Running update mode" 293 rotate_logs 294 295 check_devcontainer_cli 296 297 # Force rebuild for updates to ensure fresh environment 298 FORCE_REBUILD=true 299 ensure_devcontainer 300 301 # Run a test command to verify everything works 302 if npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- echo "Update test successful" &>/dev/null; then 303 log INFO "Update completed successfully" 304 stop_devcontainer 305 exit 0 306 else 307 log ERROR "Update failed - container test failed" 308 stop_devcontainer 309 exit 1 310 fi 311} 312 313# Execute mode - run a single Claude prompt 314mode_exec() { 315 local prompt="$1" 316 317 if [ -z "$prompt" ]; then 318 log ERROR "No prompt provided for exec mode" 319 exit 1 320 fi 321 322 log INFO "Running exec mode with prompt: $prompt" 323 324 check_devcontainer_cli 325 ensure_devcontainer 326 327 # Check if Claude is available 328 if ! check_claude_cli; then 329 stop_devcontainer 330 exit 1 331 fi 332 333 # Execute Claude with the prompt in non-interactive mode 334 log INFO "Executing Claude prompt..." 335 336 local claude_output 337 local exit_code 338 339 # Create a temporary file for the prompt to handle complex prompts safely 340 local prompt_file=$(mktemp) 341 echo "$prompt" > "$prompt_file" 342 343 # Run Claude in non-interactive mode 344 claude_output=$(npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \ 345 bash -c "cd /workspace && claude --dangerously-skip-permissions < '$prompt_file'" 2>&1) || exit_code=$? 346 347 rm -f "$prompt_file" 348 349 if [ -z "$exit_code" ] || [ "$exit_code" -eq 0 ]; then 350 echo "$claude_output" 351 log INFO "Claude execution completed successfully" 352 stop_devcontainer 353 exit 0 354 else 355 echo "$claude_output" >&2 356 log ERROR "Claude execution failed with exit code: ${exit_code:-unknown}" 357 stop_devcontainer 358 exit ${exit_code:-1} 359 fi 360} 361 362# Shell mode - interactive shell in devcontainer 363mode_shell() { 364 log INFO "Running shell mode" 365 366 check_devcontainer_cli 367 ensure_devcontainer 368 369 log INFO "Starting interactive shell in devcontainer..." 370 371 # Start interactive bash session 372 npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- bash 373 374 local exit_code=$? 375 376 stop_devcontainer 377 378 if [ $exit_code -eq 0 ]; then 379 log INFO "Shell session ended normally" 380 else 381 log WARN "Shell session ended with exit code: $exit_code" 382 fi 383 384 exit $exit_code 385} 386 387# Claude interactive mode 388mode_claude() { 389 log INFO "Running Claude interactive mode" 390 391 check_devcontainer_cli 392 ensure_devcontainer 393 394 if ! check_claude_cli; then 395 stop_devcontainer 396 exit 1 397 fi 398 399 log INFO "Starting interactive Claude session..." 400 401 # Start interactive Claude session 402 npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \ 403 bash -c "cd /workspace && claude" 404 405 local exit_code=$? 406 407 stop_devcontainer 408 409 if [ $exit_code -eq 0 ]; then 410 log INFO "Claude session ended normally" 411 else 412 log WARN "Claude session ended with exit code: $exit_code" 413 fi 414 415 exit $exit_code 416} 417 418# OCaml mode - interactive shell with history logging and Claude integration 419mode_ocaml() { 420 log INFO "Running OCaml development mode with history logging" 421 422 check_devcontainer_cli 423 ensure_devcontainer 424 425 # Create session ID and file paths 426 local session_id="ocaml-$(date +%Y%m%d-%H%M%S)" 427 local typescript_file="${HISTORY_DIR}/${session_id}.typescript" 428 local clean_log="${HISTORY_DIR}/${session_id}.log" 429 430 log INFO "Starting OCaml development session: $session_id" 431 log INFO "Session will be recorded to: $typescript_file" 432 433 # Create session recording script 434 local tracker_script=$(mktemp) 435 cat > "$tracker_script" << 'EOF' 436#!/bin/bash 437 438# OCaml development session with complete recording using script 439SESSION_ID="$1" 440TYPESCRIPT_FILE="$2" 441CLEAN_LOG="$3" 442 443echo "=== OCaml Development Session Started ===" 444echo "Session ID: $SESSION_ID" 445echo "Timestamp: $(date)" 446echo "Workspace: $(pwd)" 447echo "" 448 449# Check OCaml installation 450if command -v ocaml >/dev/null 2>&1; then 451 echo "OCaml version: $(ocaml -version)" 452else 453 echo "Note: OCaml not found - you may need to install it" 454fi 455 456if command -v opam >/dev/null 2>&1; then 457 echo "OPAM version: $(opam --version)" 458 echo "Current switch: $(opam switch show 2>/dev/null || echo 'none')" 459else 460 echo "Note: OPAM not found - you may need to install it" 461fi 462 463echo "" 464echo "OCaml Development Environment Ready!" 465echo "- Complete session recording enabled" 466echo "- Session ID: $SESSION_ID" 467echo "- Type 'exit' when done to process with Claude" 468echo "" 469 470# Record the entire interactive session using script 471# -f: flush output immediately 472# -q: quiet mode (don't show start/stop messages) 473script -f -q "$TYPESCRIPT_FILE" -c "bash --login -i" 474 475echo "" 476echo "=== OCaml Development Session Ended ===" 477echo "Timestamp: $(date)" 478 479# Clean the typescript file to remove colors and control sequences 480if command -v col >/dev/null 2>&1; then 481 echo "Cleaning session log..." 482 cat "$TYPESCRIPT_FILE" | col -b > "$CLEAN_LOG" 483 echo "Clean session log saved to: $CLEAN_LOG" 484else 485 echo "Warning: 'col' command not found, using raw typescript" 486 cp "$TYPESCRIPT_FILE" "$CLEAN_LOG" 487fi 488EOF 489 490 chmod +x "$tracker_script" 491 492 # Ensure the history directory exists in the container first 493 npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \ 494 mkdir -p "/home/node/.slopper/history" 495 496 # Copy tracker script to container and run the tracked session 497 local container_script="/tmp/tracker_script.sh" 498 npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \ 499 bash -c "cat > '$container_script' << 'EOF' 500$(cat "$tracker_script") 501EOF" 502 503 npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \ 504 chmod +x "$container_script" 505 506 # Run with container paths for the session files 507 local container_typescript="/home/node/.slopper/history/${session_id}.typescript" 508 local container_clean_log="/home/node/.slopper/history/${session_id}.log" 509 510 npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \ 511 bash "$container_script" "$session_id" "$container_typescript" "$container_clean_log" 512 513 local exit_code=$? 514 515 # Clean up tracker script 516 rm -f "$tracker_script" 517 518 log INFO "OCaml session ended. Processing with Claude..." 519 520 # Check if we have session data to process (files are accessible via the mount) 521 if [ -f "$clean_log" ] && [ -s "$clean_log" ]; then 522 process_ocaml_session_with_claude "$session_id" "$typescript_file" "$clean_log" 523 else 524 log WARN "No session data captured, skipping Claude processing" 525 fi 526 527 stop_devcontainer 528 529 if [ $exit_code -eq 0 ]; then 530 log INFO "OCaml development session completed successfully" 531 else 532 log WARN "OCaml development session ended with exit code: $exit_code" 533 fi 534 535 exit $exit_code 536} 537 538# Function to process OCaml session with Claude 539process_ocaml_session_with_claude() { 540 local session_id="$1" 541 local typescript_file="$2" 542 local clean_log="$3" 543 544 log INFO "Processing OCaml session $session_id with Claude..." 545 log DEBUG "Session files - typescript: $typescript_file, clean_log: $clean_log" 546 547 # Check file sizes for debugging 548 if [ -f "$clean_log" ]; then 549 local log_size=$(wc -l < "$clean_log" 2>/dev/null || echo "0") 550 log DEBUG "Clean log file exists with $log_size lines" 551 log DEBUG "First few lines of clean log:" 552 head -10 "$clean_log" 2>/dev/null | while read line; do 553 log DEBUG " $line" 554 done 555 else 556 log WARN "Clean log file does not exist: $clean_log" 557 return 1 558 fi 559 560 # Check if Claude is available in the devcontainer 561 log DEBUG "Checking for Claude CLI in devcontainer..." 562 if ! npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- which claude &>/dev/null; then 563 log WARN "Claude CLI not found in devcontainer, skipping setup-ocaml script refinement" 564 return 0 565 fi 566 log DEBUG "Claude CLI found in devcontainer" 567 568 # Create analysis prompt (inside container paths) 569 local analysis_file="/home/node/.slopper/history/${session_id}.analysis" 570 log DEBUG "Creating analysis file: $analysis_file" 571 npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \ 572 bash -c "cat > '$analysis_file' << 'EOF' 573Please analyze this OCaml development session and help refine the setup-ocaml script. 574 575SESSION DETAILS: 576- Session ID: $session_id 577- Workspace: /workspace 578 579COMPLETE SESSION RECORDING: 580\$(if [ -f \"/home/node/.slopper/history/${session_id}.log\" ]; then cat \"/home/node/.slopper/history/${session_id}.log\"; else echo \"No session recording available\"; fi) 581 582CURRENT setup-ocaml.sh SCRIPT: 583\$(cat \"/usr/local/bin/setup-ocaml.sh\") 584 585TASK: 586Based on the complete session recording above, please analyze my OCaml development workflow and suggest improvements to the setup-ocaml.sh script. Focus on: 587 5881. Any missing dependencies or tools that were needed during the session 5892. Additional opam packages that should be pre-installed 5903. Environment setup that could be automated 5914. Common development patterns that could be scripted 5925. Any pins or specific package versions that were useful 5936. Error patterns that suggest missing configurations 594 595The session recording shows everything I typed and all output, so you can see the complete workflow including any errors, installations, or manual setup steps. 596 597Please provide specific, actionable improvements that would make OCaml development setup more efficient. In particular, pay attention to the .devcontainer files. 598EOF'" 599 600 log DEBUG "Analysis file created, verifying it exists..." 601 npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \ 602 bash -c "if [ -f '$analysis_file' ]; then echo 'Analysis file exists, size:' \$(wc -c < '$analysis_file'); else echo 'Analysis file missing!'; fi" || log WARN "Failed to verify analysis file" 603 604 log INFO "Sending session analysis to Claude..." 605 606 # Run Claude analysis inside the devcontainer 607 # Since .slopper/history is bind-mounted, write directly to /workspace/.slopper/history 608 local claude_output_container="/workspace/.slopper/history/${session_id}.claude-suggestions" 609 local claude_output_host="${HISTORY_DIR}/${session_id}.claude-suggestions" 610 611 log DEBUG "Running Claude with analysis file, output will go to: $claude_output_container (mounted path)" 612 log DEBUG "Claude command: cd /workspace && claude -p '$analysis_file' > '$claude_output_container'" 613 614 if npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \ 615 bash -c "cd /workspace && claude --dangerously-skip-permissions -p '$analysis_file' > '$claude_output_container' 2>&1"; then 616 log INFO "Claude analysis completed successfully" 617 618 # Check if output file exists and has content (directly on host via mount) 619 if [ -f "$claude_output_host" ]; then 620 local output_size=$(wc -c < "$claude_output_host" 2>/dev/null || echo "0") 621 log DEBUG "Claude output file size: $output_size bytes" 622 else 623 local output_size=0 624 log DEBUG "Claude output file does not exist on host" 625 fi 626 627 if [ "$output_size" -gt 0 ]; then 628 # Output is already on host via bind mount, display it and add to TODO.md 629 echo "" 630 echo "=== Claude Analysis and Suggestions ===" 631 cat "$claude_output_host" 632 echo "" 633 echo "Analysis saved to: $claude_output_host" 634 635 # Process suggestions and add to TODO.md 636 process_claude_suggestions_to_todos "$claude_output_host" "$session_id" 637 echo "Suggestions added to TODO.md: $TODO_FILE" 638 else 639 log WARN "Claude output file is empty or missing" 640 log DEBUG "Attempting to show stderr from Claude command..." 641 npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \ 642 bash -c "if [ -f '$claude_output_container' ]; then cat '$claude_output_container'; else echo 'Output file does not exist'; fi" 643 fi 644 else 645 local exit_code=$? 646 log ERROR "Claude analysis failed with exit code: $exit_code" 647 648 # Try to get error output 649 if npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \ 650 test -f "$claude_output_container"; then 651 echo "Error output:" 652 npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \ 653 cat "$claude_output_container" 654 else 655 log DEBUG "No error output file found" 656 fi 657 658 # Additional debugging - check if Claude is actually available 659 log DEBUG "Verifying Claude CLI accessibility:" 660 npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \ 661 bash -c "which claude; claude --version 2>&1 || echo 'Claude version check failed'" 662 fi 663 664 # Clean up analysis file (but keep it for debugging if Claude failed) 665 if [ "$output_size" -gt 0 ] 2>/dev/null; then 666 log DEBUG "Cleaning up analysis file (Claude succeeded)" 667 npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \ 668 rm -f "$analysis_file" 669 else 670 log DEBUG "Keeping analysis file for debugging (Claude failed or produced no output)" 671 log DEBUG "Analysis file preserved at: $analysis_file" 672 fi 673} 674 675# Show TODO.md contents 676mode_show_todos() { 677 log INFO "Displaying current TODOs" 678 679 if [ ! -f "$TODO_FILE" ]; then 680 echo "No TODO file found. Run with --ocaml mode first to generate suggestions." 681 exit 0 682 fi 683 684 cat "$TODO_FILE" 685 exit 0 686} 687 688# Clear completed TODOs from TODO.md 689mode_clear_todos() { 690 log INFO "Clearing completed TODOs" 691 692 if [ ! -f "$TODO_FILE" ]; then 693 log WARN "No TODO file found" 694 exit 0 695 fi 696 697 # Create a new file without completed items 698 awk ' 699 BEGIN { in_completed = 0; skip_empty = 0 } 700 /^## Completed/ { 701 in_completed = 1 702 print $0 703 skip_empty = 1 704 next 705 } 706 /^##/ && !/^## Completed/ { 707 in_completed = 0 708 skip_empty = 0 709 print 710 next 711 } 712 in_completed && /^- \[x\]/ { next } 713 in_completed && /^[[:space:]]*$/ && skip_empty { next } 714 in_completed { skip_empty = 0 } 715 { print } 716 ' "$TODO_FILE" > "$TODO_FILE.tmp" && mv "$TODO_FILE.tmp" "$TODO_FILE" 717 718 log INFO "Completed TODOs cleared from $TODO_FILE" 719 exit 0 720} 721 722# Implement pending TODOs non-interactively 723mode_implement_todos() { 724 log INFO "Implementing pending TODOs" 725 726 check_devcontainer_cli 727 ensure_devcontainer 728 729 if ! check_claude_cli; then 730 stop_devcontainer 731 exit 1 732 fi 733 734 if [ ! -f "$TODO_FILE" ]; then 735 echo "No TODO file found. Run with --ocaml mode first to generate suggestions." 736 stop_devcontainer 737 exit 0 738 fi 739 740 # Get pending TODOs 741 local pending_todos 742 pending_todos=$(get_pending_todos) 743 744 if [ -z "$pending_todos" ]; then 745 echo "No pending TODOs to implement." 746 stop_devcontainer 747 exit 0 748 fi 749 750 local total_todos=$(echo "$pending_todos" | wc -l) 751 local implemented=0 752 local failed=0 753 754 # Apply limit if specified 755 if [ -n "$TODO_LIMIT" ] && [ "$TODO_LIMIT" -gt 0 ]; then 756 pending_todos=$(echo "$pending_todos" | head -n "$TODO_LIMIT") 757 total_todos=$(echo "$pending_todos" | wc -l) 758 log INFO "Processing first $total_todos TODOs (limited by --limit $TODO_LIMIT)" 759 fi 760 761 log INFO "Found $total_todos pending TODOs to implement" 762 763 # Process each TODO item 764 local line_num=0 765 while IFS= read -r todo_line; do 766 line_num=$((line_num + 1)) 767 768 if [ -z "$todo_line" ]; then 769 continue 770 fi 771 772 # Extract the description (everything after the checkbox and metadata) 773 local description 774 if [[ "$todo_line" =~ \[[0-9-]+\ [^]]+\][[:space:]]*(.+)$ ]]; then 775 description="${BASH_REMATCH[1]}" 776 else 777 # Fallback parsing 778 description=$(echo "$todo_line" | sed 's/^- \[ \][[:space:]]*//') 779 fi 780 781 if [ -z "$description" ]; then 782 log WARN "Could not parse TODO description from: $todo_line" 783 continue 784 fi 785 786 echo "" 787 echo "=== Implementing TODO $line_num/$total_todos ===" 788 echo "Description: $description" 789 echo "" 790 791 # Create focused implementation prompt 792 local impl_prompt="Implement this specific task: $description 793 794Please make the necessary changes to implement this requirement. Focus on: 7951. Making the minimal necessary changes 7962. Following existing code patterns and conventions 7973. Testing that the implementation works 7984. Providing clear output about what was changed 799 800If this is a setup/configuration task, make the changes to the appropriate files. 801If this is a code fix, identify and fix the specific issue. 802If this is a feature implementation, add the functionality as described." 803 804 log INFO "Implementing: $description" 805 806 # Run Claude implementation inside devcontainer 807 # Use a heredoc to pass the prompt directly to claude inside the container 808 local claude_exit_code=0 809 local claude_output 810 811 claude_output=$(npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \ 812 bash -c "cd /workspace && claude --dangerously-skip-permissions << 'EOF' 813$impl_prompt 814EOF" 2>&1) || claude_exit_code=$? 815 816 if [ $claude_exit_code -eq 0 ]; then 817 echo "$claude_output" 818 echo "" 819 log INFO "Successfully implemented: $description" 820 821 # Mark as completed in TODO.md 822 complete_todo_item "$description" 823 implemented=$((implemented + 1)) 824 825 # Optional: Create a git commit for this change 826 if command -v git &>/dev/null; then 827 local commit_msg="slopper: implement TODO - $description" 828 if npx @devcontainers/cli exec --workspace-folder "${WORKSPACE}" -- \ 829 bash -c "cd /workspace && git add -A && git diff --cached --quiet || git commit -m '$commit_msg'" 2>/dev/null; then 830 log DEBUG "Created commit for TODO implementation" 831 fi 832 fi 833 else 834 echo "Implementation failed:" 835 echo "$claude_output" 836 echo "" 837 log ERROR "Failed to implement: $description" 838 failed=$((failed + 1)) 839 fi 840 841 # Brief pause between implementations 842 sleep 1 843 844 done <<< "$pending_todos" 845 846 echo "" 847 echo "=== Implementation Summary ===" 848 echo "Total TODOs processed: $total_todos" 849 echo "Successfully implemented: $implemented" 850 echo "Failed: $failed" 851 echo "" 852 853 if [ $implemented -gt 0 ]; then 854 echo "Updated TODO file: $TODO_FILE" 855 fi 856 857 stop_devcontainer 858 859 if [ $failed -eq 0 ]; then 860 log INFO "All TODO implementations completed successfully" 861 exit 0 862 else 863 log WARN "Some TODO implementations failed" 864 exit 1 865 fi 866} 867 868# Help function 869show_help() { 870 cat << EOF 871slopper - Unified Claude Code devcontainer workflow manager 872 873Usage: $(basename "$0") [OPTIONS] 874 875MODES (mutually exclusive): 876 -u, --update Update/rebuild devcontainer (suitable for cron) 877 -e, --exec PROMPT Execute a single Claude prompt non-interactively 878 -s, --shell Open interactive shell in devcontainer (default) 879 -c, --claude Start interactive Claude session 880 -o, --ocaml OCaml development mode with history logging 881 --implement-todos Implement pending TODOs from TODO.md non-interactively 882 --show-todos Display current TODO.md contents 883 --clear-todos Clear completed TODOs from TODO.md 884 885OPTIONS: 886 -f, --force Force rebuild devcontainer from scratch 887 -w, --workspace PATH Set workspace directory (default: current directory) 888 -q, --quiet Minimal output (errors only) 889 -h, --help Show this help message 890 --debug Enable debug output 891 --limit N Limit number of TODOs to implement (for --implement-todos) 892 893ENVIRONMENT: 894 SLOPPER_WORKSPACE Default workspace directory 895 896USE CASES: 897 Development: 898 ./slopper # Quick shell into dev environment 899 ./slopper --claude # Interactive Claude coding session 900 ./slopper --ocaml # OCaml dev mode with history logging 901 902 Automation: 903 ./slopper -e "fix the bug in main.ml" # CI/CD code fixes 904 ./slopper -e "write tests for module X" # Automated test generation 905 ./slopper --implement-todos # Implement all pending TODO suggestions 906 ./slopper --implement-todos --limit 3 # Implement first 3 TODOs 907 908 Maintenance: 909 0 3 * * * slopper -u -q # Cron: nightly container updates 910 ./slopper -f -s # Force rebuild after config changes 911 ./slopper --show-todos # View current TODO list 912 ./slopper --clear-todos # Clean up completed TODOs 913 914 Scripting: 915 SLOPPER_WORKSPACE=/project slopper -e "refactor all Python files" 916 for dir in */; do slopper -w "\$dir" -e "update dependencies"; done 917 918EXAMPLES: 919 $(basename "$0") # Interactive shell (default) 920 $(basename "$0") --update --quiet # Silent update for cron 921 $(basename "$0") --exec "analyze security" # One-off Claude task 922 $(basename "$0") --claude # Interactive Claude session 923 $(basename "$0") --force --shell # Rebuild and enter shell 924 925Logs: ${LOG_DIR}/ 926EOF 927} 928 929# Parse command line arguments 930while [[ $# -gt 0 ]]; do 931 case $1 in 932 -u|--update) 933 [ -n "$MODE" ] && { log ERROR "Multiple modes specified"; exit 1; } 934 MODE="update" 935 shift 936 ;; 937 -e|--exec) 938 [ -n "$MODE" ] && { log ERROR "Multiple modes specified"; exit 1; } 939 MODE="exec" 940 EXEC_PROMPT="$2" 941 shift 2 942 ;; 943 -s|--shell) 944 [ -n "$MODE" ] && { log ERROR "Multiple modes specified"; exit 1; } 945 MODE="shell" 946 shift 947 ;; 948 -c|--claude) 949 [ -n "$MODE" ] && { log ERROR "Multiple modes specified"; exit 1; } 950 MODE="claude" 951 shift 952 ;; 953 -o|--ocaml) 954 [ -n "$MODE" ] && { log ERROR "Multiple modes specified"; exit 1; } 955 MODE="ocaml" 956 shift 957 ;; 958 --implement-todos) 959 [ -n "$MODE" ] && { log ERROR "Multiple modes specified"; exit 1; } 960 MODE="implement_todos" 961 shift 962 ;; 963 --show-todos) 964 [ -n "$MODE" ] && { log ERROR "Multiple modes specified"; exit 1; } 965 MODE="show_todos" 966 shift 967 ;; 968 --clear-todos) 969 [ -n "$MODE" ] && { log ERROR "Multiple modes specified"; exit 1; } 970 MODE="clear_todos" 971 shift 972 ;; 973 --limit) 974 TODO_LIMIT="$2" 975 shift 2 976 ;; 977 -f|--force) 978 FORCE_REBUILD=true 979 shift 980 ;; 981 -w|--workspace) 982 WORKSPACE="$2" 983 shift 2 984 ;; 985 -q|--quiet) 986 QUIET=true 987 shift 988 ;; 989 --debug) 990 DEBUG=true 991 shift 992 ;; 993 -h|--help) 994 show_help 995 exit 0 996 ;; 997 *) 998 log ERROR "Unknown option: $1" 999 echo "Use --help for usage information" 1000 exit 1 1001 ;; 1002 esac 1003done 1004 1005# Default to shell mode if no mode specified 1006[ -z "$MODE" ] && MODE="shell" 1007 1008# Validate workspace 1009WORKSPACE="$(cd "${WORKSPACE}" 2>/dev/null && pwd)" || { 1010 log ERROR "Invalid workspace directory: ${WORKSPACE}" 1011 exit 1 1012} 1013 1014log DEBUG "Starting slopper in $MODE mode" 1015log DEBUG "Workspace: $WORKSPACE" 1016log DEBUG "Force rebuild: $FORCE_REBUILD" 1017 1018# Execute the appropriate mode 1019case "$MODE" in 1020 update) 1021 mode_update 1022 ;; 1023 exec) 1024 mode_exec "$EXEC_PROMPT" 1025 ;; 1026 shell) 1027 mode_shell 1028 ;; 1029 claude) 1030 mode_claude 1031 ;; 1032 ocaml) 1033 mode_ocaml 1034 ;; 1035 implement_todos) 1036 mode_implement_todos 1037 ;; 1038 show_todos) 1039 mode_show_todos 1040 ;; 1041 clear_todos) 1042 mode_clear_todos 1043 ;; 1044 *) 1045 log ERROR "Invalid mode: $MODE" 1046 exit 1 1047 ;; 1048esac