Config
braid.config.ts default-exports a { processes, plugins?,
logs?, foreground?, statsPollIntervalMs? } object (or a bare
ProcessConfig[], treated the
same way). Full field list and defaults live in
src/types.ts.
- Basics
- Watching and restarting
- Dependent restarts
- Post-restart hooks
- Pre-restart hooks
- Waiting for readiness
- Logs
- Foreground mode
- Process stats
Basics
import { defineConfig } from "@aip-tech/braid";
export default defineConfig({
processes: [
{
name: "api",
command: "pnpm",
args: ["--filter", "./api", "run", "dev"],
cwd: "api",
color: "blue",
env: { NODE_ENV: "development" },
},
],
});
name— unique; used for log prefixes, the pidfile, and status output.command/args— the process to run.cwd— resolved relative to wherever the CLI was invoked; defaults to that same directory.env— extra environment variables merged overprocess.env, for this process only.color— ANSI color name used for this process's log prefix.
Watching and restarting
Set watch to restart a process whenever a matched path changes.
Omit it for a command that manages its own reload (a dev server with its own
HMR, for instance).
{
name: "api",
command: "pnpm",
args: ["--filter", "./api", "run", "dev"],
watch: ["api/src"],
ext: "ts,graphql,json", // @default "ts,js,json" - comma-separated, only used when watch is set
},
Dependent restarts
A process can restart whenever another one does — e.g. a client that needs to regenerate its GraphQL SDK once the API it talks to restarts:
{ name: "api", command: "pnpm", args: ["--filter", "./api", "run", "dev"], watch: ["api/src"] },
{
name: "client",
command: "pnpm",
args: ["--filter", "./client", "run", "dev"],
dependsOn: {
processes: ["api"],
run: { command: "pnpm", args: ["--filter", "./client", "run", "generate"] },
},
},
When api restarts: client stops once
api has actually re-spawned (not the instant it decides
to restart), run executes (retried on failure —
retries/retryDelayMs, default 5× / 1s
apart, since api may still be starting back up), then
client restarts. Left stopped with a logged reason if
run never succeeds. A dependsOn chain that loops
back on its own trigger is rejected at startup. Multiple processes can
depend on the same trigger — each cascades independently, in
parallel.
Post-restart hooks
For a shared workspace package other processes just read from (no process of its own to restart), run a command directly after the process that changed it restarts:
{
name: "api",
command: "pnpm",
args: ["--filter", "./api", "run", "dev"],
watch: ["api/src"],
onRestart: { command: "pnpm", args: ["--filter", "./types", "run", "generate"] },
},
Same shape and retry behavior as dependsOn.run, but scoped to
api restarting itself — no dependent process is stopped
or restarted. If api also has dependents (via
dependsOn), they're notified only once this hook succeeds, and
not at all if it never does.
Pre-restart hooks
For a process that needs to regenerate its own on-disk dependencies (e.g. a
GraphQL SDK generated from a schema it also watches) before it restarts on
that same change, beforeRestart runs after the process is
stopped and before a fresh one starts:
{
name: "client",
command: "pnpm",
args: ["vike", "dev"],
watch: ["server", "lib/sdk"],
beforeRestart: { command: "pnpm", args: ["--filter", "lib/sdk", "run", "generate"] },
},
client is fully stopped, generate runs to
completion (retried like onRestart/dependsOn.run),
and only then does client restart — the fresh process
never boots against stale or half-regenerated code. Requires
watch; rejected at startup otherwise. If the hook keeps failing
past its retries, client is left stopped, but the watcher stays
active — the next matching file change retries the whole cycle, rather
than requiring a manual restart.
Waiting for readiness
Re-spawning a process isn't the same as it being ready — an API might
take a moment after restarting before it's actually serving.
readyPattern holds off onRestart and any
dependsOn cascades until a regex matches that process's own
stdout/stderr:
{
name: "api",
command: "pnpm",
args: ["--filter", "./api", "run", "dev"],
watch: ["api/src"],
readyPattern: "Server listening",
readyTimeoutMs: 15000, // @default 10000
},
Without readyPattern, dependents are held off only until
api has re-spawned (not exited/killed while restarting, but
not necessarily done starting up either) — a
run/onRestart hook's own retry is what bridges
the rest of that gap. If readyPattern never matches within
readyTimeoutMs, braid logs why (in api's own log)
and proceeds anyway, rather than holding dependents off forever on a
misconfigured pattern.
Logs
Each process gets a rotated log file at .braid/logs/<name>.log.
Rotated (one backup kept) on every start, on a watch-triggered
restart, and past logs.maxSizeBytes (default 5MB, set at the
top level of the config, not per-process):
export default defineConfig({
processes: [ /* ... */ ],
logs: {
dir: ".braid/logs", // @default ".braid/logs"
maxSizeBytes: 5242880, // @default 5MB
timestamps: false, // @default false
},
});
Braid's own diagnostics (plugin failures, crash notices) go to
.braid/daemon.log; a
dependsOn/onRestart/beforeRestart
hook that keeps failing, or a readyPattern that never matches,
is also logged into the relevant process's own log — visible via
braid logs/--follow, not just daemon.log.
logs.timestamps prepends a dimmed HH:MM:SS.mmm to
every line, before the [name] tag. It's the same bytes wherever
that line shows up — a process's log file, the web UI's log view, and
the terminal during braid start all get it together, there's no
way to enable it in just one place.
Foreground mode
start forks a background daemon by default. Set foreground:
true at the top level of the config to make it run attached to the
terminal instead — it then blocks, streaming every process's combined
output there, until Ctrl-C (or a braid stop from another
terminal) stops everything:
export default defineConfig({
processes: [ /* ... */ ],
foreground: true, // @default false
});
Override this per invocation with braid start --foreground or
braid start --daemon, regardless of what the config says.
Process stats
Each process's CPU (percent of one core) and memory (RSS) usage is
sampled on a timer via pidusage,
surfaced through PluginContext.getProcesses() (so
GET /api/status and @aip-tech/braid-plugin-ui's
dashboard get it for free) and in braid status's own output
once the daemon has sampled it. Adjust the polling cadence at the top
level of the config:
export default defineConfig({
processes: [ /* ... */ ],
statsPollIntervalMs: 2000, // @default 2000
});
A process that's stopped (or hasn't been sampled yet) simply has no
cpu/memory fields, rather than stale or zeroed
values.