Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/benchmarks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ concurrency:

jobs:
tools:
name: fetch + games
name: fetch + games + lazybox
runs-on: ubuntu-latest
timeout-minutes: 15

Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ path = "src/bin/fetch.rs"
name = "games"
path = "src/bin/games.rs"

[[bin]]
name = "lazybox"
path = "src/bin/lazybox.rs"

[dependencies]
crossterm = { version = "=0.29.0", default-features = false, features = ["events", "windows"] }

Expand Down
28 changes: 22 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@
Small native terminal tools with one job each:

- `fetch` shows a fast system and CPU overview with styled, plain, or JSON output.
- `games` is an original native terminal arcade with eleven complete game modes.
- `games` is a native clean-room terminal arcade with eleven complete game modes.
- `lazybox` is one direct dashboard for Docker containers, Apple containers, and Slurm jobs.

The binaries never download or install software. `games` uses Crossterm for
raw keyboard input and terminal restoration; no upstream game code, assets, or
runtime is bundled.
The binaries never download or install software. `games` and `lazybox` use
Crossterm for raw keyboard input and terminal restoration; no upstream TUI,
game code, or assets are bundled.

## Install

After the first release is published, install both commands from its native
After the first release is published, install all commands from its native
PyPI wheel:

```sh
Expand All @@ -30,6 +31,7 @@ Rust 1.97.1 is pinned for builds.
cargo build --release --locked
./target/release/fetch
./target/release/games
./target/release/lazybox
```

Useful non-interactive forms:
Expand All @@ -40,6 +42,8 @@ fetch --json
games list --json
games info merge
games run merge --seed 1
lazybox snapshot --backend auto --json
lazybox open --backend docker
```

`NO_COLOR=1` disables decoration. Interactive play needs a real terminal and
Expand All @@ -54,14 +58,26 @@ informed only by the high-level behavior of the research projects. Sessions
are finite and seeded; v1 has no network, audio, persistence, plugins, external
executables, or copied assets.

## Unified operations

`lazybox` auto-detects a responding Docker, Apple Container, or Slurm backend,
then renders its own consistent dashboard. It calls only the installed native
CLI for inventory, details, bounded container logs, and confirmed start, stop,
restart, or cancellation actions. It never invokes Lazydocker, Lazyslurm, or
Lazycontainer. Plain and JSON snapshots make the same inventory usable in
scripts without entering raw terminal mode. Backend calls stop after 30 seconds
and capture at most 8 MiB of combined output.

## Scope

`fetch` has detailed native collection on macOS and Linux and a smaller
environment-backed view on Windows. It is intentionally not a configurable
replacement for every Fastfetch module. See [research](docs/research.md) and
[fetch benchmarks](benches/fetch.md) for the measured comparison and limits.
The [games benchmark](benches/games.md) measures the native arcade's owned
non-interactive path and binary size.
non-interactive path and binary size. The [lazybox benchmark](benches/lazybox.md)
measures native CLI startup and package size without pretending backend latency
belongs to the dashboard.

## Project

Expand Down
5 changes: 0 additions & 5 deletions benches/games.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,6 @@ Measured 2026-08-14 on Darwin 25.5.0 ARM64 with Rust 1.97.1 and Hyperfine
The two list commands are not equivalent workloads, so their timing difference
is not a performance claim. The native binary adds 84,496 bytes (about 24%)
for eleven game engines and cross-platform terminal input.
The macOS ARM64 wheel is 410,899 bytes, including both binaries, package
metadata, project and dependency licenses, and CycloneDX SBOM; that is 67,191
bytes (about 20%) over
the prior 343,708-byte wheel.

Run the same benchmark used by CI:

```sh
Expand Down
26 changes: 26 additions & 0 deletions benches/lazybox.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Lazybox benchmark

`lazybox --version` measures native process startup without requiring Docker,
Apple Container, or Slurm. Backend snapshot latency belongs mostly to the
selected external CLI and environment, so it is not used to claim that
Lazybox is faster than the three unrelated upstream applications.

Measured 2026-08-17 on Darwin 25.5.0 ARM64 with Rust 1.97.1 and Hyperfine
1.20.0, using 100 runs after 20 warmups:

| Command | Mean ± standard deviation | Stripped binary |
| --- | ---: | ---: |
| `lazybox --version` | 1.5 ± 0.1 ms | 422,384 bytes |

The macOS ARM64 wheel is 625,260 bytes for all three binaries, package
metadata, project and dependency licenses, and the CycloneDX SBOM.

Run 100 measured launches after 20 warmups and record all three packaged tools:

```sh
cargo build --release --locked
RUNS=100 WARMUP=20 bash benches/run.sh
```

Results and stripped binary sizes are written below `target/benchmarks/`.
Use one host and backend fixture for any future end-to-end comparison.
8 changes: 6 additions & 2 deletions benches/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ warmup=${WARMUP:-20}
output="$project/target/benchmarks"
fetch="$project/target/release/fetch"
games="$project/target/release/games"
lazybox="$project/target/release/lazybox"

command -v hyperfine >/dev/null 2>&1 || {
echo "benchmarks: install hyperfine 1.20.0" >&2
exit 127
}
for binary in "$fetch" "$games"; do
for binary in "$fetch" "$games" "$lazybox"; do
[[ -x "$binary" ]] || {
echo "benchmarks: run cargo build --release --locked first" >&2
exit 2
Expand All @@ -25,10 +26,12 @@ hyperfine --shell=none --warmup "$warmup" --runs "$runs" \
--export-json "$output/tools.json" \
--export-markdown "$output/tools.md" \
--command-name fetch "$fetch --plain" \
--command-name games "$games list --plain"
--command-name games "$games list --plain" \
--command-name lazybox "$lazybox --version"

fetch_size=$(wc -c < "$fetch" | tr -d ' ')
games_size=$(wc -c < "$games" | tr -d ' ')
lazybox_size=$(wc -c < "$lazybox" | tr -d ' ')
{
echo "## Keys Tools benchmarks"
echo
Expand All @@ -42,6 +45,7 @@ games_size=$(wc -c < "$games" | tr -d ' ')
echo "| --- | ---: |"
echo "| fetch | $fetch_size |"
echo "| games | $games_size |"
echo "| lazybox | $lazybox_size |"
echo
echo "Hyperfine $runs runs after $warmup warmups; hosted-runner results are informational."
} > "$output/summary.md"
7 changes: 6 additions & 1 deletion docs/research.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,16 @@ or assets are copied into Keys Tools.
high-level behavior. It uses an original implementation, maps, text, words,
and ASCII assets; no upstream source, data, branding, save format, or executable is
copied, linked, or launched.
- `lazybox` owns a small dashboard and directly calls the installed `docker`,
Apple `container`, or Slurm commands. It does not launch or embed Lazydocker,
Lazycontainer, or Lazyslurm. One row model replaces their unrelated UI and
dependency stacks; backend-specific operations remain direct `match` arms,
not a plugin or trait layer.
- Crossterm is the only runtime dependency and owns cross-platform raw input,
resize events, alternate-screen entry, and terminal restoration. Ratatui is
excluded because these fixed layouts do not need a widget or layout engine.
- PyPI distribution uses [maturin binary bindings](https://www.maturin.rs/bindings.html),
which put native binaries on the environment `PATH`. PyO3 is excluded because
the commands expose no Python API; native wheels are required per
OS/architecture. [`uv tool install`](https://docs.astral.sh/uv/concepts/tools/)
then exposes both commands from an isolated tool environment.
then exposes all commands from an isolated tool environment.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ readme = "README.md"
requires-python = ">=3.9"
license = "MIT"
authors = [{ name = "Keys" }]
keywords = ["cli", "terminal", "system-information", "games"]
keywords = ["cli", "terminal", "system-information", "containers", "slurm", "games"]
classifiers = [
"Environment :: Console",
"Programming Language :: Rust",
Expand Down
1 change: 1 addition & 0 deletions scripts/smoke-wheel.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ fi
"$python_bin" -m pip install --disable-pip-version-check --no-cache-dir --no-deps "$1"
"$bin_dir/fetch$suffix" --json
"$bin_dir/games$suffix" list --json
"$bin_dir/lazybox$suffix" --version
9 changes: 9 additions & 0 deletions src/bin/lazybox.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
fn main() {
match keys_tools::lazybox::run(std::env::args().skip(1).collect()) {
Ok(code) => std::process::exit(code),
Err(error) => {
eprintln!("lazybox: {error}");
std::process::exit(2);
}
}
}
44 changes: 5 additions & 39 deletions src/games.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,13 @@ mod turns;
use std::io::{self, IsTerminal as _, Write};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};

use crossterm::cursor::{Hide, MoveTo, Show};
use crossterm::cursor::MoveTo;
use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
use crossterm::queue;
use crossterm::style::{Color, Print, ResetColor, SetForegroundColor};
use crossterm::terminal::{
BeginSynchronizedUpdate, Clear, ClearType, DisableLineWrap, EnableLineWrap,
EndSynchronizedUpdate, EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode,
enable_raw_mode, size,
};
use crossterm::{execute, queue};
use crossterm::terminal::{BeginSynchronizedUpdate, Clear, ClearType, EndSynchronizedUpdate, size};

use crate::terminal::Session;
use crate::{VERSION, json_string, use_color};

const HELP: &str = "Native clean-room terminal arcade\n\nUsage:\n games\n games list [--plain | --json]\n games info NAME\n games run NAME [--seed NUMBER]\n\nOptions:\n --plain Stable text without decoration\n --json Machine-readable game list\n --seed NUMBER Reproducible randomized setup\n -h, --help Show this help\n -V, --version Show the version\n\nInteractive controls: arrows or WASD; Esc returns to the menu. Most modes use r to restart and q to quit; text modes use Ctrl-R and Ctrl-C.";
Expand Down Expand Up @@ -284,7 +281,7 @@ fn session_seed() -> u64 {
}

fn interactive(initial: Option<&str>, mut seed: u64) -> Result<i32, String> {
let _terminal = TerminalSession::enter().map_err(|error| format!("terminal: {error}"))?;
let _terminal = Session::enter().map_err(|error| format!("terminal: {error}"))?;
let mut stdout = io::stdout();
let mut selected = initial
.and_then(|name| MODES.iter().position(|mode| mode.name == name))
Expand Down Expand Up @@ -519,37 +516,6 @@ fn write_centered<W: Write>(
Ok(())
}

struct TerminalSession;

impl TerminalSession {
fn enter() -> io::Result<Self> {
enable_raw_mode()?;
if let Err(error) = execute!(io::stdout(), EnterAlternateScreen, Hide, DisableLineWrap) {
restore_terminal();
return Err(error);
}
Ok(Self)
}
}

impl Drop for TerminalSession {
fn drop(&mut self) {
restore_terminal();
}
}

fn restore_terminal() {
let _ = execute!(
io::stdout(),
EndSynchronizedUpdate,
ResetColor,
Show,
EnableLineWrap,
LeaveAlternateScreen
);
let _ = disable_raw_mode();
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading
Loading