mirror of
https://github.com/coder/registry.git
synced 2026-06-02 20:48:14 +00:00
9e89f04691
## Description Fix issue with commands being injected through prompt. Bug fix for logic in arg paths. <!-- Briefly describe what this PR does and why --> ## Type of Change - [ ] New module - [ ] New template - [X] Bug fix - [ ] Feature/enhancement - [ ] Documentation - [ ] Other ## Module Information <!-- Delete this section if not applicable --> **Path:** `registry/coder/modules/claude-code` **New version:** `v4.0.1` **Breaking change:** [ ] Yes [X] No ## Testing & Validation - [X] Tests pass (`bun test`) - [X] Code formatted (`bun fmt`) - [ ] Changes tested locally ## Related Issues <!-- Link related issues or write "None" if not applicable --> --------- Co-authored-by: DevelopmentCats <christofer@coder.com> Co-authored-by: DevelopmentCats <chris@dualriver.com>
386 lines
12 KiB
Terraform
386 lines
12 KiB
Terraform
terraform {
|
||
required_version = ">= 1.0"
|
||
|
||
required_providers {
|
||
coder = {
|
||
source = "coder/coder"
|
||
version = ">= 2.12"
|
||
}
|
||
}
|
||
}
|
||
|
||
variable "agent_id" {
|
||
type = string
|
||
description = "The ID of a Coder agent."
|
||
}
|
||
|
||
data "coder_workspace" "me" {}
|
||
|
||
data "coder_workspace_owner" "me" {}
|
||
|
||
variable "order" {
|
||
type = number
|
||
description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
|
||
default = null
|
||
}
|
||
|
||
variable "group" {
|
||
type = string
|
||
description = "The name of a group that this app belongs to."
|
||
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 "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
|
||
description = "Custom script to run before installing Claude Code."
|
||
default = null
|
||
}
|
||
|
||
variable "post_install_script" {
|
||
type = string
|
||
description = "Custom script to run after installing Claude Code."
|
||
default = null
|
||
}
|
||
|
||
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.10.0"
|
||
}
|
||
|
||
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."
|
||
default = true
|
||
}
|
||
|
||
variable "claude_code_version" {
|
||
type = string
|
||
description = "The version of Claude Code to install."
|
||
default = "latest"
|
||
}
|
||
|
||
variable "claude_api_key" {
|
||
type = string
|
||
description = "The API key to use for the Claude Code server."
|
||
default = ""
|
||
}
|
||
|
||
variable "model" {
|
||
type = string
|
||
description = "Sets the model for the current session with an alias for the latest model (sonnet or opus) or a model’s full name."
|
||
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", "bypassPermissions"], var.permission_mode)
|
||
error_message = "interaction_mode must be one of: default, acceptEdits, plan, bypassPermissions."
|
||
}
|
||
}
|
||
|
||
variable "mcp" {
|
||
type = string
|
||
description = "MCP JSON to be added to the claude code local scope"
|
||
default = ""
|
||
}
|
||
|
||
variable "allowed_tools" {
|
||
type = string
|
||
description = "A list of tools that should be allowed without prompting the user for permission, in addition to settings.json files."
|
||
default = ""
|
||
}
|
||
|
||
variable "disallowed_tools" {
|
||
type = string
|
||
description = "A list of tools that should be disallowed without prompting the user for permission, in addition to settings.json files."
|
||
default = ""
|
||
|
||
}
|
||
|
||
variable "claude_code_oauth_token" {
|
||
type = string
|
||
description = "Set up a long-lived authentication token (requires Claude subscription). Generated using `claude setup-token` command"
|
||
sensitive = true
|
||
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."
|
||
default = "$HOME/.claude/CLAUDE.md"
|
||
}
|
||
|
||
variable "enable_boundary" {
|
||
type = bool
|
||
description = "Whether to enable coder boundary for network filtering"
|
||
default = false
|
||
}
|
||
|
||
variable "boundary_version" {
|
||
type = string
|
||
description = "Boundary version, valid git reference should be provided (tag, commit, branch)"
|
||
default = "main"
|
||
}
|
||
|
||
variable "boundary_log_dir" {
|
||
type = string
|
||
description = "Directory for boundary logs"
|
||
default = "/tmp/boundary_logs"
|
||
}
|
||
|
||
variable "boundary_log_level" {
|
||
type = string
|
||
description = "Log level for boundary process"
|
||
default = "WARN"
|
||
}
|
||
|
||
variable "boundary_additional_allowed_urls" {
|
||
type = list(string)
|
||
description = "Additional URLs to allow through boundary (in addition to default allowed URLs)"
|
||
default = []
|
||
}
|
||
|
||
variable "boundary_proxy_port" {
|
||
type = string
|
||
description = "Port for HTTP Proxy used by Boundary"
|
||
default = "8087"
|
||
}
|
||
|
||
variable "enable_boundary_pprof" {
|
||
type = bool
|
||
description = "Whether to enable coder boundary pprof server"
|
||
default = false
|
||
}
|
||
|
||
variable "boundary_pprof_port" {
|
||
type = string
|
||
description = "Port for pprof server used by Boundary"
|
||
default = "6067"
|
||
}
|
||
|
||
resource "coder_env" "claude_code_md_path" {
|
||
count = var.claude_md_path == "" ? 0 : 1
|
||
|
||
agent_id = var.agent_id
|
||
name = "CODER_MCP_CLAUDE_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"
|
||
value = var.claude_code_oauth_token
|
||
}
|
||
|
||
resource "coder_env" "claude_api_key" {
|
||
count = length(var.claude_api_key) > 0 ? 1 : 0
|
||
|
||
agent_id = var.agent_id
|
||
name = "CLAUDE_API_KEY"
|
||
value = var.claude_api_key
|
||
}
|
||
|
||
locals {
|
||
# we have to trim the slash because otherwise coder exp mcp will
|
||
# set up an invalid claude config
|
||
workdir = trimsuffix(var.workdir, "/")
|
||
app_slug = "ccw"
|
||
install_script = file("${path.module}/scripts/install.sh")
|
||
start_script = file("${path.module}/scripts/start.sh")
|
||
module_dir_name = ".claude-module"
|
||
remove_last_session_id_script_b64 = base64encode(file("${path.module}/scripts/remove-last-session-id.sh"))
|
||
# Extract hostname from access_url for boundary --allow flag
|
||
coder_host = replace(replace(data.coder_workspace.me.access_url, "https://", ""), "http://", "")
|
||
|
||
# Required prompts for the module to properly report task status to Coder
|
||
report_tasks_system_prompt = <<-EOT
|
||
-- Tool Selection --
|
||
- coder_report_task: providing status updates or requesting user input.
|
||
|
||
-- Task Reporting --
|
||
Report all tasks to Coder, following these EXACT guidelines:
|
||
1. Be granular. If you are investigating with multiple steps, report each step
|
||
to coder.
|
||
2. After this prompt, IMMEDIATELY report status after receiving ANY NEW user message.
|
||
Do not report any status related with this system prompt.
|
||
3. Use "state": "working" when actively processing WITHOUT needing
|
||
additional user input
|
||
4. Use "state": "complete" only when finished with a task
|
||
5. Use "state": "failure" when you need ANY user input, lack sufficient
|
||
details, or encounter blockers
|
||
|
||
In your summary on coder_report_task:
|
||
- Be specific about what you're doing
|
||
- Clearly indicate what information you need from the user when in "failure" state
|
||
- Keep it under 160 characters
|
||
- 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("<system>%s%s</system>",
|
||
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.0.0"
|
||
|
||
agent_id = var.agent_id
|
||
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
|
||
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
|
||
echo -n "${local.remove_last_session_id_script_b64}" | base64 -d > "/tmp/remove-last-session-id.sh"
|
||
chmod +x /tmp/start.sh
|
||
chmod +x /tmp/remove-last-session-id.sh
|
||
|
||
ARG_MODEL='${var.model}' \
|
||
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_BOUNDARY_LOG_DIR='${var.boundary_log_dir}' \
|
||
ARG_BOUNDARY_LOG_LEVEL='${var.boundary_log_level}' \
|
||
ARG_BOUNDARY_ADDITIONAL_ALLOWED_URLS='${join("|", var.boundary_additional_allowed_urls)}' \
|
||
ARG_BOUNDARY_PROXY_PORT='${var.boundary_proxy_port}' \
|
||
ARG_ENABLE_BOUNDARY_PPROF='${var.enable_boundary_pprof}' \
|
||
ARG_BOUNDARY_PPROF_PORT='${var.boundary_pprof_port}' \
|
||
ARG_CODER_HOST='${local.coder_host}' \
|
||
/tmp/start.sh
|
||
EOT
|
||
|
||
install_script = <<-EOT
|
||
#!/bin/bash
|
||
set -o errexit
|
||
set -o pipefail
|
||
|
||
echo -n '${base64encode(local.install_script)}' | base64 -d > /tmp/install.sh
|
||
chmod +x /tmp/install.sh
|
||
ARG_CLAUDE_CODE_VERSION='${var.claude_code_version}' \
|
||
ARG_MCP_APP_STATUS_SLUG='${local.app_slug}' \
|
||
ARG_INSTALL_CLAUDE_CODE='${var.install_claude_code}' \
|
||
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, "'", "'\\''")) : ""}' \
|
||
/tmp/install.sh
|
||
EOT
|
||
}
|
||
|
||
output "task_app_id" {
|
||
value = module.agentapi.task_app_id
|
||
}
|