@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
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `centrs settings` CLI surface: command metadata, subcommand parsing,
|
|
3
|
+
* dispatch, and error-to-envelope rendering. Core logic lives in
|
|
4
|
+
* `../settings.ts`; this file only does CLI-shaped plumbing, mirroring
|
|
5
|
+
* `./devices.ts`'s pattern for the other transport-less command.
|
|
6
|
+
*
|
|
7
|
+
* Bare `centrs settings` (no subcommand) behaves like `print` regardless of
|
|
8
|
+
* TTY — the interactive first-time-setup flow (`@clack/prompts`) is an
|
|
9
|
+
* explicitly deferred later slice (`commands/settings/README.md`), not built
|
|
10
|
+
* here.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { asCentrsError, formatCentrsErrorText } from "../errors.ts";
|
|
14
|
+
import { describeCentrs } from "../index.ts";
|
|
15
|
+
import { defaultSettingsPath } from "../resolver/index.ts";
|
|
16
|
+
import {
|
|
17
|
+
buildSettingsErrorEnvelope,
|
|
18
|
+
renderSettingsEnvelope,
|
|
19
|
+
type SettingsCommand,
|
|
20
|
+
type SettingsEnvelope,
|
|
21
|
+
type SettingsOutputFormat,
|
|
22
|
+
settingsGet,
|
|
23
|
+
settingsOutputFormats,
|
|
24
|
+
settingsPrint,
|
|
25
|
+
settingsReset,
|
|
26
|
+
settingsSet,
|
|
27
|
+
} from "../settings.ts";
|
|
28
|
+
import {
|
|
29
|
+
type CliCommandMetadata,
|
|
30
|
+
expectValue,
|
|
31
|
+
renderCommandHelp,
|
|
32
|
+
unknownFlagError,
|
|
33
|
+
} from "./common.ts";
|
|
34
|
+
|
|
35
|
+
export const settingsCommand: CliCommandMetadata = {
|
|
36
|
+
name: "settings",
|
|
37
|
+
usage: "centrs settings [print|get|set|reset] [args] [flags]",
|
|
38
|
+
summary:
|
|
39
|
+
"Manage centrs's own global preferences (`centrs.env`) and inspect the `__default__` CDB record.",
|
|
40
|
+
options: [
|
|
41
|
+
{
|
|
42
|
+
flag: "--all",
|
|
43
|
+
description:
|
|
44
|
+
"`print` only — also list unrecognized CENTRS_* lines found in the file.",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
flag: "--cdb-file",
|
|
48
|
+
valueName: "<path>",
|
|
49
|
+
description:
|
|
50
|
+
"`print` only — CDB to probe for the `__default__` record summary (falls back to normal CDB resolution; never centrs.env).",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
flag: "--cdb-password",
|
|
54
|
+
valueName: "<secret>",
|
|
55
|
+
description: "`print` only — decrypt password for the probed CDB.",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
flag: "--skip-env-file",
|
|
59
|
+
description:
|
|
60
|
+
"`print` only — note that other commands in this environment would not see centrs.env (settings itself always reads the real file).",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
flag: "--format",
|
|
64
|
+
valueName: `<${settingsOutputFormats.join("|")}>`,
|
|
65
|
+
description: "Output format for the CLI response.",
|
|
66
|
+
},
|
|
67
|
+
{ flag: "--json", description: "Shortcut for `--format json`." },
|
|
68
|
+
{ flag: "--help", description: "Show this help." },
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
type SettingsSubcommand = SettingsCommand;
|
|
73
|
+
|
|
74
|
+
interface SettingsCliArgs {
|
|
75
|
+
help?: boolean;
|
|
76
|
+
subcommand: SettingsSubcommand;
|
|
77
|
+
attr?: string;
|
|
78
|
+
value?: string;
|
|
79
|
+
all?: boolean;
|
|
80
|
+
cdbFile?: string;
|
|
81
|
+
cdbPassword?: string;
|
|
82
|
+
skipEnvFile?: boolean;
|
|
83
|
+
format?: SettingsOutputFormat;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function parseSettingsCliArgs(args: readonly string[]): SettingsCliArgs {
|
|
87
|
+
const positional: string[] = [];
|
|
88
|
+
let help = false;
|
|
89
|
+
let all = false;
|
|
90
|
+
let cdbFile: string | undefined;
|
|
91
|
+
let cdbPassword: string | undefined;
|
|
92
|
+
let skipEnvFile = false;
|
|
93
|
+
let format: SettingsOutputFormat | undefined;
|
|
94
|
+
|
|
95
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
96
|
+
const arg = args[index];
|
|
97
|
+
if (arg === undefined) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
switch (arg) {
|
|
101
|
+
case "--help":
|
|
102
|
+
case "-h":
|
|
103
|
+
help = true;
|
|
104
|
+
break;
|
|
105
|
+
case "--all":
|
|
106
|
+
all = true;
|
|
107
|
+
break;
|
|
108
|
+
case "--cdb-file":
|
|
109
|
+
cdbFile = expectValue(args, ++index, arg);
|
|
110
|
+
break;
|
|
111
|
+
case "--cdb-password":
|
|
112
|
+
cdbPassword = expectValue(args, ++index, arg);
|
|
113
|
+
break;
|
|
114
|
+
case "--skip-env-file":
|
|
115
|
+
skipEnvFile = true;
|
|
116
|
+
break;
|
|
117
|
+
case "--format": {
|
|
118
|
+
const value = expectValue(args, ++index, arg);
|
|
119
|
+
if (!settingsOutputFormats.includes(value as SettingsOutputFormat)) {
|
|
120
|
+
throw new Error(
|
|
121
|
+
`--format must be one of ${settingsOutputFormats.join(", ")}; got ${value}.`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
format = value as SettingsOutputFormat;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
case "--json":
|
|
128
|
+
format = "json";
|
|
129
|
+
break;
|
|
130
|
+
default:
|
|
131
|
+
// A negative-integer value (e.g. `settings set max-results -1`) looks
|
|
132
|
+
// like a flag but is a legitimate positional — `settings` has no flag
|
|
133
|
+
// shaped like a bare negative number, so this is unambiguous.
|
|
134
|
+
if (arg.startsWith("-") && !/^-\d+$/.test(arg)) {
|
|
135
|
+
throw unknownFlagError("settings", arg, settingsCommand.options);
|
|
136
|
+
}
|
|
137
|
+
positional.push(arg);
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const result: SettingsCliArgs = {
|
|
143
|
+
help,
|
|
144
|
+
subcommand: "print",
|
|
145
|
+
all,
|
|
146
|
+
cdbFile,
|
|
147
|
+
cdbPassword,
|
|
148
|
+
skipEnvFile,
|
|
149
|
+
format,
|
|
150
|
+
};
|
|
151
|
+
if (help) {
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const [rawSub, ...rest] = positional;
|
|
156
|
+
// Bare `centrs settings` (no subcommand at all) behaves like `print`.
|
|
157
|
+
if (rawSub === undefined) {
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const subcommands: readonly SettingsSubcommand[] = [
|
|
162
|
+
"print",
|
|
163
|
+
"get",
|
|
164
|
+
"set",
|
|
165
|
+
"reset",
|
|
166
|
+
];
|
|
167
|
+
if (!subcommands.includes(rawSub as SettingsSubcommand)) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
`Unknown settings subcommand: ${rawSub}. Use print, get, set, or reset (or omit the subcommand for print).`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
result.subcommand = rawSub as SettingsSubcommand;
|
|
173
|
+
|
|
174
|
+
if (result.subcommand === "print" || result.subcommand === "reset") {
|
|
175
|
+
if (rest.length > 1) {
|
|
176
|
+
throw new Error(
|
|
177
|
+
`\`centrs settings ${result.subcommand}\` accepts at most one <attr>.`,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
result.attr = rest[0];
|
|
181
|
+
} else if (result.subcommand === "get") {
|
|
182
|
+
if (rest.length !== 1) {
|
|
183
|
+
throw new Error("`centrs settings get` requires exactly one <attr>.");
|
|
184
|
+
}
|
|
185
|
+
result.attr = rest[0];
|
|
186
|
+
} else if (result.subcommand === "set") {
|
|
187
|
+
if (rest.length !== 2) {
|
|
188
|
+
throw new Error("`centrs settings set` requires exactly <attr> <value>.");
|
|
189
|
+
}
|
|
190
|
+
result.attr = rest[0];
|
|
191
|
+
result.value = rest[1];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export async function runSettingsCli(args: readonly string[]): Promise<number> {
|
|
198
|
+
let parsed: SettingsCliArgs | undefined;
|
|
199
|
+
let envSnapshot: Record<string, string | undefined> | undefined;
|
|
200
|
+
try {
|
|
201
|
+
parsed = parseSettingsCliArgs(args);
|
|
202
|
+
if (parsed.help) {
|
|
203
|
+
console.log(renderCommandHelp(describeCentrs(), settingsCommand));
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
envSnapshot = Bun.env;
|
|
208
|
+
let envelope: SettingsEnvelope<unknown>;
|
|
209
|
+
switch (parsed.subcommand) {
|
|
210
|
+
case "print":
|
|
211
|
+
envelope = await settingsPrint({
|
|
212
|
+
attr: parsed.attr,
|
|
213
|
+
all: parsed.all,
|
|
214
|
+
env: envSnapshot,
|
|
215
|
+
skipEnvFile: parsed.skipEnvFile,
|
|
216
|
+
cdbFile: parsed.cdbFile,
|
|
217
|
+
cdbPassword: parsed.cdbPassword,
|
|
218
|
+
});
|
|
219
|
+
break;
|
|
220
|
+
case "get":
|
|
221
|
+
if (!parsed.attr) {
|
|
222
|
+
throw new Error("Missing <attr> for settings get.");
|
|
223
|
+
}
|
|
224
|
+
envelope = await settingsGet({ attr: parsed.attr, env: envSnapshot });
|
|
225
|
+
break;
|
|
226
|
+
case "set":
|
|
227
|
+
if (!parsed.attr || parsed.value === undefined) {
|
|
228
|
+
throw new Error("Missing <attr> <value> for settings set.");
|
|
229
|
+
}
|
|
230
|
+
envelope = await settingsSet({
|
|
231
|
+
attr: parsed.attr,
|
|
232
|
+
value: parsed.value,
|
|
233
|
+
env: envSnapshot,
|
|
234
|
+
});
|
|
235
|
+
break;
|
|
236
|
+
case "reset":
|
|
237
|
+
envelope = await settingsReset({
|
|
238
|
+
attr: parsed.attr,
|
|
239
|
+
env: envSnapshot,
|
|
240
|
+
});
|
|
241
|
+
break;
|
|
242
|
+
default:
|
|
243
|
+
throw new Error("Unreachable settings subcommand.");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const format = parsed.format ?? "text";
|
|
247
|
+
// codeql[js/clear-text-logging] passwordSet is a has-password boolean, never the secret; the property-NAME heuristic (maybePassword) matches the field name, not its value type.
|
|
248
|
+
console.log(renderSettingsEnvelope(envelope, format));
|
|
249
|
+
return 0;
|
|
250
|
+
} catch (error) {
|
|
251
|
+
const format = parsed?.format ?? inferSettingsFormat(args);
|
|
252
|
+
const settingsFile = defaultSettingsPath(envSnapshot ?? Bun.env);
|
|
253
|
+
const centrsError = asCentrsError(error, {
|
|
254
|
+
code: "input/invalid-command",
|
|
255
|
+
summary: error instanceof Error ? error.message : String(error),
|
|
256
|
+
remediation:
|
|
257
|
+
"Use `centrs settings --help` to inspect the supported subcommands and flags.",
|
|
258
|
+
});
|
|
259
|
+
const envelope = buildSettingsErrorEnvelope(
|
|
260
|
+
parsed?.subcommand ?? "print",
|
|
261
|
+
settingsFile,
|
|
262
|
+
[],
|
|
263
|
+
centrsError,
|
|
264
|
+
);
|
|
265
|
+
if (format === "json" || format === "yaml") {
|
|
266
|
+
// codeql[js/clear-text-logging] sink renders only a literal error string (e.g. "Missing value for --password."), never the real value; CodeQL over-taints via shared expectValue/CentrsError code.
|
|
267
|
+
console.error(renderSettingsEnvelope(envelope, format));
|
|
268
|
+
} else {
|
|
269
|
+
// codeql[js/clear-text-logging] sink renders only a literal error string (e.g. "Missing value for --password."), never the real value; CodeQL over-taints via shared expectValue/CentrsError code.
|
|
270
|
+
console.error(formatCentrsErrorText(centrsError));
|
|
271
|
+
}
|
|
272
|
+
return 1;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function inferSettingsFormat(args: readonly string[]): SettingsOutputFormat {
|
|
277
|
+
if (args.includes("--json")) {
|
|
278
|
+
return "json";
|
|
279
|
+
}
|
|
280
|
+
const formatIndex = args.indexOf("--format");
|
|
281
|
+
const value = formatIndex >= 0 ? args[formatIndex + 1] : undefined;
|
|
282
|
+
if (value && settingsOutputFormats.includes(value as SettingsOutputFormat)) {
|
|
283
|
+
return value as SettingsOutputFormat;
|
|
284
|
+
}
|
|
285
|
+
return "text";
|
|
286
|
+
}
|
package/src/cli/terminal.ts
CHANGED
|
@@ -6,7 +6,11 @@
|
|
|
6
6
|
* `process.*`); the core relay in `../terminal.ts` stays io-agnostic.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
asCentrsError,
|
|
11
|
+
CentrsError,
|
|
12
|
+
formatCentrsErrorText,
|
|
13
|
+
} from "../errors.ts";
|
|
10
14
|
import {
|
|
11
15
|
buildTerminalErrorEnvelope,
|
|
12
16
|
describeCentrs,
|
|
@@ -19,6 +23,7 @@ import {
|
|
|
19
23
|
type CliCommandMetadata,
|
|
20
24
|
expectValue,
|
|
21
25
|
renderCommandHelp,
|
|
26
|
+
unknownFlagError,
|
|
22
27
|
} from "./common.ts";
|
|
23
28
|
import {
|
|
24
29
|
buildTargetSelectionTips,
|
|
@@ -28,6 +33,7 @@ import {
|
|
|
28
33
|
missingTargetError,
|
|
29
34
|
withTips,
|
|
30
35
|
} from "./missing-target.ts";
|
|
36
|
+
import { selectionFlagTokens } from "./selection.ts";
|
|
31
37
|
|
|
32
38
|
export const terminalCommand: CliCommandMetadata = {
|
|
33
39
|
name: "terminal",
|
|
@@ -181,10 +187,12 @@ export async function runTerminalCli(args: readonly string[]): Promise<number> {
|
|
|
181
187
|
if (format === "json" || format === "yaml") {
|
|
182
188
|
const envelope = withTips(buildTerminalErrorEnvelope(error), tips);
|
|
183
189
|
console.error(
|
|
190
|
+
// codeql[js/clear-text-logging] CLI error renderer false positive; password reaches neither the error nor the stderr renderers. Dismissed across all CLI commands.
|
|
184
191
|
format === "yaml" ? toYaml(envelope) : JSON.stringify(envelope),
|
|
185
192
|
);
|
|
186
193
|
} else {
|
|
187
194
|
console.error(
|
|
195
|
+
// codeql[js/clear-text-logging] Password parsed from args never reaches error.message in the text-format catch path; the parsed request (which holds the raw password) is never rendered.
|
|
188
196
|
formatCentrsErrorText(
|
|
189
197
|
asCentrsError(error, {
|
|
190
198
|
code: "input/invalid-command",
|
|
@@ -205,7 +213,7 @@ interface ParsedTerminal {
|
|
|
205
213
|
request: TerminalRequest;
|
|
206
214
|
}
|
|
207
215
|
|
|
208
|
-
function parseTerminalCliArgs(args: readonly string[]): ParsedTerminal {
|
|
216
|
+
export function parseTerminalCliArgs(args: readonly string[]): ParsedTerminal {
|
|
209
217
|
const flags: TerminalRequest = {};
|
|
210
218
|
const positional: string[] = [];
|
|
211
219
|
let help = false;
|
|
@@ -268,14 +276,21 @@ function parseTerminalCliArgs(args: readonly string[]): ParsedTerminal {
|
|
|
268
276
|
flags.verbose = true;
|
|
269
277
|
break;
|
|
270
278
|
default:
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
279
|
+
// A single interactive session cannot fan out: reject a target-selection
|
|
280
|
+
// flag with the shared `usage/fanout-not-supported` (mirrors `api --stream`).
|
|
281
|
+
if ((selectionFlagTokens as readonly string[]).includes(arg)) {
|
|
282
|
+
throw new CentrsError({
|
|
283
|
+
code: "usage/fanout-not-supported",
|
|
284
|
+
summary:
|
|
285
|
+
"`centrs terminal` is a single interactive session and cannot fan out across a multi-target selection.",
|
|
275
286
|
remediation:
|
|
276
|
-
"
|
|
287
|
+
"Open a terminal to one router at a time; use `execute` / `api` for multi-target commands.",
|
|
288
|
+
context: { flag: arg, capability: "terminal" },
|
|
277
289
|
});
|
|
278
290
|
}
|
|
291
|
+
if (arg.startsWith("-")) {
|
|
292
|
+
throw unknownFlagError("terminal", arg, terminalCommand.options);
|
|
293
|
+
}
|
|
279
294
|
positional.push(arg);
|
|
280
295
|
break;
|
|
281
296
|
}
|