- Rust 91.8%
- Python 6.7%
- Nix 0.7%
- Shell 0.3%
- WGSL 0.2%
- Other 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
fuzz / cargo fuzz nightly long-run (push) Has been skipped
pr / frontend smoke (x86_64-linux) (push) Successful in 55s
windows / detect relevant changes (push) Successful in 6s
windows / windows docs-only no-op (push) Has been skipped
pr / publish felis (x86_64-linux) (push) Successful in 12s
windows / frontend smoke (Windows) (push) Successful in 1m38s
darwin / detect relevant changes (push) Successful in 4s
darwin / darwin docs-only no-op (push) Has been skipped
darwin / build felis (aarch64-darwin) (push) Successful in 51s
fuzz / cargo fuzz smoke (per target) (push) Successful in 1m6s
pr / nix flake check (push) Successful in 8s
pr / cargo build / clippy / test / deny (push) Successful in 1m37s
pr / wire schema is compatible with the base (push) Successful in 13s
windows / cargo clippy (Windows cross) (push) Successful in 12s
windows / cargo nextest (Windows) (push) Successful in 4m17s
windows / package felis (x86_64-pc-windows-msvc) (push) Successful in 1m45s
Implements the decision on #296: [remove operation ids from the v0.1 contract](#296 (comment)) (option 1).
## Why
No message ever read the daemon's landing records back, so the minted token named a fan-out nothing could ask about: the CLI and the bridge report `queued` and exit, and the shipped agent skill lists the switch verbs under the surfaces it does not cover. Freezing `<incarnation:016x>-<sequence>` as contract before the consumer that reads it exists is the worst of both options, since a field whose meaning arrives later cannot change shape when it does. Adding the id back is an additive minor whenever a consumer appears.
## Scope of the removal
- **Proto**: `OperationId`, `OperationEcho`, `OpsSwitched.operation_id`, `PushReattach.operation`, `PushRetargetHost.operation`, `SessionAttach.operation`. Tags are left free, not reserved; the break is acknowledged with a `base:` entry in `crates/felis-protocol/proto/BREAKING.md`, and the codegen is regenerated.
- **Protocol crate**: `MINOR_OPERATION_IDS` and its re-export, the four gated-field entries and their tests, the domain types, and the converts. The minor-8 ledger row keeps `Spawn` / `Created` and the `accepted` → `queued` rename.
- **Daemon**: `src/operations.rs` and the `Operations` handle on the pool, the mint at the `OpsSwitch` handler, the `land` call at the attach path, and the per-target stamping in the session task. A switch push now answers with a count rather than the list of attachment ids nothing reads.
- **Client**: the echo threading through `felis-client-core` (`connector.rs`, `dial.rs`) and `felis-client`; `attach_landing` folds back into `attach`.
- **CLI / bridge**: `operation_id` leaves the `switch`, `ssh` and `window retarget` objects and the bridge results, and `crates/felis-cli/schemas/felis-cli-v1.schema.json` is regenerated. The bridge goldens held no successful `sessions.switch` result, so a success-path test now pins `{from, to, queued}`.
- **Kept**: `queued` as the sole completion claim, `not_queued` as its error kind.
- **Docs**: `docs/reference/ipc.md` (Switch/Switched, the deleted "Operation ids" section, the minor-8 row), `docs/reference/cli.md`, the rewritten "What a switch reply can report" in `docs/explanation/architecture/ipc.md`, and a `CHANGELOG.md` entry for the machine-output and bridge-result key.
The skew outcome is stated in `BREAKING.md` rather than handled: the removed fields decode as unknown and therefore absent in both directions, so the switch path works between a daemon and a client on either side of the change.
## Verification
- `just check` green (fmt, clippy, tests, prose-check, skill-check, docs-links, deny, proto-compat, release-check). The pre-commit `buf breaking` hook has no acknowledgment path, so the commits skip that one hook; the CI gate `just proto-compat` reads `BREAKING.md` and passes.
- `grep -rn 'OperationId\|OperationEcho\|MINOR_OPERATION_IDS\|operation_id' crates/ docs/ skills/` returns only `BREAKING.md`.
- Two `openai-codex/gpt-5.6-sol` reviews: round 1 PASS with two non-blocking findings (vestigial `Vec<u64>` in `PushOutcome`; the nonzero-`queued`-at-minor-0 invariant left unpinned), both fixed in
|
||
| .agents/skills | ||
| .cargo | ||
| .claude | ||
| .config | ||
| .forgejo | ||
| crates | ||
| dev | ||
| docs | ||
| fuzz | ||
| nix | ||
| share/terminfo | ||
| skills/felis | ||
| tests | ||
| tools | ||
| .gitattributes | ||
| .gitignore | ||
| AGENTS.md | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CHANGELOG.md | ||
| CLAUDE.md | ||
| clippy.toml | ||
| CONTRIBUTING.md | ||
| deny.toml | ||
| flake.lock | ||
| flake.nix | ||
| justfile | ||
| LICENSE | ||
| README.md | ||
felis
A terminal for your toolkit. Not an environment.
felis is a GPU-accelerated terminal that keeps your sessions alive and renders modern terminal protocols, while leaving layout to your window manager and automation to external tools.
What felis owns
- Sessions, beyond the window. Close the window without killing the shell. Reattach locally or over SSH with the grid and scrollback still in the daemon. Sessions live in memory, not across daemon restarts or reboots.
- Modern terminal rendering. Kitty graphics and text sizing, true color, ligatures, font fallback, and native IME, without an in-terminal multiplexer between the application and the renderer.
- An interface for your tools. Typed CLI commands and a public, versioned IPC let scripts and programs drive sessions, send input, and read output.
The rest stays yours
Your window manager handles layout. Your shell and tools handle workflow. felis provides the interfaces for those tools to work with it, without moving their logic inside the terminal. There are no in-terminal tabs or splits, and no embedded scripting language.
Small utilities belong alongside the terminal: a session picker, a notification consumer, an editor integration. They can use the same public interfaces as your own scripts. The point is to let you choose and combine tools, not require an entire toolset to use a terminal.
Read the vision for where felis draws the line, and the design values for why.
Quick start
With Nix and flakes enabled:
nix run git+https://git.natsukium.com/natsukium/felis
A window opens on a fresh session, and the shell inside it keeps running once you close the window. The install guide covers the profile, home-manager, Stylix and from-source paths; your first session walks the close-and-reattach loop.
Working with your tools
felis --host user@remote puts a local window on a remote daemon that keeps its sessions across disconnects
(attach over SSH). Session verbs speak JSON (--format json or jsonl) and
felis bridge keeps a connection open for a longer-lived integration
(driving a session without a window, CLI reference,
IPC protocol). One TOML file controls fonts, colors, keybindings, and window appearance
(configuration, keybindings).
Status and compatibility
felis is at 0.1.0, an early release distributed as a Nix flake. The CLI and IPC are versioned, but expect edges.
x86_64-linux has a passed runtime release gate and a published build. x86_64-pc-windows-msvc passes the runtime gate
but is available as a CI artifact rather than a distribution. aarch64-linux and aarch64-darwin build and run tests
in CI; neither has a claimed runtime release gate. See the
platform matrix for details and the
protocol support matrix for terminal compatibility.
Open work is tracked in Forgejo issues.
Documentation and development
The manual is published at https://felis-docs.pages.dev; its source and full index live in docs/.
To understand the design, read the vision, values, and testable principles, in that order. For architecture and comparisons with other terminals, follow the explanation index.
felis is written in Rust with wgpu and winit. The Nix flake owns the development toolchain:
nix develop
cargo build
See CONTRIBUTING.md for the development workflow and verification gates, or the install guide to build and run from source.
License
Apache-2.0. See LICENSE.
One file is excepted: tools/bench/doom-fire-bench.patch is GPL-3.0-or-later, since a patch carries the upstream lines
it modifies and DOOM-fire-zig is GPLv3. It is applied to that project's source at build time by the benchmark dev shell
and reaches no felis binary.