Documentation menu
Configuration reference
Files and precedence#
| File | Scope and supported root keys |
|---|---|
nid.yaml or nid.yml | Project configuration: groups, agents and default models. Looked up from the current directory toward its ancestors; .yaml wins when both exist in the same directory. |
$NID (normally ~/) | User configuration: custom agents, global notify settings, default models and model prices. This is not a second place to declare groups or default arbitrary group settings. |
Use nid up -f path/ to choose a file explicitly. Relative group
dir values resolve against that file's directory. Project root, group, session,
custom-agent and notification objects reject unknown keys; user config is read by
section, so unrelated root keys there are ignored by the current readers.
A project that still has only a terminid.yaml or pawmux.yaml (or the .yml spelling)
keeps working: nid reads it when no nid.yaml/nid.yml sits in the same directory
(terminid.yaml before pawmux.yaml) and prints one line,
terminid. nid migrate renames the file; see
upgrading from terminid.
Custom agents resolve in this order: shipped presets, user definitions, then project definitions. A later definition replaces the whole earlier agent definition rather than merging individual fields. Built-in driver names cannot be replaced. A session records the definition it used; on resume a current definition for that name takes precedence, with the stored one as fallback.
up is idempotent: missing sessions spawn, stopped sessions resume, and running
sessions stay running. Group settings from the settings table below apply to an
existing group. Changing the file's goal, budget, policy, setup, or a running session's
model is not a general live reconfiguration operation; use the appropriate commands
or recreate the session. up --copy/ replaces those lists for that invocation.
Project reference#
At the root, groups maps group names to the following objects; agents maps
custom agent names to the agent definition; models sets
default models for every group in the file and for nid spawn
in the repository. All root keys are optional. Group and session names use letters, digits, ., _, -, start with
a letter or digit, and have at most 64 characters.
| Group field | Type / default | Meaning |
|---|---|---|
goal | string / unset | Team objective; also supplies the lead's initial work. |
policy | lead or peers / lead | Coordination policy. |
setup | string / unset | Shell command run after local files arrive in a new worktree. |
budgetUsd | number ≥ 0 / no cap | Cumulative USD cap on billed spend (plus subscription estimates with budgetIncludesEstimates); pauses automatic turns. |
autoRestart | boolean / true | Default crash recovery for sessions; session value wins. |
dir | string / . | Repository relative to the config file. |
sessions | nonempty array / required | Agent shorthand strings or objects in the next table. |
A session shorthand is agent[:name][@model]. An object supports:
| Session field | Type / default | Meaning |
|---|---|---|
agent | string / required | Built-in, preset or configured custom agent name. |
name | string / agent name | Numbered from 2 when repeated; duplicate explicit names are rejected. |
model | nonempty string / default models, else vendor default | Vendor model selection. |
permissions | worktree, ask, yolo / group, user config, worktree | What the agent may do without asking; see permission profiles. |
yolo | boolean / false | Legacy shorthand for permissions: yolo. |
shared | boolean / false | Use the main checkout instead of an isolated worktree. |
autoRestart | boolean / group default | Allow one automatic resume after an eligible crash. |
role | nonempty string / policy-derived | First session is lead under lead, otherwise worker, unless explicitly set. |
prompt | string / unset | Additional first message. |
billing | auto, subscription, api / group, user config, auto | Bill this session to the agent's subscription login or an API key; see billing. |
compression | boolean or object / group, user config, on | This session's compression overrides, merged per key over the group's; see compression settings. |
Group settings#
These keys sit directly inside each groups.<name> object, alongside sessions.
They are also accepted by nid group set <group> key=value and the dashboard.
Minute values must be positive and at most 10080 (seven days), unless stated otherwise.
| Key | Type / default | Meaning |
|---|---|---|
stallMinutes | minutes / 30 | Task time budget, overridden by a task's budgetMinutes (task --minutes). |
stallGraceMinutes | minutes / 10 | Grace after a stall warning before release/reassignment. |
maxReassign | integer 0–100 / 2 | Shared automatic reassignment cap for stalls and long provider limits. |
idleClaimMinutes | minutes / 2 | An owner whose turn ended while it still holds a claimed task is re-prompted after this long idle; 0 turns it off. |
idleClaimNudges | integer 0–20 / 3 | Re-prompts (waiting 2, 4, 8 … minutes) before the idle owner's claims are released and the lead and you are told. Real work by the owner starts the count over. |
limitReleaseMinutes | minutes / 20 | Release claimed work when a provider limit is expected to last longer. |
limitWarnPercent | number 1–100 / 90 | Usage-window percentage at which to show a warning. |
permissions | worktree, ask, yolo / user config, else worktree | Default permission profile for the group's sessions. |
crashResume | stopped, continue / user config, else stopped | After the daemon stopped without a clean shutdown (crash, kill, reboot, logoff, power loss), the next start is unattended. stopped leaves the group's sessions stopped and tells you how to continue; continue (opt-in) resumes them and lets mid-turn sessions continue, which spends budget with nobody watching. See troubleshooting. |
fallback | api, off / user config, else off | Opt-in: a rate-limited subscription session continues on its API key until the plan window resets, then switches back. See billing. |
budgetIncludesEstimates | boolean / false | Also count subscription estimates (not just billed API spend) against budgetUsd; see Billed spend vs subscription estimates. |
copy | string array / empty | Relative local files/directories/globs copied to new worktrees (max 200 entries). |
link | string array / empty | Relative directories/globs shared through symlinks/junctions (max 200). |
review | off, auto, reviewers / off | Review before integration; a nonempty reviewers list implies reviewers mode if this key is absent. |
reviewers | string array / empty | Session names or full group/name IDs eligible for reviewers mode. |
notify | object / global settings | Notification overrides and additional webhook targets; see below. |
pr | object / unset | Saved PR metadata, normally maintained by land --pr and pr checks --watch. |
billing | mode or {default?, <agent>: mode} / user config, else auto | Subscription login vs API key per agent (auto prefers the subscription); a session's billing wins. See billing. |
models | object / file's top-level models, user config | Default models per agent type and role for sessions spawned into the group; merged over the file's top-level models (per agent, per role). |
compression | boolean or object / user config, else on | Token compression of nid traffic and the opt-in proxy, terse prompts, project memory and learn-after-done; false turns it off. See compression settings. |
rotate | object / {mode: auto, contextPercent: 85} | Context rotation for long-running sessions: mode (auto, compact, handoff, off), contextPercent (10–100) and maxTurns (positive integer, default none). See long-running sessions. |
pr requires string url and integer number, with optional string base and
number watchMinutes (0.01–1440). An absent watch interval means no background watch;
the CLI's bare --watch uses one minute. Keep generated PR metadata unless you intend
to change the watched pull request.
CLI values parse as JSON where possible. copy, link and reviewers also accept
comma-separated text. An empty value (stallMinutes=) removes the stored override;
API patches use null. YAML omits a field to use its default; YAML null is not a
general reset syntax.
Default models#
A session runs the model it was spawned with (claude:lead@opus, a session's model:,
the dashboard's Spawn form, nid spawn claude@sonnet). Without one, nid picks
a default from models: blocks, so you can say "the lead on Opus, workers on Sonnet" once:
models:
claude: sonnet # per agent type
codex: gpt-6-luna
roles:
lead: opus # a string: any agent in that role whose vendor uses the name
worker: { claude: sonnet, codex: gpt-6-luna } # or per agent
The same block is accepted in ~/, at the top of nid.yaml,
and inside a group (also nid group set <group> 'models={"claude":"opus"}').
Keys other than roles are agent names (built-in, preset or custom); role names are
free text like a session's role. The most specific value wins:
| Order | Source | Recorded as |
|---|---|---|
| 1 | The session's own model: agent@model, session model:, dashboard Spawn, CLI | session |
| 2 | nid.yaml models (the group's, else the file's top level) | project-role |
| 3 | nid.yaml models.<agent> (the group's, else the file's top level) | project-agent |
| 4 | ~/ models | global-role |
| 5 | ~/ models.<agent> | global-agent |
| 6 | The agent's own default: nid passes no model | agent-default |
A role string names one vendor's model, so it is only used for agents of that vendor:
claude takes Claude aliases (opus, sonnet, haiku, opusplan, default, ...) and
claude-* ids; codex takes gpt-*, o<n>* and codex-*; gemini takes gemini-*; any
other agent takes only names with no recognisable vendor (such as opencode's
anthropic/). Otherwise that step is skipped and the next one applies:
with roles: { lead: opus } a codex lead falls through to models.codex. Values keyed
by agent (models.codex, roles) always apply.
The model is chosen when a session is created and kept with it, together with its
source (nid ls shows a model that came from configuration as configured until
the agent reports the model it runs). Editing models: later affects new sessions only;
change a running session's model with nid model. An invalid models: block does not
stop spawning: the new session shows a warning naming the file and key, and that block is
skipped (the next source in the table applies). Model names have no spaces or control
characters, do not start with - and have at most 200 characters; whether an agent accepts a
name is not checked here (nid models [agent] lists what each agent accepts).
User configuration#
# ~/.nid/config.yaml (or $NID_HOME/config.yaml)
agents:
myagent:
command: myagent
args: [--acp]
mode: acp
billing: # subscription | api | auto, or per agent (see billing.md)
default: auto
codex: subscription
fallback: off # api: rate-limited subscription sessions continue on their API key
permissions: worktree # worktree | ask | yolo: default permission profile (see security.md)
crashResume: stopped # stopped | continue: after a crash/reboot, leave sessions stopped (default) or continue them
models: # default models per agent type and role (see Default models above)
claude: sonnet
roles: { lead: opus }
compression: # token compression defaults (see compression-settings.md)
enabled: true
minTokens: 300
notify:
desktop: true
events: [approval, question, blocked, done, budget]
webhooks:
- url: https://example.com/nid-events
kind: json
events: [done, budget]
prices: # USD per 1M tokens; overrides or extends the built-in table
gpt-6-astra: { input: 10, cachedInput: 1, output: 50 }
my-local-model: { input: 0, output: 0 }
Model prices#
prices maps a model id to its list price in USD per 1M tokens: input (uncached) and
output (including reasoning) are required; cachedInput (cache reads) and cacheWrite default
to the input price. nid uses them to estimate what a turn would cost at API prices for agents
that report tokens but no dollars (Codex, ACP agents such as opencode, Gemini CLI and Kimi). Ids are
matched after dropping a provider prefix (openai/gpt-5 is gpt-5), a dated snapshot suffix and,
for Claude, writing 5.5 as 5-5. Entries here win over the built-in table in
packages/ (OpenAI, Anthropic, Google and Moonshot list prices as of the date in
PRICES_AS_OF). Models in neither have no estimate: their turns show tokens only. An invalid
prices section is ignored (the daemon logs why); the file is re-read when it changes.
Custom agent fields#
These fields are identical in project and user agents sections. Agent names start
with a lowercase letter and contain lowercase letters, digits, _ or - (max 32).
See custom agents for complete examples.
| Field | Type / default | Meaning |
|---|---|---|
command | nonempty string / required | Executable name or path. |
mode | pty or acp / required | Terminal driver or Agent Client Protocol over stdio. |
args | string array / empty | Arguments always passed. |
env | string-to-string map / empty | Environment overrides merged for the agent process. |
promptPattern | valid nonempty regex / unset | PTY ready prompt, matched in multiline mode. |
busyPattern | valid nonempty regex / unset | PTY working indicator; prevents prompt-based early turn completion. |
quietMs | integer 100–600000 / 3000 | PTY silence timeout; extended tenfold while the busy indicator remains. |
interrupt | nonempty string / ctrl-c | PTY interrupt key (esc, ctrl-<letter>, enter, tab) or escaped bytes. |
modelArgs | string array / empty | Added only when a model is selected; use {model}. |
models | array of ids or {id, description} / unset | Models the agent accepts, listed by nid models and used to check a model choice; it wins over what an ACP agent's sessions advertised. |
mcp | none or object / unset | PTY MCP injection: args and/or file; ACP receives MCP through its protocol. |
systemPrompt | prefix or none / prefix | Prefix the first message with coordination instructions. |
description | string / unset | Displayed agent explanation. |
untested | boolean / false | Mark unverified integration definitions in the catalog. |
mcp.args is a string array. mcp.file requires path and optionally content;
without content nid writes its standard mcpServers JSON. The path resolves
against the worktree, but absolute paths and .. can write outside it; this is
trusted configuration, not the constrained copy/link path mechanism. Choose a
private path inside the worktree; inside-worktree files are git-excluded best-effort.
Templates {configPath}, {cwd}, {session} and {model} expand in commands,
arguments, environment values and MCP settings. Unknown template names stay literal.
For PTY mode, absent mcp means no coordination tools; ACP's protocol injection
does not depend on mcp.args or mcp.file.
Notification fields#
Global notify and group notify have the same shape:
| Field | Type / default | Meaning |
|---|---|---|
desktop | boolean / true | Native desktop notifications; group value overrides global. |
events | event-name array / all | Group filter replaces global; empty array disables matching events. |
webhooks | object array / empty | Group targets are added to global targets, deduplicated by URL. |
webhooks[].url | HTTP(S) URL / required | Notification destination; treat secret-bearing URLs as credentials. |
webhooks[].kind | slack, discord, json / inferred | Inferred from Slack/Discord host, otherwise JSON. |
webhooks[].events | event-name array / all | Additional per-target filter. |
Event names: approval, question, blocked, stall, limit, done, budget,
review, pr, test. NID disables desktop notifications
regardless of YAML. Notification config reloads when the user file changes.
nid notify config [group] shows effective settings with webhook URLs masked.
The explicit notify test command bypasses event filters and deduplication so you
can check configured destinations even when no normal event is selected.
Environment variables#
Set daemon settings before starting it; restart the daemon to change its environment.
Most users only need NID_HOME, dashboard port selection or release credentials.
| Variable | Default / behavior |
|---|---|
NID_HOME | ~/.nid; database, runtime files, logs, worktrees and user config. |
NID_PORT | 0: choose a free loopback daemon RPC port. |
NID | 7420 with free-port fallback; a number pins it; off defers the listener until requested. |
NID_NO_BROWSER | Any nonempty value suppresses launching the browser. |
NID | 60 consecutive automatic turns before waiting for a human message. |
NID | 10 minutes without agent events while working; positive fractions accepted. |
NID | 30000 ms quiet/stall scan interval. |
NID | 3 minutes before interrupting an ACP turn for a waiting message; 0 disables. |
NID | 60000 ms minimum process lifetime for automatic crash recovery. |
NID | 600000 ms window that permits one automatic crash recovery. |
NID | 0 keeps sessions stopped after the daemon itself died, even in groups with crashResume: continue. (By default they stay stopped anyway; see crashResume.) |
NID | 30000 ms between write retries while the disk holding NID_HOME is full; no new turns start until a write succeeds. |
NID | 20000 ms a daemon may go without answering before daemon stop, daemon restart and update treat it as hung (pinged every second). |
NID | 5000 ms between checks that daemon.json is intact; the daemon rewrites it when it was deleted or corrupted. |
NID | 20000 events per session; positive integer, otherwise default. Pruning occurs in batches. |
NID | 64 simultaneous event streams. |
NID | 0 disables desktop notifications. |
NID | 600000 ms deduplication interval per event key. |
NID | 6 notifications per target per rate window. |
NID | 60000 ms notification rate window. |
NID | 12000 characters of diff included in a review task; full diff remains available. |
NID | Normally random 2000–4999 ms delay after a rate-limit reset; nonnegative override. |
NID | Codex home whose session files feed nid usage (default CODEX_HOME, else ~/.codex); off disables. See usage. |
NID | 300000 ms usage re-read interval while a session is limited. |
NID | 30000 ms: account data must be this much newer than a limit to clear it. |
NID | 1 disables background update checks (normally at most once daily on a terminal). |
NID | CatsWithKeyboards/; owner/repo used by installer/updater/local publisher. |
NID | https:/; updater API override, mainly for tests/custom hosting. API tokens go to this origin. |
NID_GITHUB_API | PR/CI REST API override; also selects API transport instead of gh. Default derives from the repository host. |
NID_VERSION | Installer-only exact release version (same as --version / -Version). |
NID_CHANNEL | Installer-only release channel, stable, beta or alpha (same as --channel / -Channel); saved to $NID, which nid update --channel also writes. |
NID | https:/; release pages used for downloads when the API is unavailable or rate-limited (installers and updater). Never receives a token. |
NID | Optional token for release lookups (preferred over GITHUB_TOKEN/GH_TOKEN); public releases need none. |
NID | Installer destination; otherwise platform-specific default. |
NID | Installer: 1 leaves shell profiles/user PATH alone. |
NID | 1 lets the daemon start while a terminid or pawmux daemon runs on the migrated ~/.terminid or ~/.pawmux (rolling back on purpose); otherwise it stops that daemon. See upgrading from terminid. |
Releases are public and need no token. GITHUB_TOKEN/GH_TOKEN (or a logged-in gh, asked only
when https:/ refuses the anonymous request; never for a custom NID) raise the API rate limit, open private forks and are used for
GitHub operations such as PRs. NO_COLOR suppresses terminal color.
Deprecated TERMINID_* and PAWMUX_* names#
Every NID_* variable above is also read under its old names: when NID_HOME
is unset, TERMINID_HOME is used, then PAWMUX_HOME, and so on. The first read of each old name prints one
deprecation line to stderr; NID silences it. When more than one is set,
the NID_* value wins, then TERMINID_*. Rename them in your shell profile when convenient; the
fallback will be removed in a later release.
Developer/internal overrides: NID_BIN selects the executable used for nid child
processes (unset when testing source), and NID overrides the source
build identity. NID_SESSION and NID are injected MCP session
identity/credentials; do not set them for normal use (agents started before an upgrade
still see TERMINID/TERMINID, which nid also sets for one release). __NID_BUILD__ and
_ are compile-time globals, not environment settings.
Build/signing variables (NID_BUN, NID_SIGN, certificate and ASC settings)
are covered in releasing; opt-in NID_LIVE_* and browser-test
variables are covered in development.
nid.yaml and nid up#
Declare groups in a nid.yaml (or nid.yml) at the repository root and start them with one
command:
groups:
web:
goal: Add dark mode to the settings page
policy: lead # lead | peers
setup: pnpm install # run in each new worktree
budgetUsd: 20 # pause automatic turns once the group spends this much
autoRestart: true # resume crashed agents once (default; sessions can override)
dir: . # repository, relative to this file (default: its folder)
sessions:
- claude:lead@opus # shorthand: agent[:name][@model]
- agent: codex
name: impl
permissions: ask # worktree (default) | ask | yolo
role: worker
- agent: opencode
prompt: Start by reading the test suite.
shared: false
Session fields: agent (required: claude, codex, opencode, kimi, mock or a custom agent), name
(default: the agent name, numbered when repeated), model, permissions, yolo, role, shared, autoRestart, prompt.
Unknown keys are rejected with a readable error.
nid up # every group in ./nid.yaml (searched upward from the current directory)
nid up web # just one group
nid up -f infra/nid.yaml -C some/dir
up is idempotent: it spawns missing sessions, resumes stopped ones and leaves running ones alone.
MCP isolation#
Agents default to mcp: project: nid's coordination server plus the repository's
own servers. Claude reads .mcp.json in the session worktree; Codex reads
.codex/ and its mcp_servers table. If these files are untracked,
include them in the group's copy setting so they reach each worktree.
| Mode | Available MCP servers |
|---|---|
project (default) | nid and the worktree's project servers |
all | nid plus the agent's normal inherited user/project servers and connectors |
none | nid only; coordination still works |
Set a global default in $NID (normally ~/):
mcp: project
Override it per group or per session in nid.yaml:
groups:
web:
mcp: project
sessions:
- claude:lead
- agent: codex
mcp: none
Precedence is session, group, global, then project. nid doctor reports the
global default. nid group set web mcp=none applies on the next agent start
(including resume); running processes keep the selection they started with.
Claude uses --strict-mcp-config with a merged config, and
ENABLE suppresses fetched claude.ai connectors
(Claude documentation).
Codex explicitly disables inherited servers, plugins and app connectors using
config overrides, retaining its login and other settings. An MCP table override
alone does not clear inherited servers because Codex merges configuration tables.
Native takeover uses the same selection for Claude and Codex.
ACP receives the selected project servers via its protocol. Other ACP/PTY agents
may still load their own user configuration: isolation is best effort unless the
agent provides a vendor-specific switch. Custom agents can supply it under
agents.<name>.mcp, which is added for project and none;
{mcpMode} is available in argument/environment templates. Custom mcp: none
inside an agent definition continues to mean that agent has no MCP integration.
Generated artifacts#
nid excludes common untracked artifacts from its automatic commits by default:
__pycache__/, *.pyc, *.pyo, node_modules/, .venv/, /venv/,
.pytest_cache/, .mypy_cache/, .ruff_cache/, .tox/, /target/, .next/,
.turbo/, .parcel-cache/, /coverage/, .nyc_output/, .DS_Store,
Thumbs.db, *.log, and .gradle/. Tracked files matching these patterns
still have their edits and deletions committed. dist/ is intentionally not excluded.
groups:
web:
excludes: [generated/, '*.tmp'] # add gitignore patterns to the defaults
# defaultExcludes: false # keep only your own excludes above
# excludes: false # turn off all nid artifact filtering
sessions: [claude:lead, codex]
nid group set web excludes=false changes the next status/staging operation.
Patterns live in per-session files under $NID, applied to
nid's Git status and staging commands through -c core.
This avoids changing Git's shared info/exclude, repository configuration, or
the human's main checkout. Existing global Git excludes are preserved. Agents'
own Git commands continue to use the repository's normal ignore rules, so their
raw git status may still list artifacts that nid will omit from commits.
The /target/, /coverage/ and /venv/ defaults match only the worktree root;
source directories with those names under src/ remain eligible for commits.
After automatic staging (including an artifacts-only attempt), nid messages the
agent about ambiguous omissions such as logs, root output directories and custom
excludes. Routine caches, dependencies, .venv/, Python bytecode and OS metadata
stay silent, as do repository and user ignores. Reports show up to 20 paths and
the full count, and repeat only when the ambiguous omitted set changes during
that session. They appear in verbose feeds only and never on the board. The files stay
on disk. For an intentional file such as a log fixture, git add -f <path> includes
it in the next commit; alternatively adjust excludes or defaultExcludes.
Local files in worktrees#
Keep local configuration available to agents without committing it. Set paths relative to the
group's main checkout in nid.yaml:
groups:
web:
copy: [.env, "config/**/*.json"]
link: [node_modules]
setup: pnpm install
sessions: [claude, codex]
copy accepts files, directories and globs (* and ? within a path segment, ** across
directories, including dotfiles). link accepts directories, including directory globs.
Links share the original directory, so writes through a link also change the main checkout.
Copying is independent. Files arrive before setup, only in newly created isolated worktrees;
resuming an existing session leaves its files alone. Existing files are preserved, tracked files
are skipped, and missing sources produce notices in the session log. Paths must remain within
the repository; Git metadata and source symlinks are skipped.
nid spawn claude codex --copy .env --copy "config/*.json" --link node_modules
nid up web --copy .env --link node_modules
The up flags replace the corresponding configured list for that invocation. Spawn flags
persist as group settings for future sessions. Change them later through Manage group →
Goal & budget → Group options, or group.update with
settings: {copy: [".env"], link: ["node (empty arrays clear the lists).
Copied and linked paths are added to Git's info/exclude, resolved from the worktree and
deduplicated. Git shares this exclude file across the repository's worktrees. Normal task
completion and integration therefore leave these local files out of commits.
Group settings#
Daily-use features (stall handling, rate limits, worktree files, review gate, notifications) are configured per group. Put the keys at the top level of a group in nid.yaml, or change them on a running group:
nid group show web
nid group set web stallMinutes=20 copy=.env,config/local.json review=auto
nid group set web stallMinutes= # empty value: back to the default
Values are JSON when they parse (notify='{"desktop":false}'); copy, link and reviewers also
take comma-separated lists. The keys and defaults are listed under
Group settings in the API reference; the dashboard's group settings
dialog edits the same fields.
Notifications#
nid tells you when it needs you: an approval request, a question, a blocked task, a stalled or
rate-limited agent, a group posting DONE, or a budget reached. It shows desktop notifications
(Windows toast through PowerShell, macOS osascript, Linux notify-send; nothing to install, and
silently skipped when unavailable) and posts to webhooks. Configure targets for every group in
~/:
notify:
desktop: true
webhooks:
- url: https://hooks.slack.com/services/T000/B000/XXXX # Slack
- url: https://discord.com/api/webhooks/123/abc # Discord
- url: https://example.com/hooks/nid # anything else gets plain JSON
kind: json
events: [done, budget] # optional filter per webhook
or per group (notify: in nid.yaml, nid group set web notify='{"desktop":false}', or the
dashboard's Manage group → Notifications dialog). Repeated events are de-duplicated and each
target is rate-limited.
nid notify test [group] # send a test notification to every target and show the result
nid notify config [group] # effective targets (webhook URLs masked)
nid notify ls [group] # recent notifications and their delivery status