beachcomber 0.5.1

A centralized daemon that caches shell state (git, battery, hostname, etc.) so every consumer reads from one fast cache instead of independently forking shells
Documentation
---
sidebar_position: 1
---

# Shell

Shell prompts run on every keypress that produces a new prompt line. If your prompt calls `git status`, `kubectl config current-context`, or similar tools, you are forking a process — often several — dozens of times per minute. On a busy workstation with many terminal windows open, this adds up quickly and causes visible prompt lag.

Beachcomber solves this by keeping those values in a shared cache updated by a background daemon. Your prompt calls `comb g`, which reads from that cache over a Unix socket in roughly 34 microseconds. The underlying tool (git, kubectl, etc.) runs on a timer in the background, not on your prompt's critical path.

## Prerequisites

Before adding shell integration, make sure `comb` is installed and on your `PATH`. Run `comb --version` to confirm.

The daemon is socket-activated — it starts automatically the first time your prompt runs `comb g`, and shuts down after an idle period. You do not need to launch it manually. At any point, run `comb s` to confirm the daemon is reachable and see cache statistics.

## zsh

### Where to put it

Add the `precmd` function to your `~/.zshrc`. The `precmd` hook runs after each command completes, just before zsh draws the next prompt — making it the right place to refresh prompt variables.

### Setup

```zsh
# ~/.zshrc
# comb g returns plain text by default. g = get, no suffix needed.
# See /docs/reference/cli-commands for all shorthands.
precmd() {
    local branch dirty untracked
    branch=$(comb g git.branch . 2>/dev/null)
    dirty=$(comb g git.dirty . 2>/dev/null)
    untracked=$(comb g git.untracked . 2>/dev/null)

    local git_part=""
    if [[ -n "$branch" ]]; then
        git_part="%F{blue}${branch}%f"
        [[ "$dirty" == "true" ]] && git_part+="*"
        [[ "$untracked" -gt 0 ]] && git_part+="?"
        git_part+=" "
    fi

    PS1="${git_part}%F{green}%~%f %# "
}
```

The `.` argument passed to `comb g git.branch .` tells the daemon to scope the lookup to the current directory. Path-scoped providers like `git` return data relative to the nearest repository root from that path. Always pass `.` (or an explicit path) when querying providers that are directory-aware.

### How to reload

```zsh
source ~/.zshrc
```

### Expected result

Open a new terminal in a git repository. Your prompt should show the current branch name. Move into a directory with uncommitted changes and the `*` indicator should appear. In a directory outside any repository the git portion of the prompt will be absent.

## bash

### Where to put it

Add the function and `PROMPT_COMMAND` assignment to your `~/.bashrc`. bash evaluates `PROMPT_COMMAND` before drawing each prompt, equivalent to zsh's `precmd`.

### Setup

```bash
# ~/.bashrc
# comb g.s is shorthand for `comb get -f shell`. g = get, .s = shell format (key=value output).
__beachcomber_prompt() {
    # Fetch entire git state in one query, parse key=value output
    local git_state
    git_state=$(comb g.s git . 2>/dev/null)

    local branch dirty
    while IFS='=' read -r key value; do
        case "$key" in
            branch) branch="$value" ;;
            dirty)  dirty="$value" ;;
        esac
    done <<< "$git_state"

    local git_part=""
    [[ -n "$branch" ]] && git_part="(${branch}${dirty:+*}) "

    local kube
    kube=$(comb g kubecontext.context 2>/dev/null)
    local kube_part=""
    [[ -n "$kube" ]] && kube_part="[${kube}] "

    PS1="${kube_part}${git_part}\w \$ "
}

PROMPT_COMMAND=__beachcomber_prompt
```

The bash example queries the entire `git` namespace at once with `comb g.s git .`, which returns all fields as `key=value` lines. Parsing that single response is more efficient than making one `comb g` call per field. The `kubecontext.context` query has no path argument because the current Kubernetes context is global, not directory-scoped.

### How to reload

```bash
source ~/.bashrc
```

### Expected result

Open a new terminal. In a git repository the prompt shows `(branch) ~/path/to/repo $`. If your kubeconfig has a current context set, it appears as `[context-name]` before the git section. Outside a repository the git section is omitted entirely.

## fish

### Where to put it

Create or replace `~/.config/fish/functions/fish_prompt.fish`. fish auto-loads this file and calls `fish_prompt` before each prompt. If you already have a custom prompt, merge the beachcomber calls into your existing function.

### Setup

```fish
# ~/.config/fish/functions/fish_prompt.fish
function fish_prompt
    set -l branch (comb g git.branch . 2>/dev/null)
    set -l dirty (comb g git.dirty . 2>/dev/null)
    set -l battery (comb g battery.percent 2>/dev/null)

    set -l git_info ""
    if test -n "$branch"
        set git_info " $branch"
        test "$dirty" = "true"; and set git_info "$git_info*"
    end

    set -l bat_info ""
    if test -n "$battery"
        set bat_info " $battery%%"
    end

    echo -n (set_color blue)(prompt_pwd)(set_color normal)$git_info$bat_info" > "
end
```

fish has no subshell penalty for command substitutions — each `(comb g ...)` call is still a socket round trip, but there is no fork overhead. The `battery.percent` field has no path argument because battery state is a machine-level value.

### How to reload

```fish
source ~/.config/fish/functions/fish_prompt.fish
```

Or open a new terminal. fish reloads function files automatically at startup.

### Expected result

Your prompt shows the current directory in blue, the git branch if present, a `*` if the working tree is dirty, and the battery percentage if the `battery` provider is enabled in your config.

## Testing your integration

Before restarting your shell, verify that the daemon is returning data:

```sh
# Confirm the daemon is reachable
comb s

# Query a path-scoped provider from within a git repo
comb g git.branch .

# Query a global provider
comb g kubecontext.context
```

If both commands return values, reload your shell config or open a new terminal. Your prompt should immediately reflect live data without any noticeable delay.

To confirm data freshness, make a change in a repository (stage a file) and press Enter at the prompt. The dirty indicator should appear within one polling cycle (default: a few seconds).

## Performance

Each `comb g` call returns a cached value over a Unix socket. Typical round-trip time is around 34 microseconds. Compare that to the tools beachcomber replaces:

| Source | Typical cost |
|---|---|
| `comb g` (cached) | ~34 µs |
| `git rev-parse --abbrev-ref HEAD` | 5–15 ms |
| `kubectl config current-context` | 50–150 ms |

A prompt that used to call `git` and `kubectl` directly could take 60–170 ms to draw. With beachcomber it takes under 1 ms. The difference is most noticeable when switching between many terminal tabs quickly or working over a slow filesystem.

The daemon handles the expensive work on its own schedule, independent of your prompt.

## Provided Scripts

The repository ships two curl-able integration scripts in `scripts/`:

- **`scripts/polyfill.sh`** — Defines a `comb()` shell function that stands in for the real binary. If comb is installed, the script does nothing. If comb is not installed, it handles `comb g <key>` calls by falling back to native tools (git, hostname, uptime, etc.) for known keys. This lets integrations write `comb g git.branch .` and have it work everywhere — with or without beachcomber. See the [Polyfill]../integrating/polyfill.md guide for the full key list.

- **`scripts/chpwd.sh`** — A directory-change hook that pokes path-scoped providers (`git`, `mise`, `terraform`, `python`, `direnv`, `asdf`) in the background whenever the working directory changes. Warms the cache before your first prompt renders in a new directory. Supports zsh (`chpwd`), bash (`PROMPT_COMMAND`), and fish (separate config file). No-op if comb is not installed.

```sh
# Install via curl (bash/zsh)
source <(curl -fsSL https://beachcomber.sh/scripts/polyfill.sh)
source <(curl -fsSL https://beachcomber.sh/scripts/chpwd.sh)

# Or download and source from a local path
curl -fsSL https://beachcomber.sh/scripts/polyfill.sh -o ~/.local/share/beachcomber/polyfill.sh
source ~/.local/share/beachcomber/polyfill.sh
```

## Troubleshooting

- **Prompt shows no dynamic data:** the `2>/dev/null` in the examples silences errors, so if the daemon is not running you get an empty prompt with no error message. Remove `2>/dev/null` temporarily to see what `comb g` reports.
- **Wrong branch after switching:** the cache updates on its next poll cycle. Run `comb r git .` to force an immediate refresh.

See the [Troubleshooting](./troubleshooting.md) guide for general diagnostics.