Network CLI (netcli) Highlighting
How NetStacks highlights Cisco and Junos config backups in Monaco with the custom netcli grammar, plus the YANG, XML, and JSON format providers.
Overview
When you open a saved device configuration in the NetStacks Workspace editor (Monaco), CLI backups are highlighted with a custom language called netcli. It is a single, vendor-agnostic grammar that covers both major config families:
- Cisco-style — IOS, IOS-XE, IOS-XR, and NX-OS, with
!comments and indentation-scoped stanzas. - Junos-style —
{ }braces,set/deletestatements,#comments, and[ ... ]value lists.
Rather than ship two grammars plus a vendor detector, netcli tokenizes the lexical features the two families share — comments, stanza keywords, IP/CIDR/MAC literals, numbers, strings, and brackets. The result is readable highlighting for any backup without having to tell the editor which vendor wrote it.
netcli is a client-side Monaco language. It needs no download, no Agent, and no network access, and it works in every build — the standalone Terminal and the Controller-served web client alike. The same is true of the YANG, XML, and JSON support described below.
How It Works
NetStacks registers its language features on the Monaco instance once at app startup, after the Monaco environment is configured but before any editor is created. A single bootstrap call wires up YANG, the XML format provider, and netcli. JSON is intentionally left to Monaco's own bundled worker.
Language Registration
At bootstrap the app calls a single registration function that adds every NetStacks-specific language feature to Monaco. For netcli it registers the language id, attaches a Monarch tokenizer, and sets a language configuration (brackets, comments, auto-closing pairs):
// Network CLI — Cisco/Junos config backups
// (no built-in Monaco language, so NetStacks adds one).
monaco.languages.register({
id: 'netcli',
aliases: ['Network CLI', 'netcli'],
});
monaco.languages.setMonarchTokensProvider('netcli', netcliLanguage);
monaco.languages.setLanguageConfiguration('netcli', netcliLanguageConfig);Unlike YANG (which is registered with a .yang file extension), netcli is registered with aliases only. Backups are not files on disk with a recognizable extension, so the editor selects the language by the backup's stored format rather than by filename — see the next section.
The netcli Monarch grammar runs case-insensitively (ignoreCase), so INTERFACE, Interface, and interface are all recognized as the same keyword. YANG, by contrast, is case-sensitive because YANG statement names are.
How Backups Pick a Language
Every stored device config carries a config_format string (for example cli, json, xml). When the Backup History view opens a version, it maps that format to a Monaco language id. CLI backups resolve to netcli:
// Map a backup's config_format to a Monaco language id.
// CLI configs use the custom 'netcli' grammar (Cisco/Junos).
function backupLanguage(format: string): string {
switch (format.toLowerCase()) {
case 'json':
return 'json';
case 'xml':
case 'netconf-xml':
return 'xml';
case 'yaml':
case 'yml':
return 'yaml';
case 'cli':
return 'netcli';
default:
return 'plaintext';
}
}So a backup with config_format: "cli" is highlighted as netcli; xml and netconf-xml both render as XML; structured JSON dumps render as JSON; and anything unrecognized falls back to plaintext rather than guessing wrong.
In Backup History, a backup is considered "structured" when its format is anything other than cli (JSON, XML, YAML, gNMI, and so on). CLI backups are the ones that get netcli highlighting.
What netcli Highlights
The tokenizer recognizes a small, deliberate set of lexical categories that make a raw config easier to scan. The following sub-sections walk through each one, grounded in the actual grammar.
Stanza & Statement Keywords
A curated list of config keywords is highlighted as keywords wherever they appear as an identifier-leading word. The list mixes Junos operative verbs, Cisco operative verbs, and common top-level stanzas from both vendors. The exact set the grammar ships with is:
// Junos operative verbs
set delete deactivate activate rename insert replace
// Cisco operative verbs
no shutdown description enable disable
// common top-level stanzas (both vendors)
interface interfaces router routing-options protocols
policy policy-options policy-map class-map route-map
prefix-list access-list ip ipv6 mpls bgp ospf ospf3
isis rip ldp rsvp vlan vlans vrf bridge-domain
system security firewall snmp snmp-server logging ntp
aaa tacacs radius username user group groups
chassis forwarding-options class-of-service event-options
address-family neighbor network redistribute permit deny
match then from unit family inet inet6 vlan-id
apply-groups import export community as-path route
static next-hop metric preference local-preference
hostname switchport channel-group spanning-tree mtuAny other identifier — an interface name like GigabitEthernet0/1, a policy name, a hostname value — is tokenized as a plain identifier, not a keyword. That keeps structural keywords visually distinct from the values you actually configured.
netcli is a readability aid, not a vendor parser. A fixed, shared keyword list highlights the words that anchor a config stanza across vendors without trying to model every command tree. Words outside the list simply render as identifiers — nothing breaks, they just are not colored as keywords.
Comments
Both vendors' comment conventions are recognized, including Junos inline block annotations:
- Cisco —
!to end of line. - Junos / shell —
#to end of line. - Junos inline annotations —
/* ... */block comments, which may span multiple lines.
! Cisco-style banner divider — highlighted as a comment
interface GigabitEthernet0/1
description uplink ! trailing comment to end of line
# Junos / shell-style comment
set system host-name edge1 # also a comment
/* Junos inline annotation that
spans more than one line */
interfaces {
ge-0/0/0 { /* per-stanza note */
description core;
}
}IP, IPv6, MAC & Number Literals
Address and numeric literals are highlighted as numbers so they jump out against keywords and identifiers. The grammar recognizes, in order:
- IPv4, IPv4 + mask, and CIDR — a dotted quad, optionally followed by a
/prefix(for example10.0.0.1,10.0.0.0/24). A bare mask like255.255.255.0is itself a dotted quad, so it is highlighted too. - IPv6 (loose) — runs of hex groups separated by colons (for example
2001:db8::1). - MAC addresses — hex pairs separated by
:or.(for example00:1a:2b:3c:4d:5eor001a.2b3c.4d5e). - Plain integers — AS numbers, VLAN ids, MTU values, metrics, and so on.
ip address 10.0.0.1 255.255.255.0 ! IPv4 + mask
ip route 0.0.0.0/0 10.0.0.254 ! CIDR + next-hop
ipv6 address 2001:db8:abcd::1/64 ! IPv6 + prefix
mac-address 00:1a:2b:3c:4d:5e ! colon MAC
mac address 001a.2b3c.4d5e ! dotted MAC
router bgp 65001 ! plain integer (AS)
vlan 100 ! plain integer (VLAN id)
mtu 9000The IPv6 and MAC patterns are intentionally loose — they match the common shapes seen in real backups rather than validating every edge case. The goal is to color the address so a human can scan it, not to reject malformed input.
Braces, Brackets & Strings
Structural punctuation common to Junos configs is tokenized so Monaco can bracket-match and so value lists read clearly:
{}— Junos stanza braces, matched as bracket pairs.[]— Junos value lists, also matched as bracket pairs.;— the Junos statement terminator, tokenized as a delimiter.- Double-quoted strings — with escape handling inside the string; an unterminated quote is flagged as an invalid string.
policy-options {
policy-statement export-statics {
term static {
from protocol [ static aggregate ]; /* value list in [ ] */
then accept; /* ; terminates statement */
}
}
}
system {
login {
message "Authorized access only"; /* quoted string */
}
}Worked Examples
The same netcli grammar highlights both vendor families. These are representative backups you can paste into the Workspace editor (or view from Backup History) to see the highlighting.
Cisco IOS / IOS-XE / NX-OS
Cisco configs use ! comments and indentation-scoped stanzas. Keywords like interface, router, bgp, no, and shutdown are colored as keywords; addresses and AS numbers as literals; everything else (interface and neighbor descriptions, process names) as identifiers.
! edge1 — running config backup (config_format: cli)
hostname edge1
!
interface GigabitEthernet0/1
description uplink-to-core
ip address 10.0.0.1 255.255.255.0
ipv6 address 2001:db8::1/64
no shutdown
!
interface Vlan100
ip address 192.168.100.1 255.255.255.0
!
router bgp 65001
neighbor 10.0.0.2 remote-as 65002
address-family ipv4
network 192.168.100.0 mask 255.255.255.0
redistribute static
!
ip route 0.0.0.0/0 10.0.0.254
access-list 10 permit 10.0.0.0 0.0.0.255Junos (brace and set styles)
Junos backups come in two shapes — the nested brace format and the flat set format. netcli handles both. In the brace format, { } match as brackets and ; reads as a delimiter; in the set format, each line begins with an operative verb keyword.
Brace format
# srx1 — show configuration (config_format: cli)
interfaces {
ge-0/0/0 {
unit 0 {
family inet {
address 10.0.0.1/24;
}
family inet6 {
address 2001:db8::1/64;
}
}
}
}
protocols {
bgp {
group core {
neighbor 10.0.0.2 {
peer-as 65002;
}
}
}
}
routing-options {
static {
route 0.0.0.0/0 next-hop 10.0.0.254;
}
}Set format
# srx1 — show configuration | display set
set system host-name srx1
set interfaces ge-0/0/0 unit 0 family inet address 10.0.0.1/24
set protocols bgp group core neighbor 10.0.0.2 peer-as 65002
set routing-options static route 0.0.0.0/0 next-hop 10.0.0.254
delete interfaces ge-0/0/1 disable
deactivate protocols ospfYANG Highlighting & Formatter
Alongside netcli, NetStacks registers a YANG language (id yang, extension .yang) with its own Monarch tokenizer and a document format provider. The tokenizer highlights YANG statement keywords (module, container, leaf, list, rpc, and many more), and gives the argument of a type statement special treatment so built-in types (string, uint32, boolean, enumeration, and the rest) are tokenized as types.
The YANG formatter is an indentation-based pretty-printer: it walks the document line by line, tracks nesting depth from { and }, and re-indents with two spaces per level. Quoted strings and block comments are passed through verbatim so their contents are never reformatted, and it does not try to expand one-line stanzas onto multiple lines. Run Format Document to normalize nesting.
module example-system {
namespace "urn:example:system";
prefix sys;
container system {
leaf hostname {
type string;
}
leaf-list name-server {
type inet:ipv4-address;
}
list user {
key name;
leaf name { type string; }
leaf uid { type uint32; }
}
}
}YANG and XML both register a document formatting provider, so the editor's Format Document action works on them. The YANG formatter is the custom indent printer described here; XML uses the provider described next.
XML Format Provider
XML highlighting comes from Monaco's built-in XML language; NetStacks adds a document format provider on top of it so you can pretty-print NETCONF payloads and config exports. The provider runs an XML formatter configured with two-space indentation, content collapsing, and newline line separators. If the input is not well-formed, the formatter returns it unchanged rather than throwing — so a malformed payload is left as-is instead of corrupting it.
<config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><interfaces><interface><name>eth0</name></interface></interfaces></config>After running Format Document, the same payload is indented two spaces per level:
<config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<interfaces>
<interface>
<name>eth0</name>
</interface>
</interfaces>
</config>Backups stored with the format netconf-xml (as well as plain xml) are highlighted as XML, so NETCONF configuration responses get the same highlighting and Format Document support as any other XML document.
JSON (Monaco built-in)
JSON is intentionally not given a NetStacks provider. Monaco's bundled json.worker already supplies syntax highlighting, schema validation, and Format Document, so NetStacks leaves JSON to the worker rather than duplicating it. Structured backups stored as json (for example gNMI or REST dumps) are highlighted by that worker.
{
"interfaces": {
"interface": [
{
"name": "GigabitEthernet0/1",
"config": { "enabled": true, "mtu": 9000 },
"ipv4": { "address": "10.0.0.1", "prefix-length": 24 }
}
]
}
}Editor Behavior (Brackets, Comments)
Beyond coloring, netcli ships a language configuration that drives editor conveniences. Because the grammar runs inside Monaco, these behaviors come for free:
- Line comment — toggling a line comment uses
!(the Cisco convention); the block comment pair is/* */. - Bracket matching —
{ }and[ ]are registered as bracket pairs, so Monaco highlights the matching brace and lets you jump between them. - Auto-closing & surrounding pairs — typing an opening
{,[, or"auto-inserts its closer, and selecting text then typing one of those wraps the selection.
In a netcli buffer the comment-toggle shortcut inserts !. In a YANG buffer it inserts //, because YANG's line comment is // (with /* */ for blocks). Each language sets its own configuration.
What It Is Not
It is worth being precise about scope so expectations are right:
- Not a parser or validator.
netcliis a lexical highlighter. It does not build a command tree, does not know whether a command is valid for a given platform, and reports no diagnostics or errors. - Not vendor detection. There is one grammar for both families; the editor does not try to decide "this is IOS" vs "this is Junos." The shared lexical features are highlighted either way.
- Not exhaustive keyword coverage. Only the curated keyword list is colored as keywords. Commands outside that list render as identifiers — readable, just not keyword-colored.
- No completion or hover. Those are LSP features.
netcliis client-side highlighting only; for diagnostics and completion in other languages, see the LSP system.
Editing a backup buffer in the Workspace is for exploration and readability. Highlighting a config does not push it to a device. Config change control and push-to-device are handled separately — see Config Snapshots and Diff & History.
Q&A
- Q: What is the netcli language?
- A: A custom, vendor-agnostic Monaco grammar that highlights Cisco and Junos config backups. It colors comments, a curated set of stanza/statement keywords, IP/CIDR/MAC and number literals, strings, and Junos braces/brackets.
- Q: Does it work for both Cisco and Junos?
- A: Yes. There is one grammar covering both families — Cisco-style (
!comments, indentation stanzas) and Junos-style ({ }braces,set/deleteverbs,#comments,[ ... ]value lists). No vendor detection is needed. - Q: How does a backup get netcli highlighting?
- A: Backups carry a
config_format. When the value iscli, the editor maps it to thenetclilanguage.jsonmaps to JSON,xmlandnetconf-xmlto XML,yaml/ymlto YAML, and anything else to plain text. - Q: Does netcli validate my config?
- A: No. It is a lexical readability aid, not a parser or validator. It does not check command validity or produce diagnostics.
- Q: Is it available in every build?
- A: Yes.
netcli, YANG highlighting/formatting, the XML format provider, and Monaco's JSON support are all client-side and run with no download or network access — in the standalone Terminal and the Controller-served web client. - Q: Why does NetStacks not add its own JSON support?
- A: Monaco's bundled
json.workeralready provides JSON highlighting, schema validation, and Format Document, so there is nothing to duplicate. - Q: How do I pretty-print a YANG model or NETCONF XML?
- A: Run Format Document. YANG uses a custom indent-based pretty-printer (two spaces per level, strings and block comments untouched); XML uses a format provider that indents two spaces and safely returns malformed input unchanged.
- Q: Can I add highlighting for another vendor's syntax?
- A:
netcliitself is a fixed grammar, but the editor supports a pluggable LSP system for richer per-language features. See Language Support (LSP) for adding your own language server.
Related
- Language Support (LSP) — the full picture of client-side highlighting and pluggable language servers.
- Code Editor — the Monaco-based editor these grammars and formatters run in.
- Workspace Overview — how the editor, file explorer, and Git fit together.
- Config Snapshots — how CLI and structured backups are captured and stored.
- Config Diff & History — browse, compare, and view past config versions.
- Templates: Jinja2 Syntax — author config templates with editor support.