Initial commit
This commit is contained in:
18
.claude-plugin/plugin.json
Normal file
18
.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "atuin",
|
||||||
|
"description": "Atuin shell history integration - search, capture, and manage bash command history with AI",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "Nathan Vale",
|
||||||
|
"email": "hi@nathanvale.com"
|
||||||
|
},
|
||||||
|
"skills": [
|
||||||
|
"./skills"
|
||||||
|
],
|
||||||
|
"commands": [
|
||||||
|
"./commands"
|
||||||
|
],
|
||||||
|
"hooks": [
|
||||||
|
"./hooks"
|
||||||
|
]
|
||||||
|
}
|
||||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# atuin
|
||||||
|
|
||||||
|
Atuin shell history integration - search, capture, and manage bash command history with AI
|
||||||
20
commands/history.md
Normal file
20
commands/history.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
description: [search-query] - Search bash command history using Atuin
|
||||||
|
---
|
||||||
|
|
||||||
|
# Search Bash History
|
||||||
|
|
||||||
|
Search through your bash command history using Atuin. This command helps you find previously executed commands with their exit codes and timestamps.
|
||||||
|
|
||||||
|
## Your Task
|
||||||
|
|
||||||
|
Search the bash history for commands matching: $ARGUMENTS
|
||||||
|
|
||||||
|
Use the `mcp__bash-history__search_history` tool to search for matching commands. If no search query is provided, use `mcp__bash-history__get_recent_history` to show recent commands.
|
||||||
|
|
||||||
|
Display the results clearly, showing:
|
||||||
|
- The command that was executed
|
||||||
|
- Whether it succeeded or failed (exit code)
|
||||||
|
- When it was run
|
||||||
|
|
||||||
|
If the user is looking for a specific workflow or pattern, help them identify the relevant commands and explain what they do.
|
||||||
166
hooks/atuin-post-tool.sh
Executable file
166
hooks/atuin-post-tool.sh
Executable file
@@ -0,0 +1,166 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# PostToolUse Hook: Atuin Integration
|
||||||
|
# Captures Bash commands executed by Claude Code and adds them to:
|
||||||
|
# - Atuin history (with metadata tags)
|
||||||
|
# - Zsh history (fallback)
|
||||||
|
#
|
||||||
|
# Enable debug logging: export CLAUDE_ATUIN_DEBUG=1
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Check for jq dependency
|
||||||
|
if ! command -v jq >/dev/null 2>&1; then
|
||||||
|
echo "Warning: 'jq' is not installed. Atuin hook skipping." >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
DEBUG="${CLAUDE_ATUIN_DEBUG:-0}"
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
LOG_FILE="${SCRIPT_DIR}/atuin-hook.log"
|
||||||
|
# Respect HISTFILE or fallback to standard locations
|
||||||
|
HISTORY_FILE="${HISTFILE:-${HOME}/.zsh_history}"
|
||||||
|
if [[ ! -f "$HISTORY_FILE" && -f "${HOME}/.bash_history" ]]; then
|
||||||
|
HISTORY_FILE="${HOME}/.bash_history"
|
||||||
|
fi
|
||||||
|
# Context log for git branch and session tracking
|
||||||
|
CONTEXT_FILE="${HOME}/.claude/atuin-context.jsonl"
|
||||||
|
|
||||||
|
# Helper: Log debug messages
|
||||||
|
debug_log() {
|
||||||
|
if [[ "$DEBUG" == "1" ]]; then
|
||||||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Helper: Log errors (always logged)
|
||||||
|
error_log() {
|
||||||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" >> "$LOG_FILE"
|
||||||
|
echo "[Atuin Hook Error] $*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
# Helper: Write context to JSONL for later searching by git branch/session
|
||||||
|
write_context() {
|
||||||
|
local cmd="$1"
|
||||||
|
local session="$2"
|
||||||
|
local cwd="$3"
|
||||||
|
|
||||||
|
# Capture git branch (fast, fails gracefully outside git repos)
|
||||||
|
local git_branch=""
|
||||||
|
git_branch=$(git -C "$cwd" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
# Ensure context directory exists
|
||||||
|
mkdir -p "$(dirname "$CONTEXT_FILE")"
|
||||||
|
|
||||||
|
# Get ISO timestamp
|
||||||
|
local timestamp
|
||||||
|
timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||||
|
|
||||||
|
# Escape command for JSON (replace quotes and newlines)
|
||||||
|
local escaped_cmd
|
||||||
|
escaped_cmd=$(printf '%s' "$cmd" | sed 's/\\/\\\\/g; s/"/\\"/g' | tr '\n' ' ')
|
||||||
|
|
||||||
|
# Write JSONL entry (lightweight, no jq dependency for writing)
|
||||||
|
printf '{"ts":"%s","cmd":"%s","branch":"%s","session":"%s","cwd":"%s"}\n' \
|
||||||
|
"$timestamp" \
|
||||||
|
"$escaped_cmd" \
|
||||||
|
"$git_branch" \
|
||||||
|
"$session" \
|
||||||
|
"$cwd" \
|
||||||
|
>> "$CONTEXT_FILE"
|
||||||
|
|
||||||
|
debug_log "Wrote context: branch=$git_branch, session=$session"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main execution
|
||||||
|
main() {
|
||||||
|
debug_log "=== Hook started ==="
|
||||||
|
|
||||||
|
# Read JSON input from stdin
|
||||||
|
INPUT=$(cat)
|
||||||
|
debug_log "Received input: $INPUT"
|
||||||
|
|
||||||
|
# Parse JSON fields
|
||||||
|
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
|
||||||
|
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
|
||||||
|
EXIT_CODE=$(echo "$INPUT" | jq -r '.tool_response.exit_code // 0')
|
||||||
|
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
|
||||||
|
# Get working directory from tool input, or fall back to current directory
|
||||||
|
CWD=$(echo "$INPUT" | jq -r '.cwd // empty')
|
||||||
|
if [[ -z "$CWD" ]]; then
|
||||||
|
CWD="$(pwd)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
debug_log "Tool: $TOOL_NAME, Exit: $EXIT_CODE, CWD: $CWD"
|
||||||
|
|
||||||
|
# Filter: Only process Bash tool calls
|
||||||
|
if [[ "$TOOL_NAME" != "Bash" ]]; then
|
||||||
|
debug_log "Skipping non-Bash tool: $TOOL_NAME"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Append to history file if it exists
|
||||||
|
if [[ -f "$HISTORY_FILE" ]]; then
|
||||||
|
# Format depends on shell, simple append for now
|
||||||
|
echo "$COMMAND" >> "$HISTORY_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate command exists
|
||||||
|
if [[ -z "$COMMAND" ]]; then
|
||||||
|
debug_log "No command found in tool_input"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
debug_log "Processing command: $COMMAND"
|
||||||
|
|
||||||
|
# Use atuin's native history tracking for rich metadata
|
||||||
|
# This captures: command, exit code, duration, timestamp, cwd, hostname
|
||||||
|
|
||||||
|
# Helper function to add to shell history as fallback
|
||||||
|
add_to_shell_history() {
|
||||||
|
local timestamp
|
||||||
|
timestamp=$(date +%s)
|
||||||
|
if [[ -f "$HISTORY_FILE" ]]; then
|
||||||
|
local escaped_cmd
|
||||||
|
escaped_cmd=$(echo "$COMMAND" | tr '\n' ';' | sed 's/\\/\\\\/g')
|
||||||
|
echo ": ${timestamp}:0;${escaped_cmd}" >> "$HISTORY_FILE"
|
||||||
|
debug_log "Fallback: Added to shell history"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Step 1: Start history entry and capture the ID
|
||||||
|
# Wrap in conditional to handle atuin not being available
|
||||||
|
HISTORY_ID=""
|
||||||
|
if ! HISTORY_ID=$(atuin history start -- "$COMMAND" 2>&1); then
|
||||||
|
debug_log "Atuin history start failed, using fallback"
|
||||||
|
add_to_shell_history
|
||||||
|
# Still write context even when using fallback
|
||||||
|
write_context "$COMMAND" "$SESSION_ID" "$CWD"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
debug_log "Started atuin history entry: $HISTORY_ID"
|
||||||
|
|
||||||
|
# Step 2: End history entry with exit code and duration
|
||||||
|
# Duration is set to 0 since we don't track execution time in post-hook
|
||||||
|
if atuin history end --exit "$EXIT_CODE" --duration 0 "$HISTORY_ID" 2>&1; then
|
||||||
|
debug_log "Added to atuin with exit code: $EXIT_CODE"
|
||||||
|
else
|
||||||
|
error_log "Failed to end atuin history entry"
|
||||||
|
add_to_shell_history
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 3: Write context (git branch, session ID) for later searching
|
||||||
|
write_context "$COMMAND" "$SESSION_ID" "$CWD"
|
||||||
|
|
||||||
|
debug_log "=== Hook completed successfully ==="
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run main function with error handling
|
||||||
|
if ! main; then
|
||||||
|
error_log "Hook execution failed"
|
||||||
|
exit 0 # Always exit 0 to not block Claude Code
|
||||||
|
fi
|
||||||
17
hooks/hooks.json
Normal file
17
hooks/hooks.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"description": "Atuin shell history integration hooks",
|
||||||
|
"hooks": {
|
||||||
|
"PostToolUse": [
|
||||||
|
{
|
||||||
|
"matcher": "Bash",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/atuin-post-tool.sh",
|
||||||
|
"timeout": 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
57
plugin.lock.json
Normal file
57
plugin.lock.json
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
{
|
||||||
|
"$schema": "internal://schemas/plugin.lock.v1.json",
|
||||||
|
"pluginId": "gh:nathanvale/side-quest-marketplace:plugins/atuin",
|
||||||
|
"normalized": {
|
||||||
|
"repo": null,
|
||||||
|
"ref": "refs/tags/v20251128.0",
|
||||||
|
"commit": "fb0b8de5a1d8fae022dabb217d647aedcc64629b",
|
||||||
|
"treeHash": "83357d796690bd4a0651e7a2274a727b53b659fbd02464091f035fbba3f2c4ea",
|
||||||
|
"generatedAt": "2025-11-28T10:27:14.359524Z",
|
||||||
|
"toolVersion": "publish_plugins.py@0.2.0"
|
||||||
|
},
|
||||||
|
"origin": {
|
||||||
|
"remote": "git@github.com:zhongweili/42plugin-data.git",
|
||||||
|
"branch": "master",
|
||||||
|
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
|
||||||
|
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
|
||||||
|
},
|
||||||
|
"manifest": {
|
||||||
|
"name": "atuin",
|
||||||
|
"description": "Atuin shell history integration - search, capture, and manage bash command history with AI",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "README.md",
|
||||||
|
"sha256": "c326c27c924507b7a43cf13404d16527564433e06a38497ad72c68dd168df7d5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "hooks/hooks.json",
|
||||||
|
"sha256": "e42f15163fd07733633fd649a73bc8332d54d798042db6f424df6ceab9a16499"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "hooks/atuin-post-tool.sh",
|
||||||
|
"sha256": "cb52ae1dfdb4c0ef5f1e57ec73c36cfacd4dd41c7d02c5cca685e15942dfdb75"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": ".claude-plugin/plugin.json",
|
||||||
|
"sha256": "8debb6a34c0aa84591e8f9707941e0830a8a70d1eb96678e8e72385d2abbd226"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "commands/history.md",
|
||||||
|
"sha256": "89a23cd138a251be974fc499f130947fbd8cb8655af59ff43938ef359646c19a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/bash-history/SKILL.md",
|
||||||
|
"sha256": "83cb09bb758617270a73df25d1e5750a1fc8d6ad2cc3152854f3e6a06ad148cd"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dirSha256": "83357d796690bd4a0651e7a2274a727b53b659fbd02464091f035fbba3f2c4ea"
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"scannedAt": null,
|
||||||
|
"scannerVersion": null,
|
||||||
|
"flags": []
|
||||||
|
}
|
||||||
|
}
|
||||||
48
skills/bash-history/SKILL.md
Normal file
48
skills/bash-history/SKILL.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
name: bash-history
|
||||||
|
description: Search and retrieve bash command history using Atuin. Use when users ask about commands they've run before, want to find a specific command, recall how they did something previously, or ask "how did I..." or "what command did I use to..."
|
||||||
|
---
|
||||||
|
|
||||||
|
# Bash History Skill
|
||||||
|
|
||||||
|
Access bash command history through Atuin to search for and retrieve previously executed commands.
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
|
||||||
|
### mcp__bash-history__search_history
|
||||||
|
|
||||||
|
Search for commands matching a query.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `query` (string): Search term to find matching commands
|
||||||
|
- `limit` (number, default: 10): Maximum results to return
|
||||||
|
- `include_failed` (boolean, default: false): Include failed commands
|
||||||
|
|
||||||
|
### mcp__bash-history__get_recent_history
|
||||||
|
|
||||||
|
Get the most recent commands.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `limit` (number, default: 10): Number of recent commands
|
||||||
|
- `include_failed` (boolean, default: false): Include failed commands
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
When user asks: "How did I deploy last time?"
|
||||||
|
```
|
||||||
|
Use mcp__bash-history__search_history with query "deploy"
|
||||||
|
```
|
||||||
|
|
||||||
|
When user asks: "What commands did I run recently?"
|
||||||
|
```
|
||||||
|
Use mcp__bash-history__get_recent_history with limit 20
|
||||||
|
```
|
||||||
|
|
||||||
|
When user asks: "Show me failed git commands"
|
||||||
|
```
|
||||||
|
Use mcp__bash-history__search_history with query "git" and include_failed true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
Results include command text, exit code (0 = success), and timestamp. Present clearly and offer to help reuse commands.
|
||||||
Reference in New Issue
Block a user