@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.
- package/CHANGELOG.md +348 -0
- package/GLOSSARY.txt +252 -0
- package/LICENSE +21 -0
- package/README.md +44 -12
- package/commands/AGENTS.md +14 -5
- package/commands/api/AGENTS.md +13 -1
- package/commands/api/README.md +56 -26
- package/commands/api/examples.md +46 -1
- package/commands/btest/README.md +21 -32
- package/commands/check/README.md +6 -1
- package/commands/discover/README.md +5 -11
- package/commands/execute/README.md +77 -36
- package/commands/execute/examples.md +120 -10
- package/commands/explain/README.md +2756 -0
- package/commands/explain/examples.md +702 -0
- package/commands/mcp/README.md +7 -4
- package/commands/mcp/examples.md +2 -2
- package/commands/retrieve/README.md +25 -27
- package/commands/retrieve/examples.md +16 -0
- package/commands/terminal/examples.md +7 -1
- package/commands/transfer/README.md +28 -20
- package/commands/transfer/examples.md +29 -0
- package/docs/CLI.md +353 -0
- package/docs/CONSTITUTION.md +138 -30
- package/docs/MATRIX.md +92 -18
- package/docs/errors/README.md +3 -0
- package/docs/errors/quickchr/machine-not-found.md +22 -0
- package/docs/errors/quickchr/machine-stopped.md +20 -0
- package/docs/errors/quickchr/package-unavailable.md +27 -0
- package/docs/errors/quickchr/unsupported-via.md +35 -0
- package/docs/errors/quickchr/unsupported.md +35 -0
- package/docs/errors/routeros/version-unsupported.md +26 -0
- package/docs/errors/transport/provider-trust.md +29 -0
- package/docs/errors/usage/stdin-ignored.md +28 -0
- package/docs/errors/validation/syntax.md +29 -4
- package/docs/errors/validation/unknown-attribute.md +37 -4
- package/package.json +56 -10
- package/src/api-fanout.ts +46 -18
- package/src/api.ts +351 -121
- package/src/cli/api.ts +26 -7
- package/src/cli/btest.ts +19 -3
- package/src/cli/execute.ts +25 -6
- package/src/cli/explain.ts +503 -0
- package/src/cli/retrieve.ts +33 -1
- package/src/cli/selection.ts +71 -11
- package/src/cli/settings.ts +2 -2
- package/src/cli/terminal.ts +1 -1
- package/src/cli/transfer.ts +23 -6
- package/src/cli.ts +15 -2
- package/src/core/envelope.ts +58 -2
- package/src/core/error-catalog.ts +42 -1
- package/src/core/execute-command.ts +148 -0
- package/src/core/inspect.ts +36 -4
- package/src/core/preflight.ts +306 -0
- package/src/core/routeros-string.ts +24 -0
- package/src/core/version.ts +4 -0
- package/src/core/yaml.ts +86 -0
- package/src/errors.ts +1 -0
- package/src/execute-fanout.ts +47 -18
- package/src/execute.ts +339 -307
- package/src/explain/arg-tokens.ts +151 -0
- package/src/explain/args.ts +1749 -0
- package/src/explain/blocks.ts +175 -0
- package/src/explain/brace-slots.ts +143 -0
- package/src/explain/brace-tokens.ts +39 -0
- package/src/explain/catalog.ts +1343 -0
- package/src/explain/coordinates.ts +488 -0
- package/src/explain/defects.ts +218 -0
- package/src/explain/escape-tokens.ts +77 -0
- package/src/explain/highlight-projection.ts +308 -0
- package/src/explain/is-known-menu.ts +17 -0
- package/src/explain/menus.ts +668 -0
- package/src/explain/operator-tokens.ts +316 -0
- package/src/explain/operators.ts +465 -0
- package/src/explain/path-tokens.ts +150 -0
- package/src/explain/pathresolve.ts +1569 -0
- package/src/explain/quoted-string.ts +331 -0
- package/src/explain/scope-brace.ts +461 -0
- package/src/explain/segment.ts +1165 -0
- package/src/explain/separator.ts +187 -0
- package/src/explain/split-owner.ts +194 -0
- package/src/explain/string-tokens.ts +77 -0
- package/src/explain/symbol-values.ts +556 -0
- package/src/explain/symbols.ts +1617 -0
- package/src/explain/token-ranges.ts +72 -0
- package/src/explain/transport.ts +514 -0
- package/src/explain/value-tokens.ts +83 -0
- package/src/explain/values.ts +357 -0
- package/src/explain/verbs.ts +64 -0
- package/src/explain/verbsplit.ts +955 -0
- package/src/explain/write.ts +856 -0
- package/src/explain.ts +2602 -0
- package/src/index.ts +225 -6
- package/src/mcp/server.ts +2 -1
- package/src/offline-gate.ts +148 -0
- package/src/protocols/mac-telnet-console.ts +61 -17
- package/src/resolver/facts.ts +1 -1
- package/src/resolver/index.ts +23 -4
- package/src/resolver/quickchr-provider.ts +597 -0
- package/src/resolver/selection.ts +99 -12
- package/src/resolver/service-endpoint.ts +155 -0
- package/src/resolver/target.ts +1 -1
- package/src/retrieve-fanout.ts +27 -13
- package/src/retrieve.ts +82 -77
- package/src/settings.ts +2 -1
- package/src/transfer-fanout.ts +43 -26
- package/src/transfer.ts +88 -45
package/src/cli/api.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
formatCentrsErrorText,
|
|
22
22
|
} from "../errors.ts";
|
|
23
23
|
import { describeCentrs } from "../index.ts";
|
|
24
|
+
import { assertNoQuickchrOverrideConflict } from "../resolver/index.ts";
|
|
24
25
|
import {
|
|
25
26
|
type CliCommandMetadata,
|
|
26
27
|
expectValue,
|
|
@@ -36,6 +37,7 @@ import {
|
|
|
36
37
|
withTips,
|
|
37
38
|
} from "./missing-target.ts";
|
|
38
39
|
import {
|
|
40
|
+
assertQuickchrExclusive,
|
|
39
41
|
buildTargetSelection,
|
|
40
42
|
consumeSelectionFlag,
|
|
41
43
|
emptySelectionFlags,
|
|
@@ -57,7 +59,7 @@ export const apiCommand: CliCommandMetadata = {
|
|
|
57
59
|
"HTTP method, default GET (case-insensitive). GET→print, PUT→add, PATCH→set, DELETE→remove, POST→run.",
|
|
58
60
|
},
|
|
59
61
|
{
|
|
60
|
-
flag: "-f",
|
|
62
|
+
flag: "-f / --field",
|
|
61
63
|
valueName: "<key=value>",
|
|
62
64
|
description:
|
|
63
65
|
"Body field, repeatable; assembled into the JSON body (verbatim string values).",
|
|
@@ -92,7 +94,7 @@ export const apiCommand: CliCommandMetadata = {
|
|
|
92
94
|
{
|
|
93
95
|
flag: "--raw",
|
|
94
96
|
description:
|
|
95
|
-
"Strip the envelope; emit bare RouterOS JSON.
|
|
97
|
+
"Strip the envelope; emit bare RouterOS JSON. Defaults `--validate` to false (an explicit `--validate=true` still runs the gate); does not imply `--yes`.",
|
|
96
98
|
},
|
|
97
99
|
{
|
|
98
100
|
flag: "--yes",
|
|
@@ -168,9 +170,9 @@ export const apiCommand: CliCommandMetadata = {
|
|
|
168
170
|
description: "Per-request timeout (for REST, max 60s).",
|
|
169
171
|
},
|
|
170
172
|
{
|
|
171
|
-
flag: "--validate
|
|
173
|
+
flag: "--validate / --no-validate",
|
|
172
174
|
description:
|
|
173
|
-
"Run `/console/inspect` validation before the request (default true).",
|
|
175
|
+
"Run `/console/inspect` validation before the request (default true; `--validate=false` also accepted).",
|
|
174
176
|
},
|
|
175
177
|
{
|
|
176
178
|
flag: "--format",
|
|
@@ -366,6 +368,20 @@ export async function runApiCli(args: readonly string[]): Promise<number> {
|
|
|
366
368
|
console.log(renderCommandHelp(describeCentrs(), apiCommand));
|
|
367
369
|
return 0;
|
|
368
370
|
}
|
|
371
|
+
// Selection-shape usage errors fire before `--input` is read: a bad
|
|
372
|
+
// `--quickchr` combination must not block on stdin (`--input -`) or
|
|
373
|
+
// degrade into per-member fan-out failures. The resolver re-checks the
|
|
374
|
+
// override conflict for library callers.
|
|
375
|
+
const selectionFlags = parsed.selectionFlags ?? emptySelectionFlags();
|
|
376
|
+
const targetPositionals = parsed.targetPositionals ?? [];
|
|
377
|
+
assertQuickchrExclusive(selectionFlags, targetPositionals.length);
|
|
378
|
+
if (selectionFlags.quickchr.length > 0) {
|
|
379
|
+
assertNoQuickchrOverrideConflict(
|
|
380
|
+
parsed,
|
|
381
|
+
selectionFlags.quickchr[0] ?? "",
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
369
385
|
// Resolve `--input` (file / stdin) before handing the body to the orchestrator.
|
|
370
386
|
if (parsed.inputPath !== undefined) {
|
|
371
387
|
const inputPath = parsed.inputPath;
|
|
@@ -389,8 +405,6 @@ export async function runApiCli(args: readonly string[]): Promise<number> {
|
|
|
389
405
|
// Fan-out mode (selector flag present, or >1 positional target) has its own
|
|
390
406
|
// envelope shape, guards, and granular exit code. It runs first and rejects
|
|
391
407
|
// `--stream`/`--listen` + fan-out itself (single-session is exclusive).
|
|
392
|
-
const selectionFlags = parsed.selectionFlags ?? emptySelectionFlags();
|
|
393
|
-
const targetPositionals = parsed.targetPositionals ?? [];
|
|
394
408
|
if (isFanoutMode(selectionFlags, targetPositionals.length)) {
|
|
395
409
|
return await runApiFanoutCli(
|
|
396
410
|
parsed,
|
|
@@ -399,6 +413,11 @@ export async function runApiCli(args: readonly string[]): Promise<number> {
|
|
|
399
413
|
args,
|
|
400
414
|
);
|
|
401
415
|
}
|
|
416
|
+
// A single `--quickchr <name>` is single-target mode (streaming included):
|
|
417
|
+
// the machine name is the target, resolved from the live descriptor.
|
|
418
|
+
if (selectionFlags.quickchr.length === 1) {
|
|
419
|
+
parsed.quickchr = selectionFlags.quickchr[0];
|
|
420
|
+
}
|
|
402
421
|
|
|
403
422
|
// Open-ended follow (`--stream`/`--listen`, or a `/listen` endpoint) consumes
|
|
404
423
|
// the NDJSON envelope stream instead of a single one-shot envelope.
|
|
@@ -410,7 +429,7 @@ export async function runApiCli(args: readonly string[]): Promise<number> {
|
|
|
410
429
|
return await runApiListenCli(parsed, args);
|
|
411
430
|
}
|
|
412
431
|
|
|
413
|
-
if (!parsed.targetInput) {
|
|
432
|
+
if (!parsed.targetInput && parsed.quickchr === undefined) {
|
|
414
433
|
throw missingTargetError({
|
|
415
434
|
command: "api",
|
|
416
435
|
summary: "`centrs api` requires a <router> and an <endpoint>.",
|
package/src/cli/btest.ts
CHANGED
|
@@ -23,10 +23,10 @@ import {
|
|
|
23
23
|
renderBtestClientEnvelope,
|
|
24
24
|
renderBtestServerEnvelope,
|
|
25
25
|
} from "../btest.ts";
|
|
26
|
+
import { toYaml } from "../core/yaml.ts";
|
|
26
27
|
import { asCentrsError, formatCentrsErrorText } from "../errors.ts";
|
|
27
28
|
import { describeCentrs, parseDuration } from "../index.ts";
|
|
28
29
|
import type { BtestDirection, BtestProtocol } from "../protocols/btest.ts";
|
|
29
|
-
import { toYaml } from "../retrieve.ts";
|
|
30
30
|
import {
|
|
31
31
|
type CliCommandMetadata,
|
|
32
32
|
type CliCommandOption,
|
|
@@ -95,8 +95,14 @@ export const btestCommand: CliCommandMetadata = {
|
|
|
95
95
|
},
|
|
96
96
|
{ flag: "--random-data", description: "(client) Incompressible payload." },
|
|
97
97
|
{
|
|
98
|
-
flag: "--
|
|
99
|
-
description:
|
|
98
|
+
flag: "--nat-mode",
|
|
99
|
+
description:
|
|
100
|
+
"(client, UDP only) Originate an outbound datagram first so server→client data can traverse NAT. Automatic when the test receives.",
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
flag: "--authenticate / --no-authenticate",
|
|
104
|
+
description:
|
|
105
|
+
"(server) Require EC-SRP5 auth. Default true; `--authenticate=false` also accepted.",
|
|
100
106
|
},
|
|
101
107
|
{
|
|
102
108
|
flag: "--bind",
|
|
@@ -129,6 +135,16 @@ export const btestCommand: CliCommandMetadata = {
|
|
|
129
135
|
valueName: "<port>",
|
|
130
136
|
description: "Control port. Default 2000.",
|
|
131
137
|
},
|
|
138
|
+
{
|
|
139
|
+
flag: "--cdb-file",
|
|
140
|
+
valueName: "<path>",
|
|
141
|
+
description: "(client) Read target credentials from a WinBox CDB file.",
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
flag: "--cdb-password",
|
|
145
|
+
valueName: "<password>",
|
|
146
|
+
description: "(client) Decrypt an encrypted WinBox CDB file.",
|
|
147
|
+
},
|
|
132
148
|
{
|
|
133
149
|
flag: "--format",
|
|
134
150
|
valueName: `<${btestOutputFormats.join("|")}>`,
|
package/src/cli/execute.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
renderExecuteFanoutEnvelope,
|
|
15
15
|
} from "../execute-fanout.ts";
|
|
16
16
|
import { describeCentrs } from "../index.ts";
|
|
17
|
+
import { assertNoQuickchrOverrideConflict } from "../resolver/index.ts";
|
|
17
18
|
import {
|
|
18
19
|
type CliCommandMetadata,
|
|
19
20
|
expectValue,
|
|
@@ -29,9 +30,11 @@ import {
|
|
|
29
30
|
withTips,
|
|
30
31
|
} from "./missing-target.ts";
|
|
31
32
|
import {
|
|
33
|
+
assertQuickchrExclusive,
|
|
32
34
|
buildTargetSelection,
|
|
33
35
|
consumeSelectionFlag,
|
|
34
36
|
emptySelectionFlags,
|
|
37
|
+
hasTargetSelector,
|
|
35
38
|
isFanoutMode,
|
|
36
39
|
type SelectionFlags,
|
|
37
40
|
selectionCommandOptions,
|
|
@@ -105,9 +108,9 @@ export const executeCommand: CliCommandMetadata = {
|
|
|
105
108
|
description: "Per-request timeout (for REST, max 60s).",
|
|
106
109
|
},
|
|
107
110
|
{
|
|
108
|
-
flag: "--validate
|
|
111
|
+
flag: "--validate / --no-validate",
|
|
109
112
|
description:
|
|
110
|
-
"Run RouterOS :parse and /console/inspect validation before execution (default true).",
|
|
113
|
+
"Run RouterOS :parse and /console/inspect validation before execution (default true; `--validate=false` also accepted).",
|
|
111
114
|
},
|
|
112
115
|
{
|
|
113
116
|
flag: "--yes",
|
|
@@ -265,12 +268,12 @@ export function parseExecuteCliArgs(args: readonly string[]): ExecuteCliArgs {
|
|
|
265
268
|
parsed.selectionFlags = selectionFlags;
|
|
266
269
|
// Positional boundary for execute (see `docs/CONSTITUTION.md` → Target selection):
|
|
267
270
|
// - `--` present: targets are the positionals BEFORE it, command is AFTER it.
|
|
268
|
-
// - selector flag present, no `--`: every positional is the
|
|
269
|
-
// come from the
|
|
271
|
+
// - selector flag (or `--quickchr`) present, no `--`: every positional is the
|
|
272
|
+
// command (targets come from the flag).
|
|
270
273
|
// - otherwise (no `--`, no selector): legacy single-target split — first
|
|
271
274
|
// positional is the target, the rest is the command. Multiple positional
|
|
272
275
|
// targets therefore REQUIRE `--`.
|
|
273
|
-
const hasSelector =
|
|
276
|
+
const hasSelector = hasTargetSelector(selectionFlags);
|
|
274
277
|
if (endOfOptions) {
|
|
275
278
|
parsed.targetPositionals = positional;
|
|
276
279
|
parsed.command = afterDashDash.join(" ");
|
|
@@ -300,6 +303,17 @@ export async function runExecuteCli(args: readonly string[]): Promise<number> {
|
|
|
300
303
|
// own envelope shape, single up-front write-confirm, and granular exit code.
|
|
301
304
|
const selectionFlags = parsed.selectionFlags ?? emptySelectionFlags();
|
|
302
305
|
const targetPositionals = parsed.targetPositionals ?? [];
|
|
306
|
+
assertQuickchrExclusive(selectionFlags, targetPositionals.length);
|
|
307
|
+
// Direct connection overrides conflict with `--quickchr` globally: reject
|
|
308
|
+
// before fan-out dispatch so a repeated `--quickchr` gets one usage error
|
|
309
|
+
// (exit 1), never per-member failures. The resolver re-checks for library
|
|
310
|
+
// callers.
|
|
311
|
+
if (selectionFlags.quickchr.length > 0) {
|
|
312
|
+
assertNoQuickchrOverrideConflict(
|
|
313
|
+
parsed,
|
|
314
|
+
selectionFlags.quickchr[0] ?? "",
|
|
315
|
+
);
|
|
316
|
+
}
|
|
303
317
|
if (isFanoutMode(selectionFlags, targetPositionals.length)) {
|
|
304
318
|
return await runExecuteFanoutCli(
|
|
305
319
|
parsed,
|
|
@@ -308,12 +322,17 @@ export async function runExecuteCli(args: readonly string[]): Promise<number> {
|
|
|
308
322
|
args,
|
|
309
323
|
);
|
|
310
324
|
}
|
|
325
|
+
// A single `--quickchr <name>` is single-target mode: the machine name is
|
|
326
|
+
// the target (resolved from the live descriptor inside the resolver).
|
|
327
|
+
if (selectionFlags.quickchr.length === 1) {
|
|
328
|
+
parsed.quickchr = selectionFlags.quickchr[0];
|
|
329
|
+
}
|
|
311
330
|
|
|
312
331
|
// No positional at all: lead with the missing-target guidance (CDB picker /
|
|
313
332
|
// discover) rather than the missing-command error — there is nothing to run
|
|
314
333
|
// without a device. A lone positional is treated as the target, so the
|
|
315
334
|
// "requires a command" path still fires for `execute <target>`.
|
|
316
|
-
if (!parsed.targetInput) {
|
|
335
|
+
if (!parsed.targetInput && parsed.quickchr === undefined) {
|
|
317
336
|
throw missingTargetError({
|
|
318
337
|
command: "execute",
|
|
319
338
|
summary: "`centrs execute` requires a <target> and a RouterOS command.",
|