From 50ac3b31f6bc6449dcc83d629a07619a6320bc06 Mon Sep 17 00:00:00 2001 From: DevCats Date: Mon, 6 Oct 2025 12:52:58 -0500 Subject: [PATCH 01/27] docs: add MAINTAINER.md link to CONTRIBUTING.md and README.md (#453) ## Description Add links to `MAINTAINER.md` in `README.md` and `CONTRIBUTING.md` to help guide internal contributors. ## Type of Change - [ ] New module - [ ] Bug fix - [ ] Feature/enhancement - [X] Documentation - [ ] Other ## Testing & Validation - [ ] Tests pass (`bun test`) - [X] Code formatted (`bun run fmt`) - [ ] Changes tested locally ## Related Issues --- CONTRIBUTING.md | 4 ++++ README.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 97a33d9f..282788ec 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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! πŸš€ diff --git a/README.md b/README.md index 23746bd6..97f4677e 100644 --- a/README.md +++ b/README.md @@ -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) From b4e9545c35a4f2a5cd6081568a9aeb80c9f44398 Mon Sep 17 00:00:00 2001 From: Satbir Chahal Date: Tue, 7 Oct 2025 00:33:17 -0700 Subject: [PATCH 02/27] fix(claude-code): source bashrc file only if it exists (#459) --- registry/coder/modules/claude-code/README.md | 8 ++++---- registry/coder/modules/claude-code/scripts/install.sh | 4 +++- registry/coder/modules/claude-code/scripts/start.sh | 4 +++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/registry/coder/modules/claude-code/README.md b/registry/coder/modules/claude-code/README.md index 3c334e74..952e3d73 100644 --- a/registry/coder/modules/claude-code/README.md +++ b/registry/coder/modules/claude-code/README.md @@ -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.0.2" 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.0.2" agent_id = coder_agent.example.id workdir = "/home/coder/project" @@ -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.0.2" 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.2" agent_id = coder_agent.example.id workdir = "/home/coder/project" claude_code_oauth_token = var.claude_code_oauth_token diff --git a/registry/coder/modules/claude-code/scripts/install.sh b/registry/coder/modules/claude-code/scripts/install.sh index c3dcc22f..1285df90 100644 --- a/registry/coder/modules/claude-code/scripts/install.sh +++ b/registry/coder/modules/claude-code/scripts/install.sh @@ -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' diff --git a/registry/coder/modules/claude-code/scripts/start.sh b/registry/coder/modules/claude-code/scripts/start.sh index b5fca7a5..6eeb411b 100644 --- a/registry/coder/modules/claude-code/scripts/start.sh +++ b/registry/coder/modules/claude-code/scripts/start.sh @@ -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() { From d057a820c1cb22bd109df74d0408f810a19a9b45 Mon Sep 17 00:00:00 2001 From: Susana Ferreira Date: Tue, 7 Oct 2025 10:09:49 +0100 Subject: [PATCH 03/27] feat(claude-code): add coder-specific prompt to system_prompt (#443) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description This PR updates the `claude-code` module to automatically include the Coder task-reporting system prompt whenever `report_tasks = true`, and to wrap the final system prompt in `…` when non-empty. Previously, users needed to manually include this content in their system prompts to enable proper task reporting. When `report_tasks = true`, the system prompt is prepended with the Coder task-reporting, and any user `system_prompt` (if provided) is appended after it, ensuring consistent integration without manual copy/paste. When `report_tasks = false`, the module includes only the user `system_prompt` (if any). If both `report_tasks = false` and `system_prompt` is empty, the system prompt sent to Claude is empty. ## Type of Change - [ ] New module - [x] Bug fix - [ ] Feature/enhancement - [ ] Documentation - [ ] Other ## Module Information **Path:** `registry/coder/modules/claude-code` **New version:** `v3.0.2` **Breaking change:** [] Yes [x] No ## Testing & Validation - [x] Tests pass (`bun test`) - [x] Code formatted (`bun run fmt`) - [x] Changes tested locally Related to internal slack thread: https://codercom.slack.com/archives/C0992H8HGCS/p1759317555713269 --------- Co-authored-by: DevCats --- registry/coder/modules/claude-code/main.tf | 31 ++++++- .../coder/modules/claude-code/main.tftest.hcl | 81 +++++++++++++++++++ 2 files changed, 108 insertions(+), 4 deletions(-) diff --git a/registry/coder/modules/claude-code/main.tf b/registry/coder/modules/claude-code/main.tf index 4836347b..6fbdc72b 100644 --- a/registry/coder/modules/claude-code/main.tf +++ b/registry/coder/modules/claude-code/main.tf @@ -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,6 +229,31 @@ 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 + EOT + + # Only include coder system prompts if report_tasks is enabled + custom_system_prompt = trimspace(try(var.system_prompt, "")) + final_system_prompt = format("%s%s", + 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" { diff --git a/registry/coder/modules/claude-code/main.tftest.hcl b/registry/coder/modules/claude-code/main.tftest.hcl index c48923cf..9999c1b1 100644 --- a/registry/coder/modules/claude-code/main.tftest.hcl +++ b/registry/coder/modules/claude-code/main.tftest.hcl @@ -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 + assert { + condition = startswith(trimspace(coder_env.claude_code_system_prompt.value), "") + error_message = "System prompt should start with " + } + assert { + condition = endswith(trimspace(coder_env.claude_code_system_prompt.value), "") + error_message = "System prompt should end with " + } + + # 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 + assert { + condition = startswith(trimspace(coder_env.claude_code_system_prompt.value), "") + error_message = "System prompt should start with " + } + assert { + condition = endswith(trimspace(coder_env.claude_code_system_prompt.value), "") + error_message = "System prompt should end with " + } +} \ No newline at end of file From 182e5548e23a84ebc52a7469779d39b3f262d0d2 Mon Sep 17 00:00:00 2001 From: Susana Ferreira Date: Tue, 7 Oct 2025 10:47:41 +0100 Subject: [PATCH 04/27] chore: update MAINTAINER.md to check PR version label (#460) ## Description Update MAINTAINER.md to include a check of the version label on the PRs ## Type of Change - [ ] New module - [ ] Bug fix - [ ] Feature/enhancement - [x] Documentation - [ ] Other --- MAINTAINER.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/MAINTAINER.md b/MAINTAINER.md index 7d80d2fa..d37ea26d 100644 --- a/MAINTAINER.md +++ b/MAINTAINER.md @@ -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 From f75afeb0c88142f5b1ef9f718541594d4249ba44 Mon Sep 17 00:00:00 2001 From: DevCats Date: Tue, 7 Oct 2025 07:47:02 -0500 Subject: [PATCH 05/27] feat: New Copilot-CLI Module (#441) ## Description New Copilot-CLI Module using AgentAPI Need to test once AgentAPI Changes are pushed. ## Type of Change - [X] New module - [ ] Bug fix - [ ] Feature/enhancement - [ ] Documentation - [ ] Other ## Module Information **Path:** `registry/coder-labs/modules/copilot-cli` **New version:** `v0.1.0` **Breaking change:** [ ] Yes [ ] No ## Testing & Validation - [X] Tests pass (`bun test`) - [X] Code formatted (`bun run fmt`) - [ ] Changes tested locally ## Related Issues --------- Co-authored-by: Atif Ali --- registry/coder-labs/modules/copilot/README.md | 210 ++++++++++++ .../modules/copilot/copilot.tftest.hcl | 236 ++++++++++++++ .../coder-labs/modules/copilot/main.test.ts | 136 ++++++++ registry/coder-labs/modules/copilot/main.tf | 300 ++++++++++++++++++ .../modules/copilot/scripts/install.sh | 233 ++++++++++++++ .../modules/copilot/scripts/start.sh | 156 +++++++++ .../modules/copilot/testdata/copilot-mock.sh | 12 + 7 files changed, 1283 insertions(+) create mode 100644 registry/coder-labs/modules/copilot/README.md create mode 100644 registry/coder-labs/modules/copilot/copilot.tftest.hcl create mode 100644 registry/coder-labs/modules/copilot/main.test.ts create mode 100644 registry/coder-labs/modules/copilot/main.tf create mode 100644 registry/coder-labs/modules/copilot/scripts/install.sh create mode 100644 registry/coder-labs/modules/copilot/scripts/start.sh create mode 100644 registry/coder-labs/modules/copilot/testdata/copilot-mock.sh diff --git a/registry/coder-labs/modules/copilot/README.md b/registry/coder-labs/modules/copilot/README.md new file mode 100644 index 00000000..c6e8dea2 --- /dev/null +++ b/registry/coder-labs/modules/copilot/README.md @@ -0,0 +1,210 @@ +--- +display_name: Copilot +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.1.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.1.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.1.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.1.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.1.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) diff --git a/registry/coder-labs/modules/copilot/copilot.tftest.hcl b/registry/coder-labs/modules/copilot/copilot.tftest.hcl new file mode 100644 index 00000000..185c019b --- /dev/null +++ b/registry/coder-labs/modules/copilot/copilot.tftest.hcl @@ -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'" + } +} diff --git a/registry/coder-labs/modules/copilot/main.test.ts b/registry/coder-labs/modules/copilot/main.test.ts new file mode 100644 index 00000000..1d438e33 --- /dev/null +++ b/registry/coder-labs/modules/copilot/main.test.ts @@ -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(); + }); +}); diff --git a/registry/coder-labs/modules/copilot/main.tf b/registry/coder-labs/modules/copilot/main.tf new file mode 100644 index 00000000..fd93b048 --- /dev/null +++ b/registry/coder-labs/modules/copilot/main.tf @@ -0,0 +1,300 @@ +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 ? "\n${var.system_prompt}${local.task_reporting_prompt}\n" : "\n${var.system_prompt}\n" +} + +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.1.1" + + 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}' \ + /tmp/install.sh + EOT +} \ No newline at end of file diff --git a/registry/coder-labs/modules/copilot/scripts/install.sh b/registry/coder-labs/modules/copilot/scripts/install.sh new file mode 100644 index 00000000..f44d5087 --- /dev/null +++ b/registry/coder-labs/modules/copilot/scripts/install.sh @@ -0,0 +1,233 @@ +#!/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} + +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" + mkdir -p "$HOME/.config" + + setup_copilot_config + + echo "$ARG_WORKDIR" > "$module_path/trusted_directories" +} + +setup_copilot_config() { + local copilot_config_dir="$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." diff --git a/registry/coder-labs/modules/copilot/scripts/start.sh b/registry/coder-labs/modules/copilot/scripts/start.sh new file mode 100644 index 00000000..91a40409 --- /dev/null +++ b/registry/coder-labs/modules/copilot/scripts/start.sh @@ -0,0 +1,156 @@ +#!/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() { + 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 diff --git a/registry/coder-labs/modules/copilot/testdata/copilot-mock.sh b/registry/coder-labs/modules/copilot/testdata/copilot-mock.sh new file mode 100644 index 00000000..f1daa15f --- /dev/null +++ b/registry/coder-labs/modules/copilot/testdata/copilot-mock.sh @@ -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 From db8217e4e5c599d5b0f4a03ad739aab043ad0915 Mon Sep 17 00:00:00 2001 From: Susana Ferreira Date: Tue, 7 Oct 2025 15:26:09 +0100 Subject: [PATCH 06/27] fix(claude-code): update inner system prompt to include summary rules (#461) ## Description Update `report_tasks_system_prompt` to include `coder_report_task` summary rules. ## Type of Change - [ ] New module - [x] Bug fix - [ ] Feature/enhancement - [ ] Documentation - [ ] Other ## Module Information **Path:** `registry/coder/modules/claude-code` **New version:** `v3.0.3` **Breaking change:** [ ] Yes [x] No ## Testing & Validation - [x] Tests pass (`bun test`) - [x] Code formatted (`bun run fmt`) - [x] Changes tested locally ## Related Issues Follow-up from: https://github.com/coder/registry/pull/443 Related to: https://github.com/coder/coder/pull/20191/files#r2410441026 --- registry/coder/modules/claude-code/README.md | 8 ++++---- registry/coder/modules/claude-code/main.tf | 6 ++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/registry/coder/modules/claude-code/README.md b/registry/coder/modules/claude-code/README.md index 952e3d73..f97a0232 100644 --- a/registry/coder/modules/claude-code/README.md +++ b/registry/coder/modules/claude-code/README.md @@ -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.2" + version = "3.0.3" 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.2" + version = "3.0.3" agent_id = coder_agent.example.id workdir = "/home/coder/project" @@ -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.2" + version = "3.0.3" 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.2" + version = "3.0.3" agent_id = coder_agent.example.id workdir = "/home/coder/project" claude_code_oauth_token = var.claude_code_oauth_token diff --git a/registry/coder/modules/claude-code/main.tf b/registry/coder/modules/claude-code/main.tf index 6fbdc72b..48303658 100644 --- a/registry/coder/modules/claude-code/main.tf +++ b/registry/coder/modules/claude-code/main.tf @@ -246,6 +246,12 @@ locals { 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 From 7abe422e0a544945a5c9423c86baa8588230a828 Mon Sep 17 00:00:00 2001 From: DevCats Date: Tue, 7 Oct 2025 12:05:50 -0500 Subject: [PATCH 07/27] fix: Add COPILOT_MODEL to install script args (#464) Closes #462 ## Description Fixes missing COPILOT_MODEL arg from install script ## Type of Change - [ ] New module - [X] Bug fix - [ ] Feature/enhancement - [ ] Documentation - [ ] Other ## Module Information **Path:** `registry/coder-labs/modules/copilot` **New version:** `v0.1.1` **Breaking change:** [ ] Yes [X] No ## Testing & Validation - [X] Tests pass (`bun test`) - [X] Code formatted (`bun run fmt`) - [X] Changes tested locally --- registry/coder-labs/modules/copilot/README.md | 10 +++++----- registry/coder-labs/modules/copilot/main.tf | 1 + registry/coder-labs/modules/copilot/scripts/install.sh | 5 +++-- registry/coder-labs/modules/copilot/scripts/start.sh | 1 + 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/registry/coder-labs/modules/copilot/README.md b/registry/coder-labs/modules/copilot/README.md index c6e8dea2..85732d49 100644 --- a/registry/coder-labs/modules/copilot/README.md +++ b/registry/coder-labs/modules/copilot/README.md @@ -13,7 +13,7 @@ Run [GitHub Copilot CLI](https://docs.github.com/copilot/concepts/agents/about-c ```tf module "copilot" { source = "registry.coder.com/coder-labs/copilot/coder" - version = "0.1.0" + version = "0.1.1" agent_id = coder_agent.example.id workdir = "/home/coder/projects" } @@ -51,7 +51,7 @@ data "coder_parameter" "ai_prompt" { module "copilot" { source = "registry.coder.com/coder-labs/copilot/coder" - version = "0.1.0" + version = "0.1.1" agent_id = coder_agent.example.id workdir = "/home/coder/projects" @@ -71,7 +71,7 @@ Customize tool permissions, MCP servers, and Copilot settings: ```tf module "copilot" { source = "registry.coder.com/coder-labs/copilot/coder" - version = "0.1.0" + version = "0.1.1" agent_id = coder_agent.example.id workdir = "/home/coder/projects" @@ -142,7 +142,7 @@ variable "github_token" { module "copilot" { source = "registry.coder.com/coder-labs/copilot/coder" - version = "0.1.0" + version = "0.1.1" agent_id = coder_agent.example.id workdir = "/home/coder/projects" github_token = var.github_token @@ -156,7 +156,7 @@ Run Copilot as a command-line tool without task reporting or web interface. This ```tf module "copilot" { source = "registry.coder.com/coder-labs/copilot/coder" - version = "0.1.0" + version = "0.1.1" agent_id = coder_agent.example.id workdir = "/home/coder" report_tasks = false diff --git a/registry/coder-labs/modules/copilot/main.tf b/registry/coder-labs/modules/copilot/main.tf index fd93b048..c6436f3e 100644 --- a/registry/coder-labs/modules/copilot/main.tf +++ b/registry/coder-labs/modules/copilot/main.tf @@ -295,6 +295,7 @@ module "agentapi" { 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 } \ No newline at end of file diff --git a/registry/coder-labs/modules/copilot/scripts/install.sh b/registry/coder-labs/modules/copilot/scripts/install.sh index f44d5087..0aabd761 100644 --- a/registry/coder-labs/modules/copilot/scripts/install.sh +++ b/registry/coder-labs/modules/copilot/scripts/install.sh @@ -14,6 +14,7 @@ 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 @@ -84,7 +85,6 @@ setup_copilot_configurations() { local module_path="$HOME/.copilot-module" mkdir -p "$module_path" - mkdir -p "$HOME/.config" setup_copilot_config @@ -92,7 +92,8 @@ setup_copilot_configurations() { } setup_copilot_config() { - local copilot_config_dir="$HOME/.copilot" + 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" diff --git a/registry/coder-labs/modules/copilot/scripts/start.sh b/registry/coder-labs/modules/copilot/scripts/start.sh index 91a40409..2653d593 100644 --- a/registry/coder-labs/modules/copilot/scripts/start.sh +++ b/registry/coder-labs/modules/copilot/scripts/start.sh @@ -82,6 +82,7 @@ check_existing_session() { } setup_github_authentication() { + export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}" echo "Setting up GitHub authentication..." if [ -n "${GITHUB_TOKEN:-}" ]; then From 59b67c2c98880892d2a99733939032b4dae1638f Mon Sep 17 00:00:00 2001 From: DevCats Date: Tue, 7 Oct 2025 12:40:23 -0500 Subject: [PATCH 08/27] chore: update display name for copilot module to Copilot CLI (#467) ## Description update display name for copilot module to Copilot CLI ## Type of Change - [ ] New module - [ ] Bug fix - [ ] Feature/enhancement - [X] Documentation - [ ] Other ## Module Information **Path:** `registry/coder-labs/modules/copilot` **New version:** `v0.1.2` **Breaking change:** [ ] Yes [X] No ## Testing & Validation - [X] Tests pass (`bun test`) - [X] Code formatted (`bun run fmt`) - [X] Changes tested locally --- registry/coder-labs/modules/copilot/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/registry/coder-labs/modules/copilot/README.md b/registry/coder-labs/modules/copilot/README.md index 85732d49..20ac8848 100644 --- a/registry/coder-labs/modules/copilot/README.md +++ b/registry/coder-labs/modules/copilot/README.md @@ -1,5 +1,5 @@ --- -display_name: Copilot +display_name: Copilot CLI description: GitHub Copilot CLI agent for AI-powered terminal assistance icon: ../../../../.icons/github.svg verified: false @@ -13,7 +13,7 @@ Run [GitHub Copilot CLI](https://docs.github.com/copilot/concepts/agents/about-c ```tf module "copilot" { source = "registry.coder.com/coder-labs/copilot/coder" - version = "0.1.1" + version = "0.1.2" agent_id = coder_agent.example.id workdir = "/home/coder/projects" } @@ -51,7 +51,7 @@ data "coder_parameter" "ai_prompt" { module "copilot" { source = "registry.coder.com/coder-labs/copilot/coder" - version = "0.1.1" + version = "0.1.2" agent_id = coder_agent.example.id workdir = "/home/coder/projects" @@ -71,7 +71,7 @@ Customize tool permissions, MCP servers, and Copilot settings: ```tf module "copilot" { source = "registry.coder.com/coder-labs/copilot/coder" - version = "0.1.1" + version = "0.1.2" agent_id = coder_agent.example.id workdir = "/home/coder/projects" @@ -142,7 +142,7 @@ variable "github_token" { module "copilot" { source = "registry.coder.com/coder-labs/copilot/coder" - version = "0.1.1" + version = "0.1.2" agent_id = coder_agent.example.id workdir = "/home/coder/projects" github_token = var.github_token @@ -156,7 +156,7 @@ Run Copilot as a command-line tool without task reporting or web interface. This ```tf module "copilot" { source = "registry.coder.com/coder-labs/copilot/coder" - version = "0.1.1" + version = "0.1.2" agent_id = coder_agent.example.id workdir = "/home/coder" report_tasks = false From c1c0dec90ff89db02eccd97ce230e24ad6f852bf Mon Sep 17 00:00:00 2001 From: 35C4n0r <70096901+35C4n0r@users.noreply.github.com> Date: Tue, 7 Oct 2025 23:39:44 +0530 Subject: [PATCH 09/27] chore: bump agentapi module version (#465) --- registry/coder/modules/agentapi/README.md | 2 +- registry/coder/modules/agentapi/main.tf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/registry/coder/modules/agentapi/README.md b/registry/coder/modules/agentapi/README.md index 0b5ed0ba..d68af511 100644 --- a/registry/coder/modules/agentapi/README.md +++ b/registry/coder/modules/agentapi/README.md @@ -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 diff --git a/registry/coder/modules/agentapi/main.tf b/registry/coder/modules/agentapi/main.tf index 225a992d..e73f45f6 100644 --- a/registry/coder/modules/agentapi/main.tf +++ b/registry/coder/modules/agentapi/main.tf @@ -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" { From 5d0504aef9a71428d463f4566257178b034b0860 Mon Sep 17 00:00:00 2001 From: 35C4n0r <70096901+35C4n0r@users.noreply.github.com> Date: Tue, 7 Oct 2025 23:57:12 +0530 Subject: [PATCH 10/27] feat: update agentapi_version to 0.10.0 (#456) Closes # ## Description ## Type of Change - [ ] New module - [ ] Bug fix - [ ] Feature/enhancement - [ ] Documentation - [ ] Other ## Module Information **Path:** `registry/[namespace]/modules/[module-name]` **New version:** `v1.0.0` **Breaking change:** [ ] Yes [ ] No ## Testing & Validation - [ ] Tests pass (`bun test`) - [ ] Code formatted (`bun run fmt`) - [ ] Changes tested locally ## Related Issues --- registry/coder-labs/modules/auggie/README.md | 6 +++--- registry/coder-labs/modules/auggie/main.tf | 4 ++-- registry/coder-labs/modules/codex/README.md | 8 ++++---- registry/coder-labs/modules/codex/main.tf | 4 ++-- registry/coder-labs/modules/copilot/README.md | 10 +++++----- registry/coder-labs/modules/copilot/main.tf | 2 +- .../coder-labs/modules/cursor-cli/README.md | 4 ++-- .../coder-labs/modules/cursor-cli/main.tf | 4 ++-- registry/coder-labs/modules/gemini/README.md | 8 ++++---- registry/coder-labs/modules/gemini/main.tf | 4 ++-- .../modules/sourcegraph-amp/README.md | 4 ++-- .../modules/sourcegraph-amp/main.tf | 4 ++-- registry/coder/modules/amazon-q/README.md | 20 +++++++++---------- registry/coder/modules/amazon-q/main.tf | 4 ++-- registry/coder/modules/claude-code/README.md | 8 ++++---- registry/coder/modules/claude-code/main.tf | 4 ++-- registry/coder/modules/goose/README.md | 4 ++-- registry/coder/modules/goose/main.tf | 4 ++-- 18 files changed, 53 insertions(+), 53 deletions(-) diff --git a/registry/coder-labs/modules/auggie/README.md b/registry/coder-labs/modules/auggie/README.md index 4b8e9315..6cb7102d 100644 --- a/registry/coder-labs/modules/auggie/README.md +++ b/registry/coder-labs/modules/auggie/README.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" diff --git a/registry/coder-labs/modules/auggie/main.tf b/registry/coder-labs/modules/auggie/main.tf index d6c326e0..5e268ab9 100644 --- a/registry/coder-labs/modules/auggie/main.tf +++ b/registry/coder-labs/modules/auggie/main.tf @@ -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 diff --git a/registry/coder-labs/modules/codex/README.md b/registry/coder-labs/modules/codex/README.md index 62c63a32..e5eaafe4 100644 --- a/registry/coder-labs/modules/codex/README.md +++ b/registry/coder-labs/modules/codex/README.md @@ -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 diff --git a/registry/coder-labs/modules/codex/main.tf b/registry/coder-labs/modules/codex/main.tf index 9fdec401..7c2e890d 100644 --- a/registry/coder-labs/modules/codex/main.tf +++ b/registry/coder-labs/modules/codex/main.tf @@ -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 diff --git a/registry/coder-labs/modules/copilot/README.md b/registry/coder-labs/modules/copilot/README.md index 20ac8848..26bdc1b4 100644 --- a/registry/coder-labs/modules/copilot/README.md +++ b/registry/coder-labs/modules/copilot/README.md @@ -13,7 +13,7 @@ Run [GitHub Copilot CLI](https://docs.github.com/copilot/concepts/agents/about-c ```tf module "copilot" { source = "registry.coder.com/coder-labs/copilot/coder" - version = "0.1.2" + version = "0.2.0" agent_id = coder_agent.example.id workdir = "/home/coder/projects" } @@ -51,7 +51,7 @@ data "coder_parameter" "ai_prompt" { module "copilot" { source = "registry.coder.com/coder-labs/copilot/coder" - version = "0.1.2" + version = "0.2.0" agent_id = coder_agent.example.id workdir = "/home/coder/projects" @@ -71,7 +71,7 @@ Customize tool permissions, MCP servers, and Copilot settings: ```tf module "copilot" { source = "registry.coder.com/coder-labs/copilot/coder" - version = "0.1.2" + version = "0.2.0" agent_id = coder_agent.example.id workdir = "/home/coder/projects" @@ -142,7 +142,7 @@ variable "github_token" { module "copilot" { source = "registry.coder.com/coder-labs/copilot/coder" - version = "0.1.2" + version = "0.2.0" agent_id = coder_agent.example.id workdir = "/home/coder/projects" github_token = var.github_token @@ -156,7 +156,7 @@ Run Copilot as a command-line tool without task reporting or web interface. This ```tf module "copilot" { source = "registry.coder.com/coder-labs/copilot/coder" - version = "0.1.2" + version = "0.2.0" agent_id = coder_agent.example.id workdir = "/home/coder" report_tasks = false diff --git a/registry/coder-labs/modules/copilot/main.tf b/registry/coder-labs/modules/copilot/main.tf index c6436f3e..72af49b9 100644 --- a/registry/coder-labs/modules/copilot/main.tf +++ b/registry/coder-labs/modules/copilot/main.tf @@ -242,7 +242,7 @@ resource "coder_env" "github_token" { module "agentapi" { source = "registry.coder.com/coder/agentapi/coder" - version = "1.1.1" + version = "1.2.0" agent_id = var.agent_id folder = local.workdir diff --git a/registry/coder-labs/modules/cursor-cli/README.md b/registry/coder-labs/modules/cursor-cli/README.md index 0d3cd753..92f045de 100644 --- a/registry/coder-labs/modules/cursor-cli/README.md +++ b/registry/coder-labs/modules/cursor-cli/README.md @@ -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" diff --git a/registry/coder-labs/modules/cursor-cli/main.tf b/registry/coder-labs/modules/cursor-cli/main.tf index 48210371..c07193a6 100644 --- a/registry/coder-labs/modules/cursor-cli/main.tf +++ b/registry/coder-labs/modules/cursor-cli/main.tf @@ -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 diff --git a/registry/coder-labs/modules/gemini/README.md b/registry/coder-labs/modules/gemini/README.md index e661093e..1d499817 100644 --- a/registry/coder-labs/modules/gemini/README.md +++ b/registry/coder-labs/modules/gemini/README.md @@ -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" diff --git a/registry/coder-labs/modules/gemini/main.tf b/registry/coder-labs/modules/gemini/main.tf index 889164b1..1dc25c6d 100644 --- a/registry/coder-labs/modules/gemini/main.tf +++ b/registry/coder-labs/modules/gemini/main.tf @@ -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 diff --git a/registry/coder-labs/modules/sourcegraph-amp/README.md b/registry/coder-labs/modules/sourcegraph-amp/README.md index d4486e65..61607d9d 100644 --- a/registry/coder-labs/modules/sourcegraph-amp/README.md +++ b/registry/coder-labs/modules/sourcegraph-amp/README.md @@ -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 diff --git a/registry/coder-labs/modules/sourcegraph-amp/main.tf b/registry/coder-labs/modules/sourcegraph-amp/main.tf index 033fc84e..b5566fef 100644 --- a/registry/coder-labs/modules/sourcegraph-amp/main.tf +++ b/registry/coder-labs/modules/sourcegraph-amp/main.tf @@ -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 diff --git a/registry/coder/modules/amazon-q/README.md b/registry/coder/modules/amazon-q/README.md index 03551487..630c3a6b 100644 --- a/registry/coder/modules/amazon-q/README.md +++ b/registry/coder/modules/amazon-q/README.md @@ -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 diff --git a/registry/coder/modules/amazon-q/main.tf b/registry/coder/modules/amazon-q/main.tf index b845cedc..a4bac213 100644 --- a/registry/coder/modules/amazon-q/main.tf +++ b/registry/coder/modules/amazon-q/main.tf @@ -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 diff --git a/registry/coder/modules/claude-code/README.md b/registry/coder/modules/claude-code/README.md index f97a0232..9486927c 100644 --- a/registry/coder/modules/claude-code/README.md +++ b/registry/coder/modules/claude-code/README.md @@ -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.3" + 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.3" + 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.3" + version = "3.1.0" agent_id = coder_agent.example.id workdir = "/home/coder" install_claude_code = true diff --git a/registry/coder/modules/claude-code/main.tf b/registry/coder/modules/claude-code/main.tf index 48303658..8909a024 100644 --- a/registry/coder/modules/claude-code/main.tf +++ b/registry/coder/modules/claude-code/main.tf @@ -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" { @@ -265,7 +265,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 diff --git a/registry/coder/modules/goose/README.md b/registry/coder/modules/goose/README.md index a1dbfefe..6e7a0c9c 100644 --- a/registry/coder/modules/goose/README.md +++ b/registry/coder/modules/goose/README.md @@ -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 diff --git a/registry/coder/modules/goose/main.tf b/registry/coder/modules/goose/main.tf index 2e015e76..09fc966c 100644 --- a/registry/coder/modules/goose/main.tf +++ b/registry/coder/modules/goose/main.tf @@ -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 From cb553209a588a81a08c7508b00fa204c6c9c2f81 Mon Sep 17 00:00:00 2001 From: romracer Date: Tue, 7 Oct 2025 13:54:14 -0500 Subject: [PATCH 11/27] fix: update CLI icon for copilot module to same icon as web app (#469) ## Description Sets `cli_app_icon` in agentapi to the same icon used for `web_app_icon`. Its currently using the default of Claude. ## Type of Change - [ ] New module - [x] Bug fix - [ ] Feature/enhancement - [ ] Documentation - [ ] Other ## Module Information **Path:** `registry/coder-labs/modules/copilot` **New version:** `v0.2.1` **Breaking change:** [ ] Yes [x] No ## Testing & Validation - [ ] Tests pass (`bun test`) - [ ] Code formatted (`bun run fmt`) - [x] Changes tested locally ## Related Issues --- registry/coder-labs/modules/copilot/README.md | 10 +++++----- registry/coder-labs/modules/copilot/main.tf | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/registry/coder-labs/modules/copilot/README.md b/registry/coder-labs/modules/copilot/README.md index 26bdc1b4..83f59c7c 100644 --- a/registry/coder-labs/modules/copilot/README.md +++ b/registry/coder-labs/modules/copilot/README.md @@ -13,7 +13,7 @@ Run [GitHub Copilot CLI](https://docs.github.com/copilot/concepts/agents/about-c ```tf module "copilot" { source = "registry.coder.com/coder-labs/copilot/coder" - version = "0.2.0" + version = "0.2.1" agent_id = coder_agent.example.id workdir = "/home/coder/projects" } @@ -51,7 +51,7 @@ data "coder_parameter" "ai_prompt" { module "copilot" { source = "registry.coder.com/coder-labs/copilot/coder" - version = "0.2.0" + version = "0.2.1" agent_id = coder_agent.example.id workdir = "/home/coder/projects" @@ -71,7 +71,7 @@ Customize tool permissions, MCP servers, and Copilot settings: ```tf module "copilot" { source = "registry.coder.com/coder-labs/copilot/coder" - version = "0.2.0" + version = "0.2.1" agent_id = coder_agent.example.id workdir = "/home/coder/projects" @@ -142,7 +142,7 @@ variable "github_token" { module "copilot" { source = "registry.coder.com/coder-labs/copilot/coder" - version = "0.2.0" + version = "0.2.1" agent_id = coder_agent.example.id workdir = "/home/coder/projects" github_token = var.github_token @@ -156,7 +156,7 @@ Run Copilot as a command-line tool without task reporting or web interface. This ```tf module "copilot" { source = "registry.coder.com/coder-labs/copilot/coder" - version = "0.2.0" + version = "0.2.1" agent_id = coder_agent.example.id workdir = "/home/coder" report_tasks = false diff --git a/registry/coder-labs/modules/copilot/main.tf b/registry/coder-labs/modules/copilot/main.tf index 72af49b9..eb9f78d4 100644 --- a/registry/coder-labs/modules/copilot/main.tf +++ b/registry/coder-labs/modules/copilot/main.tf @@ -253,6 +253,7 @@ module "agentapi" { 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_icon = var.cli_app ? var.icon : 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 From f28bcdb71318e54e25f7affd4af1c0f06f204911 Mon Sep 17 00:00:00 2001 From: Rishi Mondal Date: Wed, 8 Oct 2025 01:14:00 +0530 Subject: [PATCH 12/27] Auto-Start Development Servers Module (#316) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Auto-Start Development Servers Module ## Summary /claim #204 Implements automatic detection and startup of development servers based on project detection as requested in #204. - βœ… **Multi-language support**: Node.js, Rails, Django, Flask, Spring Boot, Go, PHP, Rust, .NET - βœ… **Background execution**: Servers start automatically without user intervention - βœ… **Devcontainer.json integration**: Uses custom start commands when available - βœ… **Smart fallback**: Creates sample project when no existing projects found - βœ… **Comprehensive logging**: Full activity logs for troubleshooting https://github.com/user-attachments/assets/2eddf67c-3ac1-4e55-a5ba-79292d61e918 ## Addresses GitHub Issue Closes #204 - "Auto-start development servers based on project detection" --------- Co-authored-by: DevCats Co-authored-by: DevCats --- registry/mavrickrishi/README.md | 1 + .../modules/auto-start-dev-server/README.md | 151 ++++++ .../auto-start-dev-server/main.test.ts | 109 ++++ .../modules/auto-start-dev-server/main.tf | 195 ++++++++ .../modules/auto-start-dev-server/run.sh | 468 ++++++++++++++++++ 5 files changed, 924 insertions(+) create mode 100644 registry/mavrickrishi/modules/auto-start-dev-server/README.md create mode 100644 registry/mavrickrishi/modules/auto-start-dev-server/main.test.ts create mode 100644 registry/mavrickrishi/modules/auto-start-dev-server/main.tf create mode 100755 registry/mavrickrishi/modules/auto-start-dev-server/run.sh diff --git a/registry/mavrickrishi/README.md b/registry/mavrickrishi/README.md index f4326ac3..c2f53d98 100644 --- a/registry/mavrickrishi/README.md +++ b/registry/mavrickrishi/README.md @@ -19,3 +19,4 @@ participating in LFX CNCF programs, and helping the developer community grow. ## Modules - **aws-ami-snapshot**: Create and manage AMI snapshots for Coder workspaces with restore capabilities +- [auto-start-dev-server](modules/auto-start-dev-server/README.md) - Automatically detect and start development servers for various project types diff --git a/registry/mavrickrishi/modules/auto-start-dev-server/README.md b/registry/mavrickrishi/modules/auto-start-dev-server/README.md new file mode 100644 index 00000000..fc6b48e4 --- /dev/null +++ b/registry/mavrickrishi/modules/auto-start-dev-server/README.md @@ -0,0 +1,151 @@ +--- +display_name: Auto-Start Development Servers +description: Automatically detect and start development servers for various project types +icon: ../../../../.icons/server.svg +verified: false +tags: [development, automation, servers] +--- + +# Auto-Start Development Servers + +Automatically detect and start development servers for various project types when a workspace starts. This module scans your workspace for common project structures and starts the appropriate development servers in the background without manual intervention. + +```tf +module "auto_start_dev_servers" { + source = "registry.coder.com/mavrickrishi/auto-start-dev-server/coder" + version = "1.0.0" + agent_id = coder_agent.main.id +} +``` + +## Features + +- **Multi-language support**: Detects and starts servers for Node.js, Python (Django/Flask), Ruby (Rails), Java (Spring Boot), Go, PHP, Rust, and .NET projects +- **Smart script prioritization**: Prioritizes `dev` scripts over `start` scripts for better development experience +- **Intelligent frontend detection**: Automatically identifies frontend projects (React, Vue, Angular, Next.js, Nuxt, Svelte, Vite) and prioritizes them for preview apps +- **Devcontainer integration**: Respects custom start commands defined in `.devcontainer/devcontainer.json` +- **Configurable scanning**: Adjustable directory scan depth and project type toggles +- **Non-blocking startup**: Servers start in the background with configurable startup delay +- **Comprehensive logging**: All server output and detection results logged to a central file +- **Smart detection**: Uses project-specific files and configurations to identify project types +- **Integrated live preview**: Automatically creates a preview app for the primary frontend project + +## Supported Project Types + +| Framework/Language | Detection Files | Start Commands (in priority order) | +| ------------------ | -------------------------------------------- | ----------------------------------------------------- | +| **Node.js/npm** | `package.json` | `npm run dev`, `npm run serve`, `npm start` (or yarn) | +| **Ruby on Rails** | `Gemfile` with rails gem | `bundle exec rails server` | +| **Django** | `manage.py` | `python manage.py runserver` | +| **Flask** | `requirements.txt` with Flask | `python app.py/main.py/run.py` | +| **Spring Boot** | `pom.xml` or `build.gradle` with spring-boot | `mvn spring-boot:run`, `gradle bootRun` | +| **Go** | `go.mod` | `go run main.go` | +| **PHP** | `composer.json` | `php -S 0.0.0.0:8080` | +| **Rust** | `Cargo.toml` | `cargo run` | +| **.NET** | `*.csproj` | `dotnet run` | + +## Examples + +### Basic Usage + +```hcl +module "auto_start" { + source = "./modules/auto-start-dev-server" + version = "1.0.0" + agent_id = coder_agent.main.id +} +``` + +### Advanced Usage + +```hcl +module "auto_start_dev_servers" { + source = "./modules/auto-start-dev-server" + version = "1.0.0" + agent_id = coder_agent.main.id + + # Optional: Configure which project types to detect + enable_npm = true + enable_rails = true + enable_django = true + enable_flask = true + enable_spring_boot = true + enable_go = true + enable_php = true + enable_rust = true + enable_dotnet = true + + # Optional: Enable devcontainer.json integration + enable_devcontainer = true + + # Optional: Workspace directory to scan (supports environment variables) + workspace_directory = "$HOME" + + # Optional: Directory scan depth (1-5) + scan_depth = 2 + + # Optional: Startup delay in seconds + startup_delay = 10 + + # Optional: Log file path + log_path = "/tmp/dev-servers.log" + + # Optional: Enable automatic preview app (default: true) + enable_preview_app = true +} +``` + +### Disable Preview App + +```hcl +module "auto_start" { + source = "./modules/auto-start-dev-server" + version = "1.0.0" + agent_id = coder_agent.main.id + + # Disable automatic preview app creation + enable_preview_app = false +} +``` + +### Selective Project Types + +```hcl +module "auto_start" { + source = "./modules/auto-start-dev-server" + version = "1.0.0" + agent_id = coder_agent.main.id + + # Only enable web development projects + enable_npm = true + enable_rails = true + enable_django = true + enable_flask = true + + # Disable other project types + enable_spring_boot = false + enable_go = false + enable_php = false + enable_rust = false + enable_dotnet = false +} +``` + +### Deep Workspace Scanning + +```hcl +module "auto_start" { + source = "./modules/auto-start-dev-server" + version = "1.0.0" + agent_id = coder_agent.main.id + + workspace_directory = "/workspaces" + scan_depth = 3 + startup_delay = 5 + log_path = "/var/log/dev-servers.log" +} +``` + +## License + +This module is provided under the same license as the Coder Registry. diff --git a/registry/mavrickrishi/modules/auto-start-dev-server/main.test.ts b/registry/mavrickrishi/modules/auto-start-dev-server/main.test.ts new file mode 100644 index 00000000..05893194 --- /dev/null +++ b/registry/mavrickrishi/modules/auto-start-dev-server/main.test.ts @@ -0,0 +1,109 @@ +import { describe, expect, it } from "bun:test"; +import { + runTerraformApply, + runTerraformInit, + testRequiredVariables, +} from "~test"; + +describe("auto-start-dev-server", async () => { + await runTerraformInit(import.meta.dir); + + testRequiredVariables(import.meta.dir, { + agent_id: "test-agent-123", + }); + + it("validates scan_depth range", () => { + const t1 = async () => { + await runTerraformApply(import.meta.dir, { + agent_id: "test-agent-123", + scan_depth: "0", + }); + }; + expect(t1).toThrow("Scan depth must be between 1 and 5"); + + const t2 = async () => { + await runTerraformApply(import.meta.dir, { + agent_id: "test-agent-123", + scan_depth: "6", + }); + }; + expect(t2).toThrow("Scan depth must be between 1 and 5"); + }); + + it("applies successfully with default values", async () => { + await runTerraformApply(import.meta.dir, { + agent_id: "test-agent-123", + }); + }); + + it("applies successfully with all project types enabled", async () => { + await runTerraformApply(import.meta.dir, { + agent_id: "test-agent-123", + enable_npm: "true", + enable_rails: "true", + enable_django: "true", + enable_flask: "true", + enable_spring_boot: "true", + enable_go: "true", + enable_php: "true", + enable_rust: "true", + enable_dotnet: "true", + enable_devcontainer: "true", + }); + }); + + it("applies successfully with all project types disabled", async () => { + await runTerraformApply(import.meta.dir, { + agent_id: "test-agent-123", + enable_npm: "false", + enable_rails: "false", + enable_django: "false", + enable_flask: "false", + enable_spring_boot: "false", + enable_go: "false", + enable_php: "false", + enable_rust: "false", + enable_dotnet: "false", + enable_devcontainer: "false", + }); + }); + + it("applies successfully with custom configuration", async () => { + await runTerraformApply(import.meta.dir, { + agent_id: "test-agent-123", + workspace_directory: "/custom/workspace", + scan_depth: "3", + startup_delay: "5", + log_path: "/var/log/custom-dev-servers.log", + display_name: "Custom Dev Server Startup", + }); + }); + + it("validates scan_depth boundary values", async () => { + // Test valid boundary values + await runTerraformApply(import.meta.dir, { + agent_id: "test-agent-123", + scan_depth: "1", + }); + + await runTerraformApply(import.meta.dir, { + agent_id: "test-agent-123", + scan_depth: "5", + }); + }); + + it("applies with selective project type configuration", async () => { + await runTerraformApply(import.meta.dir, { + agent_id: "test-agent-123", + enable_npm: "true", + enable_django: "true", + enable_go: "true", + enable_rails: "false", + enable_flask: "false", + enable_spring_boot: "false", + enable_php: "false", + enable_rust: "false", + enable_dotnet: "false", + }); + }); +}); diff --git a/registry/mavrickrishi/modules/auto-start-dev-server/main.tf b/registry/mavrickrishi/modules/auto-start-dev-server/main.tf new file mode 100644 index 00000000..e4227ee7 --- /dev/null +++ b/registry/mavrickrishi/modules/auto-start-dev-server/main.tf @@ -0,0 +1,195 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + coder = { + source = "coder/coder" + version = ">= 2.5" + } + } +} + +variable "agent_id" { + type = string + description = "The ID of a Coder agent." +} + +variable "workspace_directory" { + type = string + description = "The directory to scan for development projects." + default = "$HOME" +} + +variable "project_detection" { + type = bool + description = "Enable automatic project detection for all supported types. When true, all project types are detected unless individually disabled. When false, only explicitly enabled project types are detected." + default = true +} + +variable "enable_npm" { + type = bool + description = "Enable auto-detection and startup of npm projects." + default = null +} + +variable "enable_rails" { + type = bool + description = "Enable auto-detection and startup of Rails projects." + default = null +} + +variable "enable_django" { + type = bool + description = "Enable auto-detection and startup of Django projects." + default = null +} + +variable "enable_flask" { + type = bool + description = "Enable auto-detection and startup of Flask projects." + default = null +} + +variable "enable_spring_boot" { + type = bool + description = "Enable auto-detection and startup of Spring Boot projects." + default = null +} + +variable "enable_go" { + type = bool + description = "Enable auto-detection and startup of Go projects." + default = null +} + +variable "enable_php" { + type = bool + description = "Enable auto-detection and startup of PHP projects." + default = null +} + +variable "enable_rust" { + type = bool + description = "Enable auto-detection and startup of Rust projects." + default = null +} + +variable "enable_dotnet" { + type = bool + description = "Enable auto-detection and startup of .NET projects." + default = null +} + +variable "enable_devcontainer" { + type = bool + description = "Enable integration with devcontainer.json configuration." + default = null +} + +variable "log_path" { + type = string + description = "The path to log development server output to." + default = "/tmp/dev-servers.log" +} + +variable "scan_depth" { + type = number + description = "Maximum directory depth to scan for projects (1-5)." + default = 2 + validation { + condition = var.scan_depth >= 1 && var.scan_depth <= 5 + error_message = "Scan depth must be between 1 and 5." + } +} + +variable "startup_delay" { + type = number + description = "Delay in seconds before starting dev servers (allows other setup to complete)." + default = 10 +} + +variable "display_name" { + type = string + description = "Display name for the auto-start dev server script." + default = "Auto-Start Dev Servers" +} + +variable "enable_preview_app" { + type = bool + description = "Enable automatic creation of a preview app for the first detected project." + default = true +} + +# Read the detected port from the file written by the script +locals { + detected_port = var.enable_preview_app ? try(tonumber(trimspace(file("/tmp/detected-port.txt"))), 3000) : 3000 + # Attempt to read project information for better preview naming + detected_projects = try(jsondecode(file("/tmp/detected-projects.json")), []) + preview_project = length(local.detected_projects) > 0 ? local.detected_projects[0] : null +} + +resource "coder_script" "auto_start_dev_server" { + agent_id = var.agent_id + display_name = var.display_name + icon = "/icon/server.svg" + script = templatefile("${path.module}/run.sh", { + WORKSPACE_DIR = var.workspace_directory + ENABLE_NPM = coalesce(var.enable_npm, var.project_detection) + ENABLE_RAILS = coalesce(var.enable_rails, var.project_detection) + ENABLE_DJANGO = coalesce(var.enable_django, var.project_detection) + ENABLE_FLASK = coalesce(var.enable_flask, var.project_detection) + ENABLE_SPRING_BOOT = coalesce(var.enable_spring_boot, var.project_detection) + ENABLE_GO = coalesce(var.enable_go, var.project_detection) + ENABLE_PHP = coalesce(var.enable_php, var.project_detection) + ENABLE_RUST = coalesce(var.enable_rust, var.project_detection) + ENABLE_DOTNET = coalesce(var.enable_dotnet, var.project_detection) + ENABLE_DEVCONTAINER = coalesce(var.enable_devcontainer, var.project_detection) + LOG_PATH = var.log_path + SCAN_DEPTH = var.scan_depth + STARTUP_DELAY = var.startup_delay + }) + run_on_start = true +} + +# Create preview app for first detected project +resource "coder_app" "preview" { + count = var.enable_preview_app ? 1 : 0 + agent_id = var.agent_id + slug = "dev-preview" + display_name = "Live Preview" + url = "http://localhost:${local.detected_port}" + icon = "/icon/globe.svg" + subdomain = true + share = "owner" +} + +output "log_path" { + value = var.log_path + description = "Path to the log file for dev server output" +} + +# Example output values for common port mappings +output "common_ports" { + value = { + nodejs = 3000 + rails = 3000 + django = 8000 + flask = 5000 + spring = 8080 + go = 8080 + php = 8080 + rust = 8000 + dotnet = 5000 + } + description = "Common default ports for different project types" +} + +output "preview_url" { + value = var.enable_preview_app ? try(coder_app.preview[0].url, null) : null + description = "URL of the live preview app (if enabled)" +} + +output "detected_port" { + value = local.detected_port + description = "Port of the first detected development server" +} diff --git a/registry/mavrickrishi/modules/auto-start-dev-server/run.sh b/registry/mavrickrishi/modules/auto-start-dev-server/run.sh new file mode 100755 index 00000000..d0386e74 --- /dev/null +++ b/registry/mavrickrishi/modules/auto-start-dev-server/run.sh @@ -0,0 +1,468 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Color codes for output +BOLD='\033[0;1m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +RED='\033[0;31m' +RESET='\033[0m' + +echo -e "$${BOLD}πŸš€ Auto-Start Development Servers$${RESET}" +echo "Workspace Directory: ${WORKSPACE_DIR}" +echo "Log Path: ${LOG_PATH}" +echo "Scan Depth: ${SCAN_DEPTH}" + +# Wait for startup delay to allow other setup to complete +if [ "${STARTUP_DELAY}" -gt 0 ]; then + echo -e "$${YELLOW}⏳ Waiting ${STARTUP_DELAY} seconds for system initialization...$${RESET}" + sleep "${STARTUP_DELAY}" +fi + +# Initialize log file +echo "=== Auto-Start Dev Servers Log ===" > "${LOG_PATH}" +echo "Started at: $(date)" >> "${LOG_PATH}" + +# Initialize detected projects JSON file +DETECTED_PROJECTS_FILE="/tmp/detected-projects.json" +echo '[]' > "$DETECTED_PROJECTS_FILE" + +# Initialize detected port file for preview app +DETECTED_PORT_FILE="/tmp/detected-port.txt" +FIRST_PORT_DETECTED=false +FRONTEND_PROJECT_DETECTED=false + +# Function to log messages +log_message() { + echo -e "$1" + echo "$1" >> "${LOG_PATH}" +} + +# Function to determine if a project is likely a frontend project +is_frontend_project() { + local project_dir="$1" + local project_type="$2" + + # Check for common frontend indicators + if [ "$project_type" = "nodejs" ]; then + # Check package.json for frontend dependencies + if [ -f "$project_dir/package.json" ] && command -v jq &> /dev/null; then + # Check for common frontend frameworks + local has_react=$(jq '.dependencies.react // .devDependencies.react // empty' "$project_dir/package.json") + local has_vue=$(jq '.dependencies.vue // .devDependencies.vue // empty' "$project_dir/package.json") + local has_angular=$(jq '.dependencies["@angular/core"] // .devDependencies["@angular/core"] // empty' "$project_dir/package.json") + local has_next=$(jq '.dependencies.next // .devDependencies.next // empty' "$project_dir/package.json") + local has_nuxt=$(jq '.dependencies.nuxt // .devDependencies.nuxt // empty' "$project_dir/package.json") + local has_svelte=$(jq '.dependencies.svelte // .devDependencies.svelte // empty' "$project_dir/package.json") + local has_vite=$(jq '.dependencies.vite // .devDependencies.vite // empty' "$project_dir/package.json") + + if [ -n "$has_react" ] || [ -n "$has_vue" ] || [ -n "$has_angular" ] \ + || [ -n "$has_next" ] || [ -n "$has_nuxt" ] || [ -n "$has_svelte" ] \ + || [ -n "$has_vite" ]; then + return 0 # It's a frontend project + fi + fi + + # Check for common frontend directory structures + if [ -d "$project_dir/src/components" ] || [ -d "$project_dir/components" ] \ + || [ -d "$project_dir/pages" ] || [ -d "$project_dir/views" ] \ + || [ -f "$project_dir/index.html" ] || [ -f "$project_dir/public/index.html" ]; then + return 0 # It's likely a frontend project + fi + fi + + # Rails projects with webpack/webpacker are frontend-enabled + if [ "$project_type" = "rails" ]; then + if [ -f "$project_dir/config/webpacker.yml" ] || [ -f "$project_dir/webpack.config.js" ]; then + return 0 + fi + fi + + # Django projects with static/templates are frontend-enabled + if [ "$project_type" = "django" ]; then + if [ -d "$project_dir/static" ] || [ -d "$project_dir/templates" ]; then + return 0 + fi + fi + + return 1 # Not a frontend project +} + +# Function to add detected project to JSON +add_detected_project() { + local project_dir="$1" + local project_type="$2" + local port="$3" + local command="$4" + + # Check if this is a frontend project + local is_frontend=false + if is_frontend_project "$project_dir" "$project_type"; then + is_frontend=true + log_message "$${BLUE}🎨 Detected frontend project at $project_dir$${RESET}" + fi + + # Prioritize frontend projects for the preview app + # Set port if: 1) No port set yet, OR 2) This is frontend and no frontend detected yet + if [ "$FIRST_PORT_DETECTED" = false ] || ([ "$is_frontend" = true ] && [ "$FRONTEND_PROJECT_DETECTED" = false ]); then + echo "$port" > "$DETECTED_PORT_FILE" + FIRST_PORT_DETECTED=true + if [ "$is_frontend" = true ]; then + FRONTEND_PROJECT_DETECTED=true + log_message "$${BLUE}🎯 Frontend project detected - Preview app will be available on port $port$${RESET}" + else + log_message "$${BLUE}🎯 Project detected - Preview app will be available on port $port$${RESET}" + fi + fi + + # Create JSON entry for this project + local project_json=$(jq -n \ + --arg dir "$project_dir" \ + --arg type "$project_type" \ + --arg port "$port" \ + --arg cmd "$command" \ + --arg frontend "$is_frontend" \ + '{"directory": $dir, "type": $type, "port": $port, "command": $cmd, "is_frontend": ($frontend == "true")}') + + # Append to the detected projects file + jq ". += [$project_json]" "$DETECTED_PROJECTS_FILE" > "$DETECTED_PROJECTS_FILE.tmp" \ + && mv "$DETECTED_PROJECTS_FILE.tmp" "$DETECTED_PROJECTS_FILE" +} + +# Function to detect and start npm/yarn projects +detect_npm_projects() { + if [ "${ENABLE_NPM}" != "true" ]; then + return + fi + + log_message "$${BLUE}πŸ” Scanning for Node.js/npm projects...$${RESET}" + + # Use find with maxdepth to respect scan depth + while IFS= read -r -d '' package_json; do + project_dir=$(dirname "$package_json") + log_message "$${GREEN}πŸ“¦ Found Node.js project: $project_dir$${RESET}" + + cd "$project_dir" + + # Check package.json for start script + if [ -f "package.json" ] && command -v jq &> /dev/null; then + start_script=$(jq -r '.scripts.start // empty' package.json) + dev_script=$(jq -r '.scripts.dev // empty' package.json) + serve_script=$(jq -r '.scripts.serve // empty' package.json) + + # Determine port (check for common port configurations) + local project_port=3000 + if [ -n "$dev_script" ] && echo "$dev_script" | grep -q "\-\-port"; then + project_port=$(echo "$dev_script" | grep -oE "\-\-port[[:space:]]+[0-9]+" | grep -oE "[0-9]+$" || echo "3000") + fi + + # Use yarn if yarn.lock exists + local pkg_manager="npm" + local cmd_prefix="" + if [ -f "yarn.lock" ] && command -v yarn &> /dev/null; then + pkg_manager="yarn" + cmd_prefix="" + else + cmd_prefix="run " + fi + + # Prioritize scripts: 'dev' > 'serve' > 'start' for development environments + if [ -n "$dev_script" ]; then + if [ "$pkg_manager" = "yarn" ]; then + log_message "$${GREEN}🟒 Starting project with 'yarn dev' in $project_dir$${RESET}" + nohup yarn dev >> "${LOG_PATH}" 2>&1 & + add_detected_project "$project_dir" "nodejs" "$project_port" "yarn dev" + else + log_message "$${GREEN}🟒 Starting project with 'npm run dev' in $project_dir$${RESET}" + nohup npm run dev >> "${LOG_PATH}" 2>&1 & + add_detected_project "$project_dir" "nodejs" "$project_port" "npm run dev" + fi + elif [ -n "$serve_script" ]; then + if [ "$pkg_manager" = "yarn" ]; then + log_message "$${GREEN}🟒 Starting project with 'yarn serve' in $project_dir$${RESET}" + nohup yarn serve >> "${LOG_PATH}" 2>&1 & + add_detected_project "$project_dir" "nodejs" "$project_port" "yarn serve" + else + log_message "$${GREEN}🟒 Starting project with 'npm run serve' in $project_dir$${RESET}" + nohup npm run serve >> "${LOG_PATH}" 2>&1 & + add_detected_project "$project_dir" "nodejs" "$project_port" "npm run serve" + fi + elif [ -n "$start_script" ]; then + if [ "$pkg_manager" = "yarn" ]; then + log_message "$${GREEN}🟒 Starting project with 'yarn start' in $project_dir$${RESET}" + nohup yarn start >> "${LOG_PATH}" 2>&1 & + add_detected_project "$project_dir" "nodejs" "$project_port" "yarn start" + else + log_message "$${GREEN}🟒 Starting project with 'npm start' in $project_dir$${RESET}" + nohup npm start >> "${LOG_PATH}" 2>&1 & + add_detected_project "$project_dir" "nodejs" "$project_port" "npm start" + fi + fi + fi + + done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "package.json" -type f -print0) +} + +# Function to detect and start Rails projects +detect_rails_projects() { + if [ "${ENABLE_RAILS}" != "true" ]; then + return + fi + + log_message "$${BLUE}πŸ” Scanning for Ruby on Rails projects...$${RESET}" + + while IFS= read -r -d '' gemfile; do + project_dir=$(dirname "$gemfile") + log_message "$${GREEN}πŸ’Ž Found Rails project: $project_dir$${RESET}" + + cd "$project_dir" + + # Check if it's actually a Rails project + if grep -q "gem ['\"]rails['\"]" Gemfile 2> /dev/null; then + log_message "$${GREEN}🟒 Starting Rails server in $project_dir$${RESET}" + nohup bundle exec rails server >> "${LOG_PATH}" 2>&1 & + add_detected_project "$project_dir" "rails" "3000" "bundle exec rails server" + fi + + done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "Gemfile" -type f -print0) +} + +# Function to detect and start Django projects +detect_django_projects() { + if [ "${ENABLE_DJANGO}" != "true" ]; then + return + fi + + log_message "$${BLUE}πŸ” Scanning for Django projects...$${RESET}" + + while IFS= read -r -d '' manage_py; do + project_dir=$(dirname "$manage_py") + log_message "$${GREEN}🐍 Found Django project: $project_dir$${RESET}" + + cd "$project_dir" + log_message "$${GREEN}🟒 Starting Django development server in $project_dir$${RESET}" + nohup python manage.py runserver 0.0.0.0:8000 >> "${LOG_PATH}" 2>&1 & + add_detected_project "$project_dir" "django" "8000" "python manage.py runserver" + + done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "manage.py" -type f -print0) +} + +# Function to detect and start Flask projects +detect_flask_projects() { + if [ "${ENABLE_FLASK}" != "true" ]; then + return + fi + + log_message "$${BLUE}πŸ” Scanning for Flask projects...$${RESET}" + + while IFS= read -r -d '' requirements_txt; do + project_dir=$(dirname "$requirements_txt") + + # Check if Flask is in requirements + if grep -q -i "flask" "$requirements_txt" 2> /dev/null; then + log_message "$${GREEN}🌢️ Found Flask project: $project_dir$${RESET}" + + cd "$project_dir" + + # Look for common Flask app files + for app_file in app.py main.py run.py; do + if [ -f "$app_file" ]; then + log_message "$${GREEN}🟒 Starting Flask application ($app_file) in $project_dir$${RESET}" + export FLASK_ENV=development + nohup python "$app_file" >> "${LOG_PATH}" 2>&1 & + add_detected_project "$project_dir" "flask" "5000" "python $app_file" + break + fi + done + fi + + done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "requirements.txt" -type f -print0) +} + +# Function to detect and start Spring Boot projects +detect_spring_boot_projects() { + if [ "${ENABLE_SPRING_BOOT}" != "true" ]; then + return + fi + + log_message "$${BLUE}πŸ” Scanning for Spring Boot projects...$${RESET}" + + # Maven projects + while IFS= read -r -d '' pom_xml; do + project_dir=$(dirname "$pom_xml") + + # Check if it's a Spring Boot project + if grep -q "spring-boot" "$pom_xml" 2> /dev/null; then + log_message "$${GREEN}πŸƒ Found Spring Boot Maven project: $project_dir$${RESET}" + + cd "$project_dir" + if command -v ./mvnw &> /dev/null; then + log_message "$${GREEN}🟒 Starting Spring Boot application with Maven wrapper in $project_dir$${RESET}" + nohup ./mvnw spring-boot:run >> "${LOG_PATH}" 2>&1 & + add_detected_project "$project_dir" "spring-boot" "8080" "./mvnw spring-boot:run" + elif command -v mvn &> /dev/null; then + log_message "$${GREEN}🟒 Starting Spring Boot application with Maven in $project_dir$${RESET}" + nohup mvn spring-boot:run >> "${LOG_PATH}" 2>&1 & + add_detected_project "$project_dir" "spring-boot" "8080" "mvn spring-boot:run" + fi + fi + + done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "pom.xml" -type f -print0) + + # Gradle projects + while IFS= read -r -d '' build_gradle; do + project_dir=$(dirname "$build_gradle") + + # Check if it's a Spring Boot project + if grep -q "spring-boot" "$build_gradle" 2> /dev/null; then + log_message "$${GREEN}πŸƒ Found Spring Boot Gradle project: $project_dir$${RESET}" + + cd "$project_dir" + if command -v ./gradlew &> /dev/null; then + log_message "$${GREEN}🟒 Starting Spring Boot application with Gradle wrapper in $project_dir$${RESET}" + nohup ./gradlew bootRun >> "${LOG_PATH}" 2>&1 & + add_detected_project "$project_dir" "spring-boot" "8080" "./gradlew bootRun" + elif command -v gradle &> /dev/null; then + log_message "$${GREEN}🟒 Starting Spring Boot application with Gradle in $project_dir$${RESET}" + nohup gradle bootRun >> "${LOG_PATH}" 2>&1 & + add_detected_project "$project_dir" "spring-boot" "8080" "gradle bootRun" + fi + fi + + done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "build.gradle" -type f -print0) +} + +# Function to detect and start Go projects +detect_go_projects() { + if [ "${ENABLE_GO}" != "true" ]; then + return + fi + + log_message "$${BLUE}πŸ” Scanning for Go projects...$${RESET}" + + while IFS= read -r -d '' go_mod; do + project_dir=$(dirname "$go_mod") + log_message "$${GREEN}🐹 Found Go project: $project_dir$${RESET}" + + cd "$project_dir" + + # Look for main.go or check if there's a main function + if [ -f "main.go" ]; then + log_message "$${GREEN}🟒 Starting Go application in $project_dir$${RESET}" + nohup go run main.go >> "${LOG_PATH}" 2>&1 & + add_detected_project "$project_dir" "go" "8080" "go run main.go" + elif [ -f "cmd/main.go" ]; then + log_message "$${GREEN}🟒 Starting Go application (cmd/main.go) in $project_dir$${RESET}" + nohup go run cmd/main.go >> "${LOG_PATH}" 2>&1 & + add_detected_project "$project_dir" "go" "8080" "go run cmd/main.go" + fi + + done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "go.mod" -type f -print0) +} + +# Function to detect and start PHP projects +detect_php_projects() { + if [ "${ENABLE_PHP}" != "true" ]; then + return + fi + + log_message "$${BLUE}πŸ” Scanning for PHP projects...$${RESET}" + + while IFS= read -r -d '' composer_json; do + project_dir=$(dirname "$composer_json") + log_message "$${GREEN}🐘 Found PHP project: $project_dir$${RESET}" + + cd "$project_dir" + + # Look for common PHP entry points + for entry_file in index.php public/index.php; do + if [ -f "$entry_file" ]; then + log_message "$${GREEN}🟒 Starting PHP development server in $project_dir$${RESET}" + nohup php -S 0.0.0.0:8080 -t "$(dirname "$entry_file")" >> "${LOG_PATH}" 2>&1 & + add_detected_project "$project_dir" "php" "8080" "php -S 0.0.0.0:8080" + break + fi + done + + done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "composer.json" -type f -print0) +} + +# Function to detect and start Rust projects +detect_rust_projects() { + if [ "${ENABLE_RUST}" != "true" ]; then + return + fi + + log_message "$${BLUE}πŸ” Scanning for Rust projects...$${RESET}" + + while IFS= read -r -d '' cargo_toml; do + project_dir=$(dirname "$cargo_toml") + log_message "$${GREEN}πŸ¦€ Found Rust project: $project_dir$${RESET}" + + cd "$project_dir" + + # Check if it's a binary project (has [[bin]] or default main.rs) + if grep -q "\[\[bin\]\]" Cargo.toml 2> /dev/null || [ -f "src/main.rs" ]; then + log_message "$${GREEN}🟒 Starting Rust application in $project_dir$${RESET}" + nohup cargo run >> "${LOG_PATH}" 2>&1 & + add_detected_project "$project_dir" "rust" "8000" "cargo run" + fi + + done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "Cargo.toml" -type f -print0) +} + +# Function to detect and start .NET projects +detect_dotnet_projects() { + if [ "${ENABLE_DOTNET}" != "true" ]; then + return + fi + + log_message "$${BLUE}πŸ” Scanning for .NET projects...$${RESET}" + + while IFS= read -r -d '' csproj; do + project_dir=$(dirname "$csproj") + log_message "$${GREEN}πŸ”· Found .NET project: $project_dir$${RESET}" + + cd "$project_dir" + log_message "$${GREEN}🟒 Starting .NET application in $project_dir$${RESET}" + nohup dotnet run >> "${LOG_PATH}" 2>&1 & + add_detected_project "$project_dir" "dotnet" "5000" "dotnet run" + + done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "*.csproj" -type f -print0) +} + +log_message "Starting auto-detection of development projects..." + +# Expand workspace directory if it contains variables +WORKSPACE_DIR=$(eval echo "${WORKSPACE_DIR}") + +# Check if workspace directory exists +if [ ! -d "$WORKSPACE_DIR" ]; then + log_message "$${RED}❌ Workspace directory does not exist: $WORKSPACE_DIR$${RESET}" + exit 1 +fi + +cd "$WORKSPACE_DIR" + +# Run all detection functions +detect_npm_projects +detect_rails_projects +detect_django_projects +detect_flask_projects +detect_spring_boot_projects +detect_go_projects +detect_php_projects +detect_rust_projects +detect_dotnet_projects + +log_message "$${GREEN}βœ… Auto-start scan completed!$${RESET}" +log_message "$${YELLOW}πŸ’‘ Check running processes with 'ps aux | grep -E \"(npm|rails|python|java|go|php|cargo|dotnet)\"'$${RESET}" +log_message "$${YELLOW}πŸ’‘ View logs: tail -f ${LOG_PATH}$${RESET}" + +# Set default port if no projects were detected +if [ "$FIRST_PORT_DETECTED" = false ]; then + echo "3000" > "$DETECTED_PORT_FILE" + log_message "$${YELLOW}⚠️ No projects detected - Preview app will default to port 3000$${RESET}" +fi From 60372ff7972491101b7caf9acf0b185f53ec2d26 Mon Sep 17 00:00:00 2001 From: Jullian Pepito Date: Tue, 7 Oct 2025 13:35:02 -0700 Subject: [PATCH 13/27] fix(git-clone): Update README.md (#448) Changes `coder_git_auth` to `coder_external_auth` in README ## Description ## Type of Change - [ ] New module - [ ] Bug fix - [ ] Feature/enhancement - [X] Documentation - [ ] Other ## Module Information **Path:** `registry/coder/modules/git-clone` **New version:** `v1.1.2` **Breaking change:** [ ] Yes [X] No ## Testing & Validation - [ ] Tests pass (`bun test`) - [ ] Code formatted (`bun run fmt`) - [ ] Changes tested locally ## Related Issues --------- Co-authored-by: Jullian Pepito Co-authored-by: DevCats --- registry/coder/modules/git-clone/README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/registry/coder/modules/git-clone/README.md b/registry/coder/modules/git-clone/README.md index c12eb590..cf8f6d13 100644 --- a/registry/coder/modules/git-clone/README.md +++ b/registry/coder/modules/git-clone/README.md @@ -14,7 +14,7 @@ This module allows you to automatically clone a repository by URL and skip if it module "git-clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "1.1.1" + version = "1.1.2" agent_id = coder_agent.example.id url = "https://github.com/coder/coder" } @@ -28,7 +28,7 @@ module "git-clone" { module "git-clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "1.1.1" + version = "1.1.2" agent_id = coder_agent.example.id url = "https://github.com/coder/coder" base_dir = "~/projects/coder" @@ -43,12 +43,12 @@ To use with [Git Authentication](https://coder.com/docs/v2/latest/admin/git-prov module "git-clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "1.1.1" + version = "1.1.2" agent_id = coder_agent.example.id url = "https://github.com/coder/coder" } -data "coder_git_auth" "github" { +data "coder_external_auth" "github" { id = "github" } ``` @@ -69,7 +69,7 @@ data "coder_parameter" "git_repo" { module "git_clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "1.1.1" + version = "1.1.2" agent_id = coder_agent.example.id url = data.coder_parameter.git_repo.value } @@ -103,7 +103,7 @@ Configuring `git-clone` for a self-hosted GitHub Enterprise Server running at `g module "git-clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "1.1.1" + version = "1.1.2" agent_id = coder_agent.example.id url = "https://github.example.com/coder/coder/tree/feat/example" git_providers = { @@ -122,7 +122,7 @@ To GitLab clone with a specific branch like `feat/example` module "git-clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "1.1.1" + version = "1.1.2" agent_id = coder_agent.example.id url = "https://gitlab.com/coder/coder/-/tree/feat/example" } @@ -134,7 +134,7 @@ Configuring `git-clone` for a self-hosted GitLab running at `gitlab.example.com` module "git-clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "1.1.1" + version = "1.1.2" agent_id = coder_agent.example.id url = "https://gitlab.example.com/coder/coder/-/tree/feat/example" git_providers = { @@ -155,7 +155,7 @@ For example, to clone the `feat/example` branch: module "git-clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "1.1.1" + version = "1.1.2" agent_id = coder_agent.example.id url = "https://github.com/coder/coder" branch_name = "feat/example" @@ -173,7 +173,7 @@ For example, this will clone into the `~/projects/coder/coder-dev` folder: module "git-clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "1.1.1" + version = "1.1.2" agent_id = coder_agent.example.id url = "https://github.com/coder/coder" folder_name = "coder-dev" From 76c1299968ac4d06acc485db10da636b02cb03a3 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Wed, 8 Oct 2025 11:39:54 +0500 Subject: [PATCH 14/27] docs: upgrade alert style to a GFM style tip for JetBrains Gateway (#468) --- registry/coder/modules/jetbrains-gateway/README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/registry/coder/modules/jetbrains-gateway/README.md b/registry/coder/modules/jetbrains-gateway/README.md index 8cfe3b5e..0c5c8ff8 100644 --- a/registry/coder/modules/jetbrains-gateway/README.md +++ b/registry/coder/modules/jetbrains-gateway/README.md @@ -10,6 +10,7 @@ tags: [ide, jetbrains, parameter, gateway] This module adds a JetBrains Gateway Button to open any workspace with a single click. +> [!TIP] > 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. @@ -19,7 +20,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.4" + version = "1.2.5" agent_id = coder_agent.example.id folder = "/home/coder/example" jetbrains_ides = ["CL", "GO", "IU", "PY", "WS"] @@ -37,7 +38,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.4" + version = "1.2.5" agent_id = coder_agent.example.id folder = "/home/coder/example" jetbrains_ides = ["GO", "WS"] @@ -51,7 +52,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.4" + version = "1.2.5" agent_id = coder_agent.example.id folder = "/home/coder/example" jetbrains_ides = ["IU", "PY"] @@ -66,7 +67,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.4" + version = "1.2.5" agent_id = coder_agent.example.id folder = "/home/coder/example" jetbrains_ides = ["IU", "PY"] @@ -91,7 +92,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.4" + version = "1.2.5" agent_id = coder_agent.example.id folder = "/home/coder/example" jetbrains_ides = ["GO", "WS"] @@ -109,7 +110,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.4" + version = "1.2.5" agent_id = coder_agent.example.id folder = "/home/coder/example" jetbrains_ides = ["GO", "WS"] From 8acda84dd7a88b14052ffb626f596d46fd7dfa77 Mon Sep 17 00:00:00 2001 From: DevCats Date: Wed, 8 Oct 2025 13:20:45 -0500 Subject: [PATCH 15/27] chore: update icons for auto-start-dev-server module (#471) ## Description Adds icons for module, and update all refrences. PR for Site Icon's Addition: https://github.com/coder/coder/pull/20219 ## Type of Change - [ ] New module - [X] Bug fix - [ ] Feature/enhancement - [ ] Documentation - [ ] Other ## Module Information **Path:** `registry/mavrickrishi/modules/auto-start-dev-server` **New version:** `v1.0.1` **Breaking change:** [ ] Yes [ ] No ## Testing & Validation - [X] Tests pass (`bun test`) - [X] Code formatted (`bun run fmt`) - [X] Changes tested locally --- .icons/auto-dev-server.svg | 4 ++ .../modules/auto-start-dev-server/README.md | 48 +++++++++---------- .../modules/auto-start-dev-server/main.tf | 4 +- 3 files changed, 30 insertions(+), 26 deletions(-) create mode 100644 .icons/auto-dev-server.svg diff --git a/.icons/auto-dev-server.svg b/.icons/auto-dev-server.svg new file mode 100644 index 00000000..f043b56d --- /dev/null +++ b/.icons/auto-dev-server.svg @@ -0,0 +1,4 @@ + + + + diff --git a/registry/mavrickrishi/modules/auto-start-dev-server/README.md b/registry/mavrickrishi/modules/auto-start-dev-server/README.md index fc6b48e4..432b453f 100644 --- a/registry/mavrickrishi/modules/auto-start-dev-server/README.md +++ b/registry/mavrickrishi/modules/auto-start-dev-server/README.md @@ -1,7 +1,7 @@ --- -display_name: Auto-Start Development Servers +display_name: Auto-Start Dev Servers description: Automatically detect and start development servers for various project types -icon: ../../../../.icons/server.svg +icon: ../../../../.icons/auto-dev-server.svg verified: false tags: [development, automation, servers] --- @@ -13,7 +13,7 @@ Automatically detect and start development servers for various project types whe ```tf module "auto_start_dev_servers" { source = "registry.coder.com/mavrickrishi/auto-start-dev-server/coder" - version = "1.0.0" + version = "1.0.1" agent_id = coder_agent.main.id } ``` @@ -48,20 +48,20 @@ module "auto_start_dev_servers" { ### Basic Usage -```hcl +```tf module "auto_start" { source = "./modules/auto-start-dev-server" - version = "1.0.0" + version = "1.0.1" agent_id = coder_agent.main.id } ``` ### Advanced Usage -```hcl +```tf module "auto_start_dev_servers" { source = "./modules/auto-start-dev-server" - version = "1.0.0" + version = "1.0.1" agent_id = coder_agent.main.id # Optional: Configure which project types to detect @@ -70,10 +70,10 @@ module "auto_start_dev_servers" { enable_django = true enable_flask = true enable_spring_boot = true - enable_go = true - enable_php = true - enable_rust = true - enable_dotnet = true + enable_go = true + enable_php = true + enable_rust = true + enable_dotnet = true # Optional: Enable devcontainer.json integration enable_devcontainer = true @@ -97,10 +97,10 @@ module "auto_start_dev_servers" { ### Disable Preview App -```hcl +```tf module "auto_start" { source = "./modules/auto-start-dev-server" - version = "1.0.0" + version = "1.0.1" agent_id = coder_agent.main.id # Disable automatic preview app creation @@ -110,10 +110,10 @@ module "auto_start" { ### Selective Project Types -```hcl +```tf module "auto_start" { source = "./modules/auto-start-dev-server" - version = "1.0.0" + version = "1.0.1" agent_id = coder_agent.main.id # Only enable web development projects @@ -124,25 +124,25 @@ module "auto_start" { # Disable other project types enable_spring_boot = false - enable_go = false - enable_php = false - enable_rust = false - enable_dotnet = false + enable_go = false + enable_php = false + enable_rust = false + enable_dotnet = false } ``` ### Deep Workspace Scanning -```hcl +```tf module "auto_start" { source = "./modules/auto-start-dev-server" - version = "1.0.0" + version = "1.0.1" agent_id = coder_agent.main.id workspace_directory = "/workspaces" - scan_depth = 3 - startup_delay = 5 - log_path = "/var/log/dev-servers.log" + scan_depth = 3 + startup_delay = 5 + log_path = "/var/log/dev-servers.log" } ``` diff --git a/registry/mavrickrishi/modules/auto-start-dev-server/main.tf b/registry/mavrickrishi/modules/auto-start-dev-server/main.tf index e4227ee7..f5d13941 100644 --- a/registry/mavrickrishi/modules/auto-start-dev-server/main.tf +++ b/registry/mavrickrishi/modules/auto-start-dev-server/main.tf @@ -131,7 +131,7 @@ locals { resource "coder_script" "auto_start_dev_server" { agent_id = var.agent_id display_name = var.display_name - icon = "/icon/server.svg" + icon = "/icon/auto-dev-server.svg" script = templatefile("${path.module}/run.sh", { WORKSPACE_DIR = var.workspace_directory ENABLE_NPM = coalesce(var.enable_npm, var.project_detection) @@ -158,7 +158,7 @@ resource "coder_app" "preview" { slug = "dev-preview" display_name = "Live Preview" url = "http://localhost:${local.detected_port}" - icon = "/icon/globe.svg" + icon = "/icon/auto-dev-server.svg" subdomain = true share = "owner" } From ce039f64df0b2643c62de441f65f89df2fa0ecf0 Mon Sep 17 00:00:00 2001 From: Rishi Mondal Date: Thu, 9 Oct 2025 18:01:43 +0530 Subject: [PATCH 16/27] Add Sonatype Nexus repository integration module (#262) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Add Sonatype Nexus Repository Integration Module ## Summary Implements a Coder module for Sonatype Nexus Repository Manager integration that automatically configures Maven, npm, PyPI, and Docker registries for development workspaces. ## Demo Video & Screenshots https://github.com/user-attachments/assets/2c51f229-d34d-483b-a0e9-f4e0d79332c2 ![Nexus Repository Integration](https://github.com/user-attachments/assets/1a778a8f-0e48-40f2-ae0f-5b8d5d5ce849) ## Features - βœ… **Maven Support**: Automatic `settings.xml` configuration - βœ… **npm Support**: Automatic `.npmrc` configuration with scoped packages - βœ… **PyPI Support**: Automatic `pip.conf` configuration - βœ… **Docker Support**: Registry authentication setup - βœ… **Flexible Configuration**: Support for multiple repositories per package manager - βœ… **Secure Credentials**: API token and password support - βœ… **Username Options**: Configurable username field (username or email) ## Nexus Repository Manager Requirements ### Version Requirements **Yes, this module requires Nexus Repository Manager Pro version** for full functionality, though basic features work with the Community Edition (OSS). ### Supported Authentication Methods This module supports **4 authentication methods**: 1. **User Token Authentication** (Recommended - Pro only) - Enhanced security with two-part tokens - Ideal for CI/CD and automated environments - Requires `nx-usertoken-current` privilege 2. **API Token Authentication** (Pro only) - Single-use access tokens via REST API - Programmatic token generation and management 3. **Basic Authentication** (OSS & Pro) - Standard HTTP Basic Auth with username/password - Works with both OSS and Pro versions 4. **Base64 Encoded Credentials** (OSS & Pro) - Base64 encoded `username:password` format - Compatible with npm and other package managers ### Testing Instructions #### Prerequisites - Nexus Repository Manager instance (OSS or Pro) - Admin access to configure repositories - Test repositories for each package manager you want to test #### Setup Test Environment 1. **Create Test Repositories** in your Nexus instance: - Maven: `maven-public`, `maven-releases` - npm: `npm-public`, `@company:npm-private` - PyPI: `pypi-public`, `pypi-private` - Docker: `docker-public`, `docker-private` 2. **Configure Authentication**: - For Pro: Generate user tokens via UI (User menu β†’ User Token) - For OSS: Use username/password or base64 encoded credentials - Set up appropriate permissions for test repositories 3. **Test the Module**: ```hcl module "nexus" { source = "registry.coder.com/mavrickrishi/nexus/coder" version = "1.0.0" agent_id = coder_agent.main.id nexus_url = "https://your-nexus-instance.com" nexus_password = var.nexus_api_token # or password package_managers = { maven = ["maven-public", "maven-releases"] npm = ["npm-public", "@company:npm-private"] pypi = ["pypi-public", "pypi-private"] docker = ["docker-public", "docker-private"] } } ``` 4. **Verify Configuration**: - Check generated config files in workspace - Test package installation from configured repositories - Verify authentication works for each package manager #### EC2 Deployment Testing Tested by deploying on EC2 instance with: - Ubuntu 22.04 LTS - Nexus Repository Manager Pro - All package managers (Maven, npm, PyPI, Docker) - Both token and basic authentication methods ## Usage Example ```hcl module "nexus" { source = "registry.coder.com/mavrickrishi/nexus/coder" version = "1.0.0" agent_id = coder_agent.main.id nexus_url = "https://nexus.company.com" nexus_password = var.nexus_api_token package_managers = { maven = ["maven-public", "maven-releases"] npm = ["npm-public", "@company:npm-private"] pypi = ["pypi-public", "pypi-private"] docker = ["docker-public", "docker-private"] } } ``` ## Testing - βœ… 11 comprehensive tests covering all functionality - βœ… Variable validation tests - βœ… Package manager configuration tests - βœ… Error handling tests - βœ… All tests passing - βœ… EC2 deployment tested ## Files Added - `registry/mavrickrishi/modules/nexus/main.tf` - Main module configuration - `registry/mavrickrishi/modules/nexus/README.md` - Complete documentation - `registry/mavrickrishi/modules/nexus/main.test.ts` - Test suite ## Checklist - [x] Module follows existing patterns and conventions - [x] Comprehensive test coverage (11 tests) - [x] Complete documentation with examples - [x] Input validation and error handling - [x] Secure credential handling - [x] All tests passing - [x] Demo video included - [x] Screenshots added - [x] Testing instructions provided - [x] Authentication methods documented - [x] EC2 deployment tested Closes #202 /claim #202 --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Atif Ali Co-authored-by: DevCats Co-authored-by: DevCats --- .icons/nexus-repository.svg | 5 + registry/mavrickrishi/README.md | 1 + .../modules/nexus-repository/README.md | 149 ++++++++++++++++++ .../modules/nexus-repository/main.test.ts | 147 +++++++++++++++++ .../modules/nexus-repository/main.tf | 137 ++++++++++++++++ .../modules/nexus-repository/run.sh | 105 ++++++++++++ 6 files changed, 544 insertions(+) create mode 100644 .icons/nexus-repository.svg create mode 100644 registry/mavrickrishi/modules/nexus-repository/README.md create mode 100644 registry/mavrickrishi/modules/nexus-repository/main.test.ts create mode 100644 registry/mavrickrishi/modules/nexus-repository/main.tf create mode 100644 registry/mavrickrishi/modules/nexus-repository/run.sh diff --git a/.icons/nexus-repository.svg b/.icons/nexus-repository.svg new file mode 100644 index 00000000..ca135cd5 --- /dev/null +++ b/.icons/nexus-repository.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/registry/mavrickrishi/README.md b/registry/mavrickrishi/README.md index c2f53d98..eed6a5d9 100644 --- a/registry/mavrickrishi/README.md +++ b/registry/mavrickrishi/README.md @@ -19,4 +19,5 @@ participating in LFX CNCF programs, and helping the developer community grow. ## Modules - **aws-ami-snapshot**: Create and manage AMI snapshots for Coder workspaces with restore capabilities +- [nexus-repository](./modules/nexus-repository/) - Configure package managers to use Sonatype Nexus Repository - [auto-start-dev-server](modules/auto-start-dev-server/README.md) - Automatically detect and start development servers for various project types diff --git a/registry/mavrickrishi/modules/nexus-repository/README.md b/registry/mavrickrishi/modules/nexus-repository/README.md new file mode 100644 index 00000000..4694e0f8 --- /dev/null +++ b/registry/mavrickrishi/modules/nexus-repository/README.md @@ -0,0 +1,149 @@ +--- +display_name: Nexus Repository +description: Configure package managers to use Sonatype Nexus Repository for Maven, npm, PyPI, and Docker registries. +icon: ../../../../.icons/nexus-repository.svg +verified: true +tags: [integration, nexus-repository, maven, npm, pypi, docker] +--- + +# Sonatype Nexus Repository + +Configure package managers (Maven, npm, Go, PyPI, Docker) to use [Sonatype Nexus Repository](https://help.sonatype.com/en/sonatype-nexus-repository.html) with API token authentication. This module provides secure credential handling, multiple repository support per package manager, and flexible username configuration. + +```tf +module "nexus_repository" { + source = "registry.coder.com/mavrickrishi/nexus-repository/coder" + version = "1.0.0" + agent_id = coder_agent.example.id + nexus_url = "https://nexus.example.com" + nexus_password = var.nexus_api_token + package_managers = { + maven = ["maven-public", "maven-releases"] + npm = ["npm-public", "@scoped:npm-private"] + go = ["go-public", "go-private"] + pypi = ["pypi-public", "pypi-private"] + docker = ["docker-public", "docker-private"] + } +} +``` + +## Requirements + +- Nexus Repository Manager 3.x +- Valid API token or user credentials +- Package managers installed on the workspace (Maven, npm, Go, pip, Docker as needed) + +> [!NOTE] +> This module configures package managers but does not install them. You need to handle the installation of Maven, npm, Go, Python pip, and Docker yourself. + +## Examples + +### Configure Maven to use Nexus repositories + +```tf +module "nexus_repository" { + source = "registry.coder.com/mavrickrishi/nexus-repository/coder" + version = "1.0.0" + agent_id = coder_agent.example.id + nexus_url = "https://nexus.example.com" + nexus_password = var.nexus_api_token + package_managers = { + maven = ["maven-public", "maven-releases", "maven-snapshots"] + } +} +``` + +### Configure npm with scoped packages + +```tf +module "nexus_repository" { + source = "registry.coder.com/mavrickrishi/nexus-repository/coder" + version = "1.0.0" + agent_id = coder_agent.example.id + nexus_url = "https://nexus.example.com" + nexus_password = var.nexus_api_token + package_managers = { + npm = ["npm-public", "@mycompany:npm-private"] + } +} +``` + +### Configure Go module proxy + +```tf +module "nexus_repository" { + source = "registry.coder.com/mavrickrishi/nexus-repository/coder" + version = "1.0.0" + agent_id = coder_agent.example.id + nexus_url = "https://nexus.example.com" + nexus_password = var.nexus_api_token + package_managers = { + go = ["go-public", "go-private"] + } +} +``` + +### Configure Python PyPI repositories + +```tf +module "nexus_repository" { + source = "registry.coder.com/mavrickrishi/nexus-repository/coder" + version = "1.0.0" + agent_id = coder_agent.example.id + nexus_url = "https://nexus.example.com" + nexus_password = var.nexus_api_token + package_managers = { + pypi = ["pypi-public", "pypi-private"] + } +} +``` + +### Configure Docker registries + +```tf +module "nexus_repository" { + source = "registry.coder.com/mavrickrishi/nexus-repository/coder" + version = "1.0.0" + agent_id = coder_agent.example.id + nexus_url = "https://nexus.example.com" + nexus_password = var.nexus_api_token + package_managers = { + docker = ["docker-public", "docker-private"] + } +} +``` + +### Use custom username + +```tf +module "nexus_repository" { + source = "registry.coder.com/mavrickrishi/nexus-repository/coder" + version = "1.0.0" + agent_id = coder_agent.example.id + nexus_url = "https://nexus.example.com" + nexus_username = "custom-user" + nexus_password = var.nexus_api_token + package_managers = { + maven = ["maven-public"] + } +} +``` + +### Complete configuration for all package managers + +```tf +module "nexus_repository" { + source = "registry.coder.com/mavrickrishi/nexus-repository/coder" + version = "1.0.0" + agent_id = coder_agent.example.id + nexus_url = "https://nexus.example.com" + nexus_password = var.nexus_api_token + package_managers = { + maven = ["maven-public", "maven-releases"] + npm = ["npm-public", "@company:npm-private"] + go = ["go-public", "go-private"] + pypi = ["pypi-public", "pypi-private"] + docker = ["docker-public", "docker-private"] + } +} +``` diff --git a/registry/mavrickrishi/modules/nexus-repository/main.test.ts b/registry/mavrickrishi/modules/nexus-repository/main.test.ts new file mode 100644 index 00000000..1d5724bb --- /dev/null +++ b/registry/mavrickrishi/modules/nexus-repository/main.test.ts @@ -0,0 +1,147 @@ +import { describe, expect, it } from "bun:test"; +import { + executeScriptInContainer, + runTerraformApply, + runTerraformInit, + testRequiredVariables, +} from "~test"; + +describe("nexus-repository", async () => { + await runTerraformInit(import.meta.dir); + + testRequiredVariables(import.meta.dir, { + agent_id: "test-agent", + nexus_url: "https://nexus.example.com", + nexus_password: "test-password", + }); + + it("configures Maven settings", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "test-agent", + nexus_url: "https://nexus.example.com", + nexus_password: "test-token", + package_managers: JSON.stringify({ + maven: ["maven-public"], + }), + }); + + const output = await executeScriptInContainer(state, "ubuntu:20.04"); + expect(output.stdout.join("\n")).toContain("β˜• Configuring Maven..."); + expect(output.stdout.join("\n")).toContain("πŸ₯³ Configuration complete!"); + }); + + it("configures npm registry", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "test-agent", + nexus_url: "https://nexus.example.com", + nexus_password: "test-token", + package_managers: JSON.stringify({ + npm: ["npm-public"], + }), + }); + + const output = await executeScriptInContainer(state, "ubuntu:20.04"); + expect(output.stdout.join("\n")).toContain("πŸ“¦ Configuring npm..."); + expect(output.stdout.join("\n")).toContain("πŸ₯³ Configuration complete!"); + }); + + it("configures PyPI repository", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "test-agent", + nexus_url: "https://nexus.example.com", + nexus_password: "test-token", + package_managers: JSON.stringify({ + pypi: ["pypi-public"], + }), + }); + + const output = await executeScriptInContainer(state, "ubuntu:20.04"); + expect(output.stdout.join("\n")).toContain("🐍 Configuring pip..."); + expect(output.stdout.join("\n")).toContain("πŸ₯³ Configuration complete!"); + }); + + it("configures multiple package managers", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "test-agent", + nexus_url: "https://nexus.example.com", + nexus_password: "test-token", + package_managers: JSON.stringify({ + maven: ["maven-public"], + npm: ["npm-public"], + pypi: ["pypi-public"], + }), + }); + + const output = await executeScriptInContainer(state, "ubuntu:20.04"); + expect(output.stdout.join("\n")).toContain("β˜• Configuring Maven..."); + expect(output.stdout.join("\n")).toContain("πŸ“¦ Configuring npm..."); + expect(output.stdout.join("\n")).toContain("🐍 Configuring pip..."); + expect(output.stdout.join("\n")).toContain( + "βœ… Nexus repository configuration completed!", + ); + }); + + it("handles empty package managers", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "test-agent", + nexus_url: "https://nexus.example.com", + nexus_password: "test-token", + package_managers: JSON.stringify({}), + }); + + const output = await executeScriptInContainer(state, "ubuntu:20.04"); + expect(output.stdout.join("\n")).toContain( + "πŸ€” no maven repository is set, skipping maven configuration.", + ); + expect(output.stdout.join("\n")).toContain( + "πŸ€” no npm repository is set, skipping npm configuration.", + ); + expect(output.stdout.join("\n")).toContain( + "πŸ€” no pypi repository is set, skipping pypi configuration.", + ); + expect(output.stdout.join("\n")).toContain( + "πŸ€” no docker repository is set, skipping docker configuration.", + ); + }); + + it("configures Go module proxy", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "test-agent", + nexus_url: "https://nexus.example.com", + nexus_password: "test-token", + package_managers: JSON.stringify({ + go: ["go-public", "go-private"], + }), + }); + + const output = await executeScriptInContainer(state, "ubuntu:20.04"); + expect(output.stdout.join("\n")).toContain("🐹 Configuring Go..."); + expect(output.stdout.join("\n")).toContain( + "Go proxy configured via GOPROXY environment variable", + ); + expect(output.stdout.join("\n")).toContain("πŸ₯³ Configuration complete!"); + }); + + it("validates nexus_url format", async () => { + await expect( + runTerraformApply(import.meta.dir, { + agent_id: "test-agent", + nexus_url: "invalid-url", + nexus_password: "test-token", + package_managers: JSON.stringify({}), + }), + ).rejects.toThrow(); + }); + + it("validates username_field values", async () => { + await expect( + runTerraformApply(import.meta.dir, { + agent_id: "test-agent", + nexus_url: "https://nexus.example.com", + nexus_password: "test-token", + username_field: "invalid", + package_managers: JSON.stringify({}), + }), + ).rejects.toThrow(); + }); +}); diff --git a/registry/mavrickrishi/modules/nexus-repository/main.tf b/registry/mavrickrishi/modules/nexus-repository/main.tf new file mode 100644 index 00000000..be573121 --- /dev/null +++ b/registry/mavrickrishi/modules/nexus-repository/main.tf @@ -0,0 +1,137 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + coder = { + source = "coder/coder" + version = ">= 2.5" + } + } +} + +variable "nexus_url" { + type = string + description = "The base URL of your Nexus repository manager (e.g. https://nexus.example.com)" + validation { + condition = can(regex("^(https|http)://", var.nexus_url)) + error_message = "nexus_url must be a valid URL starting with either 'https://' or 'http://'" + } +} + +variable "nexus_username" { + type = string + description = "Custom username for Nexus authentication. If not provided, defaults to the Coder username based on the username_field setting" + default = null +} + +variable "nexus_password" { + type = string + description = "API token or password for Nexus authentication. This value is sensitive and should be stored securely" + sensitive = true +} + +variable "agent_id" { + type = string + description = "The ID of a Coder agent." +} + +variable "package_managers" { + type = object({ + maven = optional(list(string), []) + npm = optional(list(string), []) + go = optional(list(string), []) + pypi = optional(list(string), []) + docker = optional(list(string), []) + }) + default = { + maven = [] + npm = [] + go = [] + pypi = [] + docker = [] + } + description = <<-EOF + Configuration for package managers. Each key maps to a list of Nexus repository names: + - maven: List of Maven repository names + - npm: List of npm repository names (supports scoped packages with "@scope:repo-name") + - go: List of Go proxy repository names + - pypi: List of PyPI repository names + - docker: List of Docker registry names + Unused package managers can be omitted. + Example: + { + maven = ["maven-public", "maven-releases"] + npm = ["npm-public", "@scoped:npm-private"] + go = ["go-public", "go-private"] + pypi = ["pypi-public", "pypi-private"] + docker = ["docker-public", "docker-private"] + } + EOF +} + +variable "username_field" { + type = string + description = "Field to use for username (\"username\" or \"email\"). Defaults to \"username\". Only used when nexus_username is not provided" + default = "username" + validation { + condition = can(regex("^(email|username)$", var.username_field)) + error_message = "username_field must be either 'email' or 'username'" + } +} + +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + +locals { + username = coalesce(var.nexus_username, var.username_field == "email" ? data.coder_workspace_owner.me.email : data.coder_workspace_owner.me.name) + nexus_host = split("/", replace(replace(var.nexus_url, "https://", ""), "http://", ""))[0] +} + +locals { + # Get first repository name or use default + maven_repo = length(var.package_managers.maven) > 0 ? var.package_managers.maven[0] : "maven-public" + npm_repo = length(var.package_managers.npm) > 0 ? var.package_managers.npm[0] : "npm-public" + go_repo = length(var.package_managers.go) > 0 ? var.package_managers.go[0] : "go-public" + pypi_repo = length(var.package_managers.pypi) > 0 ? var.package_managers.pypi[0] : "pypi-public" + + npmrc = <<-EOF +registry=${var.nexus_url}/repository/${local.npm_repo}/ +//${local.nexus_host}/repository/${local.npm_repo}/:username=${local.username} +//${local.nexus_host}/repository/${local.npm_repo}/:_password=${base64encode(var.nexus_password)} +//${local.nexus_host}/repository/${local.npm_repo}/:always-auth=true +EOF +} + +resource "coder_script" "nexus" { + agent_id = var.agent_id + display_name = "nexus-repository" + icon = "/icon/nexus-repository.svg" + script = templatefile("${path.module}/run.sh", { + NEXUS_URL = var.nexus_url + NEXUS_HOST = local.nexus_host + NEXUS_USERNAME = local.username + NEXUS_PASSWORD = var.nexus_password + HAS_MAVEN = length(var.package_managers.maven) == 0 ? "" : "YES" + MAVEN_REPO = local.maven_repo + HAS_NPM = length(var.package_managers.npm) == 0 ? "" : "YES" + NPMRC = local.npmrc + HAS_GO = length(var.package_managers.go) == 0 ? "" : "YES" + GO_REPO = local.go_repo + HAS_PYPI = length(var.package_managers.pypi) == 0 ? "" : "YES" + PYPI_REPO = local.pypi_repo + HAS_DOCKER = length(var.package_managers.docker) == 0 ? "" : "YES" + REGISTER_DOCKER = join("\n ", formatlist("register_docker \"%s\"", var.package_managers.docker)) + }) + run_on_start = true +} + +resource "coder_env" "goproxy" { + count = length(var.package_managers.go) == 0 ? 0 : 1 + agent_id = var.agent_id + name = "GOPROXY" + value = join(",", [ + for repo in var.package_managers.go : + "https://${local.username}:${var.nexus_password}@${local.nexus_host}/repository/${repo}" + ]) +} + diff --git a/registry/mavrickrishi/modules/nexus-repository/run.sh b/registry/mavrickrishi/modules/nexus-repository/run.sh new file mode 100644 index 00000000..b860c4bc --- /dev/null +++ b/registry/mavrickrishi/modules/nexus-repository/run.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash + +not_configured() { + type=$1 + echo "πŸ€” no $type repository is set, skipping $type configuration." +} + +config_complete() { + echo "πŸ₯³ Configuration complete!" +} + +register_docker() { + repo=$1 + echo -n "${NEXUS_PASSWORD}" | docker login "${NEXUS_HOST}/repository/$${repo}" --username "${NEXUS_USERNAME}" --password-stdin +} + +echo "πŸš€ Configuring Nexus repository access..." + +# Configure Maven +if [ -n "${HAS_MAVEN}" ]; then + echo "β˜• Configuring Maven..." + mkdir -p ~/.m2 + cat > ~/.m2/settings.xml << 'EOF' + + + + + nexus + ${NEXUS_USERNAME} + ${NEXUS_PASSWORD} + + + + + nexus-mirror + * + ${NEXUS_URL}/repository/${MAVEN_REPO} + + + +EOF + config_complete +else + not_configured maven +fi + +# Configure npm +if [ -n "${HAS_NPM}" ]; then + echo "πŸ“¦ Configuring npm..." + cat > ~/.npmrc << 'EOF' +${NPMRC} +EOF + config_complete +else + not_configured npm +fi + +# Configure Go +if [ -n "${HAS_GO}" ]; then + echo "🐹 Configuring Go..." + # Go configuration is handled via GOPROXY environment variable + # which is set by the Terraform configuration + echo "Go proxy configured via GOPROXY environment variable" + config_complete +else + not_configured go +fi + +# Configure pip +if [ -n "${HAS_PYPI}" ]; then + echo "🐍 Configuring pip..." + mkdir -p ~/.pip + # Create .netrc file for secure credential storage + cat > ~/.netrc << EOF +machine ${NEXUS_HOST} +login ${NEXUS_USERNAME} +password ${NEXUS_PASSWORD} +EOF + chmod 600 ~/.netrc + + # Update pip.conf to use index-url without embedded credentials + cat > ~/.pip/pip.conf << 'EOF' +[global] +index-url = https://${NEXUS_HOST}/repository/${PYPI_REPO}/simple +EOF + config_complete +else + not_configured pypi +fi + +# Configure Docker +if [ -n "${HAS_DOCKER}" ]; then + if command -v docker > /dev/null 2>&1; then + echo "🐳 Configuring Docker credentials..." + mkdir -p ~/.docker + ${REGISTER_DOCKER} + config_complete + else + echo "πŸ€” Docker is not installed, skipping Docker configuration." + fi +else + not_configured docker +fi + +echo "βœ… Nexus repository configuration completed!" From ccdca6daf5f363c58865ea536f88fdaf8fee1e66 Mon Sep 17 00:00:00 2001 From: DevCats Date: Thu, 9 Oct 2025 07:42:07 -0500 Subject: [PATCH 17/27] chore: update CONTRIBUTION docs to explain both tests, and update CI for both tests (#384) Closes #383 ## Description - Update CONTRIBUTION.md to elaborate on ts and tf tests - Add ./scripts/terraform_test_all.sh to CI for ts tests ## Type of Change - [ ] New module - [ ] Bug fix - [ ] Feature/enhancement - [X] Documentation - [X] Other ## Testing & Validation - [ ] Tests pass (`bun test`) - [X] Code formatted (`bun run fmt`) - [ ] Changes tested locally --------- Co-authored-by: Atif Ali --- .github/workflows/ci.yaml | 2 ++ CONTRIBUTING.md | 23 +++++++++++++++-------- examples/modules/MODULE_NAME.tftest.hcl | 4 ++-- examples/modules/main.tf | 24 ++++++++++++------------ 4 files changed, 31 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7e14213c..87fea922 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -28,6 +28,8 @@ jobs: run: bun install - name: Run TypeScript tests run: bun test + - name: Run Terraform tests + run: ./scripts/terraform_test_all.sh - name: Run Terraform Validate run: bun terraform-validate validate-style: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 282788ec..4f46eb67 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -124,18 +124,23 @@ This script generates: - Accurate description and usage examples - Correct icon path (usually `../../../../.icons/your-icon.svg`) - Proper tags that describe your module -3. **Create at least one `.tftest.hcl`** to test your module with `terraform test` +3. **Create tests for your module:** + - **Terraform tests**: Create a `*.tftest.hcl` file and test with `terraform test` + - **TypeScript tests**: Create `main.test.ts` file if your module runs scripts or has business logic that Terraform tests can't cover 4. **Add any scripts** or additional files your module needs ### 4. Test and Submit ```bash -# Test your module (from the module directory) +# Test your module +cd registry/[namespace]/modules/[module-name] + +# Required: Test Terraform functionality terraform init -upgrade terraform test -verbose -# Or run all tests in the repo -./scripts/terraform_test_all.sh +# Optional: Test TypeScript files if you have main.test.ts +bun test main.test.ts # Format code bun run fmt @@ -343,8 +348,8 @@ coder templates push test-[template-name] -d . terraform init -upgrade terraform test -verbose -# Test all modules -./scripts/terraform_test_all.sh +# Optional: If you have TypeScript tests +bun test main.test.ts ``` ### 3. Maintain Backward Compatibility @@ -393,7 +398,9 @@ Example: `https://github.com/coder/registry/compare/main...your-branch?template= ### Every Module Must Have - `main.tf` - Terraform code -- One or more `.tftest.hcl` files - Working tests with `terraform test` +- **Tests**: + - `*.tftest.hcl` files with `terraform test` (to test terraform specific logic) + - `main.test.ts` file with `bun test` (to test business logic, i.e., `coder_script` to install a package.) - `README.md` - Documentation with frontmatter ### Every Template Must Have @@ -493,7 +500,7 @@ When reporting bugs, include: 2. **No tests** or broken tests 3. **Hardcoded values** instead of variables 4. **Breaking changes** without defaults -5. **Not running** formatting (`bun run fmt`) and tests (`terraform test`) before submitting +5. **Not running** formatting (`bun run fmt`) and tests (`terraform test`, and `bun test main.test.ts` if applicable) before submitting ## For Maintainers diff --git a/examples/modules/MODULE_NAME.tftest.hcl b/examples/modules/MODULE_NAME.tftest.hcl index 6f11666b..a6ccc524 100644 --- a/examples/modules/MODULE_NAME.tftest.hcl +++ b/examples/modules/MODULE_NAME.tftest.hcl @@ -15,7 +15,7 @@ run "app_url_uses_port" { } assert { - condition = resource.coder_app.MODULE_NAME.url == "http://localhost:19999" - error_message = "Expected MODULE_NAME app URL to include configured port" + condition = resource.coder_app.module_name.url == "http://localhost:19999" + error_message = "Expected module-name app URL to include configured port" } } diff --git a/examples/modules/main.tf b/examples/modules/main.tf index 628eb1da..c0acfbc3 100644 --- a/examples/modules/main.tf +++ b/examples/modules/main.tf @@ -35,13 +35,13 @@ variable "agent_id" { variable "log_path" { type = string - description = "The path to log MODULE_NAME to." - default = "/tmp/MODULE_NAME.log" + description = "The path to the module log file." + default = "/tmp/module_name.log" } variable "port" { type = number - description = "The port to run MODULE_NAME on." + description = "The port to run the application on." default = 19999 } @@ -59,9 +59,9 @@ variable "order" { # Add other variables here -resource "coder_script" "MODULE_NAME" { +resource "coder_script" "module_name" { agent_id = var.agent_id - display_name = "MODULE_NAME" + display_name = "Module Name" icon = local.icon_url script = templatefile("${path.module}/run.sh", { LOG_PATH : var.log_path, @@ -70,10 +70,10 @@ resource "coder_script" "MODULE_NAME" { run_on_stop = false } -resource "coder_app" "MODULE_NAME" { +resource "coder_app" "module_name" { agent_id = var.agent_id - slug = "MODULE_NAME" - display_name = "MODULE_NAME" + slug = "module-name" + display_name = "Module Name" url = "http://localhost:${var.port}" icon = local.icon_url subdomain = false @@ -88,10 +88,10 @@ resource "coder_app" "MODULE_NAME" { } } -data "coder_parameter" "MODULE_NAME" { - type = "list(string)" - name = "MODULE_NAME" - display_name = "MODULE_NAME" +data "coder_parameter" "module_name" { + type = "string" + name = "module_name" + display_name = "Module Name" icon = local.icon_url mutable = var.mutable default = local.options["Option 1"]["value"] From d516aff908d2e768c0e1a8f66be1331606c0b8cd Mon Sep 17 00:00:00 2001 From: DevCats Date: Thu, 9 Oct 2025 08:13:06 -0500 Subject: [PATCH 18/27] chore: set verified to false and bump to 1.0.1 (#473) ## Description Removes verified status from nexus module. ## Type of Change - [ ] New module - [ ] Bug fix - [ ] Feature/enhancement - [ ] Documentation - [X] Other ## Module Information **Path:** `registry/mavrickrishi/modules/nexus-repository` **New version:** `v1.0.1` **Breaking change:** [ ] Yes [X] No ## Testing & Validation - [X] Tests pass (`bun test`) - [X] Code formatted (`bun run fmt`) - [X] Changes tested locally --- .../modules/nexus-repository/README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/registry/mavrickrishi/modules/nexus-repository/README.md b/registry/mavrickrishi/modules/nexus-repository/README.md index 4694e0f8..6bf5431c 100644 --- a/registry/mavrickrishi/modules/nexus-repository/README.md +++ b/registry/mavrickrishi/modules/nexus-repository/README.md @@ -2,7 +2,7 @@ display_name: Nexus Repository description: Configure package managers to use Sonatype Nexus Repository for Maven, npm, PyPI, and Docker registries. icon: ../../../../.icons/nexus-repository.svg -verified: true +verified: false tags: [integration, nexus-repository, maven, npm, pypi, docker] --- @@ -13,7 +13,7 @@ Configure package managers (Maven, npm, Go, PyPI, Docker) to use [Sonatype Nexus ```tf module "nexus_repository" { source = "registry.coder.com/mavrickrishi/nexus-repository/coder" - version = "1.0.0" + version = "1.0.1" agent_id = coder_agent.example.id nexus_url = "https://nexus.example.com" nexus_password = var.nexus_api_token @@ -43,7 +43,7 @@ module "nexus_repository" { ```tf module "nexus_repository" { source = "registry.coder.com/mavrickrishi/nexus-repository/coder" - version = "1.0.0" + version = "1.0.1" agent_id = coder_agent.example.id nexus_url = "https://nexus.example.com" nexus_password = var.nexus_api_token @@ -58,7 +58,7 @@ module "nexus_repository" { ```tf module "nexus_repository" { source = "registry.coder.com/mavrickrishi/nexus-repository/coder" - version = "1.0.0" + version = "1.0.1" agent_id = coder_agent.example.id nexus_url = "https://nexus.example.com" nexus_password = var.nexus_api_token @@ -73,7 +73,7 @@ module "nexus_repository" { ```tf module "nexus_repository" { source = "registry.coder.com/mavrickrishi/nexus-repository/coder" - version = "1.0.0" + version = "1.0.1" agent_id = coder_agent.example.id nexus_url = "https://nexus.example.com" nexus_password = var.nexus_api_token @@ -88,7 +88,7 @@ module "nexus_repository" { ```tf module "nexus_repository" { source = "registry.coder.com/mavrickrishi/nexus-repository/coder" - version = "1.0.0" + version = "1.0.1" agent_id = coder_agent.example.id nexus_url = "https://nexus.example.com" nexus_password = var.nexus_api_token @@ -103,7 +103,7 @@ module "nexus_repository" { ```tf module "nexus_repository" { source = "registry.coder.com/mavrickrishi/nexus-repository/coder" - version = "1.0.0" + version = "1.0.1" agent_id = coder_agent.example.id nexus_url = "https://nexus.example.com" nexus_password = var.nexus_api_token @@ -118,7 +118,7 @@ module "nexus_repository" { ```tf module "nexus_repository" { source = "registry.coder.com/mavrickrishi/nexus-repository/coder" - version = "1.0.0" + version = "1.0.1" agent_id = coder_agent.example.id nexus_url = "https://nexus.example.com" nexus_username = "custom-user" @@ -134,7 +134,7 @@ module "nexus_repository" { ```tf module "nexus_repository" { source = "registry.coder.com/mavrickrishi/nexus-repository/coder" - version = "1.0.0" + version = "1.0.1" agent_id = coder_agent.example.id nexus_url = "https://nexus.example.com" nexus_password = var.nexus_api_token From fc071e09306930928e7a414bdc9b7084d1c37797 Mon Sep 17 00:00:00 2001 From: chgl Date: Thu, 9 Oct 2025 16:36:14 +0200 Subject: [PATCH 19/27] refactor: refactored get_http_dir (#360) Closes # ## Description I just couldn't get the script to execute properly in its current form. I saw e.g. ```console [[: 1989{#d[@]}: syntax error: invalid arithmetic operator (error token is "{#d[@]}") ``` when trying to run the script locally. (GNU bash, version 5.2.21(1)-release (x86_64-pc-linux-gnu)). This uses a likely simpler bash script, but requires both grep and awk. ## Type of Change - [ ] New module - [x] Bug fix - [ ] Feature/enhancement - [ ] Documentation - [ ] Other ## Module Information **Path:** `registry/coder/modules/kasmvnc` **New version:** `v1.2.3` **Breaking change:** [ ] Yes [x] No ## Testing & Validation - [ ] Tests pass (`bun test`) - [ ] Code formatted (`bun run fmt`) - [ ] Changes tested locally ## Related Issues --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Atif Ali Co-authored-by: DevCats --- registry/coder/modules/kasmvnc/README.md | 2 +- registry/coder/modules/kasmvnc/run.sh | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/registry/coder/modules/kasmvnc/README.md b/registry/coder/modules/kasmvnc/README.md index cb07e6e9..2bc862d4 100644 --- a/registry/coder/modules/kasmvnc/README.md +++ b/registry/coder/modules/kasmvnc/README.md @@ -14,7 +14,7 @@ Automatically install [KasmVNC](https://kasmweb.com/kasmvnc) in a workspace, and module "kasmvnc" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/kasmvnc/coder" - version = "1.2.3" + version = "1.2.4" agent_id = coder_agent.example.id desktop_environment = "xfce" subdomain = true diff --git a/registry/coder/modules/kasmvnc/run.sh b/registry/coder/modules/kasmvnc/run.sh index 8389ea14..04b8b9ee 100644 --- a/registry/coder/modules/kasmvnc/run.sh +++ b/registry/coder/modules/kasmvnc/run.sh @@ -60,6 +60,9 @@ install_deb() { sudo apt-get -o DPkg::Lock::Timeout=300 -qq update fi + echo "Installing required Perl DateTime module..." + DEBIAN_FRONTEND=noninteractive sudo apt-get -o DPkg::Lock::Timeout=300 install --yes -qq --no-install-recommends --no-install-suggests libdatetime-perl + DEBIAN_FRONTEND=noninteractive sudo apt-get -o DPkg::Lock::Timeout=300 install --yes -qq --no-install-recommends --no-install-suggests "$kasmdeb" rm "$kasmdeb" } @@ -233,19 +236,17 @@ get_http_dir() { # Check the system configuration path if [[ -e /etc/kasmvnc/kasmvnc.yaml ]]; then - d=($(grep -E "^\s*httpd_directory:.*$" /etc/kasmvnc/kasmvnc.yaml)) - # If this grep is successful, it will return: - # httpd_directory: /usr/share/kasmvnc/www - if [[ $${#d[@]} -eq 2 && -d "$${d[1]}" ]]; then - httpd_directory="$${d[1]}" + d=$(grep -E '^\s*httpd_directory:.*$' "/etc/kasmvnc/kasmvnc.yaml" | awk '{print $$2}') + if [[ -n "$d" && -d "$d" ]]; then + httpd_directory=$d fi fi # Check the home directory for overriding values if [[ -e "$HOME/.vnc/kasmvnc.yaml" ]]; then - d=($(grep -E "^\s*httpd_directory:.*$" "$HOME/.vnc/kasmvnc.yaml")) - if [[ $${#d[@]} -eq 2 && -d "$${d[1]}" ]]; then - httpd_directory="$${d[1]}" + d=$(grep -E '^\s*httpd_directory:.*$' "$HOME/.vnc/kasmvnc.yaml" | awk '{print $$2}') + if [[ -n "$d" && -d "$d" ]]; then + httpd_directory=$d fi fi echo $httpd_directory From 898219b16b42d4735e7f061cd1f25fb02890c7b4 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Thu, 9 Oct 2025 20:50:38 +0500 Subject: [PATCH 20/27] Enhance PR template with template information section (#474) --- .github/PULL_REQUEST_TEMPLATE.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 87ed60be..133537e3 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,5 +1,3 @@ -Closes # - ## Description @@ -7,6 +5,7 @@ Closes # ## Type of Change - [ ] New module +- [ ] New template - [ ] Bug fix - [ ] Feature/enhancement - [ ] Documentation @@ -20,10 +19,16 @@ Closes # **New version:** `v1.0.0` **Breaking change:** [ ] Yes [ ] No +## Template Information + + + +**Path:** `registry/[namespace]/templates/[template-name]` + ## Testing & Validation - [ ] Tests pass (`bun test`) -- [ ] Code formatted (`bun run fmt`) +- [ ] Code formatted (`bun fmt`) - [ ] Changes tested locally ## Related Issues From e7d705bf98ec50a6bd779e358d9bce5db16cd4f6 Mon Sep 17 00:00:00 2001 From: greg-the-coder Date: Thu, 9 Oct 2025 20:27:18 -0500 Subject: [PATCH 21/27] Fixes from AWS Workshop testing (#428) Closes # ## Description Changes to code-server and jetbrains modules that were not caught during initial unit-testing, that appear to be related to older versions of the modules or recent changes. ## Type of Change - [ ] New module - [x] Bug fix - [ ] Feature/enhancement - [ ] Documentation - [ ] Other ## Testing & Validation - [ ] Tests pass (`bun test`) - [ ] Code formatted (`bun run fmt`) - [x] Changes tested locally ## Related Issues None --------- Co-authored-by: DevelopmentCats --- registry/coder/templates/kubernetes-devcontainer/main.tf | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/registry/coder/templates/kubernetes-devcontainer/main.tf b/registry/coder/templates/kubernetes-devcontainer/main.tf index 07af6586..5e36226d 100644 --- a/registry/coder/templates/kubernetes-devcontainer/main.tf +++ b/registry/coder/templates/kubernetes-devcontainer/main.tf @@ -426,15 +426,14 @@ module "code-server" { # This ensures that the latest non-breaking version of the module gets downloaded, you can also pin the module version to prevent breaking changes in production. version = "~> 1.0" - agent_id = coder_agent.main.id - agent_name = "main" - order = 1 + agent_id = coder_agent.main.id + order = 1 } # See https://registry.coder.com/modules/coder/jetbrains module "jetbrains" { count = data.coder_workspace.me.start_count - source = "registry.coder.com/modules/coder/jetbrains/coder" + source = "registry.coder.com/coder/jetbrains/coder" version = "~> 1.0" agent_id = coder_agent.main.id agent_name = "main" From 36943d1dfbc7c1d033bd887b4d1f0487f95541e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 07:40:01 -0500 Subject: [PATCH 22/27] chore(deps): bump crate-ci/typos from 1.37.2 to 1.38.1 in the github-actions group (#475) Bumps the github-actions group with 1 update: [crate-ci/typos](https://github.com/crate-ci/typos). Updates `crate-ci/typos` from 1.37.2 to 1.38.1
Release notes

Sourced from crate-ci/typos's releases.

v1.38.1

[1.38.1] - 2025-10-07

Fixes

  • Ignore common golang identifiers

v1.38.0

[1.38.0] - 2025-10-06

Features

  • Update type list

Fixes

  • Don't correct typ
  • Consistently error on unused config fields

v1.37.3

[1.37.3] - 2025-10-06

Fixes

  • Don't correct PN for bitbake file types
Changelog

Sourced from crate-ci/typos's changelog.

[1.38.1] - 2025-10-07

Fixes

  • Ignore common golang identifiers

[1.38.0] - 2025-10-06

Features

  • Update type list

Fixes

  • Don't correct typ
  • Consistently error on unused config fields

[1.37.3] - 2025-10-06

Fixes

  • Don't correct PN for bitbake file types
Commits
  • 80c8a49 chore: Release
  • c1008ce docs: Update changelog
  • 62a3b50 Merge pull request #1398 from ccoveille-forks/go-exclusions
  • e6bedbd fix(config): Add some Go exclusions
  • 90cacd6 docs(ref): Speak to glob ambiguity
  • b81b12e docs(ref): Clarify directories are not spell checked
  • eaf25df docs(ref): Speak to locale's behavior
  • a9735e2 docs(ref): Provide identifier/word config examples
  • 3c14191 docs(ref): Talk about include lists
  • d0f81dc docs(ref): Re-organize help more like cargo
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=crate-ci/typos&package-manager=github_actions&previous-version=1.37.2&new-version=1.38.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 87fea922..9c676ec1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -50,7 +50,7 @@ jobs: - name: Validate formatting run: bun fmt:ci - name: Check for typos - uses: crate-ci/typos@v1.37.2 + uses: crate-ci/typos@v1.38.1 with: config: .github/typos.toml validate-readme-files: From 50f4d5388b2f4b6fd713954bc9d60de9bfcfb687 Mon Sep 17 00:00:00 2001 From: Matt Hazinski Date: Tue, 14 Oct 2025 09:49:52 -0700 Subject: [PATCH 23/27] fix(codex): pass folder variable to agentapi module (#477) ## Description The folder variable was not being passed from the codex module to the agentapi module, causing agentapi to use its default value of `/home/coder` instead of the user-specified folder path. This resulted in permission errors when the codex module tried to create directories in `/home/coder` when users specified a different folder like `/home/matt/foo`. Fix by adding `folder = var.folder` to the agentapi module invocation. ## Type of Change - [ ] New module - [ ] New template - [x] Bug fix - [ ] Feature/enhancement - [ ] Documentation - [ ] Other ## Module Information Path: registry/coder-labs/modules/codex New version: v2.1.1 Breaking change: [ ] Yes [X] No ## Testing & Validation - [x] Tests pass (`bun test`) - [x] Code formatted (`bun fmt`) - [x] Changes tested locally ## Related Issues Fixes https://github.com/coder/registry/issues/476 --------- Co-authored-by: Claude Co-authored-by: DevelopmentCats --- registry/coder-labs/modules/codex/README.md | 8 ++++---- registry/coder-labs/modules/codex/main.tf | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/registry/coder-labs/modules/codex/README.md b/registry/coder-labs/modules/codex/README.md index e5eaafe4..549721ec 100644 --- a/registry/coder-labs/modules/codex/README.md +++ b/registry/coder-labs/modules/codex/README.md @@ -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.1.0" + version = "2.1.1" 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.1.0" + version = "2.1.1" 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.1.0" + version = "2.1.1" 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.1.0" + version = "2.1.1" # ... other variables ... # Override default configuration diff --git a/registry/coder-labs/modules/codex/main.tf b/registry/coder-labs/modules/codex/main.tf index 7c2e890d..460d5af9 100644 --- a/registry/coder-labs/modules/codex/main.tf +++ b/registry/coder-labs/modules/codex/main.tf @@ -131,6 +131,7 @@ module "agentapi" { version = "1.2.0" agent_id = var.agent_id + folder = var.folder web_app_slug = local.app_slug web_app_order = var.order web_app_group = var.group From 54a7bb0001ad9b84b9212249b2b77e8c25940aa1 Mon Sep 17 00:00:00 2001 From: DevCats Date: Tue, 14 Oct 2025 12:06:32 -0500 Subject: [PATCH 24/27] docs: add usage examples for bedrock and vertex (#431) Closes # ## Description Adds Usage Examples for Vertex and Bedrock as described in the linked documentation. ## Type of Change - [ ] New module - [ ] Bug fix - [ ] Feature/enhancement - [X] Documentation - [ ] Other ## Module Information **Path:** `registry/coder/modules/claude-code` **New version:** `v3.0.1` **Breaking change:** [ ] Yes [X] No ## Testing & Validation - [X] Tests pass (`bun test`) - [X] Code formatted (`bun run fmt`) - [ ] Changes tested locally ## Related Issues --- registry/coder/modules/claude-code/README.md | 163 ++++++++++++++++++- 1 file changed, 159 insertions(+), 4 deletions(-) diff --git a/registry/coder/modules/claude-code/README.md b/registry/coder/modules/claude-code/README.md index 9486927c..ed10e2a5 100644 --- a/registry/coder/modules/claude-code/README.md +++ b/registry/coder/modules/claude-code/README.md @@ -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.1.0" + version = "3.1.1" 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.1.0" + version = "3.1.1" agent_id = coder_agent.example.id workdir = "/home/coder/project" @@ -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.1.0" + version = "3.1.1" agent_id = coder_agent.example.id workdir = "/home/coder" install_claude_code = true @@ -108,13 +108,168 @@ variable "claude_code_oauth_token" { module "claude-code" { source = "registry.coder.com/coder/claude-code/coder" - version = "3.0.3" + version = "3.1.1" agent_id = coder_agent.example.id workdir = "/home/coder/project" claude_code_oauth_token = var.claude_code_oauth_token } ``` +### Usage with AWS Bedrock + +#### Prerequisites + +AWS account with Bedrock access, Claude models enabled in Bedrock console, appropriate IAM permissions. + +Configure Claude Code to use AWS Bedrock for accessing Claude models through your AWS infrastructure. + +```tf +resource "coder_env" "bedrock_use" { + agent_id = coder_agent.example.id + name = "CLAUDE_CODE_USE_BEDROCK" + value = "1" +} + +resource "coder_env" "aws_region" { + agent_id = coder_agent.example.id + name = "AWS_REGION" + value = "us-east-1" # Choose your preferred region +} + +# Option 1: Using AWS credentials + +variable "aws_access_key_id" { + type = string + description = "Your AWS access key ID. Create this in the AWS IAM console under 'Security credentials'." + sensitive = true + value = "xxxx-xxx-xxxx" +} + +variable "aws_secret_access_key" { + type = string + description = "Your AWS secret access key. This is shown once when you create an access key in the AWS IAM console." + sensitive = true + value = "xxxx-xxx-xxxx" +} + +resource "coder_env" "aws_access_key_id" { + agent_id = coder_agent.example.id + name = "AWS_ACCESS_KEY_ID" + value = var.aws_access_key_id +} + +resource "coder_env" "aws_secret_access_key" { + agent_id = coder_agent.example.id + name = "AWS_SECRET_ACCESS_KEY" + value = var.aws_secret_access_key +} + +# Option 2: Using Bedrock API key (simpler) + +variable "aws_bearer_token_bedrock" { + type = string + description = "Your AWS Bedrock bearer token. This provides access to Bedrock without needing separate access key and secret key." + sensitive = true + value = "xxxx-xxx-xxxx" +} + +resource "coder_env" "bedrock_api_key" { + agent_id = coder_agent.example.id + name = "AWS_BEARER_TOKEN_BEDROCK" + value = var.aws_bearer_token_bedrock +} + +module "claude-code" { + source = "registry.coder.com/coder/claude-code/coder" + version = "3.1.1" + agent_id = coder_agent.example.id + workdir = "/home/coder/project" + model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0" +} +``` + +> [!NOTE] +> For additional Bedrock configuration options (model selection, token limits, region overrides, etc.), see the [Claude Code Bedrock documentation](https://docs.claude.com/en/docs/claude-code/amazon-bedrock). + +### Usage with Google Vertex AI + +#### Prerequisites + +GCP project with Vertex AI API enabled, Claude models enabled through Model Garden, service account with Vertex AI permissions, appropriate IAM permissions (Vertex AI User role). + +Configure Claude Code to use Google Vertex AI for accessing Claude models through Google Cloud Platform. + +```tf +variable "vertex_sa_json" { + type = string + description = "The complete JSON content of your Google Cloud service account key file. Create a service account in the GCP Console under 'IAM & Admin > Service Accounts', then create and download a JSON key. Copy the entire JSON content into this variable." + sensitive = true +} + +resource "coder_env" "vertex_use" { + agent_id = coder_agent.example.id + name = "CLAUDE_CODE_USE_VERTEX" + value = "1" +} + +resource "coder_env" "vertex_project_id" { + agent_id = coder_agent.example.id + name = "ANTHROPIC_VERTEX_PROJECT_ID" + value = "your-gcp-project-id" +} + +resource "coder_env" "cloud_ml_region" { + agent_id = coder_agent.example.id + name = "CLOUD_ML_REGION" + value = "global" +} + +resource "coder_env" "vertex_sa_json" { + agent_id = coder_agent.example.id + name = "VERTEX_SA_JSON" + value = var.vertex_sa_json +} + +resource "coder_env" "google_application_credentials" { + agent_id = coder_agent.example.id + name = "GOOGLE_APPLICATION_CREDENTIALS" + value = "/tmp/gcp-sa.json" +} + +module "claude-code" { + source = "registry.coder.com/coder/claude-code/coder" + version = "3.1.1" + agent_id = coder_agent.example.id + workdir = "/home/coder/project" + model = "claude-sonnet-4@20250514" + + pre_install_script = <<-EOT + #!/bin/bash + # Write the service account JSON to a file + echo "$VERTEX_SA_JSON" > /tmp/gcp-sa.json + + # Install prerequisite packages + sudo apt-get update + sudo apt-get install -y apt-transport-https ca-certificates gnupg curl + + # Add Google Cloud public key + curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg + + # Add Google Cloud SDK repo to apt sources + echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | sudo tee /etc/apt/sources.list.d/google-cloud-sdk.list + + # Update and install the Google Cloud SDK + sudo apt-get update && sudo apt-get install -y google-cloud-cli + + # Authenticate gcloud with the service account + gcloud auth activate-service-account --key-file=/tmp/gcp-sa.json + EOT +} +``` + +> [!NOTE] +> For additional Vertex AI configuration options (model selection, token limits, region overrides, etc.), see the [Claude Code Vertex AI documentation](https://docs.claude.com/en/docs/claude-code/google-vertex-ai). + ## Troubleshooting If you encounter any issues, check the log files in the `~/.claude-module` directory within your workspace for detailed information. From cd759bd9a11bf18a1102e42acd3f5224cf0ab022 Mon Sep 17 00:00:00 2001 From: Hulto <7121375+hulto@users.noreply.github.com> Date: Wed, 15 Oct 2025 08:44:18 -0400 Subject: [PATCH 25/27] goose module: pass folder along to agentapi (#412) Co-authored-by: DevCats --- registry/coder/modules/goose/README.md | 4 ++-- registry/coder/modules/goose/main.tf | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/registry/coder/modules/goose/README.md b/registry/coder/modules/goose/README.md index 6e7a0c9c..f4f91ab5 100644 --- a/registry/coder/modules/goose/README.md +++ b/registry/coder/modules/goose/README.md @@ -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.2.0" + version = "2.2.1" 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.2.0" + version = "2.2.1" agent_id = coder_agent.example.id folder = "/home/coder" install_goose = true diff --git a/registry/coder/modules/goose/main.tf b/registry/coder/modules/goose/main.tf index 09fc966c..51f8b6d6 100644 --- a/registry/coder/modules/goose/main.tf +++ b/registry/coder/modules/goose/main.tf @@ -135,6 +135,7 @@ EOT install_script = file("${path.module}/scripts/install.sh") start_script = file("${path.module}/scripts/start.sh") module_dir_name = ".goose-module" + folder = trimsuffix(var.folder, "/") } module "agentapi" { @@ -156,6 +157,7 @@ module "agentapi" { pre_install_script = var.pre_install_script post_install_script = var.post_install_script start_script = local.start_script + folder = local.folder install_script = <<-EOT #!/bin/bash set -o errexit From 63cad2595429408adc2b43499df293c0763f2e78 Mon Sep 17 00:00:00 2001 From: Anas <135501348+anasfanani@users.noreply.github.com> Date: Wed, 15 Oct 2025 19:44:58 +0700 Subject: [PATCH 26/27] fix(amazon-q): pass workdir variable into agentapi folder variable (#478) Co-authored-by: DevCats --- registry/coder/modules/amazon-q/README.md | 18 +++++++++--------- registry/coder/modules/amazon-q/main.tf | 4 ++-- .../coder/modules/amazon-q/scripts/install.sh | 7 +++++++ 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/registry/coder/modules/amazon-q/README.md b/registry/coder/modules/amazon-q/README.md index 630c3a6b..3146f01e 100644 --- a/registry/coder/modules/amazon-q/README.md +++ b/registry/coder/modules/amazon-q/README.md @@ -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.1.0" + version = "2.1.1" 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.1.0" + version = "2.1.1" 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.1.0" + version = "2.1.1" 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.1.0" + version = "2.1.1" 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.1.0" + version = "2.1.1" 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.1.0" + version = "2.1.1" 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.1.0" + version = "2.1.1" agent_id = coder_agent.example.id workdir = "/home/coder" auth_tarball = var.amazon_q_auth_tarball @@ -340,7 +340,7 @@ module "amazon-q" { ```tf module "amazon-q" { source = "registry.coder.com/coder/amazon-q/coder" - version = "2.1.0" + version = "2.1.1" agent_id = coder_agent.example.id workdir = "/home/coder" auth_tarball = var.amazon_q_auth_tarball @@ -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.1.0" + version = "2.1.1" agent_id = coder_agent.example.id workdir = "/home/coder" auth_tarball = var.amazon_q_auth_tarball diff --git a/registry/coder/modules/amazon-q/main.tf b/registry/coder/modules/amazon-q/main.tf index a4bac213..84ac3c03 100644 --- a/registry/coder/modules/amazon-q/main.tf +++ b/registry/coder/modules/amazon-q/main.tf @@ -96,8 +96,6 @@ variable "workdir" { description = "The folder to run Amazon Q in." } -# --------------------------------------------- - variable "install_amazon_q" { type = bool description = "Whether to install Amazon Q." @@ -190,6 +188,7 @@ resource "coder_env" "auth_tarball" { locals { app_slug = "amazonq" + workdir = trimsuffix(var.workdir, "/") install_script = file("${path.module}/scripts/install.sh") start_script = file("${path.module}/scripts/start.sh") module_dir_name = ".amazonq-module" @@ -218,6 +217,7 @@ module "agentapi" { 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 diff --git a/registry/coder/modules/amazon-q/scripts/install.sh b/registry/coder/modules/amazon-q/scripts/install.sh index e3ae3361..f8e43e76 100644 --- a/registry/coder/modules/amazon-q/scripts/install.sh +++ b/registry/coder/modules/amazon-q/scripts/install.sh @@ -94,6 +94,13 @@ function install_amazon_q() { function extract_auth_tarball() { if [ -n "$ARG_AUTH_TARBALL" ]; then echo "Extracting auth tarball..." + + if ! command_exists zstd; then + echo "Error: zstd is required to extract the authentication tarball but is not installed." + echo "Please install zstd using the pre_install_script parameter." + exit 1 + fi + PREV_DIR="$PWD" echo "$ARG_AUTH_TARBALL" | base64 -d > /tmp/auth.tar.zst rm -rf ~/.local/share/amazon-q From da5a2ba6a8633198b0f7ce0b7114d532e3633331 Mon Sep 17 00:00:00 2001 From: Riajul Islam Date: Wed, 15 Oct 2025 18:53:17 +0600 Subject: [PATCH 27/27] feat(git-clone module): added post_clone_script. (#357) Co-authored-by: DevCats Co-authored-by: Atif Ali --- registry/coder/modules/git-clone/README.md | 43 ++++++++++++++----- registry/coder/modules/git-clone/main.test.ts | 19 +++++++- registry/coder/modules/git-clone/main.tf | 9 ++++ registry/coder/modules/git-clone/run.sh | 12 +++++- 4 files changed, 71 insertions(+), 12 deletions(-) diff --git a/registry/coder/modules/git-clone/README.md b/registry/coder/modules/git-clone/README.md index cf8f6d13..6ec2ccbe 100644 --- a/registry/coder/modules/git-clone/README.md +++ b/registry/coder/modules/git-clone/README.md @@ -14,7 +14,7 @@ This module allows you to automatically clone a repository by URL and skip if it module "git-clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "1.1.2" + version = "1.2.0" agent_id = coder_agent.example.id url = "https://github.com/coder/coder" } @@ -28,7 +28,7 @@ module "git-clone" { module "git-clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "1.1.2" + version = "1.2.0" agent_id = coder_agent.example.id url = "https://github.com/coder/coder" base_dir = "~/projects/coder" @@ -43,7 +43,7 @@ To use with [Git Authentication](https://coder.com/docs/v2/latest/admin/git-prov module "git-clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "1.1.2" + version = "1.2.0" agent_id = coder_agent.example.id url = "https://github.com/coder/coder" } @@ -69,7 +69,7 @@ data "coder_parameter" "git_repo" { module "git_clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "1.1.2" + version = "1.2.0" agent_id = coder_agent.example.id url = data.coder_parameter.git_repo.value } @@ -103,7 +103,7 @@ Configuring `git-clone` for a self-hosted GitHub Enterprise Server running at `g module "git-clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "1.1.2" + version = "1.2.0" agent_id = coder_agent.example.id url = "https://github.example.com/coder/coder/tree/feat/example" git_providers = { @@ -122,7 +122,7 @@ To GitLab clone with a specific branch like `feat/example` module "git-clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "1.1.2" + version = "1.2.0" agent_id = coder_agent.example.id url = "https://gitlab.com/coder/coder/-/tree/feat/example" } @@ -134,7 +134,7 @@ Configuring `git-clone` for a self-hosted GitLab running at `gitlab.example.com` module "git-clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "1.1.2" + version = "1.2.0" agent_id = coder_agent.example.id url = "https://gitlab.example.com/coder/coder/-/tree/feat/example" git_providers = { @@ -155,7 +155,7 @@ For example, to clone the `feat/example` branch: module "git-clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "1.1.2" + version = "1.2.0" agent_id = coder_agent.example.id url = "https://github.com/coder/coder" branch_name = "feat/example" @@ -173,7 +173,7 @@ For example, this will clone into the `~/projects/coder/coder-dev` folder: module "git-clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/git-clone/coder" - version = "1.1.2" + version = "1.2.0" agent_id = coder_agent.example.id url = "https://github.com/coder/coder" folder_name = "coder-dev" @@ -192,9 +192,32 @@ If not defined, the default, `0`, performs a full clone. module "git-clone" { count = data.coder_workspace.me.start_count source = "registry.coder.com/modules/git-clone/coder" - version = "1.1.0" + version = "1.2.0" agent_id = coder_agent.example.id url = "https://github.com/coder/coder" depth = 1 } ``` + +## Post-clone script + +Run a custom script after cloning the repository by setting the `post_clone_script` variable. +This is useful for running initialization tasks like installing dependencies or setting up the environment. + +```tf +module "git-clone" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/git-clone/coder" + version = "1.2.0" + agent_id = coder_agent.example.id + url = "https://github.com/coder/coder" + post_clone_script = <<-EOT + #!/bin/bash + echo "Repository cloned successfully!" + # Install dependencies + npm install + # Run any other initialization tasks + make setup + EOT +} +``` diff --git a/registry/coder/modules/git-clone/main.test.ts b/registry/coder/modules/git-clone/main.test.ts index 1e074fc0..0ae0a8db 100644 --- a/registry/coder/modules/git-clone/main.test.ts +++ b/registry/coder/modules/git-clone/main.test.ts @@ -30,11 +30,12 @@ describe("git-clone", async () => { url: "fake-url", }); const output = await executeScriptInContainer(state, "alpine/git"); - expect(output.exitCode).toBe(128); expect(output.stdout).toEqual([ "Creating directory ~/fake-url...", "Cloning fake-url to ~/fake-url...", ]); + expect(output.stderr.join(" ")).toContain("fatal"); + expect(output.stderr.join(" ")).toContain("fake-url"); }); it("repo_dir should match repo name for https", async () => { @@ -244,4 +245,20 @@ describe("git-clone", async () => { "Cloning https://github.com/michaelbrewer/repo-tests.log to ~/repo-tests.log on branch feat/branch...", ]); }); + + it("runs post-clone script", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + url: "fake-url", + post_clone_script: "echo 'Post-clone script executed'", + }); + const output = await executeScriptInContainer( + state, + "alpine/git", + "sh", + "mkdir -p ~/fake-url && echo 'existing' > ~/fake-url/file.txt", + ); + expect(output.stdout).toContain("Running post-clone script..."); + expect(output.stdout).toContain("Post-clone script executed"); + }); }); diff --git a/registry/coder/modules/git-clone/main.tf b/registry/coder/modules/git-clone/main.tf index d03a59d7..2d547ad2 100644 --- a/registry/coder/modules/git-clone/main.tf +++ b/registry/coder/modules/git-clone/main.tf @@ -62,6 +62,12 @@ variable "depth" { default = 0 } +variable "post_clone_script" { + description = "Custom script to run after cloning the repository. Runs always after git clone, even if the repository already exists." + type = string + default = null +} + locals { # Remove query parameters and fragments from the URL url = replace(replace(var.url, "/\\?.*/", ""), "/#.*/", "") @@ -81,6 +87,8 @@ locals { clone_path = var.base_dir != "" ? join("/", [var.base_dir, local.folder_name]) : join("/", ["~", local.folder_name]) # Construct the web URL web_url = startswith(local.clone_url, "git@") ? replace(replace(local.clone_url, ":", "/"), "git@", "https://") : local.clone_url + # Encode the post_clone_script for passing to the shell script + encoded_post_clone_script = var.post_clone_script != null ? base64encode(var.post_clone_script) : "" } output "repo_dir" { @@ -120,6 +128,7 @@ resource "coder_script" "git_clone" { REPO_URL : local.clone_url, BRANCH_NAME : local.branch_name, DEPTH = var.depth, + POST_CLONE_SCRIPT : local.encoded_post_clone_script, }) display_name = "Git Clone" icon = "/icon/git.svg" diff --git a/registry/coder/modules/git-clone/run.sh b/registry/coder/modules/git-clone/run.sh index 282c667a..07c970e9 100644 --- a/registry/coder/modules/git-clone/run.sh +++ b/registry/coder/modules/git-clone/run.sh @@ -6,6 +6,7 @@ BRANCH_NAME="${BRANCH_NAME}" # Expand home if it's specified! CLONE_PATH="$${CLONE_PATH/#\~/$${HOME}}" DEPTH="${DEPTH}" +POST_CLONE_SCRIPT="${POST_CLONE_SCRIPT}" # Check if the variable is empty... if [ -z "$REPO_URL" ]; then @@ -52,5 +53,14 @@ if [ -z "$(ls -A "$CLONE_PATH")" ]; then fi else echo "$CLONE_PATH already exists and isn't empty, skipping clone!" - exit 0 +fi + +# Run post-clone script if provided +if [ -n "$POST_CLONE_SCRIPT" ]; then + echo "Running post-clone script..." + echo "$POST_CLONE_SCRIPT" | base64 -d > /tmp/post_clone.sh + chmod +x /tmp/post_clone.sh + cd "$CLONE_PATH" + /tmp/post_clone.sh + rm /tmp/post_clone.sh fi