@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/src/execute.ts
CHANGED
|
@@ -10,17 +10,22 @@ import type {
|
|
|
10
10
|
ValidationTrace,
|
|
11
11
|
Warning,
|
|
12
12
|
} from "./core/envelope.ts";
|
|
13
|
-
import { buildTip } from "./core/envelope.ts";
|
|
13
|
+
import { buildTip, notReachedReason } from "./core/envelope.ts";
|
|
14
14
|
import {
|
|
15
15
|
type CanonicalExecuteCommand,
|
|
16
16
|
canonicalizeExecuteCommand,
|
|
17
17
|
isWriteShaped,
|
|
18
18
|
} from "./core/execute-command.ts";
|
|
19
19
|
import { inspectArgumentNames, pathTokens } from "./core/inspect.ts";
|
|
20
|
-
import {
|
|
20
|
+
import { mapRouterOsResultError } from "./core/routeros-errors.ts";
|
|
21
21
|
import { routerOsStringLiteral } from "./core/routeros-string.ts";
|
|
22
22
|
import { toYaml } from "./core/yaml.ts";
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
CentrsError,
|
|
25
|
+
formatCentrsErrorText,
|
|
26
|
+
SYNTAX_REJECTED_REMEDIATION,
|
|
27
|
+
serializeCentrsError,
|
|
28
|
+
} from "./errors.ts";
|
|
24
29
|
import {
|
|
25
30
|
assertOfflineSyntax,
|
|
26
31
|
isOfflineGateRejection,
|
|
@@ -623,18 +628,21 @@ function failedValidationMeta(
|
|
|
623
628
|
return disabledValidationMeta();
|
|
624
629
|
}
|
|
625
630
|
const deviceSource = deviceStageSource(resolved);
|
|
626
|
-
|
|
627
|
-
//
|
|
628
|
-
//
|
|
629
|
-
|
|
630
|
-
|
|
631
|
+
const structured = resolved.canonical.mode === "structured";
|
|
632
|
+
// A flat summary is present only when its check gave a verdict (GH#435).
|
|
633
|
+
// Script-shaped input has no semantic half at all: `not-applicable`. For
|
|
634
|
+
// structured input, a syntax rejection means the name-level check was
|
|
635
|
+
// never asked, so `semantic` is left out rather than reported `false`.
|
|
636
|
+
const semanticNotReached = structured
|
|
637
|
+
? {}
|
|
638
|
+
: { semantic: "not-applicable" as const };
|
|
631
639
|
if (isOfflineGateRejection(error)) {
|
|
632
640
|
return {
|
|
633
641
|
enabled: true,
|
|
634
642
|
source: OFFLINE_GATE_SOURCE,
|
|
635
643
|
result: "failed",
|
|
636
644
|
syntax: false,
|
|
637
|
-
|
|
645
|
+
...semanticNotReached,
|
|
638
646
|
stages: [
|
|
639
647
|
{ stage: "offline", source: OFFLINE_GATE_SOURCE, result: "failed" },
|
|
640
648
|
{
|
|
@@ -648,31 +656,60 @@ function failedValidationMeta(
|
|
|
648
656
|
}
|
|
649
657
|
// Only a `validation/*` error tells us the DEVICE stage rejected it — and
|
|
650
658
|
// only then do we know stage 1 passed, because stage 1 runs first and would
|
|
651
|
-
// have thrown its own (offline-tagged) error otherwise.
|
|
652
|
-
//
|
|
653
|
-
//
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
659
|
+
// have thrown its own (offline-tagged) error otherwise. Everything downstream
|
|
660
|
+
// of a passed gate — a RouterOS fault on the run, a transport drop — is
|
|
661
|
+
// served by `trace.validation` before this function is reached.
|
|
662
|
+
if (error instanceof CentrsError && error.code.startsWith("validation/")) {
|
|
663
|
+
// Only a structured `unknown-attribute` (inspect, or `:parse`'s `bad
|
|
664
|
+
// parameter`) says the syntax was fine and a name was wrong. Every other
|
|
665
|
+
// device rejection — `syntax`, `unknown-path`, `package-missing`,
|
|
666
|
+
// `menu-unavailable` — is `:parse` refusing the command itself.
|
|
667
|
+
const nameRejected =
|
|
668
|
+
structured && error.code === "validation/unknown-attribute";
|
|
669
|
+
return {
|
|
670
|
+
enabled: true,
|
|
671
|
+
source: deviceSource,
|
|
672
|
+
result: "failed",
|
|
673
|
+
...(nameRejected
|
|
674
|
+
? { syntax: true, semantic: false }
|
|
675
|
+
: { syntax: false, ...semanticNotReached }),
|
|
676
|
+
stages: [
|
|
677
|
+
offlineStagePassed(trace?.offlineVerdict),
|
|
678
|
+
{ stage: "device", source: deviceSource, result: "failed" },
|
|
679
|
+
],
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
// Anything else stopped the gate before it produced a verdict: a transport or
|
|
683
|
+
// auth failure, a device error that is not a parse answer, or a refusal
|
|
684
|
+
// before the gate ran (write confirmation). No `result`, no `syntax` /
|
|
685
|
+
// `semantic` — no stage said either (GH#435). Without a trace this builder
|
|
686
|
+
// cannot know whether stage 1 ran, so it leaves that stage out.
|
|
687
|
+
const reason = notReachedReason(
|
|
688
|
+
error instanceof CentrsError ? error.code : "internal/unhandled",
|
|
689
|
+
);
|
|
690
|
+
const device: EnvelopeValidationStage = {
|
|
691
|
+
stage: "device",
|
|
692
|
+
source: deviceSource,
|
|
693
|
+
result: "skipped",
|
|
694
|
+
reason,
|
|
695
|
+
};
|
|
696
|
+
if (!trace) {
|
|
697
|
+
return { enabled: true, source: deviceSource, stages: [device] };
|
|
698
|
+
}
|
|
658
699
|
return {
|
|
659
700
|
enabled: true,
|
|
660
701
|
source: deviceSource,
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
},
|
|
673
|
-
],
|
|
674
|
-
}
|
|
675
|
-
: {}),
|
|
702
|
+
stages: [
|
|
703
|
+
trace.offlineVerdict
|
|
704
|
+
? offlineStagePassed(trace.offlineVerdict)
|
|
705
|
+
: {
|
|
706
|
+
stage: "offline",
|
|
707
|
+
source: OFFLINE_GATE_SOURCE,
|
|
708
|
+
result: "skipped",
|
|
709
|
+
reason,
|
|
710
|
+
},
|
|
711
|
+
device,
|
|
712
|
+
],
|
|
676
713
|
};
|
|
677
714
|
}
|
|
678
715
|
|
|
@@ -738,7 +775,7 @@ export function renderExecuteEnvelope(
|
|
|
738
775
|
case "text":
|
|
739
776
|
return envelope.ok
|
|
740
777
|
? renderExecuteSuccessText(envelope, options)
|
|
741
|
-
:
|
|
778
|
+
: formatCentrsErrorText(envelope.error, options);
|
|
742
779
|
default:
|
|
743
780
|
return exhaustiveOutputFormat(format);
|
|
744
781
|
}
|
|
@@ -892,7 +929,16 @@ async function runSyntaxGate(
|
|
|
892
929
|
resolved: ResolvedExecuteRequest,
|
|
893
930
|
backend: ProtocolAdapter,
|
|
894
931
|
): Promise<void> {
|
|
895
|
-
|
|
932
|
+
await validateRouterOsScript(resolved.command, backend, resolved.via.value);
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/** Shared live `:parse` stage for every surface that carries a CLI string. */
|
|
936
|
+
export async function validateRouterOsScript(
|
|
937
|
+
command: string,
|
|
938
|
+
backend: ProtocolAdapter,
|
|
939
|
+
via: RouterOsProtocol,
|
|
940
|
+
): Promise<void> {
|
|
941
|
+
const script = `:put [:parse ${routerOsStringLiteral(command)}]`;
|
|
896
942
|
try {
|
|
897
943
|
// GH#230: `:parse` never throws (CHR 7.23.3). The diagnostic rides the
|
|
898
944
|
// success value's text — over REST/native the `ret` field (HTTP 200 /
|
|
@@ -901,7 +947,7 @@ async function runSyntaxGate(
|
|
|
901
947
|
// Read the return and classify it: one `:parse` covers syntax and the
|
|
902
948
|
// unknown-attribute (name-level) gate.
|
|
903
949
|
const result = await backend.execute({ path: "", command: "", script });
|
|
904
|
-
classifyParseResult(result.ret ?? "",
|
|
950
|
+
classifyParseResult(result.ret ?? "", command, via);
|
|
905
951
|
} catch (error) {
|
|
906
952
|
// The preflight is a syntax gate, but `backend.execute` also surfaces
|
|
907
953
|
// connection and authentication failures (login happens lazily here).
|
|
@@ -922,12 +968,16 @@ async function runSyntaxGate(
|
|
|
922
968
|
code: "validation/syntax",
|
|
923
969
|
summary:
|
|
924
970
|
"RouterOS rejected the command syntax during `:parse` preflight.",
|
|
925
|
-
remediation:
|
|
926
|
-
"Fix the RouterOS CLI syntax, especially quotes and bracketed expressions, then retry.",
|
|
971
|
+
remediation: SYNTAX_REJECTED_REMEDIATION,
|
|
927
972
|
context: {
|
|
928
|
-
command
|
|
973
|
+
command,
|
|
929
974
|
validationSource: ":put [:parse ...]",
|
|
930
|
-
via
|
|
975
|
+
via,
|
|
976
|
+
// Keep RouterOS's own words from the mapped fault (GH#362).
|
|
977
|
+
...(error instanceof CentrsError &&
|
|
978
|
+
typeof error.context?.["detail"] === "string"
|
|
979
|
+
? { detail: error.context["detail"] }
|
|
980
|
+
: {}),
|
|
931
981
|
},
|
|
932
982
|
...(position ? { position } : {}),
|
|
933
983
|
cause: error,
|
|
@@ -1013,10 +1063,8 @@ function resolveExecuteProtocol(
|
|
|
1013
1063
|
cdb?: CdbResolution,
|
|
1014
1064
|
config: Record<string, string | undefined> = {},
|
|
1015
1065
|
): ResolvedSetting<RouterOsProtocol> {
|
|
1016
|
-
const defaultVia =
|
|
1017
|
-
|
|
1018
|
-
: "native-api";
|
|
1019
|
-
const via = resolveStringSetting(
|
|
1066
|
+
const defaultVia = defaultExecuteVia(request, env, cdb, config);
|
|
1067
|
+
const resolvedVia = resolveStringSetting(
|
|
1020
1068
|
request.via,
|
|
1021
1069
|
env,
|
|
1022
1070
|
"CENTRS_VIA",
|
|
@@ -1026,12 +1074,20 @@ function resolveExecuteProtocol(
|
|
|
1026
1074
|
cdb?.overrides.via,
|
|
1027
1075
|
config,
|
|
1028
1076
|
);
|
|
1077
|
+
// A URL-derived default names its reason, since native-api is the documented one.
|
|
1078
|
+
const via =
|
|
1079
|
+
resolvedVia?.source.kind === "default" && resolvedVia.value === "rest-api"
|
|
1080
|
+
? {
|
|
1081
|
+
...resolvedVia,
|
|
1082
|
+
source: { kind: "default" as const, key: "via:target-url" },
|
|
1083
|
+
}
|
|
1084
|
+
: resolvedVia;
|
|
1029
1085
|
if (!via) {
|
|
1030
1086
|
throw new CentrsError({
|
|
1031
1087
|
code: "internal/unhandled",
|
|
1032
1088
|
summary: "Failed to resolve the default execute protocol.",
|
|
1033
1089
|
remediation:
|
|
1034
|
-
"Report this bug; execute should default to native-api or mac-telnet.",
|
|
1090
|
+
"Report this bug; execute should default to native-api, rest-api, or mac-telnet.",
|
|
1035
1091
|
});
|
|
1036
1092
|
}
|
|
1037
1093
|
if (!plannedProtocols.includes(via.value as RouterOsProtocol)) {
|
|
@@ -1065,23 +1121,25 @@ function resolveExecuteProtocol(
|
|
|
1065
1121
|
return via as ResolvedSetting<RouterOsProtocol>;
|
|
1066
1122
|
}
|
|
1067
1123
|
|
|
1068
|
-
|
|
1124
|
+
/**
|
|
1125
|
+
* Execute's default transport when no tier pins `via`, decided from the
|
|
1126
|
+
* *effective* target (host > CENTRS_HOST > CDB target > positional), not the
|
|
1127
|
+
* positional alone — an IP supplied via --host/CENTRS_HOST must default to
|
|
1128
|
+
* native-api even when the positional was a MAC. An unresolved MAC defaults to
|
|
1129
|
+
* mac-telnet, and an explicit `http(s)://` base URL names the REST service, so
|
|
1130
|
+
* its scheme and port are honored rather than dropped for native-api (#426).
|
|
1131
|
+
*/
|
|
1132
|
+
function defaultExecuteVia(
|
|
1069
1133
|
request: ExecuteRequest,
|
|
1070
1134
|
env: Record<string, string | undefined>,
|
|
1071
1135
|
cdb?: CdbResolution,
|
|
1072
1136
|
config: Record<string, string | undefined> = {},
|
|
1073
|
-
):
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
) {
|
|
1079
|
-
return false;
|
|
1137
|
+
): RouterOsProtocol {
|
|
1138
|
+
// A quickchr target's endpoint comes from its descriptor, so no host
|
|
1139
|
+
// setting may steer its transport.
|
|
1140
|
+
if (request.quickchr !== undefined) {
|
|
1141
|
+
return "native-api";
|
|
1080
1142
|
}
|
|
1081
|
-
// Decide the default transport from the *effective* target (host >
|
|
1082
|
-
// CENTRS_HOST > CDB target > positional), not the positional alone — an IP
|
|
1083
|
-
// supplied via --host/CENTRS_HOST must default to native-api even when the
|
|
1084
|
-
// positional was a MAC.
|
|
1085
1143
|
const candidate = effectiveHostCandidate({
|
|
1086
1144
|
host: request.host,
|
|
1087
1145
|
targetInput: request.targetInput,
|
|
@@ -1089,7 +1147,13 @@ function isUnresolvedMacTarget(
|
|
|
1089
1147
|
env,
|
|
1090
1148
|
config,
|
|
1091
1149
|
});
|
|
1092
|
-
|
|
1150
|
+
if (candidate === undefined) {
|
|
1151
|
+
return "native-api";
|
|
1152
|
+
}
|
|
1153
|
+
if (isMacAddress(candidate)) {
|
|
1154
|
+
return "mac-telnet";
|
|
1155
|
+
}
|
|
1156
|
+
return /^https?:\/\//i.test(candidate) ? "rest-api" : "native-api";
|
|
1093
1157
|
}
|
|
1094
1158
|
|
|
1095
1159
|
function resolveFormat(
|
|
@@ -1298,52 +1362,14 @@ function routerOsFailureFromResult(
|
|
|
1298
1362
|
result: ProtocolExecuteResult,
|
|
1299
1363
|
via: RouterOsProtocol,
|
|
1300
1364
|
): CentrsError | undefined {
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
]),
|
|
1310
|
-
].filter((value): value is string => typeof value === "string");
|
|
1311
|
-
const failure = candidates.find((candidate) =>
|
|
1312
|
-
isRouterOsFailureString(candidate),
|
|
1313
|
-
);
|
|
1314
|
-
return failure
|
|
1315
|
-
? mapRouterOsError(failure, {
|
|
1316
|
-
transport:
|
|
1317
|
-
via === "rest-api" || via === "mac-telnet" || via === "ssh"
|
|
1318
|
-
? via
|
|
1319
|
-
: "native-api",
|
|
1320
|
-
context: { via },
|
|
1321
|
-
})
|
|
1322
|
-
: undefined;
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
function isRouterOsFailureString(value: string): boolean {
|
|
1326
|
-
const trimmed = value.trim();
|
|
1327
|
-
// REST/native fault strings (unchanged).
|
|
1328
|
-
if (
|
|
1329
|
-
/^(failure:|error:)|unknown parameter|invalid value|session closed|\(:error; line \d+\)/i.test(
|
|
1330
|
-
trimmed,
|
|
1331
|
-
)
|
|
1332
|
-
) {
|
|
1333
|
-
return true;
|
|
1334
|
-
}
|
|
1335
|
-
// Console (mac-telnet) error forms. `ret` here is ordinary stdout (every
|
|
1336
|
-
// mac-telnet command and REST `/execute`), so anchor to the grounded RouterOS
|
|
1337
|
-
// error *shapes* — the `(line N column M)` source location for parse rejections
|
|
1338
|
-
// — rather than bare phrases, so `:put "syntax error"` is not misread as a
|
|
1339
|
-
// failure.
|
|
1340
|
-
return (
|
|
1341
|
-
/(?:^|\n)\s*(?:bad parameter \S+|syntax error)\b[^\n]*\(line \d+ column \d+\)/i.test(
|
|
1342
|
-
trimmed,
|
|
1343
|
-
) ||
|
|
1344
|
-
/(?:^|\n)\s*bad command name\b/i.test(trimmed) ||
|
|
1345
|
-
/(?:^|\n)\s*expected end of command\b/i.test(trimmed)
|
|
1346
|
-
);
|
|
1365
|
+
if (typeof result.ret !== "string") return undefined;
|
|
1366
|
+
return mapRouterOsResultError(result.ret, {
|
|
1367
|
+
transport:
|
|
1368
|
+
via === "rest-api" || via === "mac-telnet" || via === "ssh"
|
|
1369
|
+
? via
|
|
1370
|
+
: "native-api",
|
|
1371
|
+
context: { via },
|
|
1372
|
+
});
|
|
1347
1373
|
}
|
|
1348
1374
|
|
|
1349
1375
|
function applyMaxResultsBudget(
|
|
@@ -1396,20 +1422,6 @@ function renderExecuteSuccessText(
|
|
|
1396
1422
|
return lines.join("\n");
|
|
1397
1423
|
}
|
|
1398
1424
|
|
|
1399
|
-
function renderExecuteErrorText(
|
|
1400
|
-
envelope: ExecuteErrorEnvelope,
|
|
1401
|
-
options: { verbose?: boolean },
|
|
1402
|
-
): string {
|
|
1403
|
-
const lines = [`[${envelope.error.code}] ${envelope.error.summary}`];
|
|
1404
|
-
if (envelope.error.remediation) {
|
|
1405
|
-
lines.push(`Fix: ${envelope.error.remediation}`);
|
|
1406
|
-
}
|
|
1407
|
-
if (options.verbose && envelope.error.context) {
|
|
1408
|
-
lines.push("", JSON.stringify(envelope.error.context, null, 2));
|
|
1409
|
-
}
|
|
1410
|
-
return lines.join("\n");
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
1425
|
function syntaxCause(error: unknown): unknown {
|
|
1414
1426
|
if (error instanceof CentrsError) {
|
|
1415
1427
|
return error.causeData ?? error.context ?? error.toJSON();
|
package/src/explain/catalog.ts
CHANGED
|
@@ -9,26 +9,28 @@
|
|
|
9
9
|
* Union of two first-order sources (#228):
|
|
10
10
|
*
|
|
11
11
|
* 1. MikroTik's published CLI Reference (`https://manual.mikrotik.com/docs/cli-reference/`),
|
|
12
|
-
* 1,
|
|
12
|
+
* 1,099 pages, 1,106 entries — first-order about the definition
|
|
13
13
|
* structs and their build-time gates. Since #285 every page is a leaf whose
|
|
14
14
|
* slug is the CLI path, so no spelling has to be rewritten to be looked up.
|
|
15
|
-
* 2.
|
|
15
|
+
* 2. 8 pinned restraml `/console/inspect` trees
|
|
16
16
|
* (`https://tikoci.github.io/restraml/`) — first-order about the CLI surface:
|
|
17
17
|
*
|
|
18
18
|
* | Tree | Arch | RouterOS | Nodes |
|
|
19
19
|
* | ---- | ---- | -------- | ----- |
|
|
20
20
|
* | `7.10.2/extra/inspect.json` | x86 | 7.10.2 | 29,086 |
|
|
21
21
|
* | `7.16/extra/inspect.json` | x86 | 7.16 | 34,876 |
|
|
22
|
-
* | `7.24.
|
|
23
|
-
* | `7.24.
|
|
24
|
-
* | `7.
|
|
25
|
-
* | `7.
|
|
22
|
+
* | `7.24.4/extra/deep-inspect.x86.json` | x86 | 7.24.4 | 41,345 |
|
|
23
|
+
* | `7.24.4/extra/deep-inspect.arm64.json` | arm64 | 7.24.4 | 42,691 |
|
|
24
|
+
* | `7.25beta5/extra/deep-inspect.x86.json` | x86 | 7.25beta5 | 41,794 |
|
|
25
|
+
* | `7.25beta5/extra/deep-inspect.arm64.json` | arm64 | 7.25beta5 | 43,150 |
|
|
26
|
+
* | `7.26beta1/extra/deep-inspect.x86.json` | x86 | 7.26beta1 | 43,294 |
|
|
27
|
+
* | `7.26beta1/extra/deep-inspect.arm64.json` | arm64 | 7.26beta1 | 44,650 |
|
|
26
28
|
*
|
|
27
29
|
* | Kind | Total | `both` | `inspect` | `published` |
|
|
28
30
|
* | ---- | ----- | ------ | --------- | ----------- |
|
|
29
|
-
* | `menu` |
|
|
30
|
-
* | `command` |
|
|
31
|
-
* | `settings` |
|
|
31
|
+
* | `menu` | 568 | 457 | 67 | 44 |
|
|
32
|
+
* | `command` | 477 | 436 | 0 | 41 |
|
|
33
|
+
* | `settings` | 121 | 112 | 0 | 9 |
|
|
32
34
|
*
|
|
33
35
|
* Zero navigation-vs-command contradictions between the two sources. Generation
|
|
34
36
|
* aborts if that ever stops holding, rather than picking a winner. Where a path
|
|
@@ -89,7 +91,7 @@ export interface CatalogEntry {
|
|
|
89
91
|
* `path|kind|provenance|package|conditions|syscap` — with trailing empty
|
|
90
92
|
* columns trimmed. Lower-cased, slash-led and sorted by path.
|
|
91
93
|
*
|
|
92
|
-
* Text rather than ~1,
|
|
94
|
+
* Text rather than ~1,166 object literals so that one path is one line in a
|
|
93
95
|
* review diff, and so the formatter has nothing to re-wrap.
|
|
94
96
|
*/
|
|
95
97
|
const ROWS = `
|
|
@@ -154,6 +156,38 @@ const ROWS = `
|
|
|
154
156
|
/certificate/settings|settings|both
|
|
155
157
|
/certificate/sign|command|both
|
|
156
158
|
/certificate/sign-certificate-request|command|both
|
|
159
|
+
/cmr|settings|both|cmr
|
|
160
|
+
/cmr/alert|menu|both|cmr
|
|
161
|
+
/cmr/alert/show-devices|command|both|cmr
|
|
162
|
+
/cmr/alert/test|command|both|cmr
|
|
163
|
+
/cmr/client|settings|both||!smips, !powerpc
|
|
164
|
+
/cmr/client/forget|command|both||!smips, !powerpc
|
|
165
|
+
/cmr/client/pair|command|both||!smips, !powerpc
|
|
166
|
+
/cmr/client/push-button|command|both
|
|
167
|
+
/cmr/device|menu|both|cmr
|
|
168
|
+
/cmr/device/apptraffic|command|both|cmr
|
|
169
|
+
/cmr/device/dashboard|command|both|cmr
|
|
170
|
+
/cmr/device/pair|command|both|cmr
|
|
171
|
+
/cmr/device/reboot|command|both|cmr
|
|
172
|
+
/cmr/device/run-script|command|both|cmr
|
|
173
|
+
/cmr/device/upgrade|command|both|cmr
|
|
174
|
+
/cmr/device/wifi-logs|command|both|cmr
|
|
175
|
+
/cmr/layout|menu|both|cmr
|
|
176
|
+
/cmr/layout/add-devices|command|both|cmr
|
|
177
|
+
/cmr/layout/link|menu|both|cmr
|
|
178
|
+
/cmr/layout/node|menu|both|cmr
|
|
179
|
+
/cmr/layout/rebuild-links|command|both|cmr
|
|
180
|
+
/cmr/push-button|command|both
|
|
181
|
+
/cmr/upgrade|menu|both|cmr
|
|
182
|
+
/cmr/upgrade/job|menu|both|cmr
|
|
183
|
+
/cmr/upgrade/job/run-next|command|both|cmr
|
|
184
|
+
/cmr/upgrade/job/show-devices|command|both|cmr
|
|
185
|
+
/cmr/upgrade/show-devices|command|both|cmr
|
|
186
|
+
/cmr/upgrade/trigger|command|both|cmr
|
|
187
|
+
/cmr/upgrade/version-check|command|both|cmr
|
|
188
|
+
/cmr/vlan|menu|both|cmr
|
|
189
|
+
/cmr/wifi|menu|both|cmr
|
|
190
|
+
/cmr/wifi/radio|menu|both|cmr
|
|
157
191
|
/console|menu|inspect
|
|
158
192
|
/console/inspect|command|both
|
|
159
193
|
/console/settings|settings|both
|
|
@@ -285,7 +319,8 @@ const ROWS = `
|
|
|
285
319
|
/interface/ethernet/poe/monitor|command|published|||(poe or poe-in)
|
|
286
320
|
/interface/ethernet/poe/power-cycle|command|published|||(poe or poe-in)
|
|
287
321
|
/interface/ethernet/poe/settings|settings|published|||(poe or poe-in) and poesettings
|
|
288
|
-
/interface/ethernet/pon|menu|
|
|
322
|
+
/interface/ethernet/pon|menu|both
|
|
323
|
+
/interface/ethernet/pon/monitor|command|both
|
|
289
324
|
/interface/ethernet/reset-counters|command|both
|
|
290
325
|
/interface/ethernet/reset-mac-address|command|both
|
|
291
326
|
/interface/ethernet/switch|menu|both
|
|
@@ -327,7 +362,8 @@ const ROWS = `
|
|
|
327
362
|
/interface/ethernet/switch/reserved-fdb|menu|published|||musicswitch
|
|
328
363
|
/interface/ethernet/switch/reset-counters|command|both
|
|
329
364
|
/interface/ethernet/switch/rule|menu|both|||rbswitch
|
|
330
|
-
/interface/ethernet/switch/rule/reset-counters|command|both|||rbswitch
|
|
365
|
+
/interface/ethernet/switch/rule/reset-counters|command|both|||rbswitch and crs_prestera
|
|
366
|
+
/interface/ethernet/switch/rule/reset-counters-all|command|published|||rbswitch and crs_prestera
|
|
331
367
|
/interface/ethernet/switch/shaper|menu|published|||musicswitch
|
|
332
368
|
/interface/ethernet/switch/stats|settings|published||!smips|musicswitch
|
|
333
369
|
/interface/ethernet/switch/trunk|menu|published|||musicswitch
|
|
@@ -442,6 +478,11 @@ const ROWS = `
|
|
|
442
478
|
/interface/wifi/capsman/remote-cap/upgrade|command|both
|
|
443
479
|
/interface/wifi/channel|menu|both
|
|
444
480
|
/interface/wifi/configuration|menu|both
|
|
481
|
+
/interface/wifi/dashboard|menu|inspect
|
|
482
|
+
/interface/wifi/dashboard/settings|settings|both
|
|
483
|
+
/interface/wifi/dashboard/show|command|both
|
|
484
|
+
/interface/wifi/dashboard/show-clients|command|both
|
|
485
|
+
/interface/wifi/dashboard/show-logs|command|both
|
|
445
486
|
/interface/wifi/datapath|menu|both
|
|
446
487
|
/interface/wifi/devel|command|both
|
|
447
488
|
/interface/wifi/flat-snoop|command|both
|
|
@@ -525,6 +566,7 @@ const ROWS = `
|
|
|
525
566
|
/interface/wireless/sniffer/sniff|command|both|wireless-rep
|
|
526
567
|
/interface/wireless/snooper|settings|both|wireless-rep
|
|
527
568
|
/interface/wireless/snooper/flat-snoop|command|both|wireless-rep
|
|
569
|
+
/interface/wireless/snooper/snoop|command|both|wireless-rep
|
|
528
570
|
/interface/wireless/spectral-scan|command|both|wireless-rep
|
|
529
571
|
/interface/wireless/wds|menu|both|wireless-rep
|
|
530
572
|
/interface/wireless/wds/monitor|command|both|wireless-rep
|
|
@@ -744,7 +786,7 @@ const ROWS = `
|
|
|
744
786
|
/ip/ssh|settings|both
|
|
745
787
|
/ip/ssh/export-host-key|command|both
|
|
746
788
|
/ip/ssh/import-host-key|command|both
|
|
747
|
-
/ip/ssh/known-hosts|menu|
|
|
789
|
+
/ip/ssh/known-hosts|menu|both
|
|
748
790
|
/ip/ssh/regenerate-host-key|command|both
|
|
749
791
|
/ip/tftp|menu|both
|
|
750
792
|
/ip/tftp/settings|settings|both
|
|
@@ -984,6 +1026,9 @@ const ROWS = `
|
|
|
984
1026
|
/system/clock/manual|settings|both
|
|
985
1027
|
/system/console|menu|both
|
|
986
1028
|
/system/console/screen|settings|both||i386
|
|
1029
|
+
/system/dashboard|menu|inspect
|
|
1030
|
+
/system/dashboard/settings|settings|both
|
|
1031
|
+
/system/dashboard/show|command|both
|
|
987
1032
|
/system/default-configuration|settings|both
|
|
988
1033
|
/system/default-configuration/caps-mode-script|settings|both
|
|
989
1034
|
/system/default-configuration/custom-script|settings|both
|
|
@@ -1062,10 +1107,9 @@ const ROWS = `
|
|
|
1062
1107
|
/system/routerboard/mode-button|settings|both||!i386
|
|
1063
1108
|
/system/routerboard/reset-button|settings|both||!i386
|
|
1064
1109
|
/system/routerboard/settings|settings|both||!i386
|
|
1065
|
-
/system/routerboard/settings/keep-frequency|command|published||!i386, mipsel
|
|
1066
1110
|
/system/routerboard/upgrade|command|both||!i386
|
|
1067
|
-
/system/routerboard/usb|settings|both||!i386, !i386, !
|
|
1068
|
-
/system/routerboard/usb/power-reset|command|both||!i386, !i386, !
|
|
1111
|
+
/system/routerboard/usb|settings|both||!i386, !i386, !powerpc
|
|
1112
|
+
/system/routerboard/usb/power-reset|command|both||!i386, !i386, !powerpc
|
|
1069
1113
|
/system/routerboard/wps-button|settings|both||!i386
|
|
1070
1114
|
/system/rtrace|settings|both
|
|
1071
1115
|
/system/rtrace/start|command|both
|
|
@@ -1101,6 +1145,10 @@ const ROWS = `
|
|
|
1101
1145
|
/task/terminate|command|both
|
|
1102
1146
|
/terminal|menu|inspect
|
|
1103
1147
|
/tool|menu|inspect
|
|
1148
|
+
/tool/apptraffic|settings|both||!mmips, !powerpc, !smips
|
|
1149
|
+
/tool/apptraffic/stats|menu|both||!mmips, !powerpc, !smips
|
|
1150
|
+
/tool/apptraffic/stats/categories|menu|both||!mmips, !powerpc, !smips
|
|
1151
|
+
/tool/apptraffic/stats/clear|command|both||!mmips, !powerpc, !smips
|
|
1104
1152
|
/tool/bandwidth-server|settings|both
|
|
1105
1153
|
/tool/bandwidth-server/session|menu|both
|
|
1106
1154
|
/tool/bandwidth-test|command|both
|
package/src/explain/menus.ts
CHANGED
|
@@ -5,20 +5,22 @@
|
|
|
5
5
|
* the drift gate. The generator (`scripts/gen-explain-menus.ts`) carries the
|
|
6
6
|
* full rationale, the source pin, and why absence here is safe.
|
|
7
7
|
*
|
|
8
|
-
* Union of the `dir` and `path` nodes in
|
|
8
|
+
* Union of the `dir` and `path` nodes in 8 pinned restraml typed trees
|
|
9
9
|
* (`https://tikoci.github.io/restraml/`), all extra-packages builds:
|
|
10
10
|
*
|
|
11
11
|
* | Tree | Arch | RouterOS | Nodes | Containers |
|
|
12
12
|
* | ---- | ---- | -------- | ----- | ---------- |
|
|
13
13
|
* | `7.10.2/extra/inspect.json` | x86 | 7.10.2 | 29,086 | 458 |
|
|
14
14
|
* | `7.16/extra/inspect.json` | x86 | 7.16 | 34,876 | 522 |
|
|
15
|
-
* | `7.24.
|
|
16
|
-
* | `7.24.
|
|
17
|
-
* | `7.
|
|
18
|
-
* | `7.
|
|
15
|
+
* | `7.24.4/extra/deep-inspect.x86.json` | x86 | 7.24.4 | 41,345 | 554 |
|
|
16
|
+
* | `7.24.4/extra/deep-inspect.arm64.json` | arm64 | 7.24.4 | 42,691 | 575 |
|
|
17
|
+
* | `7.25beta5/extra/deep-inspect.x86.json` | x86 | 7.25beta5 | 41,794 | 557 |
|
|
18
|
+
* | `7.25beta5/extra/deep-inspect.arm64.json` | arm64 | 7.25beta5 | 43,150 | 578 |
|
|
19
|
+
* | `7.26beta1/extra/deep-inspect.x86.json` | x86 | 7.26beta1 | 43,294 | 576 |
|
|
20
|
+
* | `7.26beta1/extra/deep-inspect.arm64.json` | arm64 | 7.26beta1 | 44,650 | 597 |
|
|
19
21
|
*
|
|
20
22
|
* Zero node-type conflicts across those trees — no `dir`↔`cmd` flip across
|
|
21
|
-
*
|
|
23
|
+
* 5 versions or across architectures — which is what makes the union
|
|
22
24
|
* version-less rather than version-keyed. Generation aborts if that ever stops
|
|
23
25
|
* holding.
|
|
24
26
|
*
|
|
@@ -57,6 +59,18 @@ export const MENU_PATHS: ReadonlySet<string> = new Set([
|
|
|
57
59
|
"/certificate/scep-server/ra",
|
|
58
60
|
"/certificate/scep-server/requests",
|
|
59
61
|
"/certificate/settings",
|
|
62
|
+
"/cmr",
|
|
63
|
+
"/cmr/alert",
|
|
64
|
+
"/cmr/client",
|
|
65
|
+
"/cmr/device",
|
|
66
|
+
"/cmr/layout",
|
|
67
|
+
"/cmr/layout/link",
|
|
68
|
+
"/cmr/layout/node",
|
|
69
|
+
"/cmr/upgrade",
|
|
70
|
+
"/cmr/upgrade/job",
|
|
71
|
+
"/cmr/vlan",
|
|
72
|
+
"/cmr/wifi",
|
|
73
|
+
"/cmr/wifi/radio",
|
|
60
74
|
"/console",
|
|
61
75
|
"/console/settings",
|
|
62
76
|
"/container",
|
|
@@ -183,6 +197,8 @@ export const MENU_PATHS: ReadonlySet<string> = new Set([
|
|
|
183
197
|
"/interface/wifi/capsman/remote-cap",
|
|
184
198
|
"/interface/wifi/channel",
|
|
185
199
|
"/interface/wifi/configuration",
|
|
200
|
+
"/interface/wifi/dashboard",
|
|
201
|
+
"/interface/wifi/dashboard/settings",
|
|
186
202
|
"/interface/wifi/datapath",
|
|
187
203
|
"/interface/wifi/interworking",
|
|
188
204
|
"/interface/wifi/network",
|
|
@@ -526,6 +542,8 @@ export const MENU_PATHS: ReadonlySet<string> = new Set([
|
|
|
526
542
|
"/system/clock/manual",
|
|
527
543
|
"/system/console",
|
|
528
544
|
"/system/console/screen",
|
|
545
|
+
"/system/dashboard",
|
|
546
|
+
"/system/dashboard/settings",
|
|
529
547
|
"/system/default-configuration",
|
|
530
548
|
"/system/default-configuration/caps-mode-script",
|
|
531
549
|
"/system/default-configuration/custom-script",
|
|
@@ -583,6 +601,9 @@ export const MENU_PATHS: ReadonlySet<string> = new Set([
|
|
|
583
601
|
"/task",
|
|
584
602
|
"/terminal",
|
|
585
603
|
"/tool",
|
|
604
|
+
"/tool/apptraffic",
|
|
605
|
+
"/tool/apptraffic/stats",
|
|
606
|
+
"/tool/apptraffic/stats/categories",
|
|
586
607
|
"/tool/bandwidth-server",
|
|
587
608
|
"/tool/bandwidth-server/session",
|
|
588
609
|
"/tool/calea",
|
package/src/explain.ts
CHANGED
|
@@ -108,7 +108,11 @@ import {
|
|
|
108
108
|
isWriteShaped,
|
|
109
109
|
} from "./core/execute-command.ts";
|
|
110
110
|
import { toYaml } from "./core/yaml.ts";
|
|
111
|
-
import {
|
|
111
|
+
import {
|
|
112
|
+
CentrsError,
|
|
113
|
+
formatCentrsErrorText,
|
|
114
|
+
serializeCentrsError,
|
|
115
|
+
} from "./errors.ts";
|
|
112
116
|
import { argSpans } from "./explain/arg-tokens.ts";
|
|
113
117
|
import {
|
|
114
118
|
type ArgumentKind,
|
|
@@ -2495,11 +2499,7 @@ function renderExplainText(
|
|
|
2495
2499
|
): string {
|
|
2496
2500
|
const lines: string[] = [];
|
|
2497
2501
|
if (!envelope.ok) {
|
|
2498
|
-
lines.push(
|
|
2499
|
-
if (envelope.error.remediation)
|
|
2500
|
-
lines.push(`Fix: ${envelope.error.remediation}`);
|
|
2501
|
-
if (envelope.error.detailsUrl)
|
|
2502
|
-
lines.push(`Details: ${envelope.error.detailsUrl}`);
|
|
2502
|
+
lines.push(formatCentrsErrorText(envelope.error));
|
|
2503
2503
|
lines.push(...tipLines(envelope.tips));
|
|
2504
2504
|
return lines.join("\n");
|
|
2505
2505
|
}
|