@tikoci/centrs 0.1.3 → 0.1.6
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 +348 -0
- package/GLOSSARY.txt +252 -0
- package/LICENSE +21 -0
- package/README.md +44 -12
- package/commands/AGENTS.md +14 -5
- package/commands/api/AGENTS.md +13 -1
- package/commands/api/README.md +56 -26
- package/commands/api/examples.md +46 -1
- package/commands/btest/README.md +21 -32
- package/commands/check/README.md +6 -1
- package/commands/discover/README.md +5 -11
- package/commands/execute/README.md +77 -36
- package/commands/execute/examples.md +120 -10
- package/commands/explain/README.md +2756 -0
- package/commands/explain/examples.md +702 -0
- package/commands/mcp/README.md +7 -4
- package/commands/mcp/examples.md +2 -2
- package/commands/retrieve/README.md +25 -27
- package/commands/retrieve/examples.md +16 -0
- package/commands/terminal/examples.md +7 -1
- package/commands/transfer/README.md +28 -20
- package/commands/transfer/examples.md +29 -0
- package/docs/CLI.md +353 -0
- package/docs/CONSTITUTION.md +138 -30
- package/docs/MATRIX.md +92 -18
- package/docs/errors/README.md +3 -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/transport/provider-trust.md +29 -0
- package/docs/errors/usage/stdin-ignored.md +28 -0
- package/docs/errors/validation/syntax.md +29 -4
- package/docs/errors/validation/unknown-attribute.md +37 -4
- package/package.json +56 -10
- package/src/api-fanout.ts +46 -18
- package/src/api.ts +351 -121
- package/src/cli/api.ts +26 -7
- package/src/cli/btest.ts +19 -3
- package/src/cli/execute.ts +25 -6
- package/src/cli/explain.ts +503 -0
- package/src/cli/retrieve.ts +33 -1
- package/src/cli/selection.ts +71 -11
- package/src/cli/settings.ts +2 -2
- package/src/cli/terminal.ts +1 -1
- package/src/cli/transfer.ts +23 -6
- package/src/cli.ts +15 -2
- package/src/core/envelope.ts +58 -2
- package/src/core/error-catalog.ts +42 -1
- package/src/core/execute-command.ts +148 -0
- package/src/core/inspect.ts +36 -4
- package/src/core/preflight.ts +306 -0
- package/src/core/routeros-string.ts +24 -0
- package/src/core/version.ts +4 -0
- package/src/core/yaml.ts +86 -0
- package/src/errors.ts +1 -0
- package/src/execute-fanout.ts +47 -18
- package/src/execute.ts +339 -307
- package/src/explain/arg-tokens.ts +151 -0
- package/src/explain/args.ts +1749 -0
- package/src/explain/blocks.ts +175 -0
- package/src/explain/brace-slots.ts +143 -0
- package/src/explain/brace-tokens.ts +39 -0
- package/src/explain/catalog.ts +1343 -0
- package/src/explain/coordinates.ts +488 -0
- package/src/explain/defects.ts +218 -0
- package/src/explain/escape-tokens.ts +77 -0
- package/src/explain/highlight-projection.ts +308 -0
- package/src/explain/is-known-menu.ts +17 -0
- package/src/explain/menus.ts +668 -0
- package/src/explain/operator-tokens.ts +316 -0
- package/src/explain/operators.ts +465 -0
- package/src/explain/path-tokens.ts +150 -0
- package/src/explain/pathresolve.ts +1569 -0
- package/src/explain/quoted-string.ts +331 -0
- package/src/explain/scope-brace.ts +461 -0
- package/src/explain/segment.ts +1165 -0
- package/src/explain/separator.ts +187 -0
- package/src/explain/split-owner.ts +194 -0
- package/src/explain/string-tokens.ts +77 -0
- package/src/explain/symbol-values.ts +556 -0
- package/src/explain/symbols.ts +1617 -0
- package/src/explain/token-ranges.ts +72 -0
- package/src/explain/transport.ts +514 -0
- package/src/explain/value-tokens.ts +83 -0
- package/src/explain/values.ts +357 -0
- package/src/explain/verbs.ts +64 -0
- package/src/explain/verbsplit.ts +955 -0
- package/src/explain/write.ts +856 -0
- package/src/explain.ts +2602 -0
- package/src/index.ts +225 -6
- package/src/mcp/server.ts +2 -1
- package/src/offline-gate.ts +148 -0
- package/src/protocols/mac-telnet-console.ts +61 -17
- package/src/resolver/facts.ts +1 -1
- package/src/resolver/index.ts +23 -4
- package/src/resolver/quickchr-provider.ts +597 -0
- package/src/resolver/selection.ts +99 -12
- package/src/resolver/service-endpoint.ts +155 -0
- package/src/resolver/target.ts +1 -1
- package/src/retrieve-fanout.ts +27 -13
- package/src/retrieve.ts +82 -77
- package/src/settings.ts +2 -1
- package/src/transfer-fanout.ts +43 -26
- package/src/transfer.ts +88 -45
package/commands/api/examples.md
CHANGED
|
@@ -155,7 +155,23 @@ centrs api $R execute -X POST -f script=':put [/system/identity/get name]' --use
|
|
|
155
155
|
```
|
|
156
156
|
|
|
157
157
|
Envelope: `ok: true`, `data` is string-shaped and contains the CHR identity;
|
|
158
|
-
`meta.validation.semantic=not-applicable` (script, not a path)
|
|
158
|
+
`meta.validation.semantic=not-applicable` (script, not a path) and
|
|
159
|
+
`meta.validation.stages` is `offline: passed` then `device: skipped`.
|
|
160
|
+
|
|
161
|
+
### 16b. The `/execute` script is gated offline (GH#354)
|
|
162
|
+
|
|
163
|
+
Until GH#354 the script-mode carve-out meant this surface ran **no** preflight at
|
|
164
|
+
all. The offline analyzer is the whole stage-1 gate here, and it opens no
|
|
165
|
+
connection — the unreachable port proves it.
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
centrs api 127.0.0.1:1 execute -X POST -f script=':put [' --username $U --password $P --yes
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Envelope: `ok: false`, `error.code=validation/syntax`,
|
|
172
|
+
`error.context.validationStage="offline"`, `error.context.surface="api /execute"`,
|
|
173
|
+
`error.context.span={"start":5,"end":6}`, and **not** a `transport/*` code.
|
|
174
|
+
`meta.validation.stages` is `offline: failed` then `device: skipped`.
|
|
159
175
|
|
|
160
176
|
### 17. `--via rest-api --listen` is rejected
|
|
161
177
|
|
|
@@ -209,6 +225,20 @@ proplist). Combining an id with `--query`/`--proplist` folds the id into the RES
|
|
|
209
225
|
matching the native `?.id=` read. (Plain id-only reads still use the `GET …/$ID`
|
|
210
226
|
URL form; see example 5's re-read.)
|
|
211
227
|
|
|
228
|
+
### 22. `--raw --validate=true` still runs the gate (#154)
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
centrs api $R ip/no-such-menu --raw --validate=true --username $U --password $P
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
`--raw` only **defaults** `--validate` to false, so an explicit `--validate=true`
|
|
235
|
+
wins and the `/console/inspect` preflight runs. The rejection comes back in the
|
|
236
|
+
`--raw` error shape, not the envelope: stdout empty, stderr a compact
|
|
237
|
+
`{"code":"validation/unknown-path","message":…}`, nonzero exit, and no `data`
|
|
238
|
+
key. Contrast example 13, where bare `--raw` skips the preflight entirely. This
|
|
239
|
+
pair is how you tell a RouterOS rejection from a centrs bug while staying on the
|
|
240
|
+
bare-passthrough surface.
|
|
241
|
+
|
|
212
242
|
## native-api (`--via native-api`)
|
|
213
243
|
|
|
214
244
|
The same contract over the binary API. Validation still runs through
|
|
@@ -426,3 +456,18 @@ The reserved record is a credential fallback, not a connectable router, so its o
|
|
|
426
456
|
target fails deterministically: inner `ok:false` with
|
|
427
457
|
`error.code=target/unresolved` (never a `transport/dns` attempt on `"__default__"`).
|
|
428
458
|
`data.summary = { total: 1, ok: 0, failed: 1 }`. Exit code 1 (every target failed).
|
|
459
|
+
|
|
460
|
+
## quickchr targets (#134)
|
|
461
|
+
|
|
462
|
+
`$NAME` is the machine name of a running quickchr-managed CHR. Host/port/auth
|
|
463
|
+
come from the live descriptor (see `commands/retrieve/examples.md` Q-series).
|
|
464
|
+
Covered by `test/integration/quickchr-target.test.ts`.
|
|
465
|
+
|
|
466
|
+
### Q1. GET against a quickchr target
|
|
467
|
+
|
|
468
|
+
```bash
|
|
469
|
+
centrs api --quickchr $NAME system/resource --json
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
Envelope: `ok: true`, `data` is the resource object,
|
|
473
|
+
`meta.target.source.kind=provider`.
|
package/commands/btest/README.md
CHANGED
|
@@ -80,38 +80,27 @@ centrs btest server [--authenticate[=false]] [--user U] [--password P] \
|
|
|
80
80
|
|
|
81
81
|
## Flags
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
| Flag | RouterOS analog / default | Notes |
|
|
105
|
-
| ---- | ------------------------- | ----- |
|
|
106
|
-
| `--authenticate[=false]` | `authenticate`, **default `true`** | Require EC-SRP5 auth; `--authenticate=false` accepts anonymous clients. |
|
|
107
|
-
| `--user` / `--password` | — | The accepted credential when `authenticate=true` (v1: one user). Falls back to `CENTRS_*`. |
|
|
108
|
-
| `--bind <addr>` | **centrs-only** | Listen address; **default `127.0.0.1`**. Use `0.0.0.0` to expose on the LAN. |
|
|
109
|
-
| `--port <n>` | — (default `2000`) | Control-connection port. |
|
|
110
|
-
| `--allocate-udp-ports-from <n>` | `allocate-udp-ports-from` | Base of the UDP data-port range; default `2001` (just above the TCP/2000 control port). |
|
|
111
|
-
| `--max-sessions <n>` | `max-sessions` (1..1000), default `100` | Concurrent test cap; further clients get `routeros/btest-too-many-sessions`. |
|
|
112
|
-
| `--duration <dur>` | — | Optional auto-stop; otherwise runs until Ctrl-C. |
|
|
113
|
-
| `--format <text\|csv\|json\|yaml>` | — (default `text`) | `text` = human session log; `csv` = streaming CSV records; `json`/`yaml` = final summary envelope. |
|
|
114
|
-
| `--csv` | — | Shortcut for `--format csv`. |
|
|
83
|
+
Implemented flags are generated from the CLI metadata into
|
|
84
|
+
[`docs/CLI.md` → btest](../../docs/CLI.md#btest) — one table for both
|
|
85
|
+
subcommands, with `(client)`/`(server)` markers scoping each flag; "did you
|
|
86
|
+
mean?" hints are scoped to the active mode. This file does not duplicate that
|
|
87
|
+
table. Behavior notes the generated reference cannot carry:
|
|
88
|
+
|
|
89
|
+
- Flag names mirror the RouterOS analogs verbatim (`protocol`, `direction`,
|
|
90
|
+
`local-udp-tx-size`, `connection-count`, `authenticate`,
|
|
91
|
+
`allocate-udp-ports-from`, …); centrs-only additions are `--bind`, `--port`,
|
|
92
|
+
the shared `--format`/`--csv`, and the CDB credential flags.
|
|
93
|
+
- `--direction receive` = download (server→client), `transmit` = upload.
|
|
94
|
+
- `--connection-count` is TCP-only; **authenticated (EC-SRP5) sessions stay
|
|
95
|
+
single-stream** — the post-auth token is not captured — and emit a
|
|
96
|
+
`routeros/btest-connection-count-single-stream` warning (see Open
|
|
97
|
+
questions).
|
|
98
|
+
- `--user`/`--password` fall back to CDB / `CENTRS_*`; required when the
|
|
99
|
+
server has `authenticate=yes`. On the server they are the accepted
|
|
100
|
+
credential (v1: one user).
|
|
101
|
+
- Option ranges (`28..64000` UDP sizes, `1..255` connections, `20ms..5s`
|
|
102
|
+
interval, `1..1000` sessions) are validated before any socket opens (see
|
|
103
|
+
Errors).
|
|
115
104
|
|
|
116
105
|
## How it works
|
|
117
106
|
|
package/commands/check/README.md
CHANGED
|
@@ -197,6 +197,11 @@ triggered/enrichment checks; `--fast` suppresses progressive triggers.
|
|
|
197
197
|
|
|
198
198
|
## Flags
|
|
199
199
|
|
|
200
|
+
The whole command is spec-tier (`designed` — nothing is implemented yet), so
|
|
201
|
+
this table is the design, not a reference; once `check` lands, its
|
|
202
|
+
implemented flags will be generated into `docs/CLI.md` like every other
|
|
203
|
+
command's.
|
|
204
|
+
|
|
200
205
|
| Flag | Behavior |
|
|
201
206
|
| ---- | -------- |
|
|
202
207
|
| `--preset <name>` | `reach` / `health` (default) / `exposure` / `full`. |
|
|
@@ -243,7 +248,7 @@ RouterOS commands vary by version and arch, so a check must not blow up on a
|
|
|
243
248
|
device that lacks the path it reads. `resource` runs first and yields the **live**
|
|
244
249
|
version / arch / board; the orchestrator pre-flights each remaining check's
|
|
245
250
|
`requires` paths against that live value (falling back to the stored
|
|
246
|
-
`
|
|
251
|
+
`version` derived fact, or one batched `/console/inspect` when neither is
|
|
247
252
|
conclusive), and marks an unsupported check `ran:false` with
|
|
248
253
|
`skipReason:"unsupported-on-<version>"` — a graceful skip, not a raw protocol
|
|
249
254
|
error. Grounding is offline-first against restraml's historic `inspect.json`
|
|
@@ -53,17 +53,11 @@ tested without a router (crafted packet fixtures + a loopback socket).
|
|
|
53
53
|
|
|
54
54
|
## Flags and defaults
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
| `--port` | `5678` | UDP port to bind for MNDP. |
|
|
62
|
-
| `--cdb-file` | resolved CDB | CDB path override for `--save`. |
|
|
63
|
-
| `--cdb-password` | env/none | Decrypt an encrypted CDB; also used to re-encrypt on `--save`. |
|
|
64
|
-
| `--format` | `text` | `text`, `json`, or `yaml`; renders the same envelope. |
|
|
65
|
-
|
|
66
|
-
`--save` without `--timeout` uses the default 15s window.
|
|
56
|
+
Implemented flags (with their defaults) are generated from the CLI metadata
|
|
57
|
+
into [`docs/CLI.md` → discover](../../docs/CLI.md#discover); this file does
|
|
58
|
+
not duplicate that table. The first refresh is sent immediately, so the
|
|
59
|
+
`--timeout` listen window (default 15s) bounds the wait, and `--save` without
|
|
60
|
+
`--timeout` uses the same default window.
|
|
67
61
|
|
|
68
62
|
Bare `centrs discover` is **read-only** — it returns the envelope and never
|
|
69
63
|
writes the (WinBox-shared) CDB; `--save` is the explicit opt-in. When it finds
|
|
@@ -16,11 +16,26 @@ selection**, examples F1–F5). `romon` and `winbox-terminal` remain
|
|
|
16
16
|
- Mirror RouterOS `execute` semantics. Input is a CLI string; output is
|
|
17
17
|
console-shaped text (or structured records for path+verb writes) wrapped in
|
|
18
18
|
the standard envelope. This includes write-shaped add/set/remove.
|
|
19
|
-
- Validation
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
- Validation runs **offline first, then on the device** (see constitution:
|
|
20
|
+
validation; GH#354). Stage 1 is the corpus-gated analyzer over the exact
|
|
21
|
+
command string, with no connection: a syntax fault is rejected with a byte
|
|
22
|
+
span in `error.context.span` and the adapter is never dialed, and
|
|
23
|
+
`meta.validation.stages[]` then shows the device stage `skipped`. Stage 1
|
|
24
|
+
cannot see an unknown attribute — it has no per-menu schema — so stage 2 is
|
|
25
|
+
not optional.
|
|
26
|
+
- Stage 2 is itself two gates (grounded on CHR 7.23.3,
|
|
27
|
+
GH#230): a single `:put [:parse "<cmd>"]` covers both the syntax and the
|
|
28
|
+
unknown-attribute (name-level) gate on **every** transport — `:parse` never
|
|
29
|
+
throws; its return value's printed form (`(evl …)` / `(evl bad parameter …)` /
|
|
30
|
+
`(<%% bad command name …)`) is read from the REST/native `ret` *and* the
|
|
31
|
+
console output. That check is necessary, not sufficient: value-type errors pass
|
|
32
|
+
it, and the `/console/inspect request=child` / server re-validation semantic
|
|
33
|
+
checks are separate. Command-level `request=completion` is deliberately not
|
|
34
|
+
used: it hangs REST and closes native API on RouterOS 7.12.2 (GH#343), while
|
|
35
|
+
`child` returns the complete argument-name set immediately.
|
|
36
|
+
The outer RouterOS string literal escapes backslash, quote, and dollar so
|
|
37
|
+
validation parses the caller's exact command rather than expanding variables
|
|
38
|
+
in the wrapper.
|
|
24
39
|
- centrs owns the **script-vs-structured gate** (`canonicalizeExecuteCommand` +
|
|
25
40
|
`isWriteShaped` in `src/execute.ts`): it decides which validation runs and
|
|
26
41
|
whether the write-confirm prompt fires. The shared `rosetta`/`lsp-routeros-ts`
|
|
@@ -67,13 +82,33 @@ selection**, examples F1–F5). `romon` and `winbox-terminal` remain
|
|
|
67
82
|
terminal width, so each transport handles width differently. **mac-telnet** is
|
|
68
83
|
the only one that negotiates a size: `src/protocols/mac-telnet-console.ts`
|
|
69
84
|
answers the device's terminal-size probe with a deliberately tall/wide screen,
|
|
70
|
-
so captured columns are neither
|
|
71
|
-
*without* a pseudo-tty (`src/protocols/ssh.ts`),
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
85
|
+
so captured columns are neither paginated nor wrapped *at the negotiated
|
|
86
|
+
width*. **SSH execute** runs *without* a pseudo-tty (`src/protocols/ssh.ts`),
|
|
87
|
+
so no width is negotiated at all. **REST/native-api** are not consoles, so a
|
|
88
|
+
terminal width never applies (they return structured records, or a string
|
|
89
|
+
`ret` for script-mode). There is therefore no per-call `--width` knob today —
|
|
90
|
+
an explicit width override is deferred until a concrete need appears.
|
|
91
|
+
- **A device-side wrap that no width negotiation reaches (GH#352).** Declaring a
|
|
92
|
+
width is not sufficient, because RouterOS can wrap for reasons that are not
|
|
93
|
+
about width. On 7.23.6 / 7.24.3 / 7.25beta4 a settings menu with exactly one
|
|
94
|
+
property prints its value **one character per line**: `/system/identity/print`
|
|
95
|
+
returns `name: C` / `H` / `R` where 7.24.2 returns `name: CHR`. Measured on
|
|
96
|
+
stock CHR, the wrap is identical over REST `POST /rest/execute`, over
|
|
97
|
+
`ssh user@host "<cmd>"`, and over the mac-telnet console — including the
|
|
98
|
+
mac-telnet reader that has already answered the size probe with 512 columns,
|
|
99
|
+
and the ssh client that negotiates nothing. The console login modifiers
|
|
100
|
+
(`+c512w200h`, `+ct`, `+e`) do not change it, and it is not proportional to
|
|
101
|
+
the value's length. Every multi-property `print` is unaffected on the same
|
|
102
|
+
build, as is `:put [/system/identity/get name]`.
|
|
103
|
+
|
|
104
|
+
Consequence for this command: `data.ret` carries the device's console text
|
|
105
|
+
**verbatim**, wrap included — centrs cannot tell a layout newline from
|
|
106
|
+
content, and inventing a de-wrapper would corrupt legitimate output. Callers
|
|
107
|
+
that need a value rather than a rendering should read it structurally
|
|
108
|
+
(`retrieve`, or `api`) or ask the device for the scalar
|
|
109
|
+
(`:put [<menu>/get <property>]`). The integration examples compare identities
|
|
110
|
+
with `print` column wrapping collapsed for this reason — see
|
|
111
|
+
`collapsePrintWrapping` in `test/integration/chr.ts`.
|
|
77
112
|
- Attribute values pass through **verbatim** — centrs does not guess types or
|
|
78
113
|
coerce them (RouterOS REST tolerates string values; the native API is
|
|
79
114
|
all-strings). Unlike `jo`-style KV builders, there is no per-value
|
|
@@ -102,30 +137,31 @@ selection**, examples F1–F5). `romon` and `winbox-terminal` remain
|
|
|
102
137
|
|
|
103
138
|
## Flags
|
|
104
139
|
|
|
105
|
-
|
|
140
|
+
Implemented flags are generated from the CLI metadata into
|
|
141
|
+
[`docs/CLI.md` → execute](../../docs/CLI.md#execute); this file does not
|
|
142
|
+
duplicate that table. Behavior notes the generated reference cannot carry:
|
|
143
|
+
|
|
144
|
+
- `--validate` is transport-appropriate but now one gate on every transport —
|
|
145
|
+
a single `:put [:parse ...]` that is read as a value (GH#230): REST/native
|
|
146
|
+
read the `ret` text (HTTP 200 / `as-string`); mac-telnet and ssh read the
|
|
147
|
+
console's printed value.
|
|
148
|
+
- `--format` defaults to `text`; `CENTRS_FORMAT` sets the default.
|
|
149
|
+
- `--quickchr` resolves host/port/auth from the live `@tikoci/quickchr`
|
|
150
|
+
(0.4.5+, optional dependency) descriptor, bypassing CDB/env resolution for
|
|
151
|
+
those fields.
|
|
152
|
+
- `--` — use it when the command contains tokens that would otherwise be
|
|
153
|
+
claimed as centrs flags; otherwise the command must be quoted as a single
|
|
154
|
+
argument. In fan-out, positional targets come BEFORE `--` and the command
|
|
155
|
+
AFTER it (see **Target selection**).
|
|
156
|
+
|
|
157
|
+
### Designed, not implemented
|
|
158
|
+
|
|
159
|
+
Spec-tier flags with no implementation yet — today they fail as unknown flags:
|
|
160
|
+
|
|
161
|
+
| Flag | Designed behavior |
|
|
106
162
|
| ------------------------------- | ------------------------------------------------------------------------------------- |
|
|
107
|
-
| `--via <protocol>` | Pin the transport (`native-api`, `rest-api`, `ssh`, or `mac-telnet`). No silent downgrade. A bare MAC target defaults to `mac-telnet`. |
|
|
108
|
-
| `--host <host\|url>` | Override the resolved host or base URL for the target. |
|
|
109
|
-
| `--port <n>` | Override the resolved management port. |
|
|
110
|
-
| `--username` (alias `--user`, `-u`) / `--password` | RouterOS credentials; fall back to `CENTRS_USERNAME` / `CENTRS_PASSWORD`. |
|
|
111
|
-
| `--ssh-key <path>` | `--via ssh`: explicit private-key path. Falls back to `CENTRS_SSH_KEY` / the ssh-agent. |
|
|
112
|
-
| `--insecure` | Accept a new SSH host key (`--via ssh`) or a self-signed `api-ssl` TLS cert. Default verifies. |
|
|
113
|
-
| `--cdb-file` / `--cdb-password` | Read target credentials from (and decrypt) a WinBox CDB file. |
|
|
114
|
-
| `--resolve <none\|arp>` | Resolve a MAC-address target to an IP via the host ARP cache. Default `none`. |
|
|
115
|
-
| `--timeout <duration>` | Per-request timeout. REST: ≤ 60s; other transports may allow longer. |
|
|
116
|
-
| `--validate[=false]` | Run transport-appropriate preflight validation before execution (default `true`): REST/native use `:parse` + `/console/inspect`; mac-telnet and ssh use a single console `:parse` (covers syntax + unknown-attribute). |
|
|
117
163
|
| `--strict` | Treat any warning on a successful run as an error (`ok: false`, nonzero exit), like `-Werror`. Default is lenient (`ok: true` + `warnings`). |
|
|
118
|
-
| `--
|
|
119
|
-
| `--max-bytes <n>` | Byte budget for the rendered envelope; excess output is truncated with a warning + `meta.truncated` (not an error), matching `retrieve`. (Renamed from `--max-results`.) |
|
|
120
|
-
| `--format <text\|json\|yaml>` | Output format (alias `--json`). Defaults to `text`; `CENTRS_FORMAT` sets the default. |
|
|
121
|
-
| `--group <name>` | Run across every CDB record in WinBox group `<name>`. Repeatable, combinable with `<target>` positionals plus `--where`/`--all`/`--default`; de-duped by record index. See **Target selection**. |
|
|
122
|
-
| `--where <attr>=<value>` | Device-class selector — run across every CDB record whose stored fact/comment-kv matches (e.g. `--where board=RB5009 /system/package/check-for-updates`). Repeatable (AND). See constitution: target selection. |
|
|
123
|
-
| `--all` | Run across every CDB record (excludes the reserved `__default__`). See **Target selection**. |
|
|
124
|
-
| `--default` | Select the reserved `__default__` record. See **Target selection**. |
|
|
125
|
-
| `--near <lat>,<lon>,<radius>` | Geo selector — run across CDB records whose GPS is within the great-circle radius (units `m`/`km`/`mi`/`ft`; bare = km). Lat-first. Union predicate; geo-less devices never match. See **Target selection**. |
|
|
126
|
-
| `--bbox <south>,<west>,<north>,<east>` | Geo selector — run across CDB records whose GPS is inside the lat-first bounding box. Union predicate. See **Target selection**. |
|
|
127
|
-
| `--concurrency <n>` | Max in-flight targets during fan-out (integer ≥ 1). Transport-aware default: `rest-api` 8, others 4. |
|
|
128
|
-
| `--` | End-of-options marker. Every token after `--` is taken as the literal RouterOS command, even flag-shaped ones — e.g. `centrs execute $R -- /interface print where disabled=yes`. In fan-out, positional targets come BEFORE `--` and the command AFTER it (`centrs execute r1 r2 -- /system/reboot`). Use it when the command contains tokens that would otherwise be claimed as centrs flags; otherwise the command must be quoted as a single argument. |
|
|
164
|
+
| `--max-bytes <n>` | Byte budget for the rendered envelope; excess output is truncated with a warning + `meta.truncated` (not an error), matching `retrieve`. (Will replace the implemented `--max-results`, which fails instead of truncating.) |
|
|
129
165
|
|
|
130
166
|
## SSH key selection
|
|
131
167
|
|
|
@@ -174,6 +210,11 @@ many tokens):
|
|
|
174
210
|
- `execute r1 /system/resource/print` — a single positional target with no
|
|
175
211
|
selector stays **single-target** (legacy split: first positional is the target,
|
|
176
212
|
the rest is the command), never `FanoutData`.
|
|
213
|
+
- `execute --quickchr lab /system/resource/print` — the quickchr
|
|
214
|
+
named-live-provider claims the target slots like a selector (positionals are
|
|
215
|
+
the command; no `--` needed). One `--quickchr` stays single-target; repeating
|
|
216
|
+
it fans out; mixing it with positionals or CDB selectors is
|
|
217
|
+
`usage/conflicting-flags` (constitution: resolution providers).
|
|
177
218
|
|
|
178
219
|
Because `execute` is the primary **write** path, a write-shaped fan-out
|
|
179
220
|
(add/set/remove) is gated by **`--yes`**, confirmed **once** up front; without it
|
|
@@ -213,7 +254,7 @@ How it is wired:
|
|
|
213
254
|
`transport/capability-unsupported` (mac-telnet is execute/terminal only).
|
|
214
255
|
- **Validation is one console `:parse`.** Over the console, `:put [:parse "<cmd>"]`
|
|
215
256
|
prints both `syntax error` and `bad parameter <name>`, so a single gate covers
|
|
216
|
-
syntax **and** the unknown-attribute (
|
|
257
|
+
syntax **and** the unknown-attribute (name-level) check — no `/console/inspect`
|
|
217
258
|
table parsing. Maps to `validation/syntax` / `validation/unknown-attribute`,
|
|
218
259
|
same codes as REST/native (see `docs/CONSTITUTION.md`, validation).
|
|
219
260
|
- **Auth (grounded):** MTWEI is required; a stock device offers a 16-byte MD5 salt
|
|
@@ -243,7 +284,7 @@ Protocol selection: an unresolved MAC target auto-selects mac-telnet (covered by
|
|
|
243
284
|
**decided:** `--strict` promotes any warning on an otherwise-successful run to
|
|
244
285
|
`ok: false` (nonzero exit), like `-Werror`; the default stays lenient
|
|
245
286
|
(`ok: true` with the warning in the `warnings` channel). Tracked code
|
|
246
|
-
follow-up; the flag is
|
|
287
|
+
follow-up; the flag is under **Designed, not implemented** above.
|
|
247
288
|
|
|
248
289
|
These are deferred refinements to the already-grounded single-shot envelope,
|
|
249
290
|
not blockers for the `rest-api`/`native-api` cells.
|
|
@@ -62,16 +62,18 @@ The command is a script-shaped console expression, not a slash path plus verb,
|
|
|
62
62
|
so REST must use the `/rest/execute` fallback.
|
|
63
63
|
|
|
64
64
|
```bash
|
|
65
|
-
centrs execute $R ':put [/system/identity/get name]' --via rest-api --username $U --password $P
|
|
65
|
+
centrs execute $R ':local a true; :if ($a = true) do={:put [/system/identity/get name]}' --via rest-api --username $U --password $P
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
Envelope: `ok: true`, `data` is string-shaped (`data.output` or `data.ret`) and
|
|
69
69
|
contains the CHR identity name, `meta.via=rest-api`, and
|
|
70
70
|
`meta.validation.syntax=true`. `meta.validation.semantic` is absent or marked
|
|
71
71
|
`not-applicable` because `/console/inspect` validates path-shaped commands, not
|
|
72
|
-
arbitrary script expressions.
|
|
72
|
+
arbitrary script expressions. The `$a` variable must survive the validation
|
|
73
|
+
wrapper: if that outer string expands it before `:parse`, this example fails even
|
|
74
|
+
though RouterOS accepts the original command.
|
|
73
75
|
|
|
74
|
-
### 5. Syntax reject
|
|
76
|
+
### 5. Syntax reject before the write round-trip
|
|
75
77
|
|
|
76
78
|
Malformed CLI is rejected by the syntax gate before semantic validation or the
|
|
77
79
|
write round-trip.
|
|
@@ -80,9 +82,16 @@ write round-trip.
|
|
|
80
82
|
centrs execute $R '/ip/address/add address="unterminated interface=ether1' --via rest-api --username $U --password $P --yes
|
|
81
83
|
```
|
|
82
84
|
|
|
83
|
-
Envelope: `ok: false`, `error.code=validation/syntax`, `
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
Envelope: `ok: false`, `error.code=validation/syntax`, `meta.via=rest-api`, and
|
|
86
|
+
no address is added.
|
|
87
|
+
|
|
88
|
+
**Which stage rejects it changed in GH#354.** This input is now caught by the
|
|
89
|
+
*offline* stage, so `error.context.validationStage="offline"`,
|
|
90
|
+
`error.cause="explain/canonicalizer/unterminated-string"`,
|
|
91
|
+
`error.context.span={"start":24,"end":54}`, and the device is never contacted —
|
|
92
|
+
see V1–V3 below. It reached `:put [:parse ...]` before that. The code, the
|
|
93
|
+
`meta.via`, and the no-mutation guarantee are unchanged, which is why the
|
|
94
|
+
assertion in `test/integration/execute.test.ts` still reads `validation/syntax`.
|
|
86
95
|
|
|
87
96
|
### 6. Semantic reject that `:parse` accepts
|
|
88
97
|
|
|
@@ -95,7 +104,7 @@ centrs execute $R '/ip/address/add address=198.51.100.11/32 interface=ether1 no-
|
|
|
95
104
|
```
|
|
96
105
|
|
|
97
106
|
Envelope: `ok: false`, `error.code=validation/unknown-attribute`,
|
|
98
|
-
`error.
|
|
107
|
+
`error.context.parameter="no-such-arg"`,
|
|
99
108
|
`meta.validation.syntax=true`, `meta.validation.source=/console/inspect`, and
|
|
100
109
|
no address is added. This example is the product claim: syntax validation and
|
|
101
110
|
semantic validation are separate gates.
|
|
@@ -200,14 +209,16 @@ centrs execute $A "/ip/address/remove numbers=$ID" --via native-api --port $API_
|
|
|
200
209
|
Envelope: `ok: true`, `meta.via=native-api`. A subsequent retrieve of
|
|
201
210
|
`/ip/address` does not contain `$ID`.
|
|
202
211
|
|
|
203
|
-
### 15. Native syntax reject
|
|
212
|
+
### 15. Native syntax reject
|
|
204
213
|
|
|
205
214
|
```bash
|
|
206
215
|
centrs execute $A '/ip/address/add address="unterminated interface=ether1' --via native-api --port $API_PORT --username $U --password $P --yes
|
|
207
216
|
```
|
|
208
217
|
|
|
209
|
-
Envelope: `ok: false`, `error.code=validation/syntax`, `
|
|
210
|
-
|
|
218
|
+
Envelope: `ok: false`, `error.code=validation/syntax`, `meta.via=native-api`, and
|
|
219
|
+
no address is added. The native twin of example 5, and it moved the same way:
|
|
220
|
+
the offline stage rejects this input now, so the verdict no longer depends on
|
|
221
|
+
the transport at all.
|
|
211
222
|
|
|
212
223
|
### 16. Native semantic reject after clean parse
|
|
213
224
|
|
|
@@ -283,6 +294,10 @@ Envelope: `ok: true`, `meta.via=mac-telnet`, `data.ret` is the console output an
|
|
|
283
294
|
contains the device identity (cross-checked against REST). `meta.validation.source`
|
|
284
295
|
is `:put [:parse ...] over mac-telnet`.
|
|
285
296
|
|
|
297
|
+
RouterOS 7.23.6/7.24.3/7.25beta4 print a one-property menu's value one
|
|
298
|
+
character per line (GH#352), so the comparison collapses `print` column
|
|
299
|
+
wrapping on both sides rather than asserting the device's layout.
|
|
300
|
+
|
|
286
301
|
### 21. Write (add) over mac-telnet
|
|
287
302
|
|
|
288
303
|
```bash
|
|
@@ -327,6 +342,10 @@ Envelope: `ok: true`, `meta.via=ssh`, `data.ret` is the cleaned console output a
|
|
|
327
342
|
contains the device identity (cross-checked against REST). `meta.validation.source`
|
|
328
343
|
is `:put [:parse ...] over ssh`.
|
|
329
344
|
|
|
345
|
+
RouterOS 7.23.6/7.24.3/7.25beta4 print a one-property menu's value one
|
|
346
|
+
character per line (GH#352), so the comparison collapses `print` column
|
|
347
|
+
wrapping on both sides rather than asserting the device's layout.
|
|
348
|
+
|
|
330
349
|
### S2. Multi-line read returns cleaned, column-aligned output
|
|
331
350
|
|
|
332
351
|
```bash
|
|
@@ -354,6 +373,61 @@ centrs execute 127.0.0.1 '/ip/address/add address=198.51.100.51/32 interface=eth
|
|
|
354
373
|
Envelope: `ok: false`, `error.code=validation/unknown-attribute`, `meta.via=ssh`,
|
|
355
374
|
and no address is added — the console `:parse` gate catches `bogus` pre-mutation.
|
|
356
375
|
|
|
376
|
+
## Offline gate (stage 1)
|
|
377
|
+
|
|
378
|
+
Validation runs offline before it runs on the device
|
|
379
|
+
([`docs/CONSTITUTION.md` → Validation is the product](../../docs/CONSTITUTION.md#validation-is-the-product),
|
|
380
|
+
GH#354). These two prove the stage exists and that a stage-1 rejection reaches no
|
|
381
|
+
router. Run by `test/integration/execute.test.ts`.
|
|
382
|
+
|
|
383
|
+
### V1. Offline reject, with the device stage never asked
|
|
384
|
+
|
|
385
|
+
`:put [` is an unclosed bracket. The old local quote preflight counted quotes
|
|
386
|
+
only, so this used to cost a full round trip to be told no; the analyzer rejects
|
|
387
|
+
it with a byte span before anything is dialed.
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
centrs execute $R ':put [' --via rest-api --username $U --password $P --json
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Envelope: `ok: false`, `error.code=validation/syntax`,
|
|
394
|
+
`error.context.validationStage="offline"`,
|
|
395
|
+
`error.context.span={"start":5,"end":6}`,
|
|
396
|
+
`error.context.diagnostics[0].code="explain/canonicalizer/unclosed"`,
|
|
397
|
+
`error.position` absent (the offline byte offset is never dressed up as a
|
|
398
|
+
RouterOS-reported one), `meta.validation.source` is the offline analyzer, and
|
|
399
|
+
`meta.validation.stages` is `offline: failed` followed by `device: skipped`.
|
|
400
|
+
|
|
401
|
+
### V2. The same rejection with no reachable router at all
|
|
402
|
+
|
|
403
|
+
The proof that stage 1 opens no connection: point the same command at a port
|
|
404
|
+
nothing is listening on. A gate that dialed first would report a
|
|
405
|
+
`transport/*` error or hang until the timeout.
|
|
406
|
+
|
|
407
|
+
```bash
|
|
408
|
+
centrs execute 127.0.0.1:1 ':put [' --via rest-api --username $U --password $P --json
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Envelope: identical to V1 — `ok: false`, `error.code=validation/syntax`,
|
|
412
|
+
`error.context.validationStage="offline"` — and **not** a `transport/*` code.
|
|
413
|
+
|
|
414
|
+
### V3. `--validate=false` disables the offline stage too
|
|
415
|
+
|
|
416
|
+
One flag, one meaning. The escape hatch has to reach stage 1, or a caller hitting
|
|
417
|
+
an analyzer defect would have no way past it. RouterOS then rejects the command
|
|
418
|
+
itself on the run.
|
|
419
|
+
|
|
420
|
+
```bash
|
|
421
|
+
centrs execute $R ':put [' --via rest-api --username $U --password $P --json --validate=false
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
Envelope: `ok: false`, `error.code=routeros/request-failed` with RouterOS's own
|
|
425
|
+
`syntax error (line 1 column 7)` in the summary — the **device** rejecting it, not
|
|
426
|
+
centrs — plus `meta.validation.enabled=false`, `meta.validation.result="skipped"`,
|
|
427
|
+
and `meta.validation.stages` listing **both** stages as `skipped`. A gated
|
|
428
|
+
surface always reports every stage, so a consumer reading `stages` never has to
|
|
429
|
+
special-case the disabled shape.
|
|
430
|
+
|
|
357
431
|
## Target selection (fan-out)
|
|
358
432
|
|
|
359
433
|
These exercise the shared target-selection grammar
|
|
@@ -435,3 +509,39 @@ Envelope: `meta.via=mac-telnet` when `$MAC` is not resolved from CDB and no
|
|
|
435
509
|
`--host`/`--port`). If the caller wants IP-level REST or native API execution,
|
|
436
510
|
they must opt into MAC-to-IP resolution (`--resolve arp` + `--via …`) before
|
|
437
511
|
protocol selection.
|
|
512
|
+
|
|
513
|
+
## quickchr targets (#134)
|
|
514
|
+
|
|
515
|
+
`$NAME` is the machine name of a running quickchr-managed CHR. Host/port/auth
|
|
516
|
+
come from the live descriptor — the CDB, `CENTRS_HOST`-style env, and the
|
|
517
|
+
`__default__` ladder are bypassed for those fields. Covered by
|
|
518
|
+
`test/integration/quickchr-target.test.ts`.
|
|
519
|
+
|
|
520
|
+
### Q1. Run a command on a quickchr target (native API default)
|
|
521
|
+
|
|
522
|
+
```bash
|
|
523
|
+
centrs execute --quickchr $NAME ':put [/system/identity/get name]' --json
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
Envelope: `ok: true`, `meta.via=native-api` (execute's normal default),
|
|
527
|
+
`meta.target.source.kind=provider`, `meta.target.identity=$NAME`.
|
|
528
|
+
|
|
529
|
+
### Q2. A `--via` the descriptor does not forward → `quickchr/unsupported-via`
|
|
530
|
+
|
|
531
|
+
```bash
|
|
532
|
+
centrs execute --quickchr $NAME ':put 1' --via mac-telnet --json
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
Envelope: `ok: false`, `error.code=quickchr/unsupported-via` — quickchr v1
|
|
536
|
+
forwards `rest-api`/`native-api`/`ssh` only; centrs never falls back to another
|
|
537
|
+
transport.
|
|
538
|
+
|
|
539
|
+
### Q3. Repeated `--quickchr` fans out; an unknown machine is an inner failure
|
|
540
|
+
|
|
541
|
+
```bash
|
|
542
|
+
centrs execute --quickchr $NAME --quickchr no-such-machine -- ':put 1' --json
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
Envelope: outer `ok: true`, `data.summary = { total: 2, ok: 1, failed: 1 }`;
|
|
546
|
+
the unknown machine's inner error is `quickchr/machine-not-found`. Exit code 2
|
|
547
|
+
(partial).
|