Terminal that redraws the boundary of what a terminal is responsible for
  • Rust 97.3%
  • Shell 1%
  • Nix 0.8%
  • Python 0.5%
  • WGSL 0.2%
  • Other 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
natsukium e45dd0ded6
Some checks failed
fuzz / cargo fuzz nightly long-run (push) Has been skipped
pr / cargo check (MSRV) (push) Waiting to run
build / build and cache felis (macos) (push) Has started running
build / build and cache felis (nix) (push) Successful in 5m57s
fuzz / cargo fuzz smoke (per target) (push) Successful in 1m42s
pr / nix flake check (push) Successful in 15s
pr / cargo build / clippy / test / deny (push) Failing after 1m26s
pr / generated code is current (push) Successful in 2s
pr / cargo check (Windows cross) (push) Has started running
docs: follow the web client's split and rename
`felis-web-core` became two repos: `felis-web-component` (the wasm core
plus the `<felis-terminal>` view) and `felis-web-gateway` (the native
attach that re-exposes a session over a WebSocket). felis's own docs and
comments named one thing where there are now two, so every reference is
re-pointed at whichever half it actually meant.

Which half matters more than the rename does. `felis-grid::transcode`'s
wire→JSON consumer is the gateway, not the browser; the "two downstream
repos" the transcode was adopted for are now `felis-web-gateway` and
`felis-fcast`; the workspace table's "Bridge required" column was
describing the gateway without naming it, and now names it.

One correction found along the way, unrelated to the split:
`connect_with_encoding`'s doc named "the felis-web-core `.fcast`
recorder" as its JSON-consuming caller. The recorder has lived in
`felis-fcast` since that repo was carved out; the doc had simply not
followed.

Comments and prose only — no code, and nothing here builds differently.

Assisted-by: Claude Code Opus 5
2026-08-15 01:13:22 +09:00
.cargo chore: right-size the proto codegen tooling config 2026-07-07 11:30:31 +09:00
.claude chore(skills): record how to verify an animation on macOS 2026-08-14 23:26:10 +09:00
.config feat(protocol)!: split the IPC ControlMsg into six role families 2026-07-05 22:13:04 +09:00
.forgejo/workflows chore: move buf into treefmt/git-hooks, add buf breaking 2026-07-07 12:36:56 +09:00
crates docs: follow the web client's split and rename 2026-08-15 01:13:22 +09:00
docs docs: follow the web client's split and rename 2026-08-15 01:13:22 +09:00
fuzz feat: felis ssh — re-dial a window onto a different daemon 2026-07-18 18:25:41 +09:00
nix docs: record the shipped post-process shader surface 2026-08-14 23:25:43 +09:00
scripts chore: route Criterion benches through scripts/bench.py; add nightly CI 2026-06-11 13:32:21 +09:00
share/terminfo docs: dissolve the ADR layer into the design docs 2026-06-08 00:44:26 +09:00
skills/felis fix: make a relative --cwd and a non-UTF-8 launch dir resolvable 2026-07-27 11:46:59 +09:00
.gitignore chore: adopt cargo-mutants for periodic mutation testing 2026-06-16 22:00:29 +09:00
buf.gen.yaml feat(protocol): add felis.v1 proto schema, generated wire types, buf codegen 2026-07-07 00:51:08 +09:00
buf.yaml feat(protocol): add felis.v1 proto schema, generated wire types, buf codegen 2026-07-07 00:51:08 +09:00
Cargo.lock feat: open new sessions in the caller's directory, not the daemon's / 2026-07-22 00:15:47 +09:00
Cargo.toml feat(daemon): start pipe/run transients in the session's OSC 7 cwd 2026-07-27 11:34:31 +09:00
CLAUDE.md docs: dissolve research notes into the Diátaxis quadrants 2026-07-13 15:01:10 +09:00
clippy.toml build: raise MSRV to 1.88 and add devShells.msrv verification 2026-05-30 14:39:49 +09:00
deny.toml chore: restore the cargo-deny gate for public release 2026-07-05 00:49:23 +09:00
flake.lock chore(flake): update flake.lock 2026-06-28 22:47:09 +09:00
flake.nix feat: report cli/client/daemon build hashes in --version 2026-07-24 18:45:32 +09:00
justfile feat(protocol): add felis.v1 proto schema, generated wire types, buf codegen 2026-07-07 00:51:08 +09:00
LICENSE Lock in license, filesystem layout, OSC 66, and Sixel as permanent reject 2026-05-10 02:19:06 +09:00
README.md docs: document the felis ssh cross-carrier retarget 2026-07-18 18:27:18 +09:00
rustfmt.toml ADR 0007: Nix flake as the canonical development environment 2026-05-10 11:00:38 +09:00

felis

felis is a terminal that redraws the boundary of what a terminal is responsible for. Written in Rust, it draws on kitty for protocol fidelity, alacritty for simplicity, and Kakoune for design discipline.

State lives in a long-running daemon; the client owns only the window. Closing the window keeps the shell running, and the next client — local or across SSH — picks up exactly where the last one left off.

Quick start

felis ships from a Nix flake, so the fastest path needs only the Nix package manager with flakes enabled — no manual toolchain setup, no separate daemon to start.

# Try it without installing anything.
nix run git+https://git.natsukium.com/natsukium/felis

# Or install it to your profile, then just run `felis`.
nix profile install git+https://git.natsukium.com/natsukium/felis
felis

Launching felis opens a window and auto-spawns the background daemon off $PATH — there is nothing else to start. Close the window and your shell keeps running; the next felis reattaches exactly where you left off.

From there:

  • Persistent sessions — close the window, then felis sessions list and felis attach <id-prefix> to pick a detached shell back up.
  • Cross-hostfelis --host user@remote opens a window backed by a remote daemon over plain SSH, or felis ssh user@remote re-points the window you are in at that daemon (and felis ssh --local brings it back).
  • Scriptingfelis sessions {list, info, send, capture, search, spawn, kill} drives the daemon from shell scripts; see CLI and automation.

The end-user manual — install, getting started, configuration, keybindings, CLI reference, troubleshooting — is published at https://felis-docs.pages.dev and maintained in this repository under docs/, organized on the Diátaxis map (tutorials, how-to guides, reference, explanation).

Prefer to build from source or hack on felis? See Building from source.

Features

  • Persistent sessions. Closing the client window does not kill the shell; felis attach <id-prefix> rehydrates a detached one, and chords cycle or spawn sessions in-window (keybindings).
  • Cross-host attach over SSH. felis --host user@remote opens a window backed by a remote daemon — frames ride plain SSH stdio (ssh <host> felis-daemon relay), no listening network service and no auth layer beyond SSH itself.
  • CLI for scripted automation. felis sessions {list, info, send, capture, spawn, search, kill} with first-class --json — see CLI and automation.
  • Scrollback search. felis sessions search <id> <pattern> greps your session's history; in-window, Ctrl+Shift+F opens a search bar with highlighted matches and n / N navigation.
  • Modern terminal protocols. True colour, italics, the xterm underline range (curly / dotted / dashed, with underline colour), OSC 8 hyperlinks, mouse SGR, bracketed paste, focus tracking, synchronized output, the Kitty keyboard protocol, OSC 133 semantic prompts, Kitty graphics (animation and shared memory included) and text sizing.
  • Real text shaping. Per-codepoint font fallback (CJK + symbols + colour emoji + Nerd Font), opt-in OpenType features / ligatures, platform-native IME (AppKit / zwp_text_input_v3 / TSF / XIM), live font zoom with Ctrl+Wheel.
  • Familiar selection and scrolling. Linear / rectangle / word / line selection with xterm gestures; Linux PRIMARY auto-copy and middle-click paste (the gesture table).
  • Hot config reload. Edit the config and save; theme and font changes apply without restarting the daemon or losing scrollback.

Configuration

One TOML file at the platform-native path, hot-reloaded on save (and on Ctrl+Shift+R). Every field — font, theme, palette, cursor, clipboard, window, keymap — is documented with its defaults and failure behaviour in the configuration page; the default chord table and the deliberately-unbound session actions are in keybindings. A generated JSON Schema gives TOML-aware editors completion and validation.

Install with Nix

nix run / nix profile install above are the whole story for a quick try. For a managed setup the flake also exposes the package, a home-manager module (installs the binaries, writes config.toml from Nix, wires TERMINFO_DIRS), and a Stylix hook that drives the palette, font, and background opacity from the active scheme — all documented on the install guide.

CLI and automation

felis sessions <verb> exposes every IPC operation a daemon accepts as a typed shell command — list, info, spawn, send, capture, search, kill — with --json on every read verb and exit codes under contract (0 ok, 1 operation-domain failure, 2 no daemon / protocol error). Common patterns — pre-warming a session, grepping scrollback, snapshotting for a bug report, reaping stale sessions, editor integration — are collected in the how-to guides with the verb table in the CLI reference; the full surface contract and the wire it rides live in docs/reference/ipc.md, which cross-language clients can speak directly.

Status

felis is a prototype, and runnable as one.

Works today:

  • Day-to-day shell + editor use. neovim, helix, lazygit, fish, fzf, and similar TUIs behave correctly: mouse, focus tracking, bracketed paste, OSC 8 hyperlinks, OSC 133 semantic prompts, the Kitty keyboard protocol, synchronized output, true colour, italics, and the full xterm underline range.
  • Persistent sessions. Close the window, list with felis sessions list, reattach with felis attach <id>.
  • Cross-host attach. felis --host user@remote over plain SSH stdio.
  • CLI for scripted automation. felis sessions {list, info, send, capture, spawn, search, kill} — see "CLI and automation" above.
  • Kitty graphics (yazi, mdcat, presenterm previews, mpv video): protocol, daemon-side image store, renderer atlas, animation (a=a/f/c), and shared-memory transmission (t=s) all wired.
  • Kitty text-sizing protocol (OSC 66): renderer paints scaled multi-cell runs at integer and fractional sizes.
  • Scrollback search (REQ-607): daemon-side substring + regex, exposed via felis sessions search and the in-window Ctrl+Shift+F bar with highlighted matches and n / N navigation.
  • OpenType font features and programming-font ligatures, opt-in via font.features (defaults off so cell metrics never shift unasked).
  • IME on macOS / Wayland / Windows / X11; per-codepoint font fallback covering CJK, symbols, colour emoji, and Nerd-Font glyphs.
  • xterm-flavoured selection, clipboard, scrollback, and font zoom (Cmd+* on macOS, Ctrl+Shift+* elsewhere).
  • Hot config reload — theme and font changes swap into the running client without restarting the daemon.
  • Bidi-override visible markers (Trojan-Source protection).

Not yet (polish and backlog, not core-functionality gaps):

  • Read-only attach (lifts the "session must be detached" limitation on felis sessions send / capture / search).

The full list of open work lives in docs/backlog.md.

Design

felis is not defined by how many features it has — it redraws the line of what a terminal is responsible for, pulling inward what the conventional stack pushed out and pushing outward what a terminal conventionally pulls in:

  • Pulled inward. Session ownership and persistence — felis's own daemon owns the PTY, grid, and scrollback, so the shell outlives the window — and full-fidelity rendering, because nothing sits between the shell and the screen to flatten Kitty graphics or text sizing.
  • Pushed outward. Layout (tabs, panes, splits) to the window manager; extension to external processes over a versioned IPC, never an embedded Lua / Python / Wasm host; intent-guessing (URL detection, content sniffing, smart quoting) nowhere at all — the user's bytes stay the user's bytes.

Inside that boundary felis is maximalist — render everything, fast. At the boundary it is strict — delete before adding. No second system lives inside the terminal.

The full rationale is in docs/explanation/vision.md; the eight non-negotiable principles, each paired with a concrete violation test, live in docs/explanation/principles.md.

Building from source

felis is a prototype; to hack on it, build from cargo. The Nix flake at the repo root is the single source of truth for the toolchain (nightly Rust, the runtime libs wgpu / winit need, the formatter, and the pre-commit hook).

# Drop into the dev shell — installs the pre-commit hook automatically.
nix develop

# Run the daemon, then the client.
cargo run -p felis-daemon -- serve &
cargo run -p felis-client

# List detached sessions and reattach by id prefix.
cargo run -p felis-cli -- sessions list
cargo run -p felis-cli -- attach <id-prefix>

# Cross-host attach over SSH.
cargo run -p felis-client -- --host user@remote

Documentation

All documentation — the end-user manual and the design docs — is published at https://felis-docs.pages.dev and lives in this repository under docs/ — next to the code it documents, so it cannot rot; the felis-docs repository only holds the site machinery and vendors the content via a submodule.

For contributors, docs/README.md is the entry point. The design docs are the source of truth for every feature decision — each records the rationale and the rejected alternatives alongside the current design.

License

Apache-2.0. See LICENSE.