@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.
Files changed (134) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/GLOSSARY.txt +284 -0
  3. package/LICENSE +21 -0
  4. package/README.md +55 -11
  5. package/SECURITY.md +3 -3
  6. package/commands/AGENTS.md +7 -1
  7. package/commands/api/README.md +46 -27
  8. package/commands/api/examples.md +124 -6
  9. package/commands/btest/README.md +21 -32
  10. package/commands/check/README.md +376 -53
  11. package/commands/check/examples.md +390 -0
  12. package/commands/devices/README.md +177 -37
  13. package/commands/devices/examples.md +148 -0
  14. package/commands/discover/README.md +5 -11
  15. package/commands/execute/README.md +73 -26
  16. package/commands/execute/examples.md +131 -9
  17. package/commands/explain/README.md +1716 -0
  18. package/commands/explain/examples.md +606 -0
  19. package/commands/retrieve/README.md +55 -71
  20. package/commands/retrieve/examples.md +56 -9
  21. package/commands/settings/README.md +286 -22
  22. package/commands/settings/examples.md +408 -0
  23. package/commands/terminal/README.md +6 -0
  24. package/commands/transfer/README.md +57 -19
  25. package/commands/transfer/examples.md +95 -0
  26. package/docs/CLI.md +353 -0
  27. package/docs/CONSTITUTION.md +102 -26
  28. package/docs/MATRIX.md +106 -173
  29. package/docs/errors/README.md +5 -1
  30. package/docs/errors/cdb/empty-selection.md +19 -0
  31. package/docs/errors/input/incomplete-gps.md +9 -0
  32. package/docs/errors/input/invalid-altitude.md +9 -0
  33. package/docs/errors/input/invalid-bbox.md +9 -0
  34. package/docs/errors/input/invalid-coordinate.md +9 -0
  35. package/docs/errors/input/invalid-radius.md +9 -0
  36. package/docs/errors/internal/settings-failed.md +9 -0
  37. package/docs/errors/quickchr/machine-not-found.md +22 -0
  38. package/docs/errors/quickchr/machine-stopped.md +20 -0
  39. package/docs/errors/quickchr/package-unavailable.md +27 -0
  40. package/docs/errors/quickchr/unsupported-via.md +35 -0
  41. package/docs/errors/quickchr/unsupported.md +35 -0
  42. package/docs/errors/routeros/version-unsupported.md +26 -0
  43. package/docs/errors/settings/consequential-value.md +26 -0
  44. package/docs/errors/settings/invalid-boolean.md +21 -3
  45. package/docs/errors/settings/invalid-format.md +18 -3
  46. package/docs/errors/settings/invalid-integer.md +18 -3
  47. package/docs/errors/settings/invalid-timeout.md +20 -3
  48. package/docs/errors/settings/invalid-via.md +22 -3
  49. package/docs/errors/settings/reserved-key.md +29 -0
  50. package/docs/errors/settings/skip-env-file-active.md +25 -0
  51. package/docs/errors/settings/unknown-key.md +25 -0
  52. package/docs/errors/transport/provider-trust.md +29 -0
  53. package/docs/errors/usage/fanout-not-supported.md +16 -0
  54. package/docs/errors/usage/stdin-ignored.md +28 -0
  55. package/docs/errors/validation/unknown-attribute.md +37 -4
  56. package/package.json +49 -14
  57. package/src/api-fanout.ts +469 -0
  58. package/src/api.ts +171 -56
  59. package/src/cli/api.ts +156 -13
  60. package/src/cli/btest.ts +38 -4
  61. package/src/cli/common.ts +11 -0
  62. package/src/cli/devices.ts +235 -6
  63. package/src/cli/discover.ts +3 -1
  64. package/src/cli/execute.ts +129 -9
  65. package/src/cli/explain.ts +503 -0
  66. package/src/cli/mcp.ts +8 -1
  67. package/src/cli/retrieve.ts +167 -90
  68. package/src/cli/selection.ts +260 -0
  69. package/src/cli/settings.ts +286 -0
  70. package/src/cli/terminal.ts +22 -7
  71. package/src/cli/transfer.ts +192 -51
  72. package/src/cli.ts +22 -2
  73. package/src/core/envelope.ts +5 -2
  74. package/src/core/error-catalog.ts +95 -0
  75. package/src/core/fanout.ts +143 -2
  76. package/src/core/preflight.ts +306 -0
  77. package/src/core/routeros-string.ts +24 -0
  78. package/src/devices.ts +212 -20
  79. package/src/discover.ts +7 -2
  80. package/src/errors.ts +1 -0
  81. package/src/execute-fanout.ts +453 -0
  82. package/src/execute.ts +216 -68
  83. package/src/explain/arg-tokens.ts +114 -0
  84. package/src/explain/args.ts +1403 -0
  85. package/src/explain/blocks.ts +132 -0
  86. package/src/explain/brace-slots.ts +143 -0
  87. package/src/explain/brace-tokens.ts +38 -0
  88. package/src/explain/catalog.ts +1345 -0
  89. package/src/explain/coordinates.ts +329 -0
  90. package/src/explain/defects.ts +217 -0
  91. package/src/explain/is-known-menu.ts +17 -0
  92. package/src/explain/menus.ts +664 -0
  93. package/src/explain/operator-tokens.ts +310 -0
  94. package/src/explain/operators.ts +458 -0
  95. package/src/explain/path-tokens.ts +150 -0
  96. package/src/explain/pathresolve.ts +1324 -0
  97. package/src/explain/quoted-string.ts +241 -0
  98. package/src/explain/scope-brace.ts +308 -0
  99. package/src/explain/segment.ts +757 -0
  100. package/src/explain/string-tokens.ts +67 -0
  101. package/src/explain/symbol-values.ts +520 -0
  102. package/src/explain/symbols.ts +1597 -0
  103. package/src/explain/token-ranges.ts +72 -0
  104. package/src/explain/transport.ts +497 -0
  105. package/src/explain/value-tokens.ts +80 -0
  106. package/src/explain/values.ts +357 -0
  107. package/src/explain/verbs.ts +64 -0
  108. package/src/explain/verbsplit.ts +813 -0
  109. package/src/explain/write.ts +815 -0
  110. package/src/explain.ts +2152 -0
  111. package/src/index.ts +307 -1
  112. package/src/mcp/config.ts +12 -2
  113. package/src/mcp/tools.ts +137 -2
  114. package/src/protocols/adapter.ts +38 -18
  115. package/src/protocols/index.ts +11 -5
  116. package/src/protocols/mac-telnet-console.ts +61 -17
  117. package/src/resolver/cdb.ts +10 -2
  118. package/src/resolver/comment-kv.ts +58 -4
  119. package/src/resolver/config-file.ts +136 -0
  120. package/src/resolver/facts.ts +78 -0
  121. package/src/resolver/geo.ts +453 -0
  122. package/src/resolver/index.ts +65 -0
  123. package/src/resolver/mac.ts +12 -3
  124. package/src/resolver/quickchr-provider.ts +597 -0
  125. package/src/resolver/selection.ts +391 -0
  126. package/src/resolver/service-endpoint.ts +155 -0
  127. package/src/resolver/settings.ts +54 -5
  128. package/src/resolver/target.ts +21 -3
  129. package/src/retrieve-fanout.ts +239 -133
  130. package/src/retrieve.ts +120 -25
  131. package/src/settings.ts +1078 -0
  132. package/src/terminal.ts +11 -1
  133. package/src/transfer-fanout.ts +544 -0
  134. package/src/transfer.ts +133 -40
package/src/index.ts CHANGED
@@ -17,6 +17,7 @@ export {
17
17
  apiListen,
18
18
  apiMethods,
19
19
  apiOutputFormats,
20
+ apiRequestSummaryFromRequest,
20
21
  buildApiBody,
21
22
  buildApiErrorEnvelope,
22
23
  buildApiErrorEnvelopeFromResolved,
@@ -26,6 +27,7 @@ export {
26
27
  mapMethodToVerb,
27
28
  type NormalizedApiEndpoint,
28
29
  normalizeApiEndpoint,
30
+ parseApiMethod,
29
31
  type ResolvedApiRequest,
30
32
  renderApiEnvelope,
31
33
  renderApiStreamLine,
@@ -33,6 +35,18 @@ export {
33
35
  runResolvedApi,
34
36
  validateApiRequestShape,
35
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";
36
50
  export {
37
51
  BTEST_CLIENT_CSV_HEADER,
38
52
  BTEST_SERVER_CSV_HEADER,
@@ -75,13 +89,18 @@ export {
75
89
  type Warning,
76
90
  } from "./core/envelope.ts";
77
91
  export {
92
+ buildFanoutEnvelope,
93
+ buildFanoutResolveFailure,
94
+ commonVia,
78
95
  FANOUT_BACKOFF_BASE_MS,
79
96
  FANOUT_CONCURRENCY_DEFAULTS,
80
97
  FANOUT_MAX_RETRIES,
81
98
  FANOUT_RETRYABLE_CODES,
99
+ fanoutExitCode,
82
100
  isRetryableFanoutError,
83
101
  resolveFanoutConcurrency,
84
102
  runBoundedPool,
103
+ runFanout,
85
104
  runWithRetry,
86
105
  summarizeFanout,
87
106
  } from "./core/fanout.ts";
@@ -99,6 +118,24 @@ export {
99
118
  isCommandNode,
100
119
  pathTokens,
101
120
  } from "./core/inspect.ts";
121
+ export {
122
+ assertCapability,
123
+ type CapabilityRequirement,
124
+ checkCapability,
125
+ compareRouterOsVersion,
126
+ type DeviceVersion,
127
+ type DeviceVersionSource,
128
+ type PreflightState,
129
+ type PreflightVerdict,
130
+ parseRouterOsVersion,
131
+ probeDeviceVersion,
132
+ type RouterOsVersionParts,
133
+ type RouterOsVersionRange,
134
+ renderSupportedRanges,
135
+ routerOsVersionAtLeast,
136
+ versionFactFromComment,
137
+ versionInRange,
138
+ } from "./core/preflight.ts";
102
139
  export {
103
140
  encodeMndpPacket,
104
141
  MNDP_BROADCAST_ADDRESS,
@@ -173,6 +210,7 @@ export {
173
210
  type DevicesShowEnvelopeData,
174
211
  type DevicesShowItem,
175
212
  type DevicesWarning,
213
+ type DevicesWhereClause,
176
214
  defaultCdbPath,
177
215
  devicesOutputFormats,
178
216
  editInteractiveOnlyError,
@@ -228,6 +266,7 @@ export {
228
266
  canonicalizeExecuteCommand,
229
267
  type ExecuteEnvelope,
230
268
  type ExecuteErrorEnvelope,
269
+ type ExecuteGlobalContext,
231
270
  type ExecuteOperationMeta,
232
271
  type ExecuteOutputFormat,
233
272
  type ExecuteRequest,
@@ -238,10 +277,207 @@ export {
238
277
  executeOutputFormats,
239
278
  isWriteShaped,
240
279
  renderExecuteEnvelope,
280
+ resolveExecuteGlobalContext,
241
281
  resolveExecuteRequest,
242
282
  runResolvedExecute,
243
283
  validateExecuteRequestShape,
244
284
  } from "./execute.ts";
285
+ export {
286
+ buildExecuteFanoutErrorEnvelope,
287
+ type ExecuteFanoutData,
288
+ type ExecuteFanoutEnvelope,
289
+ type ExecuteFanoutErrorEnvelope,
290
+ type ExecuteFanoutInternals,
291
+ type ExecuteFanoutOperationMeta,
292
+ type ExecuteFanoutOptions,
293
+ type ExecuteSelectionSummary,
294
+ executeFanout,
295
+ renderExecuteFanoutEnvelope,
296
+ } from "./execute-fanout.ts";
297
+ // The lexer's own names are module-scoped and read naturally there; at the
298
+ // package root they need the `Explain` prefix every other explain export
299
+ // carries — a bare `Argument` says nothing about which of centrs's surfaces it
300
+ // belongs to. Aliasing at the re-export is the existing pattern here (see
301
+ // `occurrences as explainOccurrences` below).
302
+ //
303
+ // `ExplainArgument` and `ExplainArgumentToken` are the same SHAPE in two
304
+ // coordinate spaces: this one is what `lexArguments` returns, with spans
305
+ // relative to the statement it was handed; the `Token` is that rebased into
306
+ // document space by `explainCommand`. A consumer reading `explainCommand`'s
307
+ // result wants the latter.
308
+ export {
309
+ type Argument as ExplainArgument,
310
+ type ArgumentKind as ExplainArgumentKind,
311
+ type ArgumentReading as ExplainArgumentReading,
312
+ type ArgumentsRead as ExplainArgumentsRead,
313
+ type ArgumentsUnread as ExplainArgumentsUnread,
314
+ lexArguments as lexExplainArguments,
315
+ lexValueAnchors as lexExplainValueAnchors,
316
+ type ValueAnchor as ExplainValueAnchor,
317
+ type ValueAnchorKind as ExplainValueAnchorKind,
318
+ type ValueAnchorReading as ExplainValueAnchorReading,
319
+ } from "./explain/args.ts";
320
+ export {
321
+ DIRECTIVE_BODY,
322
+ HEAD_SCOPED_ARG_NAMES,
323
+ isScopeBrace,
324
+ matchBrace,
325
+ SCOPE_ARG_NAMES,
326
+ type ScopeBlock,
327
+ scopeBlocks,
328
+ scopeBodies,
329
+ scopeNameAt,
330
+ } from "./explain/blocks.ts";
331
+ export {
332
+ analyzeCoordinates,
333
+ byteSpanToRange,
334
+ byteToPosition,
335
+ type CharRun,
336
+ type CoordinateAnalysis,
337
+ coordinateDefects,
338
+ type Position,
339
+ positionToByte,
340
+ type Range,
341
+ runAtByte,
342
+ SUB,
343
+ } from "./explain/coordinates.ts";
344
+ export {
345
+ type Defect,
346
+ type DefectCode,
347
+ defectAt,
348
+ hasStructuralDefect,
349
+ isPositionalFact,
350
+ mergeDefects,
351
+ rebaseDefects,
352
+ } from "./explain/defects.ts";
353
+ export {
354
+ type DocumentAnalysis,
355
+ type Resolution,
356
+ resolveDocument,
357
+ resolveStatements,
358
+ type Span,
359
+ type StatementAnalysis,
360
+ type StatementResolution,
361
+ } from "./explain/pathresolve.ts";
362
+ export {
363
+ maskComments,
364
+ type QuotedStringScan,
365
+ type Segment,
366
+ type SegmentResult,
367
+ scanQuotedString,
368
+ segmentStatements,
369
+ } from "./explain/segment.ts";
370
+ export {
371
+ HIGHLIGHT_CLASS,
372
+ resolveSymbols,
373
+ type SymbolAnalysis,
374
+ type SymbolClass,
375
+ type SymbolOccurrence,
376
+ type SymbolRole,
377
+ } from "./explain/symbols.ts";
378
+ export type {
379
+ ExplainApiCandidateTransport,
380
+ ExplainExecuteTransport,
381
+ ExplainRestMethod,
382
+ ExplainRestRequest,
383
+ ExplainTransport,
384
+ ExplainTransportClassification,
385
+ ExplainUnknownTransport,
386
+ } from "./explain/transport.ts";
387
+ export {
388
+ VALUE_SHAPES,
389
+ type ValueContext as ExplainValueContext,
390
+ type ValueShape as ExplainValueShape,
391
+ type ValueShapeOptions as ExplainValueShapeOptions,
392
+ valueShapeHints as explainValueShapeHints,
393
+ } from "./explain/values.ts";
394
+ export {
395
+ type DocumentVerbSplit,
396
+ describeStatement,
397
+ isDirective,
398
+ type RunToken,
399
+ resolveVerb,
400
+ resolveVerbs,
401
+ runTokens,
402
+ type Sep,
403
+ type Split,
404
+ SUBMENU_DIRECTIVES,
405
+ splitRun,
406
+ VERBS,
407
+ type VerbAnalysis,
408
+ type VerbResolution,
409
+ type VerbSplit,
410
+ type VerbSplitCommandReading,
411
+ type VerbSplitMenuReading,
412
+ type VerbSplitRefusal,
413
+ } from "./explain/verbsplit.ts";
414
+ export {
415
+ classifyVerb,
416
+ containsWrite,
417
+ type Occurrence,
418
+ type OccurrenceClass,
419
+ // `occurrences` is too generic for the library root; the module-local name
420
+ // stays short because inside `write.ts` there is only one kind.
421
+ occurrences as explainOccurrences,
422
+ READ_VERBS,
423
+ ROOT_CMDS,
424
+ ROOT_DYNAMIC,
425
+ ROOT_WRITE,
426
+ WRITE_VERBS,
427
+ type WriteAnalysis,
428
+ type WriteVerdict,
429
+ } from "./explain/write.ts";
430
+ export {
431
+ buildExplainErrorEnvelope,
432
+ buildExplainFacetTip,
433
+ type ExplainArguments,
434
+ type ExplainArgumentToken,
435
+ type ExplainBlock,
436
+ type ExplainCanonical,
437
+ type ExplainCommandOptions,
438
+ type ExplainCommandReading,
439
+ type ExplainData,
440
+ type ExplainDiagnostic,
441
+ type ExplainEnvelope,
442
+ type ExplainEnvelopeOptions,
443
+ type ExplainErrorEnvelope,
444
+ type ExplainEvidence,
445
+ type ExplainFailOn,
446
+ type ExplainInput,
447
+ type ExplainMenuReading,
448
+ type ExplainOperationMeta,
449
+ type ExplainOutputFormat,
450
+ type ExplainPositionMapEntry,
451
+ type ExplainReading,
452
+ type ExplainRefusal,
453
+ type ExplainResolution,
454
+ type ExplainSeverity,
455
+ type ExplainSpan,
456
+ type ExplainSpanClass,
457
+ type ExplainSpanRange,
458
+ type ExplainStatement,
459
+ type ExplainStatementFacts,
460
+ type ExplainStructure,
461
+ type ExplainSubcommand,
462
+ type ExplainSubcommandFacts,
463
+ type ExplainSymbolClass,
464
+ type ExplainSymbolOccurrence,
465
+ type ExplainSymbols,
466
+ type ExplainToken,
467
+ type ExplainTokenClass,
468
+ type ExplainValueFacts,
469
+ type ExplainValueOccurrence,
470
+ type ExplainValueShapeFact,
471
+ type ExplainValues,
472
+ type ExplainValueTypeFact,
473
+ type ExplainVerdict,
474
+ explainCommand,
475
+ explainEnvelope,
476
+ explainExitCode,
477
+ explainFailOnLevels,
478
+ explainOutputFormats,
479
+ renderExplainEnvelope,
480
+ } from "./explain.ts";
245
481
  export {
246
482
  type ApiVerb,
247
483
  createProtocolAdapter,
@@ -262,27 +498,45 @@ export {
262
498
  type RouterOsProtocol,
263
499
  } from "./protocols/index.ts";
264
500
  export {
501
+ type AltitudeType,
265
502
  applyCommentKv,
503
+ type BboxPredicate,
266
504
  type CdbGroupExpansion,
267
505
  type CdbGroupResolveInput,
268
506
  type CdbGroupTarget,
269
507
  type CdbResolution,
270
508
  type CdbResolveInput,
509
+ type CommentKvGeoKey,
271
510
  type CommentKvKey,
272
511
  type CommentKvLayer,
273
512
  type CommentKvOverrides,
274
513
  type CommentKvResult,
275
514
  type CommentKvUpdate,
276
515
  type CommentKvWarning,
516
+ canonicalizeGeoKey,
277
517
  coerceCommentKv,
278
518
  commentKvAllowlist,
519
+ commentKvGeoKeys,
279
520
  commentKvReservedKeys,
521
+ type DeviceLocation,
280
522
  defaultPortForScheme,
523
+ deviceLocation,
281
524
  expandCdbGroup,
525
+ expandSelection,
526
+ isDefaultRecordTarget,
527
+ type NearPredicate,
528
+ type ParsedGpsTuple,
529
+ parseAltitude,
530
+ parseAltitudeType,
531
+ parseBbox,
282
532
  parseBoolean,
283
533
  parseCommentKv,
284
534
  parseDuration,
535
+ parseGpsTuple,
285
536
  parseHostCandidate,
537
+ parseLatLon,
538
+ parseNear,
539
+ parseRawCommentFacts,
286
540
  type ResolvedAuth,
287
541
  type ResolvedSetting,
288
542
  type ResolvedTarget,
@@ -296,7 +550,12 @@ export {
296
550
  resolveOptionalIntegerSetting,
297
551
  resolveStringSetting,
298
552
  resolveTarget,
553
+ type SelectionExpansion,
554
+ type SelectionMember,
555
+ type SelectionResolveInput,
556
+ type SelectionWhereClause,
299
557
  type TargetResolveInput,
558
+ type TargetSelection,
300
559
  toCoreSource,
301
560
  } from "./resolver/index.ts";
302
561
  export {
@@ -326,11 +585,44 @@ export {
326
585
  type RetrieveFanoutData,
327
586
  type RetrieveFanoutEnvelope,
328
587
  type RetrieveFanoutErrorEnvelope,
588
+ type RetrieveFanoutInternals,
329
589
  type RetrieveFanoutOperationMeta,
330
- type RetrieveGroupInternals,
590
+ type RetrieveFanoutOptions,
591
+ type RetrieveSelectionSummary,
331
592
  renderRetrieveFanoutEnvelope,
593
+ retrieveFanout,
332
594
  retrieveGroup,
333
595
  } from "./retrieve-fanout.ts";
596
+ export {
597
+ buildSettingsErrorEnvelope,
598
+ renderSettingsEnvelope,
599
+ type SettingsCommand,
600
+ type SettingsDefaultDeviceSummary,
601
+ type SettingsEnvelope,
602
+ type SettingsErrorEnvelope,
603
+ type SettingsGetArgs,
604
+ type SettingsGetData,
605
+ type SettingsKeyDef,
606
+ type SettingsKeyEntry,
607
+ type SettingsOperationMeta,
608
+ type SettingsOutputFormat,
609
+ type SettingsPrintArgs,
610
+ type SettingsPrintData,
611
+ type SettingsResetArgs,
612
+ type SettingsResetData,
613
+ type SettingsSetArgs,
614
+ type SettingsSetData,
615
+ type SettingsSuccessEnvelope,
616
+ type SettingsUnrecognizedEntry,
617
+ type SettingsValue,
618
+ settingsGet,
619
+ settingsManagedKeys,
620
+ settingsOutputFormats,
621
+ settingsPrint,
622
+ settingsRefusedKeys,
623
+ settingsReset,
624
+ settingsSet,
625
+ } from "./settings.ts";
334
626
  export {
335
627
  buildSshTerminalArgv,
336
628
  buildTerminalErrorEnvelope,
@@ -343,6 +635,7 @@ export {
343
635
  export {
344
636
  buildTransferErrorEnvelope,
345
637
  FILE_READ_CHUNK_BYTES,
638
+ isTransferWriteVerb,
346
639
  normalizeRemotePath,
347
640
  REST_FILE_WRITE_CAP_BYTES,
348
641
  renderTransferEnvelope,
@@ -365,6 +658,19 @@ export {
365
658
  transferVerbs,
366
659
  validateTransferRequestShape,
367
660
  } from "./transfer.ts";
661
+ export {
662
+ buildTransferFanoutErrorEnvelope,
663
+ renderTransferFanoutEnvelope,
664
+ type TransferFanoutData,
665
+ type TransferFanoutEnvelope,
666
+ type TransferFanoutErrorEnvelope,
667
+ type TransferFanoutInternals,
668
+ type TransferFanoutOperationMeta,
669
+ type TransferFanoutOptions,
670
+ type TransferFanoutRequestSummary,
671
+ type TransferSelectionSummary,
672
+ transferFanout,
673
+ } from "./transfer-fanout.ts";
368
674
 
369
675
  import { plannedProtocols, type RouterOsProtocol } from "./protocols/index.ts";
370
676
 
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,
package/src/mcp/tools.ts CHANGED
@@ -33,6 +33,8 @@ import {
33
33
  isWriteShaped,
34
34
  validateExecuteEnvelope,
35
35
  } from "../execute.ts";
36
+ import type { CommentKvUpdate } from "../resolver/comment-kv.ts";
37
+ import { parseBbox, parseNear } from "../resolver/geo.ts";
36
38
  import { parseDuration } from "../resolver/settings.ts";
37
39
  import { buildRetrieveErrorEnvelope, retrieve } from "../retrieve.ts";
38
40
  import {
@@ -416,6 +418,24 @@ export const devicesInputShape = {
416
418
  .describe(
417
419
  "Group filter for op=list; first-class group field for op=add/set.",
418
420
  ),
421
+ where: z
422
+ .array(z.string())
423
+ .optional()
424
+ .describe(
425
+ "op=list device-class filters `attr=value` (repeatable, AND-combined; e.g. board=RB5009 or lat=37.7749).",
426
+ ),
427
+ near: z
428
+ .string()
429
+ .optional()
430
+ .describe(
431
+ "op=list geo filter `<lat>,<lon>,<radius>` (lat-first; radius m/km/mi/ft, bare = km).",
432
+ ),
433
+ bbox: z
434
+ .string()
435
+ .optional()
436
+ .describe(
437
+ "op=list geo filter `<south>,<west>,<north>,<east>` (lat-first bounding box).",
438
+ ),
419
439
  user: z.string().optional().describe("CDB username for op=add/set."),
420
440
  password: z
421
441
  .string()
@@ -454,6 +474,30 @@ export const devicesInputShape = {
454
474
  .boolean()
455
475
  .optional()
456
476
  .describe("For op=set, reject comment keys outside the allowlist."),
477
+ lat: z
478
+ .number()
479
+ .optional()
480
+ .describe(
481
+ "Latitude in decimal degrees (-90..90) for op=add/set; a comment-kv fact, paired with lon (issue #146).",
482
+ ),
483
+ lon: z
484
+ .number()
485
+ .optional()
486
+ .describe(
487
+ "Longitude in decimal degrees (-180..180) for op=add/set; a comment-kv fact, paired with lat.",
488
+ ),
489
+ altitude: z
490
+ .number()
491
+ .optional()
492
+ .describe(
493
+ "Altitude in meters for op=add/set (may be negative); a comment-kv fact.",
494
+ ),
495
+ altitudeType: z
496
+ .enum(["MSL", "AGL"])
497
+ .optional()
498
+ .describe(
499
+ "Vertical datum for altitude, op=add/set (default MSL when altitude is set).",
500
+ ),
457
501
  confirm: z
458
502
  .boolean()
459
503
  .optional()
@@ -478,6 +522,56 @@ function requireDevicesTarget(
478
522
  });
479
523
  }
480
524
 
525
+ /**
526
+ * Lower the typed `lat`/`lon`/`altitude`/`altitudeType` MCP args into comment-kv
527
+ * updates — the same core surface the CLI's `--lat`/`--lon`/`--gps` flags lower
528
+ * into (`src/cli/devices.ts`). Light touch: no alias handling here (the MCP
529
+ * schema only exposes the canonical field names), range/enum/pairing
530
+ * validation happens once in `validateCommentKvUpdates` (`src/devices.ts`).
531
+ */
532
+ function buildGeoUpdates(args: DevicesArgs): CommentKvUpdate[] {
533
+ const updates: CommentKvUpdate[] = [];
534
+ if (args.lat !== undefined) {
535
+ updates.push({ key: "lat", value: String(args.lat) });
536
+ }
537
+ if (args.lon !== undefined) {
538
+ updates.push({ key: "lon", value: String(args.lon) });
539
+ }
540
+ if (args.altitude !== undefined) {
541
+ updates.push({ key: "altitude", value: String(args.altitude) });
542
+ }
543
+ if (args.altitudeType !== undefined) {
544
+ updates.push({ key: "altitude-type", value: args.altitudeType });
545
+ }
546
+ return updates;
547
+ }
548
+
549
+ /**
550
+ * Parse the MCP `where` array (`["attr=value", …]`) into device-class clauses
551
+ * for op=list. Geo-alias canonicalization happens centrally in `matchesWhere`
552
+ * (`src/resolver/facts.ts`), so this only splits on the first `=`.
553
+ */
554
+ function parseMcpWhereClauses(
555
+ where: readonly string[] | undefined,
556
+ ): { key: string; value: string }[] | undefined {
557
+ if (where === undefined) {
558
+ return undefined;
559
+ }
560
+ return where.map((clause) => {
561
+ const eq = clause.indexOf("=");
562
+ if (eq <= 0) {
563
+ throw new CentrsError({
564
+ code: "input/invalid-command",
565
+ summary: `where clause must be \`attr=value\`; received: ${clause}`,
566
+ remediation:
567
+ "Pass device-class filters like `board=RB5009` or `lat=37.7749`.",
568
+ context: { where: clause },
569
+ });
570
+ }
571
+ return { key: clause.slice(0, eq), value: clause.slice(eq + 1) };
572
+ });
573
+ }
574
+
481
575
  function resolveRecordType(recordType: string | undefined): number | undefined {
482
576
  if (recordType === undefined) {
483
577
  return undefined;
@@ -502,9 +596,49 @@ export async function handleDevices(
502
596
  ): Promise<McpEnvelope> {
503
597
  try {
504
598
  const cdb = await loadAllowlistCdb(config);
599
+ // GPS *setter* fields write via add/set only; the *query* selectors
600
+ // (where/near/bbox) select via list only. Reject the wrong-op combination
601
+ // instead of silently dropping the caller's input (mirrors the CLI's
602
+ // per-subcommand flag guard in src/cli/devices.ts).
603
+ if (
604
+ args.op !== "add" &&
605
+ args.op !== "set" &&
606
+ (args.lat !== undefined ||
607
+ args.lon !== undefined ||
608
+ args.altitude !== undefined ||
609
+ args.altitudeType !== undefined)
610
+ ) {
611
+ throw new CentrsError({
612
+ code: "input/invalid-command",
613
+ summary: `GPS fields (lat/lon/altitude/altitudeType) are not valid for op="${args.op}".`,
614
+ remediation:
615
+ "Store GPS with op=add or op=set; drop the GPS fields for list/show/groups/edit/remove.",
616
+ context: { op: args.op },
617
+ });
618
+ }
619
+ if (
620
+ args.op !== "list" &&
621
+ (args.where !== undefined ||
622
+ args.near !== undefined ||
623
+ args.bbox !== undefined)
624
+ ) {
625
+ throw new CentrsError({
626
+ code: "input/invalid-command",
627
+ summary: `Query selectors (where/near/bbox) are only valid for op="list", not op="${args.op}".`,
628
+ remediation:
629
+ "Use op=list with where/near/bbox to query the registry; for op=show name a target directly.",
630
+ context: { op: args.op },
631
+ });
632
+ }
505
633
  switch (args.op) {
506
634
  case "list":
507
- return listDevices({ cdb, group: args.group });
635
+ return listDevices({
636
+ cdb,
637
+ group: args.group,
638
+ where: parseMcpWhereClauses(args.where),
639
+ near: args.near !== undefined ? parseNear(args.near) : undefined,
640
+ bbox: args.bbox !== undefined ? parseBbox(args.bbox) : undefined,
641
+ });
508
642
  case "groups":
509
643
  return listGroups({ cdb, withMembers: true });
510
644
  case "show": {
@@ -527,6 +661,7 @@ export async function handleDevices(
527
661
  profile: args.profile,
528
662
  session: args.session,
529
663
  comment: args.comment,
664
+ commentKvUpdates: buildGeoUpdates(args),
530
665
  savedPassword: args.savedPassword,
531
666
  force: args.force,
532
667
  }),
@@ -542,7 +677,7 @@ export async function handleDevices(
542
677
  await setDevice({
543
678
  cdb,
544
679
  target,
545
- updates: args.updates,
680
+ updates: [...buildGeoUpdates(args), ...(args.updates ?? [])],
546
681
  user: args.user,
547
682
  password: args.password,
548
683
  group: args.group,