ModuleManifest gains a structured [[permissions]] field (bread-shared's
new ModulePermission/PermissionKind, shared between bread-cli and breadd
so the two never drift on what a permission "type" string means).
breadd now gives every third-party module's Lua chunk a scoped _ENV
instead of the shared global table: load_scoped_lua_file builds a fresh
`bread` table containing only baseline bindings (event subscription,
timers, json, module/store, logging, and the pure-Lua sugar built on top
of those) plus whatever the manifest's permissions grant, with a
metatable __index falling back to the real globals for everything else
(stdlib, require/package - so require("bread.devices") keeps working,
since builtins load unscoped and their closures capture that environment
lexically regardless of the caller's). _G is explicitly rebound to the
scoped table itself to close the obvious escape hatch. A module with no
manifest, or a manifest with no permissions key, keeps full ambient
access unchanged (today's behavior) but is now tracked as `ungated` in
module status and surfaced by `bread doctor`. An explicit `permissions =
[]` is scoped for real but not flagged, since that's a deliberate
declaration.
Adds `bread modules audit <name>`: a best-effort text scan of a module's
.lua files suggesting a [[permissions]] block to paste into its manifest.
Converts examples/modules/cpu-temp-widget.lua into a directory module
with a worked bread.module.toml (fs.read + widget) as the reference
example. Documentation.md gets a new "Capability-scoped modules" section
covering the taxonomy, the require()/closure mechanism, and an explicit
note that path/bin scoping is recorded but not yet enforced per-call -
that's the out-of-process module sandboxing workstream this manifest
schema is laid down for. API_VERSION bumped 1.4.0 -> 1.5.0.
|
||
|---|---|---|
| .forgejo/workflows | ||
| bread-cli | ||
| bread-emit | ||
| bread-shared | ||
| breadd | ||
| examples/modules | ||
| graphify-out | ||
| packaging | ||
| scripts | ||
| .gitignore | ||
| bakery.toml | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CONTRIBUTING.md | ||
| Documentation.md | ||
| Examples.md | ||
| LICENSE | ||
| README.md | ||
Bread
A reactive automation fabric for Linux desktops.
Bread is a modular desktop automation runtime built around a single idea: your desktop should behave like a programmable system, not a collection of disconnected config files.
Instead of scattering behavior across shell scripts, compositor configs, udev rules, and ad-hoc daemons, Bread centralizes runtime awareness into a coherent layer that can observe, interpret, and react to system state dynamically.
Status: Early development. The daemon (
breadd) is stable. The Lua automation API is active and feature-complete for daily use.
How it works
Bread runs a long-lived daemon (breadd) that:
- Ingests raw signals from your compositor, hardware, and OS
- Normalizes them into stable, semantic events (
bread.device.dock.connected,bread.monitor.connected, etc.) - Maintains a live model of your desktop state
- Delivers those events to Lua modules that implement your automation
Your automation lives in Lua. You subscribe to events, read state, and call APIs:
local M = bread.module({ name = "dock", version = "1.0.0" })
bread.on("bread.device.dock.connected", function(event)
bread.profile.activate("desk")
bread.exec("waybar --config ~/.config/waybar/desk.jsonc")
bread.notify("Dock connected", { urgency = "low" })
end)
bread.on("bread.device.dock.disconnected", function(event)
bread.profile.activate("default")
end)
return M
Architecture
breadd/ Rust daemon — event pipeline, state engine, IPC, adapter supervision
bread-cli/ CLI frontend — talks to breadd over a Unix socket
bread-shared/ Shared types — RawEvent, BreadEvent, AdapterSource
packaging/ Arch PKGBUILD and systemd user service
The daemon is structured in four layers:
- Adapters — interface with Hyprland IPC, udev, power state, network interfaces, and Bluetooth (BlueZ)
- Normalizer — transforms raw adapter signals into semantic Bread events
- State engine — maintains runtime state and dispatches events to subscribers
- Lua runtime — loads your modules, registers handlers, executes automation
Requirements
- Linux (Arch recommended)
- Wayland compositor (Hyprland for full functionality)
- Rust toolchain (stable, 2021 edition)
udev(standard on systemd systems)
Optional but preferred:
- UPower (for battery events via D-Bus rather than sysfs polling)
- rtnetlink (for network events; falls back to sysfs polling without it)
- BlueZ (for Bluetooth device events and control)
Installation
From source
git clone https://git.breadway.dev/Breadway/bread.git
cd bread
Run the install script — it builds, symlinks breadd and bread into ~/.local/bin (override with BIN_DIR=…), installs the systemd user service, and starts the daemon:
bash scripts/install.sh
Or step by step (system-wide install):
cargo build --release
sudo install -Dm755 target/release/breadd /usr/bin/breadd
sudo install -Dm755 target/release/bread /usr/bin/bread
Arch Linux (PKGBUILD)
cd packaging/arch
makepkg -si
systemd user service
mkdir -p ~/.config/systemd/user
cp packaging/systemd/breadd.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now breadd
Configuration
Bread reads from ~/.config/bread/breadd.toml. All values are optional — the daemon runs with defaults if the file doesn't exist.
[daemon]
log_level = "info" # trace | debug | info | warn | error
[lua]
entry_point = "~/.config/bread/init.lua"
module_path = "~/.config/bread/modules"
[adapters.hyprland]
enabled = true
[adapters.udev]
enabled = true
subsystems = ["usb", "input", "drm", "power_supply"]
[adapters.power]
enabled = true
poll_interval_secs = 30
[adapters.network]
enabled = true
[adapters.bluetooth]
enabled = true
[events]
dedup_window_ms = 100
[notifications]
default_timeout_ms = 5000
default_urgency = "normal"
notify_send_path = "notify-send"
[modules]
builtin = true # load built-in modules (monitors, devices, workspaces, binds)
disable = [] # list of built-in module names to disable
Your automation lives in ~/.config/bread/init.lua. Modules placed in ~/.config/bread/modules/ are auto-loaded after init.lua:
-- ~/.config/bread/init.lua
bread.on("bread.system.startup", function(event)
bread.profile.activate("default")
end)
CLI reference
All commands communicate with the running daemon over a Unix socket at $XDG_RUNTIME_DIR/bread/breadd.sock.
# Daemon
bread ping # Check daemon connectivity
bread health # Daemon version, uptime, PID
bread doctor # Diagnose daemon and module health
bread doctor --json # Output raw JSON
# Lua runtime
bread reload # Hot-reload all Lua modules
bread reload --watch # Watch config dir and reload on changes
# State and events
bread state # Dump full runtime state as JSON
bread state network # Read a single path from state
bread state --json # Output raw JSON
bread events # Stream live normalized events
bread events bread.device.* # Stream filtered events
bread events --since 60 # Replay events from the last 60 seconds
bread events --fields event,data # Limit output to specific fields
bread events --json # Output raw JSON
bread emit <event> # Manually fire an event (for testing)
# Profiles
bread profile-list # List defined profiles
bread profile-activate <name> # Activate a named profile
# Modules
bread modules list # List installed modules and daemon status
bread modules install /local/path # Install from a local module directory
bread modules remove <name> # Remove an installed module (--yes skips confirmation)
bread modules info <name> # Show full manifest and daemon status
Module system
Modules are Lua files (or directories) installed to ~/.config/bread/modules/. Each module must declare itself with bread.module() and have a bread.module.toml manifest.
Installing modules
Modules install from a local directory only. Modules run with full
bread.exec() privileges and are not sandboxed, so to use a module
published on a git host, clone it yourself and review the Lua before
installing from the local checkout:
git clone https://github.com/someuser/bread-wifi ~/src/bread-wifi
# review ~/src/bread-wifi, then:
bread modules install ~/src/bread-wifi
Writing a module
A module directory looks like:
~/.config/bread/modules/
└── wifi/
├── bread.module.toml ← required manifest
└── init.lua ← entry point
bread.module.toml:
name = "wifi"
version = "1.0.0"
description = "WiFi management for Bread"
author = "someuser"
source = "/home/you/src/bread-wifi"
installed_at = "2026-01-01T00:00:00Z"
init.lua:
local M = bread.module({ name = "wifi", version = "1.0.0" })
bread.on("bread.network.connected", function(event)
bread.log("Network up: " .. (event.data.interface or "unknown"))
end)
return M
Event reference, Lua API, and IPC protocol
These are fully documented in Documentation.md — the single canonical reference (event catalogue, per-function Lua API, runtime-state schema, and IPC protocol), versioned as Bread Automation API v1. This README no longer keeps a parallel copy, to avoid the two drifting apart.
- Dictionary: Event reference
- Dictionary: Lua API
- Dictionary: Runtime state schema
- Dictionary: IPC protocol
- API Stability & Versioning
Contributing
Bread is early-stage software. Contributions, issues, and feedback are welcome.
The daemon (breadd) is the most stable part of the codebase. Active development is happening across the Lua API and module system.
License
MIT — see LICENSE.