Compare commits

..

3 Commits

Author SHA1 Message Date
DevCats fac76efbfe Merge branch 'main' into cat/version-bump-comment-fix 2026-01-08 14:12:28 -06:00
DevelopmentCats a5ebd5e14b chore: address copilot comments 2026-01-08 14:10:43 -06:00
DevelopmentCats 21fc9618a7 feat: enhance version bump script and workflow for forked PRs 2026-01-07 15:49:44 -06:00
16 changed files with 213 additions and 506 deletions
+8 -4
View File
@@ -1,26 +1,29 @@
#!/bin/bash
# Version Bump Script
# Usage: ./version-bump.sh [--ci] <bump_type> [base_ref]
# Usage: ./version-bump.sh [--ci] <bump_type> [base_ref] [head_ref]
# --ci: CI mode - run bump, check for changes, exit 1 if changes needed
# bump_type: patch, minor, or major
# base_ref: base reference for diff (default: origin/main)
# head_ref: head reference for diff (default: HEAD)
set -euo pipefail
CI_MODE=false
usage() {
echo "Usage: $0 [--ci] <bump_type> [base_ref]"
echo "Usage: $0 [--ci] <bump_type> [base_ref] [head_ref]"
echo " --ci: CI mode - validates versions are already bumped (exits 1 if not)"
echo " bump_type: patch, minor, or major"
echo " base_ref: base reference for diff (default: origin/main)"
echo " head_ref: head reference for diff (default: HEAD, used for fork PRs)"
echo ""
echo "Examples:"
echo " $0 patch # Update versions with patch bump"
echo " $0 minor # Update versions with minor bump"
echo " $0 major # Update versions with major bump"
echo " $0 --ci patch # CI check: verify patch bump has been applied"
echo " $0 --ci patch base_sha head_sha # CI check with explicit refs (for fork PRs)"
exit 1
}
@@ -125,12 +128,13 @@ main() {
shift
fi
if [ $# -lt 1 ] || [ $# -gt 2 ]; then
if [ $# -lt 1 ] || [ $# -gt 3 ]; then
usage
fi
local bump_type="$1"
local base_ref="${2:-origin/main}"
local head_ref="${3:-HEAD}"
case "$bump_type" in
"patch" | "minor" | "major") ;;
@@ -144,7 +148,7 @@ main() {
echo "🔍 Detecting modified modules..."
local changed_files
changed_files=$(git diff --name-only "${base_ref}"...HEAD)
changed_files=$(git diff --name-only "${base_ref}".."${head_ref}")
local modules
modules=$(echo "$changed_files" | grep -E '^registry/[^/]+/modules/[^/]+/' | cut -d'/' -f1-4 | sort -u)
+1 -1
View File
@@ -93,7 +93,7 @@ jobs:
- name: Validate formatting
run: bun fmt:ci
- name: Check for typos
uses: crate-ci/typos@v1.42.0
uses: crate-ci/typos@v1.41.0
with:
config: .github/typos.toml
validate-readme-files:
+35 -7
View File
@@ -1,13 +1,14 @@
name: Version Bump
# Using pull_request_target to allow commenting on PRs from forks.
# SECURITY: Executable code (scripts, package.json) comes from the BASE branch only.
# Only the registry/ directory (data files) is checked out from the PR for version checking.
on:
pull_request:
pull_request_target:
types: [labeled]
paths:
- "registry/**/modules/**"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
@@ -19,24 +20,50 @@ jobs:
pull-requests: write
issues: write
steps:
- name: Checkout code
- name: Checkout base branch
uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.base.sha }}
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Fetch PR head
run: |
git fetch origin refs/pull/${{ github.event.pull_request.number }}/head:pr-head
echo "PR_HEAD_SHA=$(git rev-parse pr-head)" >> $GITHUB_ENV
- name: Check for module changes
id: check-modules
run: |
CHANGED_FILES=$(git diff --name-only ${{ github.event.pull_request.base.sha }}..pr-head)
if echo "$CHANGED_FILES" | grep -qE '^registry/[^/]+/modules/'; then
echo "has_module_changes=true" >> $GITHUB_OUTPUT
echo "✅ PR contains module changes"
else
echo "has_module_changes=false" >> $GITHUB_OUTPUT
echo "️ PR does not contain module changes, skipping version bump check"
fi
- name: Checkout PR module files
if: steps.check-modules.outputs.has_module_changes == 'true'
run: git checkout pr-head -- registry/
- name: Set up Bun
if: steps.check-modules.outputs.has_module_changes == 'true'
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Set up Terraform
if: steps.check-modules.outputs.has_module_changes == 'true'
uses: coder/coder/.github/actions/setup-tf@main
- name: Install dependencies
if: steps.check-modules.outputs.has_module_changes == 'true'
run: bun install
- name: Extract bump type from label
if: steps.check-modules.outputs.has_module_changes == 'true'
id: bump-type
run: |
case "${{ github.event.label.name }}" in
@@ -56,10 +83,11 @@ jobs:
esac
- name: Check version bump
run: ./.github/scripts/version-bump.sh --ci "${{ steps.bump-type.outputs.type }}" origin/main
if: steps.check-modules.outputs.has_module_changes == 'true'
run: ./.github/scripts/version-bump.sh --ci "${{ steps.bump-type.outputs.type }}" ${{ github.event.pull_request.base.sha }} ${{ env.PR_HEAD_SHA }}
- name: Comment on PR - Version bump required
if: failure()
if: failure() && steps.check-modules.outputs.has_module_changes == 'true'
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
-2
View File
@@ -1,2 +0,0 @@
# GitHub Actions Workflow Owners
.github/ @jdomeracki-coder
+19 -10
View File
@@ -13,7 +13,7 @@ Run Codex CLI in your workspace to access OpenAI's models through the Codex inte
```tf
module "codex" {
source = "registry.coder.com/coder-labs/codex/coder"
version = "4.0.0"
version = "3.1.1"
agent_id = coder_agent.example.id
openai_api_key = var.openai_api_key
workdir = "/home/coder/project"
@@ -22,6 +22,7 @@ module "codex" {
## Prerequisites
- You must add the [Coder Login](https://registry.coder.com/modules/coder/coder-login) module to your template
- OpenAI API key for Codex access
## Examples
@@ -32,7 +33,7 @@ module "codex" {
module "codex" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/codex/coder"
version = "4.0.0"
version = "3.1.1"
agent_id = coder_agent.example.id
openai_api_key = "..."
workdir = "/home/coder/project"
@@ -43,19 +44,27 @@ module "codex" {
### Tasks integration
```tf
resource "coder_ai_task" "task" {
count = data.coder_workspace.me.start_count
app_id = module.codex.task_app_id
data "coder_parameter" "ai_prompt" {
type = "string"
name = "AI Prompt"
default = ""
description = "Initial prompt for the Codex CLI"
mutable = true
}
data "coder_task" "me" {}
module "coder-login" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/coder-login/coder"
version = "3.1.1"
agent_id = coder_agent.example.id
}
module "codex" {
source = "registry.coder.com/coder-labs/codex/coder"
version = "4.0.0"
version = "3.1.1"
agent_id = coder_agent.example.id
openai_api_key = "..."
ai_prompt = data.coder_task.me.prompt
ai_prompt = data.coder_parameter.ai_prompt.value
workdir = "/home/coder/project"
# Custom configuration for full auto mode
@@ -99,7 +108,7 @@ For custom Codex configuration, use `base_config_toml` and/or `additional_mcp_se
```tf
module "codex" {
source = "registry.coder.com/coder-labs/codex/coder"
version = "4.0.0"
version = "3.1.1"
# ... other variables ...
# Override default configuration
@@ -128,7 +137,7 @@ module "codex" {
- Ensure your OpenAI API key has access to the specified model
> [!IMPORTANT]
> To use tasks with Codex CLI, ensure you have the `openai_api_key` variable set. [Tasks Template Example](https://registry.coder.com/templates/coder-labs/tasks-docker).
> To use tasks with Codex CLI, ensure you have the `openai_api_key` variable set, and **you create a `coder_parameter` named `"AI Prompt"` and pass its value to the codex module's `ai_prompt` variable**. [Tasks Template Example](https://registry.coder.com/templates/coder-labs/tasks-docker).
> The module automatically configures Codex with your API key and model preferences.
> workdir is a required variable for the module to function correctly.
+5 -9
View File
@@ -4,7 +4,7 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
version = ">= 2.12"
version = ">= 2.7"
}
}
}
@@ -110,12 +110,12 @@ variable "install_agentapi" {
variable "agentapi_version" {
type = string
description = "The version of AgentAPI to install."
default = "v0.11.6"
default = "v0.10.0"
}
variable "codex_model" {
type = string
description = "The model for Codex to use. Defaults to gpt-5.1-codex-max."
description = "The model for Codex to use. Defaults to gpt-5."
default = ""
}
@@ -165,7 +165,7 @@ locals {
module "agentapi" {
source = "registry.coder.com/coder/agentapi/coder"
version = "2.0.0"
version = "1.2.0"
agent_id = var.agent_id
folder = local.workdir
@@ -217,8 +217,4 @@ module "agentapi" {
ARG_CODEX_INSTRUCTION_PROMPT='${base64encode(var.codex_system_prompt)}' \
/tmp/install.sh
EOT
}
output "task_app_id" {
value = module.agentapi.task_app_id
}
}
@@ -115,7 +115,7 @@ append_mcp_servers_section() {
[mcp_servers.Coder]
command = "coder"
args = ["exp", "mcp", "server"]
env = { "CODER_MCP_APP_STATUS_SLUG" = "${ARG_CODER_MCP_APP_STATUS_SLUG}", "CODER_MCP_AI_AGENTAPI_URL" = "${CODER_MCP_AI_AGENTAPI_URL}" , "CODER_AGENT_URL" = "${CODER_AGENT_URL}", "CODER_AGENT_TOKEN" = "${CODER_AGENT_TOKEN}", "CODER_MCP_ALLOWED_TOOLS" = "coder_report_task" }
env = { "CODER_MCP_APP_STATUS_SLUG" = "${ARG_CODER_MCP_APP_STATUS_SLUG}", "CODER_MCP_AI_AGENTAPI_URL" = "${CODER_MCP_AI_AGENTAPI_URL}" , "CODER_AGENT_URL" = "${CODER_AGENT_URL}", "CODER_AGENT_TOKEN" = "${CODER_AGENT_TOKEN}" }
description = "Report ALL tasks and statuses (in progress, done, failed) you are working on."
type = "stdio"
@@ -182,7 +182,7 @@ build_codex_args() {
if [ -n "$ARG_CODEX_TASK_PROMPT" ]; then
if [ "${ARG_REPORT_TASKS}" == "true" ]; then
PROMPT="Complete the task at hand in one go. Every step of the way, report your progress using Coder.coder_report_task tool with proper summary and statuses. Your task at hand: $ARG_CODEX_TASK_PROMPT"
PROMPT="Complete the task at hand in one go. Every step of the way, report your progress using coder_report_task tool with proper summary and statuses. Your task at hand: $ARG_CODEX_TASK_PROMPT"
else
PROMPT="Your task at hand: $ARG_CODEX_TASK_PROMPT"
fi
+21 -66
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.5.0"
version = "4.3.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
claude_api_key = "xxxx-xxxxx-xxxx"
@@ -44,8 +44,8 @@ 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.5.0"
source = "dev.registry.coder.com/coder/claude-code/coder"
version = "4.3.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
enable_boundary = true
@@ -53,68 +53,22 @@ module "claude-code" {
}
```
### Usage with AI Bridge
### Usage with Tasks and Advanced Configuration
[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
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.
```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_parameter" "ai_prompt" {
type = "string"
name = "AI Prompt"
default = ""
description = "Initial task prompt for Claude Code."
mutable = true
}
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
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.5.0"
version = "4.3.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
@@ -122,11 +76,12 @@ module "claude-code" {
# OR
claude_code_oauth_token = "xxxxx-xxxx-xxxx"
claude_code_version = "2.0.62" # Pin to a specific version (uses npm)
claude_binary_path = "/opt/claude/bin" # Path to pre-installed Claude binary
claude_code_version = "2.0.62" # Pin to a specific version
agentapi_version = "0.11.4"
model = "sonnet"
ai_prompt = data.coder_parameter.ai_prompt.value
model = "sonnet"
permission_mode = "plan"
mcp = <<-EOF
@@ -149,7 +104,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.5.0"
version = "4.3.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
install_claude_code = true
@@ -171,7 +126,7 @@ variable "claude_code_oauth_token" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.5.0"
version = "4.3.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
claude_code_oauth_token = var.claude_code_oauth_token
@@ -244,7 +199,7 @@ resource "coder_env" "bedrock_api_key" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.5.0"
version = "4.3.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
@@ -301,7 +256,7 @@ resource "coder_env" "google_application_credentials" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.5.0"
version = "4.3.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
model = "claude-sonnet-4@20250514"
@@ -184,15 +184,20 @@ describe("claude-code", async () => {
test("claude-model", async () => {
const model = "opus";
const { coderEnvVars } = await setup({
const { id } = await setup({
moduleVariables: {
model: model,
ai_prompt: "test prompt",
},
});
await execModuleScript(id);
// Verify ANTHROPIC_MODEL env var is set via coder_env
expect(coderEnvVars["ANTHROPIC_MODEL"]).toBe(model);
const startLog = await execContainer(id, [
"bash",
"-c",
"cat /home/coder/.claude-module/agentapi-start.log",
]);
expect(startLog.stdout).toContain(`--model ${model}`);
});
test("claude-continue-resume-task-session", async () => {
+50 -141
View File
@@ -86,7 +86,7 @@ variable "install_agentapi" {
variable "agentapi_version" {
type = string
description = "The version of AgentAPI to install."
default = "v0.11.8"
default = "v0.11.6"
}
variable "ai_prompt" {
@@ -128,7 +128,7 @@ variable "claude_api_key" {
variable "model" {
type = string
description = "Sets the default model for Claude Code via ANTHROPIC_MODEL env var. If empty, Claude Code uses its default. Supports aliases (sonnet, opus) or full model names."
description = "Sets the model for the current session with an alias for the latest model (sonnet or opus) or a models full name."
default = ""
}
@@ -198,18 +198,6 @@ variable "claude_md_path" {
default = "$HOME/.claude/CLAUDE.md"
}
variable "claude_binary_path" {
type = string
description = "Directory where the Claude Code binary is located. Use this if Claude is pre-installed or installed outside the module to a non-default location."
default = "$HOME/.local/bin"
}
variable "install_via_npm" {
type = bool
description = "Install Claude Code via npm instead of the official installer. Useful if npm is preferred or the official installer fails."
default = false
}
variable "enable_boundary" {
type = bool
description = "Whether to enable coder boundary for network filtering"
@@ -228,30 +216,9 @@ 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
count = var.claude_md_path == "" ? 0 : 1
agent_id = var.agent_id
name = "CODER_MCP_CLAUDE_MD_PATH"
value = var.claude_md_path
@@ -270,13 +237,16 @@ 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" {
count = var.disable_autoupdater ? 1 : 0
count = var.disable_autoupdater ? 1 : 0
agent_id = var.agent_id
name = "DISABLE_AUTOUPDATER"
value = "1"
@@ -285,28 +255,7 @@ resource "coder_env" "disable_autoupdater" {
resource "coder_env" "claude_binary_path" {
agent_id = var.agent_id
name = "PATH"
value = "${var.claude_binary_path}:$PATH"
lifecycle {
precondition {
condition = var.claude_binary_path == "$HOME/.local/bin" || !var.install_claude_code
error_message = "Custom claude_binary_path can only be used when install_claude_code is false. The official installer and npm both install to fixed locations."
}
}
}
resource "coder_env" "anthropic_model" {
count = var.model != "" ? 1 : 0
agent_id = var.agent_id
name = "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"
value = "$HOME/.local/bin:$PATH"
}
locals {
@@ -350,89 +299,12 @@ 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
@@ -449,10 +321,47 @@ module "agentapi" {
agentapi_version = var.agentapi_version
pre_install_script = var.pre_install_script
post_install_script = var.post_install_script
start_script = local.agentapi_start_command
install_script = local.install_command
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_MODEL='${var.model}' \
ARG_RESUME_SESSION_ID='${var.resume_session_id}' \
ARG_CONTINUE='${var.continue}' \
ARG_DANGEROUSLY_SKIP_PERMISSIONS='${var.dangerously_skip_permissions}' \
ARG_PERMISSION_MODE='${var.permission_mode}' \
ARG_WORKDIR='${local.workdir}' \
ARG_AI_PROMPT='${base64encode(var.ai_prompt)}' \
ARG_REPORT_TASKS='${var.report_tasks}' \
ARG_ENABLE_BOUNDARY='${var.enable_boundary}' \
ARG_BOUNDARY_VERSION='${var.boundary_version}' \
ARG_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_REPORT_TASKS='${var.report_tasks}' \
ARG_WORKDIR='${local.workdir}' \
ARG_ALLOWED_TOOLS='${var.allowed_tools}' \
ARG_DISALLOWED_TOOLS='${var.disallowed_tools}' \
ARG_MCP='${var.mcp != null ? base64encode(replace(var.mcp, "'", "'\\''")) : ""}' \
/tmp/install.sh
EOT
}
output "task_app_id" {
value = try(module.agentapi[0].task_app_id, null)
value = module.agentapi.task_app_id
}
@@ -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"
}
}
@@ -11,91 +11,39 @@ command_exists() {
ARG_CLAUDE_CODE_VERSION=${ARG_CLAUDE_CODE_VERSION:-}
ARG_WORKDIR=${ARG_WORKDIR:-"$HOME"}
ARG_INSTALL_CLAUDE_CODE=${ARG_INSTALL_CLAUDE_CODE:-}
ARG_CLAUDE_BINARY_PATH=${ARG_CLAUDE_BINARY_PATH:-"$HOME/.local/bin"}
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_ALLOWED_TOOLS=${ARG_ALLOWED_TOOLS:-}
ARG_DISALLOWED_TOOLS=${ARG_DISALLOWED_TOOLS:-}
ARG_ENABLE_AIBRIDGE=${ARG_ENABLE_AIBRIDGE:-false}
echo "--------------------------------"
printf "ARG_CLAUDE_CODE_VERSION: %s\n" "$ARG_CLAUDE_CODE_VERSION"
printf "ARG_WORKDIR: %s\n" "$ARG_WORKDIR"
printf "ARG_INSTALL_CLAUDE_CODE: %s\n" "$ARG_INSTALL_CLAUDE_CODE"
printf "ARG_CLAUDE_BINARY_PATH: %s\n" "$ARG_CLAUDE_BINARY_PATH"
printf "ARG_INSTALL_VIA_NPM: %s\n" "$ARG_INSTALL_VIA_NPM"
printf "ARG_REPORT_TASKS: %s\n" "$ARG_REPORT_TASKS"
printf "ARG_MCP_APP_STATUS_SLUG: %s\n" "$ARG_MCP_APP_STATUS_SLUG"
printf "ARG_MCP: %s\n" "$ARG_MCP"
printf "ARG_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 ensure_claude_in_path() {
if [ -z "${CODER_SCRIPT_BIN_DIR:-}" ]; then
echo "CODER_SCRIPT_BIN_DIR not set, skipping PATH setup"
return
fi
if [ ! -e "$CODER_SCRIPT_BIN_DIR/claude" ]; then
local CLAUDE_BIN=""
if command -v claude > /dev/null 2>&1; then
CLAUDE_BIN=$(command -v claude)
elif [ -x "$ARG_CLAUDE_BINARY_PATH/claude" ]; then
CLAUDE_BIN="$ARG_CLAUDE_BINARY_PATH/claude"
elif [ -x "$HOME/.local/bin/claude" ]; then
CLAUDE_BIN="$HOME/.local/bin/claude"
fi
if [ -n "$CLAUDE_BIN" ] && [ -x "$CLAUDE_BIN" ]; then
ln -s "$CLAUDE_BIN" "$CODER_SCRIPT_BIN_DIR/claude"
echo "Created symlink: $CODER_SCRIPT_BIN_DIR/claude -> $CLAUDE_BIN"
else
echo "Warning: Could not find claude binary to symlink"
fi
else
echo "Claude already available in CODER_SCRIPT_BIN_DIR"
fi
local marker="# Added by claude-code module"
for profile in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile"; do
if [ -f "$profile" ] && ! grep -q "$marker" "$profile" 2> /dev/null; then
printf "\n%s\nexport PATH=\"%s:\$PATH\"\n" "$marker" "$CODER_SCRIPT_BIN_DIR" >> "$profile"
echo "Added $CODER_SCRIPT_BIN_DIR to PATH in $profile"
fi
done
}
function install_claude_code_cli() {
if [ "$ARG_INSTALL_CLAUDE_CODE" != "true" ]; then
echo "Skipping Claude Code installation as per configuration."
ensure_claude_in_path
return
fi
# Use npm when install_via_npm is true or for specific version pinning
if [ "$ARG_INSTALL_VIA_NPM" = "true" ] || { [ -n "$ARG_CLAUDE_CODE_VERSION" ] && [ "$ARG_CLAUDE_CODE_VERSION" != "latest" ]; }; then
echo "Installing Claude Code via npm (version: $ARG_CLAUDE_CODE_VERSION)"
npm install -g "@anthropic-ai/claude-code@$ARG_CLAUDE_CODE_VERSION"
echo "Installed Claude Code via npm. Version: $(claude --version || echo 'unknown')"
else
if [ "$ARG_INSTALL_CLAUDE_CODE" = "true" ]; then
echo "Installing Claude Code via official installer"
set +e
curl -fsSL claude.ai/install.sh | bash -s -- "$ARG_CLAUDE_CODE_VERSION" 2>&1
CURL_EXIT=${PIPESTATUS[0]}
set -e
if [ $CURL_EXIT -ne 0 ]; then
echo "Claude Code installer failed with exit code $CURL_EXIT"
echo "Claude Code installer failed with exit code $$CURL_EXIT"
fi
echo "Installed Claude Code successfully. Version: $(claude --version || echo 'unknown')"
else
echo "Skipping Claude Code installation as per configuration."
fi
ensure_claude_in_path
}
function setup_claude_configurations() {
@@ -115,7 +63,7 @@ function setup_claude_configurations() {
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..."
claude mcp add-json "$server_name" "$server_json"
echo "------------------------"
echo ""
done < <(echo "$ARG_MCP" | jq -r '.mcpServers | to_entries[] | .key, (.value | @json)')
@@ -135,8 +83,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
@@ -149,7 +97,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 |
@@ -2,12 +2,11 @@
set -euo pipefail
true > "$HOME/start.log"
command_exists() {
command -v "$1" > /dev/null 2>&1
}
ARG_MODEL=${ARG_MODEL:-}
ARG_RESUME_SESSION_ID=${ARG_RESUME_SESSION_ID:-}
ARG_CONTINUE=${ARG_CONTINUE:-false}
ARG_DANGEROUSLY_SKIP_PERMISSIONS=${ARG_DANGEROUSLY_SKIP_PERMISSIONS:-}
@@ -19,41 +18,35 @@ 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}
log() {
if [[ "${ARG_NON_AGENTAPI_CLI}" = "true" ]]; then
printf -- "$@" >> "$HOME/start.log"
else
printf -- "$@"
fi
}
echo "--------------------------------"
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"
printf "ARG_MODEL: %s\n" "$ARG_MODEL"
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 "--------------------------------\n"
echo "--------------------------------"
function install_boundary() {
if [ "${ARG_COMPILE_FROM_SOURCE:-false}" = "true" ]; then
# Install boundary by compiling from source
log "Compiling boundary from source (version: $ARG_BOUNDARY_VERSION)\n"
echo "Compiling boundary from source (version: $ARG_BOUNDARY_VERSION)"
log "Removing existing boundary directory to allow re-running the script safely\n"
echo "Removing existing boundary directory to allow re-running the script safely"
if [ -d boundary ]; then
rm -rf boundary
fi
log "Clone boundary repository\n"
echo "Clone boundary repository"
git clone https://github.com/coder/boundary.git
cd boundary
git checkout "$ARG_BOUNDARY_VERSION"
@@ -67,16 +60,16 @@ function install_boundary() {
sudo chmod +x /usr/local/bin/boundary-run
else
# Install boundary using official install script
log "Installing boundary using official install script (version: $ARG_BOUNDARY_VERSION)\n"
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"
fi
}
function validate_claude_installation() {
if command_exists claude; then
log "Claude Code is installed\n"
printf "Claude Code is installed\n"
else
log "Error: Claude Code is not installed. Please enable install_claude_code or install it manually\n"
printf "Error: Claude Code is not installed. Please enable install_claude_code or install it manually\n"
exit 1
fi
}
@@ -100,10 +93,10 @@ task_session_exists() {
session_file=$(get_task_session_file)
if [ -f "$session_file" ]; then
log "Task session file found: %s\n" "$session_file"
printf "Task session file found: %s\n" "$session_file"
return 0
else
log "Task session file not found: %s\n" "$session_file"
printf "Task session file not found: %s\n" "$session_file"
return 1
fi
}
@@ -114,12 +107,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
log "Session validation failed: file does not exist\n"
printf "Session validation failed: file does not exist\n"
return 1
fi
if [ ! -s "$session_file" ]; then
log "Session validation failed: file is empty, removing stale file\n"
printf "Session validation failed: file is empty, removing stale file\n"
rm -f "$session_file"
return 1
fi
@@ -129,7 +122,7 @@ is_valid_session() {
local line_count
line_count=$(wc -l < "$session_file")
if [ "$line_count" -lt 2 ]; then
log "Session validation failed: incomplete (only %s lines), removing incomplete file\n" "$line_count"
printf "Session validation failed: incomplete (only %s lines), removing incomplete file\n" "$line_count"
rm -f "$session_file"
return 1
fi
@@ -137,7 +130,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
log "Session validation failed: invalid JSONL format, removing corrupt file\n"
printf "Session validation failed: invalid JSONL format, removing corrupt file\n"
rm -f "$session_file"
return 1
fi
@@ -146,12 +139,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
log "Session validation failed: no valid sessionId found, removing malformed file\n"
printf "Session validation failed: no valid sessionId found, removing malformed file\n"
rm -f "$session_file"
return 1
fi
log "Session validation passed: %s\n" "$session_file"
printf "Session validation passed: %s\n" "$session_file"
return 0
}
@@ -160,21 +153,16 @@ 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
log "Sessions found in: %s\n" "$project_dir"
printf "Sessions found in: %s\n" "$project_dir"
return 0
else
log "No sessions found in: %s\n" "$project_dir"
printf "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"
@@ -182,12 +170,16 @@ function start_agentapi() {
mkdir -p "$ARG_WORKDIR"
cd "$ARG_WORKDIR"
if [ -n "$ARG_MODEL" ]; then
ARGS+=(--model "$ARG_MODEL")
fi
if [ -n "$ARG_PERMISSION_MODE" ]; then
ARGS+=(--permission-mode "$ARG_PERMISSION_MODE")
fi
if [ -n "$ARG_RESUME_SESSION_ID" ]; then
log "Resuming specified session: $ARG_RESUME_SESSION_ID"
echo "Resuming specified session: $ARG_RESUME_SESSION_ID"
ARGS+=(--resume "$ARG_RESUME_SESSION_ID")
[ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ] && ARGS+=(--dangerously-skip-permissions)
@@ -198,45 +190,47 @@ function start_agentapi() {
session_file=$(get_task_session_file)
if task_session_exists && is_valid_session "$session_file"; then
log "Resuming task session: $TASK_SESSION_ID"
echo "Resuming task session: $TASK_SESSION_ID"
ARGS+=(--resume "$TASK_SESSION_ID" --dangerously-skip-permissions)
else
log "Starting new task session: $TASK_SESSION_ID"
echo "Starting new task session: $TASK_SESSION_ID"
ARGS+=(--session-id "$TASK_SESSION_ID" --dangerously-skip-permissions)
[ -n "$ARG_AI_PROMPT" ] && ARGS+=(-- "$ARG_AI_PROMPT")
fi
else
if has_any_sessions; then
log "Continuing most recent standalone session"
echo "Continuing most recent standalone session"
ARGS+=(--continue)
[ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ] && ARGS+=(--dangerously-skip-permissions)
else
log "No sessions found, starting fresh standalone session"
echo "No sessions found, starting fresh standalone session"
[ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ] && ARGS+=(--dangerously-skip-permissions)
[ -n "$ARG_AI_PROMPT" ] && ARGS+=(-- "$ARG_AI_PROMPT")
fi
fi
else
log "Continue disabled, starting fresh session"
echo "Continue disabled, starting fresh session"
[ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ] && ARGS+=(--dangerously-skip-permissions)
[ -n "$ARG_AI_PROMPT" ] && ARGS+=(-- "$ARG_AI_PROMPT")
fi
log "Running claude code with args: %s\n" "$(printf '%q ' "${ARGS[@]}")"
printf "Running claude code with args: %s\n" "$(printf '%q ' "${ARGS[@]}")"
if [ "${ARG_ENABLE_BOUNDARY:-false}" = "true" ]; then
install_boundary
log "Starting with coder boundary enabled\n"
printf "Starting with coder boundary enabled\n"
BOUNDARY_ARGS+=()
# Add default allowed URLs
BOUNDARY_ARGS+=(--allow "domain=anthropic.com" --allow "domain=registry.npmjs.org" --allow "domain=sentry.io" --allow "domain=claude.ai" --allow "domain=$ARG_CODER_HOST")
"${CORE_COMMAND[@]}" boundary-run "${BOUNDARY_ARGS[@]}" -- \
agentapi server --type claude --term-width 67 --term-height 1190 -- \
boundary-run "${BOUNDARY_ARGS[@]}" -- \
claude "${ARGS[@]}"
else
"${CORE_COMMAND[@]}" claude "${ARGS[@]}"
agentapi server --type claude --term-width 67 --term-height 1190 -- claude "${ARGS[@]}"
fi
}
@@ -1,9 +1,5 @@
import { describe, expect, it } from "bun:test";
import {
execContainer,
findResourceInstance,
removeContainer,
runContainer,
runTerraformApply,
runTerraformInit,
testRequiredVariables,
@@ -38,47 +34,5 @@ describe("code-server", async () => {
expect(t).toThrow("Offline mode does not allow extensions to be installed");
});
it("installs and runs code-server", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
});
const id = await runContainer("ubuntu:latest");
try {
await execContainer(id, [
"bash",
"-c",
"apt-get update && apt-get install -y curl",
]);
const script = findResourceInstance(state, "coder_script").script;
const result = await execContainer(id, ["bash", "-c", script]);
if (result.exitCode !== 0) {
console.log(result.stdout);
console.log(result.stderr);
}
expect(result.exitCode).toBe(0);
const version = await execContainer(id, [
"/tmp/code-server/bin/code-server",
"--version",
]);
expect(version.exitCode).toBe(0);
expect(version.stdout).toMatch(/\d+\.\d+\.\d+/);
const health = await execContainer(id, [
"curl",
"--retry",
"10",
"--retry-delay",
"1",
"--retry-all-errors",
"-sf",
"http://localhost:13337/healthz",
]);
expect(health.exitCode).toBe(0);
} finally {
await removeContainer(id);
}
}, 60000);
// More tests depend on shebang refactors
});
@@ -8,9 +8,6 @@ tags: [ide, jetbrains, fleet]
# Jetbrains Fleet
> [!WARNING]
> **Deprecation Notice:** JetBrains has announced that Fleet will be discontinued. For more information, see [The Future of Fleet](https://blog.jetbrains.com/fleet/2025/12/the-future-of-fleet). Consider migrating to other JetBrains IDEs such as IntelliJ IDEA, PyCharm, or GoLand with the [JetBrains](https://registry.coder.com/modules/jetbrains) module.
This module adds a Jetbrains Fleet button to your Coder workspace that opens the workspace in JetBrains Fleet using SSH remote development.
JetBrains Fleet is a next-generation IDE that supports collaborative development and distributed architectures. It connects to your Coder workspace via SSH, providing a seamless remote development experience.
@@ -19,7 +16,7 @@ JetBrains Fleet is a next-generation IDE that supports collaborative development
module "jetbrains_fleet" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/jetbrains-fleet/coder"
version = "1.0.3"
version = "1.0.2"
agent_id = coder_agent.main.id
}
```
@@ -40,7 +37,7 @@ module "jetbrains_fleet" {
module "jetbrains_fleet" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/jetbrains-fleet/coder"
version = "1.0.3"
version = "1.0.2"
agent_id = coder_agent.main.id
}
```
@@ -51,7 +48,7 @@ module "jetbrains_fleet" {
module "jetbrains_fleet" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/jetbrains-fleet/coder"
version = "1.0.3"
version = "1.0.2"
agent_id = coder_agent.main.id
folder = "/home/coder/project"
}
@@ -63,7 +60,7 @@ module "jetbrains_fleet" {
module "jetbrains_fleet" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/jetbrains-fleet/coder"
version = "1.0.3"
version = "1.0.2"
agent_id = coder_agent.main.id
display_name = "Fleet"
group = "JetBrains IDEs"
@@ -77,7 +74,7 @@ module "jetbrains_fleet" {
module "jetbrains_fleet" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/jetbrains-fleet/coder"
version = "1.0.3"
version = "1.0.2"
agent_id = coder_agent.main.id
agent_name = coder_agent.example.name
}