@tikoci/centrs 0.1.2 → 0.1.4
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 +98 -0
- package/GLOSSARY.txt +284 -0
- package/LICENSE +21 -0
- package/README.md +55 -11
- package/SECURITY.md +3 -3
- package/commands/AGENTS.md +7 -1
- package/commands/api/README.md +46 -27
- package/commands/api/examples.md +124 -6
- package/commands/btest/README.md +21 -32
- package/commands/check/README.md +376 -53
- package/commands/check/examples.md +390 -0
- package/commands/devices/README.md +177 -37
- package/commands/devices/examples.md +148 -0
- package/commands/discover/README.md +5 -11
- package/commands/execute/README.md +73 -26
- package/commands/execute/examples.md +131 -9
- package/commands/explain/README.md +1716 -0
- package/commands/explain/examples.md +606 -0
- package/commands/retrieve/README.md +55 -71
- package/commands/retrieve/examples.md +56 -9
- package/commands/settings/README.md +286 -22
- package/commands/settings/examples.md +408 -0
- package/commands/terminal/README.md +6 -0
- package/commands/transfer/README.md +57 -19
- package/commands/transfer/examples.md +95 -0
- package/docs/CLI.md +353 -0
- package/docs/CONSTITUTION.md +102 -26
- package/docs/MATRIX.md +106 -173
- package/docs/errors/README.md +5 -1
- package/docs/errors/cdb/empty-selection.md +19 -0
- package/docs/errors/input/incomplete-gps.md +9 -0
- package/docs/errors/input/invalid-altitude.md +9 -0
- package/docs/errors/input/invalid-bbox.md +9 -0
- package/docs/errors/input/invalid-coordinate.md +9 -0
- package/docs/errors/input/invalid-radius.md +9 -0
- package/docs/errors/internal/settings-failed.md +9 -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/settings/consequential-value.md +26 -0
- package/docs/errors/settings/invalid-boolean.md +21 -3
- package/docs/errors/settings/invalid-format.md +18 -3
- package/docs/errors/settings/invalid-integer.md +18 -3
- package/docs/errors/settings/invalid-timeout.md +20 -3
- package/docs/errors/settings/invalid-via.md +22 -3
- package/docs/errors/settings/reserved-key.md +29 -0
- package/docs/errors/settings/skip-env-file-active.md +25 -0
- package/docs/errors/settings/unknown-key.md +25 -0
- package/docs/errors/transport/provider-trust.md +29 -0
- package/docs/errors/usage/fanout-not-supported.md +16 -0
- package/docs/errors/usage/stdin-ignored.md +28 -0
- package/docs/errors/validation/unknown-attribute.md +37 -4
- package/package.json +49 -14
- package/src/api-fanout.ts +469 -0
- package/src/api.ts +171 -56
- package/src/cli/api.ts +156 -13
- package/src/cli/btest.ts +38 -4
- package/src/cli/common.ts +11 -0
- package/src/cli/devices.ts +235 -6
- package/src/cli/discover.ts +3 -1
- package/src/cli/execute.ts +129 -9
- package/src/cli/explain.ts +503 -0
- package/src/cli/mcp.ts +8 -1
- package/src/cli/retrieve.ts +167 -90
- package/src/cli/selection.ts +260 -0
- package/src/cli/settings.ts +286 -0
- package/src/cli/terminal.ts +22 -7
- package/src/cli/transfer.ts +192 -51
- package/src/cli.ts +22 -2
- package/src/core/envelope.ts +5 -2
- package/src/core/error-catalog.ts +95 -0
- package/src/core/fanout.ts +143 -2
- package/src/core/preflight.ts +306 -0
- package/src/core/routeros-string.ts +24 -0
- package/src/devices.ts +212 -20
- package/src/discover.ts +7 -2
- package/src/errors.ts +1 -0
- package/src/execute-fanout.ts +453 -0
- package/src/execute.ts +216 -68
- package/src/explain/arg-tokens.ts +114 -0
- package/src/explain/args.ts +1403 -0
- package/src/explain/blocks.ts +132 -0
- package/src/explain/brace-slots.ts +143 -0
- package/src/explain/brace-tokens.ts +38 -0
- package/src/explain/catalog.ts +1345 -0
- package/src/explain/coordinates.ts +329 -0
- package/src/explain/defects.ts +217 -0
- package/src/explain/is-known-menu.ts +17 -0
- package/src/explain/menus.ts +664 -0
- package/src/explain/operator-tokens.ts +310 -0
- package/src/explain/operators.ts +458 -0
- package/src/explain/path-tokens.ts +150 -0
- package/src/explain/pathresolve.ts +1324 -0
- package/src/explain/quoted-string.ts +241 -0
- package/src/explain/scope-brace.ts +308 -0
- package/src/explain/segment.ts +757 -0
- package/src/explain/string-tokens.ts +67 -0
- package/src/explain/symbol-values.ts +520 -0
- package/src/explain/symbols.ts +1597 -0
- package/src/explain/token-ranges.ts +72 -0
- package/src/explain/transport.ts +497 -0
- package/src/explain/value-tokens.ts +80 -0
- package/src/explain/values.ts +357 -0
- package/src/explain/verbs.ts +64 -0
- package/src/explain/verbsplit.ts +813 -0
- package/src/explain/write.ts +815 -0
- package/src/explain.ts +2152 -0
- package/src/index.ts +307 -1
- package/src/mcp/config.ts +12 -2
- package/src/mcp/tools.ts +137 -2
- package/src/protocols/adapter.ts +38 -18
- package/src/protocols/index.ts +11 -5
- package/src/protocols/mac-telnet-console.ts +61 -17
- package/src/resolver/cdb.ts +10 -2
- package/src/resolver/comment-kv.ts +58 -4
- package/src/resolver/config-file.ts +136 -0
- package/src/resolver/facts.ts +78 -0
- package/src/resolver/geo.ts +453 -0
- package/src/resolver/index.ts +65 -0
- package/src/resolver/mac.ts +12 -3
- package/src/resolver/quickchr-provider.ts +597 -0
- package/src/resolver/selection.ts +391 -0
- package/src/resolver/service-endpoint.ts +155 -0
- package/src/resolver/settings.ts +54 -5
- package/src/resolver/target.ts +21 -3
- package/src/retrieve-fanout.ts +239 -133
- package/src/retrieve.ts +120 -25
- package/src/settings.ts +1078 -0
- package/src/terminal.ts +11 -1
- package/src/transfer-fanout.ts +544 -0
- package/src/transfer.ts +133 -40
package/src/transfer.ts
CHANGED
|
@@ -38,14 +38,18 @@ import {
|
|
|
38
38
|
import type { RouterOsProtocol } from "./protocols/index.ts";
|
|
39
39
|
import { SftpClient } from "./protocols/sftp.ts";
|
|
40
40
|
import {
|
|
41
|
+
assertNoQuickchrOverrideConflict,
|
|
41
42
|
type CdbResolution,
|
|
43
|
+
loadEnvFileDefaults,
|
|
42
44
|
parseDuration,
|
|
45
|
+
quickchrConnection,
|
|
43
46
|
type ResolvedAuth,
|
|
44
47
|
type ResolvedSetting,
|
|
45
48
|
type ResolvedTarget,
|
|
46
49
|
resolveAuth,
|
|
47
50
|
resolveBooleanSetting,
|
|
48
51
|
resolveCdb,
|
|
52
|
+
resolveQuickchrTarget,
|
|
49
53
|
resolveStringSetting,
|
|
50
54
|
resolveTarget,
|
|
51
55
|
toCoreSource,
|
|
@@ -66,6 +70,20 @@ export const transferVerbs = [
|
|
|
66
70
|
] as const;
|
|
67
71
|
export type TransferVerb = (typeof transferVerbs)[number];
|
|
68
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Verbs that mutate the device (write path) — gated by `--yes` once up front in a
|
|
75
|
+
* fan-out. `download`/`list` only read the device (a download writes the *local*
|
|
76
|
+
* filesystem, not the router).
|
|
77
|
+
*/
|
|
78
|
+
export function isTransferWriteVerb(verb: TransferVerb): boolean {
|
|
79
|
+
return (
|
|
80
|
+
verb === "upload" ||
|
|
81
|
+
verb === "remove" ||
|
|
82
|
+
verb === "mkdir" ||
|
|
83
|
+
verb === "copy"
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
69
87
|
/**
|
|
70
88
|
* `--via` method names. These are transfer *methods*, finer than the grid
|
|
71
89
|
* transport: `rest`/`native` ride rest-api/native-api; `sftp`/`scp` ride ssh;
|
|
@@ -89,6 +107,12 @@ export const FILE_READ_CHUNK_BYTES = 32_768;
|
|
|
89
107
|
export interface TransferRequest {
|
|
90
108
|
verb: TransferVerb;
|
|
91
109
|
targetInput?: string;
|
|
110
|
+
/**
|
|
111
|
+
* quickchr machine name (`--quickchr <name>`): resolve host/port/auth from
|
|
112
|
+
* the live VM descriptor instead of CDB/env (`docs/CONSTITUTION.md` →
|
|
113
|
+
* Resolution providers). Conflicts with host/port/username/password/sshKey.
|
|
114
|
+
*/
|
|
115
|
+
quickchr?: string;
|
|
92
116
|
/** Local path (or `-` for stdin/stdout). Present for upload/download. */
|
|
93
117
|
local?: string;
|
|
94
118
|
/** RouterOS file path. Present for upload/download/remove/mkdir and copy source. */
|
|
@@ -120,6 +144,14 @@ export interface TransferRequest {
|
|
|
120
144
|
name?: string;
|
|
121
145
|
cdbFile?: string;
|
|
122
146
|
cdbPassword?: string;
|
|
147
|
+
/** `download` fan-out: directory to write one file per target into. */
|
|
148
|
+
outDir?: string;
|
|
149
|
+
/** Fan-out write gate: confirm a mutating fan-out (upload/remove/mkdir/copy). */
|
|
150
|
+
yes?: boolean;
|
|
151
|
+
/** Fan-out TTY detection seam (defaults to `process.stdin.isTTY`). */
|
|
152
|
+
stdinIsTty?: boolean;
|
|
153
|
+
/** Fan-out confirmation prompt seam (defaults to the shared TTY prompt). */
|
|
154
|
+
confirm?: (prompt: string) => Promise<boolean>;
|
|
123
155
|
}
|
|
124
156
|
|
|
125
157
|
export type TransferOp = TransferVerb;
|
|
@@ -190,8 +222,9 @@ interface ResolvedTransferRequest {
|
|
|
190
222
|
export async function transfer(
|
|
191
223
|
request: TransferRequest,
|
|
192
224
|
env: Record<string, string | undefined> = Bun.env,
|
|
225
|
+
options: { cdbResolution?: CdbResolution } = {},
|
|
193
226
|
): Promise<TransferSuccessEnvelope> {
|
|
194
|
-
const resolved = await resolveTransferRequest(request, env);
|
|
227
|
+
const resolved = await resolveTransferRequest(request, env, options);
|
|
195
228
|
const backend = createFileBackend(resolved);
|
|
196
229
|
try {
|
|
197
230
|
return await runResolvedTransfer(resolved, backend);
|
|
@@ -778,8 +811,10 @@ function verifyReadSize(
|
|
|
778
811
|
export async function resolveTransferRequest(
|
|
779
812
|
request: TransferRequest,
|
|
780
813
|
env: Record<string, string | undefined>,
|
|
814
|
+
options: { cdbResolution?: CdbResolution } = {},
|
|
781
815
|
): Promise<ResolvedTransferRequest> {
|
|
782
816
|
validateTransferRequestShape(request);
|
|
817
|
+
const config = await loadEnvFileDefaults(env);
|
|
783
818
|
|
|
784
819
|
const direction: TransferDirection =
|
|
785
820
|
request.verb === "upload" ? "upload" : "download";
|
|
@@ -789,18 +824,36 @@ export async function resolveTransferRequest(
|
|
|
789
824
|
direction,
|
|
790
825
|
sizeHint,
|
|
791
826
|
env,
|
|
827
|
+
config,
|
|
792
828
|
);
|
|
793
829
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
)
|
|
830
|
+
// A quickchr target is a named-live-provider: host/port/auth come from the
|
|
831
|
+
// live descriptor, bypassing CDB and the `__default__` ladder. For
|
|
832
|
+
// `--via sftp`/`scp`, `quickchrConnection` additionally gates on the SSH
|
|
833
|
+
// endpoint's batch-capable auth modes (typed error, never a password prompt).
|
|
834
|
+
let quickchrResolution: Awaited<
|
|
835
|
+
ReturnType<typeof resolveQuickchrTarget>
|
|
836
|
+
> | null = null;
|
|
837
|
+
if (request.quickchr !== undefined) {
|
|
838
|
+
assertNoQuickchrOverrideConflict(request, request.quickchr);
|
|
839
|
+
quickchrResolution = await resolveQuickchrTarget(request.quickchr);
|
|
840
|
+
}
|
|
841
|
+
// Fan-out passes the pre-resolved CDB record for a member (the CDB is loaded
|
|
842
|
+
// once in `expandSelection`); single-target resolves it here.
|
|
843
|
+
const cdbResolution = quickchrResolution
|
|
844
|
+
? undefined
|
|
845
|
+
: (options.cdbResolution ??
|
|
846
|
+
(await resolveCdb(
|
|
847
|
+
{
|
|
848
|
+
targetInput: request.targetInput,
|
|
849
|
+
cdbFile: request.cdbFile,
|
|
850
|
+
cdbPassword: request.cdbPassword,
|
|
851
|
+
},
|
|
852
|
+
env,
|
|
853
|
+
config,
|
|
854
|
+
)));
|
|
802
855
|
|
|
803
|
-
const format = resolveTransferFormat(request, env);
|
|
856
|
+
const format = resolveTransferFormat(request, env, config);
|
|
804
857
|
const validate = resolveBooleanSetting(
|
|
805
858
|
request.validate,
|
|
806
859
|
env,
|
|
@@ -808,43 +861,68 @@ export async function resolveTransferRequest(
|
|
|
808
861
|
true,
|
|
809
862
|
"validate",
|
|
810
863
|
cdbResolution?.overrides.validate,
|
|
811
|
-
|
|
812
|
-
const insecure = resolveBooleanSetting(
|
|
813
|
-
request.insecure,
|
|
814
|
-
env,
|
|
815
|
-
"CENTRS_INSECURE",
|
|
816
|
-
false,
|
|
817
|
-
"insecure",
|
|
818
|
-
cdbResolution?.overrides.insecure,
|
|
864
|
+
config,
|
|
819
865
|
);
|
|
820
866
|
const timeoutMs = resolveTransferTimeout(
|
|
821
867
|
request.timeout,
|
|
822
868
|
env,
|
|
823
869
|
protocol,
|
|
824
870
|
cdbResolution,
|
|
871
|
+
config,
|
|
825
872
|
);
|
|
826
|
-
const
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
873
|
+
const connection = quickchrResolution
|
|
874
|
+
? quickchrConnection(quickchrResolution, protocol)
|
|
875
|
+
: null;
|
|
876
|
+
// A quickchr TLS/SSH endpoint is trusted by provenance (self-signed VM cert /
|
|
877
|
+
// ephemeral host key) — `insecure` is forced with provider provenance and the
|
|
878
|
+
// provider-trust warning replaces the generic `--insecure` anomaly warning.
|
|
879
|
+
const insecure =
|
|
880
|
+
connection?.insecure === true
|
|
881
|
+
? {
|
|
882
|
+
value: true,
|
|
883
|
+
source: {
|
|
884
|
+
kind: "provider" as const,
|
|
885
|
+
key: `quickchr:${request.quickchr}`,
|
|
886
|
+
},
|
|
887
|
+
}
|
|
888
|
+
: resolveBooleanSetting(
|
|
889
|
+
request.insecure,
|
|
890
|
+
env,
|
|
891
|
+
"CENTRS_INSECURE",
|
|
892
|
+
false,
|
|
893
|
+
"insecure",
|
|
894
|
+
cdbResolution?.overrides.insecure,
|
|
895
|
+
config,
|
|
896
|
+
);
|
|
897
|
+
const target = connection
|
|
898
|
+
? connection.target
|
|
899
|
+
: resolveTarget(
|
|
900
|
+
{
|
|
901
|
+
targetInput: request.targetInput,
|
|
902
|
+
host: request.host,
|
|
903
|
+
port: request.port,
|
|
904
|
+
},
|
|
905
|
+
env,
|
|
906
|
+
protocol,
|
|
907
|
+
cdbResolution,
|
|
908
|
+
config,
|
|
909
|
+
);
|
|
910
|
+
const auth = connection
|
|
911
|
+
? connection.auth
|
|
912
|
+
: resolveAuth(
|
|
913
|
+
{
|
|
914
|
+
username: request.username,
|
|
915
|
+
password: request.password,
|
|
916
|
+
sshKey: request.sshKey,
|
|
917
|
+
},
|
|
918
|
+
env,
|
|
919
|
+
cdbResolution,
|
|
920
|
+
config,
|
|
921
|
+
);
|
|
845
922
|
|
|
846
923
|
const warnings: Warning[] = [
|
|
847
924
|
...((cdbResolution?.warnings ?? []) as readonly Warning[]),
|
|
925
|
+
...(connection?.warnings ?? []),
|
|
848
926
|
];
|
|
849
927
|
// Report the auto hop: a large upload cannot ride the REST family's 60 KB
|
|
850
928
|
// `/file/set` cap, so auto-selection moved it to sftp.
|
|
@@ -855,8 +933,10 @@ export async function resolveTransferRequest(
|
|
|
855
933
|
"Large upload (>60 KB) auto-selected sftp; the REST family cannot write past the 60 KB `/file/set` cap.",
|
|
856
934
|
});
|
|
857
935
|
}
|
|
858
|
-
// A self-signed-accepting run is an anomaly worth surfacing on every
|
|
859
|
-
|
|
936
|
+
// A self-signed-accepting run is an anomaly worth surfacing on every
|
|
937
|
+
// transport — unless the trust came from the provider, whose own
|
|
938
|
+
// provider-trust warning already rides above.
|
|
939
|
+
if (insecure.value && connection?.insecure !== true) {
|
|
860
940
|
warnings.push({
|
|
861
941
|
code: "transport/insecure-trust",
|
|
862
942
|
message:
|
|
@@ -900,6 +980,7 @@ export function selectTransferMethod(
|
|
|
900
980
|
direction: TransferDirection,
|
|
901
981
|
uploadBytes: number | undefined,
|
|
902
982
|
env: Record<string, string | undefined>,
|
|
983
|
+
config: Record<string, string | undefined> = {},
|
|
903
984
|
): {
|
|
904
985
|
method: TransferMethod;
|
|
905
986
|
protocol: RouterOsProtocol;
|
|
@@ -911,6 +992,9 @@ export function selectTransferMethod(
|
|
|
911
992
|
"CENTRS_TRANSFER_VIA",
|
|
912
993
|
"auto",
|
|
913
994
|
"via",
|
|
995
|
+
undefined,
|
|
996
|
+
undefined,
|
|
997
|
+
config,
|
|
914
998
|
) as ResolvedSetting<string>;
|
|
915
999
|
const requested = via.value;
|
|
916
1000
|
|
|
@@ -1062,6 +1146,7 @@ function parseVerifyMode(
|
|
|
1062
1146
|
function resolveTransferFormat(
|
|
1063
1147
|
request: TransferRequest,
|
|
1064
1148
|
env: Record<string, string | undefined>,
|
|
1149
|
+
config: Record<string, string | undefined> = {},
|
|
1065
1150
|
): ResolvedSetting<TransferOutputFormat> {
|
|
1066
1151
|
return resolveStringSetting(
|
|
1067
1152
|
request.format,
|
|
@@ -1079,6 +1164,8 @@ function resolveTransferFormat(
|
|
|
1079
1164
|
remediation: `Choose one of ${transferOutputFormats.join(", ")}.`,
|
|
1080
1165
|
});
|
|
1081
1166
|
},
|
|
1167
|
+
undefined,
|
|
1168
|
+
config,
|
|
1082
1169
|
) as ResolvedSetting<TransferOutputFormat>;
|
|
1083
1170
|
}
|
|
1084
1171
|
|
|
@@ -1087,6 +1174,7 @@ function resolveTransferTimeout(
|
|
|
1087
1174
|
env: Record<string, string | undefined>,
|
|
1088
1175
|
protocol: RouterOsProtocol,
|
|
1089
1176
|
cdb?: CdbResolution,
|
|
1177
|
+
config: Record<string, string | undefined> = {},
|
|
1090
1178
|
): ResolvedSetting<number> {
|
|
1091
1179
|
const resolved = resolveStringSetting(
|
|
1092
1180
|
timeout === undefined ? undefined : String(timeout),
|
|
@@ -1106,6 +1194,7 @@ function resolveTransferTimeout(
|
|
|
1106
1194
|
return parsed;
|
|
1107
1195
|
},
|
|
1108
1196
|
cdb?.overrides.timeoutMs,
|
|
1197
|
+
config,
|
|
1109
1198
|
) as ResolvedSetting<number>;
|
|
1110
1199
|
|
|
1111
1200
|
if (protocol === "rest-api" && resolved.value > 60_000) {
|
|
@@ -1156,7 +1245,11 @@ function writeLocalSink(
|
|
|
1156
1245
|
process.stdout.write(bytes);
|
|
1157
1246
|
return;
|
|
1158
1247
|
}
|
|
1159
|
-
|
|
1248
|
+
// Downloaded RouterOS artifacts (backups/exports) can carry credentials,
|
|
1249
|
+
// so a newly created destination file gets owner-only permissions
|
|
1250
|
+
// instead of the process umask default. Mode only applies on creation —
|
|
1251
|
+
// an existing destination keeps its current permissions.
|
|
1252
|
+
writeFileSync(local, bytes, { mode: 0o600 });
|
|
1160
1253
|
}
|
|
1161
1254
|
|
|
1162
1255
|
function localLabel(resolved: ResolvedTransferRequest): string {
|