# skull-shell

A tiny POSIX-style shell written in C++20 and built with xmake.

## Build

```sh
xmake
```

## Run

```sh
xmake run skull
```

## Project Layout

```text
src/
  builtins/   cd, pwd, exit, and future built-in commands
  core/       main shell loop and shared command data structures
  executor/   fork, exec, pipes, redirection, and process waiting
  parser/     command-line tokenizing and parsing
  ui/         prompt, startup banner, completion, and history behavior
```

## Current Features

- Startup skull banner
- Custom interactive prompt with history
- Persistent history in `~/.skull_history`
- Standard history navigation with `Up` / `Down`
- Multi-word history search with `Ctrl-R`
- Inline history autosuggestions accepted with `Right`, `Tab`, or `Ctrl-F`
- Command and path completion with `Tab`
- External commands through `fork` + `execvp`
- Built-ins: `help`, `alias`, `unalias`, `dlload`, `dlcall`, `dllist`, `dlunload`, `jobs`, `fg`, `bg`, `prompt`, `cd`, `pwd`, `exit`
- Startup files: `~/.skullrc` and `~/.skullrc.d/*`
- Single and double quotes
- Backslash escaping
- Environment variable expansion: `$HOME`, `${HOME}`, `$?`
- Environment variable assignment with `export`, `NAME=value`, and `NAME=value command`
- Globbing: `*.cpp`
- Pipelines with `|`
- Input redirection with `<`
- Output redirection with `>` and `>>`
- Background execution with trailing `&`
- Basic job control: `jobs`, `fg`, `bg`
- Prompt customization with `prompt` or `$SKULL_PROMPT`

## Examples

```sh
pwd
cd /tmp
echo "hello skull" | wc -c
printf "one\ntwo\n" > lines.txt
cat < lines.txt
sleep 10 &
jobs
fg %1
alias ll='ls -la'
ll
unalias ll
prompt 'skull:{cwd}:{status}> '
exit
```

Aliases are expanded for the first word of each command, including commands in
pipelines. They live for the current shell session.

## Default Aliases

Skull Shell installs these aliases at startup before reading user startup files:

```sh
alias cls='clear'
alias ll='ls -la'
```

You can override them in `~/.skullrc` or `~/.skullrc.d/*.skull`.

## Startup Files

Skull Shell reads `~/.skullrc` first, then regular `*.skull` files in
`~/.skullrc.d/` in name order. Empty lines and lines starting with `#` are
ignored.

```sh
# ~/.skullrc
alias ll='ls -la'
alias gs='git status'
```

```sh
# ~/.skullrc.d/10-navigation.skull
alias ..='cd ..'
```

Startup files currently support built-ins such as `alias`, `unalias`, `cd`, and
`pwd`, `prompt`, and `export`. External commands are intentionally ignored
during startup.

## Dynamic Modules

Skull Shell can load shared libraries with `dlopen` and call C ABI functions
with `dlsym`.

```sh
dlload ./libhello.so hello
dllist
dlcall hello skull_echo hello from plugin
dlunload hello
```

Callable functions must use this ABI:

```cpp
extern "C" int skull_echo(int argc, char** argv) {
    // argv[0] is the function name, argv[1...] are arguments from dlcall.
    return 0;
}
```

Build a plugin on Linux with:

```sh
g++ -std=c++20 -fPIC -shared plugin.cpp -o libhello.so
```

Use `Up` / `Down` for normal history navigation. Press `Ctrl-R` to open the
multi-line history search, then type one or more words to filter entries that
contain all of them. Matching words are color-highlighted and the selected
entry is emphasized. Press `Ctrl-R` again to cycle through matches. Search
results are paged; moving past the last visible entry advances to the next page.
Press `Enter` while the search list is open to accept the selected entry into
the prompt, then press `Enter` again to execute it.

Type the beginning of an old command to see a dim inline autosuggestion. Press
`Right`, `Tab`, or `Ctrl-F` at the end of the line to accept it.

If there is no autosuggestion, `Tab` completes command names at the beginning of
the line and file paths elsewhere.

Customize the prompt with a template:

```sh
prompt 'skull:{cwd}:{status}> '
export SKULL_PROMPT='[{status}] {cwd}$ '
prompt '{user}@{hostname}:{cwd}{symbol} ||{if:ssh?SSH:local} {time} {if:success?ok:bad}'
prompt '{green}{user}{reset}@{cyan}{hostname}{reset}:{yellow}{cwd}{reset}{symbol} '
```

Use `||` to split the left prompt from the right prompt. The right prompt is
drawn dimmed and right-aligned when there is enough terminal space.

Supported prompt fields:

| Field | Meaning |
| --- | --- |
| `{user}` | Current user from `USER` or `LOGNAME` |
| `{hostname}` | Host name from `gethostname` |
| `{cwd}` | Current directory name |
| `{path}` | Full current directory path |
| `{path:~}` | Current directory path with `$HOME` shortened to `~` |
| `{status}` | Previous command exit status |
| `{date}` | Local date as `YYYY-MM-DD` |
| `{time}` | Local time as `HH:MM:SS` |
| `{uid}` | Numeric user id |
| `{symbol}` | `#` for root, `$` otherwise |

Color and style fields:

| Field | Meaning |
| --- | --- |
| `{reset}` | Reset color and style |
| `{bold}` | Bold text |
| `{dim}` | Dim text |
| `{black}` / `{red}` / `{green}` / `{yellow}` | Standard foreground colors |
| `{blue}` / `{magenta}` / `{cyan}` / `{white}` | Standard foreground colors |
| `{bright_black}` / `{bright_red}` / `{bright_green}` / `{bright_yellow}` | Bright foreground colors |
| `{bright_blue}` / `{bright_magenta}` / `{bright_cyan}` / `{bright_white}` | Bright foreground colors |

Conditional prompt expressions:

| Expression | Meaning |
| --- | --- |
| `{if:ssh?yes:no}` | `yes` when running inside SSH, otherwise `no` |
| `{if:root?yes:no}` | `yes` for uid 0, otherwise `no` |
| `{if:success?yes:no}` | `yes` when the previous status is 0, otherwise `no` |
| `{if:env:NAME?yes:no}` | `yes` when environment variable `NAME` is set and non-empty, otherwise `no` |
| `{if:name?yes:no}` | `yes` when a custom condition named `name` exits with status 0 |

Custom prompt conditions can be registered with `condition`. The command is run
through `/bin/sh -c`, and exit status 0 means true.

```sh
condition git='test -d .git'
condition wsl='grep -qi microsoft /proc/version'
prompt '{cwd} {if:git?git:}{symbol} ||{if:wsl?WSL:linux}'
condition git=
```

Run commands in the background with `&`, inspect them with `jobs`, and bring one
back with `fg`:

```sh
sleep 30 &
jobs
fg %1
```

Environment variables are expanded before execution. `$?` expands to the
previous command's exit status. Globs such as `*.cpp` are expanded when they
match files; unmatched globs are left unchanged.

Set environment variables with `export`, assignment-only commands, or temporary
command environments:

```sh
export HOGE=huga
export FOO = bar
ONLY=value
TEMP=one env
TEMP = two env
```

`NAME=value command` and `NAME = value command` pass the variable only to that
command. Assignment without a command updates the shell environment.

## License

Skull Shell is licensed under the Apache License 2.0.
