@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
|
@@ -486,3 +486,151 @@ Each alias produces the same envelope as its canonical verb — `meta.operation`
|
|
|
486
486
|
reports the canonical command (`list`/`show`/`remove`), never the alias. An
|
|
487
487
|
unknown verb still errors with `input/invalid-command`, and the error lists the
|
|
488
488
|
canonical verbs plus the accepted aliases.
|
|
489
|
+
|
|
490
|
+
## Location / GPS
|
|
491
|
+
|
|
492
|
+
Slice 1 of issue #146: `lat`/`lon`/`altitude`/`altitude-type` are recognized
|
|
493
|
+
comment-kv facts (see `README.md`, Location / GPS) — stored, queryable via
|
|
494
|
+
`--where`, and returned as a `location` block in `devices show`/`list` output.
|
|
495
|
+
Coordinate order is **lat-first everywhere**: `--gps <lat>,<lon>[,…]`.
|
|
496
|
+
|
|
497
|
+
### 42. `add --gps` sets lat/lon/altitude; `show` returns a `location` block
|
|
498
|
+
|
|
499
|
+
```bash
|
|
500
|
+
centrs devices add 198.51.100.40 --user admin --password p \
|
|
501
|
+
--gps 37.7749,-122.4194,16 --cdb-file $CDB
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
`ok: true`. Re-reading via `show` returns
|
|
505
|
+
`data.entry.location = { lat: 37.7749, lon: -122.4194, altitude: 16, altitudeType: "MSL" }`
|
|
506
|
+
(the 3-part `--gps` form defaults the missing altitude-type to `MSL`).
|
|
507
|
+
|
|
508
|
+
### 43. Discrete `--lat`/`--lon` round-trip the same way as `--gps`
|
|
509
|
+
|
|
510
|
+
```bash
|
|
511
|
+
centrs devices set 192.0.2.5 --lat 51.5072 --lon -0.1276 --cdb-file $CDB
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
`ok: true`. `devices show 192.0.2.5` returns
|
|
515
|
+
`data.entry.location = { lat: 51.5072, lon: -0.1276 }` (no `altitude` key —
|
|
516
|
+
altitude was never set on this record).
|
|
517
|
+
|
|
518
|
+
### 44. `--altitude` alone defaults `altitudeType` to `MSL` in the envelope
|
|
519
|
+
|
|
520
|
+
```bash
|
|
521
|
+
centrs devices set 192.0.2.6 --lat 48.8566 --lon 2.3522 --altitude 35 \
|
|
522
|
+
--cdb-file $CDB
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
`ok: true`. `altitude-type` was never written to the comment (only
|
|
526
|
+
`--altitude` was passed), but `devices show 192.0.2.6` still returns
|
|
527
|
+
`data.entry.location.altitudeType = "MSL"` — the metadata-only default applied
|
|
528
|
+
at read time (`deviceLocation` in `src/resolver/geo.ts`).
|
|
529
|
+
|
|
530
|
+
### 45. Setting `lat` without `lon` errors with `input/incomplete-gps`
|
|
531
|
+
|
|
532
|
+
```bash
|
|
533
|
+
centrs devices set 198.51.100.1 --lat 10 --cdb-file $CDB
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
Errors with `input/incomplete-gps` (target `198.51.100.1` carries no prior
|
|
537
|
+
`lon`). No write; no backup written.
|
|
538
|
+
|
|
539
|
+
### 46. An out-of-range `--lat` errors with `input/invalid-coordinate`
|
|
540
|
+
|
|
541
|
+
```bash
|
|
542
|
+
centrs devices set 192.0.2.5 --lat 200 --lon 0 --cdb-file $CDB
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
Errors with `input/invalid-coordinate`. No write; no backup written.
|
|
546
|
+
|
|
547
|
+
### 47. An invalid `--altitude-type` errors with `input/invalid-altitude`
|
|
548
|
+
|
|
549
|
+
```bash
|
|
550
|
+
centrs devices set 192.0.2.5 --lat 10 --lon 10 --altitude-type wgs84 \
|
|
551
|
+
--cdb-file $CDB
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
Errors with `input/invalid-altitude`. No write; no backup written.
|
|
555
|
+
|
|
556
|
+
### 48. Alias flags canonicalize to the stored key; no more `cdb/unknown-option`
|
|
557
|
+
|
|
558
|
+
```bash
|
|
559
|
+
centrs devices set 192.0.2.5 --latitude 10 --lng 20 --cdb-file $CDB
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
`ok: true`, **no** `cdb/unknown-option` warning — `--latitude`/`--lng` are
|
|
563
|
+
canonicalized to `lat`/`lon` in one place (`canonicalizeGeoKey`,
|
|
564
|
+
`src/resolver/geo.ts`) before storage. `devices show 192.0.2.5` returns
|
|
565
|
+
`data.entry.comment` containing `lat=10` and `lon=20` (the canonical keys), and
|
|
566
|
+
`data.entry.location = { lat: 10, lon: 20 }`.
|
|
567
|
+
|
|
568
|
+
### 49. `list --where lat=<verbatim>` matches the exact stored string
|
|
569
|
+
|
|
570
|
+
```bash
|
|
571
|
+
centrs devices list --where lat=37.774900 --cdb-file $CDB
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
`ok: true`; matches only the record whose stored `lat` fact is the exact
|
|
575
|
+
string `37.774900` (values are stored verbatim as typed, never reformatted —
|
|
576
|
+
see `README.md`, Location / GPS). `--where lat=37.7749` (numerically equal,
|
|
577
|
+
reformatted) matches nothing.
|
|
578
|
+
|
|
579
|
+
## Geo query (`list --near` / `list --bbox`)
|
|
580
|
+
|
|
581
|
+
`--near`/`--bbox` filter `devices list` by device GPS (great-circle radius or a
|
|
582
|
+
lat-first bounding box). Coordinates are lat-first everywhere (see `README.md`,
|
|
583
|
+
Location / GPS); a geo-less device carries no location and is silently excluded.
|
|
584
|
+
The same predicates ride the shared fan-out grammar on every non-terminal
|
|
585
|
+
command (`execute`/`retrieve`/`api`/`transfer`) — see `docs/CONSTITUTION.md`
|
|
586
|
+
(Target selection).
|
|
587
|
+
|
|
588
|
+
### 50. `list --near <lat>,<lon>,<radius>` selects in-radius devices
|
|
589
|
+
|
|
590
|
+
`192.0.2.5` is in San Francisco and `192.0.2.6` in New York (set via `--gps`);
|
|
591
|
+
the other records carry no GPS.
|
|
592
|
+
|
|
593
|
+
```bash
|
|
594
|
+
centrs devices list --near 37.7749,-122.4194,50km --cdb-file $CDB
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
`ok: true`; `data` holds only `192.0.2.5`. New York is ~4100 km away (outside the
|
|
598
|
+
50 km radius) and the geo-less records never match. The radius unit is one of
|
|
599
|
+
`m`/`km`/`mi`/`ft`; a bare number is kilometers.
|
|
600
|
+
|
|
601
|
+
### 51. `list --bbox <south>,<west>,<north>,<east>` selects devices in the box
|
|
602
|
+
|
|
603
|
+
```bash
|
|
604
|
+
centrs devices list --bbox 37.70,-122.52,37.83,-122.35 --cdb-file $CDB
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
`ok: true`; `data` holds only the device whose GPS falls inside the lat-first
|
|
608
|
+
box (edges inclusive). The box must satisfy `south <= north` and `west <= east`
|
|
609
|
+
(no antimeridian wrap), else `input/invalid-bbox`.
|
|
610
|
+
|
|
611
|
+
### 52. `--near`/`--bbox` are `list`-only
|
|
612
|
+
|
|
613
|
+
```bash
|
|
614
|
+
centrs devices set 192.0.2.5 --near 37.7749,-122.4194,50km --cdb-file $CDB
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
Errors with `input/invalid-command`: `--where`/`--near`/`--bbox` *select*
|
|
618
|
+
records for `list`; `add`/`set` name the `<target>` directly. Store GPS with
|
|
619
|
+
`--gps`/`--lat`/`--lon` instead.
|
|
620
|
+
|
|
621
|
+
### 53. An unrelated `set` on a record with a lone `lat` does not fail
|
|
622
|
+
|
|
623
|
+
The lat/lon pairing check fires only when the write actually touches a
|
|
624
|
+
coordinate. A record that already carries a lone `lat` (e.g. a hand-edited or
|
|
625
|
+
imported comment) can still take unrelated updates.
|
|
626
|
+
|
|
627
|
+
```bash
|
|
628
|
+
centrs devices add 203.0.113.7 --user u --password p \
|
|
629
|
+
--comment "lat=37.7749" --cdb-file $CDB
|
|
630
|
+
centrs devices set 203.0.113.7 timeout=5000 --cdb-file $CDB
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
Both succeed: the `add` stores the freeform comment verbatim, and the later
|
|
634
|
+
`set timeout=5000` does **not** raise `input/incomplete-gps` even though the
|
|
635
|
+
record's GPS pair is incomplete. Touching `lat`/`lon` (e.g. `set lat=…`) would
|
|
636
|
+
re-enable the pairing check.
|
|
@@ -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
|
|
@@ -6,8 +6,9 @@ CLI-shaped commands — there is no separate `update` command.
|
|
|
6
6
|
|
|
7
7
|
Status: `rest-api`, `native-api`, `mac-telnet`, and `ssh` are `CHR-passed` (see
|
|
8
8
|
`docs/MATRIX.md` and `commands/execute/examples.md`, examples 1–11 over REST,
|
|
9
|
-
12–
|
|
10
|
-
`bun run test:integration`)
|
|
9
|
+
12–19 over the native API, 20–22 over mac-telnet, and S1–S4 over ssh, green via
|
|
10
|
+
`bun run test:integration`), including multi-target fan-out (see **Target
|
|
11
|
+
selection**, examples F1–F5). `romon` and `winbox-terminal` remain
|
|
11
12
|
`not-started`. SNMP is retrieve-only and rejects `execute`.
|
|
12
13
|
|
|
13
14
|
## Intent
|
|
@@ -15,11 +16,17 @@ Status: `rest-api`, `native-api`, `mac-telnet`, and `ssh` are `CHR-passed` (see
|
|
|
15
16
|
- Mirror RouterOS `execute` semantics. Input is a CLI string; output is
|
|
16
17
|
console-shaped text (or structured records for path+verb writes) wrapped in
|
|
17
18
|
the standard envelope. This includes write-shaped add/set/remove.
|
|
18
|
-
- Validation is two-stage (see constitution: validation
|
|
19
|
-
"<cmd>"]`
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
- Validation is two-stage (see constitution: validation; grounded on CHR 7.23.3,
|
|
20
|
+
GH#230): a single `:put [:parse "<cmd>"]` covers both the syntax and the
|
|
21
|
+
unknown-attribute (name-level) gate on **every** transport — `:parse` never
|
|
22
|
+
throws; its return value's printed form (`(evl …)` / `(evl bad parameter …)` /
|
|
23
|
+
`(<%% bad command name …)`) is read from the REST/native `ret` *and* the
|
|
24
|
+
console output. That check is necessary, not sufficient: value-type errors pass
|
|
25
|
+
it, and the `/console/inspect` / server re-validation semantic checks are
|
|
26
|
+
separate.
|
|
27
|
+
The outer RouterOS string literal escapes backslash, quote, and dollar so
|
|
28
|
+
validation parses the caller's exact command rather than expanding variables
|
|
29
|
+
in the wrapper.
|
|
23
30
|
- centrs owns the **script-vs-structured gate** (`canonicalizeExecuteCommand` +
|
|
24
31
|
`isWriteShaped` in `src/execute.ts`): it decides which validation runs and
|
|
25
32
|
whether the write-confirm prompt fires. The shared `rosetta`/`lsp-routeros-ts`
|
|
@@ -101,24 +108,31 @@ Status: `rest-api`, `native-api`, `mac-telnet`, and `ssh` are `CHR-passed` (see
|
|
|
101
108
|
|
|
102
109
|
## Flags
|
|
103
110
|
|
|
104
|
-
|
|
111
|
+
Implemented flags are generated from the CLI metadata into
|
|
112
|
+
[`docs/CLI.md` → execute](../../docs/CLI.md#execute); this file does not
|
|
113
|
+
duplicate that table. Behavior notes the generated reference cannot carry:
|
|
114
|
+
|
|
115
|
+
- `--validate` is transport-appropriate but now one gate on every transport —
|
|
116
|
+
a single `:put [:parse ...]` that is read as a value (GH#230): REST/native
|
|
117
|
+
read the `ret` text (HTTP 200 / `as-string`); mac-telnet and ssh read the
|
|
118
|
+
console's printed value.
|
|
119
|
+
- `--format` defaults to `text`; `CENTRS_FORMAT` sets the default.
|
|
120
|
+
- `--quickchr` resolves host/port/auth from the live `@tikoci/quickchr`
|
|
121
|
+
(0.4.5+, optional dependency) descriptor, bypassing CDB/env resolution for
|
|
122
|
+
those fields.
|
|
123
|
+
- `--` — use it when the command contains tokens that would otherwise be
|
|
124
|
+
claimed as centrs flags; otherwise the command must be quoted as a single
|
|
125
|
+
argument. In fan-out, positional targets come BEFORE `--` and the command
|
|
126
|
+
AFTER it (see **Target selection**).
|
|
127
|
+
|
|
128
|
+
### Designed, not implemented
|
|
129
|
+
|
|
130
|
+
Spec-tier flags with no implementation yet — today they fail as unknown flags:
|
|
131
|
+
|
|
132
|
+
| Flag | Designed behavior |
|
|
105
133
|
| ------------------------------- | ------------------------------------------------------------------------------------- |
|
|
106
|
-
| `--via <protocol>` | Pin the transport (`native-api`, `rest-api`, `ssh`, or `mac-telnet`). No silent downgrade. A bare MAC target defaults to `mac-telnet`. |
|
|
107
|
-
| `--host <host\|url>` | Override the resolved host or base URL for the target. |
|
|
108
|
-
| `--port <n>` | Override the resolved management port. |
|
|
109
|
-
| `--username` (alias `--user`, `-u`) / `--password` | RouterOS credentials; fall back to `CENTRS_USERNAME` / `CENTRS_PASSWORD`. |
|
|
110
|
-
| `--ssh-key <path>` | `--via ssh`: explicit private-key path. Falls back to `CENTRS_SSH_KEY` / the ssh-agent. |
|
|
111
|
-
| `--insecure` | Accept a new SSH host key (`--via ssh`) or a self-signed `api-ssl` TLS cert. Default verifies. |
|
|
112
|
-
| `--cdb-file` / `--cdb-password` | Read target credentials from (and decrypt) a WinBox CDB file. |
|
|
113
|
-
| `--resolve <none\|arp>` | Resolve a MAC-address target to an IP via the host ARP cache. Default `none`. |
|
|
114
|
-
| `--timeout <duration>` | Per-request timeout. REST: ≤ 60s; other transports may allow longer. |
|
|
115
|
-
| `--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). |
|
|
116
134
|
| `--strict` | Treat any warning on a successful run as an error (`ok: false`, nonzero exit), like `-Werror`. Default is lenient (`ok: true` + `warnings`). |
|
|
117
|
-
| `--
|
|
118
|
-
| `--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`.) |
|
|
119
|
-
| `--format <text\|json\|yaml>` | Output format (alias `--json`). Defaults to `text`; `CENTRS_FORMAT` sets the default. |
|
|
120
|
-
| `--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. |
|
|
121
|
-
| `--` | 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`. 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. |
|
|
135
|
+
| `--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.) |
|
|
122
136
|
|
|
123
137
|
## SSH key selection
|
|
124
138
|
|
|
@@ -147,6 +161,39 @@ contents, and any inline key material are always sensitive and must be listed in
|
|
|
147
161
|
redacted only if the caller marks them sensitive or the error couples them with
|
|
148
162
|
key material.
|
|
149
163
|
|
|
164
|
+
## Target selection
|
|
165
|
+
|
|
166
|
+
`execute` runs the **same** RouterOS command across a multi-target selection over
|
|
167
|
+
the shared grammar (multiple `<target>` positionals, repeatable `--group`/
|
|
168
|
+
`--where`, the geo selectors `--near`/`--bbox`, `--all`, `--default`). The
|
|
169
|
+
grammar, the locked `FanoutData` envelope,
|
|
170
|
+
the record-order reassembly, and the granular **0/2/1 exit code** are normative in
|
|
171
|
+
[`docs/CONSTITUTION.md` → Target selection grammar](../../docs/CONSTITUTION.md#target-selection-grammar).
|
|
172
|
+
|
|
173
|
+
execute's positional **boundary is `--`** (it differs from `api`/`retrieve`,
|
|
174
|
+
whose last positional is the operation arg, because an execute command is itself
|
|
175
|
+
many tokens):
|
|
176
|
+
|
|
177
|
+
- `execute r1 r2 -- /system/reboot` — targets `[r1, r2]` before `--`, command
|
|
178
|
+
after. **More than one positional target requires `--`.**
|
|
179
|
+
- `execute --group prod /system/resource/print` — selector-only: targets come
|
|
180
|
+
from the selector and every positional is the command (no `--` needed).
|
|
181
|
+
- `execute r1 /system/resource/print` — a single positional target with no
|
|
182
|
+
selector stays **single-target** (legacy split: first positional is the target,
|
|
183
|
+
the rest is the command), never `FanoutData`.
|
|
184
|
+
- `execute --quickchr lab /system/resource/print` — the quickchr
|
|
185
|
+
named-live-provider claims the target slots like a selector (positionals are
|
|
186
|
+
the command; no `--` needed). One `--quickchr` stays single-target; repeating
|
|
187
|
+
it fans out; mixing it with positionals or CDB selectors is
|
|
188
|
+
`usage/conflicting-flags` (constitution: resolution providers).
|
|
189
|
+
|
|
190
|
+
Because `execute` is the primary **write** path, a write-shaped fan-out
|
|
191
|
+
(add/set/remove) is gated by **`--yes`**, confirmed **once** up front; without it
|
|
192
|
+
the error names the blast radius (how many routers). Write-ness is determined from
|
|
193
|
+
the command alone (`canonicalizeExecuteCommand` + `isWriteShaped`), so the gate
|
|
194
|
+
fires before any target is dialed. Per-target validation still runs because
|
|
195
|
+
RouterOS schemas can differ by version.
|
|
196
|
+
|
|
150
197
|
## mac-telnet L2 validation
|
|
151
198
|
|
|
152
199
|
Decided (2026-06-06): the real-L2 integration path is `@tikoci/quickchr`'s
|
|
@@ -162,7 +209,7 @@ macOS needs `SO_REUSEPORT`; mcast works on Linux/CI). Grounding: quickchr
|
|
|
162
209
|
|
|
163
210
|
`execute / mac-telnet` is now **`CHR-passed`** end to end over real L2 against
|
|
164
211
|
stock CHR 7.23.1 (`test/integration/mac-telnet-console.test.ts` +
|
|
165
|
-
`mactelnet-l2-bridge.ts`, examples
|
|
212
|
+
`mactelnet-l2-bridge.ts`, examples 20–22): `executeEnvelope` resolves a MAC
|
|
166
213
|
target, the `MacTelnetAdapter` opens a `MacTelnetConsole` over a UDP datagram
|
|
167
214
|
transport, and reads, writes (REST-verified), and validation-rejects all work.
|
|
168
215
|
|
|
@@ -178,7 +225,7 @@ How it is wired:
|
|
|
178
225
|
`transport/capability-unsupported` (mac-telnet is execute/terminal only).
|
|
179
226
|
- **Validation is one console `:parse`.** Over the console, `:put [:parse "<cmd>"]`
|
|
180
227
|
prints both `syntax error` and `bad parameter <name>`, so a single gate covers
|
|
181
|
-
syntax **and** the unknown-attribute (
|
|
228
|
+
syntax **and** the unknown-attribute (name-level) check — no `/console/inspect`
|
|
182
229
|
table parsing. Maps to `validation/syntax` / `validation/unknown-attribute`,
|
|
183
230
|
same codes as REST/native (see `docs/CONSTITUTION.md`, validation).
|
|
184
231
|
- **Auth (grounded):** MTWEI is required; a stock device offers a 16-byte MD5 salt
|
|
@@ -208,7 +255,7 @@ Protocol selection: an unresolved MAC target auto-selects mac-telnet (covered by
|
|
|
208
255
|
**decided:** `--strict` promotes any warning on an otherwise-successful run to
|
|
209
256
|
`ok: false` (nonzero exit), like `-Werror`; the default stays lenient
|
|
210
257
|
(`ok: true` with the warning in the `warnings` channel). Tracked code
|
|
211
|
-
follow-up; the flag is
|
|
258
|
+
follow-up; the flag is under **Designed, not implemented** above.
|
|
212
259
|
|
|
213
260
|
These are deferred refinements to the already-grounded single-shot envelope,
|
|
214
261
|
not blockers for the `rest-api`/`native-api` cells.
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Each numbered example is an executable spec, run against a CHR 7.23 router booted
|
|
4
4
|
by `@tikoci/quickchr`: `test/integration/execute.test.ts` covers examples 1–11
|
|
5
|
-
over REST and 12–
|
|
6
|
-
covers examples
|
|
5
|
+
over REST and 12–19 over the native API; `test/integration/mac-telnet-console.test.ts`
|
|
6
|
+
covers examples 20–22 over mac-telnet (real L2 via the `socket-connect` bridge).
|
|
7
7
|
If a line here is not exercised by a test, the test file is wrong; if a line
|
|
8
8
|
passes only with `validate=false`, the **implementation** is wrong (see
|
|
9
9
|
`docs/CONSTITUTION.md`).
|
|
@@ -62,14 +62,16 @@ 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
76
|
### 5. Syntax reject from `:put [:parse ...]`
|
|
75
77
|
|
|
@@ -95,7 +97,7 @@ centrs execute $R '/ip/address/add address=198.51.100.11/32 interface=ether1 no-
|
|
|
95
97
|
```
|
|
96
98
|
|
|
97
99
|
Envelope: `ok: false`, `error.code=validation/unknown-attribute`,
|
|
98
|
-
`error.
|
|
100
|
+
`error.context.parameter="no-such-arg"`,
|
|
99
101
|
`meta.validation.syntax=true`, `meta.validation.source=/console/inspect`, and
|
|
100
102
|
no address is added. This example is the product claim: syntax validation and
|
|
101
103
|
semantic validation are separate gates.
|
|
@@ -242,6 +244,23 @@ centrs execute $A '/ip/address/add address=198.51.100.23/32 interface=ether1 com
|
|
|
242
244
|
Envelope: `ok: false`, `error.code=usage/confirmation-required`,
|
|
243
245
|
`meta.via=native-api`, no validation command is sent, and no address is added.
|
|
244
246
|
|
|
247
|
+
### 19. Script-mode command runs through `/execute` with `as-string` (parity with REST)
|
|
248
|
+
|
|
249
|
+
A non-path-shaped console expression over native-api falls through to
|
|
250
|
+
`/execute` the same way REST does (example 4), instead of being refused.
|
|
251
|
+
`as-string` makes native `/execute` block and return the captured output
|
|
252
|
+
rather than scheduling a background job and returning only its id.
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
centrs execute $A ':put [/system/identity/get name]' --via native-api --port $API_PORT --username $U --password $P
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Envelope: `ok: true`, `data.ret` is a string containing the CHR identity (same
|
|
259
|
+
shape and cross-transport value as example 4 over REST), `meta.via=native-api`,
|
|
260
|
+
and `meta.validation.syntax=true`. Regression coverage for the bug where the
|
|
261
|
+
native adapter sent `/execute` without `as-string`, silently discarding
|
|
262
|
+
captured output.
|
|
263
|
+
|
|
245
264
|
## mac-telnet (`--via mac-telnet`)
|
|
246
265
|
|
|
247
266
|
Layer-2 execute over the RouterOS interactive console (UDP/20561). `$MAC` is the
|
|
@@ -252,11 +271,11 @@ is a **single console `:put [:parse …]`** — it reports both `syntax error` a
|
|
|
252
271
|
at once (no `/console/inspect` table parsing). The console opens through a
|
|
253
272
|
first-login license screen (auto-answered) and a ~10s terminal-negotiation stall;
|
|
254
273
|
**a successful write prints nothing** over the console (no `.id`/`ret`, unlike
|
|
255
|
-
REST/native). Examples
|
|
274
|
+
REST/native). Examples 20–22 are green via `bun run test:integration`
|
|
256
275
|
(`test/integration/mac-telnet-console.test.ts`, CHR 7.23.1) over the real L2
|
|
257
276
|
bridge; the console reader is also exercised directly in the same test.
|
|
258
277
|
|
|
259
|
-
###
|
|
278
|
+
### 20. Read a command over mac-telnet
|
|
260
279
|
|
|
261
280
|
```bash
|
|
262
281
|
centrs execute $MAC '/system/identity/print' --via mac-telnet --host $MT_HOST --port $MT_PORT --username $U --password $P
|
|
@@ -266,7 +285,7 @@ Envelope: `ok: true`, `meta.via=mac-telnet`, `data.ret` is the console output an
|
|
|
266
285
|
contains the device identity (cross-checked against REST). `meta.validation.source`
|
|
267
286
|
is `:put [:parse ...] over mac-telnet`.
|
|
268
287
|
|
|
269
|
-
###
|
|
288
|
+
### 21. Write (add) over mac-telnet
|
|
270
289
|
|
|
271
290
|
```bash
|
|
272
291
|
centrs execute $MAC '/ip/address/add address=198.51.100.40/32 interface=ether1' --via mac-telnet --host $MT_HOST --port $MT_PORT --username $U --password $P --yes
|
|
@@ -276,7 +295,7 @@ Envelope: `ok: true`, `meta.via=mac-telnet`, `data.ret` is empty (a successful
|
|
|
276
295
|
console write prints nothing — there is no `.id` to return). A subsequent REST
|
|
277
296
|
read of `/ip/address` shows `198.51.100.40`.
|
|
278
297
|
|
|
279
|
-
###
|
|
298
|
+
### 22. Validation rejects an unknown attribute over mac-telnet
|
|
280
299
|
|
|
281
300
|
```bash
|
|
282
301
|
centrs execute $MAC '/ip/address/add address=198.51.100.41/32 interface=ether1 no-such-arg=x' --via mac-telnet --host $MT_HOST --port $MT_PORT --username $U --password $P --yes
|
|
@@ -337,6 +356,73 @@ centrs execute 127.0.0.1 '/ip/address/add address=198.51.100.51/32 interface=eth
|
|
|
337
356
|
Envelope: `ok: false`, `error.code=validation/unknown-attribute`, `meta.via=ssh`,
|
|
338
357
|
and no address is added — the console `:parse` gate catches `bogus` pre-mutation.
|
|
339
358
|
|
|
359
|
+
## Target selection (fan-out)
|
|
360
|
+
|
|
361
|
+
These exercise the shared target-selection grammar
|
|
362
|
+
([`docs/CONSTITUTION.md` → Target selection grammar](../../docs/CONSTITUTION.md#target-selection-grammar)),
|
|
363
|
+
run by `test/integration/execute-fanout.test.ts` when CHR integration is enabled.
|
|
364
|
+
`$CDB` has two records in group `fanout-chr`: record 0 is the live CHR (comment
|
|
365
|
+
fact `role=edge`) and record 1 is an unreachable REST URL (`role=core`). Note the
|
|
366
|
+
execute `--` boundary: targets/selectors come before it, the command after.
|
|
367
|
+
|
|
368
|
+
### F1. Group fan-out of a read command
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
centrs execute --group fanout-chr --via rest-api --cdb-file $CDB --json -- /system/resource/print
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
`ok: true`, `data.summary = { total: 2, ok: 1, failed: 1 }`, targets ordered by
|
|
375
|
+
`recordIndex`, `meta.operation.kind = fanout`; the unreachable target is an inner
|
|
376
|
+
`ok: false` (`transport/connection-refused`). Process exit `2`.
|
|
377
|
+
|
|
378
|
+
### F2. Empty / unknown group
|
|
379
|
+
|
|
380
|
+
```bash
|
|
381
|
+
centrs execute --group no-such-group --via rest-api --cdb-file $CDB --json -- /system/resource/print
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
`ok: true`, `data.summary = { total: 0, ok: 0, failed: 0 }`,
|
|
385
|
+
`warnings` include `cdb/empty-group`. Exit `0`.
|
|
386
|
+
|
|
387
|
+
### F3. `--where` device-class selector (subset)
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
centrs execute --where role=edge --via rest-api --cdb-file $CDB --json -- /system/resource/print
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Selects only record 0: `data.summary = { total: 1, ok: 1, failed: 0 }`,
|
|
394
|
+
`meta.operation.selection.where = ["role=edge"]`, exit `0`.
|
|
395
|
+
|
|
396
|
+
### F4. `--all` (every CDB record)
|
|
397
|
+
|
|
398
|
+
```bash
|
|
399
|
+
centrs execute --all --via rest-api --cdb-file $CDB --json -- /system/resource/print
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
`data.summary = { total: 2, ok: 1, failed: 1 }`,
|
|
403
|
+
`meta.operation.selection.all = true`, exit `2`.
|
|
404
|
+
|
|
405
|
+
### F5. Write-shaped fan-out without `--yes` is refused (blast radius)
|
|
406
|
+
|
|
407
|
+
```bash
|
|
408
|
+
centrs execute --group fanout-chr --via rest-api --cdb-file $CDB --json -- /system/identity/set name=fanout-blast
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
A write-shaped fan-out is gated once up front: `ok: false`,
|
|
412
|
+
`error.code = usage/confirmation-required`, the summary names the blast radius
|
|
413
|
+
(`2 router(s)`), exit `1`. Nothing is mutated. Add `--yes` to fan the write out.
|
|
414
|
+
|
|
415
|
+
### F6. Multiple positional targets (ad-hoc literals) before `--`
|
|
416
|
+
|
|
417
|
+
```bash
|
|
418
|
+
centrs execute $REACHABLE_URL $UNREACHABLE_URL --via rest-api --username $U --password $P --json -- /system/resource/print
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
More than one positional target is fan-out mode (the `--` separates them from the
|
|
422
|
+
command). With no `--cdb-file`, both are ad-hoc literals, labeled by
|
|
423
|
+
`meta.target.input` with no `recordIndex`: `data.summary = { total: 2, ok: 1,
|
|
424
|
+
failed: 1 }`, exit `2`.
|
|
425
|
+
|
|
340
426
|
## Protocol selection notes
|
|
341
427
|
|
|
342
428
|
For an unresolved MAC target, execute auto-selection defaults to mac-telnet
|
|
@@ -351,3 +437,39 @@ Envelope: `meta.via=mac-telnet` when `$MAC` is not resolved from CDB and no
|
|
|
351
437
|
`--host`/`--port`). If the caller wants IP-level REST or native API execution,
|
|
352
438
|
they must opt into MAC-to-IP resolution (`--resolve arp` + `--via …`) before
|
|
353
439
|
protocol selection.
|
|
440
|
+
|
|
441
|
+
## quickchr targets (#134)
|
|
442
|
+
|
|
443
|
+
`$NAME` is the machine name of a running quickchr-managed CHR. Host/port/auth
|
|
444
|
+
come from the live descriptor — the CDB, `CENTRS_HOST`-style env, and the
|
|
445
|
+
`__default__` ladder are bypassed for those fields. Covered by
|
|
446
|
+
`test/integration/quickchr-target.test.ts`.
|
|
447
|
+
|
|
448
|
+
### Q1. Run a command on a quickchr target (native API default)
|
|
449
|
+
|
|
450
|
+
```bash
|
|
451
|
+
centrs execute --quickchr $NAME ':put [/system/identity/get name]' --json
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
Envelope: `ok: true`, `meta.via=native-api` (execute's normal default),
|
|
455
|
+
`meta.target.source.kind=provider`, `meta.target.identity=$NAME`.
|
|
456
|
+
|
|
457
|
+
### Q2. A `--via` the descriptor does not forward → `quickchr/unsupported-via`
|
|
458
|
+
|
|
459
|
+
```bash
|
|
460
|
+
centrs execute --quickchr $NAME ':put 1' --via mac-telnet --json
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
Envelope: `ok: false`, `error.code=quickchr/unsupported-via` — quickchr v1
|
|
464
|
+
forwards `rest-api`/`native-api`/`ssh` only; centrs never falls back to another
|
|
465
|
+
transport.
|
|
466
|
+
|
|
467
|
+
### Q3. Repeated `--quickchr` fans out; an unknown machine is an inner failure
|
|
468
|
+
|
|
469
|
+
```bash
|
|
470
|
+
centrs execute --quickchr $NAME --quickchr no-such-machine -- ':put 1' --json
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
Envelope: outer `ok: true`, `data.summary = { total: 2, ok: 1, failed: 1 }`;
|
|
474
|
+
the unknown machine's inner error is `quickchr/machine-not-found`. Exit code 2
|
|
475
|
+
(partial).
|