Compare commits

..

6 Commits

Author SHA1 Message Date
blink-so[bot] b5d7490a05 chore(claude-code): bump README version to 4.8.1 (patch) 2026-03-03 21:22:35 +00:00
DevCats c2e2964a85 Merge branch 'main' into jwb/claude-code-pre-start-script 2026-01-16 15:12:48 -06:00
DevCats 724ff48e70 Merge branch 'main' into jwb/claude-code-pre-start-script 2026-01-09 15:42:52 -06:00
DevCats cdf8f722ee Merge branch 'main' into jwb/claude-code-pre-start-script 2026-01-07 13:21:42 -06:00
DevCats 6ad0552579 Merge branch 'main' into jwb/claude-code-pre-start-script 2026-01-05 16:36:51 -06:00
Jason Barnett cdfbc6d126 docs(claude-code): document pre_install_script for module dependency ordering
Update the pre_install_script variable description to clarify that it can be
used for handling dependencies between modules, such as waiting for git-clone
to complete before Claude Code initialization.
2025-12-16 12:57:22 -07:00
14 changed files with 228 additions and 449 deletions
@@ -11,7 +11,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@v6
- name: Run check.sh
run: |
+10 -10
View File
@@ -12,9 +12,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@v6
- name: Detect changed files
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3
uses: dorny/paths-filter@v3
id: filter
with:
list-files: shell
@@ -37,9 +37,9 @@ jobs:
all:
- '**'
- name: Set up Terraform
uses: coder/coder/.github/actions/setup-tf@59cdd7e21f4d7da12567c0c29964d298fbf38f27 # v2.29.1
uses: coder/coder/.github/actions/setup-tf@main
- name: Set up Bun
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2
uses: oven-sh/setup-bun@v2
with:
# We're using the latest version of Bun for now, but it might be worth
# reconsidering. They've pushed breaking changes in patch releases
@@ -80,20 +80,20 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@v6
- name: Install Bun
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
# Need Terraform for its formatter
- name: Install Terraform
uses: coder/coder/.github/actions/setup-tf@59cdd7e21f4d7da12567c0c29964d298fbf38f27 # v2.29.1
uses: coder/coder/.github/actions/setup-tf@main
- name: Install dependencies
run: bun install
- name: Validate formatting
run: bun fmt:ci
- name: Check for typos
uses: crate-ci/typos@bb4666ad77b539a6b4ce4eda7ebb6de553704021 # v1.42.0
uses: crate-ci/typos@v1.42.0
with:
config: .github/typos.toml
validate-readme-files:
@@ -104,9 +104,9 @@ jobs:
needs: validate-style
steps:
- name: Check out code
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
uses: actions/setup-go@v6
with:
go-version: "1.24.0"
- name: Validate contributors
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@v6
- name: Authenticate with Google Cloud
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093
with:
+3 -3
View File
@@ -14,11 +14,11 @@ jobs:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: stable
- name: golangci-lint
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9
uses: golangci/golangci-lint-action@v9
with:
version: v2.1
+3 -3
View File
@@ -14,7 +14,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@v6
with:
fetch-depth: 0
persist-credentials: false
@@ -89,9 +89,9 @@ jobs:
for sha in $MODULE_COMMIT_SHAS; do
SHORT_SHA=${sha:0:7}
COMMIT_LINES=$(echo "$FULL_CHANGELOG" | grep -E "$SHORT_SHA|$(git log --format='%s' -n 1 $sha)" || true)
if [ -n "$COMMIT_LINES" ]; then
FILTERED_CHANGELOG="${FILTERED_CHANGELOG}${COMMIT_LINES}\n"
else
+6 -8
View File
@@ -20,28 +20,26 @@ jobs:
issues: write
steps:
- name: Checkout code
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Bun
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Set up Terraform
uses: coder/coder/.github/actions/setup-tf@59cdd7e21f4d7da12567c0c29964d298fbf38f27 # v2.29.1
uses: coder/coder/.github/actions/setup-tf@main
- name: Install dependencies
run: bun install
- name: Extract bump type from label
env:
LABEL_NAME: ${{ github.event.label.name }}
id: bump-type
run: |
case "$LABEL_NAME" in in
case "${{ github.event.label.name }}" in
"version:patch")
echo "type=patch" >> $GITHUB_OUTPUT
;;
@@ -52,7 +50,7 @@ jobs:
echo "type=major" >> $GITHUB_OUTPUT
;;
*)
echo "Invalid version label: ${LABEL_NAME}"
echo "Invalid version label: ${{ github.event.label.name }}"
exit 1
;;
esac
@@ -62,7 +60,7 @@ jobs:
- name: Comment on PR - Version bump required
if: failure()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
-55
View File
@@ -1,55 +0,0 @@
name: GitHub Actions Security Analysis (zizmor)
on:
pull_request:
branches: ["**"]
paths:
- ".github/workflows/**"
push:
branches: ["main"]
paths:
- ".github/workflows/**"
workflow_dispatch:
permissions: {}
jobs:
zizmor_pr_blocking:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
actions: read
steps:
- name: Checkout repository
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
persist-credentials: false
- name: Run zizmor (blocking, HIGH only)
uses: zizmorcore/zizmor-action@e639db99335bc9038abc0e066dfcd72e23d26fb4 # v0.3.0
with:
advanced-security: false
annotations: true
min-severity: high
inputs: |
.github/workflows
zizmor_main_sarif:
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
permissions:
security-events: write
contents: read
actions: read
steps:
- name: Checkout repository
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
persist-credentials: false
- name: Run zizmor (SARIF)
uses: zizmorcore/zizmor-action@e639db99335bc9038abc0e066dfcd72e23d26fb4 # v0.3.0
with:
inputs: |
.github/workflows
+156 -29
View File
@@ -1,41 +1,168 @@
# AGENTS.md
Coder Registry: Terraform modules/templates for Coder workspaces under `registry/[namespace]/modules/` and `registry/[namespace]/templates/`.
This file provides guidance to AI coding assistants when working with code in this repository.
## Commands
## Project Overview
The Coder Registry is a community-driven repository for Terraform modules and templates that extend Coder workspaces. It's organized with:
- **Modules**: Individual components and tools (IDEs, auth integrations, dev tools)
- **Templates**: Complete workspace configurations for different platforms
- **Namespaces**: Each contributor has their own namespace under `/registry/[namespace]/`
## Common Development Commands
### Formatting
```bash
bun run fmt # Format code (Prettier + Terraform) - run before commits
bun run tftest # Run all Terraform tests
bun run tstest # Run all TypeScript tests
terraform init -upgrade && terraform test -verbose # Test single module (run from module dir)
bun test main.test.ts # Run single TS test (from module dir)
./scripts/terraform_validate.sh # Validate Terraform syntax
./scripts/new_module.sh ns/name # Create new module scaffold
.github/scripts/version-bump.sh patch | minor | major # Bump module version after changes
bun run fmt # Format all code (Prettier + Terraform)
bun run fmt:ci # Check formatting (CI mode)
```
## Structure
### Testing
- **Modules**: `registry/[ns]/modules/[name]/` with `main.tf`, `README.md` (YAML frontmatter), `.tftest.hcl` (required)
- **Templates**: `registry/[ns]/templates/[name]/` with `main.tf`, `README.md`
- **Validation**: `cmd/readmevalidation/` (Go) validates structure/frontmatter; URLs must be relative, not absolute
```bash
# Test all modules with .tftest.hcl files
bun run test
## Code Style
# Test specific module (from module directory)
terraform init -upgrade
terraform test -verbose
- Every module MUST have `.tftest.hcl` tests; optional `main.test.ts` for container/script tests
- README frontmatter: `display_name`, `description`, `icon`, `verified: false`, `tags`
- Use semantic versioning; bump version via script when modifying modules
- Docker tests require Linux or Colima/OrbStack (not Docker Desktop)
- Use `tf` (not `hcl`) for code blocks in README; use relative icon paths (e.g., `../../../../.icons/`)
# Validate Terraform syntax
./scripts/terraform_validate.sh
```
## PR Review Checklist
### Module Creation
- Version bumped via `.github/scripts/version-bump.sh` if module changed (patch=bugfix, minor=feature, major=breaking)
- Breaking changes documented: removed inputs, changed defaults, new required variables
- New variables have sensible defaults to maintain backward compatibility
- Tests pass (`bun run tftest`, `bun run tstest`); add diagnostic logging for test failures
- README examples updated with new version number; tooltip/behavior changes noted
- Shell scripts handle errors gracefully (use `|| echo "Warning..."` for non-fatal failures)
- No hardcoded values that should be configurable; no absolute URLs (use relative paths)
- If AI-assisted: include model and tool/agent name at footer of PR body (e.g., "Generated with [Amp](thread-url) using Claude")
```bash
# Generate new module scaffold
./scripts/new_module.sh namespace/module-name
```
### TypeScript Testing & Setup
The repository uses Bun for TypeScript testing with utilities:
- `test/test.ts` - Testing utilities for container management and Terraform operations
- `setup.ts` - Test cleanup (removes .tfstate files and test containers)
- Container-based testing with Docker for module validation
## Architecture & Organization
### Directory Structure
```
registry/[namespace]/
├── README.md # Contributor info with frontmatter
├── .images/ # Namespace avatar (avatar.png/svg)
├── modules/ # Individual components
│ └── [module]/ # Each module has main.tf, README.md, tests
└── templates/ # Complete workspace configs
└── [template]/ # Each template has main.tf, README.md
```
### Key Components
**Module Structure**: Each module contains:
- `main.tf` - Terraform implementation
- `README.md` - Documentation with YAML frontmatter
- `.tftest.hcl` - Terraform test files (required)
- `run.sh` - Optional startup script
**Template Structure**: Each template contains:
- `main.tf` - Complete Coder template configuration
- `README.md` - Documentation with YAML frontmatter
- Additional configs, scripts as needed
### README Frontmatter Requirements
All modules/templates require YAML frontmatter:
```yaml
---
display_name: "Module Name"
description: "Brief description"
icon: "../../../../.icons/tool.svg"
verified: false
tags: ["tag1", "tag2"]
---
```
## Testing Requirements
### Module Testing
- Every module MUST have `.tftest.hcl` test files
- Optional `main.test.ts` files for container-based testing or complex business logic validation
- Tests use Docker containers with `--network=host` flag
- Linux required for testing (Docker Desktop on macOS/Windows won't work)
- Use Colima or OrbStack on macOS instead of Docker Desktop
### Test Utilities
The `test/test.ts` file provides:
- `runTerraformApply()` - Execute Terraform with variables
- `executeScriptInContainer()` - Run coder_script resources in containers
- `testRequiredVariables()` - Validate required variables
- Container management functions
## Validation & Quality
### Automated Validation
The Go validation tool (`cmd/readmevalidation/`) checks:
- Repository structure integrity
- Contributor README files
- Module and template documentation
- Frontmatter format compliance
### Versioning
Use semantic versioning for modules:
- **Patch** (1.2.3 → 1.2.4): Bug fixes
- **Minor** (1.2.3 → 1.3.0): New features, adding inputs
- **Major** (1.2.3 → 2.0.0): Breaking changes
## Dependencies & Tools
### Required Tools
- **Terraform** - Module development and testing
- **Docker** - Container-based testing
- **Bun** - JavaScript runtime for formatting/scripts
- **Go 1.23+** - Validation tooling
### Development Dependencies
- Prettier with Terraform and shell plugins
- TypeScript for test utilities
- Various npm packages for documentation processing
## Workflow Notes
### Contributing Process
1. Create namespace (first-time contributors)
2. Generate module/template files using scripts
3. Implement functionality and tests
4. Run formatting and validation
5. Submit PR with appropriate template
### Testing Workflow
- All modules must pass `terraform test`
- Use `bun run test` for comprehensive testing
- Format code with `bun run fmt` before submission
- Manual testing recommended for templates
### Namespace Management
- Each contributor gets unique namespace
- Namespace avatar required (avatar.png/svg in .images/)
- Namespace README with contributor info and frontmatter
+26 -92
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, aibridge]
tags: [agent, claude-code, ai, tasks, anthropic]
---
# 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.7.0"
version = "4.8.1"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
claude_api_key = "xxxx-xxxxx-xxxx"
@@ -42,83 +42,36 @@ By default, Claude Code automatically resumes existing conversations when your w
This example shows how to configure the Claude Code module to run the agent behind a process-level boundary that restricts its network access.
By default, when `enable_boundary = true`, the module uses `coder boundary` subcommand (provided by Coder) without requiring any installation.
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.7.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
enable_boundary = true
source = "registry.coder.com/coder/claude-code/coder"
version = "4.8.1"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
enable_boundary = true
boundary_version = "v0.5.1"
}
```
> [!NOTE]
> For developers: The module also supports installing boundary from a release version (`use_boundary_directly = true`) or compiling from source (`compile_boundary_from_source = true`). These are escape hatches for development and testing purposes.
### Usage with Tasks and Advanced Configuration
### Usage with AI Bridge
[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.7.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.7.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.
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.
> [!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.7.0"
version = "4.8.1"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
@@ -130,7 +83,9 @@ module "claude-code" {
claude_binary_path = "/opt/claude/bin" # Path to pre-installed Claude binary
agentapi_version = "0.11.4"
model = "sonnet"
ai_prompt = data.coder_parameter.ai_prompt.value
model = "sonnet"
permission_mode = "plan"
mcp = <<-EOF
@@ -143,30 +98,9 @@ module "claude-code" {
}
}
EOF
mcp_config_remote_path = [
"https://gist.githubusercontent.com/35C4n0r/cd8dce70360e5d22a070ae21893caed4/raw/",
"https://raw.githubusercontent.com/coder/coder/main/.mcp.json"
]
}
```
> [!NOTE]
> Remote URLs should return a JSON body in the following format:
>
> ```json
> {
> "mcpServers": {
> "server-name": {
> "command": "some-command",
> "args": ["arg1", "arg2"]
> }
> }
> }
> ```
>
> The `Content-Type` header doesn't matter—both `text/plain` and `application/json` work fine.
### Standalone Mode
Run and configure Claude Code as a standalone CLI in your workspace.
@@ -174,7 +108,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.7.0"
version = "4.8.1"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
install_claude_code = true
@@ -196,7 +130,7 @@ variable "claude_code_oauth_token" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.7.0"
version = "4.8.1"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
claude_code_oauth_token = var.claude_code_oauth_token
@@ -269,7 +203,7 @@ resource "coder_env" "bedrock_api_key" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.7.0"
version = "4.8.1"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
@@ -326,7 +260,7 @@ resource "coder_env" "google_application_credentials" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.7.0"
version = "4.8.1"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
model = "claude-sonnet-4@20250514"
@@ -461,54 +461,4 @@ EOF`,
expect(startLog.stdout).toContain(taskSessionId);
expect(startLog.stdout).not.toContain("manual-456");
});
test("mcp-config-remote-path", async () => {
const failingUrl = "http://localhost:19999/mcp.json";
const successUrl =
"https://raw.githubusercontent.com/coder/coder/main/.mcp.json";
const { id, coderEnvVars } = await setup({
skipClaudeMock: true,
moduleVariables: {
mcp_config_remote_path: JSON.stringify([failingUrl, successUrl]),
},
});
await execModuleScript(id, coderEnvVars);
const installLog = await readFileContainer(
id,
"/home/coder/.claude-module/install.log",
);
// Verify both URLs are attempted
expect(installLog).toContain(failingUrl);
expect(installLog).toContain(successUrl);
// First URL should fail gracefully
expect(installLog).toContain(
`Warning: Failed to fetch MCP configuration from '${failingUrl}'`,
);
// Second URL should succeed - no failure warning for it
expect(installLog).not.toContain(
`Warning: Failed to fetch MCP configuration from '${successUrl}'`,
);
// Should contain the MCP server add command from successful fetch
expect(installLog).toContain(
"Added stdio MCP server go-language-server to local config",
);
expect(installLog).toContain(
"Added stdio MCP server typescript-language-server to local config",
);
// Verify the MCP config was added to claude.json
const claudeConfig = await readFileContainer(
id,
"/home/coder/.claude.json",
);
expect(claudeConfig).toContain("typescript-language-server");
expect(claudeConfig).toContain("go-language-server");
});
});
+3 -40
View File
@@ -67,7 +67,7 @@ variable "cli_app_display_name" {
variable "pre_install_script" {
type = string
description = "Custom script to run before installing Claude Code."
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)."
default = null
}
@@ -166,12 +166,6 @@ variable "mcp" {
default = ""
}
variable "mcp_config_remote_path" {
type = list(string)
description = "List of URLs that return JSON MCP server configurations (text/plain with valid JSON)"
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."
@@ -234,28 +228,6 @@ variable "compile_boundary_from_source" {
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"
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."
}
}
resource "coder_env" "claude_code_md_path" {
count = var.claude_md_path == "" ? 0 : 1
agent_id = var.agent_id
@@ -276,9 +248,10 @@ 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.enable_aibridge ? data.coder_workspace_owner.me.session_token : var.claude_api_key
value = var.claude_api_key
}
resource "coder_env" "disable_autoupdater" {
@@ -308,13 +281,6 @@ 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
@@ -395,7 +361,6 @@ module "agentapi" {
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}' \
/tmp/start.sh
EOT
@@ -417,8 +382,6 @@ module "agentapi" {
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}' \
/tmp/install.sh
EOT
}
@@ -42,7 +42,7 @@ run "test_claude_code_with_api_key" {
}
assert {
condition = coder_env.claude_api_key.value == "test-api-key-123"
condition = coder_env.claude_api_key[0].value == "test-api-key-123"
error_message = "Claude API key value should match the input"
}
}
@@ -288,94 +288,3 @@ 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"
}
}
@@ -16,10 +16,8 @@ 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)
ARG_ALLOWED_TOOLS=${ARG_ALLOWED_TOOLS:-}
ARG_DISALLOWED_TOOLS=${ARG_DISALLOWED_TOOLS:-}
ARG_ENABLE_AIBRIDGE=${ARG_ENABLE_AIBRIDGE:-false}
echo "--------------------------------"
@@ -31,26 +29,11 @@ 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"
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 "--------------------------------"
function add_mcp_servers() {
local mcp_json="$1"
local source_desc="$2"
while IFS= read -r server_name && IFS= read -r server_json; do
echo "------------------------"
echo "Executing: claude mcp add-json \"$server_name\" '$server_json' ($source_desc)"
claude mcp add-json "$server_name" "$server_json" || echo "Warning: Failed to add MCP server '$server_name', continuing..."
echo "------------------------"
echo ""
done < <(echo "$mcp_json" | jq -r '.mcpServers | to_entries[] | .key, (.value | @json)')
}
function ensure_claude_in_path() {
if [ -z "${CODER_SCRIPT_BIN_DIR:-}" ]; then
echo "CODER_SCRIPT_BIN_DIR not set, skipping PATH setup"
@@ -127,25 +110,13 @@ function setup_claude_configurations() {
if [ "$ARG_MCP" != "" ]; then
(
cd "$ARG_WORKDIR"
add_mcp_servers "$ARG_MCP" "in $ARG_WORKDIR"
)
fi
if [ -n "$ARG_MCP_CONFIG_REMOTE_PATH" ] && [ "$ARG_MCP_CONFIG_REMOTE_PATH" != "[]" ]; then
(
cd "$ARG_WORKDIR"
for url in $(echo "$ARG_MCP_CONFIG_REMOTE_PATH" | jq -r '.[]'); do
echo "Fetching MCP configuration from: $url"
mcp_json=$(curl -fsSL "$url") || {
echo "Warning: Failed to fetch MCP configuration from '$url', continuing..."
continue
}
if ! echo "$mcp_json" | jq -e '.mcpServers' > /dev/null 2>&1; then
echo "Warning: Invalid MCP configuration from '$url' (missing mcpServers), continuing..."
continue
fi
add_mcp_servers "$mcp_json" "from $url"
done
while IFS= read -r server_name && IFS= read -r server_json; do
echo "------------------------"
echo "Executing: claude mcp add-json \"$server_name\" '$server_json' (in $ARG_WORKDIR)"
claude mcp add-json "$server_name" "$server_json" || echo "Warning: Failed to add MCP server '$server_name', continuing..."
echo "------------------------"
echo ""
done < <(echo "$ARG_MCP" | jq -r '.mcpServers | to_entries[] | .key, (.value | @json)')
)
fi
@@ -162,8 +133,8 @@ function setup_claude_configurations() {
function configure_standalone_mode() {
echo "Configuring Claude Code for standalone mode..."
if [ -z "${CLAUDE_API_KEY:-}" ] && [ "$ARG_ENABLE_AIBRIDGE" = "false" ]; then
echo "Note: Neither claude_api_key nor enable_aibridge is set, skipping authentication setup"
if [ -z "${CLAUDE_API_KEY:-}" ]; then
echo "Note: CLAUDE_API_KEY not set, skipping authentication setup"
return
fi
@@ -176,7 +147,8 @@ function configure_standalone_mode() {
if [ -f "$claude_config" ]; then
echo "Updating existing Claude configuration at $claude_config"
jq --arg workdir "$ARG_WORKDIR" --arg apikey "${CLAUDE_API_KEY:-}" \
jq --arg apikey "${CLAUDE_API_KEY:-}" \
--arg workdir "$ARG_WORKDIR" \
'.autoUpdaterStatus = "disabled" |
.bypassPermissionsModeAccepted = true |
.hasAcknowledgedCostThreshold = true |
@@ -16,7 +16,6 @@ ARG_REPORT_TASKS=${ARG_REPORT_TASKS:-true}
ARG_ENABLE_BOUNDARY=${ARG_ENABLE_BOUNDARY:-false}
ARG_BOUNDARY_VERSION=${ARG_BOUNDARY_VERSION:-"main"}
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 "--------------------------------"
@@ -31,13 +30,12 @@ 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
if [ "${ARG_COMPILE_FROM_SOURCE:-false}" = "true" ]; then
# Install boundary by compiling from source
echo "Compiling boundary from source (version: $ARG_BOUNDARY_VERSION)"
@@ -54,16 +52,14 @@ function install_boundary() {
# Build the binary
make build
# Install binary
# Install binary and wrapper script (optional)
sudo cp boundary /usr/local/bin/
sudo chmod +x /usr/local/bin/boundary
elif [ "$ARG_USE_BOUNDARY_DIRECTLY" = "true" ]; then
sudo cp scripts/boundary-wrapper.sh /usr/local/bin/boundary-run
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)"
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
}
@@ -216,30 +212,15 @@ function start_agentapi() {
printf "Running claude code with args: %s\n" "$(printf '%q ' "${ARGS[@]}")"
if [ "$ARG_ENABLE_BOUNDARY" = "true" ]; then
if [ "${ARG_ENABLE_BOUNDARY:-false}" = "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[@]}" -- \
boundary-run "${BOUNDARY_ARGS[@]}" -- \
claude "${ARGS[@]}"
else
agentapi server --type claude --term-width 67 --term-height 1190 -- claude "${ARGS[@]}"