Skip to main content

Powerlevel10k

Introduction​

powerlevel10k (p10k) is one of the fastest zsh themes. It uses gitstatus — a purpose-built C++ daemon — for git information, tightly integrated into its async rendering pipeline. beachcomber can replace gitstatus as the git data source, consolidating all shell state queries into a single daemon instead of running two.

beachcomber also provides cached data for things p10k does not cover natively, such as mise project tool versions.

There are two levels of integration:

  • Complementary segments — add beachcomber-backed segments alongside p10k's built-in ones
  • Full git replacement — replace gitstatus entirely with a beachcomber-backed segment

Prerequisites​

  • beachcomber installed and the daemon running (comb s should succeed)
  • powerlevel10k installed and configured (p10k configure has been run at least once)
  • ~/.p10k.zsh exists (generated by the configuration wizard)

The durability problem​

~/.p10k.zsh is regenerated when you run p10k configure. Any edits to that file will be overwritten. beachcomber integration must survive regeneration.

The solution is an overlay file — a separate zsh script sourced immediately after ~/.p10k.zsh. It patches the prompt element arrays and defines custom segment functions. When p10k configure regenerates ~/.p10k.zsh, the overlay re-applies its changes on next shell startup. The generated file is never modified.

Setting up the overlay​

Create ~/.config/zsh/beachcomber-p10k.zsh (or any path you prefer) and source it in your .zshrc immediately after the p10k config:

# .zshrc
source ~/.p10k.zsh
source ~/.config/zsh/beachcomber-p10k.zsh

All beachcomber segment definitions and array patches go in the overlay file. The rest of this guide shows what to put in it.

Complementary segments​

These segments add data that p10k does not provide natively. They work alongside p10k's built-in segments without replacing anything.

Kubernetes context​

# comb g returns plain text by default. g = get, no suffix needed.
function prompt_bc_kube() {
local ctx=$(comb g kubecontext.context 2>/dev/null)
[[ -n "$ctx" ]] || return
p10k segment -f cyan -i '☸' -t "$ctx"
}

AWS profile​

function prompt_bc_aws() {
local profile=$(comb g aws.profile 2>/dev/null)
[[ -n "$profile" ]] || return
p10k segment -f yellow -t "$profile"
}

The beachcomber AWS provider detects profiles from both $AWS_PROFILE (native CLI, granted) and $AWS_VAULT (aws-vault), with session expiration tracking via $AWS_CREDENTIAL_EXPIRATION.

GCloud project​

function prompt_bc_gcloud() {
local project=$(comb g gcloud.project 2>/dev/null)
[[ -n "$project" ]] || return
p10k segment -f blue -t "$project"
}

mise project tools​

Shows tool versions that are overridden at the project level (from a local mise.toml), distinguishing them from the global baseline. Only appears in directories with a project-level mise config.

typeset -g POWERLEVEL9K_COMB_MISE_FOREGROUND=147
typeset -g POWERLEVEL9K_COMB_MISE_VISUAL_IDENTIFIER_EXPANSION=$'\uF07C' # folder-open icon

function prompt_comb_mise() {
local cwd=${(%):-%/}
local project
project=$(comb g mise "$cwd" 2>/dev/null) || return
[[ -z "$project" ]] && return

local display=()
local entry tool ver
# `comb g mise "$cwd"` returns one `tool=version` line per project-scoped
# tool. Strip the mise backend prefix (e.g. `cargo:cargo-nextest` →
# `cargo-nextest`) for a tighter segment.
for entry in "${(@f)project}"; do
[[ -z "$entry" ]] && continue
tool=${entry%%=*}
tool=${tool##*:}
ver=${entry#*=}
display+=("${tool} ${ver}")
done
p10k segment -f 147 -i $'\uF07C' -t "${(j: | :)display}"
}

The beachcomber mise provider exposes one string field per tool name. Fields are scoped by query context:

  • comb g mise "$cwd" — project-scoped tools (from a local mise.toml); returns newline-separated tool=version lines
  • comb g mise (no path) — global tools (from ~/.config/mise/); same format
  • comb g mise.node "$cwd" — single tool version string, e.g. 20.11.0

Adding complementary segments to the prompt​

After defining the functions, patch the elements array in the overlay. This example inserts comb_mise before the time segment:

typeset -ga _comb_rpe=()
for _comb_e in "${POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS[@]}"; do
[[ "$_comb_e" == "time" ]] && _comb_rpe+=(comb_mise)
_comb_rpe+=("$_comb_e")
done
POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=("${_comb_rpe[@]}")
unset _comb_rpe _comb_e

For simpler cases, append to the end:

POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS+=(bc_kube bc_aws bc_gcloud)

Full git replacement​

This replaces p10k's gitstatus-backed vcs segment with a custom comb_git segment that reads all git state from beachcomber. When vcs is removed from the elements array, p10k does not start the gitstatus daemon — eliminating one long-running process from your system.

Why replace gitstatus?​

gitstatus is already fast. The motivation is architectural, not performance:

  • One daemon instead of two. gitstatus runs one C++ daemon per shell session. beachcomber runs one daemon shared across all shells, tmux panes, and other consumers. Fewer processes, less memory, shared cache.
  • Consistent data. The same git state that feeds your prompt also feeds your tmux status bar, neovim statusline, and any other beachcomber consumer. No divergence between tools.
  • Broader coverage. beachcomber's git provider includes fields that gitstatus does not expose, such as line-level diff stats (lines_added, lines_removed), stash counts, and the current tag.

Current limitations​

beachcomber's git provider now exposes commit_summary (the first line of the HEAD commit message) and push_ahead/push_behind (commits ahead of or behind the push remote). The comb_git segment above can be extended to use these fields. WIP detection and push indicator rendering require wiring them into the segment function — see the field table in Built-in Providers for the field names.

The comb_git segment​

Add this to your overlay file:

# Swap vcs for comb_git in the left prompt elements.
POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(${POWERLEVEL9K_LEFT_PROMPT_ELEMENTS[@]/vcs/comb_git})

# Colours matching the default vcs segment config
typeset -g POWERLEVEL9K_COMB_GIT_CLEAN_FOREGROUND=76
typeset -g POWERLEVEL9K_COMB_GIT_MODIFIED_FOREGROUND=178
typeset -g POWERLEVEL9K_COMB_GIT_UNTRACKED_FOREGROUND=76
typeset -g POWERLEVEL9K_COMB_GIT_VISUAL_IDENTIFIER_EXPANSION=
typeset -g POWERLEVEL9K_COMB_GIT_LOADING_VISUAL_IDENTIFIER_COLOR=244

function prompt_comb_git() {
local cwd=${(%):-%/}
local output
output=$(comb g git "$cwd" 2>/dev/null) || return
[[ -z "$output" ]] && return

# Parse key=value lines into an associative array
local -A g
local line
for line in ${(f)output}; do
local key=${line%%=*}
local val=${line#*=}
g[$key]=$val
done

[[ -z ${g[branch]} ]] && [[ -z ${g[commit]} ]] && return

# Determine state for foreground colour
local state=CLEAN
if (( ${g[unstaged]:-0} || ${g[conflicted]:-0} || ${g[staged]:-0} )); then
state=MODIFIED
elif (( ${g[untracked]:-0} )); then
state=UNTRACKED
fi

# Build display string matching p10k's default git formatting
local meta='%f'
local clean='%76F'
local modified='%178F'
local untracked='%39F'
local conflicted='%196F'

local res

# Branch name (truncate if > 32 chars)
if [[ -n ${g[branch]} ]]; then
local branch=${g[branch]}
(( $#branch > 32 )) && branch="${branch[1,12]}…${branch[-12,-1]}"
res+="${clean}${branch//\%/%%}"
elif [[ -n ${g[tag]} && ${g[tag]} != "null" ]]; then
local tag=${g[tag]}
(( $#tag > 32 )) && tag="${tag[1,12]}…${tag[-12,-1]}"
res+="${meta}#${clean}${tag//\%/%%}"
elif [[ -n ${g[commit]} ]]; then
res+="${meta}@${clean}${g[commit][1,8]}"
fi

# Upstream tracking branch (only if different from local)
if [[ -n ${g[upstream]} && ${g[upstream]} != "null" ]]; then
local remote_branch=${g[upstream]#*/}
if [[ -n ${remote_branch} && ${remote_branch} != ${g[branch]} ]]; then
res+="${meta}:${clean}${remote_branch//\%/%%}"
fi
fi

# Ahead/behind
if (( ${g[ahead]:-0} || ${g[behind]:-0} )); then
(( ${g[behind]:-0} )) && res+=" ${clean}⇣${g[behind]}"
(( ${g[ahead]:-0} && !${g[behind]:-0} )) && res+=" "
(( ${g[ahead]:-0} )) && res+="${clean}⇡${g[ahead]}"
fi

# Stashes
(( ${g[stash]:-0} )) && res+=" ${clean}*${g[stash]}"
# Merge/rebase state
[[ ${g[state]} != "clean" && -n ${g[state]} ]] && res+=" ${conflicted}${g[state]}"
# Conflicted
(( ${g[conflicted]:-0} )) && res+=" ${conflicted}~${g[conflicted]}"
# Staged
(( ${g[staged]:-0} )) && res+=" ${modified}+${g[staged]}"
# Unstaged
(( ${g[unstaged]:-0} )) && res+=" ${modified}!${g[unstaged]}"
# Untracked
(( ${g[untracked]:-0} )) && res+=" ${untracked}?${g[untracked]}"

p10k segment -s $state -t "$res"
}

The element substitution ${POWERLEVEL9K_LEFT_PROMPT_ELEMENTS[@]/vcs/comb_git} replaces vcs with comb_git in whatever array ~/.p10k.zsh defined. If p10k configure regenerates the file with vcs back in the array, the overlay swaps it out again on next shell startup.

Verifying gitstatus is not running​

After opening a new shell with the overlay active, confirm gitstatus is stopped:

pgrep -P $$ gitstatusd

This should return nothing. If it returns a PID, the vcs segment is still in the elements array — check that the overlay is being sourced after ~/.p10k.zsh.

First-prompt cache miss​

On the very first prompt after the daemon starts (or after switching to a new directory that has not been queried before), the git provider executes inline. The comb g call blocks briefly while the provider runs and returns the result directly — the segment will have data on that first prompt, with a short delay while git runs. Subsequent prompts return the cached value with no delay.

Once the provider has run for a given directory, the cache is kept warm by ongoing demand from prompt queries.

Customizing colours​

All beachcomber segments follow p10k's standard colour configuration. For a segment named comb_git, set:

typeset -g POWERLEVEL9K_COMB_GIT_FOREGROUND=76
typeset -g POWERLEVEL9K_COMB_GIT_CLEAN_FOREGROUND=76
typeset -g POWERLEVEL9K_COMB_GIT_MODIFIED_FOREGROUND=178

The -s $state argument in p10k segment selects which colour variant to use. States map to the _CLEAN_, _MODIFIED_, and _UNTRACKED_ colour keys.

Reverting​

To revert all beachcomber integration:

  1. Comment out or delete the source line for the overlay in .zshrc
  2. Restart zsh

The ~/.p10k.zsh file was never modified. The vcs segment will reappear and gitstatus will start again automatically.

To revert just the git replacement while keeping other segments, remove the POWERLEVEL9K_LEFT_PROMPT_ELEMENTS substitution line from the overlay. The vcs segment from ~/.p10k.zsh will take effect again.

Troubleshooting​

  • Segment not showing: verify the function name exactly matches prompt_<segment_name> and the segment name is in the PROMPT_ELEMENTS array. Run echo $POWERLEVEL9K_LEFT_PROMPT_ELEMENTS to check. A typo in either causes p10k to silently skip it.
  • Git segment slow on first prompt: on a cold cache the provider executes inline, which takes as long as a normal git invocation (typically a few milliseconds). This is a one-time cost per directory.
  • Overlay not taking effect: ensure the source line comes after the ~/.p10k.zsh source in .zshrc. The overlay must run second to patch the arrays.
  • gitstatus still running: check pgrep -P $$ gitstatusd. If it returns a PID, vcs is still in the elements array. The overlay may not be loaded — check type prompt_comb_git to see if the function exists.
  • Icons not rendering: the Nerd Font glyphs require a patched font. p10k's recommended fonts include all required glyphs. Replace icons with text labels if your font lacks them.
  • Changes lost after p10k configure: this is expected — p10k configure regenerates ~/.p10k.zsh. The overlay re-applies changes on next shell startup. Just open a new shell.

See the Troubleshooting guide for general diagnostics.