@tikoci/centrs 0.1.2 → 0.1.3
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 +59 -0
- package/GLOSSARY.txt +71 -0
- package/README.md +24 -8
- package/SECURITY.md +3 -3
- package/commands/api/README.md +27 -9
- package/commands/api/examples.md +109 -6
- package/commands/check/README.md +371 -53
- package/commands/check/examples.md +390 -0
- package/commands/devices/README.md +177 -37
- package/commands/devices/examples.md +148 -0
- package/commands/execute/README.md +39 -4
- package/commands/execute/examples.md +90 -6
- package/commands/retrieve/README.md +36 -50
- package/commands/retrieve/examples.md +40 -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 +32 -2
- package/commands/transfer/examples.md +66 -0
- package/docs/CONSTITUTION.md +36 -11
- package/docs/MATRIX.md +68 -172
- package/docs/errors/README.md +2 -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/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/usage/fanout-not-supported.md +16 -0
- package/package.json +6 -6
- package/src/api-fanout.ts +454 -0
- package/src/api.ts +91 -19
- package/src/cli/api.ts +135 -11
- package/src/cli/btest.ts +20 -2
- 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 +107 -6
- package/src/cli/mcp.ts +8 -1
- package/src/cli/retrieve.ts +135 -90
- package/src/cli/selection.ts +200 -0
- package/src/cli/settings.ts +286 -0
- package/src/cli/terminal.ts +22 -7
- package/src/cli/transfer.ts +171 -47
- package/src/cli.ts +7 -0
- package/src/core/error-catalog.ts +55 -0
- package/src/core/fanout.ts +143 -2
- package/src/devices.ts +212 -20
- package/src/discover.ts +7 -2
- package/src/execute-fanout.ts +438 -0
- package/src/execute.ts +120 -23
- package/src/index.ts +105 -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/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 +46 -0
- package/src/resolver/mac.ts +12 -3
- package/src/resolver/selection.ts +304 -0
- package/src/resolver/settings.ts +54 -5
- package/src/resolver/target.ts +20 -2
- package/src/retrieve-fanout.ts +225 -133
- package/src/retrieve.ts +39 -7
- package/src/settings.ts +1078 -0
- package/src/terminal.ts +11 -1
- package/src/transfer-fanout.ts +527 -0
- package/src/transfer.ts +61 -11
|
@@ -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.
|