@tikoci/centrs 0.1.6 → 0.1.8-next.0
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 +342 -0
- package/GLOSSARY.txt +32 -0
- package/README.md +10 -6
- package/SECURITY.md +1 -1
- package/commands/AGENTS.md +2 -1
- package/commands/api/AGENTS.md +55 -30
- package/commands/api/README.md +135 -42
- package/commands/api/examples.md +275 -19
- package/commands/devices/README.md +21 -4
- package/commands/devices/examples.md +33 -2
- package/commands/execute/README.md +60 -18
- package/commands/execute/examples.md +224 -0
- package/commands/explain/README.md +42 -5
- package/commands/mcp/README.md +2 -1
- package/commands/retrieve/README.md +348 -4
- package/commands/retrieve/examples.md +442 -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 +29 -21
- package/docs/CONSTITUTION.md +41 -16
- package/docs/MATRIX.md +38 -1
- package/docs/errors/README.md +3 -1
- package/docs/errors/cdb/create-failed.md +20 -0
- package/docs/errors/cdb/file-exists.md +23 -0
- package/docs/errors/input/invalid-query.md +36 -0
- package/docs/errors/input/invalid-target.md +25 -3
- 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/routeros/invalid-value.md +3 -2
- package/docs/errors/settings/invalid-timeout.md +6 -0
- package/docs/errors/transport/cancel-unacknowledged.md +24 -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 +603 -194
- package/src/btest.ts +19 -17
- package/src/cli/api.ts +126 -34
- package/src/cli/devices.ts +16 -3
- package/src/cli/execute.ts +18 -5
- package/src/cli/retrieve.ts +216 -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 +70 -1
- package/src/core/execute-command.ts +313 -6
- package/src/core/inspect.ts +65 -27
- package/src/core/query.ts +508 -0
- package/src/core/routeros-errors.ts +67 -1
- package/src/data/winbox-cdb-write.ts +22 -8
- package/src/devices.ts +148 -37
- package/src/discover.ts +7 -8
- package/src/errors.ts +116 -7
- package/src/execute-fanout.ts +14 -9
- package/src/execute.ts +128 -116
- package/src/explain/catalog.ts +64 -16
- package/src/explain/menus.ts +27 -6
- package/src/explain.ts +6 -6
- package/src/index.ts +31 -1
- package/src/mcp/tools.ts +10 -15
- package/src/protocols/adapter.ts +235 -118
- package/src/protocols/btest-session.ts +3 -2
- package/src/protocols/endpoint.ts +10 -0
- package/src/protocols/mac-telnet-console.ts +12 -7
- package/src/protocols/native-api.ts +252 -73
- 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 +84 -18
- 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 +741 -109
- package/src/settings.ts +15 -15
- package/src/transfer-fanout.ts +40 -15
- package/src/transfer.ts +234 -32
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,348 @@ documenting cross-cutting shifts that affect contributors and consumers.
|
|
|
8
8
|
|
|
9
9
|
## Unreleased
|
|
10
10
|
|
|
11
|
+
## 0.1.8-next.0 — 2026-10-12
|
|
12
|
+
|
|
13
|
+
Pre-release (npm tag `next`; `latest` stays on 0.1.7). Install with
|
|
14
|
+
`bunx @tikoci/centrs@next`.
|
|
15
|
+
|
|
16
|
+
### Maintenance
|
|
17
|
+
|
|
18
|
+
- Refresh vulnerable dependencies within existing ranges and use Bun's
|
|
19
|
+
Dependabot ecosystem for `bun.lock` (#390). Preserve the dependency group
|
|
20
|
+
and the `has-flag` major-version ignore. Remaining dependency advisories
|
|
21
|
+
and updater verification are tracked in #390.
|
|
22
|
+
- Pre-release dependency alignment (#390): runtime ranges now floor at the
|
|
23
|
+
tested versions (`@modelcontextprotocol/sdk ^1.32.1`, `zod ^4.6.7`),
|
|
24
|
+
`smol-toml` is overridden past its advisories, and `@types/bun` is pinned
|
|
25
|
+
exactly to the 1.3.14 runtime (re-pin tracked in #438). The two residual
|
|
26
|
+
advisories (`braces`, `katex`) are dev-tool-only and do not ship in the
|
|
27
|
+
package.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **`retrieve --wait` establishes readiness or a condition by a deadline (#364).**
|
|
32
|
+
One final envelope over REST or native API, with elapsed time, attempts and
|
|
33
|
+
completed observations. `--until` uses the shared query grammar and succeeds
|
|
34
|
+
on any matching selected row; `--until-empty` requires a completed zero-row
|
|
35
|
+
read. Already-true conditions succeed immediately. Only transient transport
|
|
36
|
+
errors retry; authentication and validation fail immediately. `--timeout`
|
|
37
|
+
bounds each request, and optional `--sample` sets observation cadence.
|
|
38
|
+
- **`retrieve --follow` keeps a menu's rows current over native API (#396).**
|
|
39
|
+
Snapshot rows, one `synced` line, then a line per change, as NDJSON; an
|
|
40
|
+
`.id` sweep (`--sweep`, default 10s) reports removals RouterOS never sends
|
|
41
|
+
on view menus. Single target; `--query` / `--filter` (#397) use device
|
|
42
|
+
membership reads and filtered sweeps, with membership removals distinguished
|
|
43
|
+
from wire deletions. `--sweep`
|
|
44
|
+
needs a unit (`10s`); only `0` (off) may be bare.
|
|
45
|
+
- **`retrieve --sample <interval>` re-reads a menu on a timer.** One line per
|
|
46
|
+
complete read (`data` is the whole read), then a summary; `--count` and
|
|
47
|
+
`--duration` bound it. Works over REST and native API, on list menus and
|
|
48
|
+
singletons, and sees counters `--follow` cannot. The interval needs a unit
|
|
49
|
+
(`5s`): a bare number is refused because RouterOS and centrs read it
|
|
50
|
+
differently.
|
|
51
|
+
- **`retrieve --query` filters rows on the router (#397).** Write what you
|
|
52
|
+
would after `print where` (`type=ether and !disabled`, `mtu>=1500`,
|
|
53
|
+
`(a=1 or b=2)`); centrs compiles it to the API query, so the router filters.
|
|
54
|
+
`--filter` is the same flag; repeats are AND-ed. Property names are
|
|
55
|
+
validated, and bare names are asked of the router (`disabled` → `=yes`,
|
|
56
|
+
`comment` → "is set"). `~`, `in` and script values fail
|
|
57
|
+
`input/unsupported-query`. Works with `--sample`, fan-out and `--follow`.
|
|
58
|
+
Checked on CHR against RouterOS's own `find where`.
|
|
59
|
+
- `--format ndjson` for `retrieve`. Library: `retrieveFollow()`,
|
|
60
|
+
`retrieveSample()`, and `renderRetrieveStreamLine()` for both (it replaces
|
|
61
|
+
the unreleased `renderRetrieveFollowLine()`).
|
|
62
|
+
|
|
63
|
+
### Fixed
|
|
64
|
+
|
|
65
|
+
- `execute` honors an explicit target URL, and native-api honors a typed port
|
|
66
|
+
(#426, from #384). With no `via` pinned, an `http(s)://host[:port]` target
|
|
67
|
+
selects rest-api on that scheme and port instead of silently dialing native
|
|
68
|
+
API on 8728; `meta.settings.via` reports `via:target-url`. A port typed into
|
|
69
|
+
the target (`host:9`, `--host`, `CENTRS_HOST`) now reaches native-api on
|
|
70
|
+
every command, below the port settings (`--port`, `CENTRS_PORT`, `port=`,
|
|
71
|
+
config), as it already did for ssh and REST. An explicit `:80`/`:443`, which
|
|
72
|
+
URL parsing normalizes away, is kept for native-api and ssh. A matched CDB
|
|
73
|
+
record's own port is unchanged. A `--quickchr` target keeps native-api
|
|
74
|
+
whatever `CENTRS_HOST` holds.
|
|
75
|
+
|
|
76
|
+
- A pinned CDB no longer refuses an address it has no record for (#427, from
|
|
77
|
+
#384). With `--cdb-file`, `CENTRS_CDB_FILE`, `CENTRS_CDB_PASSWORD` or a
|
|
78
|
+
`centrs.env` CDB path, an IP, `[IPv6]:port`, `http(s)://` URL or MAC that
|
|
79
|
+
matches no record is dialed as a literal host, as it is with no CDB. This
|
|
80
|
+
holds on every command and in fan-out, where each such member also failed. A
|
|
81
|
+
DNS name or identity that matches nothing still fails closed with
|
|
82
|
+
`cdb/not-found-target`, and MCP still accepts only registered targets. An
|
|
83
|
+
IPv6 literal now dials over native-api, ssh, terminal and btest. Before, the
|
|
84
|
+
address was passed to them still in brackets (`[2001:db8::1]`) and failed to
|
|
85
|
+
resolve. SFTP, which needs the brackets, keeps them. A bare IPv6 target
|
|
86
|
+
(`2001:db8::1`, no port) is now accepted everywhere instead of
|
|
87
|
+
`input/invalid-target`. Error messages bracket an IPv6 endpoint
|
|
88
|
+
(`[2001:db8::1]:8728`).
|
|
89
|
+
|
|
90
|
+
- The `--host` flag can stand in for `<router>` (#428, from #384).
|
|
91
|
+
`execute --host http://192.0.2.10:8080 '<cmd>'`, `execute --host <host> --
|
|
92
|
+
<cmd>`, `retrieve --host <host> <path>`, `api --host <host> <endpoint>`,
|
|
93
|
+
`transfer --host <host> <verb>` and `terminal --host <host>` (ssh) now reach
|
|
94
|
+
the router instead of failing `input/invalid-command` for a missing target.
|
|
95
|
+
With no `<router>` there is no CDB lookup, so auth comes from flags, env or
|
|
96
|
+
config. `CENTRS_HOST` doesn't change how positionals are read. Two or more
|
|
97
|
+
`execute` positionals without `--` still split as `<target> <command>`, so
|
|
98
|
+
`execute <mac> <cmd> --via mac-telnet --host <udp-host>` is unchanged. The
|
|
99
|
+
`upload`/`download` aliases still read their first positional as the target.
|
|
100
|
+
|
|
101
|
+
- A one-shot `retrieve` that fails after resolution keeps the resolved meta
|
|
102
|
+
(#433). A refused connection, auth failure or validation error now reports
|
|
103
|
+
`meta.target` (host, port, base URL, record, sources), `meta.via`,
|
|
104
|
+
`meta.settings` and `meta.operation.auth`, as `execute`, `api` and `transfer`
|
|
105
|
+
already did. Before, it reported `meta.via: null` and only the typed input.
|
|
106
|
+
The MCP `centrs_retrieve` tool had the same gap and is fixed too. New
|
|
107
|
+
library export: `retrieveEnvelope()`, which never throws.
|
|
108
|
+
|
|
109
|
+
- Error envelopes no longer report validation `failed` when the validator
|
|
110
|
+
never answered or had passed (#435). A transport or auth failure (and, for
|
|
111
|
+
`retrieve`, a `routeros/*` error) that stops the gate now omits
|
|
112
|
+
`meta.validation.result` and the `syntax`/`semantic` summaries. Each stage
|
|
113
|
+
the gate did not reach reports `skipped` with
|
|
114
|
+
`reason: "not reached: <error.code>"`. `retrieve` now records the gate's
|
|
115
|
+
result, so a read that fails after inspect passed reports `passed`. Success
|
|
116
|
+
envelopes now carry `result: "passed"` too. `--validate=false` reports
|
|
117
|
+
`{enabled: false, source: "disabled"}` instead of `result: "failed"`. Only a
|
|
118
|
+
gate refusal reports `failed`. On an `execute` rejection, `syntax: true` now
|
|
119
|
+
means only a name-level refusal; a `:parse` refusal (syntax, unknown path,
|
|
120
|
+
missing package, unavailable menu) is `syntax: false` with no `semantic`.
|
|
121
|
+
Before, every device rejection said `syntax: true`. A failure in `api`'s
|
|
122
|
+
`--query` boolean probe after the gate passed now reports `passed`. Applies
|
|
123
|
+
to `execute`, `api` (including `--stream`) and every `retrieve` mode. `ValidationTrace` is now a public
|
|
124
|
+
type, and `retrieve()` accepts `{ trace }`. The shape is interim until the
|
|
125
|
+
live-explain review (#236).
|
|
126
|
+
|
|
127
|
+
- SSH execute, SFTP and terminal add `IdentitiesOnly=yes` when a private key is
|
|
128
|
+
selected, preventing fallback to unrelated SSH agent identities (#177).
|
|
129
|
+
Explicit OpenSSH `IdentityFile` configuration continues to apply.
|
|
130
|
+
|
|
131
|
+
- Query QA declares the RouterOS version split for unquoted firewall `where`
|
|
132
|
+
literals. Testing/development builds select rows that 7.24.5 does not;
|
|
133
|
+
both API transports retain exact result checks on either side of the split.
|
|
134
|
+
|
|
135
|
+
- Fan-out FAIL rows and btest client/server CSV error comments replace terminal
|
|
136
|
+
controls with spaces (#395, #415). Compact error text stays on one line,
|
|
137
|
+
including target labels and verbose remediation; JSON/YAML keep the raw values.
|
|
138
|
+
|
|
139
|
+
- **`api --stream` is now a literal stream of the wire (#402).** Following a
|
|
140
|
+
menu is the `<menu>/listen` endpoint (GET; implies `--stream` and
|
|
141
|
+
native-api); a GET with `--stream` streams one literal print. RouterOS's
|
|
142
|
+
`!empty` is no longer dropped: every reply is a frame, `reply: "re"` (a row)
|
|
143
|
+
or `"empty"` (`data: null`), and one received after a local stop carries
|
|
144
|
+
`afterStop: true`. `--count` counts rows only. Property lists and queries go out
|
|
145
|
+
as typed; `tip/filtered-follow` and `tip/follow-proplist` say what RouterOS
|
|
146
|
+
will not report, and request-shape tips lead the stream as a `notice`
|
|
147
|
+
envelope. `--format ndjson` prints every envelope as one compact line,
|
|
148
|
+
one-shot or streamed. **Breaking:** `--listen` and `ApiRequest.listen` are
|
|
149
|
+
removed (`usage/removed-flag`, which prints the replacement command with
|
|
150
|
+
values redacted); `apiListen()` is renamed `apiStream()`, and
|
|
151
|
+
`ProtocolAdapter.listen()` is `stream()`.
|
|
152
|
+
|
|
153
|
+
- **`transfer` moves `/file` contents as bytes on REST and native API
|
|
154
|
+
(#404).** centrs decoded and encoded file contents as UTF-8, so every
|
|
155
|
+
non-UTF-8 byte became U+FFFD (a 2,842-byte file downloaded as 3,422 bytes).
|
|
156
|
+
Library: `ProtocolExecuteRequest` and `NativeApiCommand` gain an opt-in
|
|
157
|
+
`binary` mode; a code unit above U+00FF is refused
|
|
158
|
+
(`internal/byte-string`). Every other command stays UTF-8.
|
|
159
|
+
|
|
160
|
+
- `transfer download` checks the local destination before reading the device
|
|
161
|
+
(#403). A missing parent directory, a destination that is a directory, or a
|
|
162
|
+
write that still fails reports the new `input/local-destination` (the OS
|
|
163
|
+
error rides as `cause`) instead of `internal/unhandled` after a full read.
|
|
164
|
+
Download replaces an existing local file, like `curl -o` and `scp` (#406);
|
|
165
|
+
`--force` guards device destinations only, and the docs now say so.
|
|
166
|
+
|
|
167
|
+
- A `transfer` failure after resolution keeps the resolved meta (#391):
|
|
168
|
+
`meta.target`, `meta.via`, `meta.settings` and `meta.operation.auth`, with
|
|
169
|
+
no secret values. Batch SSH (sftp `transfer`, `execute --via ssh`) never
|
|
170
|
+
sends a password, and the `auth/failed` remediation now says so and names
|
|
171
|
+
the key source; `error.context.sshKeySelected` is new. When a password was
|
|
172
|
+
resolved, `tip/sftp-password-not-used` and
|
|
173
|
+
`tip/transfer-password-transport` explain it and suggest `--via rest` or
|
|
174
|
+
`native-api`. New library export: `transferEnvelope()`, which never throws.
|
|
175
|
+
|
|
176
|
+
- `explain` reads the CLI Reference layout `manual.mikrotik.com` has served
|
|
177
|
+
since 2026-10-06, and its path catalog now includes RouterOS 7.26beta1
|
|
178
|
+
(#417). 47 paths are added (`/cmr/*`, `/system/dashboard`,
|
|
179
|
+
`/interface/wifi/dashboard`, `/tool/apptraffic`, and others), so commands
|
|
180
|
+
such as `/cmr/device/reboot` resolve instead of warning.
|
|
181
|
+
|
|
182
|
+
- **`api --query` uses the `print where` grammar too (#397).** A bare name is
|
|
183
|
+
now `=yes` for a boolean (was "has the property"; that is
|
|
184
|
+
`--raw-query name`), `<`/`<=` include rows without the property as `where`
|
|
185
|
+
does, and a value containing an operator stays one value (`comment=a>b` was
|
|
186
|
+
sent as `>comment=a=b`). With `--validate=false` or `--raw`, a bare name
|
|
187
|
+
fails `input/invalid-query`.
|
|
188
|
+
|
|
189
|
+
- Native API one-shot and incremental commands share their sentence builder;
|
|
190
|
+
structured POST queries/projections reach both transports. Mutation and script
|
|
191
|
+
requests reject unused query/projection flags, and PUT/POST reject a
|
|
192
|
+
trailing row id instead of dropping it. Stream outcomes are read from
|
|
193
|
+
`meta.operation.stream`; failed summaries retain cancellation warnings, and
|
|
194
|
+
received terminal attributes survive a local stop (#400).
|
|
195
|
+
|
|
196
|
+
- **Native `api --stream` runs commands instead of always appending `/listen`
|
|
197
|
+
(#399).** POST ping/monitor/print commands forward their attributes and emit
|
|
198
|
+
replies incrementally; terminal attributes are retained. (#402, above,
|
|
199
|
+
then made GET-menu streams literal.) Natural completion is `completed`; RouterOS/transport
|
|
200
|
+
failures produce one `ok:false` summary and a nonzero exit, even after rows.
|
|
201
|
+
**Breaking stream contract:** structured error envelopes now appear on stdout;
|
|
202
|
+
failed summary counts/reason live in metadata, with no `data` key. Raw errors
|
|
203
|
+
retain stderr behavior. Fatal sessions close locally even if the peer does not.
|
|
204
|
+
|
|
205
|
+
- **A menu the device does not have is no longer reported as a syntax error
|
|
206
|
+
(#361).** RouterOS's `:parse` answers `/zerotier/print` on a router without
|
|
207
|
+
the package with `syntax error (line 1 column 10)`, and `/zerotier print`
|
|
208
|
+
with `bad command name zerotier`. Both point at the segment it could not
|
|
209
|
+
resolve. centrs now locates that segment and looks the path up in its path
|
|
210
|
+
catalog (every RouterOS 7.10–7.25 build, all extra packages):
|
|
211
|
+
`validation/package-missing` when MikroTik publishes the menu behind a
|
|
212
|
+
package (`error.context.packages`, with `/system/package/print` as the fix),
|
|
213
|
+
`validation/menu-unavailable` when the gate is hardware, build or version
|
|
214
|
+
(`/interface/w60g` needs `60ghz`; `error.context.gates`), and
|
|
215
|
+
`validation/unknown-path` when no published build has it. The path can end
|
|
216
|
+
in a command (`/system/license/output` is `nochr`), and the summary says so.
|
|
217
|
+
A real syntax fault, and any rejection centrs cannot locate exactly (a
|
|
218
|
+
menu-scope block, a menu named on an earlier line, a segment written twice),
|
|
219
|
+
stays `validation/syntax`. Applies to every
|
|
220
|
+
transport, since all of them classify the same `:parse` return. **Breaking
|
|
221
|
+
for code that branches on `validation/syntax`** for these rejections: match
|
|
222
|
+
`validation/package-missing`, `validation/menu-unavailable` and
|
|
223
|
+
`validation/unknown-path` as well.
|
|
224
|
+
- **Every command now renders errors the same way, and keeps the router's own
|
|
225
|
+
words (#362).** `execute`, `retrieve`, `api`, `transfer`, `settings`, the
|
|
226
|
+
fan-outs and five others each had their own text renderer; most dropped the
|
|
227
|
+
`Details:` link and RouterOS's position, and the device's own verdict was
|
|
228
|
+
shown only with `--verbose`. All of them now use `formatCentrsErrorText`,
|
|
229
|
+
which prints, in order: `[code] summary`, `At:` (RouterOS's line/column, or
|
|
230
|
+
the offline gate's byte span with the bytes it covers), `Device said:`
|
|
231
|
+
(RouterOS's message, one line, omitted when the summary already quotes it),
|
|
232
|
+
`Fix:` and `Details:`. A test pins all fourteen renderers to the same text.
|
|
233
|
+
`api --stream --format text` prints the same fields on one line, joined by
|
|
234
|
+
` — ` (`formatCentrsErrorLine`).
|
|
235
|
+
Two remediation messages stopped claiming a cause they can't know: a device-stage
|
|
236
|
+
`:parse` rejection no longer says "fix the quotes" (an absent menu or missing
|
|
237
|
+
package is rejected in the same words; #361 separates them) and points to
|
|
238
|
+
`centrs explain`, and the `routeros/api-trap` catch-all no longer says the
|
|
239
|
+
command is likely invalid. The `:parse` preflight wrapper in `execute` also
|
|
240
|
+
stopped dropping RouterOS's message from `context.detail`. `--json` output is
|
|
241
|
+
unchanged apart from those remediation strings and the restored
|
|
242
|
+
`context.detail`.
|
|
243
|
+
|
|
244
|
+
## 0.1.7 — 2026-09-27
|
|
245
|
+
|
|
246
|
+
### Added
|
|
247
|
+
|
|
248
|
+
- **`centrs devices init` creates an empty CDB, `git init`-style (#376).**
|
|
249
|
+
Bootstrapping a device book previously meant conjuring the file some other
|
|
250
|
+
way. `centrs devices init [--cdb-file PATH]` (or `CENTRS_CDB_FILE`, or the
|
|
251
|
+
default path) creates the parent directories and an exclusive `0600` file.
|
|
252
|
+
**It never clobbers**, so a bootstrap script may run it unconditionally: an
|
|
253
|
+
existing readable CDB succeeds with a `cdb/file-exists` warning and nothing
|
|
254
|
+
is written. An existing file that fails to load (`cdb/parse-failed`,
|
|
255
|
+
`cdb/password-required`) reports that error and is left untouched, and a
|
|
256
|
+
location that cannot be written fails with the new `cdb/create-failed`. CLI
|
|
257
|
+
and library (`initCdb`) only; not exposed on the MCP devices tool.
|
|
258
|
+
|
|
259
|
+
- **`devices list` reports the lookup keys it matches on (#378).** Rows now
|
|
260
|
+
carry `identity` / `mac` / `ip` when the comment sets them, with
|
|
261
|
+
`comment-kv` provenance in `sources`, and the text table gains an `IDENTITY`
|
|
262
|
+
column. `comment` itself stays `show`-only.
|
|
263
|
+
|
|
264
|
+
### Fixed
|
|
265
|
+
|
|
266
|
+
- **Write-shaped commands now require confirmation, on every transport
|
|
267
|
+
(#365, #366).** Write authorization was entangled with script-vs-structured
|
|
268
|
+
transport selection, so several shapes reached a device without passing the
|
|
269
|
+
`--yes` gate: selectors, positional ids, non-CRUD verbs, root mutators,
|
|
270
|
+
indirect execution and relative menu writes. Authorization is now decided
|
|
271
|
+
independently of how the command is carried. Read spellings are unaffected —
|
|
272
|
+
the verb is the first known verb among the path's last segment and the bare
|
|
273
|
+
words following the path token, so `/system identity print` and
|
|
274
|
+
`:put [/system identity get name]` stay reads, while a statement with no
|
|
275
|
+
recognizable verb still fails closed. Caller-addressed API `/execute` script
|
|
276
|
+
strings additionally run offline analysis, then live `:parse`, then execute;
|
|
277
|
+
structured API requests remain on `/console/inspect`. This advances the
|
|
278
|
+
synchronous half of #154 without closing it — caller-controlled `file=`,
|
|
279
|
+
`as-string` and async job-id semantics remain open there.
|
|
280
|
+
|
|
281
|
+
- **RouterOS rejections returned inside a successful transport envelope are no
|
|
282
|
+
longer read as success.** `/execute` can answer 200 while the device's own
|
|
283
|
+
text says it refused. Those are now classified as runtime failures, with the
|
|
284
|
+
passed validation-stage history preserved so the envelope still shows what
|
|
285
|
+
each stage concluded.
|
|
286
|
+
|
|
287
|
+
- **Device-stage validation missed RouterOS's bare `missing …` diagnostics
|
|
288
|
+
(#375).** `classifyParseResult` matched `syntax error`, `bad command name`
|
|
289
|
+
and `expected …` from `:put [:parse …]`, but not `missing closing brace` or
|
|
290
|
+
`missing value for where` — so the device stage reported those rejections as
|
|
291
|
+
a pass and the command ran. `/ip/address/find where=` passes offline, which
|
|
292
|
+
made the device classifier its only gate. Replaying every bare diagnostic in
|
|
293
|
+
the pinned corpus across 7.20.8 → 7.25beta3 finds exactly seven heads, and
|
|
294
|
+
the two `missing …` forms were the only misses; both are now recognized, with
|
|
295
|
+
the device's own position in `error.context.detail`. Echoed script text is
|
|
296
|
+
still not a verdict.
|
|
297
|
+
|
|
298
|
+
- **A stream's `/cancel` acknowledgement is now bounded (#385).** `api
|
|
299
|
+
--stream` stopped by `--duration` or Ctrl-C sent `/cancel` and then waited
|
|
300
|
+
with no bound, so a connected router that never answered held the CLI open
|
|
301
|
+
forever — `backend.close()` in the outer `finally` was unreachable while that
|
|
302
|
+
wait was pending. The wait is now bounded by the request `--timeout`, the
|
|
303
|
+
same bound as every other reply wait; past it the listen ends locally and the
|
|
304
|
+
session closes. The summary keeps its real `stopReason` and adds a
|
|
305
|
+
`transport/cancel-unacknowledged` **warning**, so the outcome never claims an
|
|
306
|
+
acknowledgement that did not arrive. A cooperating router is unchanged.
|
|
307
|
+
|
|
308
|
+
- **`retrieve --attributes` rejected real singleton properties (#377).**
|
|
309
|
+
`isKnownSingletonPath` knew only `/system/resource` and `/system/identity`,
|
|
310
|
+
so every other singleton (`/tool/romon`, `/system/routerboard`, `/ip/dns`, …)
|
|
311
|
+
was inspected as a list menu. `print,proplist` comes back empty on a
|
|
312
|
+
singleton, so validation fell back to `print`'s own flags (`as-value`,
|
|
313
|
+
`file`, `interval`, …) and offered those as the attribute list. The singleton
|
|
314
|
+
bit now comes from the device: a singleton's `get` takes only `value-name`,
|
|
315
|
+
while a list menu's `get` also takes `number`. The hardcoded pair survives
|
|
316
|
+
only as the `--validate=false` fallback, which skips inspect. Costs one extra
|
|
317
|
+
`request=child` for a menu that has `get`.
|
|
318
|
+
|
|
319
|
+
- **Completion candidates mixed help text and syntax rows into property names
|
|
320
|
+
(#380).** `extractCompletionNames` read `completion`/`name`/`value`/`text`
|
|
321
|
+
from every row, so the help string ("DNS cache size in kB") and the
|
|
322
|
+
`show: "false"` syntax rows (`[`, `(`, `$`, `"`, `*`, `<value>`) were offered
|
|
323
|
+
as attributes — which is why `--attributes '$'` passed validation. Rows carry
|
|
324
|
+
only `completion`/`offset`/`preference`/`show`/`style`/`text`/`type` on every
|
|
325
|
+
build recorded 7.9.2 → 7.25beta3, so `name`/`value` never matched anything.
|
|
326
|
+
On CHR 7.23.7, `show: "true"` is exactly the property set. The exported
|
|
327
|
+
`InspectCompletionItem` drops the never-present `name`/`value` and adds
|
|
328
|
+
`show`/`style`/`offset`/`preference`.
|
|
329
|
+
|
|
330
|
+
- **An unparseable `<router>` crashed instead of reporting (#381).**
|
|
331
|
+
`parseHostCandidate` passed the target straight to `new URL` — the only
|
|
332
|
+
`new URL` on user input in `src/` — so with the default CDB, `quickchr:NAME`,
|
|
333
|
+
`a b` or `bad:host:name` failed every command with a `TypeError: Invalid URL`
|
|
334
|
+
surfaced as `internal/unhandled`. It now raises `input/invalid-target` and
|
|
335
|
+
names the valid target forms; a `quickchr:NAME` guess is told to use
|
|
336
|
+
`--quickchr NAME`. With an explicit CDB the same input already stopped at a
|
|
337
|
+
classified `cdb/not-found-target`, so that path is unchanged.
|
|
338
|
+
|
|
339
|
+
### Changed
|
|
340
|
+
|
|
341
|
+
- **The pinned restraml inspect trees advance to the live channel builds.** The
|
|
342
|
+
pins named "current stable" and "next beta" while pointing a patch behind
|
|
343
|
+
both: stable 7.24.2 → 7.24.4 and development 7.25beta3 → 7.25beta5. History
|
|
344
|
+
anchors 7.10.2 and 7.16 are untouched and both current versions stay
|
|
345
|
+
architecture-paired. The refresh is structurally a no-op — the inspect union
|
|
346
|
+
holds at 617 menus and the catalog at 1,120 paths, with no path added or
|
|
347
|
+
removed and no kind change. What moved is current-only publication: the CLI
|
|
348
|
+
Reference grew 1,053 → 1,055 pages (1,060 → 1,062 entries) and now documents
|
|
349
|
+
`/interface/ethernet/pon` and `/ip/ssh/known-hosts`, so both move `inspect` →
|
|
350
|
+
`both` (961 `both`, 65 `inspect`, 94 `published`). Zero
|
|
351
|
+
navigation-vs-command contradictions, unchanged.
|
|
352
|
+
|
|
11
353
|
## 0.1.6 — 2026-09-15
|
|
12
354
|
|
|
13
355
|
### Changed
|
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
|
@@ -11,9 +11,13 @@ installing centrs or connecting to a router:
|
|
|
11
11
|
bunx @tikoci/centrs@next explain '/ip/address print' --json
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
Use `bunx @tikoci/centrs@next --help` to inspect the full preview CLI.
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
Use `bunx @tikoci/centrs@next --help` to inspect the full preview CLI. Since
|
|
15
|
+
0.1.6 the dist-tag follows standard SemVer semantics rather than the old
|
|
16
|
+
even/odd-minor rule: a plain version (`0.1.6`) publishes to `latest` and carries
|
|
17
|
+
`next` forward with it, while a pre-release suffix (`0.1.7-next.0`) publishes to
|
|
18
|
+
`next` only and never moves `latest`. So `@next` is the leading edge and can
|
|
19
|
+
equal `@latest` between cuts — `npm view @tikoci/centrs dist-tags` is the
|
|
20
|
+
authority.
|
|
17
21
|
|
|
18
22
|
The full flag reference for every command is generated from the CLI metadata
|
|
19
23
|
into [`docs/CLI.md`](docs/CLI.md); per-command behavior lives in
|
|
@@ -54,11 +58,11 @@ product; without them this would just be a worse `curl`.
|
|
|
54
58
|
|
|
55
59
|
| Command | Purpose |
|
|
56
60
|
| ---------- | ------- |
|
|
57
|
-
| `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. |
|
|
58
62
|
| `execute` | Run RouterOS CLI-shaped read/write commands (add/set/remove) over native API/REST/L2 surfaces. |
|
|
59
|
-
| `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). |
|
|
60
64
|
| `terminal` | Open an interactive console, primarily SSH or MAC-Telnet. |
|
|
61
|
-
| `check` | Probe reachability and management protocol availability. |
|
|
65
|
+
| `check` | Probe reachability and management protocol availability. **Designed, not implemented** — see `docs/MATRIX.md`. |
|
|
62
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. |
|
|
63
67
|
| `devices` | View and maintain the CDB-backed device registry (the only writer). |
|
|
64
68
|
| `discover` | Discover MNDP neighbors and optionally save them into the CDB. |
|
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.
|