Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added AGENTS.md
Empty file.
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# hookshot

A Go library for building hooks for AI coding agents like [Cursor](https://cursor.com/docs/agent/hooks) and [Claude Code](https://docs.claude.com/en/docs/claude-code/hooks).
A Go library for building hooks for AI coding agents like [Cursor](https://cursor.com/docs/agent/hooks), [Claude Code](https://docs.claude.com/en/docs/claude-code/hooks), [Windsurf Cascade](https://docs.codeium.com/windsurf/memories#hooks), and [Factory Droid](https://docs.factory.ai/reference/hooks-reference).

Hooks are a key component of [Agentic Coding Security Management (ACSM)](https://corridor.dev/blog/introducing-acsm/) — they let you observe, control, and secure AI agent behavior in your development environment.

Expand Down Expand Up @@ -126,20 +126,38 @@ hookshot.Register("cursor-before-tab-read", func() {
return cursor.AllowTabRead()
})
})

// Windsurf Cascade: Pre-run command
hookshot.Register("cascade-pre-run-command", func() {
hookshot.Run(func(input cascade.PreRunCommandInput) cascade.PreRunCommandOutput {
return cascade.AllowCommand()
})
})

// Factory Droid: Pre-tool use
hookshot.Register("droid-pre-tool-use", func() {
hookshot.Run(func(input droid.PreToolUseInput) droid.PreToolUseOutput {
return droid.PassThrough()
})
})
```

## Documentation

- [Unified API Reference](docs/reference-unified.md)
- [Claude Code Reference](docs/reference-claude.md)
- [Cursor Reference](docs/reference-cursor.md)
- [Windsurf Cascade Reference](docs/reference-cascade.md)
- [Factory Droid Reference](docs/reference-droid.md)

Full API documentation is available via godoc:

```bash
go doc github.com/CorridorSecurity/hookshot
go doc github.com/CorridorSecurity/hookshot/claude
go doc github.com/CorridorSecurity/hookshot/cursor
go doc github.com/CorridorSecurity/hookshot/cascade
go doc github.com/CorridorSecurity/hookshot/droid
```

Or view online at [pkg.go.dev/github.com/CorridorSecurity/hookshot](https://pkg.go.dev/github.com/CorridorSecurity/hookshot).
Expand Down
96 changes: 96 additions & 0 deletions cascade/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Package cascade provides types and helpers for Windsurf Cascade hooks.
//
// Windsurf Cascade hooks allow you to observe, control, and extend the AI agent loop
// using custom scripts. Hooks are spawned processes that communicate over stdio
// using JSON.
//
// # Hook Events
//
// Cascade supports the following hook events:
//
// Pre-hooks (can block or modify):
// - [PreReadCodeInput]/[PreReadCodeOutput]: Before reading code files
// - [PreWriteCodeInput]/[PreWriteCodeOutput]: Before writing code files
// - [PreRunCommandInput]/[PreRunCommandOutput]: Before running shell commands
// - [PreMCPToolUseInput]/[PreMCPToolUseOutput]: Before MCP tool execution
// - [PreUserPromptInput]/[PreUserPromptOutput]: Before processing user prompt
//
// Post-hooks (observe only):
// - [PostReadCodeInput]/[PostReadCodeOutput]: After reading code files
// - [PostWriteCodeInput]/[PostWriteCodeOutput]: After writing code files
// - [PostRunCommandInput]/[PostRunCommandOutput]: After running shell commands
// - [PostMCPToolUseInput]/[PostMCPToolUseOutput]: After MCP tool execution
// - [PostCascadeResponseInput]/[PostCascadeResponseOutput]: After Cascade responds
// - [PostSetupWorktreeInput]/[PostSetupWorktreeOutput]: After worktree setup
//
// # PreRunCommand Hooks
//
// PreRunCommand hooks control shell command execution:
//
// hookshot.Run(func(input cascade.PreRunCommandInput) cascade.PreRunCommandOutput {
// // Block dangerous commands
// if strings.Contains(input.ToolInfo.CommandLine, "rm -rf") {
// return cascade.Deny("Dangerous command blocked")
// }
// return cascade.Allow()
// })
//
// # PreWriteCode Hooks
//
// PreWriteCode hooks control file modifications:
//
// hookshot.Run(func(input cascade.PreWriteCodeInput) cascade.PreWriteCodeOutput {
// // Block writes to sensitive files
// if strings.Contains(input.ToolInfo.FilePath, ".env") {
// return cascade.Deny("Cannot modify environment files")
// }
// return cascade.Allow()
// })
//
// # PreUserPrompt Hooks
//
// PreUserPrompt hooks validate or modify user prompts:
//
// hookshot.Run(func(input cascade.PreUserPromptInput) cascade.PreUserPromptOutput {
// if containsSecrets(input.ToolInfo.Prompt) {
// return cascade.BlockPrompt("Please don't include secrets in prompts")
// }
// return cascade.AllowPrompt()
// })
//
// # Helper Functions
//
// This package provides helper functions for common responses:
//
// Pre-hooks (PreRunCommand, PreWriteCode, PreReadCode, PreMCPToolUse):
// - [Allow]: Permit the action
// - [Deny]: Block the action with a reason
// - [Ask]: Prompt user to confirm (where supported)
//
// PreUserPrompt:
// - [AllowPrompt]: Process normally
// - [BlockPrompt]: Reject with reason
//
// Post-hooks:
// - [PostOK]: Normal flow (all post-hooks are fire-and-forget)
//
// # Configuration
//
// Configure Cascade hooks in ~/.codeium/windsurf/hooks.json:
//
// {
// "hooks": {
// "pre_run_command": [
// { "command": "/path/to/hooks cascade-pre-run-command" }
// ],
// "pre_write_code": [
// { "command": "/path/to/hooks cascade-pre-write-code" }
// ],
// "pre_user_prompt": [
// { "command": "/path/to/hooks cascade-pre-user-prompt" }
// ]
// }
// }
//
// See Windsurf documentation for full details.
package cascade
180 changes: 180 additions & 0 deletions cascade/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package cascade

// =============================================================================
// Pre-Hook Decision Helpers (shared by PreRunCommand, PreWriteCode, etc.)
// =============================================================================

// Allow permits the action to proceed.
func Allow() BaseOutput {
return BaseOutput{
Decision: "allow",
}
}

// Deny blocks the action with a message.
func Deny(message string) BaseOutput {
return BaseOutput{
Decision: "deny",
Message: message,
}
}

// Ask prompts the user to confirm the action.
func Ask(message string) BaseOutput {
return BaseOutput{
Decision: "ask",
Message: message,
}
}

// =============================================================================
// PreRunCommand Helpers
// =============================================================================

// AllowCommand permits the command to execute.
func AllowCommand() PreRunCommandOutput {
return PreRunCommandOutput{
BaseOutput: Allow(),
}
}

// DenyCommand blocks the command from executing.
func DenyCommand(message string) PreRunCommandOutput {
return PreRunCommandOutput{
BaseOutput: Deny(message),
}
}

// AskCommand prompts the user to confirm the command.
func AskCommand(message string) PreRunCommandOutput {
return PreRunCommandOutput{
BaseOutput: Ask(message),
}
}

// =============================================================================
// PreWriteCode Helpers
// =============================================================================

// AllowWrite permits the file write.
func AllowWrite() PreWriteCodeOutput {
return PreWriteCodeOutput{
BaseOutput: Allow(),
}
}

// DenyWrite blocks the file write.
func DenyWrite(message string) PreWriteCodeOutput {
return PreWriteCodeOutput{
BaseOutput: Deny(message),
}
}

// AskWrite prompts the user to confirm the write.
func AskWrite(message string) PreWriteCodeOutput {
return PreWriteCodeOutput{
BaseOutput: Ask(message),
}
}

// =============================================================================
// PreReadCode Helpers
// =============================================================================

// AllowRead permits the file read.
func AllowRead() PreReadCodeOutput {
return PreReadCodeOutput{
BaseOutput: Allow(),
}
}

// DenyRead blocks the file read.
func DenyRead(message string) PreReadCodeOutput {
return PreReadCodeOutput{
BaseOutput: Deny(message),
}
}

// AskRead prompts the user to confirm the read.
func AskRead(message string) PreReadCodeOutput {
return PreReadCodeOutput{
BaseOutput: Ask(message),
}
}

// =============================================================================
// PreMCPToolUse Helpers
// =============================================================================

// AllowMCP permits the MCP tool to execute.
func AllowMCP() PreMCPToolUseOutput {
return PreMCPToolUseOutput{
BaseOutput: Allow(),
}
}

// DenyMCP blocks the MCP tool from executing.
func DenyMCP(message string) PreMCPToolUseOutput {
return PreMCPToolUseOutput{
BaseOutput: Deny(message),
}
}

// AskMCP prompts the user to confirm the MCP tool.
func AskMCP(message string) PreMCPToolUseOutput {
return PreMCPToolUseOutput{
BaseOutput: Ask(message),
}
}

// =============================================================================
// PreUserPrompt Helpers
// =============================================================================

// AllowPrompt allows the prompt to be processed.
func AllowPrompt() PreUserPromptOutput {
return PreUserPromptOutput{
BaseOutput: Allow(),
}
}

// BlockPrompt prevents the prompt from being processed.
func BlockPrompt(message string) PreUserPromptOutput {
return PreUserPromptOutput{
BaseOutput: Deny(message),
}
}

// =============================================================================
// Post-Hook Helpers (all are fire-and-forget)
// =============================================================================

// PostRunCommandOK returns an empty output for post-run-command.
func PostRunCommandOK() PostRunCommandOutput {
return PostRunCommandOutput{}
}

// PostWriteCodeOK returns an empty output for post-write-code.
func PostWriteCodeOK() PostWriteCodeOutput {
return PostWriteCodeOutput{}
}

// PostReadCodeOK returns an empty output for post-read-code.
func PostReadCodeOK() PostReadCodeOutput {
return PostReadCodeOutput{}
}

// PostMCPToolUseOK returns an empty output for post-mcp-tool-use.
func PostMCPToolUseOK() PostMCPToolUseOutput {
return PostMCPToolUseOutput{}
}

// PostCascadeResponseOK returns an empty output for post-cascade-response.
func PostCascadeResponseOK() PostCascadeResponseOutput {
return PostCascadeResponseOutput{}
}

// PostSetupWorktreeOK returns an empty output for post-setup-worktree.
func PostSetupWorktreeOK() PostSetupWorktreeOutput {
return PostSetupWorktreeOutput{}
}
Loading
Loading