-
Notifications
You must be signed in to change notification settings - Fork 46
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
base: main
Are you sure you want to change the base?
Changes from all commits
a8d9b71
7483c77
a3aa1cf
a075139
2a65de4
5e3f555
6f46bc9
ce902f6
df31818
d8b9b2a
079ff1f
496efad
538d08b
0a87206
8b6b659
4edcb00
414b5f2
f525189
fcd9f3a
058cd8e
8d3e5d6
be5c394
7391082
ee1bd53
62a0a9c
2107f93
1a38b7e
d9e49f5
a8328f4
3fdae36
5da3721
3a4fc36
d1f518a
2322298
805b385
10a96aa
f480d74
528b823
c336e44
b99023b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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/` |
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 { | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||||||||
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" | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
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" | ||||||||||||
} | ||||||||||||
} |
Uh oh!
There was an error while loading. Please reload this page.