Compare commits

..

56 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 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
16 changed files with 381 additions and 417 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
@@ -137,12 +137,11 @@ locals {
hcloud_server_types = {
for st in jsondecode(data.http.hcloud_server_types.response_body).server_types :
st.name => {
cores = st.cores
memory_gb = st.memory
disk_gb = st.disk
architecture = st.architecture
locations = [for l in st.locations : l.name]
deprecated = st.deprecated
cores = st.cores
memory_gb = st.memory
disk_gb = st.disk
locations = [for l in st.locations : l.name]
deprecated = st.deprecated
}
if st.deprecated == false
}
@@ -163,19 +162,6 @@ locals {
data.coder_parameter.hcloud_location.value
)
]
# Map Hetzner architecture (x86 or arm) to Coder agent architecture (amd64 or arm64)
agent_arch = try(
lookup(
{
"x86" = "amd64"
"arm" = "arm64"
},
local.hcloud_server_types[data.coder_parameter.hcloud_server_type.value].architecture,
"amd64" # Fallback if not returned
),
"amd64" # Fallback for template setup
)
}
data "coder_provisioner" "me" {}
@@ -201,7 +187,7 @@ data "coder_parameter" "home_volume_size" {
resource "coder_agent" "main" {
os = "linux"
arch = local.agent_arch
arch = "amd64"
metadata {
key = "cpu"
+14 -39
View File
@@ -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.5.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
claude_api_key = "xxxx-xxxxx-xxxx"
@@ -42,21 +42,17 @@ 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.5.0"
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 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`.
@@ -68,7 +64,7 @@ For tasks integration with AI Bridge, add `enable_aibridge = true` to the [Usage
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.7.0"
version = "4.5.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
enable_aibridge = true
@@ -97,7 +93,7 @@ data "coder_task" "me" {}
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.7.0"
version = "4.5.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
claude_api_key = "xxxx-xxxxx-xxxx"
@@ -118,7 +114,7 @@ This example shows additional configuration options for version pinning, custom
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.7.0"
version = "4.5.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
@@ -143,30 +139,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 +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.7.0"
version = "4.5.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
install_claude_code = true
@@ -196,7 +171,7 @@ variable "claude_code_oauth_token" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.7.0"
version = "4.5.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
claude_code_oauth_token = var.claude_code_oauth_token
@@ -269,7 +244,7 @@ resource "coder_env" "bedrock_api_key" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.7.0"
version = "4.5.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
@@ -326,7 +301,7 @@ resource "coder_env" "google_application_credentials" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.7.0"
version = "4.5.0"
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");
});
});
+86 -56
View File
@@ -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,12 +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"
@@ -256,6 +244,12 @@ variable "enable_aibridge" {
}
}
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
@@ -356,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
@@ -378,51 +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_USE_BOUNDARY_DIRECTLY='${var.use_boundary_directly}' \
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, "'", "'\\''")) : ""}' \
ARG_MCP_CONFIG_REMOTE_PATH='${base64encode(jsonencode(var.mcp_config_remote_path))}' \
ARG_ENABLE_AIBRIDGE='${var.enable_aibridge}' \
/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)
}
@@ -16,7 +16,6 @@ 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}
@@ -31,26 +30,12 @@ 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 +112,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
@@ -2,6 +2,8 @@
set -euo pipefail
true > "$HOME/start.log"
command_exists() {
command -v "$1" > /dev/null 2>&1
}
@@ -16,37 +18,42 @@ 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:-}
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_USE_BOUNDARY_DIRECTLY: %s\n" "$ARG_USE_BOUNDARY_DIRECTLY"
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" = "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)"
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"
@@ -54,24 +61,22 @@ 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
# 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"
sudo cp scripts/boundary-wrapper.sh /usr/local/bin/boundary-run
sudo chmod +x /usr/local/bin/boundary-run
else
# Use coder boundary subcommand (default) - no installation needed
echo "Using coder boundary subcommand (provided by Coder)"
# Install boundary using official install script
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
}
@@ -95,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
}
@@ -109,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
@@ -124,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
@@ -132,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
@@ -141,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
}
@@ -155,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"
@@ -177,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)
@@ -188,61 +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" = "true" ]; then
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+=()
# 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[@]}" -- \
"${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
}
+14 -28
View File
@@ -1,31 +1,31 @@
---
display_name: Mux
display_name: mux
description: Coding Agent Multiplexer - Run multiple AI agents in parallel
icon: ../../../../.icons/mux.svg
verified: true
tags: [ai, agents, development, multiplexer]
---
# Mux
# mux
Automatically install and run [Mux](https://github.com/coder/mux) in a Coder workspace. By default, the module installs `mux@next` from npm (with a fallback to downloading the npm tarball if npm is unavailable). Mux is a desktop application for parallel agentic development that enables developers to run multiple AI agents simultaneously across isolated workspaces.
Automatically install and run [mux](https://github.com/coder/mux) in a Coder workspace. By default, the module installs `mux@next` from npm (with a fallback to downloading the npm tarball if npm is unavailable). mux is a desktop application for parallel agentic development that enables developers to run multiple AI agents simultaneously across isolated workspaces.
```tf
module "mux" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/mux/coder"
version = "1.0.8"
version = "1.0.7"
agent_id = coder_agent.main.id
}
```
![Mux](../../.images/mux-product-hero.webp)
![mux](../../.images/mux-product-hero.webp)
## Features
- **Parallel Agent Execution**: Run multiple AI agents simultaneously on different tasks
- **Mux Workspace Isolation**: Each agent works in its own isolated environment
- **Git Divergence Visualization**: Track changes across different Mux agent workspaces
- **Git Divergence Visualization**: Track changes across different mux agent workspaces
- **Long-Running Processes**: Resume AI work after interruptions
- **Cost Tracking**: Monitor API usage across agents
@@ -37,7 +37,7 @@ module "mux" {
module "mux" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/mux/coder"
version = "1.0.8"
version = "1.0.7"
agent_id = coder_agent.main.id
}
```
@@ -48,34 +48,20 @@ module "mux" {
module "mux" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/mux/coder"
version = "1.0.8"
version = "1.0.7"
agent_id = coder_agent.main.id
# Default is "latest"; set to a specific version to pin
install_version = "0.4.0"
}
```
### Open a Project on Launch
Start Mux with `mux server --add-project /path/to/project`:
```tf
module "mux" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/mux/coder"
version = "1.0.8"
agent_id = coder_agent.main.id
add-project = "/path/to/project"
}
```
### Custom Port
```tf
module "mux" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/mux/coder"
version = "1.0.8"
version = "1.0.7"
agent_id = coder_agent.main.id
port = 8080
}
@@ -83,13 +69,13 @@ module "mux" {
### Use Cached Installation
Run an existing copy of Mux if found, otherwise install from npm:
Run an existing copy of mux if found, otherwise install from npm:
```tf
module "mux" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/mux/coder"
version = "1.0.8"
version = "1.0.7"
agent_id = coder_agent.main.id
use_cached = true
}
@@ -97,13 +83,13 @@ module "mux" {
### Skip Install
Run without installing from the network (requires Mux to be pre-installed):
Run without installing from the network (requires mux to be pre-installed):
```tf
module "mux" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/mux/coder"
version = "1.0.8"
version = "1.0.7"
agent_id = coder_agent.main.id
install = false
}
@@ -115,6 +101,6 @@ module "mux" {
## Notes
- Mux is currently in preview and you may encounter bugs
- mux is currently in preview and you may encounter bugs
- Requires internet connectivity for agent operations (unless `install` is set to false)
- Installs `mux@next` from npm by default (falls back to the npm tarball if npm is unavailable)
+14 -14
View File
@@ -17,43 +17,43 @@ variable "agent_id" {
variable "port" {
type = number
description = "The port to run Mux on."
description = "The port to run mux on."
default = 4000
}
variable "display_name" {
type = string
description = "The display name for the Mux application."
default = "Mux"
description = "The display name for the mux application."
default = "mux"
}
variable "slug" {
type = string
description = "The slug for the Mux application."
description = "The slug for the mux application."
default = "mux"
}
variable "install_prefix" {
type = string
description = "The prefix to install Mux to."
description = "The prefix to install mux to."
default = "/tmp/mux"
}
variable "log_path" {
type = string
description = "The path for Mux logs."
description = "The path for mux logs."
default = "/tmp/mux.log"
}
variable "add-project" {
type = string
description = "Optional path to add/open as a project in Mux on startup."
default = null
description = "Path to add/open as a project in mux (idempotent)."
default = ""
}
variable "install_version" {
type = string
description = "The version or dist-tag of Mux to install."
description = "The version or dist-tag of mux to install."
default = "next"
}
@@ -80,13 +80,13 @@ variable "group" {
variable "install" {
type = bool
description = "Install Mux from the network (npm or tarball). If false, run without installing (requires a pre-installed Mux)."
description = "Install mux from the network (npm or tarball). If false, run without installing (requires a pre-installed mux)."
default = true
}
variable "use_cached" {
type = bool
description = "Use cached copy of Mux if present; otherwise install from npm"
description = "Use cached copy of mux if present; otherwise install from npm"
default = false
}
@@ -96,7 +96,7 @@ variable "subdomain" {
Determines whether the app will be accessed via it's own subdomain or whether it will be accessed via a path on Coder.
If wildcards have not been setup by the administrator then apps with "subdomain" set to true will not be accessible.
EOT
default = true
default = false
}
variable "open_in" {
@@ -115,13 +115,13 @@ variable "open_in" {
resource "coder_script" "mux" {
agent_id = var.agent_id
display_name = var.display_name
display_name = "mux"
icon = "/icon/mux.svg"
script = templatefile("${path.module}/run.sh", {
VERSION : var.install_version,
PORT : var.port,
LOG_PATH : var.log_path,
ADD_PROJECT : var.add-project == null ? "" : var.add-project,
ADD_PROJECT : var.add-project,
INSTALL_PREFIX : var.install_prefix,
OFFLINE : !var.install,
USE_CACHED : var.use_cached,