Files
Elydora-Python-SDK/elydora/plugins/cursor.py
T
雷电芽衣 6dd0197cab chore(python): audit providers claudecode codex cursor gemini
Add _runtime (paths, config validation, status checks) and _shell_command
(quoting, parsers) and move the four providers onto them.

Claude-Session: https://claude.ai/code/session_01Agoc9bRVE9KL7mmWtey9Vq
2026-09-02 23:36:33 -04:00

128 lines
4.7 KiB
Python

"""Cursor native user-hook integration."""
from __future__ import annotations
import urllib.parse
from ._managed_files import physical_directory_exists
from ._runtime import (
DEFAULT_BASE_URL,
present,
resolve_runtime_paths,
runtime_file_changes,
validate_private_key,
validate_runtime_tree,
)
from ._transaction import write_changes
from .base import AgentPlugin, InstallConfig, PluginStatus
from .cursor_contract import (
AGENT_KEY,
build_handler,
remove_managed_hooks,
render_document,
runtime_contracts,
)
from .cursor_io import (
read_document,
rendered_change,
runtime_files_exist,
validate_config_directory,
)
from .guard_template import generate_guard_script
from .hook_template import generate_hook_script
PRODUCT = "Cursor"
def _validate_install_config(config: InstallConfig) -> None:
for field in ("org_id", "agent_id", "kid", "private_key"):
value = config.get(field)
if not isinstance(value, str) or not value:
raise ValueError(f"{field} is required")
if config.get("agent_name") != AGENT_KEY:
raise ValueError(f"Cursor installation requires agent_name {AGENT_KEY}")
validate_private_key(config["private_key"])
base_url = config.get("base_url", DEFAULT_BASE_URL)
if not isinstance(base_url, str):
raise ValueError("base_url must be a string")
parsed = urllib.parse.urlsplit(base_url)
if parsed.scheme not in ("http", "https") or not parsed.netloc:
raise ValueError("base_url must be an absolute HTTP or HTTPS URL")
if not isinstance(config.get("token", ""), str):
raise ValueError("token must be a string")
class CursorPlugin(AgentPlugin):
"""Install Elydora into Cursor's native global user hooks."""
manages_guard_runtime = True
def preflight_install(self, config: InstallConfig) -> None:
_validate_install_config(config)
read_document()
validate_config_directory()
paths = resolve_runtime_paths(config)
validate_runtime_tree(paths.agent_directory, paths.agent_id, AGENT_KEY, PRODUCT)
def install(self, config: InstallConfig) -> None:
self.preflight_install(config)
document = read_document()
paths = resolve_runtime_paths(config)
if not physical_directory_exists(
paths.agent_directory, "Elydora agent runtime directory"
):
raise FileNotFoundError(
f"Elydora agent runtime directory is missing: {paths.agent_directory}"
)
validate_runtime_tree(paths.agent_directory, paths.agent_id, AGENT_KEY, PRODUCT)
hooks = remove_managed_hooks(document.hooks)
hooks["preToolUse"] = [*hooks.get("preToolUse", []), build_handler(paths.guard_path)]
hooks["postToolUse"] = [*hooks.get("postToolUse", []), build_handler(paths.audit_path)]
hooks["postToolUseFailure"] = [
*hooks.get("postToolUseFailure", []),
build_handler(paths.audit_path),
]
audit_script = generate_hook_script(
org_id=config.get("org_id", ""),
agent_id=paths.agent_id,
kid=config.get("kid", ""),
base_url=config.get("base_url", DEFAULT_BASE_URL),
success_output="{}\n",
fail_closed=True,
native_payload=True,
agent_name=AGENT_KEY,
)
guard_script = generate_guard_script(
AGENT_KEY,
paths.agent_id,
success_output='{"permission":"allow"}\n',
fail_closed=True,
deny_protocol="cursor",
)
changes = [
*runtime_file_changes(config, paths, AGENT_KEY, guard_script, audit_script),
*present([rendered_change(render_document(document, hooks))]),
]
write_changes(changes, "Install Cursor hooks")
print(f" Cursor hooks: {document.file_path}")
def uninstall(self, agent_id: str = "") -> None:
document = read_document()
hooks = remove_managed_hooks(document.hooks, agent_id)
change = rendered_change(render_document(document, hooks))
if change is not None:
write_changes([change], "Uninstall Cursor hooks")
def status(self) -> PluginStatus:
document = read_document()
contracts = runtime_contracts(document.hooks)
if not contracts:
return PluginStatus(installed=False, agent=AGENT_KEY, details="Not installed")
installed = runtime_files_exist(contracts)
details = (
f"Config: {document.file_path}"
if installed
else f"Configured at {document.file_path}; runtime files missing"
)
return PluginStatus(installed=installed, agent=AGENT_KEY, details=details)