Skip to content

feat(integrations): support Cursor Agent CLI as non-interactive LLM backend#1149

Merged
muddlebee merged 16 commits intoTracer-Cloud:mainfrom
cerencamkiran:feat/cursor-cli-1109
May 5, 2026
Merged

feat(integrations): support Cursor Agent CLI as non-interactive LLM backend#1149
muddlebee merged 16 commits intoTracer-Cloud:mainfrom
cerencamkiran:feat/cursor-cli-1109

Conversation

@cerencamkiran
Copy link
Copy Markdown
Collaborator

@cerencamkiran cerencamkiran commented Apr 30, 2026

Fixes #1109

Summary

Adds support for Cursor Agent CLI as a subprocess-backed LLM provider using the existing LLMCLIAdapter interface.

This integration enables OpenSRE to use Cursor as a non-interactive, one-shot CLI backend, fully compatible with the Codex-style execution model.


Key Features

  • Non-interactive execution via agent --print
  • Supports stdin and argv prompt input
  • Requires explicit --trust for headless execution
  • Supports optional model override via CURSOR_MODEL
  • Authentication is handled exclusively via CLI (agent login / agent status)
  • No environment-based authentication is used
  • Compatible with subprocess.run (no TTY, no blocking)

Spike Findings (Cursor CLI behavior)

  • Binary name is agent (not cursor-agent)
  • Headless execution is supported via --print
  • Workspace trust must be explicitly enabled (--trust)
  • Free-tier accounts require --model auto
  • CLI exits cleanly with stdout → compatible with LLMCLIAdapter contract

Implementation

  • Added CursorAdapter in app/integrations/llm_cli/cursor.py

  • Implemented:

    • detect() (CLI-based status probing only)
    • build() (non-interactive invocation)
    • parse() (stdout extraction)
    • explain_failure() (error classification)
  • Registered provider in:

    • app/integrations/llm_cli/registry.py
    • app/config.py
  • Extended subprocess env forwarding:

    • _SAFE_SUBPROCESS_ENV_PREFIXES += ("CURSOR_",)
  • Added onboarding support in:

    • app/cli/wizard/config.py

detect() behavior

Returns:

  • True → authenticated
  • False → not authenticated
  • None → auth state unclear

Never raises exceptions and follows the CLIProbe contract.


Tests

Added full test coverage:

  • detect (CLI status only)
  • build
  • parse
  • explain_failure
  • registry integration
  • subprocess env forwarding

Results:

python -m pytest tests/integrations/llm_cli/
→ 29 passed

Static checks:

ruff check → passed
mypy → passed

Quick Start

agent login
opensre onboard
# Select: Cursor Agent CLI

opensre investigate

End-to-End Validation

Successfully verified full pipeline:

  • opensre onboard → Cursor Agent CLI selected
  • opensre investigate → completed successfully

Sample output:

Most likely: a database connectivity failure affecting `payments_etl`

Confirms Cursor CLI works end-to-end in OpenSRE investigation loop.


Demo (Headless Execution)

agent --print --trust --output-format text --model auto "Say hello"
# → Hello!

Cross-OS Notes

Tested on:

  • Windows (PowerShell)
  • WSL Ubuntu (primary runtime environment)

Notes:

  • Cursor CLI installed in WSL must be used from WSL for detection
  • Windows native environment may not detect agent binary
  • macOS not tested locally, but expected to work via official installer

Acceptance Criteria Coverage

Non-interactive CLI contract

  • Uses a single one-shot invocation (agent --print)
  • No REPL, no TTY, no blocking input
  • Fully compatible with subprocess.run

detect() behavior (3-state)

  • True → authenticated via CLI (agent status)
  • False → not authenticated
  • None → unclear / fallback
  • Never raises, follows CLIProbe contract

CLI-only authentication

  • No environment-based authentication
  • Authentication determined solely via CLI (agent status)

build() contract

  • Produces non-interactive invocation
  • Includes required flags: --print, --trust, --output-format text
  • Optional model override via CURSOR_MODEL

Subprocess env forwarding

  • Only forwards CURSOR_* variables
  • Verified via test coverage

Registry integration

  • Registered via CLI_PROVIDER_REGISTRY
  • Selectable via LLM_PROVIDER=cursor

End-to-end validation

  • opensre onboard
  • opensre investigate
  • Full test suite passing (29 tests)

Conclusion

Cursor CLI satisfies all requirements for a non-interactive LLM backend and integrates cleanly into the OpenSRE pipeline.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 30, 2026

Greptile Summary

This PR adds CursorAdapter as a new non-interactive CLI backend, following the same LLMCLIAdapter contract as the existing Codex adapter. The implementation is clean and well-tested, with correct wiring across config, registry, env forwarding, and the onboarding wizard.

Confidence Score: 5/5

Safe to merge; all remaining findings are P2 style/UX issues that do not affect correctness.

The integration follows the established adapter pattern, env forwarding is minimal and correct, and test coverage is thorough. The two P2 findings (misleading error message when stdout is empty on exit-0, and lack of Cursor-specific validation in the agent binary probe) are quality improvements but do not block functionality.

app/integrations/llm_cli/cursor.py — parse() error message and agent binary identity validation.

Important Files Changed

Filename Overview
app/integrations/llm_cli/cursor.py New CursorAdapter implementing the LLMCLIAdapter protocol; two minor issues: parse() delegates to explain_failure() with returncode=0 producing a confusing message, and the generic agent binary name may match unrelated system binaries without Cursor-specific validation.
app/integrations/llm_cli/runner.py Added "CURSOR_" to _SAFE_SUBPROCESS_ENV_PREFIXES so that CURSOR_API_KEY, CURSOR_MODEL, and CURSOR_BIN are forwarded to the subprocess; clean, minimal, correct change.
app/integrations/llm_cli/registry.py Registers cursor provider with _cursor_factory and CURSOR_MODEL env key; mirrors the codex pattern exactly.
app/config.py Adds "cursor" to LLMProvider literal, _normalize_provider validator, and the no-API-key exemption list; stale inline comment omits cursor from its description.
app/cli/wizard/config.py Adds CURSOR_MODELS tuple and ProviderOption for Cursor in SUPPORTED_PROVIDERS; follows the same pattern as codex.
tests/integrations/llm_cli/test_cursor_adapter.py Comprehensive test coverage for detect(), build(), parse(), explain_failure(), registry integration, and subprocess env forwarding; all paths exercised including edge cases.

Sequence Diagram

sequenceDiagram
    participant CLI as opensre CLI
    participant Runner as CLIBackedLLMClient
    participant Adapter as CursorAdapter
    participant Proc as agent subprocess

    CLI->>Runner: invoke(prompt)
    Runner->>Adapter: detect()
    Adapter->>Proc: agent --version
    Proc-->>Adapter: version stdout
    Adapter->>Proc: agent status (if no CURSOR_API_KEY)
    Proc-->>Adapter: Logged in as ...
    Adapter-->>Runner: CLIProbe(installed=True, logged_in=True)
    Runner->>Adapter: build(prompt, model, workspace)
    Adapter-->>Runner: CLIInvocation(argv=[agent,--print,--trust,...], stdin=prompt)
    Runner->>Proc: subprocess.run(argv, env=filtered_env)
    Proc-->>Runner: stdout, returncode=0
    Runner->>Adapter: parse(stdout, stderr, returncode)
    Adapter-->>Runner: stripped answer
    Runner-->>CLI: LLMResponse(content=answer)
Loading

Reviews (1): Last reviewed commit: "feat: add Cursor CLI adapter (non-intera..." | Re-trigger Greptile

Comment on lines +164 to +171
def parse(self, *, stdout: str, stderr: str, returncode: int) -> str:
result = (stdout or "").strip()
if not result:
raise RuntimeError(
self.explain_failure(stdout=stdout, stderr=stderr, returncode=returncode)
+ " (empty output)"
)
return result
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Misleading error message when stdout is empty on success

parse() delegates to explain_failure() even when returncode=0, so an empty-output success case produces the message "cursor agent exited with code 0. (empty output)". The phrase "exited with code 0" signals success to the reader while the surrounding context is an error, which is confusing. Consider a dedicated message when the exit code is 0 to avoid the contradiction.

Comment on lines +37 to +42
def _resolve_binary(self) -> str | None:
return resolve_cli_binary(
explicit_env_key="CURSOR_BIN",
binary_names=_candidate_binary_names("agent"),
fallback_paths=_default_cli_fallback_paths("agent"),
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Generic binary name agent may resolve to an unrelated binary

_candidate_binary_names("agent") will match any executable called agent on the PATH (e.g. chef-agent, puppet-agent, or other common devops tools installed as agent). The detect() method only confirms the binary exits 0 on --version — it doesn't verify the output contains any Cursor-specific string. A false positive here sends the wrong binary to build() and fails at invocation time with a confusing error. Consider asserting that "cursor" appears in the --version output before treating the binary as the Cursor Agent CLI.

Comment thread app/config.py Outdated
Comment on lines 186 to 187
if self.provider in ("ollama", "bedrock", "codex", "cursor"):
return self # ollama: local; bedrock: IAM; codex: `codex login` (CLI)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Inline comment doesn't mention cursor

The comment still only describes ollama, bedrock, and codex even though cursor was added to the same exemption. This will mislead future readers.

Suggested change
if self.provider in ("ollama", "bedrock", "codex", "cursor"):
return self # ollama: local; bedrock: IAM; codex: `codex login` (CLI)
if self.provider in ("ollama", "bedrock", "codex", "cursor"):
return self # ollama: local; bedrock: IAM; codex/cursor: CLI login

@cerencamkiran
Copy link
Copy Markdown
Collaborator Author

Ekran görüntüsü 2026-04-30 181204 Ekran görüntüsü 2026-04-30 181220 Ekran görüntüsü 2026-04-30 181241 Ekran görüntüsü 2026-04-30 181611 Ekran görüntüsü 2026-04-30 181620

@cerencamkiran
Copy link
Copy Markdown
Collaborator Author

All P2 feedback addressed:

  • improved parse() handling for empty stdout on success
  • replaced string-based binary validation with version parsing (more robust)
  • updated config comment for clarity

All tests passing, no behavior changes.

Comment thread app/integrations/llm_cli/cursor.py Outdated
detail="Binary found but does not appear to be Cursor Agent CLI.",
)

if os.environ.get("CURSOR_API_KEY"):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

why this? the auth, models and everything should be handled from CLI side only and not something which should be configured here..

check in codex how its done https://github.com/Tracer-Cloud/opensre/blob/main/app/integrations/llm_cli/AGENTS.md#codex-env-quick-reference-instance-of-the-convention-above

and refer the above AGENTS.md the specs should strictly follow it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I've removed env-based auth handling and aligned the adapter with the CLI-only contract.
Updated tests accordingly to ensure authentication is determined solely via the CLI (agent status).

@muddlebee
Copy link
Copy Markdown
Collaborator

some points from acceptance criteria still missing.

@cerencamkiran
Copy link
Copy Markdown
Collaborator Author

cerencamkiran commented May 1, 2026

I've updated the PR description with a detailed acceptance criteria mapping and aligned the implementation with the CLI-only contract.

The remaining Windows CI failures look unrelated to the Cursor adapter. Both failures are in interactive_shell prompt session tests and fail with prompt_toolkit.output.win32.NoConsoleScreenBufferError.

The Cursor-specific tests are passing locally, and the full suite mostly passes except these Windows console-buffer failures.

Let me know if anything else should be clarified @muddlebee 🙏

@muddlebee muddlebee added the llm-cli Subprocess LLM CLI integration (LLMCLIAdapter / registry) label May 3, 2026
@Devesh36
Copy link
Copy Markdown
Collaborator

Devesh36 commented May 4, 2026

Hey @cerencamkiran resolve the conflicts over here

@muddlebee
Copy link
Copy Markdown
Collaborator

image this is the conflict I guess

Comment thread app/integrations/llm_cli/cursor.py Outdated
)

status_text = (status_proc.stdout + status_proc.stderr).strip()
if status_proc.returncode == 0 and "Logged in as" in status_text:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Not-logged-in detail still mentions CURSOR_API_KEY while the adapter documents CLI-only auth elsewhere. Drop the env-var hint here for consistency.

explicit_env_key="CURSOR_BIN",
binary_names=_candidate_binary_names("agent"),
fallback_paths=_default_cli_fallback_paths("agent"),
)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Version-regex gating reduces wrong-binary risk but any agent that prints a semver can still pass. Consider requiring a Cursor-specific substring in --version output (case-insensitive "cursor") before treating the binary as installed.

Comment thread app/config.py
"bedrock",
"minimax",
"codex",
"cursor",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Unrelated to this line: the branch removes the repository root .gitignore entirely in the latest commit. Restore .gitignore before merge.

Comment thread app/cli/wizard/config.py
adapter_factory=_codex_adapter_factory,
),
ProviderOption(
value="cursor",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Wizard wires the provider; issue #1109 still expects .env.example entries for CURSOR_BIN and CURSOR_MODEL (mirroring CODEX). Add those commented lines in .env.example.

logged_in = None
detail = status_text or "Could not determine Cursor Agent auth status."

return CLIProbe(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Classify subscription auth from agent status first (current block). If logged_in is False or None and CURSOR_API_KEY is non-empty, return logged_in True with detail that notes API-key headless auth; only surface agent login when neither path applies. Extend tests for not-logged-in status plus key, unclear status plus key, and logged-in status ignoring the key.

@muddlebee
Copy link
Copy Markdown
Collaborator

image

@cerencamkiran this is P1 issue

@cerencamkiran
Copy link
Copy Markdown
Collaborator Author

Thanks for the feedback @muddlebee I’ve addressed the comments and aligned the auth handling with the current tests and CLI contract.

All tests are passing locally and in CI, and I’ve resolved the remaining conflicts.

@muddlebee
Copy link
Copy Markdown
Collaborator

muddlebee commented May 4, 2026

PR tip had an empty .gitignore; restore Tracer-Cloud main contents.
@muddlebee
Copy link
Copy Markdown
Collaborator

Looks good overall I will just do some additional set of reviews :) thank you for your patience and efforts 🪄

@cerencamkiran
Copy link
Copy Markdown
Collaborator Author

@cerencamkiran why remove? .gitignore ? https://github.com/Tracer-Cloud/opensre/blob/main/.gitignore

-- I fixed that for you in the PR :) 0278ff3

That was accidental while resolving a merge conflict. Thanks a lot for fixing it. 🤗

@cerencamkiran
Copy link
Copy Markdown
Collaborator Author

Looks good overall I will just do some additional set of reviews :) thank you for your patience and efforts 🪄

Happy to address any further feedback @muddlebee thanks again for the review 🙂

@muddlebee muddlebee merged commit 69c6604 into Tracer-Cloud:main May 5, 2026
10 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

🎻 "The diff was clean, the tests did pass, the reviewer wept." That poem was about @cerencamkiran's PR. 🥹


👋 Join us on Discord - OpenSRE : hang out, contribute, or hunt for features and issues. Everyone's welcome.

@muddlebee
Copy link
Copy Markdown
Collaborator

@cerencamkiran thank you for the PR. I added some improvements in later PRs. :) feel free to check

yashksaini-coder pushed a commit to gitsofaryan/opensre that referenced this pull request May 5, 2026
Main moved while the previous merge was in flight: PR Tracer-Cloud#1149 added Cursor
Agent CLI support touching the same prefix tuple, env-var docs, and
provider-allowlist as this branch.

Resolutions (all "combine both" merges):
- subprocess_env.py: prefix tuple now includes both CURSOR_ and KIMI_
- app/config.py: provider early-return now includes both cursor and kimi
- .env.example: keep both Cursor and Kimi env-var blocks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

llm-cli Subprocess LLM CLI integration (LLMCLIAdapter / registry)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[CLI] Integrate Cursor CLI (non-interactive)

3 participants