From a8d9b7135920e93067e13e08daa523da118265d7 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Fri, 8 Aug 2025 19:41:26 +0500 Subject: [PATCH 01/48] feat(cursor-cli): add Cursor Agent CLI module (interactive default, MCP settings, model/force) - Runs `cursor-agent` directly (no AgentAPI); interactive chat by default - Supports non-interactive prints (-p) with output-format, model (-m), force (-f) - Merges MCP settings into ~/.cursor/settings.json - Installs via npm (uses nvm if needed); terraform tests added --- .../coder-labs/modules/cursor-cli/README.md | 57 ++++++ .../modules/cursor-cli/cursor-cli.tftest.hcl | 91 +++++++++ .../modules/cursor-cli/main.test.ts | 96 +++++++++ .../coder-labs/modules/cursor-cli/main.tf | 186 ++++++++++++++++++ .../modules/cursor-cli/scripts/install.sh | 91 +++++++++ .../modules/cursor-cli/scripts/start.sh | 92 +++++++++ .../cursor-cli/testdata/cursor-mock.sh | 5 + 7 files changed, 618 insertions(+) create mode 100644 registry/coder-labs/modules/cursor-cli/README.md create mode 100644 registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl create mode 100644 registry/coder-labs/modules/cursor-cli/main.test.ts create mode 100644 registry/coder-labs/modules/cursor-cli/main.tf create mode 100644 registry/coder-labs/modules/cursor-cli/scripts/install.sh create mode 100644 registry/coder-labs/modules/cursor-cli/scripts/start.sh create mode 100644 registry/coder-labs/modules/cursor-cli/testdata/cursor-mock.sh diff --git a/registry/coder-labs/modules/cursor-cli/README.md b/registry/coder-labs/modules/cursor-cli/README.md new file mode 100644 index 000000000..15210c26d --- /dev/null +++ b/registry/coder-labs/modules/cursor-cli/README.md @@ -0,0 +1,57 @@ +--- +display_name: Cursor CLI +icon: ../../../../.icons/cursor.svg +description: Run Cursor CLI agent in your workspace (no AgentAPI) +verified: true +tags: [agent, cursor, ai, cli] +--- + +# Cursor CLI + +Run the Cursor Coding Agent in your workspace using the Cursor CLI directly. This module does not use AgentAPI and executes the Cursor agent process itself. + +- Defaults to interactive mode, with an option for non-interactive mode +- Supports `--force` runs +- Allows configuring MCP servers (settings merge) +- Lets you choose a model and pass extra CLI arguments + +```tf +module "cursor_cli" { + source = "registry.coder.com/coder-labs/cursor-cli/coder" + version = "0.1.0" + agent_id = coder_agent.example.id + + # Optional + folder = "/home/coder/project" + install_cursor_cli = true + cursor_cli_version = "latest" + interactive = true + non_interactive_cmd = "run --once" + force = false + model = "gpt-4o" + additional_settings = jsonencode({ + mcpServers = { + coder = { + command = "coder" + args = ["exp", "mcp", "server"] + type = "stdio" + name = "Coder" + env = {} + enabled = true + } + } + }) + extra_args = ["--verbose"] +} +``` + +## Notes + +- See Cursor CLI docs: `https://docs.cursor.com/en/cli/overview` +- The module writes merged settings to `~/.cursor/settings.json` +- Interactive by default; set `interactive = false` to run non-interactively via `non_interactive_cmd` + +## Troubleshooting + +- Ensure the CLI is installed (enable `install_cursor_cli = true` or preinstall it in your image) +- Logs are written to `~/.cursor-cli-module/` diff --git a/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl b/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl new file mode 100644 index 000000000..f05a6dc99 --- /dev/null +++ b/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl @@ -0,0 +1,91 @@ +// Terraform tests for the cursor-cli module +// Validates that we render expected script content given inputs + +run "defaults_interactive" { + command = plan + + variables { + agent_id = "test-agent" + } + + assert { + condition = can(regex("INTERACTIVE='true'", resource.coder_script.cursor_cli.script)) + error_message = "Expected INTERACTIVE default to be true" + } + + assert { + condition = can(regex("BINARY_NAME='cursor-agent'", resource.coder_script.cursor_cli.script)) + error_message = "Expected default binary_name to be cursor-agent" + } +} + +run "non_interactive_mode" { + command = plan + + variables { + agent_id = "test-agent" + interactive = false + non_interactive_cmd = "run --once" + } + + assert { + condition = can(regex("INTERACTIVE='false'", resource.coder_script.cursor_cli.script)) + error_message = "Expected INTERACTIVE to be false when interactive=false" + } + + assert { + condition = can(regex("NON_INTERACTIVE_CMD='run --once'", resource.coder_script.cursor_cli.script)) + error_message = "Expected NON_INTERACTIVE_CMD to be propagated" + } +} + +run "model_and_force" { + command = plan + + variables { + agent_id = "test-agent" + model = "test-model" + force = true + } + + assert { + condition = can(regex("MODEL='test-model'", resource.coder_script.cursor_cli.script)) + error_message = "Expected MODEL to be propagated" + } + + assert { + condition = can(regex("FORCE='true'", resource.coder_script.cursor_cli.script)) + error_message = "Expected FORCE true to be propagated" + } +} + +run "additional_settings_propagated" { + command = plan + + variables { + agent_id = "test-agent" + additional_settings = jsonencode({ + mcpServers = { + coder = { + command = "coder" + args = ["exp", "mcp", "server"] + type = "stdio" + } + } + }) + } + + // Ensure the encoded settings are passed into the install invocation + assert { + condition = can(regex(base64encode(jsonencode({ + mcpServers = { + coder = { + command = "coder" + args = ["exp", "mcp", "server"] + type = "stdio" + } + } + })), resource.coder_script.cursor_cli.script)) + error_message = "Expected ADDITIONAL_SETTINGS (base64) to be in the install step" + } +} diff --git a/registry/coder-labs/modules/cursor-cli/main.test.ts b/registry/coder-labs/modules/cursor-cli/main.test.ts new file mode 100644 index 000000000..421f90b4a --- /dev/null +++ b/registry/coder-labs/modules/cursor-cli/main.test.ts @@ -0,0 +1,96 @@ +import { test, afterEach, describe, setDefaultTimeout, beforeAll, expect } from "bun:test"; +import { execContainer, readFileContainer, runTerraformInit, runTerraformApply, writeFileContainer, runContainer, removeContainer, findResourceInstance } from "~test"; +import dedent from "dedent"; + +let cleanupFunctions: (() => Promise)[] = []; +const registerCleanup = (cleanup: () => Promise) => { + cleanupFunctions.push(cleanup); +}; + +afterEach(async () => { + const cleanupFnsCopy = cleanupFunctions.slice().reverse(); + cleanupFunctions = []; + for (const cleanup of cleanupFnsCopy) { + try { + await cleanup(); + } catch (error) { + console.error("Error during cleanup:", error); + } + } +}); + +const writeExecutable = async (containerId: string, filePath: string, content: string) => { + await writeFileContainer(containerId, filePath, content, { user: "root" }); + await execContainer(containerId, ["bash", "-c", `chmod 755 ${filePath}`], ["--user", "root"]); +}; + +const loadTestFile = async (...relativePath: string[]) => { + return await Bun.file(new URL(`./testdata/${relativePath.join("/")}`, import.meta.url)).text(); +}; + +const setup = async (vars?: Record): Promise<{ id: string }> => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + install_cursor_cli: "false", + ...vars, + }); + const coderScript = findResourceInstance(state, "coder_script"); + const id = await runContainer("codercom/enterprise-node:latest"); + registerCleanup(async () => removeContainer(id)); + await writeExecutable(id, "/home/coder/script.sh", coderScript.script); + await writeExecutable(id, "/usr/bin/cursor", await loadTestFile("cursor-mock.sh")); + return { id }; +}; + +setDefaultTimeout(60 * 1000); + +describe("cursor-cli", async () => { + beforeAll(async () => { + await runTerraformInit(import.meta.dir); + }); + + test("happy-path-interactive", async () => { + const { id } = await setup(); + const resp = await execContainer(id, ["bash", "-c", "cd /home/coder && ./script.sh"]); + if (resp.exitCode !== 0) { + console.log(resp.stdout); + console.log(resp.stderr); + } + expect(resp.exitCode).toBe(0); + const startLog = await readFileContainer(id, "/home/coder/.cursor-cli-module/start.log"); + expect(startLog).toContain("agent"); + expect(startLog).toContain("--interactive"); + }); + + test("non-interactive-with-cmd", async () => { + const { id } = await setup({ interactive: "false", non_interactive_cmd: "run --once" }); + const resp = await execContainer(id, ["bash", "-c", "cd /home/coder && ./script.sh"]); + expect(resp.exitCode).toBe(0); + const startLog = await readFileContainer(id, "/home/coder/.cursor-cli-module/start.log"); + expect(startLog).toContain("run"); + expect(startLog).toContain("--once"); + expect(startLog).not.toContain("--interactive"); + }); + + test("model-and-force-and-extra-args", async () => { + const { id } = await setup({ model: "test-model", force: "true" }); + const resp = await execContainer(id, ["bash", "-c", "cd /home/coder && ./script.sh"], ["--env", "TF_VAR_extra_args=--foo\nbar"]); + expect(resp.exitCode).toBe(0); + const startLog = await readFileContainer(id, "/home/coder/.cursor-cli-module/start.log"); + expect(startLog).toContain("--model"); + expect(startLog).toContain("test-model"); + expect(startLog).toContain("--force"); + }); + + test("additional-settings-merge", async () => { + const settings = dedent` + {"mcpServers": {"coder": {"command": "coder", "args": ["exp","mcp","server"], "type": "stdio"}}} + `; + const { id } = await setup({ additional_settings: settings }); + const resp = await execContainer(id, ["bash", "-c", "cd /home/coder && ./script.sh"]); + expect(resp.exitCode).toBe(0); + const cfg = await readFileContainer(id, "/home/coder/.cursor/settings.json"); + expect(cfg).toContain("mcpServers"); + expect(cfg).toContain("coder"); + }); +}); diff --git a/registry/coder-labs/modules/cursor-cli/main.tf b/registry/coder-labs/modules/cursor-cli/main.tf new file mode 100644 index 000000000..07c4a2a6a --- /dev/null +++ b/registry/coder-labs/modules/cursor-cli/main.tf @@ -0,0 +1,186 @@ +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." +} + +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." + default = "/icon/cursor.svg" +} + +variable "folder" { + type = string + description = "The folder to run Cursor CLI in." + default = "/home/coder" +} + +variable "install_cursor_cli" { + type = bool + description = "Whether to install Cursor CLI." + default = true +} + +variable "cursor_cli_version" { + type = string + description = "The version of Cursor CLI to install (latest for latest)." + default = "latest" +} + +variable "interactive" { + type = bool + description = "Run in interactive chat mode (default)." + default = true +} + +variable "initial_prompt" { + type = string + description = "Initial prompt to start the chat with (passed as trailing arg)." + default = "" +} + +variable "non_interactive_cmd" { + type = string + description = "Additional arguments appended when interactive=false (advanced usage)." + default = "" +} + +variable "force" { + type = bool + description = "Pass -f/--force to allow commands unless explicitly denied." + default = false +} + +variable "model" { + type = string + description = "Pass -m/--model to select model (e.g., sonnet-4, gpt-5)." + default = "" +} + +variable "output_format" { + type = string + description = "Output format with -p: text, json, or stream-json." + default = "" +} + +variable "api_key" { + type = string + description = "API key (sets CURSOR_API_KEY env or pass via -a)." + default = "" + sensitive = true +} + +variable "extra_args" { + type = list(string) + description = "Additional args to pass to the Cursor CLI." + default = [] +} + +variable "binary_name" { + type = string + description = "Cursor Agent binary name (default: cursor-agent)." + default = "cursor-agent" +} + +variable "base_command" { + type = string + description = "Base Cursor CLI command to run (default: none for chat)." + default = "" +} + +variable "additional_settings" { + type = string + description = "JSON to merge into ~/.cursor/settings.json (e.g., mcpServers)." + default = "" +} + +locals { + app_slug = "cursor-cli" + install_script = file("${path.module}/scripts/install.sh") + start_script = file("${path.module}/scripts/start.sh") + module_dir_name = ".cursor-cli-module" +} + +resource "coder_script" "cursor_cli" { + agent_id = var.agent_id + display_name = "Cursor CLI" + icon = var.icon + 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_INSTALL='${var.install_cursor_cli}' \ + ARG_VERSION='${var.cursor_cli_version}' \ + ADDITIONAL_SETTINGS='${base64encode(replace(var.additional_settings, "'", "'\\''"))}' \ + MODULE_DIR_NAME='${local.module_dir_name}' \ + FOLDER='${var.folder}' \ + /tmp/install.sh | tee "$HOME/${local.module_dir_name}/install.log" + + echo -n '${base64encode(local.start_script)}' | base64 -d > /tmp/start.sh + chmod +x /tmp/start.sh + INTERACTIVE='${var.interactive}' \ + INITIAL_PROMPT='${replace(var.initial_prompt, "'", "'\\''")}' \ + NON_INTERACTIVE_CMD='${replace(var.non_interactive_cmd, "'", "'\\''")}' \ + BASE_COMMAND='${var.base_command}' \ + FORCE='${var.force}' \ + MODEL='${var.model}' \ + OUTPUT_FORMAT='${var.output_format}' \ + API_KEY_SECRET='${var.api_key}' \ + EXTRA_ARGS='${base64encode(join("\n", var.extra_args))}' \ + MODULE_DIR_NAME='${local.module_dir_name}' \ + FOLDER='${var.folder}' \ + BINARY_NAME='${var.binary_name}' \ + /tmp/start.sh | tee "$HOME/${local.module_dir_name}/start.log" + EOT + run_on_start = true +} + +resource "coder_app" "cursor_cli" { + agent_id = var.agent_id + slug = local.app_slug + display_name = "Cursor CLI" + icon = var.icon + order = var.order + group = var.group + command = <<-EOT + #!/bin/bash + set -o errexit + set -o pipefail + if [ -f "$HOME/${local.module_dir_name}/start.log" ]; then + tail -n +1 -f "$HOME/${local.module_dir_name}/start.log" + else + echo "Cursor CLI not started yet. Check install/start logs in $HOME/${local.module_dir_name}/" + /bin/bash + fi + EOT +} diff --git a/registry/coder-labs/modules/cursor-cli/scripts/install.sh b/registry/coder-labs/modules/cursor-cli/scripts/install.sh new file mode 100644 index 000000000..efdbdc979 --- /dev/null +++ b/registry/coder-labs/modules/cursor-cli/scripts/install.sh @@ -0,0 +1,91 @@ +#!/bin/bash + +set -o errexit +set -o pipefail + +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +# Inputs +ARG_INSTALL=${ARG_INSTALL:-true} +ARG_VERSION=${ARG_VERSION:-latest} +MODULE_DIR_NAME=${MODULE_DIR_NAME:-.cursor-cli-module} +FOLDER=${FOLDER:-$HOME} + +mkdir -p "$HOME/$MODULE_DIR_NAME" + +ADDITIONAL_SETTINGS=$(echo -n "$ADDITIONAL_SETTINGS" | base64 -d) + +{ + echo "--------------------------------" + echo "install: $ARG_INSTALL" + echo "version: $ARG_VERSION" + echo "folder: $FOLDER" + echo "--------------------------------" +} | tee -a "$HOME/$MODULE_DIR_NAME/install.log" + +# Install Cursor Agent CLI if requested. +# The docs show Cursor Agent CLI usage; we will install via npm globally. +# This requires Node/npm; install Node via NVM if not present (similar to gemini module approach). +if [ "$ARG_INSTALL" = "true" ]; then + echo "Installing Cursor Agent CLI..." | tee -a "$HOME/$MODULE_DIR_NAME/install.log" + + install_node() { + if ! command_exists npm; then + if ! command_exists node; then + export NVM_DIR="$HOME/.nvm" + if [ ! -d "$NVM_DIR" ]; then + mkdir -p "$NVM_DIR" + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash + [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" + else + [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" + fi + nvm install --lts + nvm use --lts + nvm alias default node + else + echo "Node is installed but npm missing; please install npm manually." | tee -a "$HOME/$MODULE_DIR_NAME/install.log" + fi + fi + } + + install_node + + # If nvm not present, create local npm global dir to avoid permissions issues + if ! command_exists nvm; then + mkdir -p "$HOME/.npm-global" + npm config set prefix "$HOME/.npm-global" + export PATH="$HOME/.npm-global/bin:$PATH" + if ! grep -q "export PATH=$HOME/.npm-global/bin:\$PATH" "$HOME/.bashrc" 2>/dev/null; then + echo "export PATH=$HOME/.npm-global/bin:\$PATH" >> "$HOME/.bashrc" + fi + fi + + if [ -n "$ARG_VERSION" ] && [ "$ARG_VERSION" != "latest" ]; then + npm install -g "cursor-agent@$ARG_VERSION" 2>&1 | tee -a "$HOME/$MODULE_DIR_NAME/install.log" + else + npm install -g cursor-agent 2>&1 | tee -a "$HOME/$MODULE_DIR_NAME/install.log" + fi + + echo "Installed cursor-agent: $(command -v cursor-agent || true)" | tee -a "$HOME/$MODULE_DIR_NAME/install.log" +fi + +# Ensure settings path exists and merge additional_settings JSON +SETTINGS_PATH="$HOME/.cursor/settings.json" +mkdir -p "$(dirname "$SETTINGS_PATH")" + +# If settings file doesn't exist, initialize basic structure +if [ ! -f "$SETTINGS_PATH" ]; then + echo '{}' > "$SETTINGS_PATH" +fi + +if [ -n "$ADDITIONAL_SETTINGS" ]; then + echo "Merging additional settings into $SETTINGS_PATH" | tee -a "$HOME/$MODULE_DIR_NAME/install.log" + TMP_SETTINGS=$(mktemp) + # Merge JSON: deep merge mcpServers and top-level keys + jq --argjson add "$ADDITIONAL_SETTINGS" 'def deepmerge(a;b): reduce (b|keys[]) as $key (a; .[$key] = if ( (.[ $key ]|type?) == "object" and (b[$key]|type?) == "object" ) then deepmerge(.[ $key ]; b[$key]) else b[$key] end); deepmerge(.;$add)' "$SETTINGS_PATH" > "$TMP_SETTINGS" && mv "$TMP_SETTINGS" "$SETTINGS_PATH" +fi + +exit 0 diff --git a/registry/coder-labs/modules/cursor-cli/scripts/start.sh b/registry/coder-labs/modules/cursor-cli/scripts/start.sh new file mode 100644 index 000000000..27f2e3946 --- /dev/null +++ b/registry/coder-labs/modules/cursor-cli/scripts/start.sh @@ -0,0 +1,92 @@ +#!/bin/bash + +set -o errexit +set -o pipefail + +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +INTERACTIVE=${INTERACTIVE:-true} +INITIAL_PROMPT=${INITIAL_PROMPT:-} +NON_INTERACTIVE_CMD=${NON_INTERACTIVE_CMD:-} +FORCE=${FORCE:-false} +MODEL=${MODEL:-} +OUTPUT_FORMAT=${OUTPUT_FORMAT:-} +API_KEY_SECRET=${API_KEY_SECRET:-} +EXTRA_ARGS_BASE64=${EXTRA_ARGS:-} +MODULE_DIR_NAME=${MODULE_DIR_NAME:-.cursor-cli-module} +FOLDER=${FOLDER:-$HOME} +BINARY_NAME=${BINARY_NAME:-cursor-agent} + +mkdir -p "$HOME/$MODULE_DIR_NAME" + +# Decode EXTRA_ARGS lines into an array +IFS=$'\n' read -r -d '' -a EXTRA_ARR < <(echo -n "$EXTRA_ARGS_BASE64" | base64 -d; printf '\0') || true + +# Find cursor agent cli +if command_exists "$BINARY_NAME"; then + CURSOR_CMD="$BINARY_NAME" +elif [ -x "$HOME/.local/bin/$BINARY_NAME" ]; then + CURSOR_CMD="$HOME/.local/bin/$BINARY_NAME" +else + echo "Error: $BINARY_NAME not found. Install it or set install_cursor_cli=true." | tee -a "$HOME/$MODULE_DIR_NAME/start.log" + exit 1 +fi + +# Ensure working directory exists +if [ -d "$FOLDER" ]; then + cd "$FOLDER" +else + mkdir -p "$FOLDER" + cd "$FOLDER" +fi + +ARGS=() + +# base command: if provided, append; otherwise chat mode (no command) +if [ -n "${BASE_COMMAND:-}" ]; then + ARGS+=("${BASE_COMMAND}") +fi + +# global flags +if [ -n "$MODEL" ]; then + ARGS+=("-m" "$MODEL") +fi +if [ "$FORCE" = "true" ]; then + ARGS+=("-f") +fi + +# Non-interactive printing flags +PRINT_TO_CONSOLE=false +if [ "$INTERACTIVE" != "true" ]; then + PRINT_TO_CONSOLE=true + ARGS+=("-p") + if [ -n "$OUTPUT_FORMAT" ]; then + ARGS+=("--output-format" "$OUTPUT_FORMAT") + fi + if [ -n "$NON_INTERACTIVE_CMD" ]; then + # shellcheck disable=SC2206 + CMD_PARTS=($NON_INTERACTIVE_CMD) + ARGS+=("${CMD_PARTS[@]}") + fi +fi + +# Extra args, if any +if [ ${#EXTRA_ARR[@]} -gt 0 ]; then + ARGS+=("${EXTRA_ARR[@]}") +fi + +# If initial prompt specified (chat mode), pass as trailing arg +if [ -n "$INITIAL_PROMPT" ]; then + ARGS+=("$INITIAL_PROMPT") +fi + +# Set API key env if provided +if [ -n "$API_KEY_SECRET" ]; then + export CURSOR_API_KEY="$API_KEY_SECRET" +fi + +# Log and exec +printf "Running: %q %s\n" "$CURSOR_CMD" "$(printf '%q ' "${ARGS[@]}")" | tee -a "$HOME/$MODULE_DIR_NAME/start.log" +exec "$CURSOR_CMD" "${ARGS[@]}" diff --git a/registry/coder-labs/modules/cursor-cli/testdata/cursor-mock.sh b/registry/coder-labs/modules/cursor-cli/testdata/cursor-mock.sh new file mode 100644 index 000000000..fd160696a --- /dev/null +++ b/registry/coder-labs/modules/cursor-cli/testdata/cursor-mock.sh @@ -0,0 +1,5 @@ +#!/bin/sh +# minimal mock that prints args and exits +printf "cursor mock invoked with: %s\n" "$*" +# Exit successfully regardless +exit 0 From 7483c7794795391f9be77ad5703535f440bfddbf Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Fri, 8 Aug 2025 23:48:09 +0500 Subject: [PATCH 02/48] feat(cursor-cli): add project MCP and rules support; non-interactive only - mcp_json -> /.cursor/mcp.json (optional) - rules_files map -> /.cursor/rules/* (optional); link rules docs - always -p; default output_format=json; README updates --- .../coder-labs/modules/cursor-cli/README.md | 20 +++-- .../modules/cursor-cli/cursor-cli.tftest.hcl | 78 +++++++++++++++---- .../coder-labs/modules/cursor-cli/main.tf | 35 ++++----- .../modules/cursor-cli/scripts/install.sh | 23 ++++++ .../modules/cursor-cli/scripts/start.sh | 32 +++----- 5 files changed, 125 insertions(+), 63 deletions(-) diff --git a/registry/coder-labs/modules/cursor-cli/README.md b/registry/coder-labs/modules/cursor-cli/README.md index 15210c26d..7ded98120 100644 --- a/registry/coder-labs/modules/cursor-cli/README.md +++ b/registry/coder-labs/modules/cursor-cli/README.md @@ -10,9 +10,9 @@ tags: [agent, cursor, ai, cli] Run the Cursor Coding Agent in your workspace using the Cursor CLI directly. This module does not use AgentAPI and executes the Cursor agent process itself. -- Defaults to interactive mode, with an option for non-interactive mode +- Runs non-interactive (autonomous) by default, using `-p` (print) - Supports `--force` runs -- Allows configuring MCP servers (settings merge) +- Allows configuring MCP servers and project MCP (`~/.cursor/settings.json` and `/.cursor/mcp.json`) - Lets you choose a model and pass extra CLI arguments ```tf @@ -25,10 +25,15 @@ module "cursor_cli" { folder = "/home/coder/project" install_cursor_cli = true cursor_cli_version = "latest" - interactive = true - non_interactive_cmd = "run --once" + base_command = "status" # optional subcommand (default is chat mode) + output_format = "json" # text | json | stream-json force = false - model = "gpt-4o" + model = "gpt-5" + mcp_json = jsonencode({ + mcpServers = { + # example project-specific servers (see docs) + } + }) additional_settings = jsonencode({ mcpServers = { coder = { @@ -48,8 +53,9 @@ module "cursor_cli" { ## Notes - See Cursor CLI docs: `https://docs.cursor.com/en/cli/overview` -- The module writes merged settings to `~/.cursor/settings.json` -- Interactive by default; set `interactive = false` to run non-interactively via `non_interactive_cmd` +- For MCP project config, see `https://docs.cursor.com/en/context/mcp#using-mcp-json`. This module writes your `mcp_json` into `/.cursor/mcp.json` and merges `additional_settings` into `~/.cursor/settings.json`. +- For Rules, see `https://docs.cursor.com/en/context/rules#project-rules`. Provide `rules_files` (map of file name to content) to populate `/.cursor/rules/`. +- The agent runs non-interactively with `-p` by default. Use `output_format` to choose `text | json | stream-json` (default `json`). ## Troubleshooting diff --git a/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl b/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl index f05a6dc99..61e3a7c8b 100644 --- a/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl +++ b/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl @@ -1,18 +1,13 @@ // Terraform tests for the cursor-cli module // Validates that we render expected script content given inputs -run "defaults_interactive" { +run "defaults_noninteractive" { command = plan variables { agent_id = "test-agent" } - assert { - condition = can(regex("INTERACTIVE='true'", resource.coder_script.cursor_cli.script)) - error_message = "Expected INTERACTIVE default to be true" - } - assert { condition = can(regex("BINARY_NAME='cursor-agent'", resource.coder_script.cursor_cli.script)) error_message = "Expected default binary_name to be cursor-agent" @@ -23,19 +18,16 @@ run "non_interactive_mode" { command = plan variables { - agent_id = "test-agent" - interactive = false - non_interactive_cmd = "run --once" + agent_id = "test-agent" + base_command = "status" + extra_args = ["--dry-run"] + output_format = "json" } assert { - condition = can(regex("INTERACTIVE='false'", resource.coder_script.cursor_cli.script)) - error_message = "Expected INTERACTIVE to be false when interactive=false" - } - - assert { - condition = can(regex("NON_INTERACTIVE_CMD='run --once'", resource.coder_script.cursor_cli.script)) - error_message = "Expected NON_INTERACTIVE_CMD to be propagated" + // base command and -p --output-format json are included in env + condition = can(regex("BASE_COMMAND='status'", resource.coder_script.cursor_cli.script)) + error_message = "Expected BASE_COMMAND to be propagated" } } @@ -73,6 +65,10 @@ run "additional_settings_propagated" { } } }) + mcp_json = jsonencode({ mcpServers = { foo = { command = "foo", type = "stdio" } } }) + rules_files = { + "global.yml" = "version: 1\nrules:\n - name: global\n include: ['**/*']\n description: global rule" + } } // Ensure the encoded settings are passed into the install invocation @@ -88,4 +84,54 @@ run "additional_settings_propagated" { })), resource.coder_script.cursor_cli.script)) error_message = "Expected ADDITIONAL_SETTINGS (base64) to be in the install step" } + + // Ensure project mcp_json is passed + assert { + condition = can(regex(base64encode(jsonencode({ mcpServers = { foo = { command = "foo", type = "stdio" } } })), resource.coder_script.cursor_cli.script)) + error_message = "Expected PROJECT_MCP_JSON (base64) to be in the install step" + } + + // Ensure rules map is passed + assert { + condition = can(regex(base64encode(jsonencode({"global.yml":"version: 1\nrules:\n - name: global\n include: ['**/*']\n description: global rule"})), resource.coder_script.cursor_cli.script)) + error_message = "Expected PROJECT_RULES_JSON (base64) to be in the install step" + } +} + +run "output_api_key_binary_basecmd_extra" { + command = plan + + variables { + agent_id = "test-agent" + output_format = "json" + api_key = "sk-test-123" + binary_name = "cursor-agent" + base_command = "status" + extra_args = ["--foo", "bar"] + } + + assert { + condition = can(regex("OUTPUT_FORMAT='json'", resource.coder_script.cursor_cli.script)) + error_message = "Expected output format to be passed" + } + + assert { + condition = can(regex("API_KEY_SECRET='sk-test-123'", resource.coder_script.cursor_cli.script)) + error_message = "Expected API key to be plumbed (to CURSOR_API_KEY at runtime)" + } + + assert { + condition = can(regex("BINARY_NAME='cursor-agent'", resource.coder_script.cursor_cli.script)) + error_message = "Expected binary name to be forwarded" + } + + assert { + condition = can(regex("BASE_COMMAND='status'", resource.coder_script.cursor_cli.script)) + error_message = "Expected base command to be forwarded" + } + + assert { + condition = can(regex(base64encode("--foo\nbar"), resource.coder_script.cursor_cli.script)) + error_message = "Expected extra args to be base64 encoded and passed" + } } diff --git a/registry/coder-labs/modules/cursor-cli/main.tf b/registry/coder-labs/modules/cursor-cli/main.tf index 07c4a2a6a..1de313d77 100644 --- a/registry/coder-labs/modules/cursor-cli/main.tf +++ b/registry/coder-labs/modules/cursor-cli/main.tf @@ -54,23 +54,8 @@ variable "cursor_cli_version" { default = "latest" } -variable "interactive" { - type = bool - description = "Run in interactive chat mode (default)." - default = true -} +# Running mode is non-interactive by design for automation. -variable "initial_prompt" { - type = string - description = "Initial prompt to start the chat with (passed as trailing arg)." - default = "" -} - -variable "non_interactive_cmd" { - type = string - description = "Additional arguments appended when interactive=false (advanced usage)." - default = "" -} variable "force" { type = bool @@ -121,6 +106,18 @@ variable "additional_settings" { default = "" } +variable "mcp_json" { + type = string + description = "Project-specific MCP JSON to write to /.cursor/mcp.json. See https://docs.cursor.com/en/context/mcp#using-mcp-json" + default = null +} + +variable "rules_files" { + type = map(string) + description = "Optional map of rule file name to content. Files will be written to /.cursor/rules/. See https://docs.cursor.com/en/context/rules#project-rules" + default = null +} + locals { app_slug = "cursor-cli" install_script = file("${path.module}/scripts/install.sh") @@ -142,15 +139,15 @@ resource "coder_script" "cursor_cli" { ARG_INSTALL='${var.install_cursor_cli}' \ ARG_VERSION='${var.cursor_cli_version}' \ ADDITIONAL_SETTINGS='${base64encode(replace(var.additional_settings, "'", "'\\''"))}' \ + PROJECT_MCP_JSON='${var.mcp_json != null ? base64encode(replace(var.mcp_json, "'", "'\\''")) : ""}' \ + PROJECT_RULES_JSON='${var.rules_files != null ? base64encode(jsonencode(var.rules_files)) : ""}' \ MODULE_DIR_NAME='${local.module_dir_name}' \ FOLDER='${var.folder}' \ /tmp/install.sh | tee "$HOME/${local.module_dir_name}/install.log" echo -n '${base64encode(local.start_script)}' | base64 -d > /tmp/start.sh chmod +x /tmp/start.sh - INTERACTIVE='${var.interactive}' \ - INITIAL_PROMPT='${replace(var.initial_prompt, "'", "'\\''")}' \ - NON_INTERACTIVE_CMD='${replace(var.non_interactive_cmd, "'", "'\\''")}' \ + # Non-interactive mode by design BASE_COMMAND='${var.base_command}' \ FORCE='${var.force}' \ MODEL='${var.model}' \ diff --git a/registry/coder-labs/modules/cursor-cli/scripts/install.sh b/registry/coder-labs/modules/cursor-cli/scripts/install.sh index efdbdc979..0a4bc4259 100644 --- a/registry/coder-labs/modules/cursor-cli/scripts/install.sh +++ b/registry/coder-labs/modules/cursor-cli/scripts/install.sh @@ -16,6 +16,8 @@ FOLDER=${FOLDER:-$HOME} mkdir -p "$HOME/$MODULE_DIR_NAME" ADDITIONAL_SETTINGS=$(echo -n "$ADDITIONAL_SETTINGS" | base64 -d) +PROJECT_MCP_JSON=$(echo -n "$PROJECT_MCP_JSON" | base64 -d) +PROJECT_RULES_JSON=$(echo -n "$PROJECT_RULES_JSON" | base64 -d) { echo "--------------------------------" @@ -76,6 +78,27 @@ fi SETTINGS_PATH="$HOME/.cursor/settings.json" mkdir -p "$(dirname "$SETTINGS_PATH")" +# Write project-specific MCP if provided +if [ -n "$PROJECT_MCP_JSON" ]; then + TARGET_DIR="$FOLDER/.cursor" + mkdir -p "$TARGET_DIR" + echo "$PROJECT_MCP_JSON" > "$TARGET_DIR/mcp.json" + echo "Wrote project MCP to $TARGET_DIR/mcp.json" | tee -a "$HOME/$MODULE_DIR_NAME/install.log" +fi + +# Write rules files if provided (map of name->content) +if [ -n "$PROJECT_RULES_JSON" ]; then + RULES_DIR="$FOLDER/.cursor/rules" + mkdir -p "$RULES_DIR" + echo "$PROJECT_RULES_JSON" | jq -r 'to_entries[] | @base64' | while read -r entry; do + _jq() { echo "${entry}" | base64 -d | jq -r ${1}; } + NAME=$(_jq '.key') + CONTENT=$(_jq '.value') + echo "$CONTENT" > "$RULES_DIR/$NAME" + echo "Wrote rule: $RULES_DIR/$NAME" | tee -a "$HOME/$MODULE_DIR_NAME/install.log" + done +fi + # If settings file doesn't exist, initialize basic structure if [ ! -f "$SETTINGS_PATH" ]; then echo '{}' > "$SETTINGS_PATH" diff --git a/registry/coder-labs/modules/cursor-cli/scripts/start.sh b/registry/coder-labs/modules/cursor-cli/scripts/start.sh index 27f2e3946..8b8f19b07 100644 --- a/registry/coder-labs/modules/cursor-cli/scripts/start.sh +++ b/registry/coder-labs/modules/cursor-cli/scripts/start.sh @@ -7,12 +7,11 @@ command_exists() { command -v "$1" >/dev/null 2>&1 } -INTERACTIVE=${INTERACTIVE:-true} -INITIAL_PROMPT=${INITIAL_PROMPT:-} +# Non-interactive autonomous mode only NON_INTERACTIVE_CMD=${NON_INTERACTIVE_CMD:-} FORCE=${FORCE:-false} MODEL=${MODEL:-} -OUTPUT_FORMAT=${OUTPUT_FORMAT:-} +OUTPUT_FORMAT=${OUTPUT_FORMAT:-json} API_KEY_SECRET=${API_KEY_SECRET:-} EXTRA_ARGS_BASE64=${EXTRA_ARGS:-} MODULE_DIR_NAME=${MODULE_DIR_NAME:-.cursor-cli-module} @@ -57,19 +56,15 @@ if [ "$FORCE" = "true" ]; then ARGS+=("-f") fi -# Non-interactive printing flags -PRINT_TO_CONSOLE=false -if [ "$INTERACTIVE" != "true" ]; then - PRINT_TO_CONSOLE=true - ARGS+=("-p") - if [ -n "$OUTPUT_FORMAT" ]; then - ARGS+=("--output-format" "$OUTPUT_FORMAT") - fi - if [ -n "$NON_INTERACTIVE_CMD" ]; then - # shellcheck disable=SC2206 - CMD_PARTS=($NON_INTERACTIVE_CMD) - ARGS+=("${CMD_PARTS[@]}") - fi +# Non-interactive printing flags (always enabled) +ARGS+=("-p") +if [ -n "$OUTPUT_FORMAT" ]; then + ARGS+=("--output-format" "$OUTPUT_FORMAT") +fi +if [ -n "$NON_INTERACTIVE_CMD" ]; then + # shellcheck disable=SC2206 + CMD_PARTS=($NON_INTERACTIVE_CMD) + ARGS+=("${CMD_PARTS[@]}") fi # Extra args, if any @@ -77,11 +72,6 @@ if [ ${#EXTRA_ARR[@]} -gt 0 ]; then ARGS+=("${EXTRA_ARR[@]}") fi -# If initial prompt specified (chat mode), pass as trailing arg -if [ -n "$INITIAL_PROMPT" ]; then - ARGS+=("$INITIAL_PROMPT") -fi - # Set API key env if provided if [ -n "$API_KEY_SECRET" ]; then export CURSOR_API_KEY="$API_KEY_SECRET" From a3aa1cf4ff23beb9894768a70de8ee0d756be446 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Fri, 8 Aug 2025 23:52:01 +0500 Subject: [PATCH 03/48] chore(cursor-cli): remove Bun test; terraform tests only --- .../modules/cursor-cli/main.test.ts | 96 ------------------- 1 file changed, 96 deletions(-) delete mode 100644 registry/coder-labs/modules/cursor-cli/main.test.ts diff --git a/registry/coder-labs/modules/cursor-cli/main.test.ts b/registry/coder-labs/modules/cursor-cli/main.test.ts deleted file mode 100644 index 421f90b4a..000000000 --- a/registry/coder-labs/modules/cursor-cli/main.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { test, afterEach, describe, setDefaultTimeout, beforeAll, expect } from "bun:test"; -import { execContainer, readFileContainer, runTerraformInit, runTerraformApply, writeFileContainer, runContainer, removeContainer, findResourceInstance } from "~test"; -import dedent from "dedent"; - -let cleanupFunctions: (() => Promise)[] = []; -const registerCleanup = (cleanup: () => Promise) => { - cleanupFunctions.push(cleanup); -}; - -afterEach(async () => { - const cleanupFnsCopy = cleanupFunctions.slice().reverse(); - cleanupFunctions = []; - for (const cleanup of cleanupFnsCopy) { - try { - await cleanup(); - } catch (error) { - console.error("Error during cleanup:", error); - } - } -}); - -const writeExecutable = async (containerId: string, filePath: string, content: string) => { - await writeFileContainer(containerId, filePath, content, { user: "root" }); - await execContainer(containerId, ["bash", "-c", `chmod 755 ${filePath}`], ["--user", "root"]); -}; - -const loadTestFile = async (...relativePath: string[]) => { - return await Bun.file(new URL(`./testdata/${relativePath.join("/")}`, import.meta.url)).text(); -}; - -const setup = async (vars?: Record): Promise<{ id: string }> => { - const state = await runTerraformApply(import.meta.dir, { - agent_id: "foo", - install_cursor_cli: "false", - ...vars, - }); - const coderScript = findResourceInstance(state, "coder_script"); - const id = await runContainer("codercom/enterprise-node:latest"); - registerCleanup(async () => removeContainer(id)); - await writeExecutable(id, "/home/coder/script.sh", coderScript.script); - await writeExecutable(id, "/usr/bin/cursor", await loadTestFile("cursor-mock.sh")); - return { id }; -}; - -setDefaultTimeout(60 * 1000); - -describe("cursor-cli", async () => { - beforeAll(async () => { - await runTerraformInit(import.meta.dir); - }); - - test("happy-path-interactive", async () => { - const { id } = await setup(); - const resp = await execContainer(id, ["bash", "-c", "cd /home/coder && ./script.sh"]); - if (resp.exitCode !== 0) { - console.log(resp.stdout); - console.log(resp.stderr); - } - expect(resp.exitCode).toBe(0); - const startLog = await readFileContainer(id, "/home/coder/.cursor-cli-module/start.log"); - expect(startLog).toContain("agent"); - expect(startLog).toContain("--interactive"); - }); - - test("non-interactive-with-cmd", async () => { - const { id } = await setup({ interactive: "false", non_interactive_cmd: "run --once" }); - const resp = await execContainer(id, ["bash", "-c", "cd /home/coder && ./script.sh"]); - expect(resp.exitCode).toBe(0); - const startLog = await readFileContainer(id, "/home/coder/.cursor-cli-module/start.log"); - expect(startLog).toContain("run"); - expect(startLog).toContain("--once"); - expect(startLog).not.toContain("--interactive"); - }); - - test("model-and-force-and-extra-args", async () => { - const { id } = await setup({ model: "test-model", force: "true" }); - const resp = await execContainer(id, ["bash", "-c", "cd /home/coder && ./script.sh"], ["--env", "TF_VAR_extra_args=--foo\nbar"]); - expect(resp.exitCode).toBe(0); - const startLog = await readFileContainer(id, "/home/coder/.cursor-cli-module/start.log"); - expect(startLog).toContain("--model"); - expect(startLog).toContain("test-model"); - expect(startLog).toContain("--force"); - }); - - test("additional-settings-merge", async () => { - const settings = dedent` - {"mcpServers": {"coder": {"command": "coder", "args": ["exp","mcp","server"], "type": "stdio"}}} - `; - const { id } = await setup({ additional_settings: settings }); - const resp = await execContainer(id, ["bash", "-c", "cd /home/coder && ./script.sh"]); - expect(resp.exitCode).toBe(0); - const cfg = await readFileContainer(id, "/home/coder/.cursor/settings.json"); - expect(cfg).toContain("mcpServers"); - expect(cfg).toContain("coder"); - }); -}); From a075139b01f5488c251791cf6410feb69ac6f57d Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Fri, 8 Aug 2025 23:59:02 +0500 Subject: [PATCH 04/48] refactor(cursor-cli): drop extra_args, binary_name, base_command, additional_settings; simplify non-interactive run --- .../modules/cursor-cli/cursor-cli.tftest.hcl | 57 +++---------------- .../coder-labs/modules/cursor-cli/main.tf | 27 --------- .../modules/cursor-cli/scripts/start.sh | 7 --- 3 files changed, 7 insertions(+), 84 deletions(-) diff --git a/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl b/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl index 61e3a7c8b..17bdc9b0d 100644 --- a/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl +++ b/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl @@ -9,8 +9,8 @@ run "defaults_noninteractive" { } assert { - condition = can(regex("BINARY_NAME='cursor-agent'", resource.coder_script.cursor_cli.script)) - error_message = "Expected default binary_name to be cursor-agent" + condition = can(regex("Cursor CLI", resource.coder_script.cursor_cli.display_name)) + error_message = "Expected coder_script to be created" } } @@ -19,15 +19,13 @@ run "non_interactive_mode" { variables { agent_id = "test-agent" - base_command = "status" - extra_args = ["--dry-run"] output_format = "json" } assert { - // base command and -p --output-format json are included in env - condition = can(regex("BASE_COMMAND='status'", resource.coder_script.cursor_cli.script)) - error_message = "Expected BASE_COMMAND to be propagated" + // non-interactive always prints; output format propagates + condition = can(regex("OUTPUT_FORMAT='json'", resource.coder_script.cursor_cli.script)) + error_message = "Expected OUTPUT_FORMAT to be propagated" } } @@ -55,36 +53,13 @@ run "additional_settings_propagated" { command = plan variables { - agent_id = "test-agent" - additional_settings = jsonencode({ - mcpServers = { - coder = { - command = "coder" - args = ["exp", "mcp", "server"] - type = "stdio" - } - } - }) + agent_id = "test-agent" mcp_json = jsonencode({ mcpServers = { foo = { command = "foo", type = "stdio" } } }) rules_files = { "global.yml" = "version: 1\nrules:\n - name: global\n include: ['**/*']\n description: global rule" } } - // Ensure the encoded settings are passed into the install invocation - assert { - condition = can(regex(base64encode(jsonencode({ - mcpServers = { - coder = { - command = "coder" - args = ["exp", "mcp", "server"] - type = "stdio" - } - } - })), resource.coder_script.cursor_cli.script)) - error_message = "Expected ADDITIONAL_SETTINGS (base64) to be in the install step" - } - // Ensure project mcp_json is passed assert { condition = can(regex(base64encode(jsonencode({ mcpServers = { foo = { command = "foo", type = "stdio" } } })), resource.coder_script.cursor_cli.script)) @@ -98,16 +73,13 @@ run "additional_settings_propagated" { } } -run "output_api_key_binary_basecmd_extra" { +run "output_api_key" { command = plan variables { agent_id = "test-agent" output_format = "json" api_key = "sk-test-123" - binary_name = "cursor-agent" - base_command = "status" - extra_args = ["--foo", "bar"] } assert { @@ -119,19 +91,4 @@ run "output_api_key_binary_basecmd_extra" { condition = can(regex("API_KEY_SECRET='sk-test-123'", resource.coder_script.cursor_cli.script)) error_message = "Expected API key to be plumbed (to CURSOR_API_KEY at runtime)" } - - assert { - condition = can(regex("BINARY_NAME='cursor-agent'", resource.coder_script.cursor_cli.script)) - error_message = "Expected binary name to be forwarded" - } - - assert { - condition = can(regex("BASE_COMMAND='status'", resource.coder_script.cursor_cli.script)) - error_message = "Expected base command to be forwarded" - } - - assert { - condition = can(regex(base64encode("--foo\nbar"), resource.coder_script.cursor_cli.script)) - error_message = "Expected extra args to be base64 encoded and passed" - } } diff --git a/registry/coder-labs/modules/cursor-cli/main.tf b/registry/coder-labs/modules/cursor-cli/main.tf index 1de313d77..613400867 100644 --- a/registry/coder-labs/modules/cursor-cli/main.tf +++ b/registry/coder-labs/modules/cursor-cli/main.tf @@ -82,29 +82,6 @@ variable "api_key" { sensitive = true } -variable "extra_args" { - type = list(string) - description = "Additional args to pass to the Cursor CLI." - default = [] -} - -variable "binary_name" { - type = string - description = "Cursor Agent binary name (default: cursor-agent)." - default = "cursor-agent" -} - -variable "base_command" { - type = string - description = "Base Cursor CLI command to run (default: none for chat)." - default = "" -} - -variable "additional_settings" { - type = string - description = "JSON to merge into ~/.cursor/settings.json (e.g., mcpServers)." - default = "" -} variable "mcp_json" { type = string @@ -138,7 +115,6 @@ resource "coder_script" "cursor_cli" { chmod +x /tmp/install.sh ARG_INSTALL='${var.install_cursor_cli}' \ ARG_VERSION='${var.cursor_cli_version}' \ - ADDITIONAL_SETTINGS='${base64encode(replace(var.additional_settings, "'", "'\\''"))}' \ PROJECT_MCP_JSON='${var.mcp_json != null ? base64encode(replace(var.mcp_json, "'", "'\\''")) : ""}' \ PROJECT_RULES_JSON='${var.rules_files != null ? base64encode(jsonencode(var.rules_files)) : ""}' \ MODULE_DIR_NAME='${local.module_dir_name}' \ @@ -148,15 +124,12 @@ resource "coder_script" "cursor_cli" { echo -n '${base64encode(local.start_script)}' | base64 -d > /tmp/start.sh chmod +x /tmp/start.sh # Non-interactive mode by design - BASE_COMMAND='${var.base_command}' \ FORCE='${var.force}' \ MODEL='${var.model}' \ OUTPUT_FORMAT='${var.output_format}' \ API_KEY_SECRET='${var.api_key}' \ - EXTRA_ARGS='${base64encode(join("\n", var.extra_args))}' \ MODULE_DIR_NAME='${local.module_dir_name}' \ FOLDER='${var.folder}' \ - BINARY_NAME='${var.binary_name}' \ /tmp/start.sh | tee "$HOME/${local.module_dir_name}/start.log" EOT run_on_start = true diff --git a/registry/coder-labs/modules/cursor-cli/scripts/start.sh b/registry/coder-labs/modules/cursor-cli/scripts/start.sh index 8b8f19b07..09782a26f 100644 --- a/registry/coder-labs/modules/cursor-cli/scripts/start.sh +++ b/registry/coder-labs/modules/cursor-cli/scripts/start.sh @@ -13,15 +13,12 @@ FORCE=${FORCE:-false} MODEL=${MODEL:-} OUTPUT_FORMAT=${OUTPUT_FORMAT:-json} API_KEY_SECRET=${API_KEY_SECRET:-} -EXTRA_ARGS_BASE64=${EXTRA_ARGS:-} MODULE_DIR_NAME=${MODULE_DIR_NAME:-.cursor-cli-module} FOLDER=${FOLDER:-$HOME} BINARY_NAME=${BINARY_NAME:-cursor-agent} mkdir -p "$HOME/$MODULE_DIR_NAME" -# Decode EXTRA_ARGS lines into an array -IFS=$'\n' read -r -d '' -a EXTRA_ARR < <(echo -n "$EXTRA_ARGS_BASE64" | base64 -d; printf '\0') || true # Find cursor agent cli if command_exists "$BINARY_NAME"; then @@ -67,10 +64,6 @@ if [ -n "$NON_INTERACTIVE_CMD" ]; then ARGS+=("${CMD_PARTS[@]}") fi -# Extra args, if any -if [ ${#EXTRA_ARR[@]} -gt 0 ]; then - ARGS+=("${EXTRA_ARR[@]}") -fi # Set API key env if provided if [ -n "$API_KEY_SECRET" ]; then From 2a65de4d9104c78c93c98c06e7610966909fd6b8 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Sat, 9 Aug 2025 00:07:11 +0500 Subject: [PATCH 05/48] chore: format cursor-cli module and tests --- registry/coder-labs/modules/cursor-cli/README.md | 16 ++++++++-------- .../modules/cursor-cli/cursor-cli.tftest.hcl | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/registry/coder-labs/modules/cursor-cli/README.md b/registry/coder-labs/modules/cursor-cli/README.md index 7ded98120..6d4bbf96b 100644 --- a/registry/coder-labs/modules/cursor-cli/README.md +++ b/registry/coder-labs/modules/cursor-cli/README.md @@ -22,14 +22,14 @@ module "cursor_cli" { agent_id = coder_agent.example.id # Optional - folder = "/home/coder/project" - install_cursor_cli = true - cursor_cli_version = "latest" - base_command = "status" # optional subcommand (default is chat mode) - output_format = "json" # text | json | stream-json - force = false - model = "gpt-5" - mcp_json = jsonencode({ + folder = "/home/coder/project" + install_cursor_cli = true + cursor_cli_version = "latest" + base_command = "status" # optional subcommand (default is chat mode) + output_format = "json" # text | json | stream-json + force = false + model = "gpt-5" + mcp_json = jsonencode({ mcpServers = { # example project-specific servers (see docs) } diff --git a/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl b/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl index 17bdc9b0d..6120dd8e1 100644 --- a/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl +++ b/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl @@ -18,8 +18,8 @@ run "non_interactive_mode" { command = plan variables { - agent_id = "test-agent" - output_format = "json" + agent_id = "test-agent" + output_format = "json" } assert { @@ -53,8 +53,8 @@ run "additional_settings_propagated" { command = plan variables { - agent_id = "test-agent" - mcp_json = jsonencode({ mcpServers = { foo = { command = "foo", type = "stdio" } } }) + agent_id = "test-agent" + mcp_json = jsonencode({ mcpServers = { foo = { command = "foo", type = "stdio" } } }) rules_files = { "global.yml" = "version: 1\nrules:\n - name: global\n include: ['**/*']\n description: global rule" } @@ -68,7 +68,7 @@ run "additional_settings_propagated" { // Ensure rules map is passed assert { - condition = can(regex(base64encode(jsonencode({"global.yml":"version: 1\nrules:\n - name: global\n include: ['**/*']\n description: global rule"})), resource.coder_script.cursor_cli.script)) + condition = can(regex(base64encode(jsonencode({ "global.yml" : "version: 1\nrules:\n - name: global\n include: ['**/*']\n description: global rule" })), resource.coder_script.cursor_cli.script)) error_message = "Expected PROJECT_RULES_JSON (base64) to be in the install step" } } @@ -77,9 +77,9 @@ run "output_api_key" { command = plan variables { - agent_id = "test-agent" - output_format = "json" - api_key = "sk-test-123" + agent_id = "test-agent" + output_format = "json" + api_key = "sk-test-123" } assert { From 5e3f55558a04ef18ba0305b17c926539a2131c11 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Sat, 9 Aug 2025 00:51:29 +0500 Subject: [PATCH 06/48] feat(cursor-cli): configure Coder MCP status slug in module; docs cleanup; remove unused options --- .../coder-labs/modules/cursor-cli/README.md | 20 +++---------------- .../modules/cursor-cli/scripts/install.sh | 15 +++++++------- .../modules/cursor-cli/scripts/start.sh | 11 +++++----- 3 files changed, 15 insertions(+), 31 deletions(-) diff --git a/registry/coder-labs/modules/cursor-cli/README.md b/registry/coder-labs/modules/cursor-cli/README.md index 6d4bbf96b..81ae517d0 100644 --- a/registry/coder-labs/modules/cursor-cli/README.md +++ b/registry/coder-labs/modules/cursor-cli/README.md @@ -12,8 +12,8 @@ Run the Cursor Coding Agent in your workspace using the Cursor CLI directly. Thi - Runs non-interactive (autonomous) by default, using `-p` (print) - Supports `--force` runs -- Allows configuring MCP servers and project MCP (`~/.cursor/settings.json` and `/.cursor/mcp.json`) -- Lets you choose a model and pass extra CLI arguments +- Configures Coder MCP task reporting (sets `CODER_MCP_APP_STATUS_SLUG`), and supports project MCP via `/.cursor/mcp.json` +- Lets you choose a model ```tf module "cursor_cli" { @@ -25,7 +25,6 @@ module "cursor_cli" { folder = "/home/coder/project" install_cursor_cli = true cursor_cli_version = "latest" - base_command = "status" # optional subcommand (default is chat mode) output_format = "json" # text | json | stream-json force = false model = "gpt-5" @@ -34,26 +33,13 @@ module "cursor_cli" { # example project-specific servers (see docs) } }) - additional_settings = jsonencode({ - mcpServers = { - coder = { - command = "coder" - args = ["exp", "mcp", "server"] - type = "stdio" - name = "Coder" - env = {} - enabled = true - } - } - }) - extra_args = ["--verbose"] } ``` ## Notes - See Cursor CLI docs: `https://docs.cursor.com/en/cli/overview` -- For MCP project config, see `https://docs.cursor.com/en/context/mcp#using-mcp-json`. This module writes your `mcp_json` into `/.cursor/mcp.json` and merges `additional_settings` into `~/.cursor/settings.json`. +- For MCP project config, see `https://docs.cursor.com/en/context/mcp#using-mcp-json`. This module writes your `mcp_json` into `/.cursor/mcp.json`. - For Rules, see `https://docs.cursor.com/en/context/rules#project-rules`. Provide `rules_files` (map of file name to content) to populate `/.cursor/rules/`. - The agent runs non-interactively with `-p` by default. Use `output_format` to choose `text | json | stream-json` (default `json`). diff --git a/registry/coder-labs/modules/cursor-cli/scripts/install.sh b/registry/coder-labs/modules/cursor-cli/scripts/install.sh index 0a4bc4259..29f93a540 100644 --- a/registry/coder-labs/modules/cursor-cli/scripts/install.sh +++ b/registry/coder-labs/modules/cursor-cli/scripts/install.sh @@ -15,7 +15,6 @@ FOLDER=${FOLDER:-$HOME} mkdir -p "$HOME/$MODULE_DIR_NAME" -ADDITIONAL_SETTINGS=$(echo -n "$ADDITIONAL_SETTINGS" | base64 -d) PROJECT_MCP_JSON=$(echo -n "$PROJECT_MCP_JSON" | base64 -d) PROJECT_RULES_JSON=$(echo -n "$PROJECT_RULES_JSON" | base64 -d) @@ -74,10 +73,16 @@ if [ "$ARG_INSTALL" = "true" ]; then echo "Installed cursor-agent: $(command -v cursor-agent || true)" | tee -a "$HOME/$MODULE_DIR_NAME/install.log" fi -# Ensure settings path exists and merge additional_settings JSON +# Ensure settings path exists and set status slug for Coder MCP tools SETTINGS_PATH="$HOME/.cursor/settings.json" mkdir -p "$(dirname "$SETTINGS_PATH")" +# Ensure status slug env is exported for downstream processes +if [ -n "${STATUS_SLUG:-}" ]; then + echo "export CODER_MCP_APP_STATUS_SLUG=$STATUS_SLUG" >> "$HOME/.bashrc" + export CODER_MCP_APP_STATUS_SLUG="$STATUS_SLUG" +fi + # Write project-specific MCP if provided if [ -n "$PROJECT_MCP_JSON" ]; then TARGET_DIR="$FOLDER/.cursor" @@ -104,11 +109,5 @@ if [ ! -f "$SETTINGS_PATH" ]; then echo '{}' > "$SETTINGS_PATH" fi -if [ -n "$ADDITIONAL_SETTINGS" ]; then - echo "Merging additional settings into $SETTINGS_PATH" | tee -a "$HOME/$MODULE_DIR_NAME/install.log" - TMP_SETTINGS=$(mktemp) - # Merge JSON: deep merge mcpServers and top-level keys - jq --argjson add "$ADDITIONAL_SETTINGS" 'def deepmerge(a;b): reduce (b|keys[]) as $key (a; .[$key] = if ( (.[ $key ]|type?) == "object" and (b[$key]|type?) == "object" ) then deepmerge(.[ $key ]; b[$key]) else b[$key] end); deepmerge(.;$add)' "$SETTINGS_PATH" > "$TMP_SETTINGS" && mv "$TMP_SETTINGS" "$SETTINGS_PATH" -fi exit 0 diff --git a/registry/coder-labs/modules/cursor-cli/scripts/start.sh b/registry/coder-labs/modules/cursor-cli/scripts/start.sh index 09782a26f..a19a7f6c0 100644 --- a/registry/coder-labs/modules/cursor-cli/scripts/start.sh +++ b/registry/coder-labs/modules/cursor-cli/scripts/start.sh @@ -15,18 +15,17 @@ OUTPUT_FORMAT=${OUTPUT_FORMAT:-json} API_KEY_SECRET=${API_KEY_SECRET:-} MODULE_DIR_NAME=${MODULE_DIR_NAME:-.cursor-cli-module} FOLDER=${FOLDER:-$HOME} -BINARY_NAME=${BINARY_NAME:-cursor-agent} mkdir -p "$HOME/$MODULE_DIR_NAME" # Find cursor agent cli -if command_exists "$BINARY_NAME"; then - CURSOR_CMD="$BINARY_NAME" -elif [ -x "$HOME/.local/bin/$BINARY_NAME" ]; then - CURSOR_CMD="$HOME/.local/bin/$BINARY_NAME" +if command_exists cursor-agent; then + CURSOR_CMD=cursor-agent +elif [ -x "$HOME/.local/bin/cursor-agent" ]; then + CURSOR_CMD="$HOME/.local/bin/cursor-agent" else - echo "Error: $BINARY_NAME not found. Install it or set install_cursor_cli=true." | tee -a "$HOME/$MODULE_DIR_NAME/start.log" + echo "Error: cursor-agent not found. Install it or set install_cursor_cli=true." | tee -a "$HOME/$MODULE_DIR_NAME/start.log" exit 1 fi From 6f46bc9552baeca4ccfa9b6455bf8bf5e8ad25fc Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Sat, 9 Aug 2025 00:54:04 +0500 Subject: [PATCH 07/48] chore(cursor-cli): move env to coder_env; update scripts --- registry/coder-labs/modules/cursor-cli/README.md | 2 +- registry/coder-labs/modules/cursor-cli/main.tf | 15 ++++++++++++++- .../modules/cursor-cli/scripts/start.sh | 5 ----- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/registry/coder-labs/modules/cursor-cli/README.md b/registry/coder-labs/modules/cursor-cli/README.md index 81ae517d0..7dd75a545 100644 --- a/registry/coder-labs/modules/cursor-cli/README.md +++ b/registry/coder-labs/modules/cursor-cli/README.md @@ -25,7 +25,7 @@ module "cursor_cli" { folder = "/home/coder/project" install_cursor_cli = true cursor_cli_version = "latest" - output_format = "json" # text | json | stream-json + output_format = "json" # text | json | stream-json force = false model = "gpt-5" mcp_json = jsonencode({ diff --git a/registry/coder-labs/modules/cursor-cli/main.tf b/registry/coder-labs/modules/cursor-cli/main.tf index 613400867..250f975ab 100644 --- a/registry/coder-labs/modules/cursor-cli/main.tf +++ b/registry/coder-labs/modules/cursor-cli/main.tf @@ -102,6 +102,20 @@ locals { module_dir_name = ".cursor-cli-module" } +# Expose status slug and API key to the agent environment +resource "coder_env" "status_slug" { + agent_id = var.agent_id + name = "CODER_MCP_APP_STATUS_SLUG" + value = local.app_slug +} + +resource "coder_env" "cursor_api_key" { + count = var.api_key != "" ? 1 : 0 + agent_id = var.agent_id + name = "CURSOR_API_KEY" + value = var.api_key +} + resource "coder_script" "cursor_cli" { agent_id = var.agent_id display_name = "Cursor CLI" @@ -127,7 +141,6 @@ resource "coder_script" "cursor_cli" { FORCE='${var.force}' \ MODEL='${var.model}' \ OUTPUT_FORMAT='${var.output_format}' \ - API_KEY_SECRET='${var.api_key}' \ MODULE_DIR_NAME='${local.module_dir_name}' \ FOLDER='${var.folder}' \ /tmp/start.sh | tee "$HOME/${local.module_dir_name}/start.log" diff --git a/registry/coder-labs/modules/cursor-cli/scripts/start.sh b/registry/coder-labs/modules/cursor-cli/scripts/start.sh index a19a7f6c0..c926ad7b2 100644 --- a/registry/coder-labs/modules/cursor-cli/scripts/start.sh +++ b/registry/coder-labs/modules/cursor-cli/scripts/start.sh @@ -12,7 +12,6 @@ NON_INTERACTIVE_CMD=${NON_INTERACTIVE_CMD:-} FORCE=${FORCE:-false} MODEL=${MODEL:-} OUTPUT_FORMAT=${OUTPUT_FORMAT:-json} -API_KEY_SECRET=${API_KEY_SECRET:-} MODULE_DIR_NAME=${MODULE_DIR_NAME:-.cursor-cli-module} FOLDER=${FOLDER:-$HOME} @@ -64,10 +63,6 @@ if [ -n "$NON_INTERACTIVE_CMD" ]; then fi -# Set API key env if provided -if [ -n "$API_KEY_SECRET" ]; then - export CURSOR_API_KEY="$API_KEY_SECRET" -fi # Log and exec printf "Running: %q %s\n" "$CURSOR_CMD" "$(printf '%q ' "${ARGS[@]}")" | tee -a "$HOME/$MODULE_DIR_NAME/start.log" From ce902f6bf1d5503ae622de25ac137b3cbc1b091b Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Sat, 9 Aug 2025 01:55:34 +0500 Subject: [PATCH 08/48] wip --- .../modules/cursor-cli/cursor-cli.tftest.hcl | 22 +-- .../modules/cursor-cli/main.test.ts | 142 ++++++++++++++++++ .../coder-labs/modules/cursor-cli/main.tf | 5 +- .../modules/cursor-cli/scripts/install.sh | 74 +++------ .../modules/cursor-cli/scripts/start.sh | 11 +- .../cursor-cli/testdata/cursor-mock.sh | 5 - .../coder/modules/claude-code/main.test.ts | 2 +- 7 files changed, 184 insertions(+), 77 deletions(-) create mode 100644 registry/coder-labs/modules/cursor-cli/main.test.ts delete mode 100644 registry/coder-labs/modules/cursor-cli/testdata/cursor-mock.sh diff --git a/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl b/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl index 6120dd8e1..537101d74 100644 --- a/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl +++ b/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl @@ -1,11 +1,12 @@ // Terraform tests for the cursor-cli module // Validates that we render expected script content given inputs -run "defaults_noninteractive" { +run "defaults" { command = plan variables { agent_id = "test-agent" + folder = "/home/coder" } assert { @@ -19,6 +20,7 @@ run "non_interactive_mode" { variables { agent_id = "test-agent" + folder = "/home/coder" output_format = "json" } @@ -34,6 +36,7 @@ run "model_and_force" { variables { agent_id = "test-agent" + folder = "/home/coder" model = "test-model" force = true } @@ -54,6 +57,7 @@ run "additional_settings_propagated" { variables { agent_id = "test-agent" + folder = "/home/coder" mcp_json = jsonencode({ mcpServers = { foo = { command = "foo", type = "stdio" } } }) rules_files = { "global.yml" = "version: 1\nrules:\n - name: global\n include: ['**/*']\n description: global rule" @@ -73,22 +77,22 @@ run "additional_settings_propagated" { } } -run "output_api_key" { +run "api_key_env_var" { command = plan variables { - agent_id = "test-agent" - output_format = "json" - api_key = "sk-test-123" + agent_id = "test-agent" + folder = "/home/coder" + api_key = "sk-test-123" } assert { - condition = can(regex("OUTPUT_FORMAT='json'", resource.coder_script.cursor_cli.script)) - error_message = "Expected output format to be passed" + condition = resource.coder_env.cursor_api_key[0].name == "CURSOR_API_KEY" + error_message = "Expected CURSOR_API_KEY env to be created when api_key is set" } assert { - condition = can(regex("API_KEY_SECRET='sk-test-123'", resource.coder_script.cursor_cli.script)) - error_message = "Expected API key to be plumbed (to CURSOR_API_KEY at runtime)" + condition = resource.coder_env.cursor_api_key[0].value == "sk-test-123" + error_message = "Expected CURSOR_API_KEY env value to be set from api_key" } } diff --git a/registry/coder-labs/modules/cursor-cli/main.test.ts b/registry/coder-labs/modules/cursor-cli/main.test.ts new file mode 100644 index 000000000..e42de853c --- /dev/null +++ b/registry/coder-labs/modules/cursor-cli/main.test.ts @@ -0,0 +1,142 @@ +import { afterEach, beforeAll, describe, expect, setDefaultTimeout, test } from "bun:test"; +import { execContainer, runTerraformInit, writeFileContainer } from "~test"; +import { execModuleScript } from "../../../coder/modules/agentapi/test-util"; +import { setupContainer, writeExecutable } from "../../../coder/modules/agentapi/test-util"; + +let cleanupFns: (() => Promise)[] = []; +const registerCleanup = (fn: () => Promise) => cleanupFns.push(fn); + +afterEach(async () => { + const fns = cleanupFns.slice().reverse(); + cleanupFns = []; + for (const fn of fns) { + try { + await fn(); + } catch (err) { + console.error(err); + } + } +}); + +const setup = async (vars?: Record) => { + const projectDir = "/home/coder/project"; + const { id, coderScript, cleanup } = await setupContainer({ + moduleDir: import.meta.dir, + image: "codercom/enterprise-minimal:latest", + vars: { + folder: projectDir, + install_cursor_cli: "false", + ...vars, + }, + }); + registerCleanup(cleanup); + // Ensure project dir exists + await execContainer(id, ["bash", "-c", `mkdir -p '${projectDir}'`]); + // Write the module's script to the container + await writeExecutable({ + containerId: id, + filePath: "/home/coder/script.sh", + content: coderScript.script, + }); + return { id, projectDir }; +}; + +setDefaultTimeout(180 * 1000); + +describe("cursor-cli", async () => { + beforeAll(async () => { + await runTerraformInit(import.meta.dir); + }); + + test("installs Cursor via official installer and runs --help", async () => { + const { id } = await setup({ install_cursor_cli: "true" }); + const resp = await execModuleScript(id, { NON_INTERACTIVE_CMD: "--help" }); + expect(resp.exitCode).toBe(0); + + // Verify the start log captured the invocation + const startLog = await execContainer(id, [ + "bash", + "-c", + "cat /home/coder/script.log", + ]); + expect(startLog.exitCode).toBe(0); + expect(startLog.stdout).toContain("cursor-agent"); + // Non-interactive flags are always provided + expect(startLog.stdout).toContain("-p"); + expect(startLog.stdout).toContain("--output-format json"); + }); + + test("model and force flags propagate", async () => { + const { id } = await setup({ model: "sonnet-4", force: "true" }); + await writeExecutable({ + containerId: id, + filePath: "/usr/bin/cursor-agent", + content: `#!/bin/sh\necho cursor-agent invoked\nfor a in "$@"; do echo arg:$a; done\nexit 0\n`, + }); + + const resp = await execModuleScript(id); + expect(resp.exitCode).toBe(0); + + const startLog = await execContainer(id, [ + "bash", + "-c", + "cat /home/coder/script.log", + ]); + expect(startLog.exitCode).toBe(0); + expect(startLog.stdout).toContain("-m sonnet-4"); + expect(startLog.stdout).toContain("-f"); + }); + + test("writes project mcp.json when provided", async () => { + const mcp = JSON.stringify({ mcpServers: { foo: { command: "foo", type: "stdio" } } }); + const { id, projectDir } = await setup({ mcp_json: mcp }); + await writeExecutable({ + containerId: id, + filePath: "/usr/bin/cursor-agent", + content: `#!/bin/sh\necho ok\n`, + }); + const resp = await execModuleScript(id); + expect(resp.exitCode).toBe(0); + + const mcpContent = await execContainer(id, [ + "bash", + "-c", + `cat '${projectDir}/.cursor/mcp.json'`, + ]); + expect(mcpContent.exitCode).toBe(0); + expect(mcpContent.stdout).toContain("mcpServers"); + expect(mcpContent.stdout).toContain("foo"); + }); + + test("fails when cursor-agent missing", async () => { + const { id } = await setup(); + const resp = await execModuleScript(id); + expect(resp.exitCode).not.toBe(0); + const startLog = await execContainer(id, [ + "bash", + "-c", + "cat /home/coder/script.log || true", + ]); + expect(startLog.stdout).toContain("cursor-agent not found"); + }); + + test("install step logs folder", async () => { + const { id } = await setup({ install_cursor_cli: "false" }); + await writeExecutable({ + containerId: id, + filePath: "/usr/bin/cursor-agent", + content: `#!/bin/sh\necho ok\n`, + }); + const resp = await execModuleScript(id); + expect(resp.exitCode).toBe(0); + const installLog = await execContainer(id, [ + "bash", + "-c", + "cat /home/coder/script.log", + ]); + expect(installLog.exitCode).toBe(0); + expect(installLog.stdout).toContain("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 250f975ab..eded3e65c 100644 --- a/registry/coder-labs/modules/cursor-cli/main.tf +++ b/registry/coder-labs/modules/cursor-cli/main.tf @@ -39,7 +39,6 @@ variable "icon" { variable "folder" { type = string description = "The folder to run Cursor CLI in." - default = "/home/coder" } variable "install_cursor_cli" { @@ -125,6 +124,9 @@ resource "coder_script" "cursor_cli" { set -o errexit set -o pipefail + # Ensure module log directory exists before piping logs + mkdir -p "$HOME/${local.module_dir_name}" + echo -n '${base64encode(local.install_script)}' | base64 -d > /tmp/install.sh chmod +x /tmp/install.sh ARG_INSTALL='${var.install_cursor_cli}' \ @@ -137,7 +139,6 @@ resource "coder_script" "cursor_cli" { echo -n '${base64encode(local.start_script)}' | base64 -d > /tmp/start.sh chmod +x /tmp/start.sh - # Non-interactive mode by design FORCE='${var.force}' \ MODEL='${var.model}' \ OUTPUT_FORMAT='${var.output_format}' \ diff --git a/registry/coder-labs/modules/cursor-cli/scripts/install.sh b/registry/coder-labs/modules/cursor-cli/scripts/install.sh index 29f93a540..4b184cdbc 100644 --- a/registry/coder-labs/modules/cursor-cli/scripts/install.sh +++ b/registry/coder-labs/modules/cursor-cli/scripts/install.sh @@ -26,57 +26,36 @@ PROJECT_RULES_JSON=$(echo -n "$PROJECT_RULES_JSON" | base64 -d) echo "--------------------------------" } | tee -a "$HOME/$MODULE_DIR_NAME/install.log" -# Install Cursor Agent CLI if requested. -# The docs show Cursor Agent CLI usage; we will install via npm globally. -# This requires Node/npm; install Node via NVM if not present (similar to gemini module approach). +# Install Cursor via official installer if requested if [ "$ARG_INSTALL" = "true" ]; then - echo "Installing Cursor Agent CLI..." | tee -a "$HOME/$MODULE_DIR_NAME/install.log" - - install_node() { - if ! command_exists npm; then - if ! command_exists node; then - export NVM_DIR="$HOME/.nvm" - if [ ! -d "$NVM_DIR" ]; then - mkdir -p "$NVM_DIR" - curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash - [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" - else - [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" - fi - nvm install --lts - nvm use --lts - nvm alias default node - else - echo "Node is installed but npm missing; please install npm manually." | tee -a "$HOME/$MODULE_DIR_NAME/install.log" - fi - fi - } - - install_node - - # If nvm not present, create local npm global dir to avoid permissions issues - if ! command_exists nvm; then - mkdir -p "$HOME/.npm-global" - npm config set prefix "$HOME/.npm-global" - export PATH="$HOME/.npm-global/bin:$PATH" - if ! grep -q "export PATH=$HOME/.npm-global/bin:\$PATH" "$HOME/.bashrc" 2>/dev/null; then - echo "export PATH=$HOME/.npm-global/bin:\$PATH" >> "$HOME/.bashrc" - fi + echo "Installing Cursor via official installer..." | tee -a "$HOME/$MODULE_DIR_NAME/install.log" + set +e + curl https://cursor.com/install -fsS | bash 2>&1 | tee -a "$HOME/$MODULE_DIR_NAME/install.log" + CURL_EXIT=${PIPESTATUS[0]} + set -e + if [ $CURL_EXIT -ne 0 ]; then + echo "Cursor installer failed with exit code $CURL_EXIT" | tee -a "$HOME/$MODULE_DIR_NAME/install.log" fi - if [ -n "$ARG_VERSION" ] && [ "$ARG_VERSION" != "latest" ]; then - npm install -g "cursor-agent@$ARG_VERSION" 2>&1 | tee -a "$HOME/$MODULE_DIR_NAME/install.log" - else - npm install -g cursor-agent 2>&1 | tee -a "$HOME/$MODULE_DIR_NAME/install.log" + # Ensure binaries are discoverable; create stable symlink to cursor-agent + CANDIDATES=( + "$(command -v cursor-agent || true)" + "$HOME/.cursor/bin/cursor-agent" + ) + FOUND_BIN="" + for c in "${CANDIDATES[@]}"; do + if [ -n "$c" ] && [ -x "$c" ]; then + FOUND_BIN="$c" + break + fi + done + mkdir -p "$HOME/.local/bin" + if [ -n "$FOUND_BIN" ]; then + ln -sf "$FOUND_BIN" "$HOME/.local/bin/cursor-agent" fi - - echo "Installed cursor-agent: $(command -v cursor-agent || true)" | tee -a "$HOME/$MODULE_DIR_NAME/install.log" + echo "Installed cursor-agent at: $(command -v cursor-agent || true) (resolved: $FOUND_BIN)" | tee -a "$HOME/$MODULE_DIR_NAME/install.log" fi -# Ensure settings path exists and set status slug for Coder MCP tools -SETTINGS_PATH="$HOME/.cursor/settings.json" -mkdir -p "$(dirname "$SETTINGS_PATH")" - # Ensure status slug env is exported for downstream processes if [ -n "${STATUS_SLUG:-}" ]; then echo "export CODER_MCP_APP_STATUS_SLUG=$STATUS_SLUG" >> "$HOME/.bashrc" @@ -104,10 +83,5 @@ if [ -n "$PROJECT_RULES_JSON" ]; then done fi -# If settings file doesn't exist, initialize basic structure -if [ ! -f "$SETTINGS_PATH" ]; then - echo '{}' > "$SETTINGS_PATH" -fi - exit 0 diff --git a/registry/coder-labs/modules/cursor-cli/scripts/start.sh b/registry/coder-labs/modules/cursor-cli/scripts/start.sh index c926ad7b2..ec91b6e89 100644 --- a/registry/coder-labs/modules/cursor-cli/scripts/start.sh +++ b/registry/coder-labs/modules/cursor-cli/scripts/start.sh @@ -38,11 +38,6 @@ fi ARGS=() -# base command: if provided, append; otherwise chat mode (no command) -if [ -n "${BASE_COMMAND:-}" ]; then - ARGS+=("${BASE_COMMAND}") -fi - # global flags if [ -n "$MODEL" ]; then ARGS+=("-m" "$MODEL") @@ -57,13 +52,9 @@ if [ -n "$OUTPUT_FORMAT" ]; then ARGS+=("--output-format" "$OUTPUT_FORMAT") fi if [ -n "$NON_INTERACTIVE_CMD" ]; then - # shellcheck disable=SC2206 - CMD_PARTS=($NON_INTERACTIVE_CMD) - ARGS+=("${CMD_PARTS[@]}") + ARGS+=("$NON_INTERACTIVE_CMD") fi - - # Log and exec printf "Running: %q %s\n" "$CURSOR_CMD" "$(printf '%q ' "${ARGS[@]}")" | tee -a "$HOME/$MODULE_DIR_NAME/start.log" exec "$CURSOR_CMD" "${ARGS[@]}" diff --git a/registry/coder-labs/modules/cursor-cli/testdata/cursor-mock.sh b/registry/coder-labs/modules/cursor-cli/testdata/cursor-mock.sh deleted file mode 100644 index fd160696a..000000000 --- a/registry/coder-labs/modules/cursor-cli/testdata/cursor-mock.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh -# minimal mock that prints args and exits -printf "cursor mock invoked with: %s\n" "$*" -# Exit successfully regardless -exit 0 diff --git a/registry/coder/modules/claude-code/main.test.ts b/registry/coder/modules/claude-code/main.test.ts index d9538d45e..9f36b3964 100644 --- a/registry/coder/modules/claude-code/main.test.ts +++ b/registry/coder/modules/claude-code/main.test.ts @@ -54,7 +54,7 @@ const setupContainer = async ({ ...vars, }); const coderScript = findResourceInstance(state, "coder_script"); - const id = await runContainer(image ?? "codercom/enterprise-node:latest"); + const id = await runContainer(image ?? "node:18-alpine"); registerCleanup(() => removeContainer(id)); return { id, coderScript }; }; From df318180d6a23aa895ae981fcd8034c1a033a208 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Sat, 9 Aug 2025 02:06:44 +0500 Subject: [PATCH 09/48] docs(cursor-cli): enhance README with examples for MCP configuration and rules files --- .../coder-labs/modules/cursor-cli/README.md | 114 +++++++++++++++++- 1 file changed, 108 insertions(+), 6 deletions(-) diff --git a/registry/coder-labs/modules/cursor-cli/README.md b/registry/coder-labs/modules/cursor-cli/README.md index 7dd75a545..a7f7079b9 100644 --- a/registry/coder-labs/modules/cursor-cli/README.md +++ b/registry/coder-labs/modules/cursor-cli/README.md @@ -8,12 +8,7 @@ tags: [agent, cursor, ai, cli] # Cursor CLI -Run the Cursor Coding Agent in your workspace using the Cursor CLI directly. This module does not use AgentAPI and executes the Cursor agent process itself. - -- Runs non-interactive (autonomous) by default, using `-p` (print) -- Supports `--force` runs -- Configures Coder MCP task reporting (sets `CODER_MCP_APP_STATUS_SLUG`), and supports project MCP via `/.cursor/mcp.json` -- Lets you choose a model +Run the Cursor Coding Agent in your workspace using the Cursor CLI directly. ```tf module "cursor_cli" { @@ -36,6 +31,113 @@ module "cursor_cli" { } ``` +## Examples + +### MCP configuration + +Minimal MCP server (writes `/.cursor/mcp.json`): + +```tf +module "cursor_cli" { + source = "registry.coder.com/coder-labs/cursor-cli/coder" + version = "0.1.0" + agent_id = coder_agent.example.id + folder = "/home/coder/project" + + mcp_json = jsonencode({ + mcpServers = { + tools = { + command = "/usr/local/bin/tools-server" + type = "stdio" + } + } + }) +} +``` + +Multiple servers with args and env: + +```tf +module "cursor_cli" { + source = "registry.coder.com/coder-labs/cursor-cli/coder" + version = "0.1.0" + agent_id = coder_agent.example.id + folder = "/workspace" + + mcp_json = jsonencode({ + mcpServers = { + search = { + command = "/usr/bin/rg" + type = "stdio" + args = ["--json"] + env = { RIPGREP_CONFIG_PATH = "/workspace/.ripgreprc" } + } + python = { + command = "/usr/bin/python3" + type = "stdio" + args = ["/workspace/tools/mcp_python_server.py"] + } + } + }) +} +``` + +### Rules + +Provide a map of file name to content; files are written to `/.cursor/rules/`. + +Single rules file: + +```tf +module "cursor_cli" { + source = "registry.coder.com/coder-labs/cursor-cli/coder" + version = "0.1.0" + agent_id = coder_agent.example.id + folder = "/home/coder/project" + + rules_files = { + "global.yml" = <<-EOT + version: 1 + rules: + - name: project + include: ['**/*'] + exclude: ['node_modules/**', '.git/**'] + description: Project-wide rules + EOT + } +} +``` + +Multiple rules files (language-specific): + +```tf +module "cursor_cli" { + source = "registry.coder.com/coder-labs/cursor-cli/coder" + version = "0.1.0" + agent_id = coder_agent.example.id + folder = "/workspace" + + rules_files = { + "python.yml" = <<-EOT + version: 1 + rules: + - name: python + include: ['**/*.py'] + description: Python-focused guidance + EOT + + "frontend.yml" = <<-EOT + version: 1 + rules: + - name: web + include: ['**/*.{ts,tsx,js,jsx,css}'] + exclude: ['**/dist/**'] + description: Frontend rules + EOT + } +} +``` + ## Notes - See Cursor CLI docs: `https://docs.cursor.com/en/cli/overview` From d8b9b2aa63d99721a05eb957a956d309aace52a9 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Sat, 9 Aug 2025 02:15:50 +0500 Subject: [PATCH 10/48] feat(cursor-cli): add pre-install and post-install script support - Introduced variables for optional pre-install and post-install scripts in the module. - Updated the main script to execute these scripts if provided. - Enhanced tests to validate the embedding of the new scripts. --- .../coder-labs/modules/cursor-cli/README.md | 15 +++----- .../modules/cursor-cli/cursor-cli.tftest.hcl | 12 ++++++ .../coder-labs/modules/cursor-cli/main.tf | 37 +++++++++++++++++-- 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/registry/coder-labs/modules/cursor-cli/README.md b/registry/coder-labs/modules/cursor-cli/README.md index a7f7079b9..864211ac3 100644 --- a/registry/coder-labs/modules/cursor-cli/README.md +++ b/registry/coder-labs/modules/cursor-cli/README.md @@ -66,16 +66,13 @@ module "cursor_cli" { mcp_json = jsonencode({ mcpServers = { - search = { - command = "/usr/bin/rg" - type = "stdio" - args = ["--json"] - env = { RIPGREP_CONFIG_PATH = "/workspace/.ripgreprc" } + playwright = { + command = "npx" + args = ["-y", "@playwright/mcp@latest", "--headless", "--isolated", "--no-sandbox"] } - python = { - command = "/usr/bin/python3" - type = "stdio" - args = ["/workspace/tools/mcp_python_server.py"] + desktop-commander = { + command = "npx" + args = ["-y", "@wonderwhy-er/desktop-commander"] } } }) diff --git a/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl b/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl index 537101d74..a95f2f0c9 100644 --- a/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl +++ b/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl @@ -62,6 +62,8 @@ run "additional_settings_propagated" { rules_files = { "global.yml" = "version: 1\nrules:\n - name: global\n include: ['**/*']\n description: global rule" } + pre_install_script = "#!/bin/bash\necho pre-install" + post_install_script = "#!/bin/bash\necho post-install" } // Ensure project mcp_json is passed @@ -75,6 +77,16 @@ run "additional_settings_propagated" { condition = can(regex(base64encode(jsonencode({ "global.yml" : "version: 1\nrules:\n - name: global\n include: ['**/*']\n description: global rule" })), resource.coder_script.cursor_cli.script)) error_message = "Expected PROJECT_RULES_JSON (base64) to be in the install step" } + + // Ensure pre/post install scripts are embedded + assert { + condition = can(regex(base64encode("#!/bin/bash\necho pre-install"), resource.coder_script.cursor_cli.script)) + error_message = "Expected pre-install script to be embedded" + } + assert { + condition = can(regex(base64encode("#!/bin/bash\necho post-install"), resource.coder_script.cursor_cli.script)) + error_message = "Expected post-install script to be embedded" + } } run "api_key_env_var" { diff --git a/registry/coder-labs/modules/cursor-cli/main.tf b/registry/coder-labs/modules/cursor-cli/main.tf index eded3e65c..78c365b5c 100644 --- a/registry/coder-labs/modules/cursor-cli/main.tf +++ b/registry/coder-labs/modules/cursor-cli/main.tf @@ -94,11 +94,25 @@ variable "rules_files" { default = null } +variable "pre_install_script" { + type = string + description = "Optional script to run before installing Cursor CLI." + default = null +} + +variable "post_install_script" { + type = string + description = "Optional script to run after installing Cursor CLI." + default = null +} + locals { - app_slug = "cursor-cli" - install_script = file("${path.module}/scripts/install.sh") - start_script = file("${path.module}/scripts/start.sh") - module_dir_name = ".cursor-cli-module" + app_slug = "cursor-cli" + install_script = file("${path.module}/scripts/install.sh") + start_script = file("${path.module}/scripts/start.sh") + module_dir_name = ".cursor-cli-module" + encoded_pre_install_script = var.pre_install_script != null ? base64encode(var.pre_install_script) : "" + encoded_post_install_script = var.post_install_script != null ? base64encode(var.post_install_script) : "" } # Expose status slug and API key to the agent environment @@ -129,6 +143,13 @@ resource "coder_script" "cursor_cli" { echo -n '${base64encode(local.install_script)}' | base64 -d > /tmp/install.sh chmod +x /tmp/install.sh + # Run optional pre-install script + if [ -n "${local.encoded_pre_install_script}" ]; then + echo "${local.encoded_pre_install_script}" | base64 -d > /tmp/pre_install.sh + chmod +x /tmp/pre_install.sh + echo "[cursor-cli] running pre-install script" | tee -a "$HOME/${local.module_dir_name}/install.log" + /tmp/pre_install.sh | tee -a "$HOME/${local.module_dir_name}/pre_install.log" + fi ARG_INSTALL='${var.install_cursor_cli}' \ ARG_VERSION='${var.cursor_cli_version}' \ PROJECT_MCP_JSON='${var.mcp_json != null ? base64encode(replace(var.mcp_json, "'", "'\\''")) : ""}' \ @@ -137,6 +158,14 @@ resource "coder_script" "cursor_cli" { FOLDER='${var.folder}' \ /tmp/install.sh | tee "$HOME/${local.module_dir_name}/install.log" + # Run optional post-install script + if [ -n "${local.encoded_post_install_script}" ]; then + echo "${local.encoded_post_install_script}" | base64 -d > /tmp/post_install.sh + chmod +x /tmp/post_install.sh + echo "[cursor-cli] running post-install script" | tee -a "$HOME/${local.module_dir_name}/install.log" + /tmp/post_install.sh | tee -a "$HOME/${local.module_dir_name}/post_install.log" + fi + echo -n '${base64encode(local.start_script)}' | base64 -d > /tmp/start.sh chmod +x /tmp/start.sh FORCE='${var.force}' \ From 079ff1f0988f50f2502fb5b269f48195ce0a723d Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Sat, 9 Aug 2025 02:41:38 +0500 Subject: [PATCH 11/48] feat(cursor-cli): add ai_prompt variable --- registry/coder-labs/modules/cursor-cli/README.md | 10 +++++++++- .../modules/cursor-cli/cursor-cli.tftest.hcl | 6 ++++++ registry/coder-labs/modules/cursor-cli/main.test.ts | 7 ++++--- registry/coder-labs/modules/cursor-cli/main.tf | 7 +++++++ .../coder-labs/modules/cursor-cli/scripts/start.sh | 6 +++--- 5 files changed, 29 insertions(+), 7 deletions(-) diff --git a/registry/coder-labs/modules/cursor-cli/README.md b/registry/coder-labs/modules/cursor-cli/README.md index 864211ac3..0322a2adc 100644 --- a/registry/coder-labs/modules/cursor-cli/README.md +++ b/registry/coder-labs/modules/cursor-cli/README.md @@ -11,18 +11,26 @@ tags: [agent, cursor, ai, cli] Run the Cursor Coding Agent in your workspace using the Cursor CLI directly. ```tf + +data "coder_parameter" "ai_prompt" { + name = "ai_prompt" + type = "string" + default = "Write a simple hello world program in Python" +} + module "cursor_cli" { source = "registry.coder.com/coder-labs/cursor-cli/coder" version = "0.1.0" agent_id = coder_agent.example.id + folder = "/home/coder/project" # Optional - folder = "/home/coder/project" install_cursor_cli = true cursor_cli_version = "latest" output_format = "json" # text | json | stream-json force = false model = "gpt-5" + ai_prompt = data.coder_parameter.ai_prompt.value mcp_json = jsonencode({ mcpServers = { # example project-specific servers (see docs) diff --git a/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl b/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl index a95f2f0c9..0e30ed67a 100644 --- a/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl +++ b/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl @@ -22,6 +22,7 @@ run "non_interactive_mode" { agent_id = "test-agent" folder = "/home/coder" output_format = "json" + ai_prompt = "refactor the auth module to use JWT tokens" } assert { @@ -29,6 +30,11 @@ run "non_interactive_mode" { condition = can(regex("OUTPUT_FORMAT='json'", resource.coder_script.cursor_cli.script)) error_message = "Expected OUTPUT_FORMAT to be propagated" } + + assert { + condition = can(regex("AI_PROMPT='refactor the auth module to use JWT tokens'", resource.coder_script.cursor_cli.script)) + error_message = "Expected ai_prompt to be propagated via AI_PROMPT" + } } run "model_and_force" { diff --git a/registry/coder-labs/modules/cursor-cli/main.test.ts b/registry/coder-labs/modules/cursor-cli/main.test.ts index e42de853c..2d3f48094 100644 --- a/registry/coder-labs/modules/cursor-cli/main.test.ts +++ b/registry/coder-labs/modules/cursor-cli/main.test.ts @@ -49,8 +49,8 @@ describe("cursor-cli", async () => { }); test("installs Cursor via official installer and runs --help", async () => { - const { id } = await setup({ install_cursor_cli: "true" }); - const resp = await execModuleScript(id, { NON_INTERACTIVE_CMD: "--help" }); + const { id } = await setup({ install_cursor_cli: "true", ai_prompt: "--help" }); + const resp = await execModuleScript(id); expect(resp.exitCode).toBe(0); // Verify the start log captured the invocation @@ -67,7 +67,7 @@ describe("cursor-cli", async () => { }); test("model and force flags propagate", async () => { - const { id } = await setup({ model: "sonnet-4", force: "true" }); + const { id } = await setup({ model: "sonnet-4", force: "true", ai_prompt: "status" }); await writeExecutable({ containerId: id, filePath: "/usr/bin/cursor-agent", @@ -85,6 +85,7 @@ describe("cursor-cli", async () => { expect(startLog.exitCode).toBe(0); expect(startLog.stdout).toContain("-m sonnet-4"); expect(startLog.stdout).toContain("-f"); + expect(startLog.stdout).toContain("status"); }); test("writes project mcp.json when provided", async () => { diff --git a/registry/coder-labs/modules/cursor-cli/main.tf b/registry/coder-labs/modules/cursor-cli/main.tf index 78c365b5c..704fed275 100644 --- a/registry/coder-labs/modules/cursor-cli/main.tf +++ b/registry/coder-labs/modules/cursor-cli/main.tf @@ -74,6 +74,12 @@ variable "output_format" { default = "" } +variable "ai_prompt" { + type = string + description = "AI prompt/task passed to cursor-agent." + default = "" +} + variable "api_key" { type = string description = "API key (sets CURSOR_API_KEY env or pass via -a)." @@ -171,6 +177,7 @@ resource "coder_script" "cursor_cli" { FORCE='${var.force}' \ MODEL='${var.model}' \ OUTPUT_FORMAT='${var.output_format}' \ + AI_PROMPT='${var.ai_prompt}' \ MODULE_DIR_NAME='${local.module_dir_name}' \ FOLDER='${var.folder}' \ /tmp/start.sh | tee "$HOME/${local.module_dir_name}/start.log" diff --git a/registry/coder-labs/modules/cursor-cli/scripts/start.sh b/registry/coder-labs/modules/cursor-cli/scripts/start.sh index ec91b6e89..513335387 100644 --- a/registry/coder-labs/modules/cursor-cli/scripts/start.sh +++ b/registry/coder-labs/modules/cursor-cli/scripts/start.sh @@ -9,6 +9,7 @@ command_exists() { # Non-interactive autonomous mode only NON_INTERACTIVE_CMD=${NON_INTERACTIVE_CMD:-} +AI_PROMPT=${AI_PROMPT:-} FORCE=${FORCE:-false} MODEL=${MODEL:-} OUTPUT_FORMAT=${OUTPUT_FORMAT:-json} @@ -46,13 +47,12 @@ if [ "$FORCE" = "true" ]; then ARGS+=("-f") fi -# Non-interactive printing flags (always enabled) ARGS+=("-p") if [ -n "$OUTPUT_FORMAT" ]; then ARGS+=("--output-format" "$OUTPUT_FORMAT") fi -if [ -n "$NON_INTERACTIVE_CMD" ]; then - ARGS+=("$NON_INTERACTIVE_CMD") +if [ -n "$AI_PROMPT" ]; then + ARGS+=("$AI_PROMPT") fi # Log and exec From 496efad9d17132130ca76d566eff37924dd3503d Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Sat, 9 Aug 2025 02:50:42 +0500 Subject: [PATCH 12/48] feat(cursor-cli): add output for app ID in main.tf --- registry/coder-labs/modules/cursor-cli/main.tf | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/registry/coder-labs/modules/cursor-cli/main.tf b/registry/coder-labs/modules/cursor-cli/main.tf index 704fed275..9cffa62d3 100644 --- a/registry/coder-labs/modules/cursor-cli/main.tf +++ b/registry/coder-labs/modules/cursor-cli/main.tf @@ -204,3 +204,8 @@ resource "coder_app" "cursor_cli" { fi EOT } + +output "app_id" { + description = "The ID of the Cursor CLI app." + value = coder_app.cursor_cli.id +} From 538d08beca7390c5f0428565435191e1f88e0486 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Sat, 9 Aug 2025 03:11:42 +0500 Subject: [PATCH 13/48] feat(cursor-cli): update script execution to include folder variable and add post-install wait script - Modified main.tf to pass the folder variable to pre-install and post-install scripts. - Added a new post_install_script in README to wait for the repository to be ready before proceeding. --- registry/coder-labs/modules/cursor-cli/README.md | 13 +++++++++++++ registry/coder-labs/modules/cursor-cli/main.tf | 4 ++-- .../coder-labs/modules/cursor-cli/scripts/start.sh | 2 -- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/registry/coder-labs/modules/cursor-cli/README.md b/registry/coder-labs/modules/cursor-cli/README.md index 0322a2adc..fdca832c1 100644 --- a/registry/coder-labs/modules/cursor-cli/README.md +++ b/registry/coder-labs/modules/cursor-cli/README.md @@ -36,6 +36,19 @@ module "cursor_cli" { # example project-specific servers (see docs) } }) + + # Use post_install_script to wait for the repo to be ready + post_install_script = <<-EOT + #!/usr/bin/env bash + set -euo pipefail + TARGET="${FOLDER}/.git/config" + echo "[cursor-cli] waiting for ${TARGET}..." + for i in $(seq 1 600); do + [ -f "$TARGET" ] && { echo "ready"; exit 0; } + sleep 1 + done + echo "timeout waiting for ${TARGET}" >&2 + EOT } ``` diff --git a/registry/coder-labs/modules/cursor-cli/main.tf b/registry/coder-labs/modules/cursor-cli/main.tf index 9cffa62d3..27d835af3 100644 --- a/registry/coder-labs/modules/cursor-cli/main.tf +++ b/registry/coder-labs/modules/cursor-cli/main.tf @@ -154,7 +154,7 @@ resource "coder_script" "cursor_cli" { echo "${local.encoded_pre_install_script}" | base64 -d > /tmp/pre_install.sh chmod +x /tmp/pre_install.sh echo "[cursor-cli] running pre-install script" | tee -a "$HOME/${local.module_dir_name}/install.log" - /tmp/pre_install.sh | tee -a "$HOME/${local.module_dir_name}/pre_install.log" + FOLDER='${var.folder}' /tmp/pre_install.sh | tee -a "$HOME/${local.module_dir_name}/pre_install.log" fi ARG_INSTALL='${var.install_cursor_cli}' \ ARG_VERSION='${var.cursor_cli_version}' \ @@ -169,7 +169,7 @@ resource "coder_script" "cursor_cli" { echo "${local.encoded_post_install_script}" | base64 -d > /tmp/post_install.sh chmod +x /tmp/post_install.sh echo "[cursor-cli] running post-install script" | tee -a "$HOME/${local.module_dir_name}/install.log" - /tmp/post_install.sh | tee -a "$HOME/${local.module_dir_name}/post_install.log" + FOLDER='${var.folder}' /tmp/post_install.sh | tee -a "$HOME/${local.module_dir_name}/post_install.log" fi echo -n '${base64encode(local.start_script)}' | base64 -d > /tmp/start.sh diff --git a/registry/coder-labs/modules/cursor-cli/scripts/start.sh b/registry/coder-labs/modules/cursor-cli/scripts/start.sh index 513335387..f3480ff14 100644 --- a/registry/coder-labs/modules/cursor-cli/scripts/start.sh +++ b/registry/coder-labs/modules/cursor-cli/scripts/start.sh @@ -7,8 +7,6 @@ command_exists() { command -v "$1" >/dev/null 2>&1 } -# Non-interactive autonomous mode only -NON_INTERACTIVE_CMD=${NON_INTERACTIVE_CMD:-} AI_PROMPT=${AI_PROMPT:-} FORCE=${FORCE:-false} MODEL=${MODEL:-} From 0a872060982950f39417ecefb5565e15d0b22240 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Sat, 9 Aug 2025 03:27:20 +0500 Subject: [PATCH 14/48] refactor(cursor-cli): comment out unused arguments in start.sh script - Removed the `-p` argument and the conditional for `OUTPUT_FORMAT` from the script to streamline execution and avoid unnecessary options. --- registry/coder-labs/modules/cursor-cli/scripts/start.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/registry/coder-labs/modules/cursor-cli/scripts/start.sh b/registry/coder-labs/modules/cursor-cli/scripts/start.sh index f3480ff14..e191d26b4 100644 --- a/registry/coder-labs/modules/cursor-cli/scripts/start.sh +++ b/registry/coder-labs/modules/cursor-cli/scripts/start.sh @@ -45,10 +45,10 @@ if [ "$FORCE" = "true" ]; then ARGS+=("-f") fi -ARGS+=("-p") -if [ -n "$OUTPUT_FORMAT" ]; then - ARGS+=("--output-format" "$OUTPUT_FORMAT") -fi +# ARGS+=("-p") +# if [ -n "$OUTPUT_FORMAT" ]; then +# ARGS+=("--output-format" "$OUTPUT_FORMAT") +# fi if [ -n "$AI_PROMPT" ]; then ARGS+=("$AI_PROMPT") fi From 8b6b659a306ada6a44cfb0b7cdfc26912a17fa25 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Sat, 9 Aug 2025 03:44:36 +0500 Subject: [PATCH 15/48] refactor(cursor-cli): restore previously commented arguments in start.sh script - Reintroduced the `-p` argument and the conditional for `OUTPUT_FORMAT` to enhance script functionality and allow for more flexible execution options. --- registry/coder-labs/modules/cursor-cli/scripts/start.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/registry/coder-labs/modules/cursor-cli/scripts/start.sh b/registry/coder-labs/modules/cursor-cli/scripts/start.sh index e191d26b4..f3480ff14 100644 --- a/registry/coder-labs/modules/cursor-cli/scripts/start.sh +++ b/registry/coder-labs/modules/cursor-cli/scripts/start.sh @@ -45,10 +45,10 @@ if [ "$FORCE" = "true" ]; then ARGS+=("-f") fi -# ARGS+=("-p") -# if [ -n "$OUTPUT_FORMAT" ]; then -# ARGS+=("--output-format" "$OUTPUT_FORMAT") -# fi +ARGS+=("-p") +if [ -n "$OUTPUT_FORMAT" ]; then + ARGS+=("--output-format" "$OUTPUT_FORMAT") +fi if [ -n "$AI_PROMPT" ]; then ARGS+=("$AI_PROMPT") fi From 4edcb006374a77845a1124b35de84915d9ce623a Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Sat, 9 Aug 2025 03:57:57 +0500 Subject: [PATCH 16/48] refactor(cursor-cli): remove output_format variable and related script arguments - Eliminated the `output_format` variable from main.tf and removed associated logic from start.sh to simplify the script and reduce complexity. --- registry/coder-labs/modules/cursor-cli/main.tf | 7 ------- registry/coder-labs/modules/cursor-cli/scripts/start.sh | 4 ---- 2 files changed, 11 deletions(-) diff --git a/registry/coder-labs/modules/cursor-cli/main.tf b/registry/coder-labs/modules/cursor-cli/main.tf index 27d835af3..e9fa00791 100644 --- a/registry/coder-labs/modules/cursor-cli/main.tf +++ b/registry/coder-labs/modules/cursor-cli/main.tf @@ -68,12 +68,6 @@ variable "model" { default = "" } -variable "output_format" { - type = string - description = "Output format with -p: text, json, or stream-json." - default = "" -} - variable "ai_prompt" { type = string description = "AI prompt/task passed to cursor-agent." @@ -176,7 +170,6 @@ resource "coder_script" "cursor_cli" { chmod +x /tmp/start.sh FORCE='${var.force}' \ MODEL='${var.model}' \ - OUTPUT_FORMAT='${var.output_format}' \ AI_PROMPT='${var.ai_prompt}' \ MODULE_DIR_NAME='${local.module_dir_name}' \ FOLDER='${var.folder}' \ diff --git a/registry/coder-labs/modules/cursor-cli/scripts/start.sh b/registry/coder-labs/modules/cursor-cli/scripts/start.sh index f3480ff14..1fd095eca 100644 --- a/registry/coder-labs/modules/cursor-cli/scripts/start.sh +++ b/registry/coder-labs/modules/cursor-cli/scripts/start.sh @@ -45,10 +45,6 @@ if [ "$FORCE" = "true" ]; then ARGS+=("-f") fi -ARGS+=("-p") -if [ -n "$OUTPUT_FORMAT" ]; then - ARGS+=("--output-format" "$OUTPUT_FORMAT") -fi if [ -n "$AI_PROMPT" ]; then ARGS+=("$AI_PROMPT") fi From 414b5f295f9caa3f5220cb8b7831d0d97f1cda34 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Sat, 9 Aug 2025 22:53:33 +0500 Subject: [PATCH 17/48] revert(claude-code): restore main.test.ts to origin/main state --- registry/coder/modules/claude-code/main.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/coder/modules/claude-code/main.test.ts b/registry/coder/modules/claude-code/main.test.ts index 9f36b3964..d9538d45e 100644 --- a/registry/coder/modules/claude-code/main.test.ts +++ b/registry/coder/modules/claude-code/main.test.ts @@ -54,7 +54,7 @@ const setupContainer = async ({ ...vars, }); const coderScript = findResourceInstance(state, "coder_script"); - const id = await runContainer(image ?? "node:18-alpine"); + const id = await runContainer(image ?? "codercom/enterprise-node:latest"); registerCleanup(() => removeContainer(id)); return { id, coderScript }; }; From f5251898b369b2b368d9869c17dff6f7222b5230 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Sat, 9 Aug 2025 22:56:10 +0500 Subject: [PATCH 18/48] refactor(cursor-cli): remove non-interactive flags from main.test.ts --- registry/coder-labs/modules/cursor-cli/main.test.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/registry/coder-labs/modules/cursor-cli/main.test.ts b/registry/coder-labs/modules/cursor-cli/main.test.ts index 2d3f48094..e581f57e7 100644 --- a/registry/coder-labs/modules/cursor-cli/main.test.ts +++ b/registry/coder-labs/modules/cursor-cli/main.test.ts @@ -61,9 +61,6 @@ describe("cursor-cli", async () => { ]); expect(startLog.exitCode).toBe(0); expect(startLog.stdout).toContain("cursor-agent"); - // Non-interactive flags are always provided - expect(startLog.stdout).toContain("-p"); - expect(startLog.stdout).toContain("--output-format json"); }); test("model and force flags propagate", async () => { From fcd9f3aecdaf4cf278ba111fa21a4e750e2d523f Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Sat, 9 Aug 2025 23:14:19 +0500 Subject: [PATCH 19/48] refactor --- .../coder-labs/modules/cursor-cli/README.md | 18 +++++++-------- .../modules/cursor-cli/main.test.ts | 6 ++--- .../coder-labs/modules/cursor-cli/main.tf | 4 ++-- .../modules/cursor-cli/scripts/install.sh | 23 ++++++++++--------- 4 files changed, 25 insertions(+), 26 deletions(-) diff --git a/registry/coder-labs/modules/cursor-cli/README.md b/registry/coder-labs/modules/cursor-cli/README.md index fdca832c1..e5c4bd338 100644 --- a/registry/coder-labs/modules/cursor-cli/README.md +++ b/registry/coder-labs/modules/cursor-cli/README.md @@ -27,7 +27,6 @@ module "cursor_cli" { # Optional install_cursor_cli = true cursor_cli_version = "latest" - output_format = "json" # text | json | stream-json force = false model = "gpt-5" ai_prompt = data.coder_parameter.ai_prompt.value @@ -56,7 +55,7 @@ module "cursor_cli" { ### MCP configuration -Minimal MCP server (writes `/.cursor/mcp.json`): +Minimal MCP server (writes `~/.cursor/mcp.json`): ```tf module "cursor_cli" { @@ -83,7 +82,7 @@ module "cursor_cli" { source = "registry.coder.com/coder-labs/cursor-cli/coder" version = "0.1.0" agent_id = coder_agent.example.id - folder = "/workspace" + folder = "/home/coder/project" mcp_json = jsonencode({ mcpServers = { @@ -102,7 +101,7 @@ module "cursor_cli" { ### Rules -Provide a map of file name to content; files are written to `/.cursor/rules/`. +Provide a map of file name to content; files are written to `~/.cursor/rules/`. Single rules file: @@ -117,10 +116,10 @@ module "cursor_cli" { "global.yml" = <<-EOT version: 1 rules: - - name: project + - name: frontend include: ['**/*'] exclude: ['node_modules/**', '.git/**'] - description: Project-wide rules + description: Frontend rules EOT } } @@ -133,7 +132,7 @@ module "cursor_cli" { source = "registry.coder.com/coder-labs/cursor-cli/coder" version = "0.1.0" agent_id = coder_agent.example.id - folder = "/workspace" + folder = "/home/coder/project" rules_files = { "python.yml" = <<-EOT @@ -159,9 +158,8 @@ module "cursor_cli" { ## Notes - See Cursor CLI docs: `https://docs.cursor.com/en/cli/overview` -- For MCP project config, see `https://docs.cursor.com/en/context/mcp#using-mcp-json`. This module writes your `mcp_json` into `/.cursor/mcp.json`. -- For Rules, see `https://docs.cursor.com/en/context/rules#project-rules`. Provide `rules_files` (map of file name to content) to populate `/.cursor/rules/`. -- The agent runs non-interactively with `-p` by default. Use `output_format` to choose `text | json | stream-json` (default `json`). +- For MCP project config, see `https://docs.cursor.com/en/context/mcp#using-mcp-json`. This module writes your `mcp_json` into `~/.cursor/mcp.json`. +- For Rules, see `https://docs.cursor.com/en/context/rules#project-rules`. Provide `rules_files` (map of file name to content) to populate `~/.cursor/rules/`. ## Troubleshooting diff --git a/registry/coder-labs/modules/cursor-cli/main.test.ts b/registry/coder-labs/modules/cursor-cli/main.test.ts index e581f57e7..2f14ade8f 100644 --- a/registry/coder-labs/modules/cursor-cli/main.test.ts +++ b/registry/coder-labs/modules/cursor-cli/main.test.ts @@ -85,9 +85,9 @@ describe("cursor-cli", async () => { expect(startLog.stdout).toContain("status"); }); - test("writes project mcp.json when provided", async () => { + test("writes workspace mcp.json when provided", async () => { const mcp = JSON.stringify({ mcpServers: { foo: { command: "foo", type: "stdio" } } }); - const { id, projectDir } = await setup({ mcp_json: mcp }); + const { id } = await setup({ mcp_json: mcp }); await writeExecutable({ containerId: id, filePath: "/usr/bin/cursor-agent", @@ -99,7 +99,7 @@ describe("cursor-cli", async () => { const mcpContent = await execContainer(id, [ "bash", "-c", - `cat '${projectDir}/.cursor/mcp.json'`, + `cat '/home/coder/.cursor/mcp.json'`, ]); expect(mcpContent.exitCode).toBe(0); expect(mcpContent.stdout).toContain("mcpServers"); diff --git a/registry/coder-labs/modules/cursor-cli/main.tf b/registry/coder-labs/modules/cursor-cli/main.tf index e9fa00791..eec895e8c 100644 --- a/registry/coder-labs/modules/cursor-cli/main.tf +++ b/registry/coder-labs/modules/cursor-cli/main.tf @@ -152,8 +152,8 @@ resource "coder_script" "cursor_cli" { fi ARG_INSTALL='${var.install_cursor_cli}' \ ARG_VERSION='${var.cursor_cli_version}' \ - PROJECT_MCP_JSON='${var.mcp_json != null ? base64encode(replace(var.mcp_json, "'", "'\\''")) : ""}' \ - PROJECT_RULES_JSON='${var.rules_files != null ? base64encode(jsonencode(var.rules_files)) : ""}' \ + WORKSPACE_MCP_JSON='${var.mcp_json != null ? base64encode(replace(var.mcp_json, "'", "'\\''")) : ""}' \ + WORKSPACE_RULES_JSON='${var.rules_files != null ? base64encode(jsonencode(var.rules_files)) : ""}' \ MODULE_DIR_NAME='${local.module_dir_name}' \ FOLDER='${var.folder}' \ /tmp/install.sh | tee "$HOME/${local.module_dir_name}/install.log" diff --git a/registry/coder-labs/modules/cursor-cli/scripts/install.sh b/registry/coder-labs/modules/cursor-cli/scripts/install.sh index 4b184cdbc..d029a578b 100644 --- a/registry/coder-labs/modules/cursor-cli/scripts/install.sh +++ b/registry/coder-labs/modules/cursor-cli/scripts/install.sh @@ -15,8 +15,8 @@ FOLDER=${FOLDER:-$HOME} mkdir -p "$HOME/$MODULE_DIR_NAME" -PROJECT_MCP_JSON=$(echo -n "$PROJECT_MCP_JSON" | base64 -d) -PROJECT_RULES_JSON=$(echo -n "$PROJECT_RULES_JSON" | base64 -d) +WORKSPACE_MCP_JSON=$(echo -n "$WORKSPACE_MCP_JSON" | base64 -d) +WORKSPACE_RULES_JSON=$(echo -n "$WORKSPACE_RULES_JSON" | base64 -d) { echo "--------------------------------" @@ -62,19 +62,20 @@ if [ -n "${STATUS_SLUG:-}" ]; then export CODER_MCP_APP_STATUS_SLUG="$STATUS_SLUG" fi -# Write project-specific MCP if provided -if [ -n "$PROJECT_MCP_JSON" ]; then - TARGET_DIR="$FOLDER/.cursor" +# Write MCP config to user's home if provided (~/.cursor/mcp.json) +if [ -n "$WORKSPACE_MCP_JSON" ]; then + TARGET_DIR="$HOME/.cursor" + TARGET_FILE="$TARGET_DIR/mcp.json" mkdir -p "$TARGET_DIR" - echo "$PROJECT_MCP_JSON" > "$TARGET_DIR/mcp.json" - echo "Wrote project MCP to $TARGET_DIR/mcp.json" | tee -a "$HOME/$MODULE_DIR_NAME/install.log" + echo "$WORKSPACE_MCP_JSON" > "$TARGET_FILE" + echo "Wrote workspace MCP to $TARGET_FILE" | tee -a "$HOME/$MODULE_DIR_NAME/install.log" fi -# Write rules files if provided (map of name->content) -if [ -n "$PROJECT_RULES_JSON" ]; then - RULES_DIR="$FOLDER/.cursor/rules" +# Write rules files to user's home (~/.cursor/rules) +if [ -n "$WORKSPACE_RULES_JSON" ]; then + RULES_DIR="$HOME/.cursor/rules" mkdir -p "$RULES_DIR" - echo "$PROJECT_RULES_JSON" | jq -r 'to_entries[] | @base64' | while read -r entry; do + echo "$WORKSPACE_RULES_JSON" | jq -r 'to_entries[] | @base64' | while read -r entry; do _jq() { echo "${entry}" | base64 -d | jq -r ${1}; } NAME=$(_jq '.key') CONTENT=$(_jq '.value') From 058cd8ea74a4e78014ff8ae9c7bd091dc21064f6 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Sat, 9 Aug 2025 23:15:16 +0500 Subject: [PATCH 20/48] fixup! --- registry/coder-labs/modules/cursor-cli/main.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/registry/coder-labs/modules/cursor-cli/main.tf b/registry/coder-labs/modules/cursor-cli/main.tf index eec895e8c..ebfbad114 100644 --- a/registry/coder-labs/modules/cursor-cli/main.tf +++ b/registry/coder-labs/modules/cursor-cli/main.tf @@ -84,13 +84,13 @@ variable "api_key" { variable "mcp_json" { type = string - description = "Project-specific MCP JSON to write to /.cursor/mcp.json. See https://docs.cursor.com/en/context/mcp#using-mcp-json" + description = "Workspace-specific MCP JSON to write to ~/.cursor/mcp.json. See https://docs.cursor.com/en/context/mcp#using-mcp-json" default = null } variable "rules_files" { type = map(string) - description = "Optional map of rule file name to content. Files will be written to /.cursor/rules/. See https://docs.cursor.com/en/context/rules#project-rules" + description = "Optional map of rule file name to content. Files will be written to ~/.cursor/rules/. See https://docs.cursor.com/en/context/rules#project-rules" default = null } From 8d3e5d6d9bd61a94b690167c617a482af2bc4c48 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Sat, 9 Aug 2025 23:16:21 +0500 Subject: [PATCH 21/48] fixup! --- registry/coder-labs/modules/cursor-cli/main.tf | 1 - 1 file changed, 1 deletion(-) diff --git a/registry/coder-labs/modules/cursor-cli/main.tf b/registry/coder-labs/modules/cursor-cli/main.tf index ebfbad114..515ddba7d 100644 --- a/registry/coder-labs/modules/cursor-cli/main.tf +++ b/registry/coder-labs/modules/cursor-cli/main.tf @@ -81,7 +81,6 @@ variable "api_key" { sensitive = true } - variable "mcp_json" { type = string description = "Workspace-specific MCP JSON to write to ~/.cursor/mcp.json. See https://docs.cursor.com/en/context/mcp#using-mcp-json" From be5c394a11a50f55a70854c6953341c0c413f322 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Mon, 11 Aug 2025 16:33:57 +0500 Subject: [PATCH 22/48] wip --- .../coder-labs/modules/cursor-cli/README.md | 111 ++++-------------- .../modules/cursor-cli/scripts/start.sh | 4 +- 2 files changed, 25 insertions(+), 90 deletions(-) diff --git a/registry/coder-labs/modules/cursor-cli/README.md b/registry/coder-labs/modules/cursor-cli/README.md index e5c4bd338..76f535846 100644 --- a/registry/coder-labs/modules/cursor-cli/README.md +++ b/registry/coder-labs/modules/cursor-cli/README.md @@ -10,6 +10,8 @@ tags: [agent, cursor, ai, cli] Run the Cursor Coding Agent in your workspace using the Cursor CLI directly. +A full example with MCP, rules, and pre/post install scripts: + ```tf data "coder_parameter" "ai_prompt" { @@ -27,15 +29,32 @@ module "cursor_cli" { # Optional install_cursor_cli = true cursor_cli_version = "latest" - force = false + force = true model = "gpt-5" ai_prompt = data.coder_parameter.ai_prompt.value + + # Minimal MCP server (writes `~/.cursor/mcp.json`): mcp_json = jsonencode({ mcpServers = { - # example project-specific servers (see docs) + playwright = { + command = "npx" + args = ["-y", "@playwright/mcp@latest", "--headless", "--isolated", "--no-sandbox"] + } + desktop-commander = { + command = "npx" + args = ["-y", "@wonderwhy-er/desktop-commander"] + } } }) + # Use a pre_install_script to install the CLI + pre_install_script = <<-EOT + #!/usr/bin/env bash + set -euo pipefail + curl -fsSL https://deb.nodesource.com/setup_20.x | bash - + apt-get install -y nodejs + EOT + # Use post_install_script to wait for the repo to be ready post_install_script = <<-EOT #!/usr/bin/env bash @@ -48,92 +67,8 @@ module "cursor_cli" { done echo "timeout waiting for ${TARGET}" >&2 EOT -} -``` - -## Examples - -### MCP configuration - -Minimal MCP server (writes `~/.cursor/mcp.json`): - -```tf -module "cursor_cli" { - source = "registry.coder.com/coder-labs/cursor-cli/coder" - version = "0.1.0" - agent_id = coder_agent.example.id - folder = "/home/coder/project" - - mcp_json = jsonencode({ - mcpServers = { - tools = { - command = "/usr/local/bin/tools-server" - type = "stdio" - } - } - }) -} -``` - -Multiple servers with args and env: - -```tf -module "cursor_cli" { - source = "registry.coder.com/coder-labs/cursor-cli/coder" - version = "0.1.0" - agent_id = coder_agent.example.id - folder = "/home/coder/project" - - mcp_json = jsonencode({ - mcpServers = { - playwright = { - command = "npx" - args = ["-y", "@playwright/mcp@latest", "--headless", "--isolated", "--no-sandbox"] - } - desktop-commander = { - command = "npx" - args = ["-y", "@wonderwhy-er/desktop-commander"] - } - } - }) -} -``` - -### Rules - -Provide a map of file name to content; files are written to `~/.cursor/rules/`. - -Single rules file: - -```tf -module "cursor_cli" { - source = "registry.coder.com/coder-labs/cursor-cli/coder" - version = "0.1.0" - agent_id = coder_agent.example.id - folder = "/home/coder/project" - - rules_files = { - "global.yml" = <<-EOT - version: 1 - rules: - - name: frontend - include: ['**/*'] - exclude: ['node_modules/**', '.git/**'] - description: Frontend rules - EOT - } -} -``` - -Multiple rules files (language-specific): - -```tf -module "cursor_cli" { - source = "registry.coder.com/coder-labs/cursor-cli/coder" - version = "0.1.0" - agent_id = coder_agent.example.id - folder = "/home/coder/project" + # Provide a map of file name to content; files are written to `~/.cursor/rules/`. rules_files = { "python.yml" = <<-EOT version: 1 @@ -155,7 +90,7 @@ module "cursor_cli" { } ``` -## Notes +## References - See Cursor CLI docs: `https://docs.cursor.com/en/cli/overview` - For MCP project config, see `https://docs.cursor.com/en/context/mcp#using-mcp-json`. This module writes your `mcp_json` into `~/.cursor/mcp.json`. diff --git a/registry/coder-labs/modules/cursor-cli/scripts/start.sh b/registry/coder-labs/modules/cursor-cli/scripts/start.sh index 1fd095eca..d4d84a44f 100644 --- a/registry/coder-labs/modules/cursor-cli/scripts/start.sh +++ b/registry/coder-labs/modules/cursor-cli/scripts/start.sh @@ -49,6 +49,6 @@ if [ -n "$AI_PROMPT" ]; then ARGS+=("$AI_PROMPT") fi -# Log and exec +# Log and run in background, redirecting all output to the log file printf "Running: %q %s\n" "$CURSOR_CMD" "$(printf '%q ' "${ARGS[@]}")" | tee -a "$HOME/$MODULE_DIR_NAME/start.log" -exec "$CURSOR_CMD" "${ARGS[@]}" +("$CURSOR_CMD" "${ARGS[@]}" >> "$HOME/$MODULE_DIR_NAME/start.log" 2>&1) & From 73910821a35daf8a2774f2102c1f07370cea2a6c Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Tue, 12 Aug 2025 08:21:57 +0500 Subject: [PATCH 23/48] Update registry/coder-labs/modules/cursor-cli/README.md Co-authored-by: DevCats --- registry/coder-labs/modules/cursor-cli/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/registry/coder-labs/modules/cursor-cli/README.md b/registry/coder-labs/modules/cursor-cli/README.md index 76f535846..b6af73e8d 100644 --- a/registry/coder-labs/modules/cursor-cli/README.md +++ b/registry/coder-labs/modules/cursor-cli/README.md @@ -59,13 +59,13 @@ module "cursor_cli" { post_install_script = <<-EOT #!/usr/bin/env bash set -euo pipefail - TARGET="${FOLDER}/.git/config" - echo "[cursor-cli] waiting for ${TARGET}..." + TARGET="$${FOLDER}/.git/config" + echo "[cursor-cli] waiting for $${TARGET}..." for i in $(seq 1 600); do [ -f "$TARGET" ] && { echo "ready"; exit 0; } sleep 1 done - echo "timeout waiting for ${TARGET}" >&2 + echo "timeout waiting for $${TARGET}" >&2 EOT # Provide a map of file name to content; files are written to `~/.cursor/rules/`. From 62a0a9cf24c134717e3d36793b6a17620c10ff62 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Wed, 13 Aug 2025 22:49:54 +0530 Subject: [PATCH 24/48] feat: add support for agentapi --- .../modules/cursor-cli/main.test.ts | 2 +- .../coder-labs/modules/cursor-cli/main.tf | 92 ++++++++++++++++--- .../modules/cursor-cli/scripts/install.sh | 83 +++++++++++++---- .../modules/cursor-cli/scripts/start.sh | 65 +++++++++---- 4 files changed, 191 insertions(+), 51 deletions(-) diff --git a/registry/coder-labs/modules/cursor-cli/main.test.ts b/registry/coder-labs/modules/cursor-cli/main.test.ts index 2f14ade8f..590ddbab1 100644 --- a/registry/coder-labs/modules/cursor-cli/main.test.ts +++ b/registry/coder-labs/modules/cursor-cli/main.test.ts @@ -99,7 +99,7 @@ describe("cursor-cli", async () => { const mcpContent = await execContainer(id, [ "bash", "-c", - `cat '/home/coder/.cursor/mcp.json'`, + `cat '/home/coder/project/.cursor/mcp.json'`, ]); expect(mcpContent.exitCode).toBe(0); expect(mcpContent.stdout).toContain("mcpServers"); diff --git a/registry/coder-labs/modules/cursor-cli/main.tf b/registry/coder-labs/modules/cursor-cli/main.tf index 515ddba7d..4471ea917 100644 --- a/registry/coder-labs/modules/cursor-cli/main.tf +++ b/registry/coder-labs/modules/cursor-cli/main.tf @@ -53,6 +53,24 @@ variable "cursor_cli_version" { default = "latest" } +variable "enable_agentapi" { + type = bool + description = "Whether to enable the AgentAPI for Cursor CLI." + default = false +} + +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.4.0" +} + # Running mode is non-interactive by design for automation. @@ -132,6 +150,7 @@ resource "coder_script" "cursor_cli" { agent_id = var.agent_id display_name = "Cursor CLI" icon = var.icon + count = var.enable_agentapi ? 0 : 1 script = <<-EOT #!/bin/bash set -o errexit @@ -151,10 +170,10 @@ resource "coder_script" "cursor_cli" { fi ARG_INSTALL='${var.install_cursor_cli}' \ ARG_VERSION='${var.cursor_cli_version}' \ - WORKSPACE_MCP_JSON='${var.mcp_json != null ? base64encode(replace(var.mcp_json, "'", "'\\''")) : ""}' \ - WORKSPACE_RULES_JSON='${var.rules_files != null ? base64encode(jsonencode(var.rules_files)) : ""}' \ - MODULE_DIR_NAME='${local.module_dir_name}' \ - FOLDER='${var.folder}' \ + ARG_WORKSPACE_MCP_JSON='${var.mcp_json != null ? base64encode(replace(var.mcp_json, "'", "'\\''")) : ""}' \ + ARG_WORKSPACE_RULES_JSON='${var.rules_files != null ? base64encode(jsonencode(var.rules_files)) : ""}' \ + ARG_MODULE_DIR_NAME='${local.module_dir_name}' \ + ARG_FOLDER='${var.folder}' \ /tmp/install.sh | tee "$HOME/${local.module_dir_name}/install.log" # Run optional post-install script @@ -167,11 +186,11 @@ resource "coder_script" "cursor_cli" { echo -n '${base64encode(local.start_script)}' | base64 -d > /tmp/start.sh chmod +x /tmp/start.sh - FORCE='${var.force}' \ - MODEL='${var.model}' \ - AI_PROMPT='${var.ai_prompt}' \ - MODULE_DIR_NAME='${local.module_dir_name}' \ - FOLDER='${var.folder}' \ + ARG_FORCE='${var.force}' \ + ARG_MODEL='${var.model}' \ + ARG_AI_PROMPT='${base64encode(var.ai_prompt)}' \ + ARG_MODULE_DIR_NAME='${local.module_dir_name}' \ + ARG_FOLDER='${var.folder}' \ /tmp/start.sh | tee "$HOME/${local.module_dir_name}/start.log" EOT run_on_start = true @@ -181,6 +200,7 @@ resource "coder_app" "cursor_cli" { agent_id = var.agent_id slug = local.app_slug display_name = "Cursor CLI" + count = var.enable_agentapi ? 0 : 1 icon = var.icon order = var.order group = var.group @@ -197,7 +217,55 @@ resource "coder_app" "cursor_cli" { EOT } -output "app_id" { - description = "The ID of the Cursor CLI app." - value = coder_app.cursor_cli.id +module "agentapi" { + source = "registry.coder.com/coder/agentapi/coder" + version = "1.0.0" + count = var.enable_agentapi ? 1 : 0 + + agent_id = var.agent_id + web_app_slug = "${local.app_slug}-agentapi" + web_app_order = var.order + web_app_group = var.group + web_app_icon = var.icon + web_app_display_name = "Cursor CLI" + cli_app_slug = local.app_slug + cli_app_display_name = "Cursor CLI" + 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_FORCE='${var.force}' \ + ARG_MODEL='${var.model}' \ + ARG_AI_PROMPT='${base64encode(var.ai_prompt)}' \ + ARG_MODULE_DIR_NAME='${local.module_dir_name}' \ + ARG_FOLDER='${var.folder}' \ + ARG_AGENTAPI_MODE='${var.enable_agentapi}' \ + /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_INSTALL='${var.install_cursor_cli}' \ + ARG_VERSION='${var.cursor_cli_version}' \ + ARG_WORKSPACE_MCP_JSON='${var.mcp_json != null ? base64encode(replace(var.mcp_json, "'", "'\\''")) : ""}' \ + ARG_WORKSPACE_RULES_JSON='${var.rules_files != null ? base64encode(jsonencode(var.rules_files)) : ""}' \ + ARG_MODULE_DIR_NAME='${local.module_dir_name}' \ + ARG_FOLDER='${var.folder}' \ + ARG_AGENTAPI_MODE='${var.enable_agentapi}' \ + ARG_CODER_MCP_APP_STATUS_SLUG='${local.app_slug}-agentapi' \ + /tmp/install.sh + EOT } diff --git a/registry/coder-labs/modules/cursor-cli/scripts/install.sh b/registry/coder-labs/modules/cursor-cli/scripts/install.sh index d029a578b..eaf60ea6e 100644 --- a/registry/coder-labs/modules/cursor-cli/scripts/install.sh +++ b/registry/coder-labs/modules/cursor-cli/scripts/install.sh @@ -10,31 +10,36 @@ command_exists() { # Inputs ARG_INSTALL=${ARG_INSTALL:-true} ARG_VERSION=${ARG_VERSION:-latest} -MODULE_DIR_NAME=${MODULE_DIR_NAME:-.cursor-cli-module} -FOLDER=${FOLDER:-$HOME} +ARG_MODULE_DIR_NAME=${ARG_MODULE_DIR_NAME:-.cursor-cli-module} +ARG_FOLDER=${ARG_FOLDER:-$HOME} +ARG_AGENTAPI_MODE=${ARG_AGENTAPI_MODE:-false} +ARG_CODER_MCP_APP_STATUS_SLUG=${ARG_CODER_MCP_APP_STATUS_SLUG:-} -mkdir -p "$HOME/$MODULE_DIR_NAME" +mkdir -p "$HOME/$ARG_MODULE_DIR_NAME" -WORKSPACE_MCP_JSON=$(echo -n "$WORKSPACE_MCP_JSON" | base64 -d) -WORKSPACE_RULES_JSON=$(echo -n "$WORKSPACE_RULES_JSON" | base64 -d) +ARG_WORKSPACE_MCP_JSON=$(echo -n "$ARG_WORKSPACE_MCP_JSON" | base64 -d) +ARG_WORKSPACE_RULES_JSON=$(echo -n "$ARG_WORKSPACE_RULES_JSON" | base64 -d) { echo "--------------------------------" echo "install: $ARG_INSTALL" echo "version: $ARG_VERSION" - echo "folder: $FOLDER" + echo "folder: $ARG_FOLDER" + echo "agentapi_mode: $ARG_AGENTAPI_MODE" + echo "coder_mcp_app_status_slug: $ARG_CODER_MCP_APP_STATUS_SLUG" + echo "module_dir_name: $ARG_MODULE_DIR_NAME" echo "--------------------------------" -} | tee -a "$HOME/$MODULE_DIR_NAME/install.log" +} | tee -a "$HOME/$ARG_MODULE_DIR_NAME/install.log" # Install Cursor via official installer if requested if [ "$ARG_INSTALL" = "true" ]; then - echo "Installing Cursor via official installer..." | tee -a "$HOME/$MODULE_DIR_NAME/install.log" + echo "Installing Cursor via official installer..." | tee -a "$HOME/$ARG_MODULE_DIR_NAME/install.log" set +e - curl https://cursor.com/install -fsS | bash 2>&1 | tee -a "$HOME/$MODULE_DIR_NAME/install.log" + curl https://cursor.com/install -fsS | bash 2>&1 | tee -a "$HOME/$ARG_MODULE_DIR_NAME/install.log" CURL_EXIT=${PIPESTATUS[0]} set -e if [ $CURL_EXIT -ne 0 ]; then - echo "Cursor installer failed with exit code $CURL_EXIT" | tee -a "$HOME/$MODULE_DIR_NAME/install.log" + echo "Cursor installer failed with exit code $CURL_EXIT" | tee -a "$HOME/$ARG_MODULE_DIR_NAME/install.log" fi # Ensure binaries are discoverable; create stable symlink to cursor-agent @@ -53,7 +58,7 @@ if [ "$ARG_INSTALL" = "true" ]; then if [ -n "$FOUND_BIN" ]; then ln -sf "$FOUND_BIN" "$HOME/.local/bin/cursor-agent" fi - echo "Installed cursor-agent at: $(command -v cursor-agent || true) (resolved: $FOUND_BIN)" | tee -a "$HOME/$MODULE_DIR_NAME/install.log" + echo "Installed cursor-agent at: $(command -v cursor-agent || true) (resolved: $FOUND_BIN)" | tee -a "$HOME/$ARG_MODULE_DIR_NAME/install.log" fi # Ensure status slug env is exported for downstream processes @@ -62,25 +67,65 @@ if [ -n "${STATUS_SLUG:-}" ]; then export CODER_MCP_APP_STATUS_SLUG="$STATUS_SLUG" fi -# Write MCP config to user's home if provided (~/.cursor/mcp.json) -if [ -n "$WORKSPACE_MCP_JSON" ]; then - TARGET_DIR="$HOME/.cursor" +# Write MCP config to user's home if provided (ARG_FOLDER/.cursor/mcp.json) +if [ -n "$ARG_WORKSPACE_MCP_JSON" ] || [ "$ARG_AGENTAPI_MODE" = "true" ]; then + TARGET_DIR="$ARG_FOLDER/.cursor" TARGET_FILE="$TARGET_DIR/mcp.json" mkdir -p "$TARGET_DIR" - echo "$WORKSPACE_MCP_JSON" > "$TARGET_FILE" - echo "Wrote workspace MCP to $TARGET_FILE" | tee -a "$HOME/$MODULE_DIR_NAME/install.log" + + + if [ "$ARG_AGENTAPI_MODE" = "true" ]; then + + CURSOR_MCP_HACK_SCRIPT=$(cat < "/tmp/mcp-hack.sh" + chmod +x /tmp/mcp-hack.sh + + CODER_MCP=$(cat < "$TARGET_FILE" + else + echo "${ARG_WORKSPACE_MCP_JSON}" > "$TARGET_FILE" + fi + echo "Wrote workspace MCP to $TARGET_FILE" | tee -a "$HOME/$ARG_MODULE_DIR_NAME/install.log" fi # Write rules files to user's home (~/.cursor/rules) -if [ -n "$WORKSPACE_RULES_JSON" ]; then +if [ -n "$ARG_WORKSPACE_RULES_JSON" ]; then RULES_DIR="$HOME/.cursor/rules" mkdir -p "$RULES_DIR" - echo "$WORKSPACE_RULES_JSON" | jq -r 'to_entries[] | @base64' | while read -r entry; do + echo "$ARG_WORKSPACE_RULES_JSON" | jq -r 'to_entries[] | @base64' | while read -r entry; do _jq() { echo "${entry}" | base64 -d | jq -r ${1}; } NAME=$(_jq '.key') CONTENT=$(_jq '.value') echo "$CONTENT" > "$RULES_DIR/$NAME" - echo "Wrote rule: $RULES_DIR/$NAME" | tee -a "$HOME/$MODULE_DIR_NAME/install.log" + echo "Wrote rule: $RULES_DIR/$NAME" | tee -a "$HOME/$ARG_MODULE_DIR_NAME/install.log" done fi diff --git a/registry/coder-labs/modules/cursor-cli/scripts/start.sh b/registry/coder-labs/modules/cursor-cli/scripts/start.sh index d4d84a44f..83328c869 100644 --- a/registry/coder-labs/modules/cursor-cli/scripts/start.sh +++ b/registry/coder-labs/modules/cursor-cli/scripts/start.sh @@ -7,14 +7,31 @@ command_exists() { command -v "$1" >/dev/null 2>&1 } -AI_PROMPT=${AI_PROMPT:-} -FORCE=${FORCE:-false} -MODEL=${MODEL:-} -OUTPUT_FORMAT=${OUTPUT_FORMAT:-json} -MODULE_DIR_NAME=${MODULE_DIR_NAME:-.cursor-cli-module} -FOLDER=${FOLDER:-$HOME} +ARG_AI_PROMPT=$(echo -n "${ARG_AI_PROMPT:-}" | base64 -d) +ARG_FORCE=${ARG_FORCE:-false} +ARG_MODEL=${ARG_MODEL:-} +ARG_OUTPUT_FORMAT=${ARG_OUTPUT_FORMAT:-json} +ARG_MODULE_DIR_NAME=${ARG_MODULE_DIR_NAME:-.cursor-cli-module} +ARG_FOLDER=${ARG_FOLDER:-$HOME} +ARG_AGENTAPI_MODE=${ARG_AGENTAPI_MODE:-false} -mkdir -p "$HOME/$MODULE_DIR_NAME" +{ + echo "--------------------------------" + echo "install: $ARG_INSTALL" + echo "version: $ARG_VERSION" + echo "folder: $FOLDER" + echo "ai_prompt: $ARG_AI_PROMPT" + echo "force: $ARG_FORCE" + echo "model: $ARG_MODEL" + echo "output_format: $ARG_OUTPUT_FORMAT" + echo "module_dir_name: $ARG_MODULE_DIR_NAME" + echo "folder: $ARG_FOLDER" + echo "agentapi_mode: $ARG_AGENTAPI_MODE" + echo "--------------------------------" +} | tee -a "$HOME/$MODULE_DIR_NAME/start.log" + + +mkdir -p "$HOME/$ARG_MODULE_DIR_NAME" # Find cursor agent cli @@ -23,32 +40,42 @@ if command_exists cursor-agent; then elif [ -x "$HOME/.local/bin/cursor-agent" ]; then CURSOR_CMD="$HOME/.local/bin/cursor-agent" else - echo "Error: cursor-agent not found. Install it or set install_cursor_cli=true." | tee -a "$HOME/$MODULE_DIR_NAME/start.log" + echo "Error: cursor-agent not found. Install it or set install_cursor_cli=true." | tee -a "$HOME/$ARG_MODULE_DIR_NAME/start.log" exit 1 fi # Ensure working directory exists -if [ -d "$FOLDER" ]; then - cd "$FOLDER" +if [ -d "$ARG_FOLDER" ]; then + cd "$ARG_FOLDER" else - mkdir -p "$FOLDER" - cd "$FOLDER" + mkdir -p "$ARG_FOLDER" + cd "$ARG_FOLDER" fi ARGS=() # global flags -if [ -n "$MODEL" ]; then - ARGS+=("-m" "$MODEL") +if [ -n "$ARG_MODEL" ]; then + ARGS+=("-m" "$ARG_MODEL") fi -if [ "$FORCE" = "true" ]; then +if [ "$ARG_FORCE" = "true" ]; then ARGS+=("-f") fi -if [ -n "$AI_PROMPT" ]; then - ARGS+=("$AI_PROMPT") +if [ -n "$ARG_AI_PROMPT" ]; then + printf "AI prompt provided\n" + if [ "$ARG_AGENTAPI_MODE" = "true" ]; then + ARGS+=("Complete the task at hand and at every step of the way, report tasks to Coder with proper summary and statuses. Your task at hand: $ARG_AI_PROMPT") + elif [ "$ARG_AI_PROMPT" ]; then + ARGS+=("$ARG_AI_PROMPT") + fi fi # Log and run in background, redirecting all output to the log file -printf "Running: %q %s\n" "$CURSOR_CMD" "$(printf '%q ' "${ARGS[@]}")" | tee -a "$HOME/$MODULE_DIR_NAME/start.log" -("$CURSOR_CMD" "${ARGS[@]}" >> "$HOME/$MODULE_DIR_NAME/start.log" 2>&1) & +printf "Running: %q %s\n" "$CURSOR_CMD" "$(printf '%q ' "${ARGS[@]}")" | tee -a "$HOME/$ARG_MODULE_DIR_NAME/start.log" + +if [ "$ARG_AGENTAPI_MODE" = "true" ]; then + agentapi server --term-width 67 --term-height 1190 -- "$CURSOR_CMD" "${ARGS[@]}" +else + ("$CURSOR_CMD" "${ARGS[@]}" >> "$HOME/$ARG_MODULE_DIR_NAME/start.log" 2>&1) & +fi From 2107f93d7de93539b6fa39032a0ad04da64571b1 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Wed, 13 Aug 2025 23:54:09 +0530 Subject: [PATCH 25/48] feat: add tests --- .../modules/cursor-cli/main.test.ts | 193 +++++++++++++++++- .../coder-labs/modules/cursor-cli/main.tf | 8 - .../modules/cursor-cli/scripts/install.sh | 6 +- .../cursor-cli/testdata/cursor-cli-mock.sh | 14 ++ 4 files changed, 208 insertions(+), 13 deletions(-) create mode 100644 registry/coder-labs/modules/cursor-cli/testdata/cursor-cli-mock.sh diff --git a/registry/coder-labs/modules/cursor-cli/main.test.ts b/registry/coder-labs/modules/cursor-cli/main.test.ts index 590ddbab1..bd9d731f1 100644 --- a/registry/coder-labs/modules/cursor-cli/main.test.ts +++ b/registry/coder-labs/modules/cursor-cli/main.test.ts @@ -1,6 +1,11 @@ import { afterEach, beforeAll, describe, expect, setDefaultTimeout, test } from "bun:test"; import { execContainer, runTerraformInit, writeFileContainer } from "~test"; -import { execModuleScript } from "../../../coder/modules/agentapi/test-util"; +import { + execModuleScript, + expectAgentAPIStarted, + loadTestFile, + setup as setupUtil +} from "../../../coder/modules/agentapi/test-util"; import { setupContainer, writeExecutable } from "../../../coder/modules/agentapi/test-util"; let cleanupFns: (() => Promise)[] = []; @@ -41,6 +46,39 @@ const setup = async (vars?: Record) => { return { id, projectDir }; }; +interface SetupProps { + skipAgentAPIMock?: boolean; + skipCursorCliMock?: boolean; + moduleVariables?: Record; + agentapiMockScript?: string; +} + +const setup_agentapi_version = async (props?: SetupProps): Promise<{ id: string }> => { + const projectDir = "/home/coder/project"; + const { id } = await setupUtil({ + moduleDir: import.meta.dir, + moduleVariables: { + enable_agentapi: "true", + install_cursor_cli: props?.skipCursorCliMock ? "true" : "false", + install_agentapi: props?.skipAgentAPIMock ? "true" : "false", + folder: projectDir, + ...props?.moduleVariables, + }, + registerCleanup, + projectDir, + skipAgentAPIMock: props?.skipAgentAPIMock, + agentapiMockScript: props?.agentapiMockScript, + }); + if (!props?.skipCursorCliMock) { + await writeExecutable({ + containerId: id, + filePath: "/usr/bin/cursor-agent", + content: await loadTestFile(import.meta.dir, "cursor-cli-mock.sh"), + }); + } + return { id }; +}; + setDefaultTimeout(180 * 1000); describe("cursor-cli", async () => { @@ -48,6 +86,8 @@ describe("cursor-cli", async () => { await runTerraformInit(import.meta.dir); }); + // tests start for the non-agentapi module version + test("installs Cursor via official installer and runs --help", async () => { const { id } = await setup({ install_cursor_cli: "true", ai_prompt: "--help" }); const resp = await execModuleScript(id); @@ -135,6 +175,157 @@ describe("cursor-cli", async () => { expect(installLog.exitCode).toBe(0); expect(installLog.stdout).toContain("folder: /home/coder/project"); }); + + // tests end for the non-agentapi module version + + // tests start for the agentapi module version + + test("agentapi-happy-path", async () => { + const { id } = await setup_agentapi_version({}); + const resp = await execModuleScript(id); + expect(resp.exitCode).toBe(0); + + await expectAgentAPIStarted(id); + }); + + test("agentapi-mcp-json", async () => { + const mcpJson = '{"mcpServers": {"test": {"command": "test-cmd", "type": "stdio"}}}'; + const { id } = await setup_agentapi_version({ + moduleVariables: { + mcp_json: mcpJson, + } + }); + const resp = await execModuleScript(id); + expect(resp.exitCode).toBe(0); + + const mcpContent = await execContainer(id, [ + "bash", + "-c", + `cat '/home/coder/project/.cursor/mcp.json'`, + ]); + expect(mcpContent.exitCode).toBe(0); + expect(mcpContent.stdout).toContain("mcpServers"); + expect(mcpContent.stdout).toContain("test"); + expect(mcpContent.stdout).toContain("test-cmd"); + expect(mcpContent.stdout).toContain("/tmp/mcp-hack.sh"); + expect(mcpContent.stdout).toContain("coder"); + }); + + test("agentapi-rules-files", async () => { + const rulesContent = "Always use TypeScript"; + const { id } = await setup_agentapi_version({ + moduleVariables: { + rules_files: JSON.stringify({ "typescript.md": rulesContent }), + } + }); + const resp = await execModuleScript(id); + expect(resp.exitCode).toBe(0); + + const rulesFile = await execContainer(id, [ + "bash", + "-c", + `cat '/home/coder/project/.cursor/rules/typescript.md'`, + ]); + expect(rulesFile.exitCode).toBe(0); + expect(rulesFile.stdout).toContain(rulesContent); + }); + + test("agentapi-api-key", async () => { + const apiKey = "test-cursor-api-key-123"; + const { id } = await setup_agentapi_version({ + moduleVariables: { + api_key: apiKey, + } + }); + const resp = await execModuleScript(id); + expect(resp.exitCode).toBe(0); + + const envCheck = await execContainer(id, [ + "bash", + "-c", + `env | grep CURSOR_API_KEY || echo "CURSOR_API_KEY not found"`, + ]); + expect(envCheck.stdout).toContain("CURSOR_API_KEY"); + }); + + test("agentapi-model-and-force-flags", async () => { + const model = "sonnet-4"; + const { id } = await setup_agentapi_version({ + moduleVariables: { + model: model, + force: "true", + ai_prompt: "test prompt", + } + }); + const resp = await execModuleScript(id); + expect(resp.exitCode).toBe(0); + + const startLog = await execContainer(id, [ + "bash", + "-c", + "cat /home/coder/.cursor-cli-module/agentapi-start.log || cat /home/coder/.cursor-cli-module/start.log || true", + ]); + expect(startLog.stdout).toContain(`-m ${model}`); + expect(startLog.stdout).toContain("-f"); + expect(startLog.stdout).toContain("test prompt"); + }); + + test("agentapi-pre-post-install-scripts", async () => { + const { id } = await setup_agentapi_version({ + moduleVariables: { + pre_install_script: "#!/bin/bash\necho 'cursor-pre-install-script'", + post_install_script: "#!/bin/bash\necho 'cursor-post-install-script'", + } + }); + const resp = await execModuleScript(id); + expect(resp.exitCode).toBe(0); + + const preInstallLog = await execContainer(id, [ + "bash", + "-c", + "cat /home/coder/.cursor-cli-module/pre_install.log || true", + ]); + expect(preInstallLog.stdout).toContain("cursor-pre-install-script"); + + const postInstallLog = await execContainer(id, [ + "bash", + "-c", + "cat /home/coder/.cursor-cli-module/post_install.log || true", + ]); + expect(postInstallLog.stdout).toContain("cursor-post-install-script"); + }); + + test("agentapi-folder-variable", async () => { + const folder = "/tmp/cursor-test-folder"; + const { id } = await setup_agentapi_version({ + moduleVariables: { + folder: folder, + } + }); + const resp = await execModuleScript(id); + expect(resp.exitCode).toBe(0); + + const installLog = await execContainer(id, [ + "bash", + "-c", + "cat /home/coder/.cursor-cli-module/install.log || true", + ]); + expect(installLog.stdout).toContain(folder); + }); + + // test end for the agentapi module version + + test("install-test-cursor-cli-latest", async () => { + const { id } = await setup_agentapi_version({ + skipCursorCliMock: true, + skipAgentAPIMock: true, + }); + const resp = await execModuleScript(id); + expect(resp.exitCode).toBe(0); + + await expectAgentAPIStarted(id); + }) + }); diff --git a/registry/coder-labs/modules/cursor-cli/main.tf b/registry/coder-labs/modules/cursor-cli/main.tf index 4471ea917..78c34d5b3 100644 --- a/registry/coder-labs/modules/cursor-cli/main.tf +++ b/registry/coder-labs/modules/cursor-cli/main.tf @@ -47,12 +47,6 @@ variable "install_cursor_cli" { default = true } -variable "cursor_cli_version" { - type = string - description = "The version of Cursor CLI to install (latest for latest)." - default = "latest" -} - variable "enable_agentapi" { type = bool description = "Whether to enable the AgentAPI for Cursor CLI." @@ -169,7 +163,6 @@ resource "coder_script" "cursor_cli" { FOLDER='${var.folder}' /tmp/pre_install.sh | tee -a "$HOME/${local.module_dir_name}/pre_install.log" fi ARG_INSTALL='${var.install_cursor_cli}' \ - ARG_VERSION='${var.cursor_cli_version}' \ ARG_WORKSPACE_MCP_JSON='${var.mcp_json != null ? base64encode(replace(var.mcp_json, "'", "'\\''")) : ""}' \ ARG_WORKSPACE_RULES_JSON='${var.rules_files != null ? base64encode(jsonencode(var.rules_files)) : ""}' \ ARG_MODULE_DIR_NAME='${local.module_dir_name}' \ @@ -259,7 +252,6 @@ module "agentapi" { echo -n '${base64encode(local.install_script)}' | base64 -d > /tmp/install.sh chmod +x /tmp/install.sh ARG_INSTALL='${var.install_cursor_cli}' \ - ARG_VERSION='${var.cursor_cli_version}' \ ARG_WORKSPACE_MCP_JSON='${var.mcp_json != null ? base64encode(replace(var.mcp_json, "'", "'\\''")) : ""}' \ ARG_WORKSPACE_RULES_JSON='${var.rules_files != null ? base64encode(jsonencode(var.rules_files)) : ""}' \ ARG_MODULE_DIR_NAME='${local.module_dir_name}' \ diff --git a/registry/coder-labs/modules/cursor-cli/scripts/install.sh b/registry/coder-labs/modules/cursor-cli/scripts/install.sh index eaf60ea6e..6d8dfed78 100644 --- a/registry/coder-labs/modules/cursor-cli/scripts/install.sh +++ b/registry/coder-labs/modules/cursor-cli/scripts/install.sh @@ -9,7 +9,6 @@ command_exists() { # Inputs ARG_INSTALL=${ARG_INSTALL:-true} -ARG_VERSION=${ARG_VERSION:-latest} ARG_MODULE_DIR_NAME=${ARG_MODULE_DIR_NAME:-.cursor-cli-module} ARG_FOLDER=${ARG_FOLDER:-$HOME} ARG_AGENTAPI_MODE=${ARG_AGENTAPI_MODE:-false} @@ -23,7 +22,6 @@ ARG_WORKSPACE_RULES_JSON=$(echo -n "$ARG_WORKSPACE_RULES_JSON" | base64 -d) { echo "--------------------------------" echo "install: $ARG_INSTALL" - echo "version: $ARG_VERSION" echo "folder: $ARG_FOLDER" echo "agentapi_mode: $ARG_AGENTAPI_MODE" echo "coder_mcp_app_status_slug: $ARG_CODER_MCP_APP_STATUS_SLUG" @@ -116,9 +114,9 @@ EOF echo "Wrote workspace MCP to $TARGET_FILE" | tee -a "$HOME/$ARG_MODULE_DIR_NAME/install.log" fi -# Write rules files to user's home (~/.cursor/rules) +# Write rules files to user's home (FOLDER/.cursor/rules) if [ -n "$ARG_WORKSPACE_RULES_JSON" ]; then - RULES_DIR="$HOME/.cursor/rules" + RULES_DIR="$ARG_FOLDER/.cursor/rules" mkdir -p "$RULES_DIR" echo "$ARG_WORKSPACE_RULES_JSON" | jq -r 'to_entries[] | @base64' | while read -r entry; do _jq() { echo "${entry}" | base64 -d | jq -r ${1}; } diff --git a/registry/coder-labs/modules/cursor-cli/testdata/cursor-cli-mock.sh b/registry/coder-labs/modules/cursor-cli/testdata/cursor-cli-mock.sh new file mode 100644 index 000000000..53c9c41de --- /dev/null +++ b/registry/coder-labs/modules/cursor-cli/testdata/cursor-cli-mock.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +if [[ "$1" == "--version" ]]; then + echo "HELLO: $(bash -c env)" + echo "gemini version v2.5.0" + exit 0 +fi + +set -e + +while true; do + echo "$(date) - gemini-mock" + sleep 15 +done \ No newline at end of file From 1a38b7e5d729ae12a19e30508e2bd7cd85ff0a62 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Thu, 14 Aug 2025 00:31:08 +0530 Subject: [PATCH 26/48] feat: update readme --- .../coder-labs/modules/cursor-cli/README.md | 29 +++++++++++++++++++ .../coder-labs/modules/cursor-cli/main.tf | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/registry/coder-labs/modules/cursor-cli/README.md b/registry/coder-labs/modules/cursor-cli/README.md index b6af73e8d..4c7364a79 100644 --- a/registry/coder-labs/modules/cursor-cli/README.md +++ b/registry/coder-labs/modules/cursor-cli/README.md @@ -90,6 +90,35 @@ module "cursor_cli" { } ``` +To run this module with AgentAPI, pass `enable_agentapi=true` + +```tf +data "coder_parameter" "ai_prompt" { + type = "string" + name = "AI Prompt" + default = "" + description = "Initial prompt for the Codex CLI" + mutable = true +} + +module "coder-login" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/coder-login/coder" + version = "1.0.31" + agent_id = coder_agent.main.id +} + +module "cursor-cli" { + source = "registry.coder.com/coder-labs/cursor-cli/coder" + agent_id = coder_agent.main.id + api_key = "key_xxx" + ai_prompt = data.coder_parameter.ai_prompt.value + folder = "/home/coder/project" + enable_agentapi = true + force = true # recommended while running tasks +} +``` + ## References - See Cursor CLI docs: `https://docs.cursor.com/en/cli/overview` diff --git a/registry/coder-labs/modules/cursor-cli/main.tf b/registry/coder-labs/modules/cursor-cli/main.tf index 78c34d5b3..c30c4b8db 100644 --- a/registry/coder-labs/modules/cursor-cli/main.tf +++ b/registry/coder-labs/modules/cursor-cli/main.tf @@ -212,7 +212,7 @@ resource "coder_app" "cursor_cli" { module "agentapi" { source = "registry.coder.com/coder/agentapi/coder" - version = "1.0.0" + version = "1.1.1" count = var.enable_agentapi ? 1 : 0 agent_id = var.agent_id From d9e49f5c90258ab4736321590a5e2308b05be96b Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Thu, 14 Aug 2025 00:40:57 +0530 Subject: [PATCH 27/48] feat: update readme --- registry/coder-labs/modules/cursor-cli/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/registry/coder-labs/modules/cursor-cli/README.md b/registry/coder-labs/modules/cursor-cli/README.md index 4c7364a79..d7fc74be2 100644 --- a/registry/coder-labs/modules/cursor-cli/README.md +++ b/registry/coder-labs/modules/cursor-cli/README.md @@ -90,6 +90,8 @@ module "cursor_cli" { } ``` +## Running with AgentAPI + To run this module with AgentAPI, pass `enable_agentapi=true` ```tf From a8328f42bfb5a07d82fcd0660a187857271057bb Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Thu, 14 Aug 2025 02:00:52 +0530 Subject: [PATCH 28/48] feat: update app slug --- registry/coder-labs/modules/cursor-cli/main.tf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/registry/coder-labs/modules/cursor-cli/main.tf b/registry/coder-labs/modules/cursor-cli/main.tf index c30c4b8db..40f2f8aea 100644 --- a/registry/coder-labs/modules/cursor-cli/main.tf +++ b/registry/coder-labs/modules/cursor-cli/main.tf @@ -118,7 +118,7 @@ variable "post_install_script" { } locals { - app_slug = "cursor-cli" + app_slug = "cursorcli" install_script = file("${path.module}/scripts/install.sh") start_script = file("${path.module}/scripts/start.sh") module_dir_name = ".cursor-cli-module" @@ -216,7 +216,7 @@ module "agentapi" { count = var.enable_agentapi ? 1 : 0 agent_id = var.agent_id - web_app_slug = "${local.app_slug}-agentapi" + web_app_slug = local.app_slug web_app_order = var.order web_app_group = var.group web_app_icon = var.icon @@ -257,7 +257,7 @@ module "agentapi" { ARG_MODULE_DIR_NAME='${local.module_dir_name}' \ ARG_FOLDER='${var.folder}' \ ARG_AGENTAPI_MODE='${var.enable_agentapi}' \ - ARG_CODER_MCP_APP_STATUS_SLUG='${local.app_slug}-agentapi' \ + ARG_CODER_MCP_APP_STATUS_SLUG='${local.app_slug}' \ /tmp/install.sh EOT } From 3fdae3601ff376d6cb469a5188e772adc0edbdcb Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Thu, 14 Aug 2025 11:59:23 +0530 Subject: [PATCH 29/48] feat: remove app and cli --- .../modules/cursor-cli/main.test.ts | 137 ++---------------- .../coder-labs/modules/cursor-cli/main.tf | 81 ----------- .../modules/cursor-cli/scripts/install.sh | 133 ++++++++--------- .../modules/cursor-cli/scripts/start.sh | 46 ++---- 4 files changed, 87 insertions(+), 310 deletions(-) diff --git a/registry/coder-labs/modules/cursor-cli/main.test.ts b/registry/coder-labs/modules/cursor-cli/main.test.ts index bd9d731f1..2842374e1 100644 --- a/registry/coder-labs/modules/cursor-cli/main.test.ts +++ b/registry/coder-labs/modules/cursor-cli/main.test.ts @@ -23,29 +23,6 @@ afterEach(async () => { } }); -const setup = async (vars?: Record) => { - const projectDir = "/home/coder/project"; - const { id, coderScript, cleanup } = await setupContainer({ - moduleDir: import.meta.dir, - image: "codercom/enterprise-minimal:latest", - vars: { - folder: projectDir, - install_cursor_cli: "false", - ...vars, - }, - }); - registerCleanup(cleanup); - // Ensure project dir exists - await execContainer(id, ["bash", "-c", `mkdir -p '${projectDir}'`]); - // Write the module's script to the container - await writeExecutable({ - containerId: id, - filePath: "/home/coder/script.sh", - content: coderScript.script, - }); - return { id, projectDir }; -}; - interface SetupProps { skipAgentAPIMock?: boolean; skipCursorCliMock?: boolean; @@ -53,7 +30,7 @@ interface SetupProps { agentapiMockScript?: string; } -const setup_agentapi_version = async (props?: SetupProps): Promise<{ id: string }> => { +const setup = async (props?: SetupProps): Promise<{ id: string }> => { const projectDir = "/home/coder/project"; const { id } = await setupUtil({ moduleDir: import.meta.dir, @@ -86,102 +63,8 @@ describe("cursor-cli", async () => { await runTerraformInit(import.meta.dir); }); - // tests start for the non-agentapi module version - - test("installs Cursor via official installer and runs --help", async () => { - const { id } = await setup({ install_cursor_cli: "true", ai_prompt: "--help" }); - const resp = await execModuleScript(id); - expect(resp.exitCode).toBe(0); - - // Verify the start log captured the invocation - const startLog = await execContainer(id, [ - "bash", - "-c", - "cat /home/coder/script.log", - ]); - expect(startLog.exitCode).toBe(0); - expect(startLog.stdout).toContain("cursor-agent"); - }); - - test("model and force flags propagate", async () => { - const { id } = await setup({ model: "sonnet-4", force: "true", ai_prompt: "status" }); - await writeExecutable({ - containerId: id, - filePath: "/usr/bin/cursor-agent", - content: `#!/bin/sh\necho cursor-agent invoked\nfor a in "$@"; do echo arg:$a; done\nexit 0\n`, - }); - - const resp = await execModuleScript(id); - expect(resp.exitCode).toBe(0); - - const startLog = await execContainer(id, [ - "bash", - "-c", - "cat /home/coder/script.log", - ]); - expect(startLog.exitCode).toBe(0); - expect(startLog.stdout).toContain("-m sonnet-4"); - expect(startLog.stdout).toContain("-f"); - expect(startLog.stdout).toContain("status"); - }); - - test("writes workspace mcp.json when provided", async () => { - const mcp = JSON.stringify({ mcpServers: { foo: { command: "foo", type: "stdio" } } }); - const { id } = await setup({ mcp_json: mcp }); - await writeExecutable({ - containerId: id, - filePath: "/usr/bin/cursor-agent", - content: `#!/bin/sh\necho ok\n`, - }); - const resp = await execModuleScript(id); - expect(resp.exitCode).toBe(0); - - const mcpContent = await execContainer(id, [ - "bash", - "-c", - `cat '/home/coder/project/.cursor/mcp.json'`, - ]); - expect(mcpContent.exitCode).toBe(0); - expect(mcpContent.stdout).toContain("mcpServers"); - expect(mcpContent.stdout).toContain("foo"); - }); - - test("fails when cursor-agent missing", async () => { - const { id } = await setup(); - const resp = await execModuleScript(id); - expect(resp.exitCode).not.toBe(0); - const startLog = await execContainer(id, [ - "bash", - "-c", - "cat /home/coder/script.log || true", - ]); - expect(startLog.stdout).toContain("cursor-agent not found"); - }); - - test("install step logs folder", async () => { - const { id } = await setup({ install_cursor_cli: "false" }); - await writeExecutable({ - containerId: id, - filePath: "/usr/bin/cursor-agent", - content: `#!/bin/sh\necho ok\n`, - }); - const resp = await execModuleScript(id); - expect(resp.exitCode).toBe(0); - const installLog = await execContainer(id, [ - "bash", - "-c", - "cat /home/coder/script.log", - ]); - expect(installLog.exitCode).toBe(0); - expect(installLog.stdout).toContain("folder: /home/coder/project"); - }); - - // tests end for the non-agentapi module version - - // tests start for the agentapi module version - test("agentapi-happy-path", async () => { - const { id } = await setup_agentapi_version({}); + const { id } = await setup({}); const resp = await execModuleScript(id); expect(resp.exitCode).toBe(0); @@ -190,7 +73,7 @@ describe("cursor-cli", async () => { test("agentapi-mcp-json", async () => { const mcpJson = '{"mcpServers": {"test": {"command": "test-cmd", "type": "stdio"}}}'; - const { id } = await setup_agentapi_version({ + const { id } = await setup({ moduleVariables: { mcp_json: mcpJson, } @@ -213,7 +96,7 @@ describe("cursor-cli", async () => { test("agentapi-rules-files", async () => { const rulesContent = "Always use TypeScript"; - const { id } = await setup_agentapi_version({ + const { id } = await setup({ moduleVariables: { rules_files: JSON.stringify({ "typescript.md": rulesContent }), } @@ -232,7 +115,7 @@ describe("cursor-cli", async () => { test("agentapi-api-key", async () => { const apiKey = "test-cursor-api-key-123"; - const { id } = await setup_agentapi_version({ + const { id } = await setup({ moduleVariables: { api_key: apiKey, } @@ -250,7 +133,7 @@ describe("cursor-cli", async () => { test("agentapi-model-and-force-flags", async () => { const model = "sonnet-4"; - const { id } = await setup_agentapi_version({ + const { id } = await setup({ moduleVariables: { model: model, force: "true", @@ -271,7 +154,7 @@ describe("cursor-cli", async () => { }); test("agentapi-pre-post-install-scripts", async () => { - const { id } = await setup_agentapi_version({ + const { id } = await setup({ moduleVariables: { pre_install_script: "#!/bin/bash\necho 'cursor-pre-install-script'", post_install_script: "#!/bin/bash\necho 'cursor-post-install-script'", @@ -297,7 +180,7 @@ describe("cursor-cli", async () => { test("agentapi-folder-variable", async () => { const folder = "/tmp/cursor-test-folder"; - const { id } = await setup_agentapi_version({ + const { id } = await setup({ moduleVariables: { folder: folder, } @@ -313,10 +196,8 @@ describe("cursor-cli", async () => { expect(installLog.stdout).toContain(folder); }); - // test end for the agentapi module version - test("install-test-cursor-cli-latest", async () => { - const { id } = await setup_agentapi_version({ + const { id } = await setup({ skipCursorCliMock: true, skipAgentAPIMock: true, }); diff --git a/registry/coder-labs/modules/cursor-cli/main.tf b/registry/coder-labs/modules/cursor-cli/main.tf index 40f2f8aea..85a6287b4 100644 --- a/registry/coder-labs/modules/cursor-cli/main.tf +++ b/registry/coder-labs/modules/cursor-cli/main.tf @@ -47,12 +47,6 @@ variable "install_cursor_cli" { default = true } -variable "enable_agentapi" { - type = bool - description = "Whether to enable the AgentAPI for Cursor CLI." - default = false -} - variable "install_agentapi" { type = bool description = "Whether to install AgentAPI." @@ -122,8 +116,6 @@ locals { install_script = file("${path.module}/scripts/install.sh") start_script = file("${path.module}/scripts/start.sh") module_dir_name = ".cursor-cli-module" - encoded_pre_install_script = var.pre_install_script != null ? base64encode(var.pre_install_script) : "" - encoded_post_install_script = var.post_install_script != null ? base64encode(var.post_install_script) : "" } # Expose status slug and API key to the agent environment @@ -140,80 +132,9 @@ resource "coder_env" "cursor_api_key" { value = var.api_key } -resource "coder_script" "cursor_cli" { - agent_id = var.agent_id - display_name = "Cursor CLI" - icon = var.icon - count = var.enable_agentapi ? 0 : 1 - script = <<-EOT - #!/bin/bash - set -o errexit - set -o pipefail - - # Ensure module log directory exists before piping logs - mkdir -p "$HOME/${local.module_dir_name}" - - echo -n '${base64encode(local.install_script)}' | base64 -d > /tmp/install.sh - chmod +x /tmp/install.sh - # Run optional pre-install script - if [ -n "${local.encoded_pre_install_script}" ]; then - echo "${local.encoded_pre_install_script}" | base64 -d > /tmp/pre_install.sh - chmod +x /tmp/pre_install.sh - echo "[cursor-cli] running pre-install script" | tee -a "$HOME/${local.module_dir_name}/install.log" - FOLDER='${var.folder}' /tmp/pre_install.sh | tee -a "$HOME/${local.module_dir_name}/pre_install.log" - fi - ARG_INSTALL='${var.install_cursor_cli}' \ - ARG_WORKSPACE_MCP_JSON='${var.mcp_json != null ? base64encode(replace(var.mcp_json, "'", "'\\''")) : ""}' \ - ARG_WORKSPACE_RULES_JSON='${var.rules_files != null ? base64encode(jsonencode(var.rules_files)) : ""}' \ - ARG_MODULE_DIR_NAME='${local.module_dir_name}' \ - ARG_FOLDER='${var.folder}' \ - /tmp/install.sh | tee "$HOME/${local.module_dir_name}/install.log" - - # Run optional post-install script - if [ -n "${local.encoded_post_install_script}" ]; then - echo "${local.encoded_post_install_script}" | base64 -d > /tmp/post_install.sh - chmod +x /tmp/post_install.sh - echo "[cursor-cli] running post-install script" | tee -a "$HOME/${local.module_dir_name}/install.log" - FOLDER='${var.folder}' /tmp/post_install.sh | tee -a "$HOME/${local.module_dir_name}/post_install.log" - fi - - echo -n '${base64encode(local.start_script)}' | base64 -d > /tmp/start.sh - chmod +x /tmp/start.sh - ARG_FORCE='${var.force}' \ - ARG_MODEL='${var.model}' \ - ARG_AI_PROMPT='${base64encode(var.ai_prompt)}' \ - ARG_MODULE_DIR_NAME='${local.module_dir_name}' \ - ARG_FOLDER='${var.folder}' \ - /tmp/start.sh | tee "$HOME/${local.module_dir_name}/start.log" - EOT - run_on_start = true -} - -resource "coder_app" "cursor_cli" { - agent_id = var.agent_id - slug = local.app_slug - display_name = "Cursor CLI" - count = var.enable_agentapi ? 0 : 1 - icon = var.icon - order = var.order - group = var.group - command = <<-EOT - #!/bin/bash - set -o errexit - set -o pipefail - if [ -f "$HOME/${local.module_dir_name}/start.log" ]; then - tail -n +1 -f "$HOME/${local.module_dir_name}/start.log" - else - echo "Cursor CLI not started yet. Check install/start logs in $HOME/${local.module_dir_name}/" - /bin/bash - fi - EOT -} - module "agentapi" { source = "registry.coder.com/coder/agentapi/coder" version = "1.1.1" - count = var.enable_agentapi ? 1 : 0 agent_id = var.agent_id web_app_slug = local.app_slug @@ -240,7 +161,6 @@ module "agentapi" { ARG_AI_PROMPT='${base64encode(var.ai_prompt)}' \ ARG_MODULE_DIR_NAME='${local.module_dir_name}' \ ARG_FOLDER='${var.folder}' \ - ARG_AGENTAPI_MODE='${var.enable_agentapi}' \ /tmp/start.sh EOT @@ -256,7 +176,6 @@ module "agentapi" { ARG_WORKSPACE_RULES_JSON='${var.rules_files != null ? base64encode(jsonencode(var.rules_files)) : ""}' \ ARG_MODULE_DIR_NAME='${local.module_dir_name}' \ ARG_FOLDER='${var.folder}' \ - ARG_AGENTAPI_MODE='${var.enable_agentapi}' \ ARG_CODER_MCP_APP_STATUS_SLUG='${local.app_slug}' \ /tmp/install.sh EOT diff --git a/registry/coder-labs/modules/cursor-cli/scripts/install.sh b/registry/coder-labs/modules/cursor-cli/scripts/install.sh index 6d8dfed78..5477f07dd 100644 --- a/registry/coder-labs/modules/cursor-cli/scripts/install.sh +++ b/registry/coder-labs/modules/cursor-cli/scripts/install.sh @@ -4,14 +4,13 @@ set -o errexit set -o pipefail command_exists() { - command -v "$1" >/dev/null 2>&1 + command -v "$1" > /dev/null 2>&1 } # Inputs ARG_INSTALL=${ARG_INSTALL:-true} ARG_MODULE_DIR_NAME=${ARG_MODULE_DIR_NAME:-.cursor-cli-module} ARG_FOLDER=${ARG_FOLDER:-$HOME} -ARG_AGENTAPI_MODE=${ARG_AGENTAPI_MODE:-false} ARG_CODER_MCP_APP_STATUS_SLUG=${ARG_CODER_MCP_APP_STATUS_SLUG:-} mkdir -p "$HOME/$ARG_MODULE_DIR_NAME" @@ -19,62 +18,53 @@ mkdir -p "$HOME/$ARG_MODULE_DIR_NAME" ARG_WORKSPACE_MCP_JSON=$(echo -n "$ARG_WORKSPACE_MCP_JSON" | base64 -d) ARG_WORKSPACE_RULES_JSON=$(echo -n "$ARG_WORKSPACE_RULES_JSON" | base64 -d) -{ - echo "--------------------------------" - echo "install: $ARG_INSTALL" - echo "folder: $ARG_FOLDER" - echo "agentapi_mode: $ARG_AGENTAPI_MODE" - echo "coder_mcp_app_status_slug: $ARG_CODER_MCP_APP_STATUS_SLUG" - echo "module_dir_name: $ARG_MODULE_DIR_NAME" - echo "--------------------------------" -} | tee -a "$HOME/$ARG_MODULE_DIR_NAME/install.log" +echo "--------------------------------" +echo "install: $ARG_INSTALL" +echo "folder: $ARG_FOLDER" +echo "coder_mcp_app_status_slug: $ARG_CODER_MCP_APP_STATUS_SLUG" +echo "module_dir_name: $ARG_MODULE_DIR_NAME" +echo "--------------------------------" # Install Cursor via official installer if requested -if [ "$ARG_INSTALL" = "true" ]; then - echo "Installing Cursor via official installer..." | tee -a "$HOME/$ARG_MODULE_DIR_NAME/install.log" - set +e - curl https://cursor.com/install -fsS | bash 2>&1 | tee -a "$HOME/$ARG_MODULE_DIR_NAME/install.log" - CURL_EXIT=${PIPESTATUS[0]} - set -e - if [ $CURL_EXIT -ne 0 ]; then - echo "Cursor installer failed with exit code $CURL_EXIT" | tee -a "$HOME/$ARG_MODULE_DIR_NAME/install.log" - fi +function install_cursor_cli() { + if [ "$ARG_INSTALL" = "true" ]; then + echo "Installing Cursor via official installer..." + set +e + curl https://cursor.com/install -fsS | bash 2>&1 + CURL_EXIT=${PIPESTATUS[0]} + set -e + if [ $CURL_EXIT -ne 0 ]; then + echo "Cursor installer failed with exit code $CURL_EXIT" + fi - # Ensure binaries are discoverable; create stable symlink to cursor-agent - CANDIDATES=( - "$(command -v cursor-agent || true)" - "$HOME/.cursor/bin/cursor-agent" - ) - FOUND_BIN="" - for c in "${CANDIDATES[@]}"; do - if [ -n "$c" ] && [ -x "$c" ]; then - FOUND_BIN="$c" - break + # Ensure binaries are discoverable; create stable symlink to cursor-agent + CANDIDATES=( + "$(command -v cursor-agent || true)" + "$HOME/.cursor/bin/cursor-agent" + ) + FOUND_BIN="" + for c in "${CANDIDATES[@]}"; do + if [ -n "$c" ] && [ -x "$c" ]; then + FOUND_BIN="$c" + break + fi + done + mkdir -p "$HOME/.local/bin" + if [ -n "$FOUND_BIN" ]; then + ln -sf "$FOUND_BIN" "$HOME/.local/bin/cursor-agent" fi - done - mkdir -p "$HOME/.local/bin" - if [ -n "$FOUND_BIN" ]; then - ln -sf "$FOUND_BIN" "$HOME/.local/bin/cursor-agent" + echo "Installed cursor-agent at: $(command -v cursor-agent || true) (resolved: $FOUND_BIN)" fi - echo "Installed cursor-agent at: $(command -v cursor-agent || true) (resolved: $FOUND_BIN)" | tee -a "$HOME/$ARG_MODULE_DIR_NAME/install.log" -fi - -# Ensure status slug env is exported for downstream processes -if [ -n "${STATUS_SLUG:-}" ]; then - echo "export CODER_MCP_APP_STATUS_SLUG=$STATUS_SLUG" >> "$HOME/.bashrc" - export CODER_MCP_APP_STATUS_SLUG="$STATUS_SLUG" -fi +} # Write MCP config to user's home if provided (ARG_FOLDER/.cursor/mcp.json) -if [ -n "$ARG_WORKSPACE_MCP_JSON" ] || [ "$ARG_AGENTAPI_MODE" = "true" ]; then +function write_mcp_config() { TARGET_DIR="$ARG_FOLDER/.cursor" TARGET_FILE="$TARGET_DIR/mcp.json" mkdir -p "$TARGET_DIR" - - if [ "$ARG_AGENTAPI_MODE" = "true" ]; then - - CURSOR_MCP_HACK_SCRIPT=$(cat < "/tmp/mcp-hack.sh" chmod +x /tmp/mcp-hack.sh - CODER_MCP=$(cat < "$TARGET_FILE" - else - echo "${ARG_WORKSPACE_MCP_JSON}" > "$TARGET_FILE" - fi - echo "Wrote workspace MCP to $TARGET_FILE" | tee -a "$HOME/$ARG_MODULE_DIR_NAME/install.log" -fi + echo "${ARG_WORKSPACE_MCP_JSON:-{}}" | jq --argjson base "$CODER_MCP" \ + '.mcpServers = ((.mcpServers // {}) + $base)' > "$TARGET_FILE" + echo "Wrote workspace MCP to $TARGET_FILE" +} # Write rules files to user's home (FOLDER/.cursor/rules) -if [ -n "$ARG_WORKSPACE_RULES_JSON" ]; then - RULES_DIR="$ARG_FOLDER/.cursor/rules" - mkdir -p "$RULES_DIR" - echo "$ARG_WORKSPACE_RULES_JSON" | jq -r 'to_entries[] | @base64' | while read -r entry; do - _jq() { echo "${entry}" | base64 -d | jq -r ${1}; } - NAME=$(_jq '.key') - CONTENT=$(_jq '.value') - echo "$CONTENT" > "$RULES_DIR/$NAME" - echo "Wrote rule: $RULES_DIR/$NAME" | tee -a "$HOME/$ARG_MODULE_DIR_NAME/install.log" - done -fi - - -exit 0 +function write_rules_file() { + if [ -n "$ARG_WORKSPACE_RULES_JSON" ]; then + RULES_DIR="$ARG_FOLDER/.cursor/rules" + mkdir -p "$RULES_DIR" + echo "$ARG_WORKSPACE_RULES_JSON" | jq -r 'to_entries[] | @base64' | while read -r entry; do + _jq() { echo "${entry}" | base64 -d | jq -r ${1}; } + NAME=$(_jq '.key') + CONTENT=$(_jq '.value') + echo "$CONTENT" > "$RULES_DIR/$NAME" + echo "Wrote rule: $RULES_DIR/$NAME" + done + fi +} + +install_cursor_cli +write_mcp_config +write_rules_file diff --git a/registry/coder-labs/modules/cursor-cli/scripts/start.sh b/registry/coder-labs/modules/cursor-cli/scripts/start.sh index 83328c869..da744d52f 100644 --- a/registry/coder-labs/modules/cursor-cli/scripts/start.sh +++ b/registry/coder-labs/modules/cursor-cli/scripts/start.sh @@ -4,7 +4,7 @@ set -o errexit set -o pipefail command_exists() { - command -v "$1" >/dev/null 2>&1 + command -v "$1" > /dev/null 2>&1 } ARG_AI_PROMPT=$(echo -n "${ARG_AI_PROMPT:-}" | base64 -d) @@ -13,34 +13,28 @@ ARG_MODEL=${ARG_MODEL:-} ARG_OUTPUT_FORMAT=${ARG_OUTPUT_FORMAT:-json} ARG_MODULE_DIR_NAME=${ARG_MODULE_DIR_NAME:-.cursor-cli-module} ARG_FOLDER=${ARG_FOLDER:-$HOME} -ARG_AGENTAPI_MODE=${ARG_AGENTAPI_MODE:-false} - -{ - echo "--------------------------------" - echo "install: $ARG_INSTALL" - echo "version: $ARG_VERSION" - echo "folder: $FOLDER" - echo "ai_prompt: $ARG_AI_PROMPT" - echo "force: $ARG_FORCE" - echo "model: $ARG_MODEL" - echo "output_format: $ARG_OUTPUT_FORMAT" - echo "module_dir_name: $ARG_MODULE_DIR_NAME" - echo "folder: $ARG_FOLDER" - echo "agentapi_mode: $ARG_AGENTAPI_MODE" - echo "--------------------------------" -} | tee -a "$HOME/$MODULE_DIR_NAME/start.log" +echo "--------------------------------" +echo "install: $ARG_INSTALL" +echo "version: $ARG_VERSION" +echo "folder: $FOLDER" +echo "ai_prompt: $ARG_AI_PROMPT" +echo "force: $ARG_FORCE" +echo "model: $ARG_MODEL" +echo "output_format: $ARG_OUTPUT_FORMAT" +echo "module_dir_name: $ARG_MODULE_DIR_NAME" +echo "folder: $ARG_FOLDER" +echo "--------------------------------" mkdir -p "$HOME/$ARG_MODULE_DIR_NAME" - # Find cursor agent cli if command_exists cursor-agent; then CURSOR_CMD=cursor-agent elif [ -x "$HOME/.local/bin/cursor-agent" ]; then CURSOR_CMD="$HOME/.local/bin/cursor-agent" else - echo "Error: cursor-agent not found. Install it or set install_cursor_cli=true." | tee -a "$HOME/$ARG_MODULE_DIR_NAME/start.log" + echo "Error: cursor-agent not found. Install it or set install_cursor_cli=true." exit 1 fi @@ -64,18 +58,10 @@ fi if [ -n "$ARG_AI_PROMPT" ]; then printf "AI prompt provided\n" - if [ "$ARG_AGENTAPI_MODE" = "true" ]; then - ARGS+=("Complete the task at hand and at every step of the way, report tasks to Coder with proper summary and statuses. Your task at hand: $ARG_AI_PROMPT") - elif [ "$ARG_AI_PROMPT" ]; then - ARGS+=("$ARG_AI_PROMPT") - fi + ARGS+=("Complete the task at hand and at every step of the way, report tasks to Coder with proper summary and statuses. Your task at hand: $ARG_AI_PROMPT") fi # Log and run in background, redirecting all output to the log file -printf "Running: %q %s\n" "$CURSOR_CMD" "$(printf '%q ' "${ARGS[@]}")" | tee -a "$HOME/$ARG_MODULE_DIR_NAME/start.log" +printf "Running: %q %s\n" "$CURSOR_CMD" "$(printf '%q ' "${ARGS[@]}")" -if [ "$ARG_AGENTAPI_MODE" = "true" ]; then - agentapi server --term-width 67 --term-height 1190 -- "$CURSOR_CMD" "${ARGS[@]}" -else - ("$CURSOR_CMD" "${ARGS[@]}" >> "$HOME/$ARG_MODULE_DIR_NAME/start.log" 2>&1) & -fi +agentapi server --term-width 67 --term-height 1190 -- "$CURSOR_CMD" "${ARGS[@]}" From 5da37214742b265bdcdd3993e54552ee1599cc49 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Thu, 14 Aug 2025 13:03:18 +0530 Subject: [PATCH 30/48] feat: update readme --- .../coder-labs/modules/cursor-cli/README.md | 91 +++++++++---------- .../coder-labs/modules/cursor-cli/main.tf | 20 ++-- .../modules/cursor-cli/scripts/start.sh | 5 +- 3 files changed, 55 insertions(+), 61 deletions(-) diff --git a/registry/coder-labs/modules/cursor-cli/README.md b/registry/coder-labs/modules/cursor-cli/README.md index d7fc74be2..63b98e16a 100644 --- a/registry/coder-labs/modules/cursor-cli/README.md +++ b/registry/coder-labs/modules/cursor-cli/README.md @@ -15,9 +15,18 @@ A full example with MCP, rules, and pre/post install scripts: ```tf data "coder_parameter" "ai_prompt" { - name = "ai_prompt" - type = "string" - default = "Write a simple hello world program in Python" + type = "string" + name = "AI Prompt" + default = "" + description = "Build a Minesweeper in Python." + mutable = true +} + +module "coder-login" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/coder-login/coder" + version = "1.0.31" + agent_id = coder_agent.main.id } module "cursor_cli" { @@ -33,7 +42,7 @@ module "cursor_cli" { model = "gpt-5" ai_prompt = data.coder_parameter.ai_prompt.value - # Minimal MCP server (writes `~/.cursor/mcp.json`): + # Minimal MCP server (writes `folder/.cursor/mcp.json`): mcp_json = jsonencode({ mcpServers = { playwright = { @@ -68,64 +77,46 @@ module "cursor_cli" { echo "timeout waiting for $${TARGET}" >&2 EOT - # Provide a map of file name to content; files are written to `~/.cursor/rules/`. + # Provide a map of file name to content; files are written to `folder/.cursor/rules/`. rules_files = { - "python.yml" = <<-EOT - version: 1 - rules: - - name: python - include: ['**/*.py'] - description: Python-focused guidance + "python.mdc" = <<-EOT + --- + description: RPC Service boilerplate + globs: + alwaysApply: false + --- + + - Use our internal RPC pattern when defining services + - Always use snake_case for service names. + + @service-template.ts EOT - "frontend.yml" = <<-EOT - version: 1 - rules: - - name: web - include: ['**/*.{ts,tsx,js,jsx,css}'] - exclude: ['**/dist/**'] - description: Frontend rules + "frontend.mdc" = <<-EOT + --- + description: RPC Service boilerplate + globs: + alwaysApply: false + --- + + - Use our internal RPC pattern when defining services + - Always use snake_case for service names. + + @service-template.ts EOT } } ``` -## Running with AgentAPI - -To run this module with AgentAPI, pass `enable_agentapi=true` - -```tf -data "coder_parameter" "ai_prompt" { - type = "string" - name = "AI Prompt" - default = "" - description = "Initial prompt for the Codex CLI" - mutable = true -} - -module "coder-login" { - count = data.coder_workspace.me.start_count - source = "registry.coder.com/coder/coder-login/coder" - version = "1.0.31" - agent_id = coder_agent.main.id -} - -module "cursor-cli" { - source = "registry.coder.com/coder-labs/cursor-cli/coder" - agent_id = coder_agent.main.id - api_key = "key_xxx" - ai_prompt = data.coder_parameter.ai_prompt.value - folder = "/home/coder/project" - enable_agentapi = true - force = true # recommended while running tasks -} -``` +> [!NOTE] +> A `.cursor` directory will be created in the specified `folder`, containing the MCP configuration, rules +> Cursor CLI dosen't seem fully compatible with MCPs, so Coder tasks and Coder MCP will not work with this module. ## References - See Cursor CLI docs: `https://docs.cursor.com/en/cli/overview` -- For MCP project config, see `https://docs.cursor.com/en/context/mcp#using-mcp-json`. This module writes your `mcp_json` into `~/.cursor/mcp.json`. -- For Rules, see `https://docs.cursor.com/en/context/rules#project-rules`. Provide `rules_files` (map of file name to content) to populate `~/.cursor/rules/`. +- For MCP project config, see `https://docs.cursor.com/en/context/mcp#using-mcp-json`. This module writes your `mcp_json` into `folder/.cursor/mcp.json`. +- For Rules, see `https://docs.cursor.com/en/context/rules#project-rules`. Provide `rules_files` (map of file name to content) to populate `folder/.cursor/rules/`. ## Troubleshooting diff --git a/registry/coder-labs/modules/cursor-cli/main.tf b/registry/coder-labs/modules/cursor-cli/main.tf index 85a6287b4..4bf5be800 100644 --- a/registry/coder-labs/modules/cursor-cli/main.tf +++ b/registry/coder-labs/modules/cursor-cli/main.tf @@ -64,13 +64,13 @@ variable "agentapi_version" { variable "force" { type = bool - description = "Pass -f/--force to allow commands unless explicitly denied." - default = false + description = "Force allow commands unless explicitly denied" + default = true } variable "model" { type = string - description = "Pass -m/--model to select model (e.g., sonnet-4, gpt-5)." + description = "Model to use (e.g., sonnet-4, sonnet-4-thinking, gpt-5)" default = "" } @@ -82,20 +82,20 @@ variable "ai_prompt" { variable "api_key" { type = string - description = "API key (sets CURSOR_API_KEY env or pass via -a)." + description = "API key for Cursor CLI." default = "" sensitive = true } variable "mcp_json" { type = string - description = "Workspace-specific MCP JSON to write to ~/.cursor/mcp.json. See https://docs.cursor.com/en/context/mcp#using-mcp-json" + description = "Workspace-specific MCP JSON to write to folder/.cursor/mcp.json. See https://docs.cursor.com/en/context/mcp#using-mcp-json" default = null } variable "rules_files" { type = map(string) - description = "Optional map of rule file name to content. Files will be written to ~/.cursor/rules/. See https://docs.cursor.com/en/context/rules#project-rules" + description = "Optional map of rule file name to content. Files will be written to folder/.cursor/rules/. See https://docs.cursor.com/en/context/rules#project-rules" default = null } @@ -112,10 +112,10 @@ variable "post_install_script" { } locals { - app_slug = "cursorcli" - install_script = file("${path.module}/scripts/install.sh") - start_script = file("${path.module}/scripts/start.sh") - module_dir_name = ".cursor-cli-module" + app_slug = "cursorcli" + install_script = file("${path.module}/scripts/install.sh") + start_script = file("${path.module}/scripts/start.sh") + module_dir_name = ".cursor-cli-module" } # Expose status slug and API key to the agent environment diff --git a/registry/coder-labs/modules/cursor-cli/scripts/start.sh b/registry/coder-labs/modules/cursor-cli/scripts/start.sh index da744d52f..4310d1b3a 100644 --- a/registry/coder-labs/modules/cursor-cli/scripts/start.sh +++ b/registry/coder-labs/modules/cursor-cli/scripts/start.sh @@ -58,7 +58,10 @@ fi if [ -n "$ARG_AI_PROMPT" ]; then printf "AI prompt provided\n" - ARGS+=("Complete the task at hand and at every step of the way, report tasks to Coder with proper summary and statuses. Your task at hand: $ARG_AI_PROMPT") + # Note: As of now cursor-agent is not able to handle mcp calls properly, so we are skipping the tasks. + # ARGS+=("Execute the task end‑to‑end and also report progress to Coder. Do not only report; always produce the actual deliverable in chat. Task at hand: $ARG_AI_PROMPT") + + ARGS+=("$ARG_AI_PROMPT") fi # Log and run in background, redirecting all output to the log file From 3a4fc3611ad8cde86ef2dd102e9bf8e28fa59283 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Thu, 14 Aug 2025 13:03:41 +0530 Subject: [PATCH 31/48] feat: fix typo --- registry/coder-labs/modules/cursor-cli/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/coder-labs/modules/cursor-cli/README.md b/registry/coder-labs/modules/cursor-cli/README.md index 63b98e16a..f557f3cda 100644 --- a/registry/coder-labs/modules/cursor-cli/README.md +++ b/registry/coder-labs/modules/cursor-cli/README.md @@ -110,7 +110,7 @@ module "cursor_cli" { > [!NOTE] > A `.cursor` directory will be created in the specified `folder`, containing the MCP configuration, rules -> Cursor CLI dosen't seem fully compatible with MCPs, so Coder tasks and Coder MCP will not work with this module. +> Cursor CLI doesn't seem fully compatible with MCPs, so Coder tasks and Coder MCP will not work with this module. ## References From d1f518a8952e49c8f5c7fc6bbe50d894b5416a65 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Thu, 14 Aug 2025 13:04:30 +0530 Subject: [PATCH 32/48] feat: update mcp_json to mcp --- registry/coder-labs/modules/cursor-cli/README.md | 2 +- registry/coder-labs/modules/cursor-cli/main.tf | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/registry/coder-labs/modules/cursor-cli/README.md b/registry/coder-labs/modules/cursor-cli/README.md index f557f3cda..e53849375 100644 --- a/registry/coder-labs/modules/cursor-cli/README.md +++ b/registry/coder-labs/modules/cursor-cli/README.md @@ -43,7 +43,7 @@ module "cursor_cli" { ai_prompt = data.coder_parameter.ai_prompt.value # Minimal MCP server (writes `folder/.cursor/mcp.json`): - mcp_json = jsonencode({ + mcp = jsonencode({ mcpServers = { playwright = { command = "npx" diff --git a/registry/coder-labs/modules/cursor-cli/main.tf b/registry/coder-labs/modules/cursor-cli/main.tf index 4bf5be800..36b31b53d 100644 --- a/registry/coder-labs/modules/cursor-cli/main.tf +++ b/registry/coder-labs/modules/cursor-cli/main.tf @@ -87,7 +87,7 @@ variable "api_key" { sensitive = true } -variable "mcp_json" { +variable "mcp" { type = string description = "Workspace-specific MCP JSON to write to folder/.cursor/mcp.json. See https://docs.cursor.com/en/context/mcp#using-mcp-json" default = null @@ -172,7 +172,7 @@ module "agentapi" { echo -n '${base64encode(local.install_script)}' | base64 -d > /tmp/install.sh chmod +x /tmp/install.sh ARG_INSTALL='${var.install_cursor_cli}' \ - ARG_WORKSPACE_MCP_JSON='${var.mcp_json != null ? base64encode(replace(var.mcp_json, "'", "'\\''")) : ""}' \ + ARG_WORKSPACE_MCP_JSON='${var.mcp != null ? base64encode(replace(var.mcp, "'", "'\\''")) : ""}' \ ARG_WORKSPACE_RULES_JSON='${var.rules_files != null ? base64encode(jsonencode(var.rules_files)) : ""}' \ ARG_MODULE_DIR_NAME='${local.module_dir_name}' \ ARG_FOLDER='${var.folder}' \ From 23222981f33c1d235d95e465cddcb100cb363a3a Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Thu, 14 Aug 2025 13:08:57 +0530 Subject: [PATCH 33/48] feat: update tests --- registry/coder-labs/modules/cursor-cli/main.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/coder-labs/modules/cursor-cli/main.test.ts b/registry/coder-labs/modules/cursor-cli/main.test.ts index 2842374e1..52bc993f0 100644 --- a/registry/coder-labs/modules/cursor-cli/main.test.ts +++ b/registry/coder-labs/modules/cursor-cli/main.test.ts @@ -75,7 +75,7 @@ describe("cursor-cli", async () => { const mcpJson = '{"mcpServers": {"test": {"command": "test-cmd", "type": "stdio"}}}'; const { id } = await setup({ moduleVariables: { - mcp_json: mcpJson, + mcp: mcpJson, } }); const resp = await execModuleScript(id); From 10a96aa743645c3b7329b506f232a7613e8eb735 Mon Sep 17 00:00:00 2001 From: 35C4n0r <70096901+35C4n0r@users.noreply.github.com> Date: Thu, 14 Aug 2025 19:27:05 +0530 Subject: [PATCH 34/48] Update registry/coder-labs/modules/cursor-cli/scripts/start.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- registry/coder-labs/modules/cursor-cli/scripts/start.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/coder-labs/modules/cursor-cli/scripts/start.sh b/registry/coder-labs/modules/cursor-cli/scripts/start.sh index 4310d1b3a..e958c815e 100644 --- a/registry/coder-labs/modules/cursor-cli/scripts/start.sh +++ b/registry/coder-labs/modules/cursor-cli/scripts/start.sh @@ -58,7 +58,7 @@ fi if [ -n "$ARG_AI_PROMPT" ]; then printf "AI prompt provided\n" - # Note: As of now cursor-agent is not able to handle mcp calls properly, so we are skipping the tasks. + # Note: As of now cursor-agent is not able to handle MCP calls properly, so we are skipping the tasks. # ARGS+=("Execute the task end‑to‑end and also report progress to Coder. Do not only report; always produce the actual deliverable in chat. Task at hand: $ARG_AI_PROMPT") ARGS+=("$ARG_AI_PROMPT") From f480d748e1221c36bf19d95fdcbca431296a33c3 Mon Sep 17 00:00:00 2001 From: 35C4n0r <70096901+35C4n0r@users.noreply.github.com> Date: Thu, 14 Aug 2025 19:27:12 +0530 Subject: [PATCH 35/48] Update registry/coder-labs/modules/cursor-cli/scripts/start.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- registry/coder-labs/modules/cursor-cli/scripts/start.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/coder-labs/modules/cursor-cli/scripts/start.sh b/registry/coder-labs/modules/cursor-cli/scripts/start.sh index e958c815e..8238fb060 100644 --- a/registry/coder-labs/modules/cursor-cli/scripts/start.sh +++ b/registry/coder-labs/modules/cursor-cli/scripts/start.sh @@ -17,7 +17,7 @@ ARG_FOLDER=${ARG_FOLDER:-$HOME} echo "--------------------------------" echo "install: $ARG_INSTALL" echo "version: $ARG_VERSION" -echo "folder: $FOLDER" +echo "folder: $ARG_FOLDER" echo "ai_prompt: $ARG_AI_PROMPT" echo "force: $ARG_FORCE" echo "model: $ARG_MODEL" From 528b823e343dcf3ab9ecc5451bdd057028c8d6a3 Mon Sep 17 00:00:00 2001 From: 35C4n0r <70096901+35C4n0r@users.noreply.github.com> Date: Thu, 14 Aug 2025 19:27:18 +0530 Subject: [PATCH 36/48] Update registry/coder-labs/modules/cursor-cli/testdata/cursor-cli-mock.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../coder-labs/modules/cursor-cli/testdata/cursor-cli-mock.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/coder-labs/modules/cursor-cli/testdata/cursor-cli-mock.sh b/registry/coder-labs/modules/cursor-cli/testdata/cursor-cli-mock.sh index 53c9c41de..9da262838 100644 --- a/registry/coder-labs/modules/cursor-cli/testdata/cursor-cli-mock.sh +++ b/registry/coder-labs/modules/cursor-cli/testdata/cursor-cli-mock.sh @@ -9,6 +9,6 @@ fi set -e while true; do - echo "$(date) - gemini-mock" + echo "$(date) - cursor-agent-mock" sleep 15 done \ No newline at end of file From c336e44eddd9fcad6a68b267186bb024f9340560 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Thu, 14 Aug 2025 19:28:20 +0530 Subject: [PATCH 37/48] chore: update mock --- .../coder-labs/modules/cursor-cli/testdata/cursor-cli-mock.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/coder-labs/modules/cursor-cli/testdata/cursor-cli-mock.sh b/registry/coder-labs/modules/cursor-cli/testdata/cursor-cli-mock.sh index 9da262838..acf637d06 100644 --- a/registry/coder-labs/modules/cursor-cli/testdata/cursor-cli-mock.sh +++ b/registry/coder-labs/modules/cursor-cli/testdata/cursor-cli-mock.sh @@ -2,7 +2,7 @@ if [[ "$1" == "--version" ]]; then echo "HELLO: $(bash -c env)" - echo "gemini version v2.5.0" + echo "cursor-agent version v2.5.0" exit 0 fi From b99023beba4ba264c80f2f037467ed93d1e39a23 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Thu, 14 Aug 2025 19:29:06 +0530 Subject: [PATCH 38/48] chore: remove comments --- registry/coder-labs/modules/cursor-cli/main.tf | 3 --- 1 file changed, 3 deletions(-) diff --git a/registry/coder-labs/modules/cursor-cli/main.tf b/registry/coder-labs/modules/cursor-cli/main.tf index 36b31b53d..4bee3a084 100644 --- a/registry/coder-labs/modules/cursor-cli/main.tf +++ b/registry/coder-labs/modules/cursor-cli/main.tf @@ -59,9 +59,6 @@ variable "agentapi_version" { default = "v0.4.0" } -# Running mode is non-interactive by design for automation. - - variable "force" { type = bool description = "Force allow commands unless explicitly denied" From a844fbb1f8fe93aac7c59128a38cff6ec3683408 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Sat, 16 Aug 2025 00:44:18 +0530 Subject: [PATCH 39/48] feat: update prompt and README.md --- registry/coder-labs/modules/cursor-cli/README.md | 3 +-- registry/coder-labs/modules/cursor-cli/scripts/start.sh | 5 +---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/registry/coder-labs/modules/cursor-cli/README.md b/registry/coder-labs/modules/cursor-cli/README.md index e53849375..1215ad1f1 100644 --- a/registry/coder-labs/modules/cursor-cli/README.md +++ b/registry/coder-labs/modules/cursor-cli/README.md @@ -109,8 +109,7 @@ module "cursor_cli" { ``` > [!NOTE] -> A `.cursor` directory will be created in the specified `folder`, containing the MCP configuration, rules -> Cursor CLI doesn't seem fully compatible with MCPs, so Coder tasks and Coder MCP will not work with this module. +> A `.cursor` directory will be created in the specified `folder`, containing the MCP configuration, rules. ## References diff --git a/registry/coder-labs/modules/cursor-cli/scripts/start.sh b/registry/coder-labs/modules/cursor-cli/scripts/start.sh index 8238fb060..4eaf446c2 100644 --- a/registry/coder-labs/modules/cursor-cli/scripts/start.sh +++ b/registry/coder-labs/modules/cursor-cli/scripts/start.sh @@ -58,10 +58,7 @@ fi if [ -n "$ARG_AI_PROMPT" ]; then printf "AI prompt provided\n" - # Note: As of now cursor-agent is not able to handle MCP calls properly, so we are skipping the tasks. - # ARGS+=("Execute the task end‑to‑end and also report progress to Coder. Do not only report; always produce the actual deliverable in chat. Task at hand: $ARG_AI_PROMPT") - - ARGS+=("$ARG_AI_PROMPT") + ARGS+=("Complete the task at hand in one go. Every step of the way, report your progress using coder_report_task tool with proper summary and statuses. Your task at hand: $ARG_AI_PROMPT") fi # Log and run in background, redirecting all output to the log file From f1bca89c31f8218d64fbd2942cb1ed8b52fd47a4 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Sat, 16 Aug 2025 11:33:17 +0530 Subject: [PATCH 40/48] feat: update test --- .../modules/cursor-cli/cursor-cli.tftest.hcl | 146 +++++++++++------- 1 file changed, 91 insertions(+), 55 deletions(-) diff --git a/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl b/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl index 0e30ed67a..2adb18529 100644 --- a/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl +++ b/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl @@ -1,116 +1,152 @@ -// Terraform tests for the cursor-cli module -// Validates that we render expected script content given inputs - -run "defaults" { +run "test_cursor_cli_basic" { command = plan variables { - agent_id = "test-agent" - folder = "/home/coder" + agent_id = "test-agent-123" + folder = "/home/coder/projects" + } + + assert { + condition = coder_env.status_slug.name == "CODER_MCP_APP_STATUS_SLUG" + error_message = "Status slug environment variable should be set correctly" + } + + assert { + condition = coder_env.status_slug.value == "cursorcli" + error_message = "Status slug value should be 'cursorcli'" + } + + assert { + condition = var.folder == "/home/coder/projects" + error_message = "Folder variable should be set correctly" } assert { - condition = can(regex("Cursor CLI", resource.coder_script.cursor_cli.display_name)) - error_message = "Expected coder_script to be created" + condition = var.agent_id == "test-agent-123" + error_message = "Agent ID variable should be set correctly" } } -run "non_interactive_mode" { +run "test_cursor_cli_with_api_key" { command = plan variables { - agent_id = "test-agent" - folder = "/home/coder" - output_format = "json" - ai_prompt = "refactor the auth module to use JWT tokens" + agent_id = "test-agent-456" + folder = "/home/coder/workspace" + api_key = "test-api-key-123" } assert { - // non-interactive always prints; output format propagates - condition = can(regex("OUTPUT_FORMAT='json'", resource.coder_script.cursor_cli.script)) - error_message = "Expected OUTPUT_FORMAT to be propagated" + condition = coder_env.cursor_api_key[0].name == "CURSOR_API_KEY" + error_message = "Cursor API key environment variable should be set correctly" } assert { - condition = can(regex("AI_PROMPT='refactor the auth module to use JWT tokens'", resource.coder_script.cursor_cli.script)) - error_message = "Expected ai_prompt to be propagated via AI_PROMPT" + condition = coder_env.cursor_api_key[0].value == "test-api-key-123" + error_message = "Cursor API key value should match the input" } } -run "model_and_force" { +run "test_cursor_cli_with_custom_options" { command = plan variables { - agent_id = "test-agent" - folder = "/home/coder" - model = "test-model" - force = true + agent_id = "test-agent-789" + folder = "/home/coder/custom" + order = 5 + group = "development" + icon = "/icon/custom.svg" + model = "sonnet-4" + ai_prompt = "Help me write better code" + force = false + install_cursor_cli = false + install_agentapi = false + } + + 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.model == "sonnet-4" + error_message = "Model variable should be set to 'sonnet-4'" } assert { - condition = can(regex("MODEL='test-model'", resource.coder_script.cursor_cli.script)) - error_message = "Expected MODEL to be propagated" + condition = var.ai_prompt == "Help me write better code" + error_message = "AI prompt variable should be set correctly" } assert { - condition = can(regex("FORCE='true'", resource.coder_script.cursor_cli.script)) - error_message = "Expected FORCE true to be propagated" + condition = var.force == false + error_message = "Force variable should be set to false" } } -run "additional_settings_propagated" { +run "test_cursor_cli_with_mcp_and_rules" { command = plan variables { - agent_id = "test-agent" - folder = "/home/coder" - mcp_json = jsonencode({ mcpServers = { foo = { command = "foo", type = "stdio" } } }) + agent_id = "test-agent-mcp" + folder = "/home/coder/mcp-test" + mcp = jsonencode({ + mcpServers = { + test = { + command = "test-server" + args = ["--config", "test.json"] + } + } + }) rules_files = { - "global.yml" = "version: 1\nrules:\n - name: global\n include: ['**/*']\n description: global rule" + "general.md" = "# General coding rules\n- Write clean code\n- Add comments" + "security.md" = "# Security rules\n- Never commit secrets\n- Validate inputs" } - pre_install_script = "#!/bin/bash\necho pre-install" - post_install_script = "#!/bin/bash\necho post-install" } - // Ensure project mcp_json is passed assert { - condition = can(regex(base64encode(jsonencode({ mcpServers = { foo = { command = "foo", type = "stdio" } } })), resource.coder_script.cursor_cli.script)) - error_message = "Expected PROJECT_MCP_JSON (base64) to be in the install step" + condition = var.mcp != null + error_message = "MCP configuration should be provided" } - // Ensure rules map is passed assert { - condition = can(regex(base64encode(jsonencode({ "global.yml" : "version: 1\nrules:\n - name: global\n include: ['**/*']\n description: global rule" })), resource.coder_script.cursor_cli.script)) - error_message = "Expected PROJECT_RULES_JSON (base64) to be in the install step" + condition = var.rules_files != null + error_message = "Rules files should be provided" } - // Ensure pre/post install scripts are embedded - assert { - condition = can(regex(base64encode("#!/bin/bash\necho pre-install"), resource.coder_script.cursor_cli.script)) - error_message = "Expected pre-install script to be embedded" - } assert { - condition = can(regex(base64encode("#!/bin/bash\necho post-install"), resource.coder_script.cursor_cli.script)) - error_message = "Expected post-install script to be embedded" + condition = length(var.rules_files) == 2 + error_message = "Should have 2 rules files" } } -run "api_key_env_var" { +run "test_cursor_cli_with_scripts" { command = plan variables { - agent_id = "test-agent" - folder = "/home/coder" - api_key = "sk-test-123" + 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 = resource.coder_env.cursor_api_key[0].name == "CURSOR_API_KEY" - error_message = "Expected CURSOR_API_KEY env to be created when api_key is set" + condition = var.pre_install_script == "echo 'Pre-install script'" + error_message = "Pre-install script should be set correctly" } assert { - condition = resource.coder_env.cursor_api_key[0].value == "sk-test-123" - error_message = "Expected CURSOR_API_KEY env value to be set from api_key" + condition = var.post_install_script == "echo 'Post-install script'" + error_message = "Post-install script should be set correctly" } } From d673f3a9febbd7f005aa05fad72a5ed1e9373718 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Sat, 16 Aug 2025 11:37:34 +0530 Subject: [PATCH 41/48] feat: bun fmt --- .../modules/cursor-cli/cursor-cli.tftest.hcl | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl b/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl index 2adb18529..f8e917a1f 100644 --- a/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl +++ b/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl @@ -51,14 +51,14 @@ run "test_cursor_cli_with_custom_options" { command = plan variables { - agent_id = "test-agent-789" - folder = "/home/coder/custom" - order = 5 - group = "development" - icon = "/icon/custom.svg" - model = "sonnet-4" - ai_prompt = "Help me write better code" - force = false + agent_id = "test-agent-789" + folder = "/home/coder/custom" + order = 5 + group = "development" + icon = "/icon/custom.svg" + model = "sonnet-4" + ai_prompt = "Help me write better code" + force = false install_cursor_cli = false install_agentapi = false } @@ -100,7 +100,7 @@ run "test_cursor_cli_with_mcp_and_rules" { variables { agent_id = "test-agent-mcp" folder = "/home/coder/mcp-test" - mcp = jsonencode({ + mcp = jsonencode({ mcpServers = { test = { command = "test-server" @@ -109,7 +109,7 @@ run "test_cursor_cli_with_mcp_and_rules" { } }) rules_files = { - "general.md" = "# General coding rules\n- Write clean code\n- Add comments" + "general.md" = "# General coding rules\n- Write clean code\n- Add comments" "security.md" = "# Security rules\n- Never commit secrets\n- Validate inputs" } } @@ -134,9 +134,9 @@ run "test_cursor_cli_with_scripts" { command = plan variables { - agent_id = "test-agent-scripts" - folder = "/home/coder/scripts" - pre_install_script = "echo 'Pre-install script'" + agent_id = "test-agent-scripts" + folder = "/home/coder/scripts" + pre_install_script = "echo 'Pre-install script'" post_install_script = "echo 'Post-install script'" } From dfc007da5613beec4fbeb0500c349aa5ff02b6d7 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Mon, 18 Aug 2025 20:03:25 +0530 Subject: [PATCH 42/48] feat: bump agentapi version --- registry/coder-labs/modules/cursor-cli/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/coder-labs/modules/cursor-cli/main.tf b/registry/coder-labs/modules/cursor-cli/main.tf index 4bee3a084..2adfc7415 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.4.0" + default = "v0.4.2" } variable "force" { From 037c223f5c8c4ebb96ef16d7781907f624522fbd Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Mon, 18 Aug 2025 21:00:31 +0530 Subject: [PATCH 43/48] feat: bump agentapi version --- registry/coder-labs/modules/cursor-cli/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/coder-labs/modules/cursor-cli/main.tf b/registry/coder-labs/modules/cursor-cli/main.tf index 2adfc7415..39c88d65e 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.4.2" + default = "v0.6.0" } variable "force" { From 6748088538e848cf59493ec6b3a7b3bf61ee2169 Mon Sep 17 00:00:00 2001 From: 35C4n0r <70096901+35C4n0r@users.noreply.github.com> Date: Mon, 18 Aug 2025 21:20:00 +0530 Subject: [PATCH 44/48] Update main.tf --- registry/coder-labs/modules/cursor-cli/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/coder-labs/modules/cursor-cli/main.tf b/registry/coder-labs/modules/cursor-cli/main.tf index 39c88d65e..482103718 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.6.0" + default = "v0.5.0" } variable "force" { From 7db61610cdface70f59d184542f34ae28351f09c Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Mon, 18 Aug 2025 21:35:47 +0530 Subject: [PATCH 45/48] feat: add agent type --- registry/coder-labs/modules/cursor-cli/scripts/start.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/coder-labs/modules/cursor-cli/scripts/start.sh b/registry/coder-labs/modules/cursor-cli/scripts/start.sh index 4eaf446c2..1bbc493bb 100644 --- a/registry/coder-labs/modules/cursor-cli/scripts/start.sh +++ b/registry/coder-labs/modules/cursor-cli/scripts/start.sh @@ -64,4 +64,4 @@ fi # Log and run in background, redirecting all output to the log file printf "Running: %q %s\n" "$CURSOR_CMD" "$(printf '%q ' "${ARGS[@]}")" -agentapi server --term-width 67 --term-height 1190 -- "$CURSOR_CMD" "${ARGS[@]}" +agentapi server --type cursor --term-width 67 --term-height 1190 -- "$CURSOR_CMD" "${ARGS[@]}" From 77709d1ea8d9225b52dffa8deefe4d277c7ed854 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Mon, 18 Aug 2025 23:20:27 +0530 Subject: [PATCH 46/48] fix: documentation --- registry/coder-labs/modules/cursor-cli/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/registry/coder-labs/modules/cursor-cli/README.md b/registry/coder-labs/modules/cursor-cli/README.md index 1215ad1f1..cce8550e9 100644 --- a/registry/coder-labs/modules/cursor-cli/README.md +++ b/registry/coder-labs/modules/cursor-cli/README.md @@ -37,7 +37,6 @@ module "cursor_cli" { # Optional install_cursor_cli = true - cursor_cli_version = "latest" force = true model = "gpt-5" ai_prompt = data.coder_parameter.ai_prompt.value From 71a1f3d409a9391b37ba9772cd00ae4d8b357be0 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Mon, 18 Aug 2025 23:30:07 +0530 Subject: [PATCH 47/48] fix: update documentation --- registry/coder-labs/modules/cursor-cli/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/registry/coder-labs/modules/cursor-cli/README.md b/registry/coder-labs/modules/cursor-cli/README.md index cce8550e9..3798dc280 100644 --- a/registry/coder-labs/modules/cursor-cli/README.md +++ b/registry/coder-labs/modules/cursor-cli/README.md @@ -109,6 +109,7 @@ module "cursor_cli" { > [!NOTE] > A `.cursor` directory will be created in the specified `folder`, containing the MCP configuration, rules. +> To use this module with tasks, please pass the API Key obtained from Cursor to the `api_key` variable. To obtain the api key follow the instructions [here](https://docs.cursor.com/en/cli/reference/authentication#step-1%3A-generate-an-api-key) ## References From 522f0952d9f3470cd69e2949affe1ea77eaae9b5 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Mon, 18 Aug 2025 23:31:11 +0530 Subject: [PATCH 48/48] bun fmt --- registry/coder-labs/modules/cursor-cli/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/coder-labs/modules/cursor-cli/README.md b/registry/coder-labs/modules/cursor-cli/README.md index 3798dc280..e50ca1a33 100644 --- a/registry/coder-labs/modules/cursor-cli/README.md +++ b/registry/coder-labs/modules/cursor-cli/README.md @@ -109,7 +109,7 @@ module "cursor_cli" { > [!NOTE] > A `.cursor` directory will be created in the specified `folder`, containing the MCP configuration, rules. -> To use this module with tasks, please pass the API Key obtained from Cursor to the `api_key` variable. To obtain the api key follow the instructions [here](https://docs.cursor.com/en/cli/reference/authentication#step-1%3A-generate-an-api-key) +> To use this module with tasks, please pass the API Key obtained from Cursor to the `api_key` variable. To obtain the api key follow the instructions [here](https://docs.cursor.com/en/cli/reference/authentication#step-1%3A-generate-an-api-key) ## References