@tikoci/centrs 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/GLOSSARY.txt +71 -0
- package/README.md +24 -8
- package/SECURITY.md +3 -3
- package/commands/api/README.md +27 -9
- package/commands/api/examples.md +109 -6
- package/commands/check/README.md +371 -53
- package/commands/check/examples.md +390 -0
- package/commands/devices/README.md +177 -37
- package/commands/devices/examples.md +148 -0
- package/commands/execute/README.md +39 -4
- package/commands/execute/examples.md +90 -6
- package/commands/retrieve/README.md +36 -50
- package/commands/retrieve/examples.md +40 -9
- package/commands/settings/README.md +286 -22
- package/commands/settings/examples.md +408 -0
- package/commands/terminal/README.md +6 -0
- package/commands/transfer/README.md +32 -2
- package/commands/transfer/examples.md +66 -0
- package/docs/CONSTITUTION.md +36 -11
- package/docs/MATRIX.md +68 -172
- package/docs/errors/README.md +2 -1
- package/docs/errors/cdb/empty-selection.md +19 -0
- package/docs/errors/input/incomplete-gps.md +9 -0
- package/docs/errors/input/invalid-altitude.md +9 -0
- package/docs/errors/input/invalid-bbox.md +9 -0
- package/docs/errors/input/invalid-coordinate.md +9 -0
- package/docs/errors/input/invalid-radius.md +9 -0
- package/docs/errors/internal/settings-failed.md +9 -0
- package/docs/errors/settings/consequential-value.md +26 -0
- package/docs/errors/settings/invalid-boolean.md +21 -3
- package/docs/errors/settings/invalid-format.md +18 -3
- package/docs/errors/settings/invalid-integer.md +18 -3
- package/docs/errors/settings/invalid-timeout.md +20 -3
- package/docs/errors/settings/invalid-via.md +22 -3
- package/docs/errors/settings/reserved-key.md +29 -0
- package/docs/errors/settings/skip-env-file-active.md +25 -0
- package/docs/errors/settings/unknown-key.md +25 -0
- package/docs/errors/usage/fanout-not-supported.md +16 -0
- package/package.json +6 -6
- package/src/api-fanout.ts +454 -0
- package/src/api.ts +91 -19
- package/src/cli/api.ts +135 -11
- package/src/cli/btest.ts +20 -2
- package/src/cli/common.ts +11 -0
- package/src/cli/devices.ts +235 -6
- package/src/cli/discover.ts +3 -1
- package/src/cli/execute.ts +107 -6
- package/src/cli/mcp.ts +8 -1
- package/src/cli/retrieve.ts +135 -90
- package/src/cli/selection.ts +200 -0
- package/src/cli/settings.ts +286 -0
- package/src/cli/terminal.ts +22 -7
- package/src/cli/transfer.ts +171 -47
- package/src/cli.ts +7 -0
- package/src/core/error-catalog.ts +55 -0
- package/src/core/fanout.ts +143 -2
- package/src/devices.ts +212 -20
- package/src/discover.ts +7 -2
- package/src/execute-fanout.ts +438 -0
- package/src/execute.ts +120 -23
- package/src/index.ts +105 -1
- package/src/mcp/config.ts +12 -2
- package/src/mcp/tools.ts +137 -2
- package/src/protocols/adapter.ts +38 -18
- package/src/protocols/index.ts +11 -5
- package/src/resolver/cdb.ts +10 -2
- package/src/resolver/comment-kv.ts +58 -4
- package/src/resolver/config-file.ts +136 -0
- package/src/resolver/facts.ts +78 -0
- package/src/resolver/geo.ts +453 -0
- package/src/resolver/index.ts +46 -0
- package/src/resolver/mac.ts +12 -3
- package/src/resolver/selection.ts +304 -0
- package/src/resolver/settings.ts +54 -5
- package/src/resolver/target.ts +20 -2
- package/src/retrieve-fanout.ts +225 -133
- package/src/retrieve.ts +39 -7
- package/src/settings.ts +1078 -0
- package/src/terminal.ts +11 -1
- package/src/transfer-fanout.ts +527 -0
- package/src/transfer.ts +61 -11
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,65 @@ documenting cross-cutting shifts that affect contributors and consumers.
|
|
|
8
8
|
|
|
9
9
|
## Unreleased
|
|
10
10
|
|
|
11
|
+
## 0.1.3 — 2026-07-06
|
|
12
|
+
|
|
13
|
+
Patch release. `@tikoci/centrs@0.1.3`.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **`api` command over rest-api + native-api is `CHR-passed`.** `centrs api`
|
|
18
|
+
brings a `gh api`-style structured passthrough to both transports, and folds
|
|
19
|
+
the old standalone `stream` command into `api --stream` (native-api only,
|
|
20
|
+
`--listen` alias): a `NativeApiSession.listen()` reader with `/cancel`
|
|
21
|
+
support emits NDJSON frames plus a run summary. Grounded against real CHR
|
|
22
|
+
7.23.1.
|
|
23
|
+
- **Multi-target fan-out is uniform across `api`, `retrieve`, `execute`, and
|
|
24
|
+
`transfer`.** A shared `expandCdbSelection` + `runFanout` + `fanoutExitCode`
|
|
25
|
+
core (extracted once, consumed by all four commands) lets any command take a
|
|
26
|
+
CDB selector and run over every matching device. The positional boundary
|
|
27
|
+
that ends the selector differs per command (final positional for
|
|
28
|
+
`api`/`retrieve`, `--` for `execute`, the verb keyword for `transfer`);
|
|
29
|
+
`terminal` and `api --stream` explicitly reject `N > 1` selections since
|
|
30
|
+
neither has a sane multi-target framing.
|
|
31
|
+
- **`settings` command (`print` / `get` / `set` / `reset`).** A managed-key
|
|
32
|
+
registry for the real `CENTRS_*` environment keys backs an atomic
|
|
33
|
+
`centrs.env` writer: `set` validates per key, canonicalizes booleans to
|
|
34
|
+
`1`/`0` on disk, warns (never blocks) on consequential values
|
|
35
|
+
(`insecure=true`, `transfer-via=ftp`), and never partially writes on a
|
|
36
|
+
validation failure; `reset` deletes the managed line rather than blanking it
|
|
37
|
+
(a blank value would itself mean "set to empty" for a bash-sourced file) and
|
|
38
|
+
leaves foreign lines/comments untouched. Bare `centrs settings` behaves like
|
|
39
|
+
`print` regardless of TTY. Four new error/warning codes
|
|
40
|
+
(`settings/reserved-key`, `settings/unknown-key`,
|
|
41
|
+
`settings/consequential-value`, `settings/skip-env-file-active`) plus
|
|
42
|
+
`internal/settings-failed`, all cataloged. `CHR-passed` via 32
|
|
43
|
+
fixture-backed integration examples.
|
|
44
|
+
- **Device GPS: lat/lon/altitude storage + `--near` / `--bbox` geo
|
|
45
|
+
predicates.** `centrs devices` now stores GPS coordinates per device
|
|
46
|
+
(lat-first ordering, chosen so agents guess the field order correctly) and
|
|
47
|
+
supports proximity
|
|
48
|
+
(`--near`) and bounding-box (`--bbox`) selection predicates. `CHR-passed`
|
|
49
|
+
via fixture-backed integration coverage.
|
|
50
|
+
- **Registry⇄MATRIX drift guard + "did you mean?" flag errors.** A CI-checked
|
|
51
|
+
guard keeps the command/protocol registry and `docs/MATRIX.md` from
|
|
52
|
+
silently drifting apart; unrecognized CLI flags now suggest the closest
|
|
53
|
+
known flag instead of a bare rejection.
|
|
54
|
+
- **GitHub Pages: published API reference + error catalog.** `typedoc` output
|
|
55
|
+
and the generated error-code catalog now publish to GitHub Pages on push.
|
|
56
|
+
|
|
57
|
+
### Fixed
|
|
58
|
+
|
|
59
|
+
- **Windows unit-test portability for `settings` and MNDP.** Path-separator
|
|
60
|
+
and related platform assumptions in the `settings` and MNDP unit suites are
|
|
61
|
+
now Windows-safe (#170).
|
|
62
|
+
- **A real CodeQL-caught ReDoS in the device GPS geo-predicate parser**, fixed
|
|
63
|
+
rather than suppressed.
|
|
64
|
+
|
|
65
|
+
### Changed
|
|
66
|
+
|
|
67
|
+
- **CodeQL house-rules pack promoted to the default run**, alongside CodeRabbit
|
|
68
|
+
review-tone tuning and a reduced GitHub-checks timeout (15 → 7 minutes).
|
|
69
|
+
|
|
11
70
|
## 0.1.2 — 2026-06-26
|
|
12
71
|
|
|
13
72
|
Patch release. `@tikoci/centrs@0.1.2`.
|
package/GLOSSARY.txt
CHANGED
|
@@ -139,6 +139,7 @@ MNDP
|
|
|
139
139
|
NAND # NAND flash memory; write-back caches file writes up to ~40s on some MikroTik boards
|
|
140
140
|
netmiko # [Multi-vendor library to simplify Paramiko SSH connections to network devices](https://github.com/ktbyers/netmiko)
|
|
141
141
|
netwatch
|
|
142
|
+
nullglob # bash shopt that makes an unmatched glob expand to zero words instead of the literal pattern
|
|
142
143
|
npk # MikroTik package format
|
|
143
144
|
nvme-over-tcp # supported by RouterOS **with** rose-storage.npk extra-package
|
|
144
145
|
OID
|
|
@@ -148,6 +149,7 @@ PRGA
|
|
|
148
149
|
proplist
|
|
149
150
|
pywinauto
|
|
150
151
|
QML
|
|
152
|
+
qll # CodeQL library file extension (.qll), e.g. CleartextLoggingCustomizations.qll
|
|
151
153
|
qmljsdebugger
|
|
152
154
|
quickchr
|
|
153
155
|
RAML
|
|
@@ -161,12 +163,15 @@ rosetta
|
|
|
161
163
|
routeros
|
|
162
164
|
routeros-skills
|
|
163
165
|
rsc # RouterOS script / export file extension (.rsc)
|
|
166
|
+
SARIF # Static Analysis Results Interchange Format; CodeQL's code-scanning analysis download format
|
|
164
167
|
scp
|
|
165
168
|
scriptability
|
|
166
169
|
scriptable
|
|
170
|
+
shopt # bash builtin for toggling shell options (e.g. nullglob)
|
|
167
171
|
Sikuli
|
|
168
172
|
SikuliX
|
|
169
173
|
skillcheck
|
|
174
|
+
startswith # jq builtin filter that tests whether a string begins with a given prefix
|
|
170
175
|
SNMP
|
|
171
176
|
SPI
|
|
172
177
|
SRP
|
|
@@ -184,6 +189,7 @@ TIKOCI
|
|
|
184
189
|
tiktui
|
|
185
190
|
TLV
|
|
186
191
|
traceroute
|
|
192
|
+
tostring # jq builtin filter that renders a value as its JSON string representation
|
|
187
193
|
tsc
|
|
188
194
|
tzsp
|
|
189
195
|
TZSP
|
|
@@ -434,3 +440,68 @@ toarray
|
|
|
434
440
|
# Logic shorthand ("if and only if") used in api write-gate doc comments; cspell's
|
|
435
441
|
# markdown dictionary knows it but the TypeScript one does not.
|
|
436
442
|
iff
|
|
443
|
+
|
|
444
|
+
# GitHub Pages build vocabulary (scripts/build-pages-site.ts, pages.yaml)
|
|
445
|
+
# typedoc/pandoc: the API-reference generator and the markdown->HTML converter
|
|
446
|
+
# the Pages build chains together. gfm: GitHub-Flavored Markdown, pandoc's
|
|
447
|
+
# input format for the error pages. larr: the "←" left-arrow HTML entity
|
|
448
|
+
# in the back-to-docs link. nojekyll: the marker file that disables GitHub
|
|
449
|
+
# Pages' Jekyll processing for an Actions-deployed artifact. extensionless:
|
|
450
|
+
# describes the `details_url` contract (docs/errors/README.md) — no `.html`.
|
|
451
|
+
typedoc
|
|
452
|
+
pandoc
|
|
453
|
+
gfm
|
|
454
|
+
larr
|
|
455
|
+
nojekyll
|
|
456
|
+
extensionless
|
|
457
|
+
# nothrow: Bun Shell's `$` method to disable throwing on a non-zero exit
|
|
458
|
+
# (scripts/agent-check.ts), so the caller can inspect output either way.
|
|
459
|
+
nothrow
|
|
460
|
+
|
|
461
|
+
# check-command spec vocabulary (commands/check/README.md, examples.md)
|
|
462
|
+
# TikTOML: centrs-owned readable device-inventory format (#137); check's
|
|
463
|
+
# metadata feeds it. routerboard: RouterBOARD firmware/hardware menu
|
|
464
|
+
# (/system/routerboard). rscmd: the tikbook RouterOS command-notebook file
|
|
465
|
+
# extension (.rscmd), the format the check spec was grounded against.
|
|
466
|
+
TikTOML
|
|
467
|
+
routerboard
|
|
468
|
+
rscmd
|
|
469
|
+
# RTT: round-trip time, the host->router connect-latency reach signal. tojson: the
|
|
470
|
+
# /system/script helper (:serialize to=json) the batched execute reads reuse.
|
|
471
|
+
# qrcode: the /ip/cloud vpn-wireguard-client-config-qrcode field token (redacted
|
|
472
|
+
# by the cloud check, referenced when documenting redaction).
|
|
473
|
+
RTT
|
|
474
|
+
tojson
|
|
475
|
+
qrcode
|
|
476
|
+
|
|
477
|
+
# Device GPS vocabulary (issue #146, commands/devices/README.md "Location /
|
|
478
|
+
# GPS", src/resolver/geo.ts). MSL/AGL: the two supported altitude-type vertical
|
|
479
|
+
# datums (mean sea level / above ground level); stored case-insensitively but
|
|
480
|
+
# normalized to upper case. EPSG: the geodetic coordinate-reference-system
|
|
481
|
+
# registry (EPSG:4326 = WGS84 lat/lon), cited for the lat-first ordering
|
|
482
|
+
# rationale. bbox: the --bbox geo selector (bounding box). wgs: WGS84, the
|
|
483
|
+
# geodetic datum GPS coordinates use; appears only as a deliberately-invalid
|
|
484
|
+
# --altitude-type value in tests/examples. antimeridian: the 180° meridian a
|
|
485
|
+
# --bbox may not cross in v1. GEOSEARCH: the Redis command whose m/km/mi/ft
|
|
486
|
+
# radius-unit vocabulary --near borrows. IUGG: the geodesy union whose mean
|
|
487
|
+
# Earth radius the haversine uses. SFO/OAK: airports used as a known-distance
|
|
488
|
+
# haversine fixture in tests. ele/GPX: `ele` is GPX's altitude element, kept as
|
|
489
|
+
# an input alias for `altitude` (GPX = the GPS Exchange Format). Geolocation:
|
|
490
|
+
# the W3C Geolocation API, cited (with ISO 6709 / EPSG / IETF geo:) for why
|
|
491
|
+
# `altitude` — not `elevation` — is the canonical field name.
|
|
492
|
+
MSL
|
|
493
|
+
msl
|
|
494
|
+
AGL
|
|
495
|
+
agl
|
|
496
|
+
Agl
|
|
497
|
+
EPSG
|
|
498
|
+
bbox
|
|
499
|
+
wgs
|
|
500
|
+
antimeridian
|
|
501
|
+
GEOSEARCH
|
|
502
|
+
IUGG
|
|
503
|
+
SFO
|
|
504
|
+
sfo
|
|
505
|
+
ele
|
|
506
|
+
GPX
|
|
507
|
+
Geolocation
|
package/README.md
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
CLI (with MCP, TUI, and HTTP proxy frontends planned) for talking to MikroTik
|
|
5
5
|
RouterOS devices through a regularized, validated interface.
|
|
6
6
|
|
|
7
|
+
Preview builds are published on npm. To inspect the CLI without installing it:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bunx @tikoci/centrs --help
|
|
11
|
+
```
|
|
12
|
+
|
|
7
13
|
It is a **friendly conduit**, not a high-level configuration abstraction. It
|
|
8
14
|
helps humans and agents reach RouterOS over the right protocol, with the right
|
|
9
15
|
credentials and ports, and validates RouterOS-shaped commands before running
|
|
@@ -11,9 +17,12 @@ them. It does **not** hide RouterOS behind helpers like `createVlanOnBridge()`
|
|
|
11
17
|
— you still speak RouterOS. Validation and structured diagnostics are the
|
|
12
18
|
product; without them this would just be a worse `curl`.
|
|
13
19
|
|
|
14
|
-
> **Status:** early
|
|
15
|
-
>
|
|
16
|
-
>
|
|
20
|
+
> **Status:** early npm preview, active development. `@tikoci/centrs` is
|
|
21
|
+
> published on npm, but command flags and envelopes may still change before a
|
|
22
|
+
> stable release. Check current dist-tags with
|
|
23
|
+
> `npm view @tikoci/centrs dist-tags --json`. The library, device registry, and
|
|
24
|
+
> first MCP server phases are CHR-verified; the CLI is wired (`coded`);
|
|
25
|
+
> encrypted-CDB writes are still blocked, and the TUI/proxy frontends are planned.
|
|
17
26
|
> `docs/MATRIX.md` is the single source of truth for what works today — treat
|
|
18
27
|
> anything not green there as not-yet-shipped.
|
|
19
28
|
|
|
@@ -104,16 +113,23 @@ CDB tags, and `devices set` refuses to put them there. Unknown keys produce a
|
|
|
104
113
|
`cdb/unknown-option` warning and are ignored, so a stray `=` in human prose
|
|
105
114
|
never breaks anything.
|
|
106
115
|
|
|
107
|
-
Settings resolve lowest-to-highest: **built-in defaults →
|
|
108
|
-
comment-kv → `CENTRS_*` env → CLI flag / API arg.** Every
|
|
109
|
-
reports which source won each setting under `meta.settings`,
|
|
110
|
-
see *why* a given port or transport was chosen.
|
|
116
|
+
Settings resolve lowest-to-highest: **built-in defaults → user config file
|
|
117
|
+
(`centrs.env`) → comment-kv → `CENTRS_*` env → CLI flag / API arg.** Every
|
|
118
|
+
result envelope reports which source won each setting under `meta.settings`,
|
|
119
|
+
so you can always see *why* a given port or transport was chosen.
|
|
111
120
|
|
|
112
121
|
### Groups, discovery, and the MCP allowlist all ride on the CDB
|
|
113
122
|
|
|
114
123
|
- **Groups** are CDB-native: an entry's `group` field is just a string, and
|
|
115
124
|
`--group prod-edge` fans a single command out to every entry that shares it.
|
|
116
|
-
There is no separate group-definition file.
|
|
125
|
+
There is no separate group-definition file. Groups are one selector in a
|
|
126
|
+
**uniform fan-out grammar** shared by `api`, `retrieve`, `execute`, and
|
|
127
|
+
`transfer`: mix `<router>` positionals, repeatable `--group`/`--where
|
|
128
|
+
<attr>=<value>`, `--all`, and `--default` (de-duped by CDB record index). Every
|
|
129
|
+
fan-out returns the locked `FanoutData` envelope (per-target results, not
|
|
130
|
+
metadata) with a granular exit code (`0` all ok / `2` partial / `1` all failed);
|
|
131
|
+
a multi-target write is gated once by `--yes` naming the blast radius. See
|
|
132
|
+
`docs/CONSTITUTION.md` (Target selection grammar).
|
|
117
133
|
- **Discovery** writes back into the CDB: `discover --save` records MNDP
|
|
118
134
|
neighbors as new entries tagged `group=discovered` with a `source=mndp`
|
|
119
135
|
marker — hints to curate, not authoritative inventory.
|
package/SECURITY.md
CHANGED
|
@@ -14,14 +14,14 @@ Please include the affected files or workflow, reproduction details, and impact.
|
|
|
14
14
|
|
|
15
15
|
The repository's [Security tab](https://github.com/tikoci/centrs/security) is the live source of current alerts and advisories. This section describes *what* checks run and *why*, so the doc stays meaningful even when the badge is at 0.
|
|
16
16
|
|
|
17
|
-
- **CodeQL** — repo-managed workflow at [`.github/workflows/codeql.yaml`](.github/workflows/codeql.yaml) with config [`.github/codeql-config.yml`](.github/codeql-config.yml). Query suite: `security-and-quality` (security-extended + code-quality). Languages: `javascript-typescript`, `actions`. Schedule: push to `main`, pull requests to `main`, weekly cron, and manual dispatch.
|
|
18
|
-
- **Code Quality (AI findings, preview)** —
|
|
17
|
+
- **CodeQL** — repo-managed workflow at [`.github/workflows/codeql.yaml`](.github/workflows/codeql.yaml) with config [`.github/codeql-config.yml`](.github/codeql-config.yml). Query suite: `security-and-quality` (security-extended + code-quality) plus a small custom pack, [`.github/codeql-house-rules/`](.github/codeql-house-rules/), that structurally checks a handful of repo-specific invariants (currently: `apiFanout` must call its named protocol/validation gates) — see `.github/instructions/github-security-quality.instructions.md` for scope and the verify-before-promote discipline for adding more rules there. Languages: `javascript-typescript`, `actions`. Schedule: push to `main`, pull requests to `main`, weekly cron, and manual dispatch.
|
|
18
|
+
- **Code Quality (AI findings, preview)** — confirmed active in GitHub as of 2026-07-03 (not just "intended"; see [issue #166](https://github.com/tikoci/centrs/issues/166)). [`.github/workflows/codeql.yaml`](.github/workflows/codeql.yaml) includes a non-failing forward-compat probe for a future AI-findings API. AI findings are noisy and self-contradicting; we accept the noise because the second-opinion catches real issues that the static suite misses. Steady-state goal is 0 open findings. False positives are dismissed via the GitHub UI with a written justification — that text is the audit-log contract. Side effect: GitHub's own Code Quality scan runs in parallel with our Advanced Setup and posts a same-named `Analyze (javascript-typescript)` check, so PR checks lists show it twice — cosmetic, tracked in #166, not a required check either way (see issue #118).
|
|
19
19
|
- **Dependency review** — not enabled. GitHub dependency review follows dependency-graph ecosystem support, and this Bun-only repository currently does not have a supported lockfile for that gate.
|
|
20
20
|
- **Dependabot security updates** — enabled and configured in [`.github/dependabot.yaml`](.github/dependabot.yaml).
|
|
21
21
|
- **Secret scanning** — enabled in GitHub, with push protection.
|
|
22
22
|
- **Private vulnerability reporting** — enabled via GitHub Security Advisories.
|
|
23
23
|
|
|
24
|
-
Some GitHub Security tab data, especially Code Quality AI findings, is not
|
|
24
|
+
Some GitHub Security tab data, especially Code Quality AI findings, is not reachable through the default workflow `GITHUB_TOKEN` (`repos/{owner}/{repo}/code-quality/findings` 403s there even with `security-events: read`) — it does respond with an elevated/owner-scoped token, but no findings have been recorded yet to confirm the response shape end-to-end (see #166). Human review in the GitHub UI remains part of the security workflow until that's wired up.
|
|
25
25
|
|
|
26
26
|
## Supported versions
|
|
27
27
|
|
package/commands/api/README.md
CHANGED
|
@@ -6,17 +6,18 @@ CDB / env / flags, validates the request through `/console/inspect`, runs the
|
|
|
6
6
|
single REST or native-API operation, and returns the result in the standard
|
|
7
7
|
envelope.
|
|
8
8
|
|
|
9
|
-
Status: `CHR-passed` over `rest-api` and `native-api
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
Status: `CHR-passed` over `rest-api` and `native-api`, including multi-target
|
|
10
|
+
fan-out and open-ended `--stream` follow (native-api only), per
|
|
11
|
+
`docs/MATRIX.md`. This file describes intent and flags; the matrix holds the
|
|
12
|
+
cell states. Load-bearing rules — envelope, errors, settings precedence,
|
|
13
|
+
identity, validation, protocol selection — live in
|
|
14
14
|
[`docs/CONSTITUTION.md`](../../docs/CONSTITUTION.md); the `(constitution: …)`
|
|
15
15
|
notes below point there rather than restating them.
|
|
16
16
|
|
|
17
17
|
`api` absorbs the former `stream` command: open-ended follow is `api <router>
|
|
18
18
|
<endpoint> --stream` (or the `/listen` endpoint form). `--listen` is an accepted
|
|
19
|
-
alias of `--stream`.
|
|
19
|
+
alias of `--stream`. Streaming is single-session — it cannot combine with
|
|
20
|
+
multi-target fan-out (see below).
|
|
20
21
|
|
|
21
22
|
## Where `api` sits — the verb trichotomy
|
|
22
23
|
|
|
@@ -72,11 +73,28 @@ when a bare-collection `POST` carries create-looking fields.
|
|
|
72
73
|
| `--validate[=false]` | Default `true`. Gate is `/console/inspect` (see Validation). |
|
|
73
74
|
| `--via <protocol>` | `rest-api` (default) or `native-api`. No silent downgrade. |
|
|
74
75
|
| `--format <json\|yaml\|text>` | Output format. Defaults to `json` for `api` (machine-first); `CENTRS_FORMAT` overrides. Under `--stream`, `json`/`yaml` emit one compact envelope per line (NDJSON); `text` emits a concise row per frame. |
|
|
76
|
+
| `--group <name>` / `--where <attr>=<value>` / `--near <lat>,<lon>,<radius>` / `--bbox <south>,<west>,<north>,<east>` / `--all` / `--default` / `--concurrency <n>` | Multi-target fan-out (see below). Repeatable `--group`/`--where`; `--near`/`--bbox` select by device GPS (lat-first; see constitution: target selection). The union de-dupes by CDB record index. |
|
|
75
77
|
| target/auth | `--host`, `--port`, `--username`/`--user`/`-u`, `--password`, `--insecure`, `--timeout`, `--cdb-file`, `--cdb-password`, `--resolve <none\|arp>` — same single-target resolver as `retrieve`/`execute`. |
|
|
76
78
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
## Fan-out (multi-target)
|
|
80
|
+
|
|
81
|
+
Selecting more than one router — any selector flag (`--group` / `--where` /
|
|
82
|
+
`--near` / `--bbox` / `--all` / `--default`) or more than one positional target — switches `api` into
|
|
83
|
+
**fan-out mode** (`src/api-fanout.ts`, on the shared `src/core/fanout.ts` engine
|
|
84
|
+
and the `src/resolver/selection.ts` grammar). A plain single-positional call
|
|
85
|
+
stays the single-target envelope. Output is the locked `FanoutData` envelope
|
|
86
|
+
(`data = { summary, targets[] }`; outer `ok` = orchestration success; per-target
|
|
87
|
+
failures are inner `ok:false`), and the process exit code is granular: `0`
|
|
88
|
+
all-ok, `2` partial, `1` orchestration error or every target failed. See
|
|
89
|
+
`docs/CONSTITUTION.md` (Target selection) for the shared grammar and `--where`
|
|
90
|
+
vs `--query` distinction.
|
|
91
|
+
|
|
92
|
+
- **Writes fan out under `--yes`**, confirmed once up front (not per target).
|
|
93
|
+
Without `--yes`, the error names the blast radius (how many routers) and that
|
|
94
|
+
`--yes` is required.
|
|
95
|
+
- **`--listen`/`--stream` is single-session** → `usage/fanout-not-supported` in
|
|
96
|
+
fan-out mode. **`--raw` strips the envelope** → `usage/conflicting-flags` in
|
|
97
|
+
fan-out mode (per-target envelopes can't be bare).
|
|
80
98
|
|
|
81
99
|
## Validation
|
|
82
100
|
|
package/commands/api/examples.md
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# api — examples
|
|
2
2
|
|
|
3
|
-
Each numbered example is an executable spec
|
|
4
|
-
(booted by `@tikoci/quickchr`)
|
|
5
|
-
|
|
6
|
-
`test/integration/api-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
Each numbered example is an executable spec asserted by CHR integration tests
|
|
4
|
+
(booted by `@tikoci/quickchr`), example N ↔ assertion N:
|
|
5
|
+
`test/integration/api.test.ts` (rest-api examples),
|
|
6
|
+
`test/integration/api-native.test.ts` (native-api examples, N…),
|
|
7
|
+
`test/integration/api-listen.test.ts` (streaming examples, L…), and
|
|
8
|
+
`test/integration/api-fanout.test.ts` (fan-out examples, F…). If a line here is not
|
|
9
|
+
exercised by a test, the test file is wrong; if a line passes only with
|
|
10
|
+
`--validate=false`, the implementation is wrong (see
|
|
9
11
|
[`docs/CONSTITUTION.md`](../../docs/CONSTITUTION.md)).
|
|
10
12
|
|
|
11
13
|
`$R` is `<host>:<rest-port>` resolved by quickchr. `$A` is `<host>` and
|
|
@@ -323,3 +325,104 @@ centrs api $A ip/address --stream --duration 2s --via native-api --port $API_POR
|
|
|
323
325
|
|
|
324
326
|
With no change during the window, the stream ends after ~2 s with a summary
|
|
325
327
|
envelope whose `data.stopReason=duration-elapsed`. Exit code 0.
|
|
328
|
+
|
|
329
|
+
## fanout (multi-target, F…)
|
|
330
|
+
|
|
331
|
+
These run against a CDB fixture with two records sharing group `$G`: record 0 is
|
|
332
|
+
the live CHR (comment `board=chr`), record 1 is an unreachable host (comment
|
|
333
|
+
`board=dead`). Fan-out output is the locked `FanoutData` envelope
|
|
334
|
+
(`data = { summary, targets[] }`; outer `ok` = orchestration success; per-target
|
|
335
|
+
failures are inner `ok:false`), with the granular exit code (0 all-ok / 2 partial /
|
|
336
|
+
1 all-failed or orchestration error). See `test/integration/api-fanout.test.ts`.
|
|
337
|
+
The `--raw`/`--listen` + fan-out guards are network-free and validated in
|
|
338
|
+
`test/integration/cli-smoke.test.ts`.
|
|
339
|
+
|
|
340
|
+
### F1. `--group` fans a GET out; a dead target is an inner failure
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
centrs api --group $G ip/address --cdb-file $CDB --json
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
`ok: true`, `data.summary = { total: 2, ok: 1, failed: 1 }`, `data.targets[]` in
|
|
347
|
+
record-index order (`[0, 1]`): target 0 is an inner success (`meta.via=rest-api`),
|
|
348
|
+
target 1 is inner `ok:false` with `error.code=transport/connection-refused`.
|
|
349
|
+
`meta.operation.kind=fanout`. Exit code 2.
|
|
350
|
+
|
|
351
|
+
### F2. `--where` selects only the matching device
|
|
352
|
+
|
|
353
|
+
```bash
|
|
354
|
+
centrs api --where board=chr ip/address --cdb-file $CDB --json
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
The device-class selector matches only record 0, so `data.summary = { total: 1,
|
|
358
|
+
ok: 1, failed: 0 }`, one inner success. Exit code 0.
|
|
359
|
+
|
|
360
|
+
### F3. An empty selection is `ok:true` with summary 0/0/0
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
centrs api --group no-such-group ip/address --cdb-file $CDB --json
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
`ok: true`, `data.summary = { total: 0, ok: 0, failed: 0 }`, `data.targets = []`,
|
|
367
|
+
and a `cdb/empty-group` warning. Exit code 0.
|
|
368
|
+
|
|
369
|
+
### F4. A mutating fan-out without `--yes` is rejected, naming the blast radius
|
|
370
|
+
|
|
371
|
+
```bash
|
|
372
|
+
centrs api -X PUT ip/address -f address=10.99.0.1/32 -f interface=ether1 --group $G --cdb-file $CDB --json
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Non-interactive and unconfirmed: outer `ok:false`,
|
|
376
|
+
`error.code=usage/confirmation-required`, and the message names the router count
|
|
377
|
+
(`2 router(s)`) and that `--yes` is required. No write is attempted. Exit code 1.
|
|
378
|
+
|
|
379
|
+
### F5. A mutating fan-out with `--yes` writes across the selected devices
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
centrs api -X PUT ip/address -f address=10.99.0.7/32 -f interface=ether1 --where board=chr --yes --cdb-file $CDB --json
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Confirmed once up front; the add runs on record 0 only (selected by `--where`).
|
|
386
|
+
`data.summary = { total: 1, ok: 1, failed: 0 }`, the inner success carries the
|
|
387
|
+
created `.id`. Exit code 0.
|
|
388
|
+
|
|
389
|
+
The fixture for F6–F9 adds record 2 — the reserved `__default__` credential record.
|
|
390
|
+
|
|
391
|
+
### F6. `--all` fans across every record except `__default__`
|
|
392
|
+
|
|
393
|
+
```bash
|
|
394
|
+
centrs api --all ip/address --cdb-file $CDB --json
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
`data.summary = { total: 2, ok: 1, failed: 1 }`, `data.targets[]` record indices
|
|
398
|
+
`[0, 1]` (record 2 `__default__` is excluded). Exit code 2.
|
|
399
|
+
|
|
400
|
+
### F7. A positional + `--group` union de-dupes by record index
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
centrs api $R0 --group $G ip/address --cdb-file $CDB --json
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
`$R0` is record 0's target, also a member of `$G`; the union is `{0, 1}` — record
|
|
407
|
+
indices `[0, 1]`, `total = 2` (not 3). Exit code 2.
|
|
408
|
+
|
|
409
|
+
### F8. `--concurrency` bounds the worker pool
|
|
410
|
+
|
|
411
|
+
```bash
|
|
412
|
+
centrs api --group $G --concurrency 1 ip/address --cdb-file $CDB --json
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
Runs one target at a time; `meta.operation.concurrency = 1`, still
|
|
416
|
+
`data.summary = { total: 2, ok: 1, failed: 1 }`. (`--concurrency 2abc` / `1.5` are
|
|
417
|
+
rejected at parse time with `usage/invalid-concurrency`.) Exit code 2.
|
|
418
|
+
|
|
419
|
+
### F9. `--default` selects `__default__`, which fails the connectable guard
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
centrs api --default ip/address --cdb-file $CDB --json
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
The reserved record is a credential fallback, not a connectable router, so its one
|
|
426
|
+
target fails deterministically: inner `ok:false` with
|
|
427
|
+
`error.code=target/unresolved` (never a `transport/dns` attempt on `"__default__"`).
|
|
428
|
+
`data.summary = { total: 1, ok: 0, failed: 1 }`. Exit code 1 (every target failed).
|