@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
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
# settings — examples
|
|
2
|
+
|
|
3
|
+
Each numbered example is an executable spec, in the same style as
|
|
4
|
+
`commands/devices/examples.md`. `settings` performs no network IO, so its
|
|
5
|
+
integration tests run against a fixture `centrs.env` rather than a live CHR.
|
|
6
|
+
|
|
7
|
+
`$SETTINGS_DIR` is a per-test temporary directory passed as `XDG_CONFIG_HOME`
|
|
8
|
+
for the invocation, so the resolved file is `$SETTINGS_DIR/tikoci/centrs.env`
|
|
9
|
+
— there is no checked-in fixture file; each test starts from either an absent
|
|
10
|
+
file or one written in-test. This mirrors `$CDB` in the `devices` fixtures.
|
|
11
|
+
Some examples also reference `$CDB` (a fixture WinBox CDB, built the same way
|
|
12
|
+
as `devices`' fixtures) to exercise the `__default__` boundary between the two
|
|
13
|
+
files.
|
|
14
|
+
|
|
15
|
+
Every example below is a named test in `test/integration/settings.test.ts`
|
|
16
|
+
("example N: ..."), green via `bun run test:integration:settings`.
|
|
17
|
+
|
|
18
|
+
## print
|
|
19
|
+
|
|
20
|
+
### 1. Print with no file present
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings print
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
No `centrs.env` exists yet. `data` lists every settings-managed key with
|
|
27
|
+
`source: { kind: "default", key: <attr> }`. For `format`, since the built-in
|
|
28
|
+
default varies per command, `data.format = { value: null, source: { kind:
|
|
29
|
+
"default", key: "format" }, perCommandDefault: { retrieve: "text", execute:
|
|
30
|
+
"text", transfer: "text", api: "json" } }` instead of a single scalar.
|
|
31
|
+
|
|
32
|
+
### 2. Print after a value is set
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings set format json
|
|
36
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings print
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`data.format = { value: "json", source: { kind: "config", key:
|
|
40
|
+
"CENTRS_FORMAT" } }`. No `perCommandDefault` block once an override exists —
|
|
41
|
+
the file's value applies uniformly.
|
|
42
|
+
|
|
43
|
+
### 3. Print --all surfaces unrecognized lines
|
|
44
|
+
|
|
45
|
+
The fixture file has a hand-added `# my note` comment and a
|
|
46
|
+
`CENTRS_QUUX=5s` line (not a real key) before any `set` call in this test.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings print --all
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`data` includes an `unrecognized` array: `[{ key: "CENTRS_QUUX", value:
|
|
53
|
+
"5s" }]`, tagged `(unrecognized)`. Bare `print` (no `--all`) from the same
|
|
54
|
+
fixture omits it entirely — it isn't a key `settings` knows, managed or
|
|
55
|
+
refused.
|
|
56
|
+
|
|
57
|
+
### 4. Print a single attribute
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings print max-results
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`data` contains only the `max-results` entry, not the full set.
|
|
64
|
+
|
|
65
|
+
### 5. Real env overrides the file
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR CENTRS_FORMAT=yaml centrs settings print format
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The file (from example 2) holds `CENTRS_FORMAT=json`, but the real process
|
|
72
|
+
environment wins per precedence: `data.format = { value: "yaml", source: {
|
|
73
|
+
kind: "env", key: "CENTRS_FORMAT" } }`.
|
|
74
|
+
|
|
75
|
+
### 6. Print under --skip-env-file still shows the file
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings print --skip-env-file
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`ok: true`. Values and sources are unchanged from example 2 — `settings`
|
|
82
|
+
itself always reads the real file. `meta.warnings` includes a note that
|
|
83
|
+
`--skip-env-file` is active and other commands run in this same environment
|
|
84
|
+
would not see these values.
|
|
85
|
+
|
|
86
|
+
## get
|
|
87
|
+
|
|
88
|
+
### 7. Get a key with no override and a per-command default
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings get format
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Fresh file. `data.value = null`, `data.source = { kind: "default", key:
|
|
95
|
+
"format" }`, `data.perCommandDefault` populated as in example 1 above. This
|
|
96
|
+
is `get`'s one deliberate exception to "returns one value" — there truly
|
|
97
|
+
isn't one until an override exists.
|
|
98
|
+
|
|
99
|
+
### 8. Get after set returns the resolved value
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings set max-results 250
|
|
103
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings get max-results
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`data = { value: 250, source: { kind: "config", key: "CENTRS_MAX_RESULTS" } }`.
|
|
107
|
+
|
|
108
|
+
### 9. Get an unknown key
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings get totally-bogus-key
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Errors `settings/unknown-key` — the token isn't a recognized `CENTRS_*`-shaped
|
|
115
|
+
name at all (not a typo close enough to suggest, not a plausible env name).
|
|
116
|
+
`details_url` populated.
|
|
117
|
+
|
|
118
|
+
### 10. Get a credential-shaped key redacts the value
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR CENTRS_CDB_PASSWORD=hunter2 centrs settings get cdb-password
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`ok: true` — read-only inspection is allowed even for refused-on-write keys
|
|
125
|
+
(see README Scope boundary). `data = { value: "(redacted)", isSet: true,
|
|
126
|
+
source: { kind: "env", key: "CENTRS_CDB_PASSWORD" } }`. The literal secret
|
|
127
|
+
`hunter2` must not appear anywhere in the envelope, including
|
|
128
|
+
`meta`/`error.context` on any code path — this example exists specifically to
|
|
129
|
+
pin that.
|
|
130
|
+
|
|
131
|
+
### 11. Get a key that also has a per-device comment-kv form
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings set via ssh
|
|
135
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings get via
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`ok: true`. `data = { value: "ssh", source: { kind: "config", key:
|
|
139
|
+
"CENTRS_VIA" } }` — fully validated and managed like any other key (see
|
|
140
|
+
README: there is no second-class "unmanaged" tier). `tips` (the envelope's
|
|
141
|
+
existing informational channel, not `warnings`) carries a
|
|
142
|
+
`tip/comment-kv-may-override` entry noting that a device's own `via=`
|
|
143
|
+
comment-kv override, if set, takes precedence over this global default —
|
|
144
|
+
since `settings` has no target/device context here, it cannot say whether
|
|
145
|
+
any specific device actually has one.
|
|
146
|
+
|
|
147
|
+
## set
|
|
148
|
+
|
|
149
|
+
### 12. Set a known key (happy path)
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings set format json
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`ok: true`. `data = { key: "format", previous: null, value: "json" }`. Reading
|
|
156
|
+
the file directly shows a single `CENTRS_FORMAT=json` line.
|
|
157
|
+
|
|
158
|
+
### 13. Prefix and case are normalized on input, canonical on write
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings set CENTRS_MAX_RESULTS 250
|
|
162
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings set max-results 250
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Both calls resolve to the same key and produce the identical
|
|
166
|
+
`CENTRS_MAX_RESULTS=250` line — the second call is a no-op mutation (file
|
|
167
|
+
unchanged, `data.previous == data.value == 250`, still `ok: true`).
|
|
168
|
+
|
|
169
|
+
### 14. Set an invalid value for a typed key
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings set format xml
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Errors `settings/invalid-format` (the shared `{text, json, yaml}` union — see
|
|
176
|
+
README). No file mutation; a failed `set` never partially writes.
|
|
177
|
+
|
|
178
|
+
### 15. First write creates the directory and file
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR/does-not-exist-yet centrs settings set format text
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
`ok: true`. `$SETTINGS_DIR/does-not-exist-yet/tikoci/` is created, along with
|
|
185
|
+
`centrs.env` inside it.
|
|
186
|
+
|
|
187
|
+
### 16. Set preserves unrelated file content
|
|
188
|
+
|
|
189
|
+
Fixture file (written in-test, not via `settings`) contains:
|
|
190
|
+
|
|
191
|
+
```env
|
|
192
|
+
# personal note, keep me
|
|
193
|
+
CENTRS_VIA=ssh
|
|
194
|
+
CENTRS_FORMAT=text
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings set format json
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Re-reading the file shows the comment and the `CENTRS_VIA=ssh` line
|
|
202
|
+
byte-for-byte unchanged; only the `CENTRS_FORMAT` line's value changed to
|
|
203
|
+
`json`. (`via` is a fully managed key too — this example is about `set` only
|
|
204
|
+
touching the one line it was asked to touch, not about `via` being
|
|
205
|
+
second-class.)
|
|
206
|
+
|
|
207
|
+
### 17. Set a refused credential key
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings set cdb-password hunter2
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Errors `settings/reserved-key`. `remediation` points at `--cdb-password` /
|
|
214
|
+
`CENTRS_CDB_PASSWORD` / `Bun.secret()`. No file mutation — re-reading the
|
|
215
|
+
file (or a fresh `print`) shows no `CENTRS_CDB_PASSWORD` line was written.
|
|
216
|
+
The literal value `hunter2` does not appear anywhere in the error envelope.
|
|
217
|
+
|
|
218
|
+
### 18. Set a comment-kv-shadowed key is a normal validated write
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings set validate false
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
`ok: true`. `data = { key: "validate", previous: null, value: false }`. No
|
|
225
|
+
warning — `validate` is validated with the same `parseBoolean` rule as any
|
|
226
|
+
other boolean key; the fact that a device's own `validate=` comment-kv can
|
|
227
|
+
override it per-device is normal precedence layering, not a reason to treat
|
|
228
|
+
this write differently from `set format json`.
|
|
229
|
+
|
|
230
|
+
### 19. Set an unrecognized key name
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings set totally-bogus-key value
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Errors `settings/unknown-key`, same as `get` (example 9). No file mutation.
|
|
237
|
+
|
|
238
|
+
### 20. Set a typed integer key out of range
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings set max-results 0
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Errors `settings/invalid-integer` (positive-integer rule, matching
|
|
245
|
+
`resolveOptionalIntegerSetting`'s existing validation used elsewhere). `-1`
|
|
246
|
+
and non-numeric input (`abc`) behave the same.
|
|
247
|
+
|
|
248
|
+
### 21. Set an invalid protocol identifier
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings set via carrier-pigeon
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Errors `settings/invalid-via` — same enum and code `retrieve.ts` already uses
|
|
255
|
+
for `--via`/`CENTRS_VIA` (`rest-api`, `native-api`, `ssh`, `snmp`, `mndp`,
|
|
256
|
+
`mac-telnet`, `romon`, `winbox-terminal`). No file mutation.
|
|
257
|
+
|
|
258
|
+
### 22. Set an invalid duration
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings set timeout not-a-duration
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Errors `settings/invalid-timeout` (`parseDuration` rules: bare milliseconds
|
|
265
|
+
or a `ms`/`s`/`m` suffix). No file mutation.
|
|
266
|
+
|
|
267
|
+
### 23. Boolean values are written in canonical 1/0 form
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings set mcp-allow-adhoc true
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
`ok: true`. Reading the file directly shows `CENTRS_MCP_ALLOW_ADHOC=1`, not
|
|
274
|
+
`=true` — `settings` accepted the `parseBoolean`-recognized spelling `true`
|
|
275
|
+
as input but always writes the canonical `1`/`0` (see README: this is
|
|
276
|
+
required because `src/mcp/config.ts` checks the literal string `"1"`, not the
|
|
277
|
+
shared boolean parser). `settings get mcp-allow-adhoc` afterward returns
|
|
278
|
+
`{ value: true, source: { kind: "config", ... } }` — `get` still returns a
|
|
279
|
+
real boolean, only the on-disk serialization is canonicalized.
|
|
280
|
+
|
|
281
|
+
### 24. A value that will fail later still gets a warning now
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings set transfer-via ftp
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
`ok: true` — `ftp` is a recognized `CENTRS_TRANSFER_VIA` value syntactically,
|
|
288
|
+
so `set` writes it. `meta.warnings` contains a note that `transfer` will
|
|
289
|
+
currently reject `ftp` at call time unless `ALLOW_UNSAFE_PROTOCOLS` includes
|
|
290
|
+
it (`settings/unsafe-protocol-blocked`, `src/transfer.ts`) — `settings` warns
|
|
291
|
+
rather than silently accepting a value it knows will not work, without
|
|
292
|
+
duplicating `transfer`'s own runtime gate.
|
|
293
|
+
|
|
294
|
+
### 25. Re-setting the same value is idempotent
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings set format json
|
|
298
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings set format json
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Both calls return `ok: true`; the second reports `data.previous ==
|
|
302
|
+
data.value`. The file has exactly one `CENTRS_FORMAT=` line afterward, not
|
|
303
|
+
two.
|
|
304
|
+
|
|
305
|
+
## reset
|
|
306
|
+
|
|
307
|
+
### 26. Reset removes the line, not just its value
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings set format json
|
|
311
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings reset format
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
`ok: true`. The `CENTRS_FORMAT=` line is **absent** from the file afterward —
|
|
315
|
+
not present with an empty value (see README: an empty value would be
|
|
316
|
+
"set to empty string" for a bash-sourced file, not "unset"). A follow-up
|
|
317
|
+
`settings get format` shows `source: { kind: "default" }` again, matching
|
|
318
|
+
example 7.
|
|
319
|
+
|
|
320
|
+
### 27. Reset on a key that was never set is a no-op success
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings reset max-results
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Fresh file, `CENTRS_MAX_RESULTS` never written. `ok: true`,
|
|
327
|
+
`data.wasSet: false` — resetting nothing is not an error.
|
|
328
|
+
|
|
329
|
+
### 28. Reset with no attribute clears every managed key, leaves the rest
|
|
330
|
+
|
|
331
|
+
Fixture file:
|
|
332
|
+
|
|
333
|
+
```env
|
|
334
|
+
# keep me
|
|
335
|
+
SOME_OTHER_VAR=untouched
|
|
336
|
+
CENTRS_FORMAT=json
|
|
337
|
+
CENTRS_MAX_RESULTS=250
|
|
338
|
+
CENTRS_VIA=ssh
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings reset
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
`ok: true`. `data.cleared = ["format", "max-results", "via"]` — all three
|
|
346
|
+
recognized keys, `via` included, since it's fully managed like the others.
|
|
347
|
+
Re-reading the file shows the comment and `SOME_OTHER_VAR=untouched`
|
|
348
|
+
unchanged; the three managed lines are gone. No confirmation was required
|
|
349
|
+
(see README: local-file blast radius, always recoverable via `set`).
|
|
350
|
+
|
|
351
|
+
### 29. Reset also refuses credential keys
|
|
352
|
+
|
|
353
|
+
```bash
|
|
354
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings reset cdb-password
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Errors `settings/reserved-key`, same family as `set` (example 17) — `reset`
|
|
358
|
+
is a mutating verb and refuses the same key list.
|
|
359
|
+
|
|
360
|
+
## Interactive / non-TTY
|
|
361
|
+
|
|
362
|
+
### 30. Bare `settings` under non-TTY behaves like `print`
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings </dev/null
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
`ok: true`, identical shape to `settings print` (example 1/2 depending on
|
|
369
|
+
fixture state). No prompt is emitted and the call does not hang.
|
|
370
|
+
|
|
371
|
+
The interactive TTY flow (clack-style prompts collecting `__default__`
|
|
372
|
+
credentials and confirming preferences) is explicitly deferred — see the
|
|
373
|
+
README's "later slice" note and its `@clack/prompts` dependency prerequisite.
|
|
374
|
+
It has no example here because a genuine TTY session isn't exercisable in the
|
|
375
|
+
fixture-backed integration tier (the same reason `devices` has no interactive
|
|
376
|
+
example either, only the `</dev/null` disambiguation case). When that slice
|
|
377
|
+
ships, its examples land as a new numbered range appended here, not inserted
|
|
378
|
+
into the middle of this list.
|
|
379
|
+
|
|
380
|
+
## `__default__` boundary
|
|
381
|
+
|
|
382
|
+
### 31. Print reports `__default__` presence without leaking secrets
|
|
383
|
+
|
|
384
|
+
Fixture CDB `$CDB` has a `__default__` record with `user=admin` and a
|
|
385
|
+
password set.
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings print --cdb-file $CDB
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
`ok: true`. `data.defaultDevice = { configured: true, user: "admin",
|
|
392
|
+
passwordSet: true }` — the password value itself never appears. This is the
|
|
393
|
+
one place `settings print` reaches into the CDB rather than `centrs.env`;
|
|
394
|
+
`--cdb-file` here is a probe-only flag (see README Surface), not a settings
|
|
395
|
+
precedence source. It's read-only (no CDB mutation), matching the README's
|
|
396
|
+
"the `__default__` record only" boundary.
|
|
397
|
+
|
|
398
|
+
### 32. Print with no `__default__` record
|
|
399
|
+
|
|
400
|
+
```bash
|
|
401
|
+
XDG_CONFIG_HOME=$SETTINGS_DIR centrs settings print --cdb-file /tmp/does-not-exist.cdb
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
`ok: true` (a missing/absent CDB is not an error for `settings`, unlike
|
|
405
|
+
`devices list` in devices' example 3 — `settings` degrades to "no default
|
|
406
|
+
device configured" rather than `cdb/not-found`, since reporting settings
|
|
407
|
+
should not require a CDB to exist at all). `data.defaultDevice = {
|
|
408
|
+
configured: false }`.
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Open an interactive RouterOS console.
|
|
4
4
|
|
|
5
|
+
`terminal` is **single-session**: it is not a fan-out surface. A target-selection
|
|
6
|
+
flag (`--group` / `--where` / `--all` / `--default` / `--concurrency`) is rejected
|
|
7
|
+
with `usage/fanout-not-supported` — open a console to one router at a time; use
|
|
8
|
+
`execute` / `api` for multi-target commands. See `docs/CONSTITUTION.md`
|
|
9
|
+
(Target selection grammar).
|
|
10
|
+
|
|
5
11
|
Status: `terminal / mac-telnet` **and** `terminal / ssh` are both **`CHR-passed`**
|
|
6
12
|
(see `docs/MATRIX.md` and `examples.md` T1–T3 / TS1–TS2). The two transports take
|
|
7
13
|
different paths: **mac-telnet** is the in-process raw passthrough over
|
|
@@ -17,6 +17,7 @@ sftp-seeded >60 KB file) — confirming the `/file`
|
|
|
17
17
|
RouterOS. Unit coverage is `test/unit/transfer.test.ts`. **sftp** is the first SSH
|
|
18
18
|
consumer — a self-contained SFTP transfer client over the host OpenSSH `sftp`
|
|
19
19
|
subsystem. `scp` and `fetch` are **designed but deferred** past this pass.
|
|
20
|
+
Multi-target fan-out is built (see **Target selection**, examples F1–F5).
|
|
20
21
|
|
|
21
22
|
## Intent
|
|
22
23
|
|
|
@@ -153,26 +154,63 @@ is an explicit `--via scp` escape hatch. The reason is capability, not taste:
|
|
|
153
154
|
|
|
154
155
|
## Flags
|
|
155
156
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
157
|
+
Implemented flags are generated from the CLI metadata into
|
|
158
|
+
[`docs/CLI.md` → transfer](../../docs/CLI.md#transfer); this file does not
|
|
159
|
+
duplicate that table. Behavior notes the generated reference cannot carry:
|
|
160
|
+
|
|
161
|
+
- `--force`/`--overwrite` applies to the destination side (the local file for
|
|
162
|
+
`download`, the remote file for `upload`); the default refuses an existing
|
|
163
|
+
target with `usage/target-exists` (see *Validation*).
|
|
164
|
+
- `--verify` details and the sftp size caveat are in *Integrity*.
|
|
165
|
+
- `--timeout`: `rest`/`native` are per-request ≤ 60000 ms (a chunked read is
|
|
166
|
+
many short requests, each capped); `sftp` accepts longer for a single large
|
|
167
|
+
transfer.
|
|
168
|
+
- `--ssh-key` is the same `sshKey` setting as `terminal`/`execute`
|
|
169
|
+
(`CENTRS_SSH_KEY`, CDB `ssh-key=`); when unset, the ssh-agent /
|
|
170
|
+
`~/.ssh/config` is used. See `commands/terminal/README.md`.
|
|
171
|
+
- `--insecure` adds a `transport/insecure-trust` warning (constitution:
|
|
172
|
+
transport trust).
|
|
173
|
+
- `--quickchr` resolves host/port/auth from the live `@tikoci/quickchr`
|
|
174
|
+
(0.4.5+, optional dependency) descriptor. `--via sftp` additionally
|
|
175
|
+
requires the descriptor's SSH endpoint to advertise a batch-capable auth
|
|
176
|
+
mode, else `quickchr/unsupported-via` (never a password prompt).
|
|
177
|
+
|
|
178
|
+
### Designed, not implemented
|
|
179
|
+
|
|
180
|
+
Spec-tier flags with no implementation yet — today they fail as unknown flags:
|
|
181
|
+
|
|
182
|
+
| Flag | Designed behavior |
|
|
183
|
+
| ---- | ----------------- |
|
|
164
184
|
| `--advertise-host <host>` / `--advertise-port <n>` / `--bind <addr>` | `fetch` only *(deferred)*: the host/IP/port centrs advertises in the fetch URL and the local bind address. Default: auto-detect the local IP on the route to the router; ephemeral port; single-use random URL token. |
|
|
165
|
-
| `--resolve <none\|arp>` |
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
185
|
+
| `--resolve <none\|arp>` | Resolve a MAC-address target to an IP via the host ARP cache. Default `none`. |
|
|
186
|
+
|
|
187
|
+
## Target selection
|
|
188
|
+
|
|
189
|
+
`transfer` runs the **same** verb across a multi-target selection over the shared
|
|
190
|
+
grammar (multiple `<router>` positionals, repeatable `--group`/`--where`, the geo
|
|
191
|
+
selectors `--near`/`--bbox`, `--all`, `--default`). The grammar, the locked
|
|
192
|
+
`FanoutData` envelope, the record-order
|
|
193
|
+
reassembly, and the granular **0/2/1 exit code** are normative in
|
|
194
|
+
[`docs/CONSTITUTION.md` → Target selection grammar](../../docs/CONSTITUTION.md#target-selection-grammar).
|
|
195
|
+
|
|
196
|
+
The positional **boundary is the verb keyword** (`upload`/`download`/`list`/
|
|
197
|
+
`remove`/`mkdir`/`copy` or an alias): positionals **before** it are fan-out
|
|
198
|
+
targets, the verb and its paths follow (`transfer r1 r2 download /flash/log.txt
|
|
199
|
+
--out-dir ./logs`). A single positional target with no selector stays
|
|
200
|
+
single-target. On the top-level `upload`/`download` alias the verb is fixed, so
|
|
201
|
+
its positionals are paths — positional fan-out targets are not expressible there;
|
|
202
|
+
fan-out comes from a selector flag.
|
|
203
|
+
|
|
204
|
+
Two transfer-specific rules:
|
|
205
|
+
|
|
206
|
+
- **`download` fan-out requires `--out-dir <dir>`.** N devices cannot share one
|
|
207
|
+
local path, so each target's file is written into the directory, named by its
|
|
208
|
+
CDB identity (collision-safe — same-label targets are disambiguated by record
|
|
209
|
+
index — and keeping the remote file's extension). Each inner envelope records
|
|
210
|
+
its per-target local path.
|
|
211
|
+
- **`upload`/`remove`/`mkdir`/`copy` are device writes**, so a fan-out of them is
|
|
212
|
+
gated by **`--yes`**, confirmed once up front; without it the error names the
|
|
213
|
+
blast radius (how many routers). `download`/`list` only read the device.
|
|
176
214
|
|
|
177
215
|
## Integrity
|
|
178
216
|
|
|
@@ -336,6 +336,72 @@ centrs transfer 127.0.0.1 --via sftp --port $SSH_PORT --username $U --ssh-key $K
|
|
|
336
336
|
Both `ok: true`. On-device `copy` has no SFTP primitive — `--via sftp copy` returns
|
|
337
337
|
`transport/unsupported-operation` (it stays on rest/native).
|
|
338
338
|
|
|
339
|
+
## Target selection (fan-out)
|
|
340
|
+
|
|
341
|
+
These exercise the shared target-selection grammar
|
|
342
|
+
([`docs/CONSTITUTION.md` → Target selection grammar](../../docs/CONSTITUTION.md#target-selection-grammar)),
|
|
343
|
+
run by `test/integration/transfer-fanout.test.ts`. `$CDB` has two records in group
|
|
344
|
+
`fanout-chr`: record 0 is the live CHR (comment fact `role=edge`, seeded with
|
|
345
|
+
`fanout-test.txt`) and record 1 is an unreachable REST URL (`role=core`). Note the
|
|
346
|
+
**verb-keyword boundary**: targets/selectors precede the verb.
|
|
347
|
+
|
|
348
|
+
### F1. Group fan-out of a read verb (`list`)
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
centrs transfer --group fanout-chr list --via rest --cdb-file $CDB --json
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
`ok: true`, `data.summary = { total: 2, ok: 1, failed: 1 }`, targets ordered by
|
|
355
|
+
`recordIndex`, `meta.operation.kind = fanout`; the unreachable target is an inner
|
|
356
|
+
`ok: false`. Exit `2`.
|
|
357
|
+
|
|
358
|
+
### F2. Empty / unknown group
|
|
359
|
+
|
|
360
|
+
```bash
|
|
361
|
+
centrs transfer --group no-such-group list --via rest --cdb-file $CDB --json
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
`ok: true`, `data.summary = { total: 0, ok: 0, failed: 0 }`,
|
|
365
|
+
`warnings` include `cdb/empty-group`. Exit `0`.
|
|
366
|
+
|
|
367
|
+
### F3. `--where` device-class selector (subset)
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
centrs transfer --where role=edge list --via rest --cdb-file $CDB --json
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Selects only record 0: `data.summary = { total: 1, ok: 1, failed: 0 }`,
|
|
374
|
+
`meta.operation.selection.where = ["role=edge"]`, exit `0`.
|
|
375
|
+
|
|
376
|
+
### F4. `download` fan-out into `--out-dir`
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
centrs transfer --group fanout-chr download fanout-test.txt --out-dir ./downloads --via rest --cdb-file $CDB --json
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
Each reachable target writes one file into `./downloads`, named by its CDB identity
|
|
383
|
+
(collision-safe, keeping the remote extension). `data.summary = { total: 2, ok: 1,
|
|
384
|
+
failed: 1 }`, exit `2`; the ok target's inner `data.local` is the written path.
|
|
385
|
+
|
|
386
|
+
### F5. Mutating fan-out (`remove`) without `--yes` is refused
|
|
387
|
+
|
|
388
|
+
```bash
|
|
389
|
+
centrs transfer --group fanout-chr remove fanout-test.txt --via rest --cdb-file $CDB --json
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
A mutating fan-out is gated once up front: `ok: false`,
|
|
393
|
+
`error.code = usage/confirmation-required`, the summary names the blast radius
|
|
394
|
+
(`2 router(s)`), exit `1`. Nothing is removed. Add `--yes` to fan the write out.
|
|
395
|
+
|
|
396
|
+
### F6. `download` fan-out without `--out-dir` is refused
|
|
397
|
+
|
|
398
|
+
```bash
|
|
399
|
+
centrs transfer --group fanout-chr download fanout-test.txt --via rest --cdb-file $CDB --json
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
N devices cannot share one local path: `ok: false`,
|
|
403
|
+
`error.code = usage/conflicting-flags`, exit `1`. Pass `--out-dir <dir>`.
|
|
404
|
+
|
|
339
405
|
## Pending transports (contract defined; flips when the transport lands)
|
|
340
406
|
|
|
341
407
|
### P2. `--via scp` (follow-on pass after sftp)
|
|
@@ -366,3 +432,32 @@ centrs transfer $R upload $SRC centrs-up.txt --via ftp --username $U --password
|
|
|
366
432
|
Envelope: `ok: false`, the error reports the protocol is gated (the cleartext-FTP
|
|
367
433
|
opt-in is absent). When `ALLOW_UNSAFE_PROTOCOLS=ftp` is set but the method is not
|
|
368
434
|
yet built, it is `usage/not-implemented` instead.
|
|
435
|
+
|
|
436
|
+
## quickchr targets (#134)
|
|
437
|
+
|
|
438
|
+
`$NAME` is the machine name of a running quickchr-managed CHR. Host/port/auth
|
|
439
|
+
come from the live descriptor. Covered by
|
|
440
|
+
`test/integration/quickchr-target.test.ts`.
|
|
441
|
+
|
|
442
|
+
### Q1. `list` over the descriptor's REST service
|
|
443
|
+
|
|
444
|
+
```bash
|
|
445
|
+
centrs transfer --quickchr $NAME list --json
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
Envelope: `ok: true`, `meta.target.source.kind=provider`.
|
|
449
|
+
|
|
450
|
+
### Q2. `--via sftp` gates on the SSH endpoint's batch auth
|
|
451
|
+
|
|
452
|
+
```bash
|
|
453
|
+
centrs transfer --quickchr $NAME list --via sftp --json
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
When the descriptor's SSH endpoint advertises a **usable** batch-capable auth
|
|
457
|
+
mode (`agent-or-config`, or `private-key` together with a verified
|
|
458
|
+
`privateKeyPath` — a bare `private-key` mode with no path does not count), the
|
|
459
|
+
list succeeds over sftp: `meta.via=sftp`, `meta.target.source.kind=provider`,
|
|
460
|
+
`meta.settings.insecure` provider-sourced, and the key path (when one is
|
|
461
|
+
handed over) carried as a provider-sourced `sshKey`. Otherwise the envelope is
|
|
462
|
+
`ok: false` with `error.code=quickchr/unsupported-via` and exit code 1 — a
|
|
463
|
+
typed gate, **never** a password prompt and never a silent fallback to REST.
|