Hash

Harness Assisted SHell — A Go-based shell with editor-style input, Helix keybindings, and agent-agnostic AI integration.
⚠️ Early Stage Project
Hash is a heavily vibe-coded experiment built mostly by Claude. Expect bugs, rough edges, and breaking changes. This is a personal project shaped around my own preferences—not a polished tool for everyone. If that sounds fun, welcome aboard.
What is Hash?
Hash is a shell that treats AI as a first-class citizen without locking you into any particular model or vendor. Drop ?? anywhere in a command to get help:
# Generate a command from natural language
?? find all Go files modified today
→ find . -name "*.go" -mtime 0
[Enter: run] [Tab: edit] [Esc: cancel]
# Pipe output through the agent
kubectl get pods -o json | ?? extract names and status
→ jq -r '.items[] | "\(.metadata.name) \(.status.phase)"'
# Fill just one argument
git log --format=?? oneline with hash
→ git log --format="%h %s"
No mode switching. No special commands. Just ?? where you need help.
Features
Agent Agnostic
Use any AI that speaks ACP or HTTP. Configure Claude Code for powerful cloud AI, or Ollama for fully local inference:
[agent.claude]
transport = "stdio"
command = "claude"
[agent.ollama]
transport = "http"
url = "http://localhost:11434/api/generate"
model = "codellama:13b"
Adaptive Learning
Hash learns from how you fix errors. After seeing you run chmod +x a few times after "Permission denied", it suggests the fix instantly — no agent call needed:
$ ./deploy.sh
bash: ./deploy.sh: Permission denied
✗ Exit 126 │ Learned fix available
→ chmod +x ./deploy.sh (worked 12 times)
[Enter: run] [Tab: edit] [?: ask agent] [Esc: dismiss]
Context Picker (Ctrl+P)
Control exactly what context goes to the agent with an interactive TUI:
- Press Ctrl+P to open the context picker
- Toggle items with Space, select all with a, none with n
- Auto-detected context (cwd, git branch, kube context) is pre-selected
- Add history commands, environment variables, or last output as needed
- Your selections persist for the shell session
- Press Enter to confirm, Esc to cancel
┌─ Context for Agent ─────────────────────────────────────┐
│ ▸ History [3 items] │
│ [✓] kubectl get pods -n staging │
│ [✓] docker ps --format "{{.Names}}" │
│ [ ] cd ~/projects/hash │
│ │
│ ▸ Environment [2 items] │
│ [✓] KUBECONFIG=/Users/me/.kube/config │
│ [ ] AWS_PROFILE=production │
├─────────────────────────────────────────────────────────┤
│ Context: ████████░░░░░░ 2.1 KB / ~8 KB recommended │
└─────────────────────────────────────────────────────────┘
The prompt behaves like a code editor, not a line input. Multiline editing, visual selection, and full keyboard navigation — just like Warp:
$ docker run -d \
--name myapp \
-p 8080:80 \
-v $(pwd):/app \
myimage:latest
│ ← visual gutter shows editable area
- Multiline native — Enter inserts newlines, Escape → Enter submits (in Helix mode)
- Visual selection — Select text with keyboard, yank/paste with system clipboard
- Helix keybindings — Selection-first editing:
x selects line, w extends to word, d deletes selection
- Universal shortcuts — Ctrl+A/E, Alt+arrows work everywhere
- Sub-millisecond latency — Raw terminal I/O, feels identical to readline
[input]
keybindings = "helix" # or "emacs" (coming soon)
gutter = true # show visual indicator
Tab Completion
Smart completions with intelligent fallback:
- Tool-native (10-200ms) — Cobra
__complete for kubectl, docker, helm, etc.
- Aliases & functions (<5ms) — Your defined aliases and shell functions (ƒ icon)
- Environment variables —
$VAR and ${VAR} expansion with value preview (sensitive values like API_KEY are masked)
- Executables — Commands from your PATH
- Filesystem (<10ms) — Paths, globs, hidden file toggle
- Agent-assisted (200-800ms) — When you need AI help mid-command
Rich History with Full-Text Search
SQLite-backed history with sudo tracking, agent interaction recall, and FTS5 search:
$ history search "kubectl delete"
$ history failed # commands that failed
$ history sudo # privileged commands
$ history asked "docker" # what you asked the AI
Starship Prompt Support
Use your existing Starship configuration, or Hash's built-in Lipgloss-powered engine with Powerline support:
[prompt]
mode = "starship" # or "built-in"
Clipboard Integration
Copy commands and output without reaching for the mouse:
| Key |
Action |
Alt+c |
Copy last command |
Alt+o |
Copy last output |
Alt+Shift+c |
Copy command + output |
Progress Bars for Long Commands
Hash shows progress indication (OSC 9;4) in supporting terminals when commands run longer than 2 seconds. Works with Windows Terminal, ConEmu, and iTerm2.
Shell Integration
Hash emits standard shell integration escape sequences (OSC 133) supported by modern terminals. This enables:
- Prompt navigation — Jump between commands with Cmd+Up/Down (iTerm2) or Ctrl+Shift+Z/X (Kitty/Ghostty)
- Output selection — Triple-click or Cmd+click to select entire command output
- Directory inheritance — New tabs/panes open in the current working directory
- Smart resizing — Prompts redraw cleanly when terminal is resized
Shell integration works automatically with no configuration required. Supported terminals include iTerm2, Ghostty, Kitty, WezTerm, and Windows Terminal.
Configurable Builtins
Disable built-in commands to use external alternatives like zoxide or eza:
[shell]
disable_builtins = ["cd"] # Let zoxide handle cd
Installation
Homebrew (recommended)
brew tap tfcace/hash
brew install hash
From Source
Requires Go 1.21+.
git clone https://github.com/tfcace/hash.git
cd hash
go build -o /usr/local/bin/hash ./cmd/hash
For development builds with version info:
./scripts/build.sh # Builds to ./hash
./scripts/build.sh --install # Builds to /usr/local/bin/hash
Development Setup
If you're developing Hash from source, you can optionally add these helper functions to ~/.hashrc:
# Point to your hash source directory
export HASH_SRC="$HOME/path/to/hash"
# Quick rebuild for testing (runs from working tree)
hash-rebuild() {
[[ -z "$HASH_SRC" ]] && echo "Set HASH_SRC to your hash source directory" && return 1
(cd "$HASH_SRC" && ./scripts/build.sh)
"$HASH_SRC/hash" --version
}
# Build and install to /usr/local/bin (stable location)
hash-upgrade() {
[[ -z "$HASH_SRC" ]] && echo "Set HASH_SRC to your hash source directory" && return 1
(cd "$HASH_SRC" && ./scripts/build.sh --install)
/usr/local/bin/hash --version
}
This lets you run hash-rebuild to quickly test changes, or hash-upgrade to install a new version system-wide.
Configuration
Hash uses TOML configuration at ~/.config/hash/config.toml:
[shell]
editor = "hx"
keybindings = "helix"
[agent]
default = "claude"
timeout = "30s"
[agent.claude]
transport = "stdio"
command = "claude"
[completions]
fuzzy = true
file_icons = true
[learning]
enabled = true
suggestion_threshold = 0.7
See docs/config-reference.md for the complete reference.
Using as Login Shell
After installing Hash (see Installation above), set it as your login shell:
# Add to /etc/shells
sudo sh -c 'echo $(which hash) >> /etc/shells'
# Change your login shell
chsh -s $(which hash)
Startup Files
Hash follows zsh-style conventions for startup files:
| Mode |
Environment |
Files Sourced |
| Login |
HASH_LOGIN=1 |
/etc/profile, ~/.profile, ~/.hash_profile |
| Interactive |
HASH_INTERACTIVE=1 |
~/.hashrc |
| Login+Interactive |
Both |
All of the above, in order |
| Non-interactive |
Neither |
init_commands only |
Recommended setup:
# ~/.hash_profile - login shell setup (PATH, env vars)
export PATH="$HOME/.local/bin:$PATH"
export EDITOR=hx
# ~/.hashrc - interactive shell setup (aliases, functions)
alias ll='ls -la'
alias g='git'
Session Markers
Hash sets these environment variables:
HASH_SHELL=1 — Always set
HASH_LOGIN=1 — Set for login shells
HASH_INTERACTIVE=1 — Set for interactive shells
Use these in your scripts to detect Hash:
# In ~/.profile
if [ "$HASH_SHELL" = "1" ]; then
# Hash-specific setup
fi
Migration from Bash/Zsh
When you first launch Hash, it detects your previous shell and offers to load compatible settings. Hash fully supports bash syntax including [[, ==, &&, ||, and process substitution.
Aliases are converted to functions: Hash converts alias foo='cmd1 && cmd2' to foo() { cmd1 && cmd2; } internally. This is transparent — you use the alias name normally, and it works as expected.
Run hash migrate to re-run migration or hash migrate status to see what was imported/skipped.
Architecture
hash/
├── cmd/hash/ # Main entry point
├── internal/
│ ├── parser/ # mvdan/sh wrapper + ?? detection
│ ├── executor/ # Command execution via PTY, job control
│ ├── readline/ # Input handling, completions, keybindings
│ ├── agent/ # ACP client with transport abstraction
│ ├── history/ # SQLite storage with FTS5, sudo tracking
│ ├── learning/ # Error pattern matching and fix scoring
│ ├── clipboard/ # Cross-platform copy command/output
│ ├── prompt/ # Starship integration + built-in engine
│ └── config/ # TOML configuration parsing
└── go.mod
Key Dependencies
- mvdan.cc/sh — POSIX shell parser/interpreter
- charmbracelet/bubbletea — TUI framework
- charmbracelet/lipgloss — Styling
- creack/pty — PTY handling
- mattn/go-sqlite3 — History storage
Development
go build -o hash ./cmd/hash # Build
go test ./... # Run all tests
go vet ./... # Lint
Fuzz Testing
The parser and learning system have fuzz tests to catch edge cases:
# Run fuzz tests (30 seconds each)
go test -fuzz=FuzzParse -fuzztime=30s ./internal/parser/...
go test -fuzz=FuzzNormalizeError -fuzztime=30s ./internal/learning/...
# Run all fuzz targets
go test -fuzz=Fuzz -fuzztime=30s ./internal/...
Fuzz tests run automatically in CI on PRs that modify parser or learning code.
Demo Recordings
Demo GIFs are generated using VHS:
# Install VHS
brew install vhs # or: go install github.com/charmbracelet/vhs@latest
# Generate all demos
for tape in demos/*.tape; do vhs "$tape"; done
See demos/README.md for available demos.
This project uses jj (Jujutsu) for version control.
Advanced
Debug Environment Variables
These environment variables are for debugging and advanced use cases:
| Variable |
Description |
Default |
HASH_TRACE |
Enable shell tracing. Comma-separated subsystems: editor, shell, agent, parser, or all. |
Disabled |
HASH_TRACE_PATH |
Path for trace log file (JSONL format). |
./hash-trace.jsonl |
HASH_TRACE_LEVEL |
Trace verbosity: verbose, detailed, or high. |
verbose |
HASH_CLIPBOARD_MAX_OUTPUT_SIZE |
Maximum output capture per command. Accepts sizes like 1MB, 512KB, unlimited, or 0 to disable capture. |
1MB |
HASH_PTY_TRACE |
Enable PTY I/O tracing for debugging hangs. Set to 1 or true to enable. |
Disabled |
HASH_PTY_TRACE_PATH |
Path for PTY trace log file when tracing is enabled. |
./hash-pty-trace.log |
Example: Shell tracing
# Trace all subsystems
HASH_TRACE=all HASH_TRACE_PATH=/tmp/hash-trace.jsonl ./hash
# Trace only editor and shell with high-level events
HASH_TRACE=editor,shell HASH_TRACE_LEVEL=high ./hash
# View trace in real-time
tail -f /tmp/hash-trace.jsonl | jq .
Example: Debugging PTY issues
# Enable PTY tracing to diagnose hangs
export HASH_PTY_TRACE=1
export HASH_PTY_TRACE_PATH=/tmp/hash-pty.log
./hash
# After reproducing the issue, check the log
cat /tmp/hash-pty.log
Example: Adjust output capture
# Disable output capture entirely
export HASH_CLIPBOARD_MAX_OUTPUT_SIZE=0
# Capture unlimited output (use with caution)
export HASH_CLIPBOARD_MAX_OUTPUT_SIZE=unlimited
Status & Limitations
Hash is under active development. Here's what to expect:
- Tested on — macOS + Ghostty + Claude Code (with ACP) only. Other platforms, terminals, and agents may work but are untested.
- Stability — There will be bugs. Lots of them. File issues for anything broken.
- Performance — Built for responsiveness, not raw throughput. Go is fast enough for a shell, but this isn't a Rust rewrite of bash.
- Scope — This is shaped around my preferences (Helix keybindings, local-first, agent-agnostic). It may not fit yours.
- SSH — Not supported. Hash is designed for local terminal use only.
See Also
Warp
Warp pioneered the modern terminal experience with blocks, AI, and editor-style input. Hash borrows ideas from Warp but takes a different path:
- Warp is a macOS app; Hash is a cross-platform shell that runs in any terminal
- Warp has its own AI; Hash works with any agent (Claude, Ollama, Gemini, local models)
- Warp syncs to the cloud; Hash keeps everything local
- Warp is proprietary; Hash is open source
If you want a polished, integrated experience and don't mind the lock-in, Warp is excellent. If you want hackability and agent choice, that's what Hash is for.
Butterfish
Butterfish is an AI shell wrapper worth considering:
- Butterfish wraps your existing shell; Hash replaces it
- Butterfish uses OpenAI; Hash is agent-agnostic
- Butterfish has a "goal mode" for autonomous execution; Hash requires explicit confirmation
- Butterfish has no local learning; Hash learns from your error fixes over time
Choose Butterfish if you want a quick overlay without switching shells, or prefer autonomous AI execution.
Choose Hash if you want deeper integration, agent flexibility, or local-first data.
License
MIT