• v0.2.0 d27c5ba881

    v0.2.0
    All checks were successful
    pr / nix flake check (push) Successful in 9s
    darwin / detect relevant changes (push) Successful in 8s
    windows / detect relevant changes (push) Successful in 10s
    darwin / darwin docs-only no-op (push) Has been skipped
    windows / windows docs-only no-op (push) Has been skipped
    darwin / build felis (aarch64-darwin) (push) Successful in 1m35s
    pr / cargo build / clippy / test / deny (push) Successful in 1m45s
    pr / wire schema is compatible with the base (push) Successful in 4s
    windows / cargo nextest (Windows) (push) Successful in 3m7s
    pr / frontend smoke (x86_64-linux) (push) Successful in 2m1s
    windows / cargo clippy (Windows cross) (push) Successful in 16s
    release / the tag names this exact revision (push) Successful in 40s
    windows / frontend smoke (Windows) (push) Successful in 1m57s
    release / package felis (aarch64-darwin) (push) Successful in 58s
    release / nix flake check (push) Successful in 1m27s
    release / cargo nextest (Windows) (push) Successful in 2m48s
    release / cargo build / clippy / test / deny (push) Successful in 2m9s
    release / wire schema is compatible with the newest release (push) Successful in 12s
    release / frontend smoke (x86_64-linux) (push) Successful in 10s
    release / package felis (x86_64-linux) (push) Successful in 30s
    release / frontend smoke (Windows) (push) Successful in 1m55s
    release / publish the release page (push) Successful in 15s
    Stable

    natsukium released this 2026-10-14 11:37:33 +09:00 | 186 commits to main since this release

    Signed by natsukium
    SSH key fingerprint: SHA256:ByU5VseUJyH2UYFs65yNYhqJHhqJzP8BNBlvG0a6WOs

    felis v0.2.0, built from d27c5ba88138912dbe2da5f6dcb63f7337c003bd.

    Downloads

    • Linux (x86_64): felis-x86_64-linux.tar.gz. Unpack it anywhere and run bin/felis.
    • macOS (Apple silicon): felis-aarch64-darwin.tar.gz. Before the first run, clear the quarantine flag: xattr -dr com.apple.quarantine <directory>. Then run bin/felis or open felis.app.
    • Windows (x86_64): felis-x86_64-pc-windows-msvc.zip. Unpack its three executables into one directory on your PATH and run felis.exe.
    • Nix: nix run github:felis-terminal/felis/v0.2.0. The flake offers prebuilt binaries from https://nix-cache.natsukium.com.
    • Homebrew (macOS Apple silicon, Linux x86_64): brew install felis-terminal/tap/felis.

    Step-by-step instructions are in install.md.

    These three platforms are supported. ARM64 Linux (aarch64-linux) builds from source through the flake but is not supported.

    felis.proto and felis-config.schema.json are the IPC wire schema and the config schema of this build, for tools that talk to felis or validate its config.

    Changes

    The daemon can now replace itself with a new build without ending your sessions, and scripts and agents can do more in a
    session.

    • In-place daemon upgrade: felis daemon upgrade replaces the running daemon with the installed build on Linux and
      macOS, keeping every session and its screen.
    • Mouse input from the command line: felis sessions send --mouse click:12,4 clicks, drags and scrolls in a
      fullscreen program, at the cells felis sessions search --source visible finds on the live screen.
    • Program status: a program that reports its state over OSC 7501, such as a coding agent waiting on you, shows it in
      felis sessions list and felis sessions info.
    • Bug reports: felis doctor report prints the builds, platform, GPU, fonts and non-default config as Markdown to
      paste into a report.
    • Fonts through fontconfig on Linux: the default face, font.family and the fallback order follow the host's
      fontconfig.
    • Kitty graphics as in kitty: placement, deletion, scrolling, eviction and replies now follow kitty, and
      kitten icat over ssh and yazi's previews draw whole.

    Upgrading from 0.1:

    • A 0.1 daemon predates the upgrade request, so restart it once after installing 0.2.0; for example,
      felis daemon stop --when-empty stops it after its last session ends.
    • On Linux, felis now needs libfontconfig.so.1 on the host (libfontconfig1 on Debian and Ubuntu).

    Added

    • CLI: felis sessions send --mouse EVENT (repeatable) clicks, drags and scrolls in a fullscreen program at a
      0-based cell, [MODS+]ACTION[-BUTTON]:COL,ROW (for example --mouse click:12,4), the cell numbers
      search --source visible returns. The daemon encodes each event against the program's current mouse modes, and under
      SGR-pixels (?1016) reports the middle of the cell. The verb exits 1 under invalid_request with nothing sent when
      the program has no reporting on for an event or the cell is off the grid. --key and --mouse go out in command-line
      order.
    • CLI: felis daemon upgrade asks the running daemon to replace its binary in place with the felis-daemon
      installed beside felis, keeping every session. It runs on Linux and macOS; on Windows it refuses with unsupported.
      A refusal leaves the daemon serving and names its reason (--format json puts it in error.reason);
      felis daemon stop --when-empty remains the fallback. The verb never starts a daemon; with --host it runs the
      remote host's own felis daemon upgrade over ssh.
    • Wire: protocol minor 1 adds Ops::Upgrade and Ops::UpgradeReply, with the closed refusal enum UpgradeRefusal.
      A client refuses to send Upgrade to a minor-0 daemon, so felis daemon upgrade against an older daemon exits 2
      with unsupported.
    • CLI: felis sessions search --source visible searches only the live screen, including the alternate screen a
      fullscreen program draws on, so its col_spans give the cell columns of on-screen text. --source scrollback names
      the default, which is unchanged. The stdio bridge's sessions.search takes the same values as source.
    • Wire: protocol minor 1 also adds SearchQuery.source with the closed enum SearchSource. A client refuses to
      send a non-default source to a minor-0 daemon, which would ignore it and search scrollback, so --source visible
      against an older daemon exits 2 with unsupported.
    • CLI: felis doctor notes on the daemon row when the running daemon is a build other than the installed
      felis-daemon, naming the installed one.
    • CLI: felis doctor report prints the doctor checklist plus the builds, OS, display server, GPU driver version,
      resolved fonts, non-default config keys, and log paths as Markdown to paste into a bug report, or as JSON with
      --format json. Paths under your home directory print as ~, and keybinding commands and texts are redacted.
    • CLI: felis doctor's gpu row names the driver version where the backend reports one.
    • Protocol / CLI: programs that report their status over OSC 7501 (experimental, spec revision 0.4) show it in
      felis sessions list --format json and felis sessions info, so a script can find the session whose program is
      waiting on you. felis answers the OSC 7501 ; ? detection query and draws nothing.
    • Wire: protocol minor 1 adds SessionInfo.program_status; a minor-0 client skips it as an unknown field.

    Changed

    • CSI 14 t and CSI 16 t now answer the text area and the cell size in pixels from the PTY's winsize, instead of
      0;0. CSI 13 t and CSI 15 t (window position, screen size) still answer 0;0.
    • Kitty graphics replies now follow kitty: only a command with a non-zero i= or I= is answered, a delete is never
      answered even when it fails, a query is answered only when it carries i=, and a malformed escape or an unknown a=
      is dropped without a reply. A command with both i= and I= is refused with EINVAL before it runs. An i=0 or
      I=0 is the same as no id, so a=t,i=0 stores an anonymous image instead of failing. Error codes match kitty's: a
      t=f/t=t/t=s source that cannot be read is EBADF (was EIO), PNG data that does not decode is EBADPNG (was
      EBADF), bad base64 or zlib data is EINVAL (was EBADF), and raw pixel data shorter than s=×v= is ENODATA
      (was EINVAL).
    • On Linux, fonts are chosen by the host's fontconfig: the default face is fc-match monospace, font.family and the
      [font.bold]/[font.italic] families resolve through it (so fonts.conf aliases apply), and with font.fallback
      unset the fallback order is fontconfig's ranked list for the primary, following its prefer lists, per-language rules
      and rejected fonts and the locale's language. A text-presentation symbol takes a monochrome face before an
      earlier-ranked color one. Bitmap (PCF, BDF) and Type 1 faces that fontconfig lists are left out, so a family with only
      such faces counts as not installed. felis now needs libfontconfig.so.1 on the host (libfontconfig1 on Debian and
      Ubuntu).
    • A font.fallback list longer than 62 entries keeps the first 62, with a warning.

    Fixed

    • A new session's program sees the window's size from its first TIOCGWINSZ; it used to see 24 × 80 until the resize
      that followed the spawn landed.
    • On macOS, a session's shell no longer inherits the PTY master of a session created at the same moment.
    • A Textual app with mouse support no longer crashes on its first click. The ?2048 resize report now carries the PTY's
      pixel extents instead of 0, and reports a change of those extents alone; a session no window has sized gets 8 × 16
      pixels per cell in its winsize and in the report.
    • A felis verb piped into a reader that quits early (felis sessions capture <id> | head -1) no longer panics with
      "failed printing to stdout: Broken pipe". It stops at that write and exits 0.
    • A felis bridge stream canceled by a cancel op or by closing stdin no longer ends with a clean end whose count
      claims items it never wrote. Items that reach the bridge after the cancel are dropped, and the clean end now counts
      only the item lines that preceded it.
    • A pipe or run keybinding no longer gets refused with "this pipe/run visit still owes a return" on a session that a
      run command switched the window to (a session picker running felis sessions switch, say). The refusal used to hold
      until some other switch moved the window.
    • A Kitty graphics placement with only c= or only r= now keeps the image's aspect ratio, as kitty does: the other
      axis follows from the given one, and the image draws at that size instead of stretching to a box whose missing axis
      came from the image's own size.
    • Searching a long soft-wrapped line with many matches (felis sessions search, the search overlay) no longer stalls
      the session: mapping each match to screen columns rescanned every row of the line, so a grid-sized line held the
      session's lock, and froze its output, for billions of steps.
    • A case-insensitive search without --regex (felis sessions search --case-insensitive, the search overlay) now
      reports byte_spans and col_spans at the match itself on a line where a character before it changes length when
      lowercased, such as İ or the Kelvin sign; they used to land shifted, sometimes past the end of the text.
    • Clearing the screen now treats Kitty graphics images as kitty does. ED 2 (clear), DECSED 2 and RIS drop every
      placement on the screen, C=1 ones included, and ED 3 drops scrollback placements too; each then frees every image
      nothing displays, so transmitted-but-unplaced images no longer pile up until the byte cap evicts them. Partial erases
      (ED 0, ED 1, EL) and the soft reset DECSTR no longer remove images, so redrawing one line next to an image
      keeps it.
    • A Kitty graphics image sent without i= or I= is now freed once a delete or a scroll leaves it without a placement,
      as in kitty; nothing could display it again, yet it used to hold its share of the byte cap until evicted.
    • When Kitty graphics images fill the session's byte cap, felis now evicts as kitty does: every image without a
      placement first, then the least recently transmitted or placed. It used to evict strictly in transmit order, so an
      image redrawn a moment ago could go before a stale one.
    • A Kitty graphics Unicode-placeholder cell colored with a 256-color foreground (\e[38;5;Nm) now shows image N, as
      kitty does; only a 24-bit foreground used to name the image.
    • A Kitty graphics Unicode-placeholder cell with the default foreground no longer borrows the image id of the cell
      before it; as in kitty its foreground counts as 0 in the id. A cell without the id's high-byte diacritic now takes
      it from its run.
    • Kitty graphics file (t=f) and temp-file (t=t) transmissions now read the S= bytes at offset O=, as kitty does;
      they used to read the whole file.
    • A reply to a Kitty graphics frame command (a=f) now echoes its r=, as kitty does.
    • A Kitty graphics placement with only some of x, y, w, h now crops as kitty does: x and y default to 0 and
      w and h to the image's size. It used to draw the whole image unless all four were given.
    • A Kitty graphics placement whose image was evicted in the same burst that placed it no longer lingers in the window.
    • Kitty graphics images inside a scroll region (DECSTBM) now scroll with its text, as in kitty, and so do images on a
      reverse scroll (RI, SD). An image the scroll pushes partly past a margin is cut off there, and one pushed wholly
      out of the region is removed. They used to stay put while the text moved under them.
    • The alternate screen now has a Kitty graphics image store of its own, as in kitty. A full-screen program can no longer
      free the images on the shell screen it covers, whether by clearing its screen, deleting by id range, or filling the
      byte cap, and an image it sends with an id the shell already used no longer replaces the shell's image. Leaving the
      alternate screen with ?47l keeps its images along with its text for the next ?47h, as in kitty.
    • Kitty graphics Unicode-placeholder placements of one image now stay apart by p=, and a placeholder cell picks its
      placement by its underline color, as in kitty; felis kept one per image, so a second placement resized the first's
      tiles. d=i/I, d=n/N, d=r/R and transmitting the image again now remove its placeholder placements too.
    • Kitty graphics image numbers (I=) now work as in kitty. Each transmission with I= stores a new image under a free
      id and answers i=<id>,I=<number>; a put or animation command by I= acts on the newest image with that number; and
      the d=n/d=N deletes are supported. felis used to take the number as the image id, so a second transmission with
      the same number replaced the first.
    • A Kitty graphics image shown through Unicode-placeholder cells now keeps its aspect ratio, centered in the placement's
      c= by r= box, as kitty does; it used to be stretched to fill the box.
    • A Kitty graphics d=a or d=A delete now leaves Unicode-placeholder images and images scrolled wholly into history
      in place, as kitty does. It used to drop placeholder images in the daemon without telling the window, so they vanished
      once the window reattached.
    • A Kitty graphics put, delete, compose or animation command sent while a chunked (m=1) transmission is open now runs
      at once, as kitty does; it used to be taken as the transmission's last chunk. A delete drops the open transmission;
      the others leave it open. A non-zero q= on a later chunk now overrides the first chunk's.
    • Placing the same Kitty graphics image twice without a placement id now shows two copies, as kitty does; the second put
      used to move the first. A reply no longer echoes p=0.
    • Sending a Kitty graphics image again under the same id now removes its old placements, as kitty does; they used to
      stay on screen showing the new pixels.
    • Deleting one placement of a Kitty graphics image with an uppercase delete (d=I with p=, or d=P/Q/X/Y/Z/
      C/R) now keeps the image while other placements still show it, as kitty does; it used to free the image and orphan
      them.
    • kitten icat --transfer-mode=stream, the mode icat uses over ssh, now shows its images. felis cut every Kitty
      graphics command at 8 KiB, while kitty's own client sends up to 128 KiB at once; the limit is now kitty's 256 KiB.
    • An image that a program places one cell at a time, as yazi's previewer does, now draws whole. Past the first 64
      placements in one write, the rest were dropped and the bell rang, leaving only a few strips of the image.
    • A program repeating Kitty graphics puts no longer grows the daemon and every attached window without bound. Each
      screen keeps at most 16384 placements, and a put past that removes the oldest one.
    Downloads