@tikoci/centrs 0.1.7 → 0.1.8

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 (82) hide show
  1. package/CHANGELOG.md +256 -0
  2. package/GLOSSARY.txt +32 -0
  3. package/README.md +2 -2
  4. package/SECURITY.md +1 -1
  5. package/commands/AGENTS.md +2 -1
  6. package/commands/api/AGENTS.md +55 -30
  7. package/commands/api/README.md +123 -41
  8. package/commands/api/examples.md +205 -17
  9. package/commands/devices/README.md +11 -3
  10. package/commands/execute/README.md +31 -3
  11. package/commands/execute/examples.md +136 -1
  12. package/commands/explain/README.md +30 -4
  13. package/commands/mcp/README.md +5 -1
  14. package/commands/mcp/examples.md +18 -1
  15. package/commands/retrieve/README.md +348 -4
  16. package/commands/retrieve/examples.md +422 -5
  17. package/commands/terminal/README.md +14 -4
  18. package/commands/terminal/examples.md +22 -3
  19. package/commands/transfer/README.md +55 -9
  20. package/commands/transfer/examples.md +106 -7
  21. package/docs/CLI.md +28 -20
  22. package/docs/CONSTITUTION.md +30 -10
  23. package/docs/MATRIX.md +38 -1
  24. package/docs/errors/README.md +3 -1
  25. package/docs/errors/input/invalid-query.md +36 -0
  26. package/docs/errors/input/local-destination.md +20 -0
  27. package/docs/errors/input/unsupported-query.md +19 -0
  28. package/docs/errors/internal/byte-string.md +13 -0
  29. package/docs/errors/settings/invalid-timeout.md +6 -0
  30. package/docs/errors/transport/stream-overflow.md +21 -0
  31. package/docs/errors/usage/removed-flag.md +20 -0
  32. package/docs/errors/validation/menu-unavailable.md +22 -0
  33. package/docs/errors/validation/not-followable.md +23 -0
  34. package/docs/errors/validation/package-missing.md +29 -0
  35. package/docs/errors/validation/unknown-path.md +16 -4
  36. package/docs/errors/wait/deadline-exceeded.md +14 -0
  37. package/docs/errors/wait/interrupted.md +10 -0
  38. package/package.json +14 -11
  39. package/src/absent-menu.ts +280 -0
  40. package/src/api-fanout.ts +22 -12
  41. package/src/api.ts +511 -186
  42. package/src/btest.ts +19 -17
  43. package/src/cli/api.ts +126 -34
  44. package/src/cli/execute.ts +18 -5
  45. package/src/cli/retrieve.ts +223 -22
  46. package/src/cli/terminal.ts +10 -5
  47. package/src/cli/transfer.ts +23 -8
  48. package/src/core/envelope.ts +14 -0
  49. package/src/core/error-catalog.ts +56 -1
  50. package/src/core/inspect.ts +43 -5
  51. package/src/core/query.ts +508 -0
  52. package/src/core/routeros-errors.ts +1 -1
  53. package/src/devices.ts +6 -9
  54. package/src/discover.ts +7 -8
  55. package/src/errors.ts +116 -7
  56. package/src/execute-fanout.ts +13 -8
  57. package/src/execute.ts +106 -65
  58. package/src/explain/catalog.ts +58 -10
  59. package/src/explain/menus.ts +23 -2
  60. package/src/explain.ts +6 -6
  61. package/src/index.ts +29 -1
  62. package/src/mcp/safety.ts +43 -18
  63. package/src/mcp/tools.ts +14 -17
  64. package/src/protocols/adapter.ts +224 -118
  65. package/src/protocols/btest-session.ts +3 -2
  66. package/src/protocols/endpoint.ts +10 -0
  67. package/src/protocols/mac-telnet-console.ts +6 -3
  68. package/src/protocols/native-api.ts +223 -70
  69. package/src/protocols/sftp.ts +44 -14
  70. package/src/protocols/ssh.ts +42 -14
  71. package/src/resolver/cdb.ts +28 -7
  72. package/src/resolver/index.ts +1 -0
  73. package/src/resolver/target.ts +53 -7
  74. package/src/retrieve-fanout.ts +51 -12
  75. package/src/retrieve-follow.ts +862 -0
  76. package/src/retrieve-sample.ts +244 -0
  77. package/src/retrieve-stream.ts +115 -0
  78. package/src/retrieve-wait.ts +286 -0
  79. package/src/retrieve.ts +732 -108
  80. package/src/settings.ts +15 -15
  81. package/src/transfer-fanout.ts +40 -15
  82. package/src/transfer.ts +234 -32
@@ -5,7 +5,7 @@
5
5
  * the drift gate. The generator (`scripts/gen-explain-menus.ts`) carries the
6
6
  * full rationale, the source pin, and why absence here is safe.
7
7
  *
8
- * Union of the `dir` and `path` nodes in 6 pinned restraml typed trees
8
+ * Union of the `dir` and `path` nodes in 8 pinned restraml typed trees
9
9
  * (`https://tikoci.github.io/restraml/`), all extra-packages builds:
10
10
  *
11
11
  * | Tree | Arch | RouterOS | Nodes | Containers |
@@ -16,9 +16,11 @@
16
16
  * | `7.24.4/extra/deep-inspect.arm64.json` | arm64 | 7.24.4 | 42,691 | 575 |
17
17
  * | `7.25beta5/extra/deep-inspect.x86.json` | x86 | 7.25beta5 | 41,794 | 557 |
18
18
  * | `7.25beta5/extra/deep-inspect.arm64.json` | arm64 | 7.25beta5 | 43,150 | 578 |
19
+ * | `7.26beta1/extra/deep-inspect.x86.json` | x86 | 7.26beta1 | 43,294 | 576 |
20
+ * | `7.26beta1/extra/deep-inspect.arm64.json` | arm64 | 7.26beta1 | 44,650 | 597 |
19
21
  *
20
22
  * Zero node-type conflicts across those trees — no `dir`↔`cmd` flip across
21
- * 4 versions or across architectures — which is what makes the union
23
+ * 5 versions or across architectures — which is what makes the union
22
24
  * version-less rather than version-keyed. Generation aborts if that ever stops
23
25
  * holding.
24
26
  *
@@ -57,6 +59,18 @@ export const MENU_PATHS: ReadonlySet<string> = new Set([
57
59
  "/certificate/scep-server/ra",
58
60
  "/certificate/scep-server/requests",
59
61
  "/certificate/settings",
62
+ "/cmr",
63
+ "/cmr/alert",
64
+ "/cmr/client",
65
+ "/cmr/device",
66
+ "/cmr/layout",
67
+ "/cmr/layout/link",
68
+ "/cmr/layout/node",
69
+ "/cmr/upgrade",
70
+ "/cmr/upgrade/job",
71
+ "/cmr/vlan",
72
+ "/cmr/wifi",
73
+ "/cmr/wifi/radio",
60
74
  "/console",
61
75
  "/console/settings",
62
76
  "/container",
@@ -183,6 +197,8 @@ export const MENU_PATHS: ReadonlySet<string> = new Set([
183
197
  "/interface/wifi/capsman/remote-cap",
184
198
  "/interface/wifi/channel",
185
199
  "/interface/wifi/configuration",
200
+ "/interface/wifi/dashboard",
201
+ "/interface/wifi/dashboard/settings",
186
202
  "/interface/wifi/datapath",
187
203
  "/interface/wifi/interworking",
188
204
  "/interface/wifi/network",
@@ -526,6 +542,8 @@ export const MENU_PATHS: ReadonlySet<string> = new Set([
526
542
  "/system/clock/manual",
527
543
  "/system/console",
528
544
  "/system/console/screen",
545
+ "/system/dashboard",
546
+ "/system/dashboard/settings",
529
547
  "/system/default-configuration",
530
548
  "/system/default-configuration/caps-mode-script",
531
549
  "/system/default-configuration/custom-script",
@@ -583,6 +601,9 @@ export const MENU_PATHS: ReadonlySet<string> = new Set([
583
601
  "/task",
584
602
  "/terminal",
585
603
  "/tool",
604
+ "/tool/apptraffic",
605
+ "/tool/apptraffic/stats",
606
+ "/tool/apptraffic/stats/categories",
586
607
  "/tool/bandwidth-server",
587
608
  "/tool/bandwidth-server/session",
588
609
  "/tool/calea",
package/src/explain.ts CHANGED
@@ -108,7 +108,11 @@ import {
108
108
  isWriteShaped,
109
109
  } from "./core/execute-command.ts";
110
110
  import { toYaml } from "./core/yaml.ts";
111
- import { CentrsError, serializeCentrsError } from "./errors.ts";
111
+ import {
112
+ CentrsError,
113
+ formatCentrsErrorText,
114
+ serializeCentrsError,
115
+ } from "./errors.ts";
112
116
  import { argSpans } from "./explain/arg-tokens.ts";
113
117
  import {
114
118
  type ArgumentKind,
@@ -2495,11 +2499,7 @@ function renderExplainText(
2495
2499
  ): string {
2496
2500
  const lines: string[] = [];
2497
2501
  if (!envelope.ok) {
2498
- lines.push(`[${envelope.error.code}] ${envelope.error.summary}`);
2499
- if (envelope.error.remediation)
2500
- lines.push(`Fix: ${envelope.error.remediation}`);
2501
- if (envelope.error.detailsUrl)
2502
- lines.push(`Details: ${envelope.error.detailsUrl}`);
2502
+ lines.push(formatCentrsErrorText(envelope.error));
2503
2503
  lines.push(...tipLines(envelope.tips));
2504
2504
  return lines.join("\n");
2505
2505
  }
package/src/index.ts CHANGED
@@ -12,10 +12,10 @@ export {
12
12
  type ApiSuccessEnvelope,
13
13
  api,
14
14
  apiEnvelope,
15
- apiListen,
16
15
  apiMethods,
17
16
  apiOutputFormats,
18
17
  apiRequestSummaryFromRequest,
18
+ apiStream,
19
19
  buildApiBody,
20
20
  buildApiErrorEnvelope,
21
21
  buildApiErrorEnvelopeFromResolved,
@@ -85,6 +85,7 @@ export {
85
85
  normalizeWarnings,
86
86
  type SettingSource,
87
87
  type SettingSourceKind,
88
+ type ValidationTrace,
88
89
  type Warning,
89
90
  } from "./core/envelope.ts";
90
91
  export {
@@ -576,24 +577,36 @@ export {
576
577
  type TargetSelection,
577
578
  toCoreSource,
578
579
  } from "./resolver/index.ts";
580
+ export type { RetrieveWaitMeta, RetrieveWaitSettings } from "./retrieve.ts";
579
581
  export {
580
582
  buildResolvedRetrieve,
581
583
  buildRetrieveErrorEnvelope,
582
584
  buildRetrieveErrorEnvelopeFromResolved,
585
+ FOLLOW_SWEEP_DEFAULT_MS,
583
586
  type ResolvedRetrieveRequest,
584
587
  type RetrieveEnvelope,
585
588
  type RetrieveErrorEnvelope,
589
+ type RetrieveFollowCounts,
590
+ type RetrieveFollowSettings,
591
+ type RetrieveFollowStopReason,
592
+ type RetrieveFollowSummary,
586
593
  type RetrieveGlobalContext,
587
594
  type RetrieveOperationMeta,
595
+ type RetrieveOptions,
588
596
  type RetrieveOutputFormat,
589
597
  type RetrieveRequest,
590
598
  type RetrieveRequestSummary,
599
+ type RetrieveSampleSettings,
600
+ type RetrieveSampleStopReason,
601
+ type RetrieveSampleSummary,
602
+ type RetrieveStreamMeta,
591
603
  type RetrieveSuccessEnvelope,
592
604
  type RetrieveWarning,
593
605
  renderRetrieveEnvelope,
594
606
  resolveRetrieveGlobalContext,
595
607
  resolveRetrieveRequest,
596
608
  retrieve,
609
+ retrieveEnvelope,
597
610
  retrieveOutputFormats,
598
611
  runResolvedRetrieve,
599
612
  validateRetrieveRequestShape,
@@ -611,6 +624,17 @@ export {
611
624
  retrieveFanout,
612
625
  retrieveGroup,
613
626
  } from "./retrieve-fanout.ts";
627
+ export {
628
+ FOLLOW_BUFFER_LIMIT,
629
+ type RetrieveFollowOptions,
630
+ retrieveFollow,
631
+ } from "./retrieve-follow.ts";
632
+ export {
633
+ type RetrieveSampleOptions,
634
+ retrieveSample,
635
+ } from "./retrieve-sample.ts";
636
+ export { renderRetrieveStreamLine } from "./retrieve-stream.ts";
637
+ export { type RetrieveWaitOptions, retrieveWait } from "./retrieve-wait.ts";
614
638
  export {
615
639
  buildSettingsErrorEnvelope,
616
640
  renderSettingsEnvelope,
@@ -652,10 +676,12 @@ export {
652
676
  } from "./terminal.ts";
653
677
  export {
654
678
  buildTransferErrorEnvelope,
679
+ buildTransferErrorEnvelopeFromResolved,
655
680
  FILE_READ_CHUNK_BYTES,
656
681
  isTransferWriteVerb,
657
682
  normalizeRemotePath,
658
683
  REST_FILE_WRITE_CAP_BYTES,
684
+ type ResolvedTransferRequest,
659
685
  renderTransferEnvelope,
660
686
  resolveTransferRequest,
661
687
  runResolvedTransfer,
@@ -666,11 +692,13 @@ export {
666
692
  type TransferMethod,
667
693
  type TransferOp,
668
694
  type TransferOperationMeta,
695
+ type TransferOptions,
669
696
  type TransferOutputFormat,
670
697
  type TransferRequest,
671
698
  type TransferSuccessEnvelope,
672
699
  type TransferVerb,
673
700
  transfer,
701
+ transferEnvelope,
674
702
  transferMethods,
675
703
  transferOutputFormats,
676
704
  transferVerbs,
package/src/mcp/safety.ts CHANGED
@@ -10,7 +10,12 @@
10
10
  * `mcp=rw`, else `cdb/write-not-permitted`.
11
11
  */
12
12
 
13
- import { type LoadedCdb, loadCdb } from "../devices.ts";
13
+ import {
14
+ type DevicesShowItem,
15
+ type LoadedCdb,
16
+ loadCdb,
17
+ showDevice,
18
+ } from "../devices.ts";
14
19
  import { CentrsError } from "../errors.ts";
15
20
  import type { McpWritePolicy } from "../mcp.ts";
16
21
  import { parseCommentKv } from "../resolver/comment-kv.ts";
@@ -52,30 +57,50 @@ export async function loadAllowlistCdb(
52
57
  * `cdb/target-not-registered` when no record matches — the friendly,
53
58
  * MCP-specific framing of "this device is not on the allowlist", pointing at
54
59
  * `centrs_devices add` rather than leaking the CLI's `cdb/not-found-target`.
60
+ *
61
+ * Matches with {@link showDevice}, the matcher the core resolver dials with
62
+ * (target, `identity=`, `mac=`, `ip=`), so the record whose write policy is
63
+ * checked here is the record the call then runs against (#444). A name that
64
+ * matches more than one record fails `identity/ambiguous` rather than taking
65
+ * the first.
55
66
  */
56
67
  export function resolveRegisteredTarget(
57
68
  cdb: LoadedCdb,
58
69
  target: string,
59
70
  ): RegisteredTarget {
60
- for (let index = 0; index < cdb.entries.length; index += 1) {
61
- const entry = cdb.entries[index];
62
- if (entry && entry.target === target) {
63
- return {
64
- target,
65
- user: entry.user,
66
- group: entry.group,
67
- recordIndex: index,
68
- writePolicy: readWritePolicy(entry.comment),
69
- };
71
+ let entry: DevicesShowItem;
72
+ try {
73
+ entry = showDevice({ cdb, target }).data.entry;
74
+ } catch (error) {
75
+ if (error instanceof CentrsError && error.code === "cdb/not-found-target") {
76
+ throw new CentrsError({
77
+ code: "cdb/target-not-registered",
78
+ summary: `Target "${target}" is not registered in the CDB allowlist.`,
79
+ remediation:
80
+ "Register the device first with the `centrs_devices` tool (op `add`) or `centrs devices add`, then retry. The MCP server only acts on CDB-registered targets, by target or by its `identity=`, `mac=` or `ip=` comment key.",
81
+ context: { target, cdbFile: cdb.settings.cdbFile.value },
82
+ });
83
+ }
84
+ // The CLI remediation names `--match`, which no MCP tool takes.
85
+ if (error instanceof CentrsError && error.code === "identity/ambiguous") {
86
+ throw new CentrsError({
87
+ code: error.code,
88
+ summary: error.summary,
89
+ remediation:
90
+ "Retry with one candidate's address from `context.matches[].target` as `target`. If the candidates share that address too, the CDB holds duplicate records for it: remove or edit the extra one with the `centrs_devices` tool (op `remove` or `set`).",
91
+ context: error.context,
92
+ cause: error,
93
+ });
70
94
  }
95
+ throw error;
71
96
  }
72
- throw new CentrsError({
73
- code: "cdb/target-not-registered",
74
- summary: `Target "${target}" is not registered in the CDB allowlist.`,
75
- remediation:
76
- "Register the device first with the `centrs_devices` tool (op `add`) or `centrs devices add`, then retry. The MCP server only acts on CDB-registered targets.",
77
- context: { target, cdbFile: cdb.settings.cdbFile.value },
78
- });
97
+ return {
98
+ target: entry.target,
99
+ user: entry.user,
100
+ group: entry.group,
101
+ recordIndex: entry.cdbRecordIndex,
102
+ writePolicy: readWritePolicy(entry.comment),
103
+ };
79
104
  }
80
105
 
81
106
  /**
package/src/mcp/tools.ts CHANGED
@@ -36,7 +36,7 @@ import {
36
36
  import type { CommentKvUpdate } from "../resolver/comment-kv.ts";
37
37
  import { parseBbox, parseNear } from "../resolver/geo.ts";
38
38
  import { parseDuration } from "../resolver/settings.ts";
39
- import { buildRetrieveErrorEnvelope, retrieve } from "../retrieve.ts";
39
+ import { retrieveEnvelope } from "../retrieve.ts";
40
40
  import {
41
41
  buildRetrieveFanoutErrorEnvelope,
42
42
  retrieveGroup,
@@ -193,7 +193,7 @@ export const validateInputShape = {
193
193
  target: z
194
194
  .string()
195
195
  .describe(
196
- "CDB-registered target (name / IP / URL / MAC). Must be on the allowlist.",
196
+ "CDB-registered target: its address (IP / URL / MAC) or its identity=, mac= or ip= comment key. Must be on the allowlist.",
197
197
  ),
198
198
  command: z.string().describe("RouterOS CLI command to dry-run."),
199
199
  via: z
@@ -248,7 +248,9 @@ export const retrieveInputShape = {
248
248
  target: z
249
249
  .string()
250
250
  .optional()
251
- .describe("CDB-registered single target. Use either target or group."),
251
+ .describe(
252
+ "CDB-registered single target: its address (IP / URL / MAC) or its identity=, mac= or ip= comment key. Use either target or group.",
253
+ ),
252
254
  group: z
253
255
  .string()
254
256
  .optional()
@@ -264,11 +266,13 @@ export const retrieveInputShape = {
264
266
  .boolean()
265
267
  .optional()
266
268
  .describe("Return every attribute instead of the default projection."),
267
- filter: z.string().optional().describe("RouterOS-style filter expression."),
269
+ filter: z.string().optional().describe("Alias of `query`."),
268
270
  query: z
269
- .string()
271
+ .union([z.string(), z.array(z.string())])
270
272
  .optional()
271
- .describe("Raw query word(s) appended to the read."),
273
+ .describe(
274
+ "Row filter written as after `print where`, e.g. `type=ether and !disabled`; run on the router, several are AND-ed. No `~` (regex) or `in`.",
275
+ ),
272
276
  via: z.string().optional().describe("Override transport."),
273
277
  timeout: z
274
278
  .union([z.string(), z.number()])
@@ -324,18 +328,11 @@ export async function handleRetrieve(
324
328
  cdbPassword: config.cdbPassword,
325
329
  };
326
330
 
331
+ if (args.group === undefined) return await retrieveEnvelope(request);
327
332
  try {
328
- const envelope =
329
- args.group !== undefined
330
- ? await retrieveGroup(request)
331
- : await retrieve(request);
332
- return envelope;
333
+ return await retrieveGroup(request);
333
334
  } catch (error) {
334
- const envelope =
335
- args.group !== undefined
336
- ? buildRetrieveFanoutErrorEnvelope(request, error)
337
- : buildRetrieveErrorEnvelope(request, error);
338
- return envelope;
335
+ return buildRetrieveFanoutErrorEnvelope(request, error);
339
336
  }
340
337
  }
341
338
 
@@ -345,7 +342,7 @@ export const executeInputShape = {
345
342
  target: z
346
343
  .string()
347
344
  .describe(
348
- "CDB-registered target. Inline host+credentials are not accepted.",
345
+ "CDB-registered target: its address (IP / URL / MAC) or its identity=, mac= or ip= comment key. Inline host+credentials are not accepted.",
349
346
  ),
350
347
  command: z.string().describe("RouterOS CLI command to run."),
351
348
  confirm: z