@tikoci/centrs 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +256 -0
- package/GLOSSARY.txt +32 -0
- package/README.md +2 -2
- package/SECURITY.md +1 -1
- package/commands/AGENTS.md +2 -1
- package/commands/api/AGENTS.md +55 -30
- package/commands/api/README.md +123 -41
- package/commands/api/examples.md +205 -17
- package/commands/devices/README.md +11 -3
- package/commands/execute/README.md +31 -3
- package/commands/execute/examples.md +136 -1
- package/commands/explain/README.md +30 -4
- package/commands/mcp/README.md +5 -1
- package/commands/mcp/examples.md +18 -1
- package/commands/retrieve/README.md +348 -4
- package/commands/retrieve/examples.md +422 -5
- package/commands/terminal/README.md +14 -4
- package/commands/terminal/examples.md +22 -3
- package/commands/transfer/README.md +55 -9
- package/commands/transfer/examples.md +106 -7
- package/docs/CLI.md +28 -20
- package/docs/CONSTITUTION.md +30 -10
- package/docs/MATRIX.md +38 -1
- package/docs/errors/README.md +3 -1
- package/docs/errors/input/invalid-query.md +36 -0
- package/docs/errors/input/local-destination.md +20 -0
- package/docs/errors/input/unsupported-query.md +19 -0
- package/docs/errors/internal/byte-string.md +13 -0
- package/docs/errors/settings/invalid-timeout.md +6 -0
- package/docs/errors/transport/stream-overflow.md +21 -0
- package/docs/errors/usage/removed-flag.md +20 -0
- package/docs/errors/validation/menu-unavailable.md +22 -0
- package/docs/errors/validation/not-followable.md +23 -0
- package/docs/errors/validation/package-missing.md +29 -0
- package/docs/errors/validation/unknown-path.md +16 -4
- package/docs/errors/wait/deadline-exceeded.md +14 -0
- package/docs/errors/wait/interrupted.md +10 -0
- package/package.json +14 -11
- package/src/absent-menu.ts +280 -0
- package/src/api-fanout.ts +22 -12
- package/src/api.ts +511 -186
- package/src/btest.ts +19 -17
- package/src/cli/api.ts +126 -34
- package/src/cli/execute.ts +18 -5
- package/src/cli/retrieve.ts +223 -22
- package/src/cli/terminal.ts +10 -5
- package/src/cli/transfer.ts +23 -8
- package/src/core/envelope.ts +14 -0
- package/src/core/error-catalog.ts +56 -1
- package/src/core/inspect.ts +43 -5
- package/src/core/query.ts +508 -0
- package/src/core/routeros-errors.ts +1 -1
- package/src/devices.ts +6 -9
- package/src/discover.ts +7 -8
- package/src/errors.ts +116 -7
- package/src/execute-fanout.ts +13 -8
- package/src/execute.ts +106 -65
- package/src/explain/catalog.ts +58 -10
- package/src/explain/menus.ts +23 -2
- package/src/explain.ts +6 -6
- package/src/index.ts +29 -1
- package/src/mcp/safety.ts +43 -18
- package/src/mcp/tools.ts +14 -17
- package/src/protocols/adapter.ts +224 -118
- package/src/protocols/btest-session.ts +3 -2
- package/src/protocols/endpoint.ts +10 -0
- package/src/protocols/mac-telnet-console.ts +6 -3
- package/src/protocols/native-api.ts +223 -70
- package/src/protocols/sftp.ts +44 -14
- package/src/protocols/ssh.ts +42 -14
- package/src/resolver/cdb.ts +28 -7
- package/src/resolver/index.ts +1 -0
- package/src/resolver/target.ts +53 -7
- package/src/retrieve-fanout.ts +51 -12
- package/src/retrieve-follow.ts +862 -0
- package/src/retrieve-sample.ts +244 -0
- package/src/retrieve-stream.ts +115 -0
- package/src/retrieve-wait.ts +286 -0
- package/src/retrieve.ts +732 -108
- package/src/settings.ts +15 -15
- package/src/transfer-fanout.ts +40 -15
- package/src/transfer.ts +234 -32
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,262 @@ The product evolves as a stream of changes; there are no alpha gates.
|
|
|
6
6
|
Per-feature status is tracked in `docs/MATRIX.md`. Use this file for
|
|
7
7
|
documenting cross-cutting shifts that affect contributors and consumers.
|
|
8
8
|
|
|
9
|
+
## Unreleased
|
|
10
|
+
|
|
11
|
+
## 0.1.8 — 2026-10-12
|
|
12
|
+
|
|
13
|
+
Release (npm tag `latest`; `next` moves forward to 0.1.8). Everything in
|
|
14
|
+
0.1.8-next.0 below, plus two fixes from field-testing it.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- `retrieve --wait` with no deadline value names `--wait` (#445). Written as a
|
|
19
|
+
bare switch, `--wait --until 'expr'` read `--until` as the deadline and
|
|
20
|
+
shifted the predicate into a second target, so the CLI reported
|
|
21
|
+
`usage/fanout-not-supported`. It now fails `settings/invalid-timeout`
|
|
22
|
+
("Received the flag: --until"), and every deadline without a unit gets the
|
|
23
|
+
`--wait`-specific message. A real multi-target `--wait` still gets the
|
|
24
|
+
fanout refusal.
|
|
25
|
+
- MCP accepts a registered device by its `identity=`, `mac=` or `ip=` comment
|
|
26
|
+
key, as the CLI does (#444). The allowlist gate now matches with the same
|
|
27
|
+
resolver the call dials with, so the `mcp=` write policy checked is the
|
|
28
|
+
policy of the record the call runs against. A name on more than one record
|
|
29
|
+
fails `identity/ambiguous` with the candidates in `context.matches` and an
|
|
30
|
+
MCP remediation, instead of resolving to the first.
|
|
31
|
+
|
|
32
|
+
## 0.1.8-next.0 — 2026-10-12
|
|
33
|
+
|
|
34
|
+
Pre-release (npm tag `next`; `latest` stays on 0.1.7). Install with
|
|
35
|
+
`bunx @tikoci/centrs@next`.
|
|
36
|
+
|
|
37
|
+
### Maintenance
|
|
38
|
+
|
|
39
|
+
- Refresh vulnerable dependencies within existing ranges and use Bun's
|
|
40
|
+
Dependabot ecosystem for `bun.lock` (#390). Preserve the dependency group
|
|
41
|
+
and the `has-flag` major-version ignore. Remaining dependency advisories
|
|
42
|
+
and updater verification are tracked in #390.
|
|
43
|
+
- Pre-release dependency alignment (#390): runtime ranges now floor at the
|
|
44
|
+
tested versions (`@modelcontextprotocol/sdk ^1.32.1`, `zod ^4.6.7`),
|
|
45
|
+
`smol-toml` is overridden past its advisories, and `@types/bun` is pinned
|
|
46
|
+
exactly to the 1.3.14 runtime (re-pin tracked in #438). The two residual
|
|
47
|
+
advisories (`braces`, `katex`) are dev-tool-only and do not ship in the
|
|
48
|
+
package.
|
|
49
|
+
|
|
50
|
+
### Added
|
|
51
|
+
|
|
52
|
+
- **`retrieve --wait` establishes readiness or a condition by a deadline (#364).**
|
|
53
|
+
One final envelope over REST or native API, with elapsed time, attempts and
|
|
54
|
+
completed observations. `--until` uses the shared query grammar and succeeds
|
|
55
|
+
on any matching selected row; `--until-empty` requires a completed zero-row
|
|
56
|
+
read. Already-true conditions succeed immediately. Only transient transport
|
|
57
|
+
errors retry; authentication and validation fail immediately. `--timeout`
|
|
58
|
+
bounds each request, and optional `--sample` sets observation cadence.
|
|
59
|
+
- **`retrieve --follow` keeps a menu's rows current over native API (#396).**
|
|
60
|
+
Snapshot rows, one `synced` line, then a line per change, as NDJSON; an
|
|
61
|
+
`.id` sweep (`--sweep`, default 10s) reports removals RouterOS never sends
|
|
62
|
+
on view menus. Single target; `--query` / `--filter` (#397) use device
|
|
63
|
+
membership reads and filtered sweeps, with membership removals distinguished
|
|
64
|
+
from wire deletions. `--sweep`
|
|
65
|
+
needs a unit (`10s`); only `0` (off) may be bare.
|
|
66
|
+
- **`retrieve --sample <interval>` re-reads a menu on a timer.** One line per
|
|
67
|
+
complete read (`data` is the whole read), then a summary; `--count` and
|
|
68
|
+
`--duration` bound it. Works over REST and native API, on list menus and
|
|
69
|
+
singletons, and sees counters `--follow` cannot. The interval needs a unit
|
|
70
|
+
(`5s`): a bare number is refused because RouterOS and centrs read it
|
|
71
|
+
differently.
|
|
72
|
+
- **`retrieve --query` filters rows on the router (#397).** Write what you
|
|
73
|
+
would after `print where` (`type=ether and !disabled`, `mtu>=1500`,
|
|
74
|
+
`(a=1 or b=2)`); centrs compiles it to the API query, so the router filters.
|
|
75
|
+
`--filter` is the same flag; repeats are AND-ed. Property names are
|
|
76
|
+
validated, and bare names are asked of the router (`disabled` → `=yes`,
|
|
77
|
+
`comment` → "is set"). `~`, `in` and script values fail
|
|
78
|
+
`input/unsupported-query`. Works with `--sample`, fan-out and `--follow`.
|
|
79
|
+
Checked on CHR against RouterOS's own `find where`.
|
|
80
|
+
- `--format ndjson` for `retrieve`. Library: `retrieveFollow()`,
|
|
81
|
+
`retrieveSample()`, and `renderRetrieveStreamLine()` for both (it replaces
|
|
82
|
+
the unreleased `renderRetrieveFollowLine()`).
|
|
83
|
+
|
|
84
|
+
### Fixed
|
|
85
|
+
|
|
86
|
+
- `execute` honors an explicit target URL, and native-api honors a typed port
|
|
87
|
+
(#426, from #384). With no `via` pinned, an `http(s)://host[:port]` target
|
|
88
|
+
selects rest-api on that scheme and port instead of silently dialing native
|
|
89
|
+
API on 8728; `meta.settings.via` reports `via:target-url`. A port typed into
|
|
90
|
+
the target (`host:9`, `--host`, `CENTRS_HOST`) now reaches native-api on
|
|
91
|
+
every command, below the port settings (`--port`, `CENTRS_PORT`, `port=`,
|
|
92
|
+
config), as it already did for ssh and REST. An explicit `:80`/`:443`, which
|
|
93
|
+
URL parsing normalizes away, is kept for native-api and ssh. A matched CDB
|
|
94
|
+
record's own port is unchanged. A `--quickchr` target keeps native-api
|
|
95
|
+
whatever `CENTRS_HOST` holds.
|
|
96
|
+
|
|
97
|
+
- A pinned CDB no longer refuses an address it has no record for (#427, from
|
|
98
|
+
#384). With `--cdb-file`, `CENTRS_CDB_FILE`, `CENTRS_CDB_PASSWORD` or a
|
|
99
|
+
`centrs.env` CDB path, an IP, `[IPv6]:port`, `http(s)://` URL or MAC that
|
|
100
|
+
matches no record is dialed as a literal host, as it is with no CDB. This
|
|
101
|
+
holds on every command and in fan-out, where each such member also failed. A
|
|
102
|
+
DNS name or identity that matches nothing still fails closed with
|
|
103
|
+
`cdb/not-found-target`, and MCP still accepts only registered targets. An
|
|
104
|
+
IPv6 literal now dials over native-api, ssh, terminal and btest. Before, the
|
|
105
|
+
address was passed to them still in brackets (`[2001:db8::1]`) and failed to
|
|
106
|
+
resolve. SFTP, which needs the brackets, keeps them. A bare IPv6 target
|
|
107
|
+
(`2001:db8::1`, no port) is now accepted everywhere instead of
|
|
108
|
+
`input/invalid-target`. Error messages bracket an IPv6 endpoint
|
|
109
|
+
(`[2001:db8::1]:8728`).
|
|
110
|
+
|
|
111
|
+
- The `--host` flag can stand in for `<router>` (#428, from #384).
|
|
112
|
+
`execute --host http://192.0.2.10:8080 '<cmd>'`, `execute --host <host> --
|
|
113
|
+
<cmd>`, `retrieve --host <host> <path>`, `api --host <host> <endpoint>`,
|
|
114
|
+
`transfer --host <host> <verb>` and `terminal --host <host>` (ssh) now reach
|
|
115
|
+
the router instead of failing `input/invalid-command` for a missing target.
|
|
116
|
+
With no `<router>` there is no CDB lookup, so auth comes from flags, env or
|
|
117
|
+
config. `CENTRS_HOST` doesn't change how positionals are read. Two or more
|
|
118
|
+
`execute` positionals without `--` still split as `<target> <command>`, so
|
|
119
|
+
`execute <mac> <cmd> --via mac-telnet --host <udp-host>` is unchanged. The
|
|
120
|
+
`upload`/`download` aliases still read their first positional as the target.
|
|
121
|
+
|
|
122
|
+
- A one-shot `retrieve` that fails after resolution keeps the resolved meta
|
|
123
|
+
(#433). A refused connection, auth failure or validation error now reports
|
|
124
|
+
`meta.target` (host, port, base URL, record, sources), `meta.via`,
|
|
125
|
+
`meta.settings` and `meta.operation.auth`, as `execute`, `api` and `transfer`
|
|
126
|
+
already did. Before, it reported `meta.via: null` and only the typed input.
|
|
127
|
+
The MCP `centrs_retrieve` tool had the same gap and is fixed too. New
|
|
128
|
+
library export: `retrieveEnvelope()`, which never throws.
|
|
129
|
+
|
|
130
|
+
- Error envelopes no longer report validation `failed` when the validator
|
|
131
|
+
never answered or had passed (#435). A transport or auth failure (and, for
|
|
132
|
+
`retrieve`, a `routeros/*` error) that stops the gate now omits
|
|
133
|
+
`meta.validation.result` and the `syntax`/`semantic` summaries. Each stage
|
|
134
|
+
the gate did not reach reports `skipped` with
|
|
135
|
+
`reason: "not reached: <error.code>"`. `retrieve` now records the gate's
|
|
136
|
+
result, so a read that fails after inspect passed reports `passed`. Success
|
|
137
|
+
envelopes now carry `result: "passed"` too. `--validate=false` reports
|
|
138
|
+
`{enabled: false, source: "disabled"}` instead of `result: "failed"`. Only a
|
|
139
|
+
gate refusal reports `failed`. On an `execute` rejection, `syntax: true` now
|
|
140
|
+
means only a name-level refusal; a `:parse` refusal (syntax, unknown path,
|
|
141
|
+
missing package, unavailable menu) is `syntax: false` with no `semantic`.
|
|
142
|
+
Before, every device rejection said `syntax: true`. A failure in `api`'s
|
|
143
|
+
`--query` boolean probe after the gate passed now reports `passed`. Applies
|
|
144
|
+
to `execute`, `api` (including `--stream`) and every `retrieve` mode. `ValidationTrace` is now a public
|
|
145
|
+
type, and `retrieve()` accepts `{ trace }`. The shape is interim until the
|
|
146
|
+
live-explain review (#236).
|
|
147
|
+
|
|
148
|
+
- SSH execute, SFTP and terminal add `IdentitiesOnly=yes` when a private key is
|
|
149
|
+
selected, preventing fallback to unrelated SSH agent identities (#177).
|
|
150
|
+
Explicit OpenSSH `IdentityFile` configuration continues to apply.
|
|
151
|
+
|
|
152
|
+
- Query QA declares the RouterOS version split for unquoted firewall `where`
|
|
153
|
+
literals. Testing/development builds select rows that 7.24.5 does not;
|
|
154
|
+
both API transports retain exact result checks on either side of the split.
|
|
155
|
+
|
|
156
|
+
- Fan-out FAIL rows and btest client/server CSV error comments replace terminal
|
|
157
|
+
controls with spaces (#395, #415). Compact error text stays on one line,
|
|
158
|
+
including target labels and verbose remediation; JSON/YAML keep the raw values.
|
|
159
|
+
|
|
160
|
+
- **`api --stream` is now a literal stream of the wire (#402).** Following a
|
|
161
|
+
menu is the `<menu>/listen` endpoint (GET; implies `--stream` and
|
|
162
|
+
native-api); a GET with `--stream` streams one literal print. RouterOS's
|
|
163
|
+
`!empty` is no longer dropped: every reply is a frame, `reply: "re"` (a row)
|
|
164
|
+
or `"empty"` (`data: null`), and one received after a local stop carries
|
|
165
|
+
`afterStop: true`. `--count` counts rows only. Property lists and queries go out
|
|
166
|
+
as typed; `tip/filtered-follow` and `tip/follow-proplist` say what RouterOS
|
|
167
|
+
will not report, and request-shape tips lead the stream as a `notice`
|
|
168
|
+
envelope. `--format ndjson` prints every envelope as one compact line,
|
|
169
|
+
one-shot or streamed. **Breaking:** `--listen` and `ApiRequest.listen` are
|
|
170
|
+
removed (`usage/removed-flag`, which prints the replacement command with
|
|
171
|
+
values redacted); `apiListen()` is renamed `apiStream()`, and
|
|
172
|
+
`ProtocolAdapter.listen()` is `stream()`.
|
|
173
|
+
|
|
174
|
+
- **`transfer` moves `/file` contents as bytes on REST and native API
|
|
175
|
+
(#404).** centrs decoded and encoded file contents as UTF-8, so every
|
|
176
|
+
non-UTF-8 byte became U+FFFD (a 2,842-byte file downloaded as 3,422 bytes).
|
|
177
|
+
Library: `ProtocolExecuteRequest` and `NativeApiCommand` gain an opt-in
|
|
178
|
+
`binary` mode; a code unit above U+00FF is refused
|
|
179
|
+
(`internal/byte-string`). Every other command stays UTF-8.
|
|
180
|
+
|
|
181
|
+
- `transfer download` checks the local destination before reading the device
|
|
182
|
+
(#403). A missing parent directory, a destination that is a directory, or a
|
|
183
|
+
write that still fails reports the new `input/local-destination` (the OS
|
|
184
|
+
error rides as `cause`) instead of `internal/unhandled` after a full read.
|
|
185
|
+
Download replaces an existing local file, like `curl -o` and `scp` (#406);
|
|
186
|
+
`--force` guards device destinations only, and the docs now say so.
|
|
187
|
+
|
|
188
|
+
- A `transfer` failure after resolution keeps the resolved meta (#391):
|
|
189
|
+
`meta.target`, `meta.via`, `meta.settings` and `meta.operation.auth`, with
|
|
190
|
+
no secret values. Batch SSH (sftp `transfer`, `execute --via ssh`) never
|
|
191
|
+
sends a password, and the `auth/failed` remediation now says so and names
|
|
192
|
+
the key source; `error.context.sshKeySelected` is new. When a password was
|
|
193
|
+
resolved, `tip/sftp-password-not-used` and
|
|
194
|
+
`tip/transfer-password-transport` explain it and suggest `--via rest` or
|
|
195
|
+
`native-api`. New library export: `transferEnvelope()`, which never throws.
|
|
196
|
+
|
|
197
|
+
- `explain` reads the CLI Reference layout `manual.mikrotik.com` has served
|
|
198
|
+
since 2026-10-06, and its path catalog now includes RouterOS 7.26beta1
|
|
199
|
+
(#417). 47 paths are added (`/cmr/*`, `/system/dashboard`,
|
|
200
|
+
`/interface/wifi/dashboard`, `/tool/apptraffic`, and others), so commands
|
|
201
|
+
such as `/cmr/device/reboot` resolve instead of warning.
|
|
202
|
+
|
|
203
|
+
- **`api --query` uses the `print where` grammar too (#397).** A bare name is
|
|
204
|
+
now `=yes` for a boolean (was "has the property"; that is
|
|
205
|
+
`--raw-query name`), `<`/`<=` include rows without the property as `where`
|
|
206
|
+
does, and a value containing an operator stays one value (`comment=a>b` was
|
|
207
|
+
sent as `>comment=a=b`). With `--validate=false` or `--raw`, a bare name
|
|
208
|
+
fails `input/invalid-query`.
|
|
209
|
+
|
|
210
|
+
- Native API one-shot and incremental commands share their sentence builder;
|
|
211
|
+
structured POST queries/projections reach both transports. Mutation and script
|
|
212
|
+
requests reject unused query/projection flags, and PUT/POST reject a
|
|
213
|
+
trailing row id instead of dropping it. Stream outcomes are read from
|
|
214
|
+
`meta.operation.stream`; failed summaries retain cancellation warnings, and
|
|
215
|
+
received terminal attributes survive a local stop (#400).
|
|
216
|
+
|
|
217
|
+
- **Native `api --stream` runs commands instead of always appending `/listen`
|
|
218
|
+
(#399).** POST ping/monitor/print commands forward their attributes and emit
|
|
219
|
+
replies incrementally; terminal attributes are retained. (#402, above,
|
|
220
|
+
then made GET-menu streams literal.) Natural completion is `completed`; RouterOS/transport
|
|
221
|
+
failures produce one `ok:false` summary and a nonzero exit, even after rows.
|
|
222
|
+
**Breaking stream contract:** structured error envelopes now appear on stdout;
|
|
223
|
+
failed summary counts/reason live in metadata, with no `data` key. Raw errors
|
|
224
|
+
retain stderr behavior. Fatal sessions close locally even if the peer does not.
|
|
225
|
+
|
|
226
|
+
- **A menu the device does not have is no longer reported as a syntax error
|
|
227
|
+
(#361).** RouterOS's `:parse` answers `/zerotier/print` on a router without
|
|
228
|
+
the package with `syntax error (line 1 column 10)`, and `/zerotier print`
|
|
229
|
+
with `bad command name zerotier`. Both point at the segment it could not
|
|
230
|
+
resolve. centrs now locates that segment and looks the path up in its path
|
|
231
|
+
catalog (every RouterOS 7.10–7.25 build, all extra packages):
|
|
232
|
+
`validation/package-missing` when MikroTik publishes the menu behind a
|
|
233
|
+
package (`error.context.packages`, with `/system/package/print` as the fix),
|
|
234
|
+
`validation/menu-unavailable` when the gate is hardware, build or version
|
|
235
|
+
(`/interface/w60g` needs `60ghz`; `error.context.gates`), and
|
|
236
|
+
`validation/unknown-path` when no published build has it. The path can end
|
|
237
|
+
in a command (`/system/license/output` is `nochr`), and the summary says so.
|
|
238
|
+
A real syntax fault, and any rejection centrs cannot locate exactly (a
|
|
239
|
+
menu-scope block, a menu named on an earlier line, a segment written twice),
|
|
240
|
+
stays `validation/syntax`. Applies to every
|
|
241
|
+
transport, since all of them classify the same `:parse` return. **Breaking
|
|
242
|
+
for code that branches on `validation/syntax`** for these rejections: match
|
|
243
|
+
`validation/package-missing`, `validation/menu-unavailable` and
|
|
244
|
+
`validation/unknown-path` as well.
|
|
245
|
+
- **Every command now renders errors the same way, and keeps the router's own
|
|
246
|
+
words (#362).** `execute`, `retrieve`, `api`, `transfer`, `settings`, the
|
|
247
|
+
fan-outs and five others each had their own text renderer; most dropped the
|
|
248
|
+
`Details:` link and RouterOS's position, and the device's own verdict was
|
|
249
|
+
shown only with `--verbose`. All of them now use `formatCentrsErrorText`,
|
|
250
|
+
which prints, in order: `[code] summary`, `At:` (RouterOS's line/column, or
|
|
251
|
+
the offline gate's byte span with the bytes it covers), `Device said:`
|
|
252
|
+
(RouterOS's message, one line, omitted when the summary already quotes it),
|
|
253
|
+
`Fix:` and `Details:`. A test pins all fourteen renderers to the same text.
|
|
254
|
+
`api --stream --format text` prints the same fields on one line, joined by
|
|
255
|
+
` — ` (`formatCentrsErrorLine`).
|
|
256
|
+
Two remediation messages stopped claiming a cause they can't know: a device-stage
|
|
257
|
+
`:parse` rejection no longer says "fix the quotes" (an absent menu or missing
|
|
258
|
+
package is rejected in the same words; #361 separates them) and points to
|
|
259
|
+
`centrs explain`, and the `routeros/api-trap` catch-all no longer says the
|
|
260
|
+
command is likely invalid. The `:parse` preflight wrapper in `execute` also
|
|
261
|
+
stopped dropping RouterOS's message from `context.detail`. `--json` output is
|
|
262
|
+
unchanged apart from those remediation strings and the restored
|
|
263
|
+
`context.detail`.
|
|
264
|
+
|
|
9
265
|
## 0.1.7 — 2026-09-27
|
|
10
266
|
|
|
11
267
|
### Added
|
package/GLOSSARY.txt
CHANGED
|
@@ -69,6 +69,7 @@ Addrs
|
|
|
69
69
|
ajv
|
|
70
70
|
amazonq
|
|
71
71
|
amm
|
|
72
|
+
~askpass # OpenSSH SSH_ASKPASS helper; the CHR password-login control in ssh-auth-policy.test.ts
|
|
72
73
|
AT-SPI
|
|
73
74
|
awk
|
|
74
75
|
axuielement
|
|
@@ -148,6 +149,8 @@ nullglob # bash shopt that makes an unmatched glob expand to zero words instead
|
|
|
148
149
|
npk # MikroTik package format
|
|
149
150
|
nvme-over-tcp # supported by RouterOS **with** rose-storage.npk extra-package
|
|
150
151
|
OID
|
|
152
|
+
# %OSPF: Open Shortest Path First; RouterOS dynamic routing adjacency protocol.
|
|
153
|
+
OSPF
|
|
151
154
|
PII # Personally Identifiable Information
|
|
152
155
|
PON # passive optical network; RouterOS /interface/ethernet/pon menu
|
|
153
156
|
POV
|
|
@@ -309,6 +312,7 @@ ENOTSUP
|
|
|
309
312
|
EAFNOSUPPORT
|
|
310
313
|
ECONNREFUSED
|
|
311
314
|
ECONNRESET
|
|
315
|
+
EHOSTDOWN
|
|
312
316
|
REUSEPORT
|
|
313
317
|
REUSEADDR
|
|
314
318
|
# L2 bridge frame-parsing terms (test/integration/mndp-l2-bridge.ts):
|
|
@@ -339,6 +343,15 @@ creds
|
|
|
339
343
|
Streamable
|
|
340
344
|
# modelcontextprotocol: npm scope of the MCP SDK (@modelcontextprotocol/sdk).
|
|
341
345
|
modelcontextprotocol
|
|
346
|
+
# nextid: RouterOS `.nextid` on a listen frame after a move (the next row's id; *FFFFFFFF = last).
|
|
347
|
+
nextid
|
|
348
|
+
# WHATWG: the web standards body whose Encoding spec makes TextDecoder("latin1") windows-1252, not a byte decoder (#404).
|
|
349
|
+
WHATWG
|
|
350
|
+
# followable: a RouterOS menu `retrieve --follow` can follow — a list menu whose `print` takes `follow-only` in `/console/inspect` (`validation/not-followable` otherwise; #396).
|
|
351
|
+
followable
|
|
352
|
+
# BFD: Bidirectional Forwarding Detection (`/routing/bfd`); its sessions accept `listen` but never send changes (#396 round 2).
|
|
353
|
+
BFD
|
|
354
|
+
jsc # Bun's `bun:jsc` module (JavaScriptCore heap stats), used by the `--sample` memory regression test
|
|
342
355
|
# WIP: work in progress (used for the upstream MikroTik Docusaurus docs site).
|
|
343
356
|
WIP
|
|
344
357
|
# adhoc: future inline host+credential targets; Phase 1 MCP uses CDB-only targets.
|
|
@@ -468,6 +481,12 @@ toarray
|
|
|
468
481
|
# markdown dictionary knows it but the TypeScript one does not.
|
|
469
482
|
iff
|
|
470
483
|
|
|
484
|
+
# Dependency vocabulary (package.json overrides)
|
|
485
|
+
# smol: smol-toml, a markdownlint-cli2/cspell transitive pinned past an advisory.
|
|
486
|
+
# katex: a markdownlint-cli2 transitive with a residual dev-only advisory.
|
|
487
|
+
smol
|
|
488
|
+
katex
|
|
489
|
+
|
|
471
490
|
# GitHub Pages build vocabulary (scripts/build-pages-site.ts, pages.yaml)
|
|
472
491
|
# typedoc/pandoc: the API-reference generator and the markdown->HTML converter
|
|
473
492
|
# the Pages build chains together. gfm: GitHub-Flavored Markdown, pandoc's
|
|
@@ -757,3 +776,16 @@ dgram # Node's `node:dgram` UDP socket module — reached by `mac-telnet`, and o
|
|
|
757
776
|
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
777
|
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
778
|
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)
|
|
779
|
+
|
|
780
|
+
# #361 absent-menu controls. Deliberate NON-words: each names a path no RouterOS
|
|
781
|
+
# build has, or quotes a published gate verbatim, so the spelling is load-bearing.
|
|
782
|
+
nosuch # a top-level menu no RouterOS build has — `/nosuch` is `bad command name nosuch` on CHR 7.23.7, the `validation/unknown-path` control (#361)
|
|
783
|
+
nosuchmenu # a submenu no RouterOS build has — `/ip/nosuchmenu/print` is `syntax error (line 1 column 15)` on CHR 7.23.7, pointing at the `/` after it (#361)
|
|
784
|
+
nosuchverb # a command no RouterOS build has under a real menu — `/ip/address/nosuchverb` is `bad command name nosuchverb` on CHR 7.23.7 (#361)
|
|
785
|
+
qqq # the second segment of the parse-oracle row `/zzz qqq print`, captured on CHR 7.23.3 (GH#230)
|
|
786
|
+
userman # MikroTik's published package name `userman-5` for `/user-manager`; the installable package is `user-manager` (#361)
|
|
787
|
+
nochr # a CLI-Reference syscap that excludes CHR — `/system/license/output` is published `nochr` (#361)
|
|
788
|
+
|
|
789
|
+
# #417 CLI Reference refresh (7.26beta1).
|
|
790
|
+
cmr # MikroTik's optional `cmr` controller package and the `/cmr` menu tree it adds (7.26beta1); the catalog carries `package="cmr"` as its published gate, e.g. `/cmr/device/reboot` (#417)
|
|
791
|
+
apptraffic # two paths new in 7.26beta1 with different published gates: the `/tool/apptraffic` settings menu (conditions `!mmips, !powerpc, !smips`) and the `/cmr/device/apptraffic` command (package `cmr`, no conditions) (#417)
|
package/README.md
CHANGED
|
@@ -58,9 +58,9 @@ product; without them this would just be a worse `curl`.
|
|
|
58
58
|
|
|
59
59
|
| Command | Purpose |
|
|
60
60
|
| ---------- | ------- |
|
|
61
|
-
| `retrieve` | Read RouterOS state over REST/native API and SNMP OID/MIB values. |
|
|
61
|
+
| `retrieve` | Read RouterOS state over REST/native API and SNMP OID/MIB values. `--follow` keeps a menu's rows current over native API (snapshot, then a line per change; NDJSON); `--sample <interval>` re-reads it on a timer for counters; `--query` filters on the router with the `print where` grammar; `--wait` retries until a read succeeds (or an optional `--until` condition holds), up to a deadline. |
|
|
62
62
|
| `execute` | Run RouterOS CLI-shaped read/write commands (add/set/remove) over native API/REST/L2 surfaces. |
|
|
63
|
-
| `api` | Structured RouterOS API passthrough (gh-api style): one command per operation, structured in/out, can write, over REST or native API.
|
|
63
|
+
| `api` | Structured RouterOS API passthrough (gh-api style): one command per operation, structured in/out, can write, over REST or native API. Follow changes with `api … <menu>/listen`; `--stream` streams any command's replies (native API; NDJSON). |
|
|
64
64
|
| `terminal` | Open an interactive console, primarily SSH or MAC-Telnet. |
|
|
65
65
|
| `check` | Probe reachability and management protocol availability. **Designed, not implemented** — see `docs/MATRIX.md`. |
|
|
66
66
|
| `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. |
|
package/SECURITY.md
CHANGED
|
@@ -17,7 +17,7 @@ The repository's [Security tab](https://github.com/tikoci/centrs/security) is th
|
|
|
17
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
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
|
-
- **Dependabot security updates** —
|
|
20
|
+
- **Dependabot security updates** — unsupported for Bun in GitHub's [ecosystem support table](https://docs.github.com/en/code-security/reference/supply-chain-security/supported-ecosystems-and-repositories). [`.github/dependabot.yaml`](.github/dependabot.yaml) configures weekly Bun **version updates** and GitHub Actions updates. Dependency security cleanup also requires `bun audit` and reviewed lockfile upgrades.
|
|
21
21
|
- **Secret scanning** — enabled in GitHub, with push protection.
|
|
22
22
|
- **Private vulnerability reporting** — enabled via GitHub Security Advisories.
|
|
23
23
|
|
package/commands/AGENTS.md
CHANGED
|
@@ -29,7 +29,8 @@ dependencies within the selected capability; they do not replace its status.
|
|
|
29
29
|
Commands are centrs **verbs** (`retrieve`, `execute`, `api`, `devices`,
|
|
30
30
|
`discover`, `check`, `explain`, `terminal`, `settings`), never one tool per
|
|
31
31
|
RouterOS command. Open-ended follow
|
|
32
|
-
is
|
|
32
|
+
is a flag, not a verb: `retrieve --follow` keeps state, `api --stream` is the
|
|
33
|
+
literal wire (no separate `stream` verb). Sub-verbs and their
|
|
33
34
|
aliases are canonical across CLI / API / MCP:
|
|
34
35
|
|
|
35
36
|
| Canonical | Aliases | Meaning |
|
package/commands/api/AGENTS.md
CHANGED
|
@@ -29,18 +29,13 @@ inspect-client TS patterns — `tikoci/lsp-routeros-ts`
|
|
|
29
29
|
- **HTTP method map (REST):** `GET`→print, `PUT`→add (**create**), `PATCH`→set,
|
|
30
30
|
`DELETE`→remove, `POST`→universal (run any console command). PUT — not POST — is
|
|
31
31
|
RouterOS's create.
|
|
32
|
-
- **Multi-frame results:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
bounded
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
transports (native's `talk()` accumulates the `!re` until `!done`) — **not**
|
|
40
|
-
NDJSON. The only thing that needs the NDJSON streaming path is **open-ended
|
|
41
|
-
follow** (native `/listen`, which never sends `!done`); REST genuinely cannot do
|
|
42
|
-
open-ended follow. ⇒ `--listen` (open-ended) is native-only; `duration=` commands
|
|
43
|
-
are normal one-shot calls. (CONFIRMED below.)
|
|
32
|
+
- **Multi-frame results: one-shot → array; incremental replies → native API.**
|
|
33
|
+
REST regularizes bounded native `!re` replies into a JSON array and retains
|
|
34
|
+
its 60 s cap. A one-shot native call also accumulates rows until `!done`.
|
|
35
|
+
GH#399 adds incremental native command replies with `--stream`, including
|
|
36
|
+
bounded commands and open-ended ping/monitor/print interval. Method semantics,
|
|
37
|
+
terminal outcomes and subscription limitations live in the README;
|
|
38
|
+
examples L6–L16 ground the extended path.
|
|
44
39
|
- **`/listen` semantics.** Emits `!re` on change; a deleted/disappeared item's
|
|
45
40
|
`!re` carries the dead flag (docs say `=.dead=yes`; **CHR 7.23.1 sends
|
|
46
41
|
`.dead=true`** — see CONFIRMED below); it never self-terminates. Cancel with
|
|
@@ -68,15 +63,18 @@ inspect-client TS patterns — `tikoci/lsp-routeros-ts`
|
|
|
68
63
|
|
|
69
64
|
Two layers, both grounded above:
|
|
70
65
|
|
|
71
|
-
- **`--query` / `--filter`** —
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
- `
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
66
|
+
- **`--query` / `--filter`** — the `print where` grammar, shared with
|
|
67
|
+
`retrieve --query` (`src/core/query.ts`, #397), repeatable and AND-ed.
|
|
68
|
+
`and`/`or`/`!`/parentheses compile to `#&`/`#|`/`#!`; `a!=v` → `a=v` `#!`;
|
|
69
|
+
`a<v` → `<a=v` `-a` `#|` and `a<=v` → `>a=v` `#!` (`where` counts a row
|
|
70
|
+
without `a` as smaller than any value; API `<`/`>` skip it); a bare name → `a=yes` for a boolean, otherwise
|
|
71
|
+
the has-property word `a`, decided by asking the device (completion after
|
|
72
|
+
`where a=` offers exactly `yes`/`no` for a boolean). The table, refusals and
|
|
73
|
+
CHR differential (`find where` vs `.query`, 58 expressions, 7.23.7 + 7.24.5)
|
|
74
|
+
are in `commands/retrieve/README.md` → Query. Until #397 the structured form
|
|
75
|
+
was a per-word splitter where a bare name meant has-property, `<` skipped
|
|
76
|
+
rows without the property, and an operator anywhere in the string split it
|
|
77
|
+
(`comment=a>b` → `>comment=a=b`).
|
|
80
78
|
- **`--raw-query <word>`** — power-user escape hatch, repeatable. Each value is a
|
|
81
79
|
**verbatim RouterOS query word** (absence `-name`, OR `#|`, AND `#&`, NOT `#!`,
|
|
82
80
|
or any `>`/`<`/`=` form). Emitted as-is: a `.query` element over REST, a
|
|
@@ -203,8 +201,9 @@ green on CHR 7.23.1.
|
|
|
203
201
|
## CONFIRMED ON CHR (Phase 4 `--stream` follow — CHR 7.23.1, 2026-06-30)
|
|
204
202
|
|
|
205
203
|
Validated by `test/integration/api-listen.test.ts` (L1–L4) on CHR 7.23.1.
|
|
206
|
-
|
|
207
|
-
|
|
204
|
+
Since #402 the `/listen` endpoint is the only way to listen (it implies
|
|
205
|
+
`--stream`); `--listen` is removed. Native-api only (REST's 60s cap →
|
|
206
|
+
`transport/capability-unsupported`).
|
|
208
207
|
|
|
209
208
|
- **CONFIRMED — `NativeApiSession.listen()` + `/cancel` end-to-end.** Opening
|
|
210
209
|
`/ip/address/listen` (own `.tag`) and adding an address over REST yields an
|
|
@@ -212,13 +211,39 @@ infers both. Native-api only (REST's 60s cap → `transport/capability-unsupport
|
|
|
212
211
|
.tag=<n>` and the listen closes via the `interrupted` trap (category 2) then
|
|
213
212
|
`!done` — the generator ends **without** throwing (the interrupted trap is the
|
|
214
213
|
normal cancel, not an error). A non-interrupted trap or transport closure does
|
|
215
|
-
throw. Re-confirms the Phase-0 spike through the real `apiListen()` path
|
|
214
|
+
throw. Re-confirms the Phase-0 spike through the real `apiListen()` path (now
|
|
215
|
+
`apiStream()`).
|
|
216
216
|
- **CONFIRMED — deletion frames carry `.dead=true`.** Removing a pre-seeded
|
|
217
217
|
address over REST while listening emits a minimal `{ ".id", ".dead":"true" }`
|
|
218
218
|
`!re` (re-confirms `.dead=true`, not the docs' `=.dead=yes`).
|
|
219
|
-
- **Stream shape:** each `!re` → one
|
|
220
|
-
(`meta.operation.stream.kind="frame"`, string values, `.dead` preserved)
|
|
221
|
-
|
|
222
|
-
`
|
|
223
|
-
|
|
224
|
-
|
|
219
|
+
- **Stream shape:** each `!re` → one envelope frame
|
|
220
|
+
(`meta.operation.stream.kind="frame"`, string values, `.dead` preserved).
|
|
221
|
+
GH#399 supersedes the original terminal/exit behavior: natural completion is
|
|
222
|
+
distinct from cancellation; a failure summary has `ok:false`, and any failure
|
|
223
|
+
exits nonzero. The current contract lives in the README.
|
|
224
|
+
|
|
225
|
+
- **CONFIRMED — addressed-listen deletes (L15), CHR 7.23.7:** `test/integration/api-listen.test.ts`
|
|
226
|
+
covers `/ip/address/listen ?.id=<id>` both without projection and with
|
|
227
|
+
`.proplist=address,.id,.dead`. Removing the seeded address must produce
|
|
228
|
+
`{ ".id": <id>, ".dead": "true" }`. The ID query matches a deletion frame
|
|
229
|
+
because it carries `.id`; a predicate on another column may hide that frame.
|
|
230
|
+
|
|
231
|
+
## CONFIRMED ON CHR (#402 literal `--stream` — 7.23.7 + 7.24.5, 2026-10-07)
|
|
232
|
+
|
|
233
|
+
`api` stopped adding `.id,.dead` to a listen's `.proplist` and stopped
|
|
234
|
+
rejecting filtered listens, so these are now the device's own answers, pinned
|
|
235
|
+
by `test/integration/api-listen.test.ts` (L4, L9, L10, L15, L18):
|
|
236
|
+
|
|
237
|
+
- **`.proplist=address` strips `.id` and `.dead` from delete frames, even
|
|
238
|
+
under `?.id=`.** An addressed listen's delete arrives as an `!re` with no
|
|
239
|
+
attributes (`{}`); an unaddressed one has no `.dead` frame for the row.
|
|
240
|
+
The `?.id=` exception only holds when the projection names `.id,.dead`
|
|
241
|
+
(or there is none). This corrects the 2026-10-06 note that it held "with
|
|
242
|
+
and without `.proplist`": that run had centrs's forced `.id,.dead`.
|
|
243
|
+
- **A filtered listen (`?interface=ether1`) sends nothing for the delete of
|
|
244
|
+
a matching row.** The only reply in the window is the `!empty` that answers
|
|
245
|
+
the cancel.
|
|
246
|
+
- **Cancelling a listen that sent nothing** → `!trap` interrupted, `!empty`,
|
|
247
|
+
`!done`. centrs emits that `!empty` as a frame with `afterStop: true`.
|
|
248
|
+
- **`print interval=1` on an empty table** (`/ip/firewall/raw`) → one
|
|
249
|
+
`!empty` per tick.
|