diff --git a/CLAUDE.md b/CLAUDE.md index dfc3f983..5bc08317 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -35,7 +35,7 @@ When user says "COM": 1. Increment version in BOTH `package.json` AND `CLAUDE.md` (verify they match with `grep version package.json && grep Version CLAUDE.md`) 2. Run: `git add -A && git commit -m "chore: bump version to X.XXXX" && git push && npm run build && systemctl --user restart claudeman-web` -**Version**: 0.1611 (must match `package.json`) +**Version**: 0.1612 (must match `package.json`) ## Project Overview diff --git a/install.sh b/install.sh index f854908e..9549fb9f 100755 --- a/install.sh +++ b/install.sh @@ -806,27 +806,19 @@ main() { fi fi - # Claude CLI + # Claude CLI (required) info "Checking Claude CLI..." if ! check_claude; then - if prompt_yes_no "Claude CLI is not installed. Install it now?"; then - info "Installing Claude CLI..." - download_to_stdout https://claude.ai/install.sh | bash - # Rehash to pick up new binary - hash -r 2>/dev/null || true - if check_claude; then - local claude_path - claude_path=$(get_claude_path) - success "Claude CLI installed at $claude_path" - else - warn "Claude CLI installation may have failed. Install manually:" - echo -e " ${CYAN}curl -fsSL https://claude.ai/install.sh | bash${NC}" - fi + info "Installing Claude CLI..." + download_to_stdout https://claude.ai/install.sh | bash + # Rehash to pick up new binary + hash -r 2>/dev/null || true + if check_claude; then + local claude_path + claude_path=$(get_claude_path) + success "Claude CLI installed at $claude_path" else - warn "Claude CLI is required to run AI sessions." - echo -e " ${DIM}Install later with:${NC}" - echo -e " ${CYAN}curl -fsSL https://claude.ai/install.sh | bash${NC}" - echo "" + die "Claude CLI installation failed. Install manually with: curl -fsSL https://claude.ai/install.sh | bash" fi else local claude_path diff --git a/package.json b/package.json index 452570c6..f3cffc15 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "claudeman", - "version": "0.1611", + "version": "0.1612", "description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence", "type": "module", "main": "dist/index.js", diff --git a/src/tmux-manager.ts b/src/tmux-manager.ts index 4ad02a65..50f9bc69 100644 --- a/src/tmux-manager.ts +++ b/src/tmux-manager.ts @@ -232,6 +232,9 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer { } const claudeDir = findClaudeDir(); + if (!claudeDir && mode === 'claude') { + throw new Error('Claude CLI not found. Install it with: curl -fsSL https://claude.ai/install.sh | bash'); + } const pathExport = claudeDir ? `export PATH="${claudeDir}:$PATH" && ` : ''; const envExports = [ @@ -257,20 +260,28 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer { // Build the full command to run inside tmux const fullCmd = `${pathExport}${envExports} && ${cmd}`; - // Create tmux session in detached mode using execSync so tmux server - // cold-start (common on macOS first run) completes before we query it. - // -d: don't attach, -s: session name, -c: starting directory - // -x/-y: initial window size - // - // CRITICAL: set remain-on-exit BEFORE spawning the command. If the command - // exits quickly (e.g. Claude CLI not found), tmux destroys the session - // before we can query it. Setting it as a server default first prevents this. - const shellCmd = fullCmd.replace(/'/g, "'\\''"); + // Create tmux session in two steps to handle cold-start (no server running): + // 1. Create session with default shell (starts tmux server, session stays alive) + // 2. Set remain-on-exit + other options (server now exists) + // 3. Send actual command via send-keys + // This avoids the race where the command exits before remain-on-exit is set. + const shellCmd = fullCmd.replace(/"/g, '\\"'); execSync( - `tmux set-option -g remain-on-exit on 2>/dev/null; tmux new-session -ds "${muxName}" -c "${workingDir}" -x 120 -y 40 bash -c '${shellCmd}'`, + `tmux new-session -ds "${muxName}" -c "${workingDir}" -x 120 -y 40`, { cwd: workingDir, timeout: EXEC_TIMEOUT_MS, stdio: 'ignore' } ); + // Set remain-on-exit now that the server is running + try { + execSync(`tmux set-option -t "${muxName}" remain-on-exit on`, { timeout: EXEC_TIMEOUT_MS, stdio: 'ignore' }); + } catch { /* Non-critical */ } + + // Send the actual command to the session + execSync( + `tmux send-keys -t "${muxName}" "${shellCmd}" Enter`, + { timeout: EXEC_TIMEOUT_MS, stdio: 'ignore' } + ); + // Wait for tmux session to be queryable await new Promise(resolve => setTimeout(resolve, TMUX_CREATION_WAIT_MS));