Merge pull request #456 from abhinav/add-setup-stealth-flag
feat(setup claude): add --stealth flag
This commit is contained in:
@@ -9,6 +9,7 @@ var (
|
||||
setupProject bool
|
||||
setupCheck bool
|
||||
setupRemove bool
|
||||
setupStealth bool
|
||||
)
|
||||
|
||||
var setupCmd = &cobra.Command{
|
||||
@@ -86,7 +87,7 @@ agents from forgetting bd workflow after context compaction.`,
|
||||
return
|
||||
}
|
||||
|
||||
setup.InstallClaude(setupProject)
|
||||
setup.InstallClaude(setupProject, setupStealth)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -94,6 +95,7 @@ func init() {
|
||||
setupClaudeCmd.Flags().BoolVar(&setupProject, "project", false, "Install for this project only (not globally)")
|
||||
setupClaudeCmd.Flags().BoolVar(&setupCheck, "check", false, "Check if Claude integration is installed")
|
||||
setupClaudeCmd.Flags().BoolVar(&setupRemove, "remove", false, "Remove bd hooks from Claude settings")
|
||||
setupClaudeCmd.Flags().BoolVar(&setupStealth, "stealth", false, "Use 'bd prime --stealth' (flush only, no git operations)")
|
||||
|
||||
setupCursorCmd.Flags().BoolVar(&setupCheck, "check", false, "Check if Cursor integration is installed")
|
||||
setupCursorCmd.Flags().BoolVar(&setupRemove, "remove", false, "Remove bd rules from Cursor")
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
// InstallClaude installs Claude Code hooks
|
||||
func InstallClaude(project bool) {
|
||||
func InstallClaude(project bool, stealth bool) {
|
||||
var settingsPath string
|
||||
|
||||
if project {
|
||||
@@ -49,13 +49,19 @@ func InstallClaude(project bool) {
|
||||
settings["hooks"] = hooks
|
||||
}
|
||||
|
||||
// Determine which command to use
|
||||
command := "bd prime"
|
||||
if stealth {
|
||||
command = "bd prime --stealth"
|
||||
}
|
||||
|
||||
// Add SessionStart hook
|
||||
if addHookCommand(hooks, "SessionStart", "bd prime") {
|
||||
if addHookCommand(hooks, "SessionStart", command) {
|
||||
fmt.Println("✓ Registered SessionStart hook")
|
||||
}
|
||||
|
||||
// Add PreCompact hook
|
||||
if addHookCommand(hooks, "PreCompact", "bd prime") {
|
||||
if addHookCommand(hooks, "PreCompact", command) {
|
||||
fmt.Println("✓ Registered PreCompact hook")
|
||||
}
|
||||
|
||||
@@ -137,9 +143,11 @@ func RemoveClaude(project bool) {
|
||||
return
|
||||
}
|
||||
|
||||
// Remove bd prime hooks
|
||||
// Remove bd prime hooks (both variants for backwards compatibility)
|
||||
removeHookCommand(hooks, "SessionStart", "bd prime")
|
||||
removeHookCommand(hooks, "PreCompact", "bd prime")
|
||||
removeHookCommand(hooks, "SessionStart", "bd prime --stealth")
|
||||
removeHookCommand(hooks, "PreCompact", "bd prime --stealth")
|
||||
|
||||
// Write back
|
||||
data, err = json.MarshalIndent(settings, "", " ")
|
||||
@@ -284,7 +292,9 @@ func hasBeadsHooks(settingsPath string) bool {
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if cmdMap["command"] == "bd prime" {
|
||||
// Check for either variant
|
||||
cmd := cmdMap["command"]
|
||||
if cmd == "bd prime" || cmd == "bd prime --stealth" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,13 @@ func TestAddHookCommand(t *testing.T) {
|
||||
command: "bd prime",
|
||||
wantAdded: true,
|
||||
},
|
||||
{
|
||||
name: "add stealth hook to empty hooks",
|
||||
existingHooks: make(map[string]interface{}),
|
||||
event: "SessionStart",
|
||||
command: "bd prime --stealth",
|
||||
wantAdded: true,
|
||||
},
|
||||
{
|
||||
name: "hook already exists",
|
||||
existingHooks: map[string]interface{}{
|
||||
@@ -41,6 +48,25 @@ func TestAddHookCommand(t *testing.T) {
|
||||
command: "bd prime",
|
||||
wantAdded: false,
|
||||
},
|
||||
{
|
||||
name: "stealth hook already exists",
|
||||
existingHooks: map[string]interface{}{
|
||||
"SessionStart": []interface{}{
|
||||
map[string]interface{}{
|
||||
"matcher": "",
|
||||
"hooks": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "command",
|
||||
"command": "bd prime --stealth",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
event: "SessionStart",
|
||||
command: "bd prime --stealth",
|
||||
wantAdded: false,
|
||||
},
|
||||
{
|
||||
name: "add second hook alongside existing",
|
||||
existingHooks: map[string]interface{}{
|
||||
@@ -122,6 +148,25 @@ func TestRemoveHookCommand(t *testing.T) {
|
||||
command: "bd prime",
|
||||
wantRemaining: 0,
|
||||
},
|
||||
{
|
||||
name: "remove stealth hook",
|
||||
existingHooks: map[string]interface{}{
|
||||
"SessionStart": []interface{}{
|
||||
map[string]interface{}{
|
||||
"matcher": "",
|
||||
"hooks": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "command",
|
||||
"command": "bd prime --stealth",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
event: "SessionStart",
|
||||
command: "bd prime --stealth",
|
||||
wantRemaining: 0,
|
||||
},
|
||||
{
|
||||
name: "remove one of multiple hooks",
|
||||
existingHooks: map[string]interface{}{
|
||||
@@ -184,9 +229,9 @@ func TestHasBeadsHooks(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
name string
|
||||
settingsData map[string]interface{}
|
||||
want bool
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "has bd prime hook",
|
||||
@@ -208,9 +253,66 @@ func TestHasBeadsHooks(t *testing.T) {
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "no hooks",
|
||||
name: "has bd prime --stealth hook",
|
||||
settingsData: map[string]interface{}{
|
||||
"hooks": map[string]interface{}{
|
||||
"SessionStart": []interface{}{
|
||||
map[string]interface{}{
|
||||
"matcher": "",
|
||||
"hooks": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "command",
|
||||
"command": "bd prime --stealth",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "has bd prime in PreCompact",
|
||||
settingsData: map[string]interface{}{
|
||||
"hooks": map[string]interface{}{
|
||||
"PreCompact": []interface{}{
|
||||
map[string]interface{}{
|
||||
"matcher": "",
|
||||
"hooks": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "command",
|
||||
"command": "bd prime",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "has bd prime --stealth in PreCompact",
|
||||
settingsData: map[string]interface{}{
|
||||
"hooks": map[string]interface{}{
|
||||
"PreCompact": []interface{}{
|
||||
map[string]interface{}{
|
||||
"matcher": "",
|
||||
"hooks": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "command",
|
||||
"command": "bd prime --stealth",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "no hooks",
|
||||
settingsData: map[string]interface{}{},
|
||||
want: false,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "has other hooks but not bd prime",
|
||||
@@ -242,7 +344,7 @@ func TestHasBeadsHooks(t *testing.T) {
|
||||
t.Fatalf("Failed to marshal test data: %v", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(settingsPath, data, 0644); err != nil {
|
||||
if err := os.WriteFile(settingsPath, data, 0o644); err != nil {
|
||||
t.Fatalf("Failed to write test file: %v", err)
|
||||
}
|
||||
|
||||
@@ -276,3 +378,31 @@ func TestIdempotency(t *testing.T) {
|
||||
t.Errorf("Expected 1 hook, got %d", len(eventHooks))
|
||||
}
|
||||
}
|
||||
|
||||
// Test that running addHookCommand twice with stealth doesn't duplicate hooks
|
||||
func TestIdempotencyWithStealth(t *testing.T) {
|
||||
hooks := make(map[string]any)
|
||||
|
||||
if !addHookCommand(hooks, "SessionStart", "bd prime --stealth") {
|
||||
t.Error("First call should have added the stealth hook")
|
||||
}
|
||||
|
||||
// Second add (should detect existing)
|
||||
if addHookCommand(hooks, "SessionStart", "bd prime --stealth") {
|
||||
t.Error("Second call should have detected existing stealth hook")
|
||||
}
|
||||
|
||||
// Verify only one hook exists
|
||||
eventHooks := hooks["SessionStart"].([]any)
|
||||
if len(eventHooks) != 1 {
|
||||
t.Errorf("Expected 1 hook, got %d", len(eventHooks))
|
||||
}
|
||||
|
||||
// and that it's the correct one
|
||||
hookMap := eventHooks[0].(map[string]any)
|
||||
commands := hookMap["hooks"].([]any)
|
||||
cmdMap := commands[0].(map[string]any)
|
||||
if cmdMap["command"] != "bd prime --stealth" {
|
||||
t.Errorf("Expected 'bd prime --stealth', got %v", cmdMap["command"])
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user