diff --git a/registry/coder/modules/cursor-cli/README.md b/registry/coder/modules/cursor-cli/README.md new file mode 100644 index 000000000..dacb9d70c --- /dev/null +++ b/registry/coder/modules/cursor-cli/README.md @@ -0,0 +1,261 @@ +--- +display_name: Cursor CLI +description: Run Cursor CLI agent in your workspace with MCP and force mode support +icon: ../../../../.icons/cursor.svg +verified: true +tags: [cli, cursor, ai, agent, mcp, automation] +--- + +# Cursor CLI + +Run the [Cursor CLI](https://docs.cursor.com/en/cli/overview) agent in your workspace for terminal-based AI coding assistance. Supports both interactive and non-interactive modes, MCP (Model Context Protocol), and automation features. + +```tf +module "cursor-cli" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/cursor-cli/coder" + version = "1.0.0" + agent_id = coder_agent.example.id + folder = "/home/coder" +} +``` + +## Prerequisites + +- You must add the [Coder Login](https://registry.coder.com/modules/coder-login) module to your template + +## Features + +- **CLI Agent**: Terminal-based AI coding assistant with interactive and non-interactive modes +- **AgentAPI Integration**: Web interface for CLI interactions +- **Interactive Mode**: Conversational sessions with text output +- **Non-Interactive Mode**: Automation-friendly for scripts and CI pipelines +- **Session Management**: List, resume, and manage coding sessions +- **Model Selection**: Support for multiple AI models (GPT-5, Claude, etc.) +- **MCP Support**: Model Context Protocol for extended functionality +- **Rules System**: Custom agent behavior configuration + +## Examples + +### Basic setup + +```tf +module "coder-login" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/coder-login/coder" + version = "1.0.15" + agent_id = coder_agent.example.id +} + +module "cursor-cli" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/cursor-cli/coder" + version = "1.0.0" + agent_id = coder_agent.example.id + folder = "/home/coder/project" + install_cursor_cli = true + install_agentapi = true +} +``` + +### CLI only (no web interface) + +```tf +module "cursor-cli" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/cursor-cli/coder" + version = "1.0.0" + agent_id = coder_agent.example.id + folder = "/home/coder/project" + install_cursor_cli = true + install_agentapi = false +} +``` + +### With MCP and force mode for automation + +```tf +module "cursor-cli" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/cursor-cli/coder" + version = "1.0.0" + agent_id = coder_agent.example.id + folder = "/home/coder/project" + + # MCP Configuration + enable_mcp = true + mcp_config_path = "/home/coder/.cursor/custom-mcp.json" + + # Automation Features + enable_force_mode = true + default_model = "gpt-5" + + # Rules System + enable_rules = true +} +``` + +### Integration with Coder Tasks + +```tf +# Cursor CLI module with automation features +module "cursor-cli" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/cursor-cli/coder" + version = "1.0.0" + agent_id = coder_agent.example.id + enable_force_mode = true + default_model = "claude-4-sonnet" +} + +# Automated code review task +resource "coder_task" "ai_code_review" { + agent_id = coder_agent.example.id + name = "AI Code Review" + command = "cursor-agent -p 'review the latest git changes for security issues and best practices' --force --output-format text" + cron = "0 9 * * 1-5" # Weekdays at 9 AM +} + +# Automated test generation +resource "coder_task" "generate_tests" { + agent_id = coder_agent.example.id + name = "Generate Missing Tests" + command = "cursor-agent -p 'analyze the src/ directory and generate unit tests for functions missing test coverage' --force" + cron = "0 18 * * *" # Daily at 6 PM +} + +# Documentation updates +resource "coder_task" "update_docs" { + agent_id = coder_agent.example.id + name = "Update Documentation" + command = "cursor-agent -p 'review and update README.md to reflect any new features or API changes' --force --model gpt-5" + cron = "0 12 * * 0" # Sundays at noon +} +``` + +### With custom pre-install script + +```tf +module "cursor-cli" { + count = data.coder_workspace.me.start_count + source = "registry.coder.com/coder/cursor-cli/coder" + version = "1.0.0" + agent_id = coder_agent.example.id + + pre_install_script = <<-EOT + # Install additional dependencies + npm install -g typescript + EOT +} +``` + +## Usage + +### Web Interface + +1. Click the "Cursor CLI" button to access the web interface +2. Start interactive sessions with text output + +### Terminal Usage + +```bash +# Interactive mode (default) +cursor-agent + +# Interactive mode with initial prompt +cursor-agent "refactor the auth module to use JWT tokens" + +# Non-interactive mode with text output +cursor-agent -p "find and fix performance issues" --output-format text + +# Force mode for automation (non-interactive) +cursor-agent -p "review code for security issues" --force + +# Use specific model +cursor-agent -p "add error handling" --model "gpt-5" + +# Combine force mode with model selection +cursor-agent -p "generate comprehensive tests" --force --model "claude-4-sonnet" + +# Session management +cursor-agent ls # List all previous chats +cursor-agent resume # Resume latest conversation +cursor-agent --resume="chat-id" # Resume specific conversation +``` + +### Interactive Mode Features + +- Conversational sessions with the agent +- Review proposed changes before applying +- Real-time guidance and steering +- Text-based output optimized for terminal use +- Session persistence and resumption + +### Non-Interactive Mode Features + +- Automation-friendly for scripts and CI pipelines +- Direct prompt execution with text output +- Model selection support +- Git integration for change reviews + +## Screenshots + +### Cursor CLI with Coder Tasks Integration + +*Screenshot showing the cursor-cli module working with automated Coder Tasks will be added here* + +- Interactive web interface for cursor-agent +- Automated code review tasks running in background +- Terminal output showing force mode execution +- MCP integration with custom tools + +## Configuration + +The module supports comprehensive configuration options: + +### Core Features +- **MCP (Model Context Protocol)**: Automatically detects `mcp.json` configuration or uses custom path +- **Rules System**: Supports `.cursor/rules` directory for custom agent behavior +- **Force Mode**: Enable non-interactive automation for CI/CD pipelines +- **Model Selection**: Set default AI model (gpt-5, claude-4-sonnet, etc.) +- **Environment Variables**: Respects Cursor CLI environment settings + +### Available Variables + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `enable_mcp` | bool | `true` | Enable MCP (Model Context Protocol) support | +| `mcp_config_path` | string | `""` | Path to custom MCP configuration file | +| `enable_force_mode` | bool | `false` | Enable force mode for non-interactive automation | +| `default_model` | string | `""` | Default AI model (e.g., gpt-5, claude-4-sonnet) | +| `enable_rules` | bool | `true` | Enable the rules system (.cursor/rules directory) | +| `install_cursor_cli` | bool | `true` | Whether to install Cursor CLI | +| `install_agentapi` | bool | `true` | Whether to install AgentAPI web interface | +| `folder` | string | `"/home/coder"` | Working directory for cursor-agent | + +## Troubleshooting + +The module creates log files in the workspace's `~/.cursor-cli-module` directory. Check these files if you encounter issues: + +```bash +# Check installation logs +cat ~/.cursor-cli-module/install.log + +# Check runtime logs +cat ~/.cursor-cli-module/runtime.log + +# Verify Cursor CLI installation +cursor-agent --help +``` + +### Common Issues + +1. **Cursor CLI not found**: Ensure `install_cursor_cli = true` or install manually: + + ```bash + curl https://cursor.com/install -fsS | bash + ``` + +2. **Permission issues**: Check that the installation script has proper permissions + +3. **Path issues**: The module automatically adds Cursor CLI to PATH, but you may need to restart your shell diff --git a/registry/coder/modules/cursor-cli/main.test.ts b/registry/coder/modules/cursor-cli/main.test.ts new file mode 100644 index 000000000..0e0a674bd --- /dev/null +++ b/registry/coder/modules/cursor-cli/main.test.ts @@ -0,0 +1,80 @@ +import { describe, expect, it } from "bun:test"; +import { + runTerraformApply, + runTerraformInit, + testRequiredVariables, +} from "~test"; + +describe("cursor-cli", async () => { + await runTerraformInit(import.meta.dir); + + testRequiredVariables(import.meta.dir, { + agent_id: "foo", + }); + + it("default output with CLI enabled", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + }); + + // Check that AgentAPI module is created + const agentapi_module = state.resources.find( + (res) => res.type === "module" && res.name === "agentapi", + ); + expect(agentapi_module).not.toBeNull(); + }); + + it("adds custom folder", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + folder: "/foo/bar", + }); + + // Check that AgentAPI module is created with custom folder + const agentapi_module = state.resources.find( + (res) => res.type === "module" && res.name === "agentapi", + ); + expect(agentapi_module).not.toBeNull(); + }); + + it("expect order to be set", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + order: "22", + }); + + // Check that AgentAPI module is created + const agentapi_module = state.resources.find( + (res) => res.type === "module" && res.name === "agentapi", + ); + expect(agentapi_module).not.toBeNull(); + }); + + it("disables CLI installation", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + install_cursor_cli: "false", + install_agentapi: "false", + }); + + // AgentAPI module should still exist but with install_agentapi = false + const agentapi_module = state.resources.find( + (res) => res.type === "module" && res.name === "agentapi", + ); + expect(agentapi_module).not.toBeNull(); + }); + + it("enables only CLI without web interface", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + install_cursor_cli: "true", + install_agentapi: "false", + }); + + // AgentAPI module should exist but with install_agentapi = false + const agentapi_module = state.resources.find( + (res) => res.type === "module" && res.name === "agentapi", + ); + expect(agentapi_module).not.toBeNull(); + }); +}); diff --git a/registry/coder/modules/cursor-cli/main.tf b/registry/coder/modules/cursor-cli/main.tf new file mode 100644 index 000000000..75e86a875 --- /dev/null +++ b/registry/coder/modules/cursor-cli/main.tf @@ -0,0 +1,154 @@ +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 "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.3.3" +} + +variable "subdomain" { + type = bool + description = "Whether to use a subdomain for AgentAPI." + default = true +} + +variable "pre_install_script" { + type = string + description = "Custom script to run before installing Cursor CLI." + default = null +} + +variable "post_install_script" { + type = string + description = "Custom script to run after installing Cursor CLI." + default = null +} + +variable "enable_mcp" { + type = bool + description = "Whether to enable MCP (Model Context Protocol) support." + default = true +} + +variable "mcp_config_path" { + type = string + description = "Path to the MCP configuration file (mcp.json)." + default = "" +} + +variable "enable_force_mode" { + type = bool + description = "Whether to enable force mode for non-interactive automation." + default = false +} + +variable "default_model" { + type = string + description = "Default AI model to use (e.g., gpt-5, claude-4-sonnet)." + default = "" +} + +variable "enable_rules" { + type = bool + description = "Whether to enable the rules system (.cursor/rules directory)." + default = true +} + +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" +} + +module "agentapi" { + source = "registry.coder.com/coder/agentapi/coder" + version = "1.1.0" + + agent_id = var.agent_id + web_app_slug = local.app_slug + web_app_order = var.order + web_app_group = var.group + web_app_icon = var.icon + web_app_display_name = "Cursor CLI" + cli_app_slug = "${local.app_slug}-terminal" + cli_app_display_name = "Cursor CLI Terminal" + module_dir_name = local.module_dir_name + install_agentapi = var.install_agentapi + agentapi_version = var.agentapi_version + agentapi_subdomain = var.subdomain + pre_install_script = var.pre_install_script + post_install_script = var.post_install_script + start_script = local.start_script + 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_FOLDER='${var.folder}' \ + ARG_INSTALL='${var.install_cursor_cli}' \ + ARG_ENABLE_MCP='${var.enable_mcp}' \ + ARG_MCP_CONFIG_PATH='${var.mcp_config_path}' \ + ARG_ENABLE_FORCE_MODE='${var.enable_force_mode}' \ + ARG_DEFAULT_MODEL='${var.default_model}' \ + ARG_ENABLE_RULES='${var.enable_rules}' \ + /tmp/install.sh + EOT +} diff --git a/registry/coder/modules/cursor-cli/scripts/install.sh b/registry/coder/modules/cursor-cli/scripts/install.sh new file mode 100644 index 000000000..60f4d006f --- /dev/null +++ b/registry/coder/modules/cursor-cli/scripts/install.sh @@ -0,0 +1,103 @@ +#!/bin/bash + +# Function to check if a command exists +command_exists() { + command -v "$1" > /dev/null 2>&1 +} + +set -o nounset + +echo "--------------------------------" +echo "folder: $ARG_FOLDER" +echo "install: $ARG_INSTALL" +echo "enable_mcp: $ARG_ENABLE_MCP" +echo "mcp_config_path: $ARG_MCP_CONFIG_PATH" +echo "enable_force_mode: $ARG_ENABLE_FORCE_MODE" +echo "default_model: $ARG_DEFAULT_MODEL" +echo "enable_rules: $ARG_ENABLE_RULES" +echo "--------------------------------" + +set +o nounset + +if [ "${ARG_INSTALL}" = "true" ]; then + echo "Installing Cursor CLI..." + + # Install Cursor CLI using the official installer + curl https://cursor.com/install -fsS | bash + + # Add cursor-agent to PATH if not already there + if ! command_exists cursor-agent; then + echo 'export PATH="$HOME/.cursor/bin:$PATH"' >> "$HOME/.bashrc" + echo 'export PATH="$HOME/.cursor/bin:$PATH"' >> "$HOME/.zshrc" 2> /dev/null || true + export PATH="$HOME/.cursor/bin:$PATH" + fi + + echo "Cursor CLI installed" + + # Configure MCP if enabled + if [ "${ARG_ENABLE_MCP}" = "true" ]; then + echo "Configuring MCP (Model Context Protocol)..." + + # Create MCP config directory if it doesn't exist + mkdir -p "$HOME/.cursor" + + # If custom MCP config path is provided, copy it + if [ -n "${ARG_MCP_CONFIG_PATH}" ] && [ -f "${ARG_MCP_CONFIG_PATH}" ]; then + cp "${ARG_MCP_CONFIG_PATH}" "$HOME/.cursor/mcp.json" + echo "MCP configuration copied from ${ARG_MCP_CONFIG_PATH}" + else + # Create a basic MCP config if none exists + if [ ! -f "$HOME/.cursor/mcp.json" ]; then + cat > "$HOME/.cursor/mcp.json" << 'EOF' +{ + "mcpServers": { + "filesystem": { + "command": "npx", + "args": ["@modelcontextprotocol/server-filesystem", "/tmp"] + } + } +} +EOF + echo "Basic MCP configuration created" + fi + fi + fi + + # Configure rules system if enabled + if [ "${ARG_ENABLE_RULES}" = "true" ]; then + echo "Setting up Cursor rules system..." + mkdir -p "$HOME/.cursor/rules" + + # Create a basic rules file if none exists + if [ ! -f "$HOME/.cursor/rules/general.md" ]; then + cat > "$HOME/.cursor/rules/general.md" << 'EOF' +# General Coding Rules + +## Code Style +- Use consistent indentation (2 spaces for JS/TS, 4 for Python) +- Add meaningful comments for complex logic +- Follow language-specific naming conventions + +## Best Practices +- Write tests for new functionality +- Handle errors gracefully +- Use descriptive variable and function names +EOF + echo "Basic rules configuration created" + fi + fi +else + echo "Skipping Cursor CLI installation" +fi + +# Verify installation +if command_exists cursor-agent; then + CURSOR_CMD=cursor-agent +elif [ -f "$HOME/.cursor/bin/cursor-agent" ]; then + CURSOR_CMD="$HOME/.cursor/bin/cursor-agent" +else + echo "Warning: Cursor CLI is not installed or not found in PATH. Please enable install_cursor_cli or install it manually." + echo "You can install it manually with: curl https://cursor.com/install -fsS | bash" +fi + +echo "Cursor CLI setup complete" diff --git a/registry/coder/modules/cursor-cli/scripts/start.sh b/registry/coder/modules/cursor-cli/scripts/start.sh new file mode 100644 index 000000000..a44e7f82b --- /dev/null +++ b/registry/coder/modules/cursor-cli/scripts/start.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +# Function to check if a command exists +command_exists() { + command -v "$1" > /dev/null 2>&1 +} + +# Set working directory +if [ -n "${ARG_FOLDER:-}" ] && [ -d "${ARG_FOLDER}" ]; then + cd "${ARG_FOLDER}" || { + echo "Warning: Could not change to directory ${ARG_FOLDER}, using current directory" + } +fi + +# Find cursor-agent command +if command_exists cursor-agent; then + CURSOR_CMD=cursor-agent +elif [ -f "$HOME/.cursor/bin/cursor-agent" ]; then + CURSOR_CMD="$HOME/.cursor/bin/cursor-agent" +else + echo "Error: Cursor CLI is not installed. Please enable install_cursor_cli or install it manually." + echo "You can install it manually with: curl https://cursor.com/install -fsS | bash" + exit 1 +fi + +echo "Starting Cursor CLI in $(pwd)" +echo "Interactive mode with text output enabled" +echo "Available commands:" +echo " - Start interactive session: cursor-agent" +echo " - Non-interactive mode: cursor-agent -p 'your prompt here'" +echo " - With specific model: cursor-agent -p 'prompt' --model 'gpt-5'" +echo " - Text output format: cursor-agent -p 'prompt' --output-format text" +echo " - Force mode (non-interactive): cursor-agent -p 'prompt' --force" +echo " - List sessions: cursor-agent ls" +echo " - Resume session: cursor-agent resume" +echo "" + +# Set up environment variables for configuration +if [ -n "${ARG_DEFAULT_MODEL:-}" ]; then + export CURSOR_DEFAULT_MODEL="${ARG_DEFAULT_MODEL}" + echo "Default model set to: ${ARG_DEFAULT_MODEL}" +fi + +if [ "${ARG_ENABLE_FORCE_MODE:-false}" = "true" ]; then + export CURSOR_FORCE_MODE="true" + echo "Force mode enabled for non-interactive automation" +fi + +if [ "${ARG_ENABLE_MCP:-true}" = "true" ]; then + echo "MCP (Model Context Protocol) support enabled" +fi + +if [ "${ARG_ENABLE_RULES:-true}" = "true" ]; then + echo "Rules system enabled (.cursor/rules directory)" +fi + +echo "" + +# Configure for interactive mode with text output +# If no arguments provided, start in interactive mode +if [ $# -eq 0 ]; then + echo "Starting interactive session..." + exec "$CURSOR_CMD" +else + # Pass through all arguments for custom usage + exec "$CURSOR_CMD" "$@" +fi diff --git a/scripts/terraform_test_all.sh b/scripts/terraform_test_all.sh new file mode 100644 index 000000000..01258904d --- /dev/null +++ b/scripts/terraform_test_all.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Find all directories that contain any .tftest.hcl files and run terraform test in each + +run_dir() { + local dir="$1" + echo "==> Running terraform test in $dir" + (cd "$dir" && terraform init -upgrade -input=false -no-color > /dev/null && terraform test -no-color -verbose) +} + +mapfile -t test_dirs < <(find . -type f -name "*.tftest.hcl" -print0 | xargs -0 -I{} dirname {} | sort -u) + +if [[ ${#test_dirs[@]} -eq 0 ]]; then + echo "No .tftest.hcl tests found." + exit 0 +fi + +status=0 +for d in "${test_dirs[@]}"; do + if ! run_dir "$d"; then + status=1 + fi +done + +exit $status