@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
|
@@ -54,7 +54,7 @@ export interface SelectionWhereClause {
|
|
|
54
54
|
value: string;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
/** The parsed target selection a command hands to {@link
|
|
57
|
+
/** The parsed target selection a command hands to {@link expandSelection}. */
|
|
58
58
|
export interface TargetSelection {
|
|
59
59
|
/** Literal/lookup positionals (NOT operation args — the caller splits those). */
|
|
60
60
|
positionals: readonly string[];
|
|
@@ -70,23 +70,41 @@ export interface TargetSelection {
|
|
|
70
70
|
near?: NearPredicate;
|
|
71
71
|
/** `--bbox <south>,<west>,<north>,<east>`: a geo union predicate (device GPS in box). */
|
|
72
72
|
bbox?: BboxPredicate;
|
|
73
|
+
/**
|
|
74
|
+
* Repeatable `--quickchr <name>`: quickchr machine names (the named-live-
|
|
75
|
+
* provider, #134). Exclusive in v1 — a selection targets quickchr machines OR
|
|
76
|
+
* CDB/literals, never both; the CLI enforces the usage error before expansion
|
|
77
|
+
* and {@link expandSelection} re-asserts it.
|
|
78
|
+
*/
|
|
79
|
+
quickchr?: readonly string[];
|
|
73
80
|
}
|
|
74
81
|
|
|
75
|
-
export interface
|
|
82
|
+
export interface SelectionResolveInput {
|
|
76
83
|
cdbFile?: string;
|
|
77
84
|
cdbPassword?: string;
|
|
78
85
|
/** CLI: true (literal positionals allowed). MCP: false (CDB is the allowlist). */
|
|
79
86
|
allowAdhoc: boolean;
|
|
80
87
|
}
|
|
81
88
|
|
|
82
|
-
/**
|
|
83
|
-
|
|
89
|
+
/**
|
|
90
|
+
* One resolved selection member: a CDB record or an ad-hoc literal target.
|
|
91
|
+
*
|
|
92
|
+
* This is the connection-fact origin ("Axis B") the fan-out callers consume. The
|
|
93
|
+
* selector-source (CDB) and literal arms carry a single endpoint materialized
|
|
94
|
+
* later by `./target.ts`. The `quickchr` arm is the named-live-provider (#134):
|
|
95
|
+
* it carries only the machine NAME here — the live descriptor is resolved in the
|
|
96
|
+
* per-member fan-out `resolve()` path (via `resolveQuickchrTarget`), so a
|
|
97
|
+
* stopped/unknown machine is an inner per-target failure, not an expansion
|
|
98
|
+
* error — see `docs/CONSTITUTION.md` → Resolution providers.
|
|
99
|
+
*/
|
|
100
|
+
export type SelectionMember =
|
|
84
101
|
| { kind: "cdb"; resolution: CdbResolution; recordIndex: number }
|
|
85
|
-
| { kind: "literal"; input: string }
|
|
102
|
+
| { kind: "literal"; input: string }
|
|
103
|
+
| { kind: "quickchr"; name: string };
|
|
86
104
|
|
|
87
|
-
export interface
|
|
105
|
+
export interface SelectionExpansion {
|
|
88
106
|
/** Members ordered by record index; literals appended in positional order. */
|
|
89
|
-
targets: readonly
|
|
107
|
+
targets: readonly SelectionMember[];
|
|
90
108
|
warnings: readonly ResolverWarning[];
|
|
91
109
|
/** True when nothing matched (an empty/unknown selection is not an error). */
|
|
92
110
|
empty: boolean;
|
|
@@ -113,12 +131,16 @@ const ENV_CDB_PASSWORD = "CENTRS_CDB_PASSWORD";
|
|
|
113
131
|
* literal host when no CDB is present). An explicit `--cdb-file` that is missing
|
|
114
132
|
* still throws.
|
|
115
133
|
*/
|
|
116
|
-
export async function
|
|
134
|
+
export async function expandSelection(
|
|
117
135
|
selection: TargetSelection,
|
|
118
|
-
input:
|
|
136
|
+
input: SelectionResolveInput,
|
|
119
137
|
env: Record<string, string | undefined>,
|
|
120
138
|
config: Record<string, string | undefined> = {},
|
|
121
|
-
): Promise<
|
|
139
|
+
): Promise<SelectionExpansion> {
|
|
140
|
+
const quickchrNames = selection.quickchr ?? [];
|
|
141
|
+
if (quickchrNames.length > 0) {
|
|
142
|
+
return expandQuickchrSelection(selection, input, quickchrNames);
|
|
143
|
+
}
|
|
122
144
|
const settings = resolveDevicesSettings({
|
|
123
145
|
cdbFile: input.cdbFile,
|
|
124
146
|
cdbPassword: input.cdbPassword,
|
|
@@ -224,7 +246,7 @@ export async function expandCdbSelection(
|
|
|
224
246
|
}
|
|
225
247
|
}
|
|
226
248
|
|
|
227
|
-
const literals:
|
|
249
|
+
const literals: SelectionMember[] = [];
|
|
228
250
|
const seenLiteral = new Set<string>();
|
|
229
251
|
for (const positional of selection.positionals) {
|
|
230
252
|
try {
|
|
@@ -255,7 +277,7 @@ export async function expandCdbSelection(
|
|
|
255
277
|
}
|
|
256
278
|
}
|
|
257
279
|
|
|
258
|
-
const cdbMembers:
|
|
280
|
+
const cdbMembers: SelectionMember[] = [...indices]
|
|
259
281
|
.sort((a, b) => a - b)
|
|
260
282
|
.map((index) => ({
|
|
261
283
|
kind: "cdb",
|
|
@@ -302,3 +324,68 @@ export async function expandCdbSelection(
|
|
|
302
324
|
|
|
303
325
|
return { targets, warnings, empty: false };
|
|
304
326
|
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Expand a quickchr-only selection. No CDB is loaded — a named-live-provider
|
|
330
|
+
* member never touches the registry, the `__default__` ladder, or the selector
|
|
331
|
+
* grammar. Members carry only the machine name (de-duped by exact name, flag
|
|
332
|
+
* order preserved); the live descriptor resolves per member inside the fan-out.
|
|
333
|
+
*
|
|
334
|
+
* Two guards are load-bearing:
|
|
335
|
+
* - Exclusivity (v1): mixing `--quickchr` with selectors/positionals is a
|
|
336
|
+
* usage error. The CLI throws first with flag-level wording; this re-assert
|
|
337
|
+
* protects library/MCP callers that build a {@link TargetSelection} directly.
|
|
338
|
+
* - `allowAdhoc: false` (the MCP surface) rejects quickchr members entirely:
|
|
339
|
+
* the CDB is the MCP allowlist, and a live provider must not widen it (#134
|
|
340
|
+
* non-goal).
|
|
341
|
+
*/
|
|
342
|
+
function expandQuickchrSelection(
|
|
343
|
+
selection: TargetSelection,
|
|
344
|
+
input: SelectionResolveInput,
|
|
345
|
+
names: readonly string[],
|
|
346
|
+
): SelectionExpansion {
|
|
347
|
+
if (!input.allowAdhoc) {
|
|
348
|
+
throw new CentrsError({
|
|
349
|
+
code: "cdb/target-not-registered",
|
|
350
|
+
summary:
|
|
351
|
+
"quickchr targets are not available on this surface; the CDB is the allowlist.",
|
|
352
|
+
remediation:
|
|
353
|
+
"Register the device with `centrs devices add` and select it by identity, or use the CLI for `--quickchr` targets.",
|
|
354
|
+
context: { quickchr: [...names] },
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
if (
|
|
358
|
+
selection.positionals.length > 0 ||
|
|
359
|
+
selection.groups.length > 0 ||
|
|
360
|
+
selection.all ||
|
|
361
|
+
selection.default ||
|
|
362
|
+
selection.where.length > 0 ||
|
|
363
|
+
selection.near !== undefined ||
|
|
364
|
+
selection.bbox !== undefined
|
|
365
|
+
) {
|
|
366
|
+
throw new CentrsError({
|
|
367
|
+
code: "usage/conflicting-flags",
|
|
368
|
+
summary:
|
|
369
|
+
"`--quickchr` is exclusive: a command targets quickchr machines or CDB/literal targets, not both.",
|
|
370
|
+
remediation:
|
|
371
|
+
"Drop the positional/selector (or the `--quickchr` flag) so the selection has one target source.",
|
|
372
|
+
context: {
|
|
373
|
+
quickchr: [...names],
|
|
374
|
+
positionals: selection.positionals.length,
|
|
375
|
+
groups: selection.groups.length,
|
|
376
|
+
all: selection.all,
|
|
377
|
+
default: selection.default,
|
|
378
|
+
where: selection.where.length,
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
const seen = new Set<string>();
|
|
383
|
+
const targets: SelectionMember[] = [];
|
|
384
|
+
for (const name of names) {
|
|
385
|
+
if (!seen.has(name)) {
|
|
386
|
+
seen.add(name);
|
|
387
|
+
targets.push({ kind: "quickchr", name });
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return { targets, warnings: [], empty: false };
|
|
391
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The neutral per-service endpoint shape — the connection-fact origin ("Axis B")
|
|
3
|
+
* for a resolved target, decoupled from *how* the member was selected ("Axis A").
|
|
4
|
+
*
|
|
5
|
+
* This is the load-bearing scaffold for the resolution-provider refactor
|
|
6
|
+
* (`docs/CONSTITUTION.md` → Resolution providers; centrs #174). Two provider
|
|
7
|
+
* kinds feed it:
|
|
8
|
+
*
|
|
9
|
+
* - **Selector-source** — participates in the `--group`/`--where`/`--all`/
|
|
10
|
+
* `--near`/`--bbox` selector grammar. **CDB** today; **TikTOML** next (#137).
|
|
11
|
+
* CDB is the *degenerate single-endpoint* case: one record yields one
|
|
12
|
+
* host/port/auth, materialized lazily by `./target.ts` once `--via` is known,
|
|
13
|
+
* so a CDB member never needs a populated {@link ServiceEndpointMap}.
|
|
14
|
+
* - **Named-live-provider** — addressed only by an explicit named flag, never
|
|
15
|
+
* matched by selectors, fanned out by repeating the flag; owns an ephemeral
|
|
16
|
+
* endpoint so it also bypasses the settings/`__default__` ladder and conflicts
|
|
17
|
+
* with `--host`/`--port`/`--username`/`--password`/`--ssh-key`. **quickchr**
|
|
18
|
+
* (`--quickchr <name>`, #134). A live provider resolves a member into a
|
|
19
|
+
* *populated* per-service map, because it genuinely exposes different
|
|
20
|
+
* host/port/auth per transport (e.g. a SLiRP-forwarded loopback port per
|
|
21
|
+
* service). Flattening that to a single host/port is the exact refactor #174
|
|
22
|
+
* exists to prevent — TikTOML's `services` block is per-service too.
|
|
23
|
+
*
|
|
24
|
+
* The shape here deliberately mirrors quickchr's published descriptor contract
|
|
25
|
+
* (`tikoci/quickchr` `docs/centrs-interface.md`, `ServiceEndpoint`/
|
|
26
|
+
* `SshServiceEndpoint`), which states that centrs reuses *this same internal
|
|
27
|
+
* type* for TikTOML resolution. It is provider-neutral: `source.provider` is a
|
|
28
|
+
* free string, not a quickchr literal. Mapping a resolved endpoint's auth onto
|
|
29
|
+
* centrs' {@link ../resolver/target.ts | ResolvedAuth} (notably
|
|
30
|
+
* `privateKeyPath` → `sshKey`) is the provider adapter's job, not this type's.
|
|
31
|
+
*
|
|
32
|
+
* Phase 1 (this file) defines the contract only; the quickchr provider adapter
|
|
33
|
+
* that *produces* a map, and the `--via` consumer that *reads* `map[via]`, land
|
|
34
|
+
* with the `--quickchr` plumbing (#134 Phase 2+).
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import type { RouterOsProtocol } from "../protocols/index.ts";
|
|
38
|
+
|
|
39
|
+
/** Provenance for a resolved endpoint — which provider vouched for it. */
|
|
40
|
+
export interface ServiceEndpointSource {
|
|
41
|
+
/** Provider identity, e.g. `"quickchr"` or `"tiktoml"`. Neutral by design. */
|
|
42
|
+
provider: string;
|
|
43
|
+
/** Provider-internal forward name backing the service (e.g. `"api-ssl"`), for provenance. */
|
|
44
|
+
portMappingName?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** REST/native-api auth. RouterOS shares one credential across REST and native. */
|
|
48
|
+
export interface ServiceEndpointAuth {
|
|
49
|
+
username: string;
|
|
50
|
+
password?: string;
|
|
51
|
+
/** Pre-encoded HTTP Basic value, when the provider supplies one directly. */
|
|
52
|
+
basic?: string;
|
|
53
|
+
/** Pre-formed auth header value, when the provider supplies one directly. */
|
|
54
|
+
header?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** SSH auth. Same credential vocabulary `+ key path`, plus batch-mode capability. */
|
|
58
|
+
export interface SshEndpointAuth {
|
|
59
|
+
username: string;
|
|
60
|
+
/**
|
|
61
|
+
* Path to a usable private key. Populated only when the provider has actually
|
|
62
|
+
* verified a batch (non-interactive) login with it — maps to
|
|
63
|
+
* {@link ../resolver/target.ts | ResolvedAuth.sshKey} in the provider adapter.
|
|
64
|
+
*/
|
|
65
|
+
privateKeyPath?: string;
|
|
66
|
+
/** Every auth mode the endpoint accepts. */
|
|
67
|
+
modes: Array<"private-key" | "agent-or-config" | "password">;
|
|
68
|
+
/**
|
|
69
|
+
* The batch-capable subset. This is the gate centrs enforces for `--via ssh`
|
|
70
|
+
* and `transfer --via sftp`: an empty list means no non-interactive handoff is
|
|
71
|
+
* possible, so centrs must emit a typed unsupported-capability error rather
|
|
72
|
+
* than prompt for a password or silently fall back (#134 Phase 4).
|
|
73
|
+
*/
|
|
74
|
+
batchModes: Array<"private-key" | "agent-or-config">;
|
|
75
|
+
passwordAvailable?: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Fields shared by every available endpoint regardless of auth flavor. */
|
|
79
|
+
interface AvailableEndpointBase {
|
|
80
|
+
available: true;
|
|
81
|
+
/** Hostname/IP to dial — NOT a port. `"127.0.0.1"` for a SLiRP-forwarded service. */
|
|
82
|
+
host: string;
|
|
83
|
+
port: number;
|
|
84
|
+
guestPort?: number;
|
|
85
|
+
transport: "tcp" | "udp";
|
|
86
|
+
/** True for TLS-backed endpoints (https / api-ssl); SSH is always `false` (own transport security). */
|
|
87
|
+
tls: boolean;
|
|
88
|
+
url?: string;
|
|
89
|
+
source?: ServiceEndpointSource;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** An endpoint the provider could not vouch for; a consumer MUST gate on `available` before dialing. */
|
|
93
|
+
export interface UnavailableEndpoint {
|
|
94
|
+
available: false;
|
|
95
|
+
unavailableReason: string;
|
|
96
|
+
/** Best-effort echoes only — never dial these without a re-resolve. */
|
|
97
|
+
host?: string;
|
|
98
|
+
port?: number;
|
|
99
|
+
guestPort?: number;
|
|
100
|
+
transport?: "tcp" | "udp";
|
|
101
|
+
tls?: boolean;
|
|
102
|
+
url?: string;
|
|
103
|
+
source?: ServiceEndpointSource;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** One per-service endpoint (rest-api / native-api). */
|
|
107
|
+
export type ServiceEndpoint =
|
|
108
|
+
| (AvailableEndpointBase & { auth?: ServiceEndpointAuth })
|
|
109
|
+
| UnavailableEndpoint;
|
|
110
|
+
|
|
111
|
+
/** The SSH endpoint — same union, with SSH-specific auth substituted. */
|
|
112
|
+
export type SshServiceEndpoint =
|
|
113
|
+
| (AvailableEndpointBase & { auth: SshEndpointAuth })
|
|
114
|
+
| UnavailableEndpoint;
|
|
115
|
+
|
|
116
|
+
/** Any resolved endpoint, whatever the service. */
|
|
117
|
+
export type AnyServiceEndpoint = ServiceEndpoint | SshServiceEndpoint;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The endpoint type for a given `--via` id. `ssh` carries the SSH-specific
|
|
121
|
+
* {@link SshServiceEndpoint} (its available form guarantees `auth: SshEndpointAuth`
|
|
122
|
+
* with `batchModes`, the gate Phase 4 enforces for `--via ssh`/`transfer --via
|
|
123
|
+
* sftp`); every other transport uses the shared credential {@link ServiceEndpoint}.
|
|
124
|
+
*/
|
|
125
|
+
export type ServiceEndpointFor<K extends RouterOsProtocol> = K extends "ssh"
|
|
126
|
+
? SshServiceEndpoint
|
|
127
|
+
: ServiceEndpoint;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* A member's per-service connection facts, keyed by centrs `--via` id.
|
|
131
|
+
*
|
|
132
|
+
* Partial by design: a provider may cover only a subset of the eight protocols
|
|
133
|
+
* (quickchr v1 covers `rest-api`/`native-api`/`ssh`). A `--via` whose key is
|
|
134
|
+
* absent is a typed unsupported-provider error at consume time, not a silent
|
|
135
|
+
* fallback. CDB never populates this map (its single endpoint is materialized by
|
|
136
|
+
* `./target.ts`); only named-live-providers do.
|
|
137
|
+
*
|
|
138
|
+
* Keyed by service so per-`--via` precision survives: `map.ssh` is a
|
|
139
|
+
* {@link SshServiceEndpoint}, not a widened `AnyServiceEndpoint`, so a consumer
|
|
140
|
+
* reading `map.ssh` keeps the SSH `auth`/`batchModes` guarantee without a cast.
|
|
141
|
+
*/
|
|
142
|
+
export type ServiceEndpointMap = {
|
|
143
|
+
[K in RouterOsProtocol]?: ServiceEndpointFor<K>;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Narrow an endpoint to its available form (the only form safe to dial). Generic
|
|
148
|
+
* so the caller's precise union is preserved: an {@link SshServiceEndpoint} narrows
|
|
149
|
+
* to its available arm (`auth: SshEndpointAuth` required), not the widened base.
|
|
150
|
+
*/
|
|
151
|
+
export function isEndpointAvailable<T extends AnyServiceEndpoint>(
|
|
152
|
+
endpoint: T,
|
|
153
|
+
): endpoint is Extract<T, { available: true }> {
|
|
154
|
+
return endpoint.available;
|
|
155
|
+
}
|
package/src/resolver/target.ts
CHANGED
|
@@ -455,6 +455,6 @@ export function defaultPortForScheme(scheme: "http" | "https"): number {
|
|
|
455
455
|
return scheme === "https" ? 443 : 80;
|
|
456
456
|
}
|
|
457
457
|
|
|
458
|
-
function formatHostForUrl(host: string): string {
|
|
458
|
+
export function formatHostForUrl(host: string): string {
|
|
459
459
|
return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
|
|
460
460
|
}
|
package/src/retrieve-fanout.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Boundaries (see `commands/retrieve/README.md`, `docs/CONSTITUTION.md`), shared
|
|
14
14
|
* with `api-fanout.ts`:
|
|
15
|
-
* - The CDB is loaded + decrypted ONCE (`
|
|
15
|
+
* - The CDB is loaded + decrypted ONCE (`expandSelection`); a cdb member is
|
|
16
16
|
* resolved with its pre-resolved record, so `resolveCdb`'s `__default__`
|
|
17
17
|
* synthetic fallback never collides ad-hoc literals.
|
|
18
18
|
* - A `--default` / `__default__` member is guarded from being dialed as the
|
|
@@ -46,15 +46,16 @@ import {
|
|
|
46
46
|
runFanout,
|
|
47
47
|
summarizeFanout,
|
|
48
48
|
} from "./core/fanout.ts";
|
|
49
|
+
import { toYaml } from "./core/yaml.ts";
|
|
49
50
|
import { CentrsError, serializeCentrsError } from "./errors.ts";
|
|
50
51
|
import { plannedProtocols, type RouterOsProtocol } from "./protocols/index.ts";
|
|
51
52
|
import {
|
|
52
|
-
|
|
53
|
-
type CdbSelectionMember,
|
|
54
|
-
type CdbSelectionResolveInput,
|
|
55
|
-
expandCdbSelection,
|
|
53
|
+
expandSelection,
|
|
56
54
|
isDefaultRecordTarget,
|
|
57
55
|
loadEnvFileDefaults,
|
|
56
|
+
type SelectionExpansion,
|
|
57
|
+
type SelectionMember,
|
|
58
|
+
type SelectionResolveInput,
|
|
58
59
|
type TargetSelection,
|
|
59
60
|
} from "./resolver/index.ts";
|
|
60
61
|
import {
|
|
@@ -71,7 +72,6 @@ import {
|
|
|
71
72
|
resolveRetrieveGlobalContext,
|
|
72
73
|
resolveRetrieveRequest,
|
|
73
74
|
runResolvedRetrieve,
|
|
74
|
-
toYaml,
|
|
75
75
|
validateRetrieveRequestShape,
|
|
76
76
|
} from "./retrieve.ts";
|
|
77
77
|
|
|
@@ -82,6 +82,8 @@ export interface RetrieveSelectionSummary {
|
|
|
82
82
|
all: boolean;
|
|
83
83
|
default: boolean;
|
|
84
84
|
positionals: readonly string[];
|
|
85
|
+
/** quickchr machine names (`--quickchr`, repeatable; exclusive of the rest). */
|
|
86
|
+
quickchr?: readonly string[];
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
export interface RetrieveFanoutOperationMeta {
|
|
@@ -109,10 +111,10 @@ export interface RetrieveFanoutInternals {
|
|
|
109
111
|
/** Selection expansion. Defaults to the real CDB-backed expansion. */
|
|
110
112
|
expand?: (
|
|
111
113
|
selection: TargetSelection,
|
|
112
|
-
input:
|
|
114
|
+
input: SelectionResolveInput,
|
|
113
115
|
env: Record<string, string | undefined>,
|
|
114
116
|
config?: Record<string, string | undefined>,
|
|
115
|
-
) => Promise<
|
|
117
|
+
) => Promise<SelectionExpansion>;
|
|
116
118
|
/** Backoff sleeper. Defaults to a real timer; tests pass a no-op. */
|
|
117
119
|
sleep?: (ms: number) => Promise<void>;
|
|
118
120
|
}
|
|
@@ -132,10 +134,11 @@ function summarizeSelection(
|
|
|
132
134
|
all: selection.all,
|
|
133
135
|
default: selection.default,
|
|
134
136
|
positionals: selection.positionals,
|
|
137
|
+
quickchr: selection.quickchr,
|
|
135
138
|
};
|
|
136
139
|
}
|
|
137
140
|
|
|
138
|
-
function memberTargetMeta(member:
|
|
141
|
+
function memberTargetMeta(member: SelectionMember): {
|
|
139
142
|
input?: string;
|
|
140
143
|
identity?: string;
|
|
141
144
|
recordIndex?: number;
|
|
@@ -147,6 +150,9 @@ function memberTargetMeta(member: CdbSelectionMember): {
|
|
|
147
150
|
recordIndex: member.recordIndex,
|
|
148
151
|
};
|
|
149
152
|
}
|
|
153
|
+
if (member.kind === "quickchr") {
|
|
154
|
+
return { input: member.name, identity: member.name };
|
|
155
|
+
}
|
|
150
156
|
return { input: member.input };
|
|
151
157
|
}
|
|
152
158
|
|
|
@@ -159,7 +165,7 @@ function memberTargetMeta(member: CdbSelectionMember): {
|
|
|
159
165
|
*/
|
|
160
166
|
function relabelAdhocMember(
|
|
161
167
|
envelope: RetrieveEnvelope,
|
|
162
|
-
member:
|
|
168
|
+
member: SelectionMember,
|
|
163
169
|
): RetrieveEnvelope {
|
|
164
170
|
if (member.kind !== "literal") {
|
|
165
171
|
return envelope;
|
|
@@ -194,7 +200,7 @@ export async function retrieveFanout(
|
|
|
194
200
|
);
|
|
195
201
|
const selectionSummary = summarizeSelection(selection);
|
|
196
202
|
|
|
197
|
-
const expand = internals.expand ??
|
|
203
|
+
const expand = internals.expand ?? expandSelection;
|
|
198
204
|
const expansion = await expand(
|
|
199
205
|
selection,
|
|
200
206
|
{
|
|
@@ -234,7 +240,7 @@ export async function retrieveFanout(
|
|
|
234
240
|
const sleep = internals.sleep ?? defaultFanoutSleep;
|
|
235
241
|
|
|
236
242
|
const targets = await runFanout<
|
|
237
|
-
|
|
243
|
+
SelectionMember,
|
|
238
244
|
ResolvedRetrieveRequest,
|
|
239
245
|
RetrieveEnvelope
|
|
240
246
|
>({
|
|
@@ -267,6 +273,14 @@ export async function retrieveFanout(
|
|
|
267
273
|
config,
|
|
268
274
|
);
|
|
269
275
|
}
|
|
276
|
+
if (member.kind === "quickchr") {
|
|
277
|
+
// The live descriptor resolves here, per member — a stopped/unknown
|
|
278
|
+
// machine is an inner per-target failure, not an expansion error.
|
|
279
|
+
return resolveRetrieveRequest(
|
|
280
|
+
{ ...request, targetInput: undefined, quickchr: member.name },
|
|
281
|
+
env,
|
|
282
|
+
);
|
|
283
|
+
}
|
|
270
284
|
// Ad-hoc literal: full single-target resolution (CDB load → `__default__`
|
|
271
285
|
// fallback creds), mirroring single-target `retrieve`.
|
|
272
286
|
return resolveRetrieveRequest(
|
|
@@ -373,7 +387,7 @@ function resolveFanoutConcurrencyProtocol(
|
|
|
373
387
|
request: RetrieveRequest,
|
|
374
388
|
env: Record<string, string | undefined>,
|
|
375
389
|
globalVia: RouterOsProtocol,
|
|
376
|
-
expansion:
|
|
390
|
+
expansion: SelectionExpansion,
|
|
377
391
|
): RouterOsProtocol {
|
|
378
392
|
// Deliberately CLI/env only, not `config` — a `centrs.env` default is the
|
|
379
393
|
// weakest precedence tier and must not be treated as a hard pin the way an
|