clijson 0.2.0__tar.gz

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 (50) hide show
  1. clijson-0.2.0/LICENSE +21 -0
  2. clijson-0.2.0/PKG-INFO +390 -0
  3. clijson-0.2.0/README.md +340 -0
  4. clijson-0.2.0/pyproject.toml +194 -0
  5. clijson-0.2.0/pyproject.toml.orig +139 -0
  6. clijson-0.2.0/src/clijson/__init__.py +50 -0
  7. clijson-0.2.0/src/clijson/__main__.py +5 -0
  8. clijson-0.2.0/src/clijson/api.py +455 -0
  9. clijson-0.2.0/src/clijson/cli.py +417 -0
  10. clijson-0.2.0/src/clijson/commands.py +312 -0
  11. clijson-0.2.0/src/clijson/diff.py +177 -0
  12. clijson-0.2.0/src/clijson/engines/__init__.py +1 -0
  13. clijson-0.2.0/src/clijson/engines/config.py +247 -0
  14. clijson-0.2.0/src/clijson/engines/external.py +77 -0
  15. clijson-0.2.0/src/clijson/engines/generic.py +339 -0
  16. clijson-0.2.0/src/clijson/engines/structured.py +143 -0
  17. clijson-0.2.0/src/clijson/exceptions.py +47 -0
  18. clijson-0.2.0/src/clijson/live.py +114 -0
  19. clijson-0.2.0/src/clijson/mcp_server.py +273 -0
  20. clijson-0.2.0/src/clijson/models.py +476 -0
  21. clijson-0.2.0/src/clijson/parsers/__init__.py +1 -0
  22. clijson-0.2.0/src/clijson/parsers/iosxr/__init__.py +1 -0
  23. clijson-0.2.0/src/clijson/parsers/iosxr/bgp_rib.py +269 -0
  24. clijson-0.2.0/src/clijson/parsers/iosxr/config.py +45 -0
  25. clijson-0.2.0/src/clijson/parsers/iosxr/extra.py +547 -0
  26. clijson-0.2.0/src/clijson/parsers/iosxr/interfaces.py +452 -0
  27. clijson-0.2.0/src/clijson/parsers/iosxr/routing.py +872 -0
  28. clijson-0.2.0/src/clijson/parsers/iosxr/services.py +878 -0
  29. clijson-0.2.0/src/clijson/parsers/iosxr/system.py +440 -0
  30. clijson-0.2.0/src/clijson/parsers/junos/__init__.py +1 -0
  31. clijson-0.2.0/src/clijson/parsers/junos/common.py +35 -0
  32. clijson-0.2.0/src/clijson/parsers/junos/config.py +16 -0
  33. clijson-0.2.0/src/clijson/parsers/junos/extra.py +333 -0
  34. clijson-0.2.0/src/clijson/parsers/junos/interfaces.py +447 -0
  35. clijson-0.2.0/src/clijson/parsers/junos/routing.py +939 -0
  36. clijson-0.2.0/src/clijson/parsers/junos/services.py +346 -0
  37. clijson-0.2.0/src/clijson/parsers/junos/system.py +583 -0
  38. clijson-0.2.0/src/clijson/parsers/vrp/__init__.py +1 -0
  39. clijson-0.2.0/src/clijson/parsers/vrp/config.py +23 -0
  40. clijson-0.2.0/src/clijson/parsers/vrp/extra.py +312 -0
  41. clijson-0.2.0/src/clijson/parsers/vrp/interfaces.py +570 -0
  42. clijson-0.2.0/src/clijson/parsers/vrp/routing.py +686 -0
  43. clijson-0.2.0/src/clijson/parsers/vrp/services.py +325 -0
  44. clijson-0.2.0/src/clijson/parsers/vrp/system.py +457 -0
  45. clijson-0.2.0/src/clijson/platforms.py +278 -0
  46. clijson-0.2.0/src/clijson/py.typed +0 -0
  47. clijson-0.2.0/src/clijson/registry.py +204 -0
  48. clijson-0.2.0/src/clijson/result.py +147 -0
  49. clijson-0.2.0/src/clijson/server.py +88 -0
  50. clijson-0.2.0/src/clijson/textutils.py +378 -0
clijson-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shady Magdy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
clijson-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,390 @@
1
+ Metadata-Version: 2.4
2
+ Name: clijson
3
+ Version: 0.2.0
4
+ Summary: Turn show/display command output from Cisco IOS XR, Juniper Junos and Huawei VRP routers into JSON.
5
+ Keywords: network,automation,parser,cli,json,cisco,iosxr,juniper,junos,huawei,vrp,netdevops
6
+ Author: Shady Magdy
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Telecommunications Industry
11
+ Classifier: Intended Audience :: System Administrators
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: System :: Networking
22
+ Classifier: Topic :: Text Processing
23
+ Classifier: Typing :: Typed
24
+ Requires-Dist: pyyaml>=5.4 ; extra == 'all'
25
+ Requires-Dist: rich>=12 ; extra == 'all'
26
+ Requires-Dist: ntc-templates>=4 ; extra == 'all'
27
+ Requires-Dist: mcp>=2 ; extra == 'all'
28
+ Requires-Dist: pyats-topology ; extra == 'genie'
29
+ Requires-Dist: genie-libs-parser ; extra == 'genie'
30
+ Requires-Dist: mcp>=2 ; extra == 'mcp'
31
+ Requires-Dist: netmiko>=4 ; extra == 'netmiko'
32
+ Requires-Dist: ntc-templates>=4 ; extra == 'ntc'
33
+ Requires-Dist: rich>=12 ; extra == 'pretty'
34
+ Requires-Dist: scrapli>=2023.7.30 ; extra == 'scrapli'
35
+ Requires-Dist: pyyaml>=5.4 ; extra == 'yaml'
36
+ Requires-Python: >=3.10
37
+ Project-URL: Homepage, https://github.com/shadymagdy/network-cli-parser
38
+ Project-URL: Source, https://github.com/shadymagdy/network-cli-parser
39
+ Project-URL: Issues, https://github.com/shadymagdy/network-cli-parser/issues
40
+ Project-URL: Changelog, https://github.com/shadymagdy/network-cli-parser/blob/main/CHANGELOG.md
41
+ Provides-Extra: all
42
+ Provides-Extra: genie
43
+ Provides-Extra: mcp
44
+ Provides-Extra: netmiko
45
+ Provides-Extra: ntc
46
+ Provides-Extra: pretty
47
+ Provides-Extra: scrapli
48
+ Provides-Extra: yaml
49
+ Description-Content-Type: text/markdown
50
+
51
+ # clijson — router CLI output → JSON
52
+
53
+ [![CI](https://github.com/shadymagdy/network-cli-parser/actions/workflows/ci.yml/badge.svg)](https://github.com/shadymagdy/network-cli-parser/actions/workflows/ci.yml)
54
+ [![Docs](https://img.shields.io/badge/docs-zensical-indigo)](https://shadymagdy.github.io/network-cli-parser/)
55
+ [![Python](https://img.shields.io/badge/python-3.10%20%E2%80%93%203.14-blue)](pyproject.toml)
56
+ [![Typed](https://img.shields.io/badge/typing-mypy%20strict-informational)](pyproject.toml)
57
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
58
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
59
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
60
+
61
+ 📖 **Documentation: <https://shadymagdy.github.io/network-cli-parser/>**
62
+
63
+ **clijson** (repo: `network-cli-parser`) turns the output of `show` / `display` commands from
64
+ **Cisco IOS XR**, **Juniper Junos** and **Huawei VRP** routers into clean, predictable JSON.
65
+ It has **no runtime dependencies**.
66
+
67
+ ```python
68
+ >>> import clijson
69
+ >>> r = clijson.parse(open("pe1.log").read()) # platform and command are auto-detected
70
+ >>> r.parser, r.platform
71
+ ('iosxr.show_bgp_summary', 'iosxr')
72
+ >>> r.data["neighbors"][0]
73
+ {'neighbor': '10.255.0.2', 'instance': 'default', 'vrf': 'default', 'address_family': 'ipv4 unicast',
74
+ 'remote_as': 65000, 'messages_received': 12011, ..., 'up_down': '1w2d', 'state': 'Established',
75
+ 'prefixes_received': 512}
76
+ ```
77
+
78
+ ## Why clijson
79
+
80
+ | | |
81
+ |---|---|
82
+ | **Works on any command** | 161 dedicated parsers (237 command patterns). Anything else goes through a **generic engine** that finds tables, `key: value` pairs and indented sections in *any* output, so you always get JSON back. |
83
+ | **Understands you like a router does** | `sh ip int br`, `dis int br` and `show interfaces brief` all resolve. Huawei accepts `show` as well as `display`. Parameters such as a VRF or interface are captured. |
84
+ | **Zero configuration** | The platform is detected from the prompt, the command verb or fingerprints in the output. If those don't settle it, **trial parsing** lets each vendor's parser try and keeps the one that understands the output. Echoed prompts, `--More--` pagers, ANSI codes, timestamps and `{master}` lines are removed. |
85
+ | **One model for all vendors** | `normalize=True` adds a **vendor-neutral view** for 18 common concepts (BGP peers, interfaces, routes, LLDP, OSPF/IS-IS/LDP, ARP/ND, BFD, LAG, VRFs, …), so one script can handle all three vendors. |
86
+ | **Structured output is native** | Junos `| display json` / `| display xml` output is recognised and flattened into clean snake_case JSON. |
87
+ | **Config as data** | `show running-config`, `show configuration` (curly braces or `| display set`) and `display current-configuration` become nested trees. |
88
+ | **Whole sessions** | Paste a terminal log with 20 commands and get 20 results (`parse_session`). |
89
+ | **Pre/post change checks** | `clijson.diff(before, after)` (or `clijson diff pre.txt post.txt`) matches records by their natural key (neighbor, interface, prefix, …) and ignores counters and timers by default, so only real changes are reported. |
90
+ | **Clear about failures** | Device errors (`% Invalid input`, `syntax error`, `Error: Unrecognized command`) come back as `engine="device-error"` with the message, instead of garbage data. |
91
+ | **Stands on giants' shoulders** | If you have [ntc-templates](https://github.com/networktocode/ntc-templates) or [Cisco Genie](https://github.com/CiscoTestAutomation/genieparser) installed, their templates become extra fallback engines automatically. |
92
+ | **Tells you how it got the answer** | Every result carries `engine`, `parser`, `confidence`, `warnings` (for example "output was filtered by `| include`") and metadata such as the hostname and timestamp. |
93
+ | **Tested on real output** | 238 regression fixtures: 217 captured from real ASR9K, NCS5500, 8000, CRS, XRv, MX, PTX, QFX, EX, SRX, NE40E, CX600, ATN, CE, S and AR devices, plus 21 written from vendor documentation formats. |
94
+ | **Easy to use from any language** | Python API, a full CLI (`clijson`), and a zero-dependency HTTP API (`clijson serve`). |
95
+ | **Built for AI assistants** | `clijson mcp` is a [Model Context Protocol](https://modelcontextprotocol.io) server. Claude, Cursor, VS Code and any agent can call clijson as a tool and reason over exact, schema'd data. |
96
+
97
+ ## What it looks like
98
+
99
+ Input: a Huawei capture pasted straight from the terminal, prompt included.
100
+
101
+ ```text
102
+ <PE1>display interface brief
103
+ PHY: Physical
104
+ *down: administratively down
105
+ Interface PHY Protocol InUti OutUti inErrors outErrors
106
+ Eth-Trunk1 up up 0.01% 0.38% 0 0
107
+ GigabitEthernet0/0/1 up up 0.01% 0.40% 0 0
108
+ GigabitEthernet0/0/2 up up 0% 0.36% 0 0
109
+ GigabitEthernet0/0/3 *down down 0% 0% 0 0
110
+ LoopBack0 up up(s) 0% 0% 0 0
111
+ <PE1>
112
+ ```
113
+
114
+ `clijson pe1.txt` detects Huawei VRP and `display interface brief`, nests the trunk members and decodes the
115
+ flags. Output is abbreviated here:
116
+
117
+ ```json
118
+ [
119
+ {"interface": "Eth-Trunk1", "physical": "up", "protocol": "up", "input_utilization": 0.01, "output_utilization": 0.38,
120
+ "input_errors": 0, "output_errors": 0,
121
+ "members": [{"interface": "GigabitEthernet0/0/1", "physical": "up", "protocol": "up", ...},
122
+ {"interface": "GigabitEthernet0/0/2", ...}]},
123
+ {"interface": "GigabitEthernet0/0/3", "physical": "down", "protocol": "down", "admin_down": true, ...},
124
+ {"interface": "LoopBack0", "physical": "up", "protocol": "up", "flags": ["spoofing"], ...}
125
+ ]
126
+ ```
127
+
128
+ `clijson pe1.txt -n` gives the vendor-neutral view. It has the same shape for IOS XR and Junos:
129
+
130
+ ```json
131
+ [{"name": "Eth-Trunk1", "admin_status": "up", "oper_status": "up", "ip_address": null, "vrf": null, "description": null},
132
+ {"name": "GigabitEthernet0/0/3", "admin_status": "admin-down", "oper_status": "down", ...}, ...]
133
+ ```
134
+
135
+ ## Install
136
+
137
+ ```bash
138
+ pip install clijson # core, no dependencies
139
+ pip install "clijson[all]" # + YAML output, pretty tables, ntc-templates fallback
140
+ pip install "clijson[netmiko]" # + collect from live devices (or [scrapli])
141
+ pip install "clijson[mcp]" # + MCP server for AI assistants
142
+ ```
143
+
144
+ With [uv](https://docs.astral.sh/uv/):
145
+
146
+ ```bash
147
+ uv add clijson # add to your project
148
+ uvx clijson parse show_bgp.txt # run the CLI without installing anything
149
+ ```
150
+
151
+ Requires Python 3.10 or newer.
152
+
153
+ ## Quick start
154
+
155
+ ### Python
156
+
157
+ ```python
158
+ import clijson
159
+
160
+ # 1. Tell it everything...
161
+ r = clijson.parse(output, "show ipv4 interface brief", platform="iosxr")
162
+
163
+ # 2. ...or nothing: prompt lines like "RP/0/RP0/CPU0:PE1#show ipv4 int br" are recognised
164
+ r = clijson.parse(output)
165
+
166
+ r.data # structured data (dict / list)
167
+ r.to_json() # JSON string
168
+ r.to_yaml() # needs PyYAML
169
+ r.to_dict() # data + provenance (engine, parser, confidence, warnings, metadata)
170
+ r.records() # the most table-like view as flat rows
171
+ r.to_dataframe() # the same rows as a pandas DataFrame (needs pandas)
172
+ ```
173
+
174
+ **Pre/post maintenance check**:
175
+
176
+ ```python
177
+ before = clijson.parse(pre_capture, "show bgp summary", "iosxr", normalize=True)
178
+ after = clijson.parse(post_capture, "show bgp summary", "iosxr", normalize=True)
179
+ for change in clijson.diff(before, after):
180
+ print(change)
181
+ # ~ [neighbor=10.255.0.2].state: 'Established' -> 'Idle'
182
+ # - [neighbor=10.255.0.4]: {...}
183
+ # + [neighbor=10.255.0.5]: {...}
184
+ ```
185
+
186
+ **Same code for every vendor** with the normalized view:
187
+
188
+ ```python
189
+ jobs = [("pe1-xr.txt", "show bgp summary", "iosxr"),
190
+ ("pe2-mx.txt", "show bgp summary", "junos"),
191
+ ("pe3-ne.txt", "display bgp peer", "vrp")]
192
+
193
+ for path, cmd, platform in jobs:
194
+ r = clijson.parse(open(path).read(), cmd, platform, normalize=True)
195
+ for peer in r.normalized:
196
+ if not peer["established"]:
197
+ print(f"{path}: {peer['neighbor']} AS{peer['remote_as']} is {peer['state']}")
198
+ ```
199
+
200
+ **A whole terminal session** (PuTTY/SecureCRT/`script` log):
201
+
202
+ ```python
203
+ for r in clijson.parse_session(open("maintenance-window.log").read()):
204
+ print(r.metadata.get("hostname"), r.command, r.parser)
205
+ ```
206
+
207
+ **Live devices** (via scrapli or netmiko). You can try it against containerlab XRd / cRPD / vJunos / VRP images:
208
+
209
+ ```python
210
+ from clijson.live import collect
211
+ results = collect("10.0.0.1", "junos", ["show version", "show bgp summary"],
212
+ username="lab", password="lab123", normalize=True)
213
+ ```
214
+
215
+ ### Command line
216
+
217
+ ```bash
218
+ clijson parse show_bgp.txt -p iosxr -c "show bgp summary"
219
+ ssh mx1 "show interfaces terse" | clijson parse -p junos -c "show interfaces terse"
220
+ clijson session.log # every command in a log (shorthand for `parse`)
221
+ clijson parse out.txt -c "dis bgp peer" -n -f table # normalized, as a table
222
+ clijson parse out.txt -m # include engine/parser/confidence/warnings
223
+ clijson diff pre.txt post.txt -c "show bgp summary" # what changed? (exit code 1 if anything did)
224
+ clijson commands -p vrp --search lldp # what is supported?
225
+ clijson detect mystery.txt # which OS produced this?
226
+ clijson schema bgp.summary # JSON Schema of a normalized model
227
+ clijson run 10.0.0.1 -p iosxr -c "show version" -c "show bgp summary" -u admin
228
+ clijson serve --port 8080 # HTTP API for other languages/tools
229
+ clijson mcp # MCP server for AI assistants
230
+ ```
231
+
232
+ ### HTTP API
233
+
234
+ ```bash
235
+ clijson serve --port 8080 &
236
+ curl -s localhost:8080/parse -d '{"platform":"vrp","command":"display interface brief","output":"...","normalize":true}'
237
+ curl -s "localhost:8080/commands?platform=junos"
238
+ ```
239
+
240
+ ### AI assistants (MCP)
241
+
242
+ ```bash
243
+ claude mcp add clijson -- uvx --from "clijson[mcp]" clijson mcp # Claude Code
244
+ ```
245
+
246
+ For Claude Desktop, Cursor or VS Code, add the same command (`uvx --from clijson[mcp] clijson mcp`) to the
247
+ client's MCP config. The assistant gets read-only tools: `parse_output`, `parse_session`, `detect_platform`,
248
+ `diff_outputs`, `list_commands` and `get_model_schema`. Setup for each client and the HTTP transport are
249
+ covered in **[docs/mcp.md](docs/mcp.md)**.
250
+
251
+ ## How it works
252
+
253
+ ```mermaid
254
+ flowchart LR
255
+ A[raw text] --> B[clean<br/>ANSI, pagers, CRLF,<br/>indentation]
256
+ B --> C[prompt & echo<br/>extraction<br/>host, command, timestamp]
257
+ C --> D{platform?}
258
+ D -- given / prompt / verb / fingerprints --> E
259
+ D -- still unknown --> T[trial parse<br/>every vendor]
260
+ T --> E{output format}
261
+ E -- JSON / XML --> S[structured engine]
262
+ E -- text --> R[command grammar<br/>abbreviation-aware<br/>resolution]
263
+ R --> N[native parser]
264
+ N -. failed / missing .-> X[ntc-templates / Genie<br/>if installed]
265
+ X -. missing .-> G[generic engine<br/>tables · key/value · sections]
266
+ N --> M[normalize<br/>vendor-neutral model]
267
+ S & N & X & G --> O[ParseResult<br/>data · engine · parser ·<br/>confidence · warnings]
268
+ ```
269
+
270
+ * **Command grammar.** Parsers declare what they understand using the notation from vendor documentation:
271
+ `show bgp [instance <instance>] [vrf (all|<vrf>)] [<afi> [<safi>]] summary`.
272
+ Typed commands are scored against every pattern. Exact keywords beat abbreviations and abbreviations beat
273
+ parameters, so `show interfaces brief` never gets mistaken for `show interfaces <interface>`.
274
+ * **Engines** are tried in order: `native → ntc → genie → generic`. Optional engines are skipped if they
275
+ aren't installed. Use `engines=[...]` to choose your own order, or `strict=True` to accept only a dedicated parser.
276
+ * **Provenance.** Each result has a `confidence` score: 1.0 for native and structured output, 0.9 for
277
+ ntc/Genie, 0.4–0.6 for generic. Automation can decide how much to trust a result.
278
+
279
+ More detail: [docs/architecture.md](docs/architecture.md).
280
+
281
+ ## Supported platforms and commands
282
+
283
+ | Platform | Aliases (any of these work) | Dedicated parsers |
284
+ |---|---|---:|
285
+ | Cisco IOS XR (ASR9K, NCS 540/5500/5700, 8000, CRS, XRv 9000, XRd) | `iosxr`, `xr`, `cisco_xr`, `ios-xr`, … | 65 |
286
+ | Juniper Junos / Junos Evolved (MX, PTX, ACX, QFX, EX, SRX, vMX, cRPD) | `junos`, `juniper`, `juniper_junos`, `evo`, … | 48 |
287
+ | Huawei VRP (NE40E/NE8000, CX600, ATN, CE, S, AR) | `vrp`, `huawei`, `huawei_vrp`, `vrpv8`, … | 48 |
288
+
289
+ They cover the commands you run every day: version and inventory, platform and RE/FPC state, CPU, memory,
290
+ power, fans and temperature, interfaces (brief, detail, description, counters, optics/DOM), LAG/LACP, IPv4/IPv6
291
+ addressing, ARP/ND and MAC tables, VLANs, LLDP/CDP, RIB (brief and detail/extensive), BGP (summary, neighbor detail,
292
+ advertised/received routes and BGP RIB, including VRF, instance and address family), OSPF/OSPFv3, IS-IS, MPLS LDP,
293
+ RSVP-TE tunnels and LSPs, LFIB, BFD, HSRP/VRRP, PIM, VRFs and VPN instances, L2VPN xconnects and bridge domains,
294
+ EVPN, firewall filters, ACLs, SRX cluster and policies, NTP, users, alarms, logging, licenses, file systems,
295
+ commit history, startup/patch info and running configuration.
296
+
297
+ The full, generated list is in **[docs/commands.md](docs/commands.md)**. You can also run `clijson commands`.
298
+
299
+ ## Normalized models
300
+
301
+ With `normalize=True`, commands that map to a common concept return records with **exactly** these fields on
302
+ every vendor:
303
+
304
+ | Model | Fields |
305
+ |---|---|
306
+ | `system.version` | hostname, vendor, os, version, model, serial_number, uptime, uptime_seconds |
307
+ | `interfaces.brief` | name, admin_status, oper_status, ip_address, vrf, description |
308
+ | `interfaces.detail` | name, admin_status, oper_status, description, mac_address, mtu, bandwidth_kbps, ipv4_addresses, input/output rate & packets & errors |
309
+ | `bgp.summary` | neighbor, remote_as, state, established, uptime, uptime_seconds, prefixes_received, vrf, address_family |
310
+ | `routes` | prefix, protocol, next_hops, distance, metric, vrf, age |
311
+ | `lldp.neighbors` | local_interface, neighbor, neighbor_interface, chassis_id, capabilities, ttl |
312
+ | `ospf.neighbors`, `isis.adjacency`, `ldp.neighbors`, `bfd.sessions`, `arp`, `ipv6.neighbors`, `mac.table`, `lag`, `vrfs`, `inventory`, `cpu`, `interfaces.description` | see [docs/models.md](docs/models.md) |
313
+
314
+ Normalized values are standardised too. Statuses become `up` / `down` / `admin-down`, MACs become
315
+ `aa:bb:cc:dd:ee:ff`, and uptimes, ages and timers are also given in seconds.
316
+
317
+ **Typed and schema'd.** Every model is a `TypedDict` (`clijson.models.BgpNeighbor`, `Route`, `Interface`, ...), so
318
+ editors autocomplete fields and mypy/pyright check your code. Every model also has a JSON Schema (draft 2020-12)
319
+ for consumers in other languages, API contracts or data pipelines:
320
+
321
+ ```python
322
+ from typing import cast
323
+ from clijson.models import BgpNeighbor, json_schema, validate
324
+
325
+ peers = cast(list[BgpNeighbor], clijson.parse(text, "show bgp summary", "iosxr", normalize=True).normalized)
326
+ json_schema("bgp.summary") # dict, ready for json.dump / OpenAPI / pydantic / jsonschema
327
+ validate("bgp.summary", peers) # [] when the data matches the model
328
+ ```
329
+
330
+ ```bash
331
+ clijson schema # list the models
332
+ clijson schema routes > routes.schema.json # print one
333
+ clijson schema --out schemas/ # export all of them
334
+ clijson parse out.txt -c "show arp" -n | clijson schema arp --check - # validate output in CI
335
+ ```
336
+
337
+ The generated schemas are also committed under [`schemas/`](schemas/).
338
+
339
+ ## Extending
340
+
341
+ Adding a parser takes a decorator and a method. Run `python scripts/fixture.py add` to add a regression test for it:
342
+
343
+ ```python
344
+ from typing import Any
345
+
346
+ from clijson import Parser, register
347
+ from clijson.textutils import match_lines
348
+
349
+ @register("iosxr", "show hsrp [<interface>] brief", intent=None)
350
+ class ShowHsrpBrief(Parser):
351
+ """HSRP groups, state and virtual IP."""
352
+
353
+ def parse(self, text: str) -> list[dict[str, Any]]:
354
+ return [m.groupdict() for m in match_lines(
355
+ r"^\s*(?P<interface>\S+)\s+(?P<group>\d+)\s+(?P<priority>\d+)\s+(?P<state>\w+)\s+(?P<vip>\S+)", text)]
356
+ ```
357
+
358
+ Third-party packages can ship parsers through the `clijson.parsers` entry point. See
359
+ [docs/writing-parsers.md](docs/writing-parsers.md).
360
+
361
+ ## Testing against real routers
362
+
363
+ `lab/` has a [containerlab](https://containerlab.dev) topology with Cisco XRd, Juniper vJunos/cRPD and Huawei
364
+ VRP. `scripts/harvest.py` runs every supported command on each device (live or emulated), saves the raw output
365
+ and reports how much of it parsed natively. This is how new OS releases get checked. See
366
+ [lab/README.md](lab/README.md).
367
+
368
+ ## Development
369
+
370
+ The project is managed with [uv](https://docs.astral.sh/uv/) (`uv.lock` pins every tool):
371
+
372
+ ```bash
373
+ uv sync # create .venv with the dev dependency group
374
+ uv run pre-commit install # ruff lint + format on every commit
375
+ uv run pytest # ~1100 tests incl. 238 fixtures
376
+ uv run ruff check . && uv run ruff format .
377
+ uv run mypy # strict type check
378
+ uv run scripts/fixture.py check # or `update` after an intentional parser change
379
+ uv run scripts/gen_docs.py # refresh docs/commands.md, docs/models.md and schemas/
380
+ uv run --group docs zensical serve # preview the documentation site
381
+ ```
382
+
383
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
384
+
385
+ ## License
386
+
387
+ MIT. Some regression fixtures were taken from the Apache-2.0 licensed
388
+ [ntc-templates](https://github.com/networktocode/ntc-templates) and
389
+ [genieparser](https://github.com/CiscoTestAutomation/genieparser) projects. See
390
+ [tests/fixtures/NOTICE.md](tests/fixtures/NOTICE.md).