Lilush Shell

Lilush Shell logo

Overview

This document covers the Lilush interactive shell: startup sequence, configuration, modes, prompt, builtins, and key bindings.

Related docs:

Terminal Requirements

Lilush Shell requires a terminal that supports the Kitty keyboard protocol(refered to as KKBP in code and hereafter). Compatible terminals include kitty, ghostty, foot, alacritty, and konsole.

The shell enables both KKBP and bracketed paste mode on startup and refuses to launch if KKBP is not detected.

Configuration

Directories

The shell creates these directories on first launch if they don't exist:

DirectoryPurpose
~/.config/lilush/User configuration
~/.config/lilush/snippets/User snippet files
~/.local/share/lilush/User data (MNEME databases, packages)

Init scripts

On startup, the shell mode executes init scripts in this order:

  1. /etc/lilush/init.lsh — system-wide (if exists)

  2. ~/.config/lilush/init.lsh — user (if exists)

Both files use the LSH scripting format. Execution stops on the first non-zero exit code within each file.

A typical init.lsh:

# Set prompt blocks
setenv LILUSH_PROMPT=user,dir,git

# Set theme
theme gruvbox

# Define aliases
alias ll ls -la

# Python venvs directory
setenv LILUSH_PYTHON_VENVS_DIR=/home/user/.venvs

User modes

Custom modes are loaded from ~/.config/lilush/modes.json. This file maps shortcut keys to LILPACK package names:

{
    "F5": "my-package-name"
}

The mode module, prompt, completion, and sources are discovered from the LILPACK's manifest. Built-in mode names (shell, lua, wiki) cannot be overridden.

If a mode's package is not installed, the shell prints a warning and skips that mode instead of failing to start.

Shortcut conflicts are resolved automatically: if a user mode requests a reserved shortcut (F1F3) or one already taken, it is reassigned to the next free key in the F4F12 pool.

Startup Sequence

The full initialization sequence for lilush (interactive shell):

1. Entry start_shell.lua

Seeds the RNG, registers SIGINT handler, starts a LEV event loop (lev.run()), then within the event loop calls shell.new() and shell:run().

2. Shell constructor shell.lua:new()

  1. Sets SHELL=/usr/bin/lilush

  2. Initializes LILPACK (lilpack.init()), which replaces default Lua searchers with LILPACK and script-local searchers and clears package.path

  3. Creates a MNEME-backed history store (see MNEME for storage details)

  4. Loads built-in modes in order: shellluawiki. For each mode, load_mode() creates:

    • Prompt object (from the mode's prompt module)

    • Completion object (with all configured sources)

    • History object (backed by the shared store)

    • Input object (wiring prompt, completion, and history together)

    • Mode object via the mode module's new(input, config)

  5. Binds mode shortcuts: F1 (shell), F2 (lua), F3 (wiki)

  6. Reads ~/.config/lilush/modes.json and loads user modes from LILPACKs

3. Shell mode initialization shell/mode/shell.lua:new()

The shell mode constructor runs during step 4 above:

  1. check_config_dirs() — creates ~/.config/lilush/ and ~/.local/share/lilush/ if missing

  2. load_config() — executes /etc/lilush/init.lsh then ~/.config/lilush/init.lsh

  3. Re-applies LILUSH_PROMPT — if init.lsh set the variable via setenv, the prompt blocks are updated to match (the prompt object was created before init.lsh ran)

  4. Sets initial prompt state — populates home, user, hostname, pwd

  5. Updates completion — registers aliases as a completion source

4. Main event loop shell.lua:run()

  1. Verifies TTY, sets raw mode

  2. Probes terminal text-sizing support

  3. Checks and enables Kitty keyboard protocol

  4. Enables bracketed paste mode

  5. Clears screen, displays initial prompt

  6. Enters the main loop, processing "execute" and "combo" events

Modes

The shell is a mode-based system. Each mode provides its own prompt, completion, history, and command execution logic.

Built-in modes

ModeShortcutDescription
shellF1Standard shell — runs commands, pipelines, builtins
luaF2Lua REPL — evaluates Lua expressions and statements
wikiF3Wiki viewer — browse and search MNEME wiki databases (see WIKIDB)

Built-in shortcuts (F1F3) are reserved and cannot be reassigned.

Mode switching

Press a mode's shortcut key to switch to it. CTRL+d from a non-shell mode returns to the shell mode. Each mode preserves its own input state (content, cursor position, history position) independently.

Mode contract

Every mode must expose these methods:

Prompt

The shell prompt is composed of configurable blocks rendered left to right.

Available blocks

BlockWhat it shows
userusername@hostname (root highlighted in red)
dirCurrent directory (with ~ substitution, clipped at 25 chars)
gitBranch, modified/staged/untracked counts, ahead/behind, tags
pythonActive Python venv name
secretsLocal secrets encryption/fetch status icon

Blocks are always rendered in this fixed order regardless of the order specified in the configuration. A trailing $ is always appended.

Configuring prompt blocks

Via LILUSH_PROMPT env var — comma-separated list of block names:

setenv LILUSH_PROMPT=user,dir,git

The default if unset is user,dir.

Via the prompt builtin — toggle individual blocks at runtime:

prompt git          # toggle git block on/off
prompt git secrets  # toggle multiple blocks
prompt              # show enabled/disabled blocks

LILUSH_PROMPT resolution order

  1. Prompt object is created — reads LILUSH_PROMPT from environment, falls back to "user,dir"

  2. init.lsh runs — may setenv LILUSH_PROMPT=...

  3. After init.lsh, LILUSH_PROMPT is re-read and prompt blocks are updated

This means all three scenarios work:

Key Bindings

Global (all modes)

KeyAction
CTRL+dExit shell (or deactivate venv if active, or return to shell mode if in another mode)
CTRL+lClear screen
F1F3Switch to built-in mode
F4F12Switch to user mode (if assigned)

Builtins

Top-level builtins

These are handled directly by the shell mode because they need access to the mode's internal state:

CommandDescription
alias <name> <value>Define an alias (no args: list all)
unalias <name>Remove an alias
rehashRescan PATH for completions
prompt [blocks...]Toggle prompt blocks (no args: show status)
theme [name]Theme management (no args: list all; with name: set theme)
calm <subcommand>CALM model management
pyvenv <name\|exit>Activate/deactivate Python venv (no args: list available)
secrets <subcommand>Local secrets management (status, set, get, del, list, fetch, clear)
run_script <path>Execute an LSH script

Regular builtins

Dispatched through the builtins registry (shell.builtins):

Filesystem: ls (-a -t -l), cd, mkdir (-p), rm (-r -f), rmrf, ../.../etc. (shorthand for cd ../../...), files_matching (execute over matching files)

File viewing: kat — file viewer with pager and markdown rendering (-m --raw --pager)

Environment: setenv/export, unsetenv, envlist (optional regex filter)

Process management: ps (-a -o -x --detailed --json --text -k -p -u --sort), exec (replace shell process), job (list start kill attach reap)

Networking: dig (see dig --help), netstat (-l --source --destination), wgcli (up down)

History: history (-n --compact --time-only)

Snippets: zx (snippet launcher)

MNEME: mneme — MNEME database management

Packages: lilpack — LILPACK package management (list, modules, install, verify)

Desktop: notify — desktop notification via Kitty protocol (--after --title)

Builtin fork modes

Each builtin declares a fork mode that controls how it executes:

ModeBehavior
"always"Fork a child process (default for most builtins)
"never"Run directly in the shell process — required for builtins that modify shell state
"pipe"Run in the shell process when standalone; fork when in a pipeline or with I/O redirects

Builtins may also declare:

Fork modes by builtin:

fork = "never"fork = "pipe"
cd, ../...kat
setenv, export, unsetenvmneme
job, zx, themelilpack

Alias expansion

Aliases are expanded at:

Aliases are simple text substitution — the alias name is replaced with the alias value before parsing.

Command Execution

When you press Enter:

  1. Input text is retrieved and aliases are expanded

  2. The command list is parsed via pipeline.parse_list() (handles pipes, &&/||/; operators, I/O redirects (<, >, >>, err>, err>>, all>, all>>), and quoting)

  3. All commands are validated — each must be a known builtin, a binary on PATH, a top-level builtin, or a path starting with ./ or /

  4. The command list is executed:

    • Top-level builtins run in the shell process

    • Regular builtins with fork = "never" run in the shell process

    • Regular builtins with fork = "pipe" run in the shell process unless they are in a pipeline or have I/O redirects (file input, output, or stderr redirection), in which case they fork

    • Everything else is forked

  5. Fetched local secrets are cleared after execution (per persist mode)

The following env vars are set around each command execution:

VariableSet whenValue
LILUSH_EXEC_CWDBefore executionWorking directory
LILUSH_EXEC_STARTBefore executionUnix timestamp
LILUSH_EXEC_ENDAfter executionUnix timestamp
LILUSH_EXEC_STATUSAfter executionExit code as string

Local Secrets

Environment variables whose values start with mneme://secrets/ reference secrets stored in the shell's encrypted MNEME keyspace (~/.local/share/lilush/shell.mneme, keyspace secrets). The secrets builtin manages storage and fetching of these secrets.

Secret URI format

mneme://secrets/key_name

Example:

setenv DB_PASSWORD=mneme://secrets/db_password

Encryption

Secrets are encrypted at rest using an Ed25519 SSH key. Set the LILUSH_ENCRYPTION_KEY environment variable to the path of your SSH key:

setenv LILUSH_ENCRYPTION_KEY=~/.ssh/id_ed25519

Without this key, secrets set/get/fetch operations will fail with an error. Unencrypted keyspaces (history, session) are unaffected.

The secrets builtin

SubcommandDescription
secrets statusShow encryption status, managed vars, and persist mode
secrets set <key> [value]Store a secret (prompts interactively if value omitted)
secrets get <key>Retrieve and print a secret
secrets del <key>Delete a secret
secrets list [prefix]List stored secret keys
secrets fetch [--persist N\|manual] [VARS...]Fetch secrets and replace env var values
secrets clearImmediately reset all fetched secrets to their mneme:// URIs

secrets fetch resolves each mneme://secrets/ URI, reads the secret from the encrypted keyspace, and overwrites the environment variable with the actual value. You can fetch specific variables by name or omit the argument to fetch all secret-backed vars.

When using secrets set, omitting the value argument opens an interactive prompt with hidden input, avoiding the secret appearing in shell history.

Persist modes

After fetching, secrets remain in the environment for a limited number of commands before being automatically cleared (restored to mneme:// URIs).

--persist valueBehavior
manualSecrets persist until secrets clear is run
Integer NSecrets persist for N commands, then auto-clear
(omitted)Uses LILUSH_SECRETS_PERSIST env var, falls back to 1

Secrets in scripts

Fetched secrets are inherited by child processes, including LSH scripts run via shebang. Scripts do not re-run init.lsh, so the inherited environment (with fetched secrets) is preserved as-is.

Non-interactive Modes

lilush -c <command> (mini mode)

Creates a minimal shell with no prompt, no completion, and no history. Executes the given command and exits. Does not load init.lsh.

lsh <script> (script mode)

If lilush is invoked as lsh via symlink, it executes an LSH script file. Script arguments are exposed as env vars: ${0} (script path), ${1}..${N} (positional args), ${#} (arg count). Does not load init.lsh. See LSH for the scripting format.

Environment Variables

Shell-managed

VariablePurpose
SHELLSet to /usr/bin/lilush on startup
LUA_PATHSet to empty string (LILPACK handles module loading)
PWDCurrent working directory (updated by cd)
LILUSH_EXEC_CWDCWD before last command execution
LILUSH_EXEC_STARTTimestamp when last command started
LILUSH_EXEC_ENDTimestamp when last command ended
LILUSH_EXEC_STATUSExit status of last command
VIRTUAL_ENVActive Python venv path (set by pyvenv)

User-configurable

VariablePurposeDefault
LILUSH_PROMPTComma-separated prompt block namesuser,dir
LILUSH_PYTHON_VENVS_DIRDirectory containing Python venvs
LILUSH_TERM_TITLE_PREFIXPrefix for dynamic terminal title
LILUSH_TERM_TITLE_STATICStatic terminal title (overrides dynamic)
LILUSH_CALM_MODELPath to CALM model weights file(resolved via registry, e.g. ~/.local/share/lilush/calm/shell.cwgt)

Secrets

VariablePurposeDefault
LILUSH_ENCRYPTION_KEYPath to Ed25519 SSH key for secret encryption
LILUSH_SECRETS_PERSISTDefault persist mode for secrets fetch (manual or integer)1

History

Command history is stored in MNEME (~/.local/share/lilush/shell.mneme). Each mode maintains its own history namespace as a sorted set within a keyspace named after the mode (e.g. shell, lua). History is always persisted across sessions.

The history builtin displays past commands:

history             # show recent history
history -n 50       # show last 50 entries
history --compact   # compact format
history --time-only # show only timestamps