@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.
Files changed (134) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/GLOSSARY.txt +284 -0
  3. package/LICENSE +21 -0
  4. package/README.md +55 -11
  5. package/SECURITY.md +3 -3
  6. package/commands/AGENTS.md +7 -1
  7. package/commands/api/README.md +46 -27
  8. package/commands/api/examples.md +124 -6
  9. package/commands/btest/README.md +21 -32
  10. package/commands/check/README.md +376 -53
  11. package/commands/check/examples.md +390 -0
  12. package/commands/devices/README.md +177 -37
  13. package/commands/devices/examples.md +148 -0
  14. package/commands/discover/README.md +5 -11
  15. package/commands/execute/README.md +73 -26
  16. package/commands/execute/examples.md +131 -9
  17. package/commands/explain/README.md +1716 -0
  18. package/commands/explain/examples.md +606 -0
  19. package/commands/retrieve/README.md +55 -71
  20. package/commands/retrieve/examples.md +56 -9
  21. package/commands/settings/README.md +286 -22
  22. package/commands/settings/examples.md +408 -0
  23. package/commands/terminal/README.md +6 -0
  24. package/commands/transfer/README.md +57 -19
  25. package/commands/transfer/examples.md +95 -0
  26. package/docs/CLI.md +353 -0
  27. package/docs/CONSTITUTION.md +102 -26
  28. package/docs/MATRIX.md +106 -173
  29. package/docs/errors/README.md +5 -1
  30. package/docs/errors/cdb/empty-selection.md +19 -0
  31. package/docs/errors/input/incomplete-gps.md +9 -0
  32. package/docs/errors/input/invalid-altitude.md +9 -0
  33. package/docs/errors/input/invalid-bbox.md +9 -0
  34. package/docs/errors/input/invalid-coordinate.md +9 -0
  35. package/docs/errors/input/invalid-radius.md +9 -0
  36. package/docs/errors/internal/settings-failed.md +9 -0
  37. package/docs/errors/quickchr/machine-not-found.md +22 -0
  38. package/docs/errors/quickchr/machine-stopped.md +20 -0
  39. package/docs/errors/quickchr/package-unavailable.md +27 -0
  40. package/docs/errors/quickchr/unsupported-via.md +35 -0
  41. package/docs/errors/quickchr/unsupported.md +35 -0
  42. package/docs/errors/routeros/version-unsupported.md +26 -0
  43. package/docs/errors/settings/consequential-value.md +26 -0
  44. package/docs/errors/settings/invalid-boolean.md +21 -3
  45. package/docs/errors/settings/invalid-format.md +18 -3
  46. package/docs/errors/settings/invalid-integer.md +18 -3
  47. package/docs/errors/settings/invalid-timeout.md +20 -3
  48. package/docs/errors/settings/invalid-via.md +22 -3
  49. package/docs/errors/settings/reserved-key.md +29 -0
  50. package/docs/errors/settings/skip-env-file-active.md +25 -0
  51. package/docs/errors/settings/unknown-key.md +25 -0
  52. package/docs/errors/transport/provider-trust.md +29 -0
  53. package/docs/errors/usage/fanout-not-supported.md +16 -0
  54. package/docs/errors/usage/stdin-ignored.md +28 -0
  55. package/docs/errors/validation/unknown-attribute.md +37 -4
  56. package/package.json +49 -14
  57. package/src/api-fanout.ts +469 -0
  58. package/src/api.ts +171 -56
  59. package/src/cli/api.ts +156 -13
  60. package/src/cli/btest.ts +38 -4
  61. package/src/cli/common.ts +11 -0
  62. package/src/cli/devices.ts +235 -6
  63. package/src/cli/discover.ts +3 -1
  64. package/src/cli/execute.ts +129 -9
  65. package/src/cli/explain.ts +503 -0
  66. package/src/cli/mcp.ts +8 -1
  67. package/src/cli/retrieve.ts +167 -90
  68. package/src/cli/selection.ts +260 -0
  69. package/src/cli/settings.ts +286 -0
  70. package/src/cli/terminal.ts +22 -7
  71. package/src/cli/transfer.ts +192 -51
  72. package/src/cli.ts +22 -2
  73. package/src/core/envelope.ts +5 -2
  74. package/src/core/error-catalog.ts +95 -0
  75. package/src/core/fanout.ts +143 -2
  76. package/src/core/preflight.ts +306 -0
  77. package/src/core/routeros-string.ts +24 -0
  78. package/src/devices.ts +212 -20
  79. package/src/discover.ts +7 -2
  80. package/src/errors.ts +1 -0
  81. package/src/execute-fanout.ts +453 -0
  82. package/src/execute.ts +216 -68
  83. package/src/explain/arg-tokens.ts +114 -0
  84. package/src/explain/args.ts +1403 -0
  85. package/src/explain/blocks.ts +132 -0
  86. package/src/explain/brace-slots.ts +143 -0
  87. package/src/explain/brace-tokens.ts +38 -0
  88. package/src/explain/catalog.ts +1345 -0
  89. package/src/explain/coordinates.ts +329 -0
  90. package/src/explain/defects.ts +217 -0
  91. package/src/explain/is-known-menu.ts +17 -0
  92. package/src/explain/menus.ts +664 -0
  93. package/src/explain/operator-tokens.ts +310 -0
  94. package/src/explain/operators.ts +458 -0
  95. package/src/explain/path-tokens.ts +150 -0
  96. package/src/explain/pathresolve.ts +1324 -0
  97. package/src/explain/quoted-string.ts +241 -0
  98. package/src/explain/scope-brace.ts +308 -0
  99. package/src/explain/segment.ts +757 -0
  100. package/src/explain/string-tokens.ts +67 -0
  101. package/src/explain/symbol-values.ts +520 -0
  102. package/src/explain/symbols.ts +1597 -0
  103. package/src/explain/token-ranges.ts +72 -0
  104. package/src/explain/transport.ts +497 -0
  105. package/src/explain/value-tokens.ts +80 -0
  106. package/src/explain/values.ts +357 -0
  107. package/src/explain/verbs.ts +64 -0
  108. package/src/explain/verbsplit.ts +813 -0
  109. package/src/explain/write.ts +815 -0
  110. package/src/explain.ts +2152 -0
  111. package/src/index.ts +307 -1
  112. package/src/mcp/config.ts +12 -2
  113. package/src/mcp/tools.ts +137 -2
  114. package/src/protocols/adapter.ts +38 -18
  115. package/src/protocols/index.ts +11 -5
  116. package/src/protocols/mac-telnet-console.ts +61 -17
  117. package/src/resolver/cdb.ts +10 -2
  118. package/src/resolver/comment-kv.ts +58 -4
  119. package/src/resolver/config-file.ts +136 -0
  120. package/src/resolver/facts.ts +78 -0
  121. package/src/resolver/geo.ts +453 -0
  122. package/src/resolver/index.ts +65 -0
  123. package/src/resolver/mac.ts +12 -3
  124. package/src/resolver/quickchr-provider.ts +597 -0
  125. package/src/resolver/selection.ts +391 -0
  126. package/src/resolver/service-endpoint.ts +155 -0
  127. package/src/resolver/settings.ts +54 -5
  128. package/src/resolver/target.ts +21 -3
  129. package/src/retrieve-fanout.ts +239 -133
  130. package/src/retrieve.ts +120 -25
  131. package/src/settings.ts +1078 -0
  132. package/src/terminal.ts +11 -1
  133. package/src/transfer-fanout.ts +544 -0
  134. package/src/transfer.ts +133 -40
package/CHANGELOG.md CHANGED
@@ -8,6 +8,104 @@ documenting cross-cutting shifts that affect contributors and consumers.
8
8
 
9
9
  ## Unreleased
10
10
 
11
+ ## 0.1.4 — 2026-08-31
12
+
13
+ Preview release. `@tikoci/centrs@0.1.4` on npm's `next` tag.
14
+
15
+ ### Added
16
+
17
+ - **Offline `explain` is now a real CLI and library surface.** It analyzes
18
+ RouterOS commands and scripts without a router, CDB, or network connection,
19
+ returning canonical structure, diagnostics, write-shape and transport
20
+ classification, optional `curl` rendering, symbols, value-shape hints, and a
21
+ gapless token partition. Try it with
22
+ `bunx @tikoci/centrs@next explain '/ip/address print' --json`.
23
+ - **Generated RouterOS structure evidence.** Pinned inspect trees and
24
+ MikroTik's published CLI Reference now help distinguish known menus from
25
+ commands while preserving the fail-closed rule: missing catalog entries never
26
+ become rejections, schema claims, or runtime-acceptance claims.
27
+ - **Named quickchr targets.** Router-facing commands can resolve an ephemeral
28
+ quickchr instance with `--quickchr <name>` through the shared target-provider
29
+ path.
30
+
31
+ ### Changed
32
+
33
+ - **Offline analysis is substantially more precise and robust.** Grounded
34
+ handling now covers RouterOS comments, escapes, operators, arrays, value
35
+ shapes, path context, and flow-sensitive symbol-to-value references; bounded
36
+ traversal and shared statement indexes keep large inputs near-linear.
37
+ - **Release and QA coverage is broader.** Recency-aware RouterOS channel
38
+ selection, long-term-version expectations, generated-artifact drift checks,
39
+ corpus gates, and faster local Git gates make the published preview easier to
40
+ reproduce and audit.
41
+
42
+ ### Fixed
43
+
44
+ - **Validation now reads RouterOS `:parse` results instead of accepting the
45
+ transport round-trip alone**, and retrieve errors retain the rejected
46
+ parameter. Parser, catalog, cross-platform QA, and MAC-Telnet edge cases were
47
+ also hardened.
48
+ - **The npm package now carries the MIT license text.**
49
+
50
+ ## 0.1.3 — 2026-07-06
51
+
52
+ Patch release. `@tikoci/centrs@0.1.3`.
53
+
54
+ ### Added
55
+
56
+ - **`api` command over rest-api + native-api is `CHR-passed`.** `centrs api`
57
+ brings a `gh api`-style structured passthrough to both transports, and folds
58
+ the old standalone `stream` command into `api --stream` (native-api only,
59
+ `--listen` alias): a `NativeApiSession.listen()` reader with `/cancel`
60
+ support emits NDJSON frames plus a run summary. Grounded against real CHR
61
+ 7.23.1.
62
+ - **Multi-target fan-out is uniform across `api`, `retrieve`, `execute`, and
63
+ `transfer`.** A shared `expandCdbSelection` + `runFanout` + `fanoutExitCode`
64
+ core (extracted once, consumed by all four commands) lets any command take a
65
+ CDB selector and run over every matching device. The positional boundary
66
+ that ends the selector differs per command (final positional for
67
+ `api`/`retrieve`, `--` for `execute`, the verb keyword for `transfer`);
68
+ `terminal` and `api --stream` explicitly reject `N > 1` selections since
69
+ neither has a sane multi-target framing.
70
+ - **`settings` command (`print` / `get` / `set` / `reset`).** A managed-key
71
+ registry for the real `CENTRS_*` environment keys backs an atomic
72
+ `centrs.env` writer: `set` validates per key, canonicalizes booleans to
73
+ `1`/`0` on disk, warns (never blocks) on consequential values
74
+ (`insecure=true`, `transfer-via=ftp`), and never partially writes on a
75
+ validation failure; `reset` deletes the managed line rather than blanking it
76
+ (a blank value would itself mean "set to empty" for a bash-sourced file) and
77
+ leaves foreign lines/comments untouched. Bare `centrs settings` behaves like
78
+ `print` regardless of TTY. Four new error/warning codes
79
+ (`settings/reserved-key`, `settings/unknown-key`,
80
+ `settings/consequential-value`, `settings/skip-env-file-active`) plus
81
+ `internal/settings-failed`, all cataloged. `CHR-passed` via 32
82
+ fixture-backed integration examples.
83
+ - **Device GPS: lat/lon/altitude storage + `--near` / `--bbox` geo
84
+ predicates.** `centrs devices` now stores GPS coordinates per device
85
+ (lat-first ordering, chosen so agents guess the field order correctly) and
86
+ supports proximity
87
+ (`--near`) and bounding-box (`--bbox`) selection predicates. `CHR-passed`
88
+ via fixture-backed integration coverage.
89
+ - **Registry⇄MATRIX drift guard + "did you mean?" flag errors.** A CI-checked
90
+ guard keeps the command/protocol registry and `docs/MATRIX.md` from
91
+ silently drifting apart; unrecognized CLI flags now suggest the closest
92
+ known flag instead of a bare rejection.
93
+ - **GitHub Pages: published API reference + error catalog.** `typedoc` output
94
+ and the generated error-code catalog now publish to GitHub Pages on push.
95
+
96
+ ### Fixed
97
+
98
+ - **Windows unit-test portability for `settings` and MNDP.** Path-separator
99
+ and related platform assumptions in the `settings` and MNDP unit suites are
100
+ now Windows-safe (#170).
101
+ - **A real CodeQL-caught ReDoS in the device GPS geo-predicate parser**, fixed
102
+ rather than suppressed.
103
+
104
+ ### Changed
105
+
106
+ - **CodeQL house-rules pack promoted to the default run**, alongside CodeRabbit
107
+ review-tone tuning and a reduced GitHub-checks timeout (15 → 7 minutes).
108
+
11
109
  ## 0.1.2 — 2026-06-26
12
110
 
13
111
  Patch release. `@tikoci/centrs@0.1.2`.
package/GLOSSARY.txt CHANGED
@@ -93,9 +93,12 @@ cwd
93
93
  datagram
94
94
  davidanson
95
95
  DDNS
96
+ dialable # an endpoint a client can actually connect to (advertised AND reachable)
96
97
  defconf
97
98
  demux
98
99
  demuxes
100
+ desync # a highlight stream whose token count != input length, so classes line up against the wrong characters
101
+ desynced
99
102
  dogtail
100
103
  donny
101
104
  dotenv
@@ -125,6 +128,8 @@ keystream
125
128
  KSA
126
129
  KVM
127
130
  LDTP
131
+ LFS # Git Large File Storage. The pinned corpus blob is NOT stored through it,
132
+ # which is why a plain raw.githubusercontent GET returns the real bytes.
128
133
  librouteros # <https://github.com/haakonnessjoen/librouteros-api>
129
134
  LLDP
130
135
  lsp-routeros-ts
@@ -139,6 +144,7 @@ MNDP
139
144
  NAND # NAND flash memory; write-back caches file writes up to ~40s on some MikroTik boards
140
145
  netmiko # [Multi-vendor library to simplify Paramiko SSH connections to network devices](https://github.com/ktbyers/netmiko)
141
146
  netwatch
147
+ nullglob # bash shopt that makes an unmatched glob expand to zero words instead of the literal pattern
142
148
  npk # MikroTik package format
143
149
  nvme-over-tcp # supported by RouterOS **with** rose-storage.npk extra-package
144
150
  OID
@@ -148,6 +154,7 @@ PRGA
148
154
  proplist
149
155
  pywinauto
150
156
  QML
157
+ qll # CodeQL library file extension (.qll), e.g. CleartextLoggingCustomizations.qll
151
158
  qmljsdebugger
152
159
  quickchr
153
160
  RAML
@@ -161,12 +168,16 @@ rosetta
161
168
  routeros
162
169
  routeros-skills
163
170
  rsc # RouterOS script / export file extension (.rsc)
171
+ rstp # Rapid Spanning Tree Protocol; a RouterOS `protocol-mode=` value on /interface/bridge
172
+ SARIF # Static Analysis Results Interchange Format; CodeQL's code-scanning analysis download format
164
173
  scp
165
174
  scriptability
166
175
  scriptable
176
+ shopt # bash builtin for toggling shell options (e.g. nullglob)
167
177
  Sikuli
168
178
  SikuliX
169
179
  skillcheck
180
+ startswith # jq builtin filter that tests whether a string begins with a given prefix
170
181
  SNMP
171
182
  SPI
172
183
  SRP
@@ -184,6 +195,7 @@ TIKOCI
184
195
  tiktui
185
196
  TLV
186
197
  traceroute
198
+ tostring # jq builtin filter that renders a value as its JSON string representation
187
199
  tsc
188
200
  tzsp
189
201
  TZSP
@@ -200,6 +212,7 @@ webproxy
200
212
  WinAppDriver
201
213
  winbox
202
214
  Wireshark
215
+ workstream
203
216
  workstreams
204
217
  XCUI
205
218
  XDG
@@ -263,6 +276,9 @@ choco
263
276
  coderabbit
264
277
  coderabbitai
265
278
 
279
+ # GitHub account Codex review threads are authored under (see pr-review-bots).
280
+ mobileskyfi
281
+
266
282
  # centrs domain vocabulary
267
283
  # %identity: the device /system/identity; a centrs CDB comment *lookup key*
268
284
  # (`identity=`) used to resolve `<router>`. Human handle, deliberately may
@@ -364,6 +380,10 @@ CSI
364
380
  SIGWINCH
365
381
  # evl: RouterOS :parse renders a parsed command as "(evl ...)" on the console.
366
382
  evl
383
+ # putmessage/globalname: RouterOS :parse IL statement heads — ":put x" echoes back
384
+ # as "(evl /putmessage=x)", ":global n v" as "(evl /globalname=$n;value=v)" (GH#230).
385
+ putmessage
386
+ globalname
367
387
  # unconfigured: a freshly-flashed RouterOS device with no IP — the mac-telnet use case.
368
388
  unconfigured
369
389
  unwired
@@ -391,6 +411,7 @@ Weierstrass
391
411
  # ndjson: newline-delimited JSON; the stream / btest output contract (one envelope per line).
392
412
  ndjson
393
413
  # allocate-udp-ports-from: /tool/bandwidth-server base of the UDP data-port range.
414
+ allocate-udp-ports-from
394
415
  # srp5: the "5" suffix of EC-SRP5 (the MikroTik EC-SRP variant).
395
416
  srp5
396
417
  # SRV: the server-endpoint test variable in commands/btest/examples.md.
@@ -434,3 +455,266 @@ toarray
434
455
  # Logic shorthand ("if and only if") used in api write-gate doc comments; cspell's
435
456
  # markdown dictionary knows it but the TypeScript one does not.
436
457
  iff
458
+
459
+ # GitHub Pages build vocabulary (scripts/build-pages-site.ts, pages.yaml)
460
+ # typedoc/pandoc: the API-reference generator and the markdown->HTML converter
461
+ # the Pages build chains together. gfm: GitHub-Flavored Markdown, pandoc's
462
+ # input format for the error pages. larr: the "&larr;" left-arrow HTML entity
463
+ # in the back-to-docs link. nojekyll: the marker file that disables GitHub
464
+ # Pages' Jekyll processing for an Actions-deployed artifact. extensionless:
465
+ # describes the `details_url` contract (docs/errors/README.md) — no `.html`.
466
+ typedoc
467
+ pandoc
468
+ gfm
469
+ larr
470
+ nojekyll
471
+ extensionless
472
+ # nothrow: Bun Shell's `$` method to disable throwing on a non-zero exit
473
+ # (scripts/agent-check.ts), so the caller can inspect output either way.
474
+ nothrow
475
+
476
+ # check-command spec vocabulary (commands/check/README.md, examples.md)
477
+ # TikTOML: centrs-owned readable device-inventory format (#137); check's
478
+ # metadata feeds it. routerboard: RouterBOARD firmware/hardware menu
479
+ # (/system/routerboard). rscmd: the tikbook RouterOS command-notebook file
480
+ # extension (.rscmd), the format the check spec was grounded against.
481
+ TikTOML
482
+ routerboard
483
+ rscmd
484
+ # RTT: round-trip time, the host->router connect-latency reach signal. tojson: the
485
+ # /system/script helper (:serialize to=json) the batched execute reads reuse.
486
+ # qrcode: the /ip/cloud vpn-wireguard-client-config-qrcode field token (redacted
487
+ # by the cloud check, referenced when documenting redaction).
488
+ RTT
489
+ tojson
490
+ qrcode
491
+
492
+ # Device GPS vocabulary (issue #146, commands/devices/README.md "Location /
493
+ # GPS", src/resolver/geo.ts). MSL/AGL: the two supported altitude-type vertical
494
+ # datums (mean sea level / above ground level); stored case-insensitively but
495
+ # normalized to upper case. EPSG: the geodetic coordinate-reference-system
496
+ # registry (EPSG:4326 = WGS84 lat/lon), cited for the lat-first ordering
497
+ # rationale. bbox: the --bbox geo selector (bounding box). wgs: WGS84, the
498
+ # geodetic datum GPS coordinates use; appears only as a deliberately-invalid
499
+ # --altitude-type value in tests/examples. antimeridian: the 180° meridian a
500
+ # --bbox may not cross in v1. GEOSEARCH: the Redis command whose m/km/mi/ft
501
+ # radius-unit vocabulary --near borrows. IUGG: the geodesy union whose mean
502
+ # Earth radius the haversine uses. SFO/OAK: airports used as a known-distance
503
+ # haversine fixture in tests. ele/GPX: `ele` is GPX's altitude element, kept as
504
+ # an input alias for `altitude` (GPX = the GPS Exchange Format). Geolocation:
505
+ # the W3C Geolocation API, cited (with ISO 6709 / EPSG / IETF geo:) for why
506
+ # `altitude` — not `elevation` — is the canonical field name.
507
+ MSL
508
+ msl
509
+ AGL
510
+ agl
511
+ Agl
512
+ EPSG
513
+ bbox
514
+ wgs
515
+ antimeridian
516
+ GEOSEARCH
517
+ IUGG
518
+ SFO
519
+ sfo
520
+ ele
521
+ GPX
522
+ Geolocation
523
+ unparseable
524
+ Unparseable
525
+ versionless
526
+
527
+ # explain canonicalizer (phase-0 lab #185, commands/explain) vocabulary.
528
+ # SUT: "system under test" — the phase-0 lab's throwaway parser, scored
529
+ # against a `:parse` IL / highlight oracle; the ratified behavior is what the
530
+ # production modules under src/explain promote. CJK: the Chinese/Japanese/
531
+ # Korean Unicode range, used as a non-ASCII coordinate/segmentation fixture.
532
+ # pathresolve / blocktree: the Q3-Q4 / Q2 lab probe names, carried into the
533
+ # src/explain module + fixture names. klass: `class` is reserved, so the
534
+ # resolution feature-class field is `klass`. tostr / tostrvalue: the RouterOS
535
+ # `:tostr` directive and its IL-lowered form `/tostrvalue`. eworm: a corpus
536
+ # script author (eworm/*.rsc) cited as grounding for scope-name findings.
537
+ # rextended: another corpus script author, cited for the Q14 nested-bracket
538
+ # ambient-context case (a user function establishes no menu). ppp: the RouterOS
539
+ # /ppp menu, in the Q14 corner proving a `$` inside a `~` FILTER argument does
540
+ # not make the menu unreadable. strfind: a corpus user function in that corner.
541
+ # verbsplit: the Q6 verb/menu-boundary lab probe name, carried into the
542
+ # src/explain module + fixture names. myvar: the RouterOS example variable in
543
+ # the Q6 `:global myvar` collision corner (a positional operand indistinguishable
544
+ # from a sub-menu offline).
545
+ # failclosed: the ratified Q16 navigation arm name (the two arms that priced it
546
+ # stayed in the lab). notadirective: the Q16 fixture's deliberately unseen root
547
+ # directive, pinning that an unknown directive abstains rather than reading as
548
+ # harmless control flow.
549
+ # RouterOS root cmds (scripting directives) enumerated from /console/inspect and
550
+ # frozen in src/explain/write.ts as the closed ROOT_CMDS vocabulary — jobname,
551
+ # rndnum, rndstr, tobool, tocrlf, toid, toip, tolf, tonsec, tonum, totime.
552
+ # lte: the RouterOS /interface lte menu, cited in the Q16 long-tail-verb corner.
553
+ # dyn: the `$dyn` variable in the Q14 cascade corners — a dynamic-headed
554
+ # statement, which is context-NEUTRAL and must not poison the menu context.
555
+ # symbols (Q13): cls — the resolved variable-class field on a symbol occurrence
556
+ # (`class` is reserved, same reason as klass). sigilled: written with the `$`
557
+ # sigil, the schema-free tell that separates a variable from a directive or a
558
+ # menu-path segment. ERRVAR: the `:onerror NAME` error-variable slot (S18).
559
+ # unmodeled / distinguisher: the closure-scope finding — the lab left the rule
560
+ # unmodeled because a depth counter has no scope identity, and the statement
561
+ # head is the only distinguisher between a function `do={}` and a control-flow
562
+ # one. Corner variable names quoted from the device probes and corpus scripts:
563
+ # vlanid, vlanbytes, ipprefix, octive (the `$octive-1` name-vs-operator case),
564
+ # lppp (the device documentation's own `variable-undefined` example).
565
+ # lookback: the backward scan that decides whether a `{` follows `do=`; it must
566
+ # accept exactly the escapes the forward walker does, or the two disagree about
567
+ # where the argument name ends and a function body stops being a closure.
568
+ # tangentsoft: the maintainer/site (tangentsoft.com/mikrotik) behind the third
569
+ # corpus collection — 35 genuine `/export show-sensitive terse` device captures
570
+ # imported for the #203 genre census, and the corpus's only wholly-terse
571
+ # export stratum.
572
+ SUT
573
+ CJK
574
+ parseil # lsp-routeros-ts corpus table of captured `:parse` IL, per script per RouterOS version
575
+ pathresolve
576
+ blocktree
577
+ klass
578
+ tostr
579
+ tostrvalue
580
+ eworm
581
+ rextended
582
+ tangentsoft
583
+ ppp
584
+ strfind
585
+ verbsplit
586
+ myvar
587
+ failclosed
588
+ notadirective
589
+ jobname
590
+ rndnum
591
+ rndstr
592
+ tobool
593
+ tocrlf
594
+ toid
595
+ toip
596
+ tolf
597
+ tonsec
598
+ tonum
599
+ totime
600
+ lte
601
+ dyn
602
+ cls
603
+ sigilled
604
+ ERRVAR
605
+ unmodeled
606
+ distinguisher
607
+ vlanid
608
+ vlanbytes
609
+ ipprefix
610
+ octive
611
+ lppp
612
+ lookback
613
+
614
+ # RouterOS menu vocabulary reached by the explain menu table (#207).
615
+ # The generated table itself (src/explain/menus.ts) is cspell-ignored; these are
616
+ # the terms that also appear in hand-written prose and tests.
617
+ # %cliref: docs-derived RouterOS command reference in rosetta
618
+ # (experiments/cliref.db). A second opinion on node types, NOT a source for
619
+ # the menu table: its paths interleave the docs slug with the CLI path.
620
+ cliref
621
+ hotspot
622
+ igmp
623
+ ipfix
624
+ keymat
625
+ pmp
626
+ pptp
627
+ sstp
628
+ wlan
629
+ zerotier
630
+ cuu # `/terminal cuu` (cursor up) — a RouterOS command that reads as a bare menu path, so the table refuses it (#210)
631
+
632
+ # Hardware-gated RouterOS surfaces named when explaining why a published path is
633
+ # absent from a CHR-derived inspect tree (#228). No CHR carries these.
634
+ FDB # forwarding database — `/interface/ethernet/switch/{unicast,multicast,reserved}-fdb`, switch-chip only
635
+ MSRP # Stream Reservation Protocol (TSN) — `/interface/bridge/msrp`, gated by `conditions: MSRP_ENABLE`
636
+ PTP # Precision Time Protocol — `/system/ptp`, gated by `syscap: ptp`
637
+ syscap # a CLI-Reference applicability gate naming the hardware/system capability a path requires (`poe`, `lcd`, `multiswitch`, `nochr`)
638
+
639
+ # explain ANALYSIS/ORACLE vocabulary — not RouterOS menu names. These name the
640
+ # lab's measurement machinery and the properties it measures, and belong apart
641
+ # from the menu block above even though both arrived with the explain work.
642
+ importability # whether a document RouterOS emitted can be re-imported (#203 oracle level L2)
643
+ menuctx # the IL (`:parse`) statement type for menu navigation, as against `cmd` — the native nav-vs-command oracle (#210)
644
+
645
+ # #202c-1 argument-lexer vocabulary: one analysis term, plus the RouterOS names
646
+ # that appear verbatim in the corpus lines `test/unit/explain-args.test.ts`
647
+ # fixtures. The corpus lines are real forum scripts and are quoted rather than
648
+ # paraphrased, so their spellings are data, not prose.
649
+ renderable # of a statement: covered by a tested REST mapping rule, so `--curl` may render it (#202c-2)
650
+ wifiwave # `/interface wifiwave2` — the pre-7.13 RouterOS wifi menu, renamed `wifi` (drift the corpus carries)
651
+ nomaster # from a corpus line where two arguments ran together (`disabled=nomaster-interface=wifi1`); pinned as the first-`=`-splits case
652
+ CAINFO # `AT#CAINFO?` — an LTE modem AT command in a corpus `/interface/lte/at-chat` line
653
+ vzwinternet # Verizon LTE APN name, from a corpus `/interface lte apn add` line
654
+ rtr # short device name in an explain test input
655
+
656
+
657
+ # #228 path-catalog vocabulary: the CLI-Reference definition-module segments the
658
+ # alias allowlist maps away, the published gate values, and the prose terms used
659
+ # to describe the union. The generated table itself is cspell-ignored.
660
+ chancfg # caps-man definition module for `/caps-man/channel` — a doc spelling, not a CLI path
661
+ dpathcfg # caps-man definition module for `/caps-man/datapath`
662
+ ifaceactual # caps-man definition module for `/caps-man/actual-interface-configuration`
663
+ ratescfg # caps-man definition module for `/caps-man/rates`
664
+ remoteap # caps-man definition module for `/caps-man/remote-cap`
665
+ seccfg # caps-man definition module for `/caps-man/security`
666
+ ifaces # CLI-Reference definition module for `/tool/graphing/interface`
667
+ datapath # `/caps-man/datapath` — the real CLI menu behind `dpathcfg`
668
+ easymesh # `/interface/wifi/easymesh` — the doc spelling of `/interface/wifi/wps-push-button`
669
+ wps # Wi-Fi Protected Setup — `/interface/wifi/wps-push-button`
670
+ qos # switch-chip quality of service — `/interface/ethernet/switch/qos`, a real menu segment R1 must never drop
671
+ qca # Qualcomm Atheros — the `wireless-qca` package gate
672
+ multiswitch # syscap gate: more than one switch chip
673
+ oldswitch # syscap gate: the pre-Prestera switch-chip menu shape
674
+ smips # RouterOS architecture (single-core MIPS), named in `conditions:` gates
675
+ mmips # RouterOS architecture (multi-core MIPS), named in `conditions:` gates
676
+ CRS # MikroTik Cloud Router Switch — carries the switch-chip menus no CHR has
677
+ CCR # MikroTik Cloud Core Router
678
+ superlinear # growth faster than linear, used for resolveDocument wall-clock on separator-free input (#248)
679
+ stmts # `explainCommand` corpus measurement: statement count (shorthand for `statements`)
680
+ argCandidates # `explainCommand` corpus measurement: arg-bearing candidates
681
+ argReadable # `explainCommand` corpus measurement: readable statements
682
+ ungated # of a published path: carrying no package/conditions/syscap that would explain its absence from a tree
683
+ unioned # of the two sources: merged with per-entry provenance rather than flattened
684
+ unioning # the act of the above
685
+ greppable # of a generated table: one entry per line, so `grep` finds a path
686
+ apos # the `&apos;` XML entity, decoded by the CLI-Reference parser
687
+
688
+ # #285 CLI-Reference reshape vocabulary: the second published inventory, and the
689
+ # gate values on the paths that publish twice.
690
+ llms # `llms.txt` — MikroTik's published page inventory; the only one listing the `<dir>/<basename>` leaf a branching menu's own entry lives on, so discovery is the union of it and the sitemap
691
+ musicswitch # syscap gate: the Marvell-era switch-chip menu shape — one half of the `/interface/ethernet/switch` pair published under two gates
692
+ rbswitch # syscap gate: the RouterBOARD switch-chip menu shape — the other half of that pair
693
+ prestera # Marvell Prestera switch chip — named in the `crs_prestera` syscap gate on `/interface/ethernet/switch/qos`
694
+ xfrm # `/interface/xfrm` — the IPsec transform interface, and the one published path with no gate anywhere in its ancestry to explain its absence from a CHR tree
695
+ btrfs # the Linux B-tree filesystem, a `/disk/btrfs/...` menu on RouterOS 7.20+
696
+
697
+ # #255 operator-axis vocabulary: the device-grounded operator surface.
698
+ arities # plural of arity — the operand counts an operator accepted, which is what separates `(> 1)` (deferred expression) from `(2 > 1)` (comparison)
699
+ typeofvalue # RouterOS IL renders `[:typeof $x]` as `/typeofvalue=$x` — the directive and its argument name run together with no separator
700
+ juxt # short for juxtaposition — the unnamed ` ( a b)` node RouterOS builds for space-separated paren contents; used in ids like `any-juxt`
701
+ punct # short for punctuation — the shape class for a candidate spelling made of symbol bytes, as opposed to a word; used in the `bogus-punct` control id
702
+ gapless # of a token partition: every byte in [0, input.bytes) belongs to exactly one token, sorted by start, no gaps (#289 B1)
703
+ SCIP # Source Code Intelligence Protocol — the symbol-identity axis for `data.tokens[]`'s later vocabulary (#264 B5)
704
+ unclassified # the provisional token class for a byte no analyzer claims; a first-class answer in the gapless partition (#289 B1)
705
+
706
+ # #290 B2 operator-fill test vocabulary
707
+ xand
708
+ andx
709
+ alnum
710
+ wpa # `security.authentication-types=wpa2-psk` — a wifi argument name whose interior dot the operator fill must not read as concatenation
711
+ psk # pre-shared key — the value half of the same grounded `wpa2-psk` example
712
+
713
+ # #293 B2 arg-fill vocabulary
714
+ retag # B5 may retag the `=` separator from the provisional `arg` class without moving byte coverage
715
+
716
+ # #239 S2 flow-sensitive symbol→value
717
+ occ # short for occurrence — local variable in symbol-values flow analysis
718
+ vid # short for valueId — result-local value occurrence identifier (v0, v1)
719
+ idx # short for index — loop/event index in flow merging
720
+ mis # prefix in mis-linking — hyphenated in comments for symbol-values guard
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TIKOCI GitHub Organization
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -4,6 +4,21 @@
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 try the current offline analyzer without
8
+ installing centrs or connecting to a router:
9
+
10
+ ```bash
11
+ bunx @tikoci/centrs@next explain '/ip/address print' --json
12
+ ```
13
+
14
+ Use `bunx @tikoci/centrs@next --help` to inspect the full preview CLI. The
15
+ explicit `@next` matters: `0.1.x` preview releases do not move npm's `latest`
16
+ tag.
17
+
18
+ The full flag reference for every command is generated from the CLI metadata
19
+ into [`docs/CLI.md`](docs/CLI.md); per-command behavior lives in
20
+ `commands/<name>/README.md`.
21
+
7
22
  It is a **friendly conduit**, not a high-level configuration abstraction. It
8
23
  helps humans and agents reach RouterOS over the right protocol, with the right
9
24
  credentials and ports, and validates RouterOS-shaped commands before running
@@ -11,9 +26,15 @@ them. It does **not** hide RouterOS behind helpers like `createVlanOnBridge()`
11
26
  — you still speak RouterOS. Validation and structured diagnostics are the
12
27
  product; without them this would just be a worse `curl`.
13
28
 
14
- > **Status:** early and in active development. The library, device registry, and
15
- > first MCP server phases are CHR-verified; the CLI is wired (`coded`); encrypted-CDB
16
- > writes are still blocked, and the TUI/proxy frontends are planned.
29
+ > **Status:** early npm preview under active development. It is not
30
+ > production-ready: expect bugs and breaking changes before 1.0, and exercise
31
+ > router-writing workflows on lab or disposable targets first. Offline
32
+ > `explain` does not contact a router, but its analysis is intentionally
33
+ > conservative and never proves that RouterOS will accept a command at runtime.
34
+ > Check current dist-tags with
35
+ > `npm view @tikoci/centrs dist-tags --json`. The library, device registry, and
36
+ > first MCP server phases are CHR-verified; the CLI is wired (`coded`);
37
+ > encrypted-CDB writes are still blocked, and the TUI/proxy frontends are planned.
17
38
  > `docs/MATRIX.md` is the single source of truth for what works today — treat
18
39
  > anything not green there as not-yet-shipped.
19
40
 
@@ -26,8 +47,10 @@ product; without them this would just be a worse `curl`.
26
47
  SSH / MAC-Telnet / RoMON / WinBox Terminal for interactive and L2 execute;
27
48
  MNDP for discovery. centrs picks the protocol for you (and lets you pin one
28
49
  with `via=` or `--via`).
29
- - **Commands:** every command resolves a *target* — a router you name on the
30
- CLI — to a real address, credentials, and a protocol, then runs.
50
+ - **Commands:** every router-facing command resolves a *target* — a router you
51
+ name on the CLI — to a real address, credentials, and a protocol, then runs.
52
+ (Local commands — `devices`, `settings`, `discover`, and `explain`'s offline
53
+ mode — need no router.)
31
54
 
32
55
  | Command | Purpose |
33
56
  | ---------- | ------- |
@@ -36,6 +59,7 @@ product; without them this would just be a worse `curl`.
36
59
  | `api` | Structured RouterOS API passthrough (gh-api style): one command per operation, structured in/out, can write, over REST or native API. Open-ended follow is `api … --stream` (native API; NDJSON). |
37
60
  | `terminal` | Open an interactive console, primarily SSH or MAC-Telnet. |
38
61
  | `check` | Probe reachability and management protocol availability. |
62
+ | `explain` | Analyze a RouterOS command before running it: canonical form, syntax diagnostics, and what is valid at a path. Offline analysis runs today; the live-device probes are designed. |
39
63
  | `devices` | View and maintain the CDB-backed device registry (the only writer). |
40
64
  | `discover` | Discover MNDP neighbors and optionally save them into the CDB. |
41
65
  | `settings` | Manage centrs's own settings (`centrs.env`, `__default__`) and run first-time setup. |
@@ -104,16 +128,23 @@ CDB tags, and `devices set` refuses to put them there. Unknown keys produce a
104
128
  `cdb/unknown-option` warning and are ignored, so a stray `=` in human prose
105
129
  never breaks anything.
106
130
 
107
- Settings resolve lowest-to-highest: **built-in defaults → project config →
108
- comment-kv → `CENTRS_*` env → CLI flag / API arg.** Every result envelope
109
- reports which source won each setting under `meta.settings`, so you can always
110
- see *why* a given port or transport was chosen.
131
+ Settings resolve lowest-to-highest: **built-in defaults → user config file
132
+ (`centrs.env`) → comment-kv → `CENTRS_*` env → CLI flag / API arg.** Every
133
+ result envelope reports which source won each setting under `meta.settings`,
134
+ so you can always see *why* a given port or transport was chosen.
111
135
 
112
136
  ### Groups, discovery, and the MCP allowlist all ride on the CDB
113
137
 
114
138
  - **Groups** are CDB-native: an entry's `group` field is just a string, and
115
139
  `--group prod-edge` fans a single command out to every entry that shares it.
116
- There is no separate group-definition file.
140
+ There is no separate group-definition file. Groups are one selector in a
141
+ **uniform fan-out grammar** shared by `api`, `retrieve`, `execute`, and
142
+ `transfer`: mix `<router>` positionals, repeatable `--group`/`--where
143
+ <attr>=<value>`, `--all`, and `--default` (de-duped by CDB record index). Every
144
+ fan-out returns the locked `FanoutData` envelope (per-target results, not
145
+ metadata) with a granular exit code (`0` all ok / `2` partial / `1` all failed);
146
+ a multi-target write is gated once by `--yes` naming the blast radius. See
147
+ `docs/CONSTITUTION.md` (Target selection grammar).
117
148
  - **Discovery** writes back into the CDB: `discover --save` records MNDP
118
149
  neighbors as new entries tagged `group=discovered` with a `source=mndp`
119
150
  marker — hints to curate, not authoritative inventory.
@@ -168,7 +199,7 @@ roadmap.
168
199
 
169
200
  Requirements:
170
201
 
171
- - Bun 1.3.11 or newer (CI pinned to 1.3.13; Bun 2.x not yet validated).
202
+ - Bun 1.3.11 or newer (CI pinned to 1.3.14; Bun 2.x not yet validated).
172
203
  - Git.
173
204
  - For integration tests: QEMU plus `@tikoci/quickchr` (handles CHR image
174
205
  download and boot).
@@ -188,6 +219,19 @@ Generated API docs through TypeDoc:
188
219
  bun run build:doc:api
189
220
  ```
190
221
 
222
+ The `explain` censuses measure a RouterOS script corpus that lives in
223
+ [`tikoci/lsp-routeros-ts`](https://github.com/tikoci/lsp-routeros-ts), not in
224
+ this repo. `scripts/corpus-pin.json` records which snapshot centrs measures
225
+ against — a commit plus the blob's sha256 — and `corpus:fetch` downloads and
226
+ verifies it. A sibling `lsp-routeros-ts` checkout is used when present, and the
227
+ censuses print the source and hash they used:
228
+
229
+ ```bash
230
+ bun run corpus:fetch # download the pinned snapshot, verify its sha256
231
+ bun run explain:corpus-census # genre census (#203)
232
+ bun run explain:value-census # value-surface census (#225)
233
+ ```
234
+
191
235
  ## Done definition (short version)
192
236
 
193
237
  A feature is done when every line in `commands/<name>/examples.md` is green on
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)** — intended to be enabled in GitHub. [`.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.
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 currently available through stable unauthenticated APIs. Human review in the GitHub UI remains part of the security workflow.
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
 
@@ -7,6 +7,11 @@ Each `commands/<name>/` holds the **executable spec** for one command:
7
7
  the matching `test/integration/<name>.test.ts`; example N ↔ assertion N. This
8
8
  is what `CHR-passed` is measured against.
9
9
 
10
+ **Implemented** flags are generated from `CliCommandMetadata` into
11
+ `docs/CLI.md` (`bun run docs:cli`; drift-gated in CI) — a command README keeps
12
+ behavior prose plus a "Designed, not implemented" table for spec-only flags,
13
+ never a duplicate table of implemented flags.
14
+
10
15
  Do not restate the constitution here or in a command README — link to
11
16
  `docs/CONSTITUTION.md` for envelope, errors, settings precedence, target
12
17
  selection, and protocol selection. The identity/CDB **resolution depth** (lookup
@@ -19,7 +24,8 @@ README's "Status" line must agree with it.
19
24
  ## Verb vocabulary
20
25
 
21
26
  Commands are centrs **verbs** (`retrieve`, `execute`, `api`, `devices`,
22
- `discover`, `check`, `terminal`, `settings`), never one tool per RouterOS command. Open-ended follow
27
+ `discover`, `check`, `explain`, `terminal`, `settings`), never one tool per
28
+ RouterOS command. Open-ended follow
23
29
  is folded into `api --stream` (no separate `stream` verb). Sub-verbs and their
24
30
  aliases are canonical across CLI / API / MCP:
25
31