[0.14.0] - 2026-05-12
Added
- Friendly progress UI, on by default on plain TTYs.
kei syncon an interactive terminal now shows a smoothed multi-pass card in place of the plain progress bar: a source-aware top rule (kei · downloading from iCloud), bandwidth sparkline (B/s through GB/s), smart ETA, album-or-unfiled prefix on the active filename, color-tiered rules (bright cyan → cyan → green as progress climbs), and a moon-phase spinner. A stripped tracing format drops thetarget+timestampprefix so on-screen lines stay compact. Friendly mode auto-disables when output isn't a TTY, when--report-json/--only-print-filenames/--no-progress-baris set, underkei service run/ Docker / systemd, whenTERM=dumb, or when--log-level/RUST_LOGis set explicitly — journals and pipes keep the v0.13 structured format byte-for-byte. Opt out with--no-friendlyper invocation or[ui] friendly = falseinconfig.toml. Resolution order is CLI > TOML > default-on, with hard-off contexts winning over everything.--friendlystill works as a force-on (subject to the auto-off rules above).--verbose/-valiases--log-level infoand restores the structured log format. (#362, #363, #364, #365, #366, #367, #370) - Friendly-mode lifecycle narration. While the bar reads
scanning…, it cycles through a phase-aware verb pool ("looking around", "counting albums", "asking iCloud what is new") every ~600ms. Smart-ETA wording escalates fromcalculating…tostill calculating…after 5 seconds of indeterminate ETA. Four phase ✓ lines print alongside the bar:✓ Authenticated as you@example.com,✓ Listed N library/libraries,✓ Downloaded N new files (412 GB → 412.3 GB)(with library size delta from the state DB), and✓ Verified N files (N of N matched). All four no-op on no-op cycles. After the cycle, a summary card replaces the plain── Summary ──block with photos/videos split, skipped/failed counts, elapsed time, speed (bytes/elapsed), and library totals. A "what's new" recap (Biggest grab,Oldest find,Newest album) prints below the card when anything was downloaded. Multi-album syncs get per-album column-aligned✓lines (✓ Family 1,204 / 1,204 2m 04s). (#363, #364, #374) - Friendly-mode narration during transient errors. 2FA prompts get a context line above the code input (
Sent a code to your trusted devices. Approve the push and enter the 6-digit code below.). CloudKit 421 Misdirected Request resets printiCloud connection wobbled. Resetting…/Back on track.Per-file download retries printiCloud hiccup. Retrying in 4s…/Back on track./That one is being stubborn. Skipping for now, will retry next sync.Watch-mode idle printsSleeping until 14:32 (local time). Press Ctrl+C to stop.Graceful exit printsDone. See you next time.Ctrl+C printsStopping. Finishing in-flight downloads. Press Ctrl+C again to exit immediately.All narration is friendly-mode-only; off mode keeps its existingtracingevents for journals. (#364, #365, #366, #367) kei installregisters kei as a long-running service on Linux, macOS, and Windows. Renders the platform-native artifact (systemd unit, launchd plist, or Service Control Manager entry) and hands it to the platform's service manager. Defaults to per-user on Linux and macOS; Windows is per-user only and needs an elevated prompt. Inside Docker / Kubernetes / Podman the command is a no-op so existing compose workflows are unchanged. The service runskei service run, which iskei syncwith a once-per-day watch interval default. (#351, #352, #355, #356, #358)kei uninstallremoves the service. Reverseskei installon every platform. Pass--purgeto also delete the state database, configuration, and stored credentials; default keeps your data. Double-uninstall prints "already removed" instead of a path-not-found diagnostic.kei installprints thekei uninstallcounterpart hint. (#352, #355, #356, #358, #374)kei service statusreports per-platform service state. Surfaces the systemdSubState, launchd lifecycle, or SCMcurrent_statein the platform's own terms. (#352, #355, #356, #358)kei statusleads with aService:summary line. Cross-platform one-liner (Service: running (systemd user, pid 12345, since 2026-05-08 14:32 UTC),Service: not installed,Service: running in container (process supervisor: docker), etc.) so you can script against a single output regardless of host. (#359)- Docker image now honors
PUIDandPGIDenvironment variables. When both are set, the container's entrypoint chowns ownership-mismatched files in/configand/photosto that UID:GID and drops privileges viagosubefore running kei. Setting only one is rejected; setting neither preserves the prior root-default. The chown usesfind -not -uidso a multi-TB volume only pays for stragglers, not the full tree, on every restart. Unblocks NAS deployments (Synology Container Manager, Unraid, TrueNAS Scale) where downstream indexers like Synology Photos can't see root-owned files. (Synology wiki, Docker wiki) - Setup wizard visual polish. The
kei config setupwizard now has dimmed section banners between steps, green-bold ✓ checkmarks on step completions, brief spinners during TOML generation and config write, and a styled TOML preview with dimmed comments and cyan section headers. Prompts use warmer, more conversational language throughout. No new dependencies. (#373)
Changed
- rpassword bumped to 7.5.2. Supersedes stale dependabot PRs #353 and #354. No user-visible change. (#357)
Fixed
- Ghost top rule after Ctrl+C in friendly mode. Indicatif filled the last bar row with spaces to the terminal's right edge; when the tty driver echoed
^Cthere it overflowed to a new line, throwingcursor_upoff by one and leaving the prior top rule as a stale frame. Narration now routes throughMultiProgress::println(atomic draw), and the friendly bar clears theECHOCTLtermios flag on stdin while active so^Cnever echoes (Unix only — Windows console doesn't echo^C). Restored on Drop and on bothprocess::exitpaths. - 100 MiB free-space floor before enumeration. kei now requires at least 100 MiB of free space on the download filesystem before starting iCloud enumeration, preventing a cycle that enumerates thousands of assets then fails every download with "no space left". (#372)
- State-write failures now produce a non-zero exit code.
complete_sync_runfailure was silently swallowed, so a full disk at the "commit sync results" stage looked like a clean exit. Now bumpsstate_write_failuresand propagates through the exit-code logic. (#372) - Help text polish.
--friendly/--no-friendlyshow short help in-h; top-level--helpfooter groups commands logically (Getting started / Common / Advanced). (#374)