diff --git a/.dmux-hooks/AGENTS.md b/.dmux-hooks/AGENTS.md new file mode 100644 index 00000000..1a5001bb --- /dev/null +++ b/.dmux-hooks/AGENTS.md @@ -0,0 +1,445 @@ +# dmux Hooks System - Agent Reference + +**Auto-generated documentation for AI agents** + +This document contains everything an AI agent needs to create, modify, and understand dmux hooks. It is automatically generated from the dmux source code and embedded in the binary. + +## What You're Working On + +You are editing hooks for **dmux**, a tmux pane manager that creates AI-powered development workflows. Each pane runs in its own git worktree with an AI agent. + +## Your Goal + +Create executable bash scripts in `.dmux-hooks/` that run automatically at key lifecycle events. + +## Quick Start + +1. **Create a hook file**: `touch .dmux-hooks/worktree_created` +2. **Make it executable**: `chmod +x .dmux-hooks/worktree_created` +3. **Add shebang**: Start with `#!/bin/bash` +4. **Use environment variables**: Access `$DMUX_ROOT`, `$DMUX_WORKTREE_PATH`, etc. +5. **Test it**: Set env vars manually and run the script + +## Hook Execution Model + +- **Mostly non-blocking**: Most hooks run in background (detached processes) +- **Bootstrap-gated**: `worktree_created` blocks agent launch so setup can finish first, with no fixed timeout +- **Live bootstrap output**: During `worktree_created`, stdout/stderr is streamed into the new pane's setup UI +- **Failure behavior**: Background hook errors are logged; gated hooks can abort the operation +- **Environment-based**: All context passed via environment variables +- **Version controlled**: Hooks in `.dmux-hooks/` are shared with team +- **Priority resolution**: `.dmux-hooks/` → `.dmux/hooks/` → `~/.dmux/hooks/` + +## Available Hooks + +### Pane Lifecycle Hooks + +| Hook | When | Common Use Cases | +|------|------|------------------| +| `before_pane_create` | Before pane creation | Validation, notifications, pre-flight checks | +| `pane_created` | After pane, before worktree | Configure tmux settings, prepare environment | +| `worktree_created` | After worktree creation, before agent launch | Install deps, copy configs, setup git | +| `before_pane_close` | Before closing | Save state, backup uncommitted work | +| `pane_closed` | After closed | Cleanup resources, analytics, notifications | + +### Worktree Lifecycle Hooks + +| Hook | When | Common Use Cases | +|------|------|------------------| +| `before_worktree_remove` | Before worktree removal | Archive worktree, save artifacts | +| `worktree_removed` | After worktree removed | Cleanup external references | + +### Merge Lifecycle Hooks + +| Hook | When | Common Use Cases | +|------|------|------------------| +| `pre_merge` | Before merge operation | Run final tests, create backups | +| `post_merge` | After successful merge | Deploy, close issues, notify team | + +### Interactive Hooks (with HTTP callbacks) + +| Hook | When | Common Use Cases | +|------|------|------------------| +| `run_test` | When tests triggered | Run test suite, report status via HTTP | +| `run_dev` | When dev server triggered | Start dev server, create tunnel, report URL | + + +## Environment Variables + +### Always Available +```bash +DMUX_ROOT="/path/to/project" # Project root directory +DMUX_SERVER_PORT="3142" # HTTP server port +``` + +### Pane Context (most hooks) +```bash +DMUX_PANE_ID="dmux-1234567890" # dmux pane identifier +DMUX_SLUG="fix-auth-bug" # Branch/worktree name +DMUX_PROMPT="Fix authentication bug" # User's prompt +DMUX_AGENT="claude" # Agent type (registry id, e.g. claude, codex, opencode) +DMUX_TMUX_PANE_ID="%38" # tmux pane ID +``` + +### Worktree Context +```bash +DMUX_WORKTREE_PATH="/path/.dmux/worktrees/fix-auth-bug" +DMUX_BRANCH="fix-auth-bug" # Same as slug +``` + +### Bootstrap Progress Context +```bash +DMUX_PROGRESS="1" # Set when output is shown in the new pane setup UI +DMUX_STATUS_PREFIX="DMUX_STATUS:" # Prefix for clean status messages +``` + +`worktree_created` can emit progress while it runs. Any stdout/stderr line is shown in the setup UI; lines prefixed with `$DMUX_STATUS_PREFIX` are displayed without the prefix. + +```bash +status() { + if [ "${DMUX_PROGRESS:-0}" = "1" ]; then + echo "${DMUX_STATUS_PREFIX:-DMUX_STATUS:} $*" + else + echo "[Hook] $*" + fi +} +``` + +### Merge Context +```bash +DMUX_TARGET_BRANCH="main" # Branch being merged into +``` + +## HTTP Callback API + +Interactive hooks (`run_test` and `run_dev`) can update dmux UI via HTTP. + +### Update Test Status +```bash +curl -X PUT "http://localhost:$DMUX_SERVER_PORT/api/panes/$DMUX_PANE_ID/test" -H "Content-Type: application/json" -d '{"status": "running", "output": "optional test output"}' + +# Status values: "running" | "passed" | "failed" +``` + +### Update Dev Server +```bash +curl -X PUT "http://localhost:$DMUX_SERVER_PORT/api/panes/$DMUX_PANE_ID/dev" -H "Content-Type: application/json" -d '{"status": "running", "url": "http://localhost:3000"}' + +# Status values: "running" | "stopped" +# url: Can be localhost or tunnel URL (ngrok, cloudflared, etc.) +``` + +## Common Patterns + +### Pattern 1: Install Dependencies +```bash +#!/bin/bash +# .dmux-hooks/worktree_created + +cd "$DMUX_WORKTREE_PATH" + +status() { + if [ "${DMUX_PROGRESS:-0}" = "1" ]; then + echo "${DMUX_STATUS_PREFIX:-DMUX_STATUS:} $*" + else + echo "[Hook] $*" + fi +} + +if [ -f "pnpm-lock.yaml" ]; then + status "Installing dependencies with pnpm" + pnpm install --prefer-offline +elif [ -f "package-lock.json" ]; then + status "Installing dependencies with npm" + npm install +elif [ -f "yarn.lock" ]; then + status "Installing dependencies with yarn" + yarn install +elif [ -f "Gemfile" ]; then + status "Installing gems" + bundle install +elif [ -f "requirements.txt" ]; then + status "Installing Python dependencies" + pip install -r requirements.txt +elif [ -f "Cargo.toml" ]; then + status "Building Rust project" + cargo build +fi +``` + +### Pattern 2: Copy Configuration +```bash +#!/bin/bash +# .dmux-hooks/worktree_created + +# Copy environment file +if [ -f "$DMUX_ROOT/.env.local" ]; then + cp "$DMUX_ROOT/.env.local" "$DMUX_WORKTREE_PATH/.env.local" +fi + +# Copy other config files +for file in .env.development .npmrc .yarnrc; do + if [ -f "$DMUX_ROOT/$file" ]; then + cp "$DMUX_ROOT/$file" "$DMUX_WORKTREE_PATH/$file" + fi +done +``` + +### Pattern 3: Run Tests with Status Updates +```bash +#!/bin/bash +# .dmux-hooks/run_test + +set -e +cd "$DMUX_WORKTREE_PATH" +API="http://localhost:$DMUX_SERVER_PORT/api/panes/$DMUX_PANE_ID/test" + +# Update: starting +curl -s -X PUT "$API" -H "Content-Type: application/json" -d '{"status": "running"}' > /dev/null + +# Run tests and capture output +OUTPUT_FILE="/tmp/dmux-test-$DMUX_PANE_ID.txt" +if pnpm test > "$OUTPUT_FILE" 2>&1; then + STATUS="passed" +else + STATUS="failed" +fi + +# Get output (truncate if too long) +OUTPUT=$(head -c 5000 "$OUTPUT_FILE") + +# Update: complete +curl -s -X PUT "$API" -H "Content-Type: application/json" -d "$(jq -n --arg status "$STATUS" --arg output "$OUTPUT" '{status: $status, output: $output}')" > /dev/null + +rm -f "$OUTPUT_FILE" +``` + +### Pattern 4: Dev Server with Tunnel +```bash +#!/bin/bash +# .dmux-hooks/run_dev + +set -e +cd "$DMUX_WORKTREE_PATH" +API="http://localhost:$DMUX_SERVER_PORT/api/panes/$DMUX_PANE_ID/dev" + +# Start dev server in background +LOG_FILE="/tmp/dmux-dev-$DMUX_PANE_ID.log" +pnpm dev > "$LOG_FILE" 2>&1 & +DEV_PID=$! + +# Wait for server to start +sleep 5 + +# Detect port from logs +PORT=$(grep -oP 'localhost:Kd+' "$LOG_FILE" | head -1) +[ -z "$PORT" ] && PORT=3000 + +# Optional: Create tunnel with cloudflared +if command -v cloudflared &> /dev/null; then + TUNNEL=$(cloudflared tunnel --url "http://localhost:$PORT" 2>&1 | grep -oP 'https://[a-z0-9-]+.trycloudflare.com' | head -1) + URL="${TUNNEL:-http://localhost:$PORT}" +else + URL="http://localhost:$PORT" +fi + +# Report status +curl -s -X PUT "$API" -H "Content-Type: application/json" -d "{"status": "running", "url": "$URL"}" > /dev/null + +echo "[Hook] Dev server running at $URL (PID: $DEV_PID)" +``` + +### Pattern 5: Post-Merge Deployment +```bash +#!/bin/bash +# .dmux-hooks/post_merge + +set -e +cd "$DMUX_ROOT" + +# Only deploy from main/master +if [ "$DMUX_TARGET_BRANCH" != "main" ] && [ "$DMUX_TARGET_BRANCH" != "master" ]; then + exit 0 +fi + +# Push to remote +git push origin "$DMUX_TARGET_BRANCH" + +# Trigger deployment (example: Vercel) +if [ -n "$VERCEL_TOKEN" ]; then + curl -s -X POST "https://api.vercel.com/v1/deployments" -H "Authorization: Bearer $VERCEL_TOKEN" -H "Content-Type: application/json" -d '{"name": "my-project"}' > /dev/null +fi + +# Close GitHub issue if prompt contains #123 +ISSUE=$(echo "$DMUX_PROMPT" | grep -oP '#Kd+' | head -1) +if [ -n "$ISSUE" ] && command -v gh &> /dev/null; then + gh issue close "$ISSUE" -c "Resolved in $DMUX_SLUG, merged to $DMUX_TARGET_BRANCH" 2>/dev/null || true +fi +``` + +## Best Practices + +1. **Always start with shebang**: `#!/bin/bash` +2. **Set error handling**: `set -e` (exit on error) +3. **Make executable**: `chmod +x .dmux-hooks/hook_name` +4. **Background long operations**: Append `&` to avoid blocking +5. **Check for required tools**: `command -v tool &> /dev/null` +6. **Log for debugging**: `echo "[Hook] message" >> "$DMUX_ROOT/.dmux/hooks.log"` +7. **Handle missing vars gracefully**: `[ -z "$VAR" ] && exit 0` +8. **Use silent curl**: `curl -s` to avoid noise in logs +9. **Clean up temp files**: Remove files in `/tmp/` +10. **Test before committing**: Run hooks manually with mock env vars + +## Testing Hooks + +### Manual Testing +```bash +# 1. Set environment variables +export DMUX_ROOT="$(pwd)" +export DMUX_PANE_ID="test-pane" +export DMUX_SLUG="test-branch" +export DMUX_WORKTREE_PATH="$(pwd)" +export DMUX_SERVER_PORT="3142" +export DMUX_AGENT="claude" +export DMUX_PROMPT="Test prompt" + +# 2. Run hook directly +./.dmux-hooks/worktree_created + +# 3. Check exit code +echo $? # Should be 0 for success +``` + +### Syntax Check +```bash +# Check for syntax errors without running +bash -n ./.dmux-hooks/worktree_created +``` + +### Shellcheck (if available) +```bash +shellcheck ./.dmux-hooks/worktree_created +``` + +## Project Context Analysis + +Before creating hooks, analyze these files in the project: + +### Package Manager Detection +```bash +# Check which package manager is used +if [ -f "pnpm-lock.yaml" ]; then + # Use: pnpm install, pnpm test, pnpm dev +elif [ -f "package-lock.json" ]; then + # Use: npm install, npm test, npm run dev +elif [ -f "yarn.lock" ]; then + # Use: yarn install, yarn test, yarn dev +fi +``` + +### Test Command Discovery +```bash +# Read package.json to find test command +cat package.json | grep '"test"' +# Or with jq: +jq -r '.scripts.test' package.json +``` + +### Dev Command Discovery +```bash +# Read package.json to find dev command +cat package.json | grep '"dev"' +# Or with jq: +jq -r '.scripts.dev' package.json +``` + +### Environment Variables +```bash +# Check for .env files to copy +ls -la | grep '.env' +``` + +### Build System +```bash +# Detect build system +if [ -f "vite.config.ts" ]; then + # Vite project +elif [ -f "next.config.js" ]; then + # Next.js project +elif [ -f "nuxt.config.ts" ]; then + # Nuxt project +fi +``` + +## Common Mistakes to Avoid + +❌ **Blocking operations**: `sleep 60` (blocks dmux) +✅ **Background long tasks**: `slow_operation &` + +❌ **Hardcoded paths**: `/Users/me/project` +✅ **Use variables**: `"$DMUX_ROOT"` + +❌ **Assuming tools exist**: `pnpm install` +✅ **Check first**: `command -v pnpm && pnpm install` + +❌ **No error handling**: Script fails silently +✅ **Set error mode**: `set -e` or check exit codes + +❌ **Forgetting executable bit**: Hook won't run +✅ **Make executable**: `chmod +x` + +❌ **Noisy output**: Clutters dmux logs +✅ **Silent operations**: `curl -s`, `> /dev/null 2>&1` + +❌ **Not testing**: Deploy and hope +✅ **Test manually**: Run with mock env vars first + +## Debugging + +If a hook isn't working: + +1. **Check if file exists**: `ls -la .dmux-hooks/` +2. **Check permissions**: Should show `x` in `rwxr-xr-x` +3. **Check syntax**: `bash -n .dmux-hooks/hook_name` +4. **Test manually**: Set env vars and run +5. **Check logs**: dmux logs to stderr with `[Hooks]` prefix +6. **Simplify**: Remove complex parts, test basic version +7. **Check tool availability**: `command -v required_tool` + +### Debug Mode +```bash +#!/bin/bash +# Add to top of hook for debugging +set -x # Print each command before executing +set -e # Exit on error + +# Your hook logic here +``` + +## Summary Checklist + +When creating a new hook: + +- [ ] Create file in `.dmux-hooks/` +- [ ] Add shebang: `#!/bin/bash` +- [ ] Make executable: `chmod +x` +- [ ] Add `set -e` for error handling +- [ ] Use environment variables (never hardcode paths) +- [ ] Keep blocking hooks chatty with status output +- [ ] Background long operations with `&` only when the hook does not gate the operation +- [ ] Check for required tools before using +- [ ] Test manually with mock env vars +- [ ] Add comments explaining what it does +- [ ] Commit to version control + +## Getting Help + +- **Full documentation**: See `HOOKS.md` in project root +- **Claude-specific tips**: See `CLAUDE.md` in `.dmux-hooks/` +- **Examples**: Check `.dmux-hooks/examples/` directory +- **dmux API**: See `API.md` for REST endpoints + +--- + +*This documentation was auto-generated from dmux source code.* +*Version: 2026-05-25* diff --git a/.dmux-hooks/CLAUDE.md b/.dmux-hooks/CLAUDE.md new file mode 100644 index 00000000..1a5001bb --- /dev/null +++ b/.dmux-hooks/CLAUDE.md @@ -0,0 +1,445 @@ +# dmux Hooks System - Agent Reference + +**Auto-generated documentation for AI agents** + +This document contains everything an AI agent needs to create, modify, and understand dmux hooks. It is automatically generated from the dmux source code and embedded in the binary. + +## What You're Working On + +You are editing hooks for **dmux**, a tmux pane manager that creates AI-powered development workflows. Each pane runs in its own git worktree with an AI agent. + +## Your Goal + +Create executable bash scripts in `.dmux-hooks/` that run automatically at key lifecycle events. + +## Quick Start + +1. **Create a hook file**: `touch .dmux-hooks/worktree_created` +2. **Make it executable**: `chmod +x .dmux-hooks/worktree_created` +3. **Add shebang**: Start with `#!/bin/bash` +4. **Use environment variables**: Access `$DMUX_ROOT`, `$DMUX_WORKTREE_PATH`, etc. +5. **Test it**: Set env vars manually and run the script + +## Hook Execution Model + +- **Mostly non-blocking**: Most hooks run in background (detached processes) +- **Bootstrap-gated**: `worktree_created` blocks agent launch so setup can finish first, with no fixed timeout +- **Live bootstrap output**: During `worktree_created`, stdout/stderr is streamed into the new pane's setup UI +- **Failure behavior**: Background hook errors are logged; gated hooks can abort the operation +- **Environment-based**: All context passed via environment variables +- **Version controlled**: Hooks in `.dmux-hooks/` are shared with team +- **Priority resolution**: `.dmux-hooks/` → `.dmux/hooks/` → `~/.dmux/hooks/` + +## Available Hooks + +### Pane Lifecycle Hooks + +| Hook | When | Common Use Cases | +|------|------|------------------| +| `before_pane_create` | Before pane creation | Validation, notifications, pre-flight checks | +| `pane_created` | After pane, before worktree | Configure tmux settings, prepare environment | +| `worktree_created` | After worktree creation, before agent launch | Install deps, copy configs, setup git | +| `before_pane_close` | Before closing | Save state, backup uncommitted work | +| `pane_closed` | After closed | Cleanup resources, analytics, notifications | + +### Worktree Lifecycle Hooks + +| Hook | When | Common Use Cases | +|------|------|------------------| +| `before_worktree_remove` | Before worktree removal | Archive worktree, save artifacts | +| `worktree_removed` | After worktree removed | Cleanup external references | + +### Merge Lifecycle Hooks + +| Hook | When | Common Use Cases | +|------|------|------------------| +| `pre_merge` | Before merge operation | Run final tests, create backups | +| `post_merge` | After successful merge | Deploy, close issues, notify team | + +### Interactive Hooks (with HTTP callbacks) + +| Hook | When | Common Use Cases | +|------|------|------------------| +| `run_test` | When tests triggered | Run test suite, report status via HTTP | +| `run_dev` | When dev server triggered | Start dev server, create tunnel, report URL | + + +## Environment Variables + +### Always Available +```bash +DMUX_ROOT="/path/to/project" # Project root directory +DMUX_SERVER_PORT="3142" # HTTP server port +``` + +### Pane Context (most hooks) +```bash +DMUX_PANE_ID="dmux-1234567890" # dmux pane identifier +DMUX_SLUG="fix-auth-bug" # Branch/worktree name +DMUX_PROMPT="Fix authentication bug" # User's prompt +DMUX_AGENT="claude" # Agent type (registry id, e.g. claude, codex, opencode) +DMUX_TMUX_PANE_ID="%38" # tmux pane ID +``` + +### Worktree Context +```bash +DMUX_WORKTREE_PATH="/path/.dmux/worktrees/fix-auth-bug" +DMUX_BRANCH="fix-auth-bug" # Same as slug +``` + +### Bootstrap Progress Context +```bash +DMUX_PROGRESS="1" # Set when output is shown in the new pane setup UI +DMUX_STATUS_PREFIX="DMUX_STATUS:" # Prefix for clean status messages +``` + +`worktree_created` can emit progress while it runs. Any stdout/stderr line is shown in the setup UI; lines prefixed with `$DMUX_STATUS_PREFIX` are displayed without the prefix. + +```bash +status() { + if [ "${DMUX_PROGRESS:-0}" = "1" ]; then + echo "${DMUX_STATUS_PREFIX:-DMUX_STATUS:} $*" + else + echo "[Hook] $*" + fi +} +``` + +### Merge Context +```bash +DMUX_TARGET_BRANCH="main" # Branch being merged into +``` + +## HTTP Callback API + +Interactive hooks (`run_test` and `run_dev`) can update dmux UI via HTTP. + +### Update Test Status +```bash +curl -X PUT "http://localhost:$DMUX_SERVER_PORT/api/panes/$DMUX_PANE_ID/test" -H "Content-Type: application/json" -d '{"status": "running", "output": "optional test output"}' + +# Status values: "running" | "passed" | "failed" +``` + +### Update Dev Server +```bash +curl -X PUT "http://localhost:$DMUX_SERVER_PORT/api/panes/$DMUX_PANE_ID/dev" -H "Content-Type: application/json" -d '{"status": "running", "url": "http://localhost:3000"}' + +# Status values: "running" | "stopped" +# url: Can be localhost or tunnel URL (ngrok, cloudflared, etc.) +``` + +## Common Patterns + +### Pattern 1: Install Dependencies +```bash +#!/bin/bash +# .dmux-hooks/worktree_created + +cd "$DMUX_WORKTREE_PATH" + +status() { + if [ "${DMUX_PROGRESS:-0}" = "1" ]; then + echo "${DMUX_STATUS_PREFIX:-DMUX_STATUS:} $*" + else + echo "[Hook] $*" + fi +} + +if [ -f "pnpm-lock.yaml" ]; then + status "Installing dependencies with pnpm" + pnpm install --prefer-offline +elif [ -f "package-lock.json" ]; then + status "Installing dependencies with npm" + npm install +elif [ -f "yarn.lock" ]; then + status "Installing dependencies with yarn" + yarn install +elif [ -f "Gemfile" ]; then + status "Installing gems" + bundle install +elif [ -f "requirements.txt" ]; then + status "Installing Python dependencies" + pip install -r requirements.txt +elif [ -f "Cargo.toml" ]; then + status "Building Rust project" + cargo build +fi +``` + +### Pattern 2: Copy Configuration +```bash +#!/bin/bash +# .dmux-hooks/worktree_created + +# Copy environment file +if [ -f "$DMUX_ROOT/.env.local" ]; then + cp "$DMUX_ROOT/.env.local" "$DMUX_WORKTREE_PATH/.env.local" +fi + +# Copy other config files +for file in .env.development .npmrc .yarnrc; do + if [ -f "$DMUX_ROOT/$file" ]; then + cp "$DMUX_ROOT/$file" "$DMUX_WORKTREE_PATH/$file" + fi +done +``` + +### Pattern 3: Run Tests with Status Updates +```bash +#!/bin/bash +# .dmux-hooks/run_test + +set -e +cd "$DMUX_WORKTREE_PATH" +API="http://localhost:$DMUX_SERVER_PORT/api/panes/$DMUX_PANE_ID/test" + +# Update: starting +curl -s -X PUT "$API" -H "Content-Type: application/json" -d '{"status": "running"}' > /dev/null + +# Run tests and capture output +OUTPUT_FILE="/tmp/dmux-test-$DMUX_PANE_ID.txt" +if pnpm test > "$OUTPUT_FILE" 2>&1; then + STATUS="passed" +else + STATUS="failed" +fi + +# Get output (truncate if too long) +OUTPUT=$(head -c 5000 "$OUTPUT_FILE") + +# Update: complete +curl -s -X PUT "$API" -H "Content-Type: application/json" -d "$(jq -n --arg status "$STATUS" --arg output "$OUTPUT" '{status: $status, output: $output}')" > /dev/null + +rm -f "$OUTPUT_FILE" +``` + +### Pattern 4: Dev Server with Tunnel +```bash +#!/bin/bash +# .dmux-hooks/run_dev + +set -e +cd "$DMUX_WORKTREE_PATH" +API="http://localhost:$DMUX_SERVER_PORT/api/panes/$DMUX_PANE_ID/dev" + +# Start dev server in background +LOG_FILE="/tmp/dmux-dev-$DMUX_PANE_ID.log" +pnpm dev > "$LOG_FILE" 2>&1 & +DEV_PID=$! + +# Wait for server to start +sleep 5 + +# Detect port from logs +PORT=$(grep -oP 'localhost:Kd+' "$LOG_FILE" | head -1) +[ -z "$PORT" ] && PORT=3000 + +# Optional: Create tunnel with cloudflared +if command -v cloudflared &> /dev/null; then + TUNNEL=$(cloudflared tunnel --url "http://localhost:$PORT" 2>&1 | grep -oP 'https://[a-z0-9-]+.trycloudflare.com' | head -1) + URL="${TUNNEL:-http://localhost:$PORT}" +else + URL="http://localhost:$PORT" +fi + +# Report status +curl -s -X PUT "$API" -H "Content-Type: application/json" -d "{"status": "running", "url": "$URL"}" > /dev/null + +echo "[Hook] Dev server running at $URL (PID: $DEV_PID)" +``` + +### Pattern 5: Post-Merge Deployment +```bash +#!/bin/bash +# .dmux-hooks/post_merge + +set -e +cd "$DMUX_ROOT" + +# Only deploy from main/master +if [ "$DMUX_TARGET_BRANCH" != "main" ] && [ "$DMUX_TARGET_BRANCH" != "master" ]; then + exit 0 +fi + +# Push to remote +git push origin "$DMUX_TARGET_BRANCH" + +# Trigger deployment (example: Vercel) +if [ -n "$VERCEL_TOKEN" ]; then + curl -s -X POST "https://api.vercel.com/v1/deployments" -H "Authorization: Bearer $VERCEL_TOKEN" -H "Content-Type: application/json" -d '{"name": "my-project"}' > /dev/null +fi + +# Close GitHub issue if prompt contains #123 +ISSUE=$(echo "$DMUX_PROMPT" | grep -oP '#Kd+' | head -1) +if [ -n "$ISSUE" ] && command -v gh &> /dev/null; then + gh issue close "$ISSUE" -c "Resolved in $DMUX_SLUG, merged to $DMUX_TARGET_BRANCH" 2>/dev/null || true +fi +``` + +## Best Practices + +1. **Always start with shebang**: `#!/bin/bash` +2. **Set error handling**: `set -e` (exit on error) +3. **Make executable**: `chmod +x .dmux-hooks/hook_name` +4. **Background long operations**: Append `&` to avoid blocking +5. **Check for required tools**: `command -v tool &> /dev/null` +6. **Log for debugging**: `echo "[Hook] message" >> "$DMUX_ROOT/.dmux/hooks.log"` +7. **Handle missing vars gracefully**: `[ -z "$VAR" ] && exit 0` +8. **Use silent curl**: `curl -s` to avoid noise in logs +9. **Clean up temp files**: Remove files in `/tmp/` +10. **Test before committing**: Run hooks manually with mock env vars + +## Testing Hooks + +### Manual Testing +```bash +# 1. Set environment variables +export DMUX_ROOT="$(pwd)" +export DMUX_PANE_ID="test-pane" +export DMUX_SLUG="test-branch" +export DMUX_WORKTREE_PATH="$(pwd)" +export DMUX_SERVER_PORT="3142" +export DMUX_AGENT="claude" +export DMUX_PROMPT="Test prompt" + +# 2. Run hook directly +./.dmux-hooks/worktree_created + +# 3. Check exit code +echo $? # Should be 0 for success +``` + +### Syntax Check +```bash +# Check for syntax errors without running +bash -n ./.dmux-hooks/worktree_created +``` + +### Shellcheck (if available) +```bash +shellcheck ./.dmux-hooks/worktree_created +``` + +## Project Context Analysis + +Before creating hooks, analyze these files in the project: + +### Package Manager Detection +```bash +# Check which package manager is used +if [ -f "pnpm-lock.yaml" ]; then + # Use: pnpm install, pnpm test, pnpm dev +elif [ -f "package-lock.json" ]; then + # Use: npm install, npm test, npm run dev +elif [ -f "yarn.lock" ]; then + # Use: yarn install, yarn test, yarn dev +fi +``` + +### Test Command Discovery +```bash +# Read package.json to find test command +cat package.json | grep '"test"' +# Or with jq: +jq -r '.scripts.test' package.json +``` + +### Dev Command Discovery +```bash +# Read package.json to find dev command +cat package.json | grep '"dev"' +# Or with jq: +jq -r '.scripts.dev' package.json +``` + +### Environment Variables +```bash +# Check for .env files to copy +ls -la | grep '.env' +``` + +### Build System +```bash +# Detect build system +if [ -f "vite.config.ts" ]; then + # Vite project +elif [ -f "next.config.js" ]; then + # Next.js project +elif [ -f "nuxt.config.ts" ]; then + # Nuxt project +fi +``` + +## Common Mistakes to Avoid + +❌ **Blocking operations**: `sleep 60` (blocks dmux) +✅ **Background long tasks**: `slow_operation &` + +❌ **Hardcoded paths**: `/Users/me/project` +✅ **Use variables**: `"$DMUX_ROOT"` + +❌ **Assuming tools exist**: `pnpm install` +✅ **Check first**: `command -v pnpm && pnpm install` + +❌ **No error handling**: Script fails silently +✅ **Set error mode**: `set -e` or check exit codes + +❌ **Forgetting executable bit**: Hook won't run +✅ **Make executable**: `chmod +x` + +❌ **Noisy output**: Clutters dmux logs +✅ **Silent operations**: `curl -s`, `> /dev/null 2>&1` + +❌ **Not testing**: Deploy and hope +✅ **Test manually**: Run with mock env vars first + +## Debugging + +If a hook isn't working: + +1. **Check if file exists**: `ls -la .dmux-hooks/` +2. **Check permissions**: Should show `x` in `rwxr-xr-x` +3. **Check syntax**: `bash -n .dmux-hooks/hook_name` +4. **Test manually**: Set env vars and run +5. **Check logs**: dmux logs to stderr with `[Hooks]` prefix +6. **Simplify**: Remove complex parts, test basic version +7. **Check tool availability**: `command -v required_tool` + +### Debug Mode +```bash +#!/bin/bash +# Add to top of hook for debugging +set -x # Print each command before executing +set -e # Exit on error + +# Your hook logic here +``` + +## Summary Checklist + +When creating a new hook: + +- [ ] Create file in `.dmux-hooks/` +- [ ] Add shebang: `#!/bin/bash` +- [ ] Make executable: `chmod +x` +- [ ] Add `set -e` for error handling +- [ ] Use environment variables (never hardcode paths) +- [ ] Keep blocking hooks chatty with status output +- [ ] Background long operations with `&` only when the hook does not gate the operation +- [ ] Check for required tools before using +- [ ] Test manually with mock env vars +- [ ] Add comments explaining what it does +- [ ] Commit to version control + +## Getting Help + +- **Full documentation**: See `HOOKS.md` in project root +- **Claude-specific tips**: See `CLAUDE.md` in `.dmux-hooks/` +- **Examples**: Check `.dmux-hooks/examples/` directory +- **dmux API**: See `API.md` for REST endpoints + +--- + +*This documentation was auto-generated from dmux source code.* +*Version: 2026-05-25* diff --git a/.dmux-hooks/README.md b/.dmux-hooks/README.md new file mode 100644 index 00000000..202222ee --- /dev/null +++ b/.dmux-hooks/README.md @@ -0,0 +1,53 @@ +# dmux Hooks + +This directory contains hooks that run automatically at key lifecycle events in dmux. + +## Quick Start + +1. **Read the documentation**: + - `AGENTS.md` - Complete reference (for any AI agent) + - `CLAUDE.md` - Same content (Claude Code looks for this filename) + +2. **Check examples**: + - `examples/` directory contains starter templates + +3. **Create a hook**: + ```bash + touch worktree_created + chmod +x worktree_created + nano worktree_created + ``` + +4. **Test it**: + ```bash + export DMUX_ROOT="$(pwd)" + export DMUX_WORKTREE_PATH="$(pwd)" + ./worktree_created + ``` + +## Available Hooks + +- `before_pane_create` - Before pane creation +- `pane_created` - After pane created +- `worktree_created` - After worktree setup +- `before_pane_close` - Before closing +- `pane_closed` - After closed +- `before_worktree_remove` - Before worktree removal +- `worktree_removed` - After worktree removed +- `pre_merge` - Before merge +- `post_merge` - After merge +- `run_test` - When running tests +- `run_dev` - When starting dev server + +## Documentation + +See `AGENTS.md` or `CLAUDE.md` for complete documentation including: +- Environment variables +- HTTP callback API +- Common patterns +- Best practices +- Testing strategies + +## Note + +This directory is **version controlled**. Hooks you create here will be shared with your team. diff --git a/.dmux-hooks/examples/post_merge.example b/.dmux-hooks/examples/post_merge.example new file mode 100755 index 00000000..9b5c58d1 --- /dev/null +++ b/.dmux-hooks/examples/post_merge.example @@ -0,0 +1,66 @@ +#!/bin/bash +# Example: post_merge hook +# +# This hook runs after a successful merge into the target branch. +# Use it to trigger deployments, close issues, notify teams, etc. + +set -e + +echo "[Hook] Post-merge processing for $DMUX_SLUG → $DMUX_TARGET_BRANCH" + +cd "$DMUX_ROOT" + +# Push to remote if merging to main/master +if [ "$DMUX_TARGET_BRANCH" = "main" ] || [ "$DMUX_TARGET_BRANCH" = "master" ]; then + echo "[Hook] Pushing to origin/$DMUX_TARGET_BRANCH" + git push origin "$DMUX_TARGET_BRANCH" + + # Optional: Trigger deployment + # if [ -n "$VERCEL_TOKEN" ]; then + # echo "[Hook] Triggering Vercel deployment..." + # curl -X POST "https://api.vercel.com/v1/deployments" \ + # -H "Authorization: Bearer $VERCEL_TOKEN" \ + # -H "Content-Type: application/json" \ + # -d '{ + # "name": "my-project", + # "gitSource": { + # "type": "github", + # "ref": "main" + # } + # }' + # fi +fi + +# Close related GitHub issue (if prompt contains #123 format) +ISSUE_NUM=$(echo "$DMUX_PROMPT" | grep -oP '#\K\d+' | head -1) +if [ -n "$ISSUE_NUM" ]; then + echo "[Hook] Closing GitHub issue #$ISSUE_NUM" + if command -v gh &> /dev/null; then + gh issue close "$ISSUE_NUM" \ + -c "Resolved in branch $DMUX_SLUG, merged to $DMUX_TARGET_BRANCH" \ + 2>/dev/null || echo "[Hook] Warning: Failed to close issue (maybe already closed?)" + else + echo "[Hook] GitHub CLI (gh) not found, skipping issue close" + fi +fi + +# Send notification to Slack +# if [ -n "$SLACK_WEBHOOK" ]; then +# echo "[Hook] Sending Slack notification" +# curl -s -X POST "$SLACK_WEBHOOK" \ +# -H "Content-Type: application/json" \ +# -d "{ +# \"text\": \"Merged: $DMUX_SLUG → $DMUX_TARGET_BRANCH\", +# \"blocks\": [ +# { +# \"type\": \"section\", +# \"text\": { +# \"type\": \"mrkdwn\", +# \"text\": \"*Branch Merged* :rocket:\n\n*From:* \`$DMUX_SLUG\`\n*To:* \`$DMUX_TARGET_BRANCH\`\n*Task:* $DMUX_PROMPT\" +# } +# } +# ] +# }" > /dev/null +# fi + +echo "[Hook] Post-merge processing complete" diff --git a/.dmux-hooks/examples/run_dev.example b/.dmux-hooks/examples/run_dev.example new file mode 100755 index 00000000..c2005f43 --- /dev/null +++ b/.dmux-hooks/examples/run_dev.example @@ -0,0 +1,62 @@ +#!/bin/bash +# Example: run_dev hook +# +# This hook starts a dev server and optionally creates a tunnel for sharing. +# It reports the server URL back to dmux via the HTTP API. + +set -e + +echo "[Hook] Starting dev server for $DMUX_SLUG" + +cd "$DMUX_WORKTREE_PATH" +API_URL="http://localhost:$DMUX_SERVER_PORT/api/panes/$DMUX_PANE_ID/dev" + +# Update status: starting +curl -s -X PUT "$API_URL" \ + -H "Content-Type: application/json" \ + -d '{"status": "running"}' > /dev/null + +# Start dev server in background +# Adjust the command for your project (pnpm dev, npm run dev, vite, etc.) +LOG_FILE="/tmp/dmux-dev-$DMUX_PANE_ID.log" +pnpm dev > "$LOG_FILE" 2>&1 & +DEV_PID=$! + +# Wait for server to be ready +echo "[Hook] Waiting for dev server to start..." +sleep 5 + +# Detect port from log output +# Adjust the grep pattern for your dev server's output format +PORT=$(grep -oP '(?<=localhost:)\d+' "$LOG_FILE" | head -1) + +if [ -z "$PORT" ]; then + echo "[Hook] Warning: Could not detect port from logs, using default 3000" + PORT=3000 +fi + +LOCAL_URL="http://localhost:$PORT" +echo "[Hook] Dev server running at $LOCAL_URL" + +# Optional: Create a public tunnel (uncomment to enable) +# Requires ngrok, cloudflared, or another tunneling tool + +# Example with cloudflared: +# TUNNEL_URL=$(cloudflared tunnel --url "$LOCAL_URL" 2>&1 | \ +# grep -oP 'https://[a-z0-9-]+\.trycloudflare\.com' | head -1) + +# Example with ngrok: +# TUNNEL_URL=$(ngrok http $PORT --log=stdout 2>&1 | \ +# grep -oP 'url=https://[^"]+' | head -1 | cut -d= -f2) + +# For now, just use local URL (uncomment tunnel code above to enable) +FINAL_URL="$LOCAL_URL" + +# Report status back to dmux +curl -s -X PUT "$API_URL" \ + -H "Content-Type: application/json" \ + -d "{\"status\": \"running\", \"url\": \"$FINAL_URL\"}" > /dev/null + +echo "[Hook] Dev server ready at: $FINAL_URL" +echo "[Hook] Dev server PID: $DEV_PID" +echo "[Hook] Log file: $LOG_FILE" diff --git a/.dmux-hooks/examples/run_test.example b/.dmux-hooks/examples/run_test.example new file mode 100755 index 00000000..6b286a1c --- /dev/null +++ b/.dmux-hooks/examples/run_test.example @@ -0,0 +1,61 @@ +#!/bin/bash +# Example: run_test hook +# +# This hook runs tests and reports the status back to dmux via the HTTP API. +# Status updates appear in real-time in the dmux UI. + +set -e + +echo "[Hook] Running tests for $DMUX_SLUG" + +cd "$DMUX_WORKTREE_PATH" +API_URL="http://localhost:$DMUX_SERVER_PORT/api/panes/$DMUX_PANE_ID/test" + +# Update status: running +curl -s -X PUT "$API_URL" \ + -H "Content-Type: application/json" \ + -d '{"status": "running"}' > /dev/null + +echo "[Hook] Running test suite..." + +# Capture test output +OUTPUT_FILE="/tmp/dmux-test-$DMUX_PANE_ID.txt" + +# Run tests (adjust command for your project) +# Examples: +# - pnpm test +# - npm test +# - vitest run +# - jest +# - pytest +# - cargo test +if pnpm test > "$OUTPUT_FILE" 2>&1; then + STATUS="passed" + echo "[Hook] Tests passed ✓" +else + STATUS="failed" + echo "[Hook] Tests failed ✗" +fi + +# Get output (truncate if too long) +OUTPUT=$(head -c 5000 "$OUTPUT_FILE") + +# Report results back to dmux +curl -s -X PUT "$API_URL" \ + -H "Content-Type: application/json" \ + -d "$(jq -n \ + --arg status "$STATUS" \ + --arg output "$OUTPUT" \ + '{status: $status, output: $output}')" > /dev/null + +# Cleanup +rm -f "$OUTPUT_FILE" + +echo "[Hook] Test results reported to dmux" + +# Exit with test status +if [ "$STATUS" = "passed" ]; then + exit 0 +else + exit 1 +fi diff --git a/.dmux-hooks/examples/worktree_created.example b/.dmux-hooks/examples/worktree_created.example new file mode 100755 index 00000000..61c2979b --- /dev/null +++ b/.dmux-hooks/examples/worktree_created.example @@ -0,0 +1,48 @@ +#!/bin/bash +# Example: worktree_created hook +# +# This hook runs after a new worktree is created and before the agent launches. +# Use it to set up the worktree environment (install deps, copy configs, etc.) +# Stdout/stderr is streamed into the new pane's setup UI while this hook runs. +# dmux waits for this hook without a fixed timeout. + +set -e # Exit on error + +status() { + if [ "${DMUX_PROGRESS:-0}" = "1" ]; then + echo "${DMUX_STATUS_PREFIX:-DMUX_STATUS:} $*" + else + echo "[Hook] $*" + fi +} + +status "Setting up worktree: $DMUX_SLUG" + +cd "$DMUX_WORKTREE_PATH" + +# Install dependencies before the agent launches. +if [ -f "pnpm-lock.yaml" ]; then + status "Installing dependencies with pnpm" + pnpm install --prefer-offline +elif [ -f "package-lock.json" ]; then + status "Installing dependencies with npm" + npm install +elif [ -f "yarn.lock" ]; then + status "Installing dependencies with yarn" + yarn install +fi + +# Copy environment file if it exists +if [ -f "$DMUX_ROOT/.env.local" ]; then + status "Copying .env.local" + cp "$DMUX_ROOT/.env.local" "$DMUX_WORKTREE_PATH/.env.local" +fi + +# Keep existing git author identity. +# Do not set git user.name/user.email in this hook. + +# Create a log entry +echo "[$(date)] Created worktree: $DMUX_SLUG | Agent: $DMUX_AGENT | Prompt: $DMUX_PROMPT" \ + >> "$DMUX_ROOT/.dmux/worktree_history.log" + +status "Worktree setup complete" diff --git a/.gitignore b/.gitignore index 7bc5e245..b5cc25cd 100644 --- a/.gitignore +++ b/.gitignore @@ -52,4 +52,5 @@ go.work.sum *-source.* .codex* -/bin/ \ No newline at end of file +/bin/ +.dmux/