diff --git a/registry/coder/modules/claude-code/main.tf b/registry/coder/modules/claude-code/main.tf index db234c052..02c7c0936 100644 --- a/registry/coder/modules/claude-code/main.tf +++ b/registry/coder/modules/claude-code/main.tf @@ -30,46 +30,11 @@ variable "group" { default = null } -variable "icon" { - type = string - description = "The icon to use for the app." - default = "/icon/claude.svg" -} - variable "workdir" { type = string description = "The folder to run Claude Code in." } -variable "report_tasks" { - type = bool - description = "Whether to enable task reporting to Coder UI via AgentAPI" - default = true -} - -variable "web_app" { - type = bool - description = "Whether to create the web app for Claude Code. When false, AgentAPI still runs but no web UI app icon is shown in the Coder dashboard. This is automatically enabled when using Coder Tasks, regardless of this setting." - default = true -} - -variable "cli_app" { - type = bool - description = "Whether to create a CLI app for Claude Code" - default = false -} - -variable "web_app_display_name" { - type = string - description = "Display name for the web app" - default = "Claude Code" -} - -variable "cli_app_display_name" { - type = string - description = "Display name for the CLI app" - default = "Claude Code CLI" -} variable "pre_install_script" { type = string @@ -95,19 +60,12 @@ variable "agentapi_version" { default = "v0.11.8" } -variable "ai_prompt" { - type = string - description = "Initial task prompt for Claude Code." - default = "" -} - variable "subdomain" { type = bool description = "Whether to use a subdomain for AgentAPI." default = false } - variable "install_claude_code" { type = bool description = "Whether to install Claude Code." @@ -138,34 +96,6 @@ variable "model" { default = "" } -variable "resume_session_id" { - type = string - description = "Resume a specific session by ID." - default = "" -} - -variable "continue" { - type = bool - description = "Automatically continue existing sessions on workspace restart. When true, resumes existing conversation if found, otherwise runs prompt or starts new session. When false, always starts fresh (ignores existing sessions)." - default = true -} - -variable "dangerously_skip_permissions" { - type = bool - description = "Skip the permission prompts. Use with caution. This will be set to true if using Coder Tasks" - default = false -} - -variable "permission_mode" { - type = string - description = "Permission mode for the cli, check https://docs.anthropic.com/en/docs/claude-code/iam#permission-modes" - default = "" - validation { - condition = contains(["", "default", "acceptEdits", "plan", "auto", "bypassPermissions"], var.permission_mode) - error_message = "interaction_mode must be one of: default, acceptEdits, plan, auto, bypassPermissions." - } -} - variable "mcp" { type = string description = "MCP JSON to be added to the claude code local scope" @@ -198,12 +128,6 @@ variable "claude_code_oauth_token" { default = "" } -variable "system_prompt" { - type = string - description = "The system prompt to use for the Claude Code server." - default = "" -} - variable "claude_md_path" { type = string description = "The path to CLAUDE.md." @@ -227,30 +151,6 @@ variable "install_via_npm" { default = false } -variable "enable_boundary" { - type = bool - description = "Whether to enable coder boundary for network filtering" - default = false -} - -variable "boundary_version" { - type = string - description = "Boundary version. When use_boundary_directly is true, a release version should be provided or 'latest' for the latest release. When compile_boundary_from_source is true, a valid git reference should be provided (tag, commit, branch)." - default = "latest" -} - -variable "compile_boundary_from_source" { - type = bool - description = "Whether to compile boundary from source instead of using the official install script" - default = false -} - -variable "use_boundary_directly" { - type = bool - description = "Whether to use boundary binary directly instead of coder boundary subcommand. When false (default), uses coder boundary subcommand. When true, installs and uses boundary binary from release." - default = false -} - variable "enable_aibridge" { type = bool description = "Use AI Bridge for Claude Code. https://coder.com/docs/ai-coder/ai-bridge" @@ -280,12 +180,6 @@ resource "coder_env" "claude_code_md_path" { value = var.claude_md_path } -resource "coder_env" "claude_code_system_prompt" { - agent_id = var.agent_id - name = "CODER_MCP_CLAUDE_SYSTEM_PROMPT" - value = local.final_system_prompt -} - resource "coder_env" "claude_code_oauth_token" { agent_id = var.agent_id name = "CLAUDE_CODE_OAUTH_TOKEN" @@ -307,7 +201,6 @@ resource "coder_env" "disable_autoupdater" { value = "1" } - resource "coder_env" "anthropic_model" { count = var.model != "" ? 1 : 0 agent_id = var.agent_id @@ -358,60 +251,11 @@ locals { - Make it actionable EOT - # Only include coder system prompts if report_tasks is enabled - custom_system_prompt = trimspace(try(var.system_prompt, "")) - final_system_prompt = format("%s%s", - var.report_tasks ? format("\n%s\n", local.report_tasks_system_prompt) : "", - local.custom_system_prompt != "" ? format("\n%s\n", local.custom_system_prompt) : "" - ) -} - -module "agentapi" { - source = "registry.coder.com/coder/agentapi/coder" - version = "2.4.0" - - agent_id = var.agent_id - web_app = var.web_app - web_app_slug = local.app_slug - web_app_order = var.order - web_app_group = var.group - web_app_icon = var.icon - web_app_display_name = var.web_app_display_name - folder = local.workdir - cli_app = var.cli_app - cli_app_slug = var.cli_app ? "${local.app_slug}-cli" : null - cli_app_display_name = var.cli_app ? var.cli_app_display_name : null - agentapi_subdomain = var.subdomain - module_dir_name = local.module_dir_name - install_agentapi = var.install_agentapi - agentapi_version = var.agentapi_version - enable_state_persistence = var.enable_state_persistence - pre_install_script = var.pre_install_script - post_install_script = var.post_install_script - start_script = <<-EOT - #!/bin/bash - set -o errexit - set -o pipefail - echo -n '${base64encode(local.start_script)}' | base64 -d > /tmp/start.sh - chmod +x /tmp/start.sh - - ARG_RESUME_SESSION_ID='${var.resume_session_id}' \ - ARG_CONTINUE='${var.continue}' \ - ARG_DANGEROUSLY_SKIP_PERMISSIONS='${var.dangerously_skip_permissions}' \ - ARG_PERMISSION_MODE='${var.permission_mode}' \ - ARG_WORKDIR='${local.workdir}' \ - ARG_AI_PROMPT='${base64encode(var.ai_prompt)}' \ - ARG_REPORT_TASKS='${var.report_tasks}' \ - ARG_ENABLE_BOUNDARY='${var.enable_boundary}' \ - ARG_BOUNDARY_VERSION='${var.boundary_version}' \ - ARG_COMPILE_FROM_SOURCE='${var.compile_boundary_from_source}' \ - ARG_USE_BOUNDARY_DIRECTLY='${var.use_boundary_directly}' \ - ARG_CODER_HOST='${local.coder_host}' \ - ARG_CLAUDE_BINARY_PATH='${var.claude_binary_path}' \ - /tmp/start.sh - EOT +} - install_script = <<-EOT +resource "coder_script" "install_claude_code" { + agent_id = var.agent_id + script = <<-EOT #!/bin/bash set -o errexit set -o pipefail @@ -423,18 +267,13 @@ module "agentapi" { ARG_INSTALL_CLAUDE_CODE='${var.install_claude_code}' \ ARG_CLAUDE_BINARY_PATH='${var.claude_binary_path}' \ ARG_INSTALL_VIA_NPM='${var.install_via_npm}' \ - ARG_REPORT_TASKS='${var.report_tasks}' \ ARG_WORKDIR='${local.workdir}' \ ARG_ALLOWED_TOOLS='${var.allowed_tools}' \ ARG_DISALLOWED_TOOLS='${var.disallowed_tools}' \ ARG_MCP='${var.mcp != null ? base64encode(replace(var.mcp, "'", "'\\''")) : ""}' \ ARG_MCP_CONFIG_REMOTE_PATH='${base64encode(jsonencode(var.mcp_config_remote_path))}' \ ARG_ENABLE_AIBRIDGE='${var.enable_aibridge}' \ - ARG_PERMISSION_MODE='${var.permission_mode}' \ /tmp/install.sh EOT -} - -output "task_app_id" { - value = module.agentapi.task_app_id + display_name = "ClaudeCode Install Script" } diff --git a/registry/coder/modules/claude-code/scripts/install.sh b/registry/coder/modules/claude-code/scripts/install.sh index c00773b5e..12fd15228 100644 --- a/registry/coder/modules/claude-code/scripts/install.sh +++ b/registry/coder/modules/claude-code/scripts/install.sh @@ -15,7 +15,6 @@ ARG_CLAUDE_BINARY_PATH=${ARG_CLAUDE_BINARY_PATH:-"$HOME/.local/bin"} ARG_CLAUDE_BINARY_PATH="${ARG_CLAUDE_BINARY_PATH/#\~/$HOME}" ARG_CLAUDE_BINARY_PATH="${ARG_CLAUDE_BINARY_PATH//\$HOME/$HOME}" ARG_INSTALL_VIA_NPM=${ARG_INSTALL_VIA_NPM:-false} -ARG_REPORT_TASKS=${ARG_REPORT_TASKS:-true} ARG_MCP_APP_STATUS_SLUG=${ARG_MCP_APP_STATUS_SLUG:-} ARG_MCP=$(echo -n "${ARG_MCP:-}" | base64 -d) ARG_MCP_CONFIG_REMOTE_PATH=$(echo -n "${ARG_MCP_CONFIG_REMOTE_PATH:-}" | base64 -d) @@ -33,7 +32,6 @@ printf "ARG_WORKDIR: %s\n" "$ARG_WORKDIR" printf "ARG_INSTALL_CLAUDE_CODE: %s\n" "$ARG_INSTALL_CLAUDE_CODE" printf "ARG_CLAUDE_BINARY_PATH: %s\n" "$ARG_CLAUDE_BINARY_PATH" printf "ARG_INSTALL_VIA_NPM: %s\n" "$ARG_INSTALL_VIA_NPM" -printf "ARG_REPORT_TASKS: %s\n" "$ARG_REPORT_TASKS" printf "ARG_MCP_APP_STATUS_SLUG: %s\n" "$ARG_MCP_APP_STATUS_SLUG" printf "ARG_MCP: %s\n" "$ARG_MCP" printf "ARG_MCP_CONFIG_REMOTE_PATH: %s\n" "$ARG_MCP_CONFIG_REMOTE_PATH" @@ -227,17 +225,6 @@ EOF echo "Standalone mode configured successfully" } -function report_tasks() { - if [ "$ARG_REPORT_TASKS" = "true" ]; then - echo "Configuring Claude Code to report tasks via Coder MCP..." - export CODER_MCP_APP_STATUS_SLUG="$ARG_MCP_APP_STATUS_SLUG" - export CODER_MCP_AI_AGENTAPI_URL="http://localhost:3284" - coder exp mcp configure claude-code "$ARG_WORKDIR" - else - configure_standalone_mode - fi -} - function accept_auto_mode() { # Pre-accept the auto mode TOS prompt so it doesn't appear interactively. # Claude Code shows a confirmation dialog for auto mode that blocks @@ -258,7 +245,7 @@ function accept_auto_mode() { install_claude_code_cli setup_claude_configurations -report_tasks +cofigure_standalone_mode if [ "$ARG_PERMISSION_MODE" = "auto" ]; then accept_auto_mode diff --git a/registry/coder/modules/claude-code/scripts/start.sh b/registry/coder/modules/claude-code/scripts/start.sh deleted file mode 100644 index 5ccbc8fa1..000000000 --- a/registry/coder/modules/claude-code/scripts/start.sh +++ /dev/null @@ -1,256 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -ARG_CLAUDE_BINARY_PATH=${ARG_CLAUDE_BINARY_PATH:-"$HOME/.local/bin"} -ARG_CLAUDE_BINARY_PATH="${ARG_CLAUDE_BINARY_PATH/#\~/$HOME}" -ARG_CLAUDE_BINARY_PATH="${ARG_CLAUDE_BINARY_PATH//\$HOME/$HOME}" - -export PATH="$ARG_CLAUDE_BINARY_PATH:$PATH" - -command_exists() { - command -v "$1" > /dev/null 2>&1 -} - -ARG_RESUME_SESSION_ID=${ARG_RESUME_SESSION_ID:-} -ARG_CONTINUE=${ARG_CONTINUE:-false} -ARG_DANGEROUSLY_SKIP_PERMISSIONS=${ARG_DANGEROUSLY_SKIP_PERMISSIONS:-} -ARG_PERMISSION_MODE=${ARG_PERMISSION_MODE:-} -ARG_WORKDIR=${ARG_WORKDIR:-"$HOME"} -ARG_AI_PROMPT=$(echo -n "${ARG_AI_PROMPT:-}" | base64 -d) -ARG_REPORT_TASKS=${ARG_REPORT_TASKS:-true} -ARG_ENABLE_BOUNDARY=${ARG_ENABLE_BOUNDARY:-false} -ARG_BOUNDARY_VERSION=${ARG_BOUNDARY_VERSION:-"latest"} -ARG_COMPILE_FROM_SOURCE=${ARG_COMPILE_FROM_SOURCE:-false} -ARG_USE_BOUNDARY_DIRECTLY=${ARG_USE_BOUNDARY_DIRECTLY:-false} -ARG_CODER_HOST=${ARG_CODER_HOST:-} - -echo "--------------------------------" - -printf "ARG_RESUME: %s\n" "$ARG_RESUME_SESSION_ID" -printf "ARG_CONTINUE: %s\n" "$ARG_CONTINUE" -printf "ARG_DANGEROUSLY_SKIP_PERMISSIONS: %s\n" "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" -printf "ARG_PERMISSION_MODE: %s\n" "$ARG_PERMISSION_MODE" -printf "ARG_AI_PROMPT: %s\n" "$ARG_AI_PROMPT" -printf "ARG_WORKDIR: %s\n" "$ARG_WORKDIR" -printf "ARG_REPORT_TASKS: %s\n" "$ARG_REPORT_TASKS" -printf "ARG_ENABLE_BOUNDARY: %s\n" "$ARG_ENABLE_BOUNDARY" -printf "ARG_BOUNDARY_VERSION: %s\n" "$ARG_BOUNDARY_VERSION" -printf "ARG_COMPILE_FROM_SOURCE: %s\n" "$ARG_COMPILE_FROM_SOURCE" -printf "ARG_USE_BOUNDARY_DIRECTLY: %s\n" "$ARG_USE_BOUNDARY_DIRECTLY" -printf "ARG_CODER_HOST: %s\n" "$ARG_CODER_HOST" - -echo "--------------------------------" - -function install_boundary() { - if [ "$ARG_COMPILE_FROM_SOURCE" = "true" ]; then - # Install boundary by compiling from source - echo "Compiling boundary from source (version: $ARG_BOUNDARY_VERSION)" - - echo "Removing existing boundary directory to allow re-running the script safely" - if [ -d boundary ]; then - rm -rf boundary - fi - - echo "Clone boundary repository" - git clone https://github.com/coder/boundary.git - cd boundary - git checkout "$ARG_BOUNDARY_VERSION" - - # Build the binary - make build - - # Install binary - sudo cp boundary /usr/local/bin/ - sudo chmod +x /usr/local/bin/boundary - elif [ "$ARG_USE_BOUNDARY_DIRECTLY" = "true" ]; then - # Install boundary using official install script - echo "Installing boundary using official install script (version: $ARG_BOUNDARY_VERSION)" - curl -fsSL https://raw.githubusercontent.com/coder/boundary/main/install.sh | bash -s -- --version "$ARG_BOUNDARY_VERSION" - else - # Use coder boundary subcommand (default) - no installation needed - echo "Using coder boundary subcommand (provided by Coder)" - fi -} - -function validate_claude_installation() { - if command_exists claude; then - printf "Claude Code is installed\n" - else - printf "Error: Claude Code is not installed. Please enable install_claude_code or install it manually\n" - exit 1 - fi -} - -# Hardcoded task session ID for Coder task reporting -# This ensures all task sessions use a consistent, predictable ID -TASK_SESSION_ID="cd32e253-ca16-4fd3-9825-d837e74ae3c2" - -get_project_dir() { - local workdir_normalized - workdir_normalized=$(echo "$ARG_WORKDIR" | tr '/._' '-') - echo "$HOME/.claude/projects/${workdir_normalized}" -} - -get_task_session_file() { - echo "$(get_project_dir)/${TASK_SESSION_ID}.jsonl" -} - -task_session_exists() { - local session_file - session_file=$(get_task_session_file) - - if [ -f "$session_file" ]; then - printf "Task session file found: %s\n" "$session_file" - return 0 - else - printf "Task session file not found: %s\n" "$session_file" - return 1 - fi -} - -is_valid_session() { - local session_file="$1" - - # Check if file exists and is not empty - # Empty files indicate the session was created but never used so they need to be removed - if [ ! -f "$session_file" ]; then - printf "Session validation failed: file does not exist\n" - return 1 - fi - - if [ ! -s "$session_file" ]; then - printf "Session validation failed: file is empty, removing stale file\n" - rm -f "$session_file" - return 1 - fi - - # Check for minimum session content - # Valid sessions need at least 2 lines: initial message and first response - local line_count - line_count=$(wc -l < "$session_file") - if [ "$line_count" -lt 2 ]; then - printf "Session validation failed: incomplete (only %s lines), removing incomplete file\n" "$line_count" - rm -f "$session_file" - return 1 - fi - - # Validate JSONL format by checking first 3 lines - # Claude session files use JSONL (JSON Lines) format where each line is valid JSON - if ! head -3 "$session_file" | jq empty 2> /dev/null; then - printf "Session validation failed: invalid JSONL format, removing corrupt file\n" - rm -f "$session_file" - return 1 - fi - - # Verify the session has a valid sessionId field - # This ensures the file structure matches Claude's session format - if ! grep -q '"sessionId"' "$session_file" \ - || ! grep -m 1 '"sessionId"' "$session_file" | jq -e '.sessionId' > /dev/null 2>&1; then - printf "Session validation failed: no valid sessionId found, removing malformed file\n" - rm -f "$session_file" - return 1 - fi - - printf "Session validation passed: %s\n" "$session_file" - return 0 -} - -has_any_sessions() { - local project_dir - project_dir=$(get_project_dir) - - if [ -d "$project_dir" ] && find "$project_dir" -maxdepth 1 -name "*.jsonl" -size +0c 2> /dev/null | grep -q .; then - printf "Sessions found in: %s\n" "$project_dir" - return 0 - else - printf "No sessions found in: %s\n" "$project_dir" - return 1 - fi -} - -ARGS=() - -function start_agentapi() { - # For Task reporting - export CODER_MCP_ALLOWED_TOOLS="coder_report_task" - - mkdir -p "$ARG_WORKDIR" - cd "$ARG_WORKDIR" - - if [ -n "$ARG_PERMISSION_MODE" ]; then - ARGS+=(--permission-mode "$ARG_PERMISSION_MODE") - fi - - if [ -n "$ARG_RESUME_SESSION_ID" ]; then - echo "Resuming specified session: $ARG_RESUME_SESSION_ID" - ARGS+=(--resume "$ARG_RESUME_SESSION_ID") - [ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ] && ARGS+=(--dangerously-skip-permissions) - - elif [ "$ARG_CONTINUE" = "true" ]; then - - if [ "$ARG_REPORT_TASKS" = "true" ]; then - local session_file - session_file=$(get_task_session_file) - - if task_session_exists && is_valid_session "$session_file"; then - echo "Resuming task session: $TASK_SESSION_ID" - ARGS+=(--resume "$TASK_SESSION_ID" --dangerously-skip-permissions) - else - echo "Starting new task session: $TASK_SESSION_ID" - ARGS+=(--session-id "$TASK_SESSION_ID" --dangerously-skip-permissions) - [ -n "$ARG_AI_PROMPT" ] && ARGS+=(-- "$ARG_AI_PROMPT") - fi - - else - if has_any_sessions; then - echo "Continuing most recent standalone session" - ARGS+=(--continue) - [ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ] && ARGS+=(--dangerously-skip-permissions) - else - echo "No sessions found, starting fresh standalone session" - [ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ] && ARGS+=(--dangerously-skip-permissions) - [ -n "$ARG_AI_PROMPT" ] && ARGS+=(-- "$ARG_AI_PROMPT") - fi - fi - - else - echo "Continue disabled, starting fresh session" - [ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ] && ARGS+=(--dangerously-skip-permissions) - [ -n "$ARG_AI_PROMPT" ] && ARGS+=(-- "$ARG_AI_PROMPT") - fi - - printf "Running claude code with args: %s\n" "$(printf '%q ' "${ARGS[@]}")" - - if [ "$ARG_ENABLE_BOUNDARY" = "true" ]; then - install_boundary - - printf "Starting with coder boundary enabled\n" - - BOUNDARY_ARGS+=() - - # Determine which boundary command to use - if [ "$ARG_COMPILE_FROM_SOURCE" = "true" ] || [ "$ARG_USE_BOUNDARY_DIRECTLY" = "true" ]; then - # Use boundary binary directly (from compilation or release installation) - BOUNDARY_CMD=("boundary") - else - # Use coder boundary subcommand (default) - # Copy coder binary to coder-no-caps. Copying strips CAP_NET_ADMIN capabilities - # from the binary, which is necessary because boundary doesn't work with - # privileged binaries (you can't launch privileged binaries inside network - # namespaces unless you have sys_admin). - CODER_NO_CAPS="$(dirname "$(which coder)")/coder-no-caps" - cp "$(which coder)" "$CODER_NO_CAPS" - BOUNDARY_CMD=("$CODER_NO_CAPS" "boundary") - fi - - agentapi server --type claude --term-width 67 --term-height 1190 -- \ - "${BOUNDARY_CMD[@]}" "${BOUNDARY_ARGS[@]}" -- \ - claude "${ARGS[@]}" - else - agentapi server --type claude --term-width 67 --term-height 1190 -- claude "${ARGS[@]}" - fi -} - -validate_claude_installation -start_agentapi