@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/cli/execute.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { fanoutExitCode } from "../core/fanout.ts";
|
|
1
2
|
import { asCentrsError, formatCentrsErrorText } from "../errors.ts";
|
|
2
3
|
import {
|
|
3
4
|
buildExecuteErrorEnvelope,
|
|
@@ -7,11 +8,18 @@ import {
|
|
|
7
8
|
executeOutputFormats,
|
|
8
9
|
renderExecuteEnvelope,
|
|
9
10
|
} from "../execute.ts";
|
|
11
|
+
import {
|
|
12
|
+
buildExecuteFanoutErrorEnvelope,
|
|
13
|
+
executeFanout,
|
|
14
|
+
renderExecuteFanoutEnvelope,
|
|
15
|
+
} from "../execute-fanout.ts";
|
|
10
16
|
import { describeCentrs } from "../index.ts";
|
|
17
|
+
import { assertNoQuickchrOverrideConflict } from "../resolver/index.ts";
|
|
11
18
|
import {
|
|
12
19
|
type CliCommandMetadata,
|
|
13
20
|
expectValue,
|
|
14
21
|
renderCommandHelp,
|
|
22
|
+
unknownFlagError,
|
|
15
23
|
} from "./common.ts";
|
|
16
24
|
import {
|
|
17
25
|
buildTargetSelectionTips,
|
|
@@ -21,10 +29,21 @@ import {
|
|
|
21
29
|
missingTargetError,
|
|
22
30
|
withTips,
|
|
23
31
|
} from "./missing-target.ts";
|
|
32
|
+
import {
|
|
33
|
+
assertQuickchrExclusive,
|
|
34
|
+
buildTargetSelection,
|
|
35
|
+
consumeSelectionFlag,
|
|
36
|
+
emptySelectionFlags,
|
|
37
|
+
hasTargetSelector,
|
|
38
|
+
isFanoutMode,
|
|
39
|
+
type SelectionFlags,
|
|
40
|
+
selectionCommandOptions,
|
|
41
|
+
} from "./selection.ts";
|
|
24
42
|
|
|
25
43
|
export const executeCommand: CliCommandMetadata = {
|
|
26
44
|
name: "execute",
|
|
27
|
-
usage:
|
|
45
|
+
usage:
|
|
46
|
+
"centrs execute <target> <command> [flags] | centrs execute <target...> -- <command> [flags] | centrs execute --group <name> <command> [flags]",
|
|
28
47
|
summary:
|
|
29
48
|
"Run a RouterOS read or write command via native API, REST, or mac-telnet.",
|
|
30
49
|
options: [
|
|
@@ -34,6 +53,7 @@ export const executeCommand: CliCommandMetadata = {
|
|
|
34
53
|
description:
|
|
35
54
|
"Pin the protocol selector; no silent downgrade when set. A bare MAC target defaults to mac-telnet.",
|
|
36
55
|
},
|
|
56
|
+
...selectionCommandOptions,
|
|
37
57
|
{
|
|
38
58
|
flag: "--host",
|
|
39
59
|
valueName: "<host|url>",
|
|
@@ -88,9 +108,9 @@ export const executeCommand: CliCommandMetadata = {
|
|
|
88
108
|
description: "Per-request timeout (for REST, max 60s).",
|
|
89
109
|
},
|
|
90
110
|
{
|
|
91
|
-
flag: "--validate
|
|
111
|
+
flag: "--validate / --no-validate",
|
|
92
112
|
description:
|
|
93
|
-
"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).",
|
|
94
114
|
},
|
|
95
115
|
{
|
|
96
116
|
flag: "--yes",
|
|
@@ -127,11 +147,15 @@ export const executeCommand: CliCommandMetadata = {
|
|
|
127
147
|
interface ExecuteCliArgs extends ExecuteRequest {
|
|
128
148
|
help?: boolean;
|
|
129
149
|
format?: ExecuteOutputFormat;
|
|
150
|
+
selectionFlags?: SelectionFlags;
|
|
151
|
+
targetPositionals?: readonly string[];
|
|
130
152
|
}
|
|
131
153
|
|
|
132
154
|
export function parseExecuteCliArgs(args: readonly string[]): ExecuteCliArgs {
|
|
133
155
|
const parsed: ExecuteCliArgs = { command: "" };
|
|
156
|
+
const selectionFlags = emptySelectionFlags();
|
|
134
157
|
const positional: string[] = [];
|
|
158
|
+
const afterDashDash: string[] = [];
|
|
135
159
|
let endOfOptions = false;
|
|
136
160
|
|
|
137
161
|
for (let index = 0; index < args.length; index += 1) {
|
|
@@ -144,7 +168,7 @@ export function parseExecuteCliArgs(args: readonly string[]): ExecuteCliArgs {
|
|
|
144
168
|
// tokens like `--` / `-foo` (or a value that looks like a flag) without the
|
|
145
169
|
// parser claiming them. `--` itself is consumed, not added to the command.
|
|
146
170
|
if (endOfOptions) {
|
|
147
|
-
|
|
171
|
+
afterDashDash.push(arg);
|
|
148
172
|
continue;
|
|
149
173
|
}
|
|
150
174
|
if (arg === "--") {
|
|
@@ -226,17 +250,42 @@ export function parseExecuteCliArgs(args: readonly string[]): ExecuteCliArgs {
|
|
|
226
250
|
parsed.format = value as ExecuteOutputFormat;
|
|
227
251
|
break;
|
|
228
252
|
}
|
|
229
|
-
default:
|
|
253
|
+
default: {
|
|
254
|
+
const consumed = consumeSelectionFlag(args, index, selectionFlags);
|
|
255
|
+
if (consumed !== null) {
|
|
256
|
+
index = consumed;
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
230
259
|
if (arg.startsWith("-")) {
|
|
231
|
-
throw
|
|
260
|
+
throw unknownFlagError("execute", arg, executeCommand.options);
|
|
232
261
|
}
|
|
233
262
|
positional.push(arg);
|
|
234
263
|
break;
|
|
264
|
+
}
|
|
235
265
|
}
|
|
236
266
|
}
|
|
237
267
|
|
|
238
|
-
parsed.
|
|
239
|
-
|
|
268
|
+
parsed.selectionFlags = selectionFlags;
|
|
269
|
+
// Positional boundary for execute (see `docs/CONSTITUTION.md` → Target selection):
|
|
270
|
+
// - `--` present: targets are the positionals BEFORE it, command is AFTER it.
|
|
271
|
+
// - selector flag (or `--quickchr`) present, no `--`: every positional is the
|
|
272
|
+
// command (targets come from the flag).
|
|
273
|
+
// - otherwise (no `--`, no selector): legacy single-target split — first
|
|
274
|
+
// positional is the target, the rest is the command. Multiple positional
|
|
275
|
+
// targets therefore REQUIRE `--`.
|
|
276
|
+
const hasSelector = hasTargetSelector(selectionFlags);
|
|
277
|
+
if (endOfOptions) {
|
|
278
|
+
parsed.targetPositionals = positional;
|
|
279
|
+
parsed.command = afterDashDash.join(" ");
|
|
280
|
+
} else if (hasSelector) {
|
|
281
|
+
parsed.targetPositionals = [];
|
|
282
|
+
parsed.command = positional.join(" ");
|
|
283
|
+
} else {
|
|
284
|
+
parsed.targetPositionals =
|
|
285
|
+
positional[0] !== undefined ? [positional[0]] : [];
|
|
286
|
+
parsed.command = positional.slice(1).join(" ");
|
|
287
|
+
}
|
|
288
|
+
parsed.targetInput = parsed.targetPositionals[0];
|
|
240
289
|
parsed.stdinIsTty = process.stdin.isTTY;
|
|
241
290
|
return parsed;
|
|
242
291
|
}
|
|
@@ -249,11 +298,41 @@ export async function runExecuteCli(args: readonly string[]): Promise<number> {
|
|
|
249
298
|
console.log(renderCommandHelp(describeCentrs(), executeCommand));
|
|
250
299
|
return 0;
|
|
251
300
|
}
|
|
301
|
+
|
|
302
|
+
// Fan-out mode (a selector flag, or >1 positional target before `--`) has its
|
|
303
|
+
// own envelope shape, single up-front write-confirm, and granular exit code.
|
|
304
|
+
const selectionFlags = parsed.selectionFlags ?? emptySelectionFlags();
|
|
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
|
+
}
|
|
317
|
+
if (isFanoutMode(selectionFlags, targetPositionals.length)) {
|
|
318
|
+
return await runExecuteFanoutCli(
|
|
319
|
+
parsed,
|
|
320
|
+
selectionFlags,
|
|
321
|
+
targetPositionals,
|
|
322
|
+
args,
|
|
323
|
+
);
|
|
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
|
+
}
|
|
330
|
+
|
|
252
331
|
// No positional at all: lead with the missing-target guidance (CDB picker /
|
|
253
332
|
// discover) rather than the missing-command error — there is nothing to run
|
|
254
333
|
// without a device. A lone positional is treated as the target, so the
|
|
255
334
|
// "requires a command" path still fires for `execute <target>`.
|
|
256
|
-
if (!parsed.targetInput) {
|
|
335
|
+
if (!parsed.targetInput && parsed.quickchr === undefined) {
|
|
257
336
|
throw missingTargetError({
|
|
258
337
|
command: "execute",
|
|
259
338
|
summary: "`centrs execute` requires a <target> and a RouterOS command.",
|
|
@@ -296,12 +375,14 @@ export async function runExecuteCli(args: readonly string[]): Promise<number> {
|
|
|
296
375
|
tips,
|
|
297
376
|
);
|
|
298
377
|
console.error(
|
|
378
|
+
// codeql[js/clear-text-logging] No secret reaches this sink. The tainted 'password' flows in only as provenance (SettingSource={kind,key}), never the value — see CommonSettingsMeta.password in src/core/envelope.ts.
|
|
299
379
|
renderExecuteEnvelope(envelope, format, {
|
|
300
380
|
verbose: parsed?.verbose ?? false,
|
|
301
381
|
}),
|
|
302
382
|
);
|
|
303
383
|
} else {
|
|
304
384
|
console.error(
|
|
385
|
+
// codeql[js/clear-text-logging] Same false-positive pattern: password parsed from args never reaches error.message in the text-format catch path.
|
|
305
386
|
formatCentrsErrorText(
|
|
306
387
|
asCentrsError(error, {
|
|
307
388
|
code: "input/invalid-command",
|
|
@@ -317,6 +398,45 @@ export async function runExecuteCli(args: readonly string[]): Promise<number> {
|
|
|
317
398
|
}
|
|
318
399
|
}
|
|
319
400
|
|
|
401
|
+
async function runExecuteFanoutCli(
|
|
402
|
+
parsed: ExecuteCliArgs,
|
|
403
|
+
selectionFlags: SelectionFlags,
|
|
404
|
+
targetPositionals: readonly string[],
|
|
405
|
+
args: readonly string[],
|
|
406
|
+
): Promise<number> {
|
|
407
|
+
const selection = buildTargetSelection(selectionFlags, targetPositionals);
|
|
408
|
+
const format = inferExecuteFormat(args, parsed);
|
|
409
|
+
try {
|
|
410
|
+
const envelope = await executeFanout(
|
|
411
|
+
parsed,
|
|
412
|
+
selection,
|
|
413
|
+
Bun.env,
|
|
414
|
+
{},
|
|
415
|
+
{
|
|
416
|
+
concurrency: selectionFlags.concurrency,
|
|
417
|
+
allowAdhoc: true,
|
|
418
|
+
},
|
|
419
|
+
);
|
|
420
|
+
const resolvedFormat = envelope.meta.operation?.request.format ?? format;
|
|
421
|
+
console.log(
|
|
422
|
+
renderExecuteFanoutEnvelope(envelope, resolvedFormat, {
|
|
423
|
+
verbose: parsed.verbose ?? false,
|
|
424
|
+
}),
|
|
425
|
+
);
|
|
426
|
+
// Granular exit contract: 0 all-ok / 2 partial / 1 all-failed.
|
|
427
|
+
return fanoutExitCode(envelope);
|
|
428
|
+
} catch (error) {
|
|
429
|
+
const envelope = buildExecuteFanoutErrorEnvelope(parsed, error);
|
|
430
|
+
console.error(
|
|
431
|
+
// codeql[js/clear-text-logging] Twin of #80/#81/#83/#84. No secret logged: buildExecuteFanoutErrorEnvelope meta is {target:{},via,settings:{}}; CodeQL taints the whole request object but the builder projects the password away.
|
|
432
|
+
renderExecuteFanoutEnvelope(envelope, format, {
|
|
433
|
+
verbose: parsed.verbose ?? false,
|
|
434
|
+
}),
|
|
435
|
+
);
|
|
436
|
+
return 1;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
320
440
|
function inferExecuteFormat(
|
|
321
441
|
args: readonly string[],
|
|
322
442
|
parsed?: ExecuteCliArgs,
|