Skip to content

About

Lo-fi, ambience, and a focus timer in your terminal

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Repository files navigation

chill

A terminal player for focused work. It plays lo-fi from a hosted catalog or from YouTube, layers ambient sound underneath, and runs a focus timer that keeps track of the time you actually spent focusing. It ships as one binary for macOS and Windows.

chill playing a Lofi Girl stream with the Rainy study mix and a focus block running

Install

Prebuilt binaries cover Apple Silicon Macs and 64-bit Windows.

# macOS
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/Aquitano/chill-flow/releases/latest/download/chill-installer.sh | sh
# Windows
powershell -ExecutionPolicy Bypass -c "irm https://github.com/Aquitano/chill-flow/releases/latest/download/chill-installer.ps1 | iex"

Both put chill in ~/.cargo/bin and add it to PATH. chill update installs the latest release over an installation made this way; versions before 0.4.0 rerun the installer instead. On any other platform, build it with the Rust toolchain from rustup.rs (on Windows that also needs the Visual Studio C++ build tools):

cargo install --git https://github.com/Aquitano/chill-flow          # add --force to update

chill plays the catalog, the ambience, and the generated noise on its own. YouTube links also need three tools chill doesn't ship: ffmpeg decodes their audio, yt-dlp resolves them, and deno lets yt-dlp read YouTube's player. If Node.js 22 or newer is already installed, yt-dlp uses it in place of deno.

brew install ffmpeg yt-dlp deno                          # macOS, or Linux with Homebrew
winget install Gyan.FFmpeg yt-dlp.yt-dlp DenoLand.Deno   # Windows

After a winget install, open a new terminal so the tools are on PATH. When a tool is missing, a YouTube link names it and the command that installs it. chill doctor checks all three tools, prints the config, library, and cache paths, and never installs anything. An old yt-dlp stops working when YouTube changes, so chill reminds you at startup once yours is more than 60 days old.

Use

chill opens with your last scene loaded and paused. Space starts the music and the ambience, and t starts the focus timer. Each runs without the other, so you can just listen. For ambience alone, M turns the music off and keeps the sounds you set; press it again and the music picks up where it stopped.

F hides or shows the timer panel. While it is hidden, a running or paused timer stays in the header, and clicking it pauses or resumes. H and L put away the library on the left and the ambience and timer on the right, so the player fills the window. A side you put away leaves a thin edge with an arrow; click it, or press the same key, to bring the side back.

chill                                   # resume where you left off
chill https://youtu.be/…                # play a video, playlist, or live stream
chill "rainy study"                     # load a saved scene
chill doctor                            # check the tools YouTube links need
chill update                            # update an official installation

Paste a YouTube link anywhere in the app to add it to the library and play it. Everything responds to the mouse: click rows and buttons, drag the sliders, scroll the lists. Press ? for every key, or ctrl+p to search all commands.

An error stays in a small notice until you dismiss it. Click the notice or press D to read the latest errors and dismiss them. chill keeps the last 100 in diagnostics.json in the cache folder, with URLs and sync keys removed.

Key Does
space play or pause the music and ambience
M turn the music off and keep the ambience, or bring the music back
n / p next / previous
f like what's playing
< / > turn the music or the ambience up against the other
t / T start or pause the timer / reset it
F hide or show the timer panel
H / L hide or show the left / right side
v next picture in the player
o open a YouTube link
c settings
D recent errors and diagnostics
tab move between library, queue, and ambience
enter / a play the selection / add it to the queue

The library has five tabs. Tracks come from the catalog. Liked holds what you liked. YouTube holds every link you opened. Sounds holds the ambience mixes, which play on their own without music. Scenes holds saved setups of source, ambience, balance, and timer.

To update a saved playlist, select it in YouTube and press R, click R refresh, or search for Refresh playlist in the command palette.

The slider between music and ambience at the top of the Ambience panel turns one down against the other, for a loud stream or quiet rain, without touching the volume. A soundscape has no music, so the balance leaves it alone. White, pink, and brown noise sit below the recordings; chill generates them, so they need no download and play offline.

The player shows the cover of what's playing, or v swaps it for a moving picture drawn in text that fills the player, with what's playing on a label over it: Journey, Night lake, Fuji at dawn, or Autumn pagoda. A picture moves while something plays and shows the ambience you hear: rain falls while a rain sound plays, thunder lights up the sky, wind sways trees and water, and crickets bring out fireflies, each as strongly as the music and ambience balance lets you hear it. Journey is a landscape in plain characters that scrolls past in layers, and shows more of the ambience: birds fly over, a stream runs through the grass, waves turn the fields to sea, a fire sends up sparks, and the café lights the windows of a village on the hills. Every picture follows the seasons: blossom drifts down in spring, leaves fall in autumn, and in winter snow lies on the land and rain falls as snow. From eight in the evening to six in the morning Fuji at dawn and Autumn pagoda turn to night, with the moon and stars out. On Fuji at dawn the lake mirrors everything above it and sways with waves and wind, wind blows snow off the summit, birdsong sends birds over, and a fire lights a lantern on the boat. Autumn pagoda's pond mirrors its lanterns, which glow out into the dark at night. Journey and Night lake use your terminal's colors; the other two bring their own. Settings chooses the picture and how it is drawn. Auto, the default, draws Night lake in braille dots, and the two paintings in blocks when the player has room and in pixels otherwise. Blocks work in any font. Pixels, and the solid shapes in Night lake, use octant characters from Unicode 16; Ghostty draws them itself, and other terminals need a font that has them.

Fuji at dawn filling the player with both sides put away, drawn in blocks

Journey with a fire, crickets, and a stream playing: sparks rise, fireflies glow over the fields, and a creek runs through the grass

Focus stats count every minute a focus block ran, including blocks you stopped early. Breaks never count. The header shows today's total and a seven-day sparkline; press i for the chart.

Catalog

catalog.json lists the R2 tracks, the ambient sounds, and the built-in mixes. chill reads it from catalog_url, keeps the last copy it fetched for offline starts, and falls back to the copy compiled into the binary from this repository. File names resolve against the folder the catalog sits in.

Someone can share another catalog with you as a link to its catalog.json. Search for Add a catalog in the command palette and paste the link; its tracks join Tracks, each showing which catalog it came from, and Remove catalog takes it out again. When two catalogs use the same id, the main catalog's entry wins.

chill-catalog in tools/catalog edits that file and uploads to the bucket. It needs ffmpeg and these variables, from the environment or a .env file in the repository:

Variable Holds
R2_ACCOUNT_ID the Cloudflare account id
R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY an R2 API token with write access to the bucket
R2_BUCKET the bucket name
R2_JURISDICTION the bucket's jurisdiction, such as eu; leave it unset for the default
cargo run -p chill-catalog -- add-track night-drive.wav --title "Night Drive" --artist "…" --cover cover.png --tag focus
cargo run -p chill-catalog -- add-sound rain.flac --label "Rain" --credit "…, CC0 1.0" --start 2 --length 75 --gain 90
cargo run -p chill-catalog -- check
cargo run -p chill-catalog -- publish

add-track normalizes the audio to -14 LUFS and add-sound cuts a loop and normalizes it to -16 LUFS, both as 192 kbps MP3. They upload the file (and cover), then add the entry to catalog.json; the id defaults to the title or label in lowercase with dashes. check validates the catalog and confirms the public bucket serves every file it names. publish runs check and uploads catalog.json, which clients cache for five minutes. Add --dry-run to any command to see what would be uploaded and written without touching the bucket or the file. Commit catalog.json after publishing, so the copy compiled into the next release matches.

Audio and covers are served as immutable and cached for a year, so a client that fetched a file never asks for it again. A changed recording needs a new name: pass a new --id. The tool refuses to replace an existing file unless you pass --force.

To share music outside the main catalog, start a catalog of its own. new creates the file with a url under a random folder of the bucket, and --catalog points every other command at it:

cargo run -p chill-catalog -- new ~/chill/friends.json --name "Friends"
cargo run -p chill-catalog -- --catalog ~/chill/friends.json add-track home.wav --title "Home" --artist "…"
cargo run -p chill-catalog -- --catalog ~/chill/friends.json publish

The folder is hard to guess, not secret: anyone with the link can play the files, and taking access back means publishing under a new folder. Only put music there that you may share. Keep the file outside this repository, since it holds the link, and check flags ids that the main catalog already uses.

Sync

Sync carries your library, likes, scenes, mixes, focus history, and settings between machines. It is off until you search for Turn on sync in the command palette, which creates a sync space on the server. To add another machine, run Copy sync key on the first and paste the key into Join a sync space on the second. The key is the only thing that opens the space, so keep it like a password.

chill syncs when it starts, every hour while it runs, and when it quits, or right away with Sync now. Everything still lives in chill.db on each machine, so chill works offline and catches up on the next sync. When the same item changed on two machines, the later change wins. Turn off sync stops syncing and keeps everything on the machine. Delete sync data removes everything the space holds from the server and closes it, so every machine stops syncing and keeps its own library.

The server is the Worker in sync/. It stores what syncs as it is, readable by whoever runs it, but nothing that names you: no account, email, or IP address. The key only travels over https.

Configuration

Press c for settings: timer lengths, auto-start, the chime, notifications, the picture, the season, reduced motion, and how many keys the bottom bar lists. / searches them. Changes save to config.toml right away, keeping anything else you wrote in it. The file is optional and lives in ~/.config/chill/ on macOS and %APPDATA%\chill\ on Windows. These are the defaults:

catalog_url = "https://chill-data.aquitano.me/catalog.json"
catalogs = []           # more catalog.json links, added with "Add a catalog"
sync_url = "https://chill-sync.aquitano.me"
reduce_motion = false   # true turns off transitions and the visualizer, and holds pictures still
picture = "cover"       # or "journey", "night-lake", "fuji-dawn", "autumn-pagoda"
look = "auto"           # how pictures are drawn: "auto", "blocks", "pixels", or "dots"
season = "auto"         # the calendar's, or "spring", "summer", "autumn", "winter" (say, south of the equator)
key_hints = "essentials" # the bottom bar: play, commands, settings, keys; or "all", or "off" to hide it
chime = true            # a soft bell when a focus block or break ends
notifications = true    # an OS notification at the same moments

[pomodoro]
focus_minutes = 25
break_minutes = 5
long_break_minutes = 15
long_break_every = 4
auto_start_breaks = true
auto_start_focus = true

The library, likes, scenes, mixes, focus history, and the resume point live in chill.db. That file sits in ~/.local/share/chill/ on macOS and %APPDATA%\chill\ on Windows. Cached ambient loops and the catalog go to ~/.cache/chill/ or %LOCALAPPDATA%\chill\.

Development

cargo run
cargo test --workspace
cargo clippy --all-targets --workspace -- -D warnings

To review the UI without starting audio or fetching media, render the real UI through Ratatui's TestBackend:

cargo test --offline -p chill ui::review::export_ui_review -- --ignored --exact --nocapture

Open target/ui-review/index.html in a browser. The export covers wide, medium, and narrow layouts with fixed sample data and a fixed ANSI palette, and never reads your library or config.

On Linux the build needs libasound2-dev. CI runs the checks on Linux, macOS, and Windows. Pushing a version tag such as v0.1.0 builds the binaries and installers and publishes them as a GitHub release; bump version in Cargo.toml to match first.

To try a change on a Mac or Windows machine without a release, build it with the Try workflow. Add the try label to a pull request: it builds again on every push, and once both builds finish it comments with the downloads and a command to run each. To build any other branch, such as main after a merge, start the workflow yourself:

newest() { gh run list -R Aquitano/chill-flow -w try.yml -b main -L 1 --json databaseId -q '.[0].databaseId'; }
before=$(newest)
gh workflow run try.yml -R Aquitano/chill-flow -r main
until run=$(newest); [ -n "$run" ] && [ "$run" != "$before" ]; do sleep 2; done
gh run watch "$run" -R Aquitano/chill-flow
gh run download "$run" -R Aquitano/chill-flow -n chill-aarch64-apple-darwin -D "/tmp/chill-try-$run"
tar -xzf "/tmp/chill-try-$run/chill.tar.gz" -C "/tmp/chill-try-$run"
XDG_CONFIG_HOME="$HOME/.config/chill-try" "/tmp/chill-try-$run/chill"

The last line keeps the build's settings in ~/.config/chill-try, since an older chill won't start with settings a newer one has written. The library and cache are shared with the installed chill. On Windows the artifact is chill-x86_64-pc-windows-msvc and holds chill.exe; chill keeps its settings and library in one folder there, so point APPDATA at a folder of its own, as the pull request comment's command does. Builds are kept for 14 days.

Ambient recordings

Sound Source License
Rain, Rain on roof, Thunder, Fire USC Cinema / Sunset Editorial Collection CC0 1.0
Wind Tvabutzku1234 CC0 1.0
Birds ezwa Public domain
Stream Juandev Public domain
Waves Dsw4 Public domain
Café Stephan (pdsounds.org) Public domain
Crickets Serg Childed CC BY-SA 4.0 (the trimmed loop is an adaptation and stays BY-SA)

License

The code is licensed under the Apache License 2.0. The Fuji at dawn and Autumn pagoda pictures are ported from ascii-magic-art and keep its MIT license. The audio in the catalog is not part of this repository and keeps the licenses listed above.

About

Lo-fi, ambience, and a focus timer in your terminal

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages