Skip to content

feat(cursor-cli): add Cursor CLI module #309

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 40 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
a8d9b71
feat(cursor-cli): add Cursor Agent CLI module (interactive default, M…
matifali Aug 8, 2025
7483c77
feat(cursor-cli): add project MCP and rules support; non-interactive …
matifali Aug 8, 2025
a3aa1cf
chore(cursor-cli): remove Bun test; terraform tests only
matifali Aug 8, 2025
a075139
refactor(cursor-cli): drop extra_args, binary_name, base_command, add…
matifali Aug 8, 2025
2a65de4
chore: format cursor-cli module and tests
matifali Aug 8, 2025
5e3f555
feat(cursor-cli): configure Coder MCP status slug in module; docs cle…
matifali Aug 8, 2025
6f46bc9
chore(cursor-cli): move env to coder_env; update scripts
matifali Aug 8, 2025
ce902f6
wip
matifali Aug 8, 2025
df31818
docs(cursor-cli): enhance README with examples for MCP configuration …
matifali Aug 8, 2025
d8b9b2a
feat(cursor-cli): add pre-install and post-install script support
matifali Aug 8, 2025
079ff1f
feat(cursor-cli): add ai_prompt variable
matifali Aug 8, 2025
496efad
feat(cursor-cli): add output for app ID in main.tf
matifali Aug 8, 2025
538d08b
feat(cursor-cli): update script execution to include folder variable …
matifali Aug 8, 2025
0a87206
refactor(cursor-cli): comment out unused arguments in start.sh script
matifali Aug 8, 2025
8b6b659
refactor(cursor-cli): restore previously commented arguments in start…
matifali Aug 8, 2025
4edcb00
refactor(cursor-cli): remove output_format variable and related scrip…
matifali Aug 8, 2025
414b5f2
revert(claude-code): restore main.test.ts to origin/main state
matifali Aug 9, 2025
f525189
refactor(cursor-cli): remove non-interactive flags from main.test.ts
matifali Aug 9, 2025
fcd9f3a
refactor
matifali Aug 9, 2025
058cd8e
fixup!
matifali Aug 9, 2025
8d3e5d6
fixup!
matifali Aug 9, 2025
be5c394
wip
matifali Aug 11, 2025
7391082
Update registry/coder-labs/modules/cursor-cli/README.md
matifali Aug 12, 2025
ee1bd53
Merge branch 'main' into feat/cursor-cli-from-main
DevelopmentCats Aug 12, 2025
62a0a9c
feat: add support for agentapi
35C4n0r Aug 13, 2025
2107f93
feat: add tests
35C4n0r Aug 13, 2025
1a38b7e
feat: update readme
35C4n0r Aug 13, 2025
d9e49f5
feat: update readme
35C4n0r Aug 13, 2025
a8328f4
feat: update app slug
35C4n0r Aug 13, 2025
3fdae36
feat: remove app and cli
35C4n0r Aug 14, 2025
5da3721
feat: update readme
35C4n0r Aug 14, 2025
3a4fc36
feat: fix typo
35C4n0r Aug 14, 2025
d1f518a
feat: update mcp_json to mcp
35C4n0r Aug 14, 2025
2322298
feat: update tests
35C4n0r Aug 14, 2025
805b385
Merge branch 'main' into feat/cursor-cli-from-main
DevelopmentCats Aug 14, 2025
10a96aa
Update registry/coder-labs/modules/cursor-cli/scripts/start.sh
35C4n0r Aug 14, 2025
f480d74
Update registry/coder-labs/modules/cursor-cli/scripts/start.sh
35C4n0r Aug 14, 2025
528b823
Update registry/coder-labs/modules/cursor-cli/testdata/cursor-cli-moc…
35C4n0r Aug 14, 2025
c336e44
chore: update mock
35C4n0r Aug 14, 2025
b99023b
chore: remove comments
35C4n0r Aug 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions registry/coder-labs/modules/cursor-cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
---
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
cursor_cli_version = "latest"
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/<name>`.
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
> Cursor CLI doesn'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 `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/`
116 changes: 116 additions & 0 deletions registry/coder-labs/modules/cursor-cli/cursor-cli.tftest.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Terraform tests for the cursor-cli module
// Validates that we render expected script content given inputs

run "defaults" {
command = plan

variables {
agent_id = "test-agent"
folder = "/home/coder"
}

assert {
condition = can(regex("Cursor CLI", resource.coder_script.cursor_cli.display_name))
error_message = "Expected coder_script to be created"
}
}

run "non_interactive_mode" {
command = plan

variables {
agent_id = "test-agent"
folder = "/home/coder"
output_format = "json"
ai_prompt = "refactor the auth module to use JWT tokens"
}

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"
}

assert {
Copy link
Preview

Copilot AI Aug 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test references a non-existent resource and variable. The module doesn't have an 'output_format' variable defined in main.tf.

Suggested change
assert {
ai_prompt = "refactor the auth module to use JWT tokens"
}
assert {

Copilot uses AI. Check for mistakes.

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"
Copy link
Preview

Copilot AI Aug 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test references a non-existent resource 'coder_script.cursor_cli' and incorrect variable name format. The actual variable is 'ai_prompt' not 'AI_PROMPT'.

Suggested change
error_message = "Expected ai_prompt to be propagated via AI_PROMPT"
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"

Copilot uses AI. Check for mistakes.

}
}

run "model_and_force" {
command = plan

variables {
agent_id = "test-agent"
folder = "/home/coder"
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"
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"
}
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"
}

// 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"
}

// 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" {
command = plan

variables {
agent_id = "test-agent"
folder = "/home/coder"
api_key = "sk-test-123"
}

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"
}

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"
}
}
Loading