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..e50ca1a33 --- /dev/null +++ b/registry/coder-labs/modules/cursor-cli/README.md @@ -0,0 +1,123 @@ +--- +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. + +A full example with MCP, rules, and pre/post install scripts: + +```tf + +data "coder_parameter" "ai_prompt" { + 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" { + source = "registry.coder.com/coder-labs/cursor-cli/coder" + version = "0.1.0" + agent_id = coder_agent.example.id + folder = "/home/coder/project" + + # Optional + install_cursor_cli = true + force = true + model = "gpt-5" + ai_prompt = data.coder_parameter.ai_prompt.value + + # Minimal MCP server (writes `folder/.cursor/mcp.json`): + mcp = jsonencode({ + mcpServers = { + 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 + 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 + + # Provide a map of file name to content; files are written to `folder/.cursor/rules/`. + rules_files = { + "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.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 + } +} +``` + +> [!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 + +- 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 `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 + +- 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..f8e917a1f --- /dev/null +++ b/registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl @@ -0,0 +1,152 @@ +run "test_cursor_cli_basic" { + command = plan + + variables { + 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 = var.agent_id == "test-agent-123" + error_message = "Agent ID variable should be set correctly" + } +} + +run "test_cursor_cli_with_api_key" { + command = plan + + variables { + agent_id = "test-agent-456" + folder = "/home/coder/workspace" + api_key = "test-api-key-123" + } + + assert { + condition = coder_env.cursor_api_key[0].name == "CURSOR_API_KEY" + error_message = "Cursor API key environment variable should be set correctly" + } + + assert { + condition = coder_env.cursor_api_key[0].value == "test-api-key-123" + error_message = "Cursor API key value should match the input" + } +} + +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 + 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 = var.ai_prompt == "Help me write better code" + error_message = "AI prompt variable should be set correctly" + } + + assert { + condition = var.force == false + error_message = "Force variable should be set to false" + } +} + +run "test_cursor_cli_with_mcp_and_rules" { + command = plan + + variables { + agent_id = "test-agent-mcp" + folder = "/home/coder/mcp-test" + mcp = jsonencode({ + mcpServers = { + test = { + command = "test-server" + args = ["--config", "test.json"] + } + } + }) + rules_files = { + "general.md" = "# General coding rules\n- Write clean code\n- Add comments" + "security.md" = "# Security rules\n- Never commit secrets\n- Validate inputs" + } + } + + assert { + condition = var.mcp != null + error_message = "MCP configuration should be provided" + } + + assert { + condition = var.rules_files != null + error_message = "Rules files should be provided" + } + + assert { + condition = length(var.rules_files) == 2 + error_message = "Should have 2 rules files" + } +} + +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'" + post_install_script = "echo 'Post-install script'" + } + + assert { + condition = var.pre_install_script == "echo 'Pre-install script'" + error_message = "Pre-install script should be set correctly" + } + + assert { + condition = var.post_install_script == "echo 'Post-install script'" + error_message = "Post-install script should be set correctly" + } +} 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..52bc993f0 --- /dev/null +++ b/registry/coder-labs/modules/cursor-cli/main.test.ts @@ -0,0 +1,212 @@ +import { afterEach, beforeAll, describe, expect, setDefaultTimeout, test } from "bun:test"; +import { execContainer, runTerraformInit, writeFileContainer } from "~test"; +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)[] = []; +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); + } + } +}); + +interface SetupProps { + skipAgentAPIMock?: boolean; + skipCursorCliMock?: boolean; + moduleVariables?: Record; + agentapiMockScript?: string; +} + +const setup = async (props?: SetupProps): Promise<{ id: string }> => { + const projectDir = "/home/coder/project"; + const { id } = await setupUtil({ + moduleDir: import.meta.dir, + moduleVariables: { + 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 () => { + beforeAll(async () => { + await runTerraformInit(import.meta.dir); + }); + + test("agentapi-happy-path", async () => { + const { id } = await setup({}); + 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({ + moduleVariables: { + mcp: 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({ + 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({ + 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({ + 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({ + 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({ + 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("install-test-cursor-cli-latest", async () => { + const { id } = await setup({ + 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 new file mode 100644 index 000000000..482103718 --- /dev/null +++ b/registry/coder-labs/modules/cursor-cli/main.tf @@ -0,0 +1,179 @@ +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." +} + +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.5.0" +} + +variable "force" { + type = bool + description = "Force allow commands unless explicitly denied" + default = true +} + +variable "model" { + type = string + description = "Model to use (e.g., sonnet-4, sonnet-4-thinking, gpt-5)" + default = "" +} + +variable "ai_prompt" { + type = string + description = "AI prompt/task passed to cursor-agent." + default = "" +} + +variable "api_key" { + type = string + description = "API key for Cursor CLI." + default = "" + sensitive = true +} + +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 +} + +variable "rules_files" { + type = map(string) + 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 +} + +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 = "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 +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 +} + +module "agentapi" { + source = "registry.coder.com/coder/agentapi/coder" + version = "1.1.1" + + 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 + 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}' \ + /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_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}' \ + 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 new file mode 100644 index 000000000..5477f07dd --- /dev/null +++ b/registry/coder-labs/modules/cursor-cli/scripts/install.sh @@ -0,0 +1,122 @@ +#!/bin/bash + +set -o errexit +set -o pipefail + +command_exists() { + 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_CODER_MCP_APP_STATUS_SLUG=${ARG_CODER_MCP_APP_STATUS_SLUG:-} + +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 "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 +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 + 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 at: $(command -v cursor-agent || true) (resolved: $FOUND_BIN)" + fi +} + +# Write MCP config to user's home if provided (ARG_FOLDER/.cursor/mcp.json) +function write_mcp_config() { + TARGET_DIR="$ARG_FOLDER/.cursor" + TARGET_FILE="$TARGET_DIR/mcp.json" + mkdir -p "$TARGET_DIR" + + CURSOR_MCP_HACK_SCRIPT=$( + cat << EOF +#!/usr/bin/env bash +set -e + +# --- Set environment variables --- +export CODER_MCP_APP_STATUS_SLUG="${ARG_CODER_MCP_APP_STATUS_SLUG}" +export CODER_MCP_AI_AGENTAPI_URL="http://localhost:3284" +export CODER_AGENT_URL="${CODER_AGENT_URL}" +export CODER_AGENT_TOKEN="${CODER_AGENT_TOKEN}" + +# --- Launch the MCP server --- +exec coder exp mcp server +EOF + ) + echo "$CURSOR_MCP_HACK_SCRIPT" > "/tmp/mcp-hack.sh" + chmod +x /tmp/mcp-hack.sh + + CODER_MCP=$( + cat << EOF +{ + "coder": { + "args": [], + "command": "/tmp/mcp-hack.sh", + "description": "Report ALL tasks and statuses (in progress, done, failed) you are working on.", + "name": "Coder", + "timeout": 3000, + "type": "stdio", + "trust": true + } +} +EOF + ) + + 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) +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 new file mode 100644 index 000000000..1bbc493bb --- /dev/null +++ b/registry/coder-labs/modules/cursor-cli/scripts/start.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +set -o errexit +set -o pipefail + +command_exists() { + command -v "$1" > /dev/null 2>&1 +} + +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} + +echo "--------------------------------" +echo "install: $ARG_INSTALL" +echo "version: $ARG_VERSION" +echo "folder: $ARG_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." + exit 1 +fi + +# Ensure working directory exists +if [ -d "$ARG_FOLDER" ]; then + cd "$ARG_FOLDER" +else + mkdir -p "$ARG_FOLDER" + cd "$ARG_FOLDER" +fi + +ARGS=() + +# global flags +if [ -n "$ARG_MODEL" ]; then + ARGS+=("-m" "$ARG_MODEL") +fi +if [ "$ARG_FORCE" = "true" ]; then + ARGS+=("-f") +fi + +if [ -n "$ARG_AI_PROMPT" ]; then + printf "AI prompt provided\n" + 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 +printf "Running: %q %s\n" "$CURSOR_CMD" "$(printf '%q ' "${ARGS[@]}")" + +agentapi server --type cursor --term-width 67 --term-height 1190 -- "$CURSOR_CMD" "${ARGS[@]}" 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..acf637d06 --- /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 "cursor-agent version v2.5.0" + exit 0 +fi + +set -e + +while true; do + echo "$(date) - cursor-agent-mock" + sleep 15 +done \ No newline at end of file