Skip to content

Commit 32246a9

Browse files
matifaliDevelopmentCats35C4n0rCopilot
authored
feat(cursor-cli): add Cursor CLI module (#309)
Closes #305 ## Summary - Add new module `registry/coder-labs/modules/cursor-cli` to run Cursor Agent CLI directly (no AgentAPI) - Interactive chat by default; supports non-interactive mode (-p) with output-format - Supports model (-m) and force (-f) flags, initial prompt, and CURSOR_API_KEY - Merges MCP settings into ~/.cursor/settings.json - Installs via npm, bootstrapping Node via NVM if missing (mirrors gemini approach) - Adds Terraform-native tests (.tftest.hcl); all pass locally ## Test plan - From module dir: - terraform init -upgrade - terraform test -verbose - Expect 4 tests passing covering defaults, flag plumbing, and MCP settings injection - Basic smoke run: ensure `cursor-agent` is on PATH or set install_cursor_cli=true --------- Co-authored-by: DevCats <[email protected]> Co-authored-by: 35C4n0r <[email protected]> Co-authored-by: 35C4n0r <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent bb667d2 commit 32246a9

File tree

7 files changed

+869
-0
lines changed

7 files changed

+869
-0
lines changed
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
---
2+
display_name: Cursor CLI
3+
icon: ../../../../.icons/cursor.svg
4+
description: Run Cursor CLI agent in your workspace (no AgentAPI)
5+
verified: true
6+
tags: [agent, cursor, ai, cli]
7+
---
8+
9+
# Cursor CLI
10+
11+
Run the Cursor Coding Agent in your workspace using the Cursor CLI directly.
12+
13+
A full example with MCP, rules, and pre/post install scripts:
14+
15+
```tf
16+
17+
data "coder_parameter" "ai_prompt" {
18+
type = "string"
19+
name = "AI Prompt"
20+
default = ""
21+
description = "Build a Minesweeper in Python."
22+
mutable = true
23+
}
24+
25+
module "coder-login" {
26+
count = data.coder_workspace.me.start_count
27+
source = "registry.coder.com/coder/coder-login/coder"
28+
version = "1.0.31"
29+
agent_id = coder_agent.main.id
30+
}
31+
32+
module "cursor_cli" {
33+
source = "registry.coder.com/coder-labs/cursor-cli/coder"
34+
version = "0.1.0"
35+
agent_id = coder_agent.example.id
36+
folder = "/home/coder/project"
37+
38+
# Optional
39+
install_cursor_cli = true
40+
force = true
41+
model = "gpt-5"
42+
ai_prompt = data.coder_parameter.ai_prompt.value
43+
44+
# Minimal MCP server (writes `folder/.cursor/mcp.json`):
45+
mcp = jsonencode({
46+
mcpServers = {
47+
playwright = {
48+
command = "npx"
49+
args = ["-y", "@playwright/mcp@latest", "--headless", "--isolated", "--no-sandbox"]
50+
}
51+
desktop-commander = {
52+
command = "npx"
53+
args = ["-y", "@wonderwhy-er/desktop-commander"]
54+
}
55+
}
56+
})
57+
58+
# Use a pre_install_script to install the CLI
59+
pre_install_script = <<-EOT
60+
#!/usr/bin/env bash
61+
set -euo pipefail
62+
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
63+
apt-get install -y nodejs
64+
EOT
65+
66+
# Use post_install_script to wait for the repo to be ready
67+
post_install_script = <<-EOT
68+
#!/usr/bin/env bash
69+
set -euo pipefail
70+
TARGET="$${FOLDER}/.git/config"
71+
echo "[cursor-cli] waiting for $${TARGET}..."
72+
for i in $(seq 1 600); do
73+
[ -f "$TARGET" ] && { echo "ready"; exit 0; }
74+
sleep 1
75+
done
76+
echo "timeout waiting for $${TARGET}" >&2
77+
EOT
78+
79+
# Provide a map of file name to content; files are written to `folder/.cursor/rules/<name>`.
80+
rules_files = {
81+
"python.mdc" = <<-EOT
82+
---
83+
description: RPC Service boilerplate
84+
globs:
85+
alwaysApply: false
86+
---
87+
88+
- Use our internal RPC pattern when defining services
89+
- Always use snake_case for service names.
90+
91+
@service-template.ts
92+
EOT
93+
94+
"frontend.mdc" = <<-EOT
95+
---
96+
description: RPC Service boilerplate
97+
globs:
98+
alwaysApply: false
99+
---
100+
101+
- Use our internal RPC pattern when defining services
102+
- Always use snake_case for service names.
103+
104+
@service-template.ts
105+
EOT
106+
}
107+
}
108+
```
109+
110+
> [!NOTE]
111+
> A `.cursor` directory will be created in the specified `folder`, containing the MCP configuration, rules.
112+
> 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)
113+
114+
## References
115+
116+
- See Cursor CLI docs: `https://docs.cursor.com/en/cli/overview`
117+
- 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`.
118+
- 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/`.
119+
120+
## Troubleshooting
121+
122+
- Ensure the CLI is installed (enable `install_cursor_cli = true` or preinstall it in your image)
123+
- Logs are written to `~/.cursor-cli-module/`
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
run "test_cursor_cli_basic" {
2+
command = plan
3+
4+
variables {
5+
agent_id = "test-agent-123"
6+
folder = "/home/coder/projects"
7+
}
8+
9+
assert {
10+
condition = coder_env.status_slug.name == "CODER_MCP_APP_STATUS_SLUG"
11+
error_message = "Status slug environment variable should be set correctly"
12+
}
13+
14+
assert {
15+
condition = coder_env.status_slug.value == "cursorcli"
16+
error_message = "Status slug value should be 'cursorcli'"
17+
}
18+
19+
assert {
20+
condition = var.folder == "/home/coder/projects"
21+
error_message = "Folder variable should be set correctly"
22+
}
23+
24+
assert {
25+
condition = var.agent_id == "test-agent-123"
26+
error_message = "Agent ID variable should be set correctly"
27+
}
28+
}
29+
30+
run "test_cursor_cli_with_api_key" {
31+
command = plan
32+
33+
variables {
34+
agent_id = "test-agent-456"
35+
folder = "/home/coder/workspace"
36+
api_key = "test-api-key-123"
37+
}
38+
39+
assert {
40+
condition = coder_env.cursor_api_key[0].name == "CURSOR_API_KEY"
41+
error_message = "Cursor API key environment variable should be set correctly"
42+
}
43+
44+
assert {
45+
condition = coder_env.cursor_api_key[0].value == "test-api-key-123"
46+
error_message = "Cursor API key value should match the input"
47+
}
48+
}
49+
50+
run "test_cursor_cli_with_custom_options" {
51+
command = plan
52+
53+
variables {
54+
agent_id = "test-agent-789"
55+
folder = "/home/coder/custom"
56+
order = 5
57+
group = "development"
58+
icon = "/icon/custom.svg"
59+
model = "sonnet-4"
60+
ai_prompt = "Help me write better code"
61+
force = false
62+
install_cursor_cli = false
63+
install_agentapi = false
64+
}
65+
66+
assert {
67+
condition = var.order == 5
68+
error_message = "Order variable should be set to 5"
69+
}
70+
71+
assert {
72+
condition = var.group == "development"
73+
error_message = "Group variable should be set to 'development'"
74+
}
75+
76+
assert {
77+
condition = var.icon == "/icon/custom.svg"
78+
error_message = "Icon variable should be set to custom icon"
79+
}
80+
81+
assert {
82+
condition = var.model == "sonnet-4"
83+
error_message = "Model variable should be set to 'sonnet-4'"
84+
}
85+
86+
assert {
87+
condition = var.ai_prompt == "Help me write better code"
88+
error_message = "AI prompt variable should be set correctly"
89+
}
90+
91+
assert {
92+
condition = var.force == false
93+
error_message = "Force variable should be set to false"
94+
}
95+
}
96+
97+
run "test_cursor_cli_with_mcp_and_rules" {
98+
command = plan
99+
100+
variables {
101+
agent_id = "test-agent-mcp"
102+
folder = "/home/coder/mcp-test"
103+
mcp = jsonencode({
104+
mcpServers = {
105+
test = {
106+
command = "test-server"
107+
args = ["--config", "test.json"]
108+
}
109+
}
110+
})
111+
rules_files = {
112+
"general.md" = "# General coding rules\n- Write clean code\n- Add comments"
113+
"security.md" = "# Security rules\n- Never commit secrets\n- Validate inputs"
114+
}
115+
}
116+
117+
assert {
118+
condition = var.mcp != null
119+
error_message = "MCP configuration should be provided"
120+
}
121+
122+
assert {
123+
condition = var.rules_files != null
124+
error_message = "Rules files should be provided"
125+
}
126+
127+
assert {
128+
condition = length(var.rules_files) == 2
129+
error_message = "Should have 2 rules files"
130+
}
131+
}
132+
133+
run "test_cursor_cli_with_scripts" {
134+
command = plan
135+
136+
variables {
137+
agent_id = "test-agent-scripts"
138+
folder = "/home/coder/scripts"
139+
pre_install_script = "echo 'Pre-install script'"
140+
post_install_script = "echo 'Post-install script'"
141+
}
142+
143+
assert {
144+
condition = var.pre_install_script == "echo 'Pre-install script'"
145+
error_message = "Pre-install script should be set correctly"
146+
}
147+
148+
assert {
149+
condition = var.post_install_script == "echo 'Post-install script'"
150+
error_message = "Post-install script should be set correctly"
151+
}
152+
}

0 commit comments

Comments
 (0)