@tikoci/centrs 0.1.3 → 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 (90) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/GLOSSARY.txt +213 -0
  3. package/LICENSE +21 -0
  4. package/README.md +36 -8
  5. package/commands/AGENTS.md +7 -1
  6. package/commands/api/README.md +19 -18
  7. package/commands/api/examples.md +15 -0
  8. package/commands/btest/README.md +21 -32
  9. package/commands/check/README.md +6 -1
  10. package/commands/discover/README.md +5 -11
  11. package/commands/execute/README.md +41 -29
  12. package/commands/execute/examples.md +41 -3
  13. package/commands/explain/README.md +1716 -0
  14. package/commands/explain/examples.md +606 -0
  15. package/commands/retrieve/README.md +25 -27
  16. package/commands/retrieve/examples.md +16 -0
  17. package/commands/transfer/README.md +28 -20
  18. package/commands/transfer/examples.md +29 -0
  19. package/docs/CLI.md +353 -0
  20. package/docs/CONSTITUTION.md +66 -15
  21. package/docs/MATRIX.md +38 -1
  22. package/docs/errors/README.md +3 -0
  23. package/docs/errors/quickchr/machine-not-found.md +22 -0
  24. package/docs/errors/quickchr/machine-stopped.md +20 -0
  25. package/docs/errors/quickchr/package-unavailable.md +27 -0
  26. package/docs/errors/quickchr/unsupported-via.md +35 -0
  27. package/docs/errors/quickchr/unsupported.md +35 -0
  28. package/docs/errors/routeros/version-unsupported.md +26 -0
  29. package/docs/errors/transport/provider-trust.md +29 -0
  30. package/docs/errors/usage/stdin-ignored.md +28 -0
  31. package/docs/errors/validation/unknown-attribute.md +37 -4
  32. package/package.json +45 -10
  33. package/src/api-fanout.ts +29 -14
  34. package/src/api.ts +97 -54
  35. package/src/cli/api.ts +25 -6
  36. package/src/cli/btest.ts +18 -2
  37. package/src/cli/execute.ts +25 -6
  38. package/src/cli/explain.ts +503 -0
  39. package/src/cli/retrieve.ts +33 -1
  40. package/src/cli/selection.ts +71 -11
  41. package/src/cli/settings.ts +2 -2
  42. package/src/cli/transfer.ts +23 -6
  43. package/src/cli.ts +15 -2
  44. package/src/core/envelope.ts +5 -2
  45. package/src/core/error-catalog.ts +40 -0
  46. package/src/core/preflight.ts +306 -0
  47. package/src/core/routeros-string.ts +24 -0
  48. package/src/errors.ts +1 -0
  49. package/src/execute-fanout.ts +29 -14
  50. package/src/execute.ts +114 -63
  51. package/src/explain/arg-tokens.ts +114 -0
  52. package/src/explain/args.ts +1403 -0
  53. package/src/explain/blocks.ts +132 -0
  54. package/src/explain/brace-slots.ts +143 -0
  55. package/src/explain/brace-tokens.ts +38 -0
  56. package/src/explain/catalog.ts +1345 -0
  57. package/src/explain/coordinates.ts +329 -0
  58. package/src/explain/defects.ts +217 -0
  59. package/src/explain/is-known-menu.ts +17 -0
  60. package/src/explain/menus.ts +664 -0
  61. package/src/explain/operator-tokens.ts +310 -0
  62. package/src/explain/operators.ts +458 -0
  63. package/src/explain/path-tokens.ts +150 -0
  64. package/src/explain/pathresolve.ts +1324 -0
  65. package/src/explain/quoted-string.ts +241 -0
  66. package/src/explain/scope-brace.ts +308 -0
  67. package/src/explain/segment.ts +757 -0
  68. package/src/explain/string-tokens.ts +67 -0
  69. package/src/explain/symbol-values.ts +520 -0
  70. package/src/explain/symbols.ts +1597 -0
  71. package/src/explain/token-ranges.ts +72 -0
  72. package/src/explain/transport.ts +497 -0
  73. package/src/explain/value-tokens.ts +80 -0
  74. package/src/explain/values.ts +357 -0
  75. package/src/explain/verbs.ts +64 -0
  76. package/src/explain/verbsplit.ts +813 -0
  77. package/src/explain/write.ts +815 -0
  78. package/src/explain.ts +2152 -0
  79. package/src/index.ts +206 -4
  80. package/src/protocols/mac-telnet-console.ts +61 -17
  81. package/src/resolver/facts.ts +1 -1
  82. package/src/resolver/index.ts +23 -4
  83. package/src/resolver/quickchr-provider.ts +597 -0
  84. package/src/resolver/selection.ts +99 -12
  85. package/src/resolver/service-endpoint.ts +155 -0
  86. package/src/resolver/target.ts +1 -1
  87. package/src/retrieve-fanout.ts +26 -12
  88. package/src/retrieve.ts +81 -18
  89. package/src/transfer-fanout.ts +42 -25
  90. package/src/transfer.ts +87 -44
package/src/index.ts CHANGED
@@ -118,6 +118,24 @@ export {
118
118
  isCommandNode,
119
119
  pathTokens,
120
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";
121
139
  export {
122
140
  encodeMndpPacket,
123
141
  MNDP_BROADCAST_ADDRESS,
@@ -276,6 +294,190 @@ export {
276
294
  executeFanout,
277
295
  renderExecuteFanoutEnvelope,
278
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";
279
481
  export {
280
482
  type ApiVerb,
281
483
  createProtocolAdapter,
@@ -304,9 +506,6 @@ export {
304
506
  type CdbGroupTarget,
305
507
  type CdbResolution,
306
508
  type CdbResolveInput,
307
- type CdbSelectionExpansion,
308
- type CdbSelectionMember,
309
- type CdbSelectionResolveInput,
310
509
  type CommentKvGeoKey,
311
510
  type CommentKvKey,
312
511
  type CommentKvLayer,
@@ -323,7 +522,7 @@ export {
323
522
  defaultPortForScheme,
324
523
  deviceLocation,
325
524
  expandCdbGroup,
326
- expandCdbSelection,
525
+ expandSelection,
327
526
  isDefaultRecordTarget,
328
527
  type NearPredicate,
329
528
  type ParsedGpsTuple,
@@ -351,6 +550,9 @@ export {
351
550
  resolveOptionalIntegerSetting,
352
551
  resolveStringSetting,
353
552
  resolveTarget,
553
+ type SelectionExpansion,
554
+ type SelectionMember,
555
+ type SelectionResolveInput,
354
556
  type SelectionWhereClause,
355
557
  type TargetResolveInput,
356
558
  type TargetSelection,
@@ -39,10 +39,11 @@
39
39
  * scraped for: `(evl …)` = ok, `syntax error (line …)` = malformed, and
40
40
  * `bad parameter <name> (line …)` = unknown attribute. A single console
41
41
  * `:parse` therefore covers both the syntax and the unknown-attribute
42
- * (semantic) gate — no `/console/inspect` table parsing needed.
42
+ * (name-level) gate — no `/console/inspect` table parsing needed.
43
43
  */
44
44
 
45
45
  import { parseRouterOsPosition } from "../core/routeros-errors.ts";
46
+ import { routerOsStringLiteral } from "../core/routeros-string.ts";
46
47
  import { CentrsError } from "../errors.ts";
47
48
  import {
48
49
  type MacAddress,
@@ -323,8 +324,11 @@ export class MacTelnetConsole {
323
324
  * Validate a command without running it, using a single console `:parse`.
324
325
  * Throws `validation/syntax` for malformed CLI and
325
326
  * `validation/unknown-attribute` for a rejected parameter; returns the parsed
326
- * form on success. One gate covers both because the console's `:parse`
327
- * reports `bad parameter <name>` as well as `syntax error`.
327
+ * form on success. One gate covers both because the console prints the
328
+ * `:parse` return value, which carries `bad parameter <name>` as well as the
329
+ * `(<%%` / `(line N column M)` syntax diagnostics — see
330
+ * {@link classifyParseResult} for the full grammar and why the matcher is
331
+ * anchored to the diagnostic shape rather than to bare words.
328
332
  */
329
333
  async parseGate(cli: string): Promise<string> {
330
334
  const { output } = await this.run(parseScriptFor(cli));
@@ -604,17 +608,61 @@ export function parseScriptFor(cli: string): string {
604
608
  return `:put [:parse ${routerOsStringLiteral(cli)}]`;
605
609
  }
606
610
 
611
+ /*
612
+ * The `:parse` diagnostic grammar, anchored to where RouterOS actually emits
613
+ * each shape. Anchoring is load-bearing, not cosmetic: an *accepted* return
614
+ * echoes the script body, so `:put "bad parameter x"` comes back as
615
+ * `(evl /putmessage=bad parameter x)`. An unanchored search reads that echoed
616
+ * content as a verdict and refuses to run a valid command (GH#230).
617
+ *
618
+ * Two shapes, both corpus-verified against lsp-routeros-ts
619
+ * `parseil_results.il_text` (2736 rows, RouterOS 7.20.8/7.22.1/7.23rc1):
620
+ *
621
+ * 1. WRAPPED — the diagnostic sits inside the returned value, marked by the
622
+ * `(evl ` / `(<%%` wrapper. It can appear mid-return, because a
623
+ * multi-statement script reports the offending statement in place. Every
624
+ * corpus `bad parameter` (8/8) follows `(evl `, and every `bad command
625
+ * name` (135/135) follows `(<%%`.
626
+ * 2. BARE — the diagnostic *is* the whole return, printed on its own line
627
+ * with RouterOS's position suffix. All 425 corpus rows of this shape are
628
+ * the entire return; none appear embedded. Requiring both line-start and
629
+ * the `(line N column M)` suffix is what separates a real verdict from
630
+ * echoed body text.
631
+ */
632
+ const WRAPPED_BAD_PARAMETER = /\(evl\s+bad parameter\s+(\S+)/i;
633
+ const WRAPPED_BAD_COMMAND = /\(<%%\s*bad command name\b/i;
634
+ const BARE_BAD_PARAMETER =
635
+ /^[ \t]*bad parameter\s+(\S+)[^\n]*\(line \d+ column \d+\)/im;
636
+ const BARE_SYNTAX =
637
+ /^[ \t]*(?:syntax error|bad command name|expected [a-z]+(?: [a-z]+)*)[^\n]*\(line \d+ column \d+\)/im;
638
+
607
639
  /**
608
- * Classify the output of a console `:put [:parse "<cli>"]`. A single console
609
- * `:parse` reports both forms (grounded on CHR 7.23.1): `bad parameter <name>`
610
- * for an unknown attribute and `syntax error` / `bad command name` for malformed
611
- * CLI. Throws the matching `validation/*` error; returns on a clean parse.
640
+ * Classify the printed value of `:put [:parse "<cli>"]`. Grounded on CHR 7.23.3
641
+ * (see GH#230): `:parse` *never* throws — it returns a value whose text carries
642
+ * the verdict. For a bare menu path the accepted form is `(evl /canonical/path)`
643
+ * (abbreviations expanded); for script-shaped input the accepted return echoes
644
+ * the statement body and may contain arbitrary text — including text that looks
645
+ * like a diagnostic. Rejections are therefore matched only in the positions
646
+ * RouterOS emits them (see the grammar note above): wrapped as
647
+ * `(evl bad parameter <name> (line N column M) /path)` /
648
+ * `(<%% bad command name <name> (line N column M) a;b;c)`, or bare and
649
+ * line-initial as `expected … (line N column M)` / `syntax error (line N column
650
+ * M)`. A single `:parse` covers both the syntax and the unknown-attribute
651
+ * (name-level) gate on every transport — REST and native API expose the same text
652
+ * in the `ret` value (HTTP 200 / `as-string`), the console transports print it.
653
+ *
654
+ * Throws the matching `validation/*` error; returns on a clean parse.
612
655
  */
613
- export function classifyParseResult(output: string, cli: string): void {
614
- // The console `:parse` rejection carries RouterOS's authoritative byte offset
656
+ export function classifyParseResult(
657
+ output: string,
658
+ cli: string,
659
+ via: string = "mac-telnet",
660
+ ): void {
661
+ // The `:parse` rejection carries RouterOS's authoritative byte offset
615
662
  // (`… (line N column M)`); surface it as the structured `error.position`.
616
663
  const position = parseRouterOsPosition(output);
617
- const unknown = output.match(/bad parameter\s+(\S+)/i);
664
+ const unknown =
665
+ output.match(WRAPPED_BAD_PARAMETER) ?? output.match(BARE_BAD_PARAMETER);
618
666
  if (unknown) {
619
667
  throw new CentrsError({
620
668
  code: "validation/unknown-attribute",
@@ -624,13 +672,13 @@ export function classifyParseResult(output: string, cli: string): void {
624
672
  context: {
625
673
  command: cli,
626
674
  parameter: cleanToken(unknown[1] ?? ""),
627
- validationSource: ":put [:parse ...] over mac-telnet",
675
+ validationSource: `:put [:parse ...] over ${via}`,
628
676
  detail: output,
629
677
  },
630
678
  ...(position ? { position } : {}),
631
679
  });
632
680
  }
633
- if (/syntax error|bad command name|expected\b/i.test(output)) {
681
+ if (WRAPPED_BAD_COMMAND.test(output) || BARE_SYNTAX.test(output)) {
634
682
  throw new CentrsError({
635
683
  code: "validation/syntax",
636
684
  summary: "RouterOS rejected the command syntax while parsing it.",
@@ -638,7 +686,7 @@ export function classifyParseResult(output: string, cli: string): void {
638
686
  "Fix the RouterOS CLI syntax (quotes, brackets, attribute form), then retry.",
639
687
  context: {
640
688
  command: cli,
641
- validationSource: ":put [:parse ...] over mac-telnet",
689
+ validationSource: `:put [:parse ...] over ${via}`,
642
690
  detail: output,
643
691
  },
644
692
  ...(position ? { position } : {}),
@@ -646,10 +694,6 @@ export function classifyParseResult(output: string, cli: string): void {
646
694
  }
647
695
  }
648
696
 
649
- function routerOsStringLiteral(value: string): string {
650
- return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
651
- }
652
-
653
697
  function cleanToken(token: string): string {
654
698
  return token.replace(/^["'`]+|["'`.,;:)]+$/g, "");
655
699
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Device-class fact extraction + selector matching, shared by the fan-out
3
- * resolver ({@link ../resolver/selection.ts | expandCdbSelection}) and
3
+ * resolver ({@link ../resolver/selection.ts | expandSelection}) and
4
4
  * `devices list` (`src/devices.ts`) so **both surfaces match `--where` /
5
5
  * `--near` / `--bbox` against identical semantics**.
6
6
  *
@@ -88,14 +88,33 @@ export {
88
88
  unresolvedMacError,
89
89
  } from "./mac.ts";
90
90
  export {
91
- type CdbSelectionExpansion,
92
- type CdbSelectionMember,
93
- type CdbSelectionResolveInput,
94
- expandCdbSelection,
91
+ assertNoQuickchrOverrideConflict,
92
+ type QuickchrConnection,
93
+ type QuickchrResolution,
94
+ quickchrConnection,
95
+ resolveQuickchrTarget,
96
+ SUPPORTED_DESCRIPTOR_VERSION,
97
+ } from "./quickchr-provider.ts";
98
+ export {
99
+ expandSelection,
95
100
  isDefaultRecordTarget,
101
+ type SelectionExpansion,
102
+ type SelectionMember,
103
+ type SelectionResolveInput,
96
104
  type SelectionWhereClause,
97
105
  type TargetSelection,
98
106
  } from "./selection.ts";
107
+ export {
108
+ type AnyServiceEndpoint,
109
+ isEndpointAvailable,
110
+ type ServiceEndpoint,
111
+ type ServiceEndpointAuth,
112
+ type ServiceEndpointMap,
113
+ type ServiceEndpointSource,
114
+ type SshEndpointAuth,
115
+ type SshServiceEndpoint,
116
+ type UnavailableEndpoint,
117
+ } from "./service-endpoint.ts";
99
118
  export {
100
119
  type CommentKvLayer,
101
120
  parseBoolean,