Skip to content

Instantly share code, notes, and snippets.

@fcoury
Forked from johnlindquist/atuin-capture-cwd.sh
Created November 16, 2025 16:54
Show Gist options
  • Select an option

  • Save fcoury/442293a82f006afed89311aa4df15457 to your computer and use it in GitHub Desktop.

Select an option

Save fcoury/442293a82f006afed89311aa4df15457 to your computer and use it in GitHub Desktop.
Cursor + Atuin Integration: Automatically save Cursor AI shell commands to Atuin history with directory scoping
#!/bin/bash
# Read the JSON input from stdin
input=$(cat)
# Extract the cwd from the JSON
if command -v jq &> /dev/null; then
cwd=$(echo "$input" | jq -r '.cwd')
# If cwd is empty or null, use workspace_roots[0] as fallback
if [ -z "$cwd" ] || [ "$cwd" == "null" ]; then
cwd=$(echo "$input" | jq -r '.workspace_roots[0] // ""')
fi
else
# Fallback: simple extraction
cwd=$(echo "$input" | grep -o '"cwd":"[^"]*"' | sed 's/"cwd":"\(.*\)"/\1/')
# If cwd is empty, try to extract workspace_roots[0]
if [ -z "$cwd" ]; then
cwd=$(echo "$input" | grep -o '"workspace_roots":\["[^"]*"' | sed 's/"workspace_roots":\["\(.*\)"/\1/')
fi
fi
# Store the cwd in a temp file for the afterShellExecution hook to use
# Use generation_id if available to make it unique per command
if command -v jq &> /dev/null; then
generation_id=$(echo "$input" | jq -r '.generation_id // "default"')
else
generation_id="default"
fi
if [ -n "$cwd" ] && [ "$cwd" != "null" ]; then
echo "$cwd" > "/tmp/atuin-hook-cwd-${generation_id}"
fi
# Don't output anything - let Cursor handle permissions normally
exit 0
#!/bin/bash
# Read the JSON input from stdin
input=$(cat)
# Extract the command and generation_id from the JSON
# Using jq if available, otherwise use a simple grep/sed approach
if command -v jq &> /dev/null; then
command=$(echo "$input" | jq -r '.command')
generation_id=$(echo "$input" | jq -r '.generation_id // "default"')
else
# Fallback: simple extraction (may not handle all edge cases)
command=$(echo "$input" | grep -o '"command":"[^"]*"' | sed 's/"command":"\(.*\)"/\1/')
generation_id="default"
fi
# Only add to atuin if we successfully extracted a command
if [ -n "$command" ] && [ "$command" != "null" ]; then
# Try to read the cwd from the temp file created by beforeShellExecution
cwd_file="/tmp/atuin-hook-cwd-${generation_id}"
if [ -f "$cwd_file" ]; then
cwd=$(cat "$cwd_file")
rm -f "$cwd_file" # Clean up temp file
else
cwd=$(pwd) # Fallback to current directory
fi
# Change to the correct directory before adding to atuin
# Use a subshell to avoid changing the parent shell's directory
(
cd "$cwd" 2>/dev/null || exit 0
# Start the atuin history entry and capture the ID
# Use -- to handle commands that start with dashes
id=$(atuin history start -- "$command" 2>/dev/null)
# If we got an ID, end the history entry with exit code 0
if [ -n "$id" ]; then
atuin history end --exit 0 "$id" 2>/dev/null
fi
)
fi
# Always exit 0 to not interfere with the agent loop
exit 0

Cursor + Atuin Integration Hook

Automatically add all shell commands executed by Cursor AI to your Atuin shell history with proper directory scoping.

What This Does

This hook integration captures every shell command that Cursor runs and adds it to your Atuin history database. Commands are properly scoped to your workspace directory, making them searchable and filterable just like commands you type manually in your terminal.

Benefits

  • Never Lose AI-Generated Commands: All commands Cursor runs are preserved in your searchable Atuin history
  • Directory Scoping: Commands are associated with the correct workspace directory, enabling directory-filtered search
  • Seamless Integration: Works transparently - no changes to your workflow required
  • Complex Command Support: Handles pipes, redirects, multiline commands, heredocs, and special characters
  • Cross-Session Access: Search and reuse Cursor's commands from any terminal session
  • Learn from AI: Review what commands Cursor used to solve problems for future reference

Installation

Prerequisites

  • Atuin installed and configured
  • Cursor editor
  • jq command-line JSON processor (recommended but not required)

Setup

  1. Create the hooks directory:
mkdir -p ~/.cursor/hooks
  1. Create ~/.cursor/hooks.json:
{
  "version": 1,
  "hooks": {
    "beforeShellExecution": [
      {
        "command": "./hooks/atuin-capture-cwd.sh"
      }
    ],
    "afterShellExecution": [
      {
        "command": "./hooks/atuin-history.sh"
      }
    ]
  }
}
  1. Create ~/.cursor/hooks/atuin-capture-cwd.sh (see file below)

  2. Create ~/.cursor/hooks/atuin-history.sh (see file below)

  3. Make scripts executable:

chmod +x ~/.cursor/hooks/atuin-capture-cwd.sh
chmod +x ~/.cursor/hooks/atuin-history.sh
  1. Restart Cursor

How It Works

The integration uses two hooks:

  1. beforeShellExecution (atuin-capture-cwd.sh): Captures the workspace directory before the command runs and stores it temporarily
  2. afterShellExecution (atuin-history.sh): After the command completes, adds it to Atuin history in the correct directory context

Why Two Hooks?

Cursor's beforeShellExecution hook provides the cwd (current working directory), but we want to record commands after they execute successfully. The afterShellExecution hook doesn't include cwd, so we use a temporary file to pass the directory context between hooks.

Usage

Once installed, the integration works automatically. Every command Cursor executes will appear in your Atuin history.

Searching Commands

# Search all Atuin history (including Cursor commands)
atuin search

# List commands from current directory only
atuin history list --cwd

# Search with Ctrl+R (if configured)
# Commands from Cursor will appear alongside your manual commands

Technical Details

  • Commands are added with exit code 0 (successful completion)
  • The -- separator is used to handle commands starting with dashes
  • Falls back to workspace_roots[0] when cwd is empty (Cursor's current behavior)
  • Uses a subshell to avoid changing the parent shell's directory
  • Temporary files are cleaned up after use
  • Works with or without jq (degrades gracefully)

Troubleshooting

Commands not appearing in history

  1. Check that hooks are configured: Cursor Settings → Hooks tab
  2. Verify scripts are executable: ls -l ~/.cursor/hooks/*.sh
  3. Restart Cursor after making changes
  4. Check Atuin is working: atuin history list

Wrong directory recorded

The hook uses workspace_roots[0] from Cursor. If you have multiple workspace roots, only the first will be used.

License

MIT

Credits

Created to bridge Cursor AI and Atuin shell history for a seamless command-line workflow.

{
"version": 1,
"hooks": {
"beforeShellExecution": [
{
"command": "./hooks/atuin-capture-cwd.sh"
}
],
"afterShellExecution": [
{
"command": "./hooks/atuin-history.sh"
}
]
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment