CLI Reference
Every verb. Every flag. Every exit code. Authored against the v0.6.0 release.
bl is a single bash script (~10,700 lines) assembled from 26 numbered parts under src/bl.d/ via make bl. Nine top-level verbs dispatch from main() in 90-main.sh. Every verb except setup and --help/--version runs bl_preflight before the verb body.
This page is the canonical reference. For why the surface looks like this, see Architecture. For how to configure the runtime, see Configuration.
At a glance
| Verb | Purpose | Default tier | Source part |
|---|---|---|---|
bl observe | Read-only evidence extraction (logs, fs, cron, proc, htaccess, firewall, sigs, substrate, bundle) | read-only | 41-observe-collectors.sh, 42-observe-router.sh, 45-cpanel.sh |
bl consult | Open / attach a case via the curator | read-only | 50-consult.sh |
bl run | Execute an agent-prescribed step (tier-gated) | varies (per step) | 60-run.sh |
bl defend | Apply an agent-authored defensive payload (modsec / firewall / sig) | auto / suggested | 82-defend.sh |
bl clean | Diff-confirmed remediation (htaccess / cron / proc / file) | destructive | 83-clean.sh |
bl case | Inspect / log / close / reopen cases | read-only / destructive (close) | 70-case.sh |
bl setup | Provision or sync the Anthropic workspace | n/a (preflight bypass) | 84-setup.sh |
bl trigger | Open a case from a hook-fired event (LMD) | read-only | 29-trigger.sh |
bl flush | Drain queued outbox records | read-only | 27-outbox.sh |
Global conventions
- Help bypass.
bl -h | --help | helpandbl <verb> --helpboth bypass preflight.bl setup --helpworks on an unseeded host. - Version.
bl -v | --versionprintsbl 0.5.0. - JSONL on the wire. Every
bl observecollector emits one record per line. See Evidence Format. - Tier authorship. The agent classifies every step in
bl-case/<case>/pending/<id>.json. The Runner enforces the gate based on verb class plus tier — verb class always wins.clean.*is always destructive regardless of agent-asserted tier. - Schema validation.
bl runvalidates the step JSON againstschemas/step.jsonbefore executing. BL_DISABLE_LLM=1short-circuits every LLM call across the surface. The full BATS suite exports this.
Exit codes
| Code | Symbol | Meaning |
|---|---|---|
| 0 | BL_EX_OK | Success. |
| 64 | BL_EX_USAGE | Bad arguments / unknown subcommand. |
| 65 | BL_EX_PREFLIGHT_FAIL | Missing ANTHROPIC_API_KEY, curl, or jq; or /var/lib/bl/ unwritable. |
| 66 | BL_EX_WORKSPACE_NOT_SEEDED | No agent record in the workspace. Run bl setup --sync. |
| 67 | BL_EX_SCHEMA_FAIL | A step or payload failed JSON-schema validation. |
| 68 | BL_EX_TIER_GATE_DENIED | Tier gate refused the action (unknown verb, destructive without --yes, etc.). |
| 69 | BL_EX_UPSTREAM_ERROR | 5xx from Anthropic after retry exhaustion. |
| 70 | BL_EX_RATE_LIMITED | 429 from Anthropic; outbox-queued for next preflight. |
| 71 | BL_EX_CONFLICT | 409 from Anthropic on a CAS write; the agent-update path retries once before propagating. |
| 72 | BL_EX_NOT_FOUND | Targeted resource (case, step, file) missing. |
POSIX 1–63 reserved by convention; 73–79 reserved for future blacklight use; 80–127 unused; 128+ are signal-based exits inherited from bash.
Tier matrix
Every bl observe, bl consult, and bl case show/log/list is read-only by verb class. Every bl clean is destructive regardless of agent-asserted tier (verb class re-asserts). bl defend and bl run flow through the agent's action_tier field. See Action Tiers for the full matrix.
| Verb / sub-verb | Verb-class tier | Notes |
|---|---|---|
bl observe * | read-only | Auto-execute; never prompts. |
bl consult * | read-only | |
bl case show / list / log | read-only | |
bl case close | destructive | Agent confirms open_questions = 0; brief written. |
bl case reopen | suggested | Operator confirmation. |
bl run | (delegated) | Per-step tier from action_tier field; verb class re-asserts on clean.*. |
bl defend firewall <ip> | auto | Apply + 15-min veto window. |
bl defend sig | auto (after FP gate) | Deterministic + Haiku 4.5 adjudication. |
bl defend modsec | suggested | apachectl -t pre-flight; explicit --yes. |
bl clean * | destructive | Diff shown; backup written; --yes per-op. |
bl setup * | n/a | Preflight bypass; idempotent. |
bl trigger lmd | read-only | Opens / attaches a case; no mutation. |
bl flush --outbox | read-only | Drain only. |
bl observe
Read-only evidence extraction. Auto-runs (no confirm). Emits JSONL to stdout and appends structured output to the current case bundle.
Synopsis
bl observe <subcommand> [args] [flags]
Sub-commands
| Subcommand | Purpose |
|---|---|
bl observe file <path> | stat, magic, sha256, strings, file(1) on a target. |
bl observe log apache --around <path> [--window <dur>] [--site <fqdn>] | Time-window vhost log slice for the path. JSONL with apache.transfer records. |
bl observe log modsec [--txn <id>] [--rule <id>] [--around <path>] | ModSec audit A/B/F/H section walker. |
bl observe log journal --since <time> [--grep <pattern>] | journalctl extract. |
bl observe cron --user <user> [--system] | crontab -l piped through cat -v to reveal ANSI ESC[2J persistence. |
bl observe proc --user <user> [--verify-argv] | argv[0] vs /proc/<pid>/exe basename mismatch detection (gsocket-class). |
bl observe htaccess <dir> [--recursive] | Injected directive flagging. |
bl observe fs --mtime-cluster <path> --window <N>s [--ext <list>] | Cluster discovery within a time window. |
bl observe fs --mtime-since <date> [--under <path>] [--ext <list>] | Retrospective sweep. |
bl observe firewall [--backend auto] | APF / CSF / iptables / nftables enumeration. |
| `bl observe sigs [--scanner lmd | clamav |
bl observe substrate | cPanel / EasyApache / hosting-stack inventory (12 substrate.* records). |
bl observe bundle | Assemble all collected artifacts into an evidence bundle. |
Common flags
| Flag | Default | Purpose |
|---|---|---|
--window <duration> | varies | Time window for log slicing (6h, 30m). |
--around <path> | — | Anchor a log slice to events touching this path. |
--site <fqdn> | (auto) | Filter Apache by vhost. |
--ext <list> | — | Comma-list of extensions to filter (php,phtml,inc). |
--scanner lmd|clamav|yara | (all) | Limit signature inventory to one engine. |
Examples
# Time-window slice around a suspicious file
$ bl observe log apache --around /var/www/html/pub/media/catalog/product/.cache/a.php --window 6h
# Retrospective fleet sweep against APSB25-94 disclosure date
$ bl observe fs --mtime-since 2026-03-20T00:00:00Z --under /var/www/html --ext php
# Bundle everything collected on this host into a tar+gzip evidence pack
$ bl observe bundle
Tier
Read-only. Auto-execute. No confirm prompt.
Exit codes
0, 64 (bad args), 65 (preflight), 66 (workspace unseeded).
See also
bl consult --newto open a case anchored on the observation.- Evidence Format — JSONL contract, bundle shape.
bl consult
Case lifecycle: open a new case or attach the operator shell to an existing one. Wakes the curator session.
Synopsis
bl consult --new --trigger <path-or-event> [--notes "..."] [--dedup]
bl consult --attach <case-id>
bl consult --sweep-mode [--cve <id>]
Modes
| Mode | Purpose |
|---|---|
--new | Allocate CASE-YYYY-NNNN (flock-serialized counter), materialize templates into bl-case, create a session via POST /v1/sessions with workspace + per-case Files attached, POST a wake event. Returns the case-id on stdout. |
--attach <id> | Format-guard CASE-YYYY-NNNN, probe hypothesis.md, set case.current to the matched case. |
--sweep-mode | Read-only inventory of closed cases; optional CVE filter against each case's hypothesis. |
Flags
| Flag | Default | Purpose |
|---|---|---|
--trigger <path-or-event> | — | Required for --new. Path to the artifact that prompted the case, or an event blob. |
--notes "..." | — | Free-text notes appended to the trigger record. |
--dedup | off | If the trigger fingerprint matches an open case within lmd_trigger_dedup_window_hours, attach instead of opening a new one. |
--cve <id> | — | Sweep-mode only. Filter to cases referencing the named CVE in their hypothesis. |
Examples
# Open a fresh case from a polyshell artifact
$ bl consult --new --trigger /var/www/html/pub/media/catalog/product/.cache/a.php
CASE-2026-0017
# Attach the shell to an existing case (sets case.current)
$ bl consult --attach CASE-2026-0017
# Retrospective sweep across closed cases for a specific CVE
$ bl consult --sweep-mode --cve APSB25-94
Tier
Read-only. No host mutation. Wakes the curator session.
Exit codes
0, 64, 65, 66, 69 (upstream), 70 (rate limit), 71 (case allocation collision after 50 retries), 72 (--attach target missing).
See also
bl run --list— surface the steps the curator emitted.bl trigger lmd— case open from a hook-fired event.
bl run
Execute an agent-prescribed step under tier-gated safety.
Synopsis
bl run <step-id> [--yes] [--dry-run] [--unsafe] [--explain]
bl run --list
bl run --batch [--max <N>] [--yes]
Modes
| Mode | Purpose |
|---|---|
<step-id> | Pull <step-id> from bl-case/<case>/pending/, validate against schemas/step.json, prompt per tier policy, execute, write the result, append to ledger + memstore. |
--list | Single-fetch listing of all pending steps for the current case with their action tier and a one-line synopsis. |
--batch [--max <N>] | Execute up to <N> pending steps in order (default 16). Read-only and auto auto-run; suggested honours --yes; destructive still gates per-step. |
Flags
| Flag | Default | Purpose |
|---|---|---|
--yes | off | Skip the operator prompt at suggested tier. Destructive requires --unsafe --yes. |
--unsafe | off | Required for unknown-tier (verb not in dispatch table) or destructive steps. |
--dry-run | off | Show the plan and the diff; take no action; write nothing. |
--explain | off | Dump the step's reasoning field from the pending JSON before prompting. |
--max <N> | 16 | Batch upper bound. |
Examples
# Show the queue
$ bl run --list
s-0001 observe.modsec read-only confirm rule coverage before payload
s-0043 defend.modsec suggested polyshell double-ext staging
s-0044 clean.cron --user x destructive strip ANSI-obscured cron entry
# Auto-execute a read-only step
$ bl run s-0001
# Explicit-yes on a suggested ModSec rule
$ bl run s-0043 --yes
# Drain the queue (auto + read-only auto-run; suggested honours --yes)
$ bl run --batch --max 8 --yes
Tier
Delegated. The step's action_tier field controls the gate. The Runner re-asserts on verb class — clean.* is always destructive, unknown verbs always deny.
Exit codes
0, 64, 65, 66, 67 (schema fail), 68 (tier gate denied / unknown verb), 69, 70, 72 (step-id not in pending).
See also
- Action Tiers — full gate matrix.
bl consult --new— what populates the pending queue.
bl defend
Apply an agent-authored defensive payload. Three families: ModSec rules, firewall blocks, scanner signatures.
Synopsis
bl defend modsec <rule-file-or-id> [--remove] [--rollback <event-id>]
bl defend firewall <ip> [--backend auto] [--case <id>] [--reason <str>] [--retire <duration>]
bl defend sig <sig-file> [--scanner lmd|clamav|yara|all]
Sub-commands
| Subcommand | Purpose |
|---|---|
defend modsec | apachectl -t pre-flight in the curator sandbox; on pass, symlink-swap into mods-enabled/ + apachectl graceful; on fail, rollback to previous symlink target. --remove reverses; --rollback <event-id> restores from ledger. |
defend firewall | Auto-detect APF / CSF / iptables / nftables; CDN safe-list pre-check (internal allowlist + WHOIS/ASN cache); apply; ledger entry with retire-hint. |
defend sig | FP-baseline gate (run sig against $BL_DEFEND_FP_CORPUS) → Haiku 4.5 adjudication of borderline hits → on 0 FP, append to scanner sig file and reload. Auto-tier iff FP gate passes. |
Flags
| Flag | Default | Purpose |
|---|---|---|
--remove | off | Reverse the rule (modsec) or revoke the block (firewall). |
--rollback <event-id> | — | Restore from a prior ledger event. |
--backend <name> | auto | Force apf, csf, iptables, or nftables. |
--case <id> | (current) | Tie the action to a specific case. |
--reason <str> | — | Free-text reason recorded in the ledger entry. |
--retire <duration> | 30d | When the firewall block becomes eligible for the retire-sweep. |
--scanner <name> | all | Limit defend sig to one engine. |
Examples
# Apply a curator-authored ModSec rule (passes apachectl -t in sandbox first)
$ bl defend modsec /tmp/bl-CASE-2026-0017-941999.conf --yes
# Block an attacker IP with a 30d retire hint
$ bl defend firewall 203.0.113.42 --reason "polyshell campaign C2" --retire 30d
# Promote a curator-authored YARA sig (deterministic FP gate + Haiku adjudication)
$ bl defend sig /tmp/bl-polyshell-double-ext.yar --scanner yara
Tier
auto—defend firewall <ip>(new block; CDN safe-listed);defend sigafter FP gate passes.suggested—defend modsec(new rule).destructive—defend modsec --remove.
Exit codes
0, 64, 65, 66, 67, 68, 69, 70, 72.
See also
- Action Tiers — auto vs suggested vs destructive policy.
- Security Model · CDN safe-list — firewall guard rails.
bl clean
Diff-confirmed remediation. Backup before apply. Quarantine, never unlink. --undo round-trips.
Synopsis
bl clean htaccess <dir> [--patch <file>] [--dry-run]
bl clean cron --user <user> [--patch <file>] [--dry-run]
bl clean proc <pid> [--capture] [--dry-run]
bl clean file <path> [--reason <str>] [--dry-run]
bl clean --undo <backup-id>
bl clean --unquarantine <entry-id>
Sub-commands
| Subcommand | Purpose |
|---|---|
clean htaccess <dir> | Remove an injected directive block (e.g., <FilesMatch "\.php$"> routing). Diff shown; backup written; explicit --yes. |
clean cron --user <user> | Strip ANSI-obscured persistence from a user crontab. cat -v reveal happens before the diff. |
clean proc <pid> | /proc/<pid>/{cmdline,environ,exe,cwd,maps,status} + lsof snapshot; then SIGTERM → SIGKILL. Default is capture-on. |
clean file <path> | Move (never unlink) the file to $BL_VAR_DIR/quarantine/<case>/<sha256>-<basename>. Manifest records original metadata. |
--undo <backup-id> | Restore from $BL_VAR_DIR/backups/<id>. |
--unquarantine <entry-id> | Restore a quarantined file to its original path. |
Flags
| Flag | Default | Purpose |
|---|---|---|
--patch <file> | — | Apply a specific patch file rather than letting the curator compute the diff in-session. |
--capture | on | clean proc only. --capture=off skips the /proc snapshot. |
--reason <str> | — | Recorded in the manifest entry for clean file. |
--dry-run | off | Show the diff and the backup path, write nothing. |
Diff prompt
For file-edit subcommands the prompt offers [y/N/diff-full/explain/abort]:
- y — apply.
- N — cancel; mark step operator-rejected (the curator sees this).
- diff-full — show the whole before/after.
- explain — dump the curator's reasoning field.
- abort — cancel and emit a session warning until the case closes.
Examples
# Strip an injected FilesMatch block; diff shown, backup written
$ bl clean htaccess /home/sitefoo/public_html --yes
# Remove the entire cron entry an attacker hid via ANSI escape
$ bl clean cron --user sitefoo --yes
# Capture /proc state then SIGTERM → SIGKILL the process
$ bl clean proc 31415
# Quarantine a polyshell sample (file moves to /var/lib/bl/quarantine/<case>/)
$ bl clean file /var/www/html/.cache/a.php --reason "polyshell APSB25-94"
# Roll back a clean operation
$ bl clean --undo 2026-04-26T04-27-15Z.htaccess
Tier
Always destructive by verb class. Agent-asserted tier is ignored. Backup written before every apply; quarantine-not-delete enforced for clean file.
Exit codes
0, 64, 65, 66, 67, 68, 72.
See also
- Security Model · Filesystem safety — TOCTOU rationale.
- Action Tiers · Destructive — full discipline.
bl case
Inspect / log / close / reopen. Cases are allocated by bl consult --new — there is no bl case open.
Synopsis
bl case list [--open|--closed|--all]
bl case show [<case-id>]
bl case log [<case-id>] [--audit]
bl case close [<case-id>] [--force]
bl case reopen <case-id> --reason <str>
Sub-commands
| Subcommand | Purpose |
|---|---|
list | Workspace case roster on this host. |
show | Six-section summary: hypothesis, evidence, pending, applied, defenses, ledger tail. |
log | Full chronological ledger. --audit appends per-kind summary + decoded fence-wrapped wake entries from outbox. |
close | Agent validates open_questions = 0, renders the brief (Markdown canonical, HTML/PDF best-effort via curator sandbox). T+30d retire-sweep scheduled for applied firewall blocks. |
reopen | Re-attach a closed case to its session. Requires --reason. |
Flags
| Flag | Default | Purpose |
|---|---|---|
--open / --closed / --all | --all | Filter for bl case list. |
--audit | off | bl case log only. Per-kind summary + outbox fence decode. |
--force | off | bl case close only. Override the open-questions gate (operator-only escape). |
--reason <str> | — | Required for bl case reopen. |
Examples
# List open cases on this host
$ bl case list --open
# Six-section summary of the current case
$ bl case show
# Audit-format ledger for a regulator
$ bl case log CASE-2026-0017 --audit
# Close after the curator confirms open_questions = 0
$ bl case close
# Reopen with a documented reason
$ bl case reopen CASE-2026-0017 --reason "T+5d sweep showed re-pivot"
Tier
list / show / log are read-only. close is destructive (writes the brief, schedules retires). reopen is suggested.
Exit codes
0, 64, 65, 66, 68, 69, 70, 72.
See also
bl consult --new— case allocation.- Architecture · State model —
bl-casememstore layout.
bl setup
Workspace bootstrap. Idempotent. Preflight is bypassed (setup carries its own local preflight).
Synopsis
bl setup --sync [--dry-run]
bl setup --reset [--force]
bl setup --gc
bl setup --eval [--promote]
bl setup --check
bl setup --install-hook lmd
bl setup --import-from-lmd
Sub-commands
| Subcommand | Purpose |
|---|---|
--sync | Provision (or update) agent + Skills + Files. SHA-256 delta-check; only changed content is pushed. |
--reset | Archive the agent (POST /v1/agents/<id>/archive), delete Skills + workspace Files, clear identity from state.json. Preserves case_* keys so audit trail survives. |
--gc | Walk state.files_pending_deletion[]; delete each file_id only when no live session in state.session_ids references it. |
--eval | Live skill-routing eval (BL_EVAL_LIVE=1). --promote bumps the agent version on pass. |
--check | Print state.json snapshot + per-resource health. Hits no write endpoints. |
--install-hook lmd | Drop bl-lmd-hook into /etc/blacklight/hooks/ and edit conf.maldet's post_scan_hook=. |
--import-from-lmd | Read conf.maldet, write notify creds to /etc/blacklight/notify.d/* (mode 0600, metacharacter-rejected). |
Flags
| Flag | Default | Purpose |
|---|---|---|
--dry-run | off | Preview the calls --sync would make; hits no write endpoints. |
--force | off | Required for destructive surfaces (--reset). |
--promote | off | Bump the agent CAS version on a passing eval. |
Live-API contract (M15)
- Agent update:
POST /v1/agents/<id>with{version: <current>, ...}. PATCH returns 405. On 409 the client refetches and retries once. - Agent retire:
POST /v1/agents/<id>/archive. DELETE returns 405. Empty{}body.--resetaborts if archive fails — never wipestate.jsonwhile a live agent exists. - Sessions.create body:
{agent: <id>, environment_id: <id>, resources: [...]}. The field isagent, notagent_id. - Environments body:
{name, config:{type:"cloud", networking:{type:"unrestricted"}}}only. Nopackages,setup_script, orimagefield.
Examples
# First-run on a fresh workspace (or no-op on a seeded one)
$ export ANTHROPIC_API_KEY="sk-ant-..."
$ bl setup --sync
# Idempotent re-sync after editing skills
$ bl setup --sync
# Wipe everything except case audit trail
$ bl setup --reset --force
# Wire LMD's post_scan_hook to bl trigger
$ bl setup --install-hook lmd
Tier
Preflight bypass. --sync and --gc are non-destructive at workspace scope; --reset requires --force.
Exit codes
0, 64, 65, 66 is impossible (this is the verb that seeds it), 67, 69, 70, 71 (CAS conflict, retried internally).
See also
- Setup Flow — full call-by-call API contract.
- Anthropic API Notes — the friction this verb routes around.
bl trigger
Hook-fired case open from existing trigger surfaces. Today: LMD post_scan_hook.
Synopsis
bl trigger lmd --scanid <id> [--session-file <path>] [--source-conf <path>] [--unattended]
Sub-commands
| Subcommand | Purpose |
|---|---|
lmd --scanid <id> | Parse LMD per-session TSV → JSONL; compute cluster fingerprint sha256(scanid || sigs || paths)[:16]; bl_consult_new --dedup with lmd_trigger_dedup_window_hours from blacklight.conf (default 24h). Degraded path on empty/unreadable TSV opens a stub case anyway. |
Flags
| Flag | Default | Purpose |
|---|---|---|
--scanid <id> | — | Required. The maldet scan id. |
--session-file <path> | (auto) | Override for the LMD session TSV path. |
--source-conf <path> | (auto) | Override for the maldet conf used to resolve paths. |
--unattended | (auto) | Force unattended mode regardless of TTY detection. |
Examples
# Manual replay of an LMD hook-fire (LMD itself calls this from post_scan_hook)
$ bl trigger lmd --scanid 250426-141520.31415
# Force unattended (queue tier-gated steps for operator instead of prompting)
$ bl trigger lmd --scanid 250426-141520.31415 --unattended
Tier
Read-only at the trigger layer. The case it opens may emit destructive steps; the gate catches those at bl run time, not here.
Exit codes
0, 64, 65, 66, 69, 70.
See also
bl setup --install-hook lmd— wire the maldetpost_scan_hook=line.bl consult --new --dedup— the dedup mode this verb uses.
bl flush
Drain queued outbox records (wake / signal_upload / action_mirror). Idempotent. Safe from cron.
Synopsis
bl flush --outbox
Flags
| Flag | Default | Purpose |
|---|---|---|
--outbox | — | Drain $BL_VAR_DIR/outbox/. |
Drain policy
Preflight runs an age-gated drain automatically when the oldest queued record is ≥ BL_OUTBOX_AGE_WARN_SECS (default 3600). bl flush --outbox forces a drain regardless of age — useful from cron, useful when chasing a queued record.
Watermarks: warn at 500 records, hard-stop at 1000 (returns BL_EX_RATE_LIMITED=70). The cycle-break invariant in 27-outbox.sh keeps the queue's own backpressure-reject record from re-entering the ledger via the outbox path.
Examples
# Drain manually
$ bl flush --outbox
# From cron, every 15 minutes
*/15 * * * * /usr/local/bin/bl flush --outbox
Tier
Read-only at the verb level. The outbox replay re-issues network calls that may individually mutate workspace state (those are tier-checked at write time).
Exit codes
0, 64, 65, 69, 70.
See also
- Configuration · BL_OUTBOX_AGE_WARN_SECS — drain threshold.
- Architecture · Rate limiting — outbox's role.
Bash SDK
bl is built as a bash SDK that the CLI dispatcher consumes. Sourcing the assembled binary (source bl || true) exposes 137 bl_* functions across 14 families. Stable surfaces: bl_api_*, bl_files_*, bl_skills_*, bl_ledger_*, bl_outbox_*, bl_fence_*, bl_messages_call, bl_notify. Internal: everything else; subject to rename at any milestone.
Operators consuming the SDK should pin to a specific tagged version of bl. Full surface enumeration in Architecture · Bash SDK.
See also
- Configuration — env vars,
state.json,/var/lib/bl/,/etc/blacklight/blacklight.conf. - Architecture — three-layer contract, runtime flow, SDK surface.
- Setup Flow — endpoint-by-endpoint contract for
bl setup --sync. - Action Tiers — gate matrix for
bl run/bl defend/bl clean. - Security Model — what the gate catches.