diff --git a/registry/coder/modules/claude-code/main.tf b/registry/coder/modules/claude-code/main.tf index 1435a2f3..2527e6ac 100644 --- a/registry/coder/modules/claude-code/main.tf +++ b/registry/coder/modules/claude-code/main.tf @@ -3,8 +3,8 @@ terraform { required_providers { coder = { - source = "coder/coder" - version = ">= 2.5" + source = "coder/coder" + version = ">= 2.7.0" } } } @@ -54,6 +54,18 @@ variable "claude_code_version" { default = "latest" } +variable "install_agentapi" { + type = bool + description = "Whether to install AgentAPI." + default = true +} + +variable "agentapi_version" { + type = string + description = "The version of AgentAPI to install." + default = "v0.2.2" +} + variable "experiment_use_screen" { type = bool description = "Whether to use screen for running Claude Code in the background." @@ -87,6 +99,15 @@ variable "experiment_post_install_script" { locals { encoded_pre_install_script = var.experiment_pre_install_script != null ? base64encode(var.experiment_pre_install_script) : "" encoded_post_install_script = var.experiment_post_install_script != null ? base64encode(var.experiment_post_install_script) : "" + agentapi_start_command = <<-EOT + #!/bin/bash + set -e + + # use low width to fit in the tasks UI sidebar. height is adjusted to ~match the default 80k (80x1000) characters + # visible in the terminal screen. + agentapi server --term-width 67 --term-height 1190 -- bash -c "claude --dangerously-skip-permissions \"$(cat ~/.claude-code-prompt)\"" + EOT + agentapi_start_command_base64 = base64encode(local.agentapi_start_command) } # Install and Initialize Claude Code @@ -131,9 +152,35 @@ resource "coder_script" "claude_code" { npm install -g @anthropic-ai/claude-code@${var.claude_code_version} fi + # Install AgentAPI if enabled + if [ "${var.install_agentapi}" = "true" ]; then + echo "Installing AgentAPI..." + arch=$(uname -m) + if [ "$arch" = "x86_64" ]; then + binary_name="agentapi-linux-amd64" + elif [ "$arch" = "aarch64" ]; then + binary_name="agentapi-linux-arm64" + else + echo "Error: Unsupported architecture: $arch" + exit 1 + fi + wget "https://github.com/coder/agentapi/releases/download/${var.agentapi_version}/$binary_name" + chmod +x "$binary_name" + sudo mv "$binary_name" /usr/local/bin/agentapi + fi + + if ! command_exists agentapi; then + echo "Error: AgentAPI is not installed. Please enable install_agentapi or install it manually." + exit 1 + fi + + echo -n "$CODER_MCP_CLAUDE_TASK_PROMPT" > ~/.claude-code-prompt + echo -n "${local.agentapi_start_command_base64}" | base64 -d > ~/.agentapi-start-command + chmod +x ~/.agentapi-start-command + if [ "${var.experiment_report_tasks}" = "true" ]; then echo "Configuring Claude Code to report tasks via Coder MCP..." - coder exp mcp configure claude-code ${var.folder} + coder exp mcp configure claude-code ${var.folder} --ai-agentapi-url http://localhost:3284 fi # Run post-install script if provided @@ -166,8 +213,24 @@ resource "coder_script" "claude_code" { export LANG=en_US.UTF-8 export LC_ALL=en_US.UTF-8 - # Create a new tmux session in detached mode - tmux new-session -d -s claude-code -c ${var.folder} "claude --dangerously-skip-permissions \"$CODER_MCP_CLAUDE_TASK_PROMPT\"" + # use low width to fit in the tasks UI sidebar. height is adjusted to ~match the default 80k (80x1000) characters + # visible in the terminal screen. + tmux new-session -d -s claude-code-agentapi -c ${var.folder} '~/.agentapi-start-command; exec bash' + echo "Waiting for agentapi server to start on port 3284..." + for i in $(seq 1 15); do + if lsof -i :3284 | grep -q 'LISTEN'; then + echo "agentapi server started on port 3284." + break + fi + echo "Waiting... ($i/15)" + sleep 1 + done + if ! lsof -i :3284 | grep -q 'LISTEN'; then + echo "Error: agentapi server did not start on port 3284 after 15 seconds." + exit 1 + fi + tmux new-session -d -s claude-code -c ${var.folder} "agentapi attach" + fi @@ -203,7 +266,7 @@ resource "coder_script" "claude_code" { screen -U -dmS claude-code bash -c ' cd ${var.folder} - claude --dangerously-skip-permissions "$CODER_MCP_CLAUDE_TASK_PROMPT" | tee -a "$HOME/.claude-code.log" + ~/.agentapi-start-command exec bash ' else @@ -217,6 +280,20 @@ resource "coder_script" "claude_code" { run_on_start = true } +resource "coder_app" "claude_code_web" { + slug = "claude-code-web" + display_name = "Claude Code Web" + agent_id = var.agent_id + url = "http://localhost:3284/" + icon = var.icon + subdomain = true + healthcheck { + url = "http://localhost:3284/status" + interval = 5 + threshold = 3 + } +} + resource "coder_app" "claude_code" { slug = "claude-code" display_name = "Claude Code" @@ -229,12 +306,18 @@ resource "coder_app" "claude_code" { export LC_ALL=en_US.UTF-8 if [ "${var.experiment_use_tmux}" = "true" ]; then + + if ! tmux has-session -t claude-code-agentapi 2>/dev/null; then + echo "Starting a new Claude Code agentapi tmux session." | tee -a "$HOME/.claude-code.log" + tmux new-session -d -s claude-code-agentapi -c ${var.folder} '~/.agentapi-start-command; exec bash' + fi + if tmux has-session -t claude-code 2>/dev/null; then echo "Attaching to existing Claude Code tmux session." | tee -a "$HOME/.claude-code.log" tmux attach-session -t claude-code else echo "Starting a new Claude Code tmux session." | tee -a "$HOME/.claude-code.log" - tmux new-session -s claude-code -c ${var.folder} "claude --dangerously-skip-permissions | tee -a \"$HOME/.claude-code.log\"; exec bash" + tmux new-session -s claude-code -c ${var.folder} "agentapi attach; exec bash" fi elif [ "${var.experiment_use_screen}" = "true" ]; then if screen -list | grep -q "claude-code"; then @@ -242,7 +325,7 @@ resource "coder_app" "claude_code" { screen -xRR claude-code else echo "Starting a new Claude Code screen session." | tee -a "$HOME/.claude-code.log" - screen -S claude-code bash -c 'claude --dangerously-skip-permissions | tee -a "$HOME/.claude-code.log"; exec bash' + screen -S claude-code bash -c 'agentapi attach; exec bash' fi else cd ${var.folder} @@ -253,3 +336,9 @@ resource "coder_app" "claude_code" { order = var.order group = var.group } + +resource "coder_ai_task" "claude_code" { + sidebar_app { + id = coder_app.claude_code.id + } +} \ No newline at end of file