| 1 |
@{! variants = default, night } |
| 2 |
# Alloy starship configuration |
| 3 |
# |
| 4 |
# Cross-shell prompt per docs/STACK.md#shell. Two lines: context on the first, |
| 5 |
# the entry glyph alone on the second, so a typed command always starts at the |
| 6 |
# same column no matter how long the branch name or the path is. |
| 7 |
# |
| 8 |
# Palette drawn from docs/TOKENS.md, mapped to starship's color slots by |
| 9 |
# function rather than by name. Color is reserved for state per |
| 10 |
# DESIGN-LANGUAGE.md, which is why almost everything here is muted: the only |
| 11 |
# things that take a color are the ones saying something went wrong. |
| 12 |
# |
| 13 |
# THE RULE THIS FILE IS BUILT ON, stated 2026-08-14: every segment is a fact |
| 14 |
# somebody acts on, written as words rather than as pictures. |
| 15 |
# |
| 16 |
# What that ruled out, and why: |
| 17 |
# |
| 18 |
# - Nerd-font icons. A branch glyph in front of a branch name is the same fact |
| 19 |
# drawn twice, and it costs a font dependency to say it: shop has no font |
| 20 |
# fallback, so a glyph the font lacks draws as tofu rather than as nothing. |
| 21 |
# A prompt of words and ASCII cannot break that way. |
| 22 |
# - Language versions (rust, python, nodejs). Information, not useful |
| 23 |
# information. Inside a Rust repo it printed the toolchain version on every |
| 24 |
# line for a question nobody asks at the prompt, and `rustc -V` answers it |
| 25 |
# the moment they do. |
| 26 |
# |
| 27 |
# What it ruled in, all of them invisible until they are true: |
| 28 |
# |
| 29 |
# - The exit code as a number. A red glyph says a command failed; 1, 127, 130 |
| 30 |
# and 137 are four different next actions, and the glyph cannot tell them |
| 31 |
# apart. |
| 32 |
# - The container name. `alloy pkg` puts people inside distrobox, and a box |
| 33 |
# prompt was indistinguishable from the host. |
| 34 |
# - git_state. Mid-rebase is the most important thing on screen while it lasts. |
| 35 |
|
| 36 |
format = """ |
| 37 |
$username\ |
| 38 |
$hostname\ |
| 39 |
$container\ |
| 40 |
$directory\ |
| 41 |
$git_branch\ |
| 42 |
$git_state\ |
| 43 |
$git_status\ |
| 44 |
$line_break\ |
| 45 |
$character""" |
| 46 |
|
| 47 |
# The right prompt is a different subject from the left, and that is the whole |
| 48 |
# grouping mechanism: the left says where you are, the right says what just |
| 49 |
# happened. Exit code, duration and the time all belong to the command that |
| 50 |
# finished, so they sit together at the far end and nothing on the left has to |
| 51 |
# be separated from anything else by punctuation. |
| 52 |
right_format = """$status$cmd_duration$time""" |
| 53 |
|
| 54 |
# No blank line between prompts. The second line is already the separator, and |
| 55 |
# a blank one on top of it spends a third of a three-line prompt on nothing. |
| 56 |
add_newline = false |
| 57 |
|
| 58 |
# ------------------------------------------------------------------- |
| 59 |
# Who and where |
| 60 |
# ------------------------------------------------------------------- |
| 61 |
# Both were disabled here as "noise for Alloy's audience", on the reading that |
| 62 |
# a laptop user knows which machine they are sitting at. That did not survive |
| 63 |
# an installed test machine: two boxes gave the identical prompt and an ssh |
| 64 |
# session was indistinguishable from a local one. |
| 65 |
# |
| 66 |
# ssh_only stays off deliberately. Showing the host only over ssh keeps the |
| 67 |
# prompt silent in exactly the case where a machine is new and unfamiliar. |
| 68 |
[hostname] |
| 69 |
disabled = false |
| 70 |
ssh_only = false |
| 71 |
format = "[$hostname]($style) " |
| 72 |
style = "@{content.secondary}" |
| 73 |
|
| 74 |
[username] |
| 75 |
disabled = false |
| 76 |
show_always = true |
| 77 |
format = "[$user]($style)[@](@{content.muted})" |
| 78 |
# Not `style`. This module resolves `$style` to one of these two by who you |
| 79 |
# are, and a plain `style` key is silently ignored: starship warns and falls |
| 80 |
# back to its own bold yellow, which is off-palette and reads as a warning |
| 81 |
# about nothing. |
| 82 |
style_user = "@{content.muted}" |
| 83 |
# Root gets the danger color rather than the muted one. This is the one time |
| 84 |
# the prompt should be saying something about who you are. |
| 85 |
style_root = "@{status.danger}" |
| 86 |
|
| 87 |
# Which distrobox, blank on the host. No symbol: the name is the fact, and the |
| 88 |
# brackets are what make it read as an enclosure rather than as a word in the |
| 89 |
# path. |
| 90 |
[container] |
| 91 |
disabled = false |
| 92 |
format = "[\\[$name\\]]($style) " |
| 93 |
style = "@{status.warning}" |
| 94 |
|
| 95 |
# ------------------------------------------------------------------- |
| 96 |
# Directory — the one bold thing on the line |
| 97 |
# ------------------------------------------------------------------- |
| 98 |
[directory] |
| 99 |
style = "bold @{content.primary}" |
| 100 |
format = "[$path]($style)[$read_only]($read_only_style) " |
| 101 |
truncation_length = 4 |
| 102 |
truncation_symbol = "…/" |
| 103 |
truncate_to_repo = true |
| 104 |
# Was a nerd-font padlock. Two letters that are always in the font say it as |
| 105 |
# well and cannot arrive as tofu. |
| 106 |
read_only = " [ro]" |
| 107 |
read_only_style = "@{status.danger}" |
| 108 |
|
| 109 |
# ------------------------------------------------------------------- |
| 110 |
# Git — branch, then any operation in flight, then the working tree |
| 111 |
# ------------------------------------------------------------------- |
| 112 |
[git_branch] |
| 113 |
symbol = "" |
| 114 |
style = "@{content.secondary}" |
| 115 |
format = "[$branch]($style) " |
| 116 |
|
| 117 |
# Only ever on screen mid-operation, and then it is the thing that matters: |
| 118 |
# what you are in the middle of, and how far through. The parenthesised |
| 119 |
# progress is starship's optional syntax and collapses when there is none, so |
| 120 |
# a merge reads `merge` and a rebase reads `rebase 3/7`. |
| 121 |
[git_state] |
| 122 |
style = "@{status.warning}" |
| 123 |
format = "[$state( $progress_current/$progress_total)]($style) " |
| 124 |
|
| 125 |
# Count first, then a letter for what was counted: `2m 1u 3s 3a`. Reads as a |
| 126 |
# quantity with a unit, which is what it is. |
| 127 |
# |
| 128 |
# This replaced `!?+` with arrows, where the count was missing on the working |
| 129 |
# tree markers and present on the remote ones, nothing separated one marker |
| 130 |
# from the next, and `!` and `↑3` sat adjacent while being different kinds of |
| 131 |
# fact: one is your working tree, the other is your relationship to the remote. |
| 132 |
# `!↑3` was three characters that had to be learned rather than read. |
| 133 |
# |
| 134 |
# Each marker carries its own trailing space, because starship concatenates |
| 135 |
# these with no separator and `2m1u3s` is the run-together problem again. |
| 136 |
# |
| 137 |
# `st` for stashed rather than a second `s`, which staged already has. It is |
| 138 |
# the one marker here that is not about the working tree, and the extra letter |
| 139 |
# is cheaper than the collision. |
| 140 |
[git_status] |
| 141 |
style = "@{status.warning}" |
| 142 |
format = "[$all_status$ahead_behind]($style)" |
| 143 |
conflicted = "${count}c " |
| 144 |
ahead = "${count}a " |
| 145 |
behind = "${count}b " |
| 146 |
# Diverged needs no symbol of its own now: ahead and behind already have |
| 147 |
# letters, so it is just both of them. |
| 148 |
diverged = "${ahead_count}a ${behind_count}b " |
| 149 |
untracked = "${count}u " |
| 150 |
stashed = "${count}st " |
| 151 |
modified = "${count}m " |
| 152 |
staged = "${count}s " |
| 153 |
renamed = "${count}r " |
| 154 |
deleted = "${count}d " |
| 155 |
|
| 156 |
# ------------------------------------------------------------------- |
| 157 |
# The exit code, as a number |
| 158 |
# ------------------------------------------------------------------- |
| 159 |
# Disabled by default in starship, and the reason to turn it on is that the |
| 160 |
# entry glyph already carries this state as a color and a color cannot count. |
| 161 |
# 127 is a typo'd command, 130 is you pressing ctrl-c, 137 is the OOM killer. |
| 162 |
# Same red glyph, three unrelated problems. |
| 163 |
# |
| 164 |
# Silent on success: a prompt that printed 0 after every command would be |
| 165 |
# saying nothing, loudly. |
| 166 |
[status] |
| 167 |
disabled = false |
| 168 |
# Labelled. On the right-hand side, away from anything that gives it context, |
| 169 |
# a bare `130` is a number with no noun. `exit 130` costs five characters and |
| 170 |
# needs nothing learned. |
| 171 |
format = "[exit $status]($style) " |
| 172 |
style = "@{status.danger}" |
| 173 |
# The number, not a name for it. `map_symbol` would turn 130 into an interrupt |
| 174 |
# glyph, which is the picture-for-a-word trade this file exists to refuse. |
| 175 |
map_symbol = false |
| 176 |
pipestatus = false |
| 177 |
|
| 178 |
# ------------------------------------------------------------------- |
| 179 |
# Command duration — only for commands slow enough to have been noticed |
| 180 |
# ------------------------------------------------------------------- |
| 181 |
[cmd_duration] |
| 182 |
min_time = 2000 |
| 183 |
style = "@{content.secondary}" |
| 184 |
format = "[$duration]($style) " |
| 185 |
show_milliseconds = false |
| 186 |
|
| 187 |
# ------------------------------------------------------------------- |
| 188 |
# Entry glyph — the prompt character itself |
| 189 |
# ------------------------------------------------------------------- |
| 190 |
# Geometric shapes rather than nerd-font icons, so this survives a font with |
| 191 |
# no icon coverage. It keeps its color state even though the exit code is now |
| 192 |
# spelled out: the color is what you catch without reading. |
| 193 |
[character] |
| 194 |
success_symbol = "[▸](@{content.secondary})" |
| 195 |
error_symbol = "[▸](@{status.danger})" |
| 196 |
vimcmd_symbol = "[◂](@{status.success})" |
| 197 |
|
| 198 |
# ------------------------------------------------------------------- |
| 199 |
# Right-aligned clock — when the command in front of it ran |
| 200 |
# ------------------------------------------------------------------- |
| 201 |
# Kept even though the status bar is also a clock. The bar says what time it |
| 202 |
# is now; this says what time each command in the scrollback happened, which |
| 203 |
# is a different question and the only place it gets answered. |
| 204 |
[time] |
| 205 |
disabled = false |
| 206 |
format = "[$time]($style)" |
| 207 |
time_format = "%H:%M" |
| 208 |
style = "@{content.muted}" |
| 209 |
|
| 210 |
# ------------------------------------------------------------------- |
| 211 |
# Disabled modules — noise for Alloy's audience |
| 212 |
# ------------------------------------------------------------------- |
| 213 |
# The language modules are not merely absent from `format`; they are off, so |
| 214 |
# that adding a segment later is a deliberate act rather than something that |
| 215 |
# starts rendering because a module's default changed. |
| 216 |
[package] |
| 217 |
disabled = true |
| 218 |
|
| 219 |
[python] |
| 220 |
disabled = true |
| 221 |
|
| 222 |
[rust] |
| 223 |
disabled = true |
| 224 |
|
| 225 |
[nodejs] |
| 226 |
disabled = true |
| 227 |
|
| 228 |
[aws] |
| 229 |
disabled = true |
| 230 |
|
| 231 |
[gcloud] |
| 232 |
disabled = true |
| 233 |
|
| 234 |
[docker_context] |
| 235 |
disabled = true |
| 236 |
|