| docs | ||
| src | ||
| .gitignore | ||
| build.zig | ||
| build.zig.zon | ||
| README.md | ||
| test_manual.ps1 | ||
| test_manual.sh | ||
_ _ _ _ _ _ _ _ _
| \ | (_) | | | | | \ / | | | | |
| \| |_ __ _| |__ | |_ \ \ _ / /_ _| |_ __| |___
| . ` | |/ _` | '_ \| __| \ ` ' / _` | __|/ _| '_ \
| |\ | | (_| | | | | |_ \ / (_| | |_| (_| | | |
|_| \_|_|\__, |_| |_|\__| \_|_/ \__,_|\__|\__|_| |_|
__/ |
|___/
T H E N I G H T W A T C H
The city sleeps. The files do not.
"FABRICATI DIEM, PVNC"
The Night Watch is a file change tracker for directory trees, written in Zig.
It provides:
- A standalone CLI for tracking filesystem changes
- A module for embedding change-tracking into other Zig programs
- Minimal dependencies and consistent, predictable, cross-platform behavior
It does not interfere. It does not speculate. It simply keeps watch.
Features
- Recursive directory tree tracking
- Deterministic multi-platform support (Linux, macOS, FreeBSD, OpenBSD, NetBSD, DragonFly BSD, Windows)
- Lightweight and fast
- Embeddable Zig module API
- Standalone CLI executable
Platform backends
| Platform | Backend | Notes |
|---|---|---|
| Linux | inotify | Threaded mode (default) or poll mode (-Dlinux_read_thread=false) |
| macOS | kqueue (default) or FSEvents (-Dmacos_fsevents=true) |
FSEvents requires Xcode frameworks |
| macOS (opt-in) | kqueue dir-only (-Dkqueue_dir_only=true) |
Low fd usage; see note below |
| FreeBSD, OpenBSD, NetBSD, DragonFly BSD | kqueue | |
| FreeBSD, OpenBSD, NetBSD, DragonFly BSD (opt-in) | kqueue dir-only (-Dkqueue_dir_only=true) |
Low fd usage; see note below |
| Windows | ReadDirectoryChangesW |
kqueue_dir_only mode
By default the kqueue backend opens one file descriptor per watched file
in order to detect modified events in real time via EVFILT_VNODE. At
scale (e.g. 500k files) this exhausts the process fd limit.
Build with -Dkqueue_dir_only=true to use directory-only kqueue watches
instead. This drops fd usage from O(files) to O(directories) and removes
the setrlimit call. The trade-off:
-
modifiedevents are not generated reliably. The backend detects file modifications opportunistically by comparing mtimes during a directory scan, which only runs when a directory entry changes (file created, deleted, or renamed). A pure content write to an existing file- with no sibling changes - will not trigger a scan and the modification will be missed until the next scan.
-
Workaround: Watch individual files directly (e.g.
watcher.watch("/path/to/file.txt")). When a path passed towatch()is a regular file,kqueue_dir_onlymode attaches a per-file kqueue watch and emits real-timemodifiedevents exactly like the default backend. Only directory tree watches are affected by the limitation above.
Installation
The Watch is written in Zig and built using the Zig build system.
Requirements
- Zig (currently zig-0.15.2)
Build CLI
zig build
The executable will be located in:
zig-out/bin/nightwatch
Install System-Wide
zig build install
Using as a Zig Module
The Night Watch exposes a reusable module that can be imported into other Zig programs.
In your build.zig:
const nightwatch = b.dependency("nightwatch", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("nightwatch", nightwatch.module("nightwatch"));
In your Zig source:
const nightwatch = @import("nightwatch");
You now have programmatic access to the tracking engine.
CLI Usage
nightwatch [{path}..]
Run:
nightwatch --help
for full command documentation.
Philosophy
Other tools watch files.
The Night Watch keeps watch over the peace.
It remembers what changed. It records what vanished. It notices what arrived at 2:14 AM.
And it writes it down.
