Winix.Peep 0.4.0

Prefix Reserved
dotnet tool install --global Winix.Peep --version 0.4.0
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local Winix.Peep --version 0.4.0
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=Winix.Peep&version=0.4.0
                    
nuke :add-package Winix.Peep --version 0.4.0
                    

peep

Run a command repeatedly and display output on a refreshing screen.

Supports interval polling, file-watch triggers, diff highlighting, time-machine history, and auto-exit conditions.

watch + entr replacement (and works on Linux/macOS too).

Install

Scoop (Windows)

scoop bucket add winix https://github.com/Yortw/winix
scoop install winix/peep

Winget (Windows, stable releases)

winget install Winix.Peep

.NET Tool (cross-platform)

dotnet tool install -g Winix.Peep

Direct Download

Download native binaries from GitHub Releases.

Usage

peep [options] [--] <command> [args...]

Examples

# Watch a command every 2 seconds (default)
peep git status

# Custom interval
peep -n 5 df -h

# Re-run on file changes (no interval polling)
peep -w "src/**/*.cs" dotnet test

# Combine interval + file watching
peep -n 10 -w "*.config" dotnet build

# Highlight differences between runs
peep -d kubectl get pods

# Exit when output changes
peep -g curl -s https://api.example.com/status

# Exit when command succeeds (exit code 0)
peep --exit-on-success -- dotnet build

# Exit when output matches a regex
peep --exit-on-match "READY" -- kubectl get pods

# Run once and display (no loop)
peep --once -- docker ps

# JSON summary on exit (for scripts)
peep --json -n 5 -- dotnet test

# Use -- when child args look like peep flags
peep -- myapp --help

Interactive Controls

While running, peep responds to keyboard input:

Key Action
q / Ctrl+C Quit
Space Pause/unpause display
r / Enter Force immediate re-run
d Toggle diff highlighting
Up/Down Scroll while paused
PgUp/PgDn Scroll by page
Left/Right Time travel (older/newer snapshots)
t History overlay (browse all snapshots)
? Show/hide help overlay
Escape Exit time-machine or close overlay

Time Machine

Press Left to enter time-machine mode, browsing historical snapshots of command output. Use Left/Right to navigate, t for an overview, Enter to jump to a specific snapshot, and Space or Escape to return to live mode.

Options

Option Description
-n, --interval N Seconds between runs (default: 2)
-w, --watch GLOB Re-run on file changes matching glob (repeatable)
--debounce N Milliseconds to debounce file changes (default: 300)
--history N Max history snapshots to retain (default: 1000, 0=unlimited)
-g, --exit-on-change Exit when output changes
--exit-on-success Exit when command returns exit code 0
-e, --exit-on-error Exit when command returns non-zero
--exit-on-match PAT Exit when output matches regex (repeatable)
-d, --differences Highlight changed lines between runs
--no-gitignore Disable automatic .gitignore filtering for --watch
--once Run once, display, and exit
-t, --no-header Hide the header lines
--json JSON summary to stderr on exit
--json-output Include last captured output in JSON (implies --json)
--no-color Disable colored output
--color[=auto\|always\|never] Colored output: auto (default when omitted), always, or never.
--version Show version
-h, --help Show help

File Watching

When --watch is used, peep monitors for file changes matching the glob pattern. Multiple patterns can be specified. By default, files matching .gitignore rules are excluded (disable with --no-gitignore).

If only --watch is specified (no -n), interval polling is disabled — peep only re-runs on file changes. If both are specified, either trigger causes a re-run.

Exit Codes

Code Meaning
0 Auto-exit condition met, or manual quit with last child exit 0
N Last child process exit code (manual quit)
125 Usage error (bad arguments)
126 Command not executable
127 Command not found

Colour

  • Automatic: colour when outputting to a terminal, plain when piped
  • --color forces colour on (overrides NO_COLOR)
  • --no-color forces colour off
  • Respects the NO_COLOR environment variable (no-color.org)

Part of Winix

peep is part of the Winix CLI toolkit.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
0.4.0 189 6/13/2026
0.3.0 135 5/26/2026
0.3.0-rc2 113 5/10/2026
0.2.0 126 4/16/2026
0.2.0-test4 126 4/15/2026
0.1.0 130 4/2/2026
0.1.0-preview.4 76 3/30/2026
0.1.0-preview.3 70 3/30/2026
0.1.0-preview.2 70 3/30/2026
0.1.0-preview.1 70 3/30/2026

## [0.3.0] - 2026-05-10

### Changed
- `--json` / `--json-output` envelopes now reach every exit path, including parser errors, missing-command, regex-parse errors, and unexpected runtime exceptions in once-mode. Pre-fix, several validation early-returns and the once-mode catch list emitted bare stderr lines, breaking JSON-aware automation that decided "envelope or not" by inspecting `--json-output`.
- JSON `last_output` field is now trimmed of trailing newlines to match `bash $(cmd)` command-substitution semantics. Internal newlines are preserved unchanged; the captured `PeepResult.Output` stays byte-faithful for stdout / `--exit-on-match` / history-diff consumers.
- Captured child output is normalised to `\n` line endings at the API boundary, so JSON envelopes and `--exit-on-match` regexes behave identically across Windows and POSIX (pre-fix, `dotnet --version` produced `"10.0.200\r\n"` on Windows but `"10.0.200\n"` on Linux).
- `--exit-on-match` and `--exit-on-change` now return exit code 0 for the auto-exit success case, matching the documented "0 = Auto-exit condition met" contract. Pre-fix the child's last exit code (often non-zero on the run that triggered the match) was passed through.
- Once-mode JSON envelope now emits `history_retained: 0` so the field is always present, matching `--describe`'s declared `int|null` schema.

### Fixed
- `peep <command>` no longer crashes with an SR resource-key exception (`InvalidOperationException: InvalidOperation_ConsoleKeyAvailableOnFile`) when stdin is redirected (pipes, `/dev/null`, CI, parent-process `RedirectStandardInput=true`). The redraw loop now probes `Console.IsInputRedirected` once at entry and skips the keyboard branch on every tick when stdin is not a terminal.
- Two related stream-merge bugs fixed: child stdout and stderr are now merged line-atomically rather than in 4096-char chunks (a chunk from one stream could land mid-line of the other, producing output like `"this naCould not execute...me could not be found"`), and the line buffer enforces the 64 MB output cap mid-stream so a child writing without `\n` (binary dumps, unfolded base64, large `curl` payloads) can no longer bypass the OOM bound.
- File-change triggers are no longer silently dropped while a child is running. The previous `Interlocked.CompareExchange` ordering consumed the trigger flag before checking whether a child was already running, so saves during a long `dotnet test` were silently lost.
- ANSI-strip regex now also matches OSC sequences (`ESC ] ... BEL` and `ESC ] ... ESC \`) used by modern shells, fish, oh-my-posh, gcc, and OSC-8 hyperlinks. Pre-fix `--exit-on-match` patterns failed against lines carrying a leading title-set escape, and `--json-output last_output` leaked raw escape bytes into the JSON envelope.
- `RegexMatchTimeoutException` from `--exit-on-match` against a pathological pattern is now caught (treated as non-match) with a one-shot stderr warning per pattern. Pre-fix the timeout was silently swallowed and the user's auto-exit condition never fired with no diagnostic.
- Captured child output is bounded by a 64 MB cap with a single trailing truncation marker; readers continue draining after the cap so the child sees clean EOF rather than wedging on a full pipe. A runaway child can no longer OOM peep itself.
- `GitIgnoreChecker` no longer leaks orphan `git` processes when `git rev-parse` / `check-ignore` hangs (credential helper prompts, network FS, antivirus scanning `.pack` files). Hung calls are killed on timeout, gitignore filtering is process-wide-disabled with a one-shot stderr warning, and subsequent calls short-circuit. `git` subprocesses now also redirect and immediately close stdin so an interactive credential helper can't hold the parent process hostage.
- `peep --once` now respects Ctrl+C (exits 130 with a complete JSON envelope under `--json` / `--json-output`), handles `CommandStreamException`, and has a last-resort catch-all so any unexpected exception escaping `CommandExecutor.RunAsync` produces a typed envelope rather than an unhandled-exception stack trace. `OutOfMemoryException` and `StackOverflowException` deliberately remain uncaught.
- Interactive Ctrl+C handler no longer races against handler-unregister: `cts.Cancel()` is wrapped in `try/catch (ObjectDisposedException)` so a Ctrl+C arriving just before unregister can no longer crash the cancel-key thread.
- `process.StandardInput.Close()` no longer throws `IOException` when a fast-exiting child has already disconnected the pipe. Closing a pipe that's already gone is the success state.
- `Process.Start` exception coverage extended beyond `Win32Exception` to also catch `FileNotFoundException` (.NET 5+ may surface missing executables directly) → `command_not_found` (127) and `InvalidOperationException` / `PlatformNotSupportedException` / `ArgumentException` → `command_not_executable` (126), per the `--describe` contract.
- Alternate-screen-buffer enter / exit now swallows `IOException` and `ObjectDisposedException` from the underlying writer, so an I/O error at teardown can no longer leave the user's terminal stuck in alt-buffer mode with a hidden cursor for the rest of the shell session.
- File-watcher monitor task now has a strictly-weaker last-resort catch with a stderr warning (`file-change monitor crashed; file-change triggering disabled`). Pre-fix an unexpected crash silently disabled file-change triggering and peep kept polling on interval-only with no diagnostic.
- `--version` output no longer carries the `+gitsha` SourceLink suffix the .NET SDK appends by default. Users see plain `peep 0.3.0`, matching the suite-wide convention.

### Internal
- Library seam `SessionHelpers` extracted from `InteractiveSession` and `Program`: `WarnOnceForRegexTimeout`, `TryGetAutoExit`, `ResolveExitCode`, `RequestCancellationSilently`, and `ShouldDispatch` are now testable as named static methods rather than buried inside event-loop lambdas.
- `CommandExecutor.MaxOutputChars` changed from a process-global mutable static test seam (caused parallel-test flakiness and leaked test-overridden caps into user-facing truncation markers) to an optional per-call parameter.
- UTF-8 console adoption via `ConsoleEnv.UseUtf8Streams` so `--describe` em-dashes and any non-ASCII pipe output round-trip cleanly through Windows `cmd.exe`.
- Standard `<PackageTags>` set on the NuGet package so peep appears in nuget.org filtered searches (previously discoverable only by exact-name lookup).
- Platform-skipped tests migrated to `SkippableFact + Skip.IfNot` so non-applicable tests report Skipped rather than Passed on the wrong platform.

See full changelog at https://github.com/Yortw/winix/blob/main/src/peep/CHANGELOG.md