@tikoci/centrs 0.1.3 → 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 (90) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/GLOSSARY.txt +213 -0
  3. package/LICENSE +21 -0
  4. package/README.md +36 -8
  5. package/commands/AGENTS.md +7 -1
  6. package/commands/api/README.md +19 -18
  7. package/commands/api/examples.md +15 -0
  8. package/commands/btest/README.md +21 -32
  9. package/commands/check/README.md +6 -1
  10. package/commands/discover/README.md +5 -11
  11. package/commands/execute/README.md +41 -29
  12. package/commands/execute/examples.md +41 -3
  13. package/commands/explain/README.md +1716 -0
  14. package/commands/explain/examples.md +606 -0
  15. package/commands/retrieve/README.md +25 -27
  16. package/commands/retrieve/examples.md +16 -0
  17. package/commands/transfer/README.md +28 -20
  18. package/commands/transfer/examples.md +29 -0
  19. package/docs/CLI.md +353 -0
  20. package/docs/CONSTITUTION.md +66 -15
  21. package/docs/MATRIX.md +38 -1
  22. package/docs/errors/README.md +3 -0
  23. package/docs/errors/quickchr/machine-not-found.md +22 -0
  24. package/docs/errors/quickchr/machine-stopped.md +20 -0
  25. package/docs/errors/quickchr/package-unavailable.md +27 -0
  26. package/docs/errors/quickchr/unsupported-via.md +35 -0
  27. package/docs/errors/quickchr/unsupported.md +35 -0
  28. package/docs/errors/routeros/version-unsupported.md +26 -0
  29. package/docs/errors/transport/provider-trust.md +29 -0
  30. package/docs/errors/usage/stdin-ignored.md +28 -0
  31. package/docs/errors/validation/unknown-attribute.md +37 -4
  32. package/package.json +45 -10
  33. package/src/api-fanout.ts +29 -14
  34. package/src/api.ts +97 -54
  35. package/src/cli/api.ts +25 -6
  36. package/src/cli/btest.ts +18 -2
  37. package/src/cli/execute.ts +25 -6
  38. package/src/cli/explain.ts +503 -0
  39. package/src/cli/retrieve.ts +33 -1
  40. package/src/cli/selection.ts +71 -11
  41. package/src/cli/settings.ts +2 -2
  42. package/src/cli/transfer.ts +23 -6
  43. package/src/cli.ts +15 -2
  44. package/src/core/envelope.ts +5 -2
  45. package/src/core/error-catalog.ts +40 -0
  46. package/src/core/preflight.ts +306 -0
  47. package/src/core/routeros-string.ts +24 -0
  48. package/src/errors.ts +1 -0
  49. package/src/execute-fanout.ts +29 -14
  50. package/src/execute.ts +114 -63
  51. package/src/explain/arg-tokens.ts +114 -0
  52. package/src/explain/args.ts +1403 -0
  53. package/src/explain/blocks.ts +132 -0
  54. package/src/explain/brace-slots.ts +143 -0
  55. package/src/explain/brace-tokens.ts +38 -0
  56. package/src/explain/catalog.ts +1345 -0
  57. package/src/explain/coordinates.ts +329 -0
  58. package/src/explain/defects.ts +217 -0
  59. package/src/explain/is-known-menu.ts +17 -0
  60. package/src/explain/menus.ts +664 -0
  61. package/src/explain/operator-tokens.ts +310 -0
  62. package/src/explain/operators.ts +458 -0
  63. package/src/explain/path-tokens.ts +150 -0
  64. package/src/explain/pathresolve.ts +1324 -0
  65. package/src/explain/quoted-string.ts +241 -0
  66. package/src/explain/scope-brace.ts +308 -0
  67. package/src/explain/segment.ts +757 -0
  68. package/src/explain/string-tokens.ts +67 -0
  69. package/src/explain/symbol-values.ts +520 -0
  70. package/src/explain/symbols.ts +1597 -0
  71. package/src/explain/token-ranges.ts +72 -0
  72. package/src/explain/transport.ts +497 -0
  73. package/src/explain/value-tokens.ts +80 -0
  74. package/src/explain/values.ts +357 -0
  75. package/src/explain/verbs.ts +64 -0
  76. package/src/explain/verbsplit.ts +813 -0
  77. package/src/explain/write.ts +815 -0
  78. package/src/explain.ts +2152 -0
  79. package/src/index.ts +206 -4
  80. package/src/protocols/mac-telnet-console.ts +61 -17
  81. package/src/resolver/facts.ts +1 -1
  82. package/src/resolver/index.ts +23 -4
  83. package/src/resolver/quickchr-provider.ts +597 -0
  84. package/src/resolver/selection.ts +99 -12
  85. package/src/resolver/service-endpoint.ts +155 -0
  86. package/src/resolver/target.ts +1 -1
  87. package/src/retrieve-fanout.ts +26 -12
  88. package/src/retrieve.ts +81 -18
  89. package/src/transfer-fanout.ts +42 -25
  90. package/src/transfer.ts +87 -44
package/CHANGELOG.md CHANGED
@@ -8,6 +8,45 @@ 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
+
11
50
  ## 0.1.3 — 2026-07-06
12
51
 
13
52
  Patch release. `@tikoci/centrs@0.1.3`.
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
@@ -163,6 +168,7 @@ rosetta
163
168
  routeros
164
169
  routeros-skills
165
170
  rsc # RouterOS script / export file extension (.rsc)
171
+ rstp # Rapid Spanning Tree Protocol; a RouterOS `protocol-mode=` value on /interface/bridge
166
172
  SARIF # Static Analysis Results Interchange Format; CodeQL's code-scanning analysis download format
167
173
  scp
168
174
  scriptability
@@ -206,6 +212,7 @@ webproxy
206
212
  WinAppDriver
207
213
  winbox
208
214
  Wireshark
215
+ workstream
209
216
  workstreams
210
217
  XCUI
211
218
  XDG
@@ -269,6 +276,9 @@ choco
269
276
  coderabbit
270
277
  coderabbitai
271
278
 
279
+ # GitHub account Codex review threads are authored under (see pr-review-bots).
280
+ mobileskyfi
281
+
272
282
  # centrs domain vocabulary
273
283
  # %identity: the device /system/identity; a centrs CDB comment *lookup key*
274
284
  # (`identity=`) used to resolve `<router>`. Human handle, deliberately may
@@ -370,6 +380,10 @@ CSI
370
380
  SIGWINCH
371
381
  # evl: RouterOS :parse renders a parsed command as "(evl ...)" on the console.
372
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
373
387
  # unconfigured: a freshly-flashed RouterOS device with no IP — the mac-telnet use case.
374
388
  unconfigured
375
389
  unwired
@@ -397,6 +411,7 @@ Weierstrass
397
411
  # ndjson: newline-delimited JSON; the stream / btest output contract (one envelope per line).
398
412
  ndjson
399
413
  # allocate-udp-ports-from: /tool/bandwidth-server base of the UDP data-port range.
414
+ allocate-udp-ports-from
400
415
  # srp5: the "5" suffix of EC-SRP5 (the MikroTik EC-SRP variant).
401
416
  srp5
402
417
  # SRV: the server-endpoint test variable in commands/btest/examples.md.
@@ -505,3 +520,201 @@ sfo
505
520
  ele
506
521
  GPX
507
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,12 +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 inspect the CLI without installing it:
7
+ Preview builds are published on npm. To try the current offline analyzer without
8
+ installing centrs or connecting to a router:
8
9
 
9
10
  ```bash
10
- bunx @tikoci/centrs --help
11
+ bunx @tikoci/centrs@next explain '/ip/address print' --json
11
12
  ```
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
+
13
22
  It is a **friendly conduit**, not a high-level configuration abstraction. It
14
23
  helps humans and agents reach RouterOS over the right protocol, with the right
15
24
  credentials and ports, and validates RouterOS-shaped commands before running
@@ -17,9 +26,12 @@ them. It does **not** hide RouterOS behind helpers like `createVlanOnBridge()`
17
26
  — you still speak RouterOS. Validation and structured diagnostics are the
18
27
  product; without them this would just be a worse `curl`.
19
28
 
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
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
23
35
  > `npm view @tikoci/centrs dist-tags --json`. The library, device registry, and
24
36
  > first MCP server phases are CHR-verified; the CLI is wired (`coded`);
25
37
  > encrypted-CDB writes are still blocked, and the TUI/proxy frontends are planned.
@@ -35,8 +47,10 @@ product; without them this would just be a worse `curl`.
35
47
  SSH / MAC-Telnet / RoMON / WinBox Terminal for interactive and L2 execute;
36
48
  MNDP for discovery. centrs picks the protocol for you (and lets you pin one
37
49
  with `via=` or `--via`).
38
- - **Commands:** every command resolves a *target* — a router you name on the
39
- 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.)
40
54
 
41
55
  | Command | Purpose |
42
56
  | ---------- | ------- |
@@ -45,6 +59,7 @@ product; without them this would just be a worse `curl`.
45
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). |
46
60
  | `terminal` | Open an interactive console, primarily SSH or MAC-Telnet. |
47
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. |
48
63
  | `devices` | View and maintain the CDB-backed device registry (the only writer). |
49
64
  | `discover` | Discover MNDP neighbors and optionally save them into the CDB. |
50
65
  | `settings` | Manage centrs's own settings (`centrs.env`, `__default__`) and run first-time setup. |
@@ -184,7 +199,7 @@ roadmap.
184
199
 
185
200
  Requirements:
186
201
 
187
- - 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).
188
203
  - Git.
189
204
  - For integration tests: QEMU plus `@tikoci/quickchr` (handles CHR image
190
205
  download and boot).
@@ -204,6 +219,19 @@ Generated API docs through TypeDoc:
204
219
  bun run build:doc:api
205
220
  ```
206
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
+
207
235
  ## Done definition (short version)
208
236
 
209
237
  A feature is done when every line in `commands/<name>/examples.md` is green on
@@ -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
 
@@ -57,24 +57,25 @@ when a bare-collection `POST` carries create-looking fields.
57
57
 
58
58
  ## Flags
59
59
 
60
- | Flag | Behavior |
61
- | ---- | -------- |
62
- | `-X` / `--method <verb>` | HTTP method, default `GET`, case-insensitive. |
63
- | `-f <key=value>` | String field, repeatable; assembled into the JSON body. Values pass through verbatim (no type-guessing). |
64
- | `-d` / `--data <json>` | Raw JSON request body. Collides with `-f` → `usage/conflicting-flags`. |
65
- | `--input <file\|->` | Read the raw JSON body from a file or stdin (`-`). |
66
- | `--query` / `--filter <expr>` | RouterOS-side row filter, AND-combined, repeatable. `name=value` (eq), `name!=value` (ne), `name>value`/`name<value` (cmp), `name` (has-property). Maps to REST `.query` words / native `?` words. |
67
- | `--raw-query <word>` | Verbatim RouterOS query word (repeatable) for OR / absence / stack expressions — e.g. `--raw-query type=ether --raw-query type=vlan --raw-query '#\|'` (OR). Emitted as-is; the caller owns the stack. |
68
- | `--attribute` / `--proplist <a,b>` | Property projection → `.proplist`. |
69
- | `--raw` | Strip the envelope; emit bare RouterOS JSON. Implies `--validate=false`; does **not** imply `--yes`. |
70
- | `--yes` | Confirm a mutating (non-read) request in non-interactive runs. |
71
- | `--stream` (alias `--listen`) | Native-api-only open-ended follow → NDJSON stream of envelopes, ending with a summary envelope. Inferred from a `/listen` endpoint. `--via rest-api --stream` errors `transport/capability-unsupported` (REST's 60 s cap cannot follow). |
72
- | `--duration <dur>` / `--count <n>` | Bound a `--stream`: stop after a wall-clock window / after N frames. |
73
- | `--validate[=false]` | Default `true`. Gate is `/console/inspect` (see Validation). |
74
- | `--via <protocol>` | `rest-api` (default) or `native-api`. No silent downgrade. |
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. |
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`. |
60
+ Implemented flags are generated from the CLI metadata into
61
+ [`docs/CLI.md` → api](../../docs/CLI.md#api); this file does not duplicate
62
+ that table, and `api` has no designed-but-unimplemented flags. Behavior notes
63
+ the generated reference cannot carry:
64
+
65
+ - `-f` values pass through verbatim (no type-guessing); `-d`/`--input`
66
+ collide with `-f` → `usage/conflicting-flags`.
67
+ - `--query`/`--filter` map to REST `.query` words / native `?` words.
68
+ - `--raw-query` is emitted as-is; the caller owns the stack — e.g.
69
+ `--raw-query type=ether --raw-query type=vlan --raw-query '#|'` (OR).
70
+ - `--via rest-api --stream` errors `transport/capability-unsupported`
71
+ (REST's 60 s cap cannot follow).
72
+ - Under `--stream`, `--format json`/`yaml` emit one compact envelope per
73
+ line (NDJSON); `text` emits a concise row per frame.
74
+ - The target/auth flags (`--host`, `--port`, `--username`, `--password`,
75
+ `--insecure`, `--timeout`, `--cdb-file`, `--cdb-password`, `--resolve`)
76
+ use the same single-target resolver as `retrieve`/`execute`; `--quickchr`
77
+ resolves host/port/auth from the live `@tikoci/quickchr` descriptor
78
+ instead (constitution: resolution providers).
78
79
 
79
80
  ## Fan-out (multi-target)
80
81
 
@@ -426,3 +426,18 @@ The reserved record is a credential fallback, not a connectable router, so its o
426
426
  target fails deterministically: inner `ok:false` with
427
427
  `error.code=target/unresolved` (never a `transport/dns` attempt on `"__default__"`).
428
428
  `data.summary = { total: 1, ok: 0, failed: 1 }`. Exit code 1 (every target failed).
429
+
430
+ ## quickchr targets (#134)
431
+
432
+ `$NAME` is the machine name of a running quickchr-managed CHR. Host/port/auth
433
+ come from the live descriptor (see `commands/retrieve/examples.md` Q-series).
434
+ Covered by `test/integration/quickchr-target.test.ts`.
435
+
436
+ ### Q1. GET against a quickchr target
437
+
438
+ ```bash
439
+ centrs api --quickchr $NAME system/resource --json
440
+ ```
441
+
442
+ Envelope: `ok: true`, `data` is the resource object,
443
+ `meta.target.source.kind=provider`.
@@ -80,38 +80,27 @@ centrs btest server [--authenticate[=false]] [--user U] [--password P] \
80
80
 
81
81
  ## Flags
82
82
 
83
- ### `btest client <router>`
84
-
85
- | Flag | RouterOS analog / default | Notes |
86
- | ---- | ------------------------- | ----- |
87
- | `--protocol <udp\|tcp>` | `protocol`, default `udp` | UDP measures raw throughput + loss; TCP measures goodput. |
88
- | `--direction <receive\|transmit\|both>` | `direction`, default `receive` | `receive` = download (server→client), `transmit` = upload. |
89
- | `--duration <dur>` | `duration` | Bounds the run; reuses `parseDuration`. Omit for open-ended (Ctrl-C). |
90
- | `--interval <dur>` | `interval`, default `1s` | Report cadence (`20ms`..`5s`). One report frame per interval. |
91
- | `--connection-count <n>` | `connection-count` (1..255) | **TCP only.** Parallel TCP data connections. centrs opens the negotiated extra connections (join format grounded against RouterOS 7.23.1) so throughput aggregates across streams. **Authenticated (EC-SRP5) sessions stay single-stream** — the post-auth token is not captured — and emit a `routeros/btest-connection-count-single-stream` warning. |
92
- | `--local-udp-tx-size <n>` | `local-udp-tx-size` (28..64000) | **UDP only.** Client→server packet size. |
93
- | `--remote-udp-tx-size <n>` | `remote-udp-tx-size` (28..64000) | **UDP only.** Server→client packet size. |
94
- | `--local-tx-speed <bps>` | `local-tx-speed` | Cap on client→server rate (bits/sec). |
95
- | `--remote-tx-speed <bps>` | `remote-tx-speed` | Cap on server→client rate (bits/sec). |
96
- | `--random-data` | `random-data`, default off | Incompressible payload (defeats link compression; CPU-heavier). |
97
- | `--user` / `--password` | `user` / `password` | Fall back to CDB / `CENTRS_*`. Required when the server has `authenticate=yes`. |
98
- | `--port <n>` | — (default `2000`) | Control port override. |
99
- | `--format <text\|csv\|json\|yaml>` | — (default `text`) | `text` = live human reports; `csv` = streaming CSV records; `json`/`yaml` = final summary envelope. |
100
- | `--csv` | — | Shortcut for `--format csv`. |
101
-
102
- ### `btest server`
103
-
104
- | Flag | RouterOS analog / default | Notes |
105
- | ---- | ------------------------- | ----- |
106
- | `--authenticate[=false]` | `authenticate`, **default `true`** | Require EC-SRP5 auth; `--authenticate=false` accepts anonymous clients. |
107
- | `--user` / `--password` | — | The accepted credential when `authenticate=true` (v1: one user). Falls back to `CENTRS_*`. |
108
- | `--bind <addr>` | **centrs-only** | Listen address; **default `127.0.0.1`**. Use `0.0.0.0` to expose on the LAN. |
109
- | `--port <n>` | — (default `2000`) | Control-connection port. |
110
- | `--allocate-udp-ports-from <n>` | `allocate-udp-ports-from` | Base of the UDP data-port range; default `2001` (just above the TCP/2000 control port). |
111
- | `--max-sessions <n>` | `max-sessions` (1..1000), default `100` | Concurrent test cap; further clients get `routeros/btest-too-many-sessions`. |
112
- | `--duration <dur>` | — | Optional auto-stop; otherwise runs until Ctrl-C. |
113
- | `--format <text\|csv\|json\|yaml>` | — (default `text`) | `text` = human session log; `csv` = streaming CSV records; `json`/`yaml` = final summary envelope. |
114
- | `--csv` | — | Shortcut for `--format csv`. |
83
+ Implemented flags are generated from the CLI metadata into
84
+ [`docs/CLI.md` → btest](../../docs/CLI.md#btest) — one table for both
85
+ subcommands, with `(client)`/`(server)` markers scoping each flag; "did you
86
+ mean?" hints are scoped to the active mode. This file does not duplicate that
87
+ table. Behavior notes the generated reference cannot carry:
88
+
89
+ - Flag names mirror the RouterOS analogs verbatim (`protocol`, `direction`,
90
+ `local-udp-tx-size`, `connection-count`, `authenticate`,
91
+ `allocate-udp-ports-from`, …); centrs-only additions are `--bind`, `--port`,
92
+ the shared `--format`/`--csv`, and the CDB credential flags.
93
+ - `--direction receive` = download (server→client), `transmit` = upload.
94
+ - `--connection-count` is TCP-only; **authenticated (EC-SRP5) sessions stay
95
+ single-stream** — the post-auth token is not captured — and emit a
96
+ `routeros/btest-connection-count-single-stream` warning (see Open
97
+ questions).
98
+ - `--user`/`--password` fall back to CDB / `CENTRS_*`; required when the
99
+ server has `authenticate=yes`. On the server they are the accepted
100
+ credential (v1: one user).
101
+ - Option ranges (`28..64000` UDP sizes, `1..255` connections, `20ms..5s`
102
+ interval, `1..1000` sessions) are validated before any socket opens (see
103
+ Errors).
115
104
 
116
105
  ## How it works
117
106
 
@@ -197,6 +197,11 @@ triggered/enrichment checks; `--fast` suppresses progressive triggers.
197
197
 
198
198
  ## Flags
199
199
 
200
+ The whole command is spec-tier (`designed` — nothing is implemented yet), so
201
+ this table is the design, not a reference; once `check` lands, its
202
+ implemented flags will be generated into `docs/CLI.md` like every other
203
+ command's.
204
+
200
205
  | Flag | Behavior |
201
206
  | ---- | -------- |
202
207
  | `--preset <name>` | `reach` / `health` (default) / `exposure` / `full`. |
@@ -243,7 +248,7 @@ RouterOS commands vary by version and arch, so a check must not blow up on a
243
248
  device that lacks the path it reads. `resource` runs first and yields the **live**
244
249
  version / arch / board; the orchestrator pre-flights each remaining check's
245
250
  `requires` paths against that live value (falling back to the stored
246
- `routeros_version` derived fact, or one batched `/console/inspect` when neither is
251
+ `version` derived fact, or one batched `/console/inspect` when neither is
247
252
  conclusive), and marks an unsupported check `ran:false` with
248
253
  `skipReason:"unsupported-on-<version>"` — a graceful skip, not a raw protocol
249
254
  error. Grounding is offline-first against restraml's historic `inspect.json`
@@ -53,17 +53,11 @@ tested without a router (crafted packet fixtures + a loopback socket).
53
53
 
54
54
  ## Flags and defaults
55
55
 
56
- | Flag | Default | Meaning |
57
- | ---------------- | ------------- | ---------------------------------------------------- |
58
- | `--timeout` | `15s` | Listen window before results are returned. First refresh is sent immediately. |
59
- | `--save` | off | Persist discovered neighbors into the CDB. |
60
- | `--group` | `discovered` | First-class CDB group assigned to saved entries. |
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