feat: add hooks to plugin.json, eliminating need for global install
The beads plugin now provides SessionStart and PreCompact hooks directly via plugin.json. Users no longer need to run 'bd setup claude' when using the plugin - hooks are automatically available. Changes: - Add hooks section to .claude-plugin/plugin.json with SessionStart and PreCompact hooks that run 'bd prime' - Update doctor/claude.go to recognize plugin-provided hooks as valid - Update tips.go to check for plugin installation when determining if Claude integration is complete - Update messaging to recommend plugin installation as primary option The 'bd setup claude' command remains available for CLI-only users who do not want to install the plugin. Closes #462 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -27,5 +27,29 @@
|
|||||||
],
|
],
|
||||||
"env": {}
|
"env": {}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"hooks": {
|
||||||
|
"SessionStart": [
|
||||||
|
{
|
||||||
|
"matcher": "",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "bd prime"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"PreCompact": [
|
||||||
|
{
|
||||||
|
"matcher": "",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "bd prime"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,20 +24,14 @@ func CheckClaude() DoctorCheck {
|
|||||||
hasMCP := isMCPServerInstalled()
|
hasMCP := isMCPServerInstalled()
|
||||||
hasHooks := hasClaudeHooks()
|
hasHooks := hasClaudeHooks()
|
||||||
|
|
||||||
// Plugin provides slash commands and MCP server
|
// Plugin now provides hooks directly via plugin.json, so if plugin is installed
|
||||||
if hasPlugin && hasHooks {
|
// we consider hooks to be available (plugin hooks + any user-configured hooks)
|
||||||
|
if hasPlugin {
|
||||||
return DoctorCheck{
|
return DoctorCheck{
|
||||||
Name: "Claude Integration",
|
Name: "Claude Integration",
|
||||||
Status: "ok",
|
Status: "ok",
|
||||||
Message: "Plugin and hooks installed",
|
Message: "Plugin installed",
|
||||||
Detail: "Slash commands and workflow reminders enabled",
|
Detail: "Slash commands and workflow hooks enabled via plugin",
|
||||||
}
|
|
||||||
} else if hasPlugin && !hasHooks {
|
|
||||||
return DoctorCheck{
|
|
||||||
Name: "Claude Integration",
|
|
||||||
Status: "warning",
|
|
||||||
Message: "Plugin installed but hooks missing",
|
|
||||||
Fix: "Run: bd setup claude",
|
|
||||||
}
|
}
|
||||||
} else if hasMCP && hasHooks {
|
} else if hasMCP && hasHooks {
|
||||||
return DoctorCheck{
|
return DoctorCheck{
|
||||||
@@ -75,17 +69,19 @@ func CheckClaude() DoctorCheck {
|
|||||||
Name: "Claude Integration",
|
Name: "Claude Integration",
|
||||||
Status: "warning",
|
Status: "warning",
|
||||||
Message: "Not configured",
|
Message: "Not configured",
|
||||||
Detail: "Claude can use bd more effectively with hooks and optional plugin",
|
Detail: "Claude can use bd more effectively with the beads plugin",
|
||||||
Fix: "Set up Claude integration:\n" +
|
Fix: "Set up Claude integration:\n" +
|
||||||
" 1. Run 'bd setup claude' to add SessionStart/PreCompact hooks\n" +
|
" Option 1: Install the beads plugin (recommended)\n" +
|
||||||
" 2. (Optional) Install beads plugin for slash commands\n" +
|
" • Provides hooks, slash commands, and MCP tools automatically\n" +
|
||||||
|
" • See: https://github.com/steveyegge/beads#claude-code-plugin\n" +
|
||||||
|
"\n" +
|
||||||
|
" Option 2: CLI-only mode\n" +
|
||||||
|
" • Run 'bd setup claude' to add SessionStart/PreCompact hooks\n" +
|
||||||
|
" • No slash commands, but hooks provide workflow context\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"Benefits:\n" +
|
"Benefits:\n" +
|
||||||
" • Hooks: Auto-inject workflow context (~50-2k tokens)\n" +
|
" • Auto-inject workflow context on session start (~50-2k tokens)\n" +
|
||||||
" • Plugin: Convenient slash commands + MCP tools\n" +
|
" • Automatic context recovery before compaction",
|
||||||
" • CLI mode: Works without plugin (hooks + manual 'bd prime')\n" +
|
|
||||||
"\n" +
|
|
||||||
"See: bd setup claude --help",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
105
cmd/bd/tips.go
105
cmd/bd/tips.go
@@ -2,12 +2,14 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -244,41 +246,88 @@ func isClaudeDetected() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// isClaudeSetupComplete checks if the beads Claude integration is properly configured.
|
// isClaudeSetupComplete checks if the beads Claude integration is properly configured.
|
||||||
// Checks for either global or project-level installation of the beads hooks.
|
// Returns true if the beads plugin is installed (provides hooks via plugin.json),
|
||||||
|
// or if hooks were manually installed via 'bd setup claude'.
|
||||||
func isClaudeSetupComplete() bool {
|
func isClaudeSetupComplete() bool {
|
||||||
// Check for global installation
|
|
||||||
home, err := os.UserHomeDir()
|
home, err := os.UserHomeDir()
|
||||||
if err == nil {
|
if err != nil {
|
||||||
commandFile := filepath.Join(home, ".claude", "commands", "prime_beads.md")
|
return false
|
||||||
hooksDir := filepath.Join(home, ".claude", "hooks")
|
}
|
||||||
|
|
||||||
// Check for prime_beads command
|
// Check if beads plugin is installed - plugin now provides hooks automatically
|
||||||
if _, err := os.Stat(commandFile); err == nil {
|
settingsPath := filepath.Join(home, ".claude", "settings.json")
|
||||||
// Check for sessionstart hook (could be a file or directory)
|
if data, err := os.ReadFile(settingsPath); err == nil {
|
||||||
hookPath := filepath.Join(hooksDir, "sessionstart")
|
var settings map[string]interface{}
|
||||||
if _, err := os.Stat(hookPath); err == nil {
|
if err := json.Unmarshal(data, &settings); err == nil {
|
||||||
return true // Global hooks installed
|
if enabledPlugins, ok := settings["enabledPlugins"].(map[string]interface{}); ok {
|
||||||
}
|
for key, value := range enabledPlugins {
|
||||||
// Also check PreToolUse hook which is used by beads
|
if strings.Contains(strings.ToLower(key), "beads") {
|
||||||
preToolUsePath := filepath.Join(hooksDir, "PreToolUse")
|
if enabled, ok := value.(bool); ok && enabled {
|
||||||
if _, err := os.Stat(preToolUsePath); err == nil {
|
return true // Plugin installed - provides hooks
|
||||||
return true // Global hooks installed
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for project-level installation
|
// Check for manual hooks installation via 'bd setup claude'
|
||||||
commandFile := ".claude/commands/prime_beads.md"
|
// Global hooks in settings.json
|
||||||
hooksDir := ".claude/hooks"
|
if hasBeadsPrimeHooks(settingsPath) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := os.Stat(commandFile); err == nil {
|
// Project-level hooks in .claude/settings.local.json
|
||||||
hookPath := filepath.Join(hooksDir, "sessionstart")
|
if hasBeadsPrimeHooks(".claude/settings.local.json") {
|
||||||
if _, err := os.Stat(hookPath); err == nil {
|
return true
|
||||||
return true // Project hooks installed
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasBeadsPrimeHooks checks if a settings file has bd prime hooks configured
|
||||||
|
func hasBeadsPrimeHooks(settingsPath string) bool {
|
||||||
|
data, err := os.ReadFile(settingsPath)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var settings map[string]interface{}
|
||||||
|
if err := json.Unmarshal(data, &settings); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
hooks, ok := settings["hooks"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check SessionStart and PreCompact for "bd prime"
|
||||||
|
for _, event := range []string{"SessionStart", "PreCompact"} {
|
||||||
|
eventHooks, ok := hooks[event].([]interface{})
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
preToolUsePath := filepath.Join(hooksDir, "PreToolUse")
|
|
||||||
if _, err := os.Stat(preToolUsePath); err == nil {
|
for _, hook := range eventHooks {
|
||||||
return true // Project hooks installed
|
hookMap, ok := hook.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
commands, ok := hookMap["hooks"].([]interface{})
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, cmd := range commands {
|
||||||
|
cmdMap, ok := cmd.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cmdStr, _ := cmdMap["command"].(string)
|
||||||
|
if cmdStr == "bd prime" || cmdStr == "bd prime --stealth" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,11 +337,11 @@ func isClaudeSetupComplete() bool {
|
|||||||
// initDefaultTips registers the built-in tips.
|
// initDefaultTips registers the built-in tips.
|
||||||
// Called during initialization to populate the tip registry.
|
// Called during initialization to populate the tip registry.
|
||||||
func initDefaultTips() {
|
func initDefaultTips() {
|
||||||
// Claude setup tip - suggest running bd setup claude when Claude is detected
|
// Claude setup tip - suggest installing the beads plugin when Claude is detected
|
||||||
// but the integration is not configured
|
// but the integration is not configured
|
||||||
InjectTip(
|
InjectTip(
|
||||||
"claude_setup",
|
"claude_setup",
|
||||||
"Run 'bd setup claude' to enable automatic context recovery in Claude Code",
|
"Install the beads plugin for automatic workflow context, or run 'bd setup claude' for CLI-only mode",
|
||||||
100, // Highest priority - this is important for Claude users
|
100, // Highest priority - this is important for Claude users
|
||||||
24*time.Hour, // Daily minimum gap
|
24*time.Hour, // Daily minimum gap
|
||||||
0.6, // 60% chance when eligible (~4 times per week)
|
0.6, // 60% chance when eligible (~4 times per week)
|
||||||
|
|||||||
Reference in New Issue
Block a user