@tikoci/centrs 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +98 -0
- package/GLOSSARY.txt +284 -0
- package/LICENSE +21 -0
- package/README.md +55 -11
- package/SECURITY.md +3 -3
- package/commands/AGENTS.md +7 -1
- package/commands/api/README.md +46 -27
- package/commands/api/examples.md +124 -6
- package/commands/btest/README.md +21 -32
- package/commands/check/README.md +376 -53
- package/commands/check/examples.md +390 -0
- package/commands/devices/README.md +177 -37
- package/commands/devices/examples.md +148 -0
- package/commands/discover/README.md +5 -11
- package/commands/execute/README.md +73 -26
- package/commands/execute/examples.md +131 -9
- package/commands/explain/README.md +1716 -0
- package/commands/explain/examples.md +606 -0
- package/commands/retrieve/README.md +55 -71
- package/commands/retrieve/examples.md +56 -9
- package/commands/settings/README.md +286 -22
- package/commands/settings/examples.md +408 -0
- package/commands/terminal/README.md +6 -0
- package/commands/transfer/README.md +57 -19
- package/commands/transfer/examples.md +95 -0
- package/docs/CLI.md +353 -0
- package/docs/CONSTITUTION.md +102 -26
- package/docs/MATRIX.md +106 -173
- package/docs/errors/README.md +5 -1
- package/docs/errors/cdb/empty-selection.md +19 -0
- package/docs/errors/input/incomplete-gps.md +9 -0
- package/docs/errors/input/invalid-altitude.md +9 -0
- package/docs/errors/input/invalid-bbox.md +9 -0
- package/docs/errors/input/invalid-coordinate.md +9 -0
- package/docs/errors/input/invalid-radius.md +9 -0
- package/docs/errors/internal/settings-failed.md +9 -0
- package/docs/errors/quickchr/machine-not-found.md +22 -0
- package/docs/errors/quickchr/machine-stopped.md +20 -0
- package/docs/errors/quickchr/package-unavailable.md +27 -0
- package/docs/errors/quickchr/unsupported-via.md +35 -0
- package/docs/errors/quickchr/unsupported.md +35 -0
- package/docs/errors/routeros/version-unsupported.md +26 -0
- package/docs/errors/settings/consequential-value.md +26 -0
- package/docs/errors/settings/invalid-boolean.md +21 -3
- package/docs/errors/settings/invalid-format.md +18 -3
- package/docs/errors/settings/invalid-integer.md +18 -3
- package/docs/errors/settings/invalid-timeout.md +20 -3
- package/docs/errors/settings/invalid-via.md +22 -3
- package/docs/errors/settings/reserved-key.md +29 -0
- package/docs/errors/settings/skip-env-file-active.md +25 -0
- package/docs/errors/settings/unknown-key.md +25 -0
- package/docs/errors/transport/provider-trust.md +29 -0
- package/docs/errors/usage/fanout-not-supported.md +16 -0
- package/docs/errors/usage/stdin-ignored.md +28 -0
- package/docs/errors/validation/unknown-attribute.md +37 -4
- package/package.json +49 -14
- package/src/api-fanout.ts +469 -0
- package/src/api.ts +171 -56
- package/src/cli/api.ts +156 -13
- package/src/cli/btest.ts +38 -4
- package/src/cli/common.ts +11 -0
- package/src/cli/devices.ts +235 -6
- package/src/cli/discover.ts +3 -1
- package/src/cli/execute.ts +129 -9
- package/src/cli/explain.ts +503 -0
- package/src/cli/mcp.ts +8 -1
- package/src/cli/retrieve.ts +167 -90
- package/src/cli/selection.ts +260 -0
- package/src/cli/settings.ts +286 -0
- package/src/cli/terminal.ts +22 -7
- package/src/cli/transfer.ts +192 -51
- package/src/cli.ts +22 -2
- package/src/core/envelope.ts +5 -2
- package/src/core/error-catalog.ts +95 -0
- package/src/core/fanout.ts +143 -2
- package/src/core/preflight.ts +306 -0
- package/src/core/routeros-string.ts +24 -0
- package/src/devices.ts +212 -20
- package/src/discover.ts +7 -2
- package/src/errors.ts +1 -0
- package/src/execute-fanout.ts +453 -0
- package/src/execute.ts +216 -68
- package/src/explain/arg-tokens.ts +114 -0
- package/src/explain/args.ts +1403 -0
- package/src/explain/blocks.ts +132 -0
- package/src/explain/brace-slots.ts +143 -0
- package/src/explain/brace-tokens.ts +38 -0
- package/src/explain/catalog.ts +1345 -0
- package/src/explain/coordinates.ts +329 -0
- package/src/explain/defects.ts +217 -0
- package/src/explain/is-known-menu.ts +17 -0
- package/src/explain/menus.ts +664 -0
- package/src/explain/operator-tokens.ts +310 -0
- package/src/explain/operators.ts +458 -0
- package/src/explain/path-tokens.ts +150 -0
- package/src/explain/pathresolve.ts +1324 -0
- package/src/explain/quoted-string.ts +241 -0
- package/src/explain/scope-brace.ts +308 -0
- package/src/explain/segment.ts +757 -0
- package/src/explain/string-tokens.ts +67 -0
- package/src/explain/symbol-values.ts +520 -0
- package/src/explain/symbols.ts +1597 -0
- package/src/explain/token-ranges.ts +72 -0
- package/src/explain/transport.ts +497 -0
- package/src/explain/value-tokens.ts +80 -0
- package/src/explain/values.ts +357 -0
- package/src/explain/verbs.ts +64 -0
- package/src/explain/verbsplit.ts +813 -0
- package/src/explain/write.ts +815 -0
- package/src/explain.ts +2152 -0
- package/src/index.ts +307 -1
- package/src/mcp/config.ts +12 -2
- package/src/mcp/tools.ts +137 -2
- package/src/protocols/adapter.ts +38 -18
- package/src/protocols/index.ts +11 -5
- package/src/protocols/mac-telnet-console.ts +61 -17
- package/src/resolver/cdb.ts +10 -2
- package/src/resolver/comment-kv.ts +58 -4
- package/src/resolver/config-file.ts +136 -0
- package/src/resolver/facts.ts +78 -0
- package/src/resolver/geo.ts +453 -0
- package/src/resolver/index.ts +65 -0
- package/src/resolver/mac.ts +12 -3
- package/src/resolver/quickchr-provider.ts +597 -0
- package/src/resolver/selection.ts +391 -0
- package/src/resolver/service-endpoint.ts +155 -0
- package/src/resolver/settings.ts +54 -5
- package/src/resolver/target.ts +21 -3
- package/src/retrieve-fanout.ts +239 -133
- package/src/retrieve.ts +120 -25
- package/src/settings.ts +1078 -0
- package/src/terminal.ts +11 -1
- package/src/transfer-fanout.ts +544 -0
- package/src/transfer.ts +133 -40
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# retrieve
|
|
2
2
|
|
|
3
|
+
Status: `CHR-passed` over `rest-api` and `native-api`, including multi-target
|
|
4
|
+
fan-out (see **Target selection**). `snmp` is `not-started`. Matches
|
|
5
|
+
`docs/MATRIX.md`.
|
|
6
|
+
|
|
3
7
|
Read RouterOS state. RouterOS menu reads model `<path>/<verb>` where the verb
|
|
4
8
|
is `print`-style (`print`, `get`, and async POST-shaped reads as they're
|
|
5
9
|
added). SNMP reads use `retrieve <router> snmp <oid|MIB name>` and resolve
|
|
@@ -39,33 +43,32 @@ centrs retrieve <router> snmp <oid|MIB name> [flags]
|
|
|
39
43
|
|
|
40
44
|
## Flags
|
|
41
45
|
|
|
42
|
-
|
|
46
|
+
Implemented flags are generated from the CLI metadata into
|
|
47
|
+
[`docs/CLI.md` → retrieve](../../docs/CLI.md#retrieve); this file does not
|
|
48
|
+
duplicate that table. Behavior notes the generated reference cannot carry:
|
|
49
|
+
|
|
50
|
+
- `--format json` (shortcut `--json`): set `CENTRS_FORMAT=json` to make the
|
|
51
|
+
structured envelope the default. `--format yaml` renders the same envelope.
|
|
52
|
+
- `--port`: native-api defaults to 8728; `--port 8729` selects TLS `api-ssl`.
|
|
53
|
+
- `--query`/`--filter` are RouterOS **row** filters and always return an
|
|
54
|
+
array (see Synopsis); `--where` filters *which devices* fan out, not rows.
|
|
55
|
+
- `--all-attributes` conflicts with `--attribute(s)` (see Validation).
|
|
56
|
+
- `--quickchr` resolves host/port/auth from the live `@tikoci/quickchr`
|
|
57
|
+
(0.4.5+, optional dependency) descriptor, bypassing CDB/env resolution for
|
|
58
|
+
those fields.
|
|
59
|
+
|
|
60
|
+
### Designed, not implemented
|
|
61
|
+
|
|
62
|
+
Spec-tier flags with no implementation yet — today they fail as unknown flags:
|
|
63
|
+
|
|
64
|
+
| Flag | Designed behavior |
|
|
43
65
|
| ----------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
44
|
-
| `--attribute <name>` | Project a single attribute. Repeatable, or comma-separated. |
|
|
45
|
-
| `--attributes <a,b,c>` | Alias for `--attribute` with comma-separated input. |
|
|
46
|
-
| `--all-attributes` | Equivalent to RouterOS `details=true`. Mutually exclusive with `--attribute(s)`. |
|
|
47
|
-
| `--list-attributes` (alias `--list`) | Return the available attribute names for the path. No `print`/`get` is run. |
|
|
48
66
|
| `--once` | Bounded single read of a monitor-style menu (RouterOS `once`): returns **one** envelope and never follows. Open-ended follow is `api … --stream`. See constitution: protocol selection. |
|
|
49
|
-
| `--
|
|
50
|
-
| `--filter <expr>` | RouterOS row filter; same `.query` mapping and not-implemented handling as `--query`. |
|
|
51
|
-
| `--where <attr>=<value>` | Device-class selector: fan out across CDB records whose stored fact/comment-kv matches (e.g. `--where board=RB5009`). Repeatable (AND). Filters *which devices*, not RouterOS rows. See constitution: target selection. |
|
|
52
|
-
| `--max-bytes <n>` | Byte budget for the rendered payload. If the response would exceed it, centrs truncates to fit, keeps `ok: true`, and adds a warning + `meta.truncated`. Not an error. (Renamed from `--max-results`; the legacy flag still parses until the code split lands.) |
|
|
67
|
+
| `--max-bytes <n>` | Byte budget for the rendered payload. If the response would exceed it, centrs truncates to fit, keeps `ok: true`, and adds a warning + `meta.truncated`. Not an error. (Will replace the implemented `--max-results`, which fails instead of truncating.) |
|
|
53
68
|
| `--max-rows <n>` | Maximum row count for list reads. Excess rows are clipped; `ok: true` with a warning + `meta.truncated`. |
|
|
54
|
-
| `--
|
|
55
|
-
| `--format json` (alias `--json`) | Structured JSON envelope (set `CENTRS_FORMAT=json` to make it the default). |
|
|
56
|
-
| `--format yaml` (alias `--yaml`) | YAML rendering of the same envelope. |
|
|
57
|
-
| `--resolve <none\|arp>` | Resolve a MAC-address target to an IP via the host ARP cache. Default `none`. |
|
|
58
|
-
| `--via <protocol>` | Pin the transport (`rest-api` default, or `native-api`). No silent downgrade. See constitution: protocol selection. |
|
|
59
|
-
| `--validate=false` | Escape hatch; default is `true`. See constitution: validation is the product. |
|
|
60
|
-
| `--timeout <ms>` | Request timeout. REST: ≤ 60000. native-api allows longer. |
|
|
61
|
-
| `--username` (alias `--user`, `-u`) / `--password` | Override CDB-resolved or env credentials. |
|
|
62
|
-
| `--host <host\|url>` | Override the resolved host or base URL for the target. |
|
|
63
|
-
| `--port <n>` | Override the transport port. native-api defaults to 8728 (TLS api-ssl when `--port 8729`).|
|
|
64
|
-
| `--cdb-file` / `--cdb-password` | Override CDB file location / decrypt password. |
|
|
69
|
+
| `--yaml` | Shortcut for `--format yaml`. |
|
|
65
70
|
| `--ros-version <version>` | SNMP MIB lookup only: pin the MikroTik MIB version to cache/download. |
|
|
66
71
|
| `--ros-channel <channel>` | SNMP MIB lookup only: `stable`, `long-term`, `testing`, or `development`; default `stable`. |
|
|
67
|
-
| `--group <name>` | Fan out across every CDB target in WinBox group `<name>`. Repeatable, and combinable with `<router>` positionals plus `--all`/`--default`; the union is de-duped by CDB record index. See **Group fanout**. |
|
|
68
|
-
| `--concurrency <n>` | Max in-flight targets during fanout (integer ≥ 1). Defaults are transport-aware: `rest-api` 8, `native-api` 4. Rejected with `usage/invalid-concurrency` otherwise. |
|
|
69
72
|
|
|
70
73
|
## Validation
|
|
71
74
|
|
|
@@ -137,55 +140,36 @@ warning entry — it is not an error. (Earlier text said a byte overflow returns
|
|
|
137
140
|
*error* with the needed size; superseded — truncation is an `ok: true` footnote so
|
|
138
141
|
partial data stays usable.)
|
|
139
142
|
|
|
140
|
-
##
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
operation: {
|
|
171
|
-
kind: "fanout",
|
|
172
|
-
group: "prod-edge",
|
|
173
|
-
concurrency: 8,
|
|
174
|
-
summary: { total: 2, ok: 1, failed: 1 },
|
|
175
|
-
request: { path: "/system/resource", validate: true, format: "json", ... }
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
An empty or unknown group is `ok: true` with
|
|
182
|
-
`data.summary = { total: 0, ok: 0, failed: 0 }`, `data.targets = []`, and a
|
|
183
|
-
`cdb/empty-group` warning. Validation runs per target because RouterOS schemas
|
|
184
|
-
can differ by version. Fanout retries each target up to two times only for
|
|
185
|
-
`transport/network` and `transport/connection-closed`; REST 5xx failures are
|
|
186
|
-
mapped to `transport/connection-closed`. It does not retry `routeros/*`,
|
|
187
|
-
`validation/*`, `auth/*`, `cdb/*`, `target/*`, timeouts, DNS, TLS, or refused
|
|
188
|
-
connections.
|
|
143
|
+
## Target selection
|
|
144
|
+
|
|
145
|
+
retrieve fans out over the **shared target-selection grammar** — multiple
|
|
146
|
+
`<router>` positionals, repeatable `--group`/`--where`, the geo selectors
|
|
147
|
+
`--near`/`--bbox`, `--all`, and `--default`, combined as a set and de-duped by
|
|
148
|
+
CDB record index. The grammar, the locked `FanoutData`
|
|
149
|
+
envelope (`data = { summary, targets[] }`, outer `ok` = orchestration success,
|
|
150
|
+
per-target failure is an inner `ok:false`), the record-order reassembly, and the
|
|
151
|
+
granular **0/2/1 exit code** are all normative in
|
|
152
|
+
[`docs/CONSTITUTION.md` → Target selection grammar](../../docs/CONSTITUTION.md#target-selection-grammar).
|
|
153
|
+
The boundary is intent-keyed: a plain single-positional `retrieve <router> <path>`
|
|
154
|
+
stays the single-target envelope; a selector flag or a second positional target
|
|
155
|
+
switches to fan-out. `--quickchr <name>` (the named-live-provider) behaves like a
|
|
156
|
+
lone positional: one flag stays single-target, repeating it fans out, and mixing
|
|
157
|
+
it with positionals or CDB selectors is `usage/conflicting-flags` (constitution:
|
|
158
|
+
resolution providers).
|
|
159
|
+
|
|
160
|
+
retrieve-specific behavior on top of the shared engine:
|
|
161
|
+
|
|
162
|
+
- The CDB is loaded + decrypted **once** for the whole fan-out; each target is
|
|
163
|
+
then resolved and **validated independently**, because RouterOS schemas can
|
|
164
|
+
differ by version (`/console/inspect` runs per target).
|
|
165
|
+
- An empty or unknown selection is `ok: true` with
|
|
166
|
+
`data.summary = { total: 0, ok: 0, failed: 0 }`, `data.targets = []`, a
|
|
167
|
+
`cdb/empty-group` / `cdb/empty-selection` warning, and exit `0`.
|
|
168
|
+
- Each target retries up to two times **only** for `transport/network` and
|
|
169
|
+
`transport/connection-closed` (REST 5xx is mapped to
|
|
170
|
+
`transport/connection-closed`). It does **not** retry `routeros/*`,
|
|
171
|
+
`validation/*`, `auth/*`, `cdb/*`, `target/*`, timeouts, DNS, TLS, or refused
|
|
172
|
+
connections.
|
|
189
173
|
|
|
190
174
|
## Definition of done
|
|
191
175
|
|
|
@@ -162,15 +162,16 @@ Call succeeds with a `cdb/password-not-needed` warning in `meta.warnings`.
|
|
|
162
162
|
centrs retrieve $R /system/resource --cdb-file $CDB --cdb-password ignored --json
|
|
163
163
|
```
|
|
164
164
|
|
|
165
|
-
##
|
|
165
|
+
## Target selection (fan-out)
|
|
166
166
|
|
|
167
|
-
These
|
|
168
|
-
|
|
167
|
+
These exercise the shared target-selection grammar
|
|
168
|
+
([`docs/CONSTITUTION.md` → Target selection grammar](../../docs/CONSTITUTION.md#target-selection-grammar)).
|
|
169
|
+
`test/integration/fanout-retrieve.test.ts` runs them when CHR integration is
|
|
170
|
+
enabled. `$CDB` contains two records in group `fanout-chr`: record 0 is the live
|
|
171
|
+
CHR (comment fact `role=edge`) and record 1 is an unreachable REST URL (comment
|
|
172
|
+
fact `role=core`).
|
|
169
173
|
|
|
170
|
-
### F1. Group
|
|
171
|
-
|
|
172
|
-
`$CDB` contains two records in group `fanout-chr`: record 0 is the live CHR and
|
|
173
|
-
record 1 is an unreachable REST URL.
|
|
174
|
+
### F1. Group fan-out with one inner success and one inner failure
|
|
174
175
|
|
|
175
176
|
```bash
|
|
176
177
|
centrs retrieve --group fanout-chr /system/resource --cdb-file $CDB --json
|
|
@@ -179,7 +180,7 @@ centrs retrieve --group fanout-chr /system/resource --cdb-file $CDB --json
|
|
|
179
180
|
Outer envelope: `ok: true`, `data.summary = { total: 2, ok: 1, failed: 1 }`,
|
|
180
181
|
`data.targets` is ordered by CDB `recordIndex`, and `meta.operation.kind` is
|
|
181
182
|
`fanout`. The unreachable target is an inner `ok: false` envelope with
|
|
182
|
-
`transport/connection-refused`.
|
|
183
|
+
`transport/connection-refused`. Process exit `2` (partial).
|
|
183
184
|
|
|
184
185
|
### F2. Empty / unknown group
|
|
185
186
|
|
|
@@ -188,7 +189,37 @@ centrs retrieve --group no-such-group /system/resource --cdb-file $CDB --json
|
|
|
188
189
|
```
|
|
189
190
|
|
|
190
191
|
Envelope: `ok: true`, `data.summary = { total: 0, ok: 0, failed: 0 }`,
|
|
191
|
-
`data.targets = []`, and warnings include `cdb/empty-group`.
|
|
192
|
+
`data.targets = []`, and warnings include `cdb/empty-group`. Exit `0`.
|
|
193
|
+
|
|
194
|
+
### F3. `--where` device-class selector (subset)
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
centrs retrieve --where role=edge /system/resource --cdb-file $CDB --json
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
`--where` matches the raw comment fact, selecting only record 0:
|
|
201
|
+
`data.summary = { total: 1, ok: 1, failed: 0 }`,
|
|
202
|
+
`meta.operation.selection.where = ["role=edge"]`, exit `0`.
|
|
203
|
+
|
|
204
|
+
### F4. `--all` (every CDB record)
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
centrs retrieve --all /system/resource --cdb-file $CDB --json
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`--all` fans across every record (excluding `__default__`) — here both, so
|
|
211
|
+
`data.summary = { total: 2, ok: 1, failed: 1 }`,
|
|
212
|
+
`meta.operation.selection.all = true`, exit `2`.
|
|
213
|
+
|
|
214
|
+
### F5. Multiple positional targets (ad-hoc literals)
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
centrs retrieve $REACHABLE_URL $UNREACHABLE_URL /system/resource --username $U --password $P --json
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
More than one positional target is fan-out mode without any selector flag. With
|
|
221
|
+
no `--cdb-file`, both are ad-hoc literals, labeled by `meta.target.input` with no
|
|
222
|
+
`recordIndex`: `data.summary = { total: 2, ok: 1, failed: 1 }`, exit `2`.
|
|
192
223
|
|
|
193
224
|
## Format
|
|
194
225
|
|
|
@@ -304,3 +335,19 @@ centrs retrieve $A /system/resource --attribute bogus-attr --via native-api --po
|
|
|
304
335
|
```bash
|
|
305
336
|
centrs retrieve $A /system/resource --via native-api --port $API_PORT --username wrong --password wrong
|
|
306
337
|
```
|
|
338
|
+
|
|
339
|
+
## quickchr targets (#134)
|
|
340
|
+
|
|
341
|
+
`$NAME` is the machine name of a running quickchr-managed CHR
|
|
342
|
+
(`quickchr list`). No `$R`/`$U`/`$P` — host/port/auth come from the live
|
|
343
|
+
descriptor. Covered by `test/integration/quickchr-target.test.ts`.
|
|
344
|
+
|
|
345
|
+
### Q1. `--quickchr <name>` resolves the VM's REST endpoint
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
centrs retrieve --quickchr $NAME /system/resource --json
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Envelope: `ok: true`, `meta.via=rest-api` (normal default; quickchr does not
|
|
352
|
+
change protocol selection), `meta.target.source.kind=provider`,
|
|
353
|
+
`meta.target.identity=$NAME`, no `recordIndex` (the CDB was never consulted).
|
|
@@ -3,49 +3,198 @@
|
|
|
3
3
|
Read and write centrs's own settings — the global `centrs.env` preferences and
|
|
4
4
|
the `__default__` fallback record — and run first-time setup.
|
|
5
5
|
|
|
6
|
-
Status: `
|
|
6
|
+
Status: `CHR-passed` for the surface described by `examples.md` (fixture-backed
|
|
7
|
+
integration tests — `settings` performs no network IO, so no CHR is needed).
|
|
7
8
|
See `docs/MATRIX.md` for the cell state. `settings` is the front-end to the
|
|
8
9
|
settings the constitution defines (Settings precedence: the `centrs.env` config
|
|
9
10
|
file at tier 2; `__default__` fallback creds): it does not invent new settings,
|
|
10
|
-
only edits the ones already specified.
|
|
11
|
+
only edits the ones already specified. The interactive first-time-setup flow
|
|
12
|
+
(bare `centrs settings` under a TTY) remains a deferred later slice — see
|
|
13
|
+
"Surface" below.
|
|
11
14
|
|
|
12
15
|
## Why
|
|
13
16
|
|
|
14
17
|
centrs deliberately has no store of its own for *inventory* — that is the CDB.
|
|
15
|
-
But it does have **global, centrs-only preferences** (default
|
|
16
|
-
default
|
|
18
|
+
But it does have **global, centrs-only preferences** (default output format,
|
|
19
|
+
default result limit, MCP toggles, …) that have no per-device home, and a
|
|
17
20
|
**`__default__` fallback record** for credentials. Today both are hand-edited
|
|
18
21
|
(`centrs.env`, or `devices add __default__`). `settings` makes that ergonomic and
|
|
19
22
|
is where onboarding flows steer the user.
|
|
20
23
|
|
|
24
|
+
(Considered and declined: folding `centrs.env` into the future TikTOML device
|
|
25
|
+
format, #137. TikTOML's locked scope is inventory — devices/groups/links,
|
|
26
|
+
credential-by-reference — and stays that way; see the research posted on #137
|
|
27
|
+
for the reasoning. `centrs.env` stays a standalone file.)
|
|
28
|
+
|
|
29
|
+
## Scope boundary: which `CENTRS_*` keys `settings` manages
|
|
30
|
+
|
|
31
|
+
Not every `CENTRS_*` env var belongs to `settings`. Two groups, and the split
|
|
32
|
+
matters for what `get`/`set`/`print`/`reset` accept:
|
|
33
|
+
|
|
34
|
+
1. **Managed — full `settings` support.** Every `CENTRS_*` var that a real
|
|
35
|
+
command currently reads, validated with the same rules the consuming code
|
|
36
|
+
already applies (the shared `parseBoolean`/`parseDuration`, a command's own
|
|
37
|
+
enum, `resolveOptionalIntegerSetting`, …), gets full `get`/`set`/`print`/
|
|
38
|
+
`reset`. This includes both tool-behavior keys (`format`, `max-results`,
|
|
39
|
+
`cdb-file`, `resolve`, `mcp-allow-adhoc`) and keys that also have a
|
|
40
|
+
per-device CDB comment-kv counterpart (`via`, `validate`, `timeout`,
|
|
41
|
+
`port`, `ssh-key`, `insecure`) — an earlier draft of this README tried to
|
|
42
|
+
split those into a second, "unmanaged" tier on the theory that
|
|
43
|
+
`settings` "has no visibility into device context." That theory didn't
|
|
44
|
+
hold up: writing `CENTRS_VIA=ssh` to `centrs.env` is mechanically
|
|
45
|
+
identical to writing `CENTRS_FORMAT=json` — a context-free line write —
|
|
46
|
+
and the existing precedence ladder already places the file **below**
|
|
47
|
+
comment-kv (constitution: tier 2 < tier 3), so a device's own `via=`
|
|
48
|
+
override always wins regardless of how the global default was set.
|
|
49
|
+
There's no real distinction left to justify treating these keys worse.
|
|
50
|
+
See the table below for validation per key, including which ones also
|
|
51
|
+
have a comment-kv form (informational — it doesn't change how `settings`
|
|
52
|
+
treats them).
|
|
53
|
+
2. **Refused — must not be written by `settings`.** Credential-shaped or
|
|
54
|
+
self-referential keys. `settings set`/`settings reset` on any of these is
|
|
55
|
+
a hard `settings/reserved-key` error with a remediation pointer:
|
|
56
|
+
|
|
57
|
+
| Key | Why refused | Use instead |
|
|
58
|
+
| --- | --- | --- |
|
|
59
|
+
| `CENTRS_PASSWORD`, `CENTRS_USERNAME` | Default credentials belong to the `__default__` CDB record, which routes through the `devices` write layer and is redacted from structured output (constitution: identity and CDB). A parallel copy in a plaintext file is a second source of truth for the same secret, one that is never redacted. | `centrs devices add __default__ --user … --password …` |
|
|
60
|
+
| `CENTRS_CDB_PASSWORD` | This is the CDB's own decryption password. Storing it beside an *encrypted* CDB in a plaintext sibling file defeats the encryption. Security-sensitive surface per `SECURITY.md`. | Pass `--cdb-password` per invocation, export the env var in the calling shell, or use `Bun.secret()` once available — never persist to `centrs.env`. |
|
|
61
|
+
| `CENTRS_SKIP_ENV_FILE` | Self-referential: it means "don't load `centrs.env`." Writing it *into* `centrs.env` is a no-op by construction (a file that says "ignore me" is never read to find out). | Export it in the shell or pass `--skip-env-file` per call. |
|
|
62
|
+
| `CENTRS_RUN_FAST_INTEGRATION` | A CI/test signal, not a user preference. | N/A — set by `bun run test:integration`, never by a human. |
|
|
63
|
+
|
|
64
|
+
`settings get`/`settings print` on a refused key still works as read-only
|
|
65
|
+
inspection, **except the value itself**: `CENTRS_PASSWORD` and
|
|
66
|
+
`CENTRS_CDB_PASSWORD` are always shown as `"(redacted)"` (with an `isSet`
|
|
67
|
+
boolean instead of the value) regardless of whether the real secret came
|
|
68
|
+
from the process environment or — despite `set` refusing to write it —
|
|
69
|
+
was hand-added to the file directly. `settings` cannot stop a human from
|
|
70
|
+
editing `centrs.env` with a text editor, so the redaction has to be a
|
|
71
|
+
read-time defense, not just a write-time refusal. `CENTRS_USERNAME` is
|
|
72
|
+
refused-on-write (it belongs on the `__default__` record, not for secrecy
|
|
73
|
+
reasons) but is not secret and is shown in full on read.
|
|
74
|
+
|
|
75
|
+
## Known settings keys
|
|
76
|
+
|
|
77
|
+
| Key | Type | Built-in default | Comment-kv counterpart? | Notes |
|
|
78
|
+
| --- | --- | --- | --- | --- |
|
|
79
|
+
| `CENTRS_FORMAT` | enum: `text`\|`json`\|`yaml` | Varies **per command** (`api` defaults `json`; `retrieve`/`execute`/`transfer` default `text`) | No | See "Precedence and what `print`/`get` can actually report" below — `print` cannot show one universal current format. |
|
|
80
|
+
| `CENTRS_CDB_FILE` | path | `~/.config/tikoci/winbox.cdb` (`defaultCdbPath`) | No | Existence is not checked at `settings set` time — pre-configuring a path before the file exists is valid. |
|
|
81
|
+
| `CENTRS_MAX_RESULTS` | positive integer | command-specific (`retrieve`, `execute`) | No | `settings/invalid-integer` on a non-positive or non-integer value (`resolveOptionalIntegerSetting` rules). |
|
|
82
|
+
| `CENTRS_RESOLVE` | enum: `none`\|`arp` | `none` | No | Governs MAC→IP fallback resolution (`src/resolver/mac.ts`, `parseResolvePolicy`). |
|
|
83
|
+
| `CENTRS_MCP_ALLOW_ADHOC` | boolean | `false` | No | Currently gates a **not-yet-built** MCP feature (`src/mcp/config.ts` docstring: "reserved for future work, not yet honored"). Safe to expose in `settings` ahead of the feature — `print` should annotate it `(reserved, not yet active)`. **Its reader checks only the literal string `"1"`** (`test/unit/mcp-config.test.ts`: `"true"` → false), not the shared boolean parser — see the canonicalization rule below. |
|
|
84
|
+
| `CENTRS_VIA` | enum: `rest-api`\|`native-api`\|`ssh`\|`snmp`\|`mndp`\|`mac-telnet`\|`romon`\|`winbox-terminal` | `rest-api` (per `retrieve`; other commands vary the same way `format` does) | Yes — `via=` | `settings/invalid-via` on an unrecognized value. |
|
|
85
|
+
| `CENTRS_VALIDATE` | boolean | `true` | Yes — `validate=` | `settings/invalid-boolean`. |
|
|
86
|
+
| `CENTRS_TIMEOUT` | duration (`500ms`, `5s`, `1m`, or bare ms) | command-specific | Yes — `timeout=` | `settings/invalid-timeout` (`parseDuration` rules). |
|
|
87
|
+
| `CENTRS_PORT` | positive integer | protocol-default (no built-in default; unset falls through to the transport's own port) | Yes — `port=` | `settings/invalid-integer`. |
|
|
88
|
+
| `CENTRS_SSH_KEY` | path | unset | Yes — `ssh-key=` | Path only, never key material — matches the per-device form's own rule. Non-blank check only; existence isn't checked at set time. |
|
|
89
|
+
| `CENTRS_INSECURE` | boolean | `false` | Yes — `insecure=` | Accept RouterOS's self-signed TLS cert. `settings set` should still surface a one-line reminder of what it does (`meta.warnings`), since it's a security-relevant toggle even though it's fully validated like any other boolean key. |
|
|
90
|
+
| `CENTRS_TRANSFER_VIA` | enum: `auto`\|`rest`/`rest-api`\|`native`/`native-api`\|`sftp`\|`scp`\|`fetch`\|`ftp` | `auto` | **No** — `transfer` reads only the env var (`src/transfer.ts`, `selectTransferMethod`); there is no `transfer-via=` comment-kv key today. | `scp`/`fetch` currently error `not-implemented`; `ftp` is blocked unless `ALLOW_UNSAFE_PROTOCOLS` opts in — `settings set transfer-via ftp` should surface that same warning rather than silently accepting a value that will fail at call time. |
|
|
91
|
+
| `CENTRS_HOST` | string | unset (falls back to the target positional) | No (comment-kv has `target`/lookup keys, not a `host` override) | An unusual thing to default globally — most calls pass an explicit target — but it's a real, coded env var, so `settings` exposes it rather than editorializing. Non-blank check only. |
|
|
92
|
+
|
|
93
|
+
**Boolean write canonicalization:** `settings set <bool-key> <value>` accepts
|
|
94
|
+
any spelling the shared `parseBoolean` recognizes (`true`/`false`, `yes`/`no`,
|
|
95
|
+
`on`/`off`, `1`/`0`) as *input*, but always **writes the canonical `1` or
|
|
96
|
+
`0`** to the file, never `true`/`false`. This is driven by
|
|
97
|
+
`CENTRS_MCP_ALLOW_ADHOC`'s strict `=== "1"` reader (a locked, tested
|
|
98
|
+
behavior — changing it is out of scope here) — writing `1`/`0` uniformly
|
|
99
|
+
satisfies that reader *and* is valid input to every `parseBoolean`-based
|
|
100
|
+
reader, so one write rule works for both without touching `mcp/config.ts` or
|
|
101
|
+
its test.
|
|
102
|
+
|
|
103
|
+
*Devices/README.md note:* its comment-kv override-key table does not
|
|
104
|
+
currently list `insecure`, even though `commentKvAllowlist`
|
|
105
|
+
(`src/resolver/comment-kv.ts`) does. That's a pre-existing doc/code drift in
|
|
106
|
+
`devices`' own spec, unrelated to `settings` — flagging it here since this
|
|
107
|
+
research surfaced it, not fixing it in this file.
|
|
108
|
+
|
|
109
|
+
### Not yet wired — exclude from v1
|
|
110
|
+
|
|
111
|
+
`CENTRS_CONCURRENCY`, `CENTRS_DISCOVER_TIMEOUT`, and `CENTRS_CHECK_IGNORE` are
|
|
112
|
+
named in this file's earlier prose and in the constitution's example list, but
|
|
113
|
+
**no command in `src/` currently reads them**:
|
|
114
|
+
|
|
115
|
+
- Fan-out concurrency is `--concurrency` plus a hardcoded transport-aware
|
|
116
|
+
default (REST 8, native-api 4) — there is no env var read anywhere in
|
|
117
|
+
`src/*-fanout.ts`.
|
|
118
|
+
- `discover` reads only `--timeout` / `DISCOVER_DEFAULT_TIMEOUT_MS` — no
|
|
119
|
+
`CENTRS_DISCOVER_TIMEOUT` read exists.
|
|
120
|
+
- `CENTRS_CHECK_IGNORE` belongs to `check`, which is itself only `designed`
|
|
121
|
+
(not coded).
|
|
122
|
+
|
|
123
|
+
Shipping `settings set concurrency 8` today would silently do nothing —
|
|
124
|
+
succeed, persist a line, and never affect a single fan-out call. That's a
|
|
125
|
+
worse UX than not offering the key. **Recommendation: drop these three from
|
|
126
|
+
the v1 `settings` vocabulary entirely** (not even `get`-able) until the
|
|
127
|
+
consuming command actually reads the env var; add them back to the table
|
|
128
|
+
above in the same change that wires the read. This is a real prerequisite,
|
|
129
|
+
not a documentation nit — pursuing it here would either ship a lying command
|
|
130
|
+
or block #135 on unrelated fan-out/discover changes.
|
|
131
|
+
|
|
21
132
|
## Surface
|
|
22
133
|
|
|
23
134
|
```text
|
|
24
135
|
centrs settings # interactive setup (TTY)
|
|
25
|
-
centrs settings print [<attr>]
|
|
136
|
+
centrs settings print [<attr>] [--all] [--cdb-file <path>] [--cdb-password <pw>]
|
|
26
137
|
centrs settings get <attr> # read one value
|
|
27
138
|
centrs settings set <attr> <value> # write one value to centrs.env
|
|
28
139
|
centrs settings reset [<attr>] # clear one/all back to built-in default
|
|
29
140
|
```
|
|
30
141
|
|
|
31
|
-
- **Interactive (`centrs settings`, TTY)
|
|
32
|
-
`@tikoci/quickchr`) to optionally
|
|
33
|
-
to the `__default__` record so
|
|
34
|
-
|
|
35
|
-
|
|
142
|
+
- **Interactive (`centrs settings`, TTY) — deferred later slice, not built.**
|
|
143
|
+
Eventually: prompts (clack-style, like `@tikoci/quickchr`) to optionally
|
|
144
|
+
collect a default `user`/`password` (written to the `__default__` record so
|
|
145
|
+
a fresh CDB "starts useful"), confirm/update global preferences, and report
|
|
146
|
+
where the `centrs.env` lives. **Implementation prerequisite:** centrs has no
|
|
147
|
+
prompt-library dependency today (`package.json` lists only `zod` and the MCP
|
|
148
|
+
SDK) — this slice needs one added (`@clack/prompts`, matching the
|
|
149
|
+
`@tikoci/quickchr` reference). Until it ships, bare `centrs settings`
|
|
150
|
+
behaves identically to `settings print` **regardless of TTY** — matching
|
|
151
|
+
the issue's own phasing, which explicitly said not to block the
|
|
152
|
+
non-interactive surface (built below) on this later slice.
|
|
36
153
|
- **Non-interactive (`set`/`get`/`reset`/`print`):** scriptable edits to
|
|
37
|
-
`centrs.env`. `
|
|
38
|
-
|
|
154
|
+
`centrs.env`. Exit code `0` on success, `1` on a validation/refusal error —
|
|
155
|
+
matching the plain (non-fan-out) CLI error convention already used
|
|
156
|
+
elsewhere (`src/cli/api.ts`'s `exitCode`), never a bare non-zero without an
|
|
157
|
+
envelope. `settings` has no fan-out surface, so the constitution's `2`
|
|
158
|
+
("partial" fan-out result) never applies here.
|
|
159
|
+
- **Agent-facing:** every subcommand emits the standard envelope
|
|
160
|
+
(`ok`/`data`/`meta`/`error`) regardless of TTY — `--format json` is
|
|
161
|
+
honored on `settings` itself exactly like every other command, so an agent
|
|
162
|
+
never needs a TTY-detection special case to consume it.
|
|
163
|
+
- **`--cdb-file <path>` / `--cdb-password <pw>`** (on `print` only): scope
|
|
164
|
+
which CDB `print` probes for the `__default__` record summary (see
|
|
165
|
+
"`__default__` boundary" in the examples). These are **not** settings
|
|
166
|
+
precedence sources — they don't participate in the `default`/`config`/`env`
|
|
167
|
+
vocabulary below, because they're not resolving a `CENTRS_*` value at all;
|
|
168
|
+
they're a one-off read of a different file, reported alongside settings.
|
|
169
|
+
Absent, they fall back to the normal `CENTRS_CDB_FILE`/`CENTRS_CDB_PASSWORD`
|
|
170
|
+
resolution (built-in path / env — never `centrs.env`, since `CDB_PASSWORD`
|
|
171
|
+
is refused there).
|
|
39
172
|
- Credentials collected for `__default__` route through the `devices` write
|
|
40
173
|
layer (the only CDB writer) and are redacted from any structured output.
|
|
41
174
|
|
|
175
|
+
### `--all`
|
|
176
|
+
|
|
177
|
+
`print`'s default view lists only the keys `settings` recognizes (the table
|
|
178
|
+
above). `--all` additionally lists every other `CENTRS_*=` line literally
|
|
179
|
+
present in the file — a foreign var a user added for their own shell use, a
|
|
180
|
+
typo of a real key, or (redacted) a refused credential key someone hand-added
|
|
181
|
+
despite `set` refusing to write it — each tagged `(unrecognized)`. This keeps
|
|
182
|
+
the default output focused on what `settings` actually understands while
|
|
183
|
+
still making `print --all` the honest "what's actually in this file" dump for
|
|
184
|
+
debugging.
|
|
185
|
+
|
|
42
186
|
## Boundaries
|
|
43
187
|
|
|
44
188
|
- `settings` edits **global** preferences (`centrs.env`) and the **`__default__`**
|
|
45
189
|
record only. Per-device settings stay in `devices` (CDB comment-kv).
|
|
46
190
|
- It writes no RouterOS device; it is local-only, like `devices`.
|
|
47
191
|
- `--skip-env-file` / `CENTRS_SKIP_ENV_FILE=1` makes a run ignore `centrs.env`
|
|
48
|
-
|
|
192
|
+
**for every other command's precedence resolution**. It does not affect
|
|
193
|
+
`settings` itself: `settings` always reads and writes the real file
|
|
194
|
+
regardless of this flag, since inspecting/editing the file *is* its job. A
|
|
195
|
+
`settings print` run under `--skip-env-file` should still show the file's
|
|
196
|
+
contents, with a one-line note that the flag is active and other commands
|
|
197
|
+
in this same invocation environment would not see them.
|
|
49
198
|
|
|
50
199
|
## Onboarding
|
|
51
200
|
|
|
@@ -55,15 +204,130 @@ creds) and `centrs devices discover` (populate the registry). See
|
|
|
55
204
|
`commands/discover/README.md` (save-implied `devices discover`) and
|
|
56
205
|
`docs/CONSTITUTION.md` (Default-device record).
|
|
57
206
|
|
|
58
|
-
## Settings keys
|
|
207
|
+
## Settings keys: file format and write strategy
|
|
59
208
|
|
|
60
209
|
`centrs.env` holds **canonical `CENTRS_*=value` lines** — identical to the
|
|
61
210
|
environment-variable spellings, prefix included (`CENTRS_FORMAT=json`,
|
|
62
|
-
`
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
211
|
+
`CENTRS_MAX_RESULTS=500`). One vocabulary across file / env / CLI: the file
|
|
212
|
+
*is* "just default env vars," so `set -a && source centrs.env` works and the
|
|
213
|
+
loader applies each key as a default **only when the same `CENTRS_*` is not
|
|
214
|
+
already in the process environment** (it never injects into `process.env`).
|
|
215
|
+
`settings set <attr>` accepts the setting name with or without the prefix, and
|
|
216
|
+
in kebab-case or SCREAMING_SNAKE (`format`, `FORMAT`, `centrs_format`,
|
|
217
|
+
`CENTRS_FORMAT` all resolve to the same key), but always **writes** the
|
|
218
|
+
canonical `CENTRS_FORMAT=` line. It is **user-global only** — the single XDG
|
|
219
|
+
path above; there is no project-local config file discovered from the working
|
|
220
|
+
directory.
|
|
221
|
+
|
|
222
|
+
**The `config` precedence layer** — `src/resolver/config-file.ts` resolves
|
|
223
|
+
`${XDG_CONFIG_HOME:-~/.config}/tikoci/centrs.env` (injectable `env` map,
|
|
224
|
+
mirroring `defaultCdbPath`) and `loadEnvFileDefaults()` parses it into a flat
|
|
225
|
+
`Record<string, string>`. `resolveStringSetting`/`resolveBooleanSetting`/
|
|
226
|
+
`resolveOptionalIntegerSetting` (`src/resolver/settings.ts`) each take this as
|
|
227
|
+
a `config` argument, checked *between* `comment-kv` and the built-in default,
|
|
228
|
+
producing `{ value, source: { kind: "config", key: "CENTRS_FORMAT" } }` — a
|
|
229
|
+
genuine new layer, not a value folded into the existing `defaultValue`
|
|
230
|
+
parameter (which would have reported it with `source: "default"`, losing
|
|
231
|
+
provenance). Every command that resolves a managed key (`retrieve`, `execute`,
|
|
232
|
+
`api`, `transfer`, `terminal`, plus `resolveCdb`/`expandCdbSelection` for
|
|
233
|
+
`CENTRS_CDB_FILE` and `mcp/config.ts` for `CENTRS_CDB_FILE`/
|
|
234
|
+
`CENTRS_MCP_ALLOW_ADHOC`) threads this `config` argument through, so a value
|
|
235
|
+
set via `centrs settings set` actually takes effect everywhere, not just in
|
|
236
|
+
`settings print`'s own report. `CENTRS_USERNAME`/`CENTRS_PASSWORD`/
|
|
237
|
+
`CENTRS_CDB_PASSWORD` deliberately never read this tier, even if hand-added to
|
|
238
|
+
the file — a stronger defense than read-time redaction alone, since a
|
|
239
|
+
hand-edited credential line then cannot function as a live source. Fan-out
|
|
240
|
+
via-pinning guards (`retrieve`/`execute`/`api`) also deliberately exclude
|
|
241
|
+
`config` — a `centrs.env` default is the weakest precedence tier and must not
|
|
242
|
+
override a per-device comment-kv `via=` across a mixed fan-out.
|
|
243
|
+
|
|
244
|
+
Write strategy (new; the current file is silent on this):
|
|
245
|
+
|
|
246
|
+
- **Preserve unrecognized content.** A hand-edited `centrs.env` may carry
|
|
247
|
+
blank lines, `#` comments, or foreign (non-`CENTRS_*`) lines. `set`/`reset`
|
|
248
|
+
parse-modify-write, touching only the matched line; everything else
|
|
249
|
+
round-trips byte-for-byte, mirroring the CDB's "unknown fields preserved
|
|
250
|
+
verbatim" principle (`commands/devices/README.md`).
|
|
251
|
+
- **`reset <attr>` deletes the line, never blanks it.** A blank
|
|
252
|
+
`CENTRS_FORMAT=` line is *not* equivalent to "unset" for a file meant to be
|
|
253
|
+
bash-`source`-able — sourcing it would set the shell variable to an empty
|
|
254
|
+
string, which most shells and centrs's own `env[KEY] !== undefined` check
|
|
255
|
+
both treat as "set." Deleting the line entirely is the only way `reset`
|
|
256
|
+
actually restores the built-in default. (This is the one place `settings`
|
|
257
|
+
must **not** copy the CDB comment-kv convention, where an empty value
|
|
258
|
+
removes the token — that convention is safe there because centrs parses
|
|
259
|
+
comment-kv itself; it would be a correctness bug here.)
|
|
260
|
+
- **`reset` with no `<attr>` removes every managed-key line** this file
|
|
261
|
+
currently holds, leaving unrecognized lines and all comments/blank lines
|
|
262
|
+
untouched, and reports `data.cleared: [...]` listing what it removed. No
|
|
263
|
+
confirmation gate — the blast radius is one local file, not N routers, and
|
|
264
|
+
every value is recoverable by re-running `settings set`.
|
|
265
|
+
- **Atomic write:** write to a temp file in the same directory, `fsync`, then
|
|
266
|
+
`rename()` over the original — same pattern as the CDB write layer, scaled
|
|
267
|
+
down (a single `.bak` copy is enough; this file has no multi-KB record set
|
|
268
|
+
to protect, and a user can always re-type a couple of lines, but a crash
|
|
269
|
+
mid-write must never corrupt a file other shells `source`).
|
|
270
|
+
- **First write creates the directory.** `${XDG_CONFIG_HOME:-~/.config}/tikoci/`
|
|
271
|
+
may not exist yet on a fresh machine; `set` creates it (and the file) rather
|
|
272
|
+
than erroring.
|
|
273
|
+
|
|
274
|
+
## Precedence and what `print`/`get` can actually report
|
|
275
|
+
|
|
276
|
+
The constitution's five-tier precedence (`default < config < comment-kv < env
|
|
277
|
+
< cli`) is a **per-call** resolution — it needs a specific command invocation
|
|
278
|
+
(which CDB record matched, which CLI flags were passed) to produce a `cli` or
|
|
279
|
+
`cdb`/`comment-kv` source. A bare `centrs settings print` has no such call
|
|
280
|
+
context: there is no target, no command, no CLI flags being resolved. So for
|
|
281
|
+
each key, `print`/`get` can only ever report one of three sources —
|
|
282
|
+
`default` (built-in, nothing set), `config` (this file has a line for it —
|
|
283
|
+
the same `SettingSourceKind` every other command's `meta.settings` will use
|
|
284
|
+
once the loader above exists), or `env` (the real process environment
|
|
285
|
+
currently has the var set, which would shadow whatever the file says). It
|
|
286
|
+
should **not** claim `cli` or `comment-kv` sources — those require a call
|
|
287
|
+
`settings` never makes.
|
|
288
|
+
|
|
289
|
+
For `CENTRS_FORMAT` specifically: because each command defines its own
|
|
290
|
+
built-in default (`api`: `json`; others: `text`) independently
|
|
291
|
+
(`retrieveOutputFormats`/`apiOutputFormats`/`executeOutputFormats`/
|
|
292
|
+
`transferOutputFormats` are four separate `const` arrays that currently all
|
|
293
|
+
happen to agree on the same three values), `print` should show the
|
|
294
|
+
`config`/`env` value if set, and when unset show **all** per-command
|
|
295
|
+
defaults rather than a single misleading "default: text" — e.g. `default:
|
|
296
|
+
text (api: json)`.
|
|
297
|
+
|
|
298
|
+
## Testability
|
|
299
|
+
|
|
300
|
+
`settings` is fixture-testable the same way `devices` is: `defaultSettingsPath`
|
|
301
|
+
(`src/resolver/config-file.ts`) takes an injectable
|
|
302
|
+
`env: Record<string, string | undefined>` map and reads `XDG_CONFIG_HOME`
|
|
303
|
+
from it (falling back to `HOME`, then `os.homedir()`) — mirroring
|
|
304
|
+
`defaultCdbPath`'s pattern. `test/integration/settings.test.ts` sets
|
|
305
|
+
`XDG_CONFIG_HOME` to a per-test temp directory (exactly like `$CDB` in the
|
|
306
|
+
`devices` fixtures), so no test ever touches a real user's
|
|
307
|
+
`~/.config/tikoci/centrs.env`.
|
|
308
|
+
|
|
309
|
+
## Error and warning codes
|
|
310
|
+
|
|
311
|
+
Per the contributor contract in `docs/errors/README.md`, every code below has
|
|
312
|
+
a catalog entry (`src/core/error-catalog.ts`) and a `docs/errors/<code>.md`
|
|
313
|
+
page:
|
|
314
|
+
|
|
315
|
+
- `settings/reserved-key` — `set`/`reset` refused a credential or
|
|
316
|
+
self-referential key (see Scope boundary table).
|
|
317
|
+
- `settings/unknown-key` — `get`/`set` received a token that is not a
|
|
318
|
+
recognized `CENTRS_*`-shaped name at all — e.g. a typo. (Not needed for
|
|
319
|
+
reads via `print --all`, which tags-and-shows rather than rejecting.)
|
|
320
|
+
- `settings/consequential-value` — a non-fatal `set` warning (resolves the
|
|
321
|
+
former open question below) for exactly two keys: `insecure=true` and
|
|
322
|
+
`transfer-via=ftp`. `meta.warnings`, not an interactive confirm — a confirm
|
|
323
|
+
prompt in non-TTY mode isn't meaningful and would break scripting.
|
|
324
|
+
- `settings/skip-env-file-active` — a non-fatal `print` warning noting that
|
|
325
|
+
`--skip-env-file` is active for this invocation only; `settings` itself
|
|
326
|
+
always reads the real file regardless.
|
|
327
|
+
- `internal/settings-failed` — generic internal-error fallback, left as an
|
|
328
|
+
unenriched stub matching the sibling `internal/devices-failed`.
|
|
329
|
+
- `settings/invalid-boolean`, `settings/invalid-integer`,
|
|
330
|
+
`settings/invalid-timeout`, `settings/invalid-via`, `settings/invalid-format`,
|
|
331
|
+
`settings/unsafe-protocol-blocked` (referenced by the `consequential-value`
|
|
332
|
+
warning for `transfer-via=ftp`) already existed in the catalog (shared with
|
|
333
|
+
other commands' resolvers) and apply unchanged here.
|