Compare commits

...

57 Commits

Author SHA1 Message Date
35C4n0r 6802df9d13 hopefully final wip 2026-01-20 17:49:05 +00:00
35C4n0r a7e0b09aa4 wip 2026-01-20 17:43:22 +00:00
35C4n0r 229056b344 wip 2026-01-20 17:35:20 +00:00
35C4n0r f257efd8e1 wip 2026-01-20 17:19:25 +00:00
35C4n0r 9258f1857f wip 2026-01-20 17:14:57 +00:00
35C4n0r 5c4480daa3 wip 2026-01-19 18:44:09 +00:00
35C4n0r 0ffd71d443 wip 2026-01-19 18:31:34 +00:00
35C4n0r 60ed61368e bun fmt 2026-01-19 18:14:41 +00:00
35C4n0r 5f4d7bf1b4 feat: merge to main changes 2026-01-19 18:13:21 +00:00
35C4n0r c411657a67 feat: merge to main changes 2026-01-19 18:10:00 +00:00
35C4n0r d8a96435c6 Merge branch 'main' into feat-conditional-agentapi
# Conflicts:
#	registry/coder/modules/claude-code/main.tf
#	registry/coder/modules/claude-code/scripts/install.sh
#	registry/coder/modules/claude-code/scripts/start.sh
2026-01-19 18:05:31 +00:00
35C4n0r 2e8870bcee feat(coder/modules/claude-code): add support for aibridge (#657)
## Description

- Add support for AI Bridge

## Type of Change

- [ ] New module
- [ ] New template
- [ ] Bug fix
- [x] Feature/enhancement
- [ ] Documentation
- [ ] Other

## Module Information

<!-- Delete this section if not applicable -->

**Path:** `registry/coder/modules/claude-code`  
**New version:** `v4.5.0`  
**Breaking change:** [ ] Yes [x] No

## Testing & Validation

- [x] Tests pass (`bun test`)
- [x] Code formatted (`bun fmt`)
- [x] Changes tested locally

## Related Issues

Closes: #649

---------

Co-authored-by: Atif Ali <atif@coder.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-19 21:53:41 +05:30
35C4n0r d4efc09b20 bun fmt
Signed-off-by: 35C4n0r <work.jaykumar@gmail.com>
2025-12-14 18:05:17 +00:00
35C4n0r 4b03bce6f7 wip
Signed-off-by: 35C4n0r <work.jaykumar@gmail.com>
2025-12-14 18:00:11 +00:00
35C4n0r ae48c1043b wip
Signed-off-by: 35C4n0r <work.jaykumar@gmail.com>
2025-12-14 17:47:20 +00:00
35C4n0r 73af151f8d wip
Signed-off-by: 35C4n0r <work.jaykumar@gmail.com>
2025-12-14 17:39:22 +00:00
35C4n0r c115d860f7 wip
Signed-off-by: 35C4n0r <work.jaykumar@gmail.com>
2025-12-14 17:21:53 +00:00
35C4n0r 395f170d07 wip
Signed-off-by: 35C4n0r <work.jaykumar@gmail.com>
2025-12-14 17:11:49 +00:00
35C4n0r 65189bc068 wip
Signed-off-by: 35C4n0r <work.jaykumar@gmail.com>
2025-12-14 12:50:51 +00:00
35C4n0r d3b5057819 wip
Signed-off-by: 35C4n0r <work.jaykumar@gmail.com>
2025-12-14 12:46:45 +00:00
35C4n0r dd86d3d1d8 wip
Signed-off-by: 35C4n0r <work.jaykumar@gmail.com>
2025-12-14 12:41:21 +00:00
35C4n0r 1dee0012f5 wip
Signed-off-by: 35C4n0r <work.jaykumar@gmail.com>
2025-12-14 12:37:52 +00:00
35C4n0r ef0f597d54 wip
Signed-off-by: 35C4n0r <work.jaykumar@gmail.com>
2025-12-14 12:31:26 +00:00
35C4n0r aaf2c4e0dd wip
Signed-off-by: 35C4n0r <work.jaykumar@gmail.com>
2025-12-14 12:23:27 +00:00
35C4n0r 090fa7dd1d wip
Signed-off-by: 35C4n0r <work.jaykumar@gmail.com>
2025-12-14 11:04:55 +00:00
35C4n0r a630ffa42a wip
Signed-off-by: 35C4n0r <work.jaykumar@gmail.com>
2025-12-14 10:13:30 +00:00
35C4n0r 93f9ec3708 wip
Signed-off-by: 35C4n0r <work.jaykumar@gmail.com>
2025-12-14 10:07:30 +00:00
35C4n0r 5870805d0f wip
Signed-off-by: 35C4n0r <work.jaykumar@gmail.com>
2025-12-14 09:26:01 +00:00
35C4n0r 6806985778 wip
Signed-off-by: 35C4n0r <work.jaykumar@gmail.com>
2025-12-14 09:20:48 +00:00
35C4n0r 149e65b49f wip
Signed-off-by: 35C4n0r <work.jaykumar@gmail.com>
2025-12-14 09:13:33 +00:00
35C4n0r 327f05487d wip
Signed-off-by: 35C4n0r <work.jaykumar@gmail.com>
2025-12-14 09:07:52 +00:00
35C4n0r 19dc50db3e wip
Signed-off-by: 35C4n0r <work.jaykumar@gmail.com>
2025-12-13 11:33:20 +00:00
35C4n0r 48564621ad Merge branch 'main' into feat-conditional-agentapi
# Conflicts:
#	registry/coder/modules/claude-code/main.tf
#	registry/coder/modules/claude-code/scripts/start.sh
2025-12-13 11:30:22 +00:00
35C4n0r d0ef4f426b wip
Signed-off-by: 35C4n0r <work.jaykumar@gmail.com>
2025-12-13 11:28:06 +00:00
35C4n0r c2fa87aea6 wip
Signed-off-by: 35C4n0r <work.jaykumar@gmail.com>
2025-12-12 18:13:53 +00:00
35C4n0r 63eff436eb wip
Signed-off-by: 35C4n0r <work.jaykumar@gmail.com>
2025-12-12 18:02:51 +00:00
35C4n0r 250b64e44f wip 2025-12-05 19:18:01 +05:30
35C4n0r 78a0d14863 wip 2025-12-05 19:14:55 +05:30
35C4n0r 0d0bfa7131 wip 2025-12-05 19:10:01 +05:30
35C4n0r b32b2d4329 wip 2025-12-05 19:08:58 +05:30
35C4n0r 8664ded490 wip 2025-12-05 18:38:37 +05:30
35C4n0r 2ed4be2172 wip 2025-12-05 18:36:42 +05:30
35C4n0r d718c3b4e9 wip 2025-12-05 18:30:08 +05:30
35C4n0r 19f2a8f3ec wip 2025-12-04 21:42:56 +05:30
35C4n0r e12cd61e45 wip 2025-12-04 21:39:49 +05:30
35C4n0r 09386a43cd wip 2025-12-04 21:30:11 +05:30
35C4n0r 50eb191eaa wip 2025-12-04 21:29:50 +05:30
35C4n0r bdc8aea37f wip 2025-12-04 08:27:15 +05:30
35C4n0r c6a7d049bd wip 2025-12-04 08:17:07 +05:30
35C4n0r 65a73a8708 wip 2025-12-03 21:32:47 +05:30
35C4n0r 05c5724561 wip 2025-12-03 18:43:25 +05:30
35C4n0r 0d03fa4e58 wip 2025-12-03 18:34:46 +05:30
35C4n0r f3bfa9cc8d wip 2025-12-03 16:49:30 +05:30
35C4n0r a91c8845cb wip 2025-12-03 16:49:08 +05:30
35C4n0r 2c00575203 wip 2025-11-28 21:34:11 +05:30
35C4n0r c5d83570bc wip 2025-11-28 21:24:27 +05:30
35C4n0r dd96e8c74b feat: conditional agentapi 2025-11-28 21:22:06 +05:30
5 changed files with 322 additions and 109 deletions
+62 -21
View File
@@ -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.4.2"
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.4.2"
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.4.2"
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.4.2"
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.4.2"
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.4.2"
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.4.2"
version = "4.5.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
model = "claude-sonnet-4@20250514"
+110 -43
View File
@@ -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
}