fix(cursor-agent): enable CLI dispatch via `-p --trust` headless mode

Restores the ability for ``specify workflow run`` to dispatch the
cursor-agent CLI, complementing the existing in-IDE skill flow.
Without this fix, ``specify workflow run speckit --input
integration=cursor-agent ...`` fails with a misleading
``CLI not found or not installed`` error even when the CLI is
installed (since cursor-agent had ``requires_cli=False`` and an
unset ``build_exec_args``).

The cursor-agent CLI (>= 2026.05.16) supports headless execution
via ``-p`` (print mode with full tool access including write/shell)
and ``--trust`` (bypass Workspace Trust prompt). Without ``--trust``
the CLI exits non-zero in non-TTY contexts (verified locally).

Changes to ``src/specify_cli/integrations/cursor_agent/__init__.py``:

* ``config.requires_cli``: ``False`` -> ``True``
* ``config.install_url``: ``None`` -> Cursor CLI docs URL
* Override ``build_exec_args()`` to emit
  ``[cursor-agent, -p, --trust, <prompt>, ...]``
  with optional ``--model`` and ``--output-format json`` flags,
  mirroring the shape used by ``claude``/``codex``/``gemini``.

Tests:

* 34 existing cursor-agent tests still pass.
* 6 new tests in ``TestCursorAgentCliDispatch`` pin
  ``requires_cli``, ``install_url``, and the exact argv shape
  (default, text-output, with-model, and the hyphenated skill
  invocation form ``/speckit-<name>``).
* Full repo: 1085 / 1085 passed, no regressions.

Fixes #2629

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
刘一
2026-05-19 14:40:18 +08:00
parent 51e6a140e2
commit 1c55988637
2 changed files with 81 additions and 2 deletions

View File

@@ -2,6 +2,10 @@
Cursor Agent uses the ``.cursor/skills/speckit-<name>/SKILL.md`` layout.
Commands are deprecated; ``--skills`` defaults to ``True``.
CLI dispatch via ``cursor-agent -p --trust <prompt>`` is supported so
``specify workflow run`` can drive cursor-agent headlessly, in addition
to the existing in-IDE skill flow.
"""
from __future__ import annotations
@@ -15,8 +19,8 @@ class CursorAgentIntegration(SkillsIntegration):
"name": "Cursor",
"folder": ".cursor/",
"commands_subdir": "skills",
"install_url": None,
"requires_cli": False,
"install_url": "https://docs.cursor.com/en/cli/overview",
"requires_cli": True,
}
registrar_config = {
"dir": ".cursor/skills",
@@ -28,6 +32,29 @@ class CursorAgentIntegration(SkillsIntegration):
context_file = ".cursor/rules/specify-rules.mdc"
multi_install_safe = True
def build_exec_args(
self,
prompt: str,
*,
model: str | None = None,
output_json: bool = True,
) -> list[str] | None:
"""Build CLI arguments for non-interactive ``cursor-agent`` execution.
Uses ``-p`` (print/headless mode, which gives access to all
tools including write and shell) plus ``--trust`` (bypass the
Workspace Trust prompt — mandatory for headless execution; the
CLI exits non-zero without it).
"""
if not self.config or not self.config.get("requires_cli"):
return None
args = [self.key, "-p", "--trust", prompt]
if model:
args.extend(["--model", model])
if output_json:
args.extend(["--output-format", "json"])
return args
@classmethod
def options(cls) -> list[IntegrationOption]:
return [

View File

@@ -106,3 +106,55 @@ class TestCursorAgentAutoPromote:
assert result.exit_code == 0, f"init --ai cursor-agent failed: {result.output}"
assert (target / ".cursor" / "skills" / "speckit-plan" / "SKILL.md").exists()
class TestCursorAgentCliDispatch:
"""Verify the CLI dispatch path for cursor-agent (issue #2629).
The ``cursor-agent`` CLI supports headless execution via ``-p`` (with
full tool access including write/shell) and requires ``--trust`` to
bypass the Workspace Trust prompt. These tests pin the exact argv
shape that the workflow runner will use.
"""
def test_requires_cli_is_true(self):
i = get_integration("cursor-agent")
assert i.config.get("requires_cli") is True
def test_install_url_is_set(self):
i = get_integration("cursor-agent")
url = i.config.get("install_url")
assert url is not None
assert "cursor.com" in url
def test_build_exec_args_default_includes_trust_and_json(self):
i = get_integration("cursor-agent")
args = i.build_exec_args("/speckit-specify some-feature")
assert args == [
"cursor-agent", "-p", "--trust",
"/speckit-specify some-feature",
"--output-format", "json",
]
def test_build_exec_args_text_output_omits_format(self):
i = get_integration("cursor-agent")
args = i.build_exec_args("/speckit-plan", output_json=False)
assert args == [
"cursor-agent", "-p", "--trust", "/speckit-plan",
]
def test_build_exec_args_with_model(self):
i = get_integration("cursor-agent")
args = i.build_exec_args(
"/speckit-specify", model="sonnet-4-thinking", output_json=False
)
assert args == [
"cursor-agent", "-p", "--trust", "/speckit-specify",
"--model", "sonnet-4-thinking",
]
def test_build_command_invocation_uses_hyphenated_skill_name(self):
"""SkillsIntegration: /speckit-plan (not /speckit.plan)."""
i = get_integration("cursor-agent")
assert i.build_command_invocation("speckit.plan", "feature-x") == "/speckit-plan feature-x"
assert i.build_command_invocation("plan") == "/speckit-plan"