diff --git a/internal/app/app.go b/internal/app/app.go index 8519f258502ad10f146870b89c7bb5f0f50994e4..1becd7223cb3b4367c1ff080ed88a77ec4525750 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -23,6 +23,7 @@ import ( "github.com/charmbracelet/crush/internal/db" "github.com/charmbracelet/crush/internal/format" "github.com/charmbracelet/crush/internal/history" + "github.com/charmbracelet/crush/internal/hooks" "github.com/charmbracelet/crush/internal/log" "github.com/charmbracelet/crush/internal/lsp" "github.com/charmbracelet/crush/internal/message" @@ -46,6 +47,7 @@ type App struct { Permissions permission.Service AgentCoordinator agent.Coordinator + HooksManager hooks.Manager LSPClients *csync.Map[string, *lsp.Client] @@ -89,6 +91,9 @@ func New(ctx context.Context, conn *sql.DB, cfg *config.Config) (*App, error) { tuiWG: &sync.WaitGroup{}, } + // Initialize hooks manager. + app.HooksManager = hooks.NewManager(cfg.WorkingDir(), cfg.Options.DataDirectory, cfg.Hooks) + app.setupEvents() // Initialize LSP clients in the background. diff --git a/internal/config/config.go b/internal/config/config.go index 4b441b0b18563814ca58b9d48cf2e8ffbd7e782f..8a1a928124ffaa6ae42d44bd23c1b8d5715402ad 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -14,6 +14,7 @@ import ( "github.com/charmbracelet/catwalk/pkg/catwalk" "github.com/charmbracelet/crush/internal/csync" "github.com/charmbracelet/crush/internal/env" + "github.com/charmbracelet/crush/internal/hooks" "github.com/invopop/jsonschema" "github.com/tidwall/sjson" ) @@ -326,6 +327,8 @@ type Config struct { Tools Tools `json:"tools,omitzero" jsonschema:"description=Tool configurations"` + Hooks *hooks.Config `json:"hooks,omitempty" jsonschema:"description=Hook system configuration"` + Agents map[string]Agent `json:"-"` // Internal diff --git a/internal/hooks/builtins.go b/internal/hooks/builtins.go index 7221d63aef87b8cc553321c95015346cd11f4f60..544b2d0dc30905a757e91686eeb374fb42ec36db 100644 --- a/internal/hooks/builtins.go +++ b/internal/hooks/builtins.go @@ -39,7 +39,6 @@ func crushGetInput(ctx context.Context, args []string) error { // Usage: COMMAND=$(crush_get_tool_input "command") func crushGetToolInput(ctx context.Context, args []string) error { hc := interp.HandlerCtx(ctx) - if len(args) != 2 { fmt.Fprintln(hc.Stderr, "Usage: crush_get_tool_input ") return interp.ExitStatus(1) diff --git a/internal/hooks/config.go b/internal/hooks/config.go index bc04858e3634660db61f8ada9d3e17166d9e9149..f9223f17e365e9cd11d6a92b4526c4c93b04f5e7 100644 --- a/internal/hooks/config.go +++ b/internal/hooks/config.go @@ -3,33 +3,33 @@ package hooks // Config defines hook system configuration. type Config struct { // Enabled controls whether hooks are executed. - Enabled bool + Enabled bool `json:"enabled,omitempty" jsonschema:"description=Enable or disable hook execution,default=true"` // TimeoutSeconds is the maximum time a hook can run. - TimeoutSeconds int + TimeoutSeconds int `json:"timeout_seconds,omitempty" jsonschema:"description=Maximum execution time for hooks in seconds,default=30,example=30"` // Directories are additional directories to search for hooks. // Defaults to [".crush/hooks"] if empty. - Directories []string + Directories []string `json:"directories,omitempty" jsonschema:"description=Directories to search for hook scripts,example=.crush/hooks"` // Inline hooks defined directly in configuration. // Map key is the hook type (e.g., "pre-tool-use"). - Inline map[string][]InlineHook + Inline map[string][]InlineHook `json:"inline,omitempty" jsonschema:"description=Inline hook scripts defined in configuration"` // Disabled is a list of hook paths to skip. // Paths are relative to the hooks directory. // Example: ["pre-tool-use/02-slow-check.sh"] - Disabled []string + Disabled []string `json:"disabled,omitempty" jsonschema:"description=List of hook paths to disable,example=pre-tool-use/02-slow-check.sh"` // Environment variables to pass to hooks. - Environment map[string]string + Environment map[string]string `json:"environment,omitempty" jsonschema:"description=Environment variables to pass to all hooks"` } // InlineHook is a hook defined inline in the config. type InlineHook struct { // Name is the name of the hook (used as filename). - Name string + Name string `json:"name" jsonschema:"required,description=Name of the hook script,example=audit.sh"` // Script is the bash script content. - Script string + Script string `json:"script" jsonschema:"required,description=Bash script content to execute"` }