From 08b379a69793a54fe8b712061e8724252ea7ebdb Mon Sep 17 00:00:00 2001 From: Zhongwei Li Date: Sun, 30 Nov 2025 08:41:54 +0800 Subject: [PATCH] Initial commit --- .claude-plugin/plugin.json | 18 ++++ README.md | 3 + commands/history.md | 20 +++++ hooks/atuin-post-tool.sh | 166 +++++++++++++++++++++++++++++++++++ hooks/hooks.json | 17 ++++ plugin.lock.json | 57 ++++++++++++ skills/bash-history/SKILL.md | 48 ++++++++++ 7 files changed, 329 insertions(+) create mode 100644 .claude-plugin/plugin.json create mode 100644 README.md create mode 100644 commands/history.md create mode 100755 hooks/atuin-post-tool.sh create mode 100644 hooks/hooks.json create mode 100644 plugin.lock.json create mode 100644 skills/bash-history/SKILL.md diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..a74e9ba --- /dev/null +++ b/.claude-plugin/plugin.json @@ -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" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a55c7a6 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# atuin + +Atuin shell history integration - search, capture, and manage bash command history with AI diff --git a/commands/history.md b/commands/history.md new file mode 100644 index 0000000..3a363a4 --- /dev/null +++ b/commands/history.md @@ -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. diff --git a/hooks/atuin-post-tool.sh b/hooks/atuin-post-tool.sh new file mode 100755 index 0000000..c4066fa --- /dev/null +++ b/hooks/atuin-post-tool.sh @@ -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 diff --git a/hooks/hooks.json b/hooks/hooks.json new file mode 100644 index 0000000..1b26c4f --- /dev/null +++ b/hooks/hooks.json @@ -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 + } + ] + } + ] + } +} diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..663fecc --- /dev/null +++ b/plugin.lock.json @@ -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": [] + } +} \ No newline at end of file diff --git a/skills/bash-history/SKILL.md b/skills/bash-history/SKILL.md new file mode 100644 index 0000000..1b7f597 --- /dev/null +++ b/skills/bash-history/SKILL.md @@ -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.