sage is a terminal pager inspired by most, written in Silk.
cd sage
silk build --package .Binary: ./build/bin/sage
man -l man/sage.1./build/bin/sage <path> [path ...] # files or directories (dirs expand to child files)
./build/bin/sage src/ # open all files in a directory (non-recursive)
cat <path> | ./build/bin/sage
./build/bin/sage --index-only <path>
./build/bin/sage --compile-cache # compile syntax cache (see below)
./build/bin/sage --verbose --compile-cache
./build/bin/sage --verbose --list-syntax
./build/bin/sage -i <path> # case-insensitive search
./build/bin/sage -R <path> # regex search (std::regex)
./build/bin/sage --color never <path>
./build/bin/sage --theme ocean <path>
./build/bin/sage --no-alt-screen <path>
./build/bin/sage --no-rc <path>q— quitCtrl-C— quit (or copy selection when one is active)j/d/Down— down one visual line (wrap-to-viewport)k/u/Up— up one visual lineMouseWheel— scroll (if enabled)MouseDrag— select text (gutter excluded)Ctrl-C— copy selection (OSC 52)Space/PageDown/Ctrl-D— down one pageb/PageUp/Ctrl-U— up one page→/Right— down one page←/Left— up one pageTab— next tab (when multiple files are open)Shift-Tab— previous tabgg/Home— topG/End— bottom//Ctrl-F— search (starts searching; jumps to first match when found)Ctrl-K— find across open files (Up/Down/Tab/Shift-Tab navigate, Enter/click jumps, Wheel scrolls, Esc closes)DoubleClick— set query to clicked wordn— next matchp— previous match:— command mode (:<n>goto line,:0top, negative numbers count from end;:qquit;:bn/:bpnext/prev tab;:tab <n>go to tab<n>; tabs are 1-indexed and0jumps to the last tab)L— toggle line-number gutterEsc— cancel search / cancel pending goto / clear selection?/h— help
- By default,
sagesanitizes control bytes to avoid terminal injection, but passes through ANSI SGR (ESC [ ... m) so colored output (likeman) renders (use--no-ansito fully sanitize). - When stdout is not a TTY,
sagefalls back to streaming bytes to stdout. - Line numbers are computed using a background indexer; on very large inputs they
may briefly show
?until the indexer scans past the current viewport. - The status bar, prompts, and match highlighting use ANSI colors (256-color SGR).
Use
--color neveror setNO_COLOR=1to disable styling.
sage can apply syntax highlighting when viewing a file path (not stdin).
- Syntax definitions live in
XDG_CONFIG_HOME/sage/syntax/(~/.config/sage/syntax/). sage --compile-cachecompiles them intoXDG_CACHE_HOME/sage/syntax/(~/.cache/sage/syntax/).- Supported source formats (subset):
.sublime-syntax,.tmLanguage,.tmLanguage.json,.cson(Atom grammar).sage --list-syntaxprints supported syntax keys (one per line). Use--verboseto also show the key→cache mapping on stderr.
- Binary cache formats:
specs/sagec/2.0.md.
At startup, sage loads a config file (unless --no-rc):
SAGERC(explicit path), else./.sagerc, else$HOME/.sagerc
CLI flags always override .sagerc.
Format: key = value (comments start with # or ;).
Supported keys (high level):
theme=default|ocean|lightcolor=auto|always|neveransi,syntax,alt_screen,mouse,raw,binary,regex,ignore_case=true|falsegutter/line_numbers=auto|always|never|true|falsefind_cmd(find,find-cmd) = find command + args (whitespace-split; no shell quoting)plugins=true|falseplugins_dir= absolute path to plugins dir (not~-expanded)plugin_log_path(plugin_log) = absolute path to plugin log (not~-expanded)- Plugin limits:
plugin_load_timeout_ms,plugin_event_timeout_ms,plugin_mem_limit_mb,plugin_stack_limit_kb - Theme palette overrides (0–255):
status_bg,status_fg,status_dim,brand,accent,warn,err,mode_regex,match_bg,match_fg - Syntax palette overrides (0–255):
syn_comment,syn_string,syn_number,syn_keyword,syn_type,syn_function,syn_constant,syn_operator,syn_heading,syn_emphasis,syn_preproc
See .sagerc.example for a starting point.
At startup, sage loads *.js plugins in lexicographic order from:
--plugins-dir <path>/.sagercplugins_dir = <path>(if set and$SAGE_PLUGINS_DIRis not set), else$SAGE_PLUGINS_DIR, else$XDG_CONFIG_HOME/sage/plugins(usually~/.config/sage/plugins), else$HOME/.config/sage/plugins
Plugins run only in the interactive TUI path (stdout is a TTY). In pass-through
filter mode, sage returns early and does not load plugins.
Disable plugins (safe mode): SAGE_NO_PLUGINS=1, --no-plugins, or .sagerc plugins = false.
Plugin diagnostics are written to $SAGE_PLUGIN_LOG (if set), else
$XDG_CACHE_HOME/sage/plugins.log, else $HOME/.cache/sage/plugins.log.
If the log file cannot be opened, diagnostics are suppressed by default; set SAGE_PLUGIN_LOG_STDERR=1 to force stderr (debug only; may corrupt the TUI).
For robustness, plugin execution is bounded (timeouts + memory/stack limits). Each plugin runs in its own QuickJS runtime/context; if a plugin hits a timeout, sage disables only that plugin for the rest of the session.
Plugins are evaluated as ES modules (ESM), so import ... from ... works.
- Built-in modules:
sage:fs(read open tabs + plugin data dir; bounded reads/writes)sage:path(minimal POSIX-y path helpers)sage:process(pid,ppid,cwd(),exec(...))sage:env(get/set/unset)sage:navigator(browser-likenavigator)sage:performance(performance.now()+performance.timeOrigin)sage:crypto(crypto.getRandomValues(...)+crypto.randomUUID())sage:url(WHATWG-styleURL+URLSearchParams+URL.parse/URL.canParse)sage:core/dom(DOMException+structuredClone)sage:core/web(host-free WHATWG-ish web primitives:Headers/Request/Response/FormData/Blob/ReadableStream/AbortController/AbortSignal/TextEncoder/TextDecoder)sage:fetch(WHATWG-ishfetchbacked by the native host; extra options:timeoutMs/maxBytes/followRedirects)
- Relative imports are allowed for filesystem modules under the plugin’s directory tree. Bare imports are rejected. Top-level await is not supported.
The JS bootstrap extends globalThis (browser-like):
EventTarget,Event,CustomEvent,MessageEventaddEventListener/removeEventListener/dispatchEvent(Event objects)on/once/off(payload-only helpers:CustomEvent.detail/MessageEvent.data)queueMicrotask(fn)isSageRuntime(stable runtime check getter)DOMExceptionandstructuredCloneconsole(level-gated bySAGE_CONSOLE_LEVEL:silent|error|warn|info|verbose|debug)command(name, fn)register a custom:<name>command (handlers may be async)exec(cmd)enqueue a command for the host to execute (cmdmay include a leading:)navigator(browser-like object; also available assage:navigator)performance(also available assage:performance)crypto(also available assage:crypto)URL,URLSearchParams(WHATWG-style; also available assage:url)fetch,Headers,Request,Response,FormData,Blob,ReadableStream,AbortController,AbortSignal,TextEncoder,TextDecoder
Host events are delivered as CustomEvents; the payload is in ev.detail (or passed directly to on/once).
See examples/plugins/README.md for a curated set of plugins demonstrating the API and built-in modules.
