mirror of
https://github.com/coder/registry.git
synced 2026-06-03 21:18:15 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5d0504aef9 | |||
| c1c0dec90f | |||
| 59b67c2c98 | |||
| 7abe422e0a | |||
| db8217e4e5 | |||
| f75afeb0c8 | |||
| 182e5548e2 | |||
| d057a820c1 | |||
| b4e9545c35 | |||
| 50ac3b31f6 | |||
| 056937a758 | |||
| af8b4f02fd | |||
| 2de6a57a3f | |||
| 60fec19d7d |
@@ -48,7 +48,7 @@ jobs:
|
||||
- name: Validate formatting
|
||||
run: bun fmt:ci
|
||||
- name: Check for typos
|
||||
uses: crate-ci/typos@v1.36.3
|
||||
uses: crate-ci/typos@v1.37.2
|
||||
with:
|
||||
config: .github/typos.toml
|
||||
validate-readme-files:
|
||||
|
||||
@@ -495,4 +495,8 @@ When reporting bugs, include:
|
||||
4. **Breaking changes** without defaults
|
||||
5. **Not running** formatting (`bun run fmt`) and tests (`terraform test`) before submitting
|
||||
|
||||
## For Maintainers
|
||||
|
||||
Guidelines for reviewing PRs, managing releases, and maintaining the registry. [See the maintainer guide for detailed information.](./MAINTAINER.md)
|
||||
|
||||
Happy contributing! 🚀
|
||||
|
||||
+3
-1
@@ -23,6 +23,7 @@ Check that PRs have:
|
||||
- [ ] Working tests (`terraform test`)
|
||||
- [ ] Formatted code (`bun run fmt`)
|
||||
- [ ] Avatar image for new namespaces (`avatar.png` or `avatar.svg` in `.images/`)
|
||||
- [ ] Version label: `version:patch`, `version:minor`, or `version:major`
|
||||
|
||||
### Version Guidelines
|
||||
|
||||
@@ -32,7 +33,8 @@ When reviewing PRs, ensure the version change follows semantic versioning:
|
||||
- **Minor** (1.2.3 → 1.3.0): New features, adding inputs
|
||||
- **Major** (1.2.3 → 2.0.0): Breaking changes (removing inputs, changing types)
|
||||
|
||||
PRs should clearly indicate the version change (e.g., `v1.2.3 → v1.2.4`).
|
||||
PRs should clearly indicate the intended version change (e.g., `v1.2.3 → v1.2.4`) and include the appropriate label: `version:patch`, `version:minor`, or `version:major`.
|
||||
The “Version Bump” CI uses this label to validate required updates (README version refs, etc.).
|
||||
|
||||
### Validate READMEs
|
||||
|
||||
|
||||
@@ -48,3 +48,7 @@ Simply include that snippet inside your Coder template, defining any data depend
|
||||
## Contributing
|
||||
|
||||
We are always accepting new contributions. [Please see our contributing guide for more information.](./CONTRIBUTING.md)
|
||||
|
||||
## For Maintainers
|
||||
|
||||
Guidelines for maintainers reviewing PRs and managing releases. [See the maintainer guide for more information.](./MAINTAINER.md)
|
||||
|
||||
@@ -13,7 +13,7 @@ Run Auggie CLI in your workspace to access Augment's AI coding assistant with ad
|
||||
```tf
|
||||
module "auggie" {
|
||||
source = "registry.coder.com/coder-labs/auggie/coder"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/project"
|
||||
}
|
||||
@@ -47,7 +47,7 @@ module "coder-login" {
|
||||
|
||||
module "auggie" {
|
||||
source = "registry.coder.com/coder-labs/auggie/coder"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/project"
|
||||
|
||||
@@ -103,7 +103,7 @@ EOF
|
||||
```tf
|
||||
module "auggie" {
|
||||
source = "registry.coder.com/coder-labs/auggie/coder"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/project"
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ variable "install_agentapi" {
|
||||
variable "agentapi_version" {
|
||||
type = string
|
||||
description = "The version of AgentAPI to install."
|
||||
default = "v0.6.0"
|
||||
default = "v0.10.0"
|
||||
validation {
|
||||
condition = can(regex("^v[0-9]+\\.[0-9]+\\.[0-9]+", var.agentapi_version))
|
||||
error_message = "agentapi_version must be a valid semantic version starting with 'v', like 'v0.3.3'."
|
||||
@@ -178,7 +178,7 @@ locals {
|
||||
|
||||
module "agentapi" {
|
||||
source = "registry.coder.com/coder/agentapi/coder"
|
||||
version = "1.1.1"
|
||||
version = "1.2.0"
|
||||
|
||||
agent_id = var.agent_id
|
||||
web_app_slug = local.app_slug
|
||||
|
||||
@@ -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 = "2.0.0"
|
||||
version = "2.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
openai_api_key = var.openai_api_key
|
||||
folder = "/home/coder/project"
|
||||
@@ -33,7 +33,7 @@ module "codex" {
|
||||
module "codex" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder-labs/codex/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
openai_api_key = "..."
|
||||
folder = "/home/coder/project"
|
||||
@@ -60,7 +60,7 @@ module "coder-login" {
|
||||
|
||||
module "codex" {
|
||||
source = "registry.coder.com/coder-labs/codex/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
openai_api_key = "..."
|
||||
ai_prompt = data.coder_parameter.ai_prompt.value
|
||||
@@ -106,7 +106,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 = "2.0.0"
|
||||
version = "2.1.0"
|
||||
# ... other variables ...
|
||||
|
||||
# Override default configuration
|
||||
|
||||
@@ -80,7 +80,7 @@ variable "install_agentapi" {
|
||||
variable "agentapi_version" {
|
||||
type = string
|
||||
description = "The version of AgentAPI to install."
|
||||
default = "v0.5.0"
|
||||
default = "v0.10.0"
|
||||
}
|
||||
|
||||
variable "codex_model" {
|
||||
@@ -128,7 +128,7 @@ locals {
|
||||
|
||||
module "agentapi" {
|
||||
source = "registry.coder.com/coder/agentapi/coder"
|
||||
version = "1.1.1"
|
||||
version = "1.2.0"
|
||||
|
||||
agent_id = var.agent_id
|
||||
web_app_slug = local.app_slug
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
---
|
||||
display_name: Copilot CLI
|
||||
description: GitHub Copilot CLI agent for AI-powered terminal assistance
|
||||
icon: ../../../../.icons/github.svg
|
||||
verified: false
|
||||
tags: [agent, copilot, ai, github, tasks]
|
||||
---
|
||||
|
||||
# Copilot
|
||||
|
||||
Run [GitHub Copilot CLI](https://docs.github.com/copilot/concepts/agents/about-copilot-cli) in your workspace for AI-powered coding assistance directly from the terminal. This module integrates with [AgentAPI](https://github.com/coder/agentapi) for task reporting in the Coder UI.
|
||||
|
||||
```tf
|
||||
module "copilot" {
|
||||
source = "registry.coder.com/coder-labs/copilot/coder"
|
||||
version = "0.2.0"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder/projects"
|
||||
}
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This example assumes you have [Coder external authentication](https://coder.com/docs/admin/external-auth) configured with `id = "github"`. If not, you can provide a direct token using the `github_token` variable or provide the correct external authentication id for GitHub by setting `external_auth_id = "my-github"`.
|
||||
|
||||
> [!NOTE]
|
||||
> By default, this module is configured to run the embedded chat interface as a path-based application. In production, we recommend that you configure a [wildcard access URL](https://coder.com/docs/admin/setup#wildcard-access-url) and set `subdomain = true`. See [here](https://coder.com/docs/tutorials/best-practices/security-best-practices#disable-path-based-apps) for more details.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Node.js v22+** and **npm v10+**
|
||||
- **[Active Copilot subscription](https://docs.github.com/en/copilot/about-github-copilot/subscription-plans-for-github-copilot)** (GitHub Copilot Pro, Pro+, Business, or Enterprise)
|
||||
- **GitHub authentication** via one of:
|
||||
- [Coder external authentication](https://coder.com/docs/admin/external-auth) (recommended)
|
||||
- Direct token via `github_token` variable
|
||||
- Interactive login in Copilot
|
||||
|
||||
## Examples
|
||||
|
||||
### Usage with Tasks
|
||||
|
||||
For development environments where you want Copilot to have full access to tools and automatically resume sessions:
|
||||
|
||||
```tf
|
||||
data "coder_parameter" "ai_prompt" {
|
||||
type = "string"
|
||||
name = "AI Prompt"
|
||||
default = ""
|
||||
description = "Initial task prompt for Copilot."
|
||||
mutable = true
|
||||
}
|
||||
|
||||
module "copilot" {
|
||||
source = "registry.coder.com/coder-labs/copilot/coder"
|
||||
version = "0.2.0"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder/projects"
|
||||
|
||||
ai_prompt = data.coder_parameter.ai_prompt.value
|
||||
copilot_model = "claude-sonnet-4.5"
|
||||
allow_all_tools = true
|
||||
resume_session = true
|
||||
|
||||
trusted_directories = ["/home/coder/projects", "/tmp"]
|
||||
}
|
||||
```
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
Customize tool permissions, MCP servers, and Copilot settings:
|
||||
|
||||
```tf
|
||||
module "copilot" {
|
||||
source = "registry.coder.com/coder-labs/copilot/coder"
|
||||
version = "0.2.0"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder/projects"
|
||||
|
||||
# Version pinning (defaults to "0.0.334", use "latest" for newest version)
|
||||
copilot_version = "latest"
|
||||
|
||||
# Tool permissions
|
||||
allow_tools = ["shell(git)", "shell(npm)", "write"]
|
||||
trusted_directories = ["/home/coder/projects", "/tmp"]
|
||||
|
||||
# Custom Copilot configuration
|
||||
copilot_config = jsonencode({
|
||||
banner = "never"
|
||||
theme = "dark"
|
||||
})
|
||||
|
||||
# MCP server configuration
|
||||
mcp_config = jsonencode({
|
||||
mcpServers = {
|
||||
filesystem = {
|
||||
command = "npx"
|
||||
args = ["-y", "@modelcontextprotocol/server-filesystem", "/home/coder/projects"]
|
||||
description = "Provides file system access to the workspace"
|
||||
name = "Filesystem"
|
||||
timeout = 3000
|
||||
type = "local"
|
||||
tools = ["*"]
|
||||
trust = true
|
||||
}
|
||||
playwright = {
|
||||
command = "npx"
|
||||
args = ["-y", "@playwright/mcp@latest", "--headless", "--isolated"]
|
||||
description = "Browser automation for testing and previewing changes"
|
||||
name = "Playwright"
|
||||
timeout = 5000
|
||||
type = "local"
|
||||
tools = ["*"]
|
||||
trust = false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
# Pre-install Node.js if needed
|
||||
pre_install_script = <<-EOT
|
||||
#!/bin/bash
|
||||
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
|
||||
sudo apt-get install -y nodejs
|
||||
EOT
|
||||
}
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> GitHub Copilot CLI does not automatically install MCP servers. You have two options:
|
||||
>
|
||||
> - Use `npx -y` in the MCP config (shown above) to auto-install on each run
|
||||
> - Pre-install MCP servers in `pre_install_script` for faster startup (e.g., `npm install -g @modelcontextprotocol/server-filesystem`)
|
||||
|
||||
### Direct Token Authentication
|
||||
|
||||
Use this example when you want to provide a GitHub Personal Access Token instead of using Coder external auth:
|
||||
|
||||
```tf
|
||||
variable "github_token" {
|
||||
type = string
|
||||
description = "GitHub Personal Access Token"
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
module "copilot" {
|
||||
source = "registry.coder.com/coder-labs/copilot/coder"
|
||||
version = "0.2.0"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder/projects"
|
||||
github_token = var.github_token
|
||||
}
|
||||
```
|
||||
|
||||
### Standalone Mode
|
||||
|
||||
Run Copilot as a command-line tool without task reporting or web interface. This installs and configures Copilot, making it available as a CLI app in the Coder agent bar that you can launch to interact with Copilot directly from your terminal. Set `report_tasks = false` to disable integration with Coder Tasks.
|
||||
|
||||
```tf
|
||||
module "copilot" {
|
||||
source = "registry.coder.com/coder-labs/copilot/coder"
|
||||
version = "0.2.0"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder"
|
||||
report_tasks = false
|
||||
cli_app = true
|
||||
}
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
The module supports multiple authentication methods (in priority order):
|
||||
|
||||
1. **[Coder External Auth](https://coder.com/docs/admin/external-auth) (Recommended)** - Automatic if GitHub external auth is configured in Coder
|
||||
2. **Direct Token** - Pass `github_token` variable (OAuth or Personal Access Token)
|
||||
3. **Interactive** - Copilot prompts for login via `/login` command if no auth found
|
||||
|
||||
> [!NOTE]
|
||||
> OAuth tokens work best with Copilot. Personal Access Tokens may have limited functionality.
|
||||
|
||||
## Session Resumption
|
||||
|
||||
By default, the module resumes the latest Copilot session when the workspace restarts. Set `resume_session = false` to always start fresh sessions.
|
||||
|
||||
> [!NOTE]
|
||||
> Session resumption requires persistent storage for the home directory or workspace volume. Without persistent storage, sessions will not resume across workspace restarts.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you encounter any issues, check the log files in the `~/.copilot-module` directory within your workspace for detailed information.
|
||||
|
||||
```bash
|
||||
# Installation logs
|
||||
cat ~/.copilot-module/install.log
|
||||
|
||||
# Startup logs
|
||||
cat ~/.copilot-module/agentapi-start.log
|
||||
|
||||
# Pre/post install script logs
|
||||
cat ~/.copilot-module/pre_install.log
|
||||
cat ~/.copilot-module/post_install.log
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> To use tasks with Copilot, you must have an active GitHub Copilot subscription.
|
||||
> The `workdir` variable is required and specifies the directory where Copilot will run.
|
||||
|
||||
## References
|
||||
|
||||
- [GitHub Copilot CLI Documentation](https://docs.github.com/en/copilot/concepts/agents/about-copilot-cli)
|
||||
- [Installing GitHub Copilot CLI](https://docs.github.com/en/copilot/how-tos/set-up/install-copilot-cli)
|
||||
- [AgentAPI Documentation](https://github.com/coder/agentapi)
|
||||
- [Coder AI Agents Guide](https://coder.com/docs/tutorials/ai-agents)
|
||||
@@ -0,0 +1,236 @@
|
||||
run "defaults_are_correct" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent"
|
||||
workdir = "/home/coder"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.copilot_model == "claude-sonnet-4.5"
|
||||
error_message = "Default model should be 'claude-sonnet-4.5'"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.report_tasks == true
|
||||
error_message = "Task reporting should be enabled by default"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.resume_session == true
|
||||
error_message = "Session resumption should be enabled by default"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.allow_all_tools == false
|
||||
error_message = "allow_all_tools should be disabled by default"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = resource.coder_env.mcp_app_status_slug.name == "CODER_MCP_APP_STATUS_SLUG"
|
||||
error_message = "Status slug env var should be created"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = resource.coder_env.mcp_app_status_slug.value == "copilot"
|
||||
error_message = "Status slug value should be 'copilot'"
|
||||
}
|
||||
}
|
||||
|
||||
run "github_token_creates_env_var" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent"
|
||||
workdir = "/home/coder"
|
||||
github_token = "test_github_token_abc123"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = length(resource.coder_env.github_token) == 1
|
||||
error_message = "github_token env var should be created when token is provided"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = resource.coder_env.github_token[0].name == "GITHUB_TOKEN"
|
||||
error_message = "github_token env var name should be 'GITHUB_TOKEN'"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = resource.coder_env.github_token[0].value == "test_github_token_abc123"
|
||||
error_message = "github_token env var value should match input"
|
||||
}
|
||||
}
|
||||
|
||||
run "github_token_not_created_when_empty" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent"
|
||||
workdir = "/home/coder"
|
||||
github_token = ""
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = length(resource.coder_env.github_token) == 0
|
||||
error_message = "github_token env var should not be created when empty"
|
||||
}
|
||||
}
|
||||
|
||||
run "copilot_model_env_var_for_non_default" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent"
|
||||
workdir = "/home/coder"
|
||||
copilot_model = "claude-sonnet-4"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = length(resource.coder_env.copilot_model) == 1
|
||||
error_message = "copilot_model env var should be created for non-default model"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = resource.coder_env.copilot_model[0].name == "COPILOT_MODEL"
|
||||
error_message = "copilot_model env var name should be 'COPILOT_MODEL'"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = resource.coder_env.copilot_model[0].value == "claude-sonnet-4"
|
||||
error_message = "copilot_model env var value should match input"
|
||||
}
|
||||
}
|
||||
|
||||
run "copilot_model_not_created_for_default" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent"
|
||||
workdir = "/home/coder"
|
||||
copilot_model = "claude-sonnet-4.5"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = length(resource.coder_env.copilot_model) == 0
|
||||
error_message = "copilot_model env var should not be created for default model"
|
||||
}
|
||||
}
|
||||
|
||||
run "model_validation_accepts_valid_models" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent"
|
||||
workdir = "/home/coder"
|
||||
copilot_model = "gpt-5"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = contains(["claude-sonnet-4", "claude-sonnet-4.5", "gpt-5"], var.copilot_model)
|
||||
error_message = "Model should be one of the valid options"
|
||||
}
|
||||
}
|
||||
|
||||
run "copilot_config_merges_with_trusted_directories" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent"
|
||||
workdir = "/home/coder/project"
|
||||
trusted_directories = ["/workspace", "/data"]
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = length(local.final_copilot_config) > 0
|
||||
error_message = "final_copilot_config should be computed"
|
||||
}
|
||||
|
||||
# Verify workdir is trimmed of trailing slash
|
||||
assert {
|
||||
condition = local.workdir == "/home/coder/project"
|
||||
error_message = "workdir should be trimmed of trailing slash"
|
||||
}
|
||||
}
|
||||
|
||||
run "custom_copilot_config_overrides_default" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent"
|
||||
workdir = "/home/coder"
|
||||
copilot_config = jsonencode({
|
||||
banner = "always"
|
||||
theme = "dark"
|
||||
})
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.copilot_config != ""
|
||||
error_message = "Custom copilot config should be set"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = jsondecode(local.final_copilot_config).banner == "always"
|
||||
error_message = "Custom banner setting should be applied"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = jsondecode(local.final_copilot_config).theme == "dark"
|
||||
error_message = "Custom theme setting should be applied"
|
||||
}
|
||||
}
|
||||
|
||||
run "trusted_directories_merged_with_custom_config" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent"
|
||||
workdir = "/home/coder/project"
|
||||
copilot_config = jsonencode({
|
||||
banner = "always"
|
||||
theme = "dark"
|
||||
trusted_folders = ["/custom"]
|
||||
})
|
||||
trusted_directories = ["/workspace", "/data"]
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = contains(jsondecode(local.final_copilot_config).trusted_folders, "/custom")
|
||||
error_message = "Custom trusted folder should be included"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = contains(jsondecode(local.final_copilot_config).trusted_folders, "/home/coder/project")
|
||||
error_message = "Workdir should be included in trusted folders"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = contains(jsondecode(local.final_copilot_config).trusted_folders, "/workspace")
|
||||
error_message = "trusted_directories should be merged into config"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = contains(jsondecode(local.final_copilot_config).trusted_folders, "/data")
|
||||
error_message = "All trusted_directories should be merged into config"
|
||||
}
|
||||
}
|
||||
|
||||
run "app_slug_is_consistent" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent"
|
||||
workdir = "/home/coder"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = local.app_slug == "copilot"
|
||||
error_message = "app_slug should be 'copilot'"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = local.module_dir_name == ".copilot-module"
|
||||
error_message = "module_dir_name should be '.copilot-module'"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import {
|
||||
findResourceInstance,
|
||||
runTerraformApply,
|
||||
runTerraformInit,
|
||||
testRequiredVariables,
|
||||
} from "~test";
|
||||
|
||||
describe("copilot", async () => {
|
||||
await runTerraformInit(import.meta.dir);
|
||||
|
||||
testRequiredVariables(import.meta.dir, {
|
||||
agent_id: "test-agent",
|
||||
workdir: "/home/coder",
|
||||
});
|
||||
|
||||
it("creates mcp_app_status_slug env var", async () => {
|
||||
const state = await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "test-agent",
|
||||
workdir: "/home/coder",
|
||||
});
|
||||
|
||||
const statusSlugEnv = findResourceInstance(
|
||||
state,
|
||||
"coder_env",
|
||||
"mcp_app_status_slug",
|
||||
);
|
||||
expect(statusSlugEnv).toBeDefined();
|
||||
expect(statusSlugEnv.name).toBe("CODER_MCP_APP_STATUS_SLUG");
|
||||
expect(statusSlugEnv.value).toBe("copilot");
|
||||
});
|
||||
|
||||
it("creates github_token env var with correct value", async () => {
|
||||
const state = await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "test-agent",
|
||||
workdir: "/home/coder",
|
||||
github_token: "test_token_12345",
|
||||
});
|
||||
|
||||
const githubTokenEnv = findResourceInstance(
|
||||
state,
|
||||
"coder_env",
|
||||
"github_token",
|
||||
);
|
||||
expect(githubTokenEnv).toBeDefined();
|
||||
expect(githubTokenEnv.name).toBe("GITHUB_TOKEN");
|
||||
expect(githubTokenEnv.value).toBe("test_token_12345");
|
||||
});
|
||||
|
||||
it("does not create github_token env var when empty", async () => {
|
||||
const state = await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "test-agent",
|
||||
workdir: "/home/coder",
|
||||
github_token: "",
|
||||
});
|
||||
|
||||
const githubTokenEnvs = state.resources.filter(
|
||||
(r) => r.type === "coder_env" && r.name === "github_token",
|
||||
);
|
||||
expect(githubTokenEnvs.length).toBe(0);
|
||||
});
|
||||
|
||||
it("creates copilot_model env var for non-default models", async () => {
|
||||
const state = await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "test-agent",
|
||||
workdir: "/home/coder",
|
||||
copilot_model: "claude-sonnet-4",
|
||||
});
|
||||
|
||||
const modelEnv = findResourceInstance(state, "coder_env", "copilot_model");
|
||||
expect(modelEnv).toBeDefined();
|
||||
expect(modelEnv.name).toBe("COPILOT_MODEL");
|
||||
expect(modelEnv.value).toBe("claude-sonnet-4");
|
||||
});
|
||||
|
||||
it("does not create copilot_model env var for default model", async () => {
|
||||
const state = await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "test-agent",
|
||||
workdir: "/home/coder",
|
||||
copilot_model: "claude-sonnet-4.5",
|
||||
});
|
||||
|
||||
const modelEnvs = state.resources.filter(
|
||||
(r) => r.type === "coder_env" && r.name === "copilot_model",
|
||||
);
|
||||
expect(modelEnvs.length).toBe(0);
|
||||
});
|
||||
|
||||
it("creates coder_script resources via agentapi module", async () => {
|
||||
const state = await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "test-agent",
|
||||
workdir: "/home/coder",
|
||||
});
|
||||
|
||||
// The agentapi module should create coder_script resources for install and start
|
||||
const scripts = state.resources.filter((r) => r.type === "coder_script");
|
||||
expect(scripts.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("validates copilot_model accepts valid values", async () => {
|
||||
// Test valid models don't throw errors
|
||||
await expect(
|
||||
runTerraformApply(import.meta.dir, {
|
||||
agent_id: "test-agent",
|
||||
workdir: "/home/coder",
|
||||
copilot_model: "gpt-5",
|
||||
}),
|
||||
).resolves.toBeDefined();
|
||||
|
||||
await expect(
|
||||
runTerraformApply(import.meta.dir, {
|
||||
agent_id: "test-agent",
|
||||
workdir: "/home/coder",
|
||||
copilot_model: "claude-sonnet-4.5",
|
||||
}),
|
||||
).resolves.toBeDefined();
|
||||
});
|
||||
|
||||
it("merges trusted_directories with custom copilot_config", async () => {
|
||||
const state = await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "test-agent",
|
||||
workdir: "/home/coder/project",
|
||||
trusted_directories: JSON.stringify(["/workspace", "/data"]),
|
||||
copilot_config: JSON.stringify({
|
||||
banner: "always",
|
||||
theme: "dark",
|
||||
trusted_folders: ["/custom"],
|
||||
}),
|
||||
});
|
||||
|
||||
// Verify that the state was created successfully with the merged config
|
||||
// The actual merging logic is tested in the .tftest.hcl file
|
||||
expect(state).toBeDefined();
|
||||
expect(state.resources).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,301 @@
|
||||
terraform {
|
||||
required_version = ">= 1.0"
|
||||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = ">= 2.7"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "agent_id" {
|
||||
type = string
|
||||
description = "The ID of a Coder agent."
|
||||
}
|
||||
|
||||
variable "workdir" {
|
||||
type = string
|
||||
description = "The folder to run Copilot in."
|
||||
}
|
||||
|
||||
variable "external_auth_id" {
|
||||
type = string
|
||||
description = "ID of the GitHub external auth provider configured in Coder."
|
||||
default = "github"
|
||||
}
|
||||
|
||||
variable "github_token" {
|
||||
type = string
|
||||
description = "GitHub OAuth token or Personal Access Token. If provided, this will be used instead of auto-detecting authentication."
|
||||
default = ""
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "copilot_model" {
|
||||
type = string
|
||||
description = "Model to use. Supported values: claude-sonnet-4, claude-sonnet-4.5 (default), gpt-5."
|
||||
default = "claude-sonnet-4.5"
|
||||
validation {
|
||||
condition = contains(["claude-sonnet-4", "claude-sonnet-4.5", "gpt-5"], var.copilot_model)
|
||||
error_message = "copilot_model must be one of: claude-sonnet-4, claude-sonnet-4.5, gpt-5."
|
||||
}
|
||||
}
|
||||
|
||||
variable "copilot_config" {
|
||||
type = string
|
||||
description = "Custom Copilot configuration as JSON string. Leave empty to use default configuration with banner disabled, theme set to auto, and workdir as trusted folder."
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "ai_prompt" {
|
||||
type = string
|
||||
description = "Initial task prompt for programmatic mode."
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "system_prompt" {
|
||||
type = string
|
||||
description = "The system prompt to use for the Copilot server. Task reporting instructions are automatically added when report_tasks is enabled."
|
||||
default = "You are a helpful coding assistant that helps developers write, debug, and understand code. Provide clear explanations, follow best practices, and help solve coding problems efficiently."
|
||||
}
|
||||
|
||||
variable "trusted_directories" {
|
||||
type = list(string)
|
||||
description = "Additional directories to trust for Copilot operations."
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "allow_all_tools" {
|
||||
type = bool
|
||||
description = "Allow all tools without prompting (equivalent to --allow-all-tools)."
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "allow_tools" {
|
||||
type = list(string)
|
||||
description = "Specific tools to allow: shell(command), write, or MCP_SERVER_NAME."
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "deny_tools" {
|
||||
type = list(string)
|
||||
description = "Specific tools to deny: shell(command), write, or MCP_SERVER_NAME."
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "mcp_config" {
|
||||
type = string
|
||||
description = "Custom MCP server configuration as JSON string."
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "install_agentapi" {
|
||||
type = bool
|
||||
description = "Whether to install AgentAPI."
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "agentapi_version" {
|
||||
type = string
|
||||
description = "The version of AgentAPI to install."
|
||||
default = "v0.10.0"
|
||||
}
|
||||
|
||||
variable "copilot_version" {
|
||||
type = string
|
||||
description = "The version of GitHub Copilot CLI to install. Use 'latest' for the latest version or specify a version like '0.0.334'."
|
||||
default = "0.0.334"
|
||||
}
|
||||
|
||||
variable "report_tasks" {
|
||||
type = bool
|
||||
description = "Whether to enable task reporting to Coder UI via AgentAPI."
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "subdomain" {
|
||||
type = bool
|
||||
description = "Whether to use a subdomain for AgentAPI."
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "order" {
|
||||
type = number
|
||||
description = "The order determines the position of app in the UI presentation."
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "group" {
|
||||
type = string
|
||||
description = "The name of a group that this app belongs to."
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "icon" {
|
||||
type = string
|
||||
description = "The icon to use for the app."
|
||||
default = "/icon/github.svg"
|
||||
}
|
||||
|
||||
variable "web_app_display_name" {
|
||||
type = string
|
||||
description = "Display name for the web app."
|
||||
default = "Copilot"
|
||||
}
|
||||
|
||||
variable "cli_app" {
|
||||
type = bool
|
||||
description = "Whether to create a CLI app for Copilot."
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "cli_app_display_name" {
|
||||
type = string
|
||||
description = "Display name for the CLI app."
|
||||
default = "Copilot"
|
||||
}
|
||||
|
||||
variable "resume_session" {
|
||||
type = bool
|
||||
description = "Whether to automatically resume the latest Copilot session on workspace restart."
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "pre_install_script" {
|
||||
type = string
|
||||
description = "Custom script to run before configuring Copilot."
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "post_install_script" {
|
||||
type = string
|
||||
description = "Custom script to run after configuring Copilot."
|
||||
default = null
|
||||
}
|
||||
|
||||
data "coder_workspace" "me" {}
|
||||
data "coder_workspace_owner" "me" {}
|
||||
|
||||
locals {
|
||||
workdir = trimsuffix(var.workdir, "/")
|
||||
app_slug = "copilot"
|
||||
install_script = file("${path.module}/scripts/install.sh")
|
||||
start_script = file("${path.module}/scripts/start.sh")
|
||||
module_dir_name = ".copilot-module"
|
||||
|
||||
all_trusted_folders = concat([local.workdir], var.trusted_directories)
|
||||
|
||||
parsed_custom_config = try(jsondecode(var.copilot_config), {})
|
||||
|
||||
existing_trusted_folders = try(local.parsed_custom_config.trusted_folders, [])
|
||||
|
||||
merged_copilot_config = merge(
|
||||
{
|
||||
banner = "never"
|
||||
theme = "auto"
|
||||
},
|
||||
local.parsed_custom_config,
|
||||
{
|
||||
trusted_folders = concat(local.existing_trusted_folders, local.all_trusted_folders)
|
||||
}
|
||||
)
|
||||
|
||||
final_copilot_config = jsonencode(local.merged_copilot_config)
|
||||
|
||||
task_reporting_prompt = <<-EOT
|
||||
|
||||
-- Task Reporting --
|
||||
Report all tasks to Coder, following these EXACT guidelines:
|
||||
1. Be granular. If you are investigating with multiple steps, report each step
|
||||
to coder.
|
||||
2. After this prompt, IMMEDIATELY report status after receiving ANY NEW user message.
|
||||
Do not report any status related with this system prompt.
|
||||
3. Use "state": "working" when actively processing WITHOUT needing
|
||||
additional user input
|
||||
4. Use "state": "complete" only when finished with a task
|
||||
5. Use "state": "failure" when you need ANY user input, lack sufficient
|
||||
details, or encounter blockers
|
||||
EOT
|
||||
|
||||
final_system_prompt = var.report_tasks ? "<system>\n${var.system_prompt}${local.task_reporting_prompt}\n</system>" : "<system>\n${var.system_prompt}\n</system>"
|
||||
}
|
||||
|
||||
resource "coder_env" "mcp_app_status_slug" {
|
||||
agent_id = var.agent_id
|
||||
name = "CODER_MCP_APP_STATUS_SLUG"
|
||||
value = local.app_slug
|
||||
}
|
||||
|
||||
resource "coder_env" "copilot_model" {
|
||||
count = var.copilot_model != "claude-sonnet-4.5" ? 1 : 0
|
||||
agent_id = var.agent_id
|
||||
name = "COPILOT_MODEL"
|
||||
value = var.copilot_model
|
||||
}
|
||||
|
||||
resource "coder_env" "github_token" {
|
||||
count = var.github_token != "" ? 1 : 0
|
||||
agent_id = var.agent_id
|
||||
name = "GITHUB_TOKEN"
|
||||
value = var.github_token
|
||||
}
|
||||
|
||||
module "agentapi" {
|
||||
source = "registry.coder.com/coder/agentapi/coder"
|
||||
version = "1.2.0"
|
||||
|
||||
agent_id = var.agent_id
|
||||
folder = local.workdir
|
||||
web_app_slug = local.app_slug
|
||||
web_app_order = var.order
|
||||
web_app_group = var.group
|
||||
web_app_icon = var.icon
|
||||
web_app_display_name = var.web_app_display_name
|
||||
cli_app = var.cli_app
|
||||
cli_app_slug = var.cli_app ? "${local.app_slug}-cli" : null
|
||||
cli_app_display_name = var.cli_app ? var.cli_app_display_name : null
|
||||
agentapi_subdomain = var.subdomain
|
||||
module_dir_name = local.module_dir_name
|
||||
install_agentapi = var.install_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_WORKDIR='${local.workdir}' \
|
||||
ARG_AI_PROMPT='${base64encode(var.ai_prompt)}' \
|
||||
ARG_SYSTEM_PROMPT='${base64encode(local.final_system_prompt)}' \
|
||||
ARG_COPILOT_MODEL='${var.copilot_model}' \
|
||||
ARG_ALLOW_ALL_TOOLS='${var.allow_all_tools}' \
|
||||
ARG_ALLOW_TOOLS='${join(",", var.allow_tools)}' \
|
||||
ARG_DENY_TOOLS='${join(",", var.deny_tools)}' \
|
||||
ARG_TRUSTED_DIRECTORIES='${join(",", var.trusted_directories)}' \
|
||||
ARG_EXTERNAL_AUTH_ID='${var.external_auth_id}' \
|
||||
ARG_RESUME_SESSION='${var.resume_session}' \
|
||||
/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_MCP_APP_STATUS_SLUG='${local.app_slug}' \
|
||||
ARG_REPORT_TASKS='${var.report_tasks}' \
|
||||
ARG_WORKDIR='${local.workdir}' \
|
||||
ARG_MCP_CONFIG='${var.mcp_config != "" ? base64encode(var.mcp_config) : ""}' \
|
||||
ARG_COPILOT_CONFIG='${base64encode(local.final_copilot_config)}' \
|
||||
ARG_EXTERNAL_AUTH_ID='${var.external_auth_id}' \
|
||||
ARG_COPILOT_VERSION='${var.copilot_version}' \
|
||||
ARG_COPILOT_MODEL='${var.copilot_model}' \
|
||||
/tmp/install.sh
|
||||
EOT
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$HOME"/.bashrc
|
||||
|
||||
command_exists() {
|
||||
command -v "$1" > /dev/null 2>&1
|
||||
}
|
||||
|
||||
ARG_WORKDIR=${ARG_WORKDIR:-"$HOME"}
|
||||
ARG_REPORT_TASKS=${ARG_REPORT_TASKS:-true}
|
||||
ARG_MCP_APP_STATUS_SLUG=${ARG_MCP_APP_STATUS_SLUG:-}
|
||||
ARG_MCP_CONFIG=$(echo -n "${ARG_MCP_CONFIG:-}" | base64 -d 2> /dev/null || echo "")
|
||||
ARG_COPILOT_CONFIG=$(echo -n "${ARG_COPILOT_CONFIG:-}" | base64 -d 2> /dev/null || echo "")
|
||||
ARG_EXTERNAL_AUTH_ID=${ARG_EXTERNAL_AUTH_ID:-github}
|
||||
ARG_COPILOT_VERSION=${ARG_COPILOT_VERSION:-0.0.334}
|
||||
ARG_COPILOT_MODEL=${ARG_COPILOT_MODEL:-claude-sonnet-4.5}
|
||||
|
||||
validate_prerequisites() {
|
||||
if ! command_exists node; then
|
||||
echo "ERROR: Node.js not found. Copilot requires Node.js v22+."
|
||||
echo "Install with: curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - && sudo apt-get install -y nodejs"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command_exists npm; then
|
||||
echo "ERROR: npm not found. Copilot requires npm v10+."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
node_version=$(node --version | sed 's/v//' | cut -d. -f1)
|
||||
if [ "$node_version" -lt 22 ]; then
|
||||
echo "WARNING: Node.js v$node_version detected. Copilot requires v22+."
|
||||
fi
|
||||
}
|
||||
|
||||
install_copilot() {
|
||||
if ! command_exists copilot; then
|
||||
echo "Installing GitHub Copilot CLI (version: ${ARG_COPILOT_VERSION})..."
|
||||
if [ "$ARG_COPILOT_VERSION" = "latest" ]; then
|
||||
npm install -g @github/copilot
|
||||
else
|
||||
npm install -g "@github/copilot@${ARG_COPILOT_VERSION}"
|
||||
fi
|
||||
|
||||
if ! command_exists copilot; then
|
||||
echo "ERROR: Failed to install Copilot"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "GitHub Copilot CLI installed successfully"
|
||||
else
|
||||
echo "GitHub Copilot CLI already installed"
|
||||
fi
|
||||
}
|
||||
|
||||
check_github_authentication() {
|
||||
echo "Checking GitHub authentication..."
|
||||
|
||||
if [ -n "${GITHUB_TOKEN:-}" ]; then
|
||||
echo "✓ GitHub token provided via module configuration"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if command_exists coder; then
|
||||
if coder external-auth access-token "${ARG_EXTERNAL_AUTH_ID:-github}" > /dev/null 2>&1; then
|
||||
echo "✓ GitHub OAuth authentication via Coder external auth"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if command_exists gh && gh auth status > /dev/null 2>&1; then
|
||||
echo "✓ GitHub OAuth authentication via GitHub CLI"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "⚠ No GitHub authentication detected"
|
||||
echo " Copilot will prompt for authentication when started"
|
||||
echo " For seamless experience, configure GitHub external auth in Coder or run 'gh auth login'"
|
||||
return 0
|
||||
}
|
||||
|
||||
setup_copilot_configurations() {
|
||||
mkdir -p "$ARG_WORKDIR"
|
||||
|
||||
local module_path="$HOME/.copilot-module"
|
||||
mkdir -p "$module_path"
|
||||
|
||||
setup_copilot_config
|
||||
|
||||
echo "$ARG_WORKDIR" > "$module_path/trusted_directories"
|
||||
}
|
||||
|
||||
setup_copilot_config() {
|
||||
export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
|
||||
local copilot_config_dir="$XDG_CONFIG_HOME/.copilot"
|
||||
local copilot_config_file="$copilot_config_dir/config.json"
|
||||
local mcp_config_file="$copilot_config_dir/mcp-config.json"
|
||||
|
||||
mkdir -p "$copilot_config_dir"
|
||||
|
||||
if [ -n "$ARG_COPILOT_CONFIG" ]; then
|
||||
echo "Setting up Copilot configuration..."
|
||||
|
||||
if command_exists jq; then
|
||||
echo "$ARG_COPILOT_CONFIG" | jq 'del(.mcpServers)' > "$copilot_config_file"
|
||||
else
|
||||
echo "$ARG_COPILOT_CONFIG" > "$copilot_config_file"
|
||||
fi
|
||||
|
||||
echo "Setting up MCP server configuration..."
|
||||
setup_mcp_config "$mcp_config_file"
|
||||
else
|
||||
echo "ERROR: No Copilot configuration provided"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
setup_mcp_config() {
|
||||
local mcp_config_file="$1"
|
||||
|
||||
echo '{"mcpServers": {}}' > "$mcp_config_file"
|
||||
|
||||
if [ "$ARG_REPORT_TASKS" = "true" ] && [ -n "$ARG_MCP_APP_STATUS_SLUG" ]; then
|
||||
echo "Adding Coder MCP server for task reporting..."
|
||||
setup_coder_mcp_server "$mcp_config_file"
|
||||
fi
|
||||
|
||||
if [ -n "$ARG_MCP_CONFIG" ]; then
|
||||
echo "Adding custom MCP servers..."
|
||||
add_custom_mcp_servers "$mcp_config_file"
|
||||
fi
|
||||
|
||||
echo "MCP configuration completed: $mcp_config_file"
|
||||
}
|
||||
|
||||
setup_coder_mcp_server() {
|
||||
local mcp_config_file="$1"
|
||||
|
||||
local coder_mcp_wrapper_script
|
||||
coder_mcp_wrapper_script=$(
|
||||
cat << EOF
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
export CODER_MCP_APP_STATUS_SLUG="${ARG_MCP_APP_STATUS_SLUG}"
|
||||
export CODER_MCP_AI_AGENTAPI_URL="http://localhost:3284"
|
||||
export CODER_AGENT_URL="${CODER_AGENT_URL}"
|
||||
export CODER_AGENT_TOKEN="${CODER_AGENT_TOKEN}"
|
||||
|
||||
exec coder exp mcp server
|
||||
EOF
|
||||
)
|
||||
echo "$coder_mcp_wrapper_script" > "/tmp/coder-mcp-server.sh"
|
||||
chmod +x /tmp/coder-mcp-server.sh
|
||||
|
||||
local coder_mcp_config
|
||||
coder_mcp_config=$(
|
||||
cat << EOF
|
||||
{
|
||||
"mcpServers": {
|
||||
"coder": {
|
||||
"command": "/tmp/coder-mcp-server.sh",
|
||||
"args": [],
|
||||
"description": "CRITICAL: Call this tool immediately after receiving ANY user message to report task status. Rules: (1) Call FIRST before doing work - report what you will do with state='working'. (2) Be granular - report each step separately. (3) State 'working' = actively processing without needing user input. (4) State 'complete' = task 100% finished. (5) State 'failure' = need user input, missing info, or blocked. Example: User says 'fix the bug' -> call with state='working', description='Investigating authentication bug'. When done -> call with state='complete', description='Fixed token validation'. You MUST report on every interaction.",
|
||||
"name": "Coder",
|
||||
"timeout": 3000,
|
||||
"type": "local",
|
||||
"tools": ["*"],
|
||||
"trust": true
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
echo "$coder_mcp_config" > "$mcp_config_file"
|
||||
}
|
||||
|
||||
add_custom_mcp_servers() {
|
||||
local mcp_config_file="$1"
|
||||
|
||||
if command_exists jq; then
|
||||
local custom_servers
|
||||
custom_servers=$(echo "$ARG_MCP_CONFIG" | jq '.mcpServers // {}')
|
||||
|
||||
local updated_config
|
||||
updated_config=$(jq --argjson custom "$custom_servers" '.mcpServers += $custom' "$mcp_config_file")
|
||||
echo "$updated_config" > "$mcp_config_file"
|
||||
elif command_exists node; then
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const existing = JSON.parse(fs.readFileSync('$mcp_config_file', 'utf8'));
|
||||
const input = JSON.parse(\`$ARG_MCP_CONFIG\`);
|
||||
const custom = input.mcpServers || {};
|
||||
existing.mcpServers = {...existing.mcpServers, ...custom};
|
||||
fs.writeFileSync('$mcp_config_file', JSON.stringify(existing, null, 2));
|
||||
"
|
||||
else
|
||||
echo "WARNING: jq and node not available, cannot merge custom MCP servers"
|
||||
fi
|
||||
}
|
||||
|
||||
configure_copilot_model() {
|
||||
if [ -n "$ARG_COPILOT_MODEL" ] && [ "$ARG_COPILOT_MODEL" != "claude-sonnet-4.5" ]; then
|
||||
echo "Setting Copilot model to: $ARG_COPILOT_MODEL"
|
||||
copilot config model "$ARG_COPILOT_MODEL" || {
|
||||
echo "WARNING: Failed to set model via copilot config, will use environment variable fallback"
|
||||
export COPILOT_MODEL="$ARG_COPILOT_MODEL"
|
||||
}
|
||||
fi
|
||||
}
|
||||
|
||||
configure_coder_integration() {
|
||||
if [ "$ARG_REPORT_TASKS" = "true" ] && [ -n "$ARG_MCP_APP_STATUS_SLUG" ]; then
|
||||
echo "Configuring Copilot task reporting..."
|
||||
export CODER_MCP_APP_STATUS_SLUG="$ARG_MCP_APP_STATUS_SLUG"
|
||||
export CODER_MCP_AI_AGENTAPI_URL="http://localhost:3284"
|
||||
echo "✓ Coder MCP server configured for task reporting"
|
||||
else
|
||||
echo "Task reporting disabled or no app status slug provided."
|
||||
export CODER_MCP_APP_STATUS_SLUG=""
|
||||
export CODER_MCP_AI_AGENTAPI_URL=""
|
||||
fi
|
||||
}
|
||||
|
||||
validate_prerequisites
|
||||
install_copilot
|
||||
check_github_authentication
|
||||
setup_copilot_configurations
|
||||
configure_copilot_model
|
||||
configure_coder_integration
|
||||
|
||||
echo "Copilot module setup completed."
|
||||
@@ -0,0 +1,157 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$HOME"/.bashrc
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
|
||||
command_exists() {
|
||||
command -v "$1" > /dev/null 2>&1
|
||||
}
|
||||
|
||||
ARG_WORKDIR=${ARG_WORKDIR:-"$HOME"}
|
||||
ARG_AI_PROMPT=$(echo -n "${ARG_AI_PROMPT:-}" | base64 -d 2> /dev/null || echo "")
|
||||
ARG_SYSTEM_PROMPT=$(echo -n "${ARG_SYSTEM_PROMPT:-}" | base64 -d 2> /dev/null || echo "")
|
||||
ARG_COPILOT_MODEL=${ARG_COPILOT_MODEL:-}
|
||||
ARG_ALLOW_ALL_TOOLS=${ARG_ALLOW_ALL_TOOLS:-false}
|
||||
ARG_ALLOW_TOOLS=${ARG_ALLOW_TOOLS:-}
|
||||
ARG_DENY_TOOLS=${ARG_DENY_TOOLS:-}
|
||||
ARG_TRUSTED_DIRECTORIES=${ARG_TRUSTED_DIRECTORIES:-}
|
||||
ARG_EXTERNAL_AUTH_ID=${ARG_EXTERNAL_AUTH_ID:-github}
|
||||
ARG_RESUME_SESSION=${ARG_RESUME_SESSION:-true}
|
||||
|
||||
validate_copilot_installation() {
|
||||
if ! command_exists copilot; then
|
||||
echo "ERROR: Copilot not installed. Run: npm install -g @github/copilot"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
build_initial_prompt() {
|
||||
local initial_prompt=""
|
||||
|
||||
if [ -n "$ARG_AI_PROMPT" ]; then
|
||||
if [ -n "$ARG_SYSTEM_PROMPT" ]; then
|
||||
initial_prompt="$ARG_SYSTEM_PROMPT
|
||||
|
||||
$ARG_AI_PROMPT"
|
||||
else
|
||||
initial_prompt="$ARG_AI_PROMPT"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$initial_prompt"
|
||||
}
|
||||
|
||||
build_copilot_args() {
|
||||
COPILOT_ARGS=()
|
||||
|
||||
if [ "$ARG_ALLOW_ALL_TOOLS" = "true" ]; then
|
||||
COPILOT_ARGS+=(--allow-all-tools)
|
||||
fi
|
||||
|
||||
if [ -n "$ARG_ALLOW_TOOLS" ]; then
|
||||
IFS=',' read -ra ALLOW_ARRAY <<< "$ARG_ALLOW_TOOLS"
|
||||
for tool in "${ALLOW_ARRAY[@]}"; do
|
||||
if [ -n "$tool" ]; then
|
||||
COPILOT_ARGS+=(--allow-tool "$tool")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -n "$ARG_DENY_TOOLS" ]; then
|
||||
IFS=',' read -ra DENY_ARRAY <<< "$ARG_DENY_TOOLS"
|
||||
for tool in "${DENY_ARRAY[@]}"; do
|
||||
if [ -n "$tool" ]; then
|
||||
COPILOT_ARGS+=(--deny-tool "$tool")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
check_existing_session() {
|
||||
if [ "$ARG_RESUME_SESSION" = "true" ]; then
|
||||
if copilot --help > /dev/null 2>&1; then
|
||||
local session_dir="$HOME/.copilot/history-session-state"
|
||||
if [ -d "$session_dir" ] && [ -n "$(ls "$session_dir"/session_*_*.json 2> /dev/null)" ]; then
|
||||
echo "Found existing Copilot session. Will continue latest session." >&2
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
setup_github_authentication() {
|
||||
export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
|
||||
echo "Setting up GitHub authentication..."
|
||||
|
||||
if [ -n "${GITHUB_TOKEN:-}" ]; then
|
||||
export GH_TOKEN="$GITHUB_TOKEN"
|
||||
echo "✓ Using GitHub token from module configuration"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if command_exists coder; then
|
||||
local github_token
|
||||
if github_token=$(coder external-auth access-token "${ARG_EXTERNAL_AUTH_ID:-github}" 2> /dev/null); then
|
||||
if [ -n "$github_token" ] && [ "$github_token" != "null" ]; then
|
||||
export GITHUB_TOKEN="$github_token"
|
||||
export GH_TOKEN="$github_token"
|
||||
echo "✓ Using Coder external auth OAuth token"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if command_exists gh && gh auth status > /dev/null 2>&1; then
|
||||
echo "✓ Using GitHub CLI OAuth authentication"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "⚠ No GitHub authentication available"
|
||||
echo " Copilot will prompt for login during first use"
|
||||
echo " Use the '/login' command in Copilot to authenticate"
|
||||
return 0
|
||||
}
|
||||
|
||||
start_agentapi() {
|
||||
echo "Starting in directory: $ARG_WORKDIR"
|
||||
cd "$ARG_WORKDIR"
|
||||
|
||||
build_copilot_args
|
||||
|
||||
if check_existing_session; then
|
||||
echo "Continuing latest Copilot session..."
|
||||
if [ ${#COPILOT_ARGS[@]} -gt 0 ]; then
|
||||
echo "Copilot arguments: ${COPILOT_ARGS[*]}"
|
||||
agentapi server --type copilot --term-width 120 --term-height 40 -- copilot --continue "${COPILOT_ARGS[@]}"
|
||||
else
|
||||
agentapi server --type copilot --term-width 120 --term-height 40 -- copilot --continue
|
||||
fi
|
||||
else
|
||||
echo "Starting new Copilot session..."
|
||||
local initial_prompt
|
||||
initial_prompt=$(build_initial_prompt)
|
||||
|
||||
if [ -n "$initial_prompt" ]; then
|
||||
echo "Using initial prompt with system context"
|
||||
if [ ${#COPILOT_ARGS[@]} -gt 0 ]; then
|
||||
echo "Copilot arguments: ${COPILOT_ARGS[*]}"
|
||||
agentapi server -I="$initial_prompt" --type copilot --term-width 120 --term-height 40 -- copilot "${COPILOT_ARGS[@]}"
|
||||
else
|
||||
agentapi server -I="$initial_prompt" --type copilot --term-width 120 --term-height 40 -- copilot
|
||||
fi
|
||||
else
|
||||
if [ ${#COPILOT_ARGS[@]} -gt 0 ]; then
|
||||
echo "Copilot arguments: ${COPILOT_ARGS[*]}"
|
||||
agentapi server --type copilot --term-width 120 --term-height 40 -- copilot "${COPILOT_ARGS[@]}"
|
||||
else
|
||||
agentapi server --type copilot --term-width 120 --term-height 40 -- copilot
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
setup_github_authentication
|
||||
validate_copilot_installation
|
||||
start_agentapi
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "$1" == "--version" ]]; then
|
||||
echo "GitHub Copilot CLI v1.0.0"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
while true; do
|
||||
echo "$(date) - Copilot mock running..."
|
||||
sleep 15
|
||||
done
|
||||
@@ -13,7 +13,7 @@ Run the Cursor Agent CLI in your workspace for interactive coding assistance and
|
||||
```tf
|
||||
module "cursor_cli" {
|
||||
source = "registry.coder.com/coder-labs/cursor-cli/coder"
|
||||
version = "0.1.1"
|
||||
version = "0.2.0"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/project"
|
||||
}
|
||||
@@ -42,7 +42,7 @@ module "coder-login" {
|
||||
|
||||
module "cursor_cli" {
|
||||
source = "registry.coder.com/coder-labs/cursor-cli/coder"
|
||||
version = "0.1.1"
|
||||
version = "0.2.0"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/project"
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ variable "install_agentapi" {
|
||||
variable "agentapi_version" {
|
||||
type = string
|
||||
description = "The version of AgentAPI to install."
|
||||
default = "v0.5.0"
|
||||
default = "v0.10.0"
|
||||
}
|
||||
|
||||
variable "force" {
|
||||
@@ -131,7 +131,7 @@ resource "coder_env" "cursor_api_key" {
|
||||
|
||||
module "agentapi" {
|
||||
source = "registry.coder.com/coder/agentapi/coder"
|
||||
version = "1.1.1"
|
||||
version = "1.2.0"
|
||||
|
||||
agent_id = var.agent_id
|
||||
web_app_slug = local.app_slug
|
||||
|
||||
@@ -13,7 +13,7 @@ Run [Gemini CLI](https://github.com/google-gemini/gemini-cli) in your workspace
|
||||
```tf
|
||||
module "gemini" {
|
||||
source = "registry.coder.com/coder-labs/gemini/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/project"
|
||||
}
|
||||
@@ -46,7 +46,7 @@ variable "gemini_api_key" {
|
||||
|
||||
module "gemini" {
|
||||
source = "registry.coder.com/coder-labs/gemini/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
gemini_api_key = var.gemini_api_key
|
||||
folder = "/home/coder/project"
|
||||
@@ -94,7 +94,7 @@ data "coder_parameter" "ai_prompt" {
|
||||
module "gemini" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder-labs/gemini/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
gemini_api_key = var.gemini_api_key
|
||||
gemini_model = "gemini-2.5-flash"
|
||||
@@ -118,7 +118,7 @@ For enterprise users who prefer Google's Vertex AI platform:
|
||||
```tf
|
||||
module "gemini" {
|
||||
source = "registry.coder.com/coder-labs/gemini/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
gemini_api_key = var.gemini_api_key
|
||||
folder = "/home/coder/project"
|
||||
|
||||
@@ -81,7 +81,7 @@ variable "install_agentapi" {
|
||||
variable "agentapi_version" {
|
||||
type = string
|
||||
description = "The version of AgentAPI to install."
|
||||
default = "v0.2.3"
|
||||
default = "v0.10.0"
|
||||
}
|
||||
|
||||
variable "gemini_model" {
|
||||
@@ -176,7 +176,7 @@ EOT
|
||||
|
||||
module "agentapi" {
|
||||
source = "registry.coder.com/coder/agentapi/coder"
|
||||
version = "1.1.1"
|
||||
version = "1.2.0"
|
||||
|
||||
agent_id = var.agent_id
|
||||
web_app_slug = local.app_slug
|
||||
|
||||
@@ -13,7 +13,7 @@ Run [Amp CLI](https://ampcode.com/) in your workspace to access Sourcegraph's AI
|
||||
```tf
|
||||
module "amp-cli" {
|
||||
source = "registry.coder.com/coder-labs/sourcegraph-amp/coder"
|
||||
version = "1.0.3"
|
||||
version = "1.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
sourcegraph_amp_api_key = var.sourcegraph_amp_api_key
|
||||
install_sourcegraph_amp = true
|
||||
@@ -60,7 +60,7 @@ variable "sourcegraph_amp_api_key" {
|
||||
module "amp-cli" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder-labs/sourcegraph-amp/coder"
|
||||
version = "1.0.3"
|
||||
version = "1.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
sourcegraph_amp_api_key = var.sourcegraph_amp_api_key # recommended for authenticated usage
|
||||
install_sourcegraph_amp = true
|
||||
|
||||
@@ -69,7 +69,7 @@ variable "install_agentapi" {
|
||||
variable "agentapi_version" {
|
||||
type = string
|
||||
description = "The version of AgentAPI to install."
|
||||
default = "v0.3.0"
|
||||
default = "v0.10.0"
|
||||
}
|
||||
|
||||
variable "pre_install_script" {
|
||||
@@ -151,7 +151,7 @@ locals {
|
||||
|
||||
module "agentapi" {
|
||||
source = "registry.coder.com/coder/agentapi/coder"
|
||||
version = "1.0.1"
|
||||
version = "1.2.0"
|
||||
|
||||
agent_id = var.agent_id
|
||||
web_app_slug = local.app_slug
|
||||
|
||||
@@ -16,7 +16,7 @@ The AgentAPI module is a building block for modules that need to run an AgentAPI
|
||||
```tf
|
||||
module "agentapi" {
|
||||
source = "registry.coder.com/coder/agentapi/coder"
|
||||
version = "1.1.1"
|
||||
version = "1.2.0"
|
||||
|
||||
agent_id = var.agent_id
|
||||
web_app_slug = local.app_slug
|
||||
|
||||
@@ -117,7 +117,7 @@ variable "install_agentapi" {
|
||||
variable "agentapi_version" {
|
||||
type = string
|
||||
description = "The version of AgentAPI to install."
|
||||
default = "v0.3.3"
|
||||
default = "v0.10.0"
|
||||
}
|
||||
|
||||
variable "agentapi_port" {
|
||||
|
||||
@@ -13,7 +13,7 @@ Run [Amazon Q](https://aws.amazon.com/q/) in your workspace to access Amazon's A
|
||||
```tf
|
||||
module "amazon-q" {
|
||||
source = "registry.coder.com/coder/amazon-q/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder"
|
||||
|
||||
@@ -102,7 +102,7 @@ data "coder_parameter" "ai_prompt" {
|
||||
|
||||
module "amazon-q" {
|
||||
source = "registry.coder.com/coder/amazon-q/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder"
|
||||
auth_tarball = var.amazon_q_auth_tarball
|
||||
@@ -228,7 +228,7 @@ If no custom `agent_config` is provided, the default agent name "agent" is used.
|
||||
```tf
|
||||
module "amazon-q" {
|
||||
source = "registry.coder.com/coder/amazon-q/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder"
|
||||
auth_tarball = var.amazon_q_auth_tarball
|
||||
@@ -258,7 +258,7 @@ This example will:
|
||||
```tf
|
||||
module "amazon-q" {
|
||||
source = "registry.coder.com/coder/amazon-q/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder"
|
||||
auth_tarball = var.amazon_q_auth_tarball
|
||||
@@ -279,7 +279,7 @@ module "amazon-q" {
|
||||
```tf
|
||||
module "amazon-q" {
|
||||
source = "registry.coder.com/coder/amazon-q/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder"
|
||||
auth_tarball = var.amazon_q_auth_tarball
|
||||
@@ -305,7 +305,7 @@ module "amazon-q" {
|
||||
```tf
|
||||
module "amazon-q" {
|
||||
source = "registry.coder.com/coder/amazon-q/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder"
|
||||
auth_tarball = var.amazon_q_auth_tarball
|
||||
@@ -319,7 +319,7 @@ module "amazon-q" {
|
||||
```tf
|
||||
module "amazon-q" {
|
||||
source = "registry.coder.com/coder/amazon-q/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder"
|
||||
auth_tarball = var.amazon_q_auth_tarball
|
||||
@@ -340,14 +340,14 @@ module "amazon-q" {
|
||||
```tf
|
||||
module "amazon-q" {
|
||||
source = "registry.coder.com/coder/amazon-q/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder"
|
||||
auth_tarball = var.amazon_q_auth_tarball
|
||||
|
||||
# AgentAPI configuration for environments without wildcard access url. https://coder.com/docs/admin/setup#wildcard-access-url
|
||||
agentapi_chat_based_path = true
|
||||
agentapi_version = "v0.6.1"
|
||||
agentapi_version = "v0.10.0"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -358,7 +358,7 @@ For environments without direct internet access, you can host Amazon Q installat
|
||||
```tf
|
||||
module "amazon-q" {
|
||||
source = "registry.coder.com/coder/amazon-q/coder"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder"
|
||||
auth_tarball = var.amazon_q_auth_tarball
|
||||
|
||||
@@ -88,7 +88,7 @@ variable "post_install_script" {
|
||||
variable "agentapi_version" {
|
||||
type = string
|
||||
description = "The version of AgentAPI to install."
|
||||
default = "v0.6.1"
|
||||
default = "v0.10.0"
|
||||
}
|
||||
|
||||
variable "workdir" {
|
||||
@@ -215,7 +215,7 @@ locals {
|
||||
|
||||
module "agentapi" {
|
||||
source = "registry.coder.com/coder/agentapi/coder"
|
||||
version = "1.1.1"
|
||||
version = "1.2.0"
|
||||
|
||||
agent_id = var.agent_id
|
||||
web_app_slug = local.app_slug
|
||||
|
||||
@@ -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 = "3.0.1"
|
||||
version = "3.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder/project"
|
||||
claude_api_key = "xxxx-xxxxx-xxxx"
|
||||
@@ -49,7 +49,7 @@ data "coder_parameter" "ai_prompt" {
|
||||
|
||||
module "claude-code" {
|
||||
source = "registry.coder.com/coder/claude-code/coder"
|
||||
version = "3.0.1"
|
||||
version = "3.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder/project"
|
||||
|
||||
@@ -58,7 +58,7 @@ module "claude-code" {
|
||||
claude_code_oauth_token = "xxxxx-xxxx-xxxx"
|
||||
|
||||
claude_code_version = "1.0.82" # Pin to a specific version
|
||||
agentapi_version = "v0.6.1"
|
||||
agentapi_version = "v0.10.0"
|
||||
|
||||
ai_prompt = data.coder_parameter.ai_prompt.value
|
||||
model = "sonnet"
|
||||
@@ -85,7 +85,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 = "3.0.1"
|
||||
version = "3.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder"
|
||||
install_claude_code = true
|
||||
@@ -108,7 +108,7 @@ variable "claude_code_oauth_token" {
|
||||
|
||||
module "claude-code" {
|
||||
source = "registry.coder.com/coder/claude-code/coder"
|
||||
version = "3.0.1"
|
||||
version = "3.0.3"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder/project"
|
||||
claude_code_oauth_token = var.claude_code_oauth_token
|
||||
|
||||
@@ -86,7 +86,7 @@ variable "install_agentapi" {
|
||||
variable "agentapi_version" {
|
||||
type = string
|
||||
description = "The version of AgentAPI to install."
|
||||
default = "v0.7.1"
|
||||
default = "v0.10.0"
|
||||
}
|
||||
|
||||
variable "ai_prompt" {
|
||||
@@ -183,7 +183,7 @@ variable "claude_code_oauth_token" {
|
||||
variable "system_prompt" {
|
||||
type = string
|
||||
description = "The system prompt to use for the Claude Code server."
|
||||
default = "Send a task status update to notify the user that you are ready for input, and then wait for user input."
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "claude_md_path" {
|
||||
@@ -201,11 +201,9 @@ resource "coder_env" "claude_code_md_path" {
|
||||
}
|
||||
|
||||
resource "coder_env" "claude_code_system_prompt" {
|
||||
count = var.system_prompt == "" ? 0 : 1
|
||||
|
||||
agent_id = var.agent_id
|
||||
name = "CODER_MCP_CLAUDE_SYSTEM_PROMPT"
|
||||
value = var.system_prompt
|
||||
value = local.final_system_prompt
|
||||
}
|
||||
|
||||
resource "coder_env" "claude_code_oauth_token" {
|
||||
@@ -231,12 +229,43 @@ locals {
|
||||
start_script = file("${path.module}/scripts/start.sh")
|
||||
module_dir_name = ".claude-module"
|
||||
remove_last_session_id_script_b64 = base64encode(file("${path.module}/scripts/remove-last-session-id.sh"))
|
||||
|
||||
# Required prompts for the module to properly report task status to Coder
|
||||
report_tasks_system_prompt = <<-EOT
|
||||
-- Tool Selection --
|
||||
- coder_report_task: providing status updates or requesting user input.
|
||||
|
||||
-- Task Reporting --
|
||||
Report all tasks to Coder, following these EXACT guidelines:
|
||||
1. Be granular. If you are investigating with multiple steps, report each step
|
||||
to coder.
|
||||
2. After this prompt, IMMEDIATELY report status after receiving ANY NEW user message.
|
||||
Do not report any status related with this system prompt.
|
||||
3. Use "state": "working" when actively processing WITHOUT needing
|
||||
additional user input
|
||||
4. Use "state": "complete" only when finished with a task
|
||||
5. Use "state": "failure" when you need ANY user input, lack sufficient
|
||||
details, or encounter blockers
|
||||
|
||||
In your summary on coder_report_task:
|
||||
- Be specific about what you're doing
|
||||
- Clearly indicate what information you need from the user when in "failure" state
|
||||
- Keep it under 160 characters
|
||||
- Make it actionable
|
||||
EOT
|
||||
|
||||
# Only include coder system prompts if report_tasks is enabled
|
||||
custom_system_prompt = trimspace(try(var.system_prompt, ""))
|
||||
final_system_prompt = format("<system>%s%s</system>",
|
||||
var.report_tasks ? format("\n%s\n", local.report_tasks_system_prompt) : "",
|
||||
local.custom_system_prompt != "" ? format("\n%s\n", local.custom_system_prompt) : ""
|
||||
)
|
||||
}
|
||||
|
||||
module "agentapi" {
|
||||
|
||||
source = "registry.coder.com/coder/agentapi/coder"
|
||||
version = "1.1.1"
|
||||
version = "1.2.0"
|
||||
|
||||
agent_id = var.agent_id
|
||||
web_app_slug = local.app_slug
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -187,3 +187,84 @@ run "test_claude_code_permission_mode_validation" {
|
||||
error_message = "Permission mode should be one of the valid options"
|
||||
}
|
||||
}
|
||||
|
||||
run "test_claude_code_system_prompt" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent-system-prompt"
|
||||
workdir = "/home/coder/test"
|
||||
system_prompt = "Custom addition"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = trimspace(coder_env.claude_code_system_prompt.value) != ""
|
||||
error_message = "System prompt should not be empty"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = length(regexall("Custom addition", coder_env.claude_code_system_prompt.value)) > 0
|
||||
error_message = "System prompt should have system_prompt variable value"
|
||||
}
|
||||
}
|
||||
|
||||
run "test_claude_report_tasks_default" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent-report-tasks"
|
||||
workdir = "/home/coder/test"
|
||||
# report_tasks: default is true
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = trimspace(coder_env.claude_code_system_prompt.value) != ""
|
||||
error_message = "System prompt should not be empty"
|
||||
}
|
||||
|
||||
# Ensure system prompt is wrapped by <system>
|
||||
assert {
|
||||
condition = startswith(trimspace(coder_env.claude_code_system_prompt.value), "<system>")
|
||||
error_message = "System prompt should start with <system>"
|
||||
}
|
||||
assert {
|
||||
condition = endswith(trimspace(coder_env.claude_code_system_prompt.value), "</system>")
|
||||
error_message = "System prompt should end with </system>"
|
||||
}
|
||||
|
||||
# Ensure Coder sections are injected when report_tasks=true (default)
|
||||
assert {
|
||||
condition = length(regexall("-- Tool Selection --", coder_env.claude_code_system_prompt.value)) > 0
|
||||
error_message = "System prompt should have Tool Selection section"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = length(regexall("-- Task Reporting --", coder_env.claude_code_system_prompt.value)) > 0
|
||||
error_message = "System prompt should have Task Reporting section"
|
||||
}
|
||||
}
|
||||
|
||||
run "test_claude_report_tasks_disabled" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent-report-tasks"
|
||||
workdir = "/home/coder/test"
|
||||
report_tasks = false
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = trimspace(coder_env.claude_code_system_prompt.value) != ""
|
||||
error_message = "System prompt should not be empty"
|
||||
}
|
||||
|
||||
# Ensure system prompt is wrapped by <system>
|
||||
assert {
|
||||
condition = startswith(trimspace(coder_env.claude_code_system_prompt.value), "<system>")
|
||||
error_message = "System prompt should start with <system>"
|
||||
}
|
||||
assert {
|
||||
condition = endswith(trimspace(coder_env.claude_code_system_prompt.value), "</system>")
|
||||
error_message = "System prompt should end with </system>"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$HOME"/.bashrc
|
||||
if [ -f "$HOME/.bashrc" ]; then
|
||||
source "$HOME"/.bashrc
|
||||
fi
|
||||
|
||||
BOLD='\033[0;1m'
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$HOME"/.bashrc
|
||||
if [ -f "$HOME/.bashrc" ]; then
|
||||
source "$HOME"/.bashrc
|
||||
fi
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
|
||||
command_exists() {
|
||||
|
||||
@@ -13,7 +13,7 @@ Run the [Goose](https://block.github.io/goose/) agent in your workspace to gener
|
||||
```tf
|
||||
module "goose" {
|
||||
source = "registry.coder.com/coder/goose/coder"
|
||||
version = "2.1.2"
|
||||
version = "2.2.0"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder"
|
||||
install_goose = true
|
||||
@@ -79,7 +79,7 @@ resource "coder_agent" "main" {
|
||||
module "goose" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/goose/coder"
|
||||
version = "2.1.2"
|
||||
version = "2.2.0"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder"
|
||||
install_goose = true
|
||||
|
||||
@@ -63,7 +63,7 @@ variable "install_agentapi" {
|
||||
variable "agentapi_version" {
|
||||
type = string
|
||||
description = "The version of AgentAPI to install."
|
||||
default = "v0.3.3"
|
||||
default = "v0.10.0"
|
||||
}
|
||||
|
||||
variable "subdomain" {
|
||||
@@ -139,7 +139,7 @@ EOT
|
||||
|
||||
module "agentapi" {
|
||||
source = "registry.coder.com/coder/agentapi/coder"
|
||||
version = "1.1.1"
|
||||
version = "1.2.0"
|
||||
|
||||
agent_id = var.agent_id
|
||||
web_app_slug = local.app_slug
|
||||
|
||||
@@ -10,6 +10,8 @@ tags: [ide, jetbrains, parameter, gateway]
|
||||
|
||||
This module adds a JetBrains Gateway Button to open any workspace with a single click.
|
||||
|
||||
> We recommend using the [Coder Toolbox module](https://registry.coder.com/modules/coder/jetbrains), which offers significant stability and connectivity benefits over Gateway. Reference our [documentation](https://coder.com/docs/user-guides/workspace-access/jetbrains/toolbox) for more information.
|
||||
|
||||
JetBrains recommends a minimum of 4 CPU cores and 8GB of RAM.
|
||||
Consult the [JetBrains documentation](https://www.jetbrains.com/help/idea/prerequisites.html#min_requirements) to confirm other system requirements.
|
||||
|
||||
@@ -17,7 +19,7 @@ Consult the [JetBrains documentation](https://www.jetbrains.com/help/idea/prereq
|
||||
module "jetbrains_gateway" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/jetbrains-gateway/coder"
|
||||
version = "1.2.2"
|
||||
version = "1.2.4"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/example"
|
||||
jetbrains_ides = ["CL", "GO", "IU", "PY", "WS"]
|
||||
@@ -35,7 +37,7 @@ module "jetbrains_gateway" {
|
||||
module "jetbrains_gateway" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/jetbrains-gateway/coder"
|
||||
version = "1.2.2"
|
||||
version = "1.2.4"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/example"
|
||||
jetbrains_ides = ["GO", "WS"]
|
||||
@@ -49,7 +51,7 @@ module "jetbrains_gateway" {
|
||||
module "jetbrains_gateway" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/jetbrains-gateway/coder"
|
||||
version = "1.2.2"
|
||||
version = "1.2.4"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/example"
|
||||
jetbrains_ides = ["IU", "PY"]
|
||||
@@ -64,7 +66,7 @@ module "jetbrains_gateway" {
|
||||
module "jetbrains_gateway" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/jetbrains-gateway/coder"
|
||||
version = "1.2.2"
|
||||
version = "1.2.4"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/example"
|
||||
jetbrains_ides = ["IU", "PY"]
|
||||
@@ -89,7 +91,7 @@ module "jetbrains_gateway" {
|
||||
module "jetbrains_gateway" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/jetbrains-gateway/coder"
|
||||
version = "1.2.2"
|
||||
version = "1.2.4"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/example"
|
||||
jetbrains_ides = ["GO", "WS"]
|
||||
@@ -107,7 +109,7 @@ Due to the highest priority of the `ide_download_link` parameter in the `(jetbra
|
||||
module "jetbrains_gateway" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/jetbrains-gateway/coder"
|
||||
version = "1.2.2"
|
||||
version = "1.2.4"
|
||||
agent_id = coder_agent.example.id
|
||||
folder = "/home/coder/example"
|
||||
jetbrains_ides = ["GO", "WS"]
|
||||
|
||||
@@ -20,7 +20,7 @@ describe("jetbrains-gateway", async () => {
|
||||
folder: "/home/coder",
|
||||
});
|
||||
expect(state.outputs.url.value).toBe(
|
||||
"jetbrains-gateway://connect#type=coder&workspace=default&owner=default&folder=/home/coder&url=https://mydeployment.coder.com&token=$SESSION_TOKEN&ide_product_code=IU&ide_build_number=243.21565.193&ide_download_link=https://download.jetbrains.com/idea/ideaIU-2024.3.tar.gz&agent_id=foo",
|
||||
"jetbrains-gateway://connect#type=coder&workspace=default&owner=default&folder=/home/coder&url=https://mydeployment.coder.com&token=$SESSION_TOKEN&ide_product_code=IU&ide_build_number=243.21565.193&ide_download_link=https://download.jetbrains.com/idea/ideaIU-2024.3.tar.gz&agent=",
|
||||
);
|
||||
|
||||
const coder_app = state.resources.find(
|
||||
@@ -40,4 +40,28 @@ describe("jetbrains-gateway", async () => {
|
||||
});
|
||||
expect(state.outputs.identifier.value).toBe("IU");
|
||||
});
|
||||
|
||||
it("optionally includes agent when an agent name is provided", async () => {
|
||||
const state = await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "foo",
|
||||
agent_name: "main",
|
||||
folder: "/home/coder",
|
||||
});
|
||||
|
||||
expect(state.outputs.url.value).toBe(
|
||||
"jetbrains-gateway://connect#type=coder&workspace=default&owner=default&folder=/home/coder&url=https://mydeployment.coder.com&token=$SESSION_TOKEN&ide_product_code=IU&ide_build_number=243.21565.193&ide_download_link=https://download.jetbrains.com/idea/ideaIU-2024.3.tar.gz&agent=main",
|
||||
);
|
||||
});
|
||||
|
||||
it("includes the agent parameter even when the provided value is blank", async () => {
|
||||
const state = await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "foo",
|
||||
agent_name: " ",
|
||||
folder: "/home/coder",
|
||||
});
|
||||
|
||||
expect(state.outputs.url.value).toBe(
|
||||
"jetbrains-gateway://connect#type=coder&workspace=default&owner=default&folder=/home/coder&url=https://mydeployment.coder.com&token=$SESSION_TOKEN&ide_product_code=IU&ide_build_number=243.21565.193&ide_download_link=https://download.jetbrains.com/idea/ideaIU-2024.3.tar.gz&agent= ",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -30,15 +30,14 @@ variable "agent_id" {
|
||||
|
||||
variable "slug" {
|
||||
type = string
|
||||
description = "The slug for the coder_app. Allows resuing the module with the same template."
|
||||
description = "The slug for the coder_app. Allows reusing the module with the same template."
|
||||
default = "gateway"
|
||||
}
|
||||
|
||||
variable "agent_name" {
|
||||
type = string
|
||||
description = "Agent name. (unused). Will be removed in a future version"
|
||||
|
||||
default = ""
|
||||
description = "Agent name."
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "folder" {
|
||||
@@ -348,8 +347,8 @@ resource "coder_app" "gateway" {
|
||||
local.build_number,
|
||||
"&ide_download_link=",
|
||||
local.download_link,
|
||||
"&agent_id=",
|
||||
var.agent_id,
|
||||
"&agent=",
|
||||
var.agent_name,
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user