BB-Eco template JSON format
This page documents the JSON file format used by bb-eco template export, bb-eco template apply, bb-eco template diff, and the desktop app's Download Template / Apply Template actions on a device's Info tab.
For the concepts behind config as code see Configuration as code with BB-Eco. For the CLI commands that read and write this format, see the bb-eco template reference.
Reference templates (download)
Two fully-commented reference templates document every key, its label on the device's web page, its factory default, and the values the device accepts:
- ED range reference template: Ethernet Remote IO (digital, analogue, temperature models)
- ES range reference template: Ethernet-to-Serial models
They are .jsonc files (JSON with comments) so every key can carry its explanation inline. template apply parses strict JSON, so strip the comments before feeding one to the tool:
sed 's|//.*||' ed-template-reference.jsonc > my-template.json
bb-eco template validate my-template.json
Example templates
Real exports, one per model, exactly as bb-eco template export writes them:
| Model | Example | What it shows |
|---|---|---|
| ED-588 | ed-588.json | Digital inputs and outputs, quick stop, serial gateway, I/O tunnelling |
| ED-549 | ed-549.json | Analogue inputs, % of FSR ASCII data format, hexadecimal Modbus data format |
| ED-560 | ed-560.json | Analogue outputs with values in the hexadecimal data format |
| ED-582 | ed-582.json | RTD inputs, Kelvin over ASCII and Fahrenheit over Modbus |
| ED-593 | ed-593.json, ed-593-millivolts.json | Thermocouple inputs in Fahrenheit, and in millivolts |
| ES-246 | es-246.json | An RS-232 port with FIFO control |
| ES-511 | es-511.json | A switchable port in RS422/485 half-duplex mode, static addressing |
| ES-522 | es-522.json | Two switchable ports in RS232 mode |
Top-level shape
{
"version": 6,
"family": "ED",
"compatibleModels": ["ED-588"],
"description": "Production line baseline",
"createdAt": "2026-10-22T14:21:59+00:00",
"identity": { /* read-only, shown by bb-eco config */ },
"device": { /* the device's home page: name, location */ },
"network": { /* the Network page */ },
"security": { /* the Security page */ },
"io": { /* ED: the Protocol and IO Lines pages */ },
"serialPorts": [ /* ES, and ED models with a Serial Port page */ ]
}
Every section and every key is optional. A missing key, a null, or a missing section means don't touch: BB-Eco leaves the corresponding setting on the target device unchanged, and template diff never reports it as drift.
How a template follows the device's page
The groups and keys follow the device's own web page, so a template and the page can be read side by side:
- Each section is a page, and inside it each group is a heading on that page:
io.protocol.asciiis the Protocol page's ASCII Protocol Settings,serialPorts[0].defaultSettingsis Serial Port 1's Default Port Settings. - A model's export has only the groups and keys its page shows. An ED-549 has
io.analogInputsand no digital groups; an RS-232 port has no Duplex Mode. - Choices are written as words taken from the page's own option text, such as
activeLowfor 0 = ON,fallingEdge,engineeringUnitorhalfDuplexAutogating. The option text itself is accepted too. Line states are1and0(the page's ON and OFF), check boxestrueandfalse. - In the desktop app, Configuration Status and the Apply Template preview name each change where the page shows it, for example Serial Port 1 > Default Port Settings > Baud Rate.
bb-eco configandbb-eco template diffuse the template keys,serialPorts[0].defaultSettings.baudRate. - A value a device can't take is refused before anything is written, in the app's preview and by
bb-eco template validate,applyanddiff. Each error names the values that are accepted and links to the section of this page that explains the setting.
Envelope fields
Five top-level fields describe the template itself, not the device configuration:
| Field | Type | Default | Description |
|---|---|---|---|
version | integer | null | 6 | Template version. Omit it and the current version is assumed. See Versioning. |
family | string | null | from device on export | Target device family: "ED", "ES", or "BB". Apply refuses to push a template to a device of a different family. |
compatibleModels | string[] | null | the exported model | Optional model allowlist. If set, apply refuses a device whose model isn't in the list. Naming the models also lets template validate check each IO line's number offline. |
description | string | null | null | Human-readable description. Shown in the desktop app's template list and in bb-eco template validate output. |
createdAt | RFC 3339 timestamp | null | export time | When the template was generated. Informational only. |
These envelope fields are left out of the bb-eco config <device> listing but written by bb-eco template export.
identity: read-only device identity
A read-only snapshot of one device. bb-eco template export and the app's Download Template leave it out, so an exported template is portable from the start; bb-eco config <device> shows it as part of the device readout. Never applied by template apply, never compared by template diff. If you add it to a template by hand, it is ignored.
"identity": {
"mac": "00:0A:4F:06:25:4D",
"ip": "192.168.1.101",
"model": "ED-549",
"modelVariant": null,
"firmwareVersion": "8.25",
"firmwareType": "T",
"firmwareDate": "26/06/2023",
"checkConnectivity": false,
"dns": "",
"modbusSlaveId": 255
}
| Field | Type | Description |
|---|---|---|
mac | string | Hardware MAC, colon-separated uppercase |
ip | string | Address the device was reachable at |
model | string | Model number (e.g. ED-549) |
modelVariant | string | null | Hardware variant suffix, if any |
firmwareVersion | string | Running firmware version |
firmwareType | string | null | Chip-class marker (e.g. T) |
firmwareDate | string | Firmware build date as the device reports it |
checkConnectivity | boolean | null | The device's connectivity check setting, as reported |
dns | string | null | ED: the DNS server the device reports |
modbusSlaveId | int | null | ED: the Modbus address the device reports (Modbus TCP does not use it) |
device: the device's home page
"device": {
"name": "Pumphouse1",
"location": "Pumphouse"
}
| Field | Page label | Type | Notes |
|---|---|---|---|
name | Device Name | string | null | Maximum 10 characters on ED, 40 on ES. BB-Eco rejects longer values before they reach the device |
location | Location | string | null | Maximum 10 characters. ED only: ES devices have no location |
On ED devices name and location are always written together as one operation, because the device only accepts them as a pair.
network: the Network page
"network": {
"dhcp": false,
"subnet": "255.255.255.0",
"gateway": "192.168.127.1",
"webPort": 80,
"upnp": true
}
| Field | Page label | Type | Notes |
|---|---|---|---|
dhcp | DHCP | boolean | null | true = DHCP, false = static. Static addressing wants staticIp + subnet + gateway together; apply fills any omission from the device's current configuration |
staticIp | IP Address | string | null | Dotted quad. Never exported (see Static IP addresses). Leave it out to keep the device's current address |
subnet | Subnet Mask | string | null | Dotted-quad subnet mask. Exported under static addressing only: under DHCP the DHCP server provides it |
gateway | Gateway Address | string | null | Default gateway IP. 0.0.0.0 = no gateway. Exported like subnet |
webPort | Web Server Port | integer | null | 1–65535 (default 80). Changing it restarts the device's web server |
upnp | UPnP | boolean | null | UPnP/SSDP announcements on/off |
network is applied last in the apply order so that an IP change can't strand the rest of the apply.
Static IP addresses
An exported template never contains the device's IP address. That holds even for a device on a static address: the export records "dhcp": false with its subnet and gateway, and leaves out staticIp. This keeps a template safe to apply to other devices, because two devices given the same address would conflict on the network.
When you apply a template, staticIp works like this:
| Template | Result on the device |
|---|---|
"dhcp": false, no staticIp | Static addressing, keeping the address the device has now. A device that was on DHCP keeps its current lease address as its static address |
"dhcp": false, "staticIp": "192.168.127.30" | Static addressing at 192.168.127.30, with the template's subnet and gateway |
"dhcp": true | DHCP. staticIp, subnet and gateway are ignored, and the device keeps its previous static settings |
To give a device a specific address, add staticIp to a copy of the template and apply that copy to that one device:
"network": {
"dhcp": false,
"staticIp": "192.168.127.30",
"subnet": "255.255.255.0",
"gateway": "192.168.127.1"
}
bb-eco template apply 192.168.127.24 ed-588-at-30.json --dry-run
bb-eco template apply 192.168.127.24 ed-588-at-30.json
The device moves to its new address at the end of the apply. Run bb-eco discover to find it there.
security: credentials and access control
Excluded from export by default. Pass --include-security to opt in for usernames, MAC filters, and authentication. Passwords are never written by export; a template only carries one if you type it in (rarely a good idea; see the warning below).
"security": {
"adminUsername": "admin",
"adminPassword": null,
"userUsername": null,
"userPassword": null,
"adminMacFilterEnabled": false,
"adminMacAddress": "000000000000",
"userMacFilterEnabled": null,
"userMacAddresses": ["000A4F112233"],
"authentication": "Basic",
"resetProtection": "Everybody"
}
| Field | Type | Notes |
|---|---|---|
adminUsername, userUsername | string | null | Account usernames, up to 20 characters. Authentication turns on the moment a username is non-empty |
adminPassword, userPassword | string | null | Up to 20 characters. Never commit these to Git |
adminMacFilterEnabled, userMacFilterEnabled | boolean | null | MAC-based access control toggles |
adminMacAddress | string | null | Admin MAC as exactly 12 hex digits, no separators. 000000000000 = unset. Must differ from every entry in the user MAC list |
userMacAddresses | string[] | null | Read-only via templates. The device manages this list one entry at a time (up to 32 entries), so maintain it through the device web UI |
authentication | string | null | "Basic" or "Digest" |
resetProtection | string | null | "Everybody" or "AdminOnly": controls who may factory-reset or restart the device |
Apply uses read-modify-write: null fields are filled from the device's current values, so a partial security template can never accidentally clear a password.
A template can carry passwords if you add them by hand, but storing passwords in version control is rarely the right answer. Prefer leaving adminPassword and userPassword as null in committed templates and setting them through a separate secure-secrets workflow.
io: the Protocol and IO Lines pages (ED only)
io holds the Protocol page in io.protocol, then one group per line type: digitalOutputs, digitalInputs, quickStop, analogInputs, analogOutputs, rtdInputs and thermocoupleInputs. Each line-type group holds the settings that act on every line of that type, then its lines. A model has only the groups its page shows:
"io": {
"protocol": {
"current": "ASCII",
"ascii": { "address": 1, "tcpPort": 9500, "idleTimeout": 0, "checksum": false,
"serialGateway": { "baudRate": 9600, "commandTimeout": 200 } },
"modbus": { "respondToServerIds": "0 and 255", "acceptAllAddresses": false,
"tcpPort": 502, "idleTimeout": 0, "maxConnections": 8 }
},
"digitalOutputs": {
"polarity": "activeHigh",
"lines": [ { "line": "DOut 0", "index": 0, "powerOnValue": 0, "safeValue": 0, "quickStopValue": 0 } ]
},
"digitalInputs": {
"polarity": "activeHigh",
"counterUpdateDirection": "fallingEdge",
"counterMode": "16bit",
"lines": [ { "line": "DIn 0", "index": 8, "debounceMs": 0 } ]
},
"quickStop": { "line": "disabled", "edge": "fallingEdge" }
}
Applying protocol settings restarts the device, so plan for a brief loss of IO connectivity.
io.protocol: the Protocol page
| Field | Page label | Type | Notes |
|---|---|---|---|
current | Current Protocol | string | null | "ASCII" (factory) or "Modbus". Only the current protocol answers; each protocol keeps its own settings |
io.protocol.ascii, ASCII Protocol Settings:
| Field | Page label | Type | Notes |
|---|---|---|---|
address | Device Address | int | null | 1–255 (factory: 1) |
tcpPort | TCP Port | int | null | 1–65535 (factory: 9500) |
idleTimeout | Idle Timeout | int | null | Seconds, 0–65535 (0 = no timeout) |
dataFormat | Data Format | string | null | Analogue and temperature models; see Data formats |
checksum | Checksum | boolean | null | Factory: false |
serialGateway | Serial Gateway Settings | object | null | Models with an RS-485 serial gateway: ASCII commands for another device address are passed to the RS-485 port. baudRate: 1200, 2400, 4800, 9600 (factory), 19200, 38400, 57600 or 115200. commandTimeout: milliseconds, 0–50000 (factory: 200) |
tunneling | I/O Tunneling | object | null | See below |
io.protocol.modbus, Modbus TCP Settings:
| Field | Page label | Type | Notes |
|---|---|---|---|
respondToServerIds | Respond to Server IDs | string | null | "0 and 255" (factory) or "0-247 and 255" |
acceptAllAddresses | Accept All Register/ Coil/ Input Addresses | boolean | null | Factory: false |
tcpPort | TCP Port | int | null | 1–65535 (factory: 502) |
idleTimeout | Idle Timeout | int | null | Seconds, 0–65535 (0 = no timeout) |
maxConnections | Max. Connections | int | null | 1–8 (factory: 8) |
dataFormat | Data Format | string | null | Analogue and temperature models; see Data formats |
Data formats
Each protocol has its own Data Format on analogue and temperature models:
| Model | io.protocol.ascii.dataFormat | io.protocol.modbus.dataFormat |
|---|---|---|
| ED-549, ED-560 | engineeringUnit, percentOfFullScale, hexadecimal | engineeringUnit, hexadecimal |
| ED-582 | celsius, fahrenheit, kelvin, ohms | the same |
| ED-593 | celsius, fahrenheit, kelvin, millivolts | the same |
Temperature models measure in the data format of the current protocol. Analogue output values are written in the ASCII data format, so a template that sets them must carry io.protocol.ascii.dataFormat too. When such a template changes the ED-560's ASCII data format, BB-Eco writes the protocol settings first, waits for the device to restart, then writes the line settings, so every output value is read in the new format.
io.protocol.ascii.tunneling: I/O tunnelling
I/O tunnelling mirrors this device's digital inputs onto the outputs of a remote device over the network (ED-516, ED-527, ED-538, ED-588). It is turned on at one device only, the one that connects (the client); the remote device just needs the ASCII protocol and a matching device address (see the caution below).
"tunneling": {
"enabled": true,
"remoteIp": "192.168.1.100",
"remoteAsciiPort": 9500,
"remoteAsciiAddress": 1,
"safeModeOnDisconnect": true
}
| Field | Page label | Type | Notes |
|---|---|---|---|
enabled | Enable | boolean | null | This device connects to the remote device and keeps the tunnel open (factory: false) |
remoteIp | Remote IP Address | string | null | IPv4 address of the remote device. An enabled tunnel needs a real address; 0.0.0.0 is refused |
remoteAsciiPort | Remote ASCII Device TCP Port | int | null | The remote device's ASCII TCP port, 1–65535: its io.protocol.ascii.tcpPort (factory: 9500) |
remoteAsciiAddress | Remote ASCII Device Address | int | null | 1–255 (factory: 1). Must equal the remote device's own io.protocol.ascii.address; see the caution below |
safeModeOnDisconnect | Set to Safe Mode When Disconnected | boolean | null | Outputs go to their safe values when the tunnel drops (factory: true) |
{ "io": { "protocol": { "ascii": { "tunneling": { "enabled": true } } } } } on its own turns a tunnel back on with the remote device the device already has.
The two devices address each other by ASCII device address: this device's remoteAsciiAddress must equal the remote device's io.protocol.ascii.address, and the remote device's remoteAsciiAddress must equal this device's io.protocol.ascii.address. A remote device that has never been configured keeps the factory remoteAsciiAddress of 1, so leave this device's address at 1 or set the remote's to match. Both devices must use the ASCII protocol with the same checksum setting. When the addresses disagree, the tunnel connects but never completes; the device's Protocol page, and the Info tab in BB-Eco, show Negotiate IO tunneling settings....
An export carries remoteIp, so applying one template to several devices points all of them at the same remote device. Changing any tunnel setting restarts the device, like the other protocol settings.
How a line is named
Each entry in a group's lines is named two ways, and an export writes both:
lineis the line's label on the device's page:"DOut 0","RL 0"(relays),"DIn 0","AIn 0","AOut 0","RTD 0","TC 0".indexis its ASCII line number, the number ASCII commands use. Outputs and analogue lines count from 0. Digital inputs follow the outputs: on a model with outputsDIn 0is line 8 (ED-588, ED-038); on the input-only ED-516 it is line 0.
A template you write needs one of the two, and when it has both they must agree. Neither is a setting: they say which line the settings are for. A template can list only the lines it changes: apply reads the device's current line configuration and merges, because the device accepts line configuration only as a complete set. If the device reports an active ASCII/Modbus client connection, it refuses line changes until the client disconnects; apply reports the device's own message when that happens.
io.digitalOutputs and io.digitalInputs
| Field | Page label | Type | Notes |
|---|---|---|---|
digitalOutputs.polarity | Output Polarity | string | null | "activeHigh" (1 = ON, factory) or "activeLow" (0 = ON). Shown on the ASCII Protocol tab; applies in every protocol |
digitalInputs.polarity | Input Polarity | string | null | "activeHigh" (High = 1, factory) or "activeLow" (Low = 1) |
digitalInputs.counterUpdateDirection | Counter Update Direction | string | null | "fallingEdge" (factory) or "risingEdge" |
digitalInputs.counterMode | Counter Mode | string | null | "16bit" (factory) or "32bit" |
Each line of digitalOutputs.lines (relays included):
| Field | Page label | Type | Notes |
|---|---|---|---|
powerOnValue | Power-On Value | 0 | 1 | null | The state at power-up |
safeValue | Safe Value | 0 | 1 | null | The state when all network connections are lost |
quickStopValue | Quick Stop Value | 0 | 1 | null | The state when the quick stop fires. Models with a quick stop |
Each line of digitalInputs.lines:
| Field | Page label | Type | Notes |
|---|---|---|---|
debounceMs | Debounce Time(ms) | int | null | 0–9999 milliseconds, on models with debounce |
Digital lines keep their factory names, so they have no name.
io.quickStop
On models with both digital inputs and outputs, an input can drive every output to its Quick Stop Value. The page reads Please Select Quick Stop IO Line [DIn 0] when [Falling Edge]:
| Field | Page label | Type | Notes |
|---|---|---|---|
line | Quick Stop IO Line | string | null | "disabled" (factory) or an input's label, "DIn 0" |
edge | Quick Stop Edge | string | null | "fallingEdge" (factory) or "risingEdge" |
Analogue and temperature lines
analogInputs (ED-549), analogOutputs (ED-560) and rtdInputs (ED-582) have no settings of their own beyond their lines. thermocoupleInputs (ED-593) also has the page's thermocouple settings:
| Field | Page label | Type | Notes |
|---|---|---|---|
openWireDetection | Enable Open-Wire Detection | boolean | null | Factory: true |
coldJunctionCompensation | Enable Cold Junction Compensation | boolean | null | Factory: true |
manualOffset | T manual offset | number | null | Added to every reading, in manualOffsetUnit: −99.99 to +99.99 in celsius or kelvin, −179.96 to +179.96 in fahrenheit (factory: 0) |
manualOffsetUnit | string | null | "celsius", "fahrenheit" or "kelvin": the unit manualOffset is written in |
Each analogue or temperature line:
| Field | Page label | Lines | Notes |
|---|---|---|---|
name | Name | all | At most 10 characters |
enabled | Enabled | inputs | true or false. Analogue outputs have no Enabled column |
fullScaleRange | Full Scale Range | analogue | Inputs: +/-10V, +/-5V, +/-2.5V, +/-1V, +/-500mV, +/-250mV, +/-150mV, +/-75mV, +/-20mA, +0 to 20mA, +4 to 20mA. Outputs: 0 to 10V, 0 to 20mA, 4 to 20mA. The page's ± sign is accepted |
powerOnValue, safeValue | Power-On Value, Safe Value | analogue outputs | Written in the ASCII data format: a signed number within the range in engineeringUnit ("+05.000"), 0 to 100 in percentOfFullScale, four hex digits ("0800") in hexadecimal |
rtdType | RTD Type | RTD | Pt100-385, Pt100-3911, Pt100-3916, Pt100-3926, Pt1000-385, Pt1000-3911, Pt1000-3916, Pt1000-3926, Pt1000-375, Ni120-672 |
connection | Connection | RTD | "4-wire" (factory), "3-wire" or "2-wire" |
thermocoupleType | TC Type | thermocouple | J, K, T, E, R, S, B, N |
serialPorts: serial ports (ES, and ED models with a Serial Port page)
One entry per serial port, grouped the way the port's own page is: Serial Port 1, then Default Port Settings, Protocol Settings and Serial Tunneling. A port's export has only the settings its page shows:
"serialPorts": [
{
"port": 1,
"portType": "RS422/485",
"defaultSettings": {
"alwaysUseTheseSettings": false,
"baudRate": 19200,
"dataBits": 8,
"parity": "even",
"stopBits": "1",
"flowControl": "none",
"duplex": "halfDuplexAutogating"
},
"protocolSettings": {
"protocol": "rawTcp",
"localTcpPort": 9001,
"connectionTimeout": 0
},
"tunneling": {
"enabled": false,
"remoteIp": "192.168.0.0",
"remoteTcpPort": 9001
}
}
]
| Field | Page label | Type | Notes |
|---|---|---|---|
port | Serial Port 1 | int | The port's number, 1–8. Required: it says which port the settings are for, and is never written |
portType | Port Type | string | null | "RS232" or "RS422/485". On RS232/422/485 ports, whose page offers the choice |
defaultSettings, Default Port Settings:
| Field | Page label | Type | Notes |
|---|---|---|---|
alwaysUseTheseSettings | Always use these settings (Ignore settings from application) | boolean | null | Factory: false |
baudRate | Baud Rate | int | null | Bit/s, 60–1,000,000 (up to 3,000,000 on some ES-522 hardware). Any rate in range is accepted (factory: 115200) |
dataBits | Data Bits | int | null | 5–8 (factory: 8) |
parity | Parity | string | null | "none" (factory), "odd", "even", "mark", "space" |
stopBits | Stop Bits | string | null | "1" (factory), "2", "1.5" (1.5 on some hardware) |
flowControl | Flow Control | string | null | "none" (factory), "rtsCts", "dtrDsr", "xonXoff". RTS/CTS and DTR/DSR need those lines on the port; the device rejects a mode its port cannot do |
fifo | Enable FIFO | boolean | null | On ports whose page shows it. Changing it restarts the device |
duplex | Duplex Mode | string | null | "fullDuplex" (RS422 full-duplex mode), "halfDuplexAutogating" (RS485 half-duplex autogating mode), or "none" while a switchable port is in RS232 mode. Not on an RS232-only port |
protocolSettings, Protocol Settings:
| Field | Page label | Type | Notes |
|---|---|---|---|
protocol | Protocol Type | string | null | "telnet" (Telnet, factory) or "rawTcp" (Raw TCP) |
localTcpPort | Local TCP Port | int | null | 1–65535; a port already in use on the device is rejected (factory: 9001 for port 1, 9002 for port 2, …) |
connectionTimeout | Connection Timeout | int | null | Seconds, 0–65535 (0 = not used) |
tunneling, Serial Tunneling: a tunnel to another device's serial port across the network:
| Field | Page label | Type | Notes |
|---|---|---|---|
enabled | Enable | boolean | null | Factory: false |
remoteIp | Remote IP Address | string | null | The other device. Enabling the tunnel needs a real address, the template's or the port's own: the factory 192.168.0.0 (and 0.0.0.0) is refused |
remoteTcpPort | Remote TCP Port | int | null | 0–65535 (factory: the port's own Local TCP Port) |
A template may set only the settings it cares about. Apply reads the port's current configuration and merges before writing, because the device accepts port configuration only as a complete set.
A serial port with a connected TCP client rejects configuration changes: apply reports the error and the port keeps its old settings. Disconnect clients first.
Apply order
bb-eco template apply pushes the sections to the device in a fixed order so a connection-dropping change can't strand the rest of the apply:
device: name, location (no connection impact)security: credentials (no connection impact)io(ED): protocol changes restart the deviceserialPorts(ES, and ED models with a Serial Port page), one port at a time: FIFO changes restart the devicenetwork.webPort+network.upnp: may disconnect if the web port changesnetwork.dhcp,staticIp,subnet,gateway: may disconnect if the IP changes
If the device becomes unreachable after step 5 or 6, apply treats this as success: the new settings took effect, and the device is now at its new address. bb-eco discover finds it again.
Versioning
version: 6 is the current schema, and the only one BB-Eco reads. A template from an
earlier release (BB-Eco 1.0 exports version: 3) is refused with a request to re-export it
from the device; a template from a newer release is refused until you update BB-Eco.
template validate, template apply and template diff all refuse it, so a diff never
reports a device compliant on settings it could not read.
Version 6 grouped the settings the way each device's page groups them. A hand-written
template that still uses an earlier key is refused with the key's place now, even when it has
no version, rather than applied with that setting left out. For example io.asciiAddress is
now io.protocol.ascii.address, and serialPorts[0].baudRate is now
serialPorts[0].defaultSettings.baudRate.
Don't set the field manually; BB-Eco writes the right value on export, and a hand-written template can leave it out.
More resources
- ED reference template and ES reference template: every key with its page label, defaults and ranges
- Example templates: a real export for each model
- Configuration as code with BB-Eco: concept and rationale
- Detect configuration drift with BB-Eco: task walkthrough
bb-eco templateCLI reference: every command and option, with real output