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
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,35 @@ $ histree -v # History is preserved with the new path

This example demonstrates how histree helps track your development workflow across different directories and projects, maintaining the context of your work.

## Claude Code Integration

You can automatically record commands executed by [Claude Code](https://claude.ai/code) using the provided hook script.

### Installation

```sh
./install-claude-code-hook.sh
```

This will:
1. Create a hook script at `~/.claude/hooks/post-bash-histree.sh`
2. Add the PostToolUse hook configuration to `~/.claude/settings.json`

After installation, restart Claude Code to activate the hook.

### Features

- Commands are recorded with hostname suffix `:claude` (e.g., `myhost:claude`) for easy filtering
- Session-based pseudo-PID groups related commands together
- Exit codes are tracked

### View Claude Code History

```sh
# Show only Claude Code commands
histree-core -db ~/.histree.db -action get -v | grep :claude
```

## Requirements

- Go 1.18 or later (for building the binary and using as a library)
Expand Down
172 changes: 172 additions & 0 deletions install-claude-code-hook.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
#!/bin/bash
# Install histree hook for Claude Code
# This script sets up a PostToolUse hook to record Bash commands to histree

set -e

CLAUDE_DIR="$HOME/.claude"
HOOKS_DIR="$CLAUDE_DIR/hooks"
SETTINGS_FILE="$CLAUDE_DIR/settings.json"
HOOK_SCRIPT="$HOOKS_DIR/post-bash-histree.sh"

# Detect histree-core path
if [ -x "$HOME/.histree-zsh/bin/histree-core" ]; then
HISTREE_CORE="$HOME/.histree-zsh/bin/histree-core"
elif command -v histree-core &> /dev/null; then
HISTREE_CORE=$(command -v histree-core)
else
echo "Error: histree-core not found"
echo "Please install histree-core first"
exit 1
fi

# Database path
HISTREE_DB="$HOME/.histree.db"

echo "Installing Claude Code hook for histree..."
echo " histree-core: $HISTREE_CORE"
echo " database: $HISTREE_DB"

# Create hooks directory
mkdir -p "$HOOKS_DIR"

# Create hook script
cat > "$HOOK_SCRIPT" << 'HOOKEOF'
#!/bin/bash
# Hook script to record Claude Code's Bash commands to histree
# This script is called by Claude Code after each Bash command execution

# Read JSON input from stdin
INPUT=$(cat)

# Extract command, exit code, and working directory
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
EXIT_CODE=$(echo "$INPUT" | jq -r '.tool_response.exit_code // 0')
CWD=$(echo "$INPUT" | jq -r '.cwd // empty')
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')

# Skip if command is empty
if [ -z "$COMMAND" ]; then
exit 0
fi

# Get hostname
HOSTNAME=$(hostname)

# Path to histree-core and database (will be replaced by installer)
HISTREE_CORE="__HISTREE_CORE__"
HISTREE_DB="__HISTREE_DB__"

# Record to histree (using session_id hash as pid to group related commands)
# Use a pseudo-PID based on session_id to group Claude Code commands
PSEUDO_PID=$(echo "$SESSION_ID" | cksum | awk '{print $1 % 100000 + 900000}')

echo "$COMMAND" | "$HISTREE_CORE" \
-db "$HISTREE_DB" \
-action add \
-hostname "${HOSTNAME}:claude" \
-pid "$PSEUDO_PID" \
-dir "$CWD" \
-exit "$EXIT_CODE" \
2>/dev/null

# Always exit 0 to not block Claude Code
exit 0
HOOKEOF

# Replace placeholders with actual paths
sed -i "s|__HISTREE_CORE__|$HISTREE_CORE|g" "$HOOK_SCRIPT"
sed -i "s|__HISTREE_DB__|$HISTREE_DB|g" "$HOOK_SCRIPT"

# Make executable
chmod +x "$HOOK_SCRIPT"
echo " Created hook script: $HOOK_SCRIPT"

# Update settings.json
if [ ! -f "$SETTINGS_FILE" ]; then
# Create new settings file
cat > "$SETTINGS_FILE" << EOF
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "$HOOK_SCRIPT",
"timeout": 5
}
]
}
]
}
}
EOF
echo " Created settings file: $SETTINGS_FILE"
else
# Check if jq is available
if ! command -v jq &> /dev/null; then
echo "Error: jq is required to update existing settings.json"
echo "Please install jq or manually add the hook configuration"
exit 1
fi

# Check if hooks already configured
if jq -e '.hooks.PostToolUse' "$SETTINGS_FILE" > /dev/null 2>&1; then
# Check if histree hook already exists
if jq -e '.hooks.PostToolUse[] | select(.hooks[].command | contains("post-bash-histree"))' "$SETTINGS_FILE" > /dev/null 2>&1; then
echo " Hook already configured in settings.json"
else
# Add to existing PostToolUse array
TMP_FILE=$(mktemp)
jq --arg cmd "$HOOK_SCRIPT" '.hooks.PostToolUse += [{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": $cmd,
"timeout": 5
}]
}]' "$SETTINGS_FILE" > "$TMP_FILE"
mv "$TMP_FILE" "$SETTINGS_FILE"
echo " Added hook to existing PostToolUse in settings.json"
fi
elif jq -e '.hooks' "$SETTINGS_FILE" > /dev/null 2>&1; then
# hooks exists but no PostToolUse
TMP_FILE=$(mktemp)
jq --arg cmd "$HOOK_SCRIPT" '.hooks.PostToolUse = [{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": $cmd,
"timeout": 5
}]
}]' "$SETTINGS_FILE" > "$TMP_FILE"
mv "$TMP_FILE" "$SETTINGS_FILE"
echo " Added PostToolUse hook to settings.json"
else
# No hooks at all
TMP_FILE=$(mktemp)
jq --arg cmd "$HOOK_SCRIPT" '. + {
"hooks": {
"PostToolUse": [{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": $cmd,
"timeout": 5
}]
}]
}
}' "$SETTINGS_FILE" > "$TMP_FILE"
mv "$TMP_FILE" "$SETTINGS_FILE"
echo " Added hooks configuration to settings.json"
fi
fi

echo ""
echo "Installation complete!"
echo "Restart Claude Code to activate the hook."
echo ""
echo "Commands will be recorded with hostname suffix ':claude'"
echo "View history: $HISTREE_CORE -db $HISTREE_DB -action get -v | grep :claude"