From 4ccb76e406a2970592ca3a4906c02d30c14eeaaf Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Tue, 13 Oct 2026 17:11:54 +0000 Subject: [PATCH 1/4] refactor(registry/coder-labs/modules/auggie)!: remove agentapi, tasks and start logic --- registry/coder-labs/modules/auggie/README.md | 206 +++---- .../modules/auggie/auggie.tftest.hcl | 186 ------ .../coder-labs/modules/auggie/main.test.ts | 564 ++++++++++-------- registry/coder-labs/modules/auggie/main.tf | 201 ++----- .../coder-labs/modules/auggie/main.tftest.hcl | 136 +++++ .../modules/auggie/scripts/install.sh | 128 ---- .../modules/auggie/scripts/install.sh.tftpl | 224 +++++++ .../modules/auggie/scripts/start.sh | 106 ---- .../modules/auggie/testdata/auggie-mock.sh | 36 +- 9 files changed, 834 insertions(+), 953 deletions(-) delete mode 100644 registry/coder-labs/modules/auggie/auggie.tftest.hcl create mode 100644 registry/coder-labs/modules/auggie/main.tftest.hcl delete mode 100644 registry/coder-labs/modules/auggie/scripts/install.sh create mode 100644 registry/coder-labs/modules/auggie/scripts/install.sh.tftpl delete mode 100644 registry/coder-labs/modules/auggie/scripts/start.sh diff --git a/registry/coder-labs/modules/auggie/README.md b/registry/coder-labs/modules/auggie/README.md index 418ef8bfc6..a6c254a1cb 100644 --- a/registry/coder-labs/modules/auggie/README.md +++ b/registry/coder-labs/modules/auggie/README.md @@ -1,161 +1,149 @@ --- display_name: Auggie CLI icon: ../../../../.icons/auggie.svg -description: Run Auggie CLI in your workspace for AI-powered coding assistance with AgentAPI integration +description: Install and configure Augment's Auggie CLI in your workspace. verified: true -tags: [agent, auggie, ai, tasks, augment] +tags: [agent, auggie, ai, augment] --- # Auggie CLI -Run Auggie CLI in your workspace to access Augment's AI coding assistant with advanced context understanding and codebase integration. This module integrates with [AgentAPI](https://github.com/coder/agentapi). +Install and configure [Auggie CLI](https://docs.augmentcode.com/cli/overview) in your workspace. Auggie is installed with npm, so Node.js 20+ and npm must be available in the image or installed with `pre_install_script`. ```tf module "auggie" { source = "registry.coder.com/coder-labs/auggie/coder" - version = "0.3.1" - agent_id = coder_agent.example.id - folder = "/home/coder/project" + version = "1.0.0" + agent_id = coder_agent.main.id } ``` -## Prerequisites - -- **Node.js and npm must be sourced/available before the auggie module installs** - ensure they are installed in your workspace image or via earlier provisioning steps -- You must add the [Coder Login](https://registry.coder.com/modules/coder/coder-login) module to your template -- **Augment session token for authentication (required for tasks). [Instructions](https://docs.augmentcode.com/cli/setup-auggie/authentication) to get the session token** +> [!WARNING] +> If upgrading from v0.x.x of this module: v1 is a major refactor that drops support for Coder Tasks and AgentAPI. The module now only installs and configures Auggie; launch it with your own `coder_app`. `folder` is renamed to `workdir` and is optional. `ai_prompt`, `continue_previous_conversation`, `interaction_mode`, `report_tasks`, `order`, `group`, `cli_app`, `web_app_display_name`, `cli_app_display_name`, `install_agentapi`, and `agentapi_version` are removed in favor of CLI flags in your launcher. `mcp` and `mcp_files` servers are now added to `~/.augment/settings.json`, `rules` are written to `~/.augment/rules/coder-module.md`, and `auggie_model` sets the `model` key in `~/.augment/settings.json`. Keep using v0.x.x if you depend on Coder Tasks. ## Examples -### Usage with Tasks and Configuration +### Standalone mode with a launcher app ```tf -data "coder_parameter" "ai_prompt" { - type = "string" - name = "AI Prompt" - default = "" - description = "Initial task prompt for Auggie CLI" - mutable = true +locals { + auggie_workdir = "/home/coder/project" } -module "coder-login" { - count = data.coder_workspace.me.start_count - source = "registry.coder.com/coder/coder-login/coder" - version = "0.2.2" - agent_id = coder_agent.example.id +module "auggie" { + source = "registry.coder.com/coder-labs/auggie/coder" + version = "1.0.0" + agent_id = coder_agent.main.id + workdir = local.auggie_workdir + augment_session_token = var.augment_session_token } +resource "coder_app" "auggie" { + agent_id = coder_agent.main.id + slug = "auggie" + display_name = "Auggie" + icon = "/icon/auggie.svg" + open_in = "slim-window" + command = <<-EOT + #!/usr/bin/env bash + set -e + cd "${local.auggie_workdir}" + exec auggie --continue + EOT +} +``` + +See [CLI flags](https://docs.augmentcode.com/cli/reference) for launcher options such as `--continue`, `--model`, `--print`, and `--quiet`. + +> [!NOTE] +> The `coder_app` command re-executes on every pane reconnect. This works for interactive `auggie`, but one-shot commands like `auggie --print` will re-run each time. For one-shot prompts, use a `coder_script` (runs once at startup) and a `coder_app` that attaches to the existing session (for example, with tmux). + +### MCP servers and rules + +```tf module "auggie" { source = "registry.coder.com/coder-labs/auggie/coder" - version = "0.3.1" - agent_id = coder_agent.example.id - folder = "/home/coder/project" - - # Authentication - augment_session_token = <<-EOF - {"accessToken":"xxxx-yyyy-zzzz-jjjj","tenantURL":"https://d1.api.augmentcode.com/","scopes":["read","write"]} -EOF # Required for tasks - - # Version - auggie_version = "0.2.2" - - # Task configuration - ai_prompt = data.coder_parameter.ai_prompt.value - continue_previous_conversation = true - interaction_mode = "quiet" - auggie_model = "gpt5" - report_tasks = true - - # MCP configuration for additional integrations - mcp = <<-EOF -{ - "mcpServers": { - "filesystem": { - "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-filesystem", "/home/coder/project"] + version = "1.0.0" + agent_id = coder_agent.main.id + + mcp = jsonencode({ + mcpServers = { + filesystem = { + command = "npx" + args = ["-y", "@modelcontextprotocol/server-filesystem", "/home/coder/project"] + } } - } -} -EOF + }) + mcp_files = ["/home/coder/.config/mcp/servers.json"] - # Workspace guidelines rules = <<-EOT # Project Guidelines - ## Code Style - - Use TypeScript for all new JavaScript files - - Follow consistent naming conventions - - Add comprehensive comments for complex logic + - Use TypeScript for all new JavaScript files. + - Write unit tests for all new functions. + EOT +} +``` + +`mcp` and `mcp_files` use the [`mcpServers`](https://docs.augmentcode.com/cli/integrations#mcp-integrations) format and are added with `auggie mcp add-json`; servers that already exist are kept. `rules` are loaded as [user rules](https://docs.augmentcode.com/cli/rules). To pick a default model, set `auggie_model` to a name from `auggie models list`. - ## Testing - - Write unit tests for all new functions - - Ensure test coverage above 80% +### Pinned version - ## Documentation - - Update README.md for any new features - - Document API changes in CHANGELOG.md - EOT +```tf +module "auggie" { + source = "registry.coder.com/coder-labs/auggie/coder" + version = "1.0.0" + agent_id = coder_agent.main.id + auggie_version = "0.36.0" } ``` -### Using Multiple MCP Configuration Files +Auggie updates itself in interactive mode unless [automatic updates](https://docs.augmentcode.com/cli/autoupgrade) are disabled. + +### Serialize a downstream `coder_script` after the install pipeline + +The module exposes the `scripts` output: an ordered list of `coder exp sync` names for the scripts this module creates (pre_install, install, post_install). Scripts that were not configured are absent. ```tf module "auggie" { source = "registry.coder.com/coder-labs/auggie/coder" - version = "0.3.1" - agent_id = coder_agent.example.id - folder = "/home/coder/project" - - # Multiple MCP configuration files - mcp_files = [ - "/path/to/filesystem-mcp.json", - "/path/to/database-mcp.json", - "/path/to/api-mcp.json" - ] - - mcp = <<-EOF - { - "mcpServers": { - "Test MCP": { - "command": "uv", - "args": [ - "--directory", - "/home/coder/test-mcp", - "run", - "server.py" - ], - "timeout": 600 - } - } + version = "1.0.0" + agent_id = coder_agent.main.id } -EOF + +resource "coder_script" "post_auggie" { + agent_id = coder_agent.main.id + display_name = "Run after Auggie install" + run_on_start = true + script = <<-EOT + #!/usr/bin/env bash + set -euo pipefail + trap 'coder exp sync complete post-auggie' EXIT + coder exp sync want post-auggie ${join(" ", module.auggie.scripts)} + coder exp sync start post-auggie + + auggie --version + EOT } ``` -### Troubleshooting +## Authentication -If you have any issues, please take a look at the log files below. +`augment_session_token` takes the session JSON printed by `auggie token print` and is exported as `AUGMENT_SESSION_AUTH`. Without it, run `auggie login` in the workspace. See [Auggie authentication](https://docs.augmentcode.com/cli/setup-auggie/authentication). -```bash -# Installation logs -cat ~/.auggie-module/install.log +## Troubleshooting -# Startup logs -cat ~/.auggie-module/agentapi-start.log +Check the log files in `~/.coder-modules/coder-labs/auggie/logs/` for detailed information. -# Pre/post install script logs -cat ~/.auggie-module/pre_install.log -cat ~/.auggie-module/post_install.log +```bash +cat ~/.coder-modules/coder-labs/auggie/logs/install.log +cat ~/.coder-modules/coder-labs/auggie/logs/pre_install.log +cat ~/.coder-modules/coder-labs/auggie/logs/post_install.log ``` -> [!NOTE] -> To use tasks with Auggie CLI, create a `coder_parameter` named `"AI Prompt"` and pass its value to the auggie module's `ai_prompt` variable. The `folder` variable is required for the module to function correctly. - ## References -- [Auggie CLI Reference](https://docs.augmentcode.com/cli/reference) -- [Auggie CLI MCP Integration](https://docs.augmentcode.com/cli/integrations#mcp-integrations) -- [Augment Code Documentation](https://docs.augmentcode.com/) -- [AgentAPI Documentation](https://github.com/coder/agentapi) -- [Coder AI Agents Guide](https://coder.com/docs/tutorials/ai-agents) +- [Auggie CLI documentation](https://docs.augmentcode.com/cli/overview) +- [Auggie CLI flags](https://docs.augmentcode.com/cli/reference) +- [Auggie MCP integrations](https://docs.augmentcode.com/cli/integrations) +- [Auggie rules](https://docs.augmentcode.com/cli/rules) diff --git a/registry/coder-labs/modules/auggie/auggie.tftest.hcl b/registry/coder-labs/modules/auggie/auggie.tftest.hcl deleted file mode 100644 index eae0107991..0000000000 --- a/registry/coder-labs/modules/auggie/auggie.tftest.hcl +++ /dev/null @@ -1,186 +0,0 @@ -run "test_auggie_basic" { - command = plan - - variables { - agent_id = "test-agent-123" - folder = "/home/coder/projects" - } - - assert { - condition = coder_env.auggie_session_auth.name == "AUGMENT_SESSION_AUTH" - error_message = "Auggie session auth environment variable should be set correctly" - } - - assert { - condition = var.folder == "/home/coder/projects" - error_message = "Folder variable should be set correctly" - } - - assert { - condition = var.agent_id == "test-agent-123" - error_message = "Agent ID variable should be set correctly" - } - - assert { - condition = var.install_auggie == true - error_message = "Install auggie should default to true" - } - - assert { - condition = var.install_agentapi == true - error_message = "Install agentapi should default to true" - } -} - -run "test_auggie_with_session_token" { - command = plan - - variables { - agent_id = "test-agent-456" - folder = "/home/coder/workspace" - augment_session_token = "test-session-token-123" - } - - assert { - condition = coder_env.auggie_session_auth.value == "test-session-token-123" - error_message = "Auggie session token value should match the input" - } -} - -run "test_auggie_with_custom_options" { - command = plan - - variables { - agent_id = "test-agent-789" - folder = "/home/coder/custom" - order = 5 - group = "development" - icon = "/icon/custom.svg" - auggie_model = "gpt-4" - ai_prompt = "Help me write better code" - interaction_mode = "compact" - continue_previous_conversation = true - install_auggie = false - install_agentapi = false - auggie_version = "1.0.0" - agentapi_version = "v0.6.0" - } - - assert { - condition = var.order == 5 - error_message = "Order variable should be set to 5" - } - - assert { - condition = var.group == "development" - error_message = "Group variable should be set to 'development'" - } - - assert { - condition = var.icon == "/icon/custom.svg" - error_message = "Icon variable should be set to custom icon" - } - - assert { - condition = var.auggie_model == "gpt-4" - error_message = "Auggie model variable should be set to 'gpt-4'" - } - - assert { - condition = var.ai_prompt == "Help me write better code" - error_message = "AI prompt variable should be set correctly" - } - - assert { - condition = var.interaction_mode == "compact" - error_message = "Interaction mode should be set to 'compact'" - } - - assert { - condition = var.continue_previous_conversation == true - error_message = "Continue previous conversation should be set to true" - } - - assert { - condition = var.auggie_version == "1.0.0" - error_message = "Auggie version should be set to '1.0.0'" - } - - assert { - condition = var.agentapi_version == "v0.6.0" - error_message = "AgentAPI version should be set to 'v0.6.0'" - } -} - -run "test_auggie_with_mcp_and_rules" { - command = plan - - variables { - agent_id = "test-agent-mcp" - folder = "/home/coder/mcp-test" - mcp = jsonencode({ - mcpServers = { - test = { - command = "test-server" - args = ["--config", "test.json"] - } - } - }) - mcp_files = [ - "/path/to/mcp1.json", - "/path/to/mcp2.json" - ] - rules = "# General coding rules\n- Write clean code\n- Add comments" - } - - assert { - condition = var.mcp != "" - error_message = "MCP configuration should be provided" - } - - assert { - condition = length(var.mcp_files) == 2 - error_message = "Should have 2 MCP files" - } - - assert { - condition = var.rules != "" - error_message = "Rules should be provided" - } -} - -run "test_auggie_with_scripts" { - command = plan - - variables { - agent_id = "test-agent-scripts" - folder = "/home/coder/scripts" - pre_install_script = "echo 'Pre-install script'" - post_install_script = "echo 'Post-install script'" - } - - assert { - condition = var.pre_install_script == "echo 'Pre-install script'" - error_message = "Pre-install script should be set correctly" - } - - assert { - condition = var.post_install_script == "echo 'Post-install script'" - error_message = "Post-install script should be set correctly" - } -} - -run "test_auggie_interaction_mode_validation" { - command = plan - - variables { - agent_id = "test-agent-validation" - folder = "/home/coder/test" - interaction_mode = "print" - } - - assert { - condition = contains(["interactive", "print", "quiet", "compact"], var.interaction_mode) - error_message = "Interaction mode should be one of the valid options" - } -} diff --git a/registry/coder-labs/modules/auggie/main.test.ts b/registry/coder-labs/modules/auggie/main.test.ts index adfb48d85e..3228392be3 100644 --- a/registry/coder-labs/modules/auggie/main.test.ts +++ b/registry/coder-labs/modules/auggie/main.test.ts @@ -6,15 +6,50 @@ import { beforeAll, expect, } from "bun:test"; -import { execContainer, readFileContainer, runTerraformInit } from "~test"; import { - loadTestFile, + execContainer, + readFileContainer, + removeContainer, + runContainer, + runTerraformApply, + runTerraformInit, + TerraformState, +} from "~test"; +import { + extractCoderEnvVars, writeExecutable, - setup as setupUtil, - execModuleScript, - expectAgentAPIStarted, } from "../../../coder/modules/agentapi/test-util"; -import dedent from "dedent"; +import path from "path"; + +interface ModuleScripts { + pre_install?: string; + install: string; + post_install?: string; +} + +const collectScripts = (state: TerraformState): ModuleScripts => { + const byDisplayName: Record = {}; + for (const resource of state.resources) { + if (resource.type !== "coder_script") continue; + for (const instance of resource.instances) { + const attrs = instance.attributes as Record; + const displayName = attrs.display_name as string | undefined; + const script = attrs.script as string | undefined; + if (displayName && script) { + byDisplayName[displayName] = script; + } + } + } + const scripts: ModuleScripts = { + pre_install: byDisplayName["Auggie: Pre-Install Script"], + install: byDisplayName["Auggie: Install Script"], + post_install: byDisplayName["Auggie: Post-Install Script"], + }; + if (!scripts.install) { + throw new Error("install script not found in terraform state"); + } + return scripts; +}; let cleanupFunctions: (() => Promise)[] = []; const registerCleanup = (cleanup: () => Promise) => { @@ -33,35 +68,130 @@ afterEach(async () => { }); interface SetupProps { - skipAgentAPIMock?: boolean; skipAuggieMock?: boolean; moduleVariables?: Record; - agentapiMockScript?: string; } -const setup = async (props?: SetupProps): Promise<{ id: string }> => { - const projectDir = "/home/coder/project"; - const { id } = await setupUtil({ - moduleDir: import.meta.dir, - moduleVariables: { - install_auggie: props?.skipAuggieMock ? "true" : "false", - install_agentapi: props?.skipAgentAPIMock ? "true" : "false", - folder: projectDir, - ...props?.moduleVariables, - }, - registerCleanup, - projectDir, - skipAgentAPIMock: props?.skipAgentAPIMock, - agentapiMockScript: props?.agentapiMockScript, +const projectDir = "/home/coder/project"; +const moduleDir = path.resolve(import.meta.dir); + +const setup = async ( + props?: SetupProps, +): Promise<{ + id: string; + coderEnvVars: Record; + scripts: ModuleScripts; +}> => { + const state = await runTerraformApply(moduleDir, { + agent_id: "foo", + workdir: projectDir, + install_auggie: "false", + ...props?.moduleVariables, + }); + const scripts = collectScripts(state); + const coderEnvVars = extractCoderEnvVars(state); + + const id = await runContainer("codercom/enterprise-node:latest"); + registerCleanup(async () => { + if (process.env["DEBUG"] === "true" || process.env["DEBUG"] === "1") { + console.log(`Not removing container ${id} in debug mode`); + return; + } + await removeContainer(id); + }); + + await writeExecutable({ + containerId: id, + filePath: "/usr/bin/coder", + content: "#!/bin/bash\nexit 0\n", }); if (!props?.skipAuggieMock) { await writeExecutable({ containerId: id, filePath: "/usr/bin/auggie", - content: await loadTestFile(import.meta.dir, "auggie-mock.sh"), + content: await auggieMock(), }); } - return { id }; + return { id, coderEnvVars, scripts }; +}; + +const auggieMock = () => + Bun.file(path.join(moduleDir, "testdata", "auggie-mock.sh")).text(); + +// coder_env values reach coder_script processes through the agent environment. +const envArgs = (env: Record) => + Object.entries(env).flatMap(([key, value]) => ["--env", `${key}=${value}`]); + +const runScript = async ( + id: string, + name: string, + script: string, + env: Record = {}, +) => { + const target = `/tmp/coder-utils-${name}.sh`; + await writeExecutable({ containerId: id, filePath: target, content: script }); + return execContainer(id, ["bash", "-c", target], envArgs(env)); +}; + +const runScripts = async ( + id: string, + scripts: ModuleScripts, + env: Record = {}, +) => { + const ordered: [string, string | undefined][] = [ + ["pre_install", scripts.pre_install], + ["install", scripts.install], + ["post_install", scripts.post_install], + ]; + for (const [name, script] of ordered) { + if (!script) continue; + const resp = await runScript(id, name, script, env); + if (resp.exitCode !== 0) { + console.log(`script ${name} failed:`); + console.log(resp.stdout); + console.log(resp.stderr); + throw new Error(`coder-utils ${name} script exited ${resp.exitCode}`); + } + } +}; + +const moduleLogDir = "/home/coder/.coder-modules/coder-labs/auggie/logs"; +const installLog = (id: string) => + readFileContainer(id, `${moduleLogDir}/install.log`); +const settingsPath = "/home/coder/.augment/settings.json"; +const rulesPath = "/home/coder/.augment/rules/coder-module.md"; + +const seedFile = async (id: string, filePath: string, content: string) => { + const encoded = Buffer.from(content).toString("base64"); + await execContainer(id, [ + "bash", + "-c", + `mkdir -p "$(dirname '${filePath}')" && echo '${encoded}' | base64 -d > '${filePath}'`, + ]); +}; + +// Replaces npm with a stub that records its arguments and installs the +// Auggie mock into the npm-global prefix. +const mockNpm = async (id: string) => { + await writeExecutable({ + containerId: id, + filePath: "/tmp/auggie-mock-fixture.sh", + content: await auggieMock(), + }); + await writeExecutable({ + containerId: id, + filePath: "/usr/local/bin/npm", + content: [ + "#!/bin/bash", + 'echo "$*" >> /tmp/npm-calls', + 'if [ "$1" = "--version" ]; then echo 11.0.0; fi', + 'if [ "$1" = "install" ]; then', + ' mkdir -p "$HOME/.npm-global/bin"', + ' cp /tmp/auggie-mock-fixture.sh "$HOME/.npm-global/bin/auggie"', + ' chmod +x "$HOME/.npm-global/bin/auggie"', + "fi", + ].join("\n"), + }); }; setDefaultTimeout(60 * 1000); @@ -71,278 +201,204 @@ describe("auggie", async () => { await runTerraformInit(import.meta.dir); }); - test("happy-path", async () => { - const { id } = await setup(); - await execModuleScript(id); - await expectAgentAPIStarted(id); + test("happy-path-validates-existing-binary", async () => { + const { id, scripts } = await setup(); + await runScripts(id, scripts); + const log = await installLog(id); + expect(log).toContain("Skipping Auggie CLI installation"); + expect(log).toContain("Validated existing Auggie CLI"); + expect(log).toContain("Auggie CLI version: 0.36.0 (commit mock)"); + expect(log).toContain("Auggie module setup completed."); + expect(log).not.toContain("agentapi"); + const settings = await execContainer(id, ["test", "-e", settingsPath]); + expect(settings.exitCode).not.toBe(0); + const rules = await execContainer(id, ["test", "-e", rulesPath]); + expect(rules.exitCode).not.toBe(0); }); - test("install-auggie-version", async () => { - const version_to_install = "0.3.0"; - const { id } = await setup({ - skipAuggieMock: true, - moduleVariables: { - install_auggie: "true", - auggie_version: version_to_install, - pre_install_script: dedent` - #!/usr/bin/env bash - set -euo pipefail - - # Install Node.js and npm via system package manager - if ! command -v node >/dev/null 2>&1; then - sudo apt-get update - sudo apt-get install -y nodejs npm - fi - - # Configure npm to use user directory (avoids permission issues) - mkdir -p "$HOME/.npm-global" - npm config set prefix "$HOME/.npm-global" - - # Persist npm user directory configuration - echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> ~/.bashrc - echo "prefix=$HOME/.npm-global" > ~/.npmrc - `, - }, - }); - await execModuleScript(id); - const resp = await execContainer(id, [ - "bash", - "-c", - `cat /home/coder/.auggie-module/install.log`, - ]); - expect(resp.stdout).toContain(version_to_install); + test("preinstalled-binary-required-when-install-disabled", async () => { + const { id, scripts } = await setup({ skipAuggieMock: true }); + const resp = await runScript(id, "install", scripts.install); + expect(resp.exitCode).not.toBe(0); + const log = await installLog(id); + expect(log).toContain("was not found or is not executable"); }); - test("check-latest-auggie-version-works", async () => { - const { id } = await setup({ + test("npm-installs-pinned-version", async () => { + const { id, scripts } = await setup({ skipAuggieMock: true, - skipAgentAPIMock: true, - moduleVariables: { - install_auggie: "true", - pre_install_script: dedent` - #!/usr/bin/env bash - set -euo pipefail - - # Install Node.js and npm via system package manager - if ! command -v node >/dev/null 2>&1; then - sudo apt-get update - sudo apt-get install -y nodejs npm - fi - - # Configure npm to use user directory (avoids permission issues) - mkdir -p "$HOME/.npm-global" - npm config set prefix "$HOME/.npm-global" - - # Persist npm user directory configuration - echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> ~/.bashrc - echo "prefix=$HOME/.npm-global" > ~/.npmrc - `, - }, + moduleVariables: { install_auggie: "true", auggie_version: "0.35.0" }, }); - await execModuleScript(id); - await expectAgentAPIStarted(id); + await mockNpm(id); + await execContainer(id, ["mkdir", "-p", "/tmp/coder-bin"]); + await runScripts(id, scripts, { CODER_SCRIPT_BIN_DIR: "/tmp/coder-bin" }); + const calls = await readFileContainer(id, "/tmp/npm-calls"); + expect(calls).toContain("config set prefix /home/coder/.npm-global"); + expect(calls).toContain("install -g @augmentcode/auggie@0.35.0"); + const log = await installLog(id); + expect(log).toContain("Installed Auggie CLI"); + const link = await execContainer(id, ["readlink", "/tmp/coder-bin/auggie"]); + expect(link.stdout.trim()).toBe("/home/coder/.npm-global/bin/auggie"); + const bashrc = await readFileContainer(id, "/home/coder/.bashrc"); + expect(bashrc).toContain("/home/coder/.npm-global/bin"); }); - test("auggie-session-token", async () => { - const sessionToken = "test-session-token-123"; - const { id } = await setup({ - moduleVariables: { - augment_session_token: sessionToken, - }, + test("npm-installs-latest-by-default", async () => { + const { id, scripts } = await setup({ + skipAuggieMock: true, + moduleVariables: { install_auggie: "true" }, }); - await execModuleScript(id); - - const envCheck = await execContainer(id, [ - "bash", - "-c", - `env | grep AUGMENT_SESSION_AUTH || echo "AUGMENT_SESSION_AUTH not found"`, - ]); - expect(envCheck.stdout).toContain("AUGMENT_SESSION_AUTH"); + await mockNpm(id); + await runScripts(id, scripts); + const calls = await readFileContainer(id, "/tmp/npm-calls"); + expect(calls).toMatch(/install -g @augmentcode\/auggie$/m); }); - test("auggie-mcp-config", async () => { - const mcpConfig = JSON.stringify({ - mcpServers: { - test: { - command: "test-cmd", - type: "stdio", - }, - }, + test("npm-failure-is-terminal", async () => { + const { id, scripts } = await setup({ + skipAuggieMock: true, + moduleVariables: { install_auggie: "true" }, }); - const { id } = await setup({ - moduleVariables: { - mcp: mcpConfig, - }, + await writeExecutable({ + containerId: id, + filePath: "/usr/local/bin/npm", + content: + '#!/bin/bash\nif [ "$1" = "install" ]; then exit 1; fi\necho 11.0.0\n', }); - await execModuleScript(id); + const resp = await runScript(id, "install", scripts.install); + expect(resp.exitCode).not.toBe(0); + expect(await installLog(id)).toContain("Auggie CLI installation failed."); + }); - const resp = await readFileContainer( - id, - "/home/coder/.auggie-module/agentapi-start.log", - ); - expect(resp).toContain("--mcp-config"); + test("workdir-is-created", async () => { + const { id, scripts } = await setup(); + await runScripts(id, scripts); + const dir = await execContainer(id, ["test", "-d", projectDir]); + expect(dir.exitCode).toBe(0); }); - test("auggie-rules", async () => { - const rules = "Always use TypeScript for new files"; - const { id } = await setup({ + test("mcp-and-mcp-files-existing-servers-win", async () => { + const { id, scripts } = await setup({ moduleVariables: { - install_auggie: "false", // Don't need to install auggie to test rules file creation - rules: rules, + mcp: JSON.stringify({ + mcpServers: { + shared: { command: "module-command" }, + extra: { command: "extra-command", args: ["--flag"] }, + }, + }), + mcp_files: JSON.stringify([ + "/home/coder/mcp-a.json", + "/home/coder/missing.json", + ]), }, }); - await execModuleScript(id); - - const rulesFile = await readFileContainer( + await seedFile( id, - "/home/coder/.augment/rules.md", + "/home/coder/mcp-a.json", + JSON.stringify({ + mcpServers: { + fromfile: { type: "http", url: "https://mcp.example.com/mcp" }, + extra: { command: "file-command" }, + }, + }), ); - expect(rulesFile).toContain(rules); - }); - - test("auggie-ai-task-prompt", async () => { - const prompt = "This is a task prompt for Auggie."; - const { id } = await setup({ - moduleVariables: { - ai_prompt: prompt, - }, - }); - await execModuleScript(id); - - const resp = await execContainer(id, [ - "bash", - "-c", - `cat /home/coder/.auggie-module/agentapi-start.log`, - ]); - expect(resp.stdout).toContain(prompt); - }); - - test("auggie-interaction-mode", async () => { - const mode = "compact"; - const { id } = await setup({ - moduleVariables: { - interaction_mode: mode, - ai_prompt: "test prompt", - }, - }); - await execModuleScript(id); - - const startLog = await execContainer(id, [ - "bash", - "-c", - "cat /home/coder/.auggie-module/agentapi-start.log", - ]); - expect(startLog.stdout).toContain(`--${mode}`); - }); - - test("auggie-model", async () => { - const model = "gpt-4"; - const { id } = await setup({ - moduleVariables: { - auggie_model: model, - ai_prompt: "test prompt", - }, - }); - await execModuleScript(id); - - const startLog = await execContainer(id, [ - "bash", - "-c", - "cat /home/coder/.auggie-module/agentapi-start.log", - ]); - expect(startLog.stdout).toContain(`--model ${model}`); + await seedFile( + id, + settingsPath, + JSON.stringify({ + theme: "ansi", + mcpServers: { shared: { command: "existing-command" } }, + }), + ); + await runScripts(id, scripts); + const settings = JSON.parse(await readFileContainer(id, settingsPath)); + expect(settings.theme).toBe("ansi"); + expect(settings.mcpServers.shared.command).toBe("existing-command"); + expect(settings.mcpServers.extra.command).toBe("extra-command"); + expect(settings.mcpServers.fromfile.url).toBe( + "https://mcp.example.com/mcp", + ); + expect(settings.mcpServers.coder).toBeUndefined(); + const log = await installLog(id); + expect(log).toContain( + "MCP server 'shared' already exists in /home/coder/.augment/settings.json, keeping the existing one.", + ); + expect(log).toContain("MCP server 'extra' already exists"); + expect(log).toContain("Added MCP server 'fromfile'"); + expect(log).toContain( + "MCP file /home/coder/missing.json is missing or not valid JSON", + ); + expect(log).not.toContain("Saved MCP server"); }); - test("auggie-continue-previous-conversation", async () => { - const { id } = await setup({ - moduleVariables: { - continue_previous_conversation: "true", - ai_prompt: "test prompt", - }, - }); - await execModuleScript(id); - - const startLog = await execContainer(id, [ - "bash", - "-c", - "cat /home/coder/.auggie-module/agentapi-start.log", - ]); - expect(startLog.stdout).toContain("--continue"); + test("rules-are-written-to-user-rules", async () => { + const rules = "# Project Guidelines\n\n- Use TypeScript"; + const { id, scripts } = await setup({ moduleVariables: { rules } }); + await runScripts(id, scripts); + expect((await readFileContainer(id, rulesPath)).trim()).toBe(rules); }); - test("pre-post-install-scripts", async () => { - const { id } = await setup({ - moduleVariables: { - pre_install_script: "#!/bin/bash\necho 'auggie-pre-install-script'", - post_install_script: "#!/bin/bash\necho 'auggie-post-install-script'", - }, + test("model-is-set-preserving-settings", async () => { + const { id, scripts } = await setup({ + moduleVariables: { auggie_model: "sonnet4.5" }, }); - await execModuleScript(id); - - const preInstallLog = await readFileContainer( - id, - "/home/coder/.auggie-module/pre_install.log", - ); - expect(preInstallLog).toContain("auggie-pre-install-script"); - - const postInstallLog = await readFileContainer( + await seedFile( id, - "/home/coder/.auggie-module/post_install.log", + settingsPath, + JSON.stringify({ model: "gpt5", autoUpdate: false }), ); - expect(postInstallLog).toContain("auggie-post-install-script"); + await runScripts(id, scripts); + const settings = JSON.parse(await readFileContainer(id, settingsPath)); + expect(settings.model).toBe("sonnet4.5"); + expect(settings.autoUpdate).toBe(false); }); - test("folder-variable", async () => { - const folder = "/home/coder/auggie-test-folder"; - const { id } = await setup({ - skipAuggieMock: false, - moduleVariables: { - folder, - }, + test("jsonc-settings-are-left-unchanged-for-model", async () => { + const { id, scripts } = await setup({ + moduleVariables: { auggie_model: "sonnet4.5" }, }); - await execModuleScript(id); - - const resp = await readFileContainer( - id, - "/home/coder/.auggie-module/agentapi-start.log", + const jsonc = '{\n // user comment\n "theme": "ansi",\n}\n'; + await seedFile(id, settingsPath, jsonc); + await runScripts(id, scripts); + expect(await readFileContainer(id, settingsPath)).toBe(jsonc); + expect(await installLog(id)).toContain( + "is not a strict JSON object (comments or trailing commas); leaving the model unchanged.", ); - expect(resp).toContain(folder); }); - test("coder-mcp-config-created", async () => { - const { id } = await setup({ + test("session-token-uses-coder-env-without-leaking", async () => { + const secret = "test-access-token-123"; + const { id, coderEnvVars, scripts } = await setup({ moduleVariables: { - install_auggie: "false", // Don't need to install auggie to test MCP config creation + augment_session_token: JSON.stringify({ + accessToken: secret, + tenantURL: "https://d1.api.augmentcode.com/", + scopes: ["read", "write"], + }), }, }); - await execModuleScript(id); - - const mcpConfig = await readFileContainer( - id, - "/home/coder/.augment/coder_mcp.json", + expect(coderEnvVars["AUGMENT_SESSION_AUTH"]).toContain(secret); + expect(scripts.install).not.toContain(secret); + expect(scripts.install).not.toContain( + Buffer.from(coderEnvVars["AUGMENT_SESSION_AUTH"]).toString("base64"), ); - expect(mcpConfig).toContain("mcpServers"); - expect(mcpConfig).toContain("coder"); - expect(mcpConfig).toContain("CODER_MCP_APP_STATUS_SLUG"); - expect(mcpConfig).toContain("CODER_MCP_AI_AGENTAPI_URL"); + await runScripts(id, scripts, coderEnvVars); + expect(await installLog(id)).not.toContain(secret); }); - test("mcp-files-array", async () => { - const mcpFiles = ["/path/to/mcp1.json", "/path/to/mcp2.json"]; - const { id } = await setup({ + test("pre-post-install-scripts", async () => { + const { id, scripts } = await setup({ moduleVariables: { - mcp_files: JSON.stringify(mcpFiles), - ai_prompt: "test prompt", + pre_install_script: "#!/bin/bash\necho 'auggie-pre-install-script'", + post_install_script: "#!/bin/bash\necho 'auggie-post-install-script'", }, }); - await execModuleScript(id); - - const startLog = await execContainer(id, [ - "bash", - "-c", - "cat /home/coder/.auggie-module/agentapi-start.log", - ]); - expect(startLog.stdout).toContain("mcp1.json"); - expect(startLog.stdout).toContain("mcp2.json"); + await runScripts(id, scripts); + expect( + await readFileContainer(id, `${moduleLogDir}/pre_install.log`), + ).toContain("auggie-pre-install-script"); + expect( + await readFileContainer(id, `${moduleLogDir}/post_install.log`), + ).toContain("auggie-post-install-script"); }); }); diff --git a/registry/coder-labs/modules/auggie/main.tf b/registry/coder-labs/modules/auggie/main.tf index 6a1d03d93f..f0abee5fdc 100644 --- a/registry/coder-labs/modules/auggie/main.tf +++ b/registry/coder-labs/modules/auggie/main.tf @@ -14,63 +14,28 @@ variable "agent_id" { description = "The ID of a Coder agent." } -data "coder_workspace" "me" {} - -data "coder_workspace_owner" "me" {} - -variable "order" { - type = number - description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)." - 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." + description = "The icon to use for the install scripts." default = "/icon/auggie.svg" } -variable "folder" { +variable "workdir" { type = string - description = "The folder to run Auggie in." + description = "Optional project directory. When set, the module creates it if missing." + default = null } variable "install_auggie" { type = bool - description = "Whether to install Auggie CLI." + description = "Whether to install Auggie CLI with npm. When false, a working auggie must already be on PATH." default = true } variable "auggie_version" { type = string - description = "The version of Auggie to install." - default = "" # empty string means the latest available version - validation { - condition = var.auggie_version == "" || can(regex("^v?[0-9]+\\.[0-9]+\\.[0-9]+", var.auggie_version)) - error_message = "auggie_version must be empty (for latest) or a valid semantic version like 'v1.2.3' or '1.2.3'." - } -} - -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" - 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'." - } + description = "The version of Auggie CLI to install. Empty installs the latest version." + default = "" } variable "pre_install_script" { @@ -85,152 +50,72 @@ variable "post_install_script" { default = null } -# ---------------------------------------------- - -variable "ai_prompt" { - type = string - description = "Task prompt for the Auggie CLI" - default = "" -} - variable "mcp" { type = string - description = "MCP configuration as a JSON string for the auggie cli, check https://docs.augmentcode.com/cli/integrations#mcp-integrations" + description = "MCP servers as a JSON string ({\"mcpServers\": {...}}), added to ~/.augment/settings.json with `auggie mcp add-json`. Servers already configured win on duplicate names. See https://docs.augmentcode.com/cli/integrations#mcp-integrations" default = "" } variable "mcp_files" { type = list(string) - description = "MCP configuration from a JSON file for the auggie cli, check https://docs.augmentcode.com/cli/integrations#mcp-integrations" + description = "Paths to MCP configuration JSON files in the workspace, added the same way as mcp. See https://docs.augmentcode.com/cli/integrations#mcp-integrations" default = [] } variable "rules" { type = string - description = "Additional rules to append to workspace guidelines (markdown format)" + description = "Rules in markdown, written to ~/.augment/rules/coder-module.md so they apply to every Auggie session. See https://docs.augmentcode.com/cli/rules" default = "" } -variable "continue_previous_conversation" { - type = bool - description = "Whether to resume the previous conversation." - default = false -} - -variable "interaction_mode" { - type = string - description = "Interaction mode with the Auggie CLI. Options: interactive, print, quiet, compact. https://docs.augmentcode.com/cli/reference#cli-flags" - default = "interactive" - validation { - condition = contains(["interactive", "print", "quiet", "compact"], var.interaction_mode) - error_message = "interaction_mode must be one of: interactive, print, quiet, compact." - } -} - variable "augment_session_token" { type = string - description = "Auggie session token for authentication. https://docs.augmentcode.com/cli/setup-auggie/authentication" + description = "Augment session JSON from `auggie token print`, exported as AUGMENT_SESSION_AUTH. See https://docs.augmentcode.com/cli/setup-auggie/authentication" + sensitive = true default = "" } variable "auggie_model" { type = string - description = "The model to use for Auggie, find available models using auggie --list-models" + description = "Default model, written to the model key of ~/.augment/settings.json. Find available models with `auggie models list`." default = "" } -variable "report_tasks" { - type = bool - description = "Whether to enable task reporting to Coder UI via AgentAPI" - default = false -} - -variable "cli_app" { - type = bool - description = "Whether to create a CLI app for Auggie" - default = false -} - -variable "web_app_display_name" { - type = string - description = "Display name for the web app" - default = "Auggie" -} - -variable "cli_app_display_name" { - type = string - description = "Display name for the CLI app" - default = "Auggie CLI" -} - resource "coder_env" "auggie_session_auth" { + count = var.augment_session_token != "" ? 1 : 0 agent_id = var.agent_id name = "AUGMENT_SESSION_AUTH" value = var.augment_session_token } locals { - app_slug = "auggie" - install_script = file("${path.module}/scripts/install.sh") - start_script = file("${path.module}/scripts/start.sh") - module_dir_name = ".auggie-module" - folder = trimsuffix(var.folder, "/") -} - -module "agentapi" { - source = "registry.coder.com/coder/agentapi/coder" - version = "2.0.0" - - agent_id = var.agent_id - folder = local.folder - 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 - 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 - #!/usr/bin/env bash - set -o errexit - set -o pipefail - - echo -n '${base64encode(local.start_script)}' | base64 -d > /tmp/start.sh - chmod +x /tmp/start.sh - ARG_AUGGIE_START_DIRECTORY='${var.folder}' \ - ARG_TASK_PROMPT='${base64encode(var.ai_prompt)}' \ - ARG_MCP_FILES='${jsonencode(var.mcp_files)}' \ - ARG_AUGGIE_RULES='${base64encode(var.rules)}' \ - ARG_AUGGIE_CONTINUE_PREVIOUS_CONVERSATION='${var.continue_previous_conversation}' \ - ARG_AUGGIE_INTERACTION_MODE='${var.interaction_mode}' \ - ARG_AUGMENT_SESSION_AUTH='${var.augment_session_token}' \ - ARG_AUGGIE_MODEL='${var.auggie_model}' \ - ARG_REPORT_TASKS='${var.report_tasks}' \ - /tmp/start.sh - EOT - - install_script = <<-EOT - #!/usr/bin/env bash - set -o errexit - set -o pipefail - - echo -n '${base64encode(local.install_script)}' | base64 -d > /tmp/install.sh - chmod +x /tmp/install.sh - ARG_AUGGIE_INSTALL='${var.install_auggie}' \ - ARG_AUGGIE_VERSION='${var.auggie_version}' \ - ARG_MCP_APP_STATUS_SLUG='${local.app_slug}' \ - ARG_AUGGIE_RULES='${base64encode(var.rules)}' \ - ARG_MCP_CONFIG='${var.mcp != null ? base64encode(replace(var.mcp, "'", "'\\''")) : ""}' \ - /tmp/install.sh - EOT -} - -output "task_app_id" { - value = module.agentapi.task_app_id + workdir = var.workdir != null ? trimsuffix(var.workdir, "/") : "" + install_script = templatefile("${path.module}/scripts/install.sh.tftpl", { + ARG_INSTALL = tostring(var.install_auggie) + ARG_AUGGIE_VERSION = var.auggie_version != "" ? base64encode(var.auggie_version) : "" + ARG_WORKDIR = local.workdir != "" ? base64encode(local.workdir) : "" + ARG_MCP_CONFIG = var.mcp != "" ? base64encode(var.mcp) : "" + ARG_MCP_FILES = length(var.mcp_files) > 0 ? base64encode(jsonencode(var.mcp_files)) : "" + ARG_RULES = var.rules != "" ? base64encode(var.rules) : "" + ARG_AUGGIE_MODEL = var.auggie_model != "" ? base64encode(var.auggie_model) : "" + }) + module_dir_name = ".coder-modules/coder-labs/auggie" +} + +module "coder_utils" { + source = "registry.coder.com/coder/coder-utils/coder" + version = "0.0.1" + + agent_id = var.agent_id + module_directory = "$HOME/${local.module_dir_name}" + display_name_prefix = "Auggie" + icon = var.icon + pre_install_script = var.pre_install_script + post_install_script = var.post_install_script + install_script = local.install_script +} + +output "scripts" { + description = "Ordered list of coder exp sync names for the coder_script resources this module creates, in run order (pre_install, install, post_install). Scripts that were not configured are absent from the list." + value = module.coder_utils.scripts } diff --git a/registry/coder-labs/modules/auggie/main.tftest.hcl b/registry/coder-labs/modules/auggie/main.tftest.hcl new file mode 100644 index 0000000000..ef00931d48 --- /dev/null +++ b/registry/coder-labs/modules/auggie/main.tftest.hcl @@ -0,0 +1,136 @@ +run "defaults_are_correct" { + command = plan + + variables { + agent_id = "test-agent" + } + + assert { + condition = var.install_auggie == true + error_message = "install_auggie should default to true" + } + + assert { + condition = var.auggie_version == "" + error_message = "auggie_version should default to empty (latest)" + } + + assert { + condition = local.workdir == "" + error_message = "workdir should be empty by default" + } + + assert { + condition = local.module_dir_name == ".coder-modules/coder-labs/auggie" + error_message = "module_dir_name should be '.coder-modules/coder-labs/auggie'" + } + + assert { + condition = length(coder_env.auggie_session_auth) == 0 + error_message = "AUGMENT_SESSION_AUTH should not be created when augment_session_token is empty" + } +} + +run "workdir_trailing_slash_is_trimmed" { + command = plan + + variables { + agent_id = "test-agent" + workdir = "/home/coder/project/" + } + + assert { + condition = local.workdir == "/home/coder/project" + error_message = "workdir should have its trailing slash trimmed" + } + + assert { + condition = strcontains(local.install_script, base64encode("/home/coder/project")) + error_message = "install script should receive the base64-encoded workdir" + } +} + +run "session_token_creates_env_var" { + command = plan + + variables { + agent_id = "test-agent" + augment_session_token = "{\"accessToken\":\"test-secret-token\",\"tenantURL\":\"https://d1.api.augmentcode.com/\"}" + } + + assert { + condition = coder_env.auggie_session_auth[0].name == "AUGMENT_SESSION_AUTH" && coder_env.auggie_session_auth[0].value == var.augment_session_token + error_message = "AUGMENT_SESSION_AUTH should be set to augment_session_token" + } + + assert { + condition = !strcontains(local.install_script, "test-secret-token") && !strcontains(local.install_script, base64encode(nonsensitive(var.augment_session_token))) + error_message = "augment_session_token should not be rendered into the install script" + } +} + +run "inputs_are_encoded" { + command = plan + + variables { + agent_id = "test-agent" + auggie_version = "0.35.0" + auggie_model = "sonnet4.5" + rules = "# Rules\n- Be concise" + mcp = "{\"mcpServers\": {\"filesystem\": {\"command\": \"npx\", \"args\": [\"-y\", \"@modelcontextprotocol/server-filesystem\"]}}}" + mcp_files = ["/home/coder/mcp.json"] + } + + assert { + condition = strcontains(local.install_script, base64encode("0.35.0")) + error_message = "install script should receive the base64-encoded version" + } + + assert { + condition = strcontains(local.install_script, base64encode("sonnet4.5")) + error_message = "install script should receive the base64-encoded model" + } + + assert { + condition = strcontains(local.install_script, base64encode(var.rules)) + error_message = "install script should receive the base64-encoded rules" + } + + assert { + condition = strcontains(local.install_script, base64encode(var.mcp)) + error_message = "install script should receive the base64-encoded mcp" + } + + assert { + condition = strcontains(local.install_script, base64encode(jsonencode(var.mcp_files))) + error_message = "install script should receive the base64-encoded mcp_files" + } +} + +run "scripts_output_is_ordered" { + command = plan + + variables { + agent_id = "test-agent" + pre_install_script = "echo pre" + post_install_script = "echo post" + } + + assert { + condition = output.scripts == ["coder-labs-auggie-pre_install_script", "coder-labs-auggie-install_script", "coder-labs-auggie-post_install_script"] + error_message = "scripts output should be ordered pre_install, install, post_install" + } +} + +run "scripts_output_install_only" { + command = plan + + variables { + agent_id = "test-agent" + } + + assert { + condition = output.scripts == ["coder-labs-auggie-install_script"] + error_message = "scripts output should only contain install when no pre/post scripts are set" + } +} diff --git a/registry/coder-labs/modules/auggie/scripts/install.sh b/registry/coder-labs/modules/auggie/scripts/install.sh deleted file mode 100644 index 285a5e2aa3..0000000000 --- a/registry/coder-labs/modules/auggie/scripts/install.sh +++ /dev/null @@ -1,128 +0,0 @@ -#!/usr/bin/env bash - -if [ -f "$HOME/.bashrc" ]; then - source "$HOME"/.bashrc -fi - -set -euo pipefail - -BOLD='\033[0;1m' - -# Function to check if a command exists -command_exists() { - command -v "$1" > /dev/null 2>&1 -} - -ARG_AUGGIE_INSTALL=${ARG_AUGGIE_INSTALL:-true} -ARG_AUGGIE_VERSION=${ARG_AUGGIE_VERSION:-} -ARG_MCP_APP_STATUS_SLUG=${ARG_MCP_APP_STATUS_SLUG:-} -ARG_AUGGIE_RULES=$(echo -n "${ARG_AUGGIE_RULES:-}" | base64 -d) -ARG_MCP_CONFIG=${ARG_MCP_CONFIG:-} - -echo "--------------------------------" - -printf "install auggie: %s\n" "$ARG_AUGGIE_INSTALL" -printf "auggie_version: %s\n" "$ARG_AUGGIE_VERSION" -printf "app_slug: %s\n" "$ARG_MCP_APP_STATUS_SLUG" -printf "rules: %s\n" "$ARG_AUGGIE_RULES" - -echo "--------------------------------" - -function check_dependencies() { - if ! command_exists node; then - printf "Error: Node.js is not installed. Please install Node.js manually or use the pre_install_script to install it.\n" - exit 1 - fi - - if ! command_exists npm; then - printf "Error: npm is not installed. Please install npm manually or use the pre_install_script to install it.\n" - exit 1 - fi - - printf "Node.js version: %s\n" "$(node --version)" - printf "npm version: %s\n" "$(npm --version)" -} - -function install_auggie() { - if [ "${ARG_AUGGIE_INSTALL}" = "true" ]; then - check_dependencies - - printf "%s Installing Auggie CLI\n" "${BOLD}" - - NPM_GLOBAL_PREFIX="${HOME}/.npm-global" - if [ ! -d "$NPM_GLOBAL_PREFIX" ]; then - mkdir -p "$NPM_GLOBAL_PREFIX" - fi - - npm config set prefix "$NPM_GLOBAL_PREFIX" - - export PATH="$NPM_GLOBAL_PREFIX/bin:$PATH" - - if [ -n "$ARG_AUGGIE_VERSION" ]; then - npm install -g "@augmentcode/auggie@$ARG_AUGGIE_VERSION" - else - npm install -g "@augmentcode/auggie" - fi - - if ! grep -q "export PATH=\"\$HOME/.npm-global/bin:\$PATH\"" "$HOME/.bashrc"; then - echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> "$HOME/.bashrc" - fi - - printf "%s Successfully installed Auggie CLI. Version: %s\n" "${BOLD}" "$(auggie --version)" - else - printf "Skipping Auggie CLI installation (install_auggie=false)\n" - fi -} - -function create_coder_mcp() { - AUGGIE_CODER_MCP_FILE="$HOME/.augment/coder_mcp.json" - CODER_MCP=$( - cat << EOF -{ - "mcpServers":{ - "coder": { - "args": ["exp", "mcp", "server"], - "command": "coder", - "env": { - "CODER_MCP_APP_STATUS_SLUG": "${ARG_MCP_APP_STATUS_SLUG}", - "CODER_MCP_AI_AGENTAPI_URL": "http://localhost:3284", - "CODER_AGENT_URL": "${CODER_AGENT_URL:-}", - "CODER_AGENT_TOKEN": "${CODER_AGENT_TOKEN:-}" - } - } - } -} -EOF - ) - mkdir -p "$(dirname "$AUGGIE_CODER_MCP_FILE")" - echo "$CODER_MCP" > "$AUGGIE_CODER_MCP_FILE" - printf "Coder MCP config created at: %s\n" "$AUGGIE_CODER_MCP_FILE" -} - -function create_user_mcp() { - if [ -n "$ARG_MCP_CONFIG" ]; then - USER_MCP_CONFIG_FILE="$HOME/.augment/user_mcp.json" - USER_MCP_CONTENT=$(echo -n "$ARG_MCP_CONFIG" | base64 -d) - mkdir -p "$(dirname "$USER_MCP_CONFIG_FILE")" - echo "$USER_MCP_CONTENT" > "$USER_MCP_CONFIG_FILE" - printf "User MCP config created at: %s\n" "$USER_MCP_CONFIG_FILE" - else - printf "No user MCP config provided, skipping user MCP config creation.\n" - fi -} - -function create_rules_file() { - AUGGIE_RULES_FILE="$HOME/.augment/rules.md" - if [ -n "$ARG_AUGGIE_RULES" ]; then - mkdir -p "$(dirname "$AUGGIE_RULES_FILE")" - echo -n "$ARG_AUGGIE_RULES" > "$AUGGIE_RULES_FILE" - printf "Rules file created at: %s\n" "$AUGGIE_RULES_FILE" - else - printf "No rules provided, skipping rules file creation.\n" - fi -} - -install_auggie -create_coder_mcp -create_user_mcp -create_rules_file diff --git a/registry/coder-labs/modules/auggie/scripts/install.sh.tftpl b/registry/coder-labs/modules/auggie/scripts/install.sh.tftpl new file mode 100644 index 0000000000..396080c07a --- /dev/null +++ b/registry/coder-labs/modules/auggie/scripts/install.sh.tftpl @@ -0,0 +1,224 @@ +#!/usr/bin/env bash + +if [ -f "$HOME/.bashrc" ]; then + # shellcheck source=/dev/null + source "$HOME"/.bashrc +fi + +set -euo pipefail + +command_exists() { + command -v "$1" > /dev/null 2>&1 +} + +ARG_INSTALL='${ARG_INSTALL}' +ARG_AUGGIE_VERSION=$(echo -n '${ARG_AUGGIE_VERSION}' | base64 -d) +ARG_WORKDIR=$(echo -n '${ARG_WORKDIR}' | base64 -d) +ARG_MCP_CONFIG=$(echo -n '${ARG_MCP_CONFIG}' | base64 -d) +ARG_MCP_FILES=$(echo -n '${ARG_MCP_FILES}' | base64 -d) +ARG_RULES=$(echo -n '${ARG_RULES}' | base64 -d) +ARG_AUGGIE_MODEL=$(echo -n '${ARG_AUGGIE_MODEL}' | base64 -d) + +NPM_GLOBAL_PREFIX="$HOME/.npm-global" +export PATH="$${NPM_GLOBAL_PREFIX}/bin:$PATH" + +SETTINGS_FILE="$HOME/.augment/settings.json" +RULES_FILE="$HOME/.augment/rules/coder-module.md" + +echo "--------------------------------" +printf "install_auggie: %s\n" "$${ARG_INSTALL}" +printf "auggie_version: %s\n" "$${ARG_AUGGIE_VERSION:-latest}" +printf "workdir: %s\n" "$${ARG_WORKDIR}" +printf "auggie_model: %s\n" "$${ARG_AUGGIE_MODEL}" +echo "--------------------------------" + +require_jq() { + if ! command_exists jq; then + echo "ERROR: 'jq' is required to $1 but was not found." >&2 + exit 1 + fi +} + +function add_path_to_shell_profiles() { + local path_dir="$1" + + for profile in "$HOME/.profile" "$HOME/.bash_profile" "$HOME/.bashrc" "$HOME/.zprofile" "$HOME/.zshrc"; do + if [ -f "$${profile}" ]; then + if ! grep -q "$${path_dir}" "$${profile}" 2> /dev/null; then + echo "export PATH=\"\$PATH:$${path_dir}\"" >> "$${profile}" + echo "Added $${path_dir} to $${profile}" + fi + fi + done + + local fish_config="$HOME/.config/fish/config.fish" + if [ -f "$${fish_config}" ]; then + if ! grep -q "$${path_dir}" "$${fish_config}" 2> /dev/null; then + echo "fish_add_path $${path_dir}" >> "$${fish_config}" + echo "Added $${path_dir} to $${fish_config}" + fi + fi +} + +function ensure_auggie_in_path() { + local AUGGIE_BIN="" + if command_exists auggie; then + AUGGIE_BIN=$(command -v auggie) + fi + + if [ -z "$${AUGGIE_BIN}" ] || [ ! -x "$${AUGGIE_BIN}" ]; then + echo "Auggie CLI binary (auggie) was not found or is not executable." >&2 + return 1 + fi + + local AUGGIE_VERSION + if ! AUGGIE_VERSION=$("$${AUGGIE_BIN}" --version); then + echo "Auggie CLI binary could not be executed." >&2 + return 1 + fi + printf "Auggie CLI version: %s\n" "$${AUGGIE_VERSION}" + + if [ -n "$${CODER_SCRIPT_BIN_DIR:-}" ] && [ ! -e "$${CODER_SCRIPT_BIN_DIR}/auggie" ]; then + ln -s "$${AUGGIE_BIN}" "$${CODER_SCRIPT_BIN_DIR}/auggie" + echo "Created symlink: $${CODER_SCRIPT_BIN_DIR}/auggie -> $${AUGGIE_BIN}" + fi + + add_path_to_shell_profiles "$(dirname "$${AUGGIE_BIN}")" +} + +# The documented install method is npm; the user prefix keeps the global +# install (and `auggie upgrade`) writable without root. +install_with_npm() { + if ! command_exists node || ! command_exists npm; then + echo "ERROR: Node.js 20+ and npm are required to install Auggie CLI. Install them in the image or with pre_install_script." >&2 + return 1 + fi + printf "Node.js version: %s\n" "$(node --version)" + printf "npm version: %s\n" "$(npm --version)" + + mkdir -p "$${NPM_GLOBAL_PREFIX}" + npm config set prefix "$${NPM_GLOBAL_PREFIX}" + + local package="@augmentcode/auggie" + if [ -n "$${ARG_AUGGIE_VERSION}" ]; then + package="$${package}@$${ARG_AUGGIE_VERSION}" + fi + if ! npm install -g "$${package}"; then + echo "Auggie CLI installation failed." >&2 + return 1 + fi +} + +install_auggie() { + if [ "$${ARG_INSTALL}" != "true" ]; then + echo "Skipping Auggie CLI installation as per configuration." + if ! ensure_auggie_in_path; then + echo "ERROR: install_auggie is false but a working 'auggie' was not found on PATH." >&2 + exit 1 + fi + echo "Validated existing Auggie CLI" + return + fi + + echo "Installing Auggie CLI" + install_with_npm + + if ! ensure_auggie_in_path; then + echo "ERROR: Failed to install Auggie CLI" >&2 + exit 1 + fi + echo "Installed Auggie CLI" +} + +setup_workdir() { + if [ -n "$${ARG_WORKDIR}" ] && [ ! -d "$${ARG_WORKDIR}" ]; then + echo "Creating workdir: $${ARG_WORKDIR}" + mkdir -p "$${ARG_WORKDIR}" + fi +} + +# Like a non-interactive `auggie mcp add-json`, an existing server name is kept. +add_mcp_servers() { + local mcp_json="$1" + local source_desc="$2" + local name server output + + while IFS= read -r name && IFS= read -r server; do + if output=$(auggie mcp add-json "$${name}" "$${server}" < /dev/null 2>&1); then + echo "Added MCP server '$${name}' ($${source_desc})" + elif grep -q "already exists" <<< "$${output}"; then + echo "Warning: MCP server '$${name}' already exists in $${SETTINGS_FILE}, keeping the existing one." + else + echo "Warning: Failed to add MCP server '$${name}' ($${source_desc}): $${output}" + fi + done < <(echo "$${mcp_json}" | jq -r '.mcpServers // {} | to_entries[] | .key, (.value | @json)') +} + +setup_mcp() { + if [ -z "$${ARG_MCP_CONFIG}" ] && [ -z "$${ARG_MCP_FILES}" ]; then + echo "No MCP servers configured." + return + fi + + require_jq "configure MCP servers" + + if [ -n "$${ARG_MCP_CONFIG}" ]; then + if echo "$${ARG_MCP_CONFIG}" | jq empty > /dev/null 2>&1; then + add_mcp_servers "$${ARG_MCP_CONFIG}" "from mcp" + else + echo "Warning: mcp is not valid JSON, skipping." + fi + fi + + local file + while IFS= read -r file; do + [ -z "$${file}" ] && continue + if [ ! -f "$${file}" ] || ! jq empty "$${file}" > /dev/null 2>&1; then + echo "Warning: MCP file $${file} is missing or not valid JSON, skipping." + continue + fi + add_mcp_servers "$(cat "$${file}")" "from $${file}" + done < <(echo "$${ARG_MCP_FILES:-[]}" | jq -r '.[]') +} + +write_rules() { + if [ -z "$${ARG_RULES}" ]; then + return + fi + + mkdir -p "$(dirname "$${RULES_FILE}")" + printf '%s\n' "$${ARG_RULES}" > "$${RULES_FILE}" + echo "Wrote rules: $${RULES_FILE}" +} + +# Auggie also accepts JSONC in settings.json, which jq cannot edit safely, so +# such a file is left unchanged. +setup_model() { + if [ -z "$${ARG_AUGGIE_MODEL}" ]; then + return + fi + + require_jq "set the Auggie model" + + local existing='{}' + if [ -f "$${SETTINGS_FILE}" ]; then + if ! jq -e 'type == "object"' "$${SETTINGS_FILE}" > /dev/null 2>&1; then + echo "Warning: $${SETTINGS_FILE} is not a strict JSON object (comments or trailing commas); leaving the model unchanged." + return + fi + existing=$(cat "$${SETTINGS_FILE}") + fi + + mkdir -p "$(dirname "$${SETTINGS_FILE}")" + echo "$${existing}" | jq --arg model "$${ARG_AUGGIE_MODEL}" '.model = $model' > "$${SETTINGS_FILE}.tmp" + mv "$${SETTINGS_FILE}.tmp" "$${SETTINGS_FILE}" + echo "Set model in $${SETTINGS_FILE}" +} + +install_auggie +setup_workdir +setup_mcp +write_rules +setup_model + +echo "Auggie module setup completed." diff --git a/registry/coder-labs/modules/auggie/scripts/start.sh b/registry/coder-labs/modules/auggie/scripts/start.sh deleted file mode 100644 index df91b82f31..0000000000 --- a/registry/coder-labs/modules/auggie/scripts/start.sh +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env bash - -if [ -f "$HOME/.bashrc" ]; then - source "$HOME"/.bashrc -fi - -set -euo pipefail - -command_exists() { - command -v "$1" > /dev/null 2>&1 -} - -if [ -f "$HOME/.nvm/nvm.sh" ]; then - source "$HOME"/.nvm/nvm.sh -else - export PATH="$HOME/.npm-global/bin:$PATH" -fi - -ARG_AUGGIE_START_DIRECTORY=${ARG_AUGGIE_START_DIRECTORY:-"$HOME"} -ARG_TASK_PROMPT=$(echo -n "${ARG_TASK_PROMPT:-}" | base64 -d) -ARG_MCP_FILES=${ARG_MCP_FILES:-[]} -ARG_AUGGIE_RULES=${ARG_AUGGIE_RULES:-} -ARG_AUGMENT_SESSION_AUTH=${ARG_AUGMENT_SESSION_AUTH:-} -ARG_AUGGIE_CONTINUE_PREVIOUS_CONVERSATION=${ARG_AUGGIE_CONTINUE_PREVIOUS_CONVERSATION:-false} -ARG_AUGGIE_INTERACTION_MODE=${ARG_AUGGIE_INTERACTION_MODE:-"interactive"} -ARG_AUGGIE_MODEL=${ARG_AUGGIE_MODEL:-} -ARG_REPORT_TASKS=${ARG_REPORT_TASKS:-false} - -ARGS=() - -echo "--------------------------------" - -printf "auggie_start_directory: %s\n" "$ARG_AUGGIE_START_DIRECTORY" -printf "task_prompt: %s\n" "$ARG_TASK_PROMPT" -printf "mcp_files: %s\n" "$ARG_MCP_FILES" -printf "auggie_rules: %s\n" "$ARG_AUGGIE_RULES" -printf "continue_previous_conversation: %s\n" "$ARG_AUGGIE_CONTINUE_PREVIOUS_CONVERSATION" -printf "auggie_interaction_mode: %s\n" "$ARG_AUGGIE_INTERACTION_MODE" -printf "augment_session_auth: %s\n" "$ARG_AUGMENT_SESSION_AUTH" -printf "auggie_model: %s\n" "$ARG_AUGGIE_MODEL" -printf "report_tasks: %s\n" "$ARG_REPORT_TASKS" - -echo "--------------------------------" - -function validate_auggie_installation() { - if command_exists auggie; then - printf "Auggie is installed\n" - else - printf "Error: Auggie is not installed. Please enable install_auggie or install it manually\n" - exit 1 - fi -} - -function build_auggie_args() { - if [ -n "$ARG_AUGGIE_INTERACTION_MODE" ]; then - if [ "$ARG_AUGGIE_INTERACTION_MODE" != "interactive" ]; then - ARGS+=(--"$ARG_AUGGIE_INTERACTION_MODE") - fi - fi - - if [ -n "$ARG_AUGGIE_MODEL" ]; then - ARGS+=(--model "$ARG_AUGGIE_MODEL") - fi - - if [ -f "$HOME/.augment/user_mcp.json" ]; then - ARGS+=(--mcp-config "$HOME/.augment/user_mcp.json") - fi - - if [ -n "$ARG_MCP_FILES" ] && [ "$ARG_MCP_FILES" != "[]" ]; then - for file in $(echo "$ARG_MCP_FILES" | jq -r '.[]'); do - ARGS+=(--mcp-config "$file") - done - fi - - ARGS+=(--mcp-config "$HOME/.augment/coder_mcp.json") - - if [ -n "$ARG_AUGGIE_RULES" ]; then - AUGGIE_RULES_FILE="$HOME/.augment/rules.md" - ARGS+=(--rules "$AUGGIE_RULES_FILE") - fi - - if [ "$ARG_AUGGIE_CONTINUE_PREVIOUS_CONVERSATION" == "true" ]; then - ARGS+=(--continue) - fi - - if [ -n "$ARG_TASK_PROMPT" ]; then - if [ "$ARG_REPORT_TASKS" == "true" ]; then - PROMPT="Every step of the way, report your progress using coder_report_task tool with proper summary and statuses. Your task at hand: $ARG_TASK_PROMPT" - else - PROMPT="$ARG_TASK_PROMPT" - fi - ARGS+=(--instruction "$PROMPT") - fi -} - -function start_agentapi_server() { - mkdir -p "$ARG_AUGGIE_START_DIRECTORY" - cd "$ARG_AUGGIE_START_DIRECTORY" - ARGS+=(--workspace-root "$ARG_AUGGIE_START_DIRECTORY") - printf "Running auggie with args: %s\n" "$(printf '%q ' "${ARGS[@]}")" - agentapi server --term-width 67 --term-height 1190 -- auggie "${ARGS[@]}" -} - -validate_auggie_installation -build_auggie_args -start_agentapi_server diff --git a/registry/coder-labs/modules/auggie/testdata/auggie-mock.sh b/registry/coder-labs/modules/auggie/testdata/auggie-mock.sh index ba7c5f6db9..809813ff0c 100644 --- a/registry/coder-labs/modules/auggie/testdata/auggie-mock.sh +++ b/registry/coder-labs/modules/auggie/testdata/auggie-mock.sh @@ -1,14 +1,26 @@ -#!/bin/bash +#!/usr/bin/env bash -if [[ "$1" == "--version" ]]; then - echo "HELLO: $(bash -c env)" - echo "auggie version v1.0.0" - exit 0 -fi +# Minimal Auggie CLI stand-in. `mcp add-json` keeps an existing name and exits +# 3 like the real CLI does without --replace. +settings="${HOME}/.augment/settings.json" -set -e - -while true; do - echo "$(date) - auggie-mock" - sleep 15 -done +case "$1 ${2:-}" in + "--version "*) + echo "0.36.0 (commit mock)" + ;; + "mcp add-json") + mkdir -p "$(dirname "${settings}")" + [[ -f "${settings}" ]] || echo '{}' > "${settings}" + if jq -e --arg name "$3" '(.mcpServers // {}) | has($name)' "${settings}" > /dev/null; then + echo "Error: Server '$3' already exists; use --replace to overwrite" >&2 + exit 3 + fi + jq --arg name "$3" --argjson server "$4" '.mcpServers[$name] = $server' "${settings}" > "${settings}.tmp" + mv "${settings}.tmp" "${settings}" + echo "Saved MCP server: {\"$3\":$4}" + ;; + *) + echo "auggie invoked with: $*" + ;; +esac +exit 0 From 1bf2adf29bf670007fed498dd5c1b9e0a9a1f8be Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Wed, 14 Oct 2026 11:49:35 +0000 Subject: [PATCH 2/4] docs(registry/coder-labs/modules/auggie): remove unnecessary README notes --- registry/coder-labs/modules/auggie/README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/registry/coder-labs/modules/auggie/README.md b/registry/coder-labs/modules/auggie/README.md index a6c254a1cb..e6dbef0ad6 100644 --- a/registry/coder-labs/modules/auggie/README.md +++ b/registry/coder-labs/modules/auggie/README.md @@ -55,9 +55,6 @@ resource "coder_app" "auggie" { See [CLI flags](https://docs.augmentcode.com/cli/reference) for launcher options such as `--continue`, `--model`, `--print`, and `--quiet`. -> [!NOTE] -> The `coder_app` command re-executes on every pane reconnect. This works for interactive `auggie`, but one-shot commands like `auggie --print` will re-run each time. For one-shot prompts, use a `coder_script` (runs once at startup) and a `coder_app` that attaches to the existing session (for example, with tmux). - ### MCP servers and rules ```tf From eaa8beb86b821207139971c1abe82284e48bfe2b Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Wed, 14 Oct 2026 11:54:30 +0000 Subject: [PATCH 3/4] docs(registry/coder-labs/modules/auggie): trim README to examples and doc links --- registry/coder-labs/modules/auggie/README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/registry/coder-labs/modules/auggie/README.md b/registry/coder-labs/modules/auggie/README.md index e6dbef0ad6..36ec85f0be 100644 --- a/registry/coder-labs/modules/auggie/README.md +++ b/registry/coder-labs/modules/auggie/README.md @@ -53,8 +53,6 @@ resource "coder_app" "auggie" { } ``` -See [CLI flags](https://docs.augmentcode.com/cli/reference) for launcher options such as `--continue`, `--model`, `--print`, and `--quiet`. - ### MCP servers and rules ```tf @@ -82,7 +80,7 @@ module "auggie" { } ``` -`mcp` and `mcp_files` use the [`mcpServers`](https://docs.augmentcode.com/cli/integrations#mcp-integrations) format and are added with `auggie mcp add-json`; servers that already exist are kept. `rules` are loaded as [user rules](https://docs.augmentcode.com/cli/rules). To pick a default model, set `auggie_model` to a name from `auggie models list`. +`mcp` and `mcp_files` use the [`mcpServers`](https://docs.augmentcode.com/cli/integrations#mcp-integrations) format. `rules` are loaded as [user rules](https://docs.augmentcode.com/cli/rules). To pick a default model, set `auggie_model` to a name from `auggie models list`. ### Pinned version @@ -95,8 +93,6 @@ module "auggie" { } ``` -Auggie updates itself in interactive mode unless [automatic updates](https://docs.augmentcode.com/cli/autoupgrade) are disabled. - ### Serialize a downstream `coder_script` after the install pipeline The module exposes the `scripts` output: an ordered list of `coder exp sync` names for the scripts this module creates (pre_install, install, post_install). Scripts that were not configured are absent. From 7123f4a6f0ec98d6713cdc062d36aa9fc1042a75 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Wed, 14 Oct 2026 11:58:31 +0000 Subject: [PATCH 4/4] docs(registry/coder-labs/modules/auggie): use heredoc JSON in README examples --- registry/coder-labs/modules/auggie/README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/registry/coder-labs/modules/auggie/README.md b/registry/coder-labs/modules/auggie/README.md index 36ec85f0be..c87494bd99 100644 --- a/registry/coder-labs/modules/auggie/README.md +++ b/registry/coder-labs/modules/auggie/README.md @@ -61,14 +61,16 @@ module "auggie" { version = "1.0.0" agent_id = coder_agent.main.id - mcp = jsonencode({ - mcpServers = { - filesystem = { - command = "npx" - args = ["-y", "@modelcontextprotocol/server-filesystem", "/home/coder/project"] + mcp = <<-EOF + { + "mcpServers": { + "filesystem": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-filesystem", "/home/coder/project"] } } - }) + } + EOF mcp_files = ["/home/coder/.config/mcp/servers.json"] rules = <<-EOT