174 lines
4.4 KiB
Go
174 lines
4.4 KiB
Go
package plugins
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
)
|
|
|
|
// CursorPlugin manages Cursor's native global user hooks.
|
|
type CursorPlugin struct {
|
|
rename renameFunc
|
|
}
|
|
|
|
// ManagesGuardRuntime reports that Cursor commits its provider-specific guard
|
|
// in the same transaction as the rest of the installation.
|
|
func (p *CursorPlugin) ManagesGuardRuntime() bool {
|
|
return true
|
|
}
|
|
|
|
func cursorAgentPaths(config InstallConfig) (
|
|
runtimeRoot, agentDirectory, guardPath, auditPath string,
|
|
err error,
|
|
) {
|
|
if config.AgentID == "" {
|
|
return "", "", "", "", fmt.Errorf("agent ID is required")
|
|
}
|
|
runtimeRoot, err = AgentRuntimeRoot()
|
|
if err != nil {
|
|
return "", "", "", "", err
|
|
}
|
|
agentDirectory, err = ResolveAgentRuntimeDirectory(config.AgentID)
|
|
if err != nil {
|
|
return "", "", "", "", err
|
|
}
|
|
guardPath = filepath.Join(agentDirectory, cursorGuardScript)
|
|
if !sameCursorPath(config.GuardScriptPath, guardPath) {
|
|
return "", "", "", "", fmt.Errorf(
|
|
"Elydora guard runtime must use the managed agent directory: %s",
|
|
guardPath,
|
|
)
|
|
}
|
|
auditPath = filepath.Join(agentDirectory, cursorAuditScript)
|
|
if config.HookScript != "" && !sameCursorPath(config.HookScript, auditPath) {
|
|
return "", "", "", "", fmt.Errorf(
|
|
"Elydora audit runtime must use the managed agent directory: %s",
|
|
auditPath,
|
|
)
|
|
}
|
|
return runtimeRoot, agentDirectory, guardPath, auditPath, nil
|
|
}
|
|
|
|
// PreflightInstall validates every existing source before the CLI creates a
|
|
// runtime directory or guard file.
|
|
func (p *CursorPlugin) PreflightInstall(config InstallConfig) error {
|
|
if _, err := readCursorDocument(); err != nil {
|
|
return err
|
|
}
|
|
_, agentDirectory, _, _, err := cursorAgentPaths(config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := preflightCursorRuntime(agentDirectory, config.AgentID); err != nil {
|
|
return err
|
|
}
|
|
_, err = resolveNodeRuntime()
|
|
return err
|
|
}
|
|
|
|
func (p *CursorPlugin) Install(config InstallConfig) error {
|
|
document, err := readCursorDocument()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
runtimeRoot, agentDirectory, guardPath, auditPath, err := cursorAgentPaths(config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := preflightCursorRuntime(agentDirectory, config.AgentID); err != nil {
|
|
return err
|
|
}
|
|
nodePath, err := resolveNodeRuntime()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
hooks := removeManagedCursorHooks(document.hooks, runtimeRoot, "")
|
|
hooks["preToolUse"] = append(
|
|
hooks["preToolUse"],
|
|
buildCursorHandler(nodePath, guardPath),
|
|
)
|
|
hooks["postToolUse"] = append(
|
|
hooks["postToolUse"],
|
|
buildCursorHandler(nodePath, auditPath),
|
|
)
|
|
hooks["postToolUseFailure"] = append(
|
|
hooks["postToolUseFailure"],
|
|
buildCursorHandler(nodePath, auditPath),
|
|
)
|
|
rendered, err := renderCursorDocument(document, hooks, runtimeRoot)
|
|
if err != nil {
|
|
return fmt.Errorf("render Cursor user hooks: %w", err)
|
|
}
|
|
changes, err := prepareCursorInstallationChanges(config, agentDirectory, rendered)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := writeCursorChanges(
|
|
changes,
|
|
"Install Cursor hooks",
|
|
p.rename,
|
|
runtimeRoot,
|
|
agentDirectory,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf(" Cursor hooks: %s\n", document.filePath)
|
|
return nil
|
|
}
|
|
|
|
func (p *CursorPlugin) Uninstall(agentID string) error {
|
|
document, err := readCursorDocument()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
runtimeRoot, err := AgentRuntimeRoot()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
hooks := removeManagedCursorHooks(document.hooks, runtimeRoot, agentID)
|
|
rendered, err := renderCursorDocument(document, hooks, runtimeRoot)
|
|
if err != nil {
|
|
return fmt.Errorf("render Cursor user hooks: %w", err)
|
|
}
|
|
change, err := prepareRenderedCursorChange(rendered)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return writeCursorChanges(
|
|
[]*fileChange{change},
|
|
"Uninstall Cursor hooks",
|
|
p.rename,
|
|
"",
|
|
"",
|
|
)
|
|
}
|
|
|
|
func (p *CursorPlugin) Status() (PluginStatus, error) {
|
|
configPath, pathErr := cursorConfigPath()
|
|
entry := SupportedAgents[cursorAgentKey]
|
|
status := PluginStatus{
|
|
AgentName: cursorAgentKey, DisplayName: entry.Name, ConfigPath: configPath,
|
|
}
|
|
if pathErr != nil {
|
|
return status, pathErr
|
|
}
|
|
document, err := readCursorDocument()
|
|
if err != nil {
|
|
return status, err
|
|
}
|
|
runtimeRoot, err := AgentRuntimeRoot()
|
|
if err != nil {
|
|
return status, err
|
|
}
|
|
contracts := cursorRuntimeContracts(document.hooks, runtimeRoot)
|
|
status.HookConfigured = len(contracts) > 0
|
|
if !status.HookConfigured {
|
|
return status, nil
|
|
}
|
|
status.HookScriptExists, err = cursorRuntimeFilesExist(contracts)
|
|
if err != nil {
|
|
return status, err
|
|
}
|
|
status.Installed = status.HookScriptExists
|
|
return status, nil
|
|
}
|