@tikoci/centrs 0.1.1 → 0.1.3

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.
Files changed (95) hide show
  1. package/CHANGELOG.md +107 -2
  2. package/GLOSSARY.txt +87 -0
  3. package/README.md +25 -9
  4. package/SECURITY.md +3 -3
  5. package/commands/AGENTS.md +3 -2
  6. package/commands/api/AGENTS.md +212 -0
  7. package/commands/api/README.md +177 -0
  8. package/commands/api/examples.md +428 -0
  9. package/commands/btest/README.md +56 -31
  10. package/commands/btest/examples.md +35 -10
  11. package/commands/check/README.md +371 -53
  12. package/commands/check/examples.md +390 -0
  13. package/commands/devices/README.md +177 -37
  14. package/commands/devices/examples.md +148 -0
  15. package/commands/execute/README.md +48 -7
  16. package/commands/execute/examples.md +90 -6
  17. package/commands/retrieve/README.md +37 -51
  18. package/commands/retrieve/examples.md +40 -9
  19. package/commands/settings/README.md +286 -22
  20. package/commands/settings/examples.md +408 -0
  21. package/commands/terminal/README.md +6 -0
  22. package/commands/transfer/README.md +32 -2
  23. package/commands/transfer/examples.md +66 -0
  24. package/docs/CONSTITUTION.md +40 -15
  25. package/docs/MATRIX.md +69 -142
  26. package/docs/errors/README.md +2 -1
  27. package/docs/errors/cdb/empty-selection.md +19 -0
  28. package/docs/errors/input/incomplete-gps.md +9 -0
  29. package/docs/errors/input/invalid-altitude.md +9 -0
  30. package/docs/errors/input/invalid-bbox.md +9 -0
  31. package/docs/errors/input/invalid-coordinate.md +9 -0
  32. package/docs/errors/input/invalid-radius.md +9 -0
  33. package/docs/errors/internal/settings-failed.md +9 -0
  34. package/docs/errors/routeros/btest-connection-count-single-stream.md +22 -0
  35. package/docs/errors/routeros/btest-udp-tx-size-ignored.md +15 -0
  36. package/docs/errors/settings/consequential-value.md +26 -0
  37. package/docs/errors/settings/invalid-boolean.md +21 -3
  38. package/docs/errors/settings/invalid-format.md +18 -3
  39. package/docs/errors/settings/invalid-integer.md +18 -3
  40. package/docs/errors/settings/invalid-timeout.md +20 -3
  41. package/docs/errors/settings/invalid-via.md +22 -3
  42. package/docs/errors/settings/reserved-key.md +29 -0
  43. package/docs/errors/settings/skip-env-file-active.md +25 -0
  44. package/docs/errors/settings/unknown-key.md +25 -0
  45. package/docs/errors/usage/fanout-not-supported.md +16 -0
  46. package/docs/errors/usage/invalid-method.md +23 -0
  47. package/package.json +9 -9
  48. package/src/api-fanout.ts +454 -0
  49. package/src/api.ts +1627 -0
  50. package/src/btest.ts +61 -1
  51. package/src/cli/api.ts +660 -0
  52. package/src/cli/btest.ts +20 -2
  53. package/src/cli/common.ts +75 -0
  54. package/src/cli/devices.ts +235 -6
  55. package/src/cli/discover.ts +3 -1
  56. package/src/cli/execute.ts +107 -6
  57. package/src/cli/mcp.ts +8 -1
  58. package/src/cli/retrieve.ts +135 -90
  59. package/src/cli/selection.ts +200 -0
  60. package/src/cli/settings.ts +286 -0
  61. package/src/cli/terminal.ts +22 -7
  62. package/src/cli/transfer.ts +171 -47
  63. package/src/cli.ts +29 -0
  64. package/src/core/error-catalog.ts +69 -0
  65. package/src/core/fanout.ts +301 -0
  66. package/src/core/inspect.ts +170 -0
  67. package/src/devices.ts +212 -20
  68. package/src/discover.ts +7 -2
  69. package/src/execute-fanout.ts +438 -0
  70. package/src/execute.ts +134 -70
  71. package/src/index.ts +166 -9
  72. package/src/mcp/config.ts +12 -2
  73. package/src/mcp/tools.ts +137 -2
  74. package/src/protocols/adapter.ts +357 -9
  75. package/src/protocols/btest-session.ts +279 -46
  76. package/src/protocols/btest.ts +6 -2
  77. package/src/protocols/index.ts +11 -5
  78. package/src/protocols/native-api.ts +223 -30
  79. package/src/resolver/cdb.ts +10 -2
  80. package/src/resolver/comment-kv.ts +58 -4
  81. package/src/resolver/config-file.ts +136 -0
  82. package/src/resolver/facts.ts +78 -0
  83. package/src/resolver/geo.ts +453 -0
  84. package/src/resolver/index.ts +46 -0
  85. package/src/resolver/mac.ts +12 -3
  86. package/src/resolver/selection.ts +304 -0
  87. package/src/resolver/settings.ts +54 -5
  88. package/src/resolver/target.ts +20 -2
  89. package/src/retrieve-fanout.ts +232 -268
  90. package/src/retrieve.ts +69 -94
  91. package/src/settings.ts +1078 -0
  92. package/src/terminal.ts +11 -1
  93. package/src/transfer-fanout.ts +527 -0
  94. package/src/transfer.ts +61 -11
  95. package/commands/stream/README.md +0 -70
package/src/execute.ts CHANGED
@@ -9,6 +9,13 @@ import type {
9
9
  Warning,
10
10
  } from "./core/envelope.ts";
11
11
  import { buildTip } from "./core/envelope.ts";
12
+ import {
13
+ extractCompletionNames,
14
+ inspectChildren,
15
+ inspectCompletions,
16
+ isArgumentNode,
17
+ pathTokens,
18
+ } from "./core/inspect.ts";
12
19
  import { mapRouterOsError } from "./core/routeros-errors.ts";
13
20
  import { CentrsError, serializeCentrsError } from "./errors.ts";
14
21
  import {
@@ -28,6 +35,7 @@ import {
28
35
  effectiveHostCandidate,
29
36
  isIpTransport,
30
37
  isMacAddress,
38
+ loadEnvFileDefaults,
31
39
  parseDuration,
32
40
  parseResolvePolicy,
33
41
  type ResolvedAuth,
@@ -132,20 +140,6 @@ export interface ResolvedExecuteRequest {
132
140
  warnings: readonly Warning[];
133
141
  }
134
142
 
135
- interface InspectChildItem {
136
- type?: string;
137
- name?: string;
138
- "node-type"?: string;
139
- }
140
-
141
- interface InspectCompletionItem {
142
- type?: string;
143
- name?: string;
144
- completion?: string;
145
- value?: string;
146
- text?: string;
147
- }
148
-
149
143
  export async function execute(
150
144
  request: ExecuteRequest,
151
145
  env: Record<string, string | undefined> = Bun.env,
@@ -284,19 +278,29 @@ export async function runResolvedExecute(
284
278
  export async function resolveExecuteRequest(
285
279
  request: ExecuteRequest,
286
280
  env: Record<string, string | undefined>,
281
+ options: {
282
+ cdbResolution?: CdbResolution;
283
+ config?: Record<string, string | undefined>;
284
+ } = {},
287
285
  ): Promise<ResolvedExecuteRequest> {
288
286
  validateExecuteRequestShape(request);
289
287
  const canonical = canonicalizeExecuteCommand(request.command);
290
- const cdbResolution = await resolveCdb(
291
- {
292
- targetInput: request.targetInput,
293
- cdbFile: request.cdbFile,
294
- cdbPassword: request.cdbPassword,
295
- },
296
- env,
297
- );
298
- const via = resolveExecuteProtocol(request, env, cdbResolution);
299
- const format = resolveFormat(request, env);
288
+ const config = options.config ?? (await loadEnvFileDefaults(env));
289
+ // Fan-out passes the pre-resolved CDB record for a member (the CDB is loaded
290
+ // once in `expandCdbSelection`); single-target resolves it here.
291
+ const cdbResolution =
292
+ options.cdbResolution ??
293
+ (await resolveCdb(
294
+ {
295
+ targetInput: request.targetInput,
296
+ cdbFile: request.cdbFile,
297
+ cdbPassword: request.cdbPassword,
298
+ },
299
+ env,
300
+ config,
301
+ ));
302
+ const via = resolveExecuteProtocol(request, env, cdbResolution, config);
303
+ const format = resolveFormat(request, env, config);
300
304
  const validate = resolveBooleanSetting(
301
305
  request.validate,
302
306
  env,
@@ -304,18 +308,22 @@ export async function resolveExecuteRequest(
304
308
  true,
305
309
  "validate",
306
310
  cdbResolution?.overrides.validate,
311
+ config,
307
312
  );
308
313
  const timeoutMs = resolveTimeoutSetting(
309
314
  request.timeout,
310
315
  env,
311
316
  via.value,
312
317
  cdbResolution?.overrides.timeoutMs,
318
+ config,
313
319
  );
314
320
  const maxResultsBytes = resolveOptionalIntegerSetting(
315
321
  request.maxResultsBytes,
316
322
  env,
317
323
  "CENTRS_MAX_RESULTS",
318
324
  "max-results",
325
+ undefined,
326
+ config,
319
327
  );
320
328
  const macResolution = isIpTransport(via.value)
321
329
  ? await resolveMacTarget({
@@ -323,7 +331,10 @@ export async function resolveExecuteRequest(
323
331
  targetInput: request.targetInput,
324
332
  cdbTarget: cdbResolution?.target,
325
333
  env,
326
- policy: parseResolvePolicy(request.resolve ?? env["CENTRS_RESOLVE"]),
334
+ config,
335
+ policy: parseResolvePolicy(
336
+ request.resolve ?? env["CENTRS_RESOLVE"] ?? config["CENTRS_RESOLVE"],
337
+ ),
327
338
  operation: "execute",
328
339
  })
329
340
  : undefined;
@@ -337,6 +348,7 @@ export async function resolveExecuteRequest(
337
348
  env,
338
349
  via.value,
339
350
  cdbResolution,
351
+ config,
340
352
  );
341
353
  const auth = resolveAuth(
342
354
  {
@@ -346,6 +358,7 @@ export async function resolveExecuteRequest(
346
358
  },
347
359
  env,
348
360
  cdbResolution,
361
+ config,
349
362
  );
350
363
  const insecure = resolveBooleanSetting(
351
364
  request.insecure,
@@ -354,6 +367,7 @@ export async function resolveExecuteRequest(
354
367
  false,
355
368
  "insecure",
356
369
  cdbResolution?.overrides.insecure,
370
+ config,
357
371
  );
358
372
 
359
373
  return {
@@ -373,6 +387,79 @@ export async function resolveExecuteRequest(
373
387
  };
374
388
  }
375
389
 
390
+ export interface ExecuteGlobalContext {
391
+ via: RouterOsProtocol;
392
+ write: boolean;
393
+ summary: ExecuteRequestSummary;
394
+ settings: CommonSettingsMeta;
395
+ }
396
+
397
+ /**
398
+ * Resolve the target-independent settings (protocol, format, validate, timeout,
399
+ * max-results) for a fan-out's outer `meta.operation.request` summary and its
400
+ * transport-aware concurrency default. No CDB record is consulted; the global
401
+ * `--via` (default `native-api`) decides the summary and concurrency base. Also
402
+ * canonicalizes the command once so write-ness is known before any target runs.
403
+ */
404
+ export function resolveExecuteGlobalContext(
405
+ request: ExecuteRequest,
406
+ env: Record<string, string | undefined>,
407
+ config: Record<string, string | undefined> = {},
408
+ ): ExecuteGlobalContext {
409
+ validateExecuteRequestShape(request);
410
+ const canonical = canonicalizeExecuteCommand(request.command);
411
+ const via = resolveExecuteProtocol(request, env, undefined, config);
412
+ const format = resolveFormat(request, env, config);
413
+ const validate = resolveBooleanSetting(
414
+ request.validate,
415
+ env,
416
+ "CENTRS_VALIDATE",
417
+ true,
418
+ "validate",
419
+ undefined,
420
+ config,
421
+ );
422
+ const timeoutMs = resolveTimeoutSetting(
423
+ request.timeout,
424
+ env,
425
+ via.value,
426
+ undefined,
427
+ config,
428
+ );
429
+ const maxResultsBytes = resolveOptionalIntegerSetting(
430
+ request.maxResultsBytes,
431
+ env,
432
+ "CENTRS_MAX_RESULTS",
433
+ "max-results",
434
+ undefined,
435
+ config,
436
+ );
437
+ return {
438
+ via: via.value,
439
+ write: isWriteShaped(canonical),
440
+ summary: {
441
+ command: request.command,
442
+ canonical,
443
+ write: isWriteShaped(canonical),
444
+ yes: request.yes ?? false,
445
+ validate: validate.value,
446
+ verbose: request.verbose ?? false,
447
+ timeoutMs: timeoutMs.value,
448
+ format: format.value,
449
+ maxResultsBytes: maxResultsBytes?.value,
450
+ },
451
+ settings: {
452
+ via: toCoreSource(via.source),
453
+ timeoutMs: toCoreSource(timeoutMs.source),
454
+ format: toCoreSource(format.source),
455
+ validate: toCoreSource(validate.source),
456
+ maxResultsBytes: maxResultsBytes
457
+ ? toCoreSource(maxResultsBytes.source)
458
+ : undefined,
459
+ },
460
+ };
461
+ }
462
+
376
463
  export function buildExecuteErrorEnvelope(
377
464
  request: ExecuteRequest,
378
465
  error: unknown,
@@ -735,24 +822,18 @@ async function inspectExecuteAttributes(
735
822
  resolved: ResolvedExecuteRequest,
736
823
  backend: ProtocolAdapter,
737
824
  ): Promise<string[]> {
738
- const commandPath = inspectPath([
825
+ const commandTokens = [
739
826
  ...pathTokens(resolved.canonical.path),
740
827
  resolved.canonical.verb,
741
- ]);
742
- const children = (await backend.inspect(
743
- "child",
744
- commandPath,
745
- )) as InspectChildItem[];
828
+ ];
829
+ const children = await inspectChildren(backend, commandTokens);
746
830
  const childAttributes = children
747
831
  .filter(isArgumentNode)
748
832
  .map((child) => child.name)
749
833
  .filter(
750
834
  (name): name is string => typeof name === "string" && name.length > 0,
751
835
  );
752
- const completionRows = (await backend.inspect(
753
- "completion",
754
- commandPath,
755
- )) as InspectCompletionItem[];
836
+ const completionRows = await inspectCompletions(backend, commandTokens);
756
837
  return [
757
838
  ...new Set([...childAttributes, ...extractCompletionNames(completionRows)]),
758
839
  ].sort();
@@ -769,16 +850,6 @@ async function runCommand(
769
850
  return backend.execute({ path: "", command: "", script: resolved.command });
770
851
  }
771
852
  if (resolved.canonical.mode === "script") {
772
- if (resolved.via.value === "native-api") {
773
- throw new CentrsError({
774
- code: "transport/unsupported-operation",
775
- summary:
776
- "The native API execute path requires a slash-prefixed RouterOS command.",
777
- remediation:
778
- "Use a path-shaped command such as `/system/identity/set name=x`, or pin `--via rest-api` for `/rest/execute` script mode.",
779
- context: { via: resolved.via.value, command: resolved.command },
780
- });
781
- }
782
853
  return backend.execute({ path: "", command: "", script: resolved.command });
783
854
  }
784
855
  return backend.execute({
@@ -819,7 +890,9 @@ async function assertWriteConfirmed(
819
890
  });
820
891
  }
821
892
 
822
- async function promptForWriteConfirmation(prompt: string): Promise<boolean> {
893
+ export async function promptForWriteConfirmation(
894
+ prompt: string,
895
+ ): Promise<boolean> {
823
896
  process.stderr.write(prompt);
824
897
  for await (const chunk of Bun.stdin.stream()) {
825
898
  const answer = new TextDecoder().decode(chunk).trim().toLowerCase();
@@ -832,8 +905,9 @@ function resolveExecuteProtocol(
832
905
  request: ExecuteRequest,
833
906
  env: Record<string, string | undefined>,
834
907
  cdb?: CdbResolution,
908
+ config: Record<string, string | undefined> = {},
835
909
  ): ResolvedSetting<RouterOsProtocol> {
836
- const defaultVia = isUnresolvedMacTarget(request, env, cdb)
910
+ const defaultVia = isUnresolvedMacTarget(request, env, cdb, config)
837
911
  ? "mac-telnet"
838
912
  : "native-api";
839
913
  const via = resolveStringSetting(
@@ -844,6 +918,7 @@ function resolveExecuteProtocol(
844
918
  "via",
845
919
  undefined,
846
920
  cdb?.overrides.via,
921
+ config,
847
922
  );
848
923
  if (!via) {
849
924
  throw new CentrsError({
@@ -888,8 +963,13 @@ function isUnresolvedMacTarget(
888
963
  request: ExecuteRequest,
889
964
  env: Record<string, string | undefined>,
890
965
  cdb?: CdbResolution,
966
+ config: Record<string, string | undefined> = {},
891
967
  ): boolean {
892
- if (request.via !== undefined || env["CENTRS_VIA"] !== undefined) {
968
+ if (
969
+ request.via !== undefined ||
970
+ env["CENTRS_VIA"] !== undefined ||
971
+ config["CENTRS_VIA"] !== undefined
972
+ ) {
893
973
  return false;
894
974
  }
895
975
  // Decide the default transport from the *effective* target (host >
@@ -901,6 +981,7 @@ function isUnresolvedMacTarget(
901
981
  targetInput: request.targetInput,
902
982
  cdbTarget: cdb?.target,
903
983
  env,
984
+ config,
904
985
  });
905
986
  return candidate ? isMacAddress(candidate) : false;
906
987
  }
@@ -908,6 +989,7 @@ function isUnresolvedMacTarget(
908
989
  function resolveFormat(
909
990
  request: ExecuteRequest,
910
991
  env: Record<string, string | undefined>,
992
+ config: Record<string, string | undefined> = {},
911
993
  ): ResolvedSetting<ExecuteOutputFormat> {
912
994
  return resolveStringSetting(
913
995
  request.format,
@@ -916,6 +998,8 @@ function resolveFormat(
916
998
  "text",
917
999
  "format",
918
1000
  parseOutputFormat,
1001
+ undefined,
1002
+ config,
919
1003
  ) as ResolvedSetting<ExecuteOutputFormat>;
920
1004
  }
921
1005
 
@@ -941,6 +1025,7 @@ function resolveTimeoutSetting(
941
1025
  env: Record<string, string | undefined>,
942
1026
  via: RouterOsProtocol,
943
1027
  commentKv?: ResolvedSetting<number>,
1028
+ config: Record<string, string | undefined> = {},
944
1029
  ): ResolvedSetting<number> {
945
1030
  const resolved = resolveStringSetting(
946
1031
  timeout === undefined ? undefined : String(timeout),
@@ -961,6 +1046,7 @@ function resolveTimeoutSetting(
961
1046
  return parsed;
962
1047
  },
963
1048
  commentKv,
1049
+ config,
964
1050
  );
965
1051
  if (!resolved) {
966
1052
  throw new Error("timeout resolution produced no value");
@@ -1299,28 +1385,6 @@ function syntaxCause(error: unknown): unknown {
1299
1385
  return error;
1300
1386
  }
1301
1387
 
1302
- function pathTokens(path: string): string[] {
1303
- return path.split("/").filter(Boolean);
1304
- }
1305
-
1306
- function inspectPath(tokens: readonly string[]): string {
1307
- return tokens.join(",");
1308
- }
1309
-
1310
- function isArgumentNode(child: InspectChildItem): boolean {
1311
- return child.type === "arg" || child["node-type"] === "arg";
1312
- }
1313
-
1314
- function extractCompletionNames(
1315
- rows: readonly InspectCompletionItem[],
1316
- ): string[] {
1317
- return rows
1318
- .flatMap((row) => [row.completion, row.name, row.value, row.text])
1319
- .filter((value): value is string => typeof value === "string")
1320
- .map((value) => value.replace(/=.*$/, "").trim())
1321
- .filter((value) => value.length > 0);
1322
- }
1323
-
1324
1388
  function parseOutputFormat(value: string): ExecuteOutputFormat {
1325
1389
  if (executeOutputFormats.includes(value as ExecuteOutputFormat)) {
1326
1390
  return value as ExecuteOutputFormat;
package/src/index.ts CHANGED
@@ -1,5 +1,52 @@
1
1
  export const centrsVersion = "0.1.0";
2
2
 
3
+ export {
4
+ type ApiEnvelope,
5
+ type ApiErrorEnvelope,
6
+ type ApiMethod,
7
+ type ApiOperationMeta,
8
+ type ApiOutputFormat,
9
+ type ApiRequest,
10
+ type ApiRequestSummary,
11
+ type ApiStreamMeta,
12
+ type ApiStreamStopReason,
13
+ type ApiStreamSummary,
14
+ type ApiSuccessEnvelope,
15
+ api,
16
+ apiEnvelope,
17
+ apiListen,
18
+ apiMethods,
19
+ apiOutputFormats,
20
+ apiRequestSummaryFromRequest,
21
+ buildApiBody,
22
+ buildApiErrorEnvelope,
23
+ buildApiErrorEnvelopeFromResolved,
24
+ buildApiQuery,
25
+ buildProtocolApiRequest,
26
+ isApiMutating,
27
+ mapMethodToVerb,
28
+ type NormalizedApiEndpoint,
29
+ normalizeApiEndpoint,
30
+ parseApiMethod,
31
+ type ResolvedApiRequest,
32
+ renderApiEnvelope,
33
+ renderApiStreamLine,
34
+ resolveApiRequest,
35
+ runResolvedApi,
36
+ validateApiRequestShape,
37
+ } from "./api.ts";
38
+ export {
39
+ type ApiFanoutData,
40
+ type ApiFanoutEnvelope,
41
+ type ApiFanoutErrorEnvelope,
42
+ type ApiFanoutInternals,
43
+ type ApiFanoutOperationMeta,
44
+ type ApiFanoutOptions,
45
+ type ApiSelectionSummary,
46
+ apiFanout,
47
+ buildApiFanoutErrorEnvelope,
48
+ renderApiFanoutEnvelope,
49
+ } from "./api-fanout.ts";
3
50
  export {
4
51
  BTEST_CLIENT_CSV_HEADER,
5
52
  BTEST_SERVER_CSV_HEADER,
@@ -41,6 +88,36 @@ export {
41
88
  type SettingSourceKind,
42
89
  type Warning,
43
90
  } from "./core/envelope.ts";
91
+ export {
92
+ buildFanoutEnvelope,
93
+ buildFanoutResolveFailure,
94
+ commonVia,
95
+ FANOUT_BACKOFF_BASE_MS,
96
+ FANOUT_CONCURRENCY_DEFAULTS,
97
+ FANOUT_MAX_RETRIES,
98
+ FANOUT_RETRYABLE_CODES,
99
+ fanoutExitCode,
100
+ isRetryableFanoutError,
101
+ resolveFanoutConcurrency,
102
+ runBoundedPool,
103
+ runFanout,
104
+ runWithRetry,
105
+ summarizeFanout,
106
+ } from "./core/fanout.ts";
107
+ export {
108
+ extractCompletionNames,
109
+ type InspectBackend,
110
+ type InspectChildItem,
111
+ type InspectCompletionItem,
112
+ type InspectRequestKind,
113
+ inspectChildren,
114
+ inspectChildrenOrEmpty,
115
+ inspectCompletions,
116
+ inspectPath,
117
+ isArgumentNode,
118
+ isCommandNode,
119
+ pathTokens,
120
+ } from "./core/inspect.ts";
44
121
  export {
45
122
  encodeMndpPacket,
46
123
  MNDP_BROADCAST_ADDRESS,
@@ -115,6 +192,7 @@ export {
115
192
  type DevicesShowEnvelopeData,
116
193
  type DevicesShowItem,
117
194
  type DevicesWarning,
195
+ type DevicesWhereClause,
118
196
  defaultCdbPath,
119
197
  devicesOutputFormats,
120
198
  editInteractiveOnlyError,
@@ -170,6 +248,7 @@ export {
170
248
  canonicalizeExecuteCommand,
171
249
  type ExecuteEnvelope,
172
250
  type ExecuteErrorEnvelope,
251
+ type ExecuteGlobalContext,
173
252
  type ExecuteOperationMeta,
174
253
  type ExecuteOutputFormat,
175
254
  type ExecuteRequest,
@@ -180,16 +259,32 @@ export {
180
259
  executeOutputFormats,
181
260
  isWriteShaped,
182
261
  renderExecuteEnvelope,
262
+ resolveExecuteGlobalContext,
183
263
  resolveExecuteRequest,
184
264
  runResolvedExecute,
185
265
  validateExecuteRequestShape,
186
266
  } from "./execute.ts";
187
267
  export {
268
+ buildExecuteFanoutErrorEnvelope,
269
+ type ExecuteFanoutData,
270
+ type ExecuteFanoutEnvelope,
271
+ type ExecuteFanoutErrorEnvelope,
272
+ type ExecuteFanoutInternals,
273
+ type ExecuteFanoutOperationMeta,
274
+ type ExecuteFanoutOptions,
275
+ type ExecuteSelectionSummary,
276
+ executeFanout,
277
+ renderExecuteFanoutEnvelope,
278
+ } from "./execute-fanout.ts";
279
+ export {
280
+ type ApiVerb,
188
281
  createProtocolAdapter,
189
282
  getProtocolPlan,
190
283
  type ProtocolAdapter,
191
284
  type ProtocolAdapterCapabilities,
192
285
  type ProtocolAdapterConfig,
286
+ type ProtocolApiRequest,
287
+ type ProtocolApiResult,
193
288
  type ProtocolCapability,
194
289
  type ProtocolExecuteRequest,
195
290
  type ProtocolExecuteResult,
@@ -201,27 +296,48 @@ export {
201
296
  type RouterOsProtocol,
202
297
  } from "./protocols/index.ts";
203
298
  export {
299
+ type AltitudeType,
204
300
  applyCommentKv,
301
+ type BboxPredicate,
205
302
  type CdbGroupExpansion,
206
303
  type CdbGroupResolveInput,
207
304
  type CdbGroupTarget,
208
305
  type CdbResolution,
209
306
  type CdbResolveInput,
307
+ type CdbSelectionExpansion,
308
+ type CdbSelectionMember,
309
+ type CdbSelectionResolveInput,
310
+ type CommentKvGeoKey,
210
311
  type CommentKvKey,
211
312
  type CommentKvLayer,
212
313
  type CommentKvOverrides,
213
314
  type CommentKvResult,
214
315
  type CommentKvUpdate,
215
316
  type CommentKvWarning,
317
+ canonicalizeGeoKey,
216
318
  coerceCommentKv,
217
319
  commentKvAllowlist,
320
+ commentKvGeoKeys,
218
321
  commentKvReservedKeys,
322
+ type DeviceLocation,
219
323
  defaultPortForScheme,
324
+ deviceLocation,
220
325
  expandCdbGroup,
326
+ expandCdbSelection,
327
+ isDefaultRecordTarget,
328
+ type NearPredicate,
329
+ type ParsedGpsTuple,
330
+ parseAltitude,
331
+ parseAltitudeType,
332
+ parseBbox,
221
333
  parseBoolean,
222
334
  parseCommentKv,
223
335
  parseDuration,
336
+ parseGpsTuple,
224
337
  parseHostCandidate,
338
+ parseLatLon,
339
+ parseNear,
340
+ parseRawCommentFacts,
225
341
  type ResolvedAuth,
226
342
  type ResolvedSetting,
227
343
  type ResolvedTarget,
@@ -235,7 +351,9 @@ export {
235
351
  resolveOptionalIntegerSetting,
236
352
  resolveStringSetting,
237
353
  resolveTarget,
354
+ type SelectionWhereClause,
238
355
  type TargetResolveInput,
356
+ type TargetSelection,
239
357
  toCoreSource,
240
358
  } from "./resolver/index.ts";
241
359
  export {
@@ -262,22 +380,47 @@ export {
262
380
  } from "./retrieve.ts";
263
381
  export {
264
382
  buildRetrieveFanoutErrorEnvelope,
265
- isRetryableFanoutError,
266
- RETRIEVE_FANOUT_BACKOFF_BASE_MS,
267
- RETRIEVE_FANOUT_CONCURRENCY_DEFAULTS,
268
- RETRIEVE_FANOUT_MAX_RETRIES,
269
- RETRIEVE_FANOUT_RETRYABLE_CODES,
270
383
  type RetrieveFanoutData,
271
384
  type RetrieveFanoutEnvelope,
272
385
  type RetrieveFanoutErrorEnvelope,
386
+ type RetrieveFanoutInternals,
273
387
  type RetrieveFanoutOperationMeta,
274
- type RetrieveGroupInternals,
388
+ type RetrieveFanoutOptions,
389
+ type RetrieveSelectionSummary,
275
390
  renderRetrieveFanoutEnvelope,
276
- resolveFanoutConcurrency,
391
+ retrieveFanout,
277
392
  retrieveGroup,
278
- runBoundedPool,
279
- summarizeFanout,
280
393
  } from "./retrieve-fanout.ts";
394
+ export {
395
+ buildSettingsErrorEnvelope,
396
+ renderSettingsEnvelope,
397
+ type SettingsCommand,
398
+ type SettingsDefaultDeviceSummary,
399
+ type SettingsEnvelope,
400
+ type SettingsErrorEnvelope,
401
+ type SettingsGetArgs,
402
+ type SettingsGetData,
403
+ type SettingsKeyDef,
404
+ type SettingsKeyEntry,
405
+ type SettingsOperationMeta,
406
+ type SettingsOutputFormat,
407
+ type SettingsPrintArgs,
408
+ type SettingsPrintData,
409
+ type SettingsResetArgs,
410
+ type SettingsResetData,
411
+ type SettingsSetArgs,
412
+ type SettingsSetData,
413
+ type SettingsSuccessEnvelope,
414
+ type SettingsUnrecognizedEntry,
415
+ type SettingsValue,
416
+ settingsGet,
417
+ settingsManagedKeys,
418
+ settingsOutputFormats,
419
+ settingsPrint,
420
+ settingsRefusedKeys,
421
+ settingsReset,
422
+ settingsSet,
423
+ } from "./settings.ts";
281
424
  export {
282
425
  buildSshTerminalArgv,
283
426
  buildTerminalErrorEnvelope,
@@ -290,6 +433,7 @@ export {
290
433
  export {
291
434
  buildTransferErrorEnvelope,
292
435
  FILE_READ_CHUNK_BYTES,
436
+ isTransferWriteVerb,
293
437
  normalizeRemotePath,
294
438
  REST_FILE_WRITE_CAP_BYTES,
295
439
  renderTransferEnvelope,
@@ -312,6 +456,19 @@ export {
312
456
  transferVerbs,
313
457
  validateTransferRequestShape,
314
458
  } from "./transfer.ts";
459
+ export {
460
+ buildTransferFanoutErrorEnvelope,
461
+ renderTransferFanoutEnvelope,
462
+ type TransferFanoutData,
463
+ type TransferFanoutEnvelope,
464
+ type TransferFanoutErrorEnvelope,
465
+ type TransferFanoutInternals,
466
+ type TransferFanoutOperationMeta,
467
+ type TransferFanoutOptions,
468
+ type TransferFanoutRequestSummary,
469
+ type TransferSelectionSummary,
470
+ transferFanout,
471
+ } from "./transfer-fanout.ts";
315
472
 
316
473
  import { plannedProtocols, type RouterOsProtocol } from "./protocols/index.ts";
317
474
 
package/src/mcp/config.ts CHANGED
@@ -46,16 +46,26 @@ export interface ResolveMcpConfigArgs {
46
46
  cdbPassword?: string;
47
47
  allowAdhocTargets?: boolean;
48
48
  env?: Record<string, string | undefined>;
49
+ /**
50
+ * The `centrs.env` config tier (`src/resolver/config-file.ts`), loaded by
51
+ * the caller — `resolveMcpConfig` itself stays synchronous so it can keep
52
+ * serving as a default-parameter value (`server.ts`). `CENTRS_CDB_PASSWORD`
53
+ * deliberately never reads it, same as `resolveAuth`'s credential fields.
54
+ */
55
+ config?: Record<string, string | undefined>;
49
56
  }
50
57
 
51
58
  export function resolveMcpConfig(
52
59
  args: ResolveMcpConfigArgs = {},
53
60
  ): CentrsMcpConfig {
54
61
  const env = args.env ?? Bun.env;
55
- const explicitPath = args.cdbFile ?? env[ENV_CDB_FILE] ?? undefined;
62
+ const config = args.config ?? {};
63
+ const explicitPath =
64
+ args.cdbFile ?? env[ENV_CDB_FILE] ?? config[ENV_CDB_FILE] ?? undefined;
56
65
  const cdbFile = explicitPath ?? defaultCdbPath(env);
57
66
  const allowAdhocTargets =
58
- args.allowAdhocTargets ?? env[ENV_ALLOW_ADHOC] === "1";
67
+ args.allowAdhocTargets ??
68
+ (env[ENV_ALLOW_ADHOC] ?? config[ENV_ALLOW_ADHOC]) === "1";
59
69
  return {
60
70
  cdbFile,
61
71
  cdbFileExplicit: explicitPath !== undefined,