@tikoci/centrs 0.1.0

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 (182) hide show
  1. package/CHANGELOG.md +207 -0
  2. package/GLOSSARY.txt +420 -0
  3. package/README.md +196 -0
  4. package/SECURITY.md +30 -0
  5. package/commands/AGENTS.md +50 -0
  6. package/commands/btest/README.md +257 -0
  7. package/commands/btest/examples.md +190 -0
  8. package/commands/check/README.md +59 -0
  9. package/commands/devices/README.md +405 -0
  10. package/commands/devices/examples.md +488 -0
  11. package/commands/discover/README.md +170 -0
  12. package/commands/discover/examples.md +98 -0
  13. package/commands/execute/README.md +208 -0
  14. package/commands/execute/examples.md +353 -0
  15. package/commands/mcp/README.md +260 -0
  16. package/commands/mcp/examples.md +158 -0
  17. package/commands/retrieve/README.md +209 -0
  18. package/commands/retrieve/examples.md +306 -0
  19. package/commands/settings/README.md +69 -0
  20. package/commands/stream/README.md +70 -0
  21. package/commands/terminal/README.md +177 -0
  22. package/commands/terminal/examples.md +144 -0
  23. package/commands/transfer/README.md +315 -0
  24. package/commands/transfer/examples.md +368 -0
  25. package/docs/CONSTITUTION.md +417 -0
  26. package/docs/MATRIX.md +224 -0
  27. package/docs/errors/README.md +74 -0
  28. package/docs/errors/auth/failed.md +9 -0
  29. package/docs/errors/cdb/already-exists.md +9 -0
  30. package/docs/errors/cdb/created.md +9 -0
  31. package/docs/errors/cdb/decrypt-failed.md +9 -0
  32. package/docs/errors/cdb/empty-group.md +9 -0
  33. package/docs/errors/cdb/invalid-option.md +9 -0
  34. package/docs/errors/cdb/invalid-record.md +9 -0
  35. package/docs/errors/cdb/not-found-target.md +9 -0
  36. package/docs/errors/cdb/not-found.md +9 -0
  37. package/docs/errors/cdb/override-applied.md +9 -0
  38. package/docs/errors/cdb/parse-failed.md +9 -0
  39. package/docs/errors/cdb/password-not-needed.md +9 -0
  40. package/docs/errors/cdb/password-required.md +9 -0
  41. package/docs/errors/cdb/reserved-key.md +9 -0
  42. package/docs/errors/cdb/reserved-option.md +9 -0
  43. package/docs/errors/cdb/target-not-registered.md +9 -0
  44. package/docs/errors/cdb/unknown-field.md +9 -0
  45. package/docs/errors/cdb/unknown-option.md +9 -0
  46. package/docs/errors/cdb/write-not-permitted.md +9 -0
  47. package/docs/errors/discover/broadcast-unavailable.md +9 -0
  48. package/docs/errors/discover/failed.md +9 -0
  49. package/docs/errors/identity/ambiguous.md +9 -0
  50. package/docs/errors/identity/no-match.md +9 -0
  51. package/docs/errors/input/invalid-command.md +9 -0
  52. package/docs/errors/input/invalid-match.md +9 -0
  53. package/docs/errors/input/invalid-path.md +9 -0
  54. package/docs/errors/input/invalid-routeros-path.md +9 -0
  55. package/docs/errors/input/invalid-target.md +9 -0
  56. package/docs/errors/input/local-file-not-found.md +9 -0
  57. package/docs/errors/input/mac-address.md +9 -0
  58. package/docs/errors/input/max-results-exceeded.md +9 -0
  59. package/docs/errors/internal/devices-failed.md +9 -0
  60. package/docs/errors/internal/mcp-start.md +9 -0
  61. package/docs/errors/internal/mcp-tool.md +9 -0
  62. package/docs/errors/internal/native-api-length.md +9 -0
  63. package/docs/errors/internal/unhandled.md +9 -0
  64. package/docs/errors/internal/unreachable.md +9 -0
  65. package/docs/errors/mndp/encode-failed.md +9 -0
  66. package/docs/errors/mndp/listen-failed.md +9 -0
  67. package/docs/errors/mndp/malformed.md +9 -0
  68. package/docs/errors/routeros/api-fatal.md +9 -0
  69. package/docs/errors/routeros/api-protocol.md +9 -0
  70. package/docs/errors/routeros/api-trap.md +9 -0
  71. package/docs/errors/routeros/btest-protocol.md +12 -0
  72. package/docs/errors/routeros/btest-too-many-sessions.md +9 -0
  73. package/docs/errors/routeros/command-failed.md +14 -0
  74. package/docs/errors/routeros/ec-srp5-protocol.md +11 -0
  75. package/docs/errors/routeros/error.md +9 -0
  76. package/docs/errors/routeros/invalid-value.md +14 -0
  77. package/docs/errors/routeros/mac-telnet-error.md +9 -0
  78. package/docs/errors/routeros/mac-telnet-not-ready.md +9 -0
  79. package/docs/errors/routeros/mac-telnet-protocol.md +9 -0
  80. package/docs/errors/routeros/mac-telnet-unsupported-auth.md +9 -0
  81. package/docs/errors/routeros/protocol-not-implemented.md +9 -0
  82. package/docs/errors/routeros/request-failed.md +9 -0
  83. package/docs/errors/routeros/session-closed.md +13 -0
  84. package/docs/errors/routeros/unknown-attribute.md +13 -0
  85. package/docs/errors/routeros/unknown-path.md +15 -0
  86. package/docs/errors/routeros/unsupported-capability.md +9 -0
  87. package/docs/errors/settings/invalid-boolean.md +9 -0
  88. package/docs/errors/settings/invalid-format.md +9 -0
  89. package/docs/errors/settings/invalid-integer.md +9 -0
  90. package/docs/errors/settings/invalid-timeout.md +9 -0
  91. package/docs/errors/settings/invalid-via.md +9 -0
  92. package/docs/errors/settings/unsafe-protocol-blocked.md +9 -0
  93. package/docs/errors/target/mac-not-in-arp.md +14 -0
  94. package/docs/errors/target/mac-required.md +23 -0
  95. package/docs/errors/target/mac-unresolved.md +17 -0
  96. package/docs/errors/target/unresolved.md +9 -0
  97. package/docs/errors/transport/auth-failed.md +9 -0
  98. package/docs/errors/transport/auto-method.md +9 -0
  99. package/docs/errors/transport/capability-unsupported.md +22 -0
  100. package/docs/errors/transport/checksum-unavailable.md +9 -0
  101. package/docs/errors/transport/connection-closed.md +9 -0
  102. package/docs/errors/transport/connection-refused.md +9 -0
  103. package/docs/errors/transport/dns.md +9 -0
  104. package/docs/errors/transport/host-key-mismatch.md +9 -0
  105. package/docs/errors/transport/incomplete-transfer.md +9 -0
  106. package/docs/errors/transport/insecure-trust.md +9 -0
  107. package/docs/errors/transport/local-tool-missing.md +11 -0
  108. package/docs/errors/transport/network.md +9 -0
  109. package/docs/errors/transport/timeout.md +9 -0
  110. package/docs/errors/transport/tls-certificate.md +9 -0
  111. package/docs/errors/transport/unreachable.md +9 -0
  112. package/docs/errors/transport/unsupported-operation.md +9 -0
  113. package/docs/errors/usage/confirmation-required.md +9 -0
  114. package/docs/errors/usage/conflicting-flags.md +9 -0
  115. package/docs/errors/usage/invalid-concurrency.md +9 -0
  116. package/docs/errors/usage/missing-group.md +9 -0
  117. package/docs/errors/usage/not-implemented.md +9 -0
  118. package/docs/errors/usage/target-exists.md +9 -0
  119. package/docs/errors/usage/timeout-out-of-range.md +9 -0
  120. package/docs/errors/validation/not-implemented.md +9 -0
  121. package/docs/errors/validation/option.md +9 -0
  122. package/docs/errors/validation/syntax.md +9 -0
  123. package/docs/errors/validation/unknown-attribute.md +9 -0
  124. package/docs/errors/validation/unknown-path.md +9 -0
  125. package/package.json +96 -0
  126. package/src/AGENTS.md +7 -0
  127. package/src/btest.ts +906 -0
  128. package/src/cli/btest.ts +455 -0
  129. package/src/cli/common.ts +53 -0
  130. package/src/cli/devices.ts +527 -0
  131. package/src/cli/discover.ts +186 -0
  132. package/src/cli/execute.ts +333 -0
  133. package/src/cli/mcp.ts +109 -0
  134. package/src/cli/retrieve.ts +383 -0
  135. package/src/cli/terminal.ts +303 -0
  136. package/src/cli/transfer.ts +471 -0
  137. package/src/cli.ts +115 -0
  138. package/src/core/envelope.ts +221 -0
  139. package/src/core/error-catalog.ts +469 -0
  140. package/src/core/routeros-errors.ts +268 -0
  141. package/src/data/AGENTS.md +11 -0
  142. package/src/data/dude-db.ts +0 -0
  143. package/src/data/mndp-cache.ts +132 -0
  144. package/src/data/mndp.ts +405 -0
  145. package/src/data/winbox-cdb-write.ts +200 -0
  146. package/src/data/winbox-cdb.ts +913 -0
  147. package/src/devices-schema.ts +107 -0
  148. package/src/devices.ts +1717 -0
  149. package/src/discover.ts +802 -0
  150. package/src/errors.ts +189 -0
  151. package/src/execute.ts +1350 -0
  152. package/src/index.ts +353 -0
  153. package/src/mcp/config.ts +66 -0
  154. package/src/mcp/resources.ts +98 -0
  155. package/src/mcp/safety.ts +119 -0
  156. package/src/mcp/server.ts +225 -0
  157. package/src/mcp/tools.ts +657 -0
  158. package/src/mcp.ts +33 -0
  159. package/src/protocols/AGENTS.md +14 -0
  160. package/src/protocols/adapter.ts +866 -0
  161. package/src/protocols/btest-session.ts +1493 -0
  162. package/src/protocols/btest.ts +447 -0
  163. package/src/protocols/ec-srp5.ts +466 -0
  164. package/src/protocols/index.ts +118 -0
  165. package/src/protocols/mac-telnet-console.ts +655 -0
  166. package/src/protocols/mac-telnet.ts +1299 -0
  167. package/src/protocols/mtwei.ts +47 -0
  168. package/src/protocols/native-api.ts +796 -0
  169. package/src/protocols/sftp.ts +427 -0
  170. package/src/protocols/ssh.ts +297 -0
  171. package/src/resolver/cdb.ts +580 -0
  172. package/src/resolver/comment-kv.ts +360 -0
  173. package/src/resolver/index.ts +74 -0
  174. package/src/resolver/mac.ts +227 -0
  175. package/src/resolver/settings.ts +228 -0
  176. package/src/resolver/target.ts +442 -0
  177. package/src/retrieve-fanout.ts +533 -0
  178. package/src/retrieve.ts +1215 -0
  179. package/src/terminal.ts +444 -0
  180. package/src/transfer.ts +1554 -0
  181. package/src/tui.ts +22 -0
  182. package/src/webproxy.ts +16 -0
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Canonical result envelope.
3
+ *
4
+ * Every centrs API/CLI call returns this shape regardless of transport. The
5
+ * load-bearing rules live in `docs/CONSTITUTION.md` (Result envelope); this
6
+ * file is the typed contract those rules describe.
7
+ *
8
+ * Invariants (do not break without amending the constitution):
9
+ * - `ok` discriminates success from failure. Success always carries `data`;
10
+ * failure always carries `error`. There is no "ok with error" or
11
+ * "not-ok with data" state.
12
+ * - `warnings` is always present (`[]` when empty) so consumers never branch on
13
+ * its existence. `ok: true` may still carry warnings ("success with
14
+ * footnotes").
15
+ * - `meta` is invariant across commands. Only `data` and `meta.operation` vary
16
+ * per command. Do not add command-specific keys beside `target`/`via`/
17
+ * `settings`/`validation`/`timing`/`truncated`; put them in `meta.operation`.
18
+ * - CLI render must be lossless against the JSON envelope; `--format json|yaml`
19
+ * select serialization, not different shapes. Omit `undefined`; use `null`
20
+ * only where absence is meaningful and must round-trip (e.g. `via: null` when
21
+ * protocol resolution failed before a transport was chosen).
22
+ */
23
+
24
+ import type { SerializedCentrsError } from "../errors.ts";
25
+ import type { RouterOsProtocol } from "../protocols/index.ts";
26
+
27
+ /**
28
+ * Where a resolved setting/identity field came from. Precedence (low → high)
29
+ * is config < comment-kv < env < cli; `cdb` and `arp` describe identity
30
+ * resolution rather than settings precedence.
31
+ */
32
+ export type SettingSourceKind =
33
+ | "default"
34
+ | "config"
35
+ | "comment-kv"
36
+ | "env"
37
+ | "cli"
38
+ | "cdb"
39
+ | "arp";
40
+
41
+ export interface SettingSource {
42
+ kind: SettingSourceKind;
43
+ /** Human-facing detail: env var name, cli flag, cdb field, etc. */
44
+ key?: string;
45
+ }
46
+
47
+ export interface Warning {
48
+ /** Slash-namespaced, RouterOS-style. Example: `cdb/password-not-needed`. */
49
+ code: string;
50
+ message: string;
51
+ context?: Record<string, unknown>;
52
+ }
53
+
54
+ /**
55
+ * Advice that is explicitly *not* an error or anomaly — a pointer the caller may
56
+ * act on (no username set, no CDB found, consider a `__default__` record). Tips
57
+ * ride their own envelope channel so "this is not a problem, just a suggestion"
58
+ * never has to masquerade as a warning. See `docs/CONSTITUTION.md` (Result
59
+ * envelope). Same shape as {@link Warning} plus an optional `fix`/`detailsUrl`.
60
+ */
61
+ export interface Tip {
62
+ /** Slash-namespaced `tip/*` code. Example: `tip/credentials-missing`. */
63
+ code: string;
64
+ message: string;
65
+ /** One human sentence describing the suggested next step. */
66
+ fix?: string;
67
+ detailsUrl?: string;
68
+ }
69
+
70
+ /** Base URL for the human-readable tip pages (mirrors the error catalog scheme). */
71
+ export const TIPS_BASE_URL = "https://tikoci.github.io/centrs/tips/";
72
+
73
+ /** Build a {@link Tip} with a `tip/*` code and its generated `detailsUrl`. */
74
+ export function buildTip(code: string, message: string, fix: string): Tip {
75
+ return { code, message, fix, detailsUrl: `${TIPS_BASE_URL}${code}` };
76
+ }
77
+
78
+ /** Resolved target identity plus per-field provenance. */
79
+ export interface EnvelopeTargetMeta {
80
+ /** Raw `<router>` argument as supplied by the caller. */
81
+ input?: string;
82
+ /** CDB-facing target identity, when a command exposes registry metadata. */
83
+ target?: string;
84
+ /** Concrete resolved target, after CDB / match selection. */
85
+ resolvedTarget?: string;
86
+ host?: string;
87
+ port?: number;
88
+ baseUrl?: string;
89
+ mac?: string;
90
+ /**
91
+ * Human-facing device handle: the `identity=` comment lookup key (mirroring
92
+ * RouterOS `/system/identity`), falling back to the matched record's target.
93
+ * Deliberately may repeat across records; not a unique key.
94
+ */
95
+ identity?: string;
96
+ recordIndex?: number;
97
+ /** Alias used by devices, where the CDB record index is user-facing. */
98
+ cdbRecordIndex?: number;
99
+ user?: string;
100
+ group?: string;
101
+ via?: RouterOsProtocol;
102
+ validate?: boolean;
103
+ /** Provenance marker parsed from comment kv-soup (for example `mndp`). */
104
+ discoverySource?: string;
105
+ /** Provenance of the target identity as a whole. */
106
+ source?: SettingSource;
107
+ /** Provenance per resolved field once the resolver is wired (WP-0c). */
108
+ sources?: Record<string, SettingSource>;
109
+ }
110
+
111
+ export interface EnvelopeValidationMeta {
112
+ enabled: boolean;
113
+ /** Validator identity, e.g. `/console/inspect` or `:put [:parse]`. */
114
+ source?: string;
115
+ result?: "passed" | "failed" | "skipped";
116
+ /** Syntax gate status for commands that use RouterOS `:parse`. */
117
+ syntax?: boolean;
118
+ /** Semantic `/console/inspect` gate status, or not-applicable for script mode. */
119
+ semantic?: boolean | "not-applicable";
120
+ availableAttributes?: readonly string[];
121
+ }
122
+
123
+ export interface EnvelopeTimingMeta {
124
+ startedAt?: string;
125
+ durationMs?: number;
126
+ }
127
+
128
+ export interface EnvelopeTruncationMeta {
129
+ truncated: boolean;
130
+ reason?: string;
131
+ limitBytes?: number;
132
+ serializedBytes?: number;
133
+ }
134
+
135
+ /**
136
+ * Provenance for the settings every command shares. The index signature allows
137
+ * command-specific settings without losing discoverability of the common ones.
138
+ */
139
+ export interface CommonSettingsMeta {
140
+ via?: SettingSource;
141
+ host?: SettingSource;
142
+ port?: SettingSource;
143
+ username?: SettingSource;
144
+ password?: SettingSource;
145
+ timeoutMs?: SettingSource;
146
+ format?: SettingSource;
147
+ validate?: SettingSource;
148
+ maxResultsBytes?: SettingSource;
149
+ [key: string]: SettingSource | undefined;
150
+ }
151
+
152
+ export interface EnvelopeMeta<Operation = unknown> {
153
+ target: EnvelopeTargetMeta;
154
+ /** Chosen protocol, or `null` when resolution failed before a transport. */
155
+ via: RouterOsProtocol | null;
156
+ settings: CommonSettingsMeta;
157
+ validation?: EnvelopeValidationMeta;
158
+ timing?: EnvelopeTimingMeta;
159
+ truncated?: EnvelopeTruncationMeta;
160
+ /** Command-specific metadata. The only meta field that varies by command. */
161
+ operation?: Operation;
162
+ }
163
+
164
+ export type CentrsEnvelope<Data = unknown, Operation = unknown> =
165
+ | {
166
+ ok: true;
167
+ data: Data;
168
+ warnings: readonly Warning[];
169
+ tips: readonly Tip[];
170
+ meta: EnvelopeMeta<Operation>;
171
+ }
172
+ | {
173
+ ok: false;
174
+ error: SerializedCentrsError;
175
+ warnings: readonly Warning[];
176
+ tips: readonly Tip[];
177
+ meta: EnvelopeMeta<Operation>;
178
+ };
179
+
180
+ export type CentrsSuccessEnvelope<
181
+ Data = unknown,
182
+ Operation = unknown,
183
+ > = Extract<CentrsEnvelope<Data, Operation>, { ok: true }>;
184
+
185
+ export type CentrsErrorEnvelope<Operation = unknown> = Extract<
186
+ CentrsEnvelope<unknown, Operation>,
187
+ { ok: false }
188
+ >;
189
+
190
+ /**
191
+ * Multi-target (group / fanout) contract, locked here so WP-1b does not repaint
192
+ * it. The outer envelope's `ok` means the fanout *orchestration* succeeded and
193
+ * produced a complete per-target result set — it is `false` only when fanout
194
+ * fails before reliable per-target results exist (bad group selector, CDB
195
+ * decrypt failure, global validation failure). Per-target success/failure lives
196
+ * in `data.targets[]`; failed targets are output, not metadata.
197
+ */
198
+ export interface FanoutSummary {
199
+ total: number;
200
+ ok: number;
201
+ failed: number;
202
+ }
203
+
204
+ export interface FanoutData<Data = unknown, Operation = unknown> {
205
+ summary: FanoutSummary;
206
+ targets: readonly CentrsEnvelope<Data, Operation>[];
207
+ }
208
+
209
+ /** Normalize an optional warnings list to a readonly array. */
210
+ export function normalizeWarnings(
211
+ warnings: readonly Warning[] | undefined,
212
+ ): readonly Warning[] {
213
+ return warnings ?? [];
214
+ }
215
+
216
+ /** Normalize an optional tips list to a readonly array. */
217
+ export function normalizeTips(
218
+ tips: readonly Tip[] | undefined,
219
+ ): readonly Tip[] {
220
+ return tips ?? [];
221
+ }
@@ -0,0 +1,469 @@
1
+ /**
2
+ * The centrs error-code catalog: the enumerable source of truth for every
3
+ * `CentrsError` code the codebase can produce.
4
+ *
5
+ * The constitution (`docs/CONSTITUTION.md`, "Error URL scheme") requires one
6
+ * human page per error code at `https://tikoci.github.io/centrs/errors/<code>`
7
+ * and states that a missing page is a centrs bug. This catalog is that list, in
8
+ * code (not generated docs): the per-code pages under `docs/errors/` are
9
+ * generated from it, and `test/unit/error-pages.test.ts` enforces three-way
10
+ * consistency between this catalog, the live `code:` literals in `src/`, and the
11
+ * pages on disk.
12
+ *
13
+ * Contributor contract: when you introduce a new error code, add an entry here
14
+ * AND a `docs/errors/<code>.md` page in the same change, or the drift guard
15
+ * fails. See `.github/instructions/actionable-errors.instructions.md`.
16
+ *
17
+ * Summaries are canonical one-liners describing the fault class — not the
18
+ * interpolated, per-instance `summary` a given throw site builds.
19
+ */
20
+
21
+ import { type CentrsErrorCode, ERROR_DETAILS_BASE_URL } from "../errors.ts";
22
+
23
+ export { ERROR_DETAILS_BASE_URL };
24
+
25
+ export interface ErrorCatalogEntry {
26
+ code: CentrsErrorCode;
27
+ summary: string;
28
+ }
29
+
30
+ /** Every error code centrs can produce, with a canonical one-line summary. */
31
+ export const errorCatalog: readonly ErrorCatalogEntry[] = [
32
+ // auth/*
33
+ {
34
+ code: "auth/failed",
35
+ summary: "RouterOS rejected the stored credentials for the device.",
36
+ },
37
+
38
+ // cdb/*
39
+ {
40
+ code: "cdb/already-exists",
41
+ summary: "A CDB entry with that target already exists.",
42
+ },
43
+ {
44
+ code: "cdb/created",
45
+ summary: "A new CDB file was created at the resolved location.",
46
+ },
47
+ {
48
+ code: "cdb/decrypt-failed",
49
+ summary:
50
+ "The encrypted CDB could not be decrypted (wrong or missing password).",
51
+ },
52
+ {
53
+ code: "cdb/empty-group",
54
+ summary: "The named CDB group contains no records.",
55
+ },
56
+ {
57
+ code: "cdb/invalid-option",
58
+ summary: "A comment kv-soup option carried an invalid value.",
59
+ },
60
+ {
61
+ code: "cdb/invalid-record",
62
+ summary:
63
+ "A device record failed schema validation (blank target or wrong field type).",
64
+ },
65
+ { code: "cdb/not-found", summary: "The CDB file could not be found." },
66
+ {
67
+ code: "cdb/not-found-target",
68
+ summary: "No CDB entry matched the requested target string.",
69
+ },
70
+ {
71
+ code: "cdb/override-applied",
72
+ summary:
73
+ "A comment kv-soup override replaced a resolved setting (informational).",
74
+ },
75
+ { code: "cdb/parse-failed", summary: "The CDB file could not be parsed." },
76
+ {
77
+ code: "cdb/password-not-needed",
78
+ summary:
79
+ "A --cdb-password was supplied for an unencrypted CDB and was ignored.",
80
+ },
81
+ {
82
+ code: "cdb/password-required",
83
+ summary: "The CDB is encrypted; a --cdb-password is required to read it.",
84
+ },
85
+ {
86
+ code: "cdb/reserved-key",
87
+ summary: "Comment kv-soup cannot carry a first-class CDB field.",
88
+ },
89
+ {
90
+ code: "cdb/reserved-option",
91
+ summary: "A reserved comment kv-soup option was rejected.",
92
+ },
93
+ {
94
+ code: "cdb/target-not-registered",
95
+ summary:
96
+ "The target is not on the CDB allowlist. Register it with centrs_devices (op add) first.",
97
+ },
98
+ {
99
+ code: "cdb/unknown-field",
100
+ summary: "An unknown CDB field name was referenced.",
101
+ },
102
+ {
103
+ code: "cdb/unknown-option",
104
+ summary: "An unknown comment kv-soup option was encountered.",
105
+ },
106
+ {
107
+ code: "cdb/write-not-permitted",
108
+ summary:
109
+ "The device's CDB record is mcp=ro; MCP writes are not permitted until it is set to mcp=rw.",
110
+ },
111
+
112
+ // discover/*
113
+ {
114
+ code: "discover/broadcast-unavailable",
115
+ summary:
116
+ "The MNDP listener could not enable UDP broadcast; discovery continued passively without sending refresh probes.",
117
+ },
118
+ { code: "discover/failed", summary: "MNDP discovery failed." },
119
+
120
+ // identity/*
121
+ {
122
+ code: "identity/ambiguous",
123
+ summary: "The target matches more than one CDB entry.",
124
+ },
125
+ {
126
+ code: "identity/no-match",
127
+ summary:
128
+ "The target has no CDB entry matching the requested --match selector.",
129
+ },
130
+
131
+ // input/*
132
+ {
133
+ code: "input/invalid-command",
134
+ summary: "The request shape was invalid (bad or missing arguments).",
135
+ },
136
+ {
137
+ code: "input/invalid-match",
138
+ summary:
139
+ "The --match value is not a supported selector (user=, target=, or a record-type token).",
140
+ },
141
+ {
142
+ code: "input/invalid-path",
143
+ summary:
144
+ "A file path contains a character the transport cannot quote (e.g. a quote or newline).",
145
+ },
146
+ {
147
+ code: "input/invalid-routeros-path",
148
+ summary: "The RouterOS path is malformed (it must be slash-prefixed).",
149
+ },
150
+ {
151
+ code: "input/invalid-target",
152
+ summary: "The target string is invalid for the requested operation.",
153
+ },
154
+ {
155
+ code: "input/local-file-not-found",
156
+ summary: "A local file required for the transfer could not be read.",
157
+ },
158
+ {
159
+ code: "input/mac-address",
160
+ summary: "The value is not a valid 6-octet MAC address.",
161
+ },
162
+ {
163
+ code: "input/max-results-exceeded",
164
+ summary: "Output exceeded the requested byte budget.",
165
+ },
166
+
167
+ // internal/*
168
+ {
169
+ code: "internal/devices-failed",
170
+ summary: "The devices command failed with an unexpected internal error.",
171
+ },
172
+ {
173
+ code: "internal/mcp-start",
174
+ summary: "The MCP server failed to start.",
175
+ },
176
+ {
177
+ code: "internal/mcp-tool",
178
+ summary: "An MCP tool handler failed with an unexpected internal error.",
179
+ },
180
+ {
181
+ code: "internal/native-api-length",
182
+ summary: "A native-API word length could not be encoded.",
183
+ },
184
+ {
185
+ code: "internal/unhandled",
186
+ summary: "An unexpected internal error occurred (this is a centrs bug).",
187
+ },
188
+ {
189
+ code: "internal/unreachable",
190
+ summary: "An unreachable code path was reached (this is a centrs bug).",
191
+ },
192
+
193
+ // mndp/*
194
+ {
195
+ code: "mndp/encode-failed",
196
+ summary: "An MNDP field could not be encoded.",
197
+ },
198
+ {
199
+ code: "mndp/listen-failed",
200
+ summary: "The MNDP UDP listener could not bind.",
201
+ },
202
+ {
203
+ code: "mndp/malformed",
204
+ summary: "A received MNDP datagram could not be decoded.",
205
+ },
206
+
207
+ // routeros/*
208
+ {
209
+ code: "routeros/api-fatal",
210
+ summary: "The RouterOS native API returned a fatal sentence.",
211
+ },
212
+ {
213
+ code: "routeros/api-protocol",
214
+ summary: "A RouterOS native API reply could not be parsed.",
215
+ },
216
+ {
217
+ code: "routeros/api-trap",
218
+ summary:
219
+ "The RouterOS native API returned a !trap with no more specific mapping.",
220
+ },
221
+ {
222
+ code: "routeros/btest-protocol",
223
+ summary: "A bandwidth-test (btest) packet could not be decoded.",
224
+ },
225
+ {
226
+ code: "routeros/btest-too-many-sessions",
227
+ summary:
228
+ "The btest server refused a client because max-sessions is reached.",
229
+ },
230
+ {
231
+ code: "routeros/command-failed",
232
+ summary: "RouterOS reported a command failure.",
233
+ },
234
+ {
235
+ code: "routeros/ec-srp5-protocol",
236
+ summary: "An EC-SRP5 authentication message or key could not be processed.",
237
+ },
238
+ {
239
+ code: "routeros/error",
240
+ summary: "RouterOS returned an error executing the command.",
241
+ },
242
+ {
243
+ code: "routeros/invalid-value",
244
+ summary: "RouterOS rejected the value supplied for an argument.",
245
+ },
246
+ {
247
+ code: "routeros/mac-telnet-error",
248
+ summary: "The device reported a MAC-Telnet error.",
249
+ },
250
+ {
251
+ code: "routeros/mac-telnet-not-ready",
252
+ summary: "Input was sent before the MAC-Telnet session was ready.",
253
+ },
254
+ {
255
+ code: "routeros/mac-telnet-protocol",
256
+ summary: "A MAC-Telnet packet could not be decoded.",
257
+ },
258
+ {
259
+ code: "routeros/mac-telnet-unsupported-auth",
260
+ summary: "The device requested an unsupported MAC-Telnet auth mode.",
261
+ },
262
+ {
263
+ code: "routeros/protocol-not-implemented",
264
+ summary: "The requested protocol is planned but not implemented yet.",
265
+ },
266
+ {
267
+ code: "routeros/request-failed",
268
+ summary: "A RouterOS REST request failed with no more specific mapping.",
269
+ },
270
+ {
271
+ code: "routeros/session-closed",
272
+ summary: "RouterOS closed the session before the request completed.",
273
+ },
274
+ {
275
+ code: "routeros/unknown-attribute",
276
+ summary: "RouterOS does not recognize the supplied attribute.",
277
+ },
278
+ {
279
+ code: "routeros/unknown-path",
280
+ summary: "RouterOS does not recognize the command path.",
281
+ },
282
+ {
283
+ code: "routeros/unsupported-capability",
284
+ summary: "The chosen protocol does not support this command capability.",
285
+ },
286
+
287
+ // settings/*
288
+ {
289
+ code: "settings/invalid-boolean",
290
+ summary: "A boolean-like setting received a non-boolean value.",
291
+ },
292
+ {
293
+ code: "settings/invalid-format",
294
+ summary: "An unsupported output format was requested.",
295
+ },
296
+ {
297
+ code: "settings/invalid-integer",
298
+ summary: "An integer setting received a non-integer or out-of-range value.",
299
+ },
300
+ {
301
+ code: "settings/invalid-timeout",
302
+ summary: "A timeout setting received an invalid value.",
303
+ },
304
+ {
305
+ code: "settings/invalid-via",
306
+ summary: "An unsupported protocol identifier was supplied to --via.",
307
+ },
308
+ {
309
+ code: "settings/unsafe-protocol-blocked",
310
+ summary:
311
+ "A cleartext protocol (e.g. ftp) was requested without the explicit ALLOW_UNSAFE_PROTOCOLS opt-in.",
312
+ },
313
+
314
+ // target/*
315
+ {
316
+ code: "target/mac-not-in-arp",
317
+ summary:
318
+ "The MAC is not in the host ARP cache, so it cannot be resolved to an IP.",
319
+ },
320
+ {
321
+ code: "target/mac-required",
322
+ summary: "The chosen L2 transport (mac-telnet) needs a MAC-address target.",
323
+ },
324
+ {
325
+ code: "target/mac-unresolved",
326
+ summary: "The MAC target could not be resolved to a host.",
327
+ },
328
+ {
329
+ code: "target/unresolved",
330
+ summary: "No host could be resolved for the operation.",
331
+ },
332
+
333
+ // transport/*
334
+ {
335
+ code: "transport/auth-failed",
336
+ summary: "RouterOS rejected the credentials over the transport.",
337
+ },
338
+ {
339
+ code: "transport/auto-method",
340
+ summary:
341
+ "Auto-selection moved the operation to a different method (informational auto-hop).",
342
+ },
343
+ {
344
+ code: "transport/capability-unsupported",
345
+ summary: "The chosen transport cannot perform this kind of operation.",
346
+ },
347
+ {
348
+ code: "transport/checksum-unavailable",
349
+ summary:
350
+ "No file digest is available over the transport, so integrity was verified by size instead.",
351
+ },
352
+ {
353
+ code: "transport/connection-closed",
354
+ summary: "The transport connection was closed before a result.",
355
+ },
356
+ {
357
+ code: "transport/connection-refused",
358
+ summary: "The device refused the connection.",
359
+ },
360
+ {
361
+ code: "transport/dns",
362
+ summary: "The device host name could not be resolved.",
363
+ },
364
+ {
365
+ code: "transport/host-key-mismatch",
366
+ summary: "The device's SSH host key did not match the known key.",
367
+ },
368
+ {
369
+ code: "transport/incomplete-transfer",
370
+ summary:
371
+ "A file transfer settled at fewer bytes than expected (verify mismatch).",
372
+ },
373
+ {
374
+ code: "transport/insecure-trust",
375
+ summary:
376
+ "Peer verification was disabled via --insecure (TLS / SSH host-key checks skipped).",
377
+ },
378
+ {
379
+ code: "transport/local-tool-missing",
380
+ summary:
381
+ "A required local CLI (e.g. the OpenSSH `sftp` client) is not installed or not on PATH.",
382
+ },
383
+ {
384
+ code: "transport/network",
385
+ summary: "A network request to the device failed.",
386
+ },
387
+ {
388
+ code: "transport/timeout",
389
+ summary: "The transport request timed out.",
390
+ },
391
+ {
392
+ code: "transport/tls-certificate",
393
+ summary: "TLS certificate validation failed for the device.",
394
+ },
395
+ {
396
+ code: "transport/unreachable",
397
+ summary: "The device could not be reached over the chosen transport.",
398
+ },
399
+ {
400
+ code: "transport/unsupported-operation",
401
+ summary: "The chosen transport does not support this operation.",
402
+ },
403
+
404
+ // usage/*
405
+ {
406
+ code: "usage/confirmation-required",
407
+ summary:
408
+ "A write-shaped command needs explicit confirmation (pass confirm:true / --yes after review).",
409
+ },
410
+ {
411
+ code: "usage/conflicting-flags",
412
+ summary: "Two or more supplied flags conflict.",
413
+ },
414
+ {
415
+ code: "usage/invalid-concurrency",
416
+ summary: "The --concurrency value must be an integer >= 1.",
417
+ },
418
+ {
419
+ code: "usage/missing-group",
420
+ summary: "The operation requires a non-empty --group value.",
421
+ },
422
+ {
423
+ code: "usage/not-implemented",
424
+ summary:
425
+ "A recognized command form is reserved but not implemented yet (e.g. the interactive `devices edit` editor).",
426
+ },
427
+ {
428
+ code: "usage/target-exists",
429
+ summary:
430
+ "The destination already exists and would be overwritten; pass --force to replace it.",
431
+ },
432
+ {
433
+ code: "usage/timeout-out-of-range",
434
+ summary: "The requested timeout is outside the allowed range.",
435
+ },
436
+
437
+ // validation/*
438
+ {
439
+ code: "validation/not-implemented",
440
+ summary: "The requested validation feature is not implemented yet.",
441
+ },
442
+ {
443
+ code: "validation/option",
444
+ summary: "An unknown option value was supplied.",
445
+ },
446
+ {
447
+ code: "validation/syntax",
448
+ summary: "RouterOS rejected the command syntax during the :parse gate.",
449
+ },
450
+ {
451
+ code: "validation/unknown-attribute",
452
+ summary:
453
+ "An attribute is not valid for the path/verb per /console/inspect.",
454
+ },
455
+ {
456
+ code: "validation/unknown-path",
457
+ summary: "The RouterOS path does not expose the requested command.",
458
+ },
459
+ ];
460
+
461
+ /** Set of all catalog codes, for fast membership checks in tests/resources. */
462
+ export const errorCatalogCodes: ReadonlySet<string> = new Set(
463
+ errorCatalog.map((entry) => entry.code),
464
+ );
465
+
466
+ /** The stable details URL for a code: base URL + the slash-namespaced code. */
467
+ export function detailsUrlForCode(code: string): string {
468
+ return `${ERROR_DETAILS_BASE_URL}${code}`;
469
+ }