Files
Keep/.ralph/drivers/claude-code.sh

188 lines
5.6 KiB
Bash
Executable File

#!/bin/bash
# Claude Code driver for Ralph
# Provides platform-specific CLI invocation logic
# Driver identification
driver_name() {
echo "claude-code"
}
driver_display_name() {
echo "Claude Code"
}
driver_cli_binary() {
echo "claude"
}
driver_min_version() {
echo "2.0.76"
}
# Check if the CLI binary is available
driver_check_available() {
command -v "$(driver_cli_binary)" &>/dev/null
}
# Valid tool patterns for --allowedTools validation
# Sets the global VALID_TOOL_PATTERNS array
driver_valid_tools() {
VALID_TOOL_PATTERNS=(
"Write"
"Read"
"Edit"
"MultiEdit"
"Glob"
"Grep"
"Task"
"TodoWrite"
"WebFetch"
"WebSearch"
"AskUserQuestion"
"EnterPlanMode"
"ExitPlanMode"
"Bash"
"Bash(git *)"
"Bash(npm *)"
"Bash(bats *)"
"Bash(python *)"
"Bash(node *)"
"NotebookEdit"
)
}
driver_supports_tool_allowlist() {
return 0
}
driver_permission_denial_help() {
echo " 1. Edit $RALPHRC_FILE and keep CLAUDE_PERMISSION_MODE=bypassPermissions for unattended Claude Code loops"
echo " 2. If Claude was denied on an interactive approval step, ALLOWED_TOOLS will not fix it"
echo " 3. If Claude was denied on a normal tool, update ALLOWED_TOOLS to include the required tools"
echo " 4. Common ALLOWED_TOOLS patterns:"
echo " - Bash - All shell commands"
echo " - Bash(node *) - All Node.js commands"
echo " - Bash(npm *) - All npm commands"
echo " - Bash(pnpm *) - All pnpm commands"
echo " - AskUserQuestion - Allow interactive clarification when you want pauses"
echo ""
echo "After updating $RALPHRC_FILE:"
echo " bash .ralph/ralph_loop.sh --reset-session # Clear stale session state"
echo " bmalph run # Restart the loop"
}
# Build the CLI command arguments
# Populates global CLAUDE_CMD_ARGS array
# Parameters:
# $1 - prompt_file: path to the prompt file
# $2 - loop_context: context string for session continuity
# $3 - session_id: session ID for resume (empty for new session)
driver_build_command() {
local prompt_file=$1
local loop_context=$2
local session_id=$3
local resolved_permission_mode="${CLAUDE_PERMISSION_MODE:-bypassPermissions}"
# Note: We do NOT use --dangerously-skip-permissions here. Tool permissions
# are controlled via --allowedTools from CLAUDE_ALLOWED_TOOLS in .ralphrc.
# This preserves the permission denial circuit breaker (Issue #101).
CLAUDE_CMD_ARGS=("$(driver_cli_binary)")
if [[ ! -f "$prompt_file" ]]; then
echo "ERROR: Prompt file not found: $prompt_file" >&2
return 1
fi
# Output format
if [[ "$CLAUDE_OUTPUT_FORMAT" == "json" ]]; then
CLAUDE_CMD_ARGS+=("--output-format" "json")
fi
# Prevent interactive approval flows from blocking unattended -p loops.
CLAUDE_CMD_ARGS+=("--permission-mode" "$resolved_permission_mode")
# Allowed tools
if [[ -n "$CLAUDE_ALLOWED_TOOLS" ]]; then
CLAUDE_CMD_ARGS+=("--allowedTools")
local IFS=','
read -ra tools_array <<< "$CLAUDE_ALLOWED_TOOLS"
for tool in "${tools_array[@]}"; do
tool=$(echo "$tool" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
if [[ -n "$tool" ]]; then
CLAUDE_CMD_ARGS+=("$tool")
fi
done
fi
# Session resume
# IMPORTANT: Use --resume with explicit session ID instead of --continue.
# --continue resumes the "most recent session in current directory" which
# can hijack active Claude Code sessions. --resume with a specific session ID
# ensures we only resume Ralph's own sessions. (Issue #151)
if [[ "$CLAUDE_USE_CONTINUE" == "true" && -n "$session_id" ]]; then
CLAUDE_CMD_ARGS+=("--resume" "$session_id")
fi
# Loop context as system prompt
if [[ -n "$loop_context" ]]; then
CLAUDE_CMD_ARGS+=("--append-system-prompt" "$loop_context")
fi
# Prompt content
local prompt_content
prompt_content=$(cat "$prompt_file")
CLAUDE_CMD_ARGS+=("-p" "$prompt_content")
}
# Whether this driver supports session continuity
driver_supports_sessions() {
return 0 # true
}
# Claude Code supports stream-json live output.
driver_supports_live_output() {
return 0 # true
}
# Prepare command arguments for live stream-json output.
driver_prepare_live_command() {
LIVE_CMD_ARGS=()
local skip_next=false
for arg in "${CLAUDE_CMD_ARGS[@]}"; do
if [[ "$skip_next" == "true" ]]; then
LIVE_CMD_ARGS+=("stream-json")
skip_next=false
elif [[ "$arg" == "--output-format" ]]; then
LIVE_CMD_ARGS+=("$arg")
skip_next=true
else
LIVE_CMD_ARGS+=("$arg")
fi
done
if [[ "$skip_next" == "true" ]]; then
return 1
fi
LIVE_CMD_ARGS+=("--verbose" "--include-partial-messages")
}
# Stream filter for raw Claude stream-json events.
driver_stream_filter() {
echo '
if .type == "stream_event" then
if .event.type == "content_block_delta" and .event.delta.type == "text_delta" then
.event.delta.text
elif .event.type == "content_block_start" and .event.content_block.type == "tool_use" then
"\n\n⚡ [" + .event.content_block.name + "]\n"
elif .event.type == "content_block_stop" then
"\n"
else
empty
end
else
empty
end'
}