@tikoci/centrs 0.1.3 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/CHANGELOG.md +348 -0
  2. package/GLOSSARY.txt +252 -0
  3. package/LICENSE +21 -0
  4. package/README.md +44 -12
  5. package/commands/AGENTS.md +14 -5
  6. package/commands/api/AGENTS.md +13 -1
  7. package/commands/api/README.md +56 -26
  8. package/commands/api/examples.md +46 -1
  9. package/commands/btest/README.md +21 -32
  10. package/commands/check/README.md +6 -1
  11. package/commands/discover/README.md +5 -11
  12. package/commands/execute/README.md +77 -36
  13. package/commands/execute/examples.md +120 -10
  14. package/commands/explain/README.md +2756 -0
  15. package/commands/explain/examples.md +702 -0
  16. package/commands/mcp/README.md +7 -4
  17. package/commands/mcp/examples.md +2 -2
  18. package/commands/retrieve/README.md +25 -27
  19. package/commands/retrieve/examples.md +16 -0
  20. package/commands/terminal/examples.md +7 -1
  21. package/commands/transfer/README.md +28 -20
  22. package/commands/transfer/examples.md +29 -0
  23. package/docs/CLI.md +353 -0
  24. package/docs/CONSTITUTION.md +138 -30
  25. package/docs/MATRIX.md +92 -18
  26. package/docs/errors/README.md +3 -0
  27. package/docs/errors/quickchr/machine-not-found.md +22 -0
  28. package/docs/errors/quickchr/machine-stopped.md +20 -0
  29. package/docs/errors/quickchr/package-unavailable.md +27 -0
  30. package/docs/errors/quickchr/unsupported-via.md +35 -0
  31. package/docs/errors/quickchr/unsupported.md +35 -0
  32. package/docs/errors/routeros/version-unsupported.md +26 -0
  33. package/docs/errors/transport/provider-trust.md +29 -0
  34. package/docs/errors/usage/stdin-ignored.md +28 -0
  35. package/docs/errors/validation/syntax.md +29 -4
  36. package/docs/errors/validation/unknown-attribute.md +37 -4
  37. package/package.json +56 -10
  38. package/src/api-fanout.ts +46 -18
  39. package/src/api.ts +351 -121
  40. package/src/cli/api.ts +26 -7
  41. package/src/cli/btest.ts +19 -3
  42. package/src/cli/execute.ts +25 -6
  43. package/src/cli/explain.ts +503 -0
  44. package/src/cli/retrieve.ts +33 -1
  45. package/src/cli/selection.ts +71 -11
  46. package/src/cli/settings.ts +2 -2
  47. package/src/cli/terminal.ts +1 -1
  48. package/src/cli/transfer.ts +23 -6
  49. package/src/cli.ts +15 -2
  50. package/src/core/envelope.ts +58 -2
  51. package/src/core/error-catalog.ts +42 -1
  52. package/src/core/execute-command.ts +148 -0
  53. package/src/core/inspect.ts +36 -4
  54. package/src/core/preflight.ts +306 -0
  55. package/src/core/routeros-string.ts +24 -0
  56. package/src/core/version.ts +4 -0
  57. package/src/core/yaml.ts +86 -0
  58. package/src/errors.ts +1 -0
  59. package/src/execute-fanout.ts +47 -18
  60. package/src/execute.ts +339 -307
  61. package/src/explain/arg-tokens.ts +151 -0
  62. package/src/explain/args.ts +1749 -0
  63. package/src/explain/blocks.ts +175 -0
  64. package/src/explain/brace-slots.ts +143 -0
  65. package/src/explain/brace-tokens.ts +39 -0
  66. package/src/explain/catalog.ts +1343 -0
  67. package/src/explain/coordinates.ts +488 -0
  68. package/src/explain/defects.ts +218 -0
  69. package/src/explain/escape-tokens.ts +77 -0
  70. package/src/explain/highlight-projection.ts +308 -0
  71. package/src/explain/is-known-menu.ts +17 -0
  72. package/src/explain/menus.ts +668 -0
  73. package/src/explain/operator-tokens.ts +316 -0
  74. package/src/explain/operators.ts +465 -0
  75. package/src/explain/path-tokens.ts +150 -0
  76. package/src/explain/pathresolve.ts +1569 -0
  77. package/src/explain/quoted-string.ts +331 -0
  78. package/src/explain/scope-brace.ts +461 -0
  79. package/src/explain/segment.ts +1165 -0
  80. package/src/explain/separator.ts +187 -0
  81. package/src/explain/split-owner.ts +194 -0
  82. package/src/explain/string-tokens.ts +77 -0
  83. package/src/explain/symbol-values.ts +556 -0
  84. package/src/explain/symbols.ts +1617 -0
  85. package/src/explain/token-ranges.ts +72 -0
  86. package/src/explain/transport.ts +514 -0
  87. package/src/explain/value-tokens.ts +83 -0
  88. package/src/explain/values.ts +357 -0
  89. package/src/explain/verbs.ts +64 -0
  90. package/src/explain/verbsplit.ts +955 -0
  91. package/src/explain/write.ts +856 -0
  92. package/src/explain.ts +2602 -0
  93. package/src/index.ts +225 -6
  94. package/src/mcp/server.ts +2 -1
  95. package/src/offline-gate.ts +148 -0
  96. package/src/protocols/mac-telnet-console.ts +61 -17
  97. package/src/resolver/facts.ts +1 -1
  98. package/src/resolver/index.ts +23 -4
  99. package/src/resolver/quickchr-provider.ts +597 -0
  100. package/src/resolver/selection.ts +99 -12
  101. package/src/resolver/service-endpoint.ts +155 -0
  102. package/src/resolver/target.ts +1 -1
  103. package/src/retrieve-fanout.ts +27 -13
  104. package/src/retrieve.ts +82 -77
  105. package/src/settings.ts +2 -1
  106. package/src/transfer-fanout.ts +43 -26
  107. package/src/transfer.ts +88 -45
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
@@ -144,10 +149,12 @@ npk # MikroTik package format
144
149
  nvme-over-tcp # supported by RouterOS **with** rose-storage.npk extra-package
145
150
  OID
146
151
  PII # Personally Identifiable Information
152
+ PON # passive optical network; RouterOS /interface/ethernet/pon menu
147
153
  POV
148
154
  PRGA
149
155
  proplist
150
156
  pywinauto
157
+ QKD # quantum key distribution; RouterOS IPsec/keymat-provider vocabulary
151
158
  QML
152
159
  qll # CodeQL library file extension (.qll), e.g. CleartextLoggingCustomizations.qll
153
160
  qmljsdebugger
@@ -163,6 +170,7 @@ rosetta
163
170
  routeros
164
171
  routeros-skills
165
172
  rsc # RouterOS script / export file extension (.rsc)
173
+ rstp # Rapid Spanning Tree Protocol; a RouterOS `protocol-mode=` value on /interface/bridge
166
174
  SARIF # Static Analysis Results Interchange Format; CodeQL's code-scanning analysis download format
167
175
  scp
168
176
  scriptability
@@ -206,6 +214,7 @@ webproxy
206
214
  WinAppDriver
207
215
  winbox
208
216
  Wireshark
217
+ workstream
209
218
  workstreams
210
219
  XCUI
211
220
  XDG
@@ -269,6 +278,9 @@ choco
269
278
  coderabbit
270
279
  coderabbitai
271
280
 
281
+ # GitHub account Codex review threads are authored under (see pr-review-bots).
282
+ mobileskyfi
283
+
272
284
  # centrs domain vocabulary
273
285
  # %identity: the device /system/identity; a centrs CDB comment *lookup key*
274
286
  # (`identity=`) used to resolve `<router>`. Human handle, deliberately may
@@ -370,6 +382,20 @@ CSI
370
382
  SIGWINCH
371
383
  # evl: RouterOS :parse renders a parsed command as "(evl ...)" on the console.
372
384
  evl
385
+ # putmessage/globalname: RouterOS :parse IL statement heads — ":put x" echoes back
386
+ # as "(evl /putmessage=x)", ":global n v" as "(evl /globalname=$n;value=v)" (GH#230).
387
+ putmessage
388
+ globalname
389
+ # localname/setnote/runnumber: more of the same head rendering, quoted verbatim as
390
+ # evidence for the missing-separator rule (GH#311). ":put [:parse …]" joins the IL
391
+ # array with NO separator, so the path and its first argument run together —
392
+ # ":local a v" is "(evl /localname=$a;value=v)", "/system/note set note=hello" is
393
+ # "(evl /system/note/setnote=hello)", "/system/script/run /ip/route" is
394
+ # "(evl /system/script/runnumber=/ip/route)". Artefacts of the rendering, not
395
+ # RouterOS argument names; kept unsplit because the quote must match the bytes.
396
+ localname
397
+ setnote
398
+ runnumber
373
399
  # unconfigured: a freshly-flashed RouterOS device with no IP — the mac-telnet use case.
374
400
  unconfigured
375
401
  unwired
@@ -397,6 +423,7 @@ Weierstrass
397
423
  # ndjson: newline-delimited JSON; the stream / btest output contract (one envelope per line).
398
424
  ndjson
399
425
  # allocate-udp-ports-from: /tool/bandwidth-server base of the UDP data-port range.
426
+ allocate-udp-ports-from
400
427
  # srp5: the "5" suffix of EC-SRP5 (the MikroTik EC-SRP variant).
401
428
  srp5
402
429
  # SRV: the server-endpoint test variable in commands/btest/examples.md.
@@ -505,3 +532,228 @@ sfo
505
532
  ele
506
533
  GPX
507
534
  Geolocation
535
+ unparseable
536
+ Unparseable
537
+ versionless
538
+
539
+ # explain canonicalizer (phase-0 lab #185, commands/explain) vocabulary.
540
+ # SUT: "system under test" — the phase-0 lab's throwaway parser, scored
541
+ # against a `:parse` IL / highlight oracle; the ratified behavior is what the
542
+ # production modules under src/explain promote. CJK: the Chinese/Japanese/
543
+ # Korean Unicode range, used as a non-ASCII coordinate/segmentation fixture.
544
+ # pathresolve / blocktree: the Q3-Q4 / Q2 lab probe names, carried into the
545
+ # src/explain module + fixture names. klass: `class` is reserved, so the
546
+ # resolution feature-class field is `klass`. tostr / tostrvalue: the RouterOS
547
+ # `:tostr` directive and its IL-lowered form `/tostrvalue`. eworm: a corpus
548
+ # script author (eworm/*.rsc) cited as grounding for scope-name findings.
549
+ # rextended: another corpus script author, cited for the Q14 nested-bracket
550
+ # ambient-context case (a user function establishes no menu). ppp: the RouterOS
551
+ # /ppp menu, in the Q14 corner proving a `$` inside a `~` FILTER argument does
552
+ # not make the menu unreadable. strfind: a corpus user function in that corner.
553
+ # verbsplit: the Q6 verb/menu-boundary lab probe name, carried into the
554
+ # src/explain module + fixture names. myvar: the RouterOS example variable in
555
+ # the Q6 `:global myvar` collision corner (a positional operand indistinguishable
556
+ # from a sub-menu offline).
557
+ # failclosed: the ratified Q16 navigation arm name (the two arms that priced it
558
+ # stayed in the lab). notadirective: the Q16 fixture's deliberately unseen root
559
+ # directive, pinning that an unknown directive abstains rather than reading as
560
+ # harmless control flow.
561
+ # RouterOS root cmds (scripting directives) enumerated from /console/inspect and
562
+ # frozen in src/explain/write.ts as the closed ROOT_CMDS vocabulary — jobname,
563
+ # rndnum, rndstr, tobool, tocrlf, toid, toip, tolf, tonsec, tonum, totime.
564
+ # lte: the RouterOS /interface lte menu, cited in the Q16 long-tail-verb corner.
565
+ # dyn: the `$dyn` variable in the Q14 cascade corners — a dynamic-headed
566
+ # statement, which is context-NEUTRAL and must not poison the menu context.
567
+ # symbols (Q13): cls — the resolved variable-class field on a symbol occurrence
568
+ # (`class` is reserved, same reason as klass). sigilled: written with the `$`
569
+ # sigil, the schema-free tell that separates a variable from a directive or a
570
+ # menu-path segment. ERRVAR: the `:onerror NAME` error-variable slot (S18).
571
+ # unmodeled / distinguisher: the closure-scope finding — the lab left the rule
572
+ # unmodeled because a depth counter has no scope identity, and the statement
573
+ # head is the only distinguisher between a function `do={}` and a control-flow
574
+ # one. Corner variable names quoted from the device probes and corpus scripts:
575
+ # vlanid, vlanbytes, ipprefix, octive (the `$octive-1` name-vs-operator case),
576
+ # lppp (the device documentation's own `variable-undefined` example).
577
+ # lookback: the backward scan that decides whether a `{` follows `do=`; it must
578
+ # accept exactly the escapes the forward walker does, or the two disagree about
579
+ # where the argument name ends and a function body stops being a closure.
580
+ # tangentsoft: the maintainer/site (tangentsoft.com/mikrotik) behind the third
581
+ # corpus collection — 35 genuine `/export show-sensitive terse` device captures
582
+ # imported for the #203 genre census, and the corpus's only wholly-terse
583
+ # export stratum.
584
+ SUT
585
+ CJK
586
+ parseil # lsp-routeros-ts corpus table of captured `:parse` IL, per script per RouterOS version
587
+ pathresolve
588
+ blocktree
589
+ klass
590
+ tostr
591
+ tostrvalue
592
+ eworm
593
+ rextended
594
+ tangentsoft
595
+ ppp
596
+ strfind
597
+ verbsplit
598
+ myvar
599
+ failclosed
600
+ notadirective
601
+ jobname
602
+ rndnum
603
+ rndstr
604
+ tobool
605
+ tocrlf
606
+ toid
607
+ toip
608
+ tolf
609
+ tonsec
610
+ tonum
611
+ totime
612
+ lte
613
+ dyn
614
+ cls
615
+ sigilled
616
+ ERRVAR
617
+ unmodeled
618
+ distinguisher
619
+ vlanid
620
+ vlanbytes
621
+ ipprefix
622
+ octive
623
+ lppp
624
+ lookback
625
+
626
+ # RouterOS menu vocabulary reached by the explain menu table (#207).
627
+ # The generated table itself (src/explain/menus.ts) is cspell-ignored; these are
628
+ # the terms that also appear in hand-written prose and tests.
629
+ # %cliref: docs-derived RouterOS command reference in rosetta
630
+ # (experiments/cliref.db). A second opinion on node types, NOT a source for
631
+ # the menu table: its paths interleave the docs slug with the CLI path.
632
+ cliref
633
+ hotspot
634
+ igmp
635
+ ipfix
636
+ keymat
637
+ pmp
638
+ pptp
639
+ sstp
640
+ wlan
641
+ zerotier
642
+ cuu # `/terminal cuu` (cursor up) — a RouterOS command that reads as a bare menu path, so the table refuses it (#210)
643
+
644
+ # Hardware-gated RouterOS surfaces named when explaining why a published path is
645
+ # absent from a CHR-derived inspect tree (#228). No CHR carries these.
646
+ FDB # forwarding database — `/interface/ethernet/switch/{unicast,multicast,reserved}-fdb`, switch-chip only
647
+ MSRP # Stream Reservation Protocol (TSN) — `/interface/bridge/msrp`, gated by `conditions: MSRP_ENABLE`
648
+ PTP # Precision Time Protocol — `/system/ptp`, gated by `syscap: ptp`
649
+ syscap # a CLI-Reference applicability gate naming the hardware/system capability a path requires (`poe`, `lcd`, `multiswitch`, `nochr`)
650
+
651
+ # explain ANALYSIS/ORACLE vocabulary — not RouterOS menu names. These name the
652
+ # lab's measurement machinery and the properties it measures, and belong apart
653
+ # from the menu block above even though both arrived with the explain work.
654
+ importability # whether a document RouterOS emitted can be re-imported (#203 oracle level L2)
655
+ menuctx # the IL (`:parse`) statement type for menu navigation, as against `cmd` — the native nav-vs-command oracle (#210)
656
+
657
+ # #202c-1 argument-lexer vocabulary: one analysis term, plus the RouterOS names
658
+ # that appear verbatim in the corpus lines `test/unit/explain-args.test.ts`
659
+ # fixtures. The corpus lines are real forum scripts and are quoted rather than
660
+ # paraphrased, so their spellings are data, not prose.
661
+ renderable # of a statement: covered by a tested REST mapping rule, so `--curl` may render it (#202c-2)
662
+ wifiwave # `/interface wifiwave2` — the pre-7.13 RouterOS wifi menu, renamed `wifi` (drift the corpus carries)
663
+ nomaster # from a corpus line where two arguments ran together (`disabled=nomaster-interface=wifi1`); pinned as the first-`=`-splits case
664
+ CAINFO # `AT#CAINFO?` — an LTE modem AT command in a corpus `/interface/lte/at-chat` line
665
+ vzwinternet # Verizon LTE APN name, from a corpus `/interface lte apn add` line
666
+ rtr # short device name in an explain test input
667
+
668
+
669
+ # #228 path-catalog vocabulary: the CLI-Reference definition-module segments the
670
+ # alias allowlist maps away, the published gate values, and the prose terms used
671
+ # to describe the union. The generated table itself is cspell-ignored.
672
+ chancfg # caps-man definition module for `/caps-man/channel` — a doc spelling, not a CLI path
673
+ dpathcfg # caps-man definition module for `/caps-man/datapath`
674
+ ifaceactual # caps-man definition module for `/caps-man/actual-interface-configuration`
675
+ ratescfg # caps-man definition module for `/caps-man/rates`
676
+ remoteap # caps-man definition module for `/caps-man/remote-cap`
677
+ seccfg # caps-man definition module for `/caps-man/security`
678
+ ifaces # CLI-Reference definition module for `/tool/graphing/interface`
679
+ datapath # `/caps-man/datapath` — the real CLI menu behind `dpathcfg`
680
+ easymesh # `/interface/wifi/easymesh` — the doc spelling of `/interface/wifi/wps-push-button`
681
+ wps # Wi-Fi Protected Setup — `/interface/wifi/wps-push-button`
682
+ qos # switch-chip quality of service — `/interface/ethernet/switch/qos`, a real menu segment R1 must never drop
683
+ qca # Qualcomm Atheros — the `wireless-qca` package gate
684
+ multiswitch # syscap gate: more than one switch chip
685
+ oldswitch # syscap gate: the pre-Prestera switch-chip menu shape
686
+ smips # RouterOS architecture (single-core MIPS), named in `conditions:` gates
687
+ mmips # RouterOS architecture (multi-core MIPS), named in `conditions:` gates
688
+ CRS # MikroTik Cloud Router Switch — carries the switch-chip menus no CHR has
689
+ CCR # MikroTik Cloud Core Router
690
+ superlinear # growth faster than linear, used for resolveDocument wall-clock on separator-free input (#248)
691
+ superlinearity # the property a growth-ratio guard bounds; `superlinear+` is NOT usable here, cspell's compound form forbids the bare word (#313)
692
+ stmts # `explainCommand` corpus measurement: statement count (shorthand for `statements`)
693
+ argCandidates # `explainCommand` corpus measurement: arg-bearing candidates
694
+ argReadable # `explainCommand` corpus measurement: readable statements
695
+ ungated # of a published path: carrying no package/conditions/syscap that would explain its absence from a tree
696
+ unioned # of the two sources: merged with per-entry provenance rather than flattened
697
+ unioning # the act of the above
698
+ greppable # of a generated table: one entry per line, so `grep` finds a path
699
+ apos # the `&apos;` XML entity, decoded by the CLI-Reference parser
700
+
701
+ # #285 CLI-Reference reshape vocabulary: the second published inventory, and the
702
+ # gate values on the paths that publish twice.
703
+ 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
704
+ musicswitch # syscap gate: the Marvell-era switch-chip menu shape — one half of the `/interface/ethernet/switch` pair published under two gates
705
+ rbswitch # syscap gate: the RouterBOARD switch-chip menu shape — the other half of that pair
706
+ prestera # Marvell Prestera switch chip — named in the `crs_prestera` syscap gate on `/interface/ethernet/switch/qos`
707
+ xfrm # `/interface/xfrm` — the IPsec transform interface, device-confirmed by the pinned 7.25beta3 trees and published by the CLI Reference
708
+ btrfs # the Linux B-tree filesystem, a `/disk/btrfs/...` menu on RouterOS 7.20+
709
+
710
+ # #255 operator-axis vocabulary: the device-grounded operator surface.
711
+ arities # plural of arity — the operand counts an operator accepted, which is what separates `(> 1)` (deferred expression) from `(2 > 1)` (comparison)
712
+ typeofvalue # RouterOS IL renders `[:typeof $x]` as `/typeofvalue=$x` — the directive and its argument name run together with no separator
713
+ juxt # short for juxtaposition — the unnamed ` ( a b)` node RouterOS builds for space-separated paren contents; used in ids like `any-juxt`
714
+ 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
715
+ gapless # of a token partition: every byte in [0, input.bytes) belongs to exactly one token, sorted by start, no gaps (#289 B1)
716
+ SCIP # Source Code Intelligence Protocol — the symbol-identity axis for `data.tokens[]`'s later vocabulary (#264 B5)
717
+ unclassified # the provisional token class for a byte no analyzer claims; a first-class answer in the gapless partition (#289 B1)
718
+
719
+ # #290 B2 operator-fill test vocabulary
720
+ xand
721
+ andx
722
+ alnum
723
+ wpa # `security.authentication-types=wpa2-psk` — a wifi argument name whose interior dot the operator fill must not read as concatenation
724
+ psk # pre-shared key — the value half of the same grounded `wpa2-psk` example
725
+
726
+ # #293 B2 arg-fill vocabulary
727
+ retag # B5 may retag the `=` separator from the provisional `arg` class without moving byte coverage
728
+
729
+ # #239 S2 flow-sensitive symbol→value
730
+ occ # short for occurrence — local variable in symbol-values flow analysis
731
+ vid # short for valueId — result-local value occurrence identifier (v0, v1)
732
+ idx # short for index — loop/event index in flow merging
733
+ mis # prefix in mis-linking — hyphenated in comments for symbol-values guard
734
+
735
+ # #313 offline performance vocabulary
736
+ JIT # just-in-time compiler — warmed before either half of the growth-ratio timing so the two sizes are compared in the same compilation state
737
+
738
+ # #317 statement-ownership index vocabulary
739
+ laminarity # the property of a set of spans in which every pair is nested or disjoint, never crossing — what makes the shortest containing span reachable by a parent walk
740
+
741
+ # #264 B4 / #263 device-agreement vocabulary
742
+ unprojected # of a byte whose offline/device class PAIR the declared projection does not cover, so it can neither agree nor disagree: an offline class that deliberately maps to no RouterOS `highlight` class, or a device class this build has never seen (#264 B4)
743
+
744
+ # #324 / #325 offline-reading vocabulary. Both words are corpus data: they are
745
+ # quoted from the two slice scripts the device-agreement report names as the
746
+ # exemplars of the cells these fixes empty, so their spellings must match the
747
+ # bytes the device saw.
748
+ ytkids # an address-list name in the corpus line `/ip/firewall/address-list name=ytkids timeout=1h` — the #324 exemplar
749
+ apikey # a local variable in the corpus line `apikey-len=$([:len $apikey])` — the `$(…)` interpolation #325 deliberately leaves unreached
750
+
751
+ # #312 offline-entry boundary vocabulary
752
+ dgram # Node's `node:dgram` UDP socket module — reached by `mac-telnet`, and one of the builtins that made the old `explain` import graph unusable outside Bun; named in the boundary gate's allowlist rationale
753
+
754
+ # #347 / #348 offline false-reject controls. Both words are deliberate
755
+ # NON-words: each is the rejected half of a CHR-confirmed accepted/rejected
756
+ # pair, so its spelling is load-bearing and must not be "corrected".
757
+ elsy # the misspelled `else` in `:if (1=1) do={…} elsy {…}` — rejected as `expected end of command` on CHR 7.21.5/7.23.5/7.24.2/7.25beta3, which is what proves the bare-brace `else {` acceptance is a real device fact and not a probe artifact (#347)
758
+ prnt # a misspelt `print` verb — `/ip/address/prnt` is the offline analyzer's ABSTENTION fixture (`verdict: warn`, not `fail`), which is what proves the two-stage validation gate passes an abstention through to the device rather than refusing it (#354)
759
+ zzznotamenu # a menu name no RouterOS build has — `/ip { zzznotamenu { print } }` is a `syntax error` on device, so menu-scope resolution is schema-dependent and a catalog MISS must abstain rather than accept (#348)
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. |
@@ -163,7 +178,9 @@ and the comment grammar — lives in `commands/devices/README.md` and
163
178
 
164
179
  The load-bearing contract — envelope shape, error model, protocol selection,
165
180
  settings precedence, and the "done" definition — lives in
166
- `docs/CONSTITUTION.md`. The command×protocol status grid is `docs/MATRIX.md`.
181
+ `docs/CONSTITUTION.md`. Capability status and the selected work track,
182
+ including the command×protocol grid and offline analysis, live in
183
+ `docs/MATRIX.md`.
167
184
 
168
185
  ## Repository layout
169
186
 
@@ -172,19 +189,21 @@ code and authority docs live.
172
189
 
173
190
  - **`docs/CONSTITUTION.md`** — load-bearing rules (validation, envelope, error
174
191
  model, settings precedence, identity/CDB, protocol selection, done).
175
- - **`docs/MATRIX.md`** — command×protocol grid; the only status surface.
192
+ - **`docs/MATRIX.md`** — capability status and the selected work track (the
193
+ command×protocol grid plus offline analysis and frontend surfaces); the
194
+ only status surface.
176
195
  - **`commands/<name>/`** — per-command README and executable examples.
177
196
  - **`src/`** — Bun/TypeScript source.
178
197
  - **`test/`** — unit and CHR-backed integration tests.
179
198
 
180
- There is no `docs/specs/`, no `work/`, no roadmap doc. The matrix is the
181
- roadmap.
199
+ There is no `docs/specs/`, no `work/`, no roadmap doc. The matrix and the
200
+ GitHub issues it links are the roadmap.
182
201
 
183
202
  ## Development
184
203
 
185
204
  Requirements:
186
205
 
187
- - Bun 1.3.11 or newer (CI pinned to 1.3.13; Bun 2.x not yet validated).
206
+ - Bun 1.3.11 or newer (CI pinned to 1.3.14; Bun 2.x not yet validated).
188
207
  - Git.
189
208
  - For integration tests: QEMU plus `@tikoci/quickchr` (handles CHR image
190
209
  download and boot).
@@ -204,6 +223,19 @@ Generated API docs through TypeDoc:
204
223
  bun run build:doc:api
205
224
  ```
206
225
 
226
+ The `explain` censuses measure a RouterOS script corpus that lives in
227
+ [`tikoci/lsp-routeros-ts`](https://github.com/tikoci/lsp-routeros-ts), not in
228
+ this repo. `scripts/corpus-pin.json` records which snapshot centrs measures
229
+ against — a commit plus the blob's sha256 — and `corpus:fetch` downloads and
230
+ verifies it. A sibling `lsp-routeros-ts` checkout is used when present, and the
231
+ censuses print the source and hash they used:
232
+
233
+ ```bash
234
+ bun run corpus:fetch # download the pinned snapshot, verify its sha256
235
+ bun run explain:corpus-census # genre census (#203)
236
+ bun run explain:value-census # value-surface census (#225)
237
+ ```
238
+
207
239
  ## Done definition (short version)
208
240
 
209
241
  A feature is done when every line in `commands/<name>/examples.md` is green on
@@ -3,9 +3,16 @@
3
3
  Each `commands/<name>/` holds the **executable spec** for one command:
4
4
 
5
5
  - `README.md` — intent, flags, behavior. The "designed" tier.
6
- - `examples.md` — numbered, runnable examples. Each example is one assertion in
7
- the matching `test/integration/<name>.test.ts`; example N ↔ assertion N. This
8
- is what `CHR-passed` is measured against.
6
+ - `examples.md` — numbered, runnable examples mapped to named tests:
7
+ example N ↔ named test N. Live examples require CHR integration; offline
8
+ examples use the unit/fixture tests named by the command — a named test may
9
+ carry several assertions. Verification follows `docs/CONSTITUTION.md` →
10
+ Done definition.
11
+
12
+ **Implemented** flags are generated from `CliCommandMetadata` into
13
+ `docs/CLI.md` (`bun run docs:cli`; drift-gated in CI) — a command README keeps
14
+ behavior prose plus a "Designed, not implemented" table for spec-only flags,
15
+ never a duplicate table of implemented flags.
9
16
 
10
17
  Do not restate the constitution here or in a command README — link to
11
18
  `docs/CONSTITUTION.md` for envelope, errors, settings precedence, target
@@ -14,12 +21,14 @@ keys, ambiguity, `__default__`, comment-kv allowlist, write strategy) lives in
14
21
  `commands/devices/README.md` — the registry command owns it; the constitution
15
22
  keeps only the identity/CDB principle. A command file covers only what is
16
23
  specific to that command. `docs/MATRIX.md` is the only status surface; a
17
- README's "Status" line must agree with it.
24
+ README's "Status" line must agree with it. GitHub issues track the tasks and
25
+ dependencies within the selected capability; they do not replace its status.
18
26
 
19
27
  ## Verb vocabulary
20
28
 
21
29
  Commands are centrs **verbs** (`retrieve`, `execute`, `api`, `devices`,
22
- `discover`, `check`, `terminal`, `settings`), never one tool per RouterOS command. Open-ended follow
30
+ `discover`, `check`, `explain`, `terminal`, `settings`), never one tool per
31
+ RouterOS command. Open-ended follow
23
32
  is folded into `api --stream` (no separate `stream` verb). Sub-verbs and their
24
33
  aliases are canonical across CLI / API / MCP:
25
34
 
@@ -183,11 +183,23 @@ green on CHR 7.23.1.
183
183
  not `print`/`get`), so it is a `POST` and **write-classed** → needs `--yes`.
184
184
  - **CONFIRMED — the inspect gate over both transports.** Path existence
185
185
  (`request=child` empty ⇒ `validation/unknown-path`) and add/set attribute
186
- validity (`request=child`+`completion` ⇒ `validation/unknown-attribute`) fire
186
+ validity (`request=child` ⇒ `validation/unknown-attribute`) fire
187
187
  identically over rest-api and native-api, **before** any write — no `:put
188
188
  [:parse]` involved (api input is a path, not a CLI string). A `/execute` script
189
189
  is a CLI string ⇒ `meta.validation.semantic = "not-applicable"`.
190
190
 
191
+ ## CONFIRMED ON CHR (RouterOS 7.12.2 compatibility — GH#343, 2026-09-14)
192
+
193
+ - **Command-level `request=completion` is unsafe on 7.12.2.** For
194
+ `path=system,identity,print`, REST did not answer before the 10 s client
195
+ timeout and native API closed the connection immediately. Adding
196
+ `.proplist=completion` did not change either result. The preceding `:parse`
197
+ and `request=child` controls returned in 1–8 ms on both transports, and
198
+ `child` contained the complete command argument list. Execute/API attribute
199
+ validation therefore uses command-level `request=child` only. This does not
200
+ prohibit argument-level completion probes such as `print,proplist`, which are
201
+ a separate shape and returned immediately on the same 7.12.2 controls.
202
+
191
203
  ## CONFIRMED ON CHR (Phase 4 `--stream` follow — CHR 7.23.1, 2026-06-30)
192
204
 
193
205
  Validated by `test/integration/api-listen.test.ts` (L1–L4) on CHR 7.23.1.
@@ -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
 
@@ -98,19 +99,48 @@ vs `--query` distinction.
98
99
 
99
100
  ## Validation
100
101
 
101
- Default on. Because the input is a structured path (not a CLI string), the gate is
102
- `/console/inspect` — **not** `:put [:parse]`:
102
+ Default on. Because the input is a structured path (not a CLI string), the device
103
+ gate is `/console/inspect` — **not** `:put [:parse]`:
103
104
 
104
105
  - Path existence + per-verb attribute validity via `request=child`
105
106
  (`validation/unknown-path`, `validation/unknown-attribute`).
106
- - Value-level advice via `request=completion` (+`input=`) feeding `tips`/`warnings`.
107
+ - **Designed, not implemented:** value-level advice via `request=completion`
108
+ (+`input=`) feeding `tips`/`warnings`. Whatever implements it must not fall
109
+ back to `request=completion` at a *command* path (`path=` ending at the verb):
110
+ that shape hangs REST and closes native API on RouterOS 7.12.2, and no centrs
111
+ code uses it. Completion at an *argument* path (`print,proplist`, as `retrieve`
112
+ does) answers normally on the same build. See the 7.12.2 finding in
113
+ `AGENTS.md` (GH#343).
107
114
  - **Carve-out:** a script-shaped `POST /rest/execute` is a CLI string, not a path,
108
115
  so the inspect gate is `not-applicable` (`meta.validation.semantic`); RouterOS
109
- re-validates on the run.
110
-
111
- `--validate=false` (and `--raw`, which implies it) skip the preflight; RouterOS
112
- still re-validates writes server-side. Disabling validation to make a call pass is
113
- forbidden (constitution: validation is the product).
116
+ re-validates on the run. Until GH#354 that carve-out meant the script was sent
117
+ with *no* preflight at all. It is now gated by the offline stage (constitution:
118
+ validation) — the same analyzer `explain` publishes, run over the `script`
119
+ field with no connection, so a syntax fault is a byte span in
120
+ `error.context.span` instead of a round trip. `meta.validation.stages[]` shows
121
+ the offline stage `passed` and the device stage `skipped` with its reason. A
122
+ missing or blank `script` is still `input/invalid-command`, not a syntax
123
+ rejection.
124
+ - Structured path requests are **not** offline-gated. The input is a path plus a
125
+ body, not a CLI string, and an offline *path* gate collides with GH#211's
126
+ unlisted-path decision. On the success path `meta.validation.stages[]` is
127
+ absent there; on a `validation/*` rejection it is present and reports the
128
+ offline stage `skipped` with that reason, so the breakdown never implies an
129
+ analysis that did not run.
130
+
131
+ `--validate=false` skips **both** stages of the preflight — one flag, one meaning
132
+ (constitution: validation); RouterOS still re-validates writes server-side.
133
+ Disabling validation to make a call pass is forbidden (constitution: validation
134
+ is the product).
135
+
136
+ `--raw` **defaults** `--validate` to false — it does not override it (GH#154).
137
+ The resolution order is the explicit `--validate` flag, then `--raw`, then
138
+ `CENTRS_VALIDATE` / the CDB comment-kv / config, then the `true` default. So
139
+ `--raw` alone behaves as it always has and behaves the same on every machine
140
+ whatever the environment holds, while `--raw --validate=true` runs the gate and
141
+ reports a rejection in the `--raw` error shape (`{code,message}` on stderr,
142
+ nonzero exit, no `data` key) — which is how you debug `api` when centrs itself is
143
+ the suspect.
114
144
 
115
145
  ## Write confirmation
116
146