mirror of
https://github.com/coder/registry.git
synced 2026-06-03 13:08:14 +00:00
Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6802df9d13 | |||
| a7e0b09aa4 | |||
| 229056b344 | |||
| f257efd8e1 | |||
| 9258f1857f | |||
| 5c4480daa3 | |||
| 0ffd71d443 | |||
| 60ed61368e | |||
| 5f4d7bf1b4 | |||
| c411657a67 | |||
| d8a96435c6 | |||
| 2e8870bcee | |||
| d4efc09b20 | |||
| 4b03bce6f7 | |||
| ae48c1043b | |||
| 73af151f8d | |||
| c115d860f7 | |||
| 395f170d07 | |||
| 65189bc068 | |||
| d3b5057819 | |||
| dd86d3d1d8 | |||
| 1dee0012f5 | |||
| ef0f597d54 | |||
| aaf2c4e0dd | |||
| 090fa7dd1d | |||
| a630ffa42a | |||
| 93f9ec3708 | |||
| 5870805d0f | |||
| 6806985778 | |||
| 149e65b49f | |||
| 327f05487d | |||
| 19dc50db3e | |||
| 48564621ad | |||
| d0ef4f426b | |||
| c2fa87aea6 | |||
| 63eff436eb | |||
| 250b64e44f | |||
| 78a0d14863 | |||
| 0d0bfa7131 | |||
| b32b2d4329 | |||
| 8664ded490 | |||
| 2ed4be2172 | |||
| d718c3b4e9 | |||
| 19f2a8f3ec | |||
| e12cd61e45 | |||
| 09386a43cd | |||
| 50eb191eaa | |||
| bdc8aea37f | |||
| c6a7d049bd | |||
| 65a73a8708 | |||
| 05c5724561 | |||
| 0d03fa4e58 | |||
| f3bfa9cc8d | |||
| a91c8845cb | |||
| 2c00575203 | |||
| c5d83570bc | |||
| dd96e8c74b |
@@ -3,7 +3,7 @@ display_name: Claude Code
|
||||
description: Run the Claude Code agent in your workspace.
|
||||
icon: ../../../../.icons/claude.svg
|
||||
verified: true
|
||||
tags: [agent, claude-code, ai, tasks, anthropic]
|
||||
tags: [agent, claude-code, ai, tasks, anthropic, aibridge]
|
||||
---
|
||||
|
||||
# Claude Code
|
||||
@@ -13,7 +13,7 @@ Run the [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude
|
||||
```tf
|
||||
module "claude-code" {
|
||||
source = "registry.coder.com/coder/claude-code/coder"
|
||||
version = "4.8.1"
|
||||
version = "4.5.0"
|
||||
agent_id = coder_agent.main.id
|
||||
workdir = "/home/coder/project"
|
||||
claude_api_key = "xxxx-xxxxx-xxxx"
|
||||
@@ -45,7 +45,7 @@ This example shows how to configure the Claude Code module to run the agent behi
|
||||
```tf
|
||||
module "claude-code" {
|
||||
source = "registry.coder.com/coder/claude-code/coder"
|
||||
version = "4.8.1"
|
||||
version = "4.5.0"
|
||||
agent_id = coder_agent.main.id
|
||||
workdir = "/home/coder/project"
|
||||
enable_boundary = true
|
||||
@@ -53,25 +53,68 @@ module "claude-code" {
|
||||
}
|
||||
```
|
||||
|
||||
### Usage with Tasks and Advanced Configuration
|
||||
### Usage with AI Bridge
|
||||
|
||||
This example shows how to configure the Claude Code module with an AI prompt, API key shared by all users of the template, and other custom settings.
|
||||
[AI Bridge](https://coder.com/docs/ai-coder/ai-bridge) is a Premium Coder feature that provides centralized LLM proxy management. To use AI Bridge, set `enable_aibridge = true`.
|
||||
|
||||
For tasks integration with AI Bridge, add `enable_aibridge = true` to the [Usage with Tasks](#usage-with-tasks) example below.
|
||||
|
||||
#### Standalone usage with AI Bridge
|
||||
|
||||
```tf
|
||||
module "claude-code" {
|
||||
source = "registry.coder.com/coder/claude-code/coder"
|
||||
version = "4.5.0"
|
||||
agent_id = coder_agent.main.id
|
||||
workdir = "/home/coder/project"
|
||||
enable_aibridge = true
|
||||
}
|
||||
```
|
||||
|
||||
When `enable_aibridge = true`, the module automatically sets:
|
||||
|
||||
- `ANTHROPIC_BASE_URL` to `${data.coder_workspace.me.access_url}/api/v2/aibridge/anthropic`
|
||||
- `CLAUDE_API_KEY` to the workspace owner's session token
|
||||
|
||||
This allows Claude Code to route API requests through Coder's AI Bridge instead of directly to Anthropic's API.
|
||||
Template build will fail if either `claude_api_key` or `claude_code_oauth_token` is provided alongside `enable_aibridge = true`.
|
||||
|
||||
### Usage with Tasks
|
||||
|
||||
This example shows how to configure Claude Code with Coder tasks.
|
||||
|
||||
```tf
|
||||
resource "coder_ai_task" "task" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
app_id = module.claude-code.task_app_id
|
||||
}
|
||||
|
||||
data "coder_task" "me" {}
|
||||
|
||||
module "claude-code" {
|
||||
source = "registry.coder.com/coder/claude-code/coder"
|
||||
version = "4.5.0"
|
||||
agent_id = coder_agent.main.id
|
||||
workdir = "/home/coder/project"
|
||||
claude_api_key = "xxxx-xxxxx-xxxx"
|
||||
ai_prompt = data.coder_task.me.prompt
|
||||
|
||||
# Optional: route through AI Bridge (Premium feature)
|
||||
# enable_aibridge = true
|
||||
}
|
||||
```
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
This example shows additional configuration options for version pinning, custom models, and MCP servers.
|
||||
|
||||
> [!NOTE]
|
||||
> When a specific `claude_code_version` (other than "latest") is provided, the module will install Claude Code via npm instead of the official installer. This allows for version pinning. The `claude_binary_path` variable can be used to specify where a pre-installed Claude binary is located.
|
||||
|
||||
```tf
|
||||
data "coder_parameter" "ai_prompt" {
|
||||
type = "string"
|
||||
name = "AI Prompt"
|
||||
default = ""
|
||||
description = "Initial task prompt for Claude Code."
|
||||
mutable = true
|
||||
}
|
||||
|
||||
module "claude-code" {
|
||||
source = "registry.coder.com/coder/claude-code/coder"
|
||||
version = "4.8.1"
|
||||
version = "4.5.0"
|
||||
agent_id = coder_agent.main.id
|
||||
workdir = "/home/coder/project"
|
||||
|
||||
@@ -83,9 +126,7 @@ module "claude-code" {
|
||||
claude_binary_path = "/opt/claude/bin" # Path to pre-installed Claude binary
|
||||
agentapi_version = "0.11.4"
|
||||
|
||||
ai_prompt = data.coder_parameter.ai_prompt.value
|
||||
model = "sonnet"
|
||||
|
||||
model = "sonnet"
|
||||
permission_mode = "plan"
|
||||
|
||||
mcp = <<-EOF
|
||||
@@ -108,7 +149,7 @@ Run and configure Claude Code as a standalone CLI in your workspace.
|
||||
```tf
|
||||
module "claude-code" {
|
||||
source = "registry.coder.com/coder/claude-code/coder"
|
||||
version = "4.8.1"
|
||||
version = "4.5.0"
|
||||
agent_id = coder_agent.main.id
|
||||
workdir = "/home/coder/project"
|
||||
install_claude_code = true
|
||||
@@ -130,7 +171,7 @@ variable "claude_code_oauth_token" {
|
||||
|
||||
module "claude-code" {
|
||||
source = "registry.coder.com/coder/claude-code/coder"
|
||||
version = "4.8.1"
|
||||
version = "4.5.0"
|
||||
agent_id = coder_agent.main.id
|
||||
workdir = "/home/coder/project"
|
||||
claude_code_oauth_token = var.claude_code_oauth_token
|
||||
@@ -203,7 +244,7 @@ resource "coder_env" "bedrock_api_key" {
|
||||
|
||||
module "claude-code" {
|
||||
source = "registry.coder.com/coder/claude-code/coder"
|
||||
version = "4.8.1"
|
||||
version = "4.5.0"
|
||||
agent_id = coder_agent.main.id
|
||||
workdir = "/home/coder/project"
|
||||
model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
|
||||
@@ -260,7 +301,7 @@ resource "coder_env" "google_application_credentials" {
|
||||
|
||||
module "claude-code" {
|
||||
source = "registry.coder.com/coder/claude-code/coder"
|
||||
version = "4.8.1"
|
||||
version = "4.5.0"
|
||||
agent_id = coder_agent.main.id
|
||||
workdir = "/home/coder/project"
|
||||
model = "claude-sonnet-4@20250514"
|
||||
|
||||
@@ -67,7 +67,7 @@ variable "cli_app_display_name" {
|
||||
|
||||
variable "pre_install_script" {
|
||||
type = string
|
||||
description = "Custom script to run before installing Claude Code. Can be used for dependency ordering between modules (e.g., waiting for git-clone to complete before Claude Code initialization)."
|
||||
description = "Custom script to run before installing Claude Code."
|
||||
default = null
|
||||
}
|
||||
|
||||
@@ -228,6 +228,28 @@ variable "compile_boundary_from_source" {
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "enable_aibridge" {
|
||||
type = bool
|
||||
description = "Use AI Bridge for Claude Code. https://coder.com/docs/ai-coder/ai-bridge"
|
||||
default = false
|
||||
|
||||
validation {
|
||||
condition = !(var.enable_aibridge && length(var.claude_api_key) > 0)
|
||||
error_message = "claude_api_key cannot be provided when enable_aibridge is true. AI Bridge automatically authenticates the client using Coder credentials."
|
||||
}
|
||||
|
||||
validation {
|
||||
condition = !(var.enable_aibridge && length(var.claude_code_oauth_token) > 0)
|
||||
error_message = "claude_code_oauth_token cannot be provided when enable_aibridge is true. AI Bridge automatically authenticates the client using Coder credentials."
|
||||
}
|
||||
}
|
||||
|
||||
variable "cli_command" {
|
||||
type = string
|
||||
description = "The command to run for the Claude Code CLI app when tasks are disabled."
|
||||
default = ""
|
||||
}
|
||||
|
||||
resource "coder_env" "claude_code_md_path" {
|
||||
count = var.claude_md_path == "" ? 0 : 1
|
||||
agent_id = var.agent_id
|
||||
@@ -248,10 +270,9 @@ resource "coder_env" "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
|
||||
value = var.enable_aibridge ? data.coder_workspace_owner.me.session_token : var.claude_api_key
|
||||
}
|
||||
|
||||
resource "coder_env" "disable_autoupdater" {
|
||||
@@ -281,6 +302,13 @@ resource "coder_env" "anthropic_model" {
|
||||
value = var.model
|
||||
}
|
||||
|
||||
resource "coder_env" "anthropic_base_url" {
|
||||
count = var.enable_aibridge ? 1 : 0
|
||||
agent_id = var.agent_id
|
||||
name = "ANTHROPIC_BASE_URL"
|
||||
value = "${data.coder_workspace.me.access_url}/api/v2/aibridge/anthropic"
|
||||
}
|
||||
|
||||
locals {
|
||||
# we have to trim the slash because otherwise coder exp mcp will
|
||||
# set up an invalid claude config
|
||||
@@ -322,12 +350,89 @@ locals {
|
||||
var.report_tasks ? format("\n%s\n", local.report_tasks_system_prompt) : "",
|
||||
local.custom_system_prompt != "" ? format("\n%s\n", local.custom_system_prompt) : ""
|
||||
)
|
||||
|
||||
# Common environment variables for install script
|
||||
install_env_vars = <<-EOT
|
||||
export ARG_CLAUDE_CODE_VERSION='${var.claude_code_version}'
|
||||
export ARG_MCP_APP_STATUS_SLUG='${local.app_slug}'
|
||||
export ARG_INSTALL_CLAUDE_CODE='${var.install_claude_code}'
|
||||
export ARG_CLAUDE_BINARY_PATH='${var.claude_binary_path}'
|
||||
export ARG_INSTALL_VIA_NPM='${var.install_via_npm}'
|
||||
export ARG_REPORT_TASKS='${var.report_tasks}'
|
||||
export ARG_WORKDIR='${local.workdir}'
|
||||
export ARG_ALLOWED_TOOLS='${var.allowed_tools}'
|
||||
export ARG_DISALLOWED_TOOLS='${var.disallowed_tools}'
|
||||
export ARG_MCP='${var.mcp != null ? base64encode(replace(var.mcp, "'", "'\\''")) : ""}'
|
||||
export ARG_ENABLE_AIBRIDGE='${var.enable_aibridge}'
|
||||
EOT
|
||||
|
||||
# Common environment variables for start script
|
||||
start_env_vars = <<-EOT
|
||||
export ARG_RESUME_SESSION_ID='${var.resume_session_id}'
|
||||
export ARG_CONTINUE='${var.continue}'
|
||||
export ARG_DANGEROUSLY_SKIP_PERMISSIONS='${var.dangerously_skip_permissions}'
|
||||
export ARG_PERMISSION_MODE='${var.permission_mode}'
|
||||
export ARG_WORKDIR='${local.workdir}'
|
||||
export ARG_AI_PROMPT='${base64encode(var.ai_prompt)}'
|
||||
export ARG_REPORT_TASKS='${var.report_tasks}'
|
||||
export ARG_ENABLE_BOUNDARY='${var.enable_boundary}'
|
||||
export ARG_BOUNDARY_VERSION='${var.boundary_version}'
|
||||
export ARG_COMPILE_FROM_SOURCE='${var.compile_boundary_from_source}'
|
||||
export ARG_CODER_HOST='${local.coder_host}'
|
||||
export ARG_NON_AGENTAPI_CLI='${!var.report_tasks && var.cli_app ? true : false}'
|
||||
EOT
|
||||
|
||||
# Reusable install script command
|
||||
install_command = <<-EOT
|
||||
#!/bin/bash
|
||||
set -o pipefail
|
||||
|
||||
echo -n '${base64encode(local.install_script)}' | base64 -d > /tmp/install.sh
|
||||
|
||||
chmod +x /tmp/install.sh
|
||||
${local.install_env_vars}
|
||||
/tmp/install.sh
|
||||
EOT
|
||||
|
||||
# Reusable start script command for agentapi module
|
||||
agentapi_start_command = <<-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
|
||||
|
||||
${local.start_env_vars}
|
||||
/tmp/start.sh
|
||||
EOT
|
||||
}
|
||||
|
||||
resource "coder_script" "install_agent" {
|
||||
count = !var.report_tasks ? 1 : 0
|
||||
|
||||
agent_id = var.agent_id
|
||||
display_name = "Install agent"
|
||||
run_on_start = true
|
||||
log_path = "/home/coder/install.log"
|
||||
script = local.install_command
|
||||
}
|
||||
|
||||
resource "coder_app" "agent_cli" {
|
||||
count = (!var.report_tasks && var.cli_app) ? 1 : 0
|
||||
|
||||
agent_id = var.agent_id
|
||||
slug = local.app_slug
|
||||
display_name = var.cli_app_display_name
|
||||
|
||||
command = length(trimprefix(var.cli_command, " ")) > 0 ? var.cli_command : local.agentapi_start_command
|
||||
}
|
||||
|
||||
|
||||
module "agentapi" {
|
||||
source = "registry.coder.com/coder/agentapi/coder"
|
||||
version = "2.0.0"
|
||||
|
||||
count = var.report_tasks ? 1 : 0
|
||||
agent_id = var.agent_id
|
||||
web_app_slug = local.app_slug
|
||||
web_app_order = var.order
|
||||
@@ -344,48 +449,10 @@ module "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
|
||||
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_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_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, "'", "'\\''")) : ""}' \
|
||||
/tmp/install.sh
|
||||
EOT
|
||||
start_script = local.agentapi_start_command
|
||||
install_script = local.install_command
|
||||
}
|
||||
|
||||
output "task_app_id" {
|
||||
value = module.agentapi.task_app_id
|
||||
value = try(module.agentapi[0].task_app_id, null)
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ run "test_claude_code_with_api_key" {
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = coder_env.claude_api_key[0].value == "test-api-key-123"
|
||||
condition = coder_env.claude_api_key.value == "test-api-key-123"
|
||||
error_message = "Claude API key value should match the input"
|
||||
}
|
||||
}
|
||||
@@ -288,3 +288,94 @@ run "test_claude_report_tasks_disabled" {
|
||||
error_message = "System prompt should end with </system>"
|
||||
}
|
||||
}
|
||||
|
||||
run "test_aibridge_enabled" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent-aibridge"
|
||||
workdir = "/home/coder/aibridge"
|
||||
enable_aibridge = true
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.enable_aibridge == true
|
||||
error_message = "AI Bridge should be enabled"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = coder_env.anthropic_base_url[0].name == "ANTHROPIC_BASE_URL"
|
||||
error_message = "ANTHROPIC_BASE_URL environment variable should be set"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = length(regexall("/api/v2/aibridge/anthropic", coder_env.anthropic_base_url[0].value)) > 0
|
||||
error_message = "ANTHROPIC_BASE_URL should point to AI Bridge endpoint"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = coder_env.claude_api_key.name == "CLAUDE_API_KEY"
|
||||
error_message = "CLAUDE_API_KEY environment variable should be set"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = coder_env.claude_api_key.value == data.coder_workspace_owner.me.session_token
|
||||
error_message = "CLAUDE_API_KEY should use workspace owner's session token when aibridge is enabled"
|
||||
}
|
||||
}
|
||||
|
||||
run "test_aibridge_validation_with_api_key" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent-validation"
|
||||
workdir = "/home/coder/test"
|
||||
enable_aibridge = true
|
||||
claude_api_key = "test-api-key"
|
||||
}
|
||||
|
||||
expect_failures = [
|
||||
var.enable_aibridge,
|
||||
]
|
||||
}
|
||||
|
||||
run "test_aibridge_validation_with_oauth_token" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent-validation"
|
||||
workdir = "/home/coder/test"
|
||||
enable_aibridge = true
|
||||
claude_code_oauth_token = "test-oauth-token"
|
||||
}
|
||||
|
||||
expect_failures = [
|
||||
var.enable_aibridge,
|
||||
]
|
||||
}
|
||||
|
||||
run "test_aibridge_disabled_with_api_key" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent-no-aibridge"
|
||||
workdir = "/home/coder/test"
|
||||
enable_aibridge = false
|
||||
claude_api_key = "test-api-key-xyz"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.enable_aibridge == false
|
||||
error_message = "AI Bridge should be disabled"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = coder_env.claude_api_key.value == "test-api-key-xyz"
|
||||
error_message = "CLAUDE_API_KEY should use the provided API key when aibridge is disabled"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = length(coder_env.anthropic_base_url) == 0
|
||||
error_message = "ANTHROPIC_BASE_URL should not be set when aibridge is disabled"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ ARG_MCP_APP_STATUS_SLUG=${ARG_MCP_APP_STATUS_SLUG:-}
|
||||
ARG_MCP=$(echo -n "${ARG_MCP:-}" | base64 -d)
|
||||
ARG_ALLOWED_TOOLS=${ARG_ALLOWED_TOOLS:-}
|
||||
ARG_DISALLOWED_TOOLS=${ARG_DISALLOWED_TOOLS:-}
|
||||
ARG_ENABLE_AIBRIDGE=${ARG_ENABLE_AIBRIDGE:-false}
|
||||
|
||||
echo "--------------------------------"
|
||||
|
||||
@@ -31,6 +32,7 @@ printf "ARG_MCP_APP_STATUS_SLUG: %s\n" "$ARG_MCP_APP_STATUS_SLUG"
|
||||
printf "ARG_MCP: %s\n" "$ARG_MCP"
|
||||
printf "ARG_ALLOWED_TOOLS: %s\n" "$ARG_ALLOWED_TOOLS"
|
||||
printf "ARG_DISALLOWED_TOOLS: %s\n" "$ARG_DISALLOWED_TOOLS"
|
||||
printf "ARG_ENABLE_AIBRIDGE: %s\n" "$ARG_ENABLE_AIBRIDGE"
|
||||
|
||||
echo "--------------------------------"
|
||||
|
||||
@@ -133,8 +135,8 @@ function setup_claude_configurations() {
|
||||
function configure_standalone_mode() {
|
||||
echo "Configuring Claude Code for standalone mode..."
|
||||
|
||||
if [ -z "${CLAUDE_API_KEY:-}" ]; then
|
||||
echo "Note: CLAUDE_API_KEY not set, skipping authentication setup"
|
||||
if [ -z "${CLAUDE_API_KEY:-}" ] && [ "$ARG_ENABLE_AIBRIDGE" = "false" ]; then
|
||||
echo "Note: Neither claude_api_key nor enable_aibridge is set, skipping authentication setup"
|
||||
return
|
||||
fi
|
||||
|
||||
@@ -147,8 +149,7 @@ function configure_standalone_mode() {
|
||||
if [ -f "$claude_config" ]; then
|
||||
echo "Updating existing Claude configuration at $claude_config"
|
||||
|
||||
jq --arg apikey "${CLAUDE_API_KEY:-}" \
|
||||
--arg workdir "$ARG_WORKDIR" \
|
||||
jq --arg workdir "$ARG_WORKDIR" --arg apikey "${CLAUDE_API_KEY:-}" \
|
||||
'.autoUpdaterStatus = "disabled" |
|
||||
.bypassPermissionsModeAccepted = true |
|
||||
.hasAcknowledgedCostThreshold = true |
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
true > "$HOME/start.log"
|
||||
|
||||
command_exists() {
|
||||
command -v "$1" > /dev/null 2>&1
|
||||
}
|
||||
@@ -17,34 +19,41 @@ ARG_ENABLE_BOUNDARY=${ARG_ENABLE_BOUNDARY:-false}
|
||||
ARG_BOUNDARY_VERSION=${ARG_BOUNDARY_VERSION:-"main"}
|
||||
ARG_COMPILE_FROM_SOURCE=${ARG_COMPILE_FROM_SOURCE:-false}
|
||||
ARG_CODER_HOST=${ARG_CODER_HOST:-}
|
||||
ARG_NON_AGENTAPI_CLI=${ARG_NON_AGENTAPI_CLI:-false}
|
||||
|
||||
echo "--------------------------------"
|
||||
log() {
|
||||
if [[ "${ARG_NON_AGENTAPI_CLI}" = "true" ]]; then
|
||||
printf -- "$@" >> "$HOME/start.log"
|
||||
else
|
||||
printf -- "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
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_CODER_HOST: %s\n" "$ARG_CODER_HOST"
|
||||
log "ARG_RESUME: %s\n" "$ARG_RESUME_SESSION_ID"
|
||||
log "ARG_CONTINUE: %s\n" "$ARG_CONTINUE"
|
||||
log "ARG_DANGEROUSLY_SKIP_PERMISSIONS: %s\n" "$ARG_DANGEROUSLY_SKIP_PERMISSIONS"
|
||||
log "ARG_PERMISSION_MODE: %s\n" "$ARG_PERMISSION_MODE"
|
||||
log "ARG_AI_PROMPT: %s\n" "$ARG_AI_PROMPT"
|
||||
log "ARG_WORKDIR: %s\n" "$ARG_WORKDIR"
|
||||
log "ARG_REPORT_TASKS: %s\n" "$ARG_REPORT_TASKS"
|
||||
log "ARG_ENABLE_BOUNDARY: %s\n" "$ARG_ENABLE_BOUNDARY"
|
||||
log "ARG_BOUNDARY_VERSION: %s\n" "$ARG_BOUNDARY_VERSION"
|
||||
log "ARG_COMPILE_FROM_SOURCE: %s\n" "$ARG_COMPILE_FROM_SOURCE"
|
||||
log "ARG_CODER_HOST: %s\n" "$ARG_CODER_HOST"
|
||||
|
||||
echo "--------------------------------"
|
||||
log "--------------------------------\n"
|
||||
|
||||
function install_boundary() {
|
||||
if [ "${ARG_COMPILE_FROM_SOURCE:-false}" = "true" ]; then
|
||||
# Install boundary by compiling from source
|
||||
echo "Compiling boundary from source (version: $ARG_BOUNDARY_VERSION)"
|
||||
log "Compiling boundary from source (version: $ARG_BOUNDARY_VERSION)\n"
|
||||
|
||||
echo "Removing existing boundary directory to allow re-running the script safely"
|
||||
log "Removing existing boundary directory to allow re-running the script safely\n"
|
||||
if [ -d boundary ]; then
|
||||
rm -rf boundary
|
||||
fi
|
||||
|
||||
echo "Clone boundary repository"
|
||||
log "Clone boundary repository\n"
|
||||
git clone https://github.com/coder/boundary.git
|
||||
cd boundary
|
||||
git checkout "$ARG_BOUNDARY_VERSION"
|
||||
@@ -58,16 +67,16 @@ function install_boundary() {
|
||||
sudo chmod +x /usr/local/bin/boundary-run
|
||||
else
|
||||
# Install boundary using official install script
|
||||
echo "Installing boundary using official install script (version: $ARG_BOUNDARY_VERSION)"
|
||||
log "Installing boundary using official install script (version: $ARG_BOUNDARY_VERSION)\n"
|
||||
curl -fsSL https://raw.githubusercontent.com/coder/boundary/main/install.sh | bash -s -- --version "$ARG_BOUNDARY_VERSION"
|
||||
fi
|
||||
}
|
||||
|
||||
function validate_claude_installation() {
|
||||
if command_exists claude; then
|
||||
printf "Claude Code is installed\n"
|
||||
log "Claude Code is installed\n"
|
||||
else
|
||||
printf "Error: Claude Code is not installed. Please enable install_claude_code or install it manually\n"
|
||||
log "Error: Claude Code is not installed. Please enable install_claude_code or install it manually\n"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
@@ -91,10 +100,10 @@ task_session_exists() {
|
||||
session_file=$(get_task_session_file)
|
||||
|
||||
if [ -f "$session_file" ]; then
|
||||
printf "Task session file found: %s\n" "$session_file"
|
||||
log "Task session file found: %s\n" "$session_file"
|
||||
return 0
|
||||
else
|
||||
printf "Task session file not found: %s\n" "$session_file"
|
||||
log "Task session file not found: %s\n" "$session_file"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
@@ -105,12 +114,12 @@ is_valid_session() {
|
||||
# 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"
|
||||
log "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"
|
||||
log "Session validation failed: file is empty, removing stale file\n"
|
||||
rm -f "$session_file"
|
||||
return 1
|
||||
fi
|
||||
@@ -120,7 +129,7 @@ is_valid_session() {
|
||||
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"
|
||||
log "Session validation failed: incomplete (only %s lines), removing incomplete file\n" "$line_count"
|
||||
rm -f "$session_file"
|
||||
return 1
|
||||
fi
|
||||
@@ -128,7 +137,7 @@ is_valid_session() {
|
||||
# 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"
|
||||
log "Session validation failed: invalid JSONL format, removing corrupt file\n"
|
||||
rm -f "$session_file"
|
||||
return 1
|
||||
fi
|
||||
@@ -137,12 +146,12 @@ is_valid_session() {
|
||||
# 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"
|
||||
log "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"
|
||||
log "Session validation passed: %s\n" "$session_file"
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -151,16 +160,21 @@ has_any_sessions() {
|
||||
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"
|
||||
log "Sessions found in: %s\n" "$project_dir"
|
||||
return 0
|
||||
else
|
||||
printf "No sessions found in: %s\n" "$project_dir"
|
||||
log "No sessions found in: %s\n" "$project_dir"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
ARGS=()
|
||||
|
||||
CORE_COMMAND=()
|
||||
if [[ "${ARG_REPORT_TASKS}" == "true" ]]; then
|
||||
CORE_COMMAND+=(agentapi server --type claude --term-width 67 --term-height 1190 --)
|
||||
fi
|
||||
|
||||
function start_agentapi() {
|
||||
# For Task reporting
|
||||
export CODER_MCP_ALLOWED_TOOLS="coder_report_task"
|
||||
@@ -173,7 +187,7 @@ function start_agentapi() {
|
||||
fi
|
||||
|
||||
if [ -n "$ARG_RESUME_SESSION_ID" ]; then
|
||||
echo "Resuming specified session: $ARG_RESUME_SESSION_ID"
|
||||
log "Resuming specified session: $ARG_RESUME_SESSION_ID"
|
||||
ARGS+=(--resume "$ARG_RESUME_SESSION_ID")
|
||||
[ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ] && ARGS+=(--dangerously-skip-permissions)
|
||||
|
||||
@@ -184,46 +198,45 @@ function start_agentapi() {
|
||||
session_file=$(get_task_session_file)
|
||||
|
||||
if task_session_exists && is_valid_session "$session_file"; then
|
||||
echo "Resuming task session: $TASK_SESSION_ID"
|
||||
log "Resuming task session: $TASK_SESSION_ID"
|
||||
ARGS+=(--resume "$TASK_SESSION_ID" --dangerously-skip-permissions)
|
||||
else
|
||||
echo "Starting new task session: $TASK_SESSION_ID"
|
||||
log "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"
|
||||
log "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"
|
||||
log "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"
|
||||
log "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[@]}")"
|
||||
log "Running claude code with args: %s\n" "$(printf '%q ' "${ARGS[@]}")"
|
||||
|
||||
if [ "${ARG_ENABLE_BOUNDARY:-false}" = "true" ]; then
|
||||
install_boundary
|
||||
|
||||
printf "Starting with coder boundary enabled\n"
|
||||
log "Starting with coder boundary enabled\n"
|
||||
|
||||
BOUNDARY_ARGS+=()
|
||||
|
||||
agentapi server --type claude --term-width 67 --term-height 1190 -- \
|
||||
boundary-run "${BOUNDARY_ARGS[@]}" -- \
|
||||
"${CORE_COMMAND[@]}" boundary-run "${BOUNDARY_ARGS[@]}" -- \
|
||||
claude "${ARGS[@]}"
|
||||
else
|
||||
agentapi server --type claude --term-width 67 --term-height 1190 -- claude "${ARGS[@]}"
|
||||
"${CORE_COMMAND[@]}" claude "${ARGS[@]}"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user