Craft a custom terminal prompt with JSON.
You describe your prompt expressively in JSON and then it gets compiled to Bash. A very simple prompt might look like this:
{
"prompt": ["character", "space"],
"components": [
{
"key": "character",
"value": "❯",
"color": "magenta"
}
]
}
And it will generate Bash that looks like this:
function _promptconfig_prompt() {
local prompt=''
prompt+='\[\e[38;5;5m\]'
prompt+='❯'
prompt+='\[\e[0m\]'
prompt+=' '
PS1=$prompt
}
PROMPT_COMMAND="_promptconfig_prompt; $PROMPT_COMMAND"
Bash is hard. It’s an arcane language and environment with a steep learning curve. Most programmers wind up using it on a very superficial level, figuring out just enough to get done what they need to do.
Unfortunately, customizing your prompt correctly requires a disproportionately high understanding of how Bash and the terminal work relative to what you’re trying to actually do. The internet is filled with recommendations that will break your prompt in subtle ways that aren’t immediately obvious and later you might not understand are caused by your custom prompt. I’m speaking from personal experience. 😭
It shouldn’t be that complicated and hopefully now it isn’t.
Install it globally using npm:
❯ npm install --global promptconfig
Once you’ve configured your prompt in JSON, run promptconfig
, passing in the path to the JSON file.
❯ promptconfig <JSON configuration file>
You probably want to redirect the output to a Bash file, e.g.
❯ promptconfig prompt.json > ~/prompt.bash
Then put source prompt.bash
in your ~/.bashrc
or ~/.bash_profile
, whichever you have/use. Open a new Terminal window and you should see your new prompt!
There are two parts to the configuration: prompt
and components
. prompt
defines the structure/layout of your prompt using both built-in and custom components. components
defines the custom components you use in prompt
.
The simplest form of the prompt
configuration is just an array of component keys.
{
"prompt": ["character", "space"]
}
The array represents one line of your prompt. If you want your prompt to appear on multiple lines, you can use the built-in newline
component.
{
"prompt": ["working_directory", "newline", "character", "space"]
}
prompt
can also be an array of arrays.
{
"prompt": [
["working_directory"],
["character", "space"]
]
}
Each array represents a line in the prompt. For multi-line prompts it’s a bit clearer than using the newline
component.
If you want a blank line in your prompt to visually break up commands, you can use an empty array.
{
"prompt": [
[],
["character", "space"]
]
}
There are two built-in components that are available as a convenience: newline
and space.
Any other component you want you’ll need to create.
Every component must have a key
and value
, function
, or command
. Optionally, some components can have conditions
and color
.
key
is a unique string used to reference the component in the prompt
configuration and control its position in the prompt.
value
, function
, and command
are types that control how the component behaves.
The simplest type is value
. You can use it to output plain text.
{
"components": [
{
"key": "character",
"value": "❯",
}
]
}
A more complex type is command
. You can use it to execute arbitrary shell commands.
{
"components": [
{
"key": "username",
"command": "whoami",
}
]
}
NB: When writing commands with backslash escapes, remember to use two backslashes, e.g. "\\n"
. The first backslash is escaping the second backslash in the JSON and will not show up in the final Bash output. The second backslash is for Bash and will show up in the final Bash output.
The last type is function
. It allows you to use built-in, predefined commands.
{
"components": [
{
"key": "working_directory",
"function": "working_directory",
}
]
}
The available functions are:
working_directory
: Returns the current git branch namegit_branch_name
: Returns the current git branch namegit_behind_upstream
: Returns whether there are commits upstream waiting to be pulledgit_ahead_of_upstream
: Returns whether there are commits locally waiting to be pushedexit_status
: Returns whether the previously executed command was successful
If there is a command that you think is generic and widely-used enough to be included as a function, please open a pull request!
You’ll notice that some functions like working_directory
return text, while others like git_behind_upstream
indicate true or false. You can make use of the boolean functions with conditions
.
Conditions allow you compare the return value of a command or function and return other plain text instead.
If the conditions are true
and false
, only true
, or only false
, then it’s assumed you want to check whether the command or function exited successfully or not.
{
"components": [
{
"key": "exit_status",
"function": "exit_status",
"conditions": {
"true": "✅",
"false": "❌"
}
}
]
}
If the conditions are anything else, it’s assumed you want to compare equality against what the command or function returned.
{
"components": [
{
"key": "username",
"command": "whoami",
"conditions": {
"brandon": "👨💻",
"root": "🔒"
}
}
]
}
What would a prompt be without pretty colors? color
can be an ANSI 16 color name or an ANSI 256 color code.
The actual color values these names represent are typically controlled by settings in your Terminal app. Most Terminal apps have the concept of “themes” that allow you to change these values. It’s generally preferable to use ANSI 16 color names to color your prompt so that your Terminal uses colors consistently.
black
red
green
yellow
blue
magenta
cyan
white
bright_black
bright_red
bright_green
bright_yellow
bright_blue
bright_magenta
bright_cyan
bright_white
{
"components": [
{
"key": "character",
"value": "❯",
"color": "magenta"
}
]
}
If you have a very specific color you want to use you can choose any ANSI 256 color code—a number between 0 and 255.
{
"components": [
{
"key": "character",
"value": "❯",
"color": 124
}
]
}
Defining color
like that is actually a shorthand. For example, this:
{
"components": [
{
"key": "character",
"value": "❯",
"color": "magenta"
}
]
}
Is equivalent to this:
{
"components": [
{
"key": "character",
"value": "❯",
"color": {
"value": "magenta",
}
}
]
}
color
has behavior in the same way components
have behavior. That means color
can have a value
, command
, or function
, and optionally conditions
.
{
"components": [
{
"key": "character",
"value": "❯",
"color": {
"function": "exit_status",
"conditions": {
"true": "magenta",
"false": "red"
}
}
}
]
}
This is an example of using the exit_status
function to conditionally color the prompt character to “magenta” if the last command was successful or “red” if it was unsuccessful.
Bug reports and pull requests are welcome on GitHub.
The package is available as open source under the terms of the MIT License.